@lousy-agents/cli 2.4.0 → 2.5.1

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/README.md CHANGED
@@ -126,8 +126,10 @@ npx @lousy-agents/cli copilot-setup
126
126
  |---------|--------|
127
127
  | Scaffolding for webapps | ✅ Complete |
128
128
  | Scaffolding for REST APIs | ✅ Complete |
129
- | Scaffolding for CLI | Not Started |
129
+ | Scaffolding for CLI | Complete |
130
130
  | Scaffolding for GraphQL APIs | Not Started |
131
+ | Copilot setup package manager install steps | ✅ Complete |
132
+ | Copilot agent and skill scaffolding | ✅ Complete |
131
133
  | MCP server package | ✅ Complete |
132
134
 
133
135
  ## Documentation
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");
@@ -14291,14 +14291,14 @@ async function watchConfig(options) {
14291
14291
  }
14292
14292
  async detectMise(targetDir) {
14293
14293
  const miseTomlPath = (0,external_node_path_.join)(targetDir, "mise.toml");
14294
- return fileExists(miseTomlPath);
14294
+ return file_system_utils_fileExists(miseTomlPath);
14295
14295
  }
14296
14296
  async detectVersionFiles(targetDir, config) {
14297
14297
  const filenameToType = getVersionFilenameToTypeMap(config);
14298
14298
  const versionFiles = [];
14299
14299
  for (const fileConfig of config.versionFiles){
14300
14300
  const filePath = (0,external_node_path_.join)(targetDir, fileConfig.filename);
14301
- if (await fileExists(filePath)) {
14301
+ if (await file_system_utils_fileExists(filePath)) {
14302
14302
  const version = await readVersionFileContent(filePath);
14303
14303
  versionFiles.push({
14304
14304
  type: filenameToType[fileConfig.filename],
@@ -14333,7 +14333,7 @@ async function watchConfig(options) {
14333
14333
  }
14334
14334
  async detectNodePackageManager(targetDir, nodePackageManagers) {
14335
14335
  const packageJsonPath = (0,external_node_path_.join)(targetDir, "package.json");
14336
- if (!await fileExists(packageJsonPath)) {
14336
+ if (!await file_system_utils_fileExists(packageJsonPath)) {
14337
14337
  return null;
14338
14338
  }
14339
14339
  // Priority order for Node.js package managers: npm > yarn > pnpm
@@ -14348,7 +14348,7 @@ async function watchConfig(options) {
14348
14348
  continue;
14349
14349
  }
14350
14350
  const lockfilePath = (0,external_node_path_.join)(targetDir, pmConfig.lockfile);
14351
- if (await fileExists(lockfilePath)) {
14351
+ if (await file_system_utils_fileExists(lockfilePath)) {
14352
14352
  return {
14353
14353
  type: pmConfig.type,
14354
14354
  filename: pmConfig.manifestFile,
@@ -14375,9 +14375,9 @@ async function watchConfig(options) {
14375
14375
  continue;
14376
14376
  }
14377
14377
  const manifestPath = (0,external_node_path_.join)(targetDir, pmConfig.manifestFile);
14378
- if (await fileExists(manifestPath)) {
14378
+ if (await file_system_utils_fileExists(manifestPath)) {
14379
14379
  const lockfilePath = pmConfig.lockfile ? (0,external_node_path_.join)(targetDir, pmConfig.lockfile) : undefined;
14380
- const hasLockfile = lockfilePath ? await fileExists(lockfilePath) : false;
14380
+ const hasLockfile = lockfilePath ? await file_system_utils_fileExists(lockfilePath) : false;
14381
14381
  // Skip if lockfile is required but not present
14382
14382
  if (pmConfig.requiresLockfile && !hasLockfile) {
14383
14383
  continue;
@@ -14395,9 +14395,9 @@ async function watchConfig(options) {
14395
14395
  const packageManagers = [];
14396
14396
  for (const pmConfig of otherPackageManagers){
14397
14397
  const manifestPath = (0,external_node_path_.join)(targetDir, pmConfig.manifestFile);
14398
- if (await fileExists(manifestPath)) {
14398
+ if (await file_system_utils_fileExists(manifestPath)) {
14399
14399
  const lockfilePath = pmConfig.lockfile ? (0,external_node_path_.join)(targetDir, pmConfig.lockfile) : undefined;
14400
- const hasLockfile = lockfilePath ? await fileExists(lockfilePath) : false;
14400
+ const hasLockfile = lockfilePath ? await file_system_utils_fileExists(lockfilePath) : false;
14401
14401
  // Skip if lockfile is required but not present
14402
14402
  if (pmConfig.requiresLockfile && !hasLockfile) {
14403
14403
  continue;
@@ -14418,6 +14418,174 @@ async function watchConfig(options) {
14418
14418
  return new FileSystemEnvironmentGateway();
14419
14419
  }
14420
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
+
14421
14589
  ;// CONCATENATED MODULE: ./src/gateways/skill-file-gateway.ts
14422
14590
  /**
14423
14591
  * Gateway for skill file system operations.
@@ -14436,7 +14604,7 @@ async function watchConfig(options) {
14436
14604
  }
14437
14605
  async skillDirectoryExists(targetDir, skillName) {
14438
14606
  const dirPath = this.getSkillDirectoryPath(targetDir, skillName);
14439
- return fileExists(dirPath);
14607
+ return file_system_utils_fileExists(dirPath);
14440
14608
  }
14441
14609
  async ensureSkillDirectory(targetDir, skillName) {
14442
14610
  const skillDir = this.getSkillDirectoryPath(targetDir, skillName);
@@ -14459,6 +14627,160 @@ async function watchConfig(options) {
14459
14627
 
14460
14628
  // EXTERNAL MODULE: ./node_modules/yaml/dist/index.js
14461
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
+
14462
14784
  ;// CONCATENATED MODULE: ./src/use-cases/setup-step-discovery.ts
14463
14785
  /**
14464
14786
  * Use case for discovering setup steps in workflows.
@@ -14682,7 +15004,7 @@ var dist = __webpack_require__(1198);
14682
15004
  const workflowsDir = (0,external_node_path_.join)(targetDir, ".github", "workflows");
14683
15005
  for (const filename of COPILOT_SETUP_WORKFLOW_FILENAMES){
14684
15006
  const workflowPath = (0,external_node_path_.join)(workflowsDir, filename);
14685
- if (await fileExists(workflowPath)) {
15007
+ if (await file_system_utils_fileExists(workflowPath)) {
14686
15008
  return workflowPath;
14687
15009
  }
14688
15010
  }
@@ -14690,7 +15012,7 @@ var dist = __webpack_require__(1198);
14690
15012
  }
14691
15013
  async parseWorkflowsForSetupActions(targetDir) {
14692
15014
  const workflowsDir = (0,external_node_path_.join)(targetDir, ".github", "workflows");
14693
- if (!await fileExists(workflowsDir)) {
15015
+ if (!await file_system_utils_fileExists(workflowsDir)) {
14694
15016
  return [];
14695
15017
  }
14696
15018
  const files = await (0,promises_.readdir)(workflowsDir);
@@ -14757,6 +15079,9 @@ var dist = __webpack_require__(1198);
14757
15079
 
14758
15080
 
14759
15081
 
15082
+
15083
+
15084
+
14760
15085
  ;// CONCATENATED MODULE: ./src/use-cases/candidate-builder.ts
14761
15086
  /**
14762
15087
  * Use case for building setup step candidates from environment detection.
@@ -15619,7 +15944,7 @@ const copilotSetupArgs = {};
15619
15944
  }
15620
15945
  // Step 2: Parse existing workflows for setup actions
15621
15946
  const workflowsDir = (0,external_node_path_.join)(targetDir, ".github", "workflows");
15622
- const workflowsDirExists = await fileExists(workflowsDir);
15947
+ const workflowsDirExists = await file_system_utils_fileExists(workflowsDir);
15623
15948
  const workflowCandidates = workflowsDirExists ? await workflowGateway.parseWorkflowsForSetupActions(targetDir) : [];
15624
15949
  if (workflowCandidates.length > 0) {
15625
15950
  const actionNames = workflowCandidates.map((c)=>c.action).join(", ");
@@ -30511,7 +30836,7 @@ var Eta = class extends Eta$1 {
30511
30836
  for (const node of structure.nodes){
30512
30837
  const fullPath = (0,external_node_path_.join)(targetDir, node.path);
30513
30838
  // Skip if already exists to preserve existing files/directories
30514
- if (await fileExists(fullPath)) {
30839
+ if (await file_system_utils_fileExists(fullPath)) {
30515
30840
  consola.debug(`Skipping existing: ${fullPath}`);
30516
30841
  continue;
30517
30842
  }