depwire-cli 0.6.1 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +73 -40
- package/dist/{chunk-S3RUBXRF.js → chunk-65H7HCM4.js} +428 -15
- package/dist/index.js +318 -26
- package/dist/mcpb-entry.js +1 -1
- package/dist/viz/public/temporal.css +397 -0
- package/dist/viz/public/temporal.html +118 -0
- package/dist/viz/public/temporal.js +475 -0
- package/package.json +1 -1
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
// src/parser/index.ts
|
|
2
|
-
import { readFileSync as readFileSync3, statSync as statSync2 } from "fs";
|
|
3
|
-
import { join as join6 } from "path";
|
|
4
|
-
|
|
5
1
|
// src/utils/files.ts
|
|
6
2
|
import { readdirSync, statSync, existsSync, lstatSync } from "fs";
|
|
7
3
|
import { join, relative } from "path";
|
|
@@ -50,6 +46,42 @@ function fileExists(filePath) {
|
|
|
50
46
|
return false;
|
|
51
47
|
}
|
|
52
48
|
}
|
|
49
|
+
function findProjectRoot(startDir = process.cwd()) {
|
|
50
|
+
const projectMarkers = [
|
|
51
|
+
"package.json",
|
|
52
|
+
// Node.js
|
|
53
|
+
"tsconfig.json",
|
|
54
|
+
// TypeScript
|
|
55
|
+
"go.mod",
|
|
56
|
+
// Go
|
|
57
|
+
"pyproject.toml",
|
|
58
|
+
// Python (modern)
|
|
59
|
+
"setup.py",
|
|
60
|
+
// Python (legacy)
|
|
61
|
+
".git"
|
|
62
|
+
// Any git repo
|
|
63
|
+
];
|
|
64
|
+
let currentDir = startDir;
|
|
65
|
+
const rootDir = "/";
|
|
66
|
+
while (currentDir !== rootDir) {
|
|
67
|
+
for (const marker of projectMarkers) {
|
|
68
|
+
const markerPath = join(currentDir, marker);
|
|
69
|
+
if (existsSync(markerPath)) {
|
|
70
|
+
return currentDir;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
const parentDir = join(currentDir, "..");
|
|
74
|
+
if (parentDir === currentDir) {
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
currentDir = parentDir;
|
|
78
|
+
}
|
|
79
|
+
return startDir;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// src/parser/index.ts
|
|
83
|
+
import { readFileSync as readFileSync3, statSync as statSync2 } from "fs";
|
|
84
|
+
import { join as join6 } from "path";
|
|
53
85
|
|
|
54
86
|
// src/parser/detect.ts
|
|
55
87
|
import { extname as extname3 } from "path";
|
|
@@ -7240,8 +7272,8 @@ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
|
7240
7272
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
7241
7273
|
|
|
7242
7274
|
// src/mcp/tools.ts
|
|
7243
|
-
import { dirname as dirname14, join as
|
|
7244
|
-
import { existsSync as
|
|
7275
|
+
import { dirname as dirname14, join as join15 } from "path";
|
|
7276
|
+
import { existsSync as existsSync12, readFileSync as readFileSync8 } from "fs";
|
|
7245
7277
|
|
|
7246
7278
|
// src/mcp/connect.ts
|
|
7247
7279
|
import simpleGit from "simple-git";
|
|
@@ -7435,6 +7467,268 @@ async function connectToRepo(source, subdirectory, state) {
|
|
|
7435
7467
|
}
|
|
7436
7468
|
}
|
|
7437
7469
|
|
|
7470
|
+
// src/temporal/git.ts
|
|
7471
|
+
import { execSync as execSync2 } from "child_process";
|
|
7472
|
+
async function getCommitLog(dir, limit) {
|
|
7473
|
+
try {
|
|
7474
|
+
const limitArg = limit ? `-n ${limit}` : "";
|
|
7475
|
+
const output = execSync2(
|
|
7476
|
+
`git log ${limitArg} --pretty=format:"%H|%aI|%s|%an"`,
|
|
7477
|
+
{ cwd: dir, encoding: "utf-8" }
|
|
7478
|
+
);
|
|
7479
|
+
if (!output.trim()) {
|
|
7480
|
+
return [];
|
|
7481
|
+
}
|
|
7482
|
+
return output.trim().split("\n").map((line) => {
|
|
7483
|
+
const [hash, date, message, author] = line.split("|");
|
|
7484
|
+
return { hash, date, message, author };
|
|
7485
|
+
});
|
|
7486
|
+
} catch (error) {
|
|
7487
|
+
throw new Error(`Failed to get git log: ${error}`);
|
|
7488
|
+
}
|
|
7489
|
+
}
|
|
7490
|
+
async function getCurrentBranch(dir) {
|
|
7491
|
+
try {
|
|
7492
|
+
return execSync2("git rev-parse --abbrev-ref HEAD", {
|
|
7493
|
+
cwd: dir,
|
|
7494
|
+
encoding: "utf-8"
|
|
7495
|
+
}).trim();
|
|
7496
|
+
} catch (error) {
|
|
7497
|
+
throw new Error(`Failed to get current branch: ${error}`);
|
|
7498
|
+
}
|
|
7499
|
+
}
|
|
7500
|
+
async function checkoutCommit(dir, hash) {
|
|
7501
|
+
try {
|
|
7502
|
+
execSync2(`git checkout -q ${hash}`, { cwd: dir, stdio: "ignore" });
|
|
7503
|
+
} catch (error) {
|
|
7504
|
+
throw new Error(`Failed to checkout commit ${hash}: ${error}`);
|
|
7505
|
+
}
|
|
7506
|
+
}
|
|
7507
|
+
async function restoreOriginal(dir, originalBranch) {
|
|
7508
|
+
try {
|
|
7509
|
+
execSync2(`git checkout -q ${originalBranch}`, {
|
|
7510
|
+
cwd: dir,
|
|
7511
|
+
stdio: "ignore"
|
|
7512
|
+
});
|
|
7513
|
+
} catch (error) {
|
|
7514
|
+
throw new Error(`Failed to restore branch ${originalBranch}: ${error}`);
|
|
7515
|
+
}
|
|
7516
|
+
}
|
|
7517
|
+
async function stashChanges(dir) {
|
|
7518
|
+
try {
|
|
7519
|
+
const status = execSync2("git status --porcelain", {
|
|
7520
|
+
cwd: dir,
|
|
7521
|
+
encoding: "utf-8"
|
|
7522
|
+
}).trim();
|
|
7523
|
+
if (status) {
|
|
7524
|
+
execSync2('git stash push -q -m "depwire temporal analysis"', {
|
|
7525
|
+
cwd: dir,
|
|
7526
|
+
stdio: "ignore"
|
|
7527
|
+
});
|
|
7528
|
+
return true;
|
|
7529
|
+
}
|
|
7530
|
+
return false;
|
|
7531
|
+
} catch (error) {
|
|
7532
|
+
throw new Error(`Failed to stash changes: ${error}`);
|
|
7533
|
+
}
|
|
7534
|
+
}
|
|
7535
|
+
async function popStash(dir) {
|
|
7536
|
+
try {
|
|
7537
|
+
execSync2("git stash pop -q", { cwd: dir, stdio: "ignore" });
|
|
7538
|
+
} catch (error) {
|
|
7539
|
+
console.warn("Warning: Failed to restore stashed changes:", error);
|
|
7540
|
+
}
|
|
7541
|
+
}
|
|
7542
|
+
function isGitRepo(dir) {
|
|
7543
|
+
try {
|
|
7544
|
+
execSync2("git rev-parse --git-dir", { cwd: dir, stdio: "ignore" });
|
|
7545
|
+
return true;
|
|
7546
|
+
} catch {
|
|
7547
|
+
return false;
|
|
7548
|
+
}
|
|
7549
|
+
}
|
|
7550
|
+
|
|
7551
|
+
// src/temporal/sampler.ts
|
|
7552
|
+
function sampleCommits(commits, targetCount, strategy) {
|
|
7553
|
+
if (commits.length === 0) {
|
|
7554
|
+
return [];
|
|
7555
|
+
}
|
|
7556
|
+
if (commits.length <= targetCount) {
|
|
7557
|
+
return commits;
|
|
7558
|
+
}
|
|
7559
|
+
switch (strategy) {
|
|
7560
|
+
case "even":
|
|
7561
|
+
return sampleEvenly(commits, targetCount);
|
|
7562
|
+
case "weekly":
|
|
7563
|
+
return sampleWeekly(commits, targetCount);
|
|
7564
|
+
case "monthly":
|
|
7565
|
+
return sampleMonthly(commits, targetCount);
|
|
7566
|
+
default:
|
|
7567
|
+
return sampleEvenly(commits, targetCount);
|
|
7568
|
+
}
|
|
7569
|
+
}
|
|
7570
|
+
function sampleEvenly(commits, targetCount) {
|
|
7571
|
+
if (targetCount >= commits.length) {
|
|
7572
|
+
return commits;
|
|
7573
|
+
}
|
|
7574
|
+
const result = [];
|
|
7575
|
+
const step = (commits.length - 1) / (targetCount - 1);
|
|
7576
|
+
for (let i = 0; i < targetCount; i++) {
|
|
7577
|
+
const index = Math.round(i * step);
|
|
7578
|
+
result.push(commits[index]);
|
|
7579
|
+
}
|
|
7580
|
+
return result;
|
|
7581
|
+
}
|
|
7582
|
+
function sampleWeekly(commits, targetCount) {
|
|
7583
|
+
const result = [];
|
|
7584
|
+
const first = commits[0];
|
|
7585
|
+
const last = commits[commits.length - 1];
|
|
7586
|
+
result.push(first);
|
|
7587
|
+
const weekMap = /* @__PURE__ */ new Map();
|
|
7588
|
+
for (const commit of commits) {
|
|
7589
|
+
const date = new Date(commit.date);
|
|
7590
|
+
const year = date.getFullYear();
|
|
7591
|
+
const week = getWeekNumber(date);
|
|
7592
|
+
const key = `${year}-W${week}`;
|
|
7593
|
+
weekMap.set(key, commit);
|
|
7594
|
+
}
|
|
7595
|
+
const weeklyCommits = Array.from(weekMap.values());
|
|
7596
|
+
if (weeklyCommits.length <= targetCount) {
|
|
7597
|
+
return weeklyCommits;
|
|
7598
|
+
}
|
|
7599
|
+
const step = Math.floor((weeklyCommits.length - 2) / (targetCount - 2));
|
|
7600
|
+
for (let i = 1; i < targetCount - 1; i++) {
|
|
7601
|
+
const index = Math.min(i * step, weeklyCommits.length - 2);
|
|
7602
|
+
if (weeklyCommits[index] !== first && weeklyCommits[index] !== last) {
|
|
7603
|
+
result.push(weeklyCommits[index]);
|
|
7604
|
+
}
|
|
7605
|
+
}
|
|
7606
|
+
if (result[result.length - 1] !== last) {
|
|
7607
|
+
result.push(last);
|
|
7608
|
+
}
|
|
7609
|
+
return result;
|
|
7610
|
+
}
|
|
7611
|
+
function sampleMonthly(commits, targetCount) {
|
|
7612
|
+
const result = [];
|
|
7613
|
+
const first = commits[0];
|
|
7614
|
+
const last = commits[commits.length - 1];
|
|
7615
|
+
result.push(first);
|
|
7616
|
+
const monthMap = /* @__PURE__ */ new Map();
|
|
7617
|
+
for (const commit of commits) {
|
|
7618
|
+
const date = new Date(commit.date);
|
|
7619
|
+
const key = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}`;
|
|
7620
|
+
monthMap.set(key, commit);
|
|
7621
|
+
}
|
|
7622
|
+
const monthlyCommits = Array.from(monthMap.values());
|
|
7623
|
+
if (monthlyCommits.length <= targetCount) {
|
|
7624
|
+
return monthlyCommits;
|
|
7625
|
+
}
|
|
7626
|
+
const step = Math.floor((monthlyCommits.length - 2) / (targetCount - 2));
|
|
7627
|
+
for (let i = 1; i < targetCount - 1; i++) {
|
|
7628
|
+
const index = Math.min(i * step, monthlyCommits.length - 2);
|
|
7629
|
+
if (monthlyCommits[index] !== first && monthlyCommits[index] !== last) {
|
|
7630
|
+
result.push(monthlyCommits[index]);
|
|
7631
|
+
}
|
|
7632
|
+
}
|
|
7633
|
+
if (result[result.length - 1] !== last) {
|
|
7634
|
+
result.push(last);
|
|
7635
|
+
}
|
|
7636
|
+
return result;
|
|
7637
|
+
}
|
|
7638
|
+
function getWeekNumber(date) {
|
|
7639
|
+
const d = new Date(
|
|
7640
|
+
Date.UTC(date.getFullYear(), date.getMonth(), date.getDate())
|
|
7641
|
+
);
|
|
7642
|
+
const dayNum = d.getUTCDay() || 7;
|
|
7643
|
+
d.setUTCDate(d.getUTCDate() + 4 - dayNum);
|
|
7644
|
+
const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1));
|
|
7645
|
+
return Math.ceil(((d.getTime() - yearStart.getTime()) / 864e5 + 1) / 7);
|
|
7646
|
+
}
|
|
7647
|
+
|
|
7648
|
+
// src/temporal/snapshots.ts
|
|
7649
|
+
import { writeFileSync as writeFileSync4, readFileSync as readFileSync7, mkdirSync as mkdirSync3, existsSync as existsSync11, readdirSync as readdirSync3 } from "fs";
|
|
7650
|
+
import { join as join14 } from "path";
|
|
7651
|
+
function saveSnapshot(snapshot, outputDir) {
|
|
7652
|
+
if (!existsSync11(outputDir)) {
|
|
7653
|
+
mkdirSync3(outputDir, { recursive: true });
|
|
7654
|
+
}
|
|
7655
|
+
const filename = `${snapshot.commitHash.substring(0, 8)}.json`;
|
|
7656
|
+
const filepath = join14(outputDir, filename);
|
|
7657
|
+
writeFileSync4(filepath, JSON.stringify(snapshot, null, 2), "utf-8");
|
|
7658
|
+
}
|
|
7659
|
+
function loadSnapshot(commitHash, outputDir) {
|
|
7660
|
+
const shortHash = commitHash.substring(0, 8);
|
|
7661
|
+
const filepath = join14(outputDir, `${shortHash}.json`);
|
|
7662
|
+
if (!existsSync11(filepath)) {
|
|
7663
|
+
return null;
|
|
7664
|
+
}
|
|
7665
|
+
try {
|
|
7666
|
+
const content = readFileSync7(filepath, "utf-8");
|
|
7667
|
+
return JSON.parse(content);
|
|
7668
|
+
} catch {
|
|
7669
|
+
return null;
|
|
7670
|
+
}
|
|
7671
|
+
}
|
|
7672
|
+
function createSnapshot(graph, commitHash, commitDate, commitMessage, commitAuthor) {
|
|
7673
|
+
const fileMap = /* @__PURE__ */ new Map();
|
|
7674
|
+
for (const node of graph.nodes) {
|
|
7675
|
+
if (!fileMap.has(node.filePath)) {
|
|
7676
|
+
fileMap.set(node.filePath, { symbols: 0, inbound: 0, outbound: 0 });
|
|
7677
|
+
}
|
|
7678
|
+
fileMap.get(node.filePath).symbols++;
|
|
7679
|
+
}
|
|
7680
|
+
for (const edge of graph.edges) {
|
|
7681
|
+
const sourceNode = graph.nodes.find((n) => n.id === edge.source);
|
|
7682
|
+
const targetNode = graph.nodes.find((n) => n.id === edge.target);
|
|
7683
|
+
if (sourceNode && targetNode && sourceNode.filePath !== targetNode.filePath) {
|
|
7684
|
+
if (fileMap.has(sourceNode.filePath)) {
|
|
7685
|
+
fileMap.get(sourceNode.filePath).outbound++;
|
|
7686
|
+
}
|
|
7687
|
+
if (fileMap.has(targetNode.filePath)) {
|
|
7688
|
+
fileMap.get(targetNode.filePath).inbound++;
|
|
7689
|
+
}
|
|
7690
|
+
}
|
|
7691
|
+
}
|
|
7692
|
+
const files = Array.from(fileMap.entries()).map(([path2, data]) => ({
|
|
7693
|
+
path: path2,
|
|
7694
|
+
symbols: data.symbols,
|
|
7695
|
+
connections: data.inbound + data.outbound
|
|
7696
|
+
}));
|
|
7697
|
+
const edgeMap = /* @__PURE__ */ new Map();
|
|
7698
|
+
for (const edge of graph.edges) {
|
|
7699
|
+
const sourceNode = graph.nodes.find((n) => n.id === edge.source);
|
|
7700
|
+
const targetNode = graph.nodes.find((n) => n.id === edge.target);
|
|
7701
|
+
if (sourceNode && targetNode && sourceNode.filePath !== targetNode.filePath) {
|
|
7702
|
+
const key = sourceNode.filePath < targetNode.filePath ? `${sourceNode.filePath}|${targetNode.filePath}` : `${targetNode.filePath}|${sourceNode.filePath}`;
|
|
7703
|
+
edgeMap.set(key, (edgeMap.get(key) || 0) + 1);
|
|
7704
|
+
}
|
|
7705
|
+
}
|
|
7706
|
+
const edges = Array.from(edgeMap.entries()).map(([key, weight]) => {
|
|
7707
|
+
const [source, target] = key.split("|");
|
|
7708
|
+
return { source, target, weight };
|
|
7709
|
+
});
|
|
7710
|
+
const languages2 = {};
|
|
7711
|
+
for (const file of graph.files) {
|
|
7712
|
+
const ext = file.split(".").pop() || "unknown";
|
|
7713
|
+
const lang = ext === "ts" || ext === "tsx" ? "typescript" : ext === "js" || ext === "jsx" || ext === "mjs" || ext === "cjs" ? "javascript" : ext === "py" ? "python" : ext === "go" ? "go" : "other";
|
|
7714
|
+
languages2[lang] = (languages2[lang] || 0) + 1;
|
|
7715
|
+
}
|
|
7716
|
+
return {
|
|
7717
|
+
commitHash,
|
|
7718
|
+
commitDate,
|
|
7719
|
+
commitMessage,
|
|
7720
|
+
commitAuthor,
|
|
7721
|
+
stats: {
|
|
7722
|
+
totalFiles: graph.files.length,
|
|
7723
|
+
totalSymbols: graph.nodes.length,
|
|
7724
|
+
totalEdges: edges.length,
|
|
7725
|
+
languages: languages2
|
|
7726
|
+
},
|
|
7727
|
+
files,
|
|
7728
|
+
edges
|
|
7729
|
+
};
|
|
7730
|
+
}
|
|
7731
|
+
|
|
7438
7732
|
// src/mcp/tools.ts
|
|
7439
7733
|
function getToolsList() {
|
|
7440
7734
|
return [
|
|
@@ -7615,6 +7909,24 @@ function getToolsList() {
|
|
|
7615
7909
|
type: "object",
|
|
7616
7910
|
properties: {}
|
|
7617
7911
|
}
|
|
7912
|
+
},
|
|
7913
|
+
{
|
|
7914
|
+
name: "get_temporal_graph",
|
|
7915
|
+
description: "Show how the dependency graph evolved over git history. Returns snapshots at sampled commits showing file counts, symbol counts, edge counts, and structural changes over time.",
|
|
7916
|
+
inputSchema: {
|
|
7917
|
+
type: "object",
|
|
7918
|
+
properties: {
|
|
7919
|
+
commits: {
|
|
7920
|
+
type: "number",
|
|
7921
|
+
description: "Number of commits to sample (default: 10)"
|
|
7922
|
+
},
|
|
7923
|
+
strategy: {
|
|
7924
|
+
type: "string",
|
|
7925
|
+
enum: ["even", "weekly", "monthly"],
|
|
7926
|
+
description: "Sampling strategy (default: even)"
|
|
7927
|
+
}
|
|
7928
|
+
}
|
|
7929
|
+
}
|
|
7618
7930
|
}
|
|
7619
7931
|
];
|
|
7620
7932
|
}
|
|
@@ -7668,6 +7980,15 @@ async function handleToolCall(name, args, state) {
|
|
|
7668
7980
|
} else {
|
|
7669
7981
|
result = handleGetHealthScore(state);
|
|
7670
7982
|
}
|
|
7983
|
+
} else if (name === "get_temporal_graph") {
|
|
7984
|
+
if (!isProjectLoaded(state)) {
|
|
7985
|
+
result = {
|
|
7986
|
+
error: "No project loaded",
|
|
7987
|
+
message: "Use connect_repo to connect to a codebase first"
|
|
7988
|
+
};
|
|
7989
|
+
} else {
|
|
7990
|
+
result = await handleGetTemporalGraph(state, args.commits || 10, args.strategy || "even");
|
|
7991
|
+
}
|
|
7671
7992
|
} else {
|
|
7672
7993
|
if (!isProjectLoaded(state)) {
|
|
7673
7994
|
result = {
|
|
@@ -8092,8 +8413,8 @@ The server will keep running until you end the MCP session or press Ctrl+C.`;
|
|
|
8092
8413
|
};
|
|
8093
8414
|
}
|
|
8094
8415
|
async function handleGetProjectDocs(docType, state) {
|
|
8095
|
-
const docsDir =
|
|
8096
|
-
if (!
|
|
8416
|
+
const docsDir = join15(state.projectRoot, ".depwire");
|
|
8417
|
+
if (!existsSync12(docsDir)) {
|
|
8097
8418
|
const errorMessage = `Project documentation has not been generated yet.
|
|
8098
8419
|
|
|
8099
8420
|
Run \`depwire docs ${state.projectRoot}\` to generate codebase documentation.
|
|
@@ -8123,12 +8444,12 @@ Available document types:
|
|
|
8123
8444
|
missing.push(doc);
|
|
8124
8445
|
continue;
|
|
8125
8446
|
}
|
|
8126
|
-
const filePath =
|
|
8127
|
-
if (!
|
|
8447
|
+
const filePath = join15(docsDir, metadata.documents[doc].file);
|
|
8448
|
+
if (!existsSync12(filePath)) {
|
|
8128
8449
|
missing.push(doc);
|
|
8129
8450
|
continue;
|
|
8130
8451
|
}
|
|
8131
|
-
const content =
|
|
8452
|
+
const content = readFileSync8(filePath, "utf-8");
|
|
8132
8453
|
if (docsToReturn.length > 1) {
|
|
8133
8454
|
output += `
|
|
8134
8455
|
|
|
@@ -8153,16 +8474,16 @@ Available document types:
|
|
|
8153
8474
|
}
|
|
8154
8475
|
async function handleUpdateProjectDocs(docType, state) {
|
|
8155
8476
|
const startTime = Date.now();
|
|
8156
|
-
const docsDir =
|
|
8477
|
+
const docsDir = join15(state.projectRoot, ".depwire");
|
|
8157
8478
|
console.error("Regenerating project documentation...");
|
|
8158
8479
|
const parsedFiles = await parseProject(state.projectRoot);
|
|
8159
8480
|
const graph = buildGraph(parsedFiles);
|
|
8160
8481
|
const parseTime = (Date.now() - startTime) / 1e3;
|
|
8161
8482
|
state.graph = graph;
|
|
8162
|
-
const packageJsonPath =
|
|
8163
|
-
const packageJson = JSON.parse(
|
|
8483
|
+
const packageJsonPath = join15(__dirname, "../../package.json");
|
|
8484
|
+
const packageJson = JSON.parse(readFileSync8(packageJsonPath, "utf-8"));
|
|
8164
8485
|
const docsToGenerate = docType === "all" ? ["architecture", "conventions", "dependencies", "onboarding"] : [docType];
|
|
8165
|
-
const docsExist =
|
|
8486
|
+
const docsExist = existsSync12(docsDir);
|
|
8166
8487
|
const result = await generateDocs(graph, state.projectRoot, packageJson.version, parseTime, {
|
|
8167
8488
|
outputDir: docsDir,
|
|
8168
8489
|
format: "markdown",
|
|
@@ -8203,6 +8524,86 @@ function handleGetHealthScore(state) {
|
|
|
8203
8524
|
const report = calculateHealthScore(graph, projectRoot);
|
|
8204
8525
|
return report;
|
|
8205
8526
|
}
|
|
8527
|
+
async function handleGetTemporalGraph(state, commits, strategy) {
|
|
8528
|
+
const projectRoot = state.projectRoot;
|
|
8529
|
+
if (!isGitRepo(projectRoot)) {
|
|
8530
|
+
return {
|
|
8531
|
+
error: "Not a git repository",
|
|
8532
|
+
message: "Temporal analysis requires git history"
|
|
8533
|
+
};
|
|
8534
|
+
}
|
|
8535
|
+
try {
|
|
8536
|
+
const allCommits = await getCommitLog(projectRoot);
|
|
8537
|
+
if (allCommits.length === 0) {
|
|
8538
|
+
return {
|
|
8539
|
+
error: "No commits found",
|
|
8540
|
+
message: "Repository has no commit history"
|
|
8541
|
+
};
|
|
8542
|
+
}
|
|
8543
|
+
const sampledCommits = sampleCommits(allCommits, commits, strategy);
|
|
8544
|
+
const snapshots = [];
|
|
8545
|
+
const outputDir = join15(projectRoot, ".depwire", "temporal");
|
|
8546
|
+
for (const commit of sampledCommits) {
|
|
8547
|
+
const existing = loadSnapshot(commit.hash, outputDir);
|
|
8548
|
+
if (existing) {
|
|
8549
|
+
snapshots.push(existing);
|
|
8550
|
+
}
|
|
8551
|
+
}
|
|
8552
|
+
if (snapshots.length === 0) {
|
|
8553
|
+
return {
|
|
8554
|
+
status: "no_snapshots",
|
|
8555
|
+
message: "No temporal snapshots found. Run `depwire temporal` to generate them.",
|
|
8556
|
+
commits_found: allCommits.length,
|
|
8557
|
+
commits_to_sample: sampledCommits.length
|
|
8558
|
+
};
|
|
8559
|
+
}
|
|
8560
|
+
const first = snapshots[0];
|
|
8561
|
+
const last = snapshots[snapshots.length - 1];
|
|
8562
|
+
const growth = {
|
|
8563
|
+
files: last.stats.totalFiles - first.stats.totalFiles,
|
|
8564
|
+
symbols: last.stats.totalSymbols - first.stats.totalSymbols,
|
|
8565
|
+
edges: last.stats.totalEdges - first.stats.totalEdges
|
|
8566
|
+
};
|
|
8567
|
+
const trend = growth.files > 0 ? "Growing" : growth.files < 0 ? "Shrinking" : "Stable";
|
|
8568
|
+
let biggestGrowth = { index: 0, files: 0, date: "", message: "" };
|
|
8569
|
+
for (let i = 1; i < snapshots.length; i++) {
|
|
8570
|
+
const delta = snapshots[i].stats.totalFiles - snapshots[i - 1].stats.totalFiles;
|
|
8571
|
+
if (delta > biggestGrowth.files) {
|
|
8572
|
+
biggestGrowth = {
|
|
8573
|
+
index: i,
|
|
8574
|
+
files: delta,
|
|
8575
|
+
date: snapshots[i].commitDate,
|
|
8576
|
+
message: snapshots[i].commitMessage
|
|
8577
|
+
};
|
|
8578
|
+
}
|
|
8579
|
+
}
|
|
8580
|
+
return {
|
|
8581
|
+
status: "success",
|
|
8582
|
+
time_range: {
|
|
8583
|
+
from: first.commitDate,
|
|
8584
|
+
to: last.commitDate
|
|
8585
|
+
},
|
|
8586
|
+
snapshots: snapshots.map((s) => ({
|
|
8587
|
+
commit: s.commitHash.substring(0, 8),
|
|
8588
|
+
date: s.commitDate,
|
|
8589
|
+
message: s.commitMessage,
|
|
8590
|
+
author: s.commitAuthor,
|
|
8591
|
+
files: s.stats.totalFiles,
|
|
8592
|
+
symbols: s.stats.totalSymbols,
|
|
8593
|
+
edges: s.stats.totalEdges
|
|
8594
|
+
})),
|
|
8595
|
+
growth,
|
|
8596
|
+
trend,
|
|
8597
|
+
biggest_growth_period: biggestGrowth.files > 0 ? biggestGrowth : null,
|
|
8598
|
+
summary: `Analyzed ${snapshots.length} snapshots from ${new Date(first.commitDate).toLocaleDateString()} to ${new Date(last.commitDate).toLocaleDateString()}. Overall trend: ${trend}.`
|
|
8599
|
+
};
|
|
8600
|
+
} catch (error) {
|
|
8601
|
+
return {
|
|
8602
|
+
error: "Failed to analyze temporal graph",
|
|
8603
|
+
message: String(error)
|
|
8604
|
+
};
|
|
8605
|
+
}
|
|
8606
|
+
}
|
|
8206
8607
|
|
|
8207
8608
|
// src/mcp/server.ts
|
|
8208
8609
|
async function startMcpServer(state) {
|
|
@@ -8238,6 +8639,7 @@ async function startMcpServer(state) {
|
|
|
8238
8639
|
}
|
|
8239
8640
|
|
|
8240
8641
|
export {
|
|
8642
|
+
findProjectRoot,
|
|
8241
8643
|
parseProject,
|
|
8242
8644
|
buildGraph,
|
|
8243
8645
|
getImpact,
|
|
@@ -8251,5 +8653,16 @@ export {
|
|
|
8251
8653
|
calculateHealthScore,
|
|
8252
8654
|
getHealthTrend,
|
|
8253
8655
|
generateDocs,
|
|
8656
|
+
getCommitLog,
|
|
8657
|
+
getCurrentBranch,
|
|
8658
|
+
checkoutCommit,
|
|
8659
|
+
restoreOriginal,
|
|
8660
|
+
stashChanges,
|
|
8661
|
+
popStash,
|
|
8662
|
+
isGitRepo,
|
|
8663
|
+
sampleCommits,
|
|
8664
|
+
saveSnapshot,
|
|
8665
|
+
loadSnapshot,
|
|
8666
|
+
createSnapshot,
|
|
8254
8667
|
startMcpServer
|
|
8255
8668
|
};
|