@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,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/oracle/actions.mjs — Oracle tick executor with bounded-auto policy.
|
|
3
|
+
*
|
|
4
|
+
* Auto actions: alignment census spawn, registry validate import, adapters
|
|
5
|
+
* sync (Construct tool repo only). Approve actions queue to
|
|
6
|
+
* <project>/.cx/oracle/pending.jsonl. High-severity gaps auto-raise beads
|
|
7
|
+
* when enabled. Denied actions are skipped.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import fs from 'node:fs';
|
|
11
|
+
import path from 'node:path';
|
|
12
|
+
import { spawnSync } from 'node:child_process';
|
|
13
|
+
import { fileURLToPath } from 'node:url';
|
|
14
|
+
|
|
15
|
+
import { collectReadModel, enrichReadModel } from './read-model.mjs';
|
|
16
|
+
import { synthesizeVerdict } from './synthesize.mjs';
|
|
17
|
+
import { classifyAction, autoRaiseEnabled, autoRaiseEnabledForGap } from './policy.mjs';
|
|
18
|
+
import { isConstructPackageRepo } from '../host-disposition.mjs';
|
|
19
|
+
import { syncProjectAdapters } from '../adapters-sync.mjs';
|
|
20
|
+
import { writeVerdict } from './verdicts.mjs';
|
|
21
|
+
import { raiseIssuesForGaps } from './issues.mjs';
|
|
22
|
+
import { maybeWriteHighSeverityRouting } from './dispatch.mjs';
|
|
23
|
+
import { executeApprovedAction } from './execute.mjs';
|
|
24
|
+
import { signOffMetadata } from './routing.mjs';
|
|
25
|
+
|
|
26
|
+
function pendingPath(projectDir) {
|
|
27
|
+
return path.join(projectDir, '.cx', 'oracle', 'pending.jsonl');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function ensureOracleDir(projectDir) {
|
|
31
|
+
const dir = path.join(projectDir, '.cx', 'oracle');
|
|
32
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
33
|
+
return dir;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function appendPending(projectDir, record) {
|
|
37
|
+
ensureOracleDir(projectDir);
|
|
38
|
+
const line = JSON.stringify({ ...record, queuedAt: new Date().toISOString() }) + '\n';
|
|
39
|
+
fs.appendFileSync(pendingPath(projectDir), line, 'utf8');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function listPending(projectDir) {
|
|
43
|
+
const file = pendingPath(projectDir);
|
|
44
|
+
if (!fs.existsSync(file)) return [];
|
|
45
|
+
return fs.readFileSync(file, 'utf8')
|
|
46
|
+
.split('\n')
|
|
47
|
+
.filter(Boolean)
|
|
48
|
+
.map((line) => { try { return JSON.parse(line); } catch { return null; } })
|
|
49
|
+
.filter(Boolean);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function rewritePending(projectDir, pending) {
|
|
53
|
+
const file = pendingPath(projectDir);
|
|
54
|
+
fs.writeFileSync(file, pending.map((p) => JSON.stringify(p)).join('\n') + (pending.length ? '\n' : ''), 'utf8');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export async function approvePending(projectDir, id, { execute = true, rootDir, homeDir, dryRun = false } = {}) {
|
|
58
|
+
const pending = listPending(projectDir);
|
|
59
|
+
const match = pending.find((p) => p.id === id);
|
|
60
|
+
if (!match) return { ok: false, reason: 'not-found' };
|
|
61
|
+
if (match.status === 'approved' && match.executedAt) {
|
|
62
|
+
return { ok: true, already: true, action: match };
|
|
63
|
+
}
|
|
64
|
+
match.status = 'approved';
|
|
65
|
+
match.approvedAt = new Date().toISOString();
|
|
66
|
+
match.signOff = match.signOff ?? signOffMetadata({ id: match.kind }, projectDir);
|
|
67
|
+
|
|
68
|
+
let executionResult = null;
|
|
69
|
+
if (execute) {
|
|
70
|
+
try {
|
|
71
|
+
executionResult = await executeApprovedAction(match, {
|
|
72
|
+
rootDir: rootDir ?? defaultRootDir(),
|
|
73
|
+
projectDir,
|
|
74
|
+
homeDir,
|
|
75
|
+
dryRun,
|
|
76
|
+
});
|
|
77
|
+
match.executedAt = new Date().toISOString();
|
|
78
|
+
match.executionResult = executionResult;
|
|
79
|
+
if (executionResult?.gateway?.bdIssueId) match.beadId = executionResult.gateway.bdIssueId;
|
|
80
|
+
} catch (err) {
|
|
81
|
+
match.executionResult = { ok: false, error: err?.message || String(err) };
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
rewritePending(projectDir, pending);
|
|
86
|
+
return { ok: true, action: match, executionResult };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async function executeAutoAction(kind, { rootDir, projectDir, dryRun }) {
|
|
90
|
+
if (dryRun) return { kind, dryRun: true, ok: true };
|
|
91
|
+
|
|
92
|
+
switch (kind) {
|
|
93
|
+
case 'census-run': {
|
|
94
|
+
const script = path.join(rootDir, 'scripts', 'alignment', 'census.mjs');
|
|
95
|
+
if (!fs.existsSync(script)) return { kind, ok: false, error: 'census script missing' };
|
|
96
|
+
const result = spawnSync(process.execPath, [script], { cwd: rootDir, encoding: 'utf8' });
|
|
97
|
+
return {
|
|
98
|
+
kind,
|
|
99
|
+
ok: result.status === 0,
|
|
100
|
+
exitCode: result.status,
|
|
101
|
+
stderr: (result.stderr || '').slice(0, 500),
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
case 'registry-validate': {
|
|
105
|
+
const { validateCapabilityRegistry } = await import('../registry/validate.mjs');
|
|
106
|
+
const report = validateCapabilityRegistry({ rootDir });
|
|
107
|
+
return { kind, ok: report.valid, errors: report.errors?.length ?? 0, warnings: report.warnings?.length ?? 0 };
|
|
108
|
+
}
|
|
109
|
+
case 'adapters-sync': {
|
|
110
|
+
if (!isConstructPackageRepo(projectDir)) {
|
|
111
|
+
return { kind, ok: true, skipped: true, reason: 'not-tool-repo' };
|
|
112
|
+
}
|
|
113
|
+
const result = syncProjectAdapters({ projectRoot: projectDir, packageRoot: rootDir, log: () => {} });
|
|
114
|
+
return { kind, ok: !!result.synced, hosts: result.hosts ?? [] };
|
|
115
|
+
}
|
|
116
|
+
default:
|
|
117
|
+
return { kind, ok: false, error: 'unknown-auto-action' };
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Run one Oracle tick: collect signals, synthesize verdict, execute auto
|
|
123
|
+
* actions, queue approve actions, raise beads for high gaps.
|
|
124
|
+
*/
|
|
125
|
+
export async function runOracleTick({ rootDir, projectDir, homeDir, dryRun = false } = {}) {
|
|
126
|
+
let readModel = collectReadModel({ rootDir, projectDir, homeDir });
|
|
127
|
+
readModel = await enrichReadModel(readModel);
|
|
128
|
+
const synthesis = synthesizeVerdict(readModel);
|
|
129
|
+
|
|
130
|
+
const executed = [];
|
|
131
|
+
const queued = [];
|
|
132
|
+
const skipped = [];
|
|
133
|
+
let beadsRaised = [];
|
|
134
|
+
|
|
135
|
+
if (autoRaiseEnabled() && synthesis.gaps.some((g) => autoRaiseEnabledForGap(g))) {
|
|
136
|
+
beadsRaised = await raiseIssuesForGaps({ projectDir, gaps: synthesis.gaps, dryRun });
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
for (const rec of synthesis.recommendedActions) {
|
|
140
|
+
const classification = classifyAction(rec.kind);
|
|
141
|
+
if (classification === 'deny') {
|
|
142
|
+
skipped.push({ ...rec, classification });
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
if (classification === 'auto') {
|
|
146
|
+
const result = await executeAutoAction(rec.kind, { rootDir, projectDir, dryRun });
|
|
147
|
+
executed.push({ ...rec, classification, result });
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
const id = `oracle-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
151
|
+
const pending = {
|
|
152
|
+
id,
|
|
153
|
+
kind: rec.kind,
|
|
154
|
+
summary: rec.summary,
|
|
155
|
+
classification,
|
|
156
|
+
status: 'pending',
|
|
157
|
+
signOff: rec.signOff ?? signOffMetadata({ id: rec.kind }, projectDir),
|
|
158
|
+
context: { ...rec },
|
|
159
|
+
};
|
|
160
|
+
if (!dryRun) appendPending(projectDir, pending);
|
|
161
|
+
queued.push(pending);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const tick = {
|
|
165
|
+
at: new Date().toISOString(),
|
|
166
|
+
dryRun,
|
|
167
|
+
verdict: synthesis.verdict,
|
|
168
|
+
gaps: synthesis.gaps,
|
|
169
|
+
executed,
|
|
170
|
+
queued,
|
|
171
|
+
skipped,
|
|
172
|
+
beadsRaised,
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
if (!dryRun) {
|
|
176
|
+
writeVerdict(projectDir, tick, { beadsRaised, orgGraph: readModel.orgGraph });
|
|
177
|
+
maybeWriteHighSeverityRouting({ projectDir, tick, synthesis, readModel });
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return { readModel, ...synthesis, tick };
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const MODULE_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
184
|
+
|
|
185
|
+
export function defaultRootDir() {
|
|
186
|
+
return path.resolve(MODULE_DIR, '../..');
|
|
187
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/oracle/artifact-gate.mjs — Oracle read-model signals for typed artifact gates.
|
|
3
|
+
*
|
|
4
|
+
* Surfaces manifest audit drift, release-gate bypass markers, and high-risk
|
|
5
|
+
* artifacts missing required reviewers in the agent log.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import fs from 'node:fs';
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
|
|
11
|
+
import { auditSpecialists } from '../audit-specialists.mjs';
|
|
12
|
+
import { inferArtifactTypeFromPath, isArtifactGatePath } from '../artifact-type-from-path.mjs';
|
|
13
|
+
import {
|
|
14
|
+
missingRequiredReviewers,
|
|
15
|
+
parseReleaseGateFrontmatter,
|
|
16
|
+
} from '../artifact-reviewers.mjs';
|
|
17
|
+
import { isConstructPackageRepo } from '../host-disposition.mjs';
|
|
18
|
+
|
|
19
|
+
function walkMarkdown(dir, out = []) {
|
|
20
|
+
if (!fs.existsSync(dir)) return out;
|
|
21
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
22
|
+
const full = path.join(dir, entry.name);
|
|
23
|
+
if (entry.isDirectory()) walkMarkdown(full, out);
|
|
24
|
+
else if (entry.name.endsWith('.md')) out.push(full);
|
|
25
|
+
}
|
|
26
|
+
return out;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function collectBypassArtifacts(projectDir) {
|
|
30
|
+
const roots = [
|
|
31
|
+
path.join(projectDir, 'docs'),
|
|
32
|
+
path.join(projectDir, '.cx', 'research'),
|
|
33
|
+
];
|
|
34
|
+
const bypassed = [];
|
|
35
|
+
for (const root of roots) {
|
|
36
|
+
for (const file of walkMarkdown(root)) {
|
|
37
|
+
const rel = path.relative(projectDir, file).replace(/\\/g, '/');
|
|
38
|
+
if (!isArtifactGatePath(rel)) continue;
|
|
39
|
+
const { bypass, reason } = parseReleaseGateFrontmatter(file);
|
|
40
|
+
if (!bypass) continue;
|
|
41
|
+
bypassed.push({
|
|
42
|
+
path: rel,
|
|
43
|
+
type: inferArtifactTypeFromPath(file, { rootDir: projectDir }),
|
|
44
|
+
reason: reason || null,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return bypassed;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function collectReviewerGaps(projectDir) {
|
|
52
|
+
const roots = [
|
|
53
|
+
path.join(projectDir, 'docs', 'prd'),
|
|
54
|
+
path.join(projectDir, 'docs', 'adr'),
|
|
55
|
+
path.join(projectDir, 'docs', 'rfc'),
|
|
56
|
+
];
|
|
57
|
+
const gaps = [];
|
|
58
|
+
for (const root of roots) {
|
|
59
|
+
if (!fs.existsSync(root)) continue;
|
|
60
|
+
for (const file of walkMarkdown(root)) {
|
|
61
|
+
const rel = path.relative(projectDir, file).replace(/\\/g, '/');
|
|
62
|
+
const type = inferArtifactTypeFromPath(file, { rootDir: projectDir });
|
|
63
|
+
if (!type) continue;
|
|
64
|
+
const missing = missingRequiredReviewers({ filePath: file, cwd: projectDir });
|
|
65
|
+
if (missing.length > 0) {
|
|
66
|
+
gaps.push({ path: rel, type, missingReviewers: missing });
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return gaps.slice(0, 20);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function collectArtifactGateSignals({ rootDir, projectDir }) {
|
|
74
|
+
const bypassed = collectBypassArtifacts(projectDir);
|
|
75
|
+
const reviewerGaps = collectReviewerGaps(projectDir);
|
|
76
|
+
|
|
77
|
+
let specialistAudit = { present: false, pass: true, crossCheckCount: 0 };
|
|
78
|
+
if (isConstructPackageRepo(projectDir)) {
|
|
79
|
+
try {
|
|
80
|
+
const audit = auditSpecialists({ rootDir, silent: true });
|
|
81
|
+
specialistAudit = {
|
|
82
|
+
present: true,
|
|
83
|
+
pass: audit.pass,
|
|
84
|
+
crossCheckCount: audit.crossCheckIssues?.length ?? 0,
|
|
85
|
+
crossCheckIssues: (audit.crossCheckIssues ?? []).slice(0, 5),
|
|
86
|
+
};
|
|
87
|
+
} catch {
|
|
88
|
+
specialistAudit = { present: true, pass: false, crossCheckCount: -1 };
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
bypassed,
|
|
94
|
+
bypassCount: bypassed.length,
|
|
95
|
+
reviewerGaps,
|
|
96
|
+
reviewerGapCount: reviewerGaps.length,
|
|
97
|
+
specialistAudit,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/oracle/cli.mjs — CLI handlers for `construct oracle <subcommand>`.
|
|
3
|
+
*
|
|
4
|
+
* Subcommands: start, status, review, pending, approve, gaps, reconcile.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import fs from 'node:fs';
|
|
8
|
+
import path from 'node:path';
|
|
9
|
+
import os from 'node:os';
|
|
10
|
+
import { spawn } from 'node:child_process';
|
|
11
|
+
import { fileURLToPath } from 'node:url';
|
|
12
|
+
|
|
13
|
+
import { readHeartbeat } from '../daemons/contract.mjs';
|
|
14
|
+
import {
|
|
15
|
+
buildOracleDaemon,
|
|
16
|
+
heartbeatPath,
|
|
17
|
+
readLastTick,
|
|
18
|
+
KILLSWITCH_ENV,
|
|
19
|
+
} from './index.mjs';
|
|
20
|
+
import { runOracleTick, listPending, approvePending, defaultRootDir } from './actions.mjs';
|
|
21
|
+
import { collectOracleGaps, formatOracleGapsReport } from './gaps.mjs';
|
|
22
|
+
import { reconcileOracleHygieneBeads } from './reconcile.mjs';
|
|
23
|
+
|
|
24
|
+
const MODULE_FILE = fileURLToPath(import.meta.url);
|
|
25
|
+
|
|
26
|
+
function runtimeDir(homeDir) {
|
|
27
|
+
return path.join(homeDir, '.cx', 'runtime', 'oracle');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function cmdStart(args, { rootDir, projectDir, homeDir }) {
|
|
31
|
+
const hb = heartbeatPath(homeDir);
|
|
32
|
+
const live = readHeartbeat(hb);
|
|
33
|
+
if (live) {
|
|
34
|
+
process.stdout.write(`oracle daemon already running (pid ${live.pid})\n`);
|
|
35
|
+
return 0;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (process.env[KILLSWITCH_ENV] === 'off' || process.env[KILLSWITCH_ENV] === '0') {
|
|
39
|
+
process.stderr.write(`oracle daemon disabled (${KILLSWITCH_ENV}=off)\n`);
|
|
40
|
+
return 1;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const foreground = args.includes('--foreground') || args.includes('-f');
|
|
44
|
+
if (foreground) {
|
|
45
|
+
const daemon = buildOracleDaemon({ rootDir, projectDir, homeDir });
|
|
46
|
+
const result = await daemon.run();
|
|
47
|
+
process.stdout.write(`oracle daemon stopped: ${result.reason}\n`);
|
|
48
|
+
return 0;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const entry = path.join(path.dirname(MODULE_FILE), 'daemon-entry.mjs');
|
|
52
|
+
const logDir = runtimeDir(homeDir);
|
|
53
|
+
fs.mkdirSync(logDir, { recursive: true });
|
|
54
|
+
const logFile = path.join(logDir, 'oracle-daemon.log');
|
|
55
|
+
const fd = fs.openSync(logFile, 'a');
|
|
56
|
+
|
|
57
|
+
const child = spawn(process.execPath, [entry], {
|
|
58
|
+
detached: true,
|
|
59
|
+
stdio: ['ignore', fd, fd],
|
|
60
|
+
env: {
|
|
61
|
+
...process.env,
|
|
62
|
+
CONSTRUCT_ORACLE_ROOT: rootDir,
|
|
63
|
+
CONSTRUCT_ORACLE_PROJECT: projectDir,
|
|
64
|
+
},
|
|
65
|
+
cwd: projectDir,
|
|
66
|
+
});
|
|
67
|
+
child.unref();
|
|
68
|
+
process.stdout.write(`oracle daemon started (pid ${child.pid})\n`);
|
|
69
|
+
return 0;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function cmdStatus(_args, { homeDir }) {
|
|
73
|
+
const hb = readHeartbeat(heartbeatPath(homeDir));
|
|
74
|
+
const last = readLastTick(homeDir);
|
|
75
|
+
const payload = {
|
|
76
|
+
running: !!hb,
|
|
77
|
+
heartbeat: hb,
|
|
78
|
+
lastTick: last,
|
|
79
|
+
killswitch: process.env[KILLSWITCH_ENV] === 'off' || process.env[KILLSWITCH_ENV] === '0',
|
|
80
|
+
};
|
|
81
|
+
process.stdout.write(JSON.stringify(payload, null, 2) + '\n');
|
|
82
|
+
return 0;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async function cmdReview(args, { rootDir, projectDir, homeDir }) {
|
|
86
|
+
const dryRun = args.includes('--dry-run');
|
|
87
|
+
const json = args.includes('--json');
|
|
88
|
+
const result = await runOracleTick({ rootDir, projectDir, homeDir, dryRun });
|
|
89
|
+
if (json) {
|
|
90
|
+
process.stdout.write(JSON.stringify(result, null, 2) + '\n');
|
|
91
|
+
return 0;
|
|
92
|
+
}
|
|
93
|
+
process.stdout.write(`Oracle verdict: ${result.verdict}\n`);
|
|
94
|
+
if (result.gaps.length) {
|
|
95
|
+
process.stdout.write(`Gaps (${result.gaps.length}):\n`);
|
|
96
|
+
for (const g of result.gaps) {
|
|
97
|
+
process.stdout.write(` [${g.severity}] ${g.id}: ${g.detail}\n`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
if (result.tick.executed.length) {
|
|
101
|
+
process.stdout.write(`Auto-executed (${result.tick.executed.length}):\n`);
|
|
102
|
+
for (const e of result.tick.executed) {
|
|
103
|
+
process.stdout.write(` ${e.kind}: ${e.result?.ok ? 'ok' : 'failed'}\n`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (result.tick.queued.length) {
|
|
107
|
+
process.stdout.write(`Queued for approval (${result.tick.queued.length}):\n`);
|
|
108
|
+
for (const q of result.tick.queued) {
|
|
109
|
+
process.stdout.write(` ${q.id} ${q.kind}: ${q.summary}\n`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return 0;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function cmdPending(_args, { projectDir }) {
|
|
116
|
+
const pending = listPending(projectDir).filter((p) => p.status !== 'approved');
|
|
117
|
+
process.stdout.write(JSON.stringify(pending, null, 2) + '\n');
|
|
118
|
+
return 0;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function cmdApprove(args, { projectDir, rootDir, homeDir }) {
|
|
122
|
+
const id = args.find((a) => !a.startsWith('-'));
|
|
123
|
+
if (!id) {
|
|
124
|
+
process.stderr.write('Usage: construct oracle approve <id> [--no-execute]\n');
|
|
125
|
+
return 1;
|
|
126
|
+
}
|
|
127
|
+
const noExecute = args.includes('--no-execute');
|
|
128
|
+
return approvePending(projectDir, id, {
|
|
129
|
+
execute: !noExecute,
|
|
130
|
+
rootDir,
|
|
131
|
+
homeDir,
|
|
132
|
+
}).then((result) => {
|
|
133
|
+
if (!result.ok) {
|
|
134
|
+
process.stderr.write(`approve failed: ${result.reason}\n`);
|
|
135
|
+
return 1;
|
|
136
|
+
}
|
|
137
|
+
process.stdout.write(JSON.stringify(result.action, null, 2) + '\n');
|
|
138
|
+
return 0;
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function cmdGaps(args, { projectDir }) {
|
|
143
|
+
const json = args.includes('--json');
|
|
144
|
+
const data = collectOracleGaps(projectDir);
|
|
145
|
+
if (json) {
|
|
146
|
+
process.stdout.write(JSON.stringify(data, null, 2) + '\n');
|
|
147
|
+
return 0;
|
|
148
|
+
}
|
|
149
|
+
process.stdout.write(formatOracleGapsReport(data));
|
|
150
|
+
return 0;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async function cmdReconcile(args, { projectDir }) {
|
|
154
|
+
const dryRun = args.includes('--dry-run');
|
|
155
|
+
const json = args.includes('--json');
|
|
156
|
+
const result = await reconcileOracleHygieneBeads({ projectDir, dryRun });
|
|
157
|
+
if (json) {
|
|
158
|
+
process.stdout.write(JSON.stringify(result, null, 2) + '\n');
|
|
159
|
+
return 0;
|
|
160
|
+
}
|
|
161
|
+
process.stdout.write(`Oracle reconcile${dryRun ? ' (dry-run)' : ''}: found ${result.found} hygiene beads\n`);
|
|
162
|
+
if (Object.keys(result.kept).length) {
|
|
163
|
+
process.stdout.write(`Keeping newest per gap: ${JSON.stringify(result.kept)}\n`);
|
|
164
|
+
}
|
|
165
|
+
process.stdout.write(`Planned close: ${result.plannedClose.length}\n`);
|
|
166
|
+
if (!dryRun) process.stdout.write(`Closed: ${result.closed.length}\n`);
|
|
167
|
+
if (result.raisedPruned) process.stdout.write(`Pruned raised-issues rows: ${result.raisedPruned}\n`);
|
|
168
|
+
if (result.contractViolations?.superseded) {
|
|
169
|
+
process.stdout.write(`Superseded ${result.contractViolations.recentCount} bare-goal contract violation(s)\n`);
|
|
170
|
+
} else if (result.contractViolations?.wouldSupersede) {
|
|
171
|
+
process.stdout.write(`Would supersede ${result.contractViolations.recentCount} bare-goal contract violation(s)\n`);
|
|
172
|
+
}
|
|
173
|
+
return 0;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Entry point for bin/construct.
|
|
178
|
+
*/
|
|
179
|
+
export async function runOracleCli(args, opts = {}) {
|
|
180
|
+
const sub = args[0];
|
|
181
|
+
const subArgs = args.slice(1);
|
|
182
|
+
const homeDir = opts.homeDir ?? os.homedir();
|
|
183
|
+
const rootDir = opts.rootDir ?? defaultRootDir();
|
|
184
|
+
const projectDir = opts.projectDir ?? process.cwd();
|
|
185
|
+
|
|
186
|
+
switch (sub) {
|
|
187
|
+
case 'start': return cmdStart(subArgs, { rootDir, projectDir, homeDir });
|
|
188
|
+
case 'status': return cmdStatus(subArgs, { homeDir });
|
|
189
|
+
case 'review': return cmdReview(subArgs, { rootDir, projectDir, homeDir });
|
|
190
|
+
case 'pending': return cmdPending(subArgs, { projectDir });
|
|
191
|
+
case 'approve': return cmdApprove(subArgs, { projectDir, rootDir, homeDir });
|
|
192
|
+
case 'gaps': return cmdGaps(subArgs, { projectDir });
|
|
193
|
+
case 'reconcile': return cmdReconcile(subArgs, { projectDir });
|
|
194
|
+
default:
|
|
195
|
+
process.stderr.write(
|
|
196
|
+
'Usage: construct oracle <start|status|review|pending|approve|gaps|reconcile> [--json] [--dry-run] [--foreground] [--no-execute]\n',
|
|
197
|
+
);
|
|
198
|
+
if (sub && sub !== '--help' && sub !== '-h') {
|
|
199
|
+
process.stderr.write(`Unknown subcommand: ${sub}\n`);
|
|
200
|
+
return 1;
|
|
201
|
+
}
|
|
202
|
+
return sub === '--help' || sub === '-h' ? 0 : 1;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/oracle/daemon-entry.mjs — detached entrypoint for `construct oracle start`.
|
|
3
|
+
*
|
|
4
|
+
* Spawns the Oracle daemon in the background. Reads root/project overrides
|
|
5
|
+
* from CONSTRUCT_ORACLE_ROOT and CONSTRUCT_ORACLE_PROJECT.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { runOracleDaemon } from './index.mjs';
|
|
9
|
+
import { defaultRootDir } from './actions.mjs';
|
|
10
|
+
|
|
11
|
+
const rootDir = process.env.CONSTRUCT_ORACLE_ROOT || defaultRootDir();
|
|
12
|
+
const projectDir = process.env.CONSTRUCT_ORACLE_PROJECT || process.cwd();
|
|
13
|
+
|
|
14
|
+
await runOracleDaemon({ rootDir, projectDir });
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/oracle/dispatch.mjs — cx-oracle routing artifacts (deterministic, LLM-free).
|
|
3
|
+
*
|
|
4
|
+
* Writes audit markdown under .cx/oracle/routing/ for approve-gated dispatch.
|
|
5
|
+
* Optional high-severity auto-artifact on tick when gaps are load-bearing.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import fs from 'node:fs';
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
|
|
11
|
+
import { routeGap, routeAction } from './routing.mjs';
|
|
12
|
+
|
|
13
|
+
export function routingDir(projectDir) {
|
|
14
|
+
return path.join(projectDir, '.cx', 'oracle', 'routing');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Build deterministic routing markdown from synthesis packet.
|
|
19
|
+
*/
|
|
20
|
+
export function buildRoutingArtifact({ synthesis, readModel, tickId, route }) {
|
|
21
|
+
const lines = [
|
|
22
|
+
`# ORACLE ROUTING — ${tickId}`,
|
|
23
|
+
'',
|
|
24
|
+
`VERDICT: ${synthesis.verdict ?? 'unknown'}`,
|
|
25
|
+
'',
|
|
26
|
+
'GAPS:',
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
for (const gap of synthesis.gaps ?? []) {
|
|
30
|
+
const r = routeGap(gap);
|
|
31
|
+
lines.push(` [${gap.severity}] ${gap.id} — ${gap.detail}`);
|
|
32
|
+
lines.push(` signal: ${gap.signal ?? 'unknown'}`);
|
|
33
|
+
lines.push(` route: ${r.primary}${r.secondary ? ` (secondary: ${r.secondary})` : ''}`);
|
|
34
|
+
if (r.gateType) lines.push(` gate: ${r.gateType}`);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (!synthesis.gaps?.length) lines.push(' (none)');
|
|
38
|
+
|
|
39
|
+
lines.push('', 'RECOMMENDED ACTIONS:');
|
|
40
|
+
for (const action of synthesis.recommendedActions ?? []) {
|
|
41
|
+
const r = routeAction(action.kind);
|
|
42
|
+
lines.push(` ${action.kind}: ${action.summary}`);
|
|
43
|
+
lines.push(` route: ${r.primary} · gate: ${r.gateType ?? 'none'}`);
|
|
44
|
+
}
|
|
45
|
+
if (!synthesis.recommendedActions?.length) lines.push(' (none)');
|
|
46
|
+
|
|
47
|
+
if (readModel?.collectedAt) {
|
|
48
|
+
lines.push('', `Read model collected: ${readModel.collectedAt}`);
|
|
49
|
+
}
|
|
50
|
+
if (route?.primary) {
|
|
51
|
+
lines.push('', `DISPATCH TARGET: ${route.primary}`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
lines.push('', 'BLOCKED:', ' none (deterministic routing artifact — LLM dispatch is approve-gated)');
|
|
55
|
+
return lines.join('\n');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* @returns {string} artifact path relative to project root
|
|
60
|
+
*/
|
|
61
|
+
export function writeRoutingArtifact({ projectDir, tickId, synthesis, readModel, route }) {
|
|
62
|
+
const dir = routingDir(projectDir);
|
|
63
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
64
|
+
const fileName = `${tickId.replace(/[^a-zA-Z0-9._-]/g, '_')}.md`;
|
|
65
|
+
const file = path.join(dir, fileName);
|
|
66
|
+
const content = buildRoutingArtifact({ synthesis, readModel, tickId, route });
|
|
67
|
+
fs.writeFileSync(file, content, 'utf8');
|
|
68
|
+
return path.relative(projectDir, file);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Write routing artifact for high-severity tick when any HIGH gap exists.
|
|
73
|
+
*/
|
|
74
|
+
export function maybeWriteHighSeverityRouting({ projectDir, tick, synthesis, readModel }) {
|
|
75
|
+
const hasHigh = (synthesis.gaps ?? []).some((g) => g.severity === 'high');
|
|
76
|
+
if (!hasHigh) return null;
|
|
77
|
+
const tickId = tick.at ?? new Date().toISOString();
|
|
78
|
+
const primaryGap = synthesis.gaps.find((g) => g.severity === 'high');
|
|
79
|
+
const route = routeGap(primaryGap);
|
|
80
|
+
return writeRoutingArtifact({ projectDir, tickId, synthesis, readModel, route });
|
|
81
|
+
}
|