@geraldmaron/construct 1.4.2 → 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 +1027 -64
- 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 +6 -29
- 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 +89 -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 +18 -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/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 +50 -9
- 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-capabilities.mjs +13 -2
- package/lib/host-disposition.mjs +19 -7
- 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/intake/daemon.mjs +99 -8
- package/lib/intake/git-queue.mjs +110 -38
- 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-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 +154 -1411
- 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-registry.mjs +107 -0
- package/lib/mcp/tool-safety.mjs +1 -0
- package/lib/mcp/tools/orchestration-delegation-next.tool.mjs +68 -0
- package/lib/mcp/tools/orchestration-run.mjs +165 -25
- package/lib/mcp/tools/orchestration-task-result.tool.mjs +77 -0
- 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 +0 -8
- 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 +31 -7
- 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/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 +446 -39
- package/lib/orchestration/store.mjs +87 -12
- package/lib/orchestration/trace-store.mjs +125 -0
- package/lib/orchestration/web-capability.mjs +3 -2
- package/lib/orchestration/worker-runtime.mjs +162 -0
- package/lib/orchestration/worker.mjs +528 -89
- package/lib/orchestration-policy.mjs +67 -1111
- 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/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 +6 -0
- package/lib/providers/secret-resolver.mjs +240 -23
- 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/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 +8 -3
- 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/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/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 +38 -19
- 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 +77 -5
- 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 +16 -8
- package/personas/construct.md +12 -3
- package/registry/capabilities.json +143 -36
- package/rules/common/comments.md +1 -0
- package/schemas/project-config.schema.json +0 -12
- package/schemas/unified-registry.schema.json +2 -4
- package/scripts/sync-specialists.mjs +284 -81
- 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 +40 -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 +25 -30
- 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/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,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "postgres",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"kind": "queue",
|
|
5
|
+
"capabilities": ["enqueue", "claim", "lease", "reclaim"],
|
|
6
|
+
"secretEnvKeys": ["CONSTRUCT_DATABASE_URL"],
|
|
7
|
+
"operations": ["enqueue", "claim", "heartbeat", "complete", "skip", "reopen"],
|
|
8
|
+
"securityClassification": "trusted",
|
|
9
|
+
"owner": "construct-core",
|
|
10
|
+
"compatVersion": 1,
|
|
11
|
+
"installSource": "optional-dependency"
|
|
12
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "salesforce",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"kind": "data-source",
|
|
5
|
+
"capabilities": ["read", "search"],
|
|
6
|
+
"secretEnvKeys": ["SALESFORCE_INSTANCE_URL", "SALESFORCE_ACCESS_TOKEN"],
|
|
7
|
+
"operations": ["read", "search"],
|
|
8
|
+
"securityClassification": "trusted",
|
|
9
|
+
"owner": "construct-core",
|
|
10
|
+
"compatVersion": 1,
|
|
11
|
+
"installSource": "builtin"
|
|
12
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "slack",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"kind": "data-source",
|
|
5
|
+
"capabilities": ["read", "search"],
|
|
6
|
+
"secretEnvKeys": ["SLACK_BOT_TOKEN", "SLACK_USER_TOKEN"],
|
|
7
|
+
"operations": ["read", "search"],
|
|
8
|
+
"securityClassification": "trusted",
|
|
9
|
+
"owner": "construct-core",
|
|
10
|
+
"compatVersion": 1,
|
|
11
|
+
"installSource": "builtin",
|
|
12
|
+
"sourceTarget": {
|
|
13
|
+
"provider": "slack",
|
|
14
|
+
"selector": {
|
|
15
|
+
"field": "channel",
|
|
16
|
+
"pattern": null,
|
|
17
|
+
"patternTransform": "trim",
|
|
18
|
+
"hint": "required Slack channel name or ID",
|
|
19
|
+
"normalize": "trimHash"
|
|
20
|
+
},
|
|
21
|
+
"secondaryField": {
|
|
22
|
+
"field": "intent",
|
|
23
|
+
"enum": ["internal", "risk", "decision", "external", "how-to"],
|
|
24
|
+
"hint": "must be one of",
|
|
25
|
+
"default": "internal"
|
|
26
|
+
},
|
|
27
|
+
"signature": { "template": "slack:channel:{value}:intent:{intent}", "valueMode": "lowerOrEmpty" },
|
|
28
|
+
"legacyEnv": { "vars": ["SLACK_CHANNELS", "SLACK_CHANNEL"], "parse": "slackChannelColon" },
|
|
29
|
+
"aliases": { "strategy": "simple", "displayPrefix": "Slack", "catchAll": false },
|
|
30
|
+
"embed": {
|
|
31
|
+
"mode": "groupBy",
|
|
32
|
+
"groupField": "intent",
|
|
33
|
+
"groupDefault": "internal",
|
|
34
|
+
"valueField": "channels",
|
|
35
|
+
"valueKind": "array",
|
|
36
|
+
"refs": ["messages"],
|
|
37
|
+
"oldest": 86400,
|
|
38
|
+
"limit": 50
|
|
39
|
+
},
|
|
40
|
+
"embedFilters": {
|
|
41
|
+
"valueField": "channels",
|
|
42
|
+
"valueKind": "array",
|
|
43
|
+
"defaultRefs": ["messages"],
|
|
44
|
+
"extraFilterKeys": ["oldest"],
|
|
45
|
+
"defaultLimit": 50,
|
|
46
|
+
"includeSecondaryField": true
|
|
47
|
+
},
|
|
48
|
+
"demandFetch": {
|
|
49
|
+
"targetRefs": ["messages"],
|
|
50
|
+
"targetOptsField": "channels",
|
|
51
|
+
"targetOptsKind": "array",
|
|
52
|
+
"targetExtraFilterKeys": ["oldest"],
|
|
53
|
+
"targetOldestDefault": 86400,
|
|
54
|
+
"targetDefaultLimit": 50,
|
|
55
|
+
"querySupported": false
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "whisper",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"kind": "ingestion-provider",
|
|
5
|
+
"capabilities": ["audio-transcription"],
|
|
6
|
+
"docs": "Local audio/video transcription via whisper.cpp (Metal-accelerated on macOS). No npm dependency — the binary and model weights are provisioned outside npm and probed, not assumed.",
|
|
7
|
+
"owner": "construct-core",
|
|
8
|
+
"compatVersion": 1,
|
|
9
|
+
"installSource": "system-binary",
|
|
10
|
+
"installDependencies": [],
|
|
11
|
+
"installProbe": {
|
|
12
|
+
"kind": "binary-on-path",
|
|
13
|
+
"candidates": ["whisper-cli", "whisper-cpp", "main"],
|
|
14
|
+
"runtimeDirEnv": "CONSTRUCT_WHISPER_RUNTIME_DIR",
|
|
15
|
+
"defaultRuntimeDir": ".cx/runtime/whisper",
|
|
16
|
+
"cachedBinary": "bin/whisper-cli",
|
|
17
|
+
"description": "which(1) lookup for whisper-cli/whisper-cpp/main on PATH, falling back to a cached binary at <runtimeDir>/bin/whisper-cli."
|
|
18
|
+
},
|
|
19
|
+
"healthCheck": {
|
|
20
|
+
"kind": "subprocess-version",
|
|
21
|
+
"command": "<resolvedBinary>",
|
|
22
|
+
"args": ["--help"],
|
|
23
|
+
"timeoutMs": 10000,
|
|
24
|
+
"description": "whisper.cpp CLI has no --version flag; health runs the binary with --help and treats a clean exit (or the expected usage banner) as healthy. A missing binary or non-zero exit is unhealthy."
|
|
25
|
+
},
|
|
26
|
+
"degradation": {
|
|
27
|
+
"chain": [
|
|
28
|
+
{ "id": "whisper", "mode": "sidecar", "description": "Local ASR transcription via whisper.cpp." },
|
|
29
|
+
{ "id": "refuse", "mode": "error", "description": "No transcription backend available; audio/video ingest fails loud (ASR_REQUIRED) with an install hint (brew install whisper-cpp)." }
|
|
30
|
+
],
|
|
31
|
+
"terminal": "refuse",
|
|
32
|
+
"resultMarking": "degraded"
|
|
33
|
+
},
|
|
34
|
+
"removalStatus": "active",
|
|
35
|
+
"securityClassification": "trusted"
|
|
36
|
+
}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/extensions/validate.mjs — extension manifest validator.
|
|
3
|
+
*
|
|
4
|
+
* Validates a plain-JS manifest object against the canonical schema defined in
|
|
5
|
+
* manifest-schema.mjs. All errors are returned in a structured result; this
|
|
6
|
+
* function never throws. Error messages are single-line, actionable, and
|
|
7
|
+
* prefixed with the filePath (when provided) so consumers can surface them
|
|
8
|
+
* directly to users.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
MANIFEST_KINDS, REQUIRED_FIELDS, OPTIONAL_FIELDS, COMPAT_VERSION,
|
|
13
|
+
ALLOWED_SECRET_ENV_PREFIXES, ESCALATION_SENSITIVE_KINDS,
|
|
14
|
+
KNOWN_REQUESTABLE_TOOL_GRANTS, BUILTIN_SPECIALIST_IDS,
|
|
15
|
+
} from './manifest-schema.mjs';
|
|
16
|
+
|
|
17
|
+
/** Basic semver pattern: MAJOR.MINOR.PATCH with optional pre-release/build metadata. */
|
|
18
|
+
const SEMVER_RE = /^\d+\.\d+\.\d+(?:[-+].+)?$/;
|
|
19
|
+
|
|
20
|
+
/** Valid id characters: lowercase alphanumeric, hyphens, dots, forward slashes. */
|
|
21
|
+
const ID_RE = /^[a-z0-9\-./]+$/;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* validateManifest(manifest, { filePath, strict } = {})
|
|
25
|
+
*
|
|
26
|
+
* Returns { valid: true } on success, or { valid: false, errors: string[] }
|
|
27
|
+
* on failure. Every error string is a self-contained, single-line description
|
|
28
|
+
* of the problem. The filePath prefix is included in every error so callers
|
|
29
|
+
* can surface messages without additional decoration.
|
|
30
|
+
*
|
|
31
|
+
* Checks performed (in order):
|
|
32
|
+
* 1. Required fields are present.
|
|
33
|
+
* 2. `kind` is one of the canonical MANIFEST_KINDS.
|
|
34
|
+
* 3. `version` matches the semver pattern.
|
|
35
|
+
* 4. `id` is a non-empty string matching [a-z0-9-./]+.
|
|
36
|
+
* 5. `compatVersion` (if present) does not exceed COMPAT_VERSION.
|
|
37
|
+
*
|
|
38
|
+
* When strict is true, additional hardening checks are applied:
|
|
39
|
+
* 6. No unknown fields (fields outside REQUIRED_FIELDS + OPTIONAL_FIELDS).
|
|
40
|
+
* 7. secretEnvKeys entries must start with an allowed prefix.
|
|
41
|
+
* 8. For escalation-sensitive kinds, toolGrantsRequested must be in
|
|
42
|
+
* KNOWN_REQUESTABLE_TOOL_GRANTS.
|
|
43
|
+
* 9. Prompts targeting built-in specialists without override:true are
|
|
44
|
+
* rejected (prompt shadowing prevention).
|
|
45
|
+
* 10. owner values starting with 'cx-' must be known built-in specialist ids.
|
|
46
|
+
*
|
|
47
|
+
* @param {unknown} manifest - The parsed manifest object to validate.
|
|
48
|
+
* @param {{ filePath?: string, strict?: boolean }} [opts]
|
|
49
|
+
* @returns {{ valid: true } | { valid: false, errors: string[] }}
|
|
50
|
+
*/
|
|
51
|
+
export function validateManifest(manifest, { filePath, strict = false } = {}) {
|
|
52
|
+
const prefix = filePath ? `${filePath}: ` : '';
|
|
53
|
+
const errors = [];
|
|
54
|
+
|
|
55
|
+
if (!manifest || typeof manifest !== 'object' || Array.isArray(manifest)) {
|
|
56
|
+
return { valid: false, errors: [`${prefix}manifest must be a JSON object`] };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Required fields
|
|
60
|
+
for (const field of REQUIRED_FIELDS) {
|
|
61
|
+
if (!(field in manifest) || manifest[field] === undefined || manifest[field] === null) {
|
|
62
|
+
errors.push(`${prefix}missing required field: ${field}`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// If required fields are missing we may not have meaningful values for the
|
|
67
|
+
// semantic checks — collect what we can, but short-circuit gracefully.
|
|
68
|
+
|
|
69
|
+
// kind check (only if kind is present — otherwise already flagged above)
|
|
70
|
+
if ('kind' in manifest && manifest.kind !== undefined && manifest.kind !== null) {
|
|
71
|
+
if (!MANIFEST_KINDS.includes(manifest.kind)) {
|
|
72
|
+
errors.push(
|
|
73
|
+
`${prefix}unknown kind '${manifest.kind}'; expected one of: ${MANIFEST_KINDS.join(', ')}`
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// version semver check
|
|
79
|
+
if ('version' in manifest && manifest.version !== undefined && manifest.version !== null) {
|
|
80
|
+
if (typeof manifest.version !== 'string' || !SEMVER_RE.test(manifest.version)) {
|
|
81
|
+
errors.push(`${prefix}version must be a semver string`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// id format check
|
|
86
|
+
if ('id' in manifest && manifest.id !== undefined && manifest.id !== null) {
|
|
87
|
+
if (typeof manifest.id !== 'string' || manifest.id.length === 0) {
|
|
88
|
+
errors.push(`${prefix}id must be a non-empty string`);
|
|
89
|
+
} else if (!ID_RE.test(manifest.id)) {
|
|
90
|
+
errors.push(`${prefix}id must match [a-z0-9-./]+ (got '${manifest.id}')`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Forward-compat guard
|
|
95
|
+
if (
|
|
96
|
+
manifest.compatVersion !== undefined &&
|
|
97
|
+
manifest.compatVersion !== null
|
|
98
|
+
) {
|
|
99
|
+
if (typeof manifest.compatVersion !== 'number' || !Number.isInteger(manifest.compatVersion)) {
|
|
100
|
+
errors.push(`${prefix}compatVersion must be an integer`);
|
|
101
|
+
} else if (manifest.compatVersion > COMPAT_VERSION) {
|
|
102
|
+
errors.push(
|
|
103
|
+
`${prefix}compatVersion ${manifest.compatVersion} exceeds supported version ${COMPAT_VERSION}; upgrade Construct to use this manifest`
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Strict-mode hardening checks
|
|
109
|
+
if (strict) {
|
|
110
|
+
const knownFields = new Set([...REQUIRED_FIELDS, ...OPTIONAL_FIELDS]);
|
|
111
|
+
|
|
112
|
+
// Unknown field rejection
|
|
113
|
+
for (const key of Object.keys(manifest)) {
|
|
114
|
+
if (key.startsWith('_')) continue;
|
|
115
|
+
if (!knownFields.has(key)) {
|
|
116
|
+
errors.push(`${prefix}unknown field '${key}' in strict mode`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Secret env key allowlist
|
|
121
|
+
if (manifest.secretEnvKeys && Array.isArray(manifest.secretEnvKeys)) {
|
|
122
|
+
for (const key of manifest.secretEnvKeys) {
|
|
123
|
+
const allowed = ALLOWED_SECRET_ENV_PREFIXES.some(p => key.startsWith(p));
|
|
124
|
+
if (!allowed) {
|
|
125
|
+
errors.push(`${prefix}secretEnvKey '${key}' is not allowed; must start with one of: ${ALLOWED_SECRET_ENV_PREFIXES.join(', ')}`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Capability escalation checks for sensitive kinds
|
|
131
|
+
if (manifest.kind && ESCALATION_SENSITIVE_KINDS.includes(manifest.kind)) {
|
|
132
|
+
if (manifest.toolGrantsRequested && Array.isArray(manifest.toolGrantsRequested)) {
|
|
133
|
+
for (const grant of manifest.toolGrantsRequested) {
|
|
134
|
+
if (!KNOWN_REQUESTABLE_TOOL_GRANTS.includes(grant)) {
|
|
135
|
+
errors.push(`${prefix}toolGrantsRequested '${grant}' is not a known tool grant`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Builtin prompt shadowing prevention
|
|
142
|
+
if (manifest.prompts && Array.isArray(manifest.prompts)) {
|
|
143
|
+
for (const [i, prompt] of manifest.prompts.entries()) {
|
|
144
|
+
if (
|
|
145
|
+
prompt &&
|
|
146
|
+
typeof prompt.specialist === 'string' &&
|
|
147
|
+
BUILTIN_SPECIALIST_IDS.includes(prompt.specialist) &&
|
|
148
|
+
!prompt.override
|
|
149
|
+
) {
|
|
150
|
+
errors.push(
|
|
151
|
+
`${prefix}prompts[${i}]: specialist '${prompt.specialist}' is a built-in; set "override": true to shadow`
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Owner validation — cx- prefixed owners must be known builtins
|
|
158
|
+
if (
|
|
159
|
+
manifest.owner &&
|
|
160
|
+
typeof manifest.owner === 'string' &&
|
|
161
|
+
manifest.owner.startsWith('cx-')
|
|
162
|
+
) {
|
|
163
|
+
if (!BUILTIN_SPECIALIST_IDS.includes(manifest.owner)) {
|
|
164
|
+
errors.push(
|
|
165
|
+
`${prefix}owner '${manifest.owner}' starts with 'cx-' but is not a known built-in specialist id`
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (errors.length > 0) {
|
|
172
|
+
return { valid: false, errors };
|
|
173
|
+
}
|
|
174
|
+
return { valid: true };
|
|
175
|
+
}
|
package/lib/features.mjs
CHANGED
|
@@ -19,8 +19,12 @@ function getFeaturesFile(overrides = {}) {
|
|
|
19
19
|
return overrides.featuresFile ?? join(configDir(getHomeDir(overrides)), 'features.json');
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
// Claude Code's user-scope MCP servers live in ~/.claude.json's top-level
|
|
23
|
+
// `mcpServers`, not in settings.json (confirmed against
|
|
24
|
+
// code.claude.com/docs/en/mcp and live `claude mcp list` repro, construct-ranh).
|
|
25
|
+
|
|
26
|
+
function getClaudeUserConfigPath(overrides = {}) {
|
|
27
|
+
return overrides.claudeUserConfigPath ?? join(getHomeDir(overrides), '.claude.json');
|
|
24
28
|
}
|
|
25
29
|
|
|
26
30
|
function readEnabledFeatures(overrides = {}) {
|
|
@@ -39,11 +43,11 @@ function isFeatureEnabled(id, overrides = {}) {
|
|
|
39
43
|
return (data.enabled ?? []).includes(id);
|
|
40
44
|
}
|
|
41
45
|
|
|
42
|
-
function
|
|
43
|
-
const
|
|
44
|
-
if (!existsSync(
|
|
46
|
+
function readClaudeUserConfig(overrides = {}) {
|
|
47
|
+
const claudeUserConfig = getClaudeUserConfigPath(overrides);
|
|
48
|
+
if (!existsSync(claudeUserConfig)) return null;
|
|
45
49
|
try {
|
|
46
|
-
return JSON.parse(readFileSync(
|
|
50
|
+
return JSON.parse(readFileSync(claudeUserConfig, 'utf8'));
|
|
47
51
|
} catch {
|
|
48
52
|
return null;
|
|
49
53
|
}
|
|
@@ -194,9 +198,9 @@ function getMcpPlatforms(mcp, overrides = {}) {
|
|
|
194
198
|
platforms.push('Claude.ai');
|
|
195
199
|
}
|
|
196
200
|
|
|
197
|
-
// --- Claude Code: user
|
|
198
|
-
const
|
|
199
|
-
if (getHostSupport(mcp, 'claude').mode === 'managed' && configHasAny(
|
|
201
|
+
// --- Claude Code: user scope (~/.claude.json's top-level mcpServers) ---
|
|
202
|
+
const claudeUserConfig = readClaudeUserConfig(overrides);
|
|
203
|
+
if (getHostSupport(mcp, 'claude').mode === 'managed' && configHasAny(claudeUserConfig?.mcpServers, matchIds)) {
|
|
200
204
|
platforms.push('Claude Code');
|
|
201
205
|
}
|
|
202
206
|
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/flows/checkpoint.mjs — durable per-step checkpointing and resume for flow runs.
|
|
3
|
+
*
|
|
4
|
+
* Persists a Run after every completed step under the project's machine-scoped
|
|
5
|
+
* state root (ADR-0066) at `runtime/flows/runs/<runId>.json`, atomic write via
|
|
6
|
+
* temp-file-then-rename — the same pattern lib/orchestration/run-store.mjs uses
|
|
7
|
+
* for orchestration runs. A run's Set/Map fields (completed, joinProgress,
|
|
8
|
+
* usage) are serialized to plain arrays and reconstructed on load; the flow
|
|
9
|
+
* definition itself (its run/router functions) is never persisted — a
|
|
10
|
+
* resuming caller supplies the same flow object it would use to start a run,
|
|
11
|
+
* exactly like loadFlow's `handlers` pattern for JSON flows. A checkpoint
|
|
12
|
+
* records which flow id it was written under; resuming against a different
|
|
13
|
+
* flow id throws rather than silently replaying history against the wrong
|
|
14
|
+
* step graph.
|
|
15
|
+
*
|
|
16
|
+
* Idempotent re-entry: runCheckpointed() checkpoints strictly AFTER each
|
|
17
|
+
* advanceRun() call, so a step already reflected in a saved checkpoint's
|
|
18
|
+
* `completed` set is never in that checkpoint's `frontier` and will not
|
|
19
|
+
* re-run on resume — the frontier only ever holds not-yet-completed steps,
|
|
20
|
+
* which is a property of the Run shape itself, not something this module adds.
|
|
21
|
+
* The remaining window — a crash between a step's run() executing and the
|
|
22
|
+
* following checkpoint write landing — is closed by authoring convention, not
|
|
23
|
+
* an engine guarantee: a step's run() must be idempotent under at-least-once
|
|
24
|
+
* execution (pure computation, or a naturally idempotent side effect such as
|
|
25
|
+
* an upsert), the same constraint the engine already implies by recording
|
|
26
|
+
* delegation intent rather than performing it (engine.mjs's workerBackend
|
|
27
|
+
* handling). A step that must perform an exactly-once side effect belongs
|
|
28
|
+
* outside the engine, driven by its recorded delegation, not inside run().
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
|
|
32
|
+
import { join } from 'node:path';
|
|
33
|
+
|
|
34
|
+
import { resolveStateDir } from '../state-root.mjs';
|
|
35
|
+
import { createRun, advanceRun } from './engine.mjs';
|
|
36
|
+
import { RUN_STATUS } from './constants.mjs';
|
|
37
|
+
|
|
38
|
+
export class FlowCheckpointError extends Error {
|
|
39
|
+
constructor(message) {
|
|
40
|
+
super(message);
|
|
41
|
+
this.name = 'FlowCheckpointError';
|
|
42
|
+
this.code = 'FLOW_CHECKPOINT_MISMATCH';
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
let writeCounter = 0;
|
|
47
|
+
|
|
48
|
+
export function runsDir(cwd = process.cwd()) {
|
|
49
|
+
return resolveStateDir(cwd, 'runtime', 'flows', 'runs', { ensureDir: false });
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function checkpointPath(cwd, runId) {
|
|
53
|
+
return join(runsDir(cwd), `${runId}.json`);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function atomicWriteJson(filePath, value) {
|
|
57
|
+
writeCounter = (writeCounter + 1) % 100000;
|
|
58
|
+
const tmp = `${filePath}.${process.pid}.${writeCounter}.tmp`;
|
|
59
|
+
writeFileSync(tmp, `${JSON.stringify(value, null, 2)}\n`);
|
|
60
|
+
renameSync(tmp, filePath);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function serializeRun(run) {
|
|
64
|
+
return {
|
|
65
|
+
state: run.state,
|
|
66
|
+
frontier: [...run.frontier],
|
|
67
|
+
completed: [...run.completed],
|
|
68
|
+
joinProgress: [...run.joinProgress.entries()].map(([step, satisfied]) => [step, [...satisfied]]),
|
|
69
|
+
usage: [...run.usage.entries()],
|
|
70
|
+
history: run.history,
|
|
71
|
+
status: run.status,
|
|
72
|
+
error: run.error,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function deserializeRun(flow, data) {
|
|
77
|
+
return {
|
|
78
|
+
flow,
|
|
79
|
+
state: data.state,
|
|
80
|
+
frontier: [...(data.frontier || [])],
|
|
81
|
+
completed: new Set(data.completed || []),
|
|
82
|
+
joinProgress: new Map((data.joinProgress || []).map(([step, satisfied]) => [step, new Set(satisfied)])),
|
|
83
|
+
usage: new Map(data.usage || []),
|
|
84
|
+
history: data.history || [],
|
|
85
|
+
status: data.status,
|
|
86
|
+
error: data.error || null,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Persist a run's current state to its checkpoint file. Overwrites any prior
|
|
92
|
+
* checkpoint for the same runId — a checkpoint is the run's latest snapshot,
|
|
93
|
+
* not a history of every tick (history already lives on the run itself).
|
|
94
|
+
*/
|
|
95
|
+
export function checkpointRun(cwd, runId, flow, run) {
|
|
96
|
+
const dir = runsDir(cwd);
|
|
97
|
+
mkdirSync(dir, { recursive: true });
|
|
98
|
+
atomicWriteJson(checkpointPath(cwd, runId), {
|
|
99
|
+
runId,
|
|
100
|
+
flowId: flow.id ?? null,
|
|
101
|
+
updatedAt: new Date().toISOString(),
|
|
102
|
+
run: serializeRun(run),
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Read a checkpoint's raw persisted shape ({runId, flowId, updatedAt, run}),
|
|
108
|
+
* or null when no checkpoint exists or the file is corrupt.
|
|
109
|
+
*/
|
|
110
|
+
export function loadCheckpoint(cwd, runId) {
|
|
111
|
+
const file = checkpointPath(cwd, runId);
|
|
112
|
+
if (!existsSync(file)) return null;
|
|
113
|
+
try {
|
|
114
|
+
return JSON.parse(readFileSync(file, 'utf8'));
|
|
115
|
+
} catch {
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Reconstruct a Run from its checkpoint against the given flow, or null when
|
|
122
|
+
* no checkpoint exists. Throws FlowCheckpointError when the checkpoint was
|
|
123
|
+
* recorded against a different flow id than the one supplied — resuming a
|
|
124
|
+
* run against the wrong step graph would silently reinterpret its history.
|
|
125
|
+
*/
|
|
126
|
+
export function resumeRun(cwd, runId, flow) {
|
|
127
|
+
const checkpoint = loadCheckpoint(cwd, runId);
|
|
128
|
+
if (!checkpoint) return null;
|
|
129
|
+
if (flow.id && checkpoint.flowId && flow.id !== checkpoint.flowId) {
|
|
130
|
+
throw new FlowCheckpointError(`checkpoint "${runId}" was recorded against flow "${checkpoint.flowId}", not "${flow.id}"`);
|
|
131
|
+
}
|
|
132
|
+
return deserializeRun(flow, checkpoint.run);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Start a new run and checkpoint it immediately, so a resume attempted before
|
|
137
|
+
* the first tick still finds a valid checkpoint rather than nothing.
|
|
138
|
+
*/
|
|
139
|
+
export function startRun(cwd, runId, flow, initialState = {}) {
|
|
140
|
+
const run = createRun(flow, initialState);
|
|
141
|
+
checkpointRun(cwd, runId, flow, run);
|
|
142
|
+
return run;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Advance a checkpointed run by exactly one tick: resume from the last
|
|
147
|
+
* checkpoint when one exists, otherwise start fresh from initialState, then
|
|
148
|
+
* run a single advanceRun() and checkpoint the result. Unlike
|
|
149
|
+
* runCheckpointed(), this never loops to completion — each call surfaces
|
|
150
|
+
* exactly the step that tick just completed, which is what a caller driving
|
|
151
|
+
* a run one delegation at a time (e.g. lib/orchestration/delegation-flow.mjs)
|
|
152
|
+
* needs: the current step, not the whole remaining chain. A run already at a
|
|
153
|
+
* terminal status is returned as-is, not re-driven.
|
|
154
|
+
*/
|
|
155
|
+
export async function tickCheckpointed(cwd, runId, flow, initialState = {}) {
|
|
156
|
+
let run = resumeRun(cwd, runId, flow) ?? startRun(cwd, runId, flow, initialState);
|
|
157
|
+
if (run.status === RUN_STATUS.RUNNING) {
|
|
158
|
+
run = await advanceRun(run);
|
|
159
|
+
checkpointRun(cwd, runId, flow, run);
|
|
160
|
+
}
|
|
161
|
+
return run;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Drive a flow to completion one checkpointed tick at a time: resume from the
|
|
166
|
+
* last checkpoint when one exists, otherwise start fresh from initialState.
|
|
167
|
+
* Every tick's result is checkpointed before the loop continues, so a crash
|
|
168
|
+
* at any point resumes from the most recently completed step, never earlier
|
|
169
|
+
* and never mid-step.
|
|
170
|
+
*/
|
|
171
|
+
export async function runCheckpointed(cwd, runId, flow, initialState = {}, { maxSteps = 10000 } = {}) {
|
|
172
|
+
let run = resumeRun(cwd, runId, flow) ?? startRun(cwd, runId, flow, initialState);
|
|
173
|
+
let steps = 0;
|
|
174
|
+
while (run.status === RUN_STATUS.RUNNING && steps < maxSteps) {
|
|
175
|
+
run = await advanceRun(run);
|
|
176
|
+
checkpointRun(cwd, runId, flow, run);
|
|
177
|
+
steps += 1;
|
|
178
|
+
}
|
|
179
|
+
if (run.status === RUN_STATUS.RUNNING && steps >= maxSteps) {
|
|
180
|
+
run = { ...run, status: RUN_STATUS.ERROR, error: { code: 'MAX_STEPS_EXCEEDED', message: `flow exceeded ${maxSteps} steps without reaching a terminal state` } };
|
|
181
|
+
checkpointRun(cwd, runId, flow, run);
|
|
182
|
+
}
|
|
183
|
+
return run;
|
|
184
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/flows/constants.mjs — shared sentinels and status enums for the flow engine.
|
|
3
|
+
*
|
|
4
|
+
* TERMINAL is a plain string sentinel (not a Symbol) so a router's return value
|
|
5
|
+
* survives JSON serialization unchanged, matching the future checkpoint/resume
|
|
6
|
+
* requirement that a run's next-step pointer be persistable as-is.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export const TERMINAL = '@@flow/terminal';
|
|
10
|
+
|
|
11
|
+
export const WORKER_BACKENDS = Object.freeze(['inline', 'provider', 'host']);
|
|
12
|
+
|
|
13
|
+
export const STEP_STATUS = Object.freeze({
|
|
14
|
+
DONE: 'done',
|
|
15
|
+
ERROR: 'error',
|
|
16
|
+
BUDGET_EXHAUSTED: 'budget-exhausted',
|
|
17
|
+
INVALID_STATE: 'invalid-state',
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
export const RUN_STATUS = Object.freeze({
|
|
21
|
+
RUNNING: 'running',
|
|
22
|
+
COMPLETED: 'completed',
|
|
23
|
+
ERROR: 'error',
|
|
24
|
+
BUDGET_EXHAUSTED: 'budget-exhausted',
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
export const JOIN_MODES = Object.freeze(['all', 'any']);
|