engineering-memory 1.11.22 → 1.11.23
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/package.json +1 -1
- package/runtime/build.json +1 -1
- package/runtime/dist/src/config.js +6 -0
- package/runtime/dist/src/git/verification-gate.js +2 -0
- package/runtime/dist/src/localization/catalogue.generated.js +22 -0
- package/runtime/dist/src/mcp/delivery-tools.js +226 -22
- package/runtime/dist/src/mcp/tool-annotations.js +3 -0
- package/runtime/dist/src/mcp/tool-definitions.js +22 -1
- package/runtime/dist/src/mcp/worktree-tools.js +16 -2
- package/runtime/dist/src/runtime/api-client.js +3 -0
- package/runtime/dist/src/runtime/branch-preferences.js +15 -0
- package/runtime/dist/src/runtime/bridge-service.js +168 -10
- package/runtime/dist/src/runtime/create-bridge-service.js +4 -2
- package/runtime/dist/src/runtime/questionnaire-store.js +7 -0
- package/runtime/dist/src/runtime/task-start.js +45 -15
- package/runtime/dist/src/runtime/worktree-pool.js +97 -20
- package/skill/references/lifecycle.md +26 -3
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ignoredRuntimeFiles, localRelativePath } from './worktree-preparation.js';
|
|
2
2
|
import { decisionModeSchema } from './decision-mode-store.js';
|
|
3
|
-
import { delegatedReasonSchema } from './questionnaire-store.js';
|
|
3
|
+
import { answerSourceName, delegatedReasonSchema } from './questionnaire-store.js';
|
|
4
4
|
import { unreadableWorktree, } from './worktree-pool.js';
|
|
5
5
|
import { validWorktreePolicy } from './worktree-policy.js';
|
|
6
6
|
import { resolveTaskBase } from './branch-preferences.js';
|
|
@@ -1689,10 +1689,31 @@ export class BridgeService {
|
|
|
1689
1689
|
catch (error) {
|
|
1690
1690
|
throw refuse(error instanceof Error ? error.message : 'The resulting commit must be inspected.', 'memory.sync_start');
|
|
1691
1691
|
}
|
|
1692
|
+
if (!proof)
|
|
1693
|
+
throw refuse('This task has nothing to publish: its project had no inspected source snapshot when the task was verified.', 'memory.sync_start');
|
|
1692
1694
|
const response = await this.dependencies.client.request('/memory/sources/publish-task', { method: 'POST', body: proof });
|
|
1693
1695
|
return response.data;
|
|
1694
1696
|
});
|
|
1695
1697
|
}
|
|
1698
|
+
async publishDelivered(repoRoot, taskId) {
|
|
1699
|
+
try {
|
|
1700
|
+
const proof = await this.dependencies.gate.publicationProof(repoRoot, taskId);
|
|
1701
|
+
if (!proof)
|
|
1702
|
+
return null;
|
|
1703
|
+
const response = await this.dependencies.client.request('/memory/sources/publish-task', { method: 'POST', body: proof });
|
|
1704
|
+
const answer = objectValue(response.data);
|
|
1705
|
+
return answer?.published === false
|
|
1706
|
+
? { published: false, reason: String(answer.reason) }
|
|
1707
|
+
: { published: true };
|
|
1708
|
+
}
|
|
1709
|
+
catch (error) {
|
|
1710
|
+
return {
|
|
1711
|
+
published: false,
|
|
1712
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
1713
|
+
nextAction: 'The delivery is recorded; only the source memory of this task was not published. The member who opened the task can call memory.publish_task in this folder before another task reuses it; otherwise memory.sync_start inspects the result.',
|
|
1714
|
+
};
|
|
1715
|
+
}
|
|
1716
|
+
}
|
|
1696
1717
|
async onboardingRequest(operation, body) {
|
|
1697
1718
|
return this.execute(async () => {
|
|
1698
1719
|
if (operation === 'next' || operation === 'submit') {
|
|
@@ -2268,11 +2289,7 @@ export class BridgeService {
|
|
|
2268
2289
|
questionnaireId,
|
|
2269
2290
|
reason: waiver.reason,
|
|
2270
2291
|
paths,
|
|
2271
|
-
answerSource: question.answerSource
|
|
2272
|
-
? 'delegated_agent'
|
|
2273
|
-
: question.answerSource?.kind === 'host_native_relay'
|
|
2274
|
-
? question.answerSource.hostTool
|
|
2275
|
-
: 'mcp_form',
|
|
2292
|
+
answerSource: answerSourceName(question.answerSource),
|
|
2276
2293
|
...(delegatedEvidence(question)
|
|
2277
2294
|
? { delegatedDecision: delegatedEvidence(question) }
|
|
2278
2295
|
: {}),
|
|
@@ -2545,9 +2562,44 @@ export class BridgeService {
|
|
|
2545
2562
|
currentCommit: repository.git.head,
|
|
2546
2563
|
folder,
|
|
2547
2564
|
suggestedName,
|
|
2565
|
+
...(input.workItemId
|
|
2566
|
+
? await this.workItemBranches(repository.repoRoot, repository.projectId, input.workItemProjectId ?? repository.projectId, input.workItemId, preferences.data)
|
|
2567
|
+
: { planBranch: null, continueBranches: [] }),
|
|
2548
2568
|
});
|
|
2549
2569
|
});
|
|
2550
2570
|
}
|
|
2571
|
+
async workItemBranches(repoRoot, projectId, itemProjectId, workItemId, preferences) {
|
|
2572
|
+
const git = this.dependencies.repositories.git;
|
|
2573
|
+
const [plan, runs, reserved, openDeliveries] = await Promise.all([
|
|
2574
|
+
this.dependencies.client.request(endpoints.workItemPlan(itemProjectId, workItemId)),
|
|
2575
|
+
this.dependencies.client.request(`${endpoints.workItemRuns(itemProjectId, workItemId)}?offset=0&limit=50`),
|
|
2576
|
+
git.protectedBranchNames(repoRoot),
|
|
2577
|
+
this.dependencies.client
|
|
2578
|
+
.request(`${endpoints.projectDeliveries(projectId)}?state=open&limit=100`)
|
|
2579
|
+
.then((response) => response.data?.items ?? [], () => []),
|
|
2580
|
+
]);
|
|
2581
|
+
for (const preference of Object.values(preferences.branches))
|
|
2582
|
+
if (preference.branch)
|
|
2583
|
+
reserved.add(preference.branch);
|
|
2584
|
+
const used = [
|
|
2585
|
+
...new Set((runs.data?.items ?? [])
|
|
2586
|
+
.filter((run) => run.projectId === projectId && run.mode !== 'read_only')
|
|
2587
|
+
.map((run) => run.branch)
|
|
2588
|
+
.filter((branch) => !!branch && !reserved.has(branch))),
|
|
2589
|
+
];
|
|
2590
|
+
const planBranch = plan.data?.confirmed ? plan.data.branch : null;
|
|
2591
|
+
const continueBranches = !planBranch
|
|
2592
|
+
? used.slice(0, 2)
|
|
2593
|
+
: used.includes(planBranch) || (await git.branchExists(repoRoot, planBranch))
|
|
2594
|
+
? [planBranch]
|
|
2595
|
+
: [];
|
|
2596
|
+
const open = new Set(openDeliveries.map((delivery) => delivery.taskId));
|
|
2597
|
+
return {
|
|
2598
|
+
planBranch,
|
|
2599
|
+
continueBranches,
|
|
2600
|
+
openDeliveryBranches: continueBranches.filter((branch) => (runs.data?.items ?? []).some((run) => run.branch === branch && open.has(run.id))),
|
|
2601
|
+
};
|
|
2602
|
+
}
|
|
2551
2603
|
async suggestedBranchName(repoRoot, externalTaskId) {
|
|
2552
2604
|
const candidates = suggestedBranchNames(externalTaskId);
|
|
2553
2605
|
for (const candidate of candidates)
|
|
@@ -2773,8 +2825,17 @@ export class BridgeService {
|
|
|
2773
2825
|
}
|
|
2774
2826
|
if (operation === 'release') {
|
|
2775
2827
|
await this.requireDeliveredTaskWork(entry);
|
|
2828
|
+
const sourcePublication = outcome === 'delivered' && entry.taskId && entry.pendingDelivery
|
|
2829
|
+
? await this.publishDelivered(repository.repoRoot, entry.taskId)
|
|
2830
|
+
: null;
|
|
2776
2831
|
await pool.release(projectId, repository.repoRoot, input.generation, outcome === 'delivered' ? outcome : undefined);
|
|
2777
2832
|
this.poolAllocations.delete(repository.repoRoot);
|
|
2833
|
+
return asJsonValue({
|
|
2834
|
+
operation,
|
|
2835
|
+
complete: true,
|
|
2836
|
+
repoRoot: repository.repoRoot,
|
|
2837
|
+
...(sourcePublication ? { sourcePublication } : {}),
|
|
2838
|
+
});
|
|
2778
2839
|
}
|
|
2779
2840
|
if (operation === 'cancel_delivery') {
|
|
2780
2841
|
if (!entry.pendingDelivery)
|
|
@@ -2882,7 +2943,8 @@ export class BridgeService {
|
|
|
2882
2943
|
throw refuse(`This folder is held by task ${holder.externalTaskId}. Call task.branch again: its start form asks whether ${holder.externalTaskId} should move out of this folder before the new branch starts here, or opens the new task in a separate worktree.`, 'task.branch');
|
|
2883
2944
|
if (repository.git.changedPaths.length > 0)
|
|
2884
2945
|
throw refuse(`This folder has uncommitted changes, so the new branch cannot start here and ${holder.externalTaskId} stays in it. Call task.branch with a new decisionAttempt and open the task in a separate worktree.`, 'task.branch');
|
|
2885
|
-
if (
|
|
2946
|
+
if (input.base.kind !== 'existing' &&
|
|
2947
|
+
(await git.branchExists(repository.repoRoot, input.name)))
|
|
2886
2948
|
throw refuse(`Branch ${input.name} already exists, so it cannot start here and ${holder.externalTaskId} stays in this folder. Call task.branch with a new decisionAttempt and choose a new branch name.`, 'task.branch');
|
|
2887
2949
|
if (holder.generation) {
|
|
2888
2950
|
await pool.moveOut(projectId, repository.repoRoot, holder.generation, holder.oid ?? undefined);
|
|
@@ -2914,6 +2976,7 @@ export class BridgeService {
|
|
|
2914
2976
|
keepCurrent: input.keepCurrent,
|
|
2915
2977
|
inPlace: !existing && input.inPlace,
|
|
2916
2978
|
resume: Boolean(existing),
|
|
2979
|
+
continued: !existing && input.base?.kind === 'existing',
|
|
2917
2980
|
}, allocationPolicy));
|
|
2918
2981
|
allocationTimer.mark('pool_allocate');
|
|
2919
2982
|
allocationTimer.finish();
|
|
@@ -3725,6 +3788,22 @@ export class BridgeService {
|
|
|
3725
3788
|
return asJsonValue(response.data);
|
|
3726
3789
|
});
|
|
3727
3790
|
}
|
|
3791
|
+
async workItemComment(input) {
|
|
3792
|
+
return this.execute(async () => {
|
|
3793
|
+
const response = await this.dependencies.client.request(endpoints.workItemComments(input.projectId, input.workItemId), { method: 'POST', body: cleanJson(input.data) });
|
|
3794
|
+
return asJsonValue(response.data);
|
|
3795
|
+
});
|
|
3796
|
+
}
|
|
3797
|
+
async workItemComments(input) {
|
|
3798
|
+
return this.execute(async () => {
|
|
3799
|
+
const query = new URLSearchParams({
|
|
3800
|
+
offset: String(input.offset ?? 0),
|
|
3801
|
+
limit: String(input.limit ?? 50),
|
|
3802
|
+
});
|
|
3803
|
+
const response = await this.dependencies.client.request(`${endpoints.workItemComments(input.projectId, input.workItemId)}?${query}`);
|
|
3804
|
+
return asJsonValue(response.data);
|
|
3805
|
+
});
|
|
3806
|
+
}
|
|
3728
3807
|
async workItemPlan(input) {
|
|
3729
3808
|
return this.execute(async () => {
|
|
3730
3809
|
const response = await this.dependencies.client.request(endpoints.workItemPlan(input.projectId, input.workItemId));
|
|
@@ -4023,7 +4102,7 @@ export class BridgeService {
|
|
|
4023
4102
|
const livePointers = authenticated && state === 'bound'
|
|
4024
4103
|
? this.dependencies.activeContexts.list(repository.repoFingerprint)
|
|
4025
4104
|
: Promise.resolve([]);
|
|
4026
|
-
const [client, liveTasksRaw, workItems, gitPreferences, worktrees, pendingQuestionnaires, moved,] = await Promise.all([
|
|
4105
|
+
const [client, liveTasksRaw, workItems, gitPreferences, worktrees, pendingQuestionnaires, moved, deliveries,] = await Promise.all([
|
|
4027
4106
|
this.clientUpdate(authenticated),
|
|
4028
4107
|
livePointers,
|
|
4029
4108
|
authenticated && state === 'bound' && projectId
|
|
@@ -4044,6 +4123,9 @@ export class BridgeService {
|
|
|
4044
4123
|
authenticated && state === 'bound' && projectId
|
|
4045
4124
|
? this.movedAway(repository, projectId)
|
|
4046
4125
|
: Promise.resolve({}),
|
|
4126
|
+
authenticated && state === 'bound' && projectId
|
|
4127
|
+
? this.openDeliveries(projectId)
|
|
4128
|
+
: Promise.resolve(null),
|
|
4047
4129
|
]);
|
|
4048
4130
|
timer.mark('independent_lookups');
|
|
4049
4131
|
const liveTasks = liveTasksRaw.map(describePointer);
|
|
@@ -4083,6 +4165,7 @@ export class BridgeService {
|
|
|
4083
4165
|
...(pendingQuestionnaires.deferred.length > 0
|
|
4084
4166
|
? { deferredTaskStarts: pendingQuestionnaires.deferred }
|
|
4085
4167
|
: {}),
|
|
4168
|
+
...(deliveries ? { openDeliveries: deliveries } : {}),
|
|
4086
4169
|
client,
|
|
4087
4170
|
...moved,
|
|
4088
4171
|
...(incomplete && state === 'bound'
|
|
@@ -4126,6 +4209,30 @@ export class BridgeService {
|
|
|
4126
4209
|
return {};
|
|
4127
4210
|
}
|
|
4128
4211
|
}
|
|
4212
|
+
async openDeliveries(projectId) {
|
|
4213
|
+
try {
|
|
4214
|
+
const response = await this.dependencies.client.request(`${endpoints.projectDeliveries(projectId)}?state=open&limit=5`);
|
|
4215
|
+
const page = objectValue(response.data);
|
|
4216
|
+
const items = Array.isArray(page?.items) ? page.items : [];
|
|
4217
|
+
if (!items.length)
|
|
4218
|
+
return null;
|
|
4219
|
+
const pool = this.dependencies.worktreePool;
|
|
4220
|
+
return {
|
|
4221
|
+
total: page.total ?? items.length,
|
|
4222
|
+
items: await Promise.all(items.map(async (item) => {
|
|
4223
|
+
const folder = await pool?.forTask(String(objectValue(item)?.taskId));
|
|
4224
|
+
return {
|
|
4225
|
+
...objectOrEmpty(item),
|
|
4226
|
+
folderHere: folder?.pendingDelivery ? folder.repoRoot : null,
|
|
4227
|
+
};
|
|
4228
|
+
})),
|
|
4229
|
+
nextAction: 'These closed tasks have no concluded delivery. Mention them to the user in one line. For one whose folderHere is set, task.close with its taskId in that folder asks and performs the Git delivery there. Any other can be answered or concluded with task.delivery when the user asks.',
|
|
4230
|
+
};
|
|
4231
|
+
}
|
|
4232
|
+
catch {
|
|
4233
|
+
return null;
|
|
4234
|
+
}
|
|
4235
|
+
}
|
|
4129
4236
|
async actionableWorkItems(projectId) {
|
|
4130
4237
|
try {
|
|
4131
4238
|
const response = await this.dependencies.client.request(`${endpoints.workItemList(projectId)}?limit=100&actionable=true`);
|
|
@@ -4431,6 +4538,44 @@ export class BridgeService {
|
|
|
4431
4538
|
return asJsonValue({ queued: true, outboxId: queued.id, idempotencyKey });
|
|
4432
4539
|
}
|
|
4433
4540
|
}
|
|
4541
|
+
async taskDeliveryAnswer(input) {
|
|
4542
|
+
return this.execute(() => this.mutateWithOutbox('task.delivery', endpoints.taskDeliveryAnswer(input.projectId, input.taskId), cleanJson({
|
|
4543
|
+
expectedVersion: input.expectedVersion,
|
|
4544
|
+
choice: input.choice,
|
|
4545
|
+
baseBranch: input.baseBranch,
|
|
4546
|
+
answerSource: input.answerSource,
|
|
4547
|
+
})));
|
|
4548
|
+
}
|
|
4549
|
+
async taskDeliveryCancel(input) {
|
|
4550
|
+
return this.execute(() => this.mutateWithOutbox('task.delivery', endpoints.taskDeliveryCancel(input.projectId, input.taskId), { reason: input.reason }));
|
|
4551
|
+
}
|
|
4552
|
+
async taskDeliveryRecord(input) {
|
|
4553
|
+
return this.execute(async () => {
|
|
4554
|
+
const response = await this.dependencies.client.request(endpoints.taskDelivery(input.projectId, input.taskId));
|
|
4555
|
+
const folder = await this.dependencies.worktreePool?.forTask(input.taskId);
|
|
4556
|
+
return asJsonValue({
|
|
4557
|
+
record: response.data,
|
|
4558
|
+
folder: folder?.pendingDelivery ? folder.repoRoot : null,
|
|
4559
|
+
});
|
|
4560
|
+
});
|
|
4561
|
+
}
|
|
4562
|
+
async reportDeliveryOutcome(report) {
|
|
4563
|
+
const path = report.outcome === 'delivered'
|
|
4564
|
+
? endpoints.taskDeliveryDeliver(report.projectId, report.taskId)
|
|
4565
|
+
: endpoints.taskDeliveryCancel(report.projectId, report.taskId);
|
|
4566
|
+
const body = report.outcome === 'delivered'
|
|
4567
|
+
? { commit: report.commit, pushed: report.pushed }
|
|
4568
|
+
: { reason: report.reason };
|
|
4569
|
+
await this.dependencies.outbox.enqueue({
|
|
4570
|
+
operation: 'task.delivery',
|
|
4571
|
+
method: 'POST',
|
|
4572
|
+
path,
|
|
4573
|
+
body,
|
|
4574
|
+
projectId: report.projectId,
|
|
4575
|
+
idempotencyKey: sha256(stableStringify({ path, body })),
|
|
4576
|
+
});
|
|
4577
|
+
this.deliverInBackground();
|
|
4578
|
+
}
|
|
4434
4579
|
async enqueueTaskReceipt(operation, path, taskId, repoRoot, content) {
|
|
4435
4580
|
const repository = await this.dependencies.repositories.resolve(repoRoot);
|
|
4436
4581
|
return this.taskExclusive(taskId, async () => {
|
|
@@ -4479,7 +4624,11 @@ export class BridgeService {
|
|
|
4479
4624
|
for (const entry of entries) {
|
|
4480
4625
|
const owner = taskIdFromDeliverySafe(entry);
|
|
4481
4626
|
const body = objectOrEmpty(entry.body);
|
|
4482
|
-
const key = owner
|
|
4627
|
+
const key = owner
|
|
4628
|
+
? 'task:' + owner
|
|
4629
|
+
: entry.operation === 'task.delivery'
|
|
4630
|
+
? 'delivery:' + entry.path.slice(0, entry.path.lastIndexOf('/'))
|
|
4631
|
+
: 'project:' + String(body.projectId ?? 'unscoped');
|
|
4483
4632
|
const group = groups.get(key) ?? [];
|
|
4484
4633
|
group.push(entry);
|
|
4485
4634
|
groups.set(key, group);
|
|
@@ -4505,6 +4654,12 @@ export class BridgeService {
|
|
|
4505
4654
|
synchronized.push(entry.id);
|
|
4506
4655
|
}
|
|
4507
4656
|
catch (error) {
|
|
4657
|
+
if (entry.operation === 'task.delivery' &&
|
|
4658
|
+
error instanceof ApiResponseError &&
|
|
4659
|
+
(error.httpStatus === 404 || error.httpStatus === 409)) {
|
|
4660
|
+
await this.dependencies.outbox.acknowledge(entry.id);
|
|
4661
|
+
continue;
|
|
4662
|
+
}
|
|
4508
4663
|
const errorKind = classifyError(error);
|
|
4509
4664
|
await this.dependencies.outbox.markAttempt(entry.id, errorKind);
|
|
4510
4665
|
if (entry.journalRef)
|
|
@@ -4874,6 +5029,9 @@ export class BridgeService {
|
|
|
4874
5029
|
response = await this.dependencies.client.request(endpoints.taskClose, {
|
|
4875
5030
|
method: 'POST',
|
|
4876
5031
|
body,
|
|
5032
|
+
...(this.dependencies.clientVersion
|
|
5033
|
+
? { headers: { 'x-client-version': this.dependencies.clientVersion } }
|
|
5034
|
+
: {}),
|
|
4877
5035
|
});
|
|
4878
5036
|
}
|
|
4879
5037
|
catch (error) {
|
|
@@ -5371,7 +5529,7 @@ function deliveryQuestion(touchedContract, destination, publishApplicable) {
|
|
|
5371
5529
|
: {}),
|
|
5372
5530
|
afterPullRequest: 'Do not end the turn once a pull request exists. Check whether it merges cleanly, report the conflicting files if it does not, and ask whether to resolve them before touching anything.',
|
|
5373
5531
|
afterCommit: publishApplicable
|
|
5374
|
-
? '
|
|
5532
|
+
? "After the authorized delivery, call worktree.release in this folder with deliveryOutcome delivered: it publishes this task's source memory and then records the delivery for the project. Its sourcePublication says whether publication happened; call memory.publish_task only when it names a reason you can fix. A task without a worktree to release calls memory.publish_task after the commit instead."
|
|
5375
5533
|
: 'This task has nothing to publish (see sourcePublication on the close response); do not call memory.publish_task.',
|
|
5376
5534
|
});
|
|
5377
5535
|
}
|
|
@@ -49,6 +49,7 @@ export function createBridgeService(options = {}) {
|
|
|
49
49
|
const repositoryDecisions = new RepositoryDecisionStore(stateRoot);
|
|
50
50
|
const updateChoices = new UpdateChoiceStore(stateRoot);
|
|
51
51
|
const shadowNotices = new ShadowNoticeStore(stateRoot);
|
|
52
|
+
let service;
|
|
52
53
|
const worktreePool = new WorktreePool(stateRoot, {
|
|
53
54
|
...(options.ownerId ? { ownerId: options.ownerId } : {}),
|
|
54
55
|
pendingWork: async (entry) => {
|
|
@@ -59,11 +60,12 @@ export function createBridgeService(options = {}) {
|
|
|
59
60
|
pointer?.closeIntent ||
|
|
60
61
|
(await outbox.listForTask(entry.taskId)).length);
|
|
61
62
|
},
|
|
63
|
+
onDeliveryOutcome: async (report) => service?.reportDeliveryOutcome(report),
|
|
62
64
|
});
|
|
63
65
|
const gate = new VerificationGate(stateRoot, git, outbox, undefined, worktreePool);
|
|
64
66
|
const principalState = new PrincipalStateGuard(stateRoot, credentials, cache, outbox, activeContexts, gate);
|
|
65
67
|
const languages = new LanguageStore(stateRoot);
|
|
66
|
-
return new BridgeService({
|
|
68
|
+
return (service = new BridgeService({
|
|
67
69
|
releaseNotes: new ReleaseNotes(stateRoot, client, credentials),
|
|
68
70
|
worktreePool,
|
|
69
71
|
worktreePolicy: new WorktreePolicyCache(stateRoot),
|
|
@@ -83,6 +85,6 @@ export function createBridgeService(options = {}) {
|
|
|
83
85
|
onboarding: new OnboardingStore(stateRoot),
|
|
84
86
|
clientVersion: options.clientVersion === undefined ? config.clientVersion : options.clientVersion,
|
|
85
87
|
principalState,
|
|
86
|
-
});
|
|
88
|
+
}));
|
|
87
89
|
}
|
|
88
90
|
//# sourceMappingURL=create-bridge-service.js.map
|
|
@@ -170,6 +170,13 @@ const answerSourceSchema = z.union([
|
|
|
170
170
|
impact: z.enum(['routine', 'critical']),
|
|
171
171
|
}),
|
|
172
172
|
]);
|
|
173
|
+
export function answerSourceName(source) {
|
|
174
|
+
return source?.kind === 'delegated_agent'
|
|
175
|
+
? 'delegated_agent'
|
|
176
|
+
: source?.kind === 'host_native_relay'
|
|
177
|
+
? source.hostTool
|
|
178
|
+
: 'mcp_form';
|
|
179
|
+
}
|
|
173
180
|
const storedAnswerSchema = z.strictObject({
|
|
174
181
|
requestKey: z.string().regex(/^questionnaire_[0-9a-f]{64}$/),
|
|
175
182
|
answeredAt: z.string().datetime(),
|
|
@@ -18,6 +18,7 @@ const startCopy = {
|
|
|
18
18
|
fetched: (role, source) => `The project's ${role} branch ${source} is fetched from the remote and the new branch starts from that exact commit.`,
|
|
19
19
|
pinned: (source) => `The new branch starts where this folder is right now, ${source}. Nothing is fetched.`,
|
|
20
20
|
typedBranch: 'Write a branch name on origin below. It is fetched and the new branch starts from it.',
|
|
21
|
+
continued: (branch) => `No new branch is created. The work continues from the newest commit of ${branch}, fetched from origin when this clone lacks it.`,
|
|
21
22
|
suggested: 'The branch is created with this name.',
|
|
22
23
|
typedName: 'Write the name below. A name that already exists is refused and the form is asked again.',
|
|
23
24
|
},
|
|
@@ -37,6 +38,7 @@ const startCopy = {
|
|
|
37
38
|
fetched: (role, source) => `Projenin ${role} dalı ${source} uzak sunucudan çekilir ve yeni dal tam o commit’ten başlar.`,
|
|
38
39
|
pinned: (source) => `Yeni dal, bu klasörün şu an üzerinde olduğu ${source} noktasından başlar. Hiçbir şey çekilmez.`,
|
|
39
40
|
typedBranch: 'origin üzerindeki dal adını aşağıya yaz. O dal çekilir ve yeni dal ondan başlar.',
|
|
41
|
+
continued: (branch) => `Yeni dal açılmaz. İş ${branch} dalının en yeni commit’inden sürer; bu klonda yoksa origin’den çekilir.`,
|
|
40
42
|
suggested: 'Dal bu adla açılır.',
|
|
41
43
|
typedName: 'Adı aşağıya yaz. Zaten var olan bir ad kabul edilmez, form yeniden sorulur.',
|
|
42
44
|
},
|
|
@@ -47,6 +49,8 @@ const startWording = z.strictObject({
|
|
|
47
49
|
message: text,
|
|
48
50
|
keepContext: text,
|
|
49
51
|
newBranch: text,
|
|
52
|
+
newBranchOrContinue: text,
|
|
53
|
+
continuePlan: text,
|
|
50
54
|
example: text,
|
|
51
55
|
stay: text,
|
|
52
56
|
base: z.strictObject({
|
|
@@ -56,6 +60,9 @@ const startWording = z.strictObject({
|
|
|
56
60
|
other: text,
|
|
57
61
|
otherDetail: text,
|
|
58
62
|
fetchDetail: text,
|
|
63
|
+
continue: text,
|
|
64
|
+
continueDetail: text,
|
|
65
|
+
continueOpenDetail: text,
|
|
59
66
|
field: text,
|
|
60
67
|
}),
|
|
61
68
|
location: z.strictObject({
|
|
@@ -92,6 +99,8 @@ export function sourceLabel(base, currentBranch) {
|
|
|
92
99
|
return base.remote + '/' + base.branch;
|
|
93
100
|
if (base.kind === 'local')
|
|
94
101
|
return base.ref;
|
|
102
|
+
if (base.kind === 'existing')
|
|
103
|
+
return base.branch;
|
|
95
104
|
if (base.kind === 'current')
|
|
96
105
|
return (currentBranch ?? 'HEAD') + (base.commit ? ' (' + base.commit.slice(0, 7) + ')' : '');
|
|
97
106
|
return base.kind;
|
|
@@ -135,14 +144,20 @@ export function taskStartDefinition(facts) {
|
|
|
135
144
|
...option,
|
|
136
145
|
label: base?.kind === 'remote'
|
|
137
146
|
? sourceLabel(base, facts.currentBranch)
|
|
147
|
+
: base?.kind === 'existing'
|
|
148
|
+
? format(copy.base.continue, language, { branch: base.branch })
|
|
149
|
+
: option.id === 'current'
|
|
150
|
+
? format(copy.base.local, language, { branch })
|
|
151
|
+
: copy.base.other,
|
|
152
|
+
description: base?.kind === 'existing'
|
|
153
|
+
? facts.openDeliveryBranches?.includes(base.branch)
|
|
154
|
+
? copy.base.continueOpenDetail
|
|
155
|
+
: copy.base.continueDetail
|
|
138
156
|
: option.id === 'current'
|
|
139
|
-
?
|
|
140
|
-
:
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
: option.id === 'other'
|
|
144
|
-
? copy.base.otherDetail
|
|
145
|
-
: copy.base.fetchDetail,
|
|
157
|
+
? copy.base.localDetail
|
|
158
|
+
: option.id === 'other'
|
|
159
|
+
? copy.base.otherDetail
|
|
160
|
+
: copy.base.fetchDetail,
|
|
146
161
|
};
|
|
147
162
|
}),
|
|
148
163
|
textField: { ...question.textField, title: copy.base.field },
|
|
@@ -184,8 +199,11 @@ export function taskStartDefinition(facts) {
|
|
|
184
199
|
message: copy.message,
|
|
185
200
|
context: keepOnly
|
|
186
201
|
? copy.keepContext
|
|
187
|
-
:
|
|
188
|
-
(
|
|
202
|
+
: binding.base?.kind === 'existing'
|
|
203
|
+
? format(copy.continuePlan, language, { branch: binding.base.branch })
|
|
204
|
+
: format(Object.values(binding.bases ?? {}).some((base) => base.kind === 'existing')
|
|
205
|
+
? copy.newBranchOrContinue
|
|
206
|
+
: copy.newBranch, language, { name: binding.name }) + (binding.base ? ' · ' + sourceLabel(binding.base, facts.currentBranch) : ''),
|
|
189
207
|
example: keepOnly ? format(copy.stay, language, { source: current }) : copy.example,
|
|
190
208
|
questions,
|
|
191
209
|
binding: JSON.parse(JSON.stringify(binding)),
|
|
@@ -269,17 +287,27 @@ function legacyTaskStartDefinition(facts) {
|
|
|
269
287
|
label: base.remote + '/' + base.branch + ' (' + role + ')',
|
|
270
288
|
description: copy.fetched(copy.roles[role], base.remote + '/' + base.branch),
|
|
271
289
|
});
|
|
290
|
+
const continued = facts.continueBranches ?? [];
|
|
272
291
|
binding.bases = {
|
|
273
292
|
current: {
|
|
274
293
|
kind: 'current',
|
|
275
294
|
...(facts.currentCommit ? { commit: facts.currentCommit } : {}),
|
|
276
295
|
},
|
|
277
296
|
...Object.fromEntries(roles.map(({ role, base }) => [role, base])),
|
|
297
|
+
...Object.fromEntries(continued.map((branch, index) => [
|
|
298
|
+
'continue_' + (index + 1),
|
|
299
|
+
{ kind: 'existing', branch },
|
|
300
|
+
])),
|
|
278
301
|
};
|
|
279
302
|
questions.push({
|
|
280
303
|
id: 'base',
|
|
281
304
|
message: tr ? 'Hangi daldan başlasın?' : 'Which branch should it start from?',
|
|
282
305
|
options: [
|
|
306
|
+
...continued.map((branch, index) => ({
|
|
307
|
+
id: 'continue_' + (index + 1),
|
|
308
|
+
label: (tr ? 'Var olan dalı sürdür: ' : 'Continue the existing branch ') + branch,
|
|
309
|
+
description: copy.continued(branch),
|
|
310
|
+
})),
|
|
283
311
|
...roles.slice(0, 1).map(roleOption),
|
|
284
312
|
{
|
|
285
313
|
id: 'current',
|
|
@@ -422,12 +450,14 @@ export function taskStartChoice(record) {
|
|
|
422
450
|
? binding.bases?.[baseAnswer.choice]
|
|
423
451
|
: undefined);
|
|
424
452
|
const nameAnswer = record.answers.name;
|
|
425
|
-
const name =
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
?
|
|
430
|
-
:
|
|
453
|
+
const name = base?.kind === 'existing'
|
|
454
|
+
? base.branch
|
|
455
|
+
: (binding.name ??
|
|
456
|
+
(nameAnswer?.choice === 'custom'
|
|
457
|
+
? nameAnswer.text
|
|
458
|
+
: nameAnswer?.choice === 'suggested'
|
|
459
|
+
? binding.suggestedName
|
|
460
|
+
: undefined));
|
|
431
461
|
if (!base || !name)
|
|
432
462
|
return null;
|
|
433
463
|
return {
|