@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.
Files changed (123) hide show
  1. package/.claude-plugin/marketplace.json +27 -0
  2. package/.claude-plugin/plugin.json +11 -0
  3. package/.github/ISSUE_TEMPLATE/bug_report.md +20 -0
  4. package/.github/ISSUE_TEMPLATE/documentation.md +15 -0
  5. package/.github/ISSUE_TEMPLATE/feature_request.md +15 -0
  6. package/.github/PULL_REQUEST_TEMPLATE.md +20 -0
  7. package/.github/copilot-instructions.md +12 -0
  8. package/.github/plugin/marketplace.json +20 -0
  9. package/.github/plugin/plugin.json +16 -0
  10. package/.mcp.json +10 -0
  11. package/LICENSE +21 -0
  12. package/README.md +394 -0
  13. package/commands/frankly-help.toml +2 -0
  14. package/commands/frankly-plan.toml +2 -0
  15. package/commands/frankly-review.toml +2 -0
  16. package/commands/frankly-verify.toml +2 -0
  17. package/commands/frankly.toml +2 -0
  18. package/dist/analysis/change-classifier.d.ts +65 -0
  19. package/dist/analysis/change-classifier.d.ts.map +1 -0
  20. package/dist/analysis/change-classifier.js +241 -0
  21. package/dist/analysis/change-classifier.js.map +1 -0
  22. package/dist/analysis/repository-intelligence.d.ts +15 -0
  23. package/dist/analysis/repository-intelligence.d.ts.map +1 -0
  24. package/dist/analysis/repository-intelligence.js +179 -0
  25. package/dist/analysis/repository-intelligence.js.map +1 -0
  26. package/dist/core/config.d.ts +393 -0
  27. package/dist/core/config.d.ts.map +1 -0
  28. package/dist/core/config.js +130 -0
  29. package/dist/core/config.js.map +1 -0
  30. package/dist/core/engine.d.ts +20 -0
  31. package/dist/core/engine.d.ts.map +1 -0
  32. package/dist/core/engine.js +117 -0
  33. package/dist/core/engine.js.map +1 -0
  34. package/dist/core/errors.d.ts +27 -0
  35. package/dist/core/errors.d.ts.map +1 -0
  36. package/dist/core/errors.js +48 -0
  37. package/dist/core/errors.js.map +1 -0
  38. package/dist/core/index.d.ts +8 -0
  39. package/dist/core/index.d.ts.map +1 -0
  40. package/dist/core/index.js +8 -0
  41. package/dist/core/index.js.map +1 -0
  42. package/dist/core/models.d.ts +155 -0
  43. package/dist/core/models.d.ts.map +1 -0
  44. package/dist/core/models.js +6 -0
  45. package/dist/core/models.js.map +1 -0
  46. package/dist/git/index.d.ts +7 -0
  47. package/dist/git/index.d.ts.map +1 -0
  48. package/dist/git/index.js +6 -0
  49. package/dist/git/index.js.map +1 -0
  50. package/dist/git/repository.d.ts +44 -0
  51. package/dist/git/repository.d.ts.map +1 -0
  52. package/dist/git/repository.js +198 -0
  53. package/dist/git/repository.js.map +1 -0
  54. package/dist/graph/symbols.d.ts +45 -0
  55. package/dist/graph/symbols.d.ts.map +1 -0
  56. package/dist/graph/symbols.js +204 -0
  57. package/dist/graph/symbols.js.map +1 -0
  58. package/dist/integrations/claude/checkpoint.d.ts +2 -0
  59. package/dist/integrations/claude/checkpoint.d.ts.map +1 -0
  60. package/dist/integrations/claude/checkpoint.js +33 -0
  61. package/dist/integrations/claude/checkpoint.js.map +1 -0
  62. package/dist/integrations/cli/demo.d.ts +3 -0
  63. package/dist/integrations/cli/demo.d.ts.map +1 -0
  64. package/dist/integrations/cli/demo.js +31 -0
  65. package/dist/integrations/cli/demo.js.map +1 -0
  66. package/dist/integrations/cli/index.d.ts +7 -0
  67. package/dist/integrations/cli/index.d.ts.map +1 -0
  68. package/dist/integrations/cli/index.js +181 -0
  69. package/dist/integrations/cli/index.js.map +1 -0
  70. package/dist/integrations/mcp/index.d.ts +3 -0
  71. package/dist/integrations/mcp/index.d.ts.map +1 -0
  72. package/dist/integrations/mcp/index.js +7 -0
  73. package/dist/integrations/mcp/index.js.map +1 -0
  74. package/dist/integrations/mcp/server.d.ts +14 -0
  75. package/dist/integrations/mcp/server.d.ts.map +1 -0
  76. package/dist/integrations/mcp/server.js +232 -0
  77. package/dist/integrations/mcp/server.js.map +1 -0
  78. package/dist/project/detector.d.ts +70 -0
  79. package/dist/project/detector.d.ts.map +1 -0
  80. package/dist/project/detector.js +214 -0
  81. package/dist/project/detector.js.map +1 -0
  82. package/dist/report/json.d.ts +26 -0
  83. package/dist/report/json.d.ts.map +1 -0
  84. package/dist/report/json.js +148 -0
  85. package/dist/report/json.js.map +1 -0
  86. package/dist/review/red-ink-review.d.ts +26 -0
  87. package/dist/review/red-ink-review.d.ts.map +1 -0
  88. package/dist/review/red-ink-review.js +103 -0
  89. package/dist/review/red-ink-review.js.map +1 -0
  90. package/fixtures/retry-429/README.md +8 -0
  91. package/fixtures/retry-429/baseline/package.json +4 -0
  92. package/fixtures/retry-429/baseline/src/client.test.ts +8 -0
  93. package/fixtures/retry-429/baseline/src/client.ts +3 -0
  94. package/fixtures/retry-429/baseline/src/retry-policy.ts +1 -0
  95. package/fixtures/retry-429/baseline/tsconfig.json +7 -0
  96. package/fixtures/retry-429/candidate/src/client.ts +5 -0
  97. package/fixtures/retry-429/candidate/src/retry-strategy.ts +9 -0
  98. package/fixtures/retry-429/candidate/src/unrelated-format.ts +1 -0
  99. package/hooks/copilot-activate.js +9 -0
  100. package/hooks/copilot-hooks.json +13 -0
  101. package/hooks/hooks.json +13 -0
  102. package/package.json +70 -0
  103. package/scripts/run.cjs +32 -0
  104. package/skills/frankly/SKILL.md +12 -0
  105. package/src/analysis/change-classifier.ts +285 -0
  106. package/src/analysis/repository-intelligence.ts +236 -0
  107. package/src/core/config.ts +149 -0
  108. package/src/core/engine.ts +143 -0
  109. package/src/core/errors.ts +56 -0
  110. package/src/core/index.ts +8 -0
  111. package/src/core/models.ts +280 -0
  112. package/src/git/index.ts +7 -0
  113. package/src/git/repository.ts +207 -0
  114. package/src/graph/symbols.ts +254 -0
  115. package/src/integrations/claude/checkpoint.ts +40 -0
  116. package/src/integrations/cli/demo.ts +34 -0
  117. package/src/integrations/cli/index.ts +211 -0
  118. package/src/integrations/mcp/index.ts +7 -0
  119. package/src/integrations/mcp/server.ts +257 -0
  120. package/src/project/detector.ts +242 -0
  121. package/src/report/json.ts +175 -0
  122. package/src/review/red-ink-review.ts +125 -0
  123. 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,7 @@
1
+ #!/usr/bin/env node
2
+ import { startMcpServer } from "./server.js";
3
+
4
+ startMcpServer().catch((error) => {
5
+ console.error("Frankly MCP server failed:", error);
6
+ process.exit(1);
7
+ });
@@ -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
+ }