@wardby/cli 0.3.0 → 0.4.1

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 (275) hide show
  1. package/.env.example +22 -3
  2. package/README.md +11 -9
  3. package/dist/cli-help.d.ts +1 -1
  4. package/dist/cli-help.js +1 -0
  5. package/dist/cli.js +68 -10
  6. package/dist/coding/base-commit.d.ts +6 -0
  7. package/dist/coding/base-commit.js +12 -0
  8. package/dist/coding/protocol.d.ts +17 -1
  9. package/dist/coding/protocol.js +17 -6
  10. package/dist/coding/provider.d.ts +6 -1
  11. package/dist/coding/provider.js +16 -9
  12. package/dist/config/providers.d.ts +25 -0
  13. package/dist/config/providers.js +71 -0
  14. package/dist/core/attribution.d.ts +101 -0
  15. package/dist/core/attribution.js +208 -0
  16. package/dist/core/budget-groups.d.ts +17 -10
  17. package/dist/core/budget-groups.js +15 -12
  18. package/dist/core/coding-queue.d.ts +3 -0
  19. package/dist/core/coding-queue.js +6 -2
  20. package/dist/core/coding-service-status.d.ts +11 -0
  21. package/dist/core/coding-service-status.js +17 -0
  22. package/dist/core/cost-report.d.ts +88 -0
  23. package/dist/core/cost-report.js +248 -0
  24. package/dist/core/dispatch.d.ts +42 -2
  25. package/dist/core/dispatch.js +144 -26
  26. package/dist/core/engine-native.js +17 -4
  27. package/dist/core/glob.d.ts +10 -0
  28. package/dist/core/glob.js +33 -0
  29. package/dist/core/host-events.d.ts +24 -1
  30. package/dist/core/host-events.js +341 -1
  31. package/dist/core/host-status.d.ts +19 -2
  32. package/dist/core/host-status.js +47 -30
  33. package/dist/core/issue-bridge.d.ts +60 -0
  34. package/dist/core/issue-bridge.js +189 -0
  35. package/dist/core/issue-dedupe.d.ts +70 -0
  36. package/dist/core/issue-dedupe.js +255 -0
  37. package/dist/core/issue-events.d.ts +42 -0
  38. package/dist/core/issue-events.js +155 -0
  39. package/dist/core/issue-status.d.ts +29 -0
  40. package/dist/core/issue-status.js +241 -0
  41. package/dist/core/issue-tracker-tools.d.ts +64 -0
  42. package/dist/core/issue-tracker-tools.js +850 -0
  43. package/dist/core/model-usage.d.ts +10 -0
  44. package/dist/core/model-usage.js +24 -0
  45. package/dist/core/reconciler.d.ts +8 -4
  46. package/dist/core/reconciler.js +15 -4
  47. package/dist/core/review-host-tools.js +10 -3
  48. package/dist/core/run-pricing.d.ts +61 -0
  49. package/dist/core/run-pricing.js +56 -0
  50. package/dist/core/runner.d.ts +5 -2
  51. package/dist/core/runner.js +178 -27
  52. package/dist/core/scheduler.d.ts +4 -1
  53. package/dist/core/scheduler.js +3 -2
  54. package/dist/core/self-defects.d.ts +80 -0
  55. package/dist/core/self-defects.js +180 -0
  56. package/dist/core/tool-names.js +3 -0
  57. package/dist/core/webhooks.d.ts +9 -1
  58. package/dist/core/webhooks.js +19 -1
  59. package/dist/env.js +6 -1
  60. package/dist/generated/prisma/browser.d.ts +66 -0
  61. package/dist/generated/prisma/client.d.ts +66 -0
  62. package/dist/generated/prisma/commonInputTypes.d.ts +122 -52
  63. package/dist/generated/prisma/enums.d.ts +7 -0
  64. package/dist/generated/prisma/enums.js +6 -0
  65. package/dist/generated/prisma/internal/class.d.ts +99 -0
  66. package/dist/generated/prisma/internal/class.js +4 -4
  67. package/dist/generated/prisma/internal/prismaNamespace.d.ts +826 -1
  68. package/dist/generated/prisma/internal/prismaNamespace.js +135 -2
  69. package/dist/generated/prisma/internal/prismaNamespaceBrowser.d.ts +142 -0
  70. package/dist/generated/prisma/internal/prismaNamespaceBrowser.js +135 -2
  71. package/dist/generated/prisma/models/Agent.d.ts +389 -1
  72. package/dist/generated/prisma/models/AgentIssueProject.d.ts +1838 -0
  73. package/dist/generated/prisma/models/AgentIssueProject.js +1 -0
  74. package/dist/generated/prisma/models/AgentRepository.d.ts +1 -1
  75. package/dist/generated/prisma/models/AuthUser.d.ts +1 -1
  76. package/dist/generated/prisma/models/CodingProxySession.d.ts +73 -1
  77. package/dist/generated/prisma/models/CodingRun.d.ts +130 -1
  78. package/dist/generated/prisma/models/CodingRunServiceStatus.d.ts +1404 -0
  79. package/dist/generated/prisma/models/CodingRunServiceStatus.js +1 -0
  80. package/dist/generated/prisma/models/IssueFingerprint.d.ts +1183 -0
  81. package/dist/generated/prisma/models/IssueFingerprint.js +1 -0
  82. package/dist/generated/prisma/models/IssuePullRequest.d.ts +1255 -0
  83. package/dist/generated/prisma/models/IssuePullRequest.js +1 -0
  84. package/dist/generated/prisma/models/ModelCatalogEntry.d.ts +1322 -0
  85. package/dist/generated/prisma/models/ModelCatalogEntry.js +1 -0
  86. package/dist/generated/prisma/models/Run.d.ts +933 -1
  87. package/dist/generated/prisma/models/RunAttribution.d.ts +1259 -0
  88. package/dist/generated/prisma/models/RunAttribution.js +1 -0
  89. package/dist/generated/prisma/models/RunIssueStatus.d.ts +1199 -0
  90. package/dist/generated/prisma/models/RunIssueStatus.js +1 -0
  91. package/dist/generated/prisma/models/RunModelUsage.d.ts +1316 -0
  92. package/dist/generated/prisma/models/RunModelUsage.js +1 -0
  93. package/dist/generated/prisma/models/WorkItem.d.ts +1408 -0
  94. package/dist/generated/prisma/models/WorkItem.js +1 -0
  95. package/dist/generated/prisma/models.d.ts +9 -0
  96. package/dist/help-index.json +355 -16
  97. package/dist/import/neutral-schema.d.ts +16 -16
  98. package/dist/knowledge/check.d.ts +13 -0
  99. package/dist/knowledge/check.js +69 -0
  100. package/dist/knowledge/cli.d.ts +14 -0
  101. package/dist/knowledge/cli.js +67 -0
  102. package/dist/knowledge/concept.d.ts +54 -0
  103. package/dist/knowledge/concept.js +78 -0
  104. package/dist/knowledge/note.d.ts +11 -0
  105. package/dist/knowledge/note.js +39 -0
  106. package/dist/knowledge/relevance.d.ts +11 -0
  107. package/dist/knowledge/relevance.js +14 -0
  108. package/dist/knowledge/span-hash.d.ts +3 -0
  109. package/dist/knowledge/span-hash.js +16 -0
  110. package/dist/mcp/auth/access.d.ts +4 -2
  111. package/dist/mcp/auth/ownership.d.ts +9 -9
  112. package/dist/mcp/auth/resource-server.d.ts +3 -1
  113. package/dist/mcp/auth/resource-server.js +18 -3
  114. package/dist/mcp/auth/self-hosted/credentials.d.ts +3 -3
  115. package/dist/mcp/auth/self-hosted/session.d.ts +5 -5
  116. package/dist/mcp/context.d.ts +3 -0
  117. package/dist/mcp/host-events/deliveries.d.ts +9 -0
  118. package/dist/mcp/host-events/deliveries.js +17 -0
  119. package/dist/mcp/host-events/github-ingress.d.ts +4 -2
  120. package/dist/mcp/host-events/github-ingress.js +4 -13
  121. package/dist/mcp/host-events/jira-ingress.d.ts +29 -0
  122. package/dist/mcp/host-events/jira-ingress.js +92 -0
  123. package/dist/mcp/index.d.ts +2 -0
  124. package/dist/mcp/index.js +87 -9
  125. package/dist/mcp/server.js +5 -2
  126. package/dist/mcp/tools/agents.js +74 -3
  127. package/dist/mcp/tools/cost-report.d.ts +8 -0
  128. package/dist/mcp/tools/cost-report.js +60 -0
  129. package/dist/mcp/tools/issue-projects.d.ts +2 -0
  130. package/dist/mcp/tools/issue-projects.js +238 -0
  131. package/dist/mcp/tools/model-catalog.d.ts +22 -0
  132. package/dist/mcp/tools/model-catalog.js +423 -0
  133. package/dist/mcp/tools/repositories.js +2 -1
  134. package/dist/mcp/tools/tools.d.ts +2 -2
  135. package/dist/mcp/tools/trigger.js +33 -5
  136. package/dist/mcp/transport/streamable-http.d.ts +5 -0
  137. package/dist/mcp/transport/streamable-http.js +23 -1
  138. package/dist/mcp/webhooks/ingress.d.ts +2 -1
  139. package/dist/mcp/webhooks/ingress.js +9 -2
  140. package/dist/providers/auth/self-hosted.d.ts +8 -1
  141. package/dist/providers/auth/self-hosted.js +39 -2
  142. package/dist/providers/coding-proxy/memory-ledger.d.ts +1 -1
  143. package/dist/providers/coding-proxy/memory-ledger.js +10 -1
  144. package/dist/providers/coding-proxy/metering.d.ts +2 -1
  145. package/dist/providers/coding-proxy/metering.js +13 -2
  146. package/dist/providers/coding-proxy/prisma-ledger.js +59 -6
  147. package/dist/providers/coding-proxy/proxy.d.ts +12 -2
  148. package/dist/providers/coding-proxy/proxy.js +92 -30
  149. package/dist/providers/coding-proxy/types.d.ts +18 -1
  150. package/dist/providers/coding-proxy/types.js +12 -1
  151. package/dist/providers/engine/types.d.ts +19 -0
  152. package/dist/providers/executor/composition.js +9 -1
  153. package/dist/providers/executor/container.d.ts +30 -2
  154. package/dist/providers/executor/container.js +98 -17
  155. package/dist/providers/executor/dbos.d.ts +2 -0
  156. package/dist/providers/executor/dbos.js +7 -5
  157. package/dist/providers/executor/routing.d.ts +6 -0
  158. package/dist/providers/executor/routing.js +5 -0
  159. package/dist/providers/executor/types.d.ts +12 -0
  160. package/dist/providers/issue-tracker/adf.d.ts +31 -0
  161. package/dist/providers/issue-tracker/adf.js +181 -0
  162. package/dist/providers/issue-tracker/index.d.ts +5 -0
  163. package/dist/providers/issue-tracker/index.js +12 -0
  164. package/dist/providers/issue-tracker/jira-client.d.ts +41 -0
  165. package/dist/providers/issue-tracker/jira-client.js +151 -0
  166. package/dist/providers/issue-tracker/jira-events.d.ts +3 -0
  167. package/dist/providers/issue-tracker/jira-events.js +98 -0
  168. package/dist/providers/issue-tracker/jira.d.ts +116 -0
  169. package/dist/providers/issue-tracker/jira.js +502 -0
  170. package/dist/providers/issue-tracker/types.d.ts +269 -0
  171. package/dist/providers/issue-tracker/types.js +16 -0
  172. package/dist/providers/jobs/docker.d.ts +5 -1
  173. package/dist/providers/jobs/docker.js +61 -33
  174. package/dist/providers/jobs/kubernetes.d.ts +3 -0
  175. package/dist/providers/jobs/kubernetes.js +44 -4
  176. package/dist/providers/jobs/service-state.d.ts +22 -0
  177. package/dist/providers/jobs/service-state.js +17 -0
  178. package/dist/providers/llm/anthropic.d.ts +3 -3
  179. package/dist/providers/llm/anthropic.js +3 -9
  180. package/dist/providers/llm/bedrock.d.ts +3 -3
  181. package/dist/providers/llm/bedrock.js +3 -9
  182. package/dist/providers/llm/catalog-lookup.d.ts +10 -0
  183. package/dist/providers/llm/catalog-lookup.js +15 -0
  184. package/dist/providers/llm/catalog-shipped.d.ts +18 -0
  185. package/dist/providers/llm/catalog-shipped.js +197 -0
  186. package/dist/providers/llm/catalog-store.d.ts +58 -0
  187. package/dist/providers/llm/catalog-store.js +138 -0
  188. package/dist/providers/llm/catalog-types.d.ts +66 -0
  189. package/dist/providers/llm/catalog-types.js +64 -0
  190. package/dist/providers/llm/catalog.d.ts +61 -0
  191. package/dist/providers/llm/catalog.js +147 -0
  192. package/dist/providers/llm/claude-provider.d.ts +10 -14
  193. package/dist/providers/llm/claude-provider.js +11 -6
  194. package/dist/providers/llm/index.d.ts +9 -6
  195. package/dist/providers/llm/index.js +8 -5
  196. package/dist/providers/llm/openai.d.ts +14 -5
  197. package/dist/providers/llm/openai.js +24 -14
  198. package/dist/providers/llm/pricing-core.d.ts +5 -3
  199. package/dist/providers/llm/registration.js +8 -12
  200. package/dist/providers/llm/routing.d.ts +18 -17
  201. package/dist/providers/llm/routing.js +40 -24
  202. package/dist/providers/review-host/github-events.js +47 -1
  203. package/dist/providers/review-host/github.js +7 -6
  204. package/dist/providers/review-host/types.d.ts +25 -0
  205. package/dist/providers/vcs/git.js +2 -22
  206. package/dist/providers/vcs/github.d.ts +20 -0
  207. package/dist/providers/vcs/github.js +28 -2
  208. package/dist/providers/vcs/types.d.ts +6 -0
  209. package/dist/quickstart/index.d.ts +8 -0
  210. package/dist/quickstart/index.js +34 -34
  211. package/dist/serve.js +8 -2
  212. package/dist/viewer/api-schema.d.ts +2757 -0
  213. package/dist/viewer/api-schema.js +165 -0
  214. package/dist/viewer/build-schemas.d.ts +2 -0
  215. package/dist/viewer/build-schemas.js +18 -0
  216. package/dist/viewer/event-bus.d.ts +38 -0
  217. package/dist/viewer/event-bus.js +232 -0
  218. package/dist/viewer/graph.d.ts +40 -0
  219. package/dist/viewer/graph.js +243 -0
  220. package/dist/viewer/http.d.ts +30 -0
  221. package/dist/viewer/http.js +133 -0
  222. package/dist/viewer/run-detail.d.ts +4 -0
  223. package/dist/viewer/run-detail.js +61 -0
  224. package/dist/wardby-bin.js +5 -0
  225. package/docs/README.md +10 -0
  226. package/docs/agent-recipes.md +383 -0
  227. package/docs/code-review-agents.md +29 -2
  228. package/docs/coding-agent-setup.md +3 -0
  229. package/docs/coding-worker-isolation.md +39 -5
  230. package/docs/getting-started-gke.md +28 -11
  231. package/docs/getting-started-identity-provider.md +49 -38
  232. package/docs/getting-started.md +14 -0
  233. package/docs/jira-agents.md +649 -0
  234. package/docs/knowledge.md +387 -0
  235. package/docs/models.md +221 -0
  236. package/docs/security-deployment.md +19 -9
  237. package/docs/viewer-api.md +142 -0
  238. package/help/admin-viewer.md +39 -0
  239. package/help/agent-recipes.md +173 -0
  240. package/help/architecture-agent.md +189 -0
  241. package/help/builder-agent.md +80 -0
  242. package/help/code-review-agents.md +6 -0
  243. package/help/cost-attribution.md +67 -0
  244. package/help/creating-agents.md +22 -0
  245. package/help/deploy-gke.md +6 -0
  246. package/help/errors/model-unavailable.md +63 -0
  247. package/help/getting-started.md +1 -0
  248. package/help/github.md +18 -0
  249. package/help/identity-and-access.md +8 -3
  250. package/help/jira.md +135 -0
  251. package/help/knowledge.md +47 -0
  252. package/help/models.md +90 -0
  253. package/help/operating-agents.md +7 -1
  254. package/help/troubleshooting/budgets.md +6 -0
  255. package/package.json +5 -2
  256. package/prisma/migrations/20260930000000_jira_issue_projects/migration.sql +34 -0
  257. package/prisma/migrations/20261001000000_jira_phase2_allowlists/migration.sql +3 -0
  258. package/prisma/migrations/20261001010000_jira_link_types_allowlist/migration.sql +2 -0
  259. package/prisma/migrations/20261002000000_jira_coding_bridge/migration.sql +28 -0
  260. package/prisma/migrations/20261002010000_jira_issue_creation/migration.sql +25 -0
  261. package/prisma/migrations/20261003000000_issue_cost_attribution/migration.sql +56 -0
  262. package/prisma/migrations/20261003010000_coding_run_service_status/migration.sql +23 -0
  263. package/prisma/migrations/20261003020000_viewer_notify/migration.sql +54 -0
  264. package/prisma/migrations/20261003030000_viewer_notify_fixes/migration.sql +47 -0
  265. package/prisma/migrations/20261003040000_viewer_indexes/migration.sql +12 -0
  266. package/prisma/migrations/20261004000000_model_catalog/migration.sql +26 -0
  267. package/prisma/schema.prisma +258 -2
  268. package/dist/mcp/tools/models.d.ts +0 -8
  269. package/dist/mcp/tools/models.js +0 -15
  270. package/dist/providers/llm/pricing-anthropic.d.ts +0 -14
  271. package/dist/providers/llm/pricing-anthropic.js +0 -48
  272. package/dist/providers/llm/pricing-bedrock-claude.d.ts +0 -20
  273. package/dist/providers/llm/pricing-bedrock-claude.js +0 -46
  274. package/dist/providers/llm/pricing.d.ts +0 -30
  275. package/dist/providers/llm/pricing.js +0 -74
