engineering-memory 0.2.2 → 0.2.3

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.
@@ -107,8 +107,29 @@ export async function installEngineeringMemory(options = {}) {
107
107
  dispatcherSection: dispatcherSections.claude,
108
108
  },
109
109
  };
110
+ const mcpPlans = await planMcpRegistrations({
111
+ selectedClients,
112
+ bridgeEntry,
113
+ nodePath,
114
+ apiUrl,
115
+ clientVersion,
116
+ state: existingState,
117
+ commandRunner,
118
+ clientsWereRequested: options.selectedClients !== undefined,
119
+ });
120
+ const absentClients = mcpPlans
121
+ .filter((plan) => plan.action === 'absent')
122
+ .map((plan) => plan.clientName);
123
+ const installedClients = selectedClients.filter(
124
+ (clientName) => !absentClients.includes(clientName),
125
+ );
126
+ if (installedClients.length === 0) {
127
+ throw new Error(
128
+ `None of the supported agent CLIs are on PATH: ${selectedClients.join(', ')}. Install one, then rerun the installer.`,
129
+ );
130
+ }
110
131
  const clientPlans = await Promise.all(
111
- selectedClients.map(async (clientName) => {
132
+ installedClients.map(async (clientName) => {
112
133
  const target = clientTargets[clientName];
113
134
  return {
114
135
  clientName,
@@ -120,15 +141,6 @@ export async function installEngineeringMemory(options = {}) {
120
141
  };
121
142
  }),
122
143
  );
123
- const mcpPlans = await planMcpRegistrations({
124
- selectedClients,
125
- bridgeEntry,
126
- nodePath,
127
- apiUrl,
128
- clientVersion,
129
- state: existingState,
130
- commandRunner,
131
- });
132
144
  const hookPlan = await planGitHook({
133
145
  repoRoot: options.repoRoot ? resolve(options.repoRoot) : null,
134
146
  mode: options.hookMode ?? 'cancel',
@@ -189,7 +201,8 @@ export async function installEngineeringMemory(options = {}) {
189
201
  statePath,
190
202
  skillPaths: clientPlans.map((plan) => plan.skillPath),
191
203
  dispatcherPaths: clientPlans.map((plan) => plan.dispatcherPath),
192
- selectedClients,
204
+ selectedClients: installedClients,
205
+ absentClients,
193
206
  apiUrl,
194
207
  clientVersion,
195
208
  runtimePath,
@@ -48,6 +48,7 @@ export async function planMcpRegistrations({
48
48
  clientVersion,
49
49
  state,
50
50
  commandRunner,
51
+ clientsWereRequested = true,
51
52
  }) {
52
53
  const plans = [];
53
54
  for (const clientName of selectedClients) {
@@ -55,6 +56,10 @@ export async function planMcpRegistrations({
55
56
  if (!client) throw new Error(`Unsupported client: ${clientName}`);
56
57
  const version = await commandRunner(client.executable, ['--version']);
57
58
  if (version.code !== 0) {
59
+ if (!clientsWereRequested && !state?.mcp?.[clientName]) {
60
+ plans.push({ clientName, client, action: 'absent' });
61
+ continue;
62
+ }
58
63
  throw new Error(
59
64
  `${clientName} CLI is unavailable. Install its native CLI, ensure it is on PATH, then rerun the installer. ${commandFailure(client.executable, version)}`,
60
65
  );
@@ -121,6 +126,7 @@ export async function planMcpRegistrations({
121
126
  export async function applyMcpRegistrations(plans, commandRunner, transaction) {
122
127
  const result = {};
123
128
  for (const plan of plans) {
129
+ if (plan.action === 'absent') continue;
124
130
  if (plan.action === 'add') {
125
131
  await addRegistration(plan, plan.registration, commandRunner);
126
132
  transaction.add(() => removeRegistration(plan, commandRunner));
@@ -211,6 +217,19 @@ function registrationFingerprint(clientName, registration) {
211
217
  .digest('hex');
212
218
  }
213
219
 
220
+ function fingerprintBeforeClientVersion(clientName, registration) {
221
+ return createHash('sha256')
222
+ .update(
223
+ JSON.stringify({
224
+ clientName,
225
+ nodePath: registration.nodePath,
226
+ bridgeEntry: registration.bridgeEntry,
227
+ apiUrl: registration.apiUrl ?? null,
228
+ }),
229
+ )
230
+ .digest('hex');
231
+ }
232
+
214
233
  function sameRegistration(left, right) {
215
234
  return (
216
235
  left?.nodePath === right.nodePath &&
@@ -462,9 +481,13 @@ function isOwnedRegistration(clientName, managed) {
462
481
  typeof managed.bridgeEntry === 'string'
463
482
  );
464
483
  }
484
+ if (typeof managed.apiUrl !== 'string') return false;
485
+ if (managed.fingerprint === registrationFingerprint(clientName, managed)) {
486
+ return true;
487
+ }
465
488
  return (
466
- typeof managed.apiUrl === 'string' &&
467
- managed.fingerprint === registrationFingerprint(clientName, managed)
489
+ managed.clientVersion === undefined &&
490
+ managed.fingerprint === fingerprintBeforeClientVersion(clientName, managed)
468
491
  );
469
492
  }
470
493
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "engineering-memory",
3
- "version": "0.2.2",
3
+ "version": "0.2.3",
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",