engineering-memory 1.10.4 → 1.10.5
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/package.json +1 -1
- package/runtime/dist/src/mcp/onboarding-tools.js +85 -18
- package/runtime/dist/src/mcp/questionnaire-tools.js +25 -6
- package/runtime/dist/src/mcp/tool-definitions.js +1 -1
- package/runtime/dist/src/runtime/bridge-service.js +2 -2
- package/runtime/dist/src/runtime/questionnaire-store.js +14 -2
- package/skill/references/project-onboarding.md +13 -0
- package/skill/references/questionnaires.md +13 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "engineering-memory",
|
|
3
|
-
"version": "1.10.
|
|
3
|
+
"version": "1.10.5",
|
|
4
4
|
"description": "Installs the Engineering Memory skill and its local MCP bridge. Sign in after installing; your organization and project are resolved from your account.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"type": "module",
|
|
@@ -16,6 +16,42 @@ const json = z.lazy(() => z.union([
|
|
|
16
16
|
z.record(z.string(), json),
|
|
17
17
|
]));
|
|
18
18
|
const object = z.record(z.string(), json);
|
|
19
|
+
const inspectionCopy = {
|
|
20
|
+
tr: {
|
|
21
|
+
modes: {
|
|
22
|
+
refresh: 'Projenin hafızasını güncel kodla karşılaştırıp incelemeyi başlatalım mı?',
|
|
23
|
+
adopt: 'Mevcut projeyi mimarisiyle birlikte öğrenmeye başlayalım mı?',
|
|
24
|
+
rework: 'Rework planından önce mevcut projeyi incelemeye başlayalım mı?',
|
|
25
|
+
},
|
|
26
|
+
source: 'Kaynak',
|
|
27
|
+
scope: 'Mimari, state yönetimi, servisler, network katmanı, veri modelleri, ekranlar ve akışlar incelenecek.',
|
|
28
|
+
localChanges: 'Uygulama kodu değiştirilmeyecek. Commit edilmemiş değişiklikler ortak hafızaya alınmayacak.',
|
|
29
|
+
review: 'Bu onay incelemeyi başlatır. Kalıcı hafıza değişiklikleri ayrıca onayına sunulacak.',
|
|
30
|
+
rework: {
|
|
31
|
+
preserve: 'Rework hedefi: mevcut davranışı korumak.',
|
|
32
|
+
redesign: 'Rework hedefi: ürünü yeniden tasarlamak. Uygulama öncesinde hedef ayrıca onaylanacak.',
|
|
33
|
+
},
|
|
34
|
+
start: 'İncelemeyi başlat',
|
|
35
|
+
defer: 'Şimdilik başlatma',
|
|
36
|
+
},
|
|
37
|
+
en: {
|
|
38
|
+
modes: {
|
|
39
|
+
refresh: 'Start comparing project memory with the current code?',
|
|
40
|
+
adopt: 'Start learning the existing project and its architecture?',
|
|
41
|
+
rework: 'Start inspecting the existing project before planning its rework?',
|
|
42
|
+
},
|
|
43
|
+
source: 'Source',
|
|
44
|
+
scope: 'Inspect architecture, state, services, network, data models, screens and flows.',
|
|
45
|
+
localChanges: 'Application code will stay unchanged. Uncommitted changes will not enter shared memory.',
|
|
46
|
+
review: 'This approval starts the inspection. Permanent memory changes will be submitted for separate approval.',
|
|
47
|
+
rework: {
|
|
48
|
+
preserve: 'Rework goal: preserve existing behavior.',
|
|
49
|
+
redesign: 'Rework goal: redesign the product. The target needs separate approval before implementation.',
|
|
50
|
+
},
|
|
51
|
+
start: 'Start inspection',
|
|
52
|
+
defer: 'Not now',
|
|
53
|
+
},
|
|
54
|
+
};
|
|
19
55
|
const reference = z.object({
|
|
20
56
|
proposalId: uuid,
|
|
21
57
|
contentHash: hash,
|
|
@@ -197,12 +233,16 @@ export function registerOnboardingTools(server, service) {
|
|
|
197
233
|
}),
|
|
198
234
|
}, async (input) => output(await service.memorySource(input)));
|
|
199
235
|
server.registerTool('memory.sync_start', {
|
|
200
|
-
description: 'Start or resume an idempotent committed-source inspection after native mode approval. 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.',
|
|
236
|
+
description: 'Start or resume an idempotent committed-source inspection after native mode approval. Set language from the current conversation (tr or en), never from machine locale. 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.',
|
|
201
237
|
inputSchema: z.object({
|
|
202
238
|
...project,
|
|
203
239
|
repoRoot,
|
|
204
240
|
requestKey: z.string().regex(/^[a-zA-Z0-9_-]{1,120}$/),
|
|
205
241
|
mode: z.enum(['adopt', 'refresh', 'rework']),
|
|
242
|
+
language: z
|
|
243
|
+
.enum(['tr', 'en'])
|
|
244
|
+
.optional()
|
|
245
|
+
.describe('Use the current conversation language: tr for Turkish, en for English. Omitted by legacy callers means English.'),
|
|
206
246
|
ref: z.string().optional(),
|
|
207
247
|
previousRunId: uuid.optional(),
|
|
208
248
|
reworkBehavior: z.enum(['preserve', 'redesign']).optional(),
|
|
@@ -226,24 +266,51 @@ export function registerOnboardingTools(server, service) {
|
|
|
226
266
|
recovery: 'memory.sync_status',
|
|
227
267
|
},
|
|
228
268
|
});
|
|
229
|
-
const
|
|
269
|
+
const { language = 'en', ...request } = input;
|
|
270
|
+
const pinned = { ...request, ref: commit };
|
|
230
271
|
const digest = sha256(stableStringify(pinned));
|
|
231
|
-
const
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
'
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
272
|
+
const reference = input.ref ?? 'HEAD';
|
|
273
|
+
const sourceLabel = /^[0-9a-f]{40,64}$/i.test(reference)
|
|
274
|
+
? commit.slice(0, 12)
|
|
275
|
+
: `${reference.slice(0, 80)} · ${commit.slice(0, 12)}`;
|
|
276
|
+
const definitions = ['tr', 'en'].map((displayLanguage) => {
|
|
277
|
+
const copy = inspectionCopy[displayLanguage];
|
|
278
|
+
return {
|
|
279
|
+
questionnaireId: 'sync-start-' + digest,
|
|
280
|
+
language: displayLanguage,
|
|
281
|
+
message: [
|
|
282
|
+
copy.modes[input.mode],
|
|
283
|
+
`${copy.source}: ${sourceLabel}`,
|
|
284
|
+
copy.scope,
|
|
285
|
+
copy.localChanges,
|
|
286
|
+
copy.review,
|
|
287
|
+
...(input.reworkBehavior ? [copy.rework[input.reworkBehavior]] : []),
|
|
288
|
+
].join('\n\n'),
|
|
289
|
+
options: [
|
|
290
|
+
{ id: 'approve', label: copy.start },
|
|
291
|
+
{ id: 'defer', label: copy.defer },
|
|
292
|
+
],
|
|
293
|
+
};
|
|
294
|
+
});
|
|
295
|
+
const definition = definitions.find((entry) => entry.language === language);
|
|
296
|
+
const question = await askQuestionnaire(server, service, { ...definition, repoRoot: input.repoRoot }, context, [
|
|
297
|
+
...definitions,
|
|
298
|
+
{
|
|
299
|
+
questionnaireId: 'sync-start-' + digest,
|
|
300
|
+
message: 'Start ' +
|
|
301
|
+
input.mode +
|
|
302
|
+
' for committed source ' +
|
|
303
|
+
commit +
|
|
304
|
+
'? Inspect architecture, state, services, network, data, screens and flows; dirty changes stay local. ' +
|
|
305
|
+
(input.reworkBehavior ? 'Rework target: ' + input.reworkBehavior + '. ' : '') +
|
|
306
|
+
'Exact request: ' +
|
|
307
|
+
digest,
|
|
308
|
+
options: [
|
|
309
|
+
{ id: 'approve', label: 'Start this exact inspection' },
|
|
310
|
+
{ id: 'defer', label: 'Keep inspection unstarted' },
|
|
311
|
+
],
|
|
312
|
+
},
|
|
313
|
+
]);
|
|
247
314
|
if (answerChoice(question) !== 'approve')
|
|
248
315
|
return question;
|
|
249
316
|
return output(await service.memorySyncStart({ ...pinned, questionnaireId: 'sync-start-' + digest }));
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { acceptedContent, CLIENT_CAPABILITIES_META_KEY, inputRequired, inputResponse, } from '@modelcontextprotocol/server';
|
|
2
2
|
import { questionnaireAnswerSchema, } from '../runtime/questionnaire-store.js';
|
|
3
|
-
export async function askQuestionnaire(server, service, input, context) {
|
|
3
|
+
export async function askQuestionnaire(server, service, input, context, previousDefinitions = []) {
|
|
4
4
|
try {
|
|
5
|
-
return await present(server, service, await service.questionnaireAsk(input), input.repoRoot, context, input.preparation);
|
|
5
|
+
return await present(server, service, await service.questionnaireAsk(input, previousDefinitions), input.repoRoot, context, input.preparation);
|
|
6
6
|
}
|
|
7
7
|
catch (error) {
|
|
8
8
|
return failure(error);
|
|
@@ -53,31 +53,32 @@ async function present(server, service, record, repoRoot, context, preparation)
|
|
|
53
53
|
const elicitation = capabilities?.elicitation;
|
|
54
54
|
if (!elicitation || (!elicitation.form && elicitation.url !== undefined))
|
|
55
55
|
return pending(record, 'native_form_unavailable');
|
|
56
|
+
const copy = questionnaireCopy[record.language ?? 'en'];
|
|
56
57
|
return inputRequired({
|
|
57
58
|
inputRequests: {
|
|
58
59
|
[record.requestKey]: inputRequired.elicit({
|
|
59
60
|
mode: 'form',
|
|
60
|
-
message: `${record.message}\n\
|
|
61
|
+
message: `${record.message}\n\n${copy.pending}${record.allowFreeText ? copy.freeText : ''}${record.textField ? copy.storedText : ''}`,
|
|
61
62
|
requestedSchema: {
|
|
62
63
|
type: 'object',
|
|
63
64
|
properties: {
|
|
64
65
|
choice: {
|
|
65
66
|
type: 'string',
|
|
66
|
-
title:
|
|
67
|
+
title: copy.answer,
|
|
67
68
|
enum: [
|
|
68
69
|
...record.options.map((option) => option.id),
|
|
69
70
|
...(record.allowFreeText ? ['__other__'] : []),
|
|
70
71
|
],
|
|
71
72
|
enumNames: [
|
|
72
73
|
...record.options.map((option) => option.label),
|
|
73
|
-
...(record.allowFreeText ? [
|
|
74
|
+
...(record.allowFreeText ? [copy.other] : []),
|
|
74
75
|
],
|
|
75
76
|
},
|
|
76
77
|
...(record.allowFreeText
|
|
77
78
|
? {
|
|
78
79
|
text: {
|
|
79
80
|
type: 'string',
|
|
80
|
-
title:
|
|
81
|
+
title: copy.otherAnswer,
|
|
81
82
|
minLength: 1,
|
|
82
83
|
maxLength: 2000,
|
|
83
84
|
},
|
|
@@ -100,6 +101,24 @@ async function present(server, service, record, repoRoot, context, preparation)
|
|
|
100
101
|
},
|
|
101
102
|
});
|
|
102
103
|
}
|
|
104
|
+
const questionnaireCopy = {
|
|
105
|
+
en: {
|
|
106
|
+
pending: 'Choose an answer to resolve this decision. Closing or cancelling the form leaves it pending.',
|
|
107
|
+
freeText: ' For another answer choose Other and fill in text. Free text is not stored and cannot be replayed.',
|
|
108
|
+
storedText: ' The bounded text field is stored as part of this exact decision and is replayed after restart.',
|
|
109
|
+
answer: 'Answer',
|
|
110
|
+
other: 'Other',
|
|
111
|
+
otherAnswer: 'Other answer',
|
|
112
|
+
},
|
|
113
|
+
tr: {
|
|
114
|
+
pending: 'Kararını kaydetmek için bir seçenek seç. Formu kapatmak veya iptal etmek onay sayılmaz; soru beklemede kalır.',
|
|
115
|
+
freeText: ' Farklı bir yanıt için Diğer seçeneğini seçip metin alanını doldur. Serbest yanıt saklanmaz ve daha sonra geri getirilemez.',
|
|
116
|
+
storedText: ' Bu metin alanındaki yanıt kararınla birlikte saklanır; yeniden başlattığında korunur.',
|
|
117
|
+
answer: 'Yanıt',
|
|
118
|
+
other: 'Diğer',
|
|
119
|
+
otherAnswer: 'Diğer yanıt',
|
|
120
|
+
},
|
|
121
|
+
};
|
|
103
122
|
function pending(record, reason) {
|
|
104
123
|
return result({
|
|
105
124
|
questionnaireId: record.questionnaireId,
|
|
@@ -171,7 +171,7 @@ export function registerQuestionnaireTools(server, service) {
|
|
|
171
171
|
}
|
|
172
172
|
});
|
|
173
173
|
server.registerTool('questionnaire.ask', {
|
|
174
|
-
description: 'Persist a required decision before displaying a native questionnaire. Use a questionnaireId unique to this decision occurrence or task, and reuse it only for identical retries. A later decision or changed wording/options requires a new id. Only a schema-validated native acceptance answers it. Dismissal, timeout and missing replies remain pending without expiry. Never supply answers in tool arguments. Do not put personal information or secrets in the question or options. Free text is returned once and never stored; use explicit options for replayable decisions.',
|
|
174
|
+
description: 'Persist a required decision before displaying a native questionnaire. Write the message and labels in the conversation language and set language (tr or en) for the native form help. Use a questionnaireId unique to this decision occurrence or task, and reuse it only for identical retries. A later decision or changed wording/options requires a new id. Only a schema-validated native acceptance answers it. Dismissal, timeout and missing replies remain pending without expiry. Never supply answers in tool arguments. Do not put personal information or secrets in the question or options. Free text is returned once and never stored; use explicit options for replayable decisions.',
|
|
175
175
|
inputSchema: questionnaireDefinitionSchema.extend({
|
|
176
176
|
repoRoot: optionalRepoRoot,
|
|
177
177
|
preparation: z.boolean().optional(),
|
|
@@ -36,10 +36,10 @@ export class BridgeService {
|
|
|
36
36
|
this.dependencies = dependencies;
|
|
37
37
|
this.deferDeliveries = dependencies.deferDeliveries ?? true;
|
|
38
38
|
}
|
|
39
|
-
async questionnaireAsk(input) {
|
|
39
|
+
async questionnaireAsk(input, previousDefinitions = []) {
|
|
40
40
|
const scope = await this.questionnaireScope(input.repoRoot, input.preparation);
|
|
41
41
|
const { repoRoot: _repoRoot, preparation: _preparation, ...definition } = input;
|
|
42
|
-
return await this.dependencies.questionnaires.ask(scope, definition);
|
|
42
|
+
return await this.dependencies.questionnaires.ask(scope, definition, previousDefinitions);
|
|
43
43
|
}
|
|
44
44
|
async questionnaireResume(input) {
|
|
45
45
|
const scope = await this.questionnaireScope(input.repoRoot, input.preparation);
|
|
@@ -9,6 +9,10 @@ const identifier = z.string().regex(/^[A-Za-z0-9_-]{1,100}$/);
|
|
|
9
9
|
export const questionnaireDefinitionSchema = z.strictObject({
|
|
10
10
|
questionnaireId: identifier,
|
|
11
11
|
message: z.string().trim().min(1).max(2000),
|
|
12
|
+
language: z
|
|
13
|
+
.enum(['tr', 'en'])
|
|
14
|
+
.optional()
|
|
15
|
+
.describe('Language of the current conversation: tr for Turkish, en for English. Translate the message and option labels into that language too.'),
|
|
12
16
|
options: z
|
|
13
17
|
.array(z.strictObject({
|
|
14
18
|
id: z
|
|
@@ -97,8 +101,14 @@ export class QuestionnaireStore {
|
|
|
97
101
|
constructor(stateRoot) {
|
|
98
102
|
this.root = join(stateRoot, 'questionnaires');
|
|
99
103
|
}
|
|
100
|
-
async ask(scope, input) {
|
|
104
|
+
async ask(scope, input, previousDefinitions = []) {
|
|
101
105
|
const definition = questionnaireDefinitionSchema.parse(input);
|
|
106
|
+
const previousHashes = previousDefinitions.map((previous) => {
|
|
107
|
+
const parsed = questionnaireDefinitionSchema.parse(previous);
|
|
108
|
+
if (parsed.questionnaireId !== definition.questionnaireId)
|
|
109
|
+
throw new Error('Previous questionnaire content must belong to the same decision.');
|
|
110
|
+
return sha256(stableStringify(parsed));
|
|
111
|
+
});
|
|
102
112
|
scopeSchema.parse(scope);
|
|
103
113
|
assertSafeToPersist(definition);
|
|
104
114
|
const contentHash = sha256(stableStringify(definition));
|
|
@@ -112,7 +122,8 @@ export class QuestionnaireStore {
|
|
|
112
122
|
};
|
|
113
123
|
await this.publish(this.pathFor(scope, definition.questionnaireId, 'pending.json'), question);
|
|
114
124
|
const record = await this.get(scope, definition.questionnaireId);
|
|
115
|
-
if (!record ||
|
|
125
|
+
if (!record ||
|
|
126
|
+
(record.contentHash !== contentHash && !previousHashes.includes(record.contentHash))) {
|
|
116
127
|
throw new Error('Questionnaire id was reused with different content. Resume the original question or use a new id.');
|
|
117
128
|
}
|
|
118
129
|
return record;
|
|
@@ -125,6 +136,7 @@ export class QuestionnaireStore {
|
|
|
125
136
|
const definition = questionnaireDefinitionSchema.parse({
|
|
126
137
|
questionnaireId: question.questionnaireId,
|
|
127
138
|
message: question.message,
|
|
139
|
+
language: question.language,
|
|
128
140
|
options: question.options,
|
|
129
141
|
allowFreeText: question.allowFreeText,
|
|
130
142
|
textField: question.textField,
|
|
@@ -144,3 +144,16 @@ gates. Cross-project phases use existing work_item.plan/confirm_plan with explic
|
|
|
144
144
|
and independent per-repository runs. Planning tasks does not perform their implementation.
|
|
145
145
|
|
|
146
146
|
A deferred individual field or final profile saves no onboarding choice. Retrying that request preserves its deferral. When the user explicitly resumes, use the returned `nextRequestKey` for fresh native decisions; never infer approval from a previous defer.
|
|
147
|
+
|
|
148
|
+
## Inspection confirmation language
|
|
149
|
+
|
|
150
|
+
For memory.sync_start, pass language: tr in a Turkish conversation and language: en in an English
|
|
151
|
+
conversation. Use the conversation language, never the computer locale. Legacy calls without language
|
|
152
|
+
use English. The form shows a short source reference, scope, the treatment of uncommitted changes and
|
|
153
|
+
the separate approval needed for permanent memory. Full commit and request hashes stay in the durable
|
|
154
|
+
approval binding, not in the visible question. Generic questionnaire.ask messages and option labels
|
|
155
|
+
are written by the agent in the same language; pass language for the native help and field labels.
|
|
156
|
+
|
|
157
|
+
An existing exact-request questionnaire retains its original wording and answer across updates and
|
|
158
|
+
restart, including an older English form. Resume that question rather than silently replacing or
|
|
159
|
+
withdrawing it. Language changes never broaden the approved source, mode or previous-run scope.
|
|
@@ -239,3 +239,16 @@ again.
|
|
|
239
239
|
`project.onboard` can save the profile before initialization. Calling `project.initialize` with the
|
|
240
240
|
same request key reuses the profile answers and asks a separate exact Git-initialization confirmation.
|
|
241
241
|
Saving a profile does not authorize filesystem initialization.
|
|
242
|
+
|
|
243
|
+
## Inspection confirmation language
|
|
244
|
+
|
|
245
|
+
For memory.sync_start, pass language: tr in a Turkish conversation and language: en in an English
|
|
246
|
+
conversation. Use the conversation language, never the computer locale. Legacy calls without language
|
|
247
|
+
use English. The form shows a short source reference, scope, the treatment of uncommitted changes and
|
|
248
|
+
the separate approval needed for permanent memory. Full commit and request hashes stay in the durable
|
|
249
|
+
approval binding, not in the visible question. Generic questionnaire.ask messages and option labels
|
|
250
|
+
are written by the agent in the same language; pass language for the native help and field labels.
|
|
251
|
+
|
|
252
|
+
An existing exact-request questionnaire retains its original wording and answer across updates and
|
|
253
|
+
restart, including an older English form. Resume that question rather than silently replacing or
|
|
254
|
+
withdrawing it. Language changes never broaden the approved source, mode or previous-run scope.
|