@@ -7,6 +7,10 @@ import { normalizeGitHubRepository } from "../../coding/protocol.js";
7
7
  const PR_ACTIONS = new Set(["opened", "synchronize", "reopened", "ready_for_review"]);
8
8
  const TRUSTED_ASSOCIATIONS = new Set(["OWNER", "MEMBER", "COLLABORATOR"]);
9
9
  const SAFE_SHA = /^[0-9a-f]{40}$/;
10
+ // GitHub's push payload includes at most 2048 commits.
11
+ const PUSH_MAX_COMMITS = 2048;
12
+ const SAFE_BRANCH = /^[A-Za-z0-9._/-]{1,100}$/;
13
+ const PUSH_MAX_PATHS = 1000;
10
14
  /** Mirrors the runId shape validated in providers/vcs (github.ts, git.ts). */
11
15
  const SAFE_RUN_ID = /^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/;
12
16
  /** The hidden marker a coding run writes at the top of every PR it opens (providers/vcs/github.ts); legacy prefix too. */
@@ -75,10 +79,52 @@ export function normalizeGitHubEvent(eventName, payload, app) {
75
79
  const repository = repositoryOf(p);
76
80
  if (!repository)
77
81
  return null;
82
+ if (eventName === "push") {
83
+ const defaultBranch = obj(p.repository)?.default_branch;
84
+ const { before, after } = p;
85
+ if (typeof defaultBranch !== "string" || !SAFE_BRANCH.test(defaultBranch))
86
+ return null;
87
+ if (p.ref !== `refs/heads/${defaultBranch}` || p.deleted === true)
88
+ return null;
89
+ if (typeof before !== "string" || typeof after !== "string")
90
+ return null;
91
+ if (!SAFE_SHA.test(before) || !SAFE_SHA.test(after) || /^0+$/.test(after))
92
+ return null;
93
+ const commits = Array.isArray(p.commits) ? p.commits : [];
94
+ const paths = new Set();
95
+ for (const commit of commits) {
96
+ const c = obj(commit);
97
+ for (const list of [c?.added, c?.modified, c?.removed]) {
98
+ if (Array.isArray(list))
99
+ for (const path of list)
100
+ if (typeof path === "string")
101
+ paths.add(path);
102
+ }
103
+ }
104
+ return {
105
+ kind: "push",
106
+ provider: "github",
107
+ repository,
108
+ branch: defaultBranch,
109
+ before,
110
+ after,
111
+ changedPaths: [...paths].sort().slice(0, PUSH_MAX_PATHS),
112
+ // GitHub includes at most 2048 commits, so a full list may be truncated.
113
+ changedPathsComplete: commits.length < PUSH_MAX_COMMITS && paths.size <= PUSH_MAX_PATHS,
114
+ };
115
+ }
78
116
  if (eventName === "pull_request") {
117
+ const pr = obj(p.pull_request);
118
+ if (p.action === "closed") {
119
+ // No author or fork gate: this only marks issue/PR pairs wardby itself
120
+ // recorded for this repository and number, never acting on PR text.
121
+ const prNumber = int(pr?.number);
122
+ if (!prNumber)
123
+ return null;
124
+ return { kind: "pr_closed", provider: "github", repository, prNumber, merged: pr?.merged === true };
125
+ }
79
126
  if (typeof p.action !== "string" || !PR_ACTIONS.has(p.action))
80
127
  return null;
81
- const pr = obj(p.pull_request);
82
128
  const head = obj(pr?.head);
83
129
  const prNumber = int(pr?.number);
84
130
  const headSha = head?.sha;
@@ -335,6 +335,7 @@ export class GitHubReviewHost {
335
335
  endLine: window.startLine + slice.length - 1,
336
336
  truncated: window.startLine - 1 + slice.length < lines.length,
337
337
  content: slice.map((line, i) => `${window.startLine + i}: ${line}`).join("\n"),
338
+ text: slice.join("\n"),
338
339
  };
339
340
  });
