@lousy-agents/cli 2.3.4 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -10032,7 +10032,7 @@ function resolveArgs(argsDef) {
10032
10032
  function defineCommand(def) {
10033
10033
  return def;
10034
10034
  }
10035
- async function runCommand(cmd, opts) {
10035
+ async function dist_runCommand(cmd, opts) {
10036
10036
  const cmdArgs = await resolveValue(cmd.args || {});
10037
10037
  const parsedArgs = parseArgs(opts.rawArgs, cmdArgs);
10038
10038
  const context = {
@@ -10051,7 +10051,7 @@ async function runCommand(cmd, opts) {
10051
10051
  if (subCommandName) {
10052
10052
  if (!subCommands[subCommandName]) throw new CLIError(`Unknown command ${cyan(subCommandName)}`, "E_UNKNOWN_COMMAND");
10053
10053
  const subCommand = await resolveValue(subCommands[subCommandName]);
10054
- if (subCommand) await runCommand(subCommand, { rawArgs: opts.rawArgs.slice(subCommandArgIndex + 1) });
10054
+ if (subCommand) await dist_runCommand(subCommand, { rawArgs: opts.rawArgs.slice(subCommandArgIndex + 1) });
10055
10055
  } else if (!cmd.run) throw new CLIError(`No command specified.`, "E_NO_COMMAND");
10056
10056
  }
10057
10057
  if (typeof cmd.run === "function") result = await cmd.run(context);
@@ -10151,7 +10151,7 @@ async function runMain(cmd, opts = {}) {
10151
10151
  const meta = typeof cmd.meta === "function" ? await cmd.meta() : await cmd.meta;
10152
10152
  if (!meta?.version) throw new CLIError("No version specified", "E_NO_VERSION");
10153
10153
  console.log(meta.version);
10154
- } else await runCommand(cmd, { rawArgs });
10154
+ } else await dist_runCommand(cmd, { rawArgs });
10155
10155
  } catch (error) {
10156
10156
  if (error instanceof CLIError) {
10157
10157
  await showUsage$1(...await resolveSubCommand(cmd, rawArgs));
@@ -11754,7 +11754,7 @@ const consola = dist_createConsola();
11754
11754
  */
11755
11755
  /**
11756
11756
  * Checks if a file or directory exists
11757
- */ async function fileExists(path) {
11757
+ */ async function file_system_utils_fileExists(path) {
11758
11758
  try {
11759
11759
  await (0,promises_.access)(path);
11760
11760
  return true;
@@ -11778,7 +11778,7 @@ const consola = dist_createConsola();
11778
11778
  }
11779
11779
  async agentFileExists(targetDir, agentName) {
11780
11780
  const filePath = this.getAgentFilePath(targetDir, agentName);
11781
- return fileExists(filePath);
11781
+ return file_system_utils_fileExists(filePath);
11782
11782
  }
11783
11783
  async ensureAgentsDirectory(targetDir) {
11784
11784
  const agentsDir = (0,external_node_path_.join)(targetDir, ".github", "agents");
@@ -11799,6 +11799,27 @@ const consola = dist_createConsola();
11799
11799
  return new FileSystemAgentFileGateway();
11800
11800
  }
11801
11801
 
11802
+ ;// CONCATENATED MODULE: ./src/entities/copilot-setup.ts
11803
+ /**
11804
+ * Core domain entities for the Copilot Setup Steps feature.
11805
+ * These are the fundamental types that represent the business domain.
11806
+ */ /**
11807
+ * Types of version files supported for detection
11808
+ */ /**
11809
+ * Node.js package manager types (in priority order: npm > yarn > pnpm)
11810
+ */ const NODE_PACKAGE_MANAGERS = [
11811
+ "npm",
11812
+ "yarn",
11813
+ "pnpm"
11814
+ ];
11815
+ /**
11816
+ * Python package manager types (in priority order)
11817
+ */ const PYTHON_PACKAGE_MANAGERS = [
11818
+ "poetry",
11819
+ "pipenv",
11820
+ "pip"
11821
+ ];
11822
+
11802
11823
  // EXTERNAL MODULE: external "node:fs"
11803
11824
  var external_node_fs_ = __webpack_require__(3024);
11804
11825
  // EXTERNAL MODULE: ./node_modules/pathe/dist/shared/pathe.M-eThtNZ.mjs
@@ -14086,12 +14107,102 @@ async function watchConfig(options) {
14086
14107
  "actions/setup-ruby",
14087
14108
  "jdx/mise-action"
14088
14109
  ];
14110
+ /**
14111
+ * Default package manager mappings
14112
+ * Based on Dependabot supported ecosystems
14113
+ */ const DEFAULT_PACKAGE_MANAGERS = [
14114
+ // Node.js package managers
14115
+ {
14116
+ type: "npm",
14117
+ manifestFile: "package.json",
14118
+ lockfile: "package-lock.json",
14119
+ installCommand: "npm ci"
14120
+ },
14121
+ {
14122
+ type: "yarn",
14123
+ manifestFile: "package.json",
14124
+ lockfile: "yarn.lock",
14125
+ installCommand: "yarn install --frozen-lockfile"
14126
+ },
14127
+ {
14128
+ type: "pnpm",
14129
+ manifestFile: "package.json",
14130
+ lockfile: "pnpm-lock.yaml",
14131
+ installCommand: "pnpm install --frozen-lockfile"
14132
+ },
14133
+ // Python package managers
14134
+ {
14135
+ type: "pip",
14136
+ manifestFile: "requirements.txt",
14137
+ installCommand: "pip install -r requirements.txt"
14138
+ },
14139
+ {
14140
+ type: "pipenv",
14141
+ manifestFile: "Pipfile",
14142
+ lockfile: "Pipfile.lock",
14143
+ installCommand: "pipenv install --deploy"
14144
+ },
14145
+ {
14146
+ type: "poetry",
14147
+ manifestFile: "pyproject.toml",
14148
+ lockfile: "poetry.lock",
14149
+ requiresLockfile: true,
14150
+ installCommand: "poetry install --no-root"
14151
+ },
14152
+ // Ruby
14153
+ {
14154
+ type: "bundler",
14155
+ manifestFile: "Gemfile",
14156
+ lockfile: "Gemfile.lock",
14157
+ installCommand: "bundle install"
14158
+ },
14159
+ // Rust
14160
+ {
14161
+ type: "cargo",
14162
+ manifestFile: "Cargo.toml",
14163
+ lockfile: "Cargo.lock",
14164
+ installCommand: "cargo build"
14165
+ },
14166
+ // PHP
14167
+ {
14168
+ type: "composer",
14169
+ manifestFile: "composer.json",
14170
+ lockfile: "composer.lock",
14171
+ installCommand: "composer install"
14172
+ },
14173
+ // Java
14174
+ {
14175
+ type: "maven",
14176
+ manifestFile: "pom.xml",
14177
+ installCommand: "mvn install -DskipTests"
14178
+ },
14179
+ {
14180
+ type: "gradle",
14181
+ manifestFile: "build.gradle",
14182
+ installCommand: "gradle build -x test"
14183
+ },
14184
+ // Go
14185
+ {
14186
+ type: "gomod",
14187
+ manifestFile: "go.mod",
14188
+ lockfile: "go.sum",
14189
+ installCommand: "go mod download"
14190
+ },
14191
+ // Dart/Flutter
14192
+ {
14193
+ type: "pub",
14194
+ manifestFile: "pubspec.yaml",
14195
+ lockfile: "pubspec.lock",
14196
+ installCommand: "dart pub get"
14197
+ }
14198
+ ];
14089
14199
  /**
14090
14200
  * Default copilot-setup configuration
14091
14201
  */ const DEFAULT_CONFIG = {
14092
14202
  versionFiles: DEFAULT_VERSION_FILES,
14093
14203
  setupActions: DEFAULT_SETUP_ACTIONS,
14094
- setupActionPatterns: DEFAULT_SETUP_ACTION_PATTERNS
14204
+ setupActionPatterns: DEFAULT_SETUP_ACTION_PATTERNS,
14205
+ packageManagers: DEFAULT_PACKAGE_MANAGERS
14095
14206
  };
14096
14207
  /**
14097
14208
  * Loads the copilot-setup configuration using c12
@@ -14150,6 +14261,7 @@ async function watchConfig(options) {
14150
14261
 
14151
14262
 
14152
14263
 
14264
+
14153
14265
  /**
14154
14266
  * Reads the content of a version file and trims whitespace
14155
14267
  */ async function readVersionFileContent(filePath) {
@@ -14167,14 +14279,26 @@ async function watchConfig(options) {
14167
14279
  return this.config;
14168
14280
  }
14169
14281
  async detectEnvironment(targetDir) {
14170
- const miseTomlPath = (0,external_node_path_.join)(targetDir, "mise.toml");
14171
- const hasMise = await fileExists(miseTomlPath);
14172
14282
  const config = await this.getConfig();
14283
+ const hasMise = await this.detectMise(targetDir);
14284
+ const versionFiles = await this.detectVersionFiles(targetDir, config);
14285
+ const packageManagers = await this.detectPackageManagers(targetDir, config);
14286
+ return {
14287
+ hasMise,
14288
+ versionFiles,
14289
+ packageManagers
14290
+ };
14291
+ }
14292
+ async detectMise(targetDir) {
14293
+ const miseTomlPath = (0,external_node_path_.join)(targetDir, "mise.toml");
14294
+ return file_system_utils_fileExists(miseTomlPath);
14295
+ }
14296
+ async detectVersionFiles(targetDir, config) {
14173
14297
  const filenameToType = getVersionFilenameToTypeMap(config);
14174
14298
  const versionFiles = [];
14175
14299
  for (const fileConfig of config.versionFiles){
14176
14300
  const filePath = (0,external_node_path_.join)(targetDir, fileConfig.filename);
14177
- if (await fileExists(filePath)) {
14301
+ if (await file_system_utils_fileExists(filePath)) {
14178
14302
  const version = await readVersionFileContent(filePath);
14179
14303
  versionFiles.push({
14180
14304
  type: filenameToType[fileConfig.filename],
@@ -14183,10 +14307,109 @@ async function watchConfig(options) {
14183
14307
  });
14184
14308
  }
14185
14309
  }
14186
- return {
14187
- hasMise,
14188
- versionFiles
14189
- };
14310
+ return versionFiles;
14311
+ }
14312
+ async detectPackageManagers(targetDir, config) {
14313
+ const packageManagers = [];
14314
+ // Helper to check if a package manager type is in a list
14315
+ const isPackageManagerType = (pm, types)=>types.includes(pm.type);
14316
+ const nodePackageManagers = config.packageManagers.filter((pm)=>isPackageManagerType(pm, NODE_PACKAGE_MANAGERS));
14317
+ const pythonPackageManagers = config.packageManagers.filter((pm)=>isPackageManagerType(pm, PYTHON_PACKAGE_MANAGERS));
14318
+ const otherPackageManagers = config.packageManagers.filter((pm)=>!isPackageManagerType(pm, NODE_PACKAGE_MANAGERS) && !isPackageManagerType(pm, PYTHON_PACKAGE_MANAGERS));
14319
+ // Detect Node.js package manager (with prioritization)
14320
+ const nodePackageManager = await this.detectNodePackageManager(targetDir, nodePackageManagers);
14321
+ if (nodePackageManager) {
14322
+ packageManagers.push(nodePackageManager);
14323
+ }
14324
+ // Detect Python package manager (with prioritization)
14325
+ const pythonPackageManager = await this.detectPythonPackageManager(targetDir, pythonPackageManagers);
14326
+ if (pythonPackageManager) {
14327
+ packageManagers.push(pythonPackageManager);
14328
+ }
14329
+ // Detect other package managers
14330
+ const otherDetectedManagers = await this.detectOtherPackageManagers(targetDir, otherPackageManagers);
14331
+ packageManagers.push(...otherDetectedManagers);
14332
+ return packageManagers;
14333
+ }
14334
+ async detectNodePackageManager(targetDir, nodePackageManagers) {
14335
+ const packageJsonPath = (0,external_node_path_.join)(targetDir, "package.json");
14336
+ if (!await file_system_utils_fileExists(packageJsonPath)) {
14337
+ return null;
14338
+ }
14339
+ // Priority order for Node.js package managers: npm > yarn > pnpm
14340
+ const lockfileOrder = [
14341
+ "npm",
14342
+ "yarn",
14343
+ "pnpm"
14344
+ ];
14345
+ for (const pmType of lockfileOrder){
14346
+ const pmConfig = nodePackageManagers.find((pm)=>pm.type === pmType);
14347
+ if (!pmConfig?.lockfile) {
14348
+ continue;
14349
+ }
14350
+ const lockfilePath = (0,external_node_path_.join)(targetDir, pmConfig.lockfile);
14351
+ if (await file_system_utils_fileExists(lockfilePath)) {
14352
+ return {
14353
+ type: pmConfig.type,
14354
+ filename: pmConfig.manifestFile,
14355
+ lockfile: pmConfig.lockfile
14356
+ };
14357
+ }
14358
+ }
14359
+ // Default to npm if no lockfile found
14360
+ const npmConfig = nodePackageManagers.find((pm)=>pm.type === "npm");
14361
+ if (npmConfig) {
14362
+ return {
14363
+ type: npmConfig.type,
14364
+ filename: npmConfig.manifestFile,
14365
+ lockfile: undefined
14366
+ };
14367
+ }
14368
+ return null;
14369
+ }
14370
+ async detectPythonPackageManager(targetDir, pythonPackageManagers) {
14371
+ // Priority order for Python package managers: poetry > pipenv > pip
14372
+ for (const pmType of PYTHON_PACKAGE_MANAGERS){
14373
+ const pmConfig = pythonPackageManagers.find((pm)=>pm.type === pmType);
14374
+ if (!pmConfig) {
14375
+ continue;
14376
+ }
14377
+ const manifestPath = (0,external_node_path_.join)(targetDir, pmConfig.manifestFile);
14378
+ if (await file_system_utils_fileExists(manifestPath)) {
14379
+ const lockfilePath = pmConfig.lockfile ? (0,external_node_path_.join)(targetDir, pmConfig.lockfile) : undefined;
14380
+ const hasLockfile = lockfilePath ? await file_system_utils_fileExists(lockfilePath) : false;
14381
+ // Skip if lockfile is required but not present
14382
+ if (pmConfig.requiresLockfile && !hasLockfile) {
14383
+ continue;
14384
+ }
14385
+ return {
14386
+ type: pmConfig.type,
14387
+ filename: pmConfig.manifestFile,
14388
+ lockfile: hasLockfile ? pmConfig.lockfile : undefined
14389
+ };
14390
+ }
14391
+ }
14392
+ return null;
14393
+ }
14394
+ async detectOtherPackageManagers(targetDir, otherPackageManagers) {
14395
+ const packageManagers = [];
14396
+ for (const pmConfig of otherPackageManagers){
14397
+ const manifestPath = (0,external_node_path_.join)(targetDir, pmConfig.manifestFile);
14398
+ if (await file_system_utils_fileExists(manifestPath)) {
14399
+ const lockfilePath = pmConfig.lockfile ? (0,external_node_path_.join)(targetDir, pmConfig.lockfile) : undefined;
14400
+ const hasLockfile = lockfilePath ? await file_system_utils_fileExists(lockfilePath) : false;
14401
+ // Skip if lockfile is required but not present
14402
+ if (pmConfig.requiresLockfile && !hasLockfile) {
14403
+ continue;
14404
+ }
14405
+ packageManagers.push({
14406
+ type: pmConfig.type,
14407
+ filename: pmConfig.manifestFile,
14408
+ lockfile: hasLockfile ? pmConfig.lockfile : undefined
14409
+ });
14410
+ }
14411
+ }
14412
+ return packageManagers;
14190
14413
  }
14191
14414
  }
14192
14415
  /**
@@ -14195,6 +14418,174 @@ async function watchConfig(options) {
14195
14418
  return new FileSystemEnvironmentGateway();
14196
14419
  }
14197
14420
 
14421
+ ;// CONCATENATED MODULE: ./src/gateways/instruction-analysis-gateway.ts
14422
+ /**
14423
+ * Gateway for analyzing repository instructions for feedback loop coverage
14424
+ */
14425
+
14426
+
14427
+ /**
14428
+ * File system implementation of instruction analysis gateway
14429
+ */ class FileSystemInstructionAnalysisGateway {
14430
+ async analyzeCoverage(targetDir, scripts, tools) {
14431
+ // Find all instruction files
14432
+ const instructionFiles = await this.findInstructionFiles(targetDir);
14433
+ // Read and search instruction content
14434
+ const references = [];
14435
+ const documentedTargets = new Set();
14436
+ for (const file of instructionFiles){
14437
+ const content = await readFile(file, "utf-8");
14438
+ const lines = content.split("\n");
14439
+ // Check for script references (e.g., "npm test", "npm run build")
14440
+ for (const script of scripts){
14441
+ const scriptRefs = this.findReferencesInContent(script.name, content, lines, file, targetDir);
14442
+ if (scriptRefs.length > 0) {
14443
+ references.push(...scriptRefs);
14444
+ documentedTargets.add(script.name);
14445
+ }
14446
+ }
14447
+ // Check for tool references (e.g., "mise run test", "biome check")
14448
+ for (const tool of tools){
14449
+ const toolRefs = this.findReferencesInContent(tool.name, content, lines, file, targetDir);
14450
+ if (toolRefs.length > 0) {
14451
+ references.push(...toolRefs);
14452
+ documentedTargets.add(tool.name);
14453
+ }
14454
+ }
14455
+ }
14456
+ // Filter mandatory scripts/tools
14457
+ const mandatoryScripts = scripts.filter((s)=>s.isMandatory);
14458
+ const mandatoryTools = tools.filter((t)=>t.isMandatory);
14459
+ const allMandatory = [
14460
+ ...mandatoryScripts,
14461
+ ...mandatoryTools
14462
+ ];
14463
+ // Categorize as missing or documented
14464
+ const missingInInstructions = allMandatory.filter((item)=>!documentedTargets.has(item.name));
14465
+ const documentedInInstructions = allMandatory.filter((item)=>documentedTargets.has(item.name));
14466
+ const totalMandatory = allMandatory.length;
14467
+ const totalDocumented = documentedInInstructions.length;
14468
+ const coveragePercentage = totalMandatory === 0 ? 100 : totalDocumented / totalMandatory * 100;
14469
+ return {
14470
+ missingInInstructions,
14471
+ documentedInInstructions,
14472
+ references,
14473
+ summary: {
14474
+ totalMandatory,
14475
+ totalDocumented,
14476
+ coveragePercentage: Math.round(coveragePercentage * 100) / 100
14477
+ }
14478
+ };
14479
+ }
14480
+ async findInstructionFiles(targetDir) {
14481
+ const files = [];
14482
+ // Check for .github/copilot-instructions.md
14483
+ const copilotInstructions = join(targetDir, ".github", "copilot-instructions.md");
14484
+ if (await fileExists(copilotInstructions)) {
14485
+ files.push(copilotInstructions);
14486
+ }
14487
+ // Check for .github/instructions/*.md
14488
+ const instructionsDir = join(targetDir, ".github", "instructions");
14489
+ if (await fileExists(instructionsDir)) {
14490
+ try {
14491
+ const instructionFiles = await readdir(instructionsDir);
14492
+ for (const file of instructionFiles){
14493
+ if (file.endsWith(".md")) {
14494
+ files.push(join(instructionsDir, file));
14495
+ }
14496
+ }
14497
+ } catch {
14498
+ // Skip directory if we can't read it (e.g., permissions issues)
14499
+ // Similar to how workflow parsing errors are handled
14500
+ }
14501
+ }
14502
+ return files;
14503
+ }
14504
+ findReferencesInContent(target, content, lines, file, targetDir) {
14505
+ const references = [];
14506
+ // Build a case-insensitive, word-boundary-aware pattern for the target.
14507
+ // This reduces false positives from simple substring matches like
14508
+ // "test" in "testing" or "latest", while still matching common
14509
+ // separators such as spaces, punctuation, etc.
14510
+ const escapedTarget = target.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
14511
+ const targetPattern = new RegExp(`(?:^|[^\\w])(${escapedTarget})(?=$|[^\\w])`, "i");
14512
+ // Fast path: skip line-by-line processing if the target pattern
14513
+ // never appears in the full content.
14514
+ if (!targetPattern.test(content)) {
14515
+ return references;
14516
+ }
14517
+ // Find line numbers where target appears
14518
+ for(let i = 0; i < lines.length; i++){
14519
+ const line = lines[i];
14520
+ if (targetPattern.test(line)) {
14521
+ // Get context (line before and after if available)
14522
+ const contextLines = [];
14523
+ if (i > 0) contextLines.push(lines[i - 1]);
14524
+ contextLines.push(line);
14525
+ if (i < lines.length - 1) contextLines.push(lines[i + 1]);
14526
+ const relativePath = relative(targetDir, file);
14527
+ references.push({
14528
+ target,
14529
+ file: relativePath,
14530
+ line: i + 1,
14531
+ context: contextLines.join("\n")
14532
+ });
14533
+ }
14534
+ }
14535
+ return references;
14536
+ }
14537
+ }
14538
+ /**
14539
+ * Creates and returns the default instruction analysis gateway
14540
+ */ function createInstructionAnalysisGateway() {
14541
+ return new FileSystemInstructionAnalysisGateway();
14542
+ }
14543
+
14544
+ ;// CONCATENATED MODULE: ./src/gateways/script-discovery-gateway.ts
14545
+ /**
14546
+ * Gateway for discovering scripts from package.json manifests
14547
+ */
14548
+
14549
+
14550
+
14551
+ /**
14552
+ * File system implementation of script discovery gateway
14553
+ */ class FileSystemScriptDiscoveryGateway {
14554
+ async discoverScripts(targetDir) {
14555
+ const packageJsonPath = join(targetDir, "package.json");
14556
+ if (!await fileExists(packageJsonPath)) {
14557
+ return [];
14558
+ }
14559
+ try {
14560
+ const content = await readFile(packageJsonPath, "utf-8");
14561
+ const packageJson = JSON.parse(content);
14562
+ if (!packageJson.scripts) {
14563
+ return [];
14564
+ }
14565
+ const scripts = [];
14566
+ for (const [name, command] of Object.entries(packageJson.scripts)){
14567
+ const phase = determineScriptPhase(name, command);
14568
+ const isMandatory = isScriptMandatory(phase);
14569
+ scripts.push({
14570
+ name,
14571
+ command,
14572
+ phase,
14573
+ isMandatory
14574
+ });
14575
+ }
14576
+ return scripts;
14577
+ } catch {
14578
+ // If package.json is malformed or cannot be parsed, return empty array
14579
+ return [];
14580
+ }
14581
+ }
14582
+ }
14583
+ /**
14584
+ * Creates and returns the default script discovery gateway
14585
+ */ function createScriptDiscoveryGateway() {
14586
+ return new FileSystemScriptDiscoveryGateway();
14587
+ }
14588
+
14198
14589
  ;// CONCATENATED MODULE: ./src/gateways/skill-file-gateway.ts
14199
14590
  /**
14200
14591
  * Gateway for skill file system operations.
@@ -14213,7 +14604,7 @@ async function watchConfig(options) {
14213
14604
  }
14214
14605
  async skillDirectoryExists(targetDir, skillName) {
14215
14606
  const dirPath = this.getSkillDirectoryPath(targetDir, skillName);
14216
- return fileExists(dirPath);
14607
+ return file_system_utils_fileExists(dirPath);
14217
14608
  }
14218
14609
  async ensureSkillDirectory(targetDir, skillName) {
14219
14610
  const skillDir = this.getSkillDirectoryPath(targetDir, skillName);
@@ -14236,6 +14627,160 @@ async function watchConfig(options) {
14236
14627
 
14237
14628
  // EXTERNAL MODULE: ./node_modules/yaml/dist/index.js
14238
14629
  var dist = __webpack_require__(1198);
14630
+ ;// CONCATENATED MODULE: ./src/gateways/tool-discovery-gateway.ts
14631
+ /**
14632
+ * Gateway for discovering CLI tools and commands from GitHub Actions workflows
14633
+ */
14634
+
14635
+
14636
+
14637
+
14638
+ /**
14639
+ * File system implementation of tool discovery gateway
14640
+ */ class FileSystemToolDiscoveryGateway {
14641
+ async discoverTools(targetDir) {
14642
+ const workflowsDir = join(targetDir, ".github", "workflows");
14643
+ if (!await fileExists(workflowsDir)) {
14644
+ return [];
14645
+ }
14646
+ const files = await readdir(workflowsDir);
14647
+ const yamlFiles = files.filter((f)=>f.endsWith(".yml") || f.endsWith(".yaml"));
14648
+ const allTools = [];
14649
+ for (const file of yamlFiles){
14650
+ const filePath = join(workflowsDir, file);
14651
+ try {
14652
+ const content = await readFile(filePath, "utf-8");
14653
+ const workflow = parseYaml(content);
14654
+ const tools = this.extractToolsFromWorkflow(workflow, file);
14655
+ allTools.push(...tools);
14656
+ } catch {
14657
+ // Skip files that can't be parsed as valid YAML
14658
+ }
14659
+ }
14660
+ return this.deduplicateTools(allTools);
14661
+ }
14662
+ extractToolsFromWorkflow(workflow, sourceFile) {
14663
+ const tools = [];
14664
+ if (!workflow || typeof workflow !== "object") {
14665
+ return tools;
14666
+ }
14667
+ const jobs = workflow.jobs;
14668
+ if (!jobs || typeof jobs !== "object") {
14669
+ return tools;
14670
+ }
14671
+ for (const job of Object.values(jobs)){
14672
+ if (!job || typeof job !== "object") {
14673
+ continue;
14674
+ }
14675
+ const steps = job.steps;
14676
+ if (!Array.isArray(steps)) {
14677
+ continue;
14678
+ }
14679
+ for (const step of steps){
14680
+ if (!step || typeof step !== "object") {
14681
+ continue;
14682
+ }
14683
+ const stepObj = step;
14684
+ const run = stepObj.run;
14685
+ if (typeof run === "string") {
14686
+ // Extract tools from run commands
14687
+ const extractedTools = this.extractToolsFromRunCommand(run, sourceFile);
14688
+ tools.push(...extractedTools);
14689
+ }
14690
+ }
14691
+ }
14692
+ return tools;
14693
+ }
14694
+ extractToolsFromRunCommand(runCommand, sourceFile) {
14695
+ const tools = [];
14696
+ // Split by newlines and pipe tokens with surrounding whitespace to handle
14697
+ // multi-line and piped commands without breaking on constructs like "cmd || true"
14698
+ const commands = runCommand.split(/\n|\s\|\s/).map((c)=>c.trim()).filter((c)=>c.length > 0 && !c.startsWith("#"));
14699
+ for (const command of commands){
14700
+ // Extract the base command (first word)
14701
+ const parts = command.split(/\s+/);
14702
+ const baseCommand = parts[0];
14703
+ // Skip shell built-ins and common utilities
14704
+ if (this.isShellBuiltin(baseCommand)) {
14705
+ continue;
14706
+ }
14707
+ // Determine tool name and full command
14708
+ let toolName;
14709
+ const fullCommand = command;
14710
+ // Handle special cases like "npm run", "mise run", etc.
14711
+ if (parts.length >= 2 && (baseCommand === "npm" || baseCommand === "mise") && parts[1] === "run") {
14712
+ if (parts.length >= 3 && parts[2]) {
14713
+ // "npm run test" -> name: "npm run test"
14714
+ toolName = parts.slice(0, 3).join(" ");
14715
+ } else {
14716
+ // Handle commands like "npm run" without a script name
14717
+ toolName = parts.slice(0, 2).join(" ");
14718
+ }
14719
+ } else if (parts.length >= 2) {
14720
+ // For most tools, include the subcommand for better specificity
14721
+ // e.g., "npm test", "npx biome", "pnpm lint"
14722
+ toolName = parts.slice(0, 2).join(" ");
14723
+ } else {
14724
+ // Fallback to the base command if no subcommand is present
14725
+ toolName = baseCommand;
14726
+ }
14727
+ const phase = this.determineToolPhase(toolName, fullCommand);
14728
+ const isMandatory = isScriptMandatory(phase);
14729
+ tools.push({
14730
+ name: toolName,
14731
+ fullCommand,
14732
+ phase,
14733
+ isMandatory,
14734
+ sourceWorkflow: sourceFile
14735
+ });
14736
+ }
14737
+ return tools;
14738
+ }
14739
+ isShellBuiltin(command) {
14740
+ const builtins = [
14741
+ "cd",
14742
+ "echo",
14743
+ "mkdir",
14744
+ "rm",
14745
+ "cp",
14746
+ "mv",
14747
+ "test",
14748
+ "[",
14749
+ "if",
14750
+ "then",
14751
+ "else",
14752
+ "fi",
14753
+ "for",
14754
+ "while",
14755
+ "do",
14756
+ "done",
14757
+ "case",
14758
+ "esac"
14759
+ ];
14760
+ return builtins.includes(command);
14761
+ }
14762
+ determineToolPhase(toolName, fullCommand) {
14763
+ // Use the same logic as script phase determination
14764
+ return determineScriptPhase(toolName, fullCommand);
14765
+ }
14766
+ deduplicateTools(tools) {
14767
+ const seen = new Map();
14768
+ for (const tool of tools){
14769
+ // Use full command as key for deduplication
14770
+ const key = tool.fullCommand;
14771
+ if (!seen.has(key)) {
14772
+ seen.set(key, tool);
14773
+ }
14774
+ }
14775
+ return Array.from(seen.values());
14776
+ }
14777
+ }
14778
+ /**
14779
+ * Creates and returns the default tool discovery gateway
14780
+ */ function createToolDiscoveryGateway() {
14781
+ return new FileSystemToolDiscoveryGateway();
14782
+ }
14783
+
14239
14784
  ;// CONCATENATED MODULE: ./src/use-cases/setup-step-discovery.ts
14240
14785
  /**
14241
14786
  * Use case for discovering setup steps in workflows.
@@ -14459,7 +15004,7 @@ var dist = __webpack_require__(1198);
14459
15004
  const workflowsDir = (0,external_node_path_.join)(targetDir, ".github", "workflows");
14460
15005
  for (const filename of COPILOT_SETUP_WORKFLOW_FILENAMES){
14461
15006
  const workflowPath = (0,external_node_path_.join)(workflowsDir, filename);
14462
- if (await fileExists(workflowPath)) {
15007
+ if (await file_system_utils_fileExists(workflowPath)) {
14463
15008
  return workflowPath;
14464
15009
  }
14465
15010
  }
@@ -14467,7 +15012,7 @@ var dist = __webpack_require__(1198);
14467
15012
  }
14468
15013
  async parseWorkflowsForSetupActions(targetDir) {
14469
15014
  const workflowsDir = (0,external_node_path_.join)(targetDir, ".github", "workflows");
14470
- if (!await fileExists(workflowsDir)) {
15015
+ if (!await file_system_utils_fileExists(workflowsDir)) {
14471
15016
  return [];
14472
15017
  }
14473
15018
  const files = await (0,promises_.readdir)(workflowsDir);
@@ -14534,11 +15079,14 @@ var dist = __webpack_require__(1198);
14534
15079
 
14535
15080
 
14536
15081
 
15082
+
15083
+
15084
+
14537
15085
  ;// CONCATENATED MODULE: ./src/use-cases/candidate-builder.ts
14538
15086
  /**
14539
15087
  * Use case for building setup step candidates from environment detection.
14540
15088
  * This module handles the logic of determining which GitHub Actions
14541
- * setup steps should be added based on detected version files.
15089
+ * setup steps should be added based on detected version files and package managers.
14542
15090
  */
14543
15091
 
14544
15092
  /**
@@ -14563,7 +15111,12 @@ var dist = __webpack_require__(1198);
14563
15111
  return candidates;
14564
15112
  }
14565
15113
  // Otherwise, add individual setup actions for each version file
14566
- return buildCandidatesFromVersionFiles(environment.versionFiles, versionTypeToAction, versionFileConfigKeys, versionGateway);
15114
+ const setupCandidates = await buildCandidatesFromVersionFiles(environment.versionFiles, versionTypeToAction, versionFileConfigKeys, versionGateway);
15115
+ candidates.push(...setupCandidates);
15116
+ // Add install steps for detected package managers
15117
+ const installCandidates = buildInstallCandidatesFromPackageManagers(environment.packageManagers, loadedConfig);
15118
+ candidates.push(...installCandidates);
15119
+ return candidates;
14567
15120
  }
14568
15121
  /**
14569
15122
  * Builds setup step candidates from individual version files
@@ -14598,6 +15151,59 @@ var dist = __webpack_require__(1198);
14598
15151
  }
14599
15152
  return candidates;
14600
15153
  }
15154
+ /**
15155
+ * Builds install step candidates from detected package managers
15156
+ * @param packageManagers Array of detected package managers
15157
+ * @param config Configuration for package manager mappings
15158
+ * @returns Array of install step candidates
15159
+ */ function buildInstallCandidatesFromPackageManagers(packageManagers, config) {
15160
+ const candidates = [];
15161
+ const addedTypes = new Set();
15162
+ for (const pm of packageManagers){
15163
+ // Skip if we've already added this package manager type
15164
+ if (addedTypes.has(pm.type)) {
15165
+ continue;
15166
+ }
15167
+ addedTypes.add(pm.type);
15168
+ // Find the config for this package manager
15169
+ const pmConfig = config.packageManagers.find((c)=>c.type === pm.type);
15170
+ if (!pmConfig) {
15171
+ continue;
15172
+ }
15173
+ // Determine a descriptive name for the install step
15174
+ const stepName = getInstallStepName(pm.type);
15175
+ // Create install step candidate
15176
+ // Note: Empty action string indicates this is a run step (uses 'run' field instead of 'uses')
15177
+ // This is checked in workflow-generator.ts buildStepFromCandidate()
15178
+ candidates.push({
15179
+ action: "",
15180
+ source: "version-file",
15181
+ name: stepName,
15182
+ run: pmConfig.installCommand
15183
+ });
15184
+ }
15185
+ return candidates;
15186
+ }
15187
+ /**
15188
+ * Gets a descriptive name for an install step based on package manager type
15189
+ */ function getInstallStepName(packageManagerType) {
15190
+ const names = {
15191
+ npm: "Install Node.js dependencies",
15192
+ yarn: "Install Node.js dependencies",
15193
+ pnpm: "Install Node.js dependencies",
15194
+ pip: "Install Python dependencies",
15195
+ pipenv: "Install Python dependencies",
15196
+ poetry: "Install Python dependencies",
15197
+ bundler: "Install Ruby dependencies",
15198
+ cargo: "Build Rust project",
15199
+ composer: "Install PHP dependencies",
15200
+ maven: "Install Java dependencies",
15201
+ gradle: "Build Gradle project",
15202
+ gomod: "Download Go dependencies",
15203
+ pub: "Install Dart dependencies"
15204
+ };
15205
+ return names[packageManagerType] || "Install dependencies";
15206
+ }
14601
15207
 
14602
15208
  ;// CONCATENATED MODULE: ./node_modules/@github-actions-workflow-ts/lib/dist/esm/workflow/index.js
14603
15209
  /**
@@ -15146,11 +15752,22 @@ Example resolved format: actions/setup-node@1a2b3c4d5e6f # v4.0.0`;
15146
15752
  * @param options Optional conversion options for placeholders and resolved versions
15147
15753
  * @returns A typed Step object
15148
15754
  */ function buildStepFromCandidate(candidate, options) {
15755
+ // Handle run steps (install commands)
15756
+ // Run steps have a 'run' field and no action (or empty action string)
15757
+ if (candidate.run && !candidate.action) {
15758
+ const stepProps = {
15759
+ name: candidate.name || "Run command",
15760
+ run: candidate.run
15761
+ };
15762
+ // Type assertion is safe here - Step constructor accepts both 'uses' and 'run' at runtime
15763
+ return new Step(stepProps);
15764
+ }
15765
+ // Handle action steps (uses)
15149
15766
  const version = getVersionForAction(candidate.action, candidate.version, options);
15150
15767
  const usesValue = buildUsesValue(candidate.action, version, options);
15151
15768
  const withConfig = candidate.config && Object.keys(candidate.config).length > 0 ? candidate.config : undefined;
15152
15769
  const stepProps = {
15153
- name: generateStepName(candidate.action),
15770
+ name: candidate.name || generateStepName(candidate.action),
15154
15771
  uses: usesValue,
15155
15772
  with: withConfig
15156
15773
  };
@@ -15327,7 +15944,7 @@ const copilotSetupArgs = {};
15327
15944
  }
15328
15945
  // Step 2: Parse existing workflows for setup actions
15329
15946
  const workflowsDir = (0,external_node_path_.join)(targetDir, ".github", "workflows");
15330
- const workflowsDirExists = await fileExists(workflowsDir);
15947
+ const workflowsDirExists = await file_system_utils_fileExists(workflowsDir);
15331
15948
  const workflowCandidates = workflowsDirExists ? await workflowGateway.parseWorkflowsForSetupActions(targetDir) : [];
15332
15949
  if (workflowCandidates.length > 0) {
15333
15950
  const actionNames = workflowCandidates.map((c)=>c.action).join(", ");
@@ -30219,7 +30836,7 @@ var Eta = class extends Eta$1 {
30219
30836
  for (const node of structure.nodes){
30220
30837
  const fullPath = (0,external_node_path_.join)(targetDir, node.path);
30221
30838
  // Skip if already exists to preserve existing files/directories
30222
- if (await fileExists(fullPath)) {
30839
+ if (await file_system_utils_fileExists(fullPath)) {
30223
30840
  consola.debug(`Skipping existing: ${fullPath}`);
30224
30841
  continue;
30225
30842
  }