devguard 0.1.0 → 0.2.1

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,10 +1,10 @@
1
- # devGuard
1
+ # devguard
2
2
 
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.
3
+ MCP server that keeps a dev diary for you what changed, what decisions were made, what broke, what's next. Picks up where you left off so you never lose context between sessions.
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. Devguard fixes that. It reads your git state, tracks your branches, and writes diary entries automatically.
8
8
 
9
9
  ## Install
10
10
 
@@ -25,9 +25,40 @@ 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 with what changed, decisions, issues, and next steps |
29
29
  | `read_entries` | Reads recent entries to catch you up |
30
- | `catch_me_up` | Morning briefing — diary entries + git state in one shot |
30
+ | `catch_me_up` | Morning briefing — diary entries + git state + branch map in one shot |
31
+ | `branch_map` | Opens a visual branch map in your browser |
31
32
  | `setup` | Re-run setup manually if needed |
32
33
 
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.
34
+ ## Branch Map
35
+
36
+ Run `branch_map` to open an interactive HTML visualization of your repo in the browser. It shows:
37
+
38
+ - **All branches** with status (ahead/behind main), files changed, and latest commit
39
+ - **Collapsible diary summaries** per branch — what changed, decisions made, issues hit, next steps
40
+ - **Commit navigator** — click any branch to explore its commits on a visual timeline
41
+ - **Per-commit detail** — files changed, insertions/deletions, and diary entries linked to each commit
42
+ - **Auto-generated summaries** for commits without diary entries — categorized by type (Feature, Fix, Refactor, etc.) with affected areas and change scale
43
+
44
+ Designed for people who don't want to think about git.
45
+
46
+ ## Branch-Aware Diary
47
+
48
+ Entries are automatically routed by branch:
49
+ - **main/master** → `.devguard/entries/` (daily files)
50
+ - **feature branches** → `.devguard/branches/<branch-name>.md` (one file per branch)
51
+
52
+ This means `catch_me_up` shows you the current branch's full story first, then the main stem, then other active branches — so you always know what's happening everywhere.
53
+
54
+ ## How It Works
55
+
56
+ Entries are markdown files stored locally in your project under `.devguard/`. Each entry captures:
57
+ - **Summary** — one-line description of what happened
58
+ - **What Changed** — files modified, features added, bugs fixed
59
+ - **Decisions** — key choices made and why
60
+ - **Issues** — what broke, what's stuck
61
+ - **Next Steps** — what to do next session
62
+ - **Commit hash** — links the entry to a specific commit for traceability
63
+
64
+ Multiple sessions and agents all append to the same file. The diary builds up over time, making summaries richer and the branch map more useful with every session.
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);