340
341
  }
@@ -343,15 +344,15 @@ export class GitHubReviewHost {
343
344
  return this.withToken(repository, { contents: "read" }, async (get) => {
344
345
  const resolvedRef = ref ?? str(record(await (await get(base)).json()).default_branch);
345
346
  const tree = record(await (await get(`${base}/git/trees/${encodeURIComponent(resolvedRef)}?recursive=1`)).json());
346
- const files = list(tree.tree)
347
+ const entries = list(tree.tree)
347
348
  .filter((e) => e.type === "blob" && typeof e.path === "string" && e.path.startsWith(pathPrefix))
348
- .filter((e) => !VENDORED.test(str(e.path)) && !str(e.path).endsWith("package-lock.json"))
349
- .map((e) => `${str(e.path)} (${num(e.size)} bytes)`);
349
+ .filter((e) => !VENDORED.test(str(e.path)) && !str(e.path).endsWith("package-lock.json"));
350
350
  return {
351
351
  ref: resolvedRef,
352
- count: files.length,
353
- truncated: tree.truncated === true || files.length > MAX_LISTED_FILES,
354
- files: files.slice(0, MAX_LISTED_FILES),
352
+ count: entries.length,
353
+ truncated: tree.truncated === true || entries.length > MAX_LISTED_FILES,
354
+ files: entries.slice(0, MAX_LISTED_FILES).map((e) => `${str(e.path)} (${num(e.size)} bytes)`),
355
+ paths: entries.slice(0, MAX_LISTED_FILES).map((e) => str(e.path)),
355
356
  };
356
357
  });
357
358
  }
