@agiflowai/aicode-toolkit 1.0.3 → 1.0.5

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.
@@ -1,10 +1,9 @@
1
- import path from "node:path";
2
- import { ProjectType, messages, print } from "@agiflowai/aicode-utils";
3
- import * as fs$1 from "fs-extra";
1
+ import { CLAUDE_CODE, CODEX, CURSOR, ClaudeCodeService, CodexService, CursorService, GEMINI_CLI, GITHUB_COPILOT, GeminiCliService, GitHubCopilotService, NONE } from "@agiflowai/coding-agent-bridge";
2
+ import { ProjectType, copy, ensureDir, messages, mkdir, move, pathExists, print, readFile, readdir, remove, writeFile } from "@agiflowai/aicode-utils";
4
3
  import chalk from "chalk";
5
4
  import gradient from "gradient-string";
5
+ import path from "node:path";
6
6
  import { execa } from "execa";
7
- import { CLAUDE_CODE, CODEX, CURSOR, ClaudeCodeService, CodexService, CursorService, GEMINI_CLI, GITHUB_COPILOT, GeminiCliService, GitHubCopilotService, NONE } from "@agiflowai/coding-agent-bridge";
8
7
  import fs from "node:fs/promises";
9
8
  import { Liquid } from "liquidjs";
10
9
  import os from "node:os";
@@ -70,6 +69,187 @@ const BANNER_GRADIENT = [
70
69
  THEME.colors.secondary.dark
71
70
  ];
72
71
 
