@aviaratech/ai-delivery 0.3.23 → 0.3.24

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 (112) hide show
  1. package/CONTRIBUTING.md +9 -0
  2. package/README.md +56 -447
  3. package/dist/agent.d.ts +5 -11
  4. package/dist/agent.js +4 -8
  5. package/dist/agent.js.map +1 -1
  6. package/dist/cli.js +12 -107
  7. package/dist/cli.js.map +1 -1
  8. package/dist/config/deliveryConfig.d.ts +44 -55
  9. package/dist/config/deliveryConfig.js +85 -88
  10. package/dist/config/deliveryConfig.js.map +1 -1
  11. package/dist/config/deliveryConfig.test.js +13 -5
  12. package/dist/config/deliveryConfig.test.js.map +1 -1
  13. package/dist/delivery/common.js +7 -2
  14. package/dist/delivery/common.js.map +1 -1
  15. package/dist/delivery/delivery.test.js +2 -3
  16. package/dist/delivery/delivery.test.js.map +1 -1
  17. package/dist/delivery/index.d.ts +0 -6
  18. package/dist/delivery/index.js +0 -3
  19. package/dist/delivery/index.js.map +1 -1
  20. package/dist/delivery/legacy.d.ts +8 -0
  21. package/dist/delivery/legacy.js +5 -0
  22. package/dist/delivery/legacy.js.map +1 -0
  23. package/dist/delivery/policy.js +2 -13
  24. package/dist/delivery/policy.js.map +1 -1
  25. package/dist/directoryIndependent.test.d.ts +1 -0
  26. package/dist/directoryIndependent.test.js +262 -0
  27. package/dist/directoryIndependent.test.js.map +1 -0
  28. package/dist/dispatch.d.ts +29 -3
  29. package/dist/dispatch.js +160 -457
  30. package/dist/dispatch.js.map +1 -1
  31. package/dist/genericCompatibility.test.js +0 -35
  32. package/dist/genericCompatibility.test.js.map +1 -1
  33. package/dist/git.js +10 -3
  34. package/dist/git.js.map +1 -1
  35. package/dist/gitProcess.d.ts +6 -0
  36. package/dist/gitProcess.js +56 -0
  37. package/dist/gitProcess.js.map +1 -0
  38. package/dist/github/discovery.d.ts +1 -0
  39. package/dist/github/discovery.js +1 -1
  40. package/dist/github/discovery.js.map +1 -1
  41. package/dist/github/discovery.test.js +25 -20
  42. package/dist/github/discovery.test.js.map +1 -1
  43. package/dist/github/nativeIssueMetadata.d.ts +2 -2
  44. package/dist/github/nativeIssueMetadata.js.map +1 -1
  45. package/dist/github/projectDelivery.d.ts +1 -1
  46. package/dist/github/projectDelivery.js.map +1 -1
  47. package/dist/github/repo.d.ts +7 -1
  48. package/dist/github/repo.js +58 -2
  49. package/dist/github/repo.js.map +1 -1
  50. package/dist/issue.d.ts +11 -6
  51. package/dist/issue.js +136 -319
  52. package/dist/issue.js.map +1 -1
  53. package/dist/issueJournal.d.ts +1 -1
  54. package/dist/lifecycle.test.js +40 -7371
  55. package/dist/lifecycle.test.js.map +1 -1
  56. package/dist/logger.js +4 -4
  57. package/dist/logger.js.map +1 -1
  58. package/dist/mcp/tools.d.ts +26 -42
  59. package/dist/mcp/tools.js +33 -42
  60. package/dist/mcp/tools.js.map +1 -1
  61. package/dist/pluginInstaller.d.ts +1 -1
  62. package/dist/pluginPackaging.test.js +129 -0
  63. package/dist/pluginPackaging.test.js.map +1 -1
  64. package/dist/pr.d.ts +66 -114
  65. package/dist/pr.js +288 -1108
  66. package/dist/pr.js.map +1 -1
  67. package/dist/pr.test.js +295 -237
  68. package/dist/pr.test.js.map +1 -1
  69. package/dist/releaseReconciliation.test.d.ts +1 -0
  70. package/dist/releaseReconciliation.test.js +314 -0
  71. package/dist/releaseReconciliation.test.js.map +1 -0
  72. package/dist/review.d.ts +37 -7
  73. package/dist/review.js +106 -45
  74. package/dist/review.js.map +1 -1
  75. package/dist/review.test.js +30 -71
  76. package/dist/review.test.js.map +1 -1
  77. package/dist/services/agentReadinessService.d.ts +1 -1
  78. package/dist/services/agentReadinessService.js +2 -0
  79. package/dist/services/agentReadinessService.js.map +1 -1
  80. package/dist/services/deliveryAdmission.d.ts +4 -4
  81. package/dist/services/deliveryAdmission.js.map +1 -1
  82. package/dist/setup.test.js +10 -5
  83. package/dist/setup.test.js.map +1 -1
  84. package/dist/start.test.d.ts +1 -0
  85. package/dist/start.test.js +72 -0
  86. package/dist/start.test.js.map +1 -0
  87. package/dist/stdoutRegression.test.d.ts +1 -0
  88. package/dist/stdoutRegression.test.js +326 -0
  89. package/dist/stdoutRegression.test.js.map +1 -0
  90. package/dist/verification.d.ts +1 -27
  91. package/dist/verification.js +5 -1332
  92. package/dist/verification.js.map +1 -1
  93. package/dist/worktree.d.ts +1 -31
  94. package/dist/worktree.js +5 -200
  95. package/dist/worktree.js.map +1 -1
  96. package/dist/worktreeTransition.d.ts +1 -1
  97. package/dist/worktreeTransition.js +10 -5
  98. package/dist/worktreeTransition.js.map +1 -1
  99. package/dist/worktreeTransition.test.js +2 -3
  100. package/dist/worktreeTransition.test.js.map +1 -1
  101. package/package.json +1 -1
  102. package/plugins/ai-delivery/.claude-plugin/plugin.json +2 -2
  103. package/plugins/ai-delivery/README.md +3 -3
  104. package/plugins/ai-delivery/plugin.json +1 -1
  105. package/plugins/ai-delivery/runtime/dist/cli.js +50 -59
  106. package/plugins/ai-delivery/runtime/package.json +1 -1
  107. package/plugins/ai-delivery/skills/intake-create/SKILL.md +1 -1
  108. package/plugins/ai-delivery/skills/pr-handoff/SKILL.md +14 -21
  109. package/plugins/ai-delivery/skills/worktree-lifecycle/SKILL.md +12 -23
  110. package/dist/services/deliveryRecordService.d.ts +0 -47
  111. package/dist/services/deliveryRecordService.js +0 -199
  112. package/dist/services/deliveryRecordService.js.map +0 -1
