add-coder 0.3.23 → 0.3.25-0
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 +94 -15
- package/dist/index.js +736 -170
- package/package.json +2 -1
- package/templates/.add-coder-src-hash.json +10 -7
- package/templates/adapters/codex/config.toml.example +20 -0
- package/templates/core/scripts/db-ensure.sh +103 -0
- package/templates/core/scripts/mcp-server/shared/redact.ts +28 -0
- package/templates/core/scripts/mcp-server/shared/response.ts +3 -2
- package/templates/core/scripts/mcp-server/tools/gateway/check_rahs.ts +69 -2
- package/templates/core/scripts/mcp-server/tools/gateway/check_spec_sync.ts +1 -1
- package/templates/core/scripts/mcp-server/tools/index.ts +66 -1
- package/templates/core/scripts/mcp-server.ts +2 -1
- package/templates/core/templates/ports.example.md +49 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "add-coder",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.25-0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "[codein2027](https://github.com/xiaomingming92/codein2027) - A complete scaffolding for building the ADD programming paradigm, the implementation layer for AI code governance. Core principle: Audit as Infrastructure. Breaks the black-box programming process and cross-session amnesia, evolving programming paradigms into an auditable, traceable, and convergent new era. npx-ready.\n\n[codein2027](https://github.com/xiaomingming92/codein2027) 快速构建 ADD 编程范式的完整脚手架——AI 代码治理的落地方案。以「审计即基础设施」为核心,彻底打破编程过程黑盒与跨轮失忆,让编程范式进化为可审计、可追溯、可收敛的新时代。npx 即用,人人可体验。",
|
|
6
6
|
"repository": {
|
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
"email": "wujixmm@gmail.com",
|
|
41
41
|
"license": "AGPL-3.0-only",
|
|
42
42
|
"dependencies": {
|
|
43
|
+
"@ariga/atlas": "^1.3.0",
|
|
43
44
|
"@huggingface/transformers": "^3.8.1",
|
|
44
45
|
"@modelcontextprotocol/server": "2.0.0",
|
|
45
46
|
"commander": "^13.1.0",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"_version": "0.3.
|
|
2
|
+
"_version": "0.3.25-0",
|
|
3
3
|
"adapters/claude/hooks/doc-format-guard.sh": "ec59ad3f",
|
|
4
4
|
"adapters/claude/hooks/lib/common.sh": "4f3e64ad",
|
|
5
5
|
"adapters/claude/hooks/lib/context-inject.sh": "cfc8c65e",
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
"adapters/claude/hooks/subagent-stop.sh": "0f89e28c",
|
|
27
27
|
"adapters/claude/mcp.json": "4ddc5120",
|
|
28
28
|
"adapters/claude/settings.json": "067ccc45",
|
|
29
|
+
"adapters/codex/config.toml.example": "de1634aa",
|
|
29
30
|
"adapters/codex/hooks/doc-format-guard.sh": "8e134e78",
|
|
30
31
|
"adapters/codex/hooks/lib/common.sh": "4f3e64ad",
|
|
31
32
|
"adapters/codex/hooks/lib/context-inject.sh": "cfc8c65e",
|
|
@@ -180,7 +181,7 @@
|
|
|
180
181
|
"core/rules/profiles/webapp-profile.md": "e493ae7b",
|
|
181
182
|
"core/rules/project_rules.md": "82b25e08",
|
|
182
183
|
"core/rules/theory-practice-map.toml": "b1db99fd",
|
|
183
|
-
"core/scripts/db-ensure.sh": "
|
|
184
|
+
"core/scripts/db-ensure.sh": "94a5ff92",
|
|
184
185
|
"core/scripts/gen-plan-index.sh": "bfc6ad3e",
|
|
185
186
|
"core/scripts/mcp-server/elicitation/confirm.ts": "924857b8",
|
|
186
187
|
"core/scripts/mcp-server/elicitation/index.ts": "44c5dc3f",
|
|
@@ -202,7 +203,8 @@
|
|
|
202
203
|
"core/scripts/mcp-server/shared/package.json": "e3d5a4a2",
|
|
203
204
|
"core/scripts/mcp-server/shared/prisma.ts": "a9618f99",
|
|
204
205
|
"core/scripts/mcp-server/shared/project-root-strategy.ts": "1a5bdc57",
|
|
205
|
-
"core/scripts/mcp-server/shared/
|
|
206
|
+
"core/scripts/mcp-server/shared/redact.ts": "22e008aa",
|
|
207
|
+
"core/scripts/mcp-server/shared/response.ts": "0d1539a8",
|
|
206
208
|
"core/scripts/mcp-server/shared/run-command.ts": "42caf972",
|
|
207
209
|
"core/scripts/mcp-server/tasks/index.ts": "c040eb75",
|
|
208
210
|
"core/scripts/mcp-server/tasks/runner.ts": "d84bd35d",
|
|
@@ -214,20 +216,20 @@
|
|
|
214
216
|
"core/scripts/mcp-server/tools/gateway/check_add_route_completeness.ts": "b58e76ee",
|
|
215
217
|
"core/scripts/mcp-server/tools/gateway/check_add_route_status.ts": "23fbbb6b",
|
|
216
218
|
"core/scripts/mcp-server/tools/gateway/check_dps.ts": "4d599b38",
|
|
217
|
-
"core/scripts/mcp-server/tools/gateway/check_rahs.ts": "
|
|
218
|
-
"core/scripts/mcp-server/tools/gateway/check_spec_sync.ts": "
|
|
219
|
+
"core/scripts/mcp-server/tools/gateway/check_rahs.ts": "3ce99c7c",
|
|
220
|
+
"core/scripts/mcp-server/tools/gateway/check_spec_sync.ts": "8d6efd83",
|
|
219
221
|
"core/scripts/mcp-server/tools/gateway/helpers.ts": "f8794a92",
|
|
220
222
|
"core/scripts/mcp-server/tools/gateway/index.ts": "559acb75",
|
|
221
223
|
"core/scripts/mcp-server/tools/gateway.backup": "65eae393",
|
|
222
224
|
"core/scripts/mcp-server/tools/hitl.ts": "f447345f",
|
|
223
225
|
"core/scripts/mcp-server/tools/hook-event-report.ts": "3f21836e",
|
|
224
|
-
"core/scripts/mcp-server/tools/index.ts": "
|
|
226
|
+
"core/scripts/mcp-server/tools/index.ts": "3e6b6135",
|
|
225
227
|
"core/scripts/mcp-server/tools/plan.ts": "477fd80d",
|
|
226
228
|
"core/scripts/mcp-server/tools/quality.ts": "c4634453",
|
|
227
229
|
"core/scripts/mcp-server/tools/registrar.ts": "efd81b3a",
|
|
228
230
|
"core/scripts/mcp-server/tools/review.ts": "370f84f2",
|
|
229
231
|
"core/scripts/mcp-server/types.ts": "6c5e60bc",
|
|
230
|
-
"core/scripts/mcp-server.ts": "
|
|
232
|
+
"core/scripts/mcp-server.ts": "48b1fd69",
|
|
231
233
|
"core/skills/add-paradigm/SKILL.md": "5d4fa389",
|
|
232
234
|
"core/skills/session-init/SKILL.md": "bb1205b0",
|
|
233
235
|
"core/specs/add-coder-npm-package/checklist.md": "ed92032c",
|
|
@@ -252,6 +254,7 @@
|
|
|
252
254
|
"core/templates/hitl-template.md": "44989804",
|
|
253
255
|
"core/templates/hitl-template.schema.json": "e7ecc53e",
|
|
254
256
|
"core/templates/index.md": "0fb42ea5",
|
|
257
|
+
"core/templates/ports.example.md": "f5a7e436",
|
|
255
258
|
"core/templates/prd-incremental-template.md": "4a996ad5",
|
|
256
259
|
"core/templates/prd-incremental-template.schema.json": "bb44f022",
|
|
257
260
|
"core/templates/prd-standard-template.md": "abf639c2",
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Codex MCP 配置模板(真源)
|
|
2
|
+
# 渲染后分发到 .codex/ 与各 magic dirs templates/,供 --print-mcp-config 输出
|
|
3
|
+
# 使用方式:
|
|
4
|
+
# npx add-coder init --adapter=codex --print-mcp-config # stdout 输出片段
|
|
5
|
+
# npx add-coder init --adapter=codex --write-user-config # 显式确认后写入 ~/.codex/config.toml
|
|
6
|
+
#
|
|
7
|
+
# Windows 分支(renderer 按平台注入,无需手动修改):
|
|
8
|
+
# win32: command = "cmd /c" args = ["npx.cmd", "tsx", "..."] # .cmd 分支
|
|
9
|
+
# posix: command = "npx" args = ["tsx", "..."]
|
|
10
|
+
#
|
|
11
|
+
# 安全约定:
|
|
12
|
+
# - env 只引用环境变量({{projectRoot}} 由渲染时注入,密码/连接串禁止写入模板)
|
|
13
|
+
# - DATABASE_URL 请通过消费方 .env 或 IDE 环境传入,模板不包含任何连接串
|
|
14
|
+
|
|
15
|
+
[mcp_servers.add_coder]
|
|
16
|
+
command = "{{codexCommand}}"
|
|
17
|
+
args = ["tsx", "{{magicDir}}/scripts/mcp-server.ts"]
|
|
18
|
+
env = {
|
|
19
|
+
PROJECT_ROOT = "{{projectRoot}}",
|
|
20
|
+
}
|
|
@@ -82,3 +82,106 @@ fi
|
|
|
82
82
|
if [ "$DO_MIGRATE" = "true" ]; then
|
|
83
83
|
backup_add_tables
|
|
84
84
|
fi
|
|
85
|
+
|
|
86
|
+
# ════ Atlas 声明式同步模块(函数式;消费方日常变更同步入口,与 v2 引擎同源逻辑)════
|
|
87
|
+
# 触发:--migrate(init 流程)或宿主手动 `bash db-ensure.sh <engine> <container> --migrate`
|
|
88
|
+
# 依赖环境变量:DB_URL / ADD_DATABASE_URL(可选) / ATLAS_DEV_URL / PROJECT_NAME / DATABASE_USER
|
|
89
|
+
|
|
90
|
+
# ① atlas 可执行解析(三路径:add-coder 包内 → 顶层 .bin → PATH;无则走 npx --no-install)
|
|
91
|
+
resolve_atlas_bin() {
|
|
92
|
+
local b
|
|
93
|
+
for b in \
|
|
94
|
+
"$PROJECT_DIR/node_modules/add-coder/node_modules/.bin/atlas" \
|
|
95
|
+
"$PROJECT_DIR/node_modules/.bin/atlas" \
|
|
96
|
+
"$(command -v atlas 2>/dev/null || true)"; do
|
|
97
|
+
[ -n "$b" ] && [ -x "$b" ] && { echo "$b"; return 0; }
|
|
98
|
+
done
|
|
99
|
+
return 1
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
# ② atlas 命令执行器(本地 bin 或 npx --no-install)
|
|
103
|
+
atlas_cmd() {
|
|
104
|
+
local bin; bin="$(resolve_atlas_bin || true)"
|
|
105
|
+
if [ -n "$bin" ]; then "$bin" "$@"; else npx --no-install @ariga/atlas "$@"; fi
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
# ③ 目标构造(模式判定:分库=ADD 模型 / 共库=宿主 + 动态 exclude 非 ADD 表)
|
|
109
|
+
# 输出全局:TARGET_URL / SCHEMA_TARGET / EXCLUDE_ARGS
|
|
110
|
+
build_target() {
|
|
111
|
+
TARGET_URL=""
|
|
112
|
+
SCHEMA_TARGET="prisma/"
|
|
113
|
+
EXCLUDE_ARGS=()
|
|
114
|
+
local tables
|
|
115
|
+
if [ -n "${ADD_DATABASE_URL:-}" ]; then
|
|
116
|
+
TARGET_URL="${ADD_DATABASE_URL//?schema=public/}"
|
|
117
|
+
SCHEMA_TARGET="prisma/add.prisma"
|
|
118
|
+
echo ">>> Atlas 同步(分库模式: ADD 治理模型)..."
|
|
119
|
+
else
|
|
120
|
+
TARGET_URL="${DB_URL//?schema=public/}"
|
|
121
|
+
# 动态 exclude:库中除 ADD 7 表外的全部表(业务表/checkpoint/_prisma_migrations)——Atlas glob 不生效,需 public. 前缀精确名
|
|
122
|
+
tables="$(podman exec "${PROJECT_NAME:-add-project}-postgres" psql -U "${DATABASE_USER:-admin}" -d "${PROJECT_NAME:-add-project}" -tAc "SELECT string_agg('public.' || table_name, ',') FROM information_schema.tables WHERE table_schema='public' AND table_name NOT IN ('AddUser','DevOperation','AuditLog','HitlRecord','PlanRecord','ReviewRecord','CollabContract');" 2>/dev/null || true)"
|
|
123
|
+
[ -n "$tables" ] && EXCLUDE_ARGS=(--exclude "$tables")
|
|
124
|
+
echo ">>> Atlas 同步(共库模式: 仅 ADD 治理表,其余 $(echo "$tables" | tr ',' '\n' | wc -l) 张表排除)..."
|
|
125
|
+
fi
|
|
126
|
+
TARGET_URL="${TARGET_URL}?sslmode=disable"
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
# ④ baseline 生成(同源:Prisma schema SQL,过滤 Prisma 7 ◇ 提示)
|
|
130
|
+
generate_baseline() {
|
|
131
|
+
BASELINE_SQL="$(mktemp /tmp/atlas-target.XXXXXX.sql)"
|
|
132
|
+
trap 'rm -f "$BASELINE_SQL"' EXIT
|
|
133
|
+
npx prisma migrate diff --from-empty --to-schema "$SCHEMA_TARGET" --script 2>/dev/null | sed '/^◇/d' > "$BASELINE_SQL"
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
# ⑤ diff 检测(SQL 语句特征判定:Atlas 无变更时输出 "Schemas are synced..." 非空,不算变更)
|
|
137
|
+
# 输出全局 DIFF_SQL;返回 0=有变更 / 1=无变更
|
|
138
|
+
run_atlas_diff() {
|
|
139
|
+
DIFF_SQL="$(atlas_cmd schema diff --from "$TARGET_URL" --to "file://$BASELINE_SQL" --dev-url "$ATLAS_DEV_URL" "${EXCLUDE_ARGS[@]}" 2>/dev/null)"
|
|
140
|
+
echo "$DIFF_SQL" | grep -qE "^(CREATE|ALTER|DROP|COMMENT|-- *(Create|Modify|Drop))"
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
# ⑥ apply(确认门槛:交互输出 SQL → 确认 → apply;拒绝则跳过)
|
|
144
|
+
apply_atlas_diff() {
|
|
145
|
+
echo "=== 待应用 diff SQL(前 60 行)==="
|
|
146
|
+
echo "$DIFF_SQL" | head -60
|
|
147
|
+
read -rp "应用以上 schema 变更?[y/N] " ANS
|
|
148
|
+
if [ "$ANS" = "y" ] || [ "$ANS" = "yes" ]; then
|
|
149
|
+
atlas_cmd schema apply --url "$TARGET_URL" --to "file://$BASELINE_SQL" --dev-url "$ATLAS_DEV_URL" "${EXCLUDE_ARGS[@]}"
|
|
150
|
+
echo ">>> Atlas 同步完成"
|
|
151
|
+
else
|
|
152
|
+
echo ">>> 已取消,未应用"
|
|
153
|
+
fi
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
# ⑦ Atlas 同步主流程(探测 → dev-url → 目标 → baseline → diff → apply)
|
|
157
|
+
atlas_sync() {
|
|
158
|
+
[ "$ENGINE" = "sqlite" ] && return 0
|
|
159
|
+
# 迁移锁(进程层契约 v2 §6):多 IDE 并发 init 时仅一个进程执行迁移
|
|
160
|
+
# 非阻塞拿锁(pg_try_advisory_lock),会话级锁——脚本退出连接断开自动释放
|
|
161
|
+
LOCK_KEY="0xADD001"
|
|
162
|
+
LOCKED="$(podman exec "${PROJECT_NAME:-add-project}-postgres" psql -U "${DATABASE_USER:-admin}" -d "${PROJECT_NAME:-add-project}" -tAc "SELECT pg_try_advisory_lock(${LOCK_KEY});" 2>/dev/null || true)"
|
|
163
|
+
if [ "$LOCKED" != "t" ]; then
|
|
164
|
+
echo "!!! 另一个进程正在迁移,请稍后重试"
|
|
165
|
+
return 1
|
|
166
|
+
fi
|
|
167
|
+
if ! atlas_cmd version > /dev/null 2>&1; then
|
|
168
|
+
echo "!!! Atlas 不可用。add-coder sync --patch 可自动安装 @ariga/atlas;或手动: pnpm add -D @ariga/atlas"
|
|
169
|
+
echo " 降级路径: prisma-diff(免 shadow)→ db-push + 强制备份;文档: README「Atlas 数据库同步能力」"
|
|
170
|
+
return 1
|
|
171
|
+
fi
|
|
172
|
+
if [ -z "${ATLAS_DEV_URL:-}" ]; then
|
|
173
|
+
echo "!!! ATLAS_DEV_URL 未配置。请运行 add-coder init(分库引导自动创建 {project}-add-dev 常驻容器并登记)或手动配置"
|
|
174
|
+
return 1
|
|
175
|
+
fi
|
|
176
|
+
build_target
|
|
177
|
+
generate_baseline
|
|
178
|
+
if run_atlas_diff; then
|
|
179
|
+
apply_atlas_diff
|
|
180
|
+
else
|
|
181
|
+
echo ">>> schema 一致(幂等出口)"
|
|
182
|
+
fi
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if [ "$DO_MIGRATE" = "true" ]; then
|
|
186
|
+
atlas_sync
|
|
187
|
+
fi
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* @Author : xiaomingming wujixmm@gmail.com
|
|
3
|
+
* @Date : 2026-08-10 18:41:34
|
|
4
|
+
* @LastEditors : xiaomingming wujixmm@gmail.com
|
|
5
|
+
* @LastEditTime : 2026-08-10 18:41:35
|
|
6
|
+
* @FilePath : /farm-agent/home/xmm/ai/add-coder/templates/core/scripts/mcp-server/shared/redact.ts
|
|
7
|
+
* @Description :
|
|
8
|
+
*/
|
|
9
|
+
// 敏感信息脱敏(进程层并发契约 v2:日志/错误不泄露 DATABASE_URL/密码)
|
|
10
|
+
// 统一入口:response.ts 的 textResponse/errorResponse 内部调用,一处封装全覆盖所有工具
|
|
11
|
+
|
|
12
|
+
const CONNECTION_URL_RE =
|
|
13
|
+
/(postgres(?:ql)?|mysql|redis|mongodb(?:\+srv)?):\/\/[^\s"'`]+/gi
|
|
14
|
+
|
|
15
|
+
export function redactUrl(url: string): string {
|
|
16
|
+
// postgres://user:pass@host:port/db → postgres://user:****@host:port/db
|
|
17
|
+
return url.replace(/(:\/\/[^:/\s]+:)([^@/\s]+)(@)/, "$1****$3")
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function redact(text: string): string {
|
|
21
|
+
return text.replace(CONNECTION_URL_RE, (m) => {
|
|
22
|
+
try {
|
|
23
|
+
return redactUrl(m)
|
|
24
|
+
} catch {
|
|
25
|
+
return m
|
|
26
|
+
}
|
|
27
|
+
})
|
|
28
|
+
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import type { ToolResponse } from "../types.js"
|
|
2
|
+
import { redact } from "./redact.js"
|
|
2
3
|
|
|
3
4
|
export function textResponse(text: string): { content: ToolResponse } {
|
|
4
|
-
return { content: [{ type: "text", text }] }
|
|
5
|
+
return { content: [{ type: "text", text: redact(text) }] }
|
|
5
6
|
}
|
|
6
7
|
|
|
7
8
|
export function errorResponse(message: string): { content: ToolResponse; isError: boolean } {
|
|
8
|
-
return { content: [{ type: "text", text: message }], isError: true }
|
|
9
|
+
return { content: [{ type: "text", text: redact(message) }], isError: true }
|
|
9
10
|
}
|
|
@@ -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 } from "path";
|
|
12
|
+
import { join, basename } from "path";
|
|
13
13
|
import { textResponse, errorResponse } from "../../shared/response.js";
|
|
14
14
|
import {
|
|
15
15
|
readFileSafe,
|
|
@@ -42,7 +42,9 @@ export function registerCheckRahs(server: ToolRegistrar) {
|
|
|
42
42
|
);
|
|
43
43
|
const pm = apf.find(
|
|
44
44
|
(f) =>
|
|
45
|
-
f.toLowerCase().includes(pp.toLowerCase()) &&
|
|
45
|
+
f.toLowerCase().includes(pp.toLowerCase()) &&
|
|
46
|
+
f.includes("-plan-v") &&
|
|
47
|
+
!f.includes(".hitl"),
|
|
46
48
|
);
|
|
47
49
|
if (!pm)
|
|
48
50
|
return errorResponse(`未找到匹配的 Plan 文件(关键词: ${pp})`);
|
|
@@ -80,6 +82,71 @@ export function registerCheckRahs(server: ToolRegistrar) {
|
|
|
80
82
|
})) as Array<{ id: string }>;
|
|
81
83
|
auditScore = Math.min(100, logs.length * 10);
|
|
82
84
|
} 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 {}
|
|
83
150
|
const parts = [
|
|
84
151
|
"=== RAHS:Runtime Architecture Health Score ===",
|
|
85
152
|
`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)`/g) || [])
|
|
72
|
+
const appendixFiles = (arContent.match(/`[^`]+\.(ts|js|sh|md|tsx|json|yml|yaml)`/g) || [])
|
|
73
73
|
.map((f: string) => f.replace(/`/g, ""));
|
|
74
74
|
lines.push(`附录文件: ${appendixFiles.length} 个`);
|
|
75
75
|
|
|
@@ -12,6 +12,61 @@ import { registerReviewTools } from "./review.js"
|
|
|
12
12
|
import { PROJECT_ID } from "../shared/env.js"
|
|
13
13
|
import type { ToolRegistrar } from "./registrar.js"
|
|
14
14
|
|
|
15
|
+
// ── 读写分级信号量节流(进程层并发契约 v2 §7:Codex Parallel MCP 真并行兑底) ──
|
|
16
|
+
// 读工具共享 8 并发;写工具共享 4 并发;超限请求排队(MCP 协议允许延迟响应,排队即天然反压)
|
|
17
|
+
const READ_MAX = 8
|
|
18
|
+
const WRITE_MAX = 4
|
|
19
|
+
|
|
20
|
+
const READ_TOOLS = new Set([
|
|
21
|
+
"get_project_context", "find_related_docs", "get_db_schema", "query_audit_logs",
|
|
22
|
+
"plan_status", "review_status", "status_hitl", "contract_status",
|
|
23
|
+
"check_dps", "check_rahs", "check_add_route_status", "check_spec_sync",
|
|
24
|
+
"check_add_route_completeness", "check_phase_symmetry", "check_failure_path",
|
|
25
|
+
"check_add_compliance", "get_hook_events",
|
|
26
|
+
])
|
|
27
|
+
|
|
28
|
+
function createSemaphore(limit: number) {
|
|
29
|
+
let active = 0
|
|
30
|
+
const queue: (() => void)[] = []
|
|
31
|
+
const acquire = () =>
|
|
32
|
+
new Promise<void>((resolve) => {
|
|
33
|
+
if (active < limit) {
|
|
34
|
+
active++
|
|
35
|
+
resolve()
|
|
36
|
+
} else {
|
|
37
|
+
queue.push(resolve)
|
|
38
|
+
}
|
|
39
|
+
})
|
|
40
|
+
const release = () => {
|
|
41
|
+
active--
|
|
42
|
+
const next = queue.shift()
|
|
43
|
+
if (next) {
|
|
44
|
+
active++
|
|
45
|
+
next()
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return { acquire, release }
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// 429 指数退避重试(3 次,250ms 起步,倍率 2x):上游限流不直接透传
|
|
52
|
+
async function withRetry<T>(fn: () => Promise<T>): Promise<T> {
|
|
53
|
+
let lastErr: unknown
|
|
54
|
+
for (let attempt = 1; attempt <= 3; attempt++) {
|
|
55
|
+
try {
|
|
56
|
+
return await fn()
|
|
57
|
+
} catch (e) {
|
|
58
|
+
lastErr = e
|
|
59
|
+
const msg = e instanceof Error ? e.message : String(e)
|
|
60
|
+
if (!/429|rate.?limit|限流/i.test(msg)) throw e
|
|
61
|
+
await new Promise((r) => setTimeout(r, 250 * 2 ** (attempt - 1)))
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
throw lastErr
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const readSem = createSemaphore(READ_MAX)
|
|
68
|
+
const writeSem = createSemaphore(WRITE_MAX)
|
|
69
|
+
|
|
15
70
|
export function registerAllTools(server: McpServer) {
|
|
16
71
|
// D9 多 MCP 路由安全:派生装饰版 registrar,为所有工具 description 注入项目身份前缀
|
|
17
72
|
const origRegister = server.registerTool.bind(server)
|
|
@@ -20,10 +75,20 @@ export function registerAllTools(server: McpServer) {
|
|
|
20
75
|
type ToolCb = Parameters<RegisterTool>[2]
|
|
21
76
|
const registerTool = ((name: string, config: ToolConfig, cb: ToolCb) => {
|
|
22
77
|
const c = config as { description?: string }
|
|
78
|
+
// 读写分级节流 + 429 退避(进程层契约 v2:Codex 并行调用兜底)
|
|
79
|
+
const sem = READ_TOOLS.has(name) ? readSem : writeSem
|
|
80
|
+
const throttledCb = (async (...args: unknown[]) => {
|
|
81
|
+
await sem.acquire()
|
|
82
|
+
try {
|
|
83
|
+
return await withRetry(async () => cb(...(args as Parameters<ToolCb>)))
|
|
84
|
+
} finally {
|
|
85
|
+
sem.release()
|
|
86
|
+
}
|
|
87
|
+
}) as ToolCb
|
|
23
88
|
return origRegister(
|
|
24
89
|
name,
|
|
25
90
|
{ ...config, description: `[项目: ${PROJECT_ID}] ${c.description ?? ""}` },
|
|
26
|
-
|
|
91
|
+
throttledCb,
|
|
27
92
|
)
|
|
28
93
|
}) as RegisterTool
|
|
29
94
|
const registrar: ToolRegistrar = { registerTool }
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { McpServer } from "@modelcontextprotocol/server"
|
|
2
2
|
import { StdioServerTransport } from "@modelcontextprotocol/server/stdio"
|
|
3
3
|
import { registerAll } from "./mcp-server/index.js"
|
|
4
|
+
import { redact } from "./mcp-server/shared/redact.js"
|
|
4
5
|
|
|
5
6
|
async function main() {
|
|
6
7
|
const server = new McpServer(
|
|
@@ -14,6 +15,6 @@ async function main() {
|
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
main().catch((error) => {
|
|
17
|
-
console.error("[ADD-MCP] Fatal error:", error)
|
|
18
|
+
console.error("[ADD-MCP] Fatal error:", redact(error instanceof Error ? error.message : String(error)))
|
|
18
19
|
process.exit(1)
|
|
19
20
|
})
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# 端口契约登记表({{projectName}})
|
|
2
|
+
|
|
3
|
+
> **定位**:本项目开发环境端口统一登记表,是项目本地端口分配的唯一事实源。
|
|
4
|
+
> **维护**:新增容器/服务端口前必须先查本表;改端口后必须同步更新 `.env.development` 变量、`DATABASE_URL`/`SHADOW_DATABASE_URL`/`ATLAS_DEV_URL` 与本表三处。
|
|
5
|
+
> **最后更新**: (登记日期,按实际填写)
|
|
6
|
+
> ⚠️ 跨项目共享端口(如本机 5433/5434/5437 等)以跨项目事实源 `docs/ports.md`(如 farm-agent)为准,本项目表只登记本项目端口。
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## 1. 端口分配总表
|
|
11
|
+
|
|
12
|
+
> **init 自动登记**:`add-coder init`(分库引导)与 Atlas 同步会自动为「主库 / ADD 库 / Atlas dev 库」三类容器分配端口并登记本表——分配规则见 §4 约定规则。以下为示例行。
|
|
13
|
+
|
|
14
|
+
| 端口 | 服务 | 用途 | 状态 | 配置位置 |
|
|
15
|
+
|:---:|------|------|:---:|---------|
|
|
16
|
+
| 5432 | PostgreSQL 宿主库 | 宿主业务库(PG 标准端口,示例) | 🟡 示例 | 宿主自有配置 |
|
|
17
|
+
| 5433 | PostgreSQL ADD 配套库 | add-coder 配套容器第一顺位:`{{projectName}}-add-postgres`(宿主 5432 之后从 5433 起,示例) | 🟡 示例 | `.env.development` `ADD_DATABASE_URL` |
|
|
18
|
+
| 5434 | PostgreSQL Atlas dev 库 | Atlas 常驻 dev 空库 `{{projectName}}-add-dev`(可随时重置,示例) | 🟡 示例 | `.env.development` `ATLAS_DEV_URL` |
|
|
19
|
+
| 3000 | Web dev server | 前端/服务开发端口(示例) | 🟡 示例 | `.env.development` `PORT` |
|
|
20
|
+
|
|
21
|
+
> 表头为固定规范;行数据为**示例**,按项目实际情况增删改。
|
|
22
|
+
|
|
23
|
+
## 2. 已知冲突与处置
|
|
24
|
+
|
|
25
|
+
| # | 端口 | 冲突方 A | 冲突方 B | 处置 |
|
|
26
|
+
|:---:|:---:|---------|---------|------|
|
|
27
|
+
| 1 | 5433 | 本项目默认值 | 其他项目同默认值 | 🟡 示例:已改用空闲端口并登记 |
|
|
28
|
+
|
|
29
|
+
## 3. 容器快照
|
|
30
|
+
|
|
31
|
+
### 运行中
|
|
32
|
+
|
|
33
|
+
| 容器 | 端口 | 归属 |
|
|
34
|
+
|------|------|------|
|
|
35
|
+
| `{{projectName}}-add-postgres` | 5433 | 本项目 ADD 治理库(分库引导,示例) |
|
|
36
|
+
| `{{projectName}}-add-dev` | 5434 | 本项目 Atlas dev 空库(可随时重置,示例) |
|
|
37
|
+
|
|
38
|
+
### Created / Exited(历史容器,端口已释放)
|
|
39
|
+
|
|
40
|
+
`—`(示例:按 `podman ps -a` 实际登记)
|
|
41
|
+
|
|
42
|
+
## 4. 约定规则
|
|
43
|
+
|
|
44
|
+
1. **改端口 = 三处同步**:`.env.development` 变量 + `DATABASE_URL`/`ADD_DATABASE_URL`/`SHADOW_DATABASE_URL`/`ATLAS_DEV_URL` + compose 引用,改完更新本表
|
|
45
|
+
2. **新增服务**:先查本表 + 跨项目事实源取空闲端口;**PG 配套从 5433 起**(宿主标准 5432 之后第一顺位),按服务类型分段(PG=5433+ / Web=3xxx / MCP=30xx),避免默认值撞车
|
|
46
|
+
3. **init 全局登记(统一分配器)**:`add-coder init` 通过统一端口分配器为「主库 / ADD 库 / Atlas dev 库」一次性分配并登记本表——分配前先读本表已有登记(已登记端口复用不重复分配)与跨项目事实源,再**从 5433 起扫描真实空闲**(podman ps + `portInUse` 探测),分配后写入本表;**禁止各模块自行分散扫描端口**(建议起点可在端口规则 `ports-rules.toml` 调整)
|
|
47
|
+
4. **Atlas dev 库同表登记**:dev-url 常驻空库与主库同表登记,状态列标注「dev 库」;dev 库**可随时重置,不是数据真源**,重置后无需迁移备份
|
|
48
|
+
5. **删除容器前**:确认数据卷是否需要保留(容器删了卷还在)
|
|
49
|
+
6. **端口检测**:`podman ps --format '{{.Names}} {{.Ports}}'` 为准,`ss -tlnp` 为辅
|