@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,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/oracle/execute.mjs — post-approve action executors for Oracle pending queue.
|
|
3
|
+
*
|
|
4
|
+
* Runs maintenance and L1 dispatch after human approval. Never performs git
|
|
5
|
+
* push/commit or destructive deletes.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import fs from 'node:fs';
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
import { homedir } from 'node:os';
|
|
11
|
+
|
|
12
|
+
import { syncProjectAdapters } from '../adapters-sync.mjs';
|
|
13
|
+
import { isConstructPackageRepo } from '../host-disposition.mjs';
|
|
14
|
+
import { aggregateOutcomes } from '../outcomes/aggregate.mjs';
|
|
15
|
+
import { recordAndMaybeInvoke } from '../roles/gateway.mjs';
|
|
16
|
+
import { validateCapabilityRegistry } from '../registry/validate.mjs';
|
|
17
|
+
import { writeRoutingArtifact } from './dispatch.mjs';
|
|
18
|
+
import { routeAction } from './routing.mjs';
|
|
19
|
+
|
|
20
|
+
function rolePendingPath(homeDir = homedir()) {
|
|
21
|
+
const root = process.env.CONSTRUCT_ROLES_ROOT || path.join(homeDir, '.cx');
|
|
22
|
+
return path.join(root, 'role-pending.jsonl');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function appendRolePending(entry, homeDir = homedir()) {
|
|
26
|
+
const file = rolePendingPath(homeDir);
|
|
27
|
+
const dir = path.dirname(file);
|
|
28
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
29
|
+
fs.appendFileSync(file, JSON.stringify(entry) + '\n', 'utf8');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async function dispatchSpecialist({ action, projectDir, homeDir, personaId, eventType }) {
|
|
33
|
+
const route = routeAction(action.kind);
|
|
34
|
+
const artifactPath = writeRoutingArtifact({
|
|
35
|
+
projectDir,
|
|
36
|
+
tickId: action.id,
|
|
37
|
+
synthesis: { verdict: 'approved', gaps: [], recommendedActions: [action] },
|
|
38
|
+
readModel: null,
|
|
39
|
+
route,
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const prevRolesRoot = process.env.CONSTRUCT_ROLES_ROOT;
|
|
43
|
+
process.env.CONSTRUCT_ROLES_ROOT = path.join(homeDir, '.cx');
|
|
44
|
+
|
|
45
|
+
const event = {
|
|
46
|
+
project: projectDir,
|
|
47
|
+
cwd: projectDir,
|
|
48
|
+
summary: `[oracle] ${action.kind}: ${action.summary}`,
|
|
49
|
+
context: {
|
|
50
|
+
oracleActionId: action.id,
|
|
51
|
+
kind: action.kind,
|
|
52
|
+
artifactPath,
|
|
53
|
+
gateType: route.gateType,
|
|
54
|
+
requiredApprover: route.primary,
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
let gatewayResult = null;
|
|
59
|
+
try {
|
|
60
|
+
gatewayResult = await recordAndMaybeInvoke(eventType, event);
|
|
61
|
+
} catch {
|
|
62
|
+
gatewayResult = { recorded: false, escalated: false, reason: 'gateway-error' };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (!gatewayResult?.escalated) {
|
|
66
|
+
appendRolePending({
|
|
67
|
+
ts: Date.now(),
|
|
68
|
+
personaId: personaId.replace(/^cx-/, ''),
|
|
69
|
+
cxId: personaId,
|
|
70
|
+
bdIssueId: gatewayResult?.bdIssueId ?? null,
|
|
71
|
+
fingerprint: `oracle-${action.id}`,
|
|
72
|
+
eventType,
|
|
73
|
+
summary: action.summary,
|
|
74
|
+
source: 'oracle-execute',
|
|
75
|
+
artifactPath,
|
|
76
|
+
}, homeDir);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (prevRolesRoot === undefined) delete process.env.CONSTRUCT_ROLES_ROOT;
|
|
80
|
+
else process.env.CONSTRUCT_ROLES_ROOT = prevRolesRoot;
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
ok: true,
|
|
84
|
+
artifactPath,
|
|
85
|
+
gateway: gatewayResult,
|
|
86
|
+
personaId,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Execute an approved Oracle pending action.
|
|
92
|
+
*
|
|
93
|
+
* @param {object} action — pending row from pending.jsonl
|
|
94
|
+
* @param {object} opts
|
|
95
|
+
*/
|
|
96
|
+
export async function executeApprovedAction(action, { rootDir, projectDir, homeDir, dryRun = false } = {}) {
|
|
97
|
+
if (dryRun) return { ok: true, dryRun: true, kind: action.kind };
|
|
98
|
+
|
|
99
|
+
const kind = action.kind;
|
|
100
|
+
switch (kind) {
|
|
101
|
+
case 'outcomes-aggregate': {
|
|
102
|
+
aggregateOutcomes(projectDir);
|
|
103
|
+
return { ok: true, kind, result: 'aggregated' };
|
|
104
|
+
}
|
|
105
|
+
case 'registry-validate': {
|
|
106
|
+
const report = validateCapabilityRegistry({ rootDir });
|
|
107
|
+
return { ok: report.valid, kind, errors: report.errors?.length ?? 0, warnings: report.warnings?.length ?? 0 };
|
|
108
|
+
}
|
|
109
|
+
case 'adapters-sync': {
|
|
110
|
+
if (!isConstructPackageRepo(projectDir)) {
|
|
111
|
+
return { ok: true, kind, skipped: true, reason: 'not-tool-repo' };
|
|
112
|
+
}
|
|
113
|
+
const result = syncProjectAdapters({ projectRoot: projectDir, packageRoot: rootDir, log: () => {} });
|
|
114
|
+
return { ok: !!result.synced, kind, hosts: result.hosts ?? [] };
|
|
115
|
+
}
|
|
116
|
+
case 'census-run': {
|
|
117
|
+
const script = path.join(rootDir, 'scripts', 'alignment', 'census.mjs');
|
|
118
|
+
if (!fs.existsSync(script)) return { ok: false, kind, error: 'census script missing' };
|
|
119
|
+
const { spawnSync } = await import('node:child_process');
|
|
120
|
+
const result = spawnSync(process.execPath, [script], { cwd: rootDir, encoding: 'utf8' });
|
|
121
|
+
return { ok: result.status === 0, kind, exitCode: result.status };
|
|
122
|
+
}
|
|
123
|
+
case 'specialist-review':
|
|
124
|
+
return dispatchSpecialist({
|
|
125
|
+
action, projectDir, homeDir, personaId: 'cx-reviewer', eventType: 'contract.violation',
|
|
126
|
+
});
|
|
127
|
+
case 'trace-review':
|
|
128
|
+
return dispatchSpecialist({
|
|
129
|
+
action, projectDir, homeDir, personaId: 'cx-trace-reviewer', eventType: 'outcomes.degraded',
|
|
130
|
+
});
|
|
131
|
+
case 'doctor-followup':
|
|
132
|
+
return dispatchSpecialist({
|
|
133
|
+
action, projectDir, homeDir, personaId: 'cx-sre', eventType: 'service.down',
|
|
134
|
+
});
|
|
135
|
+
case 'executive-signoff-required':
|
|
136
|
+
case 'structure-cleanup-proposal':
|
|
137
|
+
return dispatchSpecialist({
|
|
138
|
+
action, projectDir, homeDir, personaId: routeAction(kind).primary, eventType: 'oracle.approval',
|
|
139
|
+
});
|
|
140
|
+
default:
|
|
141
|
+
return { ok: false, kind, error: 'no-executor' };
|
|
142
|
+
}
|
|
143
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/oracle/gaps.mjs — human and machine-readable gap surfaces for Oracle.
|
|
3
|
+
*
|
|
4
|
+
* Groups latest verdict gaps into verdict-only (hygiene/meta) vs actionable
|
|
5
|
+
* auto-raise candidates. Does not file tracker beads.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { readLatestVerdict } from './verdicts.mjs';
|
|
9
|
+
import { isVerdictOnlyGap, autoRaiseEnabledForGap } from './policy.mjs';
|
|
10
|
+
|
|
11
|
+
const HYGIENE_HINTS = {
|
|
12
|
+
'beads-hygiene': 'Run `construct beads drift` to inspect stuck/stale beads.',
|
|
13
|
+
'workflow-misaligned': 'Run `construct workflow init` or `construct init` to align workflow state.',
|
|
14
|
+
'propagation-stale': 'Run `construct sync` to refresh platform propagation.',
|
|
15
|
+
'census-stale': 'Run `construct census` to refresh the fleet census.',
|
|
16
|
+
'outcomes-missing': 'Run `construct outcomes aggregate` when outcome data is available.',
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @param {string} projectDir
|
|
21
|
+
* @param {object} [opts]
|
|
22
|
+
* @param {NodeJS.ProcessEnv} [opts.env]
|
|
23
|
+
* @returns {{ verdict: string|null, gaps: object[], verdictOnly: object[], actionable: object[] }}
|
|
24
|
+
*/
|
|
25
|
+
export function collectOracleGaps(projectDir, { env = process.env } = {}) {
|
|
26
|
+
const latest = readLatestVerdict(projectDir);
|
|
27
|
+
const gaps = Array.isArray(latest?.gaps) ? latest.gaps : [];
|
|
28
|
+
const verdictOnly = gaps.filter((g) => isVerdictOnlyGap(g));
|
|
29
|
+
const actionable = gaps.filter((g) => autoRaiseEnabledForGap(g, env));
|
|
30
|
+
return {
|
|
31
|
+
verdict: latest?.verdict ?? null,
|
|
32
|
+
gaps,
|
|
33
|
+
verdictOnly,
|
|
34
|
+
actionable,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @param {ReturnType<typeof collectOracleGaps>} data
|
|
40
|
+
* @returns {string}
|
|
41
|
+
*/
|
|
42
|
+
export function formatOracleGapsReport(data) {
|
|
43
|
+
const lines = [];
|
|
44
|
+
lines.push(`Oracle gaps · verdict: ${data.verdict ?? 'unknown'}`);
|
|
45
|
+
lines.push('');
|
|
46
|
+
if (!data.gaps.length) {
|
|
47
|
+
lines.push('No gaps in latest verdict.');
|
|
48
|
+
return lines.join('\n') + '\n';
|
|
49
|
+
}
|
|
50
|
+
if (data.verdictOnly.length) {
|
|
51
|
+
lines.push('Verdict-only (not filed to bd ready):');
|
|
52
|
+
for (const g of data.verdictOnly) {
|
|
53
|
+
lines.push(` [${g.severity}] ${g.id}: ${g.detail}`);
|
|
54
|
+
const hint = HYGIENE_HINTS[g.id];
|
|
55
|
+
if (hint) lines.push(` → ${hint}`);
|
|
56
|
+
}
|
|
57
|
+
lines.push('');
|
|
58
|
+
}
|
|
59
|
+
if (data.actionable.length) {
|
|
60
|
+
lines.push('Actionable (eligible for auto-raise when high):');
|
|
61
|
+
for (const g of data.actionable) {
|
|
62
|
+
lines.push(` [${g.severity}] ${g.id}: ${g.detail}`);
|
|
63
|
+
}
|
|
64
|
+
lines.push('');
|
|
65
|
+
}
|
|
66
|
+
const other = data.gaps.filter((g) => !isVerdictOnlyGap(g) && !autoRaiseEnabledForGap(g));
|
|
67
|
+
if (other.length) {
|
|
68
|
+
lines.push('Other gaps (below auto-raise threshold or disabled):');
|
|
69
|
+
for (const g of other) {
|
|
70
|
+
lines.push(` [${g.severity}] ${g.id}: ${g.detail}`);
|
|
71
|
+
}
|
|
72
|
+
lines.push('');
|
|
73
|
+
}
|
|
74
|
+
lines.push('Inspect: `construct oracle review` · Drift: `construct beads drift`');
|
|
75
|
+
return lines.join('\n') + '\n';
|
|
76
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/oracle/index.mjs — Oracle meta-controller daemon.
|
|
3
|
+
*
|
|
4
|
+
* Periodic tick over the read model with bounded-auto execution. Built on
|
|
5
|
+
* lib/daemons/contract.mjs safeguards. Disabled when CONSTRUCT_ORACLE=off.
|
|
6
|
+
* Heartbeat: ~/.cx/runtime/oracle/heartbeat.json
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import fs from 'node:fs';
|
|
10
|
+
import path from 'node:path';
|
|
11
|
+
import { homedir } from 'node:os';
|
|
12
|
+
|
|
13
|
+
import { createDaemon } from '../daemons/contract.mjs';
|
|
14
|
+
import { memoryCapMbFor } from '../resources/process-budget.mjs';
|
|
15
|
+
import { runOracleTick, defaultRootDir } from './actions.mjs';
|
|
16
|
+
|
|
17
|
+
export const KILLSWITCH_ENV = 'CONSTRUCT_ORACLE';
|
|
18
|
+
|
|
19
|
+
function runtimeDir(homeDir = homedir()) {
|
|
20
|
+
return path.join(homeDir, '.cx', 'runtime', 'oracle');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function heartbeatPath(homeDir = homedir()) {
|
|
24
|
+
return path.join(runtimeDir(homeDir), 'heartbeat.json');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function lastTickPath(homeDir = homedir()) {
|
|
28
|
+
return path.join(runtimeDir(homeDir), 'last-tick.json');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function writeLastTick(tick, homeDir) {
|
|
32
|
+
const dir = runtimeDir(homeDir);
|
|
33
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
34
|
+
fs.writeFileSync(lastTickPath(homeDir), JSON.stringify(tick, null, 2));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function readLastTick(homeDir = homedir()) {
|
|
38
|
+
const file = lastTickPath(homeDir);
|
|
39
|
+
if (!fs.existsSync(file)) return null;
|
|
40
|
+
try {
|
|
41
|
+
return JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
42
|
+
} catch {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Build a DaemonRunner for the Oracle meta-controller.
|
|
49
|
+
*/
|
|
50
|
+
export function buildOracleDaemon({
|
|
51
|
+
rootDir = defaultRootDir(),
|
|
52
|
+
projectDir = process.cwd(),
|
|
53
|
+
homeDir = homedir(),
|
|
54
|
+
intervalMs = 5 * 60_000,
|
|
55
|
+
dryRun = false,
|
|
56
|
+
} = {}) {
|
|
57
|
+
return createDaemon({
|
|
58
|
+
name: 'oracle',
|
|
59
|
+
intervalMs,
|
|
60
|
+
killswitchEnv: KILLSWITCH_ENV,
|
|
61
|
+
heartbeatPath: heartbeatPath(homeDir),
|
|
62
|
+
lockPath: path.join(runtimeDir(homeDir), 'oracle.lock'),
|
|
63
|
+
maxRuntimeMs: 24 * 60 * 60 * 1000,
|
|
64
|
+
maxIdleTicks: 3,
|
|
65
|
+
memoryCapMb: memoryCapMbFor('oracle', projectDir),
|
|
66
|
+
async tick() {
|
|
67
|
+
const result = await runOracleTick({ rootDir, projectDir, homeDir, dryRun });
|
|
68
|
+
writeLastTick(result.tick, homeDir);
|
|
69
|
+
const didWork = result.tick.executed.length > 0
|
|
70
|
+
|| result.tick.queued.length > 0
|
|
71
|
+
|| (result.tick.beadsRaised?.length ?? 0) > 0
|
|
72
|
+
|| result.verdict !== 'healthy';
|
|
73
|
+
return { didWork };
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Run the Oracle daemon until idle shutdown, killswitch, or max runtime.
|
|
80
|
+
*/
|
|
81
|
+
export async function runOracleDaemon(opts = {}) {
|
|
82
|
+
const daemon = buildOracleDaemon(opts);
|
|
83
|
+
return daemon.run();
|
|
84
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/oracle/issues.mjs — idempotent beads issue raising from Oracle gaps.
|
|
3
|
+
*
|
|
4
|
+
* Persistent gapId fingerprints and open-bead lookup prevent duplicate tracker
|
|
5
|
+
* spam. Verdict-only hygiene gaps never reach bd create — see policy.mjs.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import fs from 'node:fs';
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
|
|
11
|
+
import { runBd } from '../beads-client.mjs';
|
|
12
|
+
import { autoRaiseEnabledForGap } from './policy.mjs';
|
|
13
|
+
import { routeGap } from './routing.mjs';
|
|
14
|
+
|
|
15
|
+
const RAISED_FILE = 'raised-issues.jsonl';
|
|
16
|
+
const MAX_AUTO_RAISE_PER_TICK = 5;
|
|
17
|
+
const OPEN_STATUSES = new Set(['open', 'in_progress']);
|
|
18
|
+
|
|
19
|
+
function raisedPath(projectDir) {
|
|
20
|
+
return path.join(projectDir, '.cx', 'oracle', RAISED_FILE);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function gapFingerprint(gap) {
|
|
24
|
+
return String(gap?.id ?? 'unknown');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function oracleBeadTitlePrefix(gapId) {
|
|
28
|
+
return `[oracle/${gapId}]`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function readRaised(projectDir) {
|
|
32
|
+
const file = raisedPath(projectDir);
|
|
33
|
+
if (!fs.existsSync(file)) return [];
|
|
34
|
+
return fs.readFileSync(file, 'utf8').split('\n').filter(Boolean).map((line) => {
|
|
35
|
+
try { return JSON.parse(line); } catch { return null; }
|
|
36
|
+
}).filter(Boolean);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function appendRaised(projectDir, record) {
|
|
40
|
+
const dir = path.join(projectDir, '.cx', 'oracle');
|
|
41
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
42
|
+
fs.appendFileSync(raisedPath(projectDir), JSON.stringify(record) + '\n', 'utf8');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function parseBdList(output) {
|
|
46
|
+
try {
|
|
47
|
+
const parsed = JSON.parse(String(output ?? ''));
|
|
48
|
+
if (Array.isArray(parsed)) return parsed;
|
|
49
|
+
if (Array.isArray(parsed?.issues)) return parsed.issues;
|
|
50
|
+
} catch { /* empty */ }
|
|
51
|
+
return [];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* @param {string} projectDir
|
|
56
|
+
* @param {string} gapId
|
|
57
|
+
* @returns {Promise<{ id: string, title: string }|null>}
|
|
58
|
+
*/
|
|
59
|
+
export async function findOpenOracleBeadForGap(projectDir, gapId) {
|
|
60
|
+
const prefix = oracleBeadTitlePrefix(gapId);
|
|
61
|
+
const matches = [];
|
|
62
|
+
for (const status of OPEN_STATUSES) {
|
|
63
|
+
const result = await runBd(['list', '--json', `--status=${status}`], {
|
|
64
|
+
actor: 'oracle',
|
|
65
|
+
silent: true,
|
|
66
|
+
timeoutSeconds: 15,
|
|
67
|
+
commandTimeoutSeconds: 30,
|
|
68
|
+
});
|
|
69
|
+
if (!result.success) continue;
|
|
70
|
+
for (const bead of parseBdList(result.output)) {
|
|
71
|
+
const title = String(bead?.title ?? '');
|
|
72
|
+
if (title.startsWith(prefix)) {
|
|
73
|
+
matches.push({ id: bead.id, title });
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (!matches.length) return null;
|
|
78
|
+
return matches[0];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function alreadyRaised(projectDir, fingerprint) {
|
|
82
|
+
return readRaised(projectDir).some((r) => r.fingerprint === fingerprint && r.beadId);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* @param {object} opts
|
|
87
|
+
* @param {string} opts.projectDir
|
|
88
|
+
* @param {{ id: string, severity: string, detail: string, signal?: string }} opts.gap
|
|
89
|
+
* @param {boolean} [opts.dryRun]
|
|
90
|
+
* @returns {Promise<{ ok: boolean, skipped?: boolean, reason?: string, beadId?: string|null, fingerprint?: string }>}
|
|
91
|
+
*/
|
|
92
|
+
export async function raiseIssueForGap({ projectDir, gap, dryRun = false }) {
|
|
93
|
+
if (!autoRaiseEnabledForGap(gap)) {
|
|
94
|
+
return { ok: true, skipped: true, reason: 'verdict-only', fingerprint: gapFingerprint(gap) };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const fingerprint = gapFingerprint(gap);
|
|
98
|
+
if (alreadyRaised(projectDir, fingerprint)) {
|
|
99
|
+
return { ok: true, skipped: true, reason: 'already-raised', fingerprint };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const openBead = await findOpenOracleBeadForGap(projectDir, gap.id);
|
|
103
|
+
if (openBead) {
|
|
104
|
+
return { ok: true, skipped: true, reason: 'open-bead-exists', beadId: openBead.id, fingerprint };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const route = routeGap(gap);
|
|
108
|
+
const title = `${oracleBeadTitlePrefix(gap.id)} ${String(gap.detail).slice(0, 100)}`;
|
|
109
|
+
const body = JSON.stringify({
|
|
110
|
+
gapId: gap.id,
|
|
111
|
+
severity: gap.severity,
|
|
112
|
+
signal: gap.signal ?? null,
|
|
113
|
+
detail: gap.detail,
|
|
114
|
+
route: route.primary,
|
|
115
|
+
fingerprint,
|
|
116
|
+
raisedAt: new Date().toISOString(),
|
|
117
|
+
}, null, 2);
|
|
118
|
+
|
|
119
|
+
if (dryRun) {
|
|
120
|
+
return { ok: true, dryRun: true, fingerprint, beadId: null };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const labels = ['oracle', 'construct-hygiene', gap.severity].join(',');
|
|
124
|
+
const result = await runBd(
|
|
125
|
+
['create', title, '-t', 'task', '-l', labels, '-d', body],
|
|
126
|
+
{ actor: 'oracle', silent: true, timeoutSeconds: 15, commandTimeoutSeconds: 30 },
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
if (!result.success) {
|
|
130
|
+
return { ok: false, reason: result.error || 'bd-create-failed', fingerprint };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const match = String(result.output || '').match(/Created issue:\s*([\w-]+)/);
|
|
134
|
+
const beadId = match ? match[1] : null;
|
|
135
|
+
appendRaised(projectDir, {
|
|
136
|
+
fingerprint,
|
|
137
|
+
gapId: gap.id,
|
|
138
|
+
beadId,
|
|
139
|
+
raisedAt: new Date().toISOString(),
|
|
140
|
+
title,
|
|
141
|
+
});
|
|
142
|
+
return { ok: true, beadId, fingerprint };
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Auto-raise beads for actionable high-severity gaps with idempotency and rate limit.
|
|
147
|
+
*
|
|
148
|
+
* @param {object} opts
|
|
149
|
+
* @param {string} opts.projectDir
|
|
150
|
+
* @param {object[]} opts.gaps
|
|
151
|
+
* @param {boolean} [opts.dryRun]
|
|
152
|
+
*/
|
|
153
|
+
export async function raiseIssuesForGaps({ projectDir, gaps, dryRun = false }) {
|
|
154
|
+
const actionable = gaps.filter((g) => autoRaiseEnabledForGap(g));
|
|
155
|
+
const raised = [];
|
|
156
|
+
for (const gap of actionable.slice(0, MAX_AUTO_RAISE_PER_TICK)) {
|
|
157
|
+
const result = await raiseIssueForGap({ projectDir, gap, dryRun });
|
|
158
|
+
raised.push({ gapId: gap.id, ...result });
|
|
159
|
+
}
|
|
160
|
+
for (const gap of gaps.filter((g) => g.severity === 'high' && !autoRaiseEnabledForGap(g))) {
|
|
161
|
+
raised.push({ gapId: gap.id, ok: true, skipped: true, reason: 'verdict-only', fingerprint: gapFingerprint(gap) });
|
|
162
|
+
}
|
|
163
|
+
return raised;
|
|
164
|
+
}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/oracle/org-graph.mjs — org-in-a-box propagation graph collector.
|
|
3
|
+
*
|
|
4
|
+
* Loads policy inventory, workflow gates, intake sign-offs, and capability
|
|
5
|
+
* human gates. Produces findings for synthesizeVerdict — no side effects.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import fs from 'node:fs';
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
import { fileURLToPath } from 'node:url';
|
|
11
|
+
|
|
12
|
+
import { alignmentFindings } from '../workflow-state.mjs';
|
|
13
|
+
import { loadCapabilityRegistry } from '../registry/validate.mjs';
|
|
14
|
+
import { isConstructPackageRepo } from '../host-disposition.mjs';
|
|
15
|
+
|
|
16
|
+
const MODULE_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
17
|
+
const PACKAGE_ROOT = path.resolve(MODULE_DIR, '../..');
|
|
18
|
+
|
|
19
|
+
function readJsonSafe(filePath) {
|
|
20
|
+
try {
|
|
21
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
22
|
+
} catch {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function collectPolicyInventory(rootDir) {
|
|
28
|
+
const file = path.join(rootDir, 'specialists', 'policy-inventory.json');
|
|
29
|
+
const data = readJsonSafe(file);
|
|
30
|
+
if (!data) return { present: false, count: 0, policies: [] };
|
|
31
|
+
const policies = data.policies ?? [];
|
|
32
|
+
const orphans = [];
|
|
33
|
+
for (const p of policies) {
|
|
34
|
+
if (p.source && p.source !== 'built-in' && !p.source.startsWith('specialists/')) {
|
|
35
|
+
const rulePath = path.join(rootDir, p.source);
|
|
36
|
+
if (!fs.existsSync(rulePath)) orphans.push(p.id);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return { present: true, count: policies.length, orphanPolicyIds: orphans };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function collectWorkflow(projectDir) {
|
|
43
|
+
const file = path.join(projectDir, '.cx', 'workflow.json');
|
|
44
|
+
const workflow = readJsonSafe(file);
|
|
45
|
+
if (!workflow) return { present: false, findings: alignmentFindings(null) };
|
|
46
|
+
return { present: true, workflow, findings: alignmentFindings(workflow) };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function collectIntakeSignoffs(projectDir) {
|
|
50
|
+
const pendingDir = path.join(projectDir, '.cx', 'intake', 'pending');
|
|
51
|
+
if (!fs.existsSync(pendingDir)) return { pendingCount: 0, legalPending: [], approvalPending: [] };
|
|
52
|
+
const legalPending = [];
|
|
53
|
+
const approvalPending = [];
|
|
54
|
+
for (const name of fs.readdirSync(pendingDir)) {
|
|
55
|
+
if (!name.endsWith('.json')) continue;
|
|
56
|
+
const packet = readJsonSafe(path.join(pendingDir, name));
|
|
57
|
+
if (!packet) continue;
|
|
58
|
+
const triage = packet.triage ?? {};
|
|
59
|
+
if (triage.intakeType === 'legal-compliance' || triage.primaryOwner === 'legal-compliance') {
|
|
60
|
+
legalPending.push({ id: packet.id ?? name.replace(/\.json$/, ''), triage });
|
|
61
|
+
}
|
|
62
|
+
if (triage.requiresApproval === true) {
|
|
63
|
+
approvalPending.push({ id: packet.id ?? name.replace(/\.json$/, ''), triage });
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return { pendingCount: legalPending.length + approvalPending.length, legalPending, approvalPending };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function collectCapabilityGates(rootDir) {
|
|
70
|
+
const raw = loadCapabilityRegistry({ rootDir });
|
|
71
|
+
const unvalidated = [];
|
|
72
|
+
for (const cap of raw.capabilities ?? []) {
|
|
73
|
+
if (cap.criticality !== 'P0' && cap.criticality !== 'P1') continue;
|
|
74
|
+
if (!cap.lastValidated) {
|
|
75
|
+
unvalidated.push({ id: cap.id, criticality: cap.criticality, humanGate: cap.humanGate ?? null });
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return { count: (raw.capabilities ?? []).length, unvalidatedP0P1: unvalidated };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function collectPropagationSignals({ rootDir, projectDir, parity, registryValidate }) {
|
|
82
|
+
const registryPath = path.join(rootDir, 'specialists', 'registry.json');
|
|
83
|
+
let registryMtime = null;
|
|
84
|
+
try {
|
|
85
|
+
registryMtime = fs.statSync(registryPath).mtimeMs;
|
|
86
|
+
} catch { /* ignore */ }
|
|
87
|
+
|
|
88
|
+
const staleSurfaces = (parity?.surfaces ?? []).filter((s) => s.stale || s.status === 'drift');
|
|
89
|
+
const registryFailed = registryValidate && !registryValidate.valid;
|
|
90
|
+
const needsPropagation = (!parity?.ok && !parity?.skipped) || staleSurfaces.length > 0 || registryFailed;
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
registryMtime,
|
|
94
|
+
needsPropagation,
|
|
95
|
+
staleSurfaceCount: staleSurfaces.length,
|
|
96
|
+
registryFailed,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* @param {object} opts
|
|
102
|
+
* @param {string} opts.rootDir
|
|
103
|
+
* @param {string} opts.projectDir
|
|
104
|
+
* @param {object} [opts.parity]
|
|
105
|
+
* @param {object} [opts.registryValidate]
|
|
106
|
+
*/
|
|
107
|
+
export function collectOrgGraph({ rootDir = PACKAGE_ROOT, projectDir, parity, registryValidate }) {
|
|
108
|
+
return {
|
|
109
|
+
policyInventory: collectPolicyInventory(rootDir),
|
|
110
|
+
workflow: collectWorkflow(projectDir),
|
|
111
|
+
intakeSignoffs: collectIntakeSignoffs(projectDir),
|
|
112
|
+
capabilityGates: collectCapabilityGates(rootDir),
|
|
113
|
+
propagation: collectPropagationSignals({ rootDir, projectDir, parity, registryValidate }),
|
|
114
|
+
isToolRepo: isConstructPackageRepo(projectDir),
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Convert org graph into supplemental gap descriptors for synthesize.
|
|
120
|
+
*
|
|
121
|
+
* @param {object} orgGraph
|
|
122
|
+
* @returns {object[]}
|
|
123
|
+
*/
|
|
124
|
+
export function orgGraphToGapHints(orgGraph) {
|
|
125
|
+
const hints = [];
|
|
126
|
+
if (orgGraph.propagation?.needsPropagation) {
|
|
127
|
+
hints.push({ id: 'propagation-stale', severity: 'medium', signal: 'org-graph', detail: 'Upstream registry or parity drift — downstream surfaces may be stale' });
|
|
128
|
+
}
|
|
129
|
+
if (orgGraph.policyInventory?.orphanPolicyIds?.length) {
|
|
130
|
+
hints.push({
|
|
131
|
+
id: 'policy-coverage-gap',
|
|
132
|
+
severity: 'medium',
|
|
133
|
+
signal: 'policy-inventory',
|
|
134
|
+
detail: `${orgGraph.policyInventory.orphanPolicyIds.length} policy source(s) missing on disk`,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
for (const f of orgGraph.workflow?.findings ?? []) {
|
|
138
|
+
if (f.severity === 'HIGH') {
|
|
139
|
+
hints.push({
|
|
140
|
+
id: 'workflow-misaligned',
|
|
141
|
+
severity: 'high',
|
|
142
|
+
signal: 'workflow',
|
|
143
|
+
detail: f.issue,
|
|
144
|
+
gateType: 'executive-gate',
|
|
145
|
+
task: f.task ?? null,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
if (orgGraph.intakeSignoffs?.legalPending?.length) {
|
|
150
|
+
hints.push({
|
|
151
|
+
id: 'legal-review-pending',
|
|
152
|
+
severity: 'high',
|
|
153
|
+
signal: 'intake',
|
|
154
|
+
detail: `${orgGraph.intakeSignoffs.legalPending.length} legal-compliance intake packet(s) awaiting sign-off`,
|
|
155
|
+
gateType: 'legal-compliance',
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
const unvalidated = orgGraph.capabilityGates?.unvalidatedP0P1 ?? [];
|
|
159
|
+
const p0Unvalidated = unvalidated.filter((c) => c.criticality === 'P0');
|
|
160
|
+
if (p0Unvalidated.length) {
|
|
161
|
+
hints.push({
|
|
162
|
+
id: 'capability-unvalidated',
|
|
163
|
+
severity: 'medium',
|
|
164
|
+
signal: 'capabilities',
|
|
165
|
+
detail: `P0/P1 capabilities without validation stamp: ${p0Unvalidated.map((c) => c.id).slice(0, 5).join(', ')}`,
|
|
166
|
+
gateType: 'capability-human-gate',
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
return hints;
|
|
170
|
+
}
|