@hupan56/wlkj 2.7.11 → 3.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.
- package/bin/cli.js +375 -78
- package/package.json +29 -29
- package/templates/.qoder/.runtime/ctx-cache-5660152f1d6dd819.md +23 -0
- package/templates/.qoder/.runtime/ctx-cache-afdce0dac06b25b0.md +23 -0
- package/templates/.qoder/.runtime/search-cache-eae7644e7b122f35.txt +1 -0
- package/templates/.qoder/learning/eval-history.jsonl +28 -0
- package/templates/data/index/wiki-index.json +8 -0
- package/templates/qoder/agents/insight-planning.md +1 -1
- package/templates/qoder/agents/insight-research.md +28 -15
- package/templates/qoder/commands/optional/wl-insight.md +159 -161
- package/templates/qoder/commands/optional/wl-report.md +4 -4
- package/templates/qoder/commands/optional/wl-status.md +2 -2
- package/templates/qoder/commands/wl-code.md +2 -2
- package/templates/qoder/commands/wl-design.md +2 -2
- package/templates/qoder/commands/wl-init.md +3 -3
- package/templates/qoder/commands/wl-prd.md +2 -2
- package/templates/qoder/commands/wl-req.md +43 -0
- package/templates/qoder/commands/wl-search.md +8 -8
- package/templates/qoder/commands/wl-task.md +17 -17
- package/templates/qoder/commands/wl-test.md +41 -15
- package/templates/qoder/config.yaml +17 -1
- package/templates/qoder/hooks/session-start.py +12 -22
- package/templates/qoder/nul +4 -0
- package/templates/qoder/rules/wl-pipeline.md +22 -48
- package/templates/qoder/scripts/README.md +139 -0
- package/templates/qoder/scripts/common/autotest_auth.py +109 -0
- package/templates/qoder/scripts/common/bootstrap.py +145 -0
- package/templates/qoder/scripts/common/check_publish.py +98 -0
- package/templates/qoder/scripts/common/cmd_registry.py +112 -0
- package/templates/qoder/scripts/common/config.py +187 -0
- package/templates/qoder/scripts/common/contract.py +317 -0
- package/templates/qoder/scripts/common/developer.py +2 -1
- package/templates/qoder/scripts/common/feishu.py +10 -9
- package/templates/qoder/scripts/common/guard.py +159 -0
- package/templates/qoder/scripts/common/identity.py +121 -2
- package/templates/qoder/scripts/common/kg_capabilities.py +182 -0
- package/templates/qoder/scripts/common/mcp_base.py +268 -0
- package/templates/qoder/scripts/common/paths.py +187 -1
- package/templates/qoder/scripts/common/result.py +223 -0
- package/templates/qoder/scripts/common/roles.py +60 -0
- package/templates/qoder/scripts/common/task_utils.py +21 -9
- package/templates/qoder/scripts/common/test_extract.py +115 -0
- package/templates/qoder/scripts/kg/__init__.py +11 -0
- package/templates/qoder/scripts/kg/build_entity_registry.py +196 -0
- package/templates/qoder/scripts/kg/build_relations.py +127 -0
- package/templates/qoder/scripts/{build_style_index.py → kg/build_style_index.py} +39 -10
- package/templates/qoder/scripts/kg/build_workflows.py +144 -0
- package/templates/qoder/scripts/{context_pack.py → kg/context_pack.py} +18 -11
- package/templates/qoder/scripts/{enrich_prompt.py → kg/enrich_prompt.py} +232 -226
- package/templates/qoder/scripts/{extract_api_params.py → kg/extract.py} +398 -246
- package/templates/qoder/scripts/{kg.py → kg/kg.py} +638 -708
- package/templates/qoder/scripts/{kg_build.py → kg/kg_build.py} +618 -612
- package/templates/qoder/scripts/{kg_build_db.py → kg/kg_build_db.py} +333 -327
- package/templates/qoder/scripts/{kg_duckdb.py → kg/kg_duckdb.py} +38 -37
- package/templates/qoder/scripts/{kg_incremental.py → kg/kg_incremental.py} +420 -393
- package/templates/qoder/scripts/{kg_link_db.py → kg/kg_link_db.py} +230 -224
- package/templates/qoder/scripts/{kg_semantic.py → kg/kg_semantic.py} +156 -150
- package/templates/qoder/scripts/kg/prefetch.py +359 -0
- package/templates/qoder/scripts/{search_index.py → kg/search_index.py} +70 -14
- package/templates/qoder/scripts/mcp/__init__.py +11 -0
- package/templates/qoder/scripts/{kg_mcp_server.py → mcp/kg_mcp_server.py} +77 -272
- package/templates/qoder/scripts/{lanhu_stdio_wrapper.py → mcp/lanhu_stdio_wrapper.py} +125 -119
- package/templates/qoder/scripts/{check_mcp.py → mcp/mcp_doctor.py} +515 -298
- package/templates/qoder/scripts/{mcp_launcher.py → mcp/mcp_launcher.py} +442 -414
- package/templates/qoder/scripts/{mysql_mcp_server.py → mcp/mysql_mcp_server.py} +347 -396
- package/templates/qoder/scripts/{zentao_mcp_server.py → mcp/zentao_mcp_server.py} +384 -424
- package/templates/qoder/scripts/report/__init__.py +11 -0
- package/templates/qoder/scripts/{add_session.py → report/add_session.py} +250 -244
- package/templates/qoder/scripts/{archive_prd.py → report/archive_prd.py} +383 -377
- package/templates/qoder/scripts/{eval_prd.py → report/eval_prd.py} +73 -11
- package/templates/qoder/scripts/{export.py → report/export.py} +63 -0
- package/templates/qoder/scripts/{fill_prototype.py → report/fill_prototype.py} +6 -0
- package/templates/qoder/scripts/{gen_design_doc.py → report/gen_design_doc.py} +400 -394
- package/templates/qoder/scripts/{learn.py → report/learn.py} +152 -146
- package/templates/qoder/scripts/{learn_aggregate.py → report/learn_aggregate.py} +207 -201
- package/templates/qoder/scripts/{report.py → report/report.py} +287 -281
- package/templates/qoder/scripts/report/req.py +222 -0
- package/templates/qoder/scripts/report/role.py +33 -0
- package/templates/qoder/scripts/{status.py → report/status.py} +634 -628
- package/templates/qoder/scripts/setup/__init__.py +11 -0
- package/templates/qoder/scripts/setup/carriers.py +662 -0
- package/templates/qoder/scripts/{init_doctor.py → setup/init_doctor.py} +63 -26
- package/templates/qoder/scripts/{install_qoderwork.py → setup/install_qoderwork.py} +36 -26
- package/templates/qoder/scripts/{platform_doctor.py → setup/platform_doctor.py} +265 -259
- package/templates/qoder/scripts/{repo_root.py → setup/repo_root.py} +112 -106
- package/templates/qoder/scripts/{setup.py → setup/setup.py} +147 -0
- package/templates/qoder/scripts/{setup_lanhu.py → setup/setup_lanhu.py} +973 -963
- package/templates/qoder/scripts/task/__init__.py +11 -0
- package/templates/qoder/scripts/{git_sync.py → task/git_sync.py} +52 -31
- package/templates/qoder/scripts/{syncgate.py → task/syncgate.py} +6 -0
- package/templates/qoder/scripts/task/task.py +221 -0
- package/templates/qoder/scripts/task/task_lifecycle.py +596 -0
- package/templates/qoder/scripts/task/task_query.py +161 -0
- package/templates/qoder/scripts/task/task_relations.py +424 -0
- package/templates/qoder/scripts/{team_sync.py → task/team_sync.py} +93 -20
- package/templates/qoder/scripts/test/__init__.py +11 -0
- package/templates/qoder/scripts/{autotest.py → test/autotest.py} +1174 -1751
- package/templates/qoder/scripts/{autotest_batch.py → test/autotest_batch.py} +242 -224
- package/templates/qoder/scripts/test/autotest_data.py +675 -0
- package/templates/qoder/scripts/{autotest_run.py → test/autotest_run.py} +309 -297
- package/templates/qoder/scripts/{benchmark.py → test/benchmark.py} +6 -0
- package/templates/qoder/scripts/{kg_auto_login.py → test/kg_auto_login.py} +202 -196
- package/templates/qoder/scripts/{kg_test_runner.py → test/kg_test_runner.py} +7 -1
- package/templates/qoder/scripts/{page_probe.py → test/page_probe.py} +465 -459
- package/templates/qoder/scripts/wlkj.py +116 -0
- package/templates/qoder/settings.json +1 -10
- package/templates/qoder/skills/design-import/SKILL.md +226 -226
- package/templates/qoder/skills/design-review/SKILL.md +82 -82
- package/templates/qoder/skills/prd-generator/SKILL.md +26 -16
- package/templates/qoder/skills/prd-review/SKILL.md +5 -5
- package/templates/qoder/skills/prototype-generator/SKILL.md +256 -256
- package/templates/qoder/skills/spec-coder/SKILL.md +4 -4
- package/templates/qoder/skills/spec-generator/SKILL.md +4 -4
- package/templates/qoder/skills/test-generator/SKILL.md +5 -5
- package/templates/qoder/skills/wl-code/SKILL.md +4 -4
- package/templates/qoder/skills/wl-commit/SKILL.md +4 -4
- package/templates/qoder/skills/wl-design/SKILL.md +3 -3
- package/templates/qoder/skills/wl-init/SKILL.md +8 -8
- package/templates/qoder/skills/wl-insight/SKILL.md +5 -5
- package/templates/qoder/skills/wl-prd-full/SKILL.md +6 -6
- package/templates/qoder/skills/wl-prd-quick/SKILL.md +6 -6
- package/templates/qoder/skills/wl-prd-review/SKILL.md +4 -4
- package/templates/qoder/skills/wl-report/SKILL.md +7 -7
- package/templates/qoder/skills/wl-search/SKILL.md +13 -13
- package/templates/qoder/skills/wl-spec/SKILL.md +5 -5
- package/templates/qoder/skills/wl-status/SKILL.md +5 -5
- package/templates/qoder/skills/wl-task/SKILL.md +6 -6
- package/templates/qoder/skills/wl-test/SKILL.md +102 -39
- package/templates/root/AGENTS.md +39 -40
- package/templates/qoder/hooks/inject-workflow-state.py +0 -169
- package/templates/qoder/scripts/__pycache__/check_mcp_launch.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/__pycache__/install_qoderwork.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/__pycache__/mcp_launcher.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/__pycache__/platform_doctor.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/check_carriers.py +0 -238
- package/templates/qoder/scripts/check_mcp_launch.py +0 -183
- package/templates/qoder/scripts/check_qoderwork_consistency.py +0 -166
- package/templates/qoder/scripts/collect_prds.py +0 -31
- package/templates/qoder/scripts/common/mentions.py +0 -134
- package/templates/qoder/scripts/common/utf8.py +0 -38
- package/templates/qoder/scripts/extract_routes.py +0 -54
- package/templates/qoder/scripts/extract_routes_tree.py +0 -78
- package/templates/qoder/scripts/handoff.py +0 -22
- package/templates/qoder/scripts/init_developer.py +0 -76
- package/templates/qoder/scripts/parse_prds.py +0 -33
- package/templates/qoder/scripts/role.py +0 -51
- package/templates/qoder/scripts/sync_carriers.py +0 -259
- package/templates/qoder/scripts/task.py +0 -1261
- package/templates/qoder/scripts/workspace_init.py +0 -102
- package/templates/qoder/skills/prompt-enrich/SKILL.md +0 -90
- package/templates/qoder/skills/prototype-generator/SKILL.md.zcode-79180-2af4721f-f9a6-412c-88db-c0af680d211b.tmp +0 -0
- /package/templates/qoder/scripts/{secure-ls.js → test/secure-ls.js} +0 -0
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
# -*- coding: utf-8 -*-
|
|
3
|
+
# v3.0 路径自举: 引导到 common/bootstrap, 统一 sys.path 逻辑
|
|
4
|
+
import os as _o, sys as _s
|
|
5
|
+
_cp = _o.path.join(_o.path.dirname(_o.path.dirname(_o.path.abspath(__file__))), 'common')
|
|
6
|
+
if _cp not in _s.path: _s.path.insert(0, _cp)
|
|
7
|
+
from bootstrap import setup; setup()
|
|
8
|
+
|
|
3
9
|
"""
|
|
4
10
|
QODER EVA - PRD/原型 自动评估 (快且准的"准"侧自动指标)
|
|
5
11
|
|
|
@@ -14,7 +20,7 @@ Usage:
|
|
|
14
20
|
A3 模板完整 (30分): PRD 包含团队模板必需章节的比例
|
|
15
21
|
-> 保证团队章程的模板结构不丢
|
|
16
22
|
|
|
17
|
-
结果追加到
|
|
23
|
+
结果追加到 data/learning/eval-history.jsonl (本机), 供 /wl-status 趋势展示。
|
|
18
24
|
Exit: 0 = 总分>=80, 1 = 不达标 (发布前应修复)
|
|
19
25
|
"""
|
|
20
26
|
|
|
@@ -36,7 +42,7 @@ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
|
36
42
|
BASE = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
37
43
|
INDEX_DIR = os.path.join(BASE, 'data', 'index')
|
|
38
44
|
TPL_DIR = os.path.join(BASE, '.qoder', 'templates')
|
|
39
|
-
HISTORY = os.path.join(BASE, '
|
|
45
|
+
HISTORY = os.path.join(BASE, 'data', 'learning', 'eval-history.jsonl')
|
|
40
46
|
|
|
41
47
|
# 完整模板必需章节 (prd-full-template.md) / Mini 模板必需章节
|
|
42
48
|
FULL_SECTIONS = ['版本信息', '变更日志', '名词解释', '需求背景', '需求来源', '产品现状',
|
|
@@ -185,10 +191,17 @@ def score_reality(prd_text):
|
|
|
185
191
|
if checked == 0:
|
|
186
192
|
return 1.0, '未引用具体字段/API (零星文案类需求正常)', []
|
|
187
193
|
ratio = hits / checked
|
|
194
|
+
# 图谱覆盖度感知: 防止"认真写 API 引用但图谱没覆盖"比"啥都不写"分还低。
|
|
195
|
+
kg_api_count = len(api_paths)
|
|
196
|
+
if kg_api_count < 10 and ratio < 0.5 and hits == 0:
|
|
197
|
+
ratio = max(ratio, 0.5)
|
|
198
|
+
note_suffix = '(图谱API稀疏, 已软保底)'
|
|
199
|
+
else:
|
|
200
|
+
note_suffix = ''
|
|
188
201
|
db_note = (',其中 %d 个由数据库层确认' % db_bonus) if db_bonus else ''
|
|
189
202
|
layer_note = ('(双层: 代码+数据库)' if db_cols else '(代码层)')
|
|
190
|
-
return ratio, '{}/{} 个字段/API 引用可在图谱中找到{}{}'.format(
|
|
191
|
-
hits, checked, layer_note, db_note), misses[:8]
|
|
203
|
+
return ratio, '{}/{} 个字段/API 引用可在图谱中找到{}{}{}'.format(
|
|
204
|
+
hits, checked, layer_note, db_note, note_suffix), misses[:8]
|
|
192
205
|
|
|
193
206
|
|
|
194
207
|
# ============================================================
|
|
@@ -257,6 +270,13 @@ def score_template(prd_text):
|
|
|
257
270
|
# ============================================================
|
|
258
271
|
|
|
259
272
|
def main():
|
|
273
|
+
"""v3.0: 接入 Result + contract 校验 (让设计模式真落地)。
|
|
274
|
+
|
|
275
|
+
改动:
|
|
276
|
+
1. 评估前先 validate_contract (软校验, 无契约头降级不阻塞)
|
|
277
|
+
2. 用 Result 表达三级结果 (PASS/WARN/FAIL), 替代裸 return 0/1
|
|
278
|
+
3. graceful_error 装饰器统一输出
|
|
279
|
+
"""
|
|
260
280
|
if len(sys.argv) < 2:
|
|
261
281
|
print(__doc__)
|
|
262
282
|
return 1
|
|
@@ -264,9 +284,35 @@ def main():
|
|
|
264
284
|
html_path = sys.argv[2] if len(sys.argv) > 2 else None
|
|
265
285
|
|
|
266
286
|
if not os.path.isfile(prd_path):
|
|
267
|
-
print('PRD 不存在: ' + prd_path)
|
|
287
|
+
print('[FAIL] PRD 不存在: ' + prd_path)
|
|
268
288
|
return 1
|
|
269
289
|
|
|
290
|
+
# v3.0 接入: 角色边界校验 (guard.py, 软提示不阻塞)
|
|
291
|
+
# eval_prd 是 PRD 发布的必经质量门, 在这里检查"当前角色能否产出 prd"
|
|
292
|
+
# 越权 → 打印提示但不 return(降级, 让用户自己决定是否继续)
|
|
293
|
+
try:
|
|
294
|
+
from common.guard import check_produce
|
|
295
|
+
gr = check_produce('prd')
|
|
296
|
+
if gr.is_warn:
|
|
297
|
+
print(gr.msg)
|
|
298
|
+
if gr.hint:
|
|
299
|
+
print(' -> ' + gr.hint)
|
|
300
|
+
except Exception:
|
|
301
|
+
pass # guard 不可用不影响评估
|
|
302
|
+
|
|
303
|
+
# v3.0 接入: 契约校验 (软, 无@contract头则降级不阻塞)
|
|
304
|
+
try:
|
|
305
|
+
from common.contract import validate_contract
|
|
306
|
+
cr = validate_contract(prd_path)
|
|
307
|
+
if cr.is_warn:
|
|
308
|
+
print(cr.msg) # WARN: 无契约头或字段警告, 提示但不阻塞
|
|
309
|
+
elif cr.ok and cr.code not in ('no_contract', 'no_schema', 'ok'):
|
|
310
|
+
print(cr.msg)
|
|
311
|
+
if cr.hint:
|
|
312
|
+
print(' -> ' + cr.hint)
|
|
313
|
+
except Exception:
|
|
314
|
+
pass # 契约校验失败不影响评估
|
|
315
|
+
|
|
270
316
|
# 用程序化 API 评估 (重构后 main 只是 CLI 包装)
|
|
271
317
|
from common.eval_api import evaluate, format_report
|
|
272
318
|
try:
|
|
@@ -278,10 +324,23 @@ def main():
|
|
|
278
324
|
# 打印人类可读报告
|
|
279
325
|
print(format_report(result))
|
|
280
326
|
|
|
281
|
-
#
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
327
|
+
# v3.0 接入: 用 Result 表达三级 (PASS ≥80 / WARN 60-80 / FAIL <60)
|
|
328
|
+
# 现状是二值 passed, 这里加 WARN 中间档, 让"差一点的 PRD"能带警告发布
|
|
329
|
+
from common.result import Result, CODE
|
|
330
|
+
pct = result.get('pct', 0)
|
|
331
|
+
if result['passed']:
|
|
332
|
+
verdict = Result.success(msg='✓ PASS ({}%)'.format(pct), data=result)
|
|
333
|
+
elif pct >= 60:
|
|
334
|
+
verdict = Result.warning(
|
|
335
|
+
'⚠ WARN ({}%): 低于80但有内容, 可带警告发布'.format(pct),
|
|
336
|
+
code=CODE.BELOW_THRESH, data=result,
|
|
337
|
+
)
|
|
338
|
+
else:
|
|
339
|
+
verdict = Result.failure(
|
|
340
|
+
'✗ FAIL ({}%): 低于60, 需修复'.format(pct),
|
|
341
|
+
code=CODE.BELOW_THRESH,
|
|
342
|
+
hint='检查字段锚定(A1)和模板完整(A3)',
|
|
343
|
+
)
|
|
285
344
|
|
|
286
345
|
# 记录历史 (原子追加, 防并发损坏)
|
|
287
346
|
try:
|
|
@@ -295,11 +354,12 @@ def main():
|
|
|
295
354
|
'a3_template': result['r3'],
|
|
296
355
|
'score_pct': result['pct'],
|
|
297
356
|
'passed': result['passed'],
|
|
357
|
+
'verdict': verdict.code, # v3.0: 记录三级判定
|
|
298
358
|
}, timeout=10)
|
|
299
359
|
except Exception:
|
|
300
360
|
pass # 历史记录失败不影响评估结果
|
|
301
361
|
|
|
302
|
-
# 埋点: PRD 评估结果反馈给 learning 引擎
|
|
362
|
+
# 埋点: PRD 评估结果反馈给 learning 引擎
|
|
303
363
|
try:
|
|
304
364
|
from learn import record_feedback
|
|
305
365
|
event = 'prd_accepted' if result['passed'] else 'prd_rejected'
|
|
@@ -311,7 +371,9 @@ def main():
|
|
|
311
371
|
except Exception:
|
|
312
372
|
pass # 埋点失败不影响评估
|
|
313
373
|
|
|
314
|
-
|
|
374
|
+
# exit code: PASS=0, WARN=0(可继续), FAIL=1
|
|
375
|
+
# (WARN 不阻塞发布, 与方案"eval_prd 三级门禁"一致)
|
|
376
|
+
return 0 if verdict.ok else 1
|
|
315
377
|
|
|
316
378
|
|
|
317
379
|
if __name__ == '__main__':
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
# -*- coding: utf-8 -*-
|
|
3
|
+
# v3.0 路径自举: 引导到 common/bootstrap, 统一 sys.path 逻辑
|
|
4
|
+
import os as _o, sys as _s
|
|
5
|
+
_cp = _o.path.join(_o.path.dirname(_o.path.dirname(_o.path.abspath(__file__))), 'common')
|
|
6
|
+
if _cp not in _s.path: _s.path.insert(0, _cp)
|
|
7
|
+
from bootstrap import setup; setup()
|
|
8
|
+
|
|
3
9
|
"""
|
|
4
10
|
export.py - 集成导出工具 (阶段 C)
|
|
5
11
|
|
|
@@ -470,6 +476,13 @@ def main():
|
|
|
470
476
|
p_cg = sub.add_parser('callgraph', help='Java 调用图')
|
|
471
477
|
p_cg.add_argument('--class', dest='class_name', help='查询指定类的依赖')
|
|
472
478
|
|
|
479
|
+
p_dx = sub.add_parser('docx', help='PRD导出Word(M5)')
|
|
480
|
+
p_dx.add_argument('prd', help='PRD md文件')
|
|
481
|
+
p_dx.add_argument('-o', '--output')
|
|
482
|
+
p_pf = sub.add_parser('pdf', help='PRD导出HTML(浏览器打印为PDF)')
|
|
483
|
+
p_pf.add_argument('prd', help='PRD md文件')
|
|
484
|
+
p_pf.add_argument('-o', '--output')
|
|
485
|
+
|
|
473
486
|
args = parser.parse_args()
|
|
474
487
|
if not args.format:
|
|
475
488
|
parser.print_help()
|
|
@@ -482,6 +495,56 @@ def main():
|
|
|
482
495
|
elif args.format == 'callgraph':
|
|
483
496
|
return export_callgraph(class_name=args.class_name)
|
|
484
497
|
|
|
498
|
+
elif args.format == 'docx':
|
|
499
|
+
return export_docx(args.prd, args.output)
|
|
500
|
+
elif args.format == 'pdf':
|
|
501
|
+
return export_pdf(args.prd, args.output)
|
|
502
|
+
|
|
503
|
+
|
|
504
|
+
def export_docx(prd_path, output=None):
|
|
505
|
+
"""PRD markdown -> Word docx (M5)。用 python-docx, 纯Python不依赖pandoc。"""
|
|
506
|
+
if not os.path.isfile(prd_path):
|
|
507
|
+
print('PRD 不存在: %s' % prd_path); return 1
|
|
508
|
+
try:
|
|
509
|
+
from docx import Document
|
|
510
|
+
except ImportError:
|
|
511
|
+
print('python-docx 未装。pip install python-docx'); return 1
|
|
512
|
+
doc = Document()
|
|
513
|
+
with open(prd_path, encoding='utf-8') as f:
|
|
514
|
+
for line in f:
|
|
515
|
+
ln = line.rstrip('\n'); s = ln.strip()
|
|
516
|
+
if s.startswith('# '): doc.add_heading(s[2:], level=1)
|
|
517
|
+
elif s.startswith('## '): doc.add_heading(s[3:], level=2)
|
|
518
|
+
elif s.startswith('### '): doc.add_heading(s[4:], level=3)
|
|
519
|
+
elif s.startswith('- ') or s.startswith('* '): doc.add_paragraph(s[2:], style='List Bullet')
|
|
520
|
+
elif s.startswith('<!--'): pass
|
|
521
|
+
elif s == '': pass
|
|
522
|
+
else: doc.add_paragraph(s)
|
|
523
|
+
if not output: output = os.path.splitext(prd_path)[0] + '.docx'
|
|
524
|
+
doc.save(output)
|
|
525
|
+
print('OK Word: %s' % output); return 0
|
|
526
|
+
|
|
527
|
+
|
|
528
|
+
def export_pdf(prd_path, output=None):
|
|
529
|
+
"""PRD markdown -> HTML (浏览器Ctrl+P另存PDF) (M5)。避免wkhtmltopdf重依赖。"""
|
|
530
|
+
if not os.path.isfile(prd_path):
|
|
531
|
+
print('PRD 不存在: %s' % prd_path); return 1
|
|
532
|
+
try:
|
|
533
|
+
import markdown as md
|
|
534
|
+
except ImportError:
|
|
535
|
+
print('markdown 未装。pip install markdown'); return 1
|
|
536
|
+
with open(prd_path, encoding='utf-8') as f:
|
|
537
|
+
body = md.markdown(f.read(), extensions=['tables', 'fenced_code'])
|
|
538
|
+
html = ('<!DOCTYPE html><html lang="zh"><head><meta charset="UTF-8"><style>'
|
|
539
|
+
'body{font-family:"Microsoft YaHei",sans-serif;max-width:800px;margin:40px auto;line-height:1.6}'
|
|
540
|
+
'h1{border-bottom:2px solid #333}h2{border-bottom:1px solid #999;margin-top:24px}'
|
|
541
|
+
'table{border-collapse:collapse;width:100%}th,td{border:1px solid #ccc;padding:6px}th{background:#f5f5f5}'
|
|
542
|
+
'@media print{body{margin:20px}}</style></head><body>' + body + '</body></html>')
|
|
543
|
+
if not output: output = os.path.splitext(prd_path)[0] + '.html'
|
|
544
|
+
with open(output, 'w', encoding='utf-8') as f:
|
|
545
|
+
f.write(html)
|
|
546
|
+
print('OK HTML: %s (浏览器打开 Ctrl+P 另存PDF)' % output); return 0
|
|
547
|
+
|
|
485
548
|
|
|
486
549
|
if __name__ == '__main__':
|
|
487
550
|
sys.exit(main())
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
# -*- coding: utf-8 -*-
|
|
3
|
+
# v3.0 路径自举: 引导到 common/bootstrap, 统一 sys.path 逻辑
|
|
4
|
+
import os as _o, sys as _s
|
|
5
|
+
_cp = _o.path.join(_o.path.dirname(_o.path.dirname(_o.path.abspath(__file__))), 'common')
|
|
6
|
+
if _cp not in _s.path: _s.path.insert(0, _cp)
|
|
7
|
+
from bootstrap import setup; setup()
|
|
8
|
+
|
|
3
9
|
"""
|
|
4
10
|
QODER Fill Prototype - 用真实数据自动预填原型模板 (80%完成)
|
|
5
11
|
|