@wardby/cli 0.2.1 → 0.3.0

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 (407) hide show
  1. package/.env.example +34 -4
  2. package/README.md +41 -4
  3. package/dist/claude-coding-worker/driver.d.ts +6 -1
  4. package/dist/claude-coding-worker/driver.js +27 -1
  5. package/dist/claude-coding-worker/main.js +2 -0
  6. package/dist/claude-coding-worker/tool-socket.d.ts +9 -0
  7. package/dist/claude-coding-worker/tool-socket.js +26 -0
  8. package/dist/cli-help.d.ts +1 -1
  9. package/dist/cli-help.js +13 -4
  10. package/dist/cli.d.ts +1 -1
  11. package/dist/cli.js +219 -29
  12. package/dist/coding/collect-exclude.d.ts +25 -0
  13. package/dist/coding/collect-exclude.js +76 -0
  14. package/dist/coding/profile.d.ts +67 -22
  15. package/dist/coding/profile.js +58 -25
  16. package/dist/coding/protected-path-wording.d.ts +24 -0
  17. package/dist/coding/protected-path-wording.js +55 -0
  18. package/dist/coding/protected-paths.d.ts +31 -0
  19. package/dist/coding/protected-paths.js +48 -0
  20. package/dist/coding/protocol.d.ts +75 -4
  21. package/dist/coding/protocol.js +107 -7
  22. package/dist/coding/registry/adapters.d.ts +2 -0
  23. package/dist/coding/registry/adapters.js +9 -0
  24. package/dist/coding/registry/allowlist.d.ts +9 -0
  25. package/dist/coding/registry/allowlist.js +28 -0
  26. package/dist/coding/registry/json-scan.d.ts +55 -0
  27. package/dist/coding/registry/json-scan.js +191 -0
  28. package/dist/coding/registry/lockfiles.d.ts +6 -0
  29. package/dist/coding/registry/lockfiles.js +89 -0
  30. package/dist/coding/registry/npm-lockfile.d.ts +9 -0
  31. package/dist/coding/registry/npm-lockfile.js +73 -0
  32. package/dist/coding/registry/npm-plan.d.ts +29 -0
  33. package/dist/coding/registry/npm-plan.js +289 -0
  34. package/dist/coding/registry/npm.d.ts +7 -0
  35. package/dist/coding/registry/npm.js +252 -0
  36. package/dist/coding/registry/pypi.d.ts +4 -0
  37. package/dist/coding/registry/pypi.js +210 -0
  38. package/dist/coding/registry/report.d.ts +37 -0
  39. package/dist/coding/registry/report.js +31 -0
  40. package/dist/coding/registry/token.d.ts +4 -0
  41. package/dist/coding/registry/token.js +7 -0
  42. package/dist/coding/registry/types.d.ts +286 -0
  43. package/dist/coding/registry/types.js +19 -0
  44. package/dist/coding/registry/worker-config.d.ts +14 -0
  45. package/dist/coding/registry/worker-config.js +31 -0
  46. package/dist/coding/services/builtins.d.ts +22 -0
  47. package/dist/coding/services/builtins.js +85 -0
  48. package/dist/coding/services/catalog.d.ts +443 -0
  49. package/dist/coding/services/catalog.js +159 -0
  50. package/dist/coding/services/declaration.d.ts +11 -0
  51. package/dist/coding/services/declaration.js +114 -0
  52. package/dist/coding/services/note.d.ts +8 -0
  53. package/dist/coding/services/note.js +15 -0
  54. package/dist/coding/services/resolve.d.ts +25 -0
  55. package/dist/coding/services/resolve.js +50 -0
  56. package/dist/coding/services/wording.d.ts +25 -0
  57. package/dist/coding/services/wording.js +70 -0
  58. package/dist/coding-proxy/main.js +24 -4
  59. package/dist/coding-worker/artifact.d.ts +6 -0
  60. package/dist/coding-worker/debug-trace.d.ts +37 -0
  61. package/dist/coding-worker/debug-trace.js +117 -0
  62. package/dist/coding-worker/driver.d.ts +13 -1
  63. package/dist/coding-worker/driver.js +102 -13
  64. package/dist/coding-worker/errors.js +8 -0
  65. package/dist/coding-worker/main.js +10 -2
  66. package/dist/coding-worker/sdk.d.ts +2 -2
  67. package/dist/coding-worker/sdk.js +7 -2
  68. package/dist/coding-worker/types.d.ts +3 -0
  69. package/dist/config/providers.d.ts +41 -0
  70. package/dist/config/providers.js +66 -0
  71. package/dist/core/budget-groups.d.ts +108 -5
  72. package/dist/core/budget-groups.js +125 -19
  73. package/dist/core/budget-wording.d.ts +22 -0
  74. package/dist/core/budget-wording.js +55 -0
  75. package/dist/core/datastores.js +18 -2
  76. package/dist/core/db.d.ts +5 -1
  77. package/dist/core/db.js +8 -2
  78. package/dist/core/dispatch.d.ts +33 -6
  79. package/dist/core/dispatch.js +260 -43
  80. package/dist/core/engine-native.js +27 -10
  81. package/dist/core/grants.d.ts +86 -0
  82. package/dist/core/grants.js +126 -0
  83. package/dist/core/host-events.d.ts +49 -0
  84. package/dist/core/host-events.js +257 -0
  85. package/dist/core/host-identity-links.d.ts +54 -0
  86. package/dist/core/host-identity-links.js +189 -0
  87. package/dist/core/host-status.d.ts +61 -0
  88. package/dist/core/host-status.js +211 -0
  89. package/dist/core/in-flight-runs.d.ts +8 -0
  90. package/dist/core/in-flight-runs.js +56 -0
  91. package/dist/core/provider-wording.d.ts +12 -0
  92. package/dist/core/provider-wording.js +44 -0
  93. package/dist/core/reconciler.d.ts +38 -2
  94. package/dist/core/reconciler.js +86 -2
  95. package/dist/core/repo-access.d.ts +99 -0
  96. package/dist/core/repo-access.js +136 -0
  97. package/dist/core/review-host-checks.d.ts +11 -0
  98. package/dist/core/review-host-checks.js +41 -0
  99. package/dist/core/review-host-tools.d.ts +47 -0
  100. package/dist/core/review-host-tools.js +347 -0
  101. package/dist/core/run-heartbeat.d.ts +27 -0
  102. package/dist/core/run-heartbeat.js +54 -0
  103. package/dist/core/runner.d.ts +30 -8
  104. package/dist/core/runner.js +237 -40
  105. package/dist/core/secrets.d.ts +11 -2
  106. package/dist/core/secrets.js +25 -6
  107. package/dist/core/subagent-memory-tools.d.ts +1 -1
  108. package/dist/core/subagent-memory-tools.js +16 -3
  109. package/dist/core/tool-admin.d.ts +81 -0
  110. package/dist/core/tool-admin.js +129 -0
  111. package/dist/core/tool-names.d.ts +42 -0
  112. package/dist/core/tool-names.js +64 -0
  113. package/dist/core/untrusted-content.d.ts +32 -0
  114. package/dist/core/untrusted-content.js +72 -0
  115. package/dist/core/webhooks.d.ts +8 -1
  116. package/dist/core/webhooks.js +23 -2
  117. package/dist/generated/prisma/browser.d.ts +100 -0
  118. package/dist/generated/prisma/client.d.ts +100 -0
  119. package/dist/generated/prisma/commonInputTypes.d.ts +30 -0
  120. package/dist/generated/prisma/enums.d.ts +6 -0
  121. package/dist/generated/prisma/enums.js +6 -1
  122. package/dist/generated/prisma/internal/class.d.ts +143 -0
  123. package/dist/generated/prisma/internal/class.js +4 -4
  124. package/dist/generated/prisma/internal/prismaNamespace.d.ts +1746 -577
  125. package/dist/generated/prisma/internal/prismaNamespace.js +179 -6
  126. package/dist/generated/prisma/internal/prismaNamespaceBrowser.d.ts +186 -0
  127. package/dist/generated/prisma/internal/prismaNamespaceBrowser.js +179 -6
  128. package/dist/generated/prisma/models/Agent.d.ts +294 -1
  129. package/dist/generated/prisma/models/AgentRepository.d.ts +1425 -0
  130. package/dist/generated/prisma/models/AgentRepository.js +1 -0
  131. package/dist/generated/prisma/models/AgentTool.d.ts +95 -1
  132. package/dist/generated/prisma/models/AuthUser.d.ts +56 -1
  133. package/dist/generated/prisma/models/CodingAgentProfile.d.ts +258 -7
  134. package/dist/generated/prisma/models/CodingProxySession.d.ts +123 -2
  135. package/dist/generated/prisma/models/CodingRun.d.ts +1276 -95
  136. package/dist/generated/prisma/models/CodingService.d.ts +1348 -0
  137. package/dist/generated/prisma/models/CodingService.js +1 -0
  138. package/dist/generated/prisma/models/HostEventDelivery.d.ts +946 -0
  139. package/dist/generated/prisma/models/HostEventDelivery.js +1 -0
  140. package/dist/generated/prisma/models/HostIdentity.d.ts +1232 -0
  141. package/dist/generated/prisma/models/HostIdentity.js +1 -0
  142. package/dist/generated/prisma/models/HostIdentityLinkRequest.d.ts +1473 -0
  143. package/dist/generated/prisma/models/HostIdentityLinkRequest.js +1 -0
  144. package/dist/generated/prisma/models/Principal.d.ts +455 -0
  145. package/dist/generated/prisma/models/RegistryAllowance.d.ts +1148 -0
  146. package/dist/generated/prisma/models/RegistryAllowance.js +1 -0
  147. package/dist/generated/prisma/models/RegistryApprovedVersion.d.ts +1219 -0
  148. package/dist/generated/prisma/models/RegistryApprovedVersion.js +1 -0
  149. package/dist/generated/prisma/models/RegistryFetch.d.ts +1428 -0
  150. package/dist/generated/prisma/models/RegistryFetch.js +1 -0
  151. package/dist/generated/prisma/models/RegistryPlanRefusal.d.ts +1294 -0
  152. package/dist/generated/prisma/models/RegistryPlanRefusal.js +1 -0
  153. package/dist/generated/prisma/models/RegistryVersionFact.d.ts +1085 -0
  154. package/dist/generated/prisma/models/RegistryVersionFact.js +1 -0
  155. package/dist/generated/prisma/models/ResourceGrant.d.ts +1437 -0
  156. package/dist/generated/prisma/models/ResourceGrant.js +1 -0
  157. package/dist/generated/prisma/models/Run.d.ts +389 -1
  158. package/dist/generated/prisma/models/RunHostCheck.d.ts +1239 -0
  159. package/dist/generated/prisma/models/RunHostCheck.js +1 -0
  160. package/dist/generated/prisma/models/RunHostStatus.d.ts +1315 -0
  161. package/dist/generated/prisma/models/RunHostStatus.js +1 -0
  162. package/dist/generated/prisma/models/Tool.d.ts +15 -3
  163. package/dist/generated/prisma/models.d.ts +13 -0
  164. package/dist/help/build.d.ts +1 -0
  165. package/dist/help/build.js +9 -0
  166. package/dist/help/catalog.d.ts +24 -0
  167. package/dist/help/catalog.js +160 -0
  168. package/dist/help/cli.d.ts +2 -0
  169. package/dist/help/cli.js +65 -0
  170. package/dist/help/runtime.d.ts +3 -0
  171. package/dist/help/runtime.js +44 -0
  172. package/dist/help/search.d.ts +9 -0
  173. package/dist/help/search.js +104 -0
  174. package/dist/help-index.json +660 -0
  175. package/dist/import/cli-args.js +3 -2
  176. package/dist/import/create.d.ts +5 -0
  177. package/dist/import/create.js +67 -13
  178. package/dist/import/index.js +19 -9
  179. package/dist/import/neutral-schema.d.ts +11 -11
  180. package/dist/mcp/auth/access.d.ts +70 -0
  181. package/dist/mcp/auth/access.js +58 -0
  182. package/dist/mcp/auth/grants-cli.d.ts +149 -0
  183. package/dist/mcp/auth/grants-cli.js +518 -0
  184. package/dist/mcp/auth/host-account-cli.d.ts +2 -0
  185. package/dist/mcp/auth/host-account-cli.js +47 -0
  186. package/dist/mcp/auth/ownership.d.ts +26 -52
  187. package/dist/mcp/auth/ownership.js +19 -14
  188. package/dist/mcp/auth/repo-authorization.d.ts +22 -0
  189. package/dist/mcp/auth/repo-authorization.js +51 -0
  190. package/dist/mcp/auth/resource-server.d.ts +31 -2
  191. package/dist/mcp/auth/resource-server.js +84 -3
  192. package/dist/mcp/auth/self-hosted/browser.js +2 -2
  193. package/dist/mcp/auth/self-hosted/cli.js +47 -13
  194. package/dist/mcp/auth/self-hosted/credentials.d.ts +20 -2
  195. package/dist/mcp/auth/self-hosted/credentials.js +62 -4
  196. package/dist/mcp/auth/self-hosted/session.d.ts +2 -1
  197. package/dist/mcp/context.d.ts +27 -1
  198. package/dist/mcp/errors.d.ts +25 -6
  199. package/dist/mcp/errors.js +98 -0
  200. package/dist/mcp/host-events/github-ingress.d.ts +36 -0
  201. package/dist/mcp/host-events/github-ingress.js +92 -0
  202. package/dist/mcp/host-events/github-user-callback.d.ts +18 -0
  203. package/dist/mcp/host-events/github-user-callback.js +50 -0
  204. package/dist/mcp/index.d.ts +8 -1
  205. package/dist/mcp/index.js +106 -16
  206. package/dist/mcp/server.js +27 -9
  207. package/dist/mcp/tools/agents.js +446 -48
  208. package/dist/mcp/tools/budget-groups.js +3 -3
  209. package/dist/mcp/tools/datastore.js +22 -15
  210. package/dist/mcp/tools/grants.d.ts +2 -0
  211. package/dist/mcp/tools/grants.js +239 -0
  212. package/dist/mcp/tools/help.d.ts +5 -0
  213. package/dist/mcp/tools/help.js +67 -0
  214. package/dist/mcp/tools/host-accounts.d.ts +2 -0
  215. package/dist/mcp/tools/host-accounts.js +114 -0
  216. package/dist/mcp/tools/memory.d.ts +7 -1
  217. package/dist/mcp/tools/memory.js +5 -5
  218. package/dist/mcp/tools/repositories.d.ts +2 -0
  219. package/dist/mcp/tools/repositories.js +181 -0
  220. package/dist/mcp/tools/runs.d.ts +6 -0
  221. package/dist/mcp/tools/runs.js +60 -6
  222. package/dist/mcp/tools/scheduling.js +3 -6
  223. package/dist/mcp/tools/secrets.js +18 -6
  224. package/dist/mcp/tools/services.d.ts +2 -0
  225. package/dist/mcp/tools/services.js +222 -0
  226. package/dist/mcp/tools/subagents.js +52 -16
  227. package/dist/mcp/tools/tools.d.ts +41 -0
  228. package/dist/mcp/tools/tools.js +201 -40
  229. package/dist/mcp/tools/trigger.js +36 -6
  230. package/dist/mcp/tools/webhooks.js +15 -4
  231. package/dist/mcp/transport/streamable-http.d.ts +10 -0
  232. package/dist/mcp/transport/streamable-http.js +32 -2
  233. package/dist/providers/auth/delegating.d.ts +19 -0
  234. package/dist/providers/auth/delegating.js +71 -0
  235. package/dist/providers/auth/index.d.ts +2 -0
  236. package/dist/providers/auth/index.js +11 -0
  237. package/dist/providers/auth/self-hosted.d.ts +2 -1
  238. package/dist/providers/auth/self-hosted.js +16 -4
  239. package/dist/providers/auth/types.d.ts +6 -0
  240. package/dist/providers/coding-proxy/memory-ledger.d.ts +3 -0
  241. package/dist/providers/coding-proxy/memory-ledger.js +21 -2
  242. package/dist/providers/coding-proxy/metering.d.ts +4 -0
  243. package/dist/providers/coding-proxy/metering.js +28 -2
  244. package/dist/providers/coding-proxy/prisma-ledger.d.ts +17 -0
  245. package/dist/providers/coding-proxy/prisma-ledger.js +50 -5
  246. package/dist/providers/coding-proxy/proxy.d.ts +13 -0
  247. package/dist/providers/coding-proxy/proxy.js +479 -18
  248. package/dist/providers/coding-proxy/registry/audit.d.ts +131 -0
  249. package/dist/providers/coding-proxy/registry/audit.js +380 -0
  250. package/dist/providers/coding-proxy/registry/bounded-fetch.d.ts +16 -0
  251. package/dist/providers/coding-proxy/registry/bounded-fetch.js +77 -0
  252. package/dist/providers/coding-proxy/registry/plan.d.ts +58 -0
  253. package/dist/providers/coding-proxy/registry/plan.js +304 -0
  254. package/dist/providers/coding-proxy/registry/prisma-store.d.ts +34 -0
  255. package/dist/providers/coding-proxy/registry/prisma-store.js +151 -0
  256. package/dist/providers/coding-proxy/registry/service.d.ts +213 -0
  257. package/dist/providers/coding-proxy/registry/service.js +1137 -0
  258. package/dist/providers/coding-proxy/registry/store.d.ts +127 -0
  259. package/dist/providers/coding-proxy/registry/store.js +70 -0
  260. package/dist/providers/coding-proxy/runtime.d.ts +17 -0
  261. package/dist/providers/coding-proxy/runtime.js +63 -0
  262. package/dist/providers/coding-proxy/secure-fetch.js +0 -1
  263. package/dist/providers/coding-proxy/server.d.ts +11 -0
  264. package/dist/providers/coding-proxy/server.js +163 -0
  265. package/dist/providers/coding-proxy/types.d.ts +15 -0
  266. package/dist/providers/engine/types.d.ts +10 -0
  267. package/dist/providers/executor/build.d.ts +2 -2
  268. package/dist/providers/executor/composition.d.ts +3 -0
  269. package/dist/providers/executor/composition.js +19 -0
  270. package/dist/providers/executor/container.d.ts +105 -3
  271. package/dist/providers/executor/container.js +296 -20
  272. package/dist/providers/executor/dbos.d.ts +2 -3
  273. package/dist/providers/executor/in-process.d.ts +2 -3
  274. package/dist/providers/executor/routing.d.ts +7 -0
  275. package/dist/providers/executor/routing.js +13 -0
  276. package/dist/providers/executor/types.d.ts +22 -0
  277. package/dist/providers/jobs/claude-tool-setup.d.ts +23 -0
  278. package/dist/providers/jobs/claude-tool-setup.js +50 -0
  279. package/dist/providers/jobs/collect-prune.d.ts +7 -0
  280. package/dist/providers/jobs/collect-prune.js +27 -0
  281. package/dist/providers/jobs/docker-isolation.d.ts +48 -3
  282. package/dist/providers/jobs/docker-isolation.js +213 -22
  283. package/dist/providers/jobs/docker-services.d.ts +35 -0
  284. package/dist/providers/jobs/docker-services.js +191 -0
  285. package/dist/providers/jobs/docker.d.ts +49 -2
  286. package/dist/providers/jobs/docker.js +279 -32
  287. package/dist/providers/jobs/fake-kubernetes-api.d.ts +1 -0
  288. package/dist/providers/jobs/fake-kubernetes-api.js +12 -3
  289. package/dist/providers/jobs/kubernetes-isolation.d.ts +17 -2
  290. package/dist/providers/jobs/kubernetes-isolation.js +238 -57
  291. package/dist/providers/jobs/kubernetes-platform.d.ts +10 -4
  292. package/dist/providers/jobs/kubernetes-platform.js +11 -5
  293. package/dist/providers/jobs/kubernetes-preflight.js +3 -0
  294. package/dist/providers/jobs/kubernetes.d.ts +21 -2
  295. package/dist/providers/jobs/kubernetes.js +112 -21
  296. package/dist/providers/jobs/types.d.ts +18 -0
  297. package/dist/providers/llm/anthropic.js +6 -2
  298. package/dist/providers/llm/bedrock.js +2 -1
  299. package/dist/providers/llm/claude-messages.d.ts +5 -1
  300. package/dist/providers/llm/claude-messages.js +1 -0
  301. package/dist/providers/llm/claude-provider.d.ts +3 -1
  302. package/dist/providers/llm/claude-provider.js +5 -1
  303. package/dist/providers/llm/index.d.ts +1 -1
  304. package/dist/providers/llm/index.js +1 -1
  305. package/dist/providers/llm/pricing-anthropic.d.ts +10 -0
  306. package/dist/providers/llm/pricing-anthropic.js +32 -14
  307. package/dist/providers/llm/pricing-bedrock-claude.d.ts +8 -0
  308. package/dist/providers/llm/pricing-bedrock-claude.js +9 -0
  309. package/dist/providers/llm/routing.d.ts +10 -1
  310. package/dist/providers/llm/routing.js +18 -0
  311. package/dist/providers/llm/types.d.ts +12 -0
  312. package/dist/providers/llm/types.js +8 -1
  313. package/dist/providers/review-host/diff-lines.d.ts +16 -0
  314. package/dist/providers/review-host/diff-lines.js +59 -0
  315. package/dist/providers/review-host/github-events.d.ts +6 -0
  316. package/dist/providers/review-host/github-events.js +180 -0
  317. package/dist/providers/review-host/github-user-auth.d.ts +38 -0
  318. package/dist/providers/review-host/github-user-auth.js +128 -0
  319. package/dist/providers/review-host/github.d.ts +57 -0
  320. package/dist/providers/review-host/github.js +567 -0
  321. package/dist/providers/review-host/index.d.ts +12 -0
  322. package/dist/providers/review-host/index.js +30 -0
  323. package/dist/providers/review-host/review-format.d.ts +19 -0
  324. package/dist/providers/review-host/review-format.js +59 -0
  325. package/dist/providers/review-host/types.d.ts +284 -0
  326. package/dist/providers/review-host/types.js +24 -0
  327. package/dist/providers/vcs/git.d.ts +16 -6
  328. package/dist/providers/vcs/git.js +77 -13
  329. package/dist/providers/vcs/github.d.ts +74 -3
  330. package/dist/providers/vcs/github.js +195 -15
  331. package/dist/providers/vcs/types.d.ts +42 -4
  332. package/dist/quickstart/index.js +13 -2
  333. package/dist/sandbox/fetch-policy.d.ts +20 -2
  334. package/dist/sandbox/fetch-policy.js +64 -3
  335. package/dist/sandbox/host-functions.d.ts +9 -1
  336. package/dist/sandbox/host-functions.js +12 -5
  337. package/dist/serve.js +1 -1
  338. package/dist/wardby-bin.js +6 -0
  339. package/docs/README.md +30 -0
  340. package/docs/architecture-runtime.md +90 -0
  341. package/docs/assets/brand/wardby-icon-512.png +0 -0
  342. package/docs/assets/brand/wardby-icon.svg +16 -0
  343. package/docs/assets/brand/wardby-mascot-profile-512.png +0 -0
  344. package/docs/assets/brand/wardby-mascot.png +0 -0
  345. package/docs/assets/brand/wardby-mascot.svg +5 -0
  346. package/docs/assets/wardby-workflow.svg +106 -0
  347. package/docs/code-review-agents.md +481 -0
  348. package/docs/coding-agent-setup.md +172 -0
  349. package/docs/coding-packages.md +455 -0
  350. package/docs/coding-services.md +300 -0
  351. package/docs/coding-worker-byo-images.md +98 -0
  352. package/docs/coding-worker-isolation.md +1061 -0
  353. package/docs/getting-started-gke.md +615 -0
  354. package/docs/getting-started-identity-provider.md +308 -0
  355. package/docs/getting-started.md +133 -0
  356. package/docs/observability.md +53 -0
  357. package/docs/release-verification.md +66 -0
  358. package/docs/security-deployment.md +657 -0
  359. package/help/code-review-agents.md +31 -0
  360. package/help/coding-packages.md +31 -0
  361. package/help/coding-services.md +71 -0
  362. package/help/creating-agents.md +68 -0
  363. package/help/deploy-gke.md +39 -0
  364. package/help/deployment-targets.md +39 -0
  365. package/help/errors/budget-group-exhausted.md +25 -0
  366. package/help/errors/docker-isolation-unsupported.md +26 -0
  367. package/help/errors/protected-path.md +45 -0
  368. package/help/errors/repo-access.md +27 -0
  369. package/help/errors/service-declaration-invalid.md +27 -0
  370. package/help/errors/service-declaration-unavailable.md +25 -0
  371. package/help/errors/service-launcher-unsupported.md +30 -0
  372. package/help/errors/service-not-allowed.md +27 -0
  373. package/help/errors/service-unknown.md +23 -0
  374. package/help/errors/service-unready.md +49 -0
  375. package/help/getting-started.md +31 -0
  376. package/help/github.md +30 -0
  377. package/help/identity-and-access.md +39 -0
  378. package/help/mcp.md +30 -0
  379. package/help/native-capabilities.md +30 -0
  380. package/help/observability.md +41 -0
  381. package/help/operating-agents.md +30 -0
  382. package/help/security.md +27 -0
  383. package/help/troubleshooting/budgets.md +26 -0
  384. package/help/troubleshooting/coding-workers.md +47 -0
  385. package/help/troubleshooting/repository-access.md +27 -0
  386. package/package.json +12 -3
  387. package/prisma/migrations/20260925010000_coding_collect_exclude/migration.sql +8 -0
  388. package/prisma/migrations/20260925015000_allowed_egress_default/migration.sql +8 -0
  389. package/prisma/migrations/20260925020000_coding_package_registry/migration.sql +55 -0
  390. package/prisma/migrations/20260925030000_tool_name_per_owner/migration.sql +11 -0
  391. package/prisma/migrations/20260926010000_run_trigger_host_event/migration.sql +7 -0
  392. package/prisma/migrations/20260926020000_code_review_hosts/migration.sql +53 -0
  393. package/prisma/migrations/20260926030000_auth_user_roles/migration.sql +9 -0
  394. package/prisma/migrations/20260926040000_agent_effort/migration.sql +6 -0
  395. package/prisma/migrations/20260926050000_registry_lockfile_plan/migration.sql +32 -0
  396. package/prisma/migrations/20260926050000_repo_access_authorization/migration.sql +82 -0
  397. package/prisma/migrations/20260926060000_registry_plan_refusal/migration.sql +19 -0
  398. package/prisma/migrations/20260926100000_registry_plan_refusal_published_at/migration.sql +5 -0
  399. package/prisma/migrations/20260926190000_run_host_status/migration.sql +18 -0
  400. package/prisma/migrations/20260926210000_run_host_status_at_dispatch/migration.sql +5 -0
  401. package/prisma/migrations/20260927010000_resource_grants/migration.sql +72 -0
  402. package/prisma/migrations/20260927020000_proxy_session_budget_exhausted/migration.sql +3 -0
  403. package/prisma/migrations/20260927030000_coding_debug_trace/migration.sql +4 -0
  404. package/prisma/migrations/20260927040000_proxy_session_upstream_failure/migration.sql +3 -0
  405. package/prisma/migrations/20260927050000_coding_run_services/migration.sql +74 -0
  406. package/prisma/migrations/20260928000000_coding_run_tool_image/migration.sql +5 -0
  407. package/prisma/schema.prisma +407 -21
