@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
@@ -1,841 +0,0 @@
1
- /**
2
- * lib/server/static/app.js — <one-line purpose>
3
- *
4
- * <2–6 line summary: what it does, who calls it, key side effects.>
5
- */
6
- (function () {
7
- 'use strict';
8
-
9
- let state = null;
10
-
11
- // ── Navigation ──────────────────────────────────────────────────────
12
- function initNav() {
13
- document.querySelectorAll('#nav a').forEach(link => {
14
- link.addEventListener('click', e => {
15
- e.preventDefault();
16
- const target = link.dataset.section;
17
- activateSection(target);
18
- document.querySelectorAll('#nav a').forEach(l => l.classList.remove('active'));
19
- link.classList.add('active');
20
- });
21
- });
22
- }
23
-
24
- function activateSection(id) {
25
- document.querySelectorAll('.section').forEach(s => s.classList.remove('active'));
26
- const el = document.getElementById('section-' + id);
27
- if (el) el.classList.add('active');
28
- }
29
-
30
- // ── Data fetching ────────────────────────────────────────────────────
31
- async function fetchStatus() {
32
- const res = await fetch('/api/status');
33
- if (!res.ok) throw new Error('HTTP ' + res.status);
34
- return res.json();
35
- }
36
-
37
- async function refresh() {
38
- try {
39
- state = await fetchStatus();
40
- render(state);
41
- } catch (err) {
42
- console.error('Failed to fetch status:', err);
43
- }
44
- }
45
-
46
- async function refreshRegistry() {
47
- try { await fetchRegistry(); } catch (err) { console.error('Failed to fetch registry:', err); }
48
- }
49
-
50
- async function postJson(url, body) {
51
- const res = await fetch(url, {
52
- method: 'POST',
53
- headers: { 'Content-Type': 'application/json' },
54
- body: JSON.stringify(body),
55
- });
56
- const data = await res.json().catch(() => ({}));
57
- if (!res.ok) throw new Error(data.error || `HTTP ${res.status}`);
58
- return data;
59
- }
60
-
61
- // ── SSE ──────────────────────────────────────────────────────────────
62
- function connectSSE() {
63
- const es = new EventSource('/events');
64
- es.addEventListener('message', e => {
65
- if (e.data === 'refresh') { refresh(); refreshRegistry(); }
66
- });
67
- es.onerror = () => {
68
- es.close();
69
- setTimeout(connectSSE, 5000);
70
- };
71
- }
72
-
73
- // ── Rendering ────────────────────────────────────────────────────────
74
- function render(data) {
75
- renderTopbar(data);
76
- renderResources(data.features ?? [], data.overlays ?? [], data.promotionRequests ?? []);
77
- renderAgents(data.personas ?? [], data.specialists ?? []);
78
- renderWorkflow(data.workflow ?? null);
79
- renderOverlays(data.overlays ?? [], data.promotionRequests ?? []);
80
- renderSkills(data.skills ?? []);
81
- renderCLICommands(data.cliCommands ?? []);
82
- renderCommands(data.commands ?? []);
83
- renderHooks(data.hooks ?? []);
84
- }
85
-
86
- function renderTopbar(data) {
87
- const versionEl = document.getElementById('version');
88
- const syncEl = document.getElementById('lastsync');
89
- const dotEl = document.getElementById('healthdot');
90
- const statusEl = document.getElementById('system-status');
91
-
92
- if (versionEl) versionEl.textContent = 'v' + (data.version ?? '?');
93
-
94
- if (syncEl) {
95
- if (data.lastSync) {
96
- const d = new Date(data.lastSync);
97
- syncEl.textContent = 'synced ' + d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
98
- } else {
99
- syncEl.textContent = 'not synced';
100
- }
101
- }
102
-
103
- if (dotEl) {
104
- const overall = data.system?.overall?.status ?? 'unavailable';
105
- dotEl.className = 'health-dot ' + (overall === 'healthy' ? 'healthy' : overall === 'degraded' ? 'degraded' : 'bad');
106
- dotEl.title = data.system?.overall?.summary ?? 'System health unknown';
107
- }
108
-
109
- if (statusEl) {
110
- const usage = data.sessionUsage?.status === 'available'
111
- ? ` · ${data.sessionUsage.totalTokens.toLocaleString()} tokens this session`
112
- : '';
113
- const telemetry = data.telemetryRichness?.total !== undefined
114
- ? ` · telemetry ${data.telemetryRichness.status}${data.telemetryRichness.total ? ` ${data.telemetryRichness.rich}/${data.telemetryRichness.total} rich` : ''}`
115
- : '';
116
- statusEl.textContent = (data.system?.overall?.summary ?? 'status unavailable') + usage + telemetry;
117
- }
118
- }
119
-
120
- function statusBadge(status) {
121
- const labels = {
122
- healthy: 'Healthy',
123
- configured: 'Configured',
124
- degraded: 'Degraded',
125
- unavailable: 'Unavailable',
126
- disabled: 'Disabled',
127
- };
128
- const label = labels[status] ?? status;
129
- return `<span class="badge badge-${status}">${label}</span>`;
130
- }
131
-
132
- function tierBadge(tier) {
133
- const t = (tier ?? 'standard').toLowerCase();
134
- return `<span class="badge badge-${t}">${t}</span>`;
135
- }
136
-
137
- function esc(str) {
138
- return String(str ?? '')
139
- .replace(/&/g, '&amp;')
140
- .replace(/</g, '&lt;')
141
- .replace(/>/g, '&gt;')
142
- .replace(/"/g, '&quot;');
143
- }
144
-
145
- // ── Resources ────────────────────────────────────────────────────────
146
- function renderResources(features) {
147
- const container = document.getElementById('resources-cards');
148
- if (!container) return;
149
-
150
- if (!features.length) {
151
- container.innerHTML = '<div class="empty">No features configured.</div>';
152
- return;
153
- }
154
-
155
- const services = state?.system?.services ?? [];
156
- const telemetry = state?.telemetryRichness;
157
- const serviceCards = services.map(service => {
158
- const msg = service.message
159
- ? `<div class="card-footer">${esc(service.message)}${service.note ? ` · ${esc(service.note)}` : ''}</div>`
160
- : service.note
161
- ? `<div class="card-footer">${esc(service.note)}</div>`
162
- : '';
163
-
164
- return `
165
- <div class="card">
166
- <div class="card-header">
167
- <span class="card-name">${esc(service.name)}</span>
168
- ${statusBadge(service.status)}
169
- </div>
170
- <div class="card-desc">${esc(service.url)}</div>
171
- ${msg}
172
- </div>`;
173
- });
174
-
175
- const featureCards = features.map(f => {
176
- const msg = f.status === 'disabled'
177
- ? `<div class="card-footer">${esc(f.degradedMessage)}</div>`
178
- : f.message
179
- ? `<div class="card-footer">${esc(f.message)}</div>`
180
- : '';
181
-
182
- return `
183
- <div class="card">
184
- <div class="card-header">
185
- <span class="card-name">${esc(f.name)}</span>
186
- ${statusBadge(f.status)}
187
- </div>
188
- <div class="card-desc">${esc(f.description)}</div>
189
- ${msg}
190
- </div>`;
191
- });
192
-
193
- const telemetryCard = telemetry ? [`
194
- <div class="card">
195
- <div class="card-header">
196
- <span class="card-name">Telemetry Richness</span>
197
- ${statusBadge(telemetry.status)}
198
- </div>
199
- <div class="card-desc">${esc(telemetry.summary)}</div>
200
- <div class="card-footer">coverage ${((Number(telemetry.coverage ?? 0) || 0) * 100).toFixed(0)}% · rich ${esc(String(telemetry.rich ?? 0))} · partial ${esc(String(telemetry.partial ?? 0))} · sparse ${esc(String(telemetry.sparse ?? 0))}</div>
201
- </div>`] : [];
202
-
203
- container.innerHTML = [...telemetryCard, ...serviceCards, ...featureCards].join('');
204
- }
205
-
206
- // ── Agents ────────────────────────────────────────────────────────────
207
- function renderAgents(personas, specialists) {
208
- const container = document.getElementById('agents-content');
209
- if (!container) return;
210
-
211
- let html = '';
212
-
213
- if (personas.length) {
214
- html += `<div class="agent-group">
215
- <div class="agent-group-title">Entry Point</div>
216
- <div class="persona-cards">`;
217
-
218
- html += personas.map(p => `
219
- <div class="persona-card">
220
- <div class="card-header">
221
- <div>
222
- <div class="persona-name">@${esc(p.name)}</div>
223
- <div class="persona-role">${esc(p.role)}</div>
224
- </div>
225
- ${tierBadge(p.modelTier)}
226
- </div>
227
- <div class="persona-desc">${esc(p.description)}</div>
228
- </div>`).join('');
229
-
230
- html += '</div></div>';
231
- }
232
-
233
- if (specialists.length) {
234
- html += `<div class="agent-group">
235
- <div class="agent-group-title">Specialists</div>
236
- <div class="specialist-list">`;
237
-
238
- html += specialists.map(s => `
239
- <div class="specialist-row">
240
- <span class="specialist-name">${esc(s.name)}</span>
241
- <span class="specialist-desc" title="${esc(s.description)}">${esc(s.description)}</span>
242
- ${tierBadge(s.modelTier)}
243
- </div>`).join('');
244
-
245
- html += '</div></div>';
246
- }
247
-
248
- if (!html) html = '<div class="empty">No agents found.</div>';
249
- container.innerHTML = html;
250
- }
251
-
252
- // ── Workflow ────────────────────────────────────────────────────────
253
- const PHASE_ORDER = ['research', 'plan', 'implement', 'validate', 'operate'];
254
- const PHASE_ICONS = { research: '◎', plan: '▣', implement: '⚙', validate: '✓', operate: '▶' };
255
-
256
- function phaseStatusClass(status) {
257
- if (!status || status === 'todo') return 'pipeline-phase--todo';
258
- if (status === 'done' || status === 'executive-approved') return 'pipeline-phase--done';
259
- if (status === 'in-progress') return 'pipeline-phase--active';
260
- if (status === 'blocked') return 'pipeline-phase--blocked';
261
- return 'pipeline-phase--todo';
262
- }
263
-
264
- function renderPipeline(phases, activePhase) {
265
- return `<div class="dispatch-pipeline">${PHASE_ORDER.map((p, i) => {
266
- const phaseData = phases?.[p] ?? {};
267
- const isActive = p === activePhase;
268
- const cls = isActive ? 'pipeline-phase--active' : phaseStatusClass(phaseData.status);
269
- const connector = i < PHASE_ORDER.length - 1 ? '<span class="pipeline-connector">→</span>' : '';
270
- return `<span class="pipeline-phase ${cls}" title="${esc(phaseData.summary ?? p)}">${PHASE_ICONS[p] ?? '·'} ${esc(p)}</span>${connector}`;
271
- }).join('')}</div>`;
272
- }
273
-
274
- function renderWorkflow(workflow) {
275
- const container = document.getElementById('workflow-content');
276
- if (!container) return;
277
-
278
- if (!workflow || !workflow.exists) {
279
- container.innerHTML = '<div class="empty">No .cx/workflow.json in the dashboard working directory.</div>';
280
- return;
281
- }
282
-
283
- const state = workflow.state ?? {};
284
- const tasks = state.tasks ?? [];
285
- const current = tasks.find(t => t.key === state.currentTaskKey);
286
- const findings = workflow.findings ?? [];
287
-
288
- const taskRows = tasks.length ? tasks.map(t => {
289
- const needsReview = t.phase === 'implement' && t.status !== 'done' && t.status !== 'skipped';
290
- const verified = t.verification && t.verification.length;
291
- const badge = verified
292
- ? '<span class="badge badge-healthy">verified</span>'
293
- : needsReview
294
- ? '<span class="badge badge-degraded">needs review</span>'
295
- : '<span class="badge badge-disabled">open</span>';
296
- return `
297
- <div class="specialist-row">
298
- <span class="specialist-name">${esc(t.key)} <span style="color:var(--text-muted)">${esc(t.status)}</span></span>
299
- <span class="specialist-desc" title="${esc(t.title)}">${esc(t.phase)} / ${esc(t.owner)} — ${esc(t.title)}${t.challengeRequired ? ` · challenge: ${esc(t.challengeStatus || 'pending')}` : ''}</span>
300
- ${badge}
301
- </div>`;
302
- }).join('') : '<div class="empty">No tasks yet.</div>';
303
-
304
- const findingRows = findings.length ? findings.map(f => `
305
- <div class="hook-row">
306
- <div class="${f.severity === 'HIGH' ? 'hook-bullet blocking' : 'hook-bullet'}"></div>
307
- <div class="hook-desc">${esc(f.task ? f.task + ': ' : '')}${esc(f.issue)}<br><span class="hook-phase-badge">${esc(f.fix)}</span></div>
308
- </div>`).join('') : '<div class="empty">Alignment checks pass.</div>';
309
-
310
- const dispatchPlan = state.dispatchPlan
311
- ? `<div class="card-footer" style="font-style:italic;color:var(--accent-dim)">${esc(state.dispatchPlan)}</div>`
312
- : '';
313
-
314
- container.innerHTML = `
315
- <div class="cards">
316
- <div class="card">
317
- <div class="card-header">
318
- <span class="card-name">${esc(state.title)}</span>
319
- <span class="badge badge-${workflow.status === 'pass' ? 'healthy' : workflow.status === 'warn' ? 'degraded' : 'unavailable'}">${esc(workflow.status)}</span>
320
- </div>
321
- <div class="card-desc">${esc(workflow.summary)}</div>
322
- ${dispatchPlan}
323
- <div style="margin-top:12px">${renderPipeline(state.phases, state.phase)}</div>
324
- <div class="card-footer">${esc(workflow.cwd)}</div>
325
- </div>
326
- <div class="card">
327
- <div class="card-header">
328
- <span class="card-name">Current Task</span>
329
- ${current ? '<span class="badge badge-standard">' + esc(current.phase) + '</span>' : '<span class="badge badge-disabled">none</span>'}
330
- </div>
331
- <div class="card-desc">${current ? esc(current.key + ' — ' + current.title) : 'No active task.'}</div>
332
- <div class="card-footer">${current ? esc(current.owner + ' / ' + current.status) : ''}</div>
333
- </div>
334
- </div>
335
- <div class="agent-group">
336
- <div class="agent-group-title">Tasks</div>
337
- <div class="specialist-list">${taskRows}</div>
338
- </div>
339
- <div class="agent-group">
340
- <div class="agent-group-title">Alignment</div>
341
- ${findingRows}
342
- </div>`;
343
- }
344
-
345
- function renderOverlays(overlays, promotions) {
346
- const container = document.getElementById('workflow-content');
347
- if (!container) return;
348
- const existing = document.getElementById('workflow-overlays');
349
- if (existing) existing.remove();
350
- if (!overlays.length && !promotions.length) return;
351
-
352
- const section = document.createElement('div');
353
- section.id = 'workflow-overlays';
354
- section.className = 'agent-group';
355
-
356
- const header = document.createElement('div');
357
- header.className = 'agent-group-title';
358
- header.textContent = 'Overlays & Promotion Requests';
359
- section.appendChild(header);
360
-
361
- const list = document.createElement('div');
362
- list.className = 'specialist-list';
363
-
364
- overlays.forEach((overlay) => {
365
- const row = document.createElement('div');
366
- row.className = 'specialist-row';
367
- row.innerHTML = `
368
- <span class="specialist-name">${esc(overlay.domain)}</span>
369
- <span class="specialist-desc">${esc(overlay.objective)}${overlay.taskKey ? ` · task ${esc(overlay.taskKey)}` : ''}</span>
370
- <span class="badge badge-standard">${esc(overlay.focus)}</span>`;
371
-
372
- const actions = document.createElement('div');
373
- actions.style.display = 'flex';
374
- actions.style.gap = '6px';
375
-
376
- const promote = document.createElement('button');
377
- promote.className = 'tab-btn';
378
- promote.style.padding = '4px 8px';
379
- promote.textContent = 'promote';
380
- promote.addEventListener('click', async () => {
381
- try {
382
- await postJson('/api/headhunt/promote', { id: overlay.id });
383
- await refresh();
384
- } catch (error) {
385
- console.error('Failed to promote overlay:', error);
386
- }
387
- });
388
- actions.appendChild(promote);
389
- row.appendChild(actions);
390
- list.appendChild(row);
391
- });
392
-
393
- promotions.forEach((promotion) => {
394
- const row = document.createElement('div');
395
- row.className = 'specialist-row';
396
- row.innerHTML = `
397
- <span class="specialist-name">${esc(promotion.domain)}</span>
398
- <span class="specialist-desc">challenge ${esc(promotion.challenge?.status ?? 'pending')} · ${esc((promotion.reviewFlow ?? []).join(' → '))}</span>
399
- <span class="badge badge-degraded">${esc(promotion.status)}</span>`;
400
-
401
- const actions = document.createElement('div');
402
- actions.style.display = 'flex';
403
- actions.style.gap = '6px';
404
-
405
- ['approved', 'rejected', 'pending'].forEach((status) => {
406
- const button = document.createElement('button');
407
- button.className = 'tab-btn';
408
- button.style.padding = '4px 8px';
409
- button.textContent = status;
410
- button.addEventListener('click', async () => {
411
- try {
412
- await postJson('/api/headhunt/challenge', { id: promotion.id, status });
413
- await refresh();
414
- } catch (error) {
415
- console.error('Failed to update promotion challenge:', error);
416
- }
417
- });
418
- actions.appendChild(button);
419
- });
420
-
421
- row.appendChild(actions);
422
- list.appendChild(row);
423
- });
424
-
425
- section.appendChild(list);
426
- container.appendChild(section);
427
- }
428
-
429
- // ── Skills ────────────────────────────────────────────────────────────
430
- function toDisplayName(filename) {
431
- return filename
432
- .replace(/\.(md|mjs)$/, '')
433
- .replace(/-/g, ' ')
434
- .replace(/\b\w/g, c => c.toUpperCase());
435
- }
436
-
437
- function renderSkills(skills) {
438
- const container = document.getElementById('skills-content');
439
- if (!container) return;
440
-
441
- if (!skills.length) {
442
- container.innerHTML = '<div class="empty">No skills found.</div>';
443
- return;
444
- }
445
-
446
- container.innerHTML = skills.map((cat, i) => {
447
- const tags = (cat.files ?? []).map(f =>
448
- `<span class="skill-tag">${esc(toDisplayName(f))}</span>`
449
- ).join('');
450
-
451
- return `
452
- <div class="skill-category" id="skill-cat-${i}">
453
- <div class="skill-category-header" data-cat="${i}">
454
- <span class="skill-category-name">${esc(cat.category)}</span>
455
- <span style="display:flex;gap:12px;align-items:center">
456
- <span class="skill-category-count">${(cat.files ?? []).length} files</span>
457
- <span class="skill-toggle">▾</span>
458
- </span>
459
- </div>
460
- <div class="skill-files">${tags || '<span class="empty">No files</span>'}</div>
461
- </div>`;
462
- }).join('');
463
-
464
- container.querySelectorAll('.skill-category-header').forEach(header => {
465
- header.addEventListener('click', () => {
466
- const cat = header.closest('.skill-category');
467
- cat.classList.toggle('open');
468
- });
469
- });
470
- }
471
-
472
- // ── CLI Commands ──────────────────────────────────────────────────────
473
- const CATEGORY_ORDER = [
474
- 'Services',
475
- 'Agents & Sync',
476
- 'Work',
477
- 'Models & Integrations',
478
- 'Observability',
479
- 'Diagnostics',
480
- ];
481
-
482
- function renderCLICommands(cliCommands) {
483
- const container = document.getElementById('cli-commands-content');
484
- if (!container) return;
485
-
486
- if (!cliCommands.length) {
487
- container.innerHTML = '<div class="empty">No CLI commands found.</div>';
488
- return;
489
- }
490
-
491
- // Group by category
492
- const byCategory = {};
493
- for (const cmd of cliCommands) {
494
- const cat = cmd.category ?? 'Other';
495
- if (!byCategory[cat]) byCategory[cat] = [];
496
- byCategory[cat].push(cmd);
497
- }
498
-
499
- const orderedCats = [
500
- ...CATEGORY_ORDER.filter(c => byCategory[c]),
501
- ...Object.keys(byCategory).filter(c => !CATEGORY_ORDER.includes(c)),
502
- ];
503
-
504
- container.innerHTML = orderedCats.map(cat => {
505
- const rows = byCategory[cat].map(c => `
506
- <div class="cli-cmd-row" title="${esc(c.usage ?? c.name)}">
507
- <span class="cli-cmd-emoji">${esc(c.emoji ?? '')}</span>
508
- <span class="cli-cmd-name">construct ${esc(c.name)}</span>
509
- <span class="cli-cmd-desc">${esc(c.description)}</span>
510
- </div>`).join('');
511
- return `
512
- <div class="cli-cmd-group">
513
- <div class="cli-cmd-group-title">${esc(cat)}</div>
514
- <div class="cli-commands-list">${rows}</div>
515
- </div>`;
516
- }).join('');
517
- }
518
-
519
- // ── Commands (slash) ──────────────────────────────────────────────────
520
- function renderCommands(commands) {
521
- const container = document.getElementById('commands-content');
522
- if (!container) return;
523
-
524
- if (!commands.length) {
525
- container.innerHTML = '<div class="empty">No commands found.</div>';
526
- return;
527
- }
528
-
529
- container.innerHTML = `<div class="commands-grid">` + commands.map(d => {
530
- const items = (d.commands ?? []).map(c => `
531
- <div class="command-item">
532
- <span class="command-slash">${esc(c.slash)}</span>
533
- <span class="command-desc">${esc(c.description)}</span>
534
- </div>`).join('');
535
-
536
- return `
537
- <div class="command-domain">
538
- <div class="command-domain-header">${esc(d.domain)}</div>
539
- <div class="command-list">${items}</div>
540
- </div>`;
541
- }).join('') + `</div>`;
542
- }
543
-
544
- // ── Hooks ─────────────────────────────────────────────────────────────
545
- function renderHooks(hooks) {
546
- const container = document.getElementById('hooks-content');
547
- if (!container) return;
548
-
549
- if (!hooks.length) {
550
- container.innerHTML = '<div class="empty">No hooks configured.</div>';
551
- return;
552
- }
553
-
554
- const byPhase = {};
555
- for (const h of hooks) {
556
- const p = h.phase ?? 'Unknown';
557
- if (!byPhase[p]) byPhase[p] = [];
558
- byPhase[p].push(h);
559
- }
560
-
561
- const phaseOrder = ['SessionStart', 'PreToolUse', 'PostToolUse', 'Stop'];
562
- const phases = [
563
- ...phaseOrder.filter(p => byPhase[p]),
564
- ...Object.keys(byPhase).filter(p => !phaseOrder.includes(p)),
565
- ];
566
-
567
- container.innerHTML = phases.map(phase => {
568
- const rows = byPhase[phase].map(h => {
569
- const bulletClass = h.blocking ? 'hook-bullet blocking' : 'hook-bullet';
570
- const asyncLabel = h.blocking ? '' : '<span class="hook-phase-badge">async</span>';
571
- return `
572
- <div class="hook-row">
573
- <div class="${bulletClass}"></div>
574
- <div class="hook-desc">${esc(h.description)}</div>
575
- ${asyncLabel}
576
- </div>`;
577
- }).join('');
578
-
579
- return `
580
- <div class="hook-phase">
581
- <div class="hook-phase-title">${esc(phase)}</div>
582
- ${rows}
583
- </div>`;
584
- }).join('');
585
- }
586
-
587
- // ── Tab switching ─────────────────────────────────────────────────────
588
- function initTabs() {
589
- document.querySelectorAll('.tabs').forEach(tabGroup => {
590
- tabGroup.querySelectorAll('.tab-btn').forEach(btn => {
591
- btn.addEventListener('click', () => {
592
- const tabId = btn.dataset.tab;
593
- tabGroup.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
594
- btn.classList.add('active');
595
- const section = tabGroup.closest('.section') ?? tabGroup.parentElement;
596
- section.querySelectorAll('.tab-panel').forEach(p => p.classList.remove('active'));
597
- const panel = document.getElementById(`commands-tab-${tabId}`);
598
- if (panel) panel.classList.add('active');
599
- });
600
- });
601
- });
602
- }
603
-
604
- // ── MCP Servers ───────────────────────────────────────────────────────
605
- let registryData = null;
606
-
607
- async function fetchRegistry() {
608
- const res = await fetch('/api/registry');
609
- if (!res.ok) throw new Error('HTTP ' + res.status);
610
- registryData = await res.json();
611
- renderMcp(registryData.mcpServers ?? {});
612
- renderModels(registryData.models ?? {});
613
- }
614
-
615
- function mcpServerForm(id, server, isNew) {
616
- const isUrl = server.type === 'url';
617
- return `
618
- <div class="field-row">
619
- <label class="field-label">ID</label>
620
- <input class="field-input" data-field="id" value="${esc(id)}" placeholder="e.g. my-server" ${isNew ? '' : 'readonly'}>
621
- </div>
622
- <div class="field-row">
623
- <label class="field-label">Type</label>
624
- <select class="field-input" data-field="type">
625
- <option value="stdio" ${!isUrl ? 'selected' : ''}>stdio (command)</option>
626
- <option value="url" ${isUrl ? 'selected' : ''}>url (HTTP/SSE)</option>
627
- </select>
628
- </div>
629
- <div class="field-row" data-show="stdio" style="${isUrl ? 'display:none' : ''}">
630
- <label class="field-label">Command</label>
631
- <input class="field-input" data-field="command" value="${esc(server.command ?? '')}" placeholder="e.g. npx">
632
- </div>
633
- <div class="field-row" data-show="stdio" style="${isUrl ? 'display:none' : ''}">
634
- <label class="field-label">Args (one per line)</label>
635
- <textarea class="field-input" data-field="args" rows="3" placeholder="-y&#10;@some/mcp-server">${esc((server.args ?? []).join('\n'))}</textarea>
636
- </div>
637
- <div class="field-row" data-show="url" style="${isUrl ? '' : 'display:none'}">
638
- <label class="field-label">URL</label>
639
- <input class="field-input" data-field="url" value="${esc(server.url ?? '')}" placeholder="https://...">
640
- </div>
641
- <div class="field-row">
642
- <label class="field-label">Description</label>
643
- <input class="field-input" data-field="description" value="${esc(server.description ?? '')}" placeholder="What this server does">
644
- </div>`;
645
- }
646
-
647
- function wireFormTypeToggle(form) {
648
- const typeSelect = form.querySelector('[data-field="type"]');
649
- if (!typeSelect) return;
650
- typeSelect.addEventListener('change', () => {
651
- const isUrl = typeSelect.value === 'url';
652
- form.querySelectorAll('[data-show="stdio"]').forEach(el => el.style.display = isUrl ? 'none' : '');
653
- form.querySelectorAll('[data-show="url"]').forEach(el => el.style.display = isUrl ? '' : 'none');
654
- });
655
- }
656
-
657
- function readFormData(form) {
658
- const get = field => form.querySelector(`[data-field="${field}"]`)?.value?.trim() ?? '';
659
- const type = get('type');
660
- const server = { description: get('description') };
661
- if (type === 'url') {
662
- server.type = 'url';
663
- server.url = get('url');
664
- } else {
665
- server.command = get('command');
666
- server.args = get('args').split('\n').map(s => s.trim()).filter(Boolean);
667
- }
668
- return { id: get('id'), server };
669
- }
670
-
671
- function showMsg(container, text, isOk) {
672
- let msg = container.querySelector('.status-msg');
673
- if (!msg) { msg = document.createElement('div'); container.appendChild(msg); }
674
- msg.className = 'status-msg ' + (isOk ? 'ok' : 'err');
675
- msg.textContent = text;
676
- if (isOk) setTimeout(() => msg.remove(), 2500);
677
- }
678
-
679
- function renderMcp(mcpServers) {
680
- const container = document.getElementById('mcp-content');
681
- if (!container) return;
682
-
683
- const entries = Object.entries(mcpServers).sort(([a], [b]) => a.localeCompare(b));
684
- container.innerHTML = '';
685
-
686
- entries.forEach(([id, server]) => {
687
- const row = document.createElement('div');
688
- row.className = 'edit-row';
689
- row.innerHTML = `
690
- <div class="edit-row-header">
691
- <span class="edit-row-name">${esc(id)}</span>
692
- <span class="edit-row-desc">${esc(server.description ?? server.url ?? server.command ?? '')}</span>
693
- <span class="badge badge-${server.type === 'url' ? 'standard' : 'fast'}">${server.type === 'url' ? 'url' : 'stdio'}</span>
694
- </div>
695
- <div class="edit-form">
696
- ${mcpServerForm(id, server, false)}
697
- <div class="form-actions">
698
- <button class="btn btn-primary js-save">Save</button>
699
- <button class="btn btn-danger js-delete">Delete</button>
700
- </div>
701
- </div>`;
702
-
703
- row.querySelector('.edit-row-header').addEventListener('click', () => row.classList.toggle('expanded'));
704
- wireFormTypeToggle(row.querySelector('.edit-form'));
705
-
706
- row.querySelector('.js-save').addEventListener('click', async (e) => {
707
- const btn = e.currentTarget;
708
- btn.disabled = true;
709
- try {
710
- const { id: newId, server: newServer } = readFormData(row.querySelector('.edit-form'));
711
- await postJson('/api/registry/mcp', { action: 'save', id: newId, server: newServer });
712
- showMsg(row, 'Saved', true);
713
- await fetchRegistry();
714
- } catch (err) {
715
- showMsg(row, err.message, false);
716
- btn.disabled = false;
717
- }
718
- });
719
-
720
- row.querySelector('.js-delete').addEventListener('click', async (e) => {
721
- if (!confirm(`Delete MCP server "${id}"?`)) return;
722
- const btn = e.currentTarget;
723
- btn.disabled = true;
724
- try {
725
- await postJson('/api/registry/mcp', { action: 'delete', id });
726
- await fetchRegistry();
727
- } catch (err) {
728
- showMsg(row, err.message, false);
729
- btn.disabled = false;
730
- }
731
- });
732
-
733
- container.appendChild(row);
734
- });
735
-
736
- // Add new server form
737
- const addSection = document.createElement('div');
738
- addSection.className = 'add-server-row';
739
- const addRow = document.createElement('div');
740
- addRow.className = 'edit-row';
741
- addRow.innerHTML = `
742
- <div class="edit-row-header">
743
- <span class="edit-row-name" style="color:var(--accent-dim)">+ Add MCP Server</span>
744
- </div>
745
- <div class="edit-form">
746
- ${mcpServerForm('', { args: [] }, true)}
747
- <div class="form-actions">
748
- <button class="btn btn-primary js-add">Add Server</button>
749
- </div>
750
- </div>`;
751
-
752
- addRow.querySelector('.edit-row-header').addEventListener('click', () => addRow.classList.toggle('expanded'));
753
- wireFormTypeToggle(addRow.querySelector('.edit-form'));
754
-
755
- addRow.querySelector('.js-add').addEventListener('click', async (e) => {
756
- const btn = e.currentTarget;
757
- btn.disabled = true;
758
- try {
759
- const { id, server } = readFormData(addRow.querySelector('.edit-form'));
760
- if (!id) throw new Error('ID is required');
761
- await postJson('/api/registry/mcp', { action: 'save', id, server });
762
- await fetchRegistry();
763
- } catch (err) {
764
- showMsg(addRow, err.message, false);
765
- btn.disabled = false;
766
- }
767
- });
768
-
769
- addSection.appendChild(addRow);
770
- container.appendChild(addSection);
771
- }
772
-
773
- // ── Model Tiers ───────────────────────────────────────────────────────
774
- function renderModels(models) {
775
- const container = document.getElementById('models-content');
776
- if (!container) return;
777
-
778
- const tiers = ['reasoning', 'standard', 'fast'];
779
- container.innerHTML = tiers.map(tier => {
780
- const cfg = models[tier] ?? {};
781
- const primary = cfg.primary ?? '';
782
- const fallback = (cfg.fallback ?? []).join('\n');
783
- return `
784
- <div class="model-tier-card" id="model-tier-${tier}">
785
- <div class="model-tier-title">
786
- ${tier}
787
- ${tierBadge(tier)}
788
- </div>
789
- <div class="edit-form" style="display:flex">
790
- <div class="field-row">
791
- <label class="field-label">Primary Model</label>
792
- <input class="field-input" data-field="primary" value="${esc(primary)}" placeholder="provider/model-id">
793
- </div>
794
- <div class="field-row">
795
- <label class="field-label">Fallback Models (one per line)</label>
796
- <textarea class="field-input" data-field="fallback" rows="3" placeholder="provider/model-a&#10;provider/model-b">${esc(fallback)}</textarea>
797
- </div>
798
- <div class="form-actions">
799
- <button class="btn btn-primary js-save-tier" data-tier="${tier}">Save</button>
800
- </div>
801
- </div>
802
- </div>`;
803
- }).join('');
804
-
805
- container.querySelectorAll('.js-save-tier').forEach(btn => {
806
- btn.addEventListener('click', async (e) => {
807
- const tier = e.currentTarget.dataset.tier;
808
- const card = document.getElementById('model-tier-' + tier);
809
- e.currentTarget.disabled = true;
810
- try {
811
- const primary = card.querySelector('[data-field="primary"]').value.trim();
812
- const fallback = card.querySelector('[data-field="fallback"]').value
813
- .split('\n').map(s => s.trim()).filter(Boolean);
814
- await postJson('/api/registry/models', { tier, primary, fallback });
815
- showMsg(card, 'Saved', true);
816
- await fetchRegistry();
817
- } catch (err) {
818
- showMsg(card, err.message, false);
819
- e.currentTarget.disabled = false;
820
- }
821
- });
822
- });
823
- }
824
-
825
- // ── Bootstrap ────────────────────────────────────────────────────────
826
- function init() {
827
- initNav();
828
- initTabs();
829
- refresh();
830
- refreshRegistry();
831
- connectSSE();
832
- setInterval(refresh, 10000);
833
- setInterval(refreshRegistry, 30000);
834
- }
835
-
836
- if (document.readyState === 'loading') {
837
- document.addEventListener('DOMContentLoaded', init);
838
- } else {
839
- init();
840
- }
841
- })();