foreman-ai 1.0.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 ADDED
@@ -0,0 +1,56 @@
1
+ # foreman-ai
2
+
3
+ AI Project Manager MCP server — persistent, cross-project backlog management for Claude Code.
4
+
5
+ Foreman gives Claude Code a shared backlog across all your projects. Add work items, track priorities, and ask "what should I work on next?" from any session.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install -g foreman-ai
11
+ ```
12
+
13
+ ## Configure
14
+
15
+ Add to your Claude Code MCP config (`~/.claude/settings.json` or project `.mcp.json`):
16
+
17
+ ```json
18
+ {
19
+ "mcpServers": {
20
+ "foreman": {
21
+ "command": "foreman-ai",
22
+ "args": ["--db", "~/.foreman/foreman.db"]
23
+ }
24
+ }
25
+ }
26
+ ```
27
+
28
+ ## Tools
29
+
30
+ ### Project Management
31
+ - **pm_register_project** — Register a project (run once per project)
32
+ - **pm_list_projects** — List all registered projects
33
+
34
+ ### Backlog CRUD
35
+ - **pm_add_item** — Add a work item (required: project_id, title)
36
+ - **pm_update_item** — Update any field on an item
37
+ - **pm_list_items** — List items with filters (project, status, category, tag)
38
+ - **pm_get_item** — Get full detail on one item
39
+ - **pm_delete_item** — Remove an item
40
+
41
+ ### Smart Operations
42
+ - **pm_next_work** — "What should I work on?" Top unblocked items by priority and ROI
43
+ - **pm_prioritize** — Re-score items with new ROI scores
44
+ - **pm_bulk_import** — Import items from JSON array
45
+
46
+ ## Data Model
47
+
48
+ Items have: title, description, status (backlog/ready/in_progress/done/archived), priority, category (feature/bug/research/chore), ROI score (1-10), effort (small/medium/large), blocked_by, tags, and source tracking.
49
+
50
+ ## Database
51
+
52
+ SQLite, stored at `~/.foreman/foreman.db` by default. Override with `--db <path>`.
53
+
54
+ ## License
55
+
56
+ MIT
package/dist/db.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ import Database from "better-sqlite3";
2
+ export declare function getDb(): Database.Database;
3
+ export declare function initDb(dbPath: string): Database.Database;
package/dist/db.js ADDED
@@ -0,0 +1,68 @@
1
+ import Database from "better-sqlite3";
2
+ import path from "path";
3
+ import fs from "fs";
4
+ let db;
5
+ export function getDb() {
6
+ if (!db) {
7
+ throw new Error("Database not initialized. Call initDb() first.");
8
+ }
9
+ return db;
10
+ }
11
+ export function initDb(dbPath) {
12
+ const dir = path.dirname(dbPath);
13
+ if (!fs.existsSync(dir)) {
14
+ fs.mkdirSync(dir, { recursive: true });
15
+ }
16
+ db = new Database(dbPath);
17
+ db.pragma("journal_mode = WAL");
18
+ db.pragma("foreign_keys = ON");
19
+ migrate(db);
20
+ return db;
21
+ }
22
+ function migrate(db) {
23
+ db.exec(`
24
+ CREATE TABLE IF NOT EXISTS projects (
25
+ id TEXT PRIMARY KEY,
26
+ name TEXT NOT NULL,
27
+ description TEXT NOT NULL DEFAULT '',
28
+ repo_path TEXT NOT NULL DEFAULT '',
29
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
30
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
31
+ );
32
+
33
+ CREATE TABLE IF NOT EXISTS items (
34
+ id TEXT PRIMARY KEY,
35
+ project_id TEXT NOT NULL REFERENCES projects(id),
36
+ title TEXT NOT NULL,
37
+ description TEXT NOT NULL DEFAULT '',
38
+ status TEXT NOT NULL DEFAULT 'backlog',
39
+ priority INTEGER NOT NULL DEFAULT 100,
40
+ category TEXT NOT NULL DEFAULT 'feature',
41
+ roi_score INTEGER,
42
+ roi_reason TEXT NOT NULL DEFAULT '',
43
+ effort TEXT,
44
+ blocked_by TEXT NOT NULL DEFAULT '[]',
45
+ tags TEXT NOT NULL DEFAULT '[]',
46
+ source TEXT NOT NULL DEFAULT 'user',
47
+ execution_mode TEXT NOT NULL DEFAULT 'manual',
48
+ assigned_to TEXT NOT NULL DEFAULT '',
49
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
50
+ updated_at TEXT NOT NULL DEFAULT (datetime('now')),
51
+ completed_at TEXT
52
+ );
53
+
54
+ CREATE TABLE IF NOT EXISTS sessions (
55
+ id TEXT PRIMARY KEY,
56
+ project_id TEXT NOT NULL REFERENCES projects(id),
57
+ started_at TEXT NOT NULL DEFAULT (datetime('now')),
58
+ ended_at TEXT,
59
+ summary TEXT NOT NULL DEFAULT '',
60
+ items_touched TEXT NOT NULL DEFAULT '[]'
61
+ );
62
+
63
+ CREATE INDEX IF NOT EXISTS idx_items_project ON items(project_id);
64
+ CREATE INDEX IF NOT EXISTS idx_items_status ON items(status);
65
+ CREATE INDEX IF NOT EXISTS idx_sessions_project ON sessions(project_id);
66
+ `);
67
+ }
68
+ //# sourceMappingURL=db.js.map
package/dist/db.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"db.js","sourceRoot":"","sources":["../src/db.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AACtC,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,MAAM,IAAI,CAAC;AAEpB,IAAI,EAAqB,CAAC;AAE1B,MAAM,UAAU,KAAK;IACnB,IAAI,CAAC,EAAE,EAAE,CAAC;QACR,MAAM,IAAI,KAAK,CAAC,gDAAgD,CAAC,CAAC;IACpE,CAAC;IACD,OAAO,EAAE,CAAC;AACZ,CAAC;AAED,MAAM,UAAU,MAAM,CAAC,MAAc;IACnC,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IACjC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACxB,EAAE,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACzC,CAAC;IAED,EAAE,GAAG,IAAI,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC1B,EAAE,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAC;IAChC,EAAE,CAAC,MAAM,CAAC,mBAAmB,CAAC,CAAC;IAE/B,OAAO,CAAC,EAAE,CAAC,CAAC;IACZ,OAAO,EAAE,CAAC;AACZ,CAAC;AAED,SAAS,OAAO,CAAC,EAAqB;IACpC,EAAE,CAAC,IAAI,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CP,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env node
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { initDb } from "./db.js";
5
+ import { registerProjectTools } from "./tools/projects.js";
6
+ import { registerItemTools } from "./tools/items.js";
7
+ import { registerSmartTools } from "./tools/smart.js";
8
+ import path from "path";
9
+ import os from "os";
10
+ function getDbPath() {
11
+ const args = process.argv.slice(2);
12
+ const dbIndex = args.indexOf("--db");
13
+ if (dbIndex !== -1 && args[dbIndex + 1]) {
14
+ const raw = args[dbIndex + 1];
15
+ if (raw.startsWith("~")) {
16
+ return path.join(os.homedir(), raw.slice(1));
17
+ }
18
+ return path.resolve(raw);
19
+ }
20
+ return path.join(os.homedir(), ".foreman", "foreman.db");
21
+ }
22
+ async function main() {
23
+ const dbPath = getDbPath();
24
+ initDb(dbPath);
25
+ const server = new McpServer({
26
+ name: "foreman-ai",
27
+ version: "1.0.0",
28
+ });
29
+ registerProjectTools(server);
30
+ registerItemTools(server);
31
+ registerSmartTools(server);
32
+ const transport = new StdioServerTransport();
33
+ await server.connect(transport);
34
+ }
35
+ main().catch((err) => {
36
+ console.error("Foreman failed to start:", err);
37
+ process.exit(1);
38
+ });
39
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,MAAM,IAAI,CAAC;AAEpB,SAAS,SAAS;IAChB,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACnC,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IACrC,IAAI,OAAO,KAAK,CAAC,CAAC,IAAI,IAAI,CAAC,OAAO,GAAG,CAAC,CAAC,EAAE,CAAC;QACxC,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,GAAG,CAAC,CAAC,CAAC;QAC9B,IAAI,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;YACxB,OAAO,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;QAC/C,CAAC;QACD,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAC3B,CAAC;IACD,OAAO,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,UAAU,EAAE,YAAY,CAAC,CAAC;AAC3D,CAAC;AAED,KAAK,UAAU,IAAI;IACjB,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,MAAM,CAAC,MAAM,CAAC,CAAC;IAEf,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;QAC3B,IAAI,EAAE,YAAY;QAClB,OAAO,EAAE,OAAO;KACjB,CAAC,CAAC;IAEH,oBAAoB,CAAC,MAAM,CAAC,CAAC;IAC7B,iBAAiB,CAAC,MAAM,CAAC,CAAC;IAC1B,kBAAkB,CAAC,MAAM,CAAC,CAAC;IAE3B,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;AAClC,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,OAAO,CAAC,KAAK,CAAC,0BAA0B,EAAE,GAAG,CAAC,CAAC;IAC/C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerItemTools(server: McpServer): void;
@@ -0,0 +1,157 @@
1
+ import { z } from "zod";
2
+ import { v4 as uuidv4 } from "uuid";
3
+ import { getDb } from "../db.js";
4
+ const StatusEnum = z.enum(["backlog", "ready", "in_progress", "done", "archived"]);
5
+ const CategoryEnum = z.enum(["feature", "bug", "research", "chore"]);
6
+ const EffortEnum = z.enum(["small", "medium", "large"]);
7
+ const SourceEnum = z.enum(["user", "claude", "auto"]);
8
+ const ExecutionModeEnum = z.enum(["manual", "auto"]);
9
+ function rowToItem(row) {
10
+ return {
11
+ ...row,
12
+ blocked_by: JSON.parse(row.blocked_by),
13
+ tags: JSON.parse(row.tags),
14
+ };
15
+ }
16
+ export function registerItemTools(server) {
17
+ server.tool("pm_add_item", "Add a work item to the backlog.", {
18
+ project_id: z.string().describe("Project slug"),
19
+ title: z.string().describe("Item title"),
20
+ description: z.string().optional(),
21
+ status: StatusEnum.optional(),
22
+ priority: z.number().int().optional().describe("Lower = higher priority"),
23
+ category: CategoryEnum.optional(),
24
+ roi_score: z.number().int().min(1).max(10).optional(),
25
+ roi_reason: z.string().optional(),
26
+ effort: EffortEnum.optional(),
27
+ blocked_by: z.array(z.string()).optional(),
28
+ tags: z.array(z.string()).optional(),
29
+ source: SourceEnum.optional(),
30
+ execution_mode: ExecutionModeEnum.optional(),
31
+ assigned_to: z.string().optional(),
32
+ }, async (args) => {
33
+ const db = getDb();
34
+ const id = uuidv4();
35
+ const now = new Date().toISOString();
36
+ db.prepare(`
37
+ INSERT INTO items (id, project_id, title, description, status, priority, category,
38
+ roi_score, roi_reason, effort, blocked_by, tags, source, execution_mode,
39
+ assigned_to, created_at, updated_at)
40
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
41
+ `).run(id, args.project_id, args.title, args.description ?? "", args.status ?? "backlog", args.priority ?? 100, args.category ?? "feature", args.roi_score ?? null, args.roi_reason ?? "", args.effort ?? null, JSON.stringify(args.blocked_by ?? []), JSON.stringify(args.tags ?? []), args.source ?? "user", args.execution_mode ?? "manual", args.assigned_to ?? "", now, now);
42
+ return { content: [{ type: "text", text: `Item '${args.title}' added (${id}).` }] };
43
+ });
44
+ server.tool("pm_update_item", "Update any fields on a work item.", {
45
+ id: z.string().describe("Item UUID"),
46
+ title: z.string().optional(),
47
+ description: z.string().optional(),
48
+ status: StatusEnum.optional(),
49
+ priority: z.number().int().optional(),
50
+ category: CategoryEnum.optional(),
51
+ roi_score: z.number().int().min(1).max(10).optional(),
52
+ roi_reason: z.string().optional(),
53
+ effort: EffortEnum.optional(),
54
+ blocked_by: z.array(z.string()).optional(),
55
+ tags: z.array(z.string()).optional(),
56
+ source: SourceEnum.optional(),
57
+ execution_mode: ExecutionModeEnum.optional(),
58
+ assigned_to: z.string().optional(),
59
+ }, async (args) => {
60
+ const db = getDb();
61
+ const { id, ...updates } = args;
62
+ const now = new Date().toISOString();
63
+ const sets = [];
64
+ const values = [];
65
+ for (const [key, value] of Object.entries(updates)) {
66
+ if (value === undefined)
67
+ continue;
68
+ if (key === "blocked_by" || key === "tags") {
69
+ sets.push(`${key} = ?`);
70
+ values.push(JSON.stringify(value));
71
+ }
72
+ else {
73
+ sets.push(`${key} = ?`);
74
+ values.push(value);
75
+ }
76
+ }
77
+ if (sets.length === 0) {
78
+ return { content: [{ type: "text", text: "No fields to update." }] };
79
+ }
80
+ // Set completed_at when moving to done
81
+ if (updates.status === "done") {
82
+ sets.push("completed_at = ?");
83
+ values.push(now);
84
+ }
85
+ sets.push("updated_at = ?");
86
+ values.push(now);
87
+ values.push(id);
88
+ db.prepare(`UPDATE items SET ${sets.join(", ")} WHERE id = ?`).run(...values);
89
+ return { content: [{ type: "text", text: `Item ${id} updated.` }] };
90
+ });
91
+ server.tool("pm_list_items", "List backlog items with optional filters. Defaults to current project, non-archived.", {
92
+ project_id: z.string().optional().describe("Filter by project"),
93
+ status: StatusEnum.optional().describe("Filter by status"),
94
+ category: CategoryEnum.optional().describe("Filter by category"),
95
+ tag: z.string().optional().describe("Filter by tag"),
96
+ }, async (args) => {
97
+ const db = getDb();
98
+ const conditions = [];
99
+ const params = [];
100
+ if (args.project_id) {
101
+ conditions.push("project_id = ?");
102
+ params.push(args.project_id);
103
+ }
104
+ if (args.status) {
105
+ conditions.push("status = ?");
106
+ params.push(args.status);
107
+ }
108
+ else {
109
+ conditions.push("status != 'archived'");
110
+ }
111
+ if (args.category) {
112
+ conditions.push("category = ?");
113
+ params.push(args.category);
114
+ }
115
+ if (args.tag) {
116
+ conditions.push("tags LIKE ?");
117
+ params.push(`%"${args.tag}"%`);
118
+ }
119
+ const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
120
+ const rows = db
121
+ .prepare(`SELECT * FROM items ${where} ORDER BY priority ASC, roi_score DESC`)
122
+ .all(...params);
123
+ if (rows.length === 0) {
124
+ return { content: [{ type: "text", text: "No items found." }] };
125
+ }
126
+ const items = rows.map(rowToItem);
127
+ const text = items
128
+ .map((i) => `[${i.status}] **${i.title}** (${i.id.slice(0, 8)})\n Project: ${i.project_id} | Priority: ${i.priority} | ROI: ${i.roi_score ?? "?"}/10 | ${i.category} | ${i.effort ?? "unsized"}`)
129
+ .join("\n\n");
130
+ return { content: [{ type: "text", text: `${items.length} items:\n\n${text}` }] };
131
+ });
132
+ server.tool("pm_get_item", "Get full details of a single work item.", {
133
+ id: z.string().describe("Item UUID (full or prefix)"),
134
+ }, async ({ id }) => {
135
+ const db = getDb();
136
+ const row = db
137
+ .prepare("SELECT * FROM items WHERE id = ? OR id LIKE ?")
138
+ .get(id, `${id}%`);
139
+ if (!row) {
140
+ return { content: [{ type: "text", text: `Item '${id}' not found.` }] };
141
+ }
142
+ const item = rowToItem(row);
143
+ const text = JSON.stringify(item, null, 2);
144
+ return { content: [{ type: "text", text }] };
145
+ });
146
+ server.tool("pm_delete_item", "Delete a work item.", {
147
+ id: z.string().describe("Item UUID"),
148
+ }, async ({ id }) => {
149
+ const db = getDb();
150
+ const result = db.prepare("DELETE FROM items WHERE id = ?").run(id);
151
+ if (result.changes === 0) {
152
+ return { content: [{ type: "text", text: `Item '${id}' not found.` }] };
153
+ }
154
+ return { content: [{ type: "text", text: `Item ${id} deleted.` }] };
155
+ });
156
+ }
157
+ //# sourceMappingURL=items.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"items.js","sourceRoot":"","sources":["../../src/tools/items.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerProjectTools(server: McpServer): void;
@@ -0,0 +1,34 @@
1
+ import { z } from "zod";
2
+ import { getDb } from "../db.js";
3
+ export function registerProjectTools(server) {
4
+ server.tool("pm_register_project", "Register a project with Foreman. Run once per project to enable backlog tracking.", {
5
+ id: z.string().describe("Slug ID for the project, e.g. 'paris-bets'"),
6
+ name: z.string().describe("Display name"),
7
+ description: z.string().optional().describe("Project description"),
8
+ repo_path: z.string().optional().describe("Filesystem path to repo root"),
9
+ }, async ({ id, name, description, repo_path }) => {
10
+ const db = getDb();
11
+ const now = new Date().toISOString();
12
+ const existing = db
13
+ .prepare("SELECT id FROM projects WHERE id = ?")
14
+ .get(id);
15
+ if (existing) {
16
+ db.prepare("UPDATE projects SET name = ?, description = ?, repo_path = ?, updated_at = ? WHERE id = ?").run(name, description ?? "", repo_path ?? "", now, id);
17
+ return { content: [{ type: "text", text: `Project '${id}' updated.` }] };
18
+ }
19
+ db.prepare("INSERT INTO projects (id, name, description, repo_path, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)").run(id, name, description ?? "", repo_path ?? "", now, now);
20
+ return { content: [{ type: "text", text: `Project '${id}' registered.` }] };
21
+ });
22
+ server.tool("pm_list_projects", "List all registered projects.", {}, async () => {
23
+ const db = getDb();
24
+ const projects = db.prepare("SELECT * FROM projects ORDER BY name").all();
25
+ if (projects.length === 0) {
26
+ return { content: [{ type: "text", text: "No projects registered. Use pm_register_project first." }] };
27
+ }
28
+ const text = projects
29
+ .map((p) => `**${p.name}** (${p.id})\n ${p.description || "No description"}\n Path: ${p.repo_path || "not set"}`)
30
+ .join("\n\n");
31
+ return { content: [{ type: "text", text }] };
32
+ });
33
+ }
34
+ //# sourceMappingURL=projects.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"projects.js","sourceRoot":"","sources":["../../src/tools/projects.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,KAAK,EAAE,MAAM,UAAU,CAAC;AAGjC,MAAM,UAAU,oBAAoB,CAAC,MAAiB;IACpD,MAAM,CAAC,IAAI,CACT,qBAAqB,EACrB,mFAAmF,EACnF;QACE,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,4CAA4C,CAAC;QACrE,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,cAAc,CAAC;QACzC,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,qBAAqB,CAAC;QAClE,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,8BAA8B,CAAC;KAC1E,EACD,KAAK,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,SAAS,EAAE,EAAE,EAAE;QAC7C,MAAM,EAAE,GAAG,KAAK,EAAE,CAAC;QACnB,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;QAErC,MAAM,QAAQ,GAAG,EAAE;aAChB,OAAO,CAAC,sCAAsC,CAAC;aAC/C,GAAG,CAAC,EAAE,CAAwB,CAAC;QAElC,IAAI,QAAQ,EAAE,CAAC;YACb,EAAE,CAAC,OAAO,CACR,2FAA2F,CAC5F,CAAC,GAAG,CAAC,IAAI,EAAE,WAAW,IAAI,EAAE,EAAE,SAAS,IAAI,EAAE,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC;YAEzD,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,YAAY,EAAE,YAAY,EAAE,CAAC,EAAE,CAAC;QACpF,CAAC;QAED,EAAE,CAAC,OAAO,CACR,2GAA2G,CAC5G,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,EAAE,WAAW,IAAI,EAAE,EAAE,SAAS,IAAI,EAAE,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;QAE9D,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,YAAY,EAAE,eAAe,EAAE,CAAC,EAAE,CAAC;IACvF,CAAC,CACF,CAAC;IAEF,MAAM,CAAC,IAAI,CACT,kBAAkB,EAClB,+BAA+B,EAC/B,EAAE,EACF,KAAK,IAAI,EAAE;QACT,MAAM,EAAE,GAAG,KAAK,EAAE,CAAC;QACnB,MAAM,QAAQ,GAAG,EAAE,CAAC,OAAO,CAAC,sCAAsC,CAAC,CAAC,GAAG,EAAe,CAAC;QAEvF,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC1B,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,wDAAwD,EAAE,CAAC,EAAE,CAAC;QAClH,CAAC;QAED,MAAM,IAAI,GAAG,QAAQ;aAClB,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,IAAI,OAAO,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,WAAW,IAAI,gBAAgB,aAAa,CAAC,CAAC,SAAS,IAAI,SAAS,EAAE,CAAC;aAClH,IAAI,CAAC,MAAM,CAAC,CAAC;QAEhB,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;IACxD,CAAC,CACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,2 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerSmartTools(server: McpServer): void;
@@ -0,0 +1,118 @@
1
+ import { z } from "zod";
2
+ import { v4 as uuidv4 } from "uuid";
3
+ import { getDb } from "../db.js";
4
+ function rowToItem(row) {
5
+ return {
6
+ ...row,
7
+ blocked_by: JSON.parse(row.blocked_by),
8
+ tags: JSON.parse(row.tags),
9
+ };
10
+ }
11
+ export function registerSmartTools(server) {
12
+ server.tool("pm_next_work", "What should I work on next? Returns top unblocked items sorted by priority and ROI. This is the killer feature.", {
13
+ project_id: z.string().optional().describe("Filter by project (omit for all projects)"),
14
+ limit: z.number().int().min(1).max(20).optional().describe("Number of items to return (default 5)"),
15
+ }, async ({ project_id, limit }) => {
16
+ const db = getDb();
17
+ const max = limit ?? 5;
18
+ // Get all non-archived, non-done items
19
+ let query = `SELECT * FROM items WHERE status NOT IN ('done', 'archived')`;
20
+ const params = [];
21
+ if (project_id) {
22
+ query += " AND project_id = ?";
23
+ params.push(project_id);
24
+ }
25
+ const rows = db.prepare(query).all(...params);
26
+ const items = rows.map(rowToItem);
27
+ // Filter out blocked items: an item is blocked if any of its blocked_by IDs
28
+ // correspond to items that are NOT done
29
+ const doneIds = new Set(db.prepare("SELECT id FROM items WHERE status = 'done'").all().map((r) => r.id));
30
+ const unblocked = items.filter((item) => item.blocked_by.every((blockerId) => doneIds.has(blockerId)));
31
+ // Sort: lower priority number first, then higher ROI score, then ready before backlog
32
+ const statusOrder = { in_progress: 0, ready: 1, backlog: 2 };
33
+ unblocked.sort((a, b) => {
34
+ const statusDiff = (statusOrder[a.status] ?? 3) - (statusOrder[b.status] ?? 3);
35
+ if (statusDiff !== 0)
36
+ return statusDiff;
37
+ const priDiff = a.priority - b.priority;
38
+ if (priDiff !== 0)
39
+ return priDiff;
40
+ return (b.roi_score ?? 0) - (a.roi_score ?? 0);
41
+ });
42
+ const top = unblocked.slice(0, max);
43
+ if (top.length === 0) {
44
+ return {
45
+ content: [
46
+ {
47
+ type: "text",
48
+ text: "No actionable items found. Either everything is done, blocked, or archived.",
49
+ },
50
+ ],
51
+ };
52
+ }
53
+ const text = top
54
+ .map((i, idx) => `${idx + 1}. **${i.title}** (${i.id.slice(0, 8)})\n Status: ${i.status} | Priority: ${i.priority} | ROI: ${i.roi_score ?? "?"}/10 | ${i.category} | ${i.effort ?? "unsized"}\n ${i.description ? i.description.slice(0, 120) : "No description"}${i.roi_reason ? `\n ROI reason: ${i.roi_reason}` : ""}`)
55
+ .join("\n\n");
56
+ return {
57
+ content: [{ type: "text", text: `Top ${top.length} items to work on:\n\n${text}` }],
58
+ };
59
+ });
60
+ server.tool("pm_prioritize", "Re-score and re-rank items by ROI. Provide item IDs with new scores.", {
61
+ updates: z
62
+ .array(z.object({
63
+ id: z.string(),
64
+ roi_score: z.number().int().min(1).max(10),
65
+ roi_reason: z.string().optional(),
66
+ priority: z.number().int().optional(),
67
+ }))
68
+ .describe("Array of items with new ROI scores"),
69
+ }, async ({ updates }) => {
70
+ const db = getDb();
71
+ const now = new Date().toISOString();
72
+ const stmt = db.prepare("UPDATE items SET roi_score = ?, roi_reason = COALESCE(?, roi_reason), priority = COALESCE(?, priority), updated_at = ? WHERE id = ?");
73
+ let updated = 0;
74
+ for (const u of updates) {
75
+ const result = stmt.run(u.roi_score, u.roi_reason ?? null, u.priority ?? null, now, u.id);
76
+ updated += result.changes;
77
+ }
78
+ return {
79
+ content: [{ type: "text", text: `Updated ${updated} of ${updates.length} items.` }],
80
+ };
81
+ });
82
+ server.tool("pm_bulk_import", "Import items from a JSON array. Each object needs at minimum: project_id and title.", {
83
+ items: z
84
+ .array(z.object({
85
+ project_id: z.string(),
86
+ title: z.string(),
87
+ description: z.string().optional(),
88
+ status: z.string().optional(),
89
+ priority: z.number().int().optional(),
90
+ category: z.string().optional(),
91
+ roi_score: z.number().int().optional(),
92
+ roi_reason: z.string().optional(),
93
+ effort: z.string().optional(),
94
+ blocked_by: z.array(z.string()).optional(),
95
+ tags: z.array(z.string()).optional(),
96
+ source: z.string().optional(),
97
+ }))
98
+ .describe("Array of items to import"),
99
+ }, async ({ items }) => {
100
+ const db = getDb();
101
+ const now = new Date().toISOString();
102
+ const stmt = db.prepare(`
103
+ INSERT INTO items (id, project_id, title, description, status, priority, category,
104
+ roi_score, roi_reason, effort, blocked_by, tags, source, execution_mode,
105
+ assigned_to, created_at, updated_at)
106
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'manual', '', ?, ?)
107
+ `);
108
+ let imported = 0;
109
+ for (const item of items) {
110
+ stmt.run(uuidv4(), item.project_id, item.title, item.description ?? "", item.status ?? "backlog", item.priority ?? 100, item.category ?? "feature", item.roi_score ?? null, item.roi_reason ?? "", item.effort ?? null, JSON.stringify(item.blocked_by ?? []), JSON.stringify(item.tags ?? []), item.source ?? "auto", now, now);
111
+ imported++;
112
+ }
113
+ return {
114
+ content: [{ type: "text", text: `Imported ${imported} items.` }],
115
+ };
116
+ });
117
+ }
118
+ //# sourceMappingURL=smart.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"smart.js","sourceRoot":"","sources":["../../src/tools/smart.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,41 @@
1
+ export type Status = "backlog" | "ready" | "in_progress" | "done" | "archived";
2
+ export type Category = "feature" | "bug" | "research" | "chore";
3
+ export type Effort = "small" | "medium" | "large";
4
+ export type Source = "user" | "claude" | "auto";
5
+ export type ExecutionMode = "manual" | "auto";
6
+ export interface Project {
7
+ id: string;
8
+ name: string;
9
+ description: string;
10
+ repo_path: string;
11
+ created_at: string;
12
+ updated_at: string;
13
+ }
14
+ export interface Item {
15
+ id: string;
16
+ project_id: string;
17
+ title: string;
18
+ description: string;
19
+ status: Status;
20
+ priority: number;
21
+ category: Category;
22
+ roi_score: number | null;
23
+ roi_reason: string;
24
+ effort: Effort | null;
25
+ blocked_by: string[];
26
+ tags: string[];
27
+ source: Source;
28
+ execution_mode: ExecutionMode;
29
+ assigned_to: string;
30
+ created_at: string;
31
+ updated_at: string;
32
+ completed_at: string | null;
33
+ }
34
+ export interface Session {
35
+ id: string;
36
+ project_id: string;
37
+ started_at: string;
38
+ ended_at: string | null;
39
+ summary: string;
40
+ items_touched: string[];
41
+ }
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "foreman-ai",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "description": "AI Project Manager MCP server — persistent, cross-project backlog management for Claude Code",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "bin": {
9
+ "foreman-ai": "dist/index.js"
10
+ },
11
+ "scripts": {
12
+ "build": "tsc",
13
+ "dev": "tsc --watch",
14
+ "start": "node dist/index.js"
15
+ },
16
+ "keywords": [
17
+ "mcp",
18
+ "claude",
19
+ "project-management",
20
+ "backlog",
21
+ "ai"
22
+ ],
23
+ "author": "Mitchell",
24
+ "license": "MIT",
25
+ "files": [
26
+ "dist",
27
+ "README.md"
28
+ ],
29
+ "dependencies": {
30
+ "@modelcontextprotocol/sdk": "^1.28.0",
31
+ "better-sqlite3": "^12.8.0",
32
+ "uuid": "^13.0.0"
33
+ },
34
+ "devDependencies": {
35
+ "@types/better-sqlite3": "^7.6.13",
36
+ "@types/node": "^25.5.0",
37
+ "@types/uuid": "^10.0.0",
38
+ "typescript": "^6.0.2"
39
+ }
40
+ }