@geraldmaron/construct 1.1.1 → 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 +29 -19
- package/bin/construct +311 -81
- 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 +1 -1
- 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 +129 -15
- 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 +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/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 +360 -36
- package/lib/document-extract.mjs +44 -4
- package/lib/document-ingest.mjs +29 -20
- package/lib/embed/cli.mjs +2 -2
- package/lib/embed/daemon.mjs +18 -46
- 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/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 +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 +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 +252 -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 +228 -7
- 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 +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 +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,6 +702,7 @@ 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
|
: (req.method !== 'GET' && req.method !== 'HEAD' ? 'write' : 'read');
|
|
698
708
|
if (checkRateLimit) {
|
|
@@ -836,6 +846,141 @@ const server = createServer(async (req, res) => {
|
|
|
836
846
|
return;
|
|
837
847
|
}
|
|
838
848
|
|
|
849
|
+
if (url.pathname === '/api/certification/status' && req.method === 'GET') {
|
|
850
|
+
try {
|
|
851
|
+
const { buildCertificationDashboard } = await import('../certification/dashboard-api.mjs');
|
|
852
|
+
const capabilityId = url.searchParams.get('capabilityId');
|
|
853
|
+
const payload = capabilityId
|
|
854
|
+
? (await import('../certification/status.mjs')).buildCertificationStatus({
|
|
855
|
+
rootDir: process.cwd(),
|
|
856
|
+
capabilityId,
|
|
857
|
+
})
|
|
858
|
+
: buildCertificationDashboard({ rootDir: process.cwd() });
|
|
859
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
860
|
+
res.end(JSON.stringify(payload));
|
|
861
|
+
} catch (err) {
|
|
862
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
863
|
+
res.end(JSON.stringify({ error: err.message }));
|
|
864
|
+
}
|
|
865
|
+
return;
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
if (url.pathname === '/api/oracle/pending' && req.method === 'GET') {
|
|
869
|
+
try {
|
|
870
|
+
const { listPending } = await import('../oracle/actions.mjs');
|
|
871
|
+
const pending = listPending(process.cwd()).filter((p) => p.status !== 'approved');
|
|
872
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
873
|
+
res.end(JSON.stringify({ pending }));
|
|
874
|
+
} catch (err) {
|
|
875
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
876
|
+
res.end(JSON.stringify({ error: err.message }));
|
|
877
|
+
}
|
|
878
|
+
return;
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
if (url.pathname === '/api/improvement/pending' && req.method === 'GET') {
|
|
882
|
+
try {
|
|
883
|
+
const { listPending } = await import('../improvement/surface.mjs');
|
|
884
|
+
const pending = listPending(process.cwd());
|
|
885
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
886
|
+
res.end(JSON.stringify({ pending }));
|
|
887
|
+
} catch (err) {
|
|
888
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
889
|
+
res.end(JSON.stringify({ error: err.message }));
|
|
890
|
+
}
|
|
891
|
+
return;
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
if (url.pathname.startsWith('/api/improvement/') && req.method === 'POST') {
|
|
895
|
+
const action = url.pathname.slice('/api/improvement/'.length);
|
|
896
|
+
let body = '';
|
|
897
|
+
req.on('data', (chunk) => { body += chunk; });
|
|
898
|
+
req.on('end', async () => {
|
|
899
|
+
try {
|
|
900
|
+
const data = JSON.parse(body || '{}');
|
|
901
|
+
const projectDir = process.cwd();
|
|
902
|
+
const { reviewRecord, approveRecord, applyRecord, rollbackRecord } = await import('../improvement/surface.mjs');
|
|
903
|
+
let result;
|
|
904
|
+
if (action === 'review') {
|
|
905
|
+
if (!data.id) throw new Error('Missing id');
|
|
906
|
+
result = reviewRecord(projectDir, data.id);
|
|
907
|
+
} else if (action === 'approve') {
|
|
908
|
+
if (!data.id) throw new Error('Missing id');
|
|
909
|
+
result = approveRecord(projectDir, data.id, { identity: data.identity });
|
|
910
|
+
} else if (action === 'apply') {
|
|
911
|
+
if (!data.id) throw new Error('Missing id');
|
|
912
|
+
result = applyRecord(projectDir, data.id, { monitor: data.monitor });
|
|
913
|
+
} else if (action === 'rollback') {
|
|
914
|
+
if (!data.id) throw new Error('Missing id');
|
|
915
|
+
result = rollbackRecord(projectDir, data.id, { reason: data.reason });
|
|
916
|
+
} else {
|
|
917
|
+
throw new Error(`Unknown improvement action: ${action}`);
|
|
918
|
+
}
|
|
919
|
+
const status = result.ok === false && result.error ? 400 : 200;
|
|
920
|
+
res.writeHead(status, { 'Content-Type': 'application/json' });
|
|
921
|
+
res.end(JSON.stringify(result));
|
|
922
|
+
notifyClients();
|
|
923
|
+
} catch (err) {
|
|
924
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
925
|
+
res.end(JSON.stringify({ error: err.message }));
|
|
926
|
+
}
|
|
927
|
+
});
|
|
928
|
+
return;
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
if (url.pathname.startsWith('/api/improvement/') && req.method === 'GET') {
|
|
932
|
+
const id = decodeURIComponent(url.pathname.slice('/api/improvement/'.length));
|
|
933
|
+
if (id && id !== 'pending') {
|
|
934
|
+
try {
|
|
935
|
+
const { loadRecord } = await import('../improvement/store.mjs');
|
|
936
|
+
const record = loadRecord(process.cwd(), id);
|
|
937
|
+
if (!record) {
|
|
938
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
939
|
+
res.end(JSON.stringify({ error: 'not-found' }));
|
|
940
|
+
return;
|
|
941
|
+
}
|
|
942
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
943
|
+
res.end(JSON.stringify(record));
|
|
944
|
+
} catch (err) {
|
|
945
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
946
|
+
res.end(JSON.stringify({ error: err.message }));
|
|
947
|
+
}
|
|
948
|
+
return;
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
if (url.pathname === '/api/oracle' && req.method === 'GET') {
|
|
953
|
+
try {
|
|
954
|
+
const { homedir: osHome } = await import('node:os');
|
|
955
|
+
const { readHeartbeat } = await import('../daemons/contract.mjs');
|
|
956
|
+
const { heartbeatPath, readLastTick } = await import('../oracle/index.mjs');
|
|
957
|
+
const { collectReadModel, enrichReadModel } = await import('../oracle/read-model.mjs');
|
|
958
|
+
const { synthesizeVerdict } = await import('../oracle/synthesize.mjs');
|
|
959
|
+
const home = osHome();
|
|
960
|
+
const projectDir = process.cwd();
|
|
961
|
+
const readModel = collectReadModel({ rootDir: ROOT_DIR, projectDir, homeDir: home });
|
|
962
|
+
const enriched = await enrichReadModel(readModel);
|
|
963
|
+
const synthesis = synthesizeVerdict(enriched);
|
|
964
|
+
const hb = readHeartbeat(heartbeatPath(home));
|
|
965
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
966
|
+
res.end(JSON.stringify({
|
|
967
|
+
running: !!hb,
|
|
968
|
+
heartbeat: hb,
|
|
969
|
+
lastTick: readLastTick(home),
|
|
970
|
+
verdict: synthesis.verdict,
|
|
971
|
+
gaps: synthesis.gaps,
|
|
972
|
+
recommendedActions: synthesis.recommendedActions,
|
|
973
|
+
orgGraph: enriched.orgGraph,
|
|
974
|
+
openBeads: enriched.beads?.openCount ?? null,
|
|
975
|
+
lastPropagationCheck: enriched.orgGraph?.propagation ?? null,
|
|
976
|
+
}));
|
|
977
|
+
} catch (err) {
|
|
978
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
979
|
+
res.end(JSON.stringify({ error: err.message }));
|
|
980
|
+
}
|
|
981
|
+
return;
|
|
982
|
+
}
|
|
983
|
+
|
|
839
984
|
if (url.pathname === '/api/doctor') {
|
|
840
985
|
try {
|
|
841
986
|
const { readState } = await import('../doctor/index.mjs');
|
|
@@ -1438,7 +1583,9 @@ const server = createServer(async (req, res) => {
|
|
|
1438
1583
|
if (url.pathname === '/api/intake/config' && req.method === 'GET') {
|
|
1439
1584
|
try {
|
|
1440
1585
|
const { loadIntakeConfig, INTAKE_DEPTH_GUIDANCE, INTAKE_HARD_MAX_DEPTH } = await import('../intake/intake-config.mjs');
|
|
1586
|
+
const { getRebrand } = await import('../profiles/rebrand.mjs');
|
|
1441
1587
|
const config = loadIntakeConfig(ROOT_DIR, process.env);
|
|
1588
|
+
const rebrand = getRebrand(ROOT_DIR);
|
|
1442
1589
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
1443
1590
|
res.end(JSON.stringify({
|
|
1444
1591
|
config,
|
|
@@ -1446,6 +1593,8 @@ const server = createServer(async (req, res) => {
|
|
|
1446
1593
|
guidance: INTAKE_DEPTH_GUIDANCE,
|
|
1447
1594
|
hardMaxDepth: INTAKE_HARD_MAX_DEPTH,
|
|
1448
1595
|
defaults: { projectInbox: '.cx/inbox', docsIntake: 'docs/intake' },
|
|
1596
|
+
label: rebrand.intakeQueueLabel,
|
|
1597
|
+
itemNoun: rebrand.signalNoun,
|
|
1449
1598
|
}));
|
|
1450
1599
|
} catch (err) {
|
|
1451
1600
|
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
@@ -1457,10 +1606,17 @@ const server = createServer(async (req, res) => {
|
|
|
1457
1606
|
if (url.pathname === '/api/intake/list' && req.method === 'GET') {
|
|
1458
1607
|
try {
|
|
1459
1608
|
const { createIntakeQueue } = await import('../intake/queue.mjs');
|
|
1609
|
+
const { getRebrand } = await import('../profiles/rebrand.mjs');
|
|
1460
1610
|
const queue = createIntakeQueue(ROOT_DIR, process.env);
|
|
1461
1611
|
const items = queue.listPending();
|
|
1612
|
+
const rebrand = getRebrand(ROOT_DIR);
|
|
1462
1613
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
1463
|
-
res.end(JSON.stringify({
|
|
1614
|
+
res.end(JSON.stringify({
|
|
1615
|
+
items,
|
|
1616
|
+
total: items.length,
|
|
1617
|
+
label: rebrand.intakeQueueLabel,
|
|
1618
|
+
itemNoun: rebrand.signalNoun,
|
|
1619
|
+
}));
|
|
1464
1620
|
} catch (err) {
|
|
1465
1621
|
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
1466
1622
|
res.end(JSON.stringify({ error: err.message }));
|
|
@@ -1934,6 +2090,51 @@ const server = createServer(async (req, res) => {
|
|
|
1934
2090
|
return;
|
|
1935
2091
|
}
|
|
1936
2092
|
|
|
2093
|
+
if (url.pathname === '/api/chat/loop/stream' && req.method === 'GET') {
|
|
2094
|
+
handleChatLoopStream(req, res, { rootDir: ROOT_DIR });
|
|
2095
|
+
return;
|
|
2096
|
+
}
|
|
2097
|
+
|
|
2098
|
+
if (url.pathname === '/api/chat/loop/pending' && req.method === 'GET') {
|
|
2099
|
+
handleChatLoopPending(req, res);
|
|
2100
|
+
return;
|
|
2101
|
+
}
|
|
2102
|
+
|
|
2103
|
+
if (url.pathname === '/api/chat/loop/permission' && req.method === 'POST') {
|
|
2104
|
+
handleChatLoopPermission(req, res);
|
|
2105
|
+
return;
|
|
2106
|
+
}
|
|
2107
|
+
|
|
2108
|
+
if (url.pathname === '/api/chat/config' && (req.method === 'GET' || req.method === 'POST')) {
|
|
2109
|
+
handleChatLoopConfig(req, res, { rootDir: ROOT_DIR });
|
|
2110
|
+
return;
|
|
2111
|
+
}
|
|
2112
|
+
|
|
2113
|
+
if (url.pathname === '/api/chat/models' && req.method === 'GET') {
|
|
2114
|
+
void handleChatModels(req, res, { rootDir: ROOT_DIR });
|
|
2115
|
+
return;
|
|
2116
|
+
}
|
|
2117
|
+
|
|
2118
|
+
if (url.pathname === '/api/chat/models/select' && req.method === 'POST') {
|
|
2119
|
+
handleChatModelSelect(req, res, { rootDir: ROOT_DIR });
|
|
2120
|
+
return;
|
|
2121
|
+
}
|
|
2122
|
+
|
|
2123
|
+
if (url.pathname === '/api/chat/loop/command' && req.method === 'POST') {
|
|
2124
|
+
handleChatLoopCommand(req, res, { rootDir: ROOT_DIR });
|
|
2125
|
+
return;
|
|
2126
|
+
}
|
|
2127
|
+
|
|
2128
|
+
if (url.pathname === '/api/chat/loop/history' && req.method === 'GET') {
|
|
2129
|
+
handleChatLoopHistory(req, res, { rootDir: ROOT_DIR });
|
|
2130
|
+
return;
|
|
2131
|
+
}
|
|
2132
|
+
|
|
2133
|
+
if (url.pathname === '/api/chat/loop/cancel' && req.method === 'POST') {
|
|
2134
|
+
handleChatLoopCancel(req, res);
|
|
2135
|
+
return;
|
|
2136
|
+
}
|
|
2137
|
+
|
|
1937
2138
|
if (req.method === 'POST') {
|
|
1938
2139
|
let body = '';
|
|
1939
2140
|
req.on('data', chunk => { body += chunk; });
|
|
@@ -2298,9 +2499,21 @@ const server = createServer(async (req, res) => {
|
|
|
2298
2499
|
return;
|
|
2299
2500
|
}
|
|
2300
2501
|
|
|
2502
|
+
if (url.pathname.startsWith('/demo-preview/')) {
|
|
2503
|
+
const preview = resolveDemoPreviewPath(url.pathname);
|
|
2504
|
+
if (!preview.ok) {
|
|
2505
|
+
res.writeHead(preview.status);
|
|
2506
|
+
res.end(preview.message);
|
|
2507
|
+
return;
|
|
2508
|
+
}
|
|
2509
|
+
res.writeHead(200, { 'Content-Type': demoPreviewMime(preview.ext) });
|
|
2510
|
+
res.end(readFileSync(preview.filePath));
|
|
2511
|
+
return;
|
|
2512
|
+
}
|
|
2513
|
+
|
|
2301
2514
|
// Static files
|
|
2302
2515
|
let filePath = url.pathname === '/' ? '/index.html' : url.pathname;
|
|
2303
|
-
|
|
2516
|
+
let fullPath = join(STATIC_DIR, filePath);
|
|
2304
2517
|
|
|
2305
2518
|
if (!fullPath.startsWith(STATIC_DIR)) {
|
|
2306
2519
|
res.writeHead(403);
|
|
@@ -2308,7 +2521,14 @@ const server = createServer(async (req, res) => {
|
|
|
2308
2521
|
return;
|
|
2309
2522
|
}
|
|
2310
2523
|
|
|
2311
|
-
if (
|
|
2524
|
+
if (existsSync(fullPath) && statSync(fullPath).isDirectory()) {
|
|
2525
|
+
fullPath = join(fullPath, 'index.html');
|
|
2526
|
+
} else if (!existsSync(fullPath)) {
|
|
2527
|
+
const indexCandidate = join(STATIC_DIR, filePath, 'index.html');
|
|
2528
|
+
if (existsSync(indexCandidate)) fullPath = indexCandidate;
|
|
2529
|
+
}
|
|
2530
|
+
|
|
2531
|
+
if (!existsSync(fullPath) || statSync(fullPath).isDirectory()) {
|
|
2312
2532
|
res.writeHead(404);
|
|
2313
2533
|
res.end('Not found');
|
|
2314
2534
|
return;
|
|
@@ -2345,11 +2565,12 @@ function runEmbedSync() {
|
|
|
2345
2565
|
// from process.env (telemetry credentials, model provider keys, etc.) find them
|
|
2346
2566
|
// even when the server is started directly (not through bin/construct).
|
|
2347
2567
|
try {
|
|
2348
|
-
const {
|
|
2568
|
+
const { getUserEnvPath } = await import('../env-config.mjs');
|
|
2569
|
+
const { prepareConstructEnv } = await import('../runtime-env.mjs');
|
|
2349
2570
|
const homeDir = HOME || homedir();
|
|
2350
2571
|
const envPath = getUserEnvPath(homeDir);
|
|
2351
2572
|
if (existsSync(envPath)) {
|
|
2352
|
-
const ENV =
|
|
2573
|
+
const ENV = prepareConstructEnv({ rootDir: ROOT_DIR, homeDir, env: process.env });
|
|
2353
2574
|
for (const [key, value] of Object.entries(ENV)) {
|
|
2354
2575
|
if (!(key in process.env)) process.env[key] = value;
|
|
2355
2576
|
}
|
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 {
|
package/lib/setup.mjs
CHANGED
|
@@ -32,6 +32,9 @@ import { getCanonicalOpenCodeConfigPath, readOpenCodeConfig, writeOpenCodeConfig
|
|
|
32
32
|
import { getEmbeddingModelInfo, warmupEmbeddingModel } from './storage/embeddings-engine.mjs';
|
|
33
33
|
import { buildPressureGuardValues, installPressureGuardLaunchAgent, loadPressureGuardLaunchAgent } from './runtime-pressure.mjs';
|
|
34
34
|
import { consentToInstall } from './setup-prompts.mjs';
|
|
35
|
+
import { cleanupLegacyGlobalConstruct } from './install/legacy-global-cleanup.mjs';
|
|
36
|
+
import { downloadDesktopBinary } from './install/desktop-binary-download.mjs';
|
|
37
|
+
import { buildDesktopBinary, hasDesktopSource } from './chat/desktop-build.mjs';
|
|
35
38
|
|
|
36
39
|
const ROOT_DIR = path.resolve(import.meta.dirname, '..');
|
|
37
40
|
const HOME = os.homedir();
|
|
@@ -40,7 +43,7 @@ function printHelp() {
|
|
|
40
43
|
console.log(`Construct install — machine setup (once per machine)
|
|
41
44
|
|
|
42
45
|
Usage:
|
|
43
|
-
construct install [--scope=project|user|both] [--yes] [--dry-run] [--no-launch-agent] [--reconfigure] [--with-docling]
|
|
46
|
+
construct install [--scope=project|user|both] [--yes] [--dry-run] [--no-launch-agent] [--reconfigure] [--with-docling] [--cleanup-legacy-global]
|
|
44
47
|
|
|
45
48
|
Flags:
|
|
46
49
|
--scope=<s> project | user | both (default: project — see ADR-0029)
|
|
@@ -56,6 +59,9 @@ Flags:
|
|
|
56
59
|
--with-docling eagerly provision the docling document-extraction venv now
|
|
57
60
|
(heavy, ~10 min via uv; otherwise provisioned lazily on
|
|
58
61
|
first document ingest)
|
|
62
|
+
--cleanup-legacy-global
|
|
63
|
+
remove legacy user-global Construct adapters/MCP/history
|
|
64
|
+
before setup; does not uninstall a global construct CLI
|
|
59
65
|
|
|
60
66
|
What --scope=user does:
|
|
61
67
|
- creates ~/.construct/config.env
|
|
@@ -307,14 +313,35 @@ function printProjectScopeGuidance() {
|
|
|
307
313
|
console.log(' construct install --scope=both');
|
|
308
314
|
}
|
|
309
315
|
|
|
316
|
+
function runLegacyGlobalCleanup({ homeDir, dryRun }) {
|
|
317
|
+
const result = cleanupLegacyGlobalConstruct({ homeDir, dryRun });
|
|
318
|
+
const action = dryRun ? 'would remove' : 'removed';
|
|
319
|
+
console.log(`Legacy global cleanup: ${result.removed.length ? `${action} ${result.removed.length} item(s)` : 'nothing detected'}`);
|
|
320
|
+
for (const item of result.removed.slice(0, 20)) {
|
|
321
|
+
console.log(` - ${item}`);
|
|
322
|
+
}
|
|
323
|
+
if (result.removed.length > 20) {
|
|
324
|
+
console.log(` - ...and ${result.removed.length - 20} more`);
|
|
325
|
+
}
|
|
326
|
+
return result;
|
|
327
|
+
}
|
|
328
|
+
|
|
310
329
|
// Dry-run renders the user-scope plan as intent only — every line names a write,
|
|
311
330
|
// network call, or service registration that the real run would perform — and
|
|
312
331
|
// returns before touching disk so the preview is provably side-effect-free.
|
|
313
332
|
|
|
314
|
-
function printInstallPlan({ scope, homeDir, withDocling, noLaunchAgent }) {
|
|
333
|
+
function printInstallPlan({ scope, homeDir, withDocling, noLaunchAgent, cleanupLegacyGlobal }) {
|
|
315
334
|
console.log('Construct install — dry-run (no changes written)');
|
|
316
335
|
console.log('────────────────────────────────────────────────');
|
|
317
336
|
console.log(`Scope: ${scope}`);
|
|
337
|
+
if (cleanupLegacyGlobal) {
|
|
338
|
+
console.log('\nLegacy global cleanup:');
|
|
339
|
+
runLegacyGlobalCleanup({ homeDir, dryRun: true });
|
|
340
|
+
}
|
|
341
|
+
if (scope === 'project') {
|
|
342
|
+
console.log('\nWould not run user-scope setup. Re-run without --dry-run to apply cleanup only.');
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
318
345
|
if (scope === 'both') {
|
|
319
346
|
console.log(' · would print project-scope guidance, then run the user-scope plan below');
|
|
320
347
|
}
|
|
@@ -326,6 +353,7 @@ function printInstallPlan({ scope, homeDir, withDocling, noLaunchAgent }) {
|
|
|
326
353
|
console.log(` · ${path.dirname(defaultVectorIndexPath(homeDir))} (LanceDB vector store dir)`);
|
|
327
354
|
console.log('\nWould check / install runtime tooling:');
|
|
328
355
|
console.log(' · cm (memory CLI) and cass (session search) when available on PATH');
|
|
356
|
+
console.log(' · construct-chat desktop binary (from GitHub releases → ~/.construct/bin/)');
|
|
329
357
|
console.log('\nWould reach the network / external services:');
|
|
330
358
|
console.log(' · embedding model warmup (loads from local cache; offline-safe, no download)');
|
|
331
359
|
if (withDocling) console.log(' · docling Python venv provisioning via uv (~10 min)');
|
|
@@ -341,13 +369,14 @@ export async function runSetup({ rootDir = ROOT_DIR, args = [], homeDir = HOME }
|
|
|
341
369
|
const argSet = new Set(args);
|
|
342
370
|
const isYes = argSet.has('--yes');
|
|
343
371
|
const reconfigure = argSet.has('--reconfigure');
|
|
372
|
+
const cleanupLegacyGlobal = argSet.has('--cleanup-legacy-global');
|
|
344
373
|
|
|
345
374
|
if (argSet.has('--help') || argSet.has('-h')) {
|
|
346
375
|
printHelp();
|
|
347
376
|
return;
|
|
348
377
|
}
|
|
349
378
|
|
|
350
|
-
const KNOWN_FLAGS = new Set(['--yes', '--dry-run', '--no-launch-agent', '--reconfigure', '--with-docling', '--help', '-h']);
|
|
379
|
+
const KNOWN_FLAGS = new Set(['--yes', '--dry-run', '--no-launch-agent', '--reconfigure', '--with-docling', '--cleanup-legacy-global', '--help', '-h']);
|
|
351
380
|
const dryRun = argSet.has('--dry-run');
|
|
352
381
|
const withDocling = argSet.has('--with-docling');
|
|
353
382
|
const unknownFlags = args.filter((a) => {
|
|
@@ -369,13 +398,25 @@ export async function runSetup({ rootDir = ROOT_DIR, args = [], homeDir = HOME }
|
|
|
369
398
|
}
|
|
370
399
|
const scope = scopeResult.scope;
|
|
371
400
|
|
|
372
|
-
if (
|
|
373
|
-
|
|
401
|
+
if (dryRun) {
|
|
402
|
+
if (scope === 'project' && !cleanupLegacyGlobal) {
|
|
403
|
+
printProjectScopeGuidance();
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
printInstallPlan({ scope, homeDir, withDocling, noLaunchAgent: argSet.has('--no-launch-agent'), cleanupLegacyGlobal });
|
|
374
407
|
return;
|
|
375
408
|
}
|
|
376
409
|
|
|
377
|
-
if (
|
|
378
|
-
|
|
410
|
+
if (cleanupLegacyGlobal) {
|
|
411
|
+
runLegacyGlobalCleanup({ homeDir, dryRun: false });
|
|
412
|
+
if (scope === 'project') {
|
|
413
|
+
printProjectScopeGuidance();
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
if (scope === 'project') {
|
|
419
|
+
printProjectScopeGuidance();
|
|
379
420
|
return;
|
|
380
421
|
}
|
|
381
422
|
|
|
@@ -415,6 +456,28 @@ export async function runSetup({ rootDir = ROOT_DIR, args = [], homeDir = HOME }
|
|
|
415
456
|
if (cassInstall.installCommand) console.log(` Install with: ${cassInstall.installCommand}`);
|
|
416
457
|
}
|
|
417
458
|
|
|
459
|
+
try {
|
|
460
|
+
if (hasDesktopSource(ROOT_DIR)) {
|
|
461
|
+
const built = buildDesktopBinary({ repoRoot: ROOT_DIR, env: process.env });
|
|
462
|
+
if (built.ok) {
|
|
463
|
+
console.log(`Desktop binary: built from source → ${built.binaryPath}`);
|
|
464
|
+
} else {
|
|
465
|
+
console.log(`Desktop binary: build skipped (${built.reason}) — runs on first \`construct chat\``);
|
|
466
|
+
}
|
|
467
|
+
} else {
|
|
468
|
+
const desktop = await downloadDesktopBinary({ homeDir, env: process.env });
|
|
469
|
+
if (desktop.status === 'downloaded') {
|
|
470
|
+
console.log(`Desktop binary: ${desktop.version} → ${desktop.dest}`);
|
|
471
|
+
} else if (desktop.status === 'failed') {
|
|
472
|
+
console.log(`Desktop binary: download failed (${desktop.reason}) — run \`construct install --scope=user\` again to retry`);
|
|
473
|
+
} else {
|
|
474
|
+
console.log(`Desktop binary: ${desktop.reason}`);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
} catch (err) {
|
|
478
|
+
console.log(`Desktop binary: skipped (${err?.message || 'unknown error'})`);
|
|
479
|
+
}
|
|
480
|
+
|
|
418
481
|
const telemetryResult = process.env.CONSTRUCT_TELEMETRY_URL
|
|
419
482
|
? { status: 'configured', note: `remote export configured (${process.env.CONSTRUCT_TELEMETRY_URL})` }
|
|
420
483
|
: { status: 'local', note: 'local JSONL traces in .cx/traces; remote export optional' };
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
* shape: YAML frontmatter (structured metadata) + markdown body (the authored
|
|
6
6
|
* prose), mirroring skills/roles/*.md. This validates the frontmatter and the
|
|
7
7
|
* canonical-section contract, and gates the highest-value invariant — the
|
|
8
|
-
* frontmatter `perspective{}`
|
|
9
|
-
*
|
|
8
|
+
* frontmatter `perspective{}` is the sole source of truth (ADR-0037); registry.json
|
|
9
|
+
* carries routing metadata only.
|
|
10
10
|
*
|
|
11
11
|
* A file with no frontmatter is treated as not-yet-converted: reported, never an
|
|
12
12
|
* error, so the linter can run across a half-migrated corpus. Frontmatter is the
|
|
@@ -55,11 +55,6 @@ function hasHeading(body, title) {
|
|
|
55
55
|
return false;
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
function samePerspective(a, b) {
|
|
59
|
-
if (!a || !b) return false;
|
|
60
|
-
return PERSPECTIVE_FIELDS.every((f) => String(a[f] ?? '').trim() === String(b[f] ?? '').trim());
|
|
61
|
-
}
|
|
62
|
-
|
|
63
58
|
/**
|
|
64
59
|
* Validate one prompt file's content against the hybrid format.
|
|
65
60
|
*
|
|
@@ -103,9 +98,6 @@ export function validatePromptContent({ content, id = '(prompt)', registryEntry
|
|
|
103
98
|
for (const f of PERSPECTIVE_FIELDS) {
|
|
104
99
|
if (!persp[f] || !String(persp[f]).trim()) errors.push(`${id}: perspective.${f} is required and must be non-empty`);
|
|
105
100
|
}
|
|
106
|
-
if (registryEntry?.perspective && !samePerspective(persp, registryEntry.perspective)) {
|
|
107
|
-
errors.push(`${id}: perspective{} drifts from registry.json — they must match while both exist (frontmatter is the source of truth; update the registry)`);
|
|
108
|
-
}
|
|
109
101
|
} else if (frontmatter.perspective != null) {
|
|
110
102
|
errors.push(`${id}: perspective must be an object with ${PERSPECTIVE_FIELDS.join(', ')}`);
|
|
111
103
|
}
|
|
@@ -160,3 +152,20 @@ function loadRegistry(rootDir) {
|
|
|
160
152
|
return { specialists: [] };
|
|
161
153
|
}
|
|
162
154
|
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Resolve a specialist's perspective from its prompt frontmatter.
|
|
158
|
+
* @param {string} agentName - registry name or cx-prefixed id
|
|
159
|
+
* @returns {object|null}
|
|
160
|
+
*/
|
|
161
|
+
export function resolvePerspectiveFromPrompt(agentName, { rootDir = process.cwd(), registry } = {}) {
|
|
162
|
+
const reg = registry ?? loadRegistry(rootDir);
|
|
163
|
+
const normalized = String(agentName ?? '').trim().replace(/^cx-/, '');
|
|
164
|
+
const entry = (reg.specialists ?? []).find((s) => s.name === normalized);
|
|
165
|
+
if (!entry?.promptFile) return null;
|
|
166
|
+
const filePath = path.join(rootDir, entry.promptFile);
|
|
167
|
+
if (!fs.existsSync(filePath)) return null;
|
|
168
|
+
const { frontmatter } = splitFrontmatter(fs.readFileSync(filePath, 'utf8'));
|
|
169
|
+
const persp = frontmatter?.perspective;
|
|
170
|
+
return persp && typeof persp === 'object' ? persp : null;
|
|
171
|
+
}
|