@nilsr0711/drydock 0.1.1 → 0.1.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 (200) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/app-path-routes-manifest.json +7 -4
  3. package/.next/standalone/.next/build-manifest.json +2 -2
  4. package/.next/standalone/.next/prerender-manifest.json +3 -3
  5. package/.next/standalone/.next/routes-manifest.json +20 -0
  6. package/.next/standalone/.next/server/app/_global-error/page.js +2 -2
  7. package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  8. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  9. package/.next/standalone/.next/server/app/_global-error.rsc +1 -1
  10. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  11. package/.next/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  12. package/.next/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  13. package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  14. package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  15. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  16. package/.next/standalone/.next/server/app/_not-found/page.js +2 -2
  17. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  18. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  19. package/.next/standalone/.next/server/app/adrs/page.js +2 -2
  20. package/.next/standalone/.next/server/app/adrs/page.js.nft.json +1 -1
  21. package/.next/standalone/.next/server/app/adrs/page_client-reference-manifest.js +1 -1
  22. package/.next/standalone/.next/server/app/api/cost/export/route.js +1 -0
  23. package/.next/standalone/.next/server/app/api/cost/export/route.js.nft.json +1 -0
  24. package/.next/standalone/.next/server/app/api/cost/export/route_client-reference-manifest.js +1 -0
  25. package/.next/standalone/.next/server/app/api/sse/dashboard/route.js +4 -0
  26. package/.next/standalone/.next/server/app/api/sse/dashboard/route.js.nft.json +1 -0
  27. package/.next/standalone/.next/server/app/api/sse/dashboard/route_client-reference-manifest.js +1 -0
  28. package/.next/standalone/.next/server/app/api/sse/jobs/[id]/route.js +2 -2
  29. package/.next/standalone/.next/server/app/api/sse/jobs/[id]/route.js.nft.json +1 -1
  30. package/.next/standalone/.next/server/app/api/webhooks/[repoId]/route.js +1 -0
  31. package/.next/standalone/.next/server/app/api/webhooks/[repoId]/route.js.nft.json +1 -0
  32. package/.next/standalone/.next/server/app/api/webhooks/[repoId]/route_client-reference-manifest.js +1 -0
  33. package/.next/standalone/.next/server/app/costs/page.js +2 -2
  34. package/.next/standalone/.next/server/app/costs/page.js.nft.json +1 -1
  35. package/.next/standalone/.next/server/app/costs/page_client-reference-manifest.js +1 -1
  36. package/.next/standalone/.next/server/app/jobs/[id]/page.js +5 -2
  37. package/.next/standalone/.next/server/app/jobs/[id]/page.js.nft.json +1 -1
  38. package/.next/standalone/.next/server/app/jobs/[id]/page_client-reference-manifest.js +1 -1
  39. package/.next/standalone/.next/server/app/needs-human/page.js +2 -2
  40. package/.next/standalone/.next/server/app/needs-human/page.js.nft.json +1 -1
  41. package/.next/standalone/.next/server/app/needs-human/page_client-reference-manifest.js +1 -1
  42. package/.next/standalone/.next/server/app/page.js +2 -2
  43. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  44. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  45. package/.next/standalone/.next/server/app/prompts/page.js +2 -2
  46. package/.next/standalone/.next/server/app/prompts/page.js.nft.json +1 -1
  47. package/.next/standalone/.next/server/app/prompts/page_client-reference-manifest.js +1 -1
  48. package/.next/standalone/.next/server/app/repos/[id]/page.js +2 -2
  49. package/.next/standalone/.next/server/app/repos/[id]/page.js.nft.json +1 -1
  50. package/.next/standalone/.next/server/app/repos/[id]/page_client-reference-manifest.js +1 -1
  51. package/.next/standalone/.next/server/app/settings/page.js +2 -2
  52. package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
  53. package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  54. package/.next/standalone/.next/server/app-paths-manifest.json +7 -4
  55. package/.next/standalone/.next/server/chunks/235.js +1 -0
  56. package/.next/standalone/.next/server/chunks/309.js +1 -0
  57. package/.next/standalone/.next/server/chunks/350.js +9 -0
  58. package/.next/standalone/.next/server/chunks/39.js +3 -0
  59. package/.next/standalone/.next/server/chunks/447.js +1 -0
  60. package/.next/standalone/.next/server/chunks/507.js +9 -0
  61. package/.next/standalone/.next/server/chunks/541.js +1 -0
  62. package/.next/standalone/.next/server/chunks/710.js +1 -0
  63. package/.next/standalone/.next/server/chunks/75.js +1 -0
  64. package/.next/standalone/.next/server/chunks/777.js +1 -0
  65. package/.next/standalone/.next/server/chunks/895.js +62 -0
  66. package/.next/standalone/.next/server/chunks/9.js +3 -0
  67. package/.next/standalone/.next/server/middleware-build-manifest.js +1 -1
  68. package/.next/standalone/.next/server/pages/500.html +1 -1
  69. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  70. package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
  71. package/.next/standalone/.next/static/ZVOWTwaHoyx4ZcNdVzldW/_buildManifest.js +1 -0
  72. package/.next/standalone/.next/static/chunks/0-539cfb6a78b205f0.js +1 -0
  73. package/.next/standalone/.next/static/chunks/66-a23941cbbf88d6cd.js +1 -0
  74. package/.next/standalone/.next/static/chunks/app/_global-error/page-abc1c30fe033f680.js +1 -0
  75. package/.next/standalone/.next/static/chunks/app/adrs/page-e75b859d58967540.js +1 -0
  76. package/.next/standalone/.next/static/chunks/app/api/cost/export/route-abc1c30fe033f680.js +1 -0
  77. package/.next/standalone/.next/static/chunks/app/api/sse/dashboard/route-abc1c30fe033f680.js +1 -0
  78. package/.next/standalone/.next/static/chunks/app/api/sse/jobs/[id]/route-abc1c30fe033f680.js +1 -0
  79. package/.next/standalone/.next/static/chunks/app/api/webhooks/[repoId]/route-abc1c30fe033f680.js +1 -0
  80. package/.next/standalone/.next/static/chunks/app/costs/page-737c89e70adff98b.js +1 -0
  81. package/.next/standalone/.next/static/chunks/app/jobs/[id]/page-265e757a89803698.js +1 -0
  82. package/.next/standalone/.next/static/chunks/app/layout-f6048a8c9f541e0d.js +1 -0
  83. package/.next/standalone/.next/static/chunks/app/needs-human/page-88cb4f2c3aaf76c4.js +1 -0
  84. package/.next/standalone/.next/static/chunks/app/page-ab315dafdd344c06.js +1 -0
  85. package/.next/standalone/.next/static/chunks/app/prompts/page-562338cf5f420f96.js +1 -0
  86. package/.next/standalone/.next/static/chunks/app/repos/[id]/page-3099a9a513358664.js +1 -0
  87. package/.next/standalone/.next/static/chunks/app/settings/page-b6af5304dba71aa3.js +1 -0
  88. package/.next/standalone/.next/static/chunks/next/dist/client/components/builtin/app-error-abc1c30fe033f680.js +1 -0
  89. package/.next/standalone/.next/static/chunks/next/dist/client/components/builtin/forbidden-abc1c30fe033f680.js +1 -0
  90. package/.next/standalone/.next/static/chunks/next/dist/client/components/builtin/not-found-abc1c30fe033f680.js +1 -0
  91. package/.next/standalone/.next/static/chunks/next/dist/client/components/builtin/unauthorized-abc1c30fe033f680.js +1 -0
  92. package/.next/standalone/.next/static/css/64af283f7f467bc0.css +3 -0
  93. package/.next/standalone/drizzle/0013_true_red_wolf.sql +1 -0
  94. package/.next/standalone/drizzle/0014_dry_medusa.sql +1 -0
  95. package/.next/standalone/drizzle/0015_dapper_ben_grimm.sql +1 -0
  96. package/.next/standalone/drizzle/0016_charming_natasha_romanoff.sql +14 -0
  97. package/.next/standalone/drizzle/0017_free_warbound.sql +1 -0
  98. package/.next/standalone/drizzle/0018_confused_franklin_storm.sql +1 -0
  99. package/.next/standalone/drizzle/0019_uneven_meggan.sql +22 -0
  100. package/.next/standalone/drizzle/0020_chunky_meggan.sql +1 -0
  101. package/.next/standalone/drizzle/meta/0013_snapshot.json +1487 -0
  102. package/.next/standalone/drizzle/meta/0014_snapshot.json +1494 -0
  103. package/.next/standalone/drizzle/meta/0015_snapshot.json +1502 -0
  104. package/.next/standalone/drizzle/meta/0016_snapshot.json +1600 -0
  105. package/.next/standalone/drizzle/meta/0017_snapshot.json +1607 -0
  106. package/.next/standalone/drizzle/meta/0018_snapshot.json +1614 -0
  107. package/.next/standalone/drizzle/meta/0019_snapshot.json +1773 -0
  108. package/.next/standalone/drizzle/meta/0020_snapshot.json +1780 -0
  109. package/.next/standalone/drizzle/meta/_journal.json +56 -0
  110. package/.next/standalone/package.json +1 -1
  111. package/.next/static/ZVOWTwaHoyx4ZcNdVzldW/_buildManifest.js +1 -0
  112. package/.next/static/chunks/0-539cfb6a78b205f0.js +1 -0
  113. package/.next/static/chunks/66-a23941cbbf88d6cd.js +1 -0
  114. package/.next/static/chunks/app/_global-error/page-abc1c30fe033f680.js +1 -0
  115. package/.next/static/chunks/app/adrs/page-e75b859d58967540.js +1 -0
  116. package/.next/static/chunks/app/api/cost/export/route-abc1c30fe033f680.js +1 -0
  117. package/.next/static/chunks/app/api/sse/dashboard/route-abc1c30fe033f680.js +1 -0
  118. package/.next/static/chunks/app/api/sse/jobs/[id]/route-abc1c30fe033f680.js +1 -0
  119. package/.next/static/chunks/app/api/webhooks/[repoId]/route-abc1c30fe033f680.js +1 -0
  120. package/.next/static/chunks/app/costs/page-737c89e70adff98b.js +1 -0
  121. package/.next/static/chunks/app/jobs/[id]/page-265e757a89803698.js +1 -0
  122. package/.next/static/chunks/app/layout-f6048a8c9f541e0d.js +1 -0
  123. package/.next/static/chunks/app/needs-human/page-88cb4f2c3aaf76c4.js +1 -0
  124. package/.next/static/chunks/app/page-ab315dafdd344c06.js +1 -0
  125. package/.next/static/chunks/app/prompts/page-562338cf5f420f96.js +1 -0
  126. package/.next/static/chunks/app/repos/[id]/page-3099a9a513358664.js +1 -0
  127. package/.next/static/chunks/app/settings/page-b6af5304dba71aa3.js +1 -0
  128. package/.next/static/chunks/next/dist/client/components/builtin/app-error-abc1c30fe033f680.js +1 -0
  129. package/.next/static/chunks/next/dist/client/components/builtin/forbidden-abc1c30fe033f680.js +1 -0
  130. package/.next/static/chunks/next/dist/client/components/builtin/not-found-abc1c30fe033f680.js +1 -0
  131. package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-abc1c30fe033f680.js +1 -0
  132. package/.next/static/css/64af283f7f467bc0.css +3 -0
  133. package/README.md +24 -11
  134. package/bin/drydock.mjs +19 -0
  135. package/drizzle/0013_true_red_wolf.sql +1 -0
  136. package/drizzle/0014_dry_medusa.sql +1 -0
  137. package/drizzle/0015_dapper_ben_grimm.sql +1 -0
  138. package/drizzle/0016_charming_natasha_romanoff.sql +14 -0
  139. package/drizzle/0017_free_warbound.sql +1 -0
  140. package/drizzle/0018_confused_franklin_storm.sql +1 -0
  141. package/drizzle/0019_uneven_meggan.sql +22 -0
  142. package/drizzle/0020_chunky_meggan.sql +1 -0
  143. package/drizzle/meta/0013_snapshot.json +1487 -0
  144. package/drizzle/meta/0014_snapshot.json +1494 -0
  145. package/drizzle/meta/0015_snapshot.json +1502 -0
  146. package/drizzle/meta/0016_snapshot.json +1600 -0
  147. package/drizzle/meta/0017_snapshot.json +1607 -0
  148. package/drizzle/meta/0018_snapshot.json +1614 -0
  149. package/drizzle/meta/0019_snapshot.json +1773 -0
  150. package/drizzle/meta/0020_snapshot.json +1780 -0
  151. package/drizzle/meta/_journal.json +56 -0
  152. package/package.json +1 -1
  153. package/.next/standalone/.next/server/chunks/152.js +0 -8
  154. package/.next/standalone/.next/server/chunks/21.js +0 -1
  155. package/.next/standalone/.next/server/chunks/441.js +0 -1
  156. package/.next/standalone/.next/server/chunks/633.js +0 -1
  157. package/.next/standalone/.next/server/chunks/706.js +0 -8
  158. package/.next/standalone/.next/server/chunks/753.js +0 -1
  159. package/.next/standalone/.next/server/chunks/774.js +0 -62
  160. package/.next/standalone/.next/server/chunks/859.js +0 -1
  161. package/.next/standalone/.next/static/chunks/602-2da35213db585d76.js +0 -1
  162. package/.next/standalone/.next/static/chunks/66-2a5c080641376f3a.js +0 -1
  163. package/.next/standalone/.next/static/chunks/app/_global-error/page-94dd6bc2d60a8443.js +0 -1
  164. package/.next/standalone/.next/static/chunks/app/adrs/page-881f748d299c703e.js +0 -1
  165. package/.next/standalone/.next/static/chunks/app/api/sse/jobs/[id]/route-94dd6bc2d60a8443.js +0 -1
  166. package/.next/standalone/.next/static/chunks/app/costs/page-7aeb8fd8816090f7.js +0 -1
  167. package/.next/standalone/.next/static/chunks/app/jobs/[id]/page-23dddd5f27bc7c02.js +0 -1
  168. package/.next/standalone/.next/static/chunks/app/layout-6ad777543855c715.js +0 -1
  169. package/.next/standalone/.next/static/chunks/app/needs-human/page-abedbafec351380c.js +0 -1
  170. package/.next/standalone/.next/static/chunks/app/page-ebe54ab85fbcf147.js +0 -1
  171. package/.next/standalone/.next/static/chunks/app/prompts/page-3b9006a4513a173a.js +0 -1
  172. package/.next/standalone/.next/static/chunks/app/repos/[id]/page-16e031b6eab3ee05.js +0 -1
  173. package/.next/standalone/.next/static/chunks/app/settings/page-54d0967b6d9f734f.js +0 -1
  174. package/.next/standalone/.next/static/chunks/next/dist/client/components/builtin/app-error-94dd6bc2d60a8443.js +0 -1
  175. package/.next/standalone/.next/static/chunks/next/dist/client/components/builtin/forbidden-94dd6bc2d60a8443.js +0 -1
  176. package/.next/standalone/.next/static/chunks/next/dist/client/components/builtin/not-found-94dd6bc2d60a8443.js +0 -1
  177. package/.next/standalone/.next/static/chunks/next/dist/client/components/builtin/unauthorized-94dd6bc2d60a8443.js +0 -1
  178. package/.next/standalone/.next/static/css/b4ec7103106db91b.css +0 -3
  179. package/.next/standalone/.next/static/iEqTV0p0ZoW-sr0JEYokX/_buildManifest.js +0 -1
  180. package/.next/static/chunks/602-2da35213db585d76.js +0 -1
  181. package/.next/static/chunks/66-2a5c080641376f3a.js +0 -1
  182. package/.next/static/chunks/app/_global-error/page-94dd6bc2d60a8443.js +0 -1
  183. package/.next/static/chunks/app/adrs/page-881f748d299c703e.js +0 -1
  184. package/.next/static/chunks/app/api/sse/jobs/[id]/route-94dd6bc2d60a8443.js +0 -1
  185. package/.next/static/chunks/app/costs/page-7aeb8fd8816090f7.js +0 -1
  186. package/.next/static/chunks/app/jobs/[id]/page-23dddd5f27bc7c02.js +0 -1
  187. package/.next/static/chunks/app/layout-6ad777543855c715.js +0 -1
  188. package/.next/static/chunks/app/needs-human/page-abedbafec351380c.js +0 -1
  189. package/.next/static/chunks/app/page-ebe54ab85fbcf147.js +0 -1
  190. package/.next/static/chunks/app/prompts/page-3b9006a4513a173a.js +0 -1
  191. package/.next/static/chunks/app/repos/[id]/page-16e031b6eab3ee05.js +0 -1
  192. package/.next/static/chunks/app/settings/page-54d0967b6d9f734f.js +0 -1
  193. package/.next/static/chunks/next/dist/client/components/builtin/app-error-94dd6bc2d60a8443.js +0 -1
  194. package/.next/static/chunks/next/dist/client/components/builtin/forbidden-94dd6bc2d60a8443.js +0 -1
  195. package/.next/static/chunks/next/dist/client/components/builtin/not-found-94dd6bc2d60a8443.js +0 -1
  196. package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-94dd6bc2d60a8443.js +0 -1
  197. package/.next/static/css/b4ec7103106db91b.css +0 -3
  198. package/.next/static/iEqTV0p0ZoW-sr0JEYokX/_buildManifest.js +0 -1
  199. /package/.next/standalone/.next/static/{iEqTV0p0ZoW-sr0JEYokX → ZVOWTwaHoyx4ZcNdVzldW}/_ssgManifest.js +0 -0
  200. /package/.next/static/{iEqTV0p0ZoW-sr0JEYokX → ZVOWTwaHoyx4ZcNdVzldW}/_ssgManifest.js +0 -0
