@lousy-agents/mcp 3.1.3 → 4.0.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.
@@ -29898,7 +29898,7 @@ function schemas_preprocess(fn, schema) {
29898
29898
  // Zod 3 compat layer
29899
29899
 
29900
29900
  /** @deprecated Use the raw string literal codes instead, e.g. "invalid_type". */
29901
- const ZodIssueCode = (/* unused pure expression or super */ null && ({
29901
+ const ZodIssueCode = {
29902
29902
  invalid_type: "invalid_type",
29903
29903
  too_big: "too_big",
29904
29904
  too_small: "too_small",
@@ -29910,7 +29910,7 @@ const ZodIssueCode = (/* unused pure expression or super */ null && ({
29910
29910
  invalid_element: "invalid_element",
29911
29911
  invalid_value: "invalid_value",
29912
29912
  custom: "custom",
29913
- }));
29913
+ };
29914
29914
 
29915
29915
  /** @deprecated Use `z.config(params)` instead. */
29916
29916
  function setErrorMap(map) {
@@ -42830,8 +42830,9 @@ var external_node_path_ = __webpack_require__(6760);
42830
42830
  /**
42831
42831
  * Shared file system utilities for gateways.
42832
42832
  */
42833
+
42833
42834
  /**
42834
- * Checks if a file or directory exists
42835
+ * Checks if a file or directory exists.
42835
42836
  */ async function file_system_utils_fileExists(path) {
42836
42837
  try {
42837
42838
  await (0,promises_.access)(path);
@@ -42840,6 +42841,65 @@ var external_node_path_ = __webpack_require__(6760);
42840
42841
  return false;
42841
42842
  }
42842
42843
  }
42844
+ function isPathWithinRoot(rootPath, candidatePath) {
42845
+ return candidatePath === rootPath || candidatePath.startsWith(`${rootPath}${external_node_path_.sep}`);
42846
+ }
42847
+ /**
42848
+ * Resolves a relative path under targetDir and rejects traversal outside the root.
42849
+ */ async function resolvePathWithinRoot(targetDir, relativePath) {
42850
+ if (!relativePath) {
42851
+ throw new Error("Path must not be empty");
42852
+ }
42853
+ const rootPath = await (0,promises_.realpath)(targetDir);
42854
+ const resolvedPath = (0,external_node_path_.resolve)(rootPath, relativePath);
42855
+ if (!isPathWithinRoot(rootPath, resolvedPath)) {
42856
+ throw new Error(`Resolved path is outside target directory: ${relativePath}`);
42857
+ }
42858
+ return resolvedPath;
42859
+ }
42860
+ /**
42861
+ * Ensures existing path segments under targetDir are not symbolic links.
42862
+ */ async function assertPathHasNoSymbolicLinks(targetDir, absolutePath) {
42863
+ const rootPath = await (0,promises_.realpath)(targetDir);
42864
+ if (!isPathWithinRoot(rootPath, absolutePath)) {
42865
+ throw new Error(`Resolved path is outside target directory: ${absolutePath}`);
42866
+ }
42867
+ const relativePath = (0,external_node_path_.relative)(rootPath, absolutePath);
42868
+ if (!relativePath || relativePath === ".") {
42869
+ return;
42870
+ }
42871
+ const segments = relativePath.split(external_node_path_.sep);
42872
+ let currentPath = rootPath;
42873
+ for (const segment of segments){
42874
+ currentPath = (0,external_node_path_.join)(currentPath, segment);
42875
+ try {
42876
+ const stats = await (0,promises_.lstat)(currentPath);
42877
+ if (stats.isSymbolicLink()) {
42878
+ throw new Error(`Path contains symbolic link: ${currentPath}`);
42879
+ }
42880
+ } catch (error) {
42881
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") {
42882
+ return;
42883
+ }
42884
+ throw error;
42885
+ }
42886
+ }
42887
+ }
42888
+ /**
42889
+ * Resolves a relative path under targetDir and validates it does not pass through symlinks.
42890
+ */ async function file_system_utils_resolveSafePath(targetDir, relativePath) {
42891
+ const resolvedPath = await resolvePathWithinRoot(targetDir, relativePath);
42892
+ await assertPathHasNoSymbolicLinks(targetDir, resolvedPath);
42893
+ return resolvedPath;
42894
+ }
42895
+ /**
42896
+ * Enforces a maximum file size before reading/parsing.
42897
+ */ async function assertFileSizeWithinLimit(filePath, maxBytes, context) {
42898
+ const fileStats = await (0,promises_.stat)(filePath);
42899
+ if (fileStats.size > maxBytes) {
42900
+ throw new Error(`${context} exceeds size limit (${fileStats.size} bytes > ${maxBytes} bytes)`);
42901
+ }
42902
+ }
42843
42903
 
42844
42904
  ;// CONCATENATED MODULE: ../core/src/gateways/agent-file-gateway.ts
42845
42905
  /**
@@ -42859,13 +42919,13 @@ var external_node_path_ = __webpack_require__(6760);
42859
42919
  return fileExists(filePath);
42860
42920
  }
42861
42921
  async ensureAgentsDirectory(targetDir) {
42862
- const agentsDir = join(targetDir, ".github", "agents");
42922
+ const agentsDir = await resolveSafePath(targetDir, ".github/agents");
42863
42923
  await mkdir(agentsDir, {
42864
42924
  recursive: true
42865
42925
  });
42866
42926
  }
42867
42927
  async writeAgentFile(targetDir, agentName, content) {
42868
- const filePath = this.getAgentFilePath(targetDir, agentName);
42928
+ const filePath = await resolveSafePath(targetDir, `.github/agents/${agentName}.md`);
42869
42929
  await writeFile(filePath, content, {
42870
42930
  encoding: "utf-8"
42871
42931
  });
@@ -42883,12 +42943,11 @@ var external_node_path_ = __webpack_require__(6760);
42883
42943
  * Handles reading and writing .claude/settings.json and CLAUDE.md files.
42884
42944
  */
42885
42945
 
42886
-
42887
42946
  /**
42888
42947
  * File system implementation of ClaudeFileGateway
42889
42948
  */ class FileSystemClaudeFileGateway {
42890
42949
  async readSettings(targetDir) {
42891
- const settingsPath = (0,external_node_path_.join)(targetDir, ".claude", "settings.json");
42950
+ const settingsPath = await file_system_utils_resolveSafePath(targetDir, ".claude/settings.json");
42892
42951
  if (!await file_system_utils_fileExists(settingsPath)) {
42893
42952
  return null;
42894
42953
  }
@@ -42901,8 +42960,8 @@ var external_node_path_ = __webpack_require__(6760);
42901
42960
  }
42902
42961
  }
42903
42962
  async writeSettings(targetDir, settings) {
42904
- const claudeDir = (0,external_node_path_.join)(targetDir, ".claude");
42905
- const settingsPath = (0,external_node_path_.join)(claudeDir, "settings.json");
42963
+ const claudeDir = await file_system_utils_resolveSafePath(targetDir, ".claude");
42964
+ const settingsPath = await file_system_utils_resolveSafePath(targetDir, ".claude/settings.json");
42906
42965
  // Ensure .claude directory exists
42907
42966
  await (0,promises_.mkdir)(claudeDir, {
42908
42967
  recursive: true
@@ -42912,14 +42971,14 @@ var external_node_path_ = __webpack_require__(6760);
42912
42971
  await (0,promises_.writeFile)(settingsPath, content, "utf-8");
42913
42972
  }
42914
42973
  async readDocumentation(targetDir) {
42915
- const docPath = (0,external_node_path_.join)(targetDir, "CLAUDE.md");
42974
+ const docPath = await file_system_utils_resolveSafePath(targetDir, "CLAUDE.md");
42916
42975
  if (!await file_system_utils_fileExists(docPath)) {
42917
42976
  return null;
42918
42977
  }
42919
42978
  return (0,promises_.readFile)(docPath, "utf-8");
42920
42979
  }
42921
42980
  async writeDocumentation(targetDir, content) {
42922
- const docPath = (0,external_node_path_.join)(targetDir, "CLAUDE.md");
42981
+ const docPath = await file_system_utils_resolveSafePath(targetDir, "CLAUDE.md");
42923
42982
  // Ensure content has trailing newline
42924
42983
  const normalizedContent = content.endsWith("\n") ? content : `${content}\n`;
42925
42984
  await (0,promises_.writeFile)(docPath, normalizedContent, "utf-8");
@@ -45169,13 +45228,27 @@ async function watchConfig(options) {
45169
45228
 
45170
45229
  //#endregion
45171
45230
 
45172
- ;// CONCATENATED MODULE: ../core/src/lib/copilot-setup-config.ts
45173
- /**
45174
- * Configuration for the copilot-setup command.
45175
- * Uses c12 for configuration loading, allowing runtime customization.
45176
- */
45231
+ ;// CONCATENATED MODULE: ../core/src/entities/copilot-setup-config.ts
45232
+ /**
45233
+ * Canonical install command per package manager type.
45234
+ * Commands are intentionally fixed to a safe allowlist.
45235
+ */ const PACKAGE_MANAGER_INSTALL_COMMANDS = {
45236
+ npm: "npm ci",
45237
+ yarn: "yarn install --frozen-lockfile",
45238
+ pnpm: "pnpm install --frozen-lockfile",
45239
+ pip: "pip install -r requirements.txt",
45240
+ pipenv: "pipenv install --deploy",
45241
+ poetry: "poetry install --no-root",
45242
+ bundler: "bundle install",
45243
+ cargo: "cargo build",
45244
+ composer: "composer install",
45245
+ maven: "mvn install -DskipTests",
45246
+ gradle: "gradle build -x test",
45247
+ gomod: "go mod download",
45248
+ pub: "dart pub get"
45249
+ };
45177
45250
  /**
45178
- * Default version file mappings
45251
+ * Default version file mappings.
45179
45252
  */ const DEFAULT_VERSION_FILES = [
45180
45253
  {
45181
45254
  filename: ".nvmrc",
@@ -45203,7 +45276,7 @@ async function watchConfig(options) {
45203
45276
  }
45204
45277
  ];
45205
45278
  /**
45206
- * Default setup action configurations
45279
+ * Default setup action configurations.
45207
45280
  */ const DEFAULT_SETUP_ACTIONS = [
45208
45281
  {
45209
45282
  action: "actions/setup-node",
@@ -45232,7 +45305,7 @@ async function watchConfig(options) {
45232
45305
  }
45233
45306
  ];
45234
45307
  /**
45235
- * Default setup action patterns to detect in workflows
45308
+ * Default setup action patterns to detect in workflows.
45236
45309
  */ const DEFAULT_SETUP_ACTION_PATTERNS = [
45237
45310
  "actions/setup-node",
45238
45311
  "actions/setup-python",
@@ -45242,112 +45315,212 @@ async function watchConfig(options) {
45242
45315
  "jdx/mise-action"
45243
45316
  ];
45244
45317
  /**
45245
- * Default package manager mappings
45246
- * Based on Dependabot supported ecosystems
45318
+ * Default package manager mappings based on common ecosystems.
45247
45319
  */ const DEFAULT_PACKAGE_MANAGERS = [
45248
- // Node.js package managers
45249
45320
  {
45250
45321
  type: "npm",
45251
45322
  manifestFile: "package.json",
45252
45323
  lockfile: "package-lock.json",
45253
- installCommand: "npm ci"
45324
+ installCommand: PACKAGE_MANAGER_INSTALL_COMMANDS.npm
45254
45325
  },
45255
45326
  {
45256
45327
  type: "yarn",
45257
45328
  manifestFile: "package.json",
45258
45329
  lockfile: "yarn.lock",
45259
- installCommand: "yarn install --frozen-lockfile"
45330
+ installCommand: PACKAGE_MANAGER_INSTALL_COMMANDS.yarn
45260
45331
  },
45261
45332
  {
45262
45333
  type: "pnpm",
45263
45334
  manifestFile: "package.json",
45264
45335
  lockfile: "pnpm-lock.yaml",
45265
- installCommand: "pnpm install --frozen-lockfile"
45336
+ installCommand: PACKAGE_MANAGER_INSTALL_COMMANDS.pnpm
45266
45337
  },
45267
- // Python package managers
45268
45338
  {
45269
45339
  type: "pip",
45270
45340
  manifestFile: "requirements.txt",
45271
- installCommand: "pip install -r requirements.txt"
45341
+ installCommand: PACKAGE_MANAGER_INSTALL_COMMANDS.pip
45272
45342
  },
45273
45343
  {
45274
45344
  type: "pipenv",
45275
45345
  manifestFile: "Pipfile",
45276
45346
  lockfile: "Pipfile.lock",
45277
- installCommand: "pipenv install --deploy"
45347
+ installCommand: PACKAGE_MANAGER_INSTALL_COMMANDS.pipenv
45278
45348
  },
45279
45349
  {
45280
45350
  type: "poetry",
45281
45351
  manifestFile: "pyproject.toml",
45282
45352
  lockfile: "poetry.lock",
45283
45353
  requiresLockfile: true,
45284
- installCommand: "poetry install --no-root"
45354
+ installCommand: PACKAGE_MANAGER_INSTALL_COMMANDS.poetry
45285
45355
  },
45286
- // Ruby
45287
45356
  {
45288
45357
  type: "bundler",
45289
45358
  manifestFile: "Gemfile",
45290
45359
  lockfile: "Gemfile.lock",
45291
- installCommand: "bundle install"
45360
+ installCommand: PACKAGE_MANAGER_INSTALL_COMMANDS.bundler
45292
45361
  },
45293
- // Rust
45294
45362
  {
45295
45363
  type: "cargo",
45296
45364
  manifestFile: "Cargo.toml",
45297
45365
  lockfile: "Cargo.lock",
45298
- installCommand: "cargo build"
45366
+ installCommand: PACKAGE_MANAGER_INSTALL_COMMANDS.cargo
45299
45367
  },
45300
- // PHP
45301
45368
  {
45302
45369
  type: "composer",
45303
45370
  manifestFile: "composer.json",
45304
45371
  lockfile: "composer.lock",
45305
- installCommand: "composer install"
45372
+ installCommand: PACKAGE_MANAGER_INSTALL_COMMANDS.composer
45306
45373
  },
45307
- // Java
45308
45374
  {
45309
45375
  type: "maven",
45310
45376
  manifestFile: "pom.xml",
45311
- installCommand: "mvn install -DskipTests"
45377
+ installCommand: PACKAGE_MANAGER_INSTALL_COMMANDS.maven
45312
45378
  },
45313
45379
  {
45314
45380
  type: "gradle",
45315
45381
  manifestFile: "build.gradle",
45316
- installCommand: "gradle build -x test"
45382
+ installCommand: PACKAGE_MANAGER_INSTALL_COMMANDS.gradle
45317
45383
  },
45318
- // Go
45319
45384
  {
45320
45385
  type: "gomod",
45321
45386
  manifestFile: "go.mod",
45322
45387
  lockfile: "go.sum",
45323
- installCommand: "go mod download"
45388
+ installCommand: PACKAGE_MANAGER_INSTALL_COMMANDS.gomod
45324
45389
  },
45325
- // Dart/Flutter
45326
45390
  {
45327
45391
  type: "pub",
45328
45392
  manifestFile: "pubspec.yaml",
45329
45393
  lockfile: "pubspec.lock",
45330
- installCommand: "dart pub get"
45394
+ installCommand: PACKAGE_MANAGER_INSTALL_COMMANDS.pub
45331
45395
  }
45332
45396
  ];
45333
45397
  /**
45334
- * Default copilot-setup configuration
45335
- */ const DEFAULT_CONFIG = {
45398
+ * Default copilot-setup configuration.
45399
+ */ const DEFAULT_COPILOT_SETUP_CONFIG = {
45336
45400
  versionFiles: DEFAULT_VERSION_FILES,
45337
45401
  setupActions: DEFAULT_SETUP_ACTIONS,
45338
45402
  setupActionPatterns: DEFAULT_SETUP_ACTION_PATTERNS,
45339
45403
  packageManagers: DEFAULT_PACKAGE_MANAGERS
45340
45404
  };
45405
+
45406
+ ;// CONCATENATED MODULE: ../core/src/lib/copilot-setup-config.ts
45407
+ /**
45408
+ * Configuration for the copilot-setup command.
45409
+ * Uses c12 for configuration loading, allowing runtime customization.
45410
+ */
45411
+
45412
+
45413
+ const SAFE_BASENAME_PATTERN = /^[A-Za-z0-9._-]+$/;
45414
+ const SAFE_ACTION_PATTERN = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/;
45415
+ const SAFE_VERSION_KEY_PATTERN = /^[a-z][a-z0-9-]*$/;
45416
+ const MAX_FILENAME_LENGTH = 255;
45417
+ const VERSION_FILE_TYPES = [
45418
+ "node",
45419
+ "python",
45420
+ "java",
45421
+ "ruby",
45422
+ "go"
45423
+ ];
45424
+ const PACKAGE_MANAGER_TYPES = [
45425
+ "npm",
45426
+ "yarn",
45427
+ "pnpm",
45428
+ "pip",
45429
+ "pipenv",
45430
+ "poetry",
45431
+ "bundler",
45432
+ "cargo",
45433
+ "composer",
45434
+ "maven",
45435
+ "gradle",
45436
+ "gomod",
45437
+ "pub"
45438
+ ];
45439
+ function isSafeBasename(value) {
45440
+ return value.length > 0 && value.length <= MAX_FILENAME_LENGTH && SAFE_BASENAME_PATTERN.test(value) && !value.includes("..");
45441
+ }
45442
+ const VersionFileMappingSchema = schemas_object({
45443
+ filename: schemas_string(),
45444
+ type: schemas_enum(VERSION_FILE_TYPES)
45445
+ }).strict().superRefine((value, context)=>{
45446
+ if (!isSafeBasename(value.filename)) {
45447
+ context.addIssue({
45448
+ code: ZodIssueCode.custom,
45449
+ path: [
45450
+ "filename"
45451
+ ],
45452
+ message: "versionFiles.filename must be a safe basename without traversal or directory separators"
45453
+ });
45454
+ }
45455
+ });
45456
+ const SetupActionConfigSchema = schemas_object({
45457
+ action: schemas_string().regex(SAFE_ACTION_PATTERN, {
45458
+ message: "setupActions.action must be in owner/repo format"
45459
+ }),
45460
+ type: schemas_enum(VERSION_FILE_TYPES),
45461
+ versionFileKey: schemas_string().regex(SAFE_VERSION_KEY_PATTERN, {
45462
+ message: "setupActions.versionFileKey must use lowercase kebab-case"
45463
+ })
45464
+ }).strict();
45465
+ const PackageManagerMappingSchema = schemas_object({
45466
+ type: schemas_enum(PACKAGE_MANAGER_TYPES),
45467
+ manifestFile: schemas_string(),
45468
+ lockfile: schemas_string().optional(),
45469
+ requiresLockfile: schemas_boolean().optional(),
45470
+ installCommand: schemas_string()
45471
+ }).strict().superRefine((value, context)=>{
45472
+ if (!isSafeBasename(value.manifestFile)) {
45473
+ context.addIssue({
45474
+ code: ZodIssueCode.custom,
45475
+ path: [
45476
+ "manifestFile"
45477
+ ],
45478
+ message: "packageManagers.manifestFile must be a safe basename without traversal or directory separators"
45479
+ });
45480
+ }
45481
+ if (value.lockfile && !isSafeBasename(value.lockfile)) {
45482
+ context.addIssue({
45483
+ code: ZodIssueCode.custom,
45484
+ path: [
45485
+ "lockfile"
45486
+ ],
45487
+ message: "packageManagers.lockfile must be a safe basename without traversal or directory separators"
45488
+ });
45489
+ }
45490
+ const expectedInstallCommand = PACKAGE_MANAGER_INSTALL_COMMANDS[value.type];
45491
+ if (value.installCommand !== expectedInstallCommand) {
45492
+ context.addIssue({
45493
+ code: ZodIssueCode.custom,
45494
+ path: [
45495
+ "installCommand"
45496
+ ],
45497
+ message: `packageManagers.installCommand for ${value.type} must be '${expectedInstallCommand}'`
45498
+ });
45499
+ }
45500
+ });
45501
+ const CopilotSetupConfigSchema = schemas_object({
45502
+ versionFiles: schemas_array(VersionFileMappingSchema).min(1),
45503
+ setupActions: schemas_array(SetupActionConfigSchema).min(1),
45504
+ setupActionPatterns: schemas_array(schemas_string().regex(SAFE_ACTION_PATTERN, {
45505
+ message: "setupActionPatterns entries must be in owner/repo format"
45506
+ })).min(1),
45507
+ packageManagers: schemas_array(PackageManagerMappingSchema).min(1)
45508
+ });
45509
+ /**
45510
+ * Validates loaded copilot-setup configuration and rejects unsafe values.
45511
+ */ function validateCopilotSetupConfig(config) {
45512
+ return CopilotSetupConfigSchema.parse(config);
45513
+ }
45341
45514
  /**
45342
- * Loads the copilot-setup configuration using c12
45343
- * Falls back to defaults if no configuration is found
45515
+ * Loads the copilot-setup configuration using c12.
45516
+ * Falls back to defaults if no configuration is found.
45344
45517
  */ async function loadCopilotSetupConfig() {
45345
45518
  const { config } = await loadConfig({
45346
45519
  name: "lousy-agents",
45347
- defaults: DEFAULT_CONFIG,
45520
+ defaults: DEFAULT_COPILOT_SETUP_CONFIG,
45348
45521
  packageJson: "copilotSetup"
45349
45522
  });
45350
- return config || DEFAULT_CONFIG;
45523
+ return validateCopilotSetupConfig(config ?? DEFAULT_COPILOT_SETUP_CONFIG);
45351
45524
  }
45352
45525
  /**
45353
45526
  * Resets the cached configuration (useful for testing)
@@ -45358,8 +45531,8 @@ async function watchConfig(options) {
45358
45531
  // no in-memory cache to reset
45359
45532
  }
45360
45533
  /**
45361
- * Gets a version file type to action mapping from config
45362
- * Returns a partial record as not all types may be configured
45534
+ * Gets a version file type to action mapping from config.
45535
+ * Returns a partial record as not all types may be configured.
45363
45536
  */ function getVersionTypeToActionMap(config) {
45364
45537
  const map = {};
45365
45538
  for (const action of config.setupActions){
@@ -45368,8 +45541,8 @@ async function watchConfig(options) {
45368
45541
  return map;
45369
45542
  }
45370
45543
  /**
45371
- * Gets a version file type to config key mapping from config
45372
- * Returns a partial record as not all types may be configured
45544
+ * Gets a version file type to config key mapping from config.
45545
+ * Returns a partial record as not all types may be configured.
45373
45546
  */ function getVersionFileConfigKeyMap(config) {
45374
45547
  const map = {};
45375
45548
  for (const action of config.setupActions){
@@ -45378,7 +45551,7 @@ async function watchConfig(options) {
45378
45551
  return map;
45379
45552
  }
45380
45553
  /**
45381
- * Gets a filename to version type mapping from config
45554
+ * Gets a filename to version type mapping from config.
45382
45555
  */ function getVersionFilenameToTypeMap(config) {
45383
45556
  const map = {};
45384
45557
  for (const file of config.versionFiles){
@@ -45395,15 +45568,14 @@ async function watchConfig(options) {
45395
45568
 
45396
45569
 
45397
45570
 
45398
-
45399
- /**
45400
- * Reads the content of a version file and trims whitespace
45401
- */ async function readVersionFileContent(filePath) {
45571
+ const MAX_VERSION_FILE_BYTES = 16 * 1024;
45572
+ async function readVersionFileContent(filePath) {
45573
+ await assertFileSizeWithinLimit(filePath, MAX_VERSION_FILE_BYTES, `Version file '${filePath}'`);
45402
45574
  const content = await (0,promises_.readFile)(filePath, "utf-8");
45403
45575
  return content.trim();
45404
45576
  }
45405
45577
  /**
45406
- * File system implementation of the environment gateway
45578
+ * File system implementation of the environment gateway.
45407
45579
  */ class FileSystemEnvironmentGateway {
45408
45580
  config = null;
45409
45581
  async getConfig() {
@@ -45424,14 +45596,14 @@ async function watchConfig(options) {
45424
45596
  };
45425
45597
  }
45426
45598
  async detectMise(targetDir) {
45427
- const miseTomlPath = (0,external_node_path_.join)(targetDir, "mise.toml");
45599
+ const miseTomlPath = await file_system_utils_resolveSafePath(targetDir, "mise.toml");
45428
45600
  return file_system_utils_fileExists(miseTomlPath);
45429
45601
  }
45430
45602
  async detectVersionFiles(targetDir, config) {
45431
45603
  const filenameToType = getVersionFilenameToTypeMap(config);
45432
45604
  const versionFiles = [];
45433
45605
  for (const fileConfig of config.versionFiles){
45434
- const filePath = (0,external_node_path_.join)(targetDir, fileConfig.filename);
45606
+ const filePath = await file_system_utils_resolveSafePath(targetDir, fileConfig.filename);
45435
45607
  if (await file_system_utils_fileExists(filePath)) {
45436
45608
  const version = await readVersionFileContent(filePath);
45437
45609
  versionFiles.push({
@@ -45445,32 +45617,27 @@ async function watchConfig(options) {
45445
45617
  }
45446
45618
  async detectPackageManagers(targetDir, config) {
45447
45619
  const packageManagers = [];
45448
- // Helper to check if a package manager type is in a list
45449
45620
  const isPackageManagerType = (pm, types)=>types.includes(pm.type);
45450
45621
  const nodePackageManagers = config.packageManagers.filter((pm)=>isPackageManagerType(pm, NODE_PACKAGE_MANAGERS));
45451
45622
  const pythonPackageManagers = config.packageManagers.filter((pm)=>isPackageManagerType(pm, PYTHON_PACKAGE_MANAGERS));
45452
45623
  const otherPackageManagers = config.packageManagers.filter((pm)=>!isPackageManagerType(pm, NODE_PACKAGE_MANAGERS) && !isPackageManagerType(pm, PYTHON_PACKAGE_MANAGERS));
45453
- // Detect Node.js package manager (with prioritization)
45454
45624
  const nodePackageManager = await this.detectNodePackageManager(targetDir, nodePackageManagers);
45455
45625
  if (nodePackageManager) {
45456
45626
  packageManagers.push(nodePackageManager);
45457
45627
  }
45458
- // Detect Python package manager (with prioritization)
45459
45628
  const pythonPackageManager = await this.detectPythonPackageManager(targetDir, pythonPackageManagers);
45460
45629
  if (pythonPackageManager) {
45461
45630
  packageManagers.push(pythonPackageManager);
45462
45631
  }
45463
- // Detect other package managers
45464
45632
  const otherDetectedManagers = await this.detectOtherPackageManagers(targetDir, otherPackageManagers);
45465
45633
  packageManagers.push(...otherDetectedManagers);
45466
45634
  return packageManagers;
45467
45635
  }
45468
45636
  async detectNodePackageManager(targetDir, nodePackageManagers) {
45469
- const packageJsonPath = (0,external_node_path_.join)(targetDir, "package.json");
45637
+ const packageJsonPath = await file_system_utils_resolveSafePath(targetDir, "package.json");
45470
45638
  if (!await file_system_utils_fileExists(packageJsonPath)) {
45471
45639
  return null;
45472
45640
  }
45473
- // Priority order for Node.js package managers: npm > yarn > pnpm
45474
45641
  const lockfileOrder = [
45475
45642
  "npm",
45476
45643
  "yarn",
@@ -45481,7 +45648,7 @@ async function watchConfig(options) {
45481
45648
  if (!pmConfig?.lockfile) {
45482
45649
  continue;
45483
45650
  }
45484
- const lockfilePath = (0,external_node_path_.join)(targetDir, pmConfig.lockfile);
45651
+ const lockfilePath = await file_system_utils_resolveSafePath(targetDir, pmConfig.lockfile);
45485
45652
  if (await file_system_utils_fileExists(lockfilePath)) {
45486
45653
  return {
45487
45654
  type: pmConfig.type,
@@ -45490,7 +45657,6 @@ async function watchConfig(options) {
45490
45657
  };
45491
45658
  }
45492
45659
  }
45493
- // Default to npm if no lockfile found
45494
45660
  const npmConfig = nodePackageManagers.find((pm)=>pm.type === "npm");
45495
45661
  if (npmConfig) {
45496
45662
  return {
@@ -45502,17 +45668,15 @@ async function watchConfig(options) {
45502
45668
  return null;
45503
45669
  }
45504
45670
  async detectPythonPackageManager(targetDir, pythonPackageManagers) {
45505
- // Priority order for Python package managers: poetry > pipenv > pip
45506
45671
  for (const pmType of PYTHON_PACKAGE_MANAGERS){
45507
45672
  const pmConfig = pythonPackageManagers.find((pm)=>pm.type === pmType);
45508
45673
  if (!pmConfig) {
45509
45674
  continue;
45510
45675
  }
45511
- const manifestPath = (0,external_node_path_.join)(targetDir, pmConfig.manifestFile);
45676
+ const manifestPath = await file_system_utils_resolveSafePath(targetDir, pmConfig.manifestFile);
45512
45677
  if (await file_system_utils_fileExists(manifestPath)) {
45513
- const lockfilePath = pmConfig.lockfile ? (0,external_node_path_.join)(targetDir, pmConfig.lockfile) : undefined;
45678
+ const lockfilePath = pmConfig.lockfile ? await file_system_utils_resolveSafePath(targetDir, pmConfig.lockfile) : undefined;
45514
45679
  const hasLockfile = lockfilePath ? await file_system_utils_fileExists(lockfilePath) : false;
45515
- // Skip if lockfile is required but not present
45516
45680
  if (pmConfig.requiresLockfile && !hasLockfile) {
45517
45681
  continue;
45518
45682
  }
@@ -45528,11 +45692,10 @@ async function watchConfig(options) {
45528
45692
  async detectOtherPackageManagers(targetDir, otherPackageManagers) {
45529
45693
  const packageManagers = [];
45530
45694
  for (const pmConfig of otherPackageManagers){
45531
- const manifestPath = (0,external_node_path_.join)(targetDir, pmConfig.manifestFile);
45695
+ const manifestPath = await file_system_utils_resolveSafePath(targetDir, pmConfig.manifestFile);
45532
45696
  if (await file_system_utils_fileExists(manifestPath)) {
45533
- const lockfilePath = pmConfig.lockfile ? (0,external_node_path_.join)(targetDir, pmConfig.lockfile) : undefined;
45697
+ const lockfilePath = pmConfig.lockfile ? await file_system_utils_resolveSafePath(targetDir, pmConfig.lockfile) : undefined;
45534
45698
  const hasLockfile = lockfilePath ? await file_system_utils_fileExists(lockfilePath) : false;
45535
- // Skip if lockfile is required but not present
45536
45699
  if (pmConfig.requiresLockfile && !hasLockfile) {
45537
45700
  continue;
45538
45701
  }
@@ -45547,7 +45710,7 @@ async function watchConfig(options) {
45547
45710
  }
45548
45711
  }
45549
45712
  /**
45550
- * Creates and returns the default environment gateway
45713
+ * Creates and returns the default environment gateway.
45551
45714
  */ function createEnvironmentGateway() {
45552
45715
  return new FileSystemEnvironmentGateway();
45553
45716
  }
@@ -46041,13 +46204,13 @@ function defaultExec(command, args, options) {
46041
46204
  return fileExists(dirPath);
46042
46205
  }
46043
46206
  async ensureSkillDirectory(targetDir, skillName) {
46044
- const skillDir = this.getSkillDirectoryPath(targetDir, skillName);
46207
+ const skillDir = await resolveSafePath(targetDir, `.github/skills/${skillName}`);
46045
46208
  await mkdir(skillDir, {
46046
46209
  recursive: true
46047
46210
  });
46048
46211
  }
46049
46212
  async writeSkillFile(targetDir, skillName, content) {
46050
- const filePath = this.getSkillFilePath(targetDir, skillName);
46213
+ const filePath = await resolveSafePath(targetDir, `.github/skills/${skillName}/SKILL.md`);
46051
46214
  await writeFile(filePath, content, {
46052
46215
  encoding: "utf-8"
46053
46216
  });
@@ -46497,17 +46660,12 @@ var yaml_dist = __webpack_require__(3519);
46497
46660
 
46498
46661
 
46499
46662
 
46500
-
46501
- /**
46502
- * Possible filenames for the Copilot Setup Steps workflow.
46503
- * Supports both .yml and .yaml extensions.
46504
- */ const COPILOT_SETUP_WORKFLOW_FILENAMES = [
46663
+ const COPILOT_SETUP_WORKFLOW_FILENAMES = [
46505
46664
  "copilot-setup-steps.yml",
46506
46665
  "copilot-setup-steps.yaml"
46507
46666
  ];
46508
- /**
46509
- * File system implementation of the workflow gateway
46510
- */ class FileSystemWorkflowGateway {
46667
+ const MAX_WORKFLOW_FILE_BYTES = 1024 * 1024;
46668
+ class FileSystemWorkflowGateway {
46511
46669
  config = null;
46512
46670
  async getConfig() {
46513
46671
  if (!this.config) {
@@ -46515,13 +46673,9 @@ var yaml_dist = __webpack_require__(3519);
46515
46673
  }
46516
46674
  return this.config;
46517
46675
  }
46518
- /**
46519
- * Finds the path to an existing Copilot Setup Steps workflow file.
46520
- * @returns The full path if found, or null if no workflow exists.
46521
- */ async findCopilotSetupWorkflowPath(targetDir) {
46522
- const workflowsDir = (0,external_node_path_.join)(targetDir, ".github", "workflows");
46676
+ async findCopilotSetupWorkflowPath(targetDir) {
46523
46677
  for (const filename of COPILOT_SETUP_WORKFLOW_FILENAMES){
46524
- const workflowPath = (0,external_node_path_.join)(workflowsDir, filename);
46678
+ const workflowPath = await file_system_utils_resolveSafePath(targetDir, `.github/workflows/${filename}`);
46525
46679
  if (await file_system_utils_fileExists(workflowPath)) {
46526
46680
  return workflowPath;
46527
46681
  }
@@ -46529,7 +46683,7 @@ var yaml_dist = __webpack_require__(3519);
46529
46683
  return null;
46530
46684
  }
46531
46685
  async parseWorkflowsForSetupActions(targetDir) {
46532
- const workflowsDir = (0,external_node_path_.join)(targetDir, ".github", "workflows");
46686
+ const workflowsDir = await file_system_utils_resolveSafePath(targetDir, ".github/workflows");
46533
46687
  if (!await file_system_utils_fileExists(workflowsDir)) {
46534
46688
  return [];
46535
46689
  }
@@ -46538,15 +46692,15 @@ var yaml_dist = __webpack_require__(3519);
46538
46692
  const config = await this.getConfig();
46539
46693
  const allCandidates = [];
46540
46694
  for (const file of yamlFiles){
46541
- const filePath = (0,external_node_path_.join)(workflowsDir, file);
46695
+ const filePath = await file_system_utils_resolveSafePath(targetDir, `.github/workflows/${file}`);
46696
+ await assertFileSizeWithinLimit(filePath, MAX_WORKFLOW_FILE_BYTES, `Workflow file '${file}'`);
46542
46697
  try {
46543
46698
  const content = await (0,promises_.readFile)(filePath, "utf-8");
46544
46699
  const workflow = (0,yaml_dist.parse)(content);
46545
46700
  const candidates = extractSetupStepsFromWorkflow(workflow, config.setupActionPatterns);
46546
46701
  allCandidates.push(...candidates);
46547
46702
  } catch {
46548
- // Skip files that can't be parsed as valid YAML workflows
46549
- // This is expected for malformed or non-workflow YAML files
46703
+ // Skip unreadable or malformed YAML workflow files while continuing scan.
46550
46704
  }
46551
46705
  }
46552
46706
  return deduplicateCandidates(allCandidates);
@@ -46557,27 +46711,27 @@ var yaml_dist = __webpack_require__(3519);
46557
46711
  }
46558
46712
  async getCopilotSetupWorkflowPath(targetDir) {
46559
46713
  const existingPath = await this.findCopilotSetupWorkflowPath(targetDir);
46560
- return existingPath || (0,external_node_path_.join)(targetDir, ".github", "workflows", "copilot-setup-steps.yml");
46714
+ if (existingPath) {
46715
+ return existingPath;
46716
+ }
46717
+ return file_system_utils_resolveSafePath(targetDir, ".github/workflows/copilot-setup-steps.yml");
46561
46718
  }
46562
46719
  async readCopilotSetupWorkflow(targetDir) {
46563
46720
  const workflowPath = await this.findCopilotSetupWorkflowPath(targetDir);
46564
46721
  if (!workflowPath) {
46565
46722
  return null;
46566
46723
  }
46724
+ await assertFileSizeWithinLimit(workflowPath, MAX_WORKFLOW_FILE_BYTES, "Copilot setup workflow");
46567
46725
  const content = await (0,promises_.readFile)(workflowPath, "utf-8");
46568
46726
  return (0,yaml_dist.parse)(content);
46569
46727
  }
46570
46728
  async writeCopilotSetupWorkflow(targetDir, content) {
46571
- // Check if an existing workflow file exists (either extension)
46572
46729
  const existingPath = await this.findCopilotSetupWorkflowPath(targetDir);
46573
- // Use existing path if found, otherwise default to .yml
46574
- const workflowPath = existingPath || (0,external_node_path_.join)(targetDir, ".github", "workflows", "copilot-setup-steps.yml");
46730
+ const workflowPath = existingPath ?? await file_system_utils_resolveSafePath(targetDir, ".github/workflows/copilot-setup-steps.yml");
46575
46731
  await (0,promises_.writeFile)(workflowPath, content, "utf-8");
46576
46732
  }
46577
46733
  }
46578
- /**
46579
- * Creates and returns the default workflow gateway
46580
- */ function createWorkflowGateway() {
46734
+ function createWorkflowGateway() {
46581
46735
  return new FileSystemWorkflowGateway();
46582
46736
  }
46583
46737
 
@@ -66216,8 +66370,7 @@ const remark = unified().use(remarkParse).use(remarkStringify).freeze()
66216
66370
  * @param environment The detected environment configuration
66217
66371
  * @param config Optional copilot-setup configuration (for package manager mappings)
66218
66372
  * @returns Array of SessionStart hooks
66219
- */ async function buildSessionStartHooks(environment, config) {
66220
- const loadedConfig = config || await loadCopilotSetupConfig();
66373
+ */ async function buildSessionStartHooks(environment, config = DEFAULT_COPILOT_SETUP_CONFIG) {
66221
66374
  const hooks = [];
66222
66375
  // If mise.toml is present, add mise install
66223
66376
  if (environment.hasMise) {
@@ -66226,7 +66379,7 @@ const remark = unified().use(remarkParse).use(remarkStringify).freeze()
66226
66379
  description: "Install runtimes from mise.toml"
66227
66380
  });
66228
66381
  // After mise install, add package manager install hooks
66229
- const packageManagerHooks = buildPackageManagerHooks(environment.packageManagers, loadedConfig);
66382
+ const packageManagerHooks = buildPackageManagerHooks(environment.packageManagers, config);
66230
66383
  hooks.push(...packageManagerHooks);
66231
66384
  return hooks;
66232
66385
  }
@@ -66234,7 +66387,7 @@ const remark = unified().use(remarkParse).use(remarkStringify).freeze()
66234
66387
  const runtimeHooks = buildRuntimeHooks(environment.versionFiles);
66235
66388
  hooks.push(...runtimeHooks);
66236
66389
  // Add package manager install hooks
66237
- const packageManagerHooks = buildPackageManagerHooks(environment.packageManagers, loadedConfig);
66390
+ const packageManagerHooks = buildPackageManagerHooks(environment.packageManagers, config);
66238
66391
  hooks.push(...packageManagerHooks);
66239
66392
  return hooks;
66240
66393
  }
@@ -66504,6 +66657,7 @@ const remark = unified().use(remarkParse).use(remarkStringify).freeze()
66504
66657
 
66505
66658
 
66506
66659
 
66660
+
66507
66661
  /**
66508
66662
  * Creates or updates Claude Code web environment setup files.
66509
66663
  */ const createClaudeCodeWebSetupHandler = async (args)=>{
@@ -66515,8 +66669,9 @@ const remark = unified().use(remarkParse).use(remarkStringify).freeze()
66515
66669
  const claudeGateway = createClaudeFileGateway();
66516
66670
  // Detect environment configuration
66517
66671
  const environment = await environmentGateway.detectEnvironment(dir);
66672
+ const copilotSetupConfig = await loadCopilotSetupConfig();
66518
66673
  // Build SessionStart hooks from environment
66519
- const hooks = await buildSessionStartHooks(environment);
66674
+ const hooks = await buildSessionStartHooks(environment, copilotSetupConfig);
66520
66675
  // Read existing settings
66521
66676
  const existingSettings = await claudeGateway.readSettings(dir);
66522
66677
  // Merge settings
@@ -66728,17 +66883,29 @@ Example resolved format: actions/setup-node@1a2b3c4d5e6f # v4.0.0`;
66728
66883
  */
66729
66884
 
66730
66885
  /**
66731
- * Builds setup step candidates from detected environment
66732
- * @param environment The detected environment configuration
66733
- * @param versionGateway Optional gateway for looking up action versions (defaults to local)
66734
- * @param config Optional copilot-setup configuration
66735
- * @returns Array of setup step candidates
66736
- */ async function buildCandidatesFromEnvironment(environment, versionGateway = createActionVersionGateway(), config) {
66737
- const loadedConfig = config || await loadCopilotSetupConfig();
66738
- const versionTypeToAction = getVersionTypeToActionMap(loadedConfig);
66739
- const versionFileConfigKeys = getVersionFileConfigKeyMap(loadedConfig);
66886
+ * Creates an ActionVersionPort backed by a static version map.
66887
+ */ function createActionVersionPort(versionMap) {
66888
+ return {
66889
+ async getVersion (actionName) {
66890
+ return versionMap[actionName];
66891
+ }
66892
+ };
66893
+ }
66894
+ const candidate_builder_DEFAULT_ACTION_VERSIONS = {
66895
+ "actions/setup-node": "v4",
66896
+ "actions/setup-python": "v5",
66897
+ "actions/setup-java": "v4",
66898
+ "actions/setup-ruby": "v1",
66899
+ "actions/setup-go": "v5",
66900
+ "jdx/mise-action": "v2"
66901
+ };
66902
+ const defaultActionVersionPort = createActionVersionPort(candidate_builder_DEFAULT_ACTION_VERSIONS);
66903
+ /**
66904
+ * Builds setup step candidates from detected environment.
66905
+ */ async function buildCandidatesFromEnvironment(environment, versionGateway = defaultActionVersionPort, config = DEFAULT_COPILOT_SETUP_CONFIG) {
66906
+ const versionTypeToAction = getVersionTypeToActionMap(config);
66907
+ const versionFileConfigKeys = getVersionFileConfigKeyMap(config);
66740
66908
  const candidates = [];
66741
- // If mise.toml is present, add mise-action only
66742
66909
  if (environment.hasMise) {
66743
66910
  const miseVersion = await versionGateway.getVersion("jdx/mise-action");
66744
66911
  candidates.push({
@@ -66748,24 +66915,14 @@ Example resolved format: actions/setup-node@1a2b3c4d5e6f # v4.0.0`;
66748
66915
  });
66749
66916
  return candidates;
66750
66917
  }
66751
- // Otherwise, add individual setup actions for each version file
66752
66918
  const setupCandidates = await buildCandidatesFromVersionFiles(environment.versionFiles, versionTypeToAction, versionFileConfigKeys, versionGateway);
66753
66919
  candidates.push(...setupCandidates);
66754
- // Add install steps for detected package managers
66755
- const installCandidates = buildInstallCandidatesFromPackageManagers(environment.packageManagers, loadedConfig);
66920
+ const installCandidates = buildInstallCandidatesFromPackageManagers(environment.packageManagers, config);
66756
66921
  candidates.push(...installCandidates);
66757
66922
  return candidates;
66758
66923
  }
66759
- /**
66760
- * Builds setup step candidates from individual version files
66761
- * @param versionFiles Array of version files to process
66762
- * @param versionTypeToAction Map from version file type to action name
66763
- * @param versionFileConfigKeys Map from version file type to config key
66764
- * @param versionGateway Gateway for looking up action versions
66765
- * @returns Array of setup step candidates
66766
- */ async function buildCandidatesFromVersionFiles(versionFiles, versionTypeToAction, versionFileConfigKeys, versionGateway) {
66924
+ async function buildCandidatesFromVersionFiles(versionFiles, versionTypeToAction, versionFileConfigKeys, versionGateway) {
66767
66925
  const candidates = [];
66768
- // Track which types we've already added to deduplicate (e.g., .nvmrc and .node-version)
66769
66926
  const addedTypes = new Set();
66770
66927
  for (const versionFile of versionFiles){
66771
66928
  if (addedTypes.has(versionFile.type)) {
@@ -66789,30 +66946,19 @@ Example resolved format: actions/setup-node@1a2b3c4d5e6f # v4.0.0`;
66789
66946
  }
66790
66947
  return candidates;
66791
66948
  }
66792
- /**
66793
- * Builds install step candidates from detected package managers
66794
- * @param packageManagers Array of detected package managers
66795
- * @param config Configuration for package manager mappings
66796
- * @returns Array of install step candidates
66797
- */ function buildInstallCandidatesFromPackageManagers(packageManagers, config) {
66949
+ function buildInstallCandidatesFromPackageManagers(packageManagers, config) {
66798
66950
  const candidates = [];
66799
66951
  const addedTypes = new Set();
66800
66952
  for (const pm of packageManagers){
66801
- // Skip if we've already added this package manager type
66802
66953
  if (addedTypes.has(pm.type)) {
66803
66954
  continue;
66804
66955
  }
66805
66956
  addedTypes.add(pm.type);
66806
- // Find the config for this package manager
66807
66957
  const pmConfig = config.packageManagers.find((c)=>c.type === pm.type);
66808
66958
  if (!pmConfig) {
66809
66959
  continue;
66810
66960
  }
66811
- // Determine a descriptive name for the install step
66812
66961
  const stepName = getInstallStepName(pm.type);
66813
- // Create install step candidate
66814
- // Note: Empty action string indicates this is a run step (uses 'run' field instead of 'uses')
66815
- // This is checked in workflow-generator.ts buildStepFromCandidate()
66816
66962
  candidates.push({
66817
66963
  action: "",
66818
66964
  source: "version-file",
@@ -66822,9 +66968,7 @@ Example resolved format: actions/setup-node@1a2b3c4d5e6f # v4.0.0`;
66822
66968
  }
66823
66969
  return candidates;
66824
66970
  }
66825
- /**
66826
- * Gets a descriptive name for an install step based on package manager type
66827
- */ function getInstallStepName(packageManagerType) {
66971
+ function getInstallStepName(packageManagerType) {
66828
66972
  const names = {
66829
66973
  npm: "Install Node.js dependencies",
66830
66974
  yarn: "Install Node.js dependencies",
@@ -67234,6 +67378,10 @@ function getGlobalWacContext() {
67234
67378
 
67235
67379
 
67236
67380
 
67381
+ const workflow_generator_DEFAULT_ACTION_VERSIONS = {
67382
+ "actions/checkout": "v4"
67383
+ };
67384
+ const workflow_generator_defaultActionVersionPort = createActionVersionPort(workflow_generator_DEFAULT_ACTION_VERSIONS);
67237
67385
  /**
67238
67386
  * Generates a human-readable step name from an action name
67239
67387
  * @example "actions/setup-node" -> "Setup node"
@@ -67348,7 +67496,7 @@ function getGlobalWacContext() {
67348
67496
  * @param versionGateway Optional gateway for looking up action versions (defaults to local)
67349
67497
  * @param options Optional generation options for placeholders and resolved versions
67350
67498
  * @returns The workflow YAML content as a string
67351
- */ async function generateWorkflowContent(candidates, versionGateway = createActionVersionGateway(), options) {
67499
+ */ async function generateWorkflowContent(candidates, versionGateway = workflow_generator_defaultActionVersionPort, options) {
67352
67500
  const stepOptions = {
67353
67501
  usePlaceholders: options?.usePlaceholders,
67354
67502
  resolvedVersions: options?.resolvedVersions
@@ -67406,7 +67554,7 @@ function getGlobalWacContext() {
67406
67554
  * @param versionGateway Optional gateway for looking up action versions (defaults to local)
67407
67555
  * @param options Optional generation options for placeholders and resolved versions
67408
67556
  * @returns The updated workflow YAML content
67409
- */ async function updateWorkflowWithMissingSteps(existingWorkflow, missingCandidates, versionGateway = createActionVersionGateway(), options) {
67557
+ */ async function updateWorkflowWithMissingSteps(existingWorkflow, missingCandidates, versionGateway = workflow_generator_defaultActionVersionPort, options) {
67410
67558
  if (!existingWorkflow || typeof existingWorkflow !== "object") {
67411
67559
  return generateWorkflowContent(missingCandidates, versionGateway, options);
67412
67560
  }
@@ -67459,12 +67607,13 @@ function getGlobalWacContext() {
67459
67607
  */ async function gatherCandidates(dir, workflowsDirExists) {
67460
67608
  const environmentGateway = createEnvironmentGateway();
67461
67609
  const workflowGateway = createWorkflowGateway();
67610
+ const copilotSetupConfig = await loadCopilotSetupConfig();
67462
67611
  // Detect environment configuration
67463
67612
  const environment = await environmentGateway.detectEnvironment(dir);
67464
67613
  // Parse existing workflows for setup actions
67465
67614
  const workflowCandidates = workflowsDirExists ? await workflowGateway.parseWorkflowsForSetupActions(dir) : [];
67466
67615
  // Build candidates from environment
67467
- const envCandidates = await buildCandidatesFromEnvironment(environment);
67616
+ const envCandidates = await buildCandidatesFromEnvironment(environment, undefined, copilotSetupConfig);
67468
67617
  // Merge candidates (workflow takes precedence)
67469
67618
  return mergeCandidates(workflowCandidates, envCandidates);
67470
67619
  }
@@ -67535,7 +67684,7 @@ function getGlobalWacContext() {
67535
67684
  return types_errorResponse(`Target directory does not exist: ${dir}`);
67536
67685
  }
67537
67686
  const workflowGateway = createWorkflowGateway();
67538
- const workflowsDir = (0,external_node_path_.join)(dir, ".github", "workflows");
67687
+ const workflowsDir = await file_system_utils_resolveSafePath(dir, ".github/workflows");
67539
67688
  const workflowsDirExists = await file_system_utils_fileExists(workflowsDir);
67540
67689
  // Gather all candidates
67541
67690
  const allCandidates = await gatherCandidates(dir, workflowsDirExists);