@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/roles/fence.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* lib/roles/fence.mjs — pure action-vs-manifest fence check.
|
|
2
|
+
* lib/roles/fence.mjs — pure action-vs-manifest fence check with team awareness.
|
|
3
3
|
*
|
|
4
4
|
* Invoked from the policy-engine and edit-guard hooks when a session is
|
|
5
5
|
* tagged with a persona. Glob patterns use simple ** / * matching (no full
|
|
@@ -9,6 +9,9 @@
|
|
|
9
9
|
* - allowed: target is inside the persona's allowedPaths/Commands/Labels
|
|
10
10
|
* - approval: target matches an entry in approvalRequired (needs user yes)
|
|
11
11
|
* - denied: neither — default deny
|
|
12
|
+
*
|
|
13
|
+
* Team awareness: A specialist fence is the intersection of its team fence
|
|
14
|
+
* and its own fence — never broader than the team's boundary.
|
|
12
15
|
*/
|
|
13
16
|
|
|
14
17
|
import { loadManifest } from './manifest.mjs';
|
|
@@ -35,9 +38,112 @@ function actionPrefix(action) {
|
|
|
35
38
|
return idx === -1 ? action : action.slice(0, idx);
|
|
36
39
|
}
|
|
37
40
|
|
|
41
|
+
/**
|
|
42
|
+
* Find the team that owns a specialist.
|
|
43
|
+
* Returns the team object if found, null otherwise.
|
|
44
|
+
* Note: Uses dynamic import to avoid circular dependencies.
|
|
45
|
+
*/
|
|
46
|
+
async function findTeamForSpecialist(personaId) {
|
|
47
|
+
try {
|
|
48
|
+
const { loadRegistry } = await import('../registry/loader.mjs');
|
|
49
|
+
const registry = loadRegistry();
|
|
50
|
+
for (const team of Object.values(registry.teams || {})) {
|
|
51
|
+
if (Array.isArray(team.roles) && team.roles.includes(personaId)) {
|
|
52
|
+
return team;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
} catch {
|
|
56
|
+
// Ignore errors; return null on any failure
|
|
57
|
+
}
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Synchronous fallback to find team without async.
|
|
63
|
+
* Used when dynamic import is not available.
|
|
64
|
+
*/
|
|
65
|
+
function findTeamForSpecialistSync(personaId, registry) {
|
|
66
|
+
if (!registry || !registry.teams) return null;
|
|
67
|
+
for (const team of Object.values(registry.teams)) {
|
|
68
|
+
if (Array.isArray(team.roles) && team.roles.includes(personaId)) {
|
|
69
|
+
return team;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Intersect two fence arrays: return only items that appear in both.
|
|
77
|
+
* Handles glob patterns by checking if the tighter pattern is in the broader set.
|
|
78
|
+
*/
|
|
79
|
+
function intersectFences(teamPatterns = [], specialistPatterns = []) {
|
|
80
|
+
if (!Array.isArray(teamPatterns)) teamPatterns = [];
|
|
81
|
+
if (!Array.isArray(specialistPatterns)) specialistPatterns = [];
|
|
82
|
+
|
|
83
|
+
// If either is empty, the intersection is empty (deny by default)
|
|
84
|
+
if (teamPatterns.length === 0 || specialistPatterns.length === 0) {
|
|
85
|
+
return [];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Keep only patterns that are explicitly allowed by both team and specialist
|
|
89
|
+
return specialistPatterns.filter(sp =>
|
|
90
|
+
teamPatterns.some(tp => tp === sp || globMatch(tp, sp))
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Compute the effective fence for a specialist: the intersection of team fence
|
|
96
|
+
* and specialist's own fence. A specialist can never have broader authority
|
|
97
|
+
* than their team grants.
|
|
98
|
+
*
|
|
99
|
+
* @param {string} personaId - The specialist/role id
|
|
100
|
+
* @param {object} specialistFence - The specialist's own fence definition
|
|
101
|
+
* @param {object} [registry] - Optional registry to look up team; if not provided, no team intersection
|
|
102
|
+
* @returns {object} The effective fence (specialist fence ∩ team fence)
|
|
103
|
+
*/
|
|
104
|
+
export function computeEffectiveFence(personaId, specialistFence = {}, registry = null) {
|
|
105
|
+
try {
|
|
106
|
+
// If no registry supplied, return specialist fence as-is
|
|
107
|
+
if (!registry) {
|
|
108
|
+
return specialistFence;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const teamForSpecialist = findTeamForSpecialistSync(personaId, registry);
|
|
112
|
+
if (!teamForSpecialist) {
|
|
113
|
+
// No team found; use specialist fence as-is
|
|
114
|
+
return specialistFence;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Team fence is derived from decisionRights and forbiddenDecisions
|
|
118
|
+
const teamForbiddenDecisions = teamForSpecialist.forbiddenDecisions || [];
|
|
119
|
+
|
|
120
|
+
// Build effective fence: specialist fence intersected with team constraints
|
|
121
|
+
const effectiveFence = {
|
|
122
|
+
allowedPaths: specialistFence.allowedPaths || [],
|
|
123
|
+
allowedCommands: specialistFence.allowedCommands || [],
|
|
124
|
+
allowedBdLabels: specialistFence.allowedBdLabels || [],
|
|
125
|
+
approvalRequired: (specialistFence.approvalRequired || []).filter(
|
|
126
|
+
ap => !teamForbiddenDecisions.includes(ap)
|
|
127
|
+
),
|
|
128
|
+
deniedActions: [
|
|
129
|
+
...(specialistFence.deniedActions || []),
|
|
130
|
+
...teamForbiddenDecisions.map(d => `${d}:**`)
|
|
131
|
+
],
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
return effectiveFence;
|
|
135
|
+
} catch (err) {
|
|
136
|
+
// Fallback to specialist fence on any error
|
|
137
|
+
return specialistFence;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
38
141
|
export function checkAction({ personaId, action, target = '' }) {
|
|
39
142
|
const manifest = loadManifest(personaId);
|
|
40
143
|
if (!manifest) return { allowed: false, reason: 'persona-not-onboarded' };
|
|
144
|
+
|
|
145
|
+
// Use manifest fence directly (computeEffectiveFence requires registry which isn't available here)
|
|
146
|
+
// Team-aware intersection is enforced in policyDecision for tool access
|
|
41
147
|
const fence = manifest.fence || {};
|
|
42
148
|
|
|
43
149
|
if (action === 'edit' || action === 'write') {
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/roles/flavor-bindings.mjs — Canonical specialist ↔ role overlay bindings.
|
|
3
|
+
*
|
|
4
|
+
* Every cx-* specialist that receives prompt flavor injection is listed here.
|
|
5
|
+
* rolePrefix matches skills/roles/{rolePrefix}[.{flavor}].md on disk.
|
|
6
|
+
* classifierKey matches keys returned by classifyRoleFlavors() in orchestration-policy.
|
|
7
|
+
*
|
|
8
|
+
* baseOnly: classifier value "core" loads the base role file (no .flavor suffix).
|
|
9
|
+
* See ADR-0047 for when to split specialists vs add flavors.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
export const SPECIALIST_FLAVOR_BINDINGS = {
|
|
13
|
+
engineer: { classifierKey: 'engineer', rolePrefix: 'engineer', specialistId: 'cx-engineer' },
|
|
14
|
+
'ai-engineer': { classifierKey: 'aiEngineer', rolePrefix: 'ai-engineer', specialistId: 'cx-ai-engineer', baseOnly: true },
|
|
15
|
+
'platform-engineer': { classifierKey: 'platformEngineer', rolePrefix: 'platform-engineer', specialistId: 'cx-platform-engineer', baseOnly: true },
|
|
16
|
+
'data-engineer': { classifierKey: 'dataEngineer', rolePrefix: 'data-engineer', specialistId: 'cx-data-engineer' },
|
|
17
|
+
architect: { classifierKey: 'architect', rolePrefix: 'architect', specialistId: 'cx-architect' },
|
|
18
|
+
'product-manager': { classifierKey: 'productManager', rolePrefix: 'product-manager', specialistId: 'cx-product-manager' },
|
|
19
|
+
'business-strategist': { classifierKey: 'businessStrategist', rolePrefix: 'business-strategist', specialistId: 'cx-business-strategist', baseOnly: true },
|
|
20
|
+
qa: { classifierKey: 'qa', rolePrefix: 'qa', specialistId: 'cx-qa' },
|
|
21
|
+
'test-automation': { classifierKey: 'testAutomation', rolePrefix: 'test-automation', specialistId: 'cx-test-automation', baseOnly: true },
|
|
22
|
+
security: { classifierKey: 'security', rolePrefix: 'security', specialistId: 'cx-security' },
|
|
23
|
+
'data-analyst': { classifierKey: 'dataAnalyst', rolePrefix: 'data-analyst', specialistId: 'cx-data-analyst' },
|
|
24
|
+
sre: { classifierKey: 'sre', rolePrefix: 'sre', specialistId: 'cx-sre', baseOnly: true },
|
|
25
|
+
operations: { classifierKey: 'operations', rolePrefix: 'operations', specialistId: 'cx-operations', baseOnly: true },
|
|
26
|
+
'release-manager': { classifierKey: 'releaseManager', rolePrefix: 'release-manager', specialistId: 'cx-release-manager', baseOnly: true },
|
|
27
|
+
'docs-keeper': { classifierKey: 'docsKeeper', rolePrefix: 'docs-keeper', specialistId: 'cx-docs-keeper', baseOnly: true },
|
|
28
|
+
reviewer: { classifierKey: 'reviewer', rolePrefix: 'reviewer', specialistId: 'cx-reviewer' },
|
|
29
|
+
'devil-advocate': { classifierKey: 'devilAdvocate', rolePrefix: 'devil-advocate', specialistId: 'cx-devil-advocate', baseOnly: true },
|
|
30
|
+
evaluator: { classifierKey: 'evaluator', rolePrefix: 'evaluator', specialistId: 'cx-evaluator', baseOnly: true },
|
|
31
|
+
'trace-reviewer': { classifierKey: 'traceReviewer', rolePrefix: 'trace-reviewer', specialistId: 'cx-trace-reviewer', baseOnly: true },
|
|
32
|
+
researcher: { classifierKey: 'researcher', rolePrefix: 'researcher', specialistId: 'cx-researcher' },
|
|
33
|
+
'ux-researcher': { classifierKey: 'uxResearcher', rolePrefix: 'ux-researcher', specialistId: 'cx-ux-researcher', baseOnly: true },
|
|
34
|
+
explorer: { classifierKey: 'explorer', rolePrefix: 'explorer', specialistId: 'cx-explorer', baseOnly: true },
|
|
35
|
+
designer: { classifierKey: 'designer', rolePrefix: 'designer', specialistId: 'cx-designer' },
|
|
36
|
+
accessibility: { classifierKey: 'accessibility', rolePrefix: 'designer.accessibility', specialistId: 'cx-accessibility', baseOnly: true },
|
|
37
|
+
debugger: { classifierKey: 'debugger', rolePrefix: 'debugger', specialistId: 'cx-debugger', baseOnly: true },
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const BY_CLASSIFIER = Object.fromEntries(
|
|
41
|
+
Object.values(SPECIALIST_FLAVOR_BINDINGS).map((b) => [b.classifierKey, b]),
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
const BY_SPECIALIST = Object.fromEntries(
|
|
45
|
+
Object.entries(SPECIALIST_FLAVOR_BINDINGS).map(([name, b]) => [name, b]),
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
export function bindingForSpecialist(shortName) {
|
|
49
|
+
return BY_SPECIALIST[shortName] ?? null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function bindingForClassifier(classifierKey) {
|
|
53
|
+
return BY_CLASSIFIER[classifierKey] ?? null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function resolveRoleOverlayId(binding, flavor) {
|
|
57
|
+
if (!binding || !flavor) return null;
|
|
58
|
+
if (binding.baseOnly || flavor === 'core') return binding.rolePrefix;
|
|
59
|
+
return `${binding.rolePrefix}.${flavor}`;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function formatOverlayLine(classifierKey, flavor) {
|
|
63
|
+
const binding = bindingForClassifier(classifierKey);
|
|
64
|
+
if (!binding || !flavor) return null;
|
|
65
|
+
const roleId = resolveRoleOverlayId(binding, flavor);
|
|
66
|
+
if (!roleId) return null;
|
|
67
|
+
return `${binding.specialistId}: loaded ${roleId} overlay`;
|
|
68
|
+
}
|
package/lib/roles/gateway.mjs
CHANGED
|
@@ -19,6 +19,42 @@ import { route as routeEvent } from './router.mjs';
|
|
|
19
19
|
import { runBd } from '../beads-client.mjs';
|
|
20
20
|
import { doctorRoot } from '../config/xdg.mjs';
|
|
21
21
|
|
|
22
|
+
/**
|
|
23
|
+
* Team escalation support — loads team definitions from specialists/org
|
|
24
|
+
* and resolves escalation paths when decisions hit forbidden or unresolvable situations.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
function loadTeamRegistry(root) {
|
|
28
|
+
try {
|
|
29
|
+
const regPath = join(root, 'specialists', 'teams-registry.json');
|
|
30
|
+
if (!existsSync(regPath)) return null;
|
|
31
|
+
return JSON.parse(readFileSync(regPath, 'utf8'));
|
|
32
|
+
} catch {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function findTeamByRoleOwner(roleId, registry) {
|
|
38
|
+
if (!registry || !Array.isArray(registry.teams)) return null;
|
|
39
|
+
return registry.teams.find((team) => team.owner === roleId);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function getTeamEscalationPath(teamId, registry) {
|
|
43
|
+
if (!registry || !Array.isArray(registry.teams)) return [];
|
|
44
|
+
const team = registry.teams.find((t) => t.id === teamId);
|
|
45
|
+
return team?.escalationPath || [];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function canTeamMakeDecision(teamId, decisionId, registry) {
|
|
49
|
+
if (!registry || !Array.isArray(registry.teams)) return false;
|
|
50
|
+
const team = registry.teams.find((t) => t.id === teamId);
|
|
51
|
+
if (!team) return false;
|
|
52
|
+
if (Array.isArray(team.forbiddenDecisions) && team.forbiddenDecisions.includes(decisionId)) {
|
|
53
|
+
return false; // forbidden
|
|
54
|
+
}
|
|
55
|
+
return Array.isArray(team.decisionRights) && team.decisionRights.includes(decisionId);
|
|
56
|
+
}
|
|
57
|
+
|
|
22
58
|
// CONSTRUCT_ROLES_ROOT still wins for the roles event ecosystem's test
|
|
23
59
|
// isolation; the literal fallback follows the global doctor root instead of ~/.cx.
|
|
24
60
|
|
|
@@ -38,8 +74,37 @@ const DEFAULTS = {
|
|
|
38
74
|
};
|
|
39
75
|
|
|
40
76
|
function ensureDir() {
|
|
41
|
-
|
|
42
|
-
|
|
77
|
+
const r = rootDir();
|
|
78
|
+
if (!existsSync(r)) mkdirSync(r, { recursive: true });
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function raisedPath() {
|
|
82
|
+
return join(rootDir(), 'gateway-raised.jsonl');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function teamsPath() {
|
|
86
|
+
return join(rootDir(), 'team-decisions.jsonl');
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function readRaisedFingerprints() {
|
|
90
|
+
const rp = raisedPath();
|
|
91
|
+
if (!existsSync(rp)) return [];
|
|
92
|
+
try {
|
|
93
|
+
return readFileSync(rp, 'utf8')
|
|
94
|
+
.split('\n')
|
|
95
|
+
.filter(Boolean)
|
|
96
|
+
.map((l) => { try { return JSON.parse(l); } catch { return null; } })
|
|
97
|
+
.filter(Boolean);
|
|
98
|
+
} catch { return []; }
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function fingerprintAlreadyRaised(fingerprint) {
|
|
102
|
+
return readRaisedFingerprints().some((r) => r.fingerprint === fingerprint && r.beadId);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function recordRaisedFingerprint(fingerprint, beadId) {
|
|
106
|
+
ensureDir();
|
|
107
|
+
appendFileSync(raisedPath(), JSON.stringify({ fingerprint, beadId, ts: Date.now() }) + '\n');
|
|
43
108
|
}
|
|
44
109
|
|
|
45
110
|
function readPending() {
|
|
@@ -132,32 +197,44 @@ export function shouldEscalate(event, manifest, now = Date.now()) {
|
|
|
132
197
|
}
|
|
133
198
|
|
|
134
199
|
async function createBdIncident(event, routeResult) {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
200
|
+
// Dedup: check if this fingerprint has already been raised as a bead
|
|
201
|
+
if (fingerprintAlreadyRaised(event.fingerprint)) {
|
|
202
|
+
return { success: true, skipped: true, reason: 'fingerprint-already-raised', fingerprint: event.fingerprint };
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const labels = (routeResult.manifest.outputs?.bdLabels || []).join(',') || 'incident';
|
|
206
|
+
const firstLine = String(event.summary || '').split('\n')[0].slice(0, 120);
|
|
207
|
+
const title = `[${routeResult.personaId}] ${event.type} — ${firstLine}`;
|
|
208
|
+
const body = JSON.stringify(
|
|
209
|
+
{
|
|
210
|
+
eventType: event.type,
|
|
211
|
+
ts: event.ts,
|
|
212
|
+
project: event.project,
|
|
213
|
+
branch: event.branch,
|
|
214
|
+
cwd: event.cwd,
|
|
215
|
+
summary: event.summary,
|
|
216
|
+
context: event.context,
|
|
217
|
+
fingerprint: event.fingerprint,
|
|
218
|
+
fence: routeResult.manifest.fence || {},
|
|
219
|
+
handoffCandidates: routeResult.manifest.handoffCandidates || [],
|
|
220
|
+
},
|
|
221
|
+
null,
|
|
222
|
+
2
|
|
223
|
+
);
|
|
224
|
+
const result = await runBd(
|
|
225
|
+
['create', title, '-t', 'bug', '-l', labels, '-d', body],
|
|
226
|
+
{ actor: `roles:${routeResult.personaId}`, silent: true, timeoutSeconds: 10, commandTimeoutSeconds: 30 }
|
|
227
|
+
);
|
|
228
|
+
if (!result.success) return { success: false, error: result.error || 'bd-failed' };
|
|
229
|
+
const match = String(result.output || '').match(/Created issue:\s*([\w-]+)/);
|
|
230
|
+
const beadId = match ? match[1] : null;
|
|
231
|
+
|
|
232
|
+
// Record this fingerprint as raised
|
|
233
|
+
if (beadId) {
|
|
234
|
+
recordRaisedFingerprint(event.fingerprint, beadId);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return { success: true, issueId: beadId, raw: result.output };
|
|
161
238
|
}
|
|
162
239
|
|
|
163
240
|
async function emitToastBestEffort(payload) {
|
|
@@ -339,4 +416,38 @@ export function resetPending() {
|
|
|
339
416
|
if (existsSync(pp)) writeFileSync(pp, '');
|
|
340
417
|
}
|
|
341
418
|
|
|
342
|
-
|
|
419
|
+
/**
|
|
420
|
+
* Record a team decision (approved, blocked, or escalated) for policy-gate
|
|
421
|
+
* authority checks and durable audit trails.
|
|
422
|
+
*/
|
|
423
|
+
export function recordTeamDecision(decisionId, teamId, outcome = 'pending', context = {}) {
|
|
424
|
+
ensureDir();
|
|
425
|
+
const entry = {
|
|
426
|
+
ts: Date.now(),
|
|
427
|
+
decisionId,
|
|
428
|
+
teamId,
|
|
429
|
+
outcome, // 'approved', 'blocked', 'escalated', 'pending'
|
|
430
|
+
context,
|
|
431
|
+
};
|
|
432
|
+
appendFileSync(teamsPath(), JSON.stringify(entry) + '\n');
|
|
433
|
+
return entry;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* Record a forbidden decision attempt — someone tried to make a decision the team is not allowed to make.
|
|
438
|
+
*/
|
|
439
|
+
export function recordForbiddenDecision(decisionId, teamId, reason = '', context = {}) {
|
|
440
|
+
ensureDir();
|
|
441
|
+
const entry = {
|
|
442
|
+
ts: Date.now(),
|
|
443
|
+
type: 'forbidden-decision',
|
|
444
|
+
decisionId,
|
|
445
|
+
teamId,
|
|
446
|
+
reason,
|
|
447
|
+
context,
|
|
448
|
+
};
|
|
449
|
+
appendFileSync(teamsPath(), JSON.stringify(entry) + '\n');
|
|
450
|
+
return entry;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
export const _gatewayPaths = { rootDir, pendingPath, teamsPath };
|
package/lib/roles/manifest.mjs
CHANGED
|
@@ -1,25 +1,34 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* lib/roles/manifest.mjs — role manifest loader.
|
|
3
3
|
*
|
|
4
|
-
* Reads
|
|
5
|
-
* `events` array is non-empty. Empty entries reserve
|
|
6
|
-
* wiring without code change.
|
|
4
|
+
* Reads role manifest data from the unified registry (specialists/org).
|
|
5
|
+
* A persona is "onboarded" when its `events` array is non-empty. Empty entries reserve
|
|
6
|
+
* the slot for future wiring without code change.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import {
|
|
10
|
-
import { dirname, join } from 'node:path';
|
|
11
|
-
import { fileURLToPath } from 'node:url';
|
|
12
|
-
|
|
13
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
14
|
-
const MANIFEST_PATH = join(__dirname, '..', '..', 'specialists', 'role-manifests.json');
|
|
9
|
+
import { loadRegistry } from '../registry/loader.mjs';
|
|
15
10
|
|
|
16
11
|
let cached = null;
|
|
17
12
|
|
|
18
13
|
function load() {
|
|
19
14
|
if (cached) return cached;
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
15
|
+
const registry = loadRegistry();
|
|
16
|
+
// Extract personas from specialists: map specialist events/fence to persona format
|
|
17
|
+
const personas = {};
|
|
18
|
+
for (const [specId, spec] of Object.entries(registry.specialists)) {
|
|
19
|
+
const personaId = specId.replace(/^cx-/, '');
|
|
20
|
+
personas[personaId] = {
|
|
21
|
+
events: spec.events || [],
|
|
22
|
+
severityImmediate: spec.severityImmediate || [],
|
|
23
|
+
killSwitchEnv: spec.killSwitchEnv || '',
|
|
24
|
+
fence: spec.fence || {},
|
|
25
|
+
handoffCandidates: spec.handoffCandidates || [],
|
|
26
|
+
outputs: {
|
|
27
|
+
docTypes: spec.docArtifacts || [],
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
cached = personas;
|
|
23
32
|
return cached;
|
|
24
33
|
}
|
|
25
34
|
|
|
@@ -45,4 +54,4 @@ export function listAllPersonas() {
|
|
|
45
54
|
|
|
46
55
|
export function _resetCache() {
|
|
47
56
|
cached = null;
|
|
48
|
-
}
|
|
57
|
+
}
|
package/lib/roles/router.mjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* lib/roles/router.mjs — pure event → persona resolver.
|
|
3
3
|
*
|
|
4
4
|
* Reads ownership from the declarative routing tables built off of
|
|
5
|
-
* specialists/
|
|
5
|
+
* specialists/org, then validates against the loaded role
|
|
6
6
|
* manifest. Returns null silently when the event has no declared owner
|
|
7
7
|
* or the persona is not yet onboarded — letting ownership be declared
|
|
8
8
|
* ahead of full wiring.
|
package/lib/runtime-env.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* lib/runtime-env.mjs — Construct runtime environment with credential bootstrap.
|
|
3
3
|
*
|
|
4
|
-
* prepareConstructEnv merges ~/.construct/config.env into the process environment.
|
|
4
|
+
* prepareConstructEnv merges ~/.config/construct/config.env into the process environment.
|
|
5
5
|
* Credential auto-link from 1Password runs only when autoLink:true (setup-credentials).
|
|
6
6
|
*/
|
|
7
7
|
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/scopes/enrich.mjs — Attach org teams and roles to an active work scope.
|
|
3
|
+
*
|
|
4
|
+
* Scopes (specialists/org/scopes/*.json) carry intake taxonomy, templates, and
|
|
5
|
+
* tone only. Teams and roles always come from specialists/org — one org, no
|
|
6
|
+
* per-scope squad forks.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { loadRegistry } from '../registry/loader.mjs';
|
|
10
|
+
|
|
11
|
+
function mapTeam(team) {
|
|
12
|
+
return {
|
|
13
|
+
id: team.id,
|
|
14
|
+
name: team.name || team.id,
|
|
15
|
+
owner: team.owner,
|
|
16
|
+
roles: team.roles || [],
|
|
17
|
+
decisionRights: team.decisionRights || [],
|
|
18
|
+
forbiddenDecisions: team.forbiddenDecisions || [],
|
|
19
|
+
escalationPath: team.escalationPath || [],
|
|
20
|
+
charter: team.charter || '',
|
|
21
|
+
contact: team.contact || {},
|
|
22
|
+
specialists: team.specialists || [],
|
|
23
|
+
groupId: team.groupId || null,
|
|
24
|
+
kind: team.kind || null,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function registryTeamsFromOrg(registry) {
|
|
29
|
+
return Object.values(registry.teams || {})
|
|
30
|
+
.filter((team) => team?.id)
|
|
31
|
+
.map(mapTeam)
|
|
32
|
+
.sort((a, b) => a.id.localeCompare(b.id));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function deriveRegistryRoles(registry) {
|
|
36
|
+
const roles = new Set();
|
|
37
|
+
for (const spec of Object.values(registry.specialists || {})) {
|
|
38
|
+
if (spec?.name) roles.add(spec.name);
|
|
39
|
+
}
|
|
40
|
+
return [...roles].sort();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function enrichScope(raw, { rootDir } = {}) {
|
|
44
|
+
if (!raw || typeof raw !== 'object') return raw;
|
|
45
|
+
const scope = { ...raw };
|
|
46
|
+
const registry = loadRegistry({ rootDir });
|
|
47
|
+
|
|
48
|
+
if (!Array.isArray(scope.teams) || scope.teams.length === 0) {
|
|
49
|
+
scope.teams = registryTeamsFromOrg(registry);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (!Array.isArray(scope.roles) || scope.roles.length === 0) {
|
|
53
|
+
scope.roles = deriveRegistryRoles(registry);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return scope;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function collectScopeRoleIds(rawScope, { rootDir } = {}) {
|
|
60
|
+
const enriched = enrichScope(rawScope, { rootDir });
|
|
61
|
+
const roles = new Set(enriched?.roles || []);
|
|
62
|
+
for (const team of enriched?.teams || []) {
|
|
63
|
+
for (const role of team.roles || []) roles.add(role);
|
|
64
|
+
if (team.owner) roles.add(team.owner);
|
|
65
|
+
}
|
|
66
|
+
return roles;
|
|
67
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/scopes/hooks.mjs — Scope-level session hook toggles.
|
|
3
|
+
*
|
|
4
|
+
* Curated scopes declare hooks.sessionReflect and hooks.sessionOptimize.
|
|
5
|
+
* Callers consult these before firing optional session-end behaviors.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export function isScopeHookEnabled(scope, hookName) {
|
|
9
|
+
const setting = scope?.hooks?.[hookName];
|
|
10
|
+
if (setting === 'off') return false;
|
|
11
|
+
return setting === 'on' || setting === undefined;
|
|
12
|
+
}
|