@dotdotgod/claude-code 0.3.11 → 0.3.12

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 (3) hide show
  1. package/mcp/cli.mjs +154 -41
  2. package/mcp/server.mjs +11 -5
  3. package/package.json +2 -2
package/mcp/cli.mjs CHANGED
@@ -1718,7 +1718,7 @@ import { spawnSync } from "node:child_process";
1718
1718
  import { basename as basename3, extname as extname2, join as join5 } from "node:path";
1719
1719
 
1720
1720
  // packages/cli/src/index/constants.mjs
1721
- var CACHE_VERSION = 14;
1721
+ var CACHE_VERSION = 15;
1722
1722
  var CACHE_DIR = ".dotdotgod";
1723
1723
  var MANIFEST_FILE = "manifest.json";
1724
1724
 
@@ -2094,8 +2094,16 @@ function addPackageResource(graph, fileId, packagePath, name, target, kind) {
2094
2094
  addNode(graph, id, "package_resource", { name, target, kind, path: packagePath });
2095
2095
  addEdge(graph, fileId, id, "includes_resource", { kind, confidence: "EXTRACTED" });
2096
2096
  }
2097
+ function addReferenceEvidence(graph, sourceId, reference) {
2098
+ const source = graph.nodes.find((node2) => node2.id === sourceId);
2099
+ if (!source) return;
2100
+ source.references ??= [];
2101
+ source.references.push(reference);
2102
+ }
2097
2103
  function addTraceabilityTarget(graph, sourceId, root, relation, targetPath, data = {}, config = defaultMemoryConfig()) {
2098
- if (!isLocalRelativeTraceabilityPath(targetPath) || !existsSync8(resolve5(root, targetPath))) return;
2104
+ if (!isLocalRelativeTraceabilityPath(targetPath)) return;
2105
+ addReferenceEvidence(graph, sourceId, { targetPath, href: targetPath, relation, data: { confidence: "CURATED_TRACEABILITY", ...data } });
2106
+ if (!existsSync8(resolve5(root, targetPath))) return;
2099
2107
  const targetId = `file:${targetPath}`;
2100
2108
  addNode(graph, targetId, "file", fileNodeMetadata(targetPath, null, config));
2101
2109
  addEdge(graph, sourceId, targetId, relation, { confidence: "CURATED_TRACEABILITY", ...data });
@@ -2158,9 +2166,14 @@ function extractMarkdownGraph(root, file, graph, config = defaultMemoryConfig())
2158
2166
  addEdge(graph, fileId, id, "contains_heading", { confidence: "EXTRACTED" });
2159
2167
  }
