@hupan56/wlkj 3.3.13 → 3.3.14

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.
Files changed (103) hide show
  1. package/bin/cli.js +1 -23
  2. package/package.json +29 -29
  3. package/templates/qoder/agents/design-agent.md +20 -0
  4. package/templates/qoder/agents/spec-generator.md +21 -0
  5. package/templates/qoder/commands/optional/wl-spec.md +2 -12
  6. package/templates/qoder/commands/optional/wl-status.md +0 -8
  7. package/templates/qoder/commands/wl-code.md +0 -8
  8. package/templates/qoder/commands/wl-commit.md +1 -11
  9. package/templates/qoder/commands/wl-init.md +129 -129
  10. package/templates/qoder/commands/wl-prd.md +0 -24
  11. package/templates/qoder/commands/wl-search.md +11 -26
  12. package/templates/qoder/commands/wl-task.md +1 -11
  13. package/templates/qoder/commands/wl-test.md +0 -8
  14. package/templates/qoder/config.yaml +8 -3
  15. package/templates/qoder/hooks/session-start.py +365 -384
  16. package/templates/qoder/hooks/stop-eval.py +254 -383
  17. package/templates/qoder/scripts/capability/adapters/mcp.py +314 -322
  18. package/templates/qoder/scripts/capability/registry.py +248 -245
  19. package/templates/qoder/scripts/capability/registry_mcp.py +4 -10
  20. package/templates/qoder/scripts/capability/smoke_test_report.json.new +94 -0
  21. package/templates/qoder/scripts/deployment/setup/init_doctor.py +2 -6
  22. package/templates/qoder/scripts/deployment/setup/install_qoderwork.py +14 -0
  23. package/templates/qoder/scripts/deployment/setup/setup.py +2 -16
  24. package/templates/qoder/scripts/domain/integration/return_to_platform.py +2 -4
  25. package/templates/qoder/scripts/domain/integration/spec_upload.py +4 -4
  26. package/templates/qoder/scripts/domain/kg/build/build_entity_registry.py +12 -12
  27. package/templates/qoder/scripts/domain/kg/build/build_relations.py +12 -12
  28. package/templates/qoder/scripts/domain/kg/extract/asset/__init__.py +10 -0
  29. package/templates/qoder/scripts/domain/kg/extract/asset/asset_tree.py +57 -0
  30. package/templates/qoder/scripts/domain/kg/extract/asset/discussion_importer.py +62 -0
  31. package/templates/qoder/scripts/domain/kg/extract/asset/prd_importer.py +146 -0
  32. package/templates/qoder/scripts/domain/kg/extract/asset/prototype_importer.py +64 -0
  33. package/templates/qoder/scripts/domain/kg/extract/asset/returns_importer.py +52 -0
  34. package/templates/qoder/scripts/domain/kg/extract/build_goal3.py +104 -0
  35. package/templates/qoder/scripts/domain/kg/extract/build_goal4.py +55 -0
  36. package/templates/qoder/scripts/domain/kg/extract/build_goal5.py +95 -0
  37. package/templates/qoder/scripts/domain/kg/extract/db/__init__.py +8 -0
  38. package/templates/qoder/scripts/domain/kg/extract/db/data_profile.py +22 -0
  39. package/templates/qoder/scripts/domain/kg/extract/db/fk_extractor.py +55 -0
  40. package/templates/qoder/scripts/domain/kg/extract/db/schema_extractor.py +90 -0
  41. package/templates/qoder/scripts/domain/kg/extract/inference/__init__.py +9 -0
  42. package/templates/qoder/scripts/domain/kg/extract/inference/community_summarizer.py +206 -0
  43. package/templates/qoder/scripts/domain/kg/extract/inference/embed_builder.py +132 -0
  44. package/templates/qoder/scripts/domain/kg/extract/inference/naming_matcher.py +80 -0
  45. package/templates/qoder/scripts/domain/kg/extract/inference/promote.py +59 -0
  46. package/templates/qoder/scripts/domain/kg/extract/inference/recompute.py +93 -0
  47. package/templates/qoder/scripts/domain/kg/extract/inference/weak_link.py +421 -0
  48. package/templates/qoder/scripts/domain/kg/extract/mybatis/__init__.py +9 -0
  49. package/templates/qoder/scripts/domain/kg/extract/mybatis/all.py +79 -0
  50. package/templates/qoder/scripts/domain/kg/extract/mybatis/mapper_parser.py +99 -0
  51. package/templates/qoder/scripts/domain/kg/extract/mybatis/relation_builder.py +69 -0
  52. package/templates/qoder/scripts/domain/kg/extract/mybatis/sql_extractor.py +78 -0
  53. package/templates/qoder/scripts/domain/kg/extract/prd/__init__.py +8 -0
  54. package/templates/qoder/scripts/domain/kg/extract/prd/prd_chunk_embed.py +105 -0
  55. package/templates/qoder/scripts/domain/kg/extract/prd/prd_llm_extract.py +153 -0
  56. package/templates/qoder/scripts/domain/kg/extract/prd/req_anchor.py +120 -0
  57. package/templates/qoder/scripts/domain/kg/graph/kg_link_db.py +235 -0
  58. package/templates/qoder/scripts/domain/kg/graph/kg_semantic.py +5 -41
  59. package/templates/qoder/scripts/domain/kg/kg.py +0 -14
  60. package/templates/qoder/scripts/domain/kg/search/enrich_prompt.py +238 -0
  61. package/templates/qoder/scripts/domain/kg/server/perf_bench.py +197 -0
  62. package/templates/qoder/scripts/domain/kg/switch_project.py +2 -2
  63. package/templates/qoder/scripts/domain/kg/sync_repowiki.py +109 -0
  64. package/templates/qoder/scripts/domain/task/wlkj_panel.py +1348 -1503
  65. package/templates/qoder/scripts/engine/poller.py +219 -0
  66. package/templates/qoder/scripts/foundation/bootstrap.py +145 -156
  67. package/templates/qoder/scripts/orchestration/wlkj.py +189 -306
  68. package/templates/qoder/settings.json +0 -8
  69. package/templates/qoder/skills/wl-spec/SKILL.md +1 -1
  70. package/templates/qoder/commands/wl-data.md +0 -46
  71. package/templates/qoder/commands/wl-fix.md +0 -47
  72. package/templates/qoder/commands/wl-knowledge.md +0 -41
  73. package/templates/qoder/commands/wl-review.md +0 -49
  74. package/templates/qoder/contracts/insight.md +0 -55
  75. package/templates/qoder/hooks/pre-tool-use-commit.py +0 -124
  76. package/templates/qoder/scripts/foundation/io/context_cache.py +0 -94
  77. package/templates/qoder/scripts/tool_guide.md +0 -70
  78. package/templates/qoder/scripts/validation/eval/alignment_matrix.py +0 -176
  79. package/templates/qoder/scripts/validation/eval/bf2_content_fidelity.py +0 -110
  80. package/templates/qoder/scripts/validation/eval/bf2_llmjudge.py +0 -104
  81. package/templates/qoder/scripts/validation/eval/bf_score.py +0 -218
  82. package/templates/qoder/scripts/validation/eval/code_flywheel.py +0 -150
  83. package/templates/qoder/scripts/validation/eval/dispatcher_ab.py +0 -156
  84. package/templates/qoder/scripts/validation/eval/dispatcher_ab_2026-07-21.json +0 -23
  85. package/templates/qoder/scripts/validation/eval/feature_fidelity_flywheel.py +0 -143
  86. package/templates/qoder/scripts/validation/eval/gradient_matrix.py +0 -261
  87. package/templates/qoder/scripts/validation/eval/gradient_matrix_baseline_2026-07-21.json +0 -33
  88. package/templates/qoder/scripts/validation/eval/metrics_dashboard.py +0 -105
  89. package/templates/qoder/scripts/validation/eval/multi_turn_flywheel.py +0 -118
  90. package/templates/qoder/scripts/validation/eval/prd_fidelity_flywheel.py +0 -128
  91. package/templates/qoder/scripts/validation/eval/prd_flywheel.py +0 -148
  92. package/templates/qoder/scripts/validation/eval/prototype_fidelity_flywheel.py +0 -166
  93. package/templates/qoder/scripts/validation/eval/recall_flywheel.py +0 -148
  94. package/templates/qoder/scripts/validation/eval/robustness_flywheel.py +0 -139
  95. package/templates/qoder/scripts/validation/eval/speed_accuracy_flywheel.py +0 -188
  96. package/templates/qoder/scripts/validation/eval/task_flywheel.py +0 -124
  97. package/templates/qoder/scripts/validation/eval/token_flywheel.py +0 -88
  98. package/templates/qoder/scripts/validation/metrics/eval_code_ac.py +0 -177
  99. package/templates/qoder/scripts/validation/metrics/lint_cases.py +0 -170
  100. package/templates/qoder/scripts/validation/test/test_context_cache.py +0 -78
  101. package/templates/qoder/scripts/validation/test/test_lint_cases.py +0 -60
  102. package/templates/qoder/scripts/validation/test/test_pre_tool_use_commit.py +0 -70
  103. package/templates/workspace/specs/REQ-TEST-CTX/.context-cache.json +0 -1
