@geraldmaron/construct 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (471) hide show
  1. package/.env.example +9 -6
  2. package/LICENSE +201 -21
  3. package/README.md +132 -257
  4. package/agents/contracts.json +387 -0
  5. package/agents/prompts/cx-accessibility.md +8 -0
  6. package/agents/prompts/cx-ai-engineer.md +92 -0
  7. package/agents/prompts/cx-architect.md +10 -2
  8. package/agents/prompts/cx-business-strategist.md +13 -0
  9. package/agents/prompts/cx-data-analyst.md +80 -0
  10. package/agents/prompts/cx-data-engineer.md +4 -0
  11. package/agents/prompts/cx-debugger.md +8 -0
  12. package/agents/prompts/cx-designer.md +19 -0
  13. package/agents/prompts/cx-devil-advocate.md +4 -0
  14. package/agents/prompts/cx-docs-keeper.md +128 -0
  15. package/agents/prompts/cx-engineer.md +13 -0
  16. package/agents/prompts/cx-evaluator.md +4 -0
  17. package/agents/prompts/cx-explorer.md +12 -0
  18. package/agents/prompts/cx-legal-compliance.md +13 -0
  19. package/agents/prompts/cx-operations.md +13 -0
  20. package/agents/prompts/cx-orchestrator.md +107 -4
  21. package/agents/prompts/cx-platform-engineer.md +75 -0
  22. package/agents/prompts/cx-product-manager.md +8 -0
  23. package/agents/prompts/cx-qa.md +100 -0
  24. package/agents/prompts/cx-rd-lead.md +13 -0
  25. package/agents/prompts/cx-release-manager.md +8 -0
  26. package/agents/prompts/cx-researcher.md +21 -1
  27. package/agents/prompts/cx-reviewer.md +8 -0
  28. package/agents/prompts/cx-security.md +104 -0
  29. package/agents/prompts/cx-sre.md +104 -0
  30. package/agents/prompts/cx-test-automation.md +4 -0
  31. package/agents/prompts/cx-trace-reviewer.md +7 -3
  32. package/agents/prompts/cx-ux-researcher.md +4 -0
  33. package/agents/registry.json +365 -90
  34. package/agents/role-manifests.json +217 -0
  35. package/bin/construct +3345 -141
  36. package/bin/construct-postinstall.mjs +87 -0
  37. package/commands/build/feature.md +6 -6
  38. package/commands/plan/feature.md +6 -5
  39. package/commands/plan/requirements.md +1 -1
  40. package/commands/ship/status.md +1 -1
  41. package/commands/work/drive.md +3 -3
  42. package/commands/work/optimize-prompts.md +3 -3
  43. package/db/{migrations → schema}/001_init.sql +2 -0
  44. package/db/schema/002_pgvector.sql +182 -0
  45. package/db/schema/003_intake.sql +47 -0
  46. package/examples/README.md +85 -0
  47. package/examples/internal/roles/architect/bad/clever-plan-without-contracts.md +30 -0
  48. package/examples/internal/roles/architect/golden/explicit-tradeoff-before-plan.md +23 -0
  49. package/examples/internal/roles/engineer/bad/speculative-abstraction.md +30 -0
  50. package/examples/internal/roles/engineer/golden/read-before-write.md +28 -0
  51. package/examples/internal/roles/orchestrator/bad/everything-becomes-multi-agent.md +29 -0
  52. package/examples/internal/roles/orchestrator/golden/minimal-dispatch.md +22 -0
  53. package/examples/internal/roles/qa/bad/coverage-theater.md +30 -0
  54. package/examples/internal/roles/qa/golden/regression-gate.md +23 -0
  55. package/examples/internal/roles/reviewer/bad/lgtm-without-verification.md +29 -0
  56. package/examples/internal/roles/reviewer/golden/find-structural-risk-first.md +28 -0
  57. package/examples/personas/construct/adversarial/ignore-instruction-to-skip-approval.md +22 -0
  58. package/examples/personas/construct/bad/commit-without-approval.md +30 -0
  59. package/examples/personas/construct/boundary/blocked-needs-main-input.md +29 -0
  60. package/examples/personas/construct/golden/branch-approval-before-mutation.md +36 -0
  61. package/examples/personas/construct/golden/focused-direct-answer.md +28 -0
  62. package/examples/provider-plugin/README.md +34 -0
  63. package/examples/provider-plugin/index.mjs +74 -0
  64. package/examples/provider-plugin/package.json +15 -0
  65. package/examples/seed-observations/README.md +38 -0
  66. package/examples/seed-observations/anti-patterns.md +44 -0
  67. package/examples/seed-observations/decisions.md +36 -0
  68. package/examples/seed-observations/patterns.md +42 -0
  69. package/lib/agent-contracts-enforce.mjs +158 -0
  70. package/lib/agent-contracts.mjs +231 -0
  71. package/lib/agents/postconditions.mjs +126 -0
  72. package/lib/agents/schema.mjs +124 -0
  73. package/lib/artifact-capture.mjs +183 -0
  74. package/lib/audit-trail.mjs +149 -0
  75. package/lib/auto-docs.mjs +245 -65
  76. package/lib/beads/auto-close.mjs +126 -0
  77. package/lib/beads/drift.mjs +171 -0
  78. package/lib/beads-automation.mjs +542 -0
  79. package/lib/beads-client.mjs +518 -0
  80. package/lib/beads-lock.mjs +377 -0
  81. package/lib/beads-optimistic.mjs +365 -0
  82. package/lib/bootstrap/built-ins.mjs +136 -0
  83. package/lib/bootstrap/lazy-install.mjs +161 -0
  84. package/lib/bootstrap/resources.mjs +120 -0
  85. package/lib/bootstrap.mjs +105 -0
  86. package/lib/cache-governor.js +213 -0
  87. package/lib/cache-strategy-anthropic.js +62 -0
  88. package/lib/cache-strategy-google.js +79 -0
  89. package/lib/cache-strategy-none.js +30 -0
  90. package/lib/cache-strategy-openai.js +48 -0
  91. package/lib/cache-strategy.js +91 -0
  92. package/lib/claude-allow.mjs +149 -0
  93. package/lib/cli-commands.mjs +413 -258
  94. package/lib/codex-config.mjs +3 -1
  95. package/lib/comment-lint.mjs +55 -6
  96. package/lib/completions.mjs +21 -6
  97. package/lib/config/alias.mjs +56 -0
  98. package/lib/config/project-config.mjs +335 -0
  99. package/lib/config/schema.mjs +159 -0
  100. package/lib/context-router.mjs +308 -0
  101. package/lib/cost-ledger.mjs +177 -0
  102. package/lib/cost.mjs +171 -10
  103. package/lib/dashboard-static.mjs +158 -0
  104. package/lib/deployment-mode.mjs +86 -0
  105. package/lib/deprecate.mjs +49 -0
  106. package/lib/dispatch-batch.js +183 -0
  107. package/lib/distill.mjs +21 -8
  108. package/lib/doc-stamp.mjs +164 -0
  109. package/lib/doc-verify.mjs +119 -0
  110. package/lib/docs-routing.mjs +89 -0
  111. package/lib/docs-verify.mjs +417 -0
  112. package/lib/doctor/audit.mjs +71 -0
  113. package/lib/doctor/cli.mjs +99 -0
  114. package/lib/doctor/escalate.mjs +29 -0
  115. package/lib/doctor/index.mjs +140 -0
  116. package/lib/doctor/report.mjs +170 -0
  117. package/lib/doctor/watchers/bd-watch.mjs +117 -0
  118. package/lib/doctor/watchers/cost.mjs +130 -0
  119. package/lib/doctor/watchers/disk.mjs +122 -0
  120. package/lib/doctor/watchers/handoffs.mjs +33 -0
  121. package/lib/doctor/watchers/process-pressure.mjs +60 -0
  122. package/lib/doctor/watchers/service-health.mjs +188 -0
  123. package/lib/document-extract.mjs +288 -0
  124. package/lib/document-ingest.mjs +230 -0
  125. package/lib/drop.mjs +282 -0
  126. package/lib/embed/approval-queue.mjs +176 -0
  127. package/lib/embed/artifact.mjs +349 -0
  128. package/lib/embed/authority-guard.mjs +155 -0
  129. package/lib/embed/cli.mjs +408 -0
  130. package/lib/embed/config.mjs +355 -0
  131. package/lib/embed/conflict-detection.mjs +264 -0
  132. package/lib/embed/customer-profiles.mjs +480 -0
  133. package/lib/embed/daemon.mjs +1309 -0
  134. package/lib/embed/demand-fetch.mjs +449 -0
  135. package/lib/embed/docs-lifecycle.mjs +349 -0
  136. package/lib/embed/inbox-live-watcher.mjs +119 -0
  137. package/lib/embed/inbox.mjs +343 -0
  138. package/lib/embed/intake-metrics.mjs +190 -0
  139. package/lib/embed/jobs/vector-sync.mjs +198 -0
  140. package/lib/embed/notifications.mjs +75 -0
  141. package/lib/embed/output.mjs +79 -0
  142. package/lib/embed/providers/github.mjs +295 -0
  143. package/lib/embed/providers/jira.mjs +192 -0
  144. package/lib/embed/providers/linear.mjs +186 -0
  145. package/lib/embed/providers/registry.mjs +115 -0
  146. package/lib/embed/providers/slack.mjs +203 -0
  147. package/lib/embed/recommendation-store.mjs +378 -0
  148. package/lib/embed/roadmap.mjs +374 -0
  149. package/lib/embed/role-framing.mjs +110 -0
  150. package/lib/embed/scheduler.mjs +99 -0
  151. package/lib/embed/semantic.mjs +325 -0
  152. package/lib/embed/snapshot.mjs +191 -0
  153. package/lib/embed/supervision.mjs +235 -0
  154. package/lib/embed/target-resolver.mjs +186 -0
  155. package/lib/embed/worker.mjs +62 -0
  156. package/lib/embed/workspaces.mjs +297 -0
  157. package/lib/engine/chunker-headings.mjs +110 -0
  158. package/lib/engine/compressor-heuristic.mjs +100 -0
  159. package/lib/engine/consolidate.mjs +287 -0
  160. package/lib/engine/contracts.mjs +126 -0
  161. package/lib/engine/defaults.mjs +129 -0
  162. package/lib/engine/eval-retrieval.mjs +148 -0
  163. package/lib/engine/fuser-rrf.mjs +62 -0
  164. package/lib/engine/index.mjs +37 -0
  165. package/lib/engine/registry.mjs +146 -0
  166. package/lib/engine/reranker-mmr.mjs +90 -0
  167. package/lib/engine/tokens.mjs +77 -0
  168. package/lib/entity-store.mjs +280 -0
  169. package/lib/env-config.mjs +69 -4
  170. package/lib/evals/retrieval-bench.mjs +159 -0
  171. package/lib/evaluator-optimizer.mjs +317 -0
  172. package/lib/features.mjs +159 -29
  173. package/lib/gates-audit.mjs +236 -0
  174. package/lib/git-hooks/prepare-commit-msg +58 -0
  175. package/lib/handoffs/cleanup.mjs +159 -0
  176. package/lib/handoffs/contract.mjs +162 -0
  177. package/lib/handoffs/inventory.mjs +120 -0
  178. package/lib/headhunt.mjs +28 -47
  179. package/lib/health-check.mjs +399 -0
  180. package/lib/hook-health.mjs +442 -0
  181. package/lib/hooks/_lib/log.mjs +82 -0
  182. package/lib/hooks/adaptive-lint.mjs +26 -2
  183. package/lib/hooks/agent-tracker.mjs +171 -14
  184. package/lib/hooks/audit-reads.mjs +109 -0
  185. package/lib/hooks/audit-trail.mjs +153 -0
  186. package/lib/hooks/bash-output-logger.mjs +71 -0
  187. package/lib/hooks/block-no-verify.mjs +41 -0
  188. package/lib/hooks/ci-status-check.mjs +82 -0
  189. package/lib/hooks/comment-lint.mjs +29 -7
  190. package/lib/hooks/config-protection.mjs +39 -18
  191. package/lib/hooks/context-watch.mjs +137 -0
  192. package/lib/hooks/context-window-recovery.mjs +4 -20
  193. package/lib/hooks/dep-audit.mjs +16 -0
  194. package/lib/hooks/doc-coupling-check.mjs +80 -0
  195. package/lib/hooks/edit-accumulator.mjs +3 -0
  196. package/lib/hooks/edit-error-recovery.mjs +3 -0
  197. package/lib/hooks/edit-guard.mjs +45 -4
  198. package/lib/hooks/env-check.mjs +3 -0
  199. package/lib/hooks/guard-bash.mjs +58 -1
  200. package/lib/hooks/mcp-audit.mjs +18 -20
  201. package/lib/hooks/mcp-health-check.mjs +36 -0
  202. package/lib/hooks/model-fallback.mjs +42 -56
  203. package/lib/hooks/policy-engine.mjs +209 -0
  204. package/lib/hooks/post-merge-docs-check.mjs +63 -0
  205. package/lib/hooks/pre-compact.mjs +4 -24
  206. package/lib/hooks/pre-push-gate.mjs +200 -37
  207. package/lib/hooks/proactive-activation.mjs +284 -0
  208. package/lib/hooks/read-tracker.mjs +27 -4
  209. package/lib/hooks/readme-age-check.mjs +77 -0
  210. package/lib/hooks/registry-sync.mjs +12 -5
  211. package/lib/hooks/scan-secrets.mjs +50 -7
  212. package/lib/hooks/session-optimize.mjs +311 -0
  213. package/lib/hooks/session-start.mjs +287 -28
  214. package/lib/hooks/stop-notify.mjs +236 -96
  215. package/lib/hooks/stop-typecheck.mjs +13 -1
  216. package/lib/hooks/test-watch.mjs +68 -0
  217. package/lib/host-capabilities.mjs +31 -10
  218. package/lib/init-docs.mjs +731 -291
  219. package/lib/init-unified.mjs +1000 -0
  220. package/lib/init-update.mjs +168 -0
  221. package/lib/init.mjs +107 -0
  222. package/lib/install/first-invocation.mjs +119 -0
  223. package/lib/install/stage-project.mjs +69 -0
  224. package/lib/intake/classify.mjs +278 -0
  225. package/lib/intake/feedback.mjs +273 -0
  226. package/lib/intake/filesystem-queue.mjs +158 -0
  227. package/lib/intake/intake-config.mjs +132 -0
  228. package/lib/intake/postgres-queue.mjs +198 -0
  229. package/lib/intake/prepare.mjs +139 -0
  230. package/lib/intake/queue.mjs +83 -0
  231. package/lib/intake/session-prelude.mjs +50 -0
  232. package/lib/integrations/intake-integrations.mjs +740 -0
  233. package/lib/intent-classifier.mjs +253 -0
  234. package/lib/knowledge/layout.mjs +72 -0
  235. package/lib/knowledge/rag.mjs +331 -0
  236. package/lib/knowledge/search.mjs +315 -0
  237. package/lib/knowledge/trends.mjs +261 -0
  238. package/lib/logger.mjs +85 -0
  239. package/lib/mcp/broker.mjs +124 -0
  240. package/lib/mcp/server.mjs +554 -854
  241. package/lib/mcp/tools/document.mjs +132 -0
  242. package/lib/mcp/tools/memory.mjs +209 -0
  243. package/lib/mcp/tools/project.mjs +349 -0
  244. package/lib/mcp/tools/skills.mjs +409 -0
  245. package/lib/mcp/tools/storage.mjs +60 -0
  246. package/lib/mcp/tools/telemetry.mjs +315 -0
  247. package/lib/mcp/tools/workflow.mjs +112 -0
  248. package/lib/mcp-catalog.json +54 -3
  249. package/lib/mcp-manager.mjs +96 -48
  250. package/lib/mcp-platform-config.mjs +22 -22
  251. package/lib/memory-stats.mjs +121 -0
  252. package/lib/mode-commands.mjs +124 -0
  253. package/lib/model-free-selector.mjs +184 -0
  254. package/lib/model-pricing.mjs +152 -0
  255. package/lib/model-registry.mjs +226 -0
  256. package/lib/model-router.mjs +362 -386
  257. package/lib/observation-store.mjs +391 -0
  258. package/lib/ollama-manager.mjs +428 -0
  259. package/lib/opencode-config.mjs +13 -3
  260. package/lib/opencode-runtime-plugin.mjs +70 -38
  261. package/lib/opencode-telemetry.mjs +64 -6
  262. package/lib/orchestration-policy.mjs +509 -6
  263. package/lib/overrides/resolver.mjs +207 -0
  264. package/lib/parity.mjs +147 -0
  265. package/lib/paths.mjs +33 -0
  266. package/lib/performance/generate.mjs +212 -0
  267. package/lib/plugin-registry.mjs +268 -0
  268. package/lib/policy/engine.mjs +130 -0
  269. package/lib/policy/unified-gates.mjs +96 -0
  270. package/lib/project-detection.mjs +129 -0
  271. package/lib/project-init-shared.mjs +272 -0
  272. package/lib/project-profile.mjs +447 -0
  273. package/lib/prompt-composer.js +434 -0
  274. package/lib/prompt-metadata.mjs +1 -1
  275. package/lib/provider-capabilities-anthropic.js +44 -0
  276. package/lib/provider-capabilities-deepseek.js +37 -0
  277. package/lib/provider-capabilities-generic.js +26 -0
  278. package/lib/provider-capabilities-google.js +47 -0
  279. package/lib/provider-capabilities-openai.js +45 -0
  280. package/lib/provider-capabilities.js +142 -0
  281. package/lib/providers/atlassian-confluence/index.mjs +103 -0
  282. package/lib/providers/atlassian-jira/index.mjs +100 -0
  283. package/lib/providers/auth-manager.mjs +126 -0
  284. package/lib/providers/circuit-breaker.mjs +124 -0
  285. package/lib/providers/contract.mjs +93 -0
  286. package/lib/providers/github/index.mjs +126 -0
  287. package/lib/providers/registry.mjs +184 -0
  288. package/lib/providers/salesforce/index.mjs +100 -0
  289. package/lib/providers/slack/index.mjs +80 -0
  290. package/lib/reflect.mjs +137 -0
  291. package/lib/research-lint.mjs +164 -0
  292. package/lib/resources/budget.mjs +259 -0
  293. package/lib/role-preload.mjs +21 -6
  294. package/lib/roles/approval-surface.mjs +54 -0
  295. package/lib/roles/cli.mjs +118 -0
  296. package/lib/roles/event-bus.mjs +79 -0
  297. package/lib/roles/fence.mjs +84 -0
  298. package/lib/roles/gateway.mjs +260 -0
  299. package/lib/roles/hook-emit.mjs +37 -0
  300. package/lib/roles/manifest.mjs +48 -0
  301. package/lib/roles/router.mjs +27 -0
  302. package/lib/runtime-pressure.mjs +360 -0
  303. package/lib/schema-artifact.mjs +134 -0
  304. package/lib/schema-infer.mjs +551 -0
  305. package/lib/server/auth.mjs +168 -0
  306. package/lib/server/chat.mjs +336 -0
  307. package/lib/server/cors.mjs +77 -0
  308. package/lib/server/csrf.mjs +91 -0
  309. package/lib/server/index.mjs +1927 -78
  310. package/lib/server/insights.mjs +765 -0
  311. package/lib/server/rate-limit.mjs +91 -0
  312. package/lib/server/static/assets/index-ab25c707.js +70 -0
  313. package/lib/server/static/assets/index-f0c80a2b.css +1 -0
  314. package/lib/server/static/index.html +12 -817
  315. package/lib/server/telemetry-login.mjs +108 -0
  316. package/lib/server/webhook.mjs +510 -0
  317. package/lib/service-manager.mjs +522 -58
  318. package/lib/services/pattern-promotion-service.mjs +167 -0
  319. package/lib/services/telemetry-backend.mjs +178 -0
  320. package/lib/session-store.mjs +374 -0
  321. package/lib/setup-prompts.mjs +96 -0
  322. package/lib/setup.mjs +523 -36
  323. package/lib/skills-apply.mjs +280 -0
  324. package/lib/skills-scope.mjs +118 -0
  325. package/lib/status.mjs +261 -70
  326. package/lib/storage/admin.mjs +355 -0
  327. package/lib/storage/backend.mjs +2 -1
  328. package/lib/storage/backup.mjs +347 -0
  329. package/lib/storage/embeddings-engine.mjs +133 -0
  330. package/lib/storage/embeddings-legacy.mjs +85 -0
  331. package/lib/storage/embeddings-local.mjs +108 -0
  332. package/lib/storage/embeddings-ollama.mjs +78 -0
  333. package/lib/storage/embeddings-openai.mjs +85 -0
  334. package/lib/storage/embeddings.mjs +92 -33
  335. package/lib/storage/file-lock.mjs +130 -0
  336. package/lib/storage/fusion.mjs +95 -0
  337. package/lib/storage/hybrid-query.mjs +34 -27
  338. package/lib/storage/migrations.mjs +187 -0
  339. package/lib/storage/postgres-backup.mjs +124 -0
  340. package/lib/storage/sql-store.mjs +5 -15
  341. package/lib/storage/state-source.mjs +12 -13
  342. package/lib/storage/store-version.mjs +115 -0
  343. package/lib/storage/sync.mjs +144 -35
  344. package/lib/storage/unified-storage.mjs +550 -0
  345. package/lib/storage/vector-client.mjs +286 -0
  346. package/lib/storage/vector-store.mjs +71 -30
  347. package/lib/task-graph/generate.mjs +135 -0
  348. package/lib/task-graph/schema.mjs +81 -0
  349. package/lib/task-graph/store.mjs +71 -0
  350. package/lib/telemetry/backends/local.mjs +62 -0
  351. package/lib/telemetry/backends/{langfuse.mjs → remote.mjs} +27 -14
  352. package/lib/telemetry/backfill.mjs +180 -0
  353. package/lib/telemetry/eval-datasets.mjs +203 -0
  354. package/lib/telemetry/{langfuse-ingest.mjs → ingest.mjs} +26 -20
  355. package/lib/telemetry/intent-verifications.mjs +86 -0
  356. package/lib/telemetry/llm-judge.mjs +350 -0
  357. package/lib/telemetry/model-pricing-catalog.mjs +557 -0
  358. package/lib/telemetry/setup.mjs +151 -0
  359. package/lib/telemetry/skill-calls.mjs +78 -0
  360. package/lib/telemetry/team-rollup.mjs +4 -4
  361. package/lib/token-engine.js +117 -0
  362. package/lib/token-estimator-anthropic.js +15 -0
  363. package/lib/token-estimator-deepseek.js +13 -0
  364. package/lib/token-estimator-default.js +13 -0
  365. package/lib/token-estimator-google.js +13 -0
  366. package/lib/token-estimator-openai.js +13 -0
  367. package/lib/toolkit-env.mjs +1 -1
  368. package/lib/tty-prompts.mjs +211 -0
  369. package/lib/uninstall/uninstall.mjs +423 -0
  370. package/lib/update.mjs +115 -0
  371. package/lib/upgrade.mjs +141 -0
  372. package/lib/validator.mjs +51 -2
  373. package/lib/validators/skills.mjs +142 -0
  374. package/lib/wireframe.mjs +422 -0
  375. package/lib/worker/entrypoint.mjs +241 -0
  376. package/lib/worker/evidence.mjs +107 -0
  377. package/lib/worker/run.mjs +154 -0
  378. package/lib/worker/trace.mjs +182 -0
  379. package/lib/workflow-state.mjs +14 -18
  380. package/package.json +21 -8
  381. package/personas/construct.md +53 -51
  382. package/platforms/claude/CLAUDE.md +1 -1
  383. package/platforms/claude/settings.template.json +115 -68
  384. package/platforms/opencode/config.template.json +3 -7
  385. package/rules/common/agents.md +11 -38
  386. package/rules/common/beads-hygiene.md +75 -0
  387. package/rules/common/code-review.md +11 -100
  388. package/rules/common/coding-style.md +5 -3
  389. package/rules/common/comments.md +30 -111
  390. package/rules/common/commit-approval.md +53 -0
  391. package/rules/common/cx-agent-routing.md +1 -0
  392. package/rules/common/development-workflow.md +23 -41
  393. package/rules/common/doc-ownership.md +54 -0
  394. package/rules/common/efficiency.md +57 -0
  395. package/rules/common/framing.md +76 -0
  396. package/rules/common/git-workflow.md +2 -4
  397. package/rules/common/patterns.md +3 -7
  398. package/rules/common/performance.md +15 -31
  399. package/rules/common/release-gates.md +69 -0
  400. package/rules/common/research.md +107 -0
  401. package/rules/common/security.md +6 -6
  402. package/rules/common/skill-composition.md +67 -0
  403. package/rules/common/testing.md +15 -27
  404. package/rules/golang/hooks.md +0 -4
  405. package/rules/policy/bootstrap.yaml +11 -0
  406. package/rules/policy/drive.yaml +8 -0
  407. package/rules/policy/task.yaml +9 -0
  408. package/rules/policy/workflow.yaml +9 -0
  409. package/rules/python/hooks.md +0 -4
  410. package/rules/swift/hooks.md +0 -4
  411. package/rules/typescript/hooks.md +0 -4
  412. package/rules/web/hooks.md +0 -2
  413. package/{sync-agents.mjs → scripts/sync-agents.mjs} +306 -61
  414. package/skills/ai/prompt-optimizer.md +7 -7
  415. package/skills/compliance/ai-disclosure.md +58 -0
  416. package/skills/compliance/data-privacy.md +46 -0
  417. package/skills/compliance/license-audit.md +40 -0
  418. package/skills/compliance/regulatory-review.md +61 -0
  419. package/skills/docs/document-ingest-workflow.md +52 -0
  420. package/skills/docs/evidence-ingest-workflow.md +9 -0
  421. package/skills/docs/init-docs.md +51 -18
  422. package/skills/docs/product-intelligence-workflow.md +12 -0
  423. package/skills/docs/product-signal-workflow.md +4 -0
  424. package/skills/docs/research-workflow.md +23 -8
  425. package/skills/docs/runbook-workflow.md +1 -1
  426. package/skills/operating/orchestration-reference.md +151 -0
  427. package/skills/roles/architect.md +5 -0
  428. package/skills/roles/engineer.md +32 -0
  429. package/skills/roles/operator.md +12 -0
  430. package/skills/roles/reviewer.md +23 -0
  431. package/skills/routing.md +1 -1
  432. package/templates/devcontainer/Dockerfile.devcontainer +38 -0
  433. package/templates/devcontainer/devcontainer.json +31 -0
  434. package/templates/distribution/bootstrap.ps1 +142 -0
  435. package/templates/distribution/bootstrap.sh +196 -0
  436. package/templates/distribution/run.mjs +187 -0
  437. package/templates/docs/adr.md +44 -8
  438. package/templates/docs/changelog-entry.md +43 -0
  439. package/templates/docs/construct_guide.md +149 -0
  440. package/templates/docs/evidence-brief.md +2 -2
  441. package/templates/docs/meta-prd.md +140 -19
  442. package/templates/docs/onboarding.md +57 -0
  443. package/templates/docs/prd.md +159 -15
  444. package/templates/docs/research-brief.md +4 -4
  445. package/templates/homebrew/construct.rb +67 -0
  446. package/langfuse/docker-compose.yml +0 -82
  447. package/lib/eval-harness.mjs +0 -59
  448. package/lib/hooks/bootstrap-guard.mjs +0 -90
  449. package/lib/hooks/console-warn.mjs +0 -43
  450. package/lib/hooks/continuation-enforcer.mjs +0 -72
  451. package/lib/hooks/drive-guard.mjs +0 -89
  452. package/lib/hooks/mcp-task-scope.mjs +0 -47
  453. package/lib/hooks/task-completed-guard.mjs +0 -43
  454. package/lib/hooks/teammate-idle-guard.mjs +0 -54
  455. package/lib/hooks/workflow-guard.mjs +0 -62
  456. package/lib/prompt-composer.mjs +0 -196
  457. package/lib/review.mjs +0 -429
  458. package/lib/server/static/app.js +0 -841
  459. package/lib/telemetry/langfuse-model-sync.mjs +0 -270
  460. package/rules/common/hooks.md +0 -35
  461. package/rules/zh/README.md +0 -113
  462. package/rules/zh/agents.md +0 -55
  463. package/rules/zh/code-review.md +0 -129
  464. package/rules/zh/coding-style.md +0 -53
  465. package/rules/zh/development-workflow.md +0 -49
  466. package/rules/zh/git-workflow.md +0 -29
  467. package/rules/zh/hooks.md +0 -35
  468. package/rules/zh/patterns.md +0 -36
  469. package/rules/zh/performance.md +0 -60
  470. package/rules/zh/security.md +0 -34
  471. package/rules/zh/testing.md +0 -34