2160
2168
  for (const { href, line } of extractLinks(content)) {
2161
- const pathPart = href.split("#")[0];
2169
+ const hashIndex = href.indexOf("#");
2170
+ const pathPart = hashIndex < 0 ? href : href.slice(0, hashIndex);
2171
+ const anchor = hashIndex < 0 ? "" : href.slice(hashIndex + 1);
2172
+ const targetPath = pathPart ? rel(root, resolve6(dirname4(file), pathPart)) : path;
2173
+ const data = { line, confidence: "EXTRACTED" };
2174
+ addReferenceEvidence(graph, fileId, { targetPath, href, anchor, relation: "links_to", data });
2175
+ if (pathPart && isReadmeIndexPath(path)) addReferenceEvidence(graph, fileId, { targetPath, href, anchor, relation: "routes_to", data: { line, confidence: "CURATED_INDEX", sourceRole: "readme-index" } });
2162
2176
  if (!pathPart) continue;
2163
- const targetPath = rel(root, resolve6(dirname4(file), pathPart));
2164
2177
  const targetId = `file:${targetPath}`;
2165
2178
  addNode(graph, targetId, "file", fileNodeMetadata(targetPath, null, config));
2166
2179
  addEdge(graph, fileId, targetId, "links_to", { line, confidence: "EXTRACTED" });
@@ -3384,7 +3397,7 @@ function runMap(argv) {
3384
3397
  }
3385
3398
 
3386
3399
  // packages/cli/src/reference/resolve.mjs
3387
- import { resolve as resolve15 } from "node:path";
3400
+ import { resolve as resolve16 } from "node:path";
3388
3401
 
3389
3402
  // node_modules/.pnpm/leiden-ts@0.1.0/node_modules/leiden-ts/dist/index.js
3390
3403
  var GraphValidationError = class extends Error {
@@ -4754,16 +4767,94 @@ function compareImpactItems(seeds) {
4754
4767
  };
4755
4768
  }
4756
4769
 
4770
+ // packages/cli/src/impact/references.mjs
4771
+ import { readFileSync as readFileSync13, statSync as statSync8 } from "node:fs";
4772
+ import { extname as extname5, resolve as resolve14 } from "node:path";
4773
+ var WARNING_LIMIT = 20;
4774
+ var edgeKey = (source, target, relation) => `${source}\0${target}\0${relation}`;
4775
+ function prepareImpactReferences(index, root, changedPaths) {
4776
+ const graph = index?.graph ?? { nodes: [], edges: [] };
4777
+ if (!root) return { index, warnings: { items: [], total: 0, omitted: 0 } };
4778
+ const seeds = new Set(changedPaths.map((path) => `file:${path}`));
4779
+ const checks = /* @__PURE__ */ new Map();
4780
+ const inspect = (path) => {
4781
+ if (checks.has(path)) return checks.get(path);
4782
+ let result;
4783
+ try {
4784
+ const absolute = resolve14(root, path);
4785
+ const stat = statSync8(absolute);
4786
+ result = { status: "exists" };
4787
+ if (stat.isFile() && extname5(absolute) === ".md") result.anchors = extractAnchors(readFileSync13(absolute, "utf8"));
4788
+ } catch (error) {
4789
+ result = { status: ["ENOENT", "ENOTDIR"].includes(error.code) ? "missing" : "unavailable" };
4790
+ }
4791
+ checks.set(path, result);
4792
+ return result;
4793
+ };
4794
+ const references = graph.nodes.flatMap((node2) => (node2.references ?? []).map((reference) => ({ ...reference, source: node2.id, sourcePath: node2.path })));
4795
+ const managedEdges = new Set(references.map((ref) => edgeKey(ref.source, `file:${ref.targetPath}`, ref.relation)));
4796
+ const adjacency = /* @__PURE__ */ new Map();
4797
+ const connect = (source, target) => {
4798
+ if (!adjacency.has(source)) adjacency.set(source, /* @__PURE__ */ new Set());
4799
+ if (!adjacency.has(target)) adjacency.set(target, /* @__PURE__ */ new Set());
4800
+ adjacency.get(source).add(target);
4801
+ adjacency.get(target).add(source);
4802
+ };
4803
+ for (const edge of graph.edges) connect(edge.source, edge.target);
4804
+ for (const ref of references) connect(ref.source, `file:${ref.targetPath}`);
4805
+ const reachable = new Set(seeds);
4806
+ const queue = [...seeds];
4807
+ for (let i = 0; i < queue.length; i += 1) for (const id of adjacency.get(queue[i]) ?? []) {
4808
+ if (!reachable.has(id)) {
4809
+ reachable.add(id);
4810
+ queue.push(id);
4811
+ }
4812
+ }
4813
+ const missing = new Set(graph.nodes.filter((node2) => node2.path && inspect(node2.path).status === "missing" && !seeds.has(node2.id)).map((node2) => node2.id));
4814
+ const nodes = new Map(graph.nodes.filter((node2) => !missing.has(node2.id)).map((node2) => [node2.id, node2]));
4815
+ const edges = graph.edges.filter((edge) => !missing.has(edge.source) && !missing.has(edge.target) && !managedEdges.has(edgeKey(edge.source, edge.target, edge.relation)));
4816
+ for (const path of changedPaths) {
4817
+ const id = `file:${path}`;
4818
+ if (!nodes.has(id)) nodes.set(id, { id, type: "file", ...fileNodeMetadata(path, null, index?.memoryConfig) });
4819
+ }
4820
+ const warnings = /* @__PURE__ */ new Map();
4821
+ for (const ref of references) {
4822
+ if (/^(?:[a-z][a-z0-9+.-]*:|\/\/)/i.test(ref.href)) continue;
4823
+ const targetId = `file:${ref.targetPath}`;
4824
+ const check = inspect(ref.targetPath);
4825
+ let reason = check.status === "missing" ? "MISSING_FILE" : check.status === "unavailable" ? "REFERENCE_UNAVAILABLE" : void 0;
4826
+ if (!reason && ref.anchor && check.anchors) {
4827
+ try {
4828
+ if (!check.anchors.has(decodeURIComponent(ref.anchor))) reason = "BROKEN_ANCHOR";
4829
+ } catch {
4830
+ reason = "BROKEN_ANCHOR";
4831
+ }
4832
+ }
4833
+ if (reason) {
4834
+ if (reachable.has(ref.source) && !missing.has(ref.source)) {
4835
+ const key = `${ref.source}\0${ref.href}\0${ref.data?.line ?? ""}\0${reason}`;
4836
+ if (!warnings.has(key)) warnings.set(key, { source: ref.sourcePath ?? ref.source, target: ref.href, targetPath: ref.targetPath, relation: ref.relation, reason, ...ref.data?.line ? { line: ref.data.line } : {} });
4837
+ }
4838
+ if (reason !== "REFERENCE_UNAVAILABLE" && !(reason === "MISSING_FILE" && seeds.has(targetId))) continue;
4839
+ }
4840
+ if (missing.has(ref.source)) continue;
4841
+ if (!nodes.has(targetId)) nodes.set(targetId, { id: targetId, type: "file", ...fileNodeMetadata(ref.targetPath, null, index?.memoryConfig) });
4842
+ edges.push({ source: ref.source, target: targetId, relation: ref.relation, ...ref.data });
4843
+ }
4844
+ const items = [...warnings.values()].sort((a, b) => a.source.localeCompare(b.source) || (a.line ?? 0) - (b.line ?? 0) || a.target.localeCompare(b.target) || a.reason.localeCompare(b.reason));
4845
+ return { index: { ...index, graph: { nodes: [...nodes.values()], edges: edges.filter((edge) => nodes.has(edge.source) && nodes.has(edge.target)) } }, warnings: { items: items.slice(0, WARNING_LIMIT), total: items.length, omitted: Math.max(0, items.length - WARNING_LIMIT) } };
4846
+ }
4847
+
4757
4848
  // packages/cli/src/impact/vector-profile.mjs
4758
- import { lstatSync, readFileSync as readFileSync13, realpathSync } from "node:fs";
4759
- import { extname as extname5, relative as relative5, resolve as resolve14, sep } from "node:path";
4849
+ import { lstatSync, readFileSync as readFileSync14, realpathSync } from "node:fs";
4850
+ import { extname as extname6, relative as relative5, resolve as resolve15, sep } from "node:path";
4760
4851
  var MAX_VECTOR_PROFILE_FILE_BYTES = 64 * 1024;
4761
4852
  var MAX_VECTOR_PROFILE_CHARS = 4e3;
4762
4853
  var MAX_VECTOR_PROFILE_METADATA_ITEMS = 20;
4763
4854
  var TEXT_EXTENSIONS = /* @__PURE__ */ new Set([".c", ".cc", ".cpp", ".css", ".go", ".h", ".hpp", ".html", ".java", ".js", ".json", ".jsx", ".md", ".mjs", ".py", ".rb", ".rs", ".sh", ".ts", ".tsx", ".txt", ".yaml", ".yml"]);
4764
4855
  var GENERATED_SEGMENTS = /* @__PURE__ */ new Set([".dotdotgod", "build", "coverage", "dist", "node_modules"]);
4765
4856
  function insideRoot(root, absolute) {
4766
- const normalizedRoot = resolve14(root);
4857
+ const normalizedRoot = resolve15(root);
4767
4858
  return absolute === normalizedRoot || absolute.startsWith(`${normalizedRoot}${sep}`);
4768
4859
  }
4769
4860
  function normalizeChangedPath(path) {
@@ -4774,8 +4865,8 @@ function normalizeChangedPath(path) {
4774
4865
  function canonicalizeChangedPath(root, path) {
4775
4866
  const normalized = normalizeChangedPath(path);
4776
4867
  if (!normalized) return null;
4777
- const rootAbsolute = resolve14(root);
4778
- const absolute = resolve14(rootAbsolute, normalized);
4868
+ const rootAbsolute = resolve15(root);
4869
+ const absolute = resolve15(rootAbsolute, normalized);
4779
4870
  if (!insideRoot(rootAbsolute, absolute)) return normalized;
4780
4871
  try {
4781
4872
  const canonicalRoot = realpathSync(rootAbsolute);
@@ -4804,17 +4895,17 @@ function graphMetadata(graph, seedId) {
4804
4895
  return values;
4805
4896
  }
4806
4897
  function safeTextPrefix(root, path) {
4807
- if (unsafeProfilePath(path) || !TEXT_EXTENSIONS.has(extname5(path).toLowerCase())) return "";
4898
+ if (unsafeProfilePath(path) || !TEXT_EXTENSIONS.has(extname6(path).toLowerCase())) return "";
4808
4899
  try {
4809
- const absolute = resolve14(root, path);
4900
+ const absolute = resolve15(root, path);
4810
4901
  if (!insideRoot(root, absolute)) return "";
4811
4902
  const canonicalAbsolute = realpathSync(absolute);
4812
4903
  if (!insideRoot(realpathSync(root), canonicalAbsolute)) return "";
4813
4904
  const canonicalPath = relative5(realpathSync(root), canonicalAbsolute).replaceAll("\\", "/");
4814
- if (canonicalPath !== path || unsafeProfilePath(canonicalPath) || !TEXT_EXTENSIONS.has(extname5(canonicalPath).toLowerCase())) return "";
4905
+ if (canonicalPath !== path || unsafeProfilePath(canonicalPath) || !TEXT_EXTENSIONS.has(extname6(canonicalPath).toLowerCase())) return "";
4815
4906
  const stats = lstatSync(canonicalAbsolute);
4816
4907
  if (!stats.isFile() || stats.isSymbolicLink() || stats.size > MAX_VECTOR_PROFILE_FILE_BYTES) return "";
4817
- const buffer = readFileSync13(canonicalAbsolute);
4908
+ const buffer = readFileSync14(canonicalAbsolute);
4818
4909
  if (buffer.includes(0)) return "";
4819
4910
  const text = new TextDecoder("utf-8", { fatal: true }).decode(buffer);
4820
4911
  return text.slice(0, MAX_VECTOR_PROFILE_CHARS);
@@ -4930,7 +5021,7 @@ function buildCombinedImpactReport(index, changedPaths, limits = {}) {
4930
5021
  const traceabilityWeights = traceabilityRelationWeights(config.traceability);
4931
5022
  policy.relationWeights = { ...policy.relationWeights, ...traceabilityWeights };
4932
5023
  const curatedRelations = new Set(Object.keys(traceabilityWeights));
4933
- const nodeById = new Map(graph.nodes.map((node2) => [node2.id, node2]));
5024
+ const nodeById = new Map(graph.nodes.map(({ references, ...node2 }) => [node2.id, node2]));
4934
5025
  const seeds = changedPaths.map((path) => `file:${path}`);
4935
5026
  const seedSet = new Set(seeds);
4936
5027
  const maxRelated = Math.max(limits.related ?? 25, 0);
@@ -4969,7 +5060,8 @@ function buildCombinedImpactReport(index, changedPaths, limits = {}) {
4969
5060
  const scored = scoreImpactItem({ ...node2, reasons: reasonList, retrieval }, seedSet, changedPaths, policy, pprScores, config);
4970
5061
  return { ...node2, reasons: reasonList, hasCuratedEvidence, ...vectorEvidence.has(id) ? { vectorEvidence: vectorEvidence.get(id) } : {}, retrieval: { ...retrieval, signals: [.../* @__PURE__ */ new Set([...retrieval.signals ?? [], ...reasonSignals])] }, ...scored };
4971
5062
  }).sort(compareImpactItems(seeds));
4972
- const rankedRelated = relatedAll.filter((item) => !seedSet.has(item.id));
5063
+ const excludedIds = new Set(limits.excludeRelatedIds ?? seeds);
5064
+ const rankedRelated = relatedAll.filter((item) => !excludedIds.has(item.id));
4973
5065
  const related = selectImpactItems(rankedRelated, maxRelated, []);
4974
5066
  for (const item of related) {
4975
5067
  if (item.type === "file") {
@@ -4983,16 +5075,22 @@ function buildCombinedImpactReport(index, changedPaths, limits = {}) {
4983
5075
  else if (item.type === "event") addImpactItem(groups.events, item, limits.events ?? 10);
4984
5076
  else if (item.type === "package_resource") addImpactItem(groups.packageResources, item, limits.packageResources ?? 10);
4985
5077
  }
4986
- return { changed: changedPaths[0], changedFiles: changedPaths, semantic: { status: overlay.status ?? "disabled", ...limits.verboseSemantic ? { diagnostics: overlay.diagnostics } : {} }, ranking: { method: "weighted-personalized-pagerank+memory", configSource: index?.memoryConfig?.source ?? "default", connectionCap: policy.connectionCap, memoryCap: policy.memoryCap, pprReference: policy.ppr.reference }, related, groups, omittedRelated: Math.max(0, relatedAll.length - related.length) };
5078
+ return { changed: changedPaths[0], changedFiles: changedPaths, semantic: { status: overlay.status ?? "disabled", ...limits.verboseSemantic ? { diagnostics: overlay.diagnostics } : {} }, ranking: { method: "weighted-personalized-pagerank+memory", configSource: index?.memoryConfig?.source ?? "default", connectionCap: policy.connectionCap, memoryCap: policy.memoryCap, pprReference: policy.ppr.reference }, related, groups, omittedRelated: Math.max(0, rankedRelated.length - related.length) };
4987
5079
  }
4988
5080
  function buildImpactReport(index, changedPaths, limits = {}) {
4989
5081
  const normalized = normalizeChangedPaths(changedPaths);
5082
+ const prepared = prepareImpactReferences(index, limits.root, normalized);
5083
+ index = prepared.index;
5084
+ const validIds = new Set(index?.graph?.nodes.map((node2) => node2.id) ?? []);
5085
+ if (limits.overlay) limits = { ...limits, overlay: { ...limits.overlay, edges: (limits.overlay.edges ?? []).filter((edge) => validIds.has(edge.target)) } };
4990
5086
  const aggregate2 = buildCombinedImpactReport(index, normalized, limits);
5087
+ aggregate2.warnings = prepared.warnings;
5088
+ Object.defineProperty(aggregate2, "structuralGraph", { value: index?.graph, enumerable: false });
4991
5089
  const perSeedLimit = limits.perSeed ?? 5;
4992
5090
  aggregate2.perSeed = normalized.map((changed) => {
4993
5091
  const seedId = `file:${changed}`;
4994
5092
  const seedOverlay = limits.overlay ? { ...limits.overlay, edges: (limits.overlay.edges ?? []).filter((edge) => edge.source === seedId) } : void 0;
4995
- const report = buildCombinedImpactReport(index, [changed], { ...limits, overlay: seedOverlay, related: Math.max(limits.related ?? 25, perSeedLimit) });
5093
+ const report = buildCombinedImpactReport(index, [changed], { ...limits, excludeRelatedIds: normalized.map((path) => `file:${path}`), overlay: seedOverlay, related: Math.max(limits.related ?? 25, perSeedLimit) });
4996
5094
  const related = report.related.slice(0, perSeedLimit);
4997
5095
  return { changed, related, omittedRelated: Math.max(0, report.related.length - related.length) + report.omittedRelated };
4998
5096
  });
@@ -5023,11 +5121,11 @@ function buildCompactImpactReport(impact, limits = {}) {
5023
5121
  const groups = Object.fromEntries(groupNames.map((name) => [name, compactImpactGroup(impact.groups?.[name], groupLimit)]));
5024
5122
  const perSeed = (impact.perSeed ?? []).map((entry) => ({ changed: entry.changed, related: (entry.related ?? []).slice(0, limits.perSeed ?? 5).map(compactImpactItem), omittedRelated: entry.omittedRelated ?? 0 }));
5025
5123
  const top10 = (impact.related ?? []).filter((item) => !seedIds.has(item.id)).slice(0, 10);
5026
- return { changed: impact.changed, changedFiles, perSeed, semantic: impact.semantic, compact: true, ranking: { method: impact.ranking?.method, configSource: impact.ranking?.configSource, connectionCap: impact.ranking?.connectionCap, memoryCap: impact.ranking?.memoryCap, pprReference: impact.ranking?.pprReference }, related, groups, omittedRelated: (impact.omittedRelated ?? 0) + Math.max(0, (impact.related?.length ?? 0) - related.length), quality: { rawRelated: impact.related?.length ?? 0, compactRelated: related.length, semanticOnlyTop10: top10.filter((item) => isSemanticOnlyImpactItem(item)).length, curatedTop10: top10.filter((item) => hasCuratedImpactReason(item)).length, lowActionabilityTop10: top10.filter((item) => isLowActionabilityImpactItem(item)).length } };
5124
+ return { changed: impact.changed, changedFiles, perSeed, warnings: impact.warnings, semantic: impact.semantic, compact: true, ranking: { method: impact.ranking?.method, configSource: impact.ranking?.configSource, connectionCap: impact.ranking?.connectionCap, memoryCap: impact.ranking?.memoryCap, pprReference: impact.ranking?.pprReference }, related, groups, omittedRelated: (impact.omittedRelated ?? 0) + Math.max(0, (impact.related?.length ?? 0) - related.length), quality: { rawRelated: impact.related?.length ?? 0, compactRelated: related.length, semanticOnlyTop10: top10.filter((item) => isSemanticOnlyImpactItem(item)).length, curatedTop10: top10.filter((item) => hasCuratedImpactReason(item)).length, lowActionabilityTop10: top10.filter((item) => isLowActionabilityImpactItem(item)).length } };
5027
5125
  }
5028
5126
 
5029
5127
  // packages/cli/src/reference/extract.mjs
5030
- import { basename as basename8, dirname as dirname7, extname as extname6 } from "node:path";
5128
+ import { basename as basename8, dirname as dirname7, extname as extname7 } from "node:path";
5031
5129
  var DEFAULT_REFERENCE_LIMIT = 5;
5032
5130
  function extractBracketReferences(prompt = "") {
5033
5131
  const refs = [];
@@ -5072,7 +5170,7 @@ function isArchiveBodyPath(path = "", memoryConfig) {
5072
5170
  function aliasEntriesForPath(path = "") {
5073
5171
  const entries = [];
5074
5172
  const base = basename8(path);
5075
- const ext = extname6(base);
5173
+ const ext = extname7(base);
5076
5174
  const stem = ext ? base.slice(0, -ext.length) : base;
5077
5175
  const withoutMd = path.replace(/\.md$/i, "");
5078
5176
  const parts = withoutMd.split("/").filter(Boolean);
@@ -5089,7 +5187,7 @@ function referenceCandidateAliases(node2) {
5089
5187
  const entries = [];
5090
5188
  if (node2.type === "file" && path) entries.push(...aliasEntriesForPath(path));
5091
5189
  if (node2.type === "heading") {
5092
- const fileStem = path ? basename8(path, extname6(path)) : "";
5190
+ const fileStem = path ? basename8(path, extname7(path)) : "";
5093
5191
  const anchor = typeof node2.id === "string" && node2.id.includes("#") ? node2.id.split("#").pop() : "";
5094
5192
  for (const alias of [node2.title, anchor, fileStem && node2.title ? `${fileStem}#${node2.title}` : "", fileStem && anchor ? `${fileStem}#${anchor}` : "", path && node2.title ? `${path}#${node2.title}` : "", path && anchor ? `${path}#${anchor}` : ""]) entries.push({ alias, kind: "heading" });
5095
5193
  }
@@ -5208,7 +5306,7 @@ function parseReferenceOptions(argv, command) {
5208
5306
  else filtered.push(arg);
5209
5307
  }
5210
5308
  const operands = filtered.filter((arg) => !arg.startsWith("-"));
5211
- return { ...options, root: resolve15(operands[0] ?? "."), json: filtered.includes("--json"), rootArgv: operands.slice(1) };
5309
+ return { ...options, root: resolve16(operands[0] ?? "."), json: filtered.includes("--json"), rootArgv: operands.slice(1) };
5212
5310
  }
5213
5311
  function formatReferenceOutput(payload) {
5214
5312
  const refreshNote = payload.metadata.cacheRefreshed ? ", refreshed" : "";
@@ -5248,8 +5346,8 @@ function runExpand(argv) {
5248
5346
  if (options.withImpact) refs = refs.map((item) => {
5249
5347
  const topPath = item.top?.path;
5250
5348
  if (!topPath) return item;
5251
- const impact = buildCompactImpactReport(buildImpactReport(index, topPath));
5252
- return { ...item, impact: { changed: topPath, related: impact.related, groups: impact.groups, omittedRelated: impact.omittedRelated, quality: impact.quality } };
5349
+ const impact = buildCompactImpactReport(buildImpactReport(index, topPath, { root: options.root }));
5350
+ return { ...item, impact: { changed: topPath, warnings: impact.warnings, related: impact.related, groups: impact.groups, omittedRelated: impact.omittedRelated, quality: impact.quality } };
5253
5351
  });
5254
5352
  const payload = { ok: status.ok, command: "expand", root: options.root, prompt, status, metadata, refs, omitted: refs.reduce((sum, item) => sum + item.omitted, 0) };
5255
5353
  if (options.json) console.log(JSON.stringify(payload, null, 2));
@@ -5257,8 +5355,8 @@ function runExpand(argv) {
5257
5355
  }
5258
5356
 
5259
5357
  // packages/cli/src/commands/traceability.mjs
5260
- import { existsSync as existsSync15, readFileSync as readFileSync14, readdirSync as readdirSync5, writeFileSync as writeFileSync6 } from "node:fs";
5261
- import { join as join14, resolve as resolve16 } from "node:path";
5358
+ import { existsSync as existsSync15, readFileSync as readFileSync15, readdirSync as readdirSync5, writeFileSync as writeFileSync6 } from "node:fs";
5359
+ import { join as join14, resolve as resolve17 } from "node:path";
5262
5360
  function collectDocsMarkdownFiles(root) {
5263
5361
  const docs = join14(root, "docs");
5264
5362
  const files = [];
@@ -5290,7 +5388,7 @@ function parseTraceabilityOptions(argv) {
5290
5388
  }
5291
5389
  if (options.check && options.write) usage("Choose only one traceability links mode: --check or --write.", "traceability links");
5292
5390
  options.check = options.check || !options.write;
5293
- options.root = resolve16(options.root);
5391
+ options.root = resolve17(options.root);
5294
5392
  return options;
5295
5393
  }
5296
5394
  function runTraceability(argv) {
@@ -5301,7 +5399,7 @@ function runTraceability(argv) {
5301
5399
  const errors = [...config.errors ?? []];
5302
5400
  for (const file of files) {
5303
5401
  if (options.write && errors.length > 0) break;
5304
- const content = readFileSync14(file, "utf8");
5402
+ const content = readFileSync15(file, "utf8");
5305
5403
  for (const error of validateTraceabilityLinksRegion(content, options.root, file)) errors.push(error);
5306
5404
  const blocks = extractDotdotgodTraceabilityBlocks(content).filter((block) => !block.error);
5307
5405
  if (blocks.length === 0) continue;
@@ -5331,12 +5429,12 @@ function runTraceability(argv) {
5331
5429
 
5332
5430
  // packages/cli/src/graph-view/server.mjs
5333
5431
  import { createServer } from "node:http";
5334
- import { readFileSync as readFileSync15 } from "node:fs";
5335
- import { dirname as dirname8, extname as extname7, join as join15 } from "node:path";
5432
+ import { readFileSync as readFileSync16 } from "node:fs";
5433
+ import { dirname as dirname8, extname as extname8, join as join15 } from "node:path";
5336
5434
  import { fileURLToPath } from "node:url";
5337
5435
 
5338
5436
  // packages/cli/src/graph-view/payload.mjs
5339
- function edgeKey(edge) {
5437
+ function edgeKey2(edge) {
5340
5438
  return `${edge.source}\0${edge.target}\0${edge.relation}`;
5341
5439
  }
5342
5440
  function labelForNode(node2) {
@@ -5377,7 +5475,7 @@ function rootReachability(nodes, edges, seedIds) {
5377
5475
  return { reachable, depthByNode, nearestRootByNode };
5378
5476
  }
5379
5477
  function buildImpactGraphPayload(index, impact) {
5380
- const graph = index?.graph ?? { nodes: [], edges: [] };
5478
+ const graph = impact.structuralGraph ?? index?.graph ?? { nodes: [], edges: [] };
5381
5479
  const config = index?.memoryConfig;
5382
5480
  const seedIds = new Set((impact.changedFiles ?? [impact.changed]).map((path) => `file:${path}`));
5383
5481
  const impactItems = new Map((impact.related ?? []).map((item) => [item.id, item]));
@@ -5392,7 +5490,7 @@ function buildImpactGraphPayload(index, impact) {
5392
5490
  const edges = [];
5393
5491
  for (const edge of graph.edges ?? []) {
5394
5492
  if (!indexedNodes.has(edge.source) || !indexedNodes.has(edge.target)) continue;
5395
- const key = edgeKey(edge);
5493
+ const key = edgeKey2(edge);
5396
5494
  if (seenEdges.has(key)) continue;
5397
5495
  seenEdges.add(key);
5398
5496
  edges.push({ source: edge.source, target: edge.target, relation: edge.relation, confidence: edge.confidence, weight: edge.weight ?? edge.relationWeight, score: edge.score, traceabilityKey: edge.traceabilityKey });
@@ -5457,8 +5555,8 @@ function json(response, status, value) {
5457
5555
  }
5458
5556
  function asset(response, file) {
5459
5557
  try {
5460
- const body = readFileSync15(file);
5461
- response.writeHead(200, { "content-type": CONTENT_TYPES[extname7(file)] ?? "application/octet-stream", "cache-control": "no-store" });
5558
+ const body = readFileSync16(file);
5559
+ response.writeHead(200, { "content-type": CONTENT_TYPES[extname8(file)] ?? "application/octet-stream", "cache-control": "no-store" });
5462
5560
  response.end(body);
5463
5561
  } catch {
5464
5562
  response.writeHead(404);
@@ -5469,7 +5567,7 @@ async function buildServedImpact(root, changedPaths) {
5469
5567
  const normalized = [...new Set(changedPaths.map((path) => canonicalizeChangedPath(root, path)).filter(Boolean))];
5470
5568
  const { status, index, metadata } = readFreshIndex(root);
5471
5569
  const overlay = await buildVectorImpactOverlay(root, index, normalized);
5472
- const impact = buildImpactReport(index, normalized, { overlay, verboseSemantic: false, related: 40 });
5570
+ const impact = buildImpactReport(index, normalized, { root, overlay, verboseSemantic: false, related: 40 });
5473
5571
  const graph = buildImpactGraphPayload(index, impact);
5474
5572
  const graphIds = new Set(graph.nodes.map((node2) => node2.id));
5475
5573
  const explorerImpact = {
@@ -5501,9 +5599,9 @@ async function startImpactGraphServer({ root, changed, host = "127.0.0.1", port
5501
5599
  }
5502
5600
  return asset(response, join15(ASSET_ROOT, relative6));
5503
5601
  });
5504
- await new Promise((resolve17, reject) => {
5602
+ await new Promise((resolve18, reject) => {
5505
5603
  server.once("error", reject);
5506
- server.listen(port, host, resolve17);
5604
+ server.listen(port, host, resolve18);
5507
5605
  });
5508
5606
  const address = server.address();
5509
5607
  const actualPort = typeof address === "object" && address ? address.port : port;
@@ -5511,6 +5609,13 @@ async function startImpactGraphServer({ root, changed, host = "127.0.0.1", port
5511
5609
  }
5512
5610
 
5513
5611
  // packages/cli/src/impact/format.mjs
5612
+ function formatImpactWarnings(warnings) {
5613
+ if (!warnings?.total) return [];
5614
+ return [
5615
+ `Reference warnings: ${warnings.total} (${warnings.omitted} omitted); broken references excluded from impact.`,
5616
+ ...warnings.items.map((item) => `- ${item.source}${item.line ? `:${item.line}` : ""} -> ${item.target} [${item.reason}]${item.reason === "REFERENCE_UNAVAILABLE" ? " (access uncertain; not excluded)" : ""}`)
5617
+ ];
5618
+ }
5514
5619
  function formatCompactImpactGroup(name, group) {
5515
5620
  const items = group?.items ?? [];
5516
5621
  if (items.length === 0) return [];
@@ -5526,6 +5631,7 @@ function formatCompactImpactGroup(name, group) {
5526
5631
  function formatCompactImpactOutput(payload, impact) {
5527
5632
  const refreshNote = payload.metadata.cacheRefreshed ? ", refreshed" : "";
5528
5633
  const lines = [`graph impact compact: ${impact.related.length} related node(s), ${impact.omittedRelated ?? 0} omitted (${payload.status.status}${refreshNote} index)`, `changed files: ${(impact.changedFiles ?? [impact.changed]).join(", ")}`];
5634
+ lines.push(...formatImpactWarnings(impact.warnings));
5529
5635
  for (const entry of impact.perSeed ?? []) lines.push(...formatCompactImpactGroup(`top for ${entry.changed}`, { items: entry.related }));
5530
5636
  for (const name of ["docs", "contracts", "tests", "files", "commands", "events", "packageResources", "symbols"]) lines.push(...formatCompactImpactGroup(name, impact.groups[name]));
5531
5637
  return lines.join("\n");
@@ -5589,6 +5695,13 @@ function formatYmlImpactOutput(payload, impact) {
5589
5695
  }
5590
5696
  lines.push(" groups:");
5591
5697
  for (const name of ["docs", "contracts", "tests", "files", "commands", "events", "packageResources", "symbols"]) lines.push(...formatYmlImpactGroup(name, impact.groups[name]));
5698
+ lines.push(" warnings:");
5699
+ lines.push(` total: ${impact.warnings?.total ?? 0}`, ` omitted: ${impact.warnings?.omitted ?? 0}`, " items:");
5700
+ if (!impact.warnings?.items?.length) lines.push(" []");
5701
+ else for (const item of impact.warnings.items) {
5702
+ lines.push(` - source: ${ymlScalar(item.source)}`, ` target: ${ymlScalar(item.target)}`, ` target_path: ${ymlScalar(item.targetPath)}`, ` relation: ${ymlScalar(item.relation)}`, ` reason: ${ymlScalar(item.reason)}`);
5703
+ if (item.line) lines.push(` line: ${item.line}`);
5704
+ }
5592
5705
  lines.push(" recommended_actions:");
5593
5706
  lines.push(' - "review_related_docs"');
5594
5707
  lines.push(' - "run_related_tests"');
@@ -5692,14 +5805,14 @@ async function runGraph(argv) {
5692
5805
  if (isImpact) options.changed = [...new Set(options.changed.map((path) => canonicalizeChangedPath(options.root, path)).filter(Boolean))];
5693
5806
  const { status, index, metadata } = readFreshIndex(options.root);
5694
5807
  const overlay = isImpact ? await buildVectorImpactOverlay(options.root, index, options.changed) : void 0;
5695
- const rawImpact = isImpact ? buildImpactReport(index, options.changed, { overlay, verboseSemantic: options.json }) : void 0;
5808
+ const rawImpact = isImpact ? buildImpactReport(index, options.changed, { root: options.root, overlay, verboseSemantic: options.json }) : void 0;
5696
5809
  const impact = isImpact && (options.compact || options.yml) ? buildCompactImpactReport(rawImpact) : rawImpact;
5697
5810
  const payload = isImpact ? { ok: status.ok, command: "graph impact", compact: options.compact || void 0, root: options.root, status, metadata, changed: options.changed[0], changedFiles: options.changed, related: impact.related, impact } : { ok: status.ok, command: "graph communities", root: options.root, status, metadata, graph: graphSummary(index), communities: buildCommunities(index) };
5698
5811
  const refreshNote = metadata.cacheRefreshed ? ", refreshed" : "";
5699
5812
  if (options.json) console.log(JSON.stringify(payload, null, 2));
5700
5813
  else if (isImpact && options.yml) console.log(formatYmlImpactOutput(payload, impact));
5701
5814
  else if (isImpact && options.compact) console.log(formatCompactImpactOutput(payload, impact));
5702
- else if (isImpact) console.log(`graph impact: ${payload.related.length} related node(s), ${impact.omittedRelated ?? 0} omitted (${status.status}${refreshNote} index)`);
5815
+ else if (isImpact) console.log([`graph impact: ${payload.related.length} related node(s), ${impact.omittedRelated ?? 0} omitted (${status.status}${refreshNote} index)`, ...formatImpactWarnings(impact.warnings)].join("\n"));
5703
5816
  else console.log(`graph communities: ${payload.communities.communities.length}/${payload.communities.total} shown, ${payload.communities.omitted} omitted (${status.status}${refreshNote} index)`);
5704
5817
  }
5705
5818
 
package/mcp/server.mjs CHANGED
@@ -21745,7 +21745,13 @@ var ContextStore = class {
21745
21745
  }
21746
21746
  const filterSql = filters.length ? ` AND ${filters.join(" AND ")}` : "";
21747
21747
  const select = "SELECT c.source_id, c.ordinal, c.body, s.label, s.kind, s.scope, s.metadata";
21748
- const porterRows = this.db.prepare(`
21748
+ const wildcard = String(query ?? "").trim() === "*";
21749
+ const porterRows = wildcard ? this.db.prepare(`
21750
+ ${select}, 0 AS rank
21751
+ FROM chunks c JOIN sources s ON s.id = c.source_id
21752
+ WHERE 1 = 1${filterSql}
21753
+ ORDER BY s.created_at DESC, s.id, c.ordinal LIMIT ?
21754
+ `).all(...filterParams, candidateLimit) : this.db.prepare(`
21749
21755
  ${select}, bm25(chunks) AS rank
21750
21756
  FROM chunks c JOIN sources s ON s.id = c.source_id
21751
21757
  WHERE chunks MATCH ?${filterSql}
@@ -21764,7 +21770,7 @@ var ContextStore = class {
21764
21770
  `).all(...labelParams, ...filterParams, candidateLimit);
21765
21771
  const typoWindow = Math.min(500, candidateLimit * 5);
21766
21772
  const typoHalf = Math.max(1, Math.floor(typoWindow / 2));
21767
- const trigramRows = this.db.prepare(`
21773
+ const trigramRows = wildcard ? [] : this.db.prepare(`
21768
21774
  SELECT * FROM (
21769
21775
  SELECT ${select.replace("SELECT ", "")}, 0 AS rank
21770
21776
  FROM chunks c JOIN sources s ON s.id = c.source_id
@@ -21788,7 +21794,7 @@ var ContextStore = class {
21788
21794
  return { sourceId: row.source_id, ordinal: Number(row.ordinal), body: row.body, label: row.label, kind: row.kind, scope: row.scope, rank: row.rank, metadata: readProvenanceMetadata(storedMetadata) };
21789
21795
  };
21790
21796
  const fused = reciprocalRankFusion([
21791
- { name: "porter-bm25", candidates: porterRows.map(toCandidate) },
21797
+ { name: wildcard ? "bounded-browse" : "porter-bm25", candidates: porterRows.map(toCandidate) },
21792
21798
  { name: "label-path", candidates: labelRows.map(toCandidate) },
21793
21799
  { name: "trigram-v1", candidates: trigramRows.map(toCandidate) }
21794
21800
  ], { limit: candidateLimit });
@@ -21801,7 +21807,7 @@ var ContextStore = class {
21801
21807
  scope: row.scope,
21802
21808
  ordinal: row.ordinal,
21803
21809
  rank: row.rank,
21804
- text: excerpt(row.body, query),
21810
+ text: excerpt(row.body, wildcard ? "" : query),
21805
21811
  metadata: row.metadata,
21806
21812
  trust: row.metadata.trust,
21807
21813
  sourceType: row.metadata.sourceType,
@@ -23426,7 +23432,7 @@ register("index", "Index a local text file or bounded directory into the project
23426
23432
  maxFiles: external_exports.number().int().nonnegative().optional(),
23427
23433
  maxAggregateBytes: external_exports.number().int().nonnegative().optional()
23428
23434
  }, (input, extra) => ({ ok: true, ...indexFile(getStore(), { ...input, root }, sessionId, extra.signal) }), { readOnlyHint: true });
23429
- register("search", "Search indexed command, file, and fetched content and return bounded excerpts.", {
23435
+ register("search", "Search indexed content for bounded excerpts. For failure diagnostics try fail OR error OR reason. Query * browses bounded excerpts with the same source/scope/session filters and limit; neither no matches nor partial excerpts prove diagnostics are absent.", {
23430
23436
  query: external_exports.string().min(1),
23431
23437
  scope: scopeSchema,
23432
23438
  source: external_exports.string().optional(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dotdotgod/claude-code",
3
- "version": "0.3.11",
3
+ "version": "0.3.12",
4
4
  "description": "Claude Code adapter for dotdotgod project memory workflows.",
5
5
  "type": "module",
6
6
  "license": "Elastic-2.0",
@@ -25,7 +25,7 @@
25
25
  },
26
26
  "devDependencies": {
27
27
  "esbuild": "0.27.3",
28
- "@dotdotgod/context": "0.3.11"
28
+ "@dotdotgod/context": "0.3.12"
29
29
  },
30
30
  "files": [
31
31
  ".claude-plugin",