@lousy-agents/cli 5.13.0 → 5.13.2

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.
@@ -10,7 +10,7 @@
10
10
  },
11
11
  "ghcr.io/devcontainers/features/copilot-cli:1.0.0": {},
12
12
  "ghcr.io/anthropics/devcontainer-features/claude-code:1": {
13
- "version": "v2.1.126"
13
+ "version": "v2.1.131"
14
14
  },
15
15
  "ghcr.io/rocker-org/devcontainer-features/apt-packages:1.0.2": {
16
16
  "packages": "yamllint, shellcheck",
@@ -14,15 +14,15 @@
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.2",
21
+ "@lousy-agents/mcp": "5.13.1",
22
22
  "@modelcontextprotocol/server-sequential-thinking": "2025.12.18",
23
23
  "@testcontainers/postgresql": "11.14.0",
24
24
  "@types/node": "24.12.2",
25
- "@upstash/context7-mcp": "2.2.3",
25
+ "@upstash/context7-mcp": "2.2.4",
26
26
  "chance": "1.1.13",
27
27
  "testcontainers": "11.14.0",
28
28
  "tsx": "4.21.0",
@@ -956,15 +956,15 @@
956
956
  }
957
957
  },
958
958
  "node_modules/@lousy-agents/mcp": {
959
- "version": "5.12.2",
960
- "resolved": "https://registry.npmjs.org/@lousy-agents/mcp/-/mcp-5.12.2.tgz",
961
- "integrity": "sha512-b4Ox1tB/gH/MFaXT+jS92mx2qtQKw1DK7oycLMSGbrKyBA4xXMK9ayvfMTdvx/zcFQc4URmuM5pSKa2Ng2PwCQ==",
959
+ "version": "5.13.1",
960
+ "resolved": "https://registry.npmjs.org/@lousy-agents/mcp/-/mcp-5.13.1.tgz",
961
+ "integrity": "sha512-hzkNWfxl7YwOhjA//v7xJTuEQtWw+jhECGy6n36leuMGlqSgMJnKmQqQe7YvpE1kyQNrXkW1MxvO8xKKjkdEUg==",
962
962
  "dev": true,
963
963
  "license": "BSD-2-Clause-Patent",
964
964
  "dependencies": {
965
965
  "@modelcontextprotocol/sdk": "1.29.0",
966
966
  "yaml": "2.8.4",
967
- "zod": "4.4.2"
967
+ "zod": "4.4.3"
968
968
  },
969
969
  "bin": {
970
970
  "lousy-agents-mcp": "dist/mcp-server.js"
@@ -1649,9 +1649,9 @@
1649
1649
  "license": "MIT"
1650
1650
  },
1651
1651
  "node_modules/@upstash/context7-mcp": {
1652
- "version": "2.2.3",
1653
- "resolved": "https://registry.npmjs.org/@upstash/context7-mcp/-/context7-mcp-2.2.3.tgz",
1654
- "integrity": "sha512-AZoARvwR1MA6TeMAXFDXg0LnA0DWdEervrrJxEXb3X8I2LAFRIdGWZG7keawHz5FYwFZ4M8oyuvtnoRemRb0SQ==",
1652
+ "version": "2.2.4",
1653
+ "resolved": "https://registry.npmjs.org/@upstash/context7-mcp/-/context7-mcp-2.2.4.tgz",
1654
+ "integrity": "sha512-6kpjAxif2w+HhxSPmyi+SUsVPgFKBi+xtFgKRc9f0fhLpZGTBX1QKWFmleAIZ4T69Ykynzxc4nHGEDrr219noA==",
1655
1655
  "dev": true,
1656
1656
  "license": "MIT",
1657
1657
  "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,18 +22,18 @@
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.2",
32
+ "@lousy-agents/mcp": "5.13.1",
33
33
  "@modelcontextprotocol/server-sequential-thinking": "2025.12.18",
34
34
  "@testcontainers/postgresql": "11.14.0",
35
35
  "@types/node": "24.12.2",
36
- "@upstash/context7-mcp": "2.2.3",
36
+ "@upstash/context7-mcp": "2.2.4",
37
37
  "chance": "1.1.13",
38
38
  "testcontainers": "11.14.0",
39
39
  "tsx": "4.21.0",
@@ -10,7 +10,7 @@
10
10
  },
11
11
  "ghcr.io/devcontainers/features/copilot-cli:1.0.0": {},
12
12
  "ghcr.io/anthropics/devcontainer-features/claude-code:1": {
13
- "version": "v2.1.126"
13
+ "version": "v2.1.131"
14
14
  },
15
15
  "ghcr.io/rocker-org/devcontainer-features/apt-packages:1.0.2": {
16
16
  "packages": "yamllint, shellcheck",
@@ -16,14 +16,14 @@
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.2",
23
+ "@lousy-agents/mcp": "5.13.1",
24
24
  "@modelcontextprotocol/server-sequential-thinking": "2025.12.18",
25
25
  "@types/node": "24.12.2",
26
- "@upstash/context7-mcp": "2.2.3",
26
+ "@upstash/context7-mcp": "2.2.4",
27
27
  "chance": "1.1.13",
28
28
  "tsx": "4.21.0",
29
29
  "typescript": "6.0.3",
package/dist/27.js CHANGED
@@ -7,10 +7,10 @@ __webpack_require__.r(__webpack_exports__);
7
7
 
8
8
  // EXPORTS
9
9
  __webpack_require__.d(__webpack_exports__, {
10
+ FSWatcher: () => (/* binding */ FSWatcher),
10
11
  WatchHelper: () => (/* binding */ WatchHelper),
11
12
  "default": () => (/* binding */ chokidar),
12
- watch: () => (/* binding */ watch),
13
- FSWatcher: () => (/* binding */ FSWatcher)
13
+ watch: () => (/* binding */ watch)
14
14
  });
15
15
 
16
16
  // EXTERNAL MODULE: external "node:events"
package/dist/653.js CHANGED
@@ -720,7 +720,7 @@ __webpack_require__.d(__webpack_exports__, {
720
720
  downloadTemplate: () => (/* reexport */ downloadTemplate)
721
721
  });
722
722
 
723
- // UNUSED EXPORTS: startShell, registryProvider
723
+ // UNUSED EXPORTS: registryProvider, startShell
724
724
 
725
725
  // EXTERNAL MODULE: ../../node_modules/giget/dist/_chunks/libs/nypm.mjs
726
726
  var nypm = __webpack_require__(292);
package/dist/962.js CHANGED
@@ -8,7 +8,7 @@ __webpack_require__.d(__webpack_exports__, {
8
8
  digest: () => (/* reexport */ node_digest)
9
9
  });
10
10
 
11
- // UNUSED EXPORTS: isEqual, serialize, hash
11
+ // UNUSED EXPORTS: hash, isEqual, serialize
12
12
 
13
13
  // EXTERNAL MODULE: external "node:crypto"
14
14
  var external_node_crypto_ = __webpack_require__(7598);
package/dist/index.js CHANGED
@@ -567,7 +567,7 @@ module.exports = webpackEmptyAsyncContext;
567
567
 
568
568
 
569
569
  },
570
- 1792(__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`);
18341
+ return [];
18342
+ }
18343
+ if (!parseResult.data.scripts) {
18345
18344
  return [];
18346
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);
@@ -18573,9 +18590,7 @@ var dist = __webpack_require__(3519);
18573
18590
  const workflow = parseYaml(content);
18574
18591
  const tools = this.extractToolsFromWorkflow(workflow, file);
18575
18592
  allTools.push(...tools);
18576
- } catch {
18577
- // Skip files that can't be parsed as valid YAML
18578
- }
18593
+ } catch {}
18579
18594
  }
18580
18595
  return this.deduplicateTools(allTools);
18581
18596
  }
@@ -18603,7 +18618,6 @@ var dist = __webpack_require__(3519);
18603
18618
  const stepObj = step;
18604
18619
  const run = stepObj.run;
18605
18620
  if (typeof run === "string") {
18606
- // Extract tools from run commands
18607
18621
  const extractedTools = this.extractToolsFromRunCommand(run, sourceFile);
18608
18622
  tools.push(...extractedTools);
18609
18623
  }
@@ -18613,35 +18627,24 @@ var dist = __webpack_require__(3519);
18613
18627
  }
18614
18628
  extractToolsFromRunCommand(runCommand, sourceFile) {
18615
18629
  const tools = [];
18616
- // Split by newlines and pipe tokens with surrounding whitespace to handle
18617
- // multi-line and piped commands without breaking on constructs like "cmd || true"
18618
18630
  const commands = runCommand.split(/\n|\s\|\s/).map((c)=>c.trim()).filter((c)=>c.length > 0 && !c.startsWith("#"));
18619
18631
  for (const command of commands){
18620
- // Extract the base command (first word)
18621
18632
  const parts = command.split(/\s+/);
18622
18633
  const baseCommand = parts[0];
18623
- // Skip shell built-ins and common utilities
18624
18634
  if (this.isShellBuiltin(baseCommand)) {
18625
18635
  continue;
18626
18636
  }
18627
- // Determine tool name and full command
18628
18637
  let toolName;
18629
18638
  const fullCommand = command;
18630
- // Handle special cases like "npm run", "mise run", etc.
18631
18639
  if (parts.length >= 2 && (baseCommand === "npm" || baseCommand === "mise") && parts[1] === "run") {
18632
18640
  if (parts.length >= 3 && parts[2]) {
18633
- // "npm run test" -> name: "npm run test"
18634
18641
  toolName = parts.slice(0, 3).join(" ");
18635
18642
  } else {
18636
- // Handle commands like "npm run" without a script name
18637
18643
  toolName = parts.slice(0, 2).join(" ");
18638
18644
  }
18639
18645
  } else if (parts.length >= 2) {
18640
- // For most tools, include the subcommand for better specificity
18641
- // e.g., "npm test", "npx biome", "pnpm lint"
18642
18646
  toolName = parts.slice(0, 2).join(" ");
18643
18647
  } else {
18644
- // Fallback to the base command if no subcommand is present
18645
18648
  toolName = baseCommand;
18646
18649
  }
18647
18650
  const phase = this.determineToolPhase(toolName, fullCommand);
@@ -18680,13 +18683,11 @@ var dist = __webpack_require__(3519);
18680
18683
  return builtins.includes(command);
18681
18684
  }
18682
18685
  determineToolPhase(toolName, fullCommand) {
18683
- // Use the same logic as script phase determination
18684
18686
  return determineScriptPhase(toolName, fullCommand);
18685
18687
  }
18686
18688
  deduplicateTools(tools) {
18687
18689
  const seen = new Map();
18688
18690
  for (const tool of tools){
18689
- // Use full command as key for deduplication
18690
18691
  const key = tool.fullCommand;
18691
18692
  if (!seen.has(key)) {
18692
18693
  seen.set(key, tool);
@@ -18943,9 +18944,7 @@ class FileSystemWorkflowGateway {
18943
18944
  const workflow = (0,dist/* .parse */.qg)(content);
18944
18945
  const candidates = extractSetupStepsFromWorkflow(workflow, config.setupActionPatterns);
18945
18946
  allCandidates.push(...candidates);
18946
- } catch {
18947
- // Skip unreadable or malformed YAML workflow files while continuing scan.
18948
- }
18947
+ } catch {}
18949
18948
  }
18950
18949
  return deduplicateCandidates(allCandidates);
18951
18950
  }
@@ -18982,8 +18981,7 @@ function createWorkflowGateway(cwd) {
18982
18981
  ;// CONCATENATED MODULE: ../core/src/gateways/workflow-gateway.ts
18983
18982
  /**
18984
18983
  * Gateway interface for GitHub Actions workflow operations.
18985
- */ // Re-export implementation and factory from the implementation file
18986
-
18984
+ */
18987
18985
 
18988
18986
  ;// CONCATENATED MODULE: ../core/src/gateways/index.ts
18989
18987
  /**
@@ -19169,7 +19167,6 @@ function isCopilotCodeScanningRule(rule) {
19169
19167
  * This module handles the logic of determining which GitHub Actions
19170
19168
  * setup steps should be added based on detected version files and package managers.
19171
19169
  */
19172
-
19173
19170
  /**
19174
19171
  * Creates an ActionVersionPort backed by a static version map.
19175
19172
  */ function createActionVersionPort(versionMap) {
@@ -42031,16 +42028,14 @@ function hasFrontmatterDelimiters(content) {
42031
42028
  return false;
42032
42029
  }
42033
42030
 
42034
- ;// CONCATENATED MODULE: ../lint/src/validate-directory.ts
42031
+ ;// CONCATENATED MODULE: ../lint/src/lint-errors.ts
42035
42032
  /**
42036
- * Directory path validation for the public lint API.
42033
+ * Public lint API error types.
42037
42034
  *
42038
- * Validates user-supplied directory paths by rejecting unsafe inputs
42039
- * (control characters, path traversal, null bytes) and canonicalizing
42040
- * the path via `realpath()` for cross-platform compatibility.
42041
- */
42042
-
42043
- /**
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
+ */ /**
42044
42039
  * Thrown when user-supplied directory input fails validation.
42045
42040
  *
42046
42041
  * Consumers can catch this type to distinguish user-input errors
@@ -42051,6 +42046,17 @@ function hasFrontmatterDelimiters(content) {
42051
42046
  this.name = "LintValidationError";
42052
42047
  }
42053
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
+
42054
42060
  function isControlCharacter(code) {
42055
42061
  if (code >= 0x00 && code <= 0x1f) return true;
42056
42062
  if (code === 0x7f) return true;
@@ -42071,8 +42077,17 @@ function containsControlCharacters(value) {
42071
42077
  function sanitizeForErrorMessage(value) {
42072
42078
  return JSON.stringify(value);
42073
42079
  }
42074
- function hasPathTraversalSegment(directory) {
42075
- 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("..");
42076
42091
  }
42077
42092
  /**
42078
42093
  * Validates a user-supplied directory path for safety and existence.
@@ -42083,9 +42098,14 @@ function hasPathTraversalSegment(directory) {
42083
42098
  * the canonicalized absolute path (all symlinks resolved) so downstream
42084
42099
  * code always operates on real paths.
42085
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.
42086
42106
  * @throws {LintValidationError} If the path is empty, contains control
42087
42107
  * characters, traversal segments, does not exist, or is not a directory.
42088
- */ async function validateDirectory(directory) {
42108
+ */ async function validateDirectory(directory, platform = process.platform) {
42089
42109
  if (directory.length === 0) {
42090
42110
  throw new LintValidationError("directory must not be empty");
42091
42111
  }
@@ -42093,7 +42113,7 @@ function hasPathTraversalSegment(directory) {
42093
42113
  if (containsControlCharacters(directory)) {
42094
42114
  throw new LintValidationError(`Invalid directory path (contains control characters): ${safeDir}`);
42095
42115
  }
42096
- if (hasPathTraversalSegment(directory)) {
42116
+ if (hasPathTraversalSegment(directory, platform)) {
42097
42117
  throw new LintValidationError(`Invalid directory path (path traversal detected): ${safeDir}`);
42098
42118
  }
42099
42119
  const resolved = (0,external_node_path_.resolve)(directory);
@@ -42147,6 +42167,7 @@ function hasPathTraversalSegment(directory) {
42147
42167
 
42148
42168
 
42149
42169
 
42170
+
42150
42171
  const LintTargetsSchema = schemas_object({
42151
42172
  skills: schemas_boolean().optional(),
42152
42173
  agents: schemas_boolean().optional(),
@@ -42155,7 +42176,14 @@ const LintTargetsSchema = schemas_object({
42155
42176
  }).strict().optional();
42156
42177
  const LintOptionsSchema = schemas_object({
42157
42178
  directory: schemas_string().min(1, "directory must not be empty"),
42158
- 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()
42159
42187
  }).strict();
42160
42188
  /** Converts a use-case lint output to the unified LintOutput shape. */ function toLintOutput(output, target, totalFiles) {
42161
42189
  const diagnostics = [];
@@ -42208,10 +42236,10 @@ async function lintHooks(targetDir) {
42208
42236
  });
42209
42237
  return toLintOutput(output, "hook", output.totalFiles);
42210
42238
  }
42211
- async function lintInstructions(targetDir) {
42239
+ async function lintInstructions(targetDir, logger) {
42212
42240
  const discoveryGateway = createInstructionFileDiscoveryGateway();
42213
42241
  const astGateway = createMarkdownAstGateway();
42214
- const commandsGateway = createFeedbackLoopCommandsGateway();
42242
+ const commandsGateway = createFeedbackLoopCommandsGateway(undefined, logger);
42215
42243
  const useCase = new AnalyzeInstructionQualityUseCase(discoveryGateway, astGateway, commandsGateway);
42216
42244
  const output = await useCase.execute({
42217
42245
  targetDir
@@ -42230,24 +42258,6 @@ async function lintInstructions(targetDir) {
42230
42258
  }
42231
42259
  };
42232
42260
  }
42233
- const LINT_TARGETS = [
42234
- {
42235
- key: "skills",
42236
- execute: lintSkills
42237
- },
42238
- {
42239
- key: "agents",
42240
- execute: lintAgents
42241
- },
42242
- {
42243
- key: "hooks",
42244
- execute: lintHooks
42245
- },
42246
- {
42247
- key: "instructions",
42248
- execute: lintInstructions
42249
- }
42250
- ];
42251
42261
  function isTargetEnabled(key, targets) {
42252
42262
  if (!targets) return true;
42253
42263
  const hasAnyEnabled = Object.values(targets).some(Boolean);
@@ -42291,7 +42301,25 @@ function isTargetEnabled(key, targets) {
42291
42301
  }
42292
42302
  throw error;
42293
42303
  }
42294
- 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));
42295
42323
  const outputs = [];
42296
42324
  for (const target of enabledTargets){
42297
42325
  const rawOutput = await target.execute(targetDir);
@@ -42321,9 +42349,7 @@ function isTargetEnabled(key, targets) {
42321
42349
  * console.error(formatter.format(result.outputs));
42322
42350
  * }
42323
42351
  * ```
42324
- */ // ── Instruction quality result types (returned in LintOutput) ────────
42325
-
42326
- // ── Output formatters ────────────────────────────────────────────────
42352
+ */
42327
42353
 
42328
42354
 
42329
42355
 
@@ -42574,7 +42600,6 @@ You are a ${normalizedName} agent specialized in {domain/responsibility}.
42574
42600
  * @param agentName The name of the agent to create
42575
42601
  * @returns Result of the operation
42576
42602
  */ async execute(targetDir, agentName) {
42577
- // Validate the agent name using Zod schema
42578
42603
  const validationResult = AgentNameSchema.safeParse(agentName);
42579
42604
  if (!validationResult.success) {
42580
42605
  const errorMessage = validationResult.error.issues[0]?.message ?? "Invalid agent name";
@@ -42583,18 +42608,14 @@ You are a ${normalizedName} agent specialized in {domain/responsibility}.
42583
42608
  error: errorMessage
42584
42609
  };
42585
42610
  }
42586
- // Normalize the agent name
42587
42611
  const normalizedName = normalizeAgentName(validationResult.data);
42588
- // Validate that the normalized name is not empty (handles whitespace-only input)
42589
42612
  if (!normalizedName) {
42590
42613
  return {
42591
42614
  success: false,
42592
42615
  error: "Agent name is required"
42593
42616
  };
42594
42617
  }
42595
- // Get the file path
42596
42618
  const filePath = this.gateway.getAgentFilePath(targetDir, normalizedName);
42597
- // Check if the file already exists
42598
42619
  if (await this.gateway.agentFileExists(targetDir, normalizedName)) {
42599
42620
  return {
42600
42621
  success: false,
@@ -42602,11 +42623,8 @@ You are a ${normalizedName} agent specialized in {domain/responsibility}.
42602
42623
  filePath
42603
42624
  };
42604
42625
  }
42605
- // Ensure the directory exists
42606
42626
  await this.gateway.ensureAgentsDirectory(targetDir);
42607
- // Generate the content
42608
42627
  const content = generateAgentContent(normalizedName);
42609
- // Write the file
42610
42628
  await this.gateway.writeAgentFile(targetDir, normalizedName, content);
42611
42629
  return {
42612
42630
  success: true,
@@ -42702,7 +42720,6 @@ Copilot should use this skill when:
42702
42720
  * @param skillName The name of the skill to create
42703
42721
  * @returns Result of the operation
42704
42722
  */ async execute(targetDir, skillName) {
42705
- // Validate the skill name using Zod schema
42706
42723
  const validationResult = SkillNameSchema.safeParse(skillName);
42707
42724
  if (!validationResult.success) {
42708
42725
  const errorMessage = validationResult.error.issues[0]?.message ?? "Invalid skill name";
@@ -42711,19 +42728,15 @@ Copilot should use this skill when:
42711
42728
  error: errorMessage
42712
42729
  };
42713
42730
  }
42714
- // Normalize the skill name
42715
42731
  const normalizedName = normalizeSkillName(validationResult.data);
42716
- // Validate that the normalized name is not empty (handles whitespace-only input)
42717
42732
  if (!normalizedName) {
42718
42733
  return {
42719
42734
  success: false,
42720
42735
  error: "Skill name is required"
42721
42736
  };
42722
42737
  }
42723
- // Get the paths
42724
42738
  const skillDirectoryPath = this.gateway.getSkillDirectoryPath(targetDir, normalizedName);
42725
42739
  const skillFilePath = this.gateway.getSkillFilePath(targetDir, normalizedName);
42726
- // Check if the skill directory already exists
42727
42740
  if (await this.gateway.skillDirectoryExists(targetDir, normalizedName)) {
42728
42741
  return {
42729
42742
  success: false,
@@ -42731,11 +42744,8 @@ Copilot should use this skill when:
42731
42744
  skillDirectoryPath
42732
42745
  };
42733
42746
  }
42734
- // Ensure the skill directory exists
42735
42747
  await this.gateway.ensureSkillDirectory(targetDir, normalizedName);
42736
- // Generate the content
42737
42748
  const content = generateSkillContent(normalizedName);
42738
- // Write the SKILL.md file
42739
42749
  await this.gateway.writeSkillFile(targetDir, normalizedName, content);
42740
42750
  return {
42741
42751
  success: true,
@@ -51678,4 +51688,4 @@ if (installedChunkData !== 0) { // 0 means "already installed".'
51678
51688
  // module factories are used so entry inlining is disabled
51679
51689
  // startup
51680
51690
  // Load entry module and return exports
51681
- var __webpack_exports__ = __webpack_require__(1792);
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.13.0",
3
+ "version": "5.13.2",
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": "*",
@@ -10,7 +10,7 @@
10
10
  },
11
11
  "ghcr.io/devcontainers/features/copilot-cli:1.0.0": {},
12
12
  "ghcr.io/anthropics/devcontainer-features/claude-code:1": {
13
- "version": "v2.1.126"
13
+ "version": "v2.1.131"
14
14
  },
15
15
  "ghcr.io/rocker-org/devcontainer-features/apt-packages:1.0.2": {
16
16
  "packages": "libnss3, libnspr4, libatk1.0-0t64, libatk-bridge2.0-0t64, libcups2t64, libdrm2, libdbus-1-3, libatspi2.0-0t64, libxcomposite1, libxdamage1, libxfixes3, libxrandr2, libgbm1, libxkbcommon0, libasound2t64, yamllint, shellcheck",
@@ -17,20 +17,20 @@
17
17
  "@mui/material": "9.0.0",
18
18
  "@mui/material-nextjs": "9.0.0",
19
19
  "next": "16.2.4",
20
- "react": "19.2.5",
21
- "react-dom": "19.2.5"
20
+ "react": "19.2.6",
21
+ "react-dom": "19.2.6"
22
22
  },
23
23
  "devDependencies": {
24
24
  "@biomejs/biome": "2.4.14",
25
25
  "@eslint/eslintrc": "3.3.5",
26
- "@lousy-agents/mcp": "5.12.2",
26
+ "@lousy-agents/mcp": "5.13.1",
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",
30
30
  "@types/node": "24.12.2",
31
31
  "@types/react": "19.2.14",
32
32
  "@types/react-dom": "19.2.3",
33
- "@upstash/context7-mcp": "2.2.3",
33
+ "@upstash/context7-mcp": "2.2.4",
34
34
  "@vitejs/plugin-react": "6.0.1",
35
35
  "@vitest/ui": "4.1.5",
36
36
  "eslint": "10.3.0",