add-coder 0.1.7 → 0.1.8
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.md +1 -1
- package/dist/index.js +6 -1
- package/package.json +1 -1
- package/templates/core/scripts/mcp-server.ts +59 -17
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# add-coder
|
|
2
2
|
|
|
3
|
-
**AI 代码治理的落地方案** — [codein2027](https://github.com/xiaomingming92/codein2027) 快速构建 ADD 编程范式的完整脚手架。以「审计即基础设施」为核心,彻底打破编程过程黑盒与跨轮失忆,让编程范式进化为可审计、可追溯、可收敛的新时代。
|
|
3
|
+
**AI 代码治理的落地方案** — [codein2027](https://github.com/xiaomingming92/codein2027) 快速构建 ADD 编程范式的完整脚手架。以「审计即基础设施」为核心,彻底打破编程过程黑盒与跨轮失忆,让编程范式进化为可审计、可追溯、可收敛的新时代。[本项目NPM包地址](https://www.npmjs.com/package/add-coder)
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
6
|
npx add-coder init
|
package/dist/index.js
CHANGED
|
@@ -191,6 +191,7 @@ var PLACEHOLDERS = {
|
|
|
191
191
|
"{{docsDir}}": "docsDir",
|
|
192
192
|
"{{logDir}}": "logDir",
|
|
193
193
|
"{{envFilePath}}": "envFilePath",
|
|
194
|
+
"{{magicDir}}": "magicDir",
|
|
194
195
|
"{{auditLoggerPath}}": "auditLoggerPath",
|
|
195
196
|
"{{mcpServerCommand}}": "mcpServerCommand",
|
|
196
197
|
"{{agentAuditImport}}": "agentAuditImport"
|
|
@@ -480,10 +481,14 @@ async function initCommand(options) {
|
|
|
480
481
|
console.log(`\u68C0\u6D4B\u5230 IDE: ${target}${specified ? " (--adapter)" : " (\u81EA\u52A8)"}`);
|
|
481
482
|
const config = await loadConfig(projectRoot, options.config, { yes: options.yes, force: options.force });
|
|
482
483
|
config.projectRoot = projectRoot;
|
|
484
|
+
const MAGIC_DIR_MAP = { claude: ".claude", qoder: ".qoder", vscode: ".vscode" };
|
|
485
|
+
const magicDir = MAGIC_DIR_MAP[target] || ".qoder";
|
|
486
|
+
console.log(`magicDir: ${magicDir}`);
|
|
487
|
+
const renderConfig = { ...config, magicDir };
|
|
483
488
|
if (!options.dryRun) {
|
|
484
489
|
await injectPrisma2(projectRoot, { yes: options.yes, force: options.force, dryRun: options.dryRun });
|
|
485
490
|
}
|
|
486
|
-
const coreFiles = renderCore(
|
|
491
|
+
const coreFiles = renderCore(renderConfig, !!options.dryRun);
|
|
487
492
|
console.log(`Core \u6A21\u677F: ${coreFiles.size} \u6587\u4EF6`);
|
|
488
493
|
const CORE_TARGETS = [".add", ".qoder", ".claude"];
|
|
489
494
|
const allFiles = /* @__PURE__ */ new Map();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "add-coder",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "[codein2027](https://github.com/xiaomingming92/codein2027) 快速构建 ADD 编程范式的完整脚手架——AI 代码治理的落地方案。以「审计即基础设施」为核心,彻底打破编程过程黑盒与跨轮失忆,让编程范式进化为可审计、可追溯、可收敛的新时代。npx 即用,人人可体验。",
|
|
6
6
|
"type": "module",
|
|
@@ -23,9 +23,9 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"
|
|
|
23
23
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
24
24
|
import { z } from "zod"
|
|
25
25
|
import { readFile, readdir, stat, mkdir, writeFile } from "fs/promises"
|
|
26
|
-
import { join, relative
|
|
26
|
+
import { join, relative } from "path"
|
|
27
27
|
import { existsSync } from "fs"
|
|
28
|
-
import {
|
|
28
|
+
import { spawnSync } from "child_process"
|
|
29
29
|
import { PrismaClient, Prisma } from "@prisma/client"
|
|
30
30
|
|
|
31
31
|
const prisma = new PrismaClient({
|
|
@@ -51,6 +51,31 @@ async function readFileSafe(filePath: string): Promise<string | null> {
|
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
// ── ADD 文档模板校验(共享函数,供 create_plan / check_add_compliance / check_spec_sync 等复用)──
|
|
55
|
+
|
|
56
|
+
interface GuardResult { ok: boolean; issues: string }
|
|
57
|
+
|
|
58
|
+
async function validateDocWithGuard(filePath: string): Promise<GuardResult> {
|
|
59
|
+
const guardScript = join(PROJECT_ROOT, MAGIC_DIR, "hooks", "doc-format-guard.sh")
|
|
60
|
+
if (!existsSync(guardScript)) return { ok: true, issues: "" }
|
|
61
|
+
const content = await readFileSafe(filePath)
|
|
62
|
+
if (!content) return { ok: false, issues: "文件无法读取" }
|
|
63
|
+
const guardInput = JSON.stringify({
|
|
64
|
+
tool_input: { file_path: filePath, file_content: content },
|
|
65
|
+
})
|
|
66
|
+
const guard = spawnSync("/bin/bash", [guardScript], {
|
|
67
|
+
input: guardInput,
|
|
68
|
+
encoding: "utf-8",
|
|
69
|
+
timeout: 15000,
|
|
70
|
+
cwd: PROJECT_ROOT,
|
|
71
|
+
env: { ...process.env, MAGIC_DIR },
|
|
72
|
+
})
|
|
73
|
+
if (guard.status === 2 || guard.stderr?.includes("校验不通过")) {
|
|
74
|
+
return { ok: false, issues: guard.stderr || guard.stdout || "校验失败" }
|
|
75
|
+
}
|
|
76
|
+
return { ok: true, issues: "" }
|
|
77
|
+
}
|
|
78
|
+
|
|
54
79
|
/** 递归读取目录下所有文件(扁平化返回),支持 ${MAGIC_DIR}/plans/2026-06/05/ 等分层结构 */
|
|
55
80
|
async function readdirRecursive(dir: string): Promise<string[]> {
|
|
56
81
|
const results: string[] = []
|
|
@@ -2080,7 +2105,9 @@ server.registerTool(
|
|
|
2080
2105
|
return errorResponse(`未找到匹配的 Plan 文件(关键词: ${args.planKeyword})`)
|
|
2081
2106
|
}
|
|
2082
2107
|
const planPath = join(plansDir, planMatch)
|
|
2108
|
+
const planGuard = await validateDocWithGuard(planPath)
|
|
2083
2109
|
lines.push(`Plan: ${planMatch}`)
|
|
2110
|
+
if (!planGuard.ok) lines.push(` ⚠️ 模板校验: ${planGuard.issues.split("\n")[0]}`)
|
|
2084
2111
|
|
|
2085
2112
|
// 2. 从 Plan 中提取关联的 spec 目录名
|
|
2086
2113
|
const planContent = await readFileSafe(planPath)
|
|
@@ -2387,9 +2414,10 @@ server.registerTool(
|
|
|
2387
2414
|
// 5. Git diff 统计
|
|
2388
2415
|
let changedFiles: string[] = []
|
|
2389
2416
|
if (isEnabled("gitDiff")) {
|
|
2390
|
-
const {
|
|
2417
|
+
const { spawnSync } = await import("child_process")
|
|
2391
2418
|
try {
|
|
2392
|
-
const
|
|
2419
|
+
const diff = spawnSync("git", ["diff", "--name-only"], { cwd: PROJECT_ROOT, encoding: "utf-8", timeout: 5000 })
|
|
2420
|
+
const diffStat = diff.stdout || ""
|
|
2393
2421
|
changedFiles = diffStat.trim().split("\n").filter(Boolean)
|
|
2394
2422
|
} catch {
|
|
2395
2423
|
lines.push("Git diff: 无法获取(可能无 git 仓库或无暂存变更)")
|
|
@@ -2576,6 +2604,9 @@ server.registerTool(
|
|
|
2576
2604
|
return errorResponse(`add-route 完整性扫描失败:无法读取文件 ${matchedFile}`)
|
|
2577
2605
|
}
|
|
2578
2606
|
|
|
2607
|
+
// 模板格式校验
|
|
2608
|
+
const routeGuard = await validateDocWithGuard(filePath)
|
|
2609
|
+
|
|
2579
2610
|
const lines = content.split("\n")
|
|
2580
2611
|
|
|
2581
2612
|
// === 3. 解析 Step 结构和勾选状态 ===
|
|
@@ -2681,6 +2712,7 @@ server.registerTool(
|
|
|
2681
2712
|
"",
|
|
2682
2713
|
`整体完成度: ${globalChecked}/${globalTotal} (${completionRate}%)`,
|
|
2683
2714
|
`状态: ${isComplete ? "✅ complete — add-route 完整闭环" : "⚠️ incomplete — 存在未勾选 Step"}`,
|
|
2715
|
+
`模板校验: ${routeGuard.ok ? "✅ 通过" : `⚠️ ${routeGuard.issues.split("\n")[0]}`}`,
|
|
2684
2716
|
"",
|
|
2685
2717
|
]
|
|
2686
2718
|
|
|
@@ -3198,8 +3230,9 @@ server.registerTool(
|
|
|
3198
3230
|
let scopeScore = 100
|
|
3199
3231
|
let changedFiles: string[] = []
|
|
3200
3232
|
try {
|
|
3201
|
-
const {
|
|
3202
|
-
const
|
|
3233
|
+
const { spawnSync } = await import("child_process")
|
|
3234
|
+
const diff = spawnSync("git", ["diff", "--name-only"], { cwd: PROJECT_ROOT, encoding: "utf-8", timeout: 5000 })
|
|
3235
|
+
const diffStat = diff.stdout || ""
|
|
3203
3236
|
changedFiles = diffStat.trim().split("\n").filter(Boolean)
|
|
3204
3237
|
} catch { /* ignore */ }
|
|
3205
3238
|
|
|
@@ -3240,12 +3273,13 @@ server.registerTool(
|
|
|
3240
3273
|
// ====== 维度二:类型安全(权重 20%) ======
|
|
3241
3274
|
let typeScore = 100
|
|
3242
3275
|
try {
|
|
3243
|
-
const {
|
|
3244
|
-
const
|
|
3276
|
+
const { spawnSync } = await import("child_process")
|
|
3277
|
+
const tsc = spawnSync("npx", ["tsc", "--noEmit"], {
|
|
3245
3278
|
cwd: PROJECT_ROOT,
|
|
3246
3279
|
encoding: "utf-8",
|
|
3247
3280
|
timeout: 30000,
|
|
3248
3281
|
})
|
|
3282
|
+
const tscOut = (tsc.stdout || "") + (tsc.stderr || "")
|
|
3249
3283
|
// 统计 error TS 行数
|
|
3250
3284
|
const errorLines = tscOut.split("\n").filter(l => l.includes("error TS")).length
|
|
3251
3285
|
typeScore = Math.max(0, 100 - errorLines * 10)
|
|
@@ -3431,20 +3465,28 @@ server.registerTool(
|
|
|
3431
3465
|
|
|
3432
3466
|
// 写入 Plan 文件
|
|
3433
3467
|
await writeFile(filePath, content, "utf-8")
|
|
3468
|
+
|
|
3469
|
+
// 写后校验
|
|
3470
|
+
const guard = await validateDocWithGuard(filePath)
|
|
3471
|
+
if (!guard.ok) {
|
|
3472
|
+
try { await import("fs/promises").then(m => m.unlink(filePath)) } catch { }
|
|
3473
|
+
return errorResponse(`Plan 模板校验不通过,拒绝写入:\n${guard.issues}`)
|
|
3474
|
+
}
|
|
3475
|
+
|
|
3434
3476
|
parts.push(`✅ Plan 文件已创建: ${relativePath}`)
|
|
3435
3477
|
|
|
3436
3478
|
// 更新 index.md
|
|
3437
3479
|
const indexScript = join(PROJECT_ROOT, "scripts", "gen-plan-index.sh")
|
|
3438
3480
|
if (existsSync(indexScript)) {
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
parts.push(`📋 index.md 已更新: ${result.trim()}`)
|
|
3446
|
-
}
|
|
3447
|
-
parts.push(`⚠️ index.md
|
|
3481
|
+
const result = spawnSync("/bin/bash", [indexScript], {
|
|
3482
|
+
cwd: PROJECT_ROOT,
|
|
3483
|
+
encoding: "utf-8",
|
|
3484
|
+
timeout: 10000,
|
|
3485
|
+
})
|
|
3486
|
+
if (result.status === 0) {
|
|
3487
|
+
parts.push(`📋 index.md 已更新: ${result.stdout?.trim() || "ok"}`)
|
|
3488
|
+
} else {
|
|
3489
|
+
parts.push(`⚠️ index.md 更新失败: ${result.stderr?.trim() || result.error?.message || "unknown"}`)
|
|
3448
3490
|
}
|
|
3449
3491
|
} else {
|
|
3450
3492
|
parts.push(`⚠️ gen-plan-index.sh 不存在,index.md 将在 crontab 自动更新`)
|