@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,447 @@
1
+ /**
2
+ * lib/project-profile.mjs — agnostic tech-stack detection for a project directory.
3
+ *
4
+ * Deliberately host-agnostic: the detection layer uses only filesystem signals
5
+ * (package.json, pyproject.toml, go.mod, etc.) and knows nothing about Claude
6
+ * Code, OpenCode, Codex, or Copilot. Host-specific filter generation happens
7
+ * in a later layer that consumes the profile produced here.
8
+ *
9
+ * The profile is written to `<cwd>/.cx/project-profile.json` and consumed by:
10
+ * - `construct skills scope` — reports relevant vs irrelevant skills
11
+ * - `construct setup` — runs detection on bootstrap
12
+ * - future per-host filter generators (.claude/settings.json filter,
13
+ * .opencode/construct-skills.json sidecars, etc.)
14
+ */
15
+ import { existsSync, readFileSync, readdirSync, statSync, writeFileSync, mkdirSync } from 'node:fs';
16
+ import { join, resolve } from 'node:path';
17
+
18
+ // Detection rules. Each rule checks a filesystem signal and emits one or more
19
+ // tags. Keep rules tight — false positives cascade into bad filter decisions.
20
+ const RULES = [
21
+ // --- Languages by manifest ---
22
+ { file: 'package.json', tags: ['javascript', 'node'], inspect: (json) => {
23
+ const tags = [];
24
+ const deps = { ...(json.dependencies || {}), ...(json.devDependencies || {}) };
25
+ if (deps.typescript || deps['@types/node']) tags.push('typescript');
26
+ if (deps.react || deps['react-dom']) tags.push('react', 'frontend');
27
+ if (deps.next) tags.push('nextjs', 'frontend');
28
+ if (deps.vue) tags.push('vue', 'frontend');
29
+ if (deps.svelte) tags.push('svelte', 'frontend');
30
+ if (deps['@angular/core']) tags.push('angular', 'frontend');
31
+ if (deps.express || deps.fastify || deps.hono || deps.koa) tags.push('backend');
32
+ if (deps['@nestjs/core']) tags.push('nestjs', 'backend');
33
+ if (deps.jest || deps.vitest || deps.mocha || deps['@playwright/test']) tags.push('testing');
34
+ if (deps['@playwright/test'] || deps.playwright) tags.push('e2e');
35
+ if (deps.remotion) tags.push('remotion');
36
+ return tags;
37
+ }},
38
+ { file: 'tsconfig.json', tags: ['typescript'] },
39
+ { file: 'pyproject.toml', tags: ['python'], inspect: (_, text) => {
40
+ const tags = [];
41
+ if (/django/i.test(text)) tags.push('django', 'backend');
42
+ if (/fastapi/i.test(text)) tags.push('fastapi', 'backend');
43
+ if (/flask/i.test(text)) tags.push('flask', 'backend');
44
+ if (/pytest/i.test(text)) tags.push('testing');
45
+ if (/pandas|numpy|polars/i.test(text)) tags.push('data');
46
+ if (/torch|tensorflow|scikit-learn|transformers/i.test(text)) tags.push('ml');
47
+ return tags;
48
+ }},
49
+ { file: 'requirements.txt', tags: ['python'], inspect: (_, text) => {
50
+ const tags = [];
51
+ if (/django/i.test(text)) tags.push('django', 'backend');
52
+ if (/fastapi/i.test(text)) tags.push('fastapi', 'backend');
53
+ if (/flask/i.test(text)) tags.push('flask', 'backend');
54
+ if (/pytest/i.test(text)) tags.push('testing');
55
+ return tags;
56
+ }},
57
+ { file: 'go.mod', tags: ['golang', 'backend'], inspect: (_, text) => {
58
+ const tags = [];
59
+ if (/gin-gonic\/gin|labstack\/echo|gofiber\/fiber/.test(text)) tags.push('go-web');
60
+ return tags;
61
+ }},
62
+ { file: 'Cargo.toml', tags: ['rust'], inspect: (_, text) => {
63
+ const tags = [];
64
+ if (/actix-web|axum|rocket|tokio/i.test(text)) tags.push('backend');
65
+ if (/solana|ethers|alloy|web3/i.test(text)) tags.push('web3');
66
+ return tags;
67
+ }},
68
+ { file: 'pom.xml', tags: ['java', 'maven'], inspect: (_, text) => {
69
+ const tags = [];
70
+ if (/spring-boot/.test(text)) tags.push('spring-boot', 'backend');
71
+ return tags;
72
+ }},
73
+ { file: 'build.gradle', tags: ['jvm'], inspect: (_, text) => {
74
+ const tags = [];
75
+ if (/kotlin/i.test(text)) tags.push('kotlin');
76
+ if (/spring-boot/i.test(text)) tags.push('spring-boot', 'backend');
77
+ if (/android/i.test(text)) tags.push('android', 'mobile');
78
+ if (/ktor/i.test(text)) tags.push('ktor', 'backend');
79
+ return tags;
80
+ }},
81
+ { file: 'build.gradle.kts', tags: ['jvm', 'kotlin'] },
82
+ { file: 'Gemfile', tags: ['ruby'], inspect: (_, text) => {
83
+ const tags = [];
84
+ if (/rails/.test(text)) tags.push('rails', 'backend');
85
+ return tags;
86
+ }},
87
+ { file: 'composer.json', tags: ['php'], inspect: (json) => {
88
+ const tags = [];
89
+ const deps = { ...(json.require || {}), ...(json['require-dev'] || {}) };
90
+ if (deps['laravel/framework']) tags.push('laravel', 'backend');
91
+ if (deps['symfony/framework-bundle']) tags.push('symfony', 'backend');
92
+ if (deps['phpunit/phpunit']) tags.push('testing');
93
+ return tags;
94
+ }},
95
+ { file: 'Package.swift', tags: ['swift'] },
96
+ { file: 'Gemfile.lock', tags: ['ruby'] },
97
+ { file: 'CMakeLists.txt', tags: ['cpp'] },
98
+ { file: 'Makefile', tags: [], inspect: (_, text) => {
99
+ const tags = [];
100
+ if (/\.cpp|\.cc|\.hpp|g\+\+|clang\+\+/.test(text)) tags.push('cpp');
101
+ if (/\.c\b|gcc\b/.test(text)) tags.push('c');
102
+ return tags;
103
+ }},
104
+ { file: 'cpanfile', tags: ['perl'] },
105
+ { file: 'Dockerfile', tags: ['docker', 'deployment'] },
106
+ { file: 'docker-compose.yml', tags: ['docker', 'deployment'] },
107
+ { file: 'docker-compose.yaml', tags: ['docker', 'deployment'] },
108
+ // --- Mobile ---
109
+ { file: 'ios/Podfile', tags: ['ios', 'mobile'] },
110
+ { file: 'android/build.gradle', tags: ['android', 'mobile'] },
111
+ { file: 'pubspec.yaml', tags: ['dart', 'flutter', 'mobile'] },
112
+ // --- Infrastructure ---
113
+ { file: 'terraform.tf', tags: ['terraform', 'infrastructure'] },
114
+ { file: 'main.tf', tags: ['terraform', 'infrastructure'] },
115
+ { file: 'Pulumi.yaml', tags: ['pulumi', 'infrastructure'] },
116
+ { file: 'kustomization.yaml', tags: ['kubernetes', 'deployment'] },
117
+ // --- Data / DB ---
118
+ { file: 'prisma/schema.prisma', tags: ['prisma', 'database'] },
119
+ { file: 'alembic.ini', tags: ['alembic', 'database'] },
120
+ { file: 'dbt_project.yml', tags: ['dbt', 'data'] },
121
+ // --- CI ---
122
+ { file: '.github/workflows', tags: ['github-actions', 'ci'] },
123
+ { file: '.gitlab-ci.yml', tags: ['gitlab-ci', 'ci'] },
124
+ { file: '.circleci/config.yml', tags: ['circleci', 'ci'] },
125
+ ];
126
+
127
+ // Maps skill paths (like "devops/data-engineering" or "django-patterns")
128
+ // to the project tags that justify loading them. A skill is deemed "relevant"
129
+ // if the project profile contains ANY of its required tags.
130
+ //
131
+ // Unlisted skills default to "always relevant" — omission must not hide a skill.
132
+ export const SKILL_RELEVANCE = {
133
+ // Language-specific patterns
134
+ 'python-patterns': ['python'],
135
+ 'python-testing': ['python'],
136
+ 'django-patterns': ['django'],
137
+ 'django-security': ['django'],
138
+ 'django-verification': ['django'],
139
+ 'django-tdd': ['django'],
140
+ 'golang-patterns': ['golang'],
141
+ 'golang-testing': ['golang'],
142
+ 'rust-patterns': ['rust'],
143
+ 'rust-testing': ['rust'],
144
+ 'cpp-testing': ['cpp', 'c'],
145
+ 'cpp-coding-standards': ['cpp', 'c'],
146
+ 'kotlin-patterns': ['kotlin'],
147
+ 'kotlin-testing': ['kotlin'],
148
+ 'kotlin-coroutines-flows': ['kotlin'],
149
+ 'kotlin-exposed-patterns': ['kotlin'],
150
+ 'kotlin-ktor-patterns': ['ktor'],
151
+ 'compose-multiplatform-patterns': ['kotlin', 'android'],
152
+ 'android-clean-architecture': ['android'],
153
+ 'dart-flutter-patterns': ['flutter', 'dart'],
154
+ 'swiftui-patterns': ['swift', 'ios'],
155
+ 'swift-concurrency-6-2': ['swift'],
156
+ 'swift-actor-persistence': ['swift'],
157
+ 'swift-protocol-di-testing': ['swift'],
158
+ 'foundation-models-on-device': ['ios', 'swift'],
159
+ 'liquid-glass-design': ['ios', 'swift'],
160
+ 'springboot-patterns': ['spring-boot'],
161
+ 'springboot-security': ['spring-boot'],
162
+ 'springboot-tdd': ['spring-boot'],
163
+ 'springboot-verification': ['spring-boot'],
164
+ 'jpa-patterns': ['spring-boot', 'java'],
165
+ 'java-coding-standards': ['java', 'jvm'],
166
+ 'laravel-patterns': ['laravel'],
167
+ 'laravel-security': ['laravel'],
168
+ 'laravel-tdd': ['laravel'],
169
+ 'laravel-verification': ['laravel'],
170
+ 'laravel-plugin-discovery': ['laravel'],
171
+ 'perl-patterns': ['perl'],
172
+ 'perl-testing': ['perl'],
173
+ 'perl-security': ['perl'],
174
+ 'dotnet-patterns': ['dotnet'],
175
+ 'csharp-testing': ['dotnet'],
176
+ // Web / frontend
177
+ 'frontend-patterns': ['frontend'],
178
+ 'frontend-design': ['frontend'],
179
+ 'frontend-slides': ['frontend'],
180
+ 'remotion-video-creation': ['remotion'],
181
+ 'nestjs-patterns': ['nestjs'],
182
+ 'e2e-testing': ['e2e', 'frontend'],
183
+ // Data / infra
184
+ 'postgres-patterns': ['database', 'backend'],
185
+ 'clickhouse-io': ['data'],
186
+ 'database-migrations': ['database'],
187
+ 'docker-patterns': ['docker'],
188
+ 'deployment-patterns': ['deployment', 'docker'],
189
+ // Web3
190
+ 'defi-amm-security': ['web3'],
191
+ 'llm-trading-agent-security': ['web3'],
192
+ 'evm-token-decimals': ['web3'],
193
+ 'nodejs-keccak256': ['web3', 'node'],
194
+ // Video / media (rare — keep targeted)
195
+ 'manim-video': ['python'],
196
+ 'video-editing': [],
197
+ 'fal-ai-media': [],
198
+ };
199
+
200
+ /**
201
+ * Skills that must NEVER be filtered out of a project, regardless of the
202
+ * detected stack. Protects the "R&D org in a box" framing: any skill that
203
+ * could be called by a cx-* specialist, or that provides cross-cutting
204
+ * capability (research, docs, planning, verification), stays available.
205
+ *
206
+ * Tests match the skill name prefix — "anthropic-skills:" matches
207
+ * "anthropic-skills:pdf", "anthropic-skills:docx", etc.
208
+ */
209
+ export const NEVER_FILTER_PREFIXES = [
210
+ // Construct-native — orchestration, skills, personas
211
+ 'construct',
212
+ 'cx-',
213
+ 'construct-mcp',
214
+ // Generic / cross-cutting capabilities every agent may need
215
+ 'brand-voice',
216
+ 'coding-standards',
217
+ 'comments',
218
+ 'council',
219
+ 'continuous-learning',
220
+ 'search-first',
221
+ 'verification-loop',
222
+ 'agentic-engineering',
223
+ 'ai-first-engineering',
224
+ 'iterative-retrieval',
225
+ 'tdd-workflow',
226
+ 'quality-gate',
227
+ 'blueprint',
228
+ 'prompt-optimizer',
229
+ 'security-review',
230
+ 'security-scan',
231
+ 'ai-regression-testing',
232
+ 'strategic-compact',
233
+ 'context-hash-cache-pattern',
234
+ 'regex-vs-llm-structured-text',
235
+ 'santa-loop',
236
+ 'knowledge-ops',
237
+ 'research-ops',
238
+ 'deep-research',
239
+ 'exa-search',
240
+ 'learn',
241
+ 'learn-eval',
242
+ 'team-builder',
243
+ 'terminal-ops',
244
+ 'token-budget-advisor',
245
+ 'workspace-surface-audit',
246
+ 'skill-stocktake',
247
+ 'skill-health',
248
+ 'configure-ecc',
249
+ 'agent-sort',
250
+ // MCP / platform patterns (often needed in any project)
251
+ 'mcp-server-patterns',
252
+ 'api-connector-builder',
253
+ // Document processing (Anthropic native skills) — called on demand
254
+ 'anthropic-skills:',
255
+ // Plugin infrastructure
256
+ 'cowork-plugin-management:',
257
+ // Productivity + engineering + PM + ops + legal namespace skills — the
258
+ // "org in a box" roles. These are role-domain skills that specialists
259
+ // (or users) may invoke at any time.
260
+ 'productivity:',
261
+ 'engineering:',
262
+ 'product-management:',
263
+ 'operations:',
264
+ 'legal:',
265
+ 'data:',
266
+ // Claude Code + Claude API development — likely needed in any ECC project
267
+ 'claude-api',
268
+ 'claude-devfleet',
269
+ 'claude-in-chrome',
270
+ 'hookify',
271
+ 'autonomous-loops',
272
+ 'continuous-agent-loop',
273
+ 'dmux-workflows',
274
+ 'agent-harness-construction',
275
+ 'agent-introspection-debugging',
276
+ 'enterprise-agent-ops',
277
+ // Core Construct/ECC native routing / setup
278
+ 'init',
279
+ 'review',
280
+ 'resume-session',
281
+ 'save-session',
282
+ ];
283
+
284
+ /**
285
+ * Returns true if a skill name matches any NEVER_FILTER prefix.
286
+ */
287
+ export function isProtectedSkill(name) {
288
+ return NEVER_FILTER_PREFIXES.some((prefix) => name === prefix || name.startsWith(prefix));
289
+ }
290
+
291
+ function readJsonSafe(path) {
292
+ try { return JSON.parse(readFileSync(path, 'utf8')); } catch { return null; }
293
+ }
294
+
295
+ function readTextSafe(path) {
296
+ try { return readFileSync(path, 'utf8'); } catch { return ''; }
297
+ }
298
+
299
+ function pathExists(cwd, rel) {
300
+ const p = join(cwd, rel);
301
+ try { return existsSync(p); } catch { return false; }
302
+ }
303
+
304
+ /**
305
+ * Returns true if a Docker- or deployment-related file exists anywhere in
306
+ * the first two levels of the project tree (root or one level deep). Needed
307
+ * because monorepos often put compose/Dockerfile in subpackage directories
308
+ * (services/docker-compose.yml, etc.).
309
+ */
310
+ function hasDockerSignalAtDepth2(cwd) {
311
+ const dockerNames = ['Dockerfile', 'docker-compose.yml', 'docker-compose.yaml'];
312
+ try {
313
+ for (const name of dockerNames) {
314
+ if (existsSync(join(cwd, name))) return true;
315
+ }
316
+ const entries = readdirSync(cwd, { withFileTypes: true });
317
+ for (const entry of entries) {
318
+ if (!entry.isDirectory()) continue;
319
+ if (entry.name.startsWith('.') || entry.name === 'node_modules') continue;
320
+ const sub = join(cwd, entry.name);
321
+ for (const name of dockerNames) {
322
+ if (existsSync(join(sub, name))) return true;
323
+ }
324
+ }
325
+ } catch { /* best effort */ }
326
+ return false;
327
+ }
328
+
329
+ /**
330
+ * Walks the detection rules against the given directory and returns a
331
+ * { tags, signals } object. Tags are deduplicated and sorted.
332
+ */
333
+ export function detectProjectProfile(cwd = process.cwd()) {
334
+ const root = resolve(cwd);
335
+ const tags = new Set();
336
+ const signals = [];
337
+
338
+ for (const rule of RULES) {
339
+ const relPath = rule.file;
340
+ if (!pathExists(root, relPath)) continue;
341
+ signals.push(relPath);
342
+ for (const tag of rule.tags || []) tags.add(tag);
343
+
344
+ if (typeof rule.inspect === 'function') {
345
+ const fullPath = join(root, relPath);
346
+ try {
347
+ const isDir = statSync(fullPath).isDirectory();
348
+ if (isDir) continue;
349
+ const text = readTextSafe(fullPath);
350
+ const json = relPath.endsWith('.json') ? readJsonSafe(fullPath) : null;
351
+ const extras = rule.inspect(json, text) || [];
352
+ for (const tag of extras) tags.add(tag);
353
+ } catch { /* best effort */ }
354
+ }
355
+ }
356
+
357
+ // Subdirectory scan for docker signals (monorepo support)
358
+ if (!tags.has('docker') && hasDockerSignalAtDepth2(root)) {
359
+ tags.add('docker');
360
+ tags.add('deployment');
361
+ signals.push('(subdir docker)');
362
+ }
363
+
364
+ return {
365
+ root,
366
+ detectedAt: new Date().toISOString(),
367
+ tags: Array.from(tags).sort(),
368
+ signals: signals.sort(),
369
+ };
370
+ }
371
+
372
+ /**
373
+ * Given a profile and a list of installed skill names, returns which are
374
+ * relevant to the project and which are not.
375
+ *
376
+ * Skills not in SKILL_RELEVANCE are treated as always-relevant (the default
377
+ * is safe — we don't hide a skill we don't know about).
378
+ */
379
+ export function classifySkillRelevance(profile, installedSkills) {
380
+ const tagSet = new Set(profile.tags || []);
381
+ const relevant = [];
382
+ const irrelevant = [];
383
+ const unknown = [];
384
+ const protectedSkills = [];
385
+
386
+ for (const name of installedSkills) {
387
+ if (isProtectedSkill(name)) {
388
+ protectedSkills.push(name);
389
+ relevant.push(name);
390
+ continue;
391
+ }
392
+ const required = SKILL_RELEVANCE[name];
393
+ if (!required) {
394
+ unknown.push(name);
395
+ continue;
396
+ }
397
+ if (required.length === 0) {
398
+ relevant.push(name);
399
+ continue;
400
+ }
401
+ const matches = required.some((tag) => tagSet.has(tag));
402
+ (matches ? relevant : irrelevant).push(name);
403
+ }
404
+
405
+ return { relevant, irrelevant, unknown, protected: protectedSkills };
406
+ }
407
+
408
+ /**
409
+ * Persists the profile to `<cwd>/.cx/project-profile.json`. Returns the path.
410
+ */
411
+ export function writeProfile(profile, cwd = profile.root || process.cwd()) {
412
+ const dir = join(cwd, '.cx');
413
+ mkdirSync(dir, { recursive: true });
414
+ const path = join(dir, 'project-profile.json');
415
+ writeFileSync(path, JSON.stringify(profile, null, 2) + '\n', 'utf8');
416
+ return path;
417
+ }
418
+
419
+ /**
420
+ * Best-effort enumeration of installed skills across hosts. Returns a
421
+ * unique sorted list of skill names. Fully agnostic — looks at all known
422
+ * skill directories and merges.
423
+ */
424
+ export function enumerateInstalledSkills(homeDir = (process.env.HOME || process.env.USERPROFILE || '')) {
425
+ const skills = new Set();
426
+ const candidates = [
427
+ // Claude Code / OpenCode shared global
428
+ join(homeDir, '.claude', 'skills'),
429
+ join(homeDir, '.claude', 'plugins'),
430
+ // Codex
431
+ join(homeDir, '.codex', 'skills'),
432
+ // Construct native
433
+ join(homeDir, '.construct', 'skills'),
434
+ ];
435
+ for (const dir of candidates) {
436
+ try {
437
+ if (!existsSync(dir)) continue;
438
+ const entries = readdirSync(dir, { withFileTypes: true });
439
+ for (const entry of entries) {
440
+ if (entry.isDirectory() || entry.name.endsWith('.md')) {
441
+ skills.add(entry.name.replace(/\.md$/, ''));
442
+ }
443
+ }
444
+ } catch { /* best effort */ }
445
+ }
446
+ return Array.from(skills).sort();
447
+ }