@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.
- package/mcp/cli.mjs +154 -41
- package/mcp/server.mjs +11 -5
- 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 =
|
|
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)
|
|
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
|
|
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
|
|
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
|
|
4759
|
-
import { extname as
|
|
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 =
|
|
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 =
|
|
4778
|
-
const absolute =
|
|
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(
|
|
4898
|
+
if (unsafeProfilePath(path) || !TEXT_EXTENSIONS.has(extname6(path).toLowerCase())) return "";
|
|
4808
4899
|
try {
|
|
4809
|
-
const absolute =
|
|
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(
|
|
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 =
|
|
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
|
|
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,
|
|
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
|
|
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 =
|
|
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,
|
|
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:
|
|
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
|
|
5261
|
-
import { join as join14, resolve as
|
|
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 =
|
|
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 =
|
|
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
|
|
5335
|
-
import { dirname as dirname8, extname as
|
|
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
|
|
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 =
|
|
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 =
|
|
5461
|
-
response.writeHead(200, { "content-type": CONTENT_TYPES[
|
|
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((
|
|
5602
|
+
await new Promise((resolve18, reject) => {
|
|
5505
5603
|
server.once("error", reject);
|
|
5506
|
-
server.listen(port, host,
|
|
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
|
|
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
|
|
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.
|
|
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.
|
|
28
|
+
"@dotdotgod/context": "0.3.12"
|
|
29
29
|
},
|
|
30
30
|
"files": [
|
|
31
31
|
".claude-plugin",
|