@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
|
@@ -0,0 +1,1000 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* lib/init-unified.mjs — unified bootstrap for Construct project state and documentation system.
|
|
4
|
+
*
|
|
5
|
+
* Replaces both `construct init` and `construct init-docs`.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* node lib/init-unified.mjs [target-path] [--docs-preset=lean|product|full] [--docs-lanes=adrs,prds] [--with-architecture] [--with-readme]
|
|
9
|
+
* construct init [path] [--docs-preset=lean|product|full] [--docs-lanes=adrs,prds] [--with-architecture] [--with-readme]
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import fs from "node:fs";
|
|
13
|
+
import path from "node:path";
|
|
14
|
+
import { fileURLToPath } from "node:url";
|
|
15
|
+
|
|
16
|
+
import {
|
|
17
|
+
buildAgentsGuide,
|
|
18
|
+
buildContextJson,
|
|
19
|
+
buildContextMarkdown,
|
|
20
|
+
buildPlanTemplate,
|
|
21
|
+
writeStampedIfMissing,
|
|
22
|
+
} from "./project-init-shared.mjs";
|
|
23
|
+
import { multiSelect } from './tty-prompts.mjs';
|
|
24
|
+
import { execSync, spawnSync } from 'node:child_process';
|
|
25
|
+
import { stageProjectAdapters } from './install/stage-project.mjs';
|
|
26
|
+
|
|
27
|
+
const __dirname = fileURLToPath(new URL(".", import.meta.url));
|
|
28
|
+
const ROOT_DIR = path.join(__dirname, "..");
|
|
29
|
+
const TEMPLATE_DIR = path.join(ROOT_DIR, "templates", "docs");
|
|
30
|
+
|
|
31
|
+
const args = process.argv.slice(2);
|
|
32
|
+
const targetArg = args.find((arg) => !arg.startsWith("--"));
|
|
33
|
+
const target = path.resolve(targetArg ?? process.cwd());
|
|
34
|
+
|
|
35
|
+
// Documentation flags - granular control
|
|
36
|
+
const withDocsFlag = args.find((arg) => arg.startsWith("--with-docs="));
|
|
37
|
+
const withAllDocsFlag = args.includes("--with-all-docs");
|
|
38
|
+
const withAdrsFlag = args.includes("--with-adrs");
|
|
39
|
+
const withRfcsFlag = args.includes("--with-rfcs");
|
|
40
|
+
const withRunbooksFlag = args.includes("--with-runbooks");
|
|
41
|
+
const withPostmortemsFlag = args.includes("--with-postmortems");
|
|
42
|
+
const withArchitectureFlag = args.includes("--with-architecture");
|
|
43
|
+
const withReadmeFlag = args.includes("--with-readme");
|
|
44
|
+
const withDevcontainerFlag = args.includes("--devcontainer");
|
|
45
|
+
|
|
46
|
+
// Behavior flags
|
|
47
|
+
const verbose = args.includes("--verbose") || args.includes("-v");
|
|
48
|
+
const interactive = args.includes("--interactive") || args.includes("-i");
|
|
49
|
+
const quiet = args.includes("--quiet") || args.includes("-q");
|
|
50
|
+
const skipInteractive = !interactive;
|
|
51
|
+
|
|
52
|
+
const created = [];
|
|
53
|
+
const skipped = [];
|
|
54
|
+
|
|
55
|
+
async function confirm(question) {
|
|
56
|
+
const readline = await import('node:readline');
|
|
57
|
+
const rl = readline.createInterface({
|
|
58
|
+
input: process.stdin,
|
|
59
|
+
output: process.stdout,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
return new Promise((resolve) => {
|
|
63
|
+
rl.question(question, (answer) => {
|
|
64
|
+
rl.close();
|
|
65
|
+
resolve(answer.toLowerCase().trim() !== 'n');
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function checkPrerequisites() {
|
|
71
|
+
const { checkPrerequisites: sharedCheck } = await import('./health-check.mjs');
|
|
72
|
+
const os = await import('node:os');
|
|
73
|
+
const homeDir = os.homedir();
|
|
74
|
+
|
|
75
|
+
const result = await sharedCheck({
|
|
76
|
+
interactive: true,
|
|
77
|
+
homeDir
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
if (!result.ok) {
|
|
81
|
+
console.log('\n💡 Run `construct install --yes` to install missing dependencies automatically.');
|
|
82
|
+
|
|
83
|
+
const runInstall = await confirm('\n🔧 Run `construct install` now? [Y/n] ');
|
|
84
|
+
if (runInstall) {
|
|
85
|
+
const { spawnSync } = await import('node:child_process');
|
|
86
|
+
const result = spawnSync(process.execPath, [path.join(__dirname, '..', 'bin', 'construct'), 'install', '--yes'], {
|
|
87
|
+
stdio: 'inherit',
|
|
88
|
+
env: process.env,
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
if (result.status !== 0) {
|
|
92
|
+
console.log('\n⚠️ Install did not complete successfully. You can run `construct install` manually later.');
|
|
93
|
+
const continueAnyway = await confirm('\nContinue with project initialization anyway? [y/N] ');
|
|
94
|
+
if (!continueAnyway) {
|
|
95
|
+
process.exit(1);
|
|
96
|
+
}
|
|
97
|
+
} else {
|
|
98
|
+
console.log('\n✅ Install complete! Continuing with project initialization...\n');
|
|
99
|
+
}
|
|
100
|
+
} else {
|
|
101
|
+
console.log('\n⏭️ Skipping install. You can run `construct install` later.');
|
|
102
|
+
const continueAnyway = await confirm('\nContinue with project initialization anyway? [y/N] ');
|
|
103
|
+
if (!continueAnyway) {
|
|
104
|
+
process.exit(1);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return result.ok;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Documentation system configuration
|
|
113
|
+
const DOC_LANES = {
|
|
114
|
+
adrs: {
|
|
115
|
+
title: "ADRs",
|
|
116
|
+
dir: "adr",
|
|
117
|
+
description: "Architecture decision records for decisions that have already been made.",
|
|
118
|
+
templates: ["adr.md"],
|
|
119
|
+
},
|
|
120
|
+
briefs: {
|
|
121
|
+
title: "Briefs",
|
|
122
|
+
dir: "briefs",
|
|
123
|
+
description: "Research, evidence, signal, and one-pager style documents.",
|
|
124
|
+
templates: [
|
|
125
|
+
"research-brief.md",
|
|
126
|
+
"evidence-brief.md",
|
|
127
|
+
"signal-brief.md",
|
|
128
|
+
"one-pager.md",
|
|
129
|
+
"customer-profile.md",
|
|
130
|
+
"product-intelligence-report.md",
|
|
131
|
+
"backlog-proposal.md",
|
|
132
|
+
],
|
|
133
|
+
},
|
|
134
|
+
changelogs: {
|
|
135
|
+
title: "Changelogs",
|
|
136
|
+
dir: "changelogs",
|
|
137
|
+
description: "User-facing release notes and version history entries.",
|
|
138
|
+
templates: ["changelog-entry.md"],
|
|
139
|
+
},
|
|
140
|
+
intake: {
|
|
141
|
+
title: "Intake",
|
|
142
|
+
dir: "intake",
|
|
143
|
+
description: "Intake batch records that explain what arrived, why it matters, and how it should be ingested.",
|
|
144
|
+
templates: ["__intake-template__"],
|
|
145
|
+
},
|
|
146
|
+
memos: {
|
|
147
|
+
title: "Memos",
|
|
148
|
+
dir: "memos",
|
|
149
|
+
description: "Decision memos and internal arguments for alignment and approval.",
|
|
150
|
+
templates: ["memo.md"],
|
|
151
|
+
},
|
|
152
|
+
meetings: {
|
|
153
|
+
title: 'Meetings',
|
|
154
|
+
dir: 'meetings',
|
|
155
|
+
description: 'Meeting notes, minutes, retros, standups, planning sessions, and agendas.',
|
|
156
|
+
templates: ['__meeting-notes-template__'],
|
|
157
|
+
},
|
|
158
|
+
notes: {
|
|
159
|
+
title: "Notes",
|
|
160
|
+
dir: "notes",
|
|
161
|
+
description: "Working notes and lightweight durable context outside formal docs or meetings.",
|
|
162
|
+
templates: ["__notes-template__"],
|
|
163
|
+
},
|
|
164
|
+
onboarding: {
|
|
165
|
+
title: "Onboarding",
|
|
166
|
+
dir: "onboarding",
|
|
167
|
+
description: "Runnable setup guides and first-day workflows for engineers, product, or ops.",
|
|
168
|
+
templates: ["onboarding.md"],
|
|
169
|
+
},
|
|
170
|
+
postmortems: {
|
|
171
|
+
title: "Postmortems",
|
|
172
|
+
dir: "postmortems",
|
|
173
|
+
description: "Blameless incident reports: timeline, root cause, contributing factors, and corrective actions.",
|
|
174
|
+
templates: ["incident-report.md"],
|
|
175
|
+
},
|
|
176
|
+
prds: {
|
|
177
|
+
title: "PRDs",
|
|
178
|
+
dir: "prds",
|
|
179
|
+
description: "Product and capability requirement documents.",
|
|
180
|
+
templates: ["prd.md", "meta-prd.md", "prd-business.md", "prd-platform.md", "prfaq.md"],
|
|
181
|
+
},
|
|
182
|
+
rfcs: {
|
|
183
|
+
title: "RFCs",
|
|
184
|
+
dir: "rfcs",
|
|
185
|
+
description: "Architecture and implementation proposals that need review before a decision.",
|
|
186
|
+
templates: ["rfc.md", "rfc-platform.md"],
|
|
187
|
+
},
|
|
188
|
+
runbooks: {
|
|
189
|
+
title: "Runbooks",
|
|
190
|
+
dir: "runbooks",
|
|
191
|
+
description: "Operational procedures, diagnostics, remediation, and escalation paths.",
|
|
192
|
+
templates: ["runbook.md"],
|
|
193
|
+
},
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
// Lane order for display (alphabetical by title)
|
|
197
|
+
const LANE_ORDER = ["adrs", "briefs", "changelogs", "intake", "meetings", "memos", "notes", "onboarding", "postmortems", "prds", "rfcs", "runbooks"];
|
|
198
|
+
|
|
199
|
+
// Project type detection
|
|
200
|
+
function detectProjectType(targetPath) {
|
|
201
|
+
const files = fs.readdirSync(targetPath);
|
|
202
|
+
const hasPackageJson = files.includes('package.json');
|
|
203
|
+
const hasCargoToml = files.includes('Cargo.toml');
|
|
204
|
+
const hasGoMod = files.includes('go.mod');
|
|
205
|
+
const hasRequirementsTxt = files.includes('requirements.txt') || files.includes('pyproject.toml');
|
|
206
|
+
const hasDockerfile = files.includes('Dockerfile') || files.includes('docker-compose.yml');
|
|
207
|
+
const hasK8s = files.some(f => f.endsWith('.yaml') && (f.includes('deployment') || f.includes('service') || f.includes('ingress')));
|
|
208
|
+
|
|
209
|
+
// Read package.json if present
|
|
210
|
+
let packageInfo = null;
|
|
211
|
+
if (hasPackageJson) {
|
|
212
|
+
try {
|
|
213
|
+
packageInfo = JSON.parse(fs.readFileSync(path.join(targetPath, 'package.json'), 'utf8'));
|
|
214
|
+
} catch {}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Detect type
|
|
218
|
+
if (hasK8s) return { type: 'platform', label: 'Platform/Infrastructure', icon: '☸️' };
|
|
219
|
+
if (hasDockerfile && hasPackageJson) return { type: 'api', label: 'API Service', icon: '🔌' };
|
|
220
|
+
if (packageInfo?.dependencies?.express || packageInfo?.dependencies?.fastify) return { type: 'api', label: 'API Service', icon: '🔌' };
|
|
221
|
+
if (packageInfo?.dependencies?.react || packageInfo?.dependencies?.vue || packageInfo?.dependencies?.svelte) return { type: 'webapp', label: 'Web Application', icon: '🌐' };
|
|
222
|
+
if (hasCargoToml) return { type: 'system', label: 'Systems/CLI Tool', icon: '⚙️' };
|
|
223
|
+
if (hasGoMod) return { type: 'backend', label: 'Go Backend', icon: '🔋' };
|
|
224
|
+
if (hasRequirementsTxt) return { type: 'data', label: 'Data/ML Project', icon: '📊' };
|
|
225
|
+
if (packageInfo?.bin) return { type: 'cli', label: 'CLI Tool', icon: '⌨️' };
|
|
226
|
+
if (files.includes('README.md') && !hasPackageJson) return { type: 'docs', label: 'Documentation Project', icon: '📚' };
|
|
227
|
+
|
|
228
|
+
return { type: 'generic', label: 'Generic Project', icon: '📦' };
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Suggest lanes based on project type and detected files
|
|
232
|
+
function suggestLanes(targetPath, projectType) {
|
|
233
|
+
const suggestions = [];
|
|
234
|
+
const files = fs.readdirSync(targetPath);
|
|
235
|
+
|
|
236
|
+
// Always suggest intake for collecting feedback
|
|
237
|
+
suggestions.push({ lane: 'intake', reason: 'Collect incoming requests and feedback', recommended: true });
|
|
238
|
+
|
|
239
|
+
// ADRs for any non-trivial project
|
|
240
|
+
if (projectType.type !== 'docs') {
|
|
241
|
+
suggestions.push({ lane: 'adrs', reason: 'Track architectural decisions', recommended: true });
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// RFCs for APIs and platform projects
|
|
245
|
+
if (['api', 'platform', 'backend'].includes(projectType.type)) {
|
|
246
|
+
suggestions.push({ lane: 'rfcs', reason: 'Design reviews for API changes', recommended: true });
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Runbooks for deployed services
|
|
250
|
+
if (['api', 'platform', 'webapp', 'backend'].includes(projectType.type)) {
|
|
251
|
+
suggestions.push({ lane: 'runbooks', reason: 'Operational procedures and on-call docs', recommended: false });
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Postmortems for production services
|
|
255
|
+
if (['api', 'platform', 'webapp', 'backend'].includes(projectType.type)) {
|
|
256
|
+
suggestions.push({ lane: 'postmortems', reason: 'Incident retrospectives', recommended: false });
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// PRDs for product-focused projects
|
|
260
|
+
if (['webapp', 'api'].includes(projectType.type)) {
|
|
261
|
+
suggestions.push({ lane: 'prds', reason: 'Product requirements', recommended: false });
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Notes for all projects
|
|
265
|
+
suggestions.push({ lane: 'notes', reason: 'General notes and scratchpad', recommended: true });
|
|
266
|
+
|
|
267
|
+
return suggestions;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Parse --with-docs flag value
|
|
271
|
+
function parseWithDocs(value) {
|
|
272
|
+
if (!value || value === 'all') {
|
|
273
|
+
return LANE_ORDER;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const lanes = value.split(',').map(v => v.trim().toLowerCase());
|
|
277
|
+
const validLanes = lanes.filter(lane => LANE_ORDER.includes(lane));
|
|
278
|
+
|
|
279
|
+
// Map common aliases
|
|
280
|
+
const mapped = validLanes.map(lane => {
|
|
281
|
+
if (lane === 'adr') return 'adrs';
|
|
282
|
+
if (lane === 'rfc') return 'rfcs';
|
|
283
|
+
if (lane === 'runbook') return 'runbooks';
|
|
284
|
+
if (lane === 'postmortem') return 'postmortems';
|
|
285
|
+
if (lane === 'prd') return 'prds';
|
|
286
|
+
return lane;
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
return [...new Set(mapped)]; // dedupe
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function inferProjectName(targetPath) {
|
|
293
|
+
const packageJsonPath = path.join(targetPath, "package.json");
|
|
294
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
295
|
+
try {
|
|
296
|
+
const pkg = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
|
|
297
|
+
return pkg.name || path.basename(targetPath);
|
|
298
|
+
} catch {}
|
|
299
|
+
}
|
|
300
|
+
return path.basename(targetPath);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function normalizeAnswer(value) {
|
|
304
|
+
return value.trim().toLowerCase().replace(/\s+/g, " ");
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
function buildDocsReadme(projectName) {
|
|
310
|
+
return `# ${projectName} Documentation
|
|
311
|
+
|
|
312
|
+
This directory contains the structured documentation system for ${projectName}.
|
|
313
|
+
|
|
314
|
+
## Lanes
|
|
315
|
+
|
|
316
|
+
Each subdirectory represents a documentation lane with a specific purpose:
|
|
317
|
+
|
|
318
|
+
- **adr/** – Architecture decision records
|
|
319
|
+
- **briefs/** – Research, evidence, and one-pager documents
|
|
320
|
+
- **changelogs/** – User-facing release notes
|
|
321
|
+
- **intake/** – Raw source material awaiting processing
|
|
322
|
+
- **memos/** – Decision memos and internal arguments
|
|
323
|
+
- **meetings/** – Meeting notes and minutes
|
|
324
|
+
- **notes/** – Working notes and lightweight context
|
|
325
|
+
- **onboarding/** – Setup guides and first-day workflows
|
|
326
|
+
- **postmortems/** – Blameless incident reports
|
|
327
|
+
- **prds/** – Product and capability requirement documents
|
|
328
|
+
- **rfcs/** – Architecture and implementation proposals
|
|
329
|
+
- **runbooks/** – Operational procedures and diagnostics
|
|
330
|
+
|
|
331
|
+
## Usage
|
|
332
|
+
|
|
333
|
+
### Adding Documents
|
|
334
|
+
|
|
335
|
+
Place new documents in the appropriate lane directory. Each lane has templates in its \`templates/\` subdirectory.
|
|
336
|
+
|
|
337
|
+
### Consistency Rules
|
|
338
|
+
|
|
339
|
+
- Use the templates as starting points
|
|
340
|
+
- Keep documents focused on one lane's purpose
|
|
341
|
+
- Update related documents when making changes
|
|
342
|
+
- Link to beads issues in \`plan.md\`
|
|
343
|
+
|
|
344
|
+
### Quality Gates
|
|
345
|
+
|
|
346
|
+
Run \`construct docs:verify\` to check documentation quality.
|
|
347
|
+
|
|
348
|
+
\`\`\`bash
|
|
349
|
+
# Validate all documentation
|
|
350
|
+
construct docs:verify
|
|
351
|
+
|
|
352
|
+
# Quick check (critical only)
|
|
353
|
+
construct docs:verify --quick
|
|
354
|
+
|
|
355
|
+
# Attempt to fix issues
|
|
356
|
+
construct docs:verify --fix
|
|
357
|
+
\`\`\`
|
|
358
|
+
|
|
359
|
+
## Maintenance
|
|
360
|
+
|
|
361
|
+
- Review documentation quarterly with \`construct init:update\`
|
|
362
|
+
- Remove stale documents that no longer reflect reality
|
|
363
|
+
- Update README.md when architecture changes`;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
function buildLaneReadme(laneKey) {
|
|
367
|
+
const lane = DOC_LANES[laneKey];
|
|
368
|
+
if (!lane) return `# ${laneKey}\n\nDocumentation lane.`;
|
|
369
|
+
|
|
370
|
+
return `# ${lane.title}
|
|
371
|
+
|
|
372
|
+
${lane.description}
|
|
373
|
+
|
|
374
|
+
## Templates
|
|
375
|
+
|
|
376
|
+
This directory includes starter templates in the \`templates/\` subdirectory.`;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
function buildNotesTemplate() {
|
|
380
|
+
return `# Title
|
|
381
|
+
|
|
382
|
+
Date: {{date}}
|
|
383
|
+
Author: {{author}}
|
|
384
|
+
Related: {{related-issue}}
|
|
385
|
+
|
|
386
|
+
## Context
|
|
387
|
+
|
|
388
|
+
What prompted this note? What problem are we trying to understand?
|
|
389
|
+
|
|
390
|
+
## Observations
|
|
391
|
+
|
|
392
|
+
- Fact 1
|
|
393
|
+
- Fact 2
|
|
394
|
+
- Fact 3
|
|
395
|
+
|
|
396
|
+
## Questions
|
|
397
|
+
|
|
398
|
+
- What don't we know yet?
|
|
399
|
+
- What assumptions need testing?
|
|
400
|
+
|
|
401
|
+
## Next Steps
|
|
402
|
+
|
|
403
|
+
- [ ] Action 1
|
|
404
|
+
- [ ] Action 2`;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
function buildMeetingNotesTemplate() {
|
|
408
|
+
return `# {{meeting-title}}
|
|
409
|
+
|
|
410
|
+
Date: {{date}}
|
|
411
|
+
Time: {{time}}
|
|
412
|
+
Attendees: {{attendees}}
|
|
413
|
+
|
|
414
|
+
## Agenda
|
|
415
|
+
|
|
416
|
+
1. Topic 1
|
|
417
|
+
2. Topic 2
|
|
418
|
+
3. Topic 3
|
|
419
|
+
|
|
420
|
+
## Notes
|
|
421
|
+
|
|
422
|
+
### Topic 1
|
|
423
|
+
|
|
424
|
+
- Key point
|
|
425
|
+
- Decision
|
|
426
|
+
- Action item
|
|
427
|
+
|
|
428
|
+
### Topic 2
|
|
429
|
+
|
|
430
|
+
- Key point
|
|
431
|
+
- Decision
|
|
432
|
+
- Action item
|
|
433
|
+
|
|
434
|
+
## Decisions
|
|
435
|
+
|
|
436
|
+
- Decision 1
|
|
437
|
+
- Decision 2
|
|
438
|
+
|
|
439
|
+
## Action Items
|
|
440
|
+
|
|
441
|
+
- [ ] @owner: Task description (due: {{date}})
|
|
442
|
+
- [ ] @owner: Task description (due: {{date}})`;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
function buildIntakeTemplate() {
|
|
446
|
+
return `# Intake: {{source}}
|
|
447
|
+
|
|
448
|
+
Received: {{date}}
|
|
449
|
+
From: {{from}}
|
|
450
|
+
Priority: {{priority}}
|
|
451
|
+
|
|
452
|
+
## What arrived
|
|
453
|
+
|
|
454
|
+
Brief description of what was received.
|
|
455
|
+
|
|
456
|
+
## Why it matters
|
|
457
|
+
|
|
458
|
+
Why this intake item is important to process.
|
|
459
|
+
|
|
460
|
+
## How it should be ingested
|
|
461
|
+
|
|
462
|
+
Processing instructions:
|
|
463
|
+
- [ ] Step 1
|
|
464
|
+
- [ ] Step 2
|
|
465
|
+
- [ ] Step 3
|
|
466
|
+
|
|
467
|
+
## Notes
|
|
468
|
+
|
|
469
|
+
<!-- Caveats, access concerns, or cleanup notes. -->`;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
function writeIfMissing(filePath, content) {
|
|
473
|
+
if (fs.existsSync(filePath)) {
|
|
474
|
+
skipped.push(path.relative(target, filePath));
|
|
475
|
+
return false;
|
|
476
|
+
}
|
|
477
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
478
|
+
fs.writeFileSync(filePath, content, "utf8");
|
|
479
|
+
created.push(path.relative(target, filePath));
|
|
480
|
+
return true;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
function copyLaneTemplates(laneKey) {
|
|
484
|
+
const lane = DOC_LANES[laneKey];
|
|
485
|
+
if (!lane) return;
|
|
486
|
+
|
|
487
|
+
const laneRoot = path.join(target, "docs", lane.dir);
|
|
488
|
+
writeIfMissing(path.join(laneRoot, "README.md"), buildLaneReadme(laneKey));
|
|
489
|
+
|
|
490
|
+
for (const [index, templateName] of lane.templates.entries()) {
|
|
491
|
+
const outputName = index === 0 ? "_template.md" : templateName.replace(/\.md$/, ".template.md");
|
|
492
|
+
let content;
|
|
493
|
+
|
|
494
|
+
if (templateName === "__notes-template__") {
|
|
495
|
+
content = buildNotesTemplate();
|
|
496
|
+
} else if (templateName === '__meeting-notes-template__') {
|
|
497
|
+
content = buildMeetingNotesTemplate();
|
|
498
|
+
} else if (templateName === "__intake-template__") {
|
|
499
|
+
content = buildIntakeTemplate();
|
|
500
|
+
} else {
|
|
501
|
+
const templatePath = path.join(TEMPLATE_DIR, templateName);
|
|
502
|
+
if (fs.existsSync(templatePath)) {
|
|
503
|
+
content = fs.readFileSync(templatePath, "utf8");
|
|
504
|
+
} else {
|
|
505
|
+
content = `# ${templateName.replace('.md', '')}\n\nTemplate file.`;
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
writeIfMissing(path.join(laneRoot, "templates", outputName), content);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
async function askDocumentationQuestions() {
|
|
514
|
+
// Non-interactive mode: use flags or defaults
|
|
515
|
+
if (skipInteractive) {
|
|
516
|
+
let lanes = [];
|
|
517
|
+
|
|
518
|
+
// Build lanes from explicit flags
|
|
519
|
+
if (withAllDocsFlag) {
|
|
520
|
+
lanes = LANE_ORDER;
|
|
521
|
+
} else if (withDocsFlag) {
|
|
522
|
+
lanes = parseWithDocs(withDocsFlag.split("=")[1]);
|
|
523
|
+
} else {
|
|
524
|
+
// Check individual flags
|
|
525
|
+
if (withAdrsFlag) lanes.push('adrs');
|
|
526
|
+
if (withRfcsFlag) lanes.push('rfcs');
|
|
527
|
+
if (withRunbooksFlag) lanes.push('runbooks');
|
|
528
|
+
if (withPostmortemsFlag) lanes.push('postmortems');
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
return {
|
|
532
|
+
lanes: [...new Set(lanes)], // dedupe
|
|
533
|
+
withArchitecture: withArchitectureFlag,
|
|
534
|
+
withReadme: withReadmeFlag || !fs.existsSync(path.join(target, "README.md")),
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// Interactive mode: project-aware workflow
|
|
539
|
+
const projectType = detectProjectType(target);
|
|
540
|
+
const suggestions = suggestLanes(target, projectType);
|
|
541
|
+
|
|
542
|
+
console.log('');
|
|
543
|
+
console.log('═══════════════════════════════════════════════════════════');
|
|
544
|
+
console.log(' DOCUMENTATION SETUP');
|
|
545
|
+
console.log('═══════════════════════════════════════════════════════════');
|
|
546
|
+
console.log('');
|
|
547
|
+
console.log(`Detected project type: ${projectType.icon} ${projectType.label}`);
|
|
548
|
+
console.log('');
|
|
549
|
+
|
|
550
|
+
// Ask about README.md
|
|
551
|
+
let withReadme = withReadmeFlag;
|
|
552
|
+
if (!withReadmeFlag && !fs.existsSync(path.join(target, "README.md"))) {
|
|
553
|
+
const response = await confirm('Create README.md? [Y/n] ');
|
|
554
|
+
withReadme = response;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
// Build options for multi-select with suggestions pre-checked
|
|
558
|
+
const suggestedLaneNames = new Set(suggestions.map(s => s.lane));
|
|
559
|
+
const laneOptions = LANE_ORDER.map(lane => {
|
|
560
|
+
const suggestion = suggestions.find(s => s.lane === lane);
|
|
561
|
+
return {
|
|
562
|
+
label: DOC_LANES[lane].title,
|
|
563
|
+
value: lane,
|
|
564
|
+
checked: suggestion?.recommended ?? false,
|
|
565
|
+
description: DOC_LANES[lane].description,
|
|
566
|
+
suggestion: suggestion?.reason,
|
|
567
|
+
};
|
|
568
|
+
});
|
|
569
|
+
|
|
570
|
+
// Show checkbox multi-select
|
|
571
|
+
console.log('');
|
|
572
|
+
console.log('Documentation lanes are directories under docs/ that organize specific');
|
|
573
|
+
console.log('types of written work — decisions (ADRs), proposals (RFCs), incidents, etc.');
|
|
574
|
+
console.log('Select which ones you need (recommended items are pre-checked).');
|
|
575
|
+
console.log('');
|
|
576
|
+
|
|
577
|
+
const selectedLanes = await multiSelect({
|
|
578
|
+
title: 'Documentation Lanes',
|
|
579
|
+
instructions: 'Press Enter to confirm your selection',
|
|
580
|
+
options: laneOptions,
|
|
581
|
+
});
|
|
582
|
+
|
|
583
|
+
// Ask about architecture doc
|
|
584
|
+
let withArchitecture = withArchitectureFlag;
|
|
585
|
+
if (!withArchitectureFlag) {
|
|
586
|
+
console.log('');
|
|
587
|
+
const archResponse = await confirm('Create docs/architecture.md? [y/N] ');
|
|
588
|
+
withArchitecture = archResponse;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
return { lanes: selectedLanes, withArchitecture, withReadme };
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
function buildProjectReadme(projectName) {
|
|
595
|
+
return `# ${projectName}
|
|
596
|
+
|
|
597
|
+
## Getting Started
|
|
598
|
+
|
|
599
|
+
This project uses [Construct](https://github.com/geraldmaron/construct) for agentic software development.
|
|
600
|
+
|
|
601
|
+
## Usage
|
|
602
|
+
|
|
603
|
+
From inside OpenCode, Claude Code, or similar agent surfaces:
|
|
604
|
+
|
|
605
|
+
\`\`\`text
|
|
606
|
+
@construct build the feature and ship it when it's verified
|
|
607
|
+
@construct fix the bug
|
|
608
|
+
@construct review the changes before release
|
|
609
|
+
\`\`\`
|
|
610
|
+
|
|
611
|
+
Construct routes work across specialists, maintains project state, and ensures quality gates pass before shipping.
|
|
612
|
+
|
|
613
|
+
## Project Structure
|
|
614
|
+
|
|
615
|
+
- \`AGENTS.md\` — Operating contract for AI agents
|
|
616
|
+
- \`plan.md\` — Current implementation plan
|
|
617
|
+
- \`.cx/context.md\` — Session context and handoff state
|
|
618
|
+
- \`docs/\` — Documentation system (if initialized with \`construct init --docs-preset\`)
|
|
619
|
+
|
|
620
|
+
## Development
|
|
621
|
+
|
|
622
|
+
\`\`\`bash
|
|
623
|
+
# Initialize project with Construct
|
|
624
|
+
construct init --docs-preset=lean
|
|
625
|
+
|
|
626
|
+
# Check documentation quality
|
|
627
|
+
construct docs:verify
|
|
628
|
+
|
|
629
|
+
# Run tests
|
|
630
|
+
npm test
|
|
631
|
+
\`\`\`
|
|
632
|
+
|
|
633
|
+
## CI Enforcement
|
|
634
|
+
|
|
635
|
+
This project enforces documentation quality:
|
|
636
|
+
- README.md must exist and be current
|
|
637
|
+
- AGENTS.md must have required sections
|
|
638
|
+
- plan.md must be updated weekly
|
|
639
|
+
- .cx/context.md must track active work`;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
function preflight(target) {
|
|
643
|
+
console.log('[TRACE init:pre-flight]');
|
|
644
|
+
|
|
645
|
+
// Check git repo
|
|
646
|
+
try {
|
|
647
|
+
execSync('git rev-parse --show-toplevel', { cwd: target, stdio: 'ignore' });
|
|
648
|
+
} catch {
|
|
649
|
+
throw new Error('Not a git repository. Run `git init` first.');
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
// Check working tree clean (silent unless verbose)
|
|
653
|
+
const porcelain = execSync('git status --porcelain', { cwd: target, encoding: 'utf8' }).trim();
|
|
654
|
+
const clean = porcelain === '';
|
|
655
|
+
if (!clean && verbose) {
|
|
656
|
+
console.warn('Warning: Working tree has uncommitted changes');
|
|
657
|
+
if (porcelain.split('\n').length > 10) {
|
|
658
|
+
console.warn(` (${porcelain.split('\n').length} files modified)`);
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
// Check tests (silent unless verbose)
|
|
663
|
+
if (verbose) {
|
|
664
|
+
try {
|
|
665
|
+
execSync('npm test -- --passWithNoTests', { cwd: target, timeout: 30000, stdio: 'ignore' });
|
|
666
|
+
} catch {
|
|
667
|
+
console.warn('Warning: Tests do not pass (or no tests)');
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
return { clean };
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
function initializeBeadsTracker(target) {
|
|
675
|
+
const result = spawnSync("bd", ["init"], {
|
|
676
|
+
cwd: target,
|
|
677
|
+
encoding: "utf8",
|
|
678
|
+
stdio: "inherit",
|
|
679
|
+
killSignal: "SIGTERM",
|
|
680
|
+
timeout: 30_000,
|
|
681
|
+
});
|
|
682
|
+
|
|
683
|
+
if (result.error?.code === "ETIMEDOUT") {
|
|
684
|
+
throw new Error("bd init timed out after 30s");
|
|
685
|
+
}
|
|
686
|
+
if (result.error) {
|
|
687
|
+
throw result.error;
|
|
688
|
+
}
|
|
689
|
+
if (result.status !== 0) {
|
|
690
|
+
throw new Error(`bd init exited with code ${result.status ?? "unknown"}`);
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
async function main() {
|
|
695
|
+
const projectName = inferProjectName(target);
|
|
696
|
+
|
|
697
|
+
const { clean } = preflight(target);
|
|
698
|
+
|
|
699
|
+
if (!quiet) {
|
|
700
|
+
console.log(`Initializing Construct in ${path.relative(process.cwd(), target) || "."}`);
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
// Always create core Construct files
|
|
704
|
+
writeStampedIfMissing({
|
|
705
|
+
targetRoot: target,
|
|
706
|
+
created,
|
|
707
|
+
skipped,
|
|
708
|
+
filePath: path.join(target, "AGENTS.md"),
|
|
709
|
+
content: buildAgentsGuide(projectName),
|
|
710
|
+
generator: "construct/init",
|
|
711
|
+
});
|
|
712
|
+
|
|
713
|
+
writeStampedIfMissing({
|
|
714
|
+
targetRoot: target,
|
|
715
|
+
created,
|
|
716
|
+
skipped,
|
|
717
|
+
filePath: path.join(target, "plan.md"),
|
|
718
|
+
content: buildPlanTemplate(),
|
|
719
|
+
generator: "construct/init",
|
|
720
|
+
});
|
|
721
|
+
|
|
722
|
+
writeStampedIfMissing({
|
|
723
|
+
targetRoot: target,
|
|
724
|
+
created,
|
|
725
|
+
skipped,
|
|
726
|
+
filePath: path.join(target, ".cx", "context.json"),
|
|
727
|
+
content: buildContextJson(projectName),
|
|
728
|
+
generator: "construct/init",
|
|
729
|
+
});
|
|
730
|
+
|
|
731
|
+
writeStampedIfMissing({
|
|
732
|
+
targetRoot: target,
|
|
733
|
+
created,
|
|
734
|
+
skipped,
|
|
735
|
+
filePath: path.join(target, ".cx", "context.md"),
|
|
736
|
+
content: buildContextMarkdown(),
|
|
737
|
+
generator: "construct/init",
|
|
738
|
+
});
|
|
739
|
+
|
|
740
|
+
writeStampedIfMissing({
|
|
741
|
+
targetRoot: target,
|
|
742
|
+
created,
|
|
743
|
+
skipped,
|
|
744
|
+
filePath: path.join(target, ".cx", ".gitkeep"),
|
|
745
|
+
content: "",
|
|
746
|
+
generator: "construct/init",
|
|
747
|
+
});
|
|
748
|
+
|
|
749
|
+
writeStampedIfMissing({
|
|
750
|
+
targetRoot: target,
|
|
751
|
+
created,
|
|
752
|
+
skipped,
|
|
753
|
+
filePath: path.join(target, ".cx", "inbox", ".gitkeep"),
|
|
754
|
+
content: "",
|
|
755
|
+
generator: "construct/init",
|
|
756
|
+
});
|
|
757
|
+
|
|
758
|
+
// Stage .construct/ launcher + sync .claude/ adapters so init produces the
|
|
759
|
+
// same project shape as a fresh `npm install` of the package as a dep.
|
|
760
|
+
|
|
761
|
+
try {
|
|
762
|
+
let pkgVersion = '';
|
|
763
|
+
try {
|
|
764
|
+
pkgVersion = JSON.parse(fs.readFileSync(path.join(ROOT_DIR, 'package.json'), 'utf8')).version || '';
|
|
765
|
+
} catch { /* fall through with empty version */ }
|
|
766
|
+
const stagingPre = fs.existsSync(path.join(target, '.construct', 'run.mjs'));
|
|
767
|
+
const claudePre = fs.existsSync(path.join(target, '.claude', 'settings.json'));
|
|
768
|
+
stageProjectAdapters({
|
|
769
|
+
projectRoot: target,
|
|
770
|
+
packageRoot: ROOT_DIR,
|
|
771
|
+
pkgVersion,
|
|
772
|
+
log: (msg) => console.log(`[init:stage] ${msg}`),
|
|
773
|
+
});
|
|
774
|
+
if (!stagingPre && fs.existsSync(path.join(target, '.construct', 'run.mjs'))) {
|
|
775
|
+
created.push('.construct/ (launcher staged)');
|
|
776
|
+
}
|
|
777
|
+
if (!claudePre && fs.existsSync(path.join(target, '.claude', 'settings.json'))) {
|
|
778
|
+
created.push('.claude/ (agents + settings)');
|
|
779
|
+
}
|
|
780
|
+
} catch (err) {
|
|
781
|
+
console.warn(`⚠️ Adapter staging failed: ${err.message}`);
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
// Written once, never re-copied, so user edits survive subsequent inits
|
|
785
|
+
// and `npm install` rebuilds. Skip-when-exists is the contract.
|
|
786
|
+
|
|
787
|
+
const guideSrc = path.join(ROOT_DIR, 'templates', 'docs', 'construct_guide.md');
|
|
788
|
+
const guideDst = path.join(target, 'construct_guide.md');
|
|
789
|
+
if (fs.existsSync(guideSrc) && !fs.existsSync(guideDst)) {
|
|
790
|
+
try {
|
|
791
|
+
fs.copyFileSync(guideSrc, guideDst);
|
|
792
|
+
created.push('construct_guide.md');
|
|
793
|
+
} catch (err) {
|
|
794
|
+
console.warn(`⚠️ Could not write construct_guide.md: ${err.message}`);
|
|
795
|
+
}
|
|
796
|
+
} else if (fs.existsSync(guideDst)) {
|
|
797
|
+
skipped.push('construct_guide.md');
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
// Auto-init beads
|
|
801
|
+
const beadsMeta = path.join(target, ".beads", "metadata.json");
|
|
802
|
+
if (!fs.existsSync(beadsMeta)) {
|
|
803
|
+
console.log('');
|
|
804
|
+
console.log('═══════════════════════════════════════════════════════════');
|
|
805
|
+
console.log(' TASK TRACKING (beads)');
|
|
806
|
+
console.log('═══════════════════════════════════════════════════════════');
|
|
807
|
+
console.log('');
|
|
808
|
+
console.log('Beads is a local-first issue tracker that keeps your work');
|
|
809
|
+
console.log('organized without depending on external services like Jira.');
|
|
810
|
+
console.log('');
|
|
811
|
+
console.log('Features:');
|
|
812
|
+
console.log(' • Local SQLite database (no cloud required)');
|
|
813
|
+
console.log(' • Issues sync to git (optional)');
|
|
814
|
+
console.log(' • Works offline');
|
|
815
|
+
console.log('');
|
|
816
|
+
console.log("Initializing beads tracker...");
|
|
817
|
+
fs.mkdirSync(path.join(target, ".beads"), { recursive: true });
|
|
818
|
+
try {
|
|
819
|
+
initializeBeadsTracker(target);
|
|
820
|
+
created.push(".beads/ (initialized)");
|
|
821
|
+
} catch (e) {
|
|
822
|
+
console.warn("⚠️ Beads init failed:", e.message);
|
|
823
|
+
}
|
|
824
|
+
console.log('');
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
// Ask about documentation system
|
|
828
|
+
console.log('[TRACE init:docs-ask]');
|
|
829
|
+
|
|
830
|
+
const { lanes, withArchitecture, withReadme, docsPreset: userDocsPreset } = await askDocumentationQuestions();
|
|
831
|
+
|
|
832
|
+
// Create README.md if requested or missing
|
|
833
|
+
const readmePath = path.join(target, "README.md");
|
|
834
|
+
if (withReadme || !fs.existsSync(readmePath)) {
|
|
835
|
+
writeIfMissing(readmePath, buildProjectReadme(projectName));
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
console.log('[TRACE init:docs-write]');
|
|
839
|
+
|
|
840
|
+
// Create documentation system if lanes specified
|
|
841
|
+
if (lanes.length > 0) {
|
|
842
|
+
// Create docs/README.md
|
|
843
|
+
writeIfMissing(
|
|
844
|
+
path.join(target, "docs", "README.md"),
|
|
845
|
+
buildDocsReadme(projectName)
|
|
846
|
+
);
|
|
847
|
+
|
|
848
|
+
// Create selected lanes
|
|
849
|
+
for (const laneKey of lanes) {
|
|
850
|
+
copyLaneTemplates(laneKey);
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
// Create architecture.md if requested
|
|
854
|
+
if (withArchitecture) {
|
|
855
|
+
writeIfMissing(
|
|
856
|
+
path.join(target, "docs", "architecture.md"),
|
|
857
|
+
`# ${projectName} Architecture\n\n## Overview\n\n## Components\n\n## Data Flow\n\n## Deployment\n`
|
|
858
|
+
);
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
// Output results (respect quiet mode)
|
|
863
|
+
if (!quiet) {
|
|
864
|
+
console.log('');
|
|
865
|
+
console.log('═══════════════════════════════════════════════════════════');
|
|
866
|
+
console.log(' SETUP COMPLETE');
|
|
867
|
+
console.log('═══════════════════════════════════════════════════════════');
|
|
868
|
+
console.log('');
|
|
869
|
+
|
|
870
|
+
if (created.length) {
|
|
871
|
+
console.log('Created:');
|
|
872
|
+
for (const file of created) console.log(` ${file}`);
|
|
873
|
+
console.log('');
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
if (skipped.length && verbose) {
|
|
877
|
+
console.log('Already exist (skipped):');
|
|
878
|
+
for (const file of skipped) console.log(` ${file}`);
|
|
879
|
+
console.log('');
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
console.log(`${created.length} created, ${skipped.length} skipped`);
|
|
883
|
+
console.log('');
|
|
884
|
+
|
|
885
|
+
if (lanes.length > 0) {
|
|
886
|
+
console.log('Documentation lanes enabled:');
|
|
887
|
+
for (const lane of lanes) {
|
|
888
|
+
console.log(` • ${DOC_LANES[lane].title}`);
|
|
889
|
+
}
|
|
890
|
+
console.log('');
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
// Seed the vector index from the project's existing docs so the agent
|
|
895
|
+
// can compare new intake against established PRDs/RFCs/ADRs from day
|
|
896
|
+
// one. Best-effort: skipped silently when Postgres + embedding model
|
|
897
|
+
// aren't ready yet (DATABASE_URL unset, no ONNX cache). User can re-run
|
|
898
|
+
// `construct ingest` or `construct setup` later to seed manually.
|
|
899
|
+
|
|
900
|
+
try {
|
|
901
|
+
const { syncFileStateToSql } = await import('./storage/sync.mjs');
|
|
902
|
+
const projectName = path.basename(target);
|
|
903
|
+
const seed = await syncFileStateToSql(target, { env: process.env, project: projectName });
|
|
904
|
+
if (seed?.status === 'ok' && (seed.documentsSynced || 0) > 0) {
|
|
905
|
+
console.log(`\n🔍 Indexed existing project material: ${seed.documentsSynced} doc(s), ${seed.embeddingsSynced || 0} embeddings.`);
|
|
906
|
+
console.log(` The agent can now compare new intake (.cx/inbox/, docs/intake/) against your existing PRDs, ADRs, and notes.`);
|
|
907
|
+
}
|
|
908
|
+
} catch { /* silent — corpus seeding is best-effort, not a setup blocker */ }
|
|
909
|
+
|
|
910
|
+
// ── Devcontainer recipe ────────────────────────────────────────────────
|
|
911
|
+
if (withDevcontainerFlag) {
|
|
912
|
+
const dcDir = path.join(target, '.devcontainer');
|
|
913
|
+
const templateDir = path.join(ROOT_DIR, 'templates', 'devcontainer');
|
|
914
|
+
fs.mkdirSync(dcDir, { recursive: true });
|
|
915
|
+
for (const file of ['devcontainer.json', 'Dockerfile.devcontainer']) {
|
|
916
|
+
const src = path.join(templateDir, file);
|
|
917
|
+
const dst = path.join(dcDir, file);
|
|
918
|
+
if (fs.existsSync(dst)) { skipped.push(`.devcontainer/${file}`); continue; }
|
|
919
|
+
if (fs.existsSync(src)) { fs.copyFileSync(src, dst); created.push(`.devcontainer/${file}`); }
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
if (!quiet) {
|
|
924
|
+
console.log('═══════════════════════════════════════════════════════════');
|
|
925
|
+
console.log(' NEXT STEPS');
|
|
926
|
+
console.log('═══════════════════════════════════════════════════════════');
|
|
927
|
+
console.log('');
|
|
928
|
+
console.log('1. Review AGENTS.md');
|
|
929
|
+
console.log(' Operating rules and guidelines for this project');
|
|
930
|
+
console.log('');
|
|
931
|
+
console.log('2. Edit plan.md');
|
|
932
|
+
console.log(' Add your current work and tasks');
|
|
933
|
+
console.log('');
|
|
934
|
+
if (lanes.includes('intake')) {
|
|
935
|
+
console.log('3. Use Intake');
|
|
936
|
+
console.log(' Drop files in .cx/inbox/ for processing');
|
|
937
|
+
console.log(' Run: construct intake');
|
|
938
|
+
console.log('');
|
|
939
|
+
}
|
|
940
|
+
console.log('4. Start working');
|
|
941
|
+
console.log(' Address @construct in your editor to begin');
|
|
942
|
+
console.log('');
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
// Auto-start services by default (silent unless verbose)
|
|
946
|
+
const shouldStart = !args.includes('--no-start') &&
|
|
947
|
+
(args.includes('--auto-start') || !interactive);
|
|
948
|
+
|
|
949
|
+
if (shouldStart) {
|
|
950
|
+
if (verbose) console.log('\nStarting services...');
|
|
951
|
+
try {
|
|
952
|
+
const { startServices } = await import('./service-manager.mjs');
|
|
953
|
+
const { os } = await import('node:os');
|
|
954
|
+
const { detectDockerCompose } = await import('./setup.mjs');
|
|
955
|
+
|
|
956
|
+
const homeDir = os.homedir();
|
|
957
|
+
const composeRunner = detectDockerCompose();
|
|
958
|
+
|
|
959
|
+
const { results } = await startServices({
|
|
960
|
+
rootDir: target,
|
|
961
|
+
homeDir,
|
|
962
|
+
detectDockerComposeFn: () => composeRunner,
|
|
963
|
+
});
|
|
964
|
+
|
|
965
|
+
if (!quiet) {
|
|
966
|
+
console.log('\nServices:');
|
|
967
|
+
for (const svc of results) {
|
|
968
|
+
if (svc.status === 'started' || svc.status === 'reused') {
|
|
969
|
+
const status = svc.status === 'reused' ? ' (running)' : '';
|
|
970
|
+
const url = svc.url ? ` ${svc.url}` : '';
|
|
971
|
+
console.log(` ${svc.name}${status}${url}`);
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
// Show Telemetry credentials if running locally
|
|
976
|
+
const telemetrySvc = results.find(r => r.name === 'Telemetry' && r.url?.includes('localhost'));
|
|
977
|
+
if (telemetrySvc) {
|
|
978
|
+
console.log(`\nTelemetry: ${telemetrySvc.url}`);
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
const dashboardPort = process.env.DASHBOARD_PORT || '4242';
|
|
982
|
+
console.log(`\nDashboard: http://127.0.0.1:${dashboardPort}`);
|
|
983
|
+
}
|
|
984
|
+
} catch (error) {
|
|
985
|
+
console.error(`Services could not be started: ${error.message}`);
|
|
986
|
+
console.error(`Run 'construct dev' manually when ready`);
|
|
987
|
+
}
|
|
988
|
+
} else if (!quiet) {
|
|
989
|
+
console.log(`\nRun 'construct dev' to start services`);
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
994
|
+
main().catch((error) => {
|
|
995
|
+
console.error(`Error: ${error.message}`);
|
|
996
|
+
process.exit(1);
|
|
997
|
+
});
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
export default main;
|