engineering-memory 1.11.11 → 1.11.13

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.
@@ -5,6 +5,7 @@ import { configuredBase, sourceLabel, taskStartChoice, taskStartDefinition, } fr
5
5
  import { sha256, stableStringify } from '../utilities/hash.js';
6
6
  import { unreadableWorktree } from '../runtime/worktree-pool.js';
7
7
  import { askQuestionnaire } from './questionnaire-tools.js';
8
+ import { languageTag } from '../runtime/questionnaire-store.js';
8
9
  const repo = z.string().min(1);
9
10
  const branch = z.string().min(1).max(255);
10
11
  const remote = z.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9._-]{0,79}$/);
@@ -49,6 +50,170 @@ const policy = {
49
50
  heartbeatSeconds: z.number().int().min(5).max(300),
50
51
  inactivitySeconds: z.number().int().min(60).max(86400),
51
52
  };
53
+ const preferenceCopy = {
54
+ en: {
55
+ message: 'Save the shared branch preferences of this project? Nothing is saved until you answer.',
56
+ context: 'These branches belong to the whole project: every member who starts a task here is offered them as the branch the task starts from. Only a project or organization administrator can change them. No branch is created, moved or deleted; only the preference is recorded.',
57
+ example: 'Once the development role points at origin/develop, the start form of the next task offers origin/develop as its first choice.',
58
+ roles: { development: 'development', production: 'production', test: 'test' },
59
+ ask: (role, source) => source
60
+ ? `Save ${source} as the shared ${role} branch?`
61
+ : `Record that this project has no ${role} branch?`,
62
+ save: 'Save this choice',
63
+ saved: 'This branch is recorded as the project default now, for every member.',
64
+ leave: 'Leave unchanged',
65
+ unchanged: 'Nothing changes. This role stays as it is and can be answered another time.',
66
+ },
67
+ tr: {
68
+ message: 'Projenin ortak dal tercihleri kaydedilsin mi? Sen cevaplayana kadar hiçbir şey kaydedilmez.',
69
+ context: 'Bu dallar projenin tamamı için geçerli: burada görev başlatan herkese, görevin başlayacağı dal olarak bunlar önerilir. Yalnızca proje ya da organizasyon yöneticisi değiştirebilir. Hiçbir dal oluşturulmaz, taşınmaz veya silinmez; yalnızca tercih kaydedilir.',
70
+ example: 'Geliştirme dalı origin/develop olarak kaydedilirse, bir sonraki görevin başlangıç formunda ilk seçenek origin/develop olur.',
71
+ roles: { development: 'Geliştirme', production: 'Canlı', test: 'Test' },
72
+ ask: (role, source) => source
73
+ ? `${role} dalı ${source} olarak kaydedilsin mi?`
74
+ : `Bu projede ${role.toLocaleLowerCase('tr')} dalı olmadığı kaydedilsin mi?`,
75
+ save: 'Bu tercihi kaydet',
76
+ saved: 'Bu dal şimdi proje varsayılanı olarak kaydedilir; ekipteki herkes onu görür.',
77
+ leave: 'Şimdilik değiştirme',
78
+ unchanged: 'Hiçbir şey değişmez. Bu rol olduğu gibi kalır, sonra yeniden sorulabilir.',
79
+ },
80
+ };
81
+ const fallbackCopy = {
82
+ en: {
83
+ message: (remote, local, inPlace) => `The remote ${remote} could not be fetched. Should the task start from local ${local} ${inPlace ? 'in this folder' : 'in a separate worktree'}?`,
84
+ context: 'The branch you chose cannot be reached, so its exact commit is unavailable. The local copy is whatever was fetched last: commits pushed to the remote since then are not in it. Nothing is created until you answer.',
85
+ example: (remote, local) => `Continuing creates the new branch at ${local}; ${remote} can be merged into it once the network is back.`,
86
+ approve: 'Continue',
87
+ approved: (local) => `The new branch is created at ${local} now and the task starts.`,
88
+ defer: 'Not now',
89
+ deferred: 'Nothing is created or reserved. The task waits, and it can be started again once the remote answers.',
90
+ },
91
+ tr: {
92
+ message: (remote, local, inPlace) => `${remote} uzak dalına ulaşılamadı. Görev, yereldeki ${local} üzerinden ${inPlace ? 'bu klasörde' : 'ayrı bir worktree’de'} başlasın mı?`,
93
+ context: 'Seçtiğin dala ulaşılamadığı için tam commit’i alınamıyor. Yerel kopya, en son çekilen hâli gösterir: o günden sonra uzak dala giden commit’ler burada yok. Sen cevaplayana kadar hiçbir şey oluşturulmaz.',
94
+ example: (remote, local) => `Devam edersen yeni dal ${local} üzerinden açılır; ağ düzeldiğinde ${remote} dalını bu dala birleştirebilirsin.`,
95
+ approve: 'Devam et',
96
+ approved: (local) => `Yeni dal şimdi ${local} üzerinden açılır ve görev başlar.`,
97
+ defer: 'Şimdilik başlatma',
98
+ deferred: 'Hiçbir şey oluşturulmaz, hiçbir yer ayrılmaz. Görev bekler; uzak dala ulaşıldığında yeniden başlatılabilir.',
99
+ },
100
+ };
101
+ const forgetCopy = {
102
+ en: {
103
+ message: (task, folder) => `The ${folder} folder of task ${task} can no longer be read as a Git checkout. Should Engineering Memory stop tracking it?`,
104
+ context: (pendingDelivery) => 'A checkout that cannot be read is still counted against the worktree limit although nothing can use it; forgetting it frees that slot. No file in the folder is touched or deleted, and the branch stays.' +
105
+ (pendingDelivery
106
+ ? ' That task closed without its delivery being settled, so any undelivered work exists only in that folder.'
107
+ : ''),
108
+ example: (task, folder) => `After this, ${task} no longer appears in the worktree list and the ${folder} folder stays on disk exactly as it is.`,
109
+ forget: 'Stop tracking it; leave the files alone',
110
+ forgotten: 'The record is removed and the slot is free again. The folder, its files and its branch stay untouched.',
111
+ keep: 'Keep tracking it',
112
+ kept: 'Nothing changes. The record stays and keeps counting against the limit.',
113
+ },
114
+ tr: {
115
+ message: (task, folder) => `${task} görevinin ${folder} klasörü artık bir Git çalışma kopyası olarak okunamıyor. Engineering Memory bu klasörü takip etmeyi bıraksın mı?`,
116
+ context: (pendingDelivery) => 'Okunamayan bir çalışma kopyası hiçbir işe yaramadığı hâlde worktree sınırına sayılmaya devam eder; takipten çıkarılırsa o yer boşalır. Klasördeki hiçbir dosyaya dokunulmaz, hiçbiri silinmez; dal da kalır.' +
117
+ (pendingDelivery
118
+ ? ' Bu görev teslimi sonuçlanmadan kapandı; teslim edilmemiş iş varsa yalnızca bu klasörde duruyor.'
119
+ : ''),
120
+ example: (task, folder) => `Bundan sonra ${task} worktree listesinde görünmez, ${folder} klasörü ise diskte olduğu gibi kalır.`,
121
+ forget: 'Takip etmeyi bırak; dosyalara dokunma',
122
+ forgotten: 'Kayıt silinir ve sınırdaki yer boşalır. Klasör, içindeki dosyalar ve dal olduğu gibi kalır.',
123
+ keep: 'Takip etmeye devam et',
124
+ kept: 'Hiçbir şey değişmez. Kayıt durur ve sınıra sayılmaya devam eder.',
125
+ },
126
+ };
127
+ const retireCopy = {
128
+ en: {
129
+ missing: (folder) => `The ${folder} folder is gone but its worktree record is still here. Remove the record?`,
130
+ message: (folder, branch) => `${folder} is a worktree left in the old location (branch ${branch}). Remove the folder?`,
131
+ context: (missing) => 'Folders in the old location are no longer part of the pool: they are never reused and do not count toward the limit.' +
132
+ (missing
133
+ ? ' Only the record is left, so there is no folder to remove and no file is read or deleted.'
134
+ : ' The branch and its commits stay. Git removes only a clean folder, and the files it ignores go with it.'),
135
+ example: (folder, branch, missing) => missing
136
+ ? `After this, ${folder} is gone from the worktree list and nothing on disk changes.`
137
+ : `After this, ${folder} is gone from the worktree list and branch ${branch} still holds every commit made on it.`,
138
+ retire: 'Remove the folder',
139
+ retired: (missing) => missing
140
+ ? 'The stale record is removed. Nothing on disk changes.'
141
+ : 'Git removes the folder and Engineering Memory drops its record. The branch and its commits stay.',
142
+ keep: 'Keep it',
143
+ kept: 'Nothing changes. The folder and its record stay exactly as they are.',
144
+ },
145
+ tr: {
146
+ missing: (folder) => `${folder} klasörü artık yok ama worktree kaydı duruyor. Kayıt silinsin mi?`,
147
+ message: (folder, branch) => `${folder}, eski konumda kalmış bir worktree (dal ${branch}). Klasör kaldırılsın mı?`,
148
+ context: (missing) => 'Eski konumdaki klasörler artık havuzun parçası değil: yeniden kullanılmıyorlar ve sınıra da sayılmıyorlar.' +
149
+ (missing
150
+ ? ' Geriye yalnızca kayıt kaldı; kaldırılacak bir klasör yok, hiçbir dosya okunmaz veya silinmez.'
151
+ : ' Dal ve üzerindeki commit’ler kalır. Git yalnızca temiz bir klasörü kaldırır; kaldırırken .gitignore kapsamındaki dosyalar da gider.'),
152
+ example: (folder, branch, missing) => missing
153
+ ? `Bundan sonra ${folder} worktree listesinde görünmez; diskte hiçbir şey değişmez.`
154
+ : `Bundan sonra ${folder} worktree listesinde görünmez; ${branch} dalındaki commit’ler ise yerinde durur.`,
155
+ retire: 'Klasörü kaldır',
156
+ retired: (missing) => missing
157
+ ? 'Boşta kalan kayıt silinir. Diskte hiçbir şey değişmez.'
158
+ : 'Git klasörü kaldırır, Engineering Memory de kaydını siler. Dal ve commit’ler kalır.',
159
+ keep: 'Kalsın',
160
+ kept: 'Hiçbir şey değişmez. Klasör de kaydı da olduğu gibi kalır.',
161
+ },
162
+ };
163
+ const unownedCopy = {
164
+ en: {
165
+ message: (folder) => `Nobody is recorded as using the ${folder} folder. If the agent that worked there has stopped and its delivery is finished, should the clean checkout become reusable?`,
166
+ context: 'This checkout was found without an ownership record, so it is protected and handed to no task. Releasing it runs the safety checks again first: a folder with uncommitted work is refused. No file and no branch is ever deleted.',
167
+ example: (commit) => `Once it is released, the next task can start in that folder, and the work committed at ${commit} stays on its branch.`,
168
+ release: 'Prior work finished; release after safety checks',
169
+ released: 'The safety checks run and, if they pass, the folder returns to the pool.',
170
+ preserve: 'Keep protected',
171
+ preserved: 'Nothing changes. The folder stays protected and is given to no task.',
172
+ },
173
+ tr: {
174
+ message: (folder) => `${folder} klasörünü kimin kullandığı kayıtlı değil. Burada çalışan ajan durduysa ve teslimi bittiyse, temiz klasör yeniden kullanılabilir hâle gelsin mi?`,
175
+ context: 'Bu çalışma kopyası sahiplik kaydı olmadan bulundu; bu yüzden korunuyor ve hiçbir göreve verilmiyor. Serbest bırakmadan önce güvenlik kontrolleri yeniden çalışır: kaydedilmemiş iş varsa işlem yapılmaz. Hiçbir dosya ve hiçbir dal silinmez.',
176
+ example: (commit) => `Serbest bırakılırsa sıradaki görev bu klasörde başlayabilir; ${commit} commit’ine kaydedilmiş iş dalında durmaya devam eder.`,
177
+ release: 'Önceki iş bitti; güvenlik kontrollerinden sonra kullanılabilir yap',
178
+ released: 'Güvenlik kontrolleri çalışır; geçerse klasör havuza döner.',
179
+ preserve: 'Korumaya devam et',
180
+ preserved: 'Hiçbir şey değişmez. Klasör korunur ve hiçbir göreve verilmez.',
181
+ },
182
+ };
183
+ const cancelCopy = {
184
+ en: {
185
+ message: (task) => `${task} will not be delivered. What should happen to this folder?`,
186
+ context: (clean, managed) => 'A cancelled delivery leaves the folder reserved for this task until you say what happens to it, and a reserved folder keeps counting against the worktree limit. The branch and every commit on it stay either way.' +
187
+ (clean
188
+ ? ''
189
+ : managed
190
+ ? ' The folder has uncommitted changes; saving writes them to a Git ref they can be restored from.'
191
+ : ' The folder has uncommitted changes; this is your own folder, so its files are never touched when it is freed.'),
192
+ example: 'Freeing the folder lets the next task start in it; keeping it leaves it reserved.',
193
+ release: 'Free the folder; the branch stays',
194
+ released: 'The folder returns to the pool. Its files are left alone and the branch stays.',
195
+ save: 'Save the changes and free the folder',
196
+ saved: 'The uncommitted changes are written to a Git ref first, then the folder is freed. They can be restored from that ref.',
197
+ keep: 'Keep the folder as it is',
198
+ kept: 'Nothing changes. The folder stays reserved for this task.',
199
+ },
200
+ tr: {
201
+ message: (task) => `${task} teslim edilmeyecek. Bu klasör ne olsun?`,
202
+ context: (clean, managed) => 'Teslimi iptal edilen bir klasör, sen ne olacağını söyleyene kadar bu göreve ayrılmış kalır ve ayrılmış klasör worktree sınırına sayılmaya devam eder. Dal ve üzerindeki commit’ler her hâlükârda durur.' +
203
+ (clean
204
+ ? ''
205
+ : managed
206
+ ? ' Klasörde kaydedilmemiş değişiklikler var; kaydedilirse bir Git ref’ine yazılır ve oradan geri alınabilir.'
207
+ : ' Klasörde kaydedilmemiş değişiklikler var; burası senin kendi klasörün olduğu için boşaltılsa da dosyalara dokunulmaz.'),
208
+ example: 'Klasör boşaltılırsa sıradaki görev burada başlayabilir; kalsın dersen bu göreve ayrılmış hâlde durur.',
209
+ release: 'Klasörü boşalt; branch kalsın',
210
+ released: 'Klasör havuza döner. Dosyalarına dokunulmaz, dal kalır.',
211
+ save: 'Değişiklikleri kaydedip klasörü boşalt',
212
+ saved: 'Kaydedilmemiş değişiklikler önce bir Git ref’ine yazılır, sonra klasör boşaltılır. Değişiklikler o ref’ten geri alınabilir.',
213
+ keep: 'Klasör olduğu gibi kalsın',
214
+ kept: 'Hiçbir şey değişmez. Klasör bu göreve ayrılmış hâlde kalır.',
215
+ },
216
+ };
52
217
  const baseSchema = z.discriminatedUnion('kind', [
53
218
  z.strictObject({
54
219
  kind: z.literal('current'),
@@ -79,10 +244,11 @@ export function registerWorktreeTools(server, service) {
79
244
  development: preference.optional(),
80
245
  production: preference.optional(),
81
246
  test: preference.optional(),
82
- language: z.enum(['tr', 'en']).default('en'),
247
+ language: languageTag.optional(),
83
248
  presentation,
84
249
  }),
85
250
  }, async (input, context) => {
251
+ const language = (await service.language(input.language))?.startsWith('tr') ? 'tr' : 'en';
86
252
  const current = await service.projectGitPreferences(input);
87
253
  if (!current.ok)
88
254
  return output(current);
@@ -100,18 +266,9 @@ export function registerWorktreeTools(server, service) {
100
266
  const roles = ['development', 'production', 'test'].filter((role) => Boolean(input[role]));
101
267
  if (roles.length === 0)
102
268
  return output({ ok: true, data: { unchanged: true } });
103
- const roleLabel = (role) => {
269
+ const source = (role, none) => {
104
270
  const value = input[role];
105
- return input.language === 'tr'
106
- ? role +
107
- ' dal tercihi: ' +
108
- (value.branch ? value.remote + '/' + value.branch : 'henüz yok') +
109
- '. Ortak proje ayarını kaydedelim mi?'
110
- : 'Save the shared ' +
111
- role +
112
- ' branch preference: ' +
113
- (value.branch ? value.remote + '/' + value.branch : 'none yet') +
114
- '?';
271
+ return value.branch ? value.remote + '/' + value.branch : none;
115
272
  };
116
273
  const digest = sha256(stableStringify({
117
274
  projectId: input.projectId,
@@ -121,29 +278,44 @@ export function registerWorktreeTools(server, service) {
121
278
  ...(input.decisionAttempt ? { decisionAttempt: input.decisionAttempt } : {}),
122
279
  }));
123
280
  const questionnaireId = 'project-branches-' + digest;
124
- const definition = {
125
- repoRoot: input.repoRoot,
281
+ const definition = (turkish) => {
282
+ const copy = preferenceCopy[turkish ? 'tr' : 'en'];
283
+ return {
284
+ questionnaireId,
285
+ language: turkish ? 'tr' : 'en',
286
+ message: copy.message,
287
+ context: copy.context,
288
+ example: copy.example,
289
+ questions: roles.map((role) => ({
290
+ id: role,
291
+ message: copy.ask(copy.roles[role], source(role, null)),
292
+ options: [
293
+ { id: 'approve', label: copy.save, description: copy.saved },
294
+ { id: 'defer', label: copy.leave, description: copy.unchanged },
295
+ ],
296
+ })),
297
+ };
298
+ };
299
+ const previous = (turkish) => ({
126
300
  questionnaireId,
127
- presentation: input.presentation,
128
- language: input.language,
129
- message: input.language === 'tr'
301
+ language: turkish ? 'tr' : 'en',
302
+ message: turkish
130
303
  ? 'Ortak dal tercihlerini kaydedelim mi? Hiçbir şey kaydedilmeden önce onayını bekliyor.'
131
304
  : 'Save the shared branch preferences? Nothing is saved until you answer.',
132
305
  questions: roles.map((role) => ({
133
306
  id: role,
134
- message: roleLabel(role),
307
+ message: turkish
308
+ ? role +
309
+ ' dal tercihi: ' +
310
+ source(role, 'henüz yok') +
311
+ '. Ortak proje ayarını kaydedelim mi?'
312
+ : 'Save the shared ' + role + ' branch preference: ' + source(role, 'none yet') + '?',
135
313
  options: [
136
- {
137
- id: 'approve',
138
- label: input.language === 'tr' ? 'Bu tercihi kaydet' : 'Save this choice',
139
- },
140
- {
141
- id: 'defer',
142
- label: input.language === 'tr' ? 'Şimdilik değiştirme' : 'Leave unchanged',
143
- },
314
+ { id: 'approve', label: turkish ? 'Bu tercihi kaydet' : 'Save this choice' },
315
+ { id: 'defer', label: turkish ? 'Şimdilik değiştirme' : 'Leave unchanged' },
144
316
  ],
145
317
  })),
146
- };
318
+ });
147
319
  const owner = {
148
320
  tool: 'project.set_git_preferences',
149
321
  ...(input.decisionAttempt ? { decisionAttempt: input.decisionAttempt } : {}),
@@ -156,7 +328,11 @@ export function registerWorktreeTools(server, service) {
156
328
  language: input.language,
157
329
  }),
158
330
  };
159
- const form = await askQuestionnaire(server, service, definition, context, [], owner);
331
+ const form = await askQuestionnaire(server, service, {
332
+ ...definition(language === 'tr'),
333
+ repoRoot: input.repoRoot,
334
+ presentation: input.presentation,
335
+ }, context, [definition(language !== 'tr'), previous(true), previous(false)], owner);
160
336
  const record = await service.questionnaireResume({
161
337
  repoRoot: input.repoRoot,
162
338
  questionnaireId,
@@ -188,10 +364,11 @@ export function registerWorktreeTools(server, service) {
188
364
  name: branch.optional(),
189
365
  base: baseSchema.optional(),
190
366
  keepCurrent: z.boolean().optional(),
191
- language: z.enum(['tr', 'en']).default('en'),
367
+ language: languageTag.optional(),
192
368
  presentation,
193
369
  }),
194
370
  }, async (input, context) => {
371
+ const language = (await service.language(input.language))?.startsWith('tr') ? 'tr' : 'en';
195
372
  const preflight = await service.taskBranchPreflight(input);
196
373
  if (!preflight.ok)
197
374
  return output(preflight);
@@ -210,7 +387,7 @@ export function registerWorktreeTools(server, service) {
210
387
  recovery: 'task.branch',
211
388
  },
212
389
  });
213
- const definition = taskStartDefinition({ ...input, ...flight });
390
+ const { previous, ...definition } = taskStartDefinition({ ...input, ...flight, language });
214
391
  const owner = {
215
392
  tool: 'task.branch',
216
393
  externalTaskId: input.externalTaskId,
@@ -222,7 +399,7 @@ export function registerWorktreeTools(server, service) {
222
399
  keepCurrent: input.keepCurrent,
223
400
  }),
224
401
  };
225
- const form = await askQuestionnaire(server, service, { ...definition, repoRoot: input.repoRoot, presentation: input.presentation }, context, [], owner);
402
+ const form = await askQuestionnaire(server, service, { ...definition, repoRoot: input.repoRoot, presentation: input.presentation }, context, [previous], owner);
226
403
  const record = await service.questionnaireResume({
227
404
  repoRoot: input.repoRoot,
228
405
  questionnaireId: definition.questionnaireId,
@@ -270,16 +447,28 @@ export function registerWorktreeTools(server, service) {
270
447
  const fallbackId = 'task-branch-fallback-' +
271
448
  sha256(stableStringify({ ...request, base: fallbackBase, inPlace: choice.inPlace }));
272
449
  const fallback = async () => {
273
- const tr = input.language === 'tr';
274
450
  const from = sourceLabel(choice.base, flight.currentBranch);
275
451
  const local = sourceLabel(fallbackBase, flight.currentBranch);
276
- const fallbackForm = await askQuestionnaire(server, service, {
277
- repoRoot: input.repoRoot,
452
+ const definition = (turkish) => {
453
+ const copy = fallbackCopy[turkish ? 'tr' : 'en'];
454
+ return {
455
+ questionnaireId: fallbackId,
456
+ language: turkish ? 'tr' : 'en',
457
+ allowFreeText: false,
458
+ message: copy.message(from, local, choice.inPlace),
459
+ context: copy.context,
460
+ example: copy.example(from, local),
461
+ options: [
462
+ { id: 'approve', label: copy.approve, description: copy.approved(local) },
463
+ { id: 'defer', label: copy.defer, description: copy.deferred },
464
+ ],
465
+ };
466
+ };
467
+ const previousFallback = (turkish) => ({
278
468
  questionnaireId: fallbackId,
279
- language: input.language,
280
- presentation: input.presentation,
469
+ language: turkish ? 'tr' : 'en',
281
470
  allowFreeText: false,
282
- message: tr
471
+ message: turkish
283
472
  ? from +
284
473
  ' uzak dalına ulaşılamadı. Yereldeki ' +
285
474
  local +
@@ -292,10 +481,15 @@ export function registerWorktreeTools(server, service) {
292
481
  local +
293
482
  (choice.inPlace ? ' in this folder?' : ' in a separate worktree?'),
294
483
  options: [
295
- { id: 'approve', label: tr ? 'Devam et' : 'Continue' },
296
- { id: 'defer', label: tr ? 'Şimdilik başlatma' : 'Not now' },
484
+ { id: 'approve', label: turkish ? 'Devam et' : 'Continue' },
485
+ { id: 'defer', label: turkish ? 'Şimdilik başlatma' : 'Not now' },
297
486
  ],
298
- }, context, [], owner);
487
+ });
488
+ const fallbackForm = await askQuestionnaire(server, service, {
489
+ ...definition(language === 'tr'),
490
+ repoRoot: input.repoRoot,
491
+ presentation: input.presentation,
492
+ }, context, [definition(language !== 'tr'), previousFallback(true), previousFallback(false)], owner);
299
493
  const answer = await service.questionnaireResume({
300
494
  repoRoot: input.repoRoot,
301
495
  questionnaireId: fallbackId,
@@ -344,9 +538,10 @@ export function registerWorktreeTools(server, service) {
344
538
  retireLegacy: z.boolean().optional(),
345
539
  forgetUnreadable: z.strictObject({ externalTaskId: z.string().min(2).max(160) }).optional(),
346
540
  decisionAttempt: z.number().int().min(0).default(0),
347
- language: z.enum(['tr', 'en']).default('en'),
541
+ language: languageTag.optional(),
348
542
  }),
349
543
  }, async (input, context) => {
544
+ const language = (await service.language(input.language))?.startsWith('tr') ? 'tr' : 'en';
350
545
  const inspection = await service.worktreeOperation('reconcile', input);
351
546
  if (!inspection.ok ||
352
547
  (!input.releaseUnowned && !input.retireLegacy && !input.forgetUnreadable))
@@ -365,13 +560,26 @@ export function registerWorktreeTools(server, service) {
365
560
  });
366
561
  const questionnaireId = forgetUnreadableQuestionId(item, input.decisionAttempt);
367
562
  const folder = basename(item.repoRoot);
368
- const tr = input.language === 'tr';
369
- const form = await askQuestionnaire(server, service, {
370
- repoRoot: input.repoRoot,
563
+ const definition = (turkish) => {
564
+ const copy = forgetCopy[turkish ? 'tr' : 'en'];
565
+ return {
566
+ questionnaireId,
567
+ language: turkish ? 'tr' : 'en',
568
+ allowFreeText: false,
569
+ message: copy.message(item.externalTaskId, folder),
570
+ context: copy.context(item.pendingDelivery === true),
571
+ example: copy.example(item.externalTaskId, folder),
572
+ options: [
573
+ { id: 'forget', label: copy.forget, description: copy.forgotten },
574
+ { id: 'keep', label: copy.keep, description: copy.kept },
575
+ ],
576
+ };
577
+ };
578
+ const previous = (turkish) => ({
371
579
  questionnaireId,
372
- language: input.language,
580
+ language: turkish ? 'tr' : 'en',
373
581
  allowFreeText: false,
374
- message: tr
582
+ message: turkish
375
583
  ? item.externalTaskId +
376
584
  ' görevinin ' +
377
585
  folder +
@@ -390,13 +598,14 @@ export function registerWorktreeTools(server, service) {
390
598
  options: [
391
599
  {
392
600
  id: 'forget',
393
- label: tr
601
+ label: turkish
394
602
  ? 'Takip etmeyi bırak; dosyalara dokunma'
395
603
  : 'Stop tracking it; leave the files alone',
396
604
  },
397
- { id: 'keep', label: tr ? 'Takip etmeye devam et' : 'Keep tracking it' },
605
+ { id: 'keep', label: turkish ? 'Takip etmeye devam et' : 'Keep tracking it' },
398
606
  ],
399
- }, context, [], {
607
+ });
608
+ const form = await askQuestionnaire(server, service, { ...definition(language === 'tr'), repoRoot: input.repoRoot }, context, [definition(language !== 'tr'), previous(true), previous(false)], {
400
609
  tool: 'worktree.reconcile',
401
610
  ...(input.decisionAttempt ? { decisionAttempt: input.decisionAttempt } : {}),
402
611
  retryArguments: json({
@@ -429,12 +638,27 @@ export function registerWorktreeTools(server, service) {
429
638
  const questionnaireId = 'worktree-retire-' + sha256(item.repoRoot + item.generation);
430
639
  const missing = item.reasons.includes('worktree directory missing');
431
640
  const folder = basename(item.repoRoot);
432
- const form = await askQuestionnaire(server, service, {
433
- repoRoot: input.repoRoot,
641
+ const definition = (turkish) => {
642
+ const copy = retireCopy[turkish ? 'tr' : 'en'];
643
+ const named = item.branch ?? (turkish ? 'yok' : 'none');
644
+ return {
645
+ questionnaireId,
646
+ language: turkish ? 'tr' : 'en',
647
+ allowFreeText: false,
648
+ message: missing ? copy.missing(folder) : copy.message(folder, named),
649
+ context: copy.context(missing),
650
+ example: copy.example(folder, named, missing),
651
+ options: [
652
+ { id: 'retire', label: copy.retire, description: copy.retired(missing) },
653
+ { id: 'keep', label: copy.keep, description: copy.kept },
654
+ ],
655
+ };
656
+ };
657
+ const previous = (turkish) => ({
434
658
  questionnaireId,
435
- language: input.language,
659
+ language: turkish ? 'tr' : 'en',
436
660
  allowFreeText: false,
437
- message: input.language === 'tr'
661
+ message: turkish
438
662
  ? folder +
439
663
  (missing
440
664
  ? ' eski konumdaki bir worktree kaydı; klasör artık yok. Kaydı silinsin mi?'
@@ -448,13 +672,11 @@ export function registerWorktreeTools(server, service) {
448
672
  (item.branch ?? 'none') +
449
673
  '); it is no longer reused. Remove it? The branch stays. Git removes only a clean folder, and its ignored files go with it.'),
450
674
  options: [
451
- {
452
- id: 'retire',
453
- label: input.language === 'tr' ? 'Klasörü kaldır' : 'Remove the folder',
454
- },
455
- { id: 'keep', label: input.language === 'tr' ? 'Kalsın' : 'Keep it' },
675
+ { id: 'retire', label: turkish ? 'Klasörü kaldır' : 'Remove the folder' },
676
+ { id: 'keep', label: turkish ? 'Kalsın' : 'Keep it' },
456
677
  ],
457
- }, context);
678
+ });
679
+ const form = await askQuestionnaire(server, service, { ...definition(language === 'tr'), repoRoot: input.repoRoot }, context, [definition(language !== 'tr'), previous(true), previous(false)]);
458
680
  const decision = await service.questionnaireResume({
459
681
  repoRoot: input.repoRoot,
460
682
  questionnaireId,
@@ -498,27 +720,39 @@ export function registerWorktreeTools(server, service) {
498
720
  decisionAttempt: input.decisionAttempt,
499
721
  }));
500
722
  const questionnaireId = 'worktree-release-unowned-' + digest;
501
- const form = await askQuestionnaire(server, service, {
502
- repoRoot: input.repoRoot,
723
+ const definition = (turkish) => {
724
+ const copy = unownedCopy[turkish ? 'tr' : 'en'];
725
+ return {
726
+ questionnaireId,
727
+ language: turkish ? 'tr' : 'en',
728
+ allowFreeText: false,
729
+ message: copy.message(basename(entry.repoRoot)),
730
+ context: copy.context,
731
+ example: copy.example(entry.baseCommit.slice(0, 12)),
732
+ options: [
733
+ { id: 'release_unowned', label: copy.release, description: copy.released },
734
+ { id: 'preserve', label: copy.preserve, description: copy.preserved },
735
+ ],
736
+ };
737
+ };
738
+ const previous = (turkish) => ({
503
739
  questionnaireId,
504
- language: input.language,
740
+ language: turkish ? 'tr' : 'en',
505
741
  allowFreeText: false,
506
- message: input.language === 'tr'
742
+ message: turkish
507
743
  ? 'Bu eski çalışma klasörünün önceki sahiplik kaydı yok. Önceki ajan burada artık çalışmıyor ve teslim tamamlandıysa, temiz klasörü yeniden kullanılabilir yapalım mı? Dosyalar ve branch silinmeyecek.'
508
744
  : 'This legacy checkout has no prior ownership record. If the previous agent has stopped and delivery is complete, make the clean checkout reusable? No files or branches will be deleted.',
509
745
  options: [
510
746
  {
511
747
  id: 'release_unowned',
512
- label: input.language === 'tr'
748
+ label: turkish
513
749
  ? 'Önceki iş bitti; güvenli kontrollerden sonra kullanılabilir yap'
514
750
  : 'Prior work finished; release after safety checks',
515
751
  },
516
- {
517
- id: 'preserve',
518
- label: input.language === 'tr' ? 'Korumaya devam et' : 'Keep protected',
519
- },
752
+ { id: 'preserve', label: turkish ? 'Korumaya devam et' : 'Keep protected' },
520
753
  ],
521
- }, context);
754
+ });
755
+ const form = await askQuestionnaire(server, service, { ...definition(language === 'tr'), repoRoot: input.repoRoot }, context, [definition(language !== 'tr'), previous(true), previous(false)]);
522
756
  const decision = await service.questionnaireResume({
523
757
  repoRoot: input.repoRoot,
524
758
  questionnaireId,
@@ -546,9 +780,10 @@ export function registerWorktreeTools(server, service) {
546
780
  inputSchema: ownershipSchema.extend({
547
781
  deliveryOutcome: z.enum(['delivered', 'cancelled']).optional(),
548
782
  delivered: z.boolean().optional(),
549
- language: z.enum(['tr', 'en']).default('en'),
783
+ language: languageTag.optional(),
550
784
  }),
551
785
  }, async (input, context) => {
786
+ const language = (await service.language(input.language))?.startsWith('tr') ? 'tr' : 'en';
552
787
  if (input.deliveryOutcome !== 'cancelled')
553
788
  return output(await service.worktreeOperation('release', input));
554
789
  const state = await service.worktreeFolder(input);
@@ -561,28 +796,26 @@ export function registerWorktreeTools(server, service) {
561
796
  ...input,
562
797
  decisionId: questionnaireId,
563
798
  }));
564
- const release = {
565
- id: 'release',
566
- label: input.language === 'tr'
567
- ? 'Klasörü boşalt; branch kalsın'
568
- : 'Free the folder; the branch stays',
569
- };
570
- const save = {
571
- id: 'save_and_release',
572
- label: input.language === 'tr'
573
- ? 'Değişiklikleri kaydedip klasörü boşalt'
574
- : 'Save the changes and free the folder',
575
- };
576
- const keep = {
577
- id: 'keep',
578
- label: input.language === 'tr' ? 'Klasör olduğu gibi kalsın' : 'Keep the folder as it is',
799
+ const definition = (turkish) => {
800
+ const copy = cancelCopy[turkish ? 'tr' : 'en'];
801
+ const keep = { id: 'keep', label: copy.keep, description: copy.kept };
802
+ return {
803
+ questionnaireId,
804
+ language: turkish ? 'tr' : 'en',
805
+ allowFreeText: false,
806
+ message: copy.message(input.externalTaskId),
807
+ context: copy.context(folder.clean, folder.managed),
808
+ example: copy.example,
809
+ options: folder.clean || !folder.managed
810
+ ? [{ id: 'release', label: copy.release, description: copy.released }, keep]
811
+ : [{ id: 'save_and_release', label: copy.save, description: copy.saved }, keep],
812
+ };
579
813
  };
580
- const form = await askQuestionnaire(server, service, {
581
- repoRoot: input.repoRoot,
814
+ const previous = (turkish) => ({
582
815
  questionnaireId,
583
- language: input.language,
816
+ language: turkish ? 'tr' : 'en',
584
817
  allowFreeText: false,
585
- message: input.language === 'tr'
818
+ message: turkish
586
819
  ? input.externalTaskId +
587
820
  ' teslim edilmeyecek. Bu klasör ne olsun?' +
588
821
  (folder.clean
@@ -597,8 +830,33 @@ export function registerWorktreeTools(server, service) {
597
830
  : folder.managed
598
831
  ? ' It has uncommitted changes; saving keeps them in a Git ref they can be restored from.'
599
832
  : ' It has uncommitted changes; this is your own folder, so its files are never touched when it is freed.'),
600
- options: folder.clean || !folder.managed ? [release, keep] : [save, keep],
601
- }, context);
833
+ options: folder.clean || !folder.managed
834
+ ? [
835
+ {
836
+ id: 'release',
837
+ label: turkish
838
+ ? 'Klasörü boşalt; branch kalsın'
839
+ : 'Free the folder; the branch stays',
840
+ },
841
+ {
842
+ id: 'keep',
843
+ label: turkish ? 'Klasör olduğu gibi kalsın' : 'Keep the folder as it is',
844
+ },
845
+ ]
846
+ : [
847
+ {
848
+ id: 'save_and_release',
849
+ label: turkish
850
+ ? 'Değişiklikleri kaydedip klasörü boşalt'
851
+ : 'Save the changes and free the folder',
852
+ },
853
+ {
854
+ id: 'keep',
855
+ label: turkish ? 'Klasör olduğu gibi kalsın' : 'Keep the folder as it is',
856
+ },
857
+ ],
858
+ });
859
+ const form = await askQuestionnaire(server, service, { ...definition(language === 'tr'), repoRoot: input.repoRoot }, context, [definition(language !== 'tr'), previous(true), previous(false)]);
602
860
  const decision = await service.questionnaireResume({
603
861
  repoRoot: input.repoRoot,
604
862
  questionnaireId,