claude-dev-env 2.8.0 → 2.10.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 (323) hide show
  1. package/CLAUDE.md +8 -2
  2. package/_shared/pr-loop/CLAUDE.md +1 -1
  3. package/_shared/pr-loop/audit-contract.md +17 -6
  4. package/_shared/pr-loop/audit-reply-template.md +4 -4
  5. package/_shared/pr-loop/code-rules-gate.md +3 -5
  6. package/_shared/pr-loop/fix-protocol.md +2 -3
  7. package/_shared/pr-loop/gh-payloads.md +1 -1
  8. package/_shared/pr-loop/scripts/CLAUDE.md +1 -1
  9. package/_shared/pr-loop/scripts/README.md +1 -1
  10. package/_shared/pr-loop/scripts/code_rules_gate.py +2 -0
  11. package/_shared/pr-loop/scripts/code_rules_gate_parts/gate_running.py +16 -1
  12. package/_shared/pr-loop/scripts/code_rules_gate_parts/git_blob_readers.py +11 -5
  13. package/_shared/pr-loop/scripts/preflight.py +9 -4
  14. package/_shared/pr-loop/scripts/reviews_disabled.py +50 -22
  15. package/_shared/pr-loop/scripts/tests/conftest.py +20 -0
  16. package/_shared/pr-loop/scripts/tests/test_claude_permissions_common.py +6 -6
  17. package/_shared/pr-loop/scripts/tests/test_reviews_disabled.py +50 -6
  18. package/_shared/pr-loop/scripts/tests/test_revoke_project_claude_permissions.py +1 -1
  19. package/_shared/pr-loop/state-schema.md +5 -14
  20. package/agents/CLAUDE.md +2 -2
  21. package/agents/clean-coder.md +62 -562
  22. package/agents/code-quality-agent.md +10 -2
  23. package/agents/code-verifier.md +1 -1
  24. package/agents/test_agent_frontmatter.py +58 -40
  25. package/audit-rubrics/CLAUDE.md +2 -1
  26. package/audit-rubrics/audit-categories.json +704 -0
  27. package/audit-rubrics/prompts/category-i-concurrency.md +1 -1
  28. package/bin/CLAUDE.md +16 -5
  29. package/bin/ever-shipped-skills.mjs +2 -0
  30. package/bin/install-plan.mjs +402 -0
  31. package/bin/install-transaction.mjs +455 -0
  32. package/bin/install.mjs +593 -147
  33. package/bin/install.plan.test.mjs +194 -0
  34. package/bin/install.profile-root.test.mjs +154 -0
  35. package/bin/install.profiles.test.mjs +253 -0
  36. package/bin/install.settings-defaults.test.mjs +200 -0
  37. package/bin/install.transaction.test.mjs +400 -0
  38. package/bin/install.uninstall-transaction.test.mjs +418 -0
  39. package/bin/merge_managed_permissions.mjs +130 -0
  40. package/bin/resolve-install-root.mjs +181 -0
  41. package/bin/select-install-targets.mjs +401 -0
  42. package/commands/CLAUDE.md +0 -2
  43. package/docs/CODE_RULES.md +4 -2
  44. package/docs/references/CLAUDE.md +3 -2
  45. package/docs/references/advisor-tool.md +44 -6
  46. package/docs/references/prose-style-enforcement.md +25 -0
  47. package/docs/references/team-advisor-skill.md +14 -8
  48. package/hooks/blocking/CLAUDE.md +6 -6
  49. package/hooks/blocking/_path_setup.py +9 -5
  50. package/hooks/blocking/code_rules_docstrings.py +124 -30
  51. package/hooks/blocking/code_rules_enforcer.py +161 -16
  52. package/hooks/blocking/code_rules_shared.py +40 -23
  53. package/hooks/blocking/config/CLAUDE.md +3 -5
  54. package/hooks/blocking/config/prose_style_enforcement_constants.py +38 -0
  55. package/hooks/blocking/config/test_prose_style_enforcement_constants.py +45 -0
  56. package/hooks/blocking/eli11_reply_enforcer.py +70 -113
  57. package/hooks/blocking/hedging_language_blocker.py +103 -20
  58. package/hooks/blocking/hook_prose_detector_consistency.py +6 -0
  59. package/hooks/blocking/intent_only_ending_blocker.py +6 -0
  60. package/hooks/blocking/plain_language_blocker.py +139 -20
  61. package/hooks/blocking/pre_tool_use_dispatcher.py +102 -20
  62. package/hooks/blocking/state_description_blocker.py +7 -1
  63. package/hooks/blocking/tdd_enforcer.py +8 -0
  64. package/hooks/blocking/test__path_setup.py +28 -0
  65. package/hooks/blocking/test_code_rules_enforcer_agent_home_tooling.py +99 -0
  66. package/hooks/blocking/test_code_rules_enforcer_docstring_args_span_scope.py +232 -10
  67. package/hooks/blocking/test_code_rules_enforcer_ephemeral.py +1 -1
  68. package/hooks/blocking/test_code_rules_enforcer_join_separator_magic.py +41 -0
  69. package/hooks/blocking/test_code_rules_enforcer_string_magic.py +98 -0
  70. package/hooks/blocking/test_eli11_reply_enforcer.py +98 -165
  71. package/hooks/blocking/test_hedging_language_blocker.py +120 -1
  72. package/hooks/blocking/test_hook_prose_detector_consistency.py +28 -8
  73. package/hooks/blocking/test_intent_only_ending_blocker.py +27 -2
  74. package/hooks/blocking/test_package_inventory_stale_blocker.py +11 -4
  75. package/hooks/blocking/test_plain_language_blocker.py +129 -19
  76. package/hooks/blocking/test_plain_language_blocker_allowlist.py +70 -26
  77. package/hooks/blocking/test_pre_tool_use_dispatcher.py +99 -26
  78. package/hooks/blocking/test_pre_tool_use_dispatcher_native.py +87 -50
  79. package/hooks/blocking/test_state_description_blocker.py +45 -2
  80. package/hooks/blocking/test_stop_dispatcher.py +11 -7
  81. package/hooks/blocking/test_volatile_path_in_post_blocker.py +12 -12
  82. package/hooks/blocking/volatile_path_in_post_blocker.py +2 -2
  83. package/hooks/hooks.json +15 -0
  84. package/hooks/hooks_constants/CLAUDE.md +14 -3
  85. package/hooks/hooks_constants/ask_user_question_shape.py +281 -0
  86. package/hooks/hooks_constants/code_rules_enforcer_constants.py +2 -1
  87. package/hooks/hooks_constants/code_rules_path_utils_constants.py +1 -0
  88. package/hooks/hooks_constants/eli11_reply_enforcer_constants.py +5 -12
  89. package/hooks/hooks_constants/hedging_uncertainty_constants.py +42 -0
  90. package/hooks/hooks_constants/issue_tracker_session_starter_constants.py +23 -0
  91. package/hooks/hooks_constants/orchestrator_auto_starter_constants.py +23 -0
  92. package/hooks/hooks_constants/piped_pytest_blocker_constants.py +4 -1
  93. package/hooks/hooks_constants/plain_language_blocker_constants.py +4 -1
  94. package/hooks/hooks_constants/pre_tool_use_dispatcher_constants.py +6 -0
  95. package/hooks/hooks_constants/project_paths_reader.py +31 -4
  96. package/hooks/hooks_constants/prose_matcher_precision_constants.py +40 -0
  97. package/hooks/hooks_constants/pytest_invocation.py +354 -0
  98. package/hooks/hooks_constants/session_start_injector.py +163 -0
  99. package/hooks/hooks_constants/session_start_injector_constants.py +46 -0
  100. package/hooks/hooks_constants/shell_command_pipeline.py +397 -0
  101. package/hooks/hooks_constants/shell_command_segments.py +5 -0
  102. package/hooks/hooks_constants/test_ask_user_question_shape.py +167 -0
  103. package/hooks/hooks_constants/test_project_paths_reader.py +29 -0
  104. package/hooks/hooks_constants/test_prose_metrics_parity.py +8 -0
  105. package/hooks/hooks_constants/test_pytest_invocation.py +130 -0
  106. package/hooks/hooks_constants/test_session_start_injector.py +168 -0
  107. package/hooks/hooks_constants/test_shell_command_pipeline.py +135 -0
  108. package/hooks/hooks_constants/volatile_path_in_post_blocker_constants.py +1 -1
  109. package/hooks/hooks_constants/working_style_prompt_constants.py +30 -0
  110. package/hooks/observability/CLAUDE.md +2 -0
  111. package/hooks/observability/prose_matcher_advisory.py +237 -0
  112. package/hooks/observability/test_prose_matcher_advisory.py +143 -0
  113. package/hooks/session/CLAUDE.md +9 -1
  114. package/hooks/session/_path_setup.py +13 -0
  115. package/hooks/session/issue_tracker_session_starter.py +135 -0
  116. package/hooks/session/orchestrator_auto_starter.py +100 -0
  117. package/hooks/session/test__path_setup.py +28 -0
  118. package/hooks/session/test_issue_tracker_session_starter.py +104 -0
  119. package/hooks/session/test_orchestrator_auto_starter.py +99 -0
  120. package/hooks/session/test_working_style_prompt.py +47 -0
  121. package/hooks/session/untracked_repo_detector.py +1 -24
  122. package/hooks/session/working_style_prompt.py +36 -0
  123. package/hooks/validators/_path_setup.py +19 -0
  124. package/hooks/validators/run_all_validators.py +8 -13
  125. package/installable-surfaces.manifest.json +21 -0
  126. package/output-styles/CLAUDE.md +15 -0
  127. package/package.json +5 -2
  128. package/rules/CLAUDE.md +1 -0
  129. package/rules/code-standards.md +33 -7
  130. package/rules/durable-post-artifacts.md +2 -2
  131. package/rules/eli11-replies.md +7 -2
  132. package/rules/hedging-claims.md +4 -2
  133. package/rules/long-horizon-autonomy.md +3 -1
  134. package/rules/opus5-communication-contract.md +45 -0
  135. package/rules/plain-language.md +2 -2
  136. package/rules/research-mode.md +1 -1
  137. package/scripts/CLAUDE.md +13 -2
  138. package/scripts/Sync-RepoMain.ps1 +215 -0
  139. package/scripts/active_capability_references.py +218 -0
  140. package/scripts/ci/windows-installer-lifecycle.ps1 +78 -0
  141. package/scripts/claude_chain_runner.py +394 -6
  142. package/scripts/claude_chain_usage.py +1 -1
  143. package/scripts/codex_compat_materializer.py +105 -85
  144. package/scripts/dev_env_scripts_constants/CLAUDE.md +3 -1
  145. package/scripts/dev_env_scripts_constants/active_capability_constants.py +46 -0
  146. package/scripts/dev_env_scripts_constants/claude_chain_constants.py +74 -0
  147. package/scripts/dev_env_scripts_constants/grok_run_ledger_constants.py +50 -0
  148. package/scripts/dev_env_scripts_constants/grok_worker_constants.py +104 -0
  149. package/scripts/dev_env_scripts_constants/verify_installable_package_constants.py +116 -0
  150. package/scripts/grok_patch_artifacts.py +123 -0
  151. package/scripts/grok_run_ledger.py +318 -0
  152. package/scripts/profile-isolation-launchers/config/mcp-bundles.json +25 -0
  153. package/scripts/profile-isolation-launchers/config/profile-isolation-constants.mjs +60 -0
  154. package/scripts/profile-isolation-launchers/config/profiles.manifest.json +54 -0
  155. package/scripts/profile-isolation-launchers/config/shared-allowlist.json +64 -0
  156. package/scripts/profile-isolation-launchers/launcher-runtime.mjs +180 -0
  157. package/scripts/profile-isolation-launchers/lib/profile-manifest.mjs +288 -0
  158. package/scripts/profile-isolation-launchers/mcp-bundles.mjs +275 -0
  159. package/scripts/profile-isolation-launchers/profile-isolation-contract.test.mjs +221 -0
  160. package/scripts/profile-isolation-launchers/tests/launcher-runtime.test.mjs +108 -0
  161. package/scripts/profile-isolation-launchers/tests/mcp-bundles.test.mjs +147 -0
  162. package/scripts/profile-isolation-launchers/tests/shortcut-contract.test.ps1 +102 -0
  163. package/scripts/profile-isolation-launchers/tests/version-compatibility.test.mjs +210 -0
  164. package/scripts/profile-isolation-launchers/version-compatibility.mjs +299 -0
  165. package/scripts/profile-isolation-launchers/windows/shortcut-inventory.ps1 +127 -0
  166. package/scripts/profile-isolation-launchers/windows/shortcut-manifest.json +51 -0
  167. package/scripts/profile-isolation-launchers/windows/shortcut-reconcile.ps1 +77 -0
  168. package/scripts/spawn_grok_batch.py +556 -9
  169. package/scripts/test_active_capability_references.py +108 -0
  170. package/scripts/test_claude_chain_runner.py +414 -82
  171. package/scripts/test_claude_chain_usage.py +12 -12
  172. package/scripts/test_grok_patch_artifacts.py +82 -0
  173. package/scripts/test_grok_run_ledger.py +116 -0
  174. package/scripts/test_resolve_worker_spawn.py +2 -2
  175. package/scripts/test_spawn_grok_batch.py +295 -0
  176. package/scripts/test_verify_installable_package.py +208 -0
  177. package/scripts/tests/test_codex_compat_materializer.py +33 -0
  178. package/scripts/verify_installable_package.py +612 -0
  179. package/settings.json +10 -0
  180. package/skills/CLAUDE.md +6 -2
  181. package/skills/_shared/CLAUDE.md +37 -4
  182. package/skills/_shared/advisor/CLAUDE.md +9 -0
  183. package/skills/_shared/advisor/advisor-protocol.md +5 -0
  184. package/skills/_shared/advisor/scripts/README.md +9 -0
  185. package/skills/_shared/end-of-run-gotcha-recommendations.md +156 -0
  186. package/skills/_shared/pr-loop/CLAUDE.md +18 -1
  187. package/skills/_shared/pr-loop/audit-contract.md +5 -0
  188. package/skills/_shared/pr-loop/audit-reply-template.md +5 -0
  189. package/skills/_shared/pr-loop/code-rules-gate.md +5 -0
  190. package/skills/_shared/pr-loop/fix-protocol.md +5 -0
  191. package/skills/_shared/pr-loop/gh-payloads.md +5 -0
  192. package/skills/_shared/pr-loop/post-audit-thread-contract.md +5 -0
  193. package/skills/_shared/pr-loop/precatch-rubric.md +5 -0
  194. package/skills/_shared/pr-loop/scripts/CLAUDE.md +9 -1
  195. package/skills/_shared/pr-loop/scripts/RUNTIME_SCRIPTS.md +29 -0
  196. package/skills/_shared/pr-loop/scripts/audit_category_schema.py +355 -0
  197. package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/CLAUDE.md +1 -0
  198. package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/audit_category_schema_constants.py +32 -0
  199. package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/path_resolver_constants.py +7 -19
  200. package/skills/_shared/pr-loop/scripts/test_audit_category_schema.py +94 -0
  201. package/skills/_shared/pr-loop/scripts/test_build_audit_prompt.py +21 -0
  202. package/skills/_shared/pr-loop/state-schema.md +5 -0
  203. package/skills/_shared/pr-loop/worker-spawn.md +5 -0
  204. package/skills/autoconverge/reference/convergence.md +2 -1
  205. package/skills/autoconverge/reference/stop-conditions.md +5 -3
  206. package/skills/beat-sheet/SKILL.md +54 -0
  207. package/skills/beat-sheet/reference/visual-beats.md +29 -0
  208. package/skills/bugteam/CONSTRAINTS.md +4 -4
  209. package/skills/bugteam/EXAMPLES.md +1 -1
  210. package/skills/bugteam/reference/README.md +1 -1
  211. package/skills/e-code-review/SKILL.md +29 -3
  212. package/skills/e-code-review/reference/effort-evaluation.md +35 -0
  213. package/skills/e-code-review/reference/medium.md +15 -4
  214. package/skills/e-code-review/reference/runner-selection.md +40 -0
  215. package/skills/e-code-review/scripts/config/e_code_review_effort_constants/__init__.py +41 -0
  216. package/skills/e-code-review/scripts/config/e_code_review_effort_constants/effort_constants.py +40 -0
  217. package/skills/e-code-review/scripts/e_code_review_scripts_constants/__init__.py +1 -0
  218. package/skills/e-code-review/scripts/e_code_review_scripts_constants/finding_pipeline_constants.py +49 -0
  219. package/skills/e-code-review/scripts/e_code_review_scripts_constants/grok_code_review_constants.py +55 -0
  220. package/skills/e-code-review/scripts/effort_defaults_evidence.json +186 -0
  221. package/skills/e-code-review/scripts/effort_evaluation.py +362 -0
  222. package/skills/e-code-review/scripts/finding_pipeline.py +140 -0
  223. package/skills/e-code-review/scripts/fixtures/demanding.json +26 -0
  224. package/skills/e-code-review/scripts/fixtures/easy.json +14 -0
  225. package/skills/e-code-review/scripts/fixtures/medium.json +20 -0
  226. package/skills/e-code-review/scripts/grok_code_review.py +230 -0
  227. package/skills/e-code-review/scripts/test_effort_evaluation.py +180 -0
  228. package/skills/e-code-review/scripts/test_finding_pipeline.py +197 -0
  229. package/skills/e-code-review/scripts/test_grok_code_review.py +289 -0
  230. package/skills/grok-spawn/SKILL.md +5 -0
  231. package/skills/orchestrator/SKILL.md +5 -0
  232. package/skills/plan-to-pr/scripts/create_packet.py +4 -4
  233. package/skills/plan-to-pr/scripts/load_skill_constants.py +41 -0
  234. package/skills/plan-to-pr/scripts/validate_packet.py +4 -4
  235. package/skills/plan-to-pr/scripts/validate_protocol.py +4 -1
  236. package/skills/plan-to-pr/scripts/validate_run.py +4 -1
  237. package/skills/pr-converge/scripts/check_convergence.py +21 -19
  238. package/skills/pr-converge/scripts/check_convergence_availability.py +50 -7
  239. package/skills/pr-converge/scripts/conftest.py +35 -0
  240. package/skills/pr-converge/scripts/test_check_convergence_availability.py +65 -0
  241. package/skills/pr-converge/scripts/test_check_convergence_codex.py +11 -1
  242. package/skills/pr-converge/scripts/test_check_convergence_contract.py +9 -2
  243. package/skills/pr-loop-cloud-transport/SKILL.md +1 -1
  244. package/skills/rebase/SKILL.md +15 -3
  245. package/skills/reviewer-gates/SKILL.md +2 -2
  246. package/skills/show/SKILL.md +51 -0
  247. package/skills/show/references/accessibility.md +7 -0
  248. package/skills/show/references/art.md +3 -0
  249. package/skills/show/references/charts.md +3 -0
  250. package/skills/show/references/core-design.md +14 -0
  251. package/skills/show/references/erds.md +3 -0
  252. package/skills/show/references/flowcharts.md +3 -0
  253. package/skills/show/references/host-and-html.md +3 -0
  254. package/skills/show/references/illustrative-diagrams.md +10 -0
  255. package/skills/show/references/interaction.md +3 -0
  256. package/skills/show/references/mockups.md +3 -0
  257. package/skills/show/references/quality-gates.md +7 -0
  258. package/skills/show/references/structural-diagrams.md +3 -0
  259. package/skills/show/references/subject-inventory.md +21 -0
  260. package/skills/show/references/svg-contract.md +22 -0
  261. package/skills/show/routing.yaml +30 -0
  262. package/skills/show/samples/pr1262-v2.svg +222 -0
  263. package/skills/show/scripts/README.md +6 -0
  264. package/skills/show/scripts/validate-artifact.py +91 -0
  265. package/skills/show/scripts/validate-package.py +18 -0
  266. package/skills/show/templates/html-widget.html +4 -0
  267. package/skills/show/templates/svg-base.svg +19 -0
  268. package/skills/show/tests/fixtures/css-var.svg +6 -0
  269. package/skills/show/tests/fixtures/dead-ref.svg +7 -0
  270. package/skills/show/tests/fixtures/filled-glyph.svg +8 -0
  271. package/skills/show/tests/fixtures/inherited-fill.svg +18 -0
  272. package/skills/show/tests/fixtures/invalid.svg +1 -0
  273. package/skills/show/tests/fixtures/large-canvas.svg +21 -0
  274. package/skills/show/tests/fixtures/unfilled-connector.svg +15 -0
  275. package/skills/show/tests/fixtures/valid.html +1 -0
  276. package/skills/show/tests/test_validate-artifact.py +74 -0
  277. package/skills/show/tests/test_validators.py +59 -0
  278. package/skills/show/workflows/create-visual.md +13 -0
  279. package/skills/show/workflows/review-visual.md +20 -0
  280. package/skills/split-pr/SKILL.md +85 -0
  281. package/skills/split-pr/reference/path-layers.md +16 -0
  282. package/skills/split-pr/reference/proposal-format.md +15 -0
  283. package/skills/split-pr/reference/split-further-loop.md +10 -0
  284. package/skills/split-pr/reference/splitting-principles.md +26 -0
  285. package/skills/split-pr/scripts/analyze_pr.py +279 -0
  286. package/skills/split-pr/scripts/categorize_files.py +106 -0
  287. package/skills/split-pr/scripts/config/__init__.py +1 -0
  288. package/skills/split-pr/scripts/config/dependency_constants.py +14 -0
  289. package/skills/split-pr/scripts/config/git_operations_constants.py +36 -0
  290. package/skills/split-pr/scripts/config/packing_constants.py +61 -0
  291. package/skills/split-pr/scripts/config/plan_constants.py +49 -0
  292. package/skills/split-pr/scripts/config/split_pr_constants.py +110 -0
  293. package/skills/split-pr/scripts/execute_split_slices.py +82 -0
  294. package/skills/split-pr/scripts/pack_files_into_slices.py +212 -0
  295. package/skills/split-pr/scripts/split_pr_dependency_graph.py +70 -0
  296. package/skills/split-pr/scripts/split_pr_git_operations.py +184 -0
  297. package/skills/split-pr/scripts/split_pr_layer_order.py +58 -0
  298. package/skills/split-pr/scripts/split_pr_paginate.py +119 -0
  299. package/skills/split-pr/scripts/split_pr_process_runner.py +52 -0
  300. package/skills/split-pr/scripts/split_pr_script_types.py +126 -0
  301. package/skills/split-pr/scripts/split_pr_title.py +41 -0
  302. package/skills/split-pr/scripts/test_analyze_pr.py +228 -0
  303. package/skills/split-pr/scripts/test_categorize_files.py +55 -0
  304. package/skills/split-pr/scripts/test_categorize_files_packing.py +59 -0
  305. package/skills/split-pr/scripts/test_execute_split_slices.py +99 -0
  306. package/skills/split-pr/scripts/test_split_pr_dependency_graph.py +47 -0
  307. package/skills/split-pr/scripts/test_split_pr_git_operations.py +125 -0
  308. package/skills/split-pr/scripts/test_split_pr_layer_order.py +36 -0
  309. package/skills/split-pr/scripts/test_split_pr_paginate.py +65 -0
  310. package/skills/split-pr/scripts/test_split_pr_script_types.py +73 -0
  311. package/skills/split-pr/scripts/test_split_pr_title.py +28 -0
  312. package/skills/split-pr/scripts/test_verify_dependency_graph.py +46 -0
  313. package/skills/split-pr/scripts/test_verify_plan.py +56 -0
  314. package/skills/split-pr/scripts/test_verify_plan_contract.py +50 -0
  315. package/skills/split-pr/scripts/test_verify_plan_path_normalization.py +45 -0
  316. package/skills/split-pr/scripts/verify_dependency_graph.py +111 -0
  317. package/skills/split-pr/scripts/verify_plan.py +139 -0
  318. package/skills/task-build/reference/tool-routing.md +3 -0
  319. package/skills/team-advisor/SKILL.md +23 -44
  320. package/system-prompts/software-engineer.xml +17 -5
  321. package/commands/initialize.md +0 -90
  322. package/commands/stubcheck.md +0 -88
  323. package/skills/test_markdown_link_integrity.py +0 -107