package/README.md CHANGED
@@ -71,7 +71,7 @@ It's the difference between *driving* an agent and *operating a dock* of them.
71
71
 
72
72
  🛂 **Opt-in autonomous triage** — per repo, let Drydock label incoming issues (deterministic keyword classifier, whitelist-only output) and auto-process the ones that are *ready* and not blocked. Off by default; gated by author association for public repos, a per-issue attempt limit, and all the usual cost/concurrency limits. Never auto-merges.
73
73
 
74
- 🔧 **CI babysitting & auto-merge** — polls `gh pr checks`, merges on green, and on red resumes the session with a CI-fix prompt (up to **3 retries**), then files a follow-up issue and hands off.
74
+ 🔧 **CI babysitting & auto-merge** — polls `gh pr checks`, merges on green, and on red resumes the session with a CI-fix prompt (up to **3 retries**), then files a follow-up issue and hands off. The failed log is classified by failure type (test, type error, lint, build, dependency, timeout, flaky) and reduced to a focused, line-capped evidence slice so the fix prompt targets the actual failure.
75
75
 
76
76
  🩹 **Opt-in CI auto-heal** — per repo, turn the failure path into a structured classify → fix → verify loop: failing checks are bucketed (healable / external / flaky / unknown), only healable ones get a targeted fix, and each attempt is verified for a real, improving change. External and AI-review checks are never code-healed. Hard budgets (per-session and per-fingerprint attempts, a cooldown, and a concurrency cap) keep it bounded. Off by default; never auto-merges.
