@famgia/omnify-ai-guides 2.0.15
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 +105 -0
- package/dist/chunk-RCTEXK7C.js +549 -0
- package/dist/chunk-RCTEXK7C.js.map +1 -0
- package/dist/config/rules.yaml +524 -0
- package/dist/index.cjs +587 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +55 -0
- package/dist/index.d.ts +55 -0
- package/dist/index.js +26 -0
- package/dist/index.js.map +1 -0
- package/dist/knowledge/agents/architect.md.stub +150 -0
- package/dist/knowledge/agents/developer.md.stub +190 -0
- package/dist/knowledge/agents/reviewer.md.stub +134 -0
- package/dist/knowledge/agents/tester.md.stub +196 -0
- package/dist/knowledge/checklists/backend.md.stub +112 -0
- package/dist/knowledge/checklists/react.md.stub +108 -0
- package/dist/knowledge/claude-rules/laravel-controllers.md.stub +57 -0
- package/dist/knowledge/claude-rules/laravel-migrations.md.stub +47 -0
- package/dist/knowledge/claude-rules/laravel-tests.md.stub +52 -0
- package/dist/knowledge/claude-rules/naming.md.stub +369 -0
- package/dist/knowledge/claude-rules/performance.md.stub +256 -0
- package/dist/knowledge/claude-rules/php-standards.md.stub +305 -0
- package/dist/knowledge/claude-rules/react-components.md.stub +67 -0
- package/dist/knowledge/claude-rules/schema-yaml.md.stub +83 -0
- package/dist/knowledge/claude-rules/security.md.stub +164 -0
- package/dist/knowledge/cursor-rules/antd-deprecations.mdc.stub +62 -0
- package/dist/knowledge/cursor-rules/basemodel-readonly.mdc.stub +66 -0
- package/dist/knowledge/cursor-rules/baserequest-readonly.mdc.stub +74 -0
- package/dist/knowledge/cursor-rules/baseresource-readonly.mdc.stub +78 -0
- package/dist/knowledge/cursor-rules/laravel-controller.mdc.stub +421 -0
- package/dist/knowledge/cursor-rules/laravel-request.mdc.stub +112 -0
- package/dist/knowledge/cursor-rules/laravel-resource.mdc.stub +73 -0
- package/dist/knowledge/cursor-rules/laravel-review.mdc.stub +69 -0
- package/dist/knowledge/cursor-rules/laravel-testing.mdc.stub +138 -0
- package/dist/knowledge/cursor-rules/laravel.mdc.stub +138 -0
- package/dist/knowledge/cursor-rules/migrations-workflow.mdc.stub +224 -0
- package/dist/knowledge/cursor-rules/model-editable.mdc.stub +120 -0
- package/dist/knowledge/cursor-rules/omnify-migrations.mdc.stub +109 -0
- package/dist/knowledge/cursor-rules/omnify-schema.mdc.stub +358 -0
- package/dist/knowledge/cursor-rules/omnify.mdc.stub +58 -0
- package/dist/knowledge/cursor-rules/react-design.mdc.stub +693 -0
- package/dist/knowledge/cursor-rules/react-form.mdc.stub +292 -0
- package/dist/knowledge/cursor-rules/react-services.mdc.stub +304 -0
- package/dist/knowledge/cursor-rules/react.mdc.stub +336 -0
- package/dist/knowledge/cursor-rules/request-editable.mdc.stub +111 -0
- package/dist/knowledge/cursor-rules/resource-editable.mdc.stub +125 -0
- package/dist/knowledge/cursor-rules/schema-create.mdc.stub +440 -0
- package/dist/knowledge/cursor-rules/validation-rules.mdc.stub +181 -0
- package/dist/knowledge/laravel/README.md.stub +59 -0
- package/dist/knowledge/laravel/architecture.md.stub +424 -0
- package/dist/knowledge/laravel/authentication.md.stub +588 -0
- package/dist/knowledge/laravel/controller.md.stub +484 -0
- package/dist/knowledge/laravel/datetime.md.stub +334 -0
- package/dist/knowledge/laravel/migrations-team.md.stub +376 -0
- package/dist/knowledge/laravel/openapi.md.stub +449 -0
- package/dist/knowledge/laravel/request.md.stub +450 -0
- package/dist/knowledge/laravel/resource.md.stub +516 -0
- package/dist/knowledge/laravel/service.md.stub +503 -0
- package/dist/knowledge/laravel/testing.md.stub +1504 -0
- package/dist/knowledge/omnify/antdesign-guide.md.stub +401 -0
- package/dist/knowledge/omnify/config-guide.md.stub +405 -0
- package/dist/knowledge/omnify/japan-guide.md.stub +186 -0
- package/dist/knowledge/omnify/laravel-guide.md.stub +61 -0
- package/dist/knowledge/omnify/partial-schema-guide.md.stub +353 -0
- package/dist/knowledge/omnify/react-form-guide.md.stub +225 -0
- package/dist/knowledge/omnify/schema-guide.md.stub +144 -0
- package/dist/knowledge/omnify/typescript-guide.md.stub +337 -0
- package/dist/knowledge/react/README.md.stub +221 -0
- package/dist/knowledge/react/antd-guide.md +528 -0
- package/dist/knowledge/react/antd-guide.md.stub +528 -0
- package/dist/knowledge/react/checklist.md.stub +108 -0
- package/dist/knowledge/react/datetime-guide.md.stub +137 -0
- package/dist/knowledge/react/design-philosophy.md.stub +363 -0
- package/dist/knowledge/react/i18n-guide.md.stub +211 -0
- package/dist/knowledge/react/laravel-integration.md.stub +181 -0
- package/dist/knowledge/react/service-pattern.md.stub +180 -0
- package/dist/knowledge/react/tanstack-query.md.stub +339 -0
- package/dist/knowledge/react/types-guide.md +669 -0
- package/dist/knowledge/react/types-guide.md.stub +669 -0
- package/dist/knowledge/workflows/bug-fix.md.stub +201 -0
- package/dist/knowledge/workflows/code-review.md.stub +164 -0
- package/dist/knowledge/workflows/new-feature.md.stub +327 -0
- package/dist/plugin-M95GyBll.d.cts +191 -0
- package/dist/plugin-M95GyBll.d.ts +191 -0
- package/dist/plugin.cjs +573 -0
- package/dist/plugin.cjs.map +1 -0
- package/dist/plugin.d.cts +2 -0
- package/dist/plugin.d.ts +2 -0
- package/dist/plugin.js +15 -0
- package/dist/plugin.js.map +1 -0
- package/package.json +53 -0
package/README.md
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# @famgia/omnify-ai-guides
|
|
2
|
+
|
|
3
|
+
Generate **AI Guides** for your Omnify project:
|
|
4
|
+
|
|
5
|
+
- **Cursor rules** → `.cursor/rules/omnify/*.mdc`
|
|
6
|
+
- **Claude guides** → `.claude/omnify/guides/**`
|
|
7
|
+
- **Claude rules** → `.claude/rules/omnify/*.md`
|
|
8
|
+
- **Antigravity rules** → `.agent/rules/omnify/*.md`
|
|
9
|
+
|
|
10
|
+
This package is intentionally **separated** from Laravel/TypeScript generators to avoid mixing concerns.
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
pnpm add -D @famgia/omnify-ai-guides
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## CLI usage (recommended)
|
|
19
|
+
|
|
20
|
+
The CLI command is provided by `@famgia/omnify-cli` and loads this package dynamically.
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
# Generate all categories (omnify, laravel, react)
|
|
24
|
+
npx omnify ai-guides
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### Configuration via `omnify.config.ts`
|
|
28
|
+
|
|
29
|
+
Add `aiGuides` to your `omnify.config.ts`:
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
import { defineConfig } from '@famgia/omnify';
|
|
33
|
+
|
|
34
|
+
export default defineConfig({
|
|
35
|
+
// ... other config
|
|
36
|
+
|
|
37
|
+
aiGuides: {
|
|
38
|
+
typescriptBase: 'resources/ts', // or 'resources/js', 'src', etc.
|
|
39
|
+
laravelBase: 'app',
|
|
40
|
+
categories: ['omnify', 'laravel', 'react'], // optional
|
|
41
|
+
adapters: ['cursor', 'claude', 'antigravity'], // optional
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
CLI options override config values.
|
|
47
|
+
|
|
48
|
+
### Filter by category
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
# API-only Laravel project (skip React rules)
|
|
52
|
+
npx omnify ai-guides --categories omnify,laravel
|
|
53
|
+
|
|
54
|
+
# Frontend-only (skip Laravel rules)
|
|
55
|
+
npx omnify ai-guides --categories omnify,react
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Customize base paths (globs placeholders)
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
# Change TypeScript base path used in Cursor/Antigravity globs
|
|
62
|
+
npx omnify ai-guides --typescript-base resources/js
|
|
63
|
+
|
|
64
|
+
# Change Laravel base path used in globs
|
|
65
|
+
npx omnify ai-guides --laravel-base app
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Filter by adapter
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
# Only Cursor rules
|
|
72
|
+
npx omnify ai-guides --adapters cursor
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Dry run
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
npx omnify ai-guides --dry-run
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Plugin usage (optional)
|
|
82
|
+
|
|
83
|
+
You can also run it as an Omnify plugin generator (if you want it in `omnify generate`):
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
import { defineConfig } from '@famgia/omnify';
|
|
87
|
+
import aiGuides from '@famgia/omnify-ai-guides/plugin';
|
|
88
|
+
|
|
89
|
+
export default defineConfig({
|
|
90
|
+
plugins: [
|
|
91
|
+
aiGuides({
|
|
92
|
+
categories: ['omnify', 'laravel'], // for example
|
|
93
|
+
typescriptBasePath: 'resources/ts',
|
|
94
|
+
laravelBasePath: 'app',
|
|
95
|
+
adapters: ['cursor', 'claude', 'antigravity'],
|
|
96
|
+
}),
|
|
97
|
+
],
|
|
98
|
+
});
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Notes
|
|
102
|
+
|
|
103
|
+
- This package writes files directly under `.cursor/`, `.claude/`, `.agent/`.
|
|
104
|
+
- If you don’t install `@famgia/omnify-ai-guides`, the CLI will prompt you to install it when you run `omnify ai-guides`.
|
|
105
|
+
|
|
@@ -0,0 +1,549 @@
|
|
|
1
|
+
// src/plugin.ts
|
|
2
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
3
|
+
|
|
4
|
+
// src/generator.ts
|
|
5
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync } from "fs";
|
|
6
|
+
import { resolve, dirname, join, basename } from "path";
|
|
7
|
+
import { fileURLToPath } from "url";
|
|
8
|
+
import { parse as parseYaml } from "yaml";
|
|
9
|
+
|
|
10
|
+
// src/adapters/cursor.ts
|
|
11
|
+
function replacePlaceholders(content, placeholders) {
|
|
12
|
+
let result = content;
|
|
13
|
+
for (const [key, value] of Object.entries(placeholders)) {
|
|
14
|
+
result = result.replace(new RegExp(`\\{\\{${key}\\}\\}`, "g"), value);
|
|
15
|
+
}
|
|
16
|
+
return result;
|
|
17
|
+
}
|
|
18
|
+
var cursorAdapter = {
|
|
19
|
+
name: "cursor",
|
|
20
|
+
outputDir: ".cursor/rules/omnify",
|
|
21
|
+
transform(content, rule, placeholders) {
|
|
22
|
+
const target = rule.targets.cursor;
|
|
23
|
+
let transformedContent = replacePlaceholders(content, placeholders);
|
|
24
|
+
if (transformedContent.startsWith("---")) {
|
|
25
|
+
return transformedContent;
|
|
26
|
+
}
|
|
27
|
+
const description = target?.description || "";
|
|
28
|
+
const globs = target?.globs || [];
|
|
29
|
+
const alwaysApply = target?.alwaysApply ?? false;
|
|
30
|
+
const frontmatter = `---
|
|
31
|
+
description: "${description}"
|
|
32
|
+
globs: ${JSON.stringify(globs)}
|
|
33
|
+
alwaysApply: ${alwaysApply}
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
`;
|
|
37
|
+
return frontmatter + transformedContent;
|
|
38
|
+
},
|
|
39
|
+
getFilename(rule) {
|
|
40
|
+
const target = rule.targets.cursor;
|
|
41
|
+
if (target?.filename) {
|
|
42
|
+
return target.filename;
|
|
43
|
+
}
|
|
44
|
+
return `${rule.id}.mdc`;
|
|
45
|
+
},
|
|
46
|
+
isEnabled(rule) {
|
|
47
|
+
const target = rule.targets.cursor;
|
|
48
|
+
return target?.enabled ?? false;
|
|
49
|
+
},
|
|
50
|
+
getOutputPath(rule) {
|
|
51
|
+
const filename = this.getFilename(rule);
|
|
52
|
+
return filename;
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
// src/adapters/claude.ts
|
|
57
|
+
function replacePlaceholders2(content, placeholders) {
|
|
58
|
+
let result = content;
|
|
59
|
+
for (const [key, value] of Object.entries(placeholders)) {
|
|
60
|
+
result = result.replace(new RegExp(`\\{\\{${key}\\}\\}`, "g"), value);
|
|
61
|
+
}
|
|
62
|
+
return result;
|
|
63
|
+
}
|
|
64
|
+
var claudeAdapter = {
|
|
65
|
+
name: "claude",
|
|
66
|
+
outputDir: ".claude/omnify",
|
|
67
|
+
transform(content, _rule, placeholders) {
|
|
68
|
+
return replacePlaceholders2(content, placeholders);
|
|
69
|
+
},
|
|
70
|
+
getFilename(rule) {
|
|
71
|
+
const target = rule.targets.claude;
|
|
72
|
+
if (target?.filename) {
|
|
73
|
+
return target.filename;
|
|
74
|
+
}
|
|
75
|
+
return `${rule.id}.md`;
|
|
76
|
+
},
|
|
77
|
+
isEnabled(rule) {
|
|
78
|
+
const target = rule.targets.claude;
|
|
79
|
+
return target?.enabled ?? false;
|
|
80
|
+
},
|
|
81
|
+
getOutputPath(rule) {
|
|
82
|
+
const target = rule.targets.claude;
|
|
83
|
+
const filename = this.getFilename(rule);
|
|
84
|
+
const subdir = target?.subdir || rule.category;
|
|
85
|
+
if (subdir) {
|
|
86
|
+
return `${subdir}/${filename}`;
|
|
87
|
+
}
|
|
88
|
+
return filename;
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
// src/adapters/antigravity.ts
|
|
93
|
+
function replacePlaceholders3(content, placeholders) {
|
|
94
|
+
let result = content;
|
|
95
|
+
for (const [key, value] of Object.entries(placeholders)) {
|
|
96
|
+
result = result.replace(new RegExp(`\\{\\{${key}\\}\\}`, "g"), value);
|
|
97
|
+
}
|
|
98
|
+
return result;
|
|
99
|
+
}
|
|
100
|
+
var antigravityAdapter = {
|
|
101
|
+
name: "antigravity",
|
|
102
|
+
outputDir: ".agent/rules/omnify",
|
|
103
|
+
transform(content, rule, placeholders) {
|
|
104
|
+
const target = rule.targets.antigravity;
|
|
105
|
+
let transformedContent = replacePlaceholders3(content, placeholders);
|
|
106
|
+
if (transformedContent.startsWith("---")) {
|
|
107
|
+
const endIndex = transformedContent.indexOf("---", 3);
|
|
108
|
+
if (endIndex !== -1) {
|
|
109
|
+
const existingFrontmatter = transformedContent.substring(3, endIndex).trim();
|
|
110
|
+
const bodyContent = transformedContent.substring(endIndex + 3).trim();
|
|
111
|
+
const antigravityFrontmatter = buildAntigravityFrontmatter(rule, target, existingFrontmatter);
|
|
112
|
+
return `---
|
|
113
|
+
${antigravityFrontmatter}---
|
|
114
|
+
|
|
115
|
+
${bodyContent}`;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
const frontmatter = buildAntigravityFrontmatter(rule, target);
|
|
119
|
+
return `---
|
|
120
|
+
${frontmatter}---
|
|
121
|
+
|
|
122
|
+
${transformedContent}`;
|
|
123
|
+
},
|
|
124
|
+
getFilename(rule) {
|
|
125
|
+
const target = rule.targets.antigravity;
|
|
126
|
+
if (target?.filename) {
|
|
127
|
+
return target.filename;
|
|
128
|
+
}
|
|
129
|
+
return `${rule.id}.md`;
|
|
130
|
+
},
|
|
131
|
+
isEnabled(rule) {
|
|
132
|
+
const target = rule.targets.antigravity;
|
|
133
|
+
return target?.enabled ?? false;
|
|
134
|
+
},
|
|
135
|
+
getOutputPath(rule) {
|
|
136
|
+
const filename = this.getFilename(rule);
|
|
137
|
+
return filename;
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
function buildAntigravityFrontmatter(rule, target, existingFrontmatter) {
|
|
141
|
+
const lines = [];
|
|
142
|
+
lines.push(`id: ${rule.id}`);
|
|
143
|
+
const description = target?.description || extractDescription(existingFrontmatter) || rule.id;
|
|
144
|
+
lines.push(`description: "${description}"`);
|
|
145
|
+
const priority = target?.priority || "medium";
|
|
146
|
+
lines.push(`priority: ${priority}`);
|
|
147
|
+
const globs = target?.globs || extractGlobs(existingFrontmatter) || [];
|
|
148
|
+
if (globs.length > 0) {
|
|
149
|
+
lines.push(`globs:`);
|
|
150
|
+
for (const glob of globs) {
|
|
151
|
+
lines.push(` - "${glob}"`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
const tags = target?.tags || inferTags(rule);
|
|
155
|
+
if (tags.length > 0) {
|
|
156
|
+
lines.push(`tags:`);
|
|
157
|
+
for (const tag of tags) {
|
|
158
|
+
lines.push(` - ${tag}`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return lines.join("\n") + "\n";
|
|
162
|
+
}
|
|
163
|
+
function extractDescription(frontmatter) {
|
|
164
|
+
if (!frontmatter) return void 0;
|
|
165
|
+
const match = frontmatter.match(/description:\s*["']?([^"'\n]+)["']?/);
|
|
166
|
+
return match?.[1];
|
|
167
|
+
}
|
|
168
|
+
function extractGlobs(frontmatter) {
|
|
169
|
+
if (!frontmatter) return [];
|
|
170
|
+
const match = frontmatter.match(/globs:\s*\[([^\]]+)\]/);
|
|
171
|
+
if (match?.[1]) {
|
|
172
|
+
return match[1].split(",").map((g) => g.trim().replace(/["']/g, ""));
|
|
173
|
+
}
|
|
174
|
+
return [];
|
|
175
|
+
}
|
|
176
|
+
function inferTags(rule) {
|
|
177
|
+
const tags = [];
|
|
178
|
+
if (rule.category) {
|
|
179
|
+
const parts = rule.category.split("/");
|
|
180
|
+
tags.push(...parts.filter((p) => p !== "guides"));
|
|
181
|
+
}
|
|
182
|
+
if (rule.id.includes("laravel")) tags.push("laravel");
|
|
183
|
+
if (rule.id.includes("react")) tags.push("react");
|
|
184
|
+
if (rule.id.includes("schema")) tags.push("schema");
|
|
185
|
+
if (rule.id.includes("omnify")) tags.push("omnify");
|
|
186
|
+
return [...new Set(tags)];
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// src/adapters/index.ts
|
|
190
|
+
var allAdapters = [cursorAdapter, claudeAdapter, antigravityAdapter];
|
|
191
|
+
function getAdapters(names) {
|
|
192
|
+
if (!names || names.length === 0) {
|
|
193
|
+
return allAdapters;
|
|
194
|
+
}
|
|
195
|
+
return allAdapters.filter((a) => names.includes(a.name));
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// src/generator.ts
|
|
199
|
+
var __filename = fileURLToPath(import.meta.url);
|
|
200
|
+
var __dirname = dirname(__filename);
|
|
201
|
+
function getKnowledgeDir() {
|
|
202
|
+
const possiblePaths = [
|
|
203
|
+
// Dev: running in src
|
|
204
|
+
resolve(__dirname, "knowledge"),
|
|
205
|
+
// Dist: running from dist/
|
|
206
|
+
resolve(__dirname, "../knowledge"),
|
|
207
|
+
// Installed package: in node_modules
|
|
208
|
+
resolve(__dirname, "../../knowledge")
|
|
209
|
+
];
|
|
210
|
+
for (const path of possiblePaths) {
|
|
211
|
+
if (existsSync(path)) {
|
|
212
|
+
return path;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
throw new Error(`AI guides knowledge directory not found. Tried: ${possiblePaths.join(", ")}`);
|
|
216
|
+
}
|
|
217
|
+
function getConfigDir() {
|
|
218
|
+
const possiblePaths = [
|
|
219
|
+
// Dev: running in src
|
|
220
|
+
resolve(__dirname, "config"),
|
|
221
|
+
// Dist: running from dist/
|
|
222
|
+
resolve(__dirname, "../config"),
|
|
223
|
+
// Installed package: in node_modules
|
|
224
|
+
resolve(__dirname, "../../config")
|
|
225
|
+
];
|
|
226
|
+
for (const path of possiblePaths) {
|
|
227
|
+
if (existsSync(path)) {
|
|
228
|
+
return path;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
throw new Error(`AI guides config directory not found. Tried: ${possiblePaths.join(", ")}`);
|
|
232
|
+
}
|
|
233
|
+
function loadRulesConfig() {
|
|
234
|
+
const configDir = getConfigDir();
|
|
235
|
+
const rulesPath = join(configDir, "rules.yaml");
|
|
236
|
+
if (!existsSync(rulesPath)) {
|
|
237
|
+
throw new Error(`Rules config not found: ${rulesPath}`);
|
|
238
|
+
}
|
|
239
|
+
const content = readFileSync(rulesPath, "utf-8");
|
|
240
|
+
return parseYaml(content);
|
|
241
|
+
}
|
|
242
|
+
function loadKnowledgeFile(source) {
|
|
243
|
+
const knowledgeDir = getKnowledgeDir();
|
|
244
|
+
const filePath = join(knowledgeDir, source);
|
|
245
|
+
if (!existsSync(filePath)) {
|
|
246
|
+
throw new Error(`Knowledge file not found: ${filePath}`);
|
|
247
|
+
}
|
|
248
|
+
return readFileSync(filePath, "utf-8");
|
|
249
|
+
}
|
|
250
|
+
function replacePlaceholders4(content, placeholders) {
|
|
251
|
+
let result = content;
|
|
252
|
+
for (const [key, value] of Object.entries(placeholders)) {
|
|
253
|
+
result = result.replace(new RegExp(`\\{\\{${key}\\}\\}`, "g"), value);
|
|
254
|
+
}
|
|
255
|
+
return result;
|
|
256
|
+
}
|
|
257
|
+
function writeOutputFile(filePath, content, dryRun) {
|
|
258
|
+
if (dryRun) {
|
|
259
|
+
console.log(`[DRY RUN] Would write: ${filePath}`);
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
const dir = dirname(filePath);
|
|
263
|
+
if (!existsSync(dir)) {
|
|
264
|
+
mkdirSync(dir, { recursive: true });
|
|
265
|
+
}
|
|
266
|
+
writeFileSync(filePath, content);
|
|
267
|
+
}
|
|
268
|
+
function matchesCategory(ruleCategory, categories) {
|
|
269
|
+
if (!categories || categories.length === 0) {
|
|
270
|
+
return true;
|
|
271
|
+
}
|
|
272
|
+
const parts = ruleCategory.split("/");
|
|
273
|
+
const category = parts[parts.length - 1];
|
|
274
|
+
return categories.includes(category);
|
|
275
|
+
}
|
|
276
|
+
function sourceMatchesCategory(source, categories) {
|
|
277
|
+
if (!categories || categories.length === 0) {
|
|
278
|
+
return true;
|
|
279
|
+
}
|
|
280
|
+
const filename = basename(source).toLowerCase();
|
|
281
|
+
for (const category of categories) {
|
|
282
|
+
if (filename.startsWith(category)) {
|
|
283
|
+
return true;
|
|
284
|
+
}
|
|
285
|
+
if (source.includes(`/${category}/`) || source.startsWith(`${category}/`)) {
|
|
286
|
+
return true;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
if (categories.includes("react")) {
|
|
290
|
+
if (filename.startsWith("react") || filename.includes("antd")) {
|
|
291
|
+
return true;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
if (categories.includes("omnify")) {
|
|
295
|
+
if (filename.startsWith("omnify") || filename.startsWith("schema") || filename.startsWith("validation") || filename.includes("migration")) {
|
|
296
|
+
return true;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
return false;
|
|
300
|
+
}
|
|
301
|
+
function generateAIGuides(rootDir, options = {}) {
|
|
302
|
+
const config = loadRulesConfig();
|
|
303
|
+
const adapters = getAdapters(options.adapters);
|
|
304
|
+
const dryRun = options.dryRun ?? false;
|
|
305
|
+
const categories = options.categories;
|
|
306
|
+
const placeholders = {
|
|
307
|
+
...config.placeholders,
|
|
308
|
+
...options.placeholders
|
|
309
|
+
};
|
|
310
|
+
const result = {
|
|
311
|
+
counts: {},
|
|
312
|
+
files: []
|
|
313
|
+
};
|
|
314
|
+
for (const adapter of adapters) {
|
|
315
|
+
result.counts[adapter.name] = 0;
|
|
316
|
+
}
|
|
317
|
+
for (const rule of config.guides) {
|
|
318
|
+
if (!matchesCategory(rule.category, categories)) {
|
|
319
|
+
continue;
|
|
320
|
+
}
|
|
321
|
+
try {
|
|
322
|
+
const content = loadKnowledgeFile(rule.source);
|
|
323
|
+
for (const adapter of adapters) {
|
|
324
|
+
if (!adapter.isEnabled(rule)) {
|
|
325
|
+
continue;
|
|
326
|
+
}
|
|
327
|
+
const transformed = adapter.transform(content, rule, placeholders);
|
|
328
|
+
const outputPath = adapter.getOutputPath(rule);
|
|
329
|
+
const fullPath = resolve(rootDir, adapter.outputDir, outputPath);
|
|
330
|
+
writeOutputFile(fullPath, transformed, dryRun);
|
|
331
|
+
result.files.push(fullPath);
|
|
332
|
+
result.counts[adapter.name] = (result.counts[adapter.name] ?? 0) + 1;
|
|
333
|
+
}
|
|
334
|
+
} catch (error) {
|
|
335
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
336
|
+
result.errors = result.errors ?? [];
|
|
337
|
+
result.errors.push(`Error processing ${rule.id}: ${message}`);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
const cursorAdapter2 = adapters.find((a) => a.name === "cursor");
|
|
341
|
+
if (cursorAdapter2 && config.cursorRules) {
|
|
342
|
+
for (const rule of config.cursorRules) {
|
|
343
|
+
if (!sourceMatchesCategory(rule.source, categories)) {
|
|
344
|
+
continue;
|
|
345
|
+
}
|
|
346
|
+
try {
|
|
347
|
+
const content = loadKnowledgeFile(rule.source);
|
|
348
|
+
const transformed = replacePlaceholders4(content, placeholders);
|
|
349
|
+
const filename = basename(rule.source).replace(".mdc.stub", ".mdc").replace(".md.stub", ".md");
|
|
350
|
+
const fullPath = resolve(rootDir, cursorAdapter2.outputDir, filename);
|
|
351
|
+
writeOutputFile(fullPath, transformed, dryRun);
|
|
352
|
+
result.files.push(fullPath);
|
|
353
|
+
result.counts[cursorAdapter2.name] = (result.counts[cursorAdapter2.name] ?? 0) + 1;
|
|
354
|
+
} catch (error) {
|
|
355
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
356
|
+
result.errors = result.errors ?? [];
|
|
357
|
+
result.errors.push(`Error processing cursor rule ${rule.source}: ${message}`);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
const claudeAdapter2 = adapters.find((a) => a.name === "claude");
|
|
362
|
+
if (claudeAdapter2 && config.claudeRules) {
|
|
363
|
+
for (const rule of config.claudeRules) {
|
|
364
|
+
if (!sourceMatchesCategory(rule.source, categories)) {
|
|
365
|
+
continue;
|
|
366
|
+
}
|
|
367
|
+
try {
|
|
368
|
+
const content = loadKnowledgeFile(rule.source);
|
|
369
|
+
const strippedContent = stripFrontmatter(content);
|
|
370
|
+
const transformed = replacePlaceholders4(strippedContent, placeholders);
|
|
371
|
+
const filename = basename(rule.source).replace(".mdc.stub", ".mdc").replace(".md.stub", ".md").replace(".mdc", ".md");
|
|
372
|
+
const fullPath = resolve(rootDir, ".claude/rules/omnify", filename);
|
|
373
|
+
writeOutputFile(fullPath, transformed, dryRun);
|
|
374
|
+
result.files.push(fullPath);
|
|
375
|
+
result.counts[claudeAdapter2.name] = (result.counts[claudeAdapter2.name] ?? 0) + 1;
|
|
376
|
+
} catch (error) {
|
|
377
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
378
|
+
result.errors = result.errors ?? [];
|
|
379
|
+
result.errors.push(`Error processing claude rule ${rule.source}: ${message}`);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
const antigravityAdapterInstance = adapters.find((a) => a.name === "antigravity");
|
|
384
|
+
if (antigravityAdapterInstance && config.antigravityRules) {
|
|
385
|
+
for (const rule of config.antigravityRules) {
|
|
386
|
+
if (!sourceMatchesCategory(rule.source, categories)) {
|
|
387
|
+
continue;
|
|
388
|
+
}
|
|
389
|
+
try {
|
|
390
|
+
const content = loadKnowledgeFile(rule.source);
|
|
391
|
+
const transformed = transformForAntigravity(content, rule, placeholders);
|
|
392
|
+
const filename = basename(rule.source).replace(".mdc.stub", ".mdc").replace(".md.stub", ".md").replace(".mdc", ".md");
|
|
393
|
+
const fullPath = resolve(rootDir, antigravityAdapterInstance.outputDir, filename);
|
|
394
|
+
writeOutputFile(fullPath, transformed, dryRun);
|
|
395
|
+
result.files.push(fullPath);
|
|
396
|
+
result.counts[antigravityAdapterInstance.name] = (result.counts[antigravityAdapterInstance.name] ?? 0) + 1;
|
|
397
|
+
} catch (error) {
|
|
398
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
399
|
+
result.errors = result.errors ?? [];
|
|
400
|
+
result.errors.push(`Error processing antigravity rule ${rule.source}: ${message}`);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
return result;
|
|
405
|
+
}
|
|
406
|
+
function transformForAntigravity(content, rule, placeholders) {
|
|
407
|
+
let transformed = replacePlaceholders4(content, placeholders);
|
|
408
|
+
if (transformed.startsWith("---")) {
|
|
409
|
+
const endIndex = transformed.indexOf("---", 3);
|
|
410
|
+
if (endIndex !== -1) {
|
|
411
|
+
const existingFrontmatter = transformed.substring(3, endIndex).trim();
|
|
412
|
+
const bodyContent = transformed.substring(endIndex + 3).trim();
|
|
413
|
+
const id = basename(rule.source).replace(".mdc.stub", "").replace(".md.stub", "").replace(".mdc", "").replace(".md", "");
|
|
414
|
+
const description = extractYamlValue(existingFrontmatter, "description") || id;
|
|
415
|
+
const globs = extractYamlArray(existingFrontmatter, "globs");
|
|
416
|
+
const priority = rule.priority || "medium";
|
|
417
|
+
const tags = rule.tags || [];
|
|
418
|
+
const lines = [`id: ${id}`, `description: "${description}"`, `priority: ${priority}`];
|
|
419
|
+
if (globs.length > 0) {
|
|
420
|
+
lines.push("globs:");
|
|
421
|
+
for (const glob of globs) {
|
|
422
|
+
lines.push(` - "${glob}"`);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
if (tags.length > 0) {
|
|
426
|
+
lines.push("tags:");
|
|
427
|
+
for (const tag of tags) {
|
|
428
|
+
lines.push(` - ${tag}`);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
return `---
|
|
432
|
+
${lines.join("\n")}
|
|
433
|
+
---
|
|
434
|
+
|
|
435
|
+
${bodyContent}`;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
return transformed;
|
|
439
|
+
}
|
|
440
|
+
function extractYamlValue(yaml, key) {
|
|
441
|
+
const match = yaml.match(new RegExp(`${key}:\\s*["']?([^"'\\n]+)["']?`));
|
|
442
|
+
return match?.[1]?.trim();
|
|
443
|
+
}
|
|
444
|
+
function extractYamlArray(yaml, key) {
|
|
445
|
+
const match = yaml.match(new RegExp(`${key}:\\s*\\[([^\\]]+)\\]`));
|
|
446
|
+
if (match?.[1]) {
|
|
447
|
+
return match[1].split(",").map((g) => g.trim().replace(/["']/g, ""));
|
|
448
|
+
}
|
|
449
|
+
return [];
|
|
450
|
+
}
|
|
451
|
+
function stripFrontmatter(content) {
|
|
452
|
+
if (!content.startsWith("---")) {
|
|
453
|
+
return content;
|
|
454
|
+
}
|
|
455
|
+
const endIndex = content.indexOf("---", 3);
|
|
456
|
+
if (endIndex === -1) {
|
|
457
|
+
return content;
|
|
458
|
+
}
|
|
459
|
+
return content.substring(endIndex + 3).trim();
|
|
460
|
+
}
|
|
461
|
+
function shouldGenerateAIGuides(rootDir) {
|
|
462
|
+
const claudeDir = resolve(rootDir, ".claude/omnify/guides");
|
|
463
|
+
const cursorDir = resolve(rootDir, ".cursor/rules/omnify");
|
|
464
|
+
if (!existsSync(claudeDir) || !existsSync(cursorDir)) {
|
|
465
|
+
return true;
|
|
466
|
+
}
|
|
467
|
+
try {
|
|
468
|
+
const claudeFiles = readdirSync(claudeDir, { recursive: true });
|
|
469
|
+
const cursorFiles = readdirSync(cursorDir);
|
|
470
|
+
return claudeFiles.length === 0 || cursorFiles.length === 0;
|
|
471
|
+
} catch {
|
|
472
|
+
return true;
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
function getAvailableAdapters() {
|
|
476
|
+
return getAdapters().map((a) => a.name);
|
|
477
|
+
}
|
|
478
|
+
function getAvailableCategories() {
|
|
479
|
+
return ["omnify", "laravel", "react"];
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// src/plugin.ts
|
|
483
|
+
function getPluginVersion() {
|
|
484
|
+
try {
|
|
485
|
+
const pkgPath = new URL("../package.json", import.meta.url);
|
|
486
|
+
const pkgContent = readFileSync2(pkgPath, "utf-8");
|
|
487
|
+
const pkg = JSON.parse(pkgContent);
|
|
488
|
+
return pkg.version;
|
|
489
|
+
} catch {
|
|
490
|
+
return "0.0.0";
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
function aiGuidesPlugin(options = {}) {
|
|
494
|
+
const resolvedOptions = {
|
|
495
|
+
categories: options.categories ?? getAvailableCategories(),
|
|
496
|
+
laravelBasePath: options.laravelBasePath ?? "app",
|
|
497
|
+
typescriptBasePath: options.typescriptBasePath ?? "resources/ts",
|
|
498
|
+
adapters: options.adapters
|
|
499
|
+
};
|
|
500
|
+
return {
|
|
501
|
+
name: "@famgia/omnify-ai-guides",
|
|
502
|
+
version: getPluginVersion(),
|
|
503
|
+
generators: [
|
|
504
|
+
{
|
|
505
|
+
name: "ai-guides",
|
|
506
|
+
description: "Generate AI assistant guides (Cursor, Claude, Antigravity)",
|
|
507
|
+
generate: async (ctx) => {
|
|
508
|
+
const result = generateAIGuides(ctx.cwd, {
|
|
509
|
+
categories: resolvedOptions.categories,
|
|
510
|
+
adapters: resolvedOptions.adapters,
|
|
511
|
+
placeholders: {
|
|
512
|
+
LARAVEL_BASE: resolvedOptions.laravelBasePath,
|
|
513
|
+
LARAVEL_ROOT: "",
|
|
514
|
+
TYPESCRIPT_BASE: resolvedOptions.typescriptBasePath
|
|
515
|
+
}
|
|
516
|
+
});
|
|
517
|
+
const claudeCount = result.counts["claude"] || 0;
|
|
518
|
+
const cursorCount = result.counts["cursor"] || 0;
|
|
519
|
+
const antigravityCount = result.counts["antigravity"] || 0;
|
|
520
|
+
const parts = [];
|
|
521
|
+
if (claudeCount > 0) parts.push(`${claudeCount} Claude`);
|
|
522
|
+
if (cursorCount > 0) parts.push(`${cursorCount} Cursor`);
|
|
523
|
+
if (antigravityCount > 0) parts.push(`${antigravityCount} Antigravity`);
|
|
524
|
+
ctx.logger.info(`Generated ${parts.join(", ")} files`);
|
|
525
|
+
if (result.errors?.length) {
|
|
526
|
+
for (const error of result.errors) {
|
|
527
|
+
ctx.logger.warn(error);
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
return [];
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
]
|
|
534
|
+
};
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
export {
|
|
538
|
+
cursorAdapter,
|
|
539
|
+
claudeAdapter,
|
|
540
|
+
antigravityAdapter,
|
|
541
|
+
allAdapters,
|
|
542
|
+
getAdapters,
|
|
543
|
+
generateAIGuides,
|
|
544
|
+
shouldGenerateAIGuides,
|
|
545
|
+
getAvailableAdapters,
|
|
546
|
+
getAvailableCategories,
|
|
547
|
+
aiGuidesPlugin
|
|
548
|
+
};
|
|
549
|
+
//# sourceMappingURL=chunk-RCTEXK7C.js.map
|