procesio-cli 0.1.1__py3-none-any.whl
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.
- procesio_cli/__init__.py +18 -0
- procesio_cli/_cli.py +71 -0
- procesio_cli/agents/__init__.py +0 -0
- procesio_cli/agents/_lib/__init__.py +0 -0
- procesio_cli/agents/_lib/actiondef.py +24 -0
- procesio_cli/agents/_lib/errors.py +40 -0
- procesio_cli/agents/_lib/reversibility.py +236 -0
- procesio_cli/agents/_lib/toolrunner.py +141 -0
- procesio_cli/agents/connector-builder/CONNECTOR-BUILDER-PLAYBOOK.md +62 -0
- procesio_cli/agents/connector-builder/CONNECTOR-BUILDER-PROCESIO-INTEROP.md +76 -0
- procesio_cli/agents/connector-builder/CONNECTOR-BUILDER-TROUBLESHOOTING.md +60 -0
- procesio_cli/agents/connector-builder/README.md +51 -0
- procesio_cli/agents/connector-builder/agent.yaml +61 -0
- procesio_cli/agents/connector-builder/context.py +19 -0
- procesio_cli/agents/connector-builder/handlers/__init__.py +1 -0
- procesio_cli/agents/connector-builder/handlers/checklist.py +71 -0
- procesio_cli/agents/connector-builder/handlers/guidance.py +37 -0
- procesio_cli/agents/connector-builder/handlers/nextstep.py +221 -0
- procesio_cli/agents/connector-builder/handlers/status.py +35 -0
- procesio_cli/agents/connector-builder/knowledge.py +47 -0
- procesio_cli/agents/connector-builder/main.py +93 -0
- procesio_cli/agents/connector-builder/tests/conftest.py +95 -0
- procesio_cli/agents/connector-builder/tests/test_cba_agent.py +148 -0
- procesio_cli/agents/orchestrator/__init__.py +0 -0
- procesio_cli/agents/orchestrator/bootstraplib.py +166 -0
- procesio_cli/agents/procesio/IMPLEMENTATION-PATTERNS/00-INDEX.md +60 -0
- procesio_cli/agents/procesio/IMPLEMENTATION-PATTERNS/01-PROCESSES.md +230 -0
- procesio_cli/agents/procesio/IMPLEMENTATION-PATTERNS/02-FORMS.md +287 -0
- procesio_cli/agents/procesio/IMPLEMENTATION-PATTERNS/03-INTEGRATIONS.md +384 -0
- procesio_cli/agents/procesio/IMPLEMENTATION-PATTERNS/04-DATABASE.md +293 -0
- procesio_cli/agents/procesio/PROCESIO-API-RELIABILITY-DOCTRINE.md +113 -0
- procesio_cli/agents/procesio/PROCESIO-BEST-PRACTICES.md +316 -0
- procesio_cli/agents/procesio/PROCESIO-BUILD-AND-TEST-PLAYBOOK.md +217 -0
- procesio_cli/agents/procesio/PROCESIO-DATASTORE.md +72 -0
- procesio_cli/agents/procesio/PROCESIO-ENVIRONMENTS.md +62 -0
- procesio_cli/agents/procesio/PROCESIO-SCHEDULING.md +47 -0
- procesio_cli/agents/procesio/PROCESIO-VISUAL-ORGANIZATION.md +175 -0
- procesio_cli/agents/procesio/README.md +97 -0
- procesio_cli/agents/procesio/__init__.py +0 -0
- procesio_cli/agents/procesio/agent.yaml +73 -0
- procesio_cli/agents/procesio/audit.py +226 -0
- procesio_cli/agents/procesio/checklist.py +135 -0
- procesio_cli/agents/procesio/context.py +19 -0
- procesio_cli/agents/procesio/handlers/__init__.py +0 -0
- procesio_cli/agents/procesio/handlers/audit.py +39 -0
- procesio_cli/agents/procesio/handlers/checklist.py +34 -0
- procesio_cli/agents/procesio/handlers/guidance.py +36 -0
- procesio_cli/agents/procesio/handlers/status.py +33 -0
- procesio_cli/agents/procesio/handlers/verify.py +39 -0
- procesio_cli/agents/procesio/knowledge.py +115 -0
- procesio_cli/agents/procesio/main.py +89 -0
- procesio_cli/agents/procesio/tests/__init__.py +0 -0
- procesio_cli/agents/procesio/tests/conftest.py +62 -0
- procesio_cli/agents/procesio/tests/test_audit.py +91 -0
- procesio_cli/agents/procesio/tests/test_checklist.py +28 -0
- procesio_cli/agents/procesio/tests/test_guidance.py +78 -0
- procesio_cli/agents/procesio/tests/test_manifest_sync.py +48 -0
- procesio_cli/agents/procesio/tests/test_verify.py +134 -0
- procesio_cli/agents/procesio/tests/test_workspace_scope.py +97 -0
- procesio_cli/agents/procesio/verifylib.py +222 -0
- procesio_cli/dashboard/README.md +117 -0
- procesio_cli/dashboard/__init__.py +18 -0
- procesio_cli/dashboard/serve.py +98 -0
- procesio_cli/dashboard/server/__init__.py +1 -0
- procesio_cli/dashboard/server/config_schemas.py +92 -0
- procesio_cli/dashboard/server/google.py +103 -0
- procesio_cli/dashboard/server/handler.py +160 -0
- procesio_cli/dashboard/server/inventory.py +213 -0
- procesio_cli/dashboard/server/jobs.py +118 -0
- procesio_cli/dashboard/server/llmtest.py +181 -0
- procesio_cli/dashboard/server/procesio_env.py +94 -0
- procesio_cli/dashboard/server/routes.py +74 -0
- procesio_cli/dashboard/server/runner.py +150 -0
- procesio_cli/dashboard/server/security.py +29 -0
- procesio_cli/dashboard/server/setup.py +211 -0
- procesio_cli/dashboard/server/validate.py +152 -0
- procesio_cli/dashboard/tests/conftest.py +16 -0
- procesio_cli/dashboard/tests/test_dashboard.py +225 -0
- procesio_cli/dashboard/tests/test_procesio_env.py +159 -0
- procesio_cli/dashboard/web/actions.js +274 -0
- procesio_cli/dashboard/web/api.js +79 -0
- procesio_cli/dashboard/web/app.js +385 -0
- procesio_cli/dashboard/web/copilot.js +72 -0
- procesio_cli/dashboard/web/google.js +121 -0
- procesio_cli/dashboard/web/index.html +67 -0
- procesio_cli/dashboard/web/procesio_env.js +187 -0
- procesio_cli/dashboard/web/styles.css +207 -0
- procesio_cli/registry.py +370 -0
- procesio_cli/scripts/build-router.py +249 -0
- procesio_cli/scripts/build-tool-skill.py +202 -0
- procesio_cli/scripts/check-requirements.py +131 -0
- procesio_cli/scripts/check-skill-governance.py +174 -0
- procesio_cli/scripts/evaluate-skill-routing.py +217 -0
- procesio_cli/scripts/evaluate-skills.py +316 -0
- procesio_cli/scripts/get-skill.py +122 -0
- procesio_cli/scripts/list-skills.py +68 -0
- procesio_cli/scripts/list-tools.py +73 -0
- procesio_cli/scripts/run-agent.py +59 -0
- procesio_cli/scripts/run-tool.py +123 -0
- procesio_cli/scripts/secret_scan.py +584 -0
- procesio_cli/scripts/set-credential.py +126 -0
- procesio_cli/scripts/validate-skills.py +530 -0
- procesio_cli/skills/PORTFOLIO.md +134 -0
- procesio_cli/skills/__init__.py +6 -0
- procesio_cli/skills/agent-skill-engineer/SKILL.md +226 -0
- procesio_cli/skills/agent-skill-engineer/evals/evals.json +422 -0
- procesio_cli/skills/agent-skill-engineer/references/authoring-standard.md +178 -0
- procesio_cli/skills/agent-skill-engineer/references/decision-framework.md +107 -0
- procesio_cli/skills/agent-skill-engineer/references/domain-evidence-protocol.md +248 -0
- procesio_cli/skills/agent-skill-engineer/references/evaluation-standard.md +191 -0
- procesio_cli/skills/agent-skill-engineer/references/expert-lenses.md +167 -0
- procesio_cli/skills/agent-skill-engineer/references/field-gate-standard.md +231 -0
- procesio_cli/skills/agent-skill-engineer/references/optimization-protocol.md +233 -0
- procesio_cli/skills/agent-skill-engineer/references/optimizer-contract.md +268 -0
- procesio_cli/skills/agent-skill-engineer/references/portability-standard.md +99 -0
- procesio_cli/skills/agent-skill-engineer/references/research-basis.md +128 -0
- procesio_cli/skills/agent-skill-engineer/references/review-standard.md +143 -0
- procesio_cli/skills/agent-skill-engineer/scripts/audit_skill.py +959 -0
- procesio_cli/skills/agent-skill-engineer/scripts/optimize_skill.py +891 -0
- procesio_cli/skills/agent-skill-engineer/scripts/scaffold_skill.py +397 -0
- procesio_cli/skills/evals/baseline-catalog.json +14 -0
- procesio_cli/skills/evals/baseline-routing-v2.json +38 -0
- procesio_cli/skills/evals/baseline.json +37 -0
- procesio_cli/skills/evals/behavioral.json +367 -0
- procesio_cli/skills/evals/field-boundary-development-v1.json +126 -0
- procesio_cli/skills/evals/field-closeout-correction-development-v1.json +23 -0
- procesio_cli/skills/evals/field-closeout-development-v1.json +89 -0
- procesio_cli/skills/evals/portfolio-pressure-training-v1.json +83 -0
- procesio_cli/skills/evals/routing.json +363 -0
- procesio_cli/skills/evals/validation-baseline.json +4 -0
- procesio_cli/skills/procesio-cli/SKILL.md +134 -0
- procesio_cli/skills/procesio-cli/evals/evals.json +111 -0
- procesio_cli/skills/procesio-cli/evals/workflows.json +16 -0
- procesio_cli/skills/procesio-cli/references/connector-lifecycle.md +47 -0
- procesio_cli/skills/procesio-cli/references/credentials-admin.md +113 -0
- procesio_cli/skills/procesio-cli/references/data-verification.md +71 -0
- procesio_cli/skills/procesio-cli/references/documents-files.md +44 -0
- procesio_cli/skills/procesio-cli/references/form-e2e.md +57 -0
- procesio_cli/skills/procesio-cli/references/operation-contract.md +138 -0
- procesio_cli/skills/procesio-cli/references/process-debugging.md +65 -0
- procesio_cli/skills/procesio-cli/references/process-lifecycle.md +88 -0
- procesio_cli/skills/procesio-cli/references/schedules-webhooks.md +58 -0
- procesio_cli/skills/procesio-cli/references/transport-environments.md +53 -0
- procesio_cli/skills/procesio-cli-maintainer/SKILL.md +84 -0
- procesio_cli/skills/procesio-cli-maintainer/evals/evals.json +116 -0
- procesio_cli/skills/procesio-cli-maintainer/references/change-contract.md +34 -0
- procesio_cli/skills/procesio-cli-maintainer/references/mcp-compatibility.md +13 -0
- procesio_cli/skills/procesio-cli-maintainer/references/skill-authoring.md +51 -0
- procesio_cli/skills/procesio-platform-advisor/SKILL.md +82 -0
- procesio_cli/skills/procesio-platform-advisor/evals/evals.json +116 -0
- procesio_cli/skills/procesio-platform-advisor/references/capability-map.md +22 -0
- procesio_cli/skills/procesio-platform-advisor/references/fit-framework.md +35 -0
- procesio_cli/skills/procesio-platform-advisor/references/freshness-policy.md +25 -0
- procesio_cli/skills/procesio-platform-advisor/references/sizing-method.md +70 -0
- procesio_cli/skills/procesio-platform-advisor/scripts/estimate_capacity.py +99 -0
- procesio_cli/skills/procesio-setup/SKILL.md +180 -0
- procesio_cli/skills/sql-server-optimizer/SKILL.md +86 -0
- procesio_cli/skills/sql-server-optimizer/evals/evals.json +116 -0
- procesio_cli/skills/sql-server-optimizer/references/db-context.md +44 -0
- procesio_cli/skills/sql-server-optimizer/references/generic-mode.md +32 -0
- procesio_cli/skills/sql-server-optimizer/references/procesio-mode.md +40 -0
- procesio_cli/skills/sql-server-optimizer/references/sql-rules.md +43 -0
- procesio_cli/skills/sql-server-optimizer/scripts/export-indexes.sql +178 -0
- procesio_cli/skills/sql-server-optimizer/scripts/export-procs-and-functions.sql +118 -0
- procesio_cli/skills/sql-server-optimizer/scripts/export-tables.sql +367 -0
- procesio_cli/skills/tests/__init__.py +0 -0
- procesio_cli/skills/tests/test_agent_skill_engineer.py +240 -0
- procesio_cli/skills/tests/test_auth_failure_stop.py +25 -0
- procesio_cli/skills/tests/test_capacity_estimator.py +85 -0
- procesio_cli/skills/tests/test_field_boundary_learning.py +100 -0
- procesio_cli/skills/tests/test_field_closeout_learning.py +123 -0
- procesio_cli/skills/tests/test_field_gate_standard.py +76 -0
- procesio_cli/skills/tests/test_operational_playbooks.py +51 -0
- procesio_cli/skills/tests/test_portfolio_pressure_training.py +35 -0
- procesio_cli/skills/tests/test_process_lifecycle_single_run.py +25 -0
- procesio_cli/skills/tests/test_skill_evaluator.py +67 -0
- procesio_cli/skills/tests/test_skill_optimizer.py +473 -0
- procesio_cli/skills/tests/test_skill_portfolio_review.py +70 -0
- procesio_cli/skills/tests/test_skill_release_binding.py +204 -0
- procesio_cli/skills/tests/test_skill_routing.py +74 -0
- procesio_cli/skills/tests/test_skill_scaffold_v2.py +116 -0
- procesio_cli/skills/tests/test_validate_skills.py +115 -0
- procesio_cli/tools/__init__.py +0 -0
- procesio_cli/tools/_lib/README.md +119 -0
- procesio_cli/tools/_lib/__init__.py +0 -0
- procesio_cli/tools/_lib/accounting.py +120 -0
- procesio_cli/tools/_lib/contacts.py +544 -0
- procesio_cli/tools/_lib/creds.py +83 -0
- procesio_cli/tools/_lib/creds_backends.py +578 -0
- procesio_cli/tools/_lib/dbprofiles.py +147 -0
- procesio_cli/tools/_lib/dbquery.py +286 -0
- procesio_cli/tools/_lib/dbschema.py +225 -0
- procesio_cli/tools/_lib/io.py +54 -0
- procesio_cli/tools/_lib/manifest.py +297 -0
- procesio_cli/tools/_lib/skill_release.py +127 -0
- procesio_cli/tools/_lib/skill_resources.py +118 -0
- procesio_cli/tools/_lib/userdata.py +228 -0
- procesio_cli/tools/_lib/webserialize/__init__.py +16 -0
- procesio_cli/tools/_lib/webserialize/admin.py +61 -0
- procesio_cli/tools/_lib/webserialize/client.py +264 -0
- procesio_cli/tools/_lib/webserialize/config.py +96 -0
- procesio_cli/tools/_lib/webserialize/errors.py +27 -0
- procesio_cli/tools/_lib/webserialize/framing.py +45 -0
- procesio_cli/tools/_lib/webserialize/registry.py +47 -0
- procesio_cli/tools/_lib/webserialize/runtime.py +61 -0
- procesio_cli/tools/_lib/webserialize/server.py +510 -0
- procesio_cli/tools/_lib/websession.py +194 -0
- procesio_cli/tools/connector-builder/CONNECTOR-BUILDER-API-NOTES.md +91 -0
- procesio_cli/tools/connector-builder/README.md +101 -0
- procesio_cli/tools/connector-builder/SKILL.md +248 -0
- procesio_cli/tools/connector-builder/__init__.py +7 -0
- procesio_cli/tools/connector-builder/actiondef.py +24 -0
- procesio_cli/tools/connector-builder/client.py +251 -0
- procesio_cli/tools/connector-builder/errors.py +44 -0
- procesio_cli/tools/connector-builder/gen_manifest.py +142 -0
- procesio_cli/tools/connector-builder/handlers/__init__.py +1 -0
- procesio_cli/tools/connector-builder/handlers/admin.py +182 -0
- procesio_cli/tools/connector-builder/handlers/auth.py +74 -0
- procesio_cli/tools/connector-builder/handlers/builds.py +396 -0
- procesio_cli/tools/connector-builder/handlers/common.py +104 -0
- procesio_cli/tools/connector-builder/handlers/files.py +111 -0
- procesio_cli/tools/connector-builder/handlers/raw.py +38 -0
- procesio_cli/tools/connector-builder/handlers/telemetry.py +39 -0
- procesio_cli/tools/connector-builder/main.py +94 -0
- procesio_cli/tools/connector-builder/tests/conftest.py +146 -0
- procesio_cli/tools/connector-builder/tests/test_cb_client.py +86 -0
- procesio_cli/tools/connector-builder/tests/test_cb_dispatch.py +105 -0
- procesio_cli/tools/connector-builder/tests/test_cb_manifest_sync.py +76 -0
- procesio_cli/tools/connector-builder/tests/test_cb_wait.py +123 -0
- procesio_cli/tools/connector-builder/tool.yaml +671 -0
- procesio_cli/tools/framework-map/.gitattributes +2 -0
- procesio_cli/tools/framework-map/README.md +98 -0
- procesio_cli/tools/framework-map/SKILL.md +30 -0
- procesio_cli/tools/framework-map/app.js +188 -0
- procesio_cli/tools/framework-map/fm_builder.py +476 -0
- procesio_cli/tools/framework-map/fm_catalog.py +19 -0
- procesio_cli/tools/framework-map/fm_ro_catalog.py +94 -0
- procesio_cli/tools/framework-map/fm_strings.py +101 -0
- procesio_cli/tools/framework-map/main.py +95 -0
- procesio_cli/tools/framework-map/styles.css +214 -0
- procesio_cli/tools/framework-map/tests/conftest.py +33 -0
- procesio_cli/tools/framework-map/tests/test_fm_build.py +126 -0
- procesio_cli/tools/framework-map/tests/test_fm_manifest_sync.py +63 -0
- procesio_cli/tools/framework-map/tests/test_fm_store_tree.py +75 -0
- procesio_cli/tools/framework-map/tool.yaml +61 -0
- procesio_cli/tools/mysql/MOVED-TO-CENTRAL.md +29 -0
- procesio_cli/tools/mysql/README.md +196 -0
- procesio_cli/tools/mysql/SKILL.md +37 -0
- procesio_cli/tools/mysql/__init__.py +0 -0
- procesio_cli/tools/mysql/connection.py +191 -0
- procesio_cli/tools/mysql/main.py +487 -0
- procesio_cli/tools/mysql/profiles.template.json +1 -0
- procesio_cli/tools/mysql/schema.py +434 -0
- procesio_cli/tools/mysql/tests/__init__.py +0 -0
- procesio_cli/tools/mysql/tests/conftest.py +21 -0
- procesio_cli/tools/mysql/tests/test_manifest_sync.py +68 -0
- procesio_cli/tools/mysql/tests/test_mysql.py +201 -0
- procesio_cli/tools/mysql/tests/test_mysql_schema.py +337 -0
- procesio_cli/tools/mysql/tool.yaml +112 -0
- procesio_cli/tools/procesio/DTO-SUBTOOLS-NOTE.md +360 -0
- procesio_cli/tools/procesio/FORM-DEV-GUIDE/00-INDEX.md +67 -0
- procesio_cli/tools/procesio/FORM-DEV-GUIDE/01-ANATOMY.md +209 -0
- procesio_cli/tools/procesio/FORM-DEV-GUIDE/02-CODE-INJECTION.md +309 -0
- procesio_cli/tools/procesio/FORM-DEV-GUIDE/03-DOM-CONTRACT.md +370 -0
- procesio_cli/tools/procesio/FORM-DEV-GUIDE/04-INTERACTION-RECIPES.md +600 -0
- procesio_cli/tools/procesio/FORM-DEV-GUIDE/05-STEPPER-AND-MOTION.md +426 -0
- procesio_cli/tools/procesio/FORM-DEV-GUIDE/06-PROCESS-INTEGRATION.md +347 -0
- procesio_cli/tools/procesio/FORM-DEV-GUIDE/07-DEPLOY-WORKFLOW.md +294 -0
- procesio_cli/tools/procesio/FORM-DEV-GUIDE/08-PITFALLS.md +569 -0
- procesio_cli/tools/procesio/FORM-DEV-GUIDE/09-NAVIGATION-AND-HOSTING.md +308 -0
- procesio_cli/tools/procesio/PHASE4-E2E-NOTES.md +594 -0
- procesio_cli/tools/procesio/PROCESIO-API-CORRECTIONS.md +295 -0
- procesio_cli/tools/procesio/PROCESIO-API-NOTES.md +6976 -0
- procesio_cli/tools/procesio/PROCESIO-AUTH-NOTES.md +226 -0
- procesio_cli/tools/procesio/PROCESIO-CARD-BUILD-NOTES.md +183 -0
- procesio_cli/tools/procesio/PROCESIO-CONNECTORS-NOTES.md +141 -0
- procesio_cli/tools/procesio/PROCESIO-CUSTOM-ACTION-NOTES.md +179 -0
- procesio_cli/tools/procesio/PROCESIO-DOCS-FIX-REPORT.md +123 -0
- procesio_cli/tools/procesio/PROCESIO-ENVIRONMENTS-NOTES.md +94 -0
- procesio_cli/tools/procesio/PROCESIO-FE-VALIDATION-NOTES.md +107 -0
- procesio_cli/tools/procesio/PROCESIO-FOREACH-NOTES.md +199 -0
- procesio_cli/tools/procesio/PROCESIO-FORM-API-HANG-NOTE.md +101 -0
- procesio_cli/tools/procesio/PROCESIO-FORM-SUBMISSION-NOTES.md +172 -0
- procesio_cli/tools/procesio/PROCESIO-HTML-TO-DOCX-NOTES.md +110 -0
- procesio_cli/tools/procesio/PROCESIO-INSTANCE-HISTORY-NOTES.md +242 -0
- procesio_cli/tools/procesio/PROCESIO-METERING-NOTES.md +201 -0
- procesio_cli/tools/procesio/PROCESIO-NODE-CODE-NOTES.md +279 -0
- procesio_cli/tools/procesio/PROCESIO-NODE-MODULE-WHITELIST.md +113 -0
- procesio_cli/tools/procesio/PROCESIO-PYTHON-ACTION-NOTES.md +165 -0
- procesio_cli/tools/procesio/PROCESIO-QUERY-STORE-NOTES.md +826 -0
- procesio_cli/tools/procesio/PROCESIO-RECONCILIATION-PATTERNS.md +104 -0
- procesio_cli/tools/procesio/PROCESIO-RESOURCE-MODEL-NOTES.md +1100 -0
- procesio_cli/tools/procesio/PROCESIO-SEND-EMAIL-CRED-OPTIONALITY.md +45 -0
- procesio_cli/tools/procesio/PROCESIO-SEND-EMAIL-NOTES.md +252 -0
- procesio_cli/tools/procesio/PROCESIO-SQL-ACTIONS-NOTES.md +284 -0
- procesio_cli/tools/procesio/PROCESIO-USAGE-GUIDE.md +219 -0
- procesio_cli/tools/procesio/README.md +341 -0
- procesio_cli/tools/procesio/SCHEDULE-INPUT-SECURITY-NOTES.md +63 -0
- procesio_cli/tools/procesio/SKILL.md +694 -0
- procesio_cli/tools/procesio/__init__.py +1 -0
- procesio_cli/tools/procesio/actiondef.py +27 -0
- procesio_cli/tools/procesio/assets/html2docx/html2docx.js +2155 -0
- procesio_cli/tools/procesio/auth.py +354 -0
- procesio_cli/tools/procesio/autogen.py +121 -0
- procesio_cli/tools/procesio/client.py +355 -0
- procesio_cli/tools/procesio/config.py +96 -0
- procesio_cli/tools/procesio/data/endpoints.json +2901 -0
- procesio_cli/tools/procesio/docs_info/API-DOCUMENTATION/01-authentication.md +180 -0
- procesio_cli/tools/procesio/docs_info/API-DOCUMENTATION/02-conventions.md +116 -0
- procesio_cli/tools/procesio/docs_info/API-DOCUMENTATION/ANONYMOUS-SURFACE.md +51 -0
- procesio_cli/tools/procesio/docs_info/API-DOCUMENTATION/README.md +104 -0
- procesio_cli/tools/procesio/docs_info/API-DOCUMENTATION/SYMBOL-INDEX.md +444 -0
- procesio_cli/tools/procesio/docs_info/API-DOCUMENTATION/endpoints/03-auth-users-workspaces.md +731 -0
- procesio_cli/tools/procesio/docs_info/API-DOCUMENTATION/endpoints/04-processes.md +913 -0
- procesio_cli/tools/procesio/docs_info/API-DOCUMENTATION/endpoints/05-actions.md +605 -0
- procesio_cli/tools/procesio/docs_info/API-DOCUMENTATION/endpoints/06-files.md +237 -0
- procesio_cli/tools/procesio/docs_info/API-DOCUMENTATION/endpoints/07-forms-documents.md +985 -0
- procesio_cli/tools/procesio/docs_info/API-DOCUMENTATION/endpoints/08-credentials.md +532 -0
- procesio_cli/tools/procesio/docs_info/API-DOCUMENTATION/endpoints/09-datastore-datatypes.md +830 -0
- procesio_cli/tools/procesio/docs_info/API-DOCUMENTATION/endpoints/10-custom-urls.md +319 -0
- procesio_cli/tools/procesio/docs_info/API-DOCUMENTATION/endpoints/11-subscriptions-resources-analytics.md +455 -0
- procesio_cli/tools/procesio/docs_info/API-DOCUMENTATION/endpoints/12-webhooks-notifications-misc.md +519 -0
- procesio_cli/tools/procesio/docs_info/API-DOCUMENTATION/endpoints/13-realtime-signalr.md +153 -0
- procesio_cli/tools/procesio/docs_info/API-DOCUMENTATION/endpoints/99-internal-and-other-services.md +52 -0
- procesio_cli/tools/procesio/docs_info/FormBuilder mock.ts +5725 -0
- procesio_cli/tools/procesio/docs_info/Procesio Platform Actions.json +30219 -0
- procesio_cli/tools/procesio/docs_info/form-config-enums.ts +92 -0
- procesio_cli/tools/procesio/docs_info/form-theme.ts +1603 -0
- procesio_cli/tools/procesio/docs_info/process-validation-reference-1.md +2739 -0
- procesio_cli/tools/procesio/dto/__init__.py +13 -0
- procesio_cli/tools/procesio/dto/credential/builder.py +179 -0
- procesio_cli/tools/procesio/dto/credential/config.schema.json +19 -0
- procesio_cli/tools/procesio/dto/credential/description.md +193 -0
- procesio_cli/tools/procesio/dto/credential/fixtures/rest.config.json +11 -0
- procesio_cli/tools/procesio/dto/credential/fixtures/rest.dto.json +28 -0
- procesio_cli/tools/procesio/dto/credential/fixtures/rest.template.json +44 -0
- procesio_cli/tools/procesio/dto/credential/template.dto.json +16 -0
- procesio_cli/tools/procesio/dto/data/_refresh.py +147 -0
- procesio_cli/tools/procesio/dto/data/action_catalog.json +30825 -0
- procesio_cli/tools/procesio/dto/data/action_families.json +55 -0
- procesio_cli/tools/procesio/dto/data/platform_types.json +182 -0
- procesio_cli/tools/procesio/dto/datatype/builder.py +223 -0
- procesio_cli/tools/procesio/dto/datatype/config.schema.json +47 -0
- procesio_cli/tools/procesio/dto/datatype/description.md +85 -0
- procesio_cli/tools/procesio/dto/datatype/fixtures/customer.config.json +41 -0
- procesio_cli/tools/procesio/dto/datatype/fixtures/customer.dto.json +164 -0
- procesio_cli/tools/procesio/dto/datatype/template.dto.json +37 -0
- procesio_cli/tools/procesio/dto/document/builder.py +193 -0
- procesio_cli/tools/procesio/dto/document/config.schema.json +33 -0
- procesio_cli/tools/procesio/dto/document/description.md +124 -0
- procesio_cli/tools/procesio/dto/document/fixtures/invoice.config.json +16 -0
- procesio_cli/tools/procesio/dto/document/fixtures/invoice.dto.json +32 -0
- procesio_cli/tools/procesio/dto/document/template.dto.json +14 -0
- procesio_cli/tools/procesio/dto/form/FORM-STYLING-NOTES.md +142 -0
- procesio_cli/tools/procesio/dto/form/GOLDEN-DRIFT-NOTES.md +72 -0
- procesio_cli/tools/procesio/dto/form/RUN-PROCESS-RESULT-NOTES.md +80 -0
- procesio_cli/tools/procesio/dto/form/addelement.py +277 -0
- procesio_cli/tools/procesio/dto/form/builder.py +1009 -0
- procesio_cli/tools/procesio/dto/form/capture_goldens.py +106 -0
- procesio_cli/tools/procesio/dto/form/code_cipher.py +67 -0
- procesio_cli/tools/procesio/dto/form/config.schema.json +76 -0
- procesio_cli/tools/procesio/dto/form/data_shell.json +1 -0
- procesio_cli/tools/procesio/dto/form/description.md +346 -0
- procesio_cli/tools/procesio/dto/form/elements/approval.json +259 -0
- procesio_cli/tools/procesio/dto/form/elements/assignee.json +137 -0
- procesio_cli/tools/procesio/dto/form/elements/button.json +125 -0
- procesio_cli/tools/procesio/dto/form/elements/chart.json +341 -0
- procesio_cli/tools/procesio/dto/form/elements/chat.json +199 -0
- procesio_cli/tools/procesio/dto/form/elements/checkbox.json +153 -0
- procesio_cli/tools/procesio/dto/form/elements/column.json +77 -0
- procesio_cli/tools/procesio/dto/form/elements/columns.json +78 -0
- procesio_cli/tools/procesio/dto/form/elements/datetime-input.json +304 -0
- procesio_cli/tools/procesio/dto/form/elements/divider.json +54 -0
- procesio_cli/tools/procesio/dto/form/elements/dropdown.json +130 -0
- procesio_cli/tools/procesio/dto/form/elements/dynamic-table-row.json +253 -0
- procesio_cli/tools/procesio/dto/form/elements/file-upload.json +196 -0
- procesio_cli/tools/procesio/dto/form/elements/file-viewer.json +169 -0
- procesio_cli/tools/procesio/dto/form/elements/heading.json +79 -0
- procesio_cli/tools/procesio/dto/form/elements/icon.json +107 -0
- procesio_cli/tools/procesio/dto/form/elements/image.json +174 -0
- procesio_cli/tools/procesio/dto/form/elements/input.json +206 -0
- procesio_cli/tools/procesio/dto/form/elements/list.json +202 -0
- procesio_cli/tools/procesio/dto/form/elements/number-input.json +202 -0
- procesio_cli/tools/procesio/dto/form/elements/paragraph.json +79 -0
- procesio_cli/tools/procesio/dto/form/elements/radiobox.json +174 -0
- procesio_cli/tools/procesio/dto/form/elements/section.json +114 -0
- procesio_cli/tools/procesio/dto/form/elements/select.json +217 -0
- procesio_cli/tools/procesio/dto/form/elements/side-panel.json +146 -0
- procesio_cli/tools/procesio/dto/form/elements/signature-pad.json +141 -0
- procesio_cli/tools/procesio/dto/form/elements/static-table-row.json +84 -0
- procesio_cli/tools/procesio/dto/form/elements/step.json +105 -0
- procesio_cli/tools/procesio/dto/form/elements/stepper.json +126 -0
- procesio_cli/tools/procesio/dto/form/elements/tab.json +77 -0
- procesio_cli/tools/procesio/dto/form/elements/table.json +279 -0
- procesio_cli/tools/procesio/dto/form/elements/tabs.json +114 -0
- procesio_cli/tools/procesio/dto/form/elements/textarea.json +213 -0
- procesio_cli/tools/procesio/dto/form/enum_map.json +68 -0
- procesio_cli/tools/procesio/dto/form/fieldpath.py +284 -0
- procesio_cli/tools/procesio/dto/form/fixtures/reg.config.json +32 -0
- procesio_cli/tools/procesio/dto/form/fixtures/reg.elements.json +638 -0
- procesio_cli/tools/procesio/dto/form/sync_from_mock.py +386 -0
- procesio_cli/tools/procesio/dto/framework.py +125 -0
- procesio_cli/tools/procesio/dto/process/builder.py +1991 -0
- procesio_cli/tools/procesio/dto/process/config.schema.json +159 -0
- procesio_cli/tools/procesio/dto/process/description.md +218 -0
- procesio_cli/tools/procesio/dto/process/fixtures/greet.config.json +32 -0
- procesio_cli/tools/procesio/dto/process/fixtures/greet.dto.json +317 -0
- procesio_cli/tools/procesio/dto/process/naming.py +305 -0
- procesio_cli/tools/procesio/dto/process/normalize.py +74 -0
- procesio_cli/tools/procesio/dto/process/template.dto.json +32 -0
- procesio_cli/tools/procesio/dto/refdata.py +87 -0
- procesio_cli/tools/procesio/dto/registry.py +27 -0
- procesio_cli/tools/procesio/dto/webhook/builder.py +142 -0
- procesio_cli/tools/procesio/dto/webhook/config.schema.json +25 -0
- procesio_cli/tools/procesio/dto/webhook/description.md +92 -0
- procesio_cli/tools/procesio/dto/webhook/fixtures/order.config.json +10 -0
- procesio_cli/tools/procesio/dto/webhook/fixtures/order.dto.json +25 -0
- procesio_cli/tools/procesio/dto/webhook/template.dto.json +14 -0
- procesio_cli/tools/procesio/environments.py +264 -0
- procesio_cli/tools/procesio/errors.py +166 -0
- procesio_cli/tools/procesio/flowmodel/README.md +37 -0
- procesio_cli/tools/procesio/flowmodel/__init__.py +14 -0
- procesio_cli/tools/procesio/flowmodel/digest.py +223 -0
- procesio_cli/tools/procesio/flowmodel/families.py +34 -0
- procesio_cli/tools/procesio/flowmodel/fevalidation.py +1098 -0
- procesio_cli/tools/procesio/flowmodel/formdigest.py +232 -0
- procesio_cli/tools/procesio/flowmodel/inspect.py +78 -0
- procesio_cli/tools/procesio/flowmodel/model.py +82 -0
- procesio_cli/tools/procesio/flowmodel/nodeparam.py +871 -0
- procesio_cli/tools/procesio/flowmodel/reader.py +191 -0
- procesio_cli/tools/procesio/flowmodel/sqlparam.py +410 -0
- procesio_cli/tools/procesio/form_code_keys.py +50 -0
- procesio_cli/tools/procesio/formlint.py +292 -0
- procesio_cli/tools/procesio/gen_manifest.py +118 -0
- procesio_cli/tools/procesio/handlers/__init__.py +1 -0
- procesio_cli/tools/procesio/handlers/auth_actions.py +103 -0
- procesio_cli/tools/procesio/handlers/catalog.py +147 -0
- procesio_cli/tools/procesio/handlers/common.py +90 -0
- procesio_cli/tools/procesio/handlers/customaction_ops.py +146 -0
- procesio_cli/tools/procesio/handlers/datastore_ops.py +482 -0
- procesio_cli/tools/procesio/handlers/datatype_ops.py +275 -0
- procesio_cli/tools/procesio/handlers/dto_actions.py +258 -0
- procesio_cli/tools/procesio/handlers/environment_admin.py +146 -0
- procesio_cli/tools/procesio/handlers/fevalidate.py +236 -0
- procesio_cli/tools/procesio/handlers/files.py +141 -0
- procesio_cli/tools/procesio/handlers/flowgraph.py +229 -0
- procesio_cli/tools/procesio/handlers/flowlint.py +269 -0
- procesio_cli/tools/procesio/handlers/form_chains.py +336 -0
- procesio_cli/tools/procesio/handlers/form_code.py +277 -0
- procesio_cli/tools/procesio/handlers/form_element.py +199 -0
- procesio_cli/tools/procesio/handlers/form_events.py +441 -0
- procesio_cli/tools/procesio/handlers/form_update.py +270 -0
- procesio_cli/tools/procesio/handlers/formprocess.py +211 -0
- procesio_cli/tools/procesio/handlers/generic.py +135 -0
- procesio_cli/tools/procesio/handlers/layout.py +137 -0
- procesio_cli/tools/procesio/handlers/nodeparams.py +1018 -0
- procesio_cli/tools/procesio/handlers/process_layout.py +189 -0
- procesio_cli/tools/procesio/handlers/process_naming.py +213 -0
- procesio_cli/tools/procesio/handlers/processes.py +416 -0
- procesio_cli/tools/procesio/handlers/profile_admin.py +184 -0
- procesio_cli/tools/procesio/handlers/resource_ops.py +447 -0
- procesio_cli/tools/procesio/handlers/schedules.py +320 -0
- procesio_cli/tools/procesio/handlers/sqlactions.py +232 -0
- procesio_cli/tools/procesio/handlers/transport.py +463 -0
- procesio_cli/tools/procesio/handlers/usageguide.py +222 -0
- procesio_cli/tools/procesio/layout/README.md +196 -0
- procesio_cli/tools/procesio/layout/__init__.py +11 -0
- procesio_cli/tools/procesio/layout/adapter.py +736 -0
- procesio_cli/tools/procesio/layout/cycles.py +186 -0
- procesio_cli/tools/procesio/layout/dispatch.py +39 -0
- procesio_cli/tools/procesio/layout/elk/elk_runner.mjs +50 -0
- procesio_cli/tools/procesio/layout/elk/package-lock.json +21 -0
- procesio_cli/tools/procesio/layout/elk/package.json +11 -0
- procesio_cli/tools/procesio/layout/elk_engine.py +568 -0
- procesio_cli/tools/procesio/layout/engine.py +1311 -0
- procesio_cli/tools/procesio/layout/report.py +187 -0
- procesio_cli/tools/procesio/main.py +179 -0
- procesio_cli/tools/procesio/profiles.py +193 -0
- procesio_cli/tools/procesio/reliability.py +193 -0
- procesio_cli/tools/procesio/sqlformat.py +167 -0
- procesio_cli/tools/procesio/swagger.py +51 -0
- procesio_cli/tools/procesio/tests/__init__.py +0 -0
- procesio_cli/tools/procesio/tests/conftest.py +129 -0
- procesio_cli/tools/procesio/tests/test_auth.py +200 -0
- procesio_cli/tools/procesio/tests/test_autogen.py +78 -0
- procesio_cli/tools/procesio/tests/test_builder_attrpath_sqlbind.py +180 -0
- procesio_cli/tools/procesio/tests/test_builder_custom_action_catalog.py +72 -0
- procesio_cli/tools/procesio/tests/test_client.py +114 -0
- procesio_cli/tools/procesio/tests/test_client_reliability.py +139 -0
- procesio_cli/tools/procesio/tests/test_customaction.py +103 -0
- procesio_cli/tools/procesio/tests/test_customaction_test.py +80 -0
- procesio_cli/tools/procesio/tests/test_cycles.py +69 -0
- procesio_cli/tools/procesio/tests/test_datastore.py +378 -0
- procesio_cli/tools/procesio/tests/test_datastore_node.py +94 -0
- procesio_cli/tools/procesio/tests/test_dispatch.py +170 -0
- procesio_cli/tools/procesio/tests/test_document_page_size.py +19 -0
- procesio_cli/tools/procesio/tests/test_dto.py +734 -0
- procesio_cli/tools/procesio/tests/test_edit_dryrun_preview.py +96 -0
- procesio_cli/tools/procesio/tests/test_elk_layout.py +221 -0
- procesio_cli/tools/procesio/tests/test_environments.py +214 -0
- procesio_cli/tools/procesio/tests/test_fevalidate_gate.py +104 -0
- procesio_cli/tools/procesio/tests/test_fevalidation.py +317 -0
- procesio_cli/tools/procesio/tests/test_fieldpath_variables.py +87 -0
- procesio_cli/tools/procesio/tests/test_files.py +72 -0
- procesio_cli/tools/procesio/tests/test_flow_digest.py +202 -0
- procesio_cli/tools/procesio/tests/test_flowlint.py +114 -0
- procesio_cli/tools/procesio/tests/test_flowmodel.py +158 -0
- procesio_cli/tools/procesio/tests/test_form_add_element.py +256 -0
- procesio_cli/tools/procesio/tests/test_form_approval_gaps.py +319 -0
- procesio_cli/tools/procesio/tests/test_form_auto_names.py +60 -0
- procesio_cli/tools/procesio/tests/test_form_chains.py +217 -0
- procesio_cli/tools/procesio/tests/test_form_code_actions.py +188 -0
- procesio_cli/tools/procesio/tests/test_form_code_keys.py +92 -0
- procesio_cli/tools/procesio/tests/test_form_code_style.py +178 -0
- procesio_cli/tools/procesio/tests/test_form_delete_element.py +99 -0
- procesio_cli/tools/procesio/tests/test_form_element_config.py +141 -0
- procesio_cli/tools/procesio/tests/test_form_event_maps.py +135 -0
- procesio_cli/tools/procesio/tests/test_form_event_ops.py +376 -0
- procesio_cli/tools/procesio/tests/test_form_event_replace.py +86 -0
- procesio_cli/tools/procesio/tests/test_form_events.py +92 -0
- procesio_cli/tools/procesio/tests/test_form_parity.py +171 -0
- procesio_cli/tools/procesio/tests/test_form_select_source.py +74 -0
- procesio_cli/tools/procesio/tests/test_form_sync.py +35 -0
- procesio_cli/tools/procesio/tests/test_form_update.py +133 -0
- procesio_cli/tools/procesio/tests/test_formlint.py +155 -0
- procesio_cli/tools/procesio/tests/test_formlint_css_scope.py +38 -0
- procesio_cli/tools/procesio/tests/test_formlint_field_names.py +83 -0
- procesio_cli/tools/procesio/tests/test_guards.py +110 -0
- procesio_cli/tools/procesio/tests/test_hidden_setting_types.py +39 -0
- procesio_cli/tools/procesio/tests/test_html2docx_asset.py +132 -0
- procesio_cli/tools/procesio/tests/test_key_handle_terminology.py +45 -0
- procesio_cli/tools/procesio/tests/test_layout.py +574 -0
- procesio_cli/tools/procesio/tests/test_layout_report.py +88 -0
- procesio_cli/tools/procesio/tests/test_manifest_sync.py +68 -0
- procesio_cli/tools/procesio/tests/test_nodedelete.py +151 -0
- procesio_cli/tools/procesio/tests/test_nodeparam.py +826 -0
- procesio_cli/tools/procesio/tests/test_normalize.py +70 -0
- procesio_cli/tools/procesio/tests/test_payload_docs.py +86 -0
- procesio_cli/tools/procesio/tests/test_process_action_naming.py +170 -0
- procesio_cli/tools/procesio/tests/test_process_call_api_seed.py +46 -0
- procesio_cli/tools/procesio/tests/test_process_ext.py +373 -0
- procesio_cli/tools/procesio/tests/test_process_invariants.py +183 -0
- procesio_cli/tools/procesio/tests/test_process_layout.py +117 -0
- procesio_cli/tools/procesio/tests/test_process_naming.py +188 -0
- procesio_cli/tools/procesio/tests/test_process_query_store.py +172 -0
- procesio_cli/tools/procesio/tests/test_processinfo_variable.py +56 -0
- procesio_cli/tools/procesio/tests/test_profiles.py +127 -0
- procesio_cli/tools/procesio/tests/test_put_projects.py +41 -0
- procesio_cli/tools/procesio/tests/test_reliability.py +107 -0
- procesio_cli/tools/procesio/tests/test_removal_gate.py +95 -0
- procesio_cli/tools/procesio/tests/test_resource_ops.py +173 -0
- procesio_cli/tools/procesio/tests/test_save_validity.py +207 -0
- procesio_cli/tools/procesio/tests/test_schedule_cron_shape.py +111 -0
- procesio_cli/tools/procesio/tests/test_schedules.py +309 -0
- procesio_cli/tools/procesio/tests/test_sqlconvert.py +234 -0
- procesio_cli/tools/procesio/tests/test_sqlformat.py +83 -0
- procesio_cli/tools/procesio/tests/test_sqlparam.py +187 -0
- procesio_cli/tools/procesio/tests/test_superseded_actions.py +54 -0
- procesio_cli/tools/procesio/tests/test_surgical_concurrency.py +156 -0
- procesio_cli/tools/procesio/tests/test_tier4.py +123 -0
- procesio_cli/tools/procesio/tests/test_transport.py +147 -0
- procesio_cli/tools/procesio/tests/test_transport_data_stores.py +168 -0
- procesio_cli/tools/procesio/tests/test_transport_mapper_repair.py +150 -0
- procesio_cli/tools/procesio/tests/test_transport_webhook_strip.py +253 -0
- procesio_cli/tools/procesio/tests/test_usage_guide.py +96 -0
- procesio_cli/tools/procesio/tool.yaml +12486 -0
- procesio_cli/tools/sqlserver/MOVED-TO-CENTRAL.md +29 -0
- procesio_cli/tools/sqlserver/README.md +192 -0
- procesio_cli/tools/sqlserver/SKILL.md +37 -0
- procesio_cli/tools/sqlserver/SQLSERVER-SCHEMA-NOTES.md +284 -0
- procesio_cli/tools/sqlserver/__init__.py +0 -0
- procesio_cli/tools/sqlserver/connection.py +242 -0
- procesio_cli/tools/sqlserver/main.py +521 -0
- procesio_cli/tools/sqlserver/profiles.template.json +1 -0
- procesio_cli/tools/sqlserver/schema.py +600 -0
- procesio_cli/tools/sqlserver/tests/__init__.py +0 -0
- procesio_cli/tools/sqlserver/tests/conftest.py +21 -0
- procesio_cli/tools/sqlserver/tests/test_connection.py +75 -0
- procesio_cli/tools/sqlserver/tests/test_dbprofiles.py +92 -0
- procesio_cli/tools/sqlserver/tests/test_dbquery.py +220 -0
- procesio_cli/tools/sqlserver/tests/test_manifest_sync.py +68 -0
- procesio_cli/tools/sqlserver/tests/test_sqlserver.py +326 -0
- procesio_cli/tools/sqlserver/tests/test_sqlserver_schema.py +488 -0
- procesio_cli/tools/sqlserver/tool.yaml +123 -0
- procesio_cli/tools/web/README.md +381 -0
- procesio_cli/tools/web/SKILL.md +37 -0
- procesio_cli/tools/web/WEB-AUTOMATION-NOTES.md +277 -0
- procesio_cli/tools/web/__init__.py +21 -0
- procesio_cli/tools/web/actiondef.py +24 -0
- procesio_cli/tools/web/api.py +444 -0
- procesio_cli/tools/web/brokered.py +107 -0
- procesio_cli/tools/web/driver.py +839 -0
- procesio_cli/tools/web/drivercontract.py +44 -0
- procesio_cli/tools/web/errors.py +54 -0
- procesio_cli/tools/web/handlers/__init__.py +1 -0
- procesio_cli/tools/web/handlers/archive.py +367 -0
- procesio_cli/tools/web/handlers/browse.py +182 -0
- procesio_cli/tools/web/handlers/contacts.py +74 -0
- procesio_cli/tools/web/handlers/sessmgmt.py +120 -0
- procesio_cli/tools/web/main.py +128 -0
- procesio_cli/tools/web/sessions.py +253 -0
- procesio_cli/tools/web/steps.py +162 -0
- procesio_cli/tools/web/tests/__init__.py +0 -0
- procesio_cli/tools/web/tests/conftest.py +197 -0
- procesio_cli/tools/web/tests/test_api.py +330 -0
- procesio_cli/tools/web/tests/test_archive.py +190 -0
- procesio_cli/tools/web/tests/test_brokered.py +138 -0
- procesio_cli/tools/web/tests/test_dispatch.py +132 -0
- procesio_cli/tools/web/tests/test_errors.py +60 -0
- procesio_cli/tools/web/tests/test_manifest_sync.py +76 -0
- procesio_cli/tools/web/tests/test_page_contacts.py +68 -0
- procesio_cli/tools/web/tests/test_sessions.py +145 -0
- procesio_cli/tools/web/tests/test_steps.py +210 -0
- procesio_cli/tools/web/tool.yaml +184 -0
- procesio_cli/tools/xlsx/README.md +34 -0
- procesio_cli/tools/xlsx/SKILL.md +28 -0
- procesio_cli/tools/xlsx/__init__.py +0 -0
- procesio_cli/tools/xlsx/main.py +129 -0
- procesio_cli/tools/xlsx/reader.py +91 -0
- procesio_cli/tools/xlsx/tests/__init__.py +0 -0
- procesio_cli/tools/xlsx/tests/conftest.py +39 -0
- procesio_cli/tools/xlsx/tests/test_xlsx.py +103 -0
- procesio_cli/tools/xlsx/tool.yaml +30 -0
- procesio_cli/webplatform/aat_mcp/AAT-MCP-NOTES.md +244 -0
- procesio_cli/webplatform/aat_mcp/README.md +78 -0
- procesio_cli/webplatform/aat_mcp/bridge.py +705 -0
- procesio_cli/webplatform/aat_mcp/chat_server.py +194 -0
- procesio_cli/webplatform/aat_mcp/chat_surface.snapshot.json +668 -0
- procesio_cli/webplatform/aat_mcp/chat_surface.yaml +372 -0
- procesio_cli/webplatform/aat_mcp/gate.py +68 -0
- procesio_cli/webplatform/aat_mcp/http_server.py +139 -0
- procesio_cli/webplatform/aat_mcp/protocol.py +175 -0
- procesio_cli/webplatform/aat_mcp/server.py +306 -0
- procesio_cli/webplatform/aat_mcp/tests/conftest.py +8 -0
- procesio_cli/webplatform/aat_mcp/tests/test_aat_mcp.py +255 -0
- procesio_cli/webplatform/aat_mcp/tests/test_chat_surface.py +247 -0
- procesio_cli/webplatform/aat_mcp/tests/test_discovery.py +489 -0
- procesio_cli/webplatform/aat_mcp/tests/test_multiuser.py +86 -0
- procesio_cli/webplatform/aat_mcp/tests/test_progressive_discovery.py +89 -0
- procesio_cli/webplatform/aat_mcp/tests/test_protocol.py +180 -0
- procesio_cli/webplatform/aat_mcp/tests/test_skill_discovery.py +23 -0
- procesio_cli-0.1.1.dist-info/METADATA +467 -0
- procesio_cli-0.1.1.dist-info/RECORD +647 -0
- procesio_cli-0.1.1.dist-info/WHEEL +4 -0
- procesio_cli-0.1.1.dist-info/entry_points.txt +7 -0
- procesio_cli-0.1.1.dist-info/licenses/LICENSE +201 -0
- procesio_cli-0.1.1.dist-info/licenses/NOTICE +5 -0
|
@@ -0,0 +1,736 @@
|
|
|
1
|
+
"""Bridge the layout engine to the `.procesio` export / live flow DTO.
|
|
2
|
+
|
|
3
|
+
Reads a flow with the flow graph reader, runs the pure engine, and writes the new
|
|
4
|
+
positions back into each action's `CustomData.position` (and a For-Each area's
|
|
5
|
+
`areaSize`), resetting `CanvasData`. Case-insensitive for read AND write (PascalCase
|
|
6
|
+
exports / camelCase live). Returns a NEW object — never mutates the input.
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import copy
|
|
11
|
+
|
|
12
|
+
from tools.procesio.flowmodel import read_bundle, read_flow
|
|
13
|
+
from tools.procesio.layout import cycles, dispatch, engine, report
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _key(d: dict, *names: str):
|
|
17
|
+
"""The actual key present in `d` matching any of `names` (case-insensitive)."""
|
|
18
|
+
low = {str(k).lower(): k for k in d}
|
|
19
|
+
for n in names:
|
|
20
|
+
if n.lower() in low:
|
|
21
|
+
return low[n.lower()]
|
|
22
|
+
return None
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _get(d, *names, default=None):
|
|
26
|
+
k = _key(d, *names) if isinstance(d, dict) else None
|
|
27
|
+
return d[k] if k else default
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _flows(obj: dict):
|
|
31
|
+
k = _key(obj, "Flows")
|
|
32
|
+
return (obj[k], k) if k else (None, None)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _round(p: dict) -> dict:
|
|
36
|
+
return {"x": round(float(p["x"]), 2), "y": round(float(p["y"]), 2)}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _layout_score(bundle: dict, flow_id: str | None) -> float:
|
|
40
|
+
"""Lower = more readable. Hard issues dominate, then crossings, then long edges + overall
|
|
41
|
+
size. Uses the engine-agnostic readability report, so both engines are judged identically."""
|
|
42
|
+
rep = report.build_report(bundle, flow_id)
|
|
43
|
+
m = rep["metrics"]
|
|
44
|
+
return (rep["hard_issue_count"] * 10000.0 + m["crossings"] * 100.0
|
|
45
|
+
+ m["max_edge_px"] / 50.0 + (m["width"] + m["height"]) / 500.0)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _best_of_engines(obj: dict, flow_id: str | None = None,
|
|
49
|
+
only: list[str] | None = None) -> dict:
|
|
50
|
+
"""LAYOUT_ENGINE=auto: lay the flow out with BOTH engines and return whichever the
|
|
51
|
+
readability report scores better. Legacy runs with its opt-in cluster + min-crossings
|
|
52
|
+
layers; ELK uses its own built-in clustering. Neither engine is modified — this only
|
|
53
|
+
picks between their OUTPUTS, so 'what works' (e.g. the legacy retry loop-curl) is never
|
|
54
|
+
lost: it just wins the score where it's better. Falls back to legacy if ELK errors or
|
|
55
|
+
for `only`/subset mode (ELK has no partial layout)."""
|
|
56
|
+
import os
|
|
57
|
+
saved = os.environ.get("LAYOUT_ENGINE")
|
|
58
|
+
try:
|
|
59
|
+
os.environ["LAYOUT_ENGINE"] = dispatch.LEGACY
|
|
60
|
+
legacy = layout_flow(obj, flow_id=flow_id, only=only,
|
|
61
|
+
cluster=True, minimize_crossings=True)
|
|
62
|
+
elk = None
|
|
63
|
+
if only is None:
|
|
64
|
+
os.environ["LAYOUT_ENGINE"] = dispatch.ELK
|
|
65
|
+
try:
|
|
66
|
+
elk = layout_flow(obj, flow_id=flow_id)
|
|
67
|
+
except Exception: # noqa: BLE001
|
|
68
|
+
elk = None
|
|
69
|
+
finally:
|
|
70
|
+
if saved is None:
|
|
71
|
+
os.environ.pop("LAYOUT_ENGINE", None)
|
|
72
|
+
else:
|
|
73
|
+
os.environ["LAYOUT_ENGINE"] = saved
|
|
74
|
+
if elk is None:
|
|
75
|
+
return legacy
|
|
76
|
+
if _layout_score(elk["bundle"], elk["flow_id"]) < _layout_score(legacy["bundle"], legacy["flow_id"]):
|
|
77
|
+
return elk
|
|
78
|
+
return legacy
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _apply_fan_lanes(positions: dict, fg) -> dict:
|
|
82
|
+
"""The reference FAN pattern (hand-arranged entry spine): when a decisional's parallel alternative
|
|
83
|
+
branches all reconnect at the SAME merge, stack each branch as a flat LANE parallel to
|
|
84
|
+
the spine axis -- lanes at +/-GAP around the split->merge row, bodies spread EVENLY
|
|
85
|
+
between split.x and merge.x (a 1-node branch sits at the midpoint), keeping the side
|
|
86
|
+
the engine chose for each branch. Dead-end stubs are left alone here; the branch
|
|
87
|
+
conventions pass re-places them outward off the new lane bodies afterwards.
|
|
88
|
+
|
|
89
|
+
The base layered engine has no concept of sibling branches between one split and one
|
|
90
|
+
merge, so it gives each branch a semi-arbitrary diagonal. This pass is applied by
|
|
91
|
+
layout_flow ONLY when the final layout is strictly better (overlaps, crossings,
|
|
92
|
+
maxedge) than without it -- ties keep the existing output, so approved layouts can't
|
|
93
|
+
churn."""
|
|
94
|
+
from collections import defaultdict
|
|
95
|
+
P = positions
|
|
96
|
+
kind = {n.id: n.shape for n in fg.nodes}
|
|
97
|
+
parent = {n.id: n.parent_id for n in fg.nodes}
|
|
98
|
+
nsucc: dict = defaultdict(list)
|
|
99
|
+
pred: dict = defaultdict(list)
|
|
100
|
+
for e in fg.edges:
|
|
101
|
+
if e.source in P and e.dest in P:
|
|
102
|
+
if getattr(e, "type", 0) == 0:
|
|
103
|
+
nsucc[e.source].append(e.dest)
|
|
104
|
+
pred[e.dest].append(e.source)
|
|
105
|
+
GAP = 180.0
|
|
106
|
+
|
|
107
|
+
def deadend_private(start, src):
|
|
108
|
+
"""True if `start` heads a private chain that terminates (a stub -- not a branch)."""
|
|
109
|
+
if len(pred.get(start, [])) != 1:
|
|
110
|
+
return False
|
|
111
|
+
seen, cur = {start, src}, start
|
|
112
|
+
while True:
|
|
113
|
+
nx = nsucc.get(cur, [])
|
|
114
|
+
if not nx:
|
|
115
|
+
return True
|
|
116
|
+
if len(nx) != 1:
|
|
117
|
+
return False
|
|
118
|
+
nxt = nx[0]
|
|
119
|
+
if nxt in seen or len(pred.get(nxt, [])) != 1:
|
|
120
|
+
return False
|
|
121
|
+
seen.add(nxt)
|
|
122
|
+
cur = nxt
|
|
123
|
+
|
|
124
|
+
for s in sorted(P):
|
|
125
|
+
if kind.get(s) != "diamond" or parent.get(s):
|
|
126
|
+
continue
|
|
127
|
+
branches: list = []
|
|
128
|
+
merge = None
|
|
129
|
+
ok = True
|
|
130
|
+
for t in dict.fromkeys(nsucc.get(s, [])):
|
|
131
|
+
if len(pred.get(t, [])) > 1: # direct edge to the merge (e.g. default->Join)
|
|
132
|
+
if merge is None:
|
|
133
|
+
merge = t
|
|
134
|
+
elif merge != t:
|
|
135
|
+
ok = False
|
|
136
|
+
continue
|
|
137
|
+
if deadend_private(t, s): # stub off the split -- not a fan branch
|
|
138
|
+
continue
|
|
139
|
+
body, cur, guard = [], t, 0
|
|
140
|
+
while ok and guard < 64:
|
|
141
|
+
guard += 1
|
|
142
|
+
if parent.get(cur) or kind.get(cur) == "area":
|
|
143
|
+
ok = False
|
|
144
|
+
break
|
|
145
|
+
body.append(cur)
|
|
146
|
+
cont = [x for x in nsucc.get(cur, []) if not deadend_private(x, cur)]
|
|
147
|
+
if len(cont) != 1:
|
|
148
|
+
ok = False
|
|
149
|
+
break
|
|
150
|
+
nxt = cont[0]
|
|
151
|
+
if len(pred.get(nxt, [])) > 1: # merge reached
|
|
152
|
+
if merge is None:
|
|
153
|
+
merge = nxt
|
|
154
|
+
elif merge != nxt:
|
|
155
|
+
ok = False
|
|
156
|
+
break
|
|
157
|
+
cur = nxt
|
|
158
|
+
else:
|
|
159
|
+
ok = False
|
|
160
|
+
if not ok:
|
|
161
|
+
break
|
|
162
|
+
branches.append(body)
|
|
163
|
+
if not ok or len(branches) < 2 or merge is None or merge not in P or parent.get(merge):
|
|
164
|
+
continue
|
|
165
|
+
sx, mx = P[s]["x"], P[merge]["x"]
|
|
166
|
+
if mx - sx < 2 * GAP: # merge must sit meaningfully to the right
|
|
167
|
+
continue
|
|
168
|
+
spine_y = (P[s]["y"] + P[merge]["y"]) / 2.0
|
|
169
|
+
# balanced lanes STRADDLING the spine (the reference fan: ceil(n/2) above, rest below),
|
|
170
|
+
# preserving the engine's vertical order of the branches -- his 0GetInfo golden:
|
|
171
|
+
# 3 branches -> lanes -2, -1, +1.
|
|
172
|
+
infos = sorted([(sorted(P[b]["y"] for b in body)[len(body) // 2], body)
|
|
173
|
+
for body in branches], key=lambda bi: bi[0])
|
|
174
|
+
n_above = (len(infos) + 1) // 2
|
|
175
|
+
lanes = [(-(n_above - i), body) for i, (_m, body) in enumerate(infos[:n_above])]
|
|
176
|
+
lanes += [(+(i + 1), body) for i, (_m, body) in enumerate(infos[n_above:])]
|
|
177
|
+
def _stub_chain(start, src):
|
|
178
|
+
out, cur, seen = [start], start, {start, src}
|
|
179
|
+
while True:
|
|
180
|
+
nx = nsucc.get(cur, [])
|
|
181
|
+
if len(nx) != 1 or nx[0] in seen:
|
|
182
|
+
return out
|
|
183
|
+
out.append(nx[0])
|
|
184
|
+
seen.add(nx[0])
|
|
185
|
+
cur = nx[0]
|
|
186
|
+
|
|
187
|
+
for lane, body in lanes:
|
|
188
|
+
y = spine_y + lane * GAP
|
|
189
|
+
step = (mx - sx) / (len(body) + 1)
|
|
190
|
+
for i, b in enumerate(body):
|
|
191
|
+
nx_, ny_ = sx + (i + 1) * step, y
|
|
192
|
+
dx_, dy_ = nx_ - P[b]["x"], ny_ - P[b]["y"]
|
|
193
|
+
# drag the node's dead-end stubs along -- the branch-conventions pass will
|
|
194
|
+
# re-place them outward, but they must not be STRANDED at the old location
|
|
195
|
+
# (a stranded stub can end up under its own moved parent).
|
|
196
|
+
for t2 in nsucc.get(b, []):
|
|
197
|
+
if t2 not in body and deadend_private(t2, b):
|
|
198
|
+
for m2 in _stub_chain(t2, b):
|
|
199
|
+
P[m2] = {"x": P[m2]["x"] + dx_, "y": P[m2]["y"] + dy_}
|
|
200
|
+
P[b] = {"x": nx_, "y": ny_}
|
|
201
|
+
return P
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def _layout_quality(positions: dict, fg) -> tuple:
|
|
205
|
+
"""(overlaps, crossings, maxedge) on straight segments -- the strictly-better gate for
|
|
206
|
+
optional post-passes (fan lanes): ties keep the existing output so approved layouts
|
|
207
|
+
can't churn."""
|
|
208
|
+
P = positions
|
|
209
|
+
ids = [i for i in P]
|
|
210
|
+
ov = 0
|
|
211
|
+
for i in range(len(ids)):
|
|
212
|
+
a = P[ids[i]]
|
|
213
|
+
for j in range(i + 1, len(ids)):
|
|
214
|
+
b = P[ids[j]]
|
|
215
|
+
# TRUE intersection only (nodes are 48px): the reference layout deliberately tucks a stub ~50px
|
|
216
|
+
# from a neighbouring lane -- a near-touch must not veto a variant that saves a
|
|
217
|
+
# crossing. (Placement decisions still use the looser 56px `occupied`.)
|
|
218
|
+
if abs(a["x"] - b["x"]) < 48 and abs(a["y"] - b["y"]) < 48:
|
|
219
|
+
ov += 1
|
|
220
|
+
segs = [(P[e.source], P[e.dest]) for e in fg.edges if e.source in P and e.dest in P]
|
|
221
|
+
|
|
222
|
+
def orient(p, q, r):
|
|
223
|
+
return (q["y"] - p["y"]) * (r["x"] - q["x"]) - (q["x"] - p["x"]) * (r["y"] - q["y"])
|
|
224
|
+
cross = 0
|
|
225
|
+
for i in range(len(segs)):
|
|
226
|
+
a, b = segs[i]
|
|
227
|
+
for j in range(i + 1, len(segs)):
|
|
228
|
+
c, d = segs[j]
|
|
229
|
+
pts = {(a["x"], a["y"]), (b["x"], b["y"]), (c["x"], c["y"]), (d["x"], d["y"])}
|
|
230
|
+
if len(pts) < 4:
|
|
231
|
+
continue
|
|
232
|
+
if (orient(a, b, c) > 0) != (orient(a, b, d) > 0) and (orient(c, d, a) > 0) != (orient(c, d, b) > 0):
|
|
233
|
+
cross += 1
|
|
234
|
+
med = max((abs(a["x"] - b["x"]) + abs(a["y"] - b["y"]) for a, b in segs), default=0.0)
|
|
235
|
+
return (ov, cross, round(med))
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def _branch_managed_nodes(fg) -> set:
|
|
239
|
+
"""Node ids whose placement _apply_branch_conventions owns: the private dead-end chains of
|
|
240
|
+
decisional DEFAULT / conditional branches and error-port branches (mirrors that pass's own
|
|
241
|
+
selection). Clustering must not fold these -- the perpendicular post-pass would re-flatten
|
|
242
|
+
them, wasting the fold and leaving make-room artifacts. default->Join (in-flow) is NOT
|
|
243
|
+
managed, so it stays foldable as part of the spine."""
|
|
244
|
+
from collections import defaultdict
|
|
245
|
+
kind = {n.id: n.shape for n in fg.nodes}
|
|
246
|
+
parent = {n.id: n.parent_id for n in fg.nodes}
|
|
247
|
+
succ: dict = defaultdict(list)
|
|
248
|
+
pred: dict = defaultdict(list)
|
|
249
|
+
for e in fg.edges:
|
|
250
|
+
succ[e.source].append(e.dest)
|
|
251
|
+
pred[e.dest].append(e.source)
|
|
252
|
+
|
|
253
|
+
def private_chain(start, src):
|
|
254
|
+
chain, cur, seen = [start], start, {start, src}
|
|
255
|
+
while True:
|
|
256
|
+
nx = succ.get(cur, [])
|
|
257
|
+
if len(nx) != 1:
|
|
258
|
+
break
|
|
259
|
+
nxt = nx[0]
|
|
260
|
+
if nxt in seen or len(pred.get(nxt, [])) != 1:
|
|
261
|
+
break
|
|
262
|
+
chain.append(nxt)
|
|
263
|
+
seen.add(nxt)
|
|
264
|
+
cur = nxt
|
|
265
|
+
return chain
|
|
266
|
+
|
|
267
|
+
managed: set = set()
|
|
268
|
+
for e in fg.edges:
|
|
269
|
+
s, d = e.source, e.dest
|
|
270
|
+
if parent.get(s) or parent.get(d):
|
|
271
|
+
continue
|
|
272
|
+
is_err = getattr(e, "type", 0) == 1
|
|
273
|
+
is_def = bool(getattr(e, "is_default", False)) and kind.get(s) == "diamond"
|
|
274
|
+
# only DEFAULT + ERROR branches are laid perpendicular-flat; a conditional (non-default)
|
|
275
|
+
# port is usually the in-flow spine continuation, so it stays foldable.
|
|
276
|
+
if not (is_err or is_def):
|
|
277
|
+
continue
|
|
278
|
+
if is_def and kind.get(d) == "circle": # default -> Join stays in-flow (foldable)
|
|
279
|
+
continue
|
|
280
|
+
if len(pred.get(d, [])) != 1:
|
|
281
|
+
continue
|
|
282
|
+
chain = private_chain(d, s)
|
|
283
|
+
if succ.get(chain[-1]): # reconnects (retry loop) -> loop-curl owns it
|
|
284
|
+
continue
|
|
285
|
+
managed.update(chain)
|
|
286
|
+
return managed
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def _apply_branch_conventions(positions: dict, fg, fold_long: bool = False) -> dict:
|
|
290
|
+
"""The reference branch-placement rule as an ENGINE-AGNOSTIC post-pass (runs on final absolute
|
|
291
|
+
positions, so it applies to either engine's output).
|
|
292
|
+
|
|
293
|
+
An action's **error-port** target, and a decisional's **DEFAULT** target, are placed
|
|
294
|
+
PERPENDICULAR to the flow — the branch's private dead-end chain sits in a lane directly
|
|
295
|
+
above/below the source (first node at the source's x), continuing right along that lane.
|
|
296
|
+
The ONLY exception is a DEFAULT whose target is a **Join** (merge): it stays IN-FLOW. This
|
|
297
|
+
follows the stated rule verbatim ("default … perpendicular … exception … goes to a
|
|
298
|
+
Join") — there is deliberately NO chain-length test (a long default main-path chain still
|
|
299
|
+
drops perpendicular; confirmed by his newest hand-edit). A branch that RECONNECTS (a retry
|
|
300
|
+
loop) is left to the loop-curl. Collision-checked: a branch keeps its engine position if no
|
|
301
|
+
free up/down lane exists. Container (For-Each) members are never moved."""
|
|
302
|
+
from collections import defaultdict
|
|
303
|
+
P = positions
|
|
304
|
+
kind = {n.id: n.shape for n in fg.nodes}
|
|
305
|
+
parent = {n.id: n.parent_id for n in fg.nodes}
|
|
306
|
+
succ: dict = defaultdict(list)
|
|
307
|
+
pred: dict = defaultdict(list)
|
|
308
|
+
for e in fg.edges:
|
|
309
|
+
if e.source in P and e.dest in P:
|
|
310
|
+
succ[e.source].append(e.dest)
|
|
311
|
+
pred[e.dest].append(e.source)
|
|
312
|
+
DX, GAP = 160.0, 180.0
|
|
313
|
+
# main-body centre for the OUTWARD side choice — measured from the SPINE only (nodes that
|
|
314
|
+
# have a successor), NOT all nodes: the terminal Stops we're about to move are scattered
|
|
315
|
+
# up/down and would skew a global median, tipping near-spine sources to the wrong side.
|
|
316
|
+
_spine = sorted(P[k]["y"] for k in P if succ.get(k)) or sorted(v["y"] for v in P.values())
|
|
317
|
+
center_y = _spine[len(_spine) // 2] if _spine else 0.0
|
|
318
|
+
|
|
319
|
+
def occupied(x, y, exclude, thresh=56):
|
|
320
|
+
# default 56 = comfortable non-overlap; the _eval fallback retries at 48 (true node
|
|
321
|
+
# intersection) so the deliberate ~50px stub tucks stay possible as a LAST resort.
|
|
322
|
+
return any(k not in exclude and abs(P[k]["x"] - x) < thresh and abs(P[k]["y"] - y) < thresh
|
|
323
|
+
for k in P)
|
|
324
|
+
|
|
325
|
+
def near_edge(x, y, exclude):
|
|
326
|
+
# a node must never sit ON an edge path (rule: the default lane belongs to the edge).
|
|
327
|
+
for e2 in fg.edges:
|
|
328
|
+
a, b = e2.source, e2.dest
|
|
329
|
+
if a not in P or b not in P or a in exclude or b in exclude:
|
|
330
|
+
continue
|
|
331
|
+
if a in pending_stubs or b in pending_stubs: # stub awaits re-placement: its edge
|
|
332
|
+
continue # position is stale, must not veto
|
|
333
|
+
ax_, ay_, bx_, by_ = P[a]["x"], P[a]["y"], P[b]["x"], P[b]["y"]
|
|
334
|
+
dx_, dy_ = bx_ - ax_, by_ - ay_
|
|
335
|
+
L2 = dx_ * dx_ + dy_ * dy_
|
|
336
|
+
if L2 < 1:
|
|
337
|
+
continue
|
|
338
|
+
u = max(0.0, min(1.0, ((x - ax_) * dx_ + (y - ay_) * dy_) / L2))
|
|
339
|
+
px_, py_ = ax_ + u * dx_, ay_ + u * dy_
|
|
340
|
+
if (x - px_) ** 2 + (y - py_) ** 2 < 28 * 28:
|
|
341
|
+
return True
|
|
342
|
+
return False
|
|
343
|
+
|
|
344
|
+
edges_geo = [(e.source, e.dest) for e in fg.edges if e.source in P and e.dest in P]
|
|
345
|
+
|
|
346
|
+
def _orient(a, b, c):
|
|
347
|
+
return (b["y"] - a["y"]) * (c["x"] - b["x"]) - (b["x"] - a["x"]) * (c["y"] - b["y"])
|
|
348
|
+
|
|
349
|
+
def crossings():
|
|
350
|
+
n = 0
|
|
351
|
+
for i in range(len(edges_geo)):
|
|
352
|
+
s1, d1 = edges_geo[i]
|
|
353
|
+
a, b = P[s1], P[d1]
|
|
354
|
+
for j in range(i + 1, len(edges_geo)):
|
|
355
|
+
s2, d2 = edges_geo[j]
|
|
356
|
+
if len({s1, d1, s2, d2}) < 4:
|
|
357
|
+
continue
|
|
358
|
+
cc, dd = P[s2], P[d2]
|
|
359
|
+
if ((_orient(a, b, cc) > 0) != (_orient(a, b, dd) > 0)
|
|
360
|
+
and (_orient(cc, dd, a) > 0) != (_orient(cc, dd, b) > 0)):
|
|
361
|
+
n += 1
|
|
362
|
+
return n
|
|
363
|
+
|
|
364
|
+
def _lane_coords(ax, sy, sign, n):
|
|
365
|
+
"""Perpendicular lane positions for a branch's private chain. Short chains (or when
|
|
366
|
+
clustering is off) run flat along a single lane; a LONG chain (clustering on) FOLDS into
|
|
367
|
+
boustrophedon shelves that stack AWAY from the spine, so a 13-action default tail becomes
|
|
368
|
+
a compact block beside the flow instead of a lane running off-screen."""
|
|
369
|
+
lane = sy + sign * GAP
|
|
370
|
+
W = 4
|
|
371
|
+
if not fold_long or n <= W:
|
|
372
|
+
return [(ax + k * DX, lane) for k in range(n)]
|
|
373
|
+
coords = [None] * n
|
|
374
|
+
for si in range((n + W - 1) // W):
|
|
375
|
+
block = list(range(si * W, min(si * W + W, n)))
|
|
376
|
+
seq = block if si % 2 == 0 else list(reversed(block))
|
|
377
|
+
base = 0 if si % 2 == 0 else (W - len(block)) # right-align R->L shelves: a short
|
|
378
|
+
rowy = lane + sign * si * GAP # remainder drops under the previous
|
|
379
|
+
for ci, k in enumerate(seq): # shelf's END, not back at its start
|
|
380
|
+
coords[k] = (ax + (base + ci) * DX, rowy)
|
|
381
|
+
return coords
|
|
382
|
+
|
|
383
|
+
def _inflow_coords(ax, sy, n):
|
|
384
|
+
"""In-flow lane (a conditional dead-end continuing right of the decisional). Short chains
|
|
385
|
+
stay a single flat in-flow row; a LONG one (clustering on) FOLDS into shelves that wrap
|
|
386
|
+
DOWNWARD, so a give-up tail compacts into a block instead of running off the canvas."""
|
|
387
|
+
W = 4
|
|
388
|
+
if not fold_long or n <= W:
|
|
389
|
+
return [(ax + (k + 1) * DX, sy) for k in range(n)]
|
|
390
|
+
coords = [None] * n
|
|
391
|
+
for si in range((n + W - 1) // W):
|
|
392
|
+
block = list(range(si * W, min(si * W + W, n)))
|
|
393
|
+
seq = block if si % 2 == 0 else list(reversed(block))
|
|
394
|
+
base = 0 if si % 2 == 0 else (W - len(block)) # right-align R->L shelves (see above)
|
|
395
|
+
rowy = sy + si * GAP
|
|
396
|
+
for ci, k in enumerate(seq):
|
|
397
|
+
coords[k] = (ax + (base + ci + 1) * DX, rowy)
|
|
398
|
+
return coords
|
|
399
|
+
|
|
400
|
+
def private_chain(start, src):
|
|
401
|
+
chain, cur, seen = [start], start, {start, src}
|
|
402
|
+
while True:
|
|
403
|
+
nx = succ.get(cur, [])
|
|
404
|
+
if len(nx) != 1:
|
|
405
|
+
break
|
|
406
|
+
nxt = nx[0]
|
|
407
|
+
if nxt in seen or len(pred.get(nxt, [])) != 1:
|
|
408
|
+
break
|
|
409
|
+
chain.append(nxt)
|
|
410
|
+
seen.add(nxt)
|
|
411
|
+
cur = nxt
|
|
412
|
+
return chain
|
|
413
|
+
|
|
414
|
+
# collect every stub chain this pass WILL re-place: until a chain is placed, its edges
|
|
415
|
+
# must be invisible to near_edge (an unplaced stub's edge can slice through a good slot).
|
|
416
|
+
pending_stubs: set = set()
|
|
417
|
+
for e in fg.edges:
|
|
418
|
+
s, d = e.source, e.dest
|
|
419
|
+
if s not in P or d not in P or parent.get(s) or parent.get(d):
|
|
420
|
+
continue
|
|
421
|
+
_err = (getattr(e, "type", 0) == 1)
|
|
422
|
+
_def = bool(getattr(e, "is_default", False)) and kind.get(s) == "diamond"
|
|
423
|
+
_cond = kind.get(s) == "diamond" and not _def and not _err
|
|
424
|
+
if not (_err or _def or _cond):
|
|
425
|
+
continue
|
|
426
|
+
if _def and kind.get(d) == "circle":
|
|
427
|
+
continue
|
|
428
|
+
if len(pred.get(d, [])) != 1:
|
|
429
|
+
continue
|
|
430
|
+
ch = private_chain(d, s)
|
|
431
|
+
if not succ.get(ch[-1]):
|
|
432
|
+
pending_stubs.update(ch)
|
|
433
|
+
|
|
434
|
+
for e in fg.edges:
|
|
435
|
+
s, d = e.source, e.dest
|
|
436
|
+
if s not in P or d not in P or parent.get(s) or parent.get(d):
|
|
437
|
+
continue
|
|
438
|
+
is_err = (getattr(e, "type", 0) == 1)
|
|
439
|
+
is_def = bool(getattr(e, "is_default", False)) and kind.get(s) == "diamond"
|
|
440
|
+
# a conditional (non-default / non-error) branch OFF A DECISIONAL
|
|
441
|
+
cond = kind.get(s) == "diamond" and not is_def and not is_err
|
|
442
|
+
if not (is_err or is_def or cond):
|
|
443
|
+
continue
|
|
444
|
+
if is_def and kind.get(d) == "circle": # default -> Join: stays in-flow
|
|
445
|
+
continue
|
|
446
|
+
if len(pred.get(d, [])) != 1: # target must be a private branch head
|
|
447
|
+
continue
|
|
448
|
+
chain = private_chain(d, s)
|
|
449
|
+
if succ.get(chain[-1]): # reconnects (retry loop) -> leave it
|
|
450
|
+
continue
|
|
451
|
+
pending_stubs.difference_update(chain) # being placed now -> edges become real
|
|
452
|
+
ax, sy, excl = P[s]["x"], P[s]["y"], set(chain)
|
|
453
|
+
# A CONDITIONAL dead-end branch of a decisional continues IN-FLOW (right of the
|
|
454
|
+
# decisional) when that lane is free — the flow keeps moving right, with the
|
|
455
|
+
# perpendicular lanes reserved for the default + loop branches (the rule: a
|
|
456
|
+
# non-default terminal like a give-up Stop goes in-flow if the default didn't take it).
|
|
457
|
+
def _eval(cands):
|
|
458
|
+
exclS = excl | {s}
|
|
459
|
+
for thresh in (56, 48): # tight 48 tuck only when nothing clears 56
|
|
460
|
+
best, best_cc = None, None
|
|
461
|
+
for cand in cands:
|
|
462
|
+
if any(occupied(px, py, excl, thresh) for px, py in cand):
|
|
463
|
+
continue
|
|
464
|
+
if any(near_edge(px, py, exclS) for px, py in cand):
|
|
465
|
+
continue
|
|
466
|
+
snap = {nid: dict(P[nid]) for nid in chain}
|
|
467
|
+
for nid, (px, py) in zip(chain, cand):
|
|
468
|
+
P[nid] = {"x": px, "y": py}
|
|
469
|
+
cc = crossings()
|
|
470
|
+
for nid in chain:
|
|
471
|
+
P[nid] = snap[nid]
|
|
472
|
+
if best is None or cc < best_cc: # candidates ordered: ties keep the FIRST
|
|
473
|
+
best, best_cc = cand, cc
|
|
474
|
+
if best is not None:
|
|
475
|
+
return best
|
|
476
|
+
return None
|
|
477
|
+
|
|
478
|
+
n = len(chain)
|
|
479
|
+
|
|
480
|
+
def _vcol(x_first, y_first, sign, n_):
|
|
481
|
+
"""Vertical serpentine for a dead-end chain: columns of <=4 stepping away from the
|
|
482
|
+
flow (sign), folding into the next column when deeper -- reference chains run
|
|
483
|
+
vertically but never taller than ~4 rows (his ANAF pocket is 2 columns x 4)."""
|
|
484
|
+
W = 4
|
|
485
|
+
cols = [list(range(k, min(k + W, n_))) for k in range(0, n_, W)]
|
|
486
|
+
out = [None] * n_
|
|
487
|
+
base = len(cols[0]) - 1
|
|
488
|
+
for j, col in enumerate(cols):
|
|
489
|
+
x = x_first + j * DX
|
|
490
|
+
for r, idx in enumerate(col):
|
|
491
|
+
row = r if j % 2 == 0 else base - r
|
|
492
|
+
out[idx] = (x, y_first + sign * row * GAP)
|
|
493
|
+
return out
|
|
494
|
+
|
|
495
|
+
# Reference pattern: a LONG dead-end chain runs VERTICALLY (constant x, stepping away
|
|
496
|
+
# from the flow) -- a compact column instead of a lane running off-canvas (his
|
|
497
|
+
# GetInfoSales terminal chain and 0GetInfo stubs). Short chains (1-2) keep the
|
|
498
|
+
# approved L-shape lane. Vertical goes FIRST for n>=3 so it wins crossing ties.
|
|
499
|
+
# the DEFAULT owns the in-flow lane: when this decisional's default edge continues
|
|
500
|
+
# in-flow (default -> Join stays in-flow per the stated exception), a conditional
|
|
501
|
+
# dead-end must NOT take that slot -- it goes perpendicular instead (his 0GetInfo
|
|
502
|
+
# fix: the no-info Stop drops below the decisional; the default runs on to the Join).
|
|
503
|
+
default_inflow = any(bool(getattr(e2, "is_default", False)) and kind.get(e2.dest) == "circle"
|
|
504
|
+
for e2 in fg.edges if e2.source == s)
|
|
505
|
+
if cond and not default_inflow:
|
|
506
|
+
flat = _inflow_coords(ax, sy, n)
|
|
507
|
+
drop = _vcol(ax + DX, sy, 1, n) # head in-flow, then DOWN
|
|
508
|
+
best = _eval([drop, flat] if n >= 3 else [flat])
|
|
509
|
+
if best is not None:
|
|
510
|
+
for nid, (px, py) in zip(chain, best):
|
|
511
|
+
P[nid] = {"x": px, "y": py}
|
|
512
|
+
continue
|
|
513
|
+
# outward = AWAY from where the flow came from: a fan-lane branch above the spine
|
|
514
|
+
# sends its stub further up, one below sends it down (the 0GetInfo golden). When
|
|
515
|
+
# the source sits in-line with its predecessors (a spine node), fall back to the
|
|
516
|
+
# global centre -- which keeps every previously-approved placement unchanged.
|
|
517
|
+
preds_y = sorted(P[p]["y"] for p in pred.get(s, []) if p in P)
|
|
518
|
+
pmed = preds_y[len(preds_y) // 2] if preds_y else None
|
|
519
|
+
if pmed is not None and abs(sy - pmed) > GAP * 0.5:
|
|
520
|
+
outward = 1 if sy > pmed else -1
|
|
521
|
+
else:
|
|
522
|
+
outward = 1 if sy >= center_y else -1 # perpendicular, crossing-driven side
|
|
523
|
+
cands = []
|
|
524
|
+
for sign in (outward, -outward): # outward tried first => ties keep outward
|
|
525
|
+
lane = _lane_coords(ax, sy, sign, n)
|
|
526
|
+
vert = _vcol(ax, sy + sign * GAP, sign, n)
|
|
527
|
+
cands += [vert, lane] if n >= 3 else [lane, vert]
|
|
528
|
+
if n == 1: # the reference half-gap tuck for a lone stub
|
|
529
|
+
cands.append([(ax, sy + sign * GAP * 0.72)])
|
|
530
|
+
best = _eval(cands)
|
|
531
|
+
if best:
|
|
532
|
+
for nid, (px, py) in zip(chain, best):
|
|
533
|
+
P[nid] = {"x": px, "y": py}
|
|
534
|
+
return positions
|
|
535
|
+
|
|
536
|
+
|
|
537
|
+
def layout_flow(obj: dict, flow_id: str | None = None,
|
|
538
|
+
only: list[str] | None = None, cluster: bool = False,
|
|
539
|
+
minimize_crossings: bool = False) -> dict:
|
|
540
|
+
"""Re-lay-out one flow. Returns {flow_id, nodes_moved, areas_resized, bbox, positions,
|
|
541
|
+
areas, bundle} where `bundle` is the modified copy of `obj`.
|
|
542
|
+
|
|
543
|
+
`only` = a list of action ids to reposition WITHOUT reshuffling the rest (the
|
|
544
|
+
"re-tidy just this action" mode). In that mode only those actions' positions are
|
|
545
|
+
written; every other action, the area frames, and CanvasData are left byte-identical."""
|
|
546
|
+
import os
|
|
547
|
+
if (os.environ.get("LAYOUT_ENGINE") or "").strip().lower() == "auto":
|
|
548
|
+
return _best_of_engines(obj, flow_id=flow_id, only=only)
|
|
549
|
+
obj = copy.deepcopy(obj)
|
|
550
|
+
flows, _fk = _flows(obj)
|
|
551
|
+
if flows is not None:
|
|
552
|
+
if flow_id:
|
|
553
|
+
flow = next((f for f in flows if _get(f, "Id") == flow_id), None)
|
|
554
|
+
if flow is None:
|
|
555
|
+
raise ValueError(f"flow_id {flow_id} not found")
|
|
556
|
+
elif len(flows) == 1:
|
|
557
|
+
flow = flows[0]
|
|
558
|
+
else:
|
|
559
|
+
raise ValueError(f"bundle has {len(flows)} flows; pass flow_id")
|
|
560
|
+
else:
|
|
561
|
+
flow = obj
|
|
562
|
+
|
|
563
|
+
fg = read_flow(flow)
|
|
564
|
+
nodes = [{"id": n.id, "width": n.size["w"], "height": n.size["h"],
|
|
565
|
+
"parent_id": n.parent_id, "kind": n.shape, "position": n.position}
|
|
566
|
+
for n in fg.nodes]
|
|
567
|
+
edges = [{"source": e.source, "dest": e.dest, "type": e.type,
|
|
568
|
+
"is_default": e.is_default} for e in fg.edges]
|
|
569
|
+
only_set = set(only) if only else None
|
|
570
|
+
if only_set is not None:
|
|
571
|
+
missing = only_set - {n.id for n in fg.nodes}
|
|
572
|
+
if missing:
|
|
573
|
+
raise ValueError(f"--only action id(s) not in this flow: {sorted(missing)}")
|
|
574
|
+
opts = {}
|
|
575
|
+
if only_set is None:
|
|
576
|
+
if cluster:
|
|
577
|
+
opts["CLUSTER"] = True
|
|
578
|
+
opts["NO_CLUSTER_IDS"] = _branch_managed_nodes(fg)
|
|
579
|
+
if minimize_crossings:
|
|
580
|
+
opts["MINIMIZE_CROSSINGS"] = True
|
|
581
|
+
# Single dispatch point: dispatch.layout picks the engine from the LAYOUT_ENGINE flag
|
|
582
|
+
# (legacy default, elk opt-in). Both engines share this exact signature + return contract,
|
|
583
|
+
# so everything below (designer_writes, Start-anchoring, write-back) is engine-agnostic.
|
|
584
|
+
# Cycle-aware: a large embedded cycle (SCC) is collapsed to a meta-node, laid out around,
|
|
585
|
+
# then expanded as a compact band; falls through to a plain dispatch otherwise.
|
|
586
|
+
result = cycles.layout(nodes, edges, opts=(opts or None),
|
|
587
|
+
subset=(list(only_set) if only_set else None),
|
|
588
|
+
dispatch_layout=dispatch.layout)
|
|
589
|
+
positions, areas = result["positions"], result["areas"]
|
|
590
|
+
|
|
591
|
+
# Engine-agnostic branch-placement convention (the reference rule): error-port + decisional
|
|
592
|
+
# default branches drop perpendicular to the flow (unless default->Join). Full layouts only.
|
|
593
|
+
if only_set is None and positions:
|
|
594
|
+
base_pos = _apply_branch_conventions({k: dict(v) for k, v in positions.items()},
|
|
595
|
+
fg, fold_long=cluster)
|
|
596
|
+
# FAN pattern (parallel alternative branches between one split and one merge -> flat
|
|
597
|
+
# stacked lanes). Tried as a variant and kept ONLY when strictly better.
|
|
598
|
+
fan_pos = _apply_fan_lanes({k: dict(v) for k, v in positions.items()}, fg)
|
|
599
|
+
if fan_pos != positions:
|
|
600
|
+
fan_pos = _apply_branch_conventions(fan_pos, fg, fold_long=cluster)
|
|
601
|
+
positions = fan_pos if _layout_quality(fan_pos, fg) < _layout_quality(base_pos, fg) else base_pos
|
|
602
|
+
else:
|
|
603
|
+
positions = base_pos
|
|
604
|
+
|
|
605
|
+
actions = _get(flow, "Actions", default=[]) or []
|
|
606
|
+
# Anchor a full re-layout to where the diagram already sits — keep Start exactly put —
|
|
607
|
+
# so the user's designer viewport still frames it. Without this the whole graph jumps to
|
|
608
|
+
# the origin and the canvas looks empty until you pan/fit far away.
|
|
609
|
+
if only_set is None and positions:
|
|
610
|
+
old = {}
|
|
611
|
+
for a in actions:
|
|
612
|
+
aid = _get(a, "Id")
|
|
613
|
+
p = _get(_get(a, "CustomData", default={}) or {}, "position")
|
|
614
|
+
if aid and isinstance(p, dict) and p.get("x") is not None:
|
|
615
|
+
old[aid] = {"x": float(p["x"]), "y": float(p["y"])}
|
|
616
|
+
anchor = fg.start_id if (fg.start_id in old and fg.start_id in positions) else None
|
|
617
|
+
if anchor:
|
|
618
|
+
sx = old[anchor]["x"] - positions[anchor]["x"]
|
|
619
|
+
sy = old[anchor]["y"] - positions[anchor]["y"]
|
|
620
|
+
elif old:
|
|
621
|
+
sx = min(p["x"] for p in old.values()) - min(p["x"] for p in positions.values())
|
|
622
|
+
sy = min(p["y"] for p in old.values()) - min(p["y"] for p in positions.values())
|
|
623
|
+
else:
|
|
624
|
+
sx = sy = 0.0
|
|
625
|
+
if sx or sy:
|
|
626
|
+
positions = {k: {"x": v["x"] + sx, "y": v["y"] + sy} for k, v in positions.items()}
|
|
627
|
+
areas = {k: {**v, "x": v["x"] + sx, "y": v["y"] + sy} for k, v in areas.items()}
|
|
628
|
+
|
|
629
|
+
# PROCESIO stores a container child's position RELATIVE to its parent area's frame,
|
|
630
|
+
# and an area node's position IS that frame top-left. engine.designer_writes encodes it.
|
|
631
|
+
parent_of = {_get(a, "Id"): _get(a, "ParentId") for a in actions
|
|
632
|
+
if _get(a, "ParentId") in areas}
|
|
633
|
+
writes = engine.designer_writes(positions, areas, parent_of)
|
|
634
|
+
|
|
635
|
+
moved = resized = 0
|
|
636
|
+
for a in actions:
|
|
637
|
+
aid = _get(a, "Id")
|
|
638
|
+
if only_set is not None and aid not in only_set:
|
|
639
|
+
continue # partial mode: leave the rest untouched
|
|
640
|
+
w = writes.get(aid)
|
|
641
|
+
if not w:
|
|
642
|
+
continue
|
|
643
|
+
cd_key = _key(a, "CustomData")
|
|
644
|
+
if not cd_key:
|
|
645
|
+
continue
|
|
646
|
+
cd = a[cd_key]
|
|
647
|
+
pos_key = _key(cd, "position") or "position"
|
|
648
|
+
cd[pos_key] = dict(w["pos"])
|
|
649
|
+
moved += 1
|
|
650
|
+
if w["areaSize"] is not None:
|
|
651
|
+
asz_key = _key(cd, "areaSize") or "areaSize"
|
|
652
|
+
cd[asz_key] = dict(w["areaSize"])
|
|
653
|
+
resized += 1
|
|
654
|
+
# NEVER touch CanvasData (the designer viewport pan/zoom). Resetting it jumps the
|
|
655
|
+
# user's view away from the content — leave it exactly as the flow had it; the user
|
|
656
|
+
# can fit-to-screen in the designer to frame a re-laid-out process.
|
|
657
|
+
return {"flow_id": _get(flow, "Id"), "nodes_moved": moved,
|
|
658
|
+
"areas_resized": resized, "bbox": result["bbox"],
|
|
659
|
+
"positions": {k: _round(v) for k, v in positions.items()},
|
|
660
|
+
"areas": areas, "bundle": obj}
|
|
661
|
+
|
|
662
|
+
|
|
663
|
+
def verify_render(obj: dict, flow_id: str | None = None) -> dict:
|
|
664
|
+
"""Simulate how the PROCESIO designer renders a flow's CURRENT positions (a container
|
|
665
|
+
child's stored position is RELATIVE to its parent area's frame top-left) and report
|
|
666
|
+
layout problems: children rendered outside their container frame, and node overlaps.
|
|
667
|
+
Read-only — does not modify the flow. Returns {ok, issue_count, issues, areas, nodes}."""
|
|
668
|
+
flows, _fk = _flows(obj)
|
|
669
|
+
if flows is not None:
|
|
670
|
+
flow = (next((f for f in flows if _get(f, "Id") == flow_id), None) if flow_id
|
|
671
|
+
else (flows[0] if len(flows) == 1 else None))
|
|
672
|
+
if flow is None:
|
|
673
|
+
raise ValueError("verify_render: bundle has multiple flows; pass flow_id")
|
|
674
|
+
else:
|
|
675
|
+
flow = obj
|
|
676
|
+
actions = _get(flow, "Actions", default=[]) or []
|
|
677
|
+
nodes = {}
|
|
678
|
+
for a in actions:
|
|
679
|
+
aid = _get(a, "Id")
|
|
680
|
+
cd = _get(a, "CustomData", default={}) or {}
|
|
681
|
+
p = _get(cd, "position") or {}
|
|
682
|
+
asz = _get(cd, "areaSize") or {}
|
|
683
|
+
is_area = _get(cd, "type") == "area"
|
|
684
|
+
nodes[aid] = {
|
|
685
|
+
"x": float(p.get("x", 0) or 0), "y": float(p.get("y", 0) or 0),
|
|
686
|
+
"parent": _get(a, "ParentId"), "is_area": is_area,
|
|
687
|
+
"w": float(asz.get("width", 48) or 48) if is_area else 48.0,
|
|
688
|
+
"h": float(asz.get("height", 48) or 48) if is_area else 48.0,
|
|
689
|
+
"name": _get(cd, "name") or _get(a, "ActionTemplateName") or aid,
|
|
690
|
+
}
|
|
691
|
+
# absolute rendered position: a child is offset by its parent area's absolute top-left
|
|
692
|
+
absol = {}
|
|
693
|
+
for aid, n in nodes.items():
|
|
694
|
+
par = n["parent"]
|
|
695
|
+
if par in nodes and nodes[par]["is_area"]:
|
|
696
|
+
absol[aid] = {"x": nodes[par]["x"] + n["x"], "y": nodes[par]["y"] + n["y"]}
|
|
697
|
+
else:
|
|
698
|
+
absol[aid] = {"x": n["x"], "y": n["y"]}
|
|
699
|
+
issues = []
|
|
700
|
+
for aid, n in nodes.items():
|
|
701
|
+
par = n["parent"]
|
|
702
|
+
if par in nodes and nodes[par]["is_area"]:
|
|
703
|
+
pp = nodes[par]
|
|
704
|
+
cx, cy = absol[aid]["x"], absol[aid]["y"]
|
|
705
|
+
if not (pp["x"] - 1 <= cx - 24 and cx + 24 <= pp["x"] + pp["w"] + 1
|
|
706
|
+
and pp["y"] - 1 <= cy - 24 and cy + 24 <= pp["y"] + pp["h"] + 1):
|
|
707
|
+
issues.append({"type": "child_outside_frame", "child": n["name"], "area": pp["name"]})
|
|
708
|
+
leaves = [aid for aid, n in nodes.items() if not n["is_area"]]
|
|
709
|
+
for i in range(len(leaves)):
|
|
710
|
+
a = absol[leaves[i]]
|
|
711
|
+
for j in range(i + 1, len(leaves)):
|
|
712
|
+
b = absol[leaves[j]]
|
|
713
|
+
if abs(a["x"] - b["x"]) < 46 and abs(a["y"] - b["y"]) < 46:
|
|
714
|
+
issues.append({"type": "overlap",
|
|
715
|
+
"a": nodes[leaves[i]]["name"], "b": nodes[leaves[j]]["name"]})
|
|
716
|
+
return {"ok": not issues, "issue_count": len(issues), "issues": issues[:40],
|
|
717
|
+
"areas": sum(1 for n in nodes.values() if n["is_area"]), "nodes": len(nodes)}
|
|
718
|
+
|
|
719
|
+
|
|
720
|
+
def layout_resource_map(obj: dict, root_flow_id: str | None = None,
|
|
721
|
+
levels: int | None = None) -> dict:
|
|
722
|
+
"""Position the cross-process (process→process) call graph. Returns
|
|
723
|
+
{root, nodes:[{flow_id, title, x, y}], edges:[...]}. Callers fall left of their
|
|
724
|
+
callees (longest-path layering); LVL-centering on the root is a future refinement."""
|
|
725
|
+
rm = read_bundle(obj)
|
|
726
|
+
graphs = rm["flows"]
|
|
727
|
+
proc_edges = rm["process_edges"]
|
|
728
|
+
node_ids = set(graphs) | {e["target"] for e in proc_edges} | {e["source"] for e in proc_edges}
|
|
729
|
+
nodes = [{"id": fid, "width": 220, "height": 60} for fid in sorted(node_ids)]
|
|
730
|
+
edges = [{"source": e["source"], "dest": e["target"], "type": 0} for e in proc_edges]
|
|
731
|
+
result = engine.layout(nodes, edges, {"DX": 360, "DY": 90, "NODE_W": 220, "NODE_H": 60})
|
|
732
|
+
titles = {fid: (g.get("title") if isinstance(g, dict) else None) for fid, g in graphs.items()}
|
|
733
|
+
out_nodes = [{"flow_id": fid, "title": titles.get(fid),
|
|
734
|
+
"x": p["x"], "y": p["y"]} for fid, p in sorted(result["positions"].items())]
|
|
735
|
+
return {"root": root_flow_id, "nodes": out_nodes, "edges": proc_edges,
|
|
736
|
+
"bbox": result["bbox"]}
|