@xgent-ai/skills 0.3.0 → 0.5.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/README.md +117 -11
- package/bin/xgent-skills.js +269 -9
- package/docs/claude-context-goal-guard.md +125 -0
- package/docs/codex-context-goal-guard.md +110 -0
- package/hooks/claude/context-goal-guard.js +139 -0
- package/hooks/claude/settings.example.json +26 -0
- package/hooks/codex/context-goal-guard.py +189 -0
- package/hooks/codex/hooks.example.json +26 -0
- package/package.json +7 -1
- package/skills/agi-mode/SKILL.md +225 -0
- package/skills/agi-mode/evals/baseline-coverage.md +96 -0
- package/skills/agi-mode/evals/scenarios.md +331 -0
- package/skills/agi-mode/evals/test_check_evidence.py +141 -0
- package/skills/agi-mode/playbooks/arena.md +47 -0
- package/skills/agi-mode/playbooks/authoring-a-skill.md +26 -0
- package/skills/agi-mode/playbooks/autonomous-run.md +39 -0
- package/skills/agi-mode/playbooks/bug-fix.md +27 -0
- package/skills/agi-mode/playbooks/design-prototype.md +41 -0
- package/skills/agi-mode/playbooks/eval.md +72 -0
- package/skills/agi-mode/playbooks/feature.md +26 -0
- package/skills/agi-mode/playbooks/forensics.md +31 -0
- package/skills/agi-mode/playbooks/hillclimb.md +73 -0
- package/skills/agi-mode/playbooks/investigation.md +29 -0
- package/skills/agi-mode/playbooks/migration-refactoring.md +27 -0
- package/skills/agi-mode/playbooks/optimization.md +27 -0
- package/skills/agi-mode/playbooks/pr-lifecycle.md +38 -0
- package/skills/agi-mode/playbooks/program.md +57 -0
- package/skills/agi-mode/playbooks/reflection.md +41 -0
- package/skills/agi-mode/playbooks/resource-cleanup.md +24 -0
- package/skills/agi-mode/playbooks/review.md +27 -0
- package/skills/agi-mode/playbooks/session-handoff.md +37 -0
- package/skills/agi-mode/playbooks/shipping.md +35 -0
- package/skills/agi-mode/playbooks/ui-delivery.md +34 -0
- package/skills/agi-mode/playbooks/unblock.md +54 -0
- package/skills/agi-mode/principles/autonomous-decisions.md +21 -0
- package/skills/agi-mode/principles/behavioral-tests.md +21 -0
- package/skills/agi-mode/principles/boundary-discipline.md +21 -0
- package/skills/agi-mode/principles/bounded-convergence.md +21 -0
- package/skills/agi-mode/principles/build-leverage.md +21 -0
- package/skills/agi-mode/principles/challenge-premises.md +21 -0
- package/skills/agi-mode/principles/complete-migrations.md +21 -0
- package/skills/agi-mode/principles/context-as-state.md +21 -0
- package/skills/agi-mode/principles/critical-path-throughput.md +21 -0
- package/skills/agi-mode/principles/domain-models-and-types.md +23 -0
- package/skills/agi-mode/principles/evidence-lifetime.md +21 -0
- package/skills/agi-mode/principles/experience-first.md +21 -0
- package/skills/agi-mode/principles/explore-alternatives.md +21 -0
- package/skills/agi-mode/principles/falsifiable-verification.md +21 -0
- package/skills/agi-mode/principles/foundations-first.md +21 -0
- package/skills/agi-mode/principles/idempotent-effects.md +21 -0
- package/skills/agi-mode/principles/information-first.md +21 -0
- package/skills/agi-mode/principles/minimize-complexity.md +21 -0
- package/skills/agi-mode/principles/outcome-ownership.md +21 -0
- package/skills/agi-mode/principles/prove-real-outcomes.md +21 -0
- package/skills/agi-mode/principles/redesign-from-constraints.md +21 -0
- package/skills/agi-mode/principles/root-cause.md +21 -0
- package/skills/agi-mode/principles/state-ownership.md +21 -0
- package/skills/agi-mode/principles/structural-learning.md +21 -0
- package/skills/agi-mode/principles/verifiable-units.md +21 -0
- package/skills/agi-mode/references/continuity.md +75 -0
- package/skills/agi-mode/references/eval-protocol.md +71 -0
- package/skills/agi-mode/references/evidence-contract.md +57 -0
- package/skills/agi-mode/references/evolution-evidence.md +71 -0
- package/skills/agi-mode/references/experiments.md +82 -0
- package/skills/agi-mode/references/reflection/divergent-reviewer.md +45 -0
- package/skills/agi-mode/references/reflection/judgment-reviewer.md +44 -0
- package/skills/agi-mode/references/reflection/synthesizer.md +39 -0
- package/skills/agi-mode/references/reflection/tooling-reviewer.md +57 -0
- package/skills/agi-mode/references/skill-evolution.md +50 -0
- package/skills/agi-mode/scripts/check_evidence.py +147 -0
- package/skills/apply-code-review/SKILL.md +53 -0
- package/skills/apply-code-review/agents/openai.yaml +4 -0
- package/skills/apply-code-review/references/record-contract.md +77 -0
- package/skills/apply-doc-review/SKILL.md +79 -0
- package/skills/apply-doc-review/agents/openai.yaml +4 -0
- package/skills/apply-doc-review/references/disposition-contract.md +57 -0
- package/skills/archify/LICENSE +22 -0
- package/skills/archify/SKILL.md +137 -0
- package/skills/archify/THIRD_PARTY_NOTICES.md +69 -0
- package/skills/archify/assets/JetBrainsMono-OFL.txt +93 -0
- package/skills/archify/assets/template.html +14937 -0
- package/skills/archify/bin/archify.mjs +2139 -0
- package/skills/archify/bin/open-artifact.mjs +86 -0
- package/skills/archify/bin/preview.mjs +653 -0
- package/skills/archify/bin/visual-check.mjs +829 -0
- package/skills/archify/brand-marks/README.md +31 -0
- package/skills/archify/brand-marks/catalog.json +131 -0
- package/skills/archify/delta/architecture-delta.mjs +1232 -0
- package/skills/archify/examples/agent-run.lifecycle.json +60 -0
- package/skills/archify/examples/agent-tool-call.workflow.json +94 -0
- package/skills/archify/examples/async-job-roundtrip.sequence.json +61 -0
- package/skills/archify/examples/brand-aware-delivery.architecture.json +47 -0
- package/skills/archify/examples/cache-miss-request.sequence.json +82 -0
- package/skills/archify/examples/checkout-platform.base.architecture.json +31 -0
- package/skills/archify/examples/checkout-platform.head.architecture.json +31 -0
- package/skills/archify/examples/dataflow-product-analytics.html +15048 -0
- package/skills/archify/examples/deployment-release.lifecycle.json +49 -0
- package/skills/archify/examples/event-stream.dataflow.json +57 -0
- package/skills/archify/examples/incident-response.workflow.json +64 -0
- package/skills/archify/examples/lifecycle-agent-run.html +14983 -0
- package/skills/archify/examples/product-analytics.dataflow.json +76 -0
- package/skills/archify/examples/production-deployment.architecture.json +71 -0
- package/skills/archify/examples/release-delivery.workflow.json +62 -0
- package/skills/archify/examples/sequence-cache-miss-request.html +15063 -0
- package/skills/archify/examples/web-app-rendered.html +15012 -0
- package/skills/archify/examples/web-app.architecture.json +46 -0
- package/skills/archify/examples/workflow-agent-tool-call-rendered.html +15054 -0
- package/skills/archify/migrations/workflow-v2.mjs +279 -0
- package/skills/archify/package-lock.json +149 -0
- package/skills/archify/package.json +39 -0
- package/skills/archify/recipes/scenarios.mjs +391 -0
- package/skills/archify/references/authoring-contract.md +247 -0
- package/skills/archify/references/brand-marks.md +65 -0
- package/skills/archify/references/delivery-contract.md +138 -0
- package/skills/archify/references/viewer-runtime.md +45 -0
- package/skills/archify/renderers/architecture/grid.mjs +62 -0
- package/skills/archify/renderers/architecture/render-architecture.mjs +1078 -0
- package/skills/archify/renderers/dataflow/README.md +104 -0
- package/skills/archify/renderers/dataflow/render-dataflow.mjs +483 -0
- package/skills/archify/renderers/lifecycle/README.md +115 -0
- package/skills/archify/renderers/lifecycle/render-lifecycle.mjs +561 -0
- package/skills/archify/renderers/sequence/README.md +114 -0
- package/skills/archify/renderers/sequence/render-sequence.mjs +464 -0
- package/skills/archify/renderers/shared/brand-marks.mjs +563 -0
- package/skills/archify/renderers/shared/cli.mjs +218 -0
- package/skills/archify/renderers/shared/desktop-readability.mjs +26 -0
- package/skills/archify/renderers/shared/diagnostics.mjs +160 -0
- package/skills/archify/renderers/shared/engineering-profiles.mjs +157 -0
- package/skills/archify/renderers/shared/generated-brand-marks.mjs +2003 -0
- package/skills/archify/renderers/shared/generated-validators.mjs +13 -0
- package/skills/archify/renderers/shared/geometry.mjs +1423 -0
- package/skills/archify/renderers/shared/i18n.mjs +595 -0
- package/skills/archify/renderers/shared/layout-report.mjs +40 -0
- package/skills/archify/renderers/shared/legend.mjs +217 -0
- package/skills/archify/renderers/shared/output-path.mjs +340 -0
- package/skills/archify/renderers/shared/repository-evidence.mjs +239 -0
- package/skills/archify/renderers/shared/repository-location.mjs +58 -0
- package/skills/archify/renderers/shared/text-fit.mjs +49 -0
- package/skills/archify/renderers/shared/utils.mjs +232 -0
- package/skills/archify/renderers/shared/validator.mjs +86 -0
- package/skills/archify/renderers/workflow/README.md +223 -0
- package/skills/archify/renderers/workflow/render-workflow.mjs +35 -0
- package/skills/archify/renderers/workflow/workflow-compiler.mjs +4400 -0
- package/skills/archify/renderers/workflow/workflow-migration-geometry.mjs +144 -0
- package/skills/archify/schemas/README.md +211 -0
- package/skills/archify/schemas/architecture.schema.json +178 -0
- package/skills/archify/schemas/common.schema.json +115 -0
- package/skills/archify/schemas/dataflow.schema.json +243 -0
- package/skills/archify/schemas/lifecycle.schema.json +266 -0
- package/skills/archify/schemas/sequence.schema.json +223 -0
- package/skills/archify/schemas/workflow.schema.json +428 -0
- package/skills/archify/scripts/check-render-output.mjs +916 -0
- package/skills/archify/scripts/check-update.mjs +1667 -0
- package/skills/archify/scripts/generate-brand-marks.mjs +141 -0
- package/skills/archify/scripts/generate-validators.mjs +66 -0
- package/skills/archify/scripts/render-examples.mjs +26 -0
- package/skills/archify/scripts/update-contract.mjs +182 -0
- package/skills/archify/skill-release.json +10 -0
- package/skills/archify/test/adaptive-reader-layout.test.mjs +86 -0
- package/skills/archify/test/animation.test.mjs +179 -0
- package/skills/archify/test/architecture-compare-recovery.test.mjs +157 -0
- package/skills/archify/test/architecture-delta-markers.test.mjs +137 -0
- package/skills/archify/test/architecture-delta.test.mjs +705 -0
- package/skills/archify/test/artifact-receipt-flush.test.mjs +126 -0
- package/skills/archify/test/authored-reachability.test.mjs +95 -0
- package/skills/archify/test/authoring-safety-contract.test.mjs +54 -0
- package/skills/archify/test/automatic-port-spread.test.mjs +376 -0
- package/skills/archify/test/base-input-compatibility.test.mjs +42 -0
- package/skills/archify/test/brand-marks.test.mjs +678 -0
- package/skills/archify/test/chapter-delta-preview.test.mjs +157 -0
- package/skills/archify/test/chapter-handoff.test.mjs +97 -0
- package/skills/archify/test/chapter-rail.test.mjs +86 -0
- package/skills/archify/test/checkout-line-endings.test.mjs +72 -0
- package/skills/archify/test/ci-scope.test.mjs +59 -0
- package/skills/archify/test/clean-skill-staging.test.mjs +440 -0
- package/skills/archify/test/cli-output-types.test.mjs +201 -0
- package/skills/archify/test/cli.test.mjs +986 -0
- package/skills/archify/test/community-proof-intake.test.mjs +125 -0
- package/skills/archify/test/cursor-onboarding.test.mjs +74 -0
- package/skills/archify/test/degraded.test.mjs +161 -0
- package/skills/archify/test/delivery-contract.test.mjs +52 -0
- package/skills/archify/test/desktop-reader-browser.test.mjs +80 -0
- package/skills/archify/test/diagram-guide.test.mjs +92 -0
- package/skills/archify/test/engineering-profile.test.mjs +209 -0
- package/skills/archify/test/export-browser.test.mjs +305 -0
- package/skills/archify/test/export-cleanup-browser.test.mjs +294 -0
- package/skills/archify/test/finder-browser.test.mjs +274 -0
- package/skills/archify/test/finder.test.mjs +104 -0
- package/skills/archify/test/fixtures/automatic-routing-node-border-clearance.workflow.json +40 -0
- package/skills/archify/test/fixtures/fail-migration-cleanup.mjs +17 -0
- package/skills/archify/test/fixtures/issue-126/custom-widths.workflow.json +18 -0
- package/skills/archify/test/fixtures/issue-126/explicit-route.workflow.json +28 -0
- package/skills/archify/test/fixtures/issue-126/explicit-viewbox.workflow.json +19 -0
- package/skills/archify/test/fixtures/issue-126/labels.workflow.json +18 -0
- package/skills/archify/test/fixtures/v1-baseline/agent-run.lifecycle.json +64 -0
- package/skills/archify/test/fixtures/v1-baseline/agent-tool-call.workflow.json +88 -0
- package/skills/archify/test/fixtures/v1-baseline/cache-miss-request.sequence.json +75 -0
- package/skills/archify/test/fixtures/v1-baseline/event-stream.dataflow.json +57 -0
- package/skills/archify/test/fixtures/v1-baseline/product-analytics.dataflow.json +70 -0
- package/skills/archify/test/fixtures/v1-baseline/production-deployment.architecture.json +71 -0
- package/skills/archify/test/fixtures/v1-baseline/web-app.architecture.json +41 -0
- package/skills/archify/test/fixtures/v1-workflow-700x400.workflow.json +22 -0
- package/skills/archify/test/fixtures/v1-workflow-explicit-coordinates.workflow.json +47 -0
- package/skills/archify/test/focus-browser.test.mjs +226 -0
- package/skills/archify/test/gallery.test.mjs +130 -0
- package/skills/archify/test/generate-validators.test.mjs +59 -0
- package/skills/archify/test/generate-viewer.test.mjs +165 -0
- package/skills/archify/test/generated-artifact-xml.test.mjs +88 -0
- package/skills/archify/test/geometry.test.mjs +739 -0
- package/skills/archify/test/golden.mjs +207 -0
- package/skills/archify/test/grid.test.mjs +47 -0
- package/skills/archify/test/guide-page.test.mjs +85 -0
- package/skills/archify/test/guide.test.mjs +81 -0
- package/skills/archify/test/guided-views-browser.test.mjs +460 -0
- package/skills/archify/test/guided-views.test.mjs +144 -0
- package/skills/archify/test/helpers/desktop-browser.mjs +48 -0
- package/skills/archify/test/helpers/offline-fonts.mjs +76 -0
- package/skills/archify/test/helpers/viewer-click.mjs +72 -0
- package/skills/archify/test/helpers/xml.mjs +37 -0
- package/skills/archify/test/i18n.test.mjs +422 -0
- package/skills/archify/test/intent-trace-browser.test.mjs +284 -0
- package/skills/archify/test/intent-trace.test.mjs +111 -0
- package/skills/archify/test/landing.test.mjs +97 -0
- package/skills/archify/test/layout-rules.test.mjs +1498 -0
- package/skills/archify/test/legend-contract.test.mjs +404 -0
- package/skills/archify/test/motion-governor-browser.test.mjs +342 -0
- package/skills/archify/test/motion-governor.test.mjs +111 -0
- package/skills/archify/test/offline-font-browser.test.mjs +210 -0
- package/skills/archify/test/offline-self-containment.test.mjs +80 -0
- package/skills/archify/test/open-artifact.test.mjs +118 -0
- package/skills/archify/test/ordinary-model-floor.test.mjs +1052 -0
- package/skills/archify/test/output-path.test.mjs +587 -0
- package/skills/archify/test/presentation.test.mjs +71 -0
- package/skills/archify/test/preset-tryon.test.mjs +113 -0
- package/skills/archify/test/preview-contract.test.mjs +40 -0
- package/skills/archify/test/preview.test.mjs +530 -0
- package/skills/archify/test/proof-aperture.test.mjs +78 -0
- package/skills/archify/test/reach-share-card.test.mjs +149 -0
- package/skills/archify/test/reader-layout-browser.test.mjs +320 -0
- package/skills/archify/test/readme-showcase.test.mjs +319 -0
- package/skills/archify/test/real-repository-proof.test.mjs +209 -0
- package/skills/archify/test/relationship-direct-explorer.test.mjs +115 -0
- package/skills/archify/test/relationship-lens.test.mjs +128 -0
- package/skills/archify/test/relationship-permalink.test.mjs +130 -0
- package/skills/archify/test/relationship-pulse.test.mjs +122 -0
- package/skills/archify/test/release-identity.test.mjs +476 -0
- package/skills/archify/test/release-package-gates.test.mjs +750 -0
- package/skills/archify/test/render-output-checks.test.mjs +588 -0
- package/skills/archify/test/renderer-diagnostic-boundary.test.mjs +107 -0
- package/skills/archify/test/repair-receipt.test.mjs +181 -0
- package/skills/archify/test/repository-evidence-replacement.test.mjs +136 -0
- package/skills/archify/test/repository-evidence.test.mjs +605 -0
- package/skills/archify/test/repository-language-metadata.test.mjs +67 -0
- package/skills/archify/test/route-journey.test.mjs +125 -0
- package/skills/archify/test/route-probe-browser.test.mjs +363 -0
- package/skills/archify/test/route-probe.test.mjs +114 -0
- package/skills/archify/test/route-share-card.test.mjs +163 -0
- package/skills/archify/test/semantic-camera.test.mjs +88 -0
- package/skills/archify/test/semantic-flow.test.mjs +90 -0
- package/skills/archify/test/semantic-legend-gateway.test.mjs +156 -0
- package/skills/archify/test/semantic-lens-browser.test.mjs +372 -0
- package/skills/archify/test/semantic-lens.test.mjs +103 -0
- package/skills/archify/test/semantic-passport.test.mjs +99 -0
- package/skills/archify/test/semantic-radar.test.mjs +735 -0
- package/skills/archify/test/semantic-zoom.test.mjs +86 -0
- package/skills/archify/test/sequence-column-fit.test.mjs +137 -0
- package/skills/archify/test/settled-flow.test.mjs +97 -0
- package/skills/archify/test/share-card-export.test.mjs +132 -0
- package/skills/archify/test/site-language-continuity.test.mjs +569 -0
- package/skills/archify/test/site-language-integration.mjs +9 -0
- package/skills/archify/test/skill-metadata.test.mjs +84 -0
- package/skills/archify/test/stable-update-manifest.test.mjs +167 -0
- package/skills/archify/test/start-page.test.mjs +312 -0
- package/skills/archify/test/story-beat-navigator.test.mjs +103 -0
- package/skills/archify/test/story-carrier.test.mjs +90 -0
- package/skills/archify/test/story-director-strip.test.mjs +91 -0
- package/skills/archify/test/story-follow-camera.test.mjs +103 -0
- package/skills/archify/test/story-horizon.test.mjs +91 -0
- package/skills/archify/test/story-moment-link.test.mjs +93 -0
- package/skills/archify/test/story-shelf.test.mjs +84 -0
- package/skills/archify/test/story-trail.test.mjs +81 -0
- package/skills/archify/test/toolbar-polish.test.mjs +57 -0
- package/skills/archify/test/update-contract.test.mjs +133 -0
- package/skills/archify/test/update-notifier.test.mjs +3317 -0
- package/skills/archify/test/v1-compatibility.test.mjs +329 -0
- package/skills/archify/test/vertical-edge.test.mjs +68 -0
- package/skills/archify/test/viewer-camera-browser.test.mjs +395 -0
- package/skills/archify/test/viewer-chrome-layout.test.mjs +1104 -0
- package/skills/archify/test/viewer-identifiers-browser.test.mjs +135 -0
- package/skills/archify/test/visual-check.test.mjs +400 -0
- package/skills/archify/test/webm-artifact.smoke.mjs +1855 -0
- package/skills/archify/test/workflow-compiler-hard-contract.test.mjs +2136 -0
- package/skills/archify/test/workflow-compiler.test.mjs +1163 -0
- package/skills/archify/test/workflow-migration.test.mjs +620 -0
- package/skills/archify/test/workflow-semantic-contract.test.mjs +131 -0
- package/skills/architect/SKILL.md +83 -0
- package/skills/architect/references/design-red-flags.md +33 -0
- package/skills/architect/references/rationale-template.md +35 -0
- package/skills/architect/references/runner-prompt.md +20 -0
- package/skills/arena/SKILL.md +13 -0
- package/skills/automate-me/SKILL.md +104 -0
- package/skills/autoresearch/SKILL.md +14 -0
- package/skills/debugging/SKILL.md +279 -0
- package/skills/dev-plan/SKILL.md +25 -18
- package/skills/dev-plan/references/execution-workflow.md +8 -61
- package/skills/dev-plan/references/plan-skeleton.md +43 -28
- package/skills/dev-plan/scripts/check_progress.sh +5 -1
- package/skills/documentation-writer/SKILL.md +204 -0
- package/skills/documentation-writer/assets/templates/ARCHITECTURE.md +49 -0
- package/skills/documentation-writer/assets/templates/CONCERNS.md +56 -0
- package/skills/documentation-writer/assets/templates/CONVENTIONS.md +52 -0
- package/skills/documentation-writer/assets/templates/FEATURES.md +43 -0
- package/skills/documentation-writer/assets/templates/INTEGRATIONS.md +48 -0
- package/skills/documentation-writer/assets/templates/STACK.md +56 -0
- package/skills/documentation-writer/assets/templates/STRUCTURE.md +44 -0
- package/skills/documentation-writer/assets/templates/TESTING.md +57 -0
- package/skills/documentation-writer/references/api-docs.md +185 -0
- package/skills/documentation-writer/references/architecture.md +134 -0
- package/skills/documentation-writer/references/codebase-map.md +113 -0
- package/skills/documentation-writer/references/inquiry-checkpoints.md +78 -0
- package/skills/documentation-writer/references/readme.md +89 -0
- package/skills/documentation-writer/references/stack-detection.md +134 -0
- package/skills/documentation-writer/references/user-manual.md +103 -0
- package/skills/documentation-writer/references/writing-style.md +220 -0
- package/skills/documentation-writer/scripts/check_doc.py +273 -0
- package/skills/documentation-writer/scripts/scan.py +712 -0
- package/skills/elintp/SKILL.md +27 -1
- package/skills/figure-it-out/SKILL.md +53 -0
- package/skills/gdpr-compliant/SKILL.md +283 -0
- package/skills/gdpr-compliant/references/Security.md +266 -0
- package/skills/gdpr-compliant/references/data-rights.md +177 -0
- package/skills/how/SKILL.md +58 -0
- package/skills/how/references/explainer-prompt.md +55 -0
- package/skills/how/references/explorer-prompt.md +52 -0
- package/skills/humanize-writing/SKILL.md +417 -0
- package/skills/humanize-writing/references/ai-tells.md +291 -0
- package/skills/kpi-dashboard-design/SKILL.md +103 -0
- package/skills/landing-page-conversion-audit/SKILL.md +122 -0
- package/skills/meeting-minutes/SKILL.md +235 -0
- package/skills/portal-app-exchange/SKILL.md +8 -17
- package/skills/portal-dev-setup/SKILL.md +137 -46
- package/skills/portal-dev-setup/puller.env.example +3 -2
- package/skills/portal-dev-setup/references/kafka.md +140 -0
- package/skills/portal-dev-setup/references/troubleshooting.md +22 -26
- package/skills/portal-dev-setup/scripts/onebox.sh +510 -56
- package/skills/portal-external-app/SKILL.md +57 -21
- package/skills/portal-external-app/references/contract-doc-template.md +1 -1
- package/skills/portal-external-app/references/integration-contract.md +4 -4
- package/skills/portal-external-app/references/public-entrypoints.md +100 -0
- package/skills/portal-external-app/references/quota-and-seats.md +12 -31
- package/skills/portal-external-app/references/registration-and-onebox.md +67 -11
- package/skills/portal-micro-app/SKILL.md +18 -28
- package/skills/portal-micro-app/references/manifest-and-acl.md +21 -3
- package/skills/portal-micro-app/references/open-api-and-extensions.md +1 -1
- package/skills/portal-micro-app/references/sdk-reference.md +1 -1
- package/skills/prd/SKILL.md +56 -38
- package/skills/prd/references/prd-skeleton.md +72 -34
- package/skills/prd/references/product-quality.md +89 -0
- package/skills/prd/references/spms-mapping.md +9 -7
- package/skills/react/SKILL.md +26 -4
- package/skills/react/references/actions.md +251 -0
- package/skills/react/references/data-fetching.md +25 -1
- package/skills/react/references/react19-migration.md +407 -0
- package/skills/reflect/SKILL.md +13 -0
- package/skills/review-code/SKILL.md +63 -0
- package/skills/review-code/agents/openai.yaml +4 -0
- package/skills/review-code/references/git-scopes.md +38 -0
- package/skills/review-code/references/report-contract.md +56 -0
- package/skills/review-code/references/review-lenses.md +33 -0
- package/skills/review-dev-plan/SKILL.md +88 -0
- package/skills/review-dev-plan/agents/openai.yaml +4 -0
- package/skills/review-dev-plan/references/report-contract.md +59 -0
- package/skills/review-dev-plan/references/review-lenses.md +82 -0
- package/skills/review-prd/SKILL.md +84 -0
- package/skills/review-prd/agents/openai.yaml +4 -0
- package/skills/review-prd/references/report-contract.md +64 -0
- package/skills/review-prd/references/review-lenses.md +82 -0
- package/skills/review-prd-dev-gaps/SKILL.md +84 -0
- package/skills/review-prd-dev-gaps/agents/openai.yaml +4 -0
- package/skills/review-prd-dev-gaps/references/report-contract.md +72 -0
- package/skills/security-audit/SKILL.md +191 -0
- package/skills/security-audit/references/language-patterns.md +221 -0
- package/skills/security-audit/references/report-format.md +194 -0
- package/skills/security-audit/references/secret-patterns.md +178 -0
- package/skills/security-audit/references/vuln-categories.md +288 -0
- package/skills/security-audit/references/vulnerable-packages.md +111 -0
- package/skills/sql-optimization/SKILL.md +296 -0
- package/skills/summarize/SKILL.md +1 -0
- package/skills/swarm/references/teams/product.md +2 -2
- package/skills/tdd/SKILL.md +42 -0
- package/skills/teach/SKILL.md +21 -0
- package/skills/test-plan/SKILL.md +12 -11
- package/skills/test-plan/references/case-taxonomy.md +3 -2
- package/skills/why/SKILL.md +158 -0
- package/skills/why/references/epistemics.md +144 -0
- package/skills/why/references/investigator-prompt.md +103 -0
- package/skills/why/references/source-playbook.md +17 -0
- package/skills/why/references/sources/code-archaeology.md +88 -0
- package/skills/why/references/sources/databricks.md +70 -0
- package/skills/why/references/sources/datadog.md +99 -0
- package/skills/why/references/sources/incident-postmortem.md +15 -0
- package/skills/why/references/sources/linear.md +48 -0
- package/skills/why/references/sources/notion.md +55 -0
- package/skills/why/references/sources/sentry.md +100 -0
- package/skills/why/references/sources/slack.md +54 -0
- package/skills/why/references/synthesizer-prompt.md +135 -0
- package/skills/xgent-app-release/SKILL.md +97 -28
- package/skills/xgent-app-release/references/build-and-package.md +8 -3
- package/skills/xgent-app-release/references/embed-csp.mjs +126 -0
- package/skills/xgent-app-release/references/frontend-archive.md +67 -0
- package/skills/xgent-app-release/references/public-delivery.md +75 -0
- package/skills/xgent-app-release/references/publish-api.md +62 -56
- package/skills/xgent-app-release/references/troubleshooting.md +18 -2
- package/skills/xgent-app-release/references/verify-preflight.mjs +50 -0
- package/skills/xgent-app-release/scripts/npm-token.mjs +8 -3
- package/skills/xgent-app-release/scripts/preflight.mjs +13 -2
- package/skills/xgent-init/SKILL.md +23 -82
- package/skills/xgent-init/references/external-app-AGENTS.template.md +149 -0
- package/skills/xgent-init/references/fill-guide.md +10 -22
- package/skills/xgent-init/scripts/check-docs.mjs +14 -86
- package/vendor/impeccable/VERSION.json +10 -16
- package/vendor/impeccable/bundle/blobs/{daa2a8ecd874f9f940dfd72aaa329318b06bb9be25515be85fea84c871b637a6 → 00c067253d37cd20a41dbe9c806d6a72cbff983ad1418b3c3e447998f577b9c2} +18 -11
- package/vendor/impeccable/bundle/blobs/{4ce929b8cafd37247ea4c148bbe39a9afe15908d1abf4a8248c5122275d16499 → 00d167d9183c8d0c6c0682055107382e6672bacf7a712de0abc8f157c2cb2210} +3 -2
- package/vendor/impeccable/bundle/blobs/{a9515f6f084d8f51c378eb3cdc6db1f12e06dba2d32fc61b7ee7950fa2586f2e → 016c082dcd2aa8f3c080a757c8ed335c6070a904bd2f39668bb9d3e6f95929c1} +1 -1
- package/vendor/impeccable/bundle/blobs/0221a193d111a9de33a785706a603e55dd974a115f549dbdf74e0f9ec72bde7d +101 -0
- package/vendor/impeccable/bundle/blobs/055c571f2960b8f929d6a8d3806a4d32dfcb9750b3ef8b60adc8930fff34b559 +101 -0
- package/vendor/impeccable/bundle/blobs/{3aa908c18db91d5f31d6bba6402eab6afc4e0b75137f841a86bd012386e0d8e2 → 0605c8729dc638a46798e90ab12486fe3ae3372a869757cfb387a08e23b3c84a} +18 -11
- package/vendor/impeccable/bundle/blobs/{ec58806f16fc7ba29bccef08f2e158cd00be077ce05d45e943a59bd26dc6dc55 → 0648e4e06d01cabc91fa7dc19256ceedeeeea213b9265e9d36b0918370e16fb2} +6 -0
- package/vendor/impeccable/bundle/blobs/06b20bd42e5682a42c5d6fbfaad4c55e66ff9b9d99a604bd31d3545af120fc2d +101 -0
- package/vendor/impeccable/bundle/blobs/{420e2fdee2b7f555d9a37ce348cf62610ddd98f96b1a0a511796ba600881588c → 08346755b13f1a3f7c6fb4e512387884b80467aaeff109fd41f5521d2a95635a} +3 -1
- package/vendor/impeccable/bundle/blobs/{8f651cb1f8cde0acc5cc511b77fddd98558a527f54974698a1650b0fd7e849a7 → 08398fa2d8e2b06b849f1c0f032e1e93b832e9294aed35c1c7f62b938d62ee80} +1 -1
- package/vendor/impeccable/bundle/blobs/087f6715114e901c2ce00b438e75b82fd48978c020a782f3dc361d39eb7198a6 +26 -0
- package/vendor/impeccable/bundle/blobs/{bc7a5128cfed46148396c08798b6b9429406fbe477e84e331a36ee2840b6b776 → 09310582ed23393b171eb99ad4a288cc422ac53c26d76311586ce54c6df77b22} +3 -1
- package/vendor/impeccable/bundle/blobs/{633903ba7cb38ed502448a66691fbf655805786a2f06be67043f12294ad08e86 → 0bc2bc36ca6e74f9f610392f1a4fb8f57b2676238ba1f9c65cd8714f5d27e8a2} +5 -0
- package/vendor/impeccable/bundle/blobs/0c064c9975f25f6eb5cf7911c2eeafd3e695d729f8c86a49a24ff2d76cb3cd94 +63 -0
- package/vendor/impeccable/bundle/blobs/{4a7d8695741e2cd354baada215b06798c050aa7a8e1ca6814f0e395e054d1ab1 → 0ccf57f758eb0d53bc32c5c77156f016e528d20fe2c2740662bf19964dfbb00b} +18 -11
- package/vendor/impeccable/bundle/blobs/0dd1e6ccf053cb80e057caf7bf2fef30fd0197a41d907a1a8d2d2d02f6978674 +63 -0
- package/vendor/impeccable/bundle/blobs/{943b0d7c9ce4f6802d8440a07b2ad3c61cbd1cc00ab63962bab7eea9ca584d20 → 0ed5986d1503399f48c11104df5e523a63be2d6558770ebfbbeb2323ca3f2ad3} +5 -5
- package/vendor/impeccable/bundle/blobs/14c9c9a7816026dd77494d06b8d1777c8d64785126b0c9ac8db5ebd996dfd6b6 +101 -0
- package/vendor/impeccable/bundle/blobs/{54cb270e8826380caefc8c40642a5d973f27df46bfdb762d822541a0c72a9877 → 14ef0ab84c10331f8af6b4124387a3f80b493175a04a3f89200719b4b5aea221} +18 -11
- package/vendor/impeccable/bundle/blobs/{6abbf5979db5cb1d7965bf5f91d589e984843069c587f4d1e63aab135bbace0f → 1547005438784032de3254cb35bff393e8187d7a459d0c20a693d9bfb0b5c1a4} +5 -0
- package/vendor/impeccable/bundle/blobs/162b814ddd1133923b9584bb95f85959e64844c94341d6bf10a81c06ab7cc02c +101 -0
- package/vendor/impeccable/bundle/blobs/16498466a97a5b19a593d5b3f245468c71784bea069d7fc8aa9690db5ac1d968 +29 -0
- package/vendor/impeccable/bundle/blobs/{b31ff29c7a1ea979c6d039f7e8d17007410697ab49536690dca109f0af3f20f8 → 16ba7fca1973c5faf53dd2ca523559129c0fe9e5f8d50b645f527afa78186cd2} +9 -0
- package/vendor/impeccable/bundle/blobs/1725ca4aa546a34770f29191b58bf8dc14957d4258fb9e7d02f7b1c59c412ea4 +63 -0
- package/vendor/impeccable/bundle/blobs/{598bfed550954471ba4d257d46b4d608cd82e4cdbf36d07bf93c5f4a563f7727 → 1898529e994bf0a31ee9ff3f87c343caea3d8a7e0986f404e129fbf89078ade0} +4 -3
- package/vendor/impeccable/bundle/blobs/{6e20041e209e8705d14aa9c4fb8cca3f0d743791de633785e5b46e2dd1ff1a6b → 19b439d7d16e139357459d3874f739ea94dbdffeb2a0e201df1620ed1e40574d} +1 -1
- package/vendor/impeccable/bundle/blobs/{04af9f2d6883fe76f9539d310d9c369052822a24886d2c330ab1e3fe5981c8a1 → 1b289c8c09de9d4442109ada91b1fa3a190e113a310cab7aa26a3633c5df0bb0} +1 -1
- package/vendor/impeccable/bundle/blobs/{eb3213b9c9f4d6b2f153e7b457f13aa131bcf633695229dad4eac39a920bdf38 → 1bb3bdce54386e96e21b5956713a80583e6b3b9d1170f3bb8dec3819fa341bc3} +5 -5
- package/vendor/impeccable/bundle/blobs/{e71d6b97680084a7ccec01844e658cbd2042aec00da0c91bec62a240ad7dcf6a → 1c11cb8ea42536c7a51e197f1aa32198960bb89262da2eaf32e0feb03c70792c} +18 -11
- package/vendor/impeccable/bundle/blobs/{f0c4d0615c7d520dfc2536c01cc7f1f3aab9b60ddc399ba01289fe62edb12370 → 1c6258015ed02c522948f8f1a3b6457395a8f7954497b87c1cd83f1791f9f819} +1 -1
- package/vendor/impeccable/bundle/blobs/{27c4b6c90ad12a186136f9ece9cb1830d4a6e36b11382ab334efc4543f8082de → 1d7145fd5bf65282b2e05cf42faf41766d68e83e2b75f460b3b62d81ada5e5d7} +18 -11
- package/vendor/impeccable/bundle/blobs/{1484a7ae206ab4f0e6262b21afe0f6307a629ed27d28fb7de69536a6800a4883 → 1e2f696760ee55fb154bdcc302c7d7b0fbb807e0308130594bd892dae14dc9ea} +5 -0
- package/vendor/impeccable/bundle/blobs/{6989120346e87907d59cc6d4708dd9dee29227ad583fbefee28935395b68ce34 → 1f6c3a1d7344f44eaa88b6f38334ad7d7b60201709589039a58cee73de486f5d} +1 -1
- package/vendor/impeccable/bundle/blobs/207d443cabe4e6fe1351f91785748957f757bf1e7aeca405433c0329dad46a15 +63 -0
- package/vendor/impeccable/bundle/blobs/{f7916d0113da7a19d4e03c113d5563d181457e44f1646ecab9191cfee5ae776d → 20e2111b3e065e8e8054f715ea3902e64b77886b30d2bddee723558f22942620} +4 -3
- package/vendor/impeccable/bundle/blobs/21e78c8a77badb3f83290cd4577ed3b24bd4dd3b57136d7da75733d4f3ee9ac0 +19 -0
- package/vendor/impeccable/bundle/blobs/{0f83648f6b425225290305bced51eacebacf9bf9bc05e21cdbad46863fbc89ce → 223f08673b267f81d7586f686afe05d7cd7529a8e11ac37d979f95c2ceaae8ce} +1 -1
- package/vendor/impeccable/bundle/blobs/2499b18f26639694344ca95682f47c65494f730cfff88aa9bd2aa83a22e5926d +101 -0
- package/vendor/impeccable/bundle/blobs/{074575671f24cf4298d06f0019f76e94f62e9a6ed205fce05b8e507341e64266 → 26cd88d10f8a14ed0ddb6e6b21e5e2c981f410eac93afa179202c354d7d1c0d5} +3 -1
- package/vendor/impeccable/bundle/blobs/{5f24c5a97b4ffcabc7c432b064cfdd04c7a2ad52f52e765be648dc0aadab8f38 → 275dd1b6e63bbd30d2713227c4f79b880a8bc4e01cc48d392068bb86939d3afa} +1 -1
- package/vendor/impeccable/bundle/blobs/{356cfa0ebf61589cf5d52691d101a69d69256062fe1d11d71d95d41a78289cbd → 28f65b42c1ce9aaea35dc37a51257d7c703bb7f6553ebe3b8ca238a1007251e0} +3 -1
- package/vendor/impeccable/bundle/blobs/{a1dbde501ffabb50f30f28f2de6755b5e91b3d4028292c5a3abd3358106eb457 → 2ac1624b1253f3b6fcf2da54d54293624176539b2e9609a49245e7e46c65ca5f} +5 -0
- package/vendor/impeccable/bundle/blobs/{3895765e9434400dfc1b30e364be002cf499c1718fb2431e65b907c14ae2cb27 → 2b9379cdb329890eebe2384fbaa23f771745f30d8cfc43e180ebd092e69bcd2f} +4 -3
- package/vendor/impeccable/bundle/blobs/{1a9180cec754f8afd354e7cb759750247e97c5bd26d3242ad4c45e315069e8c0 → 2bc5d62463cc83d6417df7b8f4a02b4135e2b1bb654aa7727890662496f86e3d} +5 -0
- package/vendor/impeccable/bundle/blobs/2ce1722bd1a9f366182d9357d629796b1448f7a0126a854df507eb044d891cab +26 -0
- package/vendor/impeccable/bundle/blobs/{e21af575fcc0f1df54ae422bea4dfa1e5211422606908919f6a86e19e0d5c20c → 3149a87bbed84bffe3558d5eaf1dc596b2e84d3c1be290fc7b1c13e481ae0803} +4 -3
- package/vendor/impeccable/bundle/blobs/3253eab02b62efa80abc74a5c85da08f3e2c6c20dff8dec38f4d236cbbfa0f3c +63 -0
- package/vendor/impeccable/bundle/blobs/{38c2e839e130008b6da814ab9c16f3fcbbedfdf721a9638eebd31a9e65341a7a → 37680abf9a8e446619fcd33862718f483d197bf2652f966adca8dc54d2909b9d} +3 -2
- package/vendor/impeccable/bundle/blobs/385a0efe833bd4afe4a257e7159590de0cf1b7fd8f7b213b732f1e537c62dab7 +21 -0
- package/vendor/impeccable/bundle/blobs/{8a1c872773996dfa1c1eb765ac7ed8e8d41f99d4f12f090b4725b694e85eaa62 → 3898bd9b56a042e860a0db4fb4dd81ac81126fa218b604702e2ac0c2287ae09b} +9 -0
- package/vendor/impeccable/bundle/blobs/3b2b60f23fa8ed9d73e721e17142c3933dc5318fa9a251742a76fee890ac81a8 +26 -0
- package/vendor/impeccable/bundle/blobs/3bd55b35c253f7841ca4e6e1a855a4b477d59aa2daf6e61e93fd685caaaff131 +63 -0
- package/vendor/impeccable/bundle/blobs/{dde1b74aeab3d90320e134f0ed0422bef99917dcab4c1749aa1720a2c94f725f → 3bed76a0d528e2d73da8acae4dc63702bdb5baca5ab11600d70e75fc9f2ba90b} +5 -0
- package/vendor/impeccable/bundle/blobs/{9b2fc50c5062073d097140cde85602bba181e325ec6b9eea1bd3da74582acb9d → 3ef9a43006bc68d83bc958fc8401e59f9beb6379048f5ced047747fdad05b895} +5 -5
- package/vendor/impeccable/bundle/blobs/{30e006fdee873bac23edaeadce0371161625e021e3cf442dff4b05bb82297ba1 → 400419aaa20f710e1a7e5ca1fce9e8bc995c4db84053f40cf778d987619e0210} +1 -1
- package/vendor/impeccable/bundle/blobs/40cd6024312a1339aa66801df533afa543e60c36e708f0f9659dd8d558f83dce +63 -0
- package/vendor/impeccable/bundle/blobs/{55e95137b3f9198c1d4dfc425366b82f1ef5f77d602682b2edf91f2ce9296289 → 412dd9a5238aee223397591fe49b3bd27b6b53d649a0e67ed8570f3e199cebb9} +1 -1
- package/vendor/impeccable/bundle/blobs/{181e814c1b9317098b2300c1b02ad0d137b6d82bd1e7e8cc64de4d00b4c50c25 → 41ef23c048ab739f48afdce26a2678d36ee40bcced34ef4b969d1d8ffafca64c} +3 -1
- package/vendor/impeccable/bundle/blobs/{edddeb3fad88adec9a055b1bf9d5c75418cbe97a13b1a7f6765e73249e482563 → 42831c3cd874346d8e7f120808d91e6ed3cda5c3eca6eefd76b126659536bc87} +5 -1
- package/vendor/impeccable/bundle/blobs/{e3c331cc21d9a216e25e08628610ea99c97e150893c3d26e5c0cecc532f26d7b → 43dbfcb7c2e54c9cca9fce66024f17c06a2f2be645547b718b75c8828a463de2} +1 -1
- package/vendor/impeccable/bundle/blobs/4435529820c3fc2eceae8b1ee35c906a34c7612fb860117e896152b45aee0770 +26 -0
- package/vendor/impeccable/bundle/blobs/44a60aedcc350c2d7d501d0f830a31a586a6511a9ff9b4e361cb328bb747339d +101 -0
- package/vendor/impeccable/bundle/blobs/{c6a0ec6c28ae55fe539f0f41a507b1638495afd285d6eb15c00245fc8cc7e7fe → 45dcea8da47cc21f447041daa942cf5d92cddde9a5a02c196cedbd159c829304} +5 -0
- package/vendor/impeccable/bundle/blobs/{015725eeca94f52bffd2132d4cb49bc3eb1ee76e6ac7378c065e629d59f56613 → 4700adf4542874f204c680ebad8317a97bf7169dbc04147082618ab59ce70c7d} +3 -2
- package/vendor/impeccable/bundle/blobs/4777bbeaff2efcd6a2a66b07aacaaf57cd32f3fab1c1da5a0513937fafb48bd1 +63 -0
- package/vendor/impeccable/bundle/blobs/{b6022a8c7200e96b0b55c2ff599a01d64123a2f5fe01557d4ca5192a6e0f0b0c → 484ea165f6d49ecc15b7a5c5623e377f2e203f4dad366df23eae99255ecf4572} +18 -11
- package/vendor/impeccable/bundle/blobs/485774d58dfd5796956024cdd9c12f05e692a3703f70b12a02ab43918885c3fd +101 -0
- package/vendor/impeccable/bundle/blobs/{3bf5d8661351831dbb2195e3b677fea1f128ca319c0589d538b66d9c9d7dddbc → 488da2e7406c6b4b5c92d7bc8cbd370cdaafc82a5f3ab5adbcffb21d1d1c4df4} +3 -1
- package/vendor/impeccable/bundle/blobs/{6f1d52863482fa0f6fff877f0cc8f6dd0b4230d28050b0d267e082776e61a332 → 492a8030d11e2f4908d934bd817bd6d16a3164056c74a7172f60e2cec5ff3055} +5 -0
- package/vendor/impeccable/bundle/blobs/{51e2f8963377f142ebdc9864e0ecc5aa1e5b7d344ed72d0e9d748d2d3d8c71b3 → 4972f28d96897c8dec891f0de7a0d9d51f36030ff780ed9d960241c75413da2b} +5 -5
- package/vendor/impeccable/bundle/blobs/4a6f0386bff78bb42efa285df2919e75209ec336672cf5fff91ba66ffb352eaa +63 -0
- package/vendor/impeccable/bundle/blobs/{12c1afcd15e86d840b89a78c6d17ce6d1b5609bac713d59271c6ab5932975dac → 4e3cc4d0e229ca96dd2e28fb2da0478a7f0bb64e447d07a652c425d8fbc57573} +3 -1
- package/vendor/impeccable/bundle/blobs/4ea8e05d0f88d24f182ad554da84317f90b9730e29fad262143ddf423a1f8645 +26 -0
- package/vendor/impeccable/bundle/blobs/{402656ca6df17bad1cd55ede0a966a7bb5b187cf4f89c517851f9efe7f14c0fd → 50d4b14134fc06daeac0aab6960800f171ef85250d3bcce6f246cf81e824463f} +3 -2
- package/vendor/impeccable/bundle/blobs/{43852ca5917de66704f73f9019d15482bb1efd92f2eefaeeeae23610b4c0c6a0 → 50f3b5714ca3e2c1bcf070003733407935ee050609d3123c2c065489fbc9950b} +5 -0
- package/vendor/impeccable/bundle/blobs/{f597742c8e5562263d3bce725cb55e920d8c2c60223f41a632d523ca30cdd12b → 51032bb883137007127d30189b44b516d0e6140271bf5881da7eca76f8bd282c} +3 -1
- package/vendor/impeccable/bundle/blobs/{13bd27aa8573e6d45d8bbf9f3fc991e4719d28a7e8f709009a4a5a49a227fff3 → 511ecde67e6bd976c9bdfa8d92f0dba359621344f8135c2a5ce5a66b385a4cb5} +5 -0
- package/vendor/impeccable/bundle/blobs/514a27fc9830455828d337cfc2fa95fa86be8e5464501bbabb9aca09a66b40ad +63 -0
- package/vendor/impeccable/bundle/blobs/{78ac93689928a580c2f90242cbaa96dda3fa4e55ff1f1d00e412b2a62ad85232 → 529cb2ace696ec11a86f4b010c9b137acf282072e483932181be1871df2aa690} +18 -11
- package/vendor/impeccable/bundle/blobs/53628d05b7eff0d4f48b95e48fad40266452fe1b42f398bd88dd9328353e0dd8 +101 -0
- package/vendor/impeccable/bundle/blobs/{00023f9e7f2e27604fde978b56510a8f837b76eeadd93221d11adcf4c98f1a10 → 53de37260c4f1c37e467557e9b038faa0f285b056bb2b95f435e0a7d1b69b0de} +3 -2
- package/vendor/impeccable/bundle/blobs/{0dbbef70048cc3ae44e6710ff09992ce14ad049ce8ed519b0ff8a1ddb4633f4e → 5415103ca4422ea1422e23bfe9f9bae181f5d6d6407442f7fba31856ada58d12} +3 -1
- package/vendor/impeccable/bundle/blobs/{af34b8af89edb10e228c0482754423f48238fce816eceb8703b8781c130da54e → 56285b28b0ffe471761c2ad062d3f933ed2346ca1b617f1323a30fed99a1a852} +1 -1
- package/vendor/impeccable/bundle/blobs/{12fefe52d80d25014e5f7e2f9b278275a675653d3d99ae8f0ecf344cdb9abbe6 → 56cc1bba51e6805de370160ce4a9399fef3740571204f13c3c751725870305e5} +3 -1
- package/vendor/impeccable/bundle/blobs/571f5c3bcf01c9b1bd02ab853a54db3f88375c4b8f39dd67d53e983a33e08bb7 +26 -0
- package/vendor/impeccable/bundle/blobs/{5ce02fc8f353d7f659535fb80e453963dc047a07c4396adf098fe010b479e7c0 → 5855d7595b0212bb8d521e0265378d437df8d2343d5f8a30408c3edde0a14d96} +3 -2
- package/vendor/impeccable/bundle/blobs/{2b21f0142ee88a11dbe2593362f5a910718a61062e3a64e7ff936ef1e74fd394 → 5a07d4769a741cd00fdf8c95b943ef072eb12cd0c9e2c2d2622d5fe0a4fa3894} +5 -5
- package/vendor/impeccable/bundle/blobs/{3dd45e1c32db3d3163a96ef198954905807b25cb9a1d206a7d77986f21d27652 → 5a9626fb8c1e9051155c68626fe7c765a55be325113245d44b4991fc3d3b014e} +5 -0
- package/vendor/impeccable/bundle/blobs/{d07e3131cdf3e392e59eaa3e2f59fcb0799d9494df4adfee12a21067fb8365ce → 5e354ae11913d5846faa3681a9ed98b68d1881fbbb1ecebf0769e3010bb0a426} +5 -0
- package/vendor/impeccable/bundle/blobs/{ad389ab12800738b0e5c97618278b7a824654b25990166ec9f389caa5c3386ca → 5e6eea05456108b8766cf354dda998e90b6a5ecca7f15d807e28659ee78c306f} +5 -0
- package/vendor/impeccable/bundle/blobs/{85b9c2d051de94ee9129c58be111946f73035ca62bbd2a3936f4fa48768fe5cf → 634257e766b1fecacd25e27dbfc7e21ef3b440b01e94e92b8b17711018d8a661} +18 -11
- package/vendor/impeccable/bundle/blobs/6428239079f98fd6500e9fd032dd289b52052da7a4364ff357b0a5341b6444db +63 -0
- package/vendor/impeccable/bundle/blobs/{f424dafa190533cd32c0bc3e4131d0c472d58e16bb191eab14f0ae73c2cea917 → 647bab2f5cf6279d58d808f4df05d1ac63c1c86c6df9d13406ae776458653995} +5 -0
- package/vendor/impeccable/bundle/blobs/{8e7bb79db7331b93f99de314dd5599de8587ec0b6c25e26cf1efab25c0777fcb → 6528f2aebe4a425b724de293c8f6dc8a42ca80c7f1ed3039b8b9a813b004956a} +4 -3
- package/vendor/impeccable/bundle/blobs/{89eb0206a0d9308c6c6a7cc01bc4fd8812d2bd34bb9a831988c5d86fac2c60de → 6575a657642ccdab42d0743ceaee06621c77811b4dc4f21c616e28aabf5e28ef} +1 -1
- package/vendor/impeccable/bundle/blobs/66d60356172ccafdd334fca9bf33243c8409216a733dcde0d66a24fd70ee8132 +1 -0
- package/vendor/impeccable/bundle/blobs/{759bab1d8adc071fdde2efcf6e2157aa4026f3c2b4f3c342a8070bf328011616 → 69cad1ae7355825ef320ac9638a1c9675ba9fda01c9a0fa12244cc4a1973e550} +2 -2
- package/vendor/impeccable/bundle/blobs/6d21d8e2729effaecc5b6239dadbcffb1ec8d51e8bdadeb47a39827266a61c5e +26 -0
- package/vendor/impeccable/bundle/blobs/{ff882c59c9aced92338aa194cc99a521ea2f083fd22eef3b1c1225e06bc72f66 → 6d9258a49c0b0583ec255eeb1092554298d5eea676fb9391f5a5a1377c3fd770} +4 -3
- package/vendor/impeccable/bundle/blobs/{68bc52a604731ca024610051fc763f4d78a53eb2a45d35b40a8c0383043baa63 → 6f4dfe968f47302ea64dca0e02f8c6d81497d93e4f436d09236e305456cbcc02} +4 -3
- package/vendor/impeccable/bundle/blobs/{f97a6891c80bbbc728edefcf4b638d1a823b38f3cb16ecd0fbb717734cc5c0c8 → 6f7f1639a933ed88e116aa8ab0cfb951096930d14f413349ae22ed04e28d2358} +5 -5
- package/vendor/impeccable/bundle/blobs/713a942482531ef8ef103adf48d9decd7327697450e444289aa0479458e4ee1f +101 -0
- package/vendor/impeccable/bundle/blobs/{6d47503587ad6da17ac529f44a96123bbb96a9c91a7a9350b7690fc467561a5d → 7212014fb2477d1d54aedaa79cdb861071a58afb2ae6d4dc5a495fa4810c6f8a} +5 -5
- package/vendor/impeccable/bundle/blobs/{21bc91f6e07691e82b7bba99f9e1d956f9d7a6810dcdc18d231fe73b53a13b23 → 73f8bdec7d9a97476fd7d7e0b4114b5c3e9c7b84c22cbb876cd18c3e64a9b8c5} +5 -0
- package/vendor/impeccable/bundle/blobs/{a8547c8565979894f0c07f92a96ac2a38e681a31fbf155ffeefc7566e185d9c2 → 754e9f0aa2101245a83cb17d8cc5bc49cf87997b29a83dedbfff283dbd0a8c4f} +5 -0
- package/vendor/impeccable/bundle/blobs/{84df879a0332a56ece4bff1922ecf4f50635951b3c67e67eb46f31bc5d30fa9a → 765d3e890cd7f58ca94b6834181052166e9c4f9ef06278dc241da4d64da2d595} +3 -1
- package/vendor/impeccable/bundle/blobs/778cedc9199b407d3e23fa1041e38d548b00b1101a33517aa2a2eb299f9d0edc +26 -0
- package/vendor/impeccable/bundle/blobs/{ef0b8eca20e1321cf7216fd0bda41ce54b0cca197cfd1b075eb51f3436f59059 → 77ae09542fbc10aa4e8b119d6fd19359908a47c13c30068d6a150bfdb50e3e10} +1 -1
- package/vendor/impeccable/bundle/blobs/{15b4837dccb27d7382e2f134ef050b244c4488b1906e0308f288112183cc09ac → 796bbd8231413fcbf22eb7e6f40c402842aa4b10b1894545a7b05d9201ca7c4a} +18 -11
- package/vendor/impeccable/bundle/blobs/{5e3616ac45568f239fda3a4764f7d3bf3473b8b5eefd91e402dd9208a4b851d1 → 7af7fcc09c6b14f26ba78734460a2f9dddd2dcd2e2fa0c47c682f539e2f4cad5} +1 -1
- package/vendor/impeccable/bundle/blobs/7b1a4cc5ce94837c8309962a204c47361927e442b7a1c3cc472f90276eca8b8c +63 -0
- package/vendor/impeccable/bundle/blobs/7c417abe75f0ce4341b6f5acbadcafbbeabcc32cdb13c930aecc5cabda9f9242 +26 -0
- package/vendor/impeccable/bundle/blobs/7e36403ed58e0e82c0e42bf2fbea1c066b8e63276978647a6f1e44bf1edb23fb +63 -0
- package/vendor/impeccable/bundle/blobs/80191b3580a996a2045105976f728e0f46de22c412fc7640741add9767b2d8f4 +101 -0
- package/vendor/impeccable/bundle/blobs/814610dee1081abc4213476ff0c4ae3e8f457909550d250ea0f89953687c85c6 +63 -0
- package/vendor/impeccable/bundle/blobs/8157b5a376e46980117f1ad6d08b24ee7be966f5de64b041bd213d486ea8c077 +26 -0
- package/vendor/impeccable/bundle/blobs/{bad9250670c18d89722563fd747bae4c4bec64d1db0ee09d12d4fea04fe271b8 → 8373668da1aa37c39888809ed9e834a7edfcb884af12f74934c270d004fba626} +3 -2
- package/vendor/impeccable/bundle/blobs/{46250caaec1bd69619a5b28a73cb3c845b319d029b0af9228aaa7001e7e0104a → 83d614fec61078175383012a088f49a9c0bb08b9ebd2b651c77cb94a323474ab} +5 -0
- package/vendor/impeccable/bundle/blobs/{a5371492582dbeb69b24a1a2947b256a2da8a4d55917af709bbab6e452d9d23c → 8449781319a33d4a10a7fab9bfa4812241c303b95f33703b300b097dba51089a} +18 -11
- package/vendor/impeccable/bundle/blobs/{12d2a8043ae8668ad29c32bfa92995849fc49cb016e6ebd4f7e59539d96e7b03 → 857c4a0db5859f1fa91152db684528299dfc552525f50f416d1625a270e12671} +5 -5
- package/vendor/impeccable/bundle/blobs/{bead87679caad7c537dd7c04c3bb78be56ac7eec279ad6aad7322f48ff2df2d9 → 871a8e4d749b807c693ebabd83defbd6c76e1bd2a0f53a28a460cb1308ba8634} +6 -0
- package/vendor/impeccable/bundle/blobs/{cf3ae16765e7b5bc333ef76e24ab37f080e3d62e931c4986158e3cbcda6b9f7a → 881a58aadf300bcc8f4947243e82192ae8dd7f14822a70fabc84fc74a5f9199c} +5 -5
- package/vendor/impeccable/bundle/blobs/{896da399bafcffb0623bdd81bb18709d6eab25c9d5917e739c8798f31ff0d076 → 88549c11ea55d5dfc9f551db9a9c270d7362a9d42aa83db7747c9a7f020e1255} +5 -5
- package/vendor/impeccable/bundle/blobs/9287af96048c58ed0e72493cf7eb90c3957d4ba8b19e70ecf5d1f54264e9e597 +101 -0
- package/vendor/impeccable/bundle/blobs/92b7ab84c828fa888f4882c417c3d7f8fde2e5445b40f96f4fc8181a3e058afc +101 -0
- package/vendor/impeccable/bundle/blobs/{e2f98c4136d0ab70ec32fcdef58edb20ed8ec4f6cc067e0dae952b22cfd6a471 → 93c53421c3376f695fe9dad5c82102f74bb5dd15e4ced81bb4ad50a71e5f1027} +1 -1
- package/vendor/impeccable/bundle/blobs/{a8f4056a85706c0c8f80ec48e9b437e198464100aa42786f9252051f4b931c19 → 95f0c7935a6d0375e09ae67ae0066c0ce7b583bf1d445d268cc4d2f4f900b1bb} +3 -1
- package/vendor/impeccable/bundle/blobs/98384b78af087855dbb526ec1e870a676ecd87f57237643dfea815317dad8a07 +26 -0
- package/vendor/impeccable/bundle/blobs/{948326fab89f0d5c07355a06b9b9c30d08bba2e3a673a0151ebbf5a39dff7b9a → 99e4a6facdb07986aa721bda8a4e35466b30091f32e38d7a71d9fcbe3591f65a} +5 -0
- package/vendor/impeccable/bundle/blobs/{44a91b3f0810f43829d8584e912812444d15614e4b57ca8070aee7ab899f7c1e → 9a4aa0d4903eabee987fdc92053466ff7f6eb0604797aee0efe67d67999b3eae} +5 -5
- package/vendor/impeccable/bundle/blobs/{9b85b85443aec09918befb83208c72f475457db8261a8fc5e83ef7eec474c869 → 9d45e5f6c082de6df4a2cfff11deb969715c49b5d9891d100dd1b2252c5bdcdd} +18 -11
- package/vendor/impeccable/bundle/blobs/9db3c0857f4c2c8877b1fe24a47a229fee0de76ec9057bd8ee95f0677fb58d74 +26 -0
- package/vendor/impeccable/bundle/blobs/{5738bcf9ac21dbb2113fd0c5cc7e4bbfc8174778916ab5843644332b3dbbc36e → 9e82126a2e08aa365f9c430d383cf6e1921e04d1e0a1b5777716ff97828cfad8} +5 -5
- package/vendor/impeccable/bundle/blobs/{b3782f2d9215eb0f5df4fe3c0e0bec07cef011e1836d4d0e45ef02bc48d3adde → 9f2993499adadfdef4065a9a7fb69c75f888d892d4ee6bdbe62619bc772f495c} +1 -1
- package/vendor/impeccable/bundle/blobs/{1c804990fd8ac23a4b19c42972b5ae591d17f620d0006f0f32927c8db5d5bf50 → a1af8b8a97665a3692eb39872c235796684a387a0b83785872f81aebd9dab046} +18 -11
- package/vendor/impeccable/bundle/blobs/{824b994782a1fe68a244bbdff7218a2e0854655d90cc0fb5ade7ef9259ba6305 → a27399e3056c77a28ddd310fb5b4dee6e8e440dfa1737f7e38815097bec4c3a9} +5 -5
- package/vendor/impeccable/bundle/blobs/a569989e6a3d6453b4c45e9474e162a15cc54e11732ac6baa7990a6e7cccc0b5 +21 -0
- package/vendor/impeccable/bundle/blobs/a75b892696469e3ccd2af33c9eef793e635efc3c5ac45336b0c1373d90c856b0 +63 -0
- package/vendor/impeccable/bundle/blobs/{3ba92d4aa840ec1b6c2fda224339489d6dd65eedf43bb45836af02584d1faa88 → a7e8e60df83c6f47590467094ab2a13f1fe28ce45959a4e69dd89575a4208646} +5 -0
- package/vendor/impeccable/bundle/blobs/{e49d2b0110c55751da3c965b4ae4d294aa676c961a09f8efbbbf22c1ec7c4fa0 → a892e337eee548d53252abb72ba5aa85cbcf556ffd8dc74e05534652c010d574} +1 -1
- package/vendor/impeccable/bundle/blobs/{8f0867db7c6f6a54348a0d951f198a1bd852cd12505d821e6e1ac6bd42e7fcea → a93397adc906e245c69b42d4b25931fc92dc75baa60ca9a3749ba23b52ba01a0} +3 -2
- package/vendor/impeccable/bundle/blobs/{424b2e75cb204e2dca7db4a40dfe5a81f78dc0fc58ac4790e8b7bbf2f330bba4 → a9e63b2df611805e845e0bb1cca1a0c27e83640a78185c9fd9de90d53a539cdc} +5 -0
- package/vendor/impeccable/bundle/blobs/ab80e6a971baa66342548a846ce3fb69824ef9ef4e0f26c6f02685f225d00bbd +26 -0
- package/vendor/impeccable/bundle/blobs/af35b5fb6e45acb6483a5fbb864c3eaa9c57bacc43857f48183c836041c670ae +101 -0
- package/vendor/impeccable/bundle/blobs/af5842c4844b692b817319d5375611d4211dc7f459fb12da358baefc3a6cd373 +63 -0
- package/vendor/impeccable/bundle/blobs/b2732083f8f1d1924d38f84f179491430aebda8e0fd1f1d0785e3f883bcf3a40 +63 -0
- package/vendor/impeccable/bundle/blobs/b3b1d5a4c80ce279d88896601deee9ccfeacdae0d0cd433600985a5f7ac93c72 +26 -0
- package/vendor/impeccable/bundle/blobs/{fd5579dbae1fc2495b7dddde48df9673acebd27953bbb56d506d3b470375c9e9 → b4be5499f404a926cdd2c55d8fb7409ba74a56b3d0c7d865594cc0254d3e499a} +5 -0
- package/vendor/impeccable/bundle/blobs/{2fd096823e4a93f19edd421a69e10f90ebb630a014c673d1802a927c6f47b7d2 → b626e0677288a1660e9ce80c4f0454b527010239681f6b8dcc0f36b4a4d6bcb2} +3 -1
- package/vendor/impeccable/bundle/blobs/{33b6b32c0c3c0c4a2b22bd2275176b1c88a006dd94592a854f629911a0b21cdc → b719be9c5d4ced5d649ec6c0c90630196e7ee513d0da482aef13c705fdc23ab6} +3 -1
- package/vendor/impeccable/bundle/blobs/{df0eb9e9a484fc7b8a0f3e50573a7d7f81e0f17598ac30d9f3fe5a6c6aa5fefa → b8938fea3399aa639b4c873248035c219658069eef7be62672254e2f72c056f4} +5 -0
- package/vendor/impeccable/bundle/blobs/{ee0a15fcfc244b76b896a2650a17b35e444d4311166b69a3cfa32d11f8f44851 → ba0d96d88ab40fad1cb522e60b11affc568cfe9d340110ac5e71f834ac3d0f4a} +12 -0
- package/vendor/impeccable/bundle/blobs/{426c7f5437dd3c23a4cfaef6964dc7f1e038480f92238499b85f3ce7e4472bf4 → bc48bca63dff44e1f0205bafb64e16b2d6fb294ace405dbc9ae06a3bc6107154} +3 -1
- package/vendor/impeccable/bundle/blobs/{c3ad2a5fa570876af6645c48d246c664859e19f253d2dc9e0ead34e785d5b40a → bdd676c8f29295addfdbceaab285bb1184ec3ef7ff60501908b97ea8269580c4} +3 -2
- package/vendor/impeccable/bundle/blobs/{38d4d404030c6ea935d3223db1eeb22c45d04a94f41f98e02a43cdc5af7ff2e3 → c06effcdc60859157208d3b7ee23d56160f2386f6a2dfa2514ba82fe21828ae0} +1 -1
- package/vendor/impeccable/bundle/blobs/c100b67a4ae248ad564182b181acb1ee2fe64c00049aeef62d4e7a3213726f37 +26 -0
- package/vendor/impeccable/bundle/blobs/c2dfc965319745c403b99e00e9e97de15f1300ea721bd3f708f0ac47aadc537b +101 -0
- package/vendor/impeccable/bundle/blobs/{e9da72a13eb3411d6bbb316953022f62abc4434f505e5345907b216eda9f793b → c6b3bc811eb23d90ff9add1e0e5b40999a0bbbab364f5e6d08061f36d30429a5} +1 -1
- package/vendor/impeccable/bundle/blobs/{82a5e564563333aabb7ab4d2f8c26c785dfe2c2416ac6e9f30cba31440676194 → cdf8a88b9865599c58fe285041d2d46eea7d9bd2d4075d82de358191742b20ad} +5 -5
- package/vendor/impeccable/bundle/blobs/cf3d3a296890ac1ee363fee930f252e20d0935cd575367ad017b3cdc2dc5a50d +26 -0
- package/vendor/impeccable/bundle/blobs/{a1e4febca3455787d7c753fe177a150dccde6e742957de400b0307247802d200 → d2e16da9bb6b33c931378d1bf57dcfff5dc426a71434bc352cec6aece715a66f} +18 -11
- package/vendor/impeccable/bundle/blobs/{c36f6aac0ddf52eabbb21df09fd3310540dca6b09e7a4017d18e94c06c469405 → d4a9e18a879bbb92aa0a56e16b45481090dffa4470efdecf52fb0da8c900be5d} +5 -5
- package/vendor/impeccable/bundle/blobs/{869fa5390b84bfd00d072f5280a9002ea3e3936edf5985241be641a87f03ff7b → d57c3e0453f601ad688911fca4471057c7b46b561fcd42a1e12a581101c5186c} +1 -1
- package/vendor/impeccable/bundle/blobs/{910461b1c1f8216155165955c860a884a1e2f557cb38f21ef512ea90ef6503c2 → d5f83912eda93d42f93522bb74aeff0ea04d9c559577ec52fcccc130dd03166d} +2 -1
- package/vendor/impeccable/bundle/blobs/{9deb14303b421b5a9232755b5b9f317716f33e1e211557a98a00a9c409775c4d → d6489d7f3e607ce3f8514a7a7d3a0eb5e8f1ac55859eaf7d5c31b53de8fab9f3} +18 -11
- package/vendor/impeccable/bundle/blobs/{37080ea5e5b381adb7eae7a427231b0ad0dee0f0916fc1d6b673b7101a5b425b → d6a51f4df6d76f16b95ab81268a689f4c5936d505c5bb25c80ad7423f927bbc6} +5 -0
- package/vendor/impeccable/bundle/blobs/{61c72fdde649bf7f2e0668e48f9d222666c81672100b5f2265940e7aa13c8767 → d7b99a57652cb8741d57a15ba7e06de4707fac02a97e5e001179da007272f2b8} +3 -1
- package/vendor/impeccable/bundle/blobs/{57402ef6fd9d0745f8c60d778f77da83a8b16edf751fa1013598a0f6e5f0f541 → d9963fd73bf6129fe0c8ec25c5148f50617ec85683b8eeae7e411c9a42e2db33} +2 -1
- package/vendor/impeccable/bundle/blobs/{581409ace104a81744b7d08f5398a2c207cf76d9e50751f807b4382eaf66454f → da3b2399814db89d67959268c646601957d3e8c641b09a5bdbce84217c1c2cfe} +5 -5
- package/vendor/impeccable/bundle/blobs/{249f0437a52ab3eb4bc4d4a9f38e085334c0fbb7795e88dd419e32b5f58c924d → dc3aeccbea3e997704ce8b170f56b0eaf343a14ad4fedb22a6dbee0f205aa37f} +5 -0
- package/vendor/impeccable/bundle/blobs/{433a79eee3ce4c99e79a7aebe3a6d8ec9ff70216772e0c2ff9dce3a01d672963 → dcc1572e739cdabcbf084f276b447cb686aa18459896e6d8fe29f4d7d807bd1d} +5 -5
- package/vendor/impeccable/bundle/blobs/{2d7086992c07df1fc734cdf9c420c4bd8ad409ab7909e07c8b486c303d420732 → dd66761b291c7dbca6e9efd22023e38f8c53ad310f3a6cdab5dc7ecd7ae8f31f} +3 -2
- package/vendor/impeccable/bundle/blobs/dd953992625c240fd1842aaf533a865aabd09487cbc16fa01fe8bb4b4365b843 +101 -0
- package/vendor/impeccable/bundle/blobs/{df4f14df1daaad1606b682995f649da19a85debf264092447b3b7ddb8b4bc1ce → de70398719140e4951aa111e9625de5ed916ad1ef0877e4ab6f6b4a7e3cc5f8f} +18 -11
- package/vendor/impeccable/bundle/blobs/{8b23562ccc3e41609ce6e67fdad6cc57b6c961964c329eff6975a1b4c5471c0d → deaaa533475b6a9f7dd52e01a1f1997aa3af146694334981650f8c846f939559} +3 -1
- package/vendor/impeccable/bundle/blobs/{041535231366047a0beeb5daa64ea9c23d5a566580f7dafdb8c0623cb4a7e76b → e07176229b8a330970ca3c9c5e3dd68c2aaeb726285e30f471e91053d7f8e798} +1 -1
- package/vendor/impeccable/bundle/blobs/{115e1c3f1b58b5129910ed925dbf65d61d5ac4c2036618847555ce40098aae97 → e1075ea5b34ac9c60f7c5bda97356eeaf1b683b599592d324fd3704f0852ad7a} +18 -11
- package/vendor/impeccable/bundle/blobs/e3af212117e0c552b0976687200fe8f95846a01725a482e8e4dbfd75e40dbfa6 +26 -0
- package/vendor/impeccable/bundle/blobs/e3c2031978cd670d7150ff3140f12cf8b6882ef638ea9a9985caffa9a80c8713 +63 -0
- package/vendor/impeccable/bundle/blobs/e6e18e7c23cfe82961d90b9714cb466c20bd83f222ac63e9339b6b2c304e1787 +26 -0
- package/vendor/impeccable/bundle/blobs/e7af7ad18b307a3171fcf49421df2a557f93289d27af2430f10ee74e2a2fc2d3 +101 -0
- package/vendor/impeccable/bundle/blobs/{b161198874e4e77599bc31691e78790be14bacc2eb03be168c8a7543ece9fbe1 → e85da3098368807689009184eca2bb09a91bdb6cb1be3e7a61f91f1ae31cd69b} +18 -11
- package/vendor/impeccable/bundle/blobs/{6bdbc3f745ceee15e10b050f109ce42bbeb53ab3c3b5239257de1d596654dee7 → e8720f3aa41135211cc58cc299a39eeda86c75651f4279cc1ede8db10dcc0584} +4 -0
- package/vendor/impeccable/bundle/blobs/e9861052457cc1ddc42c687bb8c33a877834ced94fed74af2a4f00f6bae50795 +101 -0
- package/vendor/impeccable/bundle/blobs/{d1faa55eb956033d28e9f27fd2bc2a9f62c5da0f0abd6534819cc0a04db54d9d → eb3e86876c20f1cfc443dcbe48d8f41fea6e815082a304610be432272429a06d} +5 -0
- package/vendor/impeccable/bundle/blobs/{0e1819750e5448311a051d35ab6d0be0830418f78ca872ef10469fafebb27d4f → eba472f6190541c28e2e647ed019f43564ba5b92d629963f34c899175b25b31e} +5 -5
- package/vendor/impeccable/bundle/blobs/{f173ff03f989f162cee815feb2d8ca72daf00019c48694d89db16d52171a79c0 → eba609589a5a051643a7bae60b7cceef7cfe44d0f8bd9229e61473ac75910fb3} +1 -1
- package/vendor/impeccable/bundle/blobs/{8eb466778023fccea641654d662157b123eeb360f4a0875bf6fbe3d93680851c → ec81b008d2641402e3352b09b1a90562ef7a24598d7c5aded475038cce63aca0} +1 -1
- package/vendor/impeccable/bundle/blobs/{4acfb9e38f6e09f7484e36fc19704d69eab159436ee9329168be74c493042f22 → ec86b8633fbeb14ef2272f220fdd98089972b03b99da83e49eeb6a8b73b00617} +3 -1
- package/vendor/impeccable/bundle/blobs/{d15a2914d93e07a4956acd5c8b8211af17380f527acd10d8acf4f4920badaaaf → ef2e97cd690baf6fe5c895dd2d8844aff9fcc4a8bc04973402678a739c81d712} +4 -3
- package/vendor/impeccable/bundle/blobs/f0fbe529f63961a027bada2e6a3cbe86f4af0dba4b8e1010da22b007f9002f66 +63 -0
- package/vendor/impeccable/bundle/blobs/{c02feba25a961a72647ad317aba2d65baeeed53c4cdcc072f25bb37f4d867d43 → f254d59ff973e907cacdc2c34b8b4de075437d292de31dac30e10a9d7035086c} +3 -2
- package/vendor/impeccable/bundle/blobs/f5efb1b1dd4cd18e1d4789d7c8e0188f4342b25fffd53af6088ba8b67774fb99 +26 -0
- package/vendor/impeccable/bundle/blobs/{7af0a8665057c83b39bca2c8c7427ef2343734f70d4128c377113a6726a1e3b1 → f68a3d68ac92606e91aa8878e003d9432ddd09aa97ca01c6b3ce46c937dca316} +524 -4
- package/vendor/impeccable/bundle/blobs/{381ff89aa76e82a0d98456ec31cb117555ae045539fba47c45056daa50f611ca → f84b653ede0804d21e6210c6d21fd5352023914fedceae6ff14e20f1e93b06e7} +5 -5
- package/vendor/impeccable/bundle/blobs/{5b30a06646ad5e453d6b5ed1b8d446f69fca3cd13b8aeda28f0d0b2cbe8f62d1 → fb32d99751c5fc24493ff4460be1767bd5236dc820fd337775067c0620d1a7a6} +3 -1
- package/vendor/impeccable/bundle/blobs/{733504a70ed20bcfca9c26c353f7b88f8491d24f3f2fe124dc1e73b28eefa296 → fb5fec24adafa48df43eece6907c1c986a4772cbdc64142c881f00c3b312e9ca} +18 -11
- package/vendor/impeccable/bundle/blobs/fc149b5bc7a8d0cc9b9bf86e5013793f3857c1c302212e9aaf101cb95427386b +101 -0
- package/vendor/impeccable/bundle/manifest.json +413 -298
- package/skills/grafton-view-dev/SKILL.md +0 -195
- package/skills/grafton-view-dev/references/bindings.md +0 -120
- package/skills/grafton-view-dev/references/components.md +0 -168
- package/skills/grafton-view-dev/references/recipes.md +0 -208
- package/skills/grafton-view-dev/references/schema.md +0 -86
- package/skills/grafton-view-dev/references/stores.md +0 -310
- package/skills/portal-backend-app/SKILL.md +0 -56
- package/skills/portal-backend-app/references/gate-and-tokens.md +0 -93
- package/skills/portal-backend-app/references/wiring-and-conventions.md +0 -71
- package/skills/xgent-init/references/external-app-CLAUDE.template.md +0 -249
- package/vendor/impeccable/bundle/blobs/800cf1c0392b24de7c0a1c6ea6778ecb433dec71c49a150bce96a98477527b2f +0 -1
|
@@ -0,0 +1,1498 @@
|
|
|
1
|
+
// Per-rule coverage for the renderers' layout validators. The golden suite's
|
|
2
|
+
// negative cases mostly trip ajv SCHEMA rules; this file targets the hand-
|
|
3
|
+
// written LAYOUT rules (the `problems.push(...)` checks) — the layer that has
|
|
4
|
+
// regressed before — by mutating a valid example into exactly one violation
|
|
5
|
+
// and asserting the renderer exits non-zero with the expected message.
|
|
6
|
+
//
|
|
7
|
+
// It also locks the error-message CONTRACT: representative messages must carry
|
|
8
|
+
// both the numeric threshold and a remediation hint, since the consumer is an
|
|
9
|
+
// LLM that fixes the JSON from the message alone.
|
|
10
|
+
//
|
|
11
|
+
// node --test test/*.test.mjs
|
|
12
|
+
|
|
13
|
+
import { test } from 'node:test';
|
|
14
|
+
import assert from 'node:assert/strict';
|
|
15
|
+
import { execFileSync } from 'node:child_process';
|
|
16
|
+
import fs from 'node:fs';
|
|
17
|
+
import os from 'node:os';
|
|
18
|
+
import path from 'node:path';
|
|
19
|
+
import { fileURLToPath } from 'node:url';
|
|
20
|
+
|
|
21
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
22
|
+
const skillRoot = path.resolve(__dirname, '..');
|
|
23
|
+
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-rules-'));
|
|
24
|
+
|
|
25
|
+
const EXAMPLES = {
|
|
26
|
+
workflow: 'agent-tool-call.workflow.json',
|
|
27
|
+
sequence: 'cache-miss-request.sequence.json',
|
|
28
|
+
dataflow: 'product-analytics.dataflow.json',
|
|
29
|
+
lifecycle: 'agent-run.lifecycle.json',
|
|
30
|
+
architecture: 'web-app.architecture.json',
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
function load(mode) {
|
|
34
|
+
return JSON.parse(fs.readFileSync(path.join(skillRoot, 'examples', EXAMPLES[mode]), 'utf8'));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Returns { code, stderr }. Never throws on non-zero exit.
|
|
38
|
+
function render(mode, doc) {
|
|
39
|
+
const input = path.join(tmp, `${mode}-${Math.abs(hash(JSON.stringify(doc)))}.json`);
|
|
40
|
+
const outPath = path.join(tmp, `${mode}-${Math.abs(hash(JSON.stringify(doc)))}.html`);
|
|
41
|
+
fs.writeFileSync(input, JSON.stringify(doc));
|
|
42
|
+
try {
|
|
43
|
+
execFileSync('node', [
|
|
44
|
+
path.join(skillRoot, `renderers/${mode}/render-${mode}.mjs`),
|
|
45
|
+
input,
|
|
46
|
+
outPath,
|
|
47
|
+
], { stdio: ['ignore', 'ignore', 'pipe'] });
|
|
48
|
+
return { code: 0, stderr: '', outPath };
|
|
49
|
+
} catch (err) {
|
|
50
|
+
return { code: err.status ?? 1, stderr: String(err.stderr || ''), outPath };
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function validateCli(mode, doc, quality = 'showcase') {
|
|
55
|
+
const input = path.join(tmp, `${mode}-cli-${Math.abs(hash(JSON.stringify(doc)))}.json`);
|
|
56
|
+
fs.writeFileSync(input, JSON.stringify(doc));
|
|
57
|
+
try {
|
|
58
|
+
const stdout = execFileSync('node', [
|
|
59
|
+
path.join(skillRoot, 'bin', 'archify.mjs'),
|
|
60
|
+
'validate',
|
|
61
|
+
mode,
|
|
62
|
+
input,
|
|
63
|
+
'--quality',
|
|
64
|
+
quality,
|
|
65
|
+
'--json',
|
|
66
|
+
], { encoding: 'utf8' });
|
|
67
|
+
return { code: 0, result: JSON.parse(stdout) };
|
|
68
|
+
} catch (err) {
|
|
69
|
+
return {
|
|
70
|
+
code: err.status ?? 1,
|
|
71
|
+
result: JSON.parse(String(err.stdout || '{}')),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function hash(s) {
|
|
77
|
+
let h = 0;
|
|
78
|
+
for (let i = 0; i < s.length; i += 1) h = (h * 31 + s.charCodeAt(i)) | 0;
|
|
79
|
+
return h;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function workflowEdgePoints(html, id) {
|
|
83
|
+
const escapedId = id.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
84
|
+
const pointsAttribute = html.match(
|
|
85
|
+
new RegExp(`data-edge-id="${escapedId}" data-composition-points="([^"]+)"`),
|
|
86
|
+
)?.[1];
|
|
87
|
+
assert.ok(pointsAttribute, `expected rendered workflow edge points for ${id}`);
|
|
88
|
+
return pointsAttribute.split(';').map((point) => point.split(',').map(Number));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function workflowNodeRect(html, id) {
|
|
92
|
+
const escapedId = id.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
93
|
+
const match = html.match(new RegExp(
|
|
94
|
+
`<g id="node-${escapedId}"[^>]*>[\\s\\S]*?<rect x="([^"]+)" y="([^"]+)" width="([^"]+)" height="([^"]+)"`,
|
|
95
|
+
));
|
|
96
|
+
assert.ok(match, `expected rendered workflow node rect for ${id}`);
|
|
97
|
+
const [, x, y, width, height] = match.map(Number);
|
|
98
|
+
return { x, y, width, height };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function boundaryFrameRect(html, index) {
|
|
102
|
+
const match = html.match(new RegExp(
|
|
103
|
+
`<rect data-graph-role="structural-frame"[^>]*data-composition-frame-id="${index}"[^>]*x="([^"]+)" y="([^"]+)" width="([^"]+)" height="([^"]+)"`,
|
|
104
|
+
));
|
|
105
|
+
assert.ok(match, `expected boundary frame ${index}`);
|
|
106
|
+
const [, x, y, width, height] = match.map(Number);
|
|
107
|
+
return { x, y, width, height };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function boundaryTitleMasks(html) {
|
|
111
|
+
return [...html.matchAll(
|
|
112
|
+
/<g data-graph-role="structural-frame-label"[^>]*data-composition-frame-id="(\d+)"[^>]*>[\s\S]*?<rect data-graph-role="structural-frame-label-mask" x="([^"]+)" y="([^"]+)" width="([^"]+)" height="([^"]+)"/g,
|
|
113
|
+
)].map((match) => ({
|
|
114
|
+
index: Number(match[1]),
|
|
115
|
+
x: Number(match[2]),
|
|
116
|
+
y: Number(match[3]),
|
|
117
|
+
width: Number(match[4]),
|
|
118
|
+
height: Number(match[5]),
|
|
119
|
+
}));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function rectContainsRect(outer, inner) {
|
|
123
|
+
return outer.x <= inner.x
|
|
124
|
+
&& outer.y <= inner.y
|
|
125
|
+
&& outer.x + outer.width >= inner.x + inner.width
|
|
126
|
+
&& outer.y + outer.height >= inner.y + inner.height;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function rectanglesOverlap(left, right) {
|
|
130
|
+
return left.x < right.x + right.width
|
|
131
|
+
&& left.x + left.width > right.x
|
|
132
|
+
&& left.y < right.y + right.height
|
|
133
|
+
&& left.y + left.height > right.y;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function workflowEdgeLabelPoint(html, id) {
|
|
137
|
+
const escapedId = id.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
138
|
+
const match = html.match(new RegExp(
|
|
139
|
+
`<g data-detail="context"[^>]*data-edge-id="${escapedId}"[^>]*>[\\s\\S]*?<text x="([^"]+)" y="([^"]+)"`,
|
|
140
|
+
));
|
|
141
|
+
assert.ok(match, `expected rendered workflow edge label for ${id}`);
|
|
142
|
+
return match.slice(1).map(Number);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function axisOverlapLength(a1, a2, b1, b2) {
|
|
146
|
+
return Math.max(0, Math.min(Math.max(a1, a2), Math.max(b1, b2))
|
|
147
|
+
- Math.max(Math.min(a1, a2), Math.min(b1, b2)));
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function workflowNodeBorderOverlap(points, rect) {
|
|
151
|
+
const hits = [];
|
|
152
|
+
for (let index = 0; index < points.length - 1; index += 1) {
|
|
153
|
+
const [start, end] = [points[index], points[index + 1]];
|
|
154
|
+
if (start[0] === end[0] && (start[0] === rect.x || start[0] === rect.x + rect.width)) {
|
|
155
|
+
const length = axisOverlapLength(start[1], end[1], rect.y, rect.y + rect.height);
|
|
156
|
+
if (length > 0) hits.push({ segment: index, length, start, end });
|
|
157
|
+
}
|
|
158
|
+
if (start[1] === end[1] && (start[1] === rect.y || start[1] === rect.y + rect.height)) {
|
|
159
|
+
const length = axisOverlapLength(start[0], end[0], rect.x, rect.x + rect.width);
|
|
160
|
+
if (length > 0) hits.push({ segment: index, length, start, end });
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return hits;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function assertRelationshipsAvoidAllNodeBorders(html, relationships, diagramNodes) {
|
|
167
|
+
for (const [edgeIndex, edge] of relationships.entries()) {
|
|
168
|
+
const edgeId = edge.id || `edge-${edgeIndex}`;
|
|
169
|
+
const points = workflowEdgePoints(html, edgeId);
|
|
170
|
+
for (const node of diagramNodes) {
|
|
171
|
+
const hits = workflowNodeBorderOverlap(points, workflowNodeRect(html, node.id));
|
|
172
|
+
assert.deepEqual(
|
|
173
|
+
hits,
|
|
174
|
+
[],
|
|
175
|
+
`workflow edge ${edgeId} runs along node ${node.id} border: ${JSON.stringify(hits)}`,
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function assertWorkflowEdgesAvoidAllNodeBorders(html, doc) {
|
|
182
|
+
assertRelationshipsAvoidAllNodeBorders(html, doc.edges, doc.nodes);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// [name, mode, mutate(doc), expectedSubstrings[]] — every mutation introduces
|
|
186
|
+
// exactly one layout violation. Each expected substring must appear in stderr.
|
|
187
|
+
const CASES = [
|
|
188
|
+
// ---- workflow layout rules ----
|
|
189
|
+
['workflow: unknown lane', 'workflow', (d) => { d.nodes[0].lane = 'ghost'; }, ['unknown lane "ghost"']],
|
|
190
|
+
['workflow: node label wider than box', 'workflow',
|
|
191
|
+
(d) => { d.nodes[0].label = 'An Extremely Long Node Label That Overflows'; }, ['wider than node', 'shorten the label']],
|
|
192
|
+
['workflow: node sublabel wider than its legible minimum', 'workflow',
|
|
193
|
+
(d) => { d.nodes[0].sublabel = 'This supporting sentence is much too long for one workflow node'; }, ['Sublabel', 'legible', 'increase node.width']],
|
|
194
|
+
['workflow: node tag wider than its legible minimum', 'workflow',
|
|
195
|
+
(d) => { d.nodes[0].tag = 'This tag is far too long to sit inside one workflow node box'; },
|
|
196
|
+
['Tag', 'legible', 'increase node.width']],
|
|
197
|
+
['workflow: viewBox width below schema min', 'workflow',
|
|
198
|
+
(d) => { d.meta.viewBox = [699, 900]; }, ['700']],
|
|
199
|
+
['workflow: nodes too close in a lane', 'workflow',
|
|
200
|
+
(d) => { d.nodes.push({ ...d.nodes[0], id: 'dupe', col: d.nodes[0].col }); }, ['less than 8px apart']],
|
|
201
|
+
['workflow: empty group', 'workflow',
|
|
202
|
+
(d) => { d.groups = [{ id: 'empty', label: 'Empty group', lane: 'ui', fromCol: 3, toCol: 4 }]; }, ['does not contain any nodes']],
|
|
203
|
+
['workflow: mainPath missing edge', 'workflow',
|
|
204
|
+
(d) => { d.mainPath = ['user', 'planner']; }, ['mainPath step "user" -> "planner" has no matching edge']],
|
|
205
|
+
['workflow: mainPath moves backward', 'workflow',
|
|
206
|
+
(d) => { d.mainPath = ['external', 'trace']; }, ['moves backward from col']],
|
|
207
|
+
['workflow: phase ranges overlap', 'workflow',
|
|
208
|
+
(d) => { d.phases[2].fromCol = d.phases[1].toCol; }, ['overlaps phase', 'start at col 4 or later']],
|
|
209
|
+
|
|
210
|
+
// ---- sequence layout rules ----
|
|
211
|
+
['sequence: message references unknown participant', 'sequence',
|
|
212
|
+
(d) => { d.messages[0].from = 'ghost'; }, ['unknown source "ghost"']],
|
|
213
|
+
['sequence: message y outside timeline', 'sequence',
|
|
214
|
+
(d) => { d.messages[0].y = 9000; }, ['outside the readable timeline', 'keep y between']],
|
|
215
|
+
['sequence: segment to <= from', 'sequence',
|
|
216
|
+
(d) => { d.segments = [{ from: 400, to: 300, label: 'bad' }]; }, ['invalid y range', 'greater than']],
|
|
217
|
+
['sequence: segment label exceeds segment frame available width', 'sequence',
|
|
218
|
+
(d) => { d.segments[0].label = 'Long Segment Label '.repeat(10); },
|
|
219
|
+
['exceeds the segment frame\'s available width', 'increase meta.viewBox[0]']],
|
|
220
|
+
['sequence: participant sublabel wider than its legible minimum', 'sequence',
|
|
221
|
+
(d) => { d.participants[0].sublabel = 'This supporting sentence is far too long for one sequence participant'; },
|
|
222
|
+
['Sublabel', 'legible', 'shorten the sublabel']],
|
|
223
|
+
|
|
224
|
+
// ---- dataflow layout rules ----
|
|
225
|
+
['dataflow: flow missing label', 'dataflow',
|
|
226
|
+
(d) => { delete d.flows[0].label; }, ['label']],
|
|
227
|
+
['dataflow: flow references unknown node', 'dataflow',
|
|
228
|
+
(d) => { d.flows[0].to = 'ghost'; }, ['unknown target "ghost"']],
|
|
229
|
+
['dataflow: explicit via keeps every route segment orthogonal', 'dataflow',
|
|
230
|
+
(d) => {
|
|
231
|
+
d.flows[0].via = [[195, 140], [195, 260]];
|
|
232
|
+
}, ['diagonal segment', 'align via[0]']],
|
|
233
|
+
['dataflow: node sublabel wider than its legible minimum', 'dataflow',
|
|
234
|
+
(d) => { d.nodes[0].sublabel = 'This supporting sentence is far too long for one data-flow node box'; },
|
|
235
|
+
['Sublabel', 'legible', 'increase node.width']],
|
|
236
|
+
['dataflow: node tag wider than its legible minimum', 'dataflow',
|
|
237
|
+
(d) => { d.nodes[0].tag = 'This tag is far too long to sit inside one data-flow node box'; },
|
|
238
|
+
['Tag', 'legible', 'increase node.width']],
|
|
239
|
+
|
|
240
|
+
// ---- lifecycle layout rules ----
|
|
241
|
+
['lifecycle: missing reserved main lane', 'lifecycle',
|
|
242
|
+
(d) => {
|
|
243
|
+
d.lanes = d.lanes.map((l) => (l.id === 'main' ? { ...l, id: 'primary' } : l));
|
|
244
|
+
d.states = d.states.map((s) => (s.lane === 'main' ? { ...s, lane: 'primary' } : s));
|
|
245
|
+
}, ['"main"', 'reserved']],
|
|
246
|
+
['lifecycle: cross-lane state overlap', 'lifecycle',
|
|
247
|
+
(d) => {
|
|
248
|
+
const approval = d.states.find((s) => s.id === 'approval');
|
|
249
|
+
const failed = d.states.find((s) => s.id === 'failed');
|
|
250
|
+
delete failed.yOffset;
|
|
251
|
+
failed.col = approval.col;
|
|
252
|
+
}, ['less than 10px apart']],
|
|
253
|
+
['lifecycle: viewBox height below schema min', 'lifecycle',
|
|
254
|
+
(d) => { d.meta.viewBox = [980, 565]; }, ['566']],
|
|
255
|
+
['lifecycle: state sublabel wider than its legible minimum', 'lifecycle',
|
|
256
|
+
(d) => { d.states[0].sublabel = 'This supporting sentence is far too long for one lifecycle state box'; },
|
|
257
|
+
['Sublabel', 'legible', 'increase state.width']],
|
|
258
|
+
['lifecycle: state tag wider than its legible minimum', 'lifecycle',
|
|
259
|
+
(d) => { d.states[0].tag = 'This tag is far too long to sit inside one lifecycle state box'; },
|
|
260
|
+
['Tag', 'legible', 'increase state.width']],
|
|
261
|
+
|
|
262
|
+
// ---- architecture layout rules ----
|
|
263
|
+
['architecture: components overlap', 'architecture',
|
|
264
|
+
(d) => { d.components[1].pos = [...d.components[0].pos]; }, ['less than 8px apart']],
|
|
265
|
+
['architecture: connection references unknown component', 'architecture',
|
|
266
|
+
(d) => { d.connections[0].to = 'ghost'; }, ['unknown target "ghost"']],
|
|
267
|
+
['architecture: boundary wraps unknown component', 'architecture',
|
|
268
|
+
(d) => { d.boundaries[0].wraps.push('ghost'); }, ['wraps unknown component "ghost"']],
|
|
269
|
+
['architecture: boundary title must fit inside its own frame', 'architecture',
|
|
270
|
+
(d) => {
|
|
271
|
+
d.meta.quality_profile = 'standard';
|
|
272
|
+
d.meta.viewBox = [500, 400];
|
|
273
|
+
d.components = [{
|
|
274
|
+
id: 'narrow',
|
|
275
|
+
type: 'backend',
|
|
276
|
+
label: 'Narrow',
|
|
277
|
+
pos: [330, 100],
|
|
278
|
+
size: [128, 60],
|
|
279
|
+
}];
|
|
280
|
+
delete d.meta.views;
|
|
281
|
+
d.connections = [];
|
|
282
|
+
d.cards = [];
|
|
283
|
+
d.boundaries = [{
|
|
284
|
+
kind: 'region',
|
|
285
|
+
label: 'A boundary title that cannot fit its narrow authored frame',
|
|
286
|
+
wraps: ['narrow'],
|
|
287
|
+
pad: 0,
|
|
288
|
+
}];
|
|
289
|
+
}, ['Boundary label', 'fit', 'shorten']],
|
|
290
|
+
['architecture: boundary title must stay inside the authored viewBox', 'architecture',
|
|
291
|
+
(d) => {
|
|
292
|
+
d.meta.quality_profile = 'standard';
|
|
293
|
+
d.meta.viewBox = [500, 400];
|
|
294
|
+
d.components = [{
|
|
295
|
+
id: 'near-top',
|
|
296
|
+
type: 'backend',
|
|
297
|
+
label: 'Near top',
|
|
298
|
+
pos: [120, 8],
|
|
299
|
+
size: [128, 60],
|
|
300
|
+
}];
|
|
301
|
+
delete d.meta.views;
|
|
302
|
+
d.connections = [];
|
|
303
|
+
d.cards = [];
|
|
304
|
+
d.boundaries = [{
|
|
305
|
+
kind: 'region',
|
|
306
|
+
label: 'Viewport title',
|
|
307
|
+
wraps: ['near-top'],
|
|
308
|
+
pad: 0,
|
|
309
|
+
}];
|
|
310
|
+
}, ['Boundary label', 'outside the viewBox', 'move wrapped components']],
|
|
311
|
+
['architecture: label wider than component', 'architecture',
|
|
312
|
+
(d) => { d.components[0].label = 'An Extremely Long Component Label Overflow'; }, ['wider than component', 'shorten the label']],
|
|
313
|
+
['architecture: component sublabel wider than its legible minimum', 'architecture',
|
|
314
|
+
(d) => { d.components[0].sublabel = 'This supporting sentence is far too long for one architecture component'; },
|
|
315
|
+
['Sublabel', 'legible', 'widen size']],
|
|
316
|
+
['architecture: component tag wider than its legible minimum', 'architecture',
|
|
317
|
+
(d) => { d.components[0].tag = 'This tag is far too long to sit inside one architecture component box'; },
|
|
318
|
+
['Tag', 'legible', 'widen size']],
|
|
319
|
+
['architecture: component overlap suggests fix', 'architecture',
|
|
320
|
+
(d) => { d.components[1].pos = [...d.components[0].pos]; }, ['Suggested fix', 'move "']],
|
|
321
|
+
|
|
322
|
+
];
|
|
323
|
+
|
|
324
|
+
for (const [name, mode, mutate, expected] of CASES) {
|
|
325
|
+
test(name, () => {
|
|
326
|
+
const doc = load(mode);
|
|
327
|
+
mutate(doc);
|
|
328
|
+
const { code, stderr } = render(mode, doc);
|
|
329
|
+
assert.notEqual(code, 0, `expected non-zero exit; stderr:\n${stderr}`);
|
|
330
|
+
assert.doesNotMatch(stderr, /TypeError|is not a function|Cannot read/, `crashed instead of reporting:\n${stderr}`);
|
|
331
|
+
for (const sub of expected) {
|
|
332
|
+
assert.ok(stderr.includes(sub), `expected "${sub}" in stderr:\n${stderr}`);
|
|
333
|
+
}
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
test('architecture: ordinary boundaries may express orthogonal overlapping memberships', () => {
|
|
338
|
+
const d = load('architecture');
|
|
339
|
+
d.boundaries[1].wraps.push('auth');
|
|
340
|
+
const { code, stderr, outPath } = render('architecture', d);
|
|
341
|
+
assert.equal(code, 0, stderr);
|
|
342
|
+
const html = fs.readFileSync(outPath, 'utf8');
|
|
343
|
+
assert.equal(rectanglesOverlap(boundaryFrameRect(html, 0), boundaryFrameRect(html, 1)), true);
|
|
344
|
+
const masks = boundaryTitleMasks(html);
|
|
345
|
+
assert.equal(masks.length, 2);
|
|
346
|
+
assert.equal(rectanglesOverlap(masks[0], masks[1]), false);
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
test('architecture: profile-less v1 keeps rendering when a boundary title cannot meet strict composition', () => {
|
|
350
|
+
const d = load('architecture');
|
|
351
|
+
delete d.meta.quality_profile;
|
|
352
|
+
d.meta.viewBox = [500, 400];
|
|
353
|
+
d.components = [{
|
|
354
|
+
id: 'narrow',
|
|
355
|
+
type: 'backend',
|
|
356
|
+
label: 'Narrow',
|
|
357
|
+
pos: [330, 100],
|
|
358
|
+
size: [128, 60],
|
|
359
|
+
}];
|
|
360
|
+
delete d.meta.views;
|
|
361
|
+
d.connections = [];
|
|
362
|
+
d.cards = [];
|
|
363
|
+
d.boundaries = [{
|
|
364
|
+
kind: 'region',
|
|
365
|
+
label: 'A boundary title that cannot fit its narrow authored frame',
|
|
366
|
+
wraps: ['narrow'],
|
|
367
|
+
pad: 0,
|
|
368
|
+
}];
|
|
369
|
+
|
|
370
|
+
const { code, stderr } = render('architecture', d);
|
|
371
|
+
assert.equal(code, 0, stderr);
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
test('architecture: profile-less v1 keeps legacy boundary geometry at the top edge', () => {
|
|
375
|
+
const d = load('architecture');
|
|
376
|
+
delete d.meta.quality_profile;
|
|
377
|
+
d.meta.viewBox = [500, 400];
|
|
378
|
+
d.components = [{
|
|
379
|
+
id: 'near-top',
|
|
380
|
+
type: 'backend',
|
|
381
|
+
label: 'Near top',
|
|
382
|
+
pos: [120, 22],
|
|
383
|
+
size: [128, 60],
|
|
384
|
+
}];
|
|
385
|
+
delete d.meta.views;
|
|
386
|
+
d.connections = [];
|
|
387
|
+
d.cards = [];
|
|
388
|
+
d.boundaries = [{
|
|
389
|
+
kind: 'region',
|
|
390
|
+
label: 'Legacy top edge',
|
|
391
|
+
wraps: ['near-top'],
|
|
392
|
+
pad: 0,
|
|
393
|
+
}];
|
|
394
|
+
|
|
395
|
+
const { code, stderr } = render('architecture', d);
|
|
396
|
+
assert.equal(code, 0, stderr);
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
test('architecture: deployment ownership requires nested membership geometry to agree', () => {
|
|
400
|
+
const d = JSON.parse(fs.readFileSync(
|
|
401
|
+
path.join(skillRoot, 'examples/production-deployment.architecture.json'),
|
|
402
|
+
'utf8',
|
|
403
|
+
));
|
|
404
|
+
d.boundaries.find((boundary) => boundary.label === 'private application network').pad = 260;
|
|
405
|
+
const { code, stderr } = render('architecture', d);
|
|
406
|
+
assert.notEqual(code, 0);
|
|
407
|
+
assert.match(stderr, /final frames partially overlap/);
|
|
408
|
+
assert.match(stderr, /adjust wraps, pad, or component positions/);
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
test('architecture: boundary labels reserve readable space above wrapped components', () => {
|
|
412
|
+
const d = load('architecture');
|
|
413
|
+
d.boundaries = [{
|
|
414
|
+
kind: 'security-group',
|
|
415
|
+
label: 'Tool effects and permissions',
|
|
416
|
+
wraps: ['lb', 'api'],
|
|
417
|
+
pad: 14,
|
|
418
|
+
}];
|
|
419
|
+
|
|
420
|
+
const { code, stderr, outPath } = render('architecture', d);
|
|
421
|
+
assert.equal(code, 0, stderr);
|
|
422
|
+
const html = fs.readFileSync(outPath, 'utf8');
|
|
423
|
+
const label = html.match(
|
|
424
|
+
/<text data-boundary-label="" x="[^"]+" y="([^"]+)" class="t-security" font-size="[^"]+" font-weight="600">Tool effects and permissions<\/text>/,
|
|
425
|
+
);
|
|
426
|
+
assert.ok(label, 'expected the security boundary label');
|
|
427
|
+
const labelBaseline = Number(label[1]);
|
|
428
|
+
const firstWrappedNodeY = Math.min(
|
|
429
|
+
workflowNodeRect(html, 'lb').y,
|
|
430
|
+
workflowNodeRect(html, 'api').y,
|
|
431
|
+
);
|
|
432
|
+
assert.ok(
|
|
433
|
+
labelBaseline <= firstWrappedNodeY - 4,
|
|
434
|
+
`expected the boundary label baseline (${labelBaseline}) to clear the first wrapped node (${firstWrappedNodeY})`,
|
|
435
|
+
);
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
test('architecture: auto viewBox keeps expanded boundary titles readable at desktop scale', () => {
|
|
439
|
+
const d = load('architecture');
|
|
440
|
+
delete d.meta.viewBox;
|
|
441
|
+
d.meta.quality_profile = 'showcase';
|
|
442
|
+
delete d.meta.views;
|
|
443
|
+
d.components = [{
|
|
444
|
+
id: 'node',
|
|
445
|
+
type: 'backend',
|
|
446
|
+
label: 'Current node',
|
|
447
|
+
pos: [800, 100],
|
|
448
|
+
size: [120, 60],
|
|
449
|
+
}];
|
|
450
|
+
d.connections = [];
|
|
451
|
+
d.cards = [];
|
|
452
|
+
d.boundaries = [{
|
|
453
|
+
kind: 'region',
|
|
454
|
+
label: 'Disaster recovery boundary Disaster recovery boundary Disaster recovery boundary',
|
|
455
|
+
wraps: ['node'],
|
|
456
|
+
pad: 30,
|
|
457
|
+
}];
|
|
458
|
+
|
|
459
|
+
const { code, stderr, outPath } = render('architecture', d);
|
|
460
|
+
assert.equal(code, 0, stderr);
|
|
461
|
+
const html = fs.readFileSync(outPath, 'utf8');
|
|
462
|
+
const viewBoxWidth = Number(html.match(/\bviewBox="0 0 ([\d.]+) [\d.]+"/)?.[1]);
|
|
463
|
+
const sourceFontPx = Number(html.match(/<text data-boundary-label[^>]*font-size="([\d.]+)"/)?.[1]);
|
|
464
|
+
const projectedFontPx = sourceFontPx * Math.min(1, 930 / viewBoxWidth);
|
|
465
|
+
assert.ok(
|
|
466
|
+
projectedFontPx >= 6,
|
|
467
|
+
`expected the final ${viewBoxWidth}px viewBox to project its ${sourceFontPx}px boundary title at 6px or larger, got ${projectedFontPx}px`,
|
|
468
|
+
);
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
test('architecture: boundary labels and their masks paint above relationship routes', () => {
|
|
472
|
+
const d = load('architecture');
|
|
473
|
+
const { code, stderr, outPath } = render('architecture', d);
|
|
474
|
+
assert.equal(code, 0, stderr);
|
|
475
|
+
const html = fs.readFileSync(outPath, 'utf8');
|
|
476
|
+
const lastRoute = html.lastIndexOf('data-composition-points=');
|
|
477
|
+
const firstBoundaryLabel = html.indexOf('data-graph-role="structural-frame-label"');
|
|
478
|
+
assert.ok(lastRoute >= 0, 'expected at least one rendered relationship route');
|
|
479
|
+
assert.ok(firstBoundaryLabel >= 0, 'expected a foreground boundary label group');
|
|
480
|
+
assert.ok(
|
|
481
|
+
firstBoundaryLabel > lastRoute,
|
|
482
|
+
'boundary labels must paint after relationship routes so routes cannot cross the title text',
|
|
483
|
+
);
|
|
484
|
+
assert.match(
|
|
485
|
+
html.slice(firstBoundaryLabel, firstBoundaryLabel + 500),
|
|
486
|
+
/data-graph-role="structural-frame-label-mask"/,
|
|
487
|
+
'expected an opaque mask behind the foreground boundary title',
|
|
488
|
+
);
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
test('architecture: nested boundary title rails stay inside frames and avoid labels and nodes', () => {
|
|
492
|
+
const d = JSON.parse(fs.readFileSync(
|
|
493
|
+
path.join(skillRoot, 'examples/production-deployment.architecture.json'),
|
|
494
|
+
'utf8',
|
|
495
|
+
));
|
|
496
|
+
const { code, stderr, outPath } = render('architecture', d);
|
|
497
|
+
assert.equal(code, 0, stderr);
|
|
498
|
+
const html = fs.readFileSync(outPath, 'utf8');
|
|
499
|
+
const masks = boundaryTitleMasks(html);
|
|
500
|
+
assert.equal(masks.length, d.boundaries.length);
|
|
501
|
+
|
|
502
|
+
for (const mask of masks) {
|
|
503
|
+
assert.ok(
|
|
504
|
+
rectContainsRect(boundaryFrameRect(html, mask.index), mask),
|
|
505
|
+
`boundary title mask ${mask.index} must stay inside its frame: ${JSON.stringify(mask)}`,
|
|
506
|
+
);
|
|
507
|
+
for (const component of d.components) {
|
|
508
|
+
assert.equal(
|
|
509
|
+
rectanglesOverlap(mask, workflowNodeRect(html, component.id)),
|
|
510
|
+
false,
|
|
511
|
+
`boundary title mask ${mask.index} overlaps component ${component.id}`,
|
|
512
|
+
);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
for (let left = 0; left < masks.length; left += 1) {
|
|
517
|
+
for (let right = left + 1; right < masks.length; right += 1) {
|
|
518
|
+
assert.equal(
|
|
519
|
+
rectanglesOverlap(masks[left], masks[right]),
|
|
520
|
+
false,
|
|
521
|
+
`boundary title masks ${left} and ${right} overlap`,
|
|
522
|
+
);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
test('architecture: boundary title masks cannot obscure connection labels', () => {
|
|
528
|
+
const d = {
|
|
529
|
+
schema_version: 1,
|
|
530
|
+
diagram_type: 'architecture',
|
|
531
|
+
meta: { title: 'Boundary and connection labels', quality_profile: 'standard', viewBox: [700, 500] },
|
|
532
|
+
components: [
|
|
533
|
+
{ id: 'scoped', type: 'backend', label: 'Scoped', pos: [250, 100], size: [120, 60] },
|
|
534
|
+
{ id: 'source', type: 'external', label: 'Source', pos: [50, 300], size: [100, 50] },
|
|
535
|
+
{ id: 'target', type: 'external', label: 'Target', pos: [500, 300], size: [100, 50] },
|
|
536
|
+
],
|
|
537
|
+
boundaries: [{ kind: 'region', label: 'Runtime scope', wraps: ['scoped'] }],
|
|
538
|
+
connections: [{
|
|
539
|
+
from: 'source',
|
|
540
|
+
to: 'target',
|
|
541
|
+
label: 'Route label',
|
|
542
|
+
route: 'straight',
|
|
543
|
+
labelAt: [270, 88],
|
|
544
|
+
}],
|
|
545
|
+
};
|
|
546
|
+
|
|
547
|
+
const { code, stderr } = render('architecture', d);
|
|
548
|
+
assert.notEqual(code, 0);
|
|
549
|
+
assert.match(stderr, /Boundary label "Runtime scope" overlaps connection label "Route label"/);
|
|
550
|
+
assert.match(stderr, /labelAt\/labelDx\/labelDy\/labelSegment/);
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
// ---- sublabel/tag shrink-to-fit: the render half of the same rule ----
|
|
554
|
+
// Validation only rejects text that cannot fit even at its legible minimum.
|
|
555
|
+
// Everything between "fits at the preferred size" and that floor must shrink,
|
|
556
|
+
// not overflow — otherwise the common case still paints over its neighbours.
|
|
557
|
+
const SHRINK_CASES = [
|
|
558
|
+
// [mode, mutate(doc), preferredFontSize, selector for the sublabel <text>]
|
|
559
|
+
['architecture', (d) => { d.components[0].sublabel = 'Browser and mobile apps'; }, 9],
|
|
560
|
+
['sequence', (d) => {
|
|
561
|
+
d.meta.column_fit = 'fixed';
|
|
562
|
+
d.participants[0].sublabel = 'long browser session';
|
|
563
|
+
}, 7],
|
|
564
|
+
['dataflow', (d) => { d.nodes[0].sublabel = 'browser SDK and mobile SDK'; }, 7],
|
|
565
|
+
['lifecycle', (d) => { d.states[0].sublabel = 'request accepted and queued'; }, 7],
|
|
566
|
+
];
|
|
567
|
+
|
|
568
|
+
for (const [mode, mutate, preferred] of SHRINK_CASES) {
|
|
569
|
+
test(`${mode}: an over-long sublabel shrinks to fit instead of overflowing`, () => {
|
|
570
|
+
const d = load(mode);
|
|
571
|
+
mutate(d);
|
|
572
|
+
const { code, stderr, outPath } = render(mode, d);
|
|
573
|
+
assert.equal(code, 0, stderr);
|
|
574
|
+
const html = fs.readFileSync(outPath, 'utf8');
|
|
575
|
+
const sub = html.match(/<text data-detail="context"[^>]*font-size="([\d.]+)"[^>]*>/);
|
|
576
|
+
assert.ok(sub, 'expected a sublabel <text> in the rendered SVG');
|
|
577
|
+
const fontSize = Number(sub[1]);
|
|
578
|
+
assert.ok(
|
|
579
|
+
fontSize < preferred,
|
|
580
|
+
`expected the sublabel to shrink below the ${preferred}px preferred size, got ${fontSize}`,
|
|
581
|
+
);
|
|
582
|
+
assert.ok(fontSize >= 6, `expected the sublabel to stay legible, got ${fontSize}`);
|
|
583
|
+
});
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
const TAG_SHRINK_CASES = [
|
|
587
|
+
// [mode, collection, tag, preferredFontSize]
|
|
588
|
+
['architecture', 'components', 'owner: platform operations team', 7],
|
|
589
|
+
['dataflow', 'nodes', 'owner: analytics platform', 7],
|
|
590
|
+
['lifecycle', 'states', 'owner: platform operations pod', 7],
|
|
591
|
+
['workflow', 'nodes', 'owner: runtime execution squad A', 7],
|
|
592
|
+
];
|
|
593
|
+
|
|
594
|
+
for (const [mode, collection, tag, preferred] of TAG_SHRINK_CASES) {
|
|
595
|
+
test(`${mode}: an over-long tag shrinks to fit instead of overflowing`, () => {
|
|
596
|
+
const d = load(mode);
|
|
597
|
+
d[collection][0].tag = tag;
|
|
598
|
+
const { code, stderr, outPath } = render(mode, d);
|
|
599
|
+
assert.equal(code, 0, stderr);
|
|
600
|
+
const html = fs.readFileSync(outPath, 'utf8');
|
|
601
|
+
const escapedTag = tag.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
602
|
+
const match = html.match(new RegExp(
|
|
603
|
+
`<text data-detail="fine"[^>]*font-size="([\\d.]+)"[^>]*>${escapedTag}</text>`,
|
|
604
|
+
));
|
|
605
|
+
assert.ok(match, `expected a tag <text> for "${tag}" in the rendered SVG`);
|
|
606
|
+
const fontSize = Number(match[1]);
|
|
607
|
+
assert.ok(
|
|
608
|
+
fontSize < preferred,
|
|
609
|
+
`expected the tag to shrink below the ${preferred}px preferred size, got ${fontSize}`,
|
|
610
|
+
);
|
|
611
|
+
assert.ok(fontSize >= 6, `expected the tag to stay legible, got ${fontSize}`);
|
|
612
|
+
});
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
test('contract: a too-wide label is never redirected into sublabel', () => {
|
|
616
|
+
// Every renderer used to advise "move detail to sublabel" for an over-long
|
|
617
|
+
// label. Sublabels are measured now, so that advice would move the problem
|
|
618
|
+
// rather than fix it.
|
|
619
|
+
const LABELS = {
|
|
620
|
+
workflow: 'An Extremely Long Node Label That Overflows',
|
|
621
|
+
sequence: 'An Extremely Long Participant Label That Overflows',
|
|
622
|
+
dataflow: 'An Extremely Long Node Label That Overflows',
|
|
623
|
+
lifecycle: 'An Extremely Long State Label That Overflows',
|
|
624
|
+
architecture: 'An Extremely Long Component Label Overflow',
|
|
625
|
+
};
|
|
626
|
+
const FIELD = {
|
|
627
|
+
workflow: 'nodes', sequence: 'participants', dataflow: 'nodes',
|
|
628
|
+
lifecycle: 'states', architecture: 'components',
|
|
629
|
+
};
|
|
630
|
+
for (const [mode, label] of Object.entries(LABELS)) {
|
|
631
|
+
const d = load(mode);
|
|
632
|
+
d[FIELD[mode]][0].label = label;
|
|
633
|
+
const { code, stderr } = render(mode, d);
|
|
634
|
+
assert.notEqual(code, 0, `${mode}: expected non-zero exit; stderr:\n${stderr}`);
|
|
635
|
+
assert.ok(stderr.includes('wider than'), `${mode}: expected a width message:\n${stderr}`);
|
|
636
|
+
assert.ok(
|
|
637
|
+
!/move detail to sublabel/.test(stderr),
|
|
638
|
+
`${mode}: label advice still points at the measured sublabel field:\n${stderr}`,
|
|
639
|
+
);
|
|
640
|
+
}
|
|
641
|
+
});
|
|
642
|
+
|
|
643
|
+
// ---- error-message contract: threshold + remediation, not just a path ----
|
|
644
|
+
test('contract: short-edge message carries both the px minimum and a fix verb', () => {
|
|
645
|
+
const d = load('workflow');
|
|
646
|
+
// Force a too-short labeled edge between adjacent same-lane columns.
|
|
647
|
+
d.nodes.push({ id: 'a1', lane: d.nodes[0].lane, col: 0, type: 'backend', label: 'A' });
|
|
648
|
+
d.nodes.push({ id: 'a2', lane: d.nodes[0].lane, col: 0, type: 'backend', label: 'B', yOffset: 30 });
|
|
649
|
+
d.edges.push({ from: 'a1', to: 'a2', label: 'x', route: 'straight' });
|
|
650
|
+
const { stderr } = render('workflow', d);
|
|
651
|
+
// Whatever rule fires, the messages must remain actionable (threshold + verb).
|
|
652
|
+
assert.match(stderr, /\d+px|at least \d+|0\.\.\d+|less than/);
|
|
653
|
+
});
|
|
654
|
+
|
|
655
|
+
test('contract: ajv path errors are annotated with the element id', () => {
|
|
656
|
+
const d = load('workflow');
|
|
657
|
+
d.nodes[3].colour = 'red'; // unknown property → ajv additionalProperties
|
|
658
|
+
const { stderr } = render('workflow', d);
|
|
659
|
+
// Only meaningful when ajv is installed; skip the assertion in degraded mode.
|
|
660
|
+
if (!/schema validation failed/.test(stderr)) return;
|
|
661
|
+
assert.match(stderr, /id\/label:/);
|
|
662
|
+
});
|
|
663
|
+
|
|
664
|
+
test('workflow: same-lane offset auto edge stays orthogonal', () => {
|
|
665
|
+
const d = {
|
|
666
|
+
schema_version: 1,
|
|
667
|
+
diagram_type: 'workflow',
|
|
668
|
+
meta: { title: 'Same-lane offset route' },
|
|
669
|
+
lanes: [{ id: 'main', label: 'Main lane' }],
|
|
670
|
+
nodes: [
|
|
671
|
+
{ id: 'left', lane: 'main', col: 1, type: 'backend', label: 'A', width: 32, height: 38, yOffset: -14 },
|
|
672
|
+
{ id: 'right', lane: 'main', col: 2, type: 'backend', label: 'B', width: 32, height: 38, yOffset: 14 },
|
|
673
|
+
],
|
|
674
|
+
edges: [{ from: 'left', to: 'right' }],
|
|
675
|
+
};
|
|
676
|
+
const { code, stderr, outPath } = render('workflow', d);
|
|
677
|
+
assert.equal(code, 0, stderr);
|
|
678
|
+
const html = fs.readFileSync(outPath, 'utf8');
|
|
679
|
+
assert.doesNotMatch(html, /M 236 105 L 284 133/);
|
|
680
|
+
assert.match(html, /M 236 105 L 260 105 L 260 133 L 284 133/);
|
|
681
|
+
});
|
|
682
|
+
|
|
683
|
+
test('workflow: automatic routing uses one bend and avoids every node border', () => {
|
|
684
|
+
const d = JSON.parse(fs.readFileSync(
|
|
685
|
+
path.join(skillRoot, 'test/fixtures/automatic-routing-node-border-clearance.workflow.json'),
|
|
686
|
+
'utf8',
|
|
687
|
+
));
|
|
688
|
+
const { code, stderr, outPath } = render('workflow', d);
|
|
689
|
+
assert.equal(code, 0, stderr);
|
|
690
|
+
const html = fs.readFileSync(outPath, 'utf8');
|
|
691
|
+
const points = workflowEdgePoints(html, 'stdin');
|
|
692
|
+
const target = workflowNodeRect(html, 'team_send');
|
|
693
|
+
assert.equal(points.length, 3, `expected one bend, received ${JSON.stringify(points)}`);
|
|
694
|
+
assert.equal(points[0][0], points[1][0], 'edge must leave the source vertically');
|
|
695
|
+
assert.ok(points[1][1] < points[0][1], 'edge must leave the source through its top side');
|
|
696
|
+
assert.equal(points[1][1], points[2][1], 'edge must enter the target horizontally');
|
|
697
|
+
assert.ok(points[2][0] > points[1][0], 'edge must enter the target through its left side');
|
|
698
|
+
assert.equal(points[2][0], target.x, 'edge must stop at the target border');
|
|
699
|
+
assert.ok(
|
|
700
|
+
points[2][1] > target.y && points[2][1] < target.y + target.height,
|
|
701
|
+
'edge must meet the target inside its left-side anchor range',
|
|
702
|
+
);
|
|
703
|
+
assertWorkflowEdgesAvoidAllNodeBorders(html, d);
|
|
704
|
+
|
|
705
|
+
const [labelX, labelY] = workflowEdgeLabelPoint(html, 'stdin');
|
|
706
|
+
assert.equal(labelX, points[0][0], 'the label should use the longer vertical segment');
|
|
707
|
+
assert.ok(labelY > points[1][1] && labelY < points[0][1], 'the label should stay inside that segment');
|
|
708
|
+
});
|
|
709
|
+
|
|
710
|
+
test('workflow: shared automatic endpoints avoid every node border', () => {
|
|
711
|
+
const d = {
|
|
712
|
+
schema_version: 1,
|
|
713
|
+
diagram_type: 'workflow',
|
|
714
|
+
meta: { title: 'Shared endpoint border invariant' },
|
|
715
|
+
lanes: [
|
|
716
|
+
{ id: 'target-lane', label: 'Target' },
|
|
717
|
+
{ id: 'source-lane', label: 'Sources' },
|
|
718
|
+
],
|
|
719
|
+
nodes: [
|
|
720
|
+
{ id: 'target', lane: 'target-lane', col: 2, type: 'backend', label: 'Target' },
|
|
721
|
+
{ id: 'source-left', lane: 'source-lane', col: 1, type: 'backend', label: 'Left' },
|
|
722
|
+
{ id: 'source-right', lane: 'source-lane', col: 3, type: 'backend', label: 'Right' },
|
|
723
|
+
],
|
|
724
|
+
edges: [
|
|
725
|
+
{ id: 'left-to-target', from: 'source-left', to: 'target' },
|
|
726
|
+
{ id: 'right-to-target', from: 'source-right', to: 'target' },
|
|
727
|
+
],
|
|
728
|
+
};
|
|
729
|
+
const { code, stderr, outPath } = render('workflow', d);
|
|
730
|
+
assert.equal(code, 0, stderr);
|
|
731
|
+
assertWorkflowEdgesAvoidAllNodeBorders(fs.readFileSync(outPath, 'utf8'), d);
|
|
732
|
+
});
|
|
733
|
+
|
|
734
|
+
test('workflow: blocked first one-bend candidate selects the clear one-bend orientation', () => {
|
|
735
|
+
const d = {
|
|
736
|
+
schema_version: 1,
|
|
737
|
+
diagram_type: 'workflow',
|
|
738
|
+
meta: { title: 'Blocked one-bend candidate', quality_profile: 'showcase' },
|
|
739
|
+
lanes: [
|
|
740
|
+
{ id: 'target-lane', label: 'Target' },
|
|
741
|
+
{ id: 'obstacle-lane', label: 'Obstacle' },
|
|
742
|
+
{ id: 'source-lane', label: 'Source' },
|
|
743
|
+
],
|
|
744
|
+
nodes: [
|
|
745
|
+
{ id: 'target', lane: 'target-lane', col: 3, type: 'backend', label: 'Target' },
|
|
746
|
+
{ id: 'obstacle', lane: 'obstacle-lane', col: 1, type: 'security', label: 'Obstacle' },
|
|
747
|
+
{ id: 'source', lane: 'source-lane', col: 1, type: 'backend', label: 'Source' },
|
|
748
|
+
],
|
|
749
|
+
edges: [{ id: 'clear-corner', from: 'source', to: 'target' }],
|
|
750
|
+
};
|
|
751
|
+
const { code, stderr, outPath } = render('workflow', d);
|
|
752
|
+
assert.equal(code, 0, stderr);
|
|
753
|
+
const points = workflowEdgePoints(fs.readFileSync(outPath, 'utf8'), 'clear-corner');
|
|
754
|
+
assert.equal(points.length, 3, `expected the alternate one-bend route, received ${JSON.stringify(points)}`);
|
|
755
|
+
assert.equal(points[0][1], points[1][1], 'blocked vertical-first candidate must switch to horizontal-first');
|
|
756
|
+
assert.ok(points[1][0] > points[0][0], 'edge must leave the source through its right side');
|
|
757
|
+
assert.equal(points[1][0], points[2][0], 'edge must enter the target vertically');
|
|
758
|
+
assert.ok(points[2][1] < points[1][1], 'edge must enter the target through its bottom side');
|
|
759
|
+
});
|
|
760
|
+
|
|
761
|
+
test('workflow: explicit drop routing remains authoritative over the one-bend preference', () => {
|
|
762
|
+
const d = {
|
|
763
|
+
schema_version: 1,
|
|
764
|
+
diagram_type: 'workflow',
|
|
765
|
+
meta: { title: 'Explicit route compatibility', quality_profile: 'showcase' },
|
|
766
|
+
lanes: [
|
|
767
|
+
{ id: 'target-lane', label: 'Target' },
|
|
768
|
+
{ id: 'source-lane', label: 'Source' },
|
|
769
|
+
],
|
|
770
|
+
nodes: [
|
|
771
|
+
{ id: 'target', lane: 'target-lane', col: 3, type: 'backend', label: 'Target' },
|
|
772
|
+
{ id: 'source', lane: 'source-lane', col: 1, type: 'backend', label: 'Source' },
|
|
773
|
+
],
|
|
774
|
+
edges: [{
|
|
775
|
+
id: 'authored-drop',
|
|
776
|
+
from: 'source',
|
|
777
|
+
to: 'target',
|
|
778
|
+
route: 'drop',
|
|
779
|
+
fromSide: 'top',
|
|
780
|
+
toSide: 'bottom',
|
|
781
|
+
}],
|
|
782
|
+
};
|
|
783
|
+
const { code, stderr, outPath } = render('workflow', d);
|
|
784
|
+
assert.equal(code, 0, stderr);
|
|
785
|
+
const points = workflowEdgePoints(fs.readFileSync(outPath, 'utf8'), 'authored-drop');
|
|
786
|
+
assert.equal(points.length, 4, `explicit drop route was replaced: ${JSON.stringify(points)}`);
|
|
787
|
+
assert.equal(points[0][0], points[1][0]);
|
|
788
|
+
assert.equal(points[1][1], points[2][1]);
|
|
789
|
+
assert.equal(points[2][0], points[3][0]);
|
|
790
|
+
});
|
|
791
|
+
|
|
792
|
+
test('workflow: authored endpoint sides that follow a border are rejected generically', () => {
|
|
793
|
+
const d = {
|
|
794
|
+
schema_version: 1,
|
|
795
|
+
diagram_type: 'workflow',
|
|
796
|
+
meta: { title: 'Authored endpoint border run' },
|
|
797
|
+
lanes: [
|
|
798
|
+
{ id: 'target-lane', label: 'Target' },
|
|
799
|
+
{ id: 'source-lane', label: 'Source' },
|
|
800
|
+
],
|
|
801
|
+
nodes: [
|
|
802
|
+
{ id: 'target', lane: 'target-lane', col: 3, type: 'backend', label: 'Target' },
|
|
803
|
+
{ id: 'source', lane: 'source-lane', col: 1, type: 'backend', label: 'Source' },
|
|
804
|
+
],
|
|
805
|
+
edges: [{
|
|
806
|
+
id: 'invalid-drop',
|
|
807
|
+
from: 'source',
|
|
808
|
+
to: 'target',
|
|
809
|
+
route: 'drop',
|
|
810
|
+
fromSide: 'right',
|
|
811
|
+
toSide: 'left',
|
|
812
|
+
}],
|
|
813
|
+
};
|
|
814
|
+
const { code, stderr } = render('workflow', d);
|
|
815
|
+
assert.notEqual(code, 0);
|
|
816
|
+
assert.match(stderr, /\[clean-flow\/endpoint-side-direction\] workflow edges\[0\] id "invalid-drop"/);
|
|
817
|
+
assert.match(stderr, /cross node borders perpendicularly/);
|
|
818
|
+
});
|
|
819
|
+
|
|
820
|
+
test('dataflow: authored endpoint sides that follow a border are rejected generically', () => {
|
|
821
|
+
const d = {
|
|
822
|
+
schema_version: 1,
|
|
823
|
+
diagram_type: 'dataflow',
|
|
824
|
+
meta: { title: 'Data-flow endpoint border run' },
|
|
825
|
+
stages: [{ label: 'Source' }, { label: 'Target' }],
|
|
826
|
+
nodes: [
|
|
827
|
+
{ id: 'source', stage: 0, row: 0, type: 'backend', label: 'Source' },
|
|
828
|
+
{ id: 'target', stage: 1, row: 2, type: 'database', label: 'Target' },
|
|
829
|
+
],
|
|
830
|
+
flows: [{
|
|
831
|
+
id: 'invalid-bottom',
|
|
832
|
+
from: 'source',
|
|
833
|
+
to: 'target',
|
|
834
|
+
label: 'payload',
|
|
835
|
+
route: 'bottom-channel',
|
|
836
|
+
fromSide: 'right',
|
|
837
|
+
toSide: 'left',
|
|
838
|
+
}],
|
|
839
|
+
};
|
|
840
|
+
const { code, stderr } = render('dataflow', d);
|
|
841
|
+
assert.notEqual(code, 0);
|
|
842
|
+
assert.match(stderr, /\[clean-flow\/endpoint-side-direction\] dataflow flows\[0\] id "invalid-bottom"/);
|
|
843
|
+
assert.match(stderr, /cross node borders perpendicularly/);
|
|
844
|
+
});
|
|
845
|
+
|
|
846
|
+
test('lifecycle: automatic cross-lane routes avoid every state border', () => {
|
|
847
|
+
const d = {
|
|
848
|
+
schema_version: 1,
|
|
849
|
+
diagram_type: 'lifecycle',
|
|
850
|
+
meta: { title: 'Lifecycle endpoint border invariant' },
|
|
851
|
+
lanes: [
|
|
852
|
+
{ id: 'main', label: 'Main' },
|
|
853
|
+
{ id: 'terminal', label: 'Terminal' },
|
|
854
|
+
],
|
|
855
|
+
states: [
|
|
856
|
+
{ id: 'target', lane: 'main', col: 3, type: 'success', label: 'Target' },
|
|
857
|
+
{ id: 'source', lane: 'terminal', col: 1, type: 'active', label: 'Source' },
|
|
858
|
+
],
|
|
859
|
+
transitions: [{ id: 'automatic-transition', from: 'source', to: 'target' }],
|
|
860
|
+
};
|
|
861
|
+
const { code, stderr, outPath } = render('lifecycle', d);
|
|
862
|
+
assert.equal(code, 0, stderr);
|
|
863
|
+
assertRelationshipsAvoidAllNodeBorders(
|
|
864
|
+
fs.readFileSync(outPath, 'utf8'),
|
|
865
|
+
d.transitions,
|
|
866
|
+
d.states,
|
|
867
|
+
);
|
|
868
|
+
});
|
|
869
|
+
|
|
870
|
+
function tangentViaLifecycle() {
|
|
871
|
+
return {
|
|
872
|
+
schema_version: 1,
|
|
873
|
+
diagram_type: 'lifecycle',
|
|
874
|
+
meta: { title: 'Legacy tangent via compatibility' },
|
|
875
|
+
lanes: [
|
|
876
|
+
{ id: 'main', label: 'Main' },
|
|
877
|
+
{ id: 'terminal', label: 'Terminal' },
|
|
878
|
+
],
|
|
879
|
+
states: [
|
|
880
|
+
{ id: 'source', lane: 'main', col: 1, type: 'active', label: 'Source' },
|
|
881
|
+
{ id: 'target', lane: 'terminal', col: 0, type: 'success', label: 'Target' },
|
|
882
|
+
],
|
|
883
|
+
transitions: [{
|
|
884
|
+
id: 'legacy-via',
|
|
885
|
+
from: 'source',
|
|
886
|
+
to: 'target',
|
|
887
|
+
fromSide: 'bottom',
|
|
888
|
+
toSide: 'top',
|
|
889
|
+
via: [[320, 188], [320, 430], [402, 430]],
|
|
890
|
+
}],
|
|
891
|
+
};
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
test('lifecycle: legacy tangent via is rendered exactly instead of silently rewritten', () => {
|
|
895
|
+
const d = tangentViaLifecycle();
|
|
896
|
+
const { code, stderr, outPath } = render('lifecycle', d);
|
|
897
|
+
assert.equal(code, 0, stderr);
|
|
898
|
+
assert.deepEqual(workflowEdgePoints(fs.readFileSync(outPath, 'utf8'), 'legacy-via'), [
|
|
899
|
+
[248, 188], [320, 188], [320, 430], [402, 430], [402, 450],
|
|
900
|
+
]);
|
|
901
|
+
});
|
|
902
|
+
|
|
903
|
+
for (const qualityProfile of ['standard', 'showcase']) {
|
|
904
|
+
test(`lifecycle: ${qualityProfile} keeps an authored tangent via authoritative`, () => {
|
|
905
|
+
const d = tangentViaLifecycle();
|
|
906
|
+
d.meta.quality_profile = qualityProfile;
|
|
907
|
+
const { code, stderr, outPath } = render('lifecycle', d);
|
|
908
|
+
assert.equal(code, 0, stderr);
|
|
909
|
+
assert.deepEqual(workflowEdgePoints(fs.readFileSync(outPath, 'utf8'), 'legacy-via'), [
|
|
910
|
+
[248, 188], [320, 188], [320, 430], [402, 430], [402, 450],
|
|
911
|
+
]);
|
|
912
|
+
});
|
|
913
|
+
|
|
914
|
+
test(`lifecycle: public validate accepts an authoritative authored tangent via in ${qualityProfile}`, () => {
|
|
915
|
+
const d = tangentViaLifecycle();
|
|
916
|
+
d.meta.quality_profile = qualityProfile;
|
|
917
|
+
const { code, result } = validateCli('lifecycle', d);
|
|
918
|
+
assert.equal(code, 0, JSON.stringify(result, null, 2));
|
|
919
|
+
assert.equal(result.ok, true);
|
|
920
|
+
});
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
test('workflow: explicit labelAt remains authoritative on an automatic one-bend edge', () => {
|
|
924
|
+
const d = JSON.parse(fs.readFileSync(
|
|
925
|
+
path.join(skillRoot, 'test/fixtures/automatic-routing-node-border-clearance.workflow.json'),
|
|
926
|
+
'utf8',
|
|
927
|
+
));
|
|
928
|
+
d.edges[0].labelAt = [350, 166];
|
|
929
|
+
const { code, stderr, outPath } = render('workflow', d);
|
|
930
|
+
assert.equal(code, 0, stderr);
|
|
931
|
+
const html = fs.readFileSync(outPath, 'utf8');
|
|
932
|
+
assert.deepEqual(workflowEdgeLabelPoint(html, 'stdin'), [350, 166]);
|
|
933
|
+
});
|
|
934
|
+
|
|
935
|
+
test('workflow: bounded font fitting keeps an ordinary long sublabel inside its node', () => {
|
|
936
|
+
const d = load('workflow');
|
|
937
|
+
d.nodes[0].width = 92;
|
|
938
|
+
d.nodes[0].sublabel = 'shell / browser / MCP';
|
|
939
|
+
const { code, stderr, outPath } = render('workflow', d);
|
|
940
|
+
assert.equal(code, 0, stderr);
|
|
941
|
+
const html = fs.readFileSync(outPath, 'utf8');
|
|
942
|
+
assert.match(html, /font-size="6\.6"[^>]*>shell \/ browser \/ MCP<\/text>/);
|
|
943
|
+
});
|
|
944
|
+
|
|
945
|
+
test('workflow: edge crossing a non-endpoint node is rejected', () => {
|
|
946
|
+
const d = {
|
|
947
|
+
schema_version: 1,
|
|
948
|
+
diagram_type: 'workflow',
|
|
949
|
+
meta: { title: 'Crossing edge route', quality_profile: 'standard' },
|
|
950
|
+
lanes: [{ id: 'main', label: 'Main lane' }],
|
|
951
|
+
nodes: [
|
|
952
|
+
{ id: 'left', lane: 'main', col: 0, type: 'backend', label: 'Left', width: 60 },
|
|
953
|
+
{ id: 'middle', lane: 'main', col: 2, type: 'database', label: 'Middle', width: 70 },
|
|
954
|
+
{ id: 'right', lane: 'main', col: 4, type: 'backend', label: 'Right', width: 60 },
|
|
955
|
+
],
|
|
956
|
+
edges: [{ from: 'left', to: 'right', route: 'straight' }],
|
|
957
|
+
};
|
|
958
|
+
const { code, stderr } = render('workflow', d);
|
|
959
|
+
assert.notEqual(code, 0, `expected non-zero exit; stderr:\n${stderr}`);
|
|
960
|
+
assert.match(stderr, /crosses node "middle"/);
|
|
961
|
+
assert.match(stderr, /fromSide\/toSide|channel|lane\/column/);
|
|
962
|
+
});
|
|
963
|
+
|
|
964
|
+
test('architecture: Clean Flow Gate rejects a connection through a component', () => {
|
|
965
|
+
const d = {
|
|
966
|
+
schema_version: 1,
|
|
967
|
+
diagram_type: 'architecture',
|
|
968
|
+
meta: { title: 'Opaque component crossing', quality_profile: 'standard' },
|
|
969
|
+
components: [
|
|
970
|
+
{ id: 'left', type: 'frontend', label: 'Left', pos: [60, 120], size: [100, 54] },
|
|
971
|
+
{ id: 'middle', type: 'security', label: 'Middle', pos: [270, 120], size: [100, 54] },
|
|
972
|
+
{ id: 'right', type: 'backend', label: 'Right', pos: [480, 120], size: [100, 54] },
|
|
973
|
+
],
|
|
974
|
+
connections: [{ id: 'direct', from: 'left', to: 'right', route: 'straight' }],
|
|
975
|
+
};
|
|
976
|
+
const { code, stderr } = render('architecture', d);
|
|
977
|
+
assert.notEqual(code, 0, `expected non-zero exit; stderr:\n${stderr}`);
|
|
978
|
+
assert.match(stderr, /\[clean-flow\/edge-through-node\] architecture connections\[0\] id "direct"/);
|
|
979
|
+
assert.match(stderr, /crosses component "middle"/);
|
|
980
|
+
assert.match(stderr, /segment 0 .*2px clearance/);
|
|
981
|
+
});
|
|
982
|
+
|
|
983
|
+
test('dataflow: showcase rejects a relationship label that hides another route', () => {
|
|
984
|
+
const d = JSON.parse(fs.readFileSync(path.join(skillRoot, 'examples', 'event-stream.dataflow.json'), 'utf8'));
|
|
985
|
+
const approvedReplay = d.flows.find((flow) => flow.label === 'approved replay');
|
|
986
|
+
delete approvedReplay.labelAt;
|
|
987
|
+
delete approvedReplay.labelDx;
|
|
988
|
+
delete approvedReplay.labelDy;
|
|
989
|
+
delete approvedReplay.labelSegment;
|
|
990
|
+
const { code, stderr } = render('dataflow', d);
|
|
991
|
+
assert.notEqual(code, 0, `expected non-zero exit; stderr:\n${stderr}`);
|
|
992
|
+
assert.match(stderr, /\[composition\/label-route-clearance\] showcase dataflow/);
|
|
993
|
+
assert.match(stderr, /approved replay.*failure sample/);
|
|
994
|
+
assert.match(stderr, /labelAt.*labelDx.*labelDy.*labelSegment/);
|
|
995
|
+
});
|
|
996
|
+
|
|
997
|
+
test('dataflow: validator and SVG share the 27px CJK/emoji classification mask', () => {
|
|
998
|
+
const d = load('dataflow');
|
|
999
|
+
const flow = d.flows[0];
|
|
1000
|
+
flow.label = '写入🚀';
|
|
1001
|
+
flow.classification = '机密🔐';
|
|
1002
|
+
const { code, stderr, outPath } = render('dataflow', d);
|
|
1003
|
+
assert.equal(code, 0, stderr);
|
|
1004
|
+
const html = fs.readFileSync(outPath, 'utf8');
|
|
1005
|
+
const group = html.match(/<g data-detail="context"[^>]*data-edge-id="web-clickstream"[^>]*>[\s\S]*?<\/g>/)?.[0] || '';
|
|
1006
|
+
assert.match(group, /data-edge-label="写入🚀"/);
|
|
1007
|
+
assert.match(group, /<rect x="[^\"]+" y="[^\"]+" width="41\.4" height="27" rx="4" class="c-mask"\/>/);
|
|
1008
|
+
assert.match(group, />机密🔐<\/text>/);
|
|
1009
|
+
});
|
|
1010
|
+
|
|
1011
|
+
test('architecture: showcase rejects a connection label that hides another route', () => {
|
|
1012
|
+
const d = load('architecture');
|
|
1013
|
+
d.connections[0].labelAt = [620, 330];
|
|
1014
|
+
const { code, stderr } = render('architecture', d);
|
|
1015
|
+
assert.notEqual(code, 0, `expected non-zero exit; stderr:\n${stderr}`);
|
|
1016
|
+
assert.match(stderr, /\[composition\/label-route-clearance\] showcase architecture/);
|
|
1017
|
+
assert.match(stderr, /HTTPS.*lb-to-api/);
|
|
1018
|
+
});
|
|
1019
|
+
|
|
1020
|
+
test('workflow: showcase rejects an edge label that hides another route', () => {
|
|
1021
|
+
const d = {
|
|
1022
|
+
schema_version: 1,
|
|
1023
|
+
diagram_type: 'workflow',
|
|
1024
|
+
meta: {
|
|
1025
|
+
title: 'Workflow label-route clearance',
|
|
1026
|
+
quality_profile: 'showcase',
|
|
1027
|
+
viewBox: [720, 400],
|
|
1028
|
+
legend: { mode: 'hidden' },
|
|
1029
|
+
},
|
|
1030
|
+
lanes: [
|
|
1031
|
+
{ id: 'label', label: 'Label owner' },
|
|
1032
|
+
{ id: 'route', label: 'Other route' },
|
|
1033
|
+
],
|
|
1034
|
+
nodes: [
|
|
1035
|
+
{ id: 'a', lane: 'label', col: 0, type: 'backend', label: 'A' },
|
|
1036
|
+
{ id: 'b', lane: 'label', col: 2, type: 'backend', label: 'B' },
|
|
1037
|
+
{ id: 'c', lane: 'route', col: 0, type: 'backend', label: 'C' },
|
|
1038
|
+
{ id: 'd', lane: 'route', col: 2, type: 'backend', label: 'D' },
|
|
1039
|
+
],
|
|
1040
|
+
edges: [
|
|
1041
|
+
{ id: 'labeled-edge', from: 'a', to: 'b', label: 'plan', labelAt: [200, 243] },
|
|
1042
|
+
{ id: 'other-route', from: 'c', to: 'd' },
|
|
1043
|
+
],
|
|
1044
|
+
};
|
|
1045
|
+
const { code, stderr } = render('workflow', d);
|
|
1046
|
+
assert.notEqual(code, 0, `expected non-zero exit; stderr:\n${stderr}`);
|
|
1047
|
+
assert.match(stderr, /\[composition\/label-route-clearance\] showcase workflow/);
|
|
1048
|
+
assert.match(stderr, /plan.*other-route/);
|
|
1049
|
+
});
|
|
1050
|
+
|
|
1051
|
+
test('lifecycle: showcase rejects a transition label that hides another route', () => {
|
|
1052
|
+
const d = load('lifecycle');
|
|
1053
|
+
d.transitions[0].label = 'approval gate';
|
|
1054
|
+
d.transitions[0].labelAt = [556, 250];
|
|
1055
|
+
const { code, stderr } = render('lifecycle', d);
|
|
1056
|
+
assert.notEqual(code, 0, `expected non-zero exit; stderr:\n${stderr}`);
|
|
1057
|
+
assert.match(stderr, /\[composition\/label-route-clearance\] showcase lifecycle/);
|
|
1058
|
+
assert.match(stderr, /approval gate.*review-blocked/);
|
|
1059
|
+
});
|
|
1060
|
+
|
|
1061
|
+
test('sequence: showcase rejects a message label that hides an adjacent route', () => {
|
|
1062
|
+
const d = load('sequence');
|
|
1063
|
+
d.messages = d.messages.slice(0, 2);
|
|
1064
|
+
d.messages[0].label = 'customer authorization context';
|
|
1065
|
+
d.messages[0].y = 250;
|
|
1066
|
+
d.messages[1].label = 'ok';
|
|
1067
|
+
d.messages[1].y = 245;
|
|
1068
|
+
d.segments = [];
|
|
1069
|
+
d.activations = [];
|
|
1070
|
+
const { code, stderr } = render('sequence', d);
|
|
1071
|
+
assert.notEqual(code, 0, `expected non-zero exit; stderr:\n${stderr}`);
|
|
1072
|
+
assert.match(stderr, /\[composition\/label-route-clearance\] showcase sequence/);
|
|
1073
|
+
assert.match(stderr, /customer authorization context.*dashboard-request/);
|
|
1074
|
+
});
|
|
1075
|
+
|
|
1076
|
+
function autoRoutePassThroughDocument(connection) {
|
|
1077
|
+
return {
|
|
1078
|
+
schema_version: 1,
|
|
1079
|
+
diagram_type: 'architecture',
|
|
1080
|
+
meta: { title: 'Auto-route pass-through regression' },
|
|
1081
|
+
components: [
|
|
1082
|
+
{ id: 'api', type: 'backend', label: 'API', pos: [400, 280], size: [160, 76] },
|
|
1083
|
+
{ id: 'cache', type: 'database', label: 'Cache', pos: [645, 130], size: [130, 60] },
|
|
1084
|
+
{ id: 'queue', type: 'cloud', label: 'Queue', pos: [880, 130] },
|
|
1085
|
+
],
|
|
1086
|
+
connections: [connection],
|
|
1087
|
+
};
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
test('architecture: default auto route selects a safe orthogonal candidate around an unrelated component', () => {
|
|
1091
|
+
const d = autoRoutePassThroughDocument({ from: 'api', to: 'queue', variant: 'dashed' });
|
|
1092
|
+
const { code, stderr, outPath } = render('architecture', d);
|
|
1093
|
+
assert.equal(code, 0, stderr);
|
|
1094
|
+
const html = fs.readFileSync(outPath, 'utf8');
|
|
1095
|
+
assert.match(html, /data-composition-points="560,318;856,318;856,160;880,160"/);
|
|
1096
|
+
});
|
|
1097
|
+
|
|
1098
|
+
test('architecture: auto route enters explicit top and bottom ports perpendicularly', () => {
|
|
1099
|
+
const d = {
|
|
1100
|
+
schema_version: 1,
|
|
1101
|
+
diagram_type: 'architecture',
|
|
1102
|
+
meta: { title: 'Endpoint direction regression' },
|
|
1103
|
+
components: [
|
|
1104
|
+
{ id: 'cli-agents', type: 'external', label: 'CLI Agents', pos: [300, 100], size: [100, 60] },
|
|
1105
|
+
{ id: 'tasks-watch', type: 'backend', label: 'Tasks Watcher', pos: [100, 240], size: [100, 60] },
|
|
1106
|
+
],
|
|
1107
|
+
connections: [
|
|
1108
|
+
{ id: 'tasks-file', from: 'cli-agents', to: 'tasks-watch', variant: 'dashed', fromSide: 'bottom', toSide: 'top' },
|
|
1109
|
+
],
|
|
1110
|
+
};
|
|
1111
|
+
const { code, stderr, outPath } = render('architecture', d);
|
|
1112
|
+
assert.equal(code, 0, stderr);
|
|
1113
|
+
const html = fs.readFileSync(outPath, 'utf8');
|
|
1114
|
+
assert.match(html, /data-composition-points="350,160;350,200;150,200;150,240"/);
|
|
1115
|
+
});
|
|
1116
|
+
|
|
1117
|
+
test('architecture: auto route preserves inferred side normals when the primary dogleg is blocked', () => {
|
|
1118
|
+
const d = {
|
|
1119
|
+
schema_version: 1,
|
|
1120
|
+
diagram_type: 'architecture',
|
|
1121
|
+
meta: { title: 'Inferred endpoint direction regression' },
|
|
1122
|
+
components: [
|
|
1123
|
+
{ id: 'workspace', type: 'frontend', label: 'Workspace UI', pos: [40, 300], size: [120, 60] },
|
|
1124
|
+
{ id: 'runtime-server', type: 'backend', label: 'Runtime Server', pos: [220, 300], size: [120, 60] },
|
|
1125
|
+
{ id: 'runtime-store', type: 'backend', label: 'Runtime Store', pos: [400, 300], size: [120, 60] },
|
|
1126
|
+
{ id: 'stream-hub', type: 'messagebus', label: 'Terminal Stream Hub', pos: [700, 100], size: [120, 60] },
|
|
1127
|
+
],
|
|
1128
|
+
connections: [
|
|
1129
|
+
{ id: 'terminal-return', from: 'stream-hub', to: 'workspace' },
|
|
1130
|
+
],
|
|
1131
|
+
};
|
|
1132
|
+
const { code, stderr, outPath } = render('architecture', d);
|
|
1133
|
+
assert.equal(code, 0, stderr);
|
|
1134
|
+
const html = fs.readFileSync(outPath, 'utf8');
|
|
1135
|
+
assert.match(html, /data-composition-points="700,130;184,130;184,330;160,330"/);
|
|
1136
|
+
assert.doesNotMatch(html, /data-composition-points="700,130;700,230;160,230;160,330"/);
|
|
1137
|
+
});
|
|
1138
|
+
|
|
1139
|
+
test('architecture: explicit via cannot run tangentially into an authored top port', () => {
|
|
1140
|
+
const d = {
|
|
1141
|
+
schema_version: 1,
|
|
1142
|
+
diagram_type: 'architecture',
|
|
1143
|
+
meta: { title: 'Tangent endpoint regression' },
|
|
1144
|
+
components: [
|
|
1145
|
+
{ id: 'cli-agents', type: 'external', label: 'CLI Agents', pos: [300, 100], size: [100, 60] },
|
|
1146
|
+
{ id: 'tasks-watch', type: 'backend', label: 'Tasks Watcher', pos: [100, 240], size: [100, 60] },
|
|
1147
|
+
],
|
|
1148
|
+
connections: [
|
|
1149
|
+
{
|
|
1150
|
+
id: 'tasks-file',
|
|
1151
|
+
from: 'cli-agents',
|
|
1152
|
+
to: 'tasks-watch',
|
|
1153
|
+
variant: 'dashed',
|
|
1154
|
+
fromSide: 'bottom',
|
|
1155
|
+
toSide: 'top',
|
|
1156
|
+
via: [[350, 200], [100, 200], [100, 240]],
|
|
1157
|
+
},
|
|
1158
|
+
],
|
|
1159
|
+
};
|
|
1160
|
+
const { code, stderr } = render('architecture', d);
|
|
1161
|
+
assert.notEqual(code, 0, `expected non-zero exit; stderr:\n${stderr}`);
|
|
1162
|
+
assert.match(stderr, /\[clean-flow\/endpoint-side-direction\] architecture connections\[0\] id "tasks-file"/);
|
|
1163
|
+
assert.match(stderr, /final segment 3 \[100, 240\] -> \[150, 240\]/);
|
|
1164
|
+
assert.match(stderr, /toSide "top".*vertical downward from above/);
|
|
1165
|
+
});
|
|
1166
|
+
|
|
1167
|
+
test('architecture: explicit orthogonal route remains authoritative when it crosses a component', () => {
|
|
1168
|
+
const d = autoRoutePassThroughDocument({
|
|
1169
|
+
from: 'api',
|
|
1170
|
+
to: 'queue',
|
|
1171
|
+
variant: 'dashed',
|
|
1172
|
+
route: 'orthogonal-h',
|
|
1173
|
+
});
|
|
1174
|
+
const { code, stderr } = render('architecture', d);
|
|
1175
|
+
assert.notEqual(code, 0, `expected non-zero exit; stderr:\n${stderr}`);
|
|
1176
|
+
assert.match(stderr, /connections\[0\] "api" -> "queue" crosses component "cache"/);
|
|
1177
|
+
});
|
|
1178
|
+
|
|
1179
|
+
test('architecture: auto route still fails closed when doglegs and side-aware bridges are blocked', () => {
|
|
1180
|
+
const d = autoRoutePassThroughDocument({ from: 'api', to: 'queue', variant: 'dashed' });
|
|
1181
|
+
d.components.push({ id: 'guard', type: 'security', label: 'Guard', pos: [825, 215], size: [70, 50] });
|
|
1182
|
+
const { code, stderr } = render('architecture', d);
|
|
1183
|
+
assert.notEqual(code, 0, `expected non-zero exit; stderr:\n${stderr}`);
|
|
1184
|
+
assert.match(stderr, /connections\[0\] "api" -> "queue" crosses component "cache"/);
|
|
1185
|
+
});
|
|
1186
|
+
|
|
1187
|
+
test('architecture: explicit waypoints around an obstacle remain valid by default', () => {
|
|
1188
|
+
const d = autoRoutePassThroughDocument({
|
|
1189
|
+
from: 'api',
|
|
1190
|
+
to: 'queue',
|
|
1191
|
+
variant: 'dashed',
|
|
1192
|
+
fromSide: 'right',
|
|
1193
|
+
toSide: 'top',
|
|
1194
|
+
via: [[620, 318], [620, 100], [940, 100]],
|
|
1195
|
+
});
|
|
1196
|
+
const { code, stderr } = render('architecture', d);
|
|
1197
|
+
assert.equal(code, 0, stderr);
|
|
1198
|
+
});
|
|
1199
|
+
|
|
1200
|
+
test('architecture: showcase rejects an unrelated proper edge crossing', () => {
|
|
1201
|
+
const d = {
|
|
1202
|
+
schema_version: 1,
|
|
1203
|
+
diagram_type: 'architecture',
|
|
1204
|
+
meta: { title: 'Showcase crossing', quality_profile: 'showcase' },
|
|
1205
|
+
components: [
|
|
1206
|
+
{ id: 'a', type: 'frontend', label: 'A', pos: [60, 80], size: [60, 40] },
|
|
1207
|
+
{ id: 'b', type: 'backend', label: 'B', pos: [360, 260], size: [60, 40] },
|
|
1208
|
+
{ id: 'c', type: 'database', label: 'C', pos: [60, 260], size: [60, 40] },
|
|
1209
|
+
{ id: 'd', type: 'external', label: 'D', pos: [360, 80], size: [60, 40] },
|
|
1210
|
+
],
|
|
1211
|
+
connections: [
|
|
1212
|
+
{ id: 'down-right', from: 'a', to: 'b', route: 'orthogonal-h' },
|
|
1213
|
+
{ id: 'up-right', from: 'c', to: 'd', route: 'orthogonal-v', fromSide: 'top', toSide: 'bottom' },
|
|
1214
|
+
],
|
|
1215
|
+
};
|
|
1216
|
+
const { code, stderr } = render('architecture', d);
|
|
1217
|
+
assert.notEqual(code, 0, `expected non-zero exit; stderr:\n${stderr}`);
|
|
1218
|
+
assert.match(stderr, /\[composition\/proper-crossing\] showcase architecture/);
|
|
1219
|
+
assert.match(stderr, /connections\[0\] id "down-right"/);
|
|
1220
|
+
assert.match(stderr, /connections\[1\] id "up-right"/);
|
|
1221
|
+
assert.match(stderr, /at \[240, 190\]/);
|
|
1222
|
+
assert.match(stderr, /segments 1 and 1/);
|
|
1223
|
+
assert.match(stderr, /route\/via|fromSide\/toSide/);
|
|
1224
|
+
});
|
|
1225
|
+
|
|
1226
|
+
test('architecture: showcase preserves a straight-through explicit waypoint as an authored touch', () => {
|
|
1227
|
+
const d = {
|
|
1228
|
+
schema_version: 1,
|
|
1229
|
+
diagram_type: 'architecture',
|
|
1230
|
+
meta: {
|
|
1231
|
+
title: 'Forward-collinear waypoint compatibility',
|
|
1232
|
+
quality_profile: 'showcase',
|
|
1233
|
+
viewBox: [600, 320],
|
|
1234
|
+
legend: { mode: 'hidden' },
|
|
1235
|
+
},
|
|
1236
|
+
components: [
|
|
1237
|
+
{ id: 'a', type: 'backend', label: 'A', pos: [50, 100], size: [80, 60] },
|
|
1238
|
+
{ id: 'b', type: 'backend', label: 'B', pos: [450, 100], size: [80, 60] },
|
|
1239
|
+
{ id: 'c', type: 'backend', label: 'C', pos: [260, 0], size: [80, 60] },
|
|
1240
|
+
{ id: 'd', type: 'backend', label: 'D', pos: [260, 200], size: [80, 60] },
|
|
1241
|
+
],
|
|
1242
|
+
connections: [
|
|
1243
|
+
{ id: 'horizontal', from: 'a', to: 'b', via: [[300, 130]] },
|
|
1244
|
+
{ id: 'vertical', from: 'c', to: 'd' },
|
|
1245
|
+
],
|
|
1246
|
+
};
|
|
1247
|
+
|
|
1248
|
+
const { code, stderr } = render('architecture', d);
|
|
1249
|
+
assert.equal(code, 0, stderr);
|
|
1250
|
+
});
|
|
1251
|
+
|
|
1252
|
+
test('architecture: standard keeps the same proper crossing renderable', () => {
|
|
1253
|
+
const d = {
|
|
1254
|
+
schema_version: 1,
|
|
1255
|
+
diagram_type: 'architecture',
|
|
1256
|
+
meta: { title: 'Standard crossing', quality_profile: 'standard' },
|
|
1257
|
+
components: [
|
|
1258
|
+
{ id: 'a', type: 'frontend', label: 'A', pos: [60, 80], size: [60, 40] },
|
|
1259
|
+
{ id: 'b', type: 'backend', label: 'B', pos: [360, 260], size: [60, 40] },
|
|
1260
|
+
{ id: 'c', type: 'database', label: 'C', pos: [60, 260], size: [60, 40] },
|
|
1261
|
+
{ id: 'd', type: 'external', label: 'D', pos: [360, 80], size: [60, 40] },
|
|
1262
|
+
],
|
|
1263
|
+
connections: [
|
|
1264
|
+
{ from: 'a', to: 'b', route: 'orthogonal-h' },
|
|
1265
|
+
{ from: 'c', to: 'd', route: 'orthogonal-v', fromSide: 'top', toSide: 'bottom' },
|
|
1266
|
+
],
|
|
1267
|
+
};
|
|
1268
|
+
const { code, stderr } = render('architecture', d);
|
|
1269
|
+
assert.equal(code, 0, stderr);
|
|
1270
|
+
});
|
|
1271
|
+
|
|
1272
|
+
function ambiguousCorridorDocument(profile) {
|
|
1273
|
+
return {
|
|
1274
|
+
schema_version: 1,
|
|
1275
|
+
diagram_type: 'architecture',
|
|
1276
|
+
meta: { title: 'Ambiguous corridor', quality_profile: profile },
|
|
1277
|
+
components: [
|
|
1278
|
+
{ id: 'a', type: 'frontend', label: 'A', pos: [40, 60], size: [60, 40] },
|
|
1279
|
+
{ id: 'b', type: 'backend', label: 'B', pos: [400, 60], size: [60, 40] },
|
|
1280
|
+
{ id: 'c', type: 'database', label: 'C', pos: [120, 220], size: [60, 40] },
|
|
1281
|
+
{ id: 'd', type: 'external', label: 'D', pos: [480, 220], size: [60, 40] },
|
|
1282
|
+
],
|
|
1283
|
+
connections: [
|
|
1284
|
+
{ id: 'first', from: 'a', to: 'b', fromSide: 'right', toSide: 'left', route: 'straight' },
|
|
1285
|
+
{ id: 'second', from: 'c', to: 'd', fromSide: 'top', toSide: 'top', via: [[150, 80], [390, 80], [390, 180], [510, 180]] },
|
|
1286
|
+
],
|
|
1287
|
+
};
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
test('architecture: showcase rejects an unrelated shared route corridor', () => {
|
|
1291
|
+
const { code, stderr } = render('architecture', ambiguousCorridorDocument('showcase'));
|
|
1292
|
+
assert.notEqual(code, 0, `expected non-zero exit; stderr:\n${stderr}`);
|
|
1293
|
+
assert.match(stderr, /\[composition\/ambiguous-corridor\] showcase architecture/);
|
|
1294
|
+
assert.match(stderr, /connections\[0\] id "first" "a" -> "b" shares a 240px corridor with connections\[1\] id "second" "c" -> "d"/);
|
|
1295
|
+
assert.match(stderr, /\[150, 80\] -> \[390, 80\]/);
|
|
1296
|
+
assert.match(stderr, /do not visually merge/);
|
|
1297
|
+
});
|
|
1298
|
+
|
|
1299
|
+
test('architecture: standard keeps an ambiguous corridor renderable for repair', () => {
|
|
1300
|
+
const { code, stderr } = render('architecture', ambiguousCorridorDocument('standard'));
|
|
1301
|
+
assert.equal(code, 0, stderr);
|
|
1302
|
+
});
|
|
1303
|
+
|
|
1304
|
+
test('architecture: route rhythm warns in standard and blocks a showcase micro segment', () => {
|
|
1305
|
+
const base = {
|
|
1306
|
+
schema_version: 1,
|
|
1307
|
+
diagram_type: 'architecture',
|
|
1308
|
+
meta: { title: 'Readable turn rhythm' },
|
|
1309
|
+
components: [
|
|
1310
|
+
{ id: 'a', type: 'frontend', label: 'A', pos: [60, 80], size: [60, 40] },
|
|
1311
|
+
{ id: 'b', type: 'backend', label: 'B', pos: [360, 80], size: [60, 40] },
|
|
1312
|
+
],
|
|
1313
|
+
connections: [
|
|
1314
|
+
{ id: 'tight', from: 'a', to: 'b', fromSide: 'right', toSide: 'bottom', via: [[125, 100], [125, 160], [390, 160]] },
|
|
1315
|
+
],
|
|
1316
|
+
};
|
|
1317
|
+
const standard = structuredClone(base);
|
|
1318
|
+
standard.meta.quality_profile = 'standard';
|
|
1319
|
+
assert.equal(render('architecture', standard).code, 0);
|
|
1320
|
+
|
|
1321
|
+
const showcase = structuredClone(base);
|
|
1322
|
+
showcase.meta.quality_profile = 'showcase';
|
|
1323
|
+
const { code, stderr } = render('architecture', showcase);
|
|
1324
|
+
assert.notEqual(code, 0);
|
|
1325
|
+
assert.match(stderr, /\[composition\/micro-segment\] showcase architecture connections\[0\] id "tight"/);
|
|
1326
|
+
assert.match(stderr, /5px source-stub segment 0/);
|
|
1327
|
+
assert.match(stderr, /wider corridor|move the component/);
|
|
1328
|
+
});
|
|
1329
|
+
|
|
1330
|
+
test('architecture: container border run is blocking in standard and showcase', () => {
|
|
1331
|
+
for (const profile of ['standard', 'showcase']) {
|
|
1332
|
+
const d = load('architecture');
|
|
1333
|
+
d.meta.quality_profile = profile;
|
|
1334
|
+
d.connections.find((connection) => connection.id === 'jwt-verification').via = [[620, 142], [620, 270], [735, 270]];
|
|
1335
|
+
const { code, stderr } = render('architecture', d);
|
|
1336
|
+
assert.notEqual(code, 0, `expected ${profile} to reject a border run`);
|
|
1337
|
+
assert.match(stderr, /\[composition\/container-border-run\] architecture connections\[1\] id "jwt-verification"/);
|
|
1338
|
+
assert.match(stderr, /security-group "sg-api :443\/:8000" top border/);
|
|
1339
|
+
}
|
|
1340
|
+
});
|
|
1341
|
+
|
|
1342
|
+
test('dataflow: stage border run is blocking and the inter-stage gutter passes', () => {
|
|
1343
|
+
const bad = load('dataflow');
|
|
1344
|
+
bad.flows.find((flow) => flow.id === 'web-clickstream').via = [[184, 157], [184, 271]];
|
|
1345
|
+
const failed = render('dataflow', bad);
|
|
1346
|
+
assert.notEqual(failed.code, 0);
|
|
1347
|
+
assert.match(failed.stderr, /\[composition\/container-border-run\] dataflow flows\[0\] id "web-clickstream"/);
|
|
1348
|
+
assert.match(failed.stderr, /stage "Sources" right border for 114px/);
|
|
1349
|
+
|
|
1350
|
+
const clean = load('dataflow');
|
|
1351
|
+
const passed = render('dataflow', clean);
|
|
1352
|
+
assert.equal(passed.code, 0, passed.stderr);
|
|
1353
|
+
});
|
|
1354
|
+
|
|
1355
|
+
test('sequence: a message cannot masquerade as a time-segment border', () => {
|
|
1356
|
+
const d = load('sequence');
|
|
1357
|
+
d.messages.find((message) => message.id === 'cache-read').y = d.segments[1].from;
|
|
1358
|
+
const { code, stderr } = render('sequence', d);
|
|
1359
|
+
assert.notEqual(code, 0);
|
|
1360
|
+
assert.match(stderr, /\[composition\/container-border-run\] sequence messages\[4\] id "cache-read"/);
|
|
1361
|
+
assert.match(stderr, /segment "Fallback" top border/);
|
|
1362
|
+
});
|
|
1363
|
+
|
|
1364
|
+
test('dataflow: Clean Flow Gate rejects a flow through an unrelated node', () => {
|
|
1365
|
+
const d = {
|
|
1366
|
+
schema_version: 1,
|
|
1367
|
+
diagram_type: 'dataflow',
|
|
1368
|
+
meta: { title: 'Opaque data node crossing', quality_profile: 'standard' },
|
|
1369
|
+
stages: [{ label: 'Source' }, { label: 'Middle' }, { label: 'Sink' }],
|
|
1370
|
+
nodes: [
|
|
1371
|
+
{ id: 'left', type: 'frontend', label: 'Left', stage: 0, row: 1 },
|
|
1372
|
+
{ id: 'middle', type: 'security', label: 'Middle', stage: 1, row: 1 },
|
|
1373
|
+
{ id: 'right', type: 'database', label: 'Right', stage: 2, row: 1 },
|
|
1374
|
+
],
|
|
1375
|
+
flows: [{ id: 'direct', from: 'left', to: 'right', label: 'payload', route: 'straight', labelAt: [315, 190] }],
|
|
1376
|
+
};
|
|
1377
|
+
const { code, stderr } = render('dataflow', d);
|
|
1378
|
+
assert.notEqual(code, 0, `expected non-zero exit; stderr:\n${stderr}`);
|
|
1379
|
+
assert.match(stderr, /\[clean-flow\/edge-through-node\] dataflow flows\[0\] id "direct"/);
|
|
1380
|
+
assert.match(stderr, /crosses node "middle"/);
|
|
1381
|
+
assert.match(stderr, /stage\/row/);
|
|
1382
|
+
});
|
|
1383
|
+
|
|
1384
|
+
test('lifecycle: Clean Flow Gate rejects a transition through an unrelated state', () => {
|
|
1385
|
+
const d = {
|
|
1386
|
+
schema_version: 1,
|
|
1387
|
+
diagram_type: 'lifecycle',
|
|
1388
|
+
meta: { title: 'Opaque state crossing', quality_profile: 'standard' },
|
|
1389
|
+
lanes: [{ id: 'main', label: 'Main' }],
|
|
1390
|
+
states: [
|
|
1391
|
+
{ id: 'left', type: 'start', label: 'Left', lane: 'main', col: 0 },
|
|
1392
|
+
{ id: 'middle', type: 'waiting', label: 'Middle', lane: 'main', col: 2 },
|
|
1393
|
+
{ id: 'right', type: 'success', label: 'Right', lane: 'main', col: 4 },
|
|
1394
|
+
],
|
|
1395
|
+
transitions: [{ id: 'direct', from: 'left', to: 'right', route: 'straight' }],
|
|
1396
|
+
};
|
|
1397
|
+
const { code, stderr } = render('lifecycle', d);
|
|
1398
|
+
assert.notEqual(code, 0, `expected non-zero exit; stderr:\n${stderr}`);
|
|
1399
|
+
assert.match(stderr, /\[clean-flow\/edge-through-node\] lifecycle transitions\[0\] id "direct"/);
|
|
1400
|
+
assert.match(stderr, /crosses state "middle"/);
|
|
1401
|
+
assert.match(stderr, /col\/yOffset/);
|
|
1402
|
+
});
|
|
1403
|
+
|
|
1404
|
+
test('sequence: lifelines and activation bars remain intentional pass-through geometry', () => {
|
|
1405
|
+
const d = load('sequence');
|
|
1406
|
+
const { code, stderr } = render('sequence', d);
|
|
1407
|
+
assert.equal(code, 0, stderr);
|
|
1408
|
+
assert.doesNotMatch(stderr, /Clean Flow Gate/);
|
|
1409
|
+
});
|
|
1410
|
+
|
|
1411
|
+
test('sequence: segment titles render as foreground badges above their borders', () => {
|
|
1412
|
+
const d = load('sequence');
|
|
1413
|
+
const { code, stderr, outPath } = render('sequence', d);
|
|
1414
|
+
assert.equal(code, 0, stderr);
|
|
1415
|
+
const html = fs.readFileSync(outPath, 'utf8');
|
|
1416
|
+
const firstSegment = d.segments[0];
|
|
1417
|
+
const segmentLabelsAt = html.indexOf('<!-- Segment Labels -->');
|
|
1418
|
+
const activationsAt = html.indexOf('<!-- Activations -->');
|
|
1419
|
+
const messagesAt = html.indexOf('<!-- Messages -->');
|
|
1420
|
+
|
|
1421
|
+
assert.ok(segmentLabelsAt > activationsAt, 'segment labels should stay above lifelines, messages, and activations');
|
|
1422
|
+
assert.ok(messagesAt > activationsAt, 'message arrows and labels should stay above activation bars');
|
|
1423
|
+
assert.match(html, new RegExp(`data-graph-role="segment-label"[^>]*data-segment-id="0"`));
|
|
1424
|
+
assert.match(html, new RegExp(`<text x="62" y="${firstSegment.from - 9}"[^>]*>${firstSegment.label}</text>`));
|
|
1425
|
+
});
|
|
1426
|
+
|
|
1427
|
+
test('sequence: segment title badge clears a nearby first message label', () => {
|
|
1428
|
+
const d = load('sequence');
|
|
1429
|
+
const firstSegment = d.segments[0];
|
|
1430
|
+
const firstMessage = d.messages[0];
|
|
1431
|
+
firstSegment.from = 180;
|
|
1432
|
+
firstMessage.y = firstSegment.from + 5;
|
|
1433
|
+
|
|
1434
|
+
const { code, stderr, outPath } = render('sequence', d);
|
|
1435
|
+
assert.equal(code, 0, stderr);
|
|
1436
|
+
const html = fs.readFileSync(outPath, 'utf8');
|
|
1437
|
+
const segment = html.match(
|
|
1438
|
+
/<g data-graph-role="segment-label" data-segment-id="0">\s*<rect x="([^"]+)" y="([^"]+)" width="([^"]+)" height="([^"]+)"/,
|
|
1439
|
+
);
|
|
1440
|
+
const message = html.match(new RegExp(
|
|
1441
|
+
`<g [^>]*data-edge-id="${firstMessage.id}"[\\s\\S]*?<rect x="([^"]+)" y="([^"]+)" width="([^"]+)" height="([^"]+)"`,
|
|
1442
|
+
));
|
|
1443
|
+
assert.ok(segment, 'expected the first segment badge rectangle');
|
|
1444
|
+
assert.ok(message, 'expected the first message label rectangle');
|
|
1445
|
+
|
|
1446
|
+
const [segmentX, segmentY, segmentW, segmentH] = segment.slice(1).map(Number);
|
|
1447
|
+
const [messageX, messageY, messageW, messageH] = message.slice(1).map(Number);
|
|
1448
|
+
const overlaps = segmentX < messageX + messageW
|
|
1449
|
+
&& segmentX + segmentW > messageX
|
|
1450
|
+
&& segmentY < messageY + messageH
|
|
1451
|
+
&& segmentY + segmentH > messageY;
|
|
1452
|
+
assert.equal(overlaps, false, 'segment title badge must not cover the first message label');
|
|
1453
|
+
});
|
|
1454
|
+
|
|
1455
|
+
test('sequence: segment label exceeding segment frame available width fails layout validation with remediation', () => {
|
|
1456
|
+
const d = load('sequence');
|
|
1457
|
+
d.meta.viewBox[0] = 820;
|
|
1458
|
+
d.meta.column_fit = 'fixed';
|
|
1459
|
+
d.segments[0].label = 'Phase 1: Dual-Certificate mTLS Handshake & Distributed Token Verification Protocol Negotiation'.repeat(2);
|
|
1460
|
+
|
|
1461
|
+
const { code, stderr } = render('sequence', d);
|
|
1462
|
+
assert.equal(code, 1);
|
|
1463
|
+
assert.match(stderr, /Segment "Phase 1: Dual-Certificate mTLS Handshake & Distributed Token Verification Protocol NegotiationPhase 1: Dual-Certificate mTLS Handshake & Distributed Token Verification Protocol Negotiation" label \(~992px\) exceeds the segment frame's available width \(716px\) — shorten the label or increase meta\.viewBox\[0\] to at least 1096\./);
|
|
1464
|
+
|
|
1465
|
+
// Remediating by increasing viewBox[0] to the suggested minimum allows it to pass cleanly
|
|
1466
|
+
d.meta.viewBox[0] = 1096;
|
|
1467
|
+
const fixed = render('sequence', d);
|
|
1468
|
+
assert.equal(fixed.code, 0, fixed.stderr);
|
|
1469
|
+
});
|
|
1470
|
+
|
|
1471
|
+
test('sequence: segment label boundary containment within canvas vs segment frame and exact-fit', () => {
|
|
1472
|
+
// Available width in this fixed-column boundary scenario (viewBox[0] = 820):
|
|
1473
|
+
// frame right edge = 820 - 48 = 772
|
|
1474
|
+
// labelBox.x = 56
|
|
1475
|
+
// availableWidth = 772 - 56 = 716px
|
|
1476
|
+
// 135 ASCII units -> labelW = 135 * 5.2 + 14 = 716px -> label right edge = 56 + 716 = 772px (exact fit)
|
|
1477
|
+
// 136 ASCII units -> labelW = 136 * 5.2 + 14 = 721.2px -> label right edge = 56 + 721.2 = 777.2px
|
|
1478
|
+
// (777.2px <= 820 canvas width, but > 772 segment frame edge)
|
|
1479
|
+
|
|
1480
|
+
// 1. Fits within canvas (777.2 <= 820) but exceeds segment frame (777.2 > 772)
|
|
1481
|
+
const dExceed = load('sequence');
|
|
1482
|
+
dExceed.meta.viewBox[0] = 820;
|
|
1483
|
+
dExceed.meta.column_fit = 'fixed';
|
|
1484
|
+
dExceed.segments[0].label = 'A'.repeat(136);
|
|
1485
|
+
const resExceed = render('sequence', dExceed);
|
|
1486
|
+
assert.equal(resExceed.code, 1, 'label exceeding segment frame must fail even if within canvas');
|
|
1487
|
+
assert.match(resExceed.stderr, /label \(~721px\) exceeds the segment frame's available width \(716px\) — shorten the label or increase meta\.viewBox\[0\] to at least 826\./);
|
|
1488
|
+
|
|
1489
|
+
// 2. Exact-fit case at the frame's right edge (56 + 716 = 772 === 820 - 48)
|
|
1490
|
+
const dExact = load('sequence');
|
|
1491
|
+
dExact.meta.viewBox[0] = 820;
|
|
1492
|
+
dExact.meta.column_fit = 'fixed';
|
|
1493
|
+
dExact.segments[0].label = 'A'.repeat(135);
|
|
1494
|
+
const resExact = render('sequence', dExact);
|
|
1495
|
+
assert.equal(resExact.code, 0, `exact-fit label at segment frame boundary must pass cleanly: ${resExact.stderr}`);
|
|
1496
|
+
});
|
|
1497
|
+
|
|
1498
|
+
process.on('exit', () => fs.rmSync(tmp, { recursive: true, force: true }));
|