@geraldmaron/construct 1.1.1 → 1.2.1
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 -19
- package/bin/construct +487 -87
- 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 +258 -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/artifact-workflow.mjs +252 -0
- package/lib/audit-skills.mjs +4 -0
- package/lib/audit-specialists.mjs +285 -0
- package/lib/audit-trail.mjs +1 -1
- package/lib/auto-docs.mjs +90 -19
- 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 +246 -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 +146 -15
- package/lib/cli-service-inventory.mjs +42 -0
- package/lib/comment-lint.mjs +6 -4
- package/lib/config/intake-policy.mjs +209 -0
- package/lib/config/project-config.mjs +11 -2
- package/lib/config/schema.mjs +86 -6
- package/lib/config/source-targets.mjs +311 -0
- 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 +598 -140
- package/lib/diagram-export.mjs +192 -0
- package/lib/diagram.mjs +13 -11
- package/lib/docs-verify.mjs +25 -7
- package/lib/doctor/index.mjs +3 -1
- package/lib/doctor/project-adapters.mjs +44 -0
- package/lib/doctor/source-checkout.mjs +16 -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/document-export.mjs +384 -38
- package/lib/document-extract.mjs +44 -4
- package/lib/document-ingest.mjs +29 -20
- package/lib/embed/auto-sources.mjs +44 -0
- package/lib/embed/cli.mjs +4 -3
- package/lib/embed/daemon.mjs +18 -46
- package/lib/embed/demand-fetch.mjs +50 -19
- package/lib/embed/inbox.mjs +6 -10
- package/lib/embed/worker.mjs +4 -3
- package/lib/embedded-contract/index.mjs +11 -0
- 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/export-branding.mjs +32 -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/health-check.mjs +90 -76
- package/lib/hooks/artifact-release-gate.mjs +43 -0
- 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 +18 -16
- 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 +50 -119
- 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/constants.mjs +29 -0
- package/lib/intake/daemon.mjs +2 -0
- package/lib/intake/git-queue.mjs +9 -8
- package/lib/intake/intake-config.mjs +52 -105
- package/lib/intake/legacy-paths.mjs +5 -0
- 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 +103 -10
- 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/embedded-contract.mjs +14 -0
- 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 +373 -92
- 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/playwright-demo.mjs +254 -0
- package/lib/project-init-shared.mjs +4 -1
- package/lib/prompt-composer.js +7 -3
- 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 +259 -9
- package/lib/server/rate-limit.mjs +5 -3
- package/lib/server/webhook.mjs +3 -2
- package/lib/service-manager.mjs +60 -8
- package/lib/setup.mjs +70 -7
- 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/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 +28 -14
- package/personas/construct.md +2 -2
- 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 +41 -28
- 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 +7 -2
- 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 +489 -0
- package/specialists/artifact-manifest.schema.json +83 -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-reference.docx +0 -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 +8 -8
- 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/server/index.mjs
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Serves the single-page dashboard from lib/server/static/, provides a JSON
|
|
5
5
|
* status API, SSE live-reload, REST endpoints for registry management,
|
|
6
6
|
* artifact generation/listing, approval queue inspection, snapshot data,
|
|
7
|
-
* token-based auth,
|
|
7
|
+
* token-based auth, legacy claude CLI chat, and owned-loop SSE web chat.
|
|
8
8
|
* Runs on port 4242 (overridable via PORT env var), bound to 127.0.0.1.
|
|
9
9
|
*/
|
|
10
10
|
import { createServer } from 'http';
|
|
@@ -19,15 +19,21 @@ import { buildStatus as buildSharedStatus } from '../status.mjs';
|
|
|
19
19
|
import { generateArtifact, listArtifacts } from '../embed/artifact.mjs';
|
|
20
20
|
import { ApprovalQueue } from '../embed/approval-queue.mjs';
|
|
21
21
|
import { resolveEmbedStatus } from '../embed/cli.mjs';
|
|
22
|
-
import { loadConstructEnv } from '../env
|
|
22
|
+
import { loadConstructEnv, prepareConstructEnv } from '../runtime-env.mjs';
|
|
23
23
|
import {
|
|
24
24
|
isAuthConfigured, isAuthenticated, rejectUnauthorized,
|
|
25
25
|
validateToken, createSession, sessionCookieHeader, clearSessionCookieHeader,
|
|
26
26
|
getDashboardToken, getAuthConfig,
|
|
27
27
|
} from './auth.mjs';
|
|
28
28
|
import { handleChatStream, handleChat, handleChatHistory } from './chat.mjs';
|
|
29
|
+
import {
|
|
30
|
+
handleChatLoopStream, handleChatLoopPermission, handleChatLoopPending, handleChatLoopConfig,
|
|
31
|
+
handleChatLoopCommand, handleChatLoopHistory, handleChatLoopCancel, handleChatModels, handleChatModelSelect,
|
|
32
|
+
} from './chat-loop.mjs';
|
|
29
33
|
import { createWebhookHandler, createSlackCommandHandler } from './webhook.mjs';
|
|
30
34
|
import { onEmbedNotification } from '../embed/notifications.mjs';
|
|
35
|
+
import { artifactTypes, templateMetadata } from '../artifact-manifest.mjs';
|
|
36
|
+
import { resolveDemoPreviewPath, demoPreviewMime } from './demo-preview.mjs';
|
|
31
37
|
|
|
32
38
|
const __dirname = fileURLToPath(new URL('.', import.meta.url));
|
|
33
39
|
const ROOT_DIR = join(__dirname, '..', '..');
|
|
@@ -355,6 +361,9 @@ async function buildStatus() {
|
|
|
355
361
|
...status,
|
|
356
362
|
skills: listSkills(),
|
|
357
363
|
commands: listCommands(),
|
|
364
|
+
artifactManifest: Object.fromEntries(
|
|
365
|
+
artifactTypes({ rootDir: ROOT_DIR }).map((type) => [type, templateMetadata(type, { rootDir: ROOT_DIR })]),
|
|
366
|
+
),
|
|
358
367
|
};
|
|
359
368
|
}
|
|
360
369
|
|
|
@@ -693,8 +702,11 @@ const server = createServer(async (req, res) => {
|
|
|
693
702
|
ensureCsrfCookie(req, res);
|
|
694
703
|
}
|
|
695
704
|
const rateTier = url.pathname === '/api/chat/stream' || url.pathname === '/api/chat'
|
|
705
|
+
|| url.pathname === '/api/chat/loop/stream'
|
|
696
706
|
? 'chat'
|
|
697
|
-
:
|
|
707
|
+
: url.pathname === '/api/chat/loop/command'
|
|
708
|
+
? 'command'
|
|
709
|
+
: (req.method !== 'GET' && req.method !== 'HEAD' ? 'write' : 'read');
|
|
698
710
|
if (checkRateLimit) {
|
|
699
711
|
const limit = checkRateLimit(req, rateTier, { env: process.env });
|
|
700
712
|
if (!limit.allowed) {
|
|
@@ -836,6 +848,141 @@ const server = createServer(async (req, res) => {
|
|
|
836
848
|
return;
|
|
837
849
|
}
|
|
838
850
|
|
|
851
|
+
if (url.pathname === '/api/certification/status' && req.method === 'GET') {
|
|
852
|
+
try {
|
|
853
|
+
const { buildCertificationDashboard } = await import('../certification/dashboard-api.mjs');
|
|
854
|
+
const capabilityId = url.searchParams.get('capabilityId');
|
|
855
|
+
const payload = capabilityId
|
|
856
|
+
? (await import('../certification/status.mjs')).buildCertificationStatus({
|
|
857
|
+
rootDir: process.cwd(),
|
|
858
|
+
capabilityId,
|
|
859
|
+
})
|
|
860
|
+
: buildCertificationDashboard({ rootDir: process.cwd() });
|
|
861
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
862
|
+
res.end(JSON.stringify(payload));
|
|
863
|
+
} catch (err) {
|
|
864
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
865
|
+
res.end(JSON.stringify({ error: err.message }));
|
|
866
|
+
}
|
|
867
|
+
return;
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
if (url.pathname === '/api/oracle/pending' && req.method === 'GET') {
|
|
871
|
+
try {
|
|
872
|
+
const { listPending } = await import('../oracle/actions.mjs');
|
|
873
|
+
const pending = listPending(process.cwd()).filter((p) => p.status !== 'approved');
|
|
874
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
875
|
+
res.end(JSON.stringify({ pending }));
|
|
876
|
+
} catch (err) {
|
|
877
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
878
|
+
res.end(JSON.stringify({ error: err.message }));
|
|
879
|
+
}
|
|
880
|
+
return;
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
if (url.pathname === '/api/improvement/pending' && req.method === 'GET') {
|
|
884
|
+
try {
|
|
885
|
+
const { listPending } = await import('../improvement/surface.mjs');
|
|
886
|
+
const pending = listPending(process.cwd());
|
|
887
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
888
|
+
res.end(JSON.stringify({ pending }));
|
|
889
|
+
} catch (err) {
|
|
890
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
891
|
+
res.end(JSON.stringify({ error: err.message }));
|
|
892
|
+
}
|
|
893
|
+
return;
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
if (url.pathname.startsWith('/api/improvement/') && req.method === 'POST') {
|
|
897
|
+
const action = url.pathname.slice('/api/improvement/'.length);
|
|
898
|
+
let body = '';
|
|
899
|
+
req.on('data', (chunk) => { body += chunk; });
|
|
900
|
+
req.on('end', async () => {
|
|
901
|
+
try {
|
|
902
|
+
const data = JSON.parse(body || '{}');
|
|
903
|
+
const projectDir = process.cwd();
|
|
904
|
+
const { reviewRecord, approveRecord, applyRecord, rollbackRecord } = await import('../improvement/surface.mjs');
|
|
905
|
+
let result;
|
|
906
|
+
if (action === 'review') {
|
|
907
|
+
if (!data.id) throw new Error('Missing id');
|
|
908
|
+
result = reviewRecord(projectDir, data.id);
|
|
909
|
+
} else if (action === 'approve') {
|
|
910
|
+
if (!data.id) throw new Error('Missing id');
|
|
911
|
+
result = approveRecord(projectDir, data.id, { identity: data.identity });
|
|
912
|
+
} else if (action === 'apply') {
|
|
913
|
+
if (!data.id) throw new Error('Missing id');
|
|
914
|
+
result = applyRecord(projectDir, data.id, { monitor: data.monitor });
|
|
915
|
+
} else if (action === 'rollback') {
|
|
916
|
+
if (!data.id) throw new Error('Missing id');
|
|
917
|
+
result = rollbackRecord(projectDir, data.id, { reason: data.reason });
|
|
918
|
+
} else {
|
|
919
|
+
throw new Error(`Unknown improvement action: ${action}`);
|
|
920
|
+
}
|
|
921
|
+
const status = result.ok === false && result.error ? 400 : 200;
|
|
922
|
+
res.writeHead(status, { 'Content-Type': 'application/json' });
|
|
923
|
+
res.end(JSON.stringify(result));
|
|
924
|
+
notifyClients();
|
|
925
|
+
} catch (err) {
|
|
926
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
927
|
+
res.end(JSON.stringify({ error: err.message }));
|
|
928
|
+
}
|
|
929
|
+
});
|
|
930
|
+
return;
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
if (url.pathname.startsWith('/api/improvement/') && req.method === 'GET') {
|
|
934
|
+
const id = decodeURIComponent(url.pathname.slice('/api/improvement/'.length));
|
|
935
|
+
if (id && id !== 'pending') {
|
|
936
|
+
try {
|
|
937
|
+
const { loadRecord } = await import('../improvement/store.mjs');
|
|
938
|
+
const record = loadRecord(process.cwd(), id);
|
|
939
|
+
if (!record) {
|
|
940
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
941
|
+
res.end(JSON.stringify({ error: 'not-found' }));
|
|
942
|
+
return;
|
|
943
|
+
}
|
|
944
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
945
|
+
res.end(JSON.stringify(record));
|
|
946
|
+
} catch (err) {
|
|
947
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
948
|
+
res.end(JSON.stringify({ error: err.message }));
|
|
949
|
+
}
|
|
950
|
+
return;
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
if (url.pathname === '/api/oracle' && req.method === 'GET') {
|
|
955
|
+
try {
|
|
956
|
+
const { homedir: osHome } = await import('node:os');
|
|
957
|
+
const { readHeartbeat } = await import('../daemons/contract.mjs');
|
|
958
|
+
const { heartbeatPath, readLastTick } = await import('../oracle/index.mjs');
|
|
959
|
+
const { collectReadModel, enrichReadModel } = await import('../oracle/read-model.mjs');
|
|
960
|
+
const { synthesizeVerdict } = await import('../oracle/synthesize.mjs');
|
|
961
|
+
const home = osHome();
|
|
962
|
+
const projectDir = process.cwd();
|
|
963
|
+
const readModel = collectReadModel({ rootDir: ROOT_DIR, projectDir, homeDir: home });
|
|
964
|
+
const enriched = await enrichReadModel(readModel);
|
|
965
|
+
const synthesis = synthesizeVerdict(enriched);
|
|
966
|
+
const hb = readHeartbeat(heartbeatPath(home));
|
|
967
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
968
|
+
res.end(JSON.stringify({
|
|
969
|
+
running: !!hb,
|
|
970
|
+
heartbeat: hb,
|
|
971
|
+
lastTick: readLastTick(home),
|
|
972
|
+
verdict: synthesis.verdict,
|
|
973
|
+
gaps: synthesis.gaps,
|
|
974
|
+
recommendedActions: synthesis.recommendedActions,
|
|
975
|
+
orgGraph: enriched.orgGraph,
|
|
976
|
+
openBeads: enriched.beads?.openCount ?? null,
|
|
977
|
+
lastPropagationCheck: enriched.orgGraph?.propagation ?? null,
|
|
978
|
+
}));
|
|
979
|
+
} catch (err) {
|
|
980
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
981
|
+
res.end(JSON.stringify({ error: err.message }));
|
|
982
|
+
}
|
|
983
|
+
return;
|
|
984
|
+
}
|
|
985
|
+
|
|
839
986
|
if (url.pathname === '/api/doctor') {
|
|
840
987
|
try {
|
|
841
988
|
const { readState } = await import('../doctor/index.mjs');
|
|
@@ -1435,17 +1582,47 @@ const server = createServer(async (req, res) => {
|
|
|
1435
1582
|
return;
|
|
1436
1583
|
}
|
|
1437
1584
|
|
|
1585
|
+
if (url.pathname === '/api/sources' && req.method === 'GET') {
|
|
1586
|
+
try {
|
|
1587
|
+
const { loadProjectConfig } = await import('../config/project-config.mjs');
|
|
1588
|
+
const {
|
|
1589
|
+
resolveEffectiveSourceTargetsFromConfig,
|
|
1590
|
+
legacyEnvSourceTargets,
|
|
1591
|
+
normalizeConfigTarget,
|
|
1592
|
+
} = await import('../config/source-targets.mjs');
|
|
1593
|
+
const loaded = loadProjectConfig(ROOT_DIR, process.env);
|
|
1594
|
+
const configTargets = (loaded.config.sources?.targets ?? []).map(normalizeConfigTarget);
|
|
1595
|
+
const envTargets = legacyEnvSourceTargets(process.env);
|
|
1596
|
+
const effective = resolveEffectiveSourceTargetsFromConfig(loaded.config, process.env);
|
|
1597
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
1598
|
+
res.end(JSON.stringify({
|
|
1599
|
+
path: loaded.path,
|
|
1600
|
+
configTargets,
|
|
1601
|
+
envTargets,
|
|
1602
|
+
effective,
|
|
1603
|
+
}));
|
|
1604
|
+
} catch (err) {
|
|
1605
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
1606
|
+
res.end(JSON.stringify({ error: err.message }));
|
|
1607
|
+
}
|
|
1608
|
+
return;
|
|
1609
|
+
}
|
|
1610
|
+
|
|
1438
1611
|
if (url.pathname === '/api/intake/config' && req.method === 'GET') {
|
|
1439
1612
|
try {
|
|
1440
1613
|
const { loadIntakeConfig, INTAKE_DEPTH_GUIDANCE, INTAKE_HARD_MAX_DEPTH } = await import('../intake/intake-config.mjs');
|
|
1614
|
+
const { getRebrand } = await import('../profiles/rebrand.mjs');
|
|
1441
1615
|
const config = loadIntakeConfig(ROOT_DIR, process.env);
|
|
1616
|
+
const rebrand = getRebrand(ROOT_DIR);
|
|
1442
1617
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
1443
1618
|
res.end(JSON.stringify({
|
|
1444
1619
|
config,
|
|
1445
1620
|
rootDir: ROOT_DIR,
|
|
1446
1621
|
guidance: INTAKE_DEPTH_GUIDANCE,
|
|
1447
1622
|
hardMaxDepth: INTAKE_HARD_MAX_DEPTH,
|
|
1448
|
-
defaults: { projectInbox: '.cx/inbox', docsIntake: 'docs/intake' },
|
|
1623
|
+
defaults: { projectInbox: '.cx/inbox', docsIntake: 'docs/intake', rootInbox: 'inbox/' },
|
|
1624
|
+
label: rebrand.intakeQueueLabel,
|
|
1625
|
+
itemNoun: rebrand.signalNoun,
|
|
1449
1626
|
}));
|
|
1450
1627
|
} catch (err) {
|
|
1451
1628
|
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
@@ -1457,10 +1634,17 @@ const server = createServer(async (req, res) => {
|
|
|
1457
1634
|
if (url.pathname === '/api/intake/list' && req.method === 'GET') {
|
|
1458
1635
|
try {
|
|
1459
1636
|
const { createIntakeQueue } = await import('../intake/queue.mjs');
|
|
1637
|
+
const { getRebrand } = await import('../profiles/rebrand.mjs');
|
|
1460
1638
|
const queue = createIntakeQueue(ROOT_DIR, process.env);
|
|
1461
1639
|
const items = queue.listPending();
|
|
1640
|
+
const rebrand = getRebrand(ROOT_DIR);
|
|
1462
1641
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
1463
|
-
res.end(JSON.stringify({
|
|
1642
|
+
res.end(JSON.stringify({
|
|
1643
|
+
items,
|
|
1644
|
+
total: items.length,
|
|
1645
|
+
label: rebrand.intakeQueueLabel,
|
|
1646
|
+
itemNoun: rebrand.signalNoun,
|
|
1647
|
+
}));
|
|
1464
1648
|
} catch (err) {
|
|
1465
1649
|
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
1466
1650
|
res.end(JSON.stringify({ error: err.message }));
|
|
@@ -1934,6 +2118,51 @@ const server = createServer(async (req, res) => {
|
|
|
1934
2118
|
return;
|
|
1935
2119
|
}
|
|
1936
2120
|
|
|
2121
|
+
if (url.pathname === '/api/chat/loop/stream' && req.method === 'GET') {
|
|
2122
|
+
handleChatLoopStream(req, res, { rootDir: ROOT_DIR });
|
|
2123
|
+
return;
|
|
2124
|
+
}
|
|
2125
|
+
|
|
2126
|
+
if (url.pathname === '/api/chat/loop/pending' && req.method === 'GET') {
|
|
2127
|
+
handleChatLoopPending(req, res);
|
|
2128
|
+
return;
|
|
2129
|
+
}
|
|
2130
|
+
|
|
2131
|
+
if (url.pathname === '/api/chat/loop/permission' && req.method === 'POST') {
|
|
2132
|
+
handleChatLoopPermission(req, res);
|
|
2133
|
+
return;
|
|
2134
|
+
}
|
|
2135
|
+
|
|
2136
|
+
if (url.pathname === '/api/chat/config' && (req.method === 'GET' || req.method === 'POST')) {
|
|
2137
|
+
handleChatLoopConfig(req, res, { rootDir: ROOT_DIR });
|
|
2138
|
+
return;
|
|
2139
|
+
}
|
|
2140
|
+
|
|
2141
|
+
if (url.pathname === '/api/chat/models' && req.method === 'GET') {
|
|
2142
|
+
void handleChatModels(req, res, { rootDir: ROOT_DIR });
|
|
2143
|
+
return;
|
|
2144
|
+
}
|
|
2145
|
+
|
|
2146
|
+
if (url.pathname === '/api/chat/models/select' && req.method === 'POST') {
|
|
2147
|
+
handleChatModelSelect(req, res, { rootDir: ROOT_DIR });
|
|
2148
|
+
return;
|
|
2149
|
+
}
|
|
2150
|
+
|
|
2151
|
+
if (url.pathname === '/api/chat/loop/command' && req.method === 'POST') {
|
|
2152
|
+
handleChatLoopCommand(req, res, { rootDir: ROOT_DIR });
|
|
2153
|
+
return;
|
|
2154
|
+
}
|
|
2155
|
+
|
|
2156
|
+
if (url.pathname === '/api/chat/loop/history' && req.method === 'GET') {
|
|
2157
|
+
handleChatLoopHistory(req, res, { rootDir: ROOT_DIR });
|
|
2158
|
+
return;
|
|
2159
|
+
}
|
|
2160
|
+
|
|
2161
|
+
if (url.pathname === '/api/chat/loop/cancel' && req.method === 'POST') {
|
|
2162
|
+
handleChatLoopCancel(req, res);
|
|
2163
|
+
return;
|
|
2164
|
+
}
|
|
2165
|
+
|
|
1937
2166
|
if (req.method === 'POST') {
|
|
1938
2167
|
let body = '';
|
|
1939
2168
|
req.on('data', chunk => { body += chunk; });
|
|
@@ -2140,6 +2369,7 @@ const server = createServer(async (req, res) => {
|
|
|
2140
2369
|
maxDepth: data.maxDepth,
|
|
2141
2370
|
includeProjectInbox: data.includeProjectInbox,
|
|
2142
2371
|
includeDocsIntake: data.includeDocsIntake,
|
|
2372
|
+
includeRootInbox: data.includeRootInbox ?? data.includeArchetypeInbox,
|
|
2143
2373
|
});
|
|
2144
2374
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
2145
2375
|
res.end(JSON.stringify({ success: true, config: next }));
|
|
@@ -2298,9 +2528,21 @@ const server = createServer(async (req, res) => {
|
|
|
2298
2528
|
return;
|
|
2299
2529
|
}
|
|
2300
2530
|
|
|
2531
|
+
if (url.pathname.startsWith('/demo-preview/')) {
|
|
2532
|
+
const preview = resolveDemoPreviewPath(url.pathname);
|
|
2533
|
+
if (!preview.ok) {
|
|
2534
|
+
res.writeHead(preview.status);
|
|
2535
|
+
res.end(preview.message);
|
|
2536
|
+
return;
|
|
2537
|
+
}
|
|
2538
|
+
res.writeHead(200, { 'Content-Type': demoPreviewMime(preview.ext) });
|
|
2539
|
+
res.end(readFileSync(preview.filePath));
|
|
2540
|
+
return;
|
|
2541
|
+
}
|
|
2542
|
+
|
|
2301
2543
|
// Static files
|
|
2302
2544
|
let filePath = url.pathname === '/' ? '/index.html' : url.pathname;
|
|
2303
|
-
|
|
2545
|
+
let fullPath = join(STATIC_DIR, filePath);
|
|
2304
2546
|
|
|
2305
2547
|
if (!fullPath.startsWith(STATIC_DIR)) {
|
|
2306
2548
|
res.writeHead(403);
|
|
@@ -2308,7 +2550,14 @@ const server = createServer(async (req, res) => {
|
|
|
2308
2550
|
return;
|
|
2309
2551
|
}
|
|
2310
2552
|
|
|
2311
|
-
if (
|
|
2553
|
+
if (existsSync(fullPath) && statSync(fullPath).isDirectory()) {
|
|
2554
|
+
fullPath = join(fullPath, 'index.html');
|
|
2555
|
+
} else if (!existsSync(fullPath)) {
|
|
2556
|
+
const indexCandidate = join(STATIC_DIR, filePath, 'index.html');
|
|
2557
|
+
if (existsSync(indexCandidate)) fullPath = indexCandidate;
|
|
2558
|
+
}
|
|
2559
|
+
|
|
2560
|
+
if (!existsSync(fullPath) || statSync(fullPath).isDirectory()) {
|
|
2312
2561
|
res.writeHead(404);
|
|
2313
2562
|
res.end('Not found');
|
|
2314
2563
|
return;
|
|
@@ -2345,11 +2594,12 @@ function runEmbedSync() {
|
|
|
2345
2594
|
// from process.env (telemetry credentials, model provider keys, etc.) find them
|
|
2346
2595
|
// even when the server is started directly (not through bin/construct).
|
|
2347
2596
|
try {
|
|
2348
|
-
const {
|
|
2597
|
+
const { getUserEnvPath } = await import('../env-config.mjs');
|
|
2598
|
+
const { prepareConstructEnv } = await import('../runtime-env.mjs');
|
|
2349
2599
|
const homeDir = HOME || homedir();
|
|
2350
2600
|
const envPath = getUserEnvPath(homeDir);
|
|
2351
2601
|
if (existsSync(envPath)) {
|
|
2352
|
-
const ENV =
|
|
2602
|
+
const ENV = prepareConstructEnv({ rootDir: ROOT_DIR, homeDir, env: process.env });
|
|
2353
2603
|
for (const [key, value] of Object.entries(ENV)) {
|
|
2354
2604
|
if (!(key in process.env)) process.env[key] = value;
|
|
2355
2605
|
}
|
|
@@ -3,9 +3,10 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Three tiers, configurable via env:
|
|
5
5
|
*
|
|
6
|
-
* read
|
|
7
|
-
* chat
|
|
8
|
-
*
|
|
6
|
+
* read 60 req/min/key (dashboard polling, list endpoints)
|
|
7
|
+
* chat 10 req/min/key (/api/chat/stream — bursty + expensive)
|
|
8
|
+
* command 120 req/min/key (/api/chat/loop/command — slash cmds, no LLM)
|
|
9
|
+
* write 5 req/min/key (state-mutating endpoints — approvals, config)
|
|
9
10
|
*
|
|
10
11
|
* `key` is by default the request's source IP, with the active session id
|
|
11
12
|
* mixed in when present so two users behind the same NAT get separate
|
|
@@ -18,6 +19,7 @@
|
|
|
18
19
|
const DEFAULT_TIERS = {
|
|
19
20
|
read: { capacity: 60, refillPerMs: 60 / 60_000 },
|
|
20
21
|
chat: { capacity: 10, refillPerMs: 10 / 60_000 },
|
|
22
|
+
command: { capacity: 120, refillPerMs: 120 / 60_000 },
|
|
21
23
|
write: { capacity: 5, refillPerMs: 5 / 60_000 },
|
|
22
24
|
};
|
|
23
25
|
|
package/lib/server/webhook.mjs
CHANGED
|
@@ -306,8 +306,9 @@ async function runSnapshotForSlack() {
|
|
|
306
306
|
const { SnapshotEngine, renderMarkdown } = await import('../embed/snapshot.mjs');
|
|
307
307
|
const { EMPTY_CONFIG } = await import('../embed/config.mjs');
|
|
308
308
|
|
|
309
|
-
const registry = ProviderRegistry.fromEnv(env);
|
|
310
|
-
const
|
|
309
|
+
const registry = await ProviderRegistry.fromEnv(env);
|
|
310
|
+
const { resolveAutoEmbedSources } = await import('../embed/auto-sources.mjs');
|
|
311
|
+
const sources = resolveAutoEmbedSources({ cwd: process.cwd(), env, registry });
|
|
311
312
|
if (!sources.length) return '⚠️ No embed sources configured. Add credentials to config.env.';
|
|
312
313
|
|
|
313
314
|
const config = { ...EMPTY_CONFIG, sources, snapshot: { maxItems: 10 } };
|
package/lib/service-manager.mjs
CHANGED
|
@@ -13,10 +13,13 @@ import path from 'node:path';
|
|
|
13
13
|
import { fileURLToPath } from 'node:url';
|
|
14
14
|
|
|
15
15
|
import { findAvailablePort } from './host-capabilities.mjs';
|
|
16
|
+
import { wrapWithOpRun } from './providers/op-run.mjs';
|
|
16
17
|
import { getUserEnvPath, loadConstructEnv, parseEnvFile, writeEnvValues } from './env-config.mjs';
|
|
17
18
|
import { runPressureRelease } from './runtime-pressure.mjs';
|
|
18
19
|
import { resolveTraceBackend, telemetryProviderLabel } from './telemetry/client.mjs';
|
|
19
20
|
import { memoryPort as derivedMemoryPort } from './home-namespace.mjs';
|
|
21
|
+
import { readHeartbeat } from './daemons/contract.mjs';
|
|
22
|
+
import { heartbeatPath } from './oracle/index.mjs';
|
|
20
23
|
|
|
21
24
|
const DASHBOARD_STATE_FILE = 'dashboard.json';
|
|
22
25
|
|
|
@@ -95,7 +98,7 @@ export function buildRuntimeRecoverySummary({ rootDir, homeDir = os.homedir(), r
|
|
|
95
98
|
}
|
|
96
99
|
|
|
97
100
|
export function isManagedConstructPostgresUrl(databaseUrl = '') {
|
|
98
|
-
// Construct
|
|
101
|
+
// Local Postgres lifecycle is out of Construct's scope, so no URL is Construct-managed.
|
|
99
102
|
return false;
|
|
100
103
|
}
|
|
101
104
|
|
|
@@ -249,6 +252,25 @@ export function startDoctor({ rootDir, homeDir = os.homedir() } = {}) {
|
|
|
249
252
|
return { started: true, logPath };
|
|
250
253
|
}
|
|
251
254
|
|
|
255
|
+
export function startOracle({ rootDir, homeDir = os.homedir(), projectDir = rootDir } = {}) {
|
|
256
|
+
if (process.env.CONSTRUCT_ORACLE === 'off' || process.env.CONSTRUCT_ORACLE === '0') {
|
|
257
|
+
return { started: false, reason: 'disabled' };
|
|
258
|
+
}
|
|
259
|
+
const entry = path.join(INSTALL_ROOT, 'lib', 'oracle', 'daemon-entry.mjs');
|
|
260
|
+
if (!fs.existsSync(entry)) return { started: false, reason: 'missing-binary' };
|
|
261
|
+
const live = readHeartbeat(heartbeatPath(homeDir));
|
|
262
|
+
if (live) return { started: false, reused: true, pid: live.pid };
|
|
263
|
+
const { logPath } = spawnDetached('node', [entry], homeDir, 'oracle-daemon.log', {
|
|
264
|
+
cwd: projectDir,
|
|
265
|
+
env: {
|
|
266
|
+
...process.env,
|
|
267
|
+
CONSTRUCT_ORACLE_ROOT: rootDir,
|
|
268
|
+
CONSTRUCT_ORACLE_PROJECT: projectDir,
|
|
269
|
+
},
|
|
270
|
+
});
|
|
271
|
+
return { started: true, logPath };
|
|
272
|
+
}
|
|
273
|
+
|
|
252
274
|
export async function startDashboard({ rootDir, homeDir = os.homedir(), preferredPort = 4242 } = {}) {
|
|
253
275
|
const existing = readDashboardState(homeDir);
|
|
254
276
|
if (existing && await isDashboardRunning(existing.port)) {
|
|
@@ -262,7 +284,16 @@ export async function startDashboard({ rootDir, homeDir = os.homedir(), preferre
|
|
|
262
284
|
fs.mkdirSync(path.dirname(outPath), { recursive: true });
|
|
263
285
|
const out = fs.openSync(outPath, 'a');
|
|
264
286
|
const err = fs.openSync(outPath, 'a');
|
|
265
|
-
|
|
287
|
+
|
|
288
|
+
// Opt-in: with CONSTRUCT_OP_ENV_FILE set, launch the daemon under `op run` so
|
|
289
|
+
// 1Password op:// refs resolve once at startup into its env. The biometric
|
|
290
|
+
// unlock prompt makes first launch slower, so widen the readiness window then.
|
|
291
|
+
const { command, args, wrapped } = wrapWithOpRun(
|
|
292
|
+
process.execPath,
|
|
293
|
+
[path.join(INSTALL_ROOT, 'lib', 'server', 'index.mjs')],
|
|
294
|
+
{ env: process.env },
|
|
295
|
+
);
|
|
296
|
+
const child = spawn(command, args, {
|
|
266
297
|
detached: true,
|
|
267
298
|
stdio: ['ignore', out, err],
|
|
268
299
|
env: { ...process.env, PORT: String(port) },
|
|
@@ -270,7 +301,8 @@ export async function startDashboard({ rootDir, homeDir = os.homedir(), preferre
|
|
|
270
301
|
child.unref();
|
|
271
302
|
|
|
272
303
|
let ready = false;
|
|
273
|
-
|
|
304
|
+
const maxTries = wrapped ? 150 : 30;
|
|
305
|
+
for (let i = 0; i < maxTries; i += 1) {
|
|
274
306
|
if (await probeRuntimePort(port)) { ready = true; break; }
|
|
275
307
|
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
276
308
|
}
|
|
@@ -278,7 +310,8 @@ export async function startDashboard({ rootDir, homeDir = os.homedir(), preferre
|
|
|
278
310
|
if (!ready) {
|
|
279
311
|
let logTail = '';
|
|
280
312
|
try { logTail = fs.readFileSync(outPath, 'utf8').split('\n').slice(-12).join('\n'); } catch { /* log unreadable */ }
|
|
281
|
-
|
|
313
|
+
const window = wrapped ? '30s (op run awaits 1Password unlock)' : '6s';
|
|
314
|
+
return { started: false, failed: true, port, logPath: outPath, error: `dashboard did not bind 127.0.0.1:${port} within ${window}`, logTail };
|
|
282
315
|
}
|
|
283
316
|
|
|
284
317
|
const state = {
|
|
@@ -342,6 +375,16 @@ export const SELECTABLE_SERVICES = Object.freeze([
|
|
|
342
375
|
{ key: 'copilot-bridge', label: 'Copilot Bridge', description: 'Host-native Copilot bridge proxy (requires gh auth).' },
|
|
343
376
|
]);
|
|
344
377
|
|
|
378
|
+
// Construct does not impose an Ollama keep-alive: an operator value is honored when
|
|
379
|
+
// present, otherwise the variable is left unset so the Ollama server applies its own
|
|
380
|
+
// default and idle models unload on their own. A forced value would keep every
|
|
381
|
+
// requested model resident in unified memory for the whole window.
|
|
382
|
+
|
|
383
|
+
export function resolveOllamaKeepAlive(env = {}) {
|
|
384
|
+
const operatorValue = typeof env?.OLLAMA_KEEP_ALIVE === 'string' ? env.OLLAMA_KEEP_ALIVE.trim() : '';
|
|
385
|
+
return operatorValue || null;
|
|
386
|
+
}
|
|
387
|
+
|
|
345
388
|
export async function startServices({
|
|
346
389
|
rootDir,
|
|
347
390
|
homeDir = os.homedir(),
|
|
@@ -370,10 +413,10 @@ export async function startServices({
|
|
|
370
413
|
|
|
371
414
|
const liveEnv = loadConstructEnvFn({ rootDir, homeDir });
|
|
372
415
|
const results = [];
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
liveEnv.OLLAMA_KEEP_ALIVE =
|
|
376
|
-
|
|
416
|
+
|
|
417
|
+
const operatorKeepAlive = resolveOllamaKeepAlive(liveEnv);
|
|
418
|
+
if (operatorKeepAlive) liveEnv.OLLAMA_KEEP_ALIVE = operatorKeepAlive;
|
|
419
|
+
|
|
377
420
|
const pressureReport = runPressureReleaseFn({ env: { ...liveEnv, HOME: homeDir } });
|
|
378
421
|
if (pressureReport?.killed?.length) {
|
|
379
422
|
results.push({
|
|
@@ -481,6 +524,15 @@ export async function startServices({
|
|
|
481
524
|
results.push({ name: 'Doctor', status: 'unavailable', note: doctor.reason });
|
|
482
525
|
}
|
|
483
526
|
|
|
527
|
+
const oracle = startOracle({ rootDir, homeDir, projectDir: rootDir });
|
|
528
|
+
if (oracle.started) {
|
|
529
|
+
results.push({ name: 'Oracle', status: 'started', note: `L0.5 overseer · logs: ${oracle.logPath}` });
|
|
530
|
+
} else if (oracle.reused) {
|
|
531
|
+
results.push({ name: 'Oracle', status: 'reused', note: `pid ${oracle.pid}` });
|
|
532
|
+
} else if (oracle.reason !== 'disabled') {
|
|
533
|
+
results.push({ name: 'Oracle', status: 'unavailable', note: oracle.reason });
|
|
534
|
+
}
|
|
535
|
+
|
|
484
536
|
emitServiceFailures(results);
|
|
485
537
|
|
|
486
538
|
return {
|