cardbuddy 0.13.3 → 0.14.1

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 (61) hide show
  1. package/README.md +7 -3
  2. package/dist/cli.js +4 -4
  3. package/dist/config.js +7 -7
  4. package/dist/init.js +15 -4
  5. package/dist/portGuard.js +2 -2
  6. package/dist/preview-editor/assets/index-DnXqKrCc.js +307 -0
  7. package/dist/preview-editor/assets/index-cbTCHp8J.css +1 -0
  8. package/dist/preview-editor/index.html +2 -2
  9. package/dist/standalone.js +14 -6
  10. package/dist/vendored/card-editor/api.js +6 -6
  11. package/dist/vendored/mcp/client.mjs +53 -3
  12. package/dist/vendored/mcp/tools.mjs +17 -1
  13. package/dist/vendored/server/core/ai-chat.js +1443 -271
  14. package/dist/vendored/server/core/cardFiles.js +271 -39
  15. package/dist/vendored/server/core/cardImportMeta.js +16 -0
  16. package/dist/vendored/server/core/chatHistory.js +122 -0
  17. package/dist/vendored/server/core/index.js +10 -3
  18. package/dist/vendored/server/core/turnJournal.js +386 -0
  19. package/dist/vendored/skills/card-skill-trainer/SKILL.md +399 -222
  20. package/dist/vendored/skills/card-skill-trainer/scripts/build_dataset.py +103 -19
  21. package/dist/vendored/skills/card-skill-trainer/scripts/diagnose_case.mjs +378 -200
  22. package/dist/vendored/skills/card-skill-trainer/scripts/lib/runs.mjs +147 -0
  23. package/dist/vendored/skills/card-skill-trainer/scripts/lib/scoring.mjs +297 -4
  24. package/dist/vendored/skills/card-skill-trainer/scripts/plan_next_round.mjs +484 -0
  25. package/dist/vendored/skills/card-skill-trainer/scripts/round_diff.mjs +77 -96
  26. package/dist/vendored/skills/card-skill-trainer/scripts/run_round.mjs +237 -62
  27. package/dist/vendored/skills/card-skill-trainer/scripts/score_round.mjs +74 -35
  28. package/dist/vendored/skills/dsl-card-ai/SKILL.md +15 -2
  29. package/dist/vendored/skills/dsl-card-generator/SKILL.md +5 -3
  30. package/dist/vendored/skills/dsl-card-generator/references/generation_pitfalls.md +120 -0
  31. package/dist/vendored/skills/dsl-card-generator/references/previewer_contract.md +10 -0
  32. package/dist/vendored/skills/dsl-card-generator/references/quickref.md +15 -1
  33. package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/canvas-radar.json +2 -2
  34. package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/scatter-pill-band.json +10 -0
  35. package/dist/vendored/skills/dsl-card-generator/references/recipes.md +43 -1
  36. package/dist/vendored/skills/dsl-card-generator/references/rules.md +33 -6
  37. package/dist/vendored/skills/dsl-card-generator/references/slices_convention.md +12 -5
  38. package/dist/vendored/skills/dsl-card-generator/scripts/add_component.mjs +5 -10
  39. package/dist/vendored/skills/dsl-card-generator/scripts/crop_slices.mjs +47 -10
  40. package/dist/vendored/skills/dsl-card-generator/scripts/expand_ir.mjs +12 -4
  41. package/dist/vendored/skills/dsl-card-generator/scripts/lib/design_sample.mjs +388 -328
  42. package/dist/vendored/skills/dsl-card-generator/scripts/lib/slice_pixels.mjs +109 -0
  43. package/dist/vendored/skills/dsl-card-generator/scripts/render_card.mjs +45 -7
  44. package/dist/vendored/skills/dsl-card-generator/scripts/route_input.mjs +7 -3
  45. package/dist/vendored/skills/dsl-card-generator/scripts/spec_audit.mjs +110 -23
  46. package/dist/vendored/skills/dsl-card-generator/scripts/spec_to_schema_v3.mjs +2954 -127
  47. package/dist/vendored/skills/dsl-card-generator/scripts/spec_tree.mjs +128 -3
  48. package/dist/vendored/skills/dsl-card-generator/scripts/validate_schema.py +28 -0
  49. package/dist/vendored/skills/finmall-card-sync/SKILL.md +1 -1
  50. package/dist/vendored/skills/finmall-session-navigator/SKILL.md +1 -1
  51. package/dist/vendored/skills/finmall-session-navigator/scripts/session-manager.mjs +1 -1
  52. package/dist/vendored/skills/shared/finmall-config.mjs +4 -4
  53. package/dist/vendored/skills/sketch-spec-to-card/SKILL.md +2 -2
  54. package/dist/vendored/skills/sketch-spec-to-card/scripts/parse_meaxure.py +29 -2
  55. package/dist/vendored/tools/finmall-env.mjs +6 -6
  56. package/package.json +1 -1
  57. package/dist/preview-editor/assets/index-CQScMhyg.css +0 -1
  58. package/dist/preview-editor/assets/index-pW1J4vP3.js +0 -276
  59. package/dist/vendored/skills/card-skill-trainer/baselines/v3-r038n.json +0 -43
  60. package/dist/vendored/skills/card-skill-trainer/baselines/v3-r039n.json +0 -47
  61. package/dist/vendored/skills/card-skill-trainer/baselines/v3-r050n.json +0 -48
