@luckychen1993/pm-mcp 0.1.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.
Files changed (45) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +304 -0
  3. package/dist/acceptance-evaluator.js +330 -0
  4. package/dist/acceptance-model.js +440 -0
  5. package/dist/acceptance-report.js +335 -0
  6. package/dist/acceptance-tools.js +318 -0
  7. package/dist/audit-tools.js +61 -0
  8. package/dist/audit.js +364 -0
  9. package/dist/budget.js +71 -0
  10. package/dist/dashboard.js +241 -0
  11. package/dist/governance-audit.js +126 -0
  12. package/dist/governance-model.js +306 -0
  13. package/dist/governance-tools.js +240 -0
  14. package/dist/health.js +58 -0
  15. package/dist/index-db.js +97 -0
  16. package/dist/index-store.js +381 -0
  17. package/dist/index.js +210 -0
  18. package/dist/init.js +40 -0
  19. package/dist/knowledge-tools.js +204 -0
  20. package/dist/language-adapters.js +450 -0
  21. package/dist/language-dependencies.js +351 -0
  22. package/dist/license.js +261 -0
  23. package/dist/paths.js +51 -0
  24. package/dist/polyglot-ast.js +329 -0
  25. package/dist/portfolio.js +349 -0
  26. package/dist/project-fingerprint.js +83 -0
  27. package/dist/project-tools.js +217 -0
  28. package/dist/quality-store.js +77 -0
  29. package/dist/registry.js +38 -0
  30. package/dist/roadmap.js +96 -0
  31. package/dist/scan.js +271 -0
  32. package/dist/search.js +312 -0
  33. package/dist/security.js +269 -0
  34. package/dist/semantic-evidence-store.js +133 -0
  35. package/dist/semantic-evidence.js +100 -0
  36. package/dist/semantic-graph-algorithms.js +86 -0
  37. package/dist/semantic-graph.js +390 -0
  38. package/dist/semantic-parsers.js +311 -0
  39. package/dist/store.js +185 -0
  40. package/dist/task-tools.js +198 -0
  41. package/dist/tool-base.js +32 -0
  42. package/dist/tools.js +21 -0
  43. package/dist/types.js +208 -0
  44. package/dist/typescript-semantic.js +282 -0
  45. package/package.json +70 -0
