@lousy-agents/mcp 5.18.1 → 5.19.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.
Files changed (2) hide show
  1. package/dist/mcp-server.js +166 -137
  2. package/package.json +3 -2
@@ -34967,8 +34967,9 @@ async function readVersionFileContent(targetDir, relativePath) {
34967
34967
  continue;
34968
34968
  }
34969
34969
  if (await file_system_utils_pathExistsWithinRoot(targetDir, pmConfig.manifestFile)) {
34970
- const lockfilePath = pmConfig.lockfile ? await file_system_utils_resolveSafePath(targetDir, pmConfig.lockfile) : undefined;
34971
- const hasLockfile = lockfilePath ? await file_system_utils_pathExistsWithinRoot(targetDir, pmConfig.lockfile) : false;
34970
+ const { lockfile } = pmConfig;
34971
+ const lockfilePath = lockfile ? await file_system_utils_resolveSafePath(targetDir, lockfile) : undefined;
34972
+ const hasLockfile = lockfilePath && lockfile ? await file_system_utils_pathExistsWithinRoot(targetDir, lockfile) : false;
34972
34973
  if (pmConfig.requiresLockfile && !hasLockfile) {
34973
34974
  continue;
34974
34975
  }
@@ -34985,8 +34986,9 @@ async function readVersionFileContent(targetDir, relativePath) {
34985
34986
  const packageManagers = [];
34986
34987
  for (const pmConfig of otherPackageManagers){
34987
34988
  if (await file_system_utils_pathExistsWithinRoot(targetDir, pmConfig.manifestFile)) {
34988
- const lockfilePath = pmConfig.lockfile ? await file_system_utils_resolveSafePath(targetDir, pmConfig.lockfile) : undefined;
34989
- const hasLockfile = lockfilePath ? await file_system_utils_pathExistsWithinRoot(targetDir, pmConfig.lockfile) : false;
34989
+ const { lockfile } = pmConfig;
34990
+ const lockfilePath = lockfile ? await file_system_utils_resolveSafePath(targetDir, lockfile) : undefined;
34991
+ const hasLockfile = lockfilePath && lockfile ? await file_system_utils_pathExistsWithinRoot(targetDir, lockfile) : false;
34990
34992
  if (pmConfig.requiresLockfile && !hasLockfile) {
34991
34993
  continue;
34992
34994
  }
@@ -35148,11 +35150,18 @@ function defaultExec(command, args, options) {
35148
35150
  throw new Error("Not authenticated");
35149
35151
  }
35150
35152
  try {
35151
- await this.octokit.rest.repos.createRepoRuleset({
35153
+ const params = {
35152
35154
  owner,
35153
35155
  repo,
35154
- ...payload
35155
- });
35156
+ name: payload.name,
35157
+ enforcement: payload.enforcement,
35158
+ target: payload.target,
35159
+ // biome-ignore lint/style/useNamingConvention: GitHub API schema requires snake_case
35160
+ bypass_actors: payload.bypass_actors,
35161
+ conditions: payload.conditions,
35162
+ rules: payload.rules
35163
+ };
35164
+ await this.octokit.rest.repos.createRepoRuleset(params);
35156
35165
  } catch (error) {
35157
35166
  const details = formatOctokitError(error);
35158
35167
  throw new Error(`Failed to create ruleset for ${owner}/${repo}: ${details}`);
@@ -37626,13 +37635,13 @@ const PackageJsonSchema = schemas_object({
37626
37635
  "shared"
37627
37636
  ]
37628
37637
  },
37629
- // Doctor-only skill locations
37638
+ // Additional skill roots (SKILL.md convention, no alternate layout documented)
37630
37639
  {
37631
37640
  id: "skill-pi",
37632
37641
  path: ".pi/skills",
37633
37642
  matchKind: "directory-prefix",
37634
37643
  primaryConstruct: "skill",
37635
- lintTarget: "none",
37644
+ lintTarget: "skills",
37636
37645
  harnessHints: [
37637
37646
  "pi"
37638
37647
  ]
@@ -37642,7 +37651,7 @@ const PackageJsonSchema = schemas_object({
37642
37651
  path: ".pi/prompts",
37643
37652
  matchKind: "directory-prefix",
37644
37653
  primaryConstruct: "skill",
37645
- lintTarget: "none",
37654
+ lintTarget: "skills",
37646
37655
  harnessHints: [
37647
37656
  "pi"
37648
37657
  ]
@@ -37762,13 +37771,13 @@ const PackageJsonSchema = schemas_object({
37762
37771
  ],
37763
37772
  instructionFormat: "claude-md"
37764
37773
  },
37765
- // Doctor-only construct locations
37774
+ // Subagent lint root
37766
37775
  {
37767
37776
  id: "subagent-claude",
37768
37777
  path: ".claude/agents",
37769
37778
  matchKind: "directory-prefix",
37770
37779
  primaryConstruct: "subagent",
37771
- lintTarget: "none",
37780
+ lintTarget: "subagents",
37772
37781
  harnessHints: [
37773
37782
  "claude"
37774
37783
  ]
@@ -37804,11 +37813,14 @@ const PackageJsonSchema = schemas_object({
37804
37813
  * Normalizes a repo-relative path for catalog matching.
37805
37814
  * Collapses `.` / `..` segments. Returns "" when the path is absolute or
37806
37815
  * escapes the repository root — callers treat "" as non-matching.
37807
- */ function normalizeRepoRelativePath(path) {
37808
- const normalized = path.replaceAll("\\", "/");
37816
+ */ function isAbsoluteRepoPath(normalized) {
37809
37817
  // POSIX absolute, UNC-style leading slash after backslash conversion,
37810
37818
  // and Windows drive-letter absolute (C:/..., d:\...).
37811
- if (normalized.startsWith("/") || /^[A-Za-z]:(\/|$)/.test(normalized)) {
37819
+ return normalized.startsWith("/") || /^[A-Za-z]:(\/|$)/.test(normalized);
37820
+ }
37821
+ function normalizeRepoRelativePath(path) {
37822
+ const normalized = path.replaceAll("\\", "/");
37823
+ if (isAbsoluteRepoPath(normalized)) {
37812
37824
  return "";
37813
37825
  }
37814
37826
  const resolved = [];
@@ -37816,10 +37828,10 @@ const PackageJsonSchema = schemas_object({
37816
37828
  if (segment === "" || segment === ".") {
37817
37829
  continue;
37818
37830
  }
37831
+ if (segment === ".." && resolved.length === 0) {
37832
+ return "";
37833
+ }
37819
37834
  if (segment === "..") {
37820
- if (resolved.length === 0) {
37821
- return "";
37822
- }
37823
37835
  resolved.pop();
37824
37836
  continue;
37825
37837
  }
@@ -37867,14 +37879,15 @@ function constructTypesForPath(path, catalog = AGENTIC_LOCATION_CATALOG) {
37867
37879
  if (!best) {
37868
37880
  return [];
37869
37881
  }
37870
- if (best.secondaryConstructs && best.secondaryConstructs.length > 0) {
37882
+ const secondary = best.secondaryConstructs ?? [];
37883
+ if (secondary.length === 0) {
37871
37884
  return [
37872
- best.primaryConstruct,
37873
- ...best.secondaryConstructs
37885
+ best.primaryConstruct
37874
37886
  ];
37875
37887
  }
37876
37888
  return [
37877
- best.primaryConstruct
37889
+ best.primaryConstruct,
37890
+ ...secondary
37878
37891
  ];
37879
37892
  }
37880
37893
  function lintTargetEntries(target, catalog = agentic_location_catalog_AGENTIC_LOCATION_CATALOG) {
@@ -37886,8 +37899,14 @@ function skillDirectoryRoots(catalog = agentic_location_catalog_AGENTIC_LOCATION
37886
37899
  function agentDirectoryRoots(catalog = AGENTIC_LOCATION_CATALOG) {
37887
37900
  return lintTargetEntries("agents", catalog).filter((entry)=>entry.matchKind === "directory-prefix").map((entry)=>entry.path);
37888
37901
  }
37902
+ function subagentDirectoryRoots(catalog = AGENTIC_LOCATION_CATALOG) {
37903
+ return lintTargetEntries("subagents", catalog).filter((entry)=>entry.matchKind === "directory-prefix").map((entry)=>entry.path);
37904
+ }
37905
+ function isHookConfigEntry(entry) {
37906
+ return entry.matchKind === "exact" && entry.hookPlatform != null;
37907
+ }
37889
37908
  function hookConfigPaths(catalog = AGENTIC_LOCATION_CATALOG) {
37890
- return lintTargetEntries("hooks", catalog).filter((entry)=>entry.matchKind === "exact" && entry.hookPlatform != null).map((entry)=>({
37909
+ return lintTargetEntries("hooks", catalog).filter(isHookConfigEntry).map((entry)=>({
37891
37910
  relativePath: entry.path,
37892
37911
  platform: entry.hookPlatform
37893
37912
  }));
@@ -57474,120 +57493,6 @@ function lib_isUint8Array(value) {
57474
57493
  */
57475
57494
  const remark = unified().use(remarkParse).use(remarkStringify).freeze()
57476
57495
 
57477
- ;// CONCATENATED MODULE: ../core/src/gateways/markdown-ast-gateway.ts
57478
- /**
57479
- * Gateway for parsing Markdown files into AST using remark.
57480
- * Extracts structural features like headings, code blocks, and paragraphs.
57481
- */
57482
-
57483
-
57484
- /** Maximum instruction file size: 1 MB */ const markdown_ast_gateway_MAX_INSTRUCTION_FILE_BYTES = 1_048_576;
57485
- /**
57486
- * Remark-based implementation of the Markdown AST gateway.
57487
- */ class RemarkMarkdownAstGateway {
57488
- async parseFile(filePath) {
57489
- const content = await read_file_no_follow_readFileNoFollow(filePath, markdown_ast_gateway_MAX_INSTRUCTION_FILE_BYTES);
57490
- return this.parseContent(content);
57491
- }
57492
- parseContent(content) {
57493
- const processor = remark();
57494
- const ast = processor.parse(content);
57495
- const headings = [];
57496
- const codeBlocks = [];
57497
- const inlineCodes = [];
57498
- lib_visit(ast, "heading", (node)=>{
57499
- const text = this.extractHeadingText(node);
57500
- headings.push({
57501
- text,
57502
- depth: node.depth,
57503
- position: {
57504
- line: node.position?.start.line ?? 0
57505
- }
57506
- });
57507
- });
57508
- // Track code blocks with their node index in root children
57509
- for(let i = 0; i < ast.children.length; i++){
57510
- const child = ast.children[i];
57511
- if (child.type === "code") {
57512
- const codeNode = child;
57513
- codeBlocks.push({
57514
- value: codeNode.value,
57515
- lang: codeNode.lang ?? undefined,
57516
- position: {
57517
- line: codeNode.position?.start.line ?? 0
57518
- },
57519
- nodeIndex: i
57520
- });
57521
- }
57522
- }
57523
- lib_visit(ast, "inlineCode", (node)=>{
57524
- inlineCodes.push({
57525
- value: node.value,
57526
- position: {
57527
- line: node.position?.start.line ?? 0
57528
- }
57529
- });
57530
- });
57531
- return {
57532
- headings,
57533
- codeBlocks,
57534
- inlineCodes,
57535
- ast
57536
- };
57537
- }
57538
- findConditionalKeywordsInProximity(structure, codeBlockNodeIndex, proximityWindow, keywords) {
57539
- const ast = structure.ast;
57540
- const endIndex = Math.min(ast.children.length, codeBlockNodeIndex + 1 + proximityWindow);
57541
- for(let i = codeBlockNodeIndex + 1; i < endIndex; i++){
57542
- const sibling = ast.children[i];
57543
- const text = extractTextFromNode(sibling);
57544
- const lowerText = text.toLowerCase();
57545
- for (const keyword of keywords){
57546
- if (containsWordBoundary(lowerText, keyword.toLowerCase())) {
57547
- return true;
57548
- }
57549
- }
57550
- }
57551
- return false;
57552
- }
57553
- extractHeadingText(node) {
57554
- const parts = [];
57555
- lib_visit(node, "text", (textNode)=>{
57556
- parts.push(textNode.value);
57557
- });
57558
- return parts.join("");
57559
- }
57560
- }
57561
- /**
57562
- * Checks if text contains a keyword at a word boundary (safe string matching, no regex).
57563
- */ function containsWordBoundary(text, keyword) {
57564
- const index = text.indexOf(keyword);
57565
- if (index === -1) {
57566
- return false;
57567
- }
57568
- const charBefore = index > 0 ? text[index - 1] : " ";
57569
- const charAfter = index + keyword.length < text.length ? text[index + keyword.length] : " ";
57570
- const isWordBoundaryBefore = !/[a-z0-9]/i.test(charBefore);
57571
- const isWordBoundaryAfter = !/[a-z0-9]/i.test(charAfter);
57572
- return isWordBoundaryBefore && isWordBoundaryAfter;
57573
- }
57574
- /**
57575
- * Extracts plain text content from any AST node recursively.
57576
- */ function extractTextFromNode(node) {
57577
- if (node.value && typeof node.value === "string") {
57578
- return node.value;
57579
- }
57580
- if (Array.isArray(node.children)) {
57581
- return node.children.map((child)=>extractTextFromNode(child)).join(" ");
57582
- }
57583
- return "";
57584
- }
57585
- /**
57586
- * Creates and returns the default Markdown AST gateway.
57587
- */ function createMarkdownAstGateway() {
57588
- return new RemarkMarkdownAstGateway();
57589
- }
57590
-
57591
57496
  ;// CONCATENATED MODULE: ../core/src/entities/instruction-quality.ts
57592
57497
  /**
57593
57498
  * Core domain entities for instruction quality analysis.
@@ -57639,6 +57544,9 @@ const HEADING_PATTERN_DESCRIPTIONS = new Map(Object.entries(HEADING_PATTERN_DESC
57639
57544
  * Orchestrates discovery, AST parsing, and scoring of feedback loop documentation.
57640
57545
  */
57641
57546
 
57547
+ /** Type guard narrowing an `unknown` AST child to {@link AstNodeLike}. */ function isAstNodeLike(node) {
57548
+ return typeof node === "object" && node !== null && "type" in node && typeof node.type === "string";
57549
+ }
57642
57550
  /** Maximum number of raw heading pattern entries accepted before deduplication. */ const MAX_RAW_HEADING_PATTERNS = 1000;
57643
57551
  /** Maximum number of heading patterns accepted in a single execute() call. */ const MAX_HEADING_PATTERNS = 50;
57644
57552
  /** Maximum character length for a single heading pattern. */ const analyze_instruction_quality_MAX_PATTERN_LENGTH = 200;
@@ -57994,6 +57902,9 @@ const HEADING_PATTERN_DESCRIPTIONS = new Map(Object.entries(HEADING_PATTERN_DESC
57994
57902
  return parts.join(" ");
57995
57903
  }
57996
57904
  collectText(node, parts) {
57905
+ if (!isAstNodeLike(node)) {
57906
+ return;
57907
+ }
57997
57908
  if (node.type === "text" && typeof node.value === "string") {
57998
57909
  parts.push(node.value);
57999
57910
  }
@@ -58140,6 +58051,124 @@ const HEADING_PATTERN_DESCRIPTIONS = new Map(Object.entries(HEADING_PATTERN_DESC
58140
58051
  }
58141
58052
  }
58142
58053
 
58054
+ ;// CONCATENATED MODULE: ../core/src/gateways/markdown-ast-gateway.ts
58055
+ /**
58056
+ * Gateway for parsing Markdown files into AST using remark.
58057
+ * Extracts structural features like headings, code blocks, and paragraphs.
58058
+ */
58059
+
58060
+
58061
+
58062
+ /** Maximum instruction file size: 1 MB */ const markdown_ast_gateway_MAX_INSTRUCTION_FILE_BYTES = 1_048_576;
58063
+ /**
58064
+ * Remark-based implementation of the Markdown AST gateway.
58065
+ */ class RemarkMarkdownAstGateway {
58066
+ async parseFile(filePath) {
58067
+ const content = await read_file_no_follow_readFileNoFollow(filePath, markdown_ast_gateway_MAX_INSTRUCTION_FILE_BYTES);
58068
+ return this.parseContent(content);
58069
+ }
58070
+ parseContent(content) {
58071
+ const processor = remark();
58072
+ const ast = processor.parse(content);
58073
+ const headings = [];
58074
+ const codeBlocks = [];
58075
+ const inlineCodes = [];
58076
+ lib_visit(ast, "heading", (node)=>{
58077
+ const text = this.extractHeadingText(node);
58078
+ headings.push({
58079
+ text,
58080
+ depth: node.depth,
58081
+ position: {
58082
+ line: node.position?.start.line ?? 0
58083
+ }
58084
+ });
58085
+ });
58086
+ // Track code blocks with their node index in root children
58087
+ for(let i = 0; i < ast.children.length; i++){
58088
+ const child = ast.children[i];
58089
+ if (child.type === "code") {
58090
+ const codeNode = child;
58091
+ codeBlocks.push({
58092
+ value: codeNode.value,
58093
+ lang: codeNode.lang ?? undefined,
58094
+ position: {
58095
+ line: codeNode.position?.start.line ?? 0
58096
+ },
58097
+ nodeIndex: i
58098
+ });
58099
+ }
58100
+ }
58101
+ lib_visit(ast, "inlineCode", (node)=>{
58102
+ inlineCodes.push({
58103
+ value: node.value,
58104
+ position: {
58105
+ line: node.position?.start.line ?? 0
58106
+ }
58107
+ });
58108
+ });
58109
+ return {
58110
+ headings,
58111
+ codeBlocks,
58112
+ inlineCodes,
58113
+ ast
58114
+ };
58115
+ }
58116
+ findConditionalKeywordsInProximity(structure, codeBlockNodeIndex, proximityWindow, keywords) {
58117
+ const ast = structure.ast;
58118
+ const endIndex = Math.min(ast.children.length, codeBlockNodeIndex + 1 + proximityWindow);
58119
+ for(let i = codeBlockNodeIndex + 1; i < endIndex; i++){
58120
+ const sibling = ast.children[i];
58121
+ const text = extractTextFromNode(sibling);
58122
+ const lowerText = text.toLowerCase();
58123
+ for (const keyword of keywords){
58124
+ if (containsWordBoundary(lowerText, keyword.toLowerCase())) {
58125
+ return true;
58126
+ }
58127
+ }
58128
+ }
58129
+ return false;
58130
+ }
58131
+ extractHeadingText(node) {
58132
+ const parts = [];
58133
+ lib_visit(node, "text", (textNode)=>{
58134
+ parts.push(textNode.value);
58135
+ });
58136
+ return parts.join("");
58137
+ }
58138
+ }
58139
+ /**
58140
+ * Checks if text contains a keyword at a word boundary (safe string matching, no regex).
58141
+ */ function containsWordBoundary(text, keyword) {
58142
+ const index = text.indexOf(keyword);
58143
+ if (index === -1) {
58144
+ return false;
58145
+ }
58146
+ const charBefore = index > 0 ? text[index - 1] : " ";
58147
+ const charAfter = index + keyword.length < text.length ? text[index + keyword.length] : " ";
58148
+ const isWordBoundaryBefore = !/[a-z0-9]/i.test(charBefore);
58149
+ const isWordBoundaryAfter = !/[a-z0-9]/i.test(charAfter);
58150
+ return isWordBoundaryBefore && isWordBoundaryAfter;
58151
+ }
58152
+ /**
58153
+ * Extracts plain text content from any AST node recursively.
58154
+ */ function extractTextFromNode(node) {
58155
+ if (!isAstNodeLike(node)) {
58156
+ return "";
58157
+ }
58158
+ if (node.value && typeof node.value === "string") {
58159
+ return node.value;
58160
+ }
58161
+ if (Array.isArray(node.children)) {
58162
+ return node.children.map((child)=>extractTextFromNode(child)).join(" ");
58163
+ }
58164
+ return "";
58165
+ }
58166
+ /**
58167
+ * Creates and returns the default Markdown AST gateway.
58168
+ */ function createMarkdownAstGateway() {
58169
+ return new RemarkMarkdownAstGateway();
58170
+ }
58171
+
58143
58172
  ;// CONCATENATED MODULE: ./src/tools/analyze-instruction-quality.ts
58144
58173
  /**
58145
58174
  * MCP tool handler for analyzing instruction quality.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lousy-agents/mcp",
3
- "version": "5.18.1",
3
+ "version": "5.19.0",
4
4
  "description": "MCP server for lousy-agents - provides AI coding assistant tools via the Model Context Protocol",
5
5
  "type": "module",
6
6
  "repository": {
@@ -33,7 +33,8 @@
33
33
  "lousy-agents-mcp": "dist/mcp-server.js"
34
34
  },
35
35
  "scripts": {
36
- "build": "rspack build --config rspack.config.ts && chmod +x dist/mcp-server.js"
36
+ "build": "rspack build --config rspack.config.ts && chmod +x dist/mcp-server.js",
37
+ "typecheck": "tsc --noEmit"
37
38
  },
38
39
  "dependencies": {
39
40
  "@modelcontextprotocol/sdk": "1.30.0",