@jussmor/commit-memory-mcp 0.3.5 → 0.3.7

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.
@@ -0,0 +1,51 @@
1
+ export declare function whoChangedFile(options: {
2
+ repoPath: string;
3
+ filePath: string;
4
+ limit: number;
5
+ }): {
6
+ filePath: string;
7
+ commits: Array<{
8
+ sha: string;
9
+ author: string;
10
+ date: string;
11
+ subject: string;
12
+ }>;
13
+ authors: Array<{
14
+ author: string;
15
+ commitCount: number;
16
+ lastCommitAt: string;
17
+ }>;
18
+ };
19
+ export declare function latestCommitForFile(repoPath: string, filePath: string): string | null;
20
+ export declare function commitDetails(repoPath: string, sha: string): {
21
+ sha: string;
22
+ author: string;
23
+ date: string;
24
+ subject: string;
25
+ body: string;
26
+ };
27
+ export declare function mainBranchOvernightBrief(options: {
28
+ repoPath: string;
29
+ baseBranch: string;
30
+ sinceHours: number;
31
+ limit: number;
32
+ }): {
33
+ baseBranch: string;
34
+ sinceHours: number;
35
+ commits: Array<{
36
+ sha: string;
37
+ author: string;
38
+ date: string;
39
+ subject: string;
40
+ }>;
41
+ };
42
+ export declare function resumeFeatureSessionBrief(options: {
43
+ worktreePath: string;
44
+ baseBranch: string;
45
+ }): {
46
+ worktreePath: string;
47
+ branch: string;
48
+ ahead: number;
49
+ behind: number;
50
+ overlapFiles: string[];
51
+ };
@@ -0,0 +1,146 @@
1
+ import { execFileSync } from "node:child_process";
2
+ import path from "node:path";
3
+ function runGit(repoPath, args) {
4
+ return execFileSync("git", ["-C", repoPath, ...args], {
5
+ encoding: "utf8",
6
+ stdio: ["ignore", "pipe", "pipe"],
7
+ });
8
+ }
9
+ export function whoChangedFile(options) {
10
+ const repoPath = path.resolve(options.repoPath);
11
+ const output = runGit(repoPath, [
12
+ "log",
13
+ `-n${options.limit}`,
14
+ "--format=%H%x1f%an%x1f%aI%x1f%s",
15
+ "--",
16
+ options.filePath,
17
+ ]).trim();
18
+ if (!output) {
19
+ return { filePath: options.filePath, commits: [], authors: [] };
20
+ }
21
+ const commits = output.split("\n").map((line) => {
22
+ const [sha = "", author = "", date = "", subject = ""] = line.split("\x1f");
23
+ return { sha, author, date, subject };
24
+ });
25
+ const authorMap = new Map();
26
+ for (const commit of commits) {
27
+ const existing = authorMap.get(commit.author);
28
+ if (!existing) {
29
+ authorMap.set(commit.author, {
30
+ commitCount: 1,
31
+ lastCommitAt: commit.date,
32
+ });
33
+ continue;
34
+ }
35
+ existing.commitCount += 1;
36
+ if (commit.date > existing.lastCommitAt) {
37
+ existing.lastCommitAt = commit.date;
38
+ }
39
+ }
40
+ const authors = Array.from(authorMap.entries())
41
+ .map(([author, value]) => ({ author, ...value }))
42
+ .sort((a, b) => b.commitCount - a.commitCount);
43
+ return {
44
+ filePath: options.filePath,
45
+ commits,
46
+ authors,
47
+ };
48
+ }
49
+ export function latestCommitForFile(repoPath, filePath) {
50
+ const output = runGit(path.resolve(repoPath), [
51
+ "log",
52
+ "-n1",
53
+ "--format=%H",
54
+ "--",
55
+ filePath,
56
+ ]).trim();
57
+ return output || null;
58
+ }
59
+ export function commitDetails(repoPath, sha) {
60
+ const output = runGit(path.resolve(repoPath), [
61
+ "show",
62
+ "--no-color",
63
+ "--format=%H%x1f%an%x1f%aI%x1f%s%x1f%b",
64
+ "--no-patch",
65
+ sha,
66
+ ]).trimEnd();
67
+ const [commitSha = sha, author = "", date = "", subject = "", body = ""] = output.split("\x1f");
68
+ return { sha: commitSha, author, date, subject, body };
69
+ }
70
+ export function mainBranchOvernightBrief(options) {
71
+ const repoPath = path.resolve(options.repoPath);
72
+ const since = `${Math.max(1, options.sinceHours)} hours ago`;
73
+ const output = runGit(repoPath, [
74
+ "log",
75
+ `-n${Math.max(1, options.limit)}`,
76
+ `--since=${since}`,
77
+ "--format=%H%x1f%an%x1f%aI%x1f%s",
78
+ `origin/${options.baseBranch}`,
79
+ ]).trim();
80
+ if (!output) {
81
+ return {
82
+ baseBranch: options.baseBranch,
83
+ sinceHours: options.sinceHours,
84
+ commits: [],
85
+ };
86
+ }
87
+ const commits = output.split("\n").map((line) => {
88
+ const [sha = "", author = "", date = "", subject = ""] = line.split("\x1f");
89
+ return { sha, author, date, subject };
90
+ });
91
+ return {
92
+ baseBranch: options.baseBranch,
93
+ sinceHours: options.sinceHours,
94
+ commits,
95
+ };
96
+ }
97
+ export function resumeFeatureSessionBrief(options) {
98
+ const worktreePath = path.resolve(options.worktreePath);
99
+ const branch = runGit(worktreePath, [
100
+ "rev-parse",
101
+ "--abbrev-ref",
102
+ "HEAD",
103
+ ]).trim();
104
+ const countRaw = runGit(worktreePath, [
105
+ "rev-list",
106
+ "--left-right",
107
+ "--count",
108
+ `origin/${options.baseBranch}...HEAD`,
109
+ ]).trim();
110
+ const [behindRaw = "0", aheadRaw = "0"] = countRaw.split(/\s+/);
111
+ const behind = Number.parseInt(behindRaw, 10) || 0;
112
+ const ahead = Number.parseInt(aheadRaw, 10) || 0;
113
+ const mergeBase = runGit(worktreePath, [
114
+ "merge-base",
115
+ "HEAD",
116
+ `origin/${options.baseBranch}`,
117
+ ]).trim();
118
+ const baseFilesRaw = runGit(worktreePath, [
119
+ "diff",
120
+ "--name-only",
121
+ `${mergeBase}..origin/${options.baseBranch}`,
122
+ ]).trim();
123
+ const featureFilesRaw = runGit(worktreePath, [
124
+ "diff",
125
+ "--name-only",
126
+ `${mergeBase}..HEAD`,
127
+ ]).trim();
128
+ const baseFiles = new Set(baseFilesRaw
129
+ .split("\n")
130
+ .map((line) => line.trim())
131
+ .filter(Boolean));
132
+ const featureFiles = new Set(featureFilesRaw
133
+ .split("\n")
134
+ .map((line) => line.trim())
135
+ .filter(Boolean));
136
+ const overlapFiles = Array.from(featureFiles)
137
+ .filter((file) => baseFiles.has(file))
138
+ .slice(0, 40);
139
+ return {
140
+ worktreePath,
141
+ branch,
142
+ ahead,
143
+ behind,
144
+ overlapFiles,
145
+ };
146
+ }
@@ -0,0 +1,3 @@
1
+ import type { WorktreeRecord } from "../types.js";
2
+ export declare function listActiveWorktrees(repoPath: string): WorktreeRecord[];
3
+ export declare function currentBranch(repoPath: string): string;
@@ -0,0 +1,52 @@
1
+ import { execFileSync } from "node:child_process";
2
+ import path from "node:path";
3
+ function runGit(repoPath, args) {
4
+ return execFileSync("git", ["-C", repoPath, ...args], {
5
+ encoding: "utf8",
6
+ stdio: ["ignore", "pipe", "pipe"],
7
+ });
8
+ }
9
+ export function listActiveWorktrees(repoPath) {
10
+ const root = path.resolve(repoPath);
11
+ const output = runGit(root, ["worktree", "list", "--porcelain"]);
12
+ const records = [];
13
+ const blocks = output
14
+ .split("\n\n")
15
+ .map((block) => block.trim())
16
+ .filter(Boolean);
17
+ for (const block of blocks) {
18
+ const lines = block.split("\n");
19
+ let recordPath = "";
20
+ let headSha = "";
21
+ let branch = "detached";
22
+ for (const line of lines) {
23
+ if (line.startsWith("worktree ")) {
24
+ recordPath = line.slice("worktree ".length).trim();
25
+ }
26
+ if (line.startsWith("HEAD ")) {
27
+ headSha = line.slice("HEAD ".length).trim();
28
+ }
29
+ if (line.startsWith("branch ")) {
30
+ const fullRef = line.slice("branch ".length).trim();
31
+ branch = fullRef.replace("refs/heads/", "");
32
+ }
33
+ }
34
+ if (!recordPath) {
35
+ continue;
36
+ }
37
+ records.push({
38
+ path: recordPath,
39
+ branch,
40
+ headSha,
41
+ isCurrent: path.resolve(recordPath) === root,
42
+ });
43
+ }
44
+ return records;
45
+ }
46
+ export function currentBranch(repoPath) {
47
+ return runGit(path.resolve(repoPath), [
48
+ "rev-parse",
49
+ "--abbrev-ref",
50
+ "HEAD",
51
+ ]).trim();
52
+ }