@@ -0,0 +1,222 @@
1
+ <svg viewBox="0 0 1340 1680" xmlns="http://www.w3.org/2000/svg" width="1340" height="1680" role="img">
2
+ <title>PR 1262: unreachable checkpoint-skip branches removed</title>
3
+ <desc>Three panels. One: submit() stamps not_started, a value absent from workflow_order, so every should_skip_checkpoint reading is False and all five skip outlets are dead ends. Two: the four valves, the review bypass, and the LIVE_WALLPAPER spur are deleted; the checkpoint stamps remain on the straight pipe. Three: real resume is a product-level sieve in the orchestrator that turns THEME, WALLPAPER into WALLPAPER.</desc>
4
+ <defs>
5
+ <marker id="aRed" markerUnits="userSpaceOnUse" markerWidth="16" markerHeight="14" refX="15" refY="7" orient="auto">
6
+ <path d="M0,0 L16,7 L0,14 Z" fill="#C0392B"/>
7
+ </marker>
8
+ <marker id="aGrn" markerUnits="userSpaceOnUse" markerWidth="16" markerHeight="14" refX="15" refY="7" orient="auto">
9
+ <path d="M0,0 L16,7 L0,14 Z" fill="#1E8449"/>
10
+ </marker>
11
+ <marker id="aAmb" markerUnits="userSpaceOnUse" markerWidth="16" markerHeight="14" refX="15" refY="7" orient="auto">
12
+ <path d="M0,0 L16,7 L0,14 Z" fill="#B9770E"/>
13
+ </marker>
14
+ <marker id="aGry" markerUnits="userSpaceOnUse" markerWidth="14" markerHeight="12" refX="13" refY="6" orient="auto">
15
+ <path d="M0,0 L14,6 L0,12 Z" fill="#7A7A7A"/>
16
+ </marker>
17
+ </defs>
18
+
19
+ <rect x="0" y="0" width="1340" height="1680" fill="#FAF7EF"/>
20
+
21
+ <!-- ================= PANEL 1 ================= -->
22
+ <text x="60" y="56" font-family="Helvetica,Arial,sans-serif" font-size="30" font-weight="bold" fill="#C0392B">1 · TODAY — every gate reads a blank stamp</text>
23
+
24
+ <rect x="58" y="94" width="286" height="230" rx="12" fill="#EDEAE1" stroke="#3A3A3A" stroke-width="3"/>
25
+ <text x="201" y="128" text-anchor="middle" font-family="Helvetica,Arial,sans-serif" font-size="23" font-weight="bold" fill="#1A1A1A">submit()</text>
26
+ <rect x="175" y="144" width="52" height="32" fill="#8A8A8A" stroke="#3A3A3A" stroke-width="3"/>
27
+ <rect x="191" y="176" width="20" height="24" fill="#8A8A8A" stroke="#3A3A3A" stroke-width="3"/>
28
+ <rect x="147" y="200" width="108" height="26" fill="#C0392B" stroke="#7B241C" stroke-width="3"/>
29
+ <rect x="96" y="248" width="212" height="52" rx="6" fill="#FFFFFF" stroke="#C0392B" stroke-width="3"/>
30
+ <text x="202" y="281" text-anchor="middle" font-family="Menlo,Consolas,monospace" font-size="22" fill="#C0392B">not_started</text>
31
+ <text x="201" y="346" text-anchor="middle" font-family="Menlo,Consolas,monospace" font-size="17" fill="#6E6E6E">submission_service.py:173</text>
32
+
33
+ <line x1="348" y1="274" x2="404" y2="274" stroke="#C0392B" stroke-width="6" marker-end="url(#aRed)"/>
34
+
35
+ <rect x="416" y="94" width="308" height="268" rx="12" fill="#FFFFFF" stroke="#3A3A3A" stroke-width="3"/>
36
+ <text x="570" y="124" text-anchor="middle" font-family="Menlo,Consolas,monospace" font-size="21" font-weight="bold" fill="#1A1A1A">workflow_order()</text>
37
+ <rect x="438" y="136" width="264" height="32" rx="5" fill="#F2EFE6" stroke="#9A9A9A" stroke-width="2"/>
38
+ <text x="570" y="159" text-anchor="middle" font-family="Menlo,Consolas,monospace" font-size="20" fill="#3A3A3A">binary_apks_uploaded</text>
39
+ <rect x="438" y="172" width="264" height="32" rx="5" fill="#F2EFE6" stroke="#9A9A9A" stroke-width="2"/>
40
+ <text x="570" y="195" text-anchor="middle" font-family="Menlo,Consolas,monospace" font-size="20" fill="#3A3A3A">app_info_saved</text>
41
+ <rect x="438" y="208" width="264" height="32" rx="5" fill="#F2EFE6" stroke="#9A9A9A" stroke-width="2"/>
42
+ <text x="570" y="231" text-anchor="middle" font-family="Menlo,Consolas,monospace" font-size="20" fill="#3A3A3A">country_price_saved</text>
43
+ <rect x="438" y="244" width="264" height="32" rx="5" fill="#F2EFE6" stroke="#9A9A9A" stroke-width="2"/>
44
+ <text x="570" y="267" text-anchor="middle" font-family="Menlo,Consolas,monospace" font-size="20" fill="#3A3A3A">publication_saved</text>
45
+ <rect x="438" y="280" width="264" height="32" rx="5" fill="#F2EFE6" stroke="#9A9A9A" stroke-width="2"/>
46
+ <text x="570" y="303" text-anchor="middle" font-family="Menlo,Consolas,monospace" font-size="20" fill="#3A3A3A">review_saved</text>
47
+ <rect x="438" y="316" width="264" height="32" rx="5" fill="#F2EFE6" stroke="#9A9A9A" stroke-width="2"/>
48
+ <text x="570" y="339" text-anchor="middle" font-family="Menlo,Consolas,monospace" font-size="20" fill="#3A3A3A">submitted_for_review</text>
49
+
50
+ <rect x="740" y="130" width="152" height="40" rx="6" fill="#FFFFFF" stroke="#C0392B" stroke-width="3" stroke-dasharray="7 5"/>
51
+ <text x="816" y="158" text-anchor="middle" font-family="Menlo,Consolas,monospace" font-size="19" fill="#C0392B">not_started</text>
52
+ <rect x="740" y="214" width="152" height="40" rx="6" fill="#FFFFFF" stroke="#C0392B" stroke-width="3" stroke-dasharray="7 5" opacity="0.5"/>
53
+ <text x="816" y="242" text-anchor="middle" font-family="Menlo,Consolas,monospace" font-size="19" fill="#C0392B" opacity="0.5">not_started</text>
54
+ <rect x="740" y="298" width="152" height="40" rx="6" fill="#FFFFFF" stroke="#C0392B" stroke-width="3" stroke-dasharray="7 5" opacity="0.26"/>
55
+ <text x="816" y="326" text-anchor="middle" font-family="Menlo,Consolas,monospace" font-size="19" fill="#C0392B" opacity="0.26">not_started</text>
56
+ <line x1="816" y1="174" x2="816" y2="356" stroke="#C0392B" stroke-width="5" stroke-dasharray="10 8" marker-end="url(#aRed)"/>
57
+ <text x="816" y="394" text-anchor="middle" font-family="Menlo,Consolas,monospace" font-size="28" font-weight="bold" fill="#C0392B">False</text>
58
+
59
+ <text x="944" y="196" font-family="Menlo,Consolas,monospace" font-size="20" fill="#C0392B">no slot matches</text>
60
+ <text x="944" y="222" font-family="Menlo,Consolas,monospace" font-size="20" fill="#C0392B">not_started</text>
61
+ <text x="944" y="254" font-family="Menlo,Consolas,monospace" font-size="17" fill="#6E6E6E">checkpoint.py · workflow_order</text>
62
+ <text x="944" y="276" font-family="Menlo,Consolas,monospace" font-size="17" fill="#6E6E6E">excludes NOT_STARTED</text>
63
+ <text x="944" y="326" font-family="Menlo,Consolas,monospace" font-size="20" fill="#1A1A1A">should_skip_checkpoint</text>
64
+ <text x="944" y="350" font-family="Menlo,Consolas,monospace" font-size="17" fill="#6E6E6E">submission_checkpoints.py:47</text>
65
+
66
+ <line x1="816" y1="402" x2="816" y2="426" stroke="#C0392B" stroke-width="5"/>
67
+ <line x1="210" y1="426" x2="1170" y2="426" stroke="#C0392B" stroke-width="5"/>
68
+
69
+ <rect x="86" y="480" width="1204" height="56" fill="#DFF0E4" stroke="#1E8449" stroke-width="4"/>
70
+ <line x1="86" y1="508" x2="1312" y2="508" stroke="#1E8449" stroke-width="8" marker-end="url(#aGrn)"/>
71
+
72
+ <g>
73
+ <line x1="210" y1="426" x2="210" y2="460" stroke="#C0392B" stroke-width="5" marker-end="url(#aRed)"/>
74
+ <rect x="166" y="462" width="88" height="92" rx="8" fill="#FFFFFF" stroke="#C0392B" stroke-width="3"/>
75
+ <circle cx="210" cy="508" r="16" fill="#DFF0E4" stroke="#1E8449" stroke-width="4"/>
76
+ <line x1="210" y1="554" x2="210" y2="584" stroke="#C0392B" stroke-width="5" stroke-dasharray="8 6"/>
77
+ <rect x="176" y="584" width="68" height="30" fill="#F6DDD9" stroke="#C0392B" stroke-width="3"/>
78
+ <line x1="188" y1="590" x2="232" y2="608" stroke="#C0392B" stroke-width="4"/>
79
+ <line x1="232" y1="590" x2="188" y2="608" stroke="#C0392B" stroke-width="4"/>
80
+ <text x="210" y="648" text-anchor="middle" font-family="Menlo,Consolas,monospace" font-size="18" fill="#1A1A1A">binary_apks_uploaded</text>
81
+ <text x="210" y="672" text-anchor="middle" font-family="Menlo,Consolas,monospace" font-size="16" fill="#6E6E6E">pipeline.py:114</text>
82
+ </g>
83
+ <g>
84
+ <line x1="450" y1="426" x2="450" y2="460" stroke="#C0392B" stroke-width="5" marker-end="url(#aRed)"/>
85
+ <rect x="406" y="462" width="88" height="92" rx="8" fill="#FFFFFF" stroke="#C0392B" stroke-width="3"/>
86
+ <circle cx="450" cy="508" r="16" fill="#DFF0E4" stroke="#1E8449" stroke-width="4"/>
87
+ <line x1="450" y1="554" x2="450" y2="584" stroke="#C0392B" stroke-width="5" stroke-dasharray="8 6"/>
88
+ <rect x="416" y="584" width="68" height="30" fill="#F6DDD9" stroke="#C0392B" stroke-width="3"/>
89
+ <line x1="428" y1="590" x2="472" y2="608" stroke="#C0392B" stroke-width="4"/>
90
+ <line x1="472" y1="590" x2="428" y2="608" stroke="#C0392B" stroke-width="4"/>
91
+ <text x="450" y="648" text-anchor="middle" font-family="Menlo,Consolas,monospace" font-size="18" fill="#1A1A1A">app_info_saved</text>
92
+ <text x="450" y="672" text-anchor="middle" font-family="Menlo,Consolas,monospace" font-size="16" fill="#6E6E6E">pipeline.py:174</text>
93
+ </g>
94
+ <g>
95
+ <line x1="690" y1="426" x2="690" y2="460" stroke="#C0392B" stroke-width="5" marker-end="url(#aRed)"/>
96
+ <rect x="646" y="462" width="88" height="92" rx="8" fill="#FFFFFF" stroke="#C0392B" stroke-width="3"/>
97
+ <circle cx="690" cy="508" r="16" fill="#DFF0E4" stroke="#1E8449" stroke-width="4"/>
98
+ <line x1="690" y1="554" x2="690" y2="584" stroke="#C0392B" stroke-width="5" stroke-dasharray="8 6"/>
99
+ <rect x="656" y="584" width="68" height="30" fill="#F6DDD9" stroke="#C0392B" stroke-width="3"/>
100
+ <line x1="668" y1="590" x2="712" y2="608" stroke="#C0392B" stroke-width="4"/>
101
+ <line x1="712" y1="590" x2="668" y2="608" stroke="#C0392B" stroke-width="4"/>
102
+ <text x="690" y="648" text-anchor="middle" font-family="Menlo,Consolas,monospace" font-size="18" fill="#1A1A1A">country_price_saved</text>
103
+ <text x="690" y="672" text-anchor="middle" font-family="Menlo,Consolas,monospace" font-size="16" fill="#6E6E6E">pipeline.py:208</text>
104
+ </g>
105
+ <g>
106
+ <line x1="930" y1="426" x2="930" y2="460" stroke="#C0392B" stroke-width="5" marker-end="url(#aRed)"/>
107
+ <rect x="886" y="462" width="88" height="92" rx="8" fill="#FFFFFF" stroke="#C0392B" stroke-width="3"/>
108
+ <circle cx="930" cy="508" r="16" fill="#DFF0E4" stroke="#1E8449" stroke-width="4"/>
109
+ <line x1="930" y1="554" x2="930" y2="584" stroke="#C0392B" stroke-width="5" stroke-dasharray="8 6"/>
110
+ <rect x="896" y="584" width="68" height="30" fill="#F6DDD9" stroke="#C0392B" stroke-width="3"/>
111
+ <line x1="908" y1="590" x2="952" y2="608" stroke="#C0392B" stroke-width="4"/>
112
+ <line x1="952" y1="590" x2="908" y2="608" stroke="#C0392B" stroke-width="4"/>
113
+ <text x="930" y="648" text-anchor="middle" font-family="Menlo,Consolas,monospace" font-size="18" fill="#1A1A1A">publication_saved</text>
114
+ <text x="930" y="672" text-anchor="middle" font-family="Menlo,Consolas,monospace" font-size="16" fill="#6E6E6E">pipeline.py:242</text>
115
+ </g>
116
+ <g>
117
+ <line x1="1170" y1="426" x2="1170" y2="460" stroke="#C0392B" stroke-width="5" marker-end="url(#aRed)"/>
118
+ <rect x="1126" y="462" width="88" height="92" rx="8" fill="#FFFFFF" stroke="#C0392B" stroke-width="3" stroke-dasharray="8 6"/>
119
+ <circle cx="1170" cy="508" r="16" fill="#DFF0E4" stroke="#1E8449" stroke-width="4"/>
120
+ <path d="M 1170 554 L 1170 588 L 1272 588 L 1272 540" fill="none" stroke="#C0392B" stroke-width="5" stroke-dasharray="8 6"/>
121
+ <line x1="1248" y1="562" x2="1280" y2="582" stroke="#C0392B" stroke-width="4"/>
122
+ <line x1="1280" y1="562" x2="1248" y2="582" stroke="#C0392B" stroke-width="4"/>
123
+ <text x="1170" y="648" text-anchor="middle" font-family="Menlo,Consolas,monospace" font-size="18" fill="#1A1A1A">review_saved</text>
124
+ <text x="1170" y="672" text-anchor="middle" font-family="Menlo,Consolas,monospace" font-size="16" fill="#6E6E6E">pipeline.py:314 · early return</text>
125
+ </g>
126
+
127
+ <line x1="60" y1="712" x2="1280" y2="712" stroke="#D5D0C2" stroke-width="3"/>
128
+
129
+ <!-- ================= PANEL 2 ================= -->
130
+ <text x="60" y="774" font-family="Helvetica,Arial,sans-serif" font-size="30" font-weight="bold" fill="#B9770E">2 · THE CUT — the gates come off, the stamps stay</text>
131
+
132
+ <path d="M 92 820 L 566 820 L 530 1058 L 128 1058 Z" fill="#FDF3E0" stroke="#B9770E" stroke-width="4"/>
133
+ <text x="329" y="856" text-anchor="middle" font-family="Menlo,Consolas,monospace" font-size="22" font-weight="bold" fill="#B9770E">should_skip_checkpoint</text>
134
+ <text x="329" y="882" text-anchor="middle" font-family="Menlo,Consolas,monospace" font-size="19" fill="#B9770E">−993 lines</text>
135
+
136
+ <rect x="126" y="900" width="182" height="42" rx="8" fill="#FFFFFF" stroke="#B9770E" stroke-width="3"/>
137
+ <text x="217" y="928" text-anchor="middle" font-family="Menlo,Consolas,monospace" font-size="18" fill="#B9770E">valve :114</text>
138
+ <rect x="326" y="900" width="182" height="42" rx="8" fill="#FFFFFF" stroke="#B9770E" stroke-width="3"/>
139
+ <text x="417" y="928" text-anchor="middle" font-family="Menlo,Consolas,monospace" font-size="18" fill="#B9770E">valve :174</text>
140
+ <rect x="138" y="954" width="182" height="42" rx="8" fill="#FFFFFF" stroke="#B9770E" stroke-width="3"/>
141
+ <text x="229" y="982" text-anchor="middle" font-family="Menlo,Consolas,monospace" font-size="18" fill="#B9770E">valve :208</text>
142
+ <rect x="338" y="954" width="182" height="42" rx="8" fill="#FFFFFF" stroke="#B9770E" stroke-width="3"/>
143
+ <text x="429" y="982" text-anchor="middle" font-family="Menlo,Consolas,monospace" font-size="18" fill="#B9770E">valve :242</text>
144
+ <rect x="150" y="1008" width="182" height="42" rx="8" fill="#FFFFFF" stroke="#B9770E" stroke-width="3" stroke-dasharray="7 5"/>
145
+ <text x="241" y="1036" text-anchor="middle" font-family="Menlo,Consolas,monospace" font-size="18" fill="#B9770E">bypass :314</text>
146
+ <rect x="350" y="1008" width="182" height="42" rx="8" fill="#FFFFFF" stroke="#B9770E" stroke-width="3" stroke-dasharray="7 5"/>
147
+ <text x="441" y="1036" text-anchor="middle" font-family="Menlo,Consolas,monospace" font-size="18" fill="#B9770E">spur :124</text>
148
+
149
+ <rect x="92" y="1084" width="474" height="98" rx="10" fill="#FFFFFF" stroke="#B9770E" stroke-width="3"/>
150
+ <text x="114" y="1118" font-family="Menlo,Consolas,monospace" font-size="20" fill="#1A1A1A">elif LIVE_WALLPAPER:</text>
151
+ <text x="140" y="1146" font-family="Menlo,Consolas,monospace" font-size="20" fill="#1A1A1A">capture_content_id()</text>
152
+ <text x="114" y="1172" font-family="Menlo,Consolas,monospace" font-size="17" fill="#6E6E6E">its only inlet was the valve above it</text>
153
+ <line x1="478" y1="1112" x2="518" y2="1140" stroke="#C0392B" stroke-width="5"/>
154
+ <line x1="518" y1="1112" x2="478" y2="1140" stroke="#C0392B" stroke-width="5"/>
155
+
156
+ <path d="M 706 946 L 624 900" fill="none" stroke="#B9770E" stroke-width="6" marker-end="url(#aAmb)"/>
157
+
158
+ <rect x="732" y="820" width="68" height="332" fill="#DFF0E4" stroke="#1E8449" stroke-width="4"/>
159
+ <line x1="766" y1="820" x2="766" y2="1188" stroke="#1E8449" stroke-width="8" marker-end="url(#aGrn)"/>
160
+
161
+ <g>
162
+ <rect x="814" y="828" width="40" height="40" rx="6" fill="#1E8449"/>
163
+ <text x="834" y="857" text-anchor="middle" font-family="Helvetica,Arial,sans-serif" font-size="24" font-weight="bold" fill="#FFFFFF">✓</text>
164
+ <text x="870" y="844" font-family="Menlo,Consolas,monospace" font-size="20" fill="#1A1A1A">binary_apks_uploaded</text>
165
+ <text x="870" y="866" font-family="Menlo,Consolas,monospace" font-size="20" fill="#1A1A1A">binary_devices_configured</text>
166
+ </g>
167
+ <g>
168
+ <rect x="814" y="894" width="40" height="40" rx="6" fill="#1E8449"/>
169
+ <text x="834" y="923" text-anchor="middle" font-family="Helvetica,Arial,sans-serif" font-size="24" font-weight="bold" fill="#FFFFFF">✓</text>
170
+ <text x="870" y="922" font-family="Menlo,Consolas,monospace" font-size="20" fill="#1A1A1A">app_info_saved</text>
171
+ </g>
172
+ <g>
173
+ <rect x="814" y="960" width="40" height="40" rx="6" fill="#1E8449"/>
174
+ <text x="834" y="989" text-anchor="middle" font-family="Helvetica,Arial,sans-serif" font-size="24" font-weight="bold" fill="#FFFFFF">✓</text>
175
+ <text x="870" y="988" font-family="Menlo,Consolas,monospace" font-size="20" fill="#1A1A1A">country_price_saved</text>
176
+ </g>
177
+ <g>
178
+ <rect x="814" y="1026" width="40" height="40" rx="6" fill="#1E8449"/>
179
+ <text x="834" y="1055" text-anchor="middle" font-family="Helvetica,Arial,sans-serif" font-size="24" font-weight="bold" fill="#FFFFFF">✓</text>
180
+ <text x="870" y="1054" font-family="Menlo,Consolas,monospace" font-size="20" fill="#1A1A1A">publication_saved</text>
181
+ </g>
182
+ <g>
183
+ <rect x="814" y="1092" width="40" height="40" rx="6" fill="#FFFFFF" stroke="#1E8449" stroke-width="3"/>
184
+ <text x="834" y="1121" text-anchor="middle" font-family="Helvetica,Arial,sans-serif" font-size="24" font-weight="bold" fill="#1E8449">✓</text>
185
+ <text x="870" y="1120" font-family="Menlo,Consolas,monospace" font-size="20" fill="#1A1A1A">review_saved</text>
186
+ <text x="870" y="1144" font-family="Menlo,Consolas,monospace" font-size="17" fill="#6E6E6E">withheld when the save is unverified</text>
187
+ </g>
188
+
189
+ <line x1="60" y1="1226" x2="1280" y2="1226" stroke="#D5D0C2" stroke-width="3"/>
190
+
191
+ <!-- ================= PANEL 3 ================= -->
192
+ <text x="60" y="1288" font-family="Helvetica,Arial,sans-serif" font-size="30" font-weight="bold" fill="#1E8449">3 · RESUME LIVES ONE FLOOR UP — a sieve, not a valve</text>
193
+
194
+ <rect x="80" y="1338" width="300" height="118" rx="10" fill="#FFFFFF" stroke="#1E8449" stroke-width="3"/>
195
+ <text x="230" y="1370" text-anchor="middle" font-family="Menlo,Consolas,monospace" font-size="19" fill="#6E6E6E">detected order</text>
196
+ <rect x="104" y="1386" width="118" height="46" rx="6" fill="#EDEAE1" stroke="#3A3A3A" stroke-width="2"/>
197
+ <text x="163" y="1417" text-anchor="middle" font-family="Menlo,Consolas,monospace" font-size="20" fill="#1A1A1A">THEME</text>
198
+ <rect x="234" y="1386" width="126" height="46" rx="6" fill="#EDEAE1" stroke="#3A3A3A" stroke-width="2"/>
199
+ <text x="297" y="1417" text-anchor="middle" font-family="Menlo,Consolas,monospace" font-size="20" fill="#1A1A1A">WALLPAPER</text>
200
+
201
+ <line x1="388" y1="1396" x2="452" y2="1396" stroke="#1E8449" stroke-width="6" marker-end="url(#aGrn)"/>
202
+
203
+ <path d="M 464 1328 L 792 1328 L 690 1458 L 566 1458 Z" fill="#DFF0E4" stroke="#1E8449" stroke-width="4"/>
204
+ <text x="628" y="1364" text-anchor="middle" font-family="Menlo,Consolas,monospace" font-size="21" font-weight="bold" fill="#1A1A1A">is_product_submitted</text>
205
+ <text x="628" y="1392" text-anchor="middle" font-family="Menlo,Consolas,monospace" font-size="17" fill="#1E8449">_orchestrator_theme_runner.py:253</text>
206
+ <line x1="528" y1="1418" x2="726" y2="1418" stroke="#1E8449" stroke-width="3" stroke-dasharray="6 8"/>
207
+
208
+ <path d="M 566 1462 L 566 1538 L 476 1538" fill="none" stroke="#7A7A7A" stroke-width="5" marker-end="url(#aGry)"/>
209
+ <rect x="304" y="1514" width="150" height="48" rx="6" fill="#EDEAE1" stroke="#7A7A7A" stroke-width="3"/>
210
+ <text x="379" y="1546" text-anchor="middle" font-family="Menlo,Consolas,monospace" font-size="20" fill="#7A7A7A">THEME</text>
211
+ <text x="304" y="1598" font-family="Menlo,Consolas,monospace" font-size="18" fill="#7A7A7A">already submitted per tracker</text>
212
+
213
+ <line x1="798" y1="1388" x2="860" y2="1388" stroke="#1E8449" stroke-width="6" marker-end="url(#aGrn)"/>
214
+ <rect x="874" y="1338" width="300" height="118" rx="10" fill="#FFFFFF" stroke="#1E8449" stroke-width="3"/>
215
+ <text x="1024" y="1370" text-anchor="middle" font-family="Menlo,Consolas,monospace" font-size="19" fill="#6E6E6E">order sent to submit()</text>
216
+ <rect x="960" y="1386" width="126" height="46" rx="6" fill="#DFF0E4" stroke="#1E8449" stroke-width="3"/>
217
+ <text x="1023" y="1417" text-anchor="middle" font-family="Menlo,Consolas,monospace" font-size="20" fill="#1E8449">WALLPAPER</text>
218
+
219
+ <text x="874" y="1518" font-family="Menlo,Consolas,monospace" font-size="22" fill="#1A1A1A">[THEME, WALLPAPER] → [WALLPAPER]</text>
220
+ <text x="874" y="1548" font-family="Menlo,Consolas,monospace" font-size="18" fill="#6E6E6E">product granularity · one submit() per product</text>
221
+ <text x="874" y="1598" font-family="Menlo,Consolas,monospace" font-size="18" fill="#6E6E6E">PR #1262 · base 5b40e42</text>
222
+ </svg>
@@ -0,0 +1,6 @@
1
+ # Show scripts
2
+
3
+ Run these scripts from any directory by passing an absolute artifact path. They use only the Python standard library.
4
+
5
+ - `validate-package.py` checks the package skeleton and hub links.
6
+ - `validate-artifact.py` checks SVG or HTML output contracts.
@@ -0,0 +1,91 @@
1
+ from pathlib import Path
2
+ import re
3
+ import sys
4
+
5
+ standard_canvas_width = 680.0
6
+ large_canvas_minimum_width = 1360.0
7
+ large_canvas_maximum_width = 2000.0
8
+ text_floor_at_standard_width = 16.0
9
+ connector_tags = {"line", "path", "polyline"}
10
+ tag_pattern = re.compile(r"<(/?)([a-zA-Z][\w:.-]*)([^>]*?)(/?)>", re.DOTALL)
11
+ fill_pattern = re.compile(r'\bfill=["\']([^"\']*)["\']')
12
+ style_fill_pattern = re.compile(r'style=["\'][^"\']*\bfill\s*:\s*([^;"\']+)')
13
+ marker_pattern = re.compile(r"\bmarker-(?:start|mid|end)=")
14
+ comment_pattern = re.compile(r"<!--.*?-->", re.DOTALL)
15
+
16
+
17
+ def find_unfilled_connectors(markup: str) -> list[str]:
18
+ """Name every connector whose fill resolves to something other than none.
19
+
20
+ A connector is a line, path, or polyline carrying a marker. Its fill comes
21
+ from its own attribute when it has one, and from the nearest enclosing
22
+ element otherwise::
23
+
24
+ <g fill="none"><line marker-end="url(#a)"/></g> ok: inherits none
25
+ <line fill="red" marker-end="url(#a)"/> flag: own fill wins
26
+ <path d="M0 0L9 9Z" fill="currentColor"/> ok: glyph, no marker
27
+
28
+ Args:
29
+ markup: The full SVG or HTML source text.
30
+
31
+ Returns:
32
+ The tag name of each offending connector, in document order.
33
+ """
34
+ inherited_fills = [""]
35
+ offenders = []
36
+ for each_tag in tag_pattern.finditer(markup):
37
+ is_closing, tag_name, attributes, is_self_closing = each_tag.groups()
38
+ if is_closing:
39
+ inherited_fills = inherited_fills[:-1] or [""]
40
+ continue
41
+ own_fill = style_fill_pattern.search(attributes) or fill_pattern.search(attributes)
42
+ effective_fill = own_fill.group(1).strip() if own_fill else inherited_fills[-1]
43
+ is_connector = tag_name in connector_tags and bool(marker_pattern.search(attributes))
44
+ if is_connector and effective_fill != "none": offenders.append(tag_name)
45
+ if not is_self_closing: inherited_fills.append(effective_fill)
46
+ return offenders
47
+
48
+
49
+ if len(sys.argv[1:]) != 1:
50
+ raise SystemExit("usage: validate-artifact.py <svg-or-html>")
51
+ path = Path(sys.argv[1])
52
+ if not path.is_file():
53
+ raise SystemExit(f"missing artifact: {path}")
54
+ text = comment_pattern.sub("", path.read_text(encoding="utf-8"))
55
+ errors = []
56
+ warnings = []
57
+ defined_properties = set(re.findall(r'(--[\w-]+)\s*:', text))
58
+ for each_property_name in re.findall(r'var\(\s*(--[\w-]+)', text):
59
+ if each_property_name not in defined_properties: errors.append(f"undefined custom property {each_property_name}")
60
+ defined_ids = set(re.findall(r'\bid=["\']([^"\']+)["\']', text))
61
+ referenced_ids = re.findall(r'href=["\']#([^"\']+)["\']', text) + re.findall(r'url\(\s*#([^)\s"\']+)', text)
62
+ for each_referenced_id in referenced_ids:
63
+ if each_referenced_id not in defined_ids: errors.append(f"reference to missing id #{each_referenced_id}")
64
+ if path.suffix.lower() == ".svg":
65
+ if not re.search(r'<svg[^>]+role=["\']img["\']', text): errors.append("SVG needs role=img")
66
+ viewbox = re.search(r'<svg[^>]+viewBox=["\']0 0 ([0-9.]+) ', text)
67
+ canvas_width = float(viewbox.group(1)) if viewbox else 0.0
68
+ is_standard_canvas = canvas_width == standard_canvas_width
69
+ is_large_canvas = large_canvas_minimum_width <= canvas_width <= large_canvas_maximum_width
70
+ if not is_standard_canvas and not is_large_canvas: errors.append("SVG viewBox width must be 680 or 1360-2000")
71
+ if not re.search(r'<svg[^>]*>\s*<title>.*?</title>\s*<desc>', text, re.DOTALL): errors.append("SVG title and desc must be first")
72
+ unfilled_connectors = find_unfilled_connectors(text)
73
+ if unfilled_connectors: errors.append(f"connectors need fill=none: {', '.join(sorted(set(unfilled_connectors)))}")
74
+ floor_width = max(canvas_width, standard_canvas_width)
75
+ text_floor = round(text_floor_at_standard_width * floor_width / standard_canvas_width)
76
+ sizes = [float(x) for x in re.findall(r'font-size=["\']([0-9.]+)', text)]
77
+ if any(size < text_floor for size in sizes): errors.append(f"text below {text_floor}px")
78
+ for each_marker in re.findall(r'<marker\b[^>]*>', text):
79
+ if not re.search(r'markerUnits=["\']userSpaceOnUse["\']', each_marker): warnings.append("marker without markerUnits=userSpaceOnUse scales with stroke width")
80
+ elif path.suffix.lower() in {".html", ".htm"}:
81
+ if not re.match(r'\s*<h2[^>]+class=["\']sr-only["\']', text): errors.append("HTML needs a first sr-only h2")
82
+ if re.search(r'position\s*:\s*fixed|overflow\s*:\s*(?:auto|scroll)', text): errors.append("fixed positioning and nested scrolling are forbidden")
83
+ else:
84
+ errors.append("artifact must be SVG or HTML")
85
+ for each_warning in warnings:
86
+ print(f"ARTIFACT WARNING: {each_warning}")
87
+ if errors:
88
+ print("ARTIFACT INVALID")
89
+ print("\n".join(errors))
90
+ sys.exit(1)
91
+ print(f"ARTIFACT VALID: {path.name}")
@@ -0,0 +1,18 @@
1
+ import re
2
+ from pathlib import Path
3
+ import sys
4
+
5
+ skill_root = Path(__file__).resolve().parents[1]
6
+ routing_text = (skill_root / "routing.yaml").read_text(encoding="utf-8")
7
+ routed = re.findall(r"(?:references|workflows|templates)/[\w.-]+", routing_text)
8
+ required = ["SKILL.md", "routing.yaml", "workflows/review-visual.md", *sorted(set(routed))]
9
+ text = (skill_root / "SKILL.md").read_text(encoding="utf-8")
10
+ errors = [p for p in required if not (skill_root / p).is_file()]
11
+ for each_token in ["routing.yaml", "workflows/create-visual.md"]:
12
+ if each_token not in text:
13
+ errors.append(f"SKILL.md missing {each_token}")
14
+ if errors:
15
+ print("PACKAGE INVALID")
16
+ print("\n".join(errors))
17
+ sys.exit(1)
18
+ print(f"PACKAGE VALID: {len(required)} required files present")
@@ -0,0 +1,4 @@
1
+ <h2 class="sr-only">Interactive visual summary.</h2>
2
+ <section aria-label="Visual widget">
3
+ <button type="button" aria-pressed="false">Show detail</button>
4
+ </section>
@@ -0,0 +1,19 @@
1
+ <svg width="100%" viewBox="0 0 680 180" role="img">
2
+ <title>Accessible visual title</title>
3
+ <desc>One-sentence description of the visual.</desc>
4
+ <defs>
5
+ <marker id="arrow" viewBox="0 0 10 10" refX="8" refY="5" markerUnits="userSpaceOnUse" markerWidth="6" markerHeight="6" orient="auto">
6
+ <path d="M2 1L8 5L2 9" fill="none" stroke="#3A3A3A" stroke-width="1.5" />
7
+ </marker>
8
+ </defs>
9
+ <rect x="0" y="0" width="680" height="180" fill="#F2EFE6" />
10
+ <g stroke-width="1">
11
+ <rect x="40" y="40" width="180" height="44" rx="8" fill="#FFFFFF" stroke="#9A9A9A" />
12
+ <line x1="220" y1="62" x2="300" y2="62" fill="none" stroke="#3A3A3A" marker-end="url(#arrow)" />
13
+ <rect x="300" y="40" width="180" height="44" rx="8" fill="#DFF0E4" stroke="#1E8449" />
14
+ </g>
15
+ <g fill="#1A1A1A" font-family="sans-serif" font-size="16" text-anchor="middle">
16
+ <text x="130" y="62" dominant-baseline="central">Start</text>
17
+ <text x="390" y="62" dominant-baseline="central">Next step</text>
18
+ </g>
19
+ </svg>
@@ -0,0 +1,6 @@
1
+ <svg width="100%" viewBox="0 0 680 120" role="img">
2
+ <title>Variable fill</title>
3
+ <desc>A labelled box whose text fill comes from a CSS custom property.</desc>
4
+ <g fill="none" stroke="currentColor" stroke-width="1"><rect x="40" y="40" width="240" height="44" rx="8"/></g>
5
+ <g fill="var(--ink)" font-family="sans-serif" font-size="16"><text x="60" y="68">Label</text></g>
6
+ </svg>
@@ -0,0 +1,7 @@
1
+ <svg width="100%" viewBox="0 0 680 120" role="img">
2
+ <title>Dead reference</title>
3
+ <desc>A use element pointing at a symbol the file never defines.</desc>
4
+ <g fill="none" stroke="currentColor" stroke-width="1"><rect x="40" y="40" width="240" height="44" rx="8"/></g>
5
+ <use href="#ghost" x="320" y="40"/>
6
+ <g fill="currentColor" font-family="sans-serif" font-size="16"><text x="60" y="68">Label</text></g>
7
+ </svg>
@@ -0,0 +1,8 @@
1
+ <svg width="100%" viewBox="0 0 680 140" role="img">
2
+ <title>Filled glyph</title>
3
+ <desc>A solid check mark drawn as a filled path beside its label.</desc>
4
+ <path d="M40 70L60 90L100 46Z" fill="currentColor"/>
5
+ <g fill="currentColor" font-family="sans-serif" font-size="16">
6
+ <text x="120" y="76">Done</text>
7
+ </g>
8
+ </svg>
@@ -0,0 +1,18 @@
1
+ <svg width="100%" viewBox="0 0 680 140" role="img">
2
+ <title>Inherited connector fill</title>
3
+ <desc>Two boxes joined by a connector that takes its fill from the surrounding group.</desc>
4
+ <defs>
5
+ <marker id="arrow" viewBox="0 0 10 10" refX="8" refY="5" markerUnits="userSpaceOnUse" markerWidth="6" markerHeight="6" orient="auto">
6
+ <path d="M2 1L8 5L2 9" fill="none" stroke="currentColor" stroke-width="1.5"/>
7
+ </marker>
8
+ </defs>
9
+ <g fill="none" stroke="currentColor" stroke-width="1">
10
+ <rect x="40" y="48" width="200" height="44" rx="8"/>
11
+ <line x1="240" y1="70" x2="320" y2="70" marker-end="url(#arrow)"/>
12
+ <rect x="320" y="48" width="200" height="44" rx="8"/>
13
+ </g>
14
+ <g fill="currentColor" font-family="sans-serif" font-size="16" text-anchor="middle">
15
+ <text x="140" y="70" dominant-baseline="central">Start</text>
16
+ <text x="420" y="70" dominant-baseline="central">End</text>
17
+ </g>
18
+ </svg>
@@ -0,0 +1 @@
1
+ <svg viewBox="0 0 300 100"><text font-size="8">Too small</text></svg>
@@ -0,0 +1,21 @@
1
+ <svg width="100%" viewBox="0 0 1760 640" role="img">
2
+ <title>Large canvas review flow</title>
3
+ <desc>A three-step flow across a large infographic canvas, from request to review to result.</desc>
4
+ <defs>
5
+ <marker id="arrow" viewBox="0 0 10 10" refX="8" refY="5" markerUnits="userSpaceOnUse" markerWidth="16" markerHeight="16" orient="auto">
6
+ <path d="M2 1L8 5L2 9" fill="none" stroke="currentColor" stroke-width="1.5"/>
7
+ </marker>
8
+ </defs>
9
+ <g fill="none" stroke="currentColor" stroke-width="2">
10
+ <rect x="80" y="240" width="440" height="120" rx="20"/>
11
+ <line x1="520" y1="300" x2="660" y2="300" fill="none" marker-end="url(#arrow)"/>
12
+ <rect x="660" y="240" width="440" height="120" rx="20"/>
13
+ <line x1="1100" y1="300" x2="1240" y2="300" fill="none" marker-end="url(#arrow)"/>
14
+ <rect x="1240" y="240" width="440" height="120" rx="20"/>
15
+ </g>
16
+ <g fill="currentColor" font-family="sans-serif" font-size="41" text-anchor="middle">
17
+ <text x="300" y="300" dominant-baseline="central">Request</text>
18
+ <text x="880" y="300" dominant-baseline="central">Review</text>
19
+ <text x="1460" y="300" dominant-baseline="central">Result</text>
20
+ </g>
21
+ </svg>
@@ -0,0 +1,15 @@
1
+ <svg width="100%" viewBox="0 0 680 140" role="img">
2
+ <title>Connector carrying a solid fill</title>
3
+ <desc>An arrow whose own fill is a solid colour rather than none.</desc>
4
+ <defs>
5
+ <marker id="arrow" viewBox="0 0 10 10" refX="8" refY="5" markerUnits="userSpaceOnUse" markerWidth="6" markerHeight="6" orient="auto">
6
+ <path d="M2 1L8 5L2 9" fill="none" stroke="currentColor" stroke-width="1.5"/>
7
+ </marker>
8
+ </defs>
9
+ <g stroke="currentColor" stroke-width="1">
10
+ <line x1="240" y1="70" x2="320" y2="70" fill="currentColor" marker-end="url(#arrow)"/>
11
+ </g>
12
+ <g fill="currentColor" font-family="sans-serif" font-size="16">
13
+ <text x="40" y="76">Label</text>
14
+ </g>
15
+ </svg>
@@ -0,0 +1 @@
1
+ <h2 class="sr-only">A valid visual.</h2><section aria-label="Visual"></section>
@@ -0,0 +1,74 @@
1
+ import subprocess
2
+ import sys
3
+ from pathlib import Path
4
+
5
+ ROOT = Path(__file__).parents[1]
6
+ SCRIPT = ROOT / "scripts" / "validate-artifact.py"
7
+ HEADER = (
8
+ '<svg width="100%" viewBox="0 0 680 120" role="img"><title>T</title><desc>D</desc>'
9
+ )
10
+
11
+
12
+ def check(artifact: Path) -> subprocess.CompletedProcess[str]:
13
+ return subprocess.run(
14
+ [sys.executable, str(SCRIPT), str(artifact)], text=True, capture_output=True
15
+ )
16
+
17
+
18
+ def write_svg(directory: Path, name: str, body: str) -> Path:
19
+ artifact = directory / name
20
+ artifact.write_text(body, encoding="utf-8")
21
+ return artifact
22
+
23
+
24
+ def test_text_floor_reports_tiny_text_when_the_viewbox_is_unreadable(
25
+ tmp_path: Path,
26
+ ) -> None:
27
+ artifact = write_svg(
28
+ tmp_path,
29
+ "no-viewbox.svg",
30
+ '<svg role="img"><title>T</title><desc>D</desc><text font-size="4">tiny</text></svg>',
31
+ )
32
+ finished = check(artifact)
33
+ assert "text below" in finished.stdout, finished.stdout + finished.stderr
34
+
35
+
36
+ def test_commented_out_connector_is_not_reported(tmp_path: Path) -> None:
37
+ artifact = write_svg(
38
+ tmp_path,
39
+ "commented.svg",
40
+ f'{HEADER}\n<!-- <line fill="red" marker-end="url(#a)"/> -->\n</svg>',
41
+ )
42
+ finished = check(artifact)
43
+ assert finished.returncode == 0, finished.stdout + finished.stderr
44
+
45
+
46
+ def test_live_connector_after_a_comment_is_still_reported(tmp_path: Path) -> None:
47
+ artifact = write_svg(
48
+ tmp_path,
49
+ "after-comment.svg",
50
+ f"{HEADER}\n<!-- an explanatory note -->\n"
51
+ '<defs><marker id="a" markerUnits="userSpaceOnUse"><path d="M2 1L8 5"/></marker></defs>\n'
52
+ '<line x1="10" y1="10" x2="90" y2="10" fill="red" marker-end="url(#a)"/>\n</svg>',
53
+ )
54
+ finished = check(artifact)
55
+ assert "connectors need fill=none" in finished.stdout, (
56
+ finished.stdout + finished.stderr
57
+ )
58
+
59
+
60
+ def test_connector_error_names_the_offending_tag(tmp_path: Path) -> None:
61
+ artifact = write_svg(
62
+ tmp_path,
63
+ "named.svg",
64
+ f"{HEADER}\n"
65
+ '<defs><marker id="a" markerUnits="userSpaceOnUse"><path d="M2 1L8 5"/></marker></defs>\n'
66
+ '<line x1="10" y1="10" x2="90" y2="10" fill="red" marker-end="url(#a)"/>\n</svg>',
67
+ )
68
+ finished = check(artifact)
69
+ connector_error = [
70
+ line for line in finished.stdout.splitlines() if "fill=none" in line
71
+ ]
72
+ assert connector_error and "line" in connector_error[0], (
73
+ finished.stdout + finished.stderr
74
+ )
@@ -0,0 +1,59 @@
1
+ import subprocess
2
+ import sys
3
+ from pathlib import Path
4
+
5
+ ROOT = Path(__file__).parents[1]
6
+
7
+ def run(*args: str) -> subprocess.CompletedProcess[str]:
8
+ return subprocess.run([sys.executable, *args], cwd=ROOT, text=True, capture_output=True)
9
+
10
+
11
+ def test_package() -> None:
12
+ finished = run("scripts/validate-package.py")
13
+ assert finished.returncode == 0, finished.stdout + finished.stderr
14
+
15
+
16
+ def test_template_svg() -> None:
17
+ finished = run("scripts/validate-artifact.py", "templates/svg-base.svg")
18
+ assert finished.returncode == 0, finished.stdout + finished.stderr
19
+
20
+
21
+ def test_valid_html_passes() -> None:
22
+ finished = run("scripts/validate-artifact.py", "tests/fixtures/valid.html")
23
+ assert finished.returncode == 0, finished.stdout + finished.stderr
24
+
25
+
26
+ def test_invalid_fixture_fails() -> None:
27
+ finished = run("scripts/validate-artifact.py", "tests/fixtures/invalid.svg")
28
+ assert finished.returncode != 0, finished.stdout + finished.stderr
29
+
30
+
31
+ def test_css_variable_fixture_fails() -> None:
32
+ finished = run("scripts/validate-artifact.py", "tests/fixtures/css-var.svg")
33
+ assert finished.returncode != 0, finished.stdout + finished.stderr
34
+
35
+
36
+ def test_dead_reference_fixture_fails() -> None:
37
+ finished = run("scripts/validate-artifact.py", "tests/fixtures/dead-ref.svg")
38
+ assert finished.returncode != 0, finished.stdout + finished.stderr
39
+
40
+
41
+ def test_large_canvas_fixture_passes() -> None:
42
+ finished = run("scripts/validate-artifact.py", "tests/fixtures/large-canvas.svg")
43
+ assert finished.returncode == 0, finished.stdout + finished.stderr
44
+
45
+
46
+ def test_connector_inheriting_group_fill_passes() -> None:
47
+ finished = run("scripts/validate-artifact.py", "tests/fixtures/inherited-fill.svg")
48
+ assert finished.returncode == 0, finished.stdout + finished.stderr
49
+
50
+
51
+ def test_filled_glyph_without_markers_passes() -> None:
52
+ finished = run("scripts/validate-artifact.py", "tests/fixtures/filled-glyph.svg")
53
+ assert finished.returncode == 0, finished.stdout + finished.stderr
54
+
55
+
56
+ def test_connector_with_solid_fill_fails() -> None:
57
+ finished = run("scripts/validate-artifact.py", "tests/fixtures/unfilled-connector.svg")
58
+ assert finished.returncode != 0, finished.stdout + finished.stderr
59
+ assert "connectors need fill=none" in finished.stdout