engineering-memory 1.10.1 → 1.10.2

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.10.1",
3
+ "version": "1.10.2",
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",
@@ -59,19 +59,24 @@ export class RepositoryResolver {
59
59
  projectId: explicitProjectId ?? binding?.projectId ?? null,
60
60
  schemaVersion: binding?.schemaVersion ?? null,
61
61
  repoFingerprint,
62
+ legacyFingerprints: binding ? [] : [await this.git.fingerprint(repoRoot, 1)],
62
63
  git,
63
64
  };
64
65
  }
65
- async writeBinding(repoRoot, projectId) {
66
+ async writeBinding(repoRoot, projectId, fingerprint) {
66
67
  assertProjectId(projectId);
67
68
  const repository = await this.resolve(repoRoot, projectId);
69
+ const older = repository.legacyFingerprints.includes(fingerprint);
70
+ if (fingerprint !== repository.repoFingerprint && !older) {
71
+ throw new Error('The confirmed project fingerprint does not identify this repository');
72
+ }
68
73
  const repositoryKey = await this.keyFor(repository.repoRoot);
69
74
  const bindingPath = this.pathFor(repositoryKey);
70
75
  await writeJson(bindingPath, {
71
76
  projectId,
72
- schemaVersion: repository.schemaVersion ?? this.markerSchemaVersion,
77
+ schemaVersion: older ? 1 : (repository.schemaVersion ?? this.markerSchemaVersion),
73
78
  repositoryKey,
74
- repoFingerprint: repository.repoFingerprint,
79
+ repoFingerprint: fingerprint,
75
80
  }, this.stateRoot);
76
81
  return bindingPath;
77
82
  }
@@ -70,7 +70,10 @@ export class BridgeService {
70
70
  if (!repository.bindingPath) {
71
71
  const resolved = await this.dependencies.client.request(endpoints.projectResolve, {
72
72
  method: 'POST',
73
- body: { repoFingerprint: repository.repoFingerprint },
73
+ body: {
74
+ repoFingerprint: repository.repoFingerprint,
75
+ legacyFingerprints: repository.legacyFingerprints,
76
+ },
74
77
  });
75
78
  const project = objectValue(resolved.data);
76
79
  const restoreRequired = Boolean(project?.archivedAt);
@@ -1361,6 +1364,7 @@ export class BridgeService {
1361
1364
  projectName: input.projectName,
1362
1365
  projectSlug: input.projectSlug ?? slugify(input.projectName),
1363
1366
  repoFingerprint: repository.repoFingerprint,
1367
+ legacyFingerprints: repository.legacyFingerprints,
1364
1368
  framework: input.framework,
1365
1369
  developmentArea: input.developmentArea,
1366
1370
  figmaConfig: input.figmaConfig,
@@ -1384,6 +1388,7 @@ export class BridgeService {
1384
1388
  const marker = objectValue(data?.marker);
1385
1389
  if (!project ||
1386
1390
  typeof project.id !== 'string' ||
1391
+ typeof project.repoFingerprint !== 'string' ||
1387
1392
  !profileResource ||
1388
1393
  !profileRevision ||
1389
1394
  !policy ||
@@ -1392,7 +1397,7 @@ export class BridgeService {
1392
1397
  readDiscoveryUnits(policy).length === 0) {
1393
1398
  throw refuse('Project setup response is not policy-ready', 'project.setup');
1394
1399
  }
1395
- const bindingPath = await this.dependencies.repositories.writeBinding(repository.repoRoot, project.id);
1400
+ const bindingPath = await this.dependencies.repositories.writeBinding(repository.repoRoot, project.id, project.repoFingerprint);
1396
1401
  return asJsonValue({ ...data, marker: null, markerPath: null, bindingPath });
1397
1402
  });
1398
1403
  }
@@ -1689,13 +1694,18 @@ export class BridgeService {
1689
1694
  }
1690
1695
  const response = await this.dependencies.client.request(endpoints.projectBind(input.projectId), {
1691
1696
  method: 'POST',
1692
- body: { repoFingerprint: repository.repoFingerprint },
1697
+ body: {
1698
+ repoFingerprint: repository.repoFingerprint,
1699
+ legacyFingerprints: repository.legacyFingerprints,
1700
+ },
1693
1701
  });
1694
1702
  const project = objectValue(response.data);
1695
- if (!project || project.id !== input.projectId) {
1703
+ if (!project ||
1704
+ project.id !== input.projectId ||
1705
+ typeof project.repoFingerprint !== 'string') {
1696
1706
  throw refuse('Project bind response does not match the selected project', 'project.resolve');
1697
1707
  }
1698
- const bindingPath = await this.dependencies.repositories.writeBinding(repository.repoRoot, input.projectId);
1708
+ const bindingPath = await this.dependencies.repositories.writeBinding(repository.repoRoot, input.projectId, project.repoFingerprint);
1699
1709
  return asJsonValue({
1700
1710
  resolved: true,
1701
1711
  bound: true,
@@ -1708,7 +1718,10 @@ export class BridgeService {
1708
1718
  }
1709
1719
  const response = await this.dependencies.client.request(endpoints.projectResolve, {
1710
1720
  method: 'POST',
1711
- body: { repoFingerprint: repository.repoFingerprint },
1721
+ body: {
1722
+ repoFingerprint: repository.repoFingerprint,
1723
+ legacyFingerprints: repository.legacyFingerprints,
1724
+ },
1712
1725
  });
1713
1726
  const project = objectValue(response.data);
1714
1727
  const restoreRequired = Boolean(project?.archivedAt);
@@ -4,7 +4,7 @@
4
4
 
5
5
  Sign-in decides nothing beyond who the user is. The organization and the project are chosen after it, through the questionnaires in `questionnaires.md`, and both listings end with an option to create a new one. Ask for both whenever this session has not already confirmed them, and ask again the moment the user says they want to change either — changing the organization always means choosing the project again.
6
6
 
7
- Use the binding returned by `session.entry` as the authority. The bridge stores the selected project and repository identity under `~/.engineering-memory/origins/<api-hash>/project-bindings/`, outside the installed runtime and working tree. It imports a legacy `.engineering-memory/project.json` once, preserving its schema and exact backend fingerprint for existing tasks and receipts. It leaves that file unchanged; after migration the file is optional and may be removed through the repository's normal Git workflow. New bindings never create it. Never infer a selection from a directory or remote, and never edit binding records by hand. Separate clones or a changed remote require a new explicit selection; worktrees of the same checkout share the local binding.
7
+ Use the binding returned by `session.entry` as the authority. The bridge stores the selected project and repository identity under `~/.engineering-memory/origins/<api-hash>/project-bindings/`, outside the installed runtime and working tree. It imports a legacy `.engineering-memory/project.json` once, preserving its schema and exact backend fingerprint for existing tasks and receipts. It leaves that file unchanged; after migration the file is optional and may be removed through the repository's normal Git workflow. New bindings never create it. A checkout that never imported the marker still finds a project bound under the older identity: the bridge presents that identity alongside the current one and stores whichever the backend confirms. Never infer a selection from a directory or remote, and never edit binding records by hand. Separate clones or a changed remote require a new explicit selection; worktrees of the same checkout share the local binding.
8
8
 
9
9
  An archived project is recoverable state, not a missing or conflicting binding. When an owner receives `project.restore`, use the `projectId` and `expectedVersion` in its data and call that operation before retrying. A non-owner receives `project.member_list` instead: list the members, identify a project owner and explain that the owner must restore the exact project before this session can retry. Use `project.list` with `includeArchived: true` when an owner must first select the archived project. Never create or bind a replacement project to escape archive state.
10
10