@@ -0,0 +1 @@
1
+ export {};
@@ -184,10 +184,11 @@ export type ToolOrderByWithRelationInput = {
184
184
  };
185
185
  export type ToolWhereUniqueInput = Prisma.AtLeast<{
186
186
  id?: string;
187
- name?: string;
187
+ ownerId_name?: Prisma.ToolOwnerIdNameCompoundUniqueInput;
188
188
  AND?: Prisma.ToolWhereInput | Prisma.ToolWhereInput[];
189
189
  OR?: Prisma.ToolWhereInput[];
190
190
  NOT?: Prisma.ToolWhereInput | Prisma.ToolWhereInput[];
191
+ name?: Prisma.StringFilter<"Tool"> | string;
191
192
  description?: Prisma.StringFilter<"Tool"> | string;
192
193
  paramsZod?: Prisma.StringFilter<"Tool"> | string;
193
194
  jsonSchema?: Prisma.JsonFilter<"Tool">;
@@ -197,7 +198,7 @@ export type ToolWhereUniqueInput = Prisma.AtLeast<{
197
198
  ownerId?: Prisma.StringNullableFilter<"Tool"> | string | null;
198
199
  agents?: Prisma.AgentToolListRelationFilter;
199
200
  owner?: Prisma.XOR<Prisma.PrincipalNullableScalarRelationFilter, Prisma.PrincipalWhereInput> | null;
200
- }, "id" | "name">;
201
+ }, "id" | "ownerId_name">;
201
202
  export type ToolOrderByWithAggregationInput = {
202
203
  id?: Prisma.SortOrder;
203
204
  name?: Prisma.SortOrder;
@@ -306,6 +307,10 @@ export type ToolUncheckedUpdateManyInput = {
306
307
  updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string;
307
308
  ownerId?: Prisma.NullableStringFieldUpdateOperationsInput | string | null;
308
309
  };
310
+ export type ToolOwnerIdNameCompoundUniqueInput = {
311
+ ownerId: string;
312
+ name: string;
313
+ };
309
314
  export type ToolCountOrderByAggregateInput = {
310
315
  id?: Prisma.SortOrder;
311
316
  name?: Prisma.SortOrder;
@@ -648,6 +653,12 @@ export type $ToolPayload<ExtArgs extends runtime.Types.Extensions.InternalArgs =
648
653
  };
649
654
  scalars: runtime.Types.Extensions.GetPayloadResult<{
650
655
  id: string;
656
+ /**
657
+ * Unique per owner (@@unique below), not globally: the name is what the
658
+ * model calls, so what must never repeat is a name within one agent's
659
+ * attachments — attach_tool, `wardby tool attach` and the importer refuse
660
+ * a second same-named tool on an agent.
661
+ */
651
662
  name: string;
652
663
  description: string;
653
664
  /**
@@ -660,7 +671,8 @@ export type $ToolPayload<ExtArgs extends runtime.Types.Extensions.InternalArgs =
660
671
  * and cached here — re-deriving it on every run would spin a fresh QuickJS
661
672
  * runtime and evaluate the whole vendored zod bundle per attached tool,
662
673
  * before the first LLM call, on every single run. Safe to cache because
663
- * there's no "update tool" path — paramsZod can't change without a new row.
674
+ * every write of paramsZod (create, and MCP update_tool) re-derives it in
675
+ * the same write. A run pins the version its load step read.
664
676
  */
665
677
  jsonSchema: runtime.JsonValue;
666
678
  /**
@@ -10,10 +10,17 @@ export type * from './models/AgentMemory.js';
10
10
  export type * from './models/Run.js';
11
11
  export type * from './models/CodingAgentProfile.js';
12
12
  export type * from './models/CodingRun.js';
13
+ export type * from './models/CodingService.js';
13
14
  export type * from './models/CodingProxySession.js';
14
15
  export type * from './models/CodingProxyRequest.js';
16
+ export type * from './models/RegistryAllowance.js';
17
+ export type * from './models/RegistryFetch.js';
18
+ export type * from './models/RegistryVersionFact.js';
19
+ export type * from './models/RegistryApprovedVersion.js';
20
+ export type * from './models/RegistryPlanRefusal.js';
15
21
  export type * from './models/SchedulerLease.js';
16
22
  export type * from './models/Principal.js';
23
+ export type * from './models/ResourceGrant.js';
17
24
  export type * from './models/Task.js';
18
25
  export type * from './models/Secret.js';
19
26
  export type * from './models/AgentSecret.js';
@@ -29,4 +36,10 @@ export type * from './models/OAuthAuthorizationRequest.js';
29
36
  export type * from './models/OAuthAuthorizationCode.js';
30
37
  export type * from './models/OAuthFamily.js';
31
38
  export type * from './models/AuthRateLimit.js';
39
+ export type * from './models/AgentRepository.js';
40
+ export type * from './models/RunHostCheck.js';
41
+ export type * from './models/RunHostStatus.js';
42
+ export type * from './models/HostEventDelivery.js';
43
+ export type * from './models/HostIdentity.js';
44
+ export type * from './models/HostIdentityLinkRequest.js';
32
45
  export type * from './commonInputTypes.js';
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,9 @@
1
+ import { mkdir, writeFile } from "node:fs/promises";
2
+ import { dirname } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { buildHelpCatalog } from "./catalog.js";
5
+ const helpRoot = fileURLToPath(new URL("../../help/", import.meta.url));
6
+ const output = fileURLToPath(new URL("../../dist/help-index.json", import.meta.url));
7
+ const catalog = await buildHelpCatalog(helpRoot);
8
+ await mkdir(dirname(output), { recursive: true });
9
+ await writeFile(output, `${JSON.stringify(catalog, null, 2)}\n`, "utf8");
@@ -0,0 +1,24 @@
1
+ export type HelpAudience = "operator" | "developer" | "all";
2
+ export interface HelpHeading {
3
+ level: number;
4
+ text: string;
5
+ slug: string;
6
+ }
7
+ export interface HelpPage {
8
+ id: string;
9
+ title: string;
10
+ summary: string;
11
+ audience: HelpAudience;
12
+ tags: string[];
13
+ appliesTo: string;
14
+ sourcePath: string;
15
+ markdown: string;
16
+ plainText: string;
17
+ headings: HelpHeading[];
18
+ }
19
+ export interface HelpCatalog {
20
+ schemaVersion: 1;
21
+ pages: HelpPage[];
22
+ }
23
+ /** Builds the deterministic, release-bundled catalog from the checked-in help corpus. */
24
+ export declare function buildHelpCatalog(root: string): Promise<HelpCatalog>;
@@ -0,0 +1,160 @@
1
+ import { readdir, readFile } from "node:fs/promises";
2
+ import { relative, resolve, sep } from "node:path";
3
+ const REQUIRED_FIELDS = ["id", "title", "summary", "audience", "tags", "appliesTo"];
4
+ const ALLOWED_FIELDS = new Set(REQUIRED_FIELDS);
5
+ const MARKDOWN_LINK = /!?\[[^\]]*\]\(([^)\s]+)(?:\s+[^)]*)?\)/g;
6
+ function failure(sourcePath, message) {
7
+ return new Error(`Invalid help page ${sourcePath}: ${message}`);
8
+ }
9
+ function slugify(value) {
10
+ return value
11
+ .trim()
12
+ .toLowerCase()
13
+ .replace(/[`*_~]/g, "")
14
+ .replace(/[^a-z0-9]+/g, "-")
15
+ .replace(/(^-|-$)/g, "");
16
+ }
17
+ function plainText(markdown) {
18
+ return markdown
19
+ .replace(/```[\s\S]*?```/g, (block) => block.replace(/```[^\n]*\n?|```/g, ""))
20
+ .replace(/!\[[^\]]*\]\([^)]*\)/g, "")
21
+ .replace(/\[([^\]]+)\]\([^)]*\)/g, "$1")
22
+ .replace(/^\s{0,3}#{1,6}\s+/gm, "")
23
+ .replace(/^\s{0,3}[-*+]\s+/gm, "")
24
+ .replace(/^\s{0,3}\d+\.\s+/gm, "")
25
+ .replace(/[>*_`]/g, "")
26
+ .replace(/\s+/g, " ")
27
+ .trim();
28
+ }
29
+ function parseFrontmatter(sourcePath, markdown) {
30
+ const match = /^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)$/.exec(markdown);
31
+ if (!match)
32
+ throw failure(sourcePath, "expected YAML frontmatter delimited by ---");
33
+ const fields = new Map();
34
+ for (const line of match[1].split(/\r?\n/)) {
35
+ const field = /^([A-Za-z][A-Za-z0-9]*):\s*(.+)$/.exec(line);
36
+ if (!field)
37
+ throw failure(sourcePath, `unsupported frontmatter line: ${line}`);
38
+ const [, key, value] = field;
39
+ if (!ALLOWED_FIELDS.has(key))
40
+ throw failure(sourcePath, `unknown frontmatter field "${key}"`);
41
+ if (fields.has(key))
42
+ throw failure(sourcePath, `duplicate frontmatter field "${key}"`);
43
+ fields.set(key, value.trim());
44
+ }
45
+ for (const required of REQUIRED_FIELDS) {
46
+ if (!fields.get(required))
47
+ throw failure(sourcePath, `missing frontmatter field "${required}"`);
48
+ }
49
+ return { fields, body: match[2] };
50
+ }
51
+ function parseTags(sourcePath, value) {
52
+ const match = /^\[([^\]]*)\]$/.exec(value);
53
+ if (!match)
54
+ throw failure(sourcePath, "tags must be an inline list, for example [budget, security]");
55
+ const tags = match[1]
56
+ .split(",")
57
+ .map((tag) => tag.trim())
58
+ .filter(Boolean);
59
+ if (!tags.length)
60
+ throw failure(sourcePath, "tags must contain at least one value");
61
+ if (new Set(tags).size !== tags.length)
62
+ throw failure(sourcePath, "tags must not contain duplicates");
63
+ return tags;
64
+ }
65
+ function headings(markdown) {
66
+ return [...markdown.matchAll(/^(#{1,6})\s+(.+?)\s*#*\s*$/gm)].map((match) => ({
67
+ level: match[1].length,
68
+ text: match[2].trim(),
69
+ slug: slugify(match[2]),
70
+ }));
71
+ }
72
+ function parsePage(root, filePath, markdown) {
73
+ const sourcePath = relative(root, filePath).split(sep).join("/");
74
+ const { fields, body } = parseFrontmatter(sourcePath, markdown);
75
+ const audience = fields.get("audience");
76
+ if (audience !== "operator" && audience !== "developer" && audience !== "all") {
77
+ throw failure(sourcePath, 'audience must be "operator", "developer", or "all"');
78
+ }
79
+ const pageHeadings = headings(body);
80
+ return {
81
+ filePath,
82
+ page: {
83
+ id: fields.get("id"),
84
+ title: fields.get("title"),
85
+ summary: fields.get("summary"),
86
+ audience,
87
+ tags: parseTags(sourcePath, fields.get("tags")),
88
+ appliesTo: fields.get("appliesTo"),
89
+ sourcePath,
90
+ markdown: body,
91
+ plainText: plainText(body),
92
+ headings: pageHeadings,
93
+ },
94
+ };
95
+ }
96
+ async function markdownFiles(directory) {
97
+ const entries = await readdir(directory, { withFileTypes: true });
98
+ const files = [];
99
+ for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
100
+ const path = resolve(directory, entry.name);
101
+ if (entry.isDirectory())
102
+ files.push(...(await markdownFiles(path)));
103
+ else if (entry.isFile() && entry.name.endsWith(".md"))
104
+ files.push(path);
105
+ else if (entry.isSymbolicLink())
106
+ throw new Error(`Help corpus must not contain symbolic links: ${path}`);
107
+ }
108
+ return files;
109
+ }
110
+ function isWithin(root, candidate) {
111
+ const path = relative(root, candidate);
112
+ return path === "" || (!path.startsWith(`..${sep}`) && path !== "..");
113
+ }
114
+ function validateLinks(root, pages) {
115
+ const byId = new Map(pages.map(({ page }) => [page.id, page]));
116
+ const byFile = new Map(pages.map((parsed) => [resolve(parsed.filePath), parsed.page]));
117
+ for (const { page, filePath } of pages) {
118
+ for (const match of page.markdown.matchAll(MARKDOWN_LINK)) {
119
+ const href = match[1];
120
+ if (href.startsWith("help://")) {
121
+ const id = href.slice("help://".length).split("#", 1)[0];
122
+ if (!byId.has(id))
123
+ throw failure(page.sourcePath, `unknown help link "${href}"`);
124
+ continue;
125
+ }
126
+ const [target, fragment] = href.split("#", 2);
127
+ let linkedPage = page;
128
+ if (target.endsWith(".md")) {
129
+ const linkedPath = resolve(filePath, "..", target);
130
+ if (!isWithin(root, linkedPath))
131
+ continue;
132
+ linkedPage = byFile.get(linkedPath);
133
+ if (!linkedPage)
134
+ throw failure(page.sourcePath, `unknown help link "${href}"`);
135
+ }
136
+ else if (target) {
137
+ continue;
138
+ }
139
+ if (fragment && !linkedPage.headings.some((heading) => heading.slug === decodeURIComponent(fragment))) {
140
+ throw failure(page.sourcePath, `unknown help heading "${href}"`);
141
+ }
142
+ }
143
+ }
144
+ }
145
+ /** Builds the deterministic, release-bundled catalog from the checked-in help corpus. */
146
+ export async function buildHelpCatalog(root) {
147
+ const canonicalRoot = resolve(root);
148
+ const files = await markdownFiles(canonicalRoot);
149
+ if (!files.length)
150
+ throw new Error(`Help corpus has no Markdown pages: ${canonicalRoot}`);
151
+ const pages = await Promise.all(files.map(async (filePath) => parsePage(canonicalRoot, filePath, await readFile(filePath, "utf8"))));
152
+ pages.sort((a, b) => a.page.id.localeCompare(b.page.id));
153
+ for (const [index, current] of pages.entries()) {
154
+ if (index > 0 && current.page.id === pages[index - 1].page.id) {
155
+ throw failure(current.page.sourcePath, `duplicate page id "${current.page.id}"`);
156
+ }
157
+ }
158
+ validateLinks(canonicalRoot, pages);
159
+ return { schemaVersion: 1, pages: pages.map(({ page }) => page) };
160
+ }
@@ -0,0 +1,2 @@
1
+ /** Runs the dependency-free, bundled help command. */
2
+ export declare function helpCommand(args: string[]): Promise<void>;
@@ -0,0 +1,65 @@
1
+ import { loadBundledHelpCatalog } from "./runtime.js";
2
+ import { searchHelp } from "./search.js";
3
+ function helpUsage() {
4
+ return `usage:
5
+ wardby help list
6
+ wardby help search <terms>
7
+ wardby help open <article-id>
8
+
9
+ Search is offline and fuzzy across titles, tags, headings, summaries, and article text.`;
10
+ }
11
+ function print(message) {
12
+ process.stdout.write(`${message}\n`);
13
+ }
14
+ function printPage(page) {
15
+ print(page.markdown.trim());
16
+ }
17
+ function printList(catalog) {
18
+ print(`Wardby help (${catalog.pages.length} articles)`);
19
+ for (const page of catalog.pages) {
20
+ print(`\n${page.id}\n ${page.title} — ${page.summary}`);
21
+ }
22
+ print("\nUse `wardby help search <terms>` to find an article, then `wardby help open <article-id>` to read it.");
23
+ }
24
+ function printResults(catalog, query) {
25
+ const results = searchHelp(catalog, query);
26
+ if (!results.length) {
27
+ print(`No help articles matched "${query}".`);
28
+ return;
29
+ }
30
+ print(`Help results for "${query}":`);
31
+ for (const result of results.slice(0, 10)) {
32
+ print(`\n${result.page.title} (${result.page.id})`);
33
+ if (result.matchedHeading && result.matchedHeading.text !== result.page.title) {
34
+ print(` Match: ${result.matchedHeading.text}`);
35
+ }
36
+ print(` ${result.excerpt}`);
37
+ }
38
+ }
39
+ /** Runs the dependency-free, bundled help command. */
40
+ export async function helpCommand(args) {
41
+ const catalog = await loadBundledHelpCatalog();
42
+ const [operation, ...rest] = args;
43
+ if (!operation || operation === "list") {
44
+ printList(catalog);
45
+ return;
46
+ }
47
+ if (operation === "search") {
48
+ const query = rest.join(" ").trim();
49
+ if (!query)
50
+ throw new Error("help search requires one or more terms.\n\n" + helpUsage());
51
+ printResults(catalog, query);
52
+ return;
53
+ }
54
+ if (operation === "open") {
55
+ const id = rest[0];
56
+ if (!id || rest.length !== 1)
57
+ throw new Error("help open requires exactly one article id.\n\n" + helpUsage());
58
+ const page = catalog.pages.find((candidate) => candidate.id === id);
59
+ if (!page)
60
+ throw new Error(`unknown help article "${id}". Run \`wardby help list\` to see available articles.`);
61
+ printPage(page);
62
+ return;
63
+ }
64
+ throw new Error(`unknown help command "${operation}".\n\n${helpUsage()}`);
65
+ }
@@ -0,0 +1,3 @@
1
+ import type { HelpCatalog } from "./catalog.js";
2
+ /** Loads the immutable help catalog bundled with this Wardby release, once per process. */
3
+ export declare function loadBundledHelpCatalog(): Promise<HelpCatalog>;
@@ -0,0 +1,44 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { fileURLToPath } from "node:url";
3
+ const COMPILED_CATALOG = fileURLToPath(new URL("../help-index.json", import.meta.url));
4
+ const SOURCE_CATALOG = fileURLToPath(new URL("../../dist/help-index.json", import.meta.url));
5
+ function isPage(value) {
6
+ if (!value || typeof value !== "object")
7
+ return false;
8
+ const page = value;
9
+ return (typeof page.id === "string" &&
10
+ typeof page.title === "string" &&
11
+ typeof page.summary === "string" &&
12
+ typeof page.markdown === "string" &&
13
+ typeof page.plainText === "string" &&
14
+ Array.isArray(page.tags) &&
15
+ Array.isArray(page.headings));
16
+ }
17
+ function parseCatalog(serialized) {
18
+ const value = JSON.parse(serialized);
19
+ if (!value || typeof value !== "object")
20
+ throw new Error("bundled help catalog is not an object");
21
+ const catalog = value;
22
+ if (catalog.schemaVersion !== 1 || !Array.isArray(catalog.pages) || !catalog.pages.every(isPage)) {
23
+ throw new Error("bundled help catalog has an unsupported format");
24
+ }
25
+ return catalog;
26
+ }
27
+ async function readCatalog() {
28
+ for (const path of [COMPILED_CATALOG, SOURCE_CATALOG]) {
29
+ try {
30
+ return parseCatalog(await readFile(path, "utf8"));
31
+ }
32
+ catch (error) {
33
+ if (error.code !== "ENOENT")
34
+ throw error;
35
+ }
36
+ }
37
+ throw new Error("Wardby help is unavailable because the bundled help catalog is missing. Run `npm run build`.");
38
+ }
39
+ let catalog;
40
+ /** Loads the immutable help catalog bundled with this Wardby release, once per process. */
41
+ export function loadBundledHelpCatalog() {
42
+ catalog ??= readCatalog();
43
+ return catalog;
44
+ }
@@ -0,0 +1,9 @@
1
+ import type { HelpCatalog, HelpHeading, HelpPage } from "./catalog.js";
2
+ export interface HelpSearchResult {
3
+ page: HelpPage;
4
+ score: number;
5
+ matchedHeading?: HelpHeading;
6
+ excerpt: string;
7
+ }
8
+ /** Finds pages with deterministic, offline fuzzy matching over their catalog metadata and body. */
9
+ export declare function searchHelp(catalog: HelpCatalog, query: string): HelpSearchResult[];
@@ -0,0 +1,104 @@
1
+ const WORD = /[a-z0-9]+/g;
2
+ function words(value) {
3
+ return value.toLowerCase().match(WORD) ?? [];
4
+ }
5
+ function damerauLevenshtein(left, right) {
6
+ const previousPrevious = new Array(right.length + 1).fill(0);
7
+ let previous = new Array(right.length + 1).fill(0);
8
+ let current = new Array(right.length + 1).fill(0);
9
+ for (let column = 0; column <= right.length; column += 1)
10
+ previous[column] = column;
11
+ for (let row = 1; row <= left.length; row += 1) {
12
+ current[0] = row;
13
+ for (let column = 1; column <= right.length; column += 1) {
14
+ const substitution = previous[column - 1] + Number(left[row - 1] !== right[column - 1]);
15
+ const insertion = current[column - 1] + 1;
16
+ const deletion = previous[column] + 1;
17
+ let distance = Math.min(substitution, insertion, deletion);
18
+ if (row > 1 && column > 1 && left[row - 1] === right[column - 2] && left[row - 2] === right[column - 1]) {
19
+ distance = Math.min(distance, previousPrevious[column - 2] + 1);
20
+ }
21
+ current[column] = distance;
22
+ }
23
+ previousPrevious.splice(0, previousPrevious.length, ...previous);
24
+ [previous, current] = [current, previous];
25
+ }
26
+ return previous[right.length];
27
+ }
28
+ function wordScore(query, candidate) {
29
+ if (query === candidate)
30
+ return 1;
31
+ if (query.length >= 2 && candidate.startsWith(query))
32
+ return 0.9;
33
+ if (query.length >= 3 && candidate.includes(query))
34
+ return 0.75;
35
+ // Short words are too broad for typo matching. Longer terms accept a small,
36
+ // bounded edit distance, including a swapped adjacent character.
37
+ if (query.length < 4 || candidate.length < 4)
38
+ return 0;
39
+ const maximumDistance = query.length <= 5 ? 1 : query.length <= 8 ? 2 : 3;
40
+ const distance = damerauLevenshtein(query, candidate);
41
+ if (distance > maximumDistance)
42
+ return 0;
43
+ return 0.6 - (distance - 1) * 0.1;
44
+ }
45
+ function fieldScore(query, field) {
46
+ let best = 0;
47
+ for (const candidate of words(field.text))
48
+ best = Math.max(best, wordScore(query, candidate));
49
+ return best * field.weight;
50
+ }
51
+ function pageScore(queries, page) {
52
+ const fields = [
53
+ { text: page.title, weight: 12 },
54
+ { text: page.tags.join(" "), weight: 10 },
55
+ { text: page.summary, weight: 5 },
56
+ ...page.headings.map((heading) => ({ text: heading.text, weight: 8 })),
57
+ { text: page.plainText, weight: 1 },
58
+ ];
59
+ let total = 0;
60
+ for (const query of queries) {
61
+ const score = Math.max(...fields.map((field) => fieldScore(query, field)));
62
+ if (score === 0)
63
+ return undefined;
64
+ total += score;
65
+ }
66
+ return total;
67
+ }
68
+ function matchingHeading(queries, page) {
69
+ return page.headings
70
+ .map((heading) => ({
71
+ heading,
72
+ score: queries.reduce((total, query) => total + fieldScore(query, { text: heading.text, weight: 1 }), 0),
73
+ }))
74
+ .filter((candidate) => candidate.score > 0)
75
+ .sort((left, right) => right.score - left.score || left.heading.slug.localeCompare(right.heading.slug))[0]?.heading;
76
+ }
77
+ function truncate(value, length = 180) {
78
+ return value.length <= length ? value : `${value.slice(0, length - 1).trimEnd()}…`;
79
+ }
80
+ function excerpt(queries, page) {
81
+ const candidates = [page.summary, ...page.plainText.split(/(?<=[.!?])\s+/)].filter(Boolean);
82
+ const best = candidates
83
+ .map((candidate) => ({
84
+ candidate,
85
+ score: queries.reduce((total, query) => total + fieldScore(query, { text: candidate, weight: 1 }), 0),
86
+ }))
87
+ .sort((left, right) => right.score - left.score || left.candidate.length - right.candidate.length)[0];
88
+ return truncate(best?.candidate ?? page.summary);
89
+ }
90
+ /** Finds pages with deterministic, offline fuzzy matching over their catalog metadata and body. */
91
+ export function searchHelp(catalog, query) {
92
+ const queries = words(query);
93
+ if (!queries.length)
94
+ return [];
95
+ const results = [];
96
+ for (const page of catalog.pages) {
97
+ const score = pageScore(queries, page);
98
+ if (score === undefined)
99
+ continue;
100
+ const heading = matchingHeading(queries, page);
101
+ results.push({ page, score, ...(heading ? { matchedHeading: heading } : {}), excerpt: excerpt(queries, page) });
102
+ }
103
+ return results.sort((left, right) => right.score - left.score || left.page.id.localeCompare(right.page.id));
104
+ }