engineering-memory 1.11.20 → 1.11.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/bin/engineering-memory.mjs +14 -24
  2. package/install/localization.generated.mjs +18 -0
  3. package/package.json +1 -1
  4. package/runtime/build.json +1 -1
  5. package/runtime/dist/src/auth/browser-auth.js +15 -4
  6. package/runtime/dist/src/config.js +1 -0
  7. package/runtime/dist/src/git/git-inspector.js +10 -0
  8. package/runtime/dist/src/localization/catalogue.generated.js +786 -0
  9. package/runtime/dist/src/mcp/delivery-tools.js +15 -48
  10. package/runtime/dist/src/mcp/onboarding-tools.js +289 -618
  11. package/runtime/dist/src/mcp/questionnaire-tools.js +23 -26
  12. package/runtime/dist/src/mcp/status-meaning-tools.js +12 -86
  13. package/runtime/dist/src/mcp/status-remap-tools.js +252 -0
  14. package/runtime/dist/src/mcp/tool-annotations.js +1 -0
  15. package/runtime/dist/src/mcp/tool-definitions.js +33 -55
  16. package/runtime/dist/src/mcp/workflow-tools.js +17 -108
  17. package/runtime/dist/src/mcp/worktree-tools.js +168 -250
  18. package/runtime/dist/src/runtime/bridge-service.js +123 -94
  19. package/runtime/dist/src/runtime/create-bridge-service.js +4 -2
  20. package/runtime/dist/src/runtime/language-store.js +6 -0
  21. package/runtime/dist/src/runtime/release-notes.js +6 -6
  22. package/runtime/dist/src/runtime/release-report.js +30 -30
  23. package/runtime/dist/src/runtime/task-start.js +75 -73
  24. package/runtime/dist/src/runtime/texts.js +135 -0
  25. package/runtime/dist/src/runtime/worktree-pool.js +30 -5
  26. package/runtime/dist/src/runtime/worktree-preparation.js +3 -2
  27. package/runtime/dist/src/utilities/local-mutex.js +5 -2
  28. package/runtime/dist/src/utilities/process.js +7 -0
  29. package/skill/references/lifecycle.md +22 -4
  30. package/skill/references/project-onboarding.md +5 -2
  31. package/skill/references/questionnaires.md +3 -1
@@ -7,6 +7,7 @@ import { sha256, stableStringify } from '../utilities/hash.js';
7
7
  import { unreadableWorktree } from '../runtime/worktree-pool.js';
8
8
  import { askFailed, askQuestionnaire } from './questionnaire-tools.js';
9
9
  import { languageTag } from '../runtime/questionnaire-store.js';
10
+ import { copies, format } from '../runtime/texts.js';
10
11
  const repo = z.string().min(1);
11
12
  const branch = z.string().min(1).max(255);
12
13
  const remote = z.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9._-]{0,79}$/);
@@ -51,170 +52,76 @@ const policy = {
51
52
  heartbeatSeconds: z.number().int().min(5).max(300),
52
53
  inactivitySeconds: z.number().int().min(60).max(86400),
53
54
  };
