add-coder 0.3.25 → 0.3.27

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 (132) hide show
  1. package/README.md +36 -10
  2. package/dist/index.js +481 -209
  3. package/package.json +14 -4
  4. package/templates/.add-coder-src-hash.json +129 -106
  5. package/templates/adapters/claude/hooks/doc-format-guard.sh +114 -33
  6. package/templates/adapters/claude/hooks/lib/common.sh +46 -87
  7. package/templates/adapters/claude/hooks/lib/context-inject.sh +2 -2
  8. package/templates/adapters/claude/hooks/lib/notify.sh +6 -4
  9. package/templates/adapters/claude/hooks/lib/preload-templates.sh +27 -2
  10. package/templates/adapters/claude/hooks/lib/state-detect.sh +8 -112
  11. package/templates/adapters/claude/hooks/lib/vocabulary.sh +13 -7
  12. package/templates/adapters/claude/hooks/prompt-submit.sh +1 -1
  13. package/templates/adapters/claude/hooks/stop-check.sh +9 -0
  14. package/templates/adapters/codex/config.toml.example +12 -0
  15. package/templates/adapters/codex/hooks/doc-format-guard.sh +123 -41
  16. package/templates/adapters/codex/hooks/lib/common.sh +47 -99
  17. package/templates/adapters/codex/hooks/lib/context-inject.sh +4 -4
  18. package/templates/adapters/codex/hooks/lib/notify.sh +6 -4
  19. package/templates/adapters/codex/hooks/lib/preload-templates.sh +27 -6
  20. package/templates/adapters/codex/hooks/lib/state-detect.sh +5 -114
  21. package/templates/adapters/codex/hooks/lib/vocabulary.sh +3 -8
  22. package/templates/adapters/codex/hooks/notification.sh +3 -18
  23. package/templates/adapters/codex/hooks/post-tool-use.sh +24 -119
  24. package/templates/adapters/codex/hooks/pre-tool-use.sh +97 -184
  25. package/templates/adapters/codex/hooks/prompt-submit.sh +8 -3
  26. package/templates/adapters/codex/hooks/session-start.sh +19 -9
  27. package/templates/adapters/codex/hooks/stop-check.sh +44 -18
  28. package/templates/adapters/codex/hooks.json +67 -1
  29. package/templates/adapters/qoder/hooks/doc-format-guard.sh +121 -40
  30. package/templates/adapters/qoder/hooks/lib/common.sh +49 -87
  31. package/templates/adapters/qoder/hooks/lib/context-inject.sh +2 -2
  32. package/templates/adapters/qoder/hooks/lib/notify.sh +6 -4
  33. package/templates/adapters/qoder/hooks/lib/preload-templates.sh +27 -2
  34. package/templates/adapters/qoder/hooks/lib/state-detect.sh +8 -112
  35. package/templates/adapters/qoder/hooks/lib/vocabulary.sh +13 -7
  36. package/templates/adapters/qoder/hooks/prompt-submit.sh +1 -1
  37. package/templates/adapters/qoder/hooks/session-start.sh +1 -1
  38. package/templates/adapters/qoder/hooks/stop-check.sh +11 -0
  39. package/templates/adapters/trae/hooks/doc-format-guard.sh +122 -40
  40. package/templates/adapters/trae/hooks/lib/common.sh +46 -87
  41. package/templates/adapters/trae/hooks/lib/context-inject.sh +2 -2
  42. package/templates/adapters/trae/hooks/lib/notify.sh +5 -3
  43. package/templates/adapters/trae/hooks/lib/preload-templates.sh +27 -2
  44. package/templates/adapters/trae/hooks/lib/state-detect.sh +8 -115
  45. package/templates/adapters/trae/hooks/lib/vocabulary.sh +13 -7
  46. package/templates/adapters/trae/hooks/pre-tool-use.sh +14 -8
  47. package/templates/adapters/trae/hooks/stop-check.sh +9 -0
  48. package/templates/adapters/vscode/hooks/doc-format-guard.sh +114 -33
  49. package/templates/adapters/vscode/hooks/lib/common.sh +46 -87
  50. package/templates/adapters/vscode/hooks/lib/context-inject.sh +2 -2
  51. package/templates/adapters/vscode/hooks/lib/notify.sh +6 -4
  52. package/templates/adapters/vscode/hooks/lib/preload-templates.sh +27 -2
  53. package/templates/adapters/vscode/hooks/lib/state-detect.sh +8 -112
  54. package/templates/adapters/vscode/hooks/lib/vocabulary.sh +13 -7
  55. package/templates/adapters/vscode/hooks/notification.sh +3 -5
  56. package/templates/adapters/vscode/hooks/prompt-submit.sh +1 -1
  57. package/templates/adapters/vscode/hooks/stop-check.sh +9 -0
  58. package/templates/core/docs/ADD-governance-claude-code.md +1 -1
  59. package/templates/core/docs/ADD-governance-codex.md +1 -1
  60. package/templates/core/docs/ADD-governance-qoder-cn.md +1 -1
  61. package/templates/core/docs/ADD-governance-trae.md +1 -1
  62. package/templates/core/docs/ADD-governance-vscode-copilot.md +1 -1
  63. package/templates/core/hooks/doc-format-guard.sh +122 -40
  64. package/templates/core/hooks/lib/common.sh +46 -87
  65. package/templates/core/hooks/lib/context-inject.sh +2 -2
  66. package/templates/core/hooks/lib/notify.sh +5 -3
  67. package/templates/core/hooks/lib/preload-templates.sh +27 -2
  68. package/templates/core/hooks/lib/state-detect.sh +8 -115
  69. package/templates/core/hooks/lib/vocabulary.sh +13 -7
  70. package/templates/core/hooks/pre-tool-use.sh +14 -8
  71. package/templates/core/hooks/stop-check.sh +9 -0
  72. package/templates/core/prisma/add.prisma +140 -82
  73. package/templates/core/rules/project_rules.md +27 -24
  74. package/templates/core/scripts/db-ensure.sh +8 -0
  75. package/templates/core/scripts/gen-plan-index.sh +4 -15
  76. package/templates/core/scripts/mcp-server/notifications/hitl.ts +9 -1
  77. package/templates/core/scripts/mcp-server/notifications/hook.ts +25 -9
  78. package/templates/core/scripts/mcp-server/resources/hitl-approval-widget.ts +34 -0
  79. package/templates/core/scripts/mcp-server/resources/hook-events-report.ts +14 -2
  80. package/templates/core/scripts/mcp-server/resources/index.ts +2 -0
  81. package/templates/core/scripts/mcp-server/sampling/review.ts +25 -10
  82. package/templates/core/scripts/mcp-server/shared/db-types.ts +33 -8
  83. package/templates/core/scripts/mcp-server/shared/dps-scoring.strategy.ts +1 -1
  84. package/templates/core/scripts/mcp-server/shared/env.ts +28 -8
  85. package/templates/core/scripts/mcp-server/shared/fs.ts +3 -0
  86. package/templates/core/scripts/mcp-server/shared/hitl-interaction.strategy.ts +2 -3
  87. package/templates/core/scripts/mcp-server/shared/hitl-lifecycle-mutation.ts +129 -0
  88. package/templates/core/scripts/mcp-server/shared/hitl-proposal-mutation.ts +98 -0
  89. package/templates/core/scripts/mcp-server/shared/hitl-ui.ts +3 -0
  90. package/templates/core/scripts/mcp-server/shared/plan-lifecycle-events.ts +62 -0
  91. package/templates/core/scripts/mcp-server/shared/plan-lifecycle-mutation.ts +78 -0
  92. package/templates/core/scripts/mcp-server/shared/plan-lifecycle-subscriber.ts +145 -0
  93. package/templates/core/scripts/mcp-server/shared/plan-lifecycle.ts +153 -0
  94. package/templates/core/scripts/mcp-server/shared/plan-round-events.ts +56 -0
  95. package/templates/core/scripts/mcp-server/shared/plan-round-mutation.ts +143 -0
  96. package/templates/core/scripts/mcp-server/shared/plan-round-store.ts +47 -0
  97. package/templates/core/scripts/mcp-server/shared/plan-round-subscriber.ts +99 -0
  98. package/templates/core/scripts/mcp-server/shared/plan-status-store.ts +37 -0
  99. package/templates/core/scripts/mcp-server/shared/plan-tracking-mutation.ts +71 -0
  100. package/templates/core/scripts/mcp-server/shared/prisma.ts +12 -6
  101. package/templates/core/scripts/mcp-server/shared/project-root-strategy.ts +1 -2
  102. package/templates/core/scripts/mcp-server/shared/quant-recheck.strategy.ts +16 -0
  103. package/templates/core/scripts/mcp-server/shared/redact.ts +28 -0
  104. package/templates/core/scripts/mcp-server/shared/response.ts +3 -2
  105. package/templates/core/scripts/mcp-server/shared/runtime-context.ts +77 -0
  106. package/templates/core/scripts/mcp-server/shared/schema-topology.ts +159 -0
  107. package/templates/core/scripts/mcp-server/tools/audit.ts +49 -7
  108. package/templates/core/scripts/mcp-server/tools/context.ts +117 -121
  109. package/templates/core/scripts/mcp-server/tools/contract.ts +34 -9
  110. package/templates/core/scripts/mcp-server/tools/gateway/check_add_route_status.ts +50 -3
  111. package/templates/core/scripts/mcp-server/tools/gateway/check_doc_similarity.ts +218 -0
  112. package/templates/core/scripts/mcp-server/tools/gateway/check_dps.ts +22 -7
  113. package/templates/core/scripts/mcp-server/tools/gateway/check_rahs.ts +75 -4
  114. package/templates/core/scripts/mcp-server/tools/gateway/check_spec_sync.ts +19 -6
  115. package/templates/core/scripts/mcp-server/tools/gateway/index.ts +2 -0
  116. package/templates/core/scripts/mcp-server/tools/hitl.ts +160 -75
  117. package/templates/core/scripts/mcp-server/tools/hook-event-report.ts +9 -2
  118. package/templates/core/scripts/mcp-server/tools/index.ts +69 -3
  119. package/templates/core/scripts/mcp-server/tools/plan.ts +140 -15
  120. package/templates/core/scripts/mcp-server/tools/review.ts +44 -15
  121. package/templates/core/scripts/mcp-server.ts +60 -1
  122. package/templates/core/scripts/plan-status-bridge.ts +26 -0
  123. package/templates/core/skills/add-paradigm/SKILL.md +2 -2
  124. package/templates/core/templates/add-route-template.schema.json +24 -56
  125. package/templates/core/templates/collab-contract-template.md +1 -1
  126. package/templates/core/templates/handoff-multi-round-template.schema.json +48 -0
  127. package/templates/core/templates/handoff-single-round-template.schema.json +92 -0
  128. package/templates/core/templates/handoff-single-round.schema.json +1 -1
  129. package/templates/core/templates/hitl-approval-widget.html +222 -37
  130. package/templates/core/templates/spec-template.schema.json +9 -46
  131. package/templates/core/templates/standard-plan-template.schema.json +1 -1
  132. package/templates/core/templates/tasks-template.schema.json +13 -54
