@lousy-agents/cli 5.12.2 → 5.13.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.
@@ -14,11 +14,11 @@
14
14
  "kysely": "0.28.17",
15
15
  "pino": "10.3.1",
16
16
  "postgres": "3.4.9",
17
- "zod": "4.4.2"
17
+ "zod": "4.4.3"
18
18
  },
19
19
  "devDependencies": {
20
20
  "@biomejs/biome": "2.4.14",
21
- "@lousy-agents/mcp": "5.12.1",
21
+ "@lousy-agents/mcp": "5.13.0",
22
22
  "@modelcontextprotocol/server-sequential-thinking": "2025.12.18",
23
23
  "@testcontainers/postgresql": "11.14.0",
24
24
  "@types/node": "24.12.2",
@@ -956,9 +956,9 @@
956
956
  }
957
957
  },
958
958
  "node_modules/@lousy-agents/mcp": {
959
- "version": "5.12.1",
960
- "resolved": "https://registry.npmjs.org/@lousy-agents/mcp/-/mcp-5.12.1.tgz",
961
- "integrity": "sha512-iJq4CcQOeZVdNaEPCwbbG6d9K9IyDMLALKRHc++YiYnLUvFEV0zZCpuCgxwLxnNkxj8r2DP944hCMMwoPA8FVg==",
959
+ "version": "5.13.0",
960
+ "resolved": "https://registry.npmjs.org/@lousy-agents/mcp/-/mcp-5.13.0.tgz",
961
+ "integrity": "sha512-MXQIYQGn6L1tDPY/Di2n/bab/JtyAdVJ4Qy1ozhQC3RM2Kd/riKyYZky0RqS7PD3qFQMU6iMAH45f787otNG0w==",
962
962
  "dev": true,
963
963
  "license": "BSD-2-Clause-Patent",
964
964
  "dependencies": {
@@ -6009,9 +6009,9 @@
6009
6009
  }
6010
6010
  },
