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 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(config, !!options.dryRun);
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.7",
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, basename } from "path"
26
+ import { join, relative } from "path"
27
27
  import { existsSync } from "fs"
28
- import { execSync } from "child_process"
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 { execSync } = await import("child_process")
2417
+ const { spawnSync } = await import("child_process")
2391
2418
  try {
2392
- const diffStat = execSync("git diff --name-only", { cwd: PROJECT_ROOT, encoding: "utf-8", timeout: 5000 })
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 { execSync } = await import("child_process")
3202
- const diffStat = execSync("git diff --name-only", { cwd: PROJECT_ROOT, encoding: "utf-8", timeout: 5000 })
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 { execSync } = await import("child_process")
3244
- const tscOut = execSync("npx tsc --noEmit 2>&1 || true", {
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
- try {
3440
- const result = execSync(`/bin/bash "${indexScript}"`, {
3441
- cwd: PROJECT_ROOT,
3442
- encoding: "utf-8",
3443
- timeout: 10000,
3444
- })
3445
- parts.push(`📋 index.md 已更新: ${result.trim()}`)
3446
- } catch (indexErr) {
3447
- parts.push(`⚠️ index.md 更新失败(将在 crontab 或下次调用时自动更新): ${indexErr instanceof Error ? indexErr.message : String(indexErr)}`)
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 自动更新`)