engineering-memory 0.4.0 → 1.0.1

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.4.0",
3
+ "version": "1.0.1",
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",
@@ -4,6 +4,7 @@ import { assertManagedPath, canonicalPath } from '../utilities/files.js';
4
4
  import { sha256, stableStringify } from '../utilities/hash.js';
5
5
  import { NativeCommandRunner } from '../utilities/process.js';
6
6
  export const temporaryScaffoldingMarker = 'ENGINEERING-MEMORY-TEMPORARY';
7
+ export const temporaryScaffoldingPattern = `${temporaryScaffoldingMarker}:`;
7
8
  export class GitInspector {
8
9
  runner;
9
10
  constructor(runner = new NativeCommandRunner()) {
@@ -303,7 +304,7 @@ export class GitInspector {
303
304
  return [...found.values()].sort((left, right) => left.path === right.path ? left.line - right.line : left.path.localeCompare(right.path));
304
305
  }
305
306
  async markerMatches(root, scope, origin) {
306
- const result = await this.runner.run('git', ['grep', '--no-color', '-n', '-I', '-F', ...scope, '-e', temporaryScaffoldingMarker], { cwd: root });
307
+ const result = await this.runner.run('git', ['grep', '--no-color', '-n', '-I', '-F', ...scope, '-e', temporaryScaffoldingPattern], { cwd: root });
307
308
  if (result.exitCode !== 0)
308
309
  return [];
309
310
  return result.stdout
@@ -380,8 +380,22 @@ export function registerEngineeringMemoryTools(server, service) {
380
380
  initialProjectProfile: z.object({
381
381
  title: z.string().min(2),
382
382
  content: z.string().min(2),
383
- screenPathPatterns: z.array(z.string().min(1)).min(1),
384
- componentPathPatterns: z.array(z.string().min(1)).min(1),
383
+ discoveryUnits: z
384
+ .array(z.object({
385
+ kind: z.enum([
386
+ 'screen_logic',
387
+ 'component_mapping',
388
+ 'module_logic',
389
+ 'data_model',
390
+ 'api_endpoint',
391
+ ]),
392
+ patterns: z.array(z.string().min(1)).min(1),
393
+ }))
394
+ .min(1)
395
+ .optional()
396
+ .describe('What this project calls its units and where they live. A Flutter project declares screen_logic and component_mapping; a backend declares module_logic, data_model and api_endpoint.'),
397
+ screenPathPatterns: z.array(z.string().min(1)).min(1).optional(),
398
+ componentPathPatterns: z.array(z.string().min(1)).min(1).optional(),
385
399
  metadata: jsonObject,
386
400
  provenance: jsonObject,
387
401
  }),
@@ -1129,8 +1129,7 @@ export class BridgeService {
1129
1129
  !policy ||
1130
1130
  !marker ||
1131
1131
  marker.projectId !== project.id ||
1132
- stringArray(policy.screenPathPatterns).length === 0 ||
1133
- stringArray(policy.componentPathPatterns).length === 0) {
1132
+ readDiscoveryUnits(policy).length === 0) {
1134
1133
  throw new Error('Project setup response is not policy-ready');
1135
1134
  }
1136
1135
  const markerPath = await this.dependencies.repositories.writeMarker(repository.repoRoot, project.id);
@@ -2369,18 +2368,11 @@ export function newMemoryResourceCandidate(entry, policy) {
2369
2368
  return [];
2370
2369
  }
2371
2370
  if (policy) {
2372
- const screen = policy.screenPathPatterns.some((pattern) => globMatches(entry.path, pattern));
2373
- const component = policy.componentPathPatterns.some((pattern) => globMatches(entry.path, pattern));
2374
- if (screen && component) {
2371
+ const matched = policy.units.filter((unit) => unit.patterns.some((pattern) => globMatches(entry.path, pattern)));
2372
+ if (matched.length > 1) {
2375
2373
  throw new Error(`Resource discovery policy is ambiguous for path: ${entry.path}`);
2376
2374
  }
2377
- if (screen) {
2378
- return [{ path: entry.path, kind: 'screen_logic' }];
2379
- }
2380
- if (component) {
2381
- return [{ path: entry.path, kind: 'component_mapping' }];
2382
- }
2383
- return [];
2375
+ return matched[0] ? [{ path: entry.path, kind: matched[0].kind }] : [];
2384
2376
  }
2385
2377
  const extension = entry.path.toLowerCase().match(/\.[^.\/]+$/)?.[0];
2386
2378
  if (!extension ||
@@ -2420,12 +2412,32 @@ function readResourceDiscoveryPolicy(snapshot, activeLease) {
2420
2412
  if (!discovery) {
2421
2413
  return null;
2422
2414
  }
2423
- const screenPathPatterns = stringArray(discovery.screenPathPatterns);
2424
- const componentPathPatterns = stringArray(discovery.componentPathPatterns);
2425
- if (screenPathPatterns.length === 0 || componentPathPatterns.length === 0) {
2415
+ const units = readDiscoveryUnits(discovery);
2416
+ if (units.length === 0) {
2426
2417
  throw new Error('Pinned resource discovery policy is incomplete');
2427
2418
  }
2428
- return { screenPathPatterns, componentPathPatterns };
2419
+ return { units };
2420
+ }
2421
+ function readDiscoveryUnits(discovery) {
2422
+ if (!discovery)
2423
+ return [];
2424
+ const declared = Array.isArray(discovery.units) ? discovery.units : null;
2425
+ if (declared) {
2426
+ return declared.flatMap((entry) => {
2427
+ const unit = objectValue(entry);
2428
+ const patterns = stringArray(unit?.patterns);
2429
+ return typeof unit?.kind === 'string' && patterns.length > 0
2430
+ ? [{ kind: unit.kind, patterns }]
2431
+ : [];
2432
+ });
2433
+ }
2434
+ return [
2435
+ { kind: 'screen_logic', patterns: stringArray(discovery.screenPathPatterns) },
2436
+ {
2437
+ kind: 'component_mapping',
2438
+ patterns: stringArray(discovery.componentPathPatterns),
2439
+ },
2440
+ ].filter((unit) => unit.patterns.length > 0);
2429
2441
  }
2430
2442
  function stringArray(value) {
2431
2443
  if (!Array.isArray(value) || value.some((entry) => typeof entry !== 'string')) {
@@ -58,13 +58,15 @@ target inferred only from surrounding code. Carry both node ids into the discove
58
58
  checkpoint as evidence. A defect in a calculation, a response shape or a service has no
59
59
  answer in the design; do not spend the read there.
60
60
 
61
+ Name the areas this task works in, against the coverage map the core pack carries. Where an area it touches is marked as having no rule, that is not a reason to stop — it is a finding to raise at self review, so the absence is reported by the product rather than found later by whoever inherits the code.
62
+
61
63
  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.
62
64
 
63
65
  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.
64
66
 
65
67
  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.
66
68
 
67
- 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.
69
+ 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` immediately followed by a colon and 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.
68
70
 
69
71
  Do not write task Markdown files directly. The bridge owns event IDs, expected task versions, atomic projections, outbox state, and synchronization.
70
72
 
@@ -106,7 +108,9 @@ Before validation, read the changed code back against the rules that govern it.
106
108
 
107
109
  Record `task.self_review` naming the resources reviewed and, for every conflict found, the file, the rule, what was wrong and what was done about it. A review that found nothing records an empty finding list, which is a claim about the work rather than a formality.
108
110
 
109
- `task.verify` refuses without a self review of the current diff. Editing after the review invalidates it, which is the point: the last thing that happens to the code is that someone read it against the rules. This exists because a task once shipped code that broke rules it had been given the rules were present and correct, and nothing in the lifecycle ever asked whether the result matched them.
111
+ An area the task worked in that the coverage map marks as having no rule is recorded as a finding too, naming the area and what was decided in its absence. That is how the product learns which rule to write next: an area nothing has needed yet can wait, and one a real task just had to improvise in cannot.
112
+
113
+ This applies to a write task. A read-only task changed nothing, so there is nothing to read back against the rules and `task.self_review` is refused for it; go straight to verification. `task.verify` refuses a write task without a self review of the current diff. Editing after the review invalidates it, which is the point: the last thing that happens to the code is that someone read it against the rules. This exists because a task once shipped code that broke rules it had been given — the rules were present and correct, and nothing in the lifecycle ever asked whether the result matched them.
110
114
 
111
115
  The review is the agent's own job at the end of the work. Do not wait to be asked for it, and do not treat a passing test suite as a substitute: tests leave a receipt and readability does not, which is exactly why the unreviewed one is the one that degrades.
112
116