@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,126 @@
1
+ /**
2
+ * lib/providers/github/index.mjs — GitHub data-source provider.
3
+ *
4
+ * Capabilities: read, search, webhook.
5
+ *
6
+ * Auth: reads `GITHUB_TOKEN` (or `GH_TOKEN`) from env. Without a token,
7
+ * unauthenticated GitHub API rate limits apply (60 req/h/IP).
8
+ *
9
+ * Config (per call):
10
+ * - repo: "owner/name" — required for read
11
+ * - query: free-text search (issues, PRs, code) for search
12
+ * - kind: 'issues' | 'prs' | 'code' (default 'issues')
13
+ *
14
+ * The provider is intentionally narrow in v1 — issue + PR + code search +
15
+ * a single repo metadata read. Webhook signature verification is in place
16
+ * so consumers can mount a receiver without re-implementing it.
17
+ */
18
+
19
+ import crypto from 'node:crypto';
20
+
21
+ const API = 'https://api.github.com';
22
+
23
+ function authHeader(env) {
24
+ const token = env.GITHUB_TOKEN || env.GH_TOKEN;
25
+ return token ? { Authorization: `Bearer ${token}` } : {};
26
+ }
27
+
28
+ async function ghFetch(pathOrUrl, env, init = {}) {
29
+ const url = pathOrUrl.startsWith('http') ? pathOrUrl : `${API}${pathOrUrl}`;
30
+ const headers = {
31
+ 'Accept': 'application/vnd.github+json',
32
+ 'X-GitHub-Api-Version': '2022-11-28',
33
+ 'User-Agent': 'construct-github-provider',
34
+ ...authHeader(env),
35
+ ...(init.headers || {}),
36
+ };
37
+ const res = await fetch(url, { ...init, headers });
38
+ if (!res.ok) {
39
+ const body = await res.text().catch(() => '');
40
+ const err = new Error(`GitHub ${res.status} ${res.statusText}: ${body.slice(0, 200)}`);
41
+ err.status = res.status;
42
+ err.rateLimitRemaining = Number(res.headers.get('x-ratelimit-remaining'));
43
+ throw err;
44
+ }
45
+ return res.json();
46
+ }
47
+
48
+ export function create({ env = process.env } = {}) {
49
+ return {
50
+ meta: {
51
+ id: 'github',
52
+ displayName: 'GitHub',
53
+ capabilities: ['read', 'search', 'webhook'],
54
+ description: 'Repos, issues, PRs, and code search.',
55
+ },
56
+
57
+ configSchema: {
58
+ $schema: 'https://json-schema.org/draft/2020-12/schema',
59
+ type: 'object',
60
+ properties: {
61
+ repo: { type: 'string', pattern: '^[^/]+/[^/]+$', description: 'owner/name' },
62
+ kind: { enum: ['issues', 'prs', 'code'], default: 'issues' },
63
+ query: { type: 'string' },
64
+ webhookSecret: { type: 'string', description: 'webhook signature secret' },
65
+ },
66
+ },
67
+
68
+ async health() {
69
+ try {
70
+ const r = await fetch(`${API}/rate_limit`, { headers: authHeader(env) });
71
+ if (!r.ok) return { ok: false, detail: `rate_limit endpoint ${r.status}` };
72
+ const data = await r.json();
73
+ const remaining = data?.resources?.core?.remaining;
74
+ return {
75
+ ok: true,
76
+ detail: `${data?.resources?.core?.limit || 0} req/h limit; ${remaining ?? 0} remaining`,
77
+ authenticated: !!(env.GITHUB_TOKEN || env.GH_TOKEN),
78
+ };
79
+ } catch (err) {
80
+ return { ok: false, detail: err.message };
81
+ }
82
+ },
83
+
84
+ async read(config) {
85
+ if (!config?.repo) throw new Error('github.read: config.repo required (owner/name)');
86
+ return ghFetch(`/repos/${config.repo}`, env);
87
+ },
88
+
89
+ async search(config) {
90
+ const kind = config?.kind || 'issues';
91
+ const query = config?.query;
92
+ if (!query) throw new Error('github.search: config.query required');
93
+ const path = kind === 'code'
94
+ ? `/search/code?q=${encodeURIComponent(query)}`
95
+ : kind === 'prs'
96
+ ? `/search/issues?q=${encodeURIComponent(query + ' is:pr')}`
97
+ : `/search/issues?q=${encodeURIComponent(query + ' is:issue')}`;
98
+ const data = await ghFetch(path, env);
99
+ return Array.isArray(data?.items) ? data.items : [];
100
+ },
101
+
102
+ async webhook(config, request) {
103
+ const secret = config?.webhookSecret;
104
+ if (!secret) {
105
+ return { ok: false, error: 'webhookSecret not configured' };
106
+ }
107
+ const signature = request?.headers?.['x-hub-signature-256'];
108
+ if (!signature || !signature.startsWith('sha256=')) {
109
+ return { ok: false, error: 'missing or malformed signature header' };
110
+ }
111
+ const expected = 'sha256=' + crypto
112
+ .createHmac('sha256', secret)
113
+ .update(typeof request.body === 'string' ? request.body : Buffer.from(request.body))
114
+ .digest('hex');
115
+ const valid = crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
116
+ if (!valid) return { ok: false, error: 'signature mismatch' };
117
+ return {
118
+ ok: true,
119
+ event: request?.headers?.['x-github-event'] || 'unknown',
120
+ delivery: request?.headers?.['x-github-delivery'] || null,
121
+ };
122
+ },
123
+ };
124
+ }
125
+
126
+ export default create;
@@ -0,0 +1,184 @@
1
+ /**
2
+ * lib/providers/registry.mjs — provider resolution + lifecycle.
3
+ *
4
+ * Resolution order (last-write wins):
5
+ *
6
+ * 1. Built-in factories under `lib/providers/<id>/index.mjs` (loaded lazily).
7
+ * 2. Operator overrides at `~/.construct/providers.json`:
8
+ * { "providers": [{ "id": "...", "package": "...", "options": {} }] }
9
+ * 3. Project overrides at `<projectRoot>/.cx/providers.json` (same shape).
10
+ *
11
+ * Built-in providers ship with the package. Plugin providers are arbitrary
12
+ * npm modules (or local paths) that export the contract. The registry runs
13
+ * `assertProviderContract` against every loaded provider before returning
14
+ * it, so a misconfigured plugin produces a clear error rather than a runtime
15
+ * surprise inside core code.
16
+ *
17
+ * Provider config (auth tokens, default queries, allowlists) is layered:
18
+ * `~/.construct/config.env` for credentials, `<projectRoot>/.cx/providers.yaml`
19
+ * for non-secret per-project settings. The YAML file references env vars
20
+ * by name so secrets never land in source control.
21
+ */
22
+
23
+ import { readFileSync, existsSync } from 'node:fs';
24
+ import { homedir } from 'node:os';
25
+ import { join, isAbsolute, resolve, dirname } from 'node:path';
26
+ import { fileURLToPath, pathToFileURL } from 'node:url';
27
+ import { assertProviderContract } from './contract.mjs';
28
+ import { getBreaker } from './circuit-breaker.mjs';
29
+
30
+ const HERE = dirname(fileURLToPath(import.meta.url));
31
+
32
+ export const BUILT_INS = ['github', 'atlassian-jira', 'atlassian-confluence', 'slack', 'salesforce'];
33
+
34
+ function readJsonIfExists(path) {
35
+ try {
36
+ if (!existsSync(path)) return null;
37
+ return JSON.parse(readFileSync(path, 'utf8'));
38
+ } catch { return null; }
39
+ }
40
+
41
+ async function loadFactoryFromPackage(pkg, rootDir) {
42
+ if (!pkg || typeof pkg !== 'string') {
43
+ throw new Error('provider plugin package must be a non-empty string');
44
+ }
45
+ if (pkg.startsWith('github:')) {
46
+ throw new Error(
47
+ `github: plugin spec '${pkg}' is not auto-installed. ` +
48
+ `Install via 'npm install ${pkg}' first, then reference by package name.`
49
+ );
50
+ }
51
+ if (isAbsolute(pkg) || pkg.startsWith('./') || pkg.startsWith('../')) {
52
+ const abs = isAbsolute(pkg) ? pkg : resolve(rootDir, pkg);
53
+ return import(pathToFileURL(abs).href);
54
+ }
55
+ return import(pkg);
56
+ }
57
+
58
+ async function loadBuiltIn(id) {
59
+ const candidate = join(HERE, id, 'index.mjs');
60
+ if (!existsSync(candidate)) return null;
61
+ return import(pathToFileURL(candidate).href);
62
+ }
63
+
64
+ // Wrap every async I/O method on a provider with its per-id circuit breaker
65
+ // so a downed remote system fails fast for the cooldown window instead of
66
+ // blocking every caller on the same broken backend. `health` is intentionally
67
+ // not wrapped — it's the probe consumers use to surface the breaker's state.
68
+ const BREAKER_METHODS = ['read', 'search', 'watch', 'write', 'webhook'];
69
+
70
+ function wrapWithBreaker(provider) {
71
+ const breaker = getBreaker(`provider:${provider.meta.id}`, {
72
+ failureThreshold: 5,
73
+ cooldownMs: 30_000,
74
+ });
75
+ for (const method of BREAKER_METHODS) {
76
+ if (typeof provider[method] !== 'function') continue;
77
+ const original = provider[method].bind(provider);
78
+ provider[method] = (...args) => breaker.run(original, ...args);
79
+ }
80
+ return provider;
81
+ }
82
+
83
+ async function instantiate(mod, options) {
84
+ const factory = mod?.create || mod?.default;
85
+ if (typeof factory !== 'function') {
86
+ throw new Error(`provider module must export a 'create' or default factory`);
87
+ }
88
+ const instance = await factory(options || {});
89
+ assertProviderContract(instance);
90
+ return wrapWithBreaker(instance);
91
+ }
92
+
93
+ function gatherOverrides(rootDir) {
94
+ const out = [];
95
+ const userConfig = readJsonIfExists(join(homedir(), '.construct', 'providers.json'));
96
+ if (userConfig?.providers) out.push(...userConfig.providers);
97
+ const projectConfig = readJsonIfExists(join(rootDir, '.cx', 'providers.json'));
98
+ if (projectConfig?.providers) out.push(...projectConfig.providers);
99
+ return out;
100
+ }
101
+
102
+ /**
103
+ * Resolve every provider Construct should know about. Returns:
104
+ *
105
+ * { providers: { [id]: instance }, errors: [...], sources: { [id]: 'built-in'|'plugin:<pkg>' } }
106
+ */
107
+ export async function resolveProviders({ rootDir = process.cwd(), env = process.env } = {}) {
108
+ const providers = {};
109
+ const sources = {};
110
+ const errors = [];
111
+
112
+ for (const id of BUILT_INS) {
113
+ try {
114
+ const mod = await loadBuiltIn(id);
115
+ if (!mod) continue;
116
+ const instance = await instantiate(mod, { env });
117
+ providers[id] = instance;
118
+ sources[id] = 'built-in';
119
+ } catch (err) {
120
+ errors.push({ id, source: 'built-in', error: err.message });
121
+ }
122
+ }
123
+
124
+ for (const entry of gatherOverrides(rootDir)) {
125
+ const id = entry?.id;
126
+ const pkg = entry?.package;
127
+ if (!id || !pkg) {
128
+ errors.push({ id: id || '(unknown)', source: 'override', error: 'override entry must have id and package' });
129
+ continue;
130
+ }
131
+ try {
132
+ const mod = await loadFactoryFromPackage(pkg, rootDir);
133
+ const instance = await instantiate(mod, { env, ...(entry.options || {}) });
134
+ if (instance.meta.id !== id) {
135
+ errors.push({ id, source: pkg, error: `meta.id is '${instance.meta.id}' but override declared id='${id}'` });
136
+ continue;
137
+ }
138
+ providers[id] = instance;
139
+ sources[id] = `plugin:${pkg}`;
140
+ } catch (err) {
141
+ errors.push({ id, source: pkg, error: err.message });
142
+ }
143
+ }
144
+
145
+ return { providers, sources, errors };
146
+ }
147
+
148
+ export async function describeProviders({ rootDir = process.cwd(), env = process.env } = {}) {
149
+ const { providers, sources, errors } = await resolveProviders({ rootDir, env });
150
+ const summary = await Promise.all(
151
+ Object.entries(providers).map(async ([id, p]) => {
152
+ let health = { ok: false, detail: 'health probe not invoked' };
153
+ try { health = await p.health({}); } catch (err) { health = { ok: false, detail: err.message }; }
154
+ return {
155
+ id,
156
+ displayName: p.meta.displayName,
157
+ description: p.meta.description || null,
158
+ capabilities: [...p.meta.capabilities],
159
+ source: sources[id],
160
+ health,
161
+ };
162
+ })
163
+ );
164
+ return { summary, errors };
165
+ }
166
+
167
+ // Standalone validation for a candidate override entry: imports the package
168
+ // and runs the contract check without touching the on-disk overrides. Lets
169
+ // callers (e.g. dashboard write endpoint) verify before persisting.
170
+
171
+ export async function validateProviderEntry({ id, package: pkg, options = {} }, { rootDir = process.cwd(), env = process.env } = {}) {
172
+ if (!id || typeof id !== 'string') return { ok: false, error: 'id required' };
173
+ if (!pkg || typeof pkg !== 'string') return { ok: false, error: 'package required' };
174
+ try {
175
+ const mod = await loadFactoryFromPackage(pkg, rootDir);
176
+ const instance = await instantiate(mod, { env, ...options });
177
+ if (instance.meta.id !== id) {
178
+ return { ok: false, error: `meta.id is '${instance.meta.id}' but entry declared id='${id}'` };
179
+ }
180
+ return { ok: true };
181
+ } catch (err) {
182
+ return { ok: false, error: err.message };
183
+ }
184
+ }
@@ -0,0 +1,100 @@
1
+ /**
2
+ * lib/providers/salesforce/index.mjs — Salesforce data-source provider.
3
+ *
4
+ * Capabilities: search, read.
5
+ *
6
+ * Auth: OAuth password flow via env:
7
+ * SALESFORCE_INSTANCE_URL e.g. https://yourorg.my.salesforce.com
8
+ * SALESFORCE_ACCESS_TOKEN bearer token (operator obtains via sfdx or OAuth flow)
9
+ *
10
+ * Config (per call):
11
+ * - soql: SOQL query string
12
+ * - sobject: object type (Account|Opportunity|Contact|Custom__c) for read
13
+ * - id: record id for read
14
+ *
15
+ * The provider intentionally avoids embedding an OAuth flow itself —
16
+ * operators are expected to supply an access token. A future enhancement
17
+ * can land an interactive OAuth flow with refresh-token persistence.
18
+ */
19
+
20
+ const API_VERSION = 'v60.0';
21
+
22
+ function authConfig(env) {
23
+ const instanceUrl = (env.SALESFORCE_INSTANCE_URL || '').replace(/\/$/, '');
24
+ const accessToken = env.SALESFORCE_ACCESS_TOKEN || '';
25
+ return { instanceUrl, accessToken, ok: Boolean(instanceUrl && accessToken) };
26
+ }
27
+
28
+ async function sfFetch(path, env, init = {}) {
29
+ const auth = authConfig(env);
30
+ if (!auth.ok) throw new Error('salesforce: SALESFORCE_INSTANCE_URL and SALESFORCE_ACCESS_TOKEN required');
31
+ const res = await fetch(`${auth.instanceUrl}${path}`, {
32
+ ...init,
33
+ headers: {
34
+ 'Accept': 'application/json',
35
+ 'Content-Type': 'application/json',
36
+ Authorization: `Bearer ${auth.accessToken}`,
37
+ ...(init.headers || {}),
38
+ },
39
+ });
40
+ if (!res.ok) {
41
+ const body = await res.text().catch(() => '');
42
+ throw new Error(`Salesforce ${res.status} ${res.statusText}: ${body.slice(0, 200)}`);
43
+ }
44
+ return res.json();
45
+ }
46
+
47
+ export function create({ env = process.env } = {}) {
48
+ return {
49
+ meta: {
50
+ id: 'salesforce',
51
+ displayName: 'Salesforce',
52
+ capabilities: ['read', 'search'],
53
+ description: 'Accounts, opportunities, custom objects, SOQL.',
54
+ },
55
+
56
+ configSchema: {
57
+ $schema: 'https://json-schema.org/draft/2020-12/schema',
58
+ type: 'object',
59
+ properties: {
60
+ soql: { type: 'string' },
61
+ sobject: { type: 'string' },
62
+ id: { type: 'string', pattern: '^[a-zA-Z0-9]{15,18}$' },
63
+ },
64
+ },
65
+
66
+ async health() {
67
+ const auth = authConfig(env);
68
+ if (!auth.ok) {
69
+ return { ok: false, detail: 'SALESFORCE_INSTANCE_URL / SALESFORCE_ACCESS_TOKEN not set' };
70
+ }
71
+ try {
72
+ await sfFetch(`/services/data/${API_VERSION}/`, env);
73
+ return { ok: true, detail: `connected to ${auth.instanceUrl}` };
74
+ } catch (err) {
75
+ return { ok: false, detail: err.message };
76
+ }
77
+ },
78
+
79
+ async read(config) {
80
+ if (!config?.sobject || !config?.id) {
81
+ throw new Error('salesforce.read: config.sobject and config.id required');
82
+ }
83
+ return sfFetch(
84
+ `/services/data/${API_VERSION}/sobjects/${encodeURIComponent(config.sobject)}/${encodeURIComponent(config.id)}`,
85
+ env
86
+ );
87
+ },
88
+
89
+ async search(config) {
90
+ if (!config?.soql) throw new Error('salesforce.search: config.soql required');
91
+ const data = await sfFetch(
92
+ `/services/data/${API_VERSION}/query?q=${encodeURIComponent(config.soql)}`,
93
+ env
94
+ );
95
+ return Array.isArray(data?.records) ? data.records : [];
96
+ },
97
+ };
98
+ }
99
+
100
+ export default create;
@@ -0,0 +1,80 @@
1
+ /**
2
+ * lib/providers/slack/index.mjs — Slack data-source provider.
3
+ *
4
+ * Capabilities: read, search.
5
+ *
6
+ * Auth: `SLACK_BOT_TOKEN` (xoxb-...) or `SLACK_USER_TOKEN` (xoxp-...) from env.
7
+ *
8
+ * Config (per call):
9
+ * - channel: channel id or name (#engineering or C0123)
10
+ * - query: free-text search.messages query
11
+ * - count: integer (default 20, max 100)
12
+ */
13
+
14
+ const API = 'https://slack.com/api';
15
+ const DEFAULT_COUNT = 20;
16
+ const HARD_COUNT = 100;
17
+
18
+ function token(env) {
19
+ return env.SLACK_BOT_TOKEN || env.SLACK_USER_TOKEN || '';
20
+ }
21
+
22
+ async function slackFetch(method, env, params = {}) {
23
+ const tok = token(env);
24
+ if (!tok) throw new Error('slack: SLACK_BOT_TOKEN or SLACK_USER_TOKEN required');
25
+ const url = new URL(`${API}/${method}`);
26
+ for (const [k, v] of Object.entries(params)) url.searchParams.set(k, String(v));
27
+ const res = await fetch(url, { headers: { Authorization: `Bearer ${tok}` } });
28
+ if (!res.ok) throw new Error(`slack ${res.status} ${res.statusText}`);
29
+ const data = await res.json();
30
+ if (!data.ok) throw new Error(`slack api error: ${data.error || 'unknown'}`);
31
+ return data;
32
+ }
33
+
34
+ export function create({ env = process.env } = {}) {
35
+ return {
36
+ meta: {
37
+ id: 'slack',
38
+ displayName: 'Slack',
39
+ capabilities: ['read', 'search'],
40
+ description: 'Channel messages, threads, search.',
41
+ },
42
+
43
+ configSchema: {
44
+ $schema: 'https://json-schema.org/draft/2020-12/schema',
45
+ type: 'object',
46
+ properties: {
47
+ channel: { type: 'string' },
48
+ query: { type: 'string' },
49
+ count: { type: 'integer', minimum: 1, maximum: HARD_COUNT, default: DEFAULT_COUNT },
50
+ },
51
+ },
52
+
53
+ async health() {
54
+ const tok = token(env);
55
+ if (!tok) return { ok: false, detail: 'SLACK_BOT_TOKEN or SLACK_USER_TOKEN not set' };
56
+ try {
57
+ const data = await slackFetch('auth.test', env);
58
+ return { ok: true, detail: `team=${data.team || '?'} user=${data.user || '?'}` };
59
+ } catch (err) {
60
+ return { ok: false, detail: err.message };
61
+ }
62
+ },
63
+
64
+ async read(config) {
65
+ if (!config?.channel) throw new Error('slack.read: config.channel required');
66
+ const limit = Math.min(config?.count || DEFAULT_COUNT, HARD_COUNT);
67
+ const data = await slackFetch('conversations.history', env, { channel: config.channel, limit });
68
+ return data.messages || [];
69
+ },
70
+
71
+ async search(config) {
72
+ if (!config?.query) throw new Error('slack.search: config.query required');
73
+ const count = Math.min(config?.count || DEFAULT_COUNT, HARD_COUNT);
74
+ const data = await slackFetch('search.messages', env, { query: config.query, count });
75
+ return data.messages?.matches || [];
76
+ },
77
+ };
78
+ }
79
+
80
+ export default create;
@@ -0,0 +1,137 @@
1
+ /**
2
+ * lib/reflect.mjs — construct reflect command implementation.
3
+ *
4
+ * Captures improvement feedback from chat sessions and updates Construct core
5
+ * knowledge base with actionable insights for continuous self-improvement.
6
+ */
7
+
8
+ import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
9
+ import { join, resolve } from 'node:path';
10
+ import { addObservation } from './observation-store.mjs';
11
+ import { loadConstructEnv } from './env-config.mjs';
12
+ import { KNOWLEDGE_ROOT, KNOWLEDGE_SUBDIRS, inferKnowledgeTarget } from './knowledge/layout.mjs';
13
+
14
+ const HOME = process.env.HOME || process.env.USERPROFILE;
15
+ const USER_ENV_PATH = join(HOME, '.construct', 'config.env');
16
+
17
+ /**
18
+ * Main reflect command handler.
19
+ * @param {string[]} args - Command line arguments
20
+ */
21
+ export async function runReflectCli(args) {
22
+ // Parse arguments
23
+ let target = 'internal';
24
+ let summary = '';
25
+
26
+ for (const arg of args) {
27
+ if (arg.startsWith('--target=')) {
28
+ target = arg.split('=')[1];
29
+ } else if (arg.startsWith('--summary=')) {
30
+ summary = arg.split('=')[1];
31
+ }
32
+ }
33
+
34
+ // Validate target
35
+ const validTargets = KNOWLEDGE_SUBDIRS.map(s => `knowledge/${s}`);
36
+ const validShorthandTargets = KNOWLEDGE_SUBDIRS; // internal, external, etc.
37
+ const isValidTarget = validTargets.includes(target) || validShorthandTargets.includes(target);
38
+ if (!isValidTarget) {
39
+ console.error(`Error: Invalid target '${target}'. Valid targets: ${validTargets.join(', ')}`);
40
+ process.exit(1);
41
+ }
42
+
43
+ // Normalize target to full format if shorthand was provided
44
+ if (validShorthandTargets.includes(target) && !target.startsWith('knowledge/')) {
45
+ target = `knowledge/${target}`;
46
+ }
47
+
48
+ // If no summary provided, try to get it from stdin or prompt
49
+ if (!summary) {
50
+ // For now, we'll require explicit summary - in future could read from chat context
51
+ console.error('Error: --summary=<text> is required to capture improvement feedback');
52
+ console.error('Example: construct reflect --target=internal --summary="Improve Slack channel intent parsing to handle edge cases"');
53
+ process.exit(1);
54
+ }
55
+
56
+ // Resolve the knowledge directory for this target
57
+ const knowledgeSubdir = target.replace('knowledge/', '');
58
+ const knowledgeDir = join(process.cwd(), KNOWLEDGE_ROOT, knowledgeSubdir);
59
+
60
+ // Ensure the knowledge directory exists
61
+ if (!existsSync(knowledgeDir)) {
62
+ mkdirSync(knowledgeDir, { recursive: true });
63
+ }
64
+
65
+ // Generate a filename based on timestamp and summary hash
66
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
67
+ const summarySlug = summary
68
+ .toLowerCase()
69
+ .replace(/[^a-z0-9]+/g, '-')
70
+ .replace(/^-|-$/g, '')
71
+ .slice(0, 50);
72
+ const filename = `${timestamp}-${summarySlug}.md`;
73
+ const filePath = join(knowledgeDir, filename);
74
+
75
+ // Create the markdown content
76
+ const content = [
77
+ '---',
78
+ `source_path: reflect-command`,
79
+ `source_relative_path: ${filename}`,
80
+ `source_extension: .md`,
81
+ `extracted_at: ${new Date().toISOString()}`,
82
+ `extraction_method: reflect-command`,
83
+ `characters: ${summary.length}`,
84
+ `truncated: false`,
85
+ `output_path: ${JSON.stringify(filePath)}`,
86
+ `output_relative_path: ${JSON.stringify(filename)}`,
87
+ '---',
88
+ '',
89
+ `# Improvement Feedback`,
90
+ '',
91
+ summary,
92
+ '',
93
+ '## Context',
94
+ '',
95
+ `- Captured via: construct reflect`,
96
+ `- Timestamp: ${new Date().toISOString()}`,
97
+ `- Target knowledge directory: ${knowledgeSubdir}/`,
98
+ '',
99
+ '## Action Items',
100
+ '',
101
+ '- [ ] Review this feedback for validity and actionability',
102
+ '- [ ] Determine if this requires code changes, documentation updates, or process improvements',
103
+ '- [ ] If actionable, create appropriate issues in the tracker',
104
+ '- [ ] Update relevant documentation or code based on this insight',
105
+ '',
106
+ ].join('\n');
107
+
108
+ // Write the file
109
+ writeFileSync(filePath, content);
110
+
111
+ // Record an observation about this feedback
112
+ const observationSummary = `[reflect] Improvement feedback captured: ${summary.slice(0, 100)}${summary.length > 100 ? '...' : ''}`;
113
+ addObservation(process.cwd(), {
114
+ role: 'construct',
115
+ category: 'insight', // Feedback is typically an insight unless it's about a problem
116
+ summary: observationSummary,
117
+ content: `Feedback captured via construct reflect:\n\n${summary}\n\nStored as: ${filePath}\nTarget: ${knowledgeSubdir}/`,
118
+ tags: ['reflect', 'improvement-feedback', `knowledge:${knowledgeSubdir}`],
119
+ confidence: 0.9,
120
+ source: 'reflect-command',
121
+ });
122
+
123
+ console.log(`✓ Improvement feedback captured and stored as: ${filePath}`);
124
+ console.log(` Target knowledge directory: ${knowledgeSubdir}/`);
125
+ console.log(` Use 'construct ingest --target=${target} <file>' to manually add similar feedback in the future`);
126
+ }
127
+
128
+ /**
129
+ * CLI entry point for direct execution.
130
+ */
131
+ if (import.meta.url === `file://${process.argv[1]}`) {
132
+ const args = process.argv.slice(2);
133
+ runReflectCli(args).catch((err) => {
134
+ console.error(`Error: ${err.message}`);
135
+ process.exit(1);
136
+ });
137
+ }