engineering-memory 0.2.5 → 0.3.0

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": "0.2.5",
3
+ "version": "0.3.0",
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",
@@ -25,8 +25,26 @@ export const validationIds = [
25
25
  export class BridgeService {
26
26
  dependencies;
27
27
  taskQueues = new Map();
28
+ deferDeliveries;
29
+ backgroundDelivery = Promise.resolve();
28
30
  constructor(dependencies) {
29
31
  this.dependencies = dependencies;
32
+ this.deferDeliveries = dependencies.deferDeliveries ?? true;
33
+ }
34
+ deliverInBackground() {
35
+ if (!this.deferDeliveries)
36
+ return;
37
+ this.backgroundDelivery = this.backgroundDelivery.then(async () => {
38
+ try {
39
+ await this.flushOutbox();
40
+ }
41
+ catch {
42
+ return;
43
+ }
44
+ });
45
+ }
46
+ async settleDeliveries() {
47
+ await this.backgroundDelivery;
30
48
  }
31
49
  async sessionBootstrap(input) {
32
50
  return await this.execute(async () => {
@@ -676,15 +694,31 @@ export class BridgeService {
676
694
  }));
677
695
  assertSafeToPersist(cleanJson({ entries }));
678
696
  const snapshot = await this.activeTaskSnapshot(input.taskId, undefined, input.repoRoot);
697
+ const body = cleanJson({
698
+ taskId: input.taskId,
699
+ entries,
700
+ expectedTaskVersion: snapshot.taskVersion,
701
+ });
702
+ await this.dependencies.gate.invalidateTask(input.taskId);
703
+ if (this.deferDeliveries) {
704
+ const queued = await this.dependencies.outbox.enqueue({
705
+ operation: 'task.reconcile',
706
+ method: 'POST',
707
+ path: endpoints.taskReconcile,
708
+ body,
709
+ });
710
+ this.deliverInBackground();
711
+ return asJsonValue({
712
+ reconciled: entries.map((entry) => entry.resourceId),
713
+ queued: true,
714
+ outboxId: queued.id,
715
+ deliveryStatus: 'pending',
716
+ });
717
+ }
679
718
  const response = await this.dependencies.client.request(endpoints.taskReconcile, {
680
719
  method: 'POST',
681
- body: cleanJson({
682
- taskId: input.taskId,
683
- entries,
684
- expectedTaskVersion: snapshot.taskVersion,
685
- }),
720
+ body,
686
721
  });
687
- await this.dependencies.gate.invalidateTask(input.taskId);
688
722
  return asJsonValue({ reconciliations: response.data });
689
723
  });
690
724
  }