@@ -57,33 +57,103 @@ def split_for(case_id: str) -> str:
57
57
 
58
58
 
59
59
  def discover_sources(root: Path):
60
+ # --src 直接指向一份 MeaXure 导出(自带 index.html)时,它本身就是唯一 source。
61
+ # 此前必须指到父目录才能被发现,会把同级所有设计稿一起卷进来(训练常只跑一两块画板)。
62
+ if (root / "index.html").exists():
63
+ yield root
64
+ return
60
65
  for path in sorted(root.iterdir(), key=lambda p: p.name):
61
66
  if path.is_dir() and (path / "index.html").exists():
62
67
  yield path
63
68
 
64
69
 
65
- def parse_source(source: Path, raw_root: Path, force: bool, sample_size: int = 0) -> bool:
70
+ def parse_artboard_selector(value: Optional[str]):
71
+ """--artboards 拆成「1 起序号」与「画板名关键词」两类,与门禁清单同口径。"""
72
+ indices: list = []
73
+ keywords: list = []
74
+ for raw in (value or "").split(","):
75
+ token = raw.strip()
76
+ if not token:
77
+ continue
78
+ if token.isdigit():
79
+ index = int(token)
80
+ if index >= 1 and index not in indices:
81
+ indices.append(index)
82
+ elif token not in keywords:
83
+ keywords.append(token)
84
+ return indices, keywords
85
+
86
+
87
+ def matches_artboard_selector(canvas: dict, indices: list, keywords: list) -> bool:
88
+ if not indices and not keywords:
89
+ return True
90
+ order = canvas.get("order")
91
+ if indices and isinstance(order, int) and order in indices:
92
+ return True
93
+ name = str(canvas.get("name") or "")
94
+ return bool(keywords) and any(keyword in name for keyword in keywords)
95
+
96
+
97
+ def scoped_canvases_ready(target: Path, indices: list, keywords: list) -> bool:
98
+ """增量场景:所选画板已全部 generated 且 spec.json 落盘,才允许跳过重解析。"""
99
+ if not indices and not keywords:
100
+ return (target / "manifest.json").exists()
101
+ manifest_path = target / "manifest.json"
102
+ if not manifest_path.exists():
103
+ return False
104
+ try:
105
+ manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
106
+ except (OSError, ValueError):
107
+ return False
108
+ picked = [
109
+ canvas for canvas in manifest.get("canvases", [])
110
+ if isinstance(canvas, dict) and matches_artboard_selector(canvas, indices, keywords)
111
+ ]
112
+ if not picked:
113
+ return False
114
+ return all(
115
+ canvas.get("status") == "generated"
116
+ and (target / str(canvas.get("name") or "") / "spec.json").exists()
117
+ for canvas in picked
118
+ )
119
+
120
+
121
+ def parse_source(source: Path, raw_root: Path, force: bool, sample_size: int = 0,
122
+ indices: Optional[list] = None, keywords: Optional[list] = None) -> bool:
66
123
  target = raw_root / safe_name(source.name)
67
- if (target / "manifest.json").exists() and not force:
124
+ indices = indices or []
125
+ keywords = keywords or []
126
+ if not force and scoped_canvases_ready(target, indices, keywords):
68
127
  print(f"[skip] {source.name}")
69
128
  return True
70
129
  target.mkdir(parents=True, exist_ok=True)
