add-coder 0.3.16 → 0.3.19

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 (33) hide show
  1. package/dist/index.js +349 -62
  2. package/package.json +1 -1
  3. package/templates/.add-coder-src-hash.json +31 -24
  4. package/templates/adapters/claude/hooks/pre-tool-use.sh +6 -1
  5. package/templates/adapters/codex/hooks/pre-tool-use.sh +7 -1
  6. package/templates/adapters/qoder/hooks/pre-tool-use.sh +6 -1
  7. package/templates/adapters/trae/hooks/pre-tool-use.sh +7 -1
  8. package/templates/adapters/vscode/hooks/pre-tool-use.sh +6 -1
  9. package/templates/core/hooks/pre-tool-use.sh +7 -1
  10. package/templates/core/prisma/add.prisma +35 -0
  11. package/templates/core/rules/profiles/index.toml +12 -0
  12. package/templates/core/rules/profiles/machineserver-profile.md +22 -0
  13. package/templates/core/rules/profiles/webapp-profile.md +61 -0
  14. package/templates/core/rules/project_rules.md +6 -54
  15. package/templates/core/scripts/mcp-server/tools/audit.ts +23 -14
  16. package/templates/core/scripts/mcp-server/tools/context.ts +20 -2
  17. package/templates/core/scripts/mcp-server/tools/contract.ts +202 -0
  18. package/templates/core/scripts/mcp-server/tools/docs.ts +2 -2
  19. package/templates/core/scripts/mcp-server/tools/gateway/check_add_route_completeness.ts +2 -2
  20. package/templates/core/scripts/mcp-server/tools/gateway/check_add_route_status.ts +2 -2
  21. package/templates/core/scripts/mcp-server/tools/gateway/check_dps.ts +2 -2
  22. package/templates/core/scripts/mcp-server/tools/gateway/check_rahs.ts +2 -2
  23. package/templates/core/scripts/mcp-server/tools/gateway/check_spec_sync.ts +2 -2
  24. package/templates/core/scripts/mcp-server/tools/gateway/index.ts +2 -2
  25. package/templates/core/scripts/mcp-server/tools/hitl.ts +13 -5
  26. package/templates/core/scripts/mcp-server/tools/hook-event-report.ts +2 -2
  27. package/templates/core/scripts/mcp-server/tools/index.ts +29 -10
  28. package/templates/core/scripts/mcp-server/tools/plan.ts +5 -2
  29. package/templates/core/scripts/mcp-server/tools/quality.ts +2 -2
  30. package/templates/core/scripts/mcp-server/tools/registrar.ts +11 -0
  31. package/templates/core/scripts/mcp-server/tools/review.ts +2 -2
  32. package/templates/core/templates/collab-contract-template.md +203 -0
  33. package/templates/core/templates/collab-contract-template.schema.json +133 -0
