mirror of
https://github.com/larksuite/cli.git
synced 2026-07-08 02:00:19 +08:00
复用未失效的 SVG 生成产物
当 generate_svg stage 记录被上游剪掉但既有 receipt、输入 hash 和生成输出仍然 current 时,runner 记录 cache_hit 并复用现有产物。 避免下游 gate 抖动导致不必要的 renderer 重跑,审计文档同步更新 M2 状态。
This commit is contained in:
@@ -107,7 +107,7 @@ Status conventions:
|
||||
|---|---|---|---|
|
||||
| Composite `plan_gate` output path mismatch | Fixed in code; test gap | Align composite `plan_gate` output list with the real `02-plan/strategy-review.json` output instead of nonexistent `06-check/strategy-review.json`. | `svglide_project_runner.py`; needs composite output existence test. |
|
||||
| Composite gate stale ownership | Fixed in code for stale child rerun; broader ownership open | Composite gates now rerun stale child substages instead of failing the parent gate. Broader long-term work remains for richer stale diagnostics and all child-output ownership edge cases. | `svglide_project_runner.py`; `svglide_project_runner_test.py` covers stale legacy child rerun. |
|
||||
| `generate_svg` repeated reruns | Partially fixed in code | `generate_svg` now records the real generation input boundary in `input_hashes` and explicitly excludes downstream quality receipts. Full render-cache reuse by renderer/template source hash is still open. | `svglide_project_runner.py`; `svglide_project_runner_test.py` covers downstream quality-gate isolation and changed-plan invalidation. |
|
||||
| `generate_svg` repeated reruns | Fixed for current receipt reuse; broader renderer dependency hashing open | `generate_svg` now records the real generation input boundary in `input_hashes`, explicitly excludes downstream quality receipts, and reuses a current passed `generate_svg` receipt when a stage record was pruned but generation inputs/outputs are unchanged. | `svglide_project_runner.py`; `svglide_project_runner_test.py` covers downstream quality-gate isolation, changed-plan invalidation, and cache reuse after stage-record pruning. |
|
||||
| Page-family role collapse to `content` | Mitigated for this run; generic guard open | Prefer explicit `canvas_spec.page_role` over weak top-level `page_type`. Ensure selected page-family decks preserve `page_role` / `page_variant_id`. | `svglide_project_runner.py`; current deck roles passed page-family smoke. |
|
||||
| `template_fidelity` used as strict publishing blocker | Fixed in code for current-deck publish; promotion split still needs UI/docs cleanup | If page-family smoke passes and only soft screenshot issues remain above `warn_min`, mark template fidelity as `passed_with_warnings` and write separate `current-deck-visual-integrity` evidence. Quality gate now requires that current-deck evidence for warning-based publish, while template promotion still requires true fidelity pass. | `svglide_project_runner.py`, `svglide_quality_gate.py`; tests in `svglide_project_runner_test.py`, `svglide_quality_gate_test.py`. |
|
||||
| `text_decoration_policy` missing in role consumption | Fixed in code | If `text_style_roles` exist but no explicit decoration policy exists, record renderer default absent policy instead of leaving receipt incomplete. | `beautiful_template_fidelity_check.py`; test in `beautiful_template_fidelity_check_test.py`. |
|
||||
@@ -129,7 +129,7 @@ Status conventions:
|
||||
### Still Open For Long-Term Speed
|
||||
|
||||
- Composite gate child stale ownership is less risky after stale-child rerun support, but richer diagnostics and child-output ownership tests are still needed.
|
||||
- `generate_svg` has a clearer input boundary now; full cache reuse and renderer/template dependency hashing remain open.
|
||||
- `generate_svg` can reuse a current receipt after stage-record pruning; broader renderer/template source dependency hashing remains open.
|
||||
- Preflight needs a broader false-positive fixture suite.
|
||||
- Debug-time audit now separates runner time from between-event gaps; automatic agent bucket capture is still open.
|
||||
- Template promotion fidelity and current deck publish fidelity now have separate receipts for the warning path; remaining work is naming/docs cleanup and any UI/report surfacing.
|
||||
@@ -166,7 +166,9 @@ Green:
|
||||
|
||||
- Store generation input hash set separately from upstream gate receipts.
|
||||
- Let `generate_svg` reuse existing raw/contract/prepared outputs when generation inputs are unchanged.
|
||||
- Completed first slice: `generate_svg` receipt `input_hashes` now records plan, lock, source evidence, source receipt, assets, and asset manifest, while excluding downstream quality receipts.
|
||||
- Completed slices:
|
||||
- `generate_svg` receipt `input_hashes` now records plan, lock, source evidence, source receipt, assets, and asset manifest, while excluding downstream quality receipts.
|
||||
- If the `generate_svg` stage record was pruned but the existing passed receipt and generated outputs are still current, runner records a `cache_hit` and reuses the existing output instead of rerunning generation.
|
||||
|
||||
Validation:
|
||||
|
||||
|
||||
@@ -2617,6 +2617,33 @@ def validate_generator_receipt(project_root: Path, receipt: dict[str, Any]) -> l
|
||||
return issues
|
||||
|
||||
|
||||
def try_reuse_generate_svg_receipt(project_root: Path, state: dict[str, Any], *, started_at: str) -> dict[str, Any] | None:
|
||||
path = receipt_path(project_root, "generate_svg")
|
||||
receipt = read_json_optional(path)
|
||||
if receipt.get("status") != "passed":
|
||||
return None
|
||||
try:
|
||||
require_generated_svg_current(project_root)
|
||||
except RunnerError:
|
||||
return None
|
||||
cached = dict(receipt)
|
||||
cached["cache_hit"] = True
|
||||
cached["cache_reason"] = "generate inputs and generated visual outputs are current"
|
||||
record_stage(state, "generate_svg", "passed", path)
|
||||
record_timing_event(
|
||||
state,
|
||||
stage="generate_svg",
|
||||
status="passed",
|
||||
started_at=started_at,
|
||||
ended_at=now_iso(),
|
||||
wall_time_seconds=0.0,
|
||||
cache_hit=True,
|
||||
)
|
||||
write_state(project_root, state)
|
||||
write_timing_report(project_root, state)
|
||||
return cached
|
||||
|
||||
|
||||
def run_generate_svg_stage(
|
||||
project_root: Path,
|
||||
state: dict[str, Any],
|
||||
@@ -2627,6 +2654,9 @@ def run_generate_svg_stage(
|
||||
require_source_current(project_root)
|
||||
require_assets_current(project_root)
|
||||
started_at = now_iso()
|
||||
cached = try_reuse_generate_svg_receipt(project_root, state, started_at=started_at)
|
||||
if cached:
|
||||
return cached
|
||||
generation_mode = plan_generation_mode(project_root)
|
||||
command = svg_generator_command(project_root)
|
||||
artboard_result: dict[str, Any] = {}
|
||||
|
||||
@@ -2175,6 +2175,30 @@ class SVGlideProjectRunnerTest(unittest.TestCase):
|
||||
self.assertNotIn("06-check/quality-gate.json", receipt["input_hashes"])
|
||||
self.assertTrue((project_root / "04-svg/page-001.receipt.json").exists())
|
||||
|
||||
def test_generate_svg_reuses_current_receipt_after_stage_record_pruned(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
plan_root = Path(tmpdir) / ".lark-slides/plan"
|
||||
result = runner.init_project("smoke", "Smoke", plan_root=plan_root)
|
||||
project_root = Path(result["project_root"])
|
||||
self.write_plan(project_root)
|
||||
self.run_source(project_root)
|
||||
runner.run_stage(project_root, "package-check")
|
||||
runner.run_stage(project_root, "assets")
|
||||
for page in range(1, 4):
|
||||
(project_root / f"04-svg/page-{page:03d}.svg").write_text("<svg></svg>", encoding="utf-8")
|
||||
runner.run_stage(project_root, "generate-svg")
|
||||
state = runner.load_state(project_root)
|
||||
state["stages"].pop("generate_svg")
|
||||
runner.write_state(project_root, state)
|
||||
|
||||
receipt = runner.run_generate_svg_stage(project_root, runner.load_state(project_root))
|
||||
|
||||
self.assertEqual(receipt["status"], "passed")
|
||||
self.assertTrue(receipt["cache_hit"])
|
||||
updated = runner.load_state(project_root)
|
||||
self.assertEqual(updated["stages"]["generate_svg"]["status"], "passed")
|
||||
self.assertTrue(updated["timing_events"][-1]["cache_hit"])
|
||||
|
||||
def test_generate_svg_injects_file_backed_cover_asset(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
plan_root = Path(tmpdir) / ".lark-slides/plan"
|
||||
|
||||
Reference in New Issue
Block a user