@crossdelta/pf-mcp 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/dist/cli.js ADDED
@@ -0,0 +1,2379 @@
1
+ #!/usr/bin/env node
2
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
3
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
4
+ }) : x)(function(x) {
5
+ if (typeof require !== "undefined") return require.apply(this, arguments);
6
+ throw Error('Dynamic require of "' + x + '" is not supported');
7
+ });
8
+
9
+ // src/server.ts
10
+ import {
11
+ collectExecutableToolSpecs,
12
+ createContext,
13
+ createContextFromWorkspace as createContextFromWorkspace5,
14
+ isElicitInputEffect as isElicitInputEffect4,
15
+ loadPlugins
16
+ } from "@crossdelta/platform-sdk/facade";
17
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
18
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
19
+ import {
20
+ CallToolRequestSchema,
21
+ CompleteRequestSchema,
22
+ ListResourcesRequestSchema,
23
+ ListResourceTemplatesRequestSchema,
24
+ ListToolsRequestSchema,
25
+ ReadResourceRequestSchema
26
+ } from "@modelcontextprotocol/sdk/types.js";
27
+
28
+ // src/resources/contracts.ts
29
+ import { createContextFromWorkspace } from "@crossdelta/platform-sdk/facade";
30
+ var CONTRACTS_URI = "pf://contracts";
31
+ var buildContractsMeta = (contracts) => ({
32
+ path: contracts.path,
33
+ packageName: contracts.packageName
34
+ });
35
+ var readContracts = async (workspaceRoot) => {
36
+ const context = await createContextFromWorkspace(workspaceRoot);
37
+ const meta = buildContractsMeta(context.workspace.contracts);
38
+ return {
39
+ uri: CONTRACTS_URI,
40
+ mimeType: "application/json",
41
+ text: JSON.stringify(meta, null, 2)
42
+ };
43
+ };
44
+ var createContractsResource = (workspaceRoot) => ({
45
+ resource: {
46
+ uri: CONTRACTS_URI,
47
+ name: "Contracts",
48
+ description: "Contracts package configuration (path and package name).",
49
+ mimeType: "application/json"
50
+ },
51
+ read: async () => readContracts(workspaceRoot)
52
+ });
53
+
54
+ // src/resources/generator-docs.ts
55
+ import { readFile } from "fs/promises";
56
+ import { join } from "path";
57
+ import { getGeneratorDocsDir, listGeneratorDocs } from "@crossdelta/platform-sdk/facade";
58
+ var createGeneratorDocResource = (docName) => ({
59
+ resource: {
60
+ uri: `docs://generators/${docName}`,
61
+ name: `${docName} Generator Guidelines`,
62
+ description: `AI instructions for ${docName} generation`,
63
+ mimeType: "text/markdown"
64
+ },
65
+ async read() {
66
+ const docsDir = getGeneratorDocsDir();
67
+ const docPath = join(docsDir, `${docName}.md`);
68
+ try {
69
+ const content = await readFile(docPath, "utf-8");
70
+ return {
71
+ uri: `docs://generators/${docName}`,
72
+ mimeType: "text/markdown",
73
+ text: content
74
+ };
75
+ } catch (error) {
76
+ throw new Error(
77
+ `Failed to read generator doc '${docName}': ${error instanceof Error ? error.message : String(error)}`
78
+ );
79
+ }
80
+ }
81
+ });
82
+ var createGeneratorDocsListResource = () => ({
83
+ resource: {
84
+ uri: "docs://generators",
85
+ name: "Available Generator Documentation",
86
+ description: "List of all available generator guidelines",
87
+ mimeType: "application/json"
88
+ },
89
+ async read() {
90
+ const docs = await listGeneratorDocs();
91
+ const docsInfo = docs.map((name) => ({
92
+ name,
93
+ uri: `docs://generators/${name}`
94
+ }));
95
+ return {
96
+ uri: "docs://generators",
97
+ mimeType: "application/json",
98
+ text: JSON.stringify(docsInfo, null, 2)
99
+ };
100
+ }
101
+ });
102
+ var createAllGeneratorDocResources = async () => {
103
+ const docNames = await listGeneratorDocs();
104
+ const docResources = docNames.map(createGeneratorDocResource);
105
+ const listResource = createGeneratorDocsListResource();
106
+ return [listResource, ...docResources];
107
+ };
108
+
109
+ // src/plan-cache.ts
110
+ var planCache = /* @__PURE__ */ new Map();
111
+ var reviewCache = /* @__PURE__ */ new Map();
112
+ var cachePlan = (plan) => {
113
+ planCache.set(plan.planHash, plan);
114
+ };
115
+ var cacheReview = (review) => {
116
+ reviewCache.set(review.planHash, review);
117
+ };
118
+
119
+ // src/resources/services.ts
120
+ import { createContextFromWorkspace as createContextFromWorkspace2 } from "@crossdelta/platform-sdk/facade";
121
+ var SERVICES_URI = "pf://services";
122
+ var readServices = async (workspaceRoot) => {
123
+ const context = await createContextFromWorkspace2(workspaceRoot);
124
+ const meta = {
125
+ services: context.workspace.availableServices,
126
+ count: context.workspace.availableServices.length
127
+ };
128
+ return {
129
+ uri: SERVICES_URI,
130
+ mimeType: "application/json",
131
+ text: JSON.stringify(meta, null, 2)
132
+ };
133
+ };
134
+ var createServicesResource = (workspaceRoot) => ({
135
+ resource: {
136
+ uri: SERVICES_URI,
137
+ name: "Services",
138
+ description: "List of discovered services in the workspace.",
139
+ mimeType: "application/json"
140
+ },
141
+ read: async () => readServices(workspaceRoot)
142
+ });
143
+
144
+ // src/resources/templates.ts
145
+ import { readdir, readFile as readFile2, stat } from "fs/promises";
146
+ import { join as join2 } from "path";
147
+ var getTemplatesDir = () => {
148
+ const devPath = join2(process.cwd(), "packages/platform-sdk/bin/templates");
149
+ try {
150
+ const fs = __require("fs");
151
+ if (fs.existsSync(devPath)) {
152
+ return devPath;
153
+ }
154
+ } catch {
155
+ }
156
+ return join2(process.cwd(), "node_modules/@crossdelta/platform-sdk/bin/templates");
157
+ };
158
+ var getTemplateStructure = async (templateName) => {
159
+ const templateDir = join2(getTemplatesDir(), templateName);
160
+ const walk = async (dir, prefix = "") => {
161
+ try {
162
+ const entries = await readdir(dir, { withFileTypes: true });
163
+ const results = await Promise.all(
164
+ entries.map(async (entry) => {
165
+ const fullPath = join2(dir, entry.name);
166
+ const relativePath = prefix ? `${prefix}/${entry.name}` : entry.name;
167
+ if (entry.isDirectory()) {
168
+ const subFiles = await walk(fullPath, relativePath);
169
+ return [`${relativePath}/`, ...subFiles];
170
+ }
171
+ const stats = await stat(fullPath);
172
+ return [`${relativePath} (${stats.size} bytes)`];
173
+ })
174
+ );
175
+ return results.flat();
176
+ } catch {
177
+ return [];
178
+ }
179
+ };
180
+ return await walk(templateDir);
181
+ };
182
+ var listTemplates = async () => {
183
+ const templatesDir = getTemplatesDir();
184
+ try {
185
+ const entries = await readdir(templatesDir, { withFileTypes: true });
186
+ return entries.filter((e) => e.isDirectory()).map((e) => e.name);
187
+ } catch {
188
+ return [];
189
+ }
190
+ };
191
+ var listTemplateFiles = async (templateName) => {
192
+ const structure = await getTemplateStructure(templateName);
193
+ return structure.filter((entry) => !entry.endsWith("/")).map((entry) => entry.replace(/ \(\d+ bytes\)$/, ""));
194
+ };
195
+ var readTemplateFile = async (templateName, filePath) => {
196
+ const templateDir = join2(getTemplatesDir(), templateName);
197
+ const fullPath = join2(templateDir, filePath);
198
+ if (!fullPath.startsWith(templateDir)) {
199
+ throw new Error("Invalid file path");
200
+ }
201
+ return await readFile2(fullPath, "utf-8");
202
+ };
203
+ var readTemplateStructure = async (templateName) => {
204
+ const structure = await getTemplateStructure(templateName);
205
+ return JSON.stringify(
206
+ {
207
+ template: templateName,
208
+ files: structure,
209
+ totalFiles: structure.length
210
+ },
211
+ null,
212
+ 2
213
+ );
214
+ };
215
+ var createTemplatesListResource = () => ({
216
+ resource: {
217
+ uri: "templates://list",
218
+ name: "Available Service Templates",
219
+ description: "List of all available service templates (hono-microservice, nest-microservice, workspace)",
220
+ mimeType: "application/json"
221
+ },
222
+ async read() {
223
+ const templates = await listTemplates();
224
+ const templatesInfo = await Promise.all(
225
+ templates.map(async (name) => ({
226
+ name,
227
+ uri: `templates://${name}`,
228
+ description: `${name} template structure`
229
+ }))
230
+ );
231
+ return {
232
+ uri: "templates://list",
233
+ mimeType: "application/json",
234
+ text: JSON.stringify(
235
+ {
236
+ templates: templatesInfo,
237
+ count: templatesInfo.length
238
+ },
239
+ null,
240
+ 2
241
+ )
242
+ };
243
+ }
244
+ });
245
+ var createTemplateFileResourceTemplate = () => ({
246
+ uriTemplate: "templates://{template}/file/{path}",
247
+ name: "Template File",
248
+ description: "Read individual files from service templates",
249
+ mimeType: "text/plain"
250
+ });
251
+
252
+ // src/resources/workspace-summary.ts
253
+ import { createContextFromWorkspace as createContextFromWorkspace3 } from "@crossdelta/platform-sdk/facade";
254
+ var WORKSPACE_SUMMARY_URI = "pf://workspace/summary";
255
+ var buildWorkspaceSummary = (context, pluginModules) => ({
256
+ workspaceRoot: context.workspace.workspaceRoot,
257
+ contracts: context.workspace.contracts,
258
+ availableServices: context.workspace.availableServices,
259
+ loadedPlugins: pluginModules
260
+ });
261
+ var readWorkspaceSummary = async (pluginModules = [], workspaceRoot) => {
262
+ const context = await createContextFromWorkspace3(workspaceRoot);
263
+ const summary = buildWorkspaceSummary(context, pluginModules);
264
+ return {
265
+ uri: WORKSPACE_SUMMARY_URI,
266
+ mimeType: "application/json",
267
+ text: JSON.stringify(summary, null, 2)
268
+ };
269
+ };
270
+ var createWorkspaceSummaryResource = (pluginModules = [], workspaceRoot) => ({
271
+ resource: {
272
+ uri: WORKSPACE_SUMMARY_URI,
273
+ name: "Workspace Summary",
274
+ description: "Read-only workspace metadata including root path, contracts config, available services, and loaded plugins.",
275
+ mimeType: "application/json"
276
+ },
277
+ read: async () => readWorkspaceSummary(pluginModules, workspaceRoot)
278
+ });
279
+
280
+ // src/response.ts
281
+ import "@modelcontextprotocol/sdk/types.js";
282
+ var formatArtifactsSummary = (artifacts) => {
283
+ if (artifacts.length === 0) return "";
284
+ const grouped = artifacts.reduce(
285
+ (acc, a) => {
286
+ const key = a.type;
287
+ if (!acc[key]) acc[key] = [];
288
+ acc[key].push(a);
289
+ return acc;
290
+ },
291
+ {}
292
+ );
293
+ const lines = [];
294
+ for (const [type, items] of Object.entries(grouped)) {
295
+ lines.push(`
296
+ ${type.toUpperCase()} (${items.length}):`);
297
+ for (const item of items) {
298
+ lines.push(` - ${item.path}`);
299
+ }
300
+ }
301
+ return lines.join("\n");
302
+ };
303
+ var formatChangesSummary = (changes) => {
304
+ if (changes.length === 0) return "";
305
+ const grouped = changes.reduce(
306
+ (acc, c) => {
307
+ const key = c.kind;
308
+ if (!acc[key]) acc[key] = [];
309
+ acc[key].push(c);
310
+ return acc;
311
+ },
312
+ {}
313
+ );
314
+ const lines = [];
315
+ for (const [kind, items] of Object.entries(grouped)) {
316
+ lines.push(`
317
+ ${kind} (${items.length}):`);
318
+ for (const item of items) {
319
+ const path = "path" in item ? item.path : "";
320
+ lines.push(` - ${path || kind}`);
321
+ }
322
+ }
323
+ return lines.join("\n");
324
+ };
325
+ var formatNextActionsSummary = (next) => {
326
+ if (!next || next.length === 0) return "";
327
+ return `
328
+
329
+ NEXT STEPS:
330
+ ${next.map((n) => ` $ ${n.command}
331
+ ${n.description}`).join("\n")}`;
332
+ };
333
+ var formatDiagnosticsSummary = (diagnostics) => {
334
+ if (diagnostics.length === 0) return "";
335
+ return `
336
+
337
+ DIAGNOSTICS:
338
+ ${diagnostics.map((d) => ` [${d.level.toUpperCase()}] ${d.message}`).join("\n")}`;
339
+ };
340
+ var formatDependenciesSummary = (dependencies) => {
341
+ if (!dependencies || dependencies.length === 0) return "";
342
+ const lines = ["\n## Dependencies"];
343
+ for (const dep of dependencies) {
344
+ const version = dep.version || "latest";
345
+ const devTag = dep.dev ? " (dev)" : "";
346
+ lines.push(` - ${dep.name}@${version}${devTag}`);
347
+ }
348
+ return lines.join("\n");
349
+ };
350
+ var formatEnvVarsSummary = (envVars) => {
351
+ if (!envVars || envVars.length === 0) return "";
352
+ const lines = ["\n## Environment Variables"];
353
+ for (const ev of envVars) {
354
+ const reqTag = ev.required ? " (required)" : "";
355
+ lines.push(` - ${ev.key}${reqTag}: ${ev.description}`);
356
+ }
357
+ return lines.join("\n");
358
+ };
359
+ var formatFilesToGenerateSummary = (files) => {
360
+ if (!files || files.length === 0) return "";
361
+ const lines = ["\n## Files for AI to Generate"];
362
+ for (const file of files) {
363
+ const tags = [file.layer, file.kind].filter(Boolean).join("/");
364
+ lines.push(` - ${file.path} [${tags}]`);
365
+ lines.push(` Intent: ${file.intent}`);
366
+ }
367
+ return lines.join("\n");
368
+ };
369
+ var formatOperationResultAsMcpResponse = (result) => {
370
+ const extResult = result;
371
+ const textParts = [`# ${result.operation}`, "", result.ok ? "\u2713 Success" : "\u2717 Failed", "", result.summary];
372
+ if (result.artifacts.length > 0) {
373
+ textParts.push("\n## Artifacts");
374
+ textParts.push(formatArtifactsSummary(result.artifacts));
375
+ }
376
+ if (result.changes.length > 0) {
377
+ textParts.push("\n## Changes (Plan)");
378
+ textParts.push(formatChangesSummary(result.changes));
379
+ }
380
+ textParts.push(formatFilesToGenerateSummary(extResult.files));
381
+ textParts.push(formatDependenciesSummary(extResult.dependencies));
382
+ textParts.push(formatEnvVarsSummary(extResult.envVars));
383
+ if (extResult.postCommands && extResult.postCommands.length > 0) {
384
+ textParts.push("\n## Post Commands");
385
+ for (const cmd of extResult.postCommands) {
386
+ textParts.push(` $ ${cmd}`);
387
+ }
388
+ }
389
+ textParts.push(formatDiagnosticsSummary(result.diagnostics));
390
+ textParts.push(formatNextActionsSummary(result.next));
391
+ textParts.push("\n\n---\n## Raw Result (JSON)");
392
+ textParts.push("```json");
393
+ textParts.push(JSON.stringify(result, null, 2));
394
+ textParts.push("```");
395
+ return {
396
+ content: [
397
+ {
398
+ type: "text",
399
+ text: textParts.join("\n")
400
+ }
401
+ ],
402
+ structuredContent: result,
403
+ isError: !result.ok
404
+ };
405
+ };
406
+ var formatErrorAsMcpResponse = (error, operation) => {
407
+ return {
408
+ content: [
409
+ {
410
+ type: "text",
411
+ text: `# ${operation}
412
+
413
+ \u2717 Error: ${error.message}`
414
+ }
415
+ ],
416
+ structuredContent: {
417
+ ok: false,
418
+ operation,
419
+ summary: error.message,
420
+ artifacts: [],
421
+ changes: [],
422
+ diagnostics: [{ level: "error", message: error.message }]
423
+ },
424
+ isError: true
425
+ };
426
+ };
427
+ var formatElicitationAsMcpResponse = (elicitEffect, operation) => {
428
+ const fieldNames = elicitEffect.fields.map((f) => f.name);
429
+ const fieldDetails = elicitEffect.fields.map((f) => {
430
+ const parts = [`- **${f.name}**`];
431
+ if (f.required) parts.push(" (required)");
432
+ parts.push(`: ${f.prompt}`);
433
+ if (f.enum) parts.push(` [${f.enum.join(", ")}]`);
434
+ if (f.description) parts.push(`
435
+ ${f.description}`);
436
+ return parts.join("");
437
+ }).join("\n");
438
+ const textParts = [
439
+ `# ${operation}`,
440
+ "",
441
+ "\u23F3 Missing Required Inputs",
442
+ "",
443
+ elicitEffect.message,
444
+ "",
445
+ "## Please provide:",
446
+ fieldDetails,
447
+ "",
448
+ "---",
449
+ "Re-run the tool with these parameters."
450
+ ];
451
+ return {
452
+ content: [
453
+ {
454
+ type: "text",
455
+ text: textParts.join("\n")
456
+ }
457
+ ],
458
+ structuredContent: {
459
+ ok: false,
460
+ operation,
461
+ summary: elicitEffect.message,
462
+ artifacts: [],
463
+ changes: [],
464
+ diagnostics: [{ level: "info", message: "Awaiting user input" }],
465
+ elicitation: {
466
+ fields: elicitEffect.fields,
467
+ missingInputs: fieldNames
468
+ }
469
+ },
470
+ isError: false
471
+ // Not an error, just needs more input
472
+ };
473
+ };
474
+
475
+ // src/tools/apply-changes.ts
476
+ import {
477
+ findWorkspaceRoot,
478
+ isElicitInputEffect as isElicitInputEffect2
479
+ } from "@crossdelta/platform-sdk/facade";
480
+
481
+ // src/audit.ts
482
+ import { mkdirSync, writeFileSync } from "fs";
483
+ import { dirname, join as join3 } from "path";
484
+ var AUDIT_DIR = ".pf/audit";
485
+ var generateAuditFilename = (planHash, appliedAt) => {
486
+ const safeTimestamp = appliedAt.replace(/:/g, "-").replace(/\./g, "-");
487
+ return `${planHash.slice(0, 16)}-${safeTimestamp}.json`;
488
+ };
489
+ var writeAuditArtifact = (audit, workspaceRoot) => {
490
+ const auditDir = join3(workspaceRoot, AUDIT_DIR);
491
+ const filename = generateAuditFilename(audit.planHash, audit.appliedAt);
492
+ const filePath = join3(auditDir, filename);
493
+ mkdirSync(dirname(filePath), { recursive: true });
494
+ writeFileSync(filePath, JSON.stringify(audit, null, 2), "utf-8");
495
+ return filePath;
496
+ };
497
+ var createAuditArtifact = (planHash, policyVersion, effects, result, options = {}) => ({
498
+ planHash,
499
+ reviewToken: options.reviewToken,
500
+ policyVersion,
501
+ appliedAt: (/* @__PURE__ */ new Date()).toISOString(),
502
+ reviewedAt: options.reviewedAt,
503
+ dryRun: result.dryRun,
504
+ summary: result.summary,
505
+ counts: result.counts,
506
+ effects,
507
+ results: result.results
508
+ });
509
+
510
+ // src/effects/executors/deps-add.ts
511
+ import { existsSync, readFileSync, writeFileSync as writeFileSync2 } from "fs";
512
+ import { join as join4 } from "path";
513
+
514
+ // src/types/plan.ts
515
+ var DEFAULT_DEPENDENCY_POLICY = {
516
+ denyPatterns: ["file:", "git+", "git://", "github:", "https://", "http://"],
517
+ pinningStrategy: "caret"
518
+ };
519
+
520
+ // src/effects/types.ts
521
+ var ErrorCodes = {
522
+ PATH_OUTSIDE_ROOT: "PATH_OUTSIDE_ROOT",
523
+ REQUIRES_ELICITATION: "REQUIRES_ELICITATION",
524
+ UNSUPPORTED_EFFECT: "UNSUPPORTED_EFFECT",
525
+ WRITE_FAILED: "WRITE_FAILED",
526
+ ATOMIC_RENAME_FAILED: "ATOMIC_RENAME_FAILED",
527
+ PLAN_HASH_MISMATCH: "PLAN_HASH_MISMATCH",
528
+ // M10: Review gate error codes
529
+ REVIEW_REQUIRED: "REVIEW_REQUIRED",
530
+ REVIEW_TOKEN_INVALID: "REVIEW_TOKEN_INVALID",
531
+ REVIEW_TOKEN_MISMATCH: "REVIEW_TOKEN_MISMATCH",
532
+ POLICY_VERSION_MISMATCH: "POLICY_VERSION_MISMATCH",
533
+ // Policy validation error codes
534
+ POLICY_VIOLATION: "POLICY_VIOLATION",
535
+ FORBIDDEN_DIR: "FORBIDDEN_DIR",
536
+ CONSOLE_IN_DOMAIN: "CONSOLE_IN_DOMAIN",
537
+ PROCESS_ENV_IN_DOMAIN: "PROCESS_ENV_IN_DOMAIN",
538
+ FETCH_IN_DOMAIN: "FETCH_IN_DOMAIN"
539
+ };
540
+
541
+ // src/effects/executors/deps-add.ts
542
+ var validateDependency = (name, version, policy) => {
543
+ for (const pattern of policy.denyPatterns) {
544
+ if (name.startsWith(pattern)) {
545
+ return { valid: false, reason: `Package name matches denied pattern: ${pattern}` };
546
+ }
547
+ }
548
+ if (version) {
549
+ for (const pattern of policy.denyPatterns) {
550
+ if (version.startsWith(pattern)) {
551
+ return { valid: false, reason: `Version matches denied pattern: ${pattern}` };
552
+ }
553
+ }
554
+ }
555
+ return { valid: true };
556
+ };
557
+ var resolveVersion = (version, policy) => {
558
+ if (!version || version === "latest") {
559
+ return "*";
560
+ }
561
+ if (/^\d/.test(version)) {
562
+ switch (policy.pinningStrategy) {
563
+ case "exact":
564
+ return version;
565
+ case "tilde":
566
+ return `~${version}`;
567
+ default:
568
+ return `^${version}`;
569
+ }
570
+ }
571
+ return version;
572
+ };
573
+ var createSkipResult = (effect, packageJsonPath, existingVersion, dryRun) => ({
574
+ effect,
575
+ success: true,
576
+ status: dryRun ? "would_skip" : "skipped",
577
+ message: dryRun ? `Would skip: ${effect.name}@${existingVersion} already exists` : `Dependency ${effect.name}@${existingVersion} already exists`,
578
+ path: packageJsonPath,
579
+ target: effect.name,
580
+ reason: "dependency exists"
581
+ });
582
+ var createAddResult = (effect, packageJsonPath, version, dryRun) => ({
583
+ effect,
584
+ success: true,
585
+ status: dryRun ? "would_create" : "created",
586
+ message: dryRun ? `Would add dependency: ${effect.name}@${version}` : `Added dependency: ${effect.name}@${version}`,
587
+ path: packageJsonPath,
588
+ target: effect.name
589
+ });
590
+ var createPolicyViolationResult = (effect, reason) => ({
591
+ effect,
592
+ success: false,
593
+ status: "error",
594
+ message: `Policy violation: ${reason}`,
595
+ target: effect.name,
596
+ code: ErrorCodes.POLICY_VIOLATION,
597
+ reason
598
+ });
599
+ var createErrorResult = (effect, packageJsonPath, error) => ({
600
+ effect,
601
+ success: false,
602
+ status: "error",
603
+ message: `Failed to add dependency ${effect.name}: ${error instanceof Error ? error.message : String(error)}`,
604
+ path: packageJsonPath,
605
+ target: effect.name,
606
+ code: ErrorCodes.WRITE_FAILED
607
+ });
608
+ var createMissingPackageJsonResult = (effect, packageJsonPath) => ({
609
+ effect,
610
+ success: false,
611
+ status: "error",
612
+ message: `package.json not found at ${packageJsonPath}`,
613
+ path: packageJsonPath,
614
+ target: effect.name,
615
+ code: ErrorCodes.WRITE_FAILED
616
+ });
617
+ var executeDepsAdd = (effect, workspaceRoot, options, policy = DEFAULT_DEPENDENCY_POLICY) => {
618
+ const { name, version, dev = false, targetDir } = effect;
619
+ const { dryRun = false } = options;
620
+ const baseDir = targetDir ? join4(workspaceRoot, targetDir) : workspaceRoot;
621
+ const packageJsonPath = join4(baseDir, "package.json");
622
+ const validation = validateDependency(name, version, policy);
623
+ if (!validation.valid) {
624
+ return createPolicyViolationResult(effect, validation.reason);
625
+ }
626
+ if (!existsSync(packageJsonPath)) {
627
+ return createMissingPackageJsonResult(effect, packageJsonPath);
628
+ }
629
+ try {
630
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
631
+ const depsKey = dev ? "devDependencies" : "dependencies";
632
+ const deps = packageJson[depsKey] ?? {};
633
+ if (name in deps) {
634
+ return createSkipResult(effect, packageJsonPath, deps[name], dryRun);
635
+ }
636
+ const resolvedVersion = resolveVersion(version, policy);
637
+ if (!dryRun) {
638
+ packageJson[depsKey] = {
639
+ ...deps,
640
+ [name]: resolvedVersion
641
+ };
642
+ packageJson[depsKey] = Object.keys(packageJson[depsKey]).sort().reduce(
643
+ (acc, key) => {
644
+ acc[key] = packageJson[depsKey][key];
645
+ return acc;
646
+ },
647
+ {}
648
+ );
649
+ writeFileSync2(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}
650
+ `, "utf-8");
651
+ }
652
+ return createAddResult(effect, packageJsonPath, resolvedVersion, dryRun);
653
+ } catch (error) {
654
+ return createErrorResult(effect, packageJsonPath, error);
655
+ }
656
+ };
657
+
658
+ // src/effects/utils.ts
659
+ import { randomUUID } from "crypto";
660
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, renameSync, unlinkSync, writeFileSync as writeFileSync3 } from "fs";
661
+ import { dirname as dirname2, join as join5, resolve } from "path";
662
+ var normalizeLineEndings = (content) => content.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
663
+ var resolveSafePath = (targetPath, workspaceRoot) => {
664
+ const resolvedRoot = resolve(workspaceRoot);
665
+ const absolutePath = targetPath.startsWith("/") ? resolve(targetPath) : resolve(workspaceRoot, targetPath);
666
+ const isWithinRoot = absolutePath.startsWith(`${resolvedRoot}/`) || absolutePath === resolvedRoot;
667
+ return isWithinRoot ? { safe: true, absolutePath } : { safe: false, reason: `Path "${targetPath}" resolves outside workspace root` };
668
+ };
669
+ var readFileSafe = (absolutePath) => existsSync2(absolutePath) ? readFileSync2(absolutePath, "utf-8") : void 0;
670
+ var atomicWriteFile = (absolutePath, content) => {
671
+ const dir = dirname2(absolutePath);
672
+ mkdirSync2(dir, { recursive: true });
673
+ const tempPath = join5(dir, `.tmp-${randomUUID()}`);
674
+ try {
675
+ writeFileSync3(tempPath, content, "utf-8");
676
+ renameSync(tempPath, absolutePath);
677
+ } catch (error) {
678
+ cleanupTempFile(tempPath);
679
+ throw error;
680
+ }
681
+ };
682
+ var cleanupTempFile = (tempPath) => {
683
+ try {
684
+ if (existsSync2(tempPath)) {
685
+ unlinkSync(tempPath);
686
+ }
687
+ } catch {
688
+ }
689
+ };
690
+
691
+ // src/effects/executors/env-add.ts
692
+ var createSkipResult2 = (effect, key, envPath, dryRun) => ({
693
+ effect,
694
+ success: true,
695
+ status: dryRun ? "would_skip" : "skipped",
696
+ message: dryRun ? `Would skip: ${key} already exists` : `Env var ${key} already exists`,
697
+ path: envPath,
698
+ target: key,
699
+ reason: "key exists"
700
+ });
701
+ var createAddResult2 = (effect, key, envPath, dryRun) => ({
702
+ effect,
703
+ success: true,
704
+ status: dryRun ? "would_create" : "created",
705
+ message: dryRun ? `Would add env var: ${key}` : `Added env var: ${key}`,
706
+ path: envPath,
707
+ target: key
708
+ });
709
+ var createErrorResult2 = (effect, key, envPath, error) => ({
710
+ effect,
711
+ success: false,
712
+ status: "error",
713
+ message: `Failed to add env var ${key}: ${error instanceof Error ? error.message : String(error)}`,
714
+ path: envPath,
715
+ target: key,
716
+ code: ErrorCodes.WRITE_FAILED
717
+ });
718
+ var keyExists = (content, key) => {
719
+ const keyRegex = new RegExp(`^${key}=`, "m");
720
+ return keyRegex.test(content);
721
+ };
722
+ var buildNewContent = (existingContent, key, value) => {
723
+ const normalized = normalizeLineEndings(existingContent);
724
+ const needsNewline = normalized.length > 0 && !normalized.endsWith("\n");
725
+ const prefix = needsNewline ? "\n" : "";
726
+ return `${normalized}${prefix}${key}=${value}
727
+ `;
728
+ };
729
+ var executeEnvAdd = (effect, workspaceRoot, options) => {
730
+ const { key, value } = effect;
731
+ const { dryRun = false } = options;
732
+ const envPath = `${workspaceRoot}/.env.local`;
733
+ try {
734
+ const existingContent = readFileSafe(envPath) ?? "";
735
+ if (keyExists(existingContent, key)) {
736
+ return createSkipResult2(effect, key, envPath, dryRun);
737
+ }
738
+ if (!dryRun) {
739
+ const newContent = buildNewContent(existingContent, key, value);
740
+ atomicWriteFile(envPath, newContent);
741
+ }
742
+ return createAddResult2(effect, key, envPath, dryRun);
743
+ } catch (error) {
744
+ return createErrorResult2(effect, key, envPath, error);
745
+ }
746
+ };
747
+
748
+ // src/plan-hash.ts
749
+ import { createHash } from "crypto";
750
+ var stableStringify = (value) => {
751
+ if (value === null || value === void 0) {
752
+ return JSON.stringify(value);
753
+ }
754
+ if (Array.isArray(value)) {
755
+ return `[${value.map(stableStringify).join(",")}]`;
756
+ }
757
+ if (typeof value === "object") {
758
+ const obj = value;
759
+ const sortedKeys = Object.keys(obj).sort();
760
+ const pairs = sortedKeys.map((key) => `${JSON.stringify(key)}:${stableStringify(obj[key])}`);
761
+ return `{${pairs.join(",")}}`;
762
+ }
763
+ return JSON.stringify(value);
764
+ };
765
+ var computePlanHash = (changes) => {
766
+ const normalized = stableStringify(changes);
767
+ return createHash("sha256").update(normalized, "utf-8").digest("hex");
768
+ };
769
+ var computeContentHash = (content) => createHash("sha256").update(content, "utf-8").digest("hex");
770
+
771
+ // src/effects/executors/file-write.ts
772
+ var createPathSafetyError = (effect, relativePath, reason) => ({
773
+ effect,
774
+ success: false,
775
+ status: "error",
776
+ message: reason,
777
+ target: relativePath,
778
+ code: ErrorCodes.PATH_OUTSIDE_ROOT
779
+ });
780
+ var createSkipResult3 = (effect, relativePath, absolutePath, dryRun, existingContent, newContent) => ({
781
+ effect,
782
+ success: true,
783
+ status: dryRun ? "would_skip" : "skipped",
784
+ message: dryRun ? `Would skip (unchanged): ${relativePath}` : `File unchanged: ${relativePath}`,
785
+ path: absolutePath,
786
+ target: relativePath,
787
+ reason: "content identical",
788
+ preview: {
789
+ oldBytes: existingContent.length,
790
+ newBytes: newContent.length,
791
+ oldHash: computeContentHash(existingContent),
792
+ newHash: computeContentHash(newContent),
793
+ wouldOverwrite: false,
794
+ existingLength: existingContent.length,
795
+ newLength: newContent.length
796
+ }
797
+ });
798
+ var createUpdateResult = (effect, relativePath, absolutePath, dryRun, existingContent, newContent) => ({
799
+ effect,
800
+ success: true,
801
+ status: dryRun ? "would_update" : "updated",
802
+ message: dryRun ? `Would update: ${relativePath}` : `Updated: ${relativePath}`,
803
+ path: absolutePath,
804
+ target: relativePath,
805
+ reason: "content differs",
806
+ preview: {
807
+ oldBytes: existingContent.length,
808
+ newBytes: newContent.length,
809
+ oldHash: computeContentHash(existingContent),
810
+ newHash: computeContentHash(newContent),
811
+ wouldOverwrite: true,
812
+ existingLength: existingContent.length,
813
+ newLength: newContent.length
814
+ }
815
+ });
816
+ var createCreateResult = (effect, relativePath, absolutePath, dryRun, newContent) => ({
817
+ effect,
818
+ success: true,
819
+ status: dryRun ? "would_create" : "created",
820
+ message: dryRun ? `Would create: ${relativePath}` : `Created: ${relativePath}`,
821
+ path: absolutePath,
822
+ target: relativePath,
823
+ preview: {
824
+ newBytes: newContent.length,
825
+ newHash: computeContentHash(newContent),
826
+ wouldOverwrite: false,
827
+ newLength: newContent.length
828
+ }
829
+ });
830
+ var createWriteError = (effect, relativePath, absolutePath, error) => ({
831
+ effect,
832
+ success: false,
833
+ status: "error",
834
+ message: `Failed to write ${relativePath}: ${error instanceof Error ? error.message : String(error)}`,
835
+ path: absolutePath,
836
+ target: relativePath,
837
+ code: ErrorCodes.WRITE_FAILED
838
+ });
839
+ var decideWriteAction = (absolutePath, normalizedContent) => {
840
+ const existing = readFileSafe(absolutePath);
841
+ if (existing === void 0) {
842
+ return { action: "create" };
843
+ }
844
+ const normalizedExisting = normalizeLineEndings(existing);
845
+ return normalizedExisting === normalizedContent ? { action: "skip", existingContent: normalizedExisting } : { action: "update", existingContent: normalizedExisting };
846
+ };
847
+ var executeFileWrite = (effect, workspaceRoot, options) => {
848
+ const { path: relativePath, content } = effect;
849
+ const { dryRun = false } = options;
850
+ const pathResult = resolveSafePath(relativePath, workspaceRoot);
851
+ if (!pathResult.safe) {
852
+ return createPathSafetyError(effect, relativePath, pathResult.reason);
853
+ }
854
+ const { absolutePath } = pathResult;
855
+ const normalizedContent = normalizeLineEndings(content);
856
+ try {
857
+ const decision = decideWriteAction(absolutePath, normalizedContent);
858
+ switch (decision.action) {
859
+ case "skip":
860
+ return createSkipResult3(effect, relativePath, absolutePath, dryRun, decision.existingContent, normalizedContent);
861
+ case "update":
862
+ if (!dryRun) {
863
+ atomicWriteFile(absolutePath, normalizedContent);
864
+ }
865
+ return createUpdateResult(
866
+ effect,
867
+ relativePath,
868
+ absolutePath,
869
+ dryRun,
870
+ decision.existingContent,
871
+ normalizedContent
872
+ );
873
+ case "create":
874
+ if (!dryRun) {
875
+ atomicWriteFile(absolutePath, normalizedContent);
876
+ }
877
+ return createCreateResult(effect, relativePath, absolutePath, dryRun, normalizedContent);
878
+ }
879
+ } catch (error) {
880
+ return createWriteError(effect, relativePath, absolutePath, error);
881
+ }
882
+ };
883
+
884
+ // src/effects/executors/infra-add.ts
885
+ var createPathSafetyError2 = (effect, relativePath, reason) => ({
886
+ effect,
887
+ success: false,
888
+ status: "error",
889
+ message: reason,
890
+ target: relativePath,
891
+ code: ErrorCodes.PATH_OUTSIDE_ROOT
892
+ });
893
+ var createSkipResult4 = (effect, serviceName, absolutePath, relativePath, dryRun) => ({
894
+ effect,
895
+ success: true,
896
+ status: dryRun ? "would_skip" : "skipped",
897
+ message: dryRun ? `Would skip (unchanged): ${serviceName}` : `Infra config unchanged: ${serviceName}`,
898
+ path: absolutePath,
899
+ target: relativePath,
900
+ reason: "content identical"
901
+ });
902
+ var createUpdateResult2 = (effect, serviceName, absolutePath, relativePath, dryRun) => ({
903
+ effect,
904
+ success: true,
905
+ status: dryRun ? "would_update" : "updated",
906
+ message: dryRun ? `Would update infra config: ${serviceName}` : `Updated infra config: ${serviceName}`,
907
+ path: absolutePath,
908
+ target: relativePath
909
+ });
910
+ var createCreateResult2 = (effect, serviceName, absolutePath, relativePath, dryRun) => ({
911
+ effect,
912
+ success: true,
913
+ status: dryRun ? "would_create" : "created",
914
+ message: dryRun ? `Would create infra config: ${serviceName}` : `Created infra config: ${serviceName}`,
915
+ path: absolutePath,
916
+ target: relativePath
917
+ });
918
+ var createErrorResult3 = (effect, serviceName, absolutePath, relativePath, error) => ({
919
+ effect,
920
+ success: false,
921
+ status: "error",
922
+ message: `Failed to create infra config for ${serviceName}: ${error instanceof Error ? error.message : String(error)}`,
923
+ path: absolutePath,
924
+ target: relativePath,
925
+ code: ErrorCodes.WRITE_FAILED
926
+ });
927
+ var generateInfraContent = (serviceName, port) => `import { ports, type K8sServiceConfig } from '@crossdelta/infrastructure'
928
+
929
+ const config: K8sServiceConfig = {
930
+ name: '${serviceName}',
931
+ ports: ports().http(${port}).build(),
932
+ replicas: 1,
933
+ healthCheck: { httpPath: '/health' },
934
+ resources: {
935
+ requests: { cpu: '50m', memory: '64Mi' },
936
+ limits: { cpu: '150m', memory: '128Mi' },
937
+ },
938
+ }
939
+
940
+ export default config
941
+ `;
942
+ var decideInfraAction = (absolutePath, normalizedContent) => {
943
+ const existing = readFileSafe(absolutePath);
944
+ if (existing === void 0) {
945
+ return { action: "create" };
946
+ }
947
+ const normalizedExisting = normalizeLineEndings(existing);
948
+ return normalizedExisting === normalizedContent ? { action: "skip" } : { action: "update" };
949
+ };
950
+ var executeInfraAdd = (effect, workspaceRoot, options) => {
951
+ const { serviceName, port } = effect;
952
+ const { dryRun = false } = options;
953
+ const relativePath = `infra/services/${serviceName}.ts`;
954
+ const pathResult = resolveSafePath(relativePath, workspaceRoot);
955
+ if (!pathResult.safe) {
956
+ return createPathSafetyError2(effect, relativePath, pathResult.reason);
957
+ }
958
+ const { absolutePath } = pathResult;
959
+ const content = generateInfraContent(serviceName, port);
960
+ const normalizedContent = normalizeLineEndings(content);
961
+ try {
962
+ const decision = decideInfraAction(absolutePath, normalizedContent);
963
+ switch (decision.action) {
964
+ case "skip":
965
+ return createSkipResult4(effect, serviceName, absolutePath, relativePath, dryRun);
966
+ case "update":
967
+ if (!dryRun) {
968
+ atomicWriteFile(absolutePath, content);
969
+ }
970
+ return createUpdateResult2(effect, serviceName, absolutePath, relativePath, dryRun);
971
+ case "create":
972
+ if (!dryRun) {
973
+ atomicWriteFile(absolutePath, content);
974
+ }
975
+ return createCreateResult2(effect, serviceName, absolutePath, relativePath, dryRun);
976
+ }
977
+ } catch (error) {
978
+ return createErrorResult3(effect, serviceName, absolutePath, relativePath, error);
979
+ }
980
+ };
981
+
982
+ // src/effects/summary.ts
983
+ var statusCountMap = {
984
+ created: "created",
985
+ updated: "updated",
986
+ skipped: "skipped",
987
+ error: "error",
988
+ would_create: "wouldCreate",
989
+ would_update: "wouldUpdate",
990
+ would_skip: "wouldSkip"
991
+ };
992
+ var initialCounts = {
993
+ created: 0,
994
+ updated: 0,
995
+ skipped: 0,
996
+ error: 0,
997
+ wouldCreate: 0,
998
+ wouldUpdate: 0,
999
+ wouldSkip: 0
1000
+ };
1001
+ var initialKindCounts = { total: 0, created: 0, updated: 0, skipped: 0, error: 0 };
1002
+ var createInitialCounts = () => ({ ...initialCounts });
1003
+ var incrementCount = (counts, key) => ({
1004
+ ...counts,
1005
+ [key]: counts[key] + 1
1006
+ });
1007
+ var aggregateCounts = (results) => results.reduce((acc, r) => {
1008
+ const key = statusCountMap[r.status];
1009
+ return key ? incrementCount(acc, key) : acc;
1010
+ }, createInitialCounts());
1011
+ var dryRunSummaryParts = (counts) => [
1012
+ counts.wouldCreate > 0 ? `${counts.wouldCreate} would be created` : "",
1013
+ counts.wouldUpdate > 0 ? `${counts.wouldUpdate} would be updated` : "",
1014
+ counts.wouldSkip > 0 ? `${counts.wouldSkip} would be skipped` : ""
1015
+ ].filter(Boolean);
1016
+ var normalSummaryParts = (counts) => [
1017
+ counts.created > 0 ? `${counts.created} created` : "",
1018
+ counts.updated > 0 ? `${counts.updated} updated` : "",
1019
+ counts.skipped > 0 ? `${counts.skipped} skipped` : ""
1020
+ ].filter(Boolean);
1021
+ var buildSummary = (counts, dryRun) => {
1022
+ const modeParts = dryRun ? dryRunSummaryParts(counts) : normalSummaryParts(counts);
1023
+ const errorPart = counts.error > 0 ? [`${counts.error} errors`] : [];
1024
+ const parts = [...modeParts, ...errorPart];
1025
+ return parts.length > 0 ? parts.join(", ") : "No effects to apply";
1026
+ };
1027
+ var statusToKindKey = (status) => {
1028
+ switch (status) {
1029
+ case "created":
1030
+ case "would_create":
1031
+ return "created";
1032
+ case "updated":
1033
+ case "would_update":
1034
+ return "updated";
1035
+ case "skipped":
1036
+ case "would_skip":
1037
+ return "skipped";
1038
+ case "error":
1039
+ return "error";
1040
+ default:
1041
+ return null;
1042
+ }
1043
+ };
1044
+ var incrementKindCounts = (counts, status) => {
1045
+ const key = statusToKindKey(status);
1046
+ return key ? { ...counts, total: counts.total + 1, [key]: counts[key] + 1 } : { ...counts, total: counts.total + 1 };
1047
+ };
1048
+ var updateByKind = (byKind, kind, status) => ({
1049
+ ...byKind,
1050
+ [kind]: incrementKindCounts(byKind[kind] ?? { ...initialKindCounts }, status)
1051
+ });
1052
+ var buildStructuredSummary = (results) => results.reduce(
1053
+ (acc, r) => ({
1054
+ byKind: updateByKind(acc.byKind, r.effect.kind, r.status),
1055
+ totalNewBytes: acc.totalNewBytes + (r.preview?.newBytes ?? 0),
1056
+ totalOldBytes: acc.totalOldBytes + (r.preview?.oldBytes ?? 0)
1057
+ }),
1058
+ { byKind: {}, totalNewBytes: 0, totalOldBytes: 0 }
1059
+ );
1060
+
1061
+ // src/effects/validation.ts
1062
+ import { validateGeneratedFiles } from "@crossdelta/platform-sdk/facade";
1063
+ var extractPlannedFiles = (effects) => effects.filter((e) => e.kind === "file:write").map((e) => ({ path: e.path, content: e.content }));
1064
+ var detectFramework = (effects) => {
1065
+ const infraEffect = effects.find((e) => e.kind === "infra:add");
1066
+ if (!infraEffect) return void 0;
1067
+ const frameworkMap = {
1068
+ hono: "hono",
1069
+ nest: "nestjs",
1070
+ nestjs: "nestjs"
1071
+ };
1072
+ return frameworkMap[infraEffect.framework];
1073
+ };
1074
+ var createPolicyViolationResult2 = (violations, dryRun) => ({
1075
+ ok: false,
1076
+ summary: `Policy violations: ${violations.length} issue(s) found`,
1077
+ results: violations.map((v) => ({
1078
+ effect: { kind: "file:write", path: v.path, content: "" },
1079
+ success: false,
1080
+ status: "error",
1081
+ message: v.message,
1082
+ path: v.path,
1083
+ target: v.path,
1084
+ code: v.code
1085
+ })),
1086
+ counts: {
1087
+ created: 0,
1088
+ updated: 0,
1089
+ skipped: 0,
1090
+ error: violations.length,
1091
+ wouldCreate: 0,
1092
+ wouldUpdate: 0,
1093
+ wouldSkip: 0
1094
+ },
1095
+ dryRun,
1096
+ code: ErrorCodes.POLICY_VIOLATION,
1097
+ policyViolations: [...violations]
1098
+ });
1099
+ var validateEffects = (effects, framework, dryRun) => {
1100
+ if (!framework) {
1101
+ return { valid: true };
1102
+ }
1103
+ const plannedFiles = extractPlannedFiles(effects);
1104
+ const validationResult = validateGeneratedFiles(framework, plannedFiles);
1105
+ if (validationResult.valid) {
1106
+ return { valid: true };
1107
+ }
1108
+ return {
1109
+ valid: false,
1110
+ result: createPolicyViolationResult2(validationResult.violations, dryRun)
1111
+ };
1112
+ };
1113
+
1114
+ // src/effects/index.ts
1115
+ var effectExecutors = {
1116
+ "file:write": executeFileWrite,
1117
+ "env:add": executeEnvAdd,
1118
+ "infra:add": executeInfraAdd,
1119
+ "deps:add": executeDepsAdd
1120
+ };
1121
+ var isElicitInputEffect = (effect) => effect.kind === "elicit:input";
1122
+ var createElicitationRejection = (elicitEffects, dryRun) => ({
1123
+ ok: false,
1124
+ summary: "Cannot apply: Plan requires user input (elicitation pending)",
1125
+ results: elicitEffects.map((effect) => ({
1126
+ effect,
1127
+ success: false,
1128
+ status: "error",
1129
+ message: "ElicitInputEffect cannot be applied - requires host interaction",
1130
+ code: ErrorCodes.REQUIRES_ELICITATION
1131
+ })),
1132
+ counts: {
1133
+ ...createInitialCounts(),
1134
+ error: elicitEffects.length
1135
+ },
1136
+ dryRun
1137
+ });
1138
+ var createUnsupportedEffectResult = (effect) => ({
1139
+ effect,
1140
+ success: false,
1141
+ status: "error",
1142
+ message: `Unsupported effect kind: ${effect.kind}`,
1143
+ code: ErrorCodes.UNSUPPORTED_EFFECT
1144
+ });
1145
+ var applyEffects = (effects, workspaceRoot, options = {}) => {
1146
+ const { dryRun = false, framework: explicitFramework } = options;
1147
+ const elicitEffects = effects.filter(isElicitInputEffect);
1148
+ if (elicitEffects.length > 0) {
1149
+ return createElicitationRejection(elicitEffects, dryRun);
1150
+ }
1151
+ const framework = explicitFramework ?? detectFramework(effects);
1152
+ const validation = validateEffects(effects, framework, dryRun);
1153
+ if (!validation.valid) {
1154
+ return validation.result;
1155
+ }
1156
+ const executeEffect = (effect) => {
1157
+ const executor = effectExecutors[effect.kind];
1158
+ return executor ? executor(effect, workspaceRoot, options) : createUnsupportedEffectResult(effect);
1159
+ };
1160
+ const results = effects.map(executeEffect);
1161
+ const counts = aggregateCounts(results);
1162
+ return {
1163
+ ok: counts.error === 0,
1164
+ summary: buildSummary(counts, dryRun),
1165
+ results,
1166
+ counts,
1167
+ dryRun,
1168
+ structuredSummary: buildStructuredSummary(results)
1169
+ };
1170
+ };
1171
+ var isEffectSupported = (kind) => kind in effectExecutors;
1172
+
1173
+ // src/policy.ts
1174
+ var POLICY_VERSION = "2025-01-03";
1175
+ var generateReviewToken = (planHash, policyVersion = POLICY_VERSION) => `${planHash}.${policyVersion}`;
1176
+ var parseReviewToken = (token) => {
1177
+ const dotIndex = token.indexOf(".");
1178
+ if (dotIndex === -1) return null;
1179
+ const planHash = token.slice(0, dotIndex);
1180
+ const policyVersion = token.slice(dotIndex + 1);
1181
+ if (planHash.length !== 64 || !/^[a-f0-9]+$/i.test(planHash)) return null;
1182
+ if (!policyVersion || policyVersion.length === 0) return null;
1183
+ return { planHash, policyVersion };
1184
+ };
1185
+ var validateReviewToken = (token, expectedPlanHash, expectedPolicyVersion = POLICY_VERSION) => {
1186
+ const parsed = parseReviewToken(token);
1187
+ if (!parsed) {
1188
+ return {
1189
+ valid: false,
1190
+ code: "REVIEW_TOKEN_INVALID",
1191
+ message: "Invalid review token format (expected: planHash.policyVersion)"
1192
+ };
1193
+ }
1194
+ if (parsed.planHash !== expectedPlanHash) {
1195
+ return {
1196
+ valid: false,
1197
+ code: "REVIEW_TOKEN_MISMATCH",
1198
+ message: `Plan hash mismatch: token has ${parsed.planHash.slice(0, 8)}..., expected ${expectedPlanHash.slice(0, 8)}...`,
1199
+ parsed
1200
+ };
1201
+ }
1202
+ if (parsed.policyVersion !== expectedPolicyVersion) {
1203
+ return {
1204
+ valid: false,
1205
+ code: "POLICY_VERSION_MISMATCH",
1206
+ message: `Policy version mismatch: token has ${parsed.policyVersion}, expected ${expectedPolicyVersion}`,
1207
+ parsed
1208
+ };
1209
+ }
1210
+ return { valid: true, parsed };
1211
+ };
1212
+
1213
+ // src/tools/apply-changes.ts
1214
+ var validateChanges = (changes) => {
1215
+ if (!Array.isArray(changes)) return false;
1216
+ return changes.every((c) => typeof c === "object" && c !== null && "kind" in c && typeof c.kind === "string");
1217
+ };
1218
+ var createFailure = (summary, errorMsg, infoMsg, code) => ({
1219
+ ok: false,
1220
+ operation: "apply_changes",
1221
+ summary,
1222
+ artifacts: [],
1223
+ changes: [],
1224
+ diagnostics: [
1225
+ { level: "error", message: errorMsg },
1226
+ ...infoMsg ? [{ level: "info", message: infoMsg }] : []
1227
+ ],
1228
+ data: code ? {
1229
+ ok: false,
1230
+ dryRun: false,
1231
+ summary,
1232
+ results: [],
1233
+ counts: { created: 0, updated: 0, skipped: 0, error: 1, wouldCreate: 0, wouldUpdate: 0, wouldSkip: 0 },
1234
+ code: ErrorCodes[code]
1235
+ } : void 0
1236
+ });
1237
+ var checkReviewGate = (requireReview, reviewToken, actualHash, policyVer) => {
1238
+ if (requireReview !== true) return null;
1239
+ if (typeof reviewToken !== "string") {
1240
+ return createFailure(
1241
+ "Review required but no reviewToken provided",
1242
+ "requireReview is enabled but reviewToken was not provided",
1243
+ "Run plan_review first to get a reviewToken, then pass it to apply_changes",
1244
+ "REVIEW_REQUIRED"
1245
+ );
1246
+ }
1247
+ const validation = validateReviewToken(reviewToken, actualHash, policyVer);
1248
+ if (!validation.valid) {
1249
+ const info = validation.code === "POLICY_VERSION_MISMATCH" ? "Policy version has changed. Re-run plan_review to get a new reviewToken." : "Re-run plan_review to get a valid reviewToken for the current plan";
1250
+ return createFailure(
1251
+ `Review token validation failed: ${validation.message}`,
1252
+ validation.message ?? "Review token validation failed",
1253
+ info,
1254
+ validation.code
1255
+ );
1256
+ }
1257
+ return null;
1258
+ };
1259
+ var checkPlanHash = (expectedHash, actualHash) => {
1260
+ if (typeof expectedHash !== "string") return null;
1261
+ if (actualHash === expectedHash) return null;
1262
+ return createFailure(
1263
+ "Plan hash mismatch: changes have been modified since review",
1264
+ `Expected hash: ${expectedHash}, got: ${actualHash}`,
1265
+ "Re-run plan_review to get the updated hash, or remove expectedPlanHash to skip verification",
1266
+ "PLAN_HASH_MISMATCH"
1267
+ );
1268
+ };
1269
+ var writeAuditAndGetInfo = (planHash, policyVersion, changes, result, reviewToken, workspaceRoot) => {
1270
+ if (!result.ok || result.dryRun) return null;
1271
+ const audit = createAuditArtifact(planHash, policyVersion, changes, result, {
1272
+ reviewToken: typeof reviewToken === "string" ? reviewToken : void 0
1273
+ });
1274
+ const auditArtifactPath = writeAuditArtifact(audit, workspaceRoot);
1275
+ return { auditArtifactPath, appliedAt: audit.appliedAt };
1276
+ };
1277
+ var execute = async (args2) => {
1278
+ const {
1279
+ changes,
1280
+ workspaceRoot: workspaceRootArg,
1281
+ expectedPlanHash,
1282
+ requireReview,
1283
+ reviewToken,
1284
+ expectedPolicyVersion
1285
+ } = args2;
1286
+ if (!changes) {
1287
+ return createFailure("Missing required input: changes", "changes array is required");
1288
+ }
1289
+ if (!validateChanges(changes)) {
1290
+ return createFailure(
1291
+ "Invalid changes format: expected array of effects with kind property",
1292
+ 'Each change must have a "kind" property'
1293
+ );
1294
+ }
1295
+ const actualPlanHash = computePlanHash(changes);
1296
+ const policyVersion = typeof expectedPolicyVersion === "string" ? expectedPolicyVersion : POLICY_VERSION;
1297
+ const reviewGateError = checkReviewGate(requireReview, reviewToken, actualPlanHash, policyVersion);
1298
+ if (reviewGateError) return reviewGateError;
1299
+ const hashMismatchError = checkPlanHash(expectedPlanHash, actualPlanHash);
1300
+ if (hashMismatchError) return hashMismatchError;
1301
+ const elicitEffects = changes.filter(isElicitInputEffect2);
1302
+ if (elicitEffects.length > 0) {
1303
+ const fieldNames = elicitEffects.flatMap((e) => {
1304
+ if ("fields" in e && Array.isArray(e.fields)) {
1305
+ return e.fields.map((f) => f.name);
1306
+ }
1307
+ return [];
1308
+ }).join(", ");
1309
+ return {
1310
+ ok: false,
1311
+ operation: "apply_changes",
1312
+ summary: "Cannot apply: Plan requires user input",
1313
+ artifacts: [],
1314
+ changes: elicitEffects,
1315
+ diagnostics: [
1316
+ {
1317
+ level: "error",
1318
+ message: `Elicitation required for: ${fieldNames || "unknown fields"}`
1319
+ },
1320
+ {
1321
+ level: "info",
1322
+ message: "Use generate_service with required inputs first, then apply the resulting changes"
1323
+ }
1324
+ ]
1325
+ };
1326
+ }
1327
+ const workspaceRoot = typeof workspaceRootArg === "string" ? workspaceRootArg : findWorkspaceRoot();
1328
+ if (!workspaceRoot) {
1329
+ return {
1330
+ ok: false,
1331
+ operation: "apply_changes",
1332
+ summary: "Could not determine workspace root",
1333
+ artifacts: [],
1334
+ changes: [],
1335
+ diagnostics: [
1336
+ { level: "error", message: "Workspace root not found" },
1337
+ { level: "info", message: "Provide workspaceRoot parameter or run from within a workspace" }
1338
+ ]
1339
+ };
1340
+ }
1341
+ const result = applyEffects(changes, workspaceRoot);
1342
+ const artifacts = result.results.filter((r) => r.success && r.path).map((r) => ({
1343
+ type: "file",
1344
+ path: r.path,
1345
+ description: r.message
1346
+ }));
1347
+ const diagnostics = result.results.map((r) => ({
1348
+ level: r.success ? "info" : "error",
1349
+ message: r.message,
1350
+ location: r.path
1351
+ }));
1352
+ const auditInfo = writeAuditAndGetInfo(actualPlanHash, policyVersion, changes, result, reviewToken, workspaceRoot);
1353
+ if (auditInfo) {
1354
+ artifacts.push({
1355
+ type: "file",
1356
+ path: auditInfo.auditArtifactPath,
1357
+ description: "Audit artifact"
1358
+ });
1359
+ diagnostics.push({
1360
+ level: "info",
1361
+ message: `Audit artifact written to: ${auditInfo.auditArtifactPath}`,
1362
+ location: auditInfo.auditArtifactPath
1363
+ });
1364
+ }
1365
+ const extendedResult = {
1366
+ ...result,
1367
+ planHash: actualPlanHash,
1368
+ policyVersion,
1369
+ appliedAt: auditInfo?.appliedAt,
1370
+ auditArtifactPath: auditInfo?.auditArtifactPath,
1371
+ reviewTokenUsed: typeof reviewToken === "string" ? reviewToken : void 0
1372
+ };
1373
+ return {
1374
+ ok: result.ok,
1375
+ operation: "apply_changes",
1376
+ summary: result.summary,
1377
+ artifacts,
1378
+ changes: [],
1379
+ // Effects already applied
1380
+ diagnostics,
1381
+ data: extendedResult
1382
+ };
1383
+ };
1384
+ var applyChangesTool = {
1385
+ name: "apply_changes",
1386
+ description: "Apply changes (effects) from a generate_service plan. Writes files, adds env vars, and creates infra configs. Non-interactive, idempotent - safe to run multiple times. Rejects plans that require user input (elicitation). Supports requireReview gate and expectedPlanHash for drift detection.",
1387
+ inputSchema: {
1388
+ type: "object",
1389
+ properties: {
1390
+ changes: {
1391
+ type: "array",
1392
+ description: 'Array of effects to apply. Get this from the "changes" field of a generate_service result.',
1393
+ items: {
1394
+ type: "object",
1395
+ properties: {
1396
+ kind: {
1397
+ type: "string",
1398
+ description: 'Effect type: "file:write", "env:add", "infra:add"'
1399
+ }
1400
+ },
1401
+ required: ["kind"]
1402
+ }
1403
+ },
1404
+ workspaceRoot: {
1405
+ type: "string",
1406
+ description: "Optional workspace root path. Auto-detected if omitted."
1407
+ },
1408
+ expectedPlanHash: {
1409
+ type: "string",
1410
+ description: "Optional: SHA-256 hash from generate_service or plan_review. If provided, apply_changes will verify the plan has not been modified before execution."
1411
+ },
1412
+ requireReview: {
1413
+ type: "boolean",
1414
+ description: "Optional: If true, requires a valid reviewToken from plan_review before applying. Enforces review workflow."
1415
+ },
1416
+ reviewToken: {
1417
+ type: "string",
1418
+ description: "Optional: Review token from plan_review (format: planHash.policyVersion). Required if requireReview is true."
1419
+ },
1420
+ expectedPolicyVersion: {
1421
+ type: "string",
1422
+ description: "Optional: Expected policy version for reviewToken validation. Defaults to current POLICY_VERSION."
1423
+ }
1424
+ },
1425
+ required: ["changes"]
1426
+ },
1427
+ execute
1428
+ };
1429
+
1430
+ // src/tools/plan-review.ts
1431
+ import {
1432
+ findWorkspaceRoot as findWorkspaceRoot2,
1433
+ isElicitInputEffect as isElicitInputEffect3
1434
+ } from "@crossdelta/platform-sdk/facade";
1435
+ var validateChanges2 = (changes) => {
1436
+ if (!Array.isArray(changes)) return false;
1437
+ return changes.every((c) => typeof c === "object" && c !== null && "kind" in c && typeof c.kind === "string");
1438
+ };
1439
+ var getEffectTarget = (effect) => {
1440
+ if ("path" in effect && typeof effect.path === "string") {
1441
+ return effect.path;
1442
+ }
1443
+ if ("key" in effect && typeof effect.key === "string") {
1444
+ return `.env.local:${effect.key}`;
1445
+ }
1446
+ if ("serviceName" in effect && typeof effect.serviceName === "string") {
1447
+ return `infra/services/${effect.serviceName}.ts`;
1448
+ }
1449
+ return void 0;
1450
+ };
1451
+ var checkElicitation = (effects) => {
1452
+ const elicitEffects = effects.filter(isElicitInputEffect3);
1453
+ if (elicitEffects.length === 0) return [];
1454
+ const fieldNames = elicitEffects.flatMap((e) => {
1455
+ if ("fields" in e && Array.isArray(e.fields)) {
1456
+ return e.fields.map((f) => f.name);
1457
+ }
1458
+ return [];
1459
+ }).join(", ");
1460
+ return [
1461
+ {
1462
+ severity: "error",
1463
+ code: "REQUIRES_ELICITATION",
1464
+ message: `Plan requires user input for: ${fieldNames || "unknown fields"}`
1465
+ }
1466
+ ];
1467
+ };
1468
+ var checkUnsupportedEffects = (effects) => effects.filter((e) => !isEffectSupported(e.kind) && !isElicitInputEffect3(e)).map((e) => ({
1469
+ severity: "error",
1470
+ code: "UNSUPPORTED_EFFECT",
1471
+ message: `Unsupported effect kind: ${e.kind}`,
1472
+ target: getEffectTarget(e)
1473
+ }));
1474
+ var checkDuplicateTargets = (effects) => {
1475
+ const targets = effects.map(getEffectTarget).filter((t) => t !== void 0);
1476
+ const seen = /* @__PURE__ */ new Set();
1477
+ const duplicates = /* @__PURE__ */ new Set();
1478
+ for (const target of targets) {
1479
+ if (seen.has(target)) {
1480
+ duplicates.add(target);
1481
+ }
1482
+ seen.add(target);
1483
+ }
1484
+ return Array.from(duplicates).map((target) => ({
1485
+ severity: "warning",
1486
+ code: "DUPLICATE_TARGET",
1487
+ message: `Multiple effects target the same path: ${target}`,
1488
+ target
1489
+ }));
1490
+ };
1491
+ var extractTargets = (effects) => effects.map((e) => {
1492
+ const path = getEffectTarget(e);
1493
+ return path ? { path, kind: e.kind } : null;
1494
+ }).filter((t) => t !== null);
1495
+ var execute2 = async (args2) => {
1496
+ const { changes, workspaceRoot: workspaceRootArg } = args2;
1497
+ if (!changes) {
1498
+ return {
1499
+ ok: false,
1500
+ operation: "plan_review",
1501
+ summary: "Missing required input: changes",
1502
+ artifacts: [],
1503
+ changes: [],
1504
+ diagnostics: [{ level: "error", message: "changes array is required" }]
1505
+ };
1506
+ }
1507
+ if (!validateChanges2(changes)) {
1508
+ return {
1509
+ ok: false,
1510
+ operation: "plan_review",
1511
+ summary: "Invalid changes format: expected array of effects with kind property",
1512
+ artifacts: [],
1513
+ changes: [],
1514
+ diagnostics: [{ level: "error", message: 'Each change must have a "kind" property' }]
1515
+ };
1516
+ }
1517
+ const workspaceRoot = typeof workspaceRootArg === "string" ? workspaceRootArg : findWorkspaceRoot2();
1518
+ if (!workspaceRoot) {
1519
+ return {
1520
+ ok: false,
1521
+ operation: "plan_review",
1522
+ summary: "Could not determine workspace root",
1523
+ artifacts: [],
1524
+ changes: [],
1525
+ diagnostics: [
1526
+ { level: "error", message: "Workspace root not found" },
1527
+ { level: "info", message: "Provide workspaceRoot parameter or run from within a workspace" }
1528
+ ]
1529
+ };
1530
+ }
1531
+ const issues = [
1532
+ ...checkElicitation(changes),
1533
+ ...checkUnsupportedEffects(changes),
1534
+ ...checkDuplicateTargets(changes)
1535
+ ];
1536
+ const dryRunResult = applyEffects(changes, workspaceRoot, { dryRun: true });
1537
+ for (const result of dryRunResult.results) {
1538
+ if (!result.success && result.code) {
1539
+ issues.push({
1540
+ severity: "error",
1541
+ code: result.code,
1542
+ message: result.message,
1543
+ target: result.target
1544
+ });
1545
+ }
1546
+ }
1547
+ const hasErrors = issues.some((i) => i.severity === "error");
1548
+ const targets = extractTargets(changes);
1549
+ const planHash = computePlanHash(changes);
1550
+ const reviewToken = generateReviewToken(planHash, POLICY_VERSION);
1551
+ const reviewedAt = (/* @__PURE__ */ new Date()).toISOString();
1552
+ const reviewResult = {
1553
+ valid: !hasErrors,
1554
+ canApply: !hasErrors && issues.every((i) => i.severity !== "error"),
1555
+ issues,
1556
+ preview: {
1557
+ wouldCreate: dryRunResult.counts.wouldCreate,
1558
+ wouldUpdate: dryRunResult.counts.wouldUpdate,
1559
+ wouldSkip: dryRunResult.counts.wouldSkip,
1560
+ errors: dryRunResult.counts.error
1561
+ },
1562
+ targets,
1563
+ planHash,
1564
+ policyVersion: POLICY_VERSION,
1565
+ reviewToken,
1566
+ reviewedAt
1567
+ };
1568
+ cacheReview({
1569
+ planHash,
1570
+ policyVersion: POLICY_VERSION,
1571
+ reviewToken,
1572
+ reviewedAt,
1573
+ valid: reviewResult.valid,
1574
+ canApply: reviewResult.canApply,
1575
+ preview: reviewResult.preview,
1576
+ issues: reviewResult.issues
1577
+ });
1578
+ const diagnostics = issues.map((issue) => ({
1579
+ level: issue.severity === "error" ? "error" : issue.severity === "warning" ? "warning" : "info",
1580
+ message: `[${issue.code}] ${issue.message}`,
1581
+ location: issue.target
1582
+ }));
1583
+ if (reviewResult.canApply) {
1584
+ diagnostics.push({
1585
+ level: "info",
1586
+ message: dryRunResult.summary,
1587
+ location: void 0
1588
+ });
1589
+ }
1590
+ return {
1591
+ ok: reviewResult.valid,
1592
+ operation: "plan_review",
1593
+ summary: reviewResult.valid ? `Plan is valid: ${dryRunResult.summary}` : `Plan has ${issues.filter((i) => i.severity === "error").length} errors`,
1594
+ artifacts: [],
1595
+ changes: [],
1596
+ // No changes - this is review only
1597
+ diagnostics,
1598
+ data: reviewResult
1599
+ };
1600
+ };
1601
+ var planReviewTool = {
1602
+ name: "plan_review",
1603
+ description: "Review and validate a plan (effects array) without executing it. Checks for path safety issues, duplicate targets, unsupported effects, and elicitation requirements. Shows preview of what would be created/updated/skipped.",
1604
+ inputSchema: {
1605
+ type: "object",
1606
+ properties: {
1607
+ changes: {
1608
+ type: "array",
1609
+ description: 'Array of effects to review. Get this from the "changes" field of a generate_service result.',
1610
+ items: {
1611
+ type: "object",
1612
+ properties: {
1613
+ kind: {
1614
+ type: "string",
1615
+ description: 'Effect type: "file:write", "env:add", "infra:add"'
1616
+ }
1617
+ },
1618
+ required: ["kind"]
1619
+ }
1620
+ },
1621
+ workspaceRoot: {
1622
+ type: "string",
1623
+ description: "Optional workspace root path. Auto-detected if omitted."
1624
+ }
1625
+ },
1626
+ required: ["changes"]
1627
+ },
1628
+ execute: execute2
1629
+ };
1630
+
1631
+ // src/tools/services-generate/index.ts
1632
+ import { findWorkspaceRoot as findWorkspaceRoot3 } from "@crossdelta/platform-sdk/facade";
1633
+
1634
+ // src/tools/services-generate/mode-explicit.ts
1635
+ import { planServiceGeneration } from "@crossdelta/platform-sdk/facade";
1636
+
1637
+ // src/tools/services-generate/review.ts
1638
+ var runInlineReview = (changes, workspaceRoot) => {
1639
+ const issues = [];
1640
+ for (const effect of changes) {
1641
+ if (!isEffectSupported(effect.kind)) {
1642
+ issues.push({
1643
+ severity: "error",
1644
+ code: "UNSUPPORTED_EFFECT",
1645
+ message: `Unsupported effect kind: ${effect.kind}`
1646
+ });
1647
+ }
1648
+ }
1649
+ const dryRunResult = applyEffects(changes, workspaceRoot, { dryRun: true });
1650
+ for (const result of dryRunResult.results) {
1651
+ if (!result.success && result.code) {
1652
+ issues.push({
1653
+ severity: "error",
1654
+ code: result.code,
1655
+ message: result.message
1656
+ });
1657
+ }
1658
+ }
1659
+ const hasErrors = issues.some((i) => i.severity === "error");
1660
+ return {
1661
+ valid: !hasErrors,
1662
+ canApply: !hasErrors,
1663
+ issues,
1664
+ preview: {
1665
+ wouldCreate: dryRunResult.counts.wouldCreate,
1666
+ wouldUpdate: dryRunResult.counts.wouldUpdate,
1667
+ wouldSkip: dryRunResult.counts.wouldSkip,
1668
+ errors: dryRunResult.counts.error
1669
+ }
1670
+ };
1671
+ };
1672
+ var applyPlanEffects = (result, workspaceRoot) => {
1673
+ const applyResult = applyEffects(result.changes, workspaceRoot, { dryRun: false });
1674
+ return {
1675
+ ...result,
1676
+ summary: `${result.summary} - ${applyResult.summary}`,
1677
+ diagnostics: [
1678
+ ...result.diagnostics ?? [],
1679
+ ...applyResult.results.filter((r) => r.message).map((r) => ({
1680
+ level: r.success ? "info" : "error",
1681
+ message: r.message,
1682
+ location: r.path
1683
+ }))
1684
+ ]
1685
+ };
1686
+ };
1687
+
1688
+ // src/tools/services-generate/validation.ts
1689
+ var VALID_TEMPLATES = ["hono-bun", "nest"];
1690
+ var getMissingFields = (args2) => {
1691
+ const { template, name } = args2;
1692
+ const missing = [];
1693
+ if (!template || typeof template !== "string") {
1694
+ missing.push({
1695
+ name: "template",
1696
+ prompt: "Which service template would you like to use?",
1697
+ required: true,
1698
+ description: "Service template type for scaffolding",
1699
+ type: "string",
1700
+ enum: [...VALID_TEMPLATES]
1701
+ });
1702
+ }
1703
+ if (!name || typeof name !== "string") {
1704
+ missing.push({
1705
+ name: "name",
1706
+ prompt: "What should the service be named?",
1707
+ required: true,
1708
+ description: 'Service name in kebab-case (e.g., "order-processing")',
1709
+ type: "string"
1710
+ });
1711
+ }
1712
+ return missing;
1713
+ };
1714
+ var isValidTemplate = (template) => VALID_TEMPLATES.includes(template);
1715
+ var createElicitResult = (message, fields) => {
1716
+ const elicitEffect = {
1717
+ kind: "elicit:input",
1718
+ message,
1719
+ fields
1720
+ };
1721
+ return {
1722
+ ok: false,
1723
+ operation: "generate_service",
1724
+ summary: message,
1725
+ artifacts: [],
1726
+ changes: [elicitEffect],
1727
+ diagnostics: [{ level: "info", message: "Awaiting user input" }]
1728
+ };
1729
+ };
1730
+ var toMcpElicitFields = (fields) => fields.map((f) => ({
1731
+ name: f.path,
1732
+ prompt: f.prompt,
1733
+ required: f.required,
1734
+ description: f.prompt,
1735
+ type: f.type === "enum" ? "string" : "string",
1736
+ enum: f.options
1737
+ }));
1738
+
1739
+ // src/tools/services-generate/mode-explicit.ts
1740
+ var executeExplicitMode = (args2, workspaceRoot, _options) => {
1741
+ const { template, name, port, events, dryRun, autoReview } = args2;
1742
+ const missingFields = getMissingFields(args2);
1743
+ if (missingFields.length > 0) {
1744
+ return createElicitResult("Missing required inputs for service generation", missingFields);
1745
+ }
1746
+ if (!isValidTemplate(template)) {
1747
+ return createElicitResult(`Invalid template: ${template}. Please select a valid template.`, [
1748
+ {
1749
+ name: "template",
1750
+ prompt: "Which service template would you like to use?",
1751
+ required: true,
1752
+ description: "Service template type for scaffolding",
1753
+ type: "string",
1754
+ enum: ["hono-bun", "nest"]
1755
+ }
1756
+ ]);
1757
+ }
1758
+ const eventsList = Array.isArray(events) ? events.filter((e) => typeof e === "string") : [];
1759
+ const result = planServiceGeneration({
1760
+ type: template,
1761
+ name,
1762
+ workspaceRoot,
1763
+ port: typeof port === "number" ? port : void 0,
1764
+ events: eventsList
1765
+ });
1766
+ if (result.ok && result.changes.length > 0) {
1767
+ const planHash = computePlanHash(result.changes);
1768
+ const extendedResult = {
1769
+ ...result,
1770
+ planHash
1771
+ };
1772
+ cachePlan({
1773
+ planHash,
1774
+ effects: result.changes,
1775
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1776
+ source: "generate_service",
1777
+ metadata: {
1778
+ template,
1779
+ name,
1780
+ port: typeof port === "number" ? port : void 0
1781
+ }
1782
+ });
1783
+ if (autoReview === true) {
1784
+ extendedResult.review = runInlineReview(result.changes, workspaceRoot);
1785
+ }
1786
+ if (dryRun === false) {
1787
+ return applyPlanEffects(extendedResult, workspaceRoot);
1788
+ }
1789
+ return extendedResult;
1790
+ }
1791
+ return result;
1792
+ };
1793
+
1794
+ // src/tools/services-generate/mode-nl.ts
1795
+ import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
1796
+ import { join as join6 } from "path";
1797
+ import {
1798
+ loadCapabilitiesConfig,
1799
+ lowerSpecToCapabilities,
1800
+ parseServicePrompt,
1801
+ planServiceGeneration as planServiceGeneration2
1802
+ } from "@crossdelta/platform-sdk/facade";
1803
+
1804
+ // src/tools/services-generate/npm-verifier.ts
1805
+ var parsePackageJson = (content) => {
1806
+ try {
1807
+ return JSON.parse(content);
1808
+ } catch {
1809
+ return null;
1810
+ }
1811
+ };
1812
+ var extractNpmPackages = (pkg) => {
1813
+ const allDeps = {
1814
+ ...pkg.dependencies ?? {},
1815
+ ...pkg.devDependencies ?? {}
1816
+ };
1817
+ return Object.entries(allDeps).filter(([_, version]) => !version.startsWith("workspace:")).map(([name, version]) => ({ name, version }));
1818
+ };
1819
+ var normalizeVersion = (version) => version.replace(/^\^/, "");
1820
+ var createCorrection = (name, generatedVersion, actualVersion) => ({
1821
+ name,
1822
+ generatedVersion,
1823
+ actualVersion: actualVersion ?? generatedVersion,
1824
+ corrected: actualVersion !== null && normalizeVersion(generatedVersion) !== actualVersion
1825
+ });
1826
+ var applyCorrections = (pkg, corrections) => {
1827
+ const corrected = { ...pkg };
1828
+ for (const correction of corrections.filter((c) => c.corrected)) {
1829
+ if (corrected.dependencies?.[correction.name]) {
1830
+ corrected.dependencies = {
1831
+ ...corrected.dependencies,
1832
+ [correction.name]: `^${correction.actualVersion}`
1833
+ };
1834
+ }
1835
+ if (corrected.devDependencies?.[correction.name]) {
1836
+ corrected.devDependencies = {
1837
+ ...corrected.devDependencies,
1838
+ [correction.name]: `^${correction.actualVersion}`
1839
+ };
1840
+ }
1841
+ }
1842
+ return corrected;
1843
+ };
1844
+ var fetchNpmVersion = async (packageName) => {
1845
+ try {
1846
+ const response = await fetch(`https://registry.npmjs.org/${packageName}/latest`);
1847
+ if (!response.ok) return null;
1848
+ const data = await response.json();
1849
+ return data.version ?? null;
1850
+ } catch {
1851
+ return null;
1852
+ }
1853
+ };
1854
+ var isFileWriteEffect = (effect) => effect.kind === "file:write";
1855
+ var verifyAndCorrectPackageJson = async (content) => {
1856
+ const pkg = parsePackageJson(content);
1857
+ if (!pkg) {
1858
+ return {
1859
+ correctedContent: content,
1860
+ result: { corrections: [], hasCorrections: false }
1861
+ };
1862
+ }
1863
+ const packages = extractNpmPackages(pkg);
1864
+ if (packages.length === 0) {
1865
+ return {
1866
+ correctedContent: content,
1867
+ result: { corrections: [], hasCorrections: false }
1868
+ };
1869
+ }
1870
+ const corrections = await Promise.all(
1871
+ packages.map(async ({ name, version }) => {
1872
+ const actualVersion = await fetchNpmVersion(name);
1873
+ return createCorrection(name, version, actualVersion);
1874
+ })
1875
+ );
1876
+ const hasCorrections = corrections.some((c) => c.corrected);
1877
+ const correctedPkg = hasCorrections ? applyCorrections(pkg, corrections) : pkg;
1878
+ return {
1879
+ correctedContent: JSON.stringify(correctedPkg, null, 2),
1880
+ result: {
1881
+ corrections: corrections.filter((c) => c.corrected),
1882
+ hasCorrections
1883
+ }
1884
+ };
1885
+ };
1886
+ var verifyAndCorrectNpmVersions = async (changes) => {
1887
+ const pkgIndex = changes.findIndex((c) => isFileWriteEffect(c) && c.path.endsWith("package.json"));
1888
+ if (pkgIndex === -1) {
1889
+ return {
1890
+ correctedChanges: changes,
1891
+ result: { corrections: [], hasCorrections: false }
1892
+ };
1893
+ }
1894
+ const pkgChange = changes[pkgIndex];
1895
+ const { correctedContent, result } = await verifyAndCorrectPackageJson(pkgChange.content);
1896
+ const correctedChanges = [...changes];
1897
+ const correctedFileEffect = {
1898
+ kind: "file:write",
1899
+ path: pkgChange.path,
1900
+ content: correctedContent
1901
+ };
1902
+ correctedChanges[pkgIndex] = correctedFileEffect;
1903
+ return { correctedChanges, result };
1904
+ };
1905
+
1906
+ // src/tools/services-generate/mode-nl.ts
1907
+ var buildDiagnostics = (spec, serviceMeta, files, scaffoldCount, aiFilesCount, dependencies, envVars, templateType) => [
1908
+ { level: "info", message: `Parsed from prompt: ${spec.serviceName}` },
1909
+ { level: "info", message: `Template: ${templateType} (port ${serviceMeta.port})` },
1910
+ { level: "info", message: `Scaffold files: ${scaffoldCount} (ready to apply)` },
1911
+ { level: "info", message: `Files for AI: ${aiFilesCount} (${files.map((f) => f.intent).join(", ")})` },
1912
+ ...dependencies.length > 0 ? [{ level: "info", message: `Dependencies: ${dependencies.map((d) => d.name).join(", ")}` }] : [],
1913
+ ...envVars.length > 0 ? [{ level: "info", message: `Env vars: ${envVars.map((e) => e.key).join(", ")}` }] : []
1914
+ ];
1915
+ var normalizeFiles = (files) => files.map((f) => ({
1916
+ path: f.path,
1917
+ intent: f.intent,
1918
+ inputs: f.inputs ?? {},
1919
+ layer: f.layer,
1920
+ kind: f.kind
1921
+ }));
1922
+ var extractEventsFromSpec = (spec) => spec.triggers.filter((t) => t.type === "event" && !!t.eventType).map((t) => t.eventType);
1923
+ var extractDependencies = (deps) => Object.entries(deps).map(([name, version]) => ({
1924
+ name,
1925
+ version,
1926
+ dev: false
1927
+ }));
1928
+ var buildServiceResult = (spec, serviceMeta, serviceDir, correctedChanges, metadata, dependencies, verificationCorrections, templateType, scaffoldArtifacts) => ({
1929
+ ok: true,
1930
+ operation: "generate_service",
1931
+ summary: `Planned ${spec.serviceName}: ${correctedChanges.length} scaffold files + ${metadata.files.length} files for AI to generate`,
1932
+ data: serviceMeta,
1933
+ artifacts: [
1934
+ ...scaffoldArtifacts,
1935
+ { type: "service", path: serviceDir, description: `Generated service ${spec.serviceName}` }
1936
+ ],
1937
+ changes: correctedChanges,
1938
+ files: normalizeFiles(metadata.files),
1939
+ dependencies,
1940
+ envVars: metadata.envVars.map((ev) => ({
1941
+ key: ev.key,
1942
+ description: ev.description ?? "",
1943
+ required: ev.required ?? false
1944
+ })),
1945
+ postCommands: metadata.postCommands,
1946
+ diagnostics: [
1947
+ ...buildDiagnostics(
1948
+ spec,
1949
+ serviceMeta,
1950
+ metadata.files,
1951
+ correctedChanges.length,
1952
+ metadata.files.length,
1953
+ dependencies,
1954
+ metadata.envVars,
1955
+ templateType
1956
+ ),
1957
+ ...verificationCorrections.map((c) => ({
1958
+ level: "info",
1959
+ message: `\u{1F4E6} Corrected ${c.name}: ${c.generatedVersion} \u2192 ^${c.actualVersion}`
1960
+ }))
1961
+ ]
1962
+ });
1963
+ var executeNLMode = async (prompt, workspaceRoot, options) => {
1964
+ const pkgPath = join6(workspaceRoot, "package.json");
1965
+ const pkg = existsSync3(pkgPath) ? JSON.parse(readFileSync3(pkgPath, "utf-8")) : null;
1966
+ const workspaceConfig = loadCapabilitiesConfig(pkg);
1967
+ const parseResult = await parseServicePrompt(prompt, { workspaceConfig });
1968
+ if (!parseResult.complete) {
1969
+ const fields = toMcpElicitFields(parseResult.fields);
1970
+ return createElicitResult("Additional information needed", fields);
1971
+ }
1972
+ const spec = parseResult.spec;
1973
+ const events = extractEventsFromSpec(spec);
1974
+ const serviceDir = `services/${spec.serviceName}`;
1975
+ const metadata = lowerSpecToCapabilities(spec, serviceDir);
1976
+ const templateType = options.template || "hono-bun";
1977
+ const scaffoldResult = planServiceGeneration2({
1978
+ type: templateType,
1979
+ name: spec.serviceName,
1980
+ workspaceRoot,
1981
+ events,
1982
+ envVars: metadata.envVars
1983
+ });
1984
+ if (!scaffoldResult.ok) {
1985
+ return {
1986
+ ok: false,
1987
+ operation: "generate_service",
1988
+ summary: scaffoldResult.summary,
1989
+ artifacts: [],
1990
+ changes: [],
1991
+ diagnostics: [{ level: "error", message: scaffoldResult.summary }]
1992
+ };
1993
+ }
1994
+ if (!scaffoldResult.data) {
1995
+ return {
1996
+ ok: false,
1997
+ operation: "generate_service",
1998
+ summary: "Service scaffold generation failed: no data returned",
1999
+ artifacts: [],
2000
+ changes: [],
2001
+ diagnostics: [{ level: "error", message: "No service metadata returned from scaffold" }]
2002
+ };
2003
+ }
2004
+ const dependencies = extractDependencies(metadata.dependencies);
2005
+ const { correctedChanges, result: verificationResult } = await verifyAndCorrectNpmVersions(scaffoldResult.changes);
2006
+ const serviceMeta = scaffoldResult.data;
2007
+ const result = buildServiceResult(
2008
+ spec,
2009
+ serviceMeta,
2010
+ serviceDir,
2011
+ correctedChanges,
2012
+ metadata,
2013
+ dependencies,
2014
+ verificationResult.corrections,
2015
+ templateType,
2016
+ scaffoldResult.artifacts ?? []
2017
+ );
2018
+ if (result.changes.length > 0) {
2019
+ result.planHash = computePlanHash(result.changes);
2020
+ cachePlan({
2021
+ planHash: result.planHash,
2022
+ effects: result.changes,
2023
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
2024
+ source: "generate_service",
2025
+ metadata: { prompt, serviceName: spec.serviceName, filesToGenerate: metadata.files.length }
2026
+ });
2027
+ }
2028
+ if (options.autoReview === true) {
2029
+ result.review = runInlineReview(result.changes, workspaceRoot);
2030
+ }
2031
+ if (options.dryRun === false) {
2032
+ return applyPlanEffects(result, workspaceRoot);
2033
+ }
2034
+ return result;
2035
+ };
2036
+
2037
+ // src/tools/services-generate/index.ts
2038
+ var execute3 = async (args2) => {
2039
+ const { prompt, template, workspaceUri, dryRun, autoReview } = args2;
2040
+ const workspaceRoot = typeof workspaceUri === "string" ? workspaceUri : findWorkspaceRoot3();
2041
+ if (typeof prompt === "string" && prompt.trim().length > 0) {
2042
+ return executeNLMode(prompt, workspaceRoot, {
2043
+ dryRun,
2044
+ autoReview,
2045
+ template
2046
+ });
2047
+ }
2048
+ return executeExplicitMode(args2, workspaceRoot, { dryRun, autoReview });
2049
+ };
2050
+ var servicesGenerateTool = {
2051
+ name: "generate_service",
2052
+ description: 'Plan generation of a new microservice from a natural language prompt. Example: "Erstelle notification-service, h\xF6rt auf order.created, sendet Push via Pusher Beams". Returns a structured plan with files, config, and environment variables. By default runs in plan-only mode (dryRun=true). After showing the plan, ask if user wants to apply it.\n\n\u{1F4DA} IMPORTANT: Before generating services, read the generator documentation:\n1. First, read docs://generators to discover available documentation\n2. Then read the relevant docs (e.g., docs://generators/service for main guidelines)\n\nThese resources contain up-to-date package versions, naming conventions, and best practices. Always verify package versions on npm - do not hallucinate versions!',
2053
+ inputSchema: {
2054
+ type: "object",
2055
+ properties: {
2056
+ prompt: {
2057
+ type: "string",
2058
+ description: 'Natural language prompt describing the service. Example: "Erstelle notification-service, h\xF6rt auf order.created, sendet Push via Pusher Beams". When provided, uses capabilities pipeline for intelligent generation.'
2059
+ },
2060
+ template: {
2061
+ type: "string",
2062
+ enum: ["hono-bun", "nest"],
2063
+ description: "Service template type. Required when not using prompt mode."
2064
+ },
2065
+ name: {
2066
+ type: "string",
2067
+ description: "Service name in kebab-case. Required when not using prompt mode."
2068
+ },
2069
+ workspaceUri: {
2070
+ type: "string",
2071
+ description: "Optional workspace root path. Auto-detected if omitted."
2072
+ },
2073
+ port: {
2074
+ type: "number",
2075
+ description: "Optional explicit port number. Auto-allocated if omitted."
2076
+ },
2077
+ events: {
2078
+ type: "array",
2079
+ items: { type: "string" },
2080
+ description: 'Event types to consume (e.g., ["order.created"]). Only for explicit mode.'
2081
+ },
2082
+ dryRun: {
2083
+ type: "boolean",
2084
+ description: "Plan-only mode (default: true). When true, returns plan without writing files.",
2085
+ default: true
2086
+ },
2087
+ autoReview: {
2088
+ type: "boolean",
2089
+ description: "Include inline plan validation (default: false).",
2090
+ default: false
2091
+ }
2092
+ },
2093
+ required: []
2094
+ },
2095
+ execute: execute3
2096
+ };
2097
+
2098
+ // src/tools/workspace-scan.ts
2099
+ import {
2100
+ createContextFromWorkspace as createContextFromWorkspace4
2101
+ } from "@crossdelta/platform-sdk/facade";
2102
+ var normalizeInput = (args2) => ({
2103
+ workspaceRoot: typeof args2.workspaceRoot === "string" ? args2.workspaceRoot : void 0
2104
+ });
2105
+ var buildScanMeta = (context, loadedPlugins) => ({
2106
+ workspaceRoot: context.workspace.workspaceRoot,
2107
+ contracts: context.workspace.contracts,
2108
+ availableServices: context.workspace.availableServices,
2109
+ loadedPlugins
2110
+ });
2111
+ var createScanResult = (meta) => ({
2112
+ ok: true,
2113
+ operation: "scan_workspace",
2114
+ summary: `Workspace scanned: ${meta.availableServices.length} services, contracts at ${meta.contracts.packageName}`,
2115
+ artifacts: [],
2116
+ changes: [],
2117
+ // Read-only: no changes
2118
+ diagnostics: [],
2119
+ data: meta
2120
+ });
2121
+ var createScanError = (error) => ({
2122
+ ok: false,
2123
+ operation: "scan_workspace",
2124
+ summary: `Failed to scan workspace: ${error.message}`,
2125
+ artifacts: [],
2126
+ changes: [],
2127
+ // Read-only: no changes
2128
+ diagnostics: [
2129
+ {
2130
+ level: "error",
2131
+ message: error.message
2132
+ }
2133
+ ]
2134
+ });
2135
+ var execute4 = async (args2, pluginModules = []) => {
2136
+ try {
2137
+ const input = normalizeInput(args2);
2138
+ const context = await createContextFromWorkspace4(input.workspaceRoot);
2139
+ const meta = buildScanMeta(context, pluginModules);
2140
+ return createScanResult(meta);
2141
+ } catch (error) {
2142
+ return createScanError(error instanceof Error ? error : new Error(String(error)));
2143
+ }
2144
+ };
2145
+ var workspaceScanInputSchema = {
2146
+ type: "object",
2147
+ properties: {
2148
+ workspaceRoot: {
2149
+ type: "string",
2150
+ description: "Optional: Absolute path to workspace root. If omitted, auto-discovers from current directory."
2151
+ }
2152
+ },
2153
+ required: []
2154
+ };
2155
+ var createWorkspaceScanTool = (pluginModules) => ({
2156
+ name: "scan_workspace",
2157
+ description: "Scan the current workspace and return a read-only snapshot including workspace root, contracts configuration, discovered services, and loaded plugins. Does not write any files.",
2158
+ inputSchema: workspaceScanInputSchema,
2159
+ execute: async (args2) => execute4(args2, pluginModules)
2160
+ });
2161
+
2162
+ // src/server.ts
2163
+ var isOperationResult = (result) => {
2164
+ return typeof result === "object" && result !== null && "ok" in result && "operation" in result && "summary" in result;
2165
+ };
2166
+ var readTemplateFileResource = async (uri) => {
2167
+ const templateFileMatch = uri.match(/^templates:\/\/([^/]+)\/file\/(.+)$/);
2168
+ if (!templateFileMatch) return null;
2169
+ const [, templateName, filePath] = templateFileMatch;
2170
+ if (!templateName || !filePath) {
2171
+ throw new Error(`Invalid template file URI: ${uri}`);
2172
+ }
2173
+ try {
2174
+ const content = await readTemplateFile(templateName, filePath);
2175
+ return {
2176
+ contents: [
2177
+ {
2178
+ uri,
2179
+ mimeType: "text/plain",
2180
+ text: content
2181
+ }
2182
+ ]
2183
+ };
2184
+ } catch (error) {
2185
+ throw new Error(`Failed to read template file: ${error instanceof Error ? error.message : String(error)}`);
2186
+ }
2187
+ };
2188
+ var readTemplateStructureResource = async (uri) => {
2189
+ const templateStructureMatch = uri.match(/^templates:\/\/([^/]+)$/);
2190
+ if (!templateStructureMatch) return null;
2191
+ const [, templateName] = templateStructureMatch;
2192
+ if (!templateName || templateName === "list") {
2193
+ return null;
2194
+ }
2195
+ try {
2196
+ const content = await readTemplateStructure(templateName);
2197
+ return {
2198
+ contents: [
2199
+ {
2200
+ uri,
2201
+ mimeType: "application/json",
2202
+ text: content
2203
+ }
2204
+ ]
2205
+ };
2206
+ } catch (error) {
2207
+ throw new Error(`Failed to read template structure: ${error instanceof Error ? error.message : String(error)}`);
2208
+ }
2209
+ };
2210
+ var completeTemplateName = async () => {
2211
+ const templates = await listTemplates();
2212
+ return {
2213
+ completion: {
2214
+ values: templates,
2215
+ total: templates.length,
2216
+ hasMore: false
2217
+ }
2218
+ };
2219
+ };
2220
+ var completeTemplatePath = async (uri) => {
2221
+ const templateMatch = uri.match(/templates:\/\/([^/]+)/);
2222
+ if (!templateMatch?.[1]) {
2223
+ return {
2224
+ completion: {
2225
+ values: [],
2226
+ total: 0,
2227
+ hasMore: false
2228
+ }
2229
+ };
2230
+ }
2231
+ const templateName = templateMatch[1];
2232
+ try {
2233
+ const files = await listTemplateFiles(templateName);
2234
+ return {
2235
+ completion: {
2236
+ values: files,
2237
+ total: files.length,
2238
+ hasMore: false
2239
+ }
2240
+ };
2241
+ } catch {
2242
+ return {
2243
+ completion: {
2244
+ values: [],
2245
+ total: 0,
2246
+ hasMore: false
2247
+ }
2248
+ };
2249
+ }
2250
+ };
2251
+ var normalizeToOperationResult = (result, toolName) => {
2252
+ if (isOperationResult(result)) {
2253
+ return result;
2254
+ }
2255
+ const legacyResult = result;
2256
+ return {
2257
+ ok: legacyResult.ok ?? true,
2258
+ operation: legacyResult.operation ?? toolName,
2259
+ summary: legacyResult.summary ?? (legacyResult.output ? Array.isArray(legacyResult.output) ? legacyResult.output.join("\n") : legacyResult.output : `Executed ${toolName}`),
2260
+ artifacts: [],
2261
+ changes: legacyResult.effects ?? [],
2262
+ diagnostics: []
2263
+ };
2264
+ };
2265
+ var buildTools = (pluginTools, pluginModules) => {
2266
+ const workspaceScanTool = createWorkspaceScanTool(pluginModules);
2267
+ return [servicesGenerateTool, planReviewTool, applyChangesTool, workspaceScanTool, ...pluginTools];
2268
+ };
2269
+ var buildResources = async (pluginModules, workspaceRoot) => {
2270
+ const generatorDocsResources = await createAllGeneratorDocResources();
2271
+ const templatesListResource = createTemplatesListResource();
2272
+ return [
2273
+ createWorkspaceSummaryResource(pluginModules, workspaceRoot),
2274
+ createServicesResource(workspaceRoot),
2275
+ createContractsResource(workspaceRoot),
2276
+ ...generatorDocsResources,
2277
+ templatesListResource
2278
+ // Only discovery resource, not individual templates
2279
+ ];
2280
+ };
2281
+ var registerHandlers = (server, tools, resources) => {
2282
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
2283
+ tools: tools.map((tool) => ({
2284
+ name: tool.name,
2285
+ description: tool.description,
2286
+ inputSchema: tool.inputSchema
2287
+ }))
2288
+ }));
2289
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
2290
+ const { name: toolName, arguments: args2 } = request.params;
2291
+ const tool = tools.find((t) => t.name === toolName);
2292
+ if (!tool) {
2293
+ return formatErrorAsMcpResponse(new Error(`Unknown tool: ${toolName}`), toolName);
2294
+ }
2295
+ try {
2296
+ const result = await tool.execute(args2 ?? {});
2297
+ const normalizedResult = normalizeToOperationResult(result, toolName);
2298
+ const elicitEffect = normalizedResult.changes.find(isElicitInputEffect4);
2299
+ if (elicitEffect) {
2300
+ return formatElicitationAsMcpResponse(elicitEffect, toolName);
2301
+ }
2302
+ return formatOperationResultAsMcpResponse(normalizedResult);
2303
+ } catch (error) {
2304
+ return formatErrorAsMcpResponse(error instanceof Error ? error : new Error(String(error)), toolName);
2305
+ }
2306
+ });
2307
+ server.setRequestHandler(ListResourcesRequestSchema, async () => ({
2308
+ resources: resources.map((r) => r.resource)
2309
+ }));
2310
+ server.setRequestHandler(ListResourceTemplatesRequestSchema, async () => ({
2311
+ resourceTemplates: [createTemplateFileResourceTemplate()]
2312
+ }));
2313
+ server.setRequestHandler(CompleteRequestSchema, async (request) => {
2314
+ const { ref, argument } = request.params;
2315
+ if (ref.type === "ref/resource" && ref.uri?.startsWith("templates://")) {
2316
+ if (argument.name === "template") {
2317
+ return await completeTemplateName();
2318
+ }
2319
+ if (argument.name === "path" && ref.uri) {
2320
+ return await completeTemplatePath(ref.uri);
2321
+ }
2322
+ }
2323
+ return {
2324
+ completion: {
2325
+ values: [],
2326
+ total: 0,
2327
+ hasMore: false
2328
+ }
2329
+ };
2330
+ });
2331
+ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
2332
+ const { uri } = request.params;
2333
+ const templateFileResult = await readTemplateFileResource(uri);
2334
+ if (templateFileResult) return templateFileResult;
2335
+ const templateStructureResult = await readTemplateStructureResource(uri);
2336
+ if (templateStructureResult) return templateStructureResult;
2337
+ const resource = resources.find((r) => r.resource.uri === uri);
2338
+ if (!resource) {
2339
+ throw new Error(`Unknown resource: ${uri}`);
2340
+ }
2341
+ const content = await resource.read(uri);
2342
+ return { contents: [content] };
2343
+ });
2344
+ };
2345
+ var createMcpServerFromWorkspace = async (options) => {
2346
+ const { name, version, workspaceRoot, plugins: pluginModules, logger } = options;
2347
+ const context = await createContextFromWorkspace5(workspaceRoot, logger);
2348
+ const loadedPlugins = await loadPlugins(pluginModules, context);
2349
+ const pluginTools = collectExecutableToolSpecs(loadedPlugins, context);
2350
+ const discoveredRoot = context.workspace.workspaceRoot;
2351
+ const tools = buildTools(pluginTools, pluginModules);
2352
+ const resources = await buildResources(pluginModules, discoveredRoot);
2353
+ const server = new Server({ name, version }, { capabilities: { tools: {}, resources: {}, completions: {} } });
2354
+ registerHandlers(server, tools, resources);
2355
+ const transport = new StdioServerTransport();
2356
+ await server.connect(transport);
2357
+ logger?.info(
2358
+ `MCP server '${name}' started with ${tools.length} tools, ${resources.length} resources (auto-discovered workspace)`
2359
+ );
2360
+ };
2361
+
2362
+ // src/cli.ts
2363
+ var args = process.argv.slice(2);
2364
+ if (!args.includes("--stdio")) {
2365
+ console.error("Usage: pf-mcp --stdio");
2366
+ console.error("");
2367
+ console.error("The --stdio flag is required for MCP server communication.");
2368
+ console.error("This server is designed to be used with VS Code Copilot Agent Mode.");
2369
+ process.exit(1);
2370
+ }
2371
+ createMcpServerFromWorkspace({
2372
+ name: "pf-mcp",
2373
+ version: "0.1.0",
2374
+ plugins: []
2375
+ }).catch((error) => {
2376
+ console.error("Failed to start MCP server:", error);
2377
+ process.exit(1);
2378
+ });
2379
+ //# sourceMappingURL=cli.js.map