@@ -1,41 +0,0 @@
1
- ---
2
- name: wl-knowledge
3
- description: "知识问答: 理解一个对象/概念。三支柱接地(代码+流程+字段)+可溯源答案。"
4
- argument-hint: "[概念/符号/字段] 如:保险单是什么 / vehicle_no 含义"
5
- auto-approve: true
6
- allowed-tools: [Read, Glob, Grep, Bash, Write, Edit]
7
- ---
8
-
9
- # /wl-knowledge - 知识问答工序站
10
-
11
- User input: $ARGUMENTS
12
-
13
- > **理解一个对象,三支柱接地(代码实体 + 业务流程 + 字段schema)。** 与 wl-search(定位/路由) 区别:wl-knowledge 聚焦"讲清一个对象是什么"。
14
-
15
- ## 🔧 工具优先级
16
- | 步骤 | 用谁 | 原因 |
17
- |------|------|------|
18
- | 提炼概念+意图 | **Qoder AI** | 拆四路:concept/search_query/intent/field_candidates |
19
- | 代码实体 | **MCP rag_search** | 语义召回相关代码/字段/API |
20
- | 概念画像 | **MCP context_360** | 实体的代码+API+字段+测试全貌 |
21
- | 业务流程 | **MCP graphrag_ask** | GraphRAG 社区摘要(全局流程) |
22
- | 字段schema | **MCP schema_link/query_schema** | 真表结构/字段含义 |
23
- | 综合答案 | **Qoder AI** | 三支柱接地综合 |
24
-
25
- ## 🧠 流程
26
- 1. **提炼**:derive → concept(核心对象) + search_query(干净检索词) + intent(concept_explain/business_flow/code_locate/field_schema) + field_candidates
27
- 2. **代码召回**:`rag_search(query=<search_query>)` → 相关代码/字段
28
- 3. **概念画像**:`context_360(query=<concept>)` → 实体全貌
29
- 4. **业务流程**:`graphrag_ask(q=<问题>)` → 全局流程(社区摘要支撑)
30
- 5. **字段schema**:`query_schema(field=<candidate>)` → 真表字段含义
31
- 6. **综合答案**:Qoder AI → 三支柱接地的知识答案
32
-
33
- ## 🚨 铁律
34
- 1. **三支柱接地**:代码实体 + 业务流程 + 字段schema 都基于真实召回
35
- 2. **意图定重心**:intent=concept_explain 讲概念;business_flow 讲流程;code_locate 定位;field_schema 讲字段
36
- 3. **可溯源**:引用真实符号/表/字段,禁编造
37
- 4. **诚实**:召回不足标"知识层未覆盖"
38
-
39
- ## 📊 对齐
40
- - 平台 workflow_sim WL_WL_KNOWLEDGE_GRAPH(derive→rag→context_360→graphrag_ask→query_schema→synth)
41
- - graphrag_ask 依赖社区摘要(FW17-B1 已修:并发+向量化+pgvector解析)
@@ -1,49 +0,0 @@
1
- ---
2
- name: wl-review
3
- description: "代码审查: 改动影响半径+需求对齐+安全/质量/业务多维审查报告。"
4
- argument-hint: "[变更实体/diff/分支] 如:CostApplyController"
5
- auto-approve: true
6
- allowed-tools: [Read, Glob, Grep, Bash, Write, Edit]
7
- ---
8
-
9
- # /wl-review - 代码审查工序站
10
-
11
- User input: $ARGUMENTS
12
-
13
- > **影响半径 + 需求对齐 + 多维审查,给通过/打回结论。**
14
-
15
- ## 🔧 工具优先级
16
- | 步骤 | 用谁 | 原因 |
17
- |------|------|------|
18
- | 变更实体 | **Qoder AI 解析** | 从 diff/分支/文件集解析变更符号 |
19
- | 影响半径 | **MCP get_impact** | 改动影响谁(upstream/downstream) |
20
- | 全上下文 | **MCP context_360** | 变更实体的代码/API/字段画像 |
21
- | 需求对齐 | **MCP aggregate_requirement** | 关联需求的 PRD/AC(业务覆盖) |
22
- | 安全门禁 | **MCP gate_check** | conformal/risk 裁决 |
23
- | 审查报告 | **Qoder AI** | 多维综合→通过/打回 |
24
-
25
- ## 🧠 流程
26
- 1. **提取变更**:derive → changed_entities + change_intent + req_key
27
- 2. **影响半径**:`get_impact(entity=<changed>, depth=3)` → 影响面
28
- 3. **全上下文**:`context_360(query=<changed>)` → 实体画像
29
- 4. **需求对齐**:`aggregate_requirement(req_key=<req>)` → PRD/AC(业务覆盖)
30
- 5. **相似实现**:`rag_search(query=<intent>)` → 相似/缺陷模式
31
- 6. **审查焦点**:derive → risk_areas + security + quality + business_gaps + regression_scope
32
- 7. **审查报告**:Qoder AI 综合 → ① 变更范围 ② 安全 ③ 精准度 ④ 需求覆盖 ⑤ 通过/打回
33
-
34
- ## 🚨 铁律
35
- 1. **只评真实**:变更范围只评 get_impact 命中的真实实体
36
- 2. **PascalCase 引用**:审查实体用原名(CostApplyController),非中文描述(溯源)
37
- 3. **门禁量化**:gate_check 裁决 block/warn/pass
38
- 4. **结论明确**:通过/打回二选一 + 理由
39
-
40
- ## 📊 对齐
41
- - 平台 workflow_sim WL_WL_REVIEW_GRAPH(extract→impact→context→requirement→search→focus→report)
42
-
43
-
44
- ## 🚨 接地铁律(防幻觉/保真,Qoder AI 必须遵守)
45
-
46
- 1. **溯源**:引用代码符号/表名/接口用**真实原名**(如 `CostApplyController`、`base_asset`),非中文描述——只引 MCP 工具(rag_search/schema_link/context_360/get_impact 等)召回的真实符号,提升可溯源性
47
- 2. **禁编**:不编造未在召回里出现的类名/表名/字段/接口;召回不足时标"待确认(知识层未召回)",**诚实优于编造**
48
- 3. **接地(数据表)**:涉及数据表/字段时,先调 MCP `schema_link`/`query_schema` 取真表真列;未召回真表禁编 t_xxx 表名+字段充数
49
- 4. **完整**:产物详尽展开(每节实质内容,不一行带过)
@@ -1,55 +0,0 @@
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 是辅助决策,**最终是否落地由人决定**
@@ -1,124 +0,0 @@
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) # 永不阻塞
@@ -1,94 +0,0 @@
1
- """context_cache.py — 按 REQ-id 缓存 context_pack/rag_search 结果(T1.3 跨命令上下文缓存)。
2
-
3
- PRD→spec→code 链路里同一 REQ 反复拉同一份上下文(3 次 KG/MCP 往返)→ 缓存复用,省 token + 减反复搜。
4
-
5
- 存储: workspace/specs/{REQ}/.context-cache.json
6
- 结构: {query_hash: {"query":..., "text":..., "ts":...}}
7
- TTL 默认 24h(PRD/spec 改动后用 invalidate(req) 显式失效)。
8
-
9
- 纯模块,无外部依赖;任何异常 fail-open(缓存坏了不影响主流程,调用方降级直拉)。
10
- """
11
- import os
12
- import json
13
- import time
14
- import hashlib
15
-
16
- DEFAULT_TTL = 24 * 3600 # 24h
17
-
18
-
19
- def _repo_root():
20
- """从本文件向上找含 workspace/ 或 .qoder/ 的目录作仓库根。"""
21
- d = os.path.dirname(os.path.abspath(__file__))
22
- for _ in range(7):
23
- if os.path.isdir(os.path.join(d, "workspace")) or os.path.isdir(os.path.join(d, ".qoder")):
24
- return d
25
- parent = os.path.dirname(d)
26
- if parent == d:
27
- break
28
- d = parent
29
- return os.getcwd()
30
-
31
-
32
- def _sanitize(req_id):
33
- return "".join(c for c in str(req_id) if c.isalnum() or c in "-_") or "unknown"
34
-
35
-
36
- def cache_path(req_id, base=None):
37
- base = base or _repo_root()
38
- return os.path.join(base, "workspace", "specs", _sanitize(req_id), ".context-cache.json")
39
-
40
-
41
- def _load(path):
42
- try:
43
- with open(path, encoding="utf-8") as f:
44
- data = json.load(f)
45
- return data if isinstance(data, dict) else {}
46
- except Exception:
47
- return {}
48
-
49
-
50
- def _save(path, data):
51
- try:
52
- os.makedirs(os.path.dirname(path), exist_ok=True)
53
- tmp = path + ".tmp"
54
- with open(tmp, "w", encoding="utf-8") as f:
55
- json.dump(data, f, ensure_ascii=False, indent=2)
56
- os.replace(tmp, path) # 原子写
57
- return True
58
- except Exception:
59
- return False
60
-
61
-
62
- def _qhash(query):
63
- return hashlib.md5(query.strip().lower().encode("utf-8")).hexdigest()[:12]
64
-
65
-
66
- def get(req_id, query, ttl=DEFAULT_TTL, base=None):
67
- """命中且未过期 → 返回 text;否则 None。"""
68
- data = _load(cache_path(req_id, base))
69
- entry = data.get(_qhash(query))
70
- if not entry:
71
- return None
72
- if ttl and (time.time() - float(entry.get("ts", 0))) > ttl:
73
- return None
74
- return entry.get("text")
75
-
76
-
77
- def put(req_id, query, text, base=None):
78
- """写一条缓存(覆盖同 query 旧值)。"""
79
- path = cache_path(req_id, base)
80
- data = _load(path)
81
- data[_qhash(query)] = {"query": query, "text": text, "ts": time.time()}
82
- return _save(path, data)
83
-
84
-
85
- def invalidate(req_id, base=None):
86
- """清整个 REQ 的缓存(PRD/spec 改动后调)。"""
87
- return _save(cache_path(req_id, base), {})
88
-
89
-
90
- def stats(req_id, base=None):
91
- """缓存条数 + query 摘要(调试/观测命中率用)。"""
92
- data = _load(cache_path(req_id, base))
93
- return {"entries": len(data),
94
- "queries": [str(e.get("query", ""))[:40] for e in data.values()]}
@@ -1,70 +0,0 @@
1
- # tool_guide — /wl-* 命令共用工具用法(单一信源)
2
-
3
- > 本文件是所有 `/wl-*` 命令**共用**的工具用法铁律的**唯一真源**。
4
- > SessionStart 会注入精简版;命令正文不再重复,只写「见 tool_guide」。
5
- > 改这里 = 改全部命令(单一信源,DRY)。AI 需要完整细节时直接 Read 本文件。
6
-
7
- ---
8
-
9
- ## §1 环境自检(任何脚本调用前先跑,定 `R` / `$PY`)
10
-
11
- > QoderWork 桌面端 cwd 不在仓库根(在 `.qoderwork/workspace/xxx`),相对路径
12
- > `.qoder/scripts/...` **必然失败**。进模式前必须先定位仓库根。Mac 只有 python3,两个都试:
13
-
14
- ```bash
15
- R=$(python ~/.qoderwork/repo_root.py 2>/dev/null || python3 ~/.qoderwork/repo_root.py 2>/dev/null) || R=.
16
- PY=$(python --version >/dev/null 2>&1 && echo python || echo python3)
17
- ```
18
-
19
- 后续所有脚本统一用 `$PY "$R/.qoder/scripts/orchestration/wlkj.py" <命令>`。
20
- **不准自己编 `cd /d` / 裸相对路径**。
21
-
22
- ---
23
-
24
- ## §2 接口铁律(防混名 / 防歧义 / 防跑飞)
25
-
26
- ### ① 三套名字已互通(引擎层别名,AI 猜哪个都对,别因"未知子命令"慌)
27
-
28
- | 你想干 | wlkj.py | kg.py | MCP 工具名 |
29
- |---|---|---|---|
30
- | 搜代码 | `search <词>` | `search <词>` | search_code |
31
- | 搜 API | `search <词>` | `api <词>` | search_api |
32
- | 语义检索 | `semantic <词>` | `semantic <词>` | rag_search |
33
- | 上下文打包 | `context <词>` | `context <词>` | context_pack |
34
- | 影响分析 | `impact <实体>` | `impact <实体>` | get_impact |
35
-
36
- > `kg.py search_code 保险` = `kg.py search 保险`,都认。用哪个名都行。
37
-
38
- ### ② 中文歧义词先提炼再查(否则"保险"命中的是社保不是车险)
39
-
40
- - 保险 → 车辆保险 / 商业险 / 交强险 / 保险异常
41
- - 异常 → 告警 alarmType / 报错 / 校验失败
42
- - 计划 → 作业计划 / 养护计划 / 计划任务
43
- - 列表/记录 → 带具体业务对象(车辆保险记录、考勤记录)
44
-
45
- > 拿不准时先 `kg.py search <具体词>` 看返回的控制器/实体名,再用全名 `kg.py context <全名>`。
46
-
47
- ### ③ 禁止跌入手动 grep/find 读源码
48
-
49
- 知识层有就拿知识层;**只有**以下情况才用 Grep/Read 读源码:
50
- - 知识层确实没建(search 返 0 条,且换词仍 0)
51
- - 要看某个具体文件的实现细节(已定位到文件)
52
-
53
- > ❌ 不要 `find data/code` / `grep -r` / `findstr /s` / `os.walk` 全盘扫——那是知识层 search 的活,手动做 = 思考链跑飞。
54
-
55
- ---
56
-
57
- ## §3 通用工具优先级(宿主能力 × 知识层分工)
58
-
59
- | 步骤 | 用谁 | 原因 |
60
- |------|------|------|
61
- | 精确搜代码内容 | **Qoder Grep/Read** | 原生更快 |
62
- | 语义找相关代码/字段 | **MCP rag_search** | 语义召回最全(没共享词也能找到) |
63
- | 取业务全上下文 | **MCP context_pack** | 一次取全 8 段 |
64
- | 查真表结构/枚举 | **MCP query_schema**(先 list_envs 问环境) | 数据库真实列名 |
65
- | 全局业务流程问答 | **MCP ask_corpus**(GraphRAG) | 社区摘要答案级 |
66
- | 影响分析 | **MCP get_impact** | 改动传播专用 |
67
- | 生成正文/代码 | **Qoder AI** | 宿主 AI 比调 qwen-plus 更强 |
68
- | 写文件 | **Qoder Write/Edit** | 原生文件操作 |
69
-
70
- > 口诀:**精确搜用 Grep,语义找用 rag_search,取全用 context_pack,查库先问环境,影响用 get_impact**。
@@ -1,176 +0,0 @@
1
- """alignment_matrix.py — FW6 扩展:4 命令类各自的 准确性 × 速度 × token 对齐(weak 档 Pareto 赢家)。
2
-
3
- 用户:"针对不同指标对齐准确性和速度"。search/prd/code/task 各自的准确性指标(召回/完整性/精准/路由)
4
- × latency(P50) × token,bare vs +工作流 scaffold,全在 weak(qwen-turbo,FW6 证的 Pareto 最优档)。
5
- 证明:弱模型+工作流在【每个命令类】都又快又准(bare 快但废,scaffold 准且仍最快档)。
6
-
7
- 跑法: docker exec -i wlinkj-workspace-backend-1 python /app/scripts/validation/eval/alignment_matrix.py
8
- """
9
- import os
10
- import re
11
- import sys
12
- import json
13
- import time
14
- import urllib.request
15
- import urllib.error
16
-
17
- for _p in ("/app", os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "..", ".."))):
18
- if os.path.isdir(_p) and _p not in sys.path:
19
- sys.path.insert(0, _p)
20
-
21
- from sqlalchemy import text # noqa: E402
22
- from app.db import SessionLocal # noqa: E402
23
-
24
- CHAT_URL = os.environ.get("ALI_CHAT_BASE", "").rstrip("/") + "/chat/completions"
25
- CHAT_KEY = os.environ.get("DASHSCOPE_API_KEY", "")
26
- MODEL = "qwen-turbo"
27
- QUICK_SECTIONS = ["功能入口", "需求背景", "需求说明", "影响范围", "验收标准", "不在本次范围"]
28
- TASK_ACTIONS = ["create", "list", "show", "start", "finish", "archive", "rank", "plan", "sync", "zentao"]
29
- _CODE_TOK = re.compile(r"[A-Za-z_][\w./\-:]{2,}")
30
-
31
-
32
- def chat(prompt, max_tokens=500, timeout=90):
33
- body = json.dumps({"model": MODEL, "messages": [{"role": "user", "content": prompt}],
34
- "temperature": 0.0, "max_tokens": max_tokens}).encode()
35
- t0 = time.time()
36
- for a in range(3):
37
- try:
38
- req = urllib.request.Request(CHAT_URL, data=body,
39
- headers={"Authorization": f"Bearer {CHAT_KEY}", "Content-Type": "application/json"})
40
- d = json.loads(urllib.request.urlopen(req, timeout=timeout).read())
41
- return d["choices"][0]["message"]["content"], int(d.get("usage", {}).get("total_tokens", 0)), round((time.time()-t0)*1000)
42
- except (urllib.error.URLError, TimeoutError, OSError, KeyError) as e:
43
- if a < 2: time.sleep(1.0*(a+1))
44
- else: return f"[ERR {repr(e)[:50]}]", 0, round((time.time()-t0)*1000)
45
-
46
-
47
- def _q(s, sql, p=None):
48
- try: return s.execute(text(sql), p or {}).fetchall()
49
- except Exception:
50
- try: s.rollback()
51
- except Exception: pass
52
- return []
53
-
54
-
55
- def real_entities(theme, k=15):
56
- s = SessionLocal()
57
- rs = _q(s, "SELECT canonical, cn FROM entities WHERE (canonical ILIKE :p OR cn ILIKE :p) LIMIT :n", {"p": f"%{theme}%", "n": k})
58
- s.close()
59
- return [(c, n) for c, n in rs if c]
60
-
61
-
62
- # ── 各命令类的 bare/scaffold prompt + 准确性指标 ──
63
- def search_task(theme, mode):
64
- reals = real_entities(theme, 45)
65
- ctx = "\n".join("%s (%s)" % (c, n) for c, n in reals)
66
- if mode == "bare":
67
- p = f"找ICS系统和「{theme}」相关的具体对象(表/类/接口),逐行列出确信真实存在的,勿编造。"
68
- else:
69
- p = f"找ICS系统和「{theme}」相关的具体对象。知识图谱检索到(均真实):\n{ctx}\n请把检索结果实体尽量全部逐行列出,直接用名称,勿筛选勿编造。"
70
- return p, reals
71
-
72
-
73
- def search_acc(ans, reals):
74
- hit = sum(1 for c, n in reals if (c and c in ans) or (n and n in ans))
75
- return round(hit / max(len(reals), 1), 3)
76
-
77
-
78
- def prd_task(item, mode):
79
- feature, theme = item
80
- reals = real_entities(theme, 15)
81
- ctx = "\n".join("%s (%s)" % (c, n) for c, n in reals)
82
- if mode == "bare":
83
- p = f"为「{feature}」写一份PRD需求文档。直接输出正文。"
84
- else:
85
- p = (f"为「{feature}」写PRD。检索到真实对象:\n{ctx}\n铁律:必须含6章(## 功能入口/需求背景/需求说明/"
86
- f"影响范围/验收标准/不在本次范围),字段用检索到的真实名称,勿编造。")
87
- return p, None
88
-
89
-
90
- def prd_acc(ans, _):
91
- return sum(1 for s in QUICK_SECTIONS if re.search(r'#{0,3}\s*\*{0,2}' + s, ans)) # /6
92
-
93
-
94
- def code_task(item, mode):
95
- feature, theme = item
96
- reals = real_entities(theme, 45) # FW3 已证喂全 45 才达标
97
- ctx = "\n".join("%s (%s)" % (c, n) for c, n in reals)
98
- if mode == "bare":
99
- p = f"给「{feature}」加功能,逐行列出需修改的代码文件/类/方法名,只列确信真实的,勿编造。"
100
- else:
101
- p = f"给「{feature}」加功能。get_impact 检索到真实实体:\n{ctx}\n请把检索结果实体全部逐行列出,直接用名称,勿筛选勿编造。"
102
- return p, reals
103
-
104
-
105
- def code_acc(ans, reals):
106
- if not reals: return 0
107
- hit = sum(1 for c, n in reals if (c and c in ans) or (n and n in ans))
108
- return round(hit / len(reals), 3)
109
-
110
-
111
- TASK_GOLD = [("建任务报价单导出", "create"), ("看任务", "list"), ("开始做登录", "start"),
112
- ("做完了", "finish"), ("PRD发禅道", "zentao"), ("排优先级", "rank")]
113
- TASK_SPEC = ("按第一个词/意图选动作(create/list/show/start/finish/archive/rank/plan/sync/zentao):"
114
- "建→create/看→list/开始→start/完了→finish/PRD发禅道或记工时→zentao/排优先级→rank")
115
-
116
-
117
- def task_task(query, mode):
118
- if mode == "bare":
119
- p = f"判断 /wl-task 操作「{query}」该走哪个动作(从 {TASK_ACTIONS} 选),只输出动作名。"
120
- else:
121
- p = f"{TASK_SPEC}\n操作「{query}」走哪个动作?只输出动作名(从 {TASK_ACTIONS})。"
122
- return p, query
123
-
124
-
125
- def task_acc(ans, query):
126
- gold = dict(TASK_GOLD)[query]
127
- return 1.0 if gold in ans.lower() else 0.0
128
-
129
-
130
- def run():
131
- cells = {}
132
- # search (3 主题)
133
- for cls, fn, accfn, items in [
134
- ("search", search_task, search_acc, ["考勤", "保险", "车辆"]),
135
- ("prd", prd_task, prd_acc, [("车辆保养提醒", "车辆"), ("考勤异常导出", "考勤"), ("保险续保预警", "保险")]),
136
- ("code", code_task, code_acc, [("车辆保养", "车辆"), ("考勤导出", "考勤"), ("保险预警", "保险")]),
137
- ]:
138
- for mode in ("bare", "scaffold"):
139
- accs, lats, toks = [], [], []
140
- for it in items:
141
- p, aux = fn(it, mode)
142
- mt = 900 if cls == "prd" else 500 # prd 6 章需更大 max_tokens(500 截断致缺章)
143
- ans, tok, lat = chat(p, max_tokens=mt)
144
- accs.append(accfn(ans, aux)); lats.append(lat); toks.append(tok)
145
- n = len(items)
146
- cells[f"{cls}/{mode}"] = {"acc": round(sum(accs)/n, 3), "p50_lat": sorted(lats)[n//2], "tok": round(sum(toks)/n)}
147
- print(" %s/%s: acc=%.2f p50=%dms tok=%d" % (cls, mode, sum(accs)/n, sorted(lats)[n//2], sum(toks)/n))
148
- # task (6 短语)
149
- for mode in ("bare", "scaffold"):
150
- accs, lats, toks = [], [], []
151
- for q, _ in TASK_GOLD:
152
- p, aux = task_task(q, mode)
153
- ans, tok, lat = chat(p, max_tokens=20)
154
- accs.append(task_acc(ans, aux)); lats.append(lat); toks.append(tok)
155
- n = len(TASK_GOLD)
156
- cells[f"task/{mode}"] = {"acc": round(sum(accs)/n, 3), "p50_lat": sorted(lats)[n//2], "tok": round(sum(toks)/n)}
157
- print(" task/%s: acc=%.2f p50=%dms tok=%d" % (mode, sum(accs)/n, sorted(lats)[n//2], sum(toks)/n))
158
- return {"model": MODEL, "cells": cells}
159
-
160
-
161
- def main():
162
- r = run()
163
- c = r["cells"]
164
- print("\n## FW6-扩展 4 命令类 准×快×token 对齐(weak 档,bare vs +工作流)")
165
- print("| 命令类 | 模式 | 准确性 | 速P50 | tok |")
166
- print("|---|---|---|---|---|")
167
- for cls in ("search", "prd", "code", "task"):
168
- for mode in ("bare", "scaffold"):
169
- x = c[f"{cls}/{mode}"]
170
- print("| %s | %s | %.2f | %dms | %d |" % (cls, mode, x["acc"], x["p50_lat"], x["tok"]))
171
- print("\n结论:每类 bare→scaffold 准确性 0→达标,scaffold 仍在 weak 档(最快档);"
172
- "弱模型+工作流在【每个命令类】都又快又准。")
173
-
174
-
175
- if __name__ == "__main__":
176
- main()