@hupan56/wlkj 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/bin/cli.js +213 -0
  2. package/package.json +11 -0
  3. package/templates/cli.js +198 -0
  4. package/templates/qoder/commands/wl-code.md +43 -0
  5. package/templates/qoder/commands/wl-commit.md +30 -0
  6. package/templates/qoder/commands/wl-init.md +80 -0
  7. package/templates/qoder/commands/wl-insight.md +51 -0
  8. package/templates/qoder/commands/wl-prd.md +199 -0
  9. package/templates/qoder/commands/wl-report.md +166 -0
  10. package/templates/qoder/commands/wl-search.md +52 -0
  11. package/templates/qoder/commands/wl-spec.md +18 -0
  12. package/templates/qoder/commands/wl-status.md +51 -0
  13. package/templates/qoder/commands/wl-task.md +71 -0
  14. package/templates/qoder/commands/wl-test.md +42 -0
  15. package/templates/qoder/config.toml +5 -0
  16. package/templates/qoder/config.yaml +141 -0
  17. package/templates/qoder/hooks/inject-workflow-state.py +117 -0
  18. package/templates/qoder/hooks/session-start.py +204 -0
  19. package/templates/qoder/rules/wl-pipeline.md +105 -0
  20. package/templates/qoder/scripts/add_session.py +245 -0
  21. package/templates/qoder/scripts/benchmark.py +209 -0
  22. package/templates/qoder/scripts/build_style_index.py +268 -0
  23. package/templates/qoder/scripts/code_index.py +41 -0
  24. package/templates/qoder/scripts/collect_prds.py +31 -0
  25. package/templates/qoder/scripts/common/__init__.py +0 -0
  26. package/templates/qoder/scripts/common/active_task.py +230 -0
  27. package/templates/qoder/scripts/common/atomicio.py +172 -0
  28. package/templates/qoder/scripts/common/developer.py +161 -0
  29. package/templates/qoder/scripts/common/eval_api.py +144 -0
  30. package/templates/qoder/scripts/common/feishu.py +278 -0
  31. package/templates/qoder/scripts/common/filelock.py +211 -0
  32. package/templates/qoder/scripts/common/identity.py +285 -0
  33. package/templates/qoder/scripts/common/mentions.py +134 -0
  34. package/templates/qoder/scripts/common/paths.py +311 -0
  35. package/templates/qoder/scripts/common/reqid.py +218 -0
  36. package/templates/qoder/scripts/common/search_engine.py +205 -0
  37. package/templates/qoder/scripts/common/task_utils.py +342 -0
  38. package/templates/qoder/scripts/common/terms.py +234 -0
  39. package/templates/qoder/scripts/common/utf8.py +38 -0
  40. package/templates/qoder/scripts/context_pack.py +196 -0
  41. package/templates/qoder/scripts/eval_prd.py +225 -0
  42. package/templates/qoder/scripts/export.py +487 -0
  43. package/templates/qoder/scripts/git_sync.py +1087 -0
  44. package/templates/qoder/scripts/handoff.py +22 -0
  45. package/templates/qoder/scripts/init_developer.py +76 -0
  46. package/templates/qoder/scripts/init_doctor.py +527 -0
  47. package/templates/qoder/scripts/install_qoderwork.py +339 -0
  48. package/templates/qoder/scripts/learn.py +67 -0
  49. package/templates/qoder/scripts/notify.py +5 -0
  50. package/templates/qoder/scripts/parse_prds.py +33 -0
  51. package/templates/qoder/scripts/report.py +281 -0
  52. package/templates/qoder/scripts/role.py +39 -0
  53. package/templates/qoder/scripts/run_weekly_update.bat +17 -0
  54. package/templates/qoder/scripts/run_weekly_update.sh +20 -0
  55. package/templates/qoder/scripts/search_index.py +352 -0
  56. package/templates/qoder/scripts/setup.py +453 -0
  57. package/templates/qoder/scripts/setup_weekly_cron.bat +22 -0
  58. package/templates/qoder/scripts/setup_weekly_cron.sh +19 -0
  59. package/templates/qoder/scripts/status.py +389 -0
  60. package/templates/qoder/scripts/syncgate.py +330 -0
  61. package/templates/qoder/scripts/task.py +954 -0
  62. package/templates/qoder/scripts/team.py +29 -0
  63. package/templates/qoder/scripts/team_sync.py +419 -0
  64. package/templates/qoder/scripts/workspace_init.py +102 -0
  65. package/templates/qoder/settings.json +53 -0
  66. package/templates/qoder/skills/design-review/SKILL.md +25 -0
  67. package/templates/qoder/skills/prd-generator/SKILL.md +180 -0
  68. package/templates/qoder/skills/prd-review/SKILL.md +36 -0
  69. package/templates/qoder/skills/prototype-generator/SKILL.md +141 -0
  70. package/templates/qoder/skills/spec-coder/SKILL.md +69 -0
  71. package/templates/qoder/skills/spec-generator/SKILL.md +67 -0
  72. package/templates/qoder/skills/test-generator/SKILL.md +72 -0
  73. package/templates/qoder/skills/wl-commit/SKILL.md +76 -0
  74. package/templates/qoder/skills/wl-init/SKILL.md +67 -0
  75. package/templates/qoder/skills/wl-insight/SKILL.md +81 -0
  76. package/templates/qoder/skills/wl-report/SKILL.md +87 -0
  77. package/templates/qoder/skills/wl-search/SKILL.md +75 -0
  78. package/templates/qoder/skills/wl-status/SKILL.md +61 -0
  79. package/templates/qoder/skills/wl-task/SKILL.md +58 -0
  80. package/templates/qoder/templates/prd-full-template.md +103 -0
  81. package/templates/qoder/templates/prd-quick-template.md +69 -0
  82. package/templates/qoder/templates/prototype-app.html +344 -0
  83. package/templates/qoder/templates/prototype-web.html +310 -0
  84. package/templates/root/AGENTS.md +182 -0
  85. package/templates/root/README-pipeline.md +56 -0
  86. package/templates/root/ROLES.md +85 -0
  87. package/templates/root//346/226/260/346/211/213/346/214/207/345/215/227.md +186 -0
