@geraldmaron/construct 1.0.0 → 1.0.2
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/.env.example +9 -6
- package/LICENSE +201 -21
- package/README.md +132 -257
- package/agents/contracts.json +387 -0
- package/agents/prompts/cx-accessibility.md +8 -0
- package/agents/prompts/cx-ai-engineer.md +92 -0
- package/agents/prompts/cx-architect.md +10 -2
- package/agents/prompts/cx-business-strategist.md +13 -0
- package/agents/prompts/cx-data-analyst.md +80 -0
- package/agents/prompts/cx-data-engineer.md +4 -0
- package/agents/prompts/cx-debugger.md +8 -0
- package/agents/prompts/cx-designer.md +19 -0
- package/agents/prompts/cx-devil-advocate.md +4 -0
- package/agents/prompts/cx-docs-keeper.md +128 -0
- package/agents/prompts/cx-engineer.md +13 -0
- package/agents/prompts/cx-evaluator.md +4 -0
- package/agents/prompts/cx-explorer.md +12 -0
- package/agents/prompts/cx-legal-compliance.md +13 -0
- package/agents/prompts/cx-operations.md +13 -0
- package/agents/prompts/cx-orchestrator.md +107 -4
- package/agents/prompts/cx-platform-engineer.md +75 -0
- package/agents/prompts/cx-product-manager.md +8 -0
- package/agents/prompts/cx-qa.md +100 -0
- package/agents/prompts/cx-rd-lead.md +13 -0
- package/agents/prompts/cx-release-manager.md +8 -0
- package/agents/prompts/cx-researcher.md +21 -1
- package/agents/prompts/cx-reviewer.md +8 -0
- package/agents/prompts/cx-security.md +104 -0
- package/agents/prompts/cx-sre.md +104 -0
- package/agents/prompts/cx-test-automation.md +4 -0
- package/agents/prompts/cx-trace-reviewer.md +7 -3
- package/agents/prompts/cx-ux-researcher.md +4 -0
- package/agents/registry.json +365 -90
- package/agents/role-manifests.json +217 -0
- package/bin/construct +3345 -141
- package/bin/construct-postinstall.mjs +87 -0
- package/commands/build/feature.md +6 -6
- package/commands/plan/feature.md +6 -5
- package/commands/plan/requirements.md +1 -1
- package/commands/ship/status.md +1 -1
- package/commands/work/drive.md +3 -3
- package/commands/work/optimize-prompts.md +3 -3
- package/db/{migrations → schema}/001_init.sql +2 -0
- package/db/schema/002_pgvector.sql +182 -0
- package/db/schema/003_intake.sql +47 -0
- package/examples/README.md +85 -0
- package/examples/internal/roles/architect/bad/clever-plan-without-contracts.md +30 -0
- package/examples/internal/roles/architect/golden/explicit-tradeoff-before-plan.md +23 -0
- package/examples/internal/roles/engineer/bad/speculative-abstraction.md +30 -0
- package/examples/internal/roles/engineer/golden/read-before-write.md +28 -0
- package/examples/internal/roles/orchestrator/bad/everything-becomes-multi-agent.md +29 -0
- package/examples/internal/roles/orchestrator/golden/minimal-dispatch.md +22 -0
- package/examples/internal/roles/qa/bad/coverage-theater.md +30 -0
- package/examples/internal/roles/qa/golden/regression-gate.md +23 -0
- package/examples/internal/roles/reviewer/bad/lgtm-without-verification.md +29 -0
- package/examples/internal/roles/reviewer/golden/find-structural-risk-first.md +28 -0
- package/examples/personas/construct/adversarial/ignore-instruction-to-skip-approval.md +22 -0
- package/examples/personas/construct/bad/commit-without-approval.md +30 -0
- package/examples/personas/construct/boundary/blocked-needs-main-input.md +29 -0
- package/examples/personas/construct/golden/branch-approval-before-mutation.md +36 -0
- package/examples/personas/construct/golden/focused-direct-answer.md +28 -0
- package/examples/provider-plugin/README.md +34 -0
- package/examples/provider-plugin/index.mjs +74 -0
- package/examples/provider-plugin/package.json +15 -0
- package/examples/seed-observations/README.md +38 -0
- package/examples/seed-observations/anti-patterns.md +44 -0
- package/examples/seed-observations/decisions.md +36 -0
- package/examples/seed-observations/patterns.md +42 -0
- package/lib/agent-contracts-enforce.mjs +158 -0
- package/lib/agent-contracts.mjs +231 -0
- package/lib/agents/postconditions.mjs +126 -0
- package/lib/agents/schema.mjs +124 -0
- package/lib/artifact-capture.mjs +183 -0
- package/lib/audit-trail.mjs +149 -0
- package/lib/auto-docs.mjs +245 -65
- package/lib/beads/auto-close.mjs +126 -0
- package/lib/beads/drift.mjs +171 -0
- package/lib/beads-automation.mjs +542 -0
- package/lib/beads-client.mjs +518 -0
- package/lib/beads-lock.mjs +377 -0
- package/lib/beads-optimistic.mjs +365 -0
- package/lib/bootstrap/built-ins.mjs +136 -0
- package/lib/bootstrap/lazy-install.mjs +161 -0
- package/lib/bootstrap/resources.mjs +120 -0
- package/lib/bootstrap.mjs +105 -0
- package/lib/cache-governor.js +213 -0
- package/lib/cache-strategy-anthropic.js +62 -0
- package/lib/cache-strategy-google.js +79 -0
- package/lib/cache-strategy-none.js +30 -0
- package/lib/cache-strategy-openai.js +48 -0
- package/lib/cache-strategy.js +91 -0
- package/lib/claude-allow.mjs +149 -0
- package/lib/cli-commands.mjs +413 -258
- package/lib/codex-config.mjs +3 -1
- package/lib/comment-lint.mjs +55 -6
- package/lib/completions.mjs +21 -6
- package/lib/config/alias.mjs +56 -0
- package/lib/config/project-config.mjs +335 -0
- package/lib/config/schema.mjs +159 -0
- package/lib/context-router.mjs +308 -0
- package/lib/cost-ledger.mjs +177 -0
- package/lib/cost.mjs +171 -10
- package/lib/dashboard-static.mjs +158 -0
- package/lib/deployment-mode.mjs +86 -0
- package/lib/deprecate.mjs +49 -0
- package/lib/dispatch-batch.js +183 -0
- package/lib/distill.mjs +21 -8
- package/lib/doc-stamp.mjs +164 -0
- package/lib/doc-verify.mjs +119 -0
- package/lib/docs-routing.mjs +89 -0
- package/lib/docs-verify.mjs +417 -0
- package/lib/doctor/audit.mjs +71 -0
- package/lib/doctor/cli.mjs +99 -0
- package/lib/doctor/escalate.mjs +29 -0
- package/lib/doctor/index.mjs +140 -0
- package/lib/doctor/report.mjs +170 -0
- package/lib/doctor/watchers/bd-watch.mjs +117 -0
- package/lib/doctor/watchers/cost.mjs +130 -0
- package/lib/doctor/watchers/disk.mjs +122 -0
- package/lib/doctor/watchers/handoffs.mjs +33 -0
- package/lib/doctor/watchers/process-pressure.mjs +60 -0
- package/lib/doctor/watchers/service-health.mjs +188 -0
- package/lib/document-extract.mjs +288 -0
- package/lib/document-ingest.mjs +230 -0
- package/lib/drop.mjs +282 -0
- package/lib/embed/approval-queue.mjs +176 -0
- package/lib/embed/artifact.mjs +349 -0
- package/lib/embed/authority-guard.mjs +155 -0
- package/lib/embed/cli.mjs +408 -0
- package/lib/embed/config.mjs +355 -0
- package/lib/embed/conflict-detection.mjs +264 -0
- package/lib/embed/customer-profiles.mjs +480 -0
- package/lib/embed/daemon.mjs +1309 -0
- package/lib/embed/demand-fetch.mjs +449 -0
- package/lib/embed/docs-lifecycle.mjs +349 -0
- package/lib/embed/inbox-live-watcher.mjs +119 -0
- package/lib/embed/inbox.mjs +343 -0
- package/lib/embed/intake-metrics.mjs +190 -0
- package/lib/embed/jobs/vector-sync.mjs +198 -0
- package/lib/embed/notifications.mjs +75 -0
- package/lib/embed/output.mjs +79 -0
- package/lib/embed/providers/github.mjs +295 -0
- package/lib/embed/providers/jira.mjs +192 -0
- package/lib/embed/providers/linear.mjs +186 -0
- package/lib/embed/providers/registry.mjs +115 -0
- package/lib/embed/providers/slack.mjs +203 -0
- package/lib/embed/recommendation-store.mjs +378 -0
- package/lib/embed/roadmap.mjs +374 -0
- package/lib/embed/role-framing.mjs +110 -0
- package/lib/embed/scheduler.mjs +99 -0
- package/lib/embed/semantic.mjs +325 -0
- package/lib/embed/snapshot.mjs +191 -0
- package/lib/embed/supervision.mjs +235 -0
- package/lib/embed/target-resolver.mjs +186 -0
- package/lib/embed/worker.mjs +62 -0
- package/lib/embed/workspaces.mjs +297 -0
- package/lib/engine/chunker-headings.mjs +110 -0
- package/lib/engine/compressor-heuristic.mjs +100 -0
- package/lib/engine/consolidate.mjs +287 -0
- package/lib/engine/contracts.mjs +126 -0
- package/lib/engine/defaults.mjs +129 -0
- package/lib/engine/eval-retrieval.mjs +148 -0
- package/lib/engine/fuser-rrf.mjs +62 -0
- package/lib/engine/index.mjs +37 -0
- package/lib/engine/registry.mjs +146 -0
- package/lib/engine/reranker-mmr.mjs +90 -0
- package/lib/engine/tokens.mjs +77 -0
- package/lib/entity-store.mjs +280 -0
- package/lib/env-config.mjs +69 -4
- package/lib/evals/retrieval-bench.mjs +159 -0
- package/lib/evaluator-optimizer.mjs +317 -0
- package/lib/features.mjs +159 -29
- package/lib/gates-audit.mjs +236 -0
- package/lib/git-hooks/prepare-commit-msg +58 -0
- package/lib/handoffs/cleanup.mjs +159 -0
- package/lib/handoffs/contract.mjs +162 -0
- package/lib/handoffs/inventory.mjs +120 -0
- package/lib/headhunt.mjs +28 -47
- package/lib/health-check.mjs +399 -0
- package/lib/hook-health.mjs +442 -0
- package/lib/hooks/_lib/log.mjs +82 -0
- package/lib/hooks/adaptive-lint.mjs +26 -2
- package/lib/hooks/agent-tracker.mjs +171 -14
- package/lib/hooks/audit-reads.mjs +109 -0
- package/lib/hooks/audit-trail.mjs +153 -0
- package/lib/hooks/bash-output-logger.mjs +71 -0
- package/lib/hooks/block-no-verify.mjs +41 -0
- package/lib/hooks/ci-status-check.mjs +82 -0
- package/lib/hooks/comment-lint.mjs +29 -7
- package/lib/hooks/config-protection.mjs +39 -18
- package/lib/hooks/context-watch.mjs +137 -0
- package/lib/hooks/context-window-recovery.mjs +4 -20
- package/lib/hooks/dep-audit.mjs +16 -0
- package/lib/hooks/doc-coupling-check.mjs +80 -0
- package/lib/hooks/edit-accumulator.mjs +3 -0
- package/lib/hooks/edit-error-recovery.mjs +3 -0
- package/lib/hooks/edit-guard.mjs +45 -4
- package/lib/hooks/env-check.mjs +3 -0
- package/lib/hooks/guard-bash.mjs +58 -1
- package/lib/hooks/mcp-audit.mjs +18 -20
- package/lib/hooks/mcp-health-check.mjs +36 -0
- package/lib/hooks/model-fallback.mjs +42 -56
- package/lib/hooks/policy-engine.mjs +209 -0
- package/lib/hooks/post-merge-docs-check.mjs +63 -0
- package/lib/hooks/pre-compact.mjs +4 -24
- package/lib/hooks/pre-push-gate.mjs +200 -37
- package/lib/hooks/proactive-activation.mjs +284 -0
- package/lib/hooks/read-tracker.mjs +27 -4
- package/lib/hooks/readme-age-check.mjs +77 -0
- package/lib/hooks/registry-sync.mjs +12 -5
- package/lib/hooks/scan-secrets.mjs +50 -7
- package/lib/hooks/session-optimize.mjs +311 -0
- package/lib/hooks/session-start.mjs +287 -28
- package/lib/hooks/stop-notify.mjs +236 -96
- package/lib/hooks/stop-typecheck.mjs +13 -1
- package/lib/hooks/test-watch.mjs +68 -0
- package/lib/host-capabilities.mjs +31 -10
- package/lib/init-docs.mjs +731 -291
- package/lib/init-unified.mjs +1000 -0
- package/lib/init-update.mjs +168 -0
- package/lib/init.mjs +107 -0
- package/lib/install/first-invocation.mjs +119 -0
- package/lib/install/stage-project.mjs +69 -0
- package/lib/intake/classify.mjs +278 -0
- package/lib/intake/feedback.mjs +273 -0
- package/lib/intake/filesystem-queue.mjs +158 -0
- package/lib/intake/intake-config.mjs +132 -0
- package/lib/intake/postgres-queue.mjs +198 -0
- package/lib/intake/prepare.mjs +139 -0
- package/lib/intake/queue.mjs +83 -0
- package/lib/intake/session-prelude.mjs +50 -0
- package/lib/integrations/intake-integrations.mjs +740 -0
- package/lib/intent-classifier.mjs +253 -0
- package/lib/knowledge/layout.mjs +72 -0
- package/lib/knowledge/rag.mjs +331 -0
- package/lib/knowledge/search.mjs +315 -0
- package/lib/knowledge/trends.mjs +261 -0
- package/lib/logger.mjs +85 -0
- package/lib/mcp/broker.mjs +124 -0
- package/lib/mcp/server.mjs +554 -854
- package/lib/mcp/tools/document.mjs +132 -0
- package/lib/mcp/tools/memory.mjs +209 -0
- package/lib/mcp/tools/project.mjs +349 -0
- package/lib/mcp/tools/skills.mjs +409 -0
- package/lib/mcp/tools/storage.mjs +60 -0
- package/lib/mcp/tools/telemetry.mjs +315 -0
- package/lib/mcp/tools/workflow.mjs +112 -0
- package/lib/mcp-catalog.json +54 -3
- package/lib/mcp-manager.mjs +96 -48
- package/lib/mcp-platform-config.mjs +22 -22
- package/lib/memory-stats.mjs +121 -0
- package/lib/mode-commands.mjs +124 -0
- package/lib/model-free-selector.mjs +184 -0
- package/lib/model-pricing.mjs +152 -0
- package/lib/model-registry.mjs +226 -0
- package/lib/model-router.mjs +362 -386
- package/lib/observation-store.mjs +391 -0
- package/lib/ollama-manager.mjs +428 -0
- package/lib/opencode-config.mjs +13 -3
- package/lib/opencode-runtime-plugin.mjs +70 -38
- package/lib/opencode-telemetry.mjs +64 -6
- package/lib/orchestration-policy.mjs +509 -6
- package/lib/overrides/resolver.mjs +207 -0
- package/lib/parity.mjs +147 -0
- package/lib/paths.mjs +33 -0
- package/lib/performance/generate.mjs +212 -0
- package/lib/plugin-registry.mjs +268 -0
- package/lib/policy/engine.mjs +130 -0
- package/lib/policy/unified-gates.mjs +96 -0
- package/lib/project-detection.mjs +129 -0
- package/lib/project-init-shared.mjs +272 -0
- package/lib/project-profile.mjs +447 -0
- package/lib/prompt-composer.js +434 -0
- package/lib/prompt-metadata.mjs +1 -1
- package/lib/provider-capabilities-anthropic.js +44 -0
- package/lib/provider-capabilities-deepseek.js +37 -0
- package/lib/provider-capabilities-generic.js +26 -0
- package/lib/provider-capabilities-google.js +47 -0
- package/lib/provider-capabilities-openai.js +45 -0
- package/lib/provider-capabilities.js +142 -0
- package/lib/providers/atlassian-confluence/index.mjs +103 -0
- package/lib/providers/atlassian-jira/index.mjs +100 -0
- package/lib/providers/auth-manager.mjs +126 -0
- package/lib/providers/circuit-breaker.mjs +124 -0
- package/lib/providers/contract.mjs +93 -0
- package/lib/providers/github/index.mjs +126 -0
- package/lib/providers/registry.mjs +184 -0
- package/lib/providers/salesforce/index.mjs +100 -0
- package/lib/providers/slack/index.mjs +80 -0
- package/lib/reflect.mjs +137 -0
- package/lib/research-lint.mjs +164 -0
- package/lib/resources/budget.mjs +259 -0
- package/lib/role-preload.mjs +21 -6
- package/lib/roles/approval-surface.mjs +54 -0
- package/lib/roles/cli.mjs +118 -0
- package/lib/roles/event-bus.mjs +79 -0
- package/lib/roles/fence.mjs +84 -0
- package/lib/roles/gateway.mjs +260 -0
- package/lib/roles/hook-emit.mjs +37 -0
- package/lib/roles/manifest.mjs +48 -0
- package/lib/roles/router.mjs +27 -0
- package/lib/runtime-pressure.mjs +360 -0
- package/lib/schema-artifact.mjs +134 -0
- package/lib/schema-infer.mjs +551 -0
- package/lib/server/auth.mjs +168 -0
- package/lib/server/chat.mjs +336 -0
- package/lib/server/cors.mjs +77 -0
- package/lib/server/csrf.mjs +91 -0
- package/lib/server/index.mjs +1927 -78
- package/lib/server/insights.mjs +765 -0
- package/lib/server/rate-limit.mjs +91 -0
- package/lib/server/static/assets/index-ab25c707.js +70 -0
- package/lib/server/static/assets/index-f0c80a2b.css +1 -0
- package/lib/server/static/index.html +12 -817
- package/lib/server/telemetry-login.mjs +108 -0
- package/lib/server/webhook.mjs +510 -0
- package/lib/service-manager.mjs +522 -58
- package/lib/services/pattern-promotion-service.mjs +167 -0
- package/lib/services/telemetry-backend.mjs +178 -0
- package/lib/session-store.mjs +374 -0
- package/lib/setup-prompts.mjs +96 -0
- package/lib/setup.mjs +523 -36
- package/lib/skills-apply.mjs +280 -0
- package/lib/skills-scope.mjs +118 -0
- package/lib/status.mjs +261 -70
- package/lib/storage/admin.mjs +355 -0
- package/lib/storage/backend.mjs +2 -1
- package/lib/storage/backup.mjs +347 -0
- package/lib/storage/embeddings-engine.mjs +133 -0
- package/lib/storage/embeddings-legacy.mjs +85 -0
- package/lib/storage/embeddings-local.mjs +108 -0
- package/lib/storage/embeddings-ollama.mjs +78 -0
- package/lib/storage/embeddings-openai.mjs +85 -0
- package/lib/storage/embeddings.mjs +92 -33
- package/lib/storage/file-lock.mjs +130 -0
- package/lib/storage/fusion.mjs +95 -0
- package/lib/storage/hybrid-query.mjs +34 -27
- package/lib/storage/migrations.mjs +187 -0
- package/lib/storage/postgres-backup.mjs +124 -0
- package/lib/storage/sql-store.mjs +5 -15
- package/lib/storage/state-source.mjs +12 -13
- package/lib/storage/store-version.mjs +115 -0
- package/lib/storage/sync.mjs +144 -35
- package/lib/storage/unified-storage.mjs +550 -0
- package/lib/storage/vector-client.mjs +286 -0
- package/lib/storage/vector-store.mjs +71 -30
- package/lib/task-graph/generate.mjs +135 -0
- package/lib/task-graph/schema.mjs +81 -0
- package/lib/task-graph/store.mjs +71 -0
- package/lib/telemetry/backends/local.mjs +62 -0
- package/lib/telemetry/backends/{langfuse.mjs → remote.mjs} +27 -14
- package/lib/telemetry/backfill.mjs +180 -0
- package/lib/telemetry/eval-datasets.mjs +203 -0
- package/lib/telemetry/{langfuse-ingest.mjs → ingest.mjs} +26 -20
- package/lib/telemetry/intent-verifications.mjs +86 -0
- package/lib/telemetry/llm-judge.mjs +350 -0
- package/lib/telemetry/model-pricing-catalog.mjs +557 -0
- package/lib/telemetry/setup.mjs +151 -0
- package/lib/telemetry/skill-calls.mjs +78 -0
- package/lib/telemetry/team-rollup.mjs +4 -4
- package/lib/token-engine.js +117 -0
- package/lib/token-estimator-anthropic.js +15 -0
- package/lib/token-estimator-deepseek.js +13 -0
- package/lib/token-estimator-default.js +13 -0
- package/lib/token-estimator-google.js +13 -0
- package/lib/token-estimator-openai.js +13 -0
- package/lib/toolkit-env.mjs +1 -1
- package/lib/tty-prompts.mjs +211 -0
- package/lib/uninstall/uninstall.mjs +423 -0
- package/lib/update.mjs +115 -0
- package/lib/upgrade.mjs +141 -0
- package/lib/validator.mjs +51 -2
- package/lib/validators/skills.mjs +142 -0
- package/lib/wireframe.mjs +422 -0
- package/lib/worker/entrypoint.mjs +241 -0
- package/lib/worker/evidence.mjs +107 -0
- package/lib/worker/run.mjs +154 -0
- package/lib/worker/trace.mjs +182 -0
- package/lib/workflow-state.mjs +14 -18
- package/package.json +21 -8
- package/personas/construct.md +53 -51
- package/platforms/claude/CLAUDE.md +1 -1
- package/platforms/claude/settings.template.json +115 -68
- package/platforms/opencode/config.template.json +3 -7
- package/rules/common/agents.md +11 -38
- package/rules/common/beads-hygiene.md +75 -0
- package/rules/common/code-review.md +11 -100
- package/rules/common/coding-style.md +5 -3
- package/rules/common/comments.md +30 -111
- package/rules/common/commit-approval.md +53 -0
- package/rules/common/cx-agent-routing.md +1 -0
- package/rules/common/development-workflow.md +23 -41
- package/rules/common/doc-ownership.md +54 -0
- package/rules/common/efficiency.md +57 -0
- package/rules/common/framing.md +76 -0
- package/rules/common/git-workflow.md +2 -4
- package/rules/common/patterns.md +3 -7
- package/rules/common/performance.md +15 -31
- package/rules/common/release-gates.md +69 -0
- package/rules/common/research.md +107 -0
- package/rules/common/security.md +6 -6
- package/rules/common/skill-composition.md +67 -0
- package/rules/common/testing.md +15 -27
- package/rules/golang/hooks.md +0 -4
- package/rules/policy/bootstrap.yaml +11 -0
- package/rules/policy/drive.yaml +8 -0
- package/rules/policy/task.yaml +9 -0
- package/rules/policy/workflow.yaml +9 -0
- package/rules/python/hooks.md +0 -4
- package/rules/swift/hooks.md +0 -4
- package/rules/typescript/hooks.md +0 -4
- package/rules/web/hooks.md +0 -2
- package/{sync-agents.mjs → scripts/sync-agents.mjs} +306 -61
- package/skills/ai/prompt-optimizer.md +7 -7
- package/skills/compliance/ai-disclosure.md +58 -0
- package/skills/compliance/data-privacy.md +46 -0
- package/skills/compliance/license-audit.md +40 -0
- package/skills/compliance/regulatory-review.md +61 -0
- package/skills/docs/document-ingest-workflow.md +52 -0
- package/skills/docs/evidence-ingest-workflow.md +9 -0
- package/skills/docs/init-docs.md +51 -18
- package/skills/docs/product-intelligence-workflow.md +12 -0
- package/skills/docs/product-signal-workflow.md +4 -0
- package/skills/docs/research-workflow.md +23 -8
- package/skills/docs/runbook-workflow.md +1 -1
- package/skills/operating/orchestration-reference.md +151 -0
- package/skills/roles/architect.md +5 -0
- package/skills/roles/engineer.md +32 -0
- package/skills/roles/operator.md +12 -0
- package/skills/roles/reviewer.md +23 -0
- package/skills/routing.md +1 -1
- package/templates/devcontainer/Dockerfile.devcontainer +38 -0
- package/templates/devcontainer/devcontainer.json +31 -0
- package/templates/distribution/bootstrap.ps1 +142 -0
- package/templates/distribution/bootstrap.sh +196 -0
- package/templates/distribution/run.mjs +187 -0
- package/templates/docs/adr.md +44 -8
- package/templates/docs/changelog-entry.md +43 -0
- package/templates/docs/construct_guide.md +149 -0
- package/templates/docs/evidence-brief.md +2 -2
- package/templates/docs/meta-prd.md +140 -19
- package/templates/docs/onboarding.md +57 -0
- package/templates/docs/prd.md +159 -15
- package/templates/docs/research-brief.md +4 -4
- package/templates/homebrew/construct.rb +67 -0
- package/langfuse/docker-compose.yml +0 -82
- package/lib/eval-harness.mjs +0 -59
- package/lib/hooks/bootstrap-guard.mjs +0 -90
- package/lib/hooks/console-warn.mjs +0 -43
- package/lib/hooks/continuation-enforcer.mjs +0 -72
- package/lib/hooks/drive-guard.mjs +0 -89
- package/lib/hooks/mcp-task-scope.mjs +0 -47
- package/lib/hooks/task-completed-guard.mjs +0 -43
- package/lib/hooks/teammate-idle-guard.mjs +0 -54
- package/lib/hooks/workflow-guard.mjs +0 -62
- package/lib/prompt-composer.mjs +0 -196
- package/lib/review.mjs +0 -429
- package/lib/server/static/app.js +0 -841
- package/lib/telemetry/langfuse-model-sync.mjs +0 -270
- package/rules/common/hooks.md +0 -35
- package/rules/zh/README.md +0 -113
- package/rules/zh/agents.md +0 -55
- package/rules/zh/code-review.md +0 -129
- package/rules/zh/coding-style.md +0 -53
- package/rules/zh/development-workflow.md +0 -49
- package/rules/zh/git-workflow.md +0 -29
- package/rules/zh/hooks.md +0 -35
- package/rules/zh/patterns.md +0 -36
- package/rules/zh/performance.md +0 -60
- package/rules/zh/security.md +0 -34
- package/rules/zh/testing.md +0 -34
package/lib/setup.mjs
CHANGED
|
@@ -1,20 +1,35 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* lib/setup.mjs —
|
|
3
|
+
* lib/setup.mjs — Interactive first-run setup wizard for Construct.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
5
|
+
* Guides users through provider selection, API key entry, and model tier
|
|
6
|
+
* assignment. Writes the resulting config to ~/.cx/env and optionally to
|
|
7
|
+
* project-level .env. Invoked by `construct setup` and on first init.
|
|
6
8
|
*/
|
|
7
9
|
|
|
8
10
|
import fs from 'node:fs';
|
|
9
11
|
import os from 'node:os';
|
|
10
12
|
import path from 'node:path';
|
|
11
|
-
import { spawnSync } from 'node:child_process';
|
|
13
|
+
import { spawn, spawnSync } from 'node:child_process';
|
|
12
14
|
|
|
13
15
|
import { ensureUserConfigDir, getUserEnvPath, writeEnvValues } from './env-config.mjs';
|
|
16
|
+
import { DEFAULT_WORKSPACE_PATH, WORKSPACE_DOCS_LANES } from './embed/config.mjs';
|
|
17
|
+
import {
|
|
18
|
+
DEFAULT_DEPLOYMENT_MODE,
|
|
19
|
+
DEPLOYMENT_MODE_ENV_KEY,
|
|
20
|
+
getDeploymentMode,
|
|
21
|
+
} from './deployment-mode.mjs';
|
|
14
22
|
import { getCanonicalOpenCodeConfigPath, readOpenCodeConfig, writeOpenCodeConfig } from './opencode-config.mjs';
|
|
15
23
|
import { syncFileStateToSql } from './storage/sync.mjs';
|
|
16
24
|
import { createSqlClient, closeSqlClient } from './storage/backend.mjs';
|
|
17
|
-
import {
|
|
25
|
+
import { getEmbeddingModelInfo, warmupEmbeddingModel } from './storage/embeddings-engine.mjs';
|
|
26
|
+
import { restoreConstructDb } from './storage/postgres-backup.mjs';
|
|
27
|
+
import { buildPressureGuardValues, installPressureGuardLaunchAgent, loadPressureGuardLaunchAgent } from './runtime-pressure.mjs';
|
|
28
|
+
import {
|
|
29
|
+
isRemoteTelemetry,
|
|
30
|
+
startManagedServices,
|
|
31
|
+
} from './services/telemetry-backend.mjs';
|
|
32
|
+
import { consentToInstall } from './setup-prompts.mjs';
|
|
18
33
|
|
|
19
34
|
const ROOT_DIR = path.resolve(import.meta.dirname, '..');
|
|
20
35
|
const HOME = os.homedir();
|
|
@@ -35,9 +50,11 @@ What it does:
|
|
|
35
50
|
- ensures OpenCode config exists
|
|
36
51
|
- configures managed defaults for local vector retrieval
|
|
37
52
|
- starts local Postgres with Docker when available
|
|
38
|
-
-
|
|
39
|
-
-
|
|
53
|
+
- checks required runtime tools and installs cm and cass when available
|
|
54
|
+
- wires Memory, GitHub, and telemetry configuration
|
|
55
|
+
- runs construct sync (which also regenerates AUTO docs regions)
|
|
40
56
|
- runs construct doctor
|
|
57
|
+
- detects the project tech stack and writes .cx/project-profile.json
|
|
41
58
|
|
|
42
59
|
Use --yes to run without prompts and accept detected environment defaults.`);
|
|
43
60
|
}
|
|
@@ -66,20 +83,32 @@ export function defaultVectorIndexPath(homeDir = HOME) {
|
|
|
66
83
|
return path.join(homeDir, '.construct', 'vector', 'index.json');
|
|
67
84
|
}
|
|
68
85
|
|
|
69
|
-
export function buildManagedSetupValues({ homeDir = HOME, env = process.env, databaseUrl = '' } = {}) {
|
|
86
|
+
export async function buildManagedSetupValues({ homeDir = HOME, env = process.env, databaseUrl = '' } = {}) {
|
|
87
|
+
const modelInfo = await getEmbeddingModelInfo({ env });
|
|
88
|
+
|
|
89
|
+
// Local telemetry backend
|
|
70
90
|
const values = {
|
|
71
|
-
CONSTRUCT_TRACE_BACKEND: env.CONSTRUCT_TRACE_BACKEND || '
|
|
72
|
-
LANGFUSE_BASEURL: env.LANGFUSE_BASEURL || 'https://cloud.langfuse.com',
|
|
91
|
+
CONSTRUCT_TRACE_BACKEND: env.CONSTRUCT_TRACE_BACKEND || 'remote',
|
|
73
92
|
CONSTRUCT_VECTOR_INDEX_PATH: env.CONSTRUCT_VECTOR_INDEX_PATH || defaultVectorIndexPath(homeDir),
|
|
74
|
-
CONSTRUCT_VECTOR_MODEL: env.CONSTRUCT_VECTOR_MODEL ||
|
|
93
|
+
CONSTRUCT_VECTOR_MODEL: env.CONSTRUCT_VECTOR_MODEL || modelInfo.model,
|
|
94
|
+
...buildPressureGuardValues({ env }),
|
|
75
95
|
};
|
|
96
|
+
// Deployment mode is persisted in construct.config.json — setup never writes
|
|
97
|
+
// CONSTRUCT_DEPLOYMENT_MODE to ~/.construct/config.env. The env var is
|
|
98
|
+
// reserved for ephemeral runtime overrides (CI/scripts via `export …`).
|
|
99
|
+
// Forwarding it here pinned the env value and silently masked any JSON
|
|
100
|
+
// setter that ran later.
|
|
101
|
+
if (env[DEPLOYMENT_MODE_ENV_KEY] && getDeploymentMode(env) !== DEFAULT_DEPLOYMENT_MODE) {
|
|
102
|
+
values[DEPLOYMENT_MODE_ENV_KEY] = getDeploymentMode(env);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (env.CONSTRUCT_TELEMETRY_URL) values.CONSTRUCT_TELEMETRY_URL = env.CONSTRUCT_TELEMETRY_URL;
|
|
106
|
+
if (env.CONSTRUCT_TELEMETRY_PUBLIC_KEY) values.CONSTRUCT_TELEMETRY_PUBLIC_KEY = env.CONSTRUCT_TELEMETRY_PUBLIC_KEY;
|
|
107
|
+
if (env.CONSTRUCT_TELEMETRY_SECRET_KEY) values.CONSTRUCT_TELEMETRY_SECRET_KEY = env.CONSTRUCT_TELEMETRY_SECRET_KEY;
|
|
76
108
|
|
|
77
109
|
const resolvedDatabaseUrl = databaseUrl || env.DATABASE_URL || '';
|
|
78
110
|
if (resolvedDatabaseUrl) values.DATABASE_URL = resolvedDatabaseUrl;
|
|
79
111
|
if (env.CONSTRUCT_VECTOR_URL) values.CONSTRUCT_VECTOR_URL = env.CONSTRUCT_VECTOR_URL;
|
|
80
|
-
if (env.LANGFUSE_PUBLIC_KEY) values.LANGFUSE_PUBLIC_KEY = env.LANGFUSE_PUBLIC_KEY;
|
|
81
|
-
if (env.LANGFUSE_SECRET_KEY) values.LANGFUSE_SECRET_KEY = env.LANGFUSE_SECRET_KEY;
|
|
82
|
-
if (env.LANGFUSE_PROJECT_ID) values.LANGFUSE_PROJECT_ID = env.LANGFUSE_PROJECT_ID;
|
|
83
112
|
return values;
|
|
84
113
|
}
|
|
85
114
|
|
|
@@ -92,7 +121,7 @@ export function writeLocalPostgresCompose(homeDir = HOME) {
|
|
|
92
121
|
fs.mkdirSync(path.dirname(composePath), { recursive: true });
|
|
93
122
|
const content = `services:
|
|
94
123
|
postgres:
|
|
95
|
-
image:
|
|
124
|
+
image: pgvector/pgvector:pg16
|
|
96
125
|
container_name: construct-postgres
|
|
97
126
|
restart: unless-stopped
|
|
98
127
|
environment:
|
|
@@ -117,23 +146,198 @@ volumes:
|
|
|
117
146
|
}
|
|
118
147
|
|
|
119
148
|
function runQuiet(command, args, { env = process.env, spawn = spawnSync } = {}) {
|
|
120
|
-
|
|
121
|
-
encoding: 'utf8',
|
|
122
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
149
|
+
const result = spawn(command, args, {
|
|
123
150
|
env,
|
|
151
|
+
stdio: 'ignore',
|
|
124
152
|
});
|
|
153
|
+
return result;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
async function runAsyncQuiet(command, args, { env = process.env, spawn = spawn } = {}) {
|
|
157
|
+
return new Promise((resolve) => {
|
|
158
|
+
const timeout = setTimeout(() => {
|
|
159
|
+
resolve({ status: -1, signal: 'SIGTERM' }); // treat timeout as error
|
|
160
|
+
}, 5000);
|
|
161
|
+
const child = spawn(command, args, {
|
|
162
|
+
env,
|
|
163
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
164
|
+
});
|
|
165
|
+
let stdout = '';
|
|
166
|
+
let stderr = '';
|
|
167
|
+
child.stdout.on('data', (data) => { stdout += data.toString(); });
|
|
168
|
+
child.stderr.on('data', (data) => { stderr += data.toString(); });
|
|
169
|
+
child.on('close', (code, signal) => {
|
|
170
|
+
clearTimeout(timeout);
|
|
171
|
+
resolve({ status: code || 0, signal, stdout, stderr });
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export function commandExists(command, { env = process.env, spawn = spawnSync } = {}) {
|
|
177
|
+
const checker = process.platform === 'win32' ? 'where' : 'which';
|
|
178
|
+
return runQuiet(checker, [command], { env, spawn }).status === 0;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function summarizeSpawnFailure(result, fallback) {
|
|
182
|
+
return (result.stderr || result.stdout || fallback).trim().split('\n')[0];
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export function ensureCmInstalled({ env = process.env, spawn = spawnSync } = {}) {
|
|
186
|
+
if (commandExists('cm', { env, spawn })) {
|
|
187
|
+
return { status: 'available', message: 'cm already installed.' };
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (commandExists('brew', { env, spawn })) {
|
|
191
|
+
const result = runQuiet('brew', ['install', 'dicklesworthstone/tap/cm'], { env, spawn });
|
|
192
|
+
if (result.status === 0 && commandExists('cm', { env, spawn })) {
|
|
193
|
+
return { status: 'installed', message: 'Installed cm via Homebrew.' };
|
|
194
|
+
}
|
|
195
|
+
return {
|
|
196
|
+
status: 'failed',
|
|
197
|
+
message: summarizeSpawnFailure(result, 'brew install failed'),
|
|
198
|
+
installCommand: 'brew install dicklesworthstone/tap/cm',
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return {
|
|
203
|
+
status: 'missing',
|
|
204
|
+
message: 'Homebrew not available.',
|
|
205
|
+
installCommand: 'brew install dicklesworthstone/tap/cm',
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Version 0.3.0 has a known issue where the FTS-rebuild loop can OOM on large corpora and fail to converge.
|
|
210
|
+
// Version 0.4.0 resolves this. Below this version, we fail closed to avoid the problem.
|
|
211
|
+
const CASS_MIN_VERSION = '0.4.0';
|
|
212
|
+
|
|
213
|
+
function parseSemver(s) {
|
|
214
|
+
const m = String(s || '').match(/(\d+)\.(\d+)\.(\d+)/);
|
|
215
|
+
return m ? [Number(m[1]), Number(m[2]), Number(m[3])] : null;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function semverGte(a, b) {
|
|
219
|
+
const x = parseSemver(a);
|
|
220
|
+
const y = parseSemver(b);
|
|
221
|
+
if (!x || !y) return false;
|
|
222
|
+
for (let i = 0; i < 3; i++) {
|
|
223
|
+
if (x[i] > y[i]) return true;
|
|
224
|
+
if (x[i] < y[i]) return false;
|
|
225
|
+
}
|
|
226
|
+
return true;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function getCassVersion({ env, spawn }) {
|
|
230
|
+
const result = runQuiet('cass', ['--version'], { env, spawn });
|
|
231
|
+
if (result.status !== 0) return null;
|
|
232
|
+
const match = String(result.stdout || '').match(/cass\s+(\d+\.\d+\.\d+)/);
|
|
233
|
+
return match ? match[1] : null;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export function ensureCassInstalled({ env = process.env, spawn = spawnSync } = {}) {
|
|
237
|
+
if (commandExists('cass', { env, spawn })) {
|
|
238
|
+
const version = getCassVersion({ env, spawn });
|
|
239
|
+
if (version && semverGte(version, CASS_MIN_VERSION)) {
|
|
240
|
+
return { status: 'available', message: `cass ${version} already installed.` };
|
|
241
|
+
}
|
|
242
|
+
return {
|
|
243
|
+
status: 'outdated',
|
|
244
|
+
message: `cass ${version || 'unknown'} is older than required ${CASS_MIN_VERSION}. Older versions hit a frankensqlite FTS-rebuild OOM loop on large session corpora (upstream #168, #186, #110, #155).`,
|
|
245
|
+
installCommand: 'brew upgrade dicklesworthstone/tap/cass || brew install dicklesworthstone/tap/cass',
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (commandExists('brew', { env, spawn })) {
|
|
250
|
+
const result = runQuiet('brew', ['install', 'dicklesworthstone/tap/cass'], { env, spawn });
|
|
251
|
+
if (result.status === 0 && commandExists('cass', { env, spawn })) {
|
|
252
|
+
runQuiet('cass', ['index'], { env, spawn });
|
|
253
|
+
return { status: 'installed', message: 'Installed cass via Homebrew and ran cass index.' };
|
|
254
|
+
}
|
|
255
|
+
return {
|
|
256
|
+
status: 'failed',
|
|
257
|
+
message: summarizeSpawnFailure(result, 'brew install failed'),
|
|
258
|
+
installCommand: 'brew install dicklesworthstone/tap/cass && cass index',
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (commandExists('cargo', { env, spawn })) {
|
|
263
|
+
const result = runQuiet('cargo', ['install', 'cass'], { env, spawn });
|
|
264
|
+
if (result.status === 0 && commandExists('cass', { env, spawn })) {
|
|
265
|
+
runQuiet('cass', ['index'], { env, spawn });
|
|
266
|
+
return { status: 'installed', message: 'Installed cass via cargo and ran cass index.' };
|
|
267
|
+
}
|
|
268
|
+
return {
|
|
269
|
+
status: 'failed',
|
|
270
|
+
message: summarizeSpawnFailure(result, 'cargo install failed'),
|
|
271
|
+
installCommand: 'cargo install cass && cass index',
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return {
|
|
276
|
+
status: 'missing',
|
|
277
|
+
message: 'Neither Homebrew nor cargo available.',
|
|
278
|
+
installCommand: 'brew install dicklesworthstone/tap/cass && cass index',
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Platform-aware install hint shown when Docker isn't on PATH. Mirrors
|
|
284
|
+
* what `supabase start` / `prisma init` print: don't auto-install, but
|
|
285
|
+
* point at the canonical option for the user's OS so they have a one-line
|
|
286
|
+
* command to copy.
|
|
287
|
+
*/
|
|
288
|
+
export function dockerInstallHint(platform = process.platform) {
|
|
289
|
+
if (platform === 'darwin') {
|
|
290
|
+
return 'Install Docker Desktop (https://www.docker.com/products/docker-desktop) or a lighter alternative like OrbStack (`brew install orbstack`) or Colima (`brew install colima && colima start`).';
|
|
291
|
+
}
|
|
292
|
+
if (platform === 'win32') {
|
|
293
|
+
return 'Install Docker Desktop for Windows (https://www.docker.com/products/docker-desktop).';
|
|
294
|
+
}
|
|
295
|
+
return 'Install Docker Engine (https://docs.docker.com/engine/install/) — your distribution likely has a `docker` package (apt/dnf/pacman).';
|
|
125
296
|
}
|
|
126
297
|
|
|
127
298
|
export function detectDockerCompose({ env = process.env, spawn = spawnSync } = {}) {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
299
|
+
// Use a timeout wrapper so we don't block forever if Docker daemon is hung/missing
|
|
300
|
+
const timeoutWrap = (fn) => {
|
|
301
|
+
let timedOut = false;
|
|
302
|
+
const timer = setTimeout(() => { timedOut = true; }, 5000);
|
|
303
|
+
try {
|
|
304
|
+
const result = fn();
|
|
305
|
+
clearTimeout(timer);
|
|
306
|
+
return timedOut ? null : result;
|
|
307
|
+
} catch {
|
|
308
|
+
clearTimeout(timer);
|
|
309
|
+
return null;
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
const docker = timeoutWrap(() => runQuiet('docker', ['info'], { env, spawn }));
|
|
314
|
+
if (!docker || docker.status !== 0) return null;
|
|
315
|
+
const compose = timeoutWrap(() => runQuiet('docker', ['compose', 'version'], { env, spawn }));
|
|
316
|
+
if (compose && compose.status === 0) return { command: 'docker', argsPrefix: ['compose'] };
|
|
317
|
+
const dockerCompose = timeoutWrap(() => runQuiet('docker-compose', ['version'], { env, spawn }));
|
|
318
|
+
if (dockerCompose && dockerCompose.status === 0) return { command: 'docker-compose', argsPrefix: [] };
|
|
134
319
|
return null;
|
|
135
320
|
}
|
|
136
321
|
|
|
322
|
+
// Duplicated from service-manager.spawnDetached to avoid the import cycle
|
|
323
|
+
// setup ↔ service-manager (service-manager already imports detectDockerCompose
|
|
324
|
+
// from setup, so the reverse path can't exist).
|
|
325
|
+
|
|
326
|
+
function spawnDetachedForSetup(command, args, homeDir, logFile, options = {}) {
|
|
327
|
+
const logDir = path.join(homeDir, '.construct', 'runtime');
|
|
328
|
+
fs.mkdirSync(logDir, { recursive: true });
|
|
329
|
+
const logPath = path.join(logDir, logFile);
|
|
330
|
+
const fd = fs.openSync(logPath, 'a');
|
|
331
|
+
const child = spawn(command, args, {
|
|
332
|
+
detached: true,
|
|
333
|
+
stdio: ['ignore', fd, fd],
|
|
334
|
+
cwd: options.cwd,
|
|
335
|
+
env: options.env,
|
|
336
|
+
});
|
|
337
|
+
child.unref();
|
|
338
|
+
return { child, logPath };
|
|
339
|
+
}
|
|
340
|
+
|
|
137
341
|
export function startManagedPostgres({ homeDir = HOME, env = process.env, spawn = spawnSync } = {}) {
|
|
138
342
|
const composeRunner = detectDockerCompose({ env, spawn });
|
|
139
343
|
if (!composeRunner) {
|
|
@@ -215,6 +419,46 @@ function ensureOpenCodeConfig() {
|
|
|
215
419
|
return getCanonicalOpenCodeConfigPath();
|
|
216
420
|
}
|
|
217
421
|
|
|
422
|
+
// Wire core.hooksPath to .beads/hooks so the tracked hook scripts (ECC
|
|
423
|
+
// secret-scan, Construct policy gates, BEADS dispatcher) actually fire on
|
|
424
|
+
// git commit/push. Without this, .beads/hooks/* are inert — present in the
|
|
425
|
+
// repo but never executed by git. Idempotent: leaves a user-set value alone
|
|
426
|
+
// rather than clobbering it.
|
|
427
|
+
|
|
428
|
+
function ensureGitHooksPath({ cwd = process.cwd() } = {}) {
|
|
429
|
+
const hooksDir = path.join(cwd, '.beads', 'hooks');
|
|
430
|
+
if (!fs.existsSync(path.join(hooksDir, 'pre-commit'))) {
|
|
431
|
+
return { status: 'skipped', reason: 'no .beads/hooks/pre-commit in this directory' };
|
|
432
|
+
}
|
|
433
|
+
const inGit = spawnSync('git', ['rev-parse', '--is-inside-work-tree'], { cwd, stdio: 'pipe', encoding: 'utf8' });
|
|
434
|
+
if (inGit.status !== 0) {
|
|
435
|
+
return { status: 'skipped', reason: 'not a git working tree' };
|
|
436
|
+
}
|
|
437
|
+
const current = spawnSync('git', ['config', '--get', 'core.hooksPath'], { cwd, stdio: 'pipe', encoding: 'utf8' });
|
|
438
|
+
const currentValue = current.status === 0 ? (current.stdout || '').trim() : '';
|
|
439
|
+
const desired = '.beads/hooks';
|
|
440
|
+
if (currentValue === desired) {
|
|
441
|
+
return { status: 'ok', message: 'core.hooksPath already wired to .beads/hooks' };
|
|
442
|
+
}
|
|
443
|
+
if (currentValue && currentValue !== desired) {
|
|
444
|
+
return {
|
|
445
|
+
status: 'warning',
|
|
446
|
+
message: `core.hooksPath is set to '${currentValue}'. Leaving alone — set to '.beads/hooks' to activate Construct policy gates.`,
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
const result = spawnSync('git', ['config', 'core.hooksPath', desired], { cwd, stdio: 'pipe', encoding: 'utf8' });
|
|
450
|
+
if (result.status !== 0) {
|
|
451
|
+
return {
|
|
452
|
+
status: 'error',
|
|
453
|
+
message: `failed to set core.hooksPath: ${(result.stderr || '').trim() || 'unknown error'}`,
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
return {
|
|
457
|
+
status: 'set',
|
|
458
|
+
message: `core.hooksPath set to .beads/hooks (activates pre-commit secret-scan + Construct policy gates + beads dispatcher)`,
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
|
|
218
462
|
export async function runSetup({ rootDir = ROOT_DIR, args = [], homeDir = HOME } = {}) {
|
|
219
463
|
const argSet = new Set(args);
|
|
220
464
|
const isYes = argSet.has('--yes');
|
|
@@ -235,30 +479,157 @@ export async function runSetup({ rootDir = ROOT_DIR, args = [], homeDir = HOME }
|
|
|
235
479
|
console.log(`OpenCode config: ${opencodePath}`);
|
|
236
480
|
warnIfGlobalCommandIsUnavailable();
|
|
237
481
|
|
|
238
|
-
const
|
|
239
|
-
|
|
240
|
-
|
|
482
|
+
const cmInstall = ensureCmInstalled({ env: process.env });
|
|
483
|
+
if (cmInstall.status === 'installed') {
|
|
484
|
+
console.log('Memory CLI: installed cm via Homebrew');
|
|
485
|
+
} else if (cmInstall.status === 'available') {
|
|
486
|
+
console.log('Memory CLI: cm available');
|
|
487
|
+
} else {
|
|
488
|
+
console.log(`Memory CLI: ${cmInstall.message}`);
|
|
489
|
+
if (cmInstall.installCommand) console.log(` Install with: ${cmInstall.installCommand}`);
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
const cassInstall = ensureCassInstalled({ env: process.env });
|
|
493
|
+
if (cassInstall.status === 'installed') {
|
|
494
|
+
console.log(`Session search: ${cassInstall.message}`);
|
|
495
|
+
} else if (cassInstall.status === 'available') {
|
|
496
|
+
console.log('Session search: cass available');
|
|
497
|
+
} else {
|
|
498
|
+
console.log(`Session search: ${cassInstall.message}`);
|
|
499
|
+
if (cassInstall.installCommand) console.log(` Install with: ${cassInstall.installCommand}`);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
// Local Postgres — consent-driven. Interactive default-yes; --yes accepts
|
|
503
|
+
// without prompting. Skipped when --no-docker, when DATABASE_URL is already
|
|
504
|
+
// set (caller has an external DB), or when user declines.
|
|
505
|
+
|
|
506
|
+
const dockerRunner = detectDockerCompose();
|
|
507
|
+
const dockerAvailable = !skipDocker && Boolean(dockerRunner);
|
|
508
|
+
let serviceResult = {
|
|
509
|
+
status: 'skipped',
|
|
510
|
+
databaseUrl: process.env.DATABASE_URL || '',
|
|
511
|
+
message: skipDocker ? 'Docker service setup skipped by flag.' : 'Docker not detected — skipping local Postgres.',
|
|
512
|
+
};
|
|
513
|
+
|
|
514
|
+
if (dockerAvailable) {
|
|
515
|
+
const pgConsent = await consentToInstall({
|
|
516
|
+
name: 'postgres',
|
|
517
|
+
isYes,
|
|
518
|
+
alreadyConfigured: Boolean(process.env.DATABASE_URL),
|
|
519
|
+
alreadyConfiguredNote: 'DATABASE_URL already set — using external database.',
|
|
520
|
+
envPath,
|
|
521
|
+
});
|
|
522
|
+
if (pgConsent.decision) {
|
|
523
|
+
writeLocalPostgresCompose(homeDir);
|
|
524
|
+
serviceResult = startManagedPostgres({ homeDir, env: process.env });
|
|
525
|
+
console.log(`Postgres: ${serviceResult.status === 'ok' ? 'started locally' : serviceResult.message}`);
|
|
526
|
+
} else {
|
|
527
|
+
console.log(`Postgres: skipped (${pgConsent.note})`);
|
|
528
|
+
}
|
|
529
|
+
} else if (skipDocker) {
|
|
530
|
+
console.log('Postgres: skipped (--no-docker)');
|
|
531
|
+
} else {
|
|
532
|
+
console.log('Postgres: skipped (Docker not detected — local Postgres unavailable)');
|
|
533
|
+
console.log(` ${dockerInstallHint()}`);
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
// Local telemetry backend — starts Postgres via services/docker-compose.yml.
|
|
537
|
+
// Skipped automatically when CONSTRUCT_TELEMETRY_URL points to a remote instance.
|
|
538
|
+
|
|
539
|
+
let telemetryResult = { status: 'skipped', note: 'Docker not available' };
|
|
540
|
+
if (dockerAvailable) {
|
|
541
|
+
const svcConsent = await consentToInstall({
|
|
542
|
+
name: 'telemetry',
|
|
543
|
+
isYes,
|
|
544
|
+
alreadyConfigured: isRemoteTelemetry(process.env.CONSTRUCT_TELEMETRY_URL || ''),
|
|
545
|
+
alreadyConfiguredNote: 'CONSTRUCT_TELEMETRY_URL points to a remote instance — local stack not started.',
|
|
546
|
+
envPath,
|
|
547
|
+
});
|
|
548
|
+
if (svcConsent.decision) {
|
|
549
|
+
try {
|
|
550
|
+
telemetryResult = await startManagedServices({
|
|
551
|
+
rootDir,
|
|
552
|
+
homeDir,
|
|
553
|
+
env: process.env,
|
|
554
|
+
composeRunner: dockerRunner,
|
|
555
|
+
spawnDetached: spawnDetachedForSetup,
|
|
556
|
+
});
|
|
557
|
+
console.log(`Telemetry: ${telemetryResult.status} — ${telemetryResult.note || ''}`);
|
|
558
|
+
} catch (err) {
|
|
559
|
+
telemetryResult = { status: 'error', note: err?.message || 'spin-up failed' };
|
|
560
|
+
console.log(`Telemetry: error — ${telemetryResult.note}`);
|
|
561
|
+
}
|
|
562
|
+
} else {
|
|
563
|
+
telemetryResult = { status: 'skipped', note: svcConsent.note };
|
|
564
|
+
console.log(`Telemetry: skipped (${svcConsent.note})`);
|
|
565
|
+
}
|
|
566
|
+
}
|
|
241
567
|
|
|
242
568
|
fs.mkdirSync(path.dirname(defaultVectorIndexPath(homeDir)), { recursive: true });
|
|
243
|
-
|
|
569
|
+
|
|
570
|
+
// Pre-warm the embedding model so the first agent query doesn't stall on
|
|
571
|
+
// a one-time ONNX download. Same pattern as `supabase start` pre-pulling
|
|
572
|
+
// images and `playwright install` pre-fetching browsers — make the wait
|
|
573
|
+
// visible during setup, not during user-facing work. Best-effort: a
|
|
574
|
+
// download failure here degrades gracefully via the engine's hash fallback
|
|
575
|
+
// and is non-fatal to setup.
|
|
576
|
+
|
|
577
|
+
try {
|
|
578
|
+
const warm = await warmupEmbeddingModel({ env: process.env });
|
|
579
|
+
if (warm.degraded) {
|
|
580
|
+
console.log(`Embeddings: degraded fallback (${warm.fallbackReason || 'model unavailable'}) — semantic search will use the hashing backend`);
|
|
581
|
+
} else {
|
|
582
|
+
console.log(`Embeddings: ${warm.model} ready (${warm.dimensions}d, warmed in ${warm.durationMs}ms)`);
|
|
583
|
+
}
|
|
584
|
+
} catch (err) {
|
|
585
|
+
console.log(`Embeddings: warmup skipped (${err?.message || 'unknown error'}) — model will load on first use`);
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
const hooksResult = ensureGitHooksPath({ cwd: process.cwd() });
|
|
589
|
+
if (hooksResult.status === 'set') console.log(`Git hooks: ${hooksResult.message}`);
|
|
590
|
+
else if (hooksResult.status === 'ok') console.log('Git hooks: wired');
|
|
591
|
+
else if (hooksResult.status === 'warning') console.log(`Git hooks: ${hooksResult.message}`);
|
|
592
|
+
else if (hooksResult.status === 'error') console.log(`Git hooks: ${hooksResult.message}`);
|
|
593
|
+
|
|
594
|
+
// Ensure workspace directory with docs lanes exists
|
|
595
|
+
ensureWorkspace(homeDir);
|
|
596
|
+
|
|
597
|
+
const managedValues = await buildManagedSetupValues({
|
|
244
598
|
homeDir,
|
|
245
599
|
env: process.env,
|
|
246
600
|
databaseUrl: serviceResult.databaseUrl,
|
|
247
601
|
});
|
|
248
602
|
writeEnvValues(envPath, managedValues);
|
|
603
|
+
let pressureGuardAgent = null;
|
|
604
|
+
let pressureGuardLoad = null;
|
|
605
|
+
if (process.platform === 'darwin') {
|
|
606
|
+
pressureGuardAgent = installPressureGuardLaunchAgent({
|
|
607
|
+
homeDir,
|
|
608
|
+
rootDir,
|
|
609
|
+
intervalSeconds: Number(managedValues.CONSTRUCT_PRESSURE_GUARD_INTERVAL_SECONDS || 300),
|
|
610
|
+
nodePath: process.execPath,
|
|
611
|
+
});
|
|
612
|
+
pressureGuardLoad = loadPressureGuardLaunchAgent({ plistPath: pressureGuardAgent.plistPath });
|
|
613
|
+
}
|
|
249
614
|
|
|
250
615
|
if (isYes) {
|
|
251
616
|
console.log('\nManaged setup:');
|
|
617
|
+
console.log(` Deployment mode: ${getDeploymentMode(env) || DEFAULT_DEPLOYMENT_MODE} (set in construct.config.json — runtime env override available via ${DEPLOYMENT_MODE_ENV_KEY})`);
|
|
252
618
|
console.log(` Vector index: ${managedValues.CONSTRUCT_VECTOR_INDEX_PATH}`);
|
|
253
619
|
console.log(` Vector model: ${managedValues.CONSTRUCT_VECTOR_MODEL}`);
|
|
254
620
|
console.log(` Trace backend: ${managedValues.CONSTRUCT_TRACE_BACKEND}`);
|
|
255
|
-
console.log(`
|
|
621
|
+
if (managedValues.CONSTRUCT_TELEMETRY_URL) console.log(` Trace URL: ${managedValues.CONSTRUCT_TELEMETRY_URL}`);
|
|
622
|
+
console.log(` Pressure guard: every ${managedValues.CONSTRUCT_PRESSURE_GUARD_INTERVAL_SECONDS}s, swap threshold ${managedValues.CONSTRUCT_PRESSURE_GUARD_SWAP_GB} GiB`);
|
|
256
623
|
if (serviceResult.status === 'ok') {
|
|
257
624
|
console.log(` Postgres: ${serviceResult.databaseUrl}`);
|
|
258
625
|
console.log(` Compose file: ${serviceResult.composePath}`);
|
|
259
626
|
} else {
|
|
260
627
|
console.log(` Postgres: ${serviceResult.message}`);
|
|
261
628
|
}
|
|
629
|
+
if (pressureGuardAgent?.plistPath) {
|
|
630
|
+
console.log(` LaunchAgent: ${pressureGuardAgent.plistPath}`);
|
|
631
|
+
console.log(` LaunchAgent load: ${pressureGuardLoad?.loaded ? 'active' : pressureGuardLoad?.reason || 'pending manual load'}`);
|
|
632
|
+
}
|
|
262
633
|
|
|
263
634
|
const sqlClient = createSqlClient({ ...process.env, ...managedValues });
|
|
264
635
|
if (sqlClient) {
|
|
@@ -271,12 +642,50 @@ export async function runSetup({ rootDir = ROOT_DIR, args = [], homeDir = HOME }
|
|
|
271
642
|
await waitForSqlReady({ ...process.env, ...managedValues });
|
|
272
643
|
}
|
|
273
644
|
readyClient = createSqlClient({ ...process.env, ...managedValues });
|
|
274
|
-
const migration = await import('node:fs/promises').then((fs) => fs.readFile(new URL('../db/
|
|
645
|
+
const migration = await import('node:fs/promises').then((fs) => fs.readFile(new URL('../db/schema/001_init.sql', import.meta.url), 'utf8'));
|
|
275
646
|
await readyClient.unsafe(migration);
|
|
276
647
|
console.log('\nPostgres schema initialized.');
|
|
648
|
+
|
|
649
|
+
// Restore data from the most recent stash (if any) before syncing.
|
|
650
|
+
if (serviceResult.status === 'ok') {
|
|
651
|
+
const restore = restoreConstructDb({ homeDir });
|
|
652
|
+
if (restore.status === 'restored') {
|
|
653
|
+
console.log(`Construct DB restored from stash: ${restore.stashPath}`);
|
|
654
|
+
} else if (restore.status === 'no-stash') {
|
|
655
|
+
console.log('No stash found — starting with empty construct DB.');
|
|
656
|
+
} else if (restore.status !== 'no-stash') {
|
|
657
|
+
console.log(`Construct DB restore: ${restore.status}`);
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
|
|
277
661
|
const syncResult = await syncFileStateToSql(rootDir, { env: { ...process.env, ...managedValues }, project: 'construct' });
|
|
278
662
|
console.log(`Hybrid storage sync: ${syncResult.status}`);
|
|
279
663
|
if (syncResult.embeddingModel) console.log(`Embedding model: ${syncResult.embeddingModel}`);
|
|
664
|
+
|
|
665
|
+
// When setup runs inside a downstream project (cwd is not the
|
|
666
|
+
// Construct package itself), seed the vector index from the
|
|
667
|
+
// project's existing docs / AGENTS / plan so the agent has
|
|
668
|
+
// context to compare new intake against from day one. No-op
|
|
669
|
+
// when there's nothing project-like in cwd.
|
|
670
|
+
|
|
671
|
+
const projectCwd = process.cwd();
|
|
672
|
+
const isDownstreamProject = projectCwd !== rootDir && (
|
|
673
|
+
fs.existsSync(path.join(projectCwd, 'docs')) ||
|
|
674
|
+
fs.existsSync(path.join(projectCwd, 'AGENTS.md')) ||
|
|
675
|
+
fs.existsSync(path.join(projectCwd, 'package.json'))
|
|
676
|
+
);
|
|
677
|
+
if (isDownstreamProject) {
|
|
678
|
+
try {
|
|
679
|
+
const projectName = path.basename(projectCwd);
|
|
680
|
+
const projectSync = await syncFileStateToSql(projectCwd, {
|
|
681
|
+
env: { ...process.env, ...managedValues },
|
|
682
|
+
project: projectName,
|
|
683
|
+
});
|
|
684
|
+
console.log(`Project corpus indexed: ${projectName} (${projectSync.documentsSynced || 0} docs, ${projectSync.embeddingsSynced || 0} embeddings) — agent can now compare new intake against existing material.`);
|
|
685
|
+
} catch (err) {
|
|
686
|
+
console.log(`Project corpus index skipped: ${err?.message || 'unknown error'} — run \`construct ingest\` later to seed manually.`);
|
|
687
|
+
}
|
|
688
|
+
}
|
|
280
689
|
} catch (error) {
|
|
281
690
|
console.log(`Hybrid storage init failed: ${error?.message || 'unknown error'}`);
|
|
282
691
|
} finally {
|
|
@@ -289,22 +698,82 @@ export async function runSetup({ rootDir = ROOT_DIR, args = [], homeDir = HOME }
|
|
|
289
698
|
if (isYes) {
|
|
290
699
|
runConstruct(['mcp', 'add', 'memory', '--auto'], { optional: true });
|
|
291
700
|
runConstruct(['mcp', 'add', 'github', '--auto'], { optional: true });
|
|
292
|
-
if (!managedValues.LANGFUSE_PUBLIC_KEY || !managedValues.LANGFUSE_SECRET_KEY) {
|
|
293
|
-
console.log('\nLangfuse is prewired but needs account credentials:');
|
|
294
|
-
console.log(' Set LANGFUSE_PUBLIC_KEY and LANGFUSE_SECRET_KEY in ~/.construct/config.env');
|
|
295
|
-
console.log(' Set these in ~/.construct/config.env or run: cd langfuse && docker compose up -d');
|
|
296
|
-
}
|
|
297
|
-
|
|
298
701
|
} else {
|
|
299
702
|
console.log('\nManaged defaults written:');
|
|
703
|
+
console.log(` Deployment mode: ${getDeploymentMode(env) || DEFAULT_DEPLOYMENT_MODE} (set in construct.config.json — runtime env override available via ${DEPLOYMENT_MODE_ENV_KEY})`);
|
|
300
704
|
console.log(` Vector index: ${managedValues.CONSTRUCT_VECTOR_INDEX_PATH}`);
|
|
301
|
-
console.log(` Trace backend: ${managedValues.CONSTRUCT_TRACE_BACKEND} (${managedValues.
|
|
705
|
+
console.log(` Trace backend: ${managedValues.CONSTRUCT_TRACE_BACKEND}${managedValues.CONSTRUCT_TELEMETRY_URL ? ` (${managedValues.CONSTRUCT_TELEMETRY_URL})` : ''}`);
|
|
706
|
+
console.log(` Pressure guard: swap ${managedValues.CONSTRUCT_PRESSURE_GUARD_SWAP_GB} GiB, opencode max ${managedValues.CONSTRUCT_PRESSURE_GUARD_MAX_OPENCODE}`);
|
|
302
707
|
console.log('\nFor unattended setup, including local Postgres when Docker is running:');
|
|
303
708
|
console.log(' construct setup --yes');
|
|
304
709
|
}
|
|
305
710
|
|
|
306
711
|
runConstruct(['sync']);
|
|
307
712
|
runConstruct(['doctor']);
|
|
713
|
+
|
|
714
|
+
// Profile the CWD so per-host skill filtering layers downstream have
|
|
715
|
+
// a cached, host-agnostic signal to work from. Non-fatal on failure.
|
|
716
|
+
try {
|
|
717
|
+
const { detectProjectProfile, writeProfile } = await import('./project-profile.mjs');
|
|
718
|
+
const profile = detectProjectProfile(process.cwd());
|
|
719
|
+
if (profile.tags.length > 0) {
|
|
720
|
+
const profilePath = writeProfile(profile, process.cwd());
|
|
721
|
+
console.log(`\nProject profile: ${profile.tags.join(', ')}`);
|
|
722
|
+
console.log(` Saved to ${profilePath}`);
|
|
723
|
+
console.log(` Run \`construct skills scope\` to see which installed skills apply to this project.`);
|
|
724
|
+
}
|
|
725
|
+
} catch { /* best effort */ }
|
|
726
|
+
|
|
727
|
+
// ── Summary panel ────────────────────────────────────────────────────────
|
|
728
|
+
const setupTs = new Date().toISOString().slice(0, 19).replace(/[:.]/g, '-');
|
|
729
|
+
const setupLogPath = path.join(HOME, '.cx', `setup-${setupTs}.log`);
|
|
730
|
+
try {
|
|
731
|
+
fs.mkdirSync(path.dirname(setupLogPath), { recursive: true });
|
|
732
|
+
const logLines = [
|
|
733
|
+
`Construct setup completed ${new Date().toISOString()}`,
|
|
734
|
+
`Config: ${getUserEnvPath(HOME)}`,
|
|
735
|
+
`OpenCode: ${getCanonicalOpenCodeConfigPath(HOME)}`,
|
|
736
|
+
];
|
|
737
|
+
if (managedValues?.DATABASE_URL) {
|
|
738
|
+
logLines.push(`Database: ${managedValues.DATABASE_URL.replace(/:\/\/[^@]+@/, '://<credentials>@')}`);
|
|
739
|
+
}
|
|
740
|
+
fs.writeFileSync(setupLogPath, logLines.join('\n') + '\n');
|
|
741
|
+
} catch { /* best effort */ }
|
|
742
|
+
|
|
743
|
+
console.log('\n────────────────────────────────────');
|
|
744
|
+
console.log('Setup complete.');
|
|
745
|
+
|
|
746
|
+
// Local-service summary — surfaces running local services. Same pattern
|
|
747
|
+
// as `supabase start`, which prints all local URLs + keys after spin-up.
|
|
748
|
+
|
|
749
|
+
const hasLocalTelemetry = telemetryResult?.status === 'started' || telemetryResult?.status === 'degraded';
|
|
750
|
+
const hasLocalPostgres = serviceResult?.status === 'ok' && serviceResult?.databaseUrl?.includes('127.0.0.1');
|
|
751
|
+
const vectorBackend = hasLocalPostgres
|
|
752
|
+
? { label: 'Postgres + pgvector', detail: `${serviceResult.databaseUrl} (384d embeddings, ${managedValues.CONSTRUCT_VECTOR_MODEL})` }
|
|
753
|
+
: { label: 'JSON fallback', detail: `${managedValues.CONSTRUCT_VECTOR_INDEX_PATH} (${managedValues.CONSTRUCT_VECTOR_MODEL})` };
|
|
754
|
+
|
|
755
|
+
console.log('\nLocal services:');
|
|
756
|
+
if (hasLocalTelemetry) {
|
|
757
|
+
console.log(` Telemetry: ${telemetryResult.note || 'started'}`);
|
|
758
|
+
} else {
|
|
759
|
+
console.log(` Telemetry: not started`);
|
|
760
|
+
}
|
|
761
|
+
if (hasLocalPostgres) {
|
|
762
|
+
console.log(` Postgres: ${serviceResult.databaseUrl}`);
|
|
763
|
+
} else if (process.env.DATABASE_URL) {
|
|
764
|
+
console.log(` Postgres: external (${process.env.DATABASE_URL.replace(/:\/\/[^@]+@/, '://<credentials>@')})`);
|
|
765
|
+
} else {
|
|
766
|
+
console.log(` Postgres: not running — vector retrieval will use JSON fallback`);
|
|
767
|
+
}
|
|
768
|
+
console.log(` Vector: ${vectorBackend.label}`);
|
|
769
|
+
console.log(` ${vectorBackend.detail}`);
|
|
770
|
+
console.log(' Credentials are saved to ~/.construct/config.env for later reference.');
|
|
771
|
+
|
|
772
|
+
console.log('\nNext steps:');
|
|
773
|
+
console.log(' construct provider add github # Connect GitHub repository data');
|
|
774
|
+
console.log(' construct doctor # Verify all systems');
|
|
775
|
+
console.log(' construct evals retrieval # Baseline retrieval quality');
|
|
776
|
+
console.log(`\nSetup log: ${setupLogPath}`);
|
|
308
777
|
}
|
|
309
778
|
|
|
310
779
|
function ensureUserConfig(homeDir = HOME) {
|
|
@@ -314,6 +783,24 @@ function ensureUserConfig(homeDir = HOME) {
|
|
|
314
783
|
return envPath;
|
|
315
784
|
}
|
|
316
785
|
|
|
786
|
+
/**
|
|
787
|
+
* Create ~/.construct/workspace/ with the standard docs lane structure.
|
|
788
|
+
* Fallback target for embed output that isn't repo-specific.
|
|
789
|
+
*/
|
|
790
|
+
export function ensureWorkspace(homeDir = HOME) {
|
|
791
|
+
const wsPath = path.join(homeDir, '.construct', 'workspace');
|
|
792
|
+
const docsPath = path.join(wsPath, 'docs');
|
|
793
|
+
for (const lane of WORKSPACE_DOCS_LANES) {
|
|
794
|
+
fs.mkdirSync(path.join(docsPath, lane), { recursive: true });
|
|
795
|
+
}
|
|
796
|
+
// Ensure top-level workspace files exist
|
|
797
|
+
const snapshotPath = path.join(wsPath, 'snapshot.md');
|
|
798
|
+
if (!fs.existsSync(snapshotPath)) fs.writeFileSync(snapshotPath, '# Snapshot\n\nNo snapshot yet.\n');
|
|
799
|
+
const roadmapPath = path.join(wsPath, 'roadmap.md');
|
|
800
|
+
if (!fs.existsSync(roadmapPath)) fs.writeFileSync(roadmapPath, '# Roadmap\n\nNo roadmap generated yet.\n');
|
|
801
|
+
return wsPath;
|
|
802
|
+
}
|
|
803
|
+
|
|
317
804
|
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
318
805
|
runSetup({ args: process.argv.slice(2) });
|
|
319
806
|
}
|