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 +37 -6
- package/dist/index.js +5 -3
- package/dist/tools/agent-check.d.ts +2 -0
- package/dist/tools/agent-check.js +62 -0
- package/dist/tools/agent-list.d.ts +2 -0
- package/dist/tools/agent-list.js +70 -0
- package/dist/tools/agent-register.d.ts +2 -0
- package/dist/tools/agent-register.js +105 -0
- package/dist/tools/agent-update.d.ts +2 -0
- package/dist/tools/agent-update.js +111 -0
- package/dist/tools/branch-map.d.ts +2 -0
- package/dist/tools/branch-map.js +108 -0
- package/dist/tools/catch-me-up.js +76 -3
- package/dist/tools/search-entries.d.ts +2 -0
- package/dist/tools/search-entries.js +78 -0
- package/dist/tools/setup.js +5 -5
- package/dist/tools/write-entry.js +8 -1
- package/dist/utils/auto-setup.js +5 -5
- package/dist/utils/branch-map-html.d.ts +19 -0
- package/dist/utils/branch-map-html.js +1195 -0
- package/dist/utils/collision.d.ts +22 -0
- package/dist/utils/collision.js +104 -0
- package/dist/utils/git.d.ts +25 -0
- package/dist/utils/git.js +74 -0
- package/dist/utils/storage.d.ts +19 -0
- package/dist/utils/storage.js +105 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
#
|
|
1
|
+
# devguard
|
|
2
2
|
|
|
3
|
-
MCP server that
|
|
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.
|
|
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
|
|
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
|
-
|
|
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 .
|
|
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: "
|
|
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("
|
|
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,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,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,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,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,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
|
-
|
|
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
|
|
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);
|