@@ -0,0 +1,234 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Unified business term mappings - SINGLE SOURCE OF TRUTH.
4
+
5
+ Used by BOTH index builders (git_sync.py, parse_prds.py) and
6
+ index consumers (search_index.py). Do NOT define local copies
7
+ of these maps in other scripts - import from here.
8
+ """
9
+
10
+ # Chinese term -> space-separated English search words.
11
+ # Used for keyword search expansion.
12
+ # 性能优化 A6: 从 40 词扩展到 ~120 词, 覆盖库存/采购/供应商/客户/财务/工单等通用业务域
13
+ CN_MAP = {
14
+ # 考勤/人事
15
+ '考勤': 'attendance clock', '打卡': 'clock', '签到': 'sign',
16
+ '薪资': 'salary', '工资': 'salary', '请假': 'leave', '休假': 'leave',
17
+ '加班': 'overtime', '员工': 'employee staff', '用户': 'user',
18
+ '组织': 'org department', '部门': 'department', '岗位': 'position role',
19
+ '招聘': 'recruit hire', '培训': 'train training', '绩效': 'performance kpi',
20
+ '考勤机': 'clock device', '排班': 'schedule shift', '调班': 'shift change',
21
+ # 车辆/设备
22
+ '车辆': 'vehicle car', '设备': 'equipment asset device',
23
+ '资产': 'asset', '维修': 'maintain repair', '保养': 'maintenance',
24
+ '保险': 'insurance', '安全': 'safety', '年检': 'inspection annual',
25
+ '油耗': 'fuel oil', '里程': 'mileage distance', '行驶': 'drive travel',
26
+ '驾驶员': 'driver', '车队': 'fleet', '违章': 'violation traffic',
27
+ '事故': 'accident', 'GPS': 'gps location track',
28
+ # 品质/巡查
29
+ '品质': 'quality', '巡查': 'inspection patrol', '巡检': 'inspection patrol',
30
+ '检查': 'check inspect', '隐患': 'hazard risk', '整改': 'rectify fix',
31
+ '监控': 'monitor video', '告警': 'alarm alert', '预警': 'warning alert',
32
+ # 业务运营
33
+ '订单': 'order', '合同': 'contract', '项目': 'project',
34
+ '任务': 'task job', '工单': 'work order ticket',
35
+ '审批': 'approval audit', '流程': 'process workflow',
36
+ '报表': 'report', '统计': 'statistics stats', '分析': 'analysis',
37
+ '日志': 'log journal', '记录': 'record log',
38
+ '预算': 'budget', '费用': 'cost expense', '账单': 'bill invoice',
39
+ '结算': 'settlement', '报销': 'reimbursement expense',
40
+ # 库存/采购/供应链 (新增, A6)
41
+ '库存': 'inventory stock', '入库': 'inbound stockin warehouse',
42
+ '出库': 'outbound stockout', '盘点': 'inventory count check',
43
+ '采购': 'purchase procurement', '供应商': 'supplier vendor',
44
+ '物料': 'material item', '仓库': 'warehouse storage',
45
+ '调拨': 'transfer allocate', '领用': 'requisition issue',
46
+ # 客户/销售 (新增)
47
+ '客户': 'customer client', '销售': 'sale sell',
48
+ '报价': 'quote quotation', '商机': 'opportunity lead',
49
+ '联系人': 'contact', '回访': 'followup callback',
50
+ # 财务 (新增)
51
+ '发票': 'invoice receipt', '付款': 'payment pay',
52
+ '收款': 'receipt payment', '对账': 'reconcile reconciliation',
53
+ '税务': 'tax', '成本': 'cost', '利润': 'profit revenue',
54
+ '凭证': 'voucher certificate',
55
+ # 系统/权限
56
+ '权限': 'permission role', '角色': 'role', '登录': 'login auth',
57
+ '密码': 'password', '菜单': 'menu', '字典': 'dictionary dict',
58
+ '配置': 'config setting', '参数': 'param config',
59
+ '通知': 'notify notification', '消息': 'message notify',
60
+ '导入': 'import upload', '导出': 'export download',
61
+ # 通用
62
+ '在线率': 'online onlineRate', '状态': 'status state',
63
+ '版本': 'version', '模板': 'template', '标签': 'label tag',
64
+ '分类': 'category classify', '类型': 'type',
65
+ '地区': 'region area', '区域': 'zone region',
66
+ '时间': 'time date', '日期': 'date',
67
+ }
68
+
69
+ # Chinese business term -> code path/identifier patterns.
70
+ # Used for PRD <-> code mapping AND keyword search expansion,
71
+ # so building and querying use the same semantics.
72
+ BUSINESS_PATH_MAP = {
73
+ '保险': ['insurance', 'Insurance', 'ins-', '/ins/', 'vehlife/insurance'],
74
+ '考勤': ['attendance', 'Attendance', 'clock', 'Clock', 'time/clock', 'TimeClock'],
75
+ '打卡': ['clock', 'Clock', 'photoClock', 'PhotoClock'],
76
+ '车辆': ['vehicle', 'Vehicle', 'veh', 'vehlife', '/veh/'],
77
+ '品质': ['quality', 'Quality', 'QualityInspection', 'patrol', 'Patrol'],
78
+ '巡查': ['inspection', 'Inspection', 'patrol', 'Patrol'],
79
+ '资产': ['asset', 'Asset', '/asset/'],
80
+ '设备': ['equipment', 'Equipment', 'device', 'Device'],
81
+ '维修': ['maintain', 'Maintain', 'repair', 'Repair', 'maintenance'],
82
+ '排班': ['schedule', 'Schedule', 'shift', 'Shift'],
83
+ '请假': ['leave', 'Leave', 'holiday', 'Holiday'],
84
+ '审批': ['approval', 'Approval', 'audit', 'Audit', 'workflow'],
85
+ '在线率': ['online', 'Online', 'onlineRate', 'OnlineRate'],
86
+ '告警': ['alarm', 'Alarm', 'alert', 'Alert'],
87
+ '油耗': ['fuel', 'Fuel', 'oil', 'Oil', 'oilConsumption'],
88
+ '里程': ['mileage', 'Mileage', 'distance', 'Distance'],
89
+ '薪资': ['salary', 'Salary', 'wage', 'Wage', 'pay', 'Pay'],
90
+ '报表': ['report', 'Report', 'statistics', 'Statistics'],
91
+ '统计': ['statistics', 'Statistics', 'stats', 'Stats'],
92
+ '年检': ['inspection', 'Inspection', 'annual', 'Annual', 'vehInspection'],
93
+ '预算': ['budget', 'Budget'],
94
+ '费用': ['cost', 'Cost', 'expense', 'Expense'],
95
+ '安全': ['safety', 'Safety', 'safe', 'Safe', 'ehs'],
96
+ '监控': ['monitor', 'Monitor', 'video', 'Video'],
97
+ 'GPS': ['gps', 'GPS', 'location', 'Location', 'track', 'Track'],
98
+ '合同': ['contract', 'Contract'],
99
+ '组织': ['org', 'Org', 'department', 'Department'],
100
+ '员工': ['employee', 'Employee', 'staff', 'Staff', 'personnel'],
101
+ '权限': ['permission', 'Permission', 'role', 'Role', 'auth'],
102
+ '流程': ['workflow', 'Workflow', 'process', 'Process'],
103
+ # 新增 (A6 扩展)
104
+ '库存': ['inventory', 'Inventory', 'stock', 'Stock', '/stock/'],
105
+ '入库': ['inbound', 'Inbound', 'stockIn', 'StockIn'],
106
+ '出库': ['outbound', 'Outbound', 'stockOut', 'StockOut'],
107
+ '盘点': ['inventory', 'Inventory', 'count', 'Count', 'check', 'Check'],
108
+ '采购': ['purchase', 'Purchase', 'procurement', 'Procurement'],
109
+ '供应商': ['supplier', 'Supplier', 'vendor', 'Vendor'],
110
+ '仓库': ['warehouse', 'Warehouse', 'storage', 'Storage'],
111
+ '客户': ['customer', 'Customer', 'client', 'Client'],
112
+ '销售': ['sale', 'Sale', 'sell', 'Sell'],
113
+ '发票': ['invoice', 'Invoice', 'receipt', 'Receipt'],
114
+ '付款': ['payment', 'Payment', 'pay', 'Pay'],
115
+ '对账': ['reconcile', 'Reconcile', 'reconciliation'],
116
+ '登录': ['login', 'Login', 'auth', 'Auth', 'signin'],
117
+ '导入': ['import', 'Import', 'upload', 'Upload'],
118
+ '导出': ['export', 'Export', 'download', 'Download'],
119
+ '通知': ['notify', 'Notify', 'notification', 'Notification'],
120
+ '消息': ['message', 'Message', 'notify', 'Notify'],
121
+ '配置': ['config', 'Config', 'setting', 'Setting'],
122
+ '字典': ['dict', 'Dict', 'dictionary', 'Dictionary'],
123
+ }
124
+
125
+ # Chinese term -> single primary English keyword (derived from CN_MAP).
126
+ # Used by PRD parsing where one canonical keyword is needed.
127
+ CN_TO_EN = {cn: words.split()[0] for cn, words in CN_MAP.items()}
128
+
129
+
130
+ def auto_expand_from_prds(prd_index_path=None, min_freq=2):
131
+ """从已收集的 PRD 自动挖掘高频中文业务词 (性能优化 A6)。
132
+
133
+ 扫描 prd-index.json 的 cn_terms 字段, 把出现 ≥ min_freq 次且不在
134
+ 静态 CN_MAP 里的词加到运行时映射 (不写回静态文件)。
135
+
136
+ Returns:
137
+ dict: 新增的 {cn: en_guess} 映射 (合并到 CN_MAP 运行时副本)。
138
+ """
139
+ import os as _os
140
+ import json as _json
141
+ if prd_index_path is None:
142
+ base = _os.path.dirname(_os.path.dirname(_os.path.dirname(
143
+ _os.path.dirname(_os.path.abspath(__file__)))))
144
+ prd_index_path = _os.path.join(base, 'data', 'index', 'prd-index.json')
145
+
146
+ extra = {}
147
+ try:
148
+ if not _os.path.isfile(prd_index_path):
149
+ return extra
150
+ with open(prd_index_path, encoding='utf-8') as f:
151
+ prd_idx = _json.load(f)
152
+ freq = {}
153
+ for prd_file, prd in prd_idx.items():
154
+ for term in (prd.get('cn_terms') or []):
155
+ term = str(term).strip()
156
+ if term and len(term) >= 2:
157
+ freq[term] = freq.get(term, 0) + 1
158
+ for term, count in freq.items():
159
+ if count >= min_freq and term not in CN_MAP:
160
+ # 猜测: 用拼音首字母或直接小写 (无法转拼音就用原词小写)
161
+ # 简单方案: 直接 lowercase, 让英文索引做子串匹配
162
+ extra[term] = term.lower()
163
+ except Exception:
164
+ pass
165
+ return extra
166
+
167
+
168
+ def get_cn_map_with_auto():
169
+ """返回静态 CN_MAP + PRD 自动挖掘的扩展 (运行时合并, 不写文件)。"""
170
+ merged = dict(CN_MAP)
171
+ merged.update(auto_expand_from_prds())
172
+ return merged
173
+
174
+ # Stop words filtered out when extracting business terms from PRD text.
175
+ # (Union of the lists previously duplicated in git_sync.py / parse_prds.py)
176
+ PRD_STOP_WORDS = {
177
+ '需要', '可以', '使用', '进行', '功能', '实现', '显示', '支持', '包括', '或者',
178
+ '以及', '如果', '那么', '但是', '因为', '所以', '用户', '系统', '页面', '数据',
179
+ '信息', '操作', '管理', '列表', '详情', '新增', '修改', '删除', '查询', '搜索',
180
+ '筛选', '点击', '选择', '输入', '确认', '取消', '保存', '提交', '返回', '跳转',
181
+ '当前', '前端', '后端', '影响', '范围', '下拉', '条件', '字段', '全部', '异常',
182
+ '正常', '记录', '面板', '统计', '表页', '搜索表', '与其他', '表单', '背景',
183
+ '一个', '这个', '那个', '通过', '根据', '按照', '同时', '并且',
184
+ '要求', '需求', '描述', '说明', '备注', '注意', '重要', '优先', '级别',
185
+ '方案', '设计', '开发', '测试', '上线', '版本', '迭代', '更新', '发布',
186
+ }
187
+
188
+ # Platform alias -> project directory name under data/code/.
189
+ # DEFAULTS for this repo; overridden/extended by config.yaml `platforms:`
190
+ # (use get_platform_map() instead of reading this directly).
191
+ PLATFORM_MAP = {
192
+ 'web': 'fywl-ui', 'app': 'Carmg-H5', 'mobile': 'Carmg-H5',
193
+ 'h5': 'Carmg-H5', 'pc': 'fywl-ui',
194
+ '管理端': 'fywl-ui', '移动端': 'Carmg-H5', '后台': 'fywl-ui',
195
+ }
196
+
197
+ _platform_cache = None
198
+
199
+
200
+ def get_platform_map():
201
+ """Platform map merged from config.yaml (platforms:) over defaults.
202
+
203
+ config.yaml format:
204
+ platforms:
205
+ web: { project: fywl-ui, aliases: [pc, 管理端] }
206
+ This makes the knowledge graph portable to OTHER projects: point
207
+ config at different project dirs without touching code.
208
+ """
209
+ global _platform_cache
210
+ if _platform_cache is not None:
211
+ return _platform_cache
212
+ merged = dict(PLATFORM_MAP)
213
+ try:
214
+ import os
215
+ import yaml
216
+ base = os.path.dirname(os.path.dirname(os.path.dirname(
217
+ os.path.dirname(os.path.abspath(__file__)))))
218
+ cfg_path = os.path.join(base, '.qoder', 'config.yaml')
219
+ if os.path.isfile(cfg_path):
220
+ with open(cfg_path, encoding='utf-8') as f:
221
+ cfg = yaml.safe_load(f) or {}
222
+ platforms = cfg.get('platforms') or {}
223
+ if platforms:
224
+ merged = {} # config fully defines the map when present
225
+ for key, p in platforms.items():
226
+ if not isinstance(p, dict) or not p.get('project'):
227
+ continue
228
+ merged[key.lower()] = p['project']
229
+ for alias in (p.get('aliases') or []):
230
+ merged[str(alias).lower()] = p['project']
231
+ except Exception:
232
+ pass # fall back to hardcoded defaults
233
+ _platform_cache = merged
234
+ return merged
@@ -0,0 +1,38 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ utf8.py - 统一的 stdout/stderr UTF-8 配置 (防御性)
4
+
5
+ 解决审计 C2: 10 个脚本的裸 sys.stdout.reconfigure(encoding='utf-8') 在
6
+ stdout 被捕获时 (Qoder IDE 内嵌终端/管道) 会 AttributeError 崩溃。
7
+
8
+ 用法 (替换裸 reconfigure):
9
+ from common.utf8 import ensure_utf8_stdio
10
+ ensure_utf8_stdio()
11
+ """
12
+
13
+ import sys
14
+
15
+
16
+ def ensure_utf8_stdio() -> None:
17
+ """把 stdout/stderr 切到 UTF-8, 失败则忽略 (不崩溃)。
18
+
19
+ 安全性:
20
+ - TextIOWrapper 有 reconfigure -> 切换
21
+ - 其他类型 (管道包装/None) -> 静默跳过
22
+ - 永远不抛异常
23
+ """
24
+ for stream_name in ("stdout", "stderr"):
25
+ stream = getattr(sys, stream_name, None)
26
+ if stream is None:
27
+ continue
28
+ reconfigure = getattr(stream, "reconfigure", None)
29
+ if reconfigure is None:
30
+ continue # 不是 TextIOWrapper, 跳过
31
+ try:
32
+ reconfigure(encoding="utf-8", errors="replace")
33
+ except (TypeError, ValueError, OSError, IOError):
34
+ # errors 参数不支持或底层不可重配 -> 尝试仅 encoding
35
+ try:
36
+ reconfigure(encoding="utf-8")
37
+ except Exception:
38
+ pass # 彻底失败也不阻塞脚本主逻辑
@@ -0,0 +1,196 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ QODER Context Pack - 一次调用返回写 PRD/原型所需的全部上下文
5
+
6
+ 替代原来的 5-6 次 search_index.py 调用, 大幅减少 AI 工具往返次数。
7
+
8
+ Usage:
9
+ python context_pack.py <业务关键词> [--platform web|app|both] [--type table|form|detail|modal|dashboard]
10
+
11
+ 输出 (markdown, 供 AI 直接消化):
12
+ 1. 相关代码文件 (keyword-index, 平台过滤)
13
+ 2. 同类型页面真实示例 (style-index page_examples)
14
+ 3. 相关字段规格 (field_map: 字段名->中文标题->样例文件)
15
+ 4. 相关历史 PRD (prd-index)
16
+ 5. 相关 API 端点 (api-index)
17
+ 6. 设计 Token / 图标 / 模板 路径速查
18
+ 7. Repo Wiki 提示 (如存在)
19
+ """
20
+
21
+ import os
22
+ import sys
23
+ import json
24
+
25
+ # UTF-8 stdio (防御性: stdout 被捕获时不崩溃)
26
+ try:
27
+ sys.stdout.reconfigure(encoding='utf-8', errors='replace')
28
+ except (AttributeError, TypeError, OSError, IOError):
29
+ try:
30
+ sys.stdout.reconfigure(encoding='utf-8')
31
+ except Exception:
32
+ pass
33
+
34
+ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
35
+ from common.terms import CN_MAP, BUSINESS_PATH_MAP, CN_TO_EN, get_platform_map
36
+
37
+ BASE = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
38
+ INDEX_DIR = os.path.join(BASE, 'data', 'index')
39
+ PLATFORM_MAP = get_platform_map()
40
+ MIN_FUZZY_LEN = 4
41
+
42
+
43
+ def load(filename):
44
+ path = os.path.join(INDEX_DIR, filename)
45
+ if not os.path.isfile(path):
46
+ return None
47
+ try:
48
+ with open(path, encoding='utf-8') as f:
49
+ return json.load(f)
50
+ except Exception:
51
+ return None
52
+
53
+
54
+ def expand_query(query):
55
+ words = []
56
+ if query in CN_MAP:
57
+ words.extend(CN_MAP[query].split())
58
+ if query in BUSINESS_PATH_MAP:
59
+ words.extend(p.strip('/-').lower() for p in BUSINESS_PATH_MAP[query])
60
+ return sorted(set(w.lower() for w in words)) or [query.lower()]
61
+
62
+
63
+ def match_kw(word, kw):
64
+ w, k = word.lower(), kw.lower()
65
+ return w == k or (len(k) >= MIN_FUZZY_LEN and k in w) or (len(w) >= MIN_FUZZY_LEN and w in k)
66
+
67
+
68
+ def plat_filter(files, target):
69
+ if not target:
70
+ return files
71
+ return [f for f in files if target.lower() in f.lower()]
72
+
73
+
74
+ def main():
75
+ args = sys.argv[1:]
76
+ platform = None
77
+ page_type = None
78
+ if '--platform' in args:
79
+ i = args.index('--platform')
80
+ platform = args[i + 1] if i + 1 < len(args) else None
81
+ args = args[:i] + args[i + 2:]
82
+ if '--type' in args:
83
+ i = args.index('--type')
84
+ page_type = args[i + 1] if i + 1 < len(args) else None
85
+ args = args[:i] + args[i + 2:]
86
+ if not args:
87
+ print(__doc__)
88
+ return 1
89
+ query = args[0]
90
+
91
+ targets = []
92
+ if platform and platform.lower() not in ('both', '两端'):
93
+ targets = [PLATFORM_MAP.get(platform.lower(), platform)]
94
+ elif platform:
95
+ targets = sorted(set(PLATFORM_MAP.values()))
96
+
97
+ words = expand_query(query)
98
+ print('# Context Pack: {} (platform: {})'.format(query, platform or 'all'))
99
+ print('搜索词扩展: ' + ' '.join(words))
100
+
101
+ # ---- 1. 相关代码文件 ----
102
+ ki = load('keyword-index.json') or {}
103
+ hits = {}
104
+ for word in words:
105
+ for kw, files in ki.items():
106
+ if match_kw(word, kw):
107
+ fs = files if not targets else [f for t in targets for f in plat_filter(files, t)]
108
+ for f in fs:
109
+ hits.setdefault(f, set()).add(kw)
110
+ ranked = sorted(hits.items(), key=lambda x: len(x[1]), reverse=True)[:12]
111
+ print('\n## 1. 相关代码文件 ({} 个, 取前 12)'.format(len(hits)))
112
+ for f, kws in ranked:
113
+ print('- {} (命中: {})'.format(f, ','.join(sorted(kws)[:3])))
114
+ if not hits:
115
+ print('- (无命中 — 换业务词重试, 或该功能为全新)')
116
+
117
+ # ---- 2. 同类型页面真实示例 ----
118
+ si = load('style-index.json') or {}
119
+ print('\n## 2. 同类页面示例 (风格锚点, 读 1-2 个)')
120
+ for proj, pdata in sorted((si.get('projects') or {}).items()):
121
+ if targets and proj not in targets:
122
+ continue
123
+ examples = pdata.get('page_examples', {})
124
+ for pt, fs in sorted(examples.items()):
125
+ if page_type and page_type.lower() not in pt.lower():
126
+ continue
127
+ if not page_type and pt == 'unknown':
128
+ continue
129
+ print('- [{}] {}: {}'.format(proj, pt, ' | '.join(fs[:3])))
130
+
131
+ # ---- 3. 相关字段规格 ----
132
+ fm = si.get('field_map') or {}
133
+ fhits = []
134
+ for field, info in fm.items():
135
+ fl = field.lower()
136
+ if any(w in fl for w in words if len(w) >= MIN_FUZZY_LEN):
137
+ fhits.append((info.get('count', 0), field, info))
138
+ fhits.sort(reverse=True)
139
+ print('\n## 3. 相关字段 (真实字段名->中文标题)')
140
+ for cnt, field, info in fhits[:10]:
141
+ print('- {} -> {} ({}x)'.format(field, '/'.join(info.get('titles', [])[:3]), cnt))
142
+ if not fhits:
143
+ print('- (无直接相关字段)')
144
+
145
+ # ---- 4. 相关历史 PRD ----
146
+ prds = load('prd-index.json') or {}
147
+ phits = []
148
+ for pf, prd in prds.items():
149
+ text = (prd.get('title', '') + ' ' + ' '.join(prd.get('keywords', []))).lower()
150
+ score = sum(2 for w in words if w in text) + (5 if query in prd.get('title', '') else 0)
151
+ if score:
152
+ phits.append((score, pf, prd.get('title', pf)))
153
+ phits.sort(reverse=True)
154
+ print('\n## 4. 相关历史 PRD (data/docs/prd/)')
155
+ for s, pf, title in phits[:5]:
156
+ print('- {} - {}'.format(pf, title[:60]))
157
+ if not phits:
158
+ print('- (无相关 PRD)')
159
+
160
+ # ---- 5. 相关 API ----
161
+ apis = load('api-index.json') or {}
162
+ ahits = [(ep, f) for ep, f in apis.items()
163
+ if any(w in ep.lower() or w in f.lower() for w in words if len(w) >= MIN_FUZZY_LEN)]
164
+ print('\n## 5. 相关 API (取前 8 / 共 {})'.format(len(ahits)))
165
+ for ep, f in sorted(ahits)[:8]:
166
+ print('- {} -> {}'.format(ep, f.split('/')[-1]))
167
+ if not ahits:
168
+ print('- (无相关 API)')
169
+
170
+ # ---- 6. 风格/图标/模板速查 ----
171
+ print('\n## 6. 风格真源速查')
172
+ print('- Web Token: data/index/vben-style-reference.json (primary=hsl(212 100% 45%))')
173
+ print('- 看板/大屏: data/index/chart-style-reference.json (大屏深蓝风 monitor / 浅色卡片风 workbench)')
174
+ print('- 图标 (禁 emoji): data/index/icon-reference.json (Web=antd 内联SVG, APP=vant 字体)')
175
+ print('- 模板: .qoder/templates/prototype-web.html / prototype-app.html')
176
+ print('- PRD 模板: .qoder/templates/prd-full-template.md / prd-quick-template.md')
177
+
178
+ # ---- 7. Repo Wiki ----
179
+ wikis = []
180
+ if os.path.isdir(os.path.join(BASE, '.qoder', 'repowiki')):
181
+ wikis.append('.qoder/repowiki/')
182
+ code_dir = os.path.join(BASE, 'data', 'code')
183
+ if os.path.isdir(code_dir):
184
+ for p in sorted(os.listdir(code_dir)):
185
+ if os.path.isdir(os.path.join(code_dir, p, '.qoder', 'repowiki')):
186
+ wikis.append('data/code/{}/.qoder/repowiki/'.format(p))
187
+ if wikis:
188
+ print('\n## 7. Repo Wiki 可用 (模块级文档, 优先参考)')
189
+ for w in wikis:
190
+ print('- ' + w)
191
+
192
+ return 0
193
+
194
+
195
+ if __name__ == '__main__':
196
+ sys.exit(main())