engineering-memory 1.11.15 → 1.11.16

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "engineering-memory",
3
- "version": "1.11.15",
3
+ "version": "1.11.16",
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",
@@ -1,3 +1,3 @@
1
1
  {
2
- "gitHead": "52f278262fad1afde8d11aa6e2343b3de5e8fe78"
2
+ "gitHead": "9623849ae66a8cc72716533cb131790a4c517ac7"
3
3
  }
@@ -544,7 +544,7 @@ export class BridgeService {
544
544
  if ((!sourceCommit && !(previous && previous.baseCommit === null && !previous.managed)) ||
545
545
  branch === undefined)
546
546
  throw refuse('The task branch and base must be restored before resuming its worktree.', 'worktree.reconcile');
547
- const allocation = await pool.allocate({
547
+ const allocation = await pool.prepare(await pool.allocate({
548
548
  projectId,
549
549
  projectName: projectId,
550
550
  repoFingerprint: repository.repoFingerprint,
@@ -554,7 +554,7 @@ export class BridgeService {
554
554
  baseCommit: sourceCommit ?? null,
555
555
  keepCurrent: previous ? !previous.managed : false,
556
556
  resume: true,
557
- }, await this.poolPolicy());
557
+ }, await this.poolPolicy()));
558
558
  await pool.bind(projectId, allocation.repoRoot, allocation.generation, pointer.taskId);
559
559
  allocation.taskId = pointer.taskId;
560
560
  this.poolAllocations.set(allocation.repoRoot, allocation);
@@ -2615,7 +2615,7 @@ export class BridgeService {
2615
2615
  const allocationTimer = startPhaseTimer('task.branch.allocate');
2616
2616
  const allocationPolicy = await this.poolPolicy(true);
2617
2617
  allocationTimer.mark('policy');
2618
- const allocation = await pool.allocate({
2618
+ const allocation = await pool.prepare(await pool.allocate({
2619
2619
  projectId,
2620
2620
  projectName: preferences.projectName ?? projectId,
2621
2621
  repoFingerprint: repository.repoFingerprint,
@@ -2626,7 +2626,7 @@ export class BridgeService {
2626
2626
  keepCurrent: input.keepCurrent,
2627
2627
  inPlace: !existing && input.inPlace,
2628
2628
  resume: Boolean(existing),
2629
- }, allocationPolicy);
2629
+ }, allocationPolicy));
2630
2630
  allocationTimer.mark('pool_allocate');
2631
2631
  allocationTimer.finish();
2632
2632
  this.poolAllocations.set(allocation.repoRoot, allocation);
@@ -2648,7 +2648,7 @@ export class BridgeService {
2648
2648
  return asJsonValue({
2649
2649
  ...allocation,
2650
2650
  kept: !allocation.managed,
2651
- nextAction: 'Run every file, terminal, lifecycle and delivery operation from this repoRoot. Use task.heartbeat during long work. Pause before handing the task to another client.',
2651
+ nextAction: 'Run every file, terminal, lifecycle and delivery operation from this repoRoot. Inspect readiness separately: a file problem does not cancel the task, and an editor request does not prove its window is visible. Resolve reported local file issues without overwriting existing files, then retry task.branch or session.resume. Use task.heartbeat during long work and pause before handoff.',
2652
2652
  });
2653
2653
  }
2654
2654
  adoptOwned(repoRoot, entry) {
@@ -0,0 +1,184 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { constants } from 'node:fs';
3
+ import { access, open, realpath, stat } from 'node:fs/promises';
4
+ import { delimiter, dirname, isAbsolute, join, resolve } from 'node:path';
5
+ export var WorktreeEditorStatus;
6
+ (function (WorktreeEditorStatus) {
7
+ WorktreeEditorStatus["Requested"] = "requested";
8
+ WorktreeEditorStatus["Unavailable"] = "unavailable";
9
+ WorktreeEditorStatus["Failed"] = "failed";
10
+ WorktreeEditorStatus["Skipped"] = "skipped";
11
+ })(WorktreeEditorStatus || (WorktreeEditorStatus = {}));
12
+ export async function openWorktreeInEditor(repoRoot) {
13
+ if (!['win32', 'darwin', 'linux'].includes(process.platform) ||
14
+ (process.env.CI && !/^(false|0)$/i.test(process.env.CI)) ||
15
+ process.env.SSH_CONNECTION ||
16
+ process.env.SSH_CLIENT ||
17
+ process.env.SSH_TTY ||
18
+ process.env.VSCODE_AGENT_FOLDER ||
19
+ process.env.VSCODE_REMOTE_NAME ||
20
+ process.env.WSL_DISTRO_NAME ||
21
+ process.env.CODESPACES === 'true' ||
22
+ process.env.SESSIONNAME?.toLowerCase() === 'services' ||
23
+ (process.platform === 'linux' && !process.env.DISPLAY && !process.env.WAYLAND_DISPLAY)) {
24
+ return {
25
+ status: WorktreeEditorStatus.Skipped,
26
+ detail: 'A local desktop session is not available.',
27
+ };
28
+ }
29
+ let folder;
30
+ try {
31
+ if (!repoRoot.trim())
32
+ throw new Error();
33
+ folder = await realpath(resolve(repoRoot));
34
+ if (!(await stat(folder)).isDirectory())
35
+ throw new Error();
36
+ }
37
+ catch {
38
+ return {
39
+ status: WorktreeEditorStatus.Failed,
40
+ detail: 'The worktree folder is not accessible.',
41
+ };
42
+ }
43
+ const command = await findEditor().catch(() => null);
44
+ if (!command) {
45
+ return {
46
+ status: WorktreeEditorStatus.Unavailable,
47
+ detail: 'An installed local VS Code CLI was not found.',
48
+ };
49
+ }
50
+ if (/[\\/](?:\.vscode-server(?:-insiders)?|remote-cli)[\\/]/i.test(command.executable)) {
51
+ return {
52
+ status: WorktreeEditorStatus.Skipped,
53
+ detail: 'The available VS Code CLI belongs to a remote session.',
54
+ };
55
+ }
56
+ const env = { ...process.env };
57
+ delete env.VSCODE_DEV;
58
+ if (process.platform === 'win32')
59
+ env.ELECTRON_RUN_AS_NODE = '1';
60
+ try {
61
+ return await new Promise((resolveResult) => {
62
+ const child = spawn(command.executable, [...command.args, '--new-window', folder], {
63
+ cwd: folder,
64
+ env,
65
+ shell: false,
66
+ windowsHide: true,
67
+ stdio: ['ignore', 'pipe', 'pipe'],
68
+ });
69
+ let settled = false;
70
+ let outputBytes = 0;
71
+ const timer = setTimeout(() => stop('The VS Code request timed out; window state is unknown.'), 10_000);
72
+ const finish = (status, detail) => {
73
+ if (settled)
74
+ return;
75
+ settled = true;
76
+ clearTimeout(timer);
77
+ child.stdout.destroy();
78
+ child.stderr.destroy();
79
+ child.unref();
80
+ resolveResult({ status, detail });
81
+ };
82
+ const stop = (detail) => {
83
+ if (settled)
84
+ return;
85
+ try {
86
+ child.kill('SIGKILL');
87
+ }
88
+ finally {
89
+ finish(WorktreeEditorStatus.Failed, detail);
90
+ }
91
+ };
92
+ const discardOutput = (chunk) => {
93
+ outputBytes += chunk.length;
94
+ if (outputBytes > 65_536) {
95
+ stop('The VS Code CLI exceeded the output limit; window state is unknown.');
96
+ }
97
+ };
98
+ child.stdout.on('data', discardOutput);
99
+ child.stderr.on('data', discardOutput);
100
+ child.once('error', () => finish(WorktreeEditorStatus.Failed, 'The VS Code CLI could not start.'));
101
+ child.once('close', (code) => finish(code === 0 ? WorktreeEditorStatus.Requested : WorktreeEditorStatus.Failed, code === 0
102
+ ? 'VS Code launch requested; window state was not verified.'
103
+ : 'The VS Code CLI failed; window state is unknown.'));
104
+ });
105
+ }
106
+ catch {
107
+ return { status: WorktreeEditorStatus.Failed, detail: 'The VS Code CLI could not start.' };
108
+ }
109
+ }
110
+ async function findEditor() {
111
+ const directories = (process.env.PATH ?? '')
112
+ .split(delimiter)
113
+ .map((directory) => directory.replace(/^"(.+)"$/, '$1'))
114
+ .filter(isAbsolute)
115
+ .slice(0, 128);
116
+ if (process.platform === 'win32') {
117
+ for (const root of [
118
+ process.env.LOCALAPPDATA && join(process.env.LOCALAPPDATA, 'Programs'),
119
+ process.env.ProgramFiles,
120
+ process.env['ProgramFiles(x86)'],
121
+ ]) {
122
+ if (root && isAbsolute(root))
123
+ directories.push(join(root, 'Microsoft VS Code', 'bin'));
124
+ }
125
+ }
126
+ for (const directory of new Set(directories)) {
127
+ if (process.platform === 'win32') {
128
+ const command = await windowsEditor(directory);
129
+ if (command)
130
+ return command;
131
+ }
132
+ else {
133
+ const executable = join(directory, 'code');
134
+ if (await executableFile(executable)) {
135
+ return { executable: await realpath(executable), args: [] };
136
+ }
137
+ }
138
+ }
139
+ return null;
140
+ }
141
+ async function windowsEditor(bin) {
142
+ let handle;
143
+ try {
144
+ const launcher = await realpath(join(bin, 'code.cmd'));
145
+ handle = await open(launcher, 'r');
146
+ const info = await handle.stat();
147
+ if (!info.isFile() || info.size > 16_384)
148
+ return null;
149
+ const bytes = Buffer.alloc(16_385);
150
+ const { bytesRead } = await handle.read(bytes, 0, bytes.length, 0);
151
+ if (bytesRead > 16_384)
152
+ return null;
153
+ const match = bytes
154
+ .subarray(0, bytesRead)
155
+ .toString('utf8')
156
+ .match(/^[ \t]*"%~dp0\.\.\\Code\.exe"[ \t]+"%~dp0\.\.\\((?:[a-f0-9]{7,40}\\)?resources\\app\\out\\cli\.js)"[ \t]+%\*[ \t]*\r?$/im);
157
+ if (!match)
158
+ return null;
159
+ const installation = resolve(dirname(launcher), '..');
160
+ const executable = join(installation, 'Code.exe');
161
+ const cli = join(installation, match[1]);
162
+ if (!(await executableFile(executable)) || !(await stat(cli)).isFile())
163
+ return null;
164
+ return { executable, args: [cli] };
165
+ }
166
+ catch {
167
+ return null;
168
+ }
169
+ finally {
170
+ await handle?.close();
171
+ }
172
+ }
173
+ async function executableFile(path) {
174
+ try {
175
+ if (!(await stat(path)).isFile())
176
+ return false;
177
+ await access(path, process.platform === 'win32' ? constants.F_OK : constants.X_OK);
178
+ return true;
179
+ }
180
+ catch {
181
+ return false;
182
+ }
183
+ }
184
+ //# sourceMappingURL=worktree-editor.js.map
@@ -0,0 +1,318 @@
1
+ function unsupported() {
2
+ throw new Error('Unsupported or ambiguous Gradle signing configuration');
3
+ }
4
+ function tokenize(text) {
5
+ if (text.length > 256 * 1024)
6
+ unsupported();
7
+ const tokens = [];
8
+ let cursor = 0;
9
+ while (cursor < text.length) {
10
+ if (/\s/.test(text[cursor])) {
11
+ cursor++;
12
+ continue;
13
+ }
14
+ if (text.startsWith('//', cursor)) {
15
+ while (cursor < text.length && !/[\r\n]/.test(text[cursor]))
16
+ cursor++;
17
+ continue;
18
+ }
19
+ if (text.startsWith('/*', cursor)) {
20
+ let depth = 1;
21
+ cursor += 2;
22
+ while (cursor < text.length && depth) {
23
+ if (text.startsWith('/*', cursor)) {
24
+ depth++;
25
+ cursor += 2;
26
+ }
27
+ else if (text.startsWith('*/', cursor)) {
28
+ depth--;
29
+ cursor += 2;
30
+ }
31
+ else
32
+ cursor++;
33
+ }
34
+ if (depth)
35
+ unsupported();
36
+ continue;
37
+ }
38
+ const start = cursor;
39
+ const quote = text[cursor];
40
+ if (quote === '"' || quote === "'") {
41
+ if (text.startsWith(quote.repeat(3), cursor)) {
42
+ const end = text.indexOf(quote.repeat(3), cursor + 3);
43
+ if (end < 0)
44
+ unsupported();
45
+ cursor = end + 3;
46
+ tokens.push({ kind: 'string', value: '', literal: false, start, end: cursor });
47
+ continue;
48
+ }
49
+ cursor++;
50
+ let value = '';
51
+ let literal = true;
52
+ while (cursor < text.length && text[cursor] !== quote) {
53
+ let character = text[cursor++];
54
+ if (/[\r\n]/.test(character))
55
+ unsupported();
56
+ if (character === '\\') {
57
+ character = text[cursor++] ?? '';
58
+ if (character === 'u') {
59
+ const digits = text.slice(cursor, cursor + 4);
60
+ if (!/^[a-fA-F0-9]{4}$/.test(digits))
61
+ unsupported();
62
+ value += String.fromCharCode(parseInt(digits, 16));
63
+ cursor += 4;
64
+ }
65
+ else if ('\\\'"'.includes(character) && character)
66
+ value += character;
67
+ else {
68
+ literal = false;
69
+ value += character;
70
+ }
71
+ }
72
+ else {
73
+ if (character === '$')
74
+ literal = false;
75
+ value += character;
76
+ }
77
+ }
78
+ if (text[cursor++] !== quote)
79
+ unsupported();
80
+ tokens.push({ kind: 'string', value, literal, start, end: cursor });
81
+ continue;
82
+ }
83
+ const word = /^[A-Za-z_$][A-Za-z0-9_$]*/.exec(text.slice(cursor))?.[0];
84
+ cursor += word?.length ?? 1;
85
+ tokens.push({ kind: word ? 'word' : 'symbol', value: word ?? quote, start, end: cursor });
86
+ }
87
+ return tokens;
88
+ }
89
+ export function parseGradleSigning(text) {
90
+ const tokens = tokenize(text);
91
+ const signing = tokens.flatMap((token, index) => token.kind === 'word' && ['storeFile', 'setStoreFile'].includes(token.value) ? [index] : []);
92
+ if (!signing.length)
93
+ return null;
94
+ const value = (index) => tokens[index]?.value;
95
+ const expect = (index, expected) => {
96
+ if (value(index) !== expected || tokens[index]?.kind === 'string')
97
+ unsupported();
98
+ return index + 1;
99
+ };
100
+ const word = (index) => {
101
+ if (tokens[index]?.kind !== 'word')
102
+ unsupported();
103
+ return tokens[index].value;
104
+ };
105
+ const literal = (index) => {
106
+ const token = tokens[index];
107
+ if (token?.kind !== 'string' || !token.literal || !token.value || token.value.includes('$'))
108
+ unsupported();
109
+ return token.value;
110
+ };
111
+ const scopes = [];
112
+ const braces = [];
113
+ for (const [index, token] of tokens.entries()) {
114
+ scopes.push(braces.at(-1) ?? -1);
115
+ if (token.kind !== 'symbol')
116
+ continue;
117
+ if (token.value === '{')
118
+ braces.push(index);
119
+ if (token.value === '}' && braces.pop() === undefined)
120
+ unsupported();
121
+ }
122
+ if (braces.length)
123
+ unsupported();
124
+ const startStatement = (index) => {
125
+ if (!index ||
126
+ (tokens[index - 1]?.kind === 'symbol' && [';', '{', '}'].includes(value(index - 1))))
127
+ return;
128
+ const previous = value(index - 1);
129
+ if (!/[\r\n]/.test(text.slice(tokens[index - 1].end, tokens[index].start)) ||
130
+ ['=', '(', '[', ',', '.', '?', ':', '+', '-', '*', '/', 'else', 'do'].includes(previous))
131
+ unsupported();
132
+ if (previous === ')') {
133
+ let cursor = index - 1;
134
+ let depth = 0;
135
+ do {
136
+ if (tokens[cursor]?.kind !== 'string') {
137
+ if (value(cursor) === ')')
138
+ depth++;
139
+ if (value(cursor) === '(')
140
+ depth--;
141
+ }
142
+ cursor--;
143
+ } while (cursor >= 0 && depth);
144
+ if (['if', 'for', 'while', 'when', 'catch'].includes(value(cursor)))
145
+ unsupported();
146
+ }
147
+ };
148
+ const endStatement = (index) => {
149
+ if (index === tokens.length ||
150
+ (tokens[index]?.kind === 'symbol' && [';', '}'].includes(value(index))))
151
+ return;
152
+ const gap = text.slice(tokens[index - 1].end, tokens[index].start);
153
+ if (!/[\r\n]/.test(gap) ||
154
+ [
155
+ '.',
156
+ '?',
157
+ '+',
158
+ '-',
159
+ '*',
160
+ '/',
161
+ '%',
162
+ '[',
163
+ '(',
164
+ '=',
165
+ '&',
166
+ '|',
167
+ '^',
168
+ '<',
169
+ '>',
170
+ '!',
171
+ '{',
172
+ ':',
173
+ ',',
174
+ 'as',
175
+ 'in',
176
+ 'instanceof',
177
+ ].includes(value(index)))
178
+ unsupported();
179
+ };
180
+ const fileCall = (index) => {
181
+ word(index);
182
+ const root = value(index) === 'rootProject';
183
+ if (root || value(index) === 'project')
184
+ index = expect(index + 1, '.');
185
+ index = expect(index, 'file');
186
+ return { index: expect(index, '('), root };
187
+ };
188
+ const access = (index) => {
189
+ if (value(index) === '[') {
190
+ const key = literal(index + 1);
191
+ return { key, next: expect(index + 2, ']') };
192
+ }
193
+ index = expect(index, '.');
194
+ index = expect(index, 'getProperty');
195
+ index = expect(index, '(');
196
+ const key = literal(index++);
197
+ return { key, next: expect(index, ')') };
198
+ };
199
+ const declaration = (name, type) => {
200
+ const matches = tokens.flatMap((token, index) => {
201
+ if (token.kind !== 'word' ||
202
+ token.value !== name ||
203
+ tokens[index - 1]?.kind !== 'word' ||
204
+ !['def', 'val', 'var', type].includes(value(index - 1)))
205
+ return [];
206
+ if (scopes[index] !== -1)
207
+ unsupported();
208
+ startStatement(index - 1);
209
+ let next = index + 1;
210
+ if (value(next) === ':')
211
+ next = expect(next + 1, type);
212
+ return [{ index, next: expect(next, '=') }];
213
+ });
214
+ if (matches.length !== 1)
215
+ unsupported();
216
+ return matches[0];
217
+ };
218
+ let propertiesName;
219
+ let keyRoot = false;
220
+ const signingReads = new Set();
221
+ for (const start of signing) {
222
+ startStatement(start);
223
+ if (value(start) !== 'storeFile' || ['.', 'def', 'val', 'var'].includes(value(start - 1)))
224
+ unsupported();
225
+ let index = start + 1;
226
+ if (value(index) === '=')
227
+ index++;
228
+ const call = fileCall(index);
229
+ index = call.index;
230
+ const name = word(index);
231
+ signingReads.add(index++);
232
+ if (propertiesName && (propertiesName !== name || keyRoot !== call.root))
233
+ unsupported();
234
+ propertiesName = name;
235
+ keyRoot = call.root;
236
+ const read = access(index);
237
+ if (read.key !== 'storeFile')
238
+ unsupported();
239
+ index = read.next;
240
+ if (value(index) === 'as')
241
+ index = expect(index + 1, 'String');
242
+ endStatement(expect(index, ')'));
243
+ }
244
+ const properties = declaration(propertiesName, 'Properties');
245
+ let index = properties.next;
246
+ if (value(index) === 'new')
247
+ index++;
248
+ index = expect(index, 'Properties');
249
+ index = expect(index, '(');
250
+ endStatement(expect(index, ')'));
251
+ const loads = [];
252
+ for (const [position, token] of tokens.entries()) {
253
+ if (token.kind !== 'word' ||
254
+ token.value !== propertiesName ||
255
+ position === properties.index ||
256
+ signingReads.has(position))
257
+ continue;
258
+ if (value(position - 1) === '.')
259
+ unsupported();
260
+ if (value(position + 1) === '.' && value(position + 2) === 'load') {
261
+ let next = expect(position + 3, '(');
262
+ if (value(next) === 'new')
263
+ next++;
264
+ next = expect(next, 'FileInputStream');
265
+ next = expect(next, '(');
266
+ const fileIndex = next;
267
+ const fileName = word(next++);
268
+ next = expect(next, ')');
269
+ endStatement(expect(next, ')'));
270
+ loads.push({ index: position, fileIndex, fileName });
271
+ }
272
+ else {
273
+ const read = access(position + 1);
274
+ if (tokens[read.next]?.kind === 'symbol' &&
275
+ ![')', ']', '}', ',', ';'].includes(value(read.next)))
276
+ unsupported();
277
+ }
278
+ }
279
+ if (loads.length !== 1)
280
+ unsupported();
281
+ const load = loads[0];
282
+ startStatement(load.index);
283
+ const scope = scopes[load.index];
284
+ if (scope !== -1) {
285
+ if (scopes[scope] !== -1)
286
+ unsupported();
287
+ let guard = scope - 8;
288
+ if (guard < 0)
289
+ unsupported();
290
+ startStatement(guard);
291
+ for (const expected of ['if', '(', load.fileName, '.', 'exists', '(', ')', ')']) {
292
+ guard = expect(guard, expected);
293
+ }
294
+ }
295
+ if (properties.index >= load.index || signing.some((position) => position <= load.index))
296
+ unsupported();
297
+ const source = declaration(load.fileName, 'File');
298
+ if (source.index >= load.index)
299
+ unsupported();
300
+ const call = fileCall(source.next);
301
+ const propertiesPath = literal(call.index);
302
+ endStatement(expect(call.index + 1, ')'));
303
+ for (const [position, token] of tokens.entries()) {
304
+ if (token.kind !== 'word' ||
305
+ token.value !== load.fileName ||
306
+ position === source.index ||
307
+ position === load.fileIndex)
308
+ continue;
309
+ if (value(position - 1) === '.')
310
+ unsupported();
311
+ let next = expect(position + 1, '.');
312
+ next = expect(next, 'exists');
313
+ next = expect(next, '(');
314
+ expect(next, ')');
315
+ }
316
+ return { propertiesPath, propertiesRoot: call.root, keyRoot };
317
+ }
318
+ //# sourceMappingURL=worktree-gradle.js.map