@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.
- package/CONTRIBUTING.md +9 -0
- package/README.md +56 -447
- package/dist/agent.d.ts +5 -11
- package/dist/agent.js +4 -8
- package/dist/agent.js.map +1 -1
- package/dist/cli.js +12 -107
- package/dist/cli.js.map +1 -1
- package/dist/config/deliveryConfig.d.ts +44 -55
- package/dist/config/deliveryConfig.js +85 -88
- package/dist/config/deliveryConfig.js.map +1 -1
- package/dist/config/deliveryConfig.test.js +13 -5
- package/dist/config/deliveryConfig.test.js.map +1 -1
- package/dist/delivery/common.js +7 -2
- package/dist/delivery/common.js.map +1 -1
- package/dist/delivery/delivery.test.js +2 -3
- package/dist/delivery/delivery.test.js.map +1 -1
- package/dist/delivery/index.d.ts +0 -6
- package/dist/delivery/index.js +0 -3
- package/dist/delivery/index.js.map +1 -1
- package/dist/delivery/legacy.d.ts +8 -0
- package/dist/delivery/legacy.js +5 -0
- package/dist/delivery/legacy.js.map +1 -0
- package/dist/delivery/policy.js +2 -13
- package/dist/delivery/policy.js.map +1 -1
- package/dist/directoryIndependent.test.d.ts +1 -0
- package/dist/directoryIndependent.test.js +262 -0
- package/dist/directoryIndependent.test.js.map +1 -0
- package/dist/dispatch.d.ts +29 -3
- package/dist/dispatch.js +160 -457
- package/dist/dispatch.js.map +1 -1
- package/dist/genericCompatibility.test.js +0 -35
- package/dist/genericCompatibility.test.js.map +1 -1
- package/dist/git.js +10 -3
- package/dist/git.js.map +1 -1
- package/dist/gitProcess.d.ts +6 -0
- package/dist/gitProcess.js +56 -0
- package/dist/gitProcess.js.map +1 -0
- package/dist/github/discovery.d.ts +1 -0
- package/dist/github/discovery.js +1 -1
- package/dist/github/discovery.js.map +1 -1
- package/dist/github/discovery.test.js +25 -20
- package/dist/github/discovery.test.js.map +1 -1
- package/dist/github/nativeIssueMetadata.d.ts +2 -2
- package/dist/github/nativeIssueMetadata.js.map +1 -1
- package/dist/github/projectDelivery.d.ts +1 -1
- package/dist/github/projectDelivery.js.map +1 -1
- package/dist/github/repo.d.ts +7 -1
- package/dist/github/repo.js +58 -2
- package/dist/github/repo.js.map +1 -1
- package/dist/issue.d.ts +11 -6
- package/dist/issue.js +136 -319
- package/dist/issue.js.map +1 -1
- package/dist/issueJournal.d.ts +1 -1
- package/dist/lifecycle.test.js +40 -7371
- package/dist/lifecycle.test.js.map +1 -1
- package/dist/logger.js +4 -4
- package/dist/logger.js.map +1 -1
- package/dist/mcp/tools.d.ts +26 -42
- package/dist/mcp/tools.js +33 -42
- package/dist/mcp/tools.js.map +1 -1
- package/dist/pluginInstaller.d.ts +1 -1
- package/dist/pluginPackaging.test.js +129 -0
- package/dist/pluginPackaging.test.js.map +1 -1
- package/dist/pr.d.ts +66 -114
- package/dist/pr.js +288 -1108
- package/dist/pr.js.map +1 -1
- package/dist/pr.test.js +295 -237
- package/dist/pr.test.js.map +1 -1
- package/dist/releaseReconciliation.test.d.ts +1 -0
- package/dist/releaseReconciliation.test.js +314 -0
- package/dist/releaseReconciliation.test.js.map +1 -0
- package/dist/review.d.ts +37 -7
- package/dist/review.js +106 -45
- package/dist/review.js.map +1 -1
- package/dist/review.test.js +30 -71
- package/dist/review.test.js.map +1 -1
- package/dist/services/agentReadinessService.d.ts +1 -1
- package/dist/services/agentReadinessService.js +2 -0
- package/dist/services/agentReadinessService.js.map +1 -1
- package/dist/services/deliveryAdmission.d.ts +4 -4
- package/dist/services/deliveryAdmission.js.map +1 -1
- package/dist/setup.test.js +10 -5
- package/dist/setup.test.js.map +1 -1
- package/dist/start.test.d.ts +1 -0
- package/dist/start.test.js +72 -0
- package/dist/start.test.js.map +1 -0
- package/dist/stdoutRegression.test.d.ts +1 -0
- package/dist/stdoutRegression.test.js +326 -0
- package/dist/stdoutRegression.test.js.map +1 -0
- package/dist/verification.d.ts +1 -27
- package/dist/verification.js +5 -1332
- package/dist/verification.js.map +1 -1
- package/dist/worktree.d.ts +1 -31
- package/dist/worktree.js +5 -200
- package/dist/worktree.js.map +1 -1
- package/dist/worktreeTransition.d.ts +1 -1
- package/dist/worktreeTransition.js +10 -5
- package/dist/worktreeTransition.js.map +1 -1
- package/dist/worktreeTransition.test.js +2 -3
- package/dist/worktreeTransition.test.js.map +1 -1
- package/package.json +1 -1
- package/plugins/ai-delivery/.claude-plugin/plugin.json +2 -2
- package/plugins/ai-delivery/README.md +3 -3
- package/plugins/ai-delivery/plugin.json +1 -1
- package/plugins/ai-delivery/runtime/dist/cli.js +50 -59
- package/plugins/ai-delivery/runtime/package.json +1 -1
- package/plugins/ai-delivery/skills/intake-create/SKILL.md +1 -1
- package/plugins/ai-delivery/skills/pr-handoff/SKILL.md +14 -21
- package/plugins/ai-delivery/skills/worktree-lifecycle/SKILL.md +12 -23
- package/dist/services/deliveryRecordService.d.ts +0 -47
- package/dist/services/deliveryRecordService.js +0 -199
- package/dist/services/deliveryRecordService.js.map +0 -1
package/dist/dispatch.js
CHANGED
|
@@ -1,37 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
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 {
|
|
7
|
-
import { createDeliveryGitHubClients } from './github/client.js';
|
|
4
|
+
import { digestValue } from './delivery/common.js';
|
|
8
5
|
import { evaluateCommandIdentityPolicy } from './github/commandIdentityPolicy.js';
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
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
|
-
/**
|
|
26
|
+
/** Preserve every accepted tracking field when completing a partially created issue. */
|
|
51
27
|
export function resumedIssueUpdate(input) {
|
|
52
|
-
|
|
53
|
-
|
|
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
|
|
66
|
-
const
|
|
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:
|
|
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
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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
|
-
|
|
167
|
-
|
|
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
|
-
|
|
183
|
-
|
|
184
|
-
const
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
202
|
-
|
|
116
|
+
if (error instanceof CreatedTrackingIssueError) {
|
|
117
|
+
created = error.created;
|
|
118
|
+
number = created.number;
|
|
203
119
|
}
|
|
204
|
-
|
|
120
|
+
else if (!created)
|
|
121
|
+
throw error;
|
|
122
|
+
return registration('tracking', error);
|
|
205
123
|
}
|
|
206
124
|
}
|
|
207
|
-
if (input.
|
|
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
|
-
|
|
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
|
|
134
|
+
return registration('tracking', error);
|
|
223
135
|
}
|
|
224
136
|
}
|
|
225
137
|
try {
|
|
226
|
-
await
|
|
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
|
-
|
|
230
|
-
|
|
231
|
-
|
|
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
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
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 '
|
|
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
|
-
|
|
393
|
-
|
|
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
|
-
|
|
495
|
-
|
|
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
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
}
|
|
530
|
-
|
|
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 '
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
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
|