@@ -0,0 +1,325 @@
1
+ /**
2
+ * lib/embed/semantic.mjs — Semantic analysis for intake signals.
3
+ *
4
+ * Generates embeddings via @xenova/transformers, caches them to disk,
5
+ * computes cosine similarity, and clusters related signals by topic.
6
+ * All local — no external API calls. Default model is all-MiniLM-L6-v2
7
+ * (384-dimensional vectors, ~50MB on disk, fast inference).
8
+ *
9
+ * Storage:
10
+ * ~/.cx/cache/embeddings/<sha256>.json — single embedding vector
11
+ * ~/.cx/cache/embeddings/index.json — hash → metadata lookup
12
+ *
13
+ * Usage:
14
+ * const vec = await embed(text);
15
+ * const similarity = cosineSimilarity(vecA, vecB);
16
+ * const clusters = clusterVectors(vectors, { threshold: 0.7 });
17
+ */
18
+
19
+ import { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync } from 'node:fs';
20
+ import { join } from 'node:path';
21
+ import { createHash, randomUUID } from 'node:crypto';
22
+ import { homedir } from 'node:os';
23
+
24
+ const CACHE_DIR = join(homedir(), '.cx', 'cache', 'embeddings');
25
+ const INDEX_FILE = join(CACHE_DIR, 'index.json');
26
+ const MODEL_NAME = 'all-MiniLM-L6-v2';
27
+ const DIMENSIONS = 384;
28
+ const LOG = process.env.CONSTRUCT_DEBUG_EMBED === '1';
29
+
30
+ let embedder = null;
31
+
32
+ /**
33
+ * Lazy-load the embedding pipeline. Loads once per process.
34
+ */
35
+ async function getEmbedder() {
36
+ if (embedder !== null) return embedder;
37
+ try {
38
+ const { pipeline } = await import('@xenova/transformers');
39
+ embedder = await pipeline('feature-extraction', MODEL_NAME, {
40
+ quantized: true,
41
+ });
42
+ if (LOG) process.stderr.write('[semantic] embedder loaded\n');
43
+ } catch (err) {
44
+ process.stderr.write(`[semantic] model load failed: ${err.message}\n`);
45
+ embedder = false;
46
+ }
47
+ return embedder;
48
+ }
49
+
50
+ /**
51
+ * Ensure the cache directory exists.
52
+ */
53
+ function ensureCacheDir() {
54
+ if (!existsSync(CACHE_DIR)) mkdirSync(CACHE_DIR, { recursive: true });
55
+ }
56
+
57
+ /**
58
+ * Read the cache index.
59
+ */
60
+ function readIndex() {
61
+ if (!existsSync(INDEX_FILE)) return {};
62
+ try { return JSON.parse(readFileSync(INDEX_FILE, 'utf8')); } catch { return {}; }
63
+ }
64
+
65
+ /**
66
+ * Write the cache index.
67
+ */
68
+ function writeIndex(index) {
69
+ ensureCacheDir();
70
+ writeFileSync(INDEX_FILE, JSON.stringify(index, null, 2) + '\n');
71
+ }
72
+
73
+ /**
74
+ * Compute sha256 of text for cache key.
75
+ */
76
+ function hashText(text) {
77
+ return createHash('sha256').update(text).digest('hex');
78
+ }
79
+
80
+ /**
81
+ * Normalize a vector to unit length.
82
+ */
83
+ function normalize(vec) {
84
+ const mag = Math.sqrt(vec.reduce((sum, v) => sum + v * v, 0));
85
+ if (mag === 0) return vec;
86
+ return vec.map(v => v / mag);
87
+ }
88
+
89
+ /**
90
+ * Generate an embedding vector for the given text.
91
+ * Results are cached by sha256 hash to disk.
92
+ *
93
+ * @param {string} text - Text to embed
94
+ * @returns {Promise<Float32Array|number[]|null>} Embedding vector
95
+ */
96
+ export async function embed(text) {
97
+ if (!text || typeof text !== 'string' || !text.trim()) return null;
98
+
99
+ ensureCacheDir();
100
+ const h = hashText(text.trim());
101
+
102
+ // Check cache
103
+ const index = readIndex();
104
+ if (index[h]) {
105
+ try {
106
+ const data = JSON.parse(readFileSync(join(CACHE_DIR, `${h}.json`), 'utf8'));
107
+ if (LOG) process.stderr.write(`[semantic] cache hit for ${h.slice(0, 12)}\n`);
108
+ return new Float32Array(data.vector);
109
+ } catch {
110
+ // cache miss or corrupt — proceed to compute
111
+ }
112
+ }
113
+
114
+ const pipe = await getEmbedder();
115
+ if (!pipe) return null;
116
+
117
+ try {
118
+ const output = await pipe(text, { pooling: 'mean', normalize: false });
119
+ const vector = Array.from(output.data);
120
+ const data = { h, dim: vector.length, vector };
121
+
122
+ // Write cache
123
+ writeFileSync(join(CACHE_DIR, `${h}.json`), JSON.stringify(data) + '\n');
124
+
125
+ // Update index (lru tracking)
126
+ index[h] = { h, dim: vector.length, cachedAt: new Date().toISOString(), textPreview: text.slice(0, 80) };
127
+ writeIndex(index);
128
+
129
+ if (LOG) process.stderr.write(`[semantic] embedded ${text.slice(0, 40)}... (${vector.length}d)\n`);
130
+ return new Float32Array(vector);
131
+ } catch (err) {
132
+ process.stderr.write(`[semantic] embedding failed: ${err.message}\n`);
133
+ return null;
134
+ }
135
+ }
136
+
137
+ /**
138
+ * Compute cosine similarity between two vectors.
139
+ * Returns 0 if either vector is null/mismatched.
140
+ *
141
+ * @param {number[]|Float32Array} a
142
+ * @param {number[]|Float32Array} b
143
+ * @returns {number} Similarity score in [0, 1]
144
+ */
145
+ export function cosineSimilarity(a, b) {
146
+ if (!a || !b) return 0;
147
+ if (a.length !== b.length) return 0;
148
+
149
+ const na = normalize(Array.from(a));
150
+ const nb = normalize(Array.from(b));
151
+
152
+ let dot = 0;
153
+ for (let i = 0; i < na.length; i++) {
154
+ dot += na[i] * nb[i];
155
+ }
156
+ return Math.max(0, Math.min(1, dot));
157
+ }
158
+
159
+ /**
160
+ * Compute similarity matrix for an array of vectors.
161
+ * Returns lower-triangular { i, j, similarity } objects.
162
+ *
163
+ * @param {Array<Float32Array|number[]>} vectors
164
+ * @returns {Array<{ i: number, j: number, similarity: number }>}
165
+ */
166
+ export function similarityMatrix(vectors) {
167
+ const pairs = [];
168
+ for (let i = 1; i < vectors.length; i++) {
169
+ if (!vectors[i]) continue;
170
+ for (let j = 0; j < i; j++) {
171
+ if (!vectors[j]) continue;
172
+ const sim = cosineSimilarity(vectors[i], vectors[j]);
173
+ if (sim > 0) {
174
+ pairs.push({ i, j, similarity: sim });
175
+ }
176
+ }
177
+ }
178
+ return pairs.sort((a, b) => b.similarity - a.similarity);
179
+ }
180
+
181
+ /**
182
+ * Cluster vectors by similarity threshold (simple connected-components).
183
+ * Returns clusters with items grouped by similarity > threshold.
184
+ *
185
+ * @param {Array<{ id: string, vector: Float32Array|number[], text?: string }>} items
186
+ * @param {object} [opts]
187
+ * @param {number} [opts.threshold=0.7] - Similarity threshold for clustering
188
+ * @param {number} [opts.minClusterSize=1] - Minimum items per cluster
189
+ * @returns {Array<{ id: string, items: string[], avgSimilarity: number, textPreview: string }>}
190
+ */
191
+ export function clusterVectors(items, { threshold = 0.7, minClusterSize = 1 } = {}) {
192
+ if (!items.length) return [];
193
+
194
+ const n = items.length;
195
+ const adj = Array.from({ length: n }, () => []);
196
+
197
+ // Build adjacency from similarity threshold
198
+ for (let i = 1; i < n; i++) {
199
+ if (!items[i]?.vector) continue;
200
+ for (let j = 0; j < i; j++) {
201
+ if (!items[j]?.vector) continue;
202
+ if (cosineSimilarity(items[i].vector, items[j].vector) >= threshold) {
203
+ adj[i].push(j);
204
+ adj[j].push(i);
205
+ }
206
+ }
207
+ }
208
+
209
+ // BFS connected components
210
+ const visited = new Set();
211
+ const clusters = [];
212
+
213
+ for (let i = 0; i < n; i++) {
214
+ if (visited.has(i) || !items[i]?.vector) continue;
215
+
216
+ const component = [];
217
+ const queue = [i];
218
+ visited.add(i);
219
+
220
+ while (queue.length) {
221
+ const node = queue.shift();
222
+ component.push(items[node].id);
223
+ for (const neighbor of adj[node]) {
224
+ if (!visited.has(neighbor)) {
225
+ visited.add(neighbor);
226
+ queue.push(neighbor);
227
+ }
228
+ }
229
+ }
230
+
231
+ if (component.length >= minClusterSize) {
232
+ const clusterItems = component.map(id => items.find(it => it.id === id)).filter(Boolean);
233
+ const sims = [];
234
+ for (let a = 0; a < clusterItems.length; a++) {
235
+ for (let b = a + 1; b < clusterItems.length; b++) {
236
+ if (clusterItems[a]?.vector && clusterItems[b]?.vector) {
237
+ sims.push(cosineSimilarity(clusterItems[a].vector, clusterItems[b].vector));
238
+ }
239
+ }
240
+ }
241
+ const avgSim = sims.length ? sims.reduce((s, v) => s + v, 0) / sims.length : 0;
242
+
243
+ clusters.push({
244
+ id: `cluster-${randomUUID().slice(0, 8)}`,
245
+ items: component,
246
+ clusterSize: component.length,
247
+ avgSimilarity: Number(avgSim.toFixed(3)),
248
+ textPreview: clusterItems[0]?.text?.slice(0, 100) || '',
249
+ });
250
+ }
251
+ }
252
+
253
+ return clusters.sort((a, b) => b.clusterSize - a.clusterSize);
254
+ }
255
+
256
+ /**
257
+ * Extract embeddable text from an intake packet.
258
+ *
259
+ * @param {object} packet - Intake packet with triage, excerpt, etc.
260
+ * @returns {string} Text suitable for embedding
261
+ */
262
+ export function extractTextFromPacket(packet) {
263
+ const parts = [];
264
+ if (packet?.triage?.intakeType && packet.triage.intakeType !== 'unknown') {
265
+ parts.push(`Type: ${packet.triage.intakeType}`);
266
+ }
267
+ if (packet?.triage?.rationale) {
268
+ parts.push(`Rationale: ${packet.triage.rationale}`);
269
+ }
270
+ if (packet?.excerpt) {
271
+ parts.push(packet.excerpt);
272
+ }
273
+ if (packet?.suggestion?.lane) {
274
+ parts.push(`Suggested lane: ${packet.suggestion.lane}`);
275
+ }
276
+ return parts.join('\n').slice(0, 5000);
277
+ }
278
+
279
+ /**
280
+ * Find semantically similar pending intake items relative to a query text.
281
+ *
282
+ * @param {Array<{ id: string, text: string, embedding?: Float32Array }>} items
283
+ * @param {string} queryText
284
+ * @param {object} [opts]
285
+ * @param {number} [opts.threshold=0.6]
286
+ * @param {number} [opts.maxResults=10]
287
+ * @returns {Promise<Array<{ id: string, similarity: number, text: string }>>}
288
+ */
289
+ export async function findSimilar(items, queryText, { threshold = 0.6, maxResults = 10 } = {}) {
290
+ const queryVec = await embed(queryText);
291
+ if (!queryVec) return [];
292
+
293
+ const results = [];
294
+ for (const item of items) {
295
+ if (!item.text) continue;
296
+ const itemVec = item.embedding || await embed(item.text);
297
+ if (!itemVec) continue;
298
+ const sim = cosineSimilarity(queryVec, itemVec);
299
+ if (sim >= threshold) {
300
+ results.push({ id: item.id, similarity: sim, text: item.text.slice(0, 120) });
301
+ item.embedding = itemVec; // cache on object for reuse
302
+ }
303
+ }
304
+
305
+ return results.sort((a, b) => b.similarity - a.similarity).slice(0, maxResults);
306
+ }
307
+
308
+ /**
309
+ * Get cache statistics.
310
+ *
311
+ * @returns {{ total: number, oldest: string|null, newest: string|null }}
312
+ */
313
+ export function cacheStats() {
314
+ const index = readIndex();
315
+ const entries = Object.values(index);
316
+ if (!entries.length) return { total: 0, oldest: null, newest: null };
317
+ const byDate = entries.sort((a, b) => a.cachedAt?.localeCompare(b.cachedAt || '') || 0);
318
+ return {
319
+ total: entries.length,
320
+ oldest: byDate[0]?.cachedAt || null,
321
+ newest: byDate[byDate.length - 1]?.cachedAt || null,
322
+ };
323
+ }
324
+
325
+
@@ -0,0 +1,191 @@
1
+ /**
2
+ * lib/embed/snapshot.mjs — snapshot engine.
3
+ *
4
+ * Reads data from registered providers according to embed config sources,
5
+ * then produces a structured snapshot: health summary, open items, risks,
6
+ * and recommendations. Snapshots are plain objects that can be serialized
7
+ * to markdown, JSON, or dispatched to output targets.
8
+ */
9
+
10
+ import { existsSync } from 'node:fs';
11
+ import { resolve } from 'node:path';
12
+ import { DEFAULT_OPERATING_PROFILE } from './config.mjs';
13
+ import { renderRoleLensSection } from './role-framing.mjs';
14
+
15
+ export class SnapshotEngine {
16
+ #registry = null;
17
+ #config = null;
18
+ #rootDir = null;
19
+ #workspaceDir = null;
20
+
21
+ /**
22
+ * @param {ProviderRegistry} registry - Live provider registry
23
+ * @param {object} config - Normalized embed config
24
+ * @param {object} [opts]
25
+ * @param {string} [opts.rootDir] - Data dir where .cx/* runtime artifacts live (default: process.cwd())
26
+ * @param {string} [opts.workspaceDir] - Project workspace dir for project-relative focal resources (default: rootDir)
27
+ */
28
+ constructor(registry, config, { rootDir = process.cwd(), workspaceDir } = {}) {
29
+ this.#registry = registry;
30
+ this.#config = { ...config, operatingProfile: config.operatingProfile ?? DEFAULT_OPERATING_PROFILE };
31
+ this.#rootDir = rootDir;
32
+ this.#workspaceDir = workspaceDir ?? rootDir;
33
+ }
34
+
35
+ /**
36
+ * Produce a full snapshot by polling all configured sources.
37
+ * Returns a snapshot object.
38
+ */
39
+ async generate() {
40
+ const startedAt = new Date().toISOString();
41
+ const sections = [];
42
+ const errors = [];
43
+
44
+ for (const source of this.#config.sources ?? []) {
45
+ const provider = this.#registry.get(source.provider);
46
+ if (!provider) {
47
+ errors.push({ source: source.provider, error: 'Provider not registered' });
48
+ continue;
49
+ }
50
+
51
+ const items = [];
52
+ for (const ref of source.refs) {
53
+ try {
54
+ const result = await provider.read(ref, source);
55
+ items.push(...result.slice(0, this.#config.snapshot.maxItems));
56
+ } catch (err) {
57
+ errors.push({ source: source.provider, ref, error: err.message });
58
+ }
59
+ }
60
+
61
+ sections.push({ provider: source.provider, refs: source.refs, items });
62
+ }
63
+
64
+ const snapshot = {
65
+ generatedAt: startedAt,
66
+ completedAt: new Date().toISOString(),
67
+ sections,
68
+ errors,
69
+ operatingProfile: this.#config.operatingProfile ?? null,
70
+ operatingGaps: buildOperatingGaps(this.#config, { rootDir: this.#rootDir, workspaceDir: this.#workspaceDir, errors }),
71
+ executionGaps: [], // Filled by execution-gap job
72
+ roles: this.#config.roles ?? null,
73
+ summary: buildSummary(sections, errors),
74
+ };
75
+
76
+ return snapshot;
77
+ }
78
+ }
79
+
80
+ /**
81
+ * Render a snapshot as markdown for file output or messaging.
82
+ */
83
+ export function renderMarkdown(snapshot) {
84
+ const lines = [
85
+ `# Construct Snapshot`,
86
+ `> Generated: ${snapshot.generatedAt}`,
87
+ '',
88
+ ];
89
+
90
+ if (snapshot.errors.length) {
91
+ lines.push('## ⚠️ Errors', '');
92
+ for (const e of snapshot.errors) {
93
+ lines.push(`- **${e.source}**${e.ref ? `/${e.ref}` : ''}: ${e.error}`);
94
+ }
95
+ lines.push('');
96
+ }
97
+
98
+ lines.push('## Summary', '');
99
+ const s = snapshot.summary;
100
+ lines.push(`- Sources polled: **${s.sourceCount}**`);
101
+ lines.push(`- Total items: **${s.totalItems}**`);
102
+ lines.push(`- Errors: **${s.errorCount}**`);
103
+ lines.push('');
104
+
105
+ if (snapshot.operatingProfile) {
106
+ const profile = snapshot.operatingProfile;
107
+ lines.push('## Operating Profile', '');
108
+ lines.push(`- Mode: **${profile.mode ?? 'embed'}**`);
109
+ lines.push(`- Posture: **${profile.strategy?.defaultPosture ?? 'assistive'}**`);
110
+ lines.push(`- Autonomy: **${profile.strategy?.autonomy ?? 'read-first'}**`);
111
+ lines.push(`- Write policy: **${profile.strategy?.writePolicy ?? 'approval-required-for-high-risk'}**`);
112
+ lines.push('');
113
+ }
114
+
115
+ // Role lens (from configured primary/secondary roles)
116
+ if (snapshot.roles) {
117
+ const roleLens = renderRoleLensSection(snapshot.roles);
118
+ if (roleLens) lines.push(roleLens);
119
+ }
120
+
121
+ if (snapshot.operatingGaps?.length) {
122
+ lines.push('## Operating Gaps', '');
123
+ for (const gap of snapshot.operatingGaps) {
124
+ lines.push(`- **${gap.severity}** ${gap.summary}`);
125
+ }
126
+ lines.push('');
127
+ }
128
+
129
+ if (snapshot.executionGaps?.length) {
130
+ lines.push('## Execution Gaps (TPM Analysis)', '');
131
+ for (const gap of snapshot.executionGaps) {
132
+ lines.push(`- **${gap.severity}** ${gap.summary}`);
133
+ }
134
+ lines.push('');
135
+ }
136
+
137
+ for (const section of snapshot.sections) {
138
+ if (!section.items.length) continue;
139
+ lines.push(`## ${section.provider} (${section.items.length} items)`, '');
140
+ for (const item of section.items.slice(0, 10)) {
141
+ lines.push(`- ${formatItem(item)}`);
142
+ }
143
+ if (section.items.length > 10) {
144
+ lines.push(`- _…and ${section.items.length - 10} more_`);
145
+ }
146
+ lines.push('');
147
+ }
148
+
149
+ return lines.join('\n');
150
+ }
151
+
152
+ function buildSummary(sections, errors) {
153
+ return {
154
+ sourceCount: sections.length,
155
+ totalItems: sections.reduce((n, s) => n + s.items.length, 0),
156
+ errorCount: errors.length,
157
+ };
158
+ }
159
+
160
+ function buildOperatingGaps(config, { rootDir, workspaceDir, errors }) {
161
+ const gaps = [];
162
+ if (!(config.sources ?? []).length) {
163
+ gaps.push({ severity: 'blocked', kind: 'missing-sources', summary: 'No watched sources are configured, so embed mode has no external bearings.' });
164
+ }
165
+ if (!(config.outputs ?? []).length) {
166
+ gaps.push({ severity: 'attention', kind: 'missing-outputs', summary: 'No snapshot outputs are configured; findings may not reach operators.' });
167
+ }
168
+ const searchRoots = [rootDir, workspaceDir].filter((d, i, arr) => d && arr.indexOf(d) === i);
169
+ for (const resource of config.operatingProfile?.focalResources ?? []) {
170
+ if (!resource.path) continue;
171
+ const found = searchRoots.some((root) => existsSync(resolve(root, resource.path)));
172
+ if (!found) {
173
+ gaps.push({ severity: 'blocked', kind: 'missing-focal-resource', summary: `Focal resource is missing: ${resource.path}` });
174
+ }
175
+ }
176
+ if (errors?.length) {
177
+ gaps.push({ severity: 'attention', kind: 'source-errors', summary: `${errors.length} configured source read failed.` });
178
+ }
179
+ return gaps;
180
+ }
181
+
182
+ function formatItem(item) {
183
+ if (item.type === 'commit') return `\`${item.hash?.slice(0, 7)}\` ${item.subject} — ${item.author}`;
184
+ if (item.type === 'issue') return `[${item.key}] ${item.summary} (${item.status})`;
185
+ if (item.type === 'message') return `<${item.user}> ${item.text?.slice(0, 80)}`;
186
+ if (item.type === 'page') return `${item.title} (${item.spaceKey ?? ''})`;
187
+ if (item.summary) return item.summary;
188
+ if (item.title) return item.title;
189
+ if (item.text) return item.text.slice(0, 80);
190
+ return JSON.stringify(item).slice(0, 80);
191
+ }