@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/registry/cli.mjs
CHANGED
|
@@ -1,10 +1,99 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* lib/registry/cli.mjs — CLI handlers for registry validate, status, and
|
|
2
|
+
* lib/registry/cli.mjs — CLI handlers for registry validate, status, doc generation, and team/specialist lifecycle.
|
|
3
|
+
*
|
|
4
|
+
* Exports handlers for:
|
|
5
|
+
* - runRegistryStatus: Show unified registry contents
|
|
6
|
+
* - runRegistryValidate: Validate unified registry schema and invariants
|
|
7
|
+
* - runRegistryGenerateDocs: Generate capabilities.md from registry
|
|
8
|
+
* - runRegistryDiff: Show changes to unified registry vs. last commit
|
|
9
|
+
* - runRegistryPrune: List orphaned specialist prompts/skills
|
|
10
|
+
* - runTeamAdd: Wizard to add a new team
|
|
11
|
+
* - runTeamRemove: Remove a team with dependency checking
|
|
12
|
+
* - runSpecialistAdd: Wizard to add a new specialist to a team
|
|
13
|
+
* - runSpecialistRemove: Remove a specialist with contract checking
|
|
3
14
|
*/
|
|
4
15
|
|
|
16
|
+
import fs from 'node:fs';
|
|
17
|
+
import path from 'node:path';
|
|
18
|
+
import { execSync } from 'node:child_process';
|
|
5
19
|
import { validateCapabilityRegistry, loadCapabilityRegistry } from './validate.mjs';
|
|
6
20
|
import { generateCapabilitiesDoc, checkCapabilitiesDocDrift } from './generate-docs.mjs';
|
|
21
|
+
import { generateAgentManifest, checkAgentManifestDrift } from './agent-manifest.mjs';
|
|
7
22
|
import { triageBoundOrphans, formatConsolidationProposalMarkdown } from './consolidation.mjs';
|
|
23
|
+
import { loadRegistry } from './loader.mjs';
|
|
24
|
+
import { validate as validateUnifiedRegistry } from './validator.mjs';
|
|
25
|
+
import { validateRegistry } from '../validator.mjs';
|
|
26
|
+
import { assembleRegistryAtGitRef, findTeamFile, removeOrgEntityFile } from './org-io.mjs';
|
|
27
|
+
|
|
28
|
+
// The modular org under specialists/org/ is the SSOT. A project may shadow
|
|
29
|
+
// fields via a .cx/unified-registry.json overlay; the runtime loader merges
|
|
30
|
+
// it, so validation must merge it the same way (overlay wins) and run the
|
|
31
|
+
// same invariants the loader enforces on read.
|
|
32
|
+
|
|
33
|
+
function mergeUnifiedOverlay(base, overlay) {
|
|
34
|
+
for (const section of ['teams', 'specialists', 'contracts', 'policies']) {
|
|
35
|
+
if (!overlay[section]) continue;
|
|
36
|
+
base[section] = base[section] || {};
|
|
37
|
+
for (const [id, value] of Object.entries(overlay[section])) {
|
|
38
|
+
base[section][id] = { ...base[section][id], ...value };
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return base;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export async function runUnifiedRegistryValidate(args = [], { rootDir, println = console.log, errorln = console.error } = {}) {
|
|
45
|
+
const jsonOutput = args.includes('--json');
|
|
46
|
+
const orgPath = path.join(rootDir, 'specialists', 'org');
|
|
47
|
+
const overlayPath = path.join(rootDir, '.cx', 'unified-registry.json');
|
|
48
|
+
|
|
49
|
+
let registry;
|
|
50
|
+
try {
|
|
51
|
+
const { clearCache } = await import('./loader.mjs');
|
|
52
|
+
clearCache();
|
|
53
|
+
registry = loadRegistry({ rootDir });
|
|
54
|
+
} catch (err) {
|
|
55
|
+
const message = `Cannot assemble registry from ${orgPath}: ${err.message}`;
|
|
56
|
+
if (jsonOutput) println(JSON.stringify({ ok: false, registryPath: orgPath, errors: [{ id: 'unreadable', message }], warnings: [] }, null, 2));
|
|
57
|
+
else errorln(`✗ ${message}`);
|
|
58
|
+
return 1;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const overlayApplied = fs.existsSync(overlayPath);
|
|
62
|
+
|
|
63
|
+
const result = validateUnifiedRegistry(registry);
|
|
64
|
+
|
|
65
|
+
if (jsonOutput) {
|
|
66
|
+
println(JSON.stringify({
|
|
67
|
+
ok: result.ok,
|
|
68
|
+
registryPath: orgPath,
|
|
69
|
+
overlayPath: overlayApplied ? overlayPath : null,
|
|
70
|
+
teams: Object.keys(registry.teams || {}).length,
|
|
71
|
+
specialists: Object.keys(registry.specialists || {}).length,
|
|
72
|
+
contracts: Object.keys(registry.contracts || {}).length,
|
|
73
|
+
policies: Object.keys(registry.policies || {}).length,
|
|
74
|
+
errors: result.errors,
|
|
75
|
+
warnings: result.warnings,
|
|
76
|
+
}, null, 2));
|
|
77
|
+
return result.ok ? 0 : 1;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
println(`Unified registry: ${orgPath} (runtime merge)`);
|
|
81
|
+
if (overlayApplied) println(`Overlay: ${overlayPath}`);
|
|
82
|
+
println(`Teams: ${Object.keys(registry.teams || {}).length} Specialists: ${Object.keys(registry.specialists || {}).length} Contracts: ${Object.keys(registry.contracts || {}).length} Policies: ${Object.keys(registry.policies || {}).length}`);
|
|
83
|
+
if (result.errors.length) {
|
|
84
|
+
errorln(`Errors (${result.errors.length}):`);
|
|
85
|
+
for (const e of result.errors) errorln(` ✗ ${e.id}: ${e.message}`);
|
|
86
|
+
}
|
|
87
|
+
if (result.warnings.length) {
|
|
88
|
+
println(`Warnings (${result.warnings.length}):`);
|
|
89
|
+
for (const w of result.warnings) println(` ⚠ ${w.id}: ${w.message}`);
|
|
90
|
+
}
|
|
91
|
+
if (result.ok && !result.warnings.length) println('✓ Unified registry valid');
|
|
92
|
+
else if (result.ok) println('✓ Unified registry valid (with warnings)');
|
|
93
|
+
else errorln('✗ Unified registry invalid');
|
|
94
|
+
|
|
95
|
+
return result.ok ? 0 : 1;
|
|
96
|
+
}
|
|
8
97
|
|
|
9
98
|
export async function runRegistryStatus(args = [], { rootDir, println = console.log } = {}) {
|
|
10
99
|
const jsonOutput = args.includes('--json');
|
|
@@ -35,6 +124,10 @@ export async function runRegistryStatus(args = [], { rootDir, println = console.
|
|
|
35
124
|
}
|
|
36
125
|
|
|
37
126
|
export async function runRegistryValidate(args = [], { rootDir, println = console.log, errorln = console.error } = {}) {
|
|
127
|
+
if (args.includes('--unified')) {
|
|
128
|
+
return runUnifiedRegistryValidate(args, { rootDir, println, errorln });
|
|
129
|
+
}
|
|
130
|
+
|
|
38
131
|
const jsonOutput = args.includes('--json');
|
|
39
132
|
const report = validateCapabilityRegistry({ rootDir });
|
|
40
133
|
|
|
@@ -62,16 +155,63 @@ export async function runRegistryValidate(args = [], { rootDir, println = consol
|
|
|
62
155
|
|
|
63
156
|
export async function runRegistryGenerateDocs(args = [], { rootDir, println = console.log, errorln = console.error } = {}) {
|
|
64
157
|
if (args.includes('--check')) {
|
|
65
|
-
|
|
66
|
-
|
|
158
|
+
let failed = false;
|
|
159
|
+
|
|
160
|
+
const docsDrift = checkCapabilitiesDocDrift({ rootDir }).drift;
|
|
161
|
+
if (docsDrift) {
|
|
67
162
|
errorln('capabilities.md drift — run `construct registry:generate-docs`');
|
|
68
|
-
|
|
163
|
+
failed = true;
|
|
164
|
+
} else {
|
|
165
|
+
println('capabilities.md is up to date');
|
|
69
166
|
}
|
|
70
|
-
|
|
71
|
-
|
|
167
|
+
|
|
168
|
+
const manifestDrift = checkAgentManifestDrift({ rootDir }).drift;
|
|
169
|
+
if (manifestDrift) {
|
|
170
|
+
errorln('agent-manifest.json drift — run `construct registry:generate-docs`');
|
|
171
|
+
failed = true;
|
|
172
|
+
} else {
|
|
173
|
+
println('agent-manifest.json is up to date');
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return failed ? 1 : 0;
|
|
72
177
|
}
|
|
73
178
|
const out = generateCapabilitiesDoc({ rootDir });
|
|
74
179
|
println(`Generated ${out}`);
|
|
180
|
+
const { path: manifestPath } = generateAgentManifest({ rootDir });
|
|
181
|
+
println(`Generated ${manifestPath}`);
|
|
182
|
+
return 0;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export async function runTeamList(args = [], { rootDir, println = console.log } = {}) {
|
|
186
|
+
const { loadRegistry, listTeams } = await import('./loader.mjs');
|
|
187
|
+
const kindFlag = args.indexOf('--kind');
|
|
188
|
+
const kind = kindFlag !== -1 ? args[kindFlag + 1] : null;
|
|
189
|
+
loadRegistry({ rootDir });
|
|
190
|
+
const teams = listTeams({ rootDir, kind });
|
|
191
|
+
if (args.includes('--json')) {
|
|
192
|
+
println(JSON.stringify(teams, null, 2));
|
|
193
|
+
return 0;
|
|
194
|
+
}
|
|
195
|
+
for (const t of teams) {
|
|
196
|
+
println(`${t.id.padEnd(28)} ${t.kind || 'squad'} ${t.name}`);
|
|
197
|
+
}
|
|
198
|
+
return 0;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export async function runTeamShow(args = [], { rootDir, println = console.log, errorln = console.error } = {}) {
|
|
202
|
+
const id = args.find((a) => !a.startsWith('--'));
|
|
203
|
+
if (!id) {
|
|
204
|
+
errorln('Usage: construct team show <team-id>');
|
|
205
|
+
return 1;
|
|
206
|
+
}
|
|
207
|
+
const { getTeam } = await import('./loader.mjs');
|
|
208
|
+
const team = getTeam(id, { rootDir });
|
|
209
|
+
if (!team) {
|
|
210
|
+
errorln(`Team not found: ${id}`);
|
|
211
|
+
return 1;
|
|
212
|
+
}
|
|
213
|
+
if (args.includes('--json')) println(JSON.stringify({ id, ...team }, null, 2));
|
|
214
|
+
else println(JSON.stringify({ id, ...team }, null, 2));
|
|
75
215
|
return 0;
|
|
76
216
|
}
|
|
77
217
|
|
|
@@ -80,3 +220,447 @@ export async function runRegistryConsolidationProposal(args = [], { rootDir, pri
|
|
|
80
220
|
println(formatConsolidationProposalMarkdown(triage));
|
|
81
221
|
return 0;
|
|
82
222
|
}
|
|
223
|
+
|
|
224
|
+
export async function runRegistryDiff(args = [], { rootDir, println = console.log, errorln = console.error } = {}) {
|
|
225
|
+
const jsonOutput = args.includes('--json');
|
|
226
|
+
|
|
227
|
+
let currentRegistry;
|
|
228
|
+
let headRegistry;
|
|
229
|
+
try {
|
|
230
|
+
currentRegistry = loadRegistry({ rootDir });
|
|
231
|
+
} catch (err) {
|
|
232
|
+
errorln(`Error reading current registry: ${err.message}`);
|
|
233
|
+
return 1;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
try {
|
|
237
|
+
headRegistry = assembleRegistryAtGitRef(rootDir, 'HEAD');
|
|
238
|
+
} catch (err) {
|
|
239
|
+
errorln(`No previous org snapshot at HEAD: ${err.message}`);
|
|
240
|
+
return 1;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const diffs = {
|
|
244
|
+
teamChanges: { added: [], removed: [], modified: [] },
|
|
245
|
+
specialistChanges: { added: [], removed: [], modified: [] },
|
|
246
|
+
contractChanges: { added: [], removed: [], modified: [] },
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
// Compare teams
|
|
250
|
+
const currentTeamIds = new Set(Object.keys(currentRegistry.teams || {}));
|
|
251
|
+
const headTeamIds = new Set(Object.keys(headRegistry.teams || {}));
|
|
252
|
+
|
|
253
|
+
for (const id of currentTeamIds) {
|
|
254
|
+
if (!headTeamIds.has(id)) {
|
|
255
|
+
diffs.teamChanges.added.push(id);
|
|
256
|
+
} else if (JSON.stringify(currentRegistry.teams[id]) !== JSON.stringify(headRegistry.teams[id])) {
|
|
257
|
+
diffs.teamChanges.modified.push(id);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
for (const id of headTeamIds) {
|
|
262
|
+
if (!currentTeamIds.has(id)) {
|
|
263
|
+
diffs.teamChanges.removed.push(id);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Compare specialists
|
|
268
|
+
const currentSpecIds = new Set(Object.keys(currentRegistry.specialists || {}));
|
|
269
|
+
const headSpecIds = new Set(Object.keys(headRegistry.specialists || {}));
|
|
270
|
+
|
|
271
|
+
for (const id of currentSpecIds) {
|
|
272
|
+
if (!headSpecIds.has(id)) {
|
|
273
|
+
diffs.specialistChanges.added.push(id);
|
|
274
|
+
} else if (JSON.stringify(currentRegistry.specialists[id]) !== JSON.stringify(headRegistry.specialists[id])) {
|
|
275
|
+
diffs.specialistChanges.modified.push(id);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
for (const id of headSpecIds) {
|
|
280
|
+
if (!currentSpecIds.has(id)) {
|
|
281
|
+
diffs.specialistChanges.removed.push(id);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Compare contracts
|
|
286
|
+
const currentContractIds = new Set(Object.keys(currentRegistry.contracts || {}));
|
|
287
|
+
const headContractIds = new Set(Object.keys(headRegistry.contracts || {}));
|
|
288
|
+
|
|
289
|
+
for (const id of currentContractIds) {
|
|
290
|
+
if (!headContractIds.has(id)) {
|
|
291
|
+
diffs.contractChanges.added.push(id);
|
|
292
|
+
} else if (JSON.stringify(currentRegistry.contracts[id]) !== JSON.stringify(headRegistry.contracts[id])) {
|
|
293
|
+
diffs.contractChanges.modified.push(id);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
for (const id of headContractIds) {
|
|
298
|
+
if (!currentContractIds.has(id)) {
|
|
299
|
+
diffs.contractChanges.removed.push(id);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if (jsonOutput) {
|
|
304
|
+
println(JSON.stringify(diffs, null, 2));
|
|
305
|
+
return 0;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Human-readable output
|
|
309
|
+
let hasChanges = false;
|
|
310
|
+
|
|
311
|
+
if (diffs.teamChanges.added.length || diffs.teamChanges.removed.length || diffs.teamChanges.modified.length) {
|
|
312
|
+
hasChanges = true;
|
|
313
|
+
println('Teams:');
|
|
314
|
+
for (const id of diffs.teamChanges.added) println(` + ${id}`);
|
|
315
|
+
for (const id of diffs.teamChanges.removed) println(` - ${id}`);
|
|
316
|
+
for (const id of diffs.teamChanges.modified) println(` ~ ${id}`);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
if (diffs.specialistChanges.added.length || diffs.specialistChanges.removed.length || diffs.specialistChanges.modified.length) {
|
|
320
|
+
if (hasChanges) println('');
|
|
321
|
+
hasChanges = true;
|
|
322
|
+
println('Specialists:');
|
|
323
|
+
for (const id of diffs.specialistChanges.added) println(` + ${id}`);
|
|
324
|
+
for (const id of diffs.specialistChanges.removed) println(` - ${id}`);
|
|
325
|
+
for (const id of diffs.specialistChanges.modified) println(` ~ ${id}`);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
if (diffs.contractChanges.added.length || diffs.contractChanges.removed.length || diffs.contractChanges.modified.length) {
|
|
329
|
+
if (hasChanges) println('');
|
|
330
|
+
hasChanges = true;
|
|
331
|
+
println('Contracts:');
|
|
332
|
+
for (const id of diffs.contractChanges.added) println(` + ${id}`);
|
|
333
|
+
for (const id of diffs.contractChanges.removed) println(` - ${id}`);
|
|
334
|
+
for (const id of diffs.contractChanges.modified) println(` ~ ${id}`);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
if (!hasChanges) {
|
|
338
|
+
println('No changes to unified registry since last commit.');
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
return 0;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
export async function runRegistryPrune(args = [], { rootDir, println = console.log, errorln = console.error } = {}) {
|
|
345
|
+
const jsonOutput = args.includes('--json');
|
|
346
|
+
const registry = loadRegistry({ rootDir });
|
|
347
|
+
|
|
348
|
+
const registeredSpecialists = new Set(Object.keys(registry.specialists || {}));
|
|
349
|
+
const registeredSkills = new Set();
|
|
350
|
+
|
|
351
|
+
// Collect all registered skill file paths from registry
|
|
352
|
+
for (const spec of Object.values(registry.specialists || {})) {
|
|
353
|
+
if (Array.isArray(spec.skills)) {
|
|
354
|
+
for (const skillPath of spec.skills) {
|
|
355
|
+
// Skills are stored as paths like "docs/prd-workflow", resolve to file path
|
|
356
|
+
registeredSkills.add(path.join(rootDir, 'skills', `${skillPath.split('/').pop()}.md`));
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const orphaned = { prompts: [], skills: [] };
|
|
362
|
+
|
|
363
|
+
// Specialist prompts not registered appear in orphaned list.
|
|
364
|
+
const promptsDir = path.join(rootDir, 'specialists', 'prompts');
|
|
365
|
+
if (fs.existsSync(promptsDir)) {
|
|
366
|
+
const promptFiles = fs.readdirSync(promptsDir).filter(f => f.endsWith('.md'));
|
|
367
|
+
for (const file of promptFiles) {
|
|
368
|
+
const match = file.match(/^cx-(.+)\.md$/);
|
|
369
|
+
if (match) {
|
|
370
|
+
const id = `cx-${match[1]}`;
|
|
371
|
+
if (!registeredSpecialists.has(id)) {
|
|
372
|
+
orphaned.prompts.push(path.join('specialists', 'prompts', file));
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// Skill files not referenced by any specialist are orphaned.
|
|
379
|
+
const skillsDir = path.join(rootDir, 'skills');
|
|
380
|
+
if (fs.existsSync(skillsDir)) {
|
|
381
|
+
const walkSkills = (dir, relPath = '') => {
|
|
382
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
383
|
+
for (const entry of entries) {
|
|
384
|
+
const fullPath = path.join(dir, entry.name);
|
|
385
|
+
const relFullPath = path.join(relPath, entry.name);
|
|
386
|
+
|
|
387
|
+
if (entry.isDirectory()) {
|
|
388
|
+
walkSkills(fullPath, relFullPath);
|
|
389
|
+
} else if (entry.name.endsWith('.md')) {
|
|
390
|
+
// A skill is registered if any specialist lists it in their skills array.
|
|
391
|
+
let found = false;
|
|
392
|
+
for (const spec of Object.values(registry.specialists || {})) {
|
|
393
|
+
if (Array.isArray(spec.skills)) {
|
|
394
|
+
for (const skillPath of spec.skills) {
|
|
395
|
+
if (skillPath.endsWith(relFullPath.replace(/\.md$/, '').replace(/\\/g, '/'))) {
|
|
396
|
+
found = true;
|
|
397
|
+
break;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
if (found) break;
|
|
402
|
+
}
|
|
403
|
+
if (!found) {
|
|
404
|
+
orphaned.skills.push(path.join('skills', relFullPath));
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
};
|
|
409
|
+
walkSkills(skillsDir);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
if (jsonOutput) {
|
|
413
|
+
println(JSON.stringify(orphaned, null, 2));
|
|
414
|
+
return 0;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
if (orphaned.prompts.length === 0 && orphaned.skills.length === 0) {
|
|
418
|
+
println('No orphaned prompt or skill files found.');
|
|
419
|
+
return 0;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
let hasOrphans = false;
|
|
423
|
+
|
|
424
|
+
if (orphaned.prompts.length) {
|
|
425
|
+
hasOrphans = true;
|
|
426
|
+
println('Orphaned specialist prompts:');
|
|
427
|
+
for (const file of orphaned.prompts) {
|
|
428
|
+
println(` rm ${file}`);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
if (orphaned.skills.length) {
|
|
433
|
+
if (hasOrphans) println('');
|
|
434
|
+
hasOrphans = true;
|
|
435
|
+
println('Orphaned skills:');
|
|
436
|
+
for (const file of orphaned.skills) {
|
|
437
|
+
println(` rm ${file}`);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
return 0;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
export async function runTeamAdd(args = [], { rootDir, println = console.log, errorln = console.error } = {}) {
|
|
445
|
+
// Interactive wizard not available; requires terminal TTY and structured I/O.
|
|
446
|
+
// Fallback: guide user to manual JSON editing.
|
|
447
|
+
errorln('construct team add requires interactive terminal access.');
|
|
448
|
+
errorln('Edit specialists/org directly and run:');
|
|
449
|
+
errorln(' construct registry validate');
|
|
450
|
+
errorln('');
|
|
451
|
+
errorln('Expected format in teams object:');
|
|
452
|
+
errorln(JSON.stringify({
|
|
453
|
+
'example-group': {
|
|
454
|
+
name: 'Example Group',
|
|
455
|
+
owner: 'example-owner',
|
|
456
|
+
roles: ['example-owner', 'example-member'],
|
|
457
|
+
decisionRights: ['decision-1', 'decision-2'],
|
|
458
|
+
forbiddenDecisions: ['forbidden-1'],
|
|
459
|
+
escalationPath: ['example-owner', 'rd-lead', 'orchestrator'],
|
|
460
|
+
charter: 'Team charter describing scope and responsibility.',
|
|
461
|
+
contact: { slack: '#example', email: 'example@company.com' },
|
|
462
|
+
},
|
|
463
|
+
}, null, 2));
|
|
464
|
+
return 1;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
export async function runTeamRemove(args = [], { rootDir, println = console.log, errorln = console.error } = {}) {
|
|
468
|
+
if (args.length === 0) {
|
|
469
|
+
errorln('Usage: construct team remove <team-id> [--force]');
|
|
470
|
+
return 1;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
const teamId = args[0];
|
|
474
|
+
const forceRemove = args.includes('--force');
|
|
475
|
+
|
|
476
|
+
let registry;
|
|
477
|
+
try {
|
|
478
|
+
registry = loadRegistry({ rootDir });
|
|
479
|
+
} catch (err) {
|
|
480
|
+
errorln(`Error loading registry: ${err.message}`);
|
|
481
|
+
return 1;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
const team = registry.teams?.[teamId];
|
|
485
|
+
if (!team) {
|
|
486
|
+
errorln(`Team '${teamId}' not found in registry.`);
|
|
487
|
+
return 1;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// Check for policy references
|
|
491
|
+
const policyReferences = [];
|
|
492
|
+
for (const [policyId, policy] of Object.entries(registry.policies || {})) {
|
|
493
|
+
if (policy.teamOwner === teamId || (policy.requiresApprovalFrom || []).includes(teamId)) {
|
|
494
|
+
policyReferences.push(policyId);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
// Check for contract team boundaries
|
|
499
|
+
const contractReferences = [];
|
|
500
|
+
for (const [contractId, contract] of Object.entries(registry.contracts || {})) {
|
|
501
|
+
if (contract.teamBoundary?.producerTeam === teamId || contract.teamBoundary?.consumerTeam === teamId) {
|
|
502
|
+
contractReferences.push(contractId);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
if ((policyReferences.length || contractReferences.length) && !forceRemove) {
|
|
507
|
+
errorln(`Cannot remove team '${teamId}' — it is referenced by:`);
|
|
508
|
+
if (policyReferences.length) {
|
|
509
|
+
errorln(` Policies: ${policyReferences.join(', ')}`);
|
|
510
|
+
}
|
|
511
|
+
if (contractReferences.length) {
|
|
512
|
+
errorln(` Contracts: ${contractReferences.join(', ')}`);
|
|
513
|
+
}
|
|
514
|
+
errorln('');
|
|
515
|
+
errorln('Either reassign these policies/contracts first, or use --force to orphan them.');
|
|
516
|
+
return 1;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
if (forceRemove) {
|
|
520
|
+
if (policyReferences.length) {
|
|
521
|
+
println(`WARNING: Orphaning ${policyReferences.length} policy reference(s): ${policyReferences.join(', ')}`);
|
|
522
|
+
}
|
|
523
|
+
if (contractReferences.length) {
|
|
524
|
+
println(`WARNING: Orphaning ${contractReferences.length} contract reference(s): ${contractReferences.join(', ')}`);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
const specialistsOnTeam = Object.entries(registry.specialists || {})
|
|
529
|
+
.filter(([, spec]) => spec.team === teamId || spec.teamId === teamId)
|
|
530
|
+
.map(([id]) => id);
|
|
531
|
+
|
|
532
|
+
for (const specId of specialistsOnTeam) {
|
|
533
|
+
if (!removeOrgEntityFile(rootDir, 'specialists', specId)) {
|
|
534
|
+
errorln(`Could not remove specialist file for ${specId}`);
|
|
535
|
+
return 1;
|
|
536
|
+
}
|
|
537
|
+
println(`Removed specialist ${specId}`);
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
const teamFile = findTeamFile(rootDir, teamId);
|
|
541
|
+
if (!teamFile) {
|
|
542
|
+
errorln(`Could not locate org file for team '${teamId}'`);
|
|
543
|
+
return 1;
|
|
544
|
+
}
|
|
545
|
+
fs.unlinkSync(teamFile);
|
|
546
|
+
|
|
547
|
+
const { clearCache } = await import('./loader.mjs');
|
|
548
|
+
clearCache();
|
|
549
|
+
const validation = validateUnifiedRegistry(loadRegistry({ rootDir }));
|
|
550
|
+
if (!validation.ok) {
|
|
551
|
+
errorln('Validation failed after removal:');
|
|
552
|
+
for (const error of validation.errors) {
|
|
553
|
+
errorln(` ✗ ${error.id}: ${error.message}`);
|
|
554
|
+
}
|
|
555
|
+
return 1;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
println(`✓ Team '${teamId}' and ${specialistsOnTeam.length} specialist(s) removed.`);
|
|
559
|
+
return 0;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
export async function runSpecialistAdd(args = [], { rootDir, println = console.log, errorln = console.error } = {}) {
|
|
563
|
+
// Interactive wizard not available; requires terminal TTY and structured I/O.
|
|
564
|
+
// Fallback: guide user to manual JSON editing.
|
|
565
|
+
errorln('construct specialist add requires interactive terminal access.');
|
|
566
|
+
errorln('Edit specialists/org directly and run:');
|
|
567
|
+
errorln(' construct registry validate');
|
|
568
|
+
errorln('');
|
|
569
|
+
errorln('Expected format in specialists object:');
|
|
570
|
+
errorln(JSON.stringify({
|
|
571
|
+
'cx-new-specialist': {
|
|
572
|
+
name: 'new-specialist',
|
|
573
|
+
displayName: 'New Specialist',
|
|
574
|
+
team: 'team-id',
|
|
575
|
+
role: 'specialist-role',
|
|
576
|
+
skills: ['docs/skill-1', 'docs/skill-2'],
|
|
577
|
+
modelTier: 'reasoning',
|
|
578
|
+
events: ['event.type'],
|
|
579
|
+
fence: {
|
|
580
|
+
allowedPaths: ['lib/**'],
|
|
581
|
+
allowedCommands: ['bd create'],
|
|
582
|
+
approvalRequired: ['commit'],
|
|
583
|
+
},
|
|
584
|
+
},
|
|
585
|
+
}, null, 2));
|
|
586
|
+
return 1;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
export async function runSpecialistRemove(args = [], { rootDir, println = console.log, errorln = console.error } = {}) {
|
|
590
|
+
if (args.length === 0) {
|
|
591
|
+
errorln('Usage: construct specialist remove <specialist-id> [--force]');
|
|
592
|
+
return 1;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
const specialistId = args[0];
|
|
596
|
+
const forceRemove = args.includes('--force');
|
|
597
|
+
|
|
598
|
+
let registry;
|
|
599
|
+
try {
|
|
600
|
+
registry = loadRegistry({ rootDir });
|
|
601
|
+
} catch (err) {
|
|
602
|
+
errorln(`Error loading registry: ${err.message}`);
|
|
603
|
+
return 1;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
const specialist = registry.specialists?.[specialistId];
|
|
607
|
+
if (!specialist) {
|
|
608
|
+
errorln(`Specialist '${specialistId}' not found in registry.`);
|
|
609
|
+
return 1;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
// Check for contract references
|
|
613
|
+
const contractReferences = [];
|
|
614
|
+
for (const [contractId, contract] of Object.entries(registry.contracts || {})) {
|
|
615
|
+
if (contract.producer === specialistId || contract.consumer === specialistId) {
|
|
616
|
+
contractReferences.push(contractId);
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
if (contractReferences.length && !forceRemove) {
|
|
621
|
+
errorln(`Cannot remove specialist '${specialistId}' — it is referenced by contracts:`);
|
|
622
|
+
errorln(` ${contractReferences.join(', ')}`);
|
|
623
|
+
errorln('');
|
|
624
|
+
errorln('Either reassign these contracts first, or use --force.');
|
|
625
|
+
return 1;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
const teamId = specialist.team || specialist.teamId;
|
|
629
|
+
const teamSpecialists = Object.entries(registry.specialists || {})
|
|
630
|
+
.filter(([, spec]) => (spec.team || spec.teamId) === teamId)
|
|
631
|
+
.map(([id]) => id);
|
|
632
|
+
|
|
633
|
+
if (teamSpecialists.length === 1) {
|
|
634
|
+
println(`WARNING: This is the last specialist on team '${teamId}'. Team will be understaffed.`);
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
const team = registry.teams?.[teamId];
|
|
638
|
+
if (team?.owner === specialist.role) {
|
|
639
|
+
println(`WARNING: This specialist has the owner role for team '${teamId}'. Team will have no owner.`);
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
if (!removeOrgEntityFile(rootDir, 'specialists', specialistId)) {
|
|
643
|
+
errorln(`Could not locate org file for specialist '${specialistId}'`);
|
|
644
|
+
return 1;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
const { clearCache } = await import('./loader.mjs');
|
|
648
|
+
clearCache();
|
|
649
|
+
const validation = validateUnifiedRegistry(loadRegistry({ rootDir }));
|
|
650
|
+
if (!validation.ok) {
|
|
651
|
+
errorln('Validation failed after removal:');
|
|
652
|
+
for (const error of validation.errors) {
|
|
653
|
+
errorln(` ✗ ${error.id}: ${error.message}`);
|
|
654
|
+
}
|
|
655
|
+
return 1;
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
println(`✓ Specialist '${specialistId}' removed.`);
|
|
659
|
+
|
|
660
|
+
const promptFile = path.join(rootDir, specialist.promptFile || `specialists/prompts/${specialistId}.md`);
|
|
661
|
+
if (fs.existsSync(promptFile)) {
|
|
662
|
+
println(` (orphaned prompt: rm ${path.relative(rootDir, promptFile)})`);
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
return 0;
|
|
666
|
+
}
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import fs from 'node:fs';
|
|
9
9
|
import path from 'node:path';
|
|
10
10
|
import { fileURLToPath } from 'node:url';
|
|
11
|
+
import { loadRegistry } from '../registry/loader.mjs';
|
|
11
12
|
|
|
12
13
|
const MODULE_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
13
14
|
const REPO_ROOT = path.resolve(MODULE_DIR, '..', '..');
|
|
@@ -56,11 +57,11 @@ function collectProfileRoles(root) {
|
|
|
56
57
|
* Classify bound-orphans for maintainer approval (bind / merge / composer / review).
|
|
57
58
|
*/
|
|
58
59
|
export function triageBoundOrphans({ rootDir = REPO_ROOT } = {}) {
|
|
59
|
-
const registry =
|
|
60
|
+
const registry = loadRegistry({ rootDir });
|
|
60
61
|
const declared = new Set();
|
|
61
|
-
for (const s of registry.specialists ??
|
|
62
|
+
for (const s of Object.values(registry.specialists ?? {})) for (const sk of s.skills ?? []) declared.add(sk);
|
|
62
63
|
|
|
63
|
-
const specialistNames = new Set((registry.specialists ??
|
|
64
|
+
const specialistNames = new Set(Object.values(registry.specialists ?? {}).map((s) => s.name));
|
|
64
65
|
const profileRoles = collectProfileRoles(rootDir);
|
|
65
66
|
const all = collectSkillFiles(path.join(rootDir, 'skills'));
|
|
66
67
|
|
|
@@ -117,7 +118,7 @@ export function formatConsolidationProposalMarkdown(triage, { date = new Date().
|
|
|
117
118
|
'## Summary',
|
|
118
119
|
'',
|
|
119
120
|
`- **${triage.fileCount}** skill files on disk`,
|
|
120
|
-
`- **${triage.declaredCount}** declared in specialists/
|
|
121
|
+
`- **${triage.declaredCount}** declared in specialists/org`,
|
|
121
122
|
`- **${triage.boundOrphanCount}** registry bound-orphans (not declared by any specialist)`,
|
|
122
123
|
`- **${triage.composerReachableCount}** composer-reachable (B-composer — intentional via prompt composer)`,
|
|
123
124
|
`- **${triage.trueOrphanCount}** true orphans (C-merge + D-review — need maintainer action)`,
|