@hupan56/wlkj 3.0.0 → 3.1.2
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 +1435 -1373
- package/package.json +1 -1
- package/templates/qoder/commands/optional/wl-insight.md +6 -4
- package/templates/qoder/commands/optional/wl-report.md +20 -39
- package/templates/qoder/commands/optional/wl-status.md +4 -4
- package/templates/qoder/commands/wl-code.md +2 -2
- package/templates/qoder/commands/wl-design.md +5 -5
- package/templates/qoder/commands/wl-init.md +3 -3
- package/templates/qoder/commands/wl-prd.md +26 -13
- package/templates/qoder/commands/wl-req.md +3 -3
- package/templates/qoder/commands/wl-search.md +78 -47
- package/templates/qoder/commands/wl-task.md +343 -40
- package/templates/qoder/commands/wl-test.md +10 -10
- package/templates/qoder/config.yaml +35 -13
- package/templates/qoder/hooks/post-tool-use.py +181 -0
- package/templates/qoder/hooks/session-start.py +82 -1
- package/templates/qoder/hooks/stop-eval.py +207 -0
- package/templates/qoder/hooks/user-prompt-submit.py +139 -0
- package/templates/qoder/rules/wl-pipeline.md +70 -8
- package/templates/qoder/scripts/README.md +78 -115
- package/templates/qoder/scripts/capability/__init__.py +26 -0
- package/templates/qoder/scripts/capability/__main__.py +72 -0
- package/templates/qoder/scripts/capability/adapters/__init__.py +29 -0
- package/templates/qoder/scripts/capability/adapters/cli.py +316 -0
- package/templates/qoder/scripts/capability/adapters/mcp.py +334 -0
- package/templates/qoder/scripts/capability/adapters/qw.py +271 -0
- package/templates/qoder/scripts/capability/caps/__init__.py +27 -0
- package/templates/qoder/scripts/capability/caps/context.py +36 -0
- package/templates/qoder/scripts/capability/caps/cron.py +50 -0
- package/templates/qoder/scripts/capability/caps/identity.py +43 -0
- package/templates/qoder/scripts/capability/caps/memory.py +71 -0
- package/templates/qoder/scripts/capability/caps/notify.py +41 -0
- package/templates/qoder/scripts/capability/caps/present.py +48 -0
- package/templates/qoder/scripts/capability/caps/repo.py +31 -0
- package/templates/qoder/scripts/capability/caps/sandbox.py +43 -0
- package/templates/qoder/scripts/capability/chain.py +92 -0
- package/templates/qoder/scripts/capability/memory_chain.py +41 -0
- package/templates/qoder/scripts/capability/registry.py +246 -0
- package/templates/qoder/scripts/capability/registry_mcp.py +250 -0
- package/templates/qoder/scripts/capability/smoke_test.py +211 -0
- package/templates/qoder/scripts/capability/smoke_test_report.json +94 -0
- package/templates/qoder/scripts/{report → deployment/setup}/__init__.py +11 -11
- package/templates/qoder/scripts/{setup → deployment/setup}/carriers.py +33 -26
- package/templates/qoder/scripts/deployment/setup/carriers_verify.py +148 -0
- package/templates/qoder/scripts/{setup → deployment/setup}/init_doctor.py +40 -16
- package/templates/qoder/scripts/{setup → deployment/setup}/install_qoderwork.py +20 -12
- package/templates/qoder/scripts/{setup → deployment/setup}/platform_doctor.py +8 -2
- package/templates/qoder/scripts/{setup → deployment/setup}/repo_root.py +113 -112
- package/templates/qoder/scripts/{setup → deployment/setup}/setup.py +31 -11
- package/templates/qoder/scripts/{setup → deployment/setup}/setup_lanhu.py +13 -7
- package/templates/qoder/scripts/domain/__init__.py +0 -0
- package/templates/qoder/scripts/domain/design/__init__.py +0 -0
- package/templates/qoder/scripts/{report → domain/design}/fill_prototype.py +12 -6
- package/templates/qoder/scripts/{report → domain/design}/gen_design_doc.py +8 -2
- package/templates/qoder/scripts/{setup → domain/kg}/__init__.py +11 -11
- package/templates/qoder/scripts/domain/kg/build/__init__.py +0 -0
- package/templates/qoder/scripts/{kg → domain/kg/build}/build_entity_registry.py +7 -2
- package/templates/qoder/scripts/{kg → domain/kg/build}/build_relations.py +7 -2
- package/templates/qoder/scripts/{kg → domain/kg/build}/build_style_index.py +13 -7
- package/templates/qoder/scripts/{kg → domain/kg/build}/build_workflows.py +7 -2
- package/templates/qoder/scripts/{kg → domain/kg/build}/kg_build.py +76 -9
- package/templates/qoder/scripts/{kg → domain/kg/build}/kg_build_db.py +7 -2
- package/templates/qoder/scripts/{kg → domain/kg/build}/kg_incremental.py +11 -6
- package/templates/qoder/scripts/{report → domain/kg/build}/learn_aggregate.py +9 -4
- package/templates/qoder/scripts/domain/kg/extract/__init__.py +0 -0
- package/templates/qoder/scripts/{common → domain/kg/extract}/extract.py +430 -419
- package/templates/qoder/scripts/{common → domain/kg/extract}/test_extract.py +115 -115
- package/templates/qoder/scripts/{common → domain/kg/extract}/ts_extract.py +614 -536
- package/templates/qoder/scripts/domain/kg/graph/__init__.py +0 -0
- package/templates/qoder/scripts/{kg → domain/kg/graph}/kg_link_db.py +7 -2
- package/templates/qoder/scripts/domain/kg/graph/kg_semantic.py +683 -0
- package/templates/qoder/scripts/{kg → domain/kg}/kg.py +263 -30
- package/templates/qoder/scripts/{common → domain/kg}/kg_capabilities.py +182 -182
- package/templates/qoder/scripts/domain/kg/search/__init__.py +0 -0
- package/templates/qoder/scripts/{kg → domain/kg/search}/context_pack.py +30 -9
- package/templates/qoder/scripts/{kg → domain/kg/search}/enrich_prompt.py +10 -4
- package/templates/qoder/scripts/{kg → domain/kg/search}/prefetch.py +33 -8
- package/templates/qoder/scripts/{common → domain/kg/search}/search_engine.py +207 -205
- package/templates/qoder/scripts/{kg → domain/kg/search}/search_index.py +143 -77
- package/templates/qoder/scripts/domain/kg/server/__init__.py +0 -0
- package/templates/qoder/scripts/domain/kg/server/kgd.py +549 -0
- package/templates/qoder/scripts/domain/kg/server/perf_bench.py +197 -0
- package/templates/qoder/scripts/domain/kg/storage/__init__.py +0 -0
- package/templates/qoder/scripts/{kg → domain/kg/storage}/kg_duckdb.py +38 -14
- package/templates/qoder/scripts/domain/learning/__init__.py +0 -0
- package/templates/qoder/scripts/{report → domain/learning}/learn.py +8 -3
- package/templates/qoder/scripts/{mcp → domain/report}/__init__.py +11 -11
- package/templates/qoder/scripts/{report → domain/report}/add_session.py +9 -3
- package/templates/qoder/scripts/{report → domain/report}/export.py +10 -5
- package/templates/qoder/scripts/{report → domain/report}/report.py +9 -4
- package/templates/qoder/scripts/domain/report/report_snapshot.py +261 -0
- package/templates/qoder/scripts/{report → domain/report}/role.py +10 -4
- package/templates/qoder/scripts/{report → domain/report}/status.py +112 -9
- package/templates/qoder/scripts/domain/report/status_snapshot.py +191 -0
- package/templates/qoder/scripts/domain/requirement/__init__.py +0 -0
- package/templates/qoder/scripts/{report → domain/requirement}/archive_prd.py +8 -2
- package/templates/qoder/scripts/{report → domain/requirement}/req.py +228 -222
- package/templates/qoder/scripts/{kg → domain/task}/__init__.py +11 -11
- package/templates/qoder/scripts/{task → domain/task}/git_sync.py +40 -21
- package/templates/qoder/scripts/{task → domain/task}/syncgate.py +13 -8
- package/templates/qoder/scripts/{task → domain/task}/task.py +13 -5
- package/templates/qoder/scripts/{task → domain/task}/task_lifecycle.py +606 -596
- package/templates/qoder/scripts/{task → domain/task}/task_query.py +162 -161
- package/templates/qoder/scripts/{task → domain/task}/task_relations.py +425 -424
- package/templates/qoder/scripts/{task → domain/task}/team_sync.py +9 -4
- package/templates/qoder/scripts/domain/task/zentao_sync.py +370 -0
- package/templates/qoder/scripts/foundation/__init__.py +0 -0
- package/templates/qoder/scripts/{common → foundation}/bootstrap.py +10 -10
- package/templates/qoder/scripts/foundation/core/__init__.py +0 -0
- package/templates/qoder/scripts/{common → foundation/core}/cmd_registry.py +1 -1
- package/templates/qoder/scripts/{common → foundation/core}/config.py +1 -1
- package/templates/qoder/scripts/{common → foundation/core}/paths.py +706 -586
- package/templates/qoder/scripts/foundation/data/__init__.py +0 -0
- package/templates/qoder/scripts/{common → foundation/data}/contract.py +2 -2
- package/templates/qoder/scripts/{common → foundation/data}/events.py +2 -2
- package/templates/qoder/scripts/{common → foundation/data}/result.py +1 -1
- package/templates/qoder/scripts/{common → foundation/data}/task_utils.py +427 -404
- package/templates/qoder/scripts/foundation/identity/__init__.py +0 -0
- package/templates/qoder/scripts/{common → foundation/identity}/check_publish.py +103 -98
- package/templates/qoder/scripts/{common → foundation/identity}/developer.py +239 -239
- package/templates/qoder/scripts/{common → foundation/identity}/guard.py +2 -2
- package/templates/qoder/scripts/{common → foundation/identity}/identity.py +14 -10
- package/templates/qoder/scripts/{common → foundation/identity}/roles.py +1 -1
- package/templates/qoder/scripts/foundation/integrations/__init__.py +0 -0
- package/templates/qoder/scripts/{common → foundation/integrations}/active_task.py +30 -15
- package/templates/qoder/scripts/{common → foundation/integrations}/feishu.py +2 -2
- package/templates/qoder/scripts/{common → foundation/integrations}/terms.py +3 -3
- package/templates/qoder/scripts/foundation/integrations/zentao_client.py +220 -0
- package/templates/qoder/scripts/foundation/io/__init__.py +0 -0
- package/templates/qoder/scripts/{common → foundation/io}/filelock.py +1 -1
- package/templates/qoder/scripts/{common → foundation/io}/reqid.py +1 -1
- package/templates/qoder/scripts/foundation/protocol/__init__.py +0 -0
- package/templates/qoder/scripts/foundation/protocol/duckdb_conn.py +39 -0
- package/templates/qoder/scripts/{common → foundation/protocol}/mcp_base.py +1 -1
- package/templates/qoder/scripts/foundation/utils/__init__.py +0 -0
- package/templates/qoder/scripts/{common → foundation/utils}/eval_api.py +12 -1
- package/templates/qoder/scripts/{common → foundation/utils}/pip_install.py +23 -6
- package/templates/qoder/scripts/{common → foundation/utils}/platform_guard.py +1 -1
- package/templates/qoder/scripts/orchestration/__init__.py +0 -0
- package/templates/qoder/scripts/orchestration/wlkj.py +185 -0
- package/templates/qoder/scripts/protocol/__init__.py +0 -0
- package/templates/qoder/scripts/protocol/browser/README.md +207 -0
- package/templates/qoder/scripts/protocol/browser/SKILL.md +265 -0
- package/templates/qoder/scripts/protocol/browser/config.env +9 -0
- package/templates/qoder/scripts/protocol/browser/references/cdp-api.md +110 -0
- package/templates/qoder/scripts/protocol/browser/references/migration-2.5.3.md +72 -0
- package/templates/qoder/scripts/protocol/browser/references/site-patterns/.gitkeep +0 -0
- package/templates/qoder/scripts/protocol/browser/scripts/browser-discovery.mjs +138 -0
- package/templates/qoder/scripts/protocol/browser/scripts/cdp-proxy.mjs +672 -0
- package/templates/qoder/scripts/protocol/browser/scripts/check-deps.mjs +206 -0
- package/templates/qoder/scripts/protocol/browser/scripts/find-url.mjs +253 -0
- package/templates/qoder/scripts/protocol/browser/scripts/match-site.mjs +46 -0
- package/templates/qoder/scripts/protocol/browser/templates/config.env.template +9 -0
- package/templates/qoder/scripts/protocol/mcp/__init__.py +11 -0
- package/templates/qoder/scripts/protocol/mcp/kg_mcp_server.py +478 -0
- package/templates/qoder/scripts/{mcp → protocol/mcp}/lanhu_stdio_wrapper.py +11 -5
- package/templates/qoder/scripts/{mcp → protocol/mcp}/mcp_doctor.py +47 -14
- package/templates/qoder/scripts/protocol/mcp/mcp_launcher.py +600 -0
- package/templates/qoder/scripts/{mcp → protocol/mcp}/mysql_mcp_server.py +181 -67
- package/templates/qoder/scripts/protocol/mcp/zentao_mcp_server.py +1920 -0
- package/templates/qoder/scripts/protocol/transports/__init__.py +56 -0
- package/templates/qoder/scripts/protocol/transports/base.py +87 -0
- package/templates/qoder/scripts/protocol/transports/cli.py +132 -0
- package/templates/qoder/scripts/protocol/transports/http.py +141 -0
- package/templates/qoder/scripts/protocol/transports/stdio.py +293 -0
- package/templates/qoder/scripts/run_weekly_update.bat +27 -18
- package/templates/qoder/scripts/run_weekly_update.sh +22 -13
- package/templates/qoder/scripts/validation/__init__.py +0 -0
- package/templates/qoder/scripts/validation/eval/__init__.py +0 -0
- package/templates/qoder/scripts/validation/eval/perf-report-2026-06-24.md +139 -0
- package/templates/qoder/scripts/validation/eval/qwork_harness.py +617 -0
- package/templates/qoder/scripts/validation/eval/report-commands.md +220 -0
- package/templates/qoder/scripts/validation/eval/transcript_timing.py +386 -0
- package/templates/qoder/scripts/validation/metrics/__init__.py +0 -0
- package/templates/qoder/scripts/{report → validation/metrics}/eval_prd.py +17 -10
- package/templates/qoder/scripts/validation/metrics/usability_score.py +750 -0
- package/templates/qoder/scripts/validation/test/__init__.py +11 -0
- package/templates/qoder/scripts/validation/test/assertion_gen.py +551 -0
- package/templates/qoder/scripts/{test → validation/test}/autotest.py +64 -4
- package/templates/qoder/scripts/{common → validation/test}/autotest_auth.py +1 -1
- package/templates/qoder/scripts/{test → validation/test}/autotest_batch.py +16 -3
- package/templates/qoder/scripts/{test → validation/test}/autotest_data.py +17 -12
- package/templates/qoder/scripts/validation/test/autotest_opencli.py +646 -0
- package/templates/qoder/scripts/{test → validation/test}/autotest_run.py +17 -3
- package/templates/qoder/scripts/validation/test/autotest_webaccess.py +571 -0
- package/templates/qoder/scripts/{test → validation/test}/benchmark.py +12 -6
- package/templates/qoder/scripts/{test → validation/test}/kg_auto_login.py +8 -2
- package/templates/qoder/scripts/{test → validation/test}/kg_test_runner.py +7 -2
- package/templates/qoder/scripts/{test → validation/test}/page_probe.py +8 -3
- package/templates/qoder/scripts/validation/test/smoke_all_commands.py +170 -0
- package/templates/qoder/settings.json +30 -0
- package/templates/qoder/skills/design-import/SKILL.md +22 -13
- package/templates/qoder/skills/design-review/SKILL.md +19 -10
- package/templates/qoder/skills/prd-generator/SKILL.md +22 -18
- package/templates/qoder/skills/prd-review/SKILL.md +6 -4
- package/templates/qoder/skills/prototype-generator/SKILL.md +63 -14
- package/templates/qoder/skills/spec-coder/SKILL.md +22 -13
- package/templates/qoder/skills/spec-generator/SKILL.md +22 -10
- package/templates/qoder/skills/test-generator/SKILL.md +7 -5
- package/templates/qoder/skills/wl-code/SKILL.md +23 -11
- package/templates/qoder/skills/wl-commit/SKILL.md +9 -8
- package/templates/qoder/skills/wl-design/SKILL.md +5 -3
- package/templates/qoder/skills/wl-init/SKILL.md +6 -6
- package/templates/qoder/skills/wl-insight/SKILL.md +23 -10
- package/templates/qoder/skills/wl-prd-full/SKILL.md +57 -6
- package/templates/qoder/skills/wl-prd-quick/SKILL.md +3 -3
- package/templates/qoder/skills/wl-prd-review/SKILL.md +16 -4
- package/templates/qoder/skills/wl-report/SKILL.md +25 -23
- package/templates/qoder/skills/wl-search/SKILL.md +149 -78
- package/templates/qoder/skills/wl-spec/SKILL.md +7 -5
- package/templates/qoder/skills/wl-status/SKILL.md +48 -16
- package/templates/qoder/skills/wl-task/SKILL.md +92 -10
- package/templates/qoder/skills/wl-test/SKILL.md +75 -29
- package/templates/qoder/templates/prd-full-template.md +7 -0
- package/templates/root/AGENTS.md +275 -236
- package/templates/root/requirements.txt +3 -0
- package/templates/root//344/275/277/347/224/250/350/257/264/346/230/216.md +3 -3
- package/templates/root//346/226/260/346/211/213/346/214/207/345/215/227.md +2 -2
- package/templates/.qoder/.runtime/ctx-cache-5660152f1d6dd819.md +0 -23
- package/templates/.qoder/.runtime/ctx-cache-afdce0dac06b25b0.md +0 -23
- package/templates/.qoder/.runtime/search-cache-eae7644e7b122f35.txt +0 -1
- package/templates/.qoder/learning/eval-history.jsonl +0 -28
- package/templates/data/index/wiki-index.json +0 -8
- package/templates/qoder/nul +0 -4
- package/templates/qoder/scripts/kg/extract.py +0 -398
- package/templates/qoder/scripts/kg/kg_semantic.py +0 -156
- package/templates/qoder/scripts/mcp/kg_mcp_server.py +0 -606
- package/templates/qoder/scripts/mcp/mcp_launcher.py +0 -442
- package/templates/qoder/scripts/mcp/zentao_mcp_server.py +0 -384
- package/templates/qoder/scripts/task/__init__.py +0 -11
- package/templates/qoder/scripts/test/__init__.py +0 -11
- package/templates/qoder/scripts/wlkj.py +0 -116
- /package/templates/qoder/scripts/{common → deployment}/__init__.py +0 -0
- /package/templates/qoder/scripts/{common → domain/kg/graph}/graph_traverse.py +0 -0
- /package/templates/qoder/scripts/{common → domain/kg/server}/repowiki.py +0 -0
- /package/templates/qoder/scripts/{common → foundation/io}/atomicio.py +0 -0
- /package/templates/qoder/scripts/{test → validation/test}/secure-ls.js +0 -0
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
transports.stdio —— 本地 stdio MCP 传输策略(现状默认)。
|
|
4
|
+
|
|
5
|
+
设计模式: Strategy(策略模式的本地实现)
|
|
6
|
+
|
|
7
|
+
机制:
|
|
8
|
+
通过 mcp_launcher.py spawn 本地 MCP server 子进程,
|
|
9
|
+
维持 stdin/stdout 长连接,复 JSON-RPC 2.0 协议。
|
|
10
|
+
复用 mcp_base.py 已有的协议骨架,不重造轮子。
|
|
11
|
+
|
|
12
|
+
进程复用 (Flyweight/Pool):
|
|
13
|
+
每个 server 名对应一个 StdioTransport 实例,子进程长驻,
|
|
14
|
+
多次 call_tool 复用同一进程,避免冷启开销(kg 有 duckdb 冷启)。
|
|
15
|
+
子进程崩溃 (poll() != None) 自动重启。
|
|
16
|
+
|
|
17
|
+
线程安全:
|
|
18
|
+
每个 transport 内部一把锁,保证请求-响应串行配对
|
|
19
|
+
(MCP 协议要求 id 匹配,并发交错会乱)。
|
|
20
|
+
|
|
21
|
+
协议(与 mcp_base.py _handle 对齐):
|
|
22
|
+
1. initialize 握手 → capabilities
|
|
23
|
+
2. notifications/initialized 通知
|
|
24
|
+
3. tools/list → 缓存工具清单
|
|
25
|
+
4. tools/call → 返回 content[0].text
|
|
26
|
+
"""
|
|
27
|
+
from __future__ import annotations
|
|
28
|
+
|
|
29
|
+
import json
|
|
30
|
+
import os
|
|
31
|
+
import subprocess
|
|
32
|
+
import sys
|
|
33
|
+
import threading
|
|
34
|
+
from pathlib import Path
|
|
35
|
+
from typing import Optional
|
|
36
|
+
|
|
37
|
+
from .base import ToolResult
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
# MCP 协议版本(与 mcp_base.py:PROTOCOL_VERSION 对齐)
|
|
41
|
+
_PROTOCOL_VERSION = "2024-11-05"
|
|
42
|
+
|
|
43
|
+
# 读超时 (秒)。冷启动 + 握手 + tools/list 的总等待上限。
|
|
44
|
+
# 历史 bug: readline 无超时会永久阻塞 —— 并发冷启动竞态下某 server 响应迟迟不来,
|
|
45
|
+
# 整个 _build_index 的 ex.map 卡死或时序错乱 → 间歇性 0 工具。加超时后超时即失败可重试。
|
|
46
|
+
_READ_TIMEOUT = 30.0
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _get_repo_root() -> Path:
|
|
50
|
+
"""从 common.paths 获取仓库根(复用既有工具,不重复造轮子)。"""
|
|
51
|
+
try:
|
|
52
|
+
from foundation.core.paths import get_repo_root
|
|
53
|
+
return get_repo_root()
|
|
54
|
+
except Exception:
|
|
55
|
+
return Path(__file__).resolve().parent.parent.parent.parent
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class StdioTransport:
|
|
59
|
+
"""本地 stdio MCP 传输策略。
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
server_name: MCP server 名(对应 mcp_launcher 的参数,如 'kg'/'zentao')。
|
|
63
|
+
repo_root: 仓库根(定位 mcp_launcher.py),默认自动探测。
|
|
64
|
+
launcher_subprocess_name: launcher 接受的子名(多数情况同 server_name)。
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
def __init__(self, server_name: str, repo_root: Optional[Path] = None,
|
|
68
|
+
launcher_subprocess_name: Optional[str] = None):
|
|
69
|
+
self._server = server_name
|
|
70
|
+
self._launcher_arg = launcher_subprocess_name or server_name
|
|
71
|
+
self._repo = repo_root or _get_repo_root()
|
|
72
|
+
self._proc: Optional[subprocess.Popen] = None
|
|
73
|
+
self._lock = threading.RLock() # 可重入:_send_recv→_ensure_alive→_spawn 会递归加锁
|
|
74
|
+
self._tools_cache: Optional[list[dict]] = None
|
|
75
|
+
self._tool_index: Optional[set[str]] = None
|
|
76
|
+
self._req_id = 0
|
|
77
|
+
self._err: Optional[str] = None
|
|
78
|
+
self._handshook = False
|
|
79
|
+
self._stderr_lines: list[str] = [] # 排水收集(诊断用)
|
|
80
|
+
self._stderr_thread: Optional[threading.Thread] = None
|
|
81
|
+
|
|
82
|
+
@property
|
|
83
|
+
def available(self) -> bool:
|
|
84
|
+
"""此 transport 是否可用(子进程能拉起 + 握手成功)。"""
|
|
85
|
+
if self._err is not None:
|
|
86
|
+
return False
|
|
87
|
+
return self._ensure_alive()
|
|
88
|
+
|
|
89
|
+
# ------------------------------------------------------------------
|
|
90
|
+
# 进程管理
|
|
91
|
+
# ------------------------------------------------------------------
|
|
92
|
+
|
|
93
|
+
def _launcher_path(self) -> Path:
|
|
94
|
+
return self._repo / ".qoder" / "scripts" / "protocol" / "mcp" / "mcp_launcher.py"
|
|
95
|
+
|
|
96
|
+
def _ensure_alive(self) -> bool:
|
|
97
|
+
"""确保子进程存活且已握手。返回是否就绪。"""
|
|
98
|
+
if self._proc is not None and self._proc.poll() is None and self._handshook:
|
|
99
|
+
return True
|
|
100
|
+
return self._spawn()
|
|
101
|
+
|
|
102
|
+
def _spawn(self) -> bool:
|
|
103
|
+
"""spawn 子进程并握手。失败记 _err。"""
|
|
104
|
+
launcher = self._launcher_path()
|
|
105
|
+
if not launcher.is_file():
|
|
106
|
+
self._err = "mcp_launcher.py 不存在: %s" % launcher
|
|
107
|
+
return False
|
|
108
|
+
try:
|
|
109
|
+
self._proc = subprocess.Popen(
|
|
110
|
+
[sys.executable, str(launcher), self._launcher_arg],
|
|
111
|
+
stdin=subprocess.PIPE,
|
|
112
|
+
stdout=subprocess.PIPE,
|
|
113
|
+
stderr=subprocess.PIPE,
|
|
114
|
+
cwd=str(self._repo),
|
|
115
|
+
bufsize=1,
|
|
116
|
+
encoding="utf-8",
|
|
117
|
+
errors="replace",
|
|
118
|
+
# 关键:关掉 Python 子进程的 stdin 缓冲,避免 launcher in-proc 模式下
|
|
119
|
+
# runpy.run_path 的 server 读 stdin 时和 pipe 缓冲交互异常。
|
|
120
|
+
# text 模式下 close_fds 在 Win 上受限,但 PIPE 已隔离。
|
|
121
|
+
)
|
|
122
|
+
except Exception as e:
|
|
123
|
+
self._err = "拉起失败: %s" % str(e)[:120]
|
|
124
|
+
self._proc = None
|
|
125
|
+
return False
|
|
126
|
+
|
|
127
|
+
# 排空 stderr:launcher/server 诊断输出多,不排会撑满 pipe 缓冲阻塞 server。
|
|
128
|
+
# 守护线程持续读(与裸 subprocess 复刻保持一致的极简形态,先保证不阻塞)。
|
|
129
|
+
proc_ref = self._proc # 闭包捕获局部引用,避免 self._proc 重赋值竞态
|
|
130
|
+
stderr_ref = self._stderr_lines
|
|
131
|
+
def _drain():
|
|
132
|
+
assert proc_ref is not None and proc_ref.stderr is not None
|
|
133
|
+
try:
|
|
134
|
+
for line in proc_ref.stderr:
|
|
135
|
+
stderr_ref.append(line.rstrip())
|
|
136
|
+
if len(stderr_ref) > 200:
|
|
137
|
+
del stderr_ref[:-100]
|
|
138
|
+
except Exception:
|
|
139
|
+
pass
|
|
140
|
+
self._stderr_thread = threading.Thread(target=_drain, daemon=True)
|
|
141
|
+
self._stderr_thread.start()
|
|
142
|
+
|
|
143
|
+
# 握手 initialize(直接底层收发,不走 _send_recv——避免 _ensure_alive 递归 _spawn)
|
|
144
|
+
with self._lock:
|
|
145
|
+
try:
|
|
146
|
+
req = {"jsonrpc": "2.0", "id": 1, "method": "initialize",
|
|
147
|
+
"params": {"capabilities": {}, "protocolVersion": _PROTOCOL_VERSION,
|
|
148
|
+
"clientInfo": {"name": "nexus", "version": "1.0"}}}
|
|
149
|
+
self._write(req)
|
|
150
|
+
resp = self._readline_matching(1)
|
|
151
|
+
if "result" not in resp:
|
|
152
|
+
self._err = "initialize 无 result: %s" % str(resp)[:120]
|
|
153
|
+
self._kill_unlocked()
|
|
154
|
+
return False
|
|
155
|
+
# 发 initialized 通知(无 id,无响应)
|
|
156
|
+
self._write({"jsonrpc": "2.0", "method": "notifications/initialized"})
|
|
157
|
+
self._handshook = True
|
|
158
|
+
# 握手后重置 tools 缓存(新进程)
|
|
159
|
+
self._tools_cache = None
|
|
160
|
+
self._tool_index = None
|
|
161
|
+
return True
|
|
162
|
+
except Exception as e:
|
|
163
|
+
self._err = "握手失败: %s" % str(e)[:120]
|
|
164
|
+
self._kill_unlocked()
|
|
165
|
+
return False
|
|
166
|
+
|
|
167
|
+
def _kill_unlocked(self) -> None:
|
|
168
|
+
"""_kill 的无锁版本(调用方已持锁,如 _spawn 内握手失败清理)。"""
|
|
169
|
+
if self._proc is not None:
|
|
170
|
+
try:
|
|
171
|
+
self._proc.stdin.close()
|
|
172
|
+
except Exception:
|
|
173
|
+
pass
|
|
174
|
+
try:
|
|
175
|
+
self._proc.terminate()
|
|
176
|
+
self._proc.wait(timeout=2)
|
|
177
|
+
except Exception:
|
|
178
|
+
try:
|
|
179
|
+
self._proc.kill()
|
|
180
|
+
except Exception:
|
|
181
|
+
pass
|
|
182
|
+
self._proc = None
|
|
183
|
+
self._handshook = False
|
|
184
|
+
|
|
185
|
+
def _kill(self) -> None:
|
|
186
|
+
"""杀子进程(持锁版本,close/外部调用用)。"""
|
|
187
|
+
with self._lock:
|
|
188
|
+
self._kill_unlocked()
|
|
189
|
+
|
|
190
|
+
# ------------------------------------------------------------------
|
|
191
|
+
# JSON-RPC 收发
|
|
192
|
+
# ------------------------------------------------------------------
|
|
193
|
+
|
|
194
|
+
def _next_id(self) -> int:
|
|
195
|
+
self._req_id += 1
|
|
196
|
+
return self._req_id
|
|
197
|
+
|
|
198
|
+
def _write(self, obj: dict) -> None:
|
|
199
|
+
"""写一行 JSON 到 stdin(调用方持锁)。"""
|
|
200
|
+
if self._proc is None or self._proc.stdin is None:
|
|
201
|
+
raise RuntimeError("子进程未就绪")
|
|
202
|
+
self._proc.stdin.write(json.dumps(obj, ensure_ascii=False) + "\n")
|
|
203
|
+
self._proc.stdin.flush()
|
|
204
|
+
|
|
205
|
+
def _readline_matching(self, rid: int, timeout: float = _READ_TIMEOUT) -> dict:
|
|
206
|
+
"""读 stdout 直到拿到匹配 rid 的响应(跳过通知/其他行),带 wall-clock 超时。
|
|
207
|
+
|
|
208
|
+
实测结论 (2026-06): 用裸 subprocess 并发 spawn 4 个 server, 阻塞 readline 全部
|
|
209
|
+
在 2-5s 内成功 (kg/zentao/mysql/lanhu)。原版阻塞 readline 本身没问题。
|
|
210
|
+
故保持阻塞读, 仅加进程存活检查 (poll) + wall-clock 超时兜底 (防极端卡死):
|
|
211
|
+
- readline 返回空 (EOF) 且进程已死 → 立即失败
|
|
212
|
+
- 累计耗时超 timeout → 失败 (上层 _build_index 空索引可重试)
|
|
213
|
+
之前尝试的"后台 reader 线程"版反而在并发下引入竞态 (与 _drain stderr 线程争用),
|
|
214
|
+
导致全部 30s 超时, 已废弃。
|
|
215
|
+
"""
|
|
216
|
+
import time
|
|
217
|
+
assert self._proc is not None and self._proc.stdout is not None
|
|
218
|
+
deadline = time.monotonic() + timeout
|
|
219
|
+
while True:
|
|
220
|
+
line = self._proc.stdout.readline()
|
|
221
|
+
if not line:
|
|
222
|
+
# EOF: 进程已退出或关连接
|
|
223
|
+
raise RuntimeError("MCP server 关闭连接 (EOF)")
|
|
224
|
+
line = line.strip()
|
|
225
|
+
if not line:
|
|
226
|
+
continue
|
|
227
|
+
try:
|
|
228
|
+
resp = json.loads(line)
|
|
229
|
+
except json.JSONDecodeError:
|
|
230
|
+
continue # 非 JSON 行(诊断输出等)跳过
|
|
231
|
+
if resp.get("id") == rid:
|
|
232
|
+
return resp
|
|
233
|
+
# 非 id 匹配(通知/其他响应)忽略,继续读
|
|
234
|
+
if time.monotonic() > deadline:
|
|
235
|
+
raise RuntimeError("读响应超时 (rid=%s)" % rid)
|
|
236
|
+
|
|
237
|
+
def _send_recv(self, method: str, params: dict | None = None,
|
|
238
|
+
req_id: int | None = None) -> dict:
|
|
239
|
+
"""发请求读响应(线程安全,持锁)。"""
|
|
240
|
+
with self._lock:
|
|
241
|
+
if not self._ensure_alive():
|
|
242
|
+
raise RuntimeError("transport 不可用: %s" % (self._err or "未知"))
|
|
243
|
+
rid = req_id if req_id is not None else self._next_id()
|
|
244
|
+
req = {"jsonrpc": "2.0", "id": rid, "method": method}
|
|
245
|
+
if params is not None:
|
|
246
|
+
req["params"] = params
|
|
247
|
+
try:
|
|
248
|
+
self._write(req)
|
|
249
|
+
return self._readline_matching(rid)
|
|
250
|
+
except Exception:
|
|
251
|
+
# 通信失败,标记进程可疑,下次 _ensure_alive 会重 spawn
|
|
252
|
+
self._handshook = False
|
|
253
|
+
raise
|
|
254
|
+
|
|
255
|
+
# ------------------------------------------------------------------
|
|
256
|
+
# Transport 接口实现
|
|
257
|
+
# ------------------------------------------------------------------
|
|
258
|
+
|
|
259
|
+
def list_tools(self) -> list[dict]:
|
|
260
|
+
"""枚举工具(缓存,每个进程只查一次)。"""
|
|
261
|
+
if self._tools_cache is not None:
|
|
262
|
+
return self._tools_cache
|
|
263
|
+
resp = self._send_recv("tools/list")
|
|
264
|
+
tools = resp.get("result", {}).get("tools", [])
|
|
265
|
+
self._tools_cache = tools
|
|
266
|
+
self._tool_index = {t.get("name", "") for t in tools}
|
|
267
|
+
return tools
|
|
268
|
+
|
|
269
|
+
def has_tool(self, name: str) -> bool:
|
|
270
|
+
if self._tool_index is None:
|
|
271
|
+
try:
|
|
272
|
+
self.list_tools()
|
|
273
|
+
except Exception:
|
|
274
|
+
return False
|
|
275
|
+
return name in (self._tool_index or set())
|
|
276
|
+
|
|
277
|
+
def call_tool(self, name: str, args: dict | None = None) -> ToolResult:
|
|
278
|
+
try:
|
|
279
|
+
resp = self._send_recv("tools/call", {"name": name, "arguments": args or {}})
|
|
280
|
+
except Exception as e:
|
|
281
|
+
return ToolResult(text="transport 调用失败: %s" % str(e)[:120], is_error=True)
|
|
282
|
+
|
|
283
|
+
if "error" in resp:
|
|
284
|
+
msg = resp["error"].get("message", "unknown error")
|
|
285
|
+
return ToolResult(text=str(msg), is_error=True, raw=resp)
|
|
286
|
+
|
|
287
|
+
result = resp.get("result", {})
|
|
288
|
+
content = result.get("content", [])
|
|
289
|
+
text = content[0].get("text", "") if content else ""
|
|
290
|
+
return ToolResult(text=text, is_error=result.get("isError", False), raw=resp)
|
|
291
|
+
|
|
292
|
+
def close(self) -> None:
|
|
293
|
+
self._kill() # _kill 内部自行加锁
|
|
@@ -1,22 +1,31 @@
|
|
|
1
1
|
@echo off
|
|
2
|
-
REM QODER Weekly Knowledge Graph Update
|
|
3
|
-
REM Runs every Friday at 18:00
|
|
4
|
-
REM
|
|
5
|
-
|
|
2
|
+
REM QODER Weekly Knowledge Graph Update (Windows)
|
|
3
|
+
REM Runs every Friday at 18:00. %~dp0 = this script dir; project root = 2 levels up (安装后 .qoder/scripts/ 上2级)
|
|
4
|
+
REM v3.x: 走 wlkj.py 单一真源调度器 (不再用扁平路径, 防引擎分层重组后断链)
|
|
5
|
+
REM 重建后自动 commit + git gc (普通模式), 防松散对象膨胀 (见 ARCHITECTURE.md)
|
|
6
6
|
cd /d "%~dp0..\.."
|
|
7
|
-
|
|
8
|
-
echo [
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
REM
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
7
|
+
if not exist ".qoder\scripts\orchestration\wlkj.py" (
|
|
8
|
+
echo [ERROR] 引擎未安装, 请先 npx @hupan56/wlkj init
|
|
9
|
+
exit /b 1
|
|
10
|
+
)
|
|
11
|
+
set LOG=.qoder\logs\weekly-update.log
|
|
12
|
+
if not exist ".qoder\logs" mkdir ".qoder\logs"
|
|
13
|
+
echo [%date% %time%] Starting weekly update... >> "%LOG%"
|
|
14
|
+
REM Step 1: Git pull + PRD collect
|
|
15
|
+
python .qoder\scripts\orchestration\wlkj.py git-sync >> "%LOG%" 2>&1
|
|
16
|
+
REM Step 2: 重建组件级 style index (ui-style.json, 被 search/context_pack 消费, 不可删)
|
|
17
|
+
python .qoder\scripts\orchestration\wlkj.py style >> "%LOG%" 2>&1
|
|
18
|
+
REM Step 3: 知识图谱全量重建
|
|
19
|
+
python .qoder\scripts\orchestration\wlkj.py kg-build >> "%LOG%" 2>&1
|
|
18
20
|
if errorlevel 1 (
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
echo [%date% %time%] Update complete. >> .qoder\logs\weekly-update.log
|
|
21
|
+
echo [%date% %time%] UPDATE FAILED - check log above >> "%LOG%"
|
|
22
|
+
exit /b 1
|
|
22
23
|
)
|
|
24
|
+
REM Step 4: 提交重建产物 + git gc (防松散对象膨胀, kg.duckdb delta 压缩有效)
|
|
25
|
+
echo [%date% %time%] Committing rebuilt graph + git gc... >> "%LOG%"
|
|
26
|
+
git add data\index\kg.duckdb data\index\kg_db.duckdb data\index\ui-style.json >> "%LOG%" 2>&1
|
|
27
|
+
git commit -m "[kg] weekly rebuild %date%" >> "%LOG%" 2>&1
|
|
28
|
+
git gc --quiet >> "%LOG%" 2>&1
|
|
29
|
+
echo [%date% %time%] gc done (松散对象已打包) >> "%LOG%"
|
|
30
|
+
echo [%date% %time%] Update complete. >> "%LOG%"
|
|
31
|
+
exit /b 0
|
|
@@ -1,25 +1,34 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# QODER Weekly Knowledge Graph Update (macOS / Linux)
|
|
3
3
|
# Runs every Friday at 18:00 via cron (see setup_weekly_cron.sh)
|
|
4
|
+
# v3.x: 走 wlkj.py 单一真源调度器 (不再用扁平路径, 防引擎分层重组后断链)
|
|
5
|
+
# 重建后自动 commit + git gc (普通模式), 防松散对象膨胀
|
|
4
6
|
set -u
|
|
5
7
|
cd "$(dirname "$0")/../.." || exit 1
|
|
6
|
-
|
|
8
|
+
WLKJ=".qoder/scripts/orchestration/wlkj.py"
|
|
9
|
+
if [ ! -f "$WLKJ" ]; then
|
|
10
|
+
echo "[ERROR] 引擎未安装, 请先 npx @hupan56/wlkj init"
|
|
11
|
+
exit 1
|
|
12
|
+
fi
|
|
7
13
|
mkdir -p .qoder/logs
|
|
8
14
|
LOG=.qoder/logs/weekly-update.log
|
|
9
|
-
|
|
10
15
|
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Starting weekly update..." >> "$LOG"
|
|
11
|
-
|
|
12
|
-
python3
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
#
|
|
16
|
-
|
|
17
|
-
python3 .qoder/scripts/build_style_index.py >> "$LOG" 2>&1
|
|
16
|
+
# Step 1: Git pull + PRD collect
|
|
17
|
+
python3 "$WLKJ" git-sync >> "$LOG" 2>&1
|
|
18
|
+
# Step 2: 重建组件级 style index (ui-style.json, 被 search/context_pack 消费, 不可删)
|
|
19
|
+
python3 "$WLKJ" style >> "$LOG" 2>&1
|
|
20
|
+
# Step 3: 知识图谱全量重建
|
|
21
|
+
python3 "$WLKJ" kg-build >> "$LOG" 2>&1
|
|
18
22
|
STATUS=$?
|
|
19
|
-
|
|
20
23
|
if [ $STATUS -ne 0 ]; then
|
|
21
24
|
echo "[$(date '+%Y-%m-%d %H:%M:%S')] UPDATE FAILED (exit $STATUS) - check log above" >> "$LOG"
|
|
22
|
-
|
|
23
|
-
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Update complete." >> "$LOG"
|
|
25
|
+
exit $STATUS
|
|
24
26
|
fi
|
|
25
|
-
|
|
27
|
+
# Step 4: 提交重建产物 + git gc (防松散对象膨胀, kg.duckdb delta 压缩有效)
|
|
28
|
+
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Committing rebuilt graph + git gc..." >> "$LOG"
|
|
29
|
+
git add data/index/kg.duckdb data/index/kg_db.duckdb data/index/ui-style.json >> "$LOG" 2>&1
|
|
30
|
+
git commit -m "[kg] weekly rebuild $(date +%Y-%m-%d)" >> "$LOG" 2>&1
|
|
31
|
+
git gc --quiet >> "$LOG" 2>&1
|
|
32
|
+
echo "[$(date '+%Y-%m-%d %H:%M:%S')] gc done (松散对象已打包)" >> "$LOG"
|
|
33
|
+
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Update complete." >> "$LOG"
|
|
34
|
+
exit 0
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# QoderWork 工作流性能报告(v2 — 聚焦工作流本身)
|
|
2
|
+
|
|
3
|
+
> 剔除模型抖动(reason=error)和首 token 延迟,只看**工作流自身的效率**。
|
|
4
|
+
> 数据:2026-06-24 用户手动实跑 `/wl-*` 命令的真实 transcript。
|
|
5
|
+
|
|
6
|
+
## 核心结论:问题不在慢,在"碎"
|
|
7
|
+
|
|
8
|
+
工作流最大的低效不是单次调用慢,而是 **AI 在用"挤牙膏"式串行摸索**——
|
|
9
|
+
每个文件/脚本单独调一次 Bash,调完思考一轮,再调下一个。
|
|
10
|
+
|
|
11
|
+
实测每个 skill 都在**重复调用同一个目标**:
|
|
12
|
+
|
|
13
|
+
| Skill | 重复调用 | 次数 | 现象 |
|
|
14
|
+
|-------|---------|------|------|
|
|
15
|
+
| /wl-search | `kg.py`(知识图谱) | **×5** | 同一个脚本连调5遍 |
|
|
16
|
+
| /wl-status | `ls 目录` | **×5** | 反复 ls 探路 |
|
|
17
|
+
| /wl-status | `cat 文件` | **×5** | 配置/任务/反馈一个一个 cat |
|
|
18
|
+
| /wl-task | `task.py` | **×3** | 同脚本3次 |
|
|
19
|
+
| /wl-insight | `search_index.py` | ×2 | 同脚本2次 |
|
|
20
|
+
|
|
21
|
+
**对比基线**:简单命令(prd-quick / prd-review)只需 **1 轮迭代、0 次工具调用**,44s 完成。
|
|
22
|
+
复杂命令却要 **5-6 轮迭代、7-10 次调用**——多出来的全是碎片化摸索。
|
|
23
|
+
|
|
24
|
+
## 碎片化的真实样子(/wl-status 为例)
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
轮1: TodoWrite(列计划)
|
|
28
|
+
Bash: repo_root.py ← 找根目录(每次都找)
|
|
29
|
+
Bash: ls .qoder/ ← 探路
|
|
30
|
+
轮2: TodoWrite(更新)
|
|
31
|
+
Bash: cat .developer ← 查身份
|
|
32
|
+
Bash: ls tasks/ ← 列任务
|
|
33
|
+
Bash: cat feedback.jsonl ← 查反馈
|
|
34
|
+
Bash: cat eval-history ← 查评估
|
|
35
|
+
Bash: cat .index-meta ← 查索引元数据
|
|
36
|
+
轮3: TodoWrite(更新)
|
|
37
|
+
Bash: ls members/hupan56/ ← 查成员
|
|
38
|
+
Bash: ls patterns/ ← 查模式
|
|
39
|
+
Bash: cat config.yaml ← 查配置
|
|
40
|
+
轮4-5: ...
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
8 个 `cat`/`ls` 本可以**合并成 1 个脚本**一次拿全,却拆成 5 轮、10+ 次调用。
|
|
44
|
+
|
|
45
|
+
## 迭代轮数 = 成本放大器
|
|
46
|
+
|
|
47
|
+
每多一轮迭代,就多一次:
|
|
48
|
+
- 完整的模型请求(输入 token = 之前所有上下文累加)
|
|
49
|
+
- 工具往返延迟
|
|
50
|
+
- TodoWrite 进度更新(本身也占一次调用)
|
|
51
|
+
|
|
52
|
+
| 命令 | 迭代轮 | 工具调用 | vs 基线 |
|
|
53
|
+
|------|--------|---------|---------|
|
|
54
|
+
| prd-quick(基线) | **1** | 0 | — |
|
|
55
|
+
| /wl-task | 2 | 1-2 | 2× |
|
|
56
|
+
| /wl-report | 2 | 2 | 2× |
|
|
57
|
+
| /wl-insight | 3 | 4 | 3× |
|
|
58
|
+
| /wl-status | **5** | 10 | **5×** |
|
|
59
|
+
| /wl-search | **6** | 7-9 | **6×** |
|
|
60
|
+
|
|
61
|
+
迭代轮数和 token 消耗是**指数关系**——第 N 轮要把前 N-1 轮的所有内容重新喂给模型。
|
|
62
|
+
6 轮迭代 ≈ 把上下文喂了 6 遍。
|
|
63
|
+
|
|
64
|
+
## 优化方案(按收益排序)
|
|
65
|
+
|
|
66
|
+
### P0:给每个 skill 写"一次拿全"的聚合脚本(砍 60-80% 调用)
|
|
67
|
+
|
|
68
|
+
现状:AI 用 cat/ls 一个个摸。
|
|
69
|
+
目标:一个脚本一次返回 skill 需要的所有数据。
|
|
70
|
+
|
|
71
|
+
**`/wl-status` 示例**——合并成一个 `status_snapshot.py`:
|
|
72
|
+
```python
|
|
73
|
+
# 一次返回: 身份 + 任务 + 反馈 + 评估 + 索引 + 配置 + 成员
|
|
74
|
+
# 现在: 8次 cat/ls + 5轮迭代
|
|
75
|
+
# 优化后: 1次调用 + 1轮迭代
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
**`/wl-search` 示例**——kg.py 被调5次,应合并成一次:
|
|
79
|
+
```python
|
|
80
|
+
# 现在: kg.py search → kg.py api → kg.py impact → kg.py context → kg.py style (5次)
|
|
81
|
+
# 优化: kg.py all-in-one "考勤" → 一次返回全部维度
|
|
82
|
+
# (context_pack.py 本就是干这个的, 但 skill 没用足)
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
**预期**:迭代从 5-6 轮降到 1-2 轮,调用从 7-10 次降到 1-3 次。
|
|
86
|
+
|
|
87
|
+
### P1:缓存"不变量"(身份/根目录/配置)
|
|
88
|
+
|
|
89
|
+
`repo_root.py`、`cat .developer`、`cat config.yaml` 这些**会话内不变**的数据,
|
|
90
|
+
每次命令都重新查。应缓存到环境变量或临时文件,session 内复用。
|
|
91
|
+
|
|
92
|
+
现状 `/wl-status` 第一步永远是 `repo_root.py`——这是纯浪费。
|
|
93
|
+
|
|
94
|
+
### P2:强制 skill 用 Fast Path(context_pack)
|
|
95
|
+
|
|
96
|
+
AGENTS.md 已写了 Fast Path:
|
|
97
|
+
> `context_pack.py <业务词>` 一次返回 代码+页面+字段+PRD+API+样式
|
|
98
|
+
|
|
99
|
+
但实测 `/wl-search` 没用它,而是连调 5 次 `kg.py`。
|
|
100
|
+
应在 skill 指令里**强制**:搜索类命令第一步必须调 `context_pack.py`,禁止拆开调。
|
|
101
|
+
|
|
102
|
+
### P3:砍掉冗余 TodoWrite
|
|
103
|
+
|
|
104
|
+
实测 TodoWrite 占了 12% 调用。在 search/status 这类只读命令里,
|
|
105
|
+
TodoWrite 纯粹是"给用户看进度",但每次更新都消耗一次工具往返 + 触发新一轮模型思考。
|
|
106
|
+
**建议**:只读命令不用 TodoWrite,或合并到最后一次性输出。
|
|
107
|
+
|
|
108
|
+
### P4:飞轮闭环(持续优化)
|
|
109
|
+
|
|
110
|
+
```
|
|
111
|
+
你跑命令 → transcript 自动记录
|
|
112
|
+
↓
|
|
113
|
+
每周: python transcript_timing.py <session目录> --json
|
|
114
|
+
↓
|
|
115
|
+
对比上周: 迭代轮数↓了没? 调用次数↓了没?
|
|
116
|
+
↓
|
|
117
|
+
没降 → 继续优化对应 skill
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
前 3 环已通(采集→分析→归因)。缺"自动对比上周"脚本,可后续补。
|
|
121
|
+
|
|
122
|
+
## 预期收益
|
|
123
|
+
|
|
124
|
+
| 优化 | 迭代轮数 | 工具调用 | token |
|
|
125
|
+
|------|---------|---------|-------|
|
|
126
|
+
| 现状(/wl-status) | 5 | 10 | 基准 |
|
|
127
|
+
| +P0 聚合脚本 | 2 | 3 | -50% |
|
|
128
|
+
| +P1 缓存不变量 | 2 | 2 | -55% |
|
|
129
|
+
| +P3 砍 TodoWrite | 2 | 1 | -60% |
|
|
130
|
+
| **全做** | **1-2** | **1-2** | **-60%** |
|
|
131
|
+
|
|
132
|
+
> 注:token 收益主要来自减少迭代轮数(少喂几遍上下文),
|
|
133
|
+
> 而非单次调用的大小。
|
|
134
|
+
|
|
135
|
+
## 落地优先级
|
|
136
|
+
|
|
137
|
+
1. **先改 /wl-search 和 /wl-status**(最碎的两个,收益最大)
|
|
138
|
+
2. 每个写一个聚合脚本,skill 指令改成"只调聚合脚本"
|
|
139
|
+
3. 改完用 transcript_timing.py 验证:迭代和调用数应该明显下降
|