@dotdotgod/claude-code 0.3.10 → 0.3.11
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 +223 -15
- package/package.json +2 -2
package/mcp/cli.mjs
CHANGED
|
@@ -1345,12 +1345,18 @@ Checks or writes generated Markdown traceability link sections from canonical fe
|
|
|
1345
1345
|
case "graph":
|
|
1346
1346
|
return `Usage:
|
|
1347
1347
|
dotdotgod graph impact <root> --changed <path> [--changed <path> ...] [--compact|--json|--yml|--yaml]
|
|
1348
|
+
dotdotgod graph serve <root> --changed <path> [--changed <path> ...] [--host HOST] [--port PORT]
|
|
1348
1349
|
dotdotgod graph communities <root> [--json]`;
|
|
1349
1350
|
case "graph impact":
|
|
1350
1351
|
return `Usage:
|
|
1351
1352
|
dotdotgod graph impact <root> --changed <path> [--changed <path> ...] [--compact|--json|--yml|--yaml]
|
|
1352
1353
|
|
|
1353
1354
|
Ranks nodes related to one or more changed files. <root> is the project root; repeat --changed with project-relative file paths. Results include a combined ranking and the top five related nodes for each changed file. Use --compact for a short text summary or --yml/--yaml for structured agent-facing output.`;
|
|
1355
|
+
case "graph serve":
|
|
1356
|
+
return `Usage:
|
|
1357
|
+
dotdotgod graph serve <root> --changed <path> [--changed <path> ...] [--host HOST] [--port PORT]
|
|
1358
|
+
|
|
1359
|
+
Starts a local-only impact graph explorer. Changed files are rendered as root nodes and are excluded from related-result lists.`;
|
|
1354
1360
|
case "graph communities":
|
|
1355
1361
|
return `Usage:
|
|
1356
1362
|
dotdotgod graph communities <root> [--json]`;
|
|
@@ -1374,6 +1380,7 @@ Commands:
|
|
|
1374
1380
|
status Show local index status.
|
|
1375
1381
|
traceability links Check or write generated traceability links.
|
|
1376
1382
|
graph impact Find files related to changed files.
|
|
1383
|
+
graph serve Open a local impact graph explorer.
|
|
1377
1384
|
graph communities Find groups of related project-memory nodes.
|
|
1378
1385
|
|
|
1379
1386
|
Options:
|
|
@@ -1711,7 +1718,7 @@ import { spawnSync } from "node:child_process";
|
|
|
1711
1718
|
import { basename as basename3, extname as extname2, join as join5 } from "node:path";
|
|
1712
1719
|
|
|
1713
1720
|
// packages/cli/src/index/constants.mjs
|
|
1714
|
-
var CACHE_VERSION =
|
|
1721
|
+
var CACHE_VERSION = 14;
|
|
1715
1722
|
var CACHE_DIR = ".dotdotgod";
|
|
1716
1723
|
var MANIFEST_FILE = "manifest.json";
|
|
1717
1724
|
|
|
@@ -2225,7 +2232,7 @@ function mergeIncrementalGraph(previousGraph, changedGraph, changedPaths) {
|
|
|
2225
2232
|
if (!previousGraph) return changedGraph;
|
|
2226
2233
|
const changedSet = new Set(changedPaths);
|
|
2227
2234
|
const changedNodeIds = new Set(previousGraph.nodes.filter((node2) => nodeOwnedByPath(node2, changedSet)).map((node2) => node2.id));
|
|
2228
|
-
for (const node2 of changedGraph.nodes) changedNodeIds.add(node2.id);
|
|
2235
|
+
for (const node2 of changedGraph.nodes) if (nodeOwnedByPath(node2, changedSet)) changedNodeIds.add(node2.id);
|
|
2229
2236
|
const graph = { nodes: [], edges: [] };
|
|
2230
2237
|
for (const node2 of previousGraph.nodes) if (!changedNodeIds.has(node2.id) && !nodeOwnedByPath(node2, changedSet)) addNode(graph, node2.id, node2.type, Object.fromEntries(Object.entries(node2).filter(([key]) => key !== "id" && key !== "type")));
|
|
2231
2238
|
for (const edge of previousGraph.edges) if (!changedNodeIds.has(edge.source) && !changedNodeIds.has(edge.target)) addEdge(graph, edge.source, edge.target, edge.relation, Object.fromEntries(Object.entries(edge).filter(([key]) => key !== "source" && key !== "target" && key !== "relation")));
|
|
@@ -3204,15 +3211,15 @@ import { resolve as resolve11 } from "node:path";
|
|
|
3204
3211
|
function parse(argv) {
|
|
3205
3212
|
const [action2 = "status", ...rest] = argv;
|
|
3206
3213
|
let root = ".";
|
|
3207
|
-
let
|
|
3214
|
+
let json2 = false;
|
|
3208
3215
|
let confirm = false;
|
|
3209
3216
|
for (const arg of rest) {
|
|
3210
|
-
if (arg === "--json")
|
|
3217
|
+
if (arg === "--json") json2 = true;
|
|
3211
3218
|
else if (arg === "--confirm") confirm = true;
|
|
3212
3219
|
else if (!arg.startsWith("-") && root === ".") root = arg;
|
|
3213
3220
|
else usage(`Unknown embedding option: ${arg}`, `embedding ${action2}`);
|
|
3214
3221
|
}
|
|
3215
|
-
return { action: action2, root: resolve11(root), json, confirm };
|
|
3222
|
+
return { action: action2, root: resolve11(root), json: json2, confirm };
|
|
3216
3223
|
}
|
|
3217
3224
|
function runEmbedding(argv, options = {}) {
|
|
3218
3225
|
const parsed = parse(argv);
|
|
@@ -3339,8 +3346,8 @@ function buildDocumentationMap(projectRoot = ".", { depth = 5 } = {}) {
|
|
|
3339
3346
|
}
|
|
3340
3347
|
|
|
3341
3348
|
// packages/cli/src/commands/map.mjs
|
|
3342
|
-
function fail(
|
|
3343
|
-
if (
|
|
3349
|
+
function fail(json2, code, message) {
|
|
3350
|
+
if (json2) console.log(JSON.stringify({ ok: false, error: { code, message } }, null, 2));
|
|
3344
3351
|
else console.error(message);
|
|
3345
3352
|
process.exit(2);
|
|
3346
3353
|
}
|
|
@@ -4926,7 +4933,7 @@ function buildCombinedImpactReport(index, changedPaths, limits = {}) {
|
|
|
4926
4933
|
const nodeById = new Map(graph.nodes.map((node2) => [node2.id, node2]));
|
|
4927
4934
|
const seeds = changedPaths.map((path) => `file:${path}`);
|
|
4928
4935
|
const seedSet = new Set(seeds);
|
|
4929
|
-
const maxRelated = Math.max(limits.related ?? 25,
|
|
4936
|
+
const maxRelated = Math.max(limits.related ?? 25, 0);
|
|
4930
4937
|
const groups = { files: { items: [], omitted: 0 }, docs: { items: [], omitted: 0 }, contracts: { items: [], omitted: 0 }, tests: { items: [], omitted: 0 }, commands: { items: [], omitted: 0 }, events: { items: [], omitted: 0 }, packageResources: { items: [], omitted: 0 }, symbols: { items: [], omitted: 0 } };
|
|
4931
4938
|
const relatedIds = new Set(seeds);
|
|
4932
4939
|
const reasons = new Map(seeds.map((seed) => [seed, /* @__PURE__ */ new Set(["changed-file"])]));
|
|
@@ -4962,7 +4969,8 @@ function buildCombinedImpactReport(index, changedPaths, limits = {}) {
|
|
|
4962
4969
|
const scored = scoreImpactItem({ ...node2, reasons: reasonList, retrieval }, seedSet, changedPaths, policy, pprScores, config);
|
|
4963
4970
|
return { ...node2, reasons: reasonList, hasCuratedEvidence, ...vectorEvidence.has(id) ? { vectorEvidence: vectorEvidence.get(id) } : {}, retrieval: { ...retrieval, signals: [.../* @__PURE__ */ new Set([...retrieval.signals ?? [], ...reasonSignals])] }, ...scored };
|
|
4964
4971
|
}).sort(compareImpactItems(seeds));
|
|
4965
|
-
const
|
|
4972
|
+
const rankedRelated = relatedAll.filter((item) => !seedSet.has(item.id));
|
|
4973
|
+
const related = selectImpactItems(rankedRelated, maxRelated, []);
|
|
4966
4974
|
for (const item of related) {
|
|
4967
4975
|
if (item.type === "file") {
|
|
4968
4976
|
const area2 = docsArea2(item.path, config);
|
|
@@ -4984,9 +4992,9 @@ function buildImpactReport(index, changedPaths, limits = {}) {
|
|
|
4984
4992
|
aggregate2.perSeed = normalized.map((changed) => {
|
|
4985
4993
|
const seedId = `file:${changed}`;
|
|
4986
4994
|
const seedOverlay = limits.overlay ? { ...limits.overlay, edges: (limits.overlay.edges ?? []).filter((edge) => edge.source === seedId) } : void 0;
|
|
4987
|
-
const report = buildCombinedImpactReport(index, [changed], { ...limits, overlay: seedOverlay, related: Math.max(limits.related ?? 25, perSeedLimit
|
|
4988
|
-
const related = report.related.
|
|
4989
|
-
return { changed, related, omittedRelated: Math.max(0, report.related.length -
|
|
4995
|
+
const report = buildCombinedImpactReport(index, [changed], { ...limits, overlay: seedOverlay, related: Math.max(limits.related ?? 25, perSeedLimit) });
|
|
4996
|
+
const related = report.related.slice(0, perSeedLimit);
|
|
4997
|
+
return { changed, related, omittedRelated: Math.max(0, report.related.length - related.length) + report.omittedRelated };
|
|
4990
4998
|
});
|
|
4991
4999
|
return aggregate2;
|
|
4992
5000
|
}
|
|
@@ -5006,7 +5014,7 @@ function compactImpactGroup(group = { items: [], omitted: 0 }, limit = 5) {
|
|
|
5006
5014
|
return { items, omitted: (group.omitted ?? 0) + Math.max(0, (group.items?.length ?? 0) - items.length) };
|
|
5007
5015
|
}
|
|
5008
5016
|
function buildCompactImpactReport(impact, limits = {}) {
|
|
5009
|
-
const relatedLimit =
|
|
5017
|
+
const relatedLimit = limits.related ?? 10;
|
|
5010
5018
|
const groupLimit = limits.groupItems ?? 5;
|
|
5011
5019
|
const changedFiles = impact.changedFiles ?? [impact.changed];
|
|
5012
5020
|
const seedIds = new Set(changedFiles.map((path) => `file:${path}`));
|
|
@@ -5321,6 +5329,187 @@ function runTraceability(argv) {
|
|
|
5321
5329
|
process.exit(ok ? 0 : 1);
|
|
5322
5330
|
}
|
|
5323
5331
|
|
|
5332
|
+
// packages/cli/src/graph-view/server.mjs
|
|
5333
|
+
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";
|
|
5336
|
+
import { fileURLToPath } from "node:url";
|
|
5337
|
+
|
|
5338
|
+
// packages/cli/src/graph-view/payload.mjs
|
|
5339
|
+
function edgeKey(edge) {
|
|
5340
|
+
return `${edge.source}\0${edge.target}\0${edge.relation}`;
|
|
5341
|
+
}
|
|
5342
|
+
function labelForNode(node2) {
|
|
5343
|
+
if (node2.type === "contract") return node2.title ?? node2.contractId ?? node2.id;
|
|
5344
|
+
return node2.path ?? node2.command ?? node2.name ?? node2.target ?? node2.id;
|
|
5345
|
+
}
|
|
5346
|
+
function adjacencyFor(nodes, edges) {
|
|
5347
|
+
const adjacency = new Map(nodes.map((node2) => [node2.id, /* @__PURE__ */ new Set()]));
|
|
5348
|
+
for (const edge of edges) {
|
|
5349
|
+
adjacency.get(edge.source)?.add(edge.target);
|
|
5350
|
+
adjacency.get(edge.target)?.add(edge.source);
|
|
5351
|
+
}
|
|
5352
|
+
return adjacency;
|
|
5353
|
+
}
|
|
5354
|
+
function rootReachability(nodes, edges, seedIds) {
|
|
5355
|
+
const adjacency = adjacencyFor(nodes, edges);
|
|
5356
|
+
const reachable = /* @__PURE__ */ new Set();
|
|
5357
|
+
const depthByNode = /* @__PURE__ */ new Map();
|
|
5358
|
+
const nearestRootByNode = /* @__PURE__ */ new Map();
|
|
5359
|
+
const queue = [];
|
|
5360
|
+
for (const node2 of nodes) {
|
|
5361
|
+
if (!seedIds.has(node2.id)) continue;
|
|
5362
|
+
reachable.add(node2.id);
|
|
5363
|
+
depthByNode.set(node2.id, 0);
|
|
5364
|
+
nearestRootByNode.set(node2.id, node2.id);
|
|
5365
|
+
queue.push(node2.id);
|
|
5366
|
+
}
|
|
5367
|
+
for (let cursor = 0; cursor < queue.length; cursor += 1) {
|
|
5368
|
+
const current = queue[cursor];
|
|
5369
|
+
for (const neighbor of adjacency.get(current) ?? []) {
|
|
5370
|
+
if (reachable.has(neighbor)) continue;
|
|
5371
|
+
reachable.add(neighbor);
|
|
5372
|
+
depthByNode.set(neighbor, (depthByNode.get(current) ?? 0) + 1);
|
|
5373
|
+
nearestRootByNode.set(neighbor, nearestRootByNode.get(current));
|
|
5374
|
+
queue.push(neighbor);
|
|
5375
|
+
}
|
|
5376
|
+
}
|
|
5377
|
+
return { reachable, depthByNode, nearestRootByNode };
|
|
5378
|
+
}
|
|
5379
|
+
function buildImpactGraphPayload(index, impact) {
|
|
5380
|
+
const graph = index?.graph ?? { nodes: [], edges: [] };
|
|
5381
|
+
const config = index?.memoryConfig;
|
|
5382
|
+
const seedIds = new Set((impact.changedFiles ?? [impact.changed]).map((path) => `file:${path}`));
|
|
5383
|
+
const impactItems = new Map((impact.related ?? []).map((item) => [item.id, item]));
|
|
5384
|
+
const isAllowedNode = (node2) => {
|
|
5385
|
+
if (!node2) return false;
|
|
5386
|
+
if (node2.path && resolveMemoryArea(node2.path, config)?.scope === "local") return false;
|
|
5387
|
+
return true;
|
|
5388
|
+
};
|
|
5389
|
+
const sourceNodes = (graph.nodes ?? []).filter(isAllowedNode);
|
|
5390
|
+
const indexedNodes = new Map(sourceNodes.map((node2) => [node2.id, node2]));
|
|
5391
|
+
const seenEdges = /* @__PURE__ */ new Set();
|
|
5392
|
+
const edges = [];
|
|
5393
|
+
for (const edge of graph.edges ?? []) {
|
|
5394
|
+
if (!indexedNodes.has(edge.source) || !indexedNodes.has(edge.target)) continue;
|
|
5395
|
+
const key = edgeKey(edge);
|
|
5396
|
+
if (seenEdges.has(key)) continue;
|
|
5397
|
+
seenEdges.add(key);
|
|
5398
|
+
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 });
|
|
5399
|
+
}
|
|
5400
|
+
const { reachable, depthByNode, nearestRootByNode } = rootReachability(sourceNodes, edges, seedIds);
|
|
5401
|
+
const retainedNodes = sourceNodes.filter((node2) => reachable.has(node2.id));
|
|
5402
|
+
const retainedEdges = edges.filter((edge) => reachable.has(edge.source) && reachable.has(edge.target));
|
|
5403
|
+
const nodes = retainedNodes.map((source) => {
|
|
5404
|
+
const item = impactItems.get(source.id);
|
|
5405
|
+
const seed = seedIds.has(source.id);
|
|
5406
|
+
return {
|
|
5407
|
+
id: source.id,
|
|
5408
|
+
type: source.type ?? item?.type ?? "file",
|
|
5409
|
+
path: source.path ?? item?.path,
|
|
5410
|
+
label: labelForNode({ ...source, ...item }),
|
|
5411
|
+
seed,
|
|
5412
|
+
ranked: Boolean(item),
|
|
5413
|
+
depth: depthByNode.get(source.id) ?? 0,
|
|
5414
|
+
nearestRoot: nearestRootByNode.get(source.id),
|
|
5415
|
+
component: 0,
|
|
5416
|
+
seededComponent: true,
|
|
5417
|
+
impactScore: seed ? 100 : item?.impactScore ?? 0,
|
|
5418
|
+
reasons: seed ? ["changed-file"] : item?.reasons ?? [],
|
|
5419
|
+
scoreBreakdown: seed ? { seed: 100 } : item?.scoreBreakdown,
|
|
5420
|
+
retrieval: item?.retrieval ?? source.retrieval,
|
|
5421
|
+
contractId: item?.contractId ?? source.contractId,
|
|
5422
|
+
title: item?.title ?? source.title,
|
|
5423
|
+
sections: item?.sections ?? source.sections,
|
|
5424
|
+
vectorEvidence: item?.vectorEvidence
|
|
5425
|
+
};
|
|
5426
|
+
});
|
|
5427
|
+
return {
|
|
5428
|
+
changed: impact.changed,
|
|
5429
|
+
changedFiles: impact.changedFiles ?? [impact.changed],
|
|
5430
|
+
ranking: impact.ranking,
|
|
5431
|
+
semantic: impact.semantic,
|
|
5432
|
+
complete: true,
|
|
5433
|
+
nodes,
|
|
5434
|
+
edges: retainedEdges,
|
|
5435
|
+
components: retainedNodes.length > 0 ? [{ id: 0, size: retainedNodes.length, seeded: true }] : [],
|
|
5436
|
+
diagnostics: {
|
|
5437
|
+
rootNodes: retainedNodes.filter((node2) => seedIds.has(node2.id)).length,
|
|
5438
|
+
connectedNodes: retainedNodes.length,
|
|
5439
|
+
disconnectedNodesOmitted: sourceNodes.length - retainedNodes.length,
|
|
5440
|
+
maximumDepth: Math.max(0, ...depthByNode.values())
|
|
5441
|
+
},
|
|
5442
|
+
omittedRelated: impact.omittedRelated ?? 0
|
|
5443
|
+
};
|
|
5444
|
+
}
|
|
5445
|
+
|
|
5446
|
+
// packages/cli/src/graph-view/server.mjs
|
|
5447
|
+
var ASSET_ROOT = join15(dirname8(fileURLToPath(import.meta.url)), "assets");
|
|
5448
|
+
var CONTENT_TYPES = { ".css": "text/css; charset=utf-8", ".html": "text/html; charset=utf-8", ".js": "text/javascript; charset=utf-8", ".map": "application/json; charset=utf-8" };
|
|
5449
|
+
var VENDOR_FILES = {
|
|
5450
|
+
"/vendor/sigma.js": new URL("../../node_modules/sigma/dist/sigma.min.js", import.meta.url),
|
|
5451
|
+
"/vendor/graphology.js": new URL("../../node_modules/graphology/dist/graphology.umd.min.js", import.meta.url),
|
|
5452
|
+
"/vendor/d3.js": new URL("../../node_modules/d3/dist/d3.min.js", import.meta.url)
|
|
5453
|
+
};
|
|
5454
|
+
function json(response, status, value) {
|
|
5455
|
+
response.writeHead(status, { "content-type": "application/json; charset=utf-8", "cache-control": "no-store" });
|
|
5456
|
+
response.end(JSON.stringify(value));
|
|
5457
|
+
}
|
|
5458
|
+
function asset(response, file) {
|
|
5459
|
+
try {
|
|
5460
|
+
const body = readFileSync15(file);
|
|
5461
|
+
response.writeHead(200, { "content-type": CONTENT_TYPES[extname7(file)] ?? "application/octet-stream", "cache-control": "no-store" });
|
|
5462
|
+
response.end(body);
|
|
5463
|
+
} catch {
|
|
5464
|
+
response.writeHead(404);
|
|
5465
|
+
response.end("Not found");
|
|
5466
|
+
}
|
|
5467
|
+
}
|
|
5468
|
+
async function buildServedImpact(root, changedPaths) {
|
|
5469
|
+
const normalized = [...new Set(changedPaths.map((path) => canonicalizeChangedPath(root, path)).filter(Boolean))];
|
|
5470
|
+
const { status, index, metadata } = readFreshIndex(root);
|
|
5471
|
+
const overlay = await buildVectorImpactOverlay(root, index, normalized);
|
|
5472
|
+
const impact = buildImpactReport(index, normalized, { overlay, verboseSemantic: false, related: 40 });
|
|
5473
|
+
const graph = buildImpactGraphPayload(index, impact);
|
|
5474
|
+
const graphIds = new Set(graph.nodes.map((node2) => node2.id));
|
|
5475
|
+
const explorerImpact = {
|
|
5476
|
+
...impact,
|
|
5477
|
+
related: impact.related.filter((item) => graphIds.has(item.id))
|
|
5478
|
+
};
|
|
5479
|
+
return { ok: status.ok, status, metadata, impact: explorerImpact, graph };
|
|
5480
|
+
}
|
|
5481
|
+
async function startImpactGraphServer({ root, changed, host = "127.0.0.1", port = 0 }) {
|
|
5482
|
+
let payload = await buildServedImpact(root, changed);
|
|
5483
|
+
const server = createServer(async (request, response) => {
|
|
5484
|
+
const url = new URL(request.url ?? "/", `http://${request.headers.host ?? host}`);
|
|
5485
|
+
if (url.pathname === "/api/impact") return json(response, 200, payload);
|
|
5486
|
+
if (url.pathname === "/api/reroot") {
|
|
5487
|
+
const paths = url.searchParams.getAll("changed");
|
|
5488
|
+
if (paths.length === 0) return json(response, 400, { ok: false, error: { code: "MISSING_CHANGED", message: "At least one changed path is required." } });
|
|
5489
|
+
try {
|
|
5490
|
+
payload = await buildServedImpact(root, paths);
|
|
5491
|
+
return json(response, 200, payload);
|
|
5492
|
+
} catch (error) {
|
|
5493
|
+
return json(response, 500, { ok: false, error: { code: "GRAPH_SERVE_FAILED", message: error instanceof Error ? error.message : String(error) } });
|
|
5494
|
+
}
|
|
5495
|
+
}
|
|
5496
|
+
if (VENDOR_FILES[url.pathname]) return asset(response, fileURLToPath(VENDOR_FILES[url.pathname]));
|
|
5497
|
+
const relative6 = url.pathname === "/" ? "index.html" : url.pathname.slice(1);
|
|
5498
|
+
if (!["index.html", "app.css", "app.js"].includes(relative6)) {
|
|
5499
|
+
response.writeHead(404);
|
|
5500
|
+
return response.end("Not found");
|
|
5501
|
+
}
|
|
5502
|
+
return asset(response, join15(ASSET_ROOT, relative6));
|
|
5503
|
+
});
|
|
5504
|
+
await new Promise((resolve17, reject) => {
|
|
5505
|
+
server.once("error", reject);
|
|
5506
|
+
server.listen(port, host, resolve17);
|
|
5507
|
+
});
|
|
5508
|
+
const address = server.address();
|
|
5509
|
+
const actualPort = typeof address === "object" && address ? address.port : port;
|
|
5510
|
+
return { server, url: `http://${host}:${actualPort}`, payload };
|
|
5511
|
+
}
|
|
5512
|
+
|
|
5324
5513
|
// packages/cli/src/impact/format.mjs
|
|
5325
5514
|
function formatCompactImpactGroup(name, group) {
|
|
5326
5515
|
const items = group?.items ?? [];
|
|
@@ -5428,6 +5617,8 @@ function parseGraphOptions(argv) {
|
|
|
5428
5617
|
const changed = [];
|
|
5429
5618
|
let compact = false;
|
|
5430
5619
|
let yml = false;
|
|
5620
|
+
let host = "127.0.0.1";
|
|
5621
|
+
let port = 0;
|
|
5431
5622
|
for (let i = 0; i < argv.length; i += 1) {
|
|
5432
5623
|
if (argv[i] === "--changed") {
|
|
5433
5624
|
const next = argv[i + 1];
|
|
@@ -5437,19 +5628,36 @@ function parseGraphOptions(argv) {
|
|
|
5437
5628
|
}
|
|
5438
5629
|
} else if (argv[i] === "--compact") compact = true;
|
|
5439
5630
|
else if (argv[i] === "--yml" || argv[i] === "--yaml") yml = true;
|
|
5440
|
-
else
|
|
5631
|
+
else if (argv[i] === "--host" && argv[i + 1] && !argv[i + 1].startsWith("-")) {
|
|
5632
|
+
host = argv[i + 1];
|
|
5633
|
+
i += 1;
|
|
5634
|
+
} else if (argv[i] === "--port" && argv[i + 1] && !argv[i + 1].startsWith("-")) {
|
|
5635
|
+
port = Number(argv[i + 1]);
|
|
5636
|
+
i += 1;
|
|
5637
|
+
} else filtered.push(argv[i]);
|
|
5441
5638
|
}
|
|
5442
5639
|
const options = parseCommon(filtered);
|
|
5443
5640
|
options.changed = [...new Set(changed)];
|
|
5444
5641
|
options.compact = compact;
|
|
5445
5642
|
options.yml = yml;
|
|
5643
|
+
options.host = host;
|
|
5644
|
+
options.port = Number.isInteger(port) && port >= 0 && port <= 65535 ? port : NaN;
|
|
5446
5645
|
return options;
|
|
5447
5646
|
}
|
|
5448
5647
|
async function runGraph(argv) {
|
|
5449
5648
|
const sub = argv[0];
|
|
5450
5649
|
const isImpact = sub === "impact";
|
|
5451
|
-
|
|
5650
|
+
const isServe = sub === "serve";
|
|
5651
|
+
if (!["impact", "communities", "serve"].includes(sub)) usage(sub ? `Unknown graph command: ${sub}` : "Missing graph command.", "graph");
|
|
5452
5652
|
const options = parseGraphOptions(argv.slice(1));
|
|
5653
|
+
if (isServe && options.changed.length === 0) usage("Missing required option: --changed <path>.", "graph serve");
|
|
5654
|
+
if (isServe && !Number.isInteger(options.port)) usage("Invalid --port. Use an integer from 0 through 65535.", "graph serve");
|
|
5655
|
+
if (isServe) {
|
|
5656
|
+
options.changed = [...new Set(options.changed.map((path) => canonicalizeChangedPath(options.root, path)).filter(Boolean))];
|
|
5657
|
+
const running = await startImpactGraphServer({ root: options.root, changed: options.changed, host: options.host, port: options.port });
|
|
5658
|
+
console.log(`graph serve: ${running.url}`);
|
|
5659
|
+
return;
|
|
5660
|
+
}
|
|
5453
5661
|
if (isImpact && [options.json, options.compact, options.yml].filter(Boolean).length > 1) {
|
|
5454
5662
|
const message = "Choose only one graph impact output mode: --compact, --json, or --yml/--yaml.";
|
|
5455
5663
|
if (options.json) console.log(JSON.stringify({ ok: false, command: "graph impact", compact: options.compact || void 0, yml: options.yml || void 0, root: options.root, error: { code: "OUTPUT_MODE_CONFLICT", message }, usage: commandUsage("graph impact") }, null, 2));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dotdotgod/claude-code",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.11",
|
|
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.11"
|
|
29
29
|
},
|
|
30
30
|
"files": [
|
|
31
31
|
".claude-plugin",
|