codecortex-ai 0.4.3 → 0.4.4
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 +7 -4
- package/dist/{chunk-CR6LARZ4.js → chunk-4N3T43UG.js} +276 -19
- package/dist/chunk-4N3T43UG.js.map +1 -0
- package/dist/cli/index.js +35 -120
- package/dist/cli/index.js.map +1 -1
- package/dist/mcp/server.d.ts +1 -1
- package/dist/mcp/server.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-CR6LARZ4.js.map +0 -1
package/README.md
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
Persistent codebase knowledge layer for AI agents. Your AI shouldn't re-learn your codebase every session.
|
|
4
4
|
|
|
5
|
-
> **⚠️ If you're on v0.3
|
|
6
|
-
> v0.4.
|
|
5
|
+
> **⚠️ If you're on v0.4.3 or earlier, update now:** `npm install -g codecortex-ai@latest`
|
|
6
|
+
> v0.4.4 adds freshness flags on all MCP responses and `get_edit_briefing` — a pre-edit risk briefing tool.
|
|
7
7
|
|
|
8
8
|
[Website](https://codecortex-ai.vercel.app) · [npm](https://www.npmjs.com/package/codecortex-ai) · [GitHub](https://github.com/rushikeshmore/CodeCortex)
|
|
9
9
|
|
|
@@ -98,9 +98,9 @@ Example from a real codebase:
|
|
|
98
98
|
- `routes.ts` and `worker.ts` co-changed in 9/12 commits (75%) with **zero imports between them**
|
|
99
99
|
- Without this knowledge, an AI editing one file would produce a bug 75% of the time
|
|
100
100
|
|
|
101
|
-
## MCP Tools (
|
|
101
|
+
## MCP Tools (15)
|
|
102
102
|
|
|
103
|
-
### Read Tools (
|
|
103
|
+
### Read Tools (10)
|
|
104
104
|
|
|
105
105
|
| Tool | Description |
|
|
106
106
|
|------|-------------|
|
|
@@ -113,6 +113,9 @@ Example from a real codebase:
|
|
|
113
113
|
| `lookup_symbol` | Symbol by name/file/kind |
|
|
114
114
|
| `get_change_coupling` | What files must I also edit if I touch X? |
|
|
115
115
|
| `get_hotspots` | Files ranked by risk (churn x coupling) |
|
|
116
|
+
| `get_edit_briefing` | **NEW** — Pre-edit risk briefing: co-change warnings, hidden deps, bug history, importers |
|
|
117
|
+
|
|
118
|
+
All read tools include `_freshness` metadata indicating how up-to-date the knowledge is.
|
|
116
119
|
|
|
117
120
|
### Write Tools (5)
|
|
118
121
|
|
|
@@ -197,6 +197,9 @@ function getModuleDependencies(graph, moduleName) {
|
|
|
197
197
|
calls: graph.calls.filter((e) => moduleFiles.has(e.file))
|
|
198
198
|
};
|
|
199
199
|
}
|
|
200
|
+
function getFileImporters(graph, file) {
|
|
201
|
+
return graph.imports.filter((e) => e.target.includes(file)).map((e) => e.source);
|
|
202
|
+
}
|
|
200
203
|
function getMostImportedFiles(graph, limit = 10) {
|
|
201
204
|
const counts = /* @__PURE__ */ new Map();
|
|
202
205
|
for (const edge of graph.imports) {
|
|
@@ -212,6 +215,9 @@ function enrichCouplingWithImports(graph, coupling) {
|
|
|
212
215
|
for (const pair of coupling) {
|
|
213
216
|
const key = [pair.fileA, pair.fileB].sort().join("|");
|
|
214
217
|
pair.hasImport = importPairs.has(key);
|
|
218
|
+
if (pair.strength >= 0.7 && !pair.hasImport) {
|
|
219
|
+
pair.warning = `HIDDEN DEPENDENCY \u2014 ${Math.round(pair.strength * 100)}% co-change rate`;
|
|
220
|
+
}
|
|
215
221
|
}
|
|
216
222
|
}
|
|
217
223
|
|
|
@@ -456,11 +462,159 @@ async function getAllCortexFiles(dir) {
|
|
|
456
462
|
return files;
|
|
457
463
|
}
|
|
458
464
|
|
|
465
|
+
// src/git/diff.ts
|
|
466
|
+
import simpleGit from "simple-git";
|
|
467
|
+
async function getUncommittedDiff(root) {
|
|
468
|
+
const git = simpleGit(root);
|
|
469
|
+
const diff = await git.diffSummary();
|
|
470
|
+
return {
|
|
471
|
+
filesChanged: diff.files.map((f) => f.file),
|
|
472
|
+
insertions: diff.insertions,
|
|
473
|
+
deletions: diff.deletions,
|
|
474
|
+
summary: `${diff.files.length} files changed, +${diff.insertions} -${diff.deletions}`
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
async function getChangedFilesSinceDate(root, sinceDate) {
|
|
478
|
+
const git = simpleGit(root);
|
|
479
|
+
const log = await git.log({ "--since": sinceDate, "--name-only": null });
|
|
480
|
+
const files = /* @__PURE__ */ new Set();
|
|
481
|
+
for (const commit of log.all) {
|
|
482
|
+
const diff = commit.diff;
|
|
483
|
+
if (diff) {
|
|
484
|
+
for (const file of diff.files) {
|
|
485
|
+
files.add(file.file);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
return [...files];
|
|
490
|
+
}
|
|
491
|
+
function mapFilesToModules(files) {
|
|
492
|
+
const moduleMap = /* @__PURE__ */ new Map();
|
|
493
|
+
for (const file of files) {
|
|
494
|
+
const parts = file.split("/");
|
|
495
|
+
let module = "root";
|
|
496
|
+
if (parts[0] === "src" && parts.length >= 3 && parts[1]) {
|
|
497
|
+
module = parts[1];
|
|
498
|
+
} else if (parts[0] === "lib" && parts.length >= 3 && parts[1]) {
|
|
499
|
+
module = parts[1];
|
|
500
|
+
}
|
|
501
|
+
const existing = moduleMap.get(module) || [];
|
|
502
|
+
existing.push(file);
|
|
503
|
+
moduleMap.set(module, existing);
|
|
504
|
+
}
|
|
505
|
+
return moduleMap;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
// src/git/history.ts
|
|
509
|
+
import simpleGit2 from "simple-git";
|
|
510
|
+
async function getCommitHistory(root, days = 90) {
|
|
511
|
+
const git = simpleGit2(root);
|
|
512
|
+
const since = new Date(Date.now() - days * 24 * 60 * 60 * 1e3).toISOString().split("T")[0];
|
|
513
|
+
const log = await git.log({
|
|
514
|
+
"--since": since,
|
|
515
|
+
"--stat": null,
|
|
516
|
+
maxCount: 500
|
|
517
|
+
});
|
|
518
|
+
return log.all.map((commit) => ({
|
|
519
|
+
hash: commit.hash,
|
|
520
|
+
date: commit.date,
|
|
521
|
+
message: commit.message,
|
|
522
|
+
author: commit.author_name,
|
|
523
|
+
filesChanged: parseStatFiles(commit.diff)
|
|
524
|
+
}));
|
|
525
|
+
}
|
|
526
|
+
function parseStatFiles(diff) {
|
|
527
|
+
if (!diff || !diff.files) return [];
|
|
528
|
+
return diff.files.map((f) => f.file);
|
|
529
|
+
}
|
|
530
|
+
async function isGitRepo(root) {
|
|
531
|
+
const git = simpleGit2(root);
|
|
532
|
+
try {
|
|
533
|
+
await git.status();
|
|
534
|
+
return true;
|
|
535
|
+
} catch {
|
|
536
|
+
return false;
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
async function getHeadCommit(root) {
|
|
540
|
+
const git = simpleGit2(root);
|
|
541
|
+
try {
|
|
542
|
+
const log = await git.log({ maxCount: 1 });
|
|
543
|
+
return log.latest?.hash || null;
|
|
544
|
+
} catch {
|
|
545
|
+
return null;
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
// src/core/freshness.ts
|
|
550
|
+
async function computeFreshness(projectRoot) {
|
|
551
|
+
const manifest = await readManifest(projectRoot);
|
|
552
|
+
if (!manifest) return null;
|
|
553
|
+
const lastUpdated = manifest.lastUpdated;
|
|
554
|
+
if (!lastUpdated) return null;
|
|
555
|
+
const isRepo = await isGitRepo(projectRoot);
|
|
556
|
+
let changedFiles = [];
|
|
557
|
+
if (isRepo) {
|
|
558
|
+
try {
|
|
559
|
+
changedFiles = await getChangedFilesSinceDate(projectRoot, lastUpdated);
|
|
560
|
+
changedFiles = changedFiles.filter((f) => {
|
|
561
|
+
const base = f.split("/").pop() ?? "";
|
|
562
|
+
return !base.endsWith(".lock") && base !== "package-lock.json" && base !== "yarn.lock" && base !== "pnpm-lock.yaml" && base !== "CHANGELOG.md" && !f.startsWith(".codecortex/");
|
|
563
|
+
});
|
|
564
|
+
} catch {
|
|
565
|
+
changedFiles = [];
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
const now = /* @__PURE__ */ new Date();
|
|
569
|
+
const analyzed = new Date(lastUpdated);
|
|
570
|
+
const daysSinceAnalysis = Math.floor((now.getTime() - analyzed.getTime()) / (1e3 * 60 * 60 * 24));
|
|
571
|
+
const count = changedFiles.length;
|
|
572
|
+
let status;
|
|
573
|
+
let message;
|
|
574
|
+
if (count === 0 && daysSinceAnalysis <= 7) {
|
|
575
|
+
status = "fresh";
|
|
576
|
+
message = "Knowledge is up to date.";
|
|
577
|
+
} else if (count <= 2 && daysSinceAnalysis <= 7) {
|
|
578
|
+
status = "slightly_stale";
|
|
579
|
+
message = `${count} file${count === 1 ? "" : "s"} changed since last analysis. Minor drift.`;
|
|
580
|
+
} else if (count <= 5 || daysSinceAnalysis <= 14) {
|
|
581
|
+
status = "stale";
|
|
582
|
+
message = `${count} file${count === 1 ? "" : "s"} changed since last analysis. Consider running \`codecortex update\`.`;
|
|
583
|
+
} else {
|
|
584
|
+
status = "very_stale";
|
|
585
|
+
message = `${count} files changed over ${daysSinceAnalysis} days since last analysis. Run \`codecortex update\` before trusting this knowledge.`;
|
|
586
|
+
}
|
|
587
|
+
return {
|
|
588
|
+
status,
|
|
589
|
+
lastAnalyzed: lastUpdated,
|
|
590
|
+
daysSinceAnalysis,
|
|
591
|
+
filesChangedSince: count,
|
|
592
|
+
changedFiles: changedFiles.slice(0, 20),
|
|
593
|
+
// Cap to avoid bloating response
|
|
594
|
+
message
|
|
595
|
+
};
|
|
596
|
+
}
|
|
597
|
+
|
|
459
598
|
// src/mcp/tools/read.ts
|
|
460
599
|
function textResult(data) {
|
|
461
600
|
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
462
601
|
}
|
|
602
|
+
function withFreshness(data, freshness) {
|
|
603
|
+
if (!freshness) return data;
|
|
604
|
+
return { ...data, _freshness: freshness };
|
|
605
|
+
}
|
|
463
606
|
function registerReadTools(server, projectRoot) {
|
|
607
|
+
let cachedFreshness = null;
|
|
608
|
+
const FRESHNESS_TTL_MS = 6e4;
|
|
609
|
+
async function getFreshness() {
|
|
610
|
+
const now = Date.now();
|
|
611
|
+
if (cachedFreshness && now - cachedFreshness.timestamp < FRESHNESS_TTL_MS) {
|
|
612
|
+
return cachedFreshness.info;
|
|
613
|
+
}
|
|
614
|
+
const info = await computeFreshness(projectRoot);
|
|
615
|
+
cachedFreshness = { info, timestamp: now };
|
|
616
|
+
return info;
|
|
617
|
+
}
|
|
464
618
|
server.registerTool(
|
|
465
619
|
"get_project_overview",
|
|
466
620
|
{
|
|
@@ -481,12 +635,13 @@ function registerReadTools(server, projectRoot) {
|
|
|
481
635
|
mostImported: getMostImportedFiles(graph, 5)
|
|
482
636
|
};
|
|
483
637
|
}
|
|
484
|
-
|
|
638
|
+
const freshness = await getFreshness();
|
|
639
|
+
return textResult(withFreshness({
|
|
485
640
|
constitution,
|
|
486
641
|
overview,
|
|
487
642
|
manifest,
|
|
488
643
|
graphSummary
|
|
489
|
-
});
|
|
644
|
+
}, freshness));
|
|
490
645
|
}
|
|
491
646
|
);
|
|
492
647
|
server.registerTool(
|
|
@@ -508,7 +663,8 @@ function registerReadTools(server, projectRoot) {
|
|
|
508
663
|
if (graph) {
|
|
509
664
|
deps = getModuleDependencies(graph, name);
|
|
510
665
|
}
|
|
511
|
-
|
|
666
|
+
const freshness = await getFreshness();
|
|
667
|
+
return textResult(withFreshness({ found: true, name, doc, dependencies: deps }, freshness));
|
|
512
668
|
}
|
|
513
669
|
);
|
|
514
670
|
server.registerTool(
|
|
@@ -524,12 +680,13 @@ function registerReadTools(server, projectRoot) {
|
|
|
524
680
|
}
|
|
525
681
|
const session = await readSession(projectRoot, latestId);
|
|
526
682
|
const allSessions = await listSessions(projectRoot);
|
|
527
|
-
|
|
683
|
+
const freshness = await getFreshness();
|
|
684
|
+
return textResult(withFreshness({
|
|
528
685
|
hasSession: true,
|
|
529
686
|
latest: session,
|
|
530
687
|
totalSessions: allSessions.length,
|
|
531
688
|
recentSessionIds: allSessions.slice(0, 5)
|
|
532
|
-
});
|
|
689
|
+
}, freshness));
|
|
533
690
|
}
|
|
534
691
|
);
|
|
535
692
|
server.registerTool(
|
|
@@ -542,11 +699,12 @@ function registerReadTools(server, projectRoot) {
|
|
|
542
699
|
},
|
|
543
700
|
async ({ query }) => {
|
|
544
701
|
const results = await searchKnowledge(projectRoot, query);
|
|
545
|
-
|
|
702
|
+
const freshness = await getFreshness();
|
|
703
|
+
return textResult(withFreshness({
|
|
546
704
|
query,
|
|
547
705
|
totalResults: results.length,
|
|
548
706
|
results: results.slice(0, 20)
|
|
549
|
-
});
|
|
707
|
+
}, freshness));
|
|
550
708
|
}
|
|
551
709
|
);
|
|
552
710
|
server.registerTool(
|
|
@@ -568,11 +726,12 @@ function registerReadTools(server, projectRoot) {
|
|
|
568
726
|
}
|
|
569
727
|
}
|
|
570
728
|
}
|
|
571
|
-
|
|
729
|
+
const freshness = await getFreshness();
|
|
730
|
+
return textResult(withFreshness({
|
|
572
731
|
total: decisions.length,
|
|
573
732
|
topic: topic || "all",
|
|
574
733
|
decisions
|
|
575
|
-
});
|
|
734
|
+
}, freshness));
|
|
576
735
|
}
|
|
577
736
|
);
|
|
578
737
|
server.registerTool(
|
|
@@ -587,16 +746,17 @@ function registerReadTools(server, projectRoot) {
|
|
|
587
746
|
async ({ file, module }) => {
|
|
588
747
|
const graph = await readGraph(projectRoot);
|
|
589
748
|
if (!graph) return textResult({ found: false, message: "No graph data. Run codecortex init first." });
|
|
749
|
+
const freshness = await getFreshness();
|
|
590
750
|
if (module) {
|
|
591
751
|
const deps = getModuleDependencies(graph, module);
|
|
592
|
-
return textResult({ module, ...deps });
|
|
752
|
+
return textResult(withFreshness({ module, ...deps }, freshness));
|
|
593
753
|
}
|
|
594
754
|
if (file) {
|
|
595
755
|
const imports = graph.imports.filter((e) => e.source.includes(file) || e.target.includes(file));
|
|
596
756
|
const calls = graph.calls.filter((e) => e.file.includes(file));
|
|
597
|
-
return textResult({ file, imports, calls });
|
|
757
|
+
return textResult(withFreshness({ file, imports, calls }, freshness));
|
|
598
758
|
}
|
|
599
|
-
return textResult(graph);
|
|
759
|
+
return textResult(withFreshness(graph, freshness));
|
|
600
760
|
}
|
|
601
761
|
);
|
|
602
762
|
server.registerTool(
|
|
@@ -618,11 +778,12 @@ function registerReadTools(server, projectRoot) {
|
|
|
618
778
|
);
|
|
619
779
|
if (kind) matches = matches.filter((s) => s.kind === kind);
|
|
620
780
|
if (file) matches = matches.filter((s) => s.file.includes(file));
|
|
621
|
-
|
|
781
|
+
const freshness = await getFreshness();
|
|
782
|
+
return textResult(withFreshness({
|
|
622
783
|
query: { name, kind, file },
|
|
623
784
|
totalMatches: matches.length,
|
|
624
785
|
symbols: matches.slice(0, 30)
|
|
625
|
-
});
|
|
786
|
+
}, freshness));
|
|
626
787
|
}
|
|
627
788
|
);
|
|
628
789
|
server.registerTool(
|
|
@@ -644,12 +805,13 @@ function registerReadTools(server, projectRoot) {
|
|
|
644
805
|
(c) => c.fileA.includes(file) || c.fileB.includes(file)
|
|
645
806
|
);
|
|
646
807
|
}
|
|
647
|
-
|
|
808
|
+
const freshness = await getFreshness();
|
|
809
|
+
return textResult(withFreshness({
|
|
648
810
|
file: file || "all",
|
|
649
811
|
minStrength,
|
|
650
812
|
couplings: coupling,
|
|
651
813
|
warning: coupling.filter((c) => !c.hasImport).length > 0 ? "HIDDEN DEPENDENCIES FOUND \u2014 some coupled files have NO import between them" : null
|
|
652
|
-
});
|
|
814
|
+
}, freshness));
|
|
653
815
|
}
|
|
654
816
|
);
|
|
655
817
|
server.registerTool(
|
|
@@ -683,14 +845,104 @@ function registerReadTools(server, projectRoot) {
|
|
|
683
845
|
riskMap.set(b.file, entry);
|
|
684
846
|
}
|
|
685
847
|
const ranked = [...riskMap.entries()].sort((a, b) => b[1].risk - a[1].risk).slice(0, limit).map(([file, data]) => ({ file, ...data, risk: Math.round(data.risk * 100) / 100 }));
|
|
686
|
-
|
|
848
|
+
const freshness = await getFreshness();
|
|
849
|
+
return textResult(withFreshness({
|
|
687
850
|
period: `${temporal.periodDays} days`,
|
|
688
851
|
totalCommits: temporal.totalCommits,
|
|
689
852
|
hotspots: ranked
|
|
853
|
+
}, freshness));
|
|
854
|
+
}
|
|
855
|
+
);
|
|
856
|
+
server.registerTool(
|
|
857
|
+
"get_edit_briefing",
|
|
858
|
+
{
|
|
859
|
+
description: "CALL THIS BEFORE EDITING FILES. Takes a list of files you plan to edit and returns everything you need to know: co-change warnings (files you must also update), risk assessment, who imports these files, relevant patterns, and recent change history. Prevents bugs from hidden dependencies.",
|
|
860
|
+
inputSchema: {
|
|
861
|
+
files: z.array(z.string()).min(1).describe("File paths you plan to edit (relative to project root)")
|
|
862
|
+
}
|
|
863
|
+
},
|
|
864
|
+
async ({ files }) => {
|
|
865
|
+
const temporalContent = await readFile(cortexPath(projectRoot, "temporal.json"));
|
|
866
|
+
if (!temporalContent) return textResult({ found: false, message: "No temporal data. Run codecortex init first." });
|
|
867
|
+
const temporal = JSON.parse(temporalContent);
|
|
868
|
+
const graph = await readGraph(projectRoot);
|
|
869
|
+
const patternsContent = await readFile(cortexPath(projectRoot, "patterns.md"));
|
|
870
|
+
const briefings = files.map((file) => {
|
|
871
|
+
const couplings = temporal.coupling.filter((c) => c.fileA.includes(file) || c.fileB.includes(file)).map((c) => {
|
|
872
|
+
const other = c.fileA.includes(file) ? c.fileB : c.fileA;
|
|
873
|
+
return {
|
|
874
|
+
file: other,
|
|
875
|
+
cochanges: c.cochanges,
|
|
876
|
+
strength: c.strength,
|
|
877
|
+
hasImport: c.hasImport,
|
|
878
|
+
warning: c.warning || null
|
|
879
|
+
};
|
|
880
|
+
}).sort((a, b) => b.strength - a.strength);
|
|
881
|
+
const hotspot = temporal.hotspots.find((h) => h.file.includes(file));
|
|
882
|
+
const bugs = temporal.bugHistory.find((b) => b.file.includes(file));
|
|
883
|
+
const couplingCount = couplings.length;
|
|
884
|
+
const hiddenDeps = couplings.filter((c) => !c.hasImport).length;
|
|
885
|
+
let riskLevel = "LOW";
|
|
886
|
+
const riskScore = (hotspot?.changes || 0) + couplingCount * 2 + (bugs?.fixCommits || 0) * 3 + hiddenDeps * 4;
|
|
887
|
+
if (riskScore >= 20) riskLevel = "CRITICAL";
|
|
888
|
+
else if (riskScore >= 12) riskLevel = "HIGH";
|
|
889
|
+
else if (riskScore >= 6) riskLevel = "MEDIUM";
|
|
890
|
+
let importedBy = [];
|
|
891
|
+
if (graph) {
|
|
892
|
+
importedBy = getFileImporters(graph, file);
|
|
893
|
+
}
|
|
894
|
+
const recentChange = hotspot ? {
|
|
895
|
+
lastChanged: hotspot.lastChanged,
|
|
896
|
+
daysSinceChange: hotspot.daysSinceChange,
|
|
897
|
+
totalChanges: hotspot.changes,
|
|
898
|
+
stability: hotspot.stability
|
|
899
|
+
} : null;
|
|
900
|
+
const bugHistory = bugs ? {
|
|
901
|
+
fixCommits: bugs.fixCommits,
|
|
902
|
+
lessons: bugs.lessons
|
|
903
|
+
} : null;
|
|
904
|
+
return {
|
|
905
|
+
file,
|
|
906
|
+
risk: {
|
|
907
|
+
level: riskLevel,
|
|
908
|
+
score: Math.round(riskScore * 100) / 100,
|
|
909
|
+
reason: buildRiskReason(riskLevel, hotspot, couplingCount, hiddenDeps, bugs)
|
|
910
|
+
},
|
|
911
|
+
cochangeWarnings: couplings,
|
|
912
|
+
importedBy,
|
|
913
|
+
recentChange,
|
|
914
|
+
bugHistory
|
|
915
|
+
};
|
|
690
916
|
});
|
|
917
|
+
const inputSet = new Set(files);
|
|
918
|
+
const alsoConsider = /* @__PURE__ */ new Set();
|
|
919
|
+
for (const b of briefings) {
|
|
920
|
+
for (const c of b.cochangeWarnings) {
|
|
921
|
+
const coupledFile = c.file;
|
|
922
|
+
const isInInput = files.some((f) => coupledFile.includes(f) || f.includes(coupledFile));
|
|
923
|
+
if (!isInInput && c.strength >= 0.5) {
|
|
924
|
+
alsoConsider.add(`${coupledFile} (${Math.round(c.strength * 100)}% co-change with ${b.file}${c.hasImport ? "" : ", NO import \u2014 hidden dep"})`);
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
const freshness = await getFreshness();
|
|
929
|
+
return textResult(withFreshness({
|
|
930
|
+
briefings,
|
|
931
|
+
alsoConsiderEditing: [...alsoConsider],
|
|
932
|
+
patterns: patternsContent || null
|
|
933
|
+
}, freshness));
|
|
691
934
|
}
|
|
692
935
|
);
|
|
693
936
|
}
|
|
937
|
+
function buildRiskReason(level, hotspot, couplings, hiddenDeps, bugs) {
|
|
938
|
+
if (level === "LOW") return "Low change frequency, few couplings.";
|
|
939
|
+
const parts = [];
|
|
940
|
+
if (hotspot && hotspot.changes >= 5) parts.push(`${hotspot.changes} changes (${hotspot.stability})`);
|
|
941
|
+
if (couplings > 0) parts.push(`${couplings} coupled file${couplings === 1 ? "" : "s"}`);
|
|
942
|
+
if (hiddenDeps > 0) parts.push(`${hiddenDeps} hidden dep${hiddenDeps === 1 ? "" : "s"}`);
|
|
943
|
+
if (bugs && bugs.fixCommits > 0) parts.push(`${bugs.fixCommits} bug-fix commit${bugs.fixCommits === 1 ? "" : "s"}`);
|
|
944
|
+
return parts.join(", ") + ".";
|
|
945
|
+
}
|
|
694
946
|
|
|
695
947
|
// src/mcp/tools/write.ts
|
|
696
948
|
import { z as z3 } from "zod";
|
|
@@ -916,7 +1168,7 @@ function registerWriteTools(server, projectRoot) {
|
|
|
916
1168
|
function createServer(projectRoot) {
|
|
917
1169
|
const server = new McpServer({
|
|
918
1170
|
name: "codecortex",
|
|
919
|
-
version: "0.4.
|
|
1171
|
+
version: "0.4.4",
|
|
920
1172
|
description: "Persistent codebase knowledge layer. Pre-digested architecture, symbols, coupling, and patterns served to AI agents."
|
|
921
1173
|
});
|
|
922
1174
|
registerReadTools(server, projectRoot);
|
|
@@ -956,12 +1208,17 @@ export {
|
|
|
956
1208
|
writeModuleDoc,
|
|
957
1209
|
listModuleDocs,
|
|
958
1210
|
listDecisions,
|
|
1211
|
+
getCommitHistory,
|
|
1212
|
+
isGitRepo,
|
|
1213
|
+
getHeadCommit,
|
|
959
1214
|
writeSession,
|
|
960
1215
|
listSessions,
|
|
961
1216
|
getLatestSession,
|
|
962
1217
|
createSession,
|
|
963
1218
|
searchKnowledge,
|
|
1219
|
+
getUncommittedDiff,
|
|
1220
|
+
mapFilesToModules,
|
|
964
1221
|
createServer,
|
|
965
1222
|
startServer
|
|
966
1223
|
};
|
|
967
|
-
//# sourceMappingURL=chunk-
|
|
1224
|
+
//# sourceMappingURL=chunk-4N3T43UG.js.map
|