@event4u/agent-config 16.4.0 → 17.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/.augment-plugin/marketplace.json +3 -3
- package/.augment-plugin/plugin.json +1 -1
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/skills/install-agent-config/SKILL.md +4 -4
- package/CHANGELOG.md +483 -669
- package/CONTRIBUTING.md +47 -0
- package/README.md +25 -25
- package/dist/agent-src/commands/{design.md → agent-design.md} +11 -7
- package/dist/agent-src/commands/agents/init.md +66 -3
- package/dist/agent-src/commands/agents/user/init.md +3 -0
- package/dist/agent-src/commands/{optimize/deep.md → analyze/and-optimize.md} +72 -33
- package/dist/agent-src/commands/analyze/inbox.md +168 -37
- package/dist/agent-src/commands/analyze/pr.md +144 -0
- package/dist/agent-src/commands/analyze/{repo.md → reference.md} +85 -14
- package/dist/agent-src/commands/analyze/roadmap-repos.md +19 -10
- package/dist/agent-src/commands/analyze.md +28 -12
- package/dist/agent-src/commands/audit.md +115 -0
- package/dist/agent-src/commands/brand/review.md +13 -2
- package/dist/agent-src/commands/brand/tokens.md +3 -3
- package/dist/agent-src/commands/brand/voice.md +8 -5
- package/dist/agent-src/commands/brand.md +2 -2
- package/dist/agent-src/commands/challenge-me/closure.md +25 -0
- package/dist/agent-src/commands/design-system/import.md +8 -3
- package/dist/agent-src/commands/design-system/sync.md +98 -0
- package/dist/agent-src/commands/design-system.md +6 -1
- package/dist/agent-src/commands/evals/analyze.json +44 -0
- package/dist/agent-src/commands/evals/audit.json +12 -0
- package/dist/agent-src/commands/evals/retro.json +12 -0
- package/dist/agent-src/commands/fix/ci.md +10 -8
- package/dist/agent-src/commands/ghostwriter/write.md +22 -0
- package/dist/agent-src/commands/memory/learn-low-impact.md +70 -83
- package/dist/agent-src/commands/memory/propose.md +12 -0
- package/dist/agent-src/commands/memory.md +1 -1
- package/dist/agent-src/commands/optimize.md +4 -3
- package/dist/agent-src/commands/post-as/me.md +18 -0
- package/dist/agent-src/commands/pr/create.md +17 -342
- package/dist/agent-src/commands/pr/merge.md +129 -368
- package/dist/agent-src/commands/retro.md +121 -0
- package/dist/agent-src/commands/review/routing.md +1 -1
- package/dist/agent-src/commands/roadmap/next.md +4 -1
- package/dist/agent-src/commands/roadmap/process-full.md +46 -333
- package/dist/agent-src/commands/roadmap/triage-parked.md +6 -2
- package/dist/agent-src/commands/team-knowledge/consolidate.md +2 -2
- package/dist/agent-src/commands/team-knowledge.md +1 -1
- package/dist/agent-src/commands/threat-model.md +1 -1
- package/dist/agent-src/commands/upstream-contribute.md +91 -101
- package/dist/agent-src/commands/worktree/cleanup.md +18 -11
- package/dist/agent-src/contexts/contracts/consumer-agents-md-guide.md +54 -0
- package/dist/agent-src/contexts/execution/analysis-loop.md +137 -0
- package/dist/agent-src/contexts/execution/auto-dispatch-classification.md +3 -3
- package/dist/agent-src/contexts/execution/decision-closure-execution.md +1 -1
- package/dist/agent-src/contexts/execution/host-capability-manifest.md +6 -0
- package/dist/agent-src/contexts/execution/orchestration-telemetry.md +11 -4
- package/dist/agent-src/contexts/execution/pr-create-branches-content.md +250 -0
- package/dist/agent-src/contexts/execution/pr-create-branches-freshness.md +150 -0
- package/dist/agent-src/contexts/execution/pr-merge-queue.md +287 -0
- package/dist/agent-src/contexts/execution/pr-merge-sync.md +276 -0
- package/dist/agent-src/contexts/execution/pr-merge-trust-boundary.md +69 -0
- package/dist/agent-src/contexts/execution/process-full-all.md +233 -0
- package/dist/agent-src/contexts/execution/process-full-delivery.md +231 -0
- package/dist/agent-src/contexts/execution/roadmap-execution-contract-branches.md +42 -0
- package/dist/agent-src/contexts/execution/roadmap-execution-contract.md +1 -35
- package/dist/agent-src/contexts/execution/roadmap-process-loop-close.md +235 -0
- package/dist/agent-src/contexts/execution/roadmap-process-loop-prescan.md +216 -0
- package/dist/agent-src/contexts/execution/roadmap-process-loop-run.md +253 -0
- package/dist/agent-src/contexts/execution/roadmap-process-loop-set.md +212 -0
- package/dist/agent-src/contexts/execution/roadmap-process-loop.md +25 -864
- package/dist/agent-src/contexts/execution/subagent-spawn-contract.md +14 -2
- package/dist/agent-src/contexts/execution/subagent-steering.md +65 -8
- package/dist/agent-src/contexts/model-recommendations.md +5 -5
- package/dist/agent-src/contexts/subagent-configuration.md +1 -1
- package/dist/agent-src/guidelines/agent-infra/api-cost-levers.md +4 -3
- package/dist/agent-src/guidelines/agent-infra/model-recommendation.md +11 -5
- package/dist/agent-src/guidelines/agent-infra/rule-body-migration-inventory.md +1 -1
- package/dist/agent-src/guidelines/agent-infra/self-improvement-pipeline.md +47 -24
- package/dist/agent-src/guidelines/design-antipatterns.md +6 -1
- package/dist/agent-src/guidelines/design-asset-discipline.md +17 -0
- package/dist/agent-src/guidelines/design-fidelity-routing.md +1 -0
- package/dist/agent-src/guidelines/php/api-design.md +10 -1
- package/dist/agent-src/rules/brand-source-of-truth.md +7 -6
- package/dist/agent-src/rules/code-provenance.md +1 -1
- package/dist/agent-src/rules/context-hygiene.md +0 -1
- package/dist/agent-src/rules/design-fidelity.md +12 -13
- package/dist/agent-src/rules/external-reference-deep-dive.md +2 -2
- package/dist/agent-src/rules/fix-what-you-see.md +1 -1
- package/dist/agent-src/rules/low-impact-corpus-privacy-floor.md +14 -15
- package/dist/agent-src/rules/missing-skill-recovery.md +12 -11
- package/dist/agent-src/rules/neighbour-precedence.md +1 -1
- package/dist/agent-src/rules/recurring-criticism.md +1 -1
- package/dist/agent-src/rules/roadmap-ci-steps-policy.md +0 -2
- package/dist/agent-src/rules/self-repair-loop.md +2 -2
- package/dist/agent-src/rules/token-budget-discipline.md +2 -2
- package/dist/agent-src/scripts/dashboard_mode.ts +4 -1
- package/dist/agent-src/scripts/roadmap_age.ts +175 -0
- package/dist/agent-src/scripts/update_roadmap_progress.ts +10 -2
- package/dist/agent-src/skills/accessibility-auditor/SKILL.md +3 -3
- package/dist/agent-src/skills/accessibility-auditor/data/aria-patterns.csv +2 -0
- package/dist/agent-src/skills/accessibility-auditor/data/manifest.json +3 -3
- package/dist/agent-src/skills/agent-security-review/SKILL.md +2 -2
- package/dist/agent-src/skills/agents-md-thin-root/SKILL.md +12 -4
- package/dist/agent-src/skills/ai-code-blindspots/SKILL.md +7 -0
- package/dist/agent-src/skills/ai-council/references/advanced-modes.md +11 -1
- package/dist/agent-src/skills/ai-council/references/cost-and-redaction.md +7 -0
- package/dist/agent-src/skills/ai-council/references/execution-modes.md +7 -0
- package/dist/agent-src/skills/ai-council/references/output-and-synthesis.md +19 -0
- package/dist/agent-src/skills/ai-council/references/procedure.md +7 -0
- package/dist/agent-src/skills/api-design/SKILL.md +2 -2
- package/dist/agent-src/skills/api-endpoint/SKILL.md +2 -2
- package/dist/agent-src/skills/authz-review/SKILL.md +5 -3
- package/dist/agent-src/skills/blade-ui/SKILL.md +2 -2
- package/dist/agent-src/skills/brand/SKILL.md +7 -3
- package/dist/agent-src/skills/brand-asset-generation/SKILL.md +13 -6
- package/dist/agent-src/skills/brand-audit/SKILL.md +2 -1
- package/dist/agent-src/skills/brand-identity/SKILL.md +5 -4
- package/dist/agent-src/skills/brand-strategy/SKILL.md +5 -4
- package/dist/agent-src/skills/brand-to-tokens/SKILL.md +18 -39
- package/dist/agent-src/skills/brand-to-tokens/templates/marp-brand-deck.md.example +2 -2
- package/dist/agent-src/skills/brand-to-tokens/templates/reveal-brand-deck.yaml +2 -2
- package/dist/agent-src/skills/canvas-design/SKILL.md +16 -4
- package/dist/agent-src/skills/competitive-positioning/SKILL.md +2 -2
- package/dist/agent-src/skills/composer-packages/SKILL.md +18 -0
- package/dist/agent-src/skills/condense-memory/SKILL.md +1 -1
- package/dist/agent-src/skills/content-funnel-design/SKILL.md +8 -1
- package/dist/agent-src/skills/context-authoring/SKILL.md +1 -1
- package/dist/agent-src/skills/context-document/SKILL.md +2 -2
- package/dist/agent-src/skills/corpus-grounding/SKILL.md +6 -6
- package/dist/agent-src/skills/corpus-grounding/scripts/decision_engine.ts +13 -11
- package/dist/agent-src/skills/corpus-grounding/scripts/ground.mjs +1706 -0
- package/dist/agent-src/skills/corpus-grounding/scripts/schema_validator.ts +7 -1
- package/dist/agent-src/skills/dashboard-design/SKILL.md +1 -1
- package/dist/agent-src/skills/data-flow-mapper/SKILL.md +5 -2
- package/dist/agent-src/skills/database/SKILL.md +5 -4
- package/dist/agent-src/skills/decision-record/references/weighted-matrix.md +12 -0
- package/dist/agent-src/skills/deep-reading-analyst/SKILL.md +1 -1
- package/dist/agent-src/skills/dependency-upgrade/SKILL.md +61 -11
- package/dist/agent-src/skills/description-assist/SKILL.md +1 -1
- package/dist/agent-src/skills/design-intelligence/SKILL.md +1 -1
- package/dist/agent-src/skills/design-intelligence/data/landing.csv +1 -1
- package/dist/agent-src/skills/design-intelligence/references/context-and-registers.md +10 -2
- package/dist/agent-src/skills/design-intelligence/references/design-languages.md +1 -1
- package/dist/agent-src/skills/design-intelligence/references/design-rules-checklist.md +4 -3
- package/dist/agent-src/skills/design-intelligence/references/integration-mapping.md +25 -22
- package/dist/agent-src/skills/{wireframe → design-review}/references/story-beat-ledger.schema.json +1 -1
- package/dist/agent-src/skills/design-review/references/verification-automation.md +11 -3
- package/dist/agent-src/skills/design-system-capture/SKILL.md +15 -10
- package/dist/agent-src/skills/design-system-capture/references/design-system-json.md +20 -5
- package/dist/agent-src/skills/design-system-capture/references/import-procedure.md +2 -2
- package/dist/agent-src/skills/design-tokens/SKILL.md +23 -23
- package/dist/agent-src/skills/design-tokens/scripts/tokens.mjs +1037 -0
- package/dist/agent-src/skills/design-tokens/scripts/tokens.ts +336 -13
- package/dist/agent-src/skills/design-tokens/templates/design-tokens-starter.json +52 -53
- package/dist/agent-src/skills/docker/SKILL.md +1 -1
- package/dist/agent-src/skills/estimate-ticket/SKILL.md +1 -1
- package/dist/agent-src/skills/existing-ui-audit/SKILL.md +14 -276
- package/dist/agent-src/skills/existing-ui-audit/procedure.yaml +44 -0
- package/dist/agent-src/skills/existing-ui-audit/references/anti-slop-cross-reference.md +1 -1
- package/dist/agent-src/skills/existing-ui-audit/references/steps/00-inspect-stack-and-input.md +4 -0
- package/dist/agent-src/skills/existing-ui-audit/references/steps/01-enumerate-components.md +26 -0
- package/dist/agent-src/skills/existing-ui-audit/references/steps/01b-detect-component-taxonomy.md +7 -0
- package/dist/agent-src/skills/existing-ui-audit/references/steps/02-identify-design-system.md +12 -0
- package/dist/agent-src/skills/existing-ui-audit/references/steps/03-detect-design-tokens.md +35 -0
- package/dist/agent-src/skills/existing-ui-audit/references/steps/04-detect-shadcn-inventory.md +13 -0
- package/dist/agent-src/skills/existing-ui-audit/references/steps/04b-live-storybook-read.md +26 -0
- package/dist/agent-src/skills/existing-ui-audit/references/steps/05-list-reusable-patterns.md +14 -0
- package/dist/agent-src/skills/existing-ui-audit/references/steps/06-match-candidates.md +52 -0
- package/dist/agent-src/skills/existing-ui-audit/references/steps/07-greenfield-branch.md +29 -0
- package/dist/agent-src/skills/existing-ui-audit/references/steps/08-a11y-baseline.md +40 -0
- package/dist/agent-src/skills/existing-ui-audit/references/steps/09-validate-and-write.md +5 -0
- package/dist/agent-src/skills/experiment-loop/SKILL.md +1 -1
- package/dist/agent-src/skills/fe-design/references/design-patterns.md +65 -4
- package/dist/agent-src/skills/fe-design/references/design-read-and-memory.md +1 -1
- package/dist/agent-src/skills/fe-design/references/source-led-port.md +1 -1
- package/dist/agent-src/skills/finishing-a-development-branch/SKILL.md +6 -6
- package/dist/agent-src/skills/flux/SKILL.md +1 -1
- package/dist/agent-src/skills/form-handler/SKILL.md +15 -2
- package/dist/agent-src/skills/frontend-render-security/SKILL.md +9 -0
- package/dist/agent-src/skills/git-workflow/SKILL.md +2 -2
- package/dist/agent-src/skills/git-workflow/references/branch-update.md +7 -0
- package/dist/agent-src/skills/git-workflow/references/commit-subject.md +12 -0
- package/dist/agent-src/skills/git-workflow/references/push-closes-its-loop.md +10 -0
- package/dist/agent-src/skills/gtm-launch/SKILL.md +8 -1
- package/dist/agent-src/skills/html-deck/SKILL.md +7 -2
- package/dist/agent-src/skills/humanizer/SKILL.md +16 -3
- package/dist/agent-src/skills/humanizer/references/anti-aiisms.md +13 -1
- package/dist/agent-src/skills/humanizer/references/fixtures.md +8 -0
- package/dist/agent-src/skills/iconography/SKILL.md +4 -2
- package/dist/agent-src/skills/image-provider-routing/SKILL.md +16 -5
- package/dist/agent-src/skills/js-library-packaging/SKILL.md +5 -4
- package/dist/agent-src/skills/js-library-packaging/scripts/check_package_surface.mjs +197 -0
- package/dist/agent-src/skills/judge-artifact-completeness/SKILL.md +1 -1
- package/dist/agent-src/skills/judge-security-auditor/SKILL.md +2 -2
- package/dist/agent-src/skills/launch-readiness/SKILL.md +18 -22
- package/dist/agent-src/skills/learning-to-rule-or-skill/SKILL.md +5 -5
- package/dist/agent-src/skills/livewire/SKILL.md +1 -1
- package/dist/agent-src/skills/llm-provider-knowledge/SKILL.md +18 -0
- package/dist/agent-src/skills/logo-generation/SKILL.md +21 -11
- package/dist/agent-src/skills/market-entry-analysis/SKILL.md +6 -1
- package/dist/agent-src/skills/markitdown/SKILL.md +1 -1
- package/dist/agent-src/skills/memory-consolidation/SKILL.md +2 -2
- package/dist/agent-src/skills/messaging-architecture/SKILL.md +8 -1
- package/dist/agent-src/skills/motion-choreographer/SKILL.md +2 -2
- package/dist/agent-src/skills/playwright-testing/SKILL.md +2 -0
- package/dist/agent-src/skills/positioning-strategy/SKILL.md +10 -2
- package/dist/agent-src/skills/prediction-pool-optimizer/references/ev-fixtures.md +13 -0
- package/dist/agent-src/skills/prediction-pool-optimizer/references/odds-and-bonus.md +7 -0
- package/dist/agent-src/skills/project-analysis-core/SKILL.md +1 -1
- package/dist/agent-src/skills/project-analysis-hypothesis-driven/SKILL.md +1 -1
- package/dist/agent-src/skills/project-analyzer/SKILL.md +14 -75
- package/dist/agent-src/skills/project-analyzer/procedure.yaml +39 -0
- package/dist/agent-src/skills/project-analyzer/references/steps/01-project-overview.md +9 -0
- package/dist/agent-src/skills/project-analyzer/references/steps/02-architecture.md +7 -0
- package/dist/agent-src/skills/project-analyzer/references/steps/03-data-layer.md +6 -0
- package/dist/agent-src/skills/project-analyzer/references/steps/04-business-domains.md +7 -0
- package/dist/agent-src/skills/project-analyzer/references/steps/05-api-surface.md +6 -0
- package/dist/agent-src/skills/project-analyzer/references/steps/06-service-map.md +6 -0
- package/dist/agent-src/skills/project-analyzer/references/steps/07-module-inventory.md +6 -0
- package/dist/agent-src/skills/project-analyzer/references/steps/08-infrastructure-and-testing.md +5 -0
- package/dist/agent-src/skills/project-analyzer/references/steps/09-agent-docs-audit.md +6 -0
- package/dist/agent-src/skills/project-analyzer/references/steps/10-gap-analysis.md +6 -0
- package/dist/agent-src/skills/quality-tools/references/js-ts-tools.md +10 -0
- package/dist/agent-src/skills/quality-tools/references/php-tools.md +13 -0
- package/dist/agent-src/skills/quality-tools/references/python-tools.md +11 -0
- package/dist/agent-src/skills/react-native-setup/SKILL.md +7 -2
- package/dist/agent-src/skills/react-shadcn-ui/SKILL.md +5 -1
- package/dist/agent-src/skills/react-shadcn-ui/references/registry.md +13 -0
- package/dist/agent-src/skills/react-shadcn-ui/scripts/shadcn_add.mjs +315 -0
- package/dist/agent-src/skills/react-shadcn-ui/scripts/shadcn_add.ts +6 -6
- package/dist/agent-src/skills/receiving-code-review/SKILL.md +1 -1
- package/dist/agent-src/skills/refine-ticket/SKILL.md +2 -2
- package/dist/agent-src/skills/release-comms/SKILL.md +2 -1
- package/dist/agent-src/skills/repomix-packer/SKILL.md +2 -2
- package/dist/agent-src/skills/requesting-code-review/SKILL.md +3 -3
- package/dist/agent-src/skills/roadmap-management/references/archival.md +3 -3
- package/dist/agent-src/skills/roadmap-management/references/authoring.md +9 -2
- package/dist/agent-src/skills/roadmap-management/references/dashboard.md +3 -3
- package/dist/agent-src/skills/roadmap-management/references/lifecycle.md +3 -3
- package/dist/agent-src/skills/roadmap-writing/SKILL.md +4 -4
- package/dist/agent-src/skills/rtk-output-filtering/SKILL.md +1 -1
- package/dist/agent-src/skills/rule-writing/SKILL.md +1 -2
- package/dist/agent-src/skills/security/SKILL.md +41 -5
- package/dist/agent-src/skills/skill-improvement-pipeline/SKILL.md +5 -3
- package/dist/agent-src/skills/skill-reviewer/SKILL.md +1 -1
- package/dist/agent-src/skills/skill-writing/SKILL.md +23 -8
- package/dist/agent-src/skills/skill-writing/references/section-patterns.md +19 -1
- package/dist/agent-src/skills/source-discovery/SKILL.md +3 -3
- package/dist/agent-src/skills/sql-writing/SKILL.md +2 -2
- package/dist/agent-src/skills/standards-from-config/SKILL.md +2 -2
- package/dist/agent-src/skills/storybook-workshop/SKILL.md +5 -0
- package/dist/agent-src/skills/storybook-workshop/scripts/story_contrast_floor.mjs +99 -0
- package/dist/agent-src/skills/subagent-orchestration/SKILL.md +13 -13
- package/dist/agent-src/skills/subagent-orchestration/prompts/README.md +4 -3
- package/dist/agent-src/skills/svg-authoring/SKILL.md +324 -0
- package/dist/agent-src/skills/systematic-debugging/SKILL.md +2 -2
- package/dist/agent-src/skills/tailwind-engineer/SKILL.md +8 -3
- package/dist/agent-src/skills/tailwind-engineer/scripts/tailwind_config_gen.mjs +482 -0
- package/dist/agent-src/skills/test-case-discovery/SKILL.md +1 -1
- package/dist/agent-src/skills/threat-modeling/SKILL.md +8 -1
- package/dist/agent-src/skills/threat-modeling/data/manifest.json +5 -2
- package/dist/agent-src/skills/threat-modeling/data/reasoning.csv +10 -10
- package/dist/agent-src/skills/threat-modeling/data/surfaces.csv +11 -10
- package/dist/agent-src/skills/threat-modeling/data/threats.csv +34 -20
- package/dist/agent-src/skills/type-design/SKILL.md +196 -0
- package/dist/agent-src/skills/type-design/references/font-spec.md +90 -0
- package/dist/agent-src/skills/type-design/references/gates.md +113 -0
- package/dist/agent-src/skills/type-design/references/toolchain.md +66 -0
- package/dist/agent-src/skills/typography-system/SKILL.md +21 -11
- package/dist/agent-src/skills/typography-system/references/worked-scales.md +85 -0
- package/dist/agent-src/skills/ui-apply-generic/SKILL.md +1 -1
- package/dist/agent-src/skills/universal-project-analysis/SKILL.md +1 -1
- package/dist/agent-src/skills/upstream-contribute/SKILL.md +120 -125
- package/dist/agent-src/skills/using-git-worktrees/SKILL.md +1 -0
- package/dist/agent-src/skills/verify-repair-loop/SKILL.md +1 -1
- package/dist/agent-src/skills/voice-and-tone-design/SKILL.md +71 -2
- package/dist/agent-src/skills/wireframe/SKILL.md +5 -2
- package/dist/agent-src/skills/worktree-lifecycle/SKILL.md +16 -14
- package/dist/agent-src/templates/AGENTS.md +1 -1
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/agent-src/templates/agents/proposal.example.md +1 -1
- package/dist/agent-src/templates/github-workflows/advisories-daily.yml +85 -0
- package/dist/agent-src/templates/roadmaps.md +39 -1
- package/dist/agent-src/templates/scripts/check_memory.ts +2 -2
- package/dist/agent-src/templates/scripts/historical-bug-patterns.example.yml +13 -1
- package/dist/agent-src/templates/scripts/work_engine/cli.ts +17 -0
- package/dist/agent-src/templates/scripts/work_engine/cli_args.ts +10 -0
- package/dist/agent-src/templates/scripts/work_engine/directives/ui/scaffold.ts +22 -13
- package/dist/agent-src/templates/scripts/work_engine/dispatcher.ts +18 -4
- package/dist/agent-src/templates/scripts/work_engine/procedure/checks.ts +283 -0
- package/dist/agent-src/templates/scripts/work_engine/procedure/run.ts +432 -0
- package/dist/agent-src/templates/scripts/work_engine/procedure/sidecar.ts +185 -0
- package/dist/agent-src/templates/scripts/work_engine/state.ts +3 -0
- package/dist/cli/commands/uiRender.js +265 -13
- package/dist/cli/commands/uiRenderAsset.js +1316 -0
- package/dist/cli/commands/uiRenderAssetCompare.js +752 -0
- package/dist/cli/commands/uiRenderAssetFonts.js +158 -0
- package/dist/cli/commands/uiRenderAssetGrid.js +265 -0
- package/dist/cli/commands/uiRenderAssetGrounds.js +323 -0
- package/dist/cli/commands/uiRenderAssetIsolation.js +85 -0
- package/dist/cli/commands/uiRenderAssetTypes.js +133 -0
- package/dist/cli/initRouting.js +1 -1
- package/dist/cli/main.js +47 -1
- package/dist/cli/mcp/dispatch.js +31 -9
- package/dist/cli/python/knowledge_ingest.js +7 -39
- package/dist/cli/python/workspace_secrets.js +4 -177
- package/dist/cli/registry.js +5 -2
- package/dist/cli-delegate/archive_completed_roadmaps.js +3 -2
- package/dist/cli-delegate/check_update_banner.js +635 -266
- package/dist/cli-delegate/{chunk-AED2VQGM.js → chunk-2CX7NWRB.js} +233 -437
- package/dist/cli-delegate/{chunk-F3QDG2GT.js → chunk-2SFOOFJJ.js} +259 -159
- package/dist/cli-delegate/chunk-2Y5AMLMY.js +136 -0
- package/dist/cli-delegate/{chunk-OKJUIAPS.js → chunk-4E2W63DA.js} +1 -62
- package/dist/cli-delegate/{chunk-6SRJTYJS.js → chunk-4SHNERBL.js} +37 -16
- package/dist/cli-delegate/chunk-5VO3RTMV.js +208 -0
- package/dist/cli-delegate/chunk-6E2LDY4F.js +27 -0
- package/dist/cli-delegate/{chunk-XQTPXKFA.js → chunk-6ZD2QZMN.js} +8 -17
- package/dist/cli-delegate/{chunk-6V5FMA3P.js → chunk-7CEI2YPS.js} +1 -1
- package/dist/cli-delegate/{chunk-DMSZHXZS.js → chunk-7DRWULEQ.js} +80 -7
- package/dist/cli-delegate/chunk-7NXCZSDL.js +54 -0
- package/dist/cli-delegate/chunk-AX3U3UWJ.js +328 -0
- package/dist/cli-delegate/{chunk-WOKWX5PL.js → chunk-BKALSTBZ.js} +18 -3
- package/dist/cli-delegate/chunk-BMGUGSY6.js +103 -0
- package/dist/cli-delegate/chunk-BYV46VRM.js +3309 -0
- package/dist/cli-delegate/{chunk-5GPULQBE.js → chunk-CAL76ADI.js} +4215 -682
- package/dist/cli-delegate/chunk-DSY6LLCQ.js +127 -0
- package/dist/cli-delegate/chunk-E6XNBVE4.js +581 -0
- package/dist/cli-delegate/{chunk-ZUZKZYCN.js → chunk-ESUNQ5XR.js} +46 -10
- package/dist/cli-delegate/{chunk-G5VVIZRC.js → chunk-F7HLSW6P.js} +1 -1
- package/dist/cli-delegate/chunk-FIEEOH5C.js +133 -0
- package/dist/cli-delegate/chunk-FKYZOWVD.js +719 -0
- package/dist/cli-delegate/chunk-GOMILC3O.js +918 -0
- package/dist/cli-delegate/chunk-I4KJYVPB.js +69 -0
- package/dist/cli-delegate/{chunk-JQOM7TTM.js → chunk-IDAOTDPE.js} +1 -100
- package/dist/cli-delegate/chunk-IROAQ77A.js +137 -0
- package/dist/cli-delegate/chunk-J2SJZICO.js +698 -0
- package/dist/cli-delegate/{chunk-6YQC2HLF.js → chunk-JHMMWNJ2.js} +54 -40
- package/dist/cli-delegate/{chunk-WQWXGIWB.js → chunk-M6CRG5O6.js} +165 -15
- package/dist/cli-delegate/{chunk-PHD7OOLE.js → chunk-MQ2UDEGV.js} +14 -3
- package/dist/cli-delegate/chunk-NTM7G2V4.js +220 -0
- package/dist/cli-delegate/chunk-PV7S5DPJ.js +175 -0
- package/dist/cli-delegate/{chunk-F7DQWYR5.js → chunk-RAXQOETZ.js} +6 -2
- package/dist/cli-delegate/{chunk-T7XSJSUQ.js → chunk-SIOMDAKL.js} +980 -784
- package/dist/cli-delegate/{chunk-KR6EM23A.js → chunk-TULVDQ4L.js} +3 -48
- package/dist/cli-delegate/chunk-UUYEKXKR.js +20 -0
- package/dist/cli-delegate/{chunk-7XGD2OEU.js → chunk-UXWHY6CQ.js} +2 -1
- package/dist/cli-delegate/{chunk-PWX7CKMD.js → chunk-VC3ECUEK.js} +1 -1
- package/dist/cli-delegate/{chunk-KXXWJTDO.js → chunk-WMNYVIYX.js} +1 -1
- package/dist/cli-delegate/chunk-XCY5KKUC.js +181 -0
- package/dist/cli-delegate/{chunk-H76PHZBI.js → chunk-XLY5G3CM.js} +14 -19
- package/dist/cli-delegate/cmd_adr_effective.js +41 -2
- package/dist/cli-delegate/cmd_analyze_session.js +7 -7
- package/dist/cli-delegate/cmd_brand_status.js +1287 -10
- package/dist/cli-delegate/cmd_ci_settle.js +330 -0
- package/dist/cli-delegate/cmd_conformance.js +669 -822
- package/dist/cli-delegate/cmd_doctor.js +26 -16
- package/dist/cli-delegate/cmd_doctor_neighbours.js +10 -7
- package/dist/cli-delegate/cmd_explain.js +9 -24
- package/dist/cli-delegate/cmd_export.js +14 -11
- package/dist/cli-delegate/cmd_fleet.js +5 -3
- package/dist/cli-delegate/cmd_git_convention.js +8 -4
- package/dist/cli-delegate/cmd_handoff.js +79 -242
- package/dist/cli-delegate/cmd_mcp_available.js +1 -1
- package/dist/cli-delegate/cmd_memory_get.js +4 -2
- package/dist/cli-delegate/cmd_migrate.js +2 -2
- package/dist/cli-delegate/cmd_packs_active.js +2 -2
- package/dist/cli-delegate/cmd_preflight.js +7 -5
- package/dist/cli-delegate/cmd_prune.js +16 -13
- package/dist/cli-delegate/cmd_route_audit.js +11 -8
- package/dist/cli-delegate/cmd_route_explain.js +649 -16
- package/dist/cli-delegate/cmd_session_recycle.js +15 -6
- package/dist/cli-delegate/cmd_settings_check.js +2 -2
- package/dist/cli-delegate/cmd_settings_get.js +1 -1
- package/dist/cli-delegate/cmd_settings_migrate.js +14 -11
- package/dist/cli-delegate/cmd_settings_set.js +1 -1
- package/dist/cli-delegate/cmd_sync.js +16 -13
- package/dist/cli-delegate/cmd_uninstall.js +185 -31
- package/dist/cli-delegate/cmd_update.js +67 -3
- package/dist/cli-delegate/cmd_upgrade.js +38 -7
- package/dist/cli-delegate/cmd_validate.js +16 -13
- package/dist/cli-delegate/cmd_versions.js +1 -1
- package/dist/cli-delegate/dashboard_mode.js +1 -1
- package/dist/cli-delegate/gate_execute.js +4 -3
- package/dist/cli-delegate/roadmap_age.js +22 -0
- package/dist/cli-delegate/roadmap_gates.js +4 -3
- package/dist/cli-delegate/update_roadmap_progress.js +3 -2
- package/dist/cli-delegate/upgrade_unattended-VVL6JLSX.js +541 -0
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +409 -201
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +8 -8
- package/dist/discovery/dormancy-report.md +1 -1
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +36 -27
- package/dist/discovery/trust-report.md +3 -3
- package/dist/discovery/workspaces.json +25 -16
- package/dist/hooks/chat_history.chunk.cjs +6852 -0
- package/dist/hooks/chat_history.chunk.cjs.sha256 +1 -0
- package/dist/hooks/dispatch.js +17706 -18627
- package/dist/hooks/dispatch.sha256 +1 -1
- package/dist/hooks/egress_engine.chunk.cjs +915 -0
- package/dist/hooks/egress_engine.chunk.cjs.sha256 +1 -0
- package/dist/hooks/svg_rules.chunk.cjs +1858 -0
- package/dist/hooks/svg_rules.chunk.cjs.sha256 +1 -0
- package/dist/install/conflict.js +36 -6
- package/dist/install/conflictTracker.js +81 -7
- package/dist/install/detect.js +3 -3
- package/dist/install/hostPermissionsAdapter.js +106 -0
- package/dist/install/install.mjs +3117 -1949
- package/dist/install/installThinLayer.js +1 -1
- package/dist/install/manifestFiles.js +21 -0
- package/dist/install/noGoDenyRows.js +300 -0
- package/dist/install/noGoRules.js +368 -0
- package/dist/install/ownershipLedger.js +133 -0
- package/dist/install/plan.js +1 -1
- package/dist/install/preflight.js +39 -8
- package/dist/install/preserve.js +74 -12
- package/dist/install/recordedOwnership.js +88 -5
- package/dist/install/rule_scope.js +20 -3
- package/dist/install/stagedRuntime.js +455 -0
- package/dist/install/toolsArg.js +19 -0
- package/dist/install/userPin.js +47 -0
- package/dist/install/wizard-plan.js +26 -4
- package/dist/mcp/awesome-mcp-servers.row.md +1 -1
- package/dist/mcp/mcp-cloudflare-catalogue.json +3 -2
- package/dist/mcp/registry-manifest.json +6 -5
- package/dist/mcp/server.json +3 -3
- package/dist/mcp/server.mjs +9814 -5333
- package/dist/router.json +1 -1
- package/dist/scripts/_lib/agent_settings.js +7 -12
- package/dist/scripts/_lib/asset_render_record.js +219 -0
- package/dist/scripts/_lib/claude_builtin_names.js +9 -4
- package/dist/scripts/_lib/claude_settings_hooks.js +102 -34
- package/dist/scripts/_lib/egress_rules.js +766 -0
- package/dist/scripts/_lib/environment_detector.js +24 -33
- package/dist/scripts/_lib/global_deploy_inventory.js +146 -9
- package/dist/scripts/_lib/guidelines_lane.js +10 -1
- package/dist/scripts/_lib/identifier_redaction.js +24 -0
- package/dist/scripts/_lib/installed_layer.js +17 -0
- package/dist/scripts/_lib/installed_layer_normalise.js +72 -0
- package/dist/scripts/_lib/lean_projection_mode.js +34 -152
- package/dist/scripts/_lib/project_tree_form.js +118 -0
- package/dist/scripts/_lib/scan_scope.js +105 -0
- package/dist/scripts/_lib/scoped_projection.js +133 -3
- package/dist/scripts/_lib/settings_removed_keys.js +29 -0
- package/dist/scripts/_lib/settings_renamed_keys.js +17 -5
- package/dist/scripts/_lib/subagent_native_fields.js +35 -0
- package/dist/scripts/_lib/svg_dom.js +284 -0
- package/dist/scripts/_lib/svg_filled_counter.js +285 -0
- package/dist/scripts/_lib/svg_geometry.js +477 -0
- package/dist/scripts/_lib/svg_motion.js +179 -0
- package/dist/scripts/_lib/svg_rules.js +1339 -0
- package/dist/scripts/_lib/svg_urls.js +67 -0
- package/dist/scripts/_lib/ui_surface.js +19 -0
- package/dist/scripts/_lib/verification_command.js +364 -0
- package/dist/scripts/_lib/workspace_secrets.js +229 -0
- package/dist/scripts/ai_council/seat_policy.js +27 -0
- package/dist/scripts/ai_council/transport_resolver.js +9 -10
- package/dist/scripts/condense.js +25 -29
- package/dist/scripts/lint_svg_asset.js +324 -0
- package/dist/server/io/substituteTemplate.js +2 -2
- package/dist/server/io/yamlIO.js +2 -2
- package/dist/server/routes/install.js +5 -1
- package/dist/server/routes/settings.js +2 -2
- package/dist/server/routes/wizard.js +1 -1
- package/dist/server/schemas/settings.js +22 -16
- package/dist/server/serverInfo.js +1 -1
- package/dist/shared/brandTokenDocument.js +47 -0
- package/dist/shared/brandTokenPaths.js +43 -0
- package/dist/shared/settingsCarveOut.js +0 -7
- package/dist/shared/skillRanking.js +103 -5
- package/dist/shared/userMd/formAdapter.js +16 -0
- package/dist/shared/userMd/schema.js +17 -0
- package/dist/ui/assets/index-w70Ado09.js +23 -0
- package/dist/ui/index.html +1 -1
- package/docs/MIGRATION.md +89 -2
- package/docs/THIRD-PARTY-NOTICES.md +27 -0
- package/docs/contracts/settings-classes.md +27 -9
- package/docs/guidelines/agent-infra/api-cost-levers.md +4 -3
- package/docs/guidelines/agent-infra/model-recommendation.md +11 -5
- package/docs/guidelines/agent-infra/rule-body-migration-inventory.md +1 -1
- package/docs/guidelines/agent-infra/self-improvement-pipeline.md +47 -24
- package/docs/guidelines/design-antipatterns.md +6 -1
- package/docs/guidelines/design-asset-discipline.md +17 -0
- package/docs/guidelines/design-fidelity-routing.md +1 -0
- package/docs/guidelines/php/api-design.md +10 -1
- package/llms.txt +12 -10
- package/package.json +15 -5
- package/src/agent-src/scripts/dashboard_mode.ts +4 -1
- package/src/agent-src/scripts/roadmap_age.ts +175 -0
- package/src/agent-src/scripts/update_roadmap_progress.ts +10 -2
- package/src/agent-src/templates/AGENTS.md +1 -1
- package/src/agent-src/templates/scripts/check_memory.ts +2 -2
- package/src/agent-src/templates/scripts/historical-bug-patterns.example.yml +13 -1
- package/src/agent-src/templates/scripts/work_engine/cli.ts +17 -0
- package/src/agent-src/templates/scripts/work_engine/cli_args.ts +10 -0
- package/src/agent-src/templates/scripts/work_engine/directives/ui/scaffold.ts +22 -13
- package/src/agent-src/templates/scripts/work_engine/dispatcher.ts +18 -4
- package/src/agent-src/templates/scripts/work_engine/procedure/checks.ts +283 -0
- package/src/agent-src/templates/scripts/work_engine/procedure/run.ts +432 -0
- package/src/agent-src/templates/scripts/work_engine/procedure/sidecar.ts +185 -0
- package/src/agent-src/templates/scripts/work_engine/state.ts +3 -0
- package/src/config/agent-settings.template.yml +120 -57
- package/src/config/agents-paths.yml +13 -7
- package/src/config/ci-local-parity.yml +44 -0
- package/src/config/continuity-surface.json +51 -2
- package/src/config/depth-audit-samples.json +5 -0
- package/src/config/discovery/command-verbs.yml +1 -0
- package/src/config/dispatch-economy-metrics.json +1 -0
- package/src/config/egress-mutations/class-precedence-swapped.json +7 -0
- package/src/config/egress-mutations/credential-token-restores.json +7 -0
- package/src/config/egress-mutations/iban-checksum-disabled.json +7 -0
- package/src/config/egress-mutations/raw-value-in-the-count-record.json +7 -0
- package/src/config/evaluator-budgets.json +4 -4
- package/src/config/gate-coverage.yml +149 -3
- package/src/config/gate-reachability-exemptions.json +3 -5
- package/src/config/gate-violation-baselines.json +29 -25
- package/src/config/gitignore-block.txt +6 -3
- package/src/config/hook-bundle-budget.json +34 -3
- package/src/config/hook-carrier-probes.json +30 -0
- package/src/config/hook-latency-budget.json +4 -2
- package/src/config/host-injection-effect.json +8 -1
- package/src/config/host-skill-frontmatter-keys.json +56 -0
- package/src/config/layer-form-brakes.json +4 -0
- package/src/config/layer-form-comparison-corpus.json +42 -0
- package/src/config/loop-surfaces.yaml +28 -47
- package/src/config/memory-twin-verdicts.yml +9 -1
- package/src/config/metric-registry.yml +40 -3
- package/src/config/pack-size-budget.json +15 -0
- package/src/config/procedure-core-budget.json +21 -0
- package/src/config/profiles/minimal.ini +1 -1
- package/src/config/project-tree-form.json +24 -0
- package/src/config/reach-channels.yml +3 -3
- package/src/config/reach-prescriptions-intake.yml +1 -1
- package/src/config/roadmap-step-size-baseline.json +118 -0
- package/src/config/routing-coverage-seed.json +100 -2
- package/src/config/skill-body-budget.json +686 -0
- package/src/config/skill-fork-pilot.json +17 -0
- package/src/config/skill-lifecycle-probes-codex.json +57 -0
- package/src/config/skill-lifecycle-probes.json +57 -0
- package/src/config/skill-output-shape.json +316 -0
- package/src/config/step-depth-table.json +58 -0
- package/src/config/turnaround-budget.json +90 -0
- package/src/install/conflict.ts +38 -6
- package/src/install/conflictTracker.ts +126 -8
- package/src/install/detect.ts +3 -3
- package/src/install/hostPermissionsAdapter.ts +136 -0
- package/src/install/installThinLayer.ts +6 -1
- package/src/install/manifestFiles.ts +24 -0
- package/src/install/noGoDenyRows.ts +368 -0
- package/src/install/noGoRules.ts +438 -0
- package/src/install/ownershipLedger.ts +180 -0
- package/src/install/plan.ts +1 -1
- package/src/install/preflight.ts +42 -10
- package/src/install/preserve.ts +94 -12
- package/src/install/recordedOwnership.ts +121 -3
- package/src/install/rule_scope.ts +20 -3
- package/src/install/stagedRuntime.ts +528 -0
- package/src/install/toolsArg.ts +16 -0
- package/src/install/types.ts +23 -2
- package/src/install/userPin.ts +54 -0
- package/src/install/wizard-plan.ts +25 -4
- package/src/scripts/_cli/_doctor_emit.ts +24 -0
- package/src/scripts/_cli/cmd_brand_status.ts +223 -12
- package/src/scripts/_cli/cmd_ci_settle.ts +32 -0
- package/src/scripts/_cli/cmd_doctor.ts +23 -44
- package/src/scripts/_cli/cmd_route_explain.ts +176 -2
- package/src/scripts/_cli/cmd_uninstall.ts +270 -26
- package/src/scripts/_cli/cmd_update.ts +34 -1
- package/src/scripts/_cli/cmd_upgrade.ts +38 -2
- package/src/scripts/_cli/detection_report.ts +7 -0
- package/src/scripts/_cli/doctor_execution.ts +1 -2
- package/src/scripts/_cli/doctor_install_mode.ts +39 -0
- package/src/scripts/_cli/doctor_network_posture.ts +3 -3
- package/src/scripts/_cli/doctor_ownership_ledger.ts +126 -0
- package/src/scripts/_cli/doctor_update_verdict.ts +104 -0
- package/src/scripts/_cli/explain_last/scrubber.ts +9 -21
- package/src/scripts/_cli/upgrade_unattended.ts +548 -0
- package/src/scripts/_dispatch.bash +42 -9
- package/src/scripts/_lib/activation_ladder.ts +1 -1
- package/src/scripts/_lib/advisory_inventory.ts +405 -0
- package/src/scripts/_lib/advisory_notice.ts +270 -0
- package/src/scripts/_lib/advisory_sources.ts +752 -0
- package/src/scripts/_lib/advisory_verdicts.ts +363 -0
- package/src/scripts/_lib/agent_artifact_location.ts +177 -0
- package/src/scripts/_lib/agent_settings.ts +7 -12
- package/src/scripts/_lib/arrival_record.ts +217 -0
- package/src/scripts/_lib/asset_render_record.ts +247 -0
- package/src/scripts/_lib/billing_grant.ts +4 -2
- package/src/scripts/_lib/brand_voice_slot.ts +258 -0
- package/src/scripts/_lib/canary_status.ts +24 -0
- package/src/scripts/_lib/cc_transcript.ts +276 -0
- package/src/scripts/_lib/changelog_eras.ts +83 -0
- package/src/scripts/_lib/claim_phrases.ts +71 -0
- package/src/scripts/_lib/classify_update.ts +171 -0
- package/src/scripts/_lib/claude_builtin_names.ts +9 -4
- package/src/scripts/_lib/claude_hook_scope.ts +195 -0
- package/src/scripts/_lib/claude_settings_hooks.ts +118 -34
- package/src/scripts/_lib/codex_resident_set.ts +460 -0
- package/src/scripts/_lib/codex_skill_finder.ts +225 -0
- package/src/scripts/_lib/confusables.ts +19 -0
- package/src/scripts/_lib/consumer_impact.ts +199 -0
- package/src/scripts/_lib/content_class.ts +127 -0
- package/src/scripts/_lib/counted_probe.ts +68 -1
- package/src/scripts/_lib/credential_deny_rows.ts +151 -0
- package/src/scripts/_lib/credential_files.ts +367 -0
- package/src/scripts/_lib/delivery_arm_experiment.ts +2 -2
- package/src/scripts/_lib/depth_flip.ts +141 -0
- package/src/scripts/_lib/design_lock.ts +117 -0
- package/src/scripts/_lib/design_sync_ledger.ts +142 -0
- package/src/scripts/_lib/design_sync_plan.ts +177 -0
- package/src/scripts/_lib/design_sync_reconcile.ts +159 -0
- package/src/scripts/_lib/design_sync_verify.ts +99 -0
- package/src/scripts/_lib/design_system_claude_design.ts +323 -0
- package/src/scripts/_lib/design_system_import.ts +84 -6
- package/src/scripts/_lib/dispatch_import_closure.ts +42 -11
- package/src/scripts/_lib/egress_credential_env.ts +35 -0
- package/src/scripts/_lib/egress_engine.ts +22 -0
- package/src/scripts/_lib/egress_known_values.ts +223 -0
- package/src/scripts/_lib/egress_normalize.ts +67 -0
- package/src/scripts/_lib/egress_prefixes.ts +42 -0
- package/src/scripts/_lib/egress_rules.ts +896 -0
- package/src/scripts/_lib/egress_rules_linearity.ts +56 -0
- package/src/scripts/_lib/egress_sender_scan.ts +290 -0
- package/src/scripts/_lib/egress_session_table.ts +510 -0
- package/src/scripts/_lib/egress_text.ts +63 -0
- package/src/scripts/_lib/egress_token.ts +252 -0
- package/src/scripts/_lib/empty_cycles.ts +1 -1
- package/src/scripts/_lib/environment_detector.ts +45 -30
- package/src/scripts/_lib/exemption_shape.ts +75 -1
- package/src/scripts/_lib/floor_posture.ts +185 -0
- package/src/scripts/_lib/forge_protection.ts +140 -2
- package/src/scripts/_lib/global_deploy_inventory.ts +159 -6
- package/src/scripts/_lib/guidelines_lane.ts +12 -1
- package/src/scripts/_lib/hand_kinds.ts +65 -0
- package/src/scripts/_lib/headless_invocation.ts +3 -3
- package/src/scripts/_lib/host_capability.ts +34 -2
- package/src/scripts/_lib/host_env_write.ts +39 -18
- package/src/scripts/_lib/host_listing_model.ts +14 -7
- package/src/scripts/_lib/identifier_redaction.ts +26 -0
- package/src/scripts/_lib/impact_bump.ts +70 -0
- package/src/scripts/_lib/install_reach_checks.ts +53 -1
- package/src/scripts/_lib/installed_layer.ts +31 -0
- package/src/scripts/_lib/installed_layer_normalise.ts +75 -0
- package/src/scripts/_lib/instructions_loaded_classify.ts +75 -0
- package/src/scripts/_lib/instructions_loaded_event.ts +53 -0
- package/src/scripts/_lib/knowledge_global_redaction.ts +2 -2
- package/src/scripts/_lib/layer_form_corpus.ts +165 -0
- package/src/scripts/_lib/layer_form_decision.ts +248 -0
- package/src/scripts/_lib/layer_form_evidence.ts +112 -0
- package/src/scripts/_lib/lean_projection_mode.ts +32 -161
- package/src/scripts/_lib/llm_proposer_transport.ts +1 -0
- package/src/scripts/_lib/loop_guards.ts +8 -120
- package/src/scripts/_lib/major_surface.ts +207 -0
- package/src/scripts/_lib/mcp_bridge.ts +30 -69
- package/src/scripts/_lib/merge_clearance_record.ts +384 -0
- package/src/scripts/_lib/neighbour_census.ts +18 -1
- package/src/scripts/_lib/orchestration_record.ts +61 -0
- package/src/scripts/_lib/outcome_vocabularies.ts +15 -23
- package/src/scripts/_lib/pin_advice.ts +49 -0
- package/src/scripts/_lib/platform_anchor.ts +222 -0
- package/src/scripts/_lib/procedure_checks.ts +5 -0
- package/src/scripts/_lib/project_tree_form.ts +134 -0
- package/src/scripts/_lib/projection_key_migration.ts +122 -0
- package/src/scripts/_lib/prompt_shape.ts +13 -2
- package/src/scripts/_lib/provenance_verify.ts +272 -0
- package/src/scripts/_lib/quota_parked.ts +27 -1
- package/src/scripts/_lib/ratification_artifact.ts +64 -1
- package/src/scripts/_lib/ratification_subject.ts +508 -46
- package/src/scripts/_lib/release_findings_ingest.ts +52 -5
- package/src/scripts/_lib/release_highlights.ts +72 -2
- package/src/scripts/_lib/release_settings_surface.ts +392 -0
- package/src/scripts/_lib/review_round.ts +134 -0
- package/src/scripts/_lib/roadmap_checkboxes.ts +1 -1
- package/src/scripts/_lib/roadmap_slug_lock.ts +254 -0
- package/src/scripts/_lib/roadmap_step_size.ts +75 -0
- package/src/scripts/_lib/run_checkpoint.ts +60 -3
- package/src/scripts/_lib/runtime_wiring_checks.ts +15 -1
- package/src/scripts/_lib/scope_guard.sh +64 -5
- package/src/scripts/_lib/scoped_projection.ts +136 -3
- package/src/scripts/_lib/secret_detector.ts +24 -103
- package/src/scripts/_lib/security_fix.ts +429 -0
- package/src/scripts/_lib/security_fix_context.ts +133 -0
- package/src/scripts/_lib/self_repair.ts +69 -39
- package/src/scripts/_lib/self_repair_outgoing.ts +209 -0
- package/src/scripts/_lib/self_repair_payload.ts +86 -0
- package/src/scripts/_lib/session_eol.ts +3 -0
- package/src/scripts/_lib/session_pr_watch.ts +207 -0
- package/src/scripts/_lib/session_register.ts +51 -1
- package/src/scripts/_lib/session_tag.ts +51 -0
- package/src/scripts/_lib/settings_removed_keys.ts +30 -0
- package/src/scripts/_lib/settings_renamed_keys.ts +19 -5
- package/src/scripts/_lib/skill_catalogue.ts +66 -25
- package/src/scripts/_lib/skill_fork_pilot.ts +121 -0
- package/src/scripts/_lib/skill_leases.ts +203 -0
- package/src/scripts/_lib/skill_listing_overrides.ts +294 -0
- package/src/scripts/_lib/skill_materialisation.ts +388 -0
- package/src/scripts/_lib/skill_materialisation_gate.ts +210 -0
- package/src/scripts/_lib/skill_materialisation_lock.ts +149 -0
- package/src/scripts/_lib/skill_materialisation_triggers.ts +96 -0
- package/src/scripts/_lib/skill_origin.ts +21 -1
- package/src/scripts/_lib/skill_reach.ts +342 -0
- package/src/scripts/_lib/source_shape.ts +7 -2
- package/src/scripts/_lib/sqlite_guard.ts +1 -1
- package/src/scripts/_lib/step_anatomy.ts +330 -0
- package/src/scripts/_lib/subagent_capsule.ts +8 -2
- package/src/scripts/_lib/subagent_native_fields.ts +30 -0
- package/src/scripts/_lib/subagent_spawn.ts +36 -0
- package/src/scripts/_lib/subagent_steering.ts +367 -0
- package/src/scripts/_lib/svg_dom.ts +318 -0
- package/src/scripts/_lib/svg_filled_counter.ts +287 -0
- package/src/scripts/_lib/svg_geometry.ts +480 -0
- package/src/scripts/_lib/svg_motion.ts +208 -0
- package/src/scripts/_lib/svg_rules.ts +1455 -0
- package/src/scripts/_lib/svg_urls.ts +72 -0
- package/src/scripts/_lib/test_delta.ts +89 -6
- package/src/scripts/_lib/tier_downshift_verdict.ts +222 -0
- package/src/scripts/_lib/tool_result_census.ts +62 -0
- package/src/scripts/_lib/trigger_routers.ts +1 -0
- package/src/scripts/_lib/turnaround_throughput.ts +378 -0
- package/src/scripts/_lib/ui_surface.ts +22 -0
- package/src/scripts/_lib/unattended_gates.ts +100 -0
- package/src/scripts/_lib/undeployed_fixes.ts +34 -0
- package/src/scripts/_lib/update_check.ts +143 -12
- package/src/scripts/_lib/update_invite.ts +116 -0
- package/src/scripts/_lib/update_notice.ts +259 -0
- package/src/scripts/_lib/verification_command.ts +59 -0
- package/src/scripts/_lib/verify_clause.ts +31 -19
- package/src/scripts/_lib/version_order.ts +146 -0
- package/src/scripts/_lib/worker_reuse.ts +191 -0
- package/src/scripts/_lib/workspace_secrets.ts +273 -0
- package/src/scripts/_lib/worktree_git.ts +67 -0
- package/src/scripts/_lib/worktree_ignored.ts +116 -0
- package/src/scripts/adoption_status.ts +330 -3
- package/src/scripts/adr_cite_check.ts +93 -0
- package/src/scripts/advisories.ts +803 -0
- package/src/scripts/ai-image/adapters/flux.sh +1 -0
- package/src/scripts/ai-image/adapters/gemini-image.sh +1 -0
- package/src/scripts/ai-image/adapters/ideogram.sh +1 -0
- package/src/scripts/ai-image/adapters/recraft.sh +1 -0
- package/src/scripts/ai-video/adapters/comfyui.sh +1 -0
- package/src/scripts/ai-video/adapters/fal.sh +1 -0
- package/src/scripts/ai-video/adapters/gemini-veo.sh +1 -0
- package/src/scripts/ai-video/adapters/higgsfield.sh +1 -0
- package/src/scripts/ai-video/adapters/kling.sh +1 -0
- package/src/scripts/ai-video/adapters/musetalk.sh +1 -0
- package/src/scripts/ai-video/adapters/openai-images.sh +1 -0
- package/src/scripts/ai-video/adapters/replicate.sh +1 -0
- package/src/scripts/ai-video/adapters/sora.sh +1 -0
- package/src/scripts/ai-video/adapters/syncso.sh +1 -0
- package/src/scripts/ai-video/stitch.sh +115 -2
- package/src/scripts/ai_council/airgap.ts +1 -0
- package/src/scripts/ai_council/api_transport.ts +27 -4
- package/src/scripts/ai_council/budget_guard.ts +300 -11
- package/src/scripts/ai_council/bundler.ts +20 -29
- package/src/scripts/ai_council/chairman.ts +17 -2
- package/src/scripts/ai_council/cli_agency_bounds.ts +158 -0
- package/src/scripts/ai_council/cli_launch_env.ts +54 -0
- package/src/scripts/ai_council/cli_least_agency_canary.ts +18 -2
- package/src/scripts/ai_council/clients.ts +55 -73
- package/src/scripts/ai_council/config.ts +16 -22
- package/src/scripts/ai_council/council_egress.ts +167 -0
- package/src/scripts/ai_council/exposure_ledger.ts +117 -0
- package/src/scripts/ai_council/free_hand_shadow.ts +276 -0
- package/src/scripts/ai_council/free_route.ts +216 -0
- package/src/scripts/ai_council/free_route_catalog.ts +173 -0
- package/src/scripts/ai_council/free_route_client.ts +95 -0
- package/src/scripts/ai_council/free_rung.ts +61 -0
- package/src/scripts/ai_council/hand_payload.ts +230 -0
- package/src/scripts/ai_council/hand_schemas.ts +123 -0
- package/src/scripts/ai_council/jury_aggregate.ts +155 -1
- package/src/scripts/ai_council/kind_flip.ts +221 -0
- package/src/scripts/ai_council/kind_flip_state.ts +19 -0
- package/src/scripts/ai_council/learn_low_impact_preview.ts +50 -22
- package/src/scripts/ai_council/modification_review.ts +98 -5
- package/src/scripts/ai_council/necessity.ts +7 -0
- package/src/scripts/ai_council/orchestrator.ts +16 -16
- package/src/scripts/ai_council/pricing.ts +10 -0
- package/src/scripts/ai_council/redact_low_impact_entry.ts +5 -2
- package/src/scripts/ai_council/response_render.ts +27 -4
- package/src/scripts/ai_council/round_reservations.ts +119 -0
- package/src/scripts/ai_council/route_chooser.ts +161 -0
- package/src/scripts/ai_council/route_liveness.ts +152 -0
- package/src/scripts/ai_council/route_member.ts +52 -0
- package/src/scripts/ai_council/seat_policy.ts +40 -0
- package/src/scripts/ai_council/seat_resume.ts +40 -0
- package/src/scripts/ai_council/shadow_dispatch.ts +13 -3
- package/src/scripts/ai_council/spend_gate.ts +18 -2
- package/src/scripts/ai_council/status_surface.ts +37 -0
- package/src/scripts/ai_council/transport_resolver.ts +15 -18
- package/src/scripts/analysis_analysts.ts +226 -0
- package/src/scripts/analysis_anchors.ts +140 -0
- package/src/scripts/analysis_and_optimize.ts +44 -0
- package/src/scripts/analysis_emit.ts +150 -0
- package/src/scripts/analysis_exchange.ts +186 -0
- package/src/scripts/analysis_gate.ts +158 -0
- package/src/scripts/analysis_inbox_final.ts +173 -0
- package/src/scripts/analysis_ledger.ts +119 -0
- package/src/scripts/analysis_pr_optimize.ts +219 -0
- package/src/scripts/analysis_pr_review.ts +240 -0
- package/src/scripts/analysis_pr_target.ts +121 -0
- package/src/scripts/analysis_prompts.ts +130 -0
- package/src/scripts/analysis_publish.ts +358 -0
- package/src/scripts/analysis_questions.ts +78 -0
- package/src/scripts/analysis_run.ts +248 -0
- package/src/scripts/analysis_run_record.ts +210 -0
- package/src/scripts/analysis_target.ts +343 -0
- package/src/scripts/analysis_validate.ts +161 -0
- package/src/scripts/anchor_eval_falsify.ts +1 -1
- package/src/scripts/arrival_report.ts +115 -0
- package/src/scripts/ask_transport.ts +284 -35
- package/src/scripts/backfill_model_tier.ts +4 -5
- package/src/scripts/before_complete_hook.ts +48 -2
- package/src/scripts/bench_hook_latency.ts +3 -3
- package/src/scripts/bench_procedure_cursor.ts +440 -0
- package/src/scripts/bootstrap.sh +3 -3
- package/src/scripts/build_exit_code_register.ts +440 -0
- package/src/scripts/build_proof.ts +23 -1
- package/src/scripts/cache_realization_report.ts +155 -14
- package/src/scripts/canary_liveness.ts +117 -0
- package/src/scripts/chat_history.ts +12 -15
- package/src/scripts/check_account_surface.ts +318 -0
- package/src/scripts/check_agent_artifact_location.ts +17 -160
- package/src/scripts/check_bundle_path_leakage.ts +6 -0
- package/src/scripts/check_ci_local_parity.ts +21 -13
- package/src/scripts/check_claims.ts +77 -2
- package/src/scripts/check_completion_review.ts +34 -1
- package/src/scripts/check_concern_admissions.ts +41 -5
- package/src/scripts/check_consumer_scope_flip.ts +1 -1
- package/src/scripts/check_context_paths.ts +2 -0
- package/src/scripts/check_council_layout.ts +9 -0
- package/src/scripts/check_credential_spelling.ts +267 -0
- package/src/scripts/check_egress_mutations.ts +239 -0
- package/src/scripts/check_enforcement_coverage.ts +16 -3
- package/src/scripts/check_estate_count.ts +16 -1
- package/src/scripts/check_finding_dispositions.ts +147 -0
- package/src/scripts/check_gate_coverage.ts +123 -21
- package/src/scripts/check_gate_reachability.ts +16 -2
- package/src/scripts/check_generator_sync.ts +46 -0
- package/src/scripts/check_hook_bundle_composition.ts +139 -1
- package/src/scripts/check_host_docs_digest.ts +142 -5
- package/src/scripts/check_kernel_edit_ratified.ts +301 -76
- package/src/scripts/check_kernel_rule_bundle.ts +14 -10
- package/src/scripts/check_knowledge_cards.ts +83 -3
- package/src/scripts/check_memory.ts +13 -3
- package/src/scripts/check_pack_size.ts +1 -1
- package/src/scripts/check_permission_deny_syntax.ts +247 -0
- package/src/scripts/check_platform_anchor.ts +243 -0
- package/src/scripts/check_proposal.ts +183 -1
- package/src/scripts/check_reach_channels.ts +5 -3
- package/src/scripts/check_references.ts +5 -2
- package/src/scripts/check_release_manifest_content.ts +33 -0
- package/src/scripts/check_release_pr_shape.ts +5 -0
- package/src/scripts/check_rule_invariants.ts +9 -1
- package/src/scripts/check_skill_body_budget.ts +343 -0
- package/src/scripts/check_skill_requires.ts +137 -1
- package/src/scripts/check_step_receipts.ts +246 -0
- package/src/scripts/check_structural_breaking.ts +7 -1
- package/src/scripts/check_suppression_hygiene.ts +8 -0
- package/src/scripts/check_test_weakening.ts +14 -0
- package/src/scripts/check_token_quality_golden.ts +5 -1
- package/src/scripts/check_update_banner.ts +50 -3
- package/src/scripts/check_verify_expectation_delta.ts +2 -1
- package/src/scripts/check_voice_lexicon.ts +531 -0
- package/src/scripts/ci_os_attribution.ts +327 -0
- package/src/scripts/ci_platform_scope.ts +69 -0
- package/src/scripts/ci_settle.ts +4 -1
- package/src/scripts/ci_status.ts +0 -1
- package/src/scripts/ci_time_to_green.ts +748 -0
- package/src/scripts/classify_merge_risk.ts +407 -19
- package/src/scripts/code_graph/sqlite_store.ts +1 -1
- package/src/scripts/compile_trigger_evals.ts +353 -0
- package/src/scripts/condense.ts +25 -31
- package/src/scripts/consumer_matrix.ts +61 -3
- package/src/scripts/council_availability_hook.ts +12 -4
- package/src/scripts/council_cli.ts +48 -38
- package/src/scripts/cross_repo_retrieve.ts +9 -72
- package/src/scripts/design_lock.ts +106 -0
- package/src/scripts/design_slop_rules.ts +30 -0
- package/src/scripts/design_sync.ts +264 -0
- package/src/scripts/design_system_import.ts +30 -13
- package/src/scripts/dispatch_economy_report.ts +46 -1
- package/src/scripts/dispatch_r2_reviewer.ts +41 -5
- package/src/scripts/egress_canary.ts +166 -0
- package/src/scripts/egress_sender_census.ts +101 -0
- package/src/scripts/external_sources_denylist.json +94 -12
- package/src/scripts/generate_catalog.ts +6 -2
- package/src/scripts/generate_owned_digest.ts +163 -0
- package/src/scripts/generate_reference_toc.ts +143 -0
- package/src/scripts/generate_subagent_floor.ts +11 -1
- package/src/scripts/handoff_context_hook.ts +34 -0
- package/src/scripts/hook_carrier_census.ts +263 -0
- package/src/scripts/hook_chunks.d.mts +12 -0
- package/src/scripts/hook_chunks.mjs +114 -0
- package/src/scripts/hook_manifest.json +1 -1
- package/src/scripts/hook_manifest.yaml +65 -42
- package/src/scripts/hooks/block_config_weakening.ts +127 -3
- package/src/scripts/hooks/block_credential_reads.ts +165 -0
- package/src/scripts/hooks/block_no_verify.ts +47 -23
- package/src/scripts/hooks/block_speaking_inbox_dir.ts +54 -8
- package/src/scripts/hooks/category_a.ts +202 -25
- package/src/scripts/hooks/concern_failure_policy.ts +6 -6
- package/src/scripts/hooks/concern_registry.ts +31 -6
- package/src/scripts/hooks/concern_registry_types.ts +11 -0
- package/src/scripts/hooks/delegation_nudge_hook.ts +105 -15
- package/src/scripts/hooks/design_pass_hook.ts +199 -13
- package/src/scripts/hooks/dispatch_hook.ts +13 -12
- package/src/scripts/hooks/egress_engine_loader.ts +125 -0
- package/src/scripts/hooks/egress_stage.ts +440 -0
- package/src/scripts/hooks/hook_stdin.ts +29 -4
- package/src/scripts/hooks/host_docs_pages.ts +194 -0
- package/src/scripts/hooks/host_docs_pages.yaml +75 -0
- package/src/scripts/hooks/host_lowering.json +1 -1
- package/src/scripts/hooks/host_lowering.yaml +22 -21
- package/src/scripts/hooks/host_rate_marker_hook.ts +73 -0
- package/src/scripts/hooks/instructions_observer_hook.ts +170 -0
- package/src/scripts/hooks/interruption_ledger_hook.ts +1 -1
- package/src/scripts/hooks/lazy_concern.ts +148 -0
- package/src/scripts/hooks/orchestration_record_hook.ts +80 -2
- package/src/scripts/hooks/payload_stub.ts +16 -6
- package/src/scripts/hooks/permission_gate.ts +7 -7
- package/src/scripts/hooks/push_settle_hook.ts +32 -3
- package/src/scripts/hooks/replay_hook.ts +43 -0
- package/src/scripts/hooks/reread_guard_hook.ts +87 -10
- package/src/scripts/hooks/rtk_wrap_hook.ts +4 -3
- package/src/scripts/hooks/rule_inject_hook.ts +8 -1
- package/src/scripts/hooks/session_eol_hook.ts +48 -1
- package/src/scripts/hooks/source_first_gate_hook.ts +6 -8
- package/src/scripts/hooks/spawn_guard_shadow_hook.ts +203 -10
- package/src/scripts/hooks/state_io.ts +2 -2
- package/src/scripts/hooks/svg_obligation.ts +553 -0
- package/src/scripts/hooks/svg_rules_loader.ts +133 -0
- package/src/scripts/hooks/tool_result_bytes_hook.ts +14 -5
- package/src/scripts/hooks/turn_end_gate_hook.ts +5 -3
- package/src/scripts/hooks/ui_route_nudge_hook.ts +2 -12
- package/src/scripts/hooks_doctor.ts +31 -0
- package/src/scripts/hooks_status.ts +58 -3
- package/src/scripts/inbox_source_census.ts +74 -13
- package/src/scripts/install +25 -3
- package/src/scripts/install.ts +60 -91
- package/src/scripts/interruption_report.ts +11 -1
- package/src/scripts/layer_baseline_report.ts +267 -0
- package/src/scripts/learning_sidecar.ts +1 -1
- package/src/scripts/lint_agents_layout.ts +3 -0
- package/src/scripts/lint_blocker_citations.ts +104 -0
- package/src/scripts/lint_breaking_changes_index.ts +40 -7
- package/src/scripts/lint_consolidation_lineage.ts +150 -19
- package/src/scripts/lint_consumer_internal_refs.ts +90 -42
- package/src/scripts/lint_context_spine_usage.ts +21 -0
- package/src/scripts/lint_design_antipattern_parity.ts +39 -7
- package/src/scripts/lint_design_quality.ts +60 -12
- package/src/scripts/lint_evidence_artifacts.ts +31 -0
- package/src/scripts/lint_example_fences.ts +11 -2
- package/src/scripts/lint_ghostwriter_source.ts +42 -10
- package/src/scripts/lint_hook_manifest.ts +94 -0
- package/src/scripts/lint_marketplace_install_completeness.ts +4 -0
- package/src/scripts/lint_mcp_config_security.ts +9 -11
- package/src/scripts/lint_motion_authority_drift.ts +27 -0
- package/src/scripts/lint_plan_risk_register.ts +28 -1
- package/src/scripts/lint_positioning.ts +4 -4
- package/src/scripts/lint_provenance.ts +1 -0
- package/src/scripts/lint_roadmap_blockers.ts +65 -4
- package/src/scripts/lint_roadmap_complexity.ts +98 -0
- package/src/scripts/lint_skill_link_reach.ts +251 -9
- package/src/scripts/lint_skill_procedures.ts +252 -0
- package/src/scripts/lint_skill_scripts_readonly.ts +12 -1
- package/src/scripts/lint_subagent_determinism.ts +14 -1
- package/src/scripts/lint_svg_asset.ts +365 -0
- package/src/scripts/lint_ticked_unmet_criteria.ts +300 -21
- package/src/scripts/mcp_registry_submit.sh +2 -2
- package/src/scripts/mcp_server/consumer_tool_catalog.json +4 -4
- package/src/scripts/mcp_server/suggest_skill.ts +89 -4
- package/src/scripts/mcp_server/tools.ts +2 -33
- package/src/scripts/measure_skill_ranker_baseline.ts +203 -5
- package/src/scripts/measure_turn_end_gate.ts +61 -1
- package/src/scripts/merge_clearance.ts +225 -0
- package/src/scripts/migrate_command_suggestions.ts +1 -1
- package/src/scripts/network_sources.ts +236 -0
- package/src/scripts/no_go_deny_rows.ts +225 -0
- package/src/scripts/orchestration_record.ts +15 -2
- package/src/scripts/plan_test_shards.ts +103 -0
- package/src/scripts/pr_url_reminder_hook.ts +10 -0
- package/src/scripts/print_required_checks.ts +5 -5
- package/src/scripts/probe_depth_shadow.ts +406 -0
- package/src/scripts/probe_turnaround.ts +342 -10
- package/src/scripts/ratification_subject.ts +176 -0
- package/src/scripts/rdp_gate_classify.ts +1 -0
- package/src/scripts/rdp_quality_eval.ts +1 -0
- package/src/scripts/release.ts +26 -55
- package/src/scripts/release_drill.ts +52 -23
- package/src/scripts/release_env.ts +37 -0
- package/src/scripts/release_publication.ts +11 -1
- package/src/scripts/report_consumer_gate_reach.ts +233 -0
- package/src/scripts/report_merge_gate_load.ts +295 -0
- package/src/scripts/report_project_contract.ts +123 -0
- package/src/scripts/report_reducible_classes.ts +342 -0
- package/src/scripts/report_required_checks_drift.ts +5 -3
- package/src/scripts/report_skill_host_census.ts +199 -0
- package/src/scripts/report_standing_payload_by_host.ts +7 -7
- package/src/scripts/retro.ts +758 -0
- package/src/scripts/retro_facts.ts +575 -0
- package/src/scripts/retro_queue.ts +739 -0
- package/src/scripts/retro_session_facts.ts +218 -0
- package/src/scripts/retro_sweep.ts +493 -0
- package/src/scripts/retro_triage.ts +126 -0
- package/src/scripts/roadmap_context.ts +2 -2
- package/src/scripts/roadmap_drain_queue.ts +601 -0
- package/src/scripts/run_supervise.ts +24 -2
- package/src/scripts/schemas/agent-settings.schema.json +0 -16
- package/src/scripts/schemas/command.schema.json +3 -3
- package/src/scripts/schemas/review-findings.schema.json +8 -0
- package/src/scripts/schemas/skill.schema.json +7 -6
- package/src/scripts/schemas/subagent.schema.json +33 -2
- package/src/scripts/score_skill_selection.ts +5 -2
- package/src/scripts/second_brain_retrieval.ts +1 -0
- package/src/scripts/second_brain_run.ts +1 -0
- package/src/scripts/self_repair_cli.ts +563 -87
- package/src/scripts/self_review_gate.ts +59 -4
- package/src/scripts/session_register_hook.ts +91 -4
- package/src/scripts/sessions_cli.ts +100 -5
- package/src/scripts/skill_collision_clusters.ts +1 -1
- package/src/scripts/skill_tools/score_skill_relevance.ts +31 -2
- package/src/scripts/skill_trigger_eval.ts +4 -2
- package/src/scripts/source_adapters.ts +183 -0
- package/src/scripts/step_receipt.ts +100 -0
- package/src/scripts/sync_pr_branch.ts +4 -0
- package/src/scripts/test_council_qualification.ts +1 -1
- package/src/scripts/trigger_eval_grandfather.json +3 -49
- package/src/scripts/trigger_eval_rotation.ts +31 -3
- package/src/scripts/ui_conformance_probe.ts +405 -1
- package/src/scripts/update_counts.ts +230 -11
- package/src/scripts/update_invite_hook.ts +245 -0
- package/src/scripts/update_notice_hook.ts +298 -0
- package/src/scripts/validate_frontmatter.ts +85 -0
- package/src/scripts/worker_reuse_shadow_report.ts +121 -0
- package/src/scripts/worktree_cleanup_check.ts +915 -121
- package/src/server/io/yamlIO.ts +2 -2
- package/src/server/schemas/settings.ts +37 -23
- package/src/shared/brandTokenDocument.ts +46 -0
- package/src/shared/brandTokenPaths.ts +43 -0
- package/src/shared/settingsCarveOut.ts +0 -8
- package/src/shared/skillRanking.ts +106 -5
- package/src/shared/userMd/formAdapter.ts +15 -1
- package/src/shared/userMd/schema.ts +22 -0
- package/src/templates/agent-user.md +4 -0
- package/src/templates/agent-user.yml +4 -0
- package/dist/cli-delegate/chunk-523HCFYO.js +0 -90
- package/dist/cli-delegate/chunk-AL3RZ22D.js +0 -74
- package/dist/cli-delegate/chunk-KB5FZ655.js +0 -208
- package/dist/cli-delegate/chunk-MDPPKQCQ.js +0 -875
- package/dist/cli-delegate/chunk-MSUL5NYP.js +0 -1123
- package/dist/cli-delegate/chunk-OAFSJBHH.js +0 -272
- package/dist/ui/assets/index-C3djEJah.js +0 -23
- package/src/scripts/_lib/continuation_ladder.ts +0 -335
- package/src/scripts/hooks/run_continuation_hook.ts +0 -1524
|
@@ -0,0 +1,1316 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `agent-config ui:render <file> --profile asset` — the engine witness for one
|
|
3
|
+
* hand-authored SVG. Step 2.1 and step 2.6 of `road-to-svg-with-a-witness`.
|
|
4
|
+
*
|
|
5
|
+
* The PAGE profile is untouched. Its `VIEWPORTS` contract, its capture loop and
|
|
6
|
+
* its manifest keys are the same bytes they were before this file existed —
|
|
7
|
+
* step 2.2 pins that, and the pin is the reason the asset code lives here
|
|
8
|
+
* rather than inside `uiRender.ts`'s loop.
|
|
9
|
+
*
|
|
10
|
+
* CLASS A, inherited from `uiRender.ts` and not weakened: the browser this
|
|
11
|
+
* module is handed is closed by its caller in a `finally`, nothing is cached
|
|
12
|
+
* between invocations, and no listener or watch mode exists.
|
|
13
|
+
*
|
|
14
|
+
* WHAT THIS READS, and why each reading is here rather than in the static
|
|
15
|
+
* linter (`src/scripts/lint_svg_asset.ts`):
|
|
16
|
+
*
|
|
17
|
+
* - fill ratio, per asset class — a global threshold read a 24-grid
|
|
18
|
+
* hamburger's designed padding as a loose viewBox (R12). The threshold
|
|
19
|
+
* comes from the fixture manifest's `fill_ratio_thresholds` when one is
|
|
20
|
+
* supplied, from the per-class defaults below otherwise. Never global.
|
|
21
|
+
* - the resolved font family — a `<text>` with no `font-family` resolves to
|
|
22
|
+
* `Times` in Chromium (R1). Only a renderer knows that.
|
|
23
|
+
* - draw-on dead time — `getTotalLength()` against the dash. Deliberately
|
|
24
|
+
* NOT a static rule: the prototype's regex fired on an intentional spinner
|
|
25
|
+
* (R9, R12), so the dash is measured against the geometry it actually runs
|
|
26
|
+
* on or it is not measured at all.
|
|
27
|
+
* - frames at a declared time — `pauseAnimations()` + `setCurrentTime()`.
|
|
28
|
+
* `screenshot({ animations: 'disabled' })` does NOT freeze SMIL: three
|
|
29
|
+
* captures 300 ms apart read `cx` 21.3, 46.3, 70.9, while the pause-and-seek
|
|
30
|
+
* pair held 100 and 100 (R6).
|
|
31
|
+
* - a near-white region enclosed by paint where the source shows a hole (S5).
|
|
32
|
+
* - `img` / CSS-background delivery: the emulated preference does not reach an
|
|
33
|
+
* image document (R5), so the reading is `not_witnessable` and the HOST is
|
|
34
|
+
* checked for a `<picture>` route instead.
|
|
35
|
+
*
|
|
36
|
+
* NO NETWORK, NO SCRIPT, NO FILE OUTSIDE THE PROJECT — `uiRenderAssetIsolation.ts`.
|
|
37
|
+
*/
|
|
38
|
+
import * as crypto from 'node:crypto';
|
|
39
|
+
import * as fs from 'node:fs';
|
|
40
|
+
import * as path from 'node:path';
|
|
41
|
+
import { elementExtent, parseElements, readViewBox, vectorVerdict } from '../../scripts/lint_svg_asset.js';
|
|
42
|
+
import { hasCssAnimation, hasSmilMarkup } from '../../scripts/_lib/svg_motion.js';
|
|
43
|
+
import { tokenDocumentError } from '../../shared/brandTokenDocument.js';
|
|
44
|
+
import { compareGeometry, sampleSource } from './uiRenderAssetCompare.js';
|
|
45
|
+
import { applyFaceStates, faceFindings } from './uiRenderAssetFonts.js';
|
|
46
|
+
import { readGrounds } from './uiRenderAssetGrounds.js';
|
|
47
|
+
import { gradeIconGrid } from './uiRenderAssetGrid.js';
|
|
48
|
+
import { confinePage, fileRootsFor, injectStyle } from './uiRenderAssetIsolation.js';
|
|
49
|
+
export * from './uiRenderAssetGrid.js';
|
|
50
|
+
import { ASSET_CLASSES, BRAND_TOKEN_PATHS, DEFAULT_FILL_RATIO_THRESHOLDS, DEFAULT_GEOMETRY_TOLERANCES, DRAW_ON_DEAD_TIME_THRESHOLD, FALLBACK_BACKGROUNDS, BLEED_SHARE_THRESHOLD, FOREIGN_CONTENT_SHARE_THRESHOLD, HOLE_ATTRIBUTE, HOST_BASENAME, ICON_SIZES, LOW_CONTRAST_SHARE_THRESHOLD, MARK_SMALL_SIZES, NEAR_WHITE_CHANNEL, } from './uiRenderAssetTypes.js';
|
|
51
|
+
export * from './uiRenderAssetTypes.js';
|
|
52
|
+
export * from './uiRenderAssetCompare.js';
|
|
53
|
+
// ---------------------------------------------------------------------------
|
|
54
|
+
// Pure readings — no browser
|
|
55
|
+
// ---------------------------------------------------------------------------
|
|
56
|
+
/**
|
|
57
|
+
* The cost reading — step 3.2 of `road-to-svg-from-a-live-session`.
|
|
58
|
+
*
|
|
59
|
+
* Counted from the bytes, so it needs no renderer and is the same number on
|
|
60
|
+
* every machine. `coordinates` counts the numbers inside `d` and `points`,
|
|
61
|
+
* which is the axis that makes a traced file hostile to edit: one accepted
|
|
62
|
+
* mark went 5.3 KB to 49.5 KB in a conversion and nothing measured it.
|
|
63
|
+
*/
|
|
64
|
+
export function costReading(content, elements) {
|
|
65
|
+
let coordinates = 0;
|
|
66
|
+
for (const el of elements) {
|
|
67
|
+
if (el.closing)
|
|
68
|
+
continue;
|
|
69
|
+
for (const key of ['d', 'points']) {
|
|
70
|
+
const v = el.attrs[key];
|
|
71
|
+
if (!v)
|
|
72
|
+
continue;
|
|
73
|
+
coordinates += (v.match(/-?\d*\.?\d+(?:[eE][-+]?\d+)?/g) ?? []).length;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
const open = elements.filter((el) => !el.closing);
|
|
77
|
+
return {
|
|
78
|
+
bytes: Buffer.byteLength(content, 'utf8'),
|
|
79
|
+
elements: open.length,
|
|
80
|
+
paths: open.filter((el) => el.name === 'path').length,
|
|
81
|
+
gradients: open.filter((el) => el.name === 'lineargradient' || el.name === 'radialgradient').length,
|
|
82
|
+
coordinates,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
export function sha256(buf) {
|
|
86
|
+
return crypto.createHash('sha256').update(buf).digest('hex');
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* The class when `--class` was not given. Recorded as `asset_class_inferred`
|
|
90
|
+
* so a reader never mistakes a guess for a declaration.
|
|
91
|
+
*/
|
|
92
|
+
export function inferAssetClass(content) {
|
|
93
|
+
if (hasSmilMarkup(content) || hasCssAnimation(content))
|
|
94
|
+
return 'animated';
|
|
95
|
+
const elements = parseElements(content);
|
|
96
|
+
const root = elements.find((el) => el.name === 'svg' && !el.closing);
|
|
97
|
+
const vb = readViewBox(root);
|
|
98
|
+
const texts = elements.filter((el) => el.name === 'text' && !el.closing).length;
|
|
99
|
+
const markers = elements.filter((el) => el.name === 'marker' && !el.closing).length;
|
|
100
|
+
if (vb && vb.w === vb.h && Math.max(vb.w, vb.h) <= 64)
|
|
101
|
+
return 'icon';
|
|
102
|
+
if (markers > 0 || texts >= 2)
|
|
103
|
+
return 'diagram';
|
|
104
|
+
if (texts >= 1)
|
|
105
|
+
return 'illustration';
|
|
106
|
+
return 'mark';
|
|
107
|
+
}
|
|
108
|
+
/** The declared intrinsic size: `width`/`height` first, the viewBox otherwise. */
|
|
109
|
+
export function intrinsicSize(root, vb) {
|
|
110
|
+
const num = (raw) => {
|
|
111
|
+
if (!raw)
|
|
112
|
+
return null;
|
|
113
|
+
const m = /^\s*(-?[\d.]+)\s*(px)?\s*$/i.exec(raw);
|
|
114
|
+
const v = m ? Number(m[1]) : Number.NaN;
|
|
115
|
+
return Number.isFinite(v) && v > 0 ? v : null;
|
|
116
|
+
};
|
|
117
|
+
const w = num(root?.attrs['width']) ?? vb?.w ?? 48;
|
|
118
|
+
const h = num(root?.attrs['height']) ?? vb?.h ?? 48;
|
|
119
|
+
return { width: w, height: h };
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Icons at 16/24/48; a MARK additionally at 16 and 32; every other class at 1x
|
|
123
|
+
* and 2x its intrinsic size.
|
|
124
|
+
*
|
|
125
|
+
* Step 4.3 of `road-to-svg-from-a-live-session`. A review sent an accepted
|
|
126
|
+
* mark back because its face turned to mush at 32 px, and the only way anybody
|
|
127
|
+
* could know that was to look — there was no capture at a small size to look
|
|
128
|
+
* AT. Legibility stays a look; the capture is the evidence that the look
|
|
129
|
+
* happened, which is the same split the rest of this profile keeps.
|
|
130
|
+
*
|
|
131
|
+
* The small sizes come FIRST so a reader scanning the manifest meets them
|
|
132
|
+
* before the 1x the file was designed at.
|
|
133
|
+
*/
|
|
134
|
+
/**
|
|
135
|
+
* The LARGEST capture, which is not the last one.
|
|
136
|
+
*
|
|
137
|
+
* Finding 6 of the third completion review. `assetSizes` PREPENDS
|
|
138
|
+
* `MARK_SMALL_SIZES` for a mark — so a reader taking `sizes[sizes.length - 1]`
|
|
139
|
+
* gets the 2x scaled size, which is smaller than 32 whenever the intrinsic
|
|
140
|
+
* width is under 16. Reproduced: intrinsic width 10 gives a last entry of 20
|
|
141
|
+
* against a maximum of 32, so the geometry pass read the asset at a size it
|
|
142
|
+
* was not biggest at and the grounds reading used a capture that was not the
|
|
143
|
+
* largest taken.
|
|
144
|
+
*
|
|
145
|
+
* A named function rather than an inline `reduce`, because the defect was
|
|
146
|
+
* exactly that the selection looked obvious enough not to be read — and an
|
|
147
|
+
* inline expression is not something a test can hold.
|
|
148
|
+
*/
|
|
149
|
+
export function biggestSize(sizes) {
|
|
150
|
+
let best = null;
|
|
151
|
+
for (const s of sizes)
|
|
152
|
+
if (best === null || s.width > best.width)
|
|
153
|
+
best = s;
|
|
154
|
+
return best ?? { label: '1x', width: 64, height: 64 };
|
|
155
|
+
}
|
|
156
|
+
export function assetSizes(cls, intrinsic) {
|
|
157
|
+
if (cls === 'icon') {
|
|
158
|
+
return ICON_SIZES.map((px) => ({ label: `${px}`, width: px, height: px }));
|
|
159
|
+
}
|
|
160
|
+
const ratio = intrinsic.height / intrinsic.width;
|
|
161
|
+
const scaled = [1, 2].map((k) => ({
|
|
162
|
+
label: `${k}x`,
|
|
163
|
+
width: Math.round(intrinsic.width * k),
|
|
164
|
+
height: Math.round(intrinsic.width * k * ratio),
|
|
165
|
+
}));
|
|
166
|
+
if (cls !== 'mark')
|
|
167
|
+
return scaled;
|
|
168
|
+
const small = MARK_SMALL_SIZES.map((px) => ({
|
|
169
|
+
label: `${px}`,
|
|
170
|
+
width: px,
|
|
171
|
+
height: Math.max(1, Math.round(px * ratio)),
|
|
172
|
+
}));
|
|
173
|
+
return [...small, ...scaled];
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Thresholds from a fixture manifest when one is supplied, the per-class
|
|
177
|
+
* defaults otherwise. The SOURCE travels with the number: a threshold a reader
|
|
178
|
+
* cannot trace is a threshold they cannot argue with.
|
|
179
|
+
*/
|
|
180
|
+
export function readThresholds(file) {
|
|
181
|
+
const out = {
|
|
182
|
+
fillRatio: DEFAULT_FILL_RATIO_THRESHOLDS,
|
|
183
|
+
fillRatioSource: 'default',
|
|
184
|
+
geometry: DEFAULT_GEOMETRY_TOLERANCES,
|
|
185
|
+
geometrySource: 'default',
|
|
186
|
+
};
|
|
187
|
+
if (!file || !fs.existsSync(file))
|
|
188
|
+
return out;
|
|
189
|
+
let parsed;
|
|
190
|
+
try {
|
|
191
|
+
parsed = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
192
|
+
}
|
|
193
|
+
catch {
|
|
194
|
+
return out;
|
|
195
|
+
}
|
|
196
|
+
const obj = parsed;
|
|
197
|
+
const fr = obj.fill_ratio_thresholds;
|
|
198
|
+
if (fr && ASSET_CLASSES.every((c) => typeof fr[c] === 'number')) {
|
|
199
|
+
out.fillRatio = { ...DEFAULT_FILL_RATIO_THRESHOLDS, ...fr };
|
|
200
|
+
out.fillRatioSource = 'manifest';
|
|
201
|
+
}
|
|
202
|
+
const gt = obj.geometry_tolerances;
|
|
203
|
+
// EVERY SUPPLIED FIELD IS A NUMBER, not just the one the guard used to
|
|
204
|
+
// name. Finding 5 of the review of this branch: the merge closed the
|
|
205
|
+
// missing-key hole and left the wrong-TYPE one, so
|
|
206
|
+
// `max_boundary_distance_units: "lots"` was accepted as manifest-sourced
|
|
207
|
+
// and returned verbatim — and `0 <= "lots"` is false, which is the same
|
|
208
|
+
// fail-closed comparison the missing key produced. A threshold that is not
|
|
209
|
+
// a number is not a threshold.
|
|
210
|
+
const wellTyped = (t) => t !== undefined &&
|
|
211
|
+
typeof t.silhouette_iou_min === 'number' &&
|
|
212
|
+
Object.values(t).every((v) => typeof v === 'number' && Number.isFinite(v));
|
|
213
|
+
if (gt && ASSET_CLASSES.every((c) => wellTyped(gt[c]))) {
|
|
214
|
+
// MERGED PER CLASS, not spread over the class. Finding 3 of the sixth
|
|
215
|
+
// completion review: the validation checks one field per class and the
|
|
216
|
+
// spread then replaced the whole class object, so a manifest naming
|
|
217
|
+
// every class but supplying only `silhouette_iou_min` was accepted as
|
|
218
|
+
// `geometrySource: manifest` with `max_boundary_distance_units`
|
|
219
|
+
// UNDEFINED.
|
|
220
|
+
//
|
|
221
|
+
// THE DIRECTION, corrected — the first version of this comment had it
|
|
222
|
+
// backwards and the review of this branch caught it. `undefined` does
|
|
223
|
+
// not make the boundary check vanish: `maxDistance <= undefined` is
|
|
224
|
+
// FALSE, so every comparison failed it, and a pixel-perfect render
|
|
225
|
+
// (iou 1.0, boundary distance 0.0) scored `fail`. It was fail-CLOSED,
|
|
226
|
+
// not silently absent. That is the better of the two directions and
|
|
227
|
+
// still wrong: a manifest the reader was told to trust refused every
|
|
228
|
+
// file it was applied to.
|
|
229
|
+
const merged = {};
|
|
230
|
+
for (const c of ASSET_CLASSES) {
|
|
231
|
+
merged[c] = { ...DEFAULT_GEOMETRY_TOLERANCES[c], ...gt[c] };
|
|
232
|
+
}
|
|
233
|
+
out.geometry = merged;
|
|
234
|
+
out.geometrySource = 'manifest';
|
|
235
|
+
}
|
|
236
|
+
return out;
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* The light and dark background from the project's tokens, or the stated
|
|
240
|
+
* fallback. Never a silent fallback — the caller records which it was.
|
|
241
|
+
*/
|
|
242
|
+
export function readBackgroundTokens(projectRoot) {
|
|
243
|
+
for (const rel of BRAND_TOKEN_PATHS) {
|
|
244
|
+
const abs = path.join(projectRoot, rel);
|
|
245
|
+
if (!fs.existsSync(abs))
|
|
246
|
+
continue;
|
|
247
|
+
let doc;
|
|
248
|
+
try {
|
|
249
|
+
const text = fs.readFileSync(abs, 'utf8');
|
|
250
|
+
// The validity `brand:status` reports, not merely parseable JSON:
|
|
251
|
+
// `findToken` takes a bare `value`, so a non-DTCG file would win.
|
|
252
|
+
if (tokenDocumentError(text) !== null)
|
|
253
|
+
continue;
|
|
254
|
+
doc = JSON.parse(text);
|
|
255
|
+
}
|
|
256
|
+
catch {
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
const light = findToken(doc, ['background', 'bg', 'surface'], ['light', 'default', 'base']);
|
|
260
|
+
const dark = findToken(doc, ['background', 'bg', 'surface'], ['dark', 'inverse', 'inverted']);
|
|
261
|
+
if (light || dark) {
|
|
262
|
+
return {
|
|
263
|
+
light: light ?? FALLBACK_BACKGROUNDS.light,
|
|
264
|
+
dark: dark ?? FALLBACK_BACKGROUNDS.dark,
|
|
265
|
+
source: 'tokens',
|
|
266
|
+
tokens_file: rel,
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
return { ...FALLBACK_BACKGROUNDS, source: 'fallback', tokens_file: null };
|
|
271
|
+
}
|
|
272
|
+
/** A depth-first walk for a DTCG `$value` whose path carries both hints. */
|
|
273
|
+
function findToken(doc, nameHints, modeHints) {
|
|
274
|
+
const stack = [{ node: doc, keys: [] }];
|
|
275
|
+
let best = null;
|
|
276
|
+
while (stack.length > 0) {
|
|
277
|
+
const top = stack.pop();
|
|
278
|
+
if (!top || typeof top.node !== 'object' || top.node === null)
|
|
279
|
+
continue;
|
|
280
|
+
const rec = top.node;
|
|
281
|
+
const value = rec['$value'] ?? rec['value'];
|
|
282
|
+
if (typeof value === 'string' && /^#[0-9a-f]{3,8}$/i.test(value)) {
|
|
283
|
+
const joined = top.keys.join('.').toLowerCase();
|
|
284
|
+
if (nameHints.some((h) => joined.includes(h)) && modeHints.some((h) => joined.includes(h))) {
|
|
285
|
+
best = value;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
for (const [k, v] of Object.entries(rec)) {
|
|
289
|
+
if (k.startsWith('$'))
|
|
290
|
+
continue;
|
|
291
|
+
stack.push({ node: v, keys: [...top.keys, k] });
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
return best;
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* The fraction of a draw-on animation's duration during which the drawn length
|
|
298
|
+
* does not change — the R8 defect, stated as a number.
|
|
299
|
+
*
|
|
300
|
+
* With a dash of `D` real units on a path of real length `L`, a dash offset of
|
|
301
|
+
* `o` paints `clamp(D - o, 0, L)`. When `D > L` the paint reaches `L` while `o`
|
|
302
|
+
* is still above zero, and every further step of the animation draws nothing.
|
|
303
|
+
*/
|
|
304
|
+
export function drawOnDeadFraction(dashUnits, from, to, totalLength) {
|
|
305
|
+
const span = Math.abs(from - to);
|
|
306
|
+
if (span <= 0 || totalLength <= 0 || dashUnits <= 0)
|
|
307
|
+
return 0;
|
|
308
|
+
const drawn = (o) => Math.min(Math.max(dashUnits - o, 0), totalLength);
|
|
309
|
+
const lo = Math.min(from, to);
|
|
310
|
+
const hi = Math.max(from, to);
|
|
311
|
+
const STEPS = 400;
|
|
312
|
+
let live = 0;
|
|
313
|
+
let prev = drawn(hi);
|
|
314
|
+
for (let i = 1; i <= STEPS; i++) {
|
|
315
|
+
const o = hi - ((hi - lo) * i) / STEPS;
|
|
316
|
+
const now = drawn(o);
|
|
317
|
+
if (Math.abs(now - prev) > 1e-6)
|
|
318
|
+
live += 1;
|
|
319
|
+
prev = now;
|
|
320
|
+
}
|
|
321
|
+
return 1 - live / STEPS;
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* One screenshot, with exactly one retry on a protocol error.
|
|
325
|
+
*
|
|
326
|
+
* Measured on a GitHub-hosted ubuntu runner with `chromium_headless_shell`:
|
|
327
|
+
* `page.screenshot` failed with `Protocol error (Page.captureScreenshot):
|
|
328
|
+
* Unable to capture screenshot` on one asset while the same asset captured
|
|
329
|
+
* cleanly in another test of the same run — a compositor surface that was not
|
|
330
|
+
* ready, not a property of the file. ONE retry, deliberately: a genuine failure
|
|
331
|
+
* fails twice and still surfaces, while a loop would turn a real break into a
|
|
332
|
+
* slow one. A `path` write also creates its parent directories, so a missing
|
|
333
|
+
* `outDir` is not what this is for.
|
|
334
|
+
*/
|
|
335
|
+
async function captureWithOneRetry(page, shot) {
|
|
336
|
+
try {
|
|
337
|
+
await page.screenshot({ path: shot });
|
|
338
|
+
}
|
|
339
|
+
catch (first) {
|
|
340
|
+
if (!/Unable to capture screenshot|Protocol error/i.test(String(first)))
|
|
341
|
+
throw first;
|
|
342
|
+
await page.waitForTimeout(250);
|
|
343
|
+
await page.screenshot({ path: shot });
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
/** Does this element declare itself less than fully opaque? */
|
|
347
|
+
export function isDeclaredTranslucent(el) {
|
|
348
|
+
const style = el.attrs['style'] ?? '';
|
|
349
|
+
const read = (key) => {
|
|
350
|
+
const fromStyle = new RegExp(`(?:^|;)\\s*${key}\\s*:\\s*([^;]+)`).exec(style)?.[1];
|
|
351
|
+
const raw = (fromStyle ?? el.attrs[key] ?? '').trim();
|
|
352
|
+
if (raw === '')
|
|
353
|
+
return null;
|
|
354
|
+
// A PERCENTAGE IS LEGAL CSS AND `Number()` CANNOT READ IT.
|
|
355
|
+
//
|
|
356
|
+
// Finding 8 of the third completion review: `Number('50%')` is NaN, so
|
|
357
|
+
// `fill-opacity="50%"` read as "not declared translucent" and every
|
|
358
|
+
// pixel of that half-transparent shape was counted as background
|
|
359
|
+
// bleeding through the author's paint — the exact false warning the
|
|
360
|
+
// comment above this function says the declaration prevents.
|
|
361
|
+
const pct = /^([+-]?(?:\d*\.\d+|\d+\.?))\s*%$/.exec(raw);
|
|
362
|
+
const v = pct ? Number(pct[1]) / 100 : Number(raw);
|
|
363
|
+
return Number.isFinite(v) ? v : null;
|
|
364
|
+
};
|
|
365
|
+
// `stroke-opacity` belongs here for the same reason the other two do: a
|
|
366
|
+
// stroke the author made translucent is a declaration, not a seam. It was
|
|
367
|
+
// missing from the list, which finding 8 also names.
|
|
368
|
+
for (const key of ['opacity', 'fill-opacity', 'stroke-opacity']) {
|
|
369
|
+
const v = read(key);
|
|
370
|
+
if (v !== null && v < 0.99)
|
|
371
|
+
return true;
|
|
372
|
+
}
|
|
373
|
+
return false;
|
|
374
|
+
}
|
|
375
|
+
/** Boxes, in user units, of every element matching `pick`. */
|
|
376
|
+
function extentsOf(elements, pick) {
|
|
377
|
+
const out = [];
|
|
378
|
+
for (const el of elements) {
|
|
379
|
+
if (el.closing || !pick(el))
|
|
380
|
+
continue;
|
|
381
|
+
const b = elementExtent(el);
|
|
382
|
+
if (b)
|
|
383
|
+
out.push({ x: b.minX, y: b.minY, w: b.maxX - b.minX, h: b.maxY - b.minY });
|
|
384
|
+
}
|
|
385
|
+
return out;
|
|
386
|
+
}
|
|
387
|
+
/** A reading pass over one asset. The caller owns the browser's lifetime. */
|
|
388
|
+
export async function runAssetRender(browser, opts) {
|
|
389
|
+
const bytes = fs.readFileSync(opts.file);
|
|
390
|
+
const content = bytes.toString('utf8');
|
|
391
|
+
const isHost = /\.html?$/i.test(opts.file);
|
|
392
|
+
const dir = path.dirname(opts.file);
|
|
393
|
+
const elements = isHost ? [] : parseElements(content);
|
|
394
|
+
const root = elements.find((el) => el.name === 'svg' && !el.closing);
|
|
395
|
+
const vb = isHost ? null : readViewBox(root);
|
|
396
|
+
const intrinsic = intrinsicSize(root, vb);
|
|
397
|
+
const declaredClass = opts.assetClass;
|
|
398
|
+
const assetClass = declaredClass ?? inferAssetClass(content);
|
|
399
|
+
const delivery = opts.delivery ?? 'inline';
|
|
400
|
+
const thresholds = readThresholds(opts.thresholdsFile);
|
|
401
|
+
const backgrounds = readBackgroundTokens(opts.projectRoot);
|
|
402
|
+
const blocked = [];
|
|
403
|
+
const reading = {
|
|
404
|
+
profile: 'asset',
|
|
405
|
+
file: path.relative(opts.projectRoot, opts.file).split(path.sep).join('/'),
|
|
406
|
+
digest: sha256(bytes),
|
|
407
|
+
asset_class: assetClass,
|
|
408
|
+
asset_class_inferred: declaredClass === undefined,
|
|
409
|
+
delivery,
|
|
410
|
+
view_box: vb,
|
|
411
|
+
intrinsic,
|
|
412
|
+
backgrounds,
|
|
413
|
+
sizes: [],
|
|
414
|
+
readings_at: null,
|
|
415
|
+
true_vector: isHost ? false : vectorVerdict(elements).trueVector,
|
|
416
|
+
painted_box: null,
|
|
417
|
+
fill_ratio: null,
|
|
418
|
+
fill_ratio_threshold: thresholds.fillRatio[assetClass],
|
|
419
|
+
fill_ratio_threshold_source: thresholds.fillRatioSource,
|
|
420
|
+
outside_view_box: [],
|
|
421
|
+
texts: [],
|
|
422
|
+
accessible_names: [],
|
|
423
|
+
duplicate_accessible_names: [],
|
|
424
|
+
frames: [],
|
|
425
|
+
animation_duration_s: 0,
|
|
426
|
+
distinct_frames: 0,
|
|
427
|
+
draw_on: [],
|
|
428
|
+
icon_grid: null,
|
|
429
|
+
near_white_regions: [],
|
|
430
|
+
cost: costReading(content, elements),
|
|
431
|
+
grounds: [],
|
|
432
|
+
network: { aborted: 0, blocked_urls: blocked },
|
|
433
|
+
findings: [],
|
|
434
|
+
reduced_motion: { witnessable: false, frames_equal: null, reason: 'not read' },
|
|
435
|
+
};
|
|
436
|
+
const hostHtml = isHost ? null : inlineHost(content);
|
|
437
|
+
const targetUrl = isHost ? `file://${opts.file}` : `file://${path.join(dir, HOST_BASENAME)}`;
|
|
438
|
+
const fileRoots = fileRootsFor(opts.projectRoot, opts.file);
|
|
439
|
+
const context = await browser.newContext({ javaScriptEnabled: false });
|
|
440
|
+
// A transpiler shim, and it belongs here rather than in a test helper.
|
|
441
|
+
// Running this file through `tsx` — which is how `./scripts-run` and every
|
|
442
|
+
// consumer script reach it — emits `__name(...)` wrappers around the
|
|
443
|
+
// functions handed to `page.evaluate`. The browser has no such global, so
|
|
444
|
+
// the first evaluate dies with `ReferenceError: __name is not defined`
|
|
445
|
+
// while the same code passes under vitest, whose transform does not add it.
|
|
446
|
+
// Defining an identity shim before any page script runs makes both paths
|
|
447
|
+
// behave the same; it is a no-op where the wrapper was never emitted.
|
|
448
|
+
await context.addInitScript(() => {
|
|
449
|
+
const g = globalThis;
|
|
450
|
+
if (typeof g.__name !== 'function')
|
|
451
|
+
g.__name = (fn) => fn;
|
|
452
|
+
});
|
|
453
|
+
try {
|
|
454
|
+
// Sizes x backgrounds. Icons get 16/24/48; everything else 1x and 2x.
|
|
455
|
+
const sizes = isHost
|
|
456
|
+
? [{ label: 'host', width: 800, height: 600 }]
|
|
457
|
+
: assetSizes(assetClass, intrinsic);
|
|
458
|
+
for (const size of sizes) {
|
|
459
|
+
for (const mode of ['light', 'dark']) {
|
|
460
|
+
const colour = backgrounds[mode];
|
|
461
|
+
const page = await openPage(context, {
|
|
462
|
+
url: targetUrl,
|
|
463
|
+
hostHtml,
|
|
464
|
+
hostBasename: HOST_BASENAME,
|
|
465
|
+
width: Math.max(size.width, 64),
|
|
466
|
+
height: Math.max(size.height, 64),
|
|
467
|
+
background: colour,
|
|
468
|
+
cssWidth: size.width,
|
|
469
|
+
blocked,
|
|
470
|
+
fileRoots,
|
|
471
|
+
});
|
|
472
|
+
const shot = path.join(opts.outDir, `asset-${size.label}-${mode}.png`);
|
|
473
|
+
await captureWithOneRetry(page, shot);
|
|
474
|
+
reading.sizes.push({
|
|
475
|
+
label: size.label,
|
|
476
|
+
css_width: size.width,
|
|
477
|
+
css_height: size.height,
|
|
478
|
+
background: mode,
|
|
479
|
+
background_color: colour,
|
|
480
|
+
screenshot: path.relative(opts.projectRoot, shot).split(path.sep).join('/'),
|
|
481
|
+
});
|
|
482
|
+
await page.close();
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
reading.network.aborted = blocked.length;
|
|
486
|
+
// One geometry pass. Every reading below is in viewBox units, which do
|
|
487
|
+
// not move with the render size — so reading them once and SAYING where
|
|
488
|
+
// beats re-reading them six times and implying they might differ.
|
|
489
|
+
const biggest = biggestSize(sizes);
|
|
490
|
+
// The moment the geometry was read, named rather than implied: an
|
|
491
|
+
// animated asset's painted box is a property of a time, and t = 0 is a
|
|
492
|
+
// declared one.
|
|
493
|
+
reading.readings_at = { css_width: biggest.width, css_height: biggest.height, background: 'light', t: 0 };
|
|
494
|
+
const page = await openPage(context, {
|
|
495
|
+
url: targetUrl,
|
|
496
|
+
hostHtml,
|
|
497
|
+
hostBasename: HOST_BASENAME,
|
|
498
|
+
width: Math.max(biggest.width, 64),
|
|
499
|
+
height: Math.max(biggest.height, 64),
|
|
500
|
+
background: backgrounds.light,
|
|
501
|
+
cssWidth: biggest.width,
|
|
502
|
+
blocked,
|
|
503
|
+
fileRoots,
|
|
504
|
+
});
|
|
505
|
+
if (!isHost) {
|
|
506
|
+
const raw = await page.evaluate(readAssetInPage, { nearWhite: NEAR_WHITE_CHANNEL });
|
|
507
|
+
applyGeometry(reading, raw, vb);
|
|
508
|
+
await applyFaceStates(page, reading);
|
|
509
|
+
}
|
|
510
|
+
// Accessible names across every graphic on the page (R7).
|
|
511
|
+
const names = await page.evaluate(readAccessibleNamesInPage);
|
|
512
|
+
const counts = new Map();
|
|
513
|
+
for (const n of names)
|
|
514
|
+
counts.set(n, (counts.get(n) ?? 0) + 1);
|
|
515
|
+
reading.accessible_names = [...counts].map(([name, count]) => ({ name, count }));
|
|
516
|
+
reading.duplicate_accessible_names = [...counts].filter(([, c]) => c > 1).map(([n]) => n);
|
|
517
|
+
// Frames at t = 0, mid, end. SMIL is paused and sought; CSS/WAAPI
|
|
518
|
+
// animations are paused and their currentTime is set (R6).
|
|
519
|
+
const duration = await page.evaluate(readAnimationEndInPage);
|
|
520
|
+
reading.animation_duration_s = duration;
|
|
521
|
+
if (duration > 0) {
|
|
522
|
+
const points = [
|
|
523
|
+
['start', 0],
|
|
524
|
+
['mid', duration / 2],
|
|
525
|
+
['end', duration],
|
|
526
|
+
];
|
|
527
|
+
for (const [label, t] of points) {
|
|
528
|
+
await page.evaluate(seekInPage, t);
|
|
529
|
+
const buf = await page.screenshot();
|
|
530
|
+
reading.frames.push({ label, t, digest: sha256(buf) });
|
|
531
|
+
}
|
|
532
|
+
reading.distinct_frames = new Set(reading.frames.map((f) => f.digest)).size;
|
|
533
|
+
}
|
|
534
|
+
await page.close();
|
|
535
|
+
// Reduced motion. Inline is witnessable; an image document is not (R5).
|
|
536
|
+
reading.reduced_motion = await readReducedMotion(context, {
|
|
537
|
+
url: targetUrl,
|
|
538
|
+
hostHtml,
|
|
539
|
+
width: Math.max(biggest.width, 64),
|
|
540
|
+
height: Math.max(biggest.height, 64),
|
|
541
|
+
background: backgrounds.light,
|
|
542
|
+
cssWidth: biggest.width,
|
|
543
|
+
blocked,
|
|
544
|
+
delivery,
|
|
545
|
+
duration,
|
|
546
|
+
isHost,
|
|
547
|
+
hostDir: path.dirname(path.resolve(opts.file)),
|
|
548
|
+
fileRoots,
|
|
549
|
+
});
|
|
550
|
+
if (opts.compareTo) {
|
|
551
|
+
reading.comparison = await compareGeometry(context, {
|
|
552
|
+
baseline: opts.file,
|
|
553
|
+
candidate: opts.compareTo,
|
|
554
|
+
assetClass,
|
|
555
|
+
tolerance: thresholds.geometry[assetClass],
|
|
556
|
+
toleranceSource: thresholds.geometrySource,
|
|
557
|
+
...(opts.registration ? { registration: opts.registration } : {}),
|
|
558
|
+
blocked,
|
|
559
|
+
});
|
|
560
|
+
// Step 1.2: ask the SOURCE what is under each near-white region.
|
|
561
|
+
// Only reachable with a registration, which is the whole condition
|
|
562
|
+
// — without one the regions keep `no-source` and produce no
|
|
563
|
+
// finding, because the rule's claim is about the source and there
|
|
564
|
+
// is no source to make it against.
|
|
565
|
+
if (opts.registration && vb && reading.near_white_regions.length > 0) {
|
|
566
|
+
const points = reading.near_white_regions.map((n) => n.centre);
|
|
567
|
+
const samples = await sampleSource(context, opts.compareTo, vb, opts.registration, points, blocked);
|
|
568
|
+
if (samples !== null) {
|
|
569
|
+
reading.near_white_regions = reading.near_white_regions.map((n, i) => ({
|
|
570
|
+
...n,
|
|
571
|
+
source_verdict: (samples[i]?.isBackground ?? false) ? 'hole' : 'paint',
|
|
572
|
+
}));
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
// Steps 4.1 and 4.2: the per-ground readings, taken at the biggest
|
|
577
|
+
// size so the counts are not dominated by anti-aliasing. Not for a
|
|
578
|
+
// host document — the silhouette of a page is the page.
|
|
579
|
+
if (!isHost) {
|
|
580
|
+
const confinedOpen = (ctx, spec) => openPage(ctx, { ...spec, fileRoots });
|
|
581
|
+
reading.grounds = await readGrounds(context, confinedOpen, {
|
|
582
|
+
url: targetUrl,
|
|
583
|
+
hostHtml,
|
|
584
|
+
width: Math.max(biggest.width, 64),
|
|
585
|
+
height: Math.max(biggest.height, 64),
|
|
586
|
+
cssWidth: biggest.width,
|
|
587
|
+
blocked,
|
|
588
|
+
backgrounds,
|
|
589
|
+
holeBoxes: extentsOf(elements, (el) => (el.attrs[HOLE_ATTRIBUTE] ?? '') !== ''),
|
|
590
|
+
// Finding 7: a shape the author made translucent is not a seam.
|
|
591
|
+
softBoxes: extentsOf(elements, isDeclaredTranslucent),
|
|
592
|
+
viewBox: vb,
|
|
593
|
+
});
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
finally {
|
|
597
|
+
await context.close();
|
|
598
|
+
}
|
|
599
|
+
if (!isHost && opts.iconSet && assetClass === 'icon') {
|
|
600
|
+
reading.icon_grid = gradeIconGrid(opts.file, vb, elements, opts.iconSet, reading.painted_box);
|
|
601
|
+
}
|
|
602
|
+
reading.network.aborted = blocked.length;
|
|
603
|
+
reading.findings = deriveFindings(reading);
|
|
604
|
+
return { reading, verification: 'verified' };
|
|
605
|
+
}
|
|
606
|
+
/** The host document for an inline delivery: one background, one sized graphic. */
|
|
607
|
+
function inlineHost(svg) {
|
|
608
|
+
const body = svg.replace(/<\?xml[^>]*\?>\s*/i, '').replace(/<!DOCTYPE[^>]*>\s*/i, '');
|
|
609
|
+
return `<!doctype html><html><head><meta charset="utf-8"><title>asset</title></head><body>${body}</body></html>`;
|
|
610
|
+
}
|
|
611
|
+
/**
|
|
612
|
+
* One page with the network closed. The synthetic host is fulfilled at a URL
|
|
613
|
+
* inside the asset's own directory, so the asset's relative references resolve
|
|
614
|
+
* from where it actually lives.
|
|
615
|
+
*/
|
|
616
|
+
async function openPage(context, spec) {
|
|
617
|
+
const page = await context.newPage();
|
|
618
|
+
await page.setViewportSize({ width: Math.ceil(spec.width), height: Math.ceil(spec.height) });
|
|
619
|
+
if (spec.reducedMotion)
|
|
620
|
+
await page.emulateMedia({ reducedMotion: 'reduce' });
|
|
621
|
+
await confinePage(page, spec.blocked, spec.fileRoots, async (route) => {
|
|
622
|
+
if (!spec.hostHtml || !route.request().url().endsWith(`/${spec.hostBasename}`))
|
|
623
|
+
return false;
|
|
624
|
+
await route.fulfill({ status: 200, contentType: 'text/html; charset=utf-8', body: spec.hostHtml });
|
|
625
|
+
return true;
|
|
626
|
+
});
|
|
627
|
+
await page.goto(spec.url, { waitUntil: 'load', timeout: 30_000 });
|
|
628
|
+
await injectStyle(page, `html,body{margin:0;padding:0;background:${spec.background}}` +
|
|
629
|
+
`body>svg{display:block;width:${spec.cssWidth}px;height:auto}`);
|
|
630
|
+
return page;
|
|
631
|
+
}
|
|
632
|
+
function readAssetInPage(args) {
|
|
633
|
+
const svg = document.querySelector('svg');
|
|
634
|
+
if (!svg)
|
|
635
|
+
return { viewBox: null, elements: [], texts: [] };
|
|
636
|
+
const vbAttr = (svg.getAttribute('viewBox') ?? '').trim().split(/[\s,]+/).map(Number);
|
|
637
|
+
const viewBox = vbAttr.length === 4 && vbAttr.every((n) => Number.isFinite(n))
|
|
638
|
+
? [vbAttr[0], vbAttr[1], vbAttr[2], vbAttr[3]]
|
|
639
|
+
: null;
|
|
640
|
+
const ctm = svg.getScreenCTM();
|
|
641
|
+
const inv = ctm ? ctm.inverse() : null;
|
|
642
|
+
const userBox = (el) => {
|
|
643
|
+
const r = el.getBoundingClientRect();
|
|
644
|
+
if (!inv)
|
|
645
|
+
return null;
|
|
646
|
+
const pts = [
|
|
647
|
+
[r.left, r.top],
|
|
648
|
+
[r.right, r.top],
|
|
649
|
+
[r.left, r.bottom],
|
|
650
|
+
[r.right, r.bottom],
|
|
651
|
+
].map(([x, y]) => new DOMPoint(x, y).matrixTransform(inv));
|
|
652
|
+
const xs = pts.map((p) => p.x);
|
|
653
|
+
const ys = pts.map((p) => p.y);
|
|
654
|
+
const minX = Math.min(...xs);
|
|
655
|
+
const minY = Math.min(...ys);
|
|
656
|
+
return { x: minX, y: minY, w: Math.max(...xs) - minX, h: Math.max(...ys) - minY };
|
|
657
|
+
};
|
|
658
|
+
const SKIP = ['defs', 'title', 'desc', 'style', 'metadata', 'clippath', 'mask', 'marker', 'pattern', 'symbol'];
|
|
659
|
+
const inSkipped = (el) => {
|
|
660
|
+
let p = el.parentElement;
|
|
661
|
+
while (p && p !== svg) {
|
|
662
|
+
if (SKIP.includes(p.tagName.toLowerCase()))
|
|
663
|
+
return true;
|
|
664
|
+
p = p.parentElement;
|
|
665
|
+
}
|
|
666
|
+
return false;
|
|
667
|
+
};
|
|
668
|
+
const GENERIC = [
|
|
669
|
+
'serif', 'sans-serif', 'monospace', 'cursive', 'fantasy', 'system-ui',
|
|
670
|
+
'ui-serif', 'ui-sans-serif', 'ui-monospace', 'ui-rounded', 'math', 'emoji', 'fangsong',
|
|
671
|
+
];
|
|
672
|
+
const ctx = document.createElement('canvas').getContext('2d');
|
|
673
|
+
const resolves = (first, sample) => {
|
|
674
|
+
if (!first)
|
|
675
|
+
return false;
|
|
676
|
+
if (GENERIC.includes(first.toLowerCase()))
|
|
677
|
+
return true;
|
|
678
|
+
if (!ctx)
|
|
679
|
+
return true;
|
|
680
|
+
ctx.font = `32px "${first}", monospace`;
|
|
681
|
+
const a = ctx.measureText(sample).width;
|
|
682
|
+
ctx.font = '32px "__agent_config_absent_family__", monospace';
|
|
683
|
+
const b = ctx.measureText(sample).width;
|
|
684
|
+
return Math.abs(a - b) > 0.01;
|
|
685
|
+
};
|
|
686
|
+
// The document's own default family. A <text> that declares nothing lands
|
|
687
|
+
// on exactly this — which is how "no family was declared" is told apart
|
|
688
|
+
// from "a family was declared and did not resolve" (R1).
|
|
689
|
+
const defaultFamily = window.getComputedStyle(document.documentElement).fontFamily;
|
|
690
|
+
const all = Array.from(svg.querySelectorAll('*'));
|
|
691
|
+
const elements = [];
|
|
692
|
+
const texts = [];
|
|
693
|
+
for (let i = 0; i < all.length; i++) {
|
|
694
|
+
const el = all[i];
|
|
695
|
+
const tag = el.tagName.toLowerCase();
|
|
696
|
+
if (SKIP.includes(tag) || inSkipped(el))
|
|
697
|
+
continue;
|
|
698
|
+
if (!('getBBox' in el) || typeof el.getBBox !== 'function')
|
|
699
|
+
continue;
|
|
700
|
+
const g = el;
|
|
701
|
+
const cs = window.getComputedStyle(g);
|
|
702
|
+
if (cs.display === 'none' || cs.visibility === 'hidden')
|
|
703
|
+
continue;
|
|
704
|
+
let geom = null;
|
|
705
|
+
try {
|
|
706
|
+
const bb = g.getBBox();
|
|
707
|
+
geom = { x: bb.x, y: bb.y, w: bb.width, h: bb.height };
|
|
708
|
+
}
|
|
709
|
+
catch {
|
|
710
|
+
geom = null;
|
|
711
|
+
}
|
|
712
|
+
if (!geom || (geom.w === 0 && geom.h === 0))
|
|
713
|
+
continue;
|
|
714
|
+
let totalLength = null;
|
|
715
|
+
const geometry = g;
|
|
716
|
+
if (typeof geometry.getTotalLength === 'function') {
|
|
717
|
+
try {
|
|
718
|
+
totalLength = geometry.getTotalLength();
|
|
719
|
+
}
|
|
720
|
+
catch {
|
|
721
|
+
totalLength = null;
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
const pathLengthRaw = Number(el.getAttribute('pathLength'));
|
|
725
|
+
let from = null;
|
|
726
|
+
let to = null;
|
|
727
|
+
for (const anim of Array.from(el.children)) {
|
|
728
|
+
if (anim.tagName.toLowerCase() !== 'animate')
|
|
729
|
+
continue;
|
|
730
|
+
if ((anim.getAttribute('attributeName') ?? '') !== 'stroke-dashoffset')
|
|
731
|
+
continue;
|
|
732
|
+
const values = (anim.getAttribute('values') ?? '').split(';').map(Number).filter((n) => Number.isFinite(n));
|
|
733
|
+
if (values.length >= 2) {
|
|
734
|
+
from = values[0];
|
|
735
|
+
to = values[values.length - 1];
|
|
736
|
+
}
|
|
737
|
+
else {
|
|
738
|
+
const f = Number(anim.getAttribute('from'));
|
|
739
|
+
const t = Number(anim.getAttribute('to'));
|
|
740
|
+
if (Number.isFinite(f))
|
|
741
|
+
from = f;
|
|
742
|
+
if (Number.isFinite(t))
|
|
743
|
+
to = t;
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
const fillOpacity = Number(cs.fillOpacity || '1');
|
|
747
|
+
const opacity = Number(cs.opacity || '1');
|
|
748
|
+
elements.push({
|
|
749
|
+
index: i,
|
|
750
|
+
id: el.getAttribute('id') ?? '',
|
|
751
|
+
tag,
|
|
752
|
+
box: userBox(el),
|
|
753
|
+
geom,
|
|
754
|
+
fill: cs.fill,
|
|
755
|
+
opaque: fillOpacity >= 0.99 && opacity >= 0.99 && cs.fill !== 'none',
|
|
756
|
+
strokeWidth: Number.parseFloat(cs.strokeWidth || '0') || 0,
|
|
757
|
+
dash: cs.strokeDasharray,
|
|
758
|
+
totalLength,
|
|
759
|
+
pathLengthAttr: Number.isFinite(pathLengthRaw) && pathLengthRaw > 0 ? pathLengthRaw : null,
|
|
760
|
+
containedBy: [],
|
|
761
|
+
dashOffsetFrom: from,
|
|
762
|
+
dashOffsetTo: to,
|
|
763
|
+
});
|
|
764
|
+
if (tag === 'text') {
|
|
765
|
+
const attrFamily = el.getAttribute('font-family');
|
|
766
|
+
const cssFamily = cs.fontFamily;
|
|
767
|
+
const hasDeclaration = attrFamily !== null || cssFamily !== defaultFamily;
|
|
768
|
+
const declared = hasDeclaration ? (attrFamily ?? cssFamily) : '';
|
|
769
|
+
const list = (declared || cssFamily).split(',');
|
|
770
|
+
const first = (list[0] ?? '').trim().replace(/^["']|["']$/g, '');
|
|
771
|
+
const boxRefId = el.getAttribute('data-box');
|
|
772
|
+
const clip = /url\(['"]?#([^)'"]+)/.exec(el.getAttribute('clip-path') ?? '');
|
|
773
|
+
const refId = boxRefId ?? (clip ? clip[1] : null);
|
|
774
|
+
let declaredBox = null;
|
|
775
|
+
if (refId) {
|
|
776
|
+
const ref = svg.querySelector(`#${CSS.escape(refId)}`);
|
|
777
|
+
if (ref && typeof ref.getBBox === 'function') {
|
|
778
|
+
const rb = ref.getBBox();
|
|
779
|
+
declaredBox = { x: rb.x, y: rb.y, w: rb.width, h: rb.height };
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
const sample = el.textContent ?? '';
|
|
783
|
+
texts.push({
|
|
784
|
+
index: i,
|
|
785
|
+
id: el.getAttribute('id') ?? '',
|
|
786
|
+
text: sample,
|
|
787
|
+
box: geom,
|
|
788
|
+
declaredBoxId: refId,
|
|
789
|
+
declaredBox,
|
|
790
|
+
declaredFamily: declared,
|
|
791
|
+
firstFamily: first,
|
|
792
|
+
resolvedFamily: cssFamily,
|
|
793
|
+
firstFamilyResolved: hasDeclaration && resolves(first, sample || 'Hamburgefonstiv'),
|
|
794
|
+
});
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
// Containment by SAMPLED FILL (step 1.2). A second pass rather than a
|
|
798
|
+
// branch in the loop above, because it needs every element to exist first.
|
|
799
|
+
// `isPointInFill` is what the renderer itself answers with, so a near-white
|
|
800
|
+
// shape beside a ring is no longer "inside" it for having a smaller box.
|
|
801
|
+
// Finding 9: the OUTER loop is restricted to the only consumer. The pass
|
|
802
|
+
// used to call `isPointInFill` for every ordered pair of geometry-bearing
|
|
803
|
+
// elements and `applyGeometry` then read `containedBy` for opaque
|
|
804
|
+
// near-white elements alone — on the 195 KB, 355-gradient trace this change
|
|
805
|
+
// cites as its motivation that is 10^5-10^6 cross-process calls whose
|
|
806
|
+
// results are discarded. The reading is identical; only the pairs nobody
|
|
807
|
+
// asks about are gone.
|
|
808
|
+
//
|
|
809
|
+
// The channel floor is PASSED, not spelled twice. Finding 10 of this
|
|
810
|
+
// change's second completion review: a literal `237` on this side of the
|
|
811
|
+
// page boundary and `NEAR_WHITE_CHANNEL` on the other, bound only by a
|
|
812
|
+
// trailing comment, is the same shape as the dead constants this branch
|
|
813
|
+
// fixes elsewhere — one of them changes and nothing says so. A
|
|
814
|
+
// `page.evaluate` body cannot close over module scope, so it arrives as an
|
|
815
|
+
// argument, exactly as `minContrast` does two files away.
|
|
816
|
+
const NEAR_WHITE = args.nearWhite;
|
|
817
|
+
const candidates = elements.filter((el) => {
|
|
818
|
+
if (el.geom === null || !el.opaque)
|
|
819
|
+
return false;
|
|
820
|
+
const m = /rgba?\(\s*(\d+)[,\s]+(\d+)[,\s]+(\d+)/.exec(el.fill);
|
|
821
|
+
return m !== null && [m[1], m[2], m[3]].every((c) => Number(c) >= NEAR_WHITE);
|
|
822
|
+
});
|
|
823
|
+
for (const inner of candidates) {
|
|
824
|
+
if (!inner.geom)
|
|
825
|
+
continue;
|
|
826
|
+
const cx = inner.geom.x + inner.geom.w / 2;
|
|
827
|
+
const cy = inner.geom.y + inner.geom.h / 2;
|
|
828
|
+
for (const outer of elements) {
|
|
829
|
+
if (outer.index === inner.index)
|
|
830
|
+
continue;
|
|
831
|
+
const node = all[outer.index];
|
|
832
|
+
if (typeof node.isPointInFill !== 'function')
|
|
833
|
+
continue;
|
|
834
|
+
try {
|
|
835
|
+
if (node.isPointInFill({ x: cx, y: cy }))
|
|
836
|
+
inner.containedBy.push(outer.index);
|
|
837
|
+
}
|
|
838
|
+
catch {
|
|
839
|
+
// A node that refuses the question answers "not contained",
|
|
840
|
+
// which is the safe direction: it produces no finding.
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
return { viewBox, elements, texts };
|
|
845
|
+
}
|
|
846
|
+
function readAccessibleNamesInPage() {
|
|
847
|
+
const out = [];
|
|
848
|
+
for (const svg of Array.from(document.querySelectorAll('svg'))) {
|
|
849
|
+
const role = svg.getAttribute('role') ?? '';
|
|
850
|
+
if (svg.getAttribute('aria-hidden') === 'true')
|
|
851
|
+
continue;
|
|
852
|
+
if (!/^(img|graphics-document|graphics-symbol)$/.test(role))
|
|
853
|
+
continue;
|
|
854
|
+
const label = svg.getAttribute('aria-label');
|
|
855
|
+
if (label) {
|
|
856
|
+
out.push(label.trim());
|
|
857
|
+
continue;
|
|
858
|
+
}
|
|
859
|
+
const by = svg.getAttribute('aria-labelledby');
|
|
860
|
+
if (by) {
|
|
861
|
+
// EVERY id, joined by a space. Finding 9 of the fifth completion
|
|
862
|
+
// review: this read the first id only, while the static rule in
|
|
863
|
+
// `svg_rules.ts` resolves the whole list — so for
|
|
864
|
+
// `aria-labelledby="a b"` the two halves of one change disagreed
|
|
865
|
+
// about what the accessible name IS, and the rendered reading
|
|
866
|
+
// reported a shorter name than the file actually exposes. The
|
|
867
|
+
// accname spec concatenates the referenced elements in order.
|
|
868
|
+
const parts = by
|
|
869
|
+
.split(/\s+/)
|
|
870
|
+
.filter((id) => id !== '')
|
|
871
|
+
.map((id) => (document.getElementById(id)?.textContent ?? '').trim())
|
|
872
|
+
.filter((t) => t !== '');
|
|
873
|
+
out.push(parts.join(' '));
|
|
874
|
+
continue;
|
|
875
|
+
}
|
|
876
|
+
const title = svg.querySelector(':scope > title');
|
|
877
|
+
if (title)
|
|
878
|
+
out.push((title.textContent ?? '').trim());
|
|
879
|
+
}
|
|
880
|
+
for (const el of Array.from(document.querySelectorAll('[role="img"]:not(svg)'))) {
|
|
881
|
+
const label = el.getAttribute('aria-label');
|
|
882
|
+
if (label)
|
|
883
|
+
out.push(label.trim());
|
|
884
|
+
}
|
|
885
|
+
return out.filter((n) => n !== '');
|
|
886
|
+
}
|
|
887
|
+
/** Seconds to the end of one iteration of the longest animation; 0 when static. */
|
|
888
|
+
function readAnimationEndInPage() {
|
|
889
|
+
let end = 0;
|
|
890
|
+
const seconds = (raw) => {
|
|
891
|
+
if (!raw)
|
|
892
|
+
return 0;
|
|
893
|
+
const m = /^\s*([\d.]+)\s*(ms|s)?\s*$/.exec(raw);
|
|
894
|
+
if (!m)
|
|
895
|
+
return 0;
|
|
896
|
+
const v = Number(m[1]);
|
|
897
|
+
return m[2] === 'ms' ? v / 1000 : v;
|
|
898
|
+
};
|
|
899
|
+
for (const el of Array.from(document.querySelectorAll('animate,animateTransform,animateMotion,animateColor,set'))) {
|
|
900
|
+
const dur = seconds(el.getAttribute('dur'));
|
|
901
|
+
const rc = el.getAttribute('repeatCount');
|
|
902
|
+
const reps = rc && rc !== 'indefinite' && Number.isFinite(Number(rc)) ? Number(rc) : 1;
|
|
903
|
+
end = Math.max(end, dur * reps);
|
|
904
|
+
}
|
|
905
|
+
for (const anim of document.getAnimations()) {
|
|
906
|
+
const timing = anim.effect?.getComputedTiming();
|
|
907
|
+
const d = typeof timing?.duration === 'number' ? timing.duration / 1000 : 0;
|
|
908
|
+
end = Math.max(end, d);
|
|
909
|
+
}
|
|
910
|
+
return end;
|
|
911
|
+
}
|
|
912
|
+
/**
|
|
913
|
+
* A declared time, held. `screenshot({ animations: 'disabled' })` does NOT
|
|
914
|
+
* freeze SMIL (R6) — `pauseAnimations()` plus `setCurrentTime()` does.
|
|
915
|
+
*/
|
|
916
|
+
function seekInPage(t) {
|
|
917
|
+
for (const svg of Array.from(document.querySelectorAll('svg'))) {
|
|
918
|
+
const s = svg;
|
|
919
|
+
if (typeof s.pauseAnimations === 'function') {
|
|
920
|
+
s.pauseAnimations();
|
|
921
|
+
s.setCurrentTime(t);
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
for (const anim of document.getAnimations()) {
|
|
925
|
+
anim.pause();
|
|
926
|
+
anim.currentTime = t * 1000;
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
// ---------------------------------------------------------------------------
|
|
930
|
+
// Assembling the reading
|
|
931
|
+
// ---------------------------------------------------------------------------
|
|
932
|
+
function applyGeometry(reading, raw, vb) {
|
|
933
|
+
const EPS = 1e-6;
|
|
934
|
+
let box = null;
|
|
935
|
+
for (const el of raw.elements) {
|
|
936
|
+
const b = el.box ?? el.geom;
|
|
937
|
+
if (!b)
|
|
938
|
+
continue;
|
|
939
|
+
box = box
|
|
940
|
+
? {
|
|
941
|
+
x: Math.min(box.x, b.x),
|
|
942
|
+
y: Math.min(box.y, b.y),
|
|
943
|
+
w: Math.max(box.x + box.w, b.x + b.w) - Math.min(box.x, b.x),
|
|
944
|
+
h: Math.max(box.y + box.h, b.y + b.h) - Math.min(box.y, b.y),
|
|
945
|
+
}
|
|
946
|
+
: { ...b };
|
|
947
|
+
if (vb) {
|
|
948
|
+
const outLeft = b.x < vb.x - EPS;
|
|
949
|
+
const outTop = b.y < vb.y - EPS;
|
|
950
|
+
const outRight = b.x + b.w > vb.x + vb.w + EPS;
|
|
951
|
+
const outBottom = b.y + b.h > vb.y + vb.h + EPS;
|
|
952
|
+
if (outLeft || outTop || outRight || outBottom) {
|
|
953
|
+
reading.outside_view_box.push({
|
|
954
|
+
index: el.index,
|
|
955
|
+
id: el.id,
|
|
956
|
+
tag: el.tag,
|
|
957
|
+
box: b,
|
|
958
|
+
wholly_outside: b.x + b.w <= vb.x || b.y + b.h <= vb.y || b.x >= vb.x + vb.w || b.y >= vb.y + vb.h,
|
|
959
|
+
});
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
reading.painted_box = box;
|
|
964
|
+
if (box && vb && vb.w > 0 && vb.h > 0) {
|
|
965
|
+
const clipped = {
|
|
966
|
+
x0: Math.max(box.x, vb.x),
|
|
967
|
+
y0: Math.max(box.y, vb.y),
|
|
968
|
+
x1: Math.min(box.x + box.w, vb.x + vb.w),
|
|
969
|
+
y1: Math.min(box.y + box.h, vb.y + vb.h),
|
|
970
|
+
};
|
|
971
|
+
const area = Math.max(0, clipped.x1 - clipped.x0) * Math.max(0, clipped.y1 - clipped.y0);
|
|
972
|
+
reading.fill_ratio = area / (vb.w * vb.h);
|
|
973
|
+
}
|
|
974
|
+
for (const t of raw.texts) {
|
|
975
|
+
const declaredBox = t.declaredBox ?? (vb ? { x: vb.x, y: vb.y, w: vb.w, h: vb.h } : t.box);
|
|
976
|
+
reading.texts.push({
|
|
977
|
+
index: t.index,
|
|
978
|
+
id: t.id,
|
|
979
|
+
text: t.text,
|
|
980
|
+
box: t.box,
|
|
981
|
+
declared_box_id: t.declaredBoxId,
|
|
982
|
+
declared_box: declaredBox,
|
|
983
|
+
fits: t.box.x >= declaredBox.x - 0.5 &&
|
|
984
|
+
t.box.y >= declaredBox.y - 0.5 &&
|
|
985
|
+
t.box.x + t.box.w <= declaredBox.x + declaredBox.w + 0.5 &&
|
|
986
|
+
t.box.y + t.box.h <= declaredBox.y + declaredBox.h + 0.5,
|
|
987
|
+
declared_family: t.declaredFamily,
|
|
988
|
+
first_family: t.firstFamily,
|
|
989
|
+
resolved_family: t.resolvedFamily,
|
|
990
|
+
first_family_resolved: t.firstFamilyResolved,
|
|
991
|
+
});
|
|
992
|
+
}
|
|
993
|
+
for (const el of raw.elements) {
|
|
994
|
+
if (el.dashOffsetFrom === null || el.dashOffsetTo === null)
|
|
995
|
+
continue;
|
|
996
|
+
if (!el.totalLength || el.totalLength <= 0)
|
|
997
|
+
continue;
|
|
998
|
+
const dashFirst = Number.parseFloat(el.dash.split(/[\s,]+/)[0] ?? '');
|
|
999
|
+
if (!Number.isFinite(dashFirst) || dashFirst <= 0)
|
|
1000
|
+
continue;
|
|
1001
|
+
const scale = el.pathLengthAttr ? el.totalLength / el.pathLengthAttr : 1;
|
|
1002
|
+
reading.draw_on.push({
|
|
1003
|
+
index: el.index,
|
|
1004
|
+
id: el.id,
|
|
1005
|
+
total_length: el.totalLength,
|
|
1006
|
+
path_length_attr: el.pathLengthAttr,
|
|
1007
|
+
dash_units: dashFirst * scale,
|
|
1008
|
+
offset_from: el.dashOffsetFrom * scale,
|
|
1009
|
+
offset_to: el.dashOffsetTo * scale,
|
|
1010
|
+
dead_fraction: drawOnDeadFraction(dashFirst * scale, el.dashOffsetFrom * scale, el.dashOffsetTo * scale, el.totalLength),
|
|
1011
|
+
});
|
|
1012
|
+
}
|
|
1013
|
+
// Near-white paint where the source shows a hole (S5). Geometry, not pixels:
|
|
1014
|
+
// an opaque near-white shape wholly inside a non-near-white painted shape is
|
|
1015
|
+
// a filled hole, on any background — which is why no render is needed to
|
|
1016
|
+
// decide it and why the dark capture above is confirmation rather than input.
|
|
1017
|
+
const isNearWhite = (fill) => {
|
|
1018
|
+
const m = /rgba?\(\s*(\d+)[,\s]+(\d+)[,\s]+(\d+)/.exec(fill);
|
|
1019
|
+
if (!m)
|
|
1020
|
+
return false;
|
|
1021
|
+
return [m[1], m[2], m[3]].every((c) => Number(c) >= NEAR_WHITE_CHANNEL);
|
|
1022
|
+
};
|
|
1023
|
+
for (const inner of raw.elements) {
|
|
1024
|
+
if (!inner.opaque || !isNearWhite(inner.fill))
|
|
1025
|
+
continue;
|
|
1026
|
+
if (!inner.geom)
|
|
1027
|
+
continue;
|
|
1028
|
+
// Step 1.2: containment is the SAMPLED fill, computed in-page by
|
|
1029
|
+
// `isPointInFill`, not a bounding-box comparison. A box test calls a
|
|
1030
|
+
// near-white shape "inside" a ring, an L or a crescent it never
|
|
1031
|
+
// touches, which is how this rule came to fire six times on paint an
|
|
1032
|
+
// owner had deliberately chosen.
|
|
1033
|
+
const host = raw.elements.find((o) => inner.containedBy.includes(o.index) && o.opaque && !isNearWhite(o.fill));
|
|
1034
|
+
if (host) {
|
|
1035
|
+
reading.near_white_regions.push({
|
|
1036
|
+
index: inner.index,
|
|
1037
|
+
id: inner.id,
|
|
1038
|
+
fill: inner.fill,
|
|
1039
|
+
enclosed_by: host.id || `${host.tag}@${host.index}`,
|
|
1040
|
+
centre: { x: inner.geom.x + inner.geom.w / 2, y: inner.geom.y + inner.geom.h / 2 },
|
|
1041
|
+
// Filled in by the source sampling when a registered raster is
|
|
1042
|
+
// supplied. Absent evidence stays absent.
|
|
1043
|
+
source_verdict: 'no-source',
|
|
1044
|
+
});
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
function deriveFindings(r) {
|
|
1049
|
+
const out = faceFindings(r);
|
|
1050
|
+
if (r.fill_ratio !== null && r.fill_ratio < r.fill_ratio_threshold) {
|
|
1051
|
+
out.push({
|
|
1052
|
+
rule: 'structure/loose-viewbox',
|
|
1053
|
+
severity: 'warning',
|
|
1054
|
+
detail: `fill ratio ${r.fill_ratio.toFixed(3)} is below the ${r.asset_class} threshold ` +
|
|
1055
|
+
`${r.fill_ratio_threshold} (${r.fill_ratio_threshold_source}) — the painted box leaves the rest of the viewBox empty`,
|
|
1056
|
+
});
|
|
1057
|
+
}
|
|
1058
|
+
for (const t of r.texts) {
|
|
1059
|
+
if (!t.fits) {
|
|
1060
|
+
out.push({
|
|
1061
|
+
rule: 'geometry/text-overflows-box',
|
|
1062
|
+
severity: 'warning',
|
|
1063
|
+
detail: `<text>${t.id ? ` #${t.id}` : ''} renders ${t.box.w.toFixed(1)} x ${t.box.h.toFixed(1)} units at ` +
|
|
1064
|
+
`(${t.box.x.toFixed(1)}, ${t.box.y.toFixed(1)}), outside its declared box ` +
|
|
1065
|
+
`${t.declared_box_id ? `#${t.declared_box_id} ` : '(the viewBox) '}` +
|
|
1066
|
+
`${t.declared_box.w.toFixed(1)} x ${t.declared_box.h.toFixed(1)}`,
|
|
1067
|
+
});
|
|
1068
|
+
}
|
|
1069
|
+
if (!t.first_family_resolved) {
|
|
1070
|
+
out.push({
|
|
1071
|
+
rule: 'geometry/default-font-fallback',
|
|
1072
|
+
severity: 'warning',
|
|
1073
|
+
detail: t.declared_family
|
|
1074
|
+
? `declared family "${t.first_family}" did not resolve; the renderer used "${t.resolved_family}"`
|
|
1075
|
+
: `no font-family is declared, so the label resolved to the renderer default "${t.resolved_family}"`,
|
|
1076
|
+
});
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
for (const d of r.draw_on) {
|
|
1080
|
+
if (d.dead_fraction >= DRAW_ON_DEAD_TIME_THRESHOLD) {
|
|
1081
|
+
out.push({
|
|
1082
|
+
rule: 'motion/draw-on-dead-time',
|
|
1083
|
+
severity: 'warning',
|
|
1084
|
+
detail: `${(d.dead_fraction * 100).toFixed(1)}% of the draw-on duration paints nothing new: a ` +
|
|
1085
|
+
`${d.dash_units.toFixed(1)}-unit dash on a ${d.total_length.toFixed(2)}-unit path` +
|
|
1086
|
+
`${d.path_length_attr === null ? ' with no pathLength declared' : ''}`,
|
|
1087
|
+
});
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
// Steps 4.1 and 4.2. Both name the GROUND they were read on, because
|
|
1091
|
+
// "the mark is hard to see" is not a property of the mark.
|
|
1092
|
+
for (const g of r.grounds) {
|
|
1093
|
+
if (g.background === 'dark' && g.silhouette_px > 0 && g.bleed_share > BLEED_SHARE_THRESHOLD) {
|
|
1094
|
+
out.push({
|
|
1095
|
+
rule: 'geometry/background-bleed-inside-silhouette',
|
|
1096
|
+
severity: 'warning',
|
|
1097
|
+
detail: `${g.bleed_px} of ${g.silhouette_px} pixels inside the silhouette read as the ${g.background} ` +
|
|
1098
|
+
`ground (${g.background_color}) — background showing THROUGH the graphic, which is what a ` +
|
|
1099
|
+
'hairline seam between two abutting shapes looks like. Let the earlier surface reach about a ' +
|
|
1100
|
+
`pixel under its neighbour, or declare the gap with ${HOLE_ATTRIBUTE} if it is meant to be there`,
|
|
1101
|
+
});
|
|
1102
|
+
}
|
|
1103
|
+
if (g.silhouette_px > 0 && g.low_contrast_share > LOW_CONTRAST_SHARE_THRESHOLD) {
|
|
1104
|
+
out.push({
|
|
1105
|
+
rule: 'accessibility/low-contrast-on-background',
|
|
1106
|
+
severity: 'warning',
|
|
1107
|
+
detail: `${(g.low_contrast_share * 100).toFixed(0)} % of the painted pixels fall below a 3:1 contrast ` +
|
|
1108
|
+
`ratio against the ${g.background} ground (${g.background_color}) — build a variant for that ` +
|
|
1109
|
+
'ground, or name it as missing',
|
|
1110
|
+
});
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
// Step 2.1: the two readings the subject-only comparison separates out.
|
|
1114
|
+
// Neither is folded into IoU, and neither is silent.
|
|
1115
|
+
const raster = r.comparison?.raster;
|
|
1116
|
+
if (raster) {
|
|
1117
|
+
if (raster.frame_edge_contact.length > 0) {
|
|
1118
|
+
out.push({
|
|
1119
|
+
rule: 'geometry/source-cut-by-frame',
|
|
1120
|
+
severity: 'warning',
|
|
1121
|
+
detail: `the registered source runs into its frame on the ${raster.frame_edge_contact.join(', ')} ` +
|
|
1122
|
+
'edge(s) — content cut by a crop agrees with a trace of that crop exactly, so the silhouette ' +
|
|
1123
|
+
'reading cannot see the cut. Widen the crop and re-register before trusting the numbers',
|
|
1124
|
+
});
|
|
1125
|
+
}
|
|
1126
|
+
if (raster.foreign_content_share > FOREIGN_CONTENT_SHARE_THRESHOLD) {
|
|
1127
|
+
out.push({
|
|
1128
|
+
rule: 'geometry/foreign-content-in-frame',
|
|
1129
|
+
severity: 'warning',
|
|
1130
|
+
detail: `${(raster.foreign_content_share * 100).toFixed(1)} % of the registered source's painted pixels ` +
|
|
1131
|
+
'lie outside the subject — a neighbouring mark, a glow or a shadow inside the registration ' +
|
|
1132
|
+
'frame. It is reported here and kept out of the silhouette and colour readings',
|
|
1133
|
+
});
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
for (const n of r.near_white_regions) {
|
|
1137
|
+
// Step 1.2: a finding needs EVIDENCE of a hole. Without a registered
|
|
1138
|
+
// source the region is recorded in the manifest and nothing is pushed —
|
|
1139
|
+
// the rule's message was always conditional on what the source shows,
|
|
1140
|
+
// and nothing was reading it.
|
|
1141
|
+
if (n.source_verdict !== 'hole')
|
|
1142
|
+
continue;
|
|
1143
|
+
out.push({
|
|
1144
|
+
rule: 'geometry/near-white-hole',
|
|
1145
|
+
severity: 'warning',
|
|
1146
|
+
detail: `${n.fill} is painted opaquely inside ${n.enclosed_by}, and the registered source shows its own ` +
|
|
1147
|
+
'background there — a hole must be cut out, not painted near-white, or it reads white on a dark ' +
|
|
1148
|
+
'background (S5)',
|
|
1149
|
+
});
|
|
1150
|
+
}
|
|
1151
|
+
if (r.icon_grid?.status === 'read') {
|
|
1152
|
+
const g = r.icon_grid;
|
|
1153
|
+
const strokeOff = g.stroke_width !== null &&
|
|
1154
|
+
g.reference_stroke_widths.length > 0 &&
|
|
1155
|
+
!g.reference_stroke_widths.some((s) => Math.abs(s - g.stroke_width) < 0.01);
|
|
1156
|
+
const minRefPadding = Math.min(...g.reference_padding);
|
|
1157
|
+
const paddingOff = g.padding !== null && g.padding < minRefPadding - 0.01;
|
|
1158
|
+
if (strokeOff || paddingOff) {
|
|
1159
|
+
out.push({
|
|
1160
|
+
rule: 'geometry/icon-off-grid',
|
|
1161
|
+
severity: 'warning',
|
|
1162
|
+
detail: `stroke ${g.stroke_width ?? 'none'} / padding ${g.padding?.toFixed(2) ?? 'none'} against the incumbent set's ` +
|
|
1163
|
+
`stroke ${g.reference_stroke_widths.join('/')} and padding ${g.reference_padding.join('/')} ` +
|
|
1164
|
+
`(read from ${g.reference_files.join(', ')})`,
|
|
1165
|
+
});
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
// Measured, and previously derived nothing: an inline asset whose route was
|
|
1169
|
+
// read in a real browser and did NOT stop the motion produced an empty
|
|
1170
|
+
// findings array, so the one layer that can actually see the failure stayed
|
|
1171
|
+
// silent about it.
|
|
1172
|
+
if (r.reduced_motion.witnessable && r.reduced_motion.frames_equal === false) {
|
|
1173
|
+
out.push({
|
|
1174
|
+
rule: 'motion/reduced-motion-route-does-not-stop-it',
|
|
1175
|
+
severity: 'error',
|
|
1176
|
+
detail: 'under an emulated prefers-reduced-motion: reduce the frames still differ — the route this ' +
|
|
1177
|
+
'file declares does not stop its animation, which is the R4 failure measured rather than inferred',
|
|
1178
|
+
});
|
|
1179
|
+
}
|
|
1180
|
+
// Step 1.1: gated on the duration, not only on the host's route. A host
|
|
1181
|
+
// with no `<picture>` route is correct for a file that does not move, and
|
|
1182
|
+
// this used to fire on all six files of a real conversion session — none
|
|
1183
|
+
// of which carries `<animate`, `@keyframes` or `animation`. A warning that
|
|
1184
|
+
// appears on every file is a warning an operator learns to scroll past,
|
|
1185
|
+
// which is Risk 1 of the parent roadmap arriving by a different door.
|
|
1186
|
+
const delivered = r.reduced_motion.delivered_animates;
|
|
1187
|
+
const unread = r.reduced_motion.delivered_unreadable ?? [];
|
|
1188
|
+
// Finding 13. An unreadable delivered file is UNKNOWN, and unknown is
|
|
1189
|
+
// reported rather than passed: the page-duration fallback reads 0 for a
|
|
1190
|
+
// spinner and a static logo alike, so falling back to it here would be the
|
|
1191
|
+
// absent-is-not-clean failure the rest of this change argues against.
|
|
1192
|
+
const moves = unread.length > 0
|
|
1193
|
+
? true
|
|
1194
|
+
: delivered === null || delivered === undefined
|
|
1195
|
+
? r.animation_duration_s > 0
|
|
1196
|
+
: delivered;
|
|
1197
|
+
if (r.reduced_motion.host_picture_route === false && moves) {
|
|
1198
|
+
out.push({
|
|
1199
|
+
rule: 'motion/img-delivery-no-static-route',
|
|
1200
|
+
severity: 'warning',
|
|
1201
|
+
detail: (unread.length > 0
|
|
1202
|
+
? `${unread.join(', ')} could not be read, so whether it animates is unknown — reported ` +
|
|
1203
|
+
'rather than passed. '
|
|
1204
|
+
: '') +
|
|
1205
|
+
'an animated SVG is delivered through <img> or a CSS background and the host carries no ' +
|
|
1206
|
+
'<picture><source media="(prefers-reduced-motion: reduce)"> route — the emulated preference ' +
|
|
1207
|
+
'does not reach an image document (R5), so this is the only place the route can live',
|
|
1208
|
+
});
|
|
1209
|
+
}
|
|
1210
|
+
for (const name of r.duplicate_accessible_names) {
|
|
1211
|
+
out.push({
|
|
1212
|
+
rule: 'accessibility/duplicate-accessible-name',
|
|
1213
|
+
severity: 'warning',
|
|
1214
|
+
detail: `"${name}" names more than one graphic on this page (R7)`,
|
|
1215
|
+
});
|
|
1216
|
+
}
|
|
1217
|
+
return out;
|
|
1218
|
+
}
|
|
1219
|
+
async function readReducedMotion(context, spec) {
|
|
1220
|
+
const R5 = 'an image document is unreachable by media emulation; the host\'s <picture> route is the thing to check';
|
|
1221
|
+
const page = await openPage(context, {
|
|
1222
|
+
url: spec.url,
|
|
1223
|
+
hostHtml: spec.hostHtml,
|
|
1224
|
+
hostBasename: HOST_BASENAME,
|
|
1225
|
+
width: spec.width,
|
|
1226
|
+
height: spec.height,
|
|
1227
|
+
background: spec.background,
|
|
1228
|
+
cssWidth: spec.cssWidth,
|
|
1229
|
+
blocked: spec.blocked,
|
|
1230
|
+
fileRoots: spec.fileRoots,
|
|
1231
|
+
reducedMotion: true,
|
|
1232
|
+
});
|
|
1233
|
+
try {
|
|
1234
|
+
if (spec.delivery === 'img' || spec.delivery === 'css-bg') {
|
|
1235
|
+
const seen = await page.evaluate(() => {
|
|
1236
|
+
const sources = Array.from(document.querySelectorAll('picture > source'));
|
|
1237
|
+
const route = sources.some((s) => /prefers-reduced-motion\s*:\s*reduce/i.test(s.getAttribute('media') ?? ''));
|
|
1238
|
+
// Every file this host DELIVERS, by its own reference — the
|
|
1239
|
+
// `<img>` src, each `<source srcset>`, and any CSS
|
|
1240
|
+
// background-image url. Names only; they are resolved on disk.
|
|
1241
|
+
const refs = new Set();
|
|
1242
|
+
for (const img of Array.from(document.querySelectorAll('img'))) {
|
|
1243
|
+
const src = img.getAttribute('src');
|
|
1244
|
+
if (src)
|
|
1245
|
+
refs.add(src);
|
|
1246
|
+
}
|
|
1247
|
+
for (const s of sources) {
|
|
1248
|
+
for (const part of (s.getAttribute('srcset') ?? '').split(',')) {
|
|
1249
|
+
const url = part.trim().split(/\s+/)[0];
|
|
1250
|
+
if (url)
|
|
1251
|
+
refs.add(url);
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
for (const el of Array.from(document.querySelectorAll('*'))) {
|
|
1255
|
+
const bg = window.getComputedStyle(el).backgroundImage;
|
|
1256
|
+
const m = /url\((['"]?)([^)'"]+)\1\)/.exec(bg);
|
|
1257
|
+
if (m?.[2])
|
|
1258
|
+
refs.add(m[2]);
|
|
1259
|
+
}
|
|
1260
|
+
return { route, refs: [...refs] };
|
|
1261
|
+
});
|
|
1262
|
+
// Step 1.1: the question is about the DELIVERED file, and the host
|
|
1263
|
+
// page cannot answer it — its own duration is 0 for a spinner and
|
|
1264
|
+
// for a static logo alike.
|
|
1265
|
+
let animates = null;
|
|
1266
|
+
const unreadable = [];
|
|
1267
|
+
for (const ref of seen.refs) {
|
|
1268
|
+
if (/^(?:https?:|data:)/i.test(ref))
|
|
1269
|
+
continue;
|
|
1270
|
+
const abs = path.resolve(spec.hostDir, ref.replace(/^file:\/\//, ''));
|
|
1271
|
+
if (!/\.svg$/i.test(abs))
|
|
1272
|
+
continue;
|
|
1273
|
+
let body;
|
|
1274
|
+
try {
|
|
1275
|
+
body = fs.readFileSync(abs, 'utf8');
|
|
1276
|
+
}
|
|
1277
|
+
catch {
|
|
1278
|
+
// Finding 13: an `.svg` this host DELIVERS that could not
|
|
1279
|
+
// be read is absent evidence, and absent is not clean. It
|
|
1280
|
+
// used to `continue` into the page-duration fallback, which
|
|
1281
|
+
// that duration's own doc calls useless for an image
|
|
1282
|
+
// delivery — so an animating file behind an unreadable path
|
|
1283
|
+
// produced no warning at all.
|
|
1284
|
+
unreadable.push(ref);
|
|
1285
|
+
continue;
|
|
1286
|
+
}
|
|
1287
|
+
const moves = hasSmilMarkup(body) || hasCssAnimation(body);
|
|
1288
|
+
animates = (animates ?? false) || moves;
|
|
1289
|
+
}
|
|
1290
|
+
return {
|
|
1291
|
+
witnessable: false,
|
|
1292
|
+
frames_equal: null,
|
|
1293
|
+
reason: R5,
|
|
1294
|
+
host_picture_route: seen.route,
|
|
1295
|
+
delivered_animates: animates,
|
|
1296
|
+
...(unreadable.length > 0 ? { delivered_unreadable: unreadable } : {}),
|
|
1297
|
+
};
|
|
1298
|
+
}
|
|
1299
|
+
if (spec.duration <= 0) {
|
|
1300
|
+
return { witnessable: true, frames_equal: true, reason: 'nothing animates in this file' };
|
|
1301
|
+
}
|
|
1302
|
+
await page.evaluate(seekInPage, 0);
|
|
1303
|
+
const a = sha256(await page.screenshot());
|
|
1304
|
+
await page.evaluate(seekInPage, spec.duration / 2);
|
|
1305
|
+
const b = sha256(await page.screenshot());
|
|
1306
|
+
return {
|
|
1307
|
+
witnessable: true,
|
|
1308
|
+
frames_equal: a === b,
|
|
1309
|
+
reason: 'inline delivery: the emulated preference reaches this document',
|
|
1310
|
+
};
|
|
1311
|
+
}
|
|
1312
|
+
finally {
|
|
1313
|
+
await page.close();
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
//# sourceMappingURL=uiRenderAsset.js.map
|