devguard 0.1.0 → 0.2.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 CHANGED
@@ -1,21 +1,21 @@
1
- # devGuard
1
+ # dev-mark
2
2
 
3
3
  MCP server that auto-generates dev diary entries from your git activity. What changed, what decisions were made, what broke, what's next — so you can pick up where you left off.
4
4
 
5
5
  ## Why
6
6
 
7
- You vibe code for 3 hours, close your laptop, and come back the next day with no idea what you were doing. devGuard fixes that. It reads your git state and writes a diary entry automatically.
7
+ You vibe code for 3 hours, close your laptop, and come back the next day with no idea what you were doing. Dev-mark fixes that. It reads your git state and writes a diary entry automatically.
8
8
 
9
9
  ## Install
10
10
 
11
11
  Add to Claude Code:
12
12
 
13
13
  ```bash
14
- claude mcp add devguard -- npx devguard
14
+ claude mcp add devdiary -- npx devdiary
15
15
  ```
16
16
 
17
- That's it. On first run, devguard automatically:
18
- - Adds `.devguard/` to your `.gitignore`
17
+ That's it. On first run, devdiary automatically:
18
+ - Adds `.devdiary/` to your `.gitignore`
19
19
  - Adds an auto-logging instruction to your `CLAUDE.md` (or `.cursorrules` if that exists)
20
20
 
21
21
  From then on, your AI writes diary entries on its own — after finishing a feature, after a big commit, before context gets lost. You never think about it. The diary just fills itself.
@@ -25,9 +25,9 @@ From then on, your AI writes diary entries on its own — after finishing a feat
25
25
  | Tool | What it does |
26
26
  |------|-------------|
27
27
  | `get_context` | Reads git branch, status, recent commits, and diffs |
28
- | `write_entry` | Saves a diary entry to `.devguard/entries/` |
28
+ | `write_entry` | Saves a diary entry to `.devdiary/entries/` |
29
29
  | `read_entries` | Reads recent entries to catch you up |
30
30
  | `catch_me_up` | Morning briefing — diary entries + git state in one shot |
31
31
  | `setup` | Re-run setup manually if needed |
32
32
 
33
- Entries are markdown files stored locally in your project under `.devguard/`, one file per day. Multiple sessions and agents all append to the same daily file.
33
+ Entries are markdown files stored locally in your project under `.devdiary/`, one file per day. Multiple sessions and agents all append to the same daily file.
package/dist/index.js CHANGED
@@ -8,12 +8,13 @@ const write_entry_js_1 = require("./tools/write-entry.js");
8
8
  const read_entries_js_1 = require("./tools/read-entries.js");
9
9
  const catch_me_up_js_1 = require("./tools/catch-me-up.js");
10
10
  const setup_js_1 = require("./tools/setup.js");
11
+ const branch_map_js_1 = require("./tools/branch-map.js");
11
12
  const auto_setup_js_1 = require("./utils/auto-setup.js");
12
- // Auto-setup on first run: adds .devguard/ to .gitignore
13
+ // Auto-setup on first run: adds .devdiary/ to .gitignore
13
14
  // and auto-logging instruction to CLAUDE.md or .cursorrules
14
15
  (0, auto_setup_js_1.autoSetup)(process.cwd());
15
16
  const server = new mcp_js_1.McpServer({
16
- name: "devguard",
17
+ name: "devdiary",
17
18
  version: "0.1.0",
18
19
  });
19
20
  (0, get_context_js_1.registerGetContext)(server);
