@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,365 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/embed/capability-jobs.mjs — daemon-side registration of embed-capability
|
|
3
|
+
* scheduled jobs (ADR-0061 §4, LMCP-P2/F5).
|
|
4
|
+
*
|
|
5
|
+
* `registerEmbedCapabilityJobs` reads the enabled set and registers exactly
|
|
6
|
+
* one Scheduler job per enabled capability — no more, no fewer, and never
|
|
7
|
+
* for a capability that is merely available-but-not-enabled. Each job body
|
|
8
|
+
* (`runCapabilityTick`) is the real F5 glue: it slices the specialist's
|
|
9
|
+
* bound provider snapshot (E4 providerBindings + B11 filter enforcement)
|
|
10
|
+
* out of the daemon's last snapshot, asks `workflow-invoke.mjs` for the
|
|
11
|
+
* orchestration plan, and — only when a reasoning executor is actually
|
|
12
|
+
* wired in (see below) — runs it, validates the resulting output packet
|
|
13
|
+
* against the role's output contract (F2's `validateOutputPacket`), checks
|
|
14
|
+
* every proposed external write against the specialist's E4 grant
|
|
15
|
+
* (`AuthorityGuard`), and enqueues surviving proposals as durable
|
|
16
|
+
* writeIntents in the approval queue (I2). The daemon never calls a
|
|
17
|
+
* provider write adapter itself — enqueue is the only path out of this
|
|
18
|
+
* module; the J2 envelope executor is the sole drain.
|
|
19
|
+
*
|
|
20
|
+
* Reasoning-executor placement (ADR-0061 §3): `resolveRuntime` reports
|
|
21
|
+
* whether an `in-process` or `external` runtime is *configured*, but no
|
|
22
|
+
* actual reasoning engine ships in this repo yet — invoking one is a
|
|
23
|
+
* separate, later capability. `runCapabilityTick` accepts an optional
|
|
24
|
+
* `reasoningExecutor(plan, sliceCtx) -> { outputPacket, writeProposals }`
|
|
25
|
+
* so a real engine can be wired in without changing this module again;
|
|
26
|
+
* absent that injection, a resolved runtime with no executor records a
|
|
27
|
+
* visible `skipped-with-reason(reasoning-executor-not-available)` tick —
|
|
28
|
+
* never a fabricated completion. A `runtime: 'none'` resolution still
|
|
29
|
+
* short-circuits to `skipped-with-reason(no-runtime)` before any of this
|
|
30
|
+
* even runs, exactly as the pre-F5 stub did.
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
import { loadEmbedCapabilities } from './capability-loader.mjs';
|
|
34
|
+
import { enabledCapabilityIds, writeCapabilityTick } from './capability-lifecycle.mjs';
|
|
35
|
+
import { resolveRuntime } from './capability-runtime.mjs';
|
|
36
|
+
import { matchesFilter, validateFilterConfig } from '../providers/contract.mjs';
|
|
37
|
+
import { validateOutputPacket } from '../orchestration/worker.mjs';
|
|
38
|
+
import { invokeWorkflow } from '../embedded-contract/workflow-invoke.mjs';
|
|
39
|
+
import { loadAllPacks } from '../packs/loader.mjs';
|
|
40
|
+
import { AuthorityGuard } from './authority-guard.mjs';
|
|
41
|
+
|
|
42
|
+
/** Default cadence (ms) for a capability with no declared `embed.cadence.every`. */
|
|
43
|
+
const DEFAULT_CADENCE_MS = 15 * 60_000;
|
|
44
|
+
|
|
45
|
+
/** Reserved reason recorded when a runtime resolves but no reasoning executor is wired in. */
|
|
46
|
+
export const SKIP_REASON_NO_EXECUTOR = 'reasoning-executor-not-available';
|
|
47
|
+
|
|
48
|
+
/** Generic linear workflow used to obtain a role-scoped plan for a capability's bound specialist. */
|
|
49
|
+
const CAPABILITY_WORKFLOW_TYPE = 'structure-notes';
|
|
50
|
+
|
|
51
|
+
const PACK_TIER_RANK = { project: 0, user: 1, builtin: 2, unknown: 3 };
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Parse the ISO-8601 duration subset accepted by `embed.cadence.every`
|
|
55
|
+
* (P and T with D/H/M components — the same grammar ADR-0060's
|
|
56
|
+
* `updatedSince` predicate accepts). An unparsable value returns null,
|
|
57
|
+
* falling back to DEFAULT_CADENCE_MS rather than throwing at daemon
|
|
58
|
+
* startup over one malformed capability.
|
|
59
|
+
*/
|
|
60
|
+
export function parseCadenceMs(every) {
|
|
61
|
+
if (typeof every !== 'string' || !every) return null;
|
|
62
|
+
const match = /^P(?:(\d+)D)?(?:T(?:(\d+)H)?(?:(\d+)M)?)?$/.exec(every);
|
|
63
|
+
if (!match) return null;
|
|
64
|
+
const days = Number(match[1] ?? 0);
|
|
65
|
+
const hours = Number(match[2] ?? 0);
|
|
66
|
+
const minutes = Number(match[3] ?? 0);
|
|
67
|
+
const ms = ((days * 24 + hours) * 60 + minutes) * 60_000;
|
|
68
|
+
return ms > 0 ? ms : null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Strip a `cx-` prefix from a specialist id, mirroring the bare role ids
|
|
73
|
+
* `workflow-invoke.mjs` (role-facts.mjs `roleMap`) and `AuthorityGuard`'s
|
|
74
|
+
* embedBindings map key expect.
|
|
75
|
+
*/
|
|
76
|
+
function bareRoleId(specialistId) {
|
|
77
|
+
return String(specialistId || '').replace(/^cx-/, '');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Merge every loaded pack's `embedBindings` block into a single
|
|
82
|
+
* specialistId → { providers[], proposals[] } map, project tier winning
|
|
83
|
+
* over user winning over builtin — the same precedence
|
|
84
|
+
* `lib/embedded-contract/workflow-invoke.mjs` applies to framework
|
|
85
|
+
* resolution. Reuses `loadAllPacks` read-only; performs no validation of
|
|
86
|
+
* its own (E4 validation already ran when the pack was loaded).
|
|
87
|
+
*/
|
|
88
|
+
export function mergedEmbedBindings({ rootDir, env = process.env } = {}) {
|
|
89
|
+
const { packs } = loadAllPacks({ rootDir, env });
|
|
90
|
+
const ordered = [...packs].sort((a, b) => (PACK_TIER_RANK[a._tier] ?? 3) - (PACK_TIER_RANK[b._tier] ?? 3));
|
|
91
|
+
|
|
92
|
+
const merged = {};
|
|
93
|
+
// Lowest precedence first so a higher-precedence tier's binding for the
|
|
94
|
+
// same specialist overwrites rather than merges field-by-field — a
|
|
95
|
+
// project override fully replaces a builtin grant for that specialist.
|
|
96
|
+
for (const pack of [...ordered].reverse()) {
|
|
97
|
+
for (const [specialistId, binding] of Object.entries(pack.embedBindings || {})) {
|
|
98
|
+
merged[specialistId] = binding;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return merged;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Slice the capability's bound providers out of the daemon's last-generated
|
|
106
|
+
* snapshot and re-apply the capability's own `embed.filter` (ADR-0060/B11)
|
|
107
|
+
* on top of whatever the poll-time source filter already admitted — a
|
|
108
|
+
* capability's filter can be narrower than the source's, never wider,
|
|
109
|
+
* since it runs after the source filter already ran. Returns `null`
|
|
110
|
+
* sections are skipped entirely when the bound provider has no section in
|
|
111
|
+
* the snapshot (not configured as a source, or the provider errored).
|
|
112
|
+
*
|
|
113
|
+
* @param {object|null} snapshot the daemon's last SnapshotEngine.generate() result
|
|
114
|
+
* @param {string[]} providerBindings
|
|
115
|
+
* @param {object|null} filter embed.filter (ADR-0060 block) or null
|
|
116
|
+
* @returns {{ sections: Array<{provider:string, items:object[]}>, errors: string[] }}
|
|
117
|
+
*/
|
|
118
|
+
export function sliceBoundSnapshot(snapshot, providerBindings, filter) {
|
|
119
|
+
const bound = new Set(providerBindings || []);
|
|
120
|
+
const sections = [];
|
|
121
|
+
const errors = [];
|
|
122
|
+
|
|
123
|
+
for (const section of snapshot?.sections ?? []) {
|
|
124
|
+
if (!bound.has(section.provider)) continue;
|
|
125
|
+
|
|
126
|
+
let items = section.items ?? [];
|
|
127
|
+
if (filter != null) {
|
|
128
|
+
try {
|
|
129
|
+
validateFilterConfig(section.provider, filter);
|
|
130
|
+
items = items.filter((item) => matchesFilter(item, filter));
|
|
131
|
+
} catch (err) {
|
|
132
|
+
errors.push(`embed.filter invalid for bound provider '${section.provider}': ${err.message}`);
|
|
133
|
+
items = [];
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
sections.push({ provider: section.provider, items });
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return { sections, errors };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Any-key-autonomous authority profile: `AuthorityGuard.check()` runs its
|
|
144
|
+
* E4 binding gate before consulting authority level at all, but its
|
|
145
|
+
* approval-queued branch calls `approvalQueue.approvalMode()`, a method
|
|
146
|
+
* the real `lib/embed/approval-queue.mjs` ApprovalQueue does not implement
|
|
147
|
+
* (only test doubles do). This module owns writeIntent enqueueing itself
|
|
148
|
+
* (the actual durable record), so the guard is only ever consulted here
|
|
149
|
+
* for the binding decision — every key resolves `autonomous` so a granted
|
|
150
|
+
* proposal short-circuits to `allowed:true` without reaching that branch.
|
|
151
|
+
*/
|
|
152
|
+
const AUTONOMOUS_AUTHORITY = new Proxy({}, { get: () => 'autonomous' });
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Check a single proposed write against the specialist's E4 grant.
|
|
156
|
+
* Returns `{ allowed: true }` or `{ allowed: false, reason }` — never
|
|
157
|
+
* throws, so one denied proposal never aborts the rest of the batch.
|
|
158
|
+
*/
|
|
159
|
+
async function checkProposalAuthority(embedBindings, { specialistId, providerId, writeKind }) {
|
|
160
|
+
const guard = new AuthorityGuard({ authority: AUTONOMOUS_AUTHORITY }, null, embedBindings);
|
|
161
|
+
const result = await guard.check(`${providerId}.${writeKind}`, {
|
|
162
|
+
proposal: { specialistId, providerId, writeKind },
|
|
163
|
+
});
|
|
164
|
+
return result.allowed
|
|
165
|
+
? { allowed: true }
|
|
166
|
+
: { allowed: false, reason: result.reason || `proposal ${providerId}.${writeKind} denied` };
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Real tick body (LMCP-F5): resolves the runtime selector, and when a
|
|
171
|
+
* runtime is available AND a reasoning executor is wired in, composes the
|
|
172
|
+
* specialist's bound snapshot slice, asks workflow-invoke.mjs for the
|
|
173
|
+
* orchestration plan, runs the executor, validates its output packet
|
|
174
|
+
* against the role's output contract, checks every proposed write against
|
|
175
|
+
* the E4 grant, and enqueues surviving proposals as writeIntents. Every
|
|
176
|
+
* other path — no runtime, no executor, contract failure — records an
|
|
177
|
+
* honest status and never a fabricated completion.
|
|
178
|
+
*
|
|
179
|
+
* @param {object} manifest embed-capability manifest (capability-loader.mjs shape)
|
|
180
|
+
* @param {object} [opts]
|
|
181
|
+
* @param {string} opts.rootDir
|
|
182
|
+
* @param {object} [opts.env]
|
|
183
|
+
* @param {() => object|null} [opts.getSnapshot] returns the daemon's last snapshot, or null
|
|
184
|
+
* @param {import('./approval-queue.mjs').ApprovalQueue} [opts.approvalQueue]
|
|
185
|
+
* @param {object} [opts.embedBindings] specialistId → {providers[],proposals[]}; built fresh from mergedEmbedBindings when omitted
|
|
186
|
+
* @param {(plan: object, ctx: object) => Promise<{outputPacket?: object, writeProposals?: object[]}>} [opts.reasoningExecutor]
|
|
187
|
+
* @returns {Promise<object>} the recorded tick
|
|
188
|
+
*/
|
|
189
|
+
export async function runCapabilityTick(manifest, {
|
|
190
|
+
rootDir,
|
|
191
|
+
env = process.env,
|
|
192
|
+
getSnapshot = () => null,
|
|
193
|
+
approvalQueue = null,
|
|
194
|
+
embedBindings = null,
|
|
195
|
+
reasoningExecutor = null,
|
|
196
|
+
} = {}) {
|
|
197
|
+
const embed = manifest.embed;
|
|
198
|
+
const runtime = await resolveRuntime(embed.runtime, env);
|
|
199
|
+
const tickedAt = new Date().toISOString();
|
|
200
|
+
|
|
201
|
+
if (runtime.resolved === 'none') {
|
|
202
|
+
const tick = { status: 'skipped-with-reason', reason: runtime.reason, runtime: runtime.resolved, tickedAt };
|
|
203
|
+
writeCapabilityTick(manifest.id, tick, rootDir);
|
|
204
|
+
return tick;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (typeof reasoningExecutor !== 'function') {
|
|
208
|
+
const tick = { status: 'skipped-with-reason', reason: SKIP_REASON_NO_EXECUTOR, runtime: runtime.resolved, tickedAt };
|
|
209
|
+
writeCapabilityTick(manifest.id, tick, rootDir);
|
|
210
|
+
return tick;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const specialistId = bareRoleId(embed.specialist);
|
|
214
|
+
const snapshot = getSnapshot();
|
|
215
|
+
const { sections, errors: sliceErrors } = sliceBoundSnapshot(snapshot, embed.providerBindings, embed.filter ?? null);
|
|
216
|
+
|
|
217
|
+
const plan = await invokeWorkflow(
|
|
218
|
+
{
|
|
219
|
+
workflowType: CAPABILITY_WORKFLOW_TYPE,
|
|
220
|
+
roleStrategy: 'explicit',
|
|
221
|
+
requestedRoles: [specialistId],
|
|
222
|
+
approvalMode: 'proposal-only',
|
|
223
|
+
context: { snapshotSections: sections },
|
|
224
|
+
},
|
|
225
|
+
{ env, cwd: rootDir },
|
|
226
|
+
);
|
|
227
|
+
|
|
228
|
+
if (plan.status === 'error') {
|
|
229
|
+
const tick = {
|
|
230
|
+
status: 'skipped-with-reason',
|
|
231
|
+
reason: `plan-error: ${plan.errors?.[0]?.code || 'unknown'}`,
|
|
232
|
+
runtime: runtime.resolved,
|
|
233
|
+
tickedAt,
|
|
234
|
+
};
|
|
235
|
+
writeCapabilityTick(manifest.id, tick, rootDir);
|
|
236
|
+
return tick;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const bindings = embedBindings ?? mergedEmbedBindings({ rootDir, env });
|
|
240
|
+
|
|
241
|
+
const executorResult = await reasoningExecutor(plan, { manifest, sections, sliceErrors, specialistId });
|
|
242
|
+
const outputPacket = executorResult?.outputPacket ?? null;
|
|
243
|
+
const writeProposals = Array.isArray(executorResult?.writeProposals) ? executorResult.writeProposals : [];
|
|
244
|
+
|
|
245
|
+
const outputCheck = validateOutputPacket(
|
|
246
|
+
{ role: `cx-${specialistId}`, outputContractId: embed.outputContract, outputPacket },
|
|
247
|
+
{ cwd: rootDir },
|
|
248
|
+
);
|
|
249
|
+
|
|
250
|
+
if (outputCheck.checked && outputCheck.contractStatus === 'contract-failed') {
|
|
251
|
+
const tick = {
|
|
252
|
+
status: 'blocked',
|
|
253
|
+
reason: 'output-contract-violation',
|
|
254
|
+
contractId: outputCheck.contractId,
|
|
255
|
+
violations: outputCheck.violations,
|
|
256
|
+
runtime: runtime.resolved,
|
|
257
|
+
tickedAt,
|
|
258
|
+
};
|
|
259
|
+
writeCapabilityTick(manifest.id, tick, rootDir);
|
|
260
|
+
return tick;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const enqueued = [];
|
|
264
|
+
const denied = [];
|
|
265
|
+
|
|
266
|
+
for (const proposal of writeProposals) {
|
|
267
|
+
const { providerId, writeKind, payload } = proposal || {};
|
|
268
|
+
const authResult = await checkProposalAuthority(bindings, { specialistId, providerId, writeKind });
|
|
269
|
+
|
|
270
|
+
if (!authResult.allowed) {
|
|
271
|
+
denied.push({ providerId, writeKind, reason: authResult.reason });
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const record = approvalQueue?.enqueue({
|
|
276
|
+
tool: `${providerId}.${writeKind}`,
|
|
277
|
+
args: payload ?? {},
|
|
278
|
+
surface: 'embed-capability',
|
|
279
|
+
requestedBy: { serviceId: manifest.id, role: `cx-${specialistId}` },
|
|
280
|
+
});
|
|
281
|
+
enqueued.push({ providerId, writeKind, approvalId: record?.approvalId ?? null });
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const tick = {
|
|
285
|
+
status: 'ran',
|
|
286
|
+
runtime: runtime.resolved,
|
|
287
|
+
contractStatus: outputCheck.contractStatus,
|
|
288
|
+
proposalsEnqueued: enqueued,
|
|
289
|
+
proposalsDenied: denied,
|
|
290
|
+
tickedAt,
|
|
291
|
+
};
|
|
292
|
+
writeCapabilityTick(manifest.id, tick, rootDir);
|
|
293
|
+
return tick;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Register exactly one Scheduler job per enabled embed capability.
|
|
298
|
+
* Capabilities that are available but not enabled are never registered.
|
|
299
|
+
* An invalid or missing manifest for a nominally-enabled id is skipped with
|
|
300
|
+
* a stderr line rather than crashing daemon startup — the loader already
|
|
301
|
+
* fails closed at `enable` time, so this path only defends against a
|
|
302
|
+
* project manifest edited or corrupted after enable.
|
|
303
|
+
*
|
|
304
|
+
* @param {import('./scheduler.mjs').Scheduler} scheduler
|
|
305
|
+
* @param {object} opts
|
|
306
|
+
* @param {string} opts.rootDir
|
|
307
|
+
* @param {object} [opts.env]
|
|
308
|
+
* @param {string[]} [opts.packRoots]
|
|
309
|
+
* @param {string[]} [opts.knownSpecialists]
|
|
310
|
+
* @param {() => object|null} [opts.getSnapshot]
|
|
311
|
+
* @param {import('./approval-queue.mjs').ApprovalQueue} [opts.approvalQueue]
|
|
312
|
+
* @param {object} [opts.embedBindings]
|
|
313
|
+
* @param {(plan: object, ctx: object) => Promise<object>} [opts.reasoningExecutor]
|
|
314
|
+
* @returns {string[]} ids of capabilities actually registered
|
|
315
|
+
*/
|
|
316
|
+
export function registerEmbedCapabilityJobs(scheduler, {
|
|
317
|
+
rootDir,
|
|
318
|
+
env = process.env,
|
|
319
|
+
packRoots,
|
|
320
|
+
knownSpecialists,
|
|
321
|
+
getSnapshot,
|
|
322
|
+
approvalQueue,
|
|
323
|
+
embedBindings,
|
|
324
|
+
reasoningExecutor,
|
|
325
|
+
} = {}) {
|
|
326
|
+
const enabledIds = new Set(enabledCapabilityIds({ rootDir, packRoots, knownSpecialists }));
|
|
327
|
+
if (enabledIds.size === 0) return [];
|
|
328
|
+
|
|
329
|
+
const { capabilities, errors } = loadEmbedCapabilities({ rootDir, packRoots, knownSpecialists });
|
|
330
|
+
for (const err of errors) {
|
|
331
|
+
process.stderr.write(`[embed] capability load error: ${err}\n`);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
const registered = [];
|
|
335
|
+
for (const manifest of capabilities) {
|
|
336
|
+
if (!enabledIds.has(manifest.id)) continue;
|
|
337
|
+
|
|
338
|
+
const cadenceMs = parseCadenceMs(manifest.embed.cadence?.every) ?? DEFAULT_CADENCE_MS;
|
|
339
|
+
scheduler.register(
|
|
340
|
+
`embed-capability:${manifest.id}`,
|
|
341
|
+
cadenceMs,
|
|
342
|
+
async () => {
|
|
343
|
+
const tick = await runCapabilityTick(manifest, {
|
|
344
|
+
rootDir,
|
|
345
|
+
env,
|
|
346
|
+
getSnapshot,
|
|
347
|
+
approvalQueue,
|
|
348
|
+
embedBindings,
|
|
349
|
+
reasoningExecutor,
|
|
350
|
+
});
|
|
351
|
+
const detail = tick.reason ? ` (${tick.reason})` : '';
|
|
352
|
+
process.stderr.write(`[embed] capability '${manifest.id}': ${tick.status}${detail}\n`);
|
|
353
|
+
},
|
|
354
|
+
{ runImmediately: true },
|
|
355
|
+
);
|
|
356
|
+
registered.push(manifest.id);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
const missingIds = [...enabledIds].filter((id) => !registered.includes(id));
|
|
360
|
+
for (const id of missingIds) {
|
|
361
|
+
process.stderr.write(`[embed] capability '${id}' is enabled but its manifest failed to load — not registered\n`);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
return registered;
|
|
365
|
+
}
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/embed/capability-lifecycle.mjs — enable/disable/status/dry-run surface
|
|
3
|
+
* for embed capabilities (ADR-0061 §4, LMCP-P2).
|
|
4
|
+
*
|
|
5
|
+
* `enableCapability` / `disableCapability` persist the per-project tier
|
|
6
|
+
* manifest at `.cx/embed/<id>.manifest.json` (via lib/embed/capability-loader.mjs);
|
|
7
|
+
* a capability is only ever active when this file exists with `embed.enabled
|
|
8
|
+
* === true` — pack/builtin tiers ship it merely *available*, and enablement
|
|
9
|
+
* is explicit opt-in, never a default. `listCapabilities` and
|
|
10
|
+
* `capabilityStatus` read the merged D1 view plus the durable last-tick
|
|
11
|
+
* record the daemon-registration helper in lib/embed/capability-jobs.mjs
|
|
12
|
+
* writes. `resolveCapabilityChain` resolves the full binding chain
|
|
13
|
+
* (specialist → providers → filter → framework → authority → runtime) and
|
|
14
|
+
* returns it without calling a model or touching the last-tick record — a
|
|
15
|
+
* pure read, matching the ADR's "execute nothing" requirement.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
19
|
+
import { join } from 'node:path';
|
|
20
|
+
|
|
21
|
+
import {
|
|
22
|
+
loadEmbedCapabilities,
|
|
23
|
+
readProjectEmbedManifest,
|
|
24
|
+
validateEmbedManifest,
|
|
25
|
+
writeProjectEmbedManifest,
|
|
26
|
+
} from './capability-loader.mjs';
|
|
27
|
+
import { resolveRuntime } from './capability-runtime.mjs';
|
|
28
|
+
|
|
29
|
+
/** Durable per-capability last-tick record directory. */
|
|
30
|
+
export function capabilityStatusDir(rootDir = process.cwd()) {
|
|
31
|
+
return join(rootDir, '.cx', 'runtime', 'embed-capabilities');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function capabilityStatusPath(id, rootDir = process.cwd()) {
|
|
35
|
+
return join(capabilityStatusDir(rootDir), `${id}.json`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Read the durable last-tick record for a capability. Returns null when the
|
|
40
|
+
* capability has never ticked (e.g. just enabled, daemon not yet started).
|
|
41
|
+
*/
|
|
42
|
+
export function readCapabilityTick(id, rootDir = process.cwd()) {
|
|
43
|
+
const p = capabilityStatusPath(id, rootDir);
|
|
44
|
+
if (!existsSync(p)) return null;
|
|
45
|
+
try {
|
|
46
|
+
return JSON.parse(readFileSync(p, 'utf8'));
|
|
47
|
+
} catch {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Write the durable last-tick record for a capability. Exported so the
|
|
54
|
+
* daemon's registered job body (this bead's stub, F5's real body) has one
|
|
55
|
+
* place to record `ran | skipped-with-reason | error` outcomes.
|
|
56
|
+
*/
|
|
57
|
+
export function writeCapabilityTick(id, tick, rootDir = process.cwd()) {
|
|
58
|
+
const dir = capabilityStatusDir(rootDir);
|
|
59
|
+
mkdirSync(dir, { recursive: true });
|
|
60
|
+
writeFileSync(capabilityStatusPath(id, rootDir), `${JSON.stringify(tick, null, 2)}\n`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* List every discoverable embed capability with its merged manifest,
|
|
65
|
+
* project-tier enabled/override state, and loader errors. Each entry:
|
|
66
|
+
* { id, available: true, enabled, manifest, source: 'project'|'pack'|'builtin' }
|
|
67
|
+
*/
|
|
68
|
+
export function listCapabilities({ rootDir = process.cwd(), packRoots, knownSpecialists } = {}) {
|
|
69
|
+
const { capabilities, errors } = loadEmbedCapabilities({ rootDir, packRoots, knownSpecialists });
|
|
70
|
+
|
|
71
|
+
const entries = capabilities.map((manifest) => {
|
|
72
|
+
const override = readProjectEmbedManifest(manifest.id, rootDir);
|
|
73
|
+
const enabled = override?.manifest?.embed?.enabled === true;
|
|
74
|
+
return {
|
|
75
|
+
id: manifest.id,
|
|
76
|
+
available: true,
|
|
77
|
+
enabled,
|
|
78
|
+
manifest,
|
|
79
|
+
source: override ? 'project' : (manifest._filePath?.includes(join('.cx', 'embed')) ? 'project' : 'pack-or-builtin'),
|
|
80
|
+
};
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
return { capabilities: entries, errors };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Enable a capability: merge the pack/builtin-default manifest (if any) with
|
|
88
|
+
* an optional override, stamp `embed.enabled: true`, validate the resulting
|
|
89
|
+
* manifest against the ADR-0061 schema, and write it to the project tier.
|
|
90
|
+
* Fails closed — an invalid manifest is never written; the returned result
|
|
91
|
+
* carries the JSON-schema-style error paths instead.
|
|
92
|
+
*
|
|
93
|
+
* @returns {{ ok: true, filePath: string, manifest: object } | { ok: false, errors: string[] }}
|
|
94
|
+
*/
|
|
95
|
+
export function enableCapability(id, { rootDir = process.cwd(), overrides = {}, packRoots, knownSpecialists } = {}) {
|
|
96
|
+
const { capabilities, errors: loadErrors } = loadEmbedCapabilities({ rootDir, packRoots, knownSpecialists });
|
|
97
|
+
const defaultManifest = capabilities.find((c) => c.id === id);
|
|
98
|
+
|
|
99
|
+
if (!defaultManifest && Object.keys(overrides).length === 0) {
|
|
100
|
+
return {
|
|
101
|
+
ok: false,
|
|
102
|
+
errors: [`embed capability '${id}' not found in any tier (pack defaults or .cx/embed/) and no overrides supplied to define it`, ...loadErrors],
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const base = defaultManifest ?? { id, type: 'embed', version: '1.0.0', defaultApprovalMode: 'proposal-only', embed: {} };
|
|
107
|
+
const candidate = {
|
|
108
|
+
...base,
|
|
109
|
+
...overrides,
|
|
110
|
+
embed: { ...base.embed, ...(overrides.embed ?? {}), enabled: true },
|
|
111
|
+
};
|
|
112
|
+
delete candidate._filePath;
|
|
113
|
+
|
|
114
|
+
const result = validateEmbedManifest(candidate, { filePath: `.cx/embed/${id}.manifest.json`, knownSpecialists });
|
|
115
|
+
if (!result.valid) {
|
|
116
|
+
return { ok: false, errors: result.errors };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const filePath = writeProjectEmbedManifest(id, candidate, rootDir);
|
|
120
|
+
return { ok: true, filePath, manifest: candidate };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Disable a capability: if a project-tier override exists, stamp
|
|
125
|
+
* `embed.enabled: false` in place (preserving any project overrides) rather
|
|
126
|
+
* than deleting the file — a subsequent `enable` should not silently lose
|
|
127
|
+
* prior project-tier customization. Idempotent: disabling an
|
|
128
|
+
* already-disabled or never-enabled capability succeeds without error.
|
|
129
|
+
*/
|
|
130
|
+
export function disableCapability(id, { rootDir = process.cwd() } = {}) {
|
|
131
|
+
const existing = readProjectEmbedManifest(id, rootDir);
|
|
132
|
+
if (!existing) {
|
|
133
|
+
return { ok: true, filePath: null, wasEnabled: false };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const manifest = {
|
|
137
|
+
...existing.manifest,
|
|
138
|
+
embed: { ...existing.manifest.embed, enabled: false },
|
|
139
|
+
};
|
|
140
|
+
const filePath = writeProjectEmbedManifest(id, manifest, rootDir);
|
|
141
|
+
return { ok: true, filePath, wasEnabled: existing.manifest?.embed?.enabled === true };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Resolve the full binding chain for a capability without side effects —
|
|
146
|
+
* no model call, no last-tick record written. Returns the same shape
|
|
147
|
+
* `construct embed dry-run <id>` prints.
|
|
148
|
+
*/
|
|
149
|
+
export async function resolveCapabilityChain(id, { rootDir = process.cwd(), env = process.env, packRoots, knownSpecialists } = {}) {
|
|
150
|
+
const { capabilities, errors } = loadEmbedCapabilities({ rootDir, packRoots, knownSpecialists });
|
|
151
|
+
const manifest = capabilities.find((c) => c.id === id);
|
|
152
|
+
if (!manifest) {
|
|
153
|
+
return { ok: false, errors: [`embed capability '${id}' not found`, ...errors] };
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const embed = manifest.embed;
|
|
157
|
+
const runtime = await resolveRuntime(embed.runtime, env);
|
|
158
|
+
|
|
159
|
+
return {
|
|
160
|
+
ok: true,
|
|
161
|
+
id,
|
|
162
|
+
chain: {
|
|
163
|
+
specialist: embed.specialist,
|
|
164
|
+
providerBindings: embed.providerBindings,
|
|
165
|
+
filter: embed.filter ?? null,
|
|
166
|
+
framework: embed.framework,
|
|
167
|
+
outputContract: embed.outputContract,
|
|
168
|
+
proposalAuthority: embed.proposalAuthority,
|
|
169
|
+
cadence: embed.cadence ?? null,
|
|
170
|
+
runtime: { declared: embed.runtime, ...runtime },
|
|
171
|
+
},
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Per-specialist status: enabled state, bound providers, filter, runtime
|
|
177
|
+
* resolution, and the last durable tick record (`ran | skipped-with-reason |
|
|
178
|
+
* error`, or null if the capability has never ticked).
|
|
179
|
+
*/
|
|
180
|
+
export async function capabilityStatus(id, { rootDir = process.cwd(), env = process.env, packRoots, knownSpecialists } = {}) {
|
|
181
|
+
const chain = await resolveCapabilityChain(id, { rootDir, env, packRoots, knownSpecialists });
|
|
182
|
+
if (!chain.ok) return chain;
|
|
183
|
+
|
|
184
|
+
const override = readProjectEmbedManifest(id, rootDir);
|
|
185
|
+
const enabled = override?.manifest?.embed?.enabled === true;
|
|
186
|
+
const lastTick = readCapabilityTick(id, rootDir);
|
|
187
|
+
|
|
188
|
+
return {
|
|
189
|
+
ok: true,
|
|
190
|
+
id,
|
|
191
|
+
enabled,
|
|
192
|
+
chain: chain.chain,
|
|
193
|
+
lastTick,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Enumerate the enabled-set as the daemon should register it: every
|
|
199
|
+
* discoverable capability whose project-tier manifest carries
|
|
200
|
+
* `embed.enabled === true`. Per ADR-0061, enablement is explicit opt-in —
|
|
201
|
+
* a project-tier file with no `enabled` field, or any value other than
|
|
202
|
+
* `true`, is not active. Both the CLI (`embed list`) and the daemon's
|
|
203
|
+
* startup registration read this same definition of "enabled", so there
|
|
204
|
+
* is one source of truth and no drift between what a user sees and what
|
|
205
|
+
* actually schedules.
|
|
206
|
+
*/
|
|
207
|
+
export function enabledCapabilityIds({ rootDir = process.cwd(), packRoots, knownSpecialists } = {}) {
|
|
208
|
+
const { capabilities } = listCapabilities({ rootDir, packRoots, knownSpecialists });
|
|
209
|
+
return capabilities.filter((c) => c.enabled).map((c) => c.id);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Remove the durable last-tick record for a capability. Exported for test
|
|
214
|
+
* cleanup and for `disable` callers that want a clean status surface.
|
|
215
|
+
*/
|
|
216
|
+
export function clearCapabilityTick(id, rootDir = process.cwd()) {
|
|
217
|
+
const p = capabilityStatusPath(id, rootDir);
|
|
218
|
+
if (existsSync(p)) rmSync(p, { force: true });
|
|
219
|
+
}
|