@geraldmaron/construct 1.2.3 → 1.3.1
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 +235 -209
- 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 +55 -54
- package/lib/comment-lint.mjs +1 -5
- package/lib/completions.mjs +20 -2
- package/lib/config/legacy-config-migration.mjs +90 -24
- 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/cli.mjs +12 -2
- package/lib/doctor/source-checkout.mjs +3 -4
- package/lib/doctor/watchers/consistency.mjs +148 -43
- 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/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 +6 -26
- 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 -8
- 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 +269 -51
- 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/apps/chat/engine/ai-sdk-agent.mjs +0 -183
- package/apps/chat/engine/loop-driver.mjs +0 -211
- package/apps/chat/engine/models.mjs +0 -122
- package/apps/chat/engine/provider-adapters.mjs +0 -171
- package/apps/chat/engine/tools/permission.mjs +0 -54
- package/apps/chat/engine/tools/primitives.mjs +0 -180
- package/apps/chat/engine/tools/registry.mjs +0 -122
- package/apps/chat/engine/turn-controls.mjs +0 -70
- 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
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),
|
package/lib/oracle/reconcile.mjs
CHANGED
|
@@ -93,6 +93,20 @@ function pruneRaisedIssues(projectDir, { dryRun = false } = {}) {
|
|
|
93
93
|
|
|
94
94
|
const BARE_GOAL_FIELD_LABELS = ['intent', 'workCategory', 'riskFlags', 'acceptanceCriteria'];
|
|
95
95
|
|
|
96
|
+
const RESEARCHER_PROBE_MISSING = [
|
|
97
|
+
'artifact missing required field: question',
|
|
98
|
+
'artifact missing required field: method',
|
|
99
|
+
'artifact missing required field: sources',
|
|
100
|
+
'artifact missing required field: findings',
|
|
101
|
+
'artifact missing required field: confidence',
|
|
102
|
+
];
|
|
103
|
+
|
|
104
|
+
const A11Y_PROBE_MISSING = [
|
|
105
|
+
'artifact missing required field: findings',
|
|
106
|
+
'artifact missing required field: wcagCriterion',
|
|
107
|
+
'artifact missing required field: userImpact',
|
|
108
|
+
];
|
|
109
|
+
|
|
96
110
|
function missingBareGoalConstructFields(missing) {
|
|
97
111
|
if (!Array.isArray(missing) || missing.length !== BARE_GOAL_FIELD_LABELS.length) return false;
|
|
98
112
|
return BARE_GOAL_FIELD_LABELS.every((field) =>
|
|
@@ -107,6 +121,19 @@ function isBareGoalConstructViolation(row) {
|
|
|
107
121
|
return Array.isArray(keys) && keys.length === 1 && keys[0] === 'goal';
|
|
108
122
|
}
|
|
109
123
|
|
|
124
|
+
function isEmptyProbeViolation(row, contractId, expectedMissing) {
|
|
125
|
+
if (row?.contractId !== contractId) return false;
|
|
126
|
+
const keys = row.packet_keys ?? row.packetKeys;
|
|
127
|
+
if (!Array.isArray(keys) || keys.length !== 0) return false;
|
|
128
|
+
return JSON.stringify(row.missing) === JSON.stringify(expectedMissing);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function isDevSessionContractNoise(row) {
|
|
132
|
+
return isBareGoalConstructViolation(row)
|
|
133
|
+
|| isEmptyProbeViolation(row, 'researcher-to-architect', RESEARCHER_PROBE_MISSING)
|
|
134
|
+
|| isEmptyProbeViolation(row, 'accessibility-to-qa', A11Y_PROBE_MISSING);
|
|
135
|
+
}
|
|
136
|
+
|
|
110
137
|
/**
|
|
111
138
|
* @param {string} projectDir
|
|
112
139
|
* @returns {{ shouldSupersede: boolean, recentCount: number, reason: string }}
|
|
@@ -116,18 +143,18 @@ export function planContractViolationSupersede(projectDir) {
|
|
|
116
143
|
if (!recent.length) {
|
|
117
144
|
return { shouldSupersede: false, recentCount: 0, reason: 'no recent contract violations' };
|
|
118
145
|
}
|
|
119
|
-
const
|
|
120
|
-
if (!
|
|
146
|
+
const allDevSessionNoise = recent.every(isDevSessionContractNoise);
|
|
147
|
+
if (!allDevSessionNoise) {
|
|
121
148
|
return {
|
|
122
149
|
shouldSupersede: false,
|
|
123
150
|
recentCount: recent.length,
|
|
124
|
-
reason: 'recent violations include non-
|
|
151
|
+
reason: 'recent violations include non-probe contract failures',
|
|
125
152
|
};
|
|
126
153
|
}
|
|
127
154
|
return {
|
|
128
155
|
shouldSupersede: true,
|
|
129
156
|
recentCount: recent.length,
|
|
130
|
-
reason: '
|
|
157
|
+
reason: 'dev-session contract probes superseded (bare-goal construct, empty researcher/a11y packets)',
|
|
131
158
|
};
|
|
132
159
|
}
|
|
133
160
|
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/oracle/remediation-dispatch.mjs — static vs swarm dispatch for Oracle remediation.
|
|
3
|
+
*
|
|
4
|
+
* Seeds orchestration-policy teamRouting with gap/action remediation specialists.
|
|
5
|
+
* Swarm when involvedTeams.length > 1; static when a single team owns the work.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { classifyIntent, teamRoutingForSpecialists } from '../orchestration-policy.mjs';
|
|
9
|
+
import { routeGap, routeAction } from './routing.mjs';
|
|
10
|
+
|
|
11
|
+
function remediationText(item) {
|
|
12
|
+
return String(item.detail ?? item.summary ?? item.id ?? 'Oracle remediation');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function seededSpecialists(item) {
|
|
16
|
+
const route = item.remediationRoute ?? (item.kind ? routeAction(item.kind) : routeGap(item));
|
|
17
|
+
return [route.primary, route.secondary].filter(Boolean);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @param {object} item — gap or recommended action with optional remediationRoute
|
|
22
|
+
* @param {{ projectDir?: string, cwd?: string }} [opts]
|
|
23
|
+
* @returns {{ mode: 'static'|'swarm', primary: string, specialists: string[], teamRouting: object }}
|
|
24
|
+
*/
|
|
25
|
+
export function resolveRemediationDispatch(item, { projectDir, cwd } = {}) {
|
|
26
|
+
const route = item.remediationRoute ?? (item.kind ? routeAction(item.kind) : routeGap(item));
|
|
27
|
+
const primary = route.primary;
|
|
28
|
+
const specialists = seededSpecialists(item);
|
|
29
|
+
const request = remediationText(item);
|
|
30
|
+
const intent = classifyIntent(request);
|
|
31
|
+
const teamRouting = teamRoutingForSpecialists(specialists, {
|
|
32
|
+
intent,
|
|
33
|
+
request,
|
|
34
|
+
cwd: cwd ?? projectDir ?? null,
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const involvedTeams = teamRouting.involvedTeams ?? [];
|
|
38
|
+
if (involvedTeams.length <= 1) {
|
|
39
|
+
return {
|
|
40
|
+
mode: 'static',
|
|
41
|
+
primary,
|
|
42
|
+
specialists: [primary],
|
|
43
|
+
teamRouting,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
mode: 'swarm',
|
|
49
|
+
primary,
|
|
50
|
+
specialists: Array.from(new Set(specialists)),
|
|
51
|
+
teamRouting,
|
|
52
|
+
};
|
|
53
|
+
}
|