engineering-memory 1.11.20 → 1.11.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/engineering-memory.mjs +14 -24
- package/install/localization.generated.mjs +18 -0
- package/package.json +1 -1
- package/runtime/build.json +1 -1
- package/runtime/dist/src/auth/browser-auth.js +15 -4
- package/runtime/dist/src/config.js +1 -0
- package/runtime/dist/src/git/git-inspector.js +10 -0
- package/runtime/dist/src/localization/catalogue.generated.js +786 -0
- package/runtime/dist/src/mcp/delivery-tools.js +15 -48
- package/runtime/dist/src/mcp/onboarding-tools.js +289 -618
- package/runtime/dist/src/mcp/questionnaire-tools.js +23 -26
- package/runtime/dist/src/mcp/status-meaning-tools.js +12 -86
- package/runtime/dist/src/mcp/status-remap-tools.js +252 -0
- package/runtime/dist/src/mcp/tool-annotations.js +1 -0
- package/runtime/dist/src/mcp/tool-definitions.js +33 -55
- package/runtime/dist/src/mcp/workflow-tools.js +17 -108
- package/runtime/dist/src/mcp/worktree-tools.js +168 -250
- package/runtime/dist/src/runtime/bridge-service.js +123 -94
- package/runtime/dist/src/runtime/create-bridge-service.js +4 -2
- package/runtime/dist/src/runtime/language-store.js +6 -0
- package/runtime/dist/src/runtime/release-notes.js +6 -6
- package/runtime/dist/src/runtime/release-report.js +30 -30
- package/runtime/dist/src/runtime/task-start.js +75 -73
- package/runtime/dist/src/runtime/texts.js +135 -0
- package/runtime/dist/src/runtime/worktree-pool.js +30 -5
- package/runtime/dist/src/runtime/worktree-preparation.js +3 -2
- package/runtime/dist/src/utilities/local-mutex.js +5 -2
- package/runtime/dist/src/utilities/process.js +7 -0
- package/skill/references/lifecycle.md +22 -4
- package/skill/references/project-onboarding.md +5 -2
- package/skill/references/questionnaires.md +3 -1
|
@@ -2,6 +2,7 @@ import * as z from 'zod/v4';
|
|
|
2
2
|
import { sha256, stableStringify } from '../utilities/hash.js';
|
|
3
3
|
import { restrictedValueKind } from '../runtime/privacy-detector.js';
|
|
4
4
|
import { languageTag } from '../runtime/questionnaire-store.js';
|
|
5
|
+
import { copies, format, texts } from '../runtime/texts.js';
|
|
5
6
|
import { answerChoice, answerData, askFailed, askQuestionnaire, resumeQuestionnaire, } from './questionnaire-tools.js';
|
|
6
7
|
const uuid = z.string().uuid();
|
|
7
8
|
const hash = z.string().regex(/^[a-f0-9]{64}$/);
|
|
@@ -18,58 +19,6 @@ const json = z.lazy(() => z.union([
|
|
|
18
19
|
z.record(z.string(), json),
|
|
19
20
|
]));
|
|
20
21
|
const object = z.record(z.string(), json);
|
|
21
|
-
const inspectionCopy = {
|
|
22
|
-
tr: {
|
|
23
|
-
modes: {
|
|
24
|
-
refresh: 'Projenin hafızasını güncel kodla karşılaştırıp incelemeyi başlatalım mı?',
|
|
25
|
-
adopt: 'Mevcut projeyi mimarisiyle birlikte öğrenmeye başlayalım mı?',
|
|
26
|
-
rework: 'Rework planından önce mevcut projeyi incelemeye başlayalım mı?',
|
|
27
|
-
},
|
|
28
|
-
source: 'Kaynak',
|
|
29
|
-
scope: 'Mimari, state yönetimi, servisler, network katmanı, veri modelleri, ekranlar ve akışlar incelenecek.',
|
|
30
|
-
localChanges: 'Uygulama kodu değiştirilmeyecek. Commit edilmemiş değişiklikler ortak hafızaya alınmayacak.',
|
|
31
|
-
review: 'Bu onay incelemeyi başlatır. Kalıcı hafıza değişiklikleri ayrıca onayına sunulacak.',
|
|
32
|
-
rework: {
|
|
33
|
-
preserve: 'Rework hedefi: mevcut davranışı korumak.',
|
|
34
|
-
redesign: 'Rework hedefi: ürünü yeniden tasarlamak. Uygulama öncesinde hedef ayrıca onaylanacak.',
|
|
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.',
|
|
42
|
-
start: 'İncelemeyi başlat',
|
|
43
|
-
startDetail: 'İnceleme şimdi başlar. Kod değişmez; çıkan kayıtlar sonunda tek tek onayına gelir.',
|
|
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.',
|
|
46
|
-
},
|
|
47
|
-
en: {
|
|
48
|
-
modes: {
|
|
49
|
-
refresh: 'Start comparing project memory with the current code?',
|
|
50
|
-
adopt: 'Start learning the existing project and its architecture?',
|
|
51
|
-
rework: 'Start inspecting the existing project before planning its rework?',
|
|
52
|
-
},
|
|
53
|
-
source: 'Source',
|
|
54
|
-
scope: 'Inspect architecture, state, services, network, data models, screens and flows.',
|
|
55
|
-
localChanges: 'Application code will stay unchanged. Uncommitted changes will not enter shared memory.',
|
|
56
|
-
review: 'This approval starts the inspection. Permanent memory changes will be submitted for separate approval.',
|
|
57
|
-
rework: {
|
|
58
|
-
preserve: 'Rework goal: preserve existing behavior.',
|
|
59
|
-
redesign: 'Rework goal: redesign the product. The target needs separate approval before implementation.',
|
|
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.',
|
|
67
|
-
start: 'Start inspection',
|
|
68
|
-
startDetail: 'The inspection starts now. The code stays unchanged and every record it writes comes back for your approval.',
|
|
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.',
|
|
71
|
-
},
|
|
72
|
-
};
|
|
73
22
|
const reference = z.object({
|
|
74
23
|
proposalId: uuid,
|
|
75
24
|
contentHash: hash,
|
|
@@ -94,82 +43,47 @@ const kind = z.enum([
|
|
|
94
43
|
'quality_gate',
|
|
95
44
|
'architecture_template',
|
|
96
45
|
]);
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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'];
|
|
46
|
+
const text = z.string().min(1);
|
|
47
|
+
const option = z.strictObject({ label: text, description: text });
|
|
48
|
+
const inspectionWording = z.strictObject({
|
|
49
|
+
modes: z.strictObject({ refresh: text, adopt: text, rework: text }),
|
|
50
|
+
source: text,
|
|
51
|
+
scope: text,
|
|
52
|
+
localChanges: text,
|
|
53
|
+
review: text,
|
|
54
|
+
rework: z.strictObject({ preserve: text, redesign: text }),
|
|
55
|
+
why: z.strictObject({ refresh: text, adopt: text, rework: text }),
|
|
56
|
+
example: text,
|
|
57
|
+
start: text,
|
|
58
|
+
startDetail: text,
|
|
59
|
+
defer: text,
|
|
60
|
+
deferDetail: text,
|
|
61
|
+
});
|
|
62
|
+
const reviewWording = z.strictObject({
|
|
63
|
+
ready: text,
|
|
64
|
+
context: text,
|
|
65
|
+
unversioned: text,
|
|
66
|
+
example: text,
|
|
67
|
+
untitled: text,
|
|
68
|
+
scope: z.strictObject({ project: text, organization: text, product: text }),
|
|
69
|
+
newRecord: text,
|
|
70
|
+
revision: text,
|
|
71
|
+
rebase: text,
|
|
72
|
+
why: text,
|
|
73
|
+
approved: option,
|
|
74
|
+
rejected: option,
|
|
75
|
+
oneByOne: option,
|
|
76
|
+
defer: option,
|
|
77
|
+
kinds: z.record(kind, text),
|
|
78
|
+
});
|
|
79
|
+
function batchQuestion(copy, language, proposals) {
|
|
166
80
|
const clean = (text) => (restrictedValueKind(text, 'message', true) ? undefined : text);
|
|
167
81
|
const named = proposals.map((proposal) => {
|
|
168
|
-
const kind =
|
|
82
|
+
const kind = copy.kinds[proposal.kind] ?? proposal.kind.replaceAll('_', ' ');
|
|
169
83
|
return {
|
|
170
84
|
proposal,
|
|
171
85
|
kind,
|
|
172
|
-
title: clean(proposal.title.slice(0, 70)) ??
|
|
86
|
+
title: clean(proposal.title.slice(0, 70)) ?? format(copy.untitled, language, { kind }),
|
|
173
87
|
};
|
|
174
88
|
});
|
|
175
89
|
const unversioned = named
|
|
@@ -178,21 +92,22 @@ function batchQuestion(turkish, proposals) {
|
|
|
178
92
|
.join(', ');
|
|
179
93
|
return {
|
|
180
94
|
message: [
|
|
181
|
-
|
|
95
|
+
format(copy.ready, language, { count: proposals.length }),
|
|
182
96
|
...named.map(({ proposal, kind, title }, index) => {
|
|
183
|
-
const
|
|
184
|
-
|
|
185
|
-
: copy.revision(proposal.currentRevisionNumber)}${proposal.rebaseRequired ? `, ${copy.rebase}` : ''})`;
|
|
97
|
+
const from = proposal.currentRevisionNumber;
|
|
98
|
+
const line = `${index + 1}. ${title} (${kind}, ${copy.scope[proposal.scope]}, ${from === 0 ? copy.newRecord : format(copy.revision, language, { from, to: from + 1 })}${proposal.rebaseRequired ? `, ${copy.rebase}` : ''})`;
|
|
186
99
|
return clean(`${line} ${copy.why}: ${proposal.reasonPreview.slice(0, 60)}`) ?? line;
|
|
187
100
|
}),
|
|
188
101
|
].join('\n'),
|
|
189
|
-
context:
|
|
102
|
+
context: unversioned
|
|
103
|
+
? `${copy.context} ${format(copy.unversioned, language, { titles: unversioned })}`
|
|
104
|
+
: copy.context,
|
|
190
105
|
example: copy.example,
|
|
191
106
|
options: [
|
|
192
|
-
{ id: 'approved', label: copy.approved
|
|
193
|
-
{ id: 'rejected', label: copy.rejected
|
|
194
|
-
{ id: 'one_by_one', label: copy.oneByOne
|
|
195
|
-
{ id: 'defer', label: copy.defer
|
|
107
|
+
{ id: 'approved', label: copy.approved.label, description: copy.approved.description },
|
|
108
|
+
{ id: 'rejected', label: copy.rejected.label, description: copy.rejected.description },
|
|
109
|
+
{ id: 'one_by_one', label: copy.oneByOne.label, description: copy.oneByOne.description },
|
|
110
|
+
{ id: 'defer', label: copy.defer.label, description: copy.defer.description },
|
|
196
111
|
],
|
|
197
112
|
};
|
|
198
113
|
}
|
|
@@ -254,78 +169,27 @@ const onboardingInput = z.strictObject({
|
|
|
254
169
|
language: languageTag.optional(),
|
|
255
170
|
reworkBehavior: z.enum(['preserve', 'redesign']).optional(),
|
|
256
171
|
});
|
|
257
|
-
const
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
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
|
-
};
|
|
172
|
+
const moveWording = z.strictObject({
|
|
173
|
+
ask: text,
|
|
174
|
+
from: text,
|
|
175
|
+
to: text,
|
|
176
|
+
unknown: text,
|
|
177
|
+
keeps: text,
|
|
178
|
+
after: text,
|
|
179
|
+
why: text,
|
|
180
|
+
example: text,
|
|
181
|
+
approve: option,
|
|
182
|
+
decline: option,
|
|
183
|
+
});
|
|
184
|
+
const reworkWording = z.strictObject({
|
|
185
|
+
phases: text,
|
|
186
|
+
branch: text,
|
|
187
|
+
more: text,
|
|
188
|
+
context: text,
|
|
189
|
+
example: text,
|
|
190
|
+
approve: option,
|
|
191
|
+
defer: option,
|
|
192
|
+
});
|
|
329
193
|
export function registerOnboardingTools(server, service) {
|
|
330
194
|
server.registerTool('project.move_repository', {
|
|
331
195
|
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.',
|
|
@@ -337,7 +201,7 @@ export function registerOnboardingTools(server, service) {
|
|
|
337
201
|
presentation: z.enum(['host_native']).optional(),
|
|
338
202
|
}),
|
|
339
203
|
}, async (input, context) => {
|
|
340
|
-
const
|
|
204
|
+
const wording = copies(moveWording, 'move', await service.language(input.language));
|
|
341
205
|
const preflight = await service.projectRepositoryMovePreflight(input);
|
|
342
206
|
if (!preflight.ok)
|
|
343
207
|
return output(preflight);
|
|
@@ -371,46 +235,41 @@ export function registerOnboardingTools(server, service) {
|
|
|
371
235
|
version: flight.history.lockVersion,
|
|
372
236
|
...(input.decisionAttempt ? { decisionAttempt: input.decisionAttempt } : {}),
|
|
373
237
|
}));
|
|
374
|
-
const
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
{ id: 'approve', label: copy.approve[0] },
|
|
410
|
-
{ id: 'decline', label: copy.decline[0] },
|
|
411
|
-
],
|
|
412
|
-
};
|
|
413
|
-
});
|
|
238
|
+
const counts = {
|
|
239
|
+
tasks: records.tasks,
|
|
240
|
+
records: records.memoryRecords,
|
|
241
|
+
items: records.workItems,
|
|
242
|
+
};
|
|
243
|
+
const definitions = wording.map(({ language, copy }) => ({
|
|
244
|
+
questionnaireId,
|
|
245
|
+
language,
|
|
246
|
+
message: [
|
|
247
|
+
format(copy.ask, language, { name }),
|
|
248
|
+
`${copy.from}: ${from ?? copy.unknown}`,
|
|
249
|
+
`${copy.to}: ${to ?? copy.unknown}`,
|
|
250
|
+
].join('\n'),
|
|
251
|
+
context: [copy.why, format(copy.keeps, language, counts), copy.after].join(' '),
|
|
252
|
+
example: copy.example,
|
|
253
|
+
options: [
|
|
254
|
+
{ id: 'approve', label: copy.approve.label, description: copy.approve.description },
|
|
255
|
+
{ id: 'decline', label: copy.decline.label, description: copy.decline.description },
|
|
256
|
+
],
|
|
257
|
+
}));
|
|
258
|
+
const previousWording = wording.map(({ language, copy }) => ({
|
|
259
|
+
questionnaireId,
|
|
260
|
+
language,
|
|
261
|
+
message: [
|
|
262
|
+
format(copy.ask, language, { name }),
|
|
263
|
+
`${copy.from}: ${from ?? copy.unknown}`,
|
|
264
|
+
`${copy.to}: ${to ?? copy.unknown}`,
|
|
265
|
+
format(copy.keeps, language, counts),
|
|
266
|
+
copy.after,
|
|
267
|
+
].join('\n'),
|
|
268
|
+
options: [
|
|
269
|
+
{ id: 'approve', label: copy.approve.label },
|
|
270
|
+
{ id: 'decline', label: copy.decline.label },
|
|
271
|
+
],
|
|
272
|
+
}));
|
|
414
273
|
const binding = { decision: questionnaireId };
|
|
415
274
|
const bound = definitions.map((entry) => ({ ...entry, binding }));
|
|
416
275
|
const asked = {
|
|
@@ -422,7 +281,7 @@ export function registerOnboardingTools(server, service) {
|
|
|
422
281
|
const form = stored?.binding && stableStringify(stored.binding) === stableStringify(binding)
|
|
423
282
|
? await resumeQuestionnaire(server, service, asked, context)
|
|
424
283
|
: await askQuestionnaire(server, service, {
|
|
425
|
-
...bound
|
|
284
|
+
...bound[0],
|
|
426
285
|
repoRoot: input.repoRoot,
|
|
427
286
|
presentation: input.presentation,
|
|
428
287
|
}, context, [...bound, ...definitions, ...previousWording], {
|
|
@@ -550,36 +409,33 @@ export function registerOnboardingTools(server, service) {
|
|
|
550
409
|
}),
|
|
551
410
|
}, async (input, context) => {
|
|
552
411
|
const { repoRoot: root, language: told, ...plan } = input;
|
|
553
|
-
const language = (await service.language(told))?.startsWith('tr') ? 'tr' : 'en';
|
|
554
412
|
const digest = sha256(stableStringify(plan));
|
|
555
413
|
const questionnaireId = 'rework-' + digest;
|
|
556
414
|
const shown = plan.phases.slice(0, 8);
|
|
557
|
-
const
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
});
|
|
582
|
-
const answer = await askQuestionnaire(server, service, { ...definitions.find((entry) => entry.language === language), repoRoot: root }, context, [
|
|
415
|
+
const wording = copies(reworkWording, 'reworkPlan', await service.language(told));
|
|
416
|
+
const definitions = wording.map(({ language, copy }) => ({
|
|
417
|
+
questionnaireId,
|
|
418
|
+
language,
|
|
419
|
+
message: [
|
|
420
|
+
format(copy.phases, language, { count: plan.phases.length }),
|
|
421
|
+
...shown.map((phase, index) => {
|
|
422
|
+
const branch = `${copy.branch}: ${phase.branch.slice(0, 60)}`;
|
|
423
|
+
const line = `${index + 1}. ${phase.title.slice(0, 70)} (${branch})`;
|
|
424
|
+
return restrictedValueKind(line, 'message', true) ? `${index + 1}. ${branch}` : line;
|
|
425
|
+
}),
|
|
426
|
+
...(plan.phases.length > shown.length
|
|
427
|
+
? [format(copy.more, language, { count: plan.phases.length - shown.length })]
|
|
428
|
+
: []),
|
|
429
|
+
].join('\n'),
|
|
430
|
+
context: copy.context,
|
|
431
|
+
example: copy.example,
|
|
432
|
+
options: [
|
|
433
|
+
{ id: 'approve', label: copy.approve.label, description: copy.approve.description },
|
|
434
|
+
{ id: 'defer', label: copy.defer.label, description: copy.defer.description },
|
|
435
|
+
],
|
|
436
|
+
binding: { planDigest: digest },
|
|
437
|
+
}));
|
|
438
|
+
const answer = await askQuestionnaire(server, service, { ...definitions[0], repoRoot: root }, context, [
|
|
583
439
|
...definitions,
|
|
584
440
|
{
|
|
585
441
|
questionnaireId,
|
|
@@ -644,52 +500,45 @@ export function registerOnboardingTools(server, service) {
|
|
|
644
500
|
},
|
|
645
501
|
});
|
|
646
502
|
const { language: told, ...request } = input;
|
|
647
|
-
const
|
|
503
|
+
const wording = copies(inspectionWording, 'inspection', await service.language(told));
|
|
648
504
|
const pinned = { ...request, ref: commit };
|
|
649
505
|
const digest = sha256(stableStringify(pinned));
|
|
650
506
|
const reference = input.ref ?? 'HEAD';
|
|
651
507
|
const sourceLabel = /^[0-9a-f]{40,64}$/i.test(reference)
|
|
652
508
|
? commit.slice(0, 12)
|
|
653
509
|
: `${reference.slice(0, 80)} · ${commit.slice(0, 12)}`;
|
|
654
|
-
const definitions =
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
{ id: 'approve', label: copy.start },
|
|
687
|
-
{ id: 'defer', label: copy.defer },
|
|
688
|
-
],
|
|
689
|
-
};
|
|
690
|
-
});
|
|
691
|
-
const definition = definitions.find((entry) => entry.language === language);
|
|
692
|
-
const question = await askQuestionnaire(server, service, { ...definition, repoRoot: input.repoRoot }, context, [
|
|
510
|
+
const definitions = wording.map(({ language, copy }) => ({
|
|
511
|
+
questionnaireId: 'sync-start-' + digest,
|
|
512
|
+
language,
|
|
513
|
+
message: [
|
|
514
|
+
copy.modes[input.mode],
|
|
515
|
+
`${copy.source}: ${sourceLabel}`,
|
|
516
|
+
...(input.reworkBehavior ? [copy.rework[input.reworkBehavior]] : []),
|
|
517
|
+
].join('\n\n'),
|
|
518
|
+
context: [copy.why[input.mode], copy.scope, copy.localChanges, copy.review].join(' '),
|
|
519
|
+
example: copy.example,
|
|
520
|
+
options: [
|
|
521
|
+
{ id: 'approve', label: copy.start, description: copy.startDetail },
|
|
522
|
+
{ id: 'defer', label: copy.defer, description: copy.deferDetail },
|
|
523
|
+
],
|
|
524
|
+
}));
|
|
525
|
+
const previousWording = wording.map(({ language, copy }) => ({
|
|
526
|
+
questionnaireId: 'sync-start-' + digest,
|
|
527
|
+
language,
|
|
528
|
+
message: [
|
|
529
|
+
copy.modes[input.mode],
|
|
530
|
+
`${copy.source}: ${sourceLabel}`,
|
|
531
|
+
copy.scope,
|
|
532
|
+
copy.localChanges,
|
|
533
|
+
copy.review,
|
|
534
|
+
...(input.reworkBehavior ? [copy.rework[input.reworkBehavior]] : []),
|
|
535
|
+
].join('\n\n'),
|
|
536
|
+
options: [
|
|
537
|
+
{ id: 'approve', label: copy.start },
|
|
538
|
+
{ id: 'defer', label: copy.defer },
|
|
539
|
+
],
|
|
540
|
+
}));
|
|
541
|
+
const question = await askQuestionnaire(server, service, { ...definitions[0], repoRoot: input.repoRoot }, context, [
|
|
693
542
|
...definitions,
|
|
694
543
|
...previousWording,
|
|
695
544
|
{
|
|
@@ -875,7 +724,7 @@ export function registerOnboardingTools(server, service) {
|
|
|
875
724
|
questionnaireId: 'memory-review-' + digest + '-' + input.reviewAttempt,
|
|
876
725
|
};
|
|
877
726
|
const binding = body({ projectId: input.projectId, entries });
|
|
878
|
-
const language = (await service.language(input.language))
|
|
727
|
+
const { language, copy } = copies(reviewWording, 'reviewBatch', await service.language(input.language))[0];
|
|
879
728
|
const stored = await service.questionnaireResume(question).catch(() => undefined);
|
|
880
729
|
let result;
|
|
881
730
|
if (stored && stableStringify(stored.binding) === stableStringify(binding))
|
|
@@ -908,7 +757,7 @@ export function registerOnboardingTools(server, service) {
|
|
|
908
757
|
recovery: 'memory.list_proposals',
|
|
909
758
|
},
|
|
910
759
|
});
|
|
911
|
-
result = await askQuestionnaire(server, service, { ...question, language, ...batchQuestion(language
|
|
760
|
+
result = await askQuestionnaire(server, service, { ...question, language, ...batchQuestion(copy, language, proposals), binding }, context);
|
|
912
761
|
}
|
|
913
762
|
const decision = answerChoice(result);
|
|
914
763
|
if (decision === 'defer' || decision === 'one_by_one')
|
|
@@ -934,285 +783,110 @@ export function registerOnboardingTools(server, service) {
|
|
|
934
783
|
})));
|
|
935
784
|
});
|
|
936
785
|
}
|
|
937
|
-
const
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
},
|
|
1013
|
-
field: {
|
|
1014
|
-
value: 'Önerilen değer',
|
|
1015
|
-
empty: 'henüz belirlenmedi',
|
|
1016
|
-
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.',
|
|
1017
|
-
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.',
|
|
1018
|
-
keep: ['Bu değeri olduğu gibi kaydet', 'Yukarıdaki değer aynen kaydedilir.'],
|
|
1019
|
-
change: (value) => `${value} olarak değiştir`,
|
|
1020
|
-
changeDetail: (value) => `Proje ${value} olarak kaydedilir ve kalan sorular buna göre gelir.`,
|
|
1021
|
-
edit: [
|
|
1022
|
-
'Aşağıdaki alana farklı bir değer yaz',
|
|
1023
|
-
'Aşağıya yazdığın metin, yukarıdaki değerin yerine kaydedilir.',
|
|
1024
|
-
],
|
|
1025
|
-
defer: [
|
|
1026
|
-
'Bu değeri şimdilik boş bırak',
|
|
1027
|
-
'Hiçbir şey kaydedilmez. Kurulum burada durur; karar verdiğinde bu soruya dönersin.',
|
|
1028
|
-
],
|
|
1029
|
-
editTitle: 'Yeni değer',
|
|
1030
|
-
idsTitle: 'Proje ID listesi (JSON dizisi)',
|
|
1031
|
-
},
|
|
1032
|
-
git: {
|
|
1033
|
-
development: {
|
|
1034
|
-
message: 'Yeni görev dalları hangi daldan başlasın?',
|
|
1035
|
-
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.',
|
|
1036
|
-
example: 'Buraya main yazarsan sonraki görev main üzerinden dallanır ve pull request main dalına açılır.',
|
|
1037
|
-
second: [
|
|
1038
|
-
'Şimdilik kuruluma başlama',
|
|
1039
|
-
'Hiçbir şey kaydedilmez, kurulum burada durur; istediğinde devam edebilirsin.',
|
|
1040
|
-
],
|
|
1041
|
-
},
|
|
1042
|
-
production: {
|
|
1043
|
-
message: 'Bu projenin canlı sürümü hangi daldan çıkıyor?',
|
|
1044
|
-
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.',
|
|
1045
|
-
example: 'Canlı dal main ise, biten bir görevin nereye gideceği sana tekrar sorulmaz.',
|
|
1046
|
-
second: [
|
|
1047
|
-
'Henüz canlı dal yok',
|
|
1048
|
-
'Yok olarak kaydedilir ve bir daha sorulmaz. İleride belirleyebilirsin.',
|
|
1049
|
-
],
|
|
1050
|
-
},
|
|
1051
|
-
test: {
|
|
1052
|
-
message: 'Bu proje hangi dalda test ediliyor?',
|
|
1053
|
-
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.',
|
|
1054
|
-
example: 'Test dalı develop ise, testlerin hangi dalda koştuğu sana tekrar sorulmaz.',
|
|
1055
|
-
second: [
|
|
1056
|
-
'Henüz test dalı yok',
|
|
1057
|
-
'Yok olarak kaydedilir ve bir daha sorulmaz. İleride belirleyebilirsin.',
|
|
1058
|
-
],
|
|
1059
|
-
},
|
|
1060
|
-
set: ['Aşağıya yazdığım dalı kullan', 'Yazdığın dal bu proje için kaydedilir.'],
|
|
1061
|
-
branchTitle: 'Dal adı',
|
|
1062
|
-
},
|
|
1063
|
-
save: {
|
|
1064
|
-
message: 'Bu kurulum seçimlerini kaydedelim mi?',
|
|
1065
|
-
routeLabel: 'Seçilen yol',
|
|
1066
|
-
route: {
|
|
1067
|
-
greenfield: 'yeni bir proje kur',
|
|
1068
|
-
adopt: 'mevcut projeyi öğren ve koru',
|
|
1069
|
-
rework: 'önce öğren, sonra aşama aşama yeniden yazmayı planla',
|
|
1070
|
-
refresh: 'hafızayı güncel projeyle tazele',
|
|
1071
|
-
},
|
|
1072
|
-
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.',
|
|
1073
|
-
git: ' Bu klasörde Git hazırlanır; mevcut dosyalar olduğu gibi kalır, hiçbiri silinmez veya değiştirilmez.',
|
|
1074
|
-
example: 'Kaydettikten sonra sonraki görev teknolojiyi ve mimariyi sana tekrar sormadan buradan okur.',
|
|
1075
|
-
approve: ['Bu seçimleri kaydet', 'Seçimler kaydedilir. Kodda hiçbir şey değişmez.'],
|
|
1076
|
-
approveGit: 'Seçimler kaydedilir ve bu klasörde Git hazırlanır.',
|
|
1077
|
-
defer: [
|
|
1078
|
-
'Seçimleri beklemede bırak',
|
|
1079
|
-
'Hiçbir şey kaydedilmez. Soruları sonra yeniden geçebilirsin.',
|
|
1080
|
-
],
|
|
1081
|
-
},
|
|
1082
|
-
},
|
|
1083
|
-
en: {
|
|
1084
|
-
mode: {
|
|
1085
|
-
message: 'How should this project enter Engineering Memory?',
|
|
1086
|
-
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.',
|
|
1087
|
-
example: 'Choosing to learn the existing project means the next step reads the code and writes down how it works, without changing a line.',
|
|
1088
|
-
greenfield: [
|
|
1089
|
-
'Set up a new or barely-started project',
|
|
1090
|
-
'The project starts from the profile you approve next; no existing code is read.',
|
|
1091
|
-
],
|
|
1092
|
-
adopt: [
|
|
1093
|
-
'Learn and preserve the existing architecture',
|
|
1094
|
-
'The code is read as it stands and written into memory; nothing is redesigned.',
|
|
1095
|
-
],
|
|
1096
|
-
rework: [
|
|
1097
|
-
'Learn first, then plan a phased rework',
|
|
1098
|
-
'The code is read first, and nothing changes until the rework plan is approved separately.',
|
|
1099
|
-
],
|
|
1100
|
-
refresh: [
|
|
1101
|
-
'Refresh memory against the current project',
|
|
1102
|
-
'The records already in memory are compared with the current code and the differences come back as proposals.',
|
|
1103
|
-
],
|
|
1104
|
-
},
|
|
1105
|
-
behavior: {
|
|
1106
|
-
message: 'Should the rework keep the product behaving as it does today, or redesign it?',
|
|
1107
|
-
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.',
|
|
1108
|
-
example: 'If the behavior is preserved, a screen that asks for a serial number today still asks for it after the rework.',
|
|
1109
|
-
preserve: [
|
|
1110
|
-
'Preserve current behavior',
|
|
1111
|
-
'The rework only changes how the code is built; what the user sees stays the same.',
|
|
1112
|
-
],
|
|
1113
|
-
redesign: [
|
|
1114
|
-
'Redesign the product explicitly',
|
|
1115
|
-
'Nothing is implemented until the target screens, flows and contracts are approved.',
|
|
1116
|
-
],
|
|
1117
|
-
},
|
|
1118
|
-
field: {
|
|
1119
|
-
value: 'Proposed value',
|
|
1120
|
-
empty: 'not set yet',
|
|
1121
|
-
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.',
|
|
1122
|
-
example: 'If the runtime stays Node 22, a later task builds and tests on Node 22 instead of whatever the machine happens to have.',
|
|
1123
|
-
keep: ['Keep this exact value', 'The value above is saved as it stands.'],
|
|
1124
|
-
change: (value) => `Change to ${value}`,
|
|
1125
|
-
changeDetail: (value) => `The project is saved as ${value} and the remaining questions follow it.`,
|
|
1126
|
-
edit: [
|
|
1127
|
-
'Edit this value in the field below',
|
|
1128
|
-
'What you write below is saved instead of the value above.',
|
|
1129
|
-
],
|
|
1130
|
-
defer: [
|
|
1131
|
-
'Defer this value explicitly',
|
|
1132
|
-
'Nothing is saved. Onboarding stops here and you come back to this question once you have decided.',
|
|
1133
|
-
],
|
|
1134
|
-
editTitle: 'Edited value',
|
|
1135
|
-
idsTitle: 'Project ID JSON array',
|
|
1136
|
-
},
|
|
1137
|
-
git: {
|
|
1138
|
-
development: {
|
|
1139
|
-
message: 'Which branch should new task branches start from?',
|
|
1140
|
-
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.',
|
|
1141
|
-
example: 'With main here, a later task branches off main and its pull request targets main.',
|
|
1142
|
-
second: [
|
|
1143
|
-
'Do not start setup yet',
|
|
1144
|
-
'Nothing is saved and setup stops here; you can continue whenever you want.',
|
|
1145
|
-
],
|
|
1146
|
-
},
|
|
1147
|
-
production: {
|
|
1148
|
-
message: 'Which branch is this project released from?',
|
|
1149
|
-
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.',
|
|
1150
|
-
example: 'With main as the release branch, where a finished task goes is never asked again.',
|
|
1151
|
-
second: [
|
|
1152
|
-
'There is no release branch yet',
|
|
1153
|
-
'It is recorded as absent and you are not asked again. You can set it later.',
|
|
1154
|
-
],
|
|
1155
|
-
},
|
|
1156
|
-
test: {
|
|
1157
|
-
message: 'Which branch is this project tested on?',
|
|
1158
|
-
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.',
|
|
1159
|
-
example: 'With develop as the test branch, where the tests run is never asked again.',
|
|
1160
|
-
second: [
|
|
1161
|
-
'There is no test branch yet',
|
|
1162
|
-
'It is recorded as absent and you are not asked again. You can set it later.',
|
|
1163
|
-
],
|
|
1164
|
-
},
|
|
1165
|
-
set: ['Use the branch named below', 'That branch is saved for this project.'],
|
|
1166
|
-
branchTitle: 'Branch name',
|
|
1167
|
-
},
|
|
1168
|
-
save: {
|
|
1169
|
-
message: 'Save the onboarding choices you just went through?',
|
|
1170
|
-
routeLabel: 'Chosen route',
|
|
1171
|
-
route: {
|
|
1172
|
-
greenfield: 'set up a new project',
|
|
1173
|
-
adopt: 'learn and preserve the existing project',
|
|
1174
|
-
rework: 'learn first, then plan a phased rework',
|
|
1175
|
-
refresh: 'refresh memory against the current project',
|
|
1176
|
-
},
|
|
1177
|
-
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.',
|
|
1178
|
-
git: ' Git is prepared in this folder: existing files stay as they are and none of them is deleted or replaced.',
|
|
1179
|
-
example: 'After saving, a later task reads the stack and the architecture from here instead of asking you again.',
|
|
1180
|
-
approve: [
|
|
1181
|
-
'Save these exact onboarding choices',
|
|
1182
|
-
'The choices are saved. Nothing in the code changes.',
|
|
1183
|
-
],
|
|
1184
|
-
approveGit: 'The choices are saved and Git is prepared in this folder.',
|
|
1185
|
-
defer: [
|
|
1186
|
-
'Keep the choices pending',
|
|
1187
|
-
'Nothing is saved. You can go through the questions again later.',
|
|
1188
|
-
],
|
|
1189
|
-
},
|
|
1190
|
-
},
|
|
1191
|
-
};
|
|
786
|
+
const profileField = z.enum([
|
|
787
|
+
'stack',
|
|
788
|
+
'runtime',
|
|
789
|
+
'architecture',
|
|
790
|
+
'environments',
|
|
791
|
+
'serviceReadiness',
|
|
792
|
+
'apiContract',
|
|
793
|
+
'authentication',
|
|
794
|
+
'storage',
|
|
795
|
+
'backendProjectIds',
|
|
796
|
+
'migrations',
|
|
797
|
+
'figma',
|
|
798
|
+
'colorPalette',
|
|
799
|
+
'typography',
|
|
800
|
+
'designSystem',
|
|
801
|
+
'navigation',
|
|
802
|
+
'platforms',
|
|
803
|
+
'localization',
|
|
804
|
+
]);
|
|
805
|
+
const branchQuestion = z.strictObject({
|
|
806
|
+
message: text,
|
|
807
|
+
context: text,
|
|
808
|
+
example: text,
|
|
809
|
+
second: option,
|
|
810
|
+
});
|
|
811
|
+
const onboardingWording = z.strictObject({
|
|
812
|
+
mode: z.strictObject({
|
|
813
|
+
message: text,
|
|
814
|
+
context: text,
|
|
815
|
+
example: text,
|
|
816
|
+
greenfield: option,
|
|
817
|
+
adopt: option,
|
|
818
|
+
rework: option,
|
|
819
|
+
refresh: option,
|
|
820
|
+
}),
|
|
821
|
+
behavior: z.strictObject({
|
|
822
|
+
message: text,
|
|
823
|
+
context: text,
|
|
824
|
+
example: text,
|
|
825
|
+
preserve: option,
|
|
826
|
+
redesign: option,
|
|
827
|
+
}),
|
|
828
|
+
field: z.strictObject({
|
|
829
|
+
value: text,
|
|
830
|
+
empty: text,
|
|
831
|
+
context: text,
|
|
832
|
+
example: text,
|
|
833
|
+
keep: option,
|
|
834
|
+
change: text,
|
|
835
|
+
changeDetail: text,
|
|
836
|
+
edit: option,
|
|
837
|
+
defer: option,
|
|
838
|
+
editTitle: text,
|
|
839
|
+
idsTitle: text,
|
|
840
|
+
}),
|
|
841
|
+
git: z.strictObject({
|
|
842
|
+
development: branchQuestion,
|
|
843
|
+
production: branchQuestion,
|
|
844
|
+
test: branchQuestion,
|
|
845
|
+
set: option,
|
|
846
|
+
branchTitle: text,
|
|
847
|
+
}),
|
|
848
|
+
save: z.strictObject({
|
|
849
|
+
message: text,
|
|
850
|
+
routeLabel: text,
|
|
851
|
+
route: z.record(onboardingMode, text),
|
|
852
|
+
context: text,
|
|
853
|
+
git: text,
|
|
854
|
+
example: text,
|
|
855
|
+
approve: option,
|
|
856
|
+
approveGit: text,
|
|
857
|
+
defer: option,
|
|
858
|
+
}),
|
|
859
|
+
fieldNames: z.record(profileField, text),
|
|
860
|
+
});
|
|
1192
861
|
async function approveOnboarding(server, service, input, context, initialize) {
|
|
1193
862
|
const preparation = !input.projectId;
|
|
1194
863
|
const scope = { repoRoot: input.repoRoot, preparation };
|
|
1195
|
-
const
|
|
864
|
+
const wording = copies(onboardingWording, 'onboarding', await service.language(input.language));
|
|
865
|
+
const english = onboardingWording.parse(texts('onboarding', 'en'));
|
|
1196
866
|
const modeId = 'onboard-mode-' + sha256(input.requestKey);
|
|
1197
|
-
const modeDefinitions =
|
|
1198
|
-
const copy =
|
|
867
|
+
const modeDefinitions = wording.map((entry) => {
|
|
868
|
+
const copy = entry.copy.mode;
|
|
1199
869
|
return {
|
|
1200
870
|
questionnaireId: modeId,
|
|
1201
|
-
language:
|
|
871
|
+
language: entry.language,
|
|
1202
872
|
message: copy.message,
|
|
1203
873
|
context: copy.context,
|
|
1204
874
|
example: copy.example,
|
|
1205
875
|
options: [
|
|
1206
|
-
{
|
|
1207
|
-
|
|
1208
|
-
|
|
876
|
+
{
|
|
877
|
+
id: 'greenfield',
|
|
878
|
+
label: copy.greenfield.label,
|
|
879
|
+
description: copy.greenfield.description,
|
|
880
|
+
},
|
|
881
|
+
{ id: 'adopt', label: copy.adopt.label, description: copy.adopt.description },
|
|
882
|
+
{ id: 'rework', label: copy.rework.label, description: copy.rework.description },
|
|
1209
883
|
...(input.projectId
|
|
1210
|
-
? [{ id: 'refresh', label: copy.refresh
|
|
884
|
+
? [{ id: 'refresh', label: copy.refresh.label, description: copy.refresh.description }]
|
|
1211
885
|
: []),
|
|
1212
886
|
],
|
|
1213
887
|
};
|
|
1214
888
|
});
|
|
1215
|
-
const mode = await askQuestionnaire(server, service, { ...scope, ...modeDefinitions
|
|
889
|
+
const mode = await askQuestionnaire(server, service, { ...scope, ...modeDefinitions[0] }, context, [
|
|
1216
890
|
...modeDefinitions,
|
|
1217
891
|
{
|
|
1218
892
|
questionnaireId: modeId,
|
|
@@ -1240,21 +914,21 @@ async function approveOnboarding(server, service, input, context, initialize) {
|
|
|
1240
914
|
});
|
|
1241
915
|
if (selected === 'rework') {
|
|
1242
916
|
const behaviorId = 'rework-behavior-' + sha256(input.requestKey);
|
|
1243
|
-
const behaviorDefinitions =
|
|
1244
|
-
const copy =
|
|
917
|
+
const behaviorDefinitions = wording.map((entry) => {
|
|
918
|
+
const copy = entry.copy.behavior;
|
|
1245
919
|
return {
|
|
1246
920
|
questionnaireId: behaviorId,
|
|
1247
|
-
language:
|
|
921
|
+
language: entry.language,
|
|
1248
922
|
message: copy.message,
|
|
1249
923
|
context: copy.context,
|
|
1250
924
|
example: copy.example,
|
|
1251
925
|
options: [
|
|
1252
|
-
{ id: 'preserve', label: copy.preserve
|
|
1253
|
-
{ id: 'redesign', label: copy.redesign
|
|
926
|
+
{ id: 'preserve', label: copy.preserve.label, description: copy.preserve.description },
|
|
927
|
+
{ id: 'redesign', label: copy.redesign.label, description: copy.redesign.description },
|
|
1254
928
|
],
|
|
1255
929
|
};
|
|
1256
930
|
});
|
|
1257
|
-
const behavior = await askQuestionnaire(server, service, { ...scope, ...behaviorDefinitions
|
|
931
|
+
const behavior = await askQuestionnaire(server, service, { ...scope, ...behaviorDefinitions[0] }, context, [
|
|
1258
932
|
...behaviorDefinitions,
|
|
1259
933
|
{
|
|
1260
934
|
questionnaireId: behaviorId,
|
|
@@ -1310,18 +984,15 @@ async function approveOnboarding(server, service, input, context, initialize) {
|
|
|
1310
984
|
requiredForChoice: 'edit_value',
|
|
1311
985
|
...(field === 'backendProjectIds' ? { valueType: 'uuid_list' } : {}),
|
|
1312
986
|
};
|
|
1313
|
-
const fieldDefinitions =
|
|
1314
|
-
const copy =
|
|
987
|
+
const fieldDefinitions = wording.map((entry) => {
|
|
988
|
+
const copy = entry.copy.field;
|
|
1315
989
|
return {
|
|
1316
990
|
questionnaireId: fieldId,
|
|
1317
|
-
language:
|
|
1318
|
-
message: [
|
|
1319
|
-
profileFieldNames[displayLanguage][field],
|
|
1320
|
-
`${copy.value}: ${written || copy.empty}`,
|
|
1321
|
-
].join('\n'),
|
|
991
|
+
language: entry.language,
|
|
992
|
+
message: [entry.copy.fieldNames[field], `${copy.value}: ${written || copy.empty}`].join('\n'),
|
|
1322
993
|
context: copy.context,
|
|
1323
994
|
example: copy.example,
|
|
1324
|
-
options: profileFieldOptions(field, currentValue, copy),
|
|
995
|
+
options: profileFieldOptions(field, currentValue, copy, entry.language),
|
|
1325
996
|
...(textField
|
|
1326
997
|
? {
|
|
1327
998
|
textField: {
|
|
@@ -1332,13 +1003,13 @@ async function approveOnboarding(server, service, input, context, initialize) {
|
|
|
1332
1003
|
: {}),
|
|
1333
1004
|
};
|
|
1334
1005
|
});
|
|
1335
|
-
const response = await askQuestionnaire(server, service, { ...scope, ...fieldDefinitions
|
|
1006
|
+
const response = await askQuestionnaire(server, service, { ...scope, ...fieldDefinitions[0] }, context, [
|
|
1336
1007
|
...fieldDefinitions,
|
|
1337
1008
|
{
|
|
1338
1009
|
questionnaireId: fieldId,
|
|
1339
1010
|
message: 'Review this onboarding choice before it is persisted.\n' +
|
|
1340
1011
|
'Field: ' +
|
|
1341
|
-
|
|
1012
|
+
english.fieldNames[field] +
|
|
1342
1013
|
'\nCurrent value:\n' +
|
|
1343
1014
|
JSON.stringify(currentValue, null, 2) +
|
|
1344
1015
|
'\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.',
|
|
@@ -1378,27 +1049,27 @@ async function approveOnboarding(server, service, input, context, initialize) {
|
|
|
1378
1049
|
gitPreferences = {};
|
|
1379
1050
|
for (const role of ['development', 'production', 'test']) {
|
|
1380
1051
|
const branchId = 'onboard-git-' + role + '-' + sha256(input.requestKey);
|
|
1381
|
-
const branchDefinitions =
|
|
1382
|
-
const copy =
|
|
1052
|
+
const branchDefinitions = wording.map((entry) => {
|
|
1053
|
+
const copy = entry.copy.git;
|
|
1383
1054
|
return {
|
|
1384
1055
|
questionnaireId: branchId,
|
|
1385
|
-
language:
|
|
1056
|
+
language: entry.language,
|
|
1386
1057
|
message: copy[role].message,
|
|
1387
1058
|
context: copy[role].context,
|
|
1388
1059
|
example: copy[role].example,
|
|
1389
1060
|
options: [
|
|
1390
|
-
{ id: 'set', label: copy.set
|
|
1061
|
+
{ id: 'set', label: copy.set.label, description: copy.set.description },
|
|
1391
1062
|
{
|
|
1392
1063
|
id: role === 'development' ? 'defer' : 'none',
|
|
1393
|
-
label: copy[role].second
|
|
1394
|
-
description: copy[role].second
|
|
1064
|
+
label: copy[role].second.label,
|
|
1065
|
+
description: copy[role].second.description,
|
|
1395
1066
|
},
|
|
1396
1067
|
],
|
|
1397
1068
|
textField: { title: copy.branchTitle, maxLength: 255, requiredForChoice: 'set' },
|
|
1398
1069
|
allowFreeText: false,
|
|
1399
1070
|
};
|
|
1400
1071
|
});
|
|
1401
|
-
const response = await askQuestionnaire(server, service, { ...scope, ...branchDefinitions
|
|
1072
|
+
const response = await askQuestionnaire(server, service, { ...scope, ...branchDefinitions[0] }, context, [
|
|
1402
1073
|
...branchDefinitions,
|
|
1403
1074
|
{
|
|
1404
1075
|
questionnaireId: branchId,
|
|
@@ -1438,26 +1109,26 @@ async function approveOnboarding(server, service, input, context, initialize) {
|
|
|
1438
1109
|
};
|
|
1439
1110
|
const digest = sha256(stableStringify(choice));
|
|
1440
1111
|
const questionnaireId = 'onboard-profile-' + digest + (initialize ? '-initialize' : '-save');
|
|
1441
|
-
const saveDefinitions =
|
|
1442
|
-
const copy =
|
|
1112
|
+
const saveDefinitions = wording.map((entry) => {
|
|
1113
|
+
const copy = entry.copy.save;
|
|
1443
1114
|
return {
|
|
1444
1115
|
questionnaireId,
|
|
1445
|
-
language:
|
|
1116
|
+
language: entry.language,
|
|
1446
1117
|
message: [copy.message, `${copy.routeLabel}: ${copy.route[input.mode]}`].join('\n'),
|
|
1447
|
-
context:
|
|
1118
|
+
context: initialize ? `${copy.context} ${copy.git}` : copy.context,
|
|
1448
1119
|
example: copy.example,
|
|
1449
1120
|
options: [
|
|
1450
1121
|
{
|
|
1451
1122
|
id: 'approve',
|
|
1452
|
-
label: copy.approve
|
|
1453
|
-
description: initialize ? copy.approveGit : copy.approve
|
|
1123
|
+
label: copy.approve.label,
|
|
1124
|
+
description: initialize ? copy.approveGit : copy.approve.description,
|
|
1454
1125
|
},
|
|
1455
|
-
{ id: 'defer', label: copy.defer
|
|
1126
|
+
{ id: 'defer', label: copy.defer.label, description: copy.defer.description },
|
|
1456
1127
|
],
|
|
1457
1128
|
binding: { choiceDigest: digest },
|
|
1458
1129
|
};
|
|
1459
1130
|
});
|
|
1460
|
-
const approval = await askQuestionnaire(server, service, { ...scope, ...saveDefinitions
|
|
1131
|
+
const approval = await askQuestionnaire(server, service, { ...scope, ...saveDefinitions[0] }, context, [
|
|
1461
1132
|
...saveDefinitions,
|
|
1462
1133
|
{
|
|
1463
1134
|
questionnaireId,
|
|
@@ -1519,26 +1190,26 @@ function parseEditedProfileValue(field, value) {
|
|
|
1519
1190
|
return z.array(uuid).max(10).parse(parsed);
|
|
1520
1191
|
}
|
|
1521
1192
|
const stackValues = ['Flutter', 'React', 'Nest', 'Spring'];
|
|
1522
|
-
function profileFieldOptions(field, currentValue, copy) {
|
|
1193
|
+
function profileFieldOptions(field, currentValue, copy, language) {
|
|
1523
1194
|
if (field === 'stack') {
|
|
1524
1195
|
return [
|
|
1525
|
-
{ id: 'keep_current', label: copy.keep
|
|
1196
|
+
{ id: 'keep_current', label: copy.keep.label, description: copy.keep.description },
|
|
1526
1197
|
...stackValues
|
|
1527
1198
|
.filter((value) => value !== currentValue)
|
|
1528
1199
|
.map((value) => ({
|
|
1529
1200
|
id: value,
|
|
1530
|
-
label: copy.change
|
|
1531
|
-
description: copy.changeDetail
|
|
1201
|
+
label: format(copy.change, language, { value }),
|
|
1202
|
+
description: format(copy.changeDetail, language, { value }),
|
|
1532
1203
|
})),
|
|
1533
|
-
{ id: 'defer_current', label: copy.defer
|
|
1204
|
+
{ id: 'defer_current', label: copy.defer.label, description: copy.defer.description },
|
|
1534
1205
|
];
|
|
1535
1206
|
}
|
|
1536
1207
|
return [
|
|
1537
1208
|
...(currentValue === null
|
|
1538
1209
|
? []
|
|
1539
|
-
: [{ id: 'keep_current', label: copy.keep
|
|
1540
|
-
{ id: 'edit_value', label: copy.edit
|
|
1541
|
-
{ id: 'defer_current', label: copy.defer
|
|
1210
|
+
: [{ id: 'keep_current', label: copy.keep.label, description: copy.keep.description }]),
|
|
1211
|
+
{ id: 'edit_value', label: copy.edit.label, description: copy.edit.description },
|
|
1212
|
+
{ id: 'defer_current', label: copy.defer.label, description: copy.defer.description },
|
|
1542
1213
|
];
|
|
1543
1214
|
}
|
|
1544
1215
|
function legacyProfileFieldOptions(field, currentValue) {
|