@splicr/mcp-server 0.11.1 → 0.12.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/dist/index.js +6 -0
- package/dist/lib/api-client.d.ts +0 -3
- package/dist/lib/api-client.js +0 -4
- package/dist/lib/github-local.d.ts +25 -0
- package/dist/lib/github-local.js +92 -0
- package/dist/lib/pattern-validator.d.ts +21 -0
- package/dist/lib/pattern-validator.js +163 -0
- package/dist/tools/get-team-status.d.ts +1 -6
- package/dist/tools/get-team-status.js +4 -16
- package/dist/tools/review-code.d.ts +15 -0
- package/dist/tools/review-code.js +45 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -15,6 +15,7 @@ import { grepKnowledgeSchema, handleGrepKnowledge } from './tools/grep-knowledge
|
|
|
15
15
|
import { exploreKnowledgeSchema, handleExploreKnowledge } from './tools/explore-knowledge.js';
|
|
16
16
|
import { getDecisionsSchema, handleGetDecisions } from './tools/get-decisions.js';
|
|
17
17
|
import { getTeamStatusSchema, handleGetTeamStatus } from './tools/get-team-status.js';
|
|
18
|
+
import { reviewCodeSchema, handleReviewCode } from './tools/review-code.js';
|
|
18
19
|
import { completeSession } from './lib/api-client.js';
|
|
19
20
|
// Prevent unhandled errors from crashing the MCP server
|
|
20
21
|
process.on('uncaughtException', (err) => {
|
|
@@ -44,6 +45,9 @@ CONTRACTS (you must follow these):
|
|
|
44
45
|
3. Only fall back to web search or training data if Splicr returns no results or results don't cover the question.
|
|
45
46
|
4. At the END of significant sessions, call save_from_agent with decisions, discoveries, or workarounds you made. Include the context field for attribution.
|
|
46
47
|
|
|
48
|
+
QUALITY GATE:
|
|
49
|
+
5. Before committing or opening a PR, call review_code to check your changes against the team's active patterns. Fix any violations before proceeding.
|
|
50
|
+
|
|
47
51
|
FAILURE MODES:
|
|
48
52
|
- Splicr returns no results → proceed with web search or training data
|
|
49
53
|
- Splicr API is unreachable → proceed without it, don't block the session
|
|
@@ -65,6 +69,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
65
69
|
exploreKnowledgeSchema,
|
|
66
70
|
getDecisionsSchema,
|
|
67
71
|
getTeamStatusSchema,
|
|
72
|
+
reviewCodeSchema,
|
|
68
73
|
],
|
|
69
74
|
}));
|
|
70
75
|
// Handle tool calls with per-tool timeout
|
|
@@ -85,6 +90,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
85
90
|
explore_knowledge: handleExploreKnowledge,
|
|
86
91
|
get_decisions: handleGetDecisions,
|
|
87
92
|
get_team_status: handleGetTeamStatus,
|
|
93
|
+
review_code: handleReviewCode,
|
|
88
94
|
}[name];
|
|
89
95
|
if (!handler) {
|
|
90
96
|
return { content: [{ type: 'text', text: `Unknown tool: ${name}` }], isError: true };
|
package/dist/lib/api-client.d.ts
CHANGED
package/dist/lib/api-client.js
CHANGED
|
@@ -104,8 +104,4 @@ export async function getDecisions(params) {
|
|
|
104
104
|
const data = await apiRequest('POST', '/mcp/decisions', params);
|
|
105
105
|
return { results: data.results ?? [] };
|
|
106
106
|
}
|
|
107
|
-
export async function getTeamStatus(params) {
|
|
108
|
-
const query = params.project ? `?project=${encodeURIComponent(params.project)}` : '';
|
|
109
|
-
return await apiRequest('GET', `/mcp/team-status${query}`);
|
|
110
|
-
}
|
|
111
107
|
export { API_URL };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local GitHub Status - uses `gh` CLI instead of stored token.
|
|
3
|
+
* Zero onboarding friction: if the user has `gh auth login` done, this just works.
|
|
4
|
+
*/
|
|
5
|
+
export interface TeamStatus {
|
|
6
|
+
repo: string;
|
|
7
|
+
open_prs: Array<{
|
|
8
|
+
number: number;
|
|
9
|
+
title: string;
|
|
10
|
+
author: string;
|
|
11
|
+
branch: string;
|
|
12
|
+
updated: string;
|
|
13
|
+
draft: boolean;
|
|
14
|
+
}>;
|
|
15
|
+
recent_merges: Array<{
|
|
16
|
+
title: string;
|
|
17
|
+
author: string;
|
|
18
|
+
merged_at: string;
|
|
19
|
+
}>;
|
|
20
|
+
active_branches: Array<{
|
|
21
|
+
name: string;
|
|
22
|
+
}>;
|
|
23
|
+
error?: string;
|
|
24
|
+
}
|
|
25
|
+
export declare function getLocalGitHubStatus(cwd: string): Promise<TeamStatus>;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local GitHub Status - uses `gh` CLI instead of stored token.
|
|
3
|
+
* Zero onboarding friction: if the user has `gh auth login` done, this just works.
|
|
4
|
+
*/
|
|
5
|
+
import { execSync } from 'child_process';
|
|
6
|
+
import { getGitRemoteUrl, normalizeGitUrl } from './project-detector.js';
|
|
7
|
+
/** Check if `gh` CLI is available and authenticated */
|
|
8
|
+
function isGhAvailable() {
|
|
9
|
+
try {
|
|
10
|
+
execSync('gh auth status', { encoding: 'utf-8', stdio: 'pipe' });
|
|
11
|
+
return true;
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
/** Extract owner/repo from a git remote URL */
|
|
18
|
+
function extractRepoFullName(cwd) {
|
|
19
|
+
const remote = getGitRemoteUrl(cwd);
|
|
20
|
+
if (!remote)
|
|
21
|
+
return null;
|
|
22
|
+
const normalized = normalizeGitUrl(remote);
|
|
23
|
+
// https://github.com/owner/repo -> owner/repo
|
|
24
|
+
const match = normalized.match(/github\.com\/([^/]+\/[^/]+)/);
|
|
25
|
+
return match ? match[1] : null;
|
|
26
|
+
}
|
|
27
|
+
/** Run a gh api command and parse JSON result */
|
|
28
|
+
function ghApi(endpoint) {
|
|
29
|
+
try {
|
|
30
|
+
const result = execSync(`gh api "${endpoint}" --paginate`, {
|
|
31
|
+
encoding: 'utf-8',
|
|
32
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
33
|
+
timeout: 10000,
|
|
34
|
+
});
|
|
35
|
+
return JSON.parse(result);
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
export async function getLocalGitHubStatus(cwd) {
|
|
42
|
+
const repoFullName = extractRepoFullName(cwd);
|
|
43
|
+
if (!repoFullName) {
|
|
44
|
+
return { repo: 'unknown', open_prs: [], recent_merges: [], active_branches: [], error: 'No GitHub remote found in this directory.' };
|
|
45
|
+
}
|
|
46
|
+
if (!isGhAvailable()) {
|
|
47
|
+
return { repo: repoFullName, open_prs: [], recent_merges: [], active_branches: [], error: 'GitHub CLI (gh) not authenticated. Run `gh auth login` to enable team status.' };
|
|
48
|
+
}
|
|
49
|
+
const status = {
|
|
50
|
+
repo: repoFullName,
|
|
51
|
+
open_prs: [],
|
|
52
|
+
recent_merges: [],
|
|
53
|
+
active_branches: [],
|
|
54
|
+
};
|
|
55
|
+
// Fetch open PRs
|
|
56
|
+
const openPRs = ghApi(`repos/${repoFullName}/pulls?state=open&sort=updated&direction=desc&per_page=10`);
|
|
57
|
+
if (openPRs) {
|
|
58
|
+
status.open_prs = openPRs.map(pr => ({
|
|
59
|
+
number: pr.number,
|
|
60
|
+
title: pr.title,
|
|
61
|
+
author: pr.user.login,
|
|
62
|
+
branch: pr.head?.ref || pr.headRefName || '',
|
|
63
|
+
updated: pr.updated_at || pr.updatedAt || '',
|
|
64
|
+
draft: pr.draft ?? pr.isDraft ?? false,
|
|
65
|
+
}));
|
|
66
|
+
}
|
|
67
|
+
// Fetch recently merged PRs
|
|
68
|
+
const mergedPRs = ghApi(`repos/${repoFullName}/pulls?state=closed&sort=updated&direction=desc&per_page=10`);
|
|
69
|
+
if (mergedPRs) {
|
|
70
|
+
const sevenDaysAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000);
|
|
71
|
+
status.recent_merges = mergedPRs
|
|
72
|
+
.filter(pr => {
|
|
73
|
+
const mergedDate = pr.merged_at || pr.mergedAt || pr.updated_at || pr.updatedAt;
|
|
74
|
+
return mergedDate && new Date(mergedDate) >= sevenDaysAgo;
|
|
75
|
+
})
|
|
76
|
+
.map(pr => ({
|
|
77
|
+
title: pr.title,
|
|
78
|
+
author: pr.user.login,
|
|
79
|
+
merged_at: pr.merged_at || pr.mergedAt || pr.updated_at || pr.updatedAt || '',
|
|
80
|
+
}));
|
|
81
|
+
}
|
|
82
|
+
// Fetch branches
|
|
83
|
+
const branches = ghApi(`repos/${repoFullName}/branches?per_page=20`);
|
|
84
|
+
if (branches) {
|
|
85
|
+
const skipBranches = new Set(['main', 'master', 'develop', 'dev', 'staging', 'production']);
|
|
86
|
+
status.active_branches = branches
|
|
87
|
+
.filter(b => !skipBranches.has(b.name))
|
|
88
|
+
.slice(0, 10)
|
|
89
|
+
.map(b => ({ name: b.name }));
|
|
90
|
+
}
|
|
91
|
+
return status;
|
|
92
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pattern Validator - checks git diff against active project patterns.
|
|
3
|
+
* Core logic reusable from MCP tool, hooks, or future GitHub Action.
|
|
4
|
+
*/
|
|
5
|
+
export interface PatternViolationReport {
|
|
6
|
+
repo: string;
|
|
7
|
+
project_name: string | null;
|
|
8
|
+
diff_stats: {
|
|
9
|
+
files_changed: number;
|
|
10
|
+
insertions: number;
|
|
11
|
+
deletions: number;
|
|
12
|
+
};
|
|
13
|
+
patterns_checked: Array<{
|
|
14
|
+
name: string;
|
|
15
|
+
description: string;
|
|
16
|
+
}>;
|
|
17
|
+
diff_summary: string;
|
|
18
|
+
review_prompt: string;
|
|
19
|
+
error?: string;
|
|
20
|
+
}
|
|
21
|
+
export declare function validateAgainstPatterns(cwd: string, scope?: 'staged' | 'unstaged' | 'last-commit' | 'branch'): Promise<PatternViolationReport>;
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pattern Validator - checks git diff against active project patterns.
|
|
3
|
+
* Core logic reusable from MCP tool, hooks, or future GitHub Action.
|
|
4
|
+
*/
|
|
5
|
+
import { execSync } from 'child_process';
|
|
6
|
+
import { getProjectContext } from './api-client.js';
|
|
7
|
+
import { detectProject } from './project-detector.js';
|
|
8
|
+
/** Get git diff - supports staged, unstaged, or last N commits */
|
|
9
|
+
function getGitDiff(cwd, scope) {
|
|
10
|
+
try {
|
|
11
|
+
const cmds = {
|
|
12
|
+
'staged': 'git diff --cached',
|
|
13
|
+
'unstaged': 'git diff',
|
|
14
|
+
'last-commit': 'git diff HEAD~1 HEAD',
|
|
15
|
+
'branch': 'git diff main...HEAD',
|
|
16
|
+
};
|
|
17
|
+
return execSync(cmds[scope], { cwd, encoding: 'utf-8', maxBuffer: 1024 * 1024 }).trim();
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return '';
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/** Get diff stats */
|
|
24
|
+
function getDiffStats(cwd, scope) {
|
|
25
|
+
try {
|
|
26
|
+
const cmds = {
|
|
27
|
+
'staged': 'git diff --cached --shortstat',
|
|
28
|
+
'unstaged': 'git diff --shortstat',
|
|
29
|
+
'last-commit': 'git diff HEAD~1 HEAD --shortstat',
|
|
30
|
+
'branch': 'git diff main...HEAD --shortstat',
|
|
31
|
+
};
|
|
32
|
+
const stat = execSync(cmds[scope], { cwd, encoding: 'utf-8' }).trim();
|
|
33
|
+
const files = stat.match(/(\d+) files? changed/)?.[1] || '0';
|
|
34
|
+
const ins = stat.match(/(\d+) insertions?/)?.[1] || '0';
|
|
35
|
+
const del = stat.match(/(\d+) deletions?/)?.[1] || '0';
|
|
36
|
+
return { files_changed: parseInt(files), insertions: parseInt(ins), deletions: parseInt(del) };
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return { files_changed: 0, insertions: 0, deletions: 0 };
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/** Get changed file names */
|
|
43
|
+
function getChangedFiles(cwd, scope) {
|
|
44
|
+
try {
|
|
45
|
+
const cmds = {
|
|
46
|
+
'staged': 'git diff --cached --name-only',
|
|
47
|
+
'unstaged': 'git diff --name-only',
|
|
48
|
+
'last-commit': 'git diff HEAD~1 HEAD --name-only',
|
|
49
|
+
'branch': 'git diff main...HEAD --name-only',
|
|
50
|
+
};
|
|
51
|
+
const result = execSync(cmds[scope], { cwd, encoding: 'utf-8' }).trim();
|
|
52
|
+
return result ? result.split('\n') : [];
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return [];
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/** Truncate diff to fit in context - keep most important parts */
|
|
59
|
+
function truncateDiff(diff, maxLines = 200) {
|
|
60
|
+
const lines = diff.split('\n');
|
|
61
|
+
if (lines.length <= maxLines)
|
|
62
|
+
return diff;
|
|
63
|
+
// Keep first maxLines with a truncation note
|
|
64
|
+
return lines.slice(0, maxLines).join('\n') + `\n\n... (truncated - ${lines.length - maxLines} more lines. Use a narrower scope for full diff.)`;
|
|
65
|
+
}
|
|
66
|
+
export async function validateAgainstPatterns(cwd, scope = 'staged') {
|
|
67
|
+
// Detect project
|
|
68
|
+
const detected = await detectProject(cwd).catch(() => null);
|
|
69
|
+
if (!detected) {
|
|
70
|
+
return {
|
|
71
|
+
repo: cwd,
|
|
72
|
+
project_name: null,
|
|
73
|
+
diff_stats: { files_changed: 0, insertions: 0, deletions: 0 },
|
|
74
|
+
patterns_checked: [],
|
|
75
|
+
diff_summary: '',
|
|
76
|
+
review_prompt: '',
|
|
77
|
+
error: 'Could not detect project. Register this project with Splicr first.',
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
// Fetch patterns
|
|
81
|
+
let patterns = [];
|
|
82
|
+
try {
|
|
83
|
+
const ctx = await getProjectContext({
|
|
84
|
+
project_name: detected.name,
|
|
85
|
+
project_id: detected.id,
|
|
86
|
+
limit: 1, // only need patterns
|
|
87
|
+
});
|
|
88
|
+
patterns = (ctx.patterns || []).map((p) => ({
|
|
89
|
+
name: p.name,
|
|
90
|
+
description: p.description,
|
|
91
|
+
}));
|
|
92
|
+
}
|
|
93
|
+
catch { /* no patterns available */ }
|
|
94
|
+
if (patterns.length === 0) {
|
|
95
|
+
return {
|
|
96
|
+
repo: detected.name,
|
|
97
|
+
project_name: detected.name,
|
|
98
|
+
diff_stats: { files_changed: 0, insertions: 0, deletions: 0 },
|
|
99
|
+
patterns_checked: [],
|
|
100
|
+
diff_summary: '',
|
|
101
|
+
review_prompt: '',
|
|
102
|
+
error: 'No active patterns found for this project. Patterns are established over time as agents save learnings.',
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
// Get diff
|
|
106
|
+
let diff = getGitDiff(cwd, scope);
|
|
107
|
+
// If staged is empty, try unstaged
|
|
108
|
+
if (!diff && scope === 'staged') {
|
|
109
|
+
diff = getGitDiff(cwd, 'unstaged');
|
|
110
|
+
if (!diff) {
|
|
111
|
+
diff = getGitDiff(cwd, 'last-commit');
|
|
112
|
+
if (diff)
|
|
113
|
+
scope = 'last-commit';
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
scope = 'unstaged';
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
if (!diff) {
|
|
120
|
+
return {
|
|
121
|
+
repo: detected.name,
|
|
122
|
+
project_name: detected.name,
|
|
123
|
+
diff_stats: { files_changed: 0, insertions: 0, deletions: 0 },
|
|
124
|
+
patterns_checked: patterns,
|
|
125
|
+
diff_summary: '',
|
|
126
|
+
review_prompt: '',
|
|
127
|
+
error: 'No changes detected. Stage changes, make commits, or specify a scope.',
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
const stats = getDiffStats(cwd, scope);
|
|
131
|
+
const files = getChangedFiles(cwd, scope);
|
|
132
|
+
const truncatedDiff = truncateDiff(diff);
|
|
133
|
+
// Build the review prompt - the agent IS the AI reviewer
|
|
134
|
+
const patternList = patterns.map((p, i) => `${i + 1}. **${p.name}** — ${p.description}`).join('\n');
|
|
135
|
+
const reviewPrompt = `## Pattern Compliance Review
|
|
136
|
+
|
|
137
|
+
### Active Patterns (${patterns.length}):
|
|
138
|
+
${patternList}
|
|
139
|
+
|
|
140
|
+
### Changes (${scope}, ${stats.files_changed} files, +${stats.insertions}/-${stats.deletions}):
|
|
141
|
+
Files: ${files.join(', ')}
|
|
142
|
+
|
|
143
|
+
### Diff:
|
|
144
|
+
\`\`\`diff
|
|
145
|
+
${truncatedDiff}
|
|
146
|
+
\`\`\`
|
|
147
|
+
|
|
148
|
+
### Your task:
|
|
149
|
+
Review the diff above against each active pattern. For each pattern:
|
|
150
|
+
- **PASS** if the changes comply or the pattern is not relevant to these changes
|
|
151
|
+
- **VIOLATION** if the changes contradict the pattern - cite the specific line(s)
|
|
152
|
+
- **WARNING** if the changes are in a gray area
|
|
153
|
+
|
|
154
|
+
End with a summary: total violations, total warnings, and whether this is safe to merge.`;
|
|
155
|
+
return {
|
|
156
|
+
repo: detected.name,
|
|
157
|
+
project_name: detected.name,
|
|
158
|
+
diff_stats: stats,
|
|
159
|
+
patterns_checked: patterns,
|
|
160
|
+
diff_summary: `${stats.files_changed} files changed (+${stats.insertions}/-${stats.deletions}): ${files.slice(0, 5).join(', ')}${files.length > 5 ? ` +${files.length - 5} more` : ''}`,
|
|
161
|
+
review_prompt: reviewPrompt,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
@@ -3,12 +3,7 @@ export declare const getTeamStatusSchema: {
|
|
|
3
3
|
description: string;
|
|
4
4
|
inputSchema: {
|
|
5
5
|
type: "object";
|
|
6
|
-
properties: {
|
|
7
|
-
project: {
|
|
8
|
-
type: "string";
|
|
9
|
-
description: string;
|
|
10
|
-
};
|
|
11
|
-
};
|
|
6
|
+
properties: {};
|
|
12
7
|
};
|
|
13
8
|
};
|
|
14
9
|
export declare function handleGetTeamStatus(args: Record<string, unknown>): Promise<string>;
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { detectProject } from '../lib/project-detector.js';
|
|
1
|
+
import { getLocalGitHubStatus } from '../lib/github-local.js';
|
|
3
2
|
import * as session from '../lib/session-state.js';
|
|
4
3
|
export const getTeamStatusSchema = {
|
|
5
4
|
name: 'get_team_status',
|
|
@@ -10,25 +9,14 @@ Use when:
|
|
|
10
9
|
- Before starting work on a feature (check if someone else is already on it)
|
|
11
10
|
- To understand recent changes to the codebase
|
|
12
11
|
|
|
13
|
-
Requires GitHub
|
|
12
|
+
Requires GitHub CLI (gh) to be authenticated. Run \`gh auth login\` if not set up.`,
|
|
14
13
|
inputSchema: {
|
|
15
14
|
type: 'object',
|
|
16
|
-
properties: {
|
|
17
|
-
project: { type: 'string', description: 'Project name or "auto" (default: auto)' },
|
|
18
|
-
},
|
|
15
|
+
properties: {},
|
|
19
16
|
},
|
|
20
17
|
};
|
|
21
18
|
export async function handleGetTeamStatus(args) {
|
|
22
|
-
const
|
|
23
|
-
let projectName;
|
|
24
|
-
if (projectArg === 'auto') {
|
|
25
|
-
const detected = await detectProject(process.cwd());
|
|
26
|
-
projectName = detected?.name;
|
|
27
|
-
}
|
|
28
|
-
else {
|
|
29
|
-
projectName = projectArg;
|
|
30
|
-
}
|
|
31
|
-
const data = await getTeamStatus({ project: projectName });
|
|
19
|
+
const data = await getLocalGitHubStatus(process.cwd());
|
|
32
20
|
session.recordToolCall();
|
|
33
21
|
if (data.error) {
|
|
34
22
|
return data.error;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export declare const reviewCodeSchema: {
|
|
2
|
+
name: "review_code";
|
|
3
|
+
description: string;
|
|
4
|
+
inputSchema: {
|
|
5
|
+
type: "object";
|
|
6
|
+
properties: {
|
|
7
|
+
scope: {
|
|
8
|
+
type: "string";
|
|
9
|
+
enum: string[];
|
|
10
|
+
description: string;
|
|
11
|
+
};
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
export declare function handleReviewCode(args: Record<string, unknown>): Promise<string>;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { validateAgainstPatterns } from '../lib/pattern-validator.js';
|
|
2
|
+
import * as session from '../lib/session-state.js';
|
|
3
|
+
export const reviewCodeSchema = {
|
|
4
|
+
name: 'review_code',
|
|
5
|
+
description: `Review current code changes against the team's active patterns. Checks your git diff for pattern violations before you commit or open a PR.
|
|
6
|
+
|
|
7
|
+
Use when:
|
|
8
|
+
- Before committing: catch violations early
|
|
9
|
+
- Before opening a PR: ensure compliance with team conventions
|
|
10
|
+
- After making changes: self-review against established patterns
|
|
11
|
+
- When asked to review code quality
|
|
12
|
+
|
|
13
|
+
Scopes:
|
|
14
|
+
- "staged" (default): checks staged changes (git diff --cached)
|
|
15
|
+
- "unstaged": checks working directory changes
|
|
16
|
+
- "last-commit": checks the most recent commit
|
|
17
|
+
- "branch": checks all changes since diverging from main
|
|
18
|
+
|
|
19
|
+
Returns the diff + active patterns formatted for review. You (the agent) are the reviewer - analyze each pattern against the changes and report violations.`,
|
|
20
|
+
inputSchema: {
|
|
21
|
+
type: 'object',
|
|
22
|
+
properties: {
|
|
23
|
+
scope: {
|
|
24
|
+
type: 'string',
|
|
25
|
+
enum: ['staged', 'unstaged', 'last-commit', 'branch'],
|
|
26
|
+
description: 'What to review: staged (default), unstaged, last-commit, or branch (all commits since main)',
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
export async function handleReviewCode(args) {
|
|
32
|
+
const scope = args.scope || 'staged';
|
|
33
|
+
const report = await validateAgainstPatterns(process.cwd(), scope);
|
|
34
|
+
session.recordToolCall();
|
|
35
|
+
if (report.error) {
|
|
36
|
+
return report.error;
|
|
37
|
+
}
|
|
38
|
+
// Header
|
|
39
|
+
let output = `*Pattern review for ${report.project_name}*\n`;
|
|
40
|
+
output += `${report.diff_summary}\n`;
|
|
41
|
+
output += `Checking against ${report.patterns_checked.length} active pattern(s).\n\n`;
|
|
42
|
+
// The review prompt — agent reads this and does the actual review
|
|
43
|
+
output += report.review_prompt;
|
|
44
|
+
return output;
|
|
45
|
+
}
|