71
- command = [
72
- sys.executable,
73
- str(PARSER),
74
- "--src",
75
- str(source),
76
- "--out",
77
- str(target),
78
- "--build",
79
- # 无人值守训练 = 显式批量授权(rules.md §13.3 例外):直接过 parse_meaxure 的多画板选择门禁
80
- "--scope-all",
81
- ]
82
- if sample_size:
83
- command.extend(["--sample", str(sample_size)])
84
- print(f"[parse] {source.name}")
85
- result = subprocess.run(command)
86
- return result.returncode == 0 and (target / "manifest.json").exists()
130
+ # parse_meaxure 的 --only 优先于 --indices,混用时拆成两次局部解析:
131
+ # 局部运行只重解析选中画板,未选中的状态从旧 manifest 继承,两次结果自然合并。
132
+ scopes: list = []
133
+ if indices:
134
+ # 门禁清单是 1 起,--indices 是 0 起
135
+ scopes.append(["--indices", ",".join(str(index - 1) for index in sorted(indices))])
136
+ if keywords:
137
+ scopes.append(["--only", ",".join(keywords)])
138
+ if not scopes:
139
+ scopes.append(["--sample", str(sample_size)] if sample_size else [])
140
+ for scope in scopes:
141
+ command = [
142
+ sys.executable,
143
+ str(PARSER),
144
+ "--src",
145
+ str(source),
146
+ "--out",
147
+ str(target),
148
+ "--build",
149
+ # 无人值守训练 = 显式批量授权(rules.md §13.3 例外):直接过 parse_meaxure 的多画板选择门禁
150
+ "--scope-all",
151
+ ] + scope
152
+ print(f"[parse] {source.name}{' ' + ' '.join(scope) if scope else ''}")
153
+ result = subprocess.run(command)
154
+ if result.returncode != 0:
155
+ return False
156
+ return (target / "manifest.json").exists()
87
157
 
88
158
 
89
159
  def first_image(spec_dir: Path, card: dict) -> Optional[Path]:
@@ -287,6 +357,11 @@ def main():
287
357
  parser.add_argument("--max-cases", type=int, default=160)
288
358
  parser.add_argument("--max-sources", type=int, default=0)
289
359
  parser.add_argument("--artboards-per-source", type=int, default=0)
360
+ parser.add_argument(
361
+ "--artboards", default=None,
362
+ help="只构建指定画板:1 起序号或画板名关键词,逗号分隔(如 4,14 / 金额极限,保险保障)。"
363
+ "序号转 parse_meaxure 的 0 起 --indices,关键词走 --only;未选画板不进数据集。",
364
+ )
290
365
  parser.add_argument("--force", action="store_true")
291
366
  parser.add_argument("--duplicate-threshold", type=int, default=4)
292
367
  parser.add_argument("--no-artboard-dedup", action="store_true")
@@ -297,6 +372,10 @@ def main():
297
372
  raw_root = out_root / "raw"
298
373
  raw_root.mkdir(parents=True, exist_ok=True)
299
374
 
375
+ sel_indices, sel_keywords = parse_artboard_selector(args.artboards)
376
+ if sel_indices or sel_keywords:
377
+ print(f"[scope] 只构建画板:indices={sel_indices or '[]'} keywords={sel_keywords or '[]'}")
378
+
300
379
  sources = list(discover_sources(source_root))
301
380
  if args.max_sources:
302
381
  sources = sources[: args.max_sources]
@@ -311,7 +390,8 @@ def main():
311
390
  eligibleRecords = []
312
391
  generatedArtboards = 0
313
392
  for source in sources:
314
- ok = parse_source(source, raw_root, args.force, args.artboards_per_source)
393
+ ok = parse_source(source, raw_root, args.force, args.artboards_per_source,
394
+ sel_indices, sel_keywords)
315
395
  if not ok:
316
396
  failures.append({"source": source.name, "error": "parse-failed"})
317
397
  continue
@@ -324,6 +404,9 @@ def main():
324
404
  spec_path = spec_dir / "spec.json"
325
405
  if status == "skipped" or not spec_path.exists():
326
406
  continue
407
+ # 旧 raw 目录可能来自一次全量解析:按本次 --artboards 收口,避免把未选画板混进数据集
408
+ if not matches_artboard_selector(canvas, sel_indices, sel_keywords):
409
+ continue
327
410
  generatedArtboards += 1
328
411
  if canvas.get("trainingEligible") is False:
329
412
  skippedPopups.append({"source": source.name, "canvas": name, "reason": "manifest-training-ineligible"})
@@ -442,6 +525,7 @@ def main():
442
525
  "parser": str(PARSER.relative_to(REPO_ROOT)),
443
526
  "duplicateThreshold": args.duplicate_threshold,
444
527
  "artboardsPerSource": args.artboards_per_source,
528
+ "artboards": args.artboards or "",
445
529
  "maxCases": args.max_cases,
446
530
  "noArtboardDedup": bool(args.no_artboard_dedup),
447
531
  },