@@ -21,10 +22,11 @@ const server = new mcp_js_1.McpServer({
21
22
  (0, read_entries_js_1.registerReadEntries)(server);
22
23
  (0, catch_me_up_js_1.registerCatchMeUp)(server);
23
24
  (0, setup_js_1.registerSetup)(server);
25
+ (0, branch_map_js_1.registerBranchMap)(server);
24
26
  async function main() {
25
27
  const transport = new stdio_js_1.StdioServerTransport();
26
28
  await server.connect(transport);
27
- console.error("devguard MCP server running on stdio");
29
+ console.error("devdiary MCP server running on stdio");
28
30
  }
29
31
  main().catch((err) => {
30
32
  console.error("Fatal error:", err);
@@ -0,0 +1,2 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerAgentCheck(server: McpServer): void;
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerAgentCheck = registerAgentCheck;
4
+ const zod_1 = require("zod");
5
+ const collision_js_1 = require("../utils/collision.js");
6
+ function registerAgentCheck(server) {
7
+ server.tool("agent_check", "Check for file collisions between active agents. Call with no files to see all current collisions, or pass a list of files to check if they conflict with any active agent.", {
8
+ project_path: zod_1.z.string().describe("Absolute path to the project directory"),
9
+ files: zod_1.z
10
+ .array(zod_1.z.string())
11
+ .optional()
12
+ .describe("Files to check for conflicts. If omitted, reports all collisions."),
13
+ exclude_agent_id: zod_1.z
14
+ .string()
15
+ .optional()
16
+ .describe("Agent to exclude from collision check (usually yourself)"),
17
+ }, async ({ project_path, files, exclude_agent_id, }) => {
18
+ if (files && files.length > 0) {
19
+ const collisions = (0, collision_js_1.checkFilesForCollisions)(project_path, files, exclude_agent_id);
20
+ if (collisions.length === 0) {
21
+ return {
22
+ content: [
23
+ {
24
+ type: "text",
25
+ text: "No conflicts. These files are not claimed by any other active agent.",
26
+ },
27
+ ],
28
+ };
29
+ }
30
+ const lines = collisions.map((c) => `- **${c.file}** — claimed by: ${c.agents.join(", ")}`);
31
+ return {
32
+ content: [
33
+ {
34
+ type: "text",
35
+ text: `**Conflicts found (${collisions.length}):**\n${lines.join("\n")}\n\nCoordinate with these agents before modifying shared files.`,
36
+ },
37
+ ],
38
+ };
39
+ }
40
+ // No files specified — report all collisions
41
+ const collisions = (0, collision_js_1.detectCollisions)(project_path);
42
+ if (collisions.length === 0) {
43
+ return {
44
+ content: [
45
+ {
46
+ type: "text",
47
+ text: "No file collisions between active agents.",
48
+ },
49
+ ],
50
+ };
51
+ }
52
+ const lines = collisions.map((c) => `- **${c.file}** — shared by: ${c.agents.join(", ")}`);
53
+ return {
54
+ content: [
55
+ {
56
+ type: "text",
57
+ text: `**Active collisions (${collisions.length}):**\n${lines.join("\n")}\n\nThese files are claimed by multiple active agents. Agents should coordinate before editing.`,
58
+ },
59
+ ],
60
+ };
61
+ });
62
+ }
@@ -0,0 +1,2 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerAgentList(server: McpServer): void;
@@ -0,0 +1,70 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.registerAgentList = registerAgentList;
37
+ const zod_1 = require("zod");
38
+ const storage = __importStar(require("../utils/storage.js"));
39
+ function registerAgentList(server) {
40
+ server.tool("agent_list", "List all registered agents and their current status.", {
41
+ project_path: zod_1.z.string().describe("Absolute path to the project directory"),
42
+ }, async ({ project_path }) => {
43
+ const files = storage.listAgentFiles(project_path);
44
+ if (files.length === 0) {
45
+ return {
46
+ content: [{ type: "text", text: "No agents registered." }],
47
+ };
48
+ }
49
+ const summaries = files.map((f) => {
50
+ const agentId = f.replace(/\.md$/, "");
51
+ const content = storage.readAgentFile(project_path, agentId);
52
+ const statusMatch = content?.match(/^status:\s*(.+)$/m);
53
+ const taskMatch = content?.match(/^task:\s*"?(.+?)"?$/m);
54
+ const nameMatch = content?.match(/^name:\s*(.+)$/m);
55
+ const status = statusMatch?.[1] ?? "unknown";
56
+ const task = taskMatch?.[1] ?? "";
57
+ const name = nameMatch?.[1] ?? "";
58
+ const label = name ? `**${name}** (${agentId})` : `**${agentId}**`;
59
+ return `- ${label} [${status}]: ${task}`;
60
+ });
61
+ return {
62
+ content: [
63
+ {
64
+ type: "text",
65
+ text: `**Registered agents (${files.length}):**\n${summaries.join("\n")}`,
66
+ },
67
+ ],
68
+ };
69
+ });
70
+ }
@@ -0,0 +1,2 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerAgentRegister(server: McpServer): void;
@@ -0,0 +1,105 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.registerAgentRegister = registerAgentRegister;
37
+ const zod_1 = require("zod");
38
+ const git = __importStar(require("../utils/git.js"));
39
+ const storage = __importStar(require("../utils/storage.js"));
40
+ function registerAgentRegister(server) {
41
+ server.tool("agent_register", "Register a subagent before it starts work. Declares the agent's task and which files it plans to touch. Creates a per-agent log file and records the registration in the daily ledger so other agents can see it.", {
42
+ project_path: zod_1.z.string().describe("Absolute path to the project directory"),
43
+ agent_id: zod_1.z.string().describe("Unique slug for this agent (e.g. refactor-auth, add-tests)"),
44
+ task_summary: zod_1.z.string().describe("One-line description of what this agent will do"),
45
+ files_touching: zod_1.z
46
+ .array(zod_1.z.string())
47
+ .describe("List of file paths this agent plans to modify"),
48
+ }, async ({ project_path, agent_id, task_summary, files_touching }) => {
49
+ const now = new Date();
50
+ const isoDate = now.toISOString();
51
+ const time = storage.formatTime(now);
52
+ const branch = git.isGitRepo(project_path)
53
+ ? git.getBranch(project_path)
54
+ : "unknown";
55
+ const filesList = files_touching.join(", ");
56
+ // 1. Create per-agent MD file
57
+ const frontmatter = [
58
+ "---",
59
+ `agent_id: ${agent_id}`,
60
+ `task: "${task_summary}"`,
61
+ `registered: ${isoDate}`,
62
+ `status: active`,
63
+ `files_touching:`,
64
+ ...files_touching.map((f) => ` - ${f}`),
65
+ "---",
66
+ ].join("\n");
67
+ const agentContent = [
68
+ frontmatter,
69
+ "",
70
+ `# Agent: ${agent_id}`,
71
+ "",
72
+ "## Task",
73
+ task_summary,
74
+ "",
75
+ "## Files",
76
+ ...files_touching.map((f) => `- ${f}`),
77
+ "",
78
+ "---",
79
+ "",
80
+ `<!-- registered: ${time} -->`,
81
+ "Agent registered and starting work.",
82
+ "",
83
+ ].join("\n");
84
+ const agentFilePath = storage.writeAgentFile(project_path, agent_id, agentContent);
85
+ // 2. Append registration to daily ledger
86
+ const ledgerEntry = [
87
+ `<!-- agent: ${agent_id} | status: active | files: ${filesList} -->`,
88
+ `### [Agent] ${agent_id}`,
89
+ `**Task:** ${task_summary}`,
90
+ `**Files:** ${filesList}`,
91
+ `**Branch:** ${branch}`,
92
+ `**Registered:** ${isoDate}`,
93
+ "",
94
+ ].join("\n");
95
+ const ledgerPath = storage.writeEntry(project_path, ledgerEntry);
96
+ return {
97
+ content: [
98
+ {
99
+ type: "text",
100
+ text: `Agent "${agent_id}" registered.\nAgent log: ${agentFilePath}\nDaily ledger: ${ledgerPath}\n\nTask: ${task_summary}\nFiles: ${filesList}`,
101
+ },
102
+ ],
103
+ };
104
+ });
105
+ }
@@ -0,0 +1,2 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerAgentUpdate(server: McpServer): void;
@@ -0,0 +1,111 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.registerAgentUpdate = registerAgentUpdate;
37
+ const zod_1 = require("zod");
38
+ const storage = __importStar(require("../utils/storage.js"));
39
+ function registerAgentUpdate(server) {
40
+ server.tool("agent_update", "Update an agent's status, task, or append a log message. Use this to mark agents as done, paused, or blocked, update their scope, or record progress notes.", {
41
+ project_path: zod_1.z.string().describe("Absolute path to the project directory"),
42
+ agent_id: zod_1.z.string().describe("The agent's slug (e.g. entries, context)"),
43
+ status: zod_1.z
44
+ .enum(["active", "done", "paused", "blocked"])
45
+ .optional()
46
+ .describe("New status for the agent"),
47
+ task_summary: zod_1.z.string().optional().describe("Updated task description"),
48
+ log_message: zod_1.z.string().optional().describe("A progress note to append to the agent's log"),
49
+ }, async ({ project_path, agent_id, status, task_summary, log_message }) => {
50
+ const existing = storage.readAgentFile(project_path, agent_id);
51
+ if (!existing) {
52
+ return {
53
+ content: [
54
+ {
55
+ type: "text",
56
+ text: `Agent "${agent_id}" not found. Register it first with agent_register.`,
57
+ },
58
+ ],
59
+ };
60
+ }
61
+ let updated = existing;
62
+ const changes = [];
63
+ // Update status in frontmatter
64
+ if (status) {
65
+ updated = updated.replace(/^status:\s*.+$/m, `status: ${status}`);
66
+ changes.push(`status → ${status}`);
67
+ }
68
+ // Update task in frontmatter
69
+ if (task_summary) {
70
+ updated = updated.replace(/^task:\s*"?.+?"?$/m, `task: "${task_summary}"`);
71
+ // Also update the Task section body
72
+ updated = updated.replace(/## Task\n.+/, `## Task\n${task_summary}`);
73
+ changes.push(`task → "${task_summary}"`);
74
+ }
75
+ // Write back if frontmatter changed
76
+ if (status || task_summary) {
77
+ storage.writeAgentFile(project_path, agent_id, updated);
78
+ }
79
+ // Append log message
80
+ if (log_message) {
81
+ storage.appendToAgentFile(project_path, agent_id, log_message);
82
+ changes.push(`log appended`);
83
+ }
84
+ if (changes.length === 0) {
85
+ return {
86
+ content: [
87
+ {
88
+ type: "text",
89
+ text: `No changes specified for agent "${agent_id}". Provide status, task_summary, or log_message.`,
90
+ },
91
+ ],
92
+ };
93
+ }
94
+ // Record in daily ledger
95
+ const ledgerEntry = [
96
+ `<!-- agent-update: ${agent_id} | ${changes.join(", ")} -->`,
97
+ `### [Agent Update] ${agent_id}`,
98
+ `**Changes:** ${changes.join(", ")}`,
99
+ "",
100
+ ].join("\n");
101
+ storage.writeEntry(project_path, ledgerEntry);
102
+ return {
103
+ content: [
104
+ {
105
+ type: "text",
106
+ text: `Agent "${agent_id}" updated: ${changes.join(", ")}`,
107
+ },
108
+ ],
109
+ };
110
+ });
111
+ }
@@ -0,0 +1,2 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerBranchMap(server: McpServer): void;
@@ -0,0 +1,108 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.registerBranchMap = registerBranchMap;
37
+ const zod_1 = require("zod");
38
+ const git = __importStar(require("../utils/git.js"));
39
+ const storage = __importStar(require("../utils/storage.js"));
40
+ const branch_map_html_js_1 = require("../utils/branch-map-html.js");
41
+ function registerBranchMap(server) {
42
+ server.tool("branch_map", "Opens a visual branch map in the browser. Shows all branches, what they're for, and where you are — designed for people who don't want to think about git.", {
43
+ project_path: zod_1.z.string().describe("Absolute path to the project directory"),
44
+ }, async ({ project_path }) => {
45
+ if (!git.isGitRepo(project_path)) {
46
+ return {
47
+ content: [{ type: "text", text: "This project isn't using git yet — no branches to show." }],
48
+ };
49
+ }
50
+ const branch = git.getBranch(project_path);
51
+ const gitBranches = git.getLocalBranches(project_path);
52
+ const branchFiles = storage.listBranchFiles(project_path);
53
+ const branchFileMap = new Map(branchFiles.map((b) => [b.branch, b.content]));
54
+ const mainBranch = gitBranches.includes("main") ? "main" : "master";
55
+ // Also read main stem entries for diary content on main
56
+ const mainEntries = storage.readEntries(project_path, 5);
57
+ const mainDiaryEntries = mainEntries.flatMap((content) => storage.parseDiaryEntries(content));
58
+ const branches = gitBranches.map((b) => {
59
+ const isCurrent = b === branch;
60
+ const isMain = b === "main" || b === "master";
61
+ const sanitized = b.replace(/\//g, "-");
62
+ const content = branchFileMap.get(sanitized);
63
+ let summary;
64
+ let diaryEntries = [];
65
+ if (content) {
66
+ summary = storage.extractLatestSummary(content);
67
+ diaryEntries = storage.parseDiaryEntries(content);
68
+ }
69
+ else if (isMain && mainDiaryEntries.length > 0) {
70
+ summary = mainDiaryEntries[0]?.summary || git.getBranchLastCommit(project_path, b) || "Empty branch";
71
+ diaryEntries = mainDiaryEntries;
72
+ }
73
+ else {
74
+ summary = git.getBranchLastCommit(project_path, b) || "Empty branch";
75
+ }
76
+ const ahead = isMain ? 0 : git.getBranchCommitCount(project_path, b, mainBranch);
77
+ const behind = isMain ? 0 : git.getBehindCount(project_path, b, mainBranch);
78
+ const commits = git.getBranchCommits(project_path, b, 10);
79
+ // Enrich commits with per-commit file data for branch viewer
80
+ for (const c of commits) {
81
+ c.files = git.getCommitFiles(project_path, c.shortHash);
82
+ const stats = git.getCommitStats(project_path, c.shortHash);
83
+ c.insertions = stats.insertions;
84
+ c.deletions = stats.deletions;
85
+ c.body = git.getCommitBody(project_path, c.shortHash);
86
+ }
87
+ const filesChanged = isMain ? 0 : git.getFilesChangedCount(project_path, b, mainBranch);
88
+ return { name: b, isCurrent, isMain, summary, ahead, behind, commits, filesChanged, diaryEntries };
89
+ });
90
+ // Build events from branch data
91
+ const events = [];
92
+ branches.forEach((b) => {
93
+ if (!b.isMain && b.ahead > 0) {
94
+ events.push({ type: "fork", label: `Forked: ${b.name}`, branch: b.name });
95
+ }
96
+ });
97
+ if (branches.some((b) => !b.isMain && b.ahead === 0 && !b.isCurrent)) {
98
+ const merged = branches.filter((b) => !b.isMain && b.ahead === 0 && !b.isCurrent);
99
+ merged.forEach((b) => {
100
+ events.push({ type: "merge", label: `Merged: ${b.name} into main`, branch: b.name });
101
+ });
102
+ }
103
+ const filePath = (0, branch_map_html_js_1.generateAndOpenBranchMap)(project_path, branches, events);
104
+ return {
105
+ content: [{ type: "text", text: `Branch map opened in your browser.\n\nSaved to: ${filePath}` }],
106
+ };
107
+ });
108
+ }
@@ -42,10 +42,69 @@ function registerCatchMeUp(server) {
42
42
  project_path: zod_1.z.string().describe("Absolute path to the project directory"),
43
43
  }, async ({ project_path }) => {
44
44
  const parts = [];
45
- // Recent diary entries
45
+ const branch = git.isGitRepo(project_path)
46
+ ? git.getBranch(project_path)
47
+ : "main";
48
+ const isMainBranch = branch === "main" || branch === "master";
49
+ // Branch Map — visual overview of all branches
50
+ if (git.isGitRepo(project_path)) {
51
+ const gitBranches = git.getLocalBranches(project_path);
52
+ const branchFiles = storage.listBranchFiles(project_path);
53
+ const branchFileMap = new Map(branchFiles.map((b) => [b.branch, b.content]));
54
+ if (gitBranches.length > 1 || branchFiles.length > 0) {
55
+ const mainBranch = gitBranches.includes("main") ? "main" : "master";
56
+ const branchData = gitBranches.map((b) => {
57
+ const isCurrent = b === branch;
58
+ const isMain = b === "main" || b === "master";
59
+ const sanitized = b.replace(/\//g, "-");
60
+ const content = branchFileMap.get(sanitized);
61
+ let summary;
62
+ if (content) {
63
+ summary = storage.extractLatestSummary(content);
64
+ }
65
+ else {
66
+ summary = git.getBranchLastCommit(project_path, b) || "Empty branch";
67
+ }
68
+ let status = "";
69
+ if (isCurrent)
70
+ status = "current";
71
+ else if (isMain)
72
+ status = "main";
73
+ else {
74
+ const ahead = git.getBranchCommitCount(project_path, b, mainBranch);
75
+ status = ahead > 0 ? `${ahead} ahead` : "";
76
+ }
77
+ return { name: b, isCurrent, isMain, summary, status };
78
+ });
79
+ const lines = [];
80
+ lines.push("## Branch Map\n");
81
+ for (const b of branchData) {
82
+ const marker = b.isCurrent ? "> " : b.isMain ? " " : " ";
83
+ const icon = b.isCurrent ? "[*]" : b.isMain ? "[o]" : "[-]";
84
+ const statusTag = b.status ? ` (${b.status})` : "";
85
+ const truncSummary = b.summary.length > 60
86
+ ? b.summary.slice(0, 57) + "..."
87
+ : b.summary;
88
+ lines.push(`${marker}${icon} **${b.name}**${statusTag} — ${truncSummary}`);
89
+ }
90
+ parts.push(lines.join("\n"));
91
+ }
92
+ }
93
+ // If on a feature branch, show its log first
94
+ if (!isMainBranch) {
95
+ const branchLog = storage.readBranchEntry(project_path, branch);
96
+ if (branchLog) {
97
+ parts.push(`## Current Branch: ${branch}\n`);
98
+ parts.push(branchLog);
99
+ }
100
+ else {
101
+ parts.push(`## Current Branch: ${branch}\n\nNo diary entries for this branch yet.`);
102
+ }
103
+ }
104
+ // Recent main stem entries
46
105
  const entries = storage.readEntries(project_path, 3);
47
106
  if (entries.length > 0) {
48
- parts.push("## Recent Diary Entries\n");
107
+ parts.push("## Main Stem (Recent Daily Entries)\n");
49
108
  entries.forEach((entry, i) => {
50
109
  parts.push(`### ${i === 0 ? "Latest" : `Entry ${i + 1}`}\n${entry}`);
51
110
  });
@@ -53,9 +112,23 @@ function registerCatchMeUp(server) {
53
112
  else {
54
113
  parts.push("## No previous diary entries found.\n");
55
114
  }
115
+ // Other branch logs
116
+ const allBranches = storage.listBranchFiles(project_path);
117
+ const otherBranches = allBranches.filter((b) => b.branch !== branch.replace(/\//g, "-"));
118
+ if (otherBranches.length > 0) {
119
+ parts.push("## Other Branches\n");
120
+ for (const b of otherBranches) {
121
+ // Show just the last entry snippet for each
122
+ const lines = b.content.split("\n");
123
+ const lastSeparator = b.content.lastIndexOf("---\n\n<!--");
124
+ const snippet = lastSeparator >= 0
125
+ ? b.content.slice(lastSeparator).split("\n").slice(0, 15).join("\n")
126
+ : lines.slice(-10).join("\n");
127
+ parts.push(`### ${b.branch}\n${snippet}`);
128
+ }
129
+ }
56
130
  // Current git state
57
131
  if (git.isGitRepo(project_path)) {
58
- const branch = git.getBranch(project_path);
59
132
  const status = git.getStatus(project_path);
60
133
  const commits = git.getRecentCommits(project_path, 5);
61
134
  const diff = git.getDiffFull(project_path);
@@ -0,0 +1,2 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerSearchEntries(server: McpServer): void;