@@ -0,0 +1,198 @@
1
+ import { z } from "zod";
2
+ import { refreshDerived } from "./dashboard.js";
3
+ import { foldLines, normSep } from "./budget.js";
4
+ import { requireInitialized } from "./paths.js";
5
+ import { loadRoadmap, loadTasks, nextId, saveTasks } from "./store.js";
6
+ import { now } from "./types.js";
7
+ import { budgetLines, tool, toolW } from "./tool-base.js";
8
+ const stepSchema = z.object({ text: z.string(), done: z.boolean().optional() });
9
+ export function registerTaskTools(server, root) {
10
+ toolW(server, root, "add_task", "添加任务。长任务务必给 steps(显式步骤清单,断点续做的载体);走了捷径 type=debt 登记(债务账);修 bug type=fix。", {
11
+ title: z.string().min(1),
12
+ detail: z.string().optional(),
13
+ type: z.enum(["feature", "refactor", "fix", "chore", "debt"]).optional().describe("默认 feature"),
14
+ priority: z.enum(["P0", "P1", "P2", "P3"]).optional(),
15
+ milestone: z.string().optional().describe("所属里程碑 id,如 M1"),
16
+ tags: z.array(z.string()).optional(),
17
+ files: z.array(z.string()).optional().describe("关联文件(相对路径)"),
18
+ acceptance: z.string().optional().describe("验收标准"),
19
+ steps: z.array(stepSchema).optional().describe("步骤清单(长任务必填)"),
20
+ }, (args) => {
21
+ requireInitialized(root);
22
+ const roadmap = loadRoadmap(root);
23
+ if (args.milestone && !roadmap.milestones.some((m) => m.id === args.milestone)) {
24
+ throw new Error(`里程碑 ${args.milestone} 不存在。现有: ${roadmap.milestones.map((m) => m.id).join(", ") || "无"}`);
25
+ }
26
+ const data = loadTasks(root);
27
+ const id = nextId("T", data.seq);
28
+ const t = now();
29
+ data.seq += 1;
30
+ data.tasks.push({
31
+ id,
32
+ title: args.title,
33
+ detail: args.detail ?? "",
34
+ type: args.type ?? "feature",
35
+ status: "backlog",
36
+ priority: args.priority ?? null,
37
+ milestone: args.milestone ?? null,
38
+ steps: (args.steps ?? []).map((s) => ({ text: s.text, done: s.done ?? false })),
39
+ checkpoint: null,
40
+ files: (args.files ?? []).map(normSep),
41
+ acceptance: args.acceptance ?? "",
42
+ result_note: "",
43
+ verification: "",
44
+ tags: args.tags ?? [],
45
+ author: "",
46
+ created: t,
47
+ updated: t,
48
+ started_at: null,
49
+ completed_at: null,
50
+ });
51
+ saveTasks(root, data);
52
+ refreshDerived(root);
53
+ const tip = (args.steps ?? []).length === 0
54
+ ? "\n提示: 长任务建议补 steps(update_task 传 steps),否则中断后续做只能靠回忆。"
55
+ : "";
56
+ return `✅ 任务 ${id}「${args.title}」已创建(${args.type ?? "feature"}/${args.milestone ?? "无里程碑"})。${tip}`;
57
+ });
58
+ tool(server, "list_tasks", "列出任务(默认不含 done/cancelled)。可按状态/类型/里程碑/标签过滤。", {
59
+ status: z.enum(["backlog", "todo", "in_progress", "blocked", "done", "cancelled"]).optional(),
60
+ type: z.enum(["feature", "refactor", "fix", "chore", "debt"]).optional(),
61
+ milestone: z.string().optional(),
62
+ tag: z.string().optional(),
63
+ include_done: z.boolean().optional().describe("默认 false"),
64
+ }, (args) => {
65
+ requireInitialized(root);
66
+ const { tasks } = loadTasks(root);
67
+ let list = tasks;
68
+ if (args.status)
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 整体替换步骤清单。", {
95
+ id: z.string(),
96
+ status: z.enum(["backlog", "todo", "in_progress", "blocked", "done", "cancelled"]).optional(),
97
+ title: z.string().optional(),
98
+ detail: z.string().optional(),
99
+ priority: z.enum(["P0", "P1", "P2", "P3"]).nullable().optional(),
100
+ milestone: z.string().nullable().optional(),
101
+ tags: z.array(z.string()).optional(),
102
+ files: z.array(z.string()).optional(),
103
+ acceptance: z.string().optional(),
104
+ result_note: z.string().optional().describe("完成笔记(转 done 必填)"),
105
+ verification: z.string().optional().describe("怎么验证的:命令/测试名"),
106
+ steps: z.array(stepSchema).optional().describe("整体替换步骤清单"),
107
+ step_done: z.number().int().min(1).optional().describe("勾选完成第 N 步"),
108
+ author: z.string().optional(),
109
+ }, (args) => {
110
+ requireInitialized(root);
111
+ const data = loadTasks(root);
112
+ const t = data.tasks.find((x) => x.id === args.id);
113
+ if (!t)
114
+ throw new Error(`找不到任务 ${args.id}。`);
115
+ const tnow = now();
116
+ if (args.title !== undefined)
117
+ t.title = args.title;
118
+ if (args.detail !== undefined)
119
+ t.detail = args.detail;
120
+ if (args.priority !== undefined)
121
+ t.priority = args.priority ?? null;
122
+ if (args.milestone !== undefined) {
123
+ if (args.milestone) {
124
+ const roadmap = loadRoadmap(root);
125
+ if (!roadmap.milestones.some((m) => m.id === args.milestone)) {
126
+ throw new Error(`里程碑 ${args.milestone} 不存在。`);
127
+ }
128
+ }
129
+ t.milestone = args.milestone ?? null;
130
+ }
131
+ if (args.tags !== undefined)
132
+ t.tags = args.tags;
133
+ if (args.files !== undefined)
134
+ t.files = args.files.map(normSep);
135
+ if (args.acceptance !== undefined)
136
+ t.acceptance = args.acceptance;
137
+ if (args.result_note !== undefined)
138
+ t.result_note = args.result_note;
139
+ if (args.verification !== undefined)
140
+ t.verification = args.verification;
141
+ if (args.author !== undefined)
142
+ t.author = args.author;
143
+ if (args.steps !== undefined)
144
+ t.steps = args.steps.map((s) => ({ text: s.text, done: s.done ?? false }));
145
+ if (args.step_done !== undefined) {
146
+ const s = t.steps[args.step_done - 1];
147
+ if (!s)
148
+ throw new Error(`步骤 ${args.step_done} 不存在(共 ${t.steps.length} 步)。`);
149
+ s.done = true;
150
+ }
151
+ const notes = [];
152
+ if (args.status !== undefined && args.status !== t.status) {
153
+ if (args.status === "done") {
154
+ const note = args.result_note !== undefined ? args.result_note : t.result_note;
155
+ if (!note.trim()) {
156
+ throw new Error(`转 done 必须填 result_note(完成笔记)——「声称做完」要有交代。当前任务 ${t.id} 缺少。`);
157
+ }
158
+ if (note.trim().length < 4) {
159
+ throw new Error(`result_note 过于空洞(「${note.trim()}」):完成笔记至少要说明做了什么(4 字以上),别拿一个字糊弄账本。`);
160
+ }
161
+ 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
+ }
168
+ if (t.status === "done" && args.status !== "done")
169
+ t.completed_at = null;
170
+ if (args.status === "in_progress" && !t.started_at)
171
+ t.started_at = tnow;
172
+ t.status = args.status;
173
+ }
174
+ t.updated = tnow;
175
+ saveTasks(root, data);
176
+ refreshDerived(root);
177
+ const stepsInfo = t.steps.length > 0 ? `,步骤 ${t.steps.filter((s) => s.done).length}/${t.steps.length}` : "";
178
+ return [`✅ ${t.id} 已更新(${t.status}${stepsInfo})。`, ...notes].join("\n");
179
+ });
180
+ toolW(server, root, "checkpoint", "断点存档:长任务做到一半(或上下文快满)时随手保存「当前进度 + 下一步动作」。新会话 get_status 会直接给出恢复点,不用靠回忆。", {
181
+ task_id: z.string(),
182
+ note: z.string().describe("当前进展到哪了"),
183
+ next_step: z.string().describe("下一个具体动作(新会话从这里续做)"),
184
+ }, (args) => {
185
+ requireInitialized(root);
186
+ const data = loadTasks(root);
187
+ const t = data.tasks.find((x) => x.id === args.task_id);
188
+ if (!t)
189
+ throw new Error(`找不到任务 ${args.task_id}。`);
190
+ t.checkpoint = { note: args.note, next_step: args.next_step, at: now() };
191
+ if (t.status === "backlog" || t.status === "todo")
192
+ t.status = "in_progress";
193
+ t.updated = now();
194
+ saveTasks(root, data);
195
+ refreshDerived(root);
196
+ return `✅ 断点已存档(${t.id})。下次会话开工 get_status 会显示「下一步: ${args.next_step}」。`;
197
+ });
198
+ }
@@ -0,0 +1,32 @@
1
+ import { loadProject, withLedgerLock } from "./store.js";
2
+ export function tool(server, name, description, schema, fn) {
3
+ server.registerTool(name, { description, inputSchema: schema }, (async (args) => {
4
+ try {
5
+ const text = await fn(args);
6
+ return { content: [{ type: "text", text }] };
7
+ }
8
+ catch (error) {
9
+ return { content: [{ type: "text", text: `错误: ${error.message}` }], isError: true };
10
+ }
11
+ }));
12
+ }
13
+ /** 写工具必须使用同步 handler,保证跨进程账本锁覆盖完整读-改-写。 */
14
+ export function toolW(server, root, name, description, schema, fn) {
15
+ server.registerTool(name, { description, inputSchema: schema }, ((args) => {
16
+ try {
17
+ const text = withLedgerLock(root, () => fn(args));
18
+ return { content: [{ type: "text", text }] };
19
+ }
20
+ catch (error) {
21
+ return { content: [{ type: "text", text: `错误: ${error.message}` }], isError: true };
22
+ }
23
+ }));
24
+ }
25
+ export function budgetLines(root) {
26
+ try {
27
+ return loadProject(root).budgets.outputBudgetLines;
28
+ }
29
+ catch {
30
+ return 150;
31
+ }
32
+ }
package/dist/tools.js ADDED
@@ -0,0 +1,21 @@
1
+ import { registerAcceptanceTools } from "./acceptance-tools.js";
2
+ import { registerAuditTools, registerRegistryTools } from "./audit-tools.js";
3
+ import { registerGovernanceTools } from "./governance-tools.js";
4
+ import { registerDecisionTools, registerFeatureTools, registerSearchTools, registerSessionTools, } from "./knowledge-tools.js";
5
+ import { registerProjectTools, registerRoadmapTools } from "./project-tools.js";
6
+ import { registerTaskTools } from "./task-tools.js";
7
+ export { registerProjectTools, registerRoadmapTools, registerTaskTools, registerFeatureTools, registerDecisionTools, registerSessionTools, registerSearchTools, registerAuditTools, registerRegistryTools, };
8
+ /** Stable aggregate entrypoint used by the MCP server. */
9
+ export function registerAllTools(server, root) {
10
+ registerProjectTools(server, root);
11
+ registerRoadmapTools(server, root);
12
+ registerTaskTools(server, root);
13
+ registerFeatureTools(server, root);
14
+ registerDecisionTools(server, root);
15
+ registerSessionTools(server, root);
16
+ registerSearchTools(server, root);
17
+ registerAuditTools(server, root);
18
+ registerRegistryTools(server);
19
+ registerGovernanceTools(server, root);
20
+ registerAcceptanceTools(server, root);
21
+ }
package/dist/types.js ADDED
@@ -0,0 +1,208 @@
1
+ import { z } from "zod";
2
+ import { normSep } from "./budget.js";
3
+ export const now = () => new Date().toISOString();
4
+ /** 文件清单字段:统一 / 分隔(Windows 反斜杠入库即规范化),缺省空数组 */
5
+ const filesField = () => z.array(z.string()).default([]).transform((list) => list.map(normSep));
6
+ /* ---------------------------------- 项目 ---------------------------------- */
7
+ export const BudgetsSchema = z.object({
8
+ /** 单文件行数预算(超出进复杂度账) */
9
+ maxFileLoc: z.number().int().min(10).default(500),
10
+ /** 里程碑重构类任务(refactor+debt)最低占比 %,低于则告警"重构被挤出" */
11
+ refactorQuotaPct: z.number().min(0).max(100).default(20),
12
+ /** 单次会话波及文件数阈值(超出告警"波及面过大") */
13
+ sessionBlastRadius: z.number().int().min(1).default(15),
14
+ /** 读类工具输出行数硬预算(超出自动折叠) */
15
+ outputBudgetLines: z.number().int().min(20).default(150),
16
+ });
17
+ export const ExposureSchema = z.enum(["local", "network", "public"]);
18
+ export const ProjectSchema = z.object({
19
+ name: z.string().trim().min(1),
20
+ description: z.string().default(""),
21
+ stack: z.array(z.string()).default([]),
22
+ goals: z.array(z.string()).default([]),
23
+ /** 当前阶段,自由文本,如 "MVP 开发中" */
24
+ phase: z.string().default(""),
25
+ /** 登记的模块名(用于复杂度账的"未登记目录"检测) */
26
+ modules: z.array(z.string()).default([]),
27
+ /** 暴露面:local=本机工具 / network=内网服务 / public=公网服务,影响安全告警力度 */
28
+ exposure: ExposureSchema.default("local"),
29
+ /** 项目许可证(SPDX 或名称),用于依赖兼容性对账 */
30
+ license: z.string().default(""),
31
+ budgets: BudgetsSchema.default(BudgetsSchema.parse({})),
32
+ created: z.string(),
33
+ updated: z.string(),
34
+ });
35
+ /* --------------------------------- 里程碑 --------------------------------- */
36
+ export const MilestoneStatusSchema = z.enum(["planned", "active", "done", "paused"]);
37
+ export const MilestoneSchema = z.object({
38
+ id: z.string(),
39
+ title: z.string().trim().min(1),
40
+ goal: z.string().default(""),
41
+ status: MilestoneStatusSchema.default("planned"),
42
+ order: z.number().int().default(0),
43
+ created: z.string(),
44
+ updated: z.string(),
45
+ });
46
+ export const RoadmapFileSchema = z.object({
47
+ seq: z.number().int().default(0),
48
+ milestones: z.array(MilestoneSchema).default([]),
49
+ });
50
+ /* ---------------------------------- 任务 ---------------------------------- */
51
+ export const TaskTypeSchema = z.enum(["feature", "refactor", "fix", "chore", "debt"]);
52
+ export const TaskStatusSchema = z.enum([
53
+ "backlog",
54
+ "todo",
55
+ "in_progress",
56
+ "blocked",
57
+ "done",
58
+ "cancelled",
59
+ ]);
60
+ export const StepSchema = z.object({
61
+ text: z.string(),
62
+ done: z.boolean().default(false),
63
+ });
64
+ export const CheckpointSchema = z.object({
65
+ note: z.string(),
66
+ next_step: z.string(),
67
+ at: z.string(),
68
+ });
69
+ export const TaskSchema = z.object({
70
+ id: z.string(),
71
+ title: z.string().trim().min(1),
72
+ detail: z.string().default(""),
73
+ type: TaskTypeSchema.default("feature"),
74
+ status: TaskStatusSchema.default("backlog"),
75
+ priority: z.enum(["P0", "P1", "P2", "P3"]).nullable().default(null),
76
+ milestone: z.string().nullable().default(null),
77
+ /** 长任务显式步骤清单——断点续做的载体 */
78
+ steps: z.array(StepSchema).default([]),
79
+ checkpoint: CheckpointSchema.nullable().default(null),
80
+ files: filesField(),
81
+ acceptance: z.string().default(""),
82
+ result_note: z.string().default(""),
83
+ /** done 时"怎么证明":用什么命令/测试验证 */
84
+ verification: z.string().default(""),
85
+ tags: z.array(z.string()).default([]),
86
+ author: z.string().default(""),
87
+ created: z.string(),
88
+ updated: z.string(),
89
+ started_at: z.string().nullable().default(null),
90
+ completed_at: z.string().nullable().default(null),
91
+ });
92
+ export const TasksFileSchema = z.object({
93
+ seq: z.number().int().default(0),
94
+ tasks: z.array(TaskSchema).default([]),
95
+ });
96
+ /* ---------------------------------- 功能 ---------------------------------- */
97
+ export const FeatureStatusSchema = z.enum(["planned", "implemented", "deprecated"]);
98
+ export const FeatureSchema = z.object({
99
+ id: z.string(),
100
+ name: z.string().trim().min(1),
101
+ description: z.string().default(""),
102
+ module: z.string().default(""),
103
+ entry_files: filesField(),
104
+ test_files: filesField(),
105
+ status: FeatureStatusSchema.default("implemented"),
106
+ created: z.string(),
107
+ updated: z.string(),
108
+ });
109
+ export const FeaturesFileSchema = z.object({
110
+ seq: z.number().int().default(0),
111
+ features: z.array(FeatureSchema).default([]),
112
+ });
113
+ /* ---------------------------------- 会话 ---------------------------------- */
114
+ export const SessionSchema = z.object({
115
+ id: z.string(),
116
+ date: z.string(),
117
+ /** 谁做的:zcode / codex / human:张三 …… */
118
+ author: z.string().default(""),
119
+ summary: z.string().trim().min(1),
120
+ files: filesField(),
121
+ next_steps: z.array(z.string()).default([]),
122
+ });
123
+ export const SessionsFileSchema = z.object({
124
+ seq: z.number().int().default(0),
125
+ sessions: z.array(SessionSchema).default([]),
126
+ });
127
+ /* --------------------------------- 调试记录 --------------------------------- */
128
+ export const DebugEntrySchema = z.object({
129
+ id: z.string(),
130
+ date: z.string(),
131
+ author: z.string().default(""),
132
+ symptom: z.string(),
133
+ root_cause: z.string(),
134
+ fix: z.string(),
135
+ verified_how: z.string().default(""),
136
+ files: filesField(),
137
+ task_id: z.string().nullable().default(null),
138
+ });
139
+ export const DebugLogFileSchema = z.object({
140
+ seq: z.number().int().default(0),
141
+ entries: z.array(DebugEntrySchema).default([]),
142
+ });
143
+ /* -------------------------------- 文件用途索引 ------------------------------- */
144
+ export const FileNoteSchema = z.object({
145
+ purpose: z.string(),
146
+ /** 代码来源(如 Stack Overflow 链接、某仓库),provenance 用 */
147
+ source: z.string().default(""),
148
+ /** 该文件引用代码的许可证 */
149
+ license: z.string().default(""),
150
+ updated: z.string(),
151
+ });
152
+ export const FileNotesFileSchema = z.object({
153
+ notes: z.record(z.string(), FileNoteSchema).default({}),
154
+ });
155
+ /* --------------------------------- 安全体检 --------------------------------- */
156
+ export const SeveritySchema = z.enum(["high", "medium", "low"]);
157
+ export const FindingSchema = z.object({
158
+ id: z.string(),
159
+ rule: z.string(),
160
+ severity: SeveritySchema,
161
+ file: z.string(),
162
+ line: z.number().int(),
163
+ /** sha1(rule + 相对路径 + 命中内容),行号漂移不误报;绝不存命中明文 */
164
+ fingerprint: z.string(),
165
+ status: z.enum(["open", "fixed", "accepted"]).default("open"),
166
+ note: z.string().default(""),
167
+ first_seen: z.string(),
168
+ last_seen: z.string(),
169
+ });
170
+ export const SecurityFileSchema = z.object({
171
+ findings: z.array(FindingSchema).default([]),
172
+ last_scan: z.string().nullable().default(null),
173
+ });
174
+ /* ---------------------------------- 快照 ---------------------------------- */
175
+ export const SnapshotSchema = z.object({
176
+ taken_at: z.string(),
177
+ file: z.string(),
178
+ total_files: z.number().int(),
179
+ total_loc: z.number().int(),
180
+ test_files: z.number().int(),
181
+ skip_markers: z.number().int(),
182
+ /** 依赖清单(扫描时的直接依赖,供 audit 对比新增) */
183
+ deps: z.array(z.string()).default([]),
184
+ by_ext: z.array(z.object({ ext: z.string(), files: z.number().int(), loc: z.number().int() })).default([]),
185
+ top_dirs: z.array(z.object({ dir: z.string(), files: z.number().int(), loc: z.number().int() })).default([]),
186
+ largest_files: z.array(z.object({ path: z.string(), loc: z.number().int() })).default([]),
187
+ index_coverage_pct: z.number().default(0),
188
+ });
189
+ /* --------------------------------- 全局注册表 -------------------------------- */
190
+ export const RegistryEntrySchema = z.object({
191
+ name: z.string(),
192
+ root: z.string(),
193
+ last_seen: z.string(),
194
+ });
195
+ export const RegistrySchema = z.object({
196
+ projects: z.array(RegistryEntrySchema).default([]),
197
+ });
198
+ /* --------------------------- 用户自定义安全规则 ------------------------------ */
199
+ export const CustomRuleSchema = z.object({
200
+ id: z.string(),
201
+ severity: SeveritySchema.default("medium"),
202
+ pattern: z.string(),
203
+ message: z.string().default(""),
204
+ glob: z.string().optional(),
205
+ });
206
+ export const ExtraRulesFileSchema = z.object({
207
+ rules: z.array(CustomRuleSchema).default([]),
208
+ });