@terasky/backstage-plugin-ai-rules 0.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/README.md ADDED
@@ -0,0 +1,15 @@
1
+ # AI Coding Rules
2
+
3
+ Welcome to the AI Coding Rules plugin!
4
+
5
+ [![npm latest version](https://img.shields.io/npm/v/@terasky/backstage-plugin-ai-rules/latest.svg)](https://www.npmjs.com/package/@terasky/backstage-plugin-ai-rules)
6
+
7
+ The `AI Coding Rules` plugin for Backstage provides comprehensive visualization and management of AI coding rules from various sources like Cursor, GitHub Copilot, and Cline. It enables teams to discover, view, and manage their AI coding guidelines directly within the Backstage interface, featuring modern UI with filtering capabilities, statistics overview, and expandable rule cards with metadata display.
8
+
9
+ For detailed docs go to https://terasky-oss.github.io/backstage-plugins/plugins/ai-rules-plugin/overview
10
+
11
+ ## Contributing
12
+ Contributions are welcome! Please open an issue or submit a pull request on GitHub.
13
+
14
+ ## License
15
+ This project is licensed under the Apache-2.0 License.
package/config.d.ts ADDED
@@ -0,0 +1,15 @@
1
+ export interface Config {
2
+ /**
3
+ * AI Rules plugin configuration
4
+ * NOTE: Visibility applies to only this field
5
+ * @visibility frontend
6
+ */
7
+ aiRules?: {
8
+ /**
9
+ * Allowed rule types to search for in repositories
10
+ * Defaults to ["cursor", "copilot"] if not specified
11
+ * @visibility frontend
12
+ */
13
+ allowedRuleTypes: string[]; // e.g. ["cursor", "copilot", "cline"]
14
+ }
15
+ }
@@ -0,0 +1,374 @@
1
+ import { jsx, jsxs } from 'react/jsx-runtime';
2
+ import { useAiRules } from '../../hooks/useAiRules.esm.js';
3
+ import { InfoCard, Progress, EmptyState, MarkdownContent } from '@backstage/core-components';
4
+ import { makeStyles, Button, Typography, FormControlLabel, Checkbox, Card, CardContent, useTheme, Accordion, AccordionSummary, Chip, Tooltip, IconButton, AccordionDetails } from '@material-ui/core';
5
+ import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
6
+ import CodeIcon from '@material-ui/icons/Code';
7
+ import LaunchIcon from '@material-ui/icons/Launch';
8
+ import { AIRuleType } from '../../types.esm.js';
9
+
10
+ const isAIRulesAvailable = (entity) => {
11
+ const sourceAnnotation = entity.metadata?.annotations?.["backstage.io/source-location"] || "";
12
+ return sourceAnnotation.startsWith("url:");
13
+ };
14
+ const useStyles = makeStyles((theme) => ({
15
+ root: {
16
+ "& .MuiAccordion-root": {
17
+ marginBottom: theme.spacing(1),
18
+ "&:before": {
19
+ display: "none"
20
+ }
21
+ }
22
+ },
23
+ filterSection: {
24
+ marginBottom: theme.spacing(2),
25
+ padding: theme.spacing(2),
26
+ backgroundColor: theme.palette.background.default,
27
+ borderRadius: theme.shape.borderRadius
28
+ },
29
+ ruleCard: {
30
+ marginBottom: theme.spacing(1),
31
+ border: `1px solid ${theme.palette.divider}`
32
+ },
33
+ ruleHeader: {
34
+ display: "flex",
35
+ alignItems: "center",
36
+ gap: theme.spacing(1),
37
+ width: "100%"
38
+ },
39
+ ruleHeaderContent: {
40
+ display: "flex",
41
+ alignItems: "center",
42
+ gap: theme.spacing(1),
43
+ flex: 1
44
+ },
45
+ ruleType: {
46
+ textTransform: "uppercase",
47
+ fontWeight: "bold",
48
+ fontSize: "0.75rem"
49
+ },
50
+ ruleContent: {
51
+ padding: theme.spacing(1),
52
+ borderRadius: theme.shape.borderRadius,
53
+ overflow: "auto",
54
+ maxHeight: "300px",
55
+ "& > *": {
56
+ backgroundColor: "transparent !important"
57
+ }
58
+ },
59
+ ruleMetadata: {
60
+ display: "flex",
61
+ flexWrap: "wrap",
62
+ gap: theme.spacing(0.5),
63
+ marginBottom: theme.spacing(1)
64
+ },
65
+ statsContainer: {
66
+ display: "flex",
67
+ gap: theme.spacing(2),
68
+ marginBottom: theme.spacing(2)
69
+ },
70
+ statCard: {
71
+ minWidth: "120px",
72
+ textAlign: "center"
73
+ },
74
+ emptyStateIcon: {
75
+ fontSize: "4rem",
76
+ color: theme.palette.grey[400]
77
+ },
78
+ filterContainer: {
79
+ display: "flex",
80
+ flexWrap: "wrap",
81
+ "& > *": {
82
+ marginRight: theme.spacing(1)
83
+ }
84
+ }
85
+ }));
86
+ const RuleTypeIcon = ({ type }) => {
87
+ const colors = {
88
+ [AIRuleType.CURSOR]: "#0066CC",
89
+ [AIRuleType.COPILOT]: "#6F42C1",
90
+ [AIRuleType.CLINE]: "#28A745"
91
+ };
92
+ return /* @__PURE__ */ jsx(CodeIcon, { style: { color: colors[type] } });
93
+ };
94
+ const renderFrontmatter = (theme, frontmatter) => {
95
+ if (!frontmatter || Object.keys(frontmatter).length === 0) {
96
+ return null;
97
+ }
98
+ const filteredEntries = Object.entries(frontmatter).filter(
99
+ ([key]) => !["description", "globs"].includes(key)
100
+ );
101
+ if (filteredEntries.length === 0) {
102
+ return null;
103
+ }
104
+ return /* @__PURE__ */ jsxs("div", { style: {
105
+ marginBottom: "16px",
106
+ padding: "16px",
107
+ backgroundColor: theme.palette.type === "dark" ? "rgba(255,255,255,0.05)" : "rgba(0,0,0,0.05)",
108
+ borderRadius: "8px",
109
+ border: `1px solid ${theme.palette.type === "dark" ? "rgba(255,255,255,0.12)" : "rgba(0,0,0,0.12)"}`
110
+ }, children: [
111
+ /* @__PURE__ */ jsx(Typography, { variant: "subtitle2", style: {
112
+ marginBottom: "12px",
113
+ fontWeight: "bold",
114
+ color: theme.palette.text.secondary,
115
+ textTransform: "uppercase",
116
+ letterSpacing: "0.5px"
117
+ }, children: "Metadata" }),
118
+ filteredEntries.map(([key, value], index) => /* @__PURE__ */ jsxs("div", { style: { marginBottom: index < filteredEntries.length - 1 ? "12px" : "0" }, children: [
119
+ /* @__PURE__ */ jsxs(Typography, { variant: "body2", style: {
120
+ fontWeight: "bold",
121
+ textTransform: "capitalize",
122
+ color: theme.palette.primary.main,
123
+ marginBottom: "4px"
124
+ }, children: [
125
+ key,
126
+ ":"
127
+ ] }),
128
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", style: {
129
+ lineHeight: "1.5",
130
+ marginLeft: "8px",
131
+ color: theme.palette.text.primary
132
+ }, children: Array.isArray(value) ? value.join(", ") : String(value) })
133
+ ] }, key))
134
+ ] });
135
+ };
136
+ const parseCursorContent = (content) => {
137
+ return manualParseFrontmatter(content);
138
+ };
139
+ const manualParseFrontmatter = (content) => {
140
+ if (!content.trim().startsWith("---")) {
141
+ return {
142
+ frontmatter: void 0,
143
+ content
144
+ };
145
+ }
146
+ try {
147
+ const lines = content.split("\n");
148
+ let frontmatterEndIndex = -1;
149
+ for (let i = 1; i < lines.length; i++) {
150
+ if (lines[i].trim() === "---") {
151
+ frontmatterEndIndex = i;
152
+ break;
153
+ }
154
+ }
155
+ if (frontmatterEndIndex === -1) {
156
+ return {
157
+ frontmatter: void 0,
158
+ content
159
+ };
160
+ }
161
+ const frontmatterLines = lines.slice(1, frontmatterEndIndex);
162
+ const contentLines = lines.slice(frontmatterEndIndex + 1);
163
+ const frontmatter = {};
164
+ for (const line of frontmatterLines) {
165
+ const trimmedLine = line.trim();
166
+ if (trimmedLine && trimmedLine.includes(":")) {
167
+ const colonIndex = trimmedLine.indexOf(":");
168
+ const key = trimmedLine.substring(0, colonIndex).trim();
169
+ const value = trimmedLine.substring(colonIndex + 1).trim();
170
+ frontmatter[key] = value;
171
+ }
172
+ }
173
+ return {
174
+ frontmatter: Object.keys(frontmatter).length > 0 ? frontmatter : void 0,
175
+ content: contentLines.join("\n").trim()
176
+ };
177
+ } catch (error) {
178
+ return {
179
+ frontmatter: void 0,
180
+ content
181
+ };
182
+ }
183
+ };
184
+ const constructFileUrl = (gitUrl, filePath) => {
185
+ const cleanGitUrl = gitUrl.replace(/\/+$/, "");
186
+ if (cleanGitUrl.includes("github.com")) {
187
+ return `${cleanGitUrl}/blob/main/${filePath}`;
188
+ }
189
+ if (cleanGitUrl.includes("gitlab.com")) {
190
+ return `${cleanGitUrl}/-/blob/main/${filePath}`;
191
+ }
192
+ return `${cleanGitUrl}/blob/main/${filePath}`;
193
+ };
194
+ const RuleComponent = ({ rule }) => {
195
+ const styles = useStyles();
196
+ const theme = useTheme();
197
+ const renderCursorRule = (rule2) => {
198
+ const { frontmatter, content } = parseCursorContent(rule2.content);
199
+ return /* @__PURE__ */ jsxs(Accordion, { className: styles.ruleCard, children: [
200
+ /* @__PURE__ */ jsx(AccordionSummary, { expandIcon: /* @__PURE__ */ jsx(ExpandMoreIcon, {}), children: /* @__PURE__ */ jsxs("div", { className: styles.ruleHeader, children: [
201
+ /* @__PURE__ */ jsxs("div", { className: styles.ruleHeaderContent, children: [
202
+ /* @__PURE__ */ jsx(RuleTypeIcon, { type: rule2.type }),
203
+ /* @__PURE__ */ jsx(Typography, { variant: "h6", children: rule2.fileName }),
204
+ /* @__PURE__ */ jsx(Chip, { label: rule2.type, size: "small", className: styles.ruleType }),
205
+ frontmatter?.description && /* @__PURE__ */ jsx(Typography, { variant: "body2", style: { marginLeft: 8, color: theme.palette.text.secondary }, children: frontmatter.description })
206
+ ] }),
207
+ rule2.gitUrl && /* @__PURE__ */ jsx(Tooltip, { title: "Open file in repository", children: /* @__PURE__ */ jsx(
208
+ IconButton,
209
+ {
210
+ size: "small",
211
+ onClick: (e) => {
212
+ e.stopPropagation();
213
+ window.open(constructFileUrl(rule2.gitUrl, rule2.filePath), "_blank");
214
+ },
215
+ children: /* @__PURE__ */ jsx(LaunchIcon, { fontSize: "small" })
216
+ }
217
+ ) })
218
+ ] }) }),
219
+ /* @__PURE__ */ jsx(AccordionDetails, { children: /* @__PURE__ */ jsxs("div", { children: [
220
+ /* @__PURE__ */ jsxs("div", { className: styles.ruleMetadata, children: [
221
+ /* @__PURE__ */ jsx(Chip, { label: `Path: ${rule2.filePath}`, size: "small", variant: "outlined" }),
222
+ frontmatter?.globs && /* @__PURE__ */ jsx(Chip, { label: `Globs: ${Array.isArray(frontmatter.globs) ? frontmatter.globs.join(", ") : frontmatter.globs}`, size: "small", variant: "outlined" })
223
+ ] }),
224
+ renderFrontmatter(theme, frontmatter),
225
+ /* @__PURE__ */ jsx("div", { className: styles.ruleContent, children: /* @__PURE__ */ jsx(MarkdownContent, { content }) })
226
+ ] }) })
227
+ ] });
228
+ };
229
+ const renderCopilotRule = (rule2) => /* @__PURE__ */ jsx(Card, { className: styles.ruleCard, children: /* @__PURE__ */ jsxs(CardContent, { children: [
230
+ /* @__PURE__ */ jsxs("div", { className: styles.ruleHeader, children: [
231
+ /* @__PURE__ */ jsxs("div", { className: styles.ruleHeaderContent, children: [
232
+ /* @__PURE__ */ jsx(RuleTypeIcon, { type: rule2.type }),
233
+ /* @__PURE__ */ jsxs(Typography, { variant: "h6", children: [
234
+ "Copilot Rule #",
235
+ rule2.order
236
+ ] }),
237
+ /* @__PURE__ */ jsx(Chip, { label: rule2.type, size: "small", className: styles.ruleType })
238
+ ] }),
239
+ rule2.gitUrl && /* @__PURE__ */ jsx(Tooltip, { title: "Open file in repository", children: /* @__PURE__ */ jsx(
240
+ IconButton,
241
+ {
242
+ size: "small",
243
+ onClick: () => window.open(constructFileUrl(rule2.gitUrl, rule2.filePath), "_blank"),
244
+ children: /* @__PURE__ */ jsx(LaunchIcon, { fontSize: "small" })
245
+ }
246
+ ) })
247
+ ] }),
248
+ /* @__PURE__ */ jsx("div", { className: styles.ruleContent, children: rule2.content })
249
+ ] }) });
250
+ const renderClineRule = (rule2) => /* @__PURE__ */ jsxs(Accordion, { className: styles.ruleCard, children: [
251
+ /* @__PURE__ */ jsx(AccordionSummary, { expandIcon: /* @__PURE__ */ jsx(ExpandMoreIcon, {}), children: /* @__PURE__ */ jsxs("div", { className: styles.ruleHeader, children: [
252
+ /* @__PURE__ */ jsxs("div", { className: styles.ruleHeaderContent, children: [
253
+ /* @__PURE__ */ jsx(RuleTypeIcon, { type: rule2.type }),
254
+ /* @__PURE__ */ jsx(Typography, { variant: "h6", children: rule2.title || rule2.fileName }),
255
+ /* @__PURE__ */ jsx(Chip, { label: rule2.type, size: "small", className: styles.ruleType })
256
+ ] }),
257
+ rule2.gitUrl && /* @__PURE__ */ jsx(Tooltip, { title: "Open file in repository", children: /* @__PURE__ */ jsx(
258
+ IconButton,
259
+ {
260
+ size: "small",
261
+ onClick: (e) => {
262
+ e.stopPropagation();
263
+ window.open(constructFileUrl(rule2.gitUrl, rule2.filePath), "_blank");
264
+ },
265
+ children: /* @__PURE__ */ jsx(LaunchIcon, { fontSize: "small" })
266
+ }
267
+ ) })
268
+ ] }) }),
269
+ /* @__PURE__ */ jsx(AccordionDetails, { children: /* @__PURE__ */ jsxs("div", { children: [
270
+ /* @__PURE__ */ jsx("div", { className: styles.ruleMetadata, children: /* @__PURE__ */ jsx(Chip, { label: `Path: ${rule2.filePath}`, size: "small", variant: "outlined" }) }),
271
+ /* @__PURE__ */ jsx("div", { className: styles.ruleContent, children: /* @__PURE__ */ jsx(MarkdownContent, { content: rule2.content }) })
272
+ ] }) })
273
+ ] });
274
+ switch (rule.type) {
275
+ case AIRuleType.CURSOR:
276
+ return renderCursorRule(rule);
277
+ case AIRuleType.COPILOT:
278
+ return renderCopilotRule(rule);
279
+ case AIRuleType.CLINE:
280
+ return renderClineRule(rule);
281
+ default:
282
+ return null;
283
+ }
284
+ };
285
+ const AIRulesComponent = ({ title = "AI Coding Rules" } = {}) => {
286
+ const { rulesByType, loading, error, hasGitUrl, totalRules, allowedRuleTypes, selectedRuleTypes, setSelectedRuleTypes } = useAiRules();
287
+ const styles = useStyles();
288
+ const handleTypeToggle = (type, checked) => {
289
+ const newTypes = checked ? [...selectedRuleTypes, type] : selectedRuleTypes.filter((t) => t !== type);
290
+ setSelectedRuleTypes(newTypes);
291
+ };
292
+ if (loading) {
293
+ return /* @__PURE__ */ jsx(InfoCard, { title, children: /* @__PURE__ */ jsx(Progress, {}) });
294
+ }
295
+ if (!hasGitUrl) {
296
+ return /* @__PURE__ */ jsx(InfoCard, { title, children: /* @__PURE__ */ jsx(
297
+ EmptyState,
298
+ {
299
+ missing: "content",
300
+ title: "No Git Repository",
301
+ description: "This component doesn't have a Git source URL configured."
302
+ }
303
+ ) });
304
+ }
305
+ if (error) {
306
+ return /* @__PURE__ */ jsx(InfoCard, { title, children: /* @__PURE__ */ jsx(
307
+ EmptyState,
308
+ {
309
+ missing: "content",
310
+ title: "Error Loading Rules",
311
+ description: error
312
+ }
313
+ ) });
314
+ }
315
+ if (totalRules === 0) {
316
+ return /* @__PURE__ */ jsx(InfoCard, { title, children: /* @__PURE__ */ jsx(
317
+ EmptyState,
318
+ {
319
+ missing: "content",
320
+ title: "No AI Rules Found",
321
+ description: "No AI rules were found in this repository for the selected rule types.",
322
+ action: /* @__PURE__ */ jsx(
323
+ Button,
324
+ {
325
+ variant: "outlined",
326
+ onClick: () => setSelectedRuleTypes(allowedRuleTypes),
327
+ children: "Reset Filters"
328
+ }
329
+ )
330
+ }
331
+ ) });
332
+ }
333
+ return /* @__PURE__ */ jsxs(InfoCard, { title, className: styles.root, children: [
334
+ /* @__PURE__ */ jsxs("div", { className: styles.filterSection, children: [
335
+ /* @__PURE__ */ jsx(Typography, { variant: "h6", gutterBottom: true, children: "Filter Rule Types" }),
336
+ /* @__PURE__ */ jsx("div", { className: styles.filterContainer, children: allowedRuleTypes.map((type) => /* @__PURE__ */ jsx(
337
+ FormControlLabel,
338
+ {
339
+ control: /* @__PURE__ */ jsx(
340
+ Checkbox,
341
+ {
342
+ checked: selectedRuleTypes.includes(type),
343
+ onChange: (e) => handleTypeToggle(type, e.target.checked)
344
+ }
345
+ ),
346
+ label: type.charAt(0).toUpperCase() + type.slice(1)
347
+ },
348
+ type
349
+ )) })
350
+ ] }),
351
+ /* @__PURE__ */ jsxs("div", { className: styles.statsContainer, children: [
352
+ /* @__PURE__ */ jsx(Card, { className: styles.statCard, children: /* @__PURE__ */ jsxs(CardContent, { children: [
353
+ /* @__PURE__ */ jsx(Typography, { variant: "h4", children: totalRules }),
354
+ /* @__PURE__ */ jsx(Typography, { color: "textSecondary", children: "Total Rules" })
355
+ ] }) }),
356
+ Object.entries(rulesByType).map(([type, typeRules]) => /* @__PURE__ */ jsx(Card, { className: styles.statCard, children: /* @__PURE__ */ jsxs(CardContent, { children: [
357
+ /* @__PURE__ */ jsx(Typography, { variant: "h4", children: typeRules.length }),
358
+ /* @__PURE__ */ jsx(Typography, { color: "textSecondary", children: type.charAt(0).toUpperCase() + type.slice(1) })
359
+ ] }) }, type))
360
+ ] }),
361
+ Object.entries(rulesByType).map(([type, typeRules]) => /* @__PURE__ */ jsxs("div", { children: [
362
+ /* @__PURE__ */ jsxs(Typography, { variant: "h5", gutterBottom: true, style: { marginTop: 16 }, children: [
363
+ type.charAt(0).toUpperCase() + type.slice(1),
364
+ " Rules (",
365
+ typeRules.length,
366
+ ")"
367
+ ] }),
368
+ typeRules.map((rule) => /* @__PURE__ */ jsx(RuleComponent, { rule }, rule.id))
369
+ ] }, type))
370
+ ] });
371
+ };
372
+
373
+ export { AIRulesComponent, isAIRulesAvailable };
374
+ //# sourceMappingURL=AiRulesComponent.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AiRulesComponent.esm.js","sources":["../../../src/components/AiRulesComponent/AiRulesComponent.tsx"],"sourcesContent":["import { useAiRules } from '../../hooks/useAiRules';\nimport { InfoCard, Progress, EmptyState, MarkdownContent } from '@backstage/core-components';\nimport { Button, makeStyles, useTheme, Typography, Chip, Card, CardContent, Accordion, AccordionSummary, AccordionDetails, FormControlLabel, Checkbox, IconButton, Tooltip } from '@material-ui/core';\nimport { Entity } from '@backstage/catalog-model';\nimport ExpandMoreIcon from '@material-ui/icons/ExpandMore';\nimport CodeIcon from '@material-ui/icons/Code';\nimport LaunchIcon from '@material-ui/icons/Launch';\nimport { AIRuleType, AIRule, CursorRule, CopilotRule, ClineRule } from '../../types';\nexport interface AIRulesComponentProps {\n title?: string;\n}\n\nexport const isAIRulesAvailable = (entity: Entity): boolean => {\n const sourceAnnotation = entity.metadata?.annotations?.['backstage.io/source-location'] || '';\n return sourceAnnotation.startsWith('url:');\n};\n\nconst useStyles = makeStyles((theme) => ({\n root: {\n '& .MuiAccordion-root': {\n marginBottom: theme.spacing(1),\n '&:before': {\n display: 'none',\n },\n },\n },\n filterSection: {\n marginBottom: theme.spacing(2),\n padding: theme.spacing(2),\n backgroundColor: theme.palette.background.default,\n borderRadius: theme.shape.borderRadius,\n },\n ruleCard: {\n marginBottom: theme.spacing(1),\n border: `1px solid ${theme.palette.divider}`,\n },\n ruleHeader: {\n display: 'flex',\n alignItems: 'center',\n gap: theme.spacing(1),\n width: '100%',\n },\n ruleHeaderContent: {\n display: 'flex',\n alignItems: 'center',\n gap: theme.spacing(1),\n flex: 1,\n },\n ruleType: {\n textTransform: 'uppercase',\n fontWeight: 'bold',\n fontSize: '0.75rem',\n },\n ruleContent: {\n padding: theme.spacing(1),\n borderRadius: theme.shape.borderRadius,\n overflow: 'auto',\n maxHeight: '300px',\n '& > *': {\n backgroundColor: 'transparent !important',\n },\n },\n ruleMetadata: {\n display: 'flex',\n flexWrap: 'wrap',\n gap: theme.spacing(0.5),\n marginBottom: theme.spacing(1),\n },\n statsContainer: {\n display: 'flex',\n gap: theme.spacing(2),\n marginBottom: theme.spacing(2),\n },\n statCard: {\n minWidth: '120px',\n textAlign: 'center',\n },\n emptyStateIcon: {\n fontSize: '4rem',\n color: theme.palette.grey[400],\n },\n filterContainer: {\n display: 'flex',\n flexWrap: 'wrap',\n '& > *': {\n marginRight: theme.spacing(1),\n },\n },\n}));\n\nconst RuleTypeIcon = ({ type }: { type: AIRuleType }) => {\n const colors = {\n [AIRuleType.CURSOR]: '#0066CC',\n [AIRuleType.COPILOT]: '#6F42C1', \n [AIRuleType.CLINE]: '#28A745',\n };\n \n return <CodeIcon style={{ color: colors[type] }} />;\n};\n\nconst renderFrontmatter = (theme: any, frontmatter?: Record<string, any>) => {\n if (!frontmatter || Object.keys(frontmatter).length === 0) {\n return null;\n }\n\n // Filter out fields that are already displayed elsewhere (description, globs)\n const filteredEntries = Object.entries(frontmatter).filter(([key]) => \n !['description', 'globs'].includes(key)\n );\n\n if (filteredEntries.length === 0) {\n return null;\n }\n\n return (\n <div style={{ \n marginBottom: '16px', \n padding: '16px', \n backgroundColor: theme.palette.type === 'dark' ? 'rgba(255,255,255,0.05)' : 'rgba(0,0,0,0.05)', \n borderRadius: '8px',\n border: `1px solid ${theme.palette.type === 'dark' ? 'rgba(255,255,255,0.12)' : 'rgba(0,0,0,0.12)'}`\n }}>\n <Typography variant=\"subtitle2\" style={{ \n marginBottom: '12px', \n fontWeight: 'bold',\n color: theme.palette.text.secondary,\n textTransform: 'uppercase',\n letterSpacing: '0.5px'\n }}>\n Metadata\n </Typography>\n {filteredEntries.map(([key, value], index) => (\n <div key={key} style={{ marginBottom: index < filteredEntries.length - 1 ? '12px' : '0' }}>\n <Typography variant=\"body2\" style={{ \n fontWeight: 'bold',\n textTransform: 'capitalize',\n color: theme.palette.primary.main,\n marginBottom: '4px'\n }}>\n {key}:\n </Typography>\n <Typography variant=\"body2\" style={{ \n lineHeight: '1.5',\n marginLeft: '8px',\n color: theme.palette.text.primary\n }}>\n {Array.isArray(value) ? value.join(', ') : String(value)}\n </Typography>\n </div>\n ))}\n </div>\n );\n};\n\n// Helper function to parse cursor rule content manually\nconst parseCursorContent = (content: string) => {\n return manualParseFrontmatter(content);\n};\n\n// Manual frontmatter parsing as fallback\nconst manualParseFrontmatter = (content: string) => {\n // Check if content starts with ---\n if (!content.trim().startsWith('---')) {\n return {\n frontmatter: undefined,\n content: content\n };\n }\n\n try {\n // Split by lines\n const lines = content.split('\\n');\n let frontmatterEndIndex = -1;\n \n // Find the closing ---\n for (let i = 1; i < lines.length; i++) {\n if (lines[i].trim() === '---') {\n frontmatterEndIndex = i;\n break;\n }\n }\n \n if (frontmatterEndIndex === -1) {\n return {\n frontmatter: undefined,\n content: content\n };\n }\n \n // Extract frontmatter lines (between the --- markers)\n const frontmatterLines = lines.slice(1, frontmatterEndIndex);\n const contentLines = lines.slice(frontmatterEndIndex + 1);\n \n // Parse the YAML manually (simple key: value pairs)\n const frontmatter: Record<string, any> = {};\n for (const line of frontmatterLines) {\n const trimmedLine = line.trim();\n if (trimmedLine && trimmedLine.includes(':')) {\n const colonIndex = trimmedLine.indexOf(':');\n const key = trimmedLine.substring(0, colonIndex).trim();\n const value = trimmedLine.substring(colonIndex + 1).trim();\n frontmatter[key] = value;\n }\n }\n \n return {\n frontmatter: Object.keys(frontmatter).length > 0 ? frontmatter : undefined,\n content: contentLines.join('\\n').trim()\n };\n } catch (error) {\n return {\n frontmatter: undefined,\n content: content\n };\n }\n};\n\nconst constructFileUrl = (gitUrl: string, filePath: string): string => {\n // Remove trailing slashes from gitUrl\n const cleanGitUrl = gitUrl.replace(/\\/+$/, '');\n \n // For GitHub URLs, convert to blob view\n if (cleanGitUrl.includes('github.com')) {\n return `${cleanGitUrl}/blob/main/${filePath}`;\n }\n \n // For GitLab URLs, convert to blob view\n if (cleanGitUrl.includes('gitlab.com')) {\n return `${cleanGitUrl}/-/blob/main/${filePath}`;\n }\n \n // For other git providers, try generic blob URL\n return `${cleanGitUrl}/blob/main/${filePath}`;\n};\n\nconst RuleComponent = ({ rule }: { rule: AIRule }) => {\n const styles = useStyles();\n const theme = useTheme();\n \n const renderCursorRule = (rule: CursorRule) => {\n // Parse the raw content to extract frontmatter and clean content\n const { frontmatter, content } = parseCursorContent(rule.content);\n \n return (\n <Accordion className={styles.ruleCard}>\n <AccordionSummary expandIcon={<ExpandMoreIcon />}>\n <div className={styles.ruleHeader}>\n <div className={styles.ruleHeaderContent}>\n <RuleTypeIcon type={rule.type} />\n <Typography variant=\"h6\">{rule.fileName}</Typography>\n <Chip label={rule.type} size=\"small\" className={styles.ruleType} />\n {frontmatter?.description && (\n <Typography variant=\"body2\" style={{ marginLeft: 8, color: theme.palette.text.secondary }}>\n {frontmatter.description}\n </Typography>\n )}\n </div>\n {rule.gitUrl && (\n <Tooltip title=\"Open file in repository\">\n <IconButton\n size=\"small\"\n onClick={(e) => {\n e.stopPropagation();\n window.open(constructFileUrl(rule.gitUrl!, rule.filePath), '_blank');\n }}\n >\n <LaunchIcon fontSize=\"small\" />\n </IconButton>\n </Tooltip>\n )}\n </div>\n </AccordionSummary>\n <AccordionDetails>\n <div>\n <div className={styles.ruleMetadata}>\n <Chip label={`Path: ${rule.filePath}`} size=\"small\" variant=\"outlined\" />\n {frontmatter?.globs && (\n <Chip label={`Globs: ${Array.isArray(frontmatter.globs) ? frontmatter.globs.join(', ') : frontmatter.globs}`} size=\"small\" variant=\"outlined\" />\n )}\n </div>\n {renderFrontmatter(theme, frontmatter)}\n <div className={styles.ruleContent}>\n <MarkdownContent content={content} />\n </div>\n </div>\n </AccordionDetails>\n </Accordion>\n );\n };\n\n const renderCopilotRule = (rule: CopilotRule) => (\n <Card className={styles.ruleCard}>\n <CardContent>\n <div className={styles.ruleHeader}>\n <div className={styles.ruleHeaderContent}>\n <RuleTypeIcon type={rule.type} />\n <Typography variant=\"h6\">Copilot Rule #{rule.order}</Typography>\n <Chip label={rule.type} size=\"small\" className={styles.ruleType} />\n </div>\n {rule.gitUrl && (\n <Tooltip title=\"Open file in repository\">\n <IconButton\n size=\"small\"\n onClick={() => window.open(constructFileUrl(rule.gitUrl!, rule.filePath), '_blank')}\n >\n <LaunchIcon fontSize=\"small\" />\n </IconButton>\n </Tooltip>\n )}\n </div>\n <div className={styles.ruleContent}>\n {rule.content}\n </div>\n </CardContent>\n </Card>\n );\n\n const renderClineRule = (rule: ClineRule) => (\n <Accordion className={styles.ruleCard}>\n <AccordionSummary expandIcon={<ExpandMoreIcon />}>\n <div className={styles.ruleHeader}>\n <div className={styles.ruleHeaderContent}>\n <RuleTypeIcon type={rule.type} />\n <Typography variant=\"h6\">{rule.title || rule.fileName}</Typography>\n <Chip label={rule.type} size=\"small\" className={styles.ruleType} />\n </div>\n {rule.gitUrl && (\n <Tooltip title=\"Open file in repository\">\n <IconButton\n size=\"small\"\n onClick={(e) => {\n e.stopPropagation();\n window.open(constructFileUrl(rule.gitUrl!, rule.filePath), '_blank');\n }}\n >\n <LaunchIcon fontSize=\"small\" />\n </IconButton>\n </Tooltip>\n )}\n </div>\n </AccordionSummary>\n <AccordionDetails>\n <div>\n <div className={styles.ruleMetadata}>\n <Chip label={`Path: ${rule.filePath}`} size=\"small\" variant=\"outlined\" />\n </div>\n <div className={styles.ruleContent}>\n <MarkdownContent content={rule.content} />\n </div>\n </div>\n </AccordionDetails>\n </Accordion>\n );\n\n switch (rule.type) {\n case AIRuleType.CURSOR:\n return renderCursorRule(rule as CursorRule);\n case AIRuleType.COPILOT:\n return renderCopilotRule(rule as CopilotRule);\n case AIRuleType.CLINE:\n return renderClineRule(rule as ClineRule);\n default:\n return null;\n }\n};\n\nexport const AIRulesComponent = ({ title = \"AI Coding Rules\" }: AIRulesComponentProps = {}) => {\n const { rulesByType, loading, error, hasGitUrl, totalRules, allowedRuleTypes, selectedRuleTypes, setSelectedRuleTypes } = useAiRules();\n const styles = useStyles();\n const handleTypeToggle = (type: AIRuleType, checked: boolean) => {\n const newTypes = checked \n ? [...selectedRuleTypes, type]\n : selectedRuleTypes.filter(t => t !== type);\n setSelectedRuleTypes(newTypes);\n };\n\n if (loading) {\n return (\n <InfoCard title={title}>\n <Progress />\n </InfoCard>\n );\n }\n\n if (!hasGitUrl) {\n return (\n <InfoCard title={title}>\n <EmptyState\n missing=\"content\"\n title=\"No Git Repository\"\n description=\"This component doesn't have a Git source URL configured.\"\n />\n </InfoCard>\n );\n }\n\n if (error) {\n return (\n <InfoCard title={title}>\n <EmptyState\n missing=\"content\"\n title=\"Error Loading Rules\"\n description={error}\n />\n </InfoCard>\n );\n }\n\n if (totalRules === 0) {\n return (\n <InfoCard title={title}>\n <EmptyState\n missing=\"content\"\n title=\"No AI Rules Found\"\n description=\"No AI rules were found in this repository for the selected rule types.\"\n action={\n <Button\n variant=\"outlined\"\n onClick={() => setSelectedRuleTypes(allowedRuleTypes)}\n >\n Reset Filters\n </Button>\n }\n />\n </InfoCard>\n );\n }\n\n return (\n <InfoCard title={title} className={styles.root}>\n <div className={styles.filterSection}>\n <Typography variant=\"h6\" gutterBottom>\n Filter Rule Types\n </Typography>\n <div className={styles.filterContainer}>\n {allowedRuleTypes.map(type => (\n <FormControlLabel\n key={type}\n control={\n <Checkbox\n checked={selectedRuleTypes.includes(type)}\n onChange={(e) => handleTypeToggle(type, e.target.checked)}\n />\n }\n label={type.charAt(0).toUpperCase() + type.slice(1)}\n />\n ))}\n </div>\n </div>\n\n <div className={styles.statsContainer}>\n <Card className={styles.statCard}>\n <CardContent>\n <Typography variant=\"h4\">{totalRules}</Typography>\n <Typography color=\"textSecondary\">Total Rules</Typography>\n </CardContent>\n </Card>\n {Object.entries(rulesByType).map(([type, typeRules]) => (\n <Card key={type} className={styles.statCard}>\n <CardContent>\n <Typography variant=\"h4\">{typeRules.length}</Typography>\n <Typography color=\"textSecondary\">{type.charAt(0).toUpperCase() + type.slice(1)}</Typography>\n </CardContent>\n </Card>\n ))}\n </div>\n\n {Object.entries(rulesByType).map(([type, typeRules]) => (\n <div key={type}>\n <Typography variant=\"h5\" gutterBottom style={{ marginTop: 16 }}>\n {type.charAt(0).toUpperCase() + type.slice(1)} Rules ({typeRules.length})\n </Typography>\n {typeRules.map(rule => (\n <RuleComponent key={rule.id} rule={rule} />\n ))}\n </div>\n ))}\n </InfoCard>\n );\n};"],"names":["rule"],"mappings":"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"}
@@ -0,0 +1,105 @@
1
+ import { useEntity } from '@backstage/plugin-catalog-react';
2
+ import { AIRuleType } from '../types.esm.js';
3
+ import { useState, useMemo, useEffect, useCallback } from 'react';
4
+ import { useApi, configApiRef, discoveryApiRef, fetchApiRef } from '@backstage/core-plugin-api';
5
+
6
+ const useAiRules = () => {
7
+ const { entity } = useEntity();
8
+ const configApi = useApi(configApiRef);
9
+ const discoveryApi = useApi(discoveryApiRef);
10
+ const fetchApi = useApi(fetchApiRef);
11
+ const [rules, setRules] = useState([]);
12
+ const [loading, setLoading] = useState(true);
13
+ const [error, setError] = useState(null);
14
+ const [selectedRuleTypes, setSelectedRuleTypes] = useState([]);
15
+ const allowedRuleTypes = useMemo(() => {
16
+ return configApi.getOptionalStringArray("aiRules.allowedRuleTypes") || [AIRuleType.CURSOR, AIRuleType.COPILOT];
17
+ }, [configApi]);
18
+ const entityData = useMemo(() => {
19
+ const sourceAnnotation = entity.metadata?.annotations?.["backstage.io/source-location"] || "";
20
+ const hasGitUrl = sourceAnnotation.startsWith("url:");
21
+ let gitUrl = hasGitUrl ? sourceAnnotation.substring(4) : void 0;
22
+ if (gitUrl) {
23
+ gitUrl = gitUrl.replace(/\/+$/, "");
24
+ const treeMatch = gitUrl.match(/^(.+)\/tree\/([^/]+)(?:\/(.+))?$/);
25
+ const blobMatch = gitUrl.match(/^(.+)\/blob\/([^/]+)(?:\/(.+))?$/);
26
+ if (treeMatch) {
27
+ gitUrl = treeMatch[1];
28
+ }
29
+ if (blobMatch) {
30
+ gitUrl = blobMatch[1];
31
+ }
32
+ }
33
+ return {
34
+ kind: entity.kind,
35
+ namespace: entity.metadata.namespace || "default",
36
+ name: entity.metadata.name,
37
+ sourceAnnotation,
38
+ hasGitUrl,
39
+ gitUrl
40
+ };
41
+ }, [entity.kind, entity.metadata.namespace, entity.metadata.name, entity.metadata?.annotations?.["backstage.io/source-location"]]);
42
+ useEffect(() => {
43
+ setSelectedRuleTypes(allowedRuleTypes);
44
+ }, []);
45
+ const fetchAiRules = useCallback(async (ruleTypes) => {
46
+ if (!entityData.hasGitUrl || !entityData.gitUrl) {
47
+ setLoading(false);
48
+ return;
49
+ }
50
+ try {
51
+ setLoading(true);
52
+ setError(null);
53
+ const baseUrl = await discoveryApi.getBaseUrl("ai-rules");
54
+ const url = new URL(`${baseUrl}/rules`);
55
+ url.searchParams.append("gitUrl", entityData.gitUrl);
56
+ if (ruleTypes.length > 0) {
57
+ url.searchParams.append("ruleTypes", ruleTypes.join(","));
58
+ }
59
+ console.log("Fetching AI rules from:", url.toString());
60
+ const response = await fetchApi.fetch(url.toString());
61
+ if (!response.ok) {
62
+ throw new Error(`Failed to fetch AI rules: ${response.statusText}`);
63
+ }
64
+ const data = await response.json();
65
+ console.log("Received AI rules:", data);
66
+ setRules(data.rules);
67
+ } catch (err) {
68
+ const errorMessage = err instanceof Error ? err.message : "Unknown error occurred";
69
+ console.error("Error fetching AI rules:", errorMessage);
70
+ setError(errorMessage);
71
+ setRules([]);
72
+ } finally {
73
+ setLoading(false);
74
+ }
75
+ }, [entityData.hasGitUrl, entityData.gitUrl, discoveryApi, fetchApi]);
76
+ useEffect(() => {
77
+ if (selectedRuleTypes.length > 0) {
78
+ fetchAiRules(selectedRuleTypes);
79
+ }
80
+ }, [fetchAiRules, selectedRuleTypes]);
81
+ const rulesByType = useMemo(() => {
82
+ return rules.reduce((acc, rule) => {
83
+ if (!acc[rule.type]) {
84
+ acc[rule.type] = [];
85
+ }
86
+ acc[rule.type].push(rule);
87
+ return acc;
88
+ }, {});
89
+ }, [rules]);
90
+ return {
91
+ rules,
92
+ rulesByType,
93
+ loading,
94
+ error,
95
+ hasGitUrl: entityData.hasGitUrl,
96
+ componentName: entityData.name,
97
+ allowedRuleTypes,
98
+ selectedRuleTypes,
99
+ setSelectedRuleTypes,
100
+ totalRules: rules.length
101
+ };
102
+ };
103
+
104
+ export { useAiRules };
105
+ //# sourceMappingURL=useAiRules.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useAiRules.esm.js","sources":["../../src/hooks/useAiRules.ts"],"sourcesContent":["import { useEntity } from '@backstage/plugin-catalog-react';\nimport { AIRuleType, AIRulesResponse, AIRule } from '../types';\nimport { useState, useEffect, useMemo, useCallback } from 'react';\nimport { useApi, configApiRef, discoveryApiRef, fetchApiRef } from '@backstage/core-plugin-api';\n\nexport const useAiRules = () => {\n const { entity } = useEntity();\n const configApi = useApi(configApiRef);\n const discoveryApi = useApi(discoveryApiRef);\n const fetchApi = useApi(fetchApiRef);\n \n const [rules, setRules] = useState<AIRule[]>([]);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n const [selectedRuleTypes, setSelectedRuleTypes] = useState<AIRuleType[]>([]);\n\n // Stabilize allowed rule types\n const allowedRuleTypes = useMemo(() => {\n return configApi.getOptionalStringArray('aiRules.allowedRuleTypes') as AIRuleType[] || [AIRuleType.CURSOR, AIRuleType.COPILOT];\n }, [configApi]);\n\n // Extract stable entity properties\n const entityData = useMemo(() => {\n const sourceAnnotation = entity.metadata?.annotations?.['backstage.io/source-location'] || '';\n const hasGitUrl = sourceAnnotation.startsWith('url:');\n \n let gitUrl = hasGitUrl ? sourceAnnotation.substring(4) : undefined;\n if (gitUrl) {\n // Remove trailing slashes and normalize URL\n gitUrl = gitUrl.replace(/\\/+$/, '');\n // Handle GitHub tree/blob URLs - extract base repo URL\n const treeMatch = gitUrl.match(/^(.+)\\/tree\\/([^/]+)(?:\\/(.+))?$/);\n const blobMatch = gitUrl.match(/^(.+)\\/blob\\/([^/]+)(?:\\/(.+))?$/);\n if (treeMatch) {\n gitUrl = treeMatch[1]; // Just the base repo URL\n }\n if (blobMatch) {\n gitUrl = blobMatch[1]; // Just the base repo URL \n }\n }\n\n return {\n kind: entity.kind,\n namespace: entity.metadata.namespace || 'default',\n name: entity.metadata.name,\n sourceAnnotation,\n hasGitUrl,\n gitUrl,\n };\n }, [entity.kind, entity.metadata.namespace, entity.metadata.name, entity.metadata?.annotations?.['backstage.io/source-location']]);\n\n // Initialize selected rule types only once\n useEffect(() => {\n setSelectedRuleTypes(allowedRuleTypes);\n }, []); // Empty dependency - only run once\n\n // Stable fetch function\n const fetchAiRules = useCallback(async (ruleTypes: AIRuleType[]) => {\n if (!entityData.hasGitUrl || !entityData.gitUrl) {\n setLoading(false);\n return;\n }\n\n try {\n setLoading(true);\n setError(null);\n \n const baseUrl = await discoveryApi.getBaseUrl('ai-rules');\n const url = new URL(`${baseUrl}/rules`);\n \n // Send the Git URL directly to the backend\n url.searchParams.append('gitUrl', entityData.gitUrl);\n if (ruleTypes.length > 0) {\n url.searchParams.append('ruleTypes', ruleTypes.join(','));\n }\n\n console.log('Fetching AI rules from:', url.toString());\n\n const response = await fetchApi.fetch(url.toString());\n if (!response.ok) {\n throw new Error(`Failed to fetch AI rules: ${response.statusText}`);\n }\n\n const data: AIRulesResponse = await response.json();\n console.log('Received AI rules:', data);\n setRules(data.rules);\n } catch (err) {\n const errorMessage = err instanceof Error ? err.message : 'Unknown error occurred';\n console.error('Error fetching AI rules:', errorMessage);\n setError(errorMessage);\n setRules([]);\n } finally {\n setLoading(false);\n }\n }, [entityData.hasGitUrl, entityData.gitUrl, discoveryApi, fetchApi]);\n\n // Fetch rules when dependencies change\n useEffect(() => {\n if (selectedRuleTypes.length > 0) {\n fetchAiRules(selectedRuleTypes);\n }\n }, [fetchAiRules, selectedRuleTypes]);\n\n const rulesByType = useMemo(() => {\n return rules.reduce((acc, rule) => {\n if (!acc[rule.type]) {\n acc[rule.type] = [];\n }\n acc[rule.type].push(rule);\n return acc;\n }, {} as Record<AIRuleType, AIRule[]>);\n }, [rules]);\n\n return {\n rules,\n rulesByType,\n loading,\n error,\n hasGitUrl: entityData.hasGitUrl,\n componentName: entityData.name,\n allowedRuleTypes,\n selectedRuleTypes,\n setSelectedRuleTypes,\n totalRules: rules.length,\n };\n};"],"names":[],"mappings":";;;;;AAKO,MAAM,aAAa,MAAM;AAC9B,EAAM,MAAA,EAAE,MAAO,EAAA,GAAI,SAAU,EAAA;AAC7B,EAAM,MAAA,SAAA,GAAY,OAAO,YAAY,CAAA;AACrC,EAAM,MAAA,YAAA,GAAe,OAAO,eAAe,CAAA;AAC3C,EAAM,MAAA,QAAA,GAAW,OAAO,WAAW,CAAA;AAEnC,EAAA,MAAM,CAAC,KAAO,EAAA,QAAQ,CAAI,GAAA,QAAA,CAAmB,EAAE,CAAA;AAC/C,EAAA,MAAM,CAAC,OAAA,EAAS,UAAU,CAAA,GAAI,SAAS,IAAI,CAAA;AAC3C,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAI,SAAwB,IAAI,CAAA;AACtD,EAAA,MAAM,CAAC,iBAAmB,EAAA,oBAAoB,CAAI,GAAA,QAAA,CAAuB,EAAE,CAAA;AAG3E,EAAM,MAAA,gBAAA,GAAmB,QAAQ,MAAM;AACrC,IAAO,OAAA,SAAA,CAAU,uBAAuB,0BAA0B,CAAA,IAAqB,CAAC,UAAW,CAAA,MAAA,EAAQ,WAAW,OAAO,CAAA;AAAA,GAC/H,EAAG,CAAC,SAAS,CAAC,CAAA;AAGd,EAAM,MAAA,UAAA,GAAa,QAAQ,MAAM;AAC/B,IAAA,MAAM,gBAAmB,GAAA,MAAA,CAAO,QAAU,EAAA,WAAA,GAAc,8BAA8B,CAAK,IAAA,EAAA;AAC3F,IAAM,MAAA,SAAA,GAAY,gBAAiB,CAAA,UAAA,CAAW,MAAM,CAAA;AAEpD,IAAA,IAAI,MAAS,GAAA,SAAA,GAAY,gBAAiB,CAAA,SAAA,CAAU,CAAC,CAAI,GAAA,KAAA,CAAA;AACzD,IAAA,IAAI,MAAQ,EAAA;AAEV,MAAS,MAAA,GAAA,MAAA,CAAO,OAAQ,CAAA,MAAA,EAAQ,EAAE,CAAA;AAElC,MAAM,MAAA,SAAA,GAAY,MAAO,CAAA,KAAA,CAAM,kCAAkC,CAAA;AACjE,MAAM,MAAA,SAAA,GAAY,MAAO,CAAA,KAAA,CAAM,kCAAkC,CAAA;AACjE,MAAA,IAAI,SAAW,EAAA;AACb,QAAA,MAAA,GAAS,UAAU,CAAC,CAAA;AAAA;AAEtB,MAAA,IAAI,SAAW,EAAA;AACb,QAAA,MAAA,GAAS,UAAU,CAAC,CAAA;AAAA;AACtB;AAGF,IAAO,OAAA;AAAA,MACL,MAAM,MAAO,CAAA,IAAA;AAAA,MACb,SAAA,EAAW,MAAO,CAAA,QAAA,CAAS,SAAa,IAAA,SAAA;AAAA,MACxC,IAAA,EAAM,OAAO,QAAS,CAAA,IAAA;AAAA,MACtB,gBAAA;AAAA,MACA,SAAA;AAAA,MACA;AAAA,KACF;AAAA,GACC,EAAA,CAAC,MAAO,CAAA,IAAA,EAAM,OAAO,QAAS,CAAA,SAAA,EAAW,MAAO,CAAA,QAAA,CAAS,MAAM,MAAO,CAAA,QAAA,EAAU,WAAc,GAAA,8BAA8B,CAAC,CAAC,CAAA;AAGjI,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,oBAAA,CAAqB,gBAAgB,CAAA;AAAA,GACvC,EAAG,EAAE,CAAA;AAGL,EAAM,MAAA,YAAA,GAAe,WAAY,CAAA,OAAO,SAA4B,KAAA;AAClE,IAAA,IAAI,CAAC,UAAA,CAAW,SAAa,IAAA,CAAC,WAAW,MAAQ,EAAA;AAC/C,MAAA,UAAA,CAAW,KAAK,CAAA;AAChB,MAAA;AAAA;AAGF,IAAI,IAAA;AACF,MAAA,UAAA,CAAW,IAAI,CAAA;AACf,MAAA,QAAA,CAAS,IAAI,CAAA;AAEb,MAAA,MAAM,OAAU,GAAA,MAAM,YAAa,CAAA,UAAA,CAAW,UAAU,CAAA;AACxD,MAAA,MAAM,GAAM,GAAA,IAAI,GAAI,CAAA,CAAA,EAAG,OAAO,CAAQ,MAAA,CAAA,CAAA;AAGtC,MAAA,GAAA,CAAI,YAAa,CAAA,MAAA,CAAO,QAAU,EAAA,UAAA,CAAW,MAAM,CAAA;AACnD,MAAI,IAAA,SAAA,CAAU,SAAS,CAAG,EAAA;AACxB,QAAA,GAAA,CAAI,aAAa,MAAO,CAAA,WAAA,EAAa,SAAU,CAAA,IAAA,CAAK,GAAG,CAAC,CAAA;AAAA;AAG1D,MAAA,OAAA,CAAQ,GAAI,CAAA,yBAAA,EAA2B,GAAI,CAAA,QAAA,EAAU,CAAA;AAErD,MAAA,MAAM,WAAW,MAAM,QAAA,CAAS,KAAM,CAAA,GAAA,CAAI,UAAU,CAAA;AACpD,MAAI,IAAA,CAAC,SAAS,EAAI,EAAA;AAChB,QAAA,MAAM,IAAI,KAAA,CAAM,CAA6B,0BAAA,EAAA,QAAA,CAAS,UAAU,CAAE,CAAA,CAAA;AAAA;AAGpE,MAAM,MAAA,IAAA,GAAwB,MAAM,QAAA,CAAS,IAAK,EAAA;AAClD,MAAQ,OAAA,CAAA,GAAA,CAAI,sBAAsB,IAAI,CAAA;AACtC,MAAA,QAAA,CAAS,KAAK,KAAK,CAAA;AAAA,aACZ,GAAK,EAAA;AACZ,MAAA,MAAM,YAAe,GAAA,GAAA,YAAe,KAAQ,GAAA,GAAA,CAAI,OAAU,GAAA,wBAAA;AAC1D,MAAQ,OAAA,CAAA,KAAA,CAAM,4BAA4B,YAAY,CAAA;AACtD,MAAA,QAAA,CAAS,YAAY,CAAA;AACrB,MAAA,QAAA,CAAS,EAAE,CAAA;AAAA,KACX,SAAA;AACA,MAAA,UAAA,CAAW,KAAK,CAAA;AAAA;AAClB,GACF,EAAG,CAAC,UAAW,CAAA,SAAA,EAAW,WAAW,MAAQ,EAAA,YAAA,EAAc,QAAQ,CAAC,CAAA;AAGpE,EAAA,SAAA,CAAU,MAAM;AACd,IAAI,IAAA,iBAAA,CAAkB,SAAS,CAAG,EAAA;AAChC,MAAA,YAAA,CAAa,iBAAiB,CAAA;AAAA;AAChC,GACC,EAAA,CAAC,YAAc,EAAA,iBAAiB,CAAC,CAAA;AAEpC,EAAM,MAAA,WAAA,GAAc,QAAQ,MAAM;AAChC,IAAA,OAAO,KAAM,CAAA,MAAA,CAAO,CAAC,GAAA,EAAK,IAAS,KAAA;AACjC,MAAA,IAAI,CAAC,GAAA,CAAI,IAAK,CAAA,IAAI,CAAG,EAAA;AACnB,QAAI,GAAA,CAAA,IAAA,CAAK,IAAI,CAAA,GAAI,EAAC;AAAA;AAEpB,MAAA,GAAA,CAAI,IAAK,CAAA,IAAI,CAAE,CAAA,IAAA,CAAK,IAAI,CAAA;AACxB,MAAO,OAAA,GAAA;AAAA,KACT,EAAG,EAAkC,CAAA;AAAA,GACvC,EAAG,CAAC,KAAK,CAAC,CAAA;AAEV,EAAO,OAAA;AAAA,IACL,KAAA;AAAA,IACA,WAAA;AAAA,IACA,OAAA;AAAA,IACA,KAAA;AAAA,IACA,WAAW,UAAW,CAAA,SAAA;AAAA,IACtB,eAAe,UAAW,CAAA,IAAA;AAAA,IAC1B,gBAAA;AAAA,IACA,iBAAA;AAAA,IACA,oBAAA;AAAA,IACA,YAAY,KAAM,CAAA;AAAA,GACpB;AACF;;;;"}
@@ -0,0 +1,64 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { Entity } from '@backstage/catalog-model';
3
+ import * as _backstage_core_plugin_api from '@backstage/core-plugin-api';
4
+
5
+ interface AIRulesComponentProps {
6
+ title?: string;
7
+ }
8
+ declare const isAIRulesAvailable: (entity: Entity) => boolean;
9
+
10
+ declare const aiRulesPlugin: _backstage_core_plugin_api.BackstagePlugin<{
11
+ root: _backstage_core_plugin_api.RouteRef<undefined>;
12
+ }, {}, {}>;
13
+ declare const AIRulesComponent: ({ title }?: AIRulesComponentProps) => react_jsx_runtime.JSX.Element;
14
+
15
+ declare enum AIRuleType {
16
+ CURSOR = "cursor",
17
+ COPILOT = "copilot",
18
+ CLINE = "cline"
19
+ }
20
+ interface CursorRule {
21
+ type: AIRuleType.CURSOR;
22
+ id: string;
23
+ filePath: string;
24
+ fileName: string;
25
+ gitUrl?: string;
26
+ description?: string;
27
+ globs?: string[];
28
+ alwaysApply?: boolean;
29
+ frontmatter?: Record<string, any>;
30
+ content: string;
31
+ }
32
+ interface CopilotRule {
33
+ type: AIRuleType.COPILOT;
34
+ id: string;
35
+ filePath: string;
36
+ fileName: string;
37
+ gitUrl?: string;
38
+ content: string;
39
+ order: number;
40
+ }
41
+ interface ClineRule {
42
+ type: AIRuleType.CLINE;
43
+ id: string;
44
+ filePath: string;
45
+ fileName: string;
46
+ gitUrl?: string;
47
+ content: string;
48
+ title?: string;
49
+ sections?: Array<{
50
+ title: string;
51
+ content: string;
52
+ }>;
53
+ }
54
+ type AIRule = CursorRule | CopilotRule | ClineRule;
55
+ interface AIRulesResponse {
56
+ rules: AIRule[];
57
+ totalCount: number;
58
+ ruleTypes: AIRuleType[];
59
+ }
60
+ interface AIRulesConfig {
61
+ allowedRuleTypes?: AIRuleType[];
62
+ }
63
+
64
+ export { type AIRule, AIRuleType, AIRulesComponent, type AIRulesComponentProps, type AIRulesConfig, type AIRulesResponse, type ClineRule, type CopilotRule, type CursorRule, aiRulesPlugin, isAIRulesAvailable };
@@ -0,0 +1,4 @@
1
+ export { AIRulesComponent, aiRulesPlugin } from './plugin.esm.js';
2
+ export { AIRuleType } from './types.esm.js';
3
+ export { isAIRulesAvailable } from './components/AiRulesComponent/AiRulesComponent.esm.js';
4
+ //# sourceMappingURL=index.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.esm.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;"}
@@ -0,0 +1,20 @@
1
+ import { createPlugin, createComponentExtension } from '@backstage/core-plugin-api';
2
+ import { rootRouteRef } from './routes.esm.js';
3
+
4
+ const aiRulesPlugin = createPlugin({
5
+ id: "ai-rules",
6
+ routes: {
7
+ root: rootRouteRef
8
+ }
9
+ });
10
+ const AIRulesComponent = aiRulesPlugin.provide(
11
+ createComponentExtension({
12
+ name: "AIRulesComponent",
13
+ component: {
14
+ lazy: () => import('./components/AiRulesComponent/AiRulesComponent.esm.js').then((m) => m.AIRulesComponent)
15
+ }
16
+ })
17
+ );
18
+
19
+ export { AIRulesComponent, aiRulesPlugin };
20
+ //# sourceMappingURL=plugin.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.esm.js","sources":["../src/plugin.ts"],"sourcesContent":["import { createPlugin, createComponentExtension } from '@backstage/core-plugin-api';\nimport { rootRouteRef } from './routes';\n\nexport const aiRulesPlugin = createPlugin({\n id: 'ai-rules',\n routes: {\n root: rootRouteRef,\n },\n});\n\nexport const AIRulesComponent = aiRulesPlugin.provide(\n createComponentExtension({\n name: 'AIRulesComponent',\n component: {\n lazy: () => import('./components/AiRulesComponent/AiRulesComponent').then(m => m.AIRulesComponent),\n },\n }),\n);"],"names":[],"mappings":";;;AAGO,MAAM,gBAAgB,YAAa,CAAA;AAAA,EACxC,EAAI,EAAA,UAAA;AAAA,EACJ,MAAQ,EAAA;AAAA,IACN,IAAM,EAAA;AAAA;AAEV,CAAC;AAEM,MAAM,mBAAmB,aAAc,CAAA,OAAA;AAAA,EAC5C,wBAAyB,CAAA;AAAA,IACvB,IAAM,EAAA,kBAAA;AAAA,IACN,SAAW,EAAA;AAAA,MACT,IAAA,EAAM,MAAM,OAAO,uDAAgD,EAAE,IAAK,CAAA,CAAA,CAAA,KAAK,EAAE,gBAAgB;AAAA;AACnG,GACD;AACH;;;;"}
@@ -0,0 +1,8 @@
1
+ import { createRouteRef } from '@backstage/core-plugin-api';
2
+
3
+ const rootRouteRef = createRouteRef({
4
+ id: "ai-rules"
5
+ });
6
+
7
+ export { rootRouteRef };
8
+ //# sourceMappingURL=routes.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.esm.js","sources":["../src/routes.ts"],"sourcesContent":["import { createRouteRef } from '@backstage/core-plugin-api';\n\nexport const rootRouteRef = createRouteRef({\n id: 'ai-rules',\n});"],"names":[],"mappings":";;AAEO,MAAM,eAAe,cAAe,CAAA;AAAA,EACzC,EAAI,EAAA;AACN,CAAC;;;;"}
@@ -0,0 +1,9 @@
1
+ var AIRuleType = /* @__PURE__ */ ((AIRuleType2) => {
2
+ AIRuleType2["CURSOR"] = "cursor";
3
+ AIRuleType2["COPILOT"] = "copilot";
4
+ AIRuleType2["CLINE"] = "cline";
5
+ return AIRuleType2;
6
+ })(AIRuleType || {});
7
+
8
+ export { AIRuleType };
9
+ //# sourceMappingURL=types.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.esm.js","sources":["../src/types.ts"],"sourcesContent":["export enum AIRuleType {\n CURSOR = 'cursor',\n COPILOT = 'copilot', \n CLINE = 'cline',\n}\n\nexport interface CursorRule {\n type: AIRuleType.CURSOR;\n id: string;\n filePath: string;\n fileName: string;\n gitUrl?: string;\n description?: string;\n globs?: string[];\n alwaysApply?: boolean;\n frontmatter?: Record<string, any>;\n content: string;\n}\n\nexport interface CopilotRule {\n type: AIRuleType.COPILOT;\n id: string;\n filePath: string;\n fileName: string;\n gitUrl?: string;\n content: string;\n order: number; // Position in the file\n}\n\nexport interface ClineRule {\n type: AIRuleType.CLINE;\n id: string;\n filePath: string;\n fileName: string;\n gitUrl?: string;\n content: string;\n title?: string;\n sections?: Array<{\n title: string;\n content: string;\n }>;\n}\n\nexport type AIRule = CursorRule | CopilotRule | ClineRule;\n\nexport interface AIRulesResponse {\n rules: AIRule[];\n totalCount: number;\n ruleTypes: AIRuleType[];\n}\n\nexport interface AIRulesConfig {\n allowedRuleTypes?: AIRuleType[];\n}"],"names":["AIRuleType"],"mappings":"AAAY,IAAA,UAAA,qBAAAA,WAAL,KAAA;AACL,EAAAA,YAAA,QAAS,CAAA,GAAA,QAAA;AACT,EAAAA,YAAA,SAAU,CAAA,GAAA,SAAA;AACV,EAAAA,YAAA,OAAQ,CAAA,GAAA,OAAA;AAHE,EAAAA,OAAAA,WAAAA;AAAA,CAAA,EAAA,UAAA,IAAA,EAAA;;;;"}
package/package.json ADDED
@@ -0,0 +1,82 @@
1
+ {
2
+ "name": "@terasky/backstage-plugin-ai-rules",
3
+ "description": "AI Rules Visualizer Plugin for Backstage",
4
+ "version": "0.1.0",
5
+ "main": "dist/index.esm.js",
6
+ "types": "dist/index.d.ts",
7
+ "license": "Apache-2.0",
8
+ "configSchema": "config.d.ts",
9
+ "publishConfig": {
10
+ "access": "public",
11
+ "main": "dist/index.esm.js",
12
+ "types": "dist/index.d.ts"
13
+ },
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "git+https://github.com/TeraSky-OSS/backstage-plugins.git",
17
+ "directory": "plugins/ai-rules-plugin"
18
+ },
19
+ "homepage": "https://terasky.com",
20
+ "backstage": {
21
+ "role": "frontend-plugin",
22
+ "pluginId": "ai-rules",
23
+ "pluginPackages": [
24
+ "@terasky/backstage-plugin-ai-rules",
25
+ "@terasky/backstage-plugin-ai-rules-backend"
26
+ ]
27
+ },
28
+ "sideEffects": false,
29
+ "scripts": {
30
+ "start": "backstage-cli package start",
31
+ "build": "backstage-cli package build",
32
+ "lint": "backstage-cli package lint",
33
+ "test": "backstage-cli package test",
34
+ "clean": "backstage-cli package clean",
35
+ "prepack": "backstage-cli package prepack",
36
+ "postpack": "backstage-cli package postpack"
37
+ },
38
+ "dependencies": {
39
+ "@backstage/catalog-model": "^1.7.4",
40
+ "@backstage/core-components": "^0.17.3",
41
+ "@backstage/core-plugin-api": "^1.10.8",
42
+ "@backstage/plugin-catalog-react": "^1.19.0",
43
+ "@backstage/theme": "^0.6.6",
44
+ "@material-ui/core": "^4.12.4",
45
+ "@material-ui/icons": "^4.11.3",
46
+ "@material-ui/lab": "4.0.0-alpha.61"
47
+ },
48
+ "peerDependencies": {
49
+ "react": "^16.13.1 || ^17.0.0 || ^18.0.0"
50
+ },
51
+ "devDependencies": {
52
+ "@backstage/cli": "^0.33.0",
53
+ "@backstage/core-app-api": "^1.17.1",
54
+ "@backstage/dev-utils": "^1.1.11",
55
+ "@backstage/test-utils": "^1.7.9",
56
+ "@testing-library/jest-dom": "^6.0.0",
57
+ "@testing-library/react": "^14.0.0",
58
+ "@testing-library/user-event": "^14.0.0",
59
+ "msw": "^1.0.0",
60
+ "typescript": "^5.8.3"
61
+ },
62
+ "files": [
63
+ "dist",
64
+ "config.d.ts"
65
+ ],
66
+ "keywords": [
67
+ "backstage",
68
+ "ai",
69
+ "rules",
70
+ "cursor",
71
+ "copilot",
72
+ "cline"
73
+ ],
74
+ "typesVersions": {
75
+ "*": {
76
+ "package.json": [
77
+ "package.json"
78
+ ]
79
+ }
80
+ },
81
+ "module": "./dist/index.esm.js"
82
+ }