package/dist/dispatch.js CHANGED
@@ -1,37 +1,13 @@
1
- import { existsSync, readFileSync } from 'node:fs';
2
- import { loadDeliveryConfig, loadDeliverySettings, readDeliveryOverrides } from './config/deliveryConfig.js';
3
- import { resolveRepoFromRemote } from './github/repo.js';
4
- import { assertRepositoryClassificationCurrent } from './delivery/index.js';
1
+ import { loadDeliverySettings } from './config/deliveryConfig.js';
2
+ import { resolveCheckout, resolveRepoFromRemote } from './github/repo.js';
5
3
  import { DeliveryError } from './errors.js';
6
- import { assertClean, defaultBaseRef, gitCommonDir, gitExitCode, gitRoot, primaryGitRoot } from './git.js';
7
- import { createDeliveryGitHubClients } from './github/client.js';
4
+ import { digestValue } from './delivery/common.js';
8
5
  import { evaluateCommandIdentityPolicy } from './github/commandIdentityPolicy.js';
9
- import { resolveNativeRelationshipTargets } from './github/relationships.js';
10
- import { cachedIssueInfo, CreatedTrackingIssueError, createIssue, commentIssue, developIssue, issueInfo, listIssues, journalIssueStart, loadDeliveryContext, readyCheck, resumeCreatedIssue, updateIssue, } from './issue.js';
11
- import { finishIssue, mergePr, preflightReviewRoute, prInfo, publishPr, submitFormalReview } from './pr.js';
12
- import { parseReviewArtifact, reviewArtifactApproval, savePrepublicationArtifact } from './review.js';
13
- import { evaluateAgentReadiness } from './services/agentReadinessService.js';
14
- import { assertDeliveryRuntimeAdmitted } from './services/deliveryAdmission.js';
15
- import { assertIssueWorktreeTransitionAdmission, getIssueWorktreeStrict, listWorktreesStrict, } from './services/worktreeRegistry.js';
6
+ import { CreatedTrackingIssueError, createIssue, commentIssue, issueInfo, listIssues, journalIssueStart, loadDeliveryContext, readyCheck, resumeCreatedIssue, startIssueBranch, updateIssue, } from './issue.js';
7
+ import { finishIssue, mergePr, prInfo, publishPr, submitFormalReview } from './pr.js';
16
8
  import { applyWorktreeTransition, inspectWorktreeTransition, } from './worktreeTransition.js';
17
- import { createIssuePhaseEvidence, loadVerifiedRun, verifyIssue, } from './verification.js';
18
9
  import { stageRuntime, admitRuntime } from './setup.js';
19
- import { prepareStandaloneWorktree } from './worktree.js';
20
10
  import { getAiDeliveryMcpTool } from './mcp/tools.js';