@@ -6,13 +6,23 @@ import { stat } from "fs/promises"
6
6
  import { existsSync } from "fs"
7
7
  import { join, relative } from "path"
8
8
  import { textResponse, errorResponse } from "../shared/response.js"
9
- import { readFileSafe, readdirRecursive } from "../shared/fs.js"
9
+ import { readFileSafe } from "../shared/fs.js"
10
10
  import { PROJECT_ROOT, MAGIC_DIR } from "../shared/fs.js"
11
11
  import { prisma } from "../shared/prisma.js"
12
- import type { PlanRow } from "../shared/db-types.js"
13
- import { PlanRowSchema, validatedDelegate } from "../shared/db-types.js"
12
+ import type { PlanRow, ReviewRow } from "../shared/db-types.js"
13
+ import { PlanRowSchema, ReviewRowSchema, validatedDelegate } from "../shared/db-types.js"
14
+ import { getRuntimeContext } from "../shared/env.js"
15
+ import { resolvePlanStatus } from "../shared/plan-lifecycle.js"
16
+ import { createPrismaPlanStatusStore } from "../shared/plan-status-store.js"
17
+ import { assertPathInRuntimeScope } from "../shared/runtime-context.js"
18
+ import {
19
+ formatSchemaTopology,
20
+ loadPrismaSchemaTopology,
21
+ type SchemaView,
22
+ } from "../shared/schema-topology.js"
14
23
 
