@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,261 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
report_snapshot.py — /wl-report 的一次性数据聚合器 (性能优化)
|
|
4
|
+
|
|
5
|
+
问题: wl-report skill 让 AI 逐个 cat/ls/git log 收集数据, 实测产生 5-7 次工具调用、
|
|
6
|
+
多轮思考。本脚本把 report 需要的全部数据源**一次返回**, 把 7 步顺序采集压成 2 步。
|
|
7
|
+
|
|
8
|
+
与 status_snapshot.py 同构 (Fast Path 模式), 只是数据源换成 report 需要的:
|
|
9
|
+
1. .developer — 当前开发者 (报告归属 + 全局过滤键)
|
|
10
|
+
2. task.py list --mine — 我创建/被指派的任务
|
|
11
|
+
3. journal/feedback — 今天的学习/反馈事件
|
|
12
|
+
4. git log --author — 我的提交 (扫 data/code/ 各项目)
|
|
13
|
+
5. drafts/ + specs/prd — 我写的 PRD/草稿
|
|
14
|
+
|
|
15
|
+
输出: JSON, 缺失的字段标 null 并注明原因。AI 拿到后直接写报告, 不用再探查。
|
|
16
|
+
所有数据严格按 .developer 里的开发者名过滤 (report 的隔离铁律)。
|
|
17
|
+
|
|
18
|
+
用法:
|
|
19
|
+
python report_snapshot.py # 当前仓库, 默认今天
|
|
20
|
+
python report_snapshot.py /path/repo # 指定仓库
|
|
21
|
+
python report_snapshot.py --days 7 # 近 7 天 (周报)
|
|
22
|
+
"""
|
|
23
|
+
import json
|
|
24
|
+
import os
|
|
25
|
+
import subprocess
|
|
26
|
+
import sys
|
|
27
|
+
from datetime import datetime, timedelta
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _repo_root():
|
|
31
|
+
"""优先用命令行参数, 否则从环境反推 (.qoder 标记)。"""
|
|
32
|
+
args = [a for a in sys.argv[1:] if not a.startswith("--")]
|
|
33
|
+
if args and os.path.isdir(args[0]):
|
|
34
|
+
return os.path.abspath(args[0])
|
|
35
|
+
d = os.getcwd()
|
|
36
|
+
for _ in range(8):
|
|
37
|
+
if os.path.isdir(os.path.join(d, ".qoder")):
|
|
38
|
+
return d
|
|
39
|
+
d = os.path.dirname(d)
|
|
40
|
+
return os.getcwd()
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _days():
|
|
44
|
+
"""--days N 参数, 默认 1 (今天/日报)。"""
|
|
45
|
+
for i, a in enumerate(sys.argv):
|
|
46
|
+
if a == "--days" and i + 1 < len(sys.argv):
|
|
47
|
+
try:
|
|
48
|
+
return int(sys.argv[i + 1])
|
|
49
|
+
except ValueError:
|
|
50
|
+
pass
|
|
51
|
+
return 1
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _read_text(path, limit=4000):
|
|
55
|
+
"""安全读文本文件, 不存在返回 None。"""
|
|
56
|
+
if not os.path.exists(path):
|
|
57
|
+
return None
|
|
58
|
+
try:
|
|
59
|
+
with open(path, "r", encoding="utf-8") as f:
|
|
60
|
+
return f.read(limit)
|
|
61
|
+
except Exception as e:
|
|
62
|
+
return f"<read_error: {e}>"
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _parse_developer(text):
|
|
66
|
+
"""从 .developer 文本里取 name= 行的值; 无则取首行。"""
|
|
67
|
+
if not text:
|
|
68
|
+
return None
|
|
69
|
+
for line in text.splitlines():
|
|
70
|
+
line = line.strip()
|
|
71
|
+
if line.startswith("name="):
|
|
72
|
+
return line[5:].strip()
|
|
73
|
+
first = text.splitlines()[0].strip() if text.strip() else None
|
|
74
|
+
return first or None
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _run_script(script_rel, repo, args=None, timeout=20):
|
|
78
|
+
"""运行 .qoder/scripts 下的脚本, 返回 (stdout, ok)。"""
|
|
79
|
+
script = os.path.join(repo, script_rel)
|
|
80
|
+
if not os.path.exists(script):
|
|
81
|
+
return None, False
|
|
82
|
+
try:
|
|
83
|
+
cmd = [sys.executable, script] + (args or [])
|
|
84
|
+
r = subprocess.run(
|
|
85
|
+
cmd, cwd=repo, capture_output=True, text=True, timeout=timeout,
|
|
86
|
+
encoding="utf-8", errors="replace",
|
|
87
|
+
)
|
|
88
|
+
return (r.stdout.strip() if r.returncode == 0 else r.stderr.strip()), r.returncode == 0
|
|
89
|
+
except Exception as e:
|
|
90
|
+
return f"<run_error: {e}>", False
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _git_log_for_author(repo, author, days):
|
|
94
|
+
"""扫 data/code/ 下各项目的 git log, 按作者过滤。返回 {project: [commits]}。"""
|
|
95
|
+
code_root = os.path.join(repo, "data", "code")
|
|
96
|
+
if not os.path.isdir(code_root):
|
|
97
|
+
return None
|
|
98
|
+
since = (datetime.now() - timedelta(days=days)).strftime("%Y-%m-%d")
|
|
99
|
+
result = {}
|
|
100
|
+
try:
|
|
101
|
+
for name in sorted(os.listdir(code_root)):
|
|
102
|
+
proj = os.path.join(code_root, name)
|
|
103
|
+
if not os.path.isdir(os.path.join(proj, ".git")):
|
|
104
|
+
continue
|
|
105
|
+
try:
|
|
106
|
+
r = subprocess.run(
|
|
107
|
+
["git", "-C", proj, "log", f"--author={author}",
|
|
108
|
+
f"--since={since}", "--oneline", "-n", "50"],
|
|
109
|
+
capture_output=True, text=True, timeout=8,
|
|
110
|
+
encoding="utf-8", errors="replace",
|
|
111
|
+
)
|
|
112
|
+
out = r.stdout.strip() if r.returncode == 0 else ""
|
|
113
|
+
if out:
|
|
114
|
+
commits = [l for l in out.splitlines() if l.strip()]
|
|
115
|
+
result[name] = commits[:50]
|
|
116
|
+
except Exception:
|
|
117
|
+
continue
|
|
118
|
+
except Exception:
|
|
119
|
+
pass
|
|
120
|
+
return result if result else {}
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _list_prds_by_author(repo, author, days):
|
|
124
|
+
"""收集 workspace/specs/prd/*.md 和 members/*/drafts 里 author==developer 的 PRD。
|
|
125
|
+
frontmatter 里有 author 字段就按它过滤; 草稿目录按开发者归属过滤。"""
|
|
126
|
+
since = datetime.now() - timedelta(days=days + 30) # PRD 按文件宽松一点
|
|
127
|
+
hits = []
|
|
128
|
+
|
|
129
|
+
# 1. 团队归档的 PRD (按 frontmatter author 过滤)
|
|
130
|
+
prd_dir = os.path.join(repo, "workspace", "specs", "prd")
|
|
131
|
+
if os.path.isdir(prd_dir):
|
|
132
|
+
for name in sorted(os.listdir(prd_dir)):
|
|
133
|
+
if not name.endswith(".md"):
|
|
134
|
+
continue
|
|
135
|
+
fp = os.path.join(prd_dir, name)
|
|
136
|
+
try:
|
|
137
|
+
mtime = datetime.fromtimestamp(os.path.getmtime(fp))
|
|
138
|
+
if mtime < since:
|
|
139
|
+
continue
|
|
140
|
+
head = _read_text(fp, limit=600) or ""
|
|
141
|
+
# frontmatter 里找 author:
|
|
142
|
+
is_mine = False
|
|
143
|
+
if "author:" in head.lower() or "author =" in head.lower():
|
|
144
|
+
is_mine = author.lower() in head.lower()
|
|
145
|
+
if is_mine:
|
|
146
|
+
title = name[:-3]
|
|
147
|
+
hits.append({"file": f"workspace/specs/prd/{name}",
|
|
148
|
+
"title": title, "mtime": mtime.strftime("%Y-%m-%d")})
|
|
149
|
+
except Exception:
|
|
150
|
+
continue
|
|
151
|
+
|
|
152
|
+
# 2. 个人草稿 (按目录归属天然隔离, 整个目录都是该开发者的)
|
|
153
|
+
dev_drafts = os.path.join(repo, "workspace", "members", author, "drafts")
|
|
154
|
+
if os.path.isdir(dev_drafts):
|
|
155
|
+
for root, _dirs, files in os.walk(dev_drafts):
|
|
156
|
+
for fn in files:
|
|
157
|
+
if fn.endswith(".md"):
|
|
158
|
+
fp = os.path.join(root, fn)
|
|
159
|
+
try:
|
|
160
|
+
mtime = datetime.fromtimestamp(os.path.getmtime(fp))
|
|
161
|
+
if mtime >= since:
|
|
162
|
+
rel = os.path.relpath(fp, repo).replace("\\", "/")
|
|
163
|
+
hits.append({"file": rel, "title": fn[:-3],
|
|
164
|
+
"mtime": mtime.strftime("%Y-%m-%d")})
|
|
165
|
+
except Exception:
|
|
166
|
+
continue
|
|
167
|
+
return hits[:50] if hits else []
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def _journal_today(repo, author, days):
|
|
171
|
+
"""读该开发者 journal 目录近 days 天的 feedback/learning 事件。"""
|
|
172
|
+
journal = os.path.join(repo, "workspace", "members", author, "journal")
|
|
173
|
+
if not os.path.isdir(journal):
|
|
174
|
+
return None
|
|
175
|
+
since = datetime.now() - timedelta(days=days)
|
|
176
|
+
events = []
|
|
177
|
+
for fn in sorted(os.listdir(journal)):
|
|
178
|
+
fp = os.path.join(journal, fn)
|
|
179
|
+
if not os.path.isfile(fp):
|
|
180
|
+
continue
|
|
181
|
+
try:
|
|
182
|
+
mtime = datetime.fromtimestamp(os.path.getmtime(fp))
|
|
183
|
+
if mtime < since:
|
|
184
|
+
continue
|
|
185
|
+
if fn.endswith(".jsonl"):
|
|
186
|
+
# feedback.jsonl: 读近几天的事件行
|
|
187
|
+
with open(fp, encoding="utf-8") as f:
|
|
188
|
+
for line in f:
|
|
189
|
+
line = line.strip()
|
|
190
|
+
if not line:
|
|
191
|
+
continue
|
|
192
|
+
try:
|
|
193
|
+
ev = json.loads(line)
|
|
194
|
+
# 事件带时间戳则按时间过滤, 否则按文件 mtime 已过滤
|
|
195
|
+
events.append(ev)
|
|
196
|
+
except json.JSONDecodeError:
|
|
197
|
+
continue
|
|
198
|
+
elif fn.endswith((".yaml", ".yml")):
|
|
199
|
+
txt = _read_text(fp, limit=2000)
|
|
200
|
+
if txt:
|
|
201
|
+
events.append({"_file": fn, "_summary": txt[:500]})
|
|
202
|
+
except Exception:
|
|
203
|
+
continue
|
|
204
|
+
return events[:100] if events else []
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def snapshot(repo=None, days=None):
|
|
208
|
+
"""收集 wl-report 需要的全部数据, 返回 dict。"""
|
|
209
|
+
repo = repo or _repo_root()
|
|
210
|
+
days = days or _days()
|
|
211
|
+
qd = os.path.join(repo, ".qoder")
|
|
212
|
+
|
|
213
|
+
data = {"_repo": repo, "_ts": datetime.now().isoformat(timespec="seconds"),
|
|
214
|
+
"_days": days}
|
|
215
|
+
|
|
216
|
+
# 1. 开发者身份 (report 的全局过滤键)
|
|
217
|
+
dev_text = _read_text(os.path.join(qd, ".developer"))
|
|
218
|
+
developer = _parse_developer(dev_text)
|
|
219
|
+
data["developer"] = developer or "(未设置)"
|
|
220
|
+
data["developer_raw"] = dev_text
|
|
221
|
+
|
|
222
|
+
if not developer or developer == "(未设置)":
|
|
223
|
+
data["_note"] = "未设置 .developer, 无法按开发者过滤。先 /wl-init。"
|
|
224
|
+
return data
|
|
225
|
+
|
|
226
|
+
# 2-5 四个独立数据源并行采集 (task list 最慢~440ms, 并行后总耗时≈最慢的那个)
|
|
227
|
+
# task list / git log / journal / prds 互不依赖 → 用线程池同时跑, 砍掉串行等待。
|
|
228
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
229
|
+
with ThreadPoolExecutor(max_workers=4) as pool:
|
|
230
|
+
f_tasks = pool.submit(_run_script, ".qoder/scripts/domain/task/task.py", repo,
|
|
231
|
+
["list", "--mine"])
|
|
232
|
+
f_journal = pool.submit(_journal_today, repo, developer, days)
|
|
233
|
+
f_commits = pool.submit(_git_log_for_author, repo, developer, days)
|
|
234
|
+
f_prds = pool.submit(_list_prds_by_author, repo, developer, days)
|
|
235
|
+
|
|
236
|
+
task_out, task_ok = f_tasks.result()
|
|
237
|
+
data["my_tasks"] = task_out if task_ok else None
|
|
238
|
+
data["my_tasks_error"] = None if task_ok else task_out
|
|
239
|
+
data["journal"] = f_journal.result()
|
|
240
|
+
data["commits"] = f_commits.result()
|
|
241
|
+
data["my_prds"] = f_prds.result()
|
|
242
|
+
|
|
243
|
+
# 6. 报告日期范围 (供 AI 套模板)
|
|
244
|
+
today = datetime.now().strftime("%Y-%m-%d")
|
|
245
|
+
start = (datetime.now() - timedelta(days=days - 1)).strftime("%Y-%m-%d")
|
|
246
|
+
data["date_range"] = f"{start} ~ {today}" if days > 1 else today
|
|
247
|
+
|
|
248
|
+
return data
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def main():
|
|
252
|
+
repo = None
|
|
253
|
+
args = [a for a in sys.argv[1:] if not a.startswith("--")]
|
|
254
|
+
if args:
|
|
255
|
+
repo = args[0]
|
|
256
|
+
data = snapshot(repo)
|
|
257
|
+
print(json.dumps(data, ensure_ascii=False, indent=2, default=str))
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
if __name__ == "__main__":
|
|
261
|
+
main()
|
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
# v3.0 路径自举: 引导到 common/bootstrap, 统一 sys.path 逻辑
|
|
2
2
|
import os as _o, sys as _s
|
|
3
|
-
|
|
3
|
+
_f = _o.path.abspath(__file__)
|
|
4
|
+
for _ in range(10):
|
|
5
|
+
_f = _o.path.dirname(_f)
|
|
6
|
+
_cp = _o.path.join(_f, 'foundation')
|
|
7
|
+
if _o.path.isfile(_o.path.join(_cp, 'bootstrap.py')):
|
|
8
|
+
break
|
|
4
9
|
if _cp not in _s.path: _s.path.insert(0, _cp)
|
|
5
10
|
from bootstrap import setup; setup()
|
|
11
|
+
from foundation.core.paths import get_repo_root
|
|
6
12
|
|
|
7
13
|
# role.py - 角色解析 (v3.0: 已并入 common/identity.py, 本文件为兼容转发层)
|
|
8
14
|
#
|
|
@@ -11,15 +17,15 @@ from bootstrap import setup; setup()
|
|
|
11
17
|
# 1. CLI 兼容: skill 脚本里 `ROLE=$(python role.py)` 仍能输出角色名
|
|
12
18
|
# 2. import 兼容: `from role import get_role` 仍可用 (转发到 identity)
|
|
13
19
|
#
|
|
14
|
-
# ⚠️ 新代码请直接用: from
|
|
20
|
+
# ⚠️ 新代码请直接用: from foundation.identity.identity import get_role / from foundation.identity.guard import ...
|
|
15
21
|
# 本文件未来随 M2 引擎重构可能移除。
|
|
16
22
|
import os
|
|
17
23
|
import sys
|
|
18
24
|
|
|
19
25
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
20
|
-
from
|
|
26
|
+
from foundation.identity.identity import get_role, get_permissions, get_capabilities, can_produce, can_consume # noqa: E402,F401
|
|
21
27
|
|
|
22
|
-
BASE =
|
|
28
|
+
BASE = get_repo_root()
|
|
23
29
|
|
|
24
30
|
|
|
25
31
|
if __name__ == '__main__':
|
|
@@ -2,7 +2,12 @@
|
|
|
2
2
|
# -*- coding: utf-8 -*-
|
|
3
3
|
# v3.0 路径自举: 引导到 common/bootstrap, 统一 sys.path 逻辑
|
|
4
4
|
import os as _o, sys as _s
|
|
5
|
-
|
|
5
|
+
_f = _o.path.abspath(__file__)
|
|
6
|
+
for _ in range(10):
|
|
7
|
+
_f = _o.path.dirname(_f)
|
|
8
|
+
_cp = _o.path.join(_f, 'foundation')
|
|
9
|
+
if _o.path.isfile(_o.path.join(_cp, 'bootstrap.py')):
|
|
10
|
+
break
|
|
6
11
|
if _cp not in _s.path: _s.path.insert(0, _cp)
|
|
7
12
|
from bootstrap import setup; setup()
|
|
8
13
|
|
|
@@ -34,9 +39,9 @@ except (AttributeError, TypeError, OSError):
|
|
|
34
39
|
|
|
35
40
|
THIS_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
36
41
|
sys.path.insert(0, THIS_DIR)
|
|
37
|
-
from
|
|
38
|
-
from
|
|
39
|
-
from
|
|
42
|
+
from foundation.core.paths import get_repo_root, get_developer, get_tasks_dir, MEMBERS_DIR, EVAL_HISTORY_PATH
|
|
43
|
+
from foundation.data.task_utils import load_task_json
|
|
44
|
+
from foundation.io.atomicio import safe_read_json
|
|
40
45
|
|
|
41
46
|
BASE = get_repo_root()
|
|
42
47
|
INDEX_DIR = BASE / 'data' / 'index'
|
|
@@ -230,6 +235,100 @@ def _count_all_feedback():
|
|
|
230
235
|
return total
|
|
231
236
|
|
|
232
237
|
|
|
238
|
+
def _compute_adoption_score():
|
|
239
|
+
"""工作流采纳度 (0-5), 供 compute_health 的 adoption 维度用。
|
|
240
|
+
|
|
241
|
+
与 usability_score.py 的 adoption_score 同源逻辑 (避免 import 验证脚本产生环依赖, 内联):
|
|
242
|
+
取能算的自动采纳指标 (U2一次过审/U3返工/U4违规/U6活跃), 按达标率映射 0-5。
|
|
243
|
+
无数据 → 中性 2.5。
|
|
244
|
+
"""
|
|
245
|
+
# U2 一次过审率 + U3 返工 (从 eval-history)
|
|
246
|
+
records = []
|
|
247
|
+
if EVAL_HISTORY_PATH.is_file():
|
|
248
|
+
try:
|
|
249
|
+
with open(EVAL_HISTORY_PATH, encoding='utf-8') as f:
|
|
250
|
+
for line in f:
|
|
251
|
+
line = line.strip()
|
|
252
|
+
if line:
|
|
253
|
+
records.append(json.loads(line))
|
|
254
|
+
except Exception:
|
|
255
|
+
pass
|
|
256
|
+
|
|
257
|
+
scored = [] # 每项 1.0(绿)/0.5(黄)/0(红)
|
|
258
|
+
|
|
259
|
+
# U2: 首轮过审率
|
|
260
|
+
by_prd = {}
|
|
261
|
+
for r in records:
|
|
262
|
+
prd = r.get('prd')
|
|
263
|
+
if not prd:
|
|
264
|
+
continue
|
|
265
|
+
t = r.get('time', '')
|
|
266
|
+
if prd not in by_prd or (t and t < by_prd[prd][0]):
|
|
267
|
+
by_prd[prd] = (t, r)
|
|
268
|
+
if by_prd:
|
|
269
|
+
first_pass = sum(1 for (t, row) in by_prd.values() if row.get('passed'))
|
|
270
|
+
u2_rate = 100.0 * first_pass / len(by_prd)
|
|
271
|
+
scored.append(1.0 if u2_rate >= 60 else (0.5 if u2_rate >= 30 else 0.0))
|
|
272
|
+
|
|
273
|
+
# U3: 平均返工轮数 (越低越好)
|
|
274
|
+
if by_prd:
|
|
275
|
+
per_prd = {}
|
|
276
|
+
for r in records:
|
|
277
|
+
if r.get('prd'):
|
|
278
|
+
per_prd.setdefault(r['prd'], []).append(r)
|
|
279
|
+
total_evals = sum(len(v) for v in per_prd.values())
|
|
280
|
+
avg_rework = total_evals / len(per_prd) - 1.0
|
|
281
|
+
avg_rework = max(avg_rework, 0.0)
|
|
282
|
+
scored.append(1.0 if avg_rework <= 1.0 else (0.5 if avg_rework <= 2.0 else 0.0))
|
|
283
|
+
|
|
284
|
+
# U4 规则违反率 + U6 活跃 (从 feedback)
|
|
285
|
+
violations = 0
|
|
286
|
+
prds_gen = 0
|
|
287
|
+
active_devs = set()
|
|
288
|
+
now = datetime.now()
|
|
289
|
+
week_ago = now - timedelta(days=7)
|
|
290
|
+
if MEMBERS_DIR.is_dir():
|
|
291
|
+
for dev_dir in MEMBERS_DIR.iterdir():
|
|
292
|
+
if not dev_dir.is_dir():
|
|
293
|
+
continue
|
|
294
|
+
fb = dev_dir / 'journal' / 'feedback.jsonl'
|
|
295
|
+
if not fb.is_file():
|
|
296
|
+
continue
|
|
297
|
+
try:
|
|
298
|
+
with open(fb, encoding='utf-8') as f:
|
|
299
|
+
for line in f:
|
|
300
|
+
line = line.strip()
|
|
301
|
+
if not line:
|
|
302
|
+
continue
|
|
303
|
+
e = json.loads(line)
|
|
304
|
+
if e.get('event') == 'rule_violation':
|
|
305
|
+
violations += 1
|
|
306
|
+
if e.get('event') == 'prd_accepted':
|
|
307
|
+
prds_gen += 1
|
|
308
|
+
# U6: 近7天有事件的 dev
|
|
309
|
+
ts = e.get('ts', '')
|
|
310
|
+
try:
|
|
311
|
+
t = datetime.fromisoformat(str(ts).split('.')[0])
|
|
312
|
+
if t >= week_ago:
|
|
313
|
+
active_devs.add(e.get('dev'))
|
|
314
|
+
except (ValueError, TypeError):
|
|
315
|
+
pass
|
|
316
|
+
except Exception:
|
|
317
|
+
continue
|
|
318
|
+
# U4
|
|
319
|
+
if prds_gen:
|
|
320
|
+
vrate = 100.0 * violations / prds_gen
|
|
321
|
+
scored.append(1.0 if vrate <= 10 else (0.5 if vrate <= 25 else 0.0))
|
|
322
|
+
# U6
|
|
323
|
+
n_devs = len(active_devs)
|
|
324
|
+
scored.append(1.0 if n_devs >= 2 else (0.5 if n_devs == 1 else 0.0))
|
|
325
|
+
|
|
326
|
+
if not scored:
|
|
327
|
+
return 2.5 # 无数据, 中性
|
|
328
|
+
# 达标率 → 0-5 (1.0全绿=5, 全红=0)
|
|
329
|
+
return round(5.0 * sum(scored) / len(scored), 2)
|
|
330
|
+
|
|
331
|
+
|
|
233
332
|
# ============================================================
|
|
234
333
|
# 健康分 (综合)
|
|
235
334
|
# ============================================================
|
|
@@ -243,7 +342,7 @@ def compute_health(tasks):
|
|
|
243
342
|
|
|
244
343
|
# 1. EVA 合格率 (25%)
|
|
245
344
|
# eval-history 是 jsonl (每行一个 JSON), 不能用 safe_read_json
|
|
246
|
-
eval_path =
|
|
345
|
+
eval_path = EVAL_HISTORY_PATH
|
|
247
346
|
if eval_path.is_file():
|
|
248
347
|
records = []
|
|
249
348
|
try:
|
|
@@ -301,17 +400,21 @@ def compute_health(tasks):
|
|
|
301
400
|
else:
|
|
302
401
|
scores['pipeline'] = 2.0
|
|
303
402
|
|
|
304
|
-
# 6. 学习 (
|
|
403
|
+
# 6. 学习 (5%)
|
|
305
404
|
# 修 bug: 旧代码读 data/learning/feedback.jsonl (旧路径, 已废弃),
|
|
306
405
|
# 但 learn.py 实际写 workspace/members/{dev}/journal/feedback.jsonl。
|
|
307
406
|
# 迁移了写路径没迁移读路径 → 学习度恒为 2.0。改为扫全部 dev 的 journal。
|
|
308
407
|
fb_count = _count_all_feedback()
|
|
309
408
|
scores['learning'] = min(5.0, 2.0 + fb_count * 0.1) if fb_count else 2.0
|
|
310
409
|
|
|
410
|
+
# 7. 工作流采纳 (10%) — 与 EVA/学习并列, 回答"用不用得起来"
|
|
411
|
+
# 复用 usability_score.py 的采纳分逻辑 (不 import 验证脚本, 内联等价计算避免环依赖)
|
|
412
|
+
scores['adoption'] = _compute_adoption_score()
|
|
413
|
+
|
|
311
414
|
# 加权
|
|
312
415
|
weights = {
|
|
313
416
|
'eva': 0.25, 'index': 0.20, 'on_time': 0.20,
|
|
314
|
-
'sync': 0.15, 'pipeline': 0.
|
|
417
|
+
'sync': 0.15, 'pipeline': 0.05, 'learning': 0.05, 'adoption': 0.10,
|
|
315
418
|
}
|
|
316
419
|
total = sum(scores[k] * weights[k] for k in weights)
|
|
317
420
|
return {
|
|
@@ -356,7 +459,7 @@ def _analyze_eva_rounds():
|
|
|
356
459
|
返回 {samples, rework_count, avg_rounds}。
|
|
357
460
|
rework = 连续 passed=false 后出现 passed=true 的序列。
|
|
358
461
|
"""
|
|
359
|
-
eval_path =
|
|
462
|
+
eval_path = EVAL_HISTORY_PATH
|
|
360
463
|
if not eval_path.is_file():
|
|
361
464
|
return {'samples': 0, 'rework_count': 0, 'avg_rounds': 0}
|
|
362
465
|
records = []
|
|
@@ -466,7 +569,7 @@ def compute_role_guide():
|
|
|
466
569
|
role = None
|
|
467
570
|
try:
|
|
468
571
|
sys.path.insert(0, str(BASE / '.qoder' / 'scripts'))
|
|
469
|
-
from
|
|
572
|
+
from foundation.identity.identity import get_role
|
|
470
573
|
role = get_role()
|
|
471
574
|
except Exception:
|
|
472
575
|
pass
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
status_snapshot.py — /wl-status 的一次性数据聚合器 (性能优化)
|
|
4
|
+
|
|
5
|
+
问题: wl-status skill 让 AI 逐个 cat/ls 收集数据, 实测产生 5 轮迭代、10+ 次工具调用。
|
|
6
|
+
本脚本把 status 需要的全部数据源**一次返回**, 把 5 轮迭代压成 1 轮。
|
|
7
|
+
|
|
8
|
+
聚合的数据源 (来自 wl-status SKILL.md):
|
|
9
|
+
1. .developer — 当前开发者
|
|
10
|
+
2. .index-meta.json — 图谱新鲜度
|
|
11
|
+
3. eval-history.jsonl — EVA 评估历史 (健康度趋势)
|
|
12
|
+
4. feedback.jsonl — 学习反馈记录
|
|
13
|
+
5. config.yaml — 项目配置
|
|
14
|
+
6. task.py list — 任务列表
|
|
15
|
+
7. team_sync.py status — 团队同步状态
|
|
16
|
+
8. tasks 目录 — 任务文件清单
|
|
17
|
+
9. learning/patterns — 学习模式
|
|
18
|
+
|
|
19
|
+
输出: JSON, 缺失的字段标 null 并注明原因。AI 拿到后直接算健康度, 不用再探查。
|
|
20
|
+
|
|
21
|
+
用法:
|
|
22
|
+
python status_snapshot.py # 当前仓库
|
|
23
|
+
python status_snapshot.py /path/to/repo
|
|
24
|
+
"""
|
|
25
|
+
import json
|
|
26
|
+
import os
|
|
27
|
+
import subprocess
|
|
28
|
+
import sys
|
|
29
|
+
from datetime import datetime
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _repo_root():
|
|
33
|
+
"""优先用命令行参数, 否则从环境反推。"""
|
|
34
|
+
if len(sys.argv) > 1 and os.path.isdir(sys.argv[1]):
|
|
35
|
+
return os.path.abspath(sys.argv[1])
|
|
36
|
+
# 同 task.py 的逻辑: 找 .qoder 标记
|
|
37
|
+
d = os.getcwd()
|
|
38
|
+
for _ in range(8):
|
|
39
|
+
if os.path.isdir(os.path.join(d, ".qoder")):
|
|
40
|
+
return d
|
|
41
|
+
d = os.path.dirname(d)
|
|
42
|
+
return os.getcwd()
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _read_text(path, limit=20000):
|
|
46
|
+
"""安全读文本文件, 不存在返回 None。"""
|
|
47
|
+
if not os.path.exists(path):
|
|
48
|
+
return None
|
|
49
|
+
try:
|
|
50
|
+
with open(path, "r", encoding="utf-8") as f:
|
|
51
|
+
return f.read(limit)
|
|
52
|
+
except Exception as e:
|
|
53
|
+
return f"<read_error: {e}>"
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _read_json(path):
|
|
57
|
+
if not os.path.exists(path):
|
|
58
|
+
return None
|
|
59
|
+
try:
|
|
60
|
+
with open(path, "r", encoding="utf-8") as f:
|
|
61
|
+
return json.load(f)
|
|
62
|
+
except Exception as e:
|
|
63
|
+
return {"_error": str(e)}
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _read_jsonl_tail(path, n=10):
|
|
67
|
+
"""读 jsonl 最后 n 行 (评估/反馈历史, 只关心最近的)。"""
|
|
68
|
+
if not os.path.exists(path):
|
|
69
|
+
return None
|
|
70
|
+
try:
|
|
71
|
+
with open(path, "r", encoding="utf-8") as f:
|
|
72
|
+
lines = [l.strip() for l in f if l.strip()]
|
|
73
|
+
out = []
|
|
74
|
+
for l in lines[-n:]:
|
|
75
|
+
try:
|
|
76
|
+
out.append(json.loads(l))
|
|
77
|
+
except json.JSONDecodeError:
|
|
78
|
+
continue
|
|
79
|
+
return out
|
|
80
|
+
except Exception:
|
|
81
|
+
return None
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _run_script(script_rel, repo, args=None, timeout=20):
|
|
85
|
+
"""运行 .qoder/scripts 下的脚本, 返回 (stdout, ok)。"""
|
|
86
|
+
script = os.path.join(repo, script_rel)
|
|
87
|
+
if not os.path.exists(script):
|
|
88
|
+
return None, False
|
|
89
|
+
try:
|
|
90
|
+
cmd = [sys.executable, script] + (args or [])
|
|
91
|
+
r = subprocess.run(
|
|
92
|
+
cmd, cwd=repo, capture_output=True, text=True, timeout=timeout,
|
|
93
|
+
encoding="utf-8", errors="replace",
|
|
94
|
+
)
|
|
95
|
+
return r.stdout.strip() if r.returncode == 0 else r.stderr.strip(), r.returncode == 0
|
|
96
|
+
except Exception as e:
|
|
97
|
+
return f"<run_error: {e}>", False
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _list_dir(path):
|
|
101
|
+
if not os.path.isdir(path):
|
|
102
|
+
return None
|
|
103
|
+
try:
|
|
104
|
+
items = sorted(os.listdir(path))
|
|
105
|
+
return items[:50] # 限 50 项防爆
|
|
106
|
+
except Exception:
|
|
107
|
+
return None
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _index_freshness(meta):
|
|
111
|
+
"""从 .index-meta.json 算新鲜度(距今天数)。"""
|
|
112
|
+
if not isinstance(meta, dict):
|
|
113
|
+
return None
|
|
114
|
+
last = meta.get("last_sync") or meta.get("last_build") or meta.get("updated")
|
|
115
|
+
if not last:
|
|
116
|
+
return None
|
|
117
|
+
try:
|
|
118
|
+
# 兼容多种时间格式
|
|
119
|
+
ts = str(last).replace("Z", "+00:00")
|
|
120
|
+
dt = datetime.fromisoformat(ts)
|
|
121
|
+
if dt.tzinfo:
|
|
122
|
+
dt = dt.replace(tzinfo=None)
|
|
123
|
+
return (datetime.now() - dt).days
|
|
124
|
+
except Exception:
|
|
125
|
+
return None
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def snapshot(repo=None):
|
|
129
|
+
"""收集 wl-status 需要的全部数据, 返回 dict。"""
|
|
130
|
+
repo = repo or _repo_root()
|
|
131
|
+
qd = os.path.join(repo, ".qoder")
|
|
132
|
+
|
|
133
|
+
data = {"_repo": repo, "_ts": datetime.now().isoformat(timespec="seconds")}
|
|
134
|
+
|
|
135
|
+
# 1. 开发者身份
|
|
136
|
+
data["developer"] = _read_text(os.path.join(qd, ".developer")) or "(未设置)"
|
|
137
|
+
|
|
138
|
+
# 2. 图谱索引新鲜度
|
|
139
|
+
meta = _read_json(os.path.join(repo, "data", "index", ".index-meta.json"))
|
|
140
|
+
data["index_meta"] = meta
|
|
141
|
+
data["index_age_days"] = _index_freshness(meta)
|
|
142
|
+
|
|
143
|
+
# 3. EVA 评估历史 (健康度趋势)
|
|
144
|
+
data["eval_history"] = _read_jsonl_tail(
|
|
145
|
+
os.path.join(qd, "learning", "eval-history.jsonl"), n=10)
|
|
146
|
+
|
|
147
|
+
# 4. 学习反馈
|
|
148
|
+
data["feedback_count"] = None
|
|
149
|
+
fb = os.path.join(qd, "learning", "feedback.jsonl")
|
|
150
|
+
if os.path.exists(fb):
|
|
151
|
+
try:
|
|
152
|
+
with open(fb, encoding="utf-8") as f:
|
|
153
|
+
data["feedback_count"] = sum(1 for _ in f)
|
|
154
|
+
except Exception:
|
|
155
|
+
pass
|
|
156
|
+
|
|
157
|
+
# 5. 项目配置 (只取关键摘要, 防过大)
|
|
158
|
+
cfg = _read_text(os.path.join(qd, "config.yaml"), limit=4000)
|
|
159
|
+
data["config_summary"] = cfg[:4000] if cfg else None
|
|
160
|
+
|
|
161
|
+
# 6. 任务列表 (调 task.py list)
|
|
162
|
+
task_out, task_ok = _run_script(".qoder/scripts/domain/task/task.py", repo, args=["list"])
|
|
163
|
+
data["task_list"] = task_out if task_ok else None
|
|
164
|
+
data["task_list_error"] = None if task_ok else task_out
|
|
165
|
+
|
|
166
|
+
# 7. 团队同步状态 (调 team_sync.py status)
|
|
167
|
+
sync_out, sync_ok = _run_script(".qoder/scripts/domain/task/team_sync.py", repo,
|
|
168
|
+
args=["status"], timeout=15)
|
|
169
|
+
data["team_sync"] = sync_out if sync_ok else None
|
|
170
|
+
data["team_sync_error"] = None if sync_ok else sync_out
|
|
171
|
+
|
|
172
|
+
# 8. 任务目录结构
|
|
173
|
+
data["tasks_dir"] = _list_dir(os.path.join(qd, "workspace", "tasks"))
|
|
174
|
+
|
|
175
|
+
# 9. 学习模式
|
|
176
|
+
data["patterns"] = _list_dir(os.path.join(qd, "learning", "patterns"))
|
|
177
|
+
|
|
178
|
+
# 10. 成员目录 (团队规模)
|
|
179
|
+
data["members"] = _list_dir(os.path.join(qd, "workspace", "members"))
|
|
180
|
+
|
|
181
|
+
return data
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def main():
|
|
185
|
+
repo = sys.argv[1] if len(sys.argv) > 1 else None
|
|
186
|
+
data = snapshot(repo)
|
|
187
|
+
print(json.dumps(data, ensure_ascii=False, indent=2, default=str))
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
if __name__ == "__main__":
|
|
191
|
+
main()
|
|
File without changes
|
|
@@ -2,9 +2,15 @@
|
|
|
2
2
|
# -*- coding: utf-8 -*-
|
|
3
3
|
# v3.0 路径自举: 引导到 common/bootstrap, 统一 sys.path 逻辑
|
|
4
4
|
import os as _o, sys as _s
|
|
5
|
-
|
|
5
|
+
_f = _o.path.abspath(__file__)
|
|
6
|
+
for _ in range(10):
|
|
7
|
+
_f = _o.path.dirname(_f)
|
|
8
|
+
_cp = _o.path.join(_f, 'foundation')
|
|
9
|
+
if _o.path.isfile(_o.path.join(_cp, 'bootstrap.py')):
|
|
10
|
+
break
|
|
6
11
|
if _cp not in _s.path: _s.path.insert(0, _cp)
|
|
7
12
|
from bootstrap import setup; setup()
|
|
13
|
+
from foundation.core.paths import get_repo_root
|
|
8
14
|
|
|
9
15
|
"""
|
|
10
16
|
archive_prd.py - PRD 按时间归档到个人工作区
|
|
@@ -41,7 +47,7 @@ except (AttributeError, TypeError, OSError, IOError):
|
|
|
41
47
|
|
|
42
48
|
# 让 common/ 可被 import(直接当脚本跑时)
|
|
43
49
|
_THIS_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
44
|
-
_COMMON = os.path.join(_THIS_DIR, "
|
|
50
|
+
_COMMON = os.path.join(_THIS_DIR, "foundation")
|
|
45
51
|
if _COMMON not in sys.path:
|
|
46
52
|
sys.path.insert(0, _COMMON)
|
|
47
53
|
|