@@ -70,7 +70,10 @@ export type FileReadResult = {
70
70
  startLine: number;
71
71
  endLine: number;
72
72
  truncated: boolean;
73
+ /** The window with `N: ` line-number prefixes: the display form shown to agents. */
73
74
  content: string;
75
+ /** The same window without line-number prefixes, lines joined by "\n": for machine parsing. */
76
+ text: string;
74
77
  } | {
75
78
  kind: "directory";
76
79
  path: string;
@@ -83,7 +86,10 @@ export interface FileListView {
83
86
  ref: string;
84
87
  count: number;
85
88
  truncated: boolean;
89
+ /** Display strings (path plus size) shown to agents. */
86
90
  files: string[];
91
+ /** Bare repository paths, same order and length as `files`. */
92
+ paths: string[];
87
93
  }
88
94
  export interface InlineComment {
89
95
  path: string;
@@ -247,12 +253,31 @@ export type HostUserAuthorizerRegistry = Partial<Record<ReviewHostProvider, Host
247
253
  * provider payload into, for src/core/host-events.ts to route.
248
254
  */
249
255
  export type HostEvent = {
256
+ kind: "push";
257
+ provider: ReviewHostProvider;
258
+ repository: string;
259
+ /** The branch pushed (without refs/heads/), always the repository's default branch. */
260
+ branch: string;
261
+ before: string;
262
+ after: string;
263
+ /** Union of added/modified/removed paths over the payload's commits, deduplicated, at most 1000. */
264
+ changedPaths: string[];
265
+ /** False when GitHub truncated the commit list (> 20 commits) or paths were capped: treat every concept as in scope. */
266
+ changedPathsComplete: boolean;
267
+ } | {
250
268
  kind: "pr_updated";
251
269
  provider: ReviewHostProvider;
252
270
  repository: string;
253
271
  prNumber: number;
254
272
  headSha: string;
255
273
  isFork: boolean;
274
+ } | {
275
+ /** The PR was merged or closed; only drives bookkeeping on issue/PR pairs wardby recorded. */
276
+ kind: "pr_closed";
277
+ provider: ReviewHostProvider;
278
+ repository: string;
279
+ prNumber: number;
280
+ merged: boolean;
256
281
  } | {
257
282
  kind: "check_rerun";
258
283
  provider: ReviewHostProvider;
@@ -5,6 +5,7 @@ import { fileURLToPath } from "node:url";
5
5
  import { gitExcludePathspecs, normalizeCollectExclusions } from "../../coding/collect-exclude.js";
6
6
  import { WARDBY_PROTECTED_PATHS, isProtectedPathException, isWellFormedProtectedPath, protectedPathBody, protectsSomePath, } from "../../coding/protected-paths.js";
7
7
  import { ensurePrivateDirectory } from "../../core/private-directory.js";
8
+ import { globRegex } from "../../core/glob.js";
8
9
  import { MAX_REDACTED_SPAN, redactTokenShapedValues, normalizeGitHubRepository, normalizeGitRef, } from "../../coding/protocol.js";
9
10
  import { isSafeGitHubInstallationToken } from "./github.js";
10
11
  export const DEFAULT_MAX_CHANGED_FILES = 1_000;
@@ -149,28 +150,6 @@ function validateProtectedPath(value) {
149
150
  }
150
151
  return path;
151
152
  }
152
- function globRegex(pattern) {
153
- let source = "^";
154
- for (let index = 0; index < pattern.length; index += 1) {
155
- const character = pattern[index];
156
- if (character === "*") {
157
- if (pattern[index + 1] === "*") {
158
- source += ".*";
159
- index += 1;
160
- }
161
- else {
162
- source += "[^/]*";
163
- }
164
- }
165
- else if (character === "?") {
166
- source += "[^/]";
167
- }
168
- else {
169
- source += character.replace(/[|\\{}()[\]^$+?.]/g, "\\$&");
170
- }
171
- }
172
- return new RegExp(`${source}$`);
173
- }
174
153
  /** The fixed baseline, split once: its literal exceptions and its protected globs. */
175
154
  const WARDBY_UNPROTECTED = new Set(WARDBY_PROTECTED_PATHS.filter(isProtectedPathException).map(protectedPathBody));
176
155
  const WARDBY_PROTECTED = WARDBY_PROTECTED_PATHS.filter((pattern) => !isProtectedPathException(pattern)).map((pattern) => globRegex(pattern));
@@ -458,6 +437,7 @@ export class GitVcsProvider {
458
437
  tag: details?.tag,
459
438
  packages: details?.packages,
460
439
  packageRefusals: details?.packageRefusals,
440
+ ...(details?.issue ? { issue: details.issue } : {}),
461
441
  });
462
442
  return {
463
443
  outcome: prepared.continuation ? "pull_request_updated" : "pull_request_opened",
@@ -40,6 +40,19 @@ export interface PullRequestInput {
40
40
  * draft either way; unset (a fresh run), only a draft PR is accepted.
41
41
  */
42
42
  acceptReadyForReview?: boolean;
43
+ /**
44
+ * The originating issue-tracker issue (control-plane data, never model
45
+ * output). Provider-neutral: the title gets a `[KEY] ` prefix and the body a
46
+ * first line linking it ("Resolves <trackerName> issue …"). The key is
47
+ * re-validated here, a non-https `url` is dropped, and so is a `trackerName`
48
+ * that isn't a short plain name. The PR lookup keys on the hidden run
49
+ * marker, never the title.
50
+ */
51
+ issue?: {
52
+ key: string;
53
+ url?: string;
54
+ trackerName?: string;
55
+ };
43
56
  }
44
57
  export interface PullRequestResult {
45
58
  number: number;
@@ -95,6 +108,13 @@ export interface GitHubRepositoryAccess {
95
108
  type Fetch = typeof globalThis.fetch;
96
109
  /** Accepts bounded opaque installation tokens without allowing control characters. */
97
110
  export declare function isSafeGitHubInstallationToken(value: unknown): value is string;
111
+ /**
112
+ * Same shape as ISSUE_KEY (issue-tracker/types.ts); duplicated so the VCS layer
113
+ * stays free of issue-tracker imports (a test keeps the two in step). The
114
+ * executor validates with the shared one.
115
+ */
116
+ export declare const PR_ISSUE_KEY: RegExp;
117
+ export declare function pullRequestTitle(input: PullRequestInput): string;
98
118
  /** At most this many packages, and separately this many refusals, are listed
99
119
  * in a PR body; the rest are counted in a "…and N more" line pointing at
100
120
  * get_run, which returns the full list. */
@@ -33,8 +33,30 @@ export function isSafeGitHubInstallationToken(value) {
33
33
  Buffer.byteLength(value, "utf8") <= 512 &&
34
34
  /^[A-Za-z0-9._-]+$/.test(value));
35
35
  }
36
- function pullRequestTitle(input) {
37
- return input.tag ? `[${input.tag}] Wardby run ${input.runId}` : `Wardby run ${input.runId}`;
36
+ /**
37
+ * Same shape as ISSUE_KEY (issue-tracker/types.ts); duplicated so the VCS layer
38
+ * stays free of issue-tracker imports (a test keeps the two in step). The
39
+ * executor validates with the shared one.
40
+ */
41
+ export const PR_ISSUE_KEY = /^[A-Z][A-Z0-9_]{0,254}-[1-9]\d{0,9}$/;
42
+ /** The issue key when it is well-formed; a malformed one is dropped everywhere it would be rendered. */
43
+ function validIssueKey(input) {
44
+ const key = input.issue?.key;
45
+ return typeof key === "string" && PR_ISSUE_KEY.test(key) ? key : undefined;
46
+ }
47
+ export function pullRequestTitle(input) {
48
+ const base = input.tag ? `[${input.tag}] Wardby run ${input.runId}` : `Wardby run ${input.runId}`;
49
+ const key = validIssueKey(input);
50
+ return key ? `[${key}] ${base}` : base;
51
+ }
52
+ function issueLine(input) {
53
+ const key = validIssueKey(input);
54
+ const url = input.issue?.url;
55
+ if (!key || typeof url !== "string" || !url.startsWith("https://") || /[\s<>()]/.test(url))
56
+ return undefined;
57
+ const name = input.issue?.trackerName;
58
+ const tracker = typeof name === "string" && /^[A-Za-z][A-Za-z0-9 .-]{0,39}$/.test(name) ? `${name.trim()} ` : "";
59
+ return `Resolves ${tracker}issue [${key}](${url})`;
38
60
  }
39
61
  /**
40
62
  * Package names/versions/reasons come from registry metadata, not from
@@ -103,6 +125,10 @@ function packagesSection(input) {
103
125
  /** The hidden run marker stays first and unconditional: createOrFindDraftPullRequest's idempotent lookup depends on it. */
104
126
  export function pullRequestBody(input) {
105
127
  const sections = [`${RUN_MARKER_PREFIX}${input.runId} -->`];
128
+ // Hidden marker stays first; the issue link is the first visible line.
129
+ const issue = issueLine(input);
130
+ if (issue)
131
+ sections.push(issue);
106
132
  if (input.summary)
107
133
  sections.push(input.summary);
108
134
  if (input.tests?.length) {
@@ -65,6 +65,12 @@ export interface FinalizeChangesDetails {
65
65
  name: string;
66
66
  reason: string;
67
67
  }[];
68
+ /** Originating issue (control-plane data): named in the PR title and body. Provider-neutral. */
69
+ issue?: {
70
+ key: string;
71
+ url?: string;
72
+ trackerName?: string;
73
+ };
68
74
  }
69
75
  export type FinalizeChangesResult = {
70
76
  outcome: "no_changes";
@@ -1,4 +1,12 @@
1
+ /**
2
+ * Arguments for `<client> mcp add`. The server name comes right after `add`:
3
+ * Claude Code's `-e/--env <env...>` is variadic and would otherwise read the
4
+ * name as another KEY=value ("Invalid environment variable format: wardby").
5
+ */
6
+ export declare function mcpAddArgs(client: "codex" | "claude", projectDir: string, packageSpec: string): string[];
1
7
  export declare function quickstartCommand(args: string[]): Promise<void>;
8
+ /** The closing "build your first agent" hint; the assistant prompts only make sense once an MCP client is configured. */
9
+ export declare function nextStepLines(mcpConfigured: boolean): string[];
2
10
  export declare function doctorCommand(args: string[]): Promise<void>;
3
11
  export declare function statusCommand(args: string[]): Promise<void>;
4
12
  export declare function logsCommand(args: string[]): Promise<void>;
@@ -263,54 +263,41 @@ function runDemo(paths) {
263
263
  function clientInstalled(command) {
264
264
  return runCommand(command, ["--version"]).status === 0;
265
265
  }
266
+ /**
267
+ * Arguments for `<client> mcp add`. The server name comes right after `add`:
268
+ * Claude Code's `-e/--env <env...>` is variadic and would otherwise read the
269
+ * name as another KEY=value ("Invalid environment variable format: wardby").
270
+ */
271
+ export function mcpAddArgs(client, projectDir, packageSpec) {
272
+ const env = `WARDBY_PROJECT_DIR=${projectDir}`;
273
+ const options = client === "codex" ? ["--env", env] : ["--scope", "local", "-e", env];
274
+ return ["mcp", "add", "wardby", ...options, "--", "npx", "--yes", packageSpec, "mcp"];
275
+ }
266
276
  function configureOneMcpClient(client, paths) {
267
277
  if (!clientInstalled(client)) {
268
278
  console.warn(`! ${client} is not installed; skipped its MCP configuration.`);
269
- return;
279
+ return false;
270
280
  }
271
281
  const existing = runCommand(client, ["mcp", "get", "wardby"], { cwd: paths.projectDir });
272
282
  if (existing.status === 0) {
273
283
  console.log(`✓ ${client} already has an MCP server named wardby.`);
274
- return;
284
+ return true;
275
285
  }
276
- const packageSpec = `@wardby/cli@${packageJson.version}`;
277
- const args = client === "codex"
278
- ? [
279
- "mcp",
280
- "add",
281
- "--env",
282
- `WARDBY_PROJECT_DIR=${paths.projectDir}`,
283
- "wardby",
284
- "--",
285
- "npx",
286
- "--yes",
287
- packageSpec,
288
- "mcp",
289
- ]
290
- : [
291
- "mcp",
292
- "add",
293
- "--scope",
294
- "local",
295
- "-e",
296
- `WARDBY_PROJECT_DIR=${paths.projectDir}`,
297
- "wardby",
298
- "--",
299
- "npx",
300
- "--yes",
301
- packageSpec,
302
- "mcp",
303
- ];
286
+ const args = mcpAddArgs(client, paths.projectDir, `@wardby/cli@${packageJson.version}`);
304
287
  const result = runCommand(client, args, { cwd: paths.projectDir });
305
288
  if (result.status !== 0)
306
289
  commandFailure(`${client} MCP configuration`, result);
307
290
  console.log(`✓ Configured Wardby MCP for ${client}.`);
291
+ return true;
308
292
  }
293
+ /** True when at least one requested client ended up with the wardby MCP server. */
309
294
  function configureMcpClients(client, paths) {
295
+ const results = [];
310
296
  if (client === "codex" || client === "both")
311
- configureOneMcpClient("codex", paths);
297
+ results.push(configureOneMcpClient("codex", paths));
312
298
  if (client === "claude" || client === "both")
313
- configureOneMcpClient("claude", paths);
299
+ results.push(configureOneMcpClient("claude", paths));
300
+ return results.some(Boolean);
314
301
  }
315
302
  async function chooseClient(nonInteractive) {
316
303
  if (nonInteractive)
@@ -412,12 +399,25 @@ export async function quickstartCommand(args) {
412
399
  console.log(`Skipped the billed run. Start it later with: npx @wardby/cli@latest run ${DEMO_AGENT}`);
413
400
  }
414
401
  const client = parseClient(values.client) ?? (await chooseClient(nonInteractive));
415
- if (client !== "none")
416
- configureMcpClients(client, paths);
402
+ const mcpConfigured = client !== "none" && configureMcpClients(client, paths);
417
403
  console.log("\nWardby is ready.");
418
404
  console.log(" npx @wardby/cli@latest status");
419
405
  console.log(" npx @wardby/cli@latest doctor");
420
406
  console.log(" npx @wardby/cli@latest down");
407
+ for (const line of nextStepLines(mcpConfigured))
408
+ console.log(line);
409
+ }
410
+ /** The closing "build your first agent" hint; the assistant prompts only make sense once an MCP client is configured. */
411
+ export function nextStepLines(mcpConfigured) {
412
+ const guide = "the guide: npx @wardby/cli@latest help open agent-recipes";
413
+ const lines = ["", "Next: build your first agent."];
414
+ if (mcpConfigured) {
415
+ lines.push("Ask your assistant one of:", ' "Set up the Wardby architecture keeper for this repository"', ' "Set up a Wardby builder for this repository"', `Or read ${guide}`);
416
+ }
417
+ else {
418
+ lines.push(`Read ${guide}`);
419
+ }
420
+ return lines;
421
421
  }
422
422
  async function databaseHealthy(paths) {
423
423
  const env = readQuickstartEnv(paths);
package/dist/serve.js CHANGED
@@ -33,12 +33,18 @@ export async function startServe(options = {}) {
33
33
  const concurrency = loadCodingConcurrencyConfig();
34
34
  const providers = options.providers ?? buildMcpProviders().providers;
35
35
  const mcp = await startMcp({ providers, schedulerAttached: true });
36
- const reconciler = startReconciler({ executor: providers.executor, reviewHosts: providers.reviewHosts });
36
+ const reconciler = startReconciler({
37
+ executor: providers.executor,
38
+ reviewHosts: providers.reviewHosts,
39
+ issueTrackers: providers.issueTrackers,
40
+ });
41
+ const selfDefects = { db: prisma, issueTrackers: providers.issueTrackers };
37
42
  const scheduler = startScheduler({
38
43
  executor: providers.executor,
39
44
  scope: options.scope,
45
+ selfDefects,
40
46
  onLeaderTick: async () => {
41
- await drainCodingQueue({ db: prisma, executor: providers.executor, ...concurrency });
47
+ await drainCodingQueue({ db: prisma, executor: providers.executor, ...concurrency, selfDefects });
42
48
  },
43
49
  });
44
50
  return {