@geraldmaron/construct 1.4.1 → 1.5.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/.env.example +36 -0
- package/README.md +41 -7
- package/bin/construct +1029 -65
- package/bin/construct-postinstall.mjs +27 -2
- package/config/tag-vocabulary.json +264 -0
- package/examples/distribution/sources/deck-one-pager.md +1 -1
- package/lib/acp/server.mjs +21 -7
- package/lib/adapters-sync.mjs +2 -1
- package/lib/audit-trail.mjs +185 -2
- package/lib/beads/auto-close.mjs +2 -1
- package/lib/beads/drift.mjs +2 -1
- package/lib/bridges/copilot-proxy.mjs +20 -5
- package/lib/certification/skill-inventory.mjs +10 -1
- package/lib/cli/approvals.mjs +147 -0
- package/lib/cli-commands.mjs +105 -14
- package/lib/comment-lint.mjs +127 -8
- package/lib/config/schema.mjs +7 -30
- package/lib/config/source-target-registry.mjs +70 -0
- package/lib/config/source-targets.mjs +179 -205
- package/lib/contracts/coverage.mjs +77 -0
- package/lib/contracts/validate.mjs +102 -13
- package/lib/contracts/violation-log.mjs +50 -4
- package/lib/db/migrate.mjs +69 -0
- package/lib/db/migrations/001_orchestration_runs.sql +9 -0
- package/lib/db/migrations/002_queue_provider.sql +50 -0
- package/lib/db/migrations/003_worker_registry.sql +17 -0
- package/lib/db/migrations/004_trace_events.sql +16 -0
- package/lib/db/migrations/005_shared_memory.sql +15 -0
- package/lib/db/migrations/006_orchestration_runs_tenant.sql +5 -0
- package/lib/decisions/enforced-baseline.json +0 -2
- package/lib/decisions/registry.mjs +3 -2
- package/lib/deployment/parity-contract.mjs +1 -1
- package/lib/deployment-mode.mjs +78 -2
- package/lib/diagram-export.mjs +10 -1
- package/lib/distill.mjs +5 -2
- package/lib/docs-verify.mjs +2 -1
- package/lib/doctor/cli.mjs +1 -0
- package/lib/doctor/command-on-path.mjs +24 -0
- package/lib/doctor/diagnosis.mjs +109 -0
- package/lib/doctor/embedding-health.mjs +50 -0
- package/lib/doctor/engine-health.mjs +93 -0
- package/lib/doctor/graph-validate.mjs +47 -0
- package/lib/doctor/index.mjs +23 -4
- package/lib/doctor/sidecar-providers.mjs +56 -0
- package/lib/doctor/watchers/consistency.mjs +93 -1
- package/lib/doctor/watchers/cx-budget.mjs +1 -1
- package/lib/doctor/watchers/graph-staleness.mjs +1 -1
- package/lib/doctor/watchers/mcp-protocol.mjs +17 -0
- package/lib/doctor/watchers/oracle-liveness.mjs +92 -0
- package/lib/doctor/watchers/orchestration-runs.mjs +108 -0
- package/lib/doctor/watchers/provider-breaker.mjs +78 -0
- package/lib/document-export.mjs +52 -27
- package/lib/document-extract/docling-client.mjs +9 -5
- package/lib/document-extract/docling-sidecar.py +30 -0
- package/lib/document-extract.mjs +1 -1
- package/lib/document-ingest.mjs +9 -0
- package/lib/embed/approval-queue.mjs +184 -88
- package/lib/embed/authority-guard.mjs +65 -2
- package/lib/embed/capability-jobs.mjs +365 -0
- package/lib/embed/capability-lifecycle.mjs +219 -0
- package/lib/embed/capability-loader.mjs +303 -0
- package/lib/embed/capability-runtime.mjs +58 -0
- package/lib/embed/cli.mjs +185 -8
- package/lib/embed/config.mjs +4 -0
- package/lib/embed/daemon.mjs +125 -6
- package/lib/embed/demand-fetch.mjs +190 -54
- package/lib/embed/inbox.mjs +12 -10
- package/lib/embed/presets/ops-triage.mjs +236 -0
- package/lib/embed/presets/pm-feedback.mjs +341 -0
- package/lib/embed/presets/tpm.mjs +401 -0
- package/lib/embed/providers/jira.mjs +72 -1
- package/lib/embed/providers/registry.mjs +140 -33
- package/lib/embed/worker.mjs +5 -0
- package/lib/embedded-contract/capability.mjs +4 -0
- package/lib/embedded-contract/execution.mjs +1 -1
- package/lib/embedded-contract/model-resolve.mjs +53 -3
- package/lib/embedded-contract/workflow-defs.mjs +48 -73
- package/lib/embedded-contract/workflow-invoke.mjs +144 -4
- package/lib/embedded-contract/workflows/architecture-review.manifest.json +15 -0
- package/lib/embedded-contract/workflows/data-structure.manifest.json +14 -0
- package/lib/embedded-contract/workflows/evidence-ingest.manifest.json +14 -0
- package/lib/embedded-contract/workflows/memo-draft.manifest.json +14 -0
- package/lib/embedded-contract/workflows/operations-triage.manifest.json +18 -0
- package/lib/embedded-contract/workflows/operations.manifest.json +18 -0
- package/lib/embedded-contract/workflows/pm-feedback.manifest.json +18 -0
- package/lib/embedded-contract/workflows/prd-draft.manifest.json +15 -0
- package/lib/embedded-contract/workflows/proposal-review.manifest.json +15 -0
- package/lib/embedded-contract/workflows/research-synthesis.manifest.json +14 -0
- package/lib/embedded-contract/workflows/risk-review.manifest.json +15 -0
- package/lib/embedded-contract/workflows/structure-notes.manifest.json +14 -0
- package/lib/embedded-contract/workflows/transcript-process.manifest.json +14 -0
- package/lib/embedded-contract/workflows/triage.manifest.json +14 -0
- package/lib/env-config.mjs +60 -11
- package/lib/export-validate.mjs +34 -2
- package/lib/extensions/index.mjs +27 -0
- package/lib/extensions/loader.mjs +120 -0
- package/lib/extensions/manifest-schema.mjs +141 -0
- package/lib/extensions/manifests/anthropic.manifest.json +12 -0
- package/lib/extensions/manifests/atlassian-confluence.manifest.json +12 -0
- package/lib/extensions/manifests/atlassian-jira.manifest.json +53 -0
- package/lib/extensions/manifests/directory.manifest.json +12 -0
- package/lib/extensions/manifests/docling.manifest.json +37 -0
- package/lib/extensions/manifests/echo.manifest.json +8 -0
- package/lib/extensions/manifests/feedback.manifest.json +12 -0
- package/lib/extensions/manifests/github-copilot.manifest.json +12 -0
- package/lib/extensions/manifests/github.manifest.json +52 -0
- package/lib/extensions/manifests/linear.manifest.json +50 -0
- package/lib/extensions/manifests/local.manifest.json +12 -0
- package/lib/extensions/manifests/ollama.manifest.json +12 -0
- package/lib/extensions/manifests/openai.manifest.json +12 -0
- package/lib/extensions/manifests/openrouter-anthropic.manifest.json +12 -0
- package/lib/extensions/manifests/openrouter-deepseek.manifest.json +12 -0
- package/lib/extensions/manifests/openrouter-google.manifest.json +12 -0
- package/lib/extensions/manifests/openrouter-llama.manifest.json +12 -0
- package/lib/extensions/manifests/openrouter-qwen.manifest.json +12 -0
- package/lib/extensions/manifests/openrouter.manifest.json +12 -0
- package/lib/extensions/manifests/postgres.manifest.json +12 -0
- package/lib/extensions/manifests/salesforce.manifest.json +12 -0
- package/lib/extensions/manifests/slack.manifest.json +58 -0
- package/lib/extensions/manifests/whisper.manifest.json +36 -0
- package/lib/extensions/validate.mjs +175 -0
- package/lib/features.mjs +13 -9
- package/lib/flows/checkpoint.mjs +184 -0
- package/lib/flows/constants.mjs +27 -0
- package/lib/flows/define.mjs +146 -0
- package/lib/flows/engine.mjs +249 -0
- package/lib/flows/errors.mjs +19 -0
- package/lib/flows/index.mjs +27 -0
- package/lib/flows/joins.mjs +18 -0
- package/lib/flows/schema.mjs +90 -0
- package/lib/flows/state.mjs +31 -0
- package/lib/frameworks/loader.mjs +99 -0
- package/lib/frameworks/schema.mjs +157 -0
- package/lib/graph/build-from-corpus.mjs +67 -0
- package/lib/graph/build-from-embed.mjs +82 -0
- package/lib/graph/build-from-registry.mjs +164 -3
- package/lib/graph/cli.mjs +456 -12
- package/lib/graph/gap-queries.mjs +156 -0
- package/lib/graph/gaps.mjs +41 -0
- package/lib/graph/impacted.mjs +129 -0
- package/lib/graph/runtime-evidence.mjs +177 -0
- package/lib/graph/security-coverage.mjs +113 -0
- package/lib/graph/staleness.mjs +241 -10
- package/lib/graph/store.mjs +24 -7
- package/lib/graph/validate.mjs +161 -0
- package/lib/headhunt.mjs +9 -8
- package/lib/health-check.mjs +15 -7
- package/lib/hook-health.mjs +1 -1
- package/lib/hooks/agent-tracker.mjs +10 -4
- package/lib/hooks/edit-guard.mjs +3 -3
- package/lib/hooks/graph-impact-advisory.mjs +2 -2
- package/lib/hooks/guard-bash.mjs +41 -15
- package/lib/hooks/mcp-health-check.mjs +11 -4
- package/lib/hooks/model-fallback.mjs +50 -6
- package/lib/hooks/orchestration-dispatch-guard.mjs +14 -3
- package/lib/hooks/pre-compact.mjs +181 -173
- package/lib/hooks/rule-verifier.mjs +2 -1
- package/lib/hooks/session-reflect.mjs +2 -1
- package/lib/hooks/session-start.mjs +3 -3
- package/lib/host/readiness.mjs +109 -0
- package/lib/host-capabilities.mjs +13 -2
- package/lib/host-disposition.mjs +29 -8
- package/lib/identity.mjs +92 -0
- package/lib/ingest/degraded-extract.mjs +96 -0
- package/lib/ingest/docling-remote.mjs +2 -1
- package/lib/ingest/provider-extract.mjs +7 -19
- package/lib/ingest/sidecar-providers.mjs +196 -0
- package/lib/ingest-tooling.mjs +11 -5
- package/lib/init-unified.mjs +55 -38
- package/lib/init-update.mjs +2 -1
- package/lib/install/legacy-global-cleanup.mjs +25 -0
- package/lib/install/stage-project.mjs +41 -4
- package/lib/intake/daemon.mjs +99 -8
- package/lib/intake/git-queue.mjs +110 -38
- package/lib/intake/prepare.mjs +2 -0
- package/lib/intake/queue-registry.mjs +50 -0
- package/lib/intake/queue.mjs +133 -17
- package/lib/intake/session-prelude.mjs +57 -8
- package/lib/integrations/intake-integrations.mjs +61 -111
- package/lib/intent-classifier.mjs +43 -5
- package/lib/libreoffice-export.mjs +8 -8
- package/lib/logging/rotate.mjs +5 -4
- package/lib/mcp/broker.mjs +332 -17
- package/lib/mcp/denied-store.mjs +95 -0
- package/lib/mcp/destructive-approval.mjs +57 -0
- package/lib/mcp/destructive-gate.mjs +30 -0
- package/lib/mcp/dispatch-envelope.mjs +199 -0
- package/lib/mcp/memory-bridge.mjs +2 -1
- package/lib/mcp/server.mjs +185 -1268
- package/lib/mcp/tool-definitions-memory.mjs +376 -0
- package/lib/mcp/tool-definitions-project.mjs +271 -0
- package/lib/mcp/tool-definitions-skills.mjs +311 -0
- package/lib/mcp/tool-definitions-workflow.mjs +398 -0
- package/lib/mcp/tool-definitions.mjs +25 -0
- package/lib/mcp/tool-rate-limit.mjs +47 -0
- package/lib/mcp/tool-registry.mjs +107 -0
- package/lib/mcp/tool-safety.mjs +95 -0
- package/lib/mcp/tool-surface-parity.mjs +60 -0
- package/lib/mcp/tools/orchestration-delegation-next.tool.mjs +68 -0
- package/lib/mcp/tools/orchestration-run.mjs +197 -19
- package/lib/mcp/tools/orchestration-task-result.tool.mjs +77 -0
- package/lib/mcp/tools/project.mjs +25 -8
- package/lib/mcp/tools/provider-write.mjs +187 -0
- package/lib/mcp/tools/scope.mjs +0 -3
- package/lib/mcp/tools/skills.mjs +1 -1
- package/lib/mcp/tools/storage.mjs +2 -2
- package/lib/mcp/tools/web-search-governance.mjs +96 -0
- package/lib/mcp/tools/web-search.mjs +5 -76
- package/lib/mcp/transport/auth.mjs +238 -0
- package/lib/mcp/transport/http.mjs +136 -0
- package/lib/mcp/transport/mode.mjs +31 -0
- package/lib/mcp/transport/stdio.mjs +22 -0
- package/lib/mcp-catalog.json +4 -4
- package/lib/mcp-manager.mjs +34 -23
- package/lib/mcp-platform-config.mjs +53 -20
- package/lib/mode-capabilities.mjs +109 -0
- package/lib/model-router.mjs +42 -9
- package/lib/models/catalog.mjs +40 -1
- package/lib/net-guard.mjs +247 -0
- package/lib/observation-store.mjs +6 -2
- package/lib/ollama/provision-context.mjs +2 -1
- package/lib/opencode-config.mjs +22 -3
- package/lib/opencode-runtime-plugin.mjs +120 -2
- package/lib/oracle/actions.mjs +204 -10
- package/lib/oracle/cli.mjs +24 -3
- package/lib/oracle/daemon-entry.mjs +6 -0
- package/lib/oracle/dispatch.mjs +2 -1
- package/lib/oracle/execute.mjs +2 -2
- package/lib/oracle/gaps.mjs +3 -3
- package/lib/oracle/heartbeat.mjs +53 -0
- package/lib/oracle/read-model.mjs +69 -13
- package/lib/oracle/reconcile.mjs +3 -46
- package/lib/oracle/routing.mjs +37 -31
- package/lib/oracle/synthesize.mjs +1 -1
- package/lib/orchestration/classification.mjs +434 -0
- package/lib/orchestration/delegation-flow.mjs +132 -0
- package/lib/orchestration/flow-selection.mjs +418 -0
- package/lib/orchestration/gates.mjs +244 -0
- package/lib/orchestration/host-sampling.mjs +121 -0
- package/lib/orchestration/policy-constants.mjs +48 -0
- package/lib/orchestration/provider-outcome.mjs +197 -0
- package/lib/orchestration/readiness.mjs +114 -13
- package/lib/orchestration/run-store-postgres.mjs +32 -26
- package/lib/orchestration/run-store-sqlite.mjs +8 -14
- package/lib/orchestration/run-store.mjs +25 -12
- package/lib/orchestration/runtime.mjs +508 -62
- package/lib/orchestration/store.mjs +87 -12
- package/lib/orchestration/trace-store.mjs +125 -0
- package/lib/orchestration/web-capability.mjs +60 -0
- package/lib/orchestration/worker-runtime.mjs +162 -0
- package/lib/orchestration/worker.mjs +763 -80
- package/lib/orchestration-policy.mjs +67 -1111
- package/lib/output-quality.mjs +61 -2
- package/lib/packs/cli.mjs +144 -0
- package/lib/packs/core-pack.mjs +112 -0
- package/lib/packs/enablement.mjs +187 -0
- package/lib/packs/index.mjs +23 -0
- package/lib/packs/loader.mjs +176 -0
- package/lib/packs/manifest-schema.mjs +58 -0
- package/lib/packs/prompts.mjs +120 -0
- package/lib/packs/validate.mjs +208 -0
- package/lib/path-policy.mjs +56 -0
- package/lib/plugin-registry.mjs +4 -5
- package/lib/policy/audit-gate.mjs +42 -0
- package/lib/policy/consumption-budget.mjs +149 -0
- package/lib/policy/engine.mjs +81 -6
- package/lib/policy/role-authority.mjs +89 -0
- package/lib/prompt-composer.js +19 -3
- package/lib/providers/contract/adapters/confluence/governed-write.mjs +215 -0
- package/lib/providers/contract/adapters/confluence/manifest.json +17 -0
- package/lib/providers/contract/adapters/confluence/transport.mjs +135 -0
- package/lib/providers/contract/adapters/github/governed-write.mjs +121 -0
- package/lib/providers/contract/adapters/github/index.mjs +38 -3
- package/lib/providers/contract/adapters/jira/adf.mjs +151 -0
- package/lib/providers/contract/adapters/jira/createmeta.mjs +143 -0
- package/lib/providers/contract/adapters/jira/governed-write.mjs +182 -0
- package/lib/providers/contract/adapters/jira/manifest.json +17 -0
- package/lib/providers/contract/adapters/jira/transport.mjs +119 -0
- package/lib/providers/contract.mjs +207 -0
- package/lib/providers/credential-bootstrap.mjs +7 -4
- package/lib/providers/credential-sources.mjs +14 -2
- package/lib/providers/directory/index.mjs +261 -0
- package/lib/providers/feedback/index.mjs +392 -0
- package/lib/providers/filter-audit.mjs +68 -0
- package/lib/providers/filter-schema.mjs +54 -0
- package/lib/providers/github/index.mjs +31 -0
- package/lib/providers/instance-config.mjs +215 -0
- package/lib/providers/op-locate.mjs +96 -0
- package/lib/providers/op-run.mjs +64 -9
- package/lib/providers/registry.mjs +26 -3
- package/lib/providers/secret-audit-wiring.mjs +41 -18
- package/lib/providers/secret-resolver.mjs +263 -31
- package/lib/publish-template.mjs +6 -3
- package/lib/publish-tooling.mjs +4 -0
- package/lib/publish.mjs +32 -4
- package/lib/queue/pg-queue.mjs +395 -0
- package/lib/reflect.mjs +2 -1
- package/lib/registry/assemble.mjs +28 -2
- package/lib/registry/catalog.mjs +6 -0
- package/lib/registry/consolidation.mjs +8 -1
- package/lib/registry/custom-scaffold.mjs +200 -0
- package/lib/registry/custom-schema.mjs +137 -0
- package/lib/registry/loader.mjs +15 -4
- package/lib/registry/manifests/format-engines.default.json +15 -0
- package/lib/registry/manifests/surface-map.default.json +46 -0
- package/lib/registry/retired-paths.mjs +1 -0
- package/lib/registry/surface-map.mjs +100 -50
- package/lib/registry/validate.mjs +3 -3
- package/lib/render-visual-check.mjs +240 -0
- package/lib/resources/budget.mjs +40 -17
- package/lib/roles/flavor-bindings.mjs +36 -14
- package/lib/roles/gateway.mjs +15 -8
- package/lib/roots.mjs +107 -0
- package/lib/runtime/uv-bootstrap.mjs +32 -6
- package/lib/runtime/whisper-bootstrap.mjs +11 -3
- package/lib/runtime-env.mjs +5 -2
- package/lib/sandbox.mjs +1 -1
- package/lib/scheduler/index.mjs +8 -20
- package/lib/schema-infer.mjs +31 -2
- package/lib/scopes/lifecycle.mjs +29 -25
- package/lib/scopes/loader.mjs +49 -12
- package/lib/scopes/teams.mjs +1 -1
- package/lib/security/ingest-boundary.mjs +80 -0
- package/lib/security/recall-wrapper.mjs +99 -0
- package/lib/security/trust.mjs +132 -0
- package/lib/service-manager.mjs +93 -24
- package/lib/setup.mjs +41 -23
- package/lib/skills-apply.mjs +4 -2
- package/lib/specialists/postconditions.mjs +2 -2
- package/lib/state-root.mjs +147 -0
- package/lib/status.mjs +597 -6
- package/lib/storage/admin.mjs +84 -7
- package/lib/storage/backend-registry.mjs +73 -0
- package/lib/storage/backend.mjs +63 -9
- package/lib/storage/embeddings-openai.mjs +12 -2
- package/lib/storage/hybrid-query.mjs +11 -3
- package/lib/storage/retrieval-hardening.mjs +384 -0
- package/lib/storage/shared-memory.mjs +158 -0
- package/lib/storage/vector-client.mjs +69 -2
- package/lib/team/health.mjs +72 -0
- package/lib/telemetry/backends/local.mjs +9 -3
- package/lib/telemetry/client.mjs +3 -7
- package/lib/template-registry.mjs +10 -12
- package/lib/tenant/context.mjs +82 -0
- package/lib/tenant/isolation.mjs +129 -0
- package/lib/test-corpus-inventory.mjs +103 -2
- package/lib/uninstall/uninstall.mjs +78 -12
- package/lib/validator.mjs +4 -6
- package/lib/worker/entrypoint.mjs +2 -1
- package/lib/worker/run.mjs +2 -2
- package/lib/worker/trace.mjs +5 -11
- package/lib/workflow-state.mjs +52 -8
- package/lib/workflows/liveness.mjs +203 -0
- package/lib/workflows/loader.mjs +177 -0
- package/lib/workflows/manifest-schema.mjs +71 -0
- package/lib/workflows/surface-parity.mjs +118 -0
- package/lib/workflows/validate.mjs +127 -0
- package/lib/writes/control-plane.mjs +140 -0
- package/lib/writes/envelope.mjs +206 -0
- package/lib/writes/sent-log.mjs +86 -0
- package/lib/writes/write-intent.mjs +109 -0
- package/package.json +20 -7
- package/personas/construct.md +12 -3
- package/registry/agent-manifest.json +117 -0
- package/registry/capabilities.json +1987 -0
- package/rules/common/comments.md +1 -0
- package/schemas/brand-voice.schema.json +24 -0
- package/schemas/capability-registry.schema.json +72 -0
- package/schemas/certification-run.schema.json +130 -0
- package/schemas/demo-recording.schema.json +46 -0
- package/schemas/eval-dataset.schema.json +79 -0
- package/schemas/execution-capability-profile.schema.json +46 -0
- package/schemas/execution-policy.schema.json +114 -0
- package/schemas/improvement-proposal.schema.json +65 -0
- package/schemas/mcp-tool-output.schema.json +61 -0
- package/schemas/platform-capabilities.schema.json +83 -0
- package/schemas/project-config.schema.json +215 -0
- package/schemas/project-demo.schema.json +60 -0
- package/schemas/provider-behavior-matrix.schema.json +91 -0
- package/schemas/scope.schema.json +197 -0
- package/schemas/specialist-trace.schema.json +107 -0
- package/schemas/team.schema.json +99 -0
- package/schemas/unified-registry.schema.json +546 -0
- package/scripts/sync-specialists.mjs +336 -106
- package/skills/docs/adr-workflow.md +1 -1
- package/skills/docs/codebase-research-workflow.md +3 -3
- package/skills/docs/prd-workflow.md +5 -6
- package/skills/docs/runbook-workflow.md +2 -2
- package/skills/docs/user-research-workflow.md +3 -3
- package/skills/operating/fleet-health-routing.md +77 -0
- package/skills/roles/ai-engineer.md +1 -1
- package/skills/roles/architect.md +0 -1
- package/skills/roles/business-strategist.md +1 -1
- package/skills/roles/data-analyst.telemetry.md +1 -1
- package/skills/roles/data-engineer.md +1 -1
- package/skills/roles/data-engineer.pipeline.md +1 -1
- package/skills/roles/data-engineer.vector-retrieval.md +1 -2
- package/skills/roles/data-engineer.warehouse.md +1 -1
- package/skills/roles/designer.accessibility.md +1 -1
- package/skills/roles/designer.md +0 -1
- package/skills/roles/devil-advocate.md +1 -1
- package/skills/roles/docs-keeper.md +1 -1
- package/skills/roles/evaluator.md +1 -1
- package/skills/roles/explorer.md +1 -1
- package/skills/roles/platform-engineer.md +1 -1
- package/skills/roles/qa.ai-eval.md +1 -2
- package/skills/roles/qa.api-contract.md +0 -1
- package/skills/roles/qa.data-pipeline.md +0 -1
- package/skills/roles/qa.md +0 -1
- package/skills/roles/qa.web-ui.md +0 -1
- package/skills/roles/release-manager.md +1 -1
- package/skills/roles/researcher.md +0 -2
- package/skills/roles/reviewer.md +0 -3
- package/skills/roles/security.ai.md +1 -1
- package/skills/roles/security.legal-compliance.md +1 -1
- package/skills/roles/security.md +0 -1
- package/skills/roles/security.privacy.md +0 -1
- package/skills/roles/security.supply-chain.md +1 -1
- package/skills/roles/sre.md +1 -1
- package/skills/roles/test-automation.md +1 -1
- package/skills/roles/trace-reviewer.md +1 -1
- package/skills/roles/ux-researcher.md +1 -1
- package/specialists/artifact-manifest.json +36 -36
- package/specialists/org/contracts/accessibility-to-qa.json +11 -3
- package/specialists/org/contracts/any-to-business-strategist.json +19 -7
- package/specialists/org/contracts/any-to-debugger.json +7 -1
- package/specialists/org/contracts/any-to-designer.json +7 -1
- package/specialists/org/contracts/any-to-docs-keeper.json +18 -4
- package/specialists/org/contracts/any-to-explorer.json +13 -4
- package/specialists/org/contracts/any-to-sre-incident.json +6 -2
- package/specialists/org/contracts/any-to-trace-reviewer.json +16 -4
- package/specialists/org/contracts/architect-to-ai-engineer.json +6 -2
- package/specialists/org/contracts/architect-to-data-engineer.json +17 -5
- package/specialists/org/contracts/architect-to-devil-advocate.json +11 -3
- package/specialists/org/contracts/architect-to-engineer.json +20 -4
- package/specialists/org/contracts/architect-to-evaluator.json +15 -5
- package/specialists/org/contracts/architect-to-legal-compliance.json +15 -5
- package/specialists/org/contracts/architect-to-operations.json +17 -4
- package/specialists/org/contracts/architect-to-platform-engineer.json +20 -4
- package/specialists/org/contracts/construct-to-orchestrator.json +10 -2
- package/specialists/org/contracts/data-analyst-to-product-manager.json +11 -2
- package/specialists/org/contracts/engineer-to-qa.json +20 -4
- package/specialists/org/contracts/engineer-to-reviewer.json +29 -5
- package/specialists/org/contracts/explorer-to-engineer.json +11 -3
- package/specialists/org/contracts/legal-compliance-to-release-manager.json +12 -4
- package/specialists/org/contracts/operations-to-user.json +53 -0
- package/specialists/org/contracts/operations-triage-output.json +53 -0
- package/specialists/org/contracts/pm-requirements-candidates.json +53 -0
- package/specialists/org/contracts/product-manager-to-architect.json +30 -10
- package/specialists/org/contracts/product-manager-to-data-analyst.json +13 -3
- package/specialists/org/contracts/product-manager-to-ux-researcher.json +15 -5
- package/specialists/org/contracts/qa-to-release-manager.json +11 -3
- package/specialists/org/contracts/researcher-to-architect.json +10 -2
- package/specialists/org/contracts/researcher-to-product-manager.json +16 -5
- package/specialists/org/contracts/reviewer-to-security.json +17 -3
- package/specialists/org/contracts/test-automation-to-engineer.json +11 -3
- package/specialists/org/contracts/trace-reviewer-to-sre.json +12 -4
- package/specialists/org/contracts/user-to-construct.json +11 -4
- package/specialists/org/frameworks/cx-architect-constraint-option-failure.md +66 -0
- package/specialists/org/frameworks/cx-engineer-feasibility-blast-radius.md +66 -0
- package/specialists/org/frameworks/cx-ops-dependency-sequencing.md +74 -0
- package/specialists/org/frameworks/cx-pm-value-tradeoff.md +66 -0
- package/specialists/org/frameworks/cx-qa-risk-based-coverage.md +69 -0
- package/specialists/org/groups/engineering-group.json +2 -6
- package/specialists/org/groups/governance-group.json +2 -3
- package/specialists/org/groups/operations-group.json +5 -8
- package/specialists/org/groups/product-group.json +4 -8
- package/specialists/org/groups/quality-group.json +3 -7
- package/specialists/org/groups/strategy-group.json +6 -9
- package/specialists/org/models.json.example +8 -0
- package/specialists/org/scopes/creative.json +6 -1
- package/specialists/org/scopes/operations.json +7 -1
- package/specialists/org/scopes/research.json +6 -1
- package/specialists/org/scopes/rnd.json +7 -1
- package/specialists/org/specialists/cx-architect.json +27 -8
- package/specialists/org/specialists/cx-designer.json +22 -8
- package/specialists/org/specialists/cx-engineer.json +71 -7
- package/specialists/org/specialists/cx-operations.json +89 -15
- package/specialists/org/specialists/cx-orchestrator.json +16 -4
- package/specialists/org/specialists/cx-product-manager.json +28 -9
- package/specialists/org/specialists/cx-qa.json +16 -6
- package/specialists/org/specialists/cx-researcher.json +41 -7
- package/specialists/org/specialists/cx-reviewer.json +61 -11
- package/specialists/org/specialists/cx-security.json +30 -10
- package/specialists/org/teams/design-team.json +3 -4
- package/specialists/org/teams/engineering-team.json +3 -10
- package/specialists/org/teams/governance-team.json +3 -5
- package/specialists/org/teams/operations-team.json +6 -12
- package/specialists/org/teams/product-management-team.json +2 -3
- package/specialists/org/teams/quality-team.json +4 -12
- package/specialists/org/teams/research-team.json +3 -3
- package/specialists/org/teams/strategy-team.json +7 -14
- package/specialists/prompts/cx-architect.md +20 -1
- package/specialists/prompts/cx-data-analyst.md +1 -1
- package/specialists/prompts/cx-designer.md +12 -4
- package/specialists/prompts/cx-engineer.md +15 -1
- package/specialists/prompts/cx-operations.md +14 -2
- package/specialists/prompts/cx-orchestrator.md +9 -5
- package/specialists/prompts/cx-product-manager.md +5 -1
- package/specialists/prompts/cx-qa.md +6 -2
- package/specialists/prompts/cx-researcher.md +27 -31
- package/specialists/prompts/cx-reviewer.md +19 -1
- package/specialists/prompts/cx-security.md +5 -1
- package/templates/distribution/construct-brand.typ +123 -59
- package/templates/distribution/construct-decision.typ +6 -3
- package/templates/distribution/construct-pdf.typ +11 -4
- package/templates/distribution/construct-prd.typ +6 -3
- package/templates/distribution/construct-research.typ +7 -4
- package/vendor/pandoc-ext/README.md +3 -0
- package/vendor/pandoc-ext/diagram.lua +687 -0
- package/lib/providers/contract/adapters/git/index.mjs +0 -115
- package/lib/providers/contract/adapters/slack/index.mjs +0 -175
- package/specialists/org/contracts/business-strategist-to-product-manager.json +0 -39
- package/specialists/org/contracts/construct-to-rd-lead.json +0 -53
- package/specialists/org/contracts/data-engineer-to-platform-engineer.json +0 -36
- package/specialists/org/contracts/designer-to-accessibility.json +0 -36
- package/specialists/org/contracts/platform-engineer-to-engineer.json +0 -31
- package/specialists/org/contracts/qa-to-test-automation.json +0 -42
- package/specialists/org/contracts/rd-lead-to-architect.json +0 -38
- package/specialists/org/contracts/sre-to-release-manager.json +0 -36
- package/specialists/org/specialists/cx-accessibility.json +0 -69
- package/specialists/org/specialists/cx-ai-engineer.json +0 -75
- package/specialists/org/specialists/cx-business-strategist.json +0 -72
- package/specialists/org/specialists/cx-data-engineer.json +0 -71
- package/specialists/org/specialists/cx-devil-advocate.json +0 -71
- package/specialists/org/specialists/cx-docs-keeper.json +0 -82
- package/specialists/org/specialists/cx-evaluator.json +0 -69
- package/specialists/org/specialists/cx-explorer.json +0 -69
- package/specialists/org/specialists/cx-legal-compliance.json +0 -74
- package/specialists/org/specialists/cx-oracle.json +0 -46
- package/specialists/org/specialists/cx-platform-engineer.json +0 -80
- package/specialists/org/specialists/cx-rd-lead.json +0 -73
- package/specialists/org/specialists/cx-release-manager.json +0 -77
- package/specialists/org/specialists/cx-sre.json +0 -94
- package/specialists/org/specialists/cx-test-automation.json +0 -69
- package/specialists/org/specialists/cx-trace-reviewer.json +0 -69
- package/specialists/org/specialists/cx-ux-researcher.json +0 -68
- package/specialists/org/teams/accessibility-team.json +0 -39
- package/specialists/org/teams/ux-research-team.json +0 -39
- package/specialists/prompts/cx-accessibility.md +0 -55
- package/specialists/prompts/cx-ai-engineer.md +0 -124
- package/specialists/prompts/cx-business-strategist.md +0 -58
- package/specialists/prompts/cx-data-engineer.md +0 -55
- package/specialists/prompts/cx-devil-advocate.md +0 -59
- package/specialists/prompts/cx-docs-keeper.md +0 -164
- package/specialists/prompts/cx-evaluator.md +0 -49
- package/specialists/prompts/cx-explorer.md +0 -71
- package/specialists/prompts/cx-legal-compliance.md +0 -58
- package/specialists/prompts/cx-oracle.md +0 -98
- package/specialists/prompts/cx-platform-engineer.md +0 -97
- package/specialists/prompts/cx-rd-lead.md +0 -59
- package/specialists/prompts/cx-release-manager.md +0 -54
- package/specialists/prompts/cx-sre.md +0 -111
- package/specialists/prompts/cx-test-automation.md +0 -55
- package/specialists/prompts/cx-trace-reviewer.md +0 -101
- package/specialists/prompts/cx-ux-researcher.md +0 -53
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/registry/custom-scaffold.mjs — CLI-facing scaffolding for user-authored
|
|
3
|
+
* custom specialists and teams (construct-rf26.13).
|
|
4
|
+
*
|
|
5
|
+
* Writes into one of two extension tiers, both read back by
|
|
6
|
+
* lib/registry/assemble.mjs at the same precedence ADR-0052 establishes for
|
|
7
|
+
* provider manifests (builtin -> user -> project):
|
|
8
|
+
* - "user" ~/.construct/org/** — usable across every project on the machine
|
|
9
|
+
* - "project" <rootDir>/.cx/org/** — git-tracked, highest precedence, scoped
|
|
10
|
+
* to one project (the existing overlay
|
|
11
|
+
* introduced by ADR-0046)
|
|
12
|
+
*
|
|
13
|
+
* Refuses to overwrite an existing file unless { force: true }, mirroring
|
|
14
|
+
* lib/specialists/scaffold.mjs's createSpecialistDraft convention. Validates
|
|
15
|
+
* with lib/registry/custom-schema.mjs before writing anything, so a scaffolded
|
|
16
|
+
* record is guaranteed registry-loadable the moment it lands on disk.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import fs from 'node:fs';
|
|
20
|
+
import path from 'node:path';
|
|
21
|
+
import { homeDir } from '../paths.mjs';
|
|
22
|
+
import { assembleRegistry } from './assemble.mjs';
|
|
23
|
+
import { validateCustomSpecialist, validateCustomTeam } from './custom-schema.mjs';
|
|
24
|
+
|
|
25
|
+
const ID_RE = /^[a-z][a-z0-9-]*$/;
|
|
26
|
+
|
|
27
|
+
export function customOrgDir(scope, { rootDir = process.cwd() } = {}) {
|
|
28
|
+
if (scope === 'user') return path.join(homeDir(), '.construct', 'org');
|
|
29
|
+
return path.join(rootDir, '.cx', 'org');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function existingTeamIds(rootDir) {
|
|
33
|
+
try {
|
|
34
|
+
return Object.keys(assembleRegistry(rootDir).teams);
|
|
35
|
+
} catch {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function renderPromptStub({ id, role, description }) {
|
|
41
|
+
return [
|
|
42
|
+
`# cx-${id}`,
|
|
43
|
+
'',
|
|
44
|
+
`Role: ${role}`,
|
|
45
|
+
'',
|
|
46
|
+
description || 'TODO: describe this specialist\'s perspective and output format.',
|
|
47
|
+
'',
|
|
48
|
+
'## Anti-fabrication contract',
|
|
49
|
+
'',
|
|
50
|
+
'Every load-bearing claim cites a source the reader can re-verify. When a fact is not in the source, write `unknown` or `[unverified]`. See `rules/common/no-fabrication.md`.',
|
|
51
|
+
'',
|
|
52
|
+
].join('\n');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Scaffold a custom specialist JSON record (+ a minimal prompt stub) into the
|
|
57
|
+
* requested tier. Throws with actionable, field-named errors on any failure.
|
|
58
|
+
* @returns {{ path: string, relPath: string, promptPath: string, record: object, scope: string }}
|
|
59
|
+
*/
|
|
60
|
+
export function createCustomSpecialist({
|
|
61
|
+
rootDir = process.cwd(),
|
|
62
|
+
scope = 'project',
|
|
63
|
+
id,
|
|
64
|
+
role,
|
|
65
|
+
description,
|
|
66
|
+
modelTier = 'standard',
|
|
67
|
+
reasoningEffort,
|
|
68
|
+
skills = [],
|
|
69
|
+
fence,
|
|
70
|
+
team,
|
|
71
|
+
teamId,
|
|
72
|
+
handoffCandidates = [],
|
|
73
|
+
claudeTools = 'Read,Grep,Glob,LS',
|
|
74
|
+
displayName,
|
|
75
|
+
force = false,
|
|
76
|
+
} = {}) {
|
|
77
|
+
if (!id || !ID_RE.test(id)) {
|
|
78
|
+
throw new Error(`invalid specialist id "${id}" — use lowercase kebab-case (e.g. "widget-specialist")`);
|
|
79
|
+
}
|
|
80
|
+
if (scope !== 'user' && scope !== 'project') {
|
|
81
|
+
throw new Error(`invalid scope "${scope}" — use "user" or "project"`);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const orgDir = customOrgDir(scope, { rootDir });
|
|
85
|
+
const specialistsDir = path.join(orgDir, 'specialists');
|
|
86
|
+
const filePath = path.join(specialistsDir, `cx-${id}.json`);
|
|
87
|
+
if (fs.existsSync(filePath) && !force) {
|
|
88
|
+
throw new Error(`${filePath} already exists — pass force to overwrite`);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const resolvedTeam = team || teamId;
|
|
92
|
+
const knownTeams = existingTeamIds(rootDir);
|
|
93
|
+
if (resolvedTeam && knownTeams && !knownTeams.includes(resolvedTeam)) {
|
|
94
|
+
throw new Error(
|
|
95
|
+
`team "${resolvedTeam}" does not exist — create it first with \`construct team create ${resolvedTeam} --owner=${role || id}\`, ` +
|
|
96
|
+
`or reference one of: ${knownTeams.slice(0, 10).join(', ')}${knownTeams.length > 10 ? ', …' : ''}`,
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const promptRelPath = path.posix.join(
|
|
101
|
+
path.relative(rootDir, orgDir).split(path.sep).join('/'),
|
|
102
|
+
'prompts',
|
|
103
|
+
`cx-${id}.md`,
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
const record = {
|
|
107
|
+
name: id,
|
|
108
|
+
displayName: displayName || description,
|
|
109
|
+
description,
|
|
110
|
+
role: role || id,
|
|
111
|
+
modelTier,
|
|
112
|
+
...(reasoningEffort ? { reasoningEffort } : {}),
|
|
113
|
+
skills,
|
|
114
|
+
fence: fence || { allowedPaths: [] },
|
|
115
|
+
claudeTools,
|
|
116
|
+
promptFile: promptRelPath,
|
|
117
|
+
handoffCandidates,
|
|
118
|
+
team: resolvedTeam,
|
|
119
|
+
teamId: resolvedTeam,
|
|
120
|
+
internal: true,
|
|
121
|
+
custom: true,
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const errors = validateCustomSpecialist(record, { rootDir, checkPromptFileExists: false });
|
|
125
|
+
if (errors.length) {
|
|
126
|
+
throw new Error(`custom specialist "${id}" failed validation:\n ${errors.join('\n ')}`);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const promptDir = path.join(orgDir, 'prompts');
|
|
130
|
+
const promptPath = path.join(rootDir, promptRelPath);
|
|
131
|
+
fs.mkdirSync(promptDir, { recursive: true });
|
|
132
|
+
if (!fs.existsSync(promptPath) || force) {
|
|
133
|
+
fs.writeFileSync(promptPath, renderPromptStub({ id, role: record.role, description }));
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
fs.mkdirSync(specialistsDir, { recursive: true });
|
|
137
|
+
fs.writeFileSync(filePath, JSON.stringify(record, null, 2) + '\n');
|
|
138
|
+
|
|
139
|
+
return { path: filePath, relPath: path.relative(rootDir, filePath), promptPath, record, scope };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Scaffold a custom team JSON record into the requested tier. Throws with
|
|
144
|
+
* actionable, field-named errors on any failure.
|
|
145
|
+
* @returns {{ path: string, relPath: string, record: object, scope: string }}
|
|
146
|
+
*/
|
|
147
|
+
export function createCustomTeam({
|
|
148
|
+
rootDir = process.cwd(),
|
|
149
|
+
scope = 'project',
|
|
150
|
+
id,
|
|
151
|
+
name,
|
|
152
|
+
owner,
|
|
153
|
+
roles,
|
|
154
|
+
specialists = [],
|
|
155
|
+
charter,
|
|
156
|
+
decisionRights = [],
|
|
157
|
+
forbiddenDecisions = [],
|
|
158
|
+
escalationPath,
|
|
159
|
+
contact = {},
|
|
160
|
+
groupId,
|
|
161
|
+
force = false,
|
|
162
|
+
} = {}) {
|
|
163
|
+
if (!id || !ID_RE.test(id)) {
|
|
164
|
+
throw new Error(`invalid team id "${id}" — use lowercase kebab-case (e.g. "widget-team")`);
|
|
165
|
+
}
|
|
166
|
+
if (scope !== 'user' && scope !== 'project') {
|
|
167
|
+
throw new Error(`invalid scope "${scope}" — use "user" or "project"`);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const orgDir = customOrgDir(scope, { rootDir });
|
|
171
|
+
const teamsDir = path.join(orgDir, 'teams');
|
|
172
|
+
const filePath = path.join(teamsDir, `${id}.json`);
|
|
173
|
+
if (fs.existsSync(filePath) && !force) {
|
|
174
|
+
throw new Error(`${filePath} already exists — pass force to overwrite`);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const resolvedRoles = roles && roles.length ? roles : (owner ? [owner] : []);
|
|
178
|
+
const record = {
|
|
179
|
+
name: name || id,
|
|
180
|
+
owner,
|
|
181
|
+
roles: resolvedRoles,
|
|
182
|
+
specialists,
|
|
183
|
+
decisionRights,
|
|
184
|
+
forbiddenDecisions,
|
|
185
|
+
escalationPath: escalationPath && escalationPath.length ? escalationPath : [owner, 'orchestrator'].filter(Boolean),
|
|
186
|
+
charter,
|
|
187
|
+
contact,
|
|
188
|
+
...(groupId ? { groupId } : {}),
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
const errors = validateCustomTeam({ id, ...record });
|
|
192
|
+
if (errors.length) {
|
|
193
|
+
throw new Error(`custom team "${id}" failed validation:\n ${errors.join('\n ')}`);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
fs.mkdirSync(teamsDir, { recursive: true });
|
|
197
|
+
fs.writeFileSync(filePath, JSON.stringify(record, null, 2) + '\n');
|
|
198
|
+
|
|
199
|
+
return { path: filePath, relPath: path.relative(rootDir, filePath), record, scope };
|
|
200
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/registry/custom-schema.mjs — validation contract for user-authored custom
|
|
3
|
+
* specialists and teams (construct-rf26.13).
|
|
4
|
+
*
|
|
5
|
+
* Hand-rolled validator, matching the style already established by
|
|
6
|
+
* lib/specialists/schema.mjs and lib/config/schema.mjs, rather than pulling in
|
|
7
|
+
* a schema-validation dependency: every error names the offending field and
|
|
8
|
+
* says what to do about it, so a scaffold failure is actionable without
|
|
9
|
+
* cross-referencing a generic validator dump. Reuses lib/specialists/schema.mjs's
|
|
10
|
+
* ALLOWED_TOOLS allowlist so a custom specialist is held to the same
|
|
11
|
+
* claudeTools contract as a built-in one.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import fs from 'node:fs';
|
|
15
|
+
import path from 'node:path';
|
|
16
|
+
import { ALLOWED_TOOLS } from '../specialists/schema.mjs';
|
|
17
|
+
|
|
18
|
+
export const MODEL_TIERS = ['fast', 'standard', 'reasoning'];
|
|
19
|
+
const SKILL_REF_RE = /^[a-z][a-z0-9-]*\/[a-z][a-z0-9-]*$/;
|
|
20
|
+
const ID_RE = /^[a-z][a-z0-9-]*$/;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Validate one custom specialist record. Returns an array of actionable error
|
|
24
|
+
* strings; empty means the record passes.
|
|
25
|
+
*/
|
|
26
|
+
export function validateCustomSpecialist(record, { rootDir, checkPromptFileExists = true } = {}) {
|
|
27
|
+
const errors = [];
|
|
28
|
+
const id = record?.name || '(unnamed)';
|
|
29
|
+
|
|
30
|
+
if (!record || typeof record !== 'object') {
|
|
31
|
+
return [`custom specialist: record must be an object`];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (!record.name || !ID_RE.test(record.name)) {
|
|
35
|
+
errors.push(`${id}: "name" must be lowercase kebab-case (e.g. "widget-specialist"), got ${JSON.stringify(record.name)}`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (!record.description || String(record.description).trim().length < 20) {
|
|
39
|
+
errors.push(`${id}: "description" is required and must be at least 20 characters — it is load-bearing for orchestration routing`);
|
|
40
|
+
} else if (String(record.description).length > 500) {
|
|
41
|
+
errors.push(`${id}: "description" is too long (>500 chars) — keep it scannable`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (!record.role || typeof record.role !== 'string') {
|
|
45
|
+
errors.push(`${id}: "role" is required (e.g. "widget-specialist") — a team's "owner" field must match a specialist's "role" for the team to validate`);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (!record.modelTier || !MODEL_TIERS.includes(record.modelTier)) {
|
|
49
|
+
errors.push(`${id}: "modelTier" must be one of ${MODEL_TIERS.join(' | ')}, got ${JSON.stringify(record.modelTier)}`);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const team = record.team || record.teamId;
|
|
53
|
+
if (!team || typeof team !== 'string') {
|
|
54
|
+
errors.push(`${id}: "team" (or "teamId") is required — reference an existing team id, or create one first with \`construct team create <id> --owner=${record.role || '<role>'}\``);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (!Array.isArray(record.skills) || record.skills.length === 0) {
|
|
58
|
+
errors.push(`${id}: "skills" must be a non-empty array of skill bundle references (e.g. ["frontend-design/accessibility"])`);
|
|
59
|
+
} else {
|
|
60
|
+
record.skills.forEach((skill, i) => {
|
|
61
|
+
if (typeof skill !== 'string' || !SKILL_REF_RE.test(skill)) {
|
|
62
|
+
errors.push(`${id}: skills[${i}] "${skill}" is not a valid skill bundle reference — expected "<bundle>/<skill>" (e.g. "frontend-design/accessibility")`);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (!record.fence || typeof record.fence !== 'object' || Array.isArray(record.fence)) {
|
|
68
|
+
errors.push(`${id}: "fence" is required — at minimum { "allowedPaths": [...] } to declare the permission boundary`);
|
|
69
|
+
} else if (!Array.isArray(record.fence.allowedPaths) || record.fence.allowedPaths.length === 0) {
|
|
70
|
+
errors.push(`${id}: "fence.allowedPaths" must be a non-empty array of path globs this specialist may touch`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (record.handoffCandidates !== undefined && !Array.isArray(record.handoffCandidates)) {
|
|
74
|
+
errors.push(`${id}: "handoffCandidates" must be an array of role ids (the delegation spec — who this specialist hands off to)`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (record.claudeTools) {
|
|
78
|
+
const tools = String(record.claudeTools).split(',').map((t) => t.trim()).filter(Boolean);
|
|
79
|
+
for (const tool of tools) {
|
|
80
|
+
if (!ALLOWED_TOOLS.has(tool)) {
|
|
81
|
+
errors.push(`${id}: unknown tool "${tool}" in claudeTools — host platforms silently drop unknown tool names, causing this specialist to appear to work without it`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (checkPromptFileExists && record.promptFile && rootDir) {
|
|
87
|
+
const promptPath = path.join(rootDir, record.promptFile);
|
|
88
|
+
if (!fs.existsSync(promptPath)) {
|
|
89
|
+
errors.push(`${id}: promptFile "${record.promptFile}" does not exist under ${rootDir}`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return errors;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Validate one custom team record. Returns an array of actionable error
|
|
98
|
+
* strings; empty means the record passes.
|
|
99
|
+
*/
|
|
100
|
+
export function validateCustomTeam(record) {
|
|
101
|
+
const errors = [];
|
|
102
|
+
const id = record?.id || record?.name || '(unnamed)';
|
|
103
|
+
|
|
104
|
+
if (!record || typeof record !== 'object') {
|
|
105
|
+
return [`custom team: record must be an object`];
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (!record.name || typeof record.name !== 'string') {
|
|
109
|
+
errors.push(`${id}: "name" is required (human-readable team name, e.g. "Widget Team")`);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (!record.owner || typeof record.owner !== 'string') {
|
|
113
|
+
errors.push(`${id}: "owner" is required — the role id that holds primary accountability (must match a specialist's "role" field)`);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (!Array.isArray(record.roles) || record.roles.length === 0) {
|
|
117
|
+
errors.push(`${id}: "roles" must be a non-empty array of role ids that constitute this team`);
|
|
118
|
+
} else if (record.owner && !record.roles.includes(record.owner)) {
|
|
119
|
+
errors.push(`${id}: "roles" must include the owner role "${record.owner}"`);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (!record.charter || String(record.charter).trim().length < 20) {
|
|
123
|
+
errors.push(`${id}: "charter" is required and must be at least 20 characters — a one-paragraph mission statement for what this team owns`);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
for (const field of ['decisionRights', 'forbiddenDecisions', 'escalationPath', 'specialists']) {
|
|
127
|
+
if (record[field] !== undefined && !Array.isArray(record[field])) {
|
|
128
|
+
errors.push(`${id}: "${field}" must be an array of strings when present`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (record.contact !== undefined && (typeof record.contact !== 'object' || Array.isArray(record.contact))) {
|
|
133
|
+
errors.push(`${id}: "contact" must be an object when present (e.g. { "slack": "#team", "owner": "role-id" })`);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return errors;
|
|
137
|
+
}
|
package/lib/registry/loader.mjs
CHANGED
|
@@ -10,14 +10,20 @@
|
|
|
10
10
|
|
|
11
11
|
import fs from 'fs';
|
|
12
12
|
import path from 'path';
|
|
13
|
-
import { fileURLToPath } from 'url';
|
|
14
13
|
import { validate } from './validator.mjs';
|
|
15
14
|
import { assembleRegistry, orgDirMtime, listTeamsFromRegistry, listGroupsHierarchical } from './assemble.mjs';
|
|
15
|
+
import { packageRoot } from '../roots.mjs';
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
// packageRoot (not a local __dirname-derived constant) because under a
|
|
18
|
+
// Bun-compiled binary every bundled module's import.meta.url collapses to
|
|
19
|
+
// the same virtual /$bunfs/root path, losing this file's real nesting
|
|
20
|
+
// under lib/registry/; packageRoot already falls back to the compiled
|
|
21
|
+
// binary's real on-disk directory (see lib/roots.mjs).
|
|
22
|
+
|
|
23
|
+
const ROOT_DIR = packageRoot;
|
|
19
24
|
|
|
20
25
|
let _registry = null;
|
|
26
|
+
let _rootDir = null;
|
|
21
27
|
let _orgMtime = null;
|
|
22
28
|
let _legacyOverlayMtime = null;
|
|
23
29
|
|
|
@@ -62,7 +68,10 @@ export function loadRegistry(opts = {}) {
|
|
|
62
68
|
legacyOverlay = null;
|
|
63
69
|
}
|
|
64
70
|
|
|
65
|
-
|
|
71
|
+
// The cache key must include rootDir: two roots can share an orgMtime/overlay mtime
|
|
72
|
+
// (tmpdir tests, coincident timestamps), and a cache keyed only on mtimes bleeds one
|
|
73
|
+
// root's registry into another — a specialist can then surface without its promptFile.
|
|
74
|
+
if (_registry && _rootDir === rootDir && _orgMtime === orgMtime && _legacyOverlayMtime === legacyOverlayMtime) {
|
|
66
75
|
return _registry;
|
|
67
76
|
}
|
|
68
77
|
|
|
@@ -79,6 +88,7 @@ export function loadRegistry(opts = {}) {
|
|
|
79
88
|
}
|
|
80
89
|
|
|
81
90
|
_registry = registry;
|
|
91
|
+
_rootDir = rootDir;
|
|
82
92
|
_orgMtime = orgMtime;
|
|
83
93
|
_legacyOverlayMtime = legacyOverlayMtime;
|
|
84
94
|
|
|
@@ -127,6 +137,7 @@ export function listSpecialists(teamId, opts = {}) {
|
|
|
127
137
|
|
|
128
138
|
export function clearCache() {
|
|
129
139
|
_registry = null;
|
|
140
|
+
_rootDir = null;
|
|
130
141
|
_orgMtime = null;
|
|
131
142
|
_legacyOverlayMtime = null;
|
|
132
143
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"pdf": { "engine": "pandoc", "writer": null, "pdfEngine": "typst", "extraBinaries": ["typst"] },
|
|
3
|
+
"docx": { "engine": "pandoc", "writer": null, "pdfEngine": null, "extraBinaries": [] },
|
|
4
|
+
"doc": { "engine": "pandoc", "writer": null, "pdfEngine": null, "extraBinaries": [], "via": "libreoffice", "intermediate": "docx" },
|
|
5
|
+
"deck": { "engine": "pandoc", "writer": null, "pdfEngine": null, "extraBinaries": [] },
|
|
6
|
+
"pptx": { "engine": "pptxgenjs", "writer": null, "pdfEngine": null, "extraBinaries": [] },
|
|
7
|
+
"html": { "engine": "pandoc", "writer": null, "pdfEngine": null, "extraBinaries": [] },
|
|
8
|
+
"rtf": { "engine": "pandoc", "writer": "rtf", "pdfEngine": null, "extraBinaries": [] },
|
|
9
|
+
"odt": { "engine": "pandoc", "writer": "odt", "pdfEngine": null, "extraBinaries": [] },
|
|
10
|
+
"epub": { "engine": "pandoc", "writer": "epub", "pdfEngine": null, "extraBinaries": [] },
|
|
11
|
+
"tex": { "engine": "pandoc", "writer": "latex", "pdfEngine": null, "extraBinaries": [] },
|
|
12
|
+
"txt": { "engine": "pandoc", "writer": "plain", "pdfEngine": null, "extraBinaries": [] },
|
|
13
|
+
"md": { "engine": "copy", "writer": null, "pdfEngine": null, "extraBinaries": [] },
|
|
14
|
+
"mdx": { "engine": "copy", "writer": null, "pdfEngine": null, "extraBinaries": [] }
|
|
15
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"tiers": ["agent-mcp", "thin-cli", "tui", "internal"],
|
|
3
|
+
"commands": {
|
|
4
|
+
"install": "thin-cli",
|
|
5
|
+
"init": "thin-cli",
|
|
6
|
+
"dev": "thin-cli",
|
|
7
|
+
"stop": "thin-cli",
|
|
8
|
+
"status": "thin-cli",
|
|
9
|
+
"doctor": "thin-cli",
|
|
10
|
+
"sync": "thin-cli",
|
|
11
|
+
"ingest": "agent-mcp",
|
|
12
|
+
"drop": "agent-mcp",
|
|
13
|
+
"distill": "agent-mcp",
|
|
14
|
+
"ask": "agent-mcp",
|
|
15
|
+
"search": "agent-mcp",
|
|
16
|
+
"knowledge": "agent-mcp",
|
|
17
|
+
"memory": "agent-mcp",
|
|
18
|
+
"reflect": "agent-mcp",
|
|
19
|
+
"intake": "tui",
|
|
20
|
+
"workflow": "agent-mcp",
|
|
21
|
+
"graph": "agent-mcp",
|
|
22
|
+
"capability": "agent-mcp",
|
|
23
|
+
"execution": "agent-mcp",
|
|
24
|
+
"orchestrate": "agent-mcp",
|
|
25
|
+
"models": "agent-mcp",
|
|
26
|
+
"profile": "tui",
|
|
27
|
+
"sandbox": "tui",
|
|
28
|
+
"review": "thin-cli",
|
|
29
|
+
"telemetry": "thin-cli",
|
|
30
|
+
"evals": "thin-cli",
|
|
31
|
+
"improvement": "thin-cli",
|
|
32
|
+
"ci": "thin-cli",
|
|
33
|
+
"docs": "thin-cli",
|
|
34
|
+
"export": "agent-mcp",
|
|
35
|
+
"diagram": "agent-mcp",
|
|
36
|
+
"demo": "agent-mcp",
|
|
37
|
+
"beads": "thin-cli",
|
|
38
|
+
"hook": "internal",
|
|
39
|
+
"lint:comments": "internal",
|
|
40
|
+
"lint:agents": "internal",
|
|
41
|
+
"registry:status": "internal",
|
|
42
|
+
"registry:validate": "internal",
|
|
43
|
+
"registry:generate-docs": "internal",
|
|
44
|
+
"rules": "thin-cli"
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -29,6 +29,7 @@ export const RETIRED_REGISTRY_PATH_ALLOWLIST = Object.freeze([
|
|
|
29
29
|
'scripts/patch-registry-readers-v2.mjs',
|
|
30
30
|
'tests/no-monolithic-registry.test.mjs',
|
|
31
31
|
'tests/migration-round-trip.test.mjs',
|
|
32
|
+
'tests/fixtures/mcp-tool-schemas/results/project_context.result.json',
|
|
32
33
|
]);
|
|
33
34
|
|
|
34
35
|
export function isRetiredRegistryPathAllowed(relPath) {
|
|
@@ -3,6 +3,12 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Declarative map from CLI command name to primary surface tier. The CLI registry
|
|
5
5
|
* remains the substrate; this file governs discovery posture without removing verbs.
|
|
6
|
+
* LMCP-B7: the tiers/command map ship as lib/registry/manifests/surface-map.default.json
|
|
7
|
+
* (loaded once at module init — this file's exports stay byte-identical to the prior
|
|
8
|
+
* hardcoded dict) with an optional `.cx/registry/surface-map.json` project override
|
|
9
|
+
* merged in per-call by resolveSurfaceMap()/surfaceForCommand()/validateSurfaceMap().
|
|
10
|
+
* An override may add or reassign command entries and replace the tier list; it can
|
|
11
|
+
* never remove a default entry outright (unset keys keep resolving to the default).
|
|
6
12
|
*
|
|
7
13
|
* Surfaces:
|
|
8
14
|
* agent-mcp — MCP tool canonical; CLI --json twin
|
|
@@ -11,64 +17,108 @@
|
|
|
11
17
|
* internal — CI/harness only
|
|
12
18
|
*/
|
|
13
19
|
|
|
14
|
-
|
|
20
|
+
import fs from 'node:fs';
|
|
21
|
+
import path from 'node:path';
|
|
22
|
+
|
|
23
|
+
import { packageRoot } from '../roots.mjs';
|
|
24
|
+
|
|
25
|
+
// packageRoot (not a local import.meta.url-derived HERE) because under a
|
|
26
|
+
// Bun-compiled binary every bundled module's import.meta.url collapses to
|
|
27
|
+
// the same virtual /$bunfs/root path, losing this file's real nesting
|
|
28
|
+
// under lib/registry/ — packageRoot already falls back to the compiled
|
|
29
|
+
// binary's real on-disk directory (see lib/roots.mjs), so rebuilding the
|
|
30
|
+
// known relative path from it resolves correctly in every run mode.
|
|
31
|
+
|
|
32
|
+
const DEFAULT_MANIFEST_PATH = path.join(packageRoot, 'lib', 'registry', 'manifests', 'surface-map.default.json');
|
|
33
|
+
|
|
34
|
+
function readJsonIfExists(filePath) {
|
|
35
|
+
try {
|
|
36
|
+
if (!fs.existsSync(filePath)) return null;
|
|
37
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
38
|
+
} catch {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function loadDefaultManifest() {
|
|
44
|
+
const manifest = readJsonIfExists(DEFAULT_MANIFEST_PATH);
|
|
45
|
+
if (!manifest || !Array.isArray(manifest.tiers) || typeof manifest.commands !== 'object') {
|
|
46
|
+
throw new Error(`lib/registry/surface-map.mjs: missing or malformed default manifest at ${DEFAULT_MANIFEST_PATH}`);
|
|
47
|
+
}
|
|
48
|
+
return manifest;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const DEFAULT_MANIFEST = loadDefaultManifest();
|
|
52
|
+
|
|
53
|
+
// Byte-identical to the pre-manifest hardcoded exports: both are read once at
|
|
54
|
+
// module init from the default manifest, with no project override applied —
|
|
55
|
+
// a direct import of these two names sees exactly the shipped defaults.
|
|
56
|
+
|
|
57
|
+
export const SURFACE_TIERS = DEFAULT_MANIFEST.tiers;
|
|
15
58
|
|
|
16
59
|
/** @type {Record<string, string>} */
|
|
17
|
-
export const COMMAND_SURFACE =
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
export: 'agent-mcp',
|
|
49
|
-
diagram: 'agent-mcp',
|
|
50
|
-
demo: 'agent-mcp',
|
|
51
|
-
beads: 'thin-cli',
|
|
52
|
-
hook: 'internal',
|
|
53
|
-
'lint:comments': 'internal',
|
|
54
|
-
'lint:agents': 'internal',
|
|
55
|
-
'registry:status': 'internal',
|
|
56
|
-
'registry:validate': 'internal',
|
|
57
|
-
'registry:generate-docs': 'internal',
|
|
58
|
-
rules: 'thin-cli',
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
export function surfaceForCommand(name) {
|
|
62
|
-
if (COMMAND_SURFACE[name]) return COMMAND_SURFACE[name];
|
|
60
|
+
export const COMMAND_SURFACE = DEFAULT_MANIFEST.commands;
|
|
61
|
+
|
|
62
|
+
const overrideCache = new Map();
|
|
63
|
+
|
|
64
|
+
function loadOverride(cwd) {
|
|
65
|
+
if (overrideCache.has(cwd)) return overrideCache.get(cwd);
|
|
66
|
+
const override = readJsonIfExists(path.join(cwd, '.cx', 'registry', 'surface-map.json'));
|
|
67
|
+
overrideCache.set(cwd, override);
|
|
68
|
+
return override;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* resolveSurfaceMap({ cwd } = {})
|
|
73
|
+
*
|
|
74
|
+
* Merges the default manifest with a project's `.cx/registry/surface-map.json`
|
|
75
|
+
* override, when present. `commands` in the override are shallow-merged over
|
|
76
|
+
* the default (an override entry replaces the matching default; every other
|
|
77
|
+
* default entry survives untouched). `tiers` in the override replaces the
|
|
78
|
+
* default tier list wholesale when present.
|
|
79
|
+
*/
|
|
80
|
+
export function resolveSurfaceMap({ cwd = process.cwd() } = {}) {
|
|
81
|
+
const override = loadOverride(cwd);
|
|
82
|
+
if (!override) return { tiers: SURFACE_TIERS, commands: COMMAND_SURFACE };
|
|
83
|
+
const tiers = Array.isArray(override.tiers) ? override.tiers : SURFACE_TIERS;
|
|
84
|
+
const commands = { ...COMMAND_SURFACE, ...(override.commands && typeof override.commands === 'object' ? override.commands : {}) };
|
|
85
|
+
return { tiers, commands };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function surfaceForCommand(name, { cwd = process.cwd() } = {}) {
|
|
89
|
+
const { commands } = resolveSurfaceMap({ cwd });
|
|
90
|
+
if (commands[name]) return commands[name];
|
|
63
91
|
if (name.includes(':')) return 'internal';
|
|
64
92
|
return 'thin-cli';
|
|
65
93
|
}
|
|
66
94
|
|
|
67
|
-
export function commandsBySurface(commands) {
|
|
68
|
-
const
|
|
95
|
+
export function commandsBySurface(commands, { cwd = process.cwd() } = {}) {
|
|
96
|
+
const { tiers } = resolveSurfaceMap({ cwd });
|
|
97
|
+
const grouped = Object.fromEntries(tiers.map((t) => [t, []]));
|
|
69
98
|
for (const cmd of commands) {
|
|
70
|
-
const surface = cmd.surface ?? surfaceForCommand(cmd.name);
|
|
99
|
+
const surface = cmd.surface ?? surfaceForCommand(cmd.name, { cwd });
|
|
71
100
|
grouped[surface]?.push(cmd.name);
|
|
72
101
|
}
|
|
73
102
|
return grouped;
|
|
74
103
|
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* validateSurfaceMap(commandNames, { cwd } = {})
|
|
107
|
+
*
|
|
108
|
+
* Stricter than surfaceForCommand's runtime fallback: every name must have an
|
|
109
|
+
* explicit entry in the resolved (default + override) command map. This is an
|
|
110
|
+
* opt-in registry/doctor-style check, separate from the CLI's own dispatch
|
|
111
|
+
* path, so the default fallback behavior for unregistered commands stays
|
|
112
|
+
* unaffected.
|
|
113
|
+
*/
|
|
114
|
+
export function validateSurfaceMap(commandNames, { cwd = process.cwd() } = {}) {
|
|
115
|
+
const { commands } = resolveSurfaceMap({ cwd });
|
|
116
|
+
const errors = commandNames
|
|
117
|
+
.filter((name) => !commands[name])
|
|
118
|
+
.map((name) => `command '${name}' has no surface-map entry (add one to .cx/registry/surface-map.json or lib/registry/manifests/surface-map.default.json)`);
|
|
119
|
+
return { valid: errors.length === 0, errors };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function __clearSurfaceMapOverrideCache() {
|
|
123
|
+
overrideCache.clear();
|
|
124
|
+
}
|
|
@@ -86,7 +86,7 @@ export function validateCapabilityRegistry({ rootDir = REPO_ROOT, now = Date.now
|
|
|
86
86
|
|
|
87
87
|
const hasTest = ver.functional || ver.hostEmulation || ver.untestableRationale;
|
|
88
88
|
if (!hasTest && (cap.criticality === 'P0' || cap.criticality === 'P1')) {
|
|
89
|
-
|
|
89
|
+
errors.push(`${cap.id}: P0/P1 capability has no functional test or untestableRationale`);
|
|
90
90
|
}
|
|
91
91
|
|
|
92
92
|
const surfaces = cap.surfaces ?? {};
|
|
@@ -105,10 +105,10 @@ export function validateCapabilityRegistry({ rootDir = REPO_ROOT, now = Date.now
|
|
|
105
105
|
if (cap.lastValidated) {
|
|
106
106
|
const age = now - Date.parse(cap.lastValidated);
|
|
107
107
|
if (Number.isFinite(age) && age > STALE_MS) {
|
|
108
|
-
|
|
108
|
+
errors.push(`${cap.id}: lastValidated older than 90 days (${cap.lastValidated})`);
|
|
109
109
|
}
|
|
110
110
|
} else if (cap.criticality === 'P0') {
|
|
111
|
-
|
|
111
|
+
errors.push(`${cap.id}: P0 capability never validated (lastValidated null)`);
|
|
112
112
|
}
|
|
113
113
|
|
|
114
114
|
for (const dep of cap.dependencies?.skills ?? []) {
|