@rigstate/mcp 0.4.2
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/.env.example +8 -0
- package/README.md +352 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +3445 -0
- package/dist/index.js.map +1 -0
- package/package.json +43 -0
- package/roadmap.json +531 -0
- package/src/agents/the-scribe.ts +122 -0
- package/src/index.ts +1792 -0
- package/src/lib/supabase.ts +120 -0
- package/src/lib/tool-registry.ts +134 -0
- package/src/lib/types.ts +415 -0
- package/src/lib/utils.ts +10 -0
- package/src/resources/project-morals.ts +92 -0
- package/src/tools/arch-tools.ts +166 -0
- package/src/tools/archaeological-scan.ts +335 -0
- package/src/tools/check-agent-bridge.ts +169 -0
- package/src/tools/check-rules-sync.ts +85 -0
- package/src/tools/complete-roadmap-task.ts +96 -0
- package/src/tools/generate-professional-pdf.ts +232 -0
- package/src/tools/get-latest-decisions.ts +130 -0
- package/src/tools/get-next-roadmap-step.ts +76 -0
- package/src/tools/get-project-context.ts +163 -0
- package/src/tools/index.ts +17 -0
- package/src/tools/list-features.ts +67 -0
- package/src/tools/list-roadmap-tasks.ts +61 -0
- package/src/tools/pending-tasks.ts +228 -0
- package/src/tools/planning-tools.ts +123 -0
- package/src/tools/query-brain.ts +125 -0
- package/src/tools/research-tools.ts +149 -0
- package/src/tools/run-architecture-audit.ts +203 -0
- package/src/tools/save-decision.ts +77 -0
- package/src/tools/security-tools.ts +82 -0
- package/src/tools/submit-idea.ts +66 -0
- package/src/tools/sync-ide-rules.ts +76 -0
- package/src/tools/teacher-mode.ts +171 -0
- package/src/tools/ui-tools.ts +191 -0
- package/src/tools/update-roadmap.ts +105 -0
- package/tsconfig.json +29 -0
- package/tsup.config.ts +16 -0
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { AnalyzeUiComponentInput, ApplyDesignSystemInput, FetchUiLibraryDocsInput } from '../lib/types.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Linus's Tool: Analyze UI Component
|
|
7
|
+
* Checks for design deviations, hardcoded values, and accessibility gaps.
|
|
8
|
+
*/
|
|
9
|
+
export async function analyzeUiComponent(input: AnalyzeUiComponentInput) {
|
|
10
|
+
const { filePath } = input;
|
|
11
|
+
|
|
12
|
+
if (!await fileExists(filePath)) {
|
|
13
|
+
throw new Error(`File not found: ${filePath}`);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
17
|
+
const issues: string[] = [];
|
|
18
|
+
const score = 100;
|
|
19
|
+
|
|
20
|
+
// 1. Check for Hardcoded Hex Colors
|
|
21
|
+
// Regex matches text-[#...], bg-[#...], or just #... inside classNames often
|
|
22
|
+
const hexRegex = /#([0-9A-Fa-f]{3}){1,2}\b/g;
|
|
23
|
+
const hexMatches = content.match(hexRegex);
|
|
24
|
+
if (hexMatches) {
|
|
25
|
+
// Filter out ID selectors in CSS if file is .css
|
|
26
|
+
const isCss = filePath.endsWith('.css');
|
|
27
|
+
const uniqueHex = [...new Set(hexMatches)];
|
|
28
|
+
|
|
29
|
+
for (const hex of uniqueHex) {
|
|
30
|
+
// In CSS, #id is valid. In TSX className, usually implies hardcoded color.
|
|
31
|
+
if (!isCss || content.includes(`color: ${hex}`) || content.includes(`background: ${hex}`)) {
|
|
32
|
+
issues.push(`Hardcoded color found: ${hex}. Use design tokens (e.g., bg-background, text-primary).`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// 2. Check for missing Alt text on images (TSX only)
|
|
38
|
+
if (filePath.endsWith('.tsx')) {
|
|
39
|
+
const imgRegex = /<img(?![^>]*\balt=)[^>]*>/g;
|
|
40
|
+
if (imgRegex.test(content)) {
|
|
41
|
+
issues.push('Accessibility: <img> tag detected without "alt" attribute.');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Check for Image component (Next.js)
|
|
45
|
+
const nextImgRegex = /<Image(?![^>]*\balt=)[^>]*>/g;
|
|
46
|
+
if (nextImgRegex.test(content)) {
|
|
47
|
+
issues.push('Accessibility: <Image> component missing "alt" prop.');
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// 3. Check for arbitrary values (Tailwind)
|
|
52
|
+
// Matches w-[123px] or p-[10px] etc.
|
|
53
|
+
const arbitraryRegex = /\w-\[\d+px\]/g;
|
|
54
|
+
if (arbitraryRegex.test(content)) {
|
|
55
|
+
issues.push('maintainability: Arbitrary pixel values detected (e.g., w-[50px]). Use standard spacing scale (w-12).');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
filePath: path.basename(filePath),
|
|
60
|
+
issueCount: issues.length,
|
|
61
|
+
issues: issues.length > 0 ? issues : ['â
No obvious design violations found. Great job!']
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Linus's Tool: Apply Design System
|
|
67
|
+
* Auto-fixes common design system violations (e.g. replacing hardcoded logic).
|
|
68
|
+
*/
|
|
69
|
+
export async function applyDesignSystem(input: ApplyDesignSystemInput) {
|
|
70
|
+
const { filePath } = input;
|
|
71
|
+
|
|
72
|
+
if (!await fileExists(filePath)) {
|
|
73
|
+
throw new Error(`File not found: ${filePath}`);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
let content = await fs.readFile(filePath, 'utf-8');
|
|
77
|
+
let replacements = 0;
|
|
78
|
+
|
|
79
|
+
// Mapping of hardcoded values to System Tokens
|
|
80
|
+
// Determine context (light/dark agnostic logic)
|
|
81
|
+
const adjustments = [
|
|
82
|
+
// Text Colors
|
|
83
|
+
{ pattern: /text-\[#000000\]/g, replacement: 'text-foreground' },
|
|
84
|
+
{ pattern: /text-\[#000\]/g, replacement: 'text-foreground' },
|
|
85
|
+
{ pattern: /text-\[#ffffff\]/g, replacement: 'text-primary-foreground' },
|
|
86
|
+
{ pattern: /text-\[#fff\]/g, replacement: 'text-primary-foreground' },
|
|
87
|
+
|
|
88
|
+
// Backgrounds
|
|
89
|
+
{ pattern: /bg-\[#ffffff\]/g, replacement: 'bg-background' },
|
|
90
|
+
{ pattern: /bg-\[#fff\]/g, replacement: 'bg-background' },
|
|
91
|
+
{ pattern: /bg-\[#000000\]/g, replacement: 'bg-black' }, // Or bg-foreground? keeping bg-black safe.
|
|
92
|
+
|
|
93
|
+
// Borders
|
|
94
|
+
{ pattern: /border-\[#e5e7eb\]/g, replacement: 'border-border' }, // Common gray
|
|
95
|
+
{ pattern: /border-\[#d1d5db\]/g, replacement: 'border-border' },
|
|
96
|
+
];
|
|
97
|
+
|
|
98
|
+
for (const adj of adjustments) {
|
|
99
|
+
if (adj.pattern.test(content)) {
|
|
100
|
+
const matches = content.match(adj.pattern);
|
|
101
|
+
replacements += matches ? matches.length : 0;
|
|
102
|
+
content = content.replace(adj.pattern, adj.replacement);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (replacements > 0) {
|
|
107
|
+
await fs.writeFile(filePath, content, 'utf-8');
|
|
108
|
+
return {
|
|
109
|
+
success: true,
|
|
110
|
+
filesFixed: 1,
|
|
111
|
+
replacementsCount: replacements,
|
|
112
|
+
message: `Applied design system: Replaced ${replacements} hardcoded values with tokens in ${path.basename(filePath)}.`
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
success: true,
|
|
118
|
+
filesFixed: 0,
|
|
119
|
+
replacementsCount: 0,
|
|
120
|
+
message: 'No automatic replacements could be applied. Manual review may be needed.'
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Astrid's Tool: Fetch UI Library Docs
|
|
126
|
+
* Retrieves reference code for components (Shadcn/UI).
|
|
127
|
+
*/
|
|
128
|
+
export async function fetchUiLibraryDocs(input: FetchUiLibraryDocsInput) {
|
|
129
|
+
const { componentName, library } = input;
|
|
130
|
+
|
|
131
|
+
if (library === 'shadcn') {
|
|
132
|
+
// Try to find the component in apps/web/src/components/ui
|
|
133
|
+
// We scan for the file.
|
|
134
|
+
// Assuming typical monorepo path from root.
|
|
135
|
+
const basePaths = [
|
|
136
|
+
'/Users/steinhofve/Documents/Nowhere/CURSOR/Rigstate/apps/web/src/components/ui',
|
|
137
|
+
path.resolve(process.cwd(), 'apps/web/src/components/ui')
|
|
138
|
+
];
|
|
139
|
+
|
|
140
|
+
for (const basePath of basePaths) {
|
|
141
|
+
if (await dirExists(basePath)) {
|
|
142
|
+
// Try .tsx and .ts
|
|
143
|
+
const variations = [`${componentName}.tsx`, `${componentName}.ts`, `${componentName}/index.tsx`];
|
|
144
|
+
|
|
145
|
+
for (const variant of variations) {
|
|
146
|
+
const fullPath = path.join(basePath, variant);
|
|
147
|
+
if (await fileExists(fullPath)) {
|
|
148
|
+
const content = await fs.readFile(fullPath, 'utf-8');
|
|
149
|
+
// Return a truncated/summarized version or full?
|
|
150
|
+
// Full is better for "Copy syntax".
|
|
151
|
+
return {
|
|
152
|
+
found: true,
|
|
153
|
+
source: 'Local Component Definition',
|
|
154
|
+
path: fullPath,
|
|
155
|
+
content: content
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return {
|
|
163
|
+
found: false,
|
|
164
|
+
message: `Component "${componentName}" not found in local Shadcn UI directory.`
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return {
|
|
169
|
+
found: false,
|
|
170
|
+
message: `Library "${library}" docs access not implemented yet.`
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Helpers
|
|
175
|
+
async function fileExists(path: string) {
|
|
176
|
+
try {
|
|
177
|
+
await fs.access(path);
|
|
178
|
+
return true;
|
|
179
|
+
} catch {
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
async function dirExists(path: string) {
|
|
185
|
+
try {
|
|
186
|
+
const stat = await fs.stat(path);
|
|
187
|
+
return stat.isDirectory();
|
|
188
|
+
} catch {
|
|
189
|
+
return false;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool: update_roadmap
|
|
3
|
+
*
|
|
4
|
+
* Updates the status of a roadmap chunk (step).
|
|
5
|
+
* Can search by chunk ID or by title.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { SupabaseClient } from '@supabase/supabase-js';
|
|
9
|
+
import type { UpdateRoadmapResponse } from '../lib/types.js';
|
|
10
|
+
|
|
11
|
+
export async function updateRoadmap(
|
|
12
|
+
supabase: SupabaseClient,
|
|
13
|
+
userId: string,
|
|
14
|
+
projectId: string,
|
|
15
|
+
status: 'LOCKED' | 'ACTIVE' | 'COMPLETED',
|
|
16
|
+
chunkId?: string,
|
|
17
|
+
title?: string
|
|
18
|
+
): Promise<UpdateRoadmapResponse> {
|
|
19
|
+
// First, verify project ownership
|
|
20
|
+
const { data: project, error: projectError } = await supabase
|
|
21
|
+
.from('projects')
|
|
22
|
+
.select('id, name')
|
|
23
|
+
.eq('id', projectId)
|
|
24
|
+
.eq('owner_id', userId)
|
|
25
|
+
.single();
|
|
26
|
+
|
|
27
|
+
if (projectError || !project) {
|
|
28
|
+
throw new Error('Project not found or access denied');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Find the roadmap chunk
|
|
32
|
+
let targetChunk: { id: string; title: string; status: string } | null = null;
|
|
33
|
+
|
|
34
|
+
if (chunkId) {
|
|
35
|
+
// Search by ID
|
|
36
|
+
const { data, error } = await supabase
|
|
37
|
+
.from('roadmap_chunks')
|
|
38
|
+
.select('id, title, status')
|
|
39
|
+
.eq('id', chunkId)
|
|
40
|
+
.eq('project_id', projectId)
|
|
41
|
+
.single();
|
|
42
|
+
|
|
43
|
+
if (error || !data) {
|
|
44
|
+
throw new Error(`Roadmap step with ID "${chunkId}" not found`);
|
|
45
|
+
}
|
|
46
|
+
targetChunk = data;
|
|
47
|
+
} else if (title) {
|
|
48
|
+
// Search by title (fuzzy match using ilike)
|
|
49
|
+
const { data, error } = await supabase
|
|
50
|
+
.from('roadmap_chunks')
|
|
51
|
+
.select('id, title, status')
|
|
52
|
+
.eq('project_id', projectId)
|
|
53
|
+
.ilike('title', `%${title}%`)
|
|
54
|
+
.limit(1)
|
|
55
|
+
.single();
|
|
56
|
+
|
|
57
|
+
if (error || !data) {
|
|
58
|
+
throw new Error(`Roadmap step matching "${title}" not found`);
|
|
59
|
+
}
|
|
60
|
+
targetChunk = data;
|
|
61
|
+
} else {
|
|
62
|
+
throw new Error('Either chunkId or title must be provided');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const previousStatus = targetChunk.status;
|
|
66
|
+
|
|
67
|
+
// Don't update if status is the same
|
|
68
|
+
if (previousStatus === status) {
|
|
69
|
+
return {
|
|
70
|
+
success: true,
|
|
71
|
+
chunkId: targetChunk.id,
|
|
72
|
+
previousStatus,
|
|
73
|
+
newStatus: status,
|
|
74
|
+
message: `âšī¸ Roadmap step "${targetChunk.title}" is already ${status}`
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Update the status
|
|
79
|
+
const { error: updateError } = await supabase
|
|
80
|
+
.from('roadmap_chunks')
|
|
81
|
+
.update({ status })
|
|
82
|
+
.eq('id', targetChunk.id);
|
|
83
|
+
|
|
84
|
+
if (updateError) {
|
|
85
|
+
if (updateError.code === '42501') {
|
|
86
|
+
throw new Error('Permission denied: Cannot update this roadmap');
|
|
87
|
+
}
|
|
88
|
+
throw new Error(`Failed to update roadmap: ${updateError.message}`);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Build status transition message
|
|
92
|
+
const statusEmoji: Record<string, string> = {
|
|
93
|
+
'LOCKED': 'đ',
|
|
94
|
+
'ACTIVE': 'đ§',
|
|
95
|
+
'COMPLETED': 'â
'
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
success: true,
|
|
100
|
+
chunkId: targetChunk.id,
|
|
101
|
+
previousStatus,
|
|
102
|
+
newStatus: status,
|
|
103
|
+
message: `${statusEmoji[status]} Roadmap step "${targetChunk.title}" updated: ${previousStatus} â ${status}`
|
|
104
|
+
};
|
|
105
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"lib": [
|
|
6
|
+
"ES2022"
|
|
7
|
+
],
|
|
8
|
+
"moduleResolution": "bundler",
|
|
9
|
+
"resolveJsonModule": true,
|
|
10
|
+
"allowJs": true,
|
|
11
|
+
"outDir": "dist",
|
|
12
|
+
"declaration": true,
|
|
13
|
+
"declarationMap": true,
|
|
14
|
+
"esModuleInterop": true,
|
|
15
|
+
"forceConsistentCasingInFileNames": true,
|
|
16
|
+
"strict": true,
|
|
17
|
+
"skipLibCheck": true,
|
|
18
|
+
"types": [
|
|
19
|
+
"node"
|
|
20
|
+
]
|
|
21
|
+
},
|
|
22
|
+
"include": [
|
|
23
|
+
"src/**/*"
|
|
24
|
+
],
|
|
25
|
+
"exclude": [
|
|
26
|
+
"node_modules",
|
|
27
|
+
"dist"
|
|
28
|
+
]
|
|
29
|
+
}
|
package/tsup.config.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { defineConfig } from 'tsup';
|
|
2
|
+
|
|
3
|
+
export default defineConfig({
|
|
4
|
+
entry: ['src/index.ts'],
|
|
5
|
+
format: ['esm'],
|
|
6
|
+
dts: true,
|
|
7
|
+
splitting: false,
|
|
8
|
+
sourcemap: true,
|
|
9
|
+
clean: true,
|
|
10
|
+
shims: true,
|
|
11
|
+
banner: {
|
|
12
|
+
js: '#!/usr/bin/env node'
|
|
13
|
+
},
|
|
14
|
+
minify: false,
|
|
15
|
+
target: 'node18'
|
|
16
|
+
});
|