21
- const MUTATING_TOOLS = new Set([
22
- 'issue_comment',
23
- 'issue_create',
24
- 'issue_start',
25
- 'issue_update',
26
- 'issue_develop',
27
- 'issue_verify',
28
- 'issue_pr_create',
29
- 'issue_pr_review',
30
- 'issue_pr_merge',
31
- 'issue_finish',
32
- 'issue_worktree_create',
33
- 'issue_worktree_transition_apply',
34
- ]);
35
11
  function requiredNumber(input, key) {
36
12
  const value = input[key];
37
13
  if (typeof value !== 'number' || !Number.isSafeInteger(value) || value <= 0)
@@ -47,502 +23,229 @@ function requireString(input, key) {
47
23
  throw new DeliveryError(`${key} is required.`);
48
24
  return value;
49
25
  }
50
- /** @internal Preserve every accepted tracking field when completing a partially created issue. */
26
+ /** Preserve every accepted tracking field when completing a partially created issue. */
51
27
  export function resumedIssueUpdate(input) {
52
- return {
53
- issueNumber: requiredNumber(input, 'issueNumber'),
54
- ...(input.body === undefined ? {} : { body: requireString(input, 'body') }),
55
- ...(input.issueType === undefined ? {} : { issueType: requireString(input, 'issueType') }),
56
- ...(input.points === undefined ? {} : { points: Number(input.points) }),
57
- ...(input.priority === undefined ? {} : { priority: requireString(input, 'priority') }),
58
- ...(input.title === undefined ? {} : { title: requireString(input, 'title') }),
59
- ...(input.labels === undefined ? {} : { labels: input.labels }),
60
- ...(input.milestone === undefined ? {} : { milestone: Number(input.milestone) }),
61
- ...(input.parentIssueNumber === undefined ? {} : { parentIssueNumber: Number(input.parentIssueNumber) }),
62
- ...(input.blockedBy === undefined ? {} : { blockedBy: input.blockedBy }),
63
- };
28
+ const { issueNumber, request: _request, requestId: _requestId, repo: _repo, resumeCreated: _resume, branch: _branch, ...fields } = input;
29
+ return { ...fields, issueNumber: requiredNumber({ issueNumber }, 'issueNumber') };
64
30
  }
65
- async function identityFor(input, commandName) {
66
- const config = await loadDeliverySettings(gitRoot(input.repoRoot));
67
- const identity = input.identity ?? '';
31
+ export async function contextFor(input, commandName, role = 'author') {
32
+ const settings = await loadDeliverySettings();
33
+ const identity = input.identity ?? settings.roles[role].identity;
68
34
  const policy = evaluateCommandIdentityPolicy({
69
35
  commandName,
70
- deliveryConfig: config,
36
+ deliveryConfig: settings,
71
37
  identity,
72
38
  ...(input.personalAuth === undefined ? {} : { personalAuth: input.personalAuth }),
73
39
  });
74
40
  if (policy.error)
75
41
  throw new DeliveryError(policy.error);
76
- return identity || config.roles.author.identity;
77
- }
78
- function assertRepositorySelector(input, execution) {
79
- const selector = input.repo ?? execution.repo;
80
- if (selector === undefined)
81
- return;
82
- const root = gitRoot(execution.repoRoot);
83
- const selected = resolveRepoFromRemote(root, readDeliveryOverrides(root).overrides.remote);
84
- const configured = `${selected.owner}/${selected.repo}`;
85
- if (typeof selector !== 'string' || selector.toLowerCase() !== configured.toLowerCase()) {
86
- throw new DeliveryError('Repository selector must match the configured repository for this Git checkout.');
87
- }
88
- }
89
- export async function contextFor(input, commandName, role = 'author') {
90
- assertRepositorySelector({}, input);
91
- const identity = await identityFor(input, commandName);
92
42
  return loadDeliveryContext({
93
43
  identity,
94
- ...(input.personalAuth === undefined ? {} : { personalAuth: input.personalAuth }),
95
44
  repoRoot: input.repoRoot,
96
45
  role,
46
+ ...(input.repo === undefined ? {} : { repository: input.repo }),
47
+ ...(input.personalAuth === undefined ? {} : { personalAuth: input.personalAuth }),
48
+ ...(input.signal === undefined ? {} : { signal: input.signal }),
97
49
  });
98
50
  }
99
- function sourceRootFor(name, input, execution) {
100
- if (name !== 'issue_verify' &&
101
- name !== 'issue_pr_create' &&
102
- name !== 'issue_pr_review' &&
103
- name !== 'issue_pr_merge' &&
104
- name !== 'issue_finish' &&
105
- !(name === 'issue_pr_info' && input.issueNumber !== undefined))
106
- return undefined;
107
- const primary = primaryGitRoot(execution.repoRoot);
108
- const requested = gitRoot(execution.repoRoot);
109
- const issueNumber = requiredNumber(input, 'issueNumber');
110
- if (name === 'issue_finish' &&
111
- !listWorktreesStrict(primary).some((row) => row.type === 'issue' && row.issueNumber === issueNumber))
112
- return primary;
113
- const row = getIssueWorktreeStrict(issueNumber, primary);
114
- if (requested !== primary && requested !== row.path) {
115
- throw new DeliveryError('Source-bound operations require the primary or exact registered issue checkout.');
116
- }
117
- // Terminal owners validate retained immutable receipts and the remote result.
118
- // A removed merged checkout must not be imported or reconstructed here.
119
- if ((name === 'issue_finish' || name === 'issue_pr_merge') && row.status === 'merged' && !existsSync(row.path)) {
120
- return primary;
121
- }
122
- if (gitCommonDir(row.path) !== gitCommonDir(primary)) {
123
- throw new DeliveryError('Registered issue source must belong to the primary Git repository.');
124
- }
125
- assertClean(row.path);
126
- return row.path;
127
- }
128
- /** @internal Keep CLI and MCP start routing on the same native lifecycle owner. */
129
- export async function startTrackedIssue(context, input, runtimeEntryPath) {
130
- const knownIssue = input.issueNumber === undefined ? undefined : requiredNumber(input, 'issueNumber');
131
- if (input.resumeCreated === true && (knownIssue === undefined || typeof input.develop !== 'boolean')) {
132
- throw new DeliveryError('Resume of a known created issue requires issueNumber and an explicit develop boolean.');
133
- }
134
- if (knownIssue !== undefined && input.resumeCreated !== true) {
135
- const row = await developIssue(context, knownIssue, runtimeEntryPath);
136
- const title = (await issueInfo(context, knownIssue)).title;
137
- return {
138
- issueNumber: knownIssue,
139
- mode: 'existing-issue',
140
- title,
141
- branch: row.branch,
142
- path: row.path,
143
- worktreePath: row.path,
144
- };
145
- }
146
- const request = optionalString(input, 'request');
147
- const title = optionalString(input, 'title') ?? requireString(input, 'request');
148
- const points = input.points === undefined ? 2 : requiredNumber(input, 'points');
149
- const tracking = {
150
- body: optionalString(input, 'body') ?? request ?? title,
151
- title,
152
- points,
153
- ...(input.issueType === undefined ? {} : { issueType: requireString(input, 'issueType') }),
154
- ...(input.priority === undefined ? {} : { priority: requireString(input, 'priority') }),
155
- ...(input.blockedBy === undefined ? {} : { blockedBy: input.blockedBy }),
156
- ...(input.labels === undefined ? {} : { labels: input.labels }),
157
- ...(input.milestone === undefined ? {} : { milestone: Number(input.milestone) }),
158
- ...(input.parentIssueNumber === undefined ? {} : { parentIssueNumber: Number(input.parentIssueNumber) }),
159
- };
160
- if (input.develop === true)
161
- await preflightStartDevelopment(context, tracking);
162
- const repository = `${context.repo.owner}/${context.repo.repo}`;
163
- const createdNotStarted = (createdIssue, phase, error) => ({
164
- schemaVersion: 'ai-delivery.issue-start-registration@1',
51
+ /** GitHub owns branch linkage; hosts own worktree preparation. */
52
+ export async function startTrackedIssue(context, input) {
53
+ let number = input.issueNumber === undefined ? undefined : requiredNumber(input, 'issueNumber');
54
+ let created;
55
+ const registration = (phase, error) => ({
165
56
  status: 'created-not-started',
166
- provider: 'github',
167
- repository,
168
- identity: context.config.roles.author.identity,
169
- createdIssue,
57
+ repository: context.config.repository,
58
+ createdIssue: created,
170
59
  failure: { phase, message: error instanceof Error ? error.message : String(error) },
171
60
  safeResume: {
172
61
  tool: 'issue_start',
173
- arguments: {
174
- ...tracking,
175
- ...(input.repo === undefined ? {} : { repo: input.repo }),
176
- issueNumber: createdIssue.number,
177
- develop: input.develop === true,
178
- resumeCreated: true,
179
- },
62
+ arguments: { ...input, repo: context.config.repository, issueNumber: number, resumeCreated: true },
180
63
  },
181
64
  });
182
- let created;
183
- if (knownIssue !== undefined) {
184
- const existing = (await context.clients.rest.issues.get({ ...context.repo, issue_number: knownIssue })).data;
185
- if (existing.number !== knownIssue || 'pull_request' in existing) {
186
- throw new DeliveryError('Known-created issue readback disagrees with the requested issue.');
187
- }
188
- created = { number: existing.number, title: existing.title, url: existing.html_url };
189
- try {
190
- await resumeCreatedIssue(context, resumedIssueUpdate(input));
191
- }
192
- catch (error) {
193
- return createdNotStarted(created, 'tracking', error);
65
+ if (number === undefined) {
66
+ const title = optionalString(input, 'title') ?? requireString(input, 'request');
67
+ const tracking = {
68
+ ...input,
69
+ title,
70
+ body: optionalString(input, 'body') ?? optionalString(input, 'request') ?? title,
71
+ };
72
+ const intent = digestValue(Object.fromEntries(Object.entries({
73
+ title,
74
+ body: tracking.body,
75
+ issueType: tracking.issueType,
76
+ points: tracking.points,
77
+ priority: tracking.priority,
78
+ labels: tracking.labels,
79
+ blockedBy: tracking.blockedBy,
80
+ parentIssueNumber: tracking.parentIssueNumber,
81
+ milestone: tracking.milestone,
82
+ branch: input.branch,
83
+ }).filter(([, value]) => value !== undefined)));
84
+ const requestKey = optionalString(input, 'requestId') ?? intent;
85
+ const marker = `<!-- ai-delivery-start: ${digestValue({ repository: context.config.repository.toLowerCase(), requestKey })} -->`;
86
+ tracking.body = `${tracking.body}\n\n<!-- ai-delivery-start-intent: ${intent} -->\n${marker}`;
87
+ let completed = false;
88
+ const matches = [];
89
+ for (let page = 1; page <= 20; page++) {
90
+ const batch = (await context.clients.rest.issues.listForRepo({ ...context.repo, state: 'all', page, per_page: 100 })).data;
91
+ matches.push(...batch.filter((value) => !('pull_request' in value) && value.body?.includes(marker)));
92
+ if (batch.length < 100) {
93
+ completed = true;
94
+ break;
95
+ }
194
96
  }
195
- }
196
- else {
97
+ if (!completed)
98
+ throw new DeliveryError('Issue-start retry search exceeds the bounded readback window; select a known issue number.');
99
+ if (matches.length > 1)
100
+ throw new DeliveryError('Conflicting issues claim the same start request.');
101
+ const prior = matches[0];
102
+ if (prior && (prior.title !== title || prior.body !== tracking.body || prior.state !== 'open'))
103
+ throw new DeliveryError('Prior issue-start request conflicts with the requested intent or state.');
197
104
  try {
198
- created = (await createIssue(context, tracking)).created;
105
+ if (prior) {
106
+ created = { number: prior.number, title: prior.title, url: prior.html_url };
107
+ number = prior.number;
108
+ await resumeCreatedIssue(context, { ...tracking, issueNumber: number });
109
+ }
110
+ else {
111
+ created = (await createIssue(context, tracking)).created;
112
+ number = created.number;
113
+ }
199
114
  }
200
115
  catch (error) {
201
- if (input.develop === true && error instanceof CreatedTrackingIssueError) {
202
- return createdNotStarted(error.created, 'tracking', error);
116
+ if (error instanceof CreatedTrackingIssueError) {
117
+ created = error.created;
118
+ number = created.number;
203
119
  }
204
- throw error;
120
+ else if (!created)
121
+ throw error;
122
+ return registration('tracking', error);
205
123
  }
206
124
  }
207
- if (input.develop === true) {
125
+ else if (input.resumeCreated === true) {
126
+ const issue = (await context.clients.rest.issues.get({ ...context.repo, issue_number: number })).data;
127
+ if (issue.number !== number || 'pull_request' in issue)
128
+ throw new DeliveryError('Known-created issue readback disagrees.');
129
+ created = { number, title: issue.title, url: issue.html_url };
208
130
  try {
209
- const row = await developIssue(context, created.number, runtimeEntryPath);
210
- return {
211
- schemaVersion: 'ai-delivery.issue-start-registration@1',
212
- status: 'started',
213
- provider: 'github',
214
- repository,
215
- identity: context.config.roles.author.identity,
216
- issue: created,
217
- branch: row.branch,
218
- worktreePath: row.path,
219
- };
131
+ await resumeCreatedIssue(context, resumedIssueUpdate(input));
220
132
  }
221
133
  catch (error) {
222
- return createdNotStarted(created, 'development', error);
134
+ return registration('tracking', error);
223
135
  }
224
136
  }
225
137
  try {
226
- await journalIssueStart(context, created.number);
138
+ const branch = await startIssueBranch(context, number, optionalString(input, 'branch'));
139
+ await journalIssueStart(context, number);
140
+ return {
141
+ issueNumber: number,
142
+ repository: context.config.repository,
143
+ branch: branch.branch,
144
+ headSha: branch.headSha,
145
+ reused: branch.reused,
146
+ };
227
147
  }
228
148
  catch (error) {
229
- return createdNotStarted(created, 'journal', error);
230
- }
231
- return { issueNumber: created.number, issueUrl: created.url, mode: 'created-issue', title: created.title };
232
- }
233
- async function preflightStartDevelopment(context, tracking) {
234
- const targets = await resolveNativeRelationshipTargets({
235
- blockers: tracking.blockedBy ?? [],
236
- ...(tracking.parentIssueNumber === undefined ? {} : { parentIssueNumber: tracking.parentIssueNumber }),
237
- repo: context.repo,
238
- rest: context.clients.rest,
239
- });
240
- const readiness = evaluateAgentReadiness({
241
- blockedBy: targets.unresolvedBlockers,
242
- body: tracking.body ?? '',
243
- ...(tracking.points === undefined ? {} : { points: tracking.points }),
244
- repoRoot: context.root,
245
- title: tracking.title,
246
- });
247
- if (!readiness.ready) {
248
- throw new DeliveryError(`New issue is not ready: ${readiness.failures.map((failure) => failure.message).join('; ')}`);
249
- }
250
- listWorktreesStrict(context.root);
251
- if (gitExitCode(context.root, 'rev-parse', '--verify', defaultBaseRef(context.root, context.configuration?.remote)) !==
252
- 0) {
253
- throw new DeliveryError('Default base ref is missing before tracked issue creation.');
149
+ if (created)
150
+ return registration('branch-or-journal', error);
151
+ throw error;
254
152
  }
255
- await preflightReviewRoute(context, undefined, undefined, 'development');
256
- }
257
- function scratchStart(input, execution, identity, remote) {
258
- const title = (optionalString(input, 'title') ?? requireString(input, 'request'))
259
- .replace(/\s+/gu, ' ')
260
- .trim()
261
- .slice(0, 90)
262
- .trim();
263
- const branchOverride = optionalString(input, 'branch');
264
- const source = (branchOverride ?? optionalString(input, 'request') ?? title).replace(/\//gu, '-');
265
- const slug = source
266
- .trim()
267
- .toLowerCase()
268
- .replace(/[^a-z0-9]+/gu, '-')
269
- .replace(/^-+|-+$/gu, '')
270
- .slice(0, 56)
271
- .replace(/-+$/gu, '') || 'scratch';
272
- const branch = branchOverride ?? `scratch/${slug}`;
273
- const name = `scratch-${slug}`;
274
- return prepareStandaloneWorktree({
275
- branch,
276
- identity,
277
- name,
278
- repoRoot: execution.repoRoot,
279
- ...(remote ? { remote } : {}),
280
- }).then((row) => ({
281
- mode: 'scratch',
282
- title,
283
- branch: row.branch,
284
- worktreePath: row.path,
285
- }));
286
153
  }
287
154
  export async function executeTool(name, raw, execution) {
288
155
  const definition = getAiDeliveryMcpTool(name);
289
156
  if (!definition)
290
157
  throw new DeliveryError(`Unknown ai-delivery tool ${name}.`);
291
- const input = definition.inputSchema.parse(raw);
292
- assertRepositorySelector(input, execution);
293
- if (name === 'runtime_stage' || name === 'runtime_admit') {
294
- const setup = {
295
- ...input,
296
- repoRoot: execution.repoRoot,
297
- identity: optionalString(input, 'identity') ?? execution.identity ?? '',
298
- ...(execution.personalAuth === undefined ? {} : { personalAuth: execution.personalAuth }),
299
- ...(execution.signal === undefined ? {} : { signal: execution.signal }),
300
- };
301
- return name === 'runtime_stage'
302
- ? stageRuntime(setup)
303
- : admitRuntime(setup);
304
- }
305
- if (name === 'issue_info' && input.cached === true)
306
- return cachedIssueInfo(execution.repoRoot, requiredNumber(input, 'issueNumber'));
307
- const commands = {
308
- issue_comment: 'comment',
309
- runtime_stage: 'runtime:stage',
310
- runtime_admit: 'runtime:admit',
311
- issue_create: 'create',
312
- issue_start: 'start',
313
- issue_update: 'update',
314
- issue_info: 'info',
315
- issue_list: 'list',
316
- issue_search: 'search',
317
- issue_ready_check: 'ready:check',
318
- issue_develop: 'develop',
319
- issue_verify: 'verify',
320
- issue_pr_create: 'pr:create',
321
- issue_pr_info: 'pr:info',
322
- issue_pr_review: 'pr:review',
323
- issue_pr_merge: 'pr:merge',
324
- issue_finish: 'finish',
325
- issue_worktree_create: 'worktree:create',
326
- issue_worktree_transition_inspect: 'worktree:transition:inspect',
327
- issue_worktree_transition_apply: 'worktree:transition:apply',
328
- };
329
- const requestedIdentity = name === 'issue_pr_review' ? optionalString(input, 'identity') : undefined;
330
- const selectedExecution = requestedIdentity === undefined ? execution : { ...execution, identity: requestedIdentity };
331
- const sourceRoot = sourceRootFor(name, input, execution);
332
- const context = await contextFor(sourceRoot === undefined ? selectedExecution : { ...selectedExecution, repoRoot: sourceRoot }, commands[name], name === 'issue_pr_review' ? 'reviewer' : 'author');
333
- let controllerConfiguration = context.configuration;
334
- if (sourceRoot !== undefined) {
335
- const primary = primaryGitRoot(execution.repoRoot);
336
- const settings = await loadDeliverySettings(primary);
337
- if (settings.repository.toLowerCase() !== context.config.repository.toLowerCase()) {
338
- throw new DeliveryError('Registered issue source and primary controller must select the same repository.');
158
+ const { repo: selected, ...input } = definition.inputSchema.parse(raw);
159
+ if (selected !== undefined && execution.repo !== undefined && selected !== execution.repo)
160
+ throw new DeliveryError('Tool and execution repository selectors disagree.');
161
+ const repo = typeof selected === 'string' ? selected : execution.repo;
162
+ const selectedExecution = { ...execution, ...(repo === undefined ? {} : { repo }) };
163
+ const local = name === 'runtime_stage' || name === 'runtime_admit' || name.startsWith('issue_worktree_transition_');
164
+ let root = execution.repoRoot;
165
+ if (local) {
166
+ const settings = await loadDeliverySettings();
167
+ const coordinates = repo ??
168
+ (() => {
169
+ const r = resolveRepoFromRemote(root, 'origin');
170
+ return `${r.owner}/${r.repo}`;
171
+ })();
172
+ root = resolveCheckout({ repository: coordinates, launchDirectory: root, checkoutRoots: settings.checkoutRoots });
173
+ if (name === 'runtime_stage' || name === 'runtime_admit') {
174
+ const setup = {
175
+ ...input,
176
+ repoRoot: root,
177
+ identity: optionalString(input, 'identity') ?? execution.identity ?? settings.roles.author.identity,
178
+ ...(execution.personalAuth === undefined ? {} : { personalAuth: execution.personalAuth }),
179
+ ...(execution.signal === undefined ? {} : { signal: execution.signal }),
180
+ };
181
+ return name === 'runtime_stage'
182
+ ? stageRuntime(setup)
183
+ : admitRuntime(setup);
339
184
  }
340
- const authorClients = name === 'issue_pr_review'
341
- ? await createDeliveryGitHubClients({
342
- config: context.config,
343
- identity: context.config.roles.author.identity,
344
- ...(execution.personalAuth ? { personalAuth: { enabled: true } } : {}),
345
- role: 'author',
346
- })
347
- : context.clients;
348
- // Discover the controller's metadata through the configured source author,
349
- // while retaining the controller's policy, settings and admission digest.
350
- controllerConfiguration = await loadDeliveryConfig(primary, { clients: authorClients });
351
- }
352
- if (MUTATING_TOOLS.has(name) && input.dryRun !== true) {
353
- if (name !== 'issue_worktree_transition_apply' && typeof input.issueNumber === 'number')
354
- assertIssueWorktreeTransitionAdmission(input.issueNumber, primaryGitRoot(execution.repoRoot), name === 'issue_develop' || (name === 'issue_start' && input.resumeCreated !== true));
355
- await assertDeliveryRuntimeAdmitted({
356
- ...execution,
357
- repoRoot: sourceRoot === undefined ? execution.repoRoot : primaryGitRoot(execution.repoRoot),
358
- ...(controllerConfiguration === undefined ? {} : { configuration: controllerConfiguration }),
359
- });
360
- }
361
- if (sourceRoot !== undefined && (name === 'issue_pr_create' || name === 'issue_pr_review')) {
362
- if (context.configuration === undefined)
363
- throw new DeliveryError('Source-bound operation requires discovered candidate configuration.');
364
- const run = loadVerifiedRun(sourceRoot, requiredNumber(input, 'issueNumber'));
365
- assertRepositoryClassificationCurrent({
366
- classification: run.classification,
367
- configDigest: context.configuration.configDigest,
368
- policySourcePath: context.config.policy.module,
369
- repoRoot: sourceRoot,
370
- });
371
185
  }
186
+ const requestedIdentity = name === 'issue_pr_review' ? optionalString(input, 'identity') : undefined;
187
+ const context = await contextFor({ ...selectedExecution, ...(requestedIdentity === undefined ? {} : { identity: requestedIdentity }) }, definition.commandName, name === 'issue_pr_review' ? 'reviewer' : 'author');
188
+ if (local)
189
+ context.root = root;
372
190
  switch (name) {
373
191
  case 'issue_comment':
374
192
  return commentIssue(context, input);
375
- case 'issue_worktree_transition_inspect':
376
- return inspectWorktreeTransition(context, {
377
- ...input,
378
- ...(execution.runtimeEntryPath === undefined ? {} : { runtimeEntryPath: execution.runtimeEntryPath }),
379
- });
380
- case 'issue_worktree_transition_apply':
381
- return applyWorktreeTransition(context, {
382
- authority: 'worktree:transition',
383
- planPath: requireString(input, 'planPath'),
384
- expectedPlanId: requireString(input, 'expectedPlanId'),
385
- relinquishmentCommentId: requiredNumber(input, 'relinquishmentCommentId'),
386
- acceptanceCommentId: requiredNumber(input, 'acceptanceCommentId'),
387
- ...(execution.runtimeEntryPath === undefined ? {} : { runtimeEntryPath: execution.runtimeEntryPath }),
388
- });
389
- case 'issue_create': {
193
+ case 'issue_create':
390
194
  return createIssue(context, input);
391
- }
392
- case 'issue_start': {
393
- const identity = await identityFor(execution, 'start');
394
- if (input.resumeCreated === true && (input.issueNumber === undefined || typeof input.develop !== 'boolean')) {
395
- throw new DeliveryError('Resume of a known created issue requires issueNumber and an explicit develop boolean.');
396
- }
397
- if (input.scratch === true) {
398
- if (input.issueNumber !== undefined || input.develop === true || input.resumeCreated === true) {
399
- throw new DeliveryError('Scratch worktree cannot claim a tracked issue.');
400
- }
401
- await preflightReviewRoute(context, undefined, undefined, 'development');
402
- return scratchStart(input, execution, identity, context.configuration?.remote);
403
- }
404
- return startTrackedIssue(context, input, execution.runtimeEntryPath);
405
- }
406
- case 'issue_update': {
195
+ case 'issue_start':
196
+ return startTrackedIssue(context, input);
197
+ case 'issue_update':
407
198
  return updateIssue(context, input);
408
- }
409
199
  case 'issue_list':
410
200
  case 'issue_search':
411
201
  return listIssues(context, input);
412
- case 'issue_info': {
413
- if (input.cached === true)
414
- return cachedIssueInfo(execution.repoRoot, requiredNumber(input, 'issueNumber'));
202
+ case 'issue_info':
415
203
  return issueInfo(context, requiredNumber(input, 'issueNumber'));
416
- }
417
- case 'issue_ready_check': {
204
+ case 'issue_ready_check':
418
205
  return readyCheck(context, requiredNumber(input, 'issueNumber'));
419
- }
420
- case 'issue_develop': {
421
- const issueNumber = requiredNumber(input, 'issueNumber');
422
- const result = await developIssue(context, issueNumber, execution.runtimeEntryPath);
423
- if (input.assignee !== undefined)
424
- await context.clients.rest.issues.addAssignees({
425
- ...context.repo,
426
- issue_number: issueNumber,
427
- assignees: [requireString(input, 'assignee')],
428
- });
429
- return result;
430
- }
431
- case 'issue_verify': {
432
- const issueNumber = requiredNumber(input, 'issueNumber');
433
- const root = primaryGitRoot(execution.repoRoot);
434
- const row = getIssueWorktreeStrict(issueNumber, root);
435
- const run = await verifyIssue({
436
- personalAuth: context.clients.authSource === 'personal' && context.config.roles.author.authSource !== 'personal',
437
- issueNumber,
438
- repoRoot: row.path,
439
- ...(execution.signal === undefined ? {} : { signal: execution.signal }),
440
- ...(input.resourceBounds === undefined
441
- ? {}
442
- : { resourceBounds: input.resourceBounds }),
443
- ...(input.admit === undefined
444
- ? {}
445
- : { admittedResourceClasses: ['source_only', ...input.admit] }),
446
- });
447
- let approval;
448
- if (input.prepublicationReview !== undefined) {
449
- const artifact = parseReviewArtifact(readFileSync(requireString(input, 'prepublicationReview'), 'utf8'));
450
- const config = (await loadDeliveryConfig(row.path, execution.personalAuth === undefined ? {} : { personalAuth: execution.personalAuth })).config;
451
- savePrepublicationArtifact({
452
- artifact,
453
- classification: run.classification,
454
- config,
455
- issueNumber,
456
- repoRoot: row.path,
457
- });
458
- approval = reviewArtifactApproval({ artifact, classification: run.classification, config, issueNumber });
459
- }
460
- const evidence = await createIssuePhaseEvidence({
461
- personalAuth: context.clients.authSource === 'personal' && context.config.roles.author.authSource !== 'personal',
462
- ...(approval === undefined ? {} : { approval }),
463
- issueNumber,
464
- phase: 'verify',
465
- repoRoot: row.path,
466
- });
467
- return {
468
- classificationReceiptId: run.classification.receiptId,
469
- aggregateId: run.aggregate.aggregateId,
470
- evidenceId: evidence.evidenceId,
471
- manifestId: run.manifestId,
472
- ...(run.resources === undefined ? {} : { resources: run.resources }),
473
- };
474
- }
475
- case 'issue_pr_create': {
476
- const issueNumber = requiredNumber(input, 'issueNumber');
477
- if (input.dryRun === true) {
478
- const row = getIssueWorktreeStrict(issueNumber, context.root);
479
- const run = loadVerifiedRun(row.path, issueNumber);
480
- return {
481
- dryRun: true,
482
- headSha: run.classification.head.sha,
483
- risk: run.classification.risk,
484
- reviewRoute: await preflightReviewRoute(context),
485
- };
486
- }
206
+ case 'issue_pr_create':
487
207
  return publishPr(context, {
488
- issueNumber,
208
+ issueNumber: requiredNumber(input, 'issueNumber'),
489
209
  ...(input.body === undefined ? {} : { body: requireString(input, 'body') }),
490
210
  ...(input.title === undefined ? {} : { title: requireString(input, 'title') }),
211
+ ...(input.headBranch === undefined ? {} : { headBranch: requireString(input, 'headBranch') }),
491
212
  draft: input.draft !== false,
213
+ dryRun: input.dryRun === true,
492
214
  });
493
- }
494
- case 'issue_pr_info': {
495
- return prInfo(context, {
496
- ...(input.issueNumber === undefined ? {} : { issueNumber: Number(input.issueNumber) }),
497
- ...(input.prNumber === undefined ? {} : { prNumber: Number(input.prNumber) }),
498
- });
499
- }
500
- case 'issue_pr_review': {
501
- if (input.dryRun === true) {
502
- const issueNumber = requiredNumber(input, 'issueNumber');
503
- const row = getIssueWorktreeStrict(issueNumber, context.root);
504
- const run = loadVerifiedRun(row.path, issueNumber);
505
- const artifact = parseReviewArtifact(requireString(input, 'artifact'));
506
- reviewArtifactApproval({
507
- artifact,
508
- classification: run.classification,
509
- config: context.config,
510
- issueNumber,
511
- prNumber: requiredNumber(input, 'prNumber'),
512
- });
513
- return { dryRun: true, artifactId: artifact.artifactId };
514
- }
215
+ case 'issue_pr_info':
216
+ return prInfo(context, input);
217
+ case 'issue_pr_review':
515
218
  return submitFormalReview(context, {
516
219
  artifact: requireString(input, 'artifact'),
517
220
  issueNumber: requiredNumber(input, 'issueNumber'),
518
221
  prNumber: requiredNumber(input, 'prNumber'),
222
+ dryRun: input.dryRun === true,
519
223
  });
520
- }
521
224
  case 'issue_pr_merge':
522
225
  case 'issue_finish': {
523
- const issueNumber = requiredNumber(input, 'issueNumber');
524
- const prNumber = requiredNumber(input, 'prNumber');
525
- if (input.dryRun === true) {
526
- const row = getIssueWorktreeStrict(issueNumber, context.root);
527
- const run = loadVerifiedRun(row.path, issueNumber);
528
- return { dryRun: true, headSha: run.classification.head.sha, prNumber };
529
- }
530
- const strategy = input.strategy;
531
- return name === 'issue_finish'
532
- ? finishIssue(context, { issueNumber, prNumber, ...(strategy === undefined ? {} : { strategy }) })
533
- : mergePr(context, { issueNumber, prNumber, ...(strategy === undefined ? {} : { strategy }) });
226
+ const merge = {
227
+ issueNumber: requiredNumber(input, 'issueNumber'),
228
+ prNumber: requiredNumber(input, 'prNumber'),
229
+ ...(input.strategy === undefined ? {} : { strategy: input.strategy }),
230
+ ...(input.reviewedHeadSha === undefined ? {} : { reviewedHeadSha: requireString(input, 'reviewedHeadSha') }),
231
+ dryRun: input.dryRun === true,
232
+ };
233
+ return name === 'issue_finish' ? finishIssue(context, merge) : mergePr(context, merge);
534
234
  }
535
- case 'issue_worktree_create': {
536
- const identity = await identityFor(execution, 'worktree:create');
537
- await preflightReviewRoute(context, undefined, undefined, 'development');
538
- return prepareStandaloneWorktree({
539
- ...(context.configuration?.remote ? { remote: context.configuration.remote } : {}),
540
- branch: requireString(input, 'branch'),
541
- identity,
542
- name: requireString(input, 'name'),
543
- repoRoot: execution.repoRoot,
235
+ case 'issue_worktree_transition_inspect':
236
+ return inspectWorktreeTransition(context, {
237
+ ...input,
238
+ ...(execution.runtimeEntryPath === undefined ? {} : { runtimeEntryPath: execution.runtimeEntryPath }),
239
+ });
240
+ case 'issue_worktree_transition_apply':
241
+ return applyWorktreeTransition(context, {
242
+ authority: 'worktree:transition',
243
+ planPath: requireString(input, 'planPath'),
244
+ expectedPlanId: requireString(input, 'expectedPlanId'),
245
+ relinquishmentCommentId: requiredNumber(input, 'relinquishmentCommentId'),
246
+ acceptanceCommentId: requiredNumber(input, 'acceptanceCommentId'),
247
+ ...(execution.runtimeEntryPath === undefined ? {} : { runtimeEntryPath: execution.runtimeEntryPath }),
544
248
  });
545
- }
546
249
  }
547
250
  }
548
251
  //# sourceMappingURL=dispatch.js.map