6011
6011
  "node_modules/zod": {
6012
- "version": "4.4.2",
6013
- "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.2.tgz",
6014
- "integrity": "sha512-IynmDyxsEsb9RKzO3J9+4SxXnl2FTFSzNBaKKaMV6tsSk0rw9gYw9gs+JFCq/qk2LCZ78KDwyj+Z289TijSkUw==",
6012
+ "version": "4.4.3",
6013
+ "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz",
6014
+ "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==",
6015
6015
  "license": "MIT",
6016
6016
  "funding": {
6017
6017
  "url": "https://github.com/sponsors/colinhacks"
@@ -22,14 +22,14 @@
22
22
  "kysely": "0.28.17",
23
23
  "pino": "10.3.1",
24
24
  "postgres": "3.4.9",
25
- "zod": "4.4.2"
25
+ "zod": "4.4.3"
26
26
  },
27
27
  "overrides": {
28
- "zod": "4.4.2"
28
+ "zod": "4.4.3"
29
29
  },
30
30
  "devDependencies": {
31
31
  "@biomejs/biome": "2.4.14",
32
- "@lousy-agents/mcp": "5.12.1",
32
+ "@lousy-agents/mcp": "5.13.0",
33
33
  "@modelcontextprotocol/server-sequential-thinking": "2025.12.18",
34
34
  "@testcontainers/postgresql": "11.14.0",
35
35
  "@types/node": "24.12.2",
@@ -16,11 +16,11 @@
16
16
  "dependencies": {
17
17
  "citty": "0.2.2",
18
18
  "consola": "3.4.2",
19
- "zod": "4.4.2"
19
+ "zod": "4.4.3"
20
20
  },
21
21
  "devDependencies": {
22
22
  "@biomejs/biome": "2.4.14",
23
- "@lousy-agents/mcp": "5.12.1",
23
+ "@lousy-agents/mcp": "5.13.0",
24
24
  "@modelcontextprotocol/server-sequential-thinking": "2025.12.18",
25
25
  "@types/node": "24.12.2",
26
26
  "@upstash/context7-mcp": "2.2.3",
package/dist/index.js CHANGED
@@ -567,7 +567,7 @@ module.exports = webpackEmptyAsyncContext;
567
567
 
568
568
 
569
569
  },
570
- 5951(__unused_rspack_module, __unused_rspack___webpack_exports__, __webpack_require__) {
570
+ 2091(__unused_rspack_module, __unused_rspack___webpack_exports__, __webpack_require__) {
571
571
  // NAMESPACE OBJECT: ../../node_modules/micromark/lib/constructs.js
572
572
  var constructs_namespaceObject = {};
573
573
  __webpack_require__.r(constructs_namespaceObject);
@@ -5685,7 +5685,7 @@ const safeDecodeAsync = /* @__PURE__*/ _safeDecodeAsync($ZodRealError);
5685
5685
  const versions_version = {
5686
5686
  major: 4,
5687
5687
  minor: 4,
5688
- patch: 2,
5688
+ patch: 3,
5689
5689
  };
5690
5690
 
5691
5691
  ;// CONCATENATED MODULE: ../../node_modules/zod/v4/core/schemas.js
@@ -7408,6 +7408,7 @@ const $ZodFile = /*@__PURE__*/ (/* unused pure expression or super */ null && (c
7408
7408
  })));
7409
7409
  const $ZodTransform = /*@__PURE__*/ $constructor("$ZodTransform", (inst, def) => {
7410
7410
  $ZodType.init(inst, def);
7411
+ inst._zod.optin = "optional";
7411
7412
  inst._zod.parse = (payload, ctx) => {
7412
7413
  if (ctx.direction === "backward") {
7413
7414
  throw new $ZodEncodeError(inst.constructor.name);
@@ -7417,6 +7418,7 @@ const $ZodTransform = /*@__PURE__*/ $constructor("$ZodTransform", (inst, def) =>
7417
7418
  const output = _out instanceof Promise ? _out : Promise.resolve(_out);
7418
7419
  return output.then((output) => {
7419
7420
  payload.value = output;
7421
+ payload.fallback = true;
7420
7422
  return payload;
7421
7423
  });
7422
7424
  }
@@ -7424,11 +7426,12 @@ const $ZodTransform = /*@__PURE__*/ $constructor("$ZodTransform", (inst, def) =>
7424
7426
  throw new $ZodAsyncError();
7425
7427
  }
7426
7428
  payload.value = _out;
7429
+ payload.fallback = true;
7427
7430
  return payload;
7428
7431
  };
7429
7432
  });
7430
7433
  function handleOptionalResult(result, input) {
7431
- if (result.issues.length && input === undefined) {
7434
+ if (input === undefined && (result.issues.length || result.fallback)) {
7432
7435
  return { issues: [], value: undefined };
7433
7436
  }
7434
7437
  return result;
@@ -7446,10 +7449,11 @@ const $ZodOptional = /*@__PURE__*/ $constructor("$ZodOptional", (inst, def) => {
7446
7449
  });
7447
7450
  inst._zod.parse = (payload, ctx) => {
7448
7451
  if (def.innerType._zod.optin === "optional") {
7452
+ const input = payload.value;
7449
7453
  const result = def.innerType._zod.run(payload, ctx);
7450
7454
  if (result instanceof Promise)
7451
- return result.then((r) => handleOptionalResult(r, payload.value));
7452
- return handleOptionalResult(result, payload.value);
7455
+ return result.then((r) => handleOptionalResult(r, input));
7456
+ return handleOptionalResult(result, input);
7453
7457
  }
7454
7458
  if (payload.value === undefined) {
7455
7459
  return payload;
@@ -7576,7 +7580,7 @@ const $ZodSuccess = /*@__PURE__*/ (/* unused pure expression or super */ null &&
7576
7580
  })));
7577
7581
  const $ZodCatch = /*@__PURE__*/ $constructor("$ZodCatch", (inst, def) => {
7578
7582
  $ZodType.init(inst, def);
7579
- defineLazy(inst._zod, "optin", () => def.innerType._zod.optin);
7583
+ inst._zod.optin = "optional";
7580
7584
  defineLazy(inst._zod, "optout", () => def.innerType._zod.optout);
7581
7585
  defineLazy(inst._zod, "values", () => def.innerType._zod.values);
7582
7586
  inst._zod.parse = (payload, ctx) => {
@@ -7597,6 +7601,7 @@ const $ZodCatch = /*@__PURE__*/ $constructor("$ZodCatch", (inst, def) => {
7597
7601
  input: payload.value,
7598
7602
  });
7599
7603
  payload.issues = [];
7604
+ payload.fallback = true;
7600
7605
  }
7601
7606
  return payload;
7602
7607
  });
@@ -7611,6 +7616,7 @@ const $ZodCatch = /*@__PURE__*/ $constructor("$ZodCatch", (inst, def) => {
7611
7616
  input: payload.value,
7612
7617
  });
7613
7618
  payload.issues = [];
7619
+ payload.fallback = true;
7614
7620
  }
7615
7621
  return payload;
7616
7622
  };
@@ -7657,7 +7663,7 @@ function handlePipeResult(left, next, ctx) {
7657
7663
  left.aborted = true;
7658
7664
  return left;
7659
7665
  }
7660
- return next._zod.run({ value: left.value, issues: left.issues }, ctx);
7666
+ return next._zod.run({ value: left.value, issues: left.issues, fallback: left.fallback }, ctx);
7661
7667
  }
7662
7668
  const $ZodCodec = /*@__PURE__*/ (/* unused pure expression or super */ null && (core.$constructor("$ZodCodec", (inst, def) => {
7663
7669
  $ZodType.init(inst, def);
@@ -7715,8 +7721,6 @@ function handleCodecTxResult(left, value, nextSchema, ctx) {
7715
7721
  }
7716
7722
  const $ZodPreprocess = /*@__PURE__*/ (/* unused pure expression or super */ null && (core.$constructor("$ZodPreprocess", (inst, def) => {
7717
7723
  $ZodPipe.init(inst, def);
7718
- util.defineLazy(inst._zod, "optin", () => def.out._zod.optin);
7719
- util.defineLazy(inst._zod, "optout", () => def.out._zod.optout);
7720
7724
  })));
7721
7725
  const $ZodReadonly = /*@__PURE__*/ $constructor("$ZodReadonly", (inst, def) => {
7722
7726
  $ZodType.init(inst, def);
@@ -8914,7 +8918,7 @@ function _promise(Class, innerType) {
8914
8918
  }
8915
8919
  // @__NO_SIDE_EFFECTS__
8916
8920
  function _custom(Class, fn, _params) {
8917
- const norm = util.normalizeParams(_params);
8921
+ const norm = normalizeParams(_params);
8918
8922
  norm.abort ?? (norm.abort = true); // default to abort:false
8919
8923
  const schema = new Class({
8920
8924
  type: "custom",
@@ -11303,10 +11307,12 @@ const ZodTransform = /*@__PURE__*/ $constructor("ZodTransform", (inst, def) => {
11303
11307
  if (output instanceof Promise) {
11304
11308
  return output.then((output) => {
11305
11309
  payload.value = output;
11310
+ payload.fallback = true;
11306
11311
  return payload;
11307
11312
  });
11308
11313
  }
11309
11314
  payload.value = output;
11315
+ payload.fallback = true;
11310
11316
  return payload;
11311
11317
  };
11312
11318
  });
@@ -11553,7 +11559,7 @@ function schemas_check(fn) {
11553
11559
  return ch;
11554
11560
  }
11555
11561
  function custom(fn, _params) {
11556
- return core._custom(ZodCustom, fn ?? (() => true), _params);
11562
+ return _custom(ZodCustom, fn ?? (() => true), _params);
11557
11563
  }
11558
11564
  function refine(fn, _params = {}) {
11559
11565
  return _refine(ZodCustom, fn, _params);
@@ -11817,6 +11823,26 @@ var compat_ZodFirstPartyTypeKind;
11817
11823
  setupActionPatterns: DEFAULT_SETUP_ACTION_PATTERNS,
11818
11824
  packageManagers: DEFAULT_PACKAGE_MANAGERS
11819
11825
  };
11826
+ /**
11827
+ * Gets a version type to action name mapping from config.
11828
+ * Returns a partial record as not all types may be configured.
11829
+ */ function getVersionTypeToActionMap(config) {
11830
+ const map = {};
11831
+ for (const action of config.setupActions){
11832
+ map[action.type] = action.action;
11833
+ }
11834
+ return map;
11835
+ }
11836
+ /**
11837
+ * Gets a version file type to config key mapping from config.
11838
+ * Returns a partial record as not all types may be configured.
11839
+ */ function getVersionFileConfigKeyMap(config) {
11840
+ const map = {};
11841
+ for (const action of config.setupActions){
11842
+ map[action.type] = action.versionFileKey;
11843
+ }
11844
+ return map;
11845
+ }
11820
11846
 
11821
11847
  ;// CONCATENATED MODULE: ../core/src/lib/copilot-setup-config.ts
11822
11848
  /**
@@ -11944,29 +11970,8 @@ const CopilotSetupConfigSchema = schemas_object({
11944
11970
  *
11945
11971
  * This function is retained for backwards compatibility but is a no-op
11946
11972
  * because configuration is no longer cached at module scope.
11947
- */ function resetCopilotSetupConfigCache() {
11948
- // no in-memory cache to reset
11949
- }
11950
- /**
11951
- * Gets a version file type to action mapping from config.
11952
- * Returns a partial record as not all types may be configured.
11953
- */ function getVersionTypeToActionMap(config) {
11954
- const map = {};
11955
- for (const action of config.setupActions){
11956
- map[action.type] = action.action;
11957
- }
11958
- return map;
11959
- }
11960
- /**
11961
- * Gets a version file type to config key mapping from config.
11962
- * Returns a partial record as not all types may be configured.
11963
- */ function getVersionFileConfigKeyMap(config) {
11964
- const map = {};
11965
- for (const action of config.setupActions){
11966
- map[action.type] = action.versionFileKey;
11967
- }
11968
- return map;
11969
- }
11973
+ */ function resetCopilotSetupConfigCache() {}
11974
+
11970
11975
  /**
11971
11976
  * Gets a filename to version type mapping from config.
11972
11977
  */ function getVersionFilenameToTypeMap(config) {
@@ -16512,15 +16517,12 @@ function defaultExec(command, args, options) {
16512
16517
  * File system implementation of instruction analysis gateway
16513
16518
  */ class FileSystemInstructionAnalysisGateway {
16514
16519
  async analyzeCoverage(targetDir, scripts, tools) {
16515
- // Find all instruction files
16516
16520
  const instructionFiles = await this.findInstructionFiles(targetDir);
16517
- // Read and search instruction content
16518
16521
  const references = [];
16519
16522
  const documentedTargets = new Set();
16520
16523
  for (const file of instructionFiles){
16521
16524
  const content = await readFile(file, "utf-8");
16522
16525
  const lines = content.split("\n");
16523
- // Check for script references (e.g., "npm test", "npm run build")
16524
16526
  for (const script of scripts){
16525
16527
  const scriptRefs = this.findReferencesInContent(script.name, content, lines, file, targetDir);
16526
16528
  if (scriptRefs.length > 0) {
@@ -16528,7 +16530,6 @@ function defaultExec(command, args, options) {
16528
16530
  documentedTargets.add(script.name);
16529
16531
  }
16530
16532
  }
16531
- // Check for tool references (e.g., "mise run test", "biome check")
16532
16533
  for (const tool of tools){
16533
16534
  const toolRefs = this.findReferencesInContent(tool.name, content, lines, file, targetDir);
16534
16535
  if (toolRefs.length > 0) {
@@ -16537,14 +16538,12 @@ function defaultExec(command, args, options) {
16537
16538
  }
16538
16539
  }
16539
16540
  }
16540
- // Filter mandatory scripts/tools
16541
16541
  const mandatoryScripts = scripts.filter((s)=>s.isMandatory);
16542
16542
  const mandatoryTools = tools.filter((t)=>t.isMandatory);
16543
16543
  const allMandatory = [
16544
16544
  ...mandatoryScripts,
16545
16545
  ...mandatoryTools
16546
16546
  ];
16547
- // Categorize as missing or documented
16548
16547
  const missingInInstructions = allMandatory.filter((item)=>!documentedTargets.has(item.name));
16549
16548
  const documentedInInstructions = allMandatory.filter((item)=>documentedTargets.has(item.name));
16550
16549
  const totalMandatory = allMandatory.length;
@@ -16563,12 +16562,10 @@ function defaultExec(command, args, options) {
16563
16562
  }
16564
16563
  async findInstructionFiles(targetDir) {
16565
16564
  const files = [];
16566
- // Check for .github/copilot-instructions.md
16567
16565
  const copilotInstructions = join(targetDir, ".github", "copilot-instructions.md");
16568
16566
  if (await fileExists(copilotInstructions)) {
16569
16567
  files.push(copilotInstructions);
16570
16568
  }
16571
- // Check for .github/instructions/*.md
16572
16569
  const instructionsDir = join(targetDir, ".github", "instructions");
16573
16570
  if (await fileExists(instructionsDir)) {
16574
16571
  try {
@@ -16578,31 +16575,20 @@ function defaultExec(command, args, options) {
16578
16575
  files.push(join(instructionsDir, file));
16579
16576
  }
16580
16577
  }
16581
- } catch {
16582
- // Skip directory if we can't read it (e.g., permissions issues)
16583
- // Similar to how workflow parsing errors are handled
16584
- }
16578
+ } catch {}
16585
16579
  }
16586
16580
  return files;
16587
16581
  }
16588
16582
  findReferencesInContent(target, content, lines, file, targetDir) {
16589
16583
  const references = [];
16590
- // Build a case-insensitive, word-boundary-aware pattern for the target.
16591
- // This reduces false positives from simple substring matches like
16592
- // "test" in "testing" or "latest", while still matching common
16593
- // separators such as spaces, punctuation, etc.
16594
16584
  const escapedTarget = target.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
16595
16585
  const targetPattern = new RegExp(`(?:^|[^\\w])(${escapedTarget})(?=$|[^\\w])`, "i");
16596
- // Fast path: skip line-by-line processing if the target pattern
16597
- // never appears in the full content.
16598
16586
  if (!targetPattern.test(content)) {
16599
16587
  return references;
16600
16588
  }
16601
- // Find line numbers where target appears
16602
16589
  for(let i = 0; i < lines.length; i++){
16603
16590
  const line = lines[i];
16604
16591
  if (targetPattern.test(line)) {
16605
- // Get context (line before and after if available)
16606
16592
  const contextLines = [];
16607
16593
  if (i > 0) contextLines.push(lines[i - 1]);
16608
16594
  contextLines.push(line);
@@ -18314,47 +18300,78 @@ const MAX_NPMRC_BYTES = 64 * 1024;
18314
18300
 
18315
18301
 
18316
18302
 
18303
+
18304
+ // 1 MB — covers even the largest real-world manifests
18305
+ const MAX_PACKAGE_JSON_BYTES = 1024 * 1024;
18306
+ const PackageJsonSchema = schemas_object({
18307
+ scripts: record(schemas_string(), schemas_string()).optional()
18308
+ });
18317
18309
  /**
18318
18310
  * File system implementation of script discovery gateway
18319
18311
  */ class FileSystemScriptDiscoveryGateway {
18312
+ constructor(logger){
18313
+ this.logger = logger ?? consola;
18314
+ }
18315
+ logger;
18320
18316
  async discoverScripts(targetDir) {
18321
18317
  const packageJsonPath = (0,external_node_path_.join)(targetDir, "package.json");
18322
- if (!await file_system_utils_fileExists(packageJsonPath)) {
18318
+ let content;
18319
+ try {
18320
+ content = await readFileNoFollow(packageJsonPath, MAX_PACKAGE_JSON_BYTES);
18321
+ } catch (error) {
18322
+ const code = error instanceof Error && "code" in error ? error.code : undefined;
18323
+ if (code !== "ENOENT") {
18324
+ this.logger.warn(`script-discovery: could not read ${JSON.stringify(packageJsonPath)}: ${JSON.stringify(error instanceof Error ? error.message : String(error))}`);
18325
+ }
18323
18326
  return [];
18324
18327
  }
18328
+ let parsed;
18325
18329
  try {
18326
- const content = await (0,promises_.readFile)(packageJsonPath, "utf-8");
18327
- const packageJson = JSON.parse(content);
18328
- if (!packageJson.scripts) {
18330
+ parsed = JSON.parse(content);
18331
+ } catch (e) {
18332
+ if (e instanceof SyntaxError) {
18333
+ this.logger.warn(`script-discovery: ${JSON.stringify(packageJsonPath)} contains invalid JSON — ${JSON.stringify(e.message)}`);
18329
18334
  return [];
18330
18335
  }
18331
- const scripts = [];
18332
- for (const [name, command] of Object.entries(packageJson.scripts)){
18333
- const phase = feedback_loop_determineScriptPhase(name, command);
18334
- const isMandatory = feedback_loop_isScriptMandatory(phase);
18335
- scripts.push({
18336
- name,
18337
- command,
18338
- phase,
18339
- isMandatory
18340
- });
18341
- }
18342
- return scripts;
18343
- } catch {
18344
- // If package.json is malformed or cannot be parsed, return empty array
18336
+ throw e;
18337
+ }
18338
+ const parseResult = PackageJsonSchema.safeParse(parsed);
18339
+ if (!parseResult.success) {
18340
+ this.logger.warn(`script-discovery: ${JSON.stringify(packageJsonPath)} has an unexpected structure — scripts field must be a record of strings`);
18345
18341
  return [];
18346
18342
  }
18343
+ if (!parseResult.data.scripts) {
18344
+ return [];
18345
+ }
18346
+ const scripts = [];
18347
+ for (const [name, command] of Object.entries(parseResult.data.scripts)){
18348
+ const phase = feedback_loop_determineScriptPhase(name, command);
18349
+ const isMandatory = feedback_loop_isScriptMandatory(phase);
18350
+ scripts.push({
18351
+ name,
18352
+ command,
18353
+ phase,
18354
+ isMandatory
18355
+ });
18356
+ }
18357
+ return scripts;
18347
18358
  }
18348
18359
  }
18349
18360
  /**
18350
18361
  * Creates and returns the default script discovery gateway
18351
- */ function createScriptDiscoveryGateway() {
18352
- return new FileSystemScriptDiscoveryGateway();
18362
+ */ function createScriptDiscoveryGateway(logger) {
18363
+ return new FileSystemScriptDiscoveryGateway(logger);
18353
18364
  }
18354
18365
  /**
18355
18366
  * Creates a FeedbackLoopCommandsGateway that discovers mandatory commands from package.json scripts.
18356
- */ function createFeedbackLoopCommandsGateway(scriptGateway) {
18357
- const gateway = scriptGateway ?? createScriptDiscoveryGateway();
18367
+ *
18368
+ * @param scriptGateway - Optional gateway implementation. If omitted, a default
18369
+ * `FileSystemScriptDiscoveryGateway` is created using `logger`.
18370
+ * @param logger - Optional logger for the default gateway. Only used when
18371
+ * `scriptGateway` is omitted; if a custom `scriptGateway` is supplied, its
18372
+ * own logger configuration applies and this parameter is ignored.
18373
+ */ function createFeedbackLoopCommandsGateway(scriptGateway, logger) {
18374
+ const gateway = scriptGateway ?? createScriptDiscoveryGateway(logger);
18358
18375
  return {
18359
18376
  async getMandatoryCommands (targetDir) {
18360
18377
  const scripts = await gateway.discoverScripts(targetDir);
@@ -18404,6 +18421,56 @@ const MAX_NPMRC_BYTES = 64 * 1024;
18404
18421
 
18405
18422
  // EXTERNAL MODULE: ../../node_modules/yaml/dist/index.js
18406
18423
  var dist = __webpack_require__(3519);
18424
+ ;// CONCATENATED MODULE: ../core/src/lib/frontmatter.ts
18425
+ /**
18426
+ * Shared YAML frontmatter parser for markdown files.
18427
+ * Extracts YAML data between leading `---` delimiters.
18428
+ */
18429
+ /**
18430
+ * Parses YAML frontmatter from markdown content.
18431
+ *
18432
+ * Returns `null` when the content has no opening `---`, no closing `---`,
18433
+ * or contains invalid YAML.
18434
+ */ function parseFrontmatter(content) {
18435
+ const lines = content.split("\n");
18436
+ if (lines[0]?.trim() !== "---") {
18437
+ return null;
18438
+ }
18439
+ let endIndex = -1;
18440
+ for(let i = 1; i < lines.length; i++){
18441
+ if (lines[i]?.trim() === "---") {
18442
+ endIndex = i;
18443
+ break;
18444
+ }
18445
+ }
18446
+ if (endIndex === -1) {
18447
+ return null;
18448
+ }
18449
+ const yamlContent = lines.slice(1, endIndex).join("\n");
18450
+ let data;
18451
+ try {
18452
+ const parsed = (0,dist/* .parse */.qg)(yamlContent, {
18453
+ maxAliasCount: 0
18454
+ });
18455
+ data = parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
18456
+ } catch {
18457
+ return null;
18458
+ }
18459
+ const fieldLines = new Map();
18460
+ for(let i = 1; i < endIndex; i++){
18461
+ // Match YAML top-level field names: non-whitespace start, any chars except colon, then colon followed by whitespace or end-of-line
18462
+ const match = lines[i]?.match(/^([^\s:][^:]*?):(?:\s|$)/);
18463
+ if (match?.[1]) {
18464
+ fieldLines.set(match[1], i + 1);
18465
+ }
18466
+ }
18467
+ return {
18468
+ data: data ?? {},
18469
+ fieldLines,
18470
+ frontmatterStartLine: 1
18471
+ };
18472
+ }
18473
+
18407
18474
  ;// CONCATENATED MODULE: ../core/src/gateways/skill-lint-gateway.ts
18408
18475
  /**
18409
18476
  * Gateway for skill lint file system operations.
@@ -18417,7 +18484,8 @@ var dist = __webpack_require__(3519);
18417
18484
  * Skill directory locations to search for SKILL.md files.
18418
18485
  */ const SKILL_DIRECTORIES = [
18419
18486
  (0,external_node_path_.join)(".github", "skills"),
18420
- (0,external_node_path_.join)(".claude", "skills")
18487
+ (0,external_node_path_.join)(".claude", "skills"),
18488
+ (0,external_node_path_.join)(".agents", "skills")
18421
18489
  ];
18422
18490
  /**
18423
18491
  * File system implementation of the skill lint gateway.
@@ -18487,41 +18555,7 @@ var dist = __webpack_require__(3519);
18487
18555
  return readFileNoFollow(filePath, MAX_SKILL_FILE_BYTES);
18488
18556
  }
18489
18557
  parseFrontmatter(content) {
18490
- const lines = content.split("\n");
18491
- if (lines[0]?.trim() !== "---") {
18492
- return null;
18493
- }
18494
- let endIndex = -1;
18495
- for(let i = 1; i < lines.length; i++){
18496
- if (lines[i]?.trim() === "---") {
18497
- endIndex = i;
18498
- break;
18499
- }
18500
- }
18501
- if (endIndex === -1) {
18502
- return null;
18503
- }
18504
- const yamlContent = lines.slice(1, endIndex).join("\n");
18505
- let data;
18506
- try {
18507
- const parsed = (0,dist/* .parse */.qg)(yamlContent);
18508
- data = parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
18509
- } catch {
18510
- return null;
18511
- }
18512
- const fieldLines = new Map();
18513
- for(let i = 1; i < endIndex; i++){
18514
- // Match YAML top-level field names: non-whitespace start, any chars except colon, then colon+space
18515
- const match = lines[i]?.match(/^([^\s:][^:]*?):\s/);
18516
- if (match?.[1]) {
18517
- fieldLines.set(match[1], i + 1);
18518
- }
18519
- }
18520
- return {
18521
- data: data ?? {},
18522
- fieldLines,
18523
- frontmatterStartLine: 1
18524
- };
18558
+ return parseFrontmatter(content);
18525
18559
  }
18526
18560
  }
18527
18561
  /**
@@ -18556,9 +18590,7 @@ var dist = __webpack_require__(3519);
18556
18590
  const workflow = parseYaml(content);
18557
18591
  const tools = this.extractToolsFromWorkflow(workflow, file);
18558
18592
  allTools.push(...tools);
18559
- } catch {
18560
- // Skip files that can't be parsed as valid YAML
18561
- }
18593
+ } catch {}
18562
18594
  }
18563
18595
  return this.deduplicateTools(allTools);
18564
18596
  }
@@ -18586,7 +18618,6 @@ var dist = __webpack_require__(3519);
18586
18618
  const stepObj = step;
18587
18619
  const run = stepObj.run;
18588
18620
  if (typeof run === "string") {
18589
- // Extract tools from run commands
18590
18621
  const extractedTools = this.extractToolsFromRunCommand(run, sourceFile);
18591
18622
  tools.push(...extractedTools);
18592
18623
  }
@@ -18596,35 +18627,24 @@ var dist = __webpack_require__(3519);
18596
18627
  }
18597
18628
  extractToolsFromRunCommand(runCommand, sourceFile) {
18598
18629
  const tools = [];
18599
- // Split by newlines and pipe tokens with surrounding whitespace to handle
18600
- // multi-line and piped commands without breaking on constructs like "cmd || true"
18601
18630
  const commands = runCommand.split(/\n|\s\|\s/).map((c)=>c.trim()).filter((c)=>c.length > 0 && !c.startsWith("#"));
18602
18631
  for (const command of commands){
18603
- // Extract the base command (first word)
18604
18632
  const parts = command.split(/\s+/);
18605
18633
  const baseCommand = parts[0];
18606
- // Skip shell built-ins and common utilities
18607
18634
  if (this.isShellBuiltin(baseCommand)) {
18608
18635
  continue;
18609
18636
  }
18610
- // Determine tool name and full command
18611
18637
  let toolName;
18612
18638
  const fullCommand = command;
18613
- // Handle special cases like "npm run", "mise run", etc.
18614
18639
  if (parts.length >= 2 && (baseCommand === "npm" || baseCommand === "mise") && parts[1] === "run") {
18615
18640
  if (parts.length >= 3 && parts[2]) {
18616
- // "npm run test" -> name: "npm run test"
18617
18641
  toolName = parts.slice(0, 3).join(" ");
18618
18642
  } else {
18619
- // Handle commands like "npm run" without a script name
18620
18643
  toolName = parts.slice(0, 2).join(" ");
18621
18644
  }
18622
18645
  } else if (parts.length >= 2) {
18623
- // For most tools, include the subcommand for better specificity
18624
- // e.g., "npm test", "npx biome", "pnpm lint"
18625
18646
  toolName = parts.slice(0, 2).join(" ");
18626
18647
  } else {
18627
- // Fallback to the base command if no subcommand is present
18628
18648
  toolName = baseCommand;
18629
18649
  }
18630
18650
  const phase = this.determineToolPhase(toolName, fullCommand);
@@ -18663,13 +18683,11 @@ var dist = __webpack_require__(3519);
18663
18683
  return builtins.includes(command);
18664
18684
  }
18665
18685
  determineToolPhase(toolName, fullCommand) {
18666
- // Use the same logic as script phase determination
18667
18686
  return determineScriptPhase(toolName, fullCommand);
18668
18687
  }
18669
18688
  deduplicateTools(tools) {
18670
18689
  const seen = new Map();
18671
18690
  for (const tool of tools){
18672
- // Use full command as key for deduplication
18673
18691
  const key = tool.fullCommand;
18674
18692
  if (!seen.has(key)) {
18675
18693
  seen.set(key, tool);
@@ -18926,9 +18944,7 @@ class FileSystemWorkflowGateway {
18926
18944
  const workflow = (0,dist/* .parse */.qg)(content);
18927
18945
  const candidates = extractSetupStepsFromWorkflow(workflow, config.setupActionPatterns);
18928
18946
  allCandidates.push(...candidates);
18929
- } catch {
18930
- // Skip unreadable or malformed YAML workflow files while continuing scan.
18931
- }
18947
+ } catch {}
18932
18948
  }
18933
18949
  return deduplicateCandidates(allCandidates);
18934
18950
  }
@@ -18965,8 +18981,7 @@ function createWorkflowGateway(cwd) {
18965
18981
  ;// CONCATENATED MODULE: ../core/src/gateways/workflow-gateway.ts
18966
18982
  /**
18967
18983
  * Gateway interface for GitHub Actions workflow operations.
18968
- */ // Re-export implementation and factory from the implementation file
18969
-
18984
+ */
18970
18985
 
18971
18986
  ;// CONCATENATED MODULE: ../core/src/gateways/index.ts
18972
18987
  /**
@@ -19152,7 +19167,6 @@ function isCopilotCodeScanningRule(rule) {
19152
19167
  * This module handles the logic of determining which GitHub Actions
19153
19168
  * setup steps should be added based on detected version files and package managers.
19154
19169
  */
19155
-
19156
19170
  /**
19157
19171
  * Creates an ActionVersionPort backed by a static version map.
19158
19172
  */ function createActionVersionPort(versionMap) {
@@ -21659,40 +21673,7 @@ const initCommand = defineCommand({
21659
21673
  return readFileNoFollow(filePath, MAX_AGENT_FILE_BYTES);
21660
21674
  }
21661
21675
  parseFrontmatter(content) {
21662
- const lines = content.split("\n");
21663
- if (lines[0]?.trim() !== "---") {
21664
- return null;
21665
- }
21666
- let endIndex = -1;
21667
- for(let i = 1; i < lines.length; i++){
21668
- if (lines[i]?.trim() === "---") {
21669
- endIndex = i;
21670
- break;
21671
- }
21672
- }
21673
- if (endIndex === -1) {
21674
- return null;
21675
- }
21676
- const yamlContent = lines.slice(1, endIndex).join("\n");
21677
- let data;
21678
- try {
21679
- const parsed = (0,dist/* .parse */.qg)(yamlContent);
21680
- data = parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
21681
- } catch {
21682
- return null;
21683
- }
21684
- const fieldLines = new Map();
21685
- for(let i = 1; i < endIndex; i++){
21686
- const match = lines[i]?.match(/^([^\s:][^:]*?):\s/);
21687
- if (match?.[1]) {
21688
- fieldLines.set(match[1], i + 1);
21689
- }
21690
- }
21691
- return {
21692
- data: data ?? {},
21693
- fieldLines,
21694
- frontmatterStartLine: 1
21695
- };
21676
+ return parseFrontmatter(content);
21696
21677
  }
21697
21678
  }
21698
21679
  /**
@@ -42047,16 +42028,14 @@ function hasFrontmatterDelimiters(content) {
42047
42028
  return false;
42048
42029
  }
42049
42030
 
42050
- ;// CONCATENATED MODULE: ../lint/src/validate-directory.ts
42031
+ ;// CONCATENATED MODULE: ../lint/src/lint-errors.ts
42051
42032
  /**
42052
- * Directory path validation for the public lint API.
42033
+ * Public lint API error types.
42053
42034
  *
42054
- * Validates user-supplied directory paths by rejecting unsafe inputs
42055
- * (control characters, path traversal, null bytes) and canonicalizing
42056
- * the path via `realpath()` for cross-platform compatibility.
42057
- */
42058
-
42059
- /**
42035
+ * Kept separate from filesystem-backed validation internals so the public
42036
+ * package barrel can export stable error contracts without depending on
42037
+ * implementation modules.
42038
+ */ /**
42060
42039
  * Thrown when user-supplied directory input fails validation.
42061
42040
  *
42062
42041
  * Consumers can catch this type to distinguish user-input errors
@@ -42067,6 +42046,17 @@ function hasFrontmatterDelimiters(content) {
42067
42046
  this.name = "LintValidationError";
42068
42047
  }
42069
42048
  }
42049
+
42050
+ ;// CONCATENATED MODULE: ../lint/src/validate-directory.ts
42051
+ /**
42052
+ * Directory path validation for the public lint API.
42053
+ *
42054
+ * Validates user-supplied directory paths by rejecting unsafe inputs
42055
+ * (control characters, path traversal, null bytes) and canonicalizing
42056
+ * the path via `realpath()` for cross-platform compatibility.
42057
+ */
42058
+
42059
+
42070
42060
  function isControlCharacter(code) {
42071
42061
  if (code >= 0x00 && code <= 0x1f) return true;
42072
42062
  if (code === 0x7f) return true;
@@ -42087,8 +42077,17 @@ function containsControlCharacters(value) {
42087
42077
  function sanitizeForErrorMessage(value) {
42088
42078
  return JSON.stringify(value);
42089
42079
  }
42090
- function hasPathTraversalSegment(directory) {
42091
- return directory.split(/[\\/]/).includes("..");
42080
+ /**
42081
+ * Detects path traversal segments in a user-supplied directory string.
42082
+ *
42083
+ * On Windows, both '/' and '\' are path separators, so the check splits on
42084
+ * both. On POSIX, '\' is a valid filename character (not a separator), so only
42085
+ * '/' is used as the split boundary to avoid false-rejecting legitimate paths
42086
+ * whose names contain backslashes. Any `platform` value other than `'win32'`
42087
+ * is treated as POSIX.
42088
+ */ function hasPathTraversalSegment(directory, platform = process.platform) {
42089
+ const separator = platform === "win32" ? /[\\/]/ : "/";
42090
+ return directory.split(separator).includes("..");
42092
42091
  }
42093
42092
  /**
42094
42093
  * Validates a user-supplied directory path for safety and existence.
@@ -42099,9 +42098,14 @@ function hasPathTraversalSegment(directory) {
42099
42098
  * the canonicalized absolute path (all symlinks resolved) so downstream
42100
42099
  * code always operates on real paths.
42101
42100
  *
42101
+ * @param directory - The path to validate.
42102
+ * @param platform - The platform identifier used for path-separator detection.
42103
+ * Defaults to `process.platform`. Only `'win32'` uses `\` as a separator;
42104
+ * all other values are treated as POSIX. Overridable for cross-platform unit
42105
+ * tests so that both branches can be exercised on any host OS.
42102
42106
  * @throws {LintValidationError} If the path is empty, contains control
42103
42107
  * characters, traversal segments, does not exist, or is not a directory.
42104
- */ async function validateDirectory(directory) {
42108
+ */ async function validateDirectory(directory, platform = process.platform) {
42105
42109
  if (directory.length === 0) {
42106
42110
  throw new LintValidationError("directory must not be empty");
42107
42111
  }
@@ -42109,7 +42113,7 @@ function hasPathTraversalSegment(directory) {
42109
42113
  if (containsControlCharacters(directory)) {
42110
42114
  throw new LintValidationError(`Invalid directory path (contains control characters): ${safeDir}`);
42111
42115
  }
42112
- if (hasPathTraversalSegment(directory)) {
42116
+ if (hasPathTraversalSegment(directory, platform)) {
42113
42117
  throw new LintValidationError(`Invalid directory path (path traversal detected): ${safeDir}`);
42114
42118
  }
42115
42119
  const resolved = (0,external_node_path_.resolve)(directory);
@@ -42163,6 +42167,7 @@ function hasPathTraversalSegment(directory) {
42163
42167
 
42164
42168
 
42165
42169
 
42170
+
42166
42171
  const LintTargetsSchema = schemas_object({
42167
42172
  skills: schemas_boolean().optional(),
42168
42173
  agents: schemas_boolean().optional(),
@@ -42171,7 +42176,14 @@ const LintTargetsSchema = schemas_object({
42171
42176
  }).strict().optional();
42172
42177
  const LintOptionsSchema = schemas_object({
42173
42178
  directory: schemas_string().min(1, "directory must not be empty"),
42174
- targets: LintTargetsSchema
42179
+ targets: LintTargetsSchema,
42180
+ // logger is a runtime object — `z.custom` validates its structural contract
42181
+ // at the boundary; TypeScript enforces it further at compile time.
42182
+ // `v === undefined` is NOT needed here: ZodOptional intercepts undefined
42183
+ // before the predicate is invoked, so the guard would be dead code.
42184
+ logger: custom((v)=>v !== null && typeof v === "object" && typeof v.warn === "function", {
42185
+ message: "logger must be an object with a .warn method"
42186
+ }).optional()
42175
42187
  }).strict();
42176
42188
  /** Converts a use-case lint output to the unified LintOutput shape. */ function toLintOutput(output, target, totalFiles) {
42177
42189
  const diagnostics = [];
@@ -42224,10 +42236,10 @@ async function lintHooks(targetDir) {
42224
42236
  });
42225
42237
  return toLintOutput(output, "hook", output.totalFiles);
42226
42238
  }
42227
- async function lintInstructions(targetDir) {
42239
+ async function lintInstructions(targetDir, logger) {
42228
42240
  const discoveryGateway = createInstructionFileDiscoveryGateway();
42229
42241
  const astGateway = createMarkdownAstGateway();
42230
- const commandsGateway = createFeedbackLoopCommandsGateway();
42242
+ const commandsGateway = createFeedbackLoopCommandsGateway(undefined, logger);
42231
42243
  const useCase = new AnalyzeInstructionQualityUseCase(discoveryGateway, astGateway, commandsGateway);
42232
42244
  const output = await useCase.execute({
42233
42245
  targetDir
@@ -42246,24 +42258,6 @@ async function lintInstructions(targetDir) {
42246
42258
  }
42247
42259
  };
42248
42260
  }
42249
- const LINT_TARGETS = [
42250
- {
42251
- key: "skills",
42252
- execute: lintSkills
42253
- },
42254
- {
42255
- key: "agents",
42256
- execute: lintAgents
42257
- },
42258
- {
42259
- key: "hooks",
42260
- execute: lintHooks
42261
- },
42262
- {
42263
- key: "instructions",
42264
- execute: lintInstructions
42265
- }
42266
- ];
42267
42261
  function isTargetEnabled(key, targets) {
42268
42262
  if (!targets) return true;
42269
42263
  const hasAnyEnabled = Object.values(targets).some(Boolean);
@@ -42307,7 +42301,25 @@ function isTargetEnabled(key, targets) {
42307
42301
  }
42308
42302
  throw error;
42309
42303
  }
42310
- const enabledTargets = LINT_TARGETS.filter((t)=>isTargetEnabled(t.key, parsed.targets));
42304
+ const lintTargets = [
42305
+ {
42306
+ key: "skills",
42307
+ execute: lintSkills
42308
+ },
42309
+ {
42310
+ key: "agents",
42311
+ execute: lintAgents
42312
+ },
42313
+ {
42314
+ key: "hooks",
42315
+ execute: lintHooks
42316
+ },
42317
+ {
42318
+ key: "instructions",
42319
+ execute: (dir)=>lintInstructions(dir, parsed.logger)
42320
+ }
42321
+ ];
42322
+ const enabledTargets = lintTargets.filter((t)=>isTargetEnabled(t.key, parsed.targets));
42311
42323
  const outputs = [];
42312
42324
  for (const target of enabledTargets){
42313
42325
  const rawOutput = await target.execute(targetDir);
@@ -42337,9 +42349,7 @@ function isTargetEnabled(key, targets) {
42337
42349
  * console.error(formatter.format(result.outputs));
42338
42350
  * }
42339
42351
  * ```
42340
- */ // ── Instruction quality result types (returned in LintOutput) ────────
42341
-
42342
- // ── Output formatters ────────────────────────────────────────────────
42352
+ */
42343
42353
 
42344
42354
 
42345
42355
 
@@ -42590,7 +42600,6 @@ You are a ${normalizedName} agent specialized in {domain/responsibility}.
42590
42600
  * @param agentName The name of the agent to create
42591
42601
  * @returns Result of the operation
42592
42602
  */ async execute(targetDir, agentName) {
42593
- // Validate the agent name using Zod schema
42594
42603
  const validationResult = AgentNameSchema.safeParse(agentName);
42595
42604
  if (!validationResult.success) {
42596
42605
  const errorMessage = validationResult.error.issues[0]?.message ?? "Invalid agent name";
@@ -42599,18 +42608,14 @@ You are a ${normalizedName} agent specialized in {domain/responsibility}.
42599
42608
  error: errorMessage
42600
42609
  };
42601
42610
  }
42602
- // Normalize the agent name
42603
42611
  const normalizedName = normalizeAgentName(validationResult.data);
42604
- // Validate that the normalized name is not empty (handles whitespace-only input)
42605
42612
  if (!normalizedName) {
42606
42613
  return {
42607
42614
  success: false,
42608
42615
  error: "Agent name is required"
42609
42616
  };
42610
42617
  }
42611
- // Get the file path
42612
42618
  const filePath = this.gateway.getAgentFilePath(targetDir, normalizedName);
42613
- // Check if the file already exists
42614
42619
  if (await this.gateway.agentFileExists(targetDir, normalizedName)) {
42615
42620
  return {
42616
42621
  success: false,
@@ -42618,11 +42623,8 @@ You are a ${normalizedName} agent specialized in {domain/responsibility}.
42618
42623
  filePath
42619
42624
  };
42620
42625
  }
42621
- // Ensure the directory exists
42622
42626
  await this.gateway.ensureAgentsDirectory(targetDir);
42623
- // Generate the content
42624
42627
  const content = generateAgentContent(normalizedName);
42625
- // Write the file
42626
42628
  await this.gateway.writeAgentFile(targetDir, normalizedName, content);
42627
42629
  return {
42628
42630
  success: true,
@@ -42718,7 +42720,6 @@ Copilot should use this skill when:
42718
42720
  * @param skillName The name of the skill to create
42719
42721
  * @returns Result of the operation
42720
42722
  */ async execute(targetDir, skillName) {
42721
- // Validate the skill name using Zod schema
42722
42723
  const validationResult = SkillNameSchema.safeParse(skillName);
42723
42724
  if (!validationResult.success) {
42724
42725
  const errorMessage = validationResult.error.issues[0]?.message ?? "Invalid skill name";
@@ -42727,19 +42728,15 @@ Copilot should use this skill when:
42727
42728
  error: errorMessage
42728
42729
  };
42729
42730
  }
42730
- // Normalize the skill name
42731
42731
  const normalizedName = normalizeSkillName(validationResult.data);
42732
- // Validate that the normalized name is not empty (handles whitespace-only input)
42733
42732
  if (!normalizedName) {
42734
42733
  return {
42735
42734
  success: false,
42736
42735
  error: "Skill name is required"
42737
42736
  };
42738
42737
  }
42739
- // Get the paths
42740
42738
  const skillDirectoryPath = this.gateway.getSkillDirectoryPath(targetDir, normalizedName);
42741
42739
  const skillFilePath = this.gateway.getSkillFilePath(targetDir, normalizedName);
42742
- // Check if the skill directory already exists
42743
42740
  if (await this.gateway.skillDirectoryExists(targetDir, normalizedName)) {
42744
42741
  return {
42745
42742
  success: false,
@@ -42747,11 +42744,8 @@ Copilot should use this skill when:
42747
42744
  skillDirectoryPath
42748
42745
  };
42749
42746
  }
42750
- // Ensure the skill directory exists
42751
42747
  await this.gateway.ensureSkillDirectory(targetDir, normalizedName);
42752
- // Generate the content
42753
42748
  const content = generateSkillContent(normalizedName);
42754
- // Write the SKILL.md file
42755
42749
  await this.gateway.writeSkillFile(targetDir, normalizedName, content);
42756
42750
  return {
42757
42751
  success: true,
@@ -51694,4 +51688,4 @@ if (installedChunkData !== 0) { // 0 means "already installed".'
51694
51688
  // module factories are used so entry inlining is disabled
51695
51689
  // startup
51696
51690
  // Load entry module and return exports
51697
- var __webpack_exports__ = __webpack_require__(5951);
51691
+ var __webpack_exports__ = __webpack_require__(2091);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lousy-agents/cli",
3
- "version": "5.12.2",
3
+ "version": "5.13.1",
4
4
  "description": "CLI scaffolding tool that sets up projects with structure, instructions, and feedback loops for AI coding assistants",
5
5
  "type": "module",
6
6
  "repository": {
@@ -45,7 +45,7 @@
45
45
  "citty": "0.2.2",
46
46
  "consola": "3.4.2",
47
47
  "eta": "4.6.0",
48
- "zod": "4.4.2"
48
+ "zod": "4.4.3"
49
49
  },
50
50
  "devDependencies": {
51
51
  "@lousy-agents/core": "*",
@@ -23,7 +23,7 @@
23
23
  "devDependencies": {
24
24
  "@biomejs/biome": "2.4.14",
25
25
  "@eslint/eslintrc": "3.3.5",
26
- "@lousy-agents/mcp": "5.12.1",
26
+ "@lousy-agents/mcp": "5.13.0",
27
27
  "@modelcontextprotocol/server-sequential-thinking": "2025.12.18",
28
28
  "@testing-library/jest-dom": "6.9.1",
29
29
  "@testing-library/react": "16.3.2",