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.
- package/README.en.md +10 -10
- package/README.md +20 -46
- package/dist/index.js +6 -69
- package/package.json +1 -1
- package/templates/.add-coder-src-hash.json +24 -28
- package/templates/adapters/claude/hooks/doc-format-guard.sh +2 -2
- package/templates/adapters/codex/hooks/doc-format-guard.sh +2 -2
- package/templates/adapters/qoder/hooks/doc-format-guard.sh +2 -2
- package/templates/adapters/trae/hooks/doc-format-guard.sh +2 -2
- package/templates/adapters/vscode/hooks/doc-format-guard.sh +2 -2
- package/templates/core/hooks/doc-format-guard.sh +2 -2
- package/templates/core/scripts/db-ensure.sh +0 -8
- package/templates/core/scripts/mcp-server/shared/db-types.ts +208 -0
- package/templates/core/scripts/mcp-server/shared/response.ts +2 -3
- package/templates/core/scripts/mcp-server/tools/audit.ts +10 -7
- package/templates/core/scripts/mcp-server/tools/context.ts +19 -0
- package/templates/core/scripts/mcp-server/tools/contract.ts +6 -4
- package/templates/core/scripts/mcp-server/tools/gateway/check_rahs.ts +2 -69
- package/templates/core/scripts/mcp-server/tools/gateway/check_spec_sync.ts +1 -1
- package/templates/core/scripts/mcp-server/tools/hitl.ts +116 -34
- package/templates/core/scripts/mcp-server/tools/index.ts +1 -66
- package/templates/core/scripts/mcp-server/tools/plan.ts +33 -16
- package/templates/core/scripts/mcp-server/tools/review.ts +12 -9
- package/templates/core/scripts/mcp-server.ts +1 -2
- package/templates/core/templates/simple-plan-template.md +13 -0
- package/templates/core/templates/simple-plan-template.schema.json +10 -0
- package/templates/core/templates/standard-plan-template.md +13 -0
- package/templates/core/templates/standard-plan-template.schema.json +10 -0
- package/templates/adapters/codex/config.toml.example +0 -20
- package/templates/core/plans/2026-07/08/add-coder-npm-package-add-route-v1.md +0 -331
- package/templates/core/plans/2026-07/08/add-coder-npm-package-handoff-v1.md +0 -686
- package/templates/core/plans/2026-07/08/add-coder-npm-package-plan-v1.md +0 -895
- 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
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
|
11
|
-
get plan() { return prisma.planRecord
|
|
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 } })
|
|
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 } })
|
|
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
|
|
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
|
|
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
|
|