77
77
 
@@ -79,17 +79,29 @@ It's the difference between *driving* an agent and *operating a dock* of them.
79
79
 
80
80
  🧩 **Opt-in issue decomposition** — per repo, split a large issue ("fix these 5 bugs", "implement X with A/B/C") into ordered, tracked subtasks. A deterministic heuristic handles GitHub task lists (`- [ ]`) and "Bug N —" headings for free; prose falls back to a one-shot agent. Decomposition is idempotent (keyed on the issue body hash, redone only when the body changes), subtasks are surfaced in the agent prompt and worked in order, and progress is reflected on the issue and in the UI. Off by default. See [ADR 020](docs/adr/020-issue-decomposition.md).
81
81
 
82
+ 🔎 **Opt-in post-PR verification** — per repo, run a **read-only** pass right after a PR opens that checks whether the diff actually satisfies the issue and each decomposed subtask. A one-shot agent is given the issue, its subtasks, and the (length-capped) diff and returns a strict JSON verdict (`done` / `pending` / `deferred`) per subtask; the result updates subtask status and posts a verification summary flagging what remains. It runs in a throwaway dir with a tight timeout, and on any failure (non-zero exit, non-JSON output, exception) leaves status unchanged — never corrupting state. Off by default; never auto-merges. See [ADR 027](docs/adr/027-post-pr-verification.md).
83
+
82
84
  🚀 **Opt-in post-merge deployment healing** — per repo, watch a merged PR's deployment via pluggable platform adapters (Vercel and Railway today; adding Netlify/Fly/Render is one adapter, no core changes). The platform is auto-detected from repo config or set explicitly. The merged commit's deployment is polled with bounded delay/interval/timeout budgets, and on failure the logs are captured and a follow-up **fix PR** is opened for a human to review. Sessions are surfaced in the repo's Deployments panel. Off by default; never auto-merges. See [ADR 021](docs/adr/021-post-merge-deployment-healing.md).