72
+ //#endregion
73
+ //#region src/services/CodingAgentService.ts
74
+ /**
75
+ * CodingAgentService
76
+ *
77
+ * DESIGN PATTERNS:
78
+ * - Service pattern for business logic encapsulation
79
+ * - Strategy pattern for different agent configurations
80
+ * - Single responsibility: Handle MCP setup for coding agents
81
+ *
82
+ * CODING STANDARDS:
83
+ * - Use async/await for asynchronous operations
84
+ * - Throw descriptive errors for error cases
85
+ * - Document methods with JSDoc comments
86
+ *
87
+ * AVOID:
88
+ * - Direct UI interaction (no prompts in services)
89
+ * - Hard-coding agent configurations (use strategies)
90
+ */
91
+ var CodingAgentService = class {
92
+ workspaceRoot;
93
+ constructor(workspaceRoot) {
94
+ this.workspaceRoot = workspaceRoot;
95
+ }
96
+ /**
97
+ * Detect which coding agent is enabled in the workspace
98
+ * Checks for Claude Code, Codex, Gemini CLI, GitHub Copilot, and Cursor installations
99
+ * @param workspaceRoot - The workspace root directory
100
+ * @returns Promise resolving to detected agent ID or null
101
+ */
102
+ static async detectCodingAgent(workspaceRoot) {
103
+ if (await new ClaudeCodeService({ workspaceRoot }).isEnabled()) return CLAUDE_CODE;
104
+ if (await new CursorService({ workspaceRoot }).isEnabled()) return CURSOR;
105
+ if (await new GitHubCopilotService({ workspaceRoot }).isEnabled()) return GITHUB_COPILOT;
106
+ if (await new CodexService({ workspaceRoot }).isEnabled()) return CODEX;
107
+ if (await new GeminiCliService({ workspaceRoot }).isEnabled()) return GEMINI_CLI;
108
+ return null;
109
+ }
110
+ /**
111
+ * Get available coding agents with their descriptions
112
+ */
113
+ static getAvailableAgents() {
114
+ return [
115
+ {
116
+ value: CLAUDE_CODE,
117
+ name: "Claude Code",
118
+ description: "Anthropic Claude Code CLI agent"
119
+ },
120
+ {
121
+ value: CURSOR,
122
+ name: "Cursor",
123
+ description: "Cursor AI-first code editor"
124
+ },
125
+ {
126
+ value: GITHUB_COPILOT,
127
+ name: "GitHub Copilot",
128
+ description: "GitHub Copilot coding agent and CLI"
129
+ },
130
+ {
131
+ value: CODEX,
132
+ name: "Codex",
133
+ description: "OpenAI Codex CLI agent"
134
+ },
135
+ {
136
+ value: GEMINI_CLI,
137
+ name: "Gemini CLI",
138
+ description: "Google Gemini CLI agent"
139
+ },
140
+ {
141
+ value: NONE,
142
+ name: "Other",
143
+ description: "Other coding agent or skip MCP configuration"
144
+ }
145
+ ];
146
+ }
147
+ /**
148
+ * Get the coding agent service instance
149
+ * @param agent - The coding agent to get service for
150
+ * @returns The service instance or null if not supported
151
+ */
152
+ getCodingAgentService(agent) {
153
+ if (agent === CLAUDE_CODE) return new ClaudeCodeService({ workspaceRoot: this.workspaceRoot });
154
+ if (agent === CURSOR) return new CursorService({ workspaceRoot: this.workspaceRoot });
155
+ if (agent === GITHUB_COPILOT) return new GitHubCopilotService({ workspaceRoot: this.workspaceRoot });
156
+ if (agent === CODEX) return new CodexService({ workspaceRoot: this.workspaceRoot });
157
+ if (agent === GEMINI_CLI) return new GeminiCliService({ workspaceRoot: this.workspaceRoot });
158
+ return null;
159
+ }
160
+ /**
161
+ * Update custom instructions/prompts for the coding agent
162
+ * Appends custom instruction prompt to the agent's configuration
163
+ * @param agent - The coding agent to update
164
+ * @param instructionPrompt - The instruction prompt to append
165
+ * @param customInstructionFile - Optional custom file path to write instructions to (e.g., '.claude/aicode-instructions.md')
166
+ */
167
+ async updateCustomInstructions(agent, instructionPrompt, customInstructionFile) {
168
+ if (agent === NONE) {
169
+ print.info("Skipping custom instruction update");
170
+ return;
171
+ }
172
+ print.info(`\nUpdating custom instructions for ${agent}...`);
173
+ const service = this.getCodingAgentService(agent);
174
+ if (!service) {
175
+ print.info(`Custom instruction update for ${agent} is not yet supported.`);
176
+ print.info("Please manually add the instructions to your agent configuration.");
177
+ print.info("\nInstruction prompt to add:");
178
+ print.info(instructionPrompt);
179
+ return;
180
+ }
181
+ await service.updatePrompt({
182
+ systemPrompt: instructionPrompt,
183
+ customInstructionFile,
184
+ marker: true
185
+ });
186
+ if (customInstructionFile) print.success(`Custom instructions written to ${customInstructionFile} and referenced in CLAUDE.md and AGENTS.md`);
187
+ else print.success(`Custom instructions appended to CLAUDE.md and AGENTS.md`);
188
+ }
189
+ /**
190
+ * Setup MCP configuration for the selected coding agent
191
+ * @param agent - The coding agent to configure
192
+ */
193
+ async setupMCP(agent) {
194
+ if (agent === NONE) {
195
+ print.info("Skipping MCP configuration");
196
+ return;
197
+ }
198
+ print.info(`\nSetting up MCP for ${agent}...`);
199
+ const service = this.getCodingAgentService(agent);
200
+ let configLocation = "";
201
+ let restartInstructions = "";
202
+ if (agent === CLAUDE_CODE) {
203
+ configLocation = ".mcp.json";
204
+ restartInstructions = "Restart Claude Code to load the new MCP servers";
205
+ } else if (agent === CURSOR) {
206
+ configLocation = "~/.cursor/mcp.json (or .cursor/mcp.json for workspace)";
207
+ restartInstructions = "Restart Cursor to load the new MCP servers";
208
+ } else if (agent === GITHUB_COPILOT) {
209
+ configLocation = "~/.copilot/config.json (CLI) or GitHub UI (Coding Agent)";
210
+ restartInstructions = "Restart GitHub Copilot CLI or configure via GitHub repository settings";
211
+ } else if (agent === CODEX) {
212
+ configLocation = "~/.codex/config.toml";
213
+ restartInstructions = "Restart Codex CLI to load the new MCP servers";
214
+ } else if (agent === GEMINI_CLI) {
215
+ configLocation = "~/.gemini/settings.json";
216
+ restartInstructions = "Restart Gemini CLI to load the new MCP servers";
217
+ }
218
+ if (!service) {
219
+ print.info(`MCP configuration for ${agent} is not yet supported.`);
220
+ print.info("Please configure MCP servers manually for this coding agent.");
221
+ return;
222
+ }
223
+ await service.updateMcpSettings({ servers: {
224
+ "scaffold-mcp": {
225
+ type: "stdio",
226
+ command: "npx",
227
+ args: [
228
+ "-y",
229
+ "@agiflowai/scaffold-mcp",
230
+ "mcp-serve"
231
+ ],
232
+ disabled: false
233
+ },
234
+ "architect-mcp": {
235
+ type: "stdio",
236
+ command: "npx",
237
+ args: [
238
+ "-y",
239
+ "@agiflowai/architect-mcp",
240
+ "mcp-serve"
241
+ ],
242
+ disabled: false
243
+ }
244
+ } });
245
+ print.success(`Added scaffold-mcp and architect-mcp to ${configLocation}`);
246
+ print.info("\nNext steps:");
247
+ print.indent(`1. ${restartInstructions}`);
248
+ print.indent("2. The scaffold-mcp and architect-mcp servers will be available");
249
+ print.success("\nMCP configuration completed!");
250
+ }
251
+ };
252
+
73
253
  //#endregion
