@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.
Files changed (471) hide show
  1. package/.env.example +9 -6
  2. package/LICENSE +201 -21
  3. package/README.md +132 -257
  4. package/agents/contracts.json +387 -0
  5. package/agents/prompts/cx-accessibility.md +8 -0
  6. package/agents/prompts/cx-ai-engineer.md +92 -0
  7. package/agents/prompts/cx-architect.md +10 -2
  8. package/agents/prompts/cx-business-strategist.md +13 -0
  9. package/agents/prompts/cx-data-analyst.md +80 -0
  10. package/agents/prompts/cx-data-engineer.md +4 -0
  11. package/agents/prompts/cx-debugger.md +8 -0
  12. package/agents/prompts/cx-designer.md +19 -0
  13. package/agents/prompts/cx-devil-advocate.md +4 -0
  14. package/agents/prompts/cx-docs-keeper.md +128 -0
  15. package/agents/prompts/cx-engineer.md +13 -0
  16. package/agents/prompts/cx-evaluator.md +4 -0
  17. package/agents/prompts/cx-explorer.md +12 -0
  18. package/agents/prompts/cx-legal-compliance.md +13 -0
  19. package/agents/prompts/cx-operations.md +13 -0
  20. package/agents/prompts/cx-orchestrator.md +107 -4
  21. package/agents/prompts/cx-platform-engineer.md +75 -0
  22. package/agents/prompts/cx-product-manager.md +8 -0
  23. package/agents/prompts/cx-qa.md +100 -0
  24. package/agents/prompts/cx-rd-lead.md +13 -0
  25. package/agents/prompts/cx-release-manager.md +8 -0
  26. package/agents/prompts/cx-researcher.md +21 -1
  27. package/agents/prompts/cx-reviewer.md +8 -0
  28. package/agents/prompts/cx-security.md +104 -0
  29. package/agents/prompts/cx-sre.md +104 -0
  30. package/agents/prompts/cx-test-automation.md +4 -0
  31. package/agents/prompts/cx-trace-reviewer.md +7 -3
  32. package/agents/prompts/cx-ux-researcher.md +4 -0
  33. package/agents/registry.json +365 -90
  34. package/agents/role-manifests.json +217 -0
  35. package/bin/construct +3345 -141
  36. package/bin/construct-postinstall.mjs +87 -0
  37. package/commands/build/feature.md +6 -6
  38. package/commands/plan/feature.md +6 -5
  39. package/commands/plan/requirements.md +1 -1
  40. package/commands/ship/status.md +1 -1
  41. package/commands/work/drive.md +3 -3
  42. package/commands/work/optimize-prompts.md +3 -3
  43. package/db/{migrations → schema}/001_init.sql +2 -0
  44. package/db/schema/002_pgvector.sql +182 -0
  45. package/db/schema/003_intake.sql +47 -0
  46. package/examples/README.md +85 -0
  47. package/examples/internal/roles/architect/bad/clever-plan-without-contracts.md +30 -0
  48. package/examples/internal/roles/architect/golden/explicit-tradeoff-before-plan.md +23 -0
  49. package/examples/internal/roles/engineer/bad/speculative-abstraction.md +30 -0
  50. package/examples/internal/roles/engineer/golden/read-before-write.md +28 -0
  51. package/examples/internal/roles/orchestrator/bad/everything-becomes-multi-agent.md +29 -0
  52. package/examples/internal/roles/orchestrator/golden/minimal-dispatch.md +22 -0
  53. package/examples/internal/roles/qa/bad/coverage-theater.md +30 -0
  54. package/examples/internal/roles/qa/golden/regression-gate.md +23 -0
  55. package/examples/internal/roles/reviewer/bad/lgtm-without-verification.md +29 -0
  56. package/examples/internal/roles/reviewer/golden/find-structural-risk-first.md +28 -0
  57. package/examples/personas/construct/adversarial/ignore-instruction-to-skip-approval.md +22 -0
  58. package/examples/personas/construct/bad/commit-without-approval.md +30 -0
  59. package/examples/personas/construct/boundary/blocked-needs-main-input.md +29 -0
  60. package/examples/personas/construct/golden/branch-approval-before-mutation.md +36 -0
  61. package/examples/personas/construct/golden/focused-direct-answer.md +28 -0
  62. package/examples/provider-plugin/README.md +34 -0
  63. package/examples/provider-plugin/index.mjs +74 -0
  64. package/examples/provider-plugin/package.json +15 -0
  65. package/examples/seed-observations/README.md +38 -0
  66. package/examples/seed-observations/anti-patterns.md +44 -0
  67. package/examples/seed-observations/decisions.md +36 -0
  68. package/examples/seed-observations/patterns.md +42 -0
  69. package/lib/agent-contracts-enforce.mjs +158 -0
  70. package/lib/agent-contracts.mjs +231 -0
  71. package/lib/agents/postconditions.mjs +126 -0
  72. package/lib/agents/schema.mjs +124 -0
  73. package/lib/artifact-capture.mjs +183 -0
  74. package/lib/audit-trail.mjs +149 -0
  75. package/lib/auto-docs.mjs +245 -65
  76. package/lib/beads/auto-close.mjs +126 -0
  77. package/lib/beads/drift.mjs +171 -0
  78. package/lib/beads-automation.mjs +542 -0
  79. package/lib/beads-client.mjs +518 -0
  80. package/lib/beads-lock.mjs +377 -0
  81. package/lib/beads-optimistic.mjs +365 -0
  82. package/lib/bootstrap/built-ins.mjs +136 -0
  83. package/lib/bootstrap/lazy-install.mjs +161 -0
  84. package/lib/bootstrap/resources.mjs +120 -0
  85. package/lib/bootstrap.mjs +105 -0
  86. package/lib/cache-governor.js +213 -0
  87. package/lib/cache-strategy-anthropic.js +62 -0
  88. package/lib/cache-strategy-google.js +79 -0
  89. package/lib/cache-strategy-none.js +30 -0
  90. package/lib/cache-strategy-openai.js +48 -0
  91. package/lib/cache-strategy.js +91 -0
  92. package/lib/claude-allow.mjs +149 -0
  93. package/lib/cli-commands.mjs +413 -258
  94. package/lib/codex-config.mjs +3 -1
  95. package/lib/comment-lint.mjs +55 -6
  96. package/lib/completions.mjs +21 -6
  97. package/lib/config/alias.mjs +56 -0
  98. package/lib/config/project-config.mjs +335 -0
  99. package/lib/config/schema.mjs +159 -0
  100. package/lib/context-router.mjs +308 -0
  101. package/lib/cost-ledger.mjs +177 -0
  102. package/lib/cost.mjs +171 -10
  103. package/lib/dashboard-static.mjs +158 -0
  104. package/lib/deployment-mode.mjs +86 -0
  105. package/lib/deprecate.mjs +49 -0
  106. package/lib/dispatch-batch.js +183 -0
  107. package/lib/distill.mjs +21 -8
  108. package/lib/doc-stamp.mjs +164 -0
  109. package/lib/doc-verify.mjs +119 -0
  110. package/lib/docs-routing.mjs +89 -0
  111. package/lib/docs-verify.mjs +417 -0
  112. package/lib/doctor/audit.mjs +71 -0
  113. package/lib/doctor/cli.mjs +99 -0
  114. package/lib/doctor/escalate.mjs +29 -0
  115. package/lib/doctor/index.mjs +140 -0
  116. package/lib/doctor/report.mjs +170 -0
  117. package/lib/doctor/watchers/bd-watch.mjs +117 -0
  118. package/lib/doctor/watchers/cost.mjs +130 -0
  119. package/lib/doctor/watchers/disk.mjs +122 -0
  120. package/lib/doctor/watchers/handoffs.mjs +33 -0
  121. package/lib/doctor/watchers/process-pressure.mjs +60 -0
  122. package/lib/doctor/watchers/service-health.mjs +188 -0
  123. package/lib/document-extract.mjs +288 -0
  124. package/lib/document-ingest.mjs +230 -0
  125. package/lib/drop.mjs +282 -0
  126. package/lib/embed/approval-queue.mjs +176 -0
  127. package/lib/embed/artifact.mjs +349 -0
  128. package/lib/embed/authority-guard.mjs +155 -0
  129. package/lib/embed/cli.mjs +408 -0
  130. package/lib/embed/config.mjs +355 -0
  131. package/lib/embed/conflict-detection.mjs +264 -0
  132. package/lib/embed/customer-profiles.mjs +480 -0
  133. package/lib/embed/daemon.mjs +1309 -0
  134. package/lib/embed/demand-fetch.mjs +449 -0
  135. package/lib/embed/docs-lifecycle.mjs +349 -0
  136. package/lib/embed/inbox-live-watcher.mjs +119 -0
  137. package/lib/embed/inbox.mjs +343 -0
  138. package/lib/embed/intake-metrics.mjs +190 -0
  139. package/lib/embed/jobs/vector-sync.mjs +198 -0
  140. package/lib/embed/notifications.mjs +75 -0
  141. package/lib/embed/output.mjs +79 -0
  142. package/lib/embed/providers/github.mjs +295 -0
  143. package/lib/embed/providers/jira.mjs +192 -0
  144. package/lib/embed/providers/linear.mjs +186 -0
  145. package/lib/embed/providers/registry.mjs +115 -0
  146. package/lib/embed/providers/slack.mjs +203 -0
  147. package/lib/embed/recommendation-store.mjs +378 -0
  148. package/lib/embed/roadmap.mjs +374 -0
  149. package/lib/embed/role-framing.mjs +110 -0
  150. package/lib/embed/scheduler.mjs +99 -0
  151. package/lib/embed/semantic.mjs +325 -0
  152. package/lib/embed/snapshot.mjs +191 -0
  153. package/lib/embed/supervision.mjs +235 -0
  154. package/lib/embed/target-resolver.mjs +186 -0
  155. package/lib/embed/worker.mjs +62 -0
  156. package/lib/embed/workspaces.mjs +297 -0
  157. package/lib/engine/chunker-headings.mjs +110 -0
  158. package/lib/engine/compressor-heuristic.mjs +100 -0
  159. package/lib/engine/consolidate.mjs +287 -0
  160. package/lib/engine/contracts.mjs +126 -0
  161. package/lib/engine/defaults.mjs +129 -0
  162. package/lib/engine/eval-retrieval.mjs +148 -0
  163. package/lib/engine/fuser-rrf.mjs +62 -0
  164. package/lib/engine/index.mjs +37 -0
  165. package/lib/engine/registry.mjs +146 -0
  166. package/lib/engine/reranker-mmr.mjs +90 -0
  167. package/lib/engine/tokens.mjs +77 -0
  168. package/lib/entity-store.mjs +280 -0
  169. package/lib/env-config.mjs +69 -4
  170. package/lib/evals/retrieval-bench.mjs +159 -0
  171. package/lib/evaluator-optimizer.mjs +317 -0
  172. package/lib/features.mjs +159 -29
  173. package/lib/gates-audit.mjs +236 -0
  174. package/lib/git-hooks/prepare-commit-msg +58 -0
  175. package/lib/handoffs/cleanup.mjs +159 -0
  176. package/lib/handoffs/contract.mjs +162 -0
  177. package/lib/handoffs/inventory.mjs +120 -0
  178. package/lib/headhunt.mjs +28 -47
  179. package/lib/health-check.mjs +399 -0
  180. package/lib/hook-health.mjs +442 -0
  181. package/lib/hooks/_lib/log.mjs +82 -0
  182. package/lib/hooks/adaptive-lint.mjs +26 -2
  183. package/lib/hooks/agent-tracker.mjs +171 -14
  184. package/lib/hooks/audit-reads.mjs +109 -0
  185. package/lib/hooks/audit-trail.mjs +153 -0
  186. package/lib/hooks/bash-output-logger.mjs +71 -0
  187. package/lib/hooks/block-no-verify.mjs +41 -0
  188. package/lib/hooks/ci-status-check.mjs +82 -0
  189. package/lib/hooks/comment-lint.mjs +29 -7
  190. package/lib/hooks/config-protection.mjs +39 -18
  191. package/lib/hooks/context-watch.mjs +137 -0
  192. package/lib/hooks/context-window-recovery.mjs +4 -20
  193. package/lib/hooks/dep-audit.mjs +16 -0
  194. package/lib/hooks/doc-coupling-check.mjs +80 -0
  195. package/lib/hooks/edit-accumulator.mjs +3 -0
  196. package/lib/hooks/edit-error-recovery.mjs +3 -0
  197. package/lib/hooks/edit-guard.mjs +45 -4
  198. package/lib/hooks/env-check.mjs +3 -0
  199. package/lib/hooks/guard-bash.mjs +58 -1
  200. package/lib/hooks/mcp-audit.mjs +18 -20
  201. package/lib/hooks/mcp-health-check.mjs +36 -0
  202. package/lib/hooks/model-fallback.mjs +42 -56
  203. package/lib/hooks/policy-engine.mjs +209 -0
  204. package/lib/hooks/post-merge-docs-check.mjs +63 -0
  205. package/lib/hooks/pre-compact.mjs +4 -24
  206. package/lib/hooks/pre-push-gate.mjs +200 -37
  207. package/lib/hooks/proactive-activation.mjs +284 -0
  208. package/lib/hooks/read-tracker.mjs +27 -4
  209. package/lib/hooks/readme-age-check.mjs +77 -0
  210. package/lib/hooks/registry-sync.mjs +12 -5
  211. package/lib/hooks/scan-secrets.mjs +50 -7
  212. package/lib/hooks/session-optimize.mjs +311 -0
  213. package/lib/hooks/session-start.mjs +287 -28
  214. package/lib/hooks/stop-notify.mjs +236 -96
  215. package/lib/hooks/stop-typecheck.mjs +13 -1
  216. package/lib/hooks/test-watch.mjs +68 -0
  217. package/lib/host-capabilities.mjs +31 -10
  218. package/lib/init-docs.mjs +731 -291
  219. package/lib/init-unified.mjs +1000 -0
  220. package/lib/init-update.mjs +168 -0
  221. package/lib/init.mjs +107 -0
  222. package/lib/install/first-invocation.mjs +119 -0
  223. package/lib/install/stage-project.mjs +69 -0
  224. package/lib/intake/classify.mjs +278 -0
  225. package/lib/intake/feedback.mjs +273 -0
  226. package/lib/intake/filesystem-queue.mjs +158 -0
  227. package/lib/intake/intake-config.mjs +132 -0
  228. package/lib/intake/postgres-queue.mjs +198 -0
  229. package/lib/intake/prepare.mjs +139 -0
  230. package/lib/intake/queue.mjs +83 -0
  231. package/lib/intake/session-prelude.mjs +50 -0
  232. package/lib/integrations/intake-integrations.mjs +740 -0
  233. package/lib/intent-classifier.mjs +253 -0
  234. package/lib/knowledge/layout.mjs +72 -0
  235. package/lib/knowledge/rag.mjs +331 -0
  236. package/lib/knowledge/search.mjs +315 -0
  237. package/lib/knowledge/trends.mjs +261 -0
  238. package/lib/logger.mjs +85 -0
  239. package/lib/mcp/broker.mjs +124 -0
  240. package/lib/mcp/server.mjs +554 -854
  241. package/lib/mcp/tools/document.mjs +132 -0
  242. package/lib/mcp/tools/memory.mjs +209 -0
  243. package/lib/mcp/tools/project.mjs +349 -0
  244. package/lib/mcp/tools/skills.mjs +409 -0
  245. package/lib/mcp/tools/storage.mjs +60 -0
  246. package/lib/mcp/tools/telemetry.mjs +315 -0
  247. package/lib/mcp/tools/workflow.mjs +112 -0
  248. package/lib/mcp-catalog.json +54 -3
  249. package/lib/mcp-manager.mjs +96 -48
  250. package/lib/mcp-platform-config.mjs +22 -22
  251. package/lib/memory-stats.mjs +121 -0
  252. package/lib/mode-commands.mjs +124 -0
  253. package/lib/model-free-selector.mjs +184 -0
  254. package/lib/model-pricing.mjs +152 -0
  255. package/lib/model-registry.mjs +226 -0
  256. package/lib/model-router.mjs +362 -386
  257. package/lib/observation-store.mjs +391 -0
  258. package/lib/ollama-manager.mjs +428 -0
  259. package/lib/opencode-config.mjs +13 -3
  260. package/lib/opencode-runtime-plugin.mjs +70 -38
  261. package/lib/opencode-telemetry.mjs +64 -6
  262. package/lib/orchestration-policy.mjs +509 -6
  263. package/lib/overrides/resolver.mjs +207 -0
  264. package/lib/parity.mjs +147 -0
  265. package/lib/paths.mjs +33 -0
  266. package/lib/performance/generate.mjs +212 -0
  267. package/lib/plugin-registry.mjs +268 -0
  268. package/lib/policy/engine.mjs +130 -0
  269. package/lib/policy/unified-gates.mjs +96 -0
  270. package/lib/project-detection.mjs +129 -0
  271. package/lib/project-init-shared.mjs +272 -0
  272. package/lib/project-profile.mjs +447 -0
  273. package/lib/prompt-composer.js +434 -0
  274. package/lib/prompt-metadata.mjs +1 -1
  275. package/lib/provider-capabilities-anthropic.js +44 -0
  276. package/lib/provider-capabilities-deepseek.js +37 -0
  277. package/lib/provider-capabilities-generic.js +26 -0
  278. package/lib/provider-capabilities-google.js +47 -0
  279. package/lib/provider-capabilities-openai.js +45 -0
  280. package/lib/provider-capabilities.js +142 -0
  281. package/lib/providers/atlassian-confluence/index.mjs +103 -0
  282. package/lib/providers/atlassian-jira/index.mjs +100 -0
  283. package/lib/providers/auth-manager.mjs +126 -0
  284. package/lib/providers/circuit-breaker.mjs +124 -0
  285. package/lib/providers/contract.mjs +93 -0
  286. package/lib/providers/github/index.mjs +126 -0
  287. package/lib/providers/registry.mjs +184 -0
  288. package/lib/providers/salesforce/index.mjs +100 -0
  289. package/lib/providers/slack/index.mjs +80 -0
  290. package/lib/reflect.mjs +137 -0
  291. package/lib/research-lint.mjs +164 -0
  292. package/lib/resources/budget.mjs +259 -0
  293. package/lib/role-preload.mjs +21 -6
  294. package/lib/roles/approval-surface.mjs +54 -0
  295. package/lib/roles/cli.mjs +118 -0
  296. package/lib/roles/event-bus.mjs +79 -0
  297. package/lib/roles/fence.mjs +84 -0
  298. package/lib/roles/gateway.mjs +260 -0
  299. package/lib/roles/hook-emit.mjs +37 -0
  300. package/lib/roles/manifest.mjs +48 -0
  301. package/lib/roles/router.mjs +27 -0
  302. package/lib/runtime-pressure.mjs +360 -0
  303. package/lib/schema-artifact.mjs +134 -0
  304. package/lib/schema-infer.mjs +551 -0
  305. package/lib/server/auth.mjs +168 -0
  306. package/lib/server/chat.mjs +336 -0
  307. package/lib/server/cors.mjs +77 -0
  308. package/lib/server/csrf.mjs +91 -0
  309. package/lib/server/index.mjs +1927 -78
  310. package/lib/server/insights.mjs +765 -0
  311. package/lib/server/rate-limit.mjs +91 -0
  312. package/lib/server/static/assets/index-ab25c707.js +70 -0
  313. package/lib/server/static/assets/index-f0c80a2b.css +1 -0
  314. package/lib/server/static/index.html +12 -817
  315. package/lib/server/telemetry-login.mjs +108 -0
  316. package/lib/server/webhook.mjs +510 -0
  317. package/lib/service-manager.mjs +522 -58
  318. package/lib/services/pattern-promotion-service.mjs +167 -0
  319. package/lib/services/telemetry-backend.mjs +178 -0
  320. package/lib/session-store.mjs +374 -0
  321. package/lib/setup-prompts.mjs +96 -0
  322. package/lib/setup.mjs +523 -36
  323. package/lib/skills-apply.mjs +280 -0
  324. package/lib/skills-scope.mjs +118 -0
  325. package/lib/status.mjs +261 -70
  326. package/lib/storage/admin.mjs +355 -0
  327. package/lib/storage/backend.mjs +2 -1
  328. package/lib/storage/backup.mjs +347 -0
  329. package/lib/storage/embeddings-engine.mjs +133 -0
  330. package/lib/storage/embeddings-legacy.mjs +85 -0
  331. package/lib/storage/embeddings-local.mjs +108 -0
  332. package/lib/storage/embeddings-ollama.mjs +78 -0
  333. package/lib/storage/embeddings-openai.mjs +85 -0
  334. package/lib/storage/embeddings.mjs +92 -33
  335. package/lib/storage/file-lock.mjs +130 -0
  336. package/lib/storage/fusion.mjs +95 -0
  337. package/lib/storage/hybrid-query.mjs +34 -27
  338. package/lib/storage/migrations.mjs +187 -0
  339. package/lib/storage/postgres-backup.mjs +124 -0
  340. package/lib/storage/sql-store.mjs +5 -15
  341. package/lib/storage/state-source.mjs +12 -13
  342. package/lib/storage/store-version.mjs +115 -0
  343. package/lib/storage/sync.mjs +144 -35
  344. package/lib/storage/unified-storage.mjs +550 -0
  345. package/lib/storage/vector-client.mjs +286 -0
  346. package/lib/storage/vector-store.mjs +71 -30
  347. package/lib/task-graph/generate.mjs +135 -0
  348. package/lib/task-graph/schema.mjs +81 -0
  349. package/lib/task-graph/store.mjs +71 -0
  350. package/lib/telemetry/backends/local.mjs +62 -0
  351. package/lib/telemetry/backends/{langfuse.mjs → remote.mjs} +27 -14
  352. package/lib/telemetry/backfill.mjs +180 -0
  353. package/lib/telemetry/eval-datasets.mjs +203 -0
  354. package/lib/telemetry/{langfuse-ingest.mjs → ingest.mjs} +26 -20
  355. package/lib/telemetry/intent-verifications.mjs +86 -0
  356. package/lib/telemetry/llm-judge.mjs +350 -0
  357. package/lib/telemetry/model-pricing-catalog.mjs +557 -0
  358. package/lib/telemetry/setup.mjs +151 -0
  359. package/lib/telemetry/skill-calls.mjs +78 -0
  360. package/lib/telemetry/team-rollup.mjs +4 -4
  361. package/lib/token-engine.js +117 -0
  362. package/lib/token-estimator-anthropic.js +15 -0
  363. package/lib/token-estimator-deepseek.js +13 -0
  364. package/lib/token-estimator-default.js +13 -0
  365. package/lib/token-estimator-google.js +13 -0
  366. package/lib/token-estimator-openai.js +13 -0
  367. package/lib/toolkit-env.mjs +1 -1
  368. package/lib/tty-prompts.mjs +211 -0
  369. package/lib/uninstall/uninstall.mjs +423 -0
  370. package/lib/update.mjs +115 -0
  371. package/lib/upgrade.mjs +141 -0
  372. package/lib/validator.mjs +51 -2
  373. package/lib/validators/skills.mjs +142 -0
  374. package/lib/wireframe.mjs +422 -0
  375. package/lib/worker/entrypoint.mjs +241 -0
  376. package/lib/worker/evidence.mjs +107 -0
  377. package/lib/worker/run.mjs +154 -0
  378. package/lib/worker/trace.mjs +182 -0
  379. package/lib/workflow-state.mjs +14 -18
  380. package/package.json +21 -8
  381. package/personas/construct.md +53 -51
  382. package/platforms/claude/CLAUDE.md +1 -1
  383. package/platforms/claude/settings.template.json +115 -68
  384. package/platforms/opencode/config.template.json +3 -7
  385. package/rules/common/agents.md +11 -38
  386. package/rules/common/beads-hygiene.md +75 -0
  387. package/rules/common/code-review.md +11 -100
  388. package/rules/common/coding-style.md +5 -3
  389. package/rules/common/comments.md +30 -111
  390. package/rules/common/commit-approval.md +53 -0
  391. package/rules/common/cx-agent-routing.md +1 -0
  392. package/rules/common/development-workflow.md +23 -41
  393. package/rules/common/doc-ownership.md +54 -0
  394. package/rules/common/efficiency.md +57 -0
  395. package/rules/common/framing.md +76 -0
  396. package/rules/common/git-workflow.md +2 -4
  397. package/rules/common/patterns.md +3 -7
  398. package/rules/common/performance.md +15 -31
  399. package/rules/common/release-gates.md +69 -0
  400. package/rules/common/research.md +107 -0
  401. package/rules/common/security.md +6 -6
  402. package/rules/common/skill-composition.md +67 -0
  403. package/rules/common/testing.md +15 -27
  404. package/rules/golang/hooks.md +0 -4
  405. package/rules/policy/bootstrap.yaml +11 -0
  406. package/rules/policy/drive.yaml +8 -0
  407. package/rules/policy/task.yaml +9 -0
  408. package/rules/policy/workflow.yaml +9 -0
  409. package/rules/python/hooks.md +0 -4
  410. package/rules/swift/hooks.md +0 -4
  411. package/rules/typescript/hooks.md +0 -4
  412. package/rules/web/hooks.md +0 -2
  413. package/{sync-agents.mjs → scripts/sync-agents.mjs} +306 -61
  414. package/skills/ai/prompt-optimizer.md +7 -7
  415. package/skills/compliance/ai-disclosure.md +58 -0
  416. package/skills/compliance/data-privacy.md +46 -0
  417. package/skills/compliance/license-audit.md +40 -0
  418. package/skills/compliance/regulatory-review.md +61 -0
  419. package/skills/docs/document-ingest-workflow.md +52 -0
  420. package/skills/docs/evidence-ingest-workflow.md +9 -0
  421. package/skills/docs/init-docs.md +51 -18
  422. package/skills/docs/product-intelligence-workflow.md +12 -0
  423. package/skills/docs/product-signal-workflow.md +4 -0
  424. package/skills/docs/research-workflow.md +23 -8
  425. package/skills/docs/runbook-workflow.md +1 -1
  426. package/skills/operating/orchestration-reference.md +151 -0
  427. package/skills/roles/architect.md +5 -0
  428. package/skills/roles/engineer.md +32 -0
  429. package/skills/roles/operator.md +12 -0
  430. package/skills/roles/reviewer.md +23 -0
  431. package/skills/routing.md +1 -1
  432. package/templates/devcontainer/Dockerfile.devcontainer +38 -0
  433. package/templates/devcontainer/devcontainer.json +31 -0
  434. package/templates/distribution/bootstrap.ps1 +142 -0
  435. package/templates/distribution/bootstrap.sh +196 -0
  436. package/templates/distribution/run.mjs +187 -0
  437. package/templates/docs/adr.md +44 -8
  438. package/templates/docs/changelog-entry.md +43 -0
  439. package/templates/docs/construct_guide.md +149 -0
  440. package/templates/docs/evidence-brief.md +2 -2
  441. package/templates/docs/meta-prd.md +140 -19
  442. package/templates/docs/onboarding.md +57 -0
  443. package/templates/docs/prd.md +159 -15
  444. package/templates/docs/research-brief.md +4 -4
  445. package/templates/homebrew/construct.rb +67 -0
  446. package/langfuse/docker-compose.yml +0 -82
  447. package/lib/eval-harness.mjs +0 -59
  448. package/lib/hooks/bootstrap-guard.mjs +0 -90
  449. package/lib/hooks/console-warn.mjs +0 -43
  450. package/lib/hooks/continuation-enforcer.mjs +0 -72
  451. package/lib/hooks/drive-guard.mjs +0 -89
  452. package/lib/hooks/mcp-task-scope.mjs +0 -47
  453. package/lib/hooks/task-completed-guard.mjs +0 -43
  454. package/lib/hooks/teammate-idle-guard.mjs +0 -54
  455. package/lib/hooks/workflow-guard.mjs +0 -62
  456. package/lib/prompt-composer.mjs +0 -196
  457. package/lib/review.mjs +0 -429
  458. package/lib/server/static/app.js +0 -841
  459. package/lib/telemetry/langfuse-model-sync.mjs +0 -270
  460. package/rules/common/hooks.md +0 -35
  461. package/rules/zh/README.md +0 -113
  462. package/rules/zh/agents.md +0 -55
  463. package/rules/zh/code-review.md +0 -129
  464. package/rules/zh/coding-style.md +0 -53
  465. package/rules/zh/development-workflow.md +0 -49
  466. package/rules/zh/git-workflow.md +0 -29
  467. package/rules/zh/hooks.md +0 -35
  468. package/rules/zh/patterns.md +0 -36
  469. package/rules/zh/performance.md +0 -60
  470. package/rules/zh/security.md +0 -34
  471. 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;