add-coder 0.3.25-0 → 0.3.26

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/README.en.md +10 -10
  2. package/README.md +20 -46
  3. package/dist/index.js +6 -69
  4. package/package.json +1 -1
  5. package/templates/.add-coder-src-hash.json +24 -28
  6. package/templates/adapters/claude/hooks/doc-format-guard.sh +2 -2
  7. package/templates/adapters/codex/hooks/doc-format-guard.sh +2 -2
  8. package/templates/adapters/qoder/hooks/doc-format-guard.sh +2 -2
  9. package/templates/adapters/trae/hooks/doc-format-guard.sh +2 -2
  10. package/templates/adapters/vscode/hooks/doc-format-guard.sh +2 -2
  11. package/templates/core/hooks/doc-format-guard.sh +2 -2
  12. package/templates/core/scripts/db-ensure.sh +0 -8
  13. package/templates/core/scripts/mcp-server/shared/db-types.ts +208 -0
  14. package/templates/core/scripts/mcp-server/shared/response.ts +2 -3
  15. package/templates/core/scripts/mcp-server/tools/audit.ts +10 -7
  16. package/templates/core/scripts/mcp-server/tools/context.ts +19 -0
  17. package/templates/core/scripts/mcp-server/tools/contract.ts +6 -4
  18. package/templates/core/scripts/mcp-server/tools/gateway/check_rahs.ts +2 -69
  19. package/templates/core/scripts/mcp-server/tools/gateway/check_spec_sync.ts +1 -1
  20. package/templates/core/scripts/mcp-server/tools/hitl.ts +116 -34
  21. package/templates/core/scripts/mcp-server/tools/index.ts +1 -66
  22. package/templates/core/scripts/mcp-server/tools/plan.ts +33 -16
  23. package/templates/core/scripts/mcp-server/tools/review.ts +12 -9
  24. package/templates/core/scripts/mcp-server.ts +1 -2
  25. package/templates/core/templates/simple-plan-template.md +13 -0
  26. package/templates/core/templates/simple-plan-template.schema.json +10 -0
  27. package/templates/core/templates/standard-plan-template.md +13 -0
  28. package/templates/core/templates/standard-plan-template.schema.json +10 -0
  29. package/templates/adapters/codex/config.toml.example +0 -20
  30. package/templates/core/plans/2026-07/08/add-coder-npm-package-add-route-v1.md +0 -331
  31. package/templates/core/plans/2026-07/08/add-coder-npm-package-handoff-v1.md +0 -686
  32. package/templates/core/plans/2026-07/08/add-coder-npm-package-plan-v1.md +0 -895
  33. package/templates/core/scripts/mcp-server/shared/redact.ts +0 -28