83
85
 
86
+ 🏷️ **Opt-in release management** — per repo, extend autonomy past merge to shipping: evaluate the PRs merged since the last tag, decide whether a release is warranted and the semver bump, generate notes, and publish a release. The auto path is **idempotent** (one run per merge commit, never a duplicate release for a tag) and a failed run is retryable; a **dry-run preview** shows the proposed version and included PRs with no side effects; a **manual publish** forces a release through the same evaluation pipeline. Gated by both a global kill-switch and a per-repo opt-in, off by default; releases at the default-branch tip and never auto-merges. See [ADR 028](docs/adr/028-release-management.md).
87
+
84
88
  ⚖️ **Rate-limit budgeting** — a priority-aware governor meters every GitHub call: the background sweep runs at *low* priority and yields once the budget drops below a reserve fraction, while interactive actions stay *high*; a hard floor stops anything from draining the budget to zero, a 429 backs off until reset, and unchanged issue lists are fetched with conditional ETag requests so they cost nothing. See [ADR 018](docs/adr/018-rate-limit-governor.md).
85
89
 
90
+ 💬 **Ask about this PR** — on a job's detail view, ask a free-text question ("why did this change X?", "is the failing test related?", "what's left to do?") and a **read-only** agent answers from a length-capped context bundle Drydock already has: PR metadata, check pass/fail state, a review-feedback summary, the recent activity log, and the PR diff. Each question is persisted with a visible lifecycle (`answering → answered | error`), scoped to the PR it was asked about, and empty or failed responses are recorded as an error rather than crashing.
91
+
92
+ 📝 **Per-repo agent instructions** — give each watched repo free-text guidance (coding conventions, "always run `pnpm test`", "don't touch `legacy/`", preferred PR style) from the automation panel. The text is injected into the issue-work prompt as a dedicated, length-capped section, so you steer agent behavior per project without editing global prompts or code. Empty by default; an unset value leaves the prompt unchanged.
93
+
86
94
  📡 **Live logs over SSE** — the agent's NDJSON output is parsed incrementally, persisted, and streamed to the browser in real time.
