@hupan56/wlkj 3.3.14 → 3.3.15
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/bin/cli.js +16 -1
- package/package.json +1 -1
- package/templates/qoder/commands/optional/wl-spec.md +4 -2
- package/templates/qoder/commands/wl-commit.md +3 -1
- package/templates/qoder/commands/wl-init.md +2 -2
- package/templates/qoder/commands/wl-prd.md +24 -0
- package/templates/qoder/commands/wl-search.md +18 -11
- package/templates/qoder/commands/wl-task.md +3 -1
- package/templates/qoder/config.yaml +0 -6
- package/templates/qoder/contracts/insight.md +55 -0
- package/templates/qoder/hooks/pre-tool-use-commit.py +124 -0
- package/templates/qoder/hooks/session-start.py +20 -1
- package/templates/qoder/hooks/stop-eval.py +129 -0
- package/templates/qoder/scripts/capability/adapters/mcp.py +9 -1
- package/templates/qoder/scripts/capability/registry.py +0 -3
- package/templates/qoder/scripts/capability/registry_mcp.py +10 -4
- package/templates/qoder/scripts/deployment/setup/init_doctor.py +6 -2
- package/templates/qoder/scripts/deployment/setup/install_qoderwork.py +7 -7
- package/templates/qoder/scripts/deployment/setup/setup.py +5 -2
- package/templates/qoder/scripts/domain/integration/return_to_platform.py +4 -2
- package/templates/qoder/scripts/domain/integration/spec_upload.py +4 -4
- package/templates/qoder/scripts/domain/kg/build/build_entity_registry.py +12 -12
- package/templates/qoder/scripts/domain/kg/build/build_relations.py +12 -12
- package/templates/qoder/scripts/domain/kg/graph/kg_semantic.py +41 -5
- package/templates/qoder/scripts/domain/kg/kg.py +14 -0
- package/templates/qoder/scripts/domain/kg/switch_project.py +2 -2
- package/templates/qoder/scripts/domain/task/wlkj_panel.py +169 -14
- package/templates/qoder/scripts/foundation/io/context_cache.py +94 -0
- package/templates/qoder/scripts/orchestration/wlkj.py +106 -0
- package/templates/qoder/scripts/tool_guide.md +70 -0
- package/templates/qoder/scripts/validation/eval/alignment_matrix.py +176 -0
- package/templates/qoder/scripts/validation/eval/bf2_content_fidelity.py +110 -0
- package/templates/qoder/scripts/validation/eval/bf2_llmjudge.py +104 -0
- package/templates/qoder/scripts/validation/eval/bf_score.py +218 -0
- package/templates/qoder/scripts/validation/eval/code_flywheel.py +150 -0
- package/templates/qoder/scripts/validation/eval/dispatcher_ab.py +156 -0
- package/templates/qoder/scripts/validation/eval/dispatcher_ab_2026-07-21.json +23 -0
- package/templates/qoder/scripts/validation/eval/feature_fidelity_flywheel.py +143 -0
- package/templates/qoder/scripts/validation/eval/gradient_matrix.py +261 -0
- package/templates/qoder/scripts/validation/eval/gradient_matrix_baseline_2026-07-21.json +33 -0
- package/templates/qoder/scripts/validation/eval/metrics_dashboard.py +105 -0
- package/templates/qoder/scripts/validation/eval/multi_turn_flywheel.py +118 -0
- package/templates/qoder/scripts/validation/eval/prd_fidelity_flywheel.py +128 -0
- package/templates/qoder/scripts/validation/eval/prd_flywheel.py +148 -0
- package/templates/qoder/scripts/validation/eval/prototype_fidelity_flywheel.py +166 -0
- package/templates/qoder/scripts/validation/eval/recall_flywheel.py +148 -0
- package/templates/qoder/scripts/validation/eval/robustness_flywheel.py +139 -0
- package/templates/qoder/scripts/validation/eval/speed_accuracy_flywheel.py +188 -0
- package/templates/qoder/scripts/validation/eval/task_flywheel.py +124 -0
- package/templates/qoder/scripts/validation/eval/token_flywheel.py +88 -0
- package/templates/qoder/scripts/validation/metrics/eval_code_ac.py +177 -0
- package/templates/qoder/scripts/validation/metrics/lint_cases.py +170 -0
- package/templates/qoder/scripts/validation/test/test_context_cache.py +78 -0
- package/templates/qoder/scripts/validation/test/test_lint_cases.py +60 -0
- package/templates/qoder/scripts/validation/test/test_pre_tool_use_commit.py +70 -0
- package/templates/qoder/settings.json +8 -0
- package/templates/qoder/skills/wl-spec/SKILL.md +1 -1
- package/templates/qoder/agents/design-agent.md +0 -20
- package/templates/qoder/agents/spec-generator.md +0 -21
- package/templates/qoder/scripts/capability/smoke_test_report.json.new +0 -94
- package/templates/qoder/scripts/domain/kg/extract/asset/__init__.py +0 -10
- package/templates/qoder/scripts/domain/kg/extract/asset/asset_tree.py +0 -57
- package/templates/qoder/scripts/domain/kg/extract/asset/discussion_importer.py +0 -62
- package/templates/qoder/scripts/domain/kg/extract/asset/prd_importer.py +0 -146
- package/templates/qoder/scripts/domain/kg/extract/asset/prototype_importer.py +0 -64
- package/templates/qoder/scripts/domain/kg/extract/asset/returns_importer.py +0 -52
- package/templates/qoder/scripts/domain/kg/extract/build_goal3.py +0 -104
- package/templates/qoder/scripts/domain/kg/extract/build_goal4.py +0 -55
- package/templates/qoder/scripts/domain/kg/extract/build_goal5.py +0 -95
- package/templates/qoder/scripts/domain/kg/extract/db/__init__.py +0 -8
- package/templates/qoder/scripts/domain/kg/extract/db/data_profile.py +0 -22
- package/templates/qoder/scripts/domain/kg/extract/db/fk_extractor.py +0 -55
- package/templates/qoder/scripts/domain/kg/extract/db/schema_extractor.py +0 -90
- package/templates/qoder/scripts/domain/kg/extract/inference/__init__.py +0 -9
- package/templates/qoder/scripts/domain/kg/extract/inference/community_summarizer.py +0 -206
- package/templates/qoder/scripts/domain/kg/extract/inference/embed_builder.py +0 -132
- package/templates/qoder/scripts/domain/kg/extract/inference/naming_matcher.py +0 -80
- package/templates/qoder/scripts/domain/kg/extract/inference/promote.py +0 -59
- package/templates/qoder/scripts/domain/kg/extract/inference/recompute.py +0 -93
- package/templates/qoder/scripts/domain/kg/extract/inference/weak_link.py +0 -421
- package/templates/qoder/scripts/domain/kg/extract/mybatis/__init__.py +0 -9
- package/templates/qoder/scripts/domain/kg/extract/mybatis/all.py +0 -79
- package/templates/qoder/scripts/domain/kg/extract/mybatis/mapper_parser.py +0 -99
- package/templates/qoder/scripts/domain/kg/extract/mybatis/relation_builder.py +0 -69
- package/templates/qoder/scripts/domain/kg/extract/mybatis/sql_extractor.py +0 -78
- package/templates/qoder/scripts/domain/kg/extract/prd/__init__.py +0 -8
- package/templates/qoder/scripts/domain/kg/extract/prd/prd_chunk_embed.py +0 -105
- package/templates/qoder/scripts/domain/kg/extract/prd/prd_llm_extract.py +0 -153
- package/templates/qoder/scripts/domain/kg/extract/prd/req_anchor.py +0 -120
- package/templates/qoder/scripts/domain/kg/graph/kg_link_db.py +0 -235
- package/templates/qoder/scripts/domain/kg/search/enrich_prompt.py +0 -238
- package/templates/qoder/scripts/domain/kg/server/perf_bench.py +0 -197
- package/templates/qoder/scripts/domain/kg/sync_repowiki.py +0 -109
- package/templates/qoder/scripts/engine/poller.py +0 -219
package/bin/cli.js
CHANGED
|
@@ -383,7 +383,7 @@ async function doInit(name, roleArg) {
|
|
|
383
383
|
console.log(`\n--- 自动初始化 ---`);
|
|
384
384
|
const setupPath = path.join(cwd, ".qoder", "scripts", "deployment", "setup", "setup.py");
|
|
385
385
|
if (fs.existsSync(setupPath)) {
|
|
386
|
-
const setupArgs = [name, role, "--skip-cron", "--skip-qoderwork"].filter(Boolean);
|
|
386
|
+
const setupArgs = [name, role, "--skip-cron", "--skip-qoderwork", "--skip-code"].filter(Boolean);
|
|
387
387
|
try {
|
|
388
388
|
const cmd = `python "${setupPath}" ${setupArgs.map(a => `"${a}"`).join(" ")}`;
|
|
389
389
|
console.log(` 运行: setup.py ${setupArgs.join(" ")}`);
|
|
@@ -443,6 +443,21 @@ async function doInit(name, roleArg) {
|
|
|
443
443
|
}
|
|
444
444
|
}
|
|
445
445
|
|
|
446
|
+
// === 6.6. 连云平台 bind 项目(云模式:KG 在云平台,引擎调云 MCP)===
|
|
447
|
+
if (pyCmd) {
|
|
448
|
+
const switchScript = path.join(cwd, ".qoder", "scripts", "domain", "kg", "switch_project.py");
|
|
449
|
+
if (fs.existsSync(switchScript)) {
|
|
450
|
+
console.log(`\n--- 连云平台 bind 项目 ---`);
|
|
451
|
+
console.log(` 问平台邮箱/密码 → 拉项目列表 → 选项目 → 写 mcp_config(连云)`);
|
|
452
|
+
try {
|
|
453
|
+
const env = { ...process.env, PYTHONIOENCODING: "utf-8", PYTHONUTF8: "1" };
|
|
454
|
+
execSync(`${pyCmd} "${switchScript}"`, { cwd, stdio: "inherit", timeout: 180000, env });
|
|
455
|
+
} catch (e) {
|
|
456
|
+
console.log(` [WARN] 云 bind 未完成,可手动: python .qoder/scripts/domain/kg/switch_project.py`);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
446
461
|
// === 7. 写版本戳 ===
|
|
447
462
|
writeEngineVersion(cwd);
|
|
448
463
|
|
package/package.json
CHANGED
|
@@ -12,7 +12,7 @@ User input:
|
|
|
12
12
|
|
|
13
13
|
## Auto Generate Spec
|
|
14
14
|
1. Read current task PRD and design artifacts
|
|
15
|
-
2.
|
|
15
|
+
2. dispatch 到 `qoder-spec-gen` sub-agent(.qoder/agents/qoder-spec-gen.toml — 它读 spec-generator skill + spec 契约自动生成)
|
|
16
16
|
3. Generate spec.md based on PRD + Design
|
|
17
17
|
4. ★ 契约头**必须**写 `status: draft`(wl-code gate 依据):
|
|
18
18
|
```markdown
|
|
@@ -26,7 +26,9 @@ User input:
|
|
|
26
26
|
6. Notify dev to review — 提示用户审阅后回复"确认",确认后改 `status: confirmed`
|
|
27
27
|
7. ★ 用户确认后,上传 spec 到平台(按 zentao_id 反查需求挂载 → 资产中心随需求可见,无需发布):
|
|
28
28
|
```bash
|
|
29
|
-
python
|
|
29
|
+
R=$(python ~/.qoderwork/repo_root.py 2>/dev/null || python3 ~/.qoderwork/repo_root.py 2>/dev/null) || R=.
|
|
30
|
+
PY=$(python --version >/dev/null 2>&1 && echo python || echo python3)
|
|
31
|
+
$PY "$R/.qoder/scripts/orchestration/wlkj.py" spec-upload <spec.md> --zentao-id <禅道story号> [--code-targets CtrlA,SvcB]
|
|
30
32
|
```
|
|
31
33
|
- 契约头无 `platform_spec_id` → `create_spec(zentao_id,...)` 新建 + 平台反查 Requirement 挂上 → `confirm_spec` 进 confirmed → 回写 `platform_spec_id`(幂等锚)
|
|
32
34
|
- 契约头有 `platform_spec_id` → 自动走 `update_spec`(version+1,不重复建)
|
|
@@ -22,7 +22,9 @@ User input: $ARGUMENTS (commit message)
|
|
|
22
22
|
7. Push to remote
|
|
23
23
|
8. 回流平台:提交 + push 后执行回流,把本次 commit 沉淀到平台知识层
|
|
24
24
|
```bash
|
|
25
|
-
python
|
|
25
|
+
R=$(python ~/.qoderwork/repo_root.py 2>/dev/null || python3 ~/.qoderwork/repo_root.py 2>/dev/null) || R=.
|
|
26
|
+
PY=$(python --version >/dev/null 2>&1 && echo python || echo python3)
|
|
27
|
+
$PY "$R/.qoder/scripts/orchestration/wlkj.py" return commit --last
|
|
26
28
|
```
|
|
27
29
|
- 自动取 HEAD commit(sha/message/author/时间)→ POST 平台 `/api/git/commits/record`
|
|
28
30
|
- message 里的 `[#REQ-xxx]` / `[#<禅道号>]` 平台自动解析,建 commit→需求 边
|
|
@@ -70,7 +70,7 @@ grep -l WLKJ_PROJECT_ID "$R/.qoder/mcp_config.json" 2>/dev/null && echo "已绑
|
|
|
70
70
|
|
|
71
71
|
### 未绑定或要切换?一行命令:
|
|
72
72
|
```bash
|
|
73
|
-
$PY "$R/.qoder/scripts/
|
|
73
|
+
$PY "$R/.qoder/scripts/orchestration/wlkj.py" switch-project
|
|
74
74
|
```
|
|
75
75
|
脚本会:
|
|
76
76
|
1. 问邮箱+密码(或用 --email/--pw 参数)
|
|
@@ -80,7 +80,7 @@ $PY "$R/.qoder/scripts/domain/kg/switch_project.py"
|
|
|
80
80
|
|
|
81
81
|
非交互模式(CI/自动化):
|
|
82
82
|
```bash
|
|
83
|
-
$PY "$R/.qoder/scripts/
|
|
83
|
+
$PY "$R/.qoder/scripts/orchestration/wlkj.py" switch-project --email a@b.c --pw xxx --project-uuid <UUID>
|
|
84
84
|
```
|
|
85
85
|
|
|
86
86
|
**之后所有命令自动用该项目的知识层、token、回流地址。不同人选不同项目 = 完全隔离。**
|
|
@@ -23,6 +23,11 @@ User input: $ARGUMENTS
|
|
|
23
23
|
| 生成 PRD 正文 | **Qoder AI** | 宿主 AI 比我们调 qwen-plus 更强 |
|
|
24
24
|
| 写文件 | **Qoder Write** | 原生文件操作 |
|
|
25
25
|
|
|
26
|
+
## 🚨 工具接口铁律(见 tool_guide · 会话已注入精简版)
|
|
27
|
+
|
|
28
|
+
**单一信源** `.qoder/scripts/tool_guide.md`(SessionStart 已注入精简版:repo_root 定位 / 三套名 wlkj·kg·MCP 互通 / 中文歧义词提炼 / 优先级口诀)。需完整三套名映射表或歧义词清单时 Read 它。
|
|
29
|
+
**两条红线常记**:① 三套名字都认(`search`/`semantic`/`context`),别因"未知子命令"慌张;② 禁 `find data/code` / `grep -r` 找模块——那是知识层 search 的活,手动做=思考链跑飞;只有 search 换词仍返 0 或已定位到具体文件时才 Grep/Read。
|
|
30
|
+
|
|
26
31
|
## 🧠 Planning Agent(完整模式强制前置 · 非建议)
|
|
27
32
|
|
|
28
33
|
> ⚠️ **完整模式必须先 Planning 再生成**(不再是"建议")。
|
|
@@ -132,7 +137,12 @@ if schema and schema.get("columns"):
|
|
|
132
137
|
```
|
|
133
138
|
等价 CLI(脚本兜底):
|
|
134
139
|
```bash
|
|
140
|
+
# ★ 有 REQ-ID 用 ctx-cache:PRD→spec→code 链路同 REQ 复用上下文(T1.3,省 2 次往返)
|
|
141
|
+
$PY "$R/.qoder/scripts/orchestration/wlkj.py" ctx-cache <REQ-ID> <业务词> --platform <web|app>
|
|
142
|
+
# 无 REQ-ID / 首次取:普通 context
|
|
135
143
|
$PY "$R/.qoder/scripts/orchestration/wlkj.py" context <业务词> --platform <web|app>
|
|
144
|
+
# PRD/spec 改动后失效该 REQ 缓存重来:
|
|
145
|
+
$PY "$R/.qoder/scripts/orchestration/wlkj.py" ctx-cache <REQ-ID> --clear
|
|
136
146
|
```
|
|
137
147
|
- 第 4 段 = 历史 PRD(防重复)
|
|
138
148
|
- 第 5 段 = 相关 API
|
|
@@ -171,6 +181,20 @@ $PY "$R/.qoder/scripts/orchestration/wlkj.py" eval <prd.md> [原型.html]
|
|
|
171
181
|
```
|
|
172
182
|
≥ 80% 才 PASS。完整模板见 `.qoder/templates/prd-full-template.md`。
|
|
173
183
|
|
|
184
|
+
### 业务保真打分(接真命令产物 · BF1/BF2)
|
|
185
|
+
|
|
186
|
+
> EVA 结构分(章齐)≠业务保真。PRD 出来后打业务保真分:产物是不是真 ICS 业务(真表/真写法)。
|
|
187
|
+
```python
|
|
188
|
+
from capability import resolve
|
|
189
|
+
cap = resolve()
|
|
190
|
+
# BF1 schema保真(提的表是真 ICS 业务表) + BF2 内容保真(LLM-judge vs 真 PRD, 是不是 ICS 写法非通用文)
|
|
191
|
+
r = cap.mcp.call("bf_score", {"kind": "prd", "content": "<PRD 正文>", "feature": "<业务主题如 保险>"})
|
|
192
|
+
# → BF1_schema.score(目标≥0.85) + BF2_content.score(目标≥4/5)
|
|
193
|
+
```
|
|
194
|
+
- BF1 低 → PRD 提了编造/非 ICS 表 → 补真表(query_schema/db_tables)。
|
|
195
|
+
- BF2 低 → 内容是通用产品文非 ICS 写法 → 参考 rag_search(kind=prd) 真 PRD 范本重写。
|
|
196
|
+
- 原型保真(BF3 组件/BF4 颜色):`cap.mcp.call("bf_score", {"kind":"proto","content":"<原型 HTML>"})` → 用真 fywl-ui 设计系统核。
|
|
197
|
+
|
|
174
198
|
---
|
|
175
199
|
|
|
176
200
|
## 快速模式(6 章 Mini-PRD)
|
|
@@ -20,20 +20,27 @@ result = cap.mcp.call("rag_search", {"query": "车辆保养", "top_k": 8})
|
|
|
20
20
|
|
|
21
21
|
自动路由:有 MCP 走 MCP(直连平台知识层,快),无 MCP 走 wlkj.py(CLI 降级)。零宿主感知。
|
|
22
22
|
|
|
23
|
-
##
|
|
23
|
+
## 🚨 接口铁律(见 tool_guide · 会话已注入精简版)
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
**单一信源** `.qoder/scripts/tool_guide.md`(SessionStart 已注入精简版:repo_root 定位 / 三套名 wlkj·kg·MCP 互通 / 中文歧义词提炼 / 优先级口诀)。需完整三套名映射表或歧义词清单时 Read 它。
|
|
26
|
+
**两条红线常记**:① 三套名字都认(`search`/`semantic`/`context`/`impact`),别因"未知子命令"慌;② 禁 `find`/`grep -r`/`findstr /s` 全盘扫 `data/code`——那是 search 的活,手动做=思考链跑飞。
|
|
26
27
|
|
|
27
|
-
| 问题类型 | 首选检索通道 | 为什么 |
|
|
28
|
-
|---------|-------------|--------|
|
|
29
|
-
| **"车辆保养"相关的代码/字段/API**(语义找相关,词不必一样) | `rag_search` 语义召回 | 能找到"维修记录"即使没共享词,召回最全 |
|
|
30
|
-
| **"这个功能的业务流程是什么 / 整体怎么运转"**(全局问答) | `ask_corpus` GraphRAG 问答 | 用社区摘要直接答,不是堆代码片段 |
|
|
31
|
-
| **"handleExport 这个符号在哪定义/调用"**(精确符号定位) | `search_code` 关键词 | 精确命中,找特定符号最快 |
|
|
32
|
-
| **"XX怎么实现的/怎么做的"**(一次取全代码+API+字段) | `context_pack` | 一条命令拿全 8 段上下文 |
|
|
33
|
-
| **"改 XX 影响谁"** | `get_impact` | 影响分析专用 |
|
|
34
28
|
|
|
35
|
-
|
|
36
|
-
|
|
29
|
+
## ⚡ 第一步:确定性路由(按顺序匹配第一个命中信号,零猜测)
|
|
30
|
+
|
|
31
|
+
**差模型也走得对:逐条检查 `$ARGUMENTS`,第一个命中的信号决定检索通道——不要"判断问题类型",按表查。**
|
|
32
|
+
|
|
33
|
+
| # | 信号(`$ARGUMENTS` 含这些词) | 走这个通道(直达,先调一次) |
|
|
34
|
+
|---|---|---|
|
|
35
|
+
| 1 | "影响谁" / "改 XX 影响" / "波及" | `get_impact`(影响分析) |
|
|
36
|
+
| 2 | "表结构" / "字段" / "枚举" / "真实数据" / "查库" | DB 通道:**先 `list_envs` 问环境**(见下「数据库查询」) |
|
|
37
|
+
| 3 | "哪些没测" / "覆盖" / "盲区" / "测试矩阵" | `coverage_matrix` |
|
|
38
|
+
| 4 | "业务流程" / "整体怎么运转" / "是干嘛的" | `ask_corpus`(GraphRAG 问答),再叠 `rag_search` 补事实 |
|
|
39
|
+
| 5 | "盘点" / "有哪些代码" / "一次取全" | `context_pack`(8 段一次拿全) |
|
|
40
|
+
| 6 | 精确符号(camelCase / 下划线 / 路径,如 `handleExport`、`/asset`) | `search_code`(精确关键词) |
|
|
41
|
+
| 7 | **(以上都没命中)默认** | **`rag_search`(语义召回最全,top_k=8)** |
|
|
42
|
+
|
|
43
|
+
> **铁律**:理解/盘点类问题默认 `rag_search`(语义召回最全,能找到没共享词的相关代码,如"车辆保养"→"维修记录");只在精确找符号时用 `search_code`;Glob 找不到文件用 `search_code` 兜底,**不准 find/findstr/dir /s 全盘扫**。
|
|
37
44
|
|
|
38
45
|
### 三路检索详解(RAG 升级核心)
|
|
39
46
|
|
|
@@ -35,7 +35,9 @@ PY=$(python --version >/dev/null 2>&1 && echo python || echo python3)
|
|
|
35
35
|
|
|
36
36
|
禅道是**每人一个账号**(凭据在 `workspace/members/{我}/.private/secrets/zentao.env`,gitignore),归属靠 member.json 的 `zentao_account` 映射 + 工具默认行为兜底:
|
|
37
37
|
|
|
38
|
-
- **看任务**:`my_workbench` / `list_my_tasks` / `list_execution_tasks` **默认只返回指派给我的**(assignedTo=我),不会看到队友。要看全队排期才传 `all=true
|
|
38
|
+
- **看任务**:`my_workbench` / `list_my_tasks` / `list_execution_tasks` **默认只返回指派给我的**(assignedTo=我),不会看到队友。要看全队排期才传 `all=true`。本地流最快,优先走它。
|
|
39
|
+
- **看我的活**:本地 `qw_mcp_call("my_workbench")` **优先**(快);本地不可得(无凭据/进程没起)再降级平台 `cap.mcp.call("get_my_zentao_panel")`(凭据走平台 per-user 配置,云防火墙时 `get_zentao_creds` 取凭据本机直连)。
|
|
40
|
+
- **Bug→需求反查**:`cap.mcp.call("bug_trace", {"bug_id": X})` 查"这个 Bug 是哪个需求引入的"。
|
|
39
41
|
- **空账号直接拒绝**:member.json 没配 `zentao_account` 时,"看我的"工具返回 `🚫 未识别禅道身份`(不再静默降级返回全队)。原样转述,引导 `/wl-init`。
|
|
40
42
|
- **改任务前必校验**:start/finish/assign/update 拿到 task_id 后,**先 `get_task(id)` 看 assignedTo**:
|
|
41
43
|
- `assignedTo == 我` → 正常执行;
|
|
@@ -226,9 +226,3 @@ notification:
|
|
|
226
226
|
console:
|
|
227
227
|
type: console
|
|
228
228
|
enabled: true
|
|
229
|
-
|
|
230
|
-
# ── 云平台 MCP(知识层走平台,不建本地kg)──
|
|
231
|
-
# 共识:本地引擎全走平台。kg MCP 连云平台 HTTP MCP@10010(81241实体+43工具),
|
|
232
|
-
# 不连本地 kg.duckdb(空)。install_qoderwork 读此 url 配 mcp.json SSE。
|
|
233
|
-
platform_mcp:
|
|
234
|
-
url: "http://10.89.7.120:10010/mcp"
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# 模块契约:insight(需求探索)
|
|
2
|
+
|
|
3
|
+
> 需求上游探索站。在写 PRD 之前把"该做什么、为什么做"想清楚。
|
|
4
|
+
> 四模式:clarify(默认,产品听不懂时对焦)/ discover(发现机会)/ research(调研竞品)/ plan(规划 roadmap)。
|
|
5
|
+
> 与 prd 的分工:insight 想清楚方向,prd 落地怎么做。
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Input(吃什么)
|
|
10
|
+
|
|
11
|
+
| 优先级 | 输入 | 怎么用 |
|
|
12
|
+
|--------|------|--------|
|
|
13
|
+
| **完整** | 一个模糊需求/产品想法 | 先 clarify 对焦,再按需 discover/research/plan |
|
|
14
|
+
| 参考 | 现状代码/业务 | `context_pack` / `feature_overview` 锚定现状,禁止编造 |
|
|
15
|
+
| 参考 | 竞品/外部信息 | research 模式走 WebSearch + 多 agent 调研 |
|
|
16
|
+
|
|
17
|
+
**第一性原理**:看未来,不编造。结论必须锚定现状(context_pack)。
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Output(吐什么)
|
|
22
|
+
|
|
23
|
+
### 探索产物
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
路径:data/docs/drafts/
|
|
27
|
+
内含(按模式产出):
|
|
28
|
+
insight-plan-{topic}-{date}.md ← plan 模式:OST/Gap 分析 + MVP + Roadmap
|
|
29
|
+
research-agent-{N}-{视角}.md ← research 模式:多视角调研中间产物
|
|
30
|
+
insight-research-{topic}-{date}.md ← research 模式:汇总报告
|
|
31
|
+
clarify-prototype-*.html ← clarify 模式:帮产品理解的原型(可选)
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### 核心方法
|
|
35
|
+
|
|
36
|
+
- **OST**(Objective-Strategy-Tactic):目标→策略→战术拆解
|
|
37
|
+
- **JTBD**(Jobs-to-be-done):用户要完成的"任务"
|
|
38
|
+
- **Gap 分析**:现状 vs 目标的差距 → 机会
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## 校验(合格标准)
|
|
43
|
+
|
|
44
|
+
- [ ] 结论锚定现状(引用了 context_pack / 真实代码),非空中楼阁
|
|
45
|
+
- [ ] 全中文输出(语言锁)
|
|
46
|
+
- [ ] 有明确"转 PRD"或"放弃"的建议,不悬空
|
|
47
|
+
- [ ] research 模式:调研有来源,不是泛泛而谈
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## 不做(边界)
|
|
52
|
+
|
|
53
|
+
- 不写实现级 PRD(那是 prd 模块的活)
|
|
54
|
+
- 不直接改代码(那是 code 模块的活)
|
|
55
|
+
- discover/research 是辅助决策,**最终是否落地由人决定**
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""pre-tool-use-commit.py - P0-3 w3: wl-commit msg 门禁(PreToolUse on Bash git commit)
|
|
4
|
+
|
|
5
|
+
stop-eval.py 里 _enforce_commit_msg_gate 的**正确落点**:pre-tool-use(commit 执行前验 msg),
|
|
6
|
+
而非 stop-eval(后者 git log -1 不分"本轮 wl-commit" vs 平台开发/工具 commit → 误拦)。
|
|
7
|
+
|
|
8
|
+
做法(确定性 + 不误拦其他):
|
|
9
|
+
1. PreToolUse 命中 Bash + command 含 "git commit"
|
|
10
|
+
2. 提取 -m 消息(兼容 -m "x" / -m 'x' / --message=x / 多 -m)
|
|
11
|
+
3. 查需求关联标记 [#REQ-<uuid>] (S3) 或 [#<禅道号>] (S5.1)
|
|
12
|
+
4. 缺标记:
|
|
13
|
+
- 默认 ADVISORY(exit 0 + stdout 强提醒)—— 不阻断,避免误拦工具/平台/文档 commit
|
|
14
|
+
- 环境变量 WLCOMMIT_GATE_BLOCK=1 时 → exit 2 硬拦(wl-commit 强流程场景才开)
|
|
15
|
+
|
|
16
|
+
铁律:永远 exit 0(除非 BLOCK 模式显式开);失败 try/except 静默(hook 崩绝不阻塞)。
|
|
17
|
+
非 git commit 命令 / 非 Bash → exit 0。
|
|
18
|
+
"""
|
|
19
|
+
import json
|
|
20
|
+
import os
|
|
21
|
+
import re
|
|
22
|
+
import sys
|
|
23
|
+
|
|
24
|
+
if sys.platform == 'win32':
|
|
25
|
+
try:
|
|
26
|
+
sys.stdout.reconfigure(encoding='utf-8')
|
|
27
|
+
sys.stderr.reconfigure(encoding='utf-8')
|
|
28
|
+
except Exception:
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
NL = chr(10)
|
|
32
|
+
BASE = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
33
|
+
HOOK_ERRORS_LOG = os.path.join(BASE, '.qoder', '.runtime', 'hook-errors.log')
|
|
34
|
+
|
|
35
|
+
# 需求关联标记:[#REQ-xxx] 或 [#123](禅道号)
|
|
36
|
+
_REQ_TAG_RE = re.compile(r'\[#REQ-[\w-]+\]|\[#\d+\]')
|
|
37
|
+
# 提取 git commit -m 的消息(兼容引号/多段/--message= / 组合短旗 -am)
|
|
38
|
+
# (?:^|\s)-[a-zA-Z]*m 匹配 -m / -am / -cm 等(单 dash 短旗组合;空白前缀避 --message 误匹)
|
|
39
|
+
_M_QUOTED = re.compile(r'''(?:^|\s)-[a-zA-Z]*m\s*(['"])(.*?)\1''', re.S)
|
|
40
|
+
_M_BARE = re.compile(r'(?:^|\s)-[a-zA-Z]*m\s+(\S.*?)\s*(?=(?:^|\s)-[a-zA-Z]*m\s|$)', re.S)
|
|
41
|
+
_MSG_EQ = re.compile(r'(?:^|\s)--message=(\S.*?)\s*(?:--|$)', re.S)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _read_stdin():
|
|
45
|
+
try:
|
|
46
|
+
raw = sys.stdin.read()
|
|
47
|
+
if raw and raw.strip():
|
|
48
|
+
return json.loads(raw)
|
|
49
|
+
except Exception:
|
|
50
|
+
pass
|
|
51
|
+
return {}
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _extract_msg(command):
|
|
55
|
+
"""从 git commit 命令里提取 commit message(拼多 -m)。提不到返 ''。"""
|
|
56
|
+
if "--message=" in command:
|
|
57
|
+
m = _MSG_EQ.search(command)
|
|
58
|
+
if m:
|
|
59
|
+
return m.group(1).strip()
|
|
60
|
+
# 所有 -m "..." / -m '...' 段拼接
|
|
61
|
+
parts = _M_QUOTED.findall(command) # [(quote, msg), ...]
|
|
62
|
+
if parts:
|
|
63
|
+
return "\n".join(p[1] for p in parts).strip()
|
|
64
|
+
# 裸 -m word(无引号)
|
|
65
|
+
m = _M_BARE.search(command)
|
|
66
|
+
if m:
|
|
67
|
+
return m.group(1).strip()
|
|
68
|
+
return ""
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _log_err(line):
|
|
72
|
+
from datetime import datetime
|
|
73
|
+
try:
|
|
74
|
+
os.makedirs(os.path.dirname(HOOK_ERRORS_LOG), exist_ok=True)
|
|
75
|
+
with open(HOOK_ERRORS_LOG, 'a', encoding='utf-8') as f:
|
|
76
|
+
f.write('[{}] pre-tool-use-commit: {}{}'.format(
|
|
77
|
+
datetime.now().strftime('%Y-%m-%d %H:%M:%S'), str(line)[:200], NL))
|
|
78
|
+
except Exception:
|
|
79
|
+
pass
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def main():
|
|
83
|
+
data = _read_stdin()
|
|
84
|
+
tool = data.get("tool_name") or data.get("tool") or ""
|
|
85
|
+
if tool.lower() not in ("bash", "shell", "execute_bash"):
|
|
86
|
+
sys.exit(0)
|
|
87
|
+
tinput = data.get("tool_input") or data.get("input") or {}
|
|
88
|
+
command = tinput.get("command") or tinput.get("cmd") or ""
|
|
89
|
+
if not isinstance(command, str) or "git commit" not in command:
|
|
90
|
+
sys.exit(0)
|
|
91
|
+
# 跳过 --amend 无 -m / 自动生成(如 merge commit)—— 只验带 -m 的显式 commit
|
|
92
|
+
msg = _extract_msg(command)
|
|
93
|
+
if not msg:
|
|
94
|
+
sys.exit(0) # 提不到 -m(可能 -F 文件或 amend),不拦
|
|
95
|
+
if _REQ_TAG_RE.search(msg):
|
|
96
|
+
sys.exit(0) # 含需求标记,放行
|
|
97
|
+
|
|
98
|
+
# 缺标记
|
|
99
|
+
block_mode = os.environ.get("WLCOMMIT_GATE_BLOCK", "").lower() in ("1", "true", "yes")
|
|
100
|
+
preview = msg.replace("\n", " ")[:100]
|
|
101
|
+
if block_mode:
|
|
102
|
+
sys.stderr.write(
|
|
103
|
+
'【wl-commit msg 门禁·硬拦】commit msg 缺需求关联标记:\n %s\n'
|
|
104
|
+
'修复: git commit --amend 补 [#REQ-<uuid>] (S3) 或 [#<禅道号>] (S5.1 push 后)。\n'
|
|
105
|
+
'(此硬拦由 WLCOMMIT_GATE_BLOCK=1 开启;平台/工具/文档 commit 关该 env 或走非 wl-commit 流程。)\n'
|
|
106
|
+
% preview)
|
|
107
|
+
sys.exit(2)
|
|
108
|
+
# 默认 advisory:不阻断,强提醒
|
|
109
|
+
sys.stdout.write(
|
|
110
|
+
'⚠️ 【wl-commit msg 提醒】本次 commit msg 缺需求关联标记 [#REQ-xxx]/[#禅道号]:\n'
|
|
111
|
+
' %s\n'
|
|
112
|
+
'若这是 /wl-commit 流程,建议 amend 补标记关联需求(F1 闭环数据);'
|
|
113
|
+
'若是平台/工具/文档 commit 可忽略本提醒。\n' % preview)
|
|
114
|
+
sys.exit(0)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
if __name__ == "__main__":
|
|
118
|
+
try:
|
|
119
|
+
main()
|
|
120
|
+
except SystemExit:
|
|
121
|
+
raise
|
|
122
|
+
except Exception as e:
|
|
123
|
+
_log_err("fatal: %s" % str(e)[:150])
|
|
124
|
+
sys.exit(0) # 永不阻塞
|
|
@@ -129,7 +129,7 @@ def get_index_info():
|
|
|
129
129
|
recent = errors[-10:] # 取最后 10 行检查
|
|
130
130
|
if len(recent) >= 3:
|
|
131
131
|
lines.append('[WARN] hook 最近频繁失败 (%d 条错误), 详情见 .qoder/.runtime/hook-errors.log' % len(recent))
|
|
132
|
-
lines.append(' 跑 python .qoder/scripts/
|
|
132
|
+
lines.append(' 跑 python .qoder/scripts/orchestration/wlkj.py doctor --fix 排查')
|
|
133
133
|
except OSError:
|
|
134
134
|
pass
|
|
135
135
|
|
|
@@ -289,6 +289,22 @@ def get_usability_summary():
|
|
|
289
289
|
return NL.join(lines) if lines else ''
|
|
290
290
|
|
|
291
291
|
|
|
292
|
+
def get_tool_guide():
|
|
293
|
+
"""注入 tool_guide 精简版(共用工具用法关键执行位), 命令正文不再重复(单一信源, T1.2)。
|
|
294
|
+
完整版见 .qoder/scripts/tool_guide.md。纯静态文本, 失败绝不阻塞。"""
|
|
295
|
+
return NL.join([
|
|
296
|
+
'## Tool Guide (共用工具用法 · 完整见 .qoder/scripts/tool_guide.md)',
|
|
297
|
+
'环境自检(任何脚本前先跑, 定 R/$PY):',
|
|
298
|
+
' R=$(python ~/.qoderwork/repo_root.py 2>/dev/null || python3 ~/.qoderwork/repo_root.py 2>/dev/null) || R=.',
|
|
299
|
+
' PY=$(python --version >/dev/null 2>&1 && echo python || echo python3)',
|
|
300
|
+
' 后续统一 $PY "$R/.qoder/scripts/orchestration/wlkj.py" <命令>',
|
|
301
|
+
'接口三套名互通: wlkj.py / kg.py / MCP 名字都认(search/semantic/context/impact), 别因"未知子命令"慌',
|
|
302
|
+
'中文歧义词先提炼再查: 保险->车险/商业险/交强险 · 异常->告警/报错 · 计划->作业/养护',
|
|
303
|
+
'禁全盘扫: 不要 find / grep -r / findstr /s / os.walk data/code, 那是知识层 search 的活',
|
|
304
|
+
'优先级口诀: 精确搜用Grep · 语义找用rag_search · 取全用context_pack · 查库先list_envs · 影响用get_impact',
|
|
305
|
+
])
|
|
306
|
+
|
|
307
|
+
|
|
292
308
|
def main():
|
|
293
309
|
parts = []
|
|
294
310
|
parts.append('<qoder-context>')
|
|
@@ -345,6 +361,9 @@ def main():
|
|
|
345
361
|
parts.append('')
|
|
346
362
|
parts.append(style_info)
|
|
347
363
|
|
|
364
|
+
parts.append('')
|
|
365
|
+
parts.append(get_tool_guide())
|
|
366
|
+
|
|
348
367
|
parts.append('</qoder-context>')
|
|
349
368
|
# Hook-1: 健康标记 — AI 能感知 hook 是否真的跑了
|
|
350
369
|
# QoderWork/Quest 里看不到 [hook-ok] 就知道 hook 没生效, 要自取上下文
|
|
@@ -179,6 +179,127 @@ def _last_record_by_prd():
|
|
|
179
179
|
return last_by_prd
|
|
180
180
|
|
|
181
181
|
|
|
182
|
+
def _enforce_code_ac_gate():
|
|
183
|
+
"""P0-3: wl-code spec AC 门禁。找近 spec + eval_code_ac.py 对账, AC<100% 阻断。
|
|
184
|
+
|
|
185
|
+
治"代码漏实现 spec AC"(wl-code 自检全打勾反例)。无 spec / 脚本缺失 / 崩溃 → 放行(不阻塞)。
|
|
186
|
+
"""
|
|
187
|
+
import subprocess
|
|
188
|
+
qoder_script = os.path.join(BASE, '.qoder', 'scripts', 'validation', 'metrics', 'eval_code_ac.py')
|
|
189
|
+
pkg_script = os.path.join(BASE, 'packages', 'wlkj', 'templates', 'qoder',
|
|
190
|
+
'scripts', 'validation', 'metrics', 'eval_code_ac.py')
|
|
191
|
+
script = qoder_script if os.path.isfile(qoder_script) else pkg_script
|
|
192
|
+
if not os.path.isfile(script):
|
|
193
|
+
return # 脚本不在, 放行
|
|
194
|
+
try:
|
|
195
|
+
r = subprocess.run([sys.executable, script], cwd=BASE,
|
|
196
|
+
capture_output=True, text=True, timeout=30)
|
|
197
|
+
except Exception:
|
|
198
|
+
return # 崩溃绝不阻塞
|
|
199
|
+
if r.returncode != 2:
|
|
200
|
+
return # exit 0 = 无 spec / AC 全覆盖, 放行
|
|
201
|
+
out_lines = [l for l in (r.stdout or '').split('\n') if l.strip()]
|
|
202
|
+
try:
|
|
203
|
+
data = json.loads(out_lines[-1]) if out_lines else {}
|
|
204
|
+
except Exception:
|
|
205
|
+
data = {}
|
|
206
|
+
missed = data.get('missed', []) or []
|
|
207
|
+
rate = data.get('rate', 0)
|
|
208
|
+
miss_str = '\n'.join(' - ' + str(m)[:80] for m in missed[:5])
|
|
209
|
+
sys.stderr.write(
|
|
210
|
+
'【wl-code AC 门禁】检测到 spec 验收标准未被代码覆盖 (覆盖率 %s%%):\n%s\n'
|
|
211
|
+
'修复: 对照 spec 第6章 PRD 验收标准映射表, 把未实现的 AC 补进代码, 重跑至 100%%。\n'
|
|
212
|
+
% (int(rate * 100), miss_str))
|
|
213
|
+
sys.exit(2)
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def _enforce_cases_lint_gate():
|
|
217
|
+
"""P0-3 w2: wl-test cases 格式门禁。扫近 10min cases 文件(含 intent/action/anchor 键的 json),
|
|
218
|
+
跑 lint_cases.py,旧格式(action+CSS selector) → exit 2。无 cases/脚本缺/崩溃 → 放行(不阻塞)。"""
|
|
219
|
+
import subprocess
|
|
220
|
+
qoder_script = os.path.join(BASE, '.qoder', 'scripts', 'validation', 'metrics', 'lint_cases.py')
|
|
221
|
+
pkg_script = os.path.join(BASE, 'packages', 'wlkj', 'templates', 'qoder',
|
|
222
|
+
'scripts', 'validation', 'metrics', 'lint_cases.py')
|
|
223
|
+
script = qoder_script if os.path.isfile(qoder_script) else pkg_script
|
|
224
|
+
if not os.path.isfile(script):
|
|
225
|
+
return
|
|
226
|
+
now = time.time()
|
|
227
|
+
cases_files = []
|
|
228
|
+
for root in (os.path.join(BASE, 'workspace', 'members'), os.path.join(BASE, 'workspace', 'tasks')):
|
|
229
|
+
if not os.path.isdir(root):
|
|
230
|
+
continue
|
|
231
|
+
for dirpath, dirs, files in os.walk(root):
|
|
232
|
+
dirs[:] = [d for d in dirs if d not in ('node_modules', '.git', '__pycache__', '.secrets')]
|
|
233
|
+
for fn in files:
|
|
234
|
+
if not fn.endswith('.json'):
|
|
235
|
+
continue
|
|
236
|
+
fp = os.path.join(dirpath, fn)
|
|
237
|
+
try:
|
|
238
|
+
if now - os.path.getmtime(fp) > 600:
|
|
239
|
+
continue
|
|
240
|
+
except OSError:
|
|
241
|
+
continue
|
|
242
|
+
try:
|
|
243
|
+
head = open(fp, encoding='utf-8', errors='replace').read(2000)
|
|
244
|
+
except Exception:
|
|
245
|
+
continue
|
|
246
|
+
if '"intent"' in head or '"action"' in head or '"anchor"' in head:
|
|
247
|
+
cases_files.append(fp)
|
|
248
|
+
for fp in cases_files[:5]:
|
|
249
|
+
try:
|
|
250
|
+
r = subprocess.run([sys.executable, script, fp, '--json'], cwd=BASE,
|
|
251
|
+
capture_output=True, text=True, timeout=15)
|
|
252
|
+
except Exception:
|
|
253
|
+
continue
|
|
254
|
+
if r.returncode != 2:
|
|
255
|
+
continue
|
|
256
|
+
out_lines = [l for l in (r.stdout or '').split('\n') if l.strip()]
|
|
257
|
+
try:
|
|
258
|
+
data = json.loads(out_lines[-1]) if out_lines else {}
|
|
259
|
+
except Exception:
|
|
260
|
+
data = {}
|
|
261
|
+
blocks = [v for v in (data.get('violations') or []) if v.get('severity') == 'BLOCK']
|
|
262
|
+
if blocks:
|
|
263
|
+
detail = '\n'.join(' - case#%s %s' % (v.get('idx'), str(v.get('detail', ''))[:60]) for v in blocks[:5])
|
|
264
|
+
sys.stderr.write(
|
|
265
|
+
'【wl-test cases 门禁】%s 检测到旧格式用例(action+CSS selector,新 Playwright 管线不认):\n%s\n'
|
|
266
|
+
'修复: 改 intent+anchor 格式,anchor={role,name}(来自 recall/snapshot),凭据用 {{ask:key}}。\n'
|
|
267
|
+
% (os.path.basename(fp), detail))
|
|
268
|
+
sys.exit(2)
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def _enforce_commit_msg_gate():
|
|
272
|
+
"""P0-3 w3: wl-commit msg 门禁。近 30min commit msg 须含 [#REQ-xxx] 或 [#禅道号]。
|
|
273
|
+
|
|
274
|
+
治"commit 不关联需求"(F1 闭环数据断)。memory commit-convention-zentao-id:
|
|
275
|
+
S3 [#REQ-<uuid8>] / S5.1 push 后 [#<禅道号>]。无标记 → exit 2 提示 amend。
|
|
276
|
+
"""
|
|
277
|
+
import re as _re
|
|
278
|
+
import subprocess
|
|
279
|
+
try:
|
|
280
|
+
r = subprocess.run(['git', 'log', '-1', '--format=%B|||%ct'], cwd=BASE,
|
|
281
|
+
capture_output=True, text=True, timeout=10)
|
|
282
|
+
out = (r.stdout or '').strip()
|
|
283
|
+
except Exception:
|
|
284
|
+
return # 崩溃不阻塞
|
|
285
|
+
if '|||' not in out:
|
|
286
|
+
return
|
|
287
|
+
msg, _, ts_s = out.rpartition('|||')
|
|
288
|
+
try:
|
|
289
|
+
ts = int(ts_s)
|
|
290
|
+
except ValueError:
|
|
291
|
+
ts = 0
|
|
292
|
+
if ts and time.time() - ts > 1800:
|
|
293
|
+
return # 非本轮 commit
|
|
294
|
+
if _re.search(r'\[#REQ-[\w-]+\]|\[#\d+\]', msg):
|
|
295
|
+
return # 含需求标记, 放行
|
|
296
|
+
sys.stderr.write(
|
|
297
|
+
'【wl-commit msg 门禁】最近 commit msg 缺需求关联标记:\n %s\n'
|
|
298
|
+
'修复: git commit --amend 补 [#REQ-<uuid>] (S3) 或 [#<禅道号>] (S5.1 push 后)。\n'
|
|
299
|
+
% msg.replace('\n', ' ')[:120])
|
|
300
|
+
sys.exit(2)
|
|
301
|
+
|
|
302
|
+
|
|
182
303
|
def main():
|
|
183
304
|
try:
|
|
184
305
|
data = _read_stdin()
|
|
@@ -186,6 +307,14 @@ def main():
|
|
|
186
307
|
if _is_reentry(data):
|
|
187
308
|
sys.exit(0)
|
|
188
309
|
|
|
310
|
+
# ★ P0-3 wl-code spec AC 门禁(代码漏实现 AC 检测, 治自检全打勾)
|
|
311
|
+
_enforce_code_ac_gate()
|
|
312
|
+
# P0-3 w2 wl-test cases 格式门禁(旧 action+CSS selector → 拦)
|
|
313
|
+
_enforce_cases_lint_gate()
|
|
314
|
+
# w3 wl-commit msg 门禁: 应在 pre-tool-use(git commit 命令前验 msg)而非 stop-eval——
|
|
315
|
+
# stop-eval 的 git log -1 不分"本轮 wl-commit"vs 其他 commit, 会在平台开发等非 wl-commit
|
|
316
|
+
# 场景误拦。函数 _enforce_commit_msg_gate 已定义待 pre-tool-use 细化接入。
|
|
317
|
+
|
|
189
318
|
# 找近 10 分钟产出的 PRD, 检查是否已【通过】门禁
|
|
190
319
|
recent = _recent_prd_files()
|
|
191
320
|
if not recent:
|
|
@@ -21,6 +21,7 @@ McpMemoryProvider:memory 职责链的 MCP provider,接通 kg.duckdb learning
|
|
|
21
21
|
"""
|
|
22
22
|
from __future__ import annotations
|
|
23
23
|
|
|
24
|
+
import json
|
|
24
25
|
from typing import Any, Optional
|
|
25
26
|
|
|
26
27
|
from ..registry_mcp import McpRegistry, _load_servers
|
|
@@ -132,6 +133,11 @@ class McpCap:
|
|
|
132
133
|
|
|
133
134
|
def _cli_fallback(self, tool_name: str, args: dict) -> Optional[str]:
|
|
134
135
|
"""MCP 工具名 → wlkj.py 执行。无映射返回 None。"""
|
|
136
|
+
# ★ 递归守卫:search_code→kg search→search_index→call_remote→call→_cli_fallback 会无限套娃。
|
|
137
|
+
# 已在降级子进程内(_run_wlkj 设了 _WLKJ_IN_CLI_FALLBACK=1)则直接放弃降级,返回原始错误。
|
|
138
|
+
import os as _os
|
|
139
|
+
if _os.environ.get("_WLKJ_IN_CLI_FALLBACK"):
|
|
140
|
+
return None
|
|
135
141
|
entry = self._TOOL_TO_WLKJ.get(tool_name)
|
|
136
142
|
if entry is None:
|
|
137
143
|
return None
|
|
@@ -183,10 +189,12 @@ class McpCap:
|
|
|
183
189
|
repo = os.getcwd()
|
|
184
190
|
wlkj = os.path.join(repo, ".qoder", "scripts", "orchestration", "wlkj.py")
|
|
185
191
|
try:
|
|
192
|
+
# 递归守卫:子进程内 _WLKJ_IN_CLI_FALLBACK=1,其 McpCap 不再降级(否则套娃)
|
|
193
|
+
env = dict(os.environ, _WLKJ_IN_CLI_FALLBACK="1")
|
|
186
194
|
r = subprocess.run(
|
|
187
195
|
[sys.executable, wlkj, cmd] + args,
|
|
188
196
|
capture_output=True, text=True, timeout=60,
|
|
189
|
-
encoding="utf-8", errors="replace",
|
|
197
|
+
encoding="utf-8", errors="replace", env=env,
|
|
190
198
|
)
|
|
191
199
|
if r.returncode == 0:
|
|
192
200
|
return r.stdout.strip()
|
|
@@ -51,7 +51,7 @@ _KNOWLEDGE_SERVER_ALIASES = ["wlinkj-knowledge", "qoder-knowledge-graph"]
|
|
|
51
51
|
SERVERS: dict[str, dict] = {
|
|
52
52
|
"qoder-knowledge-graph": {
|
|
53
53
|
"kind": "http",
|
|
54
|
-
"url": "http://
|
|
54
|
+
"url": os.environ.get("WLKJ_BASE_URL", "http://10.89.7.120") + "/mcp",
|
|
55
55
|
"note": "知识图谱。已改为 http 模式,指向资产平台 MCP 服务(POST /mcp JSON-RPC)",
|
|
56
56
|
},
|
|
57
57
|
"qoder-zentao": {
|
|
@@ -73,16 +73,22 @@ SERVERS: dict[str, dict] = {
|
|
|
73
73
|
|
|
74
74
|
|
|
75
75
|
def _mcp_config_path() -> Path:
|
|
76
|
-
"""定位 mcp_config.json(在仓库根/宿主下)。
|
|
76
|
+
"""定位 mcp_config.json(在仓库根/宿主下)。
|
|
77
|
+
|
|
78
|
+
★ switch_project.py 实际写入位置是 {repo}/.qoder/mcp_config.json(云模式连云 bind 后),
|
|
79
|
+
必须优先查这里——否则读不到 token,transport 用空 token 调云 MCP 鉴权失败(历史 bug:
|
|
80
|
+
引擎 search 全返回"MCP返回非JSON"+递归降级套娃)。老形态 repo/mcp_config.json 兼容。
|
|
81
|
+
"""
|
|
77
82
|
repo = _get_repo_root()
|
|
78
83
|
# registry_mcp.py 在 .../scripts/capability/, mcp_config.json 在宿主根(.qoder)。
|
|
79
84
|
for cand in [
|
|
80
|
-
repo / "mcp_config.json",
|
|
85
|
+
repo / ".qoder" / "mcp_config.json", # ★ 云模式实际写入位置(switch_project)
|
|
86
|
+
repo / "mcp_config.json", # 老形态:仓库根
|
|
81
87
|
repo / "registry_mcp_config.json",
|
|
82
88
|
]:
|
|
83
89
|
if cand.is_file():
|
|
84
90
|
return cand
|
|
85
|
-
return repo / "mcp_config.json"
|
|
91
|
+
return repo / ".qoder" / "mcp_config.json"
|
|
86
92
|
|
|
87
93
|
|
|
88
94
|
def _load_servers() -> dict[str, dict]:
|