74
254
  //#region src/utils/banner.ts
75
255
  /**
@@ -145,8 +325,7 @@ async function findWorkspaceRoot(startPath = process.cwd()) {
145
325
  let currentPath = path.resolve(startPath);
146
326
  const rootPath = path.parse(currentPath).root;
147
327
  while (true) {
148
- const gitPath = path.join(currentPath, ".git");
149
- if (await fs$1.pathExists(gitPath)) return currentPath;
328
+ if (await pathExists(path.join(currentPath, ".git"))) return currentPath;
150
329
  if (currentPath === rootPath) return null;
151
330
  currentPath = path.dirname(currentPath);
152
331
  }
@@ -202,8 +381,7 @@ async function cloneSubdirectory(repoUrl, branch, subdirectory, targetFolder) {
202
381
  "core.sparseCheckout",
203
382
  "true"
204
383
  ], tempFolder);
205
- const sparseCheckoutFile = path.join(tempFolder, ".git", "info", "sparse-checkout");
206
- await fs$1.writeFile(sparseCheckoutFile, `${subdirectory}\n`);
384
+ await writeFile(path.join(tempFolder, ".git", "info", "sparse-checkout"), `${subdirectory}\n`);
207
385
  await execGit([
208
386
  "pull",
209
387
  "--depth=1",
@@ -211,12 +389,12 @@ async function cloneSubdirectory(repoUrl, branch, subdirectory, targetFolder) {
211
389
  branch
212
390
  ], tempFolder);
213
391
  const sourceDir = path.join(tempFolder, subdirectory);
214
- if (!await fs$1.pathExists(sourceDir)) throw new Error(`Subdirectory '${subdirectory}' not found in repository at branch '${branch}'`);
215
- if (await fs$1.pathExists(targetFolder)) throw new Error(`Target folder already exists: ${targetFolder}`);
216
- await fs$1.move(sourceDir, targetFolder);
217
- await fs$1.remove(tempFolder);
392
+ if (!await pathExists(sourceDir)) throw new Error(`Subdirectory '${subdirectory}' not found in repository at branch '${branch}'`);
393
+ if (await pathExists(targetFolder)) throw new Error(`Target folder already exists: ${targetFolder}`);
394
+ await move(sourceDir, targetFolder);
395
+ await remove(tempFolder);
218
396
  } catch (error) {
219
- if (await fs$1.pathExists(tempFolder)) await fs$1.remove(tempFolder);
397
+ if (await pathExists(tempFolder)) await remove(tempFolder);
220
398
  throw error;
221
399
  }
222
400
  }
@@ -230,7 +408,7 @@ async function cloneRepository(repoUrl, targetFolder) {
230
408
  targetFolder
231
409
  ]);
232
410
  const gitFolder = path.join(targetFolder, ".git");
233
- if (await fs$1.pathExists(gitFolder)) await fs$1.remove(gitFolder);
411
+ if (await pathExists(gitFolder)) await remove(gitFolder);
234
412
  }
235
413
  /**
236
414
  * Fetch directory listing from GitHub API
@@ -251,172 +429,27 @@ async function fetchGitHubDirectoryContents(owner, repo, path$1, branch = "main"
251
429
  }));
252
430
  }
253
431
 
254
- //#endregion
255
- //#region src/services/CodingAgentService.ts
256
- var CodingAgentService = class {
257
- workspaceRoot;
258
- constructor(workspaceRoot) {
259
- this.workspaceRoot = workspaceRoot;
260
- }
261
- /**
262
- * Detect which coding agent is enabled in the workspace
263
- * Checks for Claude Code, Codex, Gemini CLI, GitHub Copilot, and Cursor installations
264
- * @param workspaceRoot - The workspace root directory
265
- * @returns Promise resolving to detected agent ID or null
266
- */
267
- static async detectCodingAgent(workspaceRoot) {
268
- if (await new ClaudeCodeService({ workspaceRoot }).isEnabled()) return CLAUDE_CODE;
269
- if (await new CursorService({ workspaceRoot }).isEnabled()) return CURSOR;
270
- if (await new GitHubCopilotService({ workspaceRoot }).isEnabled()) return GITHUB_COPILOT;
271
- if (await new CodexService({ workspaceRoot }).isEnabled()) return CODEX;
272
- if (await new GeminiCliService({ workspaceRoot }).isEnabled()) return GEMINI_CLI;
273
- return null;
274
- }
275
- /**
276
- * Get available coding agents with their descriptions
277
- */
278
- static getAvailableAgents() {
279
- return [
280
- {
281
- value: CLAUDE_CODE,
282
- name: "Claude Code",
283
- description: "Anthropic Claude Code CLI agent"
284
- },
285
- {
286
- value: CURSOR,
287
- name: "Cursor",
288
- description: "Cursor AI-first code editor"
289
- },
290
- {
291
- value: GITHUB_COPILOT,
292
- name: "GitHub Copilot",
293
- description: "GitHub Copilot coding agent and CLI"
294
- },
295
- {
296
- value: CODEX,
297
- name: "Codex",
298
- description: "OpenAI Codex CLI agent"
299
- },
300
- {
301
- value: GEMINI_CLI,
302
- name: "Gemini CLI",
303
- description: "Google Gemini CLI agent"
304
- },
305
- {
306
- value: NONE,
307
- name: "Other",
308
- description: "Other coding agent or skip MCP configuration"
309
- }
310
- ];
311
- }
312
- /**
313
- * Get the coding agent service instance
314
- * @param agent - The coding agent to get service for
315
- * @returns The service instance or null if not supported
316
- */
317
- getCodingAgentService(agent) {
318
- if (agent === CLAUDE_CODE) return new ClaudeCodeService({ workspaceRoot: this.workspaceRoot });
319
- if (agent === CURSOR) return new CursorService({ workspaceRoot: this.workspaceRoot });
320
- if (agent === GITHUB_COPILOT) return new GitHubCopilotService({ workspaceRoot: this.workspaceRoot });
321
- if (agent === CODEX) return new CodexService({ workspaceRoot: this.workspaceRoot });
322
- if (agent === GEMINI_CLI) return new GeminiCliService({ workspaceRoot: this.workspaceRoot });
323
- return null;
324
- }
325
- /**
326
- * Update custom instructions/prompts for the coding agent
327
- * Appends custom instruction prompt to the agent's configuration
328
- * @param agent - The coding agent to update
329
- * @param instructionPrompt - The instruction prompt to append
330
- * @param customInstructionFile - Optional custom file path to write instructions to (e.g., '.claude/aicode-instructions.md')
331
- */
332
- async updateCustomInstructions(agent, instructionPrompt, customInstructionFile) {
333
- if (agent === NONE) {
334
- print.info("Skipping custom instruction update");
335
- return;
336
- }
337
- print.info(`\nUpdating custom instructions for ${agent}...`);
338
- const service = this.getCodingAgentService(agent);
339
- if (!service) {
340
- print.info(`Custom instruction update for ${agent} is not yet supported.`);
341
- print.info("Please manually add the instructions to your agent configuration.");
342
- print.info("\nInstruction prompt to add:");
343
- print.info(instructionPrompt);
344
- return;
345
- }
346
- await service.updatePrompt({
347
- systemPrompt: instructionPrompt,
348
- customInstructionFile,
349
- marker: true
350
- });
351
- if (customInstructionFile) print.success(`Custom instructions written to ${customInstructionFile} and referenced in CLAUDE.md and AGENTS.md`);
352
- else print.success(`Custom instructions appended to CLAUDE.md and AGENTS.md`);
353
- }
354
- /**
355
- * Setup MCP configuration for the selected coding agent
356
- * @param agent - The coding agent to configure
357
- */
358
- async setupMCP(agent) {
359
- if (agent === NONE) {
360
- print.info("Skipping MCP configuration");
361
- return;
362
- }
363
- print.info(`\nSetting up MCP for ${agent}...`);
364
- const service = this.getCodingAgentService(agent);
365
- let configLocation = "";
366
- let restartInstructions = "";
367
- if (agent === CLAUDE_CODE) {
368
- configLocation = ".mcp.json";
369
- restartInstructions = "Restart Claude Code to load the new MCP servers";
370
- } else if (agent === CURSOR) {
371
- configLocation = "~/.cursor/mcp.json (or .cursor/mcp.json for workspace)";
372
- restartInstructions = "Restart Cursor to load the new MCP servers";
373
- } else if (agent === GITHUB_COPILOT) {
374
- configLocation = "~/.copilot/config.json (CLI) or GitHub UI (Coding Agent)";
375
- restartInstructions = "Restart GitHub Copilot CLI or configure via GitHub repository settings";
376
- } else if (agent === CODEX) {
377
- configLocation = "~/.codex/config.toml";
378
- restartInstructions = "Restart Codex CLI to load the new MCP servers";
379
- } else if (agent === GEMINI_CLI) {
380
- configLocation = "~/.gemini/settings.json";
381
- restartInstructions = "Restart Gemini CLI to load the new MCP servers";
382
- }
383
- if (!service) {
384
- print.info(`MCP configuration for ${agent} is not yet supported.`);
385
- print.info("Please configure MCP servers manually for this coding agent.");
386
- return;
387
- }
388
- await service.updateMcpSettings({ servers: {
389
- "scaffold-mcp": {
390
- type: "stdio",
391
- command: "npx",
392
- args: [
393
- "-y",
394
- "@agiflowai/scaffold-mcp",
395
- "mcp-serve"
396
- ],
397
- disabled: false
398
- },
399
- "architect-mcp": {
400
- type: "stdio",
401
- command: "npx",
402
- args: [
403
- "-y",
404
- "@agiflowai/architect-mcp",
405
- "mcp-serve"
406
- ],
407
- disabled: false
408
- }
409
- } });
410
- print.success(`Added scaffold-mcp and architect-mcp to ${configLocation}`);
411
- print.info("\nNext steps:");
412
- print.indent(`1. ${restartInstructions}`);
413
- print.indent("2. The scaffold-mcp and architect-mcp servers will be available");
414
- print.success("\nMCP configuration completed!");
415
- }
416
- };
417
-
418
432
  //#endregion
