@geraldmaron/construct 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -18
- package/bin/construct +392 -91
- package/bin/construct-postinstall.mjs +30 -2
- package/examples/distribution/README.md +42 -0
- package/examples/distribution/manifest.json +53 -0
- package/examples/distribution/sources/adr.md +84 -0
- package/examples/distribution/sources/deck-one-pager.md +65 -0
- package/examples/distribution/sources/prd-platform.md +161 -0
- package/examples/distribution/sources/research-brief.md +80 -0
- package/examples/distribution/sources/rfc-platform.md +82 -0
- package/examples/distribution/sources/runbook.md +103 -0
- package/examples/distribution/sources/strategy.md +88 -0
- package/lib/adapters-sync.mjs +67 -0
- package/lib/artifact-gate-notice.mjs +38 -0
- package/lib/artifact-manifest.mjs +112 -0
- package/lib/artifact-release-gate.mjs +171 -0
- package/lib/artifact-reviewers.mjs +68 -0
- package/lib/artifact-type-from-path.mjs +79 -0
- package/lib/audit-skills.mjs +4 -0
- package/lib/audit-specialists.mjs +285 -0
- package/lib/audit-trail.mjs +77 -10
- package/lib/auto-docs.mjs +87 -18
- package/lib/beads/drift.mjs +27 -6
- package/lib/beads-client.mjs +49 -121
- package/lib/beads-optimistic.mjs +5 -12
- package/lib/brand-fonts.mjs +93 -0
- package/lib/brand-prose.mjs +214 -0
- package/lib/brand-tokens.mjs +92 -0
- package/lib/bridges/copilot-proxy.mjs +13 -26
- package/lib/capability-ledger.mjs +156 -0
- package/lib/certification/artifact-fixtures.mjs +132 -0
- package/lib/certification/artifact-gates.mjs +63 -0
- package/lib/certification/artifact-provenance.mjs +97 -0
- package/lib/certification/canonical-scenarios.mjs +78 -0
- package/lib/certification/cli.mjs +191 -0
- package/lib/certification/dashboard-api.mjs +71 -0
- package/lib/certification/demo-parity.mjs +116 -0
- package/lib/certification/document-io-fixtures.mjs +246 -0
- package/lib/certification/document-workflow.mjs +97 -0
- package/lib/certification/eval-bridge.mjs +77 -0
- package/lib/certification/model-routing.mjs +149 -0
- package/lib/certification/prompt-budget.mjs +119 -0
- package/lib/certification/rc-gate.mjs +206 -0
- package/lib/certification/real-llm-scenarios.mjs +303 -0
- package/lib/certification/role-cards.mjs +113 -0
- package/lib/certification/role-overlays.mjs +117 -0
- package/lib/certification/run.mjs +122 -0
- package/lib/certification/runner.mjs +323 -0
- package/lib/certification/scenarios.mjs +51 -0
- package/lib/certification/skill-inventory.mjs +289 -0
- package/lib/certification/skill-scenarios.mjs +147 -0
- package/lib/certification/specialist-contracts.mjs +85 -0
- package/lib/certification/specialist-scenarios.mjs +175 -0
- package/lib/certification/stale-impact.mjs +146 -0
- package/lib/certification/status.mjs +252 -0
- package/lib/certification/store.mjs +77 -0
- package/lib/chat/cli.mjs +333 -0
- package/lib/chat/command-suggest.mjs +161 -0
- package/lib/chat/commands.mjs +215 -0
- package/lib/chat/config.mjs +142 -0
- package/lib/chat/context-compactor.mjs +250 -0
- package/lib/chat/context-continuation.mjs +253 -0
- package/lib/chat/continuation-source.mjs +58 -0
- package/lib/chat/demo-guide.mjs +61 -0
- package/lib/chat/design-tokens.mjs +91 -0
- package/lib/chat/desktop-binary.mjs +79 -0
- package/lib/chat/desktop-build.mjs +130 -0
- package/lib/chat/desktop-launcher.mjs +133 -0
- package/lib/chat/evidence.mjs +145 -0
- package/lib/chat/export.mjs +74 -0
- package/lib/chat/harness/driver.mjs +91 -0
- package/lib/chat/list-picker.mjs +112 -0
- package/lib/chat/model-picker.mjs +356 -0
- package/lib/chat/openrouter-fallback.mjs +151 -0
- package/lib/chat/permission-prompt.mjs +33 -0
- package/lib/chat/picker-catalog.mjs +45 -0
- package/lib/chat/policy-telemetry.mjs +34 -0
- package/lib/chat/present.mjs +245 -0
- package/lib/chat/session-context.mjs +39 -0
- package/lib/chat/session-persist.mjs +73 -0
- package/lib/chat/session-restore.mjs +71 -0
- package/lib/chat/session-settings.mjs +53 -0
- package/lib/chat/system-prompt.mjs +52 -0
- package/lib/chat/transparency.mjs +93 -0
- package/lib/chat/tui/color-scheme.mjs +42 -0
- package/lib/chat/tui/markdown.mjs +123 -0
- package/lib/chat/tui/presentation.mjs +100 -0
- package/lib/chat/tui/render.mjs +500 -0
- package/lib/chat/tui/turn-block.mjs +284 -0
- package/lib/chat/tui/turn-present.mjs +18 -0
- package/lib/chat/tui/turn-state.mjs +88 -0
- package/lib/chat/tui/usage.mjs +122 -0
- package/lib/chat/web-commands.mjs +146 -0
- package/lib/chat/web-launcher.mjs +63 -0
- package/lib/chat/web-picker-keys.mjs +46 -0
- package/lib/chat/web-session.mjs +159 -0
- package/lib/cli-commands.mjs +232 -11
- package/lib/comment-lint.mjs +6 -4
- package/lib/config/schema.mjs +36 -5
- package/lib/contract-schemas/decision.json +50 -0
- package/lib/contract-schemas/implementation.json +51 -0
- package/lib/contract-schemas/review-report.json +32 -0
- package/lib/contract-schemas/test-report.json +43 -0
- package/lib/contracts/construct-handoff.mjs +60 -0
- package/lib/contracts/validate.mjs +32 -3
- package/lib/contracts/violation-log.mjs +58 -5
- package/lib/dashboard-demo.mjs +71 -0
- package/lib/dashboard-static.mjs +19 -5
- package/lib/decisions/registry.mjs +5 -3
- package/lib/deck-export-pptx.mjs +1152 -0
- package/lib/demo-recording.mjs +142 -0
- package/lib/demo-script.mjs +114 -0
- package/lib/demo-surface.mjs +249 -0
- package/lib/demo.mjs +703 -0
- package/lib/diagram-export.mjs +192 -0
- package/lib/diagram.mjs +302 -0
- package/lib/docs-verify.mjs +25 -7
- package/lib/doctor/index.mjs +4 -2
- package/lib/doctor/project-adapters.mjs +44 -0
- package/lib/doctor/watchers/cx-budget.mjs +98 -0
- package/lib/doctor/watchers/graph-staleness.mjs +52 -0
- package/lib/doctor/watchers/process-pressure.mjs +4 -3
- package/lib/doctor/watchers/service-health.mjs +1 -1
- package/lib/document-export.mjs +360 -36
- package/lib/document-extract.mjs +88 -0
- package/lib/document-ingest.mjs +32 -18
- package/lib/embed/cli.mjs +2 -2
- package/lib/embed/daemon.mjs +18 -46
- package/lib/embed/semantic.mjs +5 -2
- package/lib/embed/worker.mjs +2 -2
- package/lib/embedded-contract/model-resolve.mjs +10 -9
- package/lib/env-config.mjs +26 -0
- package/lib/evals/dataset.mjs +137 -0
- package/lib/evals/gates.mjs +175 -0
- package/lib/graph/build-co-change.mjs +54 -0
- package/lib/graph/build-from-registry.mjs +136 -0
- package/lib/graph/build-import-graph.mjs +153 -0
- package/lib/graph/cli.mjs +110 -0
- package/lib/graph/impact-cli.mjs +89 -0
- package/lib/graph/impact.mjs +108 -0
- package/lib/graph/staleness.mjs +44 -0
- package/lib/graph/store.mjs +172 -0
- package/lib/handoffs/cleanup.mjs +1 -1
- package/lib/health-check.mjs +90 -76
- package/lib/hooks/artifact-release-gate.mjs +43 -0
- package/lib/hooks/audit-trail.mjs +14 -28
- package/lib/hooks/brand-prose-lint.mjs +38 -0
- package/lib/hooks/graph-impact-advisory.mjs +62 -0
- package/lib/hooks/session-optimize.mjs +84 -207
- package/lib/hooks/session-start.mjs +4 -5
- package/lib/host-disposition.mjs +7 -0
- package/lib/improvement/cli.mjs +189 -0
- package/lib/improvement/controller.mjs +137 -0
- package/lib/improvement/proposal.mjs +120 -0
- package/lib/improvement/specialist-loop.mjs +192 -0
- package/lib/improvement/store.mjs +89 -0
- package/lib/improvement/surface.mjs +219 -0
- package/lib/ingest-tooling.mjs +97 -0
- package/lib/init/doc-lanes.mjs +165 -0
- package/lib/init-docs.mjs +31 -178
- package/lib/init-unified.mjs +27 -97
- package/lib/init-update-guide.mjs +102 -0
- package/lib/init-update.mjs +32 -1
- package/lib/init.mjs +8 -0
- package/lib/install/desktop-binary-download.mjs +85 -0
- package/lib/install/legacy-global-cleanup.mjs +189 -0
- package/lib/intake/daemon.mjs +2 -0
- package/lib/intake/git-queue.mjs +9 -8
- package/lib/intake/queue.mjs +2 -1
- package/lib/intake/session-prelude.mjs +90 -1
- package/lib/libreoffice-export.mjs +97 -0
- package/lib/logging/rotate.mjs +9 -0
- package/lib/maintenance/docker-reclaim.mjs +206 -0
- package/lib/mcp/external-schema-cost.mjs +74 -0
- package/lib/mcp/server.mjs +84 -9
- package/lib/mcp/stdio-mcp-probe.mjs +188 -0
- package/lib/mcp/tool-budget.mjs +55 -4
- package/lib/mcp/tools/document.mjs +18 -4
- package/lib/mcp/tools/skills.mjs +32 -3
- package/lib/mcp/tools/workflow.mjs +13 -1
- package/lib/model-free-selector.mjs +18 -0
- package/lib/model-registry.mjs +13 -229
- package/lib/model-router.mjs +425 -93
- package/lib/models/behavior-matrix.mjs +289 -0
- package/lib/models/catalog.mjs +209 -0
- package/lib/models/execution-capability-profile.mjs +196 -0
- package/lib/models/execution-policy.mjs +307 -0
- package/lib/models/provider-poll.mjs +383 -0
- package/lib/npm-spawn-env.mjs +17 -0
- package/lib/ollama/installed-models.mjs +129 -0
- package/lib/oracle/actions.mjs +187 -0
- package/lib/oracle/artifact-gate.mjs +99 -0
- package/lib/oracle/cli.mjs +204 -0
- package/lib/oracle/daemon-entry.mjs +14 -0
- package/lib/oracle/dispatch.mjs +81 -0
- package/lib/oracle/execute.mjs +143 -0
- package/lib/oracle/gaps.mjs +76 -0
- package/lib/oracle/index.mjs +84 -0
- package/lib/oracle/issues.mjs +164 -0
- package/lib/oracle/org-graph.mjs +170 -0
- package/lib/oracle/policy.mjs +80 -0
- package/lib/oracle/read-model.mjs +398 -0
- package/lib/oracle/reconcile.mjs +191 -0
- package/lib/oracle/routing.mjs +89 -0
- package/lib/oracle/synthesize.mjs +384 -0
- package/lib/oracle/verdicts.mjs +51 -0
- package/lib/orchestration/worker.mjs +88 -27
- package/lib/orchestration-policy.mjs +50 -0
- package/lib/parity.mjs +96 -2
- package/lib/persona-sections.mjs +66 -0
- package/lib/playwright-demo.mjs +252 -0
- package/lib/project-init-shared.mjs +4 -1
- package/lib/prompt-composer.js +9 -4
- package/lib/prompt-validation-contract.mjs +24 -0
- package/lib/provider-capabilities.js +57 -11
- package/lib/providers/contract/adapters/confluence/index.mjs +181 -0
- package/lib/providers/contract/adapters/git/index.mjs +115 -0
- package/lib/providers/contract/adapters/github/index.mjs +166 -0
- package/lib/providers/contract/adapters/jira/index.mjs +187 -0
- package/lib/providers/contract/adapters/slack/index.mjs +175 -0
- package/lib/providers/contract/contract-tests.mjs +57 -0
- package/lib/providers/contract/errors.mjs +48 -0
- package/lib/providers/contract/interface.mjs +50 -0
- package/lib/providers/contract/registry.mjs +102 -0
- package/lib/providers/copilot-auth.mjs +297 -0
- package/lib/providers/credential-bootstrap.mjs +178 -0
- package/lib/providers/credential-catalog.mjs +46 -0
- package/lib/providers/credential-sources.mjs +63 -0
- package/lib/providers/creds.mjs +5 -2
- package/lib/providers/op-run.mjs +59 -0
- package/lib/providers/secret-resolver.mjs +159 -0
- package/lib/publish-template.mjs +163 -0
- package/lib/publish-tooling.mjs +119 -0
- package/lib/publish.mjs +305 -0
- package/lib/registry/cli.mjs +82 -0
- package/lib/registry/consolidation.mjs +147 -0
- package/lib/registry/generate-docs.mjs +75 -0
- package/lib/registry/skill-verification.mjs +57 -0
- package/lib/registry/surface-map.mjs +76 -0
- package/lib/registry/validate.mjs +135 -0
- package/lib/resources/budget.mjs +82 -0
- package/lib/resources/process-budget.mjs +45 -0
- package/lib/rules-delivery.mjs +9 -2
- package/lib/rules-read.mjs +26 -0
- package/lib/runtime-env.mjs +23 -0
- package/lib/runtime-pressure.mjs +51 -7
- package/lib/schema-infer.mjs +13 -2
- package/lib/server/chat-loop.mjs +622 -0
- package/lib/server/demo-preview.mjs +63 -0
- package/lib/server/index.mjs +229 -8
- package/lib/server/langfuse-login.mjs +3 -3
- package/lib/service-manager.mjs +61 -9
- package/lib/session-store.mjs +1 -1
- package/lib/setup.mjs +102 -2
- package/lib/specialists/prompt-schema.mjs +19 -10
- package/lib/specialists/roster.mjs +43 -0
- package/lib/specialists/scaffold.mjs +56 -0
- package/lib/storage/backend.mjs +6 -6
- package/lib/storage/embeddings-local.mjs +6 -3
- package/lib/storage/file-lock.mjs +18 -11
- package/lib/storage/hybrid-query.mjs +7 -4
- package/lib/storage/state-source.mjs +5 -5
- package/lib/storage/sync.mjs +2 -3
- package/lib/telemetry/rule-calls.mjs +52 -0
- package/lib/template-registry.mjs +2 -2
- package/lib/templates/visual-requirements.mjs +27 -51
- package/lib/test-corpus-inventory.mjs +313 -0
- package/lib/uninstall/uninstall.mjs +19 -7
- package/lib/update.mjs +12 -0
- package/lib/upgrade.mjs +14 -0
- package/lib/wireframe.mjs +20 -14
- package/lib/worker/run.mjs +17 -4
- package/lib/worker/trace.mjs +11 -3
- package/package.json +29 -13
- package/personas/construct.md +13 -13
- package/platforms/claude/settings.template.json +36 -0
- package/rules/common/patterns.md +1 -1
- package/rules/common/release-gates.md +4 -3
- package/scripts/sync-specialists.mjs +187 -60
- package/skills/devops/data-engineering.md +1 -1
- package/skills/docs/adr-workflow.md +1 -0
- package/skills/docs/backlog-proposal-workflow.md +1 -0
- package/skills/docs/codebase-research-workflow.md +40 -0
- package/skills/docs/customer-profile-workflow.md +1 -0
- package/skills/docs/document-ingest-workflow.md +1 -0
- package/skills/docs/evidence-ingest-workflow.md +1 -0
- package/skills/docs/init-docs.md +2 -2
- package/skills/docs/init-project.md +8 -3
- package/skills/docs/prd-workflow.md +24 -1
- package/skills/docs/prfaq-workflow.md +1 -0
- package/skills/docs/product-intelligence-workflow.md +1 -0
- package/skills/docs/product-signal-workflow.md +1 -0
- package/skills/docs/research-workflow.md +54 -37
- package/skills/docs/runbook-workflow.md +1 -0
- package/skills/docs/strategy-workflow.md +1 -0
- package/skills/docs/user-research-workflow.md +40 -0
- package/skills/operating/orchestration-reference.md +1 -1
- package/skills/roles/architect.md +5 -0
- package/skills/roles/operator.docs.md +4 -0
- package/skills/routing.md +4 -2
- package/specialists/artifact-manifest.json +480 -0
- package/specialists/artifact-manifest.schema.json +53 -0
- package/specialists/audit-enrichments.json +454 -0
- package/specialists/contracts.json +37 -25
- package/specialists/prompts/_shared/validation-contract.md +26 -0
- package/specialists/prompts/cx-accessibility.md +21 -2
- package/specialists/prompts/cx-ai-engineer.md +24 -1
- package/specialists/prompts/cx-architect.md +4 -1
- package/specialists/prompts/cx-business-strategist.md +23 -2
- package/specialists/prompts/cx-data-analyst.md +22 -1
- package/specialists/prompts/cx-data-engineer.md +23 -2
- package/specialists/prompts/cx-debugger.md +21 -2
- package/specialists/prompts/cx-designer.md +26 -3
- package/specialists/prompts/cx-devil-advocate.md +19 -2
- package/specialists/prompts/cx-docs-keeper.md +28 -1
- package/specialists/prompts/cx-engineer.md +22 -1
- package/specialists/prompts/cx-evaluator.md +18 -1
- package/specialists/prompts/cx-explorer.md +21 -2
- package/specialists/prompts/cx-legal-compliance.md +21 -2
- package/specialists/prompts/cx-operations.md +21 -2
- package/specialists/prompts/cx-oracle.md +94 -0
- package/specialists/prompts/cx-orchestrator.md +20 -1
- package/specialists/prompts/cx-platform-engineer.md +25 -2
- package/specialists/prompts/cx-product-manager.md +32 -1
- package/specialists/prompts/cx-qa.md +24 -2
- package/specialists/prompts/cx-rd-lead.md +23 -2
- package/specialists/prompts/cx-release-manager.md +23 -2
- package/specialists/prompts/cx-researcher.md +22 -2
- package/specialists/prompts/cx-reviewer.md +18 -1
- package/specialists/prompts/cx-security.md +22 -2
- package/specialists/prompts/cx-sre.md +30 -3
- package/specialists/prompts/cx-test-automation.md +7 -1
- package/specialists/prompts/cx-trace-reviewer.md +22 -3
- package/specialists/prompts/cx-ux-researcher.md +21 -2
- package/specialists/registry.json +52 -173
- package/specialists/tone-profiles.json +42 -0
- package/templates/demos/playwright/demo-recording.config.mjs +47 -0
- package/templates/demos/recordings/agentic-platforms-prd.json +19 -0
- package/templates/demos/scripts/agentic-platforms-prd.json +44 -0
- package/templates/demos/specs/_helpers/scroll-artifact.ts +89 -0
- package/templates/demos/tapes/agentic-platforms-prd.tape +49 -0
- package/templates/demos/tapes/resource-guard-rails.tape +49 -0
- package/templates/demos/vhs/construct-cockpit.json +24 -0
- package/templates/distribution/construct-brand.typ +446 -0
- package/templates/distribution/construct-decision.typ +38 -0
- package/templates/distribution/construct-deck.html +95 -0
- package/templates/distribution/construct-pdf.typ +38 -0
- package/templates/distribution/construct-prd.typ +38 -0
- package/templates/distribution/construct-research.typ +38 -0
- package/templates/distribution/construct-web.html +92 -0
- package/templates/distribution/fonts/Geist-Bold.ttf +0 -0
- package/templates/distribution/fonts/Geist-Medium.ttf +0 -0
- package/templates/distribution/fonts/Geist-Regular.ttf +0 -0
- package/templates/distribution/fonts/Geist-SemiBold.ttf +0 -0
- package/templates/distribution/fonts/GeistMono-Medium.ttf +0 -0
- package/templates/distribution/fonts/GeistMono-Regular.ttf +0 -0
- package/templates/distribution/fonts/GeistMono-SemiBold.ttf +0 -0
- package/templates/distribution/fonts/IBMPlexMono-Regular.otf +0 -0
- package/templates/distribution/fonts/JetBrainsMono-Medium.ttf +0 -0
- package/templates/distribution/fonts/JetBrainsMono-Regular.ttf +0 -0
- package/templates/distribution/fonts/JetBrainsMono-SemiBold.ttf +0 -0
- package/templates/distribution/fonts/PlusJakartaSans-Bold.ttf +0 -0
- package/templates/distribution/fonts/PlusJakartaSans-Medium.ttf +0 -0
- package/templates/distribution/fonts/PlusJakartaSans-Regular.ttf +0 -0
- package/templates/distribution/fonts/PlusJakartaSans-SemiBold.ttf +0 -0
- package/templates/distribution/fonts/README.md +36 -0
- package/templates/distribution/fonts/SpaceGrotesk-Variable.ttf +0 -0
- package/templates/distribution/fonts/handwritten/Caveat.ttf +0 -0
- package/templates/distribution/fonts/legacy/IBMPlexMono-Regular.otf +0 -0
- package/templates/distribution/fonts/legacy/Inter-Medium.otf +0 -0
- package/templates/distribution/fonts/legacy/Inter-Regular.otf +0 -0
- package/templates/distribution/fonts/legacy/Inter-SemiBold.otf +0 -0
- package/templates/distribution/fonts/legacy/InterDisplay-SemiBold.otf +0 -0
- package/templates/distribution/fonts/legacy/SourceSerif4-Regular.otf +0 -0
- package/templates/distribution/fonts/legacy/SourceSerif4-Semibold.otf +0 -0
- package/templates/distribution/icons/activity.svg +15 -0
- package/templates/distribution/icons/alert-triangle.svg +17 -0
- package/templates/distribution/icons/book-open.svg +16 -0
- package/templates/distribution/icons/bot.svg +20 -0
- package/templates/distribution/icons/brain.svg +22 -0
- package/templates/distribution/icons/circle-check.svg +16 -0
- package/templates/distribution/icons/clipboard-check.svg +17 -0
- package/templates/distribution/icons/cpu.svg +28 -0
- package/templates/distribution/icons/database.svg +17 -0
- package/templates/distribution/icons/eye.svg +16 -0
- package/templates/distribution/icons/file-text.svg +19 -0
- package/templates/distribution/icons/gauge.svg +16 -0
- package/templates/distribution/icons/git-branch.svg +17 -0
- package/templates/distribution/icons/key.svg +17 -0
- package/templates/distribution/icons/layers.svg +17 -0
- package/templates/distribution/icons/list-checks.svg +19 -0
- package/templates/distribution/icons/lock.svg +16 -0
- package/templates/distribution/icons/message-square.svg +15 -0
- package/templates/distribution/icons/network.svg +19 -0
- package/templates/distribution/icons/route.svg +17 -0
- package/templates/distribution/icons/scale.svg +19 -0
- package/templates/distribution/icons/search.svg +16 -0
- package/templates/distribution/icons/send.svg +16 -0
- package/templates/distribution/icons/server.svg +18 -0
- package/templates/distribution/icons/shield-check.svg +16 -0
- package/templates/distribution/icons/users.svg +18 -0
- package/templates/distribution/icons/webhook.svg +17 -0
- package/templates/distribution/icons/workflow.svg +17 -0
- package/templates/distribution/icons/wrench.svg +15 -0
- package/templates/distribution/run.mjs +18 -1
- package/templates/docs/adr.md +7 -0
- package/templates/docs/construct_guide.md +10 -10
- package/templates/docs/customer-profile.md +4 -0
- package/templates/docs/one-pager.md +4 -0
- package/templates/docs/postmortem.md +31 -0
- package/templates/docs/prd-platform.md +19 -0
- package/templates/docs/prd.md +15 -0
- package/templates/docs/prfaq.md +7 -0
- package/templates/docs/qa-strategy.md +103 -0
- package/templates/docs/research-brief.md +8 -0
- package/templates/docs/rfc-platform.md +12 -0
- package/templates/docs/test-plan.md +7 -0
- package/lib/ingest/chunker.mjs +0 -94
- package/lib/ingest/pipeline.mjs +0 -53
- package/lib/ingest/store.mjs +0 -82
- package/lib/mode-commands.mjs +0 -122
- package/lib/policy/unified-gates.mjs +0 -96
- package/lib/profiles/validate-custom.mjs +0 -114
- package/lib/services/telemetry-backend.mjs +0 -177
- package/lib/storage/fusion.mjs +0 -95
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/oracle/routing.mjs — deterministic gap → specialist routing table.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors cx-oracle.md routing without LLM involvement. Issue raising,
|
|
5
|
+
* approve execution, and routing artifact generation consume these mappings.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const GAP_ROUTES = {
|
|
9
|
+
'parity-drift': { primary: 'cx-platform-engineer', secondary: 'cx-docs-keeper' },
|
|
10
|
+
'contract-violations': { primary: 'cx-reviewer', secondary: 'cx-engineer' },
|
|
11
|
+
'doctor-escalation': { primary: 'cx-sre', secondary: 'cx-operations' },
|
|
12
|
+
'outcomes-degradation': { primary: 'cx-trace-reviewer', secondary: null },
|
|
13
|
+
'census-stale': { primary: 'cx-architect', secondary: 'cx-docs-keeper' },
|
|
14
|
+
'alignment-regression': { primary: 'cx-architect', secondary: 'cx-docs-keeper' },
|
|
15
|
+
'true-skill-orphan': { primary: 'cx-architect', secondary: 'cx-docs-keeper' },
|
|
16
|
+
'observations-empty': { primary: 'cx-explorer', secondary: 'cx-data-engineer' },
|
|
17
|
+
'beads-hygiene': { primary: 'cx-operations', secondary: null },
|
|
18
|
+
'hook-failures': { primary: 'cx-sre', secondary: null },
|
|
19
|
+
'registry-warn': { primary: 'cx-platform-engineer', secondary: null },
|
|
20
|
+
'consistency-drift': { primary: 'cx-platform-engineer', secondary: null },
|
|
21
|
+
'propagation-stale': { primary: 'cx-platform-engineer', secondary: 'cx-docs-keeper' },
|
|
22
|
+
'policy-coverage-gap': { primary: 'cx-architect', secondary: null },
|
|
23
|
+
'workflow-misaligned': { primary: 'cx-product-manager', secondary: null },
|
|
24
|
+
'legal-review-pending': { primary: 'cx-legal-compliance', secondary: null },
|
|
25
|
+
'capability-unvalidated': { primary: 'cx-platform-engineer', secondary: null },
|
|
26
|
+
'dead-code-regression': { primary: 'cx-architect', secondary: null },
|
|
27
|
+
'structure-sprawl': { primary: 'cx-architect', secondary: null },
|
|
28
|
+
'init-duplicate-lanes': { primary: 'cx-architect', secondary: null },
|
|
29
|
+
'outcomes-missing': { primary: 'cx-data-engineer', secondary: null },
|
|
30
|
+
'repo-layout-legacy': { primary: 'cx-platform-engineer', secondary: 'cx-architect' },
|
|
31
|
+
'specialist-audit-drift': { primary: 'cx-architect', secondary: 'cx-docs-keeper' },
|
|
32
|
+
'artifact-gate-bypass': { primary: 'cx-reviewer', secondary: 'cx-docs-keeper' },
|
|
33
|
+
'artifact-reviewer-gap': { primary: 'cx-reviewer', secondary: null },
|
|
34
|
+
'dependency-graph-stale': { primary: 'cx-platform-engineer', secondary: null },
|
|
35
|
+
'matrix-coverage-gap': { primary: 'cx-architect', secondary: 'cx-qa' },
|
|
36
|
+
'impact-untested': { primary: 'cx-qa', secondary: 'cx-engineer' },
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const ACTION_ROUTES = {
|
|
40
|
+
'specialist-review': { primary: 'cx-reviewer', gateType: 'specialist-dispatch' },
|
|
41
|
+
'doctor-followup': { primary: 'cx-sre', gateType: 'operational-followup' },
|
|
42
|
+
'trace-review': { primary: 'cx-trace-reviewer', gateType: 'specialist-dispatch' },
|
|
43
|
+
'outcomes-aggregate': { primary: 'cx-data-engineer', gateType: 'maintenance' },
|
|
44
|
+
'executive-signoff-required': { primary: 'cx-product-manager', gateType: 'executive-gate' },
|
|
45
|
+
'structure-cleanup-proposal': { primary: 'cx-architect', gateType: 'human-approval' },
|
|
46
|
+
'graph-rebuild': { primary: 'cx-platform-engineer', gateType: 'maintenance' },
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* @param {{ id?: string, signal?: string }} gap
|
|
51
|
+
* @returns {{ primary: string, secondary: string|null, gateType?: string }}
|
|
52
|
+
*/
|
|
53
|
+
export function routeGap(gap) {
|
|
54
|
+
const id = gap?.id ?? gap?.signal ?? '';
|
|
55
|
+
const row = GAP_ROUTES[id];
|
|
56
|
+
if (row) return { ...row, gateType: signOffGateForGap(id) };
|
|
57
|
+
return { primary: 'cx-oracle', secondary: null, gateType: 'human-approval' };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* @param {string} actionKind
|
|
62
|
+
*/
|
|
63
|
+
export function routeAction(actionKind) {
|
|
64
|
+
return ACTION_ROUTES[actionKind] ?? { primary: 'cx-oracle', gateType: 'human-approval' };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function signOffGateForGap(gapId) {
|
|
68
|
+
if (gapId === 'legal-review-pending') return 'legal-compliance';
|
|
69
|
+
if (gapId === 'workflow-misaligned') return 'executive-gate';
|
|
70
|
+
if (gapId === 'capability-unvalidated') return 'capability-human-gate';
|
|
71
|
+
if (gapId === 'structure-sprawl' || gapId === 'dead-code-regression') return 'human-approval';
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Sign-off metadata attached to approve actions and high-severity gaps.
|
|
77
|
+
*
|
|
78
|
+
* @param {{ id: string, severity?: string }} gap
|
|
79
|
+
* @param {string} projectDir
|
|
80
|
+
*/
|
|
81
|
+
export function signOffMetadata(gap, projectDir) {
|
|
82
|
+
const route = routeGap(gap);
|
|
83
|
+
return {
|
|
84
|
+
gateType: route.gateType ?? 'human-approval',
|
|
85
|
+
requiredApprover: route.primary,
|
|
86
|
+
artifactPath: `.cx/oracle/verdicts/`,
|
|
87
|
+
projectDir,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/oracle/synthesize.mjs — derive verdicts, gaps, and recommended actions
|
|
3
|
+
* from the Oracle read model. Pure function — no side effects.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { orgGraphToGapHints } from './org-graph.mjs';
|
|
7
|
+
import { routeAction, routeGap, signOffMetadata } from './routing.mjs';
|
|
8
|
+
|
|
9
|
+
const LOW_SUCCESS_RATE = 0.6;
|
|
10
|
+
|
|
11
|
+
function gap(id, severity, signal, detail, extra = {}) {
|
|
12
|
+
return { id, severity, signal, detail, ...extra };
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function action(kind, summary, context = {}) {
|
|
16
|
+
return { kind, summary, ...context };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function isDoctorEscalation(entry) {
|
|
20
|
+
return entry.result === 'escalated'
|
|
21
|
+
|| entry.kind === 'escalation'
|
|
22
|
+
|| entry.kind === 'escalate';
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @param {object} readModel — output of collectReadModel / enrichReadModel
|
|
27
|
+
* @returns {{ verdict: string, gaps: object[], recommendedActions: object[] }}
|
|
28
|
+
*/
|
|
29
|
+
export function synthesizeVerdict(readModel) {
|
|
30
|
+
const gaps = [];
|
|
31
|
+
const recommendedActions = [];
|
|
32
|
+
|
|
33
|
+
if (readModel.parity && !readModel.parity.skipped && !readModel.parity.ok) {
|
|
34
|
+
const drift = (readModel.parity.summary ?? []).filter((s) => s.includes('drift') || s.includes('stale'));
|
|
35
|
+
gaps.push(gap(
|
|
36
|
+
'parity-drift',
|
|
37
|
+
'high',
|
|
38
|
+
'parity',
|
|
39
|
+
drift.length ? drift.join('; ') : 'Project adapter parity check failed',
|
|
40
|
+
));
|
|
41
|
+
recommendedActions.push(action(
|
|
42
|
+
'adapters-sync',
|
|
43
|
+
'Sync project adapters to repair parity drift',
|
|
44
|
+
{ surfaces: (readModel.parity.surfaces ?? []).filter((s) => s.status !== 'ok').map((s) => s.surface) },
|
|
45
|
+
));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const violations = readModel.contractViolations?.recentCount ?? 0;
|
|
49
|
+
if (violations > 0) {
|
|
50
|
+
gaps.push(gap(
|
|
51
|
+
'contract-violations',
|
|
52
|
+
violations >= 5 ? 'high' : 'medium',
|
|
53
|
+
'contract-violations',
|
|
54
|
+
`${violations} contract violation(s) in the last 24h`,
|
|
55
|
+
));
|
|
56
|
+
recommendedActions.push(action(
|
|
57
|
+
'specialist-review',
|
|
58
|
+
'Review recent contract violations and route remediation to owning specialists',
|
|
59
|
+
{ count: violations, signOff: signOffMetadata({ id: 'contract-violations' }, readModel.projectDir) },
|
|
60
|
+
));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const doctorEscalations = (readModel.doctorLog?.recent ?? []).filter(isDoctorEscalation);
|
|
64
|
+
if (doctorEscalations.length > 0) {
|
|
65
|
+
gaps.push(gap(
|
|
66
|
+
'doctor-escalation',
|
|
67
|
+
'high',
|
|
68
|
+
'doctor-log',
|
|
69
|
+
`${doctorEscalations.length} doctor escalation(s) in the last 24h`,
|
|
70
|
+
));
|
|
71
|
+
recommendedActions.push(action(
|
|
72
|
+
'doctor-followup',
|
|
73
|
+
'Inspect doctor escalations and confirm L0 recovery or open a beads issue',
|
|
74
|
+
{ count: doctorEscalations.length, signOff: signOffMetadata({ id: 'doctor-escalation' }, readModel.projectDir) },
|
|
75
|
+
));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const roles = readModel.outcomes?.roles ?? {};
|
|
79
|
+
const degradedRoles = Object.entries(roles)
|
|
80
|
+
.filter(([, stats]) => {
|
|
81
|
+
const rate = stats?.last30?.successRate ?? stats?.successRate;
|
|
82
|
+
return Number.isFinite(rate) && rate < LOW_SUCCESS_RATE && (stats?.last30?.count ?? stats?.count ?? 0) >= 3;
|
|
83
|
+
})
|
|
84
|
+
.map(([role]) => role);
|
|
85
|
+
|
|
86
|
+
if (degradedRoles.length > 0) {
|
|
87
|
+
gaps.push(gap(
|
|
88
|
+
'outcomes-degradation',
|
|
89
|
+
'medium',
|
|
90
|
+
'outcomes',
|
|
91
|
+
`Low 30-day success rate for: ${degradedRoles.join(', ')}`,
|
|
92
|
+
));
|
|
93
|
+
recommendedActions.push(action(
|
|
94
|
+
'trace-review',
|
|
95
|
+
'Dispatch cx-trace-reviewer to inspect degraded specialist outcomes',
|
|
96
|
+
{ roles: degradedRoles, signOff: signOffMetadata({ id: 'outcomes-degradation' }, readModel.projectDir) },
|
|
97
|
+
));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (!readModel.outcomes?.present) {
|
|
101
|
+
gaps.push(gap(
|
|
102
|
+
'outcomes-missing',
|
|
103
|
+
'low',
|
|
104
|
+
'outcomes',
|
|
105
|
+
'No .cx/outcomes/_summary.json — learning tiebreakers are blind',
|
|
106
|
+
));
|
|
107
|
+
recommendedActions.push(action(
|
|
108
|
+
'outcomes-aggregate',
|
|
109
|
+
'Run outcomes aggregation to populate _summary.json',
|
|
110
|
+
));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const census = readModel.alignmentCensus;
|
|
114
|
+
if (census?.present === false) {
|
|
115
|
+
gaps.push(gap(
|
|
116
|
+
'census-stale',
|
|
117
|
+
'low',
|
|
118
|
+
'alignment-census',
|
|
119
|
+
'No audit-artifacts/alignment-census.json — alignment scorecard may be stale',
|
|
120
|
+
));
|
|
121
|
+
recommendedActions.push(action(
|
|
122
|
+
'census-run',
|
|
123
|
+
'Run alignment census to refresh scorecard inputs',
|
|
124
|
+
));
|
|
125
|
+
} else if (census?.present) {
|
|
126
|
+
if (census.stale) {
|
|
127
|
+
gaps.push(gap(
|
|
128
|
+
'census-stale',
|
|
129
|
+
'medium',
|
|
130
|
+
'alignment-census',
|
|
131
|
+
`Alignment census older than 7 days (${census.generatedAt})`,
|
|
132
|
+
));
|
|
133
|
+
recommendedActions.push(action('census-run', 'Refresh stale alignment census'));
|
|
134
|
+
}
|
|
135
|
+
const regressions = census.audit?.regressions ?? [];
|
|
136
|
+
if (regressions.length > 0) {
|
|
137
|
+
gaps.push(gap(
|
|
138
|
+
'alignment-regression',
|
|
139
|
+
'high',
|
|
140
|
+
'alignment-census',
|
|
141
|
+
`${regressions.length} alignment ratchet regression(s): ${regressions.slice(0, 3).join(', ')}`,
|
|
142
|
+
));
|
|
143
|
+
}
|
|
144
|
+
const findingsCount = census.audit?.findingsCount ?? 0;
|
|
145
|
+
if (findingsCount > 0 && regressions.length === 0) {
|
|
146
|
+
gaps.push(gap(
|
|
147
|
+
'alignment-regression',
|
|
148
|
+
'medium',
|
|
149
|
+
'alignment-census',
|
|
150
|
+
`${findingsCount} open alignment audit finding(s)`,
|
|
151
|
+
));
|
|
152
|
+
}
|
|
153
|
+
const trueOrphans = census.skills?.trueOrphanCount ?? 0;
|
|
154
|
+
if (trueOrphans > 0) {
|
|
155
|
+
gaps.push(gap(
|
|
156
|
+
'true-skill-orphan',
|
|
157
|
+
'medium',
|
|
158
|
+
'alignment-census',
|
|
159
|
+
`${trueOrphans} true skill orphan(s) in census`,
|
|
160
|
+
));
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (readModel.observations?.present && readModel.observations.count === 0) {
|
|
165
|
+
gaps.push(gap(
|
|
166
|
+
'observations-empty',
|
|
167
|
+
'low',
|
|
168
|
+
'observations',
|
|
169
|
+
'Observation store exists but contains no records',
|
|
170
|
+
));
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (readModel.registryValidate?.needsRun) {
|
|
174
|
+
recommendedActions.push(action(
|
|
175
|
+
'registry-validate',
|
|
176
|
+
'Validate capability registry against repo reality',
|
|
177
|
+
{ warnings: readModel.registryValidate.warningCount, errors: readModel.registryValidate.errorCount },
|
|
178
|
+
));
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if ((readModel.registryValidate?.warningCount ?? 0) > 0) {
|
|
182
|
+
gaps.push(gap(
|
|
183
|
+
'registry-warn',
|
|
184
|
+
'medium',
|
|
185
|
+
'registry',
|
|
186
|
+
`${readModel.registryValidate.warningCount} capability registry warning(s)`,
|
|
187
|
+
));
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const hookFailures = readModel.hookFailures?.count ?? 0;
|
|
191
|
+
if (hookFailures > 0) {
|
|
192
|
+
gaps.push(gap(
|
|
193
|
+
'hook-failures',
|
|
194
|
+
'high',
|
|
195
|
+
'doctor-log',
|
|
196
|
+
`${hookFailures} hook failure(s) in the last 24h`,
|
|
197
|
+
));
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const stuck = readModel.beads?.stuckInProgress ?? 0;
|
|
201
|
+
const staleOpen = readModel.beads?.staleOpen ?? 0;
|
|
202
|
+
if (stuck > 0 || staleOpen > 2) {
|
|
203
|
+
gaps.push(gap(
|
|
204
|
+
'beads-hygiene',
|
|
205
|
+
stuck > 0 ? 'high' : 'medium',
|
|
206
|
+
'beads',
|
|
207
|
+
`Beads hygiene: ${stuck} stuck in_progress, ${staleOpen} stale-open`,
|
|
208
|
+
));
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const deadRegressions = readModel.deadCode?.regressions ?? [];
|
|
212
|
+
if (deadRegressions.length > 0) {
|
|
213
|
+
gaps.push(gap(
|
|
214
|
+
'dead-code-regression',
|
|
215
|
+
'high',
|
|
216
|
+
'dead-code',
|
|
217
|
+
`${deadRegressions.length} new dead module(s): ${deadRegressions.slice(0, 3).join(', ')}`,
|
|
218
|
+
));
|
|
219
|
+
recommendedActions.push(action(
|
|
220
|
+
'structure-cleanup-proposal',
|
|
221
|
+
'Propose dead-code cleanup (no auto-delete)',
|
|
222
|
+
{ modules: deadRegressions, signOff: signOffMetadata({ id: 'dead-code-regression' }, readModel.projectDir) },
|
|
223
|
+
));
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const dupLanes = readModel.structure?.duplicateLanes ?? [];
|
|
227
|
+
if (dupLanes.length > 0) {
|
|
228
|
+
gaps.push(gap(
|
|
229
|
+
'structure-sprawl',
|
|
230
|
+
'medium',
|
|
231
|
+
'structure',
|
|
232
|
+
`Parallel doc lanes detected: ${dupLanes.join(', ')}`,
|
|
233
|
+
));
|
|
234
|
+
recommendedActions.push(action(
|
|
235
|
+
'structure-cleanup-proposal',
|
|
236
|
+
'Review duplicate doc lane scaffolding',
|
|
237
|
+
{ lanes: dupLanes, signOff: signOffMetadata({ id: 'structure-sprawl' }, readModel.projectDir) },
|
|
238
|
+
));
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const rootLayout = readModel.alignmentCensus?.rootLayout;
|
|
242
|
+
if (rootLayout && rootLayout.clean === false) {
|
|
243
|
+
const parts = [];
|
|
244
|
+
if (rootLayout.legacyDirs?.length) parts.push(`legacy dirs: ${rootLayout.legacyDirs.join(', ')}`);
|
|
245
|
+
if (rootLayout.phantomPackPaths?.length) parts.push(`phantom pack: ${rootLayout.phantomPackPaths.join(', ')}`);
|
|
246
|
+
if (rootLayout.legacyImports?.length) parts.push(`${rootLayout.legacyImports.length} legacy import(s)`);
|
|
247
|
+
gaps.push(gap(
|
|
248
|
+
'repo-layout-legacy',
|
|
249
|
+
'high',
|
|
250
|
+
'root-layout',
|
|
251
|
+
`Tool-repo layout drift (${parts.join('; ') || `${rootLayout.findingCount} finding(s)`})`,
|
|
252
|
+
));
|
|
253
|
+
recommendedActions.push(action(
|
|
254
|
+
'structure-cleanup-proposal',
|
|
255
|
+
'Remediate legacy root layout per audit phase 03c-root-layout',
|
|
256
|
+
{ rootLayout, signOff: signOffMetadata({ id: 'repo-layout-legacy' }, readModel.projectDir) },
|
|
257
|
+
));
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const artifactGate = readModel.artifactGate ?? {};
|
|
261
|
+
if (artifactGate.specialistAudit?.present && !artifactGate.specialistAudit.pass) {
|
|
262
|
+
gaps.push(gap(
|
|
263
|
+
'specialist-audit-drift',
|
|
264
|
+
'high',
|
|
265
|
+
'artifact-manifest',
|
|
266
|
+
`Specialist audit cross-check failed (${artifactGate.specialistAudit.crossCheckCount} issue(s))`,
|
|
267
|
+
));
|
|
268
|
+
recommendedActions.push(action(
|
|
269
|
+
'registry-validate',
|
|
270
|
+
'Run construct audit specialists and repair manifest/registry drift',
|
|
271
|
+
));
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if ((artifactGate.bypassCount ?? 0) > 0) {
|
|
275
|
+
gaps.push(gap(
|
|
276
|
+
'artifact-gate-bypass',
|
|
277
|
+
'medium',
|
|
278
|
+
'artifact-manifest',
|
|
279
|
+
`${artifactGate.bypassCount} artifact(s) bypass release gate via cx_release_gate frontmatter`,
|
|
280
|
+
));
|
|
281
|
+
recommendedActions.push(action(
|
|
282
|
+
'specialist-review',
|
|
283
|
+
'Review bypassed artifacts and confirm cx_release_gate_reason is durable',
|
|
284
|
+
{ paths: (artifactGate.bypassed ?? []).map((b) => b.path).slice(0, 5) },
|
|
285
|
+
));
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
if ((artifactGate.reviewerGapCount ?? 0) > 0) {
|
|
289
|
+
gaps.push(gap(
|
|
290
|
+
'artifact-reviewer-gap',
|
|
291
|
+
'medium',
|
|
292
|
+
'artifact-manifest',
|
|
293
|
+
`${artifactGate.reviewerGapCount} high-risk artifact(s) missing manifest required reviewers in agent log`,
|
|
294
|
+
));
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const dg = readModel.dependencyGraph;
|
|
298
|
+
if (dg?.present) {
|
|
299
|
+
if (dg.stale) {
|
|
300
|
+
gaps.push(gap(
|
|
301
|
+
'dependency-graph-stale',
|
|
302
|
+
'low',
|
|
303
|
+
'dependency-graph',
|
|
304
|
+
`Dependency matrix stale (${dg.staleReason ?? 'seeds changed'}) — run \`construct matrix build\``,
|
|
305
|
+
));
|
|
306
|
+
recommendedActions.push(action('graph-rebuild', 'Rebuild the dependency matrix to clear staleness'));
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const cov = dg.coverage ?? {};
|
|
310
|
+
const noTest = cov.capabilitiesWithoutTest ?? [];
|
|
311
|
+
const noImpl = cov.capabilitiesWithoutImpl ?? [];
|
|
312
|
+
const uncovered = cov.workflowsUncovered ?? [];
|
|
313
|
+
const coverageMisses = noTest.length + noImpl.length + uncovered.length;
|
|
314
|
+
if (coverageMisses > 0) {
|
|
315
|
+
const parts = [];
|
|
316
|
+
if (noTest.length) parts.push(`${noTest.length} capability(ies) with no validating test`);
|
|
317
|
+
if (noImpl.length) parts.push(`${noImpl.length} with no implementation edge`);
|
|
318
|
+
if (uncovered.length) parts.push(`${uncovered.length} workflow(s) with no capability`);
|
|
319
|
+
gaps.push(gap(
|
|
320
|
+
'matrix-coverage-gap',
|
|
321
|
+
coverageMisses >= 5 ? 'medium' : 'low',
|
|
322
|
+
'dependency-graph',
|
|
323
|
+
`Dependency matrix coverage: ${parts.join('; ')}`,
|
|
324
|
+
));
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if (dg.untested?.length) {
|
|
328
|
+
gaps.push(gap(
|
|
329
|
+
'impact-untested',
|
|
330
|
+
dg.untested.length >= 5 ? 'high' : 'medium',
|
|
331
|
+
'dependency-graph',
|
|
332
|
+
`${dg.untested.length} capability(ies) have implementation changes since last validation: ${dg.untested.slice(0, 5).map((u) => u.capability).join(', ')}`,
|
|
333
|
+
{ signOff: signOffMetadata({ id: 'impact-untested' }, readModel.projectDir) },
|
|
334
|
+
));
|
|
335
|
+
recommendedActions.push(action(
|
|
336
|
+
'specialist-review',
|
|
337
|
+
'Re-run affected tests and refresh lastValidated for capabilities with changed implementation',
|
|
338
|
+
{ capabilities: dg.untested.map((u) => u.capability) },
|
|
339
|
+
));
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
for (const hint of orgGraphToGapHints(readModel.orgGraph ?? {})) {
|
|
344
|
+
if (!gaps.some((g) => g.id === hint.id)) gaps.push(hint);
|
|
345
|
+
if (hint.id === 'workflow-misaligned') {
|
|
346
|
+
recommendedActions.push(action(
|
|
347
|
+
'executive-signoff-required',
|
|
348
|
+
'Workflow alignment requires executive gate review',
|
|
349
|
+
{ signOff: signOffMetadata(hint, readModel.projectDir) },
|
|
350
|
+
));
|
|
351
|
+
}
|
|
352
|
+
if (hint.id === 'legal-review-pending') {
|
|
353
|
+
recommendedActions.push(action(
|
|
354
|
+
'specialist-review',
|
|
355
|
+
'Route legal-compliance intake for sign-off',
|
|
356
|
+
{ signOff: signOffMetadata(hint, readModel.projectDir) },
|
|
357
|
+
));
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
for (const g of gaps) {
|
|
362
|
+
const route = routeGap(g);
|
|
363
|
+
g.remediationRoute = {
|
|
364
|
+
primary: route.primary,
|
|
365
|
+
secondary: route.secondary ?? null,
|
|
366
|
+
gateType: route.gateType ?? null,
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
for (const a of recommendedActions) {
|
|
370
|
+
const route = routeAction(a.kind);
|
|
371
|
+
a.remediationRoute = {
|
|
372
|
+
primary: route.primary,
|
|
373
|
+
gateType: route.gateType ?? null,
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
let verdict = 'healthy';
|
|
378
|
+
if (gaps.some((g) => g.severity === 'high')) verdict = 'degraded';
|
|
379
|
+
else if (gaps.length > 0) verdict = 'attention';
|
|
380
|
+
|
|
381
|
+
return { verdict, gaps, recommendedActions };
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
export { isDoctorEscalation };
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/oracle/verdicts.mjs — durable per-tick verdict history under
|
|
3
|
+
* <project>/.cx/oracle/verdicts/.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import fs from 'node:fs';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
|
|
9
|
+
export function verdictsDir(projectDir) {
|
|
10
|
+
return path.join(projectDir, '.cx', 'oracle', 'verdicts');
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function dateKey(iso = new Date().toISOString()) {
|
|
14
|
+
return iso.slice(0, 10);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Write one tick verdict. Same calendar day overwrites the latest snapshot for
|
|
19
|
+
* that date (last tick wins) while preserving tick history in an array.
|
|
20
|
+
*
|
|
21
|
+
* @param {string} projectDir
|
|
22
|
+
* @param {object} tick — runOracleTick tick payload
|
|
23
|
+
* @param {object} [extra] — beadsRaised, orgGraph, etc.
|
|
24
|
+
*/
|
|
25
|
+
export function writeVerdict(projectDir, tick, extra = {}) {
|
|
26
|
+
const dir = verdictsDir(projectDir);
|
|
27
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
28
|
+
const file = path.join(dir, `${dateKey(tick.at)}.json`);
|
|
29
|
+
let existing = { date: dateKey(tick.at), ticks: [] };
|
|
30
|
+
if (fs.existsSync(file)) {
|
|
31
|
+
try { existing = JSON.parse(fs.readFileSync(file, 'utf8')); } catch { /* reset */ }
|
|
32
|
+
}
|
|
33
|
+
if (!Array.isArray(existing.ticks)) existing.ticks = [];
|
|
34
|
+
existing.ticks.push({ ...tick, ...extra });
|
|
35
|
+
existing.latest = existing.ticks[existing.ticks.length - 1];
|
|
36
|
+
fs.writeFileSync(file, JSON.stringify(existing, null, 2));
|
|
37
|
+
return file;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function readLatestVerdict(projectDir) {
|
|
41
|
+
const dir = verdictsDir(projectDir);
|
|
42
|
+
if (!fs.existsSync(dir)) return null;
|
|
43
|
+
const files = fs.readdirSync(dir).filter((f) => f.endsWith('.json')).sort();
|
|
44
|
+
if (!files.length) return null;
|
|
45
|
+
try {
|
|
46
|
+
const data = JSON.parse(fs.readFileSync(path.join(dir, files[files.length - 1]), 'utf8'));
|
|
47
|
+
return data.latest ?? data;
|
|
48
|
+
} catch {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -23,8 +23,8 @@
|
|
|
23
23
|
|
|
24
24
|
import { readFileSync, existsSync } from 'node:fs';
|
|
25
25
|
import { dirname, join } from 'node:path';
|
|
26
|
-
import { homedir } from 'node:os';
|
|
27
26
|
import { fileURLToPath } from 'node:url';
|
|
27
|
+
import { resolveSecret } from '../providers/secret-resolver.mjs';
|
|
28
28
|
|
|
29
29
|
export const INLINE = 'inline';
|
|
30
30
|
export const PROVIDER = 'provider';
|
|
@@ -52,6 +52,33 @@ function isAnthropic(provider, model) {
|
|
|
52
52
|
return /anthropic|claude/i.test(provider || '') || /claude/i.test(model || '');
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
// Provider family for execution dispatch. The explicit `provider` argument is
|
|
56
|
+
// authoritative — orchestration model ids are OpenRouter-style slugs (for
|
|
57
|
+
// example `openai/gpt-4o-mini` is a model served *through* OpenRouter), so the
|
|
58
|
+
// direct OpenAI endpoint is taken only when the caller names `openai`. Copilot
|
|
59
|
+
// is resolved separately because it authenticates via the device-flow token
|
|
60
|
+
// store rather than an API key; everything unrecognized stays on OpenRouter.
|
|
61
|
+
|
|
62
|
+
function classifyWorkerProvider(provider, model) {
|
|
63
|
+
const p = (provider || '').toLowerCase();
|
|
64
|
+
if (p === 'github-copilot' || /^github-copilot\//.test(model || '')) return 'github-copilot';
|
|
65
|
+
if (p === 'openai') return 'openai';
|
|
66
|
+
if (isAnthropic(provider, model)) return 'anthropic';
|
|
67
|
+
return 'openrouter';
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const WORKER_KEY_VAR = {
|
|
71
|
+
anthropic: 'ANTHROPIC_API_KEY',
|
|
72
|
+
openai: 'OPENAI_API_KEY',
|
|
73
|
+
openrouter: 'OPENROUTER_API_KEY',
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const WORKER_PROVIDER_LABEL = {
|
|
77
|
+
anthropic: 'Anthropic',
|
|
78
|
+
openai: 'OpenAI',
|
|
79
|
+
openrouter: 'OpenRouter',
|
|
80
|
+
};
|
|
81
|
+
|
|
55
82
|
// Extended-thinking shape is model-specific. Opus 4.8 / 4.7 REJECT the legacy
|
|
56
83
|
// `{type:'enabled', budget_tokens}` form with a 400 and require adaptive
|
|
57
84
|
// thinking; Opus 4.6 and Sonnet 4.6 recommend adaptive too. Only genuinely
|
|
@@ -66,23 +93,14 @@ function anthropicThinkingConfig(model) {
|
|
|
66
93
|
: { type: 'enabled', budget_tokens: REASONING_BUDGET_TOKENS };
|
|
67
94
|
}
|
|
68
95
|
|
|
69
|
-
// Key resolution
|
|
70
|
-
// files,
|
|
71
|
-
//
|
|
72
|
-
// hermetic
|
|
73
|
-
// bleeds into a
|
|
96
|
+
// Key resolution delegates to the shared secret resolver so env, the dotenv
|
|
97
|
+
// files, shell rc exports, and 1Password op:// references all behave identically
|
|
98
|
+
// across the worker, the chat engine, and the router. allowAmbient stays the
|
|
99
|
+
// hermetic switch: a caller that injects its own env (embedded callers, tests)
|
|
100
|
+
// suppresses file/rc discovery so a developer's real key never bleeds into a run.
|
|
74
101
|
|
|
75
102
|
function resolveKey(varName, env, allowAmbient) {
|
|
76
|
-
|
|
77
|
-
if (!allowAmbient) return null;
|
|
78
|
-
for (const file of [join(homedir(), '.construct', 'config.env'), join(homedir(), '.env')]) {
|
|
79
|
-
try {
|
|
80
|
-
if (!existsSync(file)) continue;
|
|
81
|
-
const m = readFileSync(file, 'utf8').match(new RegExp(`^${varName}=["']?(.+?)["']?$`, 'm'));
|
|
82
|
-
if (m && m[1]) return m[1].trim();
|
|
83
|
-
} catch { /* unreadable dotenv is not authoritative */ }
|
|
84
|
-
}
|
|
85
|
-
return null;
|
|
103
|
+
return resolveSecret(varName, { env, allowAmbient });
|
|
86
104
|
}
|
|
87
105
|
|
|
88
106
|
// The persona prompt is the specialist's system context. It lives in
|
|
@@ -171,6 +189,42 @@ async function callOpenRouter({ model, apiKey, system, user, fetchImpl, reasonin
|
|
|
171
189
|
return { output: message.content || '', reasoning: reasoning ? extractOpenRouterReasoning(message) : '' };
|
|
172
190
|
}
|
|
173
191
|
|
|
192
|
+
async function callCopilot({ model, system, user, fetchImpl }) {
|
|
193
|
+
const { getCopilotToken, copilotApiHeaders, COPILOT_API_BASE } = await import('../providers/copilot-auth.mjs');
|
|
194
|
+
const token = await getCopilotToken({ fetchImpl });
|
|
195
|
+
const res = await fetchImpl(`${COPILOT_API_BASE}/chat/completions`, {
|
|
196
|
+
method: 'POST',
|
|
197
|
+
headers: { Authorization: `Bearer ${token}`, 'content-type': 'application/json', ...copilotApiHeaders() },
|
|
198
|
+
body: JSON.stringify({
|
|
199
|
+
model: model.replace(/^github-copilot\//, ''),
|
|
200
|
+
messages: [{ role: 'system', content: system }, { role: 'user', content: user }],
|
|
201
|
+
}),
|
|
202
|
+
});
|
|
203
|
+
if (!res.ok) {
|
|
204
|
+
throw providerError('PROVIDER_EXECUTION_FAILED', `GitHub Copilot specialist execution failed (HTTP ${res.status}): ${await bodyExcerpt(res)}`, 'Run `construct creds login copilot`, or set orchestration.workerBackend to "inline".');
|
|
205
|
+
}
|
|
206
|
+
const data = await res.json();
|
|
207
|
+
const message = data.choices?.[0]?.message || {};
|
|
208
|
+
return { output: message.content || '', reasoning: '' };
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
async function callOpenAI({ model, apiKey, system, user, fetchImpl }) {
|
|
212
|
+
const res = await fetchImpl('https://api.openai.com/v1/chat/completions', {
|
|
213
|
+
method: 'POST',
|
|
214
|
+
headers: { Authorization: `Bearer ${apiKey}`, 'content-type': 'application/json' },
|
|
215
|
+
body: JSON.stringify({
|
|
216
|
+
model: model.replace(/^openai\//, ''),
|
|
217
|
+
messages: [{ role: 'system', content: system }, { role: 'user', content: user }],
|
|
218
|
+
}),
|
|
219
|
+
});
|
|
220
|
+
if (!res.ok) {
|
|
221
|
+
throw providerError('PROVIDER_EXECUTION_FAILED', `OpenAI specialist execution failed (HTTP ${res.status}): ${await bodyExcerpt(res)}`, 'Verify the model id and OPENAI_API_KEY, or set orchestration.workerBackend to "inline".');
|
|
222
|
+
}
|
|
223
|
+
const data = await res.json();
|
|
224
|
+
const message = data.choices?.[0]?.message || {};
|
|
225
|
+
return { output: message.content || '', reasoning: '' };
|
|
226
|
+
}
|
|
227
|
+
|
|
174
228
|
/**
|
|
175
229
|
* Execute one specialist task via the configured provider/model.
|
|
176
230
|
*
|
|
@@ -190,26 +244,33 @@ export async function runTaskViaProvider({ task, run, model, provider = null, en
|
|
|
190
244
|
if (!model) throw providerError('PROVIDER_MODEL_UNRESOLVED', 'Provider worker backend selected but no model resolved.', 'Configure the model tier registry so a model resolves, or set orchestration.workerBackend to "inline".');
|
|
191
245
|
if (typeof fetchImpl !== 'function') throw providerError('PROVIDER_NO_FETCH', 'No fetch implementation available for provider execution.', 'Run on a runtime with global fetch (Node 18+) or inject fetchImpl.');
|
|
192
246
|
|
|
193
|
-
const
|
|
194
|
-
const keyVar = anthropic ? 'ANTHROPIC_API_KEY' : 'OPENROUTER_API_KEY';
|
|
195
|
-
const apiKey = resolveKey(keyVar, env, env === process.env);
|
|
196
|
-
if (!apiKey) {
|
|
197
|
-
throw providerError('PROVIDER_KEY_MISSING', `No API key for ${anthropic ? 'Anthropic' : 'OpenRouter'} specialist execution.`, `Set ${keyVar}, or set orchestration.workerBackend to "inline".`);
|
|
198
|
-
}
|
|
199
|
-
|
|
247
|
+
const family = classifyWorkerProvider(provider, model);
|
|
200
248
|
const system = loadPersona(task?.role);
|
|
201
249
|
const user = buildUserPrompt({ task, run });
|
|
202
250
|
const wantsReasoning = chainOfThought !== 'hidden';
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
251
|
+
|
|
252
|
+
let output;
|
|
253
|
+
let reasoning;
|
|
254
|
+
if (family === 'github-copilot') {
|
|
255
|
+
({ output, reasoning } = await callCopilot({ model, system, user, fetchImpl }));
|
|
256
|
+
} else {
|
|
257
|
+
const keyVar = WORKER_KEY_VAR[family];
|
|
258
|
+
const apiKey = resolveKey(keyVar, env, env === process.env);
|
|
259
|
+
if (!apiKey) {
|
|
260
|
+
throw providerError('PROVIDER_KEY_MISSING', `No API key for ${WORKER_PROVIDER_LABEL[family]} specialist execution.`, `Set ${keyVar} (a value or an op:// reference), or set orchestration.workerBackend to "inline".`);
|
|
261
|
+
}
|
|
262
|
+
const call = family === 'anthropic' ? callAnthropic
|
|
263
|
+
: family === 'openai' ? callOpenAI
|
|
264
|
+
: callOpenRouter;
|
|
265
|
+
({ output, reasoning } = await call({ model, apiKey, system, user, fetchImpl, reasoning: wantsReasoning }));
|
|
266
|
+
}
|
|
206
267
|
|
|
207
268
|
const text = output || '';
|
|
208
269
|
return {
|
|
209
270
|
output: text,
|
|
210
271
|
reasoning: wantsReasoning ? (reasoning || '') : '',
|
|
211
272
|
model,
|
|
212
|
-
provider:
|
|
273
|
+
provider: family,
|
|
213
274
|
characters: text.length,
|
|
214
275
|
};
|
|
215
276
|
}
|