@geraldmaron/construct 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +10 -7
- 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 +1116 -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 +525 -38
- 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
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/embed/supervision.mjs — platform supervisor integration for the embed daemon.
|
|
3
|
+
*
|
|
4
|
+
* Writes a platform service file so the embed daemon is automatically
|
|
5
|
+
* restarted with exponential backoff if it crashes, and started on login/boot.
|
|
6
|
+
*
|
|
7
|
+
* Platform support:
|
|
8
|
+
* macOS — launchd plist at ~/Library/LaunchAgents/com.construct.embed.plist
|
|
9
|
+
* Linux — systemd user unit at ~/.config/systemd/user/construct-embed.service
|
|
10
|
+
* Windows — Task Scheduler (schtasks) entry named "Construct.Embed"
|
|
11
|
+
*
|
|
12
|
+
* Usage:
|
|
13
|
+
* import { installSupervision, uninstallSupervision, supervisionStatus } from './supervision.mjs';
|
|
14
|
+
* await installSupervision();
|
|
15
|
+
* await uninstallSupervision();
|
|
16
|
+
* const info = await supervisionStatus();
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import fs from 'node:fs';
|
|
20
|
+
import os from 'node:os';
|
|
21
|
+
import path from 'node:path';
|
|
22
|
+
import { spawnSync } from 'node:child_process';
|
|
23
|
+
|
|
24
|
+
const HOME = os.homedir();
|
|
25
|
+
const PLATFORM = process.platform;
|
|
26
|
+
const NODE_BIN = process.execPath;
|
|
27
|
+
|
|
28
|
+
// Path to the construct bin — we use absolute path so launchd/systemd can find it.
|
|
29
|
+
function constructBin() {
|
|
30
|
+
const local = path.join(HOME, '.construct', 'bin', 'construct');
|
|
31
|
+
if (fs.existsSync(local)) return local;
|
|
32
|
+
const result = spawnSync('which', ['construct'], { encoding: 'utf8' });
|
|
33
|
+
return result.status === 0 ? result.stdout.trim() : 'construct';
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// ── macOS launchd ───────────────────────────────────────────────────────────
|
|
37
|
+
|
|
38
|
+
const LAUNCHD_LABEL = 'com.construct.embed';
|
|
39
|
+
const LAUNCHD_PLIST = path.join(HOME, 'Library', 'LaunchAgents', `${LAUNCHD_LABEL}.plist`);
|
|
40
|
+
|
|
41
|
+
function launchdPlist(bin, logPath) {
|
|
42
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
43
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
|
|
44
|
+
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
45
|
+
<plist version="1.0">
|
|
46
|
+
<dict>
|
|
47
|
+
<key>Label</key>
|
|
48
|
+
<string>${LAUNCHD_LABEL}</string>
|
|
49
|
+
<key>ProgramArguments</key>
|
|
50
|
+
<array>
|
|
51
|
+
<string>${NODE_BIN}</string>
|
|
52
|
+
<string>${bin}</string>
|
|
53
|
+
<string>embed</string>
|
|
54
|
+
<string>start</string>
|
|
55
|
+
<string>--foreground</string>
|
|
56
|
+
</array>
|
|
57
|
+
<key>RunAtLoad</key>
|
|
58
|
+
<true/>
|
|
59
|
+
<key>KeepAlive</key>
|
|
60
|
+
<dict>
|
|
61
|
+
<key>Crashed</key>
|
|
62
|
+
<true/>
|
|
63
|
+
</dict>
|
|
64
|
+
<key>ThrottleInterval</key>
|
|
65
|
+
<integer>10</integer>
|
|
66
|
+
<key>StandardOutPath</key>
|
|
67
|
+
<string>${logPath}</string>
|
|
68
|
+
<key>StandardErrorPath</key>
|
|
69
|
+
<string>${logPath}</string>
|
|
70
|
+
<key>EnvironmentVariables</key>
|
|
71
|
+
<dict>
|
|
72
|
+
<key>HOME</key>
|
|
73
|
+
<string>${HOME}</string>
|
|
74
|
+
</dict>
|
|
75
|
+
</dict>
|
|
76
|
+
</plist>
|
|
77
|
+
`;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function installLaunchd(bin, logPath) {
|
|
81
|
+
fs.mkdirSync(path.dirname(LAUNCHD_PLIST), { recursive: true });
|
|
82
|
+
fs.writeFileSync(LAUNCHD_PLIST, launchdPlist(bin, logPath));
|
|
83
|
+
const load = spawnSync('launchctl', ['load', '-w', LAUNCHD_PLIST], { encoding: 'utf8' });
|
|
84
|
+
if (load.status !== 0) {
|
|
85
|
+
throw new Error(`launchctl load failed: ${load.stderr}`);
|
|
86
|
+
}
|
|
87
|
+
return { method: 'launchd', file: LAUNCHD_PLIST };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async function uninstallLaunchd() {
|
|
91
|
+
if (!fs.existsSync(LAUNCHD_PLIST)) return { method: 'launchd', wasInstalled: false };
|
|
92
|
+
spawnSync('launchctl', ['unload', '-w', LAUNCHD_PLIST], { encoding: 'utf8' });
|
|
93
|
+
fs.rmSync(LAUNCHD_PLIST, { force: true });
|
|
94
|
+
return { method: 'launchd', wasInstalled: true };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function launchdStatus() {
|
|
98
|
+
if (!fs.existsSync(LAUNCHD_PLIST)) return { installed: false };
|
|
99
|
+
const result = spawnSync('launchctl', ['list', LAUNCHD_LABEL], { encoding: 'utf8' });
|
|
100
|
+
return {
|
|
101
|
+
installed: true,
|
|
102
|
+
active: result.status === 0,
|
|
103
|
+
file: LAUNCHD_PLIST,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// ── Linux systemd ───────────────────────────────────────────────────────────
|
|
108
|
+
|
|
109
|
+
const SYSTEMD_UNIT_NAME = 'construct-embed.service';
|
|
110
|
+
const SYSTEMD_UNIT_DIR = path.join(HOME, '.config', 'systemd', 'user');
|
|
111
|
+
const SYSTEMD_UNIT_FILE = path.join(SYSTEMD_UNIT_DIR, SYSTEMD_UNIT_NAME);
|
|
112
|
+
|
|
113
|
+
function systemdUnit(bin, logPath) {
|
|
114
|
+
return `[Unit]
|
|
115
|
+
Description=Construct embed daemon
|
|
116
|
+
After=network.target
|
|
117
|
+
|
|
118
|
+
[Service]
|
|
119
|
+
Type=simple
|
|
120
|
+
ExecStart=${NODE_BIN} ${bin} embed start --foreground
|
|
121
|
+
Restart=on-failure
|
|
122
|
+
RestartSec=10
|
|
123
|
+
RestartMaxDelay=300
|
|
124
|
+
StandardOutput=append:${logPath}
|
|
125
|
+
StandardError=append:${logPath}
|
|
126
|
+
Environment=HOME=${HOME}
|
|
127
|
+
|
|
128
|
+
[Install]
|
|
129
|
+
WantedBy=default.target
|
|
130
|
+
`;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async function installSystemd(bin, logPath) {
|
|
134
|
+
fs.mkdirSync(SYSTEMD_UNIT_DIR, { recursive: true });
|
|
135
|
+
fs.writeFileSync(SYSTEMD_UNIT_FILE, systemdUnit(bin, logPath));
|
|
136
|
+
|
|
137
|
+
const reload = spawnSync('systemctl', ['--user', 'daemon-reload'], { encoding: 'utf8' });
|
|
138
|
+
if (reload.status !== 0) throw new Error(`daemon-reload failed: ${reload.stderr}`);
|
|
139
|
+
|
|
140
|
+
const enable = spawnSync('systemctl', ['--user', 'enable', '--now', SYSTEMD_UNIT_NAME], { encoding: 'utf8' });
|
|
141
|
+
if (enable.status !== 0) throw new Error(`systemctl enable failed: ${enable.stderr}`);
|
|
142
|
+
|
|
143
|
+
return { method: 'systemd', file: SYSTEMD_UNIT_FILE };
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
async function uninstallSystemd() {
|
|
147
|
+
if (!fs.existsSync(SYSTEMD_UNIT_FILE)) return { method: 'systemd', wasInstalled: false };
|
|
148
|
+
spawnSync('systemctl', ['--user', 'disable', '--now', SYSTEMD_UNIT_NAME], { encoding: 'utf8' });
|
|
149
|
+
fs.rmSync(SYSTEMD_UNIT_FILE, { force: true });
|
|
150
|
+
spawnSync('systemctl', ['--user', 'daemon-reload'], { encoding: 'utf8' });
|
|
151
|
+
return { method: 'systemd', wasInstalled: true };
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function systemdStatus() {
|
|
155
|
+
if (!fs.existsSync(SYSTEMD_UNIT_FILE)) return { installed: false };
|
|
156
|
+
const result = spawnSync('systemctl', ['--user', 'is-active', SYSTEMD_UNIT_NAME], { encoding: 'utf8' });
|
|
157
|
+
return {
|
|
158
|
+
installed: true,
|
|
159
|
+
active: result.stdout.trim() === 'active',
|
|
160
|
+
file: SYSTEMD_UNIT_FILE,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// ── Windows Task Scheduler ──────────────────────────────────────────────────
|
|
165
|
+
|
|
166
|
+
const WIN_TASK_NAME = 'Construct.Embed';
|
|
167
|
+
|
|
168
|
+
async function installWindows(bin, logPath) {
|
|
169
|
+
const cmd = [
|
|
170
|
+
'schtasks', '/Create', '/F',
|
|
171
|
+
'/TN', WIN_TASK_NAME,
|
|
172
|
+
'/TR', `"${NODE_BIN}" "${bin}" embed start --foreground >> "${logPath}" 2>&1`,
|
|
173
|
+
'/SC', 'ONLOGON',
|
|
174
|
+
'/RL', 'HIGHEST',
|
|
175
|
+
].join(' ');
|
|
176
|
+
const result = spawnSync('cmd', ['/c', cmd], { encoding: 'utf8' });
|
|
177
|
+
if (result.status !== 0) throw new Error(`schtasks failed: ${result.stderr}`);
|
|
178
|
+
return { method: 'task-scheduler', taskName: WIN_TASK_NAME };
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
async function uninstallWindows() {
|
|
182
|
+
const result = spawnSync('schtasks', ['/Query', '/TN', WIN_TASK_NAME], { encoding: 'utf8' });
|
|
183
|
+
if (result.status !== 0) return { method: 'task-scheduler', wasInstalled: false };
|
|
184
|
+
spawnSync('schtasks', ['/Delete', '/TN', WIN_TASK_NAME, '/F'], { encoding: 'utf8' });
|
|
185
|
+
return { method: 'task-scheduler', wasInstalled: true };
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function windowsStatus() {
|
|
189
|
+
const result = spawnSync('schtasks', ['/Query', '/TN', WIN_TASK_NAME, '/FO', 'CSV'], { encoding: 'utf8' });
|
|
190
|
+
if (result.status !== 0) return { installed: false };
|
|
191
|
+
const running = result.stdout.includes('Running');
|
|
192
|
+
return { installed: true, active: running, taskName: WIN_TASK_NAME };
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// ── Public API ──────────────────────────────────────────────────────────────
|
|
196
|
+
|
|
197
|
+
function logPath() {
|
|
198
|
+
return path.join(HOME, '.cx', 'runtime', 'embed-daemon.log');
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Install the platform supervisor so the embed daemon auto-restarts on crash.
|
|
203
|
+
*/
|
|
204
|
+
export async function installSupervision() {
|
|
205
|
+
const bin = constructBin();
|
|
206
|
+
const log = logPath();
|
|
207
|
+
fs.mkdirSync(path.dirname(log), { recursive: true });
|
|
208
|
+
|
|
209
|
+
if (PLATFORM === 'darwin') return installLaunchd(bin, log);
|
|
210
|
+
if (PLATFORM === 'linux') return installSystemd(bin, log);
|
|
211
|
+
if (PLATFORM === 'win32') return installWindows(bin, log);
|
|
212
|
+
throw new Error(`supervision not supported on platform: ${PLATFORM}`);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Remove the platform supervisor entry. Does not stop a currently-running daemon.
|
|
217
|
+
*/
|
|
218
|
+
export async function uninstallSupervision() {
|
|
219
|
+
if (PLATFORM === 'darwin') return uninstallLaunchd();
|
|
220
|
+
if (PLATFORM === 'linux') return uninstallSystemd();
|
|
221
|
+
if (PLATFORM === 'win32') return uninstallWindows();
|
|
222
|
+
throw new Error(`supervision not supported on platform: ${PLATFORM}`);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Check whether the supervisor is installed and active.
|
|
227
|
+
*
|
|
228
|
+
* @returns {{ installed: boolean, active?: boolean, method?: string, file?: string }}
|
|
229
|
+
*/
|
|
230
|
+
export function supervisionStatus() {
|
|
231
|
+
if (PLATFORM === 'darwin') return { method: 'launchd', ...launchdStatus() };
|
|
232
|
+
if (PLATFORM === 'linux') return { method: 'systemd', ...systemdStatus() };
|
|
233
|
+
if (PLATFORM === 'win32') return { method: 'task-scheduler', ...windowsStatus() };
|
|
234
|
+
return { installed: false, method: 'unsupported' };
|
|
235
|
+
}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/embed/target-resolver.mjs — target discovery and routing for embed mode.
|
|
3
|
+
*
|
|
4
|
+
* Resolution order:
|
|
5
|
+
* 1. Explicit targets from config (targets[])
|
|
6
|
+
* 2. Provider-linked discovery (e.g., Jira issue references a GitHub repo)
|
|
7
|
+
* 3. Broadened search if nothing actionable matched
|
|
8
|
+
* 4. Workspace fallback (~/.construct/workspace) — always present
|
|
9
|
+
*
|
|
10
|
+
* Each resolved target is a normalized object:
|
|
11
|
+
* { type, ref?, path?, access, provider?, docs? }
|
|
12
|
+
*
|
|
13
|
+
* access:
|
|
14
|
+
* - 'local' → filesystem path exists, read/write via fs
|
|
15
|
+
* - 'remote' → interact via provider API only
|
|
16
|
+
* - 'hybrid' → local clone exists AND remote provider available
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import fs from 'node:fs';
|
|
20
|
+
import path from 'node:path';
|
|
21
|
+
import { DEFAULT_WORKSPACE_PATH, WORKSPACE_DOCS_LANES } from './config.mjs';
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Resolve all actionable targets for this embed cycle.
|
|
25
|
+
*
|
|
26
|
+
* @param {object} config - Parsed embed config (from loadEmbedConfig/normalize)
|
|
27
|
+
* @param {object} providerRegistry - ProviderRegistry instance for remote lookups
|
|
28
|
+
* @param {object} [opts]
|
|
29
|
+
* @param {object[]} [opts.signals] - Signals from sources (issues, PRs, etc.) that may reference repos
|
|
30
|
+
* @returns {Promise<ResolvedTarget[]>}
|
|
31
|
+
*/
|
|
32
|
+
export async function resolveTargets(config, providerRegistry, opts = {}) {
|
|
33
|
+
const resolved = [];
|
|
34
|
+
const seen = new Set();
|
|
35
|
+
|
|
36
|
+
// Phase 1: Explicit targets from config
|
|
37
|
+
for (const t of config.targets ?? []) {
|
|
38
|
+
const rt = resolveExplicit(t);
|
|
39
|
+
const key = targetKey(rt);
|
|
40
|
+
if (!seen.has(key)) {
|
|
41
|
+
seen.add(key);
|
|
42
|
+
resolved.push(rt);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Phase 2: Provider-linked discovery from source signals
|
|
47
|
+
if (opts.signals?.length) {
|
|
48
|
+
const discovered = discoverFromSignals(opts.signals, providerRegistry);
|
|
49
|
+
for (const rt of discovered) {
|
|
50
|
+
const key = targetKey(rt);
|
|
51
|
+
if (!seen.has(key)) {
|
|
52
|
+
seen.add(key);
|
|
53
|
+
resolved.push(rt);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Phase 3: Ensure workspace fallback
|
|
59
|
+
const hasWorkspace = resolved.some((t) => t.type === 'workspace');
|
|
60
|
+
if (!hasWorkspace) {
|
|
61
|
+
resolved.push({
|
|
62
|
+
type: 'workspace',
|
|
63
|
+
path: DEFAULT_WORKSPACE_PATH,
|
|
64
|
+
access: 'local',
|
|
65
|
+
docs: WORKSPACE_DOCS_LANES,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return resolved;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Resolve a single explicit target config entry into a normalized target.
|
|
74
|
+
*/
|
|
75
|
+
function resolveExplicit(target) {
|
|
76
|
+
if (target.type === 'workspace') {
|
|
77
|
+
return {
|
|
78
|
+
type: 'workspace',
|
|
79
|
+
path: target.path ?? DEFAULT_WORKSPACE_PATH,
|
|
80
|
+
access: 'local',
|
|
81
|
+
docs: WORKSPACE_DOCS_LANES,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// type: repo
|
|
86
|
+
const hasLocal = target.path && fs.existsSync(target.path);
|
|
87
|
+
const hasRemote = !!target.ref;
|
|
88
|
+
|
|
89
|
+
let access = 'remote';
|
|
90
|
+
if (hasLocal && hasRemote) access = 'hybrid';
|
|
91
|
+
else if (hasLocal) access = 'local';
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
type: target.type,
|
|
95
|
+
ref: target.ref ?? null,
|
|
96
|
+
path: hasLocal ? target.path : null,
|
|
97
|
+
access,
|
|
98
|
+
provider: target.provider ?? inferProvider(target.ref),
|
|
99
|
+
docs: target.docs ?? null,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Extract repo references from source signals (issues, PRs, commits that mention repos).
|
|
105
|
+
*/
|
|
106
|
+
function discoverFromSignals(signals, _providerRegistry) {
|
|
107
|
+
const discovered = [];
|
|
108
|
+
const repoPattern = /(?:github\.com|gitlab\.com)[/:]([A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+)/g;
|
|
109
|
+
|
|
110
|
+
for (const signal of signals) {
|
|
111
|
+
const text = signal.body ?? signal.description ?? signal.title ?? '';
|
|
112
|
+
let match;
|
|
113
|
+
while ((match = repoPattern.exec(text)) !== null) {
|
|
114
|
+
const ref = match[0].replace(/^(github\.com|gitlab\.com)[/:]/, '$1/');
|
|
115
|
+
discovered.push({
|
|
116
|
+
type: 'repo',
|
|
117
|
+
ref,
|
|
118
|
+
path: null,
|
|
119
|
+
access: 'remote',
|
|
120
|
+
provider: ref.startsWith('github') ? 'github' : 'gitlab',
|
|
121
|
+
docs: null,
|
|
122
|
+
discoveredFrom: signal.id ?? signal.url ?? 'signal',
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return discovered;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Infer provider from a ref string.
|
|
132
|
+
*/
|
|
133
|
+
function inferProvider(ref) {
|
|
134
|
+
if (!ref) return null;
|
|
135
|
+
if (ref.includes('github.com') || ref.includes('github:')) return 'github';
|
|
136
|
+
if (ref.includes('gitlab.com') || ref.includes('gitlab:')) return 'gitlab';
|
|
137
|
+
if (ref.includes('bitbucket')) return 'bitbucket';
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Deduplication key for a target.
|
|
143
|
+
*/
|
|
144
|
+
function targetKey(t) {
|
|
145
|
+
if (t.type === 'workspace') return 'workspace:' + (t.path ?? 'default');
|
|
146
|
+
return `${t.type}:${t.ref ?? ''}:${t.path ?? ''}`;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Given resolved targets and an artifact type, determine where to route output.
|
|
151
|
+
*
|
|
152
|
+
* Routing logic:
|
|
153
|
+
* - If a target has a matching docs lane, route there
|
|
154
|
+
* - If no specific target matches, route to workspace
|
|
155
|
+
*
|
|
156
|
+
* @param {ResolvedTarget[]} targets
|
|
157
|
+
* @param {string} artifactType - e.g. 'adrs', 'prds', 'memos', 'notes', 'intake'
|
|
158
|
+
* @returns {ResolvedTarget}
|
|
159
|
+
*/
|
|
160
|
+
export function routeArtifact(targets, artifactType) {
|
|
161
|
+
// Prefer non-workspace target with matching docs lane
|
|
162
|
+
for (const t of targets) {
|
|
163
|
+
if (t.type === 'workspace') continue;
|
|
164
|
+
if (t.access === 'local' || t.access === 'hybrid') {
|
|
165
|
+
// Local targets can receive docs directly
|
|
166
|
+
return t;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Fall back to workspace
|
|
171
|
+
const workspace = targets.find((t) => t.type === 'workspace');
|
|
172
|
+
return workspace ?? { type: 'workspace', path: DEFAULT_WORKSPACE_PATH, access: 'local', docs: WORKSPACE_DOCS_LANES };
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Resolve the filesystem path for writing an artifact to a target.
|
|
177
|
+
*
|
|
178
|
+
* @param {ResolvedTarget} target
|
|
179
|
+
* @param {string} artifactType - docs lane (adrs, prds, memos, notes, intake)
|
|
180
|
+
* @param {string} filename
|
|
181
|
+
* @returns {string} Absolute file path
|
|
182
|
+
*/
|
|
183
|
+
export function resolveArtifactPath(target, artifactType, filename) {
|
|
184
|
+
const base = target.path ?? DEFAULT_WORKSPACE_PATH;
|
|
185
|
+
return path.join(base, 'docs', artifactType, filename);
|
|
186
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* lib/embed/worker.mjs — Long-running embed daemon worker.
|
|
4
|
+
*
|
|
5
|
+
* Spawned detached by `construct embed start`. Runs until SIGTERM.
|
|
6
|
+
* Reads --config <path> from argv, or auto-discovers sources from config.env.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import os from 'node:os';
|
|
10
|
+
import path from 'node:path';
|
|
11
|
+
import { loadConstructEnv } from '../env-config.mjs';
|
|
12
|
+
import { EmbedDaemon } from './daemon.mjs';
|
|
13
|
+
|
|
14
|
+
function parseArgv(argv) {
|
|
15
|
+
for (let i = 0; i < argv.length; i++) {
|
|
16
|
+
if (argv[i] === '--config' && argv[i + 1]) return argv[i + 1];
|
|
17
|
+
}
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const configPath = parseArgv(process.argv.slice(2));
|
|
22
|
+
const env = loadConstructEnv();
|
|
23
|
+
|
|
24
|
+
// If no config file, build an auto-discovered config inline and inject it
|
|
25
|
+
let daemonOpts = { env };
|
|
26
|
+
if (configPath) {
|
|
27
|
+
daemonOpts.configPath = configPath;
|
|
28
|
+
} else {
|
|
29
|
+
const { ProviderRegistry } = await import('./providers/registry.mjs');
|
|
30
|
+
const { EMPTY_CONFIG } = await import('./config.mjs');
|
|
31
|
+
const registry = await ProviderRegistry.fromEnv(env);
|
|
32
|
+
const sources = registry.autoSources(env);
|
|
33
|
+
daemonOpts.registry = registry;
|
|
34
|
+
daemonOpts.config = {
|
|
35
|
+
...EMPTY_CONFIG,
|
|
36
|
+
sources,
|
|
37
|
+
// Default snapshot sink so operators always have a current markdown copy on disk.
|
|
38
|
+
outputs: [{ type: 'markdown', path: '.cx/snapshot.md' }],
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const daemon = new EmbedDaemon(daemonOpts);
|
|
43
|
+
|
|
44
|
+
process.on('SIGTERM', () => {
|
|
45
|
+
daemon.stop();
|
|
46
|
+
process.exit(0);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
process.on('SIGINT', () => {
|
|
50
|
+
daemon.stop();
|
|
51
|
+
process.exit(0);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
process.on('uncaughtException', (err) => {
|
|
55
|
+
process.stderr.write(`[embed/worker] uncaughtException: ${err.message}\n${err.stack}\n`);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
process.on('unhandledRejection', (reason) => {
|
|
59
|
+
process.stderr.write(`[embed/worker] unhandledRejection: ${reason}\n`);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
await daemon.start();
|