419
433
  //#region src/services/NewProjectService.ts
434
+ /**
435
+ * NewProjectService
436
+ *
437
+ * DESIGN PATTERNS:
438
+ * - Service pattern for business logic encapsulation
439
+ * - Single responsibility principle
440
+ * - No UI interaction (prompts handled by CLI layer)
441
+ *
442
+ * CODING STANDARDS:
443
+ * - Use async/await for asynchronous operations
444
+ * - Throw descriptive errors for error cases
445
+ * - Keep methods focused and well-named
446
+ * - Document complex logic with comments
447
+ *
448
+ * AVOID:
449
+ * - Mixing concerns (keep focused on single domain)
450
+ * - Direct UI interaction (no @inquirer/prompts in services)
451
+ * - Direct tool implementation (services should be tool-agnostic)
452
+ */
420
453
  const RESERVED_PROJECT_NAMES = [
421
454
  ".",
422
455
  "..",
@@ -490,7 +523,7 @@ var NewProjectService = class {
490
523
  */
491
524
  async createProjectDirectory(projectPath, projectName) {
492
525
  try {
493
- await fs$1.mkdir(projectPath, { recursive: false });
526
+ await mkdir(projectPath, { recursive: false });
494
527
  } catch (error) {
495
528
  if (error.code === "EEXIST") throw new Error(`Directory '${projectName}' already exists. Please choose a different name.`);
496
529
  throw error;
@@ -507,7 +540,7 @@ var NewProjectService = class {
507
540
  if (parsed.isSubdirectory && parsed.branch && parsed.subdirectory) await cloneSubdirectory(parsed.repoUrl, parsed.branch, parsed.subdirectory, projectPath);
508
541
  else await cloneRepository(parsed.repoUrl, projectPath);
509
542
  } catch (error) {
510
- await fs$1.remove(projectPath);
543
+ await remove(projectPath);
511
544
  throw new Error(`Failed to clone repository: ${error.message}`);
512
545
  }
513
546
  }
@@ -541,6 +574,18 @@ var openspec_default = "When working on this project, follow the OpenSpec spec-d
541
574
  //#endregion
542
575
  //#region src/specs/openspec.ts
543
576
  /**
577
+ * OpenSpec Bridge Implementation
578
+ *
579
+ * DESIGN PATTERNS:
580
+ * - Bridge pattern implementation for OpenSpec
581
+ * - Singleton pattern for OpenSpec configuration
582
+ *
583
+ * CODING STANDARDS:
584
+ * - Implement ISpecBridge interface
585
+ * - Use async/await for I/O operations
586
+ * - Handle errors with descriptive messages
587
+ */
588
+ /**
544
589
  * OpenSpec configuration
545
590
  */
546
591
  const OPENSPEC_CONFIG = {
@@ -606,6 +651,18 @@ var OpenSpecBridge = class {
606
651
  //#endregion
607
652
  //#region src/services/SpecToolService.ts
608
653
  /**
654
+ * Spec Tool Service
655
+ *
656
+ * DESIGN PATTERNS:
657
+ * - Service pattern for spec tool detection and installation
658
+ * - Bridge pattern to abstract spec tool implementations
659
+ *
660
+ * CODING STANDARDS:
661
+ * - Use async/await for asynchronous operations
662
+ * - Handle errors with try/catch blocks
663
+ * - Use descriptive method names
664
+ */
665
+ /**
609
666
  * Available spec tools
610
667
  */
611
668
  let SpecTool = /* @__PURE__ */ function(SpecTool$1) {
@@ -683,6 +740,22 @@ var SpecToolService = class {
683
740
 
684
741
  //#endregion
685
742
  //#region src/services/TemplateSelectionService.ts
743
+ /**
744
+ * TemplateSelectionService
745
+ *
746
+ * DESIGN PATTERNS:
747
+ * - Service pattern for business logic encapsulation
748
+ * - Single responsibility: Handle template download, listing, and selection
749
+ *
750
+ * CODING STANDARDS:
751
+ * - Use async/await for asynchronous operations
752
+ * - Throw descriptive errors for error cases
753
+ * - Document methods with JSDoc comments
754
+ *
755
+ * AVOID:
756
+ * - Direct UI interaction (no prompts in services)
757
+ * - Mixing concerns beyond template management
758
+ */
686
759
  var TemplateSelectionService = class {
687
760
  tmpDir;
688
761
  constructor(existingTmpDir) {
@@ -695,7 +768,7 @@ var TemplateSelectionService = class {
695
768
  */
696
769
  async downloadTemplatesToTmp(repoConfig) {
697
770
  try {
698
- await fs$1.ensureDir(this.tmpDir);
771
+ await ensureDir(this.tmpDir);
699
772
  const contents = await fetchGitHubDirectoryContents(repoConfig.owner, repoConfig.repo, repoConfig.path, repoConfig.branch);
700
773
  const templateDirs = contents.filter((item) => item.type === "dir");
701
774
  const globalFiles = contents.filter((item) => item.type === "file" && item.name === "RULES.yaml");
@@ -713,8 +786,7 @@ var TemplateSelectionService = class {
713
786
  const targetFile = path.join(this.tmpDir, "RULES.yaml");
714
787
  const response = await fetch(rulesUrl);
715
788
  if (response.ok) {
716
- const content = await response.text();
717
- await fs$1.writeFile(targetFile, content, "utf-8");
789
+ await writeFile(targetFile, await response.text(), "utf-8");
718
790
  print.success("Downloaded global RULES.yaml");
719
791
  }
720
792
  }
@@ -731,7 +803,7 @@ var TemplateSelectionService = class {
731
803
  */
732
804
  async listTemplates() {
733
805
  try {
734
- const entries = await fs$1.readdir(this.tmpDir, { withFileTypes: true });
806
+ const entries = await readdir(this.tmpDir, { withFileTypes: true });
735
807
  const templates = [];
736
808
  for (const entry of entries) if (entry.isDirectory()) {
737
809
  const templatePath = path.join(this.tmpDir, entry.name);
@@ -757,27 +829,27 @@ var TemplateSelectionService = class {
757
829
  async copyTemplates(templateNames, destinationPath, projectType, selectedMcpServers) {
758
830
  try {
759
831
  if (projectType === ProjectType.MONOLITH && templateNames.length > 1) throw new Error("Monolith projects can only use a single template");
760
- await fs$1.ensureDir(destinationPath);
832
+ await ensureDir(destinationPath);
761
833
  print.info(`\nCopying templates to ${destinationPath}...`);
762
834
  for (const templateName of templateNames) {
763
835
  const sourcePath = path.join(this.tmpDir, templateName);
764
836
  const targetPath = path.join(destinationPath, templateName);
765
- if (!await fs$1.pathExists(sourcePath)) throw new Error(`Template '${templateName}' not found in downloaded templates`);
766
- if (await fs$1.pathExists(targetPath)) {
837
+ if (!await pathExists(sourcePath)) throw new Error(`Template '${templateName}' not found in downloaded templates`);
838
+ if (await pathExists(targetPath)) {
767
839
  print.info(`Skipping ${templateName} (already exists)`);
768
840
  continue;
769
841
  }
770
842
  print.info(`Copying ${templateName}...`);
771
843
  if (selectedMcpServers && selectedMcpServers.length > 0) await this.copyTemplateWithMcpFilter(sourcePath, targetPath, selectedMcpServers);
772
- else await fs$1.copy(sourcePath, targetPath);
844
+ else await copy(sourcePath, targetPath);
773
845
  print.success(`Copied ${templateName}`);
774
846
  }
775
847
  const globalRulesSource = path.join(this.tmpDir, "RULES.yaml");
776
848
  const globalRulesTarget = path.join(destinationPath, "RULES.yaml");
777
- if (await fs$1.pathExists(globalRulesSource)) {
778
- if (!await fs$1.pathExists(globalRulesTarget)) {
849
+ if (await pathExists(globalRulesSource)) {
850
+ if (!await pathExists(globalRulesTarget)) {
779
851
  print.info("Copying global RULES.yaml...");
780
- await fs$1.copy(globalRulesSource, globalRulesTarget);
852
+ await copy(globalRulesSource, globalRulesTarget);
781
853
  print.success("Copied global RULES.yaml");
782
854
  }
783
855
  }
@@ -793,23 +865,23 @@ var TemplateSelectionService = class {
793
865
  * @param selectedMcpServers - Selected MCP servers
794
866
  */
795
867
  async copyTemplateWithMcpFilter(sourcePath, targetPath, selectedMcpServers) {
796
- const { MCPServer: MCPServer$1, MCP_CONFIG_FILES: MCP_CONFIG_FILES$1 } = await import("./mcp-CZIiB-6Y.js");
868
+ const { MCPServer: MCPServer$1, MCP_CONFIG_FILES: MCP_CONFIG_FILES$1 } = await import("./mcp-BgNkvV6h.mjs");
797
869
  const architectFiles = MCP_CONFIG_FILES$1[MCPServer$1.ARCHITECT];
798
870
  const hasArchitect = selectedMcpServers.includes(MCPServer$1.ARCHITECT);
799
871
  const hasScaffold = selectedMcpServers.includes(MCPServer$1.SCAFFOLD);
800
- await fs$1.ensureDir(targetPath);
801
- const entries = await fs$1.readdir(sourcePath, { withFileTypes: true });
872
+ await ensureDir(targetPath);
873
+ const entries = await readdir(sourcePath, { withFileTypes: true });
802
874
  for (const entry of entries) {
803
875
  const entrySourcePath = path.join(sourcePath, entry.name);
804
876
  const entryTargetPath = path.join(targetPath, entry.name);
805
877
  const isArchitectFile = architectFiles.includes(entry.name);
806
- if (hasArchitect && hasScaffold) if (entry.isDirectory()) await fs$1.copy(entrySourcePath, entryTargetPath);
807
- else await fs$1.copy(entrySourcePath, entryTargetPath);
878
+ if (hasArchitect && hasScaffold) if (entry.isDirectory()) await copy(entrySourcePath, entryTargetPath);
879
+ else await copy(entrySourcePath, entryTargetPath);
808
880
  else if (hasArchitect && !hasScaffold) {
809
- if (isArchitectFile) await fs$1.copy(entrySourcePath, entryTargetPath);
881
+ if (isArchitectFile) await copy(entrySourcePath, entryTargetPath);
810
882
  } else if (!hasArchitect && hasScaffold) {
811
- if (!isArchitectFile) if (entry.isDirectory()) await fs$1.copy(entrySourcePath, entryTargetPath);
812
- else await fs$1.copy(entrySourcePath, entryTargetPath);
883
+ if (!isArchitectFile) if (entry.isDirectory()) await copy(entrySourcePath, entryTargetPath);
884
+ else await copy(entrySourcePath, entryTargetPath);
813
885
  }
814
886
  }
815
887
  }
@@ -821,15 +893,15 @@ var TemplateSelectionService = class {
821
893
  async readTemplateDescription(templatePath) {
822
894
  try {
823
895
  const scaffoldYamlPath = path.join(templatePath, "scaffold.yaml");
824
- if (await fs$1.pathExists(scaffoldYamlPath)) {
896
+ if (await pathExists(scaffoldYamlPath)) {
825
897
  const yaml = await import("js-yaml");
826
- const content = await fs$1.readFile(scaffoldYamlPath, "utf-8");
898
+ const content = await readFile(scaffoldYamlPath, "utf-8");
827
899
  const scaffoldConfig = yaml.load(content);
828
900
  if (scaffoldConfig?.description) return scaffoldConfig.description;
829
901
  if (scaffoldConfig?.boilerplate?.[0]?.description) return scaffoldConfig.boilerplate[0].description;
830
902
  }
831
903
  const readmePath = path.join(templatePath, "README.md");
832
- if (await fs$1.pathExists(readmePath)) return (await fs$1.readFile(readmePath, "utf-8")).split("\n\n")[0].substring(0, 200).trim();
904
+ if (await pathExists(readmePath)) return (await readFile(readmePath, "utf-8")).split("\n\n")[0].substring(0, 200).trim();
833
905
  return;
834
906
  } catch {
835
907
  return;
@@ -846,7 +918,7 @@ var TemplateSelectionService = class {
846
918
  */
847
919
  async cleanup() {
848
920
  try {
849
- if (await fs$1.pathExists(this.tmpDir)) await fs$1.remove(this.tmpDir);
921
+ if (await pathExists(this.tmpDir)) await remove(this.tmpDir);
850
922
  } catch (error) {
851
923
  print.warning(`Warning: Failed to clean up tmp directory: ${error.message}`);
852
924
  }
@@ -855,6 +927,23 @@ var TemplateSelectionService = class {
855
927
 
856
928
  //#endregion
857
929
  //#region src/services/TemplatesService.ts
930
+ /**
931
+ * TemplatesService
932
+ *
933
+ * DESIGN PATTERNS:
934
+ * - Service pattern for business logic encapsulation
935
+ * - Single responsibility principle
936
+ *
937
+ * CODING STANDARDS:
938
+ * - Use async/await for asynchronous operations
939
+ * - Throw descriptive errors for error cases
940
+ * - Keep methods focused and well-named
941
+ * - Document complex logic with comments
942
+ *
943
+ * AVOID:
944
+ * - Mixing concerns (keep focused on single domain)
945
+ * - Direct tool implementation (services should be tool-agnostic)
946
+ */
858
947
  var TemplatesService = class {
859
948
  /**
860
949
  * Download templates from a GitHub repository with UI feedback
@@ -874,7 +963,7 @@ var TemplatesService = class {
874
963
  let _skipped = 0;
875
964
  for (const template of templateDirs) {
876
965
  const targetFolder = path.join(templatesPath, template.name);
877
- if (await fs$1.pathExists(targetFolder)) {
966
+ if (await pathExists(targetFolder)) {
878
967
  print.info(`Skipping ${template.name} (already exists)`);
879
968
  _skipped++;
880
969
  continue;
@@ -894,8 +983,8 @@ var TemplatesService = class {
894
983
  * @param templatesPath - Path where templates folder should be created
895
984
  */
896
985
  async initializeTemplatesFolder(templatesPath) {
897
- await fs$1.ensureDir(templatesPath);
898
- await fs$1.writeFile(path.join(templatesPath, "README.md"), `# Templates
986
+ await ensureDir(templatesPath);
987
+ await writeFile(path.join(templatesPath, "README.md"), `# Templates
899
988
 
900
989
  This folder contains boilerplate templates and scaffolding methods for your projects.
901
990
 
@@ -933,4 +1022,4 @@ See existing templates for examples and documentation for more details.
933
1022
  };
934
1023
 
935
1024
  //#endregion
936
- export { BANNER_GRADIENT, CodingAgentService, NewProjectService, SPEC_TOOL_INFO, SpecTool, SpecToolService, THEME, TemplateSelectionService, TemplatesService, cloneRepository, cloneSubdirectory, displayBanner, displayCompactBanner, fetchGitHubDirectoryContents, findWorkspaceRoot, gitInit, parseGitHubUrl };
1025
+ export { THEME as _, SpecToolService as a, cloneSubdirectory as c, gitInit as d, parseGitHubUrl as f, BANNER_GRADIENT as g, CodingAgentService as h, SpecTool as i, fetchGitHubDirectoryContents as l, displayCompactBanner as m, TemplateSelectionService as n, NewProjectService as o, displayBanner as p, SPEC_TOOL_INFO as r, cloneRepository as s, TemplatesService as t, findWorkspaceRoot as u };