@uipath/skills 1.196.0 → 1.197.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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +5 -2
- package/CODEOWNERS +22 -7
- package/README.md +14 -1
- package/assets/skill-status.json +14 -1
- package/assets/uip-catalog-snapshot.json +13 -2
- package/hooks/hooks.json +12 -0
- package/hooks/send-telemetry.sh +445 -0
- package/hooks/suggest-permissions.sh +6 -0
- package/package.json +1 -1
- package/skills/uipath-admin/SKILL.md +24 -4
- package/skills/uipath-admin/references/audit-commands.md +25 -21
- package/skills/uipath-admin/references/audit-workflow-guide.md +26 -25
- package/skills/uipath-admin/references/diagnose/CAPABILITY.md +74 -0
- package/skills/uipath-admin/references/diagnose/references/failure-modes.md +228 -0
- package/skills/uipath-admin/references/diagnose/references/troubleshooting-guide.md +106 -0
- package/skills/uipath-admin/references/identity-troubleshoot-guide.md +251 -0
- package/skills/uipath-agents/SKILL.md +12 -8
- package/skills/uipath-agents/references/coded/capabilities/context-grounding.md +2 -0
- package/skills/uipath-agents/references/coded/capabilities/conversational-agents.md +2 -0
- package/skills/uipath-agents/references/coded/capabilities/guardrails/guardrails-recommend.md +12 -7
- package/skills/uipath-agents/references/coded/capabilities/guardrails/guardrails.md +141 -1
- package/skills/uipath-agents/references/coded/capabilities/human-in-the-loop.md +2 -0
- package/skills/uipath-agents/references/coded/capabilities/integration-service.md +19 -33
- package/skills/uipath-agents/references/coded/lifecycle/bindings-reference.md +54 -22
- package/skills/uipath-agents/references/coded/lifecycle/build.md +1 -0
- package/skills/uipath-agents/references/coded/lifecycle/evaluations/evaluation-sets.md +5 -1
- package/skills/uipath-agents/references/coded/lifecycle/setup.md +3 -1
- package/skills/uipath-agents/references/coded/quickstart.md +11 -10
- package/skills/uipath-agents/references/coded-vs-lowcode-guide.md +1 -0
- package/skills/uipath-agents/references/context-grounding-patterns.md +1 -1
- package/skills/uipath-agents/references/lowcode/agent-definition.md +97 -12
- package/skills/uipath-agents/references/lowcode/capabilities/built-in-tools/analyze-attachments.md +2 -2
- package/skills/uipath-agents/references/lowcode/capabilities/built-in-tools/batch-transform/impl-json.md +40 -14
- package/skills/uipath-agents/references/lowcode/capabilities/built-in-tools/built-in-tools.md +4 -3
- package/skills/uipath-agents/references/lowcode/capabilities/built-in-tools/deeprag/impl-json.md +38 -6
- package/skills/uipath-agents/references/lowcode/capabilities/context/attachments.md +1 -1
- package/skills/uipath-agents/references/lowcode/capabilities/context/context.md +1 -1
- package/skills/uipath-agents/references/lowcode/capabilities/context/datafabric.md +1 -1
- package/skills/uipath-agents/references/lowcode/capabilities/context/index.md +7 -3
- package/skills/uipath-agents/references/lowcode/capabilities/escalation/escalation.md +49 -38
- package/skills/uipath-agents/references/lowcode/capabilities/guardrails/guardrails-recommend.md +3 -1
- package/skills/uipath-agents/references/lowcode/capabilities/guardrails/guardrails.md +49 -49
- package/skills/uipath-agents/references/lowcode/capabilities/inline-in-flow/inline-in-flow.md +34 -14
- package/skills/uipath-agents/references/lowcode/capabilities/integration-service/integration-service.md +1 -1
- package/skills/uipath-agents/references/lowcode/capabilities/memory/memory.md +2 -0
- package/skills/uipath-agents/references/lowcode/capabilities/process/process.md +2 -2
- package/skills/uipath-agents/references/lowcode/capabilities/process/solution-files.md +1 -1
- package/skills/uipath-agents/references/lowcode/critical-rules/autonomous-critical-rules.md +15 -0
- package/skills/uipath-agents/references/lowcode/critical-rules/conversational-critical-rules.md +19 -0
- package/skills/uipath-agents/references/lowcode/{critical-rules.md → critical-rules/critical-rules.md} +25 -27
- package/skills/uipath-agents/references/lowcode/debug.md +6 -3
- package/skills/uipath-agents/references/lowcode/evaluations/evaluate.md +2 -0
- package/skills/uipath-agents/references/lowcode/lowcode.md +29 -6
- package/skills/uipath-agents/references/lowcode/model-selection-guide.md +4 -1
- package/skills/uipath-agents/references/lowcode/project-lifecycle.md +20 -8
- package/skills/uipath-agents/references/lowcode/prompting/agent-prompting-guide.md +17 -0
- package/skills/uipath-agents/references/lowcode/{agent-prompting-guide.md → prompting/autonomous-agent-prompting-guide.md} +9 -11
- package/skills/uipath-agents/references/lowcode/prompting/conversational-agent-prompting-guide.md +123 -0
- package/skills/uipath-agents/references/lowcode/solution-resources.md +1 -1
- package/skills/uipath-api-workflow/SKILL.md +32 -12
- package/skills/uipath-api-workflow/references/cli-reference.md +110 -22
- package/skills/uipath-api-workflow/references/connector-activity-discovery.md +43 -4
- package/skills/uipath-api-workflow/references/troubleshooting.md +23 -7
- package/skills/uipath-api-workflow/references/workflow-file-format.md +25 -7
- package/skills/uipath-coded-apps/SKILL.md +55 -12
- package/skills/uipath-coded-apps/assets/fixtures/governance-dashboard-starter-kit.tar.gz +0 -0
- package/skills/uipath-coded-apps/assets/scripts/dashboards/build-dashboard.mjs +1371 -0
- package/skills/uipath-coded-apps/assets/scripts/dashboards/capability-registry.json +845 -0
- package/skills/uipath-coded-apps/assets/scripts/dashboards/flows/build.mjs +338 -0
- package/skills/uipath-coded-apps/assets/scripts/dashboards/flows/edit.mjs +248 -0
- package/skills/uipath-coded-apps/assets/scripts/dashboards/flows/template.mjs +82 -0
- package/skills/uipath-coded-apps/assets/scripts/dashboards/flows/upgrade.mjs +73 -0
- package/skills/uipath-coded-apps/assets/scripts/dashboards/lifecycle.mjs +207 -0
- package/skills/uipath-coded-apps/assets/scripts/dashboards/setup-admin-folder.mjs +146 -0
- package/skills/uipath-coded-apps/assets/scripts/dashboards/tests/regime-eject.test.mjs +124 -0
- package/skills/uipath-coded-apps/assets/scripts/dashboards/tests/resolution.test.mjs +1635 -0
- package/skills/uipath-coded-apps/assets/templates/action-app/app-template.css +9 -0
- package/skills/uipath-coded-apps/assets/templates/action-app/document-tab-template.css +139 -0
- package/skills/uipath-coded-apps/assets/templates/action-app/form-template.css +442 -0
- package/skills/uipath-coded-apps/assets/templates/action-app/index-template.css +111 -0
- package/skills/uipath-coded-apps/assets/templates/action-app-template.md +357 -152
- package/skills/uipath-coded-apps/assets/templates/web-app-template.md +255 -78
- package/skills/uipath-coded-apps/references/create-action-app.md +284 -195
- package/skills/uipath-coded-apps/references/create-web-app.md +64 -26
- package/skills/uipath-coded-apps/references/dashboards/CAPABILITY.md +179 -0
- package/skills/uipath-coded-apps/references/dashboards/aesthetic/charting.md +138 -0
- package/skills/uipath-coded-apps/references/dashboards/aesthetic/layout-patterns.md +152 -0
- package/skills/uipath-coded-apps/references/dashboards/plugins/build/impl.md +423 -0
- package/skills/uipath-coded-apps/references/dashboards/plugins/deploy/impl.md +322 -0
- package/skills/uipath-coded-apps/references/dashboards/primitives/customization.md +66 -0
- package/skills/uipath-coded-apps/references/dashboards/primitives/detail-views.md +64 -0
- package/skills/uipath-coded-apps/references/dashboards/primitives/incremental-editor.md +92 -0
- package/skills/uipath-coded-apps/references/dashboards/primitives/state-file.md +64 -0
- package/skills/uipath-coded-apps/references/dashboards/primitives/tier-resolution.md +474 -0
- package/skills/uipath-coded-apps/references/debug.md +15 -21
- package/skills/uipath-coded-apps/references/oauth-client-setup.md +5 -5
- package/skills/uipath-coded-apps/references/oauth-scopes.md +64 -2
- package/skills/uipath-coded-apps/references/pack-publish-deploy.md +12 -5
- package/skills/uipath-coded-apps/references/patterns.md +10 -5
- package/skills/uipath-coded-apps/references/sdk/action-center.md +32 -6
- package/skills/uipath-coded-apps/references/sdk/agents.md +241 -0
- package/skills/uipath-coded-apps/references/sdk/governance-traces.md +186 -0
- package/skills/uipath-coded-apps/references/sdk/governance.md +72 -0
- package/skills/uipath-coded-apps/references/sdk/imports.md +4 -0
- package/skills/uipath-coded-apps/references/sdk/maestro.md +114 -0
- package/skills/uipath-coded-apps/references/sdk/orchestrator.md +80 -4
- package/skills/uipath-coded-apps/references/sdk/pagination.md +2 -0
- package/skills/uipath-coded-apps/references/sdk/traces.md +89 -0
- package/skills/uipath-coded-apps/references/widgets/validation-station.md +308 -0
- package/skills/uipath-coded-apps/scripts/validate-action-schema.js +241 -0
- package/skills/uipath-connector-builder/SKILL.md +192 -0
- package/skills/uipath-connector-builder/references/auth.md +171 -0
- package/skills/uipath-connector-builder/references/configuration.md +130 -0
- package/skills/uipath-connector-builder/references/debugging.md +80 -0
- package/skills/uipath-connector-builder/references/element-json.md +148 -0
- package/skills/uipath-connector-builder/references/events.md +132 -0
- package/skills/uipath-connector-builder/references/hooks.md +198 -0
- package/skills/uipath-connector-builder/references/overview.md +78 -0
- package/skills/uipath-connector-builder/references/standard-resources.md +111 -0
- package/skills/uipath-connector-builder/references/system-resources.md +85 -0
- package/skills/uipath-feedback/SKILL.md +43 -29
- package/skills/{uipath-agents/references/coded/frameworks/coded-functions.md → uipath-functions/SKILL.md} +36 -23
- package/skills/uipath-governance/SKILL.md +49 -8
- package/skills/uipath-governance/references/aops-policy/aops-policy-commands.md +10 -10
- package/skills/uipath-governance/references/aops-policy/aops-policy-deploy-guide.md +7 -7
- package/skills/uipath-governance/references/auth-context.md +101 -0
- package/skills/uipath-governance/references/cli-cheatsheet.md +207 -0
- package/skills/uipath-governance/references/compliance-pack/catalog/impl.md +79 -0
- package/skills/uipath-governance/references/compliance-pack/coverage/impl.md +141 -0
- package/skills/uipath-governance/references/compliance-pack/disable/impl.md +39 -0
- package/skills/uipath-governance/references/compliance-pack/full-apply/impl.md +164 -0
- package/skills/uipath-governance/references/compliance-pack/partial-apply/impl.md +266 -0
- package/skills/uipath-governance/references/compliance-pack/partial-apply/merge-overrides-guide.md +28 -0
- package/skills/uipath-governance/references/compliance-pack/partial-apply/planning.md +82 -0
- package/skills/uipath-governance/references/compliance-pack/partial-apply/synthesize-formdata-guide.md +22 -0
- package/skills/uipath-governance/references/compliance-pack/preview-gate.md +43 -0
- package/skills/uipath-governance/references/compliance-pack/query/impl.md +75 -0
- package/skills/uipath-governance/references/diagnose/CAPABILITY.md +71 -0
- package/skills/uipath-governance/references/diagnose/references/failure-modes.md +201 -0
- package/skills/uipath-governance/references/diagnose/references/troubleshooting-guide.md +137 -0
- package/skills/uipath-governance/references/disambiguation-guide.md +15 -0
- package/skills/uipath-governance/scripts/merge-overrides.mjs +81 -0
- package/skills/uipath-governance/scripts/synthesize-formdata.mjs +86 -0
- package/skills/uipath-human-in-the-loop/SKILL.md +30 -25
- package/skills/uipath-human-in-the-loop/references/hitl-node-apptask.md +38 -4
- package/skills/uipath-human-in-the-loop/references/hitl-node-coded-action-app.md +2 -3
- package/skills/uipath-human-in-the-loop/references/hitl-node-quickform.md +92 -34
- package/skills/uipath-ixp/SKILL.md +120 -0
- package/skills/uipath-ixp/references/cli-reference.md +121 -0
- package/skills/uipath-ixp/references/improve-prompts-guide.md +283 -0
- package/skills/uipath-ixp/references/label-documents-guide.md +207 -0
- package/skills/uipath-ixp/references/project-setup-guide.md +90 -0
- package/skills/uipath-maestro-bpmn/SKILL.md +167 -68
- package/skills/uipath-maestro-bpmn/references/cli-conventions.md +47 -0
- package/skills/uipath-maestro-bpmn/references/diagnose/CAPABILITY.md +6 -6
- package/skills/uipath-maestro-bpmn/references/operate/CAPABILITY.md +3 -3
- package/skills/uipath-maestro-bpmn/references/registry-workflow.md +199 -0
- package/skills/uipath-maestro-bpmn/references/structural-bpmn.md +500 -0
- package/skills/uipath-maestro-bpmn/validator/README.md +214 -0
- package/skills/uipath-maestro-bpmn/validator/bpmn-spec.json +2477 -0
- package/skills/uipath-maestro-bpmn/validator/model.mjs +419 -0
- package/skills/uipath-maestro-bpmn/validator/package.json +17 -0
- package/skills/uipath-maestro-bpmn/validator/rules.mjs +1403 -0
- package/skills/uipath-maestro-bpmn/validator/samples/invalid-conditional-and-variable.bpmn +25 -0
- package/skills/uipath-maestro-bpmn/validator/samples/valid-baseline.bpmn +52 -0
- package/skills/uipath-maestro-bpmn/validator/test/fixtures/expected-findings/A.2.0.bpmn +157 -0
- package/skills/uipath-maestro-bpmn/validator/test/fixtures/expected-findings/A.2.1.bpmn +333 -0
- package/skills/uipath-maestro-bpmn/validator/test/fixtures/expected-findings/B.1.0.bpmn +598 -0
- package/skills/uipath-maestro-bpmn/validator/test/fixtures/expected-findings/B.2.0.bpmn +1709 -0
- package/skills/uipath-maestro-bpmn/validator/test/fixtures/expected-findings/C.2.0.bpmn +564 -0
- package/skills/uipath-maestro-bpmn/validator/test/fixtures/expected-findings/C.3.0.bpmn +671 -0
- package/skills/uipath-maestro-bpmn/validator/test/fixtures/expected-findings/C.4.0.bpmn +1045 -0
- package/skills/uipath-maestro-bpmn/validator/test/fixtures/expected-findings/C.5.0.bpmn +1176 -0
- package/skills/uipath-maestro-bpmn/validator/test/fixtures/expected-findings/C.6.0.bpmn +670 -0
- package/skills/uipath-maestro-bpmn/validator/test/fixtures/expected-findings/C.7.0.bpmn +466 -0
- package/skills/uipath-maestro-bpmn/validator/test/fixtures/expected-findings/Can_Parse_Complex_Process_With_Task_Gateway_BoundaryEvent_etc.bpmn +74 -0
- package/skills/uipath-maestro-bpmn/validator/test/fixtures/expected-findings/ExclusiveGatewayDefaultFlow.bpmn +32 -0
- package/skills/uipath-maestro-bpmn/validator/test/fixtures/expected-findings/ExternalAgentWorkflow.bpmn +62 -0
- package/skills/uipath-maestro-bpmn/validator/test/fixtures/expected-findings/Golden_Scenario.initial.bpmn +361 -0
- package/skills/uipath-maestro-bpmn/validator/test/fixtures/expected-findings/InclusiveJoinRouteAwayBranch.bpmn +62 -0
- package/skills/uipath-maestro-bpmn/validator/test/fixtures/expected-findings/Parse_AsyncExecution_And_Create_CorrectModel.bpmn +74 -0
- package/skills/uipath-maestro-bpmn/validator/test/fixtures/expected-findings/Parse_IXP_ExtractionValidation_And_Create_CorrectModel.bpmn +35 -0
- package/skills/uipath-maestro-bpmn/validator/test/fixtures/expected-findings/Parse_IXP_Extraction_FileUpload_And_Create_CorrectModel.bpmn +33 -0
- package/skills/uipath-maestro-bpmn/validator/test/fixtures/expected-findings/Parse_IXP_Extraction_JobAttachment_And_Create_CorrectModel.bpmn +35 -0
- package/skills/uipath-maestro-bpmn/validator/test/fixtures/expected-findings/Parse_SubProcess_With_Multiple_Element_Types.bpmn +76 -0
- package/skills/uipath-maestro-bpmn/validator/test/fixtures/expected-findings/StartEventWithOutputs.bpmn +36 -0
- package/skills/uipath-maestro-bpmn/validator/test/fixtures/expected-findings/StartGatewayEnd.bpmn +44 -0
- package/skills/uipath-maestro-bpmn/validator/test/fixtures/expected-findings/all elements.bpmn +516 -0
- package/skills/uipath-maestro-bpmn/validator/test/fixtures/expected-findings/all_sequence_flow_types.bpmn +173 -0
- package/skills/uipath-maestro-bpmn/validator/test/fixtures/expected-findings/demo.bpmn +181 -0
- package/skills/uipath-maestro-bpmn/validator/test/fixtures/expected-findings/subprocess-example-001-collapsed.bpmn +126 -0
- package/skills/uipath-maestro-bpmn/validator/test/fixtures/expected-findings/subprocess-example-001-expanded.bpmn +122 -0
- package/skills/uipath-maestro-bpmn/validator/test/fixtures/expected-findings/subprocess-example-003-collapsed_deeply-nested.bpmn +438 -0
- package/skills/uipath-maestro-bpmn/validator/test/fixtures/known-good/A.1.0.bpmn +87 -0
- package/skills/uipath-maestro-bpmn/validator/test/fixtures/known-good/A.3.0.bpmn +165 -0
- package/skills/uipath-maestro-bpmn/validator/test/fixtures/known-good/ApiWorkflow.bpmn +39 -0
- package/skills/uipath-maestro-bpmn/validator/test/fixtures/known-good/BpmnNestedSubProcessTests.bpmn +102 -0
- package/skills/uipath-maestro-bpmn/validator/test/fixtures/known-good/BpmnTimerBoundaryEvents.bpmn +160 -0
- package/skills/uipath-maestro-bpmn/validator/test/fixtures/known-good/BpmnXmlWithCatchAllErrorEventSubProcess.bpmn +45 -0
- package/skills/uipath-maestro-bpmn/validator/test/fixtures/known-good/BpmnXmlWithSpecificErrorEventSubProcess.bpmn +46 -0
- package/skills/uipath-maestro-bpmn/validator/test/fixtures/known-good/CaseManagementWithConstantIdentifier.bpmn +218 -0
- package/skills/uipath-maestro-bpmn/validator/test/fixtures/known-good/ErrorBoundary.bpmn +78 -0
- package/skills/uipath-maestro-bpmn/validator/test/fixtures/known-good/ErrorPropagationInEventSubprocess.bpmn +340 -0
- package/skills/uipath-maestro-bpmn/validator/test/fixtures/known-good/EventBasedGatewayFirstCatcherWins.bpmn +56 -0
- package/skills/uipath-maestro-bpmn/validator/test/fixtures/known-good/Example-EventBasedGateway.bpmn +117 -0
- package/skills/uipath-maestro-bpmn/validator/test/fixtures/known-good/ExclusiveGatewayConditional.bpmn +45 -0
- package/skills/uipath-maestro-bpmn/validator/test/fixtures/known-good/ExclusiveGatewaySharedEndEvent.bpmn +45 -0
- package/skills/uipath-maestro-bpmn/validator/test/fixtures/known-good/ExclusiveWithParallelGateway.bpmn +68 -0
- package/skills/uipath-maestro-bpmn/validator/test/fixtures/known-good/FourScriptTasks.bpmn +84 -0
- package/skills/uipath-maestro-bpmn/validator/test/fixtures/known-good/HitlTaskOnly.bpmn +55 -0
- package/skills/uipath-maestro-bpmn/validator/test/fixtures/known-good/InclusiveGatewayForkJoin.bpmn +69 -0
- package/skills/uipath-maestro-bpmn/validator/test/fixtures/known-good/Parse_BPMN_Elements_And_Create_CorrectModel.bpmn +20 -0
- package/skills/uipath-maestro-bpmn/validator/test/fixtures/known-good/Parse_HttpRequest_ServiceTask_And_Create_Correct_Model.bpmn +34 -0
- package/skills/uipath-maestro-bpmn/validator/test/fixtures/known-good/Parse_MessageBoundaryEvent_And_Create_CorrectModel.bpmn +87 -0
- package/skills/uipath-maestro-bpmn/validator/test/fixtures/known-good/Parse_Script_Task_V2_And_Create_Correct_Model.bpmn +31 -0
- package/skills/uipath-maestro-bpmn/validator/test/fixtures/known-good/Parse_Sets_Containers_Properly.bpmn +129 -0
- package/skills/uipath-maestro-bpmn/validator/test/fixtures/known-good/ScriptWritesVariableThenGatewayBranches.bpmn +55 -0
- package/skills/uipath-maestro-bpmn/validator/test/fixtures/known-good/boundaryevent.bpmn +30 -0
- package/skills/uipath-maestro-bpmn/validator/test/fixtures/known-good/collapsed-subprocess.bpmn +83 -0
- package/skills/uipath-maestro-bpmn/validator/test/fixtures/known-good/connectable-types.bpmn +85 -0
- package/skills/uipath-maestro-bpmn/validator/test/fixtures/known-good/extensions-orchestrator-start-job.bpmn +80 -0
- package/skills/uipath-maestro-bpmn/validator/test/fixtures/known-good/multiparticipantpool.bpmn +77 -0
- package/skills/uipath-maestro-bpmn/validator/test/fixtures/known-good/nestedsubprocess.bpmn +36 -0
- package/skills/uipath-maestro-bpmn/validator/test/fixtures/known-good/simple.bpmn +59 -0
- package/skills/uipath-maestro-bpmn/validator/test/integration.test.mjs +146 -0
- package/skills/uipath-maestro-bpmn/validator/test/model-helpers.mjs +42 -0
- package/skills/uipath-maestro-bpmn/validator/test/ported-rule-tests.mjs +983 -0
- package/skills/uipath-maestro-bpmn/validator/test/run-tests.mjs +530 -0
- package/skills/uipath-maestro-bpmn/validator/uipath-moddle.v1.json +715 -0
- package/skills/uipath-maestro-bpmn/validator/validate-bpmn.mjs +164 -0
- package/skills/uipath-maestro-case/SKILL.md +5 -1
- package/skills/uipath-maestro-case/assets/templates/sdd-template-examples.md +10 -0
- package/skills/uipath-maestro-case/assets/templates/sdd-template.md +89 -17
- package/skills/uipath-maestro-case/references/bindings-and-expressions.md +26 -0
- package/skills/uipath-maestro-case/references/case-editing-operations.md +3 -0
- package/skills/uipath-maestro-case/references/implementation.md +11 -4
- package/skills/uipath-maestro-case/references/phase-0-interview.md +8 -7
- package/skills/uipath-maestro-case/references/phased-execution.md +2 -1
- package/skills/uipath-maestro-case/references/plugins/conditions/stage-exit-conditions/impl-json.md +21 -0
- package/skills/uipath-maestro-case/references/plugins/conditions/stage-exit-conditions/planning.md +2 -0
- package/skills/uipath-maestro-case/references/plugins/variables/global-vars/impl-json.md +15 -9
- package/skills/uipath-maestro-case/references/plugins/variables/io-binding/impl-json.md +131 -4
- package/skills/uipath-maestro-case/references/sdd-generation-rules.md +94 -17
- package/skills/uipath-maestro-flow/references/author/CAPABILITY.md +1 -1
- package/skills/uipath-maestro-flow/references/author/references/planning-arch.md +2 -2
- package/skills/uipath-maestro-flow/references/author/references/plugins/hitl/impl.md +11 -11
- package/skills/uipath-maestro-flow/references/author/references/plugins/hitl/planning.md +6 -6
- package/skills/uipath-maestro-flow/references/author/references/plugins/http/impl-connector.md +4 -1
- package/skills/uipath-maestro-flow/references/author/references/plugins/inline-agent/impl.md +157 -122
- package/skills/uipath-maestro-flow/references/author/references/plugins/inline-agent/planning.md +16 -4
- package/skills/uipath-maestro-flow/references/evaluate/references/commands-reference.md +8 -8
- package/skills/uipath-maestro-flow/references/evaluate/references/running-guide.md +3 -3
- package/skills/uipath-maestro-flow/references/shared/cli-commands.md +1 -1
- package/skills/uipath-planner/SKILL.md +19 -8
- package/skills/uipath-planner/assets/templates/agent-sdd-template.md +14 -0
- package/skills/uipath-planner/assets/templates/api-workflow-sdd-template.md +14 -0
- package/skills/uipath-planner/assets/templates/case-sdd-template.md +14 -0
- package/skills/uipath-planner/assets/templates/coded-app-sdd-template.md +14 -0
- package/skills/uipath-planner/assets/templates/flow-sdd-template.md +14 -0
- package/skills/uipath-planner/assets/templates/rpa-sdd-template.md +40 -5
- package/skills/uipath-planner/references/asdd-crosswalk-guide.md +55 -0
- package/skills/uipath-planner/references/attended-reauth-pattern-guide.md +43 -0
- package/skills/uipath-planner/references/multi-skill-patterns-guide.md +4 -0
- package/skills/uipath-planner/references/non-pdd-lane-guide.md +2 -0
- package/skills/uipath-planner/references/pdd-analysis-guide.md +24 -1
- package/skills/uipath-planner/references/pdd-driven-lane-guide.md +6 -2
- package/skills/uipath-planner/references/plan-and-tasks-format.md +2 -0
- package/skills/uipath-planner/references/platform-availability-guide.md +43 -0
- package/skills/uipath-planner/references/product-selection-guide.md +20 -0
- package/skills/uipath-planner/references/sdd-generation-guide.md +90 -21
- package/skills/uipath-planner/references/tenant-library-search-guide.md +5 -1
- package/skills/uipath-planner/scripts/docx-extract.sh +54 -0
- package/skills/uipath-planner/scripts/sdd-to-docx.sh +76 -0
- package/skills/uipath-platform/SKILL.md +54 -7
- package/skills/uipath-platform/references/context-grounding/index-management.md +123 -0
- package/skills/uipath-platform/references/data-fabric/bulk-import.md +86 -0
- package/skills/{uipath-data-fabric/references → uipath-platform/references/data-fabric}/choice-sets.md +33 -12
- package/skills/uipath-platform/references/data-fabric/data-fabric.md +352 -0
- package/skills/uipath-platform/references/data-fabric/entity-schema.md +444 -0
- package/skills/uipath-platform/references/data-fabric/file-attachments.md +122 -0
- package/skills/{uipath-data-fabric/references → uipath-platform/references/data-fabric}/filter-platform-contract.md +3 -3
- package/skills/{uipath-data-fabric/references → uipath-platform/references/data-fabric}/records-query.md +46 -10
- package/skills/uipath-platform/references/integration-service/http-request.md +15 -1
- package/skills/uipath-platform/references/integration-service/vendor-docs-registry.json +173 -0
- package/skills/uipath-platform/references/uip-commands.md +12 -4
- package/skills/uipath-review/SKILL.md +53 -12
- package/skills/uipath-review/references/agents/agent-common-issues.md +1 -1
- package/skills/uipath-review/references/agents/agent-grading-rubric.md +133 -0
- package/skills/uipath-review/references/agents/agent-review-checklist.md +23 -3
- package/skills/uipath-review/references/agents/agents-coded-rules.md +7 -0
- package/skills/uipath-review/references/agents/agents-lowcode-rules.md +13 -3
- package/skills/uipath-review/references/agents/guardrails/guardrails-review.md +198 -0
- package/skills/uipath-review/references/architecture-assessment-guide.md +2 -0
- package/skills/uipath-review/references/review-workflow-guide.md +9 -5
- package/skills/uipath-review/references/rule-catalog-workflow.md +3 -3
- package/skills/uipath-rpa/SKILL.md +18 -6
- package/skills/uipath-rpa/references/cli-reference.md +57 -3
- package/skills/uipath-rpa/references/common-activity-card.md +1 -1
- package/skills/uipath-rpa/references/connector-capabilities.md +2 -0
- package/skills/uipath-rpa/references/data-manipulation-guide.md +311 -0
- package/skills/uipath-rpa/references/environment-setup.md +0 -2
- package/skills/uipath-rpa/references/error-handling-guide.md +381 -0
- package/skills/uipath-rpa/references/is-connector-xaml-guide.md +127 -11
- package/skills/uipath-rpa/references/reframework-guide.md +2 -0
- package/skills/uipath-rpa/references/testing-guide.md +19 -0
- package/skills/uipath-rpa/references/ui-automation-guide.md +129 -18
- package/skills/uipath-rpa/references/uia-configure-target-workflows.md +22 -1
- package/skills/uipath-rpa/references/uia-elements-interaction-guide.md +93 -0
- package/skills/uipath-rpa/references/xaml/canvas-layout-guide.md +41 -4
- package/skills/uipath-rpa/references/xaml/common-pitfalls.md +12 -3
- package/skills/uipath-rpa/references/xaml/flowchart-guide.md +113 -0
- package/skills/uipath-rpa/references/xaml/xaml-basics-and-rules.md +5 -9
- package/skills/uipath-solution/SKILL.md +11 -8
- package/skills/uipath-solution/references/pack-and-deploy.md +18 -1
- package/skills/uipath-solution/references/solution-overview.md +3 -0
- package/skills/uipath-test/SKILL.md +2 -1
- package/skills/uipath-troubleshoot/SKILL.md +24 -26
- package/skills/uipath-troubleshoot/agents/depth-verifier.md +24 -25
- package/skills/uipath-troubleshoot/agents/hypothesis-generator.md +8 -4
- package/skills/uipath-troubleshoot/agents/hypothesis-tester.md +85 -38
- package/skills/uipath-troubleshoot/agents/presenter.md +7 -7
- package/skills/uipath-troubleshoot/agents/scope-checker.md +3 -6
- package/skills/uipath-troubleshoot/agents/shared.md +26 -21
- package/skills/uipath-troubleshoot/agents/triage.md +117 -49
- package/skills/uipath-troubleshoot/references/activity-packages/cv-activities/investigation_guide.md +43 -0
- package/skills/uipath-troubleshoot/references/activity-packages/cv-activities/overview.md +57 -0
- package/skills/uipath-troubleshoot/references/activity-packages/cv-activities/playbooks/cv-action-failed-after-find.md +100 -0
- package/skills/uipath-troubleshoot/references/activity-packages/cv-activities/playbooks/cv-cell-targeting-failures.md +111 -0
- package/skills/uipath-troubleshoot/references/activity-packages/cv-activities/playbooks/cv-element-not-found.md +125 -0
- package/skills/uipath-troubleshoot/references/activity-packages/cv-activities/playbooks/cv-get-text-empty-or-wrong-result.md +117 -0
- package/skills/uipath-troubleshoot/references/activity-packages/cv-activities/playbooks/cv-invalid-descriptor.md +102 -0
- package/skills/uipath-troubleshoot/references/activity-packages/cv-activities/playbooks/cv-scope-setup-failures.md +136 -0
- package/skills/uipath-troubleshoot/references/activity-packages/cv-activities/playbooks/cv-scroll-search-failures.md +114 -0
- package/skills/uipath-troubleshoot/references/activity-packages/cv-activities/playbooks/cv-server-auth-throttling-network.md +95 -0
- package/skills/uipath-troubleshoot/references/activity-packages/cv-activities/playbooks/cv-silent-failures-and-false-results.md +124 -0
- package/skills/uipath-troubleshoot/references/activity-packages/cv-activities/presentation.md +8 -0
- package/skills/uipath-troubleshoot/references/activity-packages/cv-activities/summary.md +15 -0
- package/skills/uipath-troubleshoot/references/activity-packages/database-activities/investigation_guide.md +42 -0
- package/skills/uipath-troubleshoot/references/activity-packages/database-activities/overview.md +41 -0
- package/skills/uipath-troubleshoot/references/activity-packages/database-activities/playbooks/connect-to-database-failures.md +104 -0
- package/skills/uipath-troubleshoot/references/activity-packages/database-activities/playbooks/execute-non-query-failures.md +97 -0
- package/skills/uipath-troubleshoot/references/activity-packages/database-activities/playbooks/execute-query-failures.md +134 -0
- package/skills/uipath-troubleshoot/references/activity-packages/database-activities/playbooks/start-transaction-failures.md +94 -0
- package/skills/uipath-troubleshoot/references/activity-packages/database-activities/presentation.md +10 -0
- package/skills/uipath-troubleshoot/references/activity-packages/database-activities/summary.md +10 -0
- package/skills/uipath-troubleshoot/references/activity-packages/excel-activities/investigation_guide.md +27 -9
- package/skills/uipath-troubleshoot/references/activity-packages/excel-activities/overview.md +51 -10
- package/skills/uipath-troubleshoot/references/activity-packages/excel-activities/playbooks/append-range-failures.md +146 -0
- package/skills/uipath-troubleshoot/references/activity-packages/excel-activities/playbooks/delete-range-failures.md +126 -0
- package/skills/uipath-troubleshoot/references/activity-packages/excel-activities/playbooks/excel-application-card-failures.md +139 -0
- package/skills/uipath-troubleshoot/references/activity-packages/excel-activities/playbooks/excel-application-scope-failures.md +118 -0
- package/skills/uipath-troubleshoot/references/activity-packages/excel-activities/playbooks/execute-macro-failures.md +149 -0
- package/skills/uipath-troubleshoot/references/activity-packages/excel-activities/playbooks/write-range-failures.md +145 -0
- package/skills/uipath-troubleshoot/references/activity-packages/excel-activities/presentation.md +12 -7
- package/skills/uipath-troubleshoot/references/activity-packages/excel-activities/summary.md +6 -0
- package/skills/uipath-troubleshoot/references/activity-packages/gsuite-activities/investigation_guide.md +1 -0
- package/skills/uipath-troubleshoot/references/activity-packages/gsuite-activities/overview.md +12 -4
- package/skills/uipath-troubleshoot/references/activity-packages/gsuite-activities/playbooks/connection-and-auth-failures.md +55 -0
- package/skills/uipath-troubleshoot/references/activity-packages/gsuite-activities/playbooks/drive-file-not-found.md +18 -10
- package/skills/uipath-troubleshoot/references/activity-packages/gsuite-activities/playbooks/get-newest-email-not-found.md +9 -8
- package/skills/uipath-troubleshoot/references/activity-packages/gsuite-activities/playbooks/invalid-or-null-input.md +68 -0
- package/skills/uipath-troubleshoot/references/activity-packages/gsuite-activities/playbooks/transient-and-timeout-errors.md +52 -0
- package/skills/uipath-troubleshoot/references/activity-packages/gsuite-activities/playbooks/upload-storage-quota-exceeded.md +1 -1
- package/skills/uipath-troubleshoot/references/activity-packages/gsuite-activities/summary.md +5 -2
- package/skills/uipath-troubleshoot/references/activity-packages/mail-activities/overview.md +28 -0
- package/skills/uipath-troubleshoot/references/activity-packages/mail-activities/playbooks/delete-outlook-mail-failures.md +105 -0
- package/skills/uipath-troubleshoot/references/activity-packages/mail-activities/playbooks/get-outlook-mail-failures.md +109 -0
- package/skills/uipath-troubleshoot/references/activity-packages/mail-activities/playbooks/move-outlook-mail-failures.md +97 -0
- package/skills/uipath-troubleshoot/references/activity-packages/mail-activities/playbooks/send-outlook-mail-failures.md +95 -0
- package/skills/uipath-troubleshoot/references/activity-packages/mail-activities/summary.md +8 -0
- package/skills/uipath-troubleshoot/references/activity-packages/o365-activities/investigation_guide.md +23 -0
- package/skills/uipath-troubleshoot/references/activity-packages/o365-activities/overview.md +9 -0
- package/skills/uipath-troubleshoot/references/activity-packages/o365-activities/playbooks/application-scope-misconfigured.md +49 -0
- package/skills/uipath-troubleshoot/references/activity-packages/o365-activities/playbooks/authentication-token-invalid.md +53 -0
- package/skills/uipath-troubleshoot/references/activity-packages/o365-activities/playbooks/copy-item-argument-null.md +34 -0
- package/skills/uipath-troubleshoot/references/activity-packages/o365-activities/playbooks/create-folder-invalid-path.md +36 -0
- package/skills/uipath-troubleshoot/references/activity-packages/o365-activities/playbooks/download-file-conversion-or-destination.md +40 -0
- package/skills/uipath-troubleshoot/references/activity-packages/o365-activities/playbooks/drive-item-not-found.md +1 -1
- package/skills/uipath-troubleshoot/references/activity-packages/o365-activities/playbooks/email-trigger-connection-event-failure.md +45 -0
- package/skills/uipath-troubleshoot/references/activity-packages/o365-activities/playbooks/insufficient-graph-scope.md +44 -0
- package/skills/uipath-troubleshoot/references/activity-packages/o365-activities/playbooks/legacy-mail-null-reference.md +43 -0
- package/skills/uipath-troubleshoot/references/activity-packages/o365-activities/playbooks/mail-folder-not-found.md +4 -3
- package/skills/uipath-troubleshoot/references/activity-packages/o365-activities/playbooks/mail-invalid-odata-query.md +43 -0
- package/skills/uipath-troubleshoot/references/activity-packages/o365-activities/playbooks/mail-message-not-found.md +45 -0
- package/skills/uipath-troubleshoot/references/activity-packages/o365-activities/playbooks/request-throttled.md +45 -0
- package/skills/uipath-troubleshoot/references/activity-packages/o365-activities/playbooks/send-mail-rejected.md +52 -0
- package/skills/uipath-troubleshoot/references/activity-packages/o365-activities/playbooks/transient-service-error.md +48 -0
- package/skills/uipath-troubleshoot/references/activity-packages/o365-activities/playbooks/upload-file-quota-or-size.md +47 -0
- package/skills/uipath-troubleshoot/references/activity-packages/o365-activities/summary.md +15 -1
- package/skills/uipath-troubleshoot/references/activity-packages/python-activities/investigation_guide.md +32 -0
- package/skills/uipath-troubleshoot/references/activity-packages/python-activities/overview.md +38 -0
- package/skills/uipath-troubleshoot/references/activity-packages/python-activities/playbooks/invoke-method-failures.md +57 -0
- package/skills/uipath-troubleshoot/references/activity-packages/python-activities/playbooks/invoke-python-method-pipe-is-broken.md +44 -0
- package/skills/uipath-troubleshoot/references/activity-packages/python-activities/playbooks/load-script-failures.md +62 -0
- package/skills/uipath-troubleshoot/references/activity-packages/python-activities/playbooks/python-path-not-valid.md +39 -0
- package/skills/uipath-troubleshoot/references/activity-packages/python-activities/playbooks/python-scope-architecture-version-mismatch.md +41 -0
- package/skills/uipath-troubleshoot/references/activity-packages/python-activities/playbooks/working-folder-relative-path.md +34 -0
- package/skills/uipath-troubleshoot/references/activity-packages/python-activities/presentation.md +9 -0
- package/skills/uipath-troubleshoot/references/activity-packages/python-activities/summary.md +12 -0
- package/skills/uipath-troubleshoot/references/activity-packages/system-activities/playbooks/get-asset-activity-bug-silent-failure.md +1 -1
- package/skills/uipath-troubleshoot/references/activity-packages/system-activities/playbooks/get-asset-robot-not-authenticated.md +29 -22
- package/skills/uipath-troubleshoot/references/activity-packages/system-activities/summary.md +1 -1
- package/skills/uipath-troubleshoot/references/activity-packages/web-activities/investigation_guide.md +30 -0
- package/skills/uipath-troubleshoot/references/activity-packages/web-activities/overview.md +36 -0
- package/skills/uipath-troubleshoot/references/activity-packages/web-activities/playbooks/deserialize-malformed-input.md +36 -0
- package/skills/uipath-troubleshoot/references/activity-packages/web-activities/playbooks/deserialize-null-input.md +35 -0
- package/skills/uipath-troubleshoot/references/activity-packages/web-activities/playbooks/deserialize-type-mismatch.md +40 -0
- package/skills/uipath-troubleshoot/references/activity-packages/web-activities/playbooks/http-client-null-reference.md +34 -0
- package/skills/uipath-troubleshoot/references/activity-packages/web-activities/playbooks/http-request-connection-failure.md +48 -0
- package/skills/uipath-troubleshoot/references/activity-packages/web-activities/playbooks/http-request-timeout.md +40 -0
- package/skills/uipath-troubleshoot/references/activity-packages/web-activities/playbooks/net-http-request-aggregate-failure.md +38 -0
- package/skills/uipath-troubleshoot/references/activity-packages/web-activities/summary.md +13 -0
- package/skills/uipath-troubleshoot/references/activity-packages/word-activities/investigation_guide.md +55 -0
- package/skills/uipath-troubleshoot/references/activity-packages/word-activities/overview.md +82 -0
- package/skills/uipath-troubleshoot/references/activity-packages/word-activities/playbooks/add-picture-failures.md +51 -0
- package/skills/uipath-troubleshoot/references/activity-packages/word-activities/playbooks/append-text-missing-container.md +31 -0
- package/skills/uipath-troubleshoot/references/activity-packages/word-activities/playbooks/append-text-zero-byte-file.md +32 -0
- package/skills/uipath-troubleshoot/references/activity-packages/word-activities/playbooks/export-pdf-com-hang.md +46 -0
- package/skills/uipath-troubleshoot/references/activity-packages/word-activities/playbooks/export-pdf-missing-output-dir.md +32 -0
- package/skills/uipath-troubleshoot/references/activity-packages/word-activities/playbooks/export-pdf-output-path-format.md +35 -0
- package/skills/uipath-troubleshoot/references/activity-packages/word-activities/playbooks/read-text-doc-format.md +30 -0
- package/skills/uipath-troubleshoot/references/activity-packages/word-activities/playbooks/read-text-missing-container.md +29 -0
- package/skills/uipath-troubleshoot/references/activity-packages/word-activities/playbooks/read-text-protected-view.md +32 -0
- package/skills/uipath-troubleshoot/references/activity-packages/word-activities/playbooks/replace-text-com-busy.md +35 -0
- package/skills/uipath-troubleshoot/references/activity-packages/word-activities/playbooks/replace-text-file-locked.md +33 -0
- package/skills/uipath-troubleshoot/references/activity-packages/word-activities/playbooks/replace-text-headers-textboxes-skipped.md +30 -0
- package/skills/uipath-troubleshoot/references/activity-packages/word-activities/playbooks/replace-text-length-limit.md +30 -0
- package/skills/uipath-troubleshoot/references/activity-packages/word-activities/playbooks/replace-text-loop-template-overwrite.md +31 -0
- package/skills/uipath-troubleshoot/references/activity-packages/word-activities/playbooks/replace-text-multiline-formatting.md +31 -0
- package/skills/uipath-troubleshoot/references/activity-packages/word-activities/playbooks/replace-text-silent-no-substitution.md +32 -0
- package/skills/uipath-troubleshoot/references/activity-packages/word-activities/playbooks/replace-text-version-mismatch.md +30 -0
- package/skills/uipath-troubleshoot/references/activity-packages/word-activities/playbooks/word-com-interop-failures.md +52 -0
- package/skills/uipath-troubleshoot/references/activity-packages/word-activities/playbooks/word-com-start-background-session0.md +50 -0
- package/skills/uipath-troubleshoot/references/activity-packages/word-activities/playbooks/word-export-pdf-com-wrong-thread.md +46 -0
- package/skills/uipath-troubleshoot/references/activity-packages/word-activities/playbooks/word-open-sharepoint-url-com-command-failed.md +45 -0
- package/skills/uipath-troubleshoot/references/activity-packages/word-activities/playbooks/word-scope-cannot-create-unknown-type.md +30 -0
- package/skills/uipath-troubleshoot/references/activity-packages/word-activities/playbooks/word-scope-com-not-installed.md +35 -0
- package/skills/uipath-troubleshoot/references/activity-packages/word-activities/playbooks/word-scope-file-corrupted.md +34 -0
- package/skills/uipath-troubleshoot/references/activity-packages/word-activities/playbooks/word-scope-file-path-not-found.md +36 -0
- package/skills/uipath-troubleshoot/references/activity-packages/word-activities/playbooks/word-scope-hangs-background-prompt.md +34 -0
- package/skills/uipath-troubleshoot/references/activity-packages/word-activities/presentation.md +11 -0
- package/skills/uipath-troubleshoot/references/activity-packages/word-activities/summary.md +38 -0
- package/skills/uipath-troubleshoot/references/products/integration-service/investigation_guide.md +9 -0
- package/skills/uipath-troubleshoot/references/products/integration-service/overview.md +7 -0
- package/skills/uipath-troubleshoot/references/products/integration-service/playbooks/connection-invalid.md +9 -0
- package/skills/uipath-troubleshoot/references/products/integration-service/playbooks/connector-aggregate-exception.md +30 -0
- package/skills/uipath-troubleshoot/references/products/integration-service/playbooks/connector-general-exception.md +43 -0
- package/skills/uipath-troubleshoot/references/products/integration-service/playbooks/connector-null-reference.md +36 -0
- package/skills/uipath-troubleshoot/references/products/integration-service/playbooks/connector-remote-exception.md +31 -0
- package/skills/uipath-troubleshoot/references/products/integration-service/playbooks/connector-runtime-exception.md +40 -0
- package/skills/uipath-troubleshoot/references/products/integration-service/summary.md +5 -0
- package/skills/uipath-troubleshoot/references/products/maestro/investigation_guide.md +2 -1
- package/skills/uipath-troubleshoot/references/products/orchestrator/investigation_guide.md +105 -10
- package/skills/uipath-troubleshoot/references/products/orchestrator/playbooks/job-faulted-logon-failure.md +1 -1
- package/skills/uipath-troubleshoot/references/summary.md +59 -3
- package/skills/uipath-troubleshoot/schemas/evidence.schema.md +48 -0
- package/skills/uipath-troubleshoot/schemas/hypotheses.schema.md +22 -7
- package/skills/uipath-troubleshoot/schemas/scope-check.schema.md +1 -1
- package/skills/uipath-troubleshoot/schemas/state.schema.md +81 -6
- package/version-manifest.json +2 -2
- package/skills/uipath-data-fabric/SKILL.md +0 -239
- package/skills/uipath-data-fabric/references/bulk-import.md +0 -73
- package/skills/uipath-data-fabric/references/entity-schema.md +0 -391
- package/skills/uipath-data-fabric/references/file-attachments.md +0 -65
- package/skills/uipath-maestro-bpmn/.maintenance/README.md +0 -241
- package/skills/uipath-maestro-bpmn/.maintenance/check-all.sh +0 -41
- package/skills/uipath-maestro-bpmn/.maintenance/check-anchors.sh +0 -83
- package/skills/uipath-maestro-bpmn/.maintenance/check-depth.sh +0 -132
- package/skills/uipath-maestro-bpmn/.maintenance/check-link-text.sh +0 -123
- package/skills/uipath-maestro-bpmn/.maintenance/check-links.sh +0 -70
- package/skills/uipath-maestro-bpmn/.maintenance/check-orphans.sh +0 -93
- package/skills/uipath-maestro-bpmn/.maintenance/check-plugin-pairs.sh +0 -39
- package/skills/uipath-maestro-bpmn/.maintenance/check-real-pack.sh +0 -79
- package/skills/uipath-maestro-bpmn/.maintenance/check-template.sh +0 -42
- package/skills/uipath-maestro-bpmn/.maintenance/check-uip-commands.sh +0 -266
- package/skills/uipath-maestro-bpmn/.maintenance/check-validation-fixtures.py +0 -1255
- package/skills/uipath-maestro-bpmn/.maintenance/check-validation-fixtures.sh +0 -8
- package/skills/uipath-maestro-bpmn/fixtures/validation/README.md +0 -94
- package/skills/uipath-maestro-bpmn/fixtures/validation/agent-invocation/agent-invocation.bpmn +0 -62
- package/skills/uipath-maestro-bpmn/fixtures/validation/agent-invocation/bindings_v2.json +0 -37
- package/skills/uipath-maestro-bpmn/fixtures/validation/agent-invocation/entry-points.json +0 -21
- package/skills/uipath-maestro-bpmn/fixtures/validation/agent-invocation/operate.json +0 -9
- package/skills/uipath-maestro-bpmn/fixtures/validation/agent-invocation/package-descriptor.json +0 -10
- package/skills/uipath-maestro-bpmn/fixtures/validation/agent-invocation/project.uiproj +0 -7
- package/skills/uipath-maestro-bpmn/fixtures/validation/contract-variants/bindings_v2.json +0 -68
- package/skills/uipath-maestro-bpmn/fixtures/validation/contract-variants/contract-variants.bpmn +0 -270
- package/skills/uipath-maestro-bpmn/fixtures/validation/contract-variants/entry-points.json +0 -25
- package/skills/uipath-maestro-bpmn/fixtures/validation/contract-variants/operate.json +0 -9
- package/skills/uipath-maestro-bpmn/fixtures/validation/contract-variants/package-descriptor.json +0 -10
- package/skills/uipath-maestro-bpmn/fixtures/validation/contract-variants/project.uiproj +0 -7
- package/skills/uipath-maestro-bpmn/fixtures/validation/gateway-boundary-error/bindings_v2.json +0 -19
- package/skills/uipath-maestro-bpmn/fixtures/validation/gateway-boundary-error/entry-points.json +0 -25
- package/skills/uipath-maestro-bpmn/fixtures/validation/gateway-boundary-error/gateway-boundary-error.bpmn +0 -91
- package/skills/uipath-maestro-bpmn/fixtures/validation/gateway-boundary-error/operate.json +0 -9
- package/skills/uipath-maestro-bpmn/fixtures/validation/gateway-boundary-error/package-descriptor.json +0 -10
- package/skills/uipath-maestro-bpmn/fixtures/validation/gateway-boundary-error/project.uiproj +0 -7
- package/skills/uipath-maestro-bpmn/fixtures/validation/imported-brownfield-preservation/bindings_v2.json +0 -4
- package/skills/uipath-maestro-bpmn/fixtures/validation/imported-brownfield-preservation/entry-points.json +0 -25
- package/skills/uipath-maestro-bpmn/fixtures/validation/imported-brownfield-preservation/imported-brownfield-preservation.bpmn +0 -63
- package/skills/uipath-maestro-bpmn/fixtures/validation/imported-brownfield-preservation/operate.json +0 -9
- package/skills/uipath-maestro-bpmn/fixtures/validation/imported-brownfield-preservation/package-descriptor.json +0 -10
- package/skills/uipath-maestro-bpmn/fixtures/validation/imported-brownfield-preservation/project.uiproj +0 -7
- package/skills/uipath-maestro-bpmn/fixtures/validation/integration-service-enriched/bindings_v2.json +0 -52
- package/skills/uipath-maestro-bpmn/fixtures/validation/integration-service-enriched/entry-points.json +0 -21
- package/skills/uipath-maestro-bpmn/fixtures/validation/integration-service-enriched/integration-service-enriched.bpmn +0 -65
- package/skills/uipath-maestro-bpmn/fixtures/validation/integration-service-enriched/operate.json +0 -9
- package/skills/uipath-maestro-bpmn/fixtures/validation/integration-service-enriched/package-descriptor.json +0 -10
- package/skills/uipath-maestro-bpmn/fixtures/validation/integration-service-enriched/project.uiproj +0 -7
- package/skills/uipath-maestro-bpmn/fixtures/validation/linear-process/bindings_v2.json +0 -4
- package/skills/uipath-maestro-bpmn/fixtures/validation/linear-process/entry-points.json +0 -25
- package/skills/uipath-maestro-bpmn/fixtures/validation/linear-process/linear-process.bpmn +0 -55
- package/skills/uipath-maestro-bpmn/fixtures/validation/linear-process/operate.json +0 -9
- package/skills/uipath-maestro-bpmn/fixtures/validation/linear-process/package-descriptor.json +0 -10
- package/skills/uipath-maestro-bpmn/fixtures/validation/linear-process/project.uiproj +0 -7
- package/skills/uipath-maestro-bpmn/fixtures/validation/registry-coverage-matrix/bindings_v2.json +0 -20
- package/skills/uipath-maestro-bpmn/fixtures/validation/registry-coverage-matrix/entry-points.json +0 -17
- package/skills/uipath-maestro-bpmn/fixtures/validation/registry-coverage-matrix/operate.json +0 -9
- package/skills/uipath-maestro-bpmn/fixtures/validation/registry-coverage-matrix/package-descriptor.json +0 -10
- package/skills/uipath-maestro-bpmn/fixtures/validation/registry-coverage-matrix/project.uiproj +0 -7
- package/skills/uipath-maestro-bpmn/fixtures/validation/registry-coverage-matrix/registry-coverage-matrix.bpmn +0 -207
- package/skills/uipath-maestro-bpmn/fixtures/validation/subprocess-multi-instance/bindings_v2.json +0 -4
- package/skills/uipath-maestro-bpmn/fixtures/validation/subprocess-multi-instance/entry-points.json +0 -33
- package/skills/uipath-maestro-bpmn/fixtures/validation/subprocess-multi-instance/operate.json +0 -9
- package/skills/uipath-maestro-bpmn/fixtures/validation/subprocess-multi-instance/package-descriptor.json +0 -10
- package/skills/uipath-maestro-bpmn/fixtures/validation/subprocess-multi-instance/project.uiproj +0 -7
- package/skills/uipath-maestro-bpmn/fixtures/validation/subprocess-multi-instance/subprocess-multi-instance.bpmn +0 -94
- package/skills/uipath-maestro-bpmn/fixtures/validation/wrapper-family-contract/bindings_v2.json +0 -124
- package/skills/uipath-maestro-bpmn/fixtures/validation/wrapper-family-contract/entry-points.json +0 -25
- package/skills/uipath-maestro-bpmn/fixtures/validation/wrapper-family-contract/operate.json +0 -9
- package/skills/uipath-maestro-bpmn/fixtures/validation/wrapper-family-contract/package-descriptor.json +0 -10
- package/skills/uipath-maestro-bpmn/fixtures/validation/wrapper-family-contract/project.uiproj +0 -7
- package/skills/uipath-maestro-bpmn/fixtures/validation/wrapper-family-contract/wrapper-family-contract.bpmn +0 -151
- package/skills/uipath-maestro-bpmn/references/author/CAPABILITY.md +0 -141
- package/skills/uipath-maestro-bpmn/references/author/references/brownfield.md +0 -48
- package/skills/uipath-maestro-bpmn/references/author/references/editing-operations.md +0 -75
- package/skills/uipath-maestro-bpmn/references/author/references/greenfield.md +0 -41
- package/skills/uipath-maestro-bpmn/references/author/references/planning-arch.md +0 -82
- package/skills/uipath-maestro-bpmn/references/author/references/planning-impl.md +0 -104
- package/skills/uipath-maestro-bpmn/references/author/references/plugins/agents/impl.md +0 -34
- package/skills/uipath-maestro-bpmn/references/author/references/plugins/agents/planning.md +0 -38
- package/skills/uipath-maestro-bpmn/references/author/references/plugins/api-workflows/impl.md +0 -37
- package/skills/uipath-maestro-bpmn/references/author/references/plugins/api-workflows/planning.md +0 -28
- package/skills/uipath-maestro-bpmn/references/author/references/plugins/business-rules/impl.md +0 -36
- package/skills/uipath-maestro-bpmn/references/author/references/plugins/business-rules/planning.md +0 -29
- package/skills/uipath-maestro-bpmn/references/author/references/plugins/call-activity-subprocess/impl.md +0 -67
- package/skills/uipath-maestro-bpmn/references/author/references/plugins/call-activity-subprocess/planning.md +0 -29
- package/skills/uipath-maestro-bpmn/references/author/references/plugins/connectors/impl.md +0 -29
- package/skills/uipath-maestro-bpmn/references/author/references/plugins/connectors/planning.md +0 -33
- package/skills/uipath-maestro-bpmn/references/author/references/plugins/gateways/impl.md +0 -32
- package/skills/uipath-maestro-bpmn/references/author/references/plugins/gateways/planning.md +0 -34
- package/skills/uipath-maestro-bpmn/references/author/references/plugins/hitl/impl.md +0 -42
- package/skills/uipath-maestro-bpmn/references/author/references/plugins/hitl/planning.md +0 -30
- package/skills/uipath-maestro-bpmn/references/author/references/plugins/integration-service/impl.md +0 -134
- package/skills/uipath-maestro-bpmn/references/author/references/plugins/integration-service/planning.md +0 -67
- package/skills/uipath-maestro-bpmn/references/author/references/plugins/multi-instance/impl.md +0 -50
- package/skills/uipath-maestro-bpmn/references/author/references/plugins/multi-instance/planning.md +0 -29
- package/skills/uipath-maestro-bpmn/references/author/references/plugins/queues/impl.md +0 -29
- package/skills/uipath-maestro-bpmn/references/author/references/plugins/queues/planning.md +0 -29
- package/skills/uipath-maestro-bpmn/references/author/references/plugins/rpa-jobs/impl.md +0 -35
- package/skills/uipath-maestro-bpmn/references/author/references/plugins/rpa-jobs/planning.md +0 -29
- package/skills/uipath-maestro-bpmn/references/author/references/plugins/script/impl.md +0 -118
- package/skills/uipath-maestro-bpmn/references/author/references/plugins/script/planning.md +0 -30
- package/skills/uipath-maestro-bpmn/references/author/references/plugins/sequence-flows/impl.md +0 -30
- package/skills/uipath-maestro-bpmn/references/author/references/plugins/sequence-flows/planning.md +0 -30
- package/skills/uipath-maestro-bpmn/references/author/references/plugins/service-tasks/impl.md +0 -33
- package/skills/uipath-maestro-bpmn/references/author/references/plugins/service-tasks/planning.md +0 -31
- package/skills/uipath-maestro-bpmn/references/author/references/plugins/signals/impl.md +0 -33
- package/skills/uipath-maestro-bpmn/references/author/references/plugins/signals/planning.md +0 -46
- package/skills/uipath-maestro-bpmn/references/author/references/plugins/start-end-events/impl.md +0 -59
- package/skills/uipath-maestro-bpmn/references/author/references/plugins/start-end-events/planning.md +0 -37
- package/skills/uipath-maestro-bpmn/references/author/references/plugins/waits-triggers/impl.md +0 -40
- package/skills/uipath-maestro-bpmn/references/author/references/plugins/waits-triggers/planning.md +0 -34
- package/skills/uipath-maestro-bpmn/references/author/references/supported-elements.md +0 -123
- package/skills/uipath-maestro-bpmn/references/author/references/task-recipes/README.md +0 -29
- package/skills/uipath-maestro-bpmn/references/author/references/task-recipes/agent-job.md +0 -36
- package/skills/uipath-maestro-bpmn/references/author/references/task-recipes/api-workflow.md +0 -29
- package/skills/uipath-maestro-bpmn/references/author/references/task-recipes/business-rule.md +0 -18
- package/skills/uipath-maestro-bpmn/references/author/references/task-recipes/call-activity.md +0 -56
- package/skills/uipath-maestro-bpmn/references/author/references/task-recipes/hitl.md +0 -17
- package/skills/uipath-maestro-bpmn/references/author/references/task-recipes/http-request.md +0 -119
- package/skills/uipath-maestro-bpmn/references/author/references/task-recipes/queue.md +0 -21
- package/skills/uipath-maestro-bpmn/references/author/references/task-recipes/rpa-job.md +0 -23
- package/skills/uipath-maestro-bpmn/references/author/references/validation.md +0 -118
- package/skills/uipath-maestro-bpmn/references/shared/bpmn-xml-contract.md +0 -197
- package/skills/uipath-maestro-bpmn/references/shared/cli-conventions.md +0 -46
- package/skills/uipath-maestro-bpmn/references/shared/error-handling.md +0 -82
- package/skills/uipath-maestro-bpmn/references/shared/variables-bindings-expressions.md +0 -150
- package/skills/uipath-maestro-bpmn/references/shared/wrapper-shells.md +0 -554
- /package/skills/uipath-maestro-bpmn/references/{shared/expression-authoring.md → expression-authoring.md} +0 -0
- /package/skills/uipath-maestro-bpmn/references/{shared/public-safety.md → public-safety.md} +0 -0
- /package/{agents → skills/uipath-rpa/agents}/uipath-project-discovery-agent.md +0 -0
|
@@ -0,0 +1,1635 @@
|
|
|
1
|
+
import { test } from 'node:test'
|
|
2
|
+
import assert from 'node:assert/strict'
|
|
3
|
+
import { readFileSync, mkdtempSync, writeFileSync, rmSync, mkdirSync, existsSync } from 'node:fs'
|
|
4
|
+
import { resolve, dirname, join } from 'node:path'
|
|
5
|
+
import { fileURLToPath } from 'node:url'
|
|
6
|
+
import { tmpdir } from 'node:os'
|
|
7
|
+
import { execFileSync } from 'node:child_process'
|
|
8
|
+
import { validateIntent, resolveMetric, buildWidgetFile, generateViewFile, generateKeyedDetailViewFile, buildViewSpec, compileColumns, compileDetailWidgets, emit, parseEvent, classifyEditIntent, resolveChangeMetric, widgetLayoutGroup, widgetGetsDetailView, setWidgetsDir, VALID_DISPLAY_TYPES, metricModuleSpecifier, buildVersions, readScaffoldVersion, INTENT_SCHEMA_VERSION, STATE_SCHEMA_VERSION, scaffoldDrift, runIntentMigrations, VALID_EDIT_OPS, MIN_SDK_VERSION } from '../build-dashboard.mjs'
|
|
9
|
+
|
|
10
|
+
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
11
|
+
const REGISTRY_PATH = resolve(__dirname, '../capability-registry.json')
|
|
12
|
+
|
|
13
|
+
const registry = JSON.parse(readFileSync(REGISTRY_PATH, 'utf8'))
|
|
14
|
+
|
|
15
|
+
// Widget generator templates ship inside the committed starter-kit .tar.gz (the skill
|
|
16
|
+
// carries no template source). Extract it with the OS `tar` — the same tool the build
|
|
17
|
+
// uses at prewarm time. Falls back to the apps-dev-tools sibling source if absent.
|
|
18
|
+
const TAR = process.platform === 'win32'
|
|
19
|
+
? join(process.env.SystemRoot || 'C:\\Windows', 'System32', 'tar.exe')
|
|
20
|
+
: 'tar'
|
|
21
|
+
function locateWidgetTemplates() {
|
|
22
|
+
const archive = resolve(__dirname, '../../../fixtures/governance-dashboard-starter-kit.tar.gz')
|
|
23
|
+
if (existsSync(archive)) {
|
|
24
|
+
const dest = mkdtempSync(join(tmpdir(), 'kit-test-'))
|
|
25
|
+
try { execFileSync(TAR, ['-xzf', archive, '-C', dest], { stdio: 'pipe' }) } catch { /* fall through to sibling */ }
|
|
26
|
+
const w = join(dest, '_gen', 'widgets')
|
|
27
|
+
if (existsSync(w)) return w
|
|
28
|
+
}
|
|
29
|
+
const sibling = resolve(__dirname, '../../../../../../../apps-dev-tools/uipath-dashboard-starter-kit/widgets')
|
|
30
|
+
return existsSync(sibling) ? sibling : null
|
|
31
|
+
}
|
|
32
|
+
const WIDGETS_DIR_FOR_TESTS = locateWidgetTemplates()
|
|
33
|
+
if (WIDGETS_DIR_FOR_TESTS) setWidgetsDir(WIDGETS_DIR_FOR_TESTS)
|
|
34
|
+
|
|
35
|
+
// ── Phase 2: version stamps ───────────────────────────────────────────────────
|
|
36
|
+
// The scaffold version is read from the extracted kit (_gen/starter-kit.json) and
|
|
37
|
+
// passed in — there is no module-level SCAFFOLD_VERSION const anymore.
|
|
38
|
+
test('buildVersions stamps skill/scaffold/intentSchema/sdk', () => {
|
|
39
|
+
const v = buildVersions('1.4.1', '2.7.0')
|
|
40
|
+
assert.equal(v.scaffold, '2.7.0')
|
|
41
|
+
assert.equal(v.intentSchema, INTENT_SCHEMA_VERSION)
|
|
42
|
+
assert.equal(v.sdk, '1.4.1')
|
|
43
|
+
assert.ok(typeof v.skill === 'string' && v.skill.length > 0)
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
test('buildVersions tolerates a missing sdk version', () => {
|
|
47
|
+
assert.equal(buildVersions().sdk, null)
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
test('STATE_SCHEMA_VERSION is 2', () => {
|
|
51
|
+
assert.equal(STATE_SCHEMA_VERSION, 2)
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
// ── Phase 2: scaffold drift (current version supplied by the caller) ──────────
|
|
55
|
+
test('scaffoldDrift: none when stamped scaffold equals current', () => {
|
|
56
|
+
assert.equal(scaffoldDrift({ versions: { scaffold: '2.7.0' } }, '2.7.0'), null)
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
test('scaffoldDrift: detected when stamped differs', () => {
|
|
60
|
+
assert.deepEqual(scaffoldDrift({ versions: { scaffold: '0.9.0' } }, '2.7.0'), { from: '0.9.0', to: '2.7.0' })
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
test('scaffoldDrift: detected for a pre-versioning project (no versions block)', () => {
|
|
64
|
+
assert.deepEqual(scaffoldDrift({ widgets: {} }, '2.7.0'), { from: null, to: '2.7.0' })
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
test('readScaffoldVersion reads the version from the extracted _gen/starter-kit.json', () => {
|
|
68
|
+
const dir = mkdtempSync(join(tmpdir(), 'scaffver-'))
|
|
69
|
+
mkdirSync(join(dir, '_gen'), { recursive: true })
|
|
70
|
+
writeFileSync(join(dir, '_gen', 'starter-kit.json'), JSON.stringify({ name: 'x', version: '9.9.9' }))
|
|
71
|
+
assert.equal(readScaffoldVersion(dir), '9.9.9')
|
|
72
|
+
assert.equal(readScaffoldVersion(join(dir, 'nope')), '0.0.0') // missing → safe default
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
// ── Phase 2: migrations + UPGRADE op ──────────────────────────────────────────
|
|
76
|
+
test('runIntentMigrations: no-op when already at target', async () => {
|
|
77
|
+
const out = await runIntentMigrations({ schemaVersion: 2, metrics: [] }, '/no/such/dir', 2)
|
|
78
|
+
assert.equal(out.schemaVersion, 2)
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
test('runIntentMigrations: applies a sequenced migration from a dir', async () => {
|
|
82
|
+
const dir = mkdtempSync(join(tmpdir(), 'mig-'))
|
|
83
|
+
writeFileSync(join(dir, 'intent-v2-to-v3.mjs'), 'export function migrate(i){ return { ...i, bumped: true } }')
|
|
84
|
+
try {
|
|
85
|
+
const out = await runIntentMigrations({ schemaVersion: 2, metrics: [] }, dir, 3)
|
|
86
|
+
assert.equal(out.bumped, true)
|
|
87
|
+
assert.equal(out.schemaVersion, 3)
|
|
88
|
+
} finally { rmSync(dir, { recursive: true, force: true }) }
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
test('VALID_EDIT_OPS includes UPGRADE', () => {
|
|
92
|
+
assert.ok(VALID_EDIT_OPS.includes('UPGRADE'))
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
test('classifyEditIntent accepts a no-target UPGRADE op', () => {
|
|
96
|
+
const plan = classifyEditIntent({ projectDir: '/p', op: 'UPGRADE' })
|
|
97
|
+
assert.equal(plan.ops[0].op, 'UPGRADE')
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
// ── Phase 3: zip library ──────────────────────────────────────────────────────
|
|
101
|
+
// NOTE: zip/unzip + contentHash + zip-slip tests moved to apps-dev-tools
|
|
102
|
+
// (uipath-dashboard-starter-kit/tests/zip.test.mjs) along with zip.mjs — the
|
|
103
|
+
// skill no longer ships any zip code or scaffold source.
|
|
104
|
+
|
|
105
|
+
function resolveT1(metricName) {
|
|
106
|
+
const entry = registry.t1[metricName]
|
|
107
|
+
if (!entry) return null
|
|
108
|
+
return { tier: 'T1', entry }
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function resolveAlias(userText) {
|
|
112
|
+
const lower = userText.toLowerCase()
|
|
113
|
+
for (const [key, entry] of Object.entries(registry.t1)) {
|
|
114
|
+
if (entry.aliases.some(a => lower.includes(a))) return { tier: 'T1', key, entry }
|
|
115
|
+
}
|
|
116
|
+
for (const [key, entry] of Object.entries(registry.t2)) {
|
|
117
|
+
if (entry.aliases.some(a => lower.includes(a))) return { tier: 'T2', key, entry }
|
|
118
|
+
}
|
|
119
|
+
return null
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
test('T1 exact name lookup returns entry', () => {
|
|
123
|
+
const result = resolveT1('job-failures')
|
|
124
|
+
assert.ok(result)
|
|
125
|
+
assert.equal(result.tier, 'T1')
|
|
126
|
+
assert.equal(result.entry.template, 'data-table')
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
test('T1 exact name lookup returns null for unknown metric', () => {
|
|
130
|
+
const result = resolveT1('completely-unknown-metric')
|
|
131
|
+
assert.equal(result, null)
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
test('alias lookup finds job-failures by natural language', () => {
|
|
135
|
+
const result = resolveAlias('show me faulted jobs')
|
|
136
|
+
assert.ok(result)
|
|
137
|
+
assert.equal(result.tier, 'T1')
|
|
138
|
+
assert.equal(result.key, 'job-failures')
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
test('alias lookup finds T2 jobs-duration-threshold', () => {
|
|
142
|
+
const result = resolveAlias('long running jobs over threshold')
|
|
143
|
+
assert.ok(result)
|
|
144
|
+
assert.equal(result.tier, 'T2')
|
|
145
|
+
assert.equal(result.key, 'jobs-duration-threshold')
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
test('alias lookup returns null for unknown text', () => {
|
|
149
|
+
const result = resolveAlias('completely unrelated nonsense xyz')
|
|
150
|
+
assert.equal(result, null)
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
test('all T1 entries have required fields', () => {
|
|
154
|
+
for (const [key, entry] of Object.entries(registry.t1)) {
|
|
155
|
+
assert.ok(entry.template, `${key} missing template`)
|
|
156
|
+
assert.ok(entry.description, `${key} missing description`)
|
|
157
|
+
assert.ok(Array.isArray(entry.aliases) && entry.aliases.length > 0, `${key} missing aliases`)
|
|
158
|
+
assert.ok(entry.defaults?.title, `${key} missing defaults.title`)
|
|
159
|
+
// SDK fields must NOT be present — registry is hints-only
|
|
160
|
+
assert.equal(entry.sdkImport, undefined, `${key} should not have sdkImport (removed)`)
|
|
161
|
+
assert.equal(entry.sdkService, undefined, `${key} should not have sdkService (removed)`)
|
|
162
|
+
assert.equal(entry.sdkMethod, undefined, `${key} should not have sdkMethod (removed)`)
|
|
163
|
+
assert.equal(entry.responseType, undefined, `${key} should not have responseType (removed)`)
|
|
164
|
+
}
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
test('all T2 entries have required fields', () => {
|
|
168
|
+
for (const [key, entry] of Object.entries(registry.t2)) {
|
|
169
|
+
assert.ok(entry.description, `${key} missing description`)
|
|
170
|
+
assert.ok(Array.isArray(entry.aliases) && entry.aliases.length > 0, `${key} missing aliases`)
|
|
171
|
+
// SDK fields must NOT be present — registry is hints-only
|
|
172
|
+
assert.equal(entry.sdkImport, undefined, `${key} should not have sdkImport (removed)`)
|
|
173
|
+
assert.equal(entry.sdkService, undefined, `${key} should not have sdkService (removed)`)
|
|
174
|
+
assert.equal(entry.method, undefined, `${key} should not have method (removed)`)
|
|
175
|
+
assert.equal(entry.filterField, undefined, `${key} should not have filterField (removed)`)
|
|
176
|
+
assert.equal(entry.filterType, undefined, `${key} should not have filterType (removed)`)
|
|
177
|
+
assert.equal(entry.sortField, undefined, `${key} should not have sortField (removed)`)
|
|
178
|
+
assert.equal(entry.sortDir, undefined, `${key} should not have sortDir (removed)`)
|
|
179
|
+
assert.equal(entry.defaultDisplayAs, undefined, `${key} should not have defaultDisplayAs (removed)`)
|
|
180
|
+
}
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
test('all hardRefuse entries have pattern, reason, alternative', () => {
|
|
184
|
+
for (const entry of registry.hardRefuse) {
|
|
185
|
+
assert.ok(entry.pattern, 'hardRefuse entry missing pattern')
|
|
186
|
+
assert.ok(entry.reason, 'hardRefuse entry missing reason')
|
|
187
|
+
assert.ok(entry.alternative, 'hardRefuse entry missing alternative')
|
|
188
|
+
// Verify pattern is a valid regex
|
|
189
|
+
assert.doesNotThrow(() => new RegExp(entry.pattern), `invalid regex: ${entry.pattern}`)
|
|
190
|
+
}
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
test('hardRefuse matches cost/dollar phrases', () => {
|
|
194
|
+
const dollarEntry = registry.hardRefuse.find(e => e.pattern.includes('dollar'))
|
|
195
|
+
assert.ok(dollarEntry)
|
|
196
|
+
assert.ok(new RegExp(dollarEntry.pattern).test('cost in dollars'))
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
test('hardRefuse does not collide with valid T1 aliases', () => {
|
|
200
|
+
for (const refuseEntry of registry.hardRefuse) {
|
|
201
|
+
const re = new RegExp(refuseEntry.pattern)
|
|
202
|
+
for (const [key, t1Entry] of Object.entries(registry.t1)) {
|
|
203
|
+
for (const alias of t1Entry.aliases) {
|
|
204
|
+
assert.ok(!re.test(alias), `hardRefuse pattern "${refuseEntry.pattern}" collides with T1 alias "${alias}" (${key})`)
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
// ── validateIntent tests ──────────────────────────────────────────────────────
|
|
211
|
+
|
|
212
|
+
test('validateIntent: valid T1 intent passes', () => {
|
|
213
|
+
const errors = validateIntent({
|
|
214
|
+
schemaVersion: 2, dashboardName: 'My Dashboard',
|
|
215
|
+
timeRange: '30d',
|
|
216
|
+
metrics: [{ name: 'agent-memory-timeline', tier: 'T1', title: 'Agent Memory' }]
|
|
217
|
+
})
|
|
218
|
+
assert.deepEqual(errors, [])
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
test('validateIntent: T1 metric without fnBody is valid (fnBody no longer required in v2)', () => {
|
|
222
|
+
const errors = validateIntent({
|
|
223
|
+
schemaVersion: 2, dashboardName: 'x', timeRange: '30d',
|
|
224
|
+
metrics: [{ name: 'agent-memory-timeline', tier: 'T1', title: 'Agent Memory' }]
|
|
225
|
+
})
|
|
226
|
+
assert.deepEqual(errors, [])
|
|
227
|
+
})
|
|
228
|
+
|
|
229
|
+
test('validateIntent: rejects T1 metric without title', () => {
|
|
230
|
+
const errors = validateIntent({
|
|
231
|
+
schemaVersion: 2, dashboardName: 'x', timeRange: '30d',
|
|
232
|
+
metrics: [{ name: 'agent-memory-timeline', tier: 'T1' }]
|
|
233
|
+
})
|
|
234
|
+
assert.ok(errors.some(e => e.includes('T1') && e.includes('title')))
|
|
235
|
+
})
|
|
236
|
+
|
|
237
|
+
test('validateIntent: T2 metric without fnBody is valid (fnBody no longer required in v2)', () => {
|
|
238
|
+
const errors = validateIntent({
|
|
239
|
+
schemaVersion: 2, dashboardName: 'x', timeRange: '7d',
|
|
240
|
+
metrics: [{ name: 'jobs-by-state', tier: 'T2', title: 'Jobs', params: { value: 'Faulted' } }]
|
|
241
|
+
})
|
|
242
|
+
assert.deepEqual(errors, [])
|
|
243
|
+
})
|
|
244
|
+
|
|
245
|
+
test('validateIntent: rejects missing dashboardName', () => {
|
|
246
|
+
const errors = validateIntent({ schemaVersion: 2, timeRange: '30d', metrics: [{ name: 'x', tier: 'T1' }] })
|
|
247
|
+
assert.ok(errors.some(e => e.includes('dashboardName')))
|
|
248
|
+
})
|
|
249
|
+
|
|
250
|
+
test('validateIntent: rejects invalid timeRange', () => {
|
|
251
|
+
const errors = validateIntent({ schemaVersion: 2, dashboardName: 'x', timeRange: '2w', metrics: [{ name: 'x', tier: 'T1' }] })
|
|
252
|
+
assert.ok(errors.some(e => e.includes('timeRange')))
|
|
253
|
+
})
|
|
254
|
+
|
|
255
|
+
test('validateIntent: rejects T2 metric without params', () => {
|
|
256
|
+
const errors = validateIntent({ schemaVersion: 2, dashboardName: 'x', timeRange: '7d', metrics: [{ name: 'queue-failure-threshold', tier: 'T2' }] })
|
|
257
|
+
assert.ok(errors.some(e => e.includes('T2') && e.includes('params')))
|
|
258
|
+
})
|
|
259
|
+
|
|
260
|
+
test('validateIntent: T3 metric without fnBody is valid when displayAs and title present (v2 contract)', () => {
|
|
261
|
+
const errors = validateIntent({ schemaVersion: 2, dashboardName: 'x', timeRange: '7d', metrics: [{ name: 'custom', tier: 'T3', displayAs: 'ranked-table', title: 'Custom' }] })
|
|
262
|
+
assert.deepEqual(errors, [])
|
|
263
|
+
})
|
|
264
|
+
|
|
265
|
+
// ── resolveMetric tests ───────────────────────────────────────────────────────
|
|
266
|
+
|
|
267
|
+
test('resolveMetric: T1 known name returns entry with template', () => {
|
|
268
|
+
const result = resolveMetric({ name: 'job-failures', tier: 'T1' })
|
|
269
|
+
assert.equal(result.tier, 'T1')
|
|
270
|
+
assert.equal(result.entry.template, 'data-table')
|
|
271
|
+
})
|
|
272
|
+
|
|
273
|
+
test('resolveMetric: T2 known name returns entry with description', () => {
|
|
274
|
+
const result = resolveMetric({ name: 'jobs-duration-threshold', tier: 'T2', params: { threshold: 300, direction: 'gt' } })
|
|
275
|
+
assert.equal(result.tier, 'T2')
|
|
276
|
+
assert.ok(result.entry.description)
|
|
277
|
+
})
|
|
278
|
+
|
|
279
|
+
test('resolveMetric: T3 always resolves with null entry', () => {
|
|
280
|
+
const result = resolveMetric({ name: 'custom-thing', tier: 'T3', fnBody: 'return []', displayAs: 'kpi-card', title: 'X' })
|
|
281
|
+
assert.equal(result.tier, 'T3')
|
|
282
|
+
assert.equal(result.entry, null)
|
|
283
|
+
})
|
|
284
|
+
|
|
285
|
+
test('resolveMetric: unknown T1 name throws with "not found in registry"', () => {
|
|
286
|
+
assert.throws(() => resolveMetric({ name: 'nonexistent-metric', tier: 'T1' }), /not found in registry/)
|
|
287
|
+
})
|
|
288
|
+
|
|
289
|
+
// ── buildWidgetFile tests (unified path for all tiers) ────────────────────────
|
|
290
|
+
|
|
291
|
+
test('buildWidgetFile: T1 chart metric imports fetchData from metric module (not spliced fnBody)', () => {
|
|
292
|
+
const entry = registry.t1['memory-calls-trend']
|
|
293
|
+
const content = buildWidgetFile(
|
|
294
|
+
{
|
|
295
|
+
name: 'memory-calls-trend',
|
|
296
|
+
tier: 'T1',
|
|
297
|
+
title: 'Memory Calls',
|
|
298
|
+
displayAs: 'area-chart',
|
|
299
|
+
xKey: 'timeSlice',
|
|
300
|
+
yKey: 'memoryCallsCount',
|
|
301
|
+
},
|
|
302
|
+
entry,
|
|
303
|
+
'30d'
|
|
304
|
+
)
|
|
305
|
+
assert.ok(content.includes("import { fetchData } from '@/metrics/memory-calls-trend'"))
|
|
306
|
+
assert.ok(content.includes('useWidgetData(fetchData, [])'))
|
|
307
|
+
assert.ok(!content.includes('customDataFn'))
|
|
308
|
+
// No hardcoded type params from registry
|
|
309
|
+
assert.ok(!content.includes('useWidgetData<'))
|
|
310
|
+
})
|
|
311
|
+
|
|
312
|
+
test('buildWidgetFile: uses registry defaults for xKey/yKey when not in metric', () => {
|
|
313
|
+
const entry = registry.t1['memory-calls-trend']
|
|
314
|
+
const content = buildWidgetFile(
|
|
315
|
+
{ name: 'memory-calls-trend', tier: 'T1', title: 'Memory Calls', displayAs: 'area-chart', fnBody: 'return []' },
|
|
316
|
+
entry,
|
|
317
|
+
'30d'
|
|
318
|
+
)
|
|
319
|
+
// xKey from registry defaults
|
|
320
|
+
assert.ok(content.includes('timeSlice'))
|
|
321
|
+
})
|
|
322
|
+
|
|
323
|
+
test('chart widgets: card chrome always renders; loading shows a chart-sized skeleton (not a full-card block)', () => {
|
|
324
|
+
const cases = [
|
|
325
|
+
{ displayAs: 'area-chart', xKey: 'date', yKey: 'value', h: 'h-[180px]' },
|
|
326
|
+
{ displayAs: 'bar-chart', xKey: 'name', yKey: 'value', h: 'h-[180px]' },
|
|
327
|
+
{ displayAs: 'line-chart', xKey: 'date', yKey: 'value', h: 'h-[180px]' },
|
|
328
|
+
{ displayAs: 'donut-chart', xKey: 'name', yKey: 'value', h: 'h-[180px]' },
|
|
329
|
+
{ displayAs: 'multi-line-chart', xKey: 'date', series: [{ key: 'Pass', color: 'hsl(var(--chart-2))' }, { key: 'Matched', color: 'hsl(var(--chart-1))' }], h: 'h-[200px]' },
|
|
330
|
+
{ displayAs: 'rate-chart', xKey: 'date', rateNum: 'num', rateDen: 'den', h: 'h-[180px]' },
|
|
331
|
+
]
|
|
332
|
+
for (const c of cases) {
|
|
333
|
+
const content = buildWidgetFile({ name: `w-${c.displayAs}`, tier: 'T3', title: 'W', ...c }, null, '30d')
|
|
334
|
+
assert.equal(content.match(/<[A-Z][A-Z_]*>|<<[A-Z_]+>>/g), null, `${c.displayAs}: leftover placeholders`)
|
|
335
|
+
assert.ok(content.includes('<CardHeader'), `${c.displayAs}: CardHeader missing`)
|
|
336
|
+
// The regressed pattern was an early `if (loading) return <LoadingState ...` BEFORE the card.
|
|
337
|
+
assert.ok(!/if \(loading\)\s*return\s*<LoadingState/.test(content), `${c.displayAs}: still bails to a full-card skeleton`)
|
|
338
|
+
// Body skeleton must be chart-sized, never the oversized default h-64 / bare <LoadingState />.
|
|
339
|
+
assert.ok(content.includes(`<LoadingState height="${c.h}"`), `${c.displayAs}: loading skeleton not sized ${c.h}`)
|
|
340
|
+
assert.ok(!content.includes('<LoadingState />'), `${c.displayAs}: bare unsized LoadingState present`)
|
|
341
|
+
}
|
|
342
|
+
})
|
|
343
|
+
|
|
344
|
+
test('buildWidgetFile: T2 chart metric imports fetchData from metric module', () => {
|
|
345
|
+
const entry = registry.t2['jobs-duration-threshold']
|
|
346
|
+
const content = buildWidgetFile(
|
|
347
|
+
{
|
|
348
|
+
name: 'jobs-duration-threshold',
|
|
349
|
+
tier: 'T2',
|
|
350
|
+
title: 'Long Running Jobs',
|
|
351
|
+
displayAs: 'bar-chart',
|
|
352
|
+
xKey: 'name',
|
|
353
|
+
yKey: 'duration',
|
|
354
|
+
},
|
|
355
|
+
entry,
|
|
356
|
+
'30d'
|
|
357
|
+
)
|
|
358
|
+
assert.ok(content.includes("import { fetchData } from '@/metrics/jobs-duration-threshold'"))
|
|
359
|
+
assert.ok(content.includes('useWidgetData(fetchData, [])'))
|
|
360
|
+
assert.ok(!content.includes('customDataFn'))
|
|
361
|
+
assert.ok(!content.includes('useWidgetData<'))
|
|
362
|
+
})
|
|
363
|
+
|
|
364
|
+
test('buildWidgetFile: T3 metric (null registry entry) uses shell path for kpi-card', () => {
|
|
365
|
+
const content = buildWidgetFile(
|
|
366
|
+
{
|
|
367
|
+
name: 'running-jobs-kpi',
|
|
368
|
+
tier: 'T3',
|
|
369
|
+
title: 'Running Jobs',
|
|
370
|
+
displayAs: 'kpi-card',
|
|
371
|
+
fnBody: 'return [{ count: 42 }]',
|
|
372
|
+
valueField: 'count',
|
|
373
|
+
valueLabel: 'running jobs'
|
|
374
|
+
},
|
|
375
|
+
null,
|
|
376
|
+
'30d'
|
|
377
|
+
)
|
|
378
|
+
assert.ok(content.includes('RunningJobsKpi'))
|
|
379
|
+
assert.ok(content.includes('Running Jobs'))
|
|
380
|
+
assert.ok(content.includes("const VALUE_FIELD = 'count'"))
|
|
381
|
+
assert.ok(content.includes("const VALUE_LABEL = 'running jobs'"))
|
|
382
|
+
assert.ok(!content.includes('<<FN_BODY>>'))
|
|
383
|
+
})
|
|
384
|
+
|
|
385
|
+
test('buildWidgetFile: throws if title is missing', () => {
|
|
386
|
+
assert.throws(
|
|
387
|
+
() => buildWidgetFile({ name: 'x', tier: 'T3', displayAs: 'kpi-card', fnBody: 'return []' }, null),
|
|
388
|
+
/missing title/
|
|
389
|
+
)
|
|
390
|
+
})
|
|
391
|
+
|
|
392
|
+
test('buildWidgetFile throws when title is missing', () => {
|
|
393
|
+
assert.throws(() => buildWidgetFile({ name: 'x', tier: 'T1', displayAs: 'data-table' }, null, '30d'), /missing title/)
|
|
394
|
+
})
|
|
395
|
+
|
|
396
|
+
test('buildWidgetFile: throws if displayAs cannot be determined', () => {
|
|
397
|
+
assert.throws(
|
|
398
|
+
() => buildWidgetFile({ name: 'x', tier: 'T3', title: 'X', fnBody: 'return []' }, null),
|
|
399
|
+
/needs displayAs/
|
|
400
|
+
)
|
|
401
|
+
})
|
|
402
|
+
|
|
403
|
+
test('buildWidgetFile: shell ranked-table imports fetchData from metric module', () => {
|
|
404
|
+
const content = buildWidgetFile({
|
|
405
|
+
name: 'faulted-queues', tier: 'T3', title: 'Faulted Queues', description: 'Queues with faults',
|
|
406
|
+
displayAs: 'ranked-table',
|
|
407
|
+
}, null, '30d')
|
|
408
|
+
assert.ok(content.includes('FaultedQueues'), 'component name not injected')
|
|
409
|
+
assert.ok(content.includes('Faulted Queues'), 'title not injected')
|
|
410
|
+
assert.ok(content.includes("import { fetchData } from '@/metrics/faulted-queues'"), 'fetchData import not injected')
|
|
411
|
+
assert.ok(content.includes('fetchData(sdk, getToken)'), 'fetchData call not injected')
|
|
412
|
+
assert.ok(!content.includes('const customDataFn = async'), 'customDataFn must not appear')
|
|
413
|
+
assert.ok(!content.includes('<<COMPONENT_NAME>>'), 'COMPONENT_NAME placeholder not replaced')
|
|
414
|
+
assert.ok(!content.includes('<<METRIC_IMPORT>>'), 'METRIC_IMPORT placeholder not replaced')
|
|
415
|
+
})
|
|
416
|
+
|
|
417
|
+
test('buildWidgetFile: no unresolved << >> placeholders remain', () => {
|
|
418
|
+
const content = buildWidgetFile({
|
|
419
|
+
name: 'test', tier: 'T3', title: 'Test Widget',
|
|
420
|
+
displayAs: 'ranked-table',
|
|
421
|
+
fnBody: 'return []',
|
|
422
|
+
valueField: '', valueLabel: '',
|
|
423
|
+
}, null, '30d')
|
|
424
|
+
const unresolved = content.match(/<<[A-Z_]+>>/g)
|
|
425
|
+
assert.equal(unresolved, null, `Unresolved placeholders: ${(unresolved ?? []).join(', ')}`)
|
|
426
|
+
})
|
|
427
|
+
|
|
428
|
+
test('buildWidgetFile: uses registry defaults for icon when metric has none', () => {
|
|
429
|
+
const entry = registry.t1['agent-health']
|
|
430
|
+
const content = buildWidgetFile(
|
|
431
|
+
{ name: 'agent-health', tier: 'T1', title: 'Agent Health', displayAs: 'ranked-table', fnBody: 'return []' },
|
|
432
|
+
entry,
|
|
433
|
+
'30d'
|
|
434
|
+
)
|
|
435
|
+
assert.ok(content.includes('HeartPulse'))
|
|
436
|
+
})
|
|
437
|
+
|
|
438
|
+
// ── emit + parseEvent tests ───────────────────────────────────────────────────
|
|
439
|
+
|
|
440
|
+
test('emit: writes structured event with payload', () => {
|
|
441
|
+
const lines = []
|
|
442
|
+
emit('WIDGET_READY', { name: 'ErrorRateTrend', index: 1, total: 4 }, { write: s => lines.push(s) })
|
|
443
|
+
assert.equal(lines.length, 1)
|
|
444
|
+
const parsed = JSON.parse(lines[0].replace('WIDGET_READY:', ''))
|
|
445
|
+
assert.equal(parsed.name, 'ErrorRateTrend')
|
|
446
|
+
})
|
|
447
|
+
|
|
448
|
+
test('emit: writes simple event with no payload', () => {
|
|
449
|
+
const lines = []
|
|
450
|
+
emit('PREWARM_DONE', null, { write: s => lines.push(s) })
|
|
451
|
+
assert.equal(lines[0].trim(), 'PREWARM_DONE')
|
|
452
|
+
})
|
|
453
|
+
|
|
454
|
+
test('parseEvent: parses WIDGET_READY with payload', () => {
|
|
455
|
+
const result = parseEvent('WIDGET_READY:{"name":"Foo","index":2,"total":5}')
|
|
456
|
+
assert.equal(result.type, 'WIDGET_READY')
|
|
457
|
+
assert.equal(result.payload.name, 'Foo')
|
|
458
|
+
})
|
|
459
|
+
|
|
460
|
+
test('parseEvent: parses simple event with no payload', () => {
|
|
461
|
+
const result = parseEvent('PREWARM_DONE')
|
|
462
|
+
assert.equal(result.type, 'PREWARM_DONE')
|
|
463
|
+
assert.equal(result.payload, null)
|
|
464
|
+
})
|
|
465
|
+
|
|
466
|
+
test('parseEvent: returns null for non-event lines', () => {
|
|
467
|
+
const result = parseEvent('regular log line')
|
|
468
|
+
assert.equal(result, null)
|
|
469
|
+
})
|
|
470
|
+
|
|
471
|
+
// ── classifyEditIntent tests ──────────────────────────────────────────────────
|
|
472
|
+
|
|
473
|
+
test('classifyEditIntent: normalizes legacy single op to a one-element batch', () => {
|
|
474
|
+
const result = classifyEditIntent({ op: 'ADD', projectDir: '/tmp/x', metric: { name: 'agent-health', tier: 'T1' } })
|
|
475
|
+
assert.equal(result.ops.length, 1)
|
|
476
|
+
assert.equal(result.ops[0].op, 'ADD')
|
|
477
|
+
assert.equal(result.ops[0].metric.name, 'agent-health')
|
|
478
|
+
assert.equal(result.projectDir, '/tmp/x')
|
|
479
|
+
})
|
|
480
|
+
|
|
481
|
+
test('classifyEditIntent: accepts an ops batch and preserves order', () => {
|
|
482
|
+
const result = classifyEditIntent({
|
|
483
|
+
projectDir: '/tmp/x',
|
|
484
|
+
ops: [
|
|
485
|
+
{ op: 'CHANGE', target: 'A', delta: { timeRange: '7d' } },
|
|
486
|
+
{ op: 'REMOVE', target: 'B' },
|
|
487
|
+
{ op: 'ADD', metric: { name: 'agent-health', tier: 'T1' } },
|
|
488
|
+
],
|
|
489
|
+
})
|
|
490
|
+
assert.deepEqual(result.ops.map(o => o.op), ['CHANGE', 'REMOVE', 'ADD'])
|
|
491
|
+
assert.equal(result.ops[1].target, 'B')
|
|
492
|
+
})
|
|
493
|
+
|
|
494
|
+
test('classifyEditIntent: throws on invalid op with its batch index', () => {
|
|
495
|
+
assert.throws(() => classifyEditIntent({ op: 'DELETE', projectDir: '/tmp/x' }), /invalid op "DELETE" \(ops\[0\]\)/)
|
|
496
|
+
assert.throws(
|
|
497
|
+
() => classifyEditIntent({ projectDir: '/tmp/x', ops: [{ op: 'ADD', metric: { name: 'x' } }, { op: 'RENAME' }] }),
|
|
498
|
+
/invalid op "RENAME" \(ops\[1\]\)/
|
|
499
|
+
)
|
|
500
|
+
})
|
|
501
|
+
|
|
502
|
+
test('classifyEditIntent: throws on empty ops array', () => {
|
|
503
|
+
assert.throws(() => classifyEditIntent({ projectDir: '/tmp/x', ops: [] }), /empty/)
|
|
504
|
+
})
|
|
505
|
+
|
|
506
|
+
test('parseEvent: recognizes AUTH_MISSING event', () => {
|
|
507
|
+
const result = parseEvent('AUTH_MISSING:{"var":"clientId","message":"No client ID"}')
|
|
508
|
+
assert.equal(result.type, 'AUTH_MISSING')
|
|
509
|
+
assert.equal(result.payload.var, 'clientId')
|
|
510
|
+
})
|
|
511
|
+
|
|
512
|
+
// ── VALID_DISPLAY_TYPES + displayAs validation tests ─────────────────────────
|
|
513
|
+
|
|
514
|
+
test('VALID_DISPLAY_TYPES includes all chart and table types', () => {
|
|
515
|
+
assert.ok(VALID_DISPLAY_TYPES.includes('kpi-card'))
|
|
516
|
+
assert.ok(VALID_DISPLAY_TYPES.includes('ranked-table'))
|
|
517
|
+
assert.ok(VALID_DISPLAY_TYPES.includes('data-table'))
|
|
518
|
+
assert.ok(VALID_DISPLAY_TYPES.includes('area-chart'))
|
|
519
|
+
assert.ok(VALID_DISPLAY_TYPES.includes('line-chart'))
|
|
520
|
+
assert.ok(VALID_DISPLAY_TYPES.includes('bar-chart'))
|
|
521
|
+
assert.ok(VALID_DISPLAY_TYPES.includes('donut-chart'))
|
|
522
|
+
assert.ok(VALID_DISPLAY_TYPES.includes('multi-line-chart'))
|
|
523
|
+
})
|
|
524
|
+
|
|
525
|
+
test('validateIntent: accepts T3-SDK with chart displayAs (area-chart, line-chart, bar-chart, donut-chart)', () => {
|
|
526
|
+
for (const chartType of ['area-chart', 'line-chart', 'bar-chart', 'donut-chart']) {
|
|
527
|
+
const errors = validateIntent({
|
|
528
|
+
schemaVersion: 2, dashboardName: 'x', timeRange: '7d',
|
|
529
|
+
metrics: [{ name: 'custom', tier: 'T3', title: 'X', displayAs: chartType }]
|
|
530
|
+
})
|
|
531
|
+
assert.deepEqual(errors, [], `${chartType} should be valid for T3-SDK`)
|
|
532
|
+
}
|
|
533
|
+
})
|
|
534
|
+
|
|
535
|
+
test('validateIntent: rejects T3-SDK with truly unsupported displayAs', () => {
|
|
536
|
+
const errors = validateIntent({
|
|
537
|
+
schemaVersion: 2, dashboardName: 'x', timeRange: '7d',
|
|
538
|
+
metrics: [{ name: 'custom', tier: 'T3', title: 'X', displayAs: 'unknown-widget' }]
|
|
539
|
+
})
|
|
540
|
+
assert.ok(errors.some(e => e.includes('unsupported displayAs')))
|
|
541
|
+
})
|
|
542
|
+
|
|
543
|
+
test('validateIntent: accepts T3-SDK with ranked-table displayAs', () => {
|
|
544
|
+
const errors = validateIntent({
|
|
545
|
+
schemaVersion: 2, dashboardName: 'x', timeRange: '7d',
|
|
546
|
+
metrics: [{ name: 'custom', tier: 'T3', title: 'X', displayAs: 'ranked-table' }]
|
|
547
|
+
})
|
|
548
|
+
assert.deepEqual(errors, [])
|
|
549
|
+
})
|
|
550
|
+
|
|
551
|
+
test('buildWidgetFile: injects valueField and valueLabel placeholders', () => {
|
|
552
|
+
const content = buildWidgetFile({
|
|
553
|
+
name: 'running-jobs', tier: 'T3', title: 'Running Jobs', displayAs: 'kpi-card',
|
|
554
|
+
fnBody: 'return []', valueField: 'count', valueLabel: 'running jobs'
|
|
555
|
+
}, null)
|
|
556
|
+
assert.ok(content.includes("const VALUE_FIELD = 'count'"))
|
|
557
|
+
assert.ok(content.includes("const VALUE_LABEL = 'running jobs'"))
|
|
558
|
+
assert.ok(!content.includes('<<VALUE_FIELD>>'))
|
|
559
|
+
assert.ok(!content.includes('<<VALUE_LABEL>>'))
|
|
560
|
+
})
|
|
561
|
+
|
|
562
|
+
// ── Presentation overhaul: headline / delta / subtitle / columns / rate ───────
|
|
563
|
+
|
|
564
|
+
test('VALID_DISPLAY_TYPES includes rate-chart', () => {
|
|
565
|
+
assert.ok(VALID_DISPLAY_TYPES.includes('rate-chart'))
|
|
566
|
+
})
|
|
567
|
+
|
|
568
|
+
test('buildWidgetFile: chart injects headline aggregate + delta (not last point)', () => {
|
|
569
|
+
const content = buildWidgetFile(
|
|
570
|
+
{ name: 'runs-trend', tier: 'T3', title: 'Runs', displayAs: 'line-chart', xKey: 'date', yKey: 'count', headlineMode: 'sum', deltaPolarity: 'up-good', fnBody: 'return []' },
|
|
571
|
+
null, '30d'
|
|
572
|
+
)
|
|
573
|
+
assert.ok(content.includes("headline(chartData, 'count', 'sum')"), 'headline aggregate not injected')
|
|
574
|
+
assert.ok(content.includes("delta(chartData, 'count', 'up-good')"), 'delta not injected')
|
|
575
|
+
assert.equal(content.match(/<SUBTITLE>|<HEADLINE_MODE>|<DELTA_POLARITY>/g), null, 'placeholders not replaced')
|
|
576
|
+
})
|
|
577
|
+
|
|
578
|
+
test('buildWidgetFile: chart subtitle auto-fills from time range when none given', () => {
|
|
579
|
+
const content = buildWidgetFile(
|
|
580
|
+
{ name: 'x', tier: 'T3', title: 'X', displayAs: 'area-chart', yKey: 'value', fnBody: 'return []' },
|
|
581
|
+
null, '7d'
|
|
582
|
+
)
|
|
583
|
+
assert.ok(content.includes('Last 7 days'))
|
|
584
|
+
})
|
|
585
|
+
|
|
586
|
+
test('buildWidgetFile: rate-chart injects rate helpers + num/den fields', () => {
|
|
587
|
+
const content = buildWidgetFile(
|
|
588
|
+
{ name: 'err-rate', tier: 'T3', title: 'Error Rate', displayAs: 'rate-chart', xKey: 'date', rateNum: 'faulted', rateDen: 'total', deltaPolarity: 'up-bad', fnBody: 'return []' },
|
|
589
|
+
null, '30d'
|
|
590
|
+
)
|
|
591
|
+
assert.ok(content.includes("rateSeries(raw, 'faulted', 'total', 'date')"))
|
|
592
|
+
assert.ok(content.includes("overallRate(raw, 'faulted', 'total')"))
|
|
593
|
+
assert.ok(!content.includes('<RATE_NUM>'))
|
|
594
|
+
})
|
|
595
|
+
|
|
596
|
+
test('compileColumns: passes a string literal through unchanged', () => {
|
|
597
|
+
const lit = '[{key:"name",label:"Name"}]'
|
|
598
|
+
assert.equal(compileColumns(lit), lit)
|
|
599
|
+
})
|
|
600
|
+
|
|
601
|
+
test('compileColumns: compiles an array with format/color into render fns', () => {
|
|
602
|
+
const out = compileColumns([
|
|
603
|
+
{ key: 'processName', label: 'Process' },
|
|
604
|
+
{ key: 'duration', label: 'Duration', align: 'right', format: 'duration' },
|
|
605
|
+
{ key: 'successRate', label: 'Success', align: 'right', format: 'percent', color: 'goodHigh' },
|
|
606
|
+
])
|
|
607
|
+
assert.ok(out.includes('key:"processName"'))
|
|
608
|
+
assert.ok(out.includes('fmtDuration(Number(v))'))
|
|
609
|
+
assert.ok(out.includes('fmtPercent(Number(v))'))
|
|
610
|
+
assert.ok(out.includes('toneClass(Number(v),"goodHigh")'))
|
|
611
|
+
})
|
|
612
|
+
|
|
613
|
+
test('validateIntent: rejects invalid headlineMode', () => {
|
|
614
|
+
const errors = validateIntent({
|
|
615
|
+
schemaVersion: 2, dashboardName: 'x', timeRange: '7d',
|
|
616
|
+
metrics: [{ name: 'm', tier: 'T3', title: 'M', displayAs: 'line-chart', headlineMode: 'median' }],
|
|
617
|
+
})
|
|
618
|
+
assert.ok(errors.some(e => e.includes('headlineMode')))
|
|
619
|
+
})
|
|
620
|
+
|
|
621
|
+
test('validateIntent: rejects invalid deltaPolarity', () => {
|
|
622
|
+
const errors = validateIntent({
|
|
623
|
+
schemaVersion: 2, dashboardName: 'x', timeRange: '7d',
|
|
624
|
+
metrics: [{ name: 'm', tier: 'T3', title: 'M', displayAs: 'line-chart', deltaPolarity: 'sideways' }],
|
|
625
|
+
})
|
|
626
|
+
assert.ok(errors.some(e => e.includes('deltaPolarity')))
|
|
627
|
+
})
|
|
628
|
+
|
|
629
|
+
test('validateIntent: rate-chart requires rateNum + rateDen', () => {
|
|
630
|
+
const errors = validateIntent({
|
|
631
|
+
schemaVersion: 2, dashboardName: 'x', timeRange: '7d',
|
|
632
|
+
metrics: [{ name: 'm', tier: 'T3', title: 'M', displayAs: 'rate-chart' }],
|
|
633
|
+
})
|
|
634
|
+
assert.ok(errors.some(e => e.includes('rateNum')))
|
|
635
|
+
})
|
|
636
|
+
|
|
637
|
+
test('validateIntent: accepts valid presentation hints', () => {
|
|
638
|
+
const errors = validateIntent({
|
|
639
|
+
schemaVersion: 2, dashboardName: 'x', timeRange: '30d',
|
|
640
|
+
metrics: [{ name: 'm', tier: 'T3', title: 'M', displayAs: 'line-chart', headlineMode: 'avg', deltaPolarity: 'up-bad' }],
|
|
641
|
+
})
|
|
642
|
+
assert.deepEqual(errors, [])
|
|
643
|
+
})
|
|
644
|
+
|
|
645
|
+
test('validateIntent: rejects detailColumns entry with bad format', () => {
|
|
646
|
+
const errors = validateIntent({
|
|
647
|
+
schemaVersion: 2, dashboardName: 'x', timeRange: '30d',
|
|
648
|
+
metrics: [{ name: 'm', tier: 'T3', title: 'M', displayAs: 'line-chart', detailColumns: [{ key: 'd', label: 'D', format: 'bogus' }] }],
|
|
649
|
+
})
|
|
650
|
+
assert.ok(errors.some(e => e.includes('invalid format')))
|
|
651
|
+
})
|
|
652
|
+
|
|
653
|
+
test('buildWidgetFile: every chart type generates with no leftover placeholders and imports fetchData', () => {
|
|
654
|
+
for (const displayAs of ['line-chart', 'area-chart', 'bar-chart', 'donut-chart', 'multi-line-chart', 'rate-chart']) {
|
|
655
|
+
const metric = {
|
|
656
|
+
name: 'm', tier: 'T3', title: 'M', displayAs,
|
|
657
|
+
xKey: 'date', yKey: 'value', rateNum: 'num', rateDen: 'den',
|
|
658
|
+
}
|
|
659
|
+
const content = buildWidgetFile(metric, null, '30d')
|
|
660
|
+
assert.equal(content.match(/<[A-Z][A-Z_]*>|<<[A-Z_]+>>/g), null, `${displayAs} has leftover placeholders`)
|
|
661
|
+
assert.ok(content.includes("import { fetchData } from '@/metrics/m'"), `${displayAs} missing fetchData import`)
|
|
662
|
+
assert.ok(content.includes('useWidgetData(fetchData, [])'), `${displayAs} missing useWidgetData(fetchData, [])`)
|
|
663
|
+
assert.ok(!content.includes('customDataFn'), `${displayAs} should not splice customDataFn`)
|
|
664
|
+
}
|
|
665
|
+
})
|
|
666
|
+
|
|
667
|
+
test('generateViewFile: imports metric module and uses compiled detailColumns', () => {
|
|
668
|
+
const view = generateViewFile({
|
|
669
|
+
componentName: 'FaultedJobs',
|
|
670
|
+
title: 'Faulted Jobs',
|
|
671
|
+
subtitle: 'Last 30 days',
|
|
672
|
+
moduleSpecifier: '@/metrics/faulted-jobs',
|
|
673
|
+
detailExport: 'fetchData',
|
|
674
|
+
detailColumns: compileColumns([
|
|
675
|
+
{ key: 'processName', label: 'Process' },
|
|
676
|
+
{ key: 'startTime', label: 'Started', format: 'timeAgo' },
|
|
677
|
+
]),
|
|
678
|
+
defaultSortKey: 'startTime',
|
|
679
|
+
})
|
|
680
|
+
assert.ok(view.includes('FaultedJobsView'))
|
|
681
|
+
assert.ok(view.includes("import { fetchData } from '@/metrics/faulted-jobs'"), 'metric import not present')
|
|
682
|
+
assert.ok(view.includes('useWidgetData(fetchData, [])'), 'useWidgetData call not present')
|
|
683
|
+
assert.ok(!view.includes('const customDataFn = async'), 'customDataFn must not appear')
|
|
684
|
+
assert.ok(view.includes('fmtTimeAgo(String(v))'), 'formatted column not embedded')
|
|
685
|
+
assert.ok(view.includes('defaultSortKey={"startTime"}'), 'explicit sort key not used')
|
|
686
|
+
assert.ok(!view.includes('autoColumns(rows)') || view.includes('function autoColumns'), 'autoColumns only as fallback definition')
|
|
687
|
+
})
|
|
688
|
+
|
|
689
|
+
test('generateViewFile: falls back to autoColumns when no detailColumns', () => {
|
|
690
|
+
const view = generateViewFile({
|
|
691
|
+
componentName: 'Trend', title: 'Trend', subtitle: '',
|
|
692
|
+
moduleSpecifier: '@/metrics/trend',
|
|
693
|
+
detailExport: 'fetchData',
|
|
694
|
+
detailColumns: null,
|
|
695
|
+
})
|
|
696
|
+
assert.ok(view.includes('const columns = autoColumns(rows)'))
|
|
697
|
+
})
|
|
698
|
+
|
|
699
|
+
// ── SDK 1.4.1: agent / memory / governance metrics ────────────────────────────
|
|
700
|
+
|
|
701
|
+
test('1.4.1: previously-refused agent metrics now resolve as T1', () => {
|
|
702
|
+
for (const [text, expected] of [
|
|
703
|
+
['show active agents', 'active-agents-kpi'],
|
|
704
|
+
['agu consumption by agent', 'agent-consumption'],
|
|
705
|
+
['agents by health', 'agent-health'],
|
|
706
|
+
['memory calls over time', 'memory-calls-trend'],
|
|
707
|
+
['top memory spaces', 'top-memory-spaces'],
|
|
708
|
+
['policy denials this week', 'policy-denials'],
|
|
709
|
+
['governance summary', 'governance-verdicts'],
|
|
710
|
+
]) {
|
|
711
|
+
const result = resolveAlias(text)
|
|
712
|
+
assert.ok(result, `"${text}" did not resolve`)
|
|
713
|
+
assert.equal(result.key, expected, `"${text}" resolved to ${result.key}, expected ${expected}`)
|
|
714
|
+
}
|
|
715
|
+
})
|
|
716
|
+
|
|
717
|
+
test('1.4.1: agent/trace timeline metrics now resolve as T1', () => {
|
|
718
|
+
for (const [text, expected] of [
|
|
719
|
+
['agent error rate trend', 'agent-error-timeline'],
|
|
720
|
+
['errors over time', 'agent-error-timeline'],
|
|
721
|
+
['agent latency p95', 'agent-latency-timeline'],
|
|
722
|
+
['latency over time', 'agent-latency-timeline'],
|
|
723
|
+
['consumption timeline', 'agent-consumption-timeline'],
|
|
724
|
+
['consumption over time', 'agent-consumption-timeline'],
|
|
725
|
+
['top errors', 'agent-errors'],
|
|
726
|
+
['errors by type', 'agent-errors'],
|
|
727
|
+
['trace latency', 'trace-latency-timeline'],
|
|
728
|
+
['trace errors', 'trace-error-timeline'],
|
|
729
|
+
['unit consumption', 'agent-unit-consumption'],
|
|
730
|
+
]) {
|
|
731
|
+
const result = resolveAlias(text)
|
|
732
|
+
assert.ok(result, `"${text}" did not resolve`)
|
|
733
|
+
assert.equal(result.key, expected, `"${text}" resolved to ${result.key}, expected ${expected}`)
|
|
734
|
+
const refused = registry.hardRefuse.some(e => new RegExp(e.pattern).test(text))
|
|
735
|
+
assert.ok(!refused, `"${text}" should NOT be hard-refused — SDK 1.4.1 supports it`)
|
|
736
|
+
}
|
|
737
|
+
})
|
|
738
|
+
|
|
739
|
+
test('1.4.1: invocation-count timelines (no SDK endpoint) are still hard-refused', () => {
|
|
740
|
+
for (const text of ['invocation volume over time', 'invocations over time', 'invocation count by day']) {
|
|
741
|
+
const refused = registry.hardRefuse.some(e => new RegExp(e.pattern).test(text))
|
|
742
|
+
assert.ok(refused, `"${text}" should be hard-refused (no SDK 1.4.1 invocation-count endpoint)`)
|
|
743
|
+
}
|
|
744
|
+
})
|
|
745
|
+
|
|
746
|
+
test('1.4.1: t1_unavailable section is gone (stale PR #438 entries removed)', () => {
|
|
747
|
+
assert.equal(registry.t1_unavailable, undefined)
|
|
748
|
+
})
|
|
749
|
+
|
|
750
|
+
// ── SDK 1.5.0: Agents insights aggregate metrics ──────────────────────────────
|
|
751
|
+
|
|
752
|
+
test('1.5.0: new Agents insights metrics resolve as T1', () => {
|
|
753
|
+
for (const [text, expected] of [
|
|
754
|
+
['agents by error count', 'agents-by-errors'],
|
|
755
|
+
['noisiest agents', 'agents-by-errors'],
|
|
756
|
+
['incident distribution', 'agent-incident-distribution'],
|
|
757
|
+
['errors vs escalations vs policy', 'agent-incident-distribution'],
|
|
758
|
+
['agent success rate', 'agent-success-rate'],
|
|
759
|
+
['job success rate', 'agent-success-rate'],
|
|
760
|
+
['total agent units', 'agent-unit-consumption-summary'],
|
|
761
|
+
]) {
|
|
762
|
+
const result = resolveAlias(text)
|
|
763
|
+
assert.ok(result, `"${text}" did not resolve`)
|
|
764
|
+
assert.equal(result.key, expected, `"${text}" resolved to ${result.key}, expected ${expected}`)
|
|
765
|
+
const refused = registry.hardRefuse.some(e => new RegExp(e.pattern, 'i').test(text))
|
|
766
|
+
assert.ok(!refused, `"${text}" should NOT be hard-refused — SDK 1.5.0 supports it`)
|
|
767
|
+
}
|
|
768
|
+
})
|
|
769
|
+
|
|
770
|
+
test('1.5.0: insights metrics generate clean widgets (no leftover placeholders; KPIs default value/previous)', () => {
|
|
771
|
+
for (const name of ['agents-by-errors', 'agent-incident-distribution', 'agent-success-rate', 'agent-unit-consumption-summary', 'agent-consumption']) {
|
|
772
|
+
const entry = registry.t1[name]
|
|
773
|
+
assert.ok(entry, `missing ${name}`)
|
|
774
|
+
const content = buildWidgetFile({ name, tier: 'T1', title: entry.defaults.title, displayAs: entry.template, ...entry.defaults }, entry, '30d')
|
|
775
|
+
assert.equal(content.match(/<[A-Z][A-Z_]*>|<<[A-Z_]+>>/g), null, `${name}: leftover placeholders`)
|
|
776
|
+
assert.ok(content.includes(`import { fetchData } from '@/metrics/${name}'`), `${name}: missing fetchData import`)
|
|
777
|
+
}
|
|
778
|
+
// The two summary KPIs must carry the conventional value/previous delta fields.
|
|
779
|
+
for (const name of ['agent-success-rate', 'agent-unit-consumption-summary']) {
|
|
780
|
+
const entry = registry.t1[name]
|
|
781
|
+
const content = buildWidgetFile({ name, tier: 'T1', title: entry.defaults.title, displayAs: 'kpi-card', ...entry.defaults }, entry, '30d')
|
|
782
|
+
assert.ok(content.includes("const VALUE_FIELD = 'value'"), `${name}: VALUE_FIELD must be 'value'`)
|
|
783
|
+
assert.ok(content.includes("const PREVIOUS_FIELD = 'previous'"), `${name}: PREVIOUS_FIELD must be 'previous' for the lookback delta`)
|
|
784
|
+
}
|
|
785
|
+
})
|
|
786
|
+
|
|
787
|
+
test('1.4.1: agent-health shell build compiles formatted/colored columnDefs', () => {
|
|
788
|
+
const entry = registry.t1['agent-health']
|
|
789
|
+
const content = buildWidgetFile(
|
|
790
|
+
{ name: 'agent-health', tier: 'T1', title: 'Agent Health', displayAs: 'ranked-table', fnBody: 'return []' },
|
|
791
|
+
entry,
|
|
792
|
+
'30d'
|
|
793
|
+
)
|
|
794
|
+
assert.ok(content.includes('toneClass(Number(v),"goodHigh")'), 'healthScore colour not compiled')
|
|
795
|
+
assert.ok(content.includes('fmtTimeAgo(String(v))'), 'lastRun timeAgo format not compiled')
|
|
796
|
+
})
|
|
797
|
+
|
|
798
|
+
// ── fnBody harness signature: SDK interface arrays must be accepted ───────────
|
|
799
|
+
// SDK response types are interfaces (no implicit index signature) — NOT assignable
|
|
800
|
+
// to Record<string, unknown>[]. The injected customDataFn must be Promise<any[]>
|
|
801
|
+
// so `return result?.items ?? []` typechecks without casts. (Repro: TS2322.)
|
|
802
|
+
|
|
803
|
+
test('harness: chart widget imports fetchData from metric module (no spliced Promise<any[]> wrapper)', () => {
|
|
804
|
+
const content = buildWidgetFile(
|
|
805
|
+
{ name: 'm', tier: 'T3', title: 'M', displayAs: 'line-chart', yKey: 'v' },
|
|
806
|
+
null, '7d'
|
|
807
|
+
)
|
|
808
|
+
assert.ok(content.includes("import { fetchData } from '@/metrics/m'"), 'chart must import fetchData from metric module')
|
|
809
|
+
assert.ok(content.includes('useWidgetData(fetchData, [])'), 'chart must use useWidgetData(fetchData, [])')
|
|
810
|
+
assert.ok(!content.includes('customDataFn'), 'chart must not splice customDataFn')
|
|
811
|
+
assert.ok(!content.includes('Promise<Record<string, unknown>[]>'), 'old index-signature-demanding wrapper must be gone')
|
|
812
|
+
})
|
|
813
|
+
|
|
814
|
+
test('harness: shell widget imports fetchData from metric module (no embedded customDataFn)', () => {
|
|
815
|
+
const content = buildWidgetFile(
|
|
816
|
+
{ name: 'm', tier: 'T3', title: 'M', displayAs: 'data-table' },
|
|
817
|
+
null, '7d'
|
|
818
|
+
)
|
|
819
|
+
assert.ok(content.includes("import { fetchData } from '@/metrics/m'"), 'shell must import fetchData from metric module')
|
|
820
|
+
assert.ok(content.includes('fetchData(sdk, getToken)'), 'shell must call fetchData')
|
|
821
|
+
assert.ok(!content.includes('const customDataFn = async'), 'shell must not embed customDataFn')
|
|
822
|
+
})
|
|
823
|
+
|
|
824
|
+
test('harness: detail view imports metric module (no customDataFn, no Promise<any[]> wrapper)', () => {
|
|
825
|
+
const view = generateViewFile({
|
|
826
|
+
componentName: 'X', title: 'X', subtitle: '',
|
|
827
|
+
moduleSpecifier: '@/metrics/x',
|
|
828
|
+
detailExport: 'fetchData',
|
|
829
|
+
detailColumns: null,
|
|
830
|
+
})
|
|
831
|
+
assert.ok(view.includes("import { fetchData } from '@/metrics/x'"), 'metric import not present')
|
|
832
|
+
assert.ok(view.includes('useWidgetData(fetchData, [])'), 'useWidgetData call not present')
|
|
833
|
+
assert.ok(!view.includes('const customDataFn = async'), 'customDataFn must not appear')
|
|
834
|
+
assert.ok(!view.includes('Promise<any[]>'), 'old Promise<any[]> wrapper must be gone')
|
|
835
|
+
})
|
|
836
|
+
|
|
837
|
+
// ── Incremental edit: change-merge + layout grouping ──────────────────────────
|
|
838
|
+
|
|
839
|
+
test('resolveChangeMetric: timeRange-only delta keeps persisted metadata', () => {
|
|
840
|
+
const stored = {
|
|
841
|
+
tier: 'T1', metric: 'memory-calls-trend', template: 'area-chart',
|
|
842
|
+
intentMetric: { name: 'memory-calls-trend', tier: 'T1', title: 'Memory Calls', displayAs: 'area-chart' },
|
|
843
|
+
}
|
|
844
|
+
const merged = resolveChangeMetric(stored, 'MemoryCallsTrend', { timeRange: '7d' })
|
|
845
|
+
assert.equal(merged.title, 'Memory Calls')
|
|
846
|
+
assert.equal(merged.displayAs, 'area-chart')
|
|
847
|
+
assert.equal(merged.timeRange, '7d')
|
|
848
|
+
})
|
|
849
|
+
|
|
850
|
+
test('resolveChangeMetric: delta fields override persisted intentMetric', () => {
|
|
851
|
+
const stored = { intentMetric: { name: 'm', tier: 'T3', title: 'Old', displayAs: 'area-chart' } }
|
|
852
|
+
const merged = resolveChangeMetric(stored, 'M', { displayAs: 'data-table', module: 'metrics/m.ts' })
|
|
853
|
+
assert.equal(merged.displayAs, 'data-table')
|
|
854
|
+
assert.equal(merged.module, 'metrics/m.ts')
|
|
855
|
+
assert.equal(merged.title, 'Old')
|
|
856
|
+
})
|
|
857
|
+
|
|
858
|
+
test('resolveChangeMetric: legacy state without intentMetric yields a minimal ref', () => {
|
|
859
|
+
const merged = resolveChangeMetric({ tier: 'T1', metric: 'job-failures' }, 'JobFailures', { timeRange: '7d' })
|
|
860
|
+
assert.equal(merged.name, 'job-failures')
|
|
861
|
+
assert.equal(merged.tier, 'T1')
|
|
862
|
+
assert.equal(merged.title, undefined)
|
|
863
|
+
})
|
|
864
|
+
|
|
865
|
+
test('widgetLayoutGroup: classifies only buildable types', () => {
|
|
866
|
+
assert.equal(widgetLayoutGroup('kpi-card'), 'kpi')
|
|
867
|
+
assert.equal(widgetLayoutGroup('data-table'), 'table')
|
|
868
|
+
assert.equal(widgetLayoutGroup('ranked-table'), 'table')
|
|
869
|
+
assert.equal(widgetLayoutGroup('rate-chart'), 'chart')
|
|
870
|
+
assert.equal(widgetLayoutGroup('area-chart'), 'chart')
|
|
871
|
+
})
|
|
872
|
+
|
|
873
|
+
test('1.4.1: governance-verdicts donut uses name/value keys from defaults', () => {
|
|
874
|
+
const entry = registry.t1['governance-verdicts']
|
|
875
|
+
const content = buildWidgetFile(
|
|
876
|
+
{ name: 'governance-verdicts', tier: 'T1', title: 'Governance Verdicts', displayAs: 'donut-chart', fnBody: 'return []' },
|
|
877
|
+
entry,
|
|
878
|
+
'7d'
|
|
879
|
+
)
|
|
880
|
+
assert.ok(content.includes('dataKey="value"'))
|
|
881
|
+
assert.ok(content.includes('nameKey="name"'))
|
|
882
|
+
})
|
|
883
|
+
|
|
884
|
+
// ── metricModuleSpecifier tests ───────────────────────────────────────────────
|
|
885
|
+
|
|
886
|
+
test('metricModuleSpecifier derives @/metrics/<name> by default', () => {
|
|
887
|
+
assert.equal(metricModuleSpecifier({ name: 'agent-health' }), '@/metrics/agent-health')
|
|
888
|
+
})
|
|
889
|
+
|
|
890
|
+
test('metricModuleSpecifier honors explicit module field and strips .ts', () => {
|
|
891
|
+
assert.equal(metricModuleSpecifier({ name: 'x', module: 'metrics/custom-thing.ts' }), '@/metrics/custom-thing')
|
|
892
|
+
})
|
|
893
|
+
|
|
894
|
+
test('validateIntent accepts a metadata-only metric (no fnBody)', () => {
|
|
895
|
+
const errors = validateIntent({
|
|
896
|
+
schemaVersion: 2, dashboardName: 'D', timeRange: '30d',
|
|
897
|
+
metrics: [{ name: 'agent-health', tier: 'T1', title: 'Agent Health', displayAs: 'ranked-table' }],
|
|
898
|
+
})
|
|
899
|
+
assert.deepEqual(errors, [])
|
|
900
|
+
})
|
|
901
|
+
|
|
902
|
+
test('validateIntent rejects intent missing schemaVersion 2', () => {
|
|
903
|
+
const errors = validateIntent({
|
|
904
|
+
dashboardName: 'D', timeRange: '30d',
|
|
905
|
+
metrics: [{ name: 'm', tier: 'T3', title: 'M', displayAs: 'kpi-card' }],
|
|
906
|
+
})
|
|
907
|
+
assert.ok(errors.some(e => /schemaVersion/.test(e)))
|
|
908
|
+
})
|
|
909
|
+
|
|
910
|
+
test('buildWidgetFile (chart) imports fetchData and does not splice customDataFn', () => {
|
|
911
|
+
const out = buildWidgetFile(
|
|
912
|
+
{ name: 'memory-calls-trend', tier: 'T1', title: 'Memory Calls', displayAs: 'area-chart', xKey: 'timeSlice', yKey: 'memoryCallsCount' },
|
|
913
|
+
null, '30d'
|
|
914
|
+
)
|
|
915
|
+
assert.match(out, /import \{ fetchData \} from '@\/metrics\/memory-calls-trend'/)
|
|
916
|
+
assert.match(out, /useWidgetData\(fetchData, \[\]\)/)
|
|
917
|
+
assert.doesNotMatch(out, /const customDataFn = async/)
|
|
918
|
+
})
|
|
919
|
+
|
|
920
|
+
test('buildWidgetFile (kpi/table) imports fetchData and has no embedded customDataFn', () => {
|
|
921
|
+
const out = buildWidgetFile(
|
|
922
|
+
{ name: 'job-failures', tier: 'T1', title: 'Faulted Jobs', displayAs: 'data-table',
|
|
923
|
+
columns: '[{key:"processName",label:"Process"}]' },
|
|
924
|
+
null, '30d'
|
|
925
|
+
)
|
|
926
|
+
assert.match(out, /import \{ fetchData \} from '@\/metrics\/job-failures'/)
|
|
927
|
+
assert.match(out, /fetchData\(sdk, getToken\)/)
|
|
928
|
+
assert.doesNotMatch(out, /const customDataFn = async/)
|
|
929
|
+
})
|
|
930
|
+
|
|
931
|
+
test('generateViewFile imports the metric module detail/data export', () => {
|
|
932
|
+
const spec = buildViewSpec('MemoryCallsTrend',
|
|
933
|
+
{ name: 'memory-calls-trend', tier: 'T1', title: 'Memory Calls', displayAs: 'area-chart' },
|
|
934
|
+
null, '30d')
|
|
935
|
+
const out = generateViewFile(spec)
|
|
936
|
+
assert.match(out, /import \{ (fetchData|fetchDetail) \} from '@\/metrics\/memory-calls-trend'/)
|
|
937
|
+
assert.doesNotMatch(out, /const customDataFn = async/)
|
|
938
|
+
})
|
|
939
|
+
|
|
940
|
+
test('classifyEditIntent ADD carries metric metadata without fnBody', () => {
|
|
941
|
+
const plan = classifyEditIntent({
|
|
942
|
+
projectDir: '/p',
|
|
943
|
+
ops: [{ op: 'ADD', metric: { name: 'agent-health', tier: 'T1', title: 'Agent Health', displayAs: 'ranked-table' } }],
|
|
944
|
+
})
|
|
945
|
+
assert.equal(plan.ops[0].op, 'ADD')
|
|
946
|
+
assert.equal(plan.ops[0].metric.name, 'agent-health')
|
|
947
|
+
assert.equal(plan.ops[0].metric.fnBody, undefined)
|
|
948
|
+
})
|
|
949
|
+
|
|
950
|
+
// ── Maestro Insights + SLA coverage (review items 1–4) ────────────────────────
|
|
951
|
+
|
|
952
|
+
function genWidget(name) {
|
|
953
|
+
const entry = registry.t1[name]
|
|
954
|
+
assert.ok(entry, `missing T1 entry ${name}`)
|
|
955
|
+
const metric = { name, tier: 'T1', title: entry.defaults.title, displayAs: entry.template, ...entry.defaults }
|
|
956
|
+
const content = buildWidgetFile(metric, entry, '30d')
|
|
957
|
+
assert.equal(content.match(/<[A-Z][A-Z_]*>|<<[A-Z_]+>>/g), null, `${name} leftover placeholders`)
|
|
958
|
+
assert.ok(content.includes(`import { fetchData } from '@/metrics/${name}'`), `${name} missing fetchData import`)
|
|
959
|
+
return { entry, content }
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
test('item 4: error-text refuse narrowed — aggregated error classes NOT refused', () => {
|
|
963
|
+
for (const text of ['top errors', 'errors by type', 'agent error breakdown']) {
|
|
964
|
+
const refused = registry.hardRefuse.some(e => new RegExp(e.pattern).test(text))
|
|
965
|
+
assert.ok(!refused, `"${text}" must not be hard-refused — Agents.getErrors aggregates error classes`)
|
|
966
|
+
}
|
|
967
|
+
})
|
|
968
|
+
|
|
969
|
+
test('item 4: error-text refuse still blocks raw stack traces', () => {
|
|
970
|
+
for (const text of ['show me the stack trace', 'exception text per job', 'raw error message text']) {
|
|
971
|
+
const refused = registry.hardRefuse.some(e => new RegExp(e.pattern).test(text))
|
|
972
|
+
assert.ok(refused, `"${text}" should be hard-refused (no raw stack-trace aggregation)`)
|
|
973
|
+
}
|
|
974
|
+
})
|
|
975
|
+
|
|
976
|
+
test('item 1: SLA metrics resolve T1 and are NOT refused', () => {
|
|
977
|
+
for (const [text, expected] of [
|
|
978
|
+
['sla breach', 'case-sla-breaches'],
|
|
979
|
+
['cases at risk', 'case-sla-breaches'],
|
|
980
|
+
['overdue cases', 'case-sla-breaches'],
|
|
981
|
+
['sla status', 'case-sla-status'],
|
|
982
|
+
['sla breakdown', 'case-sla-status'],
|
|
983
|
+
['stage sla', 'case-stage-sla'],
|
|
984
|
+
]) {
|
|
985
|
+
const refused = registry.hardRefuse.some(e => new RegExp(e.pattern).test(text))
|
|
986
|
+
assert.ok(!refused, `"${text}" must not be hard-refused — SLA summary exists`)
|
|
987
|
+
const r = resolveAlias(text)
|
|
988
|
+
assert.ok(r, `"${text}" did not resolve`)
|
|
989
|
+
assert.equal(r.key, expected, `"${text}" → ${r?.key}, expected ${expected}`)
|
|
990
|
+
}
|
|
991
|
+
})
|
|
992
|
+
|
|
993
|
+
test('item 1: SLA T1 entries generate clean widgets', () => {
|
|
994
|
+
for (const name of ['case-sla-status', 'case-sla-breaches', 'case-stage-sla']) genWidget(name)
|
|
995
|
+
})
|
|
996
|
+
|
|
997
|
+
test('item 2a: Maestro process Insights metrics resolve T1', () => {
|
|
998
|
+
for (const [text, expected] of [
|
|
999
|
+
['busiest processes', 'top-maestro-processes-by-runs'],
|
|
1000
|
+
['top failing processes', 'top-maestro-processes-by-faults'],
|
|
1001
|
+
['slowest processes', 'top-maestro-processes-by-duration'],
|
|
1002
|
+
['process status over time', 'maestro-process-status-timeline'],
|
|
1003
|
+
['failing elements', 'top-failing-process-elements'],
|
|
1004
|
+
]) {
|
|
1005
|
+
const r = resolveAlias(text)
|
|
1006
|
+
assert.ok(r, `"${text}" did not resolve`)
|
|
1007
|
+
assert.equal(r.key, expected, `"${text}" → ${r?.key}, expected ${expected}`)
|
|
1008
|
+
}
|
|
1009
|
+
})
|
|
1010
|
+
|
|
1011
|
+
test('item 2a: process Insights T1 entries generate clean widgets (incl. status series)', () => {
|
|
1012
|
+
for (const name of ['top-maestro-processes-by-runs', 'top-maestro-processes-by-faults', 'top-maestro-processes-by-duration', 'maestro-process-status-timeline', 'top-failing-process-elements']) {
|
|
1013
|
+
const { entry, content } = genWidget(name)
|
|
1014
|
+
if (entry.template === 'multi-line-chart') assert.ok(content.includes('Faulted'), `${name} missing status series`)
|
|
1015
|
+
}
|
|
1016
|
+
})
|
|
1017
|
+
|
|
1018
|
+
test('item 2b: Maestro case Insights metrics resolve T1', () => {
|
|
1019
|
+
for (const [text, expected] of [
|
|
1020
|
+
['busiest cases', 'top-cases-by-runs'],
|
|
1021
|
+
['top failing cases', 'top-cases-by-faults'],
|
|
1022
|
+
['slowest cases', 'top-cases-by-duration'],
|
|
1023
|
+
['case status over time', 'case-status-timeline'],
|
|
1024
|
+
['failing case elements', 'top-failing-case-elements'],
|
|
1025
|
+
]) {
|
|
1026
|
+
const r = resolveAlias(text)
|
|
1027
|
+
assert.ok(r, `"${text}" did not resolve`)
|
|
1028
|
+
assert.equal(r.key, expected, `"${text}" → ${r?.key}, expected ${expected}`)
|
|
1029
|
+
}
|
|
1030
|
+
})
|
|
1031
|
+
|
|
1032
|
+
test('item 2b: case Insights T1 entries generate clean widgets', () => {
|
|
1033
|
+
for (const name of ['top-cases-by-runs', 'top-cases-by-faults', 'top-cases-by-duration', 'case-status-timeline', 'top-failing-case-elements']) genWidget(name)
|
|
1034
|
+
})
|
|
1035
|
+
|
|
1036
|
+
test('item 2c: element-latency-stats resolves T2', () => {
|
|
1037
|
+
const r = resolveAlias('element latency stats')
|
|
1038
|
+
assert.ok(r, 'did not resolve')
|
|
1039
|
+
assert.equal(r.tier, 'T2')
|
|
1040
|
+
assert.equal(r.key, 'element-latency-stats')
|
|
1041
|
+
})
|
|
1042
|
+
|
|
1043
|
+
test('scope: Insights RTM + PIMS present in all scope lists', () => {
|
|
1044
|
+
const build = readFileSync(resolve(__dirname, '../build-dashboard.mjs'), 'utf8')
|
|
1045
|
+
const scopesLine = build.match(/DASHBOARD_SCOPES\s*=\s*'([^']*)'/)?.[1] ?? ''
|
|
1046
|
+
const granted = scopesLine.split(/\s+/)
|
|
1047
|
+
for (const s of ['Insights', 'Insights.RealTimeData', 'OR.Folders', 'PIMS']) {
|
|
1048
|
+
assert.ok(granted.includes(s), `DASHBOARD_SCOPES missing ${s}`)
|
|
1049
|
+
}
|
|
1050
|
+
const impl = readFileSync(resolve(__dirname, '../../../../references/dashboards/plugins/build/impl.md'), 'utf8')
|
|
1051
|
+
assert.ok(/--user-scope "[^"]*Insights,Insights\.RealTimeData[^"]*PIMS/.test(impl), 'full create command missing Insights RTM + PIMS')
|
|
1052
|
+
})
|
|
1053
|
+
|
|
1054
|
+
// ── Robustness fixes (Agent-Ops postmortem: KPI delta, row-click, columns, OData lint) ──
|
|
1055
|
+
|
|
1056
|
+
test('Defect 1: KPI with previousField/deltaPolarity emits a delta badge', () => {
|
|
1057
|
+
const out = buildWidgetFile(
|
|
1058
|
+
{ name: 'active-agents', tier: 'T3', title: 'Active Agents', displayAs: 'kpi-card', valueField: 'value', previousField: 'previous', deltaPolarity: 'up-good' },
|
|
1059
|
+
null, '30d'
|
|
1060
|
+
)
|
|
1061
|
+
assert.equal(out.match(/<[A-Z][A-Z_]*>|<<[A-Z_]+>>/g), null, 'leftover placeholders')
|
|
1062
|
+
assert.ok(out.includes('kpiDelta('), 'KPI must compute kpiDelta')
|
|
1063
|
+
assert.ok(out.includes('DeltaBadge'), 'KPI must render DeltaBadge')
|
|
1064
|
+
assert.ok(out.includes("const PREVIOUS_FIELD = 'previous'"), 'previousField not substituted')
|
|
1065
|
+
assert.ok(out.includes("const DELTA_POLARITY = 'up-good'"), 'deltaPolarity not substituted')
|
|
1066
|
+
})
|
|
1067
|
+
|
|
1068
|
+
test('Defect 1: plain KPI (no previous) still generates and shows no badge data', () => {
|
|
1069
|
+
const out = buildWidgetFile(
|
|
1070
|
+
{ name: 'running-jobs', tier: 'T3', title: 'Running Jobs', displayAs: 'kpi-card', valueField: 'count' },
|
|
1071
|
+
null, '30d'
|
|
1072
|
+
)
|
|
1073
|
+
assert.equal(out.match(/<[A-Z][A-Z_]*>|<<[A-Z_]+>>/g), null, 'leftover placeholders')
|
|
1074
|
+
assert.ok(out.includes("const PREVIOUS_FIELD = ''"), 'plain KPI must have empty PREVIOUS_FIELD')
|
|
1075
|
+
})
|
|
1076
|
+
|
|
1077
|
+
test('Defect 1: registry active-agents-kpi defaults drive the delta badge', () => {
|
|
1078
|
+
const entry = registry.t1['active-agents-kpi']
|
|
1079
|
+
assert.equal(entry.defaults.previousField, 'previous')
|
|
1080
|
+
const out = buildWidgetFile({ name: 'active-agents-kpi', tier: 'T1', title: 'Active Agents', displayAs: 'kpi-card' }, entry, '30d')
|
|
1081
|
+
assert.ok(out.includes("const PREVIOUS_FIELD = 'previous'"), 'registry previousField default not applied')
|
|
1082
|
+
assert.ok(out.includes('DeltaBadge'))
|
|
1083
|
+
})
|
|
1084
|
+
|
|
1085
|
+
test('Defect 1: kpi-card with no valueField defaults to value/previous (no "headline 1" count)', () => {
|
|
1086
|
+
const out = buildWidgetFile(
|
|
1087
|
+
{ name: 'gov-violations', tier: 'T3', title: 'Governance Violations', displayAs: 'kpi-card' },
|
|
1088
|
+
null, '30d'
|
|
1089
|
+
)
|
|
1090
|
+
assert.equal(out.match(/<[A-Z][A-Z_]*>|<<[A-Z_]+>>/g), null, 'leftover placeholders')
|
|
1091
|
+
assert.ok(out.includes("const VALUE_FIELD = 'value'"), 'kpi-card must default VALUE_FIELD to "value", not render data.length')
|
|
1092
|
+
assert.ok(out.includes("const PREVIOUS_FIELD = 'previous'"), 'conventional kpi-card must default PREVIOUS_FIELD to "previous"')
|
|
1093
|
+
})
|
|
1094
|
+
|
|
1095
|
+
test('Defect 1: kpi-card with explicitly empty valueField fails loud', () => {
|
|
1096
|
+
assert.throws(
|
|
1097
|
+
() => buildWidgetFile(
|
|
1098
|
+
{ name: 'broken-kpi', tier: 'T3', title: 'Broken', displayAs: 'kpi-card', valueField: '' },
|
|
1099
|
+
null, '30d'
|
|
1100
|
+
),
|
|
1101
|
+
/no valueField/,
|
|
1102
|
+
'kpi-card with empty valueField must throw rather than silently render the row count'
|
|
1103
|
+
)
|
|
1104
|
+
})
|
|
1105
|
+
|
|
1106
|
+
test('Defect 2: table with rowLink emits onRowClick navigate; without rowLink it does not', () => {
|
|
1107
|
+
const withLink = buildWidgetFile(
|
|
1108
|
+
{ name: 'all-agents', tier: 'T3', title: 'Agents', displayAs: 'data-table', columns: '[{key:"agentName",label:"Agent"}]', rowLink: { key: 'agentName' }, defaultSortAsc: true },
|
|
1109
|
+
null, '30d'
|
|
1110
|
+
)
|
|
1111
|
+
assert.equal(withLink.match(/<[A-Z][A-Z_]*>|<<[A-Z_]+>>/g), null, 'leftover placeholders')
|
|
1112
|
+
assert.ok(withLink.includes("const ROW_LINK_KEY = 'agentName'"), 'rowLink key not substituted')
|
|
1113
|
+
assert.ok(withLink.includes("const ROW_LINK_ROUTE = '/allagents'"), 'rowLink route not derived')
|
|
1114
|
+
assert.ok(withLink.includes('onRowClick'), 'rowLink table must wire onRowClick')
|
|
1115
|
+
assert.ok(withLink.includes('defaultSortAsc={true}'), 'defaultSortAsc not substituted')
|
|
1116
|
+
|
|
1117
|
+
const noLink = buildWidgetFile(
|
|
1118
|
+
{ name: 'plain', tier: 'T3', title: 'Plain', displayAs: 'data-table', columns: '[{key:"a",label:"A"}]' },
|
|
1119
|
+
null, '30d'
|
|
1120
|
+
)
|
|
1121
|
+
assert.ok(noLink.includes("const ROW_LINK_KEY = ''"), 'plain table must have empty ROW_LINK_KEY')
|
|
1122
|
+
})
|
|
1123
|
+
|
|
1124
|
+
test('Defect 2: generateKeyedDetailViewFile imports fetchDetailByKey + reads route param', () => {
|
|
1125
|
+
const view = generateKeyedDetailViewFile({
|
|
1126
|
+
componentName: 'AllAgents', title: 'Agents', subtitle: 'Spans',
|
|
1127
|
+
moduleSpecifier: '@/metrics/all-agents', detailColumns: null,
|
|
1128
|
+
})
|
|
1129
|
+
assert.ok(view.includes("import { fetchDetailByKey } from '@/metrics/all-agents'"), 'keyed view must import fetchDetailByKey')
|
|
1130
|
+
assert.ok(view.includes('useParams'), 'keyed view must read the route param')
|
|
1131
|
+
assert.ok(view.includes('fetchDetailByKey(sdk, key, getToken)'), 'keyed view must call fetchDetailByKey with key')
|
|
1132
|
+
assert.ok(view.includes('export function AllAgentsDetailView()'), 'keyed view component name')
|
|
1133
|
+
})
|
|
1134
|
+
|
|
1135
|
+
test('Defect 3: table with no columns auto-detects at runtime (no error, no name/value placeholder)', () => {
|
|
1136
|
+
// detailColumns-only (the postmortem mistake) no longer breaks — it auto-detects.
|
|
1137
|
+
const errs = validateIntent({
|
|
1138
|
+
schemaVersion: 2, dashboardName: 'D', timeRange: '30d',
|
|
1139
|
+
metrics: [{ name: 'agents', tier: 'T3', title: 'Agents', displayAs: 'data-table', detailColumns: [{ key: 'a', label: 'A' }] }],
|
|
1140
|
+
})
|
|
1141
|
+
assert.deepEqual(errs, [])
|
|
1142
|
+
const out = buildWidgetFile({ name: 'agents', tier: 'T3', title: 'Agents', displayAs: 'data-table' }, null, '30d')
|
|
1143
|
+
assert.equal(out.match(/<[A-Z][A-Z_]*>|<<[A-Z_]+>>/g), null, 'leftover placeholders')
|
|
1144
|
+
assert.ok(out.includes('COLUMNS.length ? COLUMNS : autoColumns(data)'), 'table must auto-detect columns when none given')
|
|
1145
|
+
assert.ok(out.includes('const COLUMNS: ColumnDef<Row>[] = []'), 'no explicit columns → empty COLUMNS')
|
|
1146
|
+
assert.ok(!out.includes('{key:"name",label:"Name"},{key:"value"'), 'must not emit static name/value placeholder')
|
|
1147
|
+
})
|
|
1148
|
+
|
|
1149
|
+
test('Defect 3: explicit columns honored; T1 registry tables unaffected', () => {
|
|
1150
|
+
const out = buildWidgetFile({ name: 'agents', tier: 'T3', title: 'Agents', displayAs: 'data-table', columns: '[{key:"agentName",label:"Agent"}]' }, null, '30d')
|
|
1151
|
+
assert.ok(out.includes('agentName'))
|
|
1152
|
+
const t1ok = validateIntent({
|
|
1153
|
+
schemaVersion: 2, dashboardName: 'D', timeRange: '30d',
|
|
1154
|
+
metrics: [{ name: 'job-failures', tier: 'T1', title: 'Faulted Jobs' }],
|
|
1155
|
+
})
|
|
1156
|
+
assert.deepEqual(t1ok, [])
|
|
1157
|
+
})
|
|
1158
|
+
|
|
1159
|
+
test('Defect 2: invalid rowLink is rejected', () => {
|
|
1160
|
+
const errs = validateIntent({
|
|
1161
|
+
schemaVersion: 2, dashboardName: 'D', timeRange: '30d',
|
|
1162
|
+
metrics: [{ name: 'agents', tier: 'T3', title: 'Agents', displayAs: 'data-table', columns: '[{key:"a",label:"A"}]', rowLink: { foo: 'bar' } }],
|
|
1163
|
+
})
|
|
1164
|
+
assert.ok(errs.some(e => /rowLink must be an object with a string "key"/.test(e)))
|
|
1165
|
+
})
|
|
1166
|
+
|
|
1167
|
+
test('multi-line series: an array in intent.json compiles to a TS literal (not [object Object])', () => {
|
|
1168
|
+
const out = buildWidgetFile(
|
|
1169
|
+
{ name: 'agent-latency', tier: 'T3', title: 'Latency', displayAs: 'multi-line-chart', xKey: 'date',
|
|
1170
|
+
series: [{ key: 'P50', color: 'hsl(var(--chart-1))' }, { key: 'P95', color: 'hsl(var(--chart-2))', label: 'P95 (ms)' }] },
|
|
1171
|
+
null, '30d'
|
|
1172
|
+
)
|
|
1173
|
+
assert.ok(!out.includes('[object Object]'), 'array series must not coerce to [object Object]')
|
|
1174
|
+
assert.ok(out.includes('key:"P50",color:"hsl(var(--chart-1))"'), 'series key/color not compiled')
|
|
1175
|
+
assert.ok(out.includes('label:"P95 (ms)"'), 'optional series label not compiled')
|
|
1176
|
+
})
|
|
1177
|
+
|
|
1178
|
+
test('multi-line series: a string literal (registry form) passes through unchanged', () => {
|
|
1179
|
+
const out = buildWidgetFile(
|
|
1180
|
+
{ name: 'x', tier: 'T3', title: 'X', displayAs: 'multi-line-chart', xKey: 'date',
|
|
1181
|
+
series: '[{key:"Completed",color:"hsl(var(--chart-3))"}]' },
|
|
1182
|
+
null, '30d'
|
|
1183
|
+
)
|
|
1184
|
+
assert.ok(out.includes('key:"Completed"') && !out.includes('[object Object]'))
|
|
1185
|
+
})
|
|
1186
|
+
|
|
1187
|
+
// NOTE: scaffold tsconfig (incremental/skipLibCheck) is now validated in
|
|
1188
|
+
// apps-dev-tools (it owns the scaffold source) — not a skill concern.
|
|
1189
|
+
|
|
1190
|
+
// ── Governance violations (gated, runtime compliance) ─────────────────────────
|
|
1191
|
+
|
|
1192
|
+
const TRACE_GOV_KEYS = new Set(['agent-governance-violations', 'violations-by-standard', 'violations-by-rule', 'violations-by-hook', 'agents-by-violations', 'recent-violations', 'agent-compliance-report'])
|
|
1193
|
+
|
|
1194
|
+
test('governance: trace metrics resolve T1 only on explicit runtime/standard/rule aliases', () => {
|
|
1195
|
+
for (const [text, expected] of [
|
|
1196
|
+
['rule violations', 'agent-governance-violations'],
|
|
1197
|
+
['runtime compliance violations', 'agent-governance-violations'],
|
|
1198
|
+
['violations by standard', 'violations-by-standard'],
|
|
1199
|
+
['iso violations', 'violations-by-standard'],
|
|
1200
|
+
['violations by rule', 'violations-by-rule'],
|
|
1201
|
+
['violations by hook', 'violations-by-hook'],
|
|
1202
|
+
['agents with most violations', 'agents-by-violations'],
|
|
1203
|
+
['rule violation log', 'recent-violations'],
|
|
1204
|
+
['agent compliance report', 'agent-compliance-report'],
|
|
1205
|
+
]) {
|
|
1206
|
+
const r = resolveAlias(text)
|
|
1207
|
+
assert.ok(r, `"${text}" did not resolve`)
|
|
1208
|
+
assert.equal(r.key, expected, `"${text}" → ${r?.key}, expected ${expected}`)
|
|
1209
|
+
}
|
|
1210
|
+
})
|
|
1211
|
+
|
|
1212
|
+
test('governance gate: plain agent-ops phrases do NOT pull in a trace-governance metric', () => {
|
|
1213
|
+
for (const text of ['active agents', 'agent health', 'agent latency p95', 'memory calls over time', 'faulted jobs', 'busiest processes']) {
|
|
1214
|
+
const r = resolveAlias(text)
|
|
1215
|
+
assert.ok(!r || !TRACE_GOV_KEYS.has(r.key), `"${text}" must not resolve to a trace-governance metric (got ${r?.key})`)
|
|
1216
|
+
}
|
|
1217
|
+
})
|
|
1218
|
+
|
|
1219
|
+
test('governance no-regression: generic governance/policy phrases route to Insights-API metrics, not trace scan', () => {
|
|
1220
|
+
// The existing Governance SDK metrics must keep winning generic intent — the
|
|
1221
|
+
// trace-derived widgets are reserved for EXPLICIT runtime/standard/rule asks.
|
|
1222
|
+
for (const [text, expected] of [
|
|
1223
|
+
['policy denials', 'policy-denials'],
|
|
1224
|
+
['denied actions', 'policy-denials'],
|
|
1225
|
+
['blocked actions', 'policy-denials'],
|
|
1226
|
+
['policy violations', 'policy-denials'],
|
|
1227
|
+
['governance denials', 'policy-denials'],
|
|
1228
|
+
['governance summary', 'governance-verdicts'],
|
|
1229
|
+
['allow deny breakdown', 'governance-verdicts'],
|
|
1230
|
+
['enforcement summary', 'governance-verdicts'],
|
|
1231
|
+
['governance overview', 'governance-verdicts'],
|
|
1232
|
+
]) {
|
|
1233
|
+
const r = resolveAlias(text)
|
|
1234
|
+
assert.ok(r, `"${text}" did not resolve`)
|
|
1235
|
+
assert.ok(!TRACE_GOV_KEYS.has(r.key), `"${text}" must NOT route to a trace-governance metric (got ${r?.key})`)
|
|
1236
|
+
assert.equal(r.key, expected, `"${text}" → ${r?.key}, expected ${expected}`)
|
|
1237
|
+
}
|
|
1238
|
+
})
|
|
1239
|
+
|
|
1240
|
+
test('governance: matched-rules-by-action resolves and is an enforcement-action donut', () => {
|
|
1241
|
+
const r = resolveAlias('matched rules by action')
|
|
1242
|
+
assert.ok(r && r.key === 'matched-rules-by-action', `"matched rules by action" → ${r?.key}`)
|
|
1243
|
+
const entry = registry.t1['matched-rules-by-action']
|
|
1244
|
+
assert.equal(entry.template, 'donut-chart')
|
|
1245
|
+
assert.match(entry.description, /block.*audit.*allow|action/i)
|
|
1246
|
+
})
|
|
1247
|
+
|
|
1248
|
+
test('governance: agent-compliance-report detailView leads with multi-line + by-action, keyed on runKey', () => {
|
|
1249
|
+
const entry = registry.t1['agent-compliance-report']
|
|
1250
|
+
assert.equal(entry.defaults.rowLink.key, 'runKey', 'compliance report must key rowLink on the run (runKey)')
|
|
1251
|
+
const widgets = entry.defaults.detailView.widgets
|
|
1252
|
+
assert.equal(widgets[0].displayAs, 'multi-line-chart', 'first detail sub-widget must be the Pass-vs-Matched multi-line')
|
|
1253
|
+
assert.equal(widgets[0].source, 'byOutcomeByHook')
|
|
1254
|
+
assert.ok(Array.isArray(widgets[0].series) && widgets[0].series.length === 2, 'multi-line must declare Pass/Matched series')
|
|
1255
|
+
assert.equal(widgets[1].displayAs, 'donut-chart', 'second detail sub-widget must be the by-action donut')
|
|
1256
|
+
assert.equal(widgets[1].source, 'byAction')
|
|
1257
|
+
// The whole detail view must compile (multi-line series wired, no leftover placeholders).
|
|
1258
|
+
const view = buildViewSpec('AgentComplianceReport', { name: 'agent-compliance-report', title: 'Agent Compliance' }, entry, '30d')
|
|
1259
|
+
assert.ok(view.detailView, 'detailView must resolve from registry defaults')
|
|
1260
|
+
})
|
|
1261
|
+
|
|
1262
|
+
test('governance: all violation entries generate clean widgets (rowLink wired where set)', () => {
|
|
1263
|
+
for (const name of ['agent-governance-violations', 'violations-by-standard', 'violations-by-rule', 'violations-by-hook', 'matched-rules-by-action', 'agents-by-violations', 'recent-violations', 'agent-compliance-report']) {
|
|
1264
|
+
const entry = registry.t1[name]
|
|
1265
|
+
assert.ok(entry, `missing ${name}`)
|
|
1266
|
+
const metric = { name, tier: 'T1', title: entry.defaults.title, displayAs: entry.template, ...entry.defaults }
|
|
1267
|
+
const content = buildWidgetFile(metric, entry, '30d')
|
|
1268
|
+
assert.equal(content.match(/<[A-Z][A-Z_]*>|<<[A-Z_]+>>/g), null, `${name} leftover placeholders`)
|
|
1269
|
+
assert.ok(content.includes(`import { fetchData } from '@/metrics/${name}'`), `${name} missing fetchData import`)
|
|
1270
|
+
if (entry.defaults.rowLink) assert.ok(content.includes('onRowClick'), `${name} rowLink not wired`)
|
|
1271
|
+
}
|
|
1272
|
+
})
|
|
1273
|
+
|
|
1274
|
+
// ── Regression guards: every build path must be kept in lockstep ──────────────
|
|
1275
|
+
// (Caught the externalization bug where incremental edits never set the widgets
|
|
1276
|
+
// dir and the row-click drill-down was wired only into the fresh build.)
|
|
1277
|
+
|
|
1278
|
+
test('regression: widgets dir is set + extraction asserted on every widget-generating path', () => {
|
|
1279
|
+
// Orchestration spans build-dashboard.mjs + the extracted use-case flows; the
|
|
1280
|
+
// per-path guards (setWidgetsDir, assertScaffoldExtracted, keyed-view wiring) now
|
|
1281
|
+
// live in flows/*.mjs, so grep the combined source.
|
|
1282
|
+
const src = ['../build-dashboard.mjs', '../flows/build.mjs', '../flows/edit.mjs', '../flows/upgrade.mjs', '../flows/template.mjs']
|
|
1283
|
+
.map(f => readFileSync(resolve(__dirname, f), 'utf8')).join('\n')
|
|
1284
|
+
const setCalls = (src.match(/setWidgetsDir\(join\(P, '_gen', 'widgets'\)\)/g) || []).length
|
|
1285
|
+
assert.ok(setCalls >= 3, `setWidgetsDir(<proj>/_gen/widgets) must run on fresh build, upgrade, AND incremental edit; found ${setCalls}`)
|
|
1286
|
+
const assertCalls = (src.match(/assertScaffoldExtracted\(/g) || []).length
|
|
1287
|
+
// definition + fresh + upgrade + incremental + prewarm = 5
|
|
1288
|
+
assert.ok(assertCalls >= 5, `assertScaffoldExtracted must guard every build entry path; found ${assertCalls}`)
|
|
1289
|
+
})
|
|
1290
|
+
|
|
1291
|
+
test('regression: row-click keyed views are wired on fresh + incremental + upgrade', () => {
|
|
1292
|
+
// Orchestration spans build-dashboard.mjs + the extracted use-case flows; the
|
|
1293
|
+
// per-path guards (setWidgetsDir, assertScaffoldExtracted, keyed-view wiring) now
|
|
1294
|
+
// live in flows/*.mjs, so grep the combined source.
|
|
1295
|
+
const src = ['../build-dashboard.mjs', '../flows/build.mjs', '../flows/edit.mjs', '../flows/upgrade.mjs', '../flows/template.mjs']
|
|
1296
|
+
.map(f => readFileSync(resolve(__dirname, f), 'utf8')).join('\n')
|
|
1297
|
+
assert.ok(src.includes('injectAppRoutes(P, generatedViewNames, keyedViewWidgets)'), 'fresh build must inject keyed routes')
|
|
1298
|
+
assert.ok((src.match(/collectKeyedViews\(P, state\)/g) || []).length >= 2, 'incremental + upgrade must inject keyed routes via collectKeyedViews')
|
|
1299
|
+
assert.ok((src.match(/writeKeyedViewIfRowLink\(/g) || []).length >= 3, 'keyed detail view must be (re)written on ADD, CHANGE, and REBUILD')
|
|
1300
|
+
// no bare 2-arg injectAppRoutes call survives (the old broken incremental path)
|
|
1301
|
+
assert.ok(!/injectAppRoutes\(P, viewNames\)(?!,)/.test(src), 'no injectAppRoutes call may omit keyed views')
|
|
1302
|
+
})
|
|
1303
|
+
|
|
1304
|
+
// ── Rich detail views (detailView spec) ───────────────────────────────────────
|
|
1305
|
+
|
|
1306
|
+
/** Minimal valid schemaVersion-2 intent with one overridable metric. */
|
|
1307
|
+
function baseIntent(overrides = {}) {
|
|
1308
|
+
const { metrics, ...rest } = overrides
|
|
1309
|
+
return {
|
|
1310
|
+
schemaVersion: 2,
|
|
1311
|
+
dashboardName: 'D',
|
|
1312
|
+
timeRange: '30d',
|
|
1313
|
+
metrics: metrics ?? [{ name: 'agents', tier: 'T3', title: 'Agents', displayAs: 'data-table' }],
|
|
1314
|
+
...rest,
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
// TASK 2 — schema validation -----------------------------------------------------
|
|
1319
|
+
|
|
1320
|
+
test('detailView: valid spec on a rowLink table passes validateIntent', () => {
|
|
1321
|
+
const errs = validateIntent(baseIntent({ metrics: [{
|
|
1322
|
+
name: 'agents', tier: 'T3', title: 'Agents', displayAs: 'data-table',
|
|
1323
|
+
rowLink: { key: 'name' },
|
|
1324
|
+
detailView: { widgets: [
|
|
1325
|
+
{ displayAs: 'donut-chart', title: 'By Hook', source: 'byHook', xKey: 'name', yKey: 'value' },
|
|
1326
|
+
{ displayAs: 'data-table', title: 'All', source: 'rows' },
|
|
1327
|
+
] },
|
|
1328
|
+
}] }))
|
|
1329
|
+
assert.deepEqual(errs, [])
|
|
1330
|
+
})
|
|
1331
|
+
|
|
1332
|
+
test('detailView: chart sub-widget missing xKey/yKey throws', () => {
|
|
1333
|
+
assert.throws(() => validateIntent(baseIntent({ metrics: [{
|
|
1334
|
+
name: 'agents', tier: 'T3', title: 'Agents', displayAs: 'data-table',
|
|
1335
|
+
rowLink: { key: 'name' },
|
|
1336
|
+
detailView: { widgets: [{ displayAs: 'donut-chart', title: 'By Hook', source: 'byHook' }] },
|
|
1337
|
+
}] })), /xKey|yKey/i)
|
|
1338
|
+
})
|
|
1339
|
+
|
|
1340
|
+
test('detailView: bad displayAs throws', () => {
|
|
1341
|
+
assert.throws(() => validateIntent(baseIntent({ metrics: [{
|
|
1342
|
+
name: 'agents', tier: 'T3', title: 'Agents', displayAs: 'data-table',
|
|
1343
|
+
rowLink: { key: 'name' },
|
|
1344
|
+
detailView: { widgets: [{ displayAs: 'pie', title: 'By Hook', source: 'byHook', xKey: 'name', yKey: 'value' }] },
|
|
1345
|
+
}] })), /displayAs/i)
|
|
1346
|
+
})
|
|
1347
|
+
|
|
1348
|
+
test('detailView: requires rowLink.key or detail:true', () => {
|
|
1349
|
+
assert.throws(() => validateIntent(baseIntent({ metrics: [{
|
|
1350
|
+
name: 'agents', tier: 'T3', title: 'Agents', displayAs: 'data-table',
|
|
1351
|
+
detailView: { widgets: [{ displayAs: 'donut-chart', title: 'By Hook', source: 'byHook', xKey: 'name', yKey: 'value' }] },
|
|
1352
|
+
}] })), /detailView requires/i)
|
|
1353
|
+
})
|
|
1354
|
+
|
|
1355
|
+
// TASK 3 — compileDetailWidgets + keyed view -------------------------------------
|
|
1356
|
+
|
|
1357
|
+
test('compileDetailWidgets: emits primitive imports + JSX per sub-widget', () => {
|
|
1358
|
+
const { imports, jsx } = compileDetailWidgets({ widgets: [
|
|
1359
|
+
{ displayAs: 'donut-chart', title: 'By Hook', source: 'byHook', xKey: 'name', yKey: 'value' },
|
|
1360
|
+
{ displayAs: 'ranked-table', title: 'Rules', source: 'byRule', columns: [{ key: 'name', label: 'Rule' }] },
|
|
1361
|
+
{ displayAs: 'data-table', title: 'All', source: 'rows' },
|
|
1362
|
+
]}, 'd')
|
|
1363
|
+
assert.ok([...imports].some(i => i.includes('Donut')), 'Donut import missing')
|
|
1364
|
+
assert.ok(jsx.includes('d["byHook"]') || jsx.includes('d.byHook'), 'byHook source not referenced')
|
|
1365
|
+
assert.ok(jsx.includes('RecordsTable'), 'table sub-widget not via RecordsTable')
|
|
1366
|
+
})
|
|
1367
|
+
|
|
1368
|
+
test('detailView falls back to registry entry defaults (buildViewSpec)', () => {
|
|
1369
|
+
// A cataloged metric ships its detailView via registry defaults — the intent
|
|
1370
|
+
// metric need not restate it. Guards the fresh-build + view-spec fallback.
|
|
1371
|
+
const entry = { defaults: { detailView: { widgets: [
|
|
1372
|
+
{ displayAs: 'donut-chart', title: 'By Hook', source: 'byHook', xKey: 'name', yKey: 'value' },
|
|
1373
|
+
] } } }
|
|
1374
|
+
const spec = buildViewSpec('AgentComplianceReport', { name: 'agent-compliance-report', title: 'Agent Compliance' }, entry, '30d')
|
|
1375
|
+
assert.ok(spec.detailView, 'detailView should fall back to entry.defaults.detailView')
|
|
1376
|
+
assert.equal(spec.detailView.widgets[0].source, 'byHook')
|
|
1377
|
+
})
|
|
1378
|
+
|
|
1379
|
+
test('generateKeyedDetailViewFile: with detailView imports charts + references source map', () => {
|
|
1380
|
+
const view = generateKeyedDetailViewFile({
|
|
1381
|
+
componentName: 'AllAgents', title: 'Agents', subtitle: 'Spans',
|
|
1382
|
+
moduleSpecifier: '@/metrics/all-agents', detailColumns: null,
|
|
1383
|
+
detailView: { widgets: [
|
|
1384
|
+
{ displayAs: 'donut-chart', title: 'By Hook', source: 'byHook', xKey: 'name', yKey: 'value' },
|
|
1385
|
+
] },
|
|
1386
|
+
})
|
|
1387
|
+
assert.ok(view.includes("@/dashboard/charts"), 'keyed detailView must import chart primitives')
|
|
1388
|
+
assert.ok(view.includes('d["byHook"]') || view.includes('d.byHook'), 'keyed detailView must reference source map')
|
|
1389
|
+
assert.ok(view.includes('fetchDetailByKey(sdk, key, getToken)'), 'keyed view must still call fetchDetailByKey')
|
|
1390
|
+
})
|
|
1391
|
+
|
|
1392
|
+
test('generateKeyedDetailViewFile: without detailView still renders a single RecordsTable (backward-compat)', () => {
|
|
1393
|
+
const view = generateKeyedDetailViewFile({
|
|
1394
|
+
componentName: 'AllAgents', title: 'Agents', subtitle: 'Spans',
|
|
1395
|
+
moduleSpecifier: '@/metrics/all-agents', detailColumns: null,
|
|
1396
|
+
})
|
|
1397
|
+
assert.ok(view.includes('<RecordsTable'), 'no-detailView keyed view must render a single RecordsTable')
|
|
1398
|
+
assert.ok(!view.includes("@/dashboard/charts"), 'no-detailView keyed view must not import charts')
|
|
1399
|
+
})
|
|
1400
|
+
|
|
1401
|
+
// TASK 4 — chart record-grain view (generateViewFile) ----------------------------
|
|
1402
|
+
|
|
1403
|
+
test('generateViewFile: with detailView imports charts + references source map', () => {
|
|
1404
|
+
const view = generateViewFile({
|
|
1405
|
+
componentName: 'Errors', title: 'Errors', subtitle: 'Last 30d',
|
|
1406
|
+
moduleSpecifier: '@/metrics/errors', detailExport: 'fetchDetail', detailColumns: null,
|
|
1407
|
+
detailView: { widgets: [
|
|
1408
|
+
{ displayAs: 'bar-chart', title: 'By Hook', source: 'byHook', xKey: 'name', yKey: 'value' },
|
|
1409
|
+
] },
|
|
1410
|
+
})
|
|
1411
|
+
assert.ok(view.includes("@/dashboard/charts"), 'detailView view must import chart primitives')
|
|
1412
|
+
assert.ok(view.includes('d["byHook"]') || view.includes('d.byHook'), 'detailView view must reference source map')
|
|
1413
|
+
})
|
|
1414
|
+
|
|
1415
|
+
test('generateViewFile: without detailView still renders a single RecordsTable (backward-compat)', () => {
|
|
1416
|
+
const view = generateViewFile({
|
|
1417
|
+
componentName: 'Errors', title: 'Errors', subtitle: 'Last 30d',
|
|
1418
|
+
moduleSpecifier: '@/metrics/errors', detailExport: 'fetchData', detailColumns: null,
|
|
1419
|
+
})
|
|
1420
|
+
assert.ok(view.includes('<RecordsTable'), 'no-detailView view must render a single RecordsTable')
|
|
1421
|
+
assert.ok(!view.includes("@/dashboard/charts"), 'no-detailView view must not import charts')
|
|
1422
|
+
})
|
|
1423
|
+
|
|
1424
|
+
// ── Issue 1: rowLink falls back to registry defaults ──────────────────────────
|
|
1425
|
+
|
|
1426
|
+
test('rowLink: a cataloged table whose rowLink lives in registry defaults renders clickable', () => {
|
|
1427
|
+
const content = buildWidgetFile(
|
|
1428
|
+
{ name: 'x', tier: 'T1', title: 'X', displayAs: 'data-table' },
|
|
1429
|
+
{ template: 'data-table', defaults: { rowLink: { key: 'agentName' } } },
|
|
1430
|
+
'30d'
|
|
1431
|
+
)
|
|
1432
|
+
assert.ok(content.includes('onRowClick'), 'rowLink from registry defaults must wire onRowClick')
|
|
1433
|
+
assert.ok(content.includes("const ROW_LINK_KEY = 'agentName'"), 'ROW_LINK_KEY must come from registry defaults.rowLink.key')
|
|
1434
|
+
})
|
|
1435
|
+
|
|
1436
|
+
// ── Issue 2: per-metric explanatory empty states (emptyMessage) ────────────────
|
|
1437
|
+
// These exercise the <<EMPTY_MESSAGE>> / <EMPTY_MESSAGE> placeholders added to the
|
|
1438
|
+
// widget templates. The placeholders live in the apps-dev-tools sibling source
|
|
1439
|
+
// (the orchestrator rebuilds the fixture zip from it during the self-test build);
|
|
1440
|
+
// run against that source when present so the assertions track the edited templates.
|
|
1441
|
+
const SIBLING_WIDGETS = resolve(__dirname, '../../../../../../../apps-dev-tools/uipath-dashboard-starter-kit/widgets')
|
|
1442
|
+
function withSiblingTemplates(fn) {
|
|
1443
|
+
if (!existsSync(SIBLING_WIDGETS)) return // sibling source unavailable — skip
|
|
1444
|
+
const prev = WIDGETS_DIR_FOR_TESTS
|
|
1445
|
+
setWidgetsDir(SIBLING_WIDGETS)
|
|
1446
|
+
try { fn() } finally { if (prev) setWidgetsDir(prev) }
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1449
|
+
test('emptyMessage: table metric emits the per-metric empty message (not bare "No data")', () => {
|
|
1450
|
+
withSiblingTemplates(() => {
|
|
1451
|
+
const content = buildWidgetFile(
|
|
1452
|
+
{ name: 'gov', tier: 'T3', title: 'Violations', displayAs: 'data-table', emptyMessage: 'Rules are passing' },
|
|
1453
|
+
null, '30d'
|
|
1454
|
+
)
|
|
1455
|
+
assert.ok(content.includes('Rules are passing'), 'table empty message not substituted')
|
|
1456
|
+
assert.ok(!content.includes('message="No data"'), 'bare "No data" must be replaced')
|
|
1457
|
+
})
|
|
1458
|
+
})
|
|
1459
|
+
|
|
1460
|
+
test('emptyMessage: chart metric emits the per-metric empty message', () => {
|
|
1461
|
+
withSiblingTemplates(() => {
|
|
1462
|
+
const content = buildWidgetFile(
|
|
1463
|
+
{ name: 'gov-trend', tier: 'T3', title: 'Violations Trend', displayAs: 'area-chart', xKey: 'date', yKey: 'value', emptyMessage: 'Rules are passing' },
|
|
1464
|
+
null, '30d'
|
|
1465
|
+
)
|
|
1466
|
+
assert.ok(content.includes('Rules are passing'), 'chart empty message not substituted')
|
|
1467
|
+
})
|
|
1468
|
+
})
|
|
1469
|
+
|
|
1470
|
+
test('emptyMessage: falls back to registry defaults.emptyMessage', () => {
|
|
1471
|
+
withSiblingTemplates(() => {
|
|
1472
|
+
const content = buildWidgetFile(
|
|
1473
|
+
{ name: 'x', tier: 'T1', title: 'X', displayAs: 'data-table' },
|
|
1474
|
+
{ template: 'data-table', defaults: { emptyMessage: 'Nothing to report' } },
|
|
1475
|
+
'30d'
|
|
1476
|
+
)
|
|
1477
|
+
assert.ok(content.includes('Nothing to report'), 'emptyMessage must fall back to registry defaults')
|
|
1478
|
+
})
|
|
1479
|
+
})
|
|
1480
|
+
|
|
1481
|
+
// ── Record-grain detail views: noDetail opt-out, KPI drill-down, contract ──────
|
|
1482
|
+
|
|
1483
|
+
test('widgetGetsDetailView: chart gets a view; noDetail chart does not', () => {
|
|
1484
|
+
assert.equal(widgetGetsDetailView('area-chart', { name: 'm' }, null), true, 'plain chart drills down')
|
|
1485
|
+
assert.equal(widgetGetsDetailView('donut-chart', { name: 'm' }, { noDetail: true }), false, 'noDetail chart does not')
|
|
1486
|
+
})
|
|
1487
|
+
|
|
1488
|
+
test('widgetGetsDetailView: kpi-card drills down on metric or registry-default detail', () => {
|
|
1489
|
+
assert.equal(widgetGetsDetailView('kpi-card', { name: 'm' }, null), false, 'plain KPI links nowhere')
|
|
1490
|
+
assert.equal(widgetGetsDetailView('kpi-card', { name: 'm', detail: true }, null), true, 'metric detail:true drills down')
|
|
1491
|
+
// Cataloged KPI defaults the drill-down on via the registry entry.
|
|
1492
|
+
assert.equal(widgetGetsDetailView('kpi-card', { name: 'm' }, { defaults: { detail: true } }), true, 'registry defaults.detail drills down')
|
|
1493
|
+
// Metric override wins — suppress a defaulted-on KPI.
|
|
1494
|
+
assert.equal(widgetGetsDetailView('kpi-card', { name: 'm', detail: false }, { defaults: { detail: true } }), false, 'metric detail:false suppresses')
|
|
1495
|
+
})
|
|
1496
|
+
|
|
1497
|
+
test('registry contract: every default-on KPI ships a detailRecipe + detailColumns', () => {
|
|
1498
|
+
for (const [name, entry] of Object.entries(registry.t1)) {
|
|
1499
|
+
if (entry.template !== 'kpi-card' || entry.defaults?.detail !== true) continue
|
|
1500
|
+
assert.ok(entry.detailRecipe, `${name}: default-drill-down KPI needs a detailRecipe`)
|
|
1501
|
+
assert.ok(entry.defaults?.detailColumns, `${name}: default-drill-down KPI needs defaults.detailColumns`)
|
|
1502
|
+
}
|
|
1503
|
+
})
|
|
1504
|
+
|
|
1505
|
+
test('buildWidgetFile: cataloged default-on KPI renders a clickable card', () => {
|
|
1506
|
+
withSiblingTemplates(() => {
|
|
1507
|
+
const content = buildWidgetFile(
|
|
1508
|
+
{ name: 'active-agents-kpi', tier: 'T1', title: 'Active Agents', displayAs: 'kpi-card' },
|
|
1509
|
+
registry.t1['active-agents-kpi'], '30d'
|
|
1510
|
+
)
|
|
1511
|
+
assert.ok(content.includes("const KPI_DETAIL_ROUTE = '/activeagentskpi'"), 'registry default-on KPI must get a detail route')
|
|
1512
|
+
assert.ok(content.includes('KPI_DETAIL_ROUTE ? () => navigate(KPI_DETAIL_ROUTE)'), 'KPI card must be clickable')
|
|
1513
|
+
})
|
|
1514
|
+
})
|
|
1515
|
+
|
|
1516
|
+
test('widgetGetsDetailView: tables never get a chart-style view', () => {
|
|
1517
|
+
assert.equal(widgetGetsDetailView('data-table', { name: 'm' }, null), false)
|
|
1518
|
+
assert.equal(widgetGetsDetailView('ranked-table', { name: 'm' }, null), false)
|
|
1519
|
+
})
|
|
1520
|
+
|
|
1521
|
+
test('widgetGetsDetailView: T3 chart opts out via metric-level noDetail (no registry entry)', () => {
|
|
1522
|
+
// A T3 custom chart on an aggregate-only endpoint sets "noDetail": true in
|
|
1523
|
+
// intent.json — there is no registry entry to carry the flag.
|
|
1524
|
+
assert.equal(widgetGetsDetailView('area-chart', { name: 'm', tier: 'T3', noDetail: true }, null), false)
|
|
1525
|
+
// Without the flag, a T3 chart still requires a drill-down (fetchDetail enforced).
|
|
1526
|
+
assert.equal(widgetGetsDetailView('area-chart', { name: 'm', tier: 'T3' }, null), true)
|
|
1527
|
+
})
|
|
1528
|
+
|
|
1529
|
+
test('registry contract: every T1 chart is either noDetail or has detailRecipe + detailColumns', () => {
|
|
1530
|
+
const CHARTS = new Set(['area-chart', 'line-chart', 'bar-chart', 'donut-chart', 'multi-line-chart', 'rate-chart'])
|
|
1531
|
+
for (const [name, entry] of Object.entries(registry.t1)) {
|
|
1532
|
+
if (!CHARTS.has(entry.template)) continue
|
|
1533
|
+
if (entry.noDetail === true) {
|
|
1534
|
+
assert.ok(!entry.detailRecipe, `${name}: noDetail chart must not also carry a detailRecipe`)
|
|
1535
|
+
continue
|
|
1536
|
+
}
|
|
1537
|
+
assert.ok(entry.detailRecipe, `${name}: detail-capable chart needs a detailRecipe`)
|
|
1538
|
+
assert.ok(entry.defaults?.detailColumns, `${name}: detail-capable chart needs defaults.detailColumns`)
|
|
1539
|
+
}
|
|
1540
|
+
})
|
|
1541
|
+
|
|
1542
|
+
test('buildWidgetFile: noDetail chart renders a non-clickable card (empty detailRoute, no ViewAllLink)', () => {
|
|
1543
|
+
withSiblingTemplates(() => {
|
|
1544
|
+
const content = buildWidgetFile(
|
|
1545
|
+
{ name: 'agent-memory-timeline', tier: 'T1', title: 'Agent Memory', displayAs: 'area-chart', xKey: 'timeSlice', yKey: 'totalCount' },
|
|
1546
|
+
registry.t1['agent-memory-timeline'], '30d'
|
|
1547
|
+
)
|
|
1548
|
+
assert.ok(content.includes("const detailRoute = ''"), 'noDetail chart must have an empty detailRoute')
|
|
1549
|
+
// The ViewAllLink + card click are runtime-guarded on detailRoute, so an empty
|
|
1550
|
+
// route renders them inert (no navigation, no cursor-pointer) without a separate template.
|
|
1551
|
+
assert.ok(content.includes('detailRoute ? <ViewAllLink'), 'ViewAllLink must be guarded on detailRoute')
|
|
1552
|
+
assert.ok(content.includes('detailRoute ? () => navigate(detailRoute)'), 'card click must be guarded on detailRoute')
|
|
1553
|
+
})
|
|
1554
|
+
})
|
|
1555
|
+
|
|
1556
|
+
test('buildWidgetFile: detail-capable chart renders a clickable card with a route', () => {
|
|
1557
|
+
withSiblingTemplates(() => {
|
|
1558
|
+
const content = buildWidgetFile(
|
|
1559
|
+
{ name: 'agent-error-timeline', tier: 'T1', title: 'Agent Errors', displayAs: 'area-chart', xKey: 'date', yKey: 'value' },
|
|
1560
|
+
registry.t1['agent-error-timeline'], '30d'
|
|
1561
|
+
)
|
|
1562
|
+
assert.ok(content.includes("const detailRoute = '/agenterrortimeline'") ||
|
|
1563
|
+
content.includes("const detailRoute = '/agenterrors'"), 'capable chart must have a non-empty detailRoute')
|
|
1564
|
+
assert.ok(content.includes('detailRoute ? () => navigate(detailRoute)'), 'card click must be guarded on detailRoute')
|
|
1565
|
+
})
|
|
1566
|
+
})
|
|
1567
|
+
|
|
1568
|
+
test('buildWidgetFile: kpi-card with detail:true is clickable; plain KPI is not', () => {
|
|
1569
|
+
withSiblingTemplates(() => {
|
|
1570
|
+
const withDetail = buildWidgetFile(
|
|
1571
|
+
{ name: 'failure-rate', tier: 'T3', title: 'Failure Rate', displayAs: 'kpi-card', valueField: 'value', detail: true },
|
|
1572
|
+
null, '30d'
|
|
1573
|
+
)
|
|
1574
|
+
assert.ok(withDetail.includes("const KPI_DETAIL_ROUTE = '/failurerate'"), 'KPI detail route must be set')
|
|
1575
|
+
assert.ok(withDetail.includes('KPI_DETAIL_ROUTE ? () => navigate(KPI_DETAIL_ROUTE)'), 'KPI card must be clickable')
|
|
1576
|
+
|
|
1577
|
+
const plain = buildWidgetFile(
|
|
1578
|
+
{ name: 'active-agents', tier: 'T3', title: 'Active Agents', displayAs: 'kpi-card', valueField: 'value' },
|
|
1579
|
+
null, '30d'
|
|
1580
|
+
)
|
|
1581
|
+
assert.ok(plain.includes("const KPI_DETAIL_ROUTE = ''"), 'plain KPI must have an empty detail route')
|
|
1582
|
+
})
|
|
1583
|
+
})
|
|
1584
|
+
|
|
1585
|
+
test('buildViewSpec: detailColumns + detailSortKey fall back to registry defaults; export is fetchDetail', () => {
|
|
1586
|
+
const entry = registry.t1['violations-by-standard']
|
|
1587
|
+
const spec = buildViewSpec('ViolationsByStandard', { name: 'violations-by-standard', title: 'Violations by Standard' }, entry, '30d')
|
|
1588
|
+
assert.equal(spec.detailExport, 'fetchDetail', 'detail view runs the record-grain fetchDetail export')
|
|
1589
|
+
assert.ok(spec.detailColumns, 'detailColumns must come from registry defaults when intent omits them')
|
|
1590
|
+
assert.ok(spec.detailColumns.includes('agentName'), 'compiled detailColumns should carry the registry keys')
|
|
1591
|
+
})
|
|
1592
|
+
|
|
1593
|
+
// ── SDK 1.5.1: runtime governance via AgentTraces ─────────────────────────────
|
|
1594
|
+
|
|
1595
|
+
const GOV_151_KEYS = ['agent-governance-violations', 'violations-by-standard', 'violations-by-rule',
|
|
1596
|
+
'violations-by-hook', 'matched-rules-by-action', 'agents-by-violations', 'recent-violations',
|
|
1597
|
+
'agent-compliance-report', 'rule-evaluations-by-outcome', 'rule-evaluations-by-hook', 'rule-compliance']
|
|
1598
|
+
|
|
1599
|
+
test('1.5.1: registry references only the AgentTraces governance surface (no span-scan tokens)', () => {
|
|
1600
|
+
const raw = JSON.stringify(registry)
|
|
1601
|
+
for (const token of ['scanViolations', 'scanEvaluations', 'governance-scan', 'parseGovernanceSpans',
|
|
1602
|
+
'parseRuleEvaluations', 'scanLatestPerAgent', 'last 15', 'Last 15', 'WINDOW_LABEL', 'Traces.getById']) {
|
|
1603
|
+
assert.ok(!raw.includes(token), `registry still references retired workaround token "${token}"`)
|
|
1604
|
+
}
|
|
1605
|
+
})
|
|
1606
|
+
|
|
1607
|
+
test('1.5.1: every runtime-governance entry routes to the AgentTraces governance methods', () => {
|
|
1608
|
+
for (const key of GOV_151_KEYS) {
|
|
1609
|
+
const entry = registry.t1[key]
|
|
1610
|
+
assert.ok(entry, `missing ${key}`)
|
|
1611
|
+
const text = (entry.description ?? '') + (entry.detailRecipe ?? '')
|
|
1612
|
+
assert.match(text, /getGovernanceDecisions|getGovernanceSummary/, `${key} must reference the 1.5.1 methods`)
|
|
1613
|
+
}
|
|
1614
|
+
})
|
|
1615
|
+
|
|
1616
|
+
test('1.5.1: over-cap compliance phrasings are no longer hard-refused', () => {
|
|
1617
|
+
for (const text of ['compliance across all agent runs', 'rule violations for all runs', 'governance checks for the last 100 agent runs']) {
|
|
1618
|
+
const refused = registry.hardRefuse.some(e => new RegExp(e.pattern, 'i').test(text))
|
|
1619
|
+
assert.ok(!refused, `"${text}" must not be refused — getGovernanceDecisions honors real windows`)
|
|
1620
|
+
}
|
|
1621
|
+
})
|
|
1622
|
+
|
|
1623
|
+
test('1.5.1: SDK floor covers the governance methods', () => {
|
|
1624
|
+
assert.equal(MIN_SDK_VERSION, '1.5.1')
|
|
1625
|
+
})
|
|
1626
|
+
|
|
1627
|
+
test('regression: fresh build refuses a nonexistent projectDir (absolute-path mangling guard)', () => {
|
|
1628
|
+
// CI 28589047076: the build subagent was handed a fabricated absolute sandbox
|
|
1629
|
+
// path (coder_eval_… vs the real coder-eval-…), mkdir'd it into existence and
|
|
1630
|
+
// built a phantom sibling project. The build must fail loud when projectDir
|
|
1631
|
+
// does not already exist (the pre-warm created the real one).
|
|
1632
|
+
const src = readFileSync(resolve(__dirname, '../flows/build.mjs'), 'utf8')
|
|
1633
|
+
assert.ok(src.includes('projectDir does not exist'), 'build.mjs must refuse a nonexistent projectDir')
|
|
1634
|
+
assert.match(src, /if \(!existsSync\(P\)\)/, 'guard must run before any write/extract against P')
|
|
1635
|
+
})
|