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.
- package/README.md +7 -3
- package/dist/cli.js +4 -4
- package/dist/config.js +7 -7
- package/dist/init.js +15 -4
- package/dist/portGuard.js +2 -2
- package/dist/preview-editor/assets/index-DnXqKrCc.js +307 -0
- package/dist/preview-editor/assets/index-cbTCHp8J.css +1 -0
- package/dist/preview-editor/index.html +2 -2
- package/dist/standalone.js +14 -6
- package/dist/vendored/card-editor/api.js +6 -6
- package/dist/vendored/mcp/client.mjs +53 -3
- package/dist/vendored/mcp/tools.mjs +17 -1
- package/dist/vendored/server/core/ai-chat.js +1443 -271
- package/dist/vendored/server/core/cardFiles.js +271 -39
- package/dist/vendored/server/core/cardImportMeta.js +16 -0
- package/dist/vendored/server/core/chatHistory.js +122 -0
- package/dist/vendored/server/core/index.js +10 -3
- package/dist/vendored/server/core/turnJournal.js +386 -0
- package/dist/vendored/skills/card-skill-trainer/SKILL.md +399 -222
- package/dist/vendored/skills/card-skill-trainer/scripts/build_dataset.py +103 -19
- package/dist/vendored/skills/card-skill-trainer/scripts/diagnose_case.mjs +378 -200
- package/dist/vendored/skills/card-skill-trainer/scripts/lib/runs.mjs +147 -0
- package/dist/vendored/skills/card-skill-trainer/scripts/lib/scoring.mjs +297 -4
- package/dist/vendored/skills/card-skill-trainer/scripts/plan_next_round.mjs +484 -0
- package/dist/vendored/skills/card-skill-trainer/scripts/round_diff.mjs +77 -96
- package/dist/vendored/skills/card-skill-trainer/scripts/run_round.mjs +237 -62
- package/dist/vendored/skills/card-skill-trainer/scripts/score_round.mjs +74 -35
- package/dist/vendored/skills/dsl-card-ai/SKILL.md +15 -2
- package/dist/vendored/skills/dsl-card-generator/SKILL.md +5 -3
- package/dist/vendored/skills/dsl-card-generator/references/generation_pitfalls.md +120 -0
- package/dist/vendored/skills/dsl-card-generator/references/previewer_contract.md +10 -0
- package/dist/vendored/skills/dsl-card-generator/references/quickref.md +15 -1
- package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/canvas-radar.json +2 -2
- package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/scatter-pill-band.json +10 -0
- package/dist/vendored/skills/dsl-card-generator/references/recipes.md +43 -1
- package/dist/vendored/skills/dsl-card-generator/references/rules.md +33 -6
- package/dist/vendored/skills/dsl-card-generator/references/slices_convention.md +12 -5
- package/dist/vendored/skills/dsl-card-generator/scripts/add_component.mjs +5 -10
- package/dist/vendored/skills/dsl-card-generator/scripts/crop_slices.mjs +47 -10
- package/dist/vendored/skills/dsl-card-generator/scripts/expand_ir.mjs +12 -4
- package/dist/vendored/skills/dsl-card-generator/scripts/lib/design_sample.mjs +388 -328
- package/dist/vendored/skills/dsl-card-generator/scripts/lib/slice_pixels.mjs +109 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/render_card.mjs +45 -7
- package/dist/vendored/skills/dsl-card-generator/scripts/route_input.mjs +7 -3
- package/dist/vendored/skills/dsl-card-generator/scripts/spec_audit.mjs +110 -23
- package/dist/vendored/skills/dsl-card-generator/scripts/spec_to_schema_v3.mjs +2954 -127
- package/dist/vendored/skills/dsl-card-generator/scripts/spec_tree.mjs +128 -3
- package/dist/vendored/skills/dsl-card-generator/scripts/validate_schema.py +28 -0
- package/dist/vendored/skills/finmall-card-sync/SKILL.md +1 -1
- package/dist/vendored/skills/finmall-session-navigator/SKILL.md +1 -1
- package/dist/vendored/skills/finmall-session-navigator/scripts/session-manager.mjs +1 -1
- package/dist/vendored/skills/shared/finmall-config.mjs +4 -4
- package/dist/vendored/skills/sketch-spec-to-card/SKILL.md +2 -2
- package/dist/vendored/skills/sketch-spec-to-card/scripts/parse_meaxure.py +29 -2
- package/dist/vendored/tools/finmall-env.mjs +6 -6
- package/package.json +1 -1
- package/dist/preview-editor/assets/index-CQScMhyg.css +0 -1
- package/dist/preview-editor/assets/index-pW1J4vP3.js +0 -276
- package/dist/vendored/skills/card-skill-trainer/baselines/v3-r038n.json +0 -43
- package/dist/vendored/skills/card-skill-trainer/baselines/v3-r039n.json +0 -47
- 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
|
|
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
|
-
|
|
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
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
"--
|
|
77
|
-
|
|
78
|
-
"--
|
|
79
|
-
|
|
80
|
-
"--
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
},
|