@amrishkhan05/frankly 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/.claude-plugin/marketplace.json +27 -0
- package/.claude-plugin/plugin.json +11 -0
- package/.github/ISSUE_TEMPLATE/bug_report.md +20 -0
- package/.github/ISSUE_TEMPLATE/documentation.md +15 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +15 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +20 -0
- package/.github/copilot-instructions.md +12 -0
- package/.github/plugin/marketplace.json +20 -0
- package/.github/plugin/plugin.json +16 -0
- package/.mcp.json +10 -0
- package/LICENSE +21 -0
- package/README.md +394 -0
- package/commands/frankly-help.toml +2 -0
- package/commands/frankly-plan.toml +2 -0
- package/commands/frankly-review.toml +2 -0
- package/commands/frankly-verify.toml +2 -0
- package/commands/frankly.toml +2 -0
- package/dist/analysis/change-classifier.d.ts +65 -0
- package/dist/analysis/change-classifier.d.ts.map +1 -0
- package/dist/analysis/change-classifier.js +241 -0
- package/dist/analysis/change-classifier.js.map +1 -0
- package/dist/analysis/repository-intelligence.d.ts +15 -0
- package/dist/analysis/repository-intelligence.d.ts.map +1 -0
- package/dist/analysis/repository-intelligence.js +179 -0
- package/dist/analysis/repository-intelligence.js.map +1 -0
- package/dist/core/config.d.ts +393 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +130 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/engine.d.ts +20 -0
- package/dist/core/engine.d.ts.map +1 -0
- package/dist/core/engine.js +117 -0
- package/dist/core/engine.js.map +1 -0
- package/dist/core/errors.d.ts +27 -0
- package/dist/core/errors.d.ts.map +1 -0
- package/dist/core/errors.js +48 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/core/index.d.ts +8 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +8 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/models.d.ts +155 -0
- package/dist/core/models.d.ts.map +1 -0
- package/dist/core/models.js +6 -0
- package/dist/core/models.js.map +1 -0
- package/dist/git/index.d.ts +7 -0
- package/dist/git/index.d.ts.map +1 -0
- package/dist/git/index.js +6 -0
- package/dist/git/index.js.map +1 -0
- package/dist/git/repository.d.ts +44 -0
- package/dist/git/repository.d.ts.map +1 -0
- package/dist/git/repository.js +198 -0
- package/dist/git/repository.js.map +1 -0
- package/dist/graph/symbols.d.ts +45 -0
- package/dist/graph/symbols.d.ts.map +1 -0
- package/dist/graph/symbols.js +204 -0
- package/dist/graph/symbols.js.map +1 -0
- package/dist/integrations/claude/checkpoint.d.ts +2 -0
- package/dist/integrations/claude/checkpoint.d.ts.map +1 -0
- package/dist/integrations/claude/checkpoint.js +33 -0
- package/dist/integrations/claude/checkpoint.js.map +1 -0
- package/dist/integrations/cli/demo.d.ts +3 -0
- package/dist/integrations/cli/demo.d.ts.map +1 -0
- package/dist/integrations/cli/demo.js +31 -0
- package/dist/integrations/cli/demo.js.map +1 -0
- package/dist/integrations/cli/index.d.ts +7 -0
- package/dist/integrations/cli/index.d.ts.map +1 -0
- package/dist/integrations/cli/index.js +181 -0
- package/dist/integrations/cli/index.js.map +1 -0
- package/dist/integrations/mcp/index.d.ts +3 -0
- package/dist/integrations/mcp/index.d.ts.map +1 -0
- package/dist/integrations/mcp/index.js +7 -0
- package/dist/integrations/mcp/index.js.map +1 -0
- package/dist/integrations/mcp/server.d.ts +14 -0
- package/dist/integrations/mcp/server.d.ts.map +1 -0
- package/dist/integrations/mcp/server.js +232 -0
- package/dist/integrations/mcp/server.js.map +1 -0
- package/dist/project/detector.d.ts +70 -0
- package/dist/project/detector.d.ts.map +1 -0
- package/dist/project/detector.js +214 -0
- package/dist/project/detector.js.map +1 -0
- package/dist/report/json.d.ts +26 -0
- package/dist/report/json.d.ts.map +1 -0
- package/dist/report/json.js +148 -0
- package/dist/report/json.js.map +1 -0
- package/dist/review/red-ink-review.d.ts +26 -0
- package/dist/review/red-ink-review.d.ts.map +1 -0
- package/dist/review/red-ink-review.js +103 -0
- package/dist/review/red-ink-review.js.map +1 -0
- package/fixtures/retry-429/README.md +8 -0
- package/fixtures/retry-429/baseline/package.json +4 -0
- package/fixtures/retry-429/baseline/src/client.test.ts +8 -0
- package/fixtures/retry-429/baseline/src/client.ts +3 -0
- package/fixtures/retry-429/baseline/src/retry-policy.ts +1 -0
- package/fixtures/retry-429/baseline/tsconfig.json +7 -0
- package/fixtures/retry-429/candidate/src/client.ts +5 -0
- package/fixtures/retry-429/candidate/src/retry-strategy.ts +9 -0
- package/fixtures/retry-429/candidate/src/unrelated-format.ts +1 -0
- package/hooks/copilot-activate.js +9 -0
- package/hooks/copilot-hooks.json +13 -0
- package/hooks/hooks.json +13 -0
- package/package.json +70 -0
- package/scripts/run.cjs +32 -0
- package/skills/frankly/SKILL.md +12 -0
- package/src/analysis/change-classifier.ts +285 -0
- package/src/analysis/repository-intelligence.ts +236 -0
- package/src/core/config.ts +149 -0
- package/src/core/engine.ts +143 -0
- package/src/core/errors.ts +56 -0
- package/src/core/index.ts +8 -0
- package/src/core/models.ts +280 -0
- package/src/git/index.ts +7 -0
- package/src/git/repository.ts +207 -0
- package/src/graph/symbols.ts +254 -0
- package/src/integrations/claude/checkpoint.ts +40 -0
- package/src/integrations/cli/demo.ts +34 -0
- package/src/integrations/cli/index.ts +211 -0
- package/src/integrations/mcp/index.ts +7 -0
- package/src/integrations/mcp/server.ts +257 -0
- package/src/project/detector.ts +242 -0
- package/src/report/json.ts +175 -0
- package/src/review/red-ink-review.ts +125 -0
- package/src/tests/phase1.test.ts +57 -0
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Frankly CLI
|
|
5
|
+
* Command-line interface for Frankly analysis
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { createAnalysisEngine } from "../../core/engine.js";
|
|
9
|
+
import { createReportFormatter } from "../../report/json.js";
|
|
10
|
+
import type { FranklyConfig } from "../../core/config.js";
|
|
11
|
+
import type { ExecutedTest } from "../../core/models.js";
|
|
12
|
+
import { spawnSync } from "node:child_process";
|
|
13
|
+
import * as fs from "node:fs";
|
|
14
|
+
import * as path from "node:path";
|
|
15
|
+
|
|
16
|
+
async function main(): Promise<void> {
|
|
17
|
+
const args = process.argv.slice(2);
|
|
18
|
+
|
|
19
|
+
if (args.length === 0 || args[0] === "help") {
|
|
20
|
+
printHelp();
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const command = args[0];
|
|
25
|
+
const cwd = process.cwd();
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
switch (command) {
|
|
29
|
+
case "review": {
|
|
30
|
+
await handleReview(args.slice(1), cwd);
|
|
31
|
+
break;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
case "plan": {
|
|
35
|
+
await handlePlan(args.slice(1), cwd);
|
|
36
|
+
break;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
case "verify": {
|
|
40
|
+
await handleVerify(args.slice(1), cwd);
|
|
41
|
+
break;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
case "mcp": {
|
|
45
|
+
const { startMcpServer } = await import("../mcp/server.js");
|
|
46
|
+
console.log("Starting MCP server...");
|
|
47
|
+
await startMcpServer(cwd);
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
case "config": {
|
|
52
|
+
await handleConfig(args.slice(1), cwd);
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
default:
|
|
57
|
+
console.error(`Unknown command: ${command}`);
|
|
58
|
+
printHelp();
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
} catch (error) {
|
|
62
|
+
console.error("Error:", error instanceof Error ? error.message : String(error));
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function handleReview(args: string[], cwd: string): Promise<void> {
|
|
68
|
+
let task = "Review current changes";
|
|
69
|
+
|
|
70
|
+
// Parse arguments
|
|
71
|
+
for (let i = 0; i < args.length; i++) {
|
|
72
|
+
if (args[i] === "--task" && args[i + 1]) {
|
|
73
|
+
task = args[++i];
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const engine = await createAnalysisEngine(cwd);
|
|
78
|
+
const result = await engine.analyze(task, reviewOverrides(args), args.includes("--run-tests") ? runTests(cwd) : []);
|
|
79
|
+
|
|
80
|
+
const formatter = createReportFormatter();
|
|
81
|
+
console.log(args.includes("--json") ? formatter.formatAsJson(result) : args.includes("--markdown") ? formatter.formatAsMarkdown(result) : args.includes("--ci") ? formatter.formatAsCi(result) : formatter.formatAsTerminal(result));
|
|
82
|
+
|
|
83
|
+
if (args.includes("--ci") && result.review.verdict !== "CLEAN") {
|
|
84
|
+
process.exit(1);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function handlePlan(args: string[], cwd: string): Promise<void> {
|
|
89
|
+
let task = "Plan changes";
|
|
90
|
+
|
|
91
|
+
for (let i = 0; i < args.length; i++) {
|
|
92
|
+
if (args[i] === "--task" && args[i + 1]) {
|
|
93
|
+
task = args[++i];
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const engine = await createAnalysisEngine(cwd);
|
|
98
|
+
const plan = await engine.plan(task);
|
|
99
|
+
|
|
100
|
+
console.log(`FRANKLY · PLAN
|
|
101
|
+
|
|
102
|
+
Task: ${task}
|
|
103
|
+
Normalized intent: ${plan.normalizedIntent}
|
|
104
|
+
Keywords: ${plan.keywords.join(", ") || "none"}
|
|
105
|
+
Estimated files: ${plan.estimatedFiles}
|
|
106
|
+
Likely touched areas: ${plan.likelyTouchedAreas.length > 0 ? plan.likelyTouchedAreas.join(", ") : "none"}
|
|
107
|
+
Reuse candidates: ${plan.reuseCandidates.join(", ") || "none"}
|
|
108
|
+
Confidence: ${plan.confidence}
|
|
109
|
+
|
|
110
|
+
Concerns:
|
|
111
|
+
${plan.concerns.length > 0 ? plan.concerns.map((item) => `- ${item}`).join("\n") : "- none"}
|
|
112
|
+
`);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async function handleVerify(args: string[], cwd: string): Promise<void> {
|
|
116
|
+
let task = "Verify changes";
|
|
117
|
+
|
|
118
|
+
for (let i = 0; i < args.length; i++) {
|
|
119
|
+
if (args[i] === "--task" && args[i + 1]) {
|
|
120
|
+
task = args[++i];
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const engine = await createAnalysisEngine(cwd);
|
|
125
|
+
const result = await engine.analyze(task, reviewOverrides(args), args.includes("--run-tests") ? runTests(cwd) : []);
|
|
126
|
+
|
|
127
|
+
const formatter = createReportFormatter();
|
|
128
|
+
|
|
129
|
+
if (result.review.verdict === "CLEAN") {
|
|
130
|
+
console.log("✓ Verification passed");
|
|
131
|
+
console.log(formatter.formatAsSummary(result));
|
|
132
|
+
} else {
|
|
133
|
+
console.log("✗ Verification failed");
|
|
134
|
+
console.log(formatter.formatAsTerminal(result));
|
|
135
|
+
process.exit(1);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function runTests(cwd: string): ExecutedTest[] {
|
|
140
|
+
const command = fs.existsSync(path.join(cwd, "pnpm-lock.yaml")) ? "pnpm" : fs.existsSync(path.join(cwd, "yarn.lock")) ? "yarn" : "npm";
|
|
141
|
+
const args = ["test"];
|
|
142
|
+
const started = Date.now();
|
|
143
|
+
const result = spawnSync(command, args, { cwd, encoding: "utf8", stdio: "inherit" });
|
|
144
|
+
return [{ path: cwd, name: "project test command", status: result.status === 0 ? "PASSED" : "FAILED", duration: Date.now() - started }];
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function reviewOverrides(args: string[]): Partial<FranklyConfig> {
|
|
148
|
+
const intensity = args[args.indexOf("--intensity") + 1] as FranklyConfig["intensity"] | undefined;
|
|
149
|
+
const personality = args[args.indexOf("--personality") + 1] as FranklyConfig["personality"] | undefined;
|
|
150
|
+
return {
|
|
151
|
+
...(args.includes("--intensity") && intensity ? { intensity } : {}),
|
|
152
|
+
...(args.includes("--personality") && personality ? { personality } : {}),
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
async function handleConfig(args: string[], cwd: string): Promise<void> {
|
|
157
|
+
if (args.length === 0 || args[0] === "show") {
|
|
158
|
+
console.log(`Frankly configuration for ${cwd}`);
|
|
159
|
+
console.log(
|
|
160
|
+
`
|
|
161
|
+
Default configuration:
|
|
162
|
+
trigger: checkpoint
|
|
163
|
+
action: correct
|
|
164
|
+
intensity: full
|
|
165
|
+
personality: senior
|
|
166
|
+
maxCorrectionPasses: 1
|
|
167
|
+
|
|
168
|
+
Configuration file: frankly.config.json
|
|
169
|
+
`,
|
|
170
|
+
);
|
|
171
|
+
} else {
|
|
172
|
+
console.error("Unknown config subcommand");
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function printHelp(): void {
|
|
177
|
+
console.log(`
|
|
178
|
+
Frankly - Change Intelligence for AI Coding Agents
|
|
179
|
+
|
|
180
|
+
Usage:
|
|
181
|
+
frankly <command> [options]
|
|
182
|
+
|
|
183
|
+
Commands:
|
|
184
|
+
review Analyze current changes and produce Red Ink Review
|
|
185
|
+
plan Plan a change and estimate scope
|
|
186
|
+
verify Verify that a change is complete and correct
|
|
187
|
+
mcp Start MCP server for agent integration
|
|
188
|
+
config Show or manage configuration
|
|
189
|
+
help Show this help message
|
|
190
|
+
|
|
191
|
+
Options:
|
|
192
|
+
--task <text> Task description
|
|
193
|
+
--intensity <level> Analysis intensity: lite, full, ultra, off
|
|
194
|
+
--personality Review personality: conservative, senior, witty
|
|
195
|
+
--run-tests Execute the repository test script and record the result
|
|
196
|
+
--json|--markdown Select an output format
|
|
197
|
+
--ci Emit compact CI JSON and fail on non-clean verdict
|
|
198
|
+
|
|
199
|
+
Examples:
|
|
200
|
+
frankly review --task "Fix HTTP 429 retry"
|
|
201
|
+
frankly verify --task "Add retry logic"
|
|
202
|
+
frankly mcp
|
|
203
|
+
|
|
204
|
+
Documentation: https://github.com/amrishkhan05/frankly
|
|
205
|
+
`);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
main().catch((error) => {
|
|
209
|
+
console.error("Fatal error:", error);
|
|
210
|
+
process.exit(1);
|
|
211
|
+
});
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP (Model Context Protocol) Server
|
|
3
|
+
* Exposes Frankly analysis to coding agents
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
7
|
+
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
8
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
9
|
+
import { z } from "zod";
|
|
10
|
+
import { createAnalysisEngine } from "../../core/engine.js";
|
|
11
|
+
import type { FranklyConfig } from "../../core/config.js";
|
|
12
|
+
import { createReportFormatter } from "../../report/json.js";
|
|
13
|
+
|
|
14
|
+
const AnalyzeChangeSchema = z.object({
|
|
15
|
+
task: z.string().describe("The task or change description"),
|
|
16
|
+
repositoryRoot: z.string().optional().describe("Path to repository root"),
|
|
17
|
+
intensity: z.enum(["lite", "full", "ultra", "off"]).optional(),
|
|
18
|
+
personality: z.enum(["conservative", "senior", "witty"]).optional(),
|
|
19
|
+
executedTests: z.array(z.object({
|
|
20
|
+
path: z.string(),
|
|
21
|
+
name: z.string(),
|
|
22
|
+
status: z.enum(["PASSED", "FAILED", "SKIPPED", "NOT_RUN"]),
|
|
23
|
+
duration: z.number().optional(),
|
|
24
|
+
})).optional(),
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const PlanChangeSchema = z.object({
|
|
28
|
+
task: z.string().describe("The planned task"),
|
|
29
|
+
repositoryRoot: z.string().optional().describe("Path to repository root"),
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const VerifyChangeSchema = z.object({
|
|
33
|
+
task: z.string().describe("The task that was completed"),
|
|
34
|
+
repositoryRoot: z.string().optional().describe("Path to repository root"),
|
|
35
|
+
executedTests: AnalyzeChangeSchema.shape.executedTests,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const MinimizeChangeSchema = z.object({
|
|
39
|
+
task: z.string(),
|
|
40
|
+
repositoryRoot: z.string().optional(),
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
export class FranklyMcpServer {
|
|
44
|
+
private server: Server;
|
|
45
|
+
|
|
46
|
+
constructor(private repositoryRoot: string = process.cwd()) {
|
|
47
|
+
this.server = new Server(
|
|
48
|
+
{ name: "frankly", version: "0.1.0" },
|
|
49
|
+
{ capabilities: { tools: {} } },
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
this.setupHandlers();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
private setupHandlers(): void {
|
|
56
|
+
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
57
|
+
return {
|
|
58
|
+
tools: [
|
|
59
|
+
{
|
|
60
|
+
name: "analyze_change",
|
|
61
|
+
description:
|
|
62
|
+
"Analyze a code change and provide Red Ink Review with findings and recommendations",
|
|
63
|
+
inputSchema: {
|
|
64
|
+
type: "object" as const,
|
|
65
|
+
properties: {
|
|
66
|
+
task: {
|
|
67
|
+
type: "string",
|
|
68
|
+
description: "The task or change description",
|
|
69
|
+
},
|
|
70
|
+
repositoryRoot: {
|
|
71
|
+
type: "string",
|
|
72
|
+
description: "Path to repository root (optional, uses current working directory)",
|
|
73
|
+
},
|
|
74
|
+
intensity: {
|
|
75
|
+
type: "string",
|
|
76
|
+
enum: ["lite", "full", "ultra", "off"],
|
|
77
|
+
description: "Analysis intensity level",
|
|
78
|
+
},
|
|
79
|
+
personality: {
|
|
80
|
+
type: "string",
|
|
81
|
+
enum: ["conservative", "senior", "witty"],
|
|
82
|
+
description: "Review personality style",
|
|
83
|
+
},
|
|
84
|
+
executedTests: {
|
|
85
|
+
type: "array",
|
|
86
|
+
description: "Tests actually run by the caller; never use this for predictions",
|
|
87
|
+
items: {
|
|
88
|
+
type: "object",
|
|
89
|
+
properties: {
|
|
90
|
+
path: { type: "string" },
|
|
91
|
+
name: { type: "string" },
|
|
92
|
+
status: { type: "string", enum: ["PASSED", "FAILED", "SKIPPED", "NOT_RUN"] },
|
|
93
|
+
duration: { type: "number" },
|
|
94
|
+
},
|
|
95
|
+
required: ["path", "name", "status"],
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
required: ["task"],
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
name: "plan_change",
|
|
104
|
+
description:
|
|
105
|
+
"Plan a change and get expected scope, budget, and potential issues before implementation",
|
|
106
|
+
inputSchema: {
|
|
107
|
+
type: "object" as const,
|
|
108
|
+
properties: {
|
|
109
|
+
task: {
|
|
110
|
+
type: "string",
|
|
111
|
+
description: "The planned task",
|
|
112
|
+
},
|
|
113
|
+
repositoryRoot: {
|
|
114
|
+
type: "string",
|
|
115
|
+
description: "Path to repository root (optional)",
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
required: ["task"],
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
name: "minimize_change",
|
|
123
|
+
description: "Return one constrained correction pass backed by the current analysis",
|
|
124
|
+
inputSchema: {
|
|
125
|
+
type: "object" as const,
|
|
126
|
+
properties: {
|
|
127
|
+
task: { type: "string", description: "The task or change description" },
|
|
128
|
+
repositoryRoot: { type: "string", description: "Path to repository root (optional)" },
|
|
129
|
+
},
|
|
130
|
+
required: ["task"],
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
name: "verify_change",
|
|
135
|
+
description:
|
|
136
|
+
"Verify that a completed change correctly addresses the task with no excess modifications",
|
|
137
|
+
inputSchema: {
|
|
138
|
+
type: "object" as const,
|
|
139
|
+
properties: {
|
|
140
|
+
task: {
|
|
141
|
+
type: "string",
|
|
142
|
+
description: "The task that was completed",
|
|
143
|
+
},
|
|
144
|
+
repositoryRoot: {
|
|
145
|
+
type: "string",
|
|
146
|
+
description: "Path to repository root (optional)",
|
|
147
|
+
},
|
|
148
|
+
},
|
|
149
|
+
required: ["task"],
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
],
|
|
153
|
+
};
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
157
|
+
const { params } = request;
|
|
158
|
+
const name = params.name;
|
|
159
|
+
const args = params.arguments || {};
|
|
160
|
+
|
|
161
|
+
try {
|
|
162
|
+
const result = await this.handleToolCall(name, args);
|
|
163
|
+
return {
|
|
164
|
+
content: [
|
|
165
|
+
{
|
|
166
|
+
type: "text" as const,
|
|
167
|
+
text: result,
|
|
168
|
+
},
|
|
169
|
+
],
|
|
170
|
+
};
|
|
171
|
+
} catch (error) {
|
|
172
|
+
return {
|
|
173
|
+
content: [
|
|
174
|
+
{
|
|
175
|
+
type: "text" as const,
|
|
176
|
+
text: `Error: ${error instanceof Error ? error.message : String(error)}`,
|
|
177
|
+
},
|
|
178
|
+
],
|
|
179
|
+
isError: true,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
private async handleToolCall(name: string, args: unknown): Promise<string> {
|
|
186
|
+
switch (name) {
|
|
187
|
+
case "analyze_change": {
|
|
188
|
+
const parsed = AnalyzeChangeSchema.parse(args);
|
|
189
|
+
const repo = parsed.repositoryRoot || this.repositoryRoot;
|
|
190
|
+
const engine = await createAnalysisEngine(repo);
|
|
191
|
+
|
|
192
|
+
const configOverrides: Partial<FranklyConfig> = {};
|
|
193
|
+
if (parsed.intensity) configOverrides.intensity = parsed.intensity;
|
|
194
|
+
if (parsed.personality) configOverrides.personality = parsed.personality;
|
|
195
|
+
|
|
196
|
+
const result = await engine.analyze(parsed.task, configOverrides, parsed.executedTests);
|
|
197
|
+
const formatter = createReportFormatter();
|
|
198
|
+
|
|
199
|
+
return formatter.formatAsTerminal(result);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
case "plan_change": {
|
|
203
|
+
const parsed = PlanChangeSchema.parse(args);
|
|
204
|
+
const repo = parsed.repositoryRoot || this.repositoryRoot;
|
|
205
|
+
const engine = await createAnalysisEngine(repo);
|
|
206
|
+
const plan = await engine.plan(parsed.task);
|
|
207
|
+
|
|
208
|
+
return `FRANKLY · PLAN CHANGE
|
|
209
|
+
|
|
210
|
+
Task: ${parsed.task}
|
|
211
|
+
Normalized intent: ${plan.normalizedIntent}
|
|
212
|
+
Keywords: ${plan.keywords.join(", ") || "none"}
|
|
213
|
+
Estimated files: ${plan.estimatedFiles}
|
|
214
|
+
Likely touched areas: ${plan.likelyTouchedAreas.join(", ") || "none"}
|
|
215
|
+
Reuse candidates: ${plan.reuseCandidates.join(", ") || "none"}
|
|
216
|
+
Confidence: ${plan.confidence}
|
|
217
|
+
|
|
218
|
+
Concerns:
|
|
219
|
+
${plan.concerns.length > 0 ? plan.concerns.map((item) => `- ${item}`).join("\n") : "- none"}`;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
case "verify_change": {
|
|
223
|
+
const parsed = VerifyChangeSchema.parse(args);
|
|
224
|
+
const repo = parsed.repositoryRoot || this.repositoryRoot;
|
|
225
|
+
const engine = await createAnalysisEngine(repo);
|
|
226
|
+
const result = await engine.analyze(parsed.task, { action: "observe" }, parsed.executedTests);
|
|
227
|
+
|
|
228
|
+
if (result.review.verdict === "CLEAN") {
|
|
229
|
+
return "✓ Change verified successfully";
|
|
230
|
+
} else {
|
|
231
|
+
return `⚠ Verification found issues:\n${result.review.recommendation}`;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
case "minimize_change": {
|
|
236
|
+
const parsed = MinimizeChangeSchema.parse(args);
|
|
237
|
+
const engine = await createAnalysisEngine(parsed.repositoryRoot || this.repositoryRoot);
|
|
238
|
+
const result = await engine.analyze(parsed.task, { action: "correct" });
|
|
239
|
+
return result.review.correction?.instruction || "No correction pass is warranted by the current evidence.";
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
default:
|
|
243
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
async start(): Promise<void> {
|
|
248
|
+
const transport = new StdioServerTransport();
|
|
249
|
+
await this.server.connect(transport);
|
|
250
|
+
console.error("Frankly MCP Server started");
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
export async function startMcpServer(repositoryRoot?: string): Promise<void> {
|
|
255
|
+
const server = new FranklyMcpServer(repositoryRoot);
|
|
256
|
+
await server.start();
|
|
257
|
+
}
|