engineering-memory 1.11.7 → 1.11.9
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 +1 -1
- package/install/files.mjs +114 -8
- package/install/installer.mjs +20 -4
- package/install/launcher.mjs +95 -0
- package/package.json +1 -1
- package/runtime/build.json +3 -0
- package/runtime/dist/src/config.js +3 -0
- package/runtime/dist/src/index.js +25 -81
- package/runtime/dist/src/mcp/server.js +14 -7
- package/runtime/dist/src/mcp/tool-host.js +12 -0
- package/runtime/dist/src/runtime/bridge-service.js +80 -20
- package/runtime/dist/src/runtime/create-bridge-service.js +82 -0
- package/runtime/dist/src/runtime/runtime-entry.js +16 -0
- package/runtime/dist/src/runtime/runtime-host.js +265 -0
- package/runtime/dist/src/runtime/worktree-pool.js +145 -32
- package/runtime/dist/src/utilities/files.js +4 -1
- package/skill/references/lifecycle.md +1 -1
package/dispatcher/sections.mjs
CHANGED
|
@@ -9,7 +9,7 @@ Call \`session.entry\` before you answer anything in a repository, whatever the
|
|
|
9
9
|
- Switched off: say nothing about Engineering Memory, offer nothing, hint at nothing, however useful it would be for what was asked. They turned it off; raising it again is what this prevents. Record it back on only when they ask.
|
|
10
10
|
- Bound: continue into the lifecycle.
|
|
11
11
|
|
|
12
|
-
\`session.entry\` also reports this client's version. When it says an update is available, offer once to run \`npx engineering-memory@latest install\` and tell the user the client
|
|
12
|
+
\`session.entry\` also reports this client's version. When it says an update is available, offer once to run \`npx engineering-memory@latest install\` and tell the user that the running bridge switches to the new release by itself on its next call, so nothing needs restarting; only a bridge older than 1.11.9 needs the client restarted once after that install. If they decline, record it with \`session.decline_update\` and never raise that version again. When it says an update is required, there is no choice to offer: say that this client is older than the backend supports, give the command, and do not carry on as though the lifecycle works.
|
|
13
13
|
|
|
14
14
|
The user may say at any point that they want a different organization, a different project, Engineering Memory switched off, or switched back on. Recognise what they mean rather than matching words — the request arrives in whatever language and phrasing they happen to use — and act on it. Changing the organization always asks for the project again.
|
|
15
15
|
|
package/install/files.mjs
CHANGED
|
@@ -11,7 +11,13 @@ import {
|
|
|
11
11
|
rm,
|
|
12
12
|
writeFile,
|
|
13
13
|
} from 'node:fs/promises';
|
|
14
|
-
import { basename, dirname, join } from 'node:path';
|
|
14
|
+
import { basename, dirname, join, resolve } from 'node:path';
|
|
15
|
+
import {
|
|
16
|
+
launcherPackageJson,
|
|
17
|
+
launcherSource,
|
|
18
|
+
normalizeRelativeEntry,
|
|
19
|
+
releasePointerName,
|
|
20
|
+
} from './launcher.mjs';
|
|
15
21
|
|
|
16
22
|
export class InstallTransaction {
|
|
17
23
|
constructor() {
|
|
@@ -110,6 +116,7 @@ export async function installManagedDirectory(
|
|
|
110
116
|
) {
|
|
111
117
|
const artifact = options.artifact ?? 'skill';
|
|
112
118
|
const artifactLabel = artifact === 'skill' ? 'skill' : 'bridge runtime';
|
|
119
|
+
const accepted = new Set([artifact, ...(options.replaces ?? [])]);
|
|
113
120
|
const sourceHash = await hashDirectory(source);
|
|
114
121
|
const manifestPath = join(destination, '.engineering-memory-install.json');
|
|
115
122
|
if (await exists(destination)) {
|
|
@@ -117,7 +124,7 @@ export async function installManagedDirectory(
|
|
|
117
124
|
if (
|
|
118
125
|
manifest?.product !== 'engineering-memory' ||
|
|
119
126
|
manifest?.schemaVersion !== 1 ||
|
|
120
|
-
(manifest.artifact ?? 'skill')
|
|
127
|
+
!accepted.has(manifest.artifact ?? 'skill')
|
|
121
128
|
) {
|
|
122
129
|
throw new Error(
|
|
123
130
|
`Refusing to overwrite an unmanaged ${artifactLabel} directory: ${destination}`,
|
|
@@ -151,6 +158,7 @@ export async function installManagedDirectory(
|
|
|
151
158
|
schemaVersion: 1,
|
|
152
159
|
artifact,
|
|
153
160
|
sourceHash,
|
|
161
|
+
...(options.manifest ?? {}),
|
|
154
162
|
},
|
|
155
163
|
null,
|
|
156
164
|
2,
|
|
@@ -181,9 +189,22 @@ export async function installManagedDirectory(
|
|
|
181
189
|
|
|
182
190
|
export async function installManagedBridgeRuntime(
|
|
183
191
|
source,
|
|
184
|
-
|
|
192
|
+
runtimeRoot,
|
|
185
193
|
transaction,
|
|
194
|
+
options = {},
|
|
186
195
|
) {
|
|
196
|
+
const bridgeEntry = normalizeRelativeEntry(
|
|
197
|
+
options.bridgeEntry ?? 'dist/src/index.js',
|
|
198
|
+
'Bridge entry',
|
|
199
|
+
);
|
|
200
|
+
const gateEntry = normalizeRelativeEntry(
|
|
201
|
+
options.gateEntry ?? 'dist/src/git/pre-commit.js',
|
|
202
|
+
'Git gate entry',
|
|
203
|
+
);
|
|
204
|
+
const clientVersion =
|
|
205
|
+
typeof options.clientVersion === 'string' && options.clientVersion
|
|
206
|
+
? options.clientVersion
|
|
207
|
+
: null;
|
|
187
208
|
const packageJsonPath = join(source, 'package.json');
|
|
188
209
|
const packageLockPath = join(source, 'package-lock.json');
|
|
189
210
|
const nodeModulesPath = join(source, 'node_modules');
|
|
@@ -209,10 +230,8 @@ export async function installManagedBridgeRuntime(
|
|
|
209
230
|
packageJson,
|
|
210
231
|
packageLock,
|
|
211
232
|
);
|
|
212
|
-
await mkdir(
|
|
213
|
-
const bundle = await mkdtemp(
|
|
214
|
-
join(dirname(destination), '.bridge-runtime-source-'),
|
|
215
|
-
);
|
|
233
|
+
await mkdir(runtimeRoot, { recursive: true });
|
|
234
|
+
const bundle = await mkdtemp(join(runtimeRoot, '.bridge-runtime-source-'));
|
|
216
235
|
try {
|
|
217
236
|
await copyDirectory(join(source, 'dist'), join(bundle, 'dist'));
|
|
218
237
|
await copyFile(packageJsonPath, join(bundle, 'package.json'));
|
|
@@ -230,14 +249,101 @@ export async function installManagedBridgeRuntime(
|
|
|
230
249
|
join(bundle, ...packageEntry.path.split('/')),
|
|
231
250
|
);
|
|
232
251
|
}
|
|
233
|
-
|
|
252
|
+
for (const [entry, label] of [
|
|
253
|
+
[bridgeEntry, 'Bridge entry'],
|
|
254
|
+
[gateEntry, 'Git gate entry'],
|
|
255
|
+
]) {
|
|
256
|
+
if (!(await exists(join(bundle, ...entry.split('/'))))) {
|
|
257
|
+
throw new Error(`${label} is missing from the built bridge: ${entry}`);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
const sourceHash = await hashDirectory(bundle);
|
|
261
|
+
const releasePath = join(runtimeRoot, 'releases', sourceHash.slice(0, 16));
|
|
262
|
+
const installedAt = new Date().toISOString();
|
|
263
|
+
await installManagedDirectory(bundle, releasePath, transaction, {
|
|
234
264
|
artifact: 'bridge-runtime',
|
|
265
|
+
manifest: { clientVersion, installedAt },
|
|
235
266
|
});
|
|
267
|
+
const launcherPath = join(runtimeRoot, 'bridge');
|
|
268
|
+
const launcherStage = await mkdtemp(join(runtimeRoot, '.bridge-launcher-'));
|
|
269
|
+
try {
|
|
270
|
+
await writeAtomic(
|
|
271
|
+
join(launcherStage, 'package.json'),
|
|
272
|
+
launcherPackageJson,
|
|
273
|
+
);
|
|
274
|
+
await writeAtomic(
|
|
275
|
+
join(launcherStage, ...bridgeEntry.split('/')),
|
|
276
|
+
launcherSource('bridgeEntry', bridgeEntry),
|
|
277
|
+
);
|
|
278
|
+
await writeAtomic(
|
|
279
|
+
join(launcherStage, ...gateEntry.split('/')),
|
|
280
|
+
launcherSource('gateEntry', gateEntry),
|
|
281
|
+
);
|
|
282
|
+
await installManagedDirectory(launcherStage, launcherPath, transaction, {
|
|
283
|
+
artifact: 'bridge-launcher',
|
|
284
|
+
replaces: ['bridge-runtime'],
|
|
285
|
+
});
|
|
286
|
+
} finally {
|
|
287
|
+
await rm(launcherStage, { recursive: true, force: true });
|
|
288
|
+
}
|
|
289
|
+
await writeJson(
|
|
290
|
+
join(runtimeRoot, releasePointerName),
|
|
291
|
+
{
|
|
292
|
+
product: 'engineering-memory',
|
|
293
|
+
schemaVersion: 1,
|
|
294
|
+
version: clientVersion,
|
|
295
|
+
sourceHash,
|
|
296
|
+
path: releasePath,
|
|
297
|
+
bridgeEntry,
|
|
298
|
+
gateEntry,
|
|
299
|
+
installedAt,
|
|
300
|
+
},
|
|
301
|
+
transaction,
|
|
302
|
+
);
|
|
303
|
+
return { sourceHash, releasePath, launcherPath, bridgeEntry, gateEntry };
|
|
236
304
|
} finally {
|
|
237
305
|
await rm(bundle, { recursive: true, force: true });
|
|
238
306
|
}
|
|
239
307
|
}
|
|
240
308
|
|
|
309
|
+
export async function pruneReleases(runtimeRoot, options = {}) {
|
|
310
|
+
const keep = Math.max(1, options.keep ?? 3);
|
|
311
|
+
const releasesPath = join(runtimeRoot, 'releases');
|
|
312
|
+
if (!(await exists(releasesPath))) return [];
|
|
313
|
+
const pointer = await readJson(join(runtimeRoot, releasePointerName));
|
|
314
|
+
const currentPath =
|
|
315
|
+
pointer && typeof pointer.path === 'string' ? resolve(pointer.path) : null;
|
|
316
|
+
const candidates = [];
|
|
317
|
+
for (const entry of await readdir(releasesPath, { withFileTypes: true })) {
|
|
318
|
+
if (!entry.isDirectory()) continue;
|
|
319
|
+
const path = join(releasesPath, entry.name);
|
|
320
|
+
if (currentPath && resolve(path) === currentPath) continue;
|
|
321
|
+
const manifest = await readJson(
|
|
322
|
+
join(path, '.engineering-memory-install.json'),
|
|
323
|
+
);
|
|
324
|
+
if (
|
|
325
|
+
manifest?.product !== 'engineering-memory' ||
|
|
326
|
+
manifest.artifact !== 'bridge-runtime'
|
|
327
|
+
) {
|
|
328
|
+
continue;
|
|
329
|
+
}
|
|
330
|
+
const installedAt = Date.parse(String(manifest.installedAt ?? ''));
|
|
331
|
+
candidates.push({
|
|
332
|
+
path,
|
|
333
|
+
installedAt: Number.isNaN(installedAt)
|
|
334
|
+
? (await lstat(path)).mtimeMs
|
|
335
|
+
: installedAt,
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
candidates.sort((left, right) => right.installedAt - left.installedAt);
|
|
339
|
+
const removed = [];
|
|
340
|
+
for (const candidate of candidates.slice(keep - 1)) {
|
|
341
|
+
await rm(candidate.path, { recursive: true, force: true });
|
|
342
|
+
removed.push(candidate.path);
|
|
343
|
+
}
|
|
344
|
+
return removed;
|
|
345
|
+
}
|
|
346
|
+
|
|
241
347
|
export async function hashDirectory(root, options = {}) {
|
|
242
348
|
const hash = createHash('sha256');
|
|
243
349
|
const excluded = new Set(options.exclude ?? []);
|
package/install/installer.mjs
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
installManagedBridgeRuntime,
|
|
11
11
|
installManagedDirectory,
|
|
12
12
|
InstallTransaction,
|
|
13
|
+
pruneReleases,
|
|
13
14
|
readJson,
|
|
14
15
|
readText,
|
|
15
16
|
replaceFile,
|
|
@@ -54,7 +55,8 @@ export async function installEngineeringMemory(options = {}) {
|
|
|
54
55
|
options.gateEntry ??
|
|
55
56
|
join(bridgeRuntimeSource, 'dist', 'src', 'git', 'pre-commit.js'),
|
|
56
57
|
);
|
|
57
|
-
const
|
|
58
|
+
const runtimeRoot = join(homeDir, '.engineering-memory', 'runtime');
|
|
59
|
+
const runtimePath = join(runtimeRoot, 'bridge');
|
|
58
60
|
const bridgeEntry = stableRuntimeEntry(
|
|
59
61
|
bridgeRuntimeSource,
|
|
60
62
|
runtimePath,
|
|
@@ -153,10 +155,15 @@ export async function installEngineeringMemory(options = {}) {
|
|
|
153
155
|
const transaction = new InstallTransaction();
|
|
154
156
|
let result;
|
|
155
157
|
try {
|
|
156
|
-
const
|
|
158
|
+
const runtime = await installManagedBridgeRuntime(
|
|
157
159
|
bridgeRuntimeSource,
|
|
158
|
-
|
|
160
|
+
runtimeRoot,
|
|
159
161
|
transaction,
|
|
162
|
+
{
|
|
163
|
+
clientVersion,
|
|
164
|
+
bridgeEntry: relative(bridgeRuntimeSource, sourceBridgeEntry),
|
|
165
|
+
gateEntry: relative(bridgeRuntimeSource, sourceGateEntry),
|
|
166
|
+
},
|
|
160
167
|
);
|
|
161
168
|
const mcp = {
|
|
162
169
|
...(existingState?.mcp ?? {}),
|
|
@@ -183,7 +190,8 @@ export async function installEngineeringMemory(options = {}) {
|
|
|
183
190
|
skillHash,
|
|
184
191
|
runtime: {
|
|
185
192
|
path: runtimePath,
|
|
186
|
-
sourceHash:
|
|
193
|
+
sourceHash: runtime.sourceHash,
|
|
194
|
+
release: runtime.releasePath,
|
|
187
195
|
bridgeEntry,
|
|
188
196
|
gateEntry,
|
|
189
197
|
},
|
|
@@ -206,6 +214,7 @@ export async function installEngineeringMemory(options = {}) {
|
|
|
206
214
|
apiUrl,
|
|
207
215
|
clientVersion,
|
|
208
216
|
runtimePath,
|
|
217
|
+
release: runtime.releasePath,
|
|
209
218
|
bridgeEntry,
|
|
210
219
|
gateEntry,
|
|
211
220
|
hook: hookResult,
|
|
@@ -222,6 +231,13 @@ export async function installEngineeringMemory(options = {}) {
|
|
|
222
231
|
throw error;
|
|
223
232
|
}
|
|
224
233
|
await transaction.commit();
|
|
234
|
+
try {
|
|
235
|
+
result.prunedReleases = await pruneReleases(runtimeRoot);
|
|
236
|
+
} catch (error) {
|
|
237
|
+
result.prunedReleases = [];
|
|
238
|
+
result.releasePruneProblem =
|
|
239
|
+
error instanceof Error ? error.message : String(error);
|
|
240
|
+
}
|
|
225
241
|
return result;
|
|
226
242
|
}
|
|
227
243
|
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
export const launcherPackageJson = `${JSON.stringify(
|
|
2
|
+
{
|
|
3
|
+
name: 'engineering-memory-bridge-launcher',
|
|
4
|
+
private: true,
|
|
5
|
+
type: 'module',
|
|
6
|
+
},
|
|
7
|
+
null,
|
|
8
|
+
2,
|
|
9
|
+
)}\n`;
|
|
10
|
+
|
|
11
|
+
export const releasePointerName = 'current.json';
|
|
12
|
+
|
|
13
|
+
export function normalizeRelativeEntry(entry, label) {
|
|
14
|
+
const normalized = String(entry).replaceAll('\\', '/').replace(/^\.\//, '');
|
|
15
|
+
const segments = normalized.split('/');
|
|
16
|
+
if (
|
|
17
|
+
normalized === '' ||
|
|
18
|
+
normalized.startsWith('/') ||
|
|
19
|
+
/^[A-Za-z]:/.test(normalized) ||
|
|
20
|
+
segments.some(
|
|
21
|
+
(segment) => segment === '' || segment === '..' || segment === '.',
|
|
22
|
+
)
|
|
23
|
+
) {
|
|
24
|
+
throw new Error(
|
|
25
|
+
`${label} must be a relative path inside the release: ${entry}`,
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
if (segments[0] !== 'dist') {
|
|
29
|
+
throw new Error(
|
|
30
|
+
`${label} must be inside the built dist directory: ${entry}`,
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
return normalized;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function launcherSource(kind, relativeEntry) {
|
|
37
|
+
if (kind !== 'bridgeEntry' && kind !== 'gateEntry') {
|
|
38
|
+
throw new Error(`Unknown launcher kind: ${kind}`);
|
|
39
|
+
}
|
|
40
|
+
const depth = normalizeRelativeEntry(relativeEntry, 'Launcher entry').split(
|
|
41
|
+
'/',
|
|
42
|
+
).length;
|
|
43
|
+
const ascend = Array.from({ length: depth }, () => "'..'").join(', ');
|
|
44
|
+
const label = kind === 'bridgeEntry' ? 'bridge' : 'Git gate';
|
|
45
|
+
return [
|
|
46
|
+
'#!/usr/bin/env node',
|
|
47
|
+
"import { readFile } from 'node:fs/promises';",
|
|
48
|
+
"import { dirname, isAbsolute, join, resolve } from 'node:path';",
|
|
49
|
+
"import { fileURLToPath, pathToFileURL } from 'node:url';",
|
|
50
|
+
'',
|
|
51
|
+
`const runtimeRoot = resolve(dirname(fileURLToPath(import.meta.url)), ${ascend});`,
|
|
52
|
+
`const pointerPath = join(runtimeRoot, ${JSON.stringify(releasePointerName)});`,
|
|
53
|
+
`const installCommand = 'npx engineering-memory@latest install';`,
|
|
54
|
+
'',
|
|
55
|
+
'function stop(message) {',
|
|
56
|
+
" process.stderr.write(message + '\\n');",
|
|
57
|
+
' process.exit(1);',
|
|
58
|
+
'}',
|
|
59
|
+
'',
|
|
60
|
+
'let pointer;',
|
|
61
|
+
'try {',
|
|
62
|
+
" pointer = JSON.parse(await readFile(pointerPath, 'utf8'));",
|
|
63
|
+
'} catch (error) {',
|
|
64
|
+
' stop(',
|
|
65
|
+
` 'The Engineering Memory ${label} cannot start: ' +`,
|
|
66
|
+
' pointerPath +',
|
|
67
|
+
" ' is unreadable (' +",
|
|
68
|
+
' (error instanceof Error ? error.message : String(error)) +',
|
|
69
|
+
" '). Run ' +",
|
|
70
|
+
' installCommand +',
|
|
71
|
+
" '.',",
|
|
72
|
+
' );',
|
|
73
|
+
'}',
|
|
74
|
+
'if (',
|
|
75
|
+
' !pointer ||',
|
|
76
|
+
" pointer.product !== 'engineering-memory' ||",
|
|
77
|
+
" typeof pointer.path !== 'string' ||",
|
|
78
|
+
` typeof pointer.${kind} !== 'string'`,
|
|
79
|
+
') {',
|
|
80
|
+
' stop(',
|
|
81
|
+
` 'The Engineering Memory ${label} cannot start: ' +`,
|
|
82
|
+
' pointerPath +',
|
|
83
|
+
" ' does not name a release. Run ' +",
|
|
84
|
+
' installCommand +',
|
|
85
|
+
" '.',",
|
|
86
|
+
' );',
|
|
87
|
+
'}',
|
|
88
|
+
'const releasePath = isAbsolute(pointer.path)',
|
|
89
|
+
' ? pointer.path',
|
|
90
|
+
' : resolve(runtimeRoot, pointer.path);',
|
|
91
|
+
'process.env.ENGINEERING_MEMORY_RUNTIME_CURRENT = pointerPath;',
|
|
92
|
+
`await import(pathToFileURL(join(releasePath, ...pointer.${kind}.split('/'))).href);`,
|
|
93
|
+
'',
|
|
94
|
+
].join('\n');
|
|
95
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "engineering-memory",
|
|
3
|
-
"version": "1.11.
|
|
3
|
+
"version": "1.11.9",
|
|
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",
|
|
@@ -10,6 +10,9 @@ export function loadBridgeConfig(env = process.env) {
|
|
|
10
10
|
apiBaseUrl: (env.ENGINEERING_MEMORY_API_URL ?? 'http://127.0.0.1:3000').replace(/\/$/, ''),
|
|
11
11
|
stateRoot: resolve(env.ENGINEERING_MEMORY_STATE_DIR ?? join(homedir(), '.engineering-memory')),
|
|
12
12
|
clientVersion: env.ENGINEERING_MEMORY_CLIENT_VERSION ?? null,
|
|
13
|
+
runtimeCurrentPath: env.ENGINEERING_MEMORY_RUNTIME_CURRENT
|
|
14
|
+
? resolve(env.ENGINEERING_MEMORY_RUNTIME_CURRENT)
|
|
15
|
+
: null,
|
|
13
16
|
requestTimeoutMs: positiveInteger(env.ENGINEERING_MEMORY_TIMEOUT_MS, 15_000),
|
|
14
17
|
cacheMaxEntries: positiveInteger(env.ENGINEERING_MEMORY_CACHE_MAX_ENTRIES, 64),
|
|
15
18
|
cacheMaxBytes: positiveInteger(env.ENGINEERING_MEMORY_CACHE_MAX_BYTES, 5 * 1024 * 1024),
|
|
@@ -1,88 +1,32 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { dirname, resolve } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
4
|
import { serveStdio } from '@modelcontextprotocol/server/stdio';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
import { createEngineeringMemoryServer } from './mcp/server.js';
|
|
13
|
-
import { RepositoryResolver } from './project/repository.js';
|
|
14
|
-
import { ApiClient } from './runtime/api-client.js';
|
|
15
|
-
import { BridgeService } from './runtime/bridge-service.js';
|
|
16
|
-
import { OfflineOutbox } from './runtime/offline-outbox.js';
|
|
17
|
-
import { ActiveContextStore } from './runtime/active-context-store.js';
|
|
18
|
-
import { RepositoryDecisionStore } from './runtime/repository-decision-store.js';
|
|
19
|
-
import { ShadowNoticeStore } from './runtime/shadow-notice-store.js';
|
|
20
|
-
import { UpdateChoiceStore } from './runtime/update-choice-store.js';
|
|
21
|
-
import { PrincipalStateGuard } from './runtime/principal-state.js';
|
|
22
|
-
import { OnboardingStore } from './runtime/onboarding-store.js';
|
|
23
|
-
import { QuestionnaireStore } from './runtime/questionnaire-store.js';
|
|
24
|
-
export function createBridgeService() {
|
|
5
|
+
import { loadBridgeConfig } from './config.js';
|
|
6
|
+
import { createMcpServer } from './mcp/server.js';
|
|
7
|
+
import { createRuntime } from './runtime/runtime-entry.js';
|
|
8
|
+
import { RuntimeHost, readCurrentRelease } from './runtime/runtime-host.js';
|
|
9
|
+
export { createBridgeService } from './runtime/create-bridge-service.js';
|
|
10
|
+
const releaseRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..', '..');
|
|
11
|
+
export async function createServer() {
|
|
25
12
|
const config = loadBridgeConfig();
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
13
|
+
const server = createMcpServer();
|
|
14
|
+
let version = config.clientVersion;
|
|
15
|
+
const currentPath = config.runtimeCurrentPath;
|
|
16
|
+
if (currentPath) {
|
|
17
|
+
const current = await readCurrentRelease(currentPath);
|
|
18
|
+
if ('release' in current && current.path === releaseRoot)
|
|
19
|
+
version = current.release.version ?? version;
|
|
20
|
+
}
|
|
21
|
+
const host = new RuntimeHost({
|
|
22
|
+
server,
|
|
23
|
+
release: { path: releaseRoot, version, module: { createRuntime } },
|
|
24
|
+
...(currentPath ? { currentPath } : {}),
|
|
25
|
+
log: (message) => process.stderr.write(`${message}\n`),
|
|
32
26
|
});
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
maxBytes: config.cacheMaxBytes,
|
|
37
|
-
maxAgeMs: config.cacheMaxAgeMs,
|
|
38
|
-
});
|
|
39
|
-
const client = new ApiClient({
|
|
40
|
-
baseUrl: config.apiBaseUrl,
|
|
41
|
-
timeoutMs: config.requestTimeoutMs,
|
|
42
|
-
credentials,
|
|
43
|
-
cache,
|
|
44
|
-
refreshPath: endpoints.authRefresh,
|
|
45
|
-
});
|
|
46
|
-
const git = new GitInspector();
|
|
47
|
-
const outbox = new OfflineOutbox(stateRoot);
|
|
48
|
-
const activeContexts = new ActiveContextStore(stateRoot);
|
|
49
|
-
const repositoryDecisions = new RepositoryDecisionStore(stateRoot);
|
|
50
|
-
const updateChoices = new UpdateChoiceStore(stateRoot);
|
|
51
|
-
const shadowNotices = new ShadowNoticeStore(stateRoot);
|
|
52
|
-
const worktreePool = new WorktreePool(stateRoot, {
|
|
53
|
-
pendingWork: async (entry) => {
|
|
54
|
-
if (!entry.taskId)
|
|
55
|
-
return false;
|
|
56
|
-
const pointer = await activeContexts.loadForTask(entry.repoFingerprint, entry.taskId);
|
|
57
|
-
return Boolean(pointer?.verificationIntent ||
|
|
58
|
-
pointer?.closeIntent ||
|
|
59
|
-
(await outbox.listForTask(entry.taskId)).length);
|
|
60
|
-
},
|
|
61
|
-
});
|
|
62
|
-
const gate = new VerificationGate(stateRoot, git, outbox, undefined, worktreePool);
|
|
63
|
-
const principalState = new PrincipalStateGuard(stateRoot, credentials, cache, outbox, activeContexts, gate);
|
|
64
|
-
return new BridgeService({
|
|
65
|
-
worktreePool,
|
|
66
|
-
worktreePolicy: new WorktreePolicyCache(stateRoot),
|
|
67
|
-
client,
|
|
68
|
-
credentials,
|
|
69
|
-
browserAuth: new BrowserAuthCoordinator(client, credentials),
|
|
70
|
-
repositories: new RepositoryResolver(git, config.markerSchemaVersion, stateRoot),
|
|
71
|
-
journal: new JournalStore(stateRoot),
|
|
72
|
-
outbox,
|
|
73
|
-
gate,
|
|
74
|
-
activeContexts,
|
|
75
|
-
repositoryDecisions,
|
|
76
|
-
updateChoices,
|
|
77
|
-
shadowNotices,
|
|
78
|
-
questionnaires: new QuestionnaireStore(stateRoot),
|
|
79
|
-
onboarding: new OnboardingStore(stateRoot),
|
|
80
|
-
clientVersion: config.clientVersion,
|
|
81
|
-
principalState,
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
export function createServer() {
|
|
85
|
-
return createEngineeringMemoryServer(createBridgeService());
|
|
27
|
+
host.start();
|
|
28
|
+
host.watch(15_000);
|
|
29
|
+
return server;
|
|
86
30
|
}
|
|
87
31
|
void serveStdio(createServer);
|
|
88
32
|
//# sourceMappingURL=index.js.map
|
|
@@ -2,17 +2,24 @@ import { registerWorktreeTools } from './worktree-tools.js';
|
|
|
2
2
|
import { McpServer } from '@modelcontextprotocol/server';
|
|
3
3
|
import { registerOnboardingTools } from './onboarding-tools.js';
|
|
4
4
|
import { registerEngineeringMemoryTools, registerQuestionnaireTools } from './tool-definitions.js';
|
|
5
|
-
export
|
|
6
|
-
|
|
5
|
+
export const serverInstructions = 'Start with session.entry and follow the returned lifecycle. Do not block independent task work on memory.propose_revision drafting, submission or approval. If the host permits background agents or concurrent calls, dispatch independent proposals and continue useful work; otherwise checkpoint the draft and defer submission until needed. Checkpoint, correction, reconciliation, self-review and scaffold receipts use the durable local outbox; after deliveryStatus pending, continue independent work without polling or resending. Inventory upload, claim-bound evidence submission and test-result reporting may overlap independent analysis using host-permitted concurrent calls. Wait only when the next operation needs the result; required task deliveries must settle before verification. Preserve task/source identity and native user approval; never treat pending work as verified.';
|
|
6
|
+
export function createMcpServer() {
|
|
7
|
+
return new McpServer({
|
|
7
8
|
name: 'engineering-memory',
|
|
8
9
|
version: '0.1.0',
|
|
9
10
|
}, {
|
|
10
|
-
instructions:
|
|
11
|
+
instructions: serverInstructions,
|
|
11
12
|
});
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
}
|
|
14
|
+
export function registerAllTools(host, service) {
|
|
15
|
+
registerEngineeringMemoryTools(host, service);
|
|
16
|
+
registerQuestionnaireTools(host, service);
|
|
17
|
+
registerOnboardingTools(host, service);
|
|
18
|
+
registerWorktreeTools(host, service);
|
|
19
|
+
}
|
|
20
|
+
export function createEngineeringMemoryServer(service) {
|
|
21
|
+
const server = createMcpServer();
|
|
22
|
+
registerAllTools(server, service);
|
|
16
23
|
return server;
|
|
17
24
|
}
|
|
18
25
|
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export function recordingHost(server) {
|
|
2
|
+
const tools = [];
|
|
3
|
+
const registerTool = (name, config, callback) => {
|
|
4
|
+
tools.push({ name, config, callback });
|
|
5
|
+
return undefined;
|
|
6
|
+
};
|
|
7
|
+
return {
|
|
8
|
+
host: { registerTool: registerTool, server },
|
|
9
|
+
tools,
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=tool-host.js.map
|