@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
package/lib/parity.mjs
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* After `construct sync` writes adapters to multiple surfaces (Claude Code,
|
|
5
5
|
* OpenCode, Codex, Copilot, VS Code, Cursor), the module diffs each surface's actual state against the
|
|
6
|
-
* canonical `specialists/
|
|
6
|
+
* canonical `specialists/org`. Backs `construct doctor` for surfacing
|
|
7
7
|
* silent divergence — for instance, an agent added to the registry that
|
|
8
8
|
* never made it to OpenCode because of a sync regression.
|
|
9
9
|
*
|
|
@@ -62,9 +62,10 @@ function readJsoncFile(file) {
|
|
|
62
62
|
return parseJsonc(fs.readFileSync(file, 'utf8'));
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
+
import { loadRegistry as loadAssembledRegistry } from './registry/loader.mjs';
|
|
66
|
+
|
|
65
67
|
function loadRegistry(rootDir = ROOT_DIR) {
|
|
66
|
-
|
|
67
|
-
return JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
68
|
+
return loadAssembledRegistry({ rootDir });
|
|
68
69
|
}
|
|
69
70
|
|
|
70
71
|
function adapterName(entry, prefix) {
|
|
@@ -74,18 +75,30 @@ function adapterName(entry, prefix) {
|
|
|
74
75
|
function entriesForSurface(registry, surface, { scope = 'global' } = {}) {
|
|
75
76
|
const prefix = registry.prefix || 'cx';
|
|
76
77
|
|
|
78
|
+
// Support both legacy format (registry.orchestrator) and unified format
|
|
79
|
+
// (orchestrator lives in registry.specialists with role:'orchestrator').
|
|
80
|
+
// Unified format stores name:'orchestrator' but the adapter name is 'construct'
|
|
81
|
+
// (hardcoded in unifiedToLegacyRegistry) — normalize it here so parity matches.
|
|
82
|
+
const rawOrchestrator = registry.orchestrator
|
|
83
|
+
|| Object.values(registry.specialists || {}).find((s) => s.role === 'orchestrator');
|
|
84
|
+
const orchestratorEntry = rawOrchestrator && rawOrchestrator.name !== 'construct'
|
|
85
|
+
? { ...rawOrchestrator, name: 'construct' }
|
|
86
|
+
: rawOrchestrator;
|
|
87
|
+
|
|
77
88
|
// User-scope expects only the `construct` front-door agent. Specialists
|
|
78
|
-
// live with each Construct-managed project (apps/
|
|
89
|
+
// live with each Construct-managed project (apps/docs,
|
|
79
90
|
// and every other repo that runs `construct init`) — see the two-tier
|
|
80
91
|
// sync contract in scripts/sync-specialists.mjs.
|
|
81
92
|
|
|
82
93
|
const entries = scope === 'project'
|
|
83
94
|
? [
|
|
84
|
-
...(
|
|
85
|
-
...(registry.specialists ||
|
|
95
|
+
...(orchestratorEntry ? [{ ...orchestratorEntry, isPersona: true }] : []),
|
|
96
|
+
...Object.values(registry.specialists || {})
|
|
97
|
+
.filter((s) => s.role !== 'orchestrator')
|
|
98
|
+
.map((s) => ({ ...s, isPersona: false })),
|
|
86
99
|
]
|
|
87
|
-
:
|
|
88
|
-
? [{ ...
|
|
100
|
+
: orchestratorEntry
|
|
101
|
+
? [{ ...orchestratorEntry, isPersona: true }]
|
|
89
102
|
: [];
|
|
90
103
|
|
|
91
104
|
return entries
|
|
@@ -171,6 +184,19 @@ function checkClaude(registry, { homeDir = os.homedir() } = {}) {
|
|
|
171
184
|
|
|
172
185
|
const OPENCODE_BUILTIN_AGENTS = new Set(['title', 'summary', 'compaction']);
|
|
173
186
|
|
|
187
|
+
function normalizeOpenCodeManagedAgents(actual, expected) {
|
|
188
|
+
const managed = actual.filter((id) => !OPENCODE_BUILTIN_AGENTS.has(id));
|
|
189
|
+
const expectsConstruct = expected.length === 1 && expected[0] === 'construct';
|
|
190
|
+
if (!expectsConstruct) return managed;
|
|
191
|
+
if (managed.includes('construct')) {
|
|
192
|
+
return managed.filter((id) => id !== 'orchestrator');
|
|
193
|
+
}
|
|
194
|
+
if (managed.length === 1 && managed[0] === 'orchestrator') {
|
|
195
|
+
return ['construct'];
|
|
196
|
+
}
|
|
197
|
+
return managed;
|
|
198
|
+
}
|
|
199
|
+
|
|
174
200
|
function checkOpenCode(registry, { homeDir = os.homedir() } = {}) {
|
|
175
201
|
const file = path.join(homeDir, '.config', 'opencode', 'opencode.json');
|
|
176
202
|
if (!fs.existsSync(file)) return { surface: 'opencode', kind: 'agents', status: 'absent', file };
|
|
@@ -181,8 +207,8 @@ function checkOpenCode(registry, { homeDir = os.homedir() } = {}) {
|
|
|
181
207
|
return { surface: 'opencode', kind: 'agents', status: 'unreadable', file, error: err.message };
|
|
182
208
|
}
|
|
183
209
|
const expected = entriesForSurface(registry, 'opencode');
|
|
184
|
-
const
|
|
185
|
-
|
|
210
|
+
const rawActual = Object.keys(config.agent || config.agents || {});
|
|
211
|
+
const actual = normalizeOpenCodeManagedAgents(rawActual, expected);
|
|
186
212
|
const { missing, extra } = diffSets(expected, actual);
|
|
187
213
|
|
|
188
214
|
// OpenCode's own system agents (title/summary/compaction) are filtered from
|
|
@@ -379,7 +405,7 @@ export function checkProjectParity({ rootDir = ROOT_DIR, projectDir = rootDir }
|
|
|
379
405
|
// an upgrade," not real drift.
|
|
380
406
|
function legacyUserScopeRoster(registry) {
|
|
381
407
|
const prefix = registry.prefix || 'cx';
|
|
382
|
-
const specialists = (registry.specialists ||
|
|
408
|
+
const specialists = Object.values(registry.specialists || {}).map((s) => `${prefix}-${s.name}`);
|
|
383
409
|
// The front-door agent is project-scoped too now, so a leftover `construct.md`
|
|
384
410
|
// in user-scope `.claude/agents` is a soft upgrade state, not real drift.
|
|
385
411
|
const frontDoor = registry.orchestrator?.name ? [registry.orchestrator.name] : [];
|
package/lib/policy/engine.mjs
CHANGED
|
@@ -1,37 +1,88 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* lib/policy/engine.mjs — role-based policy decisions for tool / action access.
|
|
3
3
|
*
|
|
4
|
-
* Reads role permissions from
|
|
4
|
+
* Reads role permissions from the unified registry and decides
|
|
5
5
|
* whether a given (role, project, tool, action, risk) tuple is allowed,
|
|
6
6
|
* needs approval, or is denied outright. Powers the MCP broker for team
|
|
7
7
|
* and enterprise deployments; solo mode leaves the broker off so this
|
|
8
8
|
* engine returns allow-without-approval everywhere.
|
|
9
9
|
*
|
|
10
10
|
* Decision precedence:
|
|
11
|
-
* 1.
|
|
12
|
-
* 2.
|
|
11
|
+
* 1. Team-level decisions gate specialist decisions — forbidden by team blocks the action.
|
|
12
|
+
* 2. Explicit deny in the role's fence → denied + typed reason.
|
|
13
|
+
* 3. Action falls in the role's approvalRequired list → allowed but
|
|
13
14
|
* approvalRequired = true (UI must collect human consent).
|
|
14
|
-
*
|
|
15
|
-
*
|
|
15
|
+
* 4. risk === 'high' and role is not in HIGH_RISK_AUTONOMOUS → approval required.
|
|
16
|
+
* 5. Otherwise → allowed.
|
|
16
17
|
*/
|
|
17
18
|
|
|
18
19
|
import { readFileSync, existsSync } from 'node:fs';
|
|
19
|
-
import
|
|
20
|
-
import { fileURLToPath } from 'node:url';
|
|
21
|
-
|
|
22
|
-
const MODULE_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
23
|
-
const DEFAULT_MANIFEST_PATH = path.join(MODULE_DIR, '..', '..', 'specialists', 'role-manifests.json');
|
|
20
|
+
import { loadRegistry } from '../registry/loader.mjs';
|
|
24
21
|
|
|
25
22
|
const HIGH_RISK_AUTONOMOUS = new Set(['security', 'sre', 'release-manager']);
|
|
26
23
|
|
|
27
24
|
let cachedManifests = null;
|
|
28
25
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
26
|
+
/**
|
|
27
|
+
* Find the team that owns a specialist/role.
|
|
28
|
+
* Returns the team id if found, null otherwise.
|
|
29
|
+
*/
|
|
30
|
+
function findTeamForRole(roleId, registry) {
|
|
31
|
+
if (!registry || !registry.teams) return null;
|
|
32
|
+
for (const [teamId, team] of Object.entries(registry.teams)) {
|
|
33
|
+
if (Array.isArray(team.roles) && team.roles.includes(roleId)) {
|
|
34
|
+
return teamId;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Check if a role can make a decision at the team level.
|
|
42
|
+
* Returns { allowed: boolean, reason: string }.
|
|
43
|
+
*/
|
|
44
|
+
function canRoleDecide(roleId, decisionId, registry) {
|
|
45
|
+
const teamId = findTeamForRole(roleId, registry);
|
|
46
|
+
if (!teamId) return { allowed: true, reason: 'no-team-restriction' };
|
|
47
|
+
|
|
48
|
+
const team = registry.teams[teamId];
|
|
49
|
+
if (!team) return { allowed: true, reason: 'team-not-found' };
|
|
50
|
+
|
|
51
|
+
// Check forbidden decisions first — explicit blocks override everything
|
|
52
|
+
if (Array.isArray(team.forbiddenDecisions) && team.forbiddenDecisions.includes(decisionId)) {
|
|
53
|
+
return { allowed: false, reason: 'forbidden-by-team' };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Check allowed decisions — team must explicitly grant it
|
|
57
|
+
if (Array.isArray(team.decisionRights) && team.decisionRights.includes(decisionId)) {
|
|
58
|
+
return { allowed: true, reason: 'authorized-by-team' };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Default: if team doesn't forbid, allow (permissive default)
|
|
62
|
+
return { allowed: true, reason: 'not-restricted-by-team' };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function loadRoleManifests(manifestPath) {
|
|
66
|
+
// Unified registry is canonical. Explicit manifestPath overrides exist only for tests.
|
|
67
|
+
if (!manifestPath || !existsSync(manifestPath)) {
|
|
68
|
+
const manifestPathKey = manifestPath || '__unified_registry__';
|
|
69
|
+
if (cachedManifests && cachedManifests.path === manifestPathKey) return cachedManifests.data;
|
|
70
|
+
const registry = loadRegistry();
|
|
71
|
+
const personas = {};
|
|
72
|
+
for (const [specId, spec] of Object.entries(registry.specialists || {})) {
|
|
73
|
+
const personaId = specId.replace(/^cx-/, '');
|
|
74
|
+
const teamId = findTeamForRole(personaId, registry);
|
|
75
|
+
personas[personaId] = {
|
|
76
|
+
events: spec.events || [],
|
|
77
|
+
fence: spec.fence || {},
|
|
78
|
+
outputs: { docTypes: spec.docArtifacts || [] },
|
|
79
|
+
teamId: teamId || null,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
cachedManifests = { path: manifestPathKey, data: { personas, registry } };
|
|
33
83
|
return cachedManifests.data;
|
|
34
84
|
}
|
|
85
|
+
if (cachedManifests && cachedManifests.path === manifestPath) return cachedManifests.data;
|
|
35
86
|
const raw = readFileSync(manifestPath, 'utf8');
|
|
36
87
|
const data = JSON.parse(raw);
|
|
37
88
|
cachedManifests = { path: manifestPath, data };
|
|
@@ -72,12 +123,13 @@ function needsApprovalFromManifest(action, manifest) {
|
|
|
72
123
|
* @param {string} [input.project]
|
|
73
124
|
* @param {string} input.tool
|
|
74
125
|
* @param {string} input.action
|
|
126
|
+
* @param {string} [input.decision] — optional decision id for team-level gate
|
|
75
127
|
* @param {string} [input.risk]
|
|
76
128
|
* @param {object} [opts]
|
|
77
129
|
* @returns {{allowed: boolean, reason: string, approvalRequired: boolean, source: string}}
|
|
78
130
|
*/
|
|
79
131
|
export function policyDecision(input = {}, opts = {}) {
|
|
80
|
-
const { role, tool, action, risk = 'low' } = input;
|
|
132
|
+
const { role, tool, action, decision, risk = 'low' } = input;
|
|
81
133
|
if (!role) return { allowed: false, reason: 'role is required', approvalRequired: false, source: 'engine' };
|
|
82
134
|
if (!tool) return { allowed: false, reason: 'tool is required', approvalRequired: false, source: 'engine' };
|
|
83
135
|
if (!action) return { allowed: false, reason: 'action is required', approvalRequired: false, source: 'engine' };
|
|
@@ -94,6 +146,19 @@ export function policyDecision(input = {}, opts = {}) {
|
|
|
94
146
|
};
|
|
95
147
|
}
|
|
96
148
|
|
|
149
|
+
// Team decision gate: if a decision id is supplied, check team-level authorization first
|
|
150
|
+
if (decision && manifests.registry) {
|
|
151
|
+
const teamDecision = canRoleDecide(role, decision, manifests.registry);
|
|
152
|
+
if (!teamDecision.allowed) {
|
|
153
|
+
return {
|
|
154
|
+
allowed: false,
|
|
155
|
+
reason: `decision "${decision}" forbidden for team of role "${role}": ${teamDecision.reason}`,
|
|
156
|
+
approvalRequired: false,
|
|
157
|
+
source: 'team.forbiddenDecisions',
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
97
162
|
if (isExplicitlyDenied(action, manifest)) {
|
|
98
163
|
return {
|
|
99
164
|
allowed: false,
|
package/lib/prompt-composer.js
CHANGED
|
@@ -38,6 +38,8 @@ import { readRoleFile } from './role-preload.mjs';
|
|
|
38
38
|
import { estimateTokens, estimatePromptTokens, estimateTokensSync } from './token-engine.js';
|
|
39
39
|
import { cxDir } from './paths.mjs';
|
|
40
40
|
import { getStrategyDigestSync } from './strategy-store.mjs';
|
|
41
|
+
import { loadRegistry } from './registry/loader.mjs';
|
|
42
|
+
import { bindingForSpecialist, resolveRoleOverlayId } from './roles/flavor-bindings.mjs';
|
|
41
43
|
|
|
42
44
|
const MAX_OBSERVATIONS = 3;
|
|
43
45
|
|
|
@@ -46,6 +48,7 @@ const PRIORITY = {
|
|
|
46
48
|
'core': 1,
|
|
47
49
|
'task-packet': 1,
|
|
48
50
|
'role-flavor': 2,
|
|
51
|
+
'team-context': 2,
|
|
49
52
|
'model-profile': 2,
|
|
50
53
|
'context-digest': 3,
|
|
51
54
|
'strategy': 3,
|
|
@@ -55,23 +58,13 @@ const PRIORITY = {
|
|
|
55
58
|
|
|
56
59
|
// workspaceType → roleFlavors auto-selection when caller doesn't override
|
|
57
60
|
const WORKSPACE_FLAVOR_MAP = {
|
|
58
|
-
platform: { productManager: 'platform', architect: 'platform',
|
|
61
|
+
platform: { productManager: 'platform', architect: 'platform', platformEngineer: 'core' },
|
|
59
62
|
enterprise: { productManager: 'enterprise', architect: 'enterprise' },
|
|
60
63
|
'ai-product': { productManager: 'ai-product', architect: 'ai-systems' },
|
|
61
64
|
growth: { productManager: 'growth' },
|
|
62
65
|
product: {},
|
|
63
66
|
};
|
|
64
67
|
|
|
65
|
-
const AGENT_FLAVOR_MAP = {
|
|
66
|
-
engineer: { classifierKey: 'engineer', rolePrefix: 'engineer' },
|
|
67
|
-
architect: { classifierKey: 'architect', rolePrefix: 'architect' },
|
|
68
|
-
'product-manager': { classifierKey: 'productManager', rolePrefix: 'product-manager' },
|
|
69
|
-
qa: { classifierKey: 'qa', rolePrefix: 'qa' },
|
|
70
|
-
security: { classifierKey: 'security', rolePrefix: 'security' },
|
|
71
|
-
'data-analyst': { classifierKey: 'dataAnalyst', rolePrefix: 'data-analyst' },
|
|
72
|
-
'data-engineer': { classifierKey: 'dataEngineer', rolePrefix: 'data-engineer' },
|
|
73
|
-
};
|
|
74
|
-
|
|
75
68
|
// Merge workspaceType-derived overlays with explicit roleFlavors. Explicit always wins.
|
|
76
69
|
function resolveRoleFlavors(roleFlavors, workspaceType, shortName) {
|
|
77
70
|
const workspaceDefaults = workspaceType ? (WORKSPACE_FLAVOR_MAP[workspaceType] ?? {}) : {};
|
|
@@ -181,6 +174,14 @@ export function resolvePromptContract(entryOrName, {
|
|
|
181
174
|
};
|
|
182
175
|
}
|
|
183
176
|
|
|
177
|
+
if (directEntry?.promptFile) {
|
|
178
|
+
const body = stripSectionMarkers(readPromptBody(directEntry.promptFile, rootDir));
|
|
179
|
+
return {
|
|
180
|
+
prompt: body || fallback,
|
|
181
|
+
metadata: resolvePromptMetadata(directEntry.name, { rootDir, registry }),
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
184
185
|
const agentName = directEntry?.name || entryOrName;
|
|
185
186
|
if (!agentName) return { prompt: fallback, metadata: {} };
|
|
186
187
|
|
|
@@ -191,6 +192,27 @@ export function resolvePromptContract(entryOrName, {
|
|
|
191
192
|
};
|
|
192
193
|
}
|
|
193
194
|
|
|
195
|
+
function buildTeamBlock(entry, registry, { tokenLimit = 200, modelId = 'default' } = {}) {
|
|
196
|
+
if (!entry?.team || !registry?.teams) return null;
|
|
197
|
+
const squad = registry.teams[entry.team];
|
|
198
|
+
if (!squad) return null;
|
|
199
|
+
const groupId = squad.groupId || entry.groupId;
|
|
200
|
+
const group = groupId ? registry.teams[groupId] : null;
|
|
201
|
+
const collaborators = (squad.collaborators || []).filter(Boolean);
|
|
202
|
+
const lines = [
|
|
203
|
+
'## Team context',
|
|
204
|
+
'',
|
|
205
|
+
`Squad: **${squad.name || squad.id}** (\`${squad.id}\`)`,
|
|
206
|
+
];
|
|
207
|
+
if (group) lines.push(`Group: **${group.name || groupId}** (\`${groupId}\`)`);
|
|
208
|
+
if (squad.charter) lines.push('', compactTokens(squad.charter, tokenLimit, { modelId }));
|
|
209
|
+
if (collaborators.length) {
|
|
210
|
+
lines.push('', `Collaborators: ${collaborators.map((c) => `\`${c}\``).join(', ')}`);
|
|
211
|
+
}
|
|
212
|
+
lines.push('', 'Invoke sibling squad specialists when work crosses squad boundaries. Call `suggest_skills` when the task domain is ambiguous.');
|
|
213
|
+
return lines.join('\n');
|
|
214
|
+
}
|
|
215
|
+
|
|
194
216
|
export function composePrompt(agentName, {
|
|
195
217
|
rootDir = process.cwd(),
|
|
196
218
|
registry,
|
|
@@ -219,8 +241,20 @@ export function composePrompt(agentName, {
|
|
|
219
241
|
|
|
220
242
|
fragments.push({ type: 'core', priority: PRIORITY['core'], label: entry.name, content: stripSectionMarkers(readPromptBody(entry.promptFile, rootDir)), tokenBudget: null });
|
|
221
243
|
|
|
244
|
+
const resolvedRegistry = registry || loadRegistry({ rootDir });
|
|
245
|
+
const teamBlock = buildTeamBlock(entry, resolvedRegistry, { modelId });
|
|
246
|
+
if (teamBlock) {
|
|
247
|
+
fragments.push({
|
|
248
|
+
type: 'team-context',
|
|
249
|
+
priority: PRIORITY['team-context'],
|
|
250
|
+
label: entry.team,
|
|
251
|
+
content: teamBlock,
|
|
252
|
+
tokenBudget: 200,
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
|
|
222
256
|
const shortName = String(agentName).replace(/^cx-/, '');
|
|
223
|
-
const flavorMapping =
|
|
257
|
+
const flavorMapping = bindingForSpecialist(shortName);
|
|
224
258
|
|
|
225
259
|
// When workspaceType is provided and roleFlavors doesn't already specify this agent's flavor,
|
|
226
260
|
// auto-select the matching overlay so agents get domain guidance without requiring explicit caller config.
|
|
@@ -229,7 +263,8 @@ export function composePrompt(agentName, {
|
|
|
229
263
|
if (flavorMapping && effectiveRoleFlavors) {
|
|
230
264
|
const flavor = effectiveRoleFlavors[flavorMapping.classifierKey];
|
|
231
265
|
if (flavor) {
|
|
232
|
-
const
|
|
266
|
+
const roleId = resolveRoleOverlayId(flavorMapping, flavor);
|
|
267
|
+
const overlayBody = readRoleFile(rootDir, roleId, {
|
|
233
268
|
source: 'prompt-composer',
|
|
234
269
|
callerContext: agentName,
|
|
235
270
|
});
|
|
@@ -240,8 +275,8 @@ export function composePrompt(agentName, {
|
|
|
240
275
|
fragments.push({
|
|
241
276
|
type: 'role-flavor',
|
|
242
277
|
priority: PRIORITY['role-flavor'],
|
|
243
|
-
label:
|
|
244
|
-
content: `### ${flavor} domain guidance\n\n${overlayContent}`,
|
|
278
|
+
label: roleId,
|
|
279
|
+
content: `### ${flavor === 'core' ? shortName : flavor} domain guidance\n\n${overlayContent}`,
|
|
245
280
|
tokenBudget: selectedProfile.roleFlavorTokens,
|
|
246
281
|
});
|
|
247
282
|
}
|
|
@@ -484,6 +519,7 @@ export function summarizePromptComposition(agentName, options = {}) {
|
|
|
484
519
|
routeIntent: route.intent,
|
|
485
520
|
routeTrack: route.track,
|
|
486
521
|
routeWorkCategory: route.workCategory,
|
|
522
|
+
routeSuggestedWorkflowType: route.suggestedWorkflowType || null,
|
|
487
523
|
routeSpecialists: route.specialists,
|
|
488
524
|
routeDispatchPlan: route.dispatchPlan,
|
|
489
525
|
...(route.roleFlavors ? { routeRoleFlavors: route.roleFlavors } : {}),
|
package/lib/prompt-metadata.mjs
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* the telemetry backend instead of embedding full production prompt text in every trace.
|
|
7
7
|
*/
|
|
8
8
|
import crypto from 'node:crypto';
|
|
9
|
+
import { loadRegistry } from './registry/loader.mjs';
|
|
9
10
|
import fs from 'node:fs';
|
|
10
11
|
import path from 'node:path';
|
|
11
12
|
|
|
@@ -26,7 +27,7 @@ function findEntry(registry, agentName) {
|
|
|
26
27
|
const normalized = normalizeName(agentName);
|
|
27
28
|
const entries = [
|
|
28
29
|
registry.orchestrator,
|
|
29
|
-
...(registry.specialists ??
|
|
30
|
+
...Object.values(registry.specialists ?? {}),
|
|
30
31
|
].filter(Boolean);
|
|
31
32
|
return entries.find((entry) => {
|
|
32
33
|
const entryName = normalizeName(entry.name);
|
|
@@ -36,7 +37,7 @@ function findEntry(registry, agentName) {
|
|
|
36
37
|
|
|
37
38
|
export function resolvePromptEntry(agentName, { rootDir = process.cwd(), registry } = {}) {
|
|
38
39
|
if (!agentName) return null;
|
|
39
|
-
const loadedRegistry = registry ??
|
|
40
|
+
const loadedRegistry = registry ?? loadRegistry({ rootDir });
|
|
40
41
|
if (!loadedRegistry) return null;
|
|
41
42
|
return findEntry(loadedRegistry, agentName);
|
|
42
43
|
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/providers/connection-probe.mjs — lightweight provider auth probes for doctor and tests.
|
|
3
|
+
*
|
|
4
|
+
* Issues a minimal HTTP request per API-key provider to distinguish valid keys from
|
|
5
|
+
* auth failures. Uses injectable fetch so functional tests stay hermetic.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { resolveFirstSecret } from './secret-resolver.mjs';
|
|
9
|
+
|
|
10
|
+
const OPENROUTER_MODELS = 'https://openrouter.ai/api/v1/models';
|
|
11
|
+
const ANTHROPIC_MODELS = 'https://api.anthropic.com/v1/models';
|
|
12
|
+
const OPENAI_MODELS = 'https://api.openai.com/v1/models';
|
|
13
|
+
|
|
14
|
+
function classifyStatus(status) {
|
|
15
|
+
if (status === 401 || status === 403) return 'auth_error';
|
|
16
|
+
if (status >= 200 && status < 300) return 'ok';
|
|
17
|
+
if (status === 429) return 'rate_limited';
|
|
18
|
+
return 'probe_failed';
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function probeGet(url, headers, fetchImpl) {
|
|
22
|
+
const res = await fetchImpl(url, { method: 'GET', headers });
|
|
23
|
+
return { ok: classifyStatus(res.status) === 'ok', status: res.status, code: classifyStatus(res.status) };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export async function probeProviderConnection(providerId, { env = process.env, cwd = process.cwd(), allowAmbient = true, fetch: fetchImpl = globalThis.fetch } = {}) {
|
|
27
|
+
if (!fetchImpl) {
|
|
28
|
+
return { ok: false, code: 'fetch_unavailable', status: null, provider: providerId };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const secretOpts = { env, cwd, allowAmbient };
|
|
32
|
+
|
|
33
|
+
if (providerId === 'openrouter') {
|
|
34
|
+
const apiKey = resolveFirstSecret(['OPENROUTER_API_KEY', 'OPEN_ROUTER_API_KEY'], secretOpts);
|
|
35
|
+
if (!apiKey) return { ok: false, code: 'missing_key', status: null, provider: providerId };
|
|
36
|
+
return { provider: providerId, ...(await probeGet(OPENROUTER_MODELS, { Authorization: `Bearer ${apiKey}` }, fetchImpl)) };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (providerId === 'anthropic') {
|
|
40
|
+
const apiKey = resolveFirstSecret(['ANTHROPIC_API_KEY'], secretOpts);
|
|
41
|
+
if (!apiKey) return { ok: false, code: 'missing_key', status: null, provider: providerId };
|
|
42
|
+
return {
|
|
43
|
+
provider: providerId,
|
|
44
|
+
...(await probeGet(ANTHROPIC_MODELS, {
|
|
45
|
+
'x-api-key': apiKey,
|
|
46
|
+
'anthropic-version': '2023-06-01',
|
|
47
|
+
}, fetchImpl)),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (providerId === 'openai') {
|
|
52
|
+
const apiKey = resolveFirstSecret(['OPENAI_API_KEY'], secretOpts);
|
|
53
|
+
if (!apiKey) return { ok: false, code: 'missing_key', status: null, provider: providerId };
|
|
54
|
+
return { provider: providerId, ...(await probeGet(OPENAI_MODELS, { Authorization: `Bearer ${apiKey}` }, fetchImpl)) };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return { ok: false, code: 'unsupported_provider', status: null, provider: providerId };
|
|
58
|
+
}
|
|
@@ -283,7 +283,7 @@ export function __resetCopilotCache() {
|
|
|
283
283
|
sessionCache = null;
|
|
284
284
|
}
|
|
285
285
|
|
|
286
|
-
// Exchange probe used at
|
|
286
|
+
// Exchange probe used at host launch — a configured Copilot pin is not enough if
|
|
287
287
|
// the session token cannot be minted (stale oauth, whitespace-corrupt store).
|
|
288
288
|
|
|
289
289
|
export async function preflightCopilotSession({ fetchImpl = fetch } = {}) {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* lib/providers/credential-bootstrap.mjs — lazy credential linking for Construct.
|
|
3
3
|
*
|
|
4
4
|
* Before any LLM surface runs, missing API keys can be satisfied from existing
|
|
5
|
-
* stores or a one-time 1Password link into
|
|
5
|
+
* stores or a one-time 1Password link into the XDG config.env. Runs at
|
|
6
6
|
* the construct CLI entrypoint in presence-check mode; op item list runs only
|
|
7
7
|
* when autoLink:true (scripts/setup-credentials.mjs).
|
|
8
8
|
*/
|
package/lib/providers/op-run.mjs
CHANGED
|
@@ -2,12 +2,11 @@
|
|
|
2
2
|
* lib/providers/op-run.mjs — optionally launch a child under 1Password `op run`.
|
|
3
3
|
*
|
|
4
4
|
* When CONSTRUCT_OP_ENV_FILE points to an `op run` env-file (KEY=op://… lines)
|
|
5
|
-
* and the `op` CLI is installed, long-lived Construct services
|
|
6
|
-
* daemon that runs chat turns — are spawned through
|
|
5
|
+
* and the `op` CLI is installed, long-lived Construct services are spawned through
|
|
7
6
|
* `op run --no-masking --env-file <file> -- <cmd>`, so op:// references resolve
|
|
8
7
|
* once at startup into the process env. This mirrors the per-invocation model
|
|
9
|
-
* other LLM CLIs use (e.g. `op run -- opencode`) and
|
|
10
|
-
*
|
|
8
|
+
* other LLM CLIs use (e.g. `op run -- opencode`) and gives child processes
|
|
9
|
+
* resolved provider keys without a shell wrapper.
|
|
11
10
|
*
|
|
12
11
|
* Strictly opt-in: with the var unset, the file missing, or `op` absent, the
|
|
13
12
|
* command is returned unchanged and 1Password is never invoked. No other setup
|
|
@@ -109,6 +109,35 @@ function readShellRcVar(varName, home) {
|
|
|
109
109
|
return null;
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
+
function expandHomePath(file, home) {
|
|
113
|
+
if (file === '~') return home;
|
|
114
|
+
if (file.startsWith('~/')) return `${home}${file.slice(1)}`;
|
|
115
|
+
return file;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function readOpEnvCatalogPath({ env, home }) {
|
|
119
|
+
const pointerSources = [
|
|
120
|
+
env?.CONSTRUCT_OP_ENV_FILE,
|
|
121
|
+
readDotenvVar(path.join(configDir(home), 'config.env'), 'CONSTRUCT_OP_ENV_FILE'),
|
|
122
|
+
readDotenvVar(path.join(home, '.construct', 'config.env'), 'CONSTRUCT_OP_ENV_FILE'),
|
|
123
|
+
].filter((value) => typeof value === 'string' && value.length > 0);
|
|
124
|
+
for (const raw of pointerSources) {
|
|
125
|
+
const file = expandHomePath(unquote(raw), home);
|
|
126
|
+
try {
|
|
127
|
+
if (fs.existsSync(file)) return file;
|
|
128
|
+
} catch {
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function readFromOpEnvCatalog(varName, { env, home }) {
|
|
136
|
+
const catalogPath = readOpEnvCatalogPath({ env, home });
|
|
137
|
+
if (!catalogPath) return null;
|
|
138
|
+
return readDotenvVar(catalogPath, varName);
|
|
139
|
+
}
|
|
140
|
+
|
|
112
141
|
function rawCandidate(varName, { env, cwd, home }) {
|
|
113
142
|
const direct = env?.[varName];
|
|
114
143
|
if (typeof direct === 'string' && direct.length > 0) return direct;
|
|
@@ -119,6 +148,8 @@ function rawCandidate(varName, { env, cwd, home }) {
|
|
|
119
148
|
}
|
|
120
149
|
const alt = discoverAlternateRawForVar(varName, { home });
|
|
121
150
|
if (alt) return alt;
|
|
151
|
+
const fromOpCatalog = readFromOpEnvCatalog(varName, { env, home });
|
|
152
|
+
if (fromOpCatalog) return fromOpCatalog;
|
|
122
153
|
return readShellRcVar(varName, home);
|
|
123
154
|
}
|
|
124
155
|
|
package/lib/publish-tooling.mjs
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* lib/publish-tooling.mjs — detect optional binaries for the publish pipeline.
|
|
3
3
|
*
|
|
4
|
-
* Composes export (Pandoc/Typst), diagram (D2/mmdc), terminal demo (VHS)
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Composes export (Pandoc/Typst), diagram (D2/mmdc), and terminal demo (VHS)
|
|
5
|
+
* probes for `construct tools detect`, `construct publish --strict`, and doctor
|
|
6
|
+
* one-shot checks.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import fs from 'node:fs';
|
|
@@ -13,7 +13,6 @@ import { fileURLToPath } from 'node:url';
|
|
|
13
13
|
import { detect as detectExport } from './document-export.mjs';
|
|
14
14
|
import { locateRenderer } from './diagram.mjs';
|
|
15
15
|
import { locateRecorder } from './demo.mjs';
|
|
16
|
-
import { detectDashboardDemo } from './dashboard-demo.mjs';
|
|
17
16
|
import { detectIngestPipeline } from './ingest-tooling.mjs';
|
|
18
17
|
|
|
19
18
|
const REPO_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
@@ -56,7 +55,6 @@ export function detectPublishPipeline({
|
|
|
56
55
|
format = 'pdf',
|
|
57
56
|
includeFigures = true,
|
|
58
57
|
includeTerminalDemo = false,
|
|
59
|
-
includeDashboardDemo = false,
|
|
60
58
|
includeIngest = true,
|
|
61
59
|
cwd = process.cwd(),
|
|
62
60
|
repoRoot = REPO_ROOT,
|
|
@@ -89,12 +87,6 @@ export function detectPublishPipeline({
|
|
|
89
87
|
if (!recorder) missing.push('vhs');
|
|
90
88
|
}
|
|
91
89
|
|
|
92
|
-
if (includeDashboardDemo) {
|
|
93
|
-
const dashboard = detectDashboardDemo({ cwd, repoRoot: REPO_ROOT });
|
|
94
|
-
steps.dashboardDemo = dashboard;
|
|
95
|
-
if (!dashboard.present) missing.push(...dashboard.missing);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
90
|
const uniqueMissing = [...new Set(missing)];
|
|
99
91
|
return {
|
|
100
92
|
ok: true,
|