@lousy-agents/mcp 5.20.2 → 5.21.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 +708 -15
  2. package/package.json +1 -1
@@ -6559,7 +6559,7 @@ function escapeJsonPtr(str) {
6559
6559
 
6560
6560
 
6561
6561
  },
6562
- 4583(__unused_rspack_module, __unused_rspack___webpack_exports__, __webpack_require__) {
6562
+ 7444(__unused_rspack_module, __unused_rspack___webpack_exports__, __webpack_require__) {
6563
6563
  // NAMESPACE OBJECT: ../../node_modules/micromark/lib/constructs.js
6564
6564
  var constructs_namespaceObject = {};
6565
6565
  __webpack_require__.r(constructs_namespaceObject);
@@ -38932,6 +38932,612 @@ function createWorkflowGateway(cwd) {
38932
38932
  }
38933
38933
  };
38934
38934
 
38935
+ ;// CONCATENATED MODULE: ../core/src/entities/source-position.ts
38936
+ /**
38937
+ * Pure helpers for mapping string offsets to 1-based line/column positions.
38938
+ */ /** 1-based line and column within a text document */ /**
38939
+ * Maps a 0-based string offset into a 1-based line and column.
38940
+ * Offsets are clamped to [0, content.length]. Newlines (`\n`) advance the line.
38941
+ */ function offsetToSourcePosition(content, offset) {
38942
+ const safeOffset = Math.max(0, Math.min(offset, content.length));
38943
+ let line = 1;
38944
+ let column = 1;
38945
+ for(let i = 0; i < safeOffset; i++){
38946
+ if (content[i] === "\n") {
38947
+ line += 1;
38948
+ column = 1;
38949
+ } else {
38950
+ column += 1;
38951
+ }
38952
+ }
38953
+ return {
38954
+ line,
38955
+ column
38956
+ };
38957
+ }
38958
+
38959
+ ;// CONCATENATED MODULE: ../core/src/lib/instruction-import-expand.ts
38960
+ /**
38961
+ * Pure Claude `@` import expander that builds an ordered EffectiveDocument.
38962
+ */
38963
+
38964
+ const DEFAULT_MAX_IMPORT_DEPTH = 4;
38965
+ const DEFAULT_MAX_UNIQUE_FILES = 64;
38966
+ const DEFAULT_MAX_EDGES = 256;
38967
+ const DEFAULT_MAX_EMITTED_BYTES = 512_000;
38968
+ const DEFAULT_MAX_FILE_BYTES = 1_048_576;
38969
+ /** Aligns with doctor HARD_IMPORT: line-start `@path` where path includes `/`. */ const HARD_IMPORT_GLOBAL_RE = /^@([^\s@][^\s]*)/gm;
38970
+ const FENCED_CODE_RE = /^(`{3,}|~{3,})[^\r\n]*\r?\n[\s\S]*?^\1[ \t]*$/gm;
38971
+ const INLINE_CODE_RE = /`+[^`\r\n]*`+/g;
38972
+ function resolveLimits(overrides) {
38973
+ return {
38974
+ maxDepth: overrides?.maxDepth ?? DEFAULT_MAX_IMPORT_DEPTH,
38975
+ maxUniqueFiles: overrides?.maxUniqueFiles ?? DEFAULT_MAX_UNIQUE_FILES,
38976
+ maxEdges: overrides?.maxEdges ?? DEFAULT_MAX_EDGES,
38977
+ maxEmittedBytes: overrides?.maxEmittedBytes ?? DEFAULT_MAX_EMITTED_BYTES,
38978
+ maxFileBytes: overrides?.maxFileBytes ?? DEFAULT_MAX_FILE_BYTES
38979
+ };
38980
+ }
38981
+ function toPosixRelative(pathValue) {
38982
+ return pathValue.split(external_node_path_.sep).join("/");
38983
+ }
38984
+ function isInsideCodeRegion(index, regions) {
38985
+ return regions.some((region)=>index >= region.start && index < region.end);
38986
+ }
38987
+ function collectRegexRanges(content, pattern, skipInside) {
38988
+ const regions = [];
38989
+ for (const match of content.matchAll(pattern)){
38990
+ if (match.index === undefined) {
38991
+ continue;
38992
+ }
38993
+ if (skipInside && isInsideCodeRegion(match.index, skipInside)) {
38994
+ continue;
38995
+ }
38996
+ regions.push({
38997
+ start: match.index,
38998
+ end: match.index + match[0].length
38999
+ });
39000
+ }
39001
+ return regions;
39002
+ }
39003
+ function findCodeRegions(content) {
39004
+ const fenced = collectRegexRanges(content, FENCED_CODE_RE);
39005
+ const inline = collectRegexRanges(content, INLINE_CODE_RE, fenced);
39006
+ return [
39007
+ ...fenced,
39008
+ ...inline
39009
+ ];
39010
+ }
39011
+ function findImportTokens(content) {
39012
+ const codeRegions = findCodeRegions(content);
39013
+ const tokens = [];
39014
+ for (const match of content.matchAll(HARD_IMPORT_GLOBAL_RE)){
39015
+ if (match.index === undefined) {
39016
+ continue;
39017
+ }
39018
+ const rawTarget = match[1];
39019
+ if (!rawTarget?.includes("/")) {
39020
+ continue;
39021
+ }
39022
+ if (isInsideCodeRegion(match.index, codeRegions)) {
39023
+ continue;
39024
+ }
39025
+ tokens.push({
39026
+ start: match.index,
39027
+ end: match.index + match[0].length,
39028
+ rawTarget
39029
+ });
39030
+ }
39031
+ return tokens;
39032
+ }
39033
+ function rebaseSegments(segments, offset) {
39034
+ return segments.map((segment)=>({
39035
+ ...segment,
39036
+ effectiveRange: {
39037
+ start: segment.effectiveRange.start + offset,
39038
+ end: segment.effectiveRange.end + offset
39039
+ }
39040
+ }));
39041
+ }
39042
+ function createLiteralSegment(contentOffset, sourcePath, sourceStart, text, importChain) {
39043
+ if (text.length === 0) {
39044
+ return undefined;
39045
+ }
39046
+ return {
39047
+ effectiveRange: {
39048
+ start: contentOffset,
39049
+ end: contentOffset + text.length
39050
+ },
39051
+ sourcePath,
39052
+ sourceRange: {
39053
+ start: sourceStart,
39054
+ end: sourceStart + text.length
39055
+ },
39056
+ importChain: [
39057
+ ...importChain
39058
+ ]
39059
+ };
39060
+ }
39061
+ const RULE_ID_BY_STATUS = {
39062
+ unresolved: "instruction/import-unresolved",
39063
+ "not-regular": "instruction/import-unresolved",
39064
+ escape: "instruction/import-escape",
39065
+ absolute: "instruction/import-escape",
39066
+ home: "instruction/import-escape",
39067
+ symlink: "instruction/import-symlink",
39068
+ cycle: "instruction/import-cycle",
39069
+ "depth-exceeded": "instruction/import-depth-exceeded",
39070
+ "size-exceeded": "instruction/import-size-exceeded",
39071
+ resolved: undefined
39072
+ };
39073
+ const FAILURE_MESSAGE_BY_STATUS = {
39074
+ unresolved: (rawTarget)=>`Import target could not be resolved: ${rawTarget}`,
39075
+ escape: (rawTarget)=>`Import target escapes the repository root: ${rawTarget}`,
39076
+ absolute: (rawTarget)=>`Absolute import paths are not allowed: ${rawTarget}`,
39077
+ home: (rawTarget)=>`Home-directory import paths are not allowed: ${rawTarget}`,
39078
+ symlink: (rawTarget)=>`Import target path contains a symbolic link: ${rawTarget}`,
39079
+ cycle: (rawTarget)=>`Import cycle detected while resolving: ${rawTarget}`,
39080
+ "depth-exceeded": (rawTarget)=>`Import depth limit exceeded while resolving: ${rawTarget}`,
39081
+ "size-exceeded": (rawTarget)=>`Import expansion size or graph limit exceeded while resolving: ${rawTarget}`,
39082
+ "not-regular": (rawTarget)=>`Import target is not a regular file: ${rawTarget}`,
39083
+ resolved: ()=>""
39084
+ };
39085
+ function ruleIdForStatus(status) {
39086
+ return RULE_ID_BY_STATUS[status];
39087
+ }
39088
+ function messageForFailure(status, rawTarget) {
39089
+ return FAILURE_MESSAGE_BY_STATUS[status](rawTarget);
39090
+ }
39091
+ function classifyPathError(error) {
39092
+ if (!(error instanceof Error)) {
39093
+ return "unresolved";
39094
+ }
39095
+ const message = error.message;
39096
+ if (message.includes("outside target directory") || message.includes("invalid-path") || message.includes("outside-workspace")) {
39097
+ return "escape";
39098
+ }
39099
+ if (message.includes("Symlinks are not allowed") || message.includes("symbolic link") || message.includes("path-alias")) {
39100
+ return "symlink";
39101
+ }
39102
+ if (message.includes("exceeds size limit") || message.includes("too-large")) {
39103
+ return "size-exceeded";
39104
+ }
39105
+ return "unresolved";
39106
+ }
39107
+ function normalizeRelativeWithinRoot(importerRelativePath, rawTarget) {
39108
+ if (rawTarget === "~" || rawTarget.startsWith("~/") || rawTarget.startsWith("~\\")) {
39109
+ return {
39110
+ ok: false,
39111
+ status: "home"
39112
+ };
39113
+ }
39114
+ if ((0,external_node_path_.isAbsolute)(rawTarget)) {
39115
+ return {
39116
+ ok: false,
39117
+ status: "absolute"
39118
+ };
39119
+ }
39120
+ const importerDir = (0,external_node_path_.dirname)(importerRelativePath);
39121
+ const joined = (0,external_node_path_.normalize)((0,external_node_path_.join)(importerDir, rawTarget));
39122
+ const normalized = joined === "." ? "" : joined;
39123
+ if (normalized === ".." || normalized.startsWith(`..${external_node_path_.sep}`) || normalized.startsWith("../") || normalized.startsWith("..\\")) {
39124
+ return {
39125
+ ok: false,
39126
+ status: "escape"
39127
+ };
39128
+ }
39129
+ if (!normalized) {
39130
+ return {
39131
+ ok: false,
39132
+ status: "unresolved"
39133
+ };
39134
+ }
39135
+ return {
39136
+ ok: true,
39137
+ relativePath: toPosixRelative(normalized)
39138
+ };
39139
+ }
39140
+ function isUnsafeRootPath(rootRelativePath) {
39141
+ return !rootRelativePath || rootRelativePath === ".." || rootRelativePath.startsWith(`..${external_node_path_.sep}`) || rootRelativePath.startsWith("../") || (0,external_node_path_.isAbsolute)(rootRelativePath);
39142
+ }
39143
+ /**
39144
+ * Owns expansion bookkeeping for a single buildEffectiveDocument call.
39145
+ * Mutations stay on the session instance rather than shared parameter bags.
39146
+ */ class ExpansionSession {
39147
+ limits;
39148
+ contentCache = new Map();
39149
+ uniqueFiles = new Set();
39150
+ edges = [];
39151
+ diagnostics = [];
39152
+ emittedBytes = 0;
39153
+ constructor(limits){
39154
+ this.limits = limits;
39155
+ }
39156
+ get edgeCount() {
39157
+ return this.edges.length;
39158
+ }
39159
+ get remainingEmitBudget() {
39160
+ return this.limits.maxEmittedBytes - this.emittedBytes;
39161
+ }
39162
+ isEmitBudgetExhausted() {
39163
+ return this.emittedBytes >= this.limits.maxEmittedBytes;
39164
+ }
39165
+ snapshot() {
39166
+ return {
39167
+ edges: [
39168
+ ...this.edges
39169
+ ],
39170
+ diagnostics: [
39171
+ ...this.diagnostics
39172
+ ]
39173
+ };
39174
+ }
39175
+ recordEdge(edge) {
39176
+ this.edges.push(edge);
39177
+ // Failures always carry ruleId; resolved edges do not.
39178
+ if (!edge.ruleId) {
39179
+ return;
39180
+ }
39181
+ this.diagnostics.push({
39182
+ ruleId: edge.ruleId,
39183
+ message: messageForFailure(edge.status, edge.rawTarget),
39184
+ filePath: edge.importer,
39185
+ range: edge.tokenRange
39186
+ });
39187
+ }
39188
+ recordFailure(importer, token, status, target) {
39189
+ this.recordEdge({
39190
+ importer,
39191
+ tokenRange: {
39192
+ start: token.start,
39193
+ end: token.end
39194
+ },
39195
+ rawTarget: token.rawTarget,
39196
+ target,
39197
+ status,
39198
+ ruleId: ruleIdForStatus(status)
39199
+ });
39200
+ }
39201
+ recordResolved(importer, token, target) {
39202
+ this.recordEdge({
39203
+ importer,
39204
+ tokenRange: {
39205
+ start: token.start,
39206
+ end: token.end
39207
+ },
39208
+ rawTarget: token.rawTarget,
39209
+ target,
39210
+ status: "resolved"
39211
+ });
39212
+ }
39213
+ /**
39214
+ * Emit up to `text` against the remaining byte budget.
39215
+ * Returns emitted text (possibly clipped) and whether the full text fit.
39216
+ */ takeEmitBudget(text) {
39217
+ if (text.length === 0) {
39218
+ return {
39219
+ emitted: "",
39220
+ complete: true
39221
+ };
39222
+ }
39223
+ const room = this.remainingEmitBudget;
39224
+ if (room <= 0) {
39225
+ return {
39226
+ emitted: "",
39227
+ complete: false
39228
+ };
39229
+ }
39230
+ if (text.length <= room) {
39231
+ this.emittedBytes += text.length;
39232
+ return {
39233
+ emitted: text,
39234
+ complete: true
39235
+ };
39236
+ }
39237
+ this.emittedBytes += room;
39238
+ return {
39239
+ emitted: text.slice(0, room),
39240
+ complete: false
39241
+ };
39242
+ }
39243
+ async readFile(repoRoot, relativePath) {
39244
+ const cached = this.contentCache.get(relativePath);
39245
+ if (cached !== undefined) {
39246
+ return {
39247
+ ok: true,
39248
+ content: cached
39249
+ };
39250
+ }
39251
+ try {
39252
+ await file_system_utils_resolvePathWithinRoot(repoRoot, relativePath);
39253
+ } catch (error) {
39254
+ return {
39255
+ ok: false,
39256
+ status: classifyPathError(error)
39257
+ };
39258
+ }
39259
+ try {
39260
+ const stats = await file_system_utils_statWithinRoot(repoRoot, relativePath);
39261
+ if (stats.isSymbolicLink) {
39262
+ return {
39263
+ ok: false,
39264
+ status: "symlink"
39265
+ };
39266
+ }
39267
+ if (!stats.isFile) {
39268
+ return {
39269
+ ok: false,
39270
+ status: "not-regular"
39271
+ };
39272
+ }
39273
+ } catch (error) {
39274
+ return {
39275
+ ok: false,
39276
+ status: classifyPathError(error)
39277
+ };
39278
+ }
39279
+ const isNewUnique = !this.uniqueFiles.has(relativePath);
39280
+ if (isNewUnique && this.uniqueFiles.size >= this.limits.maxUniqueFiles) {
39281
+ return {
39282
+ ok: false,
39283
+ status: "size-exceeded"
39284
+ };
39285
+ }
39286
+ try {
39287
+ const content = await file_system_utils_readTextWithinRoot(repoRoot, relativePath, this.limits.maxFileBytes);
39288
+ this.contentCache.set(relativePath, content);
39289
+ if (isNewUnique) {
39290
+ this.uniqueFiles.add(relativePath);
39291
+ }
39292
+ return {
39293
+ ok: true,
39294
+ content
39295
+ };
39296
+ } catch (error) {
39297
+ return {
39298
+ ok: false,
39299
+ status: classifyPathError(error)
39300
+ };
39301
+ }
39302
+ }
39303
+ }
39304
+ class ContentBuilder {
39305
+ outputText = "";
39306
+ builtSegments = [];
39307
+ get output() {
39308
+ return this.outputText;
39309
+ }
39310
+ get length() {
39311
+ return this.outputText.length;
39312
+ }
39313
+ get segments() {
39314
+ return this.builtSegments;
39315
+ }
39316
+ appendLiteral(sourcePath, sourceStart, text, importChain) {
39317
+ const segment = createLiteralSegment(this.outputText.length, sourcePath, sourceStart, text, importChain);
39318
+ if (!segment) {
39319
+ return;
39320
+ }
39321
+ this.builtSegments.push(segment);
39322
+ this.outputText += text;
39323
+ }
39324
+ appendExpanded(content, segments) {
39325
+ this.outputText += content;
39326
+ this.builtSegments.push(...segments);
39327
+ }
39328
+ toResult() {
39329
+ return {
39330
+ content: this.outputText,
39331
+ segments: [
39332
+ ...this.builtSegments
39333
+ ]
39334
+ };
39335
+ }
39336
+ }
39337
+ function emitSourceSlice(session, builder, sourcePath, content, from, to, importChain) {
39338
+ if (to <= from) {
39339
+ return true;
39340
+ }
39341
+ const { emitted, complete } = session.takeEmitBudget(content.slice(from, to));
39342
+ builder.appendLiteral(sourcePath, from, emitted, importChain);
39343
+ return complete;
39344
+ }
39345
+ async function expandToken(session, repoRoot, importerPath, token, hop, stack, importChain, outputOffset) {
39346
+ if (session.edgeCount >= session.limits.maxEdges) {
39347
+ session.recordFailure(importerPath, token, "size-exceeded");
39348
+ return {
39349
+ kind: "unexpanded"
39350
+ };
39351
+ }
39352
+ const nextHop = hop + 1;
39353
+ if (nextHop > session.limits.maxDepth) {
39354
+ session.recordFailure(importerPath, token, "depth-exceeded");
39355
+ return {
39356
+ kind: "unexpanded"
39357
+ };
39358
+ }
39359
+ const normalized = normalizeRelativeWithinRoot(importerPath, token.rawTarget);
39360
+ if (!normalized.ok) {
39361
+ session.recordFailure(importerPath, token, normalized.status);
39362
+ return {
39363
+ kind: "unexpanded"
39364
+ };
39365
+ }
39366
+ const targetPath = normalized.relativePath;
39367
+ if (stack.includes(targetPath)) {
39368
+ session.recordFailure(importerPath, token, "cycle", targetPath);
39369
+ return {
39370
+ kind: "unexpanded"
39371
+ };
39372
+ }
39373
+ const readResult = await session.readFile(repoRoot, targetPath);
39374
+ if (!readResult.ok) {
39375
+ session.recordFailure(importerPath, token, readResult.status, targetPath);
39376
+ return {
39377
+ kind: "unexpanded"
39378
+ };
39379
+ }
39380
+ if (session.isEmitBudgetExhausted()) {
39381
+ session.recordFailure(importerPath, token, "size-exceeded", targetPath);
39382
+ return {
39383
+ kind: "unexpanded"
39384
+ };
39385
+ }
39386
+ const child = await expandContent(session, repoRoot, targetPath, readResult.content, nextHop, [
39387
+ ...stack,
39388
+ targetPath
39389
+ ], [
39390
+ ...importChain,
39391
+ targetPath
39392
+ ]);
39393
+ session.recordResolved(importerPath, token, targetPath);
39394
+ return {
39395
+ kind: "expanded",
39396
+ content: child.content,
39397
+ segments: rebaseSegments(child.segments, outputOffset)
39398
+ };
39399
+ }
39400
+ async function expandContent(session, repoRoot, sourcePath, content, hop, stack, importChain) {
39401
+ const tokens = findImportTokens(content);
39402
+ const builder = new ContentBuilder();
39403
+ let cursor = 0;
39404
+ for (const token of tokens){
39405
+ const literalOk = emitSourceSlice(session, builder, sourcePath, content, cursor, token.start, importChain);
39406
+ if (!literalOk) {
39407
+ session.recordFailure(sourcePath, token, "size-exceeded");
39408
+ emitSourceSlice(session, builder, sourcePath, content, token.start, content.length, importChain);
39409
+ return builder.toResult();
39410
+ }
39411
+ const expansion = await expandToken(session, repoRoot, sourcePath, token, hop, stack, importChain, builder.length);
39412
+ if (expansion.kind === "expanded") {
39413
+ builder.appendExpanded(expansion.content, expansion.segments);
39414
+ } else {
39415
+ const tokenOk = emitSourceSlice(session, builder, sourcePath, content, token.start, token.end, importChain);
39416
+ if (!tokenOk) {
39417
+ return builder.toResult();
39418
+ }
39419
+ }
39420
+ cursor = token.end;
39421
+ }
39422
+ emitSourceSlice(session, builder, sourcePath, content, cursor, content.length, importChain);
39423
+ return builder.toResult();
39424
+ }
39425
+ /**
39426
+ * Build an ordered effective document by expanding verified Claude `@` imports.
39427
+ */ async function buildEffectiveDocument(input) {
39428
+ const limits = resolveLimits(input.limits);
39429
+ const rootRelativePath = toPosixRelative((0,external_node_path_.normalize)(input.rootRelativePath));
39430
+ if (isUnsafeRootPath(rootRelativePath)) {
39431
+ throw new Error(`Root path is outside repository root: ${input.rootRelativePath}`);
39432
+ }
39433
+ const session = new ExpansionSession(limits);
39434
+ const rootRead = await session.readFile(input.repoRoot, rootRelativePath);
39435
+ if (!rootRead.ok) {
39436
+ throw new Error(`Unable to read root instruction file ${rootRelativePath}: ${rootRead.status}`);
39437
+ }
39438
+ const expanded = await expandContent(session, input.repoRoot, rootRelativePath, rootRead.content, 0, [
39439
+ rootRelativePath
39440
+ ], [
39441
+ rootRelativePath
39442
+ ]);
39443
+ const snapshot = session.snapshot();
39444
+ return {
39445
+ root: rootRelativePath,
39446
+ content: expanded.content,
39447
+ orderedSegments: expanded.segments,
39448
+ edges: snapshot.edges,
39449
+ expansionDiagnostics: snapshot.diagnostics
39450
+ };
39451
+ }
39452
+
39453
+ ;// CONCATENATED MODULE: ../core/src/gateways/claude-instruction-import-expander.ts
39454
+ /**
39455
+ * Adapter that expands Claude `@path` imports via the pure expander library.
39456
+ */
39457
+
39458
+
39459
+
39460
+ function toRepoRelativePosix(repoRoot, absoluteFilePath) {
39461
+ const relativePath = (0,external_node_path_.relative)(repoRoot, absoluteFilePath);
39462
+ if (relativePath.length === 0 || relativePath.startsWith(`..${external_node_path_.sep}`) || relativePath === ".." || (0,external_node_path_.isAbsolute)(relativePath)) {
39463
+ throw new Error(`Claude instruction path is outside repository root: ${absoluteFilePath}`);
39464
+ }
39465
+ return relativePath.split(external_node_path_.sep).join("/");
39466
+ }
39467
+ function toAbsolutePath(repoRoot, relativePosixPath) {
39468
+ return (0,external_node_path_.join)(repoRoot, ...relativePosixPath.split("/"));
39469
+ }
39470
+ async function mapExpansionDiagnostics(repoRoot, diagnostics) {
39471
+ const contentByRelative = new Map();
39472
+ const mapped = [];
39473
+ for (const diagnostic of diagnostics){
39474
+ const absolutePath = toAbsolutePath(repoRoot, diagnostic.filePath);
39475
+ let content = contentByRelative.get(diagnostic.filePath);
39476
+ if (content === undefined) {
39477
+ content = await (0,promises_.readFile)(absolutePath, "utf8");
39478
+ contentByRelative.set(diagnostic.filePath, content);
39479
+ }
39480
+ if (diagnostic.range === undefined) {
39481
+ mapped.push({
39482
+ ruleId: diagnostic.ruleId,
39483
+ message: diagnostic.message,
39484
+ filePath: absolutePath,
39485
+ line: 1,
39486
+ column: 1
39487
+ });
39488
+ continue;
39489
+ }
39490
+ const start = offsetToSourcePosition(content, diagnostic.range.start);
39491
+ const end = offsetToSourcePosition(content, diagnostic.range.end);
39492
+ mapped.push({
39493
+ ruleId: diagnostic.ruleId,
39494
+ message: diagnostic.message,
39495
+ filePath: absolutePath,
39496
+ line: start.line,
39497
+ column: start.column,
39498
+ endLine: end.line,
39499
+ endColumn: end.column
39500
+ });
39501
+ }
39502
+ return mapped;
39503
+ }
39504
+ function collectResolvedImports(repoRoot, edges) {
39505
+ const resolved = [];
39506
+ const seen = new Set();
39507
+ for (const edge of edges){
39508
+ if (edge.status !== "resolved" || edge.target === undefined) {
39509
+ continue;
39510
+ }
39511
+ const absolute = toAbsolutePath(repoRoot, edge.target);
39512
+ if (seen.has(absolute)) {
39513
+ continue;
39514
+ }
39515
+ seen.add(absolute);
39516
+ resolved.push(absolute);
39517
+ }
39518
+ return resolved;
39519
+ }
39520
+ /**
39521
+ * Creates the default Claude instruction import expander used by composition roots.
39522
+ */ function createClaudeInstructionImportExpander() {
39523
+ return {
39524
+ async expandClaudeEntrypoint (input) {
39525
+ const rootRelativePath = toRepoRelativePosix(input.repoRoot, input.absoluteFilePath);
39526
+ const document = await buildEffectiveDocument({
39527
+ repoRoot: input.repoRoot,
39528
+ rootRelativePath
39529
+ });
39530
+ const expansionDiagnostics = await mapExpansionDiagnostics(input.repoRoot, document.expansionDiagnostics);
39531
+ return {
39532
+ content: document.content,
39533
+ effectiveRoot: input.absoluteFilePath,
39534
+ resolvedImports: collectResolvedImports(input.repoRoot, document.edges),
39535
+ expansionDiagnostics
39536
+ };
39537
+ }
39538
+ };
39539
+ }
39540
+
38935
39541
  ;// CONCATENATED MODULE: ../core/src/gateways/instruction-file-discovery-gateway.ts
38936
39542
  /**
38937
39543
  * Gateway for discovering instruction files across multiple formats.
@@ -57915,10 +58521,12 @@ const HEADING_PATTERN_DESCRIPTIONS = new Map(Object.entries(HEADING_PATTERN_DESC
57915
58521
  discoveryGateway;
57916
58522
  astGateway;
57917
58523
  commandsGateway;
57918
- constructor(discoveryGateway, astGateway, commandsGateway){
58524
+ claudeImportExpander;
58525
+ constructor(discoveryGateway, astGateway, commandsGateway, claudeImportExpander){
57919
58526
  this.discoveryGateway = discoveryGateway;
57920
58527
  this.astGateway = astGateway;
57921
58528
  this.commandsGateway = commandsGateway;
58529
+ this.claudeImportExpander = claudeImportExpander;
57922
58530
  }
57923
58531
  /**
57924
58532
  * Returns true if the given heading-pattern string contains any characters
@@ -58037,13 +58645,19 @@ const HEADING_PATTERN_DESCRIPTIONS = new Map(Object.entries(HEADING_PATTERN_DESC
58037
58645
  diagnostics: []
58038
58646
  };
58039
58647
  }
58040
- // Analyze each file
58648
+ // Analyze each file (Claude entrypoints use effective import-expanded content)
58041
58649
  const fileStructures = new Map();
58042
58650
  const parsingErrors = [];
58651
+ const importDiagnostics = [];
58652
+ const effectiveDocuments = [];
58043
58653
  for (const file of discoveredFiles){
58044
58654
  try {
58045
- const structure = await this.astGateway.parseFile(file.filePath);
58046
- fileStructures.set(file.filePath, structure);
58655
+ const resolved = await this.resolveAnalysisStructure(file, parsed.targetDir);
58656
+ fileStructures.set(file.filePath, resolved.structure);
58657
+ importDiagnostics.push(...resolved.importDiagnostics);
58658
+ if (resolved.provenance !== undefined) {
58659
+ effectiveDocuments.push(resolved.provenance);
58660
+ }
58047
58661
  } catch (error) {
58048
58662
  const errorMessage = error instanceof Error ? error.message : "Unknown parsing error";
58049
58663
  parsingErrors.push({
@@ -58068,6 +58682,8 @@ const HEADING_PATTERN_DESCRIPTIONS = new Map(Object.entries(HEADING_PATTERN_DESC
58068
58682
  target: "instruction"
58069
58683
  });
58070
58684
  }
58685
+ // Import-expansion failures (stable ruleIds, importer-token provenance)
58686
+ diagnostics.push(...importDiagnostics);
58071
58687
  // Check each successfully parsed file for missing structural headings
58072
58688
  const sortedFilePaths = Array.from(fileStructures.keys()).sort((a, b)=>a < b ? -1 : a > b ? 1 : 0);
58073
58689
  for (const filePath of sortedFilePaths){
@@ -58112,17 +58728,76 @@ const HEADING_PATTERN_DESCRIPTIONS = new Map(Object.entries(HEADING_PATTERN_DESC
58112
58728
  ruleId: "instruction/parse-error"
58113
58729
  });
58114
58730
  }
58731
+ const result = {
58732
+ discoveredFiles,
58733
+ commandScores,
58734
+ overallQualityScore,
58735
+ suggestions,
58736
+ parsingErrors,
58737
+ ...effectiveDocuments.length > 0 ? {
58738
+ effectiveDocuments
58739
+ } : {}
58740
+ };
58115
58741
  return {
58116
- result: {
58117
- discoveredFiles,
58118
- commandScores,
58119
- overallQualityScore,
58120
- suggestions,
58121
- parsingErrors
58122
- },
58742
+ result,
58123
58743
  diagnostics
58124
58744
  };
58125
58745
  }
58746
+ /**
58747
+ * Resolves the Markdown structure used for content-sensitive rules on one
58748
+ * discovered entrypoint. Claude entrypoints expand verified `@` imports when
58749
+ * an expander is injected; all other formats parse the physical file only.
58750
+ */ async resolveAnalysisStructure(file, targetDir) {
58751
+ if (file.format === "claude-md" && this.claudeImportExpander !== undefined) {
58752
+ const effective = await this.claudeImportExpander.expandClaudeEntrypoint({
58753
+ repoRoot: targetDir,
58754
+ absoluteFilePath: file.filePath
58755
+ });
58756
+ const importDiagnostics = effective.expansionDiagnostics.map((diagnostic)=>AnalyzeInstructionQualityUseCase.toImportLintDiagnostic(diagnostic));
58757
+ importDiagnostics.sort(AnalyzeInstructionQualityUseCase.compareImportDiagnostics);
58758
+ return {
58759
+ structure: this.astGateway.parseContent(effective.content),
58760
+ importDiagnostics,
58761
+ provenance: {
58762
+ effectiveRoot: effective.effectiveRoot,
58763
+ resolvedImports: effective.resolvedImports
58764
+ }
58765
+ };
58766
+ }
58767
+ return {
58768
+ structure: await this.astGateway.parseFile(file.filePath),
58769
+ importDiagnostics: []
58770
+ };
58771
+ }
58772
+ static toImportLintDiagnostic(diagnostic) {
58773
+ return {
58774
+ filePath: diagnostic.filePath,
58775
+ line: diagnostic.line,
58776
+ column: diagnostic.column,
58777
+ endLine: diagnostic.endLine,
58778
+ endColumn: diagnostic.endColumn,
58779
+ severity: "warning",
58780
+ message: diagnostic.message,
58781
+ ruleId: diagnostic.ruleId,
58782
+ target: "instruction"
58783
+ };
58784
+ }
58785
+ static compareImportDiagnostics(a, b) {
58786
+ if (a.filePath !== b.filePath) {
58787
+ return a.filePath < b.filePath ? -1 : 1;
58788
+ }
58789
+ if (a.line !== b.line) {
58790
+ return a.line - b.line;
58791
+ }
58792
+ const aCol = a.column ?? 0;
58793
+ const bCol = b.column ?? 0;
58794
+ if (aCol !== bCol) {
58795
+ return aCol - bCol;
58796
+ }
58797
+ const aRule = a.ruleId ?? "";
58798
+ const bRule = b.ruleId ?? "";
58799
+ return aRule < bRule ? -1 : aRule > bRule ? 1 : 0;
58800
+ }
58126
58801
  findBestScore(command, discoveredFiles, fileStructures, headingPatterns, proximityWindow, diagnostics) {
58127
58802
  let bestAnalysis = null;
58128
58803
  let bestComposite = -1;
@@ -58523,6 +59198,7 @@ const HEADING_PATTERN_DESCRIPTIONS = new Map(Object.entries(HEADING_PATTERN_DESC
58523
59198
 
58524
59199
 
58525
59200
 
59201
+
58526
59202
  /**
58527
59203
  * Analyzes the structural quality of feedback loop documentation in instruction files.
58528
59204
  * Assesses structural context, execution clarity, and loop completeness.
@@ -58535,7 +59211,7 @@ const HEADING_PATTERN_DESCRIPTIONS = new Map(Object.entries(HEADING_PATTERN_DESC
58535
59211
  const discoveryGateway = createInstructionFileDiscoveryGateway();
58536
59212
  const astGateway = createMarkdownAstGateway();
58537
59213
  const commandsGateway = createFeedbackLoopCommandsGateway();
58538
- const useCase = new AnalyzeInstructionQualityUseCase(discoveryGateway, astGateway, commandsGateway);
59214
+ const useCase = new AnalyzeInstructionQualityUseCase(discoveryGateway, astGateway, commandsGateway, createClaudeInstructionImportExpander());
58539
59215
  const output = await useCase.execute({
58540
59216
  targetDir: dir
58541
59217
  });
@@ -58558,10 +59234,27 @@ const HEADING_PATTERN_DESCRIPTIONS = new Map(Object.entries(HEADING_PATTERN_DESC
58558
59234
  diagnostics: output.diagnostics.map((d)=>({
58559
59235
  filePath: d.filePath,
58560
59236
  line: d.line,
59237
+ ...d.column !== undefined ? {
59238
+ column: d.column
59239
+ } : {},
59240
+ ...d.endLine !== undefined ? {
59241
+ endLine: d.endLine
59242
+ } : {},
59243
+ ...d.endColumn !== undefined ? {
59244
+ endColumn: d.endColumn
59245
+ } : {},
58561
59246
  severity: d.severity,
58562
59247
  message: d.message,
58563
59248
  ruleId: d.ruleId
58564
- }))
59249
+ })),
59250
+ ...output.result.effectiveDocuments !== undefined ? {
59251
+ effectiveDocuments: output.result.effectiveDocuments.map((doc)=>({
59252
+ effectiveRoot: doc.effectiveRoot,
59253
+ resolvedImports: [
59254
+ ...doc.resolvedImports
59255
+ ]
59256
+ }))
59257
+ } : {}
58565
59258
  });
58566
59259
  } catch (error) {
58567
59260
  return types_errorResponse(`Failed to analyze instruction quality: ${error instanceof Error ? error.message : "Unknown error"}`);
@@ -70391,4 +71084,4 @@ if (installedChunkData !== 0) { // 0 means "already installed".'
70391
71084
  // module factories are used so entry inlining is disabled
70392
71085
  // startup
70393
71086
  // Load entry module and return exports
70394
- var __webpack_exports__ = __webpack_require__(4583);
71087
+ var __webpack_exports__ = __webpack_require__(7444);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lousy-agents/mcp",
3
- "version": "5.20.2",
3
+ "version": "5.21.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": {