87
95
 
88
- 💸 **Cost tracking** — per-job and aggregate spend from the agent's reported `total_cost_usd` (or estimated from tokens), with a **daily cost limit** that gates the driver loop.
96
+ 💸 **Cost tracking** — per-job and aggregate spend from the agent's reported `total_cost_usd` (or estimated from tokens), with a **daily cost limit** that gates the driver loop and an optional **per-job cost ceiling** that aborts a single runaway session mid-stream (global default + per-repo override; off when unset). Spend is **exportable** to CSV or JSON from the cost dashboard — per-job line items or aggregates by repo/model, scoped to a date range and repo, with totals that reconcile with the dashboard.
89
97
 
90
98
  ⏯️ **Global pause & per-repo controls** — pause everything from the navbar, pick an agent and model per repo, toggle serial vs. parallel processing, and customize the queue label.
91
99
 
92
- 🔔 **External notifications** — get pinged on Telegram, Slack (incoming webhook) and email (SMTP) for the lifecycle events you care about (job needs human, job failed, PR opened, PR merged, daily cost limit reached, automation paused/draining). Each channel is configured independently, every event has a per-event opt-in, and a one-click test button verifies setup. Delivery is best-effort and never blocks the loop; secrets are redacted from logs. See [ADR 024](docs/adr/024-external-notifications.md).
100
+ 🪝 **Webhook-driven issue sync** — opt in per repo to receive issue events instead of waiting for the next poll. Set a secret on a repo and Drydock exposes a signature-verified receiver (`/api/webhooks/<id>`); a validated GitHub/GitLab issue event triggers a targeted, debounced sync so new issues surface near-instantly. Polling stays on as the default fallback and shares the same idempotent reconcile, so a change is never double-processed. Since Drydock binds `127.0.0.1`, expose the URL through a tunnel (e.g. `cloudflared`, `ngrok`). See [ADR 029](docs/adr/029-webhook-issue-sync.md).
101
+
102
+ 🔔 **External notifications** — get pinged on Telegram, Slack (incoming webhook) and email (SMTP) for the lifecycle events you care about (job needs human, job failed, PR opened, PR merged, release published, daily cost limit reached, automation paused/draining). Each channel is configured independently, every event has a per-event opt-in, and a one-click test button verifies setup. Delivery is best-effort and never blocks the loop; secrets are redacted from logs. See [ADR 024](docs/adr/024-external-notifications.md).
103
+
104
+ 🆙 **Update-available notice** — a passive, dismissible navbar banner appears when a newer Drydock release is published. The check queries the latest stable GitHub release (drafts/prereleases skipped), is cached for an hour, and dedupes concurrent checks onto a single upstream call; any network or parse error advertises no update, so a transient hiccup never raises a false alarm. Global installs get a `drydock update` hint.
93
105
 
