@luckychen1993/pm-mcp 0.1.5 → 0.3.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/LICENSE +21 -21
- package/README.md +102 -10
- package/dist/agents-md.js +3 -3
- package/dist/audit.js +25 -21
- package/dist/cli.js +32 -0
- package/dist/dashboard.js +6 -4
- package/dist/doctor.js +148 -0
- package/dist/git-state.js +75 -0
- package/dist/governance-tools.js +12 -2
- package/dist/index-db.js +40 -0
- package/dist/index-store.js +35 -44
- package/dist/index.js +15 -8
- package/dist/knowledge-tools.js +6 -15
- package/dist/language-adapters.js +11 -10
- package/dist/license.js +28 -2
- package/dist/probe.js +141 -0
- package/dist/project-fingerprint.js +9 -5
- package/dist/project-tools.js +13 -2
- package/dist/quality-evidence.js +56 -0
- package/dist/quality-store.js +5 -25
- package/dist/registry.js +28 -2
- package/dist/scan-policy.js +34 -0
- package/dist/scan.js +5 -25
- package/dist/search.js +4 -0
- package/dist/security.js +2 -0
- package/dist/semantic-evidence-store.js +22 -0
- package/dist/semantic-parsers.js +18 -0
- package/dist/session-log.js +14 -0
- package/dist/setup-project.js +146 -0
- package/dist/setup.js +92 -94
- package/dist/task-evidence.js +110 -0
- package/dist/task-pagination.js +64 -0
- package/dist/task-tools.js +47 -37
- package/dist/tool-base.js +7 -5
- package/dist/types.js +5 -0
- package/dist/version.js +10 -0
- package/package.json +7 -4
package/dist/task-tools.js
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { refreshDerived } from "./dashboard.js";
|
|
3
|
-
import {
|
|
3
|
+
import { normSep } from "./budget.js";
|
|
4
4
|
import { requireInitialized } from "./paths.js";
|
|
5
5
|
import { loadRoadmap, loadTasks, nextId, saveTasks } from "./store.js";
|
|
6
6
|
import { now } from "./types.js";
|
|
7
7
|
import { budgetLines, toolR, toolW } from "./tool-base.js";
|
|
8
|
+
import { captureFileHashes } from "./git-state.js";
|
|
9
|
+
import { appendSession } from "./session-log.js";
|
|
10
|
+
import { verifiedTaskEvidence } from "./task-evidence.js";
|
|
11
|
+
import { taskPage, taskPageText, TaskPageSchema } from "./task-pagination.js";
|
|
8
12
|
const stepSchema = z.object({ text: z.string(), done: z.boolean().optional() });
|
|
9
13
|
export function registerTaskTools(server, root) {
|
|
10
14
|
toolW(server, root, "add_task", "添加任务。长任务务必给 steps(显式步骤清单,断点续做的载体);走了捷径 type=debt 登记(债务账);修 bug type=fix。", {
|
|
@@ -55,43 +59,19 @@ export function registerTaskTools(server, root) {
|
|
|
55
59
|
: "";
|
|
56
60
|
return `✅ 任务 ${id}「${args.title}」已创建(${args.type ?? "feature"}/${args.milestone ?? "无里程碑"})。${tip}`;
|
|
57
61
|
});
|
|
58
|
-
toolR(server, root, "list_tasks", "
|
|
62
|
+
toolR(server, root, "list_tasks", "分页列出任务(默认不含 done/cancelled,每页25)。按原过滤条件传 next_cursor 可取下一页;数据变化会使游标失效。文字摘要与结构化分页结果同时返回,不漏掉折叠的中间任务。", {
|
|
59
63
|
status: z.enum(["backlog", "todo", "in_progress", "blocked", "done", "cancelled"]).optional(),
|
|
60
64
|
type: z.enum(["feature", "refactor", "fix", "chore", "debt"]).optional(),
|
|
61
65
|
milestone: z.string().optional(),
|
|
62
66
|
tag: z.string().optional(),
|
|
63
67
|
include_done: z.boolean().optional().describe("默认 false"),
|
|
68
|
+
page_size: z.number().int().min(1).max(100).optional().describe("默认25,受项目输出预算限制"),
|
|
69
|
+
cursor: z.string().min(1).max(1024).optional().describe("上页返回的 next_cursor;保留相同过滤条件"),
|
|
64
70
|
}, (args) => {
|
|
65
71
|
requireInitialized(root);
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
list = list.filter((t) => t.status === args.status);
|
|
70
|
-
else if (!args.include_done)
|
|
71
|
-
list = list.filter((t) => t.status !== "done" && t.status !== "cancelled");
|
|
72
|
-
if (args.type)
|
|
73
|
-
list = list.filter((t) => t.type === args.type);
|
|
74
|
-
if (args.milestone)
|
|
75
|
-
list = list.filter((t) => t.milestone === args.milestone);
|
|
76
|
-
if (args.tag)
|
|
77
|
-
list = list.filter((t) => t.tags.includes(args.tag));
|
|
78
|
-
const L = list.map((t) => {
|
|
79
|
-
const mark = t.status === "in_progress" ? "🔄" : t.status === "blocked" ? "🚫" : t.status === "done" ? "✅" : "☐";
|
|
80
|
-
const extra = [];
|
|
81
|
-
if (t.priority)
|
|
82
|
-
extra.push(t.priority);
|
|
83
|
-
if (t.type !== "feature")
|
|
84
|
-
extra.push(t.type);
|
|
85
|
-
if (t.checkpoint)
|
|
86
|
-
extra.push(`下一步: ${t.checkpoint.next_step}`);
|
|
87
|
-
return `${mark} ${t.id} ${t.title}${extra.length ? `(${extra.join(", ")})` : ""}`;
|
|
88
|
-
});
|
|
89
|
-
return foldLines([`共 ${list.length} 个任务(全部 ${tasks.length} 个):`, ...L], {
|
|
90
|
-
maxLines: budgetLines(root),
|
|
91
|
-
hint: "用 status/type/milestone 过滤",
|
|
92
|
-
});
|
|
93
|
-
});
|
|
94
|
-
toolW(server, root, "update_task", "更新任务。转 done 必须填 result_note;feature/fix 建议填 verification(用什么命令/测试证明)。step_done 勾选第 N 步(从 1 起)。steps 整体替换步骤清单。", {
|
|
72
|
+
return JSON.stringify(taskPage(root, args, budgetLines(root) - 4));
|
|
73
|
+
}, { outputSchema: TaskPageSchema.shape, decode: (value) => { const page = TaskPageSchema.parse(JSON.parse(value)); return { text: taskPageText(page), data: page }; } });
|
|
74
|
+
toolW(server, root, "update_task", "一次更新任务、checkpoint 与会话。checkpoint 默认记会话;完成时用 record_session:true 一并记录,不再另调 log_session;files 给实际关联文件。feature/fix 转 done 必须有最新成功且源码匹配的测试报告,verification_run 可指明报告。文字 verification 不能代替执行证据。", {
|
|
95
75
|
id: z.string(),
|
|
96
76
|
status: z.enum(["backlog", "todo", "in_progress", "blocked", "done", "cancelled"]).optional(),
|
|
97
77
|
title: z.string().optional(),
|
|
@@ -103,20 +83,27 @@ export function registerTaskTools(server, root) {
|
|
|
103
83
|
acceptance: z.string().optional(),
|
|
104
84
|
result_note: z.string().optional().describe("完成笔记(转 done 必填)"),
|
|
105
85
|
verification: z.string().optional().describe("怎么验证的:命令/测试名"),
|
|
86
|
+
verification_run: z.string().min(1).optional().describe(".pm/quality-runs 内最新的质量报告;省略时自动选最新"),
|
|
87
|
+
checkpoint: z.object({ note: z.string().trim().min(1), next_step: z.string().trim().min(1) }).optional(),
|
|
88
|
+
record_session: z.boolean().optional().describe("checkpoint 默认 true;完成任务时设 true 合并会话。省略保留旧式单独记录方式"),
|
|
106
89
|
steps: z.array(stepSchema).optional().describe("整体替换步骤清单"),
|
|
107
90
|
step_done: z.number().int().min(1).optional().describe("勾选完成第 N 步"),
|
|
108
91
|
author: z.string().optional(),
|
|
109
|
-
}, (args) => {
|
|
92
|
+
}, (args, prepared) => {
|
|
110
93
|
requireInitialized(root);
|
|
111
94
|
const data = loadTasks(root);
|
|
112
95
|
const t = data.tasks.find((x) => x.id === args.id);
|
|
113
96
|
if (!t)
|
|
114
97
|
throw new Error(`找不到任务 ${args.id}。`);
|
|
98
|
+
if (JSON.stringify(t) !== prepared.taskSnapshot)
|
|
99
|
+
throw new Error("任务在证据校验期间发生变化,请重新读取状态。");
|
|
115
100
|
const tnow = now();
|
|
116
101
|
if (args.title !== undefined)
|
|
117
102
|
t.title = args.title;
|
|
118
103
|
if (args.detail !== undefined)
|
|
119
104
|
t.detail = args.detail;
|
|
105
|
+
if (args.type !== undefined)
|
|
106
|
+
t.type = args.type; // schema 接受 type 但 handler 此前未应用(schema-handler 失配 bug)
|
|
120
107
|
if (args.priority !== undefined)
|
|
121
108
|
t.priority = args.priority ?? null;
|
|
122
109
|
if (args.milestone !== undefined) {
|
|
@@ -138,6 +125,8 @@ export function registerTaskTools(server, root) {
|
|
|
138
125
|
t.result_note = args.result_note;
|
|
139
126
|
if (args.verification !== undefined)
|
|
140
127
|
t.verification = args.verification;
|
|
128
|
+
if (prepared.evidence)
|
|
129
|
+
t.verification = prepared.evidence + (args.verification ? `\n验证说明: ${args.verification}` : "");
|
|
141
130
|
if (args.author !== undefined)
|
|
142
131
|
t.author = args.author;
|
|
143
132
|
if (args.steps !== undefined)
|
|
@@ -149,6 +138,13 @@ export function registerTaskTools(server, root) {
|
|
|
149
138
|
s.done = true;
|
|
150
139
|
}
|
|
151
140
|
const notes = [];
|
|
141
|
+
if (args.checkpoint) {
|
|
142
|
+
t.checkpoint = { ...args.checkpoint, at: tnow };
|
|
143
|
+
if (args.status === undefined && (t.status === "backlog" || t.status === "todo")) {
|
|
144
|
+
t.status = "in_progress";
|
|
145
|
+
t.started_at ??= tnow;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
152
148
|
if (args.status !== undefined && args.status !== t.status) {
|
|
153
149
|
if (args.status === "done") {
|
|
154
150
|
const note = args.result_note !== undefined ? args.result_note : t.result_note;
|
|
@@ -159,11 +155,6 @@ export function registerTaskTools(server, root) {
|
|
|
159
155
|
throw new Error(`result_note 过于空洞(「${note.trim()}」):完成笔记至少要说明做了什么(4 字以上),别拿一个字糊弄账本。`);
|
|
160
156
|
}
|
|
161
157
|
t.completed_at = tnow;
|
|
162
|
-
if (!t.verification.trim() && (args.verification === undefined || !args.verification.trim())) {
|
|
163
|
-
if (t.type === "feature" || t.type === "fix") {
|
|
164
|
-
notes.push("💡 提示: 该任务是 " + t.type + " 类型但没填 verification——用什么命令/测试证明它真的好了?建议补上(update_task id=" + t.id + " verification=...)。");
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
158
|
}
|
|
168
159
|
if (t.status === "done" && args.status !== "done")
|
|
169
160
|
t.completed_at = null;
|
|
@@ -173,9 +164,28 @@ export function registerTaskTools(server, root) {
|
|
|
173
164
|
}
|
|
174
165
|
t.updated = tnow;
|
|
175
166
|
saveTasks(root, data);
|
|
167
|
+
if (args.record_session ?? (args.checkpoint !== undefined)) {
|
|
168
|
+
const session = appendSession(root, {
|
|
169
|
+
summary: `${t.id} ${t.title}:${args.status === "done" ? t.result_note : args.checkpoint?.note ?? `状态 ${t.status}`}`,
|
|
170
|
+
files: args.files ?? [], next_steps: args.checkpoint ? [args.checkpoint.next_step] : [], author: args.author,
|
|
171
|
+
}, prepared.hashes);
|
|
172
|
+
notes.push(`会话 ${session} 已自动记录,无需重复 log_session。`);
|
|
173
|
+
}
|
|
176
174
|
refreshDerived(root);
|
|
177
175
|
const stepsInfo = t.steps.length > 0 ? `,步骤 ${t.steps.filter((s) => s.done).length}/${t.steps.length}` : "";
|
|
178
176
|
return [`✅ ${t.id} 已更新(${t.status}${stepsInfo})。`, ...notes].join("\n");
|
|
177
|
+
}, (args) => {
|
|
178
|
+
requireInitialized(root);
|
|
179
|
+
const task = loadTasks(root).tasks.find((item) => item.id === args.id);
|
|
180
|
+
if (!task)
|
|
181
|
+
throw new Error(`找不到任务 ${args.id}。`);
|
|
182
|
+
if (args.status === "done" && (args.result_note ?? task.result_note).trim().length < 4) {
|
|
183
|
+
throw new Error("转 done 必须填写 result_note(至少4字,不能过于空洞)。");
|
|
184
|
+
}
|
|
185
|
+
const evidence = args.status === "done" && (task.type === "feature" || task.type === "fix")
|
|
186
|
+
? verifiedTaskEvidence(root, args.verification_run, args.files ?? task.files) : undefined;
|
|
187
|
+
const record = args.record_session ?? (args.checkpoint !== undefined);
|
|
188
|
+
return { evidence, taskSnapshot: JSON.stringify(task), hashes: record ? captureFileHashes(root, args.files ?? []) : {} };
|
|
179
189
|
});
|
|
180
190
|
toolW(server, root, "checkpoint", "断点存档:长任务做到一半(或上下文快满)时随手保存「当前进度 + 下一步动作」。新会话 get_status 会直接给出恢复点,不用靠回忆。", {
|
|
181
191
|
task_id: z.string(),
|
package/dist/tool-base.js
CHANGED
|
@@ -25,19 +25,21 @@ export function recordUsage(root, tool, kind, started, outcome) {
|
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
27
|
/** 只读工具:跨 Agent/进程合并完全相同的并行请求,短窗复用结果。 */
|
|
28
|
-
export function toolR(server, root, name, description, schema, fn) {
|
|
29
|
-
server.registerTool(name, { description, inputSchema: schema, annotations: { readOnlyHint: true } }, (async (args) => {
|
|
28
|
+
export function toolR(server, root, name, description, schema, fn, presentation) {
|
|
29
|
+
server.registerTool(name, { description, inputSchema: schema, ...(presentation ? { outputSchema: presentation.outputSchema } : {}), annotations: { readOnlyHint: true } }, (async (args) => {
|
|
30
30
|
const started = Date.now();
|
|
31
31
|
try {
|
|
32
|
-
const result = await runCoalescedRead(root, name, args, fn);
|
|
32
|
+
const result = await runCoalescedRead(root, presentation ? `${name}:${presentation.cacheVersion ?? "structured-v1"}` : name, args, fn);
|
|
33
|
+
const decoded = presentation?.decode(result.text);
|
|
34
|
+
const text = decoded?.text ?? result.text;
|
|
33
35
|
recordUsage(root, name, "read", started, {
|
|
34
36
|
ok: !result.pending,
|
|
35
|
-
text:
|
|
37
|
+
text: decoded ? text + JSON.stringify(decoded.data) : text,
|
|
36
38
|
replayed: result.replayed,
|
|
37
39
|
pending: result.pending,
|
|
38
40
|
uncertain: result.uncertain,
|
|
39
41
|
});
|
|
40
|
-
return { content: [{ type: "text", text:
|
|
42
|
+
return { content: [{ type: "text", text }], ...(decoded ? { structuredContent: decoded.data } : {}) };
|
|
41
43
|
}
|
|
42
44
|
catch (error) {
|
|
43
45
|
const message = error.message;
|
package/dist/types.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { normSep } from "./budget.js";
|
|
3
|
+
import { ScanIgnoreSchema } from "./scan-policy.js";
|
|
4
|
+
import { CompletionEvidenceSchema } from "./quality-evidence.js";
|
|
3
5
|
export const now = () => new Date().toISOString();
|
|
4
6
|
/** 文件清单字段:统一 / 分隔(Windows 反斜杠入库即规范化),缺省空数组 */
|
|
5
7
|
const filesField = () => z.array(z.string()).default([]).transform((list) => list.map(normSep));
|
|
@@ -24,6 +26,8 @@ export const ProjectSchema = z.object({
|
|
|
24
26
|
phase: z.string().default(""),
|
|
25
27
|
/** 登记的模块名(用于复杂度账的"未登记目录"检测) */
|
|
26
28
|
modules: z.array(z.string()).default([]),
|
|
29
|
+
scan_ignore: ScanIgnoreSchema.optional(),
|
|
30
|
+
completion_evidence: CompletionEvidenceSchema.optional(),
|
|
27
31
|
/** 暴露面:local=本机工具 / network=内网服务 / public=公网服务,影响安全告警力度 */
|
|
28
32
|
exposure: ExposureSchema.default("local"),
|
|
29
33
|
/** 项目许可证(SPDX 或名称),用于依赖兼容性对账 */
|
|
@@ -118,6 +122,7 @@ export const SessionSchema = z.object({
|
|
|
118
122
|
author: z.string().default(""),
|
|
119
123
|
summary: z.string().trim().min(1),
|
|
120
124
|
files: filesField(),
|
|
125
|
+
file_hashes: z.record(z.string().regex(/^[a-f0-9]{64}$/)).optional(),
|
|
121
126
|
next_steps: z.array(z.string()).default([]),
|
|
122
127
|
});
|
|
123
128
|
export const SessionsFileSchema = z.object({
|
package/dist/version.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
// src/ 与发布后的 dist/ 共用包根,避免服务端与安装器各自硬编码版本。
|
|
3
|
+
const metadata = JSON.parse(fs.readFileSync(new URL("../package.json", import.meta.url), "utf8"));
|
|
4
|
+
export const SERVER_NAME = "pm-mcp";
|
|
5
|
+
export const VERSION = metadata.version;
|
|
6
|
+
export const PACKAGE_SPEC = `${metadata.name}@${VERSION}`;
|
|
7
|
+
export const RUNTIME_CONTRACT = "pagination-evidence-v1";
|
|
8
|
+
// 服务器工具目录契约(doctor 与独立巡检 health-check 都以此核对)。
|
|
9
|
+
// 新增/移除 MCP 工具时必须同步更新这里,并用 `pm-mcp probe` 实测复核。
|
|
10
|
+
export const TOOL_CATALOG_SIZE = 49;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@luckychen1993/pm-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "AI 编码项目的单一事实来源 + 健康台账 MCP 服务(事实来源 / 路线图 / 防幻觉 / 七本账 / 断点续做 / token 经济)",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"mcp",
|
|
@@ -32,12 +32,15 @@
|
|
|
32
32
|
"engines": {
|
|
33
33
|
"node": ">=22.18.0"
|
|
34
34
|
},
|
|
35
|
+
"pm_mcp": {
|
|
36
|
+
"acceptance_baseline": { "id": "pm-mcp-local-release", "version": "1.1.0" }
|
|
37
|
+
},
|
|
35
38
|
"scripts": {
|
|
36
39
|
"build": "tsc -p tsconfig.json",
|
|
37
40
|
"prepare": "npm run build",
|
|
38
41
|
"typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.scripts.json",
|
|
39
|
-
"test": "node --test \"test/*.test.ts\"",
|
|
40
|
-
"coverage": "node --experimental-test-coverage --test-coverage-include=\"src/**/*.ts\" --test-coverage-lines=90 --test-coverage-branches=85 --test-coverage-functions=90 --test \"test/*.test.ts\"",
|
|
42
|
+
"test": "node --test-concurrency=2 --test \"test/*.test.ts\"",
|
|
43
|
+
"coverage": "node --experimental-test-coverage --test-coverage-include=\"src/**/*.ts\" --test-coverage-lines=90 --test-coverage-branches=85 --test-coverage-functions=90 --test-concurrency=2 --test \"test/*.test.ts\"",
|
|
41
44
|
"audit": "node scripts/health-check.mts",
|
|
42
45
|
"benchmark": "node scripts/benchmark.mts",
|
|
43
46
|
"benchmark:volume": "node scripts/benchmark-volume.mts",
|
|
@@ -45,7 +48,7 @@
|
|
|
45
48
|
"quality:plan": "node scripts/quality-gate.mts --plan",
|
|
46
49
|
"acceptance:evidence": "node scripts/collect-acceptance-evidence.mts --root .",
|
|
47
50
|
"acceptance:prepare": "node scripts/create-pm-acceptance-evaluation.mts --root .",
|
|
48
|
-
"acceptance": "node scripts/acceptance-
|
|
51
|
+
"acceptance": "node scripts/acceptance-cycle.mts --root .",
|
|
49
52
|
"acceptance:cycle": "node scripts/acceptance-cycle.mts --root .",
|
|
50
53
|
"gate": "npm run audit -- --root . && npm run quality && npm run acceptance:cycle",
|
|
51
54
|
"start": "node dist/index.js",
|