@@ -0,0 +1,208 @@
1
+ /*
2
+ * 数据层结构化类型(MCP server)— zod 托管版(D1)
3
+ *
4
+ * 背景:shared/prisma.ts 运行时动态 require 生成的 Prisma client(模板会被复制到任意
5
+ * 消费方项目,不能静态依赖特定生成类型)——加载点的「无类型」不可避免。
6
+ *
7
+ * 本文件职责(边界从「盲信 cast」升级为「运行期验证」):
8
+ * 1. zod schema 为单一真源:行类型一律 z.infer 派生(编译期约束不丢)
9
+ * 2. validatedDelegate 包装 findFirst/findMany/create/update/delete:
10
+ * DB 返回行过 schema.parse —— 消费方迁移滞后/schema 漂移时**立即显式报错**,
11
+ * 而非让 undefined 字段静默漂移到下游
12
+ * 3. z.looseObject 前瞻兼容(zod v4 替代已弃用的 .passthrough()):add.prisma 未来加列不误伤旧模板
13
+ * 4. parse 失败出处方式报错(弱模型可执行),不裸抛 zod issue 堆栈
14
+ *
15
+ * 契约:schema 须与 prisma/add.prisma 模型对齐(字段名/可空性)。
16
+ */
17
+ import * as z from "zod/v4"
18
+
19
+ // ===== 行 schema(对齐 prisma/add.prisma;looseObject 前瞻兼容) =====
20
+
21
+ export const PlanRowSchema = z.looseObject({
22
+ id: z.string(),
23
+ planName: z.string(),
24
+ planPath: z.string(),
25
+ planKeyword: z.string().nullable(),
26
+ specPath: z.string().nullable(),
27
+ tasksPath: z.string().nullable(),
28
+ checklistPath: z.string().nullable(),
29
+ addRoutePath: z.string().nullable(),
30
+ totalTasks: z.number(),
31
+ doneTasks: z.number(),
32
+ checklistT: z.number(),
33
+ checklistTDone: z.number(),
34
+ checklistR: z.number(),
35
+ dpsStructScore: z.number().nullable(),
36
+ dpsSemScore: z.number().nullable(),
37
+ dpsEntropyScore: z.number().nullable(),
38
+ dpsCpmScore: z.number().nullable(),
39
+ dpsComposite: z.number().nullable(),
40
+ contractRole: z.string().nullable(),
41
+ contractName: z.string().nullable(),
42
+ createdAt: z.date(),
43
+ updatedAt: z.date(),
44
+ })
45
+
46
+ export const HitlRowSchema = z.looseObject({
47
+ id: z.string(),
48
+ planName: z.string(),
49
+ round: z.number(),
50
+ type: z.enum(["PLAN", "PLAN_REVIEW", "COLLAB_CONTRACT"]),
51
+ status: z.enum(["DRAFT", "SUBMITTED", "TONGYI", "BOHUI"]),
52
+ approvedAt: z.date().nullable(),
53
+ rejectedAt: z.date().nullable(),
54
+ rejectReason: z.string().nullable(),
55
+ createdAt: z.date(),
56
+ updatedAt: z.date(),
57
+ })
58
+
59
+ export const ReviewRowSchema = z.looseObject({
60
+ id: z.string(),
61
+ planName: z.string(),
62
+ type: z.enum(["PLAN_REVIEW", "IMPLEMENTATION", "RUNTIME"]),
63
+ reviewPath: z.string().nullable(),
64
+ p0Count: z.number(),
65
+ p1Count: z.number(),
66
+ backflowRate: z.number(),
67
+ createdAt: z.date(),
68
+ updatedAt: z.date(),
69
+ })
70
+
71
+ export const AuditLogRowSchema = z.looseObject({
72
+ id: z.string(),
73
+ action: z.string(),
74
+ targetType: z.string(),
75
+ targetId: z.string(),
76
+ beforeState: z.unknown().nullable(),
77
+ afterState: z.unknown().nullable(),
78
+ reason: z.string().nullable(),
79
+ planKeyword: z.string().nullable(),
80
+ createdAt: z.date(),
81
+ })
82
+
83
+ export const DevOperationRowSchema = z.looseObject({
84
+ id: z.string(),
85
+ userId: z.string(),
86
+ planKeyword: z.string(),
87
+ action: z.string(),
88
+ targetType: z.string(),
89
+ targetId: z.string(),
90
+ beforeState: z.unknown().nullable(),
91
+ afterState: z.unknown().nullable(),
92
+ reason: z.string().nullable(),
93
+ createdAt: z.date(),
94
+ })
95
+
96
+ // AddUser 仅消费 id(select { id } 场景),username/email 宽容可空
97
+ // (避免 select 裁剪导致必填字段缺失误伤——机器占位机器回刷原则)
98
+ export const AddUserRowSchema = z.looseObject({
99
+ id: z.string(),
100
+ username: z.string().optional(),
101
+ email: z.string().nullable().optional(),
102
+ })
103
+
104
+ export const CollabContractRowSchema = z.looseObject({
105
+ id: z.string(),
106
+ contractName: z.string(),
107
+ contractPath: z.string(),
108
+ masterPlanName: z.string(),
109
+ participants: z.unknown(),
110
+ abilityMatrix: z.unknown().nullable(),
111
+ stages: z.unknown(),
112
+ dependencyGraph: z.string().nullable(),
113
+ fileBoundaries: z.unknown(),
114
+ completionCriteria: z.unknown().nullable(),
115
+ status: z.string(),
116
+ version: z.number(),
117
+ createdAt: z.date(),
118
+ updatedAt: z.date(),
119
+ })
120
+
121
+ // ===== 类型派生(单一真源:schema → 类型) =====
122
+
123
+ export type PlanRow = z.infer<typeof PlanRowSchema>
124
+ export type HitlRow = z.infer<typeof HitlRowSchema>
125
+ export type ReviewRow = z.infer<typeof ReviewRowSchema>
126
+ export type AuditLogRow = z.infer<typeof AuditLogRowSchema>
127
+ export type DevOperationRow = z.infer<typeof DevOperationRowSchema>
128
+ export type AddUserRow = z.infer<typeof AddUserRowSchema>
129
+ export type CollabContractRow = z.infer<typeof CollabContractRowSchema>
130
+
131
+ // ===== 查询参数(Prisma 最常用子集,结构化约束 + 运算符支持) =====
132
+
133
+ export type OrderDirection = "asc" | "desc"
134
+
135
+ // where 放开为 Record<string, unknown>:Prisma 运算符(contains/gte/OR 等)
136
+ // 动态组合,键名约束会阻碍合法查询(如 review.ts 的 contains、audit.ts 的 OR)
137
+ export interface QueryArgs<T> {
138
+ where?: Record<string, unknown>
139
+ orderBy?: { [K in keyof T]?: OrderDirection }
140
+ take?: number
141
+ skip?: number
142
+ include?: Record<string, unknown>
143
+ }
144
+
145
+ export interface BatchResult { count: number }
146
+
147
+ // ===== 泛型委托接口:业务层唯一依赖的数据访问契约 =====
148
+
149
+ export interface TableDelegate<T> {
150
+ findFirst(args: Pick<QueryArgs<T>, "where" | "orderBy">): Promise<T | null>
151
+ findMany(args?: QueryArgs<T>): Promise<T[]>
152
+ findUnique(args: { where: Record<string, unknown>; select?: Record<string, unknown> }): Promise<T | null>
153
+ create(args: { data: Partial<T> }): Promise<T>
154
+ update(args: { where: Partial<T>; data: Partial<T> }): Promise<T>
155
+ updateMany(args: { where: Record<string, unknown>; data: Partial<T> }): Promise<BatchResult>
156
+ delete(args: { where: Partial<T> }): Promise<T>
157
+ deleteMany(args: { where: Partial<T> }): Promise<BatchResult>
158
+ }
159
+
160
+ // ===== 边界升级:validating delegate(无类型收敛单点 + 运行期行校验) =====
161
+
162
+ /**
163
+ * 把动态加载的无类型 delegate 包装为**运行期校验**的泛型委托。
164
+ * - 读路径(findFirst/findMany)与写返回值(create/update/delete)过 schema.parse
165
+ * - parse 失败 → 可读处方报错(表名 + 首个问题 + 迁移指引),不裸抛 zod 堆栈
166
+ * - deleteMany 仅返回计数,无行结构可校验,直通
167
+ */
168
+ export function validatedDelegate<T>(
169
+ rawDelegate: unknown,
170
+ rowSchema: z.ZodType<T>,
171
+ tableName: string,
172
+ ): TableDelegate<T> {
173
+ const raw = rawDelegate as TableDelegate<unknown>
174
+
175
+ const parseRow = (row: unknown): T => {
176
+ const result = rowSchema.safeParse(row)
177
+ if (!result.success) {
178
+ const issue = result.error.issues[0]
179
+ const field = issue?.path?.join(".") || "(未知字段)"
180
+ throw new Error(
181
+ `DB 行结构与预期不符(表 ${tableName}): ${field} — ${issue?.message ?? "校验失败"}\n` +
182
+ `处方: 检查项目是否已迁移至最新 prisma/add.prisma(bash scripts/db-ensure.sh),或核对 db-types.ts schema 与 add.prisma 是否同步`
183
+ )
184
+ }
185
+ return result.data
186
+ }
187
+
188
+ return {
189
+ async findFirst(args) {
190
+ const row = await raw.findFirst(args as never)
191
+ return row === null ? null : parseRow(row)
192
+ },
193
+ async findMany(args) {
194
+ const rows = await raw.findMany(args as never)
195
+ return rows.map(parseRow)
196
+ },
197
+ async findUnique(args) {
198
+ if (!raw.findUnique) throw new Error(`表 ${tableName} 不支持 findUnique`)
199
+ const row = await raw.findUnique(args as never)
200
+ return row === null ? null : parseRow(row)
201
+ },
202
+ async create(args) { return parseRow(await raw.create(args as never)) },
203
+ async update(args) { return parseRow(await raw.update(args as never)) },
204
+ async updateMany(args) { return raw.updateMany(args as never) as Promise<BatchResult> },
205
+ async delete(args) { return parseRow(await raw.delete(args as never)) },
206
+ async deleteMany(args) { return raw.deleteMany(args as never) as Promise<BatchResult> },
207
+ }
208
+ }
@@ -1,10 +1,9 @@
1
1
  import type { ToolResponse } from "../types.js"