94
106
  📐 **ADR review queue** — a file watcher surfaces new `docs/adr/*.md` decisions for approve/reject.
95
107
 
@@ -114,7 +126,8 @@ flowchart LR
114
126
 
115
127
  A single orchestrator boots with the server process (`src/instrumentation.ts`). On start it
116
128
  runs crash recovery (requeue orphaned `working` jobs, park CI-babysitting states as
117
- `interrupted`) and installs graceful-shutdown handlers. The **driver loop** atomically claims
129
+ `interrupted`, and reap orphaned git worktrees left by a hard crash) and installs
130
+ graceful-shutdown handlers. The **driver loop** atomically claims
118
131
  the next eligible queued job with a lease (respecting per-repo priority, the daily cost limit,
119
132
  the global pause, and serial-vs-parallel settings), heartbeats it while it runs, then releases
120
133
  the lease once it settles.
@@ -235,8 +248,8 @@ Drydock is configured at runtime from the **Settings** page and per-repo control
235
248
  ¹ A source checkout (`pnpm dev`/`pnpm start`) defaults `DRYDOCK_DB` to `data/drydock.db` in the
236
249
  project; the `drydock` launcher defaults it to `~/.drydock/drydock.db`.
237
250
 
238
- **Settings (global):** pause switch · daily cost limit · log retention (days) · `claude`/`gh` CLI paths · notification channels (Telegram / Slack / email) and per-event opt-in.
239
- **Per repo:** platform (GitHub / GitLab, with base URL + token for GitLab) · default model · serial vs. parallel processing · queue label (default `drydock:queue`).
251
+ **Settings (global):** pause switch · release management kill-switch (master on/off for the opt-in release pipeline) · daily cost limit · max job cost (per-job USD ceiling that aborts a runaway session mid-stream; 0 = off) · log retention (days) · max job minutes (per-agent session timeout) · max CI wait minutes (how long the babysitter waits for checks to settle before escalating to needs-human) · `claude`/`gh` CLI paths · notification channels (Telegram / Slack / email) and per-event opt-in.
252
+ **Per repo:** platform (GitHub / GitLab, with base URL + token for GitLab) · default model · serial vs. parallel processing · queue label (default `drydock:queue`) · optional job/CI timeout overrides.
240
253
 
