@geraldmaron/construct 1.2.2 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -10
- package/bin/construct +286 -220
- package/bin/construct-postinstall.mjs +0 -21
- package/commands/work/optimize-prompts.md +1 -1
- package/examples/distribution/sources/adr.md +2 -2
- package/examples/seed-observations/decisions.md +1 -1
- package/lib/artifact-loop-core.mjs +412 -0
- package/lib/artifact-manifest.mjs +6 -0
- package/lib/artifact-release-gate.mjs +115 -49
- package/lib/audit-rules.mjs +2 -4
- package/lib/audit-skills.mjs +32 -20
- package/lib/audit-specialists.mjs +44 -26
- package/lib/auto-docs.mjs +6 -5
- package/lib/brand-prose.mjs +3 -3
- package/lib/brand-tokens.mjs +2 -2
- package/lib/certification/artifact-fixtures.mjs +4 -1
- package/lib/certification/demo-parity.mjs +6 -32
- package/lib/certification/real-llm-scenarios.mjs +58 -13
- package/lib/certification/role-cards.mjs +7 -6
- package/lib/certification/role-overlays.mjs +4 -4
- package/lib/certification/runner.mjs +9 -2
- package/lib/certification/skill-inventory.mjs +34 -22
- package/lib/certification/skill-scenarios.mjs +21 -8
- package/lib/certification/specialist-contracts.mjs +4 -3
- package/lib/certification/specialist-scenarios.mjs +7 -6
- package/lib/certification/status.mjs +5 -4
- package/lib/cli-commands.mjs +56 -57
- package/lib/comment-lint.mjs +1 -5
- package/lib/completions.mjs +20 -2
- package/lib/config/legacy-config-migration.mjs +125 -0
- package/lib/config/schema.mjs +5 -5
- package/lib/config/source-targets.mjs +80 -0
- package/lib/contracts/validate.mjs +119 -51
- package/lib/decisions/golden.mjs +3 -2
- package/lib/decisions/registry.mjs +11 -6
- package/lib/demo-project.mjs +188 -0
- package/lib/demo-recording.mjs +9 -34
- package/lib/demo-script.mjs +2 -2
- package/lib/demo-surface.mjs +13 -131
- package/lib/demo-tour-renderer.mjs +92 -0
- package/lib/demo.mjs +69 -54
- package/lib/diagram-export.mjs +63 -14
- package/lib/doctor/source-checkout.mjs +3 -4
- package/lib/doctor/watchers/consistency.mjs +90 -22
- package/lib/doctor/watchers/credential-parity.mjs +97 -0
- package/lib/doctor/watchers/mcp-protocol.mjs +1 -1
- package/lib/doctor/watchers/service-health.mjs +4 -32
- package/lib/document-export.mjs +73 -9
- package/lib/document-extract.mjs +10 -198
- package/lib/embed/artifact.mjs +2 -3
- package/lib/embed/cli.mjs +1 -1
- package/lib/embed/config.mjs +1 -2
- package/lib/embed/customer-profiles.mjs +1 -1
- package/lib/embed/daemon.mjs +5 -6
- package/lib/embed/demand-fetch.mjs +114 -2
- package/lib/embed/docs-lifecycle.mjs +9 -5
- package/lib/embed/intake-metrics.mjs +2 -2
- package/lib/embed/notifications.mjs +2 -3
- package/lib/embed/recommendation-store.mjs +25 -22
- package/lib/embed/role-framing.mjs +4 -9
- package/lib/embedded-contract/capability.mjs +4 -2
- package/lib/embedded-contract/contract-version.mjs +1 -1
- package/lib/env-config.mjs +2 -2
- package/lib/flavors/loader.mjs +21 -19
- package/lib/graph/build-from-registry.mjs +31 -7
- package/lib/graph/staleness.mjs +1 -1
- package/lib/headhunt.mjs +82 -35
- package/lib/hooks/agent-tracker.mjs +2 -2
- package/lib/hooks/config-protection.mjs +1 -1
- package/lib/hooks/registry-sync.mjs +3 -3
- package/lib/host-capabilities.mjs +5 -5
- package/lib/improvement/controller.mjs +2 -2
- package/lib/init-unified.mjs +19 -21
- package/lib/intake/classify.mjs +26 -0
- package/lib/intake/daemon.mjs +2 -3
- package/lib/intake/prepare.mjs +10 -3
- package/lib/intake/session-prelude.mjs +1 -1
- package/lib/intake/tables/creative.mjs +9 -9
- package/lib/intake/tables/operations.mjs +4 -4
- package/lib/intake/tables/research.mjs +2 -2
- package/lib/integrations/intake-integrations.mjs +9 -10
- package/lib/knowledge/research-store.mjs +2 -2
- package/lib/mcp/server.mjs +104 -61
- package/lib/mcp/tool-budget.mjs +1 -46
- package/lib/mcp/tool-recovery.mjs +44 -0
- package/lib/mcp/tools/artifact-author.mjs +36 -0
- package/lib/mcp/tools/find-tool.mjs +86 -0
- package/lib/mcp/tools/orchestration-run.mjs +107 -69
- package/lib/mcp/tools/project.mjs +4 -3
- package/lib/mcp/tools/{profile.mjs → scope.mjs} +54 -52
- package/lib/mcp/tools/skills.mjs +39 -22
- package/lib/mcp/tools/telemetry.mjs +2 -1
- package/lib/mcp/tools/workflow.mjs +1 -1
- package/lib/mcp-platform-config.mjs +7 -5
- package/lib/migrations/index.mjs +4 -2
- package/lib/migrations/v2-unified-registry.mjs +35 -0
- package/lib/model-router.mjs +203 -36
- package/lib/models/catalog.mjs +25 -9
- package/lib/models/execution-capability-profile.mjs +3 -3
- package/lib/models/execution-policy.mjs +7 -6
- package/lib/models/provider-poll.mjs +18 -4
- package/lib/opencode-config.mjs +56 -1
- package/lib/opencode-runtime-plugin.mjs +13 -10
- package/lib/oracle/artifact-gate.mjs +14 -4
- package/lib/oracle/cli.mjs +2 -0
- package/lib/oracle/dispatch.mjs +18 -1
- package/lib/oracle/execute.mjs +39 -7
- package/lib/oracle/index.mjs +1 -1
- package/lib/oracle/org-graph.mjs +10 -5
- package/lib/oracle/policy.mjs +6 -0
- package/lib/oracle/read-model.mjs +103 -6
- package/lib/oracle/reconcile.mjs +31 -4
- package/lib/oracle/remediation-dispatch.mjs +53 -0
- package/lib/oracle/routing.mjs +5 -0
- package/lib/oracle/synthesize.mjs +80 -3
- package/lib/orchestration/routing-tables.mjs +44 -10
- package/lib/orchestration/runtime.mjs +2 -0
- package/lib/orchestration/worker.mjs +1 -1
- package/lib/orchestration-policy.mjs +351 -62
- package/lib/overrides/resolver.mjs +1 -12
- package/lib/parity.mjs +37 -11
- package/lib/policy/engine.mjs +80 -15
- package/lib/prompt-composer.js +51 -15
- package/lib/prompt-metadata.mjs +3 -2
- package/lib/providers/connection-probe.mjs +58 -0
- package/lib/providers/copilot-auth.mjs +1 -1
- package/lib/providers/credential-bootstrap.mjs +1 -1
- package/lib/providers/op-run.mjs +3 -4
- package/lib/providers/secret-resolver.mjs +31 -0
- package/lib/publish-tooling.mjs +3 -11
- package/lib/publish.mjs +12 -29
- package/lib/reconcile/mcp-entry-reconcile.mjs +14 -9
- package/lib/reflect.mjs +2 -2
- package/lib/registry/agent-manifest.mjs +190 -0
- package/lib/registry/assemble.mjs +133 -0
- package/lib/registry/catalog.mjs +171 -0
- package/lib/registry/cli.mjs +590 -6
- package/lib/registry/consolidation.mjs +5 -4
- package/lib/registry/docs-sync.mjs +67 -0
- package/lib/registry/loader.mjs +147 -0
- package/lib/registry/org-io.mjs +76 -0
- package/lib/registry/retired-paths.mjs +71 -0
- package/lib/registry/surface-map.mjs +5 -7
- package/lib/registry/validator.mjs +438 -0
- package/lib/research-execution-policy.mjs +173 -0
- package/lib/roles/catalog.mjs +4 -9
- package/lib/roles/fence.mjs +107 -1
- package/lib/roles/flavor-bindings.mjs +68 -0
- package/lib/roles/gateway.mjs +140 -29
- package/lib/roles/manifest.mjs +22 -13
- package/lib/roles/router.mjs +1 -1
- package/lib/runtime-env.mjs +1 -1
- package/lib/scopes/enrich.mjs +67 -0
- package/lib/scopes/hooks.mjs +12 -0
- package/lib/{profiles → scopes}/lifecycle.mjs +70 -70
- package/lib/scopes/loader.mjs +104 -0
- package/lib/scopes/rebrand.mjs +32 -0
- package/lib/scopes/research-profile.mjs +16 -0
- package/lib/scopes/teams.mjs +98 -0
- package/lib/service-manager.mjs +1 -117
- package/lib/setup-prompts.mjs +1 -1
- package/lib/setup.mjs +17 -28
- package/lib/skills/composition-graph.mjs +98 -0
- package/lib/skills/router.mjs +80 -0
- package/lib/specialist-contracts.mjs +17 -18
- package/lib/specialists/postconditions.mjs +2 -2
- package/lib/specialists/prompt-schema.mjs +6 -5
- package/lib/specialists/roster.mjs +7 -12
- package/lib/specialists/schema.mjs +9 -6
- package/lib/status.mjs +25 -90
- package/lib/storage/file-lock.mjs +6 -3
- package/lib/telemetry/skill-calls.mjs +1 -1
- package/lib/templates/doc-presentation.mjs +63 -0
- package/lib/templates/visual-requirements.mjs +35 -2
- package/lib/term-format.mjs +107 -2
- package/lib/test-env-setup.mjs +21 -0
- package/lib/ui/components.mjs +42 -0
- package/lib/ui/glyphs.mjs +58 -0
- package/lib/ui/links.mjs +115 -0
- package/lib/ui/theme.mjs +108 -0
- package/lib/uninstall/uninstall.mjs +2 -1
- package/lib/validator.mjs +45 -8
- package/lib/validators/skill-effectiveness.mjs +174 -0
- package/lib/validators/skills.mjs +1 -1
- package/package.json +12 -7
- package/personas/construct.md +12 -3
- package/platforms/claude/CLAUDE.md +1 -1
- package/rules/common/beads-hygiene.md +52 -0
- package/rules/common/neurodivergent-output.md +4 -7
- package/rules/common/research.md +2 -0
- package/scripts/sync-specialists.mjs +260 -49
- package/skills/ai/prompt-optimizer.md +3 -3
- package/skills/brand/output-vibe.md +48 -0
- package/skills/docs/adr-workflow.md +3 -0
- package/skills/docs/backlog-proposal-workflow.md +3 -0
- package/skills/docs/codebase-research-workflow.md +5 -2
- package/skills/docs/customer-profile-workflow.md +3 -0
- package/skills/docs/document-ingest-workflow.md +3 -0
- package/skills/docs/evidence-ingest-workflow.md +4 -1
- package/skills/docs/init-project.md +1 -1
- package/skills/docs/prd-workflow.md +1 -1
- package/skills/docs/prfaq-workflow.md +3 -0
- package/skills/docs/product-intelligence-workflow.md +4 -1
- package/skills/docs/product-signal-workflow.md +3 -0
- package/skills/docs/research-workflow.md +13 -6
- package/skills/docs/runbook-workflow.md +3 -0
- package/skills/docs/strategy-workflow.md +3 -0
- package/skills/docs/user-research-workflow.md +6 -3
- package/skills/operating/orchestration-reference.md +1 -1
- package/skills/roles/{engineer.ai.md → ai-engineer.md} +2 -2
- package/skills/roles/architect.ai-systems.md +1 -1
- package/skills/roles/architect.data.md +1 -1
- package/skills/roles/architect.enterprise.md +1 -1
- package/skills/roles/architect.integration.md +1 -1
- package/skills/roles/architect.md +1 -1
- package/skills/roles/architect.platform.md +1 -1
- package/skills/roles/{product-manager.business-strategy.md → business-strategist.md} +3 -3
- package/skills/roles/data-analyst.experiment.md +1 -1
- package/skills/roles/data-analyst.md +1 -1
- package/skills/roles/data-analyst.product-intelligence.md +1 -1
- package/skills/roles/data-analyst.product.md +1 -1
- package/skills/roles/data-analyst.telemetry.md +1 -1
- package/skills/roles/{engineer.data.md → data-engineer.md} +2 -2
- package/skills/roles/data-engineer.pipeline.md +2 -2
- package/skills/roles/data-engineer.vector-retrieval.md +2 -2
- package/skills/roles/data-engineer.warehouse.md +2 -2
- package/skills/roles/debugger.md +1 -1
- package/skills/roles/designer.accessibility.md +1 -1
- package/skills/roles/designer.md +1 -1
- package/skills/roles/{reviewer.devil-advocate.md → devil-advocate.md} +2 -2
- package/skills/roles/{operator.docs.md → docs-keeper.md} +3 -3
- package/skills/roles/engineer.md +3 -7
- package/skills/roles/{reviewer.evaluator.md → evaluator.md} +2 -2
- package/skills/roles/{researcher.explorer.md → explorer.md} +2 -2
- package/skills/roles/{operator.md → operations.md} +2 -5
- package/skills/roles/orchestrator.md +1 -1
- package/skills/roles/{engineer.platform.md → platform-engineer.md} +2 -2
- package/skills/roles/product-manager.ai-product.md +1 -1
- package/skills/roles/product-manager.enterprise.md +1 -1
- package/skills/roles/product-manager.growth.md +1 -2
- package/skills/roles/product-manager.md +1 -2
- package/skills/roles/product-manager.platform.md +1 -1
- package/skills/roles/product-manager.product.md +1 -1
- package/skills/roles/qa.ai-eval.md +1 -1
- package/skills/roles/qa.api-contract.md +1 -1
- package/skills/roles/qa.data-pipeline.md +1 -1
- package/skills/roles/qa.md +1 -1
- package/skills/roles/qa.web-ui.md +1 -1
- package/skills/roles/{operator.release.md → release-manager.md} +3 -3
- package/skills/roles/researcher.md +1 -1
- package/skills/roles/reviewer.md +1 -1
- package/skills/roles/security.ai.md +1 -1
- package/skills/roles/security.appsec.md +1 -1
- package/skills/roles/security.cloud.md +1 -1
- package/skills/roles/security.legal-compliance.md +1 -1
- package/skills/roles/security.md +1 -1
- package/skills/roles/security.privacy.md +1 -1
- package/skills/roles/security.supply-chain.md +1 -1
- package/skills/roles/{operator.sre.md → sre.md} +3 -3
- package/skills/roles/{qa.test-automation.md → test-automation.md} +2 -2
- package/skills/roles/{reviewer.trace.md → trace-reviewer.md} +2 -2
- package/skills/roles/{researcher.ux.md → ux-researcher.md} +2 -2
- package/skills/routing.json +18 -0
- package/skills/routing.md +1 -1
- package/specialists/artifact-manifest.json +2 -1
- package/specialists/audit-enrichments.json +14 -14
- package/specialists/org/contracts/accessibility-to-qa.json +37 -0
- package/specialists/org/contracts/any-to-business-strategist.json +57 -0
- package/specialists/org/contracts/any-to-debugger.json +38 -0
- package/specialists/org/contracts/any-to-designer.json +33 -0
- package/specialists/org/contracts/any-to-docs-keeper.json +34 -0
- package/specialists/org/contracts/any-to-explorer.json +39 -0
- package/specialists/org/contracts/any-to-sre-incident.json +33 -0
- package/specialists/org/contracts/any-to-trace-reviewer.json +32 -0
- package/specialists/org/contracts/architect-to-ai-engineer.json +32 -0
- package/specialists/org/contracts/architect-to-data-engineer.json +52 -0
- package/specialists/org/contracts/architect-to-devil-advocate.json +38 -0
- package/specialists/org/contracts/architect-to-engineer.json +34 -0
- package/specialists/org/contracts/architect-to-evaluator.json +59 -0
- package/specialists/org/contracts/architect-to-legal-compliance.json +55 -0
- package/specialists/org/contracts/architect-to-operations.json +45 -0
- package/specialists/org/contracts/architect-to-platform-engineer.json +42 -0
- package/specialists/org/contracts/business-strategist-to-product-manager.json +39 -0
- package/specialists/org/contracts/construct-to-orchestrator.json +36 -0
- package/specialists/org/contracts/construct-to-rd-lead.json +53 -0
- package/specialists/org/contracts/data-analyst-to-product-manager.json +43 -0
- package/specialists/org/contracts/data-engineer-to-platform-engineer.json +36 -0
- package/specialists/org/contracts/designer-to-accessibility.json +36 -0
- package/specialists/org/contracts/engineer-to-qa.json +34 -0
- package/specialists/org/contracts/engineer-to-reviewer.json +52 -0
- package/specialists/org/contracts/explorer-to-engineer.json +38 -0
- package/specialists/org/contracts/legal-compliance-to-release-manager.json +43 -0
- package/specialists/org/contracts/platform-engineer-to-engineer.json +31 -0
- package/specialists/org/contracts/product-manager-to-architect.json +71 -0
- package/specialists/org/contracts/product-manager-to-data-analyst.json +49 -0
- package/specialists/org/contracts/product-manager-to-ux-researcher.json +51 -0
- package/specialists/org/contracts/qa-to-release-manager.json +42 -0
- package/specialists/org/contracts/qa-to-test-automation.json +42 -0
- package/specialists/org/contracts/rd-lead-to-architect.json +38 -0
- package/specialists/org/contracts/researcher-to-architect.json +40 -0
- package/specialists/org/contracts/researcher-to-product-manager.json +57 -0
- package/specialists/org/contracts/reviewer-to-security.json +41 -0
- package/specialists/org/contracts/sre-to-release-manager.json +36 -0
- package/specialists/org/contracts/test-automation-to-engineer.json +34 -0
- package/specialists/org/contracts/trace-reviewer-to-sre.json +41 -0
- package/specialists/org/contracts/user-to-construct.json +30 -0
- package/specialists/org/groups/engineering-group.json +43 -0
- package/specialists/org/groups/governance-group.json +38 -0
- package/specialists/org/groups/operations-group.json +41 -0
- package/specialists/org/groups/product-group.json +46 -0
- package/specialists/org/groups/quality-group.json +42 -0
- package/specialists/org/groups/strategy-group.json +41 -0
- package/specialists/org/policies/action-approval.json +13 -0
- package/specialists/org/policies/agents-routing.json +13 -0
- package/specialists/org/policies/architecture.json +20 -0
- package/specialists/org/policies/bash-safety.json +13 -0
- package/specialists/org/policies/beads-hygiene.json +13 -0
- package/specialists/org/policies/bootstrap-state.json +13 -0
- package/specialists/org/policies/code-review.json +13 -0
- package/specialists/org/policies/coding-style.json +13 -0
- package/specialists/org/policies/comments.json +13 -0
- package/specialists/org/policies/commit-approval.json +13 -0
- package/specialists/org/policies/contract-preconditions.json +13 -0
- package/specialists/org/policies/deployment.json +20 -0
- package/specialists/org/policies/description.json +14 -0
- package/specialists/org/policies/design-approval.json +19 -0
- package/specialists/org/policies/doc-ownership.json +13 -0
- package/specialists/org/policies/file-path-fence.json +13 -0
- package/specialists/org/policies/framing.json +13 -0
- package/specialists/org/policies/git-workflow.json +13 -0
- package/specialists/org/policies/incident-response.json +15 -0
- package/specialists/org/policies/intake-triage.json +15 -0
- package/specialists/org/policies/neurodivergent-output.json +13 -0
- package/specialists/org/policies/no-fabrication.json +13 -0
- package/specialists/org/policies/patterns.json +13 -0
- package/specialists/org/policies/quality-gate-approval.json +17 -0
- package/specialists/org/policies/release-gates.json +13 -0
- package/specialists/org/policies/research-evidence.json +13 -0
- package/specialists/org/policies/review-before-change.json +13 -0
- package/specialists/org/policies/rollback.json +15 -0
- package/specialists/org/policies/scope-change.json +20 -0
- package/specialists/org/policies/secret-scan.json +13 -0
- package/specialists/org/policies/security-approval.json +17 -0
- package/specialists/org/policies/security.json +13 -0
- package/specialists/org/policies/session-efficiency.json +13 -0
- package/specialists/org/policies/skill-routing.json +13 -0
- package/specialists/org/policies/strategic-prioritization.json +17 -0
- package/specialists/org/policies/testing.json +13 -0
- package/specialists/org/policies/tool-invisibility.json +14 -0
- package/specialists/org/scopes/creative.json +54 -0
- package/specialists/org/scopes/operations.json +51 -0
- package/specialists/org/scopes/research.json +60 -0
- package/specialists/org/scopes/rnd.json +81 -0
- package/specialists/org/specialists/cx-accessibility.json +69 -0
- package/specialists/org/specialists/cx-ai-engineer.json +75 -0
- package/specialists/org/specialists/cx-architect.json +89 -0
- package/specialists/org/specialists/cx-business-strategist.json +72 -0
- package/specialists/org/specialists/cx-data-analyst.json +68 -0
- package/specialists/org/specialists/cx-data-engineer.json +71 -0
- package/specialists/org/specialists/cx-debugger.json +75 -0
- package/specialists/org/specialists/cx-designer.json +85 -0
- package/specialists/org/specialists/cx-devil-advocate.json +71 -0
- package/specialists/org/specialists/cx-docs-keeper.json +82 -0
- package/specialists/org/specialists/cx-engineer.json +106 -0
- package/specialists/org/specialists/cx-evaluator.json +69 -0
- package/specialists/org/specialists/cx-explorer.json +69 -0
- package/specialists/org/specialists/cx-legal-compliance.json +74 -0
- package/specialists/org/specialists/cx-operations.json +72 -0
- package/specialists/org/specialists/cx-oracle.json +46 -0
- package/specialists/org/specialists/cx-orchestrator.json +81 -0
- package/specialists/org/specialists/cx-platform-engineer.json +80 -0
- package/specialists/org/specialists/cx-product-manager.json +102 -0
- package/specialists/org/specialists/cx-qa.json +79 -0
- package/specialists/org/specialists/cx-rd-lead.json +73 -0
- package/specialists/org/specialists/cx-release-manager.json +77 -0
- package/specialists/org/specialists/cx-researcher.json +82 -0
- package/specialists/org/specialists/cx-reviewer.json +71 -0
- package/specialists/org/specialists/cx-security.json +91 -0
- package/specialists/org/specialists/cx-sre.json +94 -0
- package/specialists/org/specialists/cx-test-automation.json +69 -0
- package/specialists/org/specialists/cx-trace-reviewer.json +69 -0
- package/specialists/org/specialists/cx-ux-researcher.json +68 -0
- package/specialists/org/teams/accessibility-team.json +39 -0
- package/specialists/org/teams/design-team.json +40 -0
- package/specialists/org/teams/engineering-team.json +50 -0
- package/specialists/org/teams/governance-team.json +41 -0
- package/specialists/org/teams/operations-team.json +46 -0
- package/specialists/org/teams/product-management-team.json +45 -0
- package/specialists/org/teams/quality-team.json +49 -0
- package/specialists/org/teams/research-team.json +39 -0
- package/specialists/org/teams/strategy-team.json +48 -0
- package/specialists/org/teams/ux-research-team.json +39 -0
- package/specialists/prompts/_shared/validation-contract.md +1 -1
- package/specialists/prompts/_team-template.md +10 -0
- package/specialists/prompts/cx-accessibility.md +1 -0
- package/specialists/prompts/cx-ai-engineer.md +1 -1
- package/specialists/prompts/cx-business-strategist.md +1 -1
- package/specialists/prompts/cx-data-engineer.md +1 -1
- package/specialists/prompts/cx-designer.md +1 -0
- package/specialists/prompts/cx-devil-advocate.md +1 -1
- package/specialists/prompts/cx-docs-keeper.md +1 -1
- package/specialists/prompts/cx-evaluator.md +1 -1
- package/specialists/prompts/cx-explorer.md +1 -1
- package/specialists/prompts/cx-operations.md +1 -1
- package/specialists/prompts/cx-oracle.md +7 -3
- package/specialists/prompts/cx-orchestrator.md +17 -1
- package/specialists/prompts/cx-platform-engineer.md +1 -1
- package/specialists/prompts/cx-product-manager.md +2 -0
- package/specialists/prompts/cx-release-manager.md +1 -1
- package/specialists/prompts/cx-researcher.md +7 -4
- package/specialists/prompts/cx-sre.md +1 -1
- package/specialists/prompts/cx-test-automation.md +2 -2
- package/specialists/prompts/cx-trace-reviewer.md +3 -3
- package/specialists/prompts/cx-ux-researcher.md +2 -1
- package/templates/demos/scripts/architecture-review-adr.json +30 -0
- package/templates/demos/scripts/capability-contract.json +25 -0
- package/templates/demos/scripts/intake-triage.json +25 -0
- package/templates/demos/scripts/profile-doctor-health.json +25 -0
- package/templates/demos/tapes/agentic-platforms-prd.tape +2 -2
- package/templates/demos/tapes/architecture-review-adr.tape +44 -0
- package/templates/demos/tapes/capability-contract.tape +39 -0
- package/templates/demos/tapes/intake-triage.tape +39 -0
- package/templates/demos/tapes/profile-doctor-health.tape +39 -0
- package/templates/distribution/construct-brand.typ +23 -18
- package/templates/distribution/construct-decision.typ +1 -1
- package/templates/distribution/construct-pdf.typ +1 -1
- package/templates/distribution/construct-prd.typ +1 -1
- package/templates/distribution/construct-research.typ +1 -1
- package/templates/distribution/mermaid-puppeteer.json +8 -0
- package/templates/docs/persona-artifact.md +1 -1
- package/templates/docs/prd.md +6 -0
- package/lib/boundary.mjs +0 -127
- package/lib/certification/dashboard-api.mjs +0 -71
- package/lib/chat/cli.mjs +0 -333
- package/lib/chat/command-suggest.mjs +0 -161
- package/lib/chat/commands.mjs +0 -215
- package/lib/chat/config.mjs +0 -142
- package/lib/chat/context-compactor.mjs +0 -250
- package/lib/chat/context-continuation.mjs +0 -253
- package/lib/chat/continuation-source.mjs +0 -58
- package/lib/chat/demo-guide.mjs +0 -61
- package/lib/chat/design-tokens.mjs +0 -91
- package/lib/chat/desktop-binary.mjs +0 -81
- package/lib/chat/desktop-build.mjs +0 -130
- package/lib/chat/desktop-launcher.mjs +0 -133
- package/lib/chat/evidence.mjs +0 -145
- package/lib/chat/export.mjs +0 -74
- package/lib/chat/harness/driver.mjs +0 -91
- package/lib/chat/list-picker.mjs +0 -112
- package/lib/chat/model-picker.mjs +0 -356
- package/lib/chat/openrouter-fallback.mjs +0 -151
- package/lib/chat/permission-prompt.mjs +0 -33
- package/lib/chat/picker-catalog.mjs +0 -45
- package/lib/chat/policy-telemetry.mjs +0 -34
- package/lib/chat/present.mjs +0 -246
- package/lib/chat/session-context.mjs +0 -39
- package/lib/chat/session-persist.mjs +0 -73
- package/lib/chat/session-restore.mjs +0 -71
- package/lib/chat/session-settings.mjs +0 -53
- package/lib/chat/system-prompt.mjs +0 -52
- package/lib/chat/transparency.mjs +0 -93
- package/lib/chat/tui/color-scheme.mjs +0 -42
- package/lib/chat/tui/markdown.mjs +0 -123
- package/lib/chat/tui/presentation.mjs +0 -100
- package/lib/chat/tui/render.mjs +0 -500
- package/lib/chat/tui/turn-block.mjs +0 -284
- package/lib/chat/tui/turn-present.mjs +0 -18
- package/lib/chat/tui/turn-state.mjs +0 -88
- package/lib/chat/tui/usage.mjs +0 -122
- package/lib/chat/web-commands.mjs +0 -146
- package/lib/chat/web-launcher.mjs +0 -63
- package/lib/chat/web-picker-keys.mjs +0 -46
- package/lib/chat/web-session.mjs +0 -159
- package/lib/config/alias.mjs +0 -57
- package/lib/dashboard-demo.mjs +0 -71
- package/lib/dashboard-static.mjs +0 -175
- package/lib/install/desktop-binary-download.mjs +0 -88
- package/lib/profiles/loader.mjs +0 -123
- package/lib/profiles/rebrand.mjs +0 -46
- package/lib/server/auth.mjs +0 -169
- package/lib/server/chat-loop.mjs +0 -622
- package/lib/server/chat.mjs +0 -336
- package/lib/server/cors.mjs +0 -77
- package/lib/server/csrf.mjs +0 -103
- package/lib/server/demo-preview.mjs +0 -63
- package/lib/server/index.mjs +0 -2641
- package/lib/server/insights.mjs +0 -780
- package/lib/server/langfuse-login.mjs +0 -58
- package/lib/server/rate-limit.mjs +0 -93
- package/lib/server/telemetry-login.mjs +0 -100
- package/lib/server/webhook.mjs +0 -512
- package/specialists/contracts.json +0 -1032
- package/specialists/contracts.schema.json +0 -83
- package/specialists/policy-inventory.json +0 -188
- package/specialists/registry.json +0 -1412
- package/specialists/role-manifests.json +0 -217
- package/specialists/teams.json +0 -94
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* record (construct-6zga.1.8).
|
|
4
4
|
*
|
|
5
5
|
* Consolidates the four formerly-scattered capability producers into one
|
|
6
|
-
* serializable, versioned record that
|
|
6
|
+
* serializable, versioned record that model routing (resolveCapabilityTier) and specialist
|
|
7
7
|
* prompt composition (resolveModelOperatingProfile via lib/prompt-composer.js) both
|
|
8
8
|
* consume. It is keyed by provider, requested/resolved model, adapter protocol, and
|
|
9
9
|
* observation time. Every capability declares an evidence source
|
|
@@ -57,8 +57,8 @@ export function providerGroupForModel(model) {
|
|
|
57
57
|
return seg || 'unknown';
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
// Adapter protocol mirrors
|
|
61
|
-
// Anthropic uses its native messages API, OpenAI and Copilot use
|
|
60
|
+
// Adapter protocol mirrors provider worker dispatch:
|
|
61
|
+
// Anthropic uses its native messages API, OpenAI and Copilot use completions,
|
|
62
62
|
// and every other provider goes through the OpenAI-compatible shim.
|
|
63
63
|
|
|
64
64
|
function adapterProtocolForGroup(group) {
|
|
@@ -81,11 +81,10 @@ function oneOf(allowed, value, fallback) {
|
|
|
81
81
|
return allowed.includes(value) ? value : fallback;
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
//
|
|
85
|
-
//
|
|
84
|
+
// Routing overlays speak the orchestration vocabulary (INTENT_CLASSES, risk flags,
|
|
85
|
+
// externalResearch). These helpers map that vocabulary
|
|
86
86
|
// onto the compiler's stable input enums so the compiler stays decoupled from
|
|
87
|
-
// routing changes. A turn that follows a failed tool result is the recovery case
|
|
88
|
-
// the loop signals with toolFailure.
|
|
87
|
+
// routing changes. A turn that follows a failed tool result is the recovery case.
|
|
89
88
|
|
|
90
89
|
export function normalizePolicyIntent({ intent = null, assumptionsBlocked = false, toolFailure = false } = {}) {
|
|
91
90
|
if (toolFailure === true) return 'tool-failure';
|
|
@@ -240,10 +239,10 @@ export function compileExecutionPolicy({
|
|
|
240
239
|
}
|
|
241
240
|
|
|
242
241
|
/**
|
|
243
|
-
* Compile a policy directly from a
|
|
242
|
+
* Compile a policy directly from a routing overlay,
|
|
244
243
|
* normalizing its routing vocabulary into the compiler's input enums first.
|
|
245
244
|
*/
|
|
246
|
-
export function
|
|
245
|
+
export function compilePolicyFromRoutingOverlay({ profile = null, overlay = null, toolFailure = false } = {}) {
|
|
247
246
|
const intent = normalizePolicyIntent({
|
|
248
247
|
intent: overlay?.intent,
|
|
249
248
|
assumptionsBlocked: overlay?.assumptionsBlocked === true,
|
|
@@ -257,6 +256,8 @@ export function compilePolicyFromOverlay({ profile = null, overlay = null, toolF
|
|
|
257
256
|
return compileExecutionPolicy({ profile, intent, risk, evidenceRequirement });
|
|
258
257
|
}
|
|
259
258
|
|
|
259
|
+
export const compilePolicyFromOverlay = compilePolicyFromRoutingOverlay;
|
|
260
|
+
|
|
260
261
|
/**
|
|
261
262
|
* Hand-rolled validator (no ajv — Construct stays dependency-free at startup).
|
|
262
263
|
* Returns { valid, errors } against schemas/execution-policy.schema.json.
|
|
@@ -71,8 +71,16 @@ function openRouterCapabilities(model) {
|
|
|
71
71
|
};
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
+
function resolveSecretOrNull(varNames, opts) {
|
|
75
|
+
try {
|
|
76
|
+
return resolveFirstSecret(varNames, opts);
|
|
77
|
+
} catch {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
74
82
|
export async function pollOpenRouter({ env = process.env } = {}) {
|
|
75
|
-
const apiKey =
|
|
83
|
+
const apiKey = resolveSecretOrNull(['OPENROUTER_API_KEY', 'OPEN_ROUTER_API_KEY'], { env, allowAmbient: env === process.env });
|
|
76
84
|
const headers = apiKey ? { Authorization: `Bearer ${apiKey}` } : {};
|
|
77
85
|
const data = await fetchJson('https://openrouter.ai/api/v1/models', { headers });
|
|
78
86
|
if (!data) return null;
|
|
@@ -80,10 +88,15 @@ export async function pollOpenRouter({ env = process.env } = {}) {
|
|
|
80
88
|
const models = [];
|
|
81
89
|
for (const m of list) {
|
|
82
90
|
if (!m?.id) continue;
|
|
91
|
+
const outputs = m.architecture?.output_modalities;
|
|
92
|
+
if (Array.isArray(outputs) && outputs.length && !outputs.includes('text')) continue;
|
|
93
|
+
if (/(embed|rerank|whisper|tts|dall-e|moderation|transcribe|sora)/i.test(m.id)) continue;
|
|
83
94
|
const free = isFreePricing(m.pricing);
|
|
84
95
|
const input = Number(m.pricing?.prompt);
|
|
85
96
|
const output = Number(m.pricing?.completion);
|
|
86
97
|
const caps = openRouterCapabilities(m);
|
|
98
|
+
const context = Number(m.context_length) || null;
|
|
99
|
+
if (!free && context && context < 8000) continue;
|
|
87
100
|
models.push({
|
|
88
101
|
id: `openrouter/${m.id}`,
|
|
89
102
|
label: m.name || m.id,
|
|
@@ -92,11 +105,12 @@ export async function pollOpenRouter({ env = process.env } = {}) {
|
|
|
92
105
|
pricing: Number.isFinite(input) && Number.isFinite(output)
|
|
93
106
|
? { input: input * 1_000_000, output: output * 1_000_000 }
|
|
94
107
|
: null,
|
|
95
|
-
context
|
|
108
|
+
context,
|
|
96
109
|
reasoning: caps.reasoning,
|
|
97
110
|
tools: caps.tools,
|
|
98
111
|
toolsKnown: true,
|
|
99
112
|
vision: caps.vision,
|
|
113
|
+
architecture: m.architecture || null,
|
|
100
114
|
source: 'live',
|
|
101
115
|
});
|
|
102
116
|
}
|
|
@@ -110,7 +124,7 @@ export async function pollOpenRouter({ env = process.env } = {}) {
|
|
|
110
124
|
const OPENAI_NON_CHAT = /(embedding|whisper|tts|audio|realtime|image|dall-e|moderation|search|babbage|davinci|transcribe|sora)/i;
|
|
111
125
|
|
|
112
126
|
export async function pollOpenAI({ env = process.env } = {}) {
|
|
113
|
-
const apiKey =
|
|
127
|
+
const apiKey = resolveSecretOrNull(['OPENAI_API_KEY'], { env, allowAmbient: env === process.env });
|
|
114
128
|
if (!apiKey) return null;
|
|
115
129
|
const data = await fetchJson('https://api.openai.com/v1/models', {
|
|
116
130
|
headers: { Authorization: `Bearer ${apiKey}` },
|
|
@@ -141,7 +155,7 @@ export async function pollOpenAI({ env = process.env } = {}) {
|
|
|
141
155
|
}
|
|
142
156
|
|
|
143
157
|
export async function pollAnthropic({ env = process.env } = {}) {
|
|
144
|
-
const apiKey =
|
|
158
|
+
const apiKey = resolveSecretOrNull(['ANTHROPIC_API_KEY'], { env, allowAmbient: env === process.env });
|
|
145
159
|
if (!apiKey) return null;
|
|
146
160
|
const data = await fetchJson('https://api.anthropic.com/v1/models?limit=100', {
|
|
147
161
|
headers: { 'x-api-key': apiKey, 'anthropic-version': '2023-06-01' },
|
package/lib/opencode-config.mjs
CHANGED
|
@@ -48,6 +48,43 @@ export function sanitizeOpenCodeConfig(config) {
|
|
|
48
48
|
return sanitized;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
// op run resolves only the launch env-file, never values baked into config
|
|
52
|
+
// JSON. A literal `op://` reference (or a build-time __PLACEHOLDER__) left in a
|
|
53
|
+
// credential field is therefore sent to OpenRouter verbatim, which 401s. Only
|
|
54
|
+
// `{env:VAR}` (resolved by OpenCode) and concrete keys count as real auth.
|
|
55
|
+
|
|
56
|
+
function isUnresolvedSecretRef(value) {
|
|
57
|
+
return typeof value === 'string' && (value.includes('op://') || value.includes('__OPENROUTER_API_KEY__'));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function openRouterHasAuth(provider = {}) {
|
|
61
|
+
const apiKey = provider.apiKey;
|
|
62
|
+
if (typeof apiKey === 'string' && apiKey.length > 0 && !isUnresolvedSecretRef(apiKey)) {
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
const auth = provider.options?.headers?.Authorization;
|
|
66
|
+
return typeof auth === 'string' && auth.length > 0 && !isUnresolvedSecretRef(auth);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function ensureOpenRouterProviderAuth(config) {
|
|
70
|
+
if (!config?.provider?.openrouter) return config;
|
|
71
|
+
const provider = config.provider.openrouter;
|
|
72
|
+
|
|
73
|
+
// A broken Authorization header overrides the working apiKey at request time,
|
|
74
|
+
// so strip it before deciding whether the provider still needs an env-ref key.
|
|
75
|
+
|
|
76
|
+
const authHeader = provider.options?.headers?.Authorization;
|
|
77
|
+
if (isUnresolvedSecretRef(authHeader)) {
|
|
78
|
+
delete provider.options.headers.Authorization;
|
|
79
|
+
}
|
|
80
|
+
if (openRouterHasAuth(provider)) return config;
|
|
81
|
+
config.provider.openrouter = {
|
|
82
|
+
...provider,
|
|
83
|
+
apiKey: '{env:OPENROUTER_API_KEY}',
|
|
84
|
+
};
|
|
85
|
+
return config;
|
|
86
|
+
}
|
|
87
|
+
|
|
51
88
|
/**
|
|
52
89
|
* Maps Construct internal tiers (fast, standard, reasoning) to OpenCode model configurations.
|
|
53
90
|
*/
|
|
@@ -124,7 +161,7 @@ export function writeOpenCodeConfig(config, file = findOpenCodeConfigPath()) {
|
|
|
124
161
|
if (config.models.some(m => m.provider === 'openrouter')) {
|
|
125
162
|
config.provider.openrouter = config.provider.openrouter || {
|
|
126
163
|
baseUrl: "https://openrouter.ai/api/v1",
|
|
127
|
-
apiKey: "
|
|
164
|
+
apiKey: "{env:OPENROUTER_API_KEY}"
|
|
128
165
|
};
|
|
129
166
|
}
|
|
130
167
|
if (config.models.some(m => m.provider === 'ollama')) {
|
|
@@ -204,6 +241,24 @@ export function writeOpenCodeConfig(config, file = findOpenCodeConfigPath()) {
|
|
|
204
241
|
// The relevant models should only be defined under provider.<name>.models
|
|
205
242
|
delete config.models;
|
|
206
243
|
|
|
244
|
+
ensureOpenRouterProviderAuth(config);
|
|
245
|
+
|
|
246
|
+
// The plugin array is hand- and CLI-editable, so it accumulates junk: stale
|
|
247
|
+
// toolkit paths from moved checkouts and stray CLI tokens (e.g. a literal
|
|
248
|
+
// "list"). A non-existent path entry can break plugin loading outright. Keep
|
|
249
|
+
// existing file-path plugins and plausible package specifiers, deduped.
|
|
250
|
+
|
|
251
|
+
if (Array.isArray(config.plugin)) {
|
|
252
|
+
const seen = new Set();
|
|
253
|
+
config.plugin = config.plugin.filter((entry) => {
|
|
254
|
+
if (typeof entry !== "string" || !entry.trim() || seen.has(entry)) return false;
|
|
255
|
+
seen.add(entry);
|
|
256
|
+
const isPath = /^([./~]|[A-Za-z]:[\\/])/.test(entry) || /\.[cm]?js$/.test(entry);
|
|
257
|
+
if (isPath) return fs.existsSync(entry);
|
|
258
|
+
return entry.length > 2 && (/^@/.test(entry) || /[-/]/.test(entry));
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
|
|
207
262
|
fs.writeFileSync(target, `${JSON.stringify(sanitizeOpenCodeConfig(config), null, 2)}\n`, "utf8");
|
|
208
263
|
return target;
|
|
209
264
|
}
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* records each as a telemetry trace, and applies model fallback on rate-limit errors.
|
|
6
6
|
*/
|
|
7
7
|
import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
8
|
+
import { loadRegistry } from './registry/loader.mjs';
|
|
8
9
|
import { homedir } from "node:os";
|
|
9
10
|
import { dirname, join, resolve } from "node:path";
|
|
10
11
|
import { spawnSync } from "node:child_process";
|
|
@@ -416,9 +417,7 @@ function buildHostConstraints() {
|
|
|
416
417
|
|
|
417
418
|
function readRegistryModels(rootDir) {
|
|
418
419
|
try {
|
|
419
|
-
const
|
|
420
|
-
if (!existsSync(registryPath)) return {};
|
|
421
|
-
const registry = JSON.parse(readFileSync(registryPath, "utf8"));
|
|
420
|
+
const registry = loadRegistry({ rootDir, skipValidation: true });
|
|
422
421
|
return registry?.models ?? {};
|
|
423
422
|
} catch {
|
|
424
423
|
return {};
|
|
@@ -759,13 +758,17 @@ async function emitRuntimeTrace(event, { client, env = process.env }) {
|
|
|
759
758
|
await ingest.flush();
|
|
760
759
|
}
|
|
761
760
|
} catch {
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
761
|
+
try {
|
|
762
|
+
await client?.app?.log?.({
|
|
763
|
+
body: {
|
|
764
|
+
service: "construct",
|
|
765
|
+
level: "warn",
|
|
766
|
+
message: "OpenCode runtime telemetry failed before delivery.",
|
|
767
|
+
},
|
|
768
|
+
});
|
|
769
|
+
} catch {
|
|
770
|
+
// best effort: telemetry failure must not block fallback handling
|
|
771
|
+
}
|
|
769
772
|
}
|
|
770
773
|
}
|
|
771
774
|
|
|
@@ -49,6 +49,10 @@ function collectBypassArtifacts(projectDir) {
|
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
function collectReviewerGaps(projectDir) {
|
|
52
|
+
const agentLog = path.join(projectDir, '.cx', 'agent-log.jsonl');
|
|
53
|
+
if (!fs.existsSync(agentLog)) {
|
|
54
|
+
return { gaps: [], armed: false };
|
|
55
|
+
}
|
|
52
56
|
const roots = [
|
|
53
57
|
path.join(projectDir, 'docs', 'specs', 'prd'),
|
|
54
58
|
path.join(projectDir, 'docs', 'prd'),
|
|
@@ -70,12 +74,12 @@ function collectReviewerGaps(projectDir) {
|
|
|
70
74
|
}
|
|
71
75
|
}
|
|
72
76
|
}
|
|
73
|
-
return gaps.slice(0, 20);
|
|
77
|
+
return { gaps: gaps.slice(0, 20), armed: true };
|
|
74
78
|
}
|
|
75
79
|
|
|
76
80
|
export function collectArtifactGateSignals({ rootDir, projectDir }) {
|
|
77
81
|
const bypassed = collectBypassArtifacts(projectDir);
|
|
78
|
-
const reviewerGaps = collectReviewerGaps(projectDir);
|
|
82
|
+
const { gaps: reviewerGaps, armed: reviewerGateArmed } = collectReviewerGaps(projectDir);
|
|
79
83
|
|
|
80
84
|
let specialistAudit = { present: false, pass: true, crossCheckCount: 0 };
|
|
81
85
|
if (isConstructPackageRepo(projectDir)) {
|
|
@@ -87,8 +91,13 @@ export function collectArtifactGateSignals({ rootDir, projectDir }) {
|
|
|
87
91
|
crossCheckCount: audit.crossCheckIssues?.length ?? 0,
|
|
88
92
|
crossCheckIssues: (audit.crossCheckIssues ?? []).slice(0, 5),
|
|
89
93
|
};
|
|
90
|
-
} catch {
|
|
91
|
-
specialistAudit = {
|
|
94
|
+
} catch (err) {
|
|
95
|
+
specialistAudit = {
|
|
96
|
+
present: true,
|
|
97
|
+
pass: false,
|
|
98
|
+
crossCheckCount: 0,
|
|
99
|
+
error: err?.message ?? String(err),
|
|
100
|
+
};
|
|
92
101
|
}
|
|
93
102
|
}
|
|
94
103
|
|
|
@@ -97,6 +106,7 @@ export function collectArtifactGateSignals({ rootDir, projectDir }) {
|
|
|
97
106
|
bypassCount: bypassed.length,
|
|
98
107
|
reviewerGaps,
|
|
99
108
|
reviewerGapCount: reviewerGaps.length,
|
|
109
|
+
reviewerGateArmed,
|
|
100
110
|
specialistAudit,
|
|
101
111
|
};
|
|
102
112
|
}
|
package/lib/oracle/cli.mjs
CHANGED
|
@@ -21,6 +21,7 @@ import { runOracleTick, listPending, approvePending, defaultRootDir } from './ac
|
|
|
21
21
|
import { collectOracleGaps, formatOracleGapsReport } from './gaps.mjs';
|
|
22
22
|
import { reconcileOracleHygieneBeads } from './reconcile.mjs';
|
|
23
23
|
import { doctorRoot } from '../config/xdg.mjs';
|
|
24
|
+
import { writeLastTick } from './index.mjs';
|
|
24
25
|
|
|
25
26
|
const MODULE_FILE = fileURLToPath(import.meta.url);
|
|
26
27
|
|
|
@@ -87,6 +88,7 @@ async function cmdReview(args, { rootDir, projectDir, homeDir }) {
|
|
|
87
88
|
const dryRun = args.includes('--dry-run');
|
|
88
89
|
const json = args.includes('--json');
|
|
89
90
|
const result = await runOracleTick({ rootDir, projectDir, homeDir, dryRun });
|
|
91
|
+
if (!dryRun) writeLastTick(result.tick, homeDir);
|
|
90
92
|
if (json) {
|
|
91
93
|
process.stdout.write(JSON.stringify(result, null, 2) + '\n');
|
|
92
94
|
return 0;
|
package/lib/oracle/dispatch.mjs
CHANGED
|
@@ -9,6 +9,7 @@ import fs from 'node:fs';
|
|
|
9
9
|
import path from 'node:path';
|
|
10
10
|
|
|
11
11
|
import { routeGap, routeAction } from './routing.mjs';
|
|
12
|
+
import { resolveRemediationDispatch } from './remediation-dispatch.mjs';
|
|
12
13
|
|
|
13
14
|
export function routingDir(projectDir) {
|
|
14
15
|
return path.join(projectDir, '.cx', 'oracle', 'routing');
|
|
@@ -28,9 +29,16 @@ export function buildRoutingArtifact({ synthesis, readModel, tickId, route }) {
|
|
|
28
29
|
|
|
29
30
|
for (const gap of synthesis.gaps ?? []) {
|
|
30
31
|
const r = routeGap(gap);
|
|
32
|
+
const dispatch = gap.remediationRoute?.mode
|
|
33
|
+
? gap.remediationRoute
|
|
34
|
+
: resolveRemediationDispatch(gap);
|
|
31
35
|
lines.push(` [${gap.severity}] ${gap.id} — ${gap.detail}`);
|
|
32
36
|
lines.push(` signal: ${gap.signal ?? 'unknown'}`);
|
|
33
37
|
lines.push(` route: ${r.primary}${r.secondary ? ` (secondary: ${r.secondary})` : ''}`);
|
|
38
|
+
lines.push(` dispatch: ${dispatch.mode}${dispatch.teamRouting?.involvedTeams?.length ? ` (teams: ${dispatch.teamRouting.involvedTeams.join(', ')})` : ''}`);
|
|
39
|
+
if (dispatch.mode === 'swarm' && dispatch.specialists?.length) {
|
|
40
|
+
lines.push(` swarm: ${dispatch.specialists.join(', ')}`);
|
|
41
|
+
}
|
|
34
42
|
if (r.gateType) lines.push(` gate: ${r.gateType}`);
|
|
35
43
|
}
|
|
36
44
|
|
|
@@ -50,6 +58,15 @@ export function buildRoutingArtifact({ synthesis, readModel, tickId, route }) {
|
|
|
50
58
|
if (route?.primary) {
|
|
51
59
|
lines.push('', `DISPATCH TARGET: ${route.primary}`);
|
|
52
60
|
}
|
|
61
|
+
if (route?.mode) {
|
|
62
|
+
lines.push(`DISPATCH MODE: ${route.mode}`);
|
|
63
|
+
if (route.mode === 'swarm' && route.specialists?.length) {
|
|
64
|
+
lines.push(`SWARM SPECIALISTS: ${route.specialists.join(', ')}`);
|
|
65
|
+
}
|
|
66
|
+
if (route.teamRouting?.involvedTeams?.length) {
|
|
67
|
+
lines.push(`INVOLVED TEAMS: ${route.teamRouting.involvedTeams.join(', ')}`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
53
70
|
|
|
54
71
|
lines.push('', 'BLOCKED:', ' none (deterministic routing artifact — LLM dispatch is approve-gated)');
|
|
55
72
|
return lines.join('\n');
|
|
@@ -76,6 +93,6 @@ export function maybeWriteHighSeverityRouting({ projectDir, tick, synthesis, rea
|
|
|
76
93
|
if (!hasHigh) return null;
|
|
77
94
|
const tickId = tick.at ?? new Date().toISOString();
|
|
78
95
|
const primaryGap = synthesis.gaps.find((g) => g.severity === 'high');
|
|
79
|
-
const route = routeGap(primaryGap);
|
|
96
|
+
const route = primaryGap?.remediationRoute ?? routeGap(primaryGap);
|
|
80
97
|
return writeRoutingArtifact({ projectDir, tickId, synthesis, readModel, route });
|
|
81
98
|
}
|
package/lib/oracle/execute.mjs
CHANGED
|
@@ -16,6 +16,7 @@ import { recordAndMaybeInvoke } from '../roles/gateway.mjs';
|
|
|
16
16
|
import { validateCapabilityRegistry } from '../registry/validate.mjs';
|
|
17
17
|
import { writeRoutingArtifact } from './dispatch.mjs';
|
|
18
18
|
import { routeAction } from './routing.mjs';
|
|
19
|
+
import { resolveRemediationDispatch } from './remediation-dispatch.mjs';
|
|
19
20
|
import { doctorRoot } from '../config/xdg.mjs';
|
|
20
21
|
|
|
21
22
|
function rolePendingPath(homeDir = homedir()) {
|
|
@@ -30,14 +31,14 @@ function appendRolePending(entry, homeDir = homedir()) {
|
|
|
30
31
|
fs.appendFileSync(file, JSON.stringify(entry) + '\n', 'utf8');
|
|
31
32
|
}
|
|
32
33
|
|
|
33
|
-
async function dispatchSpecialist({ action, projectDir, homeDir, personaId, eventType }) {
|
|
34
|
+
async function dispatchSpecialist({ action, projectDir, homeDir, personaId, eventType, dispatch = null }) {
|
|
34
35
|
const route = routeAction(action.kind);
|
|
35
36
|
const artifactPath = writeRoutingArtifact({
|
|
36
37
|
projectDir,
|
|
37
38
|
tickId: action.id,
|
|
38
39
|
synthesis: { verdict: 'approved', gaps: [], recommendedActions: [action] },
|
|
39
40
|
readModel: null,
|
|
40
|
-
route,
|
|
41
|
+
route: dispatch ?? route,
|
|
41
42
|
});
|
|
42
43
|
|
|
43
44
|
const prevRolesRoot = process.env.CONSTRUCT_ROLES_ROOT;
|
|
@@ -53,6 +54,8 @@ async function dispatchSpecialist({ action, projectDir, homeDir, personaId, even
|
|
|
53
54
|
artifactPath,
|
|
54
55
|
gateType: route.gateType,
|
|
55
56
|
requiredApprover: route.primary,
|
|
57
|
+
dispatchMode: dispatch?.mode ?? 'static',
|
|
58
|
+
involvedTeams: dispatch?.teamRouting?.involvedTeams ?? [],
|
|
56
59
|
},
|
|
57
60
|
};
|
|
58
61
|
|
|
@@ -69,11 +72,12 @@ async function dispatchSpecialist({ action, projectDir, homeDir, personaId, even
|
|
|
69
72
|
personaId: personaId.replace(/^cx-/, ''),
|
|
70
73
|
cxId: personaId,
|
|
71
74
|
bdIssueId: gatewayResult?.bdIssueId ?? null,
|
|
72
|
-
fingerprint: `oracle-${action.id}`,
|
|
75
|
+
fingerprint: `oracle-${action.id}-${personaId}`,
|
|
73
76
|
eventType,
|
|
74
77
|
summary: action.summary,
|
|
75
78
|
source: 'oracle-execute',
|
|
76
79
|
artifactPath,
|
|
80
|
+
dispatchMode: dispatch?.mode ?? 'static',
|
|
77
81
|
}, homeDir);
|
|
78
82
|
}
|
|
79
83
|
|
|
@@ -88,6 +92,34 @@ async function dispatchSpecialist({ action, projectDir, homeDir, personaId, even
|
|
|
88
92
|
};
|
|
89
93
|
}
|
|
90
94
|
|
|
95
|
+
async function dispatchRemediation({ action, projectDir, homeDir, eventType, personaId = null }) {
|
|
96
|
+
const dispatch = resolveRemediationDispatch(action, { projectDir });
|
|
97
|
+
if (dispatch.mode === 'swarm') {
|
|
98
|
+
const results = [];
|
|
99
|
+
for (const specialistId of dispatch.specialists) {
|
|
100
|
+
results.push(await dispatchSpecialist({
|
|
101
|
+
action, projectDir, homeDir, personaId: specialistId, eventType, dispatch,
|
|
102
|
+
}));
|
|
103
|
+
}
|
|
104
|
+
return {
|
|
105
|
+
ok: true,
|
|
106
|
+
mode: 'swarm',
|
|
107
|
+
specialists: dispatch.specialists,
|
|
108
|
+
teamRouting: dispatch.teamRouting,
|
|
109
|
+
results,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
const result = await dispatchSpecialist({
|
|
113
|
+
action,
|
|
114
|
+
projectDir,
|
|
115
|
+
homeDir,
|
|
116
|
+
personaId: personaId ?? dispatch.primary,
|
|
117
|
+
eventType,
|
|
118
|
+
dispatch,
|
|
119
|
+
});
|
|
120
|
+
return { ...result, mode: 'static', teamRouting: dispatch.teamRouting };
|
|
121
|
+
}
|
|
122
|
+
|
|
91
123
|
/**
|
|
92
124
|
* Execute an approved Oracle pending action.
|
|
93
125
|
*
|
|
@@ -122,20 +154,20 @@ export async function executeApprovedAction(action, { rootDir, projectDir, homeD
|
|
|
122
154
|
return { ok: result.status === 0, kind, exitCode: result.status };
|
|
123
155
|
}
|
|
124
156
|
case 'specialist-review':
|
|
125
|
-
return
|
|
157
|
+
return dispatchRemediation({
|
|
126
158
|
action, projectDir, homeDir, personaId: 'cx-reviewer', eventType: 'contract.violation',
|
|
127
159
|
});
|
|
128
160
|
case 'trace-review':
|
|
129
|
-
return
|
|
161
|
+
return dispatchRemediation({
|
|
130
162
|
action, projectDir, homeDir, personaId: 'cx-trace-reviewer', eventType: 'outcomes.degraded',
|
|
131
163
|
});
|
|
132
164
|
case 'doctor-followup':
|
|
133
|
-
return
|
|
165
|
+
return dispatchRemediation({
|
|
134
166
|
action, projectDir, homeDir, personaId: 'cx-sre', eventType: 'service.down',
|
|
135
167
|
});
|
|
136
168
|
case 'executive-signoff-required':
|
|
137
169
|
case 'structure-cleanup-proposal':
|
|
138
|
-
return
|
|
170
|
+
return dispatchRemediation({
|
|
139
171
|
action, projectDir, homeDir, personaId: routeAction(kind).primary, eventType: 'oracle.approval',
|
|
140
172
|
});
|
|
141
173
|
default:
|
package/lib/oracle/index.mjs
CHANGED
|
@@ -29,7 +29,7 @@ export function lastTickPath(homeDir = homedir()) {
|
|
|
29
29
|
return path.join(runtimeDir(homeDir), 'last-tick.json');
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
function writeLastTick(tick, homeDir) {
|
|
32
|
+
export function writeLastTick(tick, homeDir = homedir()) {
|
|
33
33
|
const dir = runtimeDir(homeDir);
|
|
34
34
|
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
35
35
|
fs.writeFileSync(lastTickPath(homeDir), JSON.stringify(tick, null, 2));
|
package/lib/oracle/org-graph.mjs
CHANGED
|
@@ -12,6 +12,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
12
12
|
import { alignmentFindings } from '../workflow-state.mjs';
|
|
13
13
|
import { loadCapabilityRegistry } from '../registry/validate.mjs';
|
|
14
14
|
import { isConstructPackageRepo } from '../host-disposition.mjs';
|
|
15
|
+
import { loadRegistry } from '../registry/loader.mjs';
|
|
15
16
|
|
|
16
17
|
const MODULE_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
17
18
|
const PACKAGE_ROOT = path.resolve(MODULE_DIR, '../..');
|
|
@@ -25,10 +26,14 @@ function readJsonSafe(filePath) {
|
|
|
25
26
|
}
|
|
26
27
|
|
|
27
28
|
function collectPolicyInventory(rootDir) {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
let policies = [];
|
|
30
|
+
try {
|
|
31
|
+
const registry = loadRegistry({ rootDir, skipValidation: true });
|
|
32
|
+
const raw = registry.policies || {};
|
|
33
|
+
policies = Array.isArray(raw) ? raw : Object.values(raw);
|
|
34
|
+
} catch {
|
|
35
|
+
return { present: false, count: 0, policies: [] };
|
|
36
|
+
}
|
|
32
37
|
const orphans = [];
|
|
33
38
|
for (const p of policies) {
|
|
34
39
|
if (p.source && p.source !== 'built-in' && !p.source.startsWith('specialists/')) {
|
|
@@ -79,7 +84,7 @@ function collectCapabilityGates(rootDir) {
|
|
|
79
84
|
}
|
|
80
85
|
|
|
81
86
|
function collectPropagationSignals({ rootDir, projectDir, parity, registryValidate }) {
|
|
82
|
-
const registryPath = path.join(rootDir, 'specialists', '
|
|
87
|
+
const registryPath = path.join(rootDir, 'specialists', 'org');
|
|
83
88
|
let registryMtime = null;
|
|
84
89
|
try {
|
|
85
90
|
registryMtime = fs.statSync(registryPath).mtimeMs;
|
package/lib/oracle/policy.mjs
CHANGED
|
@@ -30,6 +30,12 @@ export const VERDICT_ONLY_GAP_IDS = new Set([
|
|
|
30
30
|
'propagation-stale',
|
|
31
31
|
'census-stale',
|
|
32
32
|
'outcomes-missing',
|
|
33
|
+
// Team governance signals require specialist review and cross-team discussion.
|
|
34
|
+
// They are not auto-actionable and surface in doctor/prelude as informational gaps.
|
|
35
|
+
'team-understaffed',
|
|
36
|
+
'escalation-path-broken',
|
|
37
|
+
'team-decision-violation',
|
|
38
|
+
'cross-team-handoff-blocked',
|
|
33
39
|
]);
|
|
34
40
|
|
|
35
41
|
const DENY_ACTIONS = new Set([
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import fs from 'node:fs';
|
|
12
|
+
import { loadRegistry } from '../registry/loader.mjs';
|
|
12
13
|
import path from 'node:path';
|
|
13
14
|
import { spawnSync } from 'node:child_process';
|
|
14
15
|
|
|
@@ -20,6 +21,7 @@ import { collectOrgGraph } from './org-graph.mjs';
|
|
|
20
21
|
import { collectArtifactGateSignals } from './artifact-gate.mjs';
|
|
21
22
|
import { loadGraph, nodesByType, dependentsOf } from '../graph/store.mjs';
|
|
22
23
|
import { hashFiles } from '../graph/build-from-registry.mjs';
|
|
24
|
+
import { GRAPH_SEED_FILES } from '../graph/staleness.mjs';
|
|
23
25
|
import { readViolationSupersedeCutoff } from '../contracts/violation-log.mjs';
|
|
24
26
|
import { doctorRoot } from '../config/xdg.mjs';
|
|
25
27
|
|
|
@@ -235,12 +237,6 @@ function collectStructureSprawl(projectDir) {
|
|
|
235
237
|
}
|
|
236
238
|
}
|
|
237
239
|
|
|
238
|
-
const GRAPH_SEED_FILES = [
|
|
239
|
-
'registry/capabilities.json',
|
|
240
|
-
'specialists/contracts.json',
|
|
241
|
-
'lib/embedded-contract/workflow-defs.mjs',
|
|
242
|
-
];
|
|
243
|
-
|
|
244
240
|
// The living dependency matrix: file↔capability↔workflow↔test graph at
|
|
245
241
|
// .cx/graph/. Surfaces three oversight signals — staleness (seeds changed
|
|
246
242
|
// since last build), coverage (capabilities/workflows with no impl or test
|
|
@@ -314,6 +310,105 @@ function collectDeadCodeSync(rootDir, projectDir) {
|
|
|
314
310
|
return { present: false, pendingAsync: true, deadCount: 0, regressions: [] };
|
|
315
311
|
}
|
|
316
312
|
|
|
313
|
+
function specialistTeamId(specialist) {
|
|
314
|
+
return specialist.teamId ?? specialist.team ?? null;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function specialistsForTeamEntity(teamId, team, registry) {
|
|
318
|
+
const all = Object.values(registry.specialists ?? {});
|
|
319
|
+
if (team.kind === 'group' && Array.isArray(team.squads) && team.squads.length > 0) {
|
|
320
|
+
const squadIds = new Set(team.squads);
|
|
321
|
+
return all.filter((s) => squadIds.has(specialistTeamId(s)));
|
|
322
|
+
}
|
|
323
|
+
return all.filter((s) => specialistTeamId(s) === teamId);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function isTeamStaffed(teamId, team, registry) {
|
|
327
|
+
if (specialistsForTeamEntity(teamId, team, registry).length > 0) return true;
|
|
328
|
+
if (team.kind === 'squad' && Array.isArray(team.specialists) && team.specialists.length > 0) {
|
|
329
|
+
return true;
|
|
330
|
+
}
|
|
331
|
+
return false;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function ownerExistsForTeam(teamId, team, registry) {
|
|
335
|
+
const ownerRole = team.owner;
|
|
336
|
+
if (!ownerRole) return true;
|
|
337
|
+
return specialistsForTeamEntity(teamId, team, registry).some((s) => s.role === ownerRole);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function collectTeamGovernance(rootDir) {
|
|
341
|
+
const registry = loadRegistry({ rootDir });
|
|
342
|
+
if (!registry?.teams) {
|
|
343
|
+
return { present: false, teams: {} };
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const teams = {};
|
|
347
|
+
for (const [teamId, team] of Object.entries(registry.teams)) {
|
|
348
|
+
const roles = team.roles ?? [];
|
|
349
|
+
const escalationPath = team.escalationPath ?? [];
|
|
350
|
+
const decisionRights = team.decisionRights ?? [];
|
|
351
|
+
const forbiddenDecisions = team.forbiddenDecisions ?? [];
|
|
352
|
+
const teamSpecialists = specialistsForTeamEntity(teamId, team, registry);
|
|
353
|
+
|
|
354
|
+
// Check if escalation path is complete (all roles exist in registry)
|
|
355
|
+
const escalationPathBroken = escalationPath.some((role) => {
|
|
356
|
+
const specialist = Object.values(registry.specialists ?? {}).some((s) => s.role === role);
|
|
357
|
+
return !specialist;
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
teams[teamId] = {
|
|
361
|
+
id: teamId,
|
|
362
|
+
name: team.name,
|
|
363
|
+
kind: team.kind ?? null,
|
|
364
|
+
owner: team.owner,
|
|
365
|
+
roleCount: roles.length,
|
|
366
|
+
specialistCount: teamSpecialists.length,
|
|
367
|
+
decisionRightsCount: decisionRights.length,
|
|
368
|
+
forbiddenDecisionsCount: forbiddenDecisions.length,
|
|
369
|
+
escalationPathLength: escalationPath.length,
|
|
370
|
+
escalationPathBroken,
|
|
371
|
+
ownerExists: ownerExistsForTeam(teamId, team, registry),
|
|
372
|
+
understaffed: roles.length > 0 && !isTeamStaffed(teamId, team, registry),
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// Cross-team handoff health: a crossing contract whose required approver team
|
|
377
|
+
// is missing or unstaffed can never be approved, so the handoff is blocked.
|
|
378
|
+
const contracts = Array.isArray(registry.contracts)
|
|
379
|
+
? registry.contracts
|
|
380
|
+
: Object.values(registry.contracts ?? {});
|
|
381
|
+
const crossTeamHandoffsBlocked = [];
|
|
382
|
+
for (const contract of contracts) {
|
|
383
|
+
const boundary = contract.teamBoundary;
|
|
384
|
+
if (!boundary?.crosses
|
|
385
|
+
|| !Array.isArray(boundary.requiresApprovalFrom)
|
|
386
|
+
|| boundary.requiresApprovalFrom.length === 0) {
|
|
387
|
+
continue;
|
|
388
|
+
}
|
|
389
|
+
const unstaffedApprovers = boundary.requiresApprovalFrom.filter((approverId) => {
|
|
390
|
+
const team = registry.teams?.[approverId];
|
|
391
|
+
if (!team) return true;
|
|
392
|
+
return !isTeamStaffed(approverId, team, registry);
|
|
393
|
+
});
|
|
394
|
+
if (unstaffedApprovers.length > 0) {
|
|
395
|
+
crossTeamHandoffsBlocked.push({
|
|
396
|
+
contract: contract.id,
|
|
397
|
+
producerTeam: boundary.producerTeam ?? null,
|
|
398
|
+
consumerTeam: boundary.consumerTeam ?? null,
|
|
399
|
+
blockedBy: unstaffedApprovers,
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
return {
|
|
405
|
+
present: true,
|
|
406
|
+
teamCount: Object.keys(teams).length,
|
|
407
|
+
teams,
|
|
408
|
+
crossTeamHandoffsBlocked,
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
|
|
317
412
|
/**
|
|
318
413
|
* Collect the Oracle read model from durable Construct signals.
|
|
319
414
|
*
|
|
@@ -334,6 +429,7 @@ export function collectReadModel({ rootDir, projectDir, homeDir }) {
|
|
|
334
429
|
const registryValidate = collectRegistryValidate(rootDir);
|
|
335
430
|
const orgGraph = collectOrgGraph({ rootDir, projectDir, parity, registryValidate });
|
|
336
431
|
const artifactGate = collectArtifactGateSignals({ rootDir, projectDir });
|
|
432
|
+
const teamGovernance = collectTeamGovernance(rootDir);
|
|
337
433
|
|
|
338
434
|
return {
|
|
339
435
|
collectedAt,
|
|
@@ -349,6 +445,7 @@ export function collectReadModel({ rootDir, projectDir, homeDir }) {
|
|
|
349
445
|
parity,
|
|
350
446
|
orgGraph,
|
|
351
447
|
artifactGate,
|
|
448
|
+
teamGovernance,
|
|
352
449
|
beads: collectBeadsOpenCount(projectDir),
|
|
353
450
|
hookFailures: collectHookFailures(homeDir),
|
|
354
451
|
structure: collectStructureSprawl(projectDir),
|