@@ -1363,7 +1397,10 @@ export class BridgeService {
1363
1397
  assertSafeToPersist(cleanJson(safeInput));
1364
1398
  return await this.taskExclusive(input.taskId, async () => {
1365
1399
  await this.recoverJournalOutbox(input.projectId, input.taskSlug);
1366
- await this.flushOutbox();
1400
+ if (this.deferDeliveries)
1401
+ this.deliverInBackground();
1402
+ else
1403
+ await this.flushOutbox();
1367
1404
  const journalInput = {
1368
1405
  eventId: idempotencyKey,
1369
1406
  taskId: input.taskId,
@@ -1426,7 +1463,8 @@ export class BridgeService {
1426
1463
  await this.dependencies.activeContexts.updateTaskSnapshot(input.taskId, expectedTaskVersion + 1, pointer.lastSequence + 1);
1427
1464
  }
1428
1465
  const predecessorEntries = taskEntries.filter((entry) => entry.id !== queued.id);
1429
- if (predecessorEntries.length > 0) {
1466
+ if (predecessorEntries.length > 0 || this.deferDeliveries) {
1467
+ this.deliverInBackground();
1430
1468
  return asJsonValue({
1431
1469
  local: { directory: staged.directory, applied: staged.applied },
1432
1470
  queued: true,
@@ -26,7 +26,7 @@ A request can be as short as "add the KYC flow from Figma". That is enough, and
26
26
 
27
27
  1. The Figma address arrives in the core pack as the `figma_reference` record. Never ask where the design lives; read it. If the user names a different file, that is a correction to that record — propose the revision and ask for approval in the same reply, so the next session already knows.
28
28
  2. Ask for existing flows with `memory.query` naming the `flow_logic` kind. If the flow is already recorded, read it and its history before touching anything.
29
- 3. Read the flow in Figma: which frames belong to it, the order its prototype links imply, and the node id of every screen. A file-level link is orientation, not evidence.
29
+ 3. Read the flow in Figma, at orientation depth only: which frames belong to it, the order its prototype links imply, and the node id of every screen. A file-level link is orientation, not evidence. Stop there — geometry, fills, typography and tokens belong to the screen being built, read at the moment it is built, not gathered for the whole flow up front. Read this yourself rather than delegating it; nothing else can proceed until it is answered, so a second agent only adds the wait.
30
30
  4. Read what surrounds it — the screen records the flow starts from and returns to, the navigation contract, the trackers that already exist — and pull `memory.history` wherever the header shows earlier work or a run of corrections.
31
31
  5. Decide the entry point, the order, whether a tracker is needed and exactly what it carries, and where the flow ends.
32
32
  6. Ask the user what neither Figma nor memory can answer. Where a flow is entered from and what abandoning it halfway does are almost never in the design. Never invent them, and never invent the content of a document the flow displays.
@@ -45,7 +45,9 @@ answer in the design; do not spend the read there.
45
45
 
46
46
  Do not pull history for everything. Pull it for the records the task actually touches, and for anything the header shows a surprising number of corrections on. Record `task.checkpoint` with type `discovery` and update STATE, DECISIONS, DISCOVERY, and HANDOFF projections through the bridge.
47
47
 
48
- Send calls that do not feed each other in one batch rather than one at a time: reads of any kind, and proposals for different records. Reconcile every record the task touched in a single `task.reconcile` call with `entries`, not one call per record. Keep in order only what moves the task version, and what genuinely waits on another answer.
48
+ Send calls that do not feed each other in one batch rather than one at a time: reads of any kind, and proposals for different records. Reconcile every record the task touched in a single `task.reconcile` call with `entries`, not one call per record.
49
+
50
+ Checkpoints, recorded corrections and reconciliations return before the backend has them, reporting `deliveryStatus: 'pending'` with the task version they will occupy. That is a completed call, not a pending one: the journal is already durable, delivery is already under way, and `task.verify` refuses while anything remains undelivered. Do not wait for it, poll it, or send it again. Wait only for what the next step uses — a proposal's identifiers, an approval's revision, a prepared lease, and verification itself.
49
51
 
50
52
  Temporary code written to reach or force a path — a pinned state, a fixed service response, a jump straight to the screen — is allowed and expected, carries the marker `ENGINEERING-MEMORY-TEMPORARY` with its reason, and is removed before verification. `task.verify` refuses while any marker is in the tree and names every line, and the commit gate refuses while one is staged.
51
53
 
@@ -22,4 +22,12 @@ Screen logic is canonical in the backend. A screen revision should cover purpose
22
22
 
23
23
  Component mappings should cover the Flutter symbol and path, public API, purpose, states, design tokens, responsive behavior, Figma file and exact node IDs, assets, usage guidance, tests, selectors, and evidence. Mark missing or partial Figma evidence instead of inventing node IDs.
24
24
 
25
+ A backend project records three kinds instead of screens and components, and which kinds a project has is declared in its project profile rather than assumed.
26
+
27
+ A `module_logic` record covers one resource: what it is for, the entities it owns, the endpoints it exposes, the guards that protect them, the background jobs and queues it runs, what it depends on and why that dependency exists.
28
+
29
+ A `data_model` record covers one entity: its columns and their types, its relations, what it inherits from the base entity, and every index it declares **with the query that index exists to serve**. An index nobody can name a query for is reported as a finding rather than recorded as a fact.
30
+
31
+ An `api_endpoint` record covers one route and is the surface a client binds to: method and path, the request DTO, the response DTO, the guards, the error codes it can return, the localization keys it uses, its pagination shape, and which client consumes it. When a linked project exists, these are the records that cross to it, so they are written for a reader on the other side of the wire rather than for the person who wrote the controller.
32
+
25
33
  Do not send full source trees, generated files, vendor assets, raw diffs, credentials, or user data as memory content. Prefer relative paths, symbols, hashes, contract summaries, exact approved Figma identifiers, and bounded task-specific evidence.