241
254
  ## Screens
242
255
 
@@ -247,7 +260,7 @@ project; the `drydock` launcher defaults it to `~/.drydock/drydock.db`.
247
260
  | `/jobs/[id]` | Job detail — live streaming log, cost & tokens |
248
261
  | `/prompts` | Versioned prompt editor |
249
262
  | `/adrs` | ADR review queue |
250
- | `/costs` | Cost dashboard — daily, by model, top jobs |
263
+ | `/costs` | Cost dashboard — daily, by model, top jobs, CSV/JSON export |
251
264
  | `/settings` | Global settings |
252
265
 
253
266
  ## Project layout
@@ -348,10 +361,10 @@ UI: they refuse while draining, globally paused, or over the daily/per-repo cost
348
361
 
349
362
  ## Roadmap
350
363
 
351
- - [ ] Parallel multi-repo dashboards at a glance
352
- - [ ] Webhook-driven issue sync (vs. polling)
353
- - [ ] Richer CI failure classification & targeted fix prompts
354
- - [ ] Exportable cost reports
364
+ - [x] Parallel multi-repo dashboards at a glance
365
+ - [x] Webhook-driven issue sync (vs. polling)
366
+ - [x] Richer CI failure classification & targeted fix prompts
367
+ - [x] Exportable cost reports
355
368
 