15
24
  export function registerContextTools(server: ToolRegistrar) {
25
+ const runtimeContext = getRuntimeContext()
16
26
 
17
27
  // ===== get_project_context (L166-430) =====
18
28
  server.registerTool(
@@ -91,88 +101,73 @@ export function registerContextTools(server: ToolRegistrar) {
91
101
 
92
102
  if (scope === "all" || scope === "structure" || scope === "add-state") {
93
103
  parts.push("=== ADD 工作流状态 ===")
94
- const plansDir = join(PROJECT_ROOT, MAGIC_DIR, "plans")
95
- const reviewsDir = join(PROJECT_ROOT, MAGIC_DIR, "reviews")
96
- const specsDir = join(PROJECT_ROOT, MAGIC_DIR, "specs")
104
+ parts.push(`contextId: ${runtimeContext.contextId}`)
105
+ parts.push("source: database")
97
106
 
98
- let activePlan = ""
99
- let activePlanPath = ""
100
- if (existsSync(plansDir)) {
101
- const planFiles = (await readdirRecursive(plansDir))
102
- .filter(f => f.endsWith(".md") && !f.includes("add-route") && !f.includes("handoff"))
103
- .sort()
104
- if (planFiles.length > 0) {
105
- activePlan = planFiles[planFiles.length - 1]
106
- activePlanPath = join(plansDir, activePlan)
107
- parts.push(`最近 Plan: ${activePlan}`)
108
- const allPlanFiles = await readdirRecursive(plansDir)
109
- const addRouteFile = allPlanFiles.find(f => f.includes("add-route"))
110
- const planKeyword = activePlan.replace(/-plan-v\d+\.md$/, "")
111
- const handoffFiles = allPlanFiles.filter(f => f.includes("handoff"))
112
- const hasHandoff = handoffFiles.some(f => f.toLowerCase().includes(planKeyword.toLowerCase()))
113
- parts.push(` add-route: ${addRouteFile ? "✅ " + addRouteFile : "❌ 缺失(需回退 Step 0.5)"}`)
114
- parts.push(` handoff: ${hasHandoff ? "✅ 已生成" : "❌ 缺失(需回退 Step 0.5)"}`)
115
- } else { parts.push("最近 Plan: 无") }
116
- } else { parts.push("最近 Plan: 无") }
117
-
118
- let reviewFileName = ""; let reviewP0Count = 0; let reviewP1Count = 0; let reviewBackflowRate = 0
119
- if (existsSync(reviewsDir) && activePlan) {
120
- const reviewFiles = await readdir(reviewsDir)
121
- const planKeyword = activePlan.replace(/-plan-v\d+\.md$/, "")
122
- const matchingReview = reviewFiles.find(f => f.toLowerCase().includes(planKeyword.toLowerCase()) && f.includes("-review-v"))
123
- || reviewFiles.find(f => f.toLowerCase().includes(planKeyword.toLowerCase()))
124
- if (matchingReview) {
125
- reviewFileName = matchingReview
126
- const reviewContent = await readFileSafe(join(reviewsDir, matchingReview)) || ""
127
- const reviewLines = reviewContent.split("\n")
128
- let inP0 = false, inP1 = false
129
- for (const line of reviewLines) {
130
- if (line.match(/P0|ADD.*合规|阻断/)) { inP0 = true; inP1 = false; continue }
131
- if (line.match(/P1|架构设计.*缺口/)) { inP0 = false; inP1 = true; continue }
132
- if (line.match(/P2|中等|影响评估|决策结论|方案对比/)) { inP0 = false; inP1 = false; continue }
133
- if ((inP0 || inP1) && line.trim().startsWith("|") && !line.includes("---")) {
134
- const cols = line.split("|").map(c => c.trim()).filter(Boolean)
135
- if (cols.length >= 4 && cols[0].match(/^\d+$/)) {
136
- if (inP0) reviewP0Count++; else if (inP1) reviewP1Count++
137
- }
138
- }
139
- }
140
- const planContent = await readFileSafe(activePlanPath) || ""
141
- if ((reviewP0Count + reviewP1Count) > 0 && planContent) {
142
- const indicators = ["add-route", "specs/", "handoff", "Task Group", "perExpertTopK", "agri_tech", "ChromaCollectionManager", "迁移路线图", "8 个 Expert", "冗余策略"]
143
- let hit = 0
144
- for (const ind of indicators) { if (planContent.toLowerCase().includes(ind.toLowerCase())) hit++ }
145
- reviewBackflowRate = Math.min(100, Math.round((hit / Math.max(reviewP0Count + reviewP1Count, 1)) * 100))
146
- }
147
- parts.push(""); parts.push(`关联 Review: ${matchingReview}`)
148
- parts.push(` P0 问题: ${reviewP0Count} 个, P1 问题: ${reviewP1Count} 个`)
149
- parts.push(` 回流状态: 约 ${reviewBackflowRate}%`)
150
- parts.push(reviewBackflowRate < 70 ? " ⚠️ Review 结论未充分回流至 Plan — 需执行 0.6.5 卡位" : " ✅ Review 结论基本回流至 Plan")
151
- } else { parts.push(""); parts.push("关联 Review: 无(该 Plan 尚未生成方案评审)") }
152
- }
153
-
154
- if (activePlan) {
155
- const planKeyword = activePlan.replace(/-plan-v\d+\.md$/, "")
156
- const specDirs = existsSync(specsDir) ? await readdir(specsDir) : []
157
- const matchingSpec = specDirs.find(d => d.toLowerCase().includes(planKeyword.toLowerCase()))
107
+ const planDb = validatedDelegate<PlanRow>(prisma.planRecord, PlanRowSchema, "PlanRecord")
108
+ const reviewDb = validatedDelegate<ReviewRow>(prisma.reviewRecord, ReviewRowSchema, "ReviewRecord")
109
+ const resolution = await resolvePlanStatus(
110
+ createPrismaPlanStatusStore(prisma),
111
+ runtimeContext,
112
+ { activeOnly: true },
113
+ )
114
+ let activePlan: PlanRow | null = null
115
+ let reviewRows: ReviewRow[] = []
116
+ if (resolution.availability === "STATUS_UNAVAILABLE") {
117
+ parts.push(`状态: STATUS_UNAVAILABLE ${resolution.reason}`)
118
+ parts.push("裁决: fail closed;未回退 Plan/Handoff/add-route 文件猜测")
119
+ } else if (resolution.planName === null) {
120
+ parts.push("活跃 Plan: 无(DB lifecycle ACTIVE/BLOCKED)")
121
+ } else {
122
+ activePlan = await planDb.findFirst({
123
+ where: {
124
+ projectKey: runtimeContext.projectKey,
125
+ adapterKey: runtimeContext.adapterKey,
126
+ planName: resolution.planName,
127
+ },
128
+ })
129
+ if (!activePlan) throw new Error(`resolver 返回 Plan 后记录消失: ${resolution.planName}`)
130
+ assertPathInRuntimeScope(runtimeContext, activePlan.planPath)
131
+ parts.push(`活跃 Plan: ${activePlan.planName}`)
132
+ parts.push(` lifecycle: ${resolution.lifecycle} | revision: ${resolution.revision}`)
133
+ parts.push(` approval: ${resolution.approvalStatus ?? "—"}`)
134
+ parts.push(` tasks: ${resolution.progress.doneTasks}/${resolution.progress.totalTasks}`)
135
+ parts.push(` Plan: ${activePlan.planPath}`)
136
+ parts.push(` add-route: ${activePlan.addRoutePath && existsSync(activePlan.addRoutePath) ? `✅ ${activePlan.addRoutePath}` : "❌ 缺失"}`)
137
+ parts.push(" handoff: 不参与 active 裁决(仅 Step 8 交接产物)")
138
+ parts.push("")
139
+ parts.push("Specs:")
140
+ parts.push(` spec.md: ${activePlan.specPath && existsSync(activePlan.specPath) ? "✅" : "❌"}`)
141
+ parts.push(` tasks.md: ${activePlan.tasksPath && existsSync(activePlan.tasksPath) ? "✅" : "❌"}`)
142
+ parts.push(` checklist.md: ${activePlan.checklistPath && existsSync(activePlan.checklistPath) ? "" : ""}`)
143
+ reviewRows = await reviewDb.findMany({
144
+ where: {
145
+ projectKey: runtimeContext.projectKey,
146
+ adapterKey: runtimeContext.adapterKey,
147
+ planName: activePlan.planName,
148
+ },
149
+ orderBy: { updatedAt: "desc" },
150
+ })
158
151
  parts.push("")
159
- if (matchingSpec) {
160
- const hasSpec = existsSync(join(specsDir, matchingSpec, "spec.md"))
161
- const hasTasks = existsSync(join(specsDir, matchingSpec, "tasks.md"))
162
- const hasChecklist = existsSync(join(specsDir, matchingSpec, "checklist.md"))
163
- parts.push(`Specs: ${matchingSpec}/`)
164
- parts.push(` spec.md: ${hasSpec ? "✅" : "❌"}`)
165
- parts.push(` tasks.md: ${hasTasks ? "✅" : "❌"}`)
166
- parts.push(` checklist.md: ${hasChecklist ? "✅" : "❌"}`)
167
- } else { parts.push("Specs: ❌ 缺失") }
152
+ if (reviewRows.length === 0) {
153
+ parts.push("关联 Review: ")
154
+ } else {
155
+ parts.push(`关联 Review: ${reviewRows.length} 条(DB scoped)`)
156
+ for (const review of reviewRows) {
157
+ parts.push(` ${review.type}: P0=${review.p0Count}, P1=${review.p1Count}, backflow=${review.backflowRate}`)
158
+ }
159
+ }
168
160
  }
169
161
 
170
162
  parts.push(""); parts.push("=== 悬空 Plan 对账(记录在、文件缺失) ===")
171
- // 孤儿可见性:create_hitl 预置行被中断 / 放弃未清理 → 开局即提示,不静默累积
172
163
  try {
173
- // 无类型边界单点(zod 托管):动态 client → 运行期校验的泛型委托
174
- const planDb = validatedDelegate<PlanRow>(prisma.planRecord, PlanRowSchema, "PlanRecord")
175
- const allPlans = await planDb.findMany({ orderBy: { createdAt: "desc" } })
164
+ const allPlans = await planDb.findMany({
165
+ where: {
166
+ projectKey: runtimeContext.projectKey,
167
+ adapterKey: runtimeContext.adapterKey,
168
+ },
169
+ orderBy: { createdAt: "desc" },
170
+ })
176
171
  const orphans = allPlans.filter(p => !existsSync(p.planPath))
177
172
  if (orphans.length > 0) {
178
173
  const cutoff = Date.now() - 14 * 86400000
@@ -185,20 +180,19 @@ export function registerContextTools(server: ToolRegistrar) {
185
180
 
186
181
  parts.push(""); parts.push("=== 待执行 ADD 操作(按流程顺序) ===")
187
182
  const todoItems: string[] = []
188
- if (!activePlan) { todoItems.push("1. [未开始] 用户提出需求 → 生成 Plan") }
189
- else {
190
- if (!reviewFileName) { todoItems.push("1. [Step 0] 生成 Plan Review(ADD-9 方案评审)") }
191
- else if (reviewP0Count + reviewP1Count > 0 && reviewBackflowRate < 70) {
192
- todoItems.push("1. [0.6.5] Review 结论回流至 Plan — P0/P1 未写入 Plan 体")
193
- todoItems.push("2. [check_dps] DPS 门禁预计不通过(回流完整度 < 70%)")
183
+ if (resolution.availability === "STATUS_UNAVAILABLE") {
184
+ todoItems.push("1. [阻断] 恢复 DB/MCP resolver;禁止按文件继续")
185
+ } else if (!activePlan) { todoItems.push("1. [未开始] 用户提出需求 → 生成 Plan + HITL") }
186
+ else if (resolution.planName !== null) {
187
+ if (resolution.approvalStatus !== "TONGYI") todoItems.push("1. [HITL] 完成当前 Plan 审批")
188
+ if (reviewRows.length === 0) todoItems.push("2. [Step 0] 生成并追踪 Plan Review")
189
+ if (reviewRows.some(review => review.p0Count + review.p1Count > 0 && review.backflowRate === 0)) {
190
+ todoItems.push("3. [0.6.5] Review P0/P1 回流至 Plan")
194
191
  }
195
- const allPlanDir = await readdirRecursive(plansDir)
196
- const hasAddRoute = allPlanDir.some(f => f.includes("add-route"))
197
- if (!hasAddRoute && reviewBackflowRate >= 70) { todoItems.push("2. [Step 0.5] 生成 add-route") }
198
- const planKw = activePlan.replace(/-plan-v\d+\.md$/, "")
199
- const sd = existsSync(specsDir) ? await readdir(specsDir) : []
200
- if (!sd.some(d => d.toLowerCase().includes(planKw.toLowerCase())) && reviewBackflowRate >= 70) { todoItems.push("3. [Step 0] 生成 Specs 三元组") }
201
- if (todoItems.length === 0) { todoItems.push("✅ ADD 就绪 — check_dps ≥ {{dpsPass}} 后可进入 Step 1") }
192
+ if (!activePlan.addRoutePath || !existsSync(activePlan.addRoutePath)) todoItems.push("4. [Step 0.5] 生成 add-route")
193
+ if (!activePlan.specPath || !activePlan.tasksPath || !activePlan.checklistPath) todoItems.push("5. [Step 0] 补齐 Specs 三元组")
194
+ if (activePlan.doneTasks < activePlan.totalTasks) todoItems.push(`6. [实施] 继续未完成 Task(${activePlan.doneTasks}/${activePlan.totalTasks})`)
195
+ if (todoItems.length === 0) todoItems.push("✅ DB lifecycle 与文档链已就绪,可进入 Review/closure")
202
196
  }
203
197
  for (const item of todoItems) parts.push(item)
204
198
  parts.push(""); parts.push("快速指令: 说「执行 add-paradigm Step 0」进入文档先行流程")
@@ -243,39 +237,41 @@ export function registerContextTools(server: ToolRegistrar) {
243
237
  server.registerTool(
244
238
  "get_db_schema",
245
239
  {
246
- description: "获取 Prisma 数据库 Schema 定义(MCP-1 上下文优先)。返回指定模型的结构、字段、关系。AI 助手在编写数据库查询代码时应调用此工具获取真实的 Schema 信息,避免凭记忆假设。",
247
- inputSchema: z.object({ model: z.string().optional().describe("可选的模型名称(不区分大小写)。不指定则返回所有模型概况。指定则返回该模型的完整字段定义。") }),
240
+ description: "获取 topology-aware Prisma Schema。兼容单库多文件 schema folder ADD_DATABASE_URL 分库;返回扫描文件、数据库边界、模型来源和诊断。",
241
+ inputSchema: z.object({
242
+ model: z.string().optional().describe("可选模型或枚举名称(不区分大小写)"),
243
+ view: z.enum(["business", "add", "all"]).optional().default("all").describe("Schema 视图;分库时 all 仅分组汇总,不表示可跨库 join"),
244
+ }),
248
245
  },
249
- async (args: { model?: string }) => {
246
+ async (args: { model?: string; view?: SchemaView }) => {
250
247
  try {
251
- const schemaPath = join(PROJECT_ROOT, "prisma", "schema.prisma")
252
- const schema = await readFileSafe(schemaPath)
253
- if (!schema) return errorResponse("未找到 prisma/schema.prisma 文件")
254
- const modelName = args?.model?.toLowerCase()
255
- if (modelName) {
256
- const modelRegex = new RegExp(`model\\s+${modelName}\\s*\\{`, "i")
257
- const match = schema.match(modelRegex)
258
- if (match) {
259
- const startIdx = match.index ?? 0; const braceIdx = schema.indexOf("{", startIdx)
260
- if (braceIdx !== -1) { let depth = 1; let endIdx = braceIdx + 1; while (depth > 0 && endIdx < schema.length) { if (schema[endIdx] === "{") depth++; else if (schema[endIdx] === "}") depth--; endIdx++ }; return textResponse(`=== Model: ${args.model} ===\n\nmodel ${args.model} ${schema.slice(braceIdx, endIdx)}`) }
248
+ const topology = await loadPrismaSchemaTopology({
249
+ projectRoot: PROJECT_ROOT,
250
+ splitDatabase: Boolean(process.env.ADD_DATABASE_URL?.trim()),
251
+ view: args?.view ?? "all",
252
+ })
253
+ const requested = args?.model?.trim().toLowerCase()
254
+ if (requested) {
255
+ const declaration = [...topology.models, ...topology.enums]
256
+ .find(item => item.name.toLowerCase() === requested)
257
+ if (!declaration) {
258
+ return errorResponse(`未在 view=${topology.view} 找到模型或枚举: ${args.model}。扫描: ${topology.files.map(file => file.file).join(", ")}`)
261
259
  }
262
- const enumMatch = schema.match(new RegExp(`enum\\s+${modelName}\\s*\\{`, "i"))
263
- if (enumMatch) return textResponse(`=== Enum: ${args.model} ===\n\n可用的枚举值见不传参调用结果。`)
264
- return errorResponse(`未找到模型或枚举: ${args.model}。可用模型见不传参调用结果。`)
265
- }
266
- const models: Array<{ name: string; fieldCount: number }> = []
267
- const modelRegex = /model\s+(\w+)\s*\{/g; let m
268
- while ((m = modelRegex.exec(schema)) !== null) {
269
- const startIdx = m.index; const braceIdx = schema.indexOf("{", startIdx)
270
- if (braceIdx !== -1) { let depth = 1; let endIdx = braceIdx + 1; while (depth > 0 && endIdx < schema.length) { if (schema[endIdx] === "{") depth++; else if (schema[endIdx] === "}") depth--; endIdx++ }; const body = schema.slice(braceIdx, endIdx); const fieldCount = body.split("\n").filter((l: string) => l.trim() && !l.trim().startsWith("//") && !l.trim().startsWith("@@")).length; models.push({ name: m[1], fieldCount }) }
260
+ return textResponse([
261
+ `=== ${declaration.kind === "model" ? "Model" : "Enum"}: ${declaration.name} ===`,
262
+ `mode: ${topology.mode}`,
263
+ `view: ${topology.view}`,
264
+ `boundary: ${declaration.boundary}`,
265
+ `source: ${relative(PROJECT_ROOT, declaration.sourcePath)}`,
266
+ "",
267
+ `${declaration.kind} ${declaration.name} ${declaration.body}`,
268
+ ].join("\n"))
271
269
  }
272
- const enums: string[] = []; const enumRegex = /enum\s+(\w+)\s*\{/g
273
- while ((m = enumRegex.exec(schema)) !== null) { enums.push(m[1]) }
274
- const parts = ["=== Prisma Schema 概况 ===", "", `模型 (${models.length} 个):`]
275
- for (const model of models) parts.push(` ${model.name} (${model.fieldCount} 字段)`)
276
- if (enums.length > 0) { parts.push(""); parts.push(`枚举 (${enums.length} 个): ${enums.join(", ")}`) }
277
- parts.push("", '提示: 指定 model 参数获取完整字段定义,例如: get_db_schema({ model: "User" })')
278
- return textResponse(parts.join("\n"))
270
+ return textResponse([
271
+ formatSchemaTopology(topology, PROJECT_ROOT),
272
+ "",
273
+ '提示: 指定 model/view 获取完整定义,例如 get_db_schema({ model: "PlanRecord", view: "add" })',
274
+ ].join("\n"))
279
275
  } catch (error) { return errorResponse(`获取 Schema 失败: ${error instanceof Error ? error.message : String(error)}`) }
280
276
  }
281
277
  )
@@ -7,6 +7,7 @@ import { readdirRecursive, PROJECT_ROOT, MAGIC_DIR } from "../shared/fs.js"
7
7
  import { prisma } from "../shared/prisma.js"
8
8
  import type { CollabContractRow, PlanRow } from "../shared/db-types.js"
9
9
  import { CollabContractRowSchema, PlanRowSchema, validatedDelegate } from "../shared/db-types.js"
10
+ import { getRuntimeContext } from "../shared/env.js"
10
11
 
11
12
  const db = {
12
13
  get collabContract() { return validatedDelegate<CollabContractRow>(prisma.collabContract, CollabContractRowSchema, "CollabContract") },
@@ -67,6 +68,7 @@ function parseContractDoc(content: string): ParsedContract {
67
68
  }
68
69
 
69
70
  export function registerContractTools(server: ToolRegistrar) {
71
+ const runtimeContext = getRuntimeContext()
70
72
 
71
73
  // ===== contract_track =====
72
74
  server.registerTool("contract_track", {
@@ -124,13 +126,30 @@ export function registerContractTools(server: ToolRegistrar) {
124
126
  results.push(`⚠️ ${name} 缺少「总控 Plan:」声明,跳过`)
125
127
  continue
126
128
  }
127
- const existing = await db.collabContract.findFirst({ where: { contractName: name } })
129
+ const masterPlan = await db.plan.findFirst({
130
+ where: {
131
+ projectKey: runtimeContext.projectKey,
132
+ adapterKey: runtimeContext.adapterKey,
133
+ planName: masterPlanName,
134
+ },
135
+ })
136
+ if (!masterPlan) {
137
+ results.push(`⚠️ ${name} 当前 RuntimeContextKey 下不存在 Master Plan: ${masterPlanName}`)
138
+ continue
139
+ }
140
+ const existing = await db.collabContract.findFirst({
141
+ where: { projectKey: runtimeContext.projectKey, contractName: name },
142
+ })
128
143
  const version = (existing?.version as number ?? 0) + 1
129
144
 
130
145
  const data = {
131
146
  contractName: name,
147
+ projectKey: runtimeContext.projectKey,
148
+ ownerAdapterKey: runtimeContext.adapterKey,
132
149
  contractPath: path,
133
150
  masterPlanName,
151
+ masterProjectKey: runtimeContext.projectKey,
152
+ masterAdapterKey: runtimeContext.adapterKey,
134
153
  participants: parsed.participants as never,
135
154
  abilityMatrix: parsed.abilityMatrix as never,
136
155
  stages: parsed.stages as never,
@@ -142,7 +161,7 @@ export function registerContractTools(server: ToolRegistrar) {
142
161
  }
143
162
 
144
163
  if (existing) {
145
- await db.collabContract.update({ where: { contractName: name }, data })
164
+ await db.collabContract.update({ where: { id: existing.id }, data })
146
165
  results.push(`📄 ${name} ✅ 已更新 (v${version})`)
147
166
  } else {
148
167
  await db.collabContract.create({ data })
@@ -152,8 +171,12 @@ export function registerContractTools(server: ToolRegistrar) {
152
171
  // 若 masterPlan 存在,标记 PlanRecord 角色
153
172
  if (masterPlanName) {
154
173
  await db.plan.updateMany({
155
- where: { planName: masterPlanName },
156
- data: { contractRole: "MASTER", contractName: name } as never,
174
+ where: {
175
+ projectKey: runtimeContext.projectKey,
176
+ adapterKey: runtimeContext.adapterKey,
177
+ planName: masterPlanName,
178
+ },
179
+ data: { contractRole: "MASTER", contractName: name },
157
180
  })
158
181
  }
159
182
  }
@@ -173,7 +196,9 @@ export function registerContractTools(server: ToolRegistrar) {
173
196
  }, async (args: Record<string, unknown>) => {
174
197
  try {
175
198
  const { contractName } = args as { contractName: string }
176
- const contract = await db.collabContract.findFirst({ where: { contractName } })
199
+ const contract = await db.collabContract.findFirst({
200
+ where: { projectKey: runtimeContext.projectKey, contractName },
201
+ })
177
202
  if (!contract) {
178
203
  return textResponse(`📋 契约: 未跟踪\ncontractName: ${contractName}\n\n操作: contract_track({ contractName: "${contractName}" })`)
179
204
  }
@@ -182,11 +207,11 @@ export function registerContractTools(server: ToolRegistrar) {
182
207
  const boundaries = (contract.fileBoundaries as unknown[]) || []
183
208
  const parallelCount = stages.filter((s) => (s as Record<string, unknown>).parallelism === "parallel").length
184
209
  const serialCount = stages.filter((s) => (s as Record<string, unknown>).parallelism === "serial").length
185
- const versionStr = String(contract.version as number ?? "?")
186
- const statusStr = String(contract.status as string ?? "?")
187
- const masterStr = String(contract.masterPlanName as string ?? "(未绑定)")
210
+ const versionStr = String(contract.version ?? "?")
211
+ const statusStr = String(contract.status ?? "?")
212
+ const masterStr = String(contract.masterPlanName ?? "(未绑定)")
188
213
  const depStr = contract.dependencyGraph ? "已声明" : "未声明"
189
- const pathStr = String(contract.contractPath as string ?? "(无)")
214
+ const pathStr = String(contract.contractPath ?? "(无)")
190
215
  return textResponse(
191
216
  `📊 ${contractName}\n` +
192
217
  `版本: v${versionStr} (${statusStr})\n` +
@@ -17,6 +17,7 @@ import {
17
17
  MAGIC_DIR,
18
18
  } from "../../shared/fs.js";
19
19
  import { prisma } from "../../shared/prisma.js";
20
+ import { getRuntimeContext } from "../../shared/env.js";
20
21
 
21
22
  function scanCheckboxes(content: string) {
22
23
  let t = 0,
@@ -61,6 +62,7 @@ function scanCheckboxes(content: string) {
61
62
  }
62
63
 
63
64
  export function registerCheckAddRouteStatus(server: ToolRegistrar) {
65
+ const runtimeContext = getRuntimeContext();
64
66
  server.registerTool(
65
67
  "check_add_route_status",
66
68
  {
@@ -85,10 +87,13 @@ export function registerCheckAddRouteStatus(server: ToolRegistrar) {
85
87
  prisma.auditLog as Record<string, (...a: unknown[]) => unknown>
86
88
  ).findMany({
87
89
  where: {
88
- OR: [
89
- { targetId: { contains: "add-route", mode: "insensitive" } },
90
+ projectKey: runtimeContext.projectKey,
91
+ producerAdapterKey: runtimeContext.adapterKey,
92
+ // 按当前 Plan 精准定位:targetId 同时含 planKeyword 与 add-route(与文件匹配语义一致)
93
+ AND: [
94
+ { targetId: { startsWith: `${MAGIC_DIR}/` } },
90
95
  { targetId: { contains: planKeyword, mode: "insensitive" } },
91
- { reason: { contains: "add-route", mode: "insensitive" } },
96
+ { targetId: { contains: "add-route", mode: "insensitive" } },
92
97
  ],
93
98
  },
94
99
  orderBy: { createdAt: "desc" },
@@ -113,6 +118,48 @@ export function registerCheckAddRouteStatus(server: ToolRegistrar) {
113
118
  } catch {
114
119
  /* empty */
115
120
  }
121
+ // 补查 DevOperation 表(record_dev_operation 写入通道;R10 修复,2026-08-12 用户拍板不另立 Plan,本 Plan 内闭环)
122
+ try {
123
+ const devLogs = await (
124
+ prisma.devOperation as Record<string, (...a: unknown[]) => unknown>
125
+ ).findMany({
126
+ where: {
127
+ projectKey: runtimeContext.projectKey,
128
+ producerAdapterKey: runtimeContext.adapterKey,
129
+ // 按当前 Plan 精准定位(AND:planKeyword + add-route),命中集天然为当前 Plan 的 add-route 记录
130
+ AND: [
131
+ { targetId: { startsWith: `${MAGIC_DIR}/` } },
132
+ { targetId: { contains: planKeyword, mode: "insensitive" } },
133
+ { targetId: { contains: "add-route", mode: "insensitive" } },
134
+ ],
135
+ },
136
+ orderBy: { createdAt: "desc" },
137
+ take: 100,
138
+ }) as Array<{
139
+ targetId?: string;
140
+ reason?: string;
141
+ action: string;
142
+ createdAt: Date;
143
+ }>;
144
+ const pl = planKeyword.toLowerCase();
145
+ for (const l of devLogs) {
146
+ const ti = (l.targetId || "").toLowerCase();
147
+ const r = (l.reason || "").toLowerCase();
148
+ if (
149
+ (ti.includes("add-route") && ti.includes(pl)) ||
150
+ (r.includes("add-route") && r.includes(pl))
151
+ ) {
152
+ auditHasRecord = true;
153
+ auditRecords.push({
154
+ action: l.action,
155
+ targetId: l.targetId || "unknown",
156
+ createdAt: l.createdAt,
157
+ });
158
+ }
159
+ }
160
+ } catch {
161
+ /* empty */
162
+ }
116
163
  let fileExists = false;
117
164
  const matchedFiles: string[] = [];
118
165
  try {