engineering-memory 1.11.10 → 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 +3 -2
- package/package.json +1 -1
- package/runtime/build.json +1 -1
- package/runtime/dist/src/config.js +1 -0
- package/runtime/dist/src/git/git-inspector.js +32 -1
- package/runtime/dist/src/mcp/onboarding-tools.js +918 -132
- package/runtime/dist/src/mcp/questionnaire-tools.js +55 -16
- package/runtime/dist/src/mcp/tool-annotations.js +1 -0
- package/runtime/dist/src/mcp/tool-definitions.js +105 -17
- package/runtime/dist/src/mcp/worktree-tools.js +416 -86
- package/runtime/dist/src/project/repository.js +15 -3
- package/runtime/dist/src/runtime/api-client.js +1 -0
- package/runtime/dist/src/runtime/bridge-service.js +308 -46
- 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/runtime/dist/src/runtime/worktree-pool.js +41 -3
- package/skill/references/lifecycle.md +15 -0
- package/skill/references/questionnaires.md +17 -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,9 +251,201 @@ 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
|
});
|
|
257
|
+
const moveCopy = {
|
|
258
|
+
tr: {
|
|
259
|
+
ask: (name) => `"${name}" projesini bu depoya taşıyalım mı?`,
|
|
260
|
+
from: 'Projenin şu anki deposu',
|
|
261
|
+
to: 'Bu klasörün deposu',
|
|
262
|
+
unknown: 'adresi kayıtlı değil',
|
|
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.`,
|
|
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.',
|
|
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
|
+
],
|
|
275
|
+
},
|
|
276
|
+
en: {
|
|
277
|
+
ask: (name) => `Move the project "${name}" to this repository?`,
|
|
278
|
+
from: 'The project lives in',
|
|
279
|
+
to: 'This folder points at',
|
|
280
|
+
unknown: 'address not recorded',
|
|
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.`,
|
|
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.',
|
|
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
|
+
],
|
|
327
|
+
},
|
|
328
|
+
};
|
|
136
329
|
export function registerOnboardingTools(server, service) {
|
|
330
|
+
server.registerTool('project.move_repository', {
|
|
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.',
|
|
332
|
+
inputSchema: z.strictObject({
|
|
333
|
+
repoRoot,
|
|
334
|
+
projectId: uuid,
|
|
335
|
+
decisionAttempt: z.number().int().min(1).max(10000).optional(),
|
|
336
|
+
language: languageTag.optional(),
|
|
337
|
+
presentation: z.enum(['host_native']).optional(),
|
|
338
|
+
}),
|
|
339
|
+
}, async (input, context) => {
|
|
340
|
+
const language = (await service.language(input.language))?.startsWith('tr') ? 'tr' : 'en';
|
|
341
|
+
const preflight = await service.projectRepositoryMovePreflight(input);
|
|
342
|
+
if (!preflight.ok)
|
|
343
|
+
return output(preflight);
|
|
344
|
+
const flight = preflight.data;
|
|
345
|
+
if (flight.authentication)
|
|
346
|
+
return output(preflight);
|
|
347
|
+
if (flight.alreadyHere || !flight.history.current)
|
|
348
|
+
return output(await service.projectResolve({
|
|
349
|
+
repoRoot: input.repoRoot,
|
|
350
|
+
bind: true,
|
|
351
|
+
projectId: input.projectId,
|
|
352
|
+
}));
|
|
353
|
+
if (flight.history.role !== 'owner')
|
|
354
|
+
return output({
|
|
355
|
+
ok: false,
|
|
356
|
+
error: {
|
|
357
|
+
kind: 'bridge_error',
|
|
358
|
+
message: 'Only a project owner can move a project to another repository. List the members with project.member_list and ask an owner to run project.move_repository from a checkout of the new repository. Nothing was changed.',
|
|
359
|
+
retryable: false,
|
|
360
|
+
recovery: 'project.member_list',
|
|
361
|
+
},
|
|
362
|
+
});
|
|
363
|
+
const { name, records } = flight.history;
|
|
364
|
+
const from = flight.history.current.remoteIdentity;
|
|
365
|
+
const to = flight.checkout.remoteIdentity;
|
|
366
|
+
const questionnaireId = 'project-move-' +
|
|
367
|
+
sha256(stableStringify({
|
|
368
|
+
projectId: input.projectId,
|
|
369
|
+
from: flight.history.current.repoFingerprint,
|
|
370
|
+
to: flight.checkout.repoFingerprint,
|
|
371
|
+
version: flight.history.lockVersion,
|
|
372
|
+
...(input.decisionAttempt ? { decisionAttempt: input.decisionAttempt } : {}),
|
|
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
|
+
});
|
|
414
|
+
const form = await askQuestionnaire(server, service, {
|
|
415
|
+
...definitions.find((entry) => entry.language === language),
|
|
416
|
+
repoRoot: input.repoRoot,
|
|
417
|
+
presentation: input.presentation,
|
|
418
|
+
}, context, [...definitions, ...previousWording], {
|
|
419
|
+
tool: 'project.move_repository',
|
|
420
|
+
...(input.decisionAttempt ? { decisionAttempt: input.decisionAttempt } : {}),
|
|
421
|
+
retryArguments: {
|
|
422
|
+
projectId: input.projectId,
|
|
423
|
+
...(input.language ? { language: input.language } : {}),
|
|
424
|
+
},
|
|
425
|
+
});
|
|
426
|
+
const record = await service.questionnaireResume({
|
|
427
|
+
repoRoot: input.repoRoot,
|
|
428
|
+
questionnaireId,
|
|
429
|
+
});
|
|
430
|
+
if (record.status !== 'answered')
|
|
431
|
+
return form;
|
|
432
|
+
if (record.answer?.choice !== 'approve')
|
|
433
|
+
return output({
|
|
434
|
+
ok: true,
|
|
435
|
+
data: {
|
|
436
|
+
moved: false,
|
|
437
|
+
status: 'declined',
|
|
438
|
+
nextAction: 'The project was not moved and nothing changed. Tell the user in one line. If they later want the move, call project.move_repository with decisionAttempt ' +
|
|
439
|
+
((input.decisionAttempt ?? 0) + 1) +
|
|
440
|
+
'.',
|
|
441
|
+
},
|
|
442
|
+
});
|
|
443
|
+
return output(await service.projectMoveRepository({
|
|
444
|
+
repoRoot: input.repoRoot,
|
|
445
|
+
projectId: input.projectId,
|
|
446
|
+
expectedVersion: flight.history.lockVersion,
|
|
447
|
+
}));
|
|
448
|
+
});
|
|
137
449
|
server.registerTool('memory.source_targets', {
|
|
138
450
|
description: 'Read bounded Git ref candidates without assuming environment roles from their names. Confirm only the chosen committed sources with native memory.sync_start questionnaires; an explicit ref can select a source outside this preview.',
|
|
139
451
|
inputSchema: z.object({ repoRoot: z.string().optional() }),
|
|
@@ -163,11 +475,11 @@ export function registerOnboardingTools(server, service) {
|
|
|
163
475
|
inputSchema: z.object({ repoRoot }),
|
|
164
476
|
}, async (input) => output(await service.projectInspect(input)));
|
|
165
477
|
server.registerTool('project.onboard', {
|
|
166
|
-
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.',
|
|
167
479
|
inputSchema: onboardingInput,
|
|
168
480
|
}, async (input, context) => approveOnboarding(server, service, input, context, false));
|
|
169
481
|
server.registerTool('project.initialize', {
|
|
170
|
-
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.',
|
|
171
483
|
inputSchema: onboardingInput,
|
|
172
484
|
}, async (input, context) => approveOnboarding(server, service, input, context, true));
|
|
173
485
|
server.registerTool('project.onboarding_status', {
|
|
@@ -200,10 +512,11 @@ export function registerOnboardingTools(server, service) {
|
|
|
200
512
|
preparation: true,
|
|
201
513
|
})));
|
|
202
514
|
server.registerTool('project.rework_plan', {
|
|
203
|
-
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.',
|
|
204
516
|
inputSchema: z.strictObject({
|
|
205
517
|
...run,
|
|
206
518
|
repoRoot,
|
|
519
|
+
language: languageTag.optional(),
|
|
207
520
|
onboardingId: uuid,
|
|
208
521
|
requestKey: z.string().regex(/^[A-Za-z0-9_-]{1,80}$/),
|
|
209
522
|
targetRevisions: z
|
|
@@ -224,19 +537,49 @@ export function registerOnboardingTools(server, service) {
|
|
|
224
537
|
.max(100),
|
|
225
538
|
}),
|
|
226
539
|
}, async (input, context) => {
|
|
227
|
-
const { repoRoot: root, ...plan } = input;
|
|
540
|
+
const { repoRoot: root, language: told, ...plan } = input;
|
|
541
|
+
const language = (await service.language(told))?.startsWith('tr') ? 'tr' : 'en';
|
|
228
542
|
const digest = sha256(stableStringify(plan));
|
|
229
|
-
const
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
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
|
+
]);
|
|
240
583
|
if (answerChoice(answer) !== 'approve')
|
|
241
584
|
return answer;
|
|
242
585
|
return output(await service.onboardingRequest('rework', body(plan)));
|
|
@@ -257,16 +600,13 @@ export function registerOnboardingTools(server, service) {
|
|
|
257
600
|
}),
|
|
258
601
|
}, async (input) => output(await service.memorySource(input)));
|
|
259
602
|
server.registerTool('memory.sync_start', {
|
|
260
|
-
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.',
|
|
261
604
|
inputSchema: z.object({
|
|
262
605
|
...project,
|
|
263
606
|
repoRoot,
|
|
264
607
|
requestKey: z.string().regex(/^[a-zA-Z0-9_-]{1,120}$/),
|
|
265
608
|
mode: z.enum(['adopt', 'refresh', 'rework']),
|
|
266
|
-
language:
|
|
267
|
-
.enum(['tr', 'en'])
|
|
268
|
-
.optional()
|
|
269
|
-
.describe('Use the current conversation language: tr for Turkish, en for English. Omitted by legacy callers means English.'),
|
|
609
|
+
language: languageTag.optional(),
|
|
270
610
|
ref: z.string().optional(),
|
|
271
611
|
baseSnapshotId: uuid.optional(),
|
|
272
612
|
previousRunId: uuid.optional(),
|
|
@@ -291,7 +631,8 @@ export function registerOnboardingTools(server, service) {
|
|
|
291
631
|
recovery: 'memory.sync_status',
|
|
292
632
|
},
|
|
293
633
|
});
|
|
294
|
-
const { language
|
|
634
|
+
const { language: told, ...request } = input;
|
|
635
|
+
const language = (await service.language(told))?.startsWith('tr') ? 'tr' : 'en';
|
|
295
636
|
const pinned = { ...request, ref: commit };
|
|
296
637
|
const digest = sha256(stableStringify(pinned));
|
|
297
638
|
const reference = input.ref ?? 'HEAD';
|
|
@@ -299,6 +640,24 @@ export function registerOnboardingTools(server, service) {
|
|
|
299
640
|
? commit.slice(0, 12)
|
|
300
641
|
: `${reference.slice(0, 80)} · ${commit.slice(0, 12)}`;
|
|
301
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) => {
|
|
302
661
|
const copy = inspectionCopy[displayLanguage];
|
|
303
662
|
return {
|
|
304
663
|
questionnaireId: 'sync-start-' + digest,
|
|
@@ -320,6 +679,7 @@ export function registerOnboardingTools(server, service) {
|
|
|
320
679
|
const definition = definitions.find((entry) => entry.language === language);
|
|
321
680
|
const question = await askQuestionnaire(server, service, { ...definition, repoRoot: input.repoRoot }, context, [
|
|
322
681
|
...definitions,
|
|
682
|
+
...previousWording,
|
|
323
683
|
{
|
|
324
684
|
questionnaireId: 'sync-start-' + digest,
|
|
325
685
|
message: 'Start ' +
|
|
@@ -487,40 +847,69 @@ export function registerOnboardingTools(server, service) {
|
|
|
487
847
|
}),
|
|
488
848
|
}, async (input) => output(await service.onboardingRequest('read_revisions', body(input))));
|
|
489
849
|
server.registerTool('memory.review_batch', {
|
|
490
|
-
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.',
|
|
491
851
|
inputSchema: z.strictObject({
|
|
492
852
|
...project,
|
|
493
853
|
repoRoot,
|
|
494
|
-
entries: z.array(reference).min(1).max(
|
|
854
|
+
entries: z.array(reference).min(1).max(8),
|
|
495
855
|
reviewAttempt: z.number().int().min(0).max(10000).default(0),
|
|
856
|
+
language: languageTag.optional(),
|
|
496
857
|
}),
|
|
497
858
|
}, async (input, context) => {
|
|
498
859
|
const entries = [...input.entries].sort((a, b) => a.proposalId.localeCompare(b.proposalId));
|
|
499
860
|
const digest = sha256(stableStringify({ projectId: input.projectId, entries }));
|
|
500
|
-
const
|
|
861
|
+
const question = {
|
|
501
862
|
repoRoot: input.repoRoot,
|
|
502
|
-
questionnaireId: 'memory-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
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
|
+
}
|
|
514
901
|
const decision = answerChoice(result);
|
|
515
|
-
if (decision === 'defer')
|
|
902
|
+
if (decision === 'defer' || decision === 'one_by_one')
|
|
516
903
|
return output({
|
|
517
904
|
ok: true,
|
|
518
905
|
data: {
|
|
519
|
-
status: 'deferred',
|
|
906
|
+
status: decision === 'defer' ? 'deferred' : 'one_by_one',
|
|
520
907
|
digest,
|
|
521
908
|
reviewAttempt: input.reviewAttempt,
|
|
522
909
|
nextReviewAttempt: input.reviewAttempt + 1,
|
|
523
|
-
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.',
|
|
524
913
|
},
|
|
525
914
|
});
|
|
526
915
|
if (decision !== 'approved' && decision !== 'rejected')
|
|
@@ -533,22 +922,299 @@ export function registerOnboardingTools(server, service) {
|
|
|
533
922
|
})));
|
|
534
923
|
});
|
|
535
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
|
+
};
|
|
536
1180
|
async function approveOnboarding(server, service, input, context, initialize) {
|
|
537
1181
|
const preparation = !input.projectId;
|
|
538
1182
|
const scope = { repoRoot: input.repoRoot, preparation };
|
|
539
|
-
const
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
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
|
+
]);
|
|
552
1218
|
const selected = answerChoice(mode);
|
|
553
1219
|
if (!selected)
|
|
554
1220
|
return mode;
|
|
@@ -561,15 +1227,32 @@ async function approveOnboarding(server, service, input, context, initialize) {
|
|
|
561
1227
|
},
|
|
562
1228
|
});
|
|
563
1229
|
if (selected === 'rework') {
|
|
564
|
-
const
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
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
|
+
]);
|
|
573
1256
|
const chosen = answerChoice(behavior);
|
|
574
1257
|
if (!chosen)
|
|
575
1258
|
return behavior;
|
|
@@ -587,7 +1270,7 @@ async function approveOnboarding(server, service, input, context, initialize) {
|
|
|
587
1270
|
const processedFields = new Set();
|
|
588
1271
|
let fields = profileFields(effectiveProfile);
|
|
589
1272
|
for (let index = 0; index < fields.length; index += 1) {
|
|
590
|
-
const
|
|
1273
|
+
const field = fields[index];
|
|
591
1274
|
if (processedFields.has(field))
|
|
592
1275
|
continue;
|
|
593
1276
|
const currentValue = effectiveProfile[field] ?? null;
|
|
@@ -598,31 +1281,66 @@ async function approveOnboarding(server, service, input, context, initialize) {
|
|
|
598
1281
|
field,
|
|
599
1282
|
currentValue,
|
|
600
1283
|
}));
|
|
601
|
-
const
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
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
|
+
]);
|
|
626
1344
|
const answer = answerData(response);
|
|
627
1345
|
const choice = answer?.choice;
|
|
628
1346
|
if (!choice)
|
|
@@ -647,24 +1365,49 @@ async function approveOnboarding(server, service, input, context, initialize) {
|
|
|
647
1365
|
if (preparation && service.managesWorktrees()) {
|
|
648
1366
|
gitPreferences = {};
|
|
649
1367
|
for (const role of ['development', 'production', 'test']) {
|
|
650
|
-
const
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
:
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
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
|
+
]);
|
|
667
1408
|
const answer = answerData(response);
|
|
1409
|
+
if (answer?.choice === 'defer')
|
|
1410
|
+
return deferredProfile(input.requestKey, 'git-' + role);
|
|
668
1411
|
if (answer?.choice !== 'none' && (answer?.choice !== 'set' || !answer.text))
|
|
669
1412
|
return response;
|
|
670
1413
|
gitPreferences[role] = {
|
|
@@ -683,21 +1426,42 @@ async function approveOnboarding(server, service, input, context, initialize) {
|
|
|
683
1426
|
};
|
|
684
1427
|
const digest = sha256(stableStringify(choice));
|
|
685
1428
|
const questionnaireId = 'onboard-profile-' + digest + (initialize ? '-initialize' : '-save');
|
|
686
|
-
const
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
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
|
+
]);
|
|
701
1465
|
if (answerChoice(approval) === 'defer')
|
|
702
1466
|
return deferredProfile(input.requestKey, 'final-profile');
|
|
703
1467
|
if (answerChoice(approval) !== 'approve')
|
|
@@ -743,7 +1507,29 @@ function parseEditedProfileValue(field, value) {
|
|
|
743
1507
|
return z.array(uuid).max(10).parse(parsed);
|
|
744
1508
|
}
|
|
745
1509
|
const stackValues = ['Flutter', 'React', 'Nest', 'Spring'];
|
|
746
|
-
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) {
|
|
747
1533
|
if (field === 'stack') {
|
|
748
1534
|
return [
|
|
749
1535
|
{ id: 'keep_current', label: 'Keep this exact value' },
|
|
@@ -761,20 +1547,20 @@ function profileFieldOptions(field, currentValue) {
|
|
|
761
1547
|
}
|
|
762
1548
|
function profileFields(profileValue) {
|
|
763
1549
|
const fields = [
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
1550
|
+
'stack',
|
|
1551
|
+
'runtime',
|
|
1552
|
+
'architecture',
|
|
1553
|
+
'environments',
|
|
1554
|
+
'serviceReadiness',
|
|
1555
|
+
'apiContract',
|
|
1556
|
+
'authentication',
|
|
1557
|
+
'storage',
|
|
1558
|
+
'backendProjectIds',
|
|
773
1559
|
];
|
|
774
1560
|
if (['Nest', 'Spring'].includes(profileValue.stack))
|
|
775
|
-
fields.push(
|
|
1561
|
+
fields.push('migrations');
|
|
776
1562
|
if (['Flutter', 'React'].includes(profileValue.stack)) {
|
|
777
|
-
fields.push(
|
|
1563
|
+
fields.push('figma', 'colorPalette', 'typography', 'designSystem', 'navigation', 'platforms', 'localization');
|
|
778
1564
|
}
|
|
779
1565
|
return fields;
|
|
780
1566
|
}
|