356
369
  Have an idea? [Open an issue](https://github.com/NilsR0711/drydock/issues).
357
370
 
package/bin/drydock.mjs CHANGED
@@ -114,6 +114,21 @@ Options:
114
114
  Data is stored in ~/.drydock (override with DRYDOCK_DATA_DIR); the database is
115
115
  created and migrated automatically on first start.`;
116
116
 
117
+ /**
118
+ * Classify how Drydock was installed from its package directory. Drives the
119
+ * in-dashboard update notice (issue #58): a global install can self-update via
120
+ * `drydock update`, whereas an npx run or a dev checkout cannot.
121
+ *
122
+ * @param {string} packageRoot Absolute path to the installed package directory.
123
+ * @returns {"global" | "npx" | "local"}
124
+ */
125
+ export function detectInstallKind(packageRoot) {
126
+ const normalized = packageRoot.replace(/\\/g, "/");
127
+ if (normalized.includes("/_npx/")) return "npx";
128
+ if (normalized.includes("/node_modules/")) return "global";
129
+ return "local";
130
+ }
131
+
117
132
  /** The command that updates a global install to the latest published version. */
118
133
  export function updateCommand() {
119
134
  return { command: "npm", args: ["install", "--global", "@nilsr0711/drydock@latest"] };
@@ -185,6 +200,10 @@ async function serve({ host, port, open }) {
185
200
  PORT: String(port),
186
201
  DRYDOCK_DB: resolveDbPath(),
187
202
  DRYDOCK_MIGRATIONS: join(PACKAGE_ROOT, "drizzle"),
203
+ // Surface the running version and install kind to the dashboard so it can
204
+ // show an "update available" notice without bundling package.json (#58).
205
+ DRYDOCK_VERSION: readVersion(),
206
+ DRYDOCK_INSTALL_KIND: detectInstallKind(PACKAGE_ROOT),
188
207
  };
189
208
 
190
209
  const url = `http://${host}:${port}`;
@@ -0,0 +1 @@
1
+ ALTER TABLE `repos` ADD `max_job_minutes` integer;
@@ -0,0 +1 @@
1
+ ALTER TABLE `repos` ADD `max_ci_wait_minutes` integer;
@@ -0,0 +1 @@
1
+ ALTER TABLE `repos` ADD `verify_pr` integer DEFAULT false NOT NULL;
@@ -0,0 +1,14 @@
1
+ CREATE TABLE `pr_questions` (
2
+ `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
3
+ `job_id` integer NOT NULL,
4
+ `pr_number` integer NOT NULL,
5
+ `question` text NOT NULL,
6
+ `answer` text,
7
+ `status` text DEFAULT 'answering' NOT NULL,
8
+ `error_message` text,
9
+ `created_at` integer DEFAULT (unixepoch()) NOT NULL,
10
+ `updated_at` integer DEFAULT (unixepoch()) NOT NULL,
11
+ FOREIGN KEY (`job_id`) REFERENCES `jobs`(`id`) ON UPDATE no action ON DELETE cascade
12
+ );
13
+ --> statement-breakpoint
14
+ CREATE INDEX `pr_questions_job_idx` ON `pr_questions` (`job_id`);
@@ -0,0 +1 @@
1
+ ALTER TABLE `repos` ADD `agent_instructions` text;
@@ -0,0 +1 @@
1
+ ALTER TABLE `repos` ADD `max_job_cost_usd` real;
@@ -0,0 +1,22 @@
1
+ CREATE TABLE `release_runs` (
2
+ `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
3
+ `repo_id` integer NOT NULL,
4
+ `mode` text DEFAULT 'auto' NOT NULL,
5
+ `trigger_pr_number` integer,
6
+ `trigger_sha` text,
7
+ `status` text DEFAULT 'detected' NOT NULL,
8
+ `bump` text,
9
+ `from_tag` text,
10
+ `tag` text,
11
+ `title` text,
12
+ `notes` text,
13
+ `pr_numbers` text DEFAULT '[]' NOT NULL,
14
+ `error_message` text,
15
+ `created_at` integer DEFAULT (unixepoch()) NOT NULL,
16
+ `updated_at` integer DEFAULT (unixepoch()) NOT NULL,
17
+ FOREIGN KEY (`repo_id`) REFERENCES `repos`(`id`) ON UPDATE no action ON DELETE cascade
18
+ );
19
+ --> statement-breakpoint
20
+ CREATE INDEX `release_runs_repo_idx` ON `release_runs` (`repo_id`);--> statement-breakpoint
21
+ CREATE UNIQUE INDEX `release_runs_trigger_unique` ON `release_runs` (`repo_id`,`trigger_sha`) WHERE "release_runs"."trigger_sha" is not null;--> statement-breakpoint
22
+ ALTER TABLE `repos` ADD `release_enabled` integer DEFAULT false NOT NULL;
@@ -0,0 +1 @@
1
+ ALTER TABLE `repos` ADD `webhook_secret` text;