@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
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/config/legacy-config-migration.mjs — one-time forward migration from the
|
|
3
|
+
* pre-XDG ~/.construct/config.env into the active XDG config dir.
|
|
4
|
+
*
|
|
5
|
+
* Model tier overrides and credential pointers/keys stranded by the clean break
|
|
6
|
+
* (ADR-0045) are mirrored forward without overwriting values already in XDG.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import fs from 'node:fs';
|
|
10
|
+
import os from 'node:os';
|
|
11
|
+
import path from 'node:path';
|
|
12
|
+
|
|
13
|
+
import { parseEnvFile, writeEnvValues } from '../env-config.mjs';
|
|
14
|
+
import { API_KEY_CREDENTIALS } from '../providers/credential-catalog.mjs';
|
|
15
|
+
import { configDir } from './xdg.mjs';
|
|
16
|
+
|
|
17
|
+
const MIGRATABLE_MODEL_KEY = /^(?:CX|CONSTRUCT)_MODEL_[A-Z0-9_]+$/;
|
|
18
|
+
const MIGRATABLE_POINTER_KEYS = new Set(['CONSTRUCT_OP_ENV_FILE']);
|
|
19
|
+
const CREDENTIAL_ENV_KEYS = new Set(
|
|
20
|
+
API_KEY_CREDENTIALS.flatMap((entry) => entry.envVars),
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
export function legacyConfigPath(homeDir = os.homedir()) {
|
|
24
|
+
return path.join(homeDir, '.construct', 'config.env');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function xdgConfigPath(homeDir, env) {
|
|
28
|
+
return path.join(configDir(homeDir, env), 'config.env');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function expandHomePath(file, home) {
|
|
32
|
+
if (file === '~') return home;
|
|
33
|
+
if (file.startsWith('~/')) return `${home}${file.slice(1)}`;
|
|
34
|
+
return file;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function migrateKeysFromLegacy({ legacyEnv, xdgEnv, keyFilter }) {
|
|
38
|
+
const migrated = {};
|
|
39
|
+
for (const [key, value] of Object.entries(legacyEnv)) {
|
|
40
|
+
if (!keyFilter(key)) continue;
|
|
41
|
+
if (value === '' || value === undefined) continue;
|
|
42
|
+
if (xdgEnv[key] !== undefined && xdgEnv[key] !== '') continue;
|
|
43
|
+
migrated[key] = value;
|
|
44
|
+
}
|
|
45
|
+
return migrated;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function backfillFromOpEnvCatalog({ migrated, xdgEnv, homeDir }) {
|
|
49
|
+
const pointer = migrated.CONSTRUCT_OP_ENV_FILE
|
|
50
|
+
|| xdgEnv.CONSTRUCT_OP_ENV_FILE
|
|
51
|
+
|| legacyEnvPointer(homeDir);
|
|
52
|
+
if (!pointer) return migrated;
|
|
53
|
+
|
|
54
|
+
const catalogPath = expandHomePath(pointer.replace(/^["']|["']$/g, ''), homeDir);
|
|
55
|
+
if (!fs.existsSync(catalogPath)) return migrated;
|
|
56
|
+
|
|
57
|
+
const catalog = parseEnvFile(catalogPath);
|
|
58
|
+
const next = { ...migrated };
|
|
59
|
+
for (const key of CREDENTIAL_ENV_KEYS) {
|
|
60
|
+
if (xdgEnv[key] || next[key]) continue;
|
|
61
|
+
if (!catalog[key]) continue;
|
|
62
|
+
next[key] = catalog[key];
|
|
63
|
+
}
|
|
64
|
+
return next;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function legacyEnvPointer(homeDir) {
|
|
68
|
+
const legacy = parseEnvFile(legacyConfigPath(homeDir));
|
|
69
|
+
return legacy.CONSTRUCT_OP_ENV_FILE || null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Migrate stranded CX_MODEL_ and CONSTRUCT_MODEL_ tier overrides from legacy
|
|
74
|
+
* config into XDG config.env.
|
|
75
|
+
*/
|
|
76
|
+
export function migrateLegacyModelConfig({ homeDir = os.homedir(), env = process.env } = {}) {
|
|
77
|
+
const legacyPath = legacyConfigPath(homeDir);
|
|
78
|
+
const xdgPath = xdgConfigPath(homeDir, env);
|
|
79
|
+
const result = { performed: false, migrated: {}, legacyPath, xdgPath };
|
|
80
|
+
|
|
81
|
+
if (legacyPath === xdgPath || !fs.existsSync(legacyPath)) return result;
|
|
82
|
+
|
|
83
|
+
const legacyEnv = parseEnvFile(legacyPath);
|
|
84
|
+
const xdgEnv = parseEnvFile(xdgPath);
|
|
85
|
+
const migrated = migrateKeysFromLegacy({
|
|
86
|
+
legacyEnv,
|
|
87
|
+
xdgEnv,
|
|
88
|
+
keyFilter: (key) => MIGRATABLE_MODEL_KEY.test(key),
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
if (Object.keys(migrated).length === 0) return result;
|
|
92
|
+
|
|
93
|
+
writeEnvValues(xdgPath, migrated);
|
|
94
|
+
result.performed = true;
|
|
95
|
+
result.migrated = migrated;
|
|
96
|
+
return result;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Migrate CONSTRUCT_OP_ENV_FILE and API-key env vars from legacy config, then
|
|
101
|
+
* backfill any still-missing credential keys from the referenced op-run catalog.
|
|
102
|
+
*/
|
|
103
|
+
export function migrateLegacyCredentialConfig({ homeDir = os.homedir(), env = process.env } = {}) {
|
|
104
|
+
const legacyPath = legacyConfigPath(homeDir);
|
|
105
|
+
const xdgPath = xdgConfigPath(homeDir, env);
|
|
106
|
+
const result = { performed: false, migrated: {}, legacyPath, xdgPath };
|
|
107
|
+
|
|
108
|
+
if (legacyPath === xdgPath || !fs.existsSync(legacyPath)) return result;
|
|
109
|
+
|
|
110
|
+
const legacyEnv = parseEnvFile(legacyPath);
|
|
111
|
+
const xdgEnv = parseEnvFile(xdgPath);
|
|
112
|
+
let migrated = migrateKeysFromLegacy({
|
|
113
|
+
legacyEnv,
|
|
114
|
+
xdgEnv,
|
|
115
|
+
keyFilter: (key) => MIGRATABLE_POINTER_KEYS.has(key) || CREDENTIAL_ENV_KEYS.has(key),
|
|
116
|
+
});
|
|
117
|
+
migrated = backfillFromOpEnvCatalog({ migrated, xdgEnv, homeDir });
|
|
118
|
+
|
|
119
|
+
if (Object.keys(migrated).length === 0) return result;
|
|
120
|
+
|
|
121
|
+
writeEnvValues(xdgPath, migrated);
|
|
122
|
+
result.performed = true;
|
|
123
|
+
result.migrated = migrated;
|
|
124
|
+
return result;
|
|
125
|
+
}
|
package/lib/config/schema.mjs
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*
|
|
8
8
|
* Schema v1 covers: alias, deployment, providers, sources, intakePolicy,
|
|
9
9
|
* autoEmbed, ingest, telemetry.
|
|
10
|
-
* Tier model selection lives in `specialists/
|
|
10
|
+
* Tier model selection lives in `specialists/org` only — this config
|
|
11
11
|
* file is intentionally not a second edit surface for model assignments.
|
|
12
12
|
* Later phases (6, 7b) extend it with `resources` and `costs` blocks
|
|
13
13
|
* without bumping the version — additive keys are allowed.
|
|
@@ -21,7 +21,7 @@ export const CONFIG_SCHEMA_VERSION = 1;
|
|
|
21
21
|
|
|
22
22
|
export const DEPLOYMENT_MODES = ['solo', 'team', 'enterprise'];
|
|
23
23
|
export const MCP_BROKER_VALUES = ['auto', 'on', 'off'];
|
|
24
|
-
export const
|
|
24
|
+
export const DEFAULT_SCOPE_ID = 'rnd';
|
|
25
25
|
|
|
26
26
|
export const SURFACES = ['claude', 'opencode', 'codex', 'copilot', 'vscode', 'cursor'];
|
|
27
27
|
|
|
@@ -59,7 +59,7 @@ export const DEFAULT_PROJECT_CONFIG = Object.freeze({
|
|
|
59
59
|
defaults: Object.freeze({}),
|
|
60
60
|
types: Object.freeze({}),
|
|
61
61
|
}),
|
|
62
|
-
|
|
62
|
+
scope: DEFAULT_SCOPE_ID,
|
|
63
63
|
autoEmbed: false,
|
|
64
64
|
ingest: Object.freeze({
|
|
65
65
|
strategy: 'adapter',
|
|
@@ -79,7 +79,7 @@ export const DEFAULT_PROJECT_CONFIG = Object.freeze({
|
|
|
79
79
|
}),
|
|
80
80
|
models: Object.freeze({
|
|
81
81
|
visibility: Object.freeze({
|
|
82
|
-
mode: '
|
|
82
|
+
mode: 'tier_defaults',
|
|
83
83
|
include: [],
|
|
84
84
|
exclude: [],
|
|
85
85
|
providers: {},
|
|
@@ -156,7 +156,7 @@ export const FIELD_RULES = {
|
|
|
156
156
|
types: { type: 'object', required: false },
|
|
157
157
|
},
|
|
158
158
|
},
|
|
159
|
-
|
|
159
|
+
scope: { type: 'string', required: false, maxLength: 40 },
|
|
160
160
|
autoEmbed: { type: 'boolean', required: false },
|
|
161
161
|
ingest: {
|
|
162
162
|
type: 'object',
|
|
@@ -309,3 +309,83 @@ export function resolveKnownSourcesFromTargets(targets) {
|
|
|
309
309
|
|
|
310
310
|
return sources;
|
|
311
311
|
}
|
|
312
|
+
|
|
313
|
+
// Team bindings take precedence over project config, which takes precedence over
|
|
314
|
+
// legacy env. Resolution is by typed selector signature — no substring guessing —
|
|
315
|
+
// so a team's effective sources are stable and conflict-free.
|
|
316
|
+
|
|
317
|
+
export function resolveTeamSources(teamId, { registry, config, env = process.env } = {}) {
|
|
318
|
+
const team = registry?.teams?.[teamId];
|
|
319
|
+
const teamTargets = Array.isArray(team?.sources)
|
|
320
|
+
? team.sources.map((s) => ({
|
|
321
|
+
...normalizeConfigTarget(s),
|
|
322
|
+
provenance: `team:${teamId}`,
|
|
323
|
+
...(s.filters && typeof s.filters === 'object' ? { filters: s.filters } : {}),
|
|
324
|
+
}))
|
|
325
|
+
: [];
|
|
326
|
+
const configTargets = (config?.sources?.targets ?? []).map(normalizeConfigTarget);
|
|
327
|
+
const envTargets = legacyEnvSourceTargets(env);
|
|
328
|
+
return mergeSourceTargets(teamTargets, mergeSourceTargets(configTargets, envTargets));
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Per-target embed records that honor each target's own filters (jira jql,
|
|
332
|
+
// github refs/limit, slack oldest) and tag the record with the owning teamId and
|
|
333
|
+
// targetId so observations stay retrievable by team scope rather than a global
|
|
334
|
+
// pool. Falls back to the same defaults as targetsToEmbedSources when a target
|
|
335
|
+
// declares no filters.
|
|
336
|
+
|
|
337
|
+
export function targetsToEmbedSourcesWithFilters(targets, { teamId = null } = {}) {
|
|
338
|
+
const sources = [];
|
|
339
|
+
const tag = (rec, target) => ({
|
|
340
|
+
...rec,
|
|
341
|
+
...(teamId ? { teamId } : {}),
|
|
342
|
+
targetId: target.id,
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
for (const target of targets) {
|
|
346
|
+
const f = target.filters ?? {};
|
|
347
|
+
const sel = target.selector ?? {};
|
|
348
|
+
switch (target.provider) {
|
|
349
|
+
case 'github':
|
|
350
|
+
sources.push(tag({
|
|
351
|
+
provider: 'github',
|
|
352
|
+
repos: [sel.repo],
|
|
353
|
+
refs: Array.isArray(f.refs) ? f.refs : ['prs', 'issues', 'commits'],
|
|
354
|
+
limit: Number.isInteger(f.limit) ? f.limit : 25,
|
|
355
|
+
}, target));
|
|
356
|
+
break;
|
|
357
|
+
case 'jira':
|
|
358
|
+
sources.push(tag({
|
|
359
|
+
provider: 'jira',
|
|
360
|
+
project: sel.project,
|
|
361
|
+
refs: ['issues'],
|
|
362
|
+
...(typeof f.jql === 'string' ? { jql: f.jql } : {}),
|
|
363
|
+
limit: Number.isInteger(f.limit) ? f.limit : 50,
|
|
364
|
+
}, target));
|
|
365
|
+
break;
|
|
366
|
+
case 'linear':
|
|
367
|
+
sources.push(tag({
|
|
368
|
+
provider: 'linear',
|
|
369
|
+
team: sel.team,
|
|
370
|
+
refs: ['issues'],
|
|
371
|
+
...(Array.isArray(f.states) ? { states: f.states } : {}),
|
|
372
|
+
limit: Number.isInteger(f.limit) ? f.limit : 50,
|
|
373
|
+
}, target));
|
|
374
|
+
break;
|
|
375
|
+
case 'slack':
|
|
376
|
+
sources.push(tag({
|
|
377
|
+
provider: 'slack',
|
|
378
|
+
channels: [sel.channel],
|
|
379
|
+
intent: sel.intent ?? 'internal',
|
|
380
|
+
refs: ['messages'],
|
|
381
|
+
oldest: Number.isInteger(f.oldest) ? f.oldest : 86400,
|
|
382
|
+
limit: Number.isInteger(f.limit) ? f.limit : 50,
|
|
383
|
+
}, target));
|
|
384
|
+
break;
|
|
385
|
+
default:
|
|
386
|
+
break;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
return sources;
|
|
391
|
+
}
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* lib/contracts/validate.mjs — Validate specialists/contracts
|
|
2
|
+
* lib/contracts/validate.mjs — Validate specialists/org contracts at sync time + at handoff.
|
|
3
3
|
*
|
|
4
4
|
* Three validation tiers:
|
|
5
|
-
* 1. Schema shape:
|
|
6
|
-
* validator — top-level required fields, contract entry required fields).
|
|
5
|
+
* 1. Schema shape: unified registry conforms to unified-registry.schema.json.
|
|
7
6
|
* 2. Cross-file refs: every output.schema points to a real file in lib/contract-schemas/,
|
|
8
|
-
* every producer/consumer name resolves to
|
|
9
|
-
*
|
|
7
|
+
* every producer/consumer name resolves to a specialist in the unified registry,
|
|
8
|
+
* every well-known event/intake string is reachable.
|
|
10
9
|
* 3. Runtime handoff: a single artifact validated against the schema referenced
|
|
11
10
|
* by a producer→consumer contract, with mustContain post-conditions.
|
|
12
11
|
*
|
|
@@ -25,13 +24,11 @@ import {
|
|
|
25
24
|
import { inferArtifactTypeFromPath } from '../artifact-type-from-path.mjs';
|
|
26
25
|
import { POSTCONDITIONS, validateBinaryPostconditions } from '../specialists/postconditions.mjs';
|
|
27
26
|
import { logViolation } from './violation-log.mjs';
|
|
27
|
+
import { loadRegistry } from '../registry/loader.mjs';
|
|
28
28
|
|
|
29
29
|
const REPO_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..', '..');
|
|
30
|
-
const CONTRACTS_PATH = join(REPO_ROOT, 'specialists', 'contracts.json');
|
|
31
|
-
const CONTRACTS_SCHEMA_PATH = join(REPO_ROOT, 'specialists', 'contracts.schema.json');
|
|
32
|
-
const REGISTRY_PATH = join(REPO_ROOT, 'specialists', 'registry.json');
|
|
33
30
|
|
|
34
|
-
const WELL_KNOWN_PRODUCERS = new Set(['user', 'oncall', 'incident-system', '*']);
|
|
31
|
+
const WELL_KNOWN_PRODUCERS = new Set(['user', 'oncall', 'incident-system', 'construct', '*']);
|
|
35
32
|
const WELL_KNOWN_CONSUMERS = new Set(['user', 'construct']);
|
|
36
33
|
|
|
37
34
|
function readJson(path) {
|
|
@@ -39,40 +36,50 @@ function readJson(path) {
|
|
|
39
36
|
}
|
|
40
37
|
|
|
41
38
|
/**
|
|
42
|
-
* Validate
|
|
43
|
-
*
|
|
39
|
+
* Validate the unified registry's contracts (shape + cross-file references).
|
|
40
|
+
* Reads registry.contracts via loadRegistry; an explicit contractsPath
|
|
41
|
+
* overrides for legacy/standalone files. Returns { ok, errors[] } — errors
|
|
42
|
+
* is empty on success.
|
|
44
43
|
*/
|
|
45
44
|
export function validateContractsFile({ contractsPath, registryPath, repoRoot = REPO_ROOT } = {}) {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
45
|
+
const errors = [];
|
|
46
|
+
|
|
47
|
+
// Load contracts from unified registry or explicit path
|
|
48
|
+
const registry = loadRegistry();
|
|
49
|
+
const specialistIds = new Set(Object.keys(registry.specialists));
|
|
50
|
+
|
|
51
|
+
let contractsArray;
|
|
52
|
+
if (contractsPath) {
|
|
53
|
+
const cPath = contractsPath;
|
|
54
|
+
const schemaPath = join(repoRoot, 'specialists', 'contracts.schema.json');
|
|
55
|
+
if (!existsSync(cPath)) {
|
|
56
|
+
return { ok: false, errors: [`contracts file not found: ${cPath}`] };
|
|
57
|
+
}
|
|
58
|
+
if (!existsSync(schemaPath)) {
|
|
59
|
+
errors.push(`contracts schema missing: ${schemaPath}`);
|
|
60
|
+
}
|
|
61
|
+
const raw = readJson(cPath);
|
|
62
|
+
for (const key of ['version', 'terminalStates', 'severities', 'contracts']) {
|
|
63
|
+
if (!(key in raw)) errors.push(`contracts file missing top-level field: ${key}`);
|
|
64
|
+
}
|
|
65
|
+
contractsArray = raw.contracts || [];
|
|
66
|
+
} else {
|
|
67
|
+
// From unified registry - normalize contracts to array format
|
|
68
|
+
contractsArray = Object.values(registry.contracts || {});
|
|
69
|
+
// Support old-format contracts object with .contracts array
|
|
70
|
+
if (Array.isArray(contractsArray) && contractsArray.length === 0 && registry.contracts?.contracts) {
|
|
71
|
+
contractsArray = registry.contracts.contracts;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const knownNames = registryPath
|
|
76
|
+
? collectAgentNames(registryPath)
|
|
77
|
+
: new Set(Object.keys(registry.specialists).concat(Object.values(registry.specialists).map(s => s.name)));
|
|
78
|
+
|
|
79
|
+
if (Array.isArray(contractsArray)) {
|
|
80
|
+
const ids = new Set();
|
|
81
|
+
contractsArray.forEach((c, idx) => {
|
|
82
|
+
const where = `contracts[${idx}]${c.id ? ` (${c.id})` : ''}`;
|
|
76
83
|
if (!c.id) errors.push(`${where}: missing id`);
|
|
77
84
|
else if (!/^[a-z0-9][a-z0-9-]*$/.test(c.id)) errors.push(`${where}: id must be kebab-case`);
|
|
78
85
|
else if (ids.has(c.id)) errors.push(`${where}: duplicate id`);
|
|
@@ -124,7 +131,7 @@ function collectAgentNames(registryPath) {
|
|
|
124
131
|
const registry = readJson(registryPath);
|
|
125
132
|
if (registry.orchestrator?.name) names.add(registry.orchestrator.name);
|
|
126
133
|
if (registry.orchestrator?.displayName) names.add(registry.orchestrator.displayName);
|
|
127
|
-
for (const s of registry.specialists ||
|
|
134
|
+
for (const s of Object.values(registry.specialists || {})) {
|
|
128
135
|
if (s?.name) names.add(s.name);
|
|
129
136
|
if (s?.displayName) names.add(s.displayName);
|
|
130
137
|
}
|
|
@@ -135,14 +142,21 @@ function collectAgentNames(registryPath) {
|
|
|
135
142
|
/**
|
|
136
143
|
* Look up a contract by producer/consumer pair (and optional id).
|
|
137
144
|
*/
|
|
138
|
-
export function findContract({ producer, consumer, id, contractsPath
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
145
|
+
export function findContract({ producer, consumer, id, contractsPath }) {
|
|
146
|
+
if (contractsPath) {
|
|
147
|
+
if (!existsSync(contractsPath)) return null;
|
|
148
|
+
try {
|
|
149
|
+
const contracts = readJson(contractsPath);
|
|
150
|
+
const list = contracts.contracts || [];
|
|
151
|
+
if (id) return list.find((c) => c.id === id) || null;
|
|
152
|
+
return list.find((c) => c.producer === producer && c.consumer === consumer) || null;
|
|
153
|
+
} catch { return null; }
|
|
154
|
+
}
|
|
155
|
+
// Default: use unified registry
|
|
156
|
+
const registry = loadRegistry();
|
|
157
|
+
if (id) return registry.contracts[id] || null;
|
|
158
|
+
return Object.values(registry.contracts).find((c) => c.producer === producer && c.consumer === consumer) || null;
|
|
159
|
+
}
|
|
146
160
|
|
|
147
161
|
/**
|
|
148
162
|
* Validate a single artifact against its contract at handoff time.
|
|
@@ -168,7 +182,7 @@ export function validateHandoff(opts = {}) {
|
|
|
168
182
|
id,
|
|
169
183
|
artifact,
|
|
170
184
|
packet,
|
|
171
|
-
contractsPath
|
|
185
|
+
contractsPath,
|
|
172
186
|
repoRoot = REPO_ROOT,
|
|
173
187
|
enforcement = 'block',
|
|
174
188
|
} = opts;
|
|
@@ -216,6 +230,14 @@ export function validateHandoff(opts = {}) {
|
|
|
216
230
|
: { ok: true, warnings: [`no contract found for ${producer}→${consumer}`], contract: null };
|
|
217
231
|
}
|
|
218
232
|
|
|
233
|
+
// Team boundary validation: check if cross-team handoff requires approvals.
|
|
234
|
+
if (contract.teamBoundary?.crosses && Array.isArray(contract.teamBoundary.requiresApprovalFrom)) {
|
|
235
|
+
const teamBoundaryErrors = validateTeamBoundary(contract);
|
|
236
|
+
for (const err of teamBoundaryErrors) {
|
|
237
|
+
errors.push(err);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
219
241
|
// A handoff carries a producer's output, which is the consumer's input. The
|
|
220
242
|
// input contract is what the consumer expects to receive; check mustContain
|
|
221
243
|
// against that first. Output.schema validation only applies when the artifact
|
|
@@ -282,6 +304,52 @@ function hasField(obj, field) {
|
|
|
282
304
|
return Object.prototype.hasOwnProperty.call(obj, field) && obj[field] != null && obj[field] !== '';
|
|
283
305
|
}
|
|
284
306
|
|
|
307
|
+
/**
|
|
308
|
+
* Validate team boundary enforcement on a contract.
|
|
309
|
+
* Returns array of error strings (empty if validation passes).
|
|
310
|
+
*
|
|
311
|
+
* Team boundaries are declarative metadata that enforce organizational
|
|
312
|
+
* separation of concerns. A contract.teamBoundary.requiresApprovalFrom
|
|
313
|
+
* lists teams that must explicitly approve before the handoff can complete.
|
|
314
|
+
*/
|
|
315
|
+
function validateTeamBoundary(contract = {}) {
|
|
316
|
+
const errors = [];
|
|
317
|
+
const boundary = contract.teamBoundary || {};
|
|
318
|
+
|
|
319
|
+
if (!boundary.crosses) return errors;
|
|
320
|
+
if (!Array.isArray(boundary.requiresApprovalFrom) || boundary.requiresApprovalFrom.length === 0) {
|
|
321
|
+
return errors;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Check that producer and consumer teams are declared.
|
|
325
|
+
if (!boundary.producerTeam) {
|
|
326
|
+
errors.push(`team boundary marked as crossing but producerTeam is missing in contract ${contract.id}`);
|
|
327
|
+
}
|
|
328
|
+
if (!boundary.consumerTeam) {
|
|
329
|
+
errors.push(`team boundary marked as crossing but consumerTeam is missing in contract ${contract.id}`);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// Load registry to verify team ids exist.
|
|
333
|
+
try {
|
|
334
|
+
const registry = loadRegistry();
|
|
335
|
+
for (const teamId of boundary.requiresApprovalFrom) {
|
|
336
|
+
if (!registry.teams?.[teamId]) {
|
|
337
|
+
errors.push(`team boundary references unknown team: ${teamId} (in contract ${contract.id})`);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
if (boundary.producerTeam && !registry.teams?.[boundary.producerTeam]) {
|
|
341
|
+
errors.push(`contract ${contract.id}: producerTeam '${boundary.producerTeam}' not found in registry`);
|
|
342
|
+
}
|
|
343
|
+
if (boundary.consumerTeam && !registry.teams?.[boundary.consumerTeam]) {
|
|
344
|
+
errors.push(`contract ${contract.id}: consumerTeam '${boundary.consumerTeam}' not found in registry`);
|
|
345
|
+
}
|
|
346
|
+
} catch (err) {
|
|
347
|
+
errors.push(`failed to validate team boundary for contract ${contract.id}: ${err.message}`);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
return errors;
|
|
351
|
+
}
|
|
352
|
+
|
|
285
353
|
const FRONTMATTER_RE = /^---\n([\s\S]*?)\n---\n?/;
|
|
286
354
|
|
|
287
355
|
function parseArtifactFrontmatter(text) {
|
package/lib/decisions/golden.mjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { loadRegistry } from '../registry/loader.mjs';
|
|
1
2
|
/**
|
|
2
3
|
* lib/decisions/golden.mjs — behavioral golden snapshot of core surfaces.
|
|
3
4
|
*
|
|
@@ -38,8 +39,8 @@ export function buildSurfaceSnapshot({ repoRoot = REPO_ROOT } = {}) {
|
|
|
38
39
|
}
|
|
39
40
|
snapshot.commands.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
|
|
40
41
|
|
|
41
|
-
const registry =
|
|
42
|
-
snapshot.agents = (registry.specialists ||
|
|
42
|
+
const registry = loadRegistry({ rootDir: repoRoot });
|
|
43
|
+
snapshot.agents = Object.values(registry.specialists || {}).map((s) => s.name).sort();
|
|
43
44
|
|
|
44
45
|
const settings = JSON.parse(readFileSync(join(repoRoot, 'platforms', 'claude', 'settings.template.json'), 'utf8'));
|
|
45
46
|
for (const event of HOOK_EVENTS) {
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*
|
|
9
9
|
* Three decision sources are indexed:
|
|
10
10
|
* - ADRs docs/decisions/adr/*.md (status + supersedes parsed from body)
|
|
11
|
-
* - Contracts specialists/
|
|
11
|
+
* - Contracts specialists/org (executable postconditions = enforcement)
|
|
12
12
|
* - Rules rules/**\/*.md (enforcement link via frontmatter; bead wvbf.4)
|
|
13
13
|
*
|
|
14
14
|
* Enforcement bindings are discovered by scanning tests/ and lib/hooks/ for an
|
|
@@ -24,6 +24,7 @@ import { join, dirname, resolve, relative } from 'node:path';
|
|
|
24
24
|
import { fileURLToPath } from 'node:url';
|
|
25
25
|
|
|
26
26
|
import { readRuleFile } from '../rules-read.mjs';
|
|
27
|
+
import { loadRegistry } from '../registry/loader.mjs';
|
|
27
28
|
|
|
28
29
|
export const DECISION_STATUSES = ['proposed', 'accepted', 'superseded', 'deprecated'];
|
|
29
30
|
|
|
@@ -152,11 +153,15 @@ function adrDecisions({ repoRoot, bindings }) {
|
|
|
152
153
|
}
|
|
153
154
|
|
|
154
155
|
function contractDecisions({ repoRoot, bindings }) {
|
|
155
|
-
const
|
|
156
|
-
|
|
157
|
-
|
|
156
|
+
const registryFile = join('specialists', 'org');
|
|
157
|
+
let contractsArray;
|
|
158
|
+
try {
|
|
159
|
+
contractsArray = Object.values(loadRegistry({ rootDir: repoRoot, skipValidation: true }).contracts || {});
|
|
160
|
+
} catch {
|
|
161
|
+
return [];
|
|
162
|
+
}
|
|
158
163
|
const out = [];
|
|
159
|
-
for (const c of
|
|
164
|
+
for (const c of contractsArray) {
|
|
160
165
|
const checks = [];
|
|
161
166
|
let advisoryPostconditions = 0;
|
|
162
167
|
for (const pc of c.postconditions || []) {
|
|
@@ -171,7 +176,7 @@ function contractDecisions({ repoRoot, bindings }) {
|
|
|
171
176
|
status: 'accepted',
|
|
172
177
|
supersedes: null,
|
|
173
178
|
supersededBy: null,
|
|
174
|
-
file:
|
|
179
|
+
file: registryFile,
|
|
175
180
|
lastVerifiedAt: null,
|
|
176
181
|
enforcingTests,
|
|
177
182
|
enforcingChecks: checks,
|