@splicr/mcp-server 0.13.0 → 0.13.1

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.
@@ -12,6 +12,9 @@ export interface ProjectProfileData {
12
12
  recent_commits: string[];
13
13
  tech_stack: string[];
14
14
  git_remote_url: string | null;
15
+ entry_points: string | null;
16
+ recent_prs: string | null;
17
+ claude_md_rules: string[];
15
18
  }
16
19
  /**
17
20
  * Gather rich project data from the local filesystem for AI profile generation.
@@ -10,14 +10,18 @@ const GIT_TIMEOUT = 2000;
10
10
  * Every operation is individually try/caught — one failure never blocks the rest.
11
11
  */
12
12
  export function gatherProjectProfile(cwd) {
13
+ const claudeMd = readFileSafe(join(cwd, 'CLAUDE.md'), MAX_FILE_CHARS);
13
14
  return {
14
- claude_md: readFileSafe(join(cwd, 'CLAUDE.md'), MAX_FILE_CHARS),
15
+ claude_md: claudeMd,
15
16
  readme: readFileSafe(join(cwd, 'README.md'), MAX_FILE_CHARS),
16
17
  package_json_subset: readPackageJson(cwd),
17
18
  directory_structure: buildDirectoryTree(cwd),
18
19
  recent_commits: getRecentCommits(cwd),
19
20
  tech_stack: detectTechStack(cwd),
20
21
  git_remote_url: getGitRemoteUrl(cwd),
22
+ entry_points: scanEntryPoints(cwd),
23
+ recent_prs: getRecentPRs(cwd),
24
+ claude_md_rules: extractClaudeMdRules(claudeMd),
21
25
  };
22
26
  }
23
27
  function readFileSafe(path, maxChars) {
@@ -114,6 +118,107 @@ function buildDirectoryTree(cwd) {
114
118
  }
115
119
  return lines.join('\n') || '(empty)';
116
120
  }
121
+ /**
122
+ * Scan entry point files for architecture context.
123
+ * Reads the first 80 lines of key files (index.ts, app.ts, routes/, etc.)
124
+ */
125
+ function scanEntryPoints(cwd) {
126
+ const candidates = [
127
+ 'src/index.ts', 'src/index.js', 'src/app.ts', 'src/app.js',
128
+ 'src/main.ts', 'src/main.js', 'app.ts', 'index.ts',
129
+ 'src/server.ts', 'src/server.js',
130
+ 'pages/_app.tsx', 'app/layout.tsx', 'app/page.tsx',
131
+ 'src/App.tsx', 'src/App.jsx',
132
+ ];
133
+ const found = [];
134
+ for (const candidate of candidates) {
135
+ const fullPath = join(cwd, candidate);
136
+ if (existsSync(fullPath)) {
137
+ try {
138
+ const content = readFileSync(fullPath, 'utf-8');
139
+ const lines = content.split('\n').slice(0, 80);
140
+ // Extract imports and exports for architecture understanding
141
+ const importExportLines = lines.filter(l => l.trim().startsWith('import ') || l.trim().startsWith('export ') ||
142
+ l.trim().startsWith('app.') || l.trim().startsWith('router.') ||
143
+ l.trim().startsWith('const app') || l.trim().startsWith('const server'));
144
+ if (importExportLines.length > 0) {
145
+ found.push(`--- ${candidate} ---\n${importExportLines.join('\n')}`);
146
+ }
147
+ }
148
+ catch { /* skip */ }
149
+ }
150
+ if (found.length >= 3)
151
+ break; // Cap at 3 entry points
152
+ }
153
+ // Also scan for route definitions
154
+ const routeDirs = ['src/routes', 'src/pages', 'src/api', 'app/api', 'routes'];
155
+ for (const routeDir of routeDirs) {
156
+ const fullDir = join(cwd, routeDir);
157
+ if (existsSync(fullDir)) {
158
+ try {
159
+ const files = readdirSync(fullDir).filter(f => !f.startsWith('.')).slice(0, 15);
160
+ found.push(`--- ${routeDir}/ ---\n${files.join(', ')}`);
161
+ }
162
+ catch { /* skip */ }
163
+ break;
164
+ }
165
+ }
166
+ return found.length > 0 ? found.join('\n\n').substring(0, 2000) : null;
167
+ }
168
+ /**
169
+ * Get recent merged PR titles + descriptions via gh CLI.
170
+ * Rich "why" context that commit messages lack.
171
+ */
172
+ function getRecentPRs(cwd) {
173
+ try {
174
+ // Check if gh is available
175
+ execSync('gh auth status', { encoding: 'utf-8', stdio: 'pipe', timeout: 3000 });
176
+ const result = execSync('gh pr list --state merged --limit 10 --json title,body,mergedAt --jq ".[] | \\"- \\(.title)\\" + if .body != \\"\\" then \\"\\n \\(.body | split(\\"\\\\n\\") | first)\\" else \\"\\" end"', { cwd, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'], timeout: 5000 }).trim();
177
+ return result || null;
178
+ }
179
+ catch {
180
+ // gh not available or not in a GitHub repo - try simpler approach
181
+ try {
182
+ const result = execSync('gh pr list --state merged --limit 10 --json title --jq ".[].title"', { cwd, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'], timeout: 5000 }).trim();
183
+ return result ? result.split('\n').map(t => `- ${t}`).join('\n') : null;
184
+ }
185
+ catch {
186
+ return null;
187
+ }
188
+ }
189
+ }
190
+ /**
191
+ * Extract actionable rules from CLAUDE.md content.
192
+ * Looks for bullet points, numbered lists, and imperative statements.
193
+ */
194
+ function extractClaudeMdRules(claudeMd) {
195
+ if (!claudeMd)
196
+ return [];
197
+ const rules = [];
198
+ const lines = claudeMd.split('\n');
199
+ for (const line of lines) {
200
+ const trimmed = line.trim();
201
+ // Skip headers, empty lines, code blocks, tables
202
+ if (!trimmed || trimmed.startsWith('#') || trimmed.startsWith('```') ||
203
+ trimmed.startsWith('|') || trimmed.startsWith('---'))
204
+ continue;
205
+ // Look for rule-like patterns
206
+ const isRule =
207
+ // Bullet/numbered items with imperative verbs
208
+ (/^[-*]\s+(Always|Never|Don't|Do not|Must|Prefer|Avoid|Use|Keep|Follow|Ensure)/i.test(trimmed)) ||
209
+ (/^\d+\.\s+(Always|Never|Don't|Do not|Must|Prefer|Avoid|Use|Keep|Follow|Ensure)/i.test(trimmed)) ||
210
+ // Lines containing strong conventions
211
+ (/\b(must|always|never|required|forbidden|mandatory)\b/i.test(trimmed) && trimmed.length < 200);
212
+ if (isRule) {
213
+ // Clean up the rule text
214
+ const cleaned = trimmed.replace(/^[-*\d.]+\s*/, '').trim();
215
+ if (cleaned.length > 15 && cleaned.length < 300) {
216
+ rules.push(cleaned);
217
+ }
218
+ }
219
+ }
220
+ return rules.slice(0, 20); // Cap at 20 rules
221
+ }
117
222
  // Duplicated from signal-gatherer.ts to avoid circular imports
118
223
  const KNOWN_TECH = [
119
224
  'react', 'next', 'express', 'fastify', 'vue', 'angular', 'svelte',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@splicr/mcp-server",
3
- "version": "0.13.0",
3
+ "version": "0.13.1",
4
4
  "description": "Splicr MCP server — route what you read to what you're building",
5
5
  "type": "module",
6
6
  "bin": "./dist/cli.js",