2
- import { redact } from "./redact.js"
3
2
 
4
3
  export function textResponse(text: string): { content: ToolResponse } {
5
- return { content: [{ type: "text", text: redact(text) }] }
4
+ return { content: [{ type: "text", text }] }
6
5
  }
7
6
 
8
7
  export function errorResponse(message: string): { content: ToolResponse; isError: boolean } {
9
- return { content: [{ type: "text", text: redact(message) }], isError: true }
8
+ return { content: [{ type: "text", text: message }], isError: true }
10
9
  }
@@ -3,9 +3,15 @@ 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
  import { PROJECT_ID, PROJECT_ROOT } from "../shared/env.js"
6
+ import type { AddUserRow, DevOperationRow } from "../shared/db-types.js"
7
+ import { AddUserRowSchema, DevOperationRowSchema, validatedDelegate } from "../shared/db-types.js"
6
8
 
7
9
  export function registerAuditTools(server: ToolRegistrar) {
8
10
 
11
+ // 无类型边界单点(zod 托管):动态 client → 运行期校验的泛型委托
12
+ const devDb = validatedDelegate<DevOperationRow>(prisma.devOperation, DevOperationRowSchema, "DevOperation")
13
+ const userDb = validatedDelegate<AddUserRow>(prisma.addUser, AddUserRowSchema, "AddUser")
14
+
9
15
  // args 窄化辅助:MCP 工具参数可能是 string | number | undefined,统一转 string
10
16
  const s = (v: string | number | undefined): string => (typeof v === "number" ? String(v) : (v ?? ""))
11
17
 
@@ -29,8 +35,7 @@ export function registerAuditTools(server: ToolRegistrar) {
29
35
  if (targetType) where.targetType = s(targetType); if (action) where.action = s(action); if (targetId) where.targetId = s(targetId); if (planKeyword) where.planKeyword = s(planKeyword)
30
36
  const kw = s(keyword)
31
37
  if (kw) where.OR = [{ action: { contains: kw, mode: "insensitive" } },{ targetType: { contains: kw, mode: "insensitive" } },{ targetId: { contains: kw, mode: "insensitive" } },{ reason: { contains: kw, mode: "insensitive" } },{ planKeyword: { contains: kw, mode: "insensitive" } }]
32
- type AuditLogRow = { createdAt: Date; action: string; targetType: string; targetId: string | null; reason: string | null; planKeyword: string | null }
33
- const logs = await (prisma.devOperation as Record<string, (...a: unknown[]) => unknown>).findMany({ where, orderBy: { createdAt: "desc" }, take: Math.min(Number(limit) || 20, 100), include: { user: { select: { username: true } } } }) as unknown as AuditLogRow[]
38
+ const logs = await devDb.findMany({ where, orderBy: { createdAt: "desc" }, take: Math.min(Number(limit) || 20, 100), include: { user: { select: { username: true } } } })
34
39
  if (logs.length === 0) {
35
40
  const f: string[] = []; if (targetType) f.push(`targetType=${targetType}`); if (action) f.push(`action=${action}`); if (targetId) f.push(`targetId=${targetId}`); if (planKeyword) f.push(`planKeyword=${planKeyword}`); if (keyword) f.push(`keyword="${keyword}"`); if (sinceMinutes !== undefined) f.push(`sinceMinutes=${sinceMinutes}`)
36
41
  return textResponse(`=== 开发操作审计日志 ===\n\n未找到匹配的审计记录(条件: ${f.length > 0 ? f.join(", ") : "无条件"})。\n\n可能原因: 数据库未运行、尚无相关开发操作记录、或 filter 过于严格。`)
@@ -67,11 +72,9 @@ export function registerAuditTools(server: ToolRegistrar) {
67
72
  if (tId && (tId.startsWith("/") || /^[A-Z]:\\/.test(tId))) pathWarnings.push(`⚠️ targetId 使用了绝对路径: "${tId}"。请使用相对路径。`)
68
73
  let parsedBefore: unknown, parsedAfter: unknown
69
74
  try { if (beforeState) parsedBefore = JSON.parse(s(beforeState)); if (afterState) parsedAfter = JSON.parse(s(afterState)) } catch { return errorResponse("beforeState/afterState 必须是有效的 JSON 字符串。") }
70
- type AddUserRow = { id: string }
71
- let systemUser = await (prisma.addUser as Record<string, (...a: unknown[]) => unknown>).findUnique({ where: { username: "ai-assistant" }, select: { id: true } }) as unknown as AddUserRow | null
72
- if (!systemUser) systemUser = await (prisma.addUser as Record<string, (...a: unknown[]) => unknown>).create({ data: { id: "ai-assistant", username: "ai-assistant", email: "ai-assistant@internal" }, select: { id: true } }) as unknown as AddUserRow
73
- type DevLogRow = { id: string; createdAt: Date }
74
- const log = await (prisma.devOperation as Record<string, (...a: unknown[]) => unknown>).create({ data: { userId: systemUser.id, planKeyword: s(planKeyword) || "unknown", action: s(action), targetType: s(targetType), targetId: tId || "unknown", beforeState: parsedBefore ?? null, afterState: parsedAfter ?? null, reason: s(reason) || null } }) as unknown as DevLogRow
75
+ let systemUser = await userDb.findUnique({ where: { username: "ai-assistant" }, select: { id: true } })
76
+ if (!systemUser) systemUser = await userDb.create({ data: { id: "ai-assistant", username: "ai-assistant", email: "ai-assistant@internal" } })
77
+ const log = await devDb.create({ data: { userId: systemUser.id, planKeyword: s(planKeyword) || "unknown", action: s(action), targetType: s(targetType), targetId: tId || "unknown", beforeState: parsedBefore ?? null, afterState: parsedAfter ?? null, reason: s(reason) || null } })
75
78
  const lines = [`✅ 开发操作已记录`, ` 落库项目: ${PROJECT_ID} (${PROJECT_ROOT})`, ` ID: ${log.id}`, ` action: ${s(action)}`, ` targetType: ${s(targetType)}`, ` targetId: ${tId || "unknown"}`, ` planKeyword: ${s(planKeyword) || "unknown"}`, ` createdAt: ${log.createdAt.toISOString()}`]
76
79
  if (pathWarnings.length > 0) { lines.push(""); lines.push(...pathWarnings) }
77
80
  lines.push("", `📋 落库回查(必须执行):`, tId ? ` query_audit_logs({ targetId: "${tId}" }) — 确认本条记录已写入` : "")
@@ -8,6 +8,9 @@ import { join, relative } from "path"
8
8
  import { textResponse, errorResponse } from "../shared/response.js"
9
9
  import { readFileSafe, readdirRecursive } from "../shared/fs.js"
10
10
  import { PROJECT_ROOT, MAGIC_DIR } from "../shared/fs.js"
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"
11
14
 
12
15
  export function registerContextTools(server: ToolRegistrar) {
13
16
 
@@ -164,6 +167,22 @@ export function registerContextTools(server: ToolRegistrar) {
164
167
  } else { parts.push("Specs: ❌ 缺失") }
165
168
  }
166
169
 
170
+ parts.push(""); parts.push("=== 悬空 Plan 对账(记录在、文件缺失) ===")
171
+ // 孤儿可见性:create_hitl 预置行被中断 / 放弃未清理 → 开局即提示,不静默累积
172
+ try {
173
+ // 无类型边界单点(zod 托管):动态 client → 运行期校验的泛型委托
174
+ const planDb = validatedDelegate<PlanRow>(prisma.planRecord, PlanRowSchema, "PlanRecord")
175
+ const allPlans = await planDb.findMany({ orderBy: { createdAt: "desc" } })
176
+ const orphans = allPlans.filter(p => !existsSync(p.planPath))
177
+ if (orphans.length > 0) {
178
+ const cutoff = Date.now() - 14 * 86400000
179
+ for (const o of orphans) {
180
+ const stale = o.createdAt.getTime() < cutoff
181
+ parts.push(`${stale ? "🔴" : "·"} ${o.planName} → ${o.planPath}${stale ? "(超 14 天,建议清理)" : ""}`)
182
+ }
183
+ } else { parts.push("✅ 无悬空记录") }
184
+ } catch { parts.push("⚠️ 悬空对账跳过(数据库不可达)") }
185
+
167
186
  parts.push(""); parts.push("=== 待执行 ADD 操作(按流程顺序) ===")
168
187
  const todoItems: string[] = []
169
188
  if (!activePlan) { todoItems.push("1. [未开始] 用户提出需求 → 生成 Plan") }
@@ -5,10 +5,12 @@ import { join, basename } from "path"
5
5
  import { textResponse, errorResponse } from "../shared/response.js"
6
6
  import { readdirRecursive, PROJECT_ROOT, MAGIC_DIR } from "../shared/fs.js"
7
7
  import { prisma } from "../shared/prisma.js"
8
+ import type { CollabContractRow, PlanRow } from "../shared/db-types.js"
9
+ import { CollabContractRowSchema, PlanRowSchema, validatedDelegate } from "../shared/db-types.js"
8
10
 
9
11
  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
+ get collabContract() { return validatedDelegate<CollabContractRow>(prisma.collabContract, CollabContractRowSchema, "CollabContract") },
13
+ get plan() { return validatedDelegate<PlanRow>(prisma.planRecord, PlanRowSchema, "PlanRecord") },
12
14
  }
13
15
 
14
16
  /** 契约文档结构化解析结果 */
@@ -122,7 +124,7 @@ export function registerContractTools(server: ToolRegistrar) {
122
124
  results.push(`⚠️ ${name} 缺少「总控 Plan:」声明,跳过`)
123
125
  continue
124
126
  }
125
- const existing = await db.collabContract.findFirst({ where: { contractName: name } }) as Record<string, unknown> | null
127
+ const existing = await db.collabContract.findFirst({ where: { contractName: name } })
126
128
  const version = (existing?.version as number ?? 0) + 1
127
129
 
128
130
  const data = {
@@ -171,7 +173,7 @@ export function registerContractTools(server: ToolRegistrar) {
171
173
  }, async (args: Record<string, unknown>) => {
172
174
  try {
173
175
  const { contractName } = args as { contractName: string }
174
- const contract = await db.collabContract.findFirst({ where: { contractName } }) as Record<string, unknown> | null
176
+ const contract = await db.collabContract.findFirst({ where: { contractName } })
175
177
  if (!contract) {
176
178
  return textResponse(`📋 契约: 未跟踪\ncontractName: ${contractName}\n\n操作: contract_track({ contractName: "${contractName}" })`)
177
179
  }
@@ -9,7 +9,7 @@
9
9
  import * as z from "zod/v4";
10
10
  import type { ToolRegistrar } from "../registrar.js";
11
11
  import { existsSync } from "fs";
12
- import { join, basename } from "path";
12
+ import { join } from "path";
13
13
  import { textResponse, errorResponse } from "../../shared/response.js";
14
14
  import {
15
15
  readFileSafe,
@@ -42,9 +42,7 @@ export function registerCheckRahs(server: ToolRegistrar) {
42
42
  );
43
43
  const pm = apf.find(
44
44
  (f) =>
45
- f.toLowerCase().includes(pp.toLowerCase()) &&
46
- f.includes("-plan-v") &&
47
- !f.includes(".hitl"),
45
+ f.toLowerCase().includes(pp.toLowerCase()) && f.includes("-plan-v"),
48
46
  );
49
47
  if (!pm)
50
48
  return errorResponse(`未找到匹配的 Plan 文件(关键词: ${pp})`);
@@ -82,71 +80,6 @@ export function registerCheckRahs(server: ToolRegistrar) {
82
80
  })) as Array<{ id: string }>;
83
81
  auditScore = Math.min(100, logs.length * 10);
84
82
  } catch {}
85
- // ── 范围保真度 / Spec 合规 / 阶段对称性:从静态基线改为动态计算(修复:
86
- // 三个维度原为固定 80,任何 Plan 的 RAHS 上限 88 永远无法通过)──
87
- try {
88
- const arFile = apf.find(
89
- (f) =>
90
- f.toLowerCase().includes(pp.toLowerCase()) &&
91
- f.toLowerCase().includes("add-route"),
92
- );
93
- if (arFile) {
94
- const arContent =
95
- (await readFileSafe(join(plansDir, arFile))) || "";
96
- const checks = arContent.match(/^- \[[ x]\]/gm) || [];
97
- const done = checks.filter((c) => c.includes("[x]")).length;
98
- // 阶段对称性:add-route Step 产出项勾选率(Handoff/Report Closure 等
99
- // 条件项未勾选时按比例折算,不阻塞)
100
- symScore = checks.length > 0
101
- ? Math.max(70, Math.round((done / checks.length) * 100))
102
- : 70;
103
- }
104
- } catch {}
105
- try {
106
- // Spec 合规:checklist [T] 项勾选率([R] 运行时项不计入)
107
- const specDir = join(PROJECT_ROOT, MAGIC_DIR, "specs");
108
- const sn = basename(pm, ".md").replace(/-plan-v\d+$/i, "");
109
- const clPath = join(specDir, sn, "checklist.md");
110
- if (existsSync(clPath)) {
111
- const cl = (await readFileSafe(clPath)) || "";
112
- const tItems = cl.match(/^- \[[ x]\] \[T\]/gm) || [];
113
- const tDone = tItems.filter((c) => c.includes("[x]")).length;
114
- specScore = tItems.length > 0
115
- ? Math.max(70, Math.round((tDone / tItems.length) * 100))
116
- : 70;
117
- }
118
- } catch {}
119
- try {
120
- // 范围保真度:git diff 变更文件与 add-route 附录清单匹配率
121
- const arFile = apf.find(
122
- (f) =>
123
- f.toLowerCase().includes(pp.toLowerCase()) &&
124
- f.toLowerCase().includes("add-route"),
125
- );
126
- if (arFile) {
127
- const arContent =
128
- (await readFileSafe(join(plansDir, arFile))) || "";
129
- const appendix = (
130
- arContent.match(/`[^`]+\.(ts|js|sh|md|tsx|json|yml|yaml)`/g) ||
131
- []
132
- ).map((f: string) => f.replace(/`/g, "").toLowerCase());
133
- const diff = runCommand("git", ["diff", "--name-only"], {
134
- cwd: PROJECT_ROOT,
135
- timeout: 5000,
136
- });
137
- const diffFiles = diff.stdout
138
- .trim()
139
- .split("\n")
140
- .filter(Boolean)
141
- .map((f: string) => f.toLowerCase());
142
- if (appendix.length > 0 && diffFiles.length > 0) {
143
- const matched = diffFiles.filter((f) =>
144
- appendix.some((a) => f === a || f.endsWith(`/${a}`)),
145
- ).length;
146
- scopeScore = Math.max(70, Math.round((matched / diffFiles.length) * 100));
147
- }
148
- }
149
- } catch {}
150
83
  const parts = [
151
84
  "=== RAHS:Runtime Architecture Health Score ===",
152
85
  `Plan 关键词: "${pp}"`,
@@ -69,7 +69,7 @@ export function registerCheckSpecSync(server: ToolRegistrar) {
69
69
  lines.push(`add-route: ${arFile}`);
70
70
  const arContent = (await readFileSafe(join(plansDir, arFile))) || "";
71
71
  // 提取 add-route 附录文件清单
72
- const appendixFiles = (arContent.match(/`[^`]+\.(ts|js|sh|md|tsx|json|yml|yaml)`/g) || [])
72
+ const appendixFiles = (arContent.match(/`[^`]+\.(ts|js|sh|md|tsx|json)`/g) || [])
73
73
  .map((f: string) => f.replace(/`/g, ""));
74
74
  lines.push(`附录文件: ${appendixFiles.length} 个`);
75
75