engineering-memory 1.11.11 → 1.11.12
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/dispatcher/sections.mjs +2 -2
- package/package.json +1 -1
- package/runtime/build.json +1 -1
- package/runtime/dist/src/mcp/onboarding-tools.js +833 -154
- package/runtime/dist/src/mcp/questionnaire-tools.js +55 -16
- package/runtime/dist/src/mcp/tool-definitions.js +103 -16
- package/runtime/dist/src/mcp/worktree-tools.js +349 -91
- package/runtime/dist/src/runtime/bridge-service.js +111 -12
- package/runtime/dist/src/runtime/create-bridge-service.js +2 -0
- package/runtime/dist/src/runtime/language-store.js +25 -0
- package/runtime/dist/src/runtime/offline-outbox.js +4 -4
- package/runtime/dist/src/runtime/privacy-detector.js +14 -5
- package/runtime/dist/src/runtime/questionnaire-store.js +26 -7
- package/runtime/dist/src/runtime/task-start.js +115 -33
- package/skill/references/questionnaires.md +8 -3
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import * as z from 'zod/v4';
|
|
2
2
|
import { sha256, stableStringify } from '../utilities/hash.js';
|
|
3
|
-
import {
|
|
3
|
+
import { restrictedValueKind } from '../runtime/privacy-detector.js';
|
|
4
|
+
import { languageTag } from '../runtime/questionnaire-store.js';
|
|
5
|
+
import { answerChoice, answerData, askQuestionnaire, resumeQuestionnaire, } from './questionnaire-tools.js';
|
|
4
6
|
const uuid = z.string().uuid();
|
|
5
7
|
const hash = z.string().regex(/^[a-f0-9]{64}$/);
|
|
6
8
|
const project = { projectId: uuid };
|
|
@@ -31,8 +33,16 @@ const inspectionCopy = {
|
|
|
31
33
|
preserve: 'Rework hedefi: mevcut davranışı korumak.',
|
|
32
34
|
redesign: 'Rework hedefi: ürünü yeniden tasarlamak. Uygulama öncesinde hedef ayrıca onaylanacak.',
|
|
33
35
|
},
|
|
36
|
+
why: {
|
|
37
|
+
refresh: 'İnceleme, hafızadaki kayıtları bu kaynaktaki kodla karşılaştırır ve neyin değiştiğini çıkarır.',
|
|
38
|
+
adopt: 'Proje ilk kez hafızaya alınıyor. İnceleme, bu kaynaktaki kodu okuyup projenin nasıl çalıştığını çıkarır.',
|
|
39
|
+
rework: 'Yeniden yazım planının yazılabilmesi için önce projenin bugünkü hâli okunmalı.',
|
|
40
|
+
},
|
|
41
|
+
example: 'İnceleme bitince her ekran, servis ve akış için birer hafıza kaydı önerilir; hangilerinin yayınlanacağına sen karar verirsin.',
|
|
34
42
|
start: 'İncelemeyi başlat',
|
|
43
|
+
startDetail: 'İnceleme şimdi başlar. Kod değişmez; çıkan kayıtlar sonunda tek tek onayına gelir.',
|
|
35
44
|
defer: 'Şimdilik başlatma',
|
|
45
|
+
deferDetail: 'İnceleme başlamaz, hiçbir şey yazılmaz. Cevabın kaydedilir; bu kaynağı sonra incelemek yeni bir istek olur.',
|
|
36
46
|
},
|
|
37
47
|
en: {
|
|
38
48
|
modes: {
|
|
@@ -48,8 +58,16 @@ const inspectionCopy = {
|
|
|
48
58
|
preserve: 'Rework goal: preserve existing behavior.',
|
|
49
59
|
redesign: 'Rework goal: redesign the product. The target needs separate approval before implementation.',
|
|
50
60
|
},
|
|
61
|
+
why: {
|
|
62
|
+
refresh: 'The inspection compares the records already in memory with the code at that source and works out what changed.',
|
|
63
|
+
adopt: 'The project enters memory for the first time. The inspection reads the code at that source and works out how the project behaves.',
|
|
64
|
+
rework: 'The rework plan can only be written once the project as it stands today is read.',
|
|
65
|
+
},
|
|
66
|
+
example: 'When the inspection finishes, a memory record is proposed for each screen, service and flow, and you decide which of them get published.',
|
|
51
67
|
start: 'Start inspection',
|
|
68
|
+
startDetail: 'The inspection starts now. The code stays unchanged and every record it writes comes back for your approval.',
|
|
52
69
|
defer: 'Not now',
|
|
70
|
+
deferDetail: 'Nothing is inspected and nothing is written. Your answer is recorded, so inspecting this source later is a new request.',
|
|
53
71
|
},
|
|
54
72
|
};
|
|
55
73
|
const reference = z.object({
|
|
@@ -76,6 +94,108 @@ const kind = z.enum([
|
|
|
76
94
|
'quality_gate',
|
|
77
95
|
'architecture_template',
|
|
78
96
|
]);
|
|
97
|
+
const batchCopy = {
|
|
98
|
+
en: {
|
|
99
|
+
one: 'One memory record is ready to be published.',
|
|
100
|
+
many: (count) => `${count} memory records are ready to be published.`,
|
|
101
|
+
context: 'Publishing replaces the current text of these records for everyone who reads this memory. Earlier revisions stay in the history.',
|
|
102
|
+
unversioned: (titles) => ` Written without a source inspection: ${titles}. Tasks that read memory by source version will not see them until an inspection retains them.`,
|
|
103
|
+
example: 'After publishing, the next session that reads one of these records gets the new text instead of the old one.',
|
|
104
|
+
untitled: 'untitled',
|
|
105
|
+
scope: { project: 'project', organization: 'organization', product: 'product' },
|
|
106
|
+
newRecord: 'new record',
|
|
107
|
+
revision: (from) => `revision ${from} to ${from + 1}`,
|
|
108
|
+
rebase: 'written against an older revision',
|
|
109
|
+
why: 'Why',
|
|
110
|
+
approved: ['Publish all of them', 'Every listed record takes its new text now.'],
|
|
111
|
+
rejected: [
|
|
112
|
+
'Reject all of them',
|
|
113
|
+
'Nothing changes. The proposals are marked rejected and have to be written again.',
|
|
114
|
+
],
|
|
115
|
+
oneByOne: [
|
|
116
|
+
'Let me look at them one by one',
|
|
117
|
+
'Nothing changes now. Each record is shown in full and decided on its own.',
|
|
118
|
+
],
|
|
119
|
+
defer: ['Decide later', 'Nothing changes. The proposals stay pending.'],
|
|
120
|
+
},
|
|
121
|
+
tr: {
|
|
122
|
+
one: 'Bir hafıza kaydı yayına hazır.',
|
|
123
|
+
many: (count) => `${count} hafıza kaydı yayına hazır.`,
|
|
124
|
+
context: 'Yayınlarsan bu kayıtların mevcut metni, bu hafızayı okuyan herkes için değişir. Eski sürümler geçmişte kalır.',
|
|
125
|
+
unversioned: (titles) => ` Kaynak incelemesi yapılmadan yazılanlar: ${titles}. Hafızayı kaynak sürümüne göre okuyan görevler, bir inceleme bunları saklayana kadar görmez.`,
|
|
126
|
+
example: 'Yayınladıktan sonra bu kayıtlardan birini okuyan bir sonraki oturum eski metni değil yenisini görür.',
|
|
127
|
+
untitled: 'başlıksız',
|
|
128
|
+
scope: { project: 'proje', organization: 'organizasyon', product: 'ürün' },
|
|
129
|
+
newRecord: 'yeni kayıt',
|
|
130
|
+
revision: (from) => `sürüm ${from} → ${from + 1}`,
|
|
131
|
+
rebase: 'eski bir sürüme göre yazılmış',
|
|
132
|
+
why: 'Nedeni',
|
|
133
|
+
approved: ['Hepsini yayınla', 'Listedeki her kayıt şimdi yeni metnine geçer.'],
|
|
134
|
+
rejected: [
|
|
135
|
+
'Hepsini reddet',
|
|
136
|
+
'Hiçbir şey değişmez. Öneriler reddedilir ve yeniden yazılmaları gerekir.',
|
|
137
|
+
],
|
|
138
|
+
oneByOne: [
|
|
139
|
+
'Tek tek bakayım',
|
|
140
|
+
'Şimdi hiçbir şey değişmez. Her kayıt tam metniyle gösterilir ve ayrı ayrı karara bağlanır.',
|
|
141
|
+
],
|
|
142
|
+
defer: ['Şimdilik karar verme', 'Hiçbir şey değişmez. Öneriler beklemede kalır.'],
|
|
143
|
+
},
|
|
144
|
+
};
|
|
145
|
+
const kindInTurkish = {
|
|
146
|
+
engineering_rule: 'mühendislik kuralı',
|
|
147
|
+
project_profile: 'proje profili',
|
|
148
|
+
service_contract: 'servis sözleşmesi',
|
|
149
|
+
localization_contract: 'yerelleştirme sözleşmesi',
|
|
150
|
+
navigation_contract: 'gezinme sözleşmesi',
|
|
151
|
+
state_contract: 'durum yönetimi sözleşmesi',
|
|
152
|
+
screen_logic: 'ekran mantığı',
|
|
153
|
+
flow_logic: 'akış mantığı',
|
|
154
|
+
module_logic: 'modül mantığı',
|
|
155
|
+
data_model: 'veri modeli',
|
|
156
|
+
api_endpoint: 'API endpoint kaydı',
|
|
157
|
+
component_mapping: 'bileşen eşlemesi',
|
|
158
|
+
figma_mapping: 'Figma eşlemesi',
|
|
159
|
+
figma_reference: 'Figma referansı',
|
|
160
|
+
current_deviation: 'kayıtlı sapma',
|
|
161
|
+
quality_gate: 'kalite kapısı',
|
|
162
|
+
architecture_template: 'mimari şablonu',
|
|
163
|
+
};
|
|
164
|
+
function batchQuestion(turkish, proposals) {
|
|
165
|
+
const copy = batchCopy[turkish ? 'tr' : 'en'];
|
|
166
|
+
const clean = (text) => (restrictedValueKind(text, 'message', true) ? undefined : text);
|
|
167
|
+
const named = proposals.map((proposal) => {
|
|
168
|
+
const kind = (turkish && kindInTurkish[proposal.kind]) || proposal.kind.replaceAll('_', ' ');
|
|
169
|
+
return {
|
|
170
|
+
proposal,
|
|
171
|
+
kind,
|
|
172
|
+
title: clean(proposal.title.slice(0, 70)) ?? `${copy.untitled} ${kind}`,
|
|
173
|
+
};
|
|
174
|
+
});
|
|
175
|
+
const unversioned = named
|
|
176
|
+
.filter(({ proposal }) => proposal.sourceApplicabilityNotice)
|
|
177
|
+
.map(({ title }) => title)
|
|
178
|
+
.join(', ');
|
|
179
|
+
return {
|
|
180
|
+
message: [
|
|
181
|
+
proposals.length === 1 ? copy.one : copy.many(proposals.length),
|
|
182
|
+
...named.map(({ proposal, kind, title }, index) => {
|
|
183
|
+
const line = `${index + 1}. ${title} (${kind}, ${copy.scope[proposal.scope]}, ${proposal.currentRevisionNumber === 0
|
|
184
|
+
? copy.newRecord
|
|
185
|
+
: copy.revision(proposal.currentRevisionNumber)}${proposal.rebaseRequired ? `, ${copy.rebase}` : ''})`;
|
|
186
|
+
return clean(`${line} ${copy.why}: ${proposal.reasonPreview.slice(0, 60)}`) ?? line;
|
|
187
|
+
}),
|
|
188
|
+
].join('\n'),
|
|
189
|
+
context: copy.context + (unversioned ? copy.unversioned(unversioned) : ''),
|
|
190
|
+
example: copy.example,
|
|
191
|
+
options: [
|
|
192
|
+
{ id: 'approved', label: copy.approved[0], description: copy.approved[1] },
|
|
193
|
+
{ id: 'rejected', label: copy.rejected[0], description: copy.rejected[1] },
|
|
194
|
+
{ id: 'one_by_one', label: copy.oneByOne[0], description: copy.oneByOne[1] },
|
|
195
|
+
{ id: 'defer', label: copy.defer[0], description: copy.defer[1] },
|
|
196
|
+
],
|
|
197
|
+
};
|
|
198
|
+
}
|
|
79
199
|
const output = (result) => ({
|
|
80
200
|
content: [{ type: 'text', text: JSON.stringify(result) }],
|
|
81
201
|
...(!result.ok ? { isError: true } : {}),
|
|
@@ -131,6 +251,7 @@ const onboardingInput = z.strictObject({
|
|
|
131
251
|
mode: onboardingMode,
|
|
132
252
|
profile,
|
|
133
253
|
projectId: uuid.optional(),
|
|
254
|
+
language: languageTag.optional(),
|
|
134
255
|
reworkBehavior: z.enum(['preserve', 'redesign']).optional(),
|
|
135
256
|
});
|
|
136
257
|
const moveCopy = {
|
|
@@ -141,8 +262,16 @@ const moveCopy = {
|
|
|
141
262
|
unknown: 'adresi kayıtlı değil',
|
|
142
263
|
keeps: (tasks, records, items) => `Proje olduğu gibi devam eder: ${tasks} görev, ${records} hafıza kaydı ve ${items} iş kalemi projede kalır, hiçbir şey silinmez ve yeniden öğrenilmez.`,
|
|
143
264
|
after: 'Eski depo projenin geçmişinde saklanır. Hâlâ eski depoda çalışan ekip arkadaşları açık görevlerini bitirebilir, yeni görevi yeni depodan açar. Proje ileride yeniden taşınabilir.',
|
|
144
|
-
|
|
145
|
-
|
|
265
|
+
why: 'Projenin kodu başka bir depoya taşınmış. Bu klasör projenin evi sayılmadığı için burada yeni iş başlatılamıyor.',
|
|
266
|
+
example: 'Taşıdıktan sonra bu klasörde açtığın görev aynı projeye ait olur ve aynı hafızayı okur.',
|
|
267
|
+
approve: [
|
|
268
|
+
'Projeyi bu depoya taşı',
|
|
269
|
+
'Proje bundan sonra bu depoyu gösterir; burada hemen çalışmaya başlayabilirsin.',
|
|
270
|
+
],
|
|
271
|
+
decline: [
|
|
272
|
+
'Taşıma',
|
|
273
|
+
'Hiçbir şey değişmez. Proje mevcut deposunda kalır ve bu klasörde iş başlatılamaz.',
|
|
274
|
+
],
|
|
146
275
|
},
|
|
147
276
|
en: {
|
|
148
277
|
ask: (name) => `Move the project "${name}" to this repository?`,
|
|
@@ -151,21 +280,64 @@ const moveCopy = {
|
|
|
151
280
|
unknown: 'address not recorded',
|
|
152
281
|
keeps: (tasks, records, items) => `The project continues as it is: ${tasks} tasks, ${records} memory records and ${items} work items stay with it; nothing is deleted or learned again.`,
|
|
153
282
|
after: 'The old repository is kept in the project history. Teammates still working in it can finish their open tasks and start new ones from the new repository. The project can be moved again later.',
|
|
154
|
-
|
|
155
|
-
|
|
283
|
+
why: 'The code of this project moved to another repository. This folder is not recognised as the home of the project, so no new work can start here.',
|
|
284
|
+
example: 'After the move, a task started in this folder belongs to the same project and reads the same memory as before.',
|
|
285
|
+
approve: [
|
|
286
|
+
'Move the project here',
|
|
287
|
+
'The project points at this repository from now on and work can start here right away.',
|
|
288
|
+
],
|
|
289
|
+
decline: [
|
|
290
|
+
'Do not move',
|
|
291
|
+
'Nothing changes. The project keeps its current repository and no work can start in this folder.',
|
|
292
|
+
],
|
|
293
|
+
},
|
|
294
|
+
};
|
|
295
|
+
const planCopy = {
|
|
296
|
+
tr: {
|
|
297
|
+
one: 'Bu yeniden yazım planındaki tek aşama için iş kalemi oluşturalım mı?',
|
|
298
|
+
many: (count) => `Bu yeniden yazım planındaki ${count} aşama için iş kalemleri oluşturalım mı?`,
|
|
299
|
+
branch: 'dal',
|
|
300
|
+
more: (count) => `ve ${count} aşama daha`,
|
|
301
|
+
context: 'Onaylarsan plandaki her aşama için bağımlılık sırasına göre birer iş kalemi oluşturulur; başka hiçbir şey olmaz. Dal açılmaz, koda dokunulmaz. Her aşama daha sonra kendi görevi, kendi dalı ve kendi worktree ile ayrı ayrı yapılır.',
|
|
302
|
+
example: 'Onaydan sonra ilk aşama başlatılabilir bir iş kalemi olarak görünür; ona bağlı aşamalar o bitene kadar bekler.',
|
|
303
|
+
approve: [
|
|
304
|
+
'Bu iş kalemlerini oluştur',
|
|
305
|
+
'İş kalemleri listelendiği gibi şimdi oluşturulur. Aşamaların kodlanması yine ayrı ayrı başlar.',
|
|
306
|
+
],
|
|
307
|
+
defer: [
|
|
308
|
+
'Plan beklemede kalsın',
|
|
309
|
+
'Hiçbir şey değişmez. İş kalemi oluşturulmaz, planı sonra onaylayabilirsin.',
|
|
310
|
+
],
|
|
311
|
+
},
|
|
312
|
+
en: {
|
|
313
|
+
one: 'Create the work item for the single phase of this rework plan?',
|
|
314
|
+
many: (count) => `Create the work items for the ${count} phases of this rework plan?`,
|
|
315
|
+
branch: 'branch',
|
|
316
|
+
more: (count) => `and ${count} more phases`,
|
|
317
|
+
context: 'Approving creates one work item per phase, in the dependency order of the plan, and nothing else. No branch is created and no code is changed. Each phase is implemented later as its own task, with its own branch and worktree.',
|
|
318
|
+
example: 'After approval the first phase appears as a work item that can be started, and the phases that depend on it wait until it is done.',
|
|
319
|
+
approve: [
|
|
320
|
+
'Create these work items',
|
|
321
|
+
'The work items are created now, exactly as listed. Implementing each phase still starts separately.',
|
|
322
|
+
],
|
|
323
|
+
defer: [
|
|
324
|
+
'Keep the plan for further review',
|
|
325
|
+
'Nothing changes. No work item is created and the plan can be approved later.',
|
|
326
|
+
],
|
|
156
327
|
},
|
|
157
328
|
};
|
|
158
329
|
export function registerOnboardingTools(server, service) {
|
|
159
330
|
server.registerTool('project.move_repository', {
|
|
160
|
-
description: 'Move an existing project to the repository this checkout points at, after its code moved (another host, a new remote, a history restart). Project owners only. Opens ONE durable native form naming the old and the new repository; on approval the project keeps every task, memory record, work item and open session, the old repository is kept in its history, and this checkout is bound. Use it when project.resolve with bind refuses with recovery project.move_repository; never create a second project for moved code. Reconsider a declined move with a new decisionAttempt; retries keep the same attempt.',
|
|
331
|
+
description: 'Move an existing project to the repository this checkout points at, after its code moved (another host, a new remote, a history restart). Project owners only. Opens ONE durable native form naming the old and the new repository; on approval the project keeps every task, memory record, work item and open session, the old repository is kept in its history, and this checkout is bound. Use it when project.resolve with bind refuses with recovery project.move_repository; never create a second project for moved code. Pass language as the BCP-47 tag of the language the user writes in; it is remembered. Reconsider a declined move with a new decisionAttempt; retries keep the same attempt.',
|
|
161
332
|
inputSchema: z.strictObject({
|
|
162
333
|
repoRoot,
|
|
163
334
|
projectId: uuid,
|
|
164
335
|
decisionAttempt: z.number().int().min(1).max(10000).optional(),
|
|
165
|
-
language:
|
|
336
|
+
language: languageTag.optional(),
|
|
166
337
|
presentation: z.enum(['host_native']).optional(),
|
|
167
338
|
}),
|
|
168
339
|
}, async (input, context) => {
|
|
340
|
+
const language = (await service.language(input.language))?.startsWith('tr') ? 'tr' : 'en';
|
|
169
341
|
const preflight = await service.projectRepositoryMovePreflight(input);
|
|
170
342
|
if (!preflight.ok)
|
|
171
343
|
return output(preflight);
|
|
@@ -188,7 +360,9 @@ export function registerOnboardingTools(server, service) {
|
|
|
188
360
|
recovery: 'project.member_list',
|
|
189
361
|
},
|
|
190
362
|
});
|
|
191
|
-
const
|
|
363
|
+
const { name, records } = flight.history;
|
|
364
|
+
const from = flight.history.current.remoteIdentity;
|
|
365
|
+
const to = flight.checkout.remoteIdentity;
|
|
192
366
|
const questionnaireId = 'project-move-' +
|
|
193
367
|
sha256(stableStringify({
|
|
194
368
|
projectId: input.projectId,
|
|
@@ -197,26 +371,57 @@ export function registerOnboardingTools(server, service) {
|
|
|
197
371
|
version: flight.history.lockVersion,
|
|
198
372
|
...(input.decisionAttempt ? { decisionAttempt: input.decisionAttempt } : {}),
|
|
199
373
|
}));
|
|
374
|
+
const definitions = ['tr', 'en'].map((displayLanguage) => {
|
|
375
|
+
const copy = moveCopy[displayLanguage];
|
|
376
|
+
return {
|
|
377
|
+
questionnaireId,
|
|
378
|
+
language: displayLanguage,
|
|
379
|
+
message: [
|
|
380
|
+
copy.ask(name),
|
|
381
|
+
`${copy.from}: ${from ?? copy.unknown}`,
|
|
382
|
+
`${copy.to}: ${to ?? copy.unknown}`,
|
|
383
|
+
].join('\n'),
|
|
384
|
+
context: [
|
|
385
|
+
copy.why,
|
|
386
|
+
copy.keeps(records.tasks, records.memoryRecords, records.workItems),
|
|
387
|
+
copy.after,
|
|
388
|
+
].join(' '),
|
|
389
|
+
example: copy.example,
|
|
390
|
+
options: [
|
|
391
|
+
{ id: 'approve', label: copy.approve[0], description: copy.approve[1] },
|
|
392
|
+
{ id: 'decline', label: copy.decline[0], description: copy.decline[1] },
|
|
393
|
+
],
|
|
394
|
+
};
|
|
395
|
+
});
|
|
396
|
+
const previousWording = ['tr', 'en'].map((displayLanguage) => {
|
|
397
|
+
const copy = moveCopy[displayLanguage];
|
|
398
|
+
return {
|
|
399
|
+
questionnaireId,
|
|
400
|
+
language: displayLanguage,
|
|
401
|
+
message: [
|
|
402
|
+
copy.ask(name),
|
|
403
|
+
`${copy.from}: ${from ?? copy.unknown}`,
|
|
404
|
+
`${copy.to}: ${to ?? copy.unknown}`,
|
|
405
|
+
copy.keeps(records.tasks, records.memoryRecords, records.workItems),
|
|
406
|
+
copy.after,
|
|
407
|
+
].join('\n'),
|
|
408
|
+
options: [
|
|
409
|
+
{ id: 'approve', label: copy.approve[0] },
|
|
410
|
+
{ id: 'decline', label: copy.decline[0] },
|
|
411
|
+
],
|
|
412
|
+
};
|
|
413
|
+
});
|
|
200
414
|
const form = await askQuestionnaire(server, service, {
|
|
415
|
+
...definitions.find((entry) => entry.language === language),
|
|
201
416
|
repoRoot: input.repoRoot,
|
|
202
|
-
questionnaireId,
|
|
203
417
|
presentation: input.presentation,
|
|
204
|
-
|
|
205
|
-
message: [
|
|
206
|
-
copy.ask(flight.history.name),
|
|
207
|
-
`${copy.from}: ${flight.history.current.remoteIdentity ?? copy.unknown}`,
|
|
208
|
-
`${copy.to}: ${flight.checkout.remoteIdentity ?? copy.unknown}`,
|
|
209
|
-
copy.keeps(flight.history.records.tasks, flight.history.records.memoryRecords, flight.history.records.workItems),
|
|
210
|
-
copy.after,
|
|
211
|
-
].join('\n'),
|
|
212
|
-
options: [
|
|
213
|
-
{ id: 'approve', label: copy.approve },
|
|
214
|
-
{ id: 'decline', label: copy.decline },
|
|
215
|
-
],
|
|
216
|
-
}, context, [], {
|
|
418
|
+
}, context, [...definitions, ...previousWording], {
|
|
217
419
|
tool: 'project.move_repository',
|
|
218
420
|
...(input.decisionAttempt ? { decisionAttempt: input.decisionAttempt } : {}),
|
|
219
|
-
retryArguments: {
|
|
421
|
+
retryArguments: {
|
|
422
|
+
projectId: input.projectId,
|
|
423
|
+
...(input.language ? { language: input.language } : {}),
|
|
424
|
+
},
|
|
220
425
|
});
|
|
221
426
|
const record = await service.questionnaireResume({
|
|
222
427
|
repoRoot: input.repoRoot,
|
|
@@ -270,11 +475,11 @@ export function registerOnboardingTools(server, service) {
|
|
|
270
475
|
inputSchema: z.object({ repoRoot }),
|
|
271
476
|
}, async (input) => output(await service.projectInspect(input)));
|
|
272
477
|
server.registerTool('project.onboard', {
|
|
273
|
-
description: 'Choose greenfield, adopt, rework or refresh through durable native forms, then approve a stack-specific profile: palette, fonts/text styles, Figma, navigation, ready services and accessible backend links, or backend API/storage/migrations/environment choices. The agent proposes values from evidence and user answers; never invent defaults. The exact choice persists outside the repository and, with projectId, in the project onboarding record. This does not activate an engineering-memory revision.',
|
|
478
|
+
description: 'Choose greenfield, adopt, rework or refresh through durable native forms, then approve a stack-specific profile: palette, fonts/text styles, Figma, navigation, ready services and accessible backend links, or backend API/storage/migrations/environment choices. The agent proposes values from evidence and user answers; never invent defaults. Pass language as the BCP-47 tag of the language the user writes in; it is remembered. The exact choice persists outside the repository and, with projectId, in the project onboarding record. This does not activate an engineering-memory revision.',
|
|
274
479
|
inputSchema: onboardingInput,
|
|
275
480
|
}, async (input, context) => approveOnboarding(server, service, input, context, false));
|
|
276
481
|
server.registerTool('project.initialize', {
|
|
277
|
-
description: 'Choose the onboarding mode and profile through native forms before creating a folder or Git. Only an approved greenfield choice initializes Git; preserves existing files, including barely-started projects. Other choices return without changing source. Use the exact same requestKey/profile for retries.',
|
|
482
|
+
description: 'Choose the onboarding mode and profile through native forms before creating a folder or Git. Only an approved greenfield choice initializes Git; preserves existing files, including barely-started projects. Other choices return without changing source. Pass language as the BCP-47 tag of the language the user writes in; it is remembered. Use the exact same requestKey/profile for retries.',
|
|
278
483
|
inputSchema: onboardingInput,
|
|
279
484
|
}, async (input, context) => approveOnboarding(server, service, input, context, true));
|
|
280
485
|
server.registerTool('project.onboarding_status', {
|
|
@@ -307,10 +512,11 @@ export function registerOnboardingTools(server, service) {
|
|
|
307
512
|
preparation: true,
|
|
308
513
|
})));
|
|
309
514
|
server.registerTool('project.rework_plan', {
|
|
310
|
-
description: 'After completed deep learning and approved target-architecture revisions, approve an exact phased plan through a native form. Materializes real dependency-ordered WorkItems atomically and idempotently. Each phase names inspected units and a branch; later implementation uses existing task.branch/worktrees and each repository lifecycle. No source changes happen here.',
|
|
515
|
+
description: 'After completed deep learning and approved target-architecture revisions, approve an exact phased plan through a native form. Materializes real dependency-ordered WorkItems atomically and idempotently. Each phase names inspected units and a branch; later implementation uses existing task.branch/worktrees and each repository lifecycle. Pass language as the BCP-47 tag of the language the user writes in; it is remembered. No source changes happen here.',
|
|
311
516
|
inputSchema: z.strictObject({
|
|
312
517
|
...run,
|
|
313
518
|
repoRoot,
|
|
519
|
+
language: languageTag.optional(),
|
|
314
520
|
onboardingId: uuid,
|
|
315
521
|
requestKey: z.string().regex(/^[A-Za-z0-9_-]{1,80}$/),
|
|
316
522
|
targetRevisions: z
|
|
@@ -331,19 +537,49 @@ export function registerOnboardingTools(server, service) {
|
|
|
331
537
|
.max(100),
|
|
332
538
|
}),
|
|
333
539
|
}, async (input, context) => {
|
|
334
|
-
const { repoRoot: root, ...plan } = input;
|
|
540
|
+
const { repoRoot: root, language: told, ...plan } = input;
|
|
541
|
+
const language = (await service.language(told))?.startsWith('tr') ? 'tr' : 'en';
|
|
335
542
|
const digest = sha256(stableStringify(plan));
|
|
336
|
-
const
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
543
|
+
const questionnaireId = 'rework-' + digest;
|
|
544
|
+
const shown = plan.phases.slice(0, 8);
|
|
545
|
+
const definitions = ['tr', 'en'].map((displayLanguage) => {
|
|
546
|
+
const copy = planCopy[displayLanguage];
|
|
547
|
+
return {
|
|
548
|
+
questionnaireId,
|
|
549
|
+
language: displayLanguage,
|
|
550
|
+
message: [
|
|
551
|
+
plan.phases.length === 1 ? copy.one : copy.many(plan.phases.length),
|
|
552
|
+
...shown.map((phase, index) => {
|
|
553
|
+
const branch = `${copy.branch}: ${phase.branch.slice(0, 60)}`;
|
|
554
|
+
const line = `${index + 1}. ${phase.title.slice(0, 70)} (${branch})`;
|
|
555
|
+
return restrictedValueKind(line, 'message', true) ? `${index + 1}. ${branch}` : line;
|
|
556
|
+
}),
|
|
557
|
+
...(plan.phases.length > shown.length
|
|
558
|
+
? [copy.more(plan.phases.length - shown.length)]
|
|
559
|
+
: []),
|
|
560
|
+
].join('\n'),
|
|
561
|
+
context: copy.context,
|
|
562
|
+
example: copy.example,
|
|
563
|
+
options: [
|
|
564
|
+
{ id: 'approve', label: copy.approve[0], description: copy.approve[1] },
|
|
565
|
+
{ id: 'defer', label: copy.defer[0], description: copy.defer[1] },
|
|
566
|
+
],
|
|
567
|
+
binding: { planDigest: digest },
|
|
568
|
+
};
|
|
569
|
+
});
|
|
570
|
+
const answer = await askQuestionnaire(server, service, { ...definitions.find((entry) => entry.language === language), repoRoot: root }, context, [
|
|
571
|
+
...definitions,
|
|
572
|
+
{
|
|
573
|
+
questionnaireId,
|
|
574
|
+
message: 'Approve the phased rework plan already presented in full, including target revisions, units, acceptance criteria and branches? Digest: ' +
|
|
575
|
+
digest +
|
|
576
|
+
'. Creates work items only; each implementation needs its normal branch/worktree lifecycle.',
|
|
577
|
+
options: [
|
|
578
|
+
{ id: 'approve', label: 'Create these exact phases' },
|
|
579
|
+
{ id: 'defer', label: 'Keep the plan for further review' },
|
|
580
|
+
],
|
|
581
|
+
},
|
|
582
|
+
]);
|
|
347
583
|
if (answerChoice(answer) !== 'approve')
|
|
348
584
|
return answer;
|
|
349
585
|
return output(await service.onboardingRequest('rework', body(plan)));
|
|
@@ -364,16 +600,13 @@ export function registerOnboardingTools(server, service) {
|
|
|
364
600
|
}),
|
|
365
601
|
}, async (input) => output(await service.memorySource(input)));
|
|
366
602
|
server.registerTool('memory.sync_start', {
|
|
367
|
-
description: 'Start or resume an idempotent committed-source inspection after native mode approval.
|
|
603
|
+
description: 'Start or resume an idempotent committed-source inspection after native mode approval. Pass language as the BCP-47 tag of the language the user writes in, never the machine locale; it is remembered. Uploads all manifest pages, including exclusions. Request key and source are immutable. Refresh references a completed earlier run; rework requires preserve/redesign. Analyze with the current agent; no backend LLM.',
|
|
368
604
|
inputSchema: z.object({
|
|
369
605
|
...project,
|
|
370
606
|
repoRoot,
|
|
371
607
|
requestKey: z.string().regex(/^[a-zA-Z0-9_-]{1,120}$/),
|
|
372
608
|
mode: z.enum(['adopt', 'refresh', 'rework']),
|
|
373
|
-
language:
|
|
374
|
-
.enum(['tr', 'en'])
|
|
375
|
-
.optional()
|
|
376
|
-
.describe('Use the current conversation language: tr for Turkish, en for English. Omitted by legacy callers means English.'),
|
|
609
|
+
language: languageTag.optional(),
|
|
377
610
|
ref: z.string().optional(),
|
|
378
611
|
baseSnapshotId: uuid.optional(),
|
|
379
612
|
previousRunId: uuid.optional(),
|
|
@@ -398,7 +631,8 @@ export function registerOnboardingTools(server, service) {
|
|
|
398
631
|
recovery: 'memory.sync_status',
|
|
399
632
|
},
|
|
400
633
|
});
|
|
401
|
-
const { language
|
|
634
|
+
const { language: told, ...request } = input;
|
|
635
|
+
const language = (await service.language(told))?.startsWith('tr') ? 'tr' : 'en';
|
|
402
636
|
const pinned = { ...request, ref: commit };
|
|
403
637
|
const digest = sha256(stableStringify(pinned));
|
|
404
638
|
const reference = input.ref ?? 'HEAD';
|
|
@@ -406,6 +640,24 @@ export function registerOnboardingTools(server, service) {
|
|
|
406
640
|
? commit.slice(0, 12)
|
|
407
641
|
: `${reference.slice(0, 80)} · ${commit.slice(0, 12)}`;
|
|
408
642
|
const definitions = ['tr', 'en'].map((displayLanguage) => {
|
|
643
|
+
const copy = inspectionCopy[displayLanguage];
|
|
644
|
+
return {
|
|
645
|
+
questionnaireId: 'sync-start-' + digest,
|
|
646
|
+
language: displayLanguage,
|
|
647
|
+
message: [
|
|
648
|
+
copy.modes[input.mode],
|
|
649
|
+
`${copy.source}: ${sourceLabel}`,
|
|
650
|
+
...(input.reworkBehavior ? [copy.rework[input.reworkBehavior]] : []),
|
|
651
|
+
].join('\n\n'),
|
|
652
|
+
context: [copy.why[input.mode], copy.scope, copy.localChanges, copy.review].join(' '),
|
|
653
|
+
example: copy.example,
|
|
654
|
+
options: [
|
|
655
|
+
{ id: 'approve', label: copy.start, description: copy.startDetail },
|
|
656
|
+
{ id: 'defer', label: copy.defer, description: copy.deferDetail },
|
|
657
|
+
],
|
|
658
|
+
};
|
|
659
|
+
});
|
|
660
|
+
const previousWording = ['tr', 'en'].map((displayLanguage) => {
|
|
409
661
|
const copy = inspectionCopy[displayLanguage];
|
|
410
662
|
return {
|
|
411
663
|
questionnaireId: 'sync-start-' + digest,
|
|
@@ -427,6 +679,7 @@ export function registerOnboardingTools(server, service) {
|
|
|
427
679
|
const definition = definitions.find((entry) => entry.language === language);
|
|
428
680
|
const question = await askQuestionnaire(server, service, { ...definition, repoRoot: input.repoRoot }, context, [
|
|
429
681
|
...definitions,
|
|
682
|
+
...previousWording,
|
|
430
683
|
{
|
|
431
684
|
questionnaireId: 'sync-start-' + digest,
|
|
432
685
|
message: 'Start ' +
|
|
@@ -594,40 +847,69 @@ export function registerOnboardingTools(server, service) {
|
|
|
594
847
|
}),
|
|
595
848
|
}, async (input) => output(await service.onboardingRequest('read_revisions', body(input))));
|
|
596
849
|
server.registerTool('memory.review_batch', {
|
|
597
|
-
description: '
|
|
850
|
+
description: 'Collect native approval for these exact proposal ids, hashes and base revisions. The form shows each record by title, kind, scope, revision and reason; present the full contents through memory.list_proposals first when the user has not seen them. A form holds at most eight records: review a larger set as separate calls of eight or fewer. Pass language as the BCP-47 tag of the language the user writes in; it is remembered. Pending/dismissed forms never write. Stale revisions fail.',
|
|
598
851
|
inputSchema: z.strictObject({
|
|
599
852
|
...project,
|
|
600
853
|
repoRoot,
|
|
601
|
-
entries: z.array(reference).min(1).max(
|
|
854
|
+
entries: z.array(reference).min(1).max(8),
|
|
602
855
|
reviewAttempt: z.number().int().min(0).max(10000).default(0),
|
|
856
|
+
language: languageTag.optional(),
|
|
603
857
|
}),
|
|
604
858
|
}, async (input, context) => {
|
|
605
859
|
const entries = [...input.entries].sort((a, b) => a.proposalId.localeCompare(b.proposalId));
|
|
606
860
|
const digest = sha256(stableStringify({ projectId: input.projectId, entries }));
|
|
607
|
-
const
|
|
861
|
+
const question = {
|
|
608
862
|
repoRoot: input.repoRoot,
|
|
609
|
-
questionnaireId: 'memory-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
863
|
+
questionnaireId: 'memory-review-' + digest + '-' + input.reviewAttempt,
|
|
864
|
+
};
|
|
865
|
+
const binding = body({ projectId: input.projectId, entries });
|
|
866
|
+
const language = (await service.language(input.language))?.startsWith('tr') ? 'tr' : 'en';
|
|
867
|
+
const stored = await service.questionnaireResume(question).catch(() => undefined);
|
|
868
|
+
let result;
|
|
869
|
+
if (stored && stableStringify(stored.binding) === stableStringify(binding))
|
|
870
|
+
result = await resumeQuestionnaire(server, service, question, context);
|
|
871
|
+
else {
|
|
872
|
+
const pending = new Map();
|
|
873
|
+
for (let afterId;;) {
|
|
874
|
+
const page = await service.memoryListProposals({
|
|
875
|
+
projectId: input.projectId,
|
|
876
|
+
limit: 100,
|
|
877
|
+
afterId,
|
|
878
|
+
});
|
|
879
|
+
if (!page.ok)
|
|
880
|
+
return output(page);
|
|
881
|
+
const data = page.data;
|
|
882
|
+
for (const proposal of data.proposals)
|
|
883
|
+
pending.set(proposal.proposalId, proposal);
|
|
884
|
+
if (!data.nextAfterId || entries.every((entry) => pending.has(entry.proposalId)))
|
|
885
|
+
break;
|
|
886
|
+
afterId = data.nextAfterId;
|
|
887
|
+
}
|
|
888
|
+
const proposals = entries.flatMap((entry) => pending.get(entry.proposalId) ?? []);
|
|
889
|
+
if (proposals.length !== entries.length || proposals.some((p) => !p.reviewableByCaller))
|
|
890
|
+
return output({
|
|
891
|
+
ok: false,
|
|
892
|
+
error: {
|
|
893
|
+
kind: 'bridge_error',
|
|
894
|
+
message: 'Some of these proposals are no longer pending or cannot be reviewed by this account. List the pending proposals again with memory.list_proposals and send the entries it returns. Nothing was changed.',
|
|
895
|
+
retryable: false,
|
|
896
|
+
recovery: 'memory.list_proposals',
|
|
897
|
+
},
|
|
898
|
+
});
|
|
899
|
+
result = await askQuestionnaire(server, service, { ...question, language, ...batchQuestion(language === 'tr', proposals), binding }, context);
|
|
900
|
+
}
|
|
621
901
|
const decision = answerChoice(result);
|
|
622
|
-
if (decision === 'defer')
|
|
902
|
+
if (decision === 'defer' || decision === 'one_by_one')
|
|
623
903
|
return output({
|
|
624
904
|
ok: true,
|
|
625
905
|
data: {
|
|
626
|
-
status: 'deferred',
|
|
906
|
+
status: decision === 'defer' ? 'deferred' : 'one_by_one',
|
|
627
907
|
digest,
|
|
628
908
|
reviewAttempt: input.reviewAttempt,
|
|
629
909
|
nextReviewAttempt: input.reviewAttempt + 1,
|
|
630
|
-
nextAction:
|
|
910
|
+
nextAction: decision === 'defer'
|
|
911
|
+
? 'When the user is ready to review again, call memory.review_batch with the same exact entries and nextReviewAttempt as reviewAttempt. Retrying this attempt preserves the deferral.'
|
|
912
|
+
: 'Nothing was published. Show each record in full with memory.list_proposals, then collect the approval of each one with its own memory.review_batch call holding that single entry. Never call memory.review_proposal without that native approval.',
|
|
631
913
|
},
|
|
632
914
|
});
|
|
633
915
|
if (decision !== 'approved' && decision !== 'rejected')
|
|
@@ -640,22 +922,299 @@ export function registerOnboardingTools(server, service) {
|
|
|
640
922
|
})));
|
|
641
923
|
});
|
|
642
924
|
}
|
|
925
|
+
const profileFieldNames = {
|
|
926
|
+
tr: {
|
|
927
|
+
stack: 'Uygulama teknolojisi',
|
|
928
|
+
runtime: 'Çalışma ortamı ve araç sürümleri',
|
|
929
|
+
architecture: 'Uygulama mimarisi',
|
|
930
|
+
environments: 'Ortamlar ve konfigürasyon',
|
|
931
|
+
serviceReadiness: 'Servislerin hazır olup olmadığı',
|
|
932
|
+
apiContract: 'API sözleşmesi',
|
|
933
|
+
authentication: 'Kimlik doğrulama',
|
|
934
|
+
storage: 'Veri saklama',
|
|
935
|
+
backendProjectIds: 'Bağlı backend projeleri',
|
|
936
|
+
migrations: 'Migration stratejisi',
|
|
937
|
+
figma: 'Figma kaynağı',
|
|
938
|
+
colorPalette: 'Renk paleti',
|
|
939
|
+
typography: 'Yazı tipleri ve metin stilleri',
|
|
940
|
+
designSystem: 'Tasarım sistemi',
|
|
941
|
+
navigation: 'Gezinme',
|
|
942
|
+
platforms: 'Platformlar',
|
|
943
|
+
localization: 'Desteklenen diller',
|
|
944
|
+
},
|
|
945
|
+
en: {
|
|
946
|
+
stack: 'Application stack',
|
|
947
|
+
runtime: 'Runtime and toolchain contract',
|
|
948
|
+
architecture: 'Application architecture',
|
|
949
|
+
environments: 'Environments and configuration',
|
|
950
|
+
serviceReadiness: 'Service readiness',
|
|
951
|
+
apiContract: 'API contract',
|
|
952
|
+
authentication: 'Authentication',
|
|
953
|
+
storage: 'Storage and persistence',
|
|
954
|
+
backendProjectIds: 'Existing backend project links',
|
|
955
|
+
migrations: 'Migration strategy',
|
|
956
|
+
figma: 'Figma source',
|
|
957
|
+
colorPalette: 'Color palette',
|
|
958
|
+
typography: 'Font families and text styles',
|
|
959
|
+
designSystem: 'Design system',
|
|
960
|
+
navigation: 'Navigation',
|
|
961
|
+
platforms: 'Platforms',
|
|
962
|
+
localization: 'Localization languages',
|
|
963
|
+
},
|
|
964
|
+
};
|
|
965
|
+
const onboardingCopy = {
|
|
966
|
+
tr: {
|
|
967
|
+
mode: {
|
|
968
|
+
message: 'Bu proje Engineering Memory hafızasına nasıl alınsın?',
|
|
969
|
+
context: 'Cevabın ilk adımı belirler: proje sıfırdan mı kurulacak, mevcut proje olduğu gibi okunup korunacak mı, yoksa önce okunup sonra aşama aşama mı yeniden yazılacak. Mevcut bir projeyi okumak ne kodu ne de mimarisini değiştirir.',
|
|
970
|
+
example: 'Mevcut projeyi öğrenmeyi seçersen bir sonraki adım kodu okuyup nasıl çalıştığını kayda geçirir; tek satır bile değişmez.',
|
|
971
|
+
greenfield: [
|
|
972
|
+
'Yeni ya da yeni başlamış bir proje kur',
|
|
973
|
+
'Proje birazdan onaylayacağın profille başlar; mevcut kod okunmaz.',
|
|
974
|
+
],
|
|
975
|
+
adopt: [
|
|
976
|
+
'Mevcut mimariyi öğren ve koru',
|
|
977
|
+
'Kod olduğu gibi okunup hafızaya yazılır; hiçbir şey yeniden tasarlanmaz.',
|
|
978
|
+
],
|
|
979
|
+
rework: [
|
|
980
|
+
'Önce öğren, sonra aşama aşama yeniden yazmayı planla',
|
|
981
|
+
'Önce kod okunur; yeniden yazım planı ayrıca onaylanmadan hiçbir şey değişmez.',
|
|
982
|
+
],
|
|
983
|
+
refresh: [
|
|
984
|
+
'Hafızayı güncel projeyle tazele',
|
|
985
|
+
'Hafızadaki kayıtlar güncel kodla karşılaştırılır, farklar öneri olarak önüne gelir.',
|
|
986
|
+
],
|
|
987
|
+
},
|
|
988
|
+
behavior: {
|
|
989
|
+
message: 'Yeniden yazım, ürünün bugünkü davranışını koruyacak mı yoksa ürünü yeniden mi tasarlayacak?',
|
|
990
|
+
context: 'Bu seçim hem incelemenin hem de planın hedefini belirler. Korumak, kullanıcının gördüğü ekranlar ve akışlar aynı kalırken altındaki kodun değişmesi demek. Yeniden tasarlamak ürünün kendisinin değişmesi demek; o zaman hedef ekranlar, akışlar ve sözleşmeler uygulamaya geçilmeden önce ayrıca onaylanır.',
|
|
991
|
+
example: 'Davranışı korursan, bugün seri numarası soran ekran yeniden yazımdan sonra da seri numarası sorar.',
|
|
992
|
+
preserve: [
|
|
993
|
+
'Bugünkü davranışı koru',
|
|
994
|
+
'Yeniden yazım yalnızca kodun yapısını değiştirir; kullanıcının gördüğü aynı kalır.',
|
|
995
|
+
],
|
|
996
|
+
redesign: [
|
|
997
|
+
'Ürünü açıkça yeniden tasarla',
|
|
998
|
+
'Hedef ekranlar, akışlar ve sözleşmeler onaylanmadan hiçbir şey uygulanmaz.',
|
|
999
|
+
],
|
|
1000
|
+
},
|
|
1001
|
+
field: {
|
|
1002
|
+
value: 'Önerilen değer',
|
|
1003
|
+
empty: 'henüz belirlenmedi',
|
|
1004
|
+
context: 'Bu değer kurulum seçimleriyle birlikte kaydedilir; sonraki görevler tahmin etmek yerine buradan okur. Olduğu gibi bırakabilir, farklı bir değer yazabilir ya da şimdilik boş bırakabilirsin; boş bıraktığın bir değer sessizce kaydedilmez.',
|
|
1005
|
+
example: 'Çalışma ortamı Node 22 olarak kalırsa sonraki görev makinede ne varsa ona göre değil, Node 22 üzerinde derleyip test eder.',
|
|
1006
|
+
keep: ['Bu değeri olduğu gibi kaydet', 'Yukarıdaki değer aynen kaydedilir.'],
|
|
1007
|
+
change: (value) => `${value} olarak değiştir`,
|
|
1008
|
+
changeDetail: (value) => `Proje ${value} olarak kaydedilir ve kalan sorular buna göre gelir.`,
|
|
1009
|
+
edit: [
|
|
1010
|
+
'Aşağıdaki alana farklı bir değer yaz',
|
|
1011
|
+
'Aşağıya yazdığın metin, yukarıdaki değerin yerine kaydedilir.',
|
|
1012
|
+
],
|
|
1013
|
+
defer: [
|
|
1014
|
+
'Bu değeri şimdilik boş bırak',
|
|
1015
|
+
'Hiçbir şey kaydedilmez. Kurulum burada durur; karar verdiğinde bu soruya dönersin.',
|
|
1016
|
+
],
|
|
1017
|
+
editTitle: 'Yeni değer',
|
|
1018
|
+
idsTitle: 'Proje ID listesi (JSON dizisi)',
|
|
1019
|
+
},
|
|
1020
|
+
git: {
|
|
1021
|
+
development: {
|
|
1022
|
+
message: 'Yeni görev dalları hangi daldan başlasın?',
|
|
1023
|
+
context: 'Bundan sonra açtığın her görev bu daldan dallanır ve bu dalla karşılaştırılır. Henüz var olmayan bir depo için ilk dalın alacağı ismi yaz. Dal, depo origin adresine bağlandıktan sonra orada kullanılır.',
|
|
1024
|
+
example: 'Buraya main yazarsan sonraki görev main üzerinden dallanır ve pull request main dalına açılır.',
|
|
1025
|
+
second: [
|
|
1026
|
+
'Şimdilik kuruluma başlama',
|
|
1027
|
+
'Hiçbir şey kaydedilmez, kurulum burada durur; istediğinde devam edebilirsin.',
|
|
1028
|
+
],
|
|
1029
|
+
},
|
|
1030
|
+
production: {
|
|
1031
|
+
message: 'Bu projenin canlı sürümü hangi daldan çıkıyor?',
|
|
1032
|
+
context: 'Bir kez kaydedilir; sonraki işler biten bir değişikliğin nereye gideceğini buradan bilir. Böyle bir dal henüz yoksa bunu açıkça söyle, bir daha sorulmaz.',
|
|
1033
|
+
example: 'Canlı dal main ise, biten bir görevin nereye gideceği sana tekrar sorulmaz.',
|
|
1034
|
+
second: [
|
|
1035
|
+
'Henüz canlı dal yok',
|
|
1036
|
+
'Yok olarak kaydedilir ve bir daha sorulmaz. İleride belirleyebilirsin.',
|
|
1037
|
+
],
|
|
1038
|
+
},
|
|
1039
|
+
test: {
|
|
1040
|
+
message: 'Bu proje hangi dalda test ediliyor?',
|
|
1041
|
+
context: 'Bir kez kaydedilir; sonraki işler biten bir değişikliğin nerede test edileceğini buradan bilir. Böyle bir dal henüz yoksa bunu açıkça söyle, bir daha sorulmaz.',
|
|
1042
|
+
example: 'Test dalı develop ise, testlerin hangi dalda koştuğu sana tekrar sorulmaz.',
|
|
1043
|
+
second: [
|
|
1044
|
+
'Henüz test dalı yok',
|
|
1045
|
+
'Yok olarak kaydedilir ve bir daha sorulmaz. İleride belirleyebilirsin.',
|
|
1046
|
+
],
|
|
1047
|
+
},
|
|
1048
|
+
set: ['Aşağıya yazdığım dalı kullan', 'Yazdığın dal bu proje için kaydedilir.'],
|
|
1049
|
+
branchTitle: 'Dal adı',
|
|
1050
|
+
},
|
|
1051
|
+
save: {
|
|
1052
|
+
message: 'Bu kurulum seçimlerini kaydedelim mi?',
|
|
1053
|
+
routeLabel: 'Seçilen yol',
|
|
1054
|
+
route: {
|
|
1055
|
+
greenfield: 'yeni bir proje kur',
|
|
1056
|
+
adopt: 'mevcut projeyi öğren ve koru',
|
|
1057
|
+
rework: 'önce öğren, sonra aşama aşama yeniden yazmayı planla',
|
|
1058
|
+
refresh: 'hafızayı güncel projeyle tazele',
|
|
1059
|
+
},
|
|
1060
|
+
context: 'Verdiğin cevaplar bu projenin kurulum kaydı olur; sonraki işler teknolojiyi, mimariyi ve kalan seçimleri oradan okur. Hafıza kayıtları ve proje bağlantıları ayrıca onaylanır, burada hafızaya hiçbir şey yayınlanmaz.',
|
|
1061
|
+
git: ' Bu klasörde Git hazırlanır; mevcut dosyalar olduğu gibi kalır, hiçbiri silinmez veya değiştirilmez.',
|
|
1062
|
+
example: 'Kaydettikten sonra sonraki görev teknolojiyi ve mimariyi sana tekrar sormadan buradan okur.',
|
|
1063
|
+
approve: ['Bu seçimleri kaydet', 'Seçimler kaydedilir. Kodda hiçbir şey değişmez.'],
|
|
1064
|
+
approveGit: 'Seçimler kaydedilir ve bu klasörde Git hazırlanır.',
|
|
1065
|
+
defer: [
|
|
1066
|
+
'Seçimleri beklemede bırak',
|
|
1067
|
+
'Hiçbir şey kaydedilmez. Soruları sonra yeniden geçebilirsin.',
|
|
1068
|
+
],
|
|
1069
|
+
},
|
|
1070
|
+
},
|
|
1071
|
+
en: {
|
|
1072
|
+
mode: {
|
|
1073
|
+
message: 'How should this project enter Engineering Memory?',
|
|
1074
|
+
context: 'The answer decides what happens first: the project is set up from scratch, the existing one is read and preserved as it is, or it is read first and then reworked phase by phase. Reading an existing project never changes its code or its architecture.',
|
|
1075
|
+
example: 'Choosing to learn the existing project means the next step reads the code and writes down how it works, without changing a line.',
|
|
1076
|
+
greenfield: [
|
|
1077
|
+
'Set up a new or barely-started project',
|
|
1078
|
+
'The project starts from the profile you approve next; no existing code is read.',
|
|
1079
|
+
],
|
|
1080
|
+
adopt: [
|
|
1081
|
+
'Learn and preserve the existing architecture',
|
|
1082
|
+
'The code is read as it stands and written into memory; nothing is redesigned.',
|
|
1083
|
+
],
|
|
1084
|
+
rework: [
|
|
1085
|
+
'Learn first, then plan a phased rework',
|
|
1086
|
+
'The code is read first, and nothing changes until the rework plan is approved separately.',
|
|
1087
|
+
],
|
|
1088
|
+
refresh: [
|
|
1089
|
+
'Refresh memory against the current project',
|
|
1090
|
+
'The records already in memory are compared with the current code and the differences come back as proposals.',
|
|
1091
|
+
],
|
|
1092
|
+
},
|
|
1093
|
+
behavior: {
|
|
1094
|
+
message: 'Should the rework keep the product behaving as it does today, or redesign it?',
|
|
1095
|
+
context: 'This decides what both the inspection and the plan aim at. Preserving means the screens and flows a user sees stay the same while the code underneath changes. Redesigning means the product itself changes, and the target screens, flows and contracts are approved before any implementation starts.',
|
|
1096
|
+
example: 'If the behavior is preserved, a screen that asks for a serial number today still asks for it after the rework.',
|
|
1097
|
+
preserve: [
|
|
1098
|
+
'Preserve current behavior',
|
|
1099
|
+
'The rework only changes how the code is built; what the user sees stays the same.',
|
|
1100
|
+
],
|
|
1101
|
+
redesign: [
|
|
1102
|
+
'Redesign the product explicitly',
|
|
1103
|
+
'Nothing is implemented until the target screens, flows and contracts are approved.',
|
|
1104
|
+
],
|
|
1105
|
+
},
|
|
1106
|
+
field: {
|
|
1107
|
+
value: 'Proposed value',
|
|
1108
|
+
empty: 'not set yet',
|
|
1109
|
+
context: 'This value is saved with the onboarding choices and later work reads it from there instead of guessing. Keep it, write a different one, or leave it undecided; a value left undecided is never saved quietly.',
|
|
1110
|
+
example: 'If the runtime stays Node 22, a later task builds and tests on Node 22 instead of whatever the machine happens to have.',
|
|
1111
|
+
keep: ['Keep this exact value', 'The value above is saved as it stands.'],
|
|
1112
|
+
change: (value) => `Change to ${value}`,
|
|
1113
|
+
changeDetail: (value) => `The project is saved as ${value} and the remaining questions follow it.`,
|
|
1114
|
+
edit: [
|
|
1115
|
+
'Edit this value in the field below',
|
|
1116
|
+
'What you write below is saved instead of the value above.',
|
|
1117
|
+
],
|
|
1118
|
+
defer: [
|
|
1119
|
+
'Defer this value explicitly',
|
|
1120
|
+
'Nothing is saved. Onboarding stops here and you come back to this question once you have decided.',
|
|
1121
|
+
],
|
|
1122
|
+
editTitle: 'Edited value',
|
|
1123
|
+
idsTitle: 'Project ID JSON array',
|
|
1124
|
+
},
|
|
1125
|
+
git: {
|
|
1126
|
+
development: {
|
|
1127
|
+
message: 'Which branch should new task branches start from?',
|
|
1128
|
+
context: 'Every task you start later branches off this one and is compared against it. For a repository that does not exist yet, write the name its first branch will have. The branch is used on the origin remote once that remote is connected.',
|
|
1129
|
+
example: 'With main here, a later task branches off main and its pull request targets main.',
|
|
1130
|
+
second: [
|
|
1131
|
+
'Do not start setup yet',
|
|
1132
|
+
'Nothing is saved and setup stops here; you can continue whenever you want.',
|
|
1133
|
+
],
|
|
1134
|
+
},
|
|
1135
|
+
production: {
|
|
1136
|
+
message: 'Which branch is this project released from?',
|
|
1137
|
+
context: 'It is recorded once so later work knows where a finished change goes. If there is no such branch yet, say so explicitly and you are not asked again.',
|
|
1138
|
+
example: 'With main as the release branch, where a finished task goes is never asked again.',
|
|
1139
|
+
second: [
|
|
1140
|
+
'There is no release branch yet',
|
|
1141
|
+
'It is recorded as absent and you are not asked again. You can set it later.',
|
|
1142
|
+
],
|
|
1143
|
+
},
|
|
1144
|
+
test: {
|
|
1145
|
+
message: 'Which branch is this project tested on?',
|
|
1146
|
+
context: 'It is recorded once so later work knows where a finished change is tested. If there is no such branch yet, say so explicitly and you are not asked again.',
|
|
1147
|
+
example: 'With develop as the test branch, where the tests run is never asked again.',
|
|
1148
|
+
second: [
|
|
1149
|
+
'There is no test branch yet',
|
|
1150
|
+
'It is recorded as absent and you are not asked again. You can set it later.',
|
|
1151
|
+
],
|
|
1152
|
+
},
|
|
1153
|
+
set: ['Use the branch named below', 'That branch is saved for this project.'],
|
|
1154
|
+
branchTitle: 'Branch name',
|
|
1155
|
+
},
|
|
1156
|
+
save: {
|
|
1157
|
+
message: 'Save the onboarding choices you just went through?',
|
|
1158
|
+
routeLabel: 'Chosen route',
|
|
1159
|
+
route: {
|
|
1160
|
+
greenfield: 'set up a new project',
|
|
1161
|
+
adopt: 'learn and preserve the existing project',
|
|
1162
|
+
rework: 'learn first, then plan a phased rework',
|
|
1163
|
+
refresh: 'refresh memory against the current project',
|
|
1164
|
+
},
|
|
1165
|
+
context: 'Your answers become the onboarding record of this project, and later work reads the stack, the architecture and the remaining choices from there. Memory records and project links are approved separately; nothing is published to memory here.',
|
|
1166
|
+
git: ' Git is prepared in this folder: existing files stay as they are and none of them is deleted or replaced.',
|
|
1167
|
+
example: 'After saving, a later task reads the stack and the architecture from here instead of asking you again.',
|
|
1168
|
+
approve: [
|
|
1169
|
+
'Save these exact onboarding choices',
|
|
1170
|
+
'The choices are saved. Nothing in the code changes.',
|
|
1171
|
+
],
|
|
1172
|
+
approveGit: 'The choices are saved and Git is prepared in this folder.',
|
|
1173
|
+
defer: [
|
|
1174
|
+
'Keep the choices pending',
|
|
1175
|
+
'Nothing is saved. You can go through the questions again later.',
|
|
1176
|
+
],
|
|
1177
|
+
},
|
|
1178
|
+
},
|
|
1179
|
+
};
|
|
643
1180
|
async function approveOnboarding(server, service, input, context, initialize) {
|
|
644
1181
|
const preparation = !input.projectId;
|
|
645
1182
|
const scope = { repoRoot: input.repoRoot, preparation };
|
|
646
|
-
const
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
1183
|
+
const language = (await service.language(input.language))?.startsWith('tr') ? 'tr' : 'en';
|
|
1184
|
+
const modeId = 'onboard-mode-' + sha256(input.requestKey);
|
|
1185
|
+
const modeDefinitions = ['tr', 'en'].map((displayLanguage) => {
|
|
1186
|
+
const copy = onboardingCopy[displayLanguage].mode;
|
|
1187
|
+
return {
|
|
1188
|
+
questionnaireId: modeId,
|
|
1189
|
+
language: displayLanguage,
|
|
1190
|
+
message: copy.message,
|
|
1191
|
+
context: copy.context,
|
|
1192
|
+
example: copy.example,
|
|
1193
|
+
options: [
|
|
1194
|
+
{ id: 'greenfield', label: copy.greenfield[0], description: copy.greenfield[1] },
|
|
1195
|
+
{ id: 'adopt', label: copy.adopt[0], description: copy.adopt[1] },
|
|
1196
|
+
{ id: 'rework', label: copy.rework[0], description: copy.rework[1] },
|
|
1197
|
+
...(input.projectId
|
|
1198
|
+
? [{ id: 'refresh', label: copy.refresh[0], description: copy.refresh[1] }]
|
|
1199
|
+
: []),
|
|
1200
|
+
],
|
|
1201
|
+
};
|
|
1202
|
+
});
|
|
1203
|
+
const mode = await askQuestionnaire(server, service, { ...scope, ...modeDefinitions.find((entry) => entry.language === language) }, context, [
|
|
1204
|
+
...modeDefinitions,
|
|
1205
|
+
{
|
|
1206
|
+
questionnaireId: modeId,
|
|
1207
|
+
message: 'Based on project.inspect, choose how this project should enter Engineering Memory. Choosing an existing architecture never rewrites it.',
|
|
1208
|
+
options: [
|
|
1209
|
+
{ id: 'greenfield', label: 'Set up a new or barely-started project' },
|
|
1210
|
+
{ id: 'adopt', label: 'Learn and preserve the existing architecture' },
|
|
1211
|
+
{ id: 'rework', label: 'Learn first, then plan a phased rework' },
|
|
1212
|
+
...(input.projectId
|
|
1213
|
+
? [{ id: 'refresh', label: 'Refresh memory against the current project' }]
|
|
1214
|
+
: []),
|
|
1215
|
+
],
|
|
1216
|
+
},
|
|
1217
|
+
]);
|
|
659
1218
|
const selected = answerChoice(mode);
|
|
660
1219
|
if (!selected)
|
|
661
1220
|
return mode;
|
|
@@ -668,15 +1227,32 @@ async function approveOnboarding(server, service, input, context, initialize) {
|
|
|
668
1227
|
},
|
|
669
1228
|
});
|
|
670
1229
|
if (selected === 'rework') {
|
|
671
|
-
const
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
1230
|
+
const behaviorId = 'rework-behavior-' + sha256(input.requestKey);
|
|
1231
|
+
const behaviorDefinitions = ['tr', 'en'].map((displayLanguage) => {
|
|
1232
|
+
const copy = onboardingCopy[displayLanguage].behavior;
|
|
1233
|
+
return {
|
|
1234
|
+
questionnaireId: behaviorId,
|
|
1235
|
+
language: displayLanguage,
|
|
1236
|
+
message: copy.message,
|
|
1237
|
+
context: copy.context,
|
|
1238
|
+
example: copy.example,
|
|
1239
|
+
options: [
|
|
1240
|
+
{ id: 'preserve', label: copy.preserve[0], description: copy.preserve[1] },
|
|
1241
|
+
{ id: 'redesign', label: copy.redesign[0], description: copy.redesign[1] },
|
|
1242
|
+
],
|
|
1243
|
+
};
|
|
1244
|
+
});
|
|
1245
|
+
const behavior = await askQuestionnaire(server, service, { ...scope, ...behaviorDefinitions.find((entry) => entry.language === language) }, context, [
|
|
1246
|
+
...behaviorDefinitions,
|
|
1247
|
+
{
|
|
1248
|
+
questionnaireId: behaviorId,
|
|
1249
|
+
message: 'Should rework preserve current observable behavior or redesign the product? Redesign requires approved target screens, flows and contracts before implementation.',
|
|
1250
|
+
options: [
|
|
1251
|
+
{ id: 'preserve', label: 'Preserve current behavior' },
|
|
1252
|
+
{ id: 'redesign', label: 'Redesign the product explicitly' },
|
|
1253
|
+
],
|
|
1254
|
+
},
|
|
1255
|
+
]);
|
|
680
1256
|
const chosen = answerChoice(behavior);
|
|
681
1257
|
if (!chosen)
|
|
682
1258
|
return behavior;
|
|
@@ -694,7 +1270,7 @@ async function approveOnboarding(server, service, input, context, initialize) {
|
|
|
694
1270
|
const processedFields = new Set();
|
|
695
1271
|
let fields = profileFields(effectiveProfile);
|
|
696
1272
|
for (let index = 0; index < fields.length; index += 1) {
|
|
697
|
-
const
|
|
1273
|
+
const field = fields[index];
|
|
698
1274
|
if (processedFields.has(field))
|
|
699
1275
|
continue;
|
|
700
1276
|
const currentValue = effectiveProfile[field] ?? null;
|
|
@@ -705,31 +1281,66 @@ async function approveOnboarding(server, service, input, context, initialize) {
|
|
|
705
1281
|
field,
|
|
706
1282
|
currentValue,
|
|
707
1283
|
}));
|
|
708
|
-
const
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
1284
|
+
const fieldId = 'onboard-field-' + field + '-' + digest;
|
|
1285
|
+
const written = Array.isArray(currentValue)
|
|
1286
|
+
? currentValue.join(', ')
|
|
1287
|
+
: typeof currentValue === 'string'
|
|
1288
|
+
? currentValue
|
|
1289
|
+
: '';
|
|
1290
|
+
const textField = field === 'stack'
|
|
1291
|
+
? undefined
|
|
1292
|
+
: {
|
|
1293
|
+
maxLength: field === 'backendProjectIds'
|
|
1294
|
+
? 2000
|
|
1295
|
+
: ['architecture', 'colorPalette', 'typography'].includes(field)
|
|
1296
|
+
? 240
|
|
1297
|
+
: 160,
|
|
1298
|
+
requiredForChoice: 'edit_value',
|
|
1299
|
+
...(field === 'backendProjectIds' ? { valueType: 'uuid_list' } : {}),
|
|
1300
|
+
};
|
|
1301
|
+
const fieldDefinitions = ['tr', 'en'].map((displayLanguage) => {
|
|
1302
|
+
const copy = onboardingCopy[displayLanguage].field;
|
|
1303
|
+
return {
|
|
1304
|
+
questionnaireId: fieldId,
|
|
1305
|
+
language: displayLanguage,
|
|
1306
|
+
message: [
|
|
1307
|
+
profileFieldNames[displayLanguage][field],
|
|
1308
|
+
`${copy.value}: ${written || copy.empty}`,
|
|
1309
|
+
].join('\n'),
|
|
1310
|
+
context: copy.context,
|
|
1311
|
+
example: copy.example,
|
|
1312
|
+
options: profileFieldOptions(field, currentValue, copy),
|
|
1313
|
+
...(textField
|
|
1314
|
+
? {
|
|
1315
|
+
textField: {
|
|
1316
|
+
title: field === 'backendProjectIds' ? copy.idsTitle : copy.editTitle,
|
|
1317
|
+
...textField,
|
|
1318
|
+
},
|
|
1319
|
+
}
|
|
1320
|
+
: {}),
|
|
1321
|
+
};
|
|
1322
|
+
});
|
|
1323
|
+
const response = await askQuestionnaire(server, service, { ...scope, ...fieldDefinitions.find((entry) => entry.language === language) }, context, [
|
|
1324
|
+
...fieldDefinitions,
|
|
1325
|
+
{
|
|
1326
|
+
questionnaireId: fieldId,
|
|
1327
|
+
message: 'Review this onboarding choice before it is persisted.\n' +
|
|
1328
|
+
'Field: ' +
|
|
1329
|
+
profileFieldNames.en[field] +
|
|
1330
|
+
'\nCurrent value:\n' +
|
|
1331
|
+
JSON.stringify(currentValue, null, 2) +
|
|
1332
|
+
'\nKeep it, edit it in the bounded field below, or defer this field explicitly. A deferred field remains pending and is never silently saved as the current value.',
|
|
1333
|
+
options: legacyProfileFieldOptions(field, currentValue),
|
|
1334
|
+
...(textField
|
|
1335
|
+
? {
|
|
1336
|
+
textField: {
|
|
1337
|
+
title: field === 'backendProjectIds' ? 'Project ID JSON array' : 'Edited value',
|
|
1338
|
+
...textField,
|
|
1339
|
+
},
|
|
1340
|
+
}
|
|
1341
|
+
: {}),
|
|
1342
|
+
},
|
|
1343
|
+
]);
|
|
733
1344
|
const answer = answerData(response);
|
|
734
1345
|
const choice = answer?.choice;
|
|
735
1346
|
if (!choice)
|
|
@@ -754,24 +1365,49 @@ async function approveOnboarding(server, service, input, context, initialize) {
|
|
|
754
1365
|
if (preparation && service.managesWorktrees()) {
|
|
755
1366
|
gitPreferences = {};
|
|
756
1367
|
for (const role of ['development', 'production', 'test']) {
|
|
757
|
-
const
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
:
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
1368
|
+
const branchId = 'onboard-git-' + role + '-' + sha256(input.requestKey);
|
|
1369
|
+
const branchDefinitions = ['tr', 'en'].map((displayLanguage) => {
|
|
1370
|
+
const copy = onboardingCopy[displayLanguage].git;
|
|
1371
|
+
return {
|
|
1372
|
+
questionnaireId: branchId,
|
|
1373
|
+
language: displayLanguage,
|
|
1374
|
+
message: copy[role].message,
|
|
1375
|
+
context: copy[role].context,
|
|
1376
|
+
example: copy[role].example,
|
|
1377
|
+
options: [
|
|
1378
|
+
{ id: 'set', label: copy.set[0], description: copy.set[1] },
|
|
1379
|
+
{
|
|
1380
|
+
id: role === 'development' ? 'defer' : 'none',
|
|
1381
|
+
label: copy[role].second[0],
|
|
1382
|
+
description: copy[role].second[1],
|
|
1383
|
+
},
|
|
1384
|
+
],
|
|
1385
|
+
textField: { title: copy.branchTitle, maxLength: 255, requiredForChoice: 'set' },
|
|
1386
|
+
allowFreeText: false,
|
|
1387
|
+
};
|
|
1388
|
+
});
|
|
1389
|
+
const response = await askQuestionnaire(server, service, { ...scope, ...branchDefinitions.find((entry) => entry.language === language) }, context, [
|
|
1390
|
+
...branchDefinitions,
|
|
1391
|
+
{
|
|
1392
|
+
questionnaireId: branchId,
|
|
1393
|
+
message: role === 'development'
|
|
1394
|
+
? 'Which development branch should new task branches start from? For a new repository, choose its planned initial branch name. This choice uses the origin remote after it is connected.'
|
|
1395
|
+
: 'Which ' +
|
|
1396
|
+
role +
|
|
1397
|
+
' branch does this project use? Choose explicitly none if there is no such branch yet; you will not be asked again.',
|
|
1398
|
+
options: [
|
|
1399
|
+
{ id: 'set', label: 'Use the branch named below' },
|
|
1400
|
+
...(role === 'development'
|
|
1401
|
+
? [{ id: 'defer', label: 'Do not start setup yet' }]
|
|
1402
|
+
: [{ id: 'none', label: 'No ' + role + ' branch yet' }]),
|
|
1403
|
+
],
|
|
1404
|
+
textField: { title: 'Branch name', maxLength: 255, requiredForChoice: 'set' },
|
|
1405
|
+
allowFreeText: false,
|
|
1406
|
+
},
|
|
1407
|
+
]);
|
|
774
1408
|
const answer = answerData(response);
|
|
1409
|
+
if (answer?.choice === 'defer')
|
|
1410
|
+
return deferredProfile(input.requestKey, 'git-' + role);
|
|
775
1411
|
if (answer?.choice !== 'none' && (answer?.choice !== 'set' || !answer.text))
|
|
776
1412
|
return response;
|
|
777
1413
|
gitPreferences[role] = {
|
|
@@ -790,21 +1426,42 @@ async function approveOnboarding(server, service, input, context, initialize) {
|
|
|
790
1426
|
};
|
|
791
1427
|
const digest = sha256(stableStringify(choice));
|
|
792
1428
|
const questionnaireId = 'onboard-profile-' + digest + (initialize ? '-initialize' : '-save');
|
|
793
|
-
const
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
1429
|
+
const saveDefinitions = ['tr', 'en'].map((displayLanguage) => {
|
|
1430
|
+
const copy = onboardingCopy[displayLanguage].save;
|
|
1431
|
+
return {
|
|
1432
|
+
questionnaireId,
|
|
1433
|
+
language: displayLanguage,
|
|
1434
|
+
message: [copy.message, `${copy.routeLabel}: ${copy.route[input.mode]}`].join('\n'),
|
|
1435
|
+
context: copy.context + (initialize ? copy.git : ''),
|
|
1436
|
+
example: copy.example,
|
|
1437
|
+
options: [
|
|
1438
|
+
{
|
|
1439
|
+
id: 'approve',
|
|
1440
|
+
label: copy.approve[0],
|
|
1441
|
+
description: initialize ? copy.approveGit : copy.approve[1],
|
|
1442
|
+
},
|
|
1443
|
+
{ id: 'defer', label: copy.defer[0], description: copy.defer[1] },
|
|
1444
|
+
],
|
|
1445
|
+
binding: { choiceDigest: digest },
|
|
1446
|
+
};
|
|
1447
|
+
});
|
|
1448
|
+
const approval = await askQuestionnaire(server, service, { ...scope, ...saveDefinitions.find((entry) => entry.language === language) }, context, [
|
|
1449
|
+
...saveDefinitions,
|
|
1450
|
+
{
|
|
1451
|
+
questionnaireId,
|
|
1452
|
+
message: 'Save the selected mode and the profile choices just reviewed? Mode: ' +
|
|
1453
|
+
input.mode +
|
|
1454
|
+
'. Exact choice digest: ' +
|
|
1455
|
+
digest +
|
|
1456
|
+
'. ' +
|
|
1457
|
+
(initialize ? 'Greenfield will prepare Git without replacing existing files. ' : '') +
|
|
1458
|
+
'Engineering-memory revisions and project links require their own review/authority.',
|
|
1459
|
+
options: [
|
|
1460
|
+
{ id: 'approve', label: 'Save these exact onboarding choices' },
|
|
1461
|
+
{ id: 'defer', label: 'Keep the choices pending' },
|
|
1462
|
+
],
|
|
1463
|
+
},
|
|
1464
|
+
]);
|
|
808
1465
|
if (answerChoice(approval) === 'defer')
|
|
809
1466
|
return deferredProfile(input.requestKey, 'final-profile');
|
|
810
1467
|
if (answerChoice(approval) !== 'approve')
|
|
@@ -850,7 +1507,29 @@ function parseEditedProfileValue(field, value) {
|
|
|
850
1507
|
return z.array(uuid).max(10).parse(parsed);
|
|
851
1508
|
}
|
|
852
1509
|
const stackValues = ['Flutter', 'React', 'Nest', 'Spring'];
|
|
853
|
-
function profileFieldOptions(field, currentValue) {
|
|
1510
|
+
function profileFieldOptions(field, currentValue, copy) {
|
|
1511
|
+
if (field === 'stack') {
|
|
1512
|
+
return [
|
|
1513
|
+
{ id: 'keep_current', label: copy.keep[0], description: copy.keep[1] },
|
|
1514
|
+
...stackValues
|
|
1515
|
+
.filter((value) => value !== currentValue)
|
|
1516
|
+
.map((value) => ({
|
|
1517
|
+
id: value,
|
|
1518
|
+
label: copy.change(value),
|
|
1519
|
+
description: copy.changeDetail(value),
|
|
1520
|
+
})),
|
|
1521
|
+
{ id: 'defer_current', label: copy.defer[0], description: copy.defer[1] },
|
|
1522
|
+
];
|
|
1523
|
+
}
|
|
1524
|
+
return [
|
|
1525
|
+
...(currentValue === null
|
|
1526
|
+
? []
|
|
1527
|
+
: [{ id: 'keep_current', label: copy.keep[0], description: copy.keep[1] }]),
|
|
1528
|
+
{ id: 'edit_value', label: copy.edit[0], description: copy.edit[1] },
|
|
1529
|
+
{ id: 'defer_current', label: copy.defer[0], description: copy.defer[1] },
|
|
1530
|
+
];
|
|
1531
|
+
}
|
|
1532
|
+
function legacyProfileFieldOptions(field, currentValue) {
|
|
854
1533
|
if (field === 'stack') {
|
|
855
1534
|
return [
|
|
856
1535
|
{ id: 'keep_current', label: 'Keep this exact value' },
|
|
@@ -868,20 +1547,20 @@ function profileFieldOptions(field, currentValue) {
|
|
|
868
1547
|
}
|
|
869
1548
|
function profileFields(profileValue) {
|
|
870
1549
|
const fields = [
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
1550
|
+
'stack',
|
|
1551
|
+
'runtime',
|
|
1552
|
+
'architecture',
|
|
1553
|
+
'environments',
|
|
1554
|
+
'serviceReadiness',
|
|
1555
|
+
'apiContract',
|
|
1556
|
+
'authentication',
|
|
1557
|
+
'storage',
|
|
1558
|
+
'backendProjectIds',
|
|
880
1559
|
];
|
|
881
1560
|
if (['Nest', 'Spring'].includes(profileValue.stack))
|
|
882
|
-
fields.push(
|
|
1561
|
+
fields.push('migrations');
|
|
883
1562
|
if (['Flutter', 'React'].includes(profileValue.stack)) {
|
|
884
|
-
fields.push(
|
|
1563
|
+
fields.push('figma', 'colorPalette', 'typography', 'designSystem', 'navigation', 'platforms', 'localization');
|
|
885
1564
|
}
|
|
886
1565
|
return fields;
|
|
887
1566
|
}
|