@@ -0,0 +1,202 @@
1
+ import * as z from "zod/v4"
2
+ import type { ToolRegistrar } from "./registrar.js"
3
+ import { existsSync, readFileSync } from "fs"
4
+ import { join, basename } from "path"
5
+ import { textResponse, errorResponse } from "../shared/response.js"
6
+ import { readdirRecursive, PROJECT_ROOT, MAGIC_DIR } from "../shared/fs.js"
7
+ import { prisma } from "../shared/prisma.js"
8
+
9
+ const db = {
10
+ get collabContract() { return prisma.collabContract as unknown as Record<string, (...a: unknown[]) => unknown> },
11
+ get plan() { return prisma.planRecord as unknown as Record<string, (...a: unknown[]) => unknown> },
12
+ }
13
+
14
+ /** 契约文档结构化解析结果 */
15
+ interface ParsedContract {
16
+ participants: unknown[]
17
+ stages: unknown[]
18
+ fileBoundaries: unknown[]
19
+ dependencyGraph: string
20
+ abilityMatrix: unknown
21
+ completionCriteria: unknown
22
+ }
23
+
24
+ /** 从契约文档解析结构化字段(markdown 表格/JSON 块简化提取) */
25
+ function parseContractDoc(content: string): ParsedContract {
26
+ const participants: unknown[] = []
27
+ const stages: unknown[] = []
28
+ const fileBoundaries: unknown[] = []
29
+ let dependencyGraph = ""
30
+ const abilityMatrix: unknown = null
31
+ const completionCriteria: unknown = null
32
+
33
+ // participants(§二 参与者表)
34
+ const pMatch = content.match(/\| \*\*Lead Agent\*\*[\s\S]*?\n\n/)
35
+ if (pMatch) {
36
+ for (const line of pMatch[0].split("\n")) {
37
+ const m = line.match(/^\|\s*\*\*(.+?)\*\*\s*\|\s*(.+?)\s*\|\s*(.+?)\s*\|\s*(.+?)\s*\|$/)
38
+ if (m) participants.push({ role: m[1], platformEntity: m[2], boundPlan: m[3], responsibility: m[4] })
39
+ }
40
+ }
41
+
42
+ // stages(§3.1 触发条件表)
43
+ const sMatch = content.match(/\| 阶段 \| 专家 \| 触发条件 \| 并行度 \|[\s\S]*?\n\n/)
44
+ if (sMatch) {
45
+ for (const line of sMatch[0].split("\n")) {
46
+ const m = line.match(/^\|\s*([A-Za-z0-9-]+)\s*\|\s*(.+?)\s*\|\s*(.+?)\s*\|\s*([并行串行]+)\s*\|$/)
47
+ if (m) stages.push({ stage: m[1], expert: m[2], trigger: m[3], parallelism: m[4] === "并行" ? "parallel" : "serial" })
48
+ }
49
+ }
50
+
51
+ // fileBoundaries(§3.2 文件边界表)
52
+ const bMatch = content.match(/\| 专家 \| 独占文件域 \| 禁区 \|[\s\S]*?\n\n/)
53
+ if (bMatch) {
54
+ for (const line of bMatch[0].split("\n")) {
55
+ const m = line.match(/^\|\s*(.+?)\s*\|\s*(.+?)\s*\|\s*(.+?)\s*\|$/)
56
+ if (m && !m[1].startsWith("---")) fileBoundaries.push({ expert: m[1], exclusiveDomain: m[2], forbidden: m[3], isolationMode: "file" })
57
+ }
58
+ }
59
+
60
+ // dependencyGraph(§3.1.1 代码块)
61
+ const gMatch = content.match(/```\n(依赖:[\s\S]*?)\n```/)
62
+ if (gMatch) dependencyGraph = gMatch[1]
63
+
64
+ return { participants, stages, fileBoundaries, dependencyGraph, abilityMatrix, completionCriteria }
65
+ }
66
+
67
+ export function registerContractTools(server: ToolRegistrar) {
68
+
69
+ // ===== contract_track =====
70
+ server.registerTool("contract_track", {
71
+ description: "契约追踪:扫描 plans/ 目录的 *-collab-contract-*.md 文档,解析参与者/阶段/文件边界,写入 CollabContract 表。按 contractName 去重,已存在则增量更新版本。",
72
+ inputSchema: z.object({
73
+ contractName: z.string().optional().describe("指定契约名称扫描单个;不传则扫描全部"),
74
+ scanAll: z.boolean().optional().describe("true 扫描全部契约"),
75
+ }),
76
+ }, async (args: Record<string, unknown>) => {
77
+ try {
78
+ const { contractName, scanAll: _scanAll } = args as { contractName?: string; scanAll?: boolean }
79
+ const plansDir = join(PROJECT_ROOT, MAGIC_DIR, "plans")
80
+ if (!existsSync(plansDir)) return errorResponse(`plans 目录不存在: ${plansDir}`)
81
+
82
+ const allFiles = await readdirRecursive(plansDir)
83
+ // 契约文档命名:*-collab-contract-*.md
84
+ // 严格过滤:排除 -plan- / add-route / handoff / .hitl(防止 Plan 体系文档被误扫为契约)
85
+ let contractFiles = allFiles.filter(f =>
86
+ f.endsWith(".md") &&
87
+ !f.endsWith(".hitl.md") &&
88
+ f.includes("-collab-contract-") &&
89
+ !f.includes("-plan-") &&
90
+ !f.includes("add-route") &&
91
+ !f.includes("handoff"),
92
+ )
93
+ if (contractName) {
94
+ const m = contractFiles.find(f => f.includes(contractName))
95
+ contractFiles = m ? [m] : []
96
+ }
97
+
98
+ if (contractFiles.length === 0) {
99
+ return textResponse(`=== 并发协作契约追踪 ===\n\n未找到契约文档(条件: ${contractName || "全部"})。\n\n扫描规则: plans/ 下 *-collab-contract-*.md 文件(排除 -plan-/add-route/handoff/.hitl)`)
100
+ }
101
+
102
+ const results: string[] = []
103
+ for (const f of contractFiles) {
104
+ const name = basename(f, ".md")
105
+ const path = join(plansDir, f)
106
+ const content = readFileSync(path, "utf-8")
107
+
108
+ // 提取 masterPlan(元信息中"总控 Plan:" 行)
109
+ const mpMatch = content.match(/总控 Plan:\s*`?([^`\n]+)`?/)
110
+ const masterPlanName = mpMatch ? mpMatch[1].trim().replace(/\.md$/, "") : ""
111
+
112
+ const parsed = parseContractDoc(content)
113
+ // P3 #6 健壮性:解析结果为空时告警(表头可能不匹配模板),并跳过该文件
114
+ if (parsed.stages.length === 0 || parsed.fileBoundaries.length === 0) {
115
+ console.warn(`[contract_track] 解析结果为空——表头可能不匹配模板(${f})`)
116
+ results.push(`⚠️ ${name} 解析为空(stages=${parsed.stages.length}, fileBoundaries=${parsed.fileBoundaries.length}),跳过——检查表头是否与模板一致`)
117
+ continue
118
+ }
119
+ // 契约文档必须声明总控 Plan
120
+ if (!masterPlanName) {
121
+ console.warn(`[contract_track] 缺少「总控 Plan:」声明(${f})`)
122
+ results.push(`⚠️ ${name} 缺少「总控 Plan:」声明,跳过`)
123
+ continue
124
+ }
125
+ const existing = await db.collabContract.findFirst({ where: { contractName: name } }) as Record<string, unknown> | null
126
+ const version = (existing?.version as number ?? 0) + 1
127
+
128
+ const data = {
129
+ contractName: name,
130
+ contractPath: path,
131
+ masterPlanName,
132
+ participants: parsed.participants as never,
133
+ abilityMatrix: parsed.abilityMatrix as never,
134
+ stages: parsed.stages as never,
135
+ dependencyGraph: parsed.dependencyGraph,
136
+ fileBoundaries: parsed.fileBoundaries as never,
137
+ completionCriteria: parsed.completionCriteria as never,
138
+ status: "ACTIVE",
139
+ version,
140
+ }
141
+
142
+ if (existing) {
143
+ await db.collabContract.update({ where: { contractName: name }, data })
144
+ results.push(`📄 ${name} ✅ 已更新 (v${version})`)
145
+ } else {
146
+ await db.collabContract.create({ data })
147
+ results.push(`📄 ${name} ✅ 已创建 (v${version})`)
148
+ }
149
+
150
+ // 若 masterPlan 存在,标记 PlanRecord 角色
151
+ if (masterPlanName) {
152
+ await db.plan.updateMany({
153
+ where: { planName: masterPlanName },
154
+ data: { contractRole: "MASTER", contractName: name } as never,
155
+ })
156
+ }
157
+ }
158
+
159
+ return textResponse(`=== 并发协作契约追踪 ===\n${results.join("\n")}\n\n💡 新契约建议: create_hitl({ planName: "${basename(contractFiles[0], ".md")}", type: "PLAN" }) 走契约审批`)
160
+ } catch (error) {
161
+ return errorResponse(`contract_track 失败: ${error instanceof Error ? error.message : String(error)}`)
162
+ }
163
+ })
164
+
165
+ // ===== contract_status =====
166
+ server.registerTool("contract_status", {
167
+ description: "契约进度查询:返回 CollabContract 记录(版本/状态/参与者数/阶段数/边界数 + masterPlan)。",
168
+ inputSchema: z.object({
169
+ contractName: z.string().describe("契约名称(如 htc-g13-extra-time-quest-collab-contract-v1)"),
170
+ }),
171
+ }, async (args: Record<string, unknown>) => {
172
+ try {
173
+ const { contractName } = args as { contractName: string }
174
+ const contract = await db.collabContract.findFirst({ where: { contractName } }) as Record<string, unknown> | null
175
+ if (!contract) {
176
+ return textResponse(`📋 契约: 未跟踪\ncontractName: ${contractName}\n\n操作: contract_track({ contractName: "${contractName}" })`)
177
+ }
178
+ const participants = (contract.participants as unknown[]) || []
179
+ const stages = (contract.stages as unknown[]) || []
180
+ const boundaries = (contract.fileBoundaries as unknown[]) || []
181
+ const parallelCount = stages.filter((s) => (s as Record<string, unknown>).parallelism === "parallel").length
182
+ const serialCount = stages.filter((s) => (s as Record<string, unknown>).parallelism === "serial").length
183
+ const versionStr = String(contract.version as number ?? "?")
184
+ const statusStr = String(contract.status as string ?? "?")
185
+ const masterStr = String(contract.masterPlanName as string ?? "(未绑定)")
186
+ const depStr = contract.dependencyGraph ? "已声明" : "未声明"
187
+ const pathStr = String(contract.contractPath as string ?? "(无)")
188
+ return textResponse(
189
+ `📊 ${contractName}\n` +
190
+ `版本: v${versionStr} (${statusStr})\n` +
191
+ `MasterPlan: ${masterStr}\n` +
192
+ `参与者: ${participants.length} 个\n` +
193
+ `阶段: ${stages.length} 个(并行 ${parallelCount} / 串行 ${serialCount})\n` +
194
+ `文件边界: ${boundaries.length} 个\n` +
195
+ `依赖拓扑: ${depStr}\n` +
196
+ `文档: ${pathStr}`,
197
+ )
198
+ } catch (error) {
199
+ return errorResponse(`contract_status 失败: ${error instanceof Error ? error.message : String(error)}`)
200
+ }
201
+ })
202
+ }
@@ -1,5 +1,5 @@
1
1
  import * as z from "zod/v4"
2
- import type { McpServer } from "@modelcontextprotocol/server"
2
+ import type { ToolRegistrar } from "./registrar.js"
3
3
  import { readdir } from "fs/promises"
4
4
  import { existsSync } from "fs"
5
5
  import { join } from "path"
@@ -7,7 +7,7 @@ import { textResponse, errorResponse } from "../shared/response.js"
7
7
  import { readFileSafe } from "../shared/fs.js"
8
8
  import { PROJECT_ROOT, MAGIC_DIR } from "../shared/fs.js"
9
9
 
10
- export function registerDocsTools(server: McpServer) {
10
+ export function registerDocsTools(server: ToolRegistrar) {
11
11
 
12
12
  server.registerTool("find_related_docs", {
13
13
  description: `搜索与当前变更相关的项目文档(ADD-0.1 广义文档先行)。搜索范围:\n1. docs/ 目录 — 需求文档、架构文档、规范文档\n2. ${MAGIC_DIR}/ 产物 — plans/(Plan+add-route+handoff)、specs/(三元组)、reviews/(方案审查+实现审查+运行时审查)\n\nAI 助手在 ADD 范式 Step 0 中应调用此工具查找需要更新的 docs/ 文档。`,
@@ -7,7 +7,7 @@
7
7
  * @Description : ADD 范式守卫工具:扫描 add-route 文件的 Step 完成度
8
8
  */
9
9
  import * as z from "zod/v4";
10
- import type { McpServer } from "@modelcontextprotocol/server";
10
+ import type { ToolRegistrar } from "../registrar.js";
11
11
  import { existsSync } from "fs";
12
12
  import { join } from "path";
13
13
  import { textResponse, errorResponse } from "../../shared/response.js";
@@ -18,7 +18,7 @@ import {
18
18
  MAGIC_DIR,
19
19
  } from "../../shared/fs.js";
20
20
 
21
- export function registerCheckAddRouteCompleteness(server: McpServer) {
21
+ export function registerCheckAddRouteCompleteness(server: ToolRegistrar) {
22
22
  server.registerTool(
23
23
  "check_add_route_completeness",
24
24
  {
@@ -7,7 +7,7 @@
7
7
  * @Description : ADD 范式守卫工具:交叉校验 add-route 文件
8
8
  */
9
9
  import * as z from "zod/v4";
10
- import type { McpServer } from "@modelcontextprotocol/server";
10
+ import type { ToolRegistrar } from "../registrar.js";
11
11
  import { join } from "path";
12
12
  import { textResponse, errorResponse } from "../../shared/response.js";
13
13
  import {
@@ -60,7 +60,7 @@ function scanCheckboxes(content: string) {
60
60
  };
61
61
  }
62
62
 
63
- export function registerCheckAddRouteStatus(server: McpServer) {
63
+ export function registerCheckAddRouteStatus(server: ToolRegistrar) {
64
64
  server.registerTool(
65
65
  "check_add_route_status",
66
66
  {
@@ -7,7 +7,7 @@
7
7
  * @Description : DPS 闸门 — 四维复合评分 + FFT 自适应权重
8
8
  */
9
9
  import * as z from "zod/v4";
10
- import type { McpServer } from "@modelcontextprotocol/server";
10
+ import type { ToolRegistrar } from "../registrar.js";
11
11
  import { existsSync } from "fs";
12
12
  import { join, basename } from "path";
13
13
  import { textResponse, errorResponse } from "../../shared/response.js";
@@ -29,7 +29,7 @@ import {
29
29
  getEmbeddings,
30
30
  } from "./helpers.js";
31
31
 
32
- export function registerCheckDps(server: McpServer) {
32
+ export function registerCheckDps(server: ToolRegistrar) {
33
33
  server.registerTool(
34
34
  "check_dps",
35
35
  {
@@ -7,7 +7,7 @@
7
7
  * @Description : RAHS 闸门(Runtime Architecture Health Score)
8
8
  */
9
9
  import * as z from "zod/v4";
10
- import type { McpServer } from "@modelcontextprotocol/server";
10
+ import type { ToolRegistrar } from "../registrar.js";
11
11
  import { existsSync } from "fs";
12
12
  import { join } from "path";
13
13
  import { textResponse, errorResponse } from "../../shared/response.js";
@@ -19,7 +19,7 @@ import {
19
19
  } from "../../shared/fs.js";
20
20
  import { prisma } from "../../shared/prisma.js";
21
21
 
22
- export function registerCheckRahs(server: McpServer) {
22
+ export function registerCheckRahs(server: ToolRegistrar) {
23
23
  server.registerTool(
24
24
  "check_rahs",
25
25
  {
@@ -8,7 +8,7 @@
8
8
  * tasks.md/checklist.md 进度由 plan_track 在 PlanRecord 中维护,此处不再重复扫描。
9
9
  */
10
10
  import * as z from "zod/v4";
11
- import type { McpServer } from "@modelcontextprotocol/server";
11
+ import type { ToolRegistrar } from "../registrar.js";
12
12
  import { existsSync } from "fs";
13
13
  import { join, basename } from "path";
14
14
  import { textResponse, errorResponse } from "../../shared/response.js";
@@ -19,7 +19,7 @@ import {
19
19
  MAGIC_DIR,
20
20
  } from "../../shared/fs.js";
21
21
 
22
- export function registerCheckSpecSync(server: McpServer) {
22
+ export function registerCheckSpecSync(server: ToolRegistrar) {
23
23
  server.registerTool(
24
24
  "check_spec_sync",
25
25
  {
@@ -1,13 +1,13 @@
1
1
  // gateway/ 子模块拆分入口
2
2
  // 各工具文件独立注册,由 registerGatewayTools 统一聚合并导出
3
- import type { McpServer } from "@modelcontextprotocol/server";
3
+ import type { ToolRegistrar } from "../registrar.js";
4
4
  import { registerCheckAddRouteStatus } from "./check_add_route_status.js";
5
5
  import { registerCheckSpecSync } from "./check_spec_sync.js";
6
6
  import { registerCheckAddRouteCompleteness } from "./check_add_route_completeness.js";
7
7
  import { registerCheckDps } from "./check_dps.js";
8
8
  import { registerCheckRahs } from "./check_rahs.js";
9
9
 
10
- export function registerGatewayTools(server: McpServer) {
10
+ export function registerGatewayTools(server: ToolRegistrar) {
11
11
  registerCheckAddRouteStatus(server);
12
12
  registerCheckSpecSync(server);
13
13
  registerCheckAddRouteCompleteness(server);
@@ -1,5 +1,6 @@
1
1
  import * as z from "zod/v4"
2
- import { inputRequired, acceptedContent, type McpServer } from "@modelcontextprotocol/server"
2
+ import { inputRequired, acceptedContent } from "@modelcontextprotocol/server"
3
+ import type { ToolRegistrar } from "./registrar.js"
3
4
  import { writeFileSync, mkdirSync, existsSync, readFileSync } from "fs"
4
5
  import { join, basename } from "path"
5
6
  import { textResponse, errorResponse } from "../shared/response.js"
@@ -11,7 +12,7 @@ const db = {
11
12
  get hitl() { return prisma.hitlRecord as unknown as Record<string, (...a: unknown[]) => unknown> },
12
13
  }
13
14
 
14
- export function registerHitlTools(server: McpServer) {
15
+ export function registerHitlTools(server: ToolRegistrar) {
15
16
 
16
17
  // ═══════════════ 辅助:按安装环境裁决交互模式(caijuehub: hitl-interaction-rules.toml) ═══════════════
17
18
  const _interaction = (() => {
@@ -102,7 +103,7 @@ export function registerHitlTools(server: McpServer) {
102
103
  "type: PLAN=计划审批, PLAN_REVIEW=方案评审",
103
104
  inputSchema: z.object({
104
105
  planName: z.string().describe("Plan 名称,不含 .md 后缀"),
105
- type: z.enum(["PLAN", "PLAN_REVIEW"]).describe("审批类型"),
106
+ type: z.enum(["PLAN", "PLAN_REVIEW", "COLLAB_CONTRACT"]).describe("审批类型"),
106
107
  dimensions: z.array(z.object({
107
108
  name: z.string().describe("维度名称,如「实施主体」「数据模型」"),
108
109
  content: z.string().optional().describe("LLM 建议的方案内容"),
@@ -287,7 +288,7 @@ export function registerHitlTools(server: McpServer) {
287
288
  "已终态(TONGYI/BOHUI)不可再更新,BOHUI 后需 create_hitl 新建 round。",
288
289
  inputSchema: z.object({
289
290
  planName: z.string().describe("Plan 名称"),
290
- type: z.enum(["PLAN", "PLAN_REVIEW"]).describe("审批类型"),
291
+ type: z.enum(["PLAN", "PLAN_REVIEW", "COLLAB_CONTRACT"]).describe("审批类型"),
291
292
  status: z.enum(["SUBMITTED", "TONGYI", "BOHUI"]).optional().describe("降级模式(_fallback)或 genui 模式(_use_genui)必填:目标状态"),
292
293
  reason: z.string().optional().describe("驳回原因(降级模式手动传,genui 模式从 widget 回调获取)"),
293
294
  _fallback: z.boolean().optional().default(false).describe("降级模式:跳过 inputRequired,按原始代码行为直接写哨兵+更新 DB"),
@@ -419,6 +420,13 @@ export function registerHitlTools(server: McpServer) {
419
420
  ].filter(Boolean).join("\n") + "\n"
420
421
  for (const n of markerNames) writeFileSync(join(markerDir, markerPrefix + n), markerContent, "utf-8")
421
422
  const marker = markerNames.map(n => join(markerDir, markerPrefix + n)).join(", ")
423
+ // P3 #6:回写 .hitl.md 提案文件状态(DRAFT → TONGYI/BOHUI),保证双通道校验一致
424
+ const proposalPath = findHitlFile(String(planName))
425
+ if (proposalPath) {
426
+ let proposal = readFileSync(proposalPath, "utf-8")
427
+ proposal = proposal.replace(/(状态:\s*)[A-Z_]+/, `$1${s}`)
428
+ writeFileSync(proposalPath, proposal, "utf-8")
429
+ }
422
430
  // 响应
423
431
  const lines = [
424
432
  `✅ update_hitl`,
@@ -445,7 +453,7 @@ export function registerHitlTools(server: McpServer) {
445
453
  description: "HITL 审批:查询状态。返回最新 round 的状态、时间戳、拒绝原因等。与 .hitl-tongyi-{planName} 哨兵文件构成双通道校验。\n未发起时返回提示调用 create_hitl。",
446
454
  inputSchema: z.object({
447
455
  planName: z.string().describe("Plan 名称"),
448
- type: z.enum(["PLAN", "PLAN_REVIEW"]).describe("审批类型(默认 PLAN)").default("PLAN"),
456
+ type: z.enum(["PLAN", "PLAN_REVIEW", "COLLAB_CONTRACT"]).describe("审批类型(默认 PLAN)").default("PLAN"),
449
457
  }),
450
458
  }, async (args: Record<string, unknown>) => {
451
459
  try {
@@ -1,9 +1,9 @@
1
1
  import * as z from "zod/v4"
2
- import type { McpServer } from "@modelcontextprotocol/server"
2
+ import type { ToolRegistrar } from "./registrar.js"
3
3
  import { textResponse, errorResponse } from "../shared/response.js"
4
4
  import { prisma } from "../shared/prisma.js"
5
5
 
6
- export function registerHookEventTools(server: McpServer) {
6
+ export function registerHookEventTools(server: ToolRegistrar) {
7
7
 
8
8
  server.registerTool("get_hook_events", {
9
9
  description:
@@ -1,6 +1,7 @@
1
1
  import type { McpServer } from "@modelcontextprotocol/server"
2
2
  import { registerContextTools } from "./context.js"
3
3
  import { registerAuditTools } from "./audit.js"
4
+ import { registerContractTools } from "./contract.js"
4
5
  import { registerDocsTools } from "./docs.js"
5
6
  import { registerQualityTools } from "./quality.js"
6
7
  import { registerGatewayTools } from "./gateway/index.js"
@@ -8,16 +9,34 @@ import { registerHookEventTools } from "./hook-event-report.js"
8
9
  import { registerHitlTools } from "./hitl.js"
9
10
  import { registerPlanTools } from "./plan.js"
10
11
  import { registerReviewTools } from "./review.js"
12
+ import { PROJECT_ID } from "../shared/env.js"
13
+ import type { ToolRegistrar } from "./registrar.js"
11
14
 
12
15
  export function registerAllTools(server: McpServer) {
13
- registerContextTools(server) // 5 tools
14
- registerAuditTools(server) // 2 tools
15
- registerDocsTools(server) // 1 tool
16
- registerQualityTools(server) // 4 tools
17
- registerGatewayTools(server) // 5 tools
18
- registerHookEventTools(server) // 1 tool: get_hook_events
19
- registerHitlTools(server) // 3 tools: create_hitl / update_hitl / status_hitl
20
- registerPlanTools(server) // 3 tools: plan_track / plan_status / plan_sync
21
- registerReviewTools(server) // 3 tools: review_track / review_status / review_sync
22
- // Total: 27 tools
16
+ // D9 多 MCP 路由安全:派生装饰版 registrar,为所有工具 description 注入项目身份前缀
17
+ const origRegister = server.registerTool.bind(server)
18
+ type RegisterTool = ToolRegistrar["registerTool"]
19
+ type ToolConfig = Parameters<RegisterTool>[1]
20
+ type ToolCb = Parameters<RegisterTool>[2]
21
+ const registerTool = ((name: string, config: ToolConfig, cb: ToolCb) => {
22
+ const c = config as { description?: string }
23
+ return origRegister(
24
+ name,
25
+ { ...config, description: `[项目: ${PROJECT_ID}] ${c.description ?? ""}` },
26
+ cb,
27
+ )
28
+ }) as RegisterTool
29
+ const registrar: ToolRegistrar = { registerTool }
30
+
31
+ registerContextTools(registrar) // 5 tools
32
+ registerAuditTools(registrar) // 2 tools
33
+ registerContractTools(registrar) // 2 tools: contract_track / contract_status
34
+ registerDocsTools(registrar) // 1 tool
35
+ registerQualityTools(registrar) // 4 tools
36
+ registerGatewayTools(registrar) // 5 tools
37
+ registerHookEventTools(registrar) // 1 tool: get_hook_events
38
+ registerHitlTools(registrar) // 3 tools: create_hitl / update_hitl / status_hitl
39
+ registerPlanTools(registrar) // 3 tools: plan_track / plan_status / plan_sync
40
+ registerReviewTools(registrar) // 3 tools: review_track / review_status / review_sync
41
+ // Total: 29 tools
23
42
  }
@@ -1,5 +1,5 @@
1
1
  import * as z from "zod/v4"
2
- import type { McpServer } from "@modelcontextprotocol/server"
2
+ import type { ToolRegistrar } from "./registrar.js"
3
3
  import { existsSync, readFileSync, writeFileSync } from "fs"
4
4
  import { join, basename } from "path"
5
5
  import { textResponse, errorResponse } from "../shared/response.js"
@@ -10,7 +10,7 @@ const db = {
10
10
  get plan() { return prisma.planRecord as unknown as Record<string, (...a: unknown[]) => unknown> },
11
11
  }
12
12
 
13
- export function registerPlanTools(server: McpServer) {
13
+ export function registerPlanTools(server: ToolRegistrar) {
14
14
 
15
15
  // ===== plan_track =====
16
16
  server.registerTool("plan_track", {
@@ -136,6 +136,9 @@ export function registerPlanTools(server: McpServer) {
136
136
  `planKeyword: ${plan.planKeyword || "—"}`,
137
137
  plan.specPath ? `spec: ${plan.specPath}` : null,
138
138
  plan.addRoutePath ? `addRoute: ${plan.addRoutePath}` : null,
139
+ // P3 #5 契约角色展示(PlanRecord 已设置时)
140
+ plan.contractRole ? `contractRole: ${plan.contractRole}` : null,
141
+ plan.contractName ? `contractName: ${plan.contractName}` : null,
139
142
  ].filter(Boolean)
140
143
  return textResponse(lines.join("\n"))
141
144
  } catch (e) {
@@ -1,8 +1,8 @@
1
1
  import * as z from "zod/v4"
2
- import type { McpServer } from "@modelcontextprotocol/server"
2
+ import type { ToolRegistrar } from "./registrar.js"
3
3
  import { textResponse, errorResponse } from "../shared/response.js"
4
4
 
5
- export function registerQualityTools(server: McpServer) {
5
+ export function registerQualityTools(server: ToolRegistrar) {
6
6
 
7
7
  // ===== check_phase_symmetry (L615-679) =====
8
8
  server.registerTool("check_phase_symmetry", {
@@ -0,0 +1,11 @@
1
+ import type { McpServer } from "@modelcontextprotocol/server"
2
+
3
+ /**
4
+ * 工具注册器最小契约(基类接口)。
5
+ *
6
+ * 所有 registerXxxTools 只依赖 registerTool 一个成员,而非整个 McpServer:
7
+ * - 各模块注册函数 = 派生实现,签名收敛到最小依赖
8
+ * - registerAllTools 可注入装饰版 registrar(如 D9 项目身份前缀),
9
+ * 无需伪造 McpServer 对象,类型安全
10
+ */
11
+ export type ToolRegistrar = Pick<McpServer, "registerTool">
@@ -1,5 +1,5 @@
1
1
  import * as z from "zod/v4"
2
- import type { McpServer } from "@modelcontextprotocol/server"
2
+ import type { ToolRegistrar } from "./registrar.js"
3
3
  import { existsSync, readFileSync, writeFileSync } from "fs"
4
4
  import { join, basename } from "path"
5
5
  import { textResponse, errorResponse } from "../shared/response.js"
@@ -30,7 +30,7 @@ function countIssueType(content: string): { p0: number; p1: number; backflow: nu
30
30
  return { p0, p1, backflow }
31
31
  }
32
32
 
33
- export function registerReviewTools(server: McpServer) {
33
+ export function registerReviewTools(server: ToolRegistrar) {
34
34
 
35
35
  // ===== review_track =====
36
36
  server.registerTool("review_track", {