@geraldmaron/construct 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (471) hide show
  1. package/.env.example +10 -7
  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 +1116 -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 +525 -38
  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,423 @@
1
+ /**
2
+ * lib/uninstall/uninstall.mjs — interactive teardown for Construct state.
3
+ *
4
+ * npm 7+ has no uninstall hook, so this runs as an explicit command the
5
+ * user invokes before `npm uninstall @geraldmaron/construct`. It probes
6
+ * what Construct created (per-project artefacts and per-machine state),
7
+ * groups the findings into categories, and lets the user choose what to
8
+ * remove. Shared resources the user almost certainly uses for other things
9
+ * — Docker itself, Homebrew, the pgvector image, user-installed CLIs —
10
+ * are never touched; they appear in the final report as informational
11
+ * follow-ups.
12
+ *
13
+ * Categories carry a default risk level:
14
+ * - auto: removed when --yes is passed; included in the interactive list pre-checked
15
+ * - ask : never auto-removed; interactive list shows them unchecked
16
+ *
17
+ * Flags:
18
+ * --dry-run print the plan, change nothing
19
+ * --yes non-interactive; remove auto-risk items, skip ask-risk items
20
+ * --yes --all non-interactive; remove both auto- and ask-risk items
21
+ * --keep-state only remove project-scope launcher + adapters; preserve .cx/, ~/.construct, Postgres
22
+ * --scope=project|machine|all limit which categories are evaluated (default: all)
23
+ */
24
+
25
+ import fs from 'node:fs';
26
+ import path from 'node:path';
27
+ import os from 'node:os';
28
+ import { spawnSync } from 'node:child_process';
29
+ import { fileURLToPath } from 'node:url';
30
+
31
+ import { multiSelect } from '../tty-prompts.mjs';
32
+ import { getUserConfigDir, getUserEnvPath } from '../env-config.mjs';
33
+
34
+ const __dirname = fileURLToPath(new URL('.', import.meta.url));
35
+ const PKG_ROOT = path.resolve(__dirname, '..', '..');
36
+
37
+ const POSTGRES_CONTAINER = 'construct-postgres';
38
+
39
+ export function parseArgs(argv) {
40
+ const args = {
41
+ dryRun: false,
42
+ yes: false,
43
+ all: false,
44
+ keepState: false,
45
+ scope: 'all',
46
+ home: os.homedir(),
47
+ cwd: process.cwd(),
48
+ };
49
+ for (const arg of argv) {
50
+ if (arg === '--dry-run') args.dryRun = true;
51
+ else if (arg === '--yes' || arg === '-y') args.yes = true;
52
+ else if (arg === '--all') args.all = true;
53
+ else if (arg === '--keep-state') args.keepState = true;
54
+ else if (arg.startsWith('--scope=')) args.scope = arg.slice('--scope='.length);
55
+ else if (arg.startsWith('--home=')) args.home = arg.slice('--home='.length);
56
+ else if (arg.startsWith('--cwd=')) args.cwd = arg.slice('--cwd='.length);
57
+ }
58
+ if (!['project', 'machine', 'all'].includes(args.scope)) {
59
+ throw new Error(`Invalid --scope=${args.scope}; expected project|machine|all`);
60
+ }
61
+ return args;
62
+ }
63
+
64
+ function buildCategories({ cwd, home }) {
65
+ const dotConstruct = path.join(cwd, '.construct');
66
+ const dotClaudeAgents = path.join(cwd, '.claude', 'agents');
67
+ const dotClaudeCommands = path.join(cwd, '.claude', 'commands');
68
+ const dotClaudeSettings = path.join(cwd, '.claude', 'settings.json');
69
+ const dotCx = path.join(cwd, '.cx');
70
+ const projectAgentsMd = path.join(cwd, 'AGENTS.md');
71
+ const projectPlanMd = path.join(cwd, 'plan.md');
72
+ const userHome = home;
73
+ const userConstructDir = getUserConfigDir(userHome);
74
+ const userConfigEnv = getUserEnvPath(userHome);
75
+ const userEmbedCache = path.join(userConstructDir, 'cache', 'embeddings');
76
+ const userVectorIndex = path.join(userConstructDir, 'vector');
77
+ const userWorkspace = path.join(userConstructDir, 'workspace');
78
+ const userPgComposeDir = path.join(userConstructDir, 'services', 'postgres');
79
+ const userCxDir = path.join(userHome, '.cx');
80
+
81
+ return [
82
+ {
83
+ id: 'project-launcher',
84
+ scope: 'project',
85
+ risk: 'auto',
86
+ label: '.construct/ launcher directory',
87
+ detect: () => existsAny(dotConstruct),
88
+ describe: () => `Removes ${rel(cwd, dotConstruct)} (run.mjs, bootstrap shims, version, cache).`,
89
+ execute: () => removePath(dotConstruct),
90
+ },
91
+ {
92
+ id: 'project-agents',
93
+ scope: 'project',
94
+ risk: 'auto',
95
+ label: '.claude/agents/ (Construct personas)',
96
+ detect: () => {
97
+ const manifest = readAgentManifest(dotClaudeAgents);
98
+ return manifest.length > 0;
99
+ },
100
+ describe: () => {
101
+ const manifest = readAgentManifest(dotClaudeAgents);
102
+ return `Removes ${manifest.length} agent files listed in .claude/agents/.construct-manifest.`;
103
+ },
104
+ execute: () => removeManifestEntries(dotClaudeAgents),
105
+ },
106
+ {
107
+ id: 'project-commands',
108
+ scope: 'project',
109
+ risk: 'auto',
110
+ label: '.claude/commands/ (Construct slash commands)',
111
+ detect: () => {
112
+ const manifest = readAgentManifest(dotClaudeCommands);
113
+ return manifest.length > 0;
114
+ },
115
+ describe: () => {
116
+ const manifest = readAgentManifest(dotClaudeCommands);
117
+ return `Removes ${manifest.length} command files listed in .claude/commands/.construct-manifest.`;
118
+ },
119
+ execute: () => removeManifestEntries(dotClaudeCommands),
120
+ },
121
+ {
122
+ id: 'project-settings',
123
+ scope: 'project',
124
+ risk: 'auto',
125
+ label: '.claude/settings.json (un-merge Construct keys)',
126
+ detect: () => detectConstructSettings(dotClaudeSettings),
127
+ describe: () => 'Strips Construct hooks + known mcpServers from settings.json; preserves any user-added keys. Deletes the file if it becomes empty.',
128
+ execute: () => unmergeSettings(dotClaudeSettings),
129
+ },
130
+ {
131
+ id: 'project-cx',
132
+ scope: 'project',
133
+ risk: 'ask',
134
+ label: '.cx/ (per-project session state)',
135
+ detect: () => existsAny(dotCx),
136
+ describe: () => `Removes ${rel(cwd, dotCx)}. May contain in-progress work captured by Construct.`,
137
+ execute: () => removePath(dotCx),
138
+ },
139
+ {
140
+ id: 'project-scaffold-files',
141
+ scope: 'project',
142
+ risk: 'ask',
143
+ label: 'AGENTS.md and plan.md',
144
+ detect: () => existsAny(projectAgentsMd) || existsAny(projectPlanMd),
145
+ describe: () => 'Scaffolded by `construct init`. Often heavily edited by users; left alone unless explicitly removed.',
146
+ execute: () => {
147
+ const removed = [];
148
+ if (removePath(projectAgentsMd)) removed.push('AGENTS.md');
149
+ if (removePath(projectPlanMd)) removed.push('plan.md');
150
+ return removed.length ? `removed ${removed.join(', ')}` : 'nothing to remove';
151
+ },
152
+ },
153
+ {
154
+ id: 'machine-workspace',
155
+ scope: 'machine',
156
+ risk: 'auto',
157
+ label: '~/.construct/workspace and ~/.construct/vector',
158
+ detect: () => existsAny(userWorkspace) || existsAny(userVectorIndex),
159
+ describe: () => 'Removes the per-user working dir and fallback JSON vector index. Both are regenerated on next use.',
160
+ execute: () => {
161
+ const removed = [];
162
+ if (removePath(userWorkspace)) removed.push('workspace/');
163
+ if (removePath(userVectorIndex)) removed.push('vector/');
164
+ return removed.length ? `removed ${removed.join(', ')}` : 'nothing to remove';
165
+ },
166
+ },
167
+ {
168
+ id: 'machine-cx',
169
+ scope: 'machine',
170
+ risk: 'auto',
171
+ label: '~/.cx (daemon state, logs)',
172
+ detect: () => existsAny(userCxDir),
173
+ describe: () => `Removes ${rel(userHome, userCxDir)}. Daemon PIDs, telemetry, setup logs; all regenerated on next setup.`,
174
+ execute: () => removePath(userCxDir),
175
+ },
176
+ {
177
+ id: 'machine-embedding-cache',
178
+ scope: 'machine',
179
+ risk: 'ask',
180
+ label: '~/.construct/cache/embeddings (~22 MB ONNX model)',
181
+ detect: () => existsAny(userEmbedCache),
182
+ describe: () => 'Removes the cached ONNX embedding model. Skip if you plan to reinstall soon — re-downloading takes a minute.',
183
+ execute: () => removePath(userEmbedCache),
184
+ },
185
+ {
186
+ id: 'machine-config-env',
187
+ scope: 'machine',
188
+ risk: 'ask',
189
+ label: '~/.construct/config.env (API keys + consent flags)',
190
+ detect: () => existsAny(userConfigEnv),
191
+ describe: () => 'Removes saved API keys (Anthropic/OpenAI/telemetry/etc.) and bootstrap consent. Skip if you intend to reinstall and reuse the keys.',
192
+ execute: () => removePath(userConfigEnv),
193
+ },
194
+ {
195
+ id: 'machine-postgres-container',
196
+ scope: 'machine',
197
+ risk: 'ask',
198
+ label: `Docker container "${POSTGRES_CONTAINER}" (stop + remove, including data)`,
199
+ detect: () => dockerContainerExists(POSTGRES_CONTAINER),
200
+ describe: () => 'Stops and removes the construct-postgres Docker container AND its data volume. The pgvector image stays cached. This destroys indexed observations — skip if you might restore.',
201
+ execute: () => removeDockerContainer(POSTGRES_CONTAINER),
202
+ },
203
+ {
204
+ id: 'machine-postgres-compose',
205
+ scope: 'machine',
206
+ risk: 'auto',
207
+ label: '~/.construct/services/postgres/ (compose file)',
208
+ detect: () => existsAny(userPgComposeDir),
209
+ describe: () => 'Removes the local docker-compose.yml. Run after removing the container.',
210
+ execute: () => removePath(userPgComposeDir),
211
+ },
212
+ ];
213
+ }
214
+
215
+ export async function runUninstall(rawArgs = []) {
216
+ const args = parseArgs(rawArgs);
217
+ const categories = buildCategories({ cwd: args.cwd, home: args.home });
218
+
219
+ const inScope = categories.filter((cat) => {
220
+ if (args.scope !== 'all' && cat.scope !== args.scope) return false;
221
+ if (args.keepState && (cat.scope === 'machine' || cat.id === 'project-cx' || cat.id === 'project-scaffold-files')) return false;
222
+ return cat.detect();
223
+ });
224
+
225
+ if (inScope.length === 0) {
226
+ console.log('[uninstall] No Construct state detected in the selected scope. Nothing to do.');
227
+ printFollowups();
228
+ return { removed: [], skipped: [], canceled: false };
229
+ }
230
+
231
+ let toRemove;
232
+ if (args.dryRun) {
233
+ printPlan(inScope, args);
234
+ return { removed: [], skipped: inScope.map((c) => c.id), canceled: false, dryRun: true };
235
+ }
236
+
237
+ if (args.yes) {
238
+ toRemove = inScope.filter((cat) => cat.risk === 'auto' || args.all).map((cat) => cat.id);
239
+ } else if (!process.stdin.isTTY) {
240
+ console.error('[uninstall] No TTY available. Re-run with --yes (auto-risk only) or --yes --all (everything).');
241
+ process.exit(2);
242
+ } else {
243
+ const picked = await promptCategories(inScope);
244
+ if (picked === null) {
245
+ console.log('[uninstall] Canceled.');
246
+ return { removed: [], skipped: [], canceled: true };
247
+ }
248
+ toRemove = picked;
249
+ }
250
+
251
+ const removed = [];
252
+ const skipped = [];
253
+ for (const cat of inScope) {
254
+ if (!toRemove.includes(cat.id)) {
255
+ skipped.push({ id: cat.id, label: cat.label });
256
+ continue;
257
+ }
258
+ try {
259
+ const detail = cat.execute();
260
+ removed.push({ id: cat.id, label: cat.label, detail: detail || 'removed' });
261
+ console.log(` ✓ ${cat.label}${detail ? ` — ${detail}` : ''}`);
262
+ } catch (err) {
263
+ removed.push({ id: cat.id, label: cat.label, detail: `error: ${err.message}` });
264
+ console.error(` ✗ ${cat.label} — ${err.message}`);
265
+ }
266
+ }
267
+
268
+ console.log('');
269
+ printSummary(removed, skipped);
270
+ printFollowups();
271
+ return { removed, skipped, canceled: false };
272
+ }
273
+
274
+ async function promptCategories(categories) {
275
+ const options = categories.map((cat) => ({
276
+ label: cat.label,
277
+ value: cat.id,
278
+ checked: cat.risk === 'auto',
279
+ description: cat.describe(),
280
+ meta: cat.risk === 'auto' ? 'pre-checked' : 'ask',
281
+ }));
282
+ try {
283
+ return await multiSelect({
284
+ title: 'Construct uninstall',
285
+ instructions: 'Space to toggle · a all · i invert · Enter to apply · q to cancel',
286
+ options,
287
+ });
288
+ } catch (err) {
289
+ if (/Canceled/.test(err.message || '')) return null;
290
+ throw err;
291
+ }
292
+ }
293
+
294
+ function printPlan(categories, args) {
295
+ console.log(`[uninstall] dry-run plan (scope=${args.scope}${args.keepState ? ', keep-state' : ''}):`);
296
+ for (const cat of categories) {
297
+ const mark = cat.risk === 'auto' ? '✓' : '◐';
298
+ console.log(` ${mark} ${cat.label}`);
299
+ console.log(` ${cat.describe()}`);
300
+ }
301
+ console.log('');
302
+ console.log('Pass --yes to remove ✓ items, --yes --all to remove both ✓ and ◐, or run without --dry-run for interactive.');
303
+ printFollowups();
304
+ }
305
+
306
+ function printSummary(removed, skipped) {
307
+ console.log(`[uninstall] removed ${removed.length}, skipped ${skipped.length}.`);
308
+ }
309
+
310
+ function printFollowups() {
311
+ console.log('');
312
+ console.log('Follow-ups (run by hand if you want):');
313
+ console.log(' npm uninstall @geraldmaron/construct # drop the package itself');
314
+ console.log(' docker rmi pgvector/pgvector:pg16 # remove the cached pgvector image');
315
+ console.log(' brew uninstall cm cass # if installed by `construct init`');
316
+ }
317
+
318
+ function rel(base, target) {
319
+ const r = path.relative(base, target);
320
+ return r || target;
321
+ }
322
+
323
+ function existsAny(p) {
324
+ try { fs.statSync(p); return true; } catch { return false; }
325
+ }
326
+
327
+ function removePath(p) {
328
+ try {
329
+ fs.rmSync(p, { recursive: true, force: true });
330
+ return true;
331
+ } catch (err) {
332
+ if (err && err.code === 'ENOENT') return false;
333
+ throw err;
334
+ }
335
+ }
336
+
337
+ function readAgentManifest(dir) {
338
+ const manifestPath = path.join(dir, '.construct-manifest');
339
+ if (!fs.existsSync(manifestPath)) return [];
340
+ return fs.readFileSync(manifestPath, 'utf8').split('\n').filter(Boolean);
341
+ }
342
+
343
+ function removeManifestEntries(dir) {
344
+ const entries = readAgentManifest(dir);
345
+ let removedCount = 0;
346
+ for (const name of entries) {
347
+ const full = path.join(dir, name);
348
+ if (removePath(full)) removedCount += 1;
349
+ }
350
+ removePath(path.join(dir, '.construct-manifest'));
351
+ if (fs.existsSync(dir)) {
352
+ try {
353
+ const remaining = fs.readdirSync(dir);
354
+ if (remaining.length === 0) fs.rmdirSync(dir);
355
+ } catch { /* dir disappeared between checks */ }
356
+ }
357
+ return `removed ${removedCount} entries`;
358
+ }
359
+
360
+ function detectConstructSettings(filePath) {
361
+ if (!fs.existsSync(filePath)) return false;
362
+ let settings;
363
+ try { settings = JSON.parse(fs.readFileSync(filePath, 'utf8')); }
364
+ catch { return false; }
365
+ if (!settings || typeof settings !== 'object') return false;
366
+ if (settings.hooks && Object.keys(settings.hooks).length > 0) return true;
367
+ const constructMcp = constructMcpKeys();
368
+ if (settings.mcpServers && constructMcp.some((k) => k in settings.mcpServers)) return true;
369
+ return false;
370
+ }
371
+
372
+ function unmergeSettings(filePath) {
373
+ if (!fs.existsSync(filePath)) return 'no settings.json';
374
+ let settings;
375
+ try { settings = JSON.parse(fs.readFileSync(filePath, 'utf8')); }
376
+ catch { return 'settings.json was malformed; left untouched'; }
377
+
378
+ const changes = [];
379
+ if (settings.hooks && Object.keys(settings.hooks).length > 0) {
380
+ delete settings.hooks;
381
+ changes.push('hooks');
382
+ }
383
+ if (settings.mcpServers) {
384
+ let removedMcp = 0;
385
+ for (const key of constructMcpKeys()) {
386
+ if (key in settings.mcpServers) { delete settings.mcpServers[key]; removedMcp += 1; }
387
+ }
388
+ if (Object.keys(settings.mcpServers).length === 0) delete settings.mcpServers;
389
+ if (removedMcp > 0) changes.push(`${removedMcp} mcpServers`);
390
+ }
391
+
392
+ const remaining = Object.keys(settings);
393
+ if (remaining.length === 0) {
394
+ fs.unlinkSync(filePath);
395
+ return `removed (file was Construct-only) [stripped: ${changes.join(', ') || 'nothing'}]`;
396
+ }
397
+ fs.writeFileSync(filePath, JSON.stringify(settings, null, 2) + '\n', 'utf8');
398
+ return `stripped ${changes.join(', ') || 'nothing'}; preserved ${remaining.join(', ')}`;
399
+ }
400
+
401
+ function constructMcpKeys() {
402
+ try {
403
+ const registry = JSON.parse(fs.readFileSync(path.join(PKG_ROOT, 'agents', 'registry.json'), 'utf8'));
404
+ return Object.keys(registry.mcpServers || {});
405
+ } catch { return []; }
406
+ }
407
+
408
+ function dockerContainerExists(name) {
409
+ const probe = spawnSync('docker', ['ps', '-a', '--filter', `name=^/${name}$`, '--format', '{{.Names}}'], {
410
+ encoding: 'utf8',
411
+ });
412
+ if (probe.status !== 0) return false;
413
+ return probe.stdout.trim() === name;
414
+ }
415
+
416
+ function removeDockerContainer(name) {
417
+ const stop = spawnSync('docker', ['stop', name], { encoding: 'utf8' });
418
+ const rm = spawnSync('docker', ['rm', '-v', name], { encoding: 'utf8' });
419
+ if (rm.status !== 0) {
420
+ throw new Error((rm.stderr || '').trim() || 'docker rm failed');
421
+ }
422
+ return stop.status === 0 ? 'stopped and removed (with volume)' : 'removed (with volume)';
423
+ }
package/lib/update.mjs ADDED
@@ -0,0 +1,115 @@
1
+ /**
2
+ * update.mjs — source-checkout update flow for Construct.
3
+ *
4
+ * Reinstalls the current checkout globally, then runs sync and doctor from the
5
+ * checkout code so host adapters and health checks reflect the newly pulled
6
+ * repo state.
7
+ */
8
+
9
+ import fs from 'node:fs';
10
+ import path from 'node:path';
11
+ import { spawnSync } from 'node:child_process';
12
+
13
+ const PACKAGE_NAME = '@geraldmaron/construct';
14
+
15
+ function readPackageJson(dir) {
16
+ const packagePath = path.join(dir, 'package.json');
17
+ if (!fs.existsSync(packagePath)) return null;
18
+
19
+ try {
20
+ return JSON.parse(fs.readFileSync(packagePath, 'utf8'));
21
+ } catch {
22
+ return null;
23
+ }
24
+ }
25
+
26
+ export function isConstructSourceRoot(dir) {
27
+ const pkg = readPackageJson(dir);
28
+ if (!pkg) return false;
29
+
30
+ return pkg.name === PACKAGE_NAME
31
+ && pkg.bin?.construct === 'bin/construct'
32
+ && fs.existsSync(path.join(dir, 'bin', 'construct'))
33
+ && fs.existsSync(path.join(dir, 'scripts', 'sync-agents.mjs'))
34
+ && fs.existsSync(path.join(dir, 'lib', 'cli-commands.mjs'));
35
+ }
36
+
37
+ export function findConstructSourceRoot(startDir) {
38
+ let currentDir = path.resolve(startDir);
39
+
40
+ while (true) {
41
+ if (isConstructSourceRoot(currentDir)) return currentDir;
42
+ const parentDir = path.dirname(currentDir);
43
+ if (parentDir === currentDir) return '';
44
+ currentDir = parentDir;
45
+ }
46
+ }
47
+
48
+ export function buildUpdatePlan({ cwd }) {
49
+ const sourceRoot = findConstructSourceRoot(cwd);
50
+ if (!sourceRoot) {
51
+ throw new Error('construct update must be run from inside a Construct source checkout');
52
+ }
53
+
54
+ const pkg = readPackageJson(sourceRoot) ?? {};
55
+ const binPath = path.join(sourceRoot, 'bin', 'construct');
56
+
57
+ return {
58
+ sourceRoot,
59
+ version: typeof pkg.version === 'string' ? pkg.version : 'unknown',
60
+ steps: [
61
+ {
62
+ label: 'Install current checkout globally',
63
+ command: 'npm',
64
+ args: ['install', '-g', '.'],
65
+ cwd: sourceRoot,
66
+ },
67
+ {
68
+ label: 'Regenerate host adapters',
69
+ command: process.execPath,
70
+ args: [binPath, 'sync', '--no-docs'],
71
+ cwd: sourceRoot,
72
+ },
73
+ {
74
+ label: 'Run health checks',
75
+ command: process.execPath,
76
+ args: [binPath, 'doctor'],
77
+ cwd: sourceRoot,
78
+ },
79
+ ],
80
+ };
81
+ }
82
+
83
+ function formatCommand(command, args) {
84
+ return [command, ...args].join(' ');
85
+ }
86
+
87
+ function runStep(step, { spawn, env }) {
88
+ const result = spawn(step.command, step.args, {
89
+ cwd: step.cwd,
90
+ env,
91
+ stdio: 'inherit',
92
+ });
93
+ if (result?.error) throw result.error;
94
+ if ((result?.status ?? 1) !== 0) {
95
+ throw new Error(`${step.label} failed with exit code ${result.status ?? 1}`);
96
+ }
97
+ }
98
+
99
+ export function runUpdate({ cwd, env = process.env, spawn = spawnSync, stdout = process.stdout }) {
100
+ const plan = buildUpdatePlan({ cwd });
101
+
102
+ stdout.write('Construct Update\n');
103
+ stdout.write('════════════════\n\n');
104
+ stdout.write(`Source checkout: ${plan.sourceRoot}\n`);
105
+ stdout.write(`Version: ${plan.version}\n\n`);
106
+
107
+ for (const step of plan.steps) {
108
+ stdout.write(`→ ${step.label}\n`);
109
+ stdout.write(` ${formatCommand(step.command, step.args)}\n`);
110
+ runStep(step, { spawn, env });
111
+ }
112
+
113
+ stdout.write('\n✓ Construct updated from current checkout.\n');
114
+ return plan;
115
+ }
@@ -0,0 +1,141 @@
1
+ /**
2
+ * upgrade.mjs — npm upgrade flow for Construct end users.
3
+ *
4
+ * Fetches latest version from npm, upgrades global install, then re-syncs
5
+ * all editor adapters and runs health checks.
6
+ */
7
+
8
+ import fs from 'node:fs';
9
+ import path from 'node:path';
10
+ import { spawnSync } from 'node:child_process';
11
+ import { fileURLToPath } from 'node:url';
12
+
13
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
14
+ const ROOT_DIR = path.resolve(__dirname, '..');
15
+ const PACKAGE_NAME = '@geraldmaron/construct';
16
+
17
+ function getCurrentVersion() {
18
+ const pkgPath = path.join(ROOT_DIR, 'package.json');
19
+ if (!fs.existsSync(pkgPath)) return 'unknown';
20
+ try {
21
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
22
+ return pkg.version || 'unknown';
23
+ } catch {
24
+ return 'unknown';
25
+ }
26
+ }
27
+
28
+ function getLatestVersion() {
29
+ const result = spawnSync('npm', ['view', PACKAGE_NAME, 'version'], {
30
+ encoding: 'utf8',
31
+ stdio: ['ignore', 'pipe', 'ignore'],
32
+ });
33
+ if (result.status !== 0) return null;
34
+ return result.stdout.trim();
35
+ }
36
+
37
+ function compareVersions(current, latest) {
38
+ if (current === 'unknown' || !latest) return null;
39
+
40
+ const parse = (v) => v.split('.').map((n) => parseInt(n, 10) || 0);
41
+ const [cMajor, cMinor, cPatch] = parse(current);
42
+ const [lMajor, lMinor, lPatch] = parse(latest);
43
+
44
+ if (lMajor > cMajor) return 'major';
45
+ if (lMajor < cMajor) return 'older';
46
+ if (lMinor > cMinor) return 'minor';
47
+ if (lMinor < cMinor) return 'older';
48
+ if (lPatch > cPatch) return 'patch';
49
+ if (lPatch < cPatch) return 'older';
50
+ return 'current';
51
+ }
52
+
53
+ export function runUpgrade({ cwd = process.cwd(), env = process.env, stdout = process.stdout, yes = false } = {}) {
54
+ const currentVersion = getCurrentVersion();
55
+
56
+ stdout.write('Construct Upgrade Check\n');
57
+ stdout.write('═══════════════════════\n\n');
58
+ stdout.write(`Current version: ${currentVersion}\n`);
59
+
60
+ const latestVersion = getLatestVersion();
61
+ if (!latestVersion) {
62
+ stdout.write('\n');
63
+ stdout.write('Unable to fetch latest version from npm.\n');
64
+ stdout.write('Check your network connection and try again.\n');
65
+ return { success: false, reason: 'npm_view_failed' };
66
+ }
67
+
68
+ stdout.write(`Latest version: ${latestVersion}\n`);
69
+
70
+ const upgradeType = compareVersions(currentVersion, latestVersion);
71
+ if (!upgradeType) {
72
+ stdout.write('\nUnable to compare versions.\n');
73
+ return { success: false, reason: 'version_compare_failed' };
74
+ }
75
+
76
+ if (upgradeType === 'current' || upgradeType === 'older') {
77
+ stdout.write('\n');
78
+ if (upgradeType === 'current') {
79
+ stdout.write('✓ You are already on the latest version.\n');
80
+ } else {
81
+ stdout.write('⚠ Your version appears to be ahead of npm (development build).\n');
82
+ }
83
+ return { success: true, upgraded: false, reason: upgradeType };
84
+ }
85
+
86
+ stdout.write('\n');
87
+ stdout.write(`Upgrade available: ${currentVersion} → ${latestVersion} (${upgradeType})\n`);
88
+ stdout.write('\n');
89
+
90
+ if (!yes) {
91
+ stdout.write('Proceed with upgrade? [y/N] ');
92
+ const response = fs.readFileSync(0, 'utf8').trim().toLowerCase();
93
+ if (!['y', 'yes', 'yeah', 'sure', 'ok', 'go', 'proceed'].includes(response)) {
94
+ stdout.write('Upgrade cancelled.\n');
95
+ return { success: true, upgraded: false, reason: 'user_cancelled' };
96
+ }
97
+ }
98
+
99
+ stdout.write('Upgrading...\n\n');
100
+
101
+ const steps = [
102
+ {
103
+ label: 'Fetching latest from npm',
104
+ command: 'npm',
105
+ args: ['install', '-g', PACKAGE_NAME],
106
+ },
107
+ {
108
+ label: 'Regenerating host adapters',
109
+ command: 'construct',
110
+ args: ['sync', '--no-docs'],
111
+ },
112
+ {
113
+ label: 'Running health checks',
114
+ command: 'construct',
115
+ args: ['doctor'],
116
+ },
117
+ ];
118
+
119
+ for (const step of steps) {
120
+ stdout.write(`→ ${step.label}\n`);
121
+ const result = spawnSync(step.command, step.args, {
122
+ cwd,
123
+ env,
124
+ stdio: 'inherit',
125
+ });
126
+
127
+ if (result.error) {
128
+ stdout.write(`\n✗ ${step.label} failed: ${result.error.message}\n`);
129
+ return { success: false, reason: 'step_failed', step: step.label };
130
+ }
131
+
132
+ if (result.status !== 0) {
133
+ stdout.write(`\n✗ ${step.label} failed with exit code ${result.status}\n`);
134
+ return { success: false, reason: 'step_failed', step: step.label };
135
+ }
136
+ }
137
+
138
+ stdout.write('\n');
139
+ stdout.write(`✓ Upgraded to ${latestVersion}\n`);
140
+ return { success: true, upgraded: true, from: currentVersion, to: latestVersion };
141
+ }