54
- const preferenceCopy = {
55
- en: {
56
- message: 'Save the shared branch preferences of this project? Nothing is saved until you answer.',
57
- 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.',
58
- example: 'Once the development role points at origin/develop, the start form of the next task offers origin/develop as its first choice.',
59
- roles: { development: 'development', production: 'production', test: 'test' },
60
- ask: (role, source) => source
61
- ? `Save ${source} as the shared ${role} branch?`
62
- : `Record that this project has no ${role} branch?`,
63
- save: 'Save this choice',
64
- saved: 'This branch is recorded as the project default now, for every member.',
65
- leave: 'Leave unchanged',
66
- unchanged: 'Nothing changes. This role stays as it is and can be answered another time.',
67
- },
68
- tr: {
69
- message: 'Projenin ortak dal tercihleri kaydedilsin mi? Sen cevaplayana kadar hiçbir şey kaydedilmez.',
70
- 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.',
71
- example: 'Geliştirme dalı origin/develop olarak kaydedilirse, bir sonraki görevin başlangıç formunda ilk seçenek origin/develop olur.',
72
- roles: { development: 'Geliştirme', production: 'Canlı', test: 'Test' },
73
- ask: (role, source) => source
74
- ? `${role} dalı ${source} olarak kaydedilsin mi?`
75
- : `Bu projede ${role.toLocaleLowerCase('tr')} dalı olmadığı kaydedilsin mi?`,
76
- save: 'Bu tercihi kaydet',
77
- saved: 'Bu dal şimdi proje varsayılanı olarak kaydedilir; ekipteki herkes onu görür.',
78
- leave: 'Şimdilik değiştirme',
79
- unchanged: 'Hiçbir şey değişmez. Bu rol olduğu gibi kalır, sonra yeniden sorulabilir.',
80
- },
81
- };
82
- const fallbackCopy = {
83
- en: {
84
- 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'}?`,
85
- 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.',
86
- example: (remote, local) => `Continuing creates the new branch at ${local}; ${remote} can be merged into it once the network is back.`,
87
- approve: 'Continue',
88
- approved: (local) => `The new branch is created at ${local} now and the task starts.`,
89
- defer: 'Not now',
90
- deferred: 'Nothing is created or reserved. The task waits, and it can be started again once the remote answers.',
91
- },
92
- tr: {
93
- 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ı?`,
94
- 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.',
95
- example: (remote, local) => `Devam edersen yeni dal ${local} üzerinden açılır; ağ düzeldiğinde ${remote} dalını bu dala birleştirebilirsin.`,
96
- approve: 'Devam et',
97
- approved: (local) => `Yeni dal şimdi ${local} üzerinden açılır ve görev başlar.`,
98
- defer: 'Şimdilik başlatma',
99
- deferred: 'Hiçbir şey oluşturulmaz, hiçbir yer ayrılmaz. Görev bekler; uzak dala ulaşıldığında yeniden başlatılabilir.',
100
- },
101
- };
102
- const forgetCopy = {
103
- en: {
104
- message: (task, folder) => `The ${folder} folder of task ${task} can no longer be read as a Git checkout. Should Engineering Memory stop tracking it?`,
105
- 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.' +
106
- (pendingDelivery
107
- ? ' That task closed without its delivery being settled, so any undelivered work exists only in that folder.'
108
- : ''),
109
- example: (task, folder) => `After this, ${task} no longer appears in the worktree list and the ${folder} folder stays on disk exactly as it is.`,
110
- forget: 'Stop tracking it; leave the files alone',
111
- forgotten: 'The record is removed and the slot is free again. The folder, its files and its branch stay untouched.',
112
- keep: 'Keep tracking it',
113
- kept: 'Nothing changes. The record stays and keeps counting against the limit.',
114
- },
115
- tr: {
116
- 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ı?`,
117
- 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.' +
118
- (pendingDelivery
119
- ? ' Bu görev teslimi sonuçlanmadan kapandı; teslim edilmemiş iş varsa yalnızca bu klasörde duruyor.'
120
- : ''),
121
- example: (task, folder) => `Bundan sonra ${task} worktree listesinde görünmez, ${folder} klasörü ise diskte olduğu gibi kalır.`,
122
- forget: 'Takip etmeyi bırak; dosyalara dokunma',
123
- forgotten: 'Kayıt silinir ve sınırdaki yer boşalır. Klasör, içindeki dosyalar ve dal olduğu gibi kalır.',
124
- keep: 'Takip etmeye devam et',
125
- kept: 'Hiçbir şey değişmez. Kayıt durur ve sınıra sayılmaya devam eder.',
126
- },
127
- };
128
- const retireCopy = {
129
- en: {
130
- missing: (folder) => `The ${folder} folder is gone but its worktree record is still here. Remove the record?`,
131
- message: (folder, branch) => `${folder} is a worktree left in the old location (branch ${branch}). Remove the folder?`,
132
- 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.' +
133
- (missing
134
- ? ' Only the record is left, so there is no folder to remove and no file is read or deleted.'
135
- : ' The branch and its commits stay. Git removes only a clean folder, and the files it ignores go with it.'),
136
- example: (folder, branch, missing) => missing
137
- ? `After this, ${folder} is gone from the worktree list and nothing on disk changes.`
138
- : `After this, ${folder} is gone from the worktree list and branch ${branch} still holds every commit made on it.`,
139
- retire: 'Remove the folder',
140
- retired: (missing) => missing
141
- ? 'The stale record is removed. Nothing on disk changes.'
142
- : 'Git removes the folder and Engineering Memory drops its record. The branch and its commits stay.',
143
- keep: 'Keep it',
144
- kept: 'Nothing changes. The folder and its record stay exactly as they are.',
145
- },
146
- tr: {
147
- missing: (folder) => `${folder} klasörü artık yok ama worktree kaydı duruyor. Kayıt silinsin mi?`,
148
- message: (folder, branch) => `${folder}, eski konumda kalmış bir worktree (dal ${branch}). Klasör kaldırılsın mı?`,
149
- 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.' +
150
- (missing
151
- ? ' Geriye yalnızca kayıt kaldı; kaldırılacak bir klasör yok, hiçbir dosya okunmaz veya silinmez.'
152
- : ' 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.'),
153
- example: (folder, branch, missing) => missing
154
- ? `Bundan sonra ${folder} worktree listesinde görünmez; diskte hiçbir şey değişmez.`
155
- : `Bundan sonra ${folder} worktree listesinde görünmez; ${branch} dalındaki commit’ler ise yerinde durur.`,
156
- retire: 'Klasörü kaldır',
157
- retired: (missing) => missing
158
- ? 'Boşta kalan kayıt silinir. Diskte hiçbir şey değişmez.'
159
- : 'Git klasörü kaldırır, Engineering Memory de kaydını siler. Dal ve commit’ler kalır.',
160
- keep: 'Kalsın',
161
- kept: 'Hiçbir şey değişmez. Klasör de kaydı da olduğu gibi kalır.',
162
- },
163
- };
164
- const unownedCopy = {
165
- en: {
166
- 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?`,
167
- 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.',
168
- example: (commit) => `Once it is released, the next task can start in that folder, and the work committed at ${commit} stays on its branch.`,
169
- release: 'Prior work finished; release after safety checks',
170
- released: 'The safety checks run and, if they pass, the folder returns to the pool.',
171
- preserve: 'Keep protected',
172
- preserved: 'Nothing changes. The folder stays protected and is given to no task.',
173
- },
174
- tr: {
175
- 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?`,
176
- 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.',
177
- 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.`,
178
- release: 'Önceki iş bitti; güvenlik kontrollerinden sonra kullanılabilir yap',
179
- released: 'Güvenlik kontrolleri çalışır; geçerse klasör havuza döner.',
180
- preserve: 'Korumaya devam et',
181
- preserved: 'Hiçbir şey değişmez. Klasör korunur ve hiçbir göreve verilmez.',
182
- },
183
- };
184
- const cancelCopy = {
185
- en: {
186
- message: (task) => `${task} will not be delivered. What should happen to this folder?`,
187
- 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.' +
188
- (clean
189
- ? ''
190
- : managed
191
- ? ' The folder has uncommitted changes; saving writes them to a Git ref they can be restored from.'
192
- : ' The folder has uncommitted changes; this is your own folder, so its files are never touched when it is freed.'),
193
- example: 'Freeing the folder lets the next task start in it; keeping it leaves it reserved.',
194
- release: 'Free the folder; the branch stays',
195
- released: 'The folder returns to the pool. Its files are left alone and the branch stays.',
196
- save: 'Save the changes and free the folder',
197
- saved: 'The uncommitted changes are written to a Git ref first, then the folder is freed. They can be restored from that ref.',
198
- keep: 'Keep the folder as it is',
199
- kept: 'Nothing changes. The folder stays reserved for this task.',
200
- },
201
- tr: {
202
- message: (task) => `${task} teslim edilmeyecek. Bu klasör ne olsun?`,
203
- 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.' +
204
- (clean
205
- ? ''
206
- : managed
207
- ? ' Klasörde kaydedilmemiş değişiklikler var; kaydedilirse bir Git ref’ine yazılır ve oradan geri alınabilir.'
208
- : ' Klasörde kaydedilmemiş değişiklikler var; burası senin kendi klasörün olduğu için boşaltılsa da dosyalara dokunulmaz.'),
209
- 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.',
210
- release: 'Klasörü boşalt; branch kalsın',
211
- released: 'Klasör havuza döner. Dosyalarına dokunulmaz, dal kalır.',
212
- save: 'Değişiklikleri kaydedip klasörü boşalt',
213
- 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.',
214
- keep: 'Klasör olduğu gibi kalsın',
215
- kept: 'Hiçbir şey değişmez. Klasör bu göreve ayrılmış hâlde kalır.',
216
- },
217
- };
55
+ const text = z.string().min(1);
56
+ const roleTexts = z.strictObject({ development: text, production: text, test: text });
57
+ const preferenceWording = z.strictObject({
58
+ message: text,
59
+ context: text,
60
+ example: text,
61
+ ask: roleTexts,
62
+ askNone: roleTexts,
63
+ save: text,
64
+ saved: text,
65
+ leave: text,
66
+ unchanged: text,
67
+ });
68
+ const fallbackWording = z.strictObject({
69
+ messageHere: text,
70
+ messageWorktree: text,
71
+ context: text,
72
+ example: text,
73
+ approve: text,
74
+ approved: text,
75
+ defer: text,
76
+ deferred: text,
77
+ });
78
+ const forgetWording = z.strictObject({
79
+ message: text,
80
+ context: text,
81
+ pendingDelivery: text,
82
+ example: text,
83
+ forget: text,
84
+ forgotten: text,
85
+ keep: text,
86
+ kept: text,
87
+ });
88
+ const retireWording = z.strictObject({
89
+ missing: text,
90
+ message: text,
91
+ noBranch: text,
92
+ context: text,
93
+ contextMissing: text,
94
+ contextFolder: text,
95
+ example: text,
96
+ exampleMissing: text,
97
+ retire: text,
98
+ retired: text,
99
+ retiredMissing: text,
100
+ keep: text,
101
+ kept: text,
102
+ });
103
+ const unownedWording = z.strictObject({
104
+ message: text,
105
+ context: text,
106
+ example: text,
107
+ release: text,
108
+ released: text,
109
+ preserve: text,
110
+ preserved: text,
111
+ });
112
+ const cancelWording = z.strictObject({
113
+ message: text,
114
+ context: text,
115
+ uncommittedManaged: text,
116
+ uncommittedOwn: text,
117
+ example: text,
118
+ release: text,
119
+ released: text,
120
+ save: text,
121
+ saved: text,
122
+ keep: text,
123
+ kept: text,
124
+ });
218
125
  const baseSchema = z.discriminatedUnion('kind', [
219
126
  z.strictObject({
220
127
  kind: z.literal('current'),
@@ -266,7 +173,7 @@ export function registerWorktreeTools(server, service) {
266
173
  presentation,
267
174
  }),
268
175
  }, async (input, context) => {
269
- const language = (await service.language(input.language))?.startsWith('tr') ? 'tr' : 'en';
176
+ const wording = copies(preferenceWording, 'gitPreferences', await service.language(input.language));
270
177
  const current = await service.projectGitPreferences(input);
271
178
  if (!current.ok)
272
179
  return output(current);
@@ -296,24 +203,26 @@ export function registerWorktreeTools(server, service) {
296
203
  ...(input.decisionAttempt ? { decisionAttempt: input.decisionAttempt } : {}),
297
204
  }));
298
205
  const questionnaireId = 'project-branches-' + digest;
299
- const definition = (turkish) => {
300
- const copy = preferenceCopy[turkish ? 'tr' : 'en'];
301
- return {
302
- questionnaireId,
303
- language: turkish ? 'tr' : 'en',
304
- message: copy.message,
305
- context: copy.context,
306
- example: copy.example,
307
- questions: roles.map((role) => ({
206
+ const definitions = wording.map(({ language, copy }) => ({
207
+ questionnaireId,
208
+ language,
209
+ message: copy.message,
210
+ context: copy.context,
211
+ example: copy.example,
212
+ questions: roles.map((role) => {
213
+ const branch = source(role, null);
214
+ return {
308
215
  id: role,
309
- message: copy.ask(copy.roles[role], source(role, null)),
216
+ message: branch
217
+ ? format(copy.ask[role], language, { source: branch })
218
+ : copy.askNone[role],
310
219
  options: [
311
220
  { id: 'approve', label: copy.save, description: copy.saved },
312
221
  { id: 'defer', label: copy.leave, description: copy.unchanged },
313
222
  ],
314
- })),
315
- };
316
- };
223
+ };
224
+ }),
225
+ }));
317
226
  const previous = (turkish) => ({
318
227
  questionnaireId,
319
228
  language: turkish ? 'tr' : 'en',
@@ -347,10 +256,10 @@ export function registerWorktreeTools(server, service) {
347
256
  }),
348
257
  };
349
258
  const form = await askQuestionnaire(server, service, {
350
- ...definition(language === 'tr'),
259
+ ...definitions[0],
351
260
  repoRoot: input.repoRoot,
352
261
  presentation: input.presentation,
353
- }, context, [definition(language !== 'tr'), previous(true), previous(false)], owner);
262
+ }, context, [...definitions, previous(true), previous(false)], owner);
354
263
  if (askFailed(form))
355
264
  return form;
356
265
  const record = await service.questionnaireResume({
@@ -388,7 +297,7 @@ export function registerWorktreeTools(server, service) {
388
297
  presentation,
389
298
  }),
390
299
  }, async (input, context) => {
391
- const language = (await service.language(input.language))?.startsWith('tr') ? 'tr' : 'en';
300
+ const told = await service.language(input.language);
392
301
  const preflight = await service.taskBranchPreflight(input);
393
302
  if (!preflight.ok)
394
303
  return output(preflight);
@@ -410,7 +319,11 @@ export function registerWorktreeTools(server, service) {
410
319
  recovery: 'task.branch',
411
320
  },
412
321
  });
413
- const { previous, ...definition } = taskStartDefinition({ ...input, ...flight, language });
322
+ const { previous, ...definition } = taskStartDefinition({
323
+ ...input,
324
+ ...flight,
325
+ language: told ?? 'en',
326
+ });
414
327
  const owner = {
415
328
  tool: 'task.branch',
416
329
  externalTaskId: input.externalTaskId,
@@ -477,21 +390,25 @@ export function registerWorktreeTools(server, service) {
477
390
  const fallback = async () => {
478
391
  const from = sourceLabel(choice.base, flight.currentBranch);
479
392
  const local = sourceLabel(fallbackBase, flight.currentBranch);
480
- const definition = (turkish) => {
481
- const copy = fallbackCopy[turkish ? 'tr' : 'en'];
482
- return {
483
- questionnaireId: fallbackId,
484
- language: turkish ? 'tr' : 'en',
485
- allowFreeText: false,
486
- message: copy.message(from, local, choice.inPlace),
487
- context: copy.context,
488
- example: copy.example(from, local),
489
- options: [
490
- { id: 'approve', label: copy.approve, description: copy.approved(local) },
491
- { id: 'defer', label: copy.defer, description: copy.deferred },
492
- ],
493
- };
494
- };
393
+ const definitions = copies(fallbackWording, 'startFallback', told).map(({ language, copy }) => ({
394
+ questionnaireId: fallbackId,
395
+ language,
396
+ allowFreeText: false,
397
+ message: format(choice.inPlace ? copy.messageHere : copy.messageWorktree, language, {
398
+ remote: from,
399
+ local,
400
+ }),
401
+ context: copy.context,
402
+ example: format(copy.example, language, { remote: from, local }),
403
+ options: [
404
+ {
405
+ id: 'approve',
406
+ label: copy.approve,
407
+ description: format(copy.approved, language, { local }),
408
+ },
409
+ { id: 'defer', label: copy.defer, description: copy.deferred },
410
+ ],
411
+ }));
495
412
  const previousFallback = (turkish) => ({
496
413
  questionnaireId: fallbackId,
497
414
  language: turkish ? 'tr' : 'en',
@@ -514,10 +431,10 @@ export function registerWorktreeTools(server, service) {
514
431
  ],
515
432
  });
516
433
  const fallbackForm = await askQuestionnaire(server, service, {
517
- ...definition(language === 'tr'),
434
+ ...definitions[0],
518
435
  repoRoot: input.repoRoot,
519
436
  presentation: input.presentation,
520
- }, context, [definition(language !== 'tr'), previousFallback(true), previousFallback(false)], owner);
437
+ }, context, [...definitions, previousFallback(true), previousFallback(false)], owner);
521
438
  if (askFailed(fallbackForm))
522
439
  return fallbackForm;
523
440
  const answer = await service.questionnaireResume({
@@ -571,7 +488,7 @@ export function registerWorktreeTools(server, service) {
571
488
  language: languageTag.optional(),
572
489
  }),
573
490
  }, async (input, context) => {
574
- const language = (await service.language(input.language))?.startsWith('tr') ? 'tr' : 'en';
491
+ const told = await service.language(input.language);
575
492
  const inspection = await service.worktreeOperation('reconcile', input);
576
493
  if (!inspection.ok ||
577
494
  (!input.releaseUnowned && !input.retireLegacy && !input.forgetUnreadable))
@@ -590,21 +507,21 @@ export function registerWorktreeTools(server, service) {
590
507
  });
591
508
  const questionnaireId = forgetUnreadableQuestionId(item, input.decisionAttempt);
592
509
  const folder = basename(item.repoRoot);
593
- const definition = (turkish) => {
594
- const copy = forgetCopy[turkish ? 'tr' : 'en'];
595
- return {
596
- questionnaireId,
597
- language: turkish ? 'tr' : 'en',
598
- allowFreeText: false,
599
- message: copy.message(item.externalTaskId, folder),
600
- context: copy.context(item.pendingDelivery === true),
601
- example: copy.example(item.externalTaskId, folder),
602
- options: [
603
- { id: 'forget', label: copy.forget, description: copy.forgotten },
604
- { id: 'keep', label: copy.keep, description: copy.kept },
605
- ],
606
- };
607
- };
510
+ const named = { task: item.externalTaskId, folder };
511
+ const definitions = copies(forgetWording, 'forgetWorktree', told).map(({ language, copy }) => ({
512
+ questionnaireId,
513
+ language,
514
+ allowFreeText: false,
515
+ message: format(copy.message, language, named),
516
+ context: item.pendingDelivery
517
+ ? `${copy.context} ${copy.pendingDelivery}`
518
+ : copy.context,
519
+ example: format(copy.example, language, named),
520
+ options: [
521
+ { id: 'forget', label: copy.forget, description: copy.forgotten },
522
+ { id: 'keep', label: copy.keep, description: copy.kept },
523
+ ],
524
+ }));
608
525
  const previous = (turkish) => ({
609
526
  questionnaireId,
610
527
  language: turkish ? 'tr' : 'en',
@@ -635,7 +552,7 @@ export function registerWorktreeTools(server, service) {
635
552
  { id: 'keep', label: turkish ? 'Takip etmeye devam et' : 'Keep tracking it' },
636
553
  ],
637
554
  });
638
- const form = await askQuestionnaire(server, service, { ...definition(language === 'tr'), repoRoot: input.repoRoot }, context, [definition(language !== 'tr'), previous(true), previous(false)], {
555
+ const form = await askQuestionnaire(server, service, { ...definitions[0], repoRoot: input.repoRoot }, context, [...definitions, previous(true), previous(false)], {
639
556
  tool: 'worktree.reconcile',
640
557
  ...(input.decisionAttempt ? { decisionAttempt: input.decisionAttempt } : {}),
641
558
  retryArguments: json({
@@ -671,22 +588,25 @@ export function registerWorktreeTools(server, service) {
671
588
  const missing = item.reasons.includes('worktree directory missing');
672
589
  const questionnaireId = retireQuestionId(item, !missing, input.decisionAttempt);
673
590
  const folder = basename(item.repoRoot);
674
- const definition = (turkish) => {
675
- const copy = retireCopy[turkish ? 'tr' : 'en'];
676
- const named = item.branch ?? (turkish ? 'yok' : 'none');
591
+ const definitions = copies(retireWording, 'retireWorktree', told).map(({ language, copy }) => {
592
+ const named = { folder, branch: item.branch ?? copy.noBranch };
677
593
  return {
678
594
  questionnaireId,
679
- language: turkish ? 'tr' : 'en',
595
+ language,
680
596
  allowFreeText: false,
681
- message: missing ? copy.missing(folder) : copy.message(folder, named),
682
- context: copy.context(missing),
683
- example: copy.example(folder, named, missing),
597
+ message: format(missing ? copy.missing : copy.message, language, named),
598
+ context: `${copy.context} ${missing ? copy.contextMissing : copy.contextFolder}`,
599
+ example: format(missing ? copy.exampleMissing : copy.example, language, named),
684
600
  options: [
685
- { id: 'retire', label: copy.retire, description: copy.retired(missing) },
601
+ {
602
+ id: 'retire',
603
+ label: copy.retire,
604
+ description: missing ? copy.retiredMissing : copy.retired,
605
+ },
686
606
  { id: 'keep', label: copy.keep, description: copy.kept },
687
607
  ],
688
608
  };
689
- };
609
+ });
690
610
  const previous = (turkish) => ({
691
611
  questionnaireId,
692
612
  language: turkish ? 'tr' : 'en',
@@ -709,7 +629,7 @@ export function registerWorktreeTools(server, service) {
709
629
  { id: 'keep', label: turkish ? 'Kalsın' : 'Keep it' },
710
630
  ],
711
631
  });
712
- const form = await askQuestionnaire(server, service, { ...definition(language === 'tr'), repoRoot: input.repoRoot }, context, [definition(language !== 'tr'), previous(true), previous(false)]);
632
+ const form = await askQuestionnaire(server, service, { ...definitions[0], repoRoot: input.repoRoot }, context, [...definitions, previous(true), previous(false)]);
713
633
  if (askFailed(form)) {
714
634
  refused.push({
715
635
  repoRoot: item.repoRoot,
@@ -763,21 +683,18 @@ export function registerWorktreeTools(server, service) {
763
683
  decisionAttempt: input.decisionAttempt,
764
684
  }));
765
685
  const questionnaireId = 'worktree-release-unowned-' + digest;
766
- const definition = (turkish) => {
767
- const copy = unownedCopy[turkish ? 'tr' : 'en'];
768
- return {
769
- questionnaireId,
770
- language: turkish ? 'tr' : 'en',
771
- allowFreeText: false,
772
- message: copy.message(basename(entry.repoRoot)),
773
- context: copy.context,
774
- example: copy.example(entry.baseCommit.slice(0, 12)),
775
- options: [
776
- { id: 'release_unowned', label: copy.release, description: copy.released },
777
- { id: 'preserve', label: copy.preserve, description: copy.preserved },
778
- ],
779
- };
780
- };
686
+ const definitions = copies(unownedWording, 'releaseUnowned', told).map(({ language, copy }) => ({
687
+ questionnaireId,
688
+ language,
689
+ allowFreeText: false,
690
+ message: format(copy.message, language, { folder: basename(entry.repoRoot) }),
691
+ context: copy.context,
692
+ example: format(copy.example, language, { commit: entry.baseCommit.slice(0, 12) }),
693
+ options: [
694
+ { id: 'release_unowned', label: copy.release, description: copy.released },
695
+ { id: 'preserve', label: copy.preserve, description: copy.preserved },
696
+ ],
697
+ }));
781
698
  const previous = (turkish) => ({
782
699
  questionnaireId,
783
700
  language: turkish ? 'tr' : 'en',
@@ -795,7 +712,7 @@ export function registerWorktreeTools(server, service) {
795
712
  { id: 'preserve', label: turkish ? 'Korumaya devam et' : 'Keep protected' },
796
713
  ],
797
714
  });
798
- const form = await askQuestionnaire(server, service, { ...definition(language === 'tr'), repoRoot: input.repoRoot }, context, [definition(language !== 'tr'), previous(true), previous(false)]);
715
+ const form = await askQuestionnaire(server, service, { ...definitions[0], repoRoot: input.repoRoot }, context, [...definitions, previous(true), previous(false)]);
799
716
  if (askFailed(form))
800
717
  return form;
801
718
  const decision = await service.questionnaireResume({
@@ -828,7 +745,7 @@ export function registerWorktreeTools(server, service) {
828
745
  language: languageTag.optional(),
829
746
  }),
830
747
  }, async (input, context) => {
831
- const language = (await service.language(input.language))?.startsWith('tr') ? 'tr' : 'en';
748
+ const told = await service.language(input.language);
832
749
  if (input.deliveryOutcome !== 'cancelled')
833
750
  return output(await service.worktreeOperation('release', input));
834
751
  const state = await service.worktreeFolder(input);
@@ -841,21 +758,22 @@ export function registerWorktreeTools(server, service) {
841
758
  ...input,
842
759
  decisionId: questionnaireId,
843
760
  }));
844
- const definition = (turkish) => {
845
- const copy = cancelCopy[turkish ? 'tr' : 'en'];
761
+ const definitions = copies(cancelWording, 'cancelDelivery', told).map(({ language, copy }) => {
846
762
  const keep = { id: 'keep', label: copy.keep, description: copy.kept };
847
763
  return {
848
764
  questionnaireId,
849
- language: turkish ? 'tr' : 'en',
765
+ language,
850
766
  allowFreeText: false,
851
- message: copy.message(input.externalTaskId),
852
- context: copy.context(folder.clean, folder.managed),
767
+ message: format(copy.message, language, { task: input.externalTaskId }),
768
+ context: folder.clean
769
+ ? copy.context
770
+ : `${copy.context} ${folder.managed ? copy.uncommittedManaged : copy.uncommittedOwn}`,
853
771
  example: copy.example,
854
772
  options: folder.clean || !folder.managed
855
773
  ? [{ id: 'release', label: copy.release, description: copy.released }, keep]
856
774
  : [{ id: 'save_and_release', label: copy.save, description: copy.saved }, keep],
857
775
  };
858
- };
776
+ });
859
777
  const previous = (turkish) => ({
860
778
  questionnaireId,
861
779
  language: turkish ? 'tr' : 'en',
@@ -901,7 +819,7 @@ export function registerWorktreeTools(server, service) {
901
819
  },
902
820
  ],
903
821
  });
904
- const form = await askQuestionnaire(server, service, { ...definition(language === 'tr'), repoRoot: input.repoRoot }, context, [definition(language !== 'tr'), previous(true), previous(false)]);
822
+ const form = await askQuestionnaire(server, service, { ...definitions[0], repoRoot: input.repoRoot }, context, [...definitions, previous(true), previous(false)]);
905
823
  if (askFailed(form))
906
824
  return form;
907
825
  const decision = await service.questionnaireResume({