moflo 4.13.3 → 4.13.5

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.
@@ -72,6 +72,7 @@ import { isDurableNamespace } from './cherry-pick-learnings.js';
72
72
  import { planReconcile, reconcileId, splitReconcileId, isPrunableTombstone, recordStamp, TOMBSTONE_TTL_MS, } from './durable-reconcile.js';
73
73
  import { readDurableSnapshot, applyDurableActions, } from './durable-store-io.js';
74
74
  import { detectToolCallMarkup } from '../memory/tool-call-markup.js';
75
+ import { isMisfiledDurable } from './durable-key-rules.js';
75
76
  /** Allowed `type` values — the schema CHECK set. An out-of-set value would make
76
77
  * INSERT OR IGNORE silently drop a hand-edited artifact row, so we coerce. */
77
78
  const VALID_TYPES = new Set([
@@ -208,8 +209,9 @@ function readArtifact(artifactPath) {
208
209
  const lines = new Map();
209
210
  const records = new Map();
210
211
  let malformed = 0;
212
+ let misfiled = 0;
211
213
  if (!fs.existsSync(artifactPath))
212
- return { lines, records, malformed, existed: false };
214
+ return { lines, records, malformed, misfiled, existed: false };
213
215
  const raw = fs.readFileSync(artifactPath, 'utf-8');
214
216
  for (const line of raw.split(/\r?\n/)) {
215
217
  const trimmed = line.trim();
@@ -222,6 +224,13 @@ function readArtifact(artifactPath) {
222
224
  }
223
225
  const id = lineId(parsed);
224
226
  const record = lineToRecord(parsed);
227
+ // Dropped at the read both directions share, so import never applies one
228
+ // and export rewrites the file without it — the artifact shrinks instead of
229
+ // re-seeding every teammate's `learnings` on their next session (#1495).
230
+ if (isMisfiledDurable(record.namespace, record.key)) {
231
+ misfiled++;
232
+ continue;
233
+ }
225
234
  const existing = records.get(id);
226
235
  // Newest wins, ties keep the first. Built on the merge rule's own
227
236
  // comparison basis so the two cannot disagree about which of a live line
@@ -231,7 +240,7 @@ function readArtifact(artifactPath) {
231
240
  lines.set(id, parsed);
232
241
  records.set(id, record);
233
242
  }
234
- return { lines, records, malformed, existed: true };
243
+ return { lines, records, malformed, misfiled, existed: true };
235
244
  }
236
245
  /**
237
246
  * Serialise lines to JSONL, sorted by the (namespace, key) each line is about —
@@ -296,8 +305,15 @@ function payloadFromLine(id, line) {
296
305
  tags: line.tags ? JSON.stringify(line.tags) : null,
297
306
  metadata: JSON.stringify({ provenance: line.provenance, sharedFrom: 'team-artifact' }),
298
307
  ownerId: line.provenance?.author || null,
299
- // created_at/updated_at are INTEGER NOT NULL — never bind null.
300
- createdAt: typeof line.created_at === 'number' ? line.created_at : Date.now(),
308
+ // created_at/updated_at are INTEGER NOT NULL — never bind null. A line with
309
+ // no created_at falls back to its edit time, never the import time: stamping
310
+ // now makes a re-imported old entry read as a new learning, which is what
311
+ // inflated growth metrics after every re-seed (#1495).
312
+ createdAt: typeof line.created_at === 'number'
313
+ ? line.created_at
314
+ : typeof line.updated_at === 'number'
315
+ ? line.updated_at
316
+ : Date.now(),
301
317
  // The row gets the best timestamp available even when the RECORD compared
302
318
  // as 0 (a pre-#1463 line): 0 governs only who wins the merge, while the row
303
319
  // itself should carry the most accurate time we have.
@@ -326,7 +342,7 @@ export function exportTeamArtifact(opts) {
326
342
  const parsedSharedAt = Date.parse(opts.sharedAt);
327
343
  const now = opts.now ?? (Number.isNaN(parsedSharedAt) ? Date.now() : parsedSharedAt);
328
344
  const ttlMs = opts.tombstoneTtlMs ?? TOMBSTONE_TTL_MS;
329
- const { lines, records: target, malformed, existed } = readArtifact(opts.artifactPath);
345
+ const { lines, records: target, malformed, misfiled, existed } = readArtifact(opts.artifactPath);
330
346
  const provenance = {
331
347
  author: resolveAuthor(projectRoot),
332
348
  source: resolveSource(),
@@ -418,7 +434,7 @@ export function exportTeamArtifact(opts) {
418
434
  }
419
435
  // Skip the write when the merge changed nothing AND the file already exists:
420
436
  // a git-tracked artifact should not show up as modified after a no-op run.
421
- const changed = actions.length > 0 || prunedTombstones > 0 || backfilled > 0;
437
+ const changed = actions.length > 0 || prunedTombstones > 0 || backfilled > 0 || misfiled > 0;
422
438
  const wrote = changed || !existed;
423
439
  if (wrote) {
424
440
  fs.mkdirSync(path.dirname(opts.artifactPath), { recursive: true });
@@ -436,6 +452,7 @@ export function exportTeamArtifact(opts) {
436
452
  backfilled,
437
453
  skippedMalformed: malformed,
438
454
  skippedCorrupt,
455
+ droppedMisfiled: misfiled,
439
456
  total: live,
440
457
  tombstones,
441
458
  wrote,
@@ -467,9 +484,11 @@ export function importTeamArtifact(opts) {
467
484
  skippedMalformed: 0,
468
485
  skippedNonDurable: 0,
469
486
  skippedCorrupt: 0,
487
+ skippedMisfiled: 0,
470
488
  };
471
- const { lines, records: parsed, malformed } = readArtifact(opts.artifactPath);
489
+ const { lines, records: parsed, malformed, misfiled } = readArtifact(opts.artifactPath);
472
490
  report.skippedMalformed = malformed;
491
+ report.skippedMisfiled = misfiled;
473
492
  if (lines.size === 0)
474
493
  return report;
475
494
  const source = new Map();
@@ -2,5 +2,5 @@
2
2
  * Auto-generated by build. Do not edit manually.
3
3
  * Source of truth: root package.json → scripts/sync-version.mjs
4
4
  */
5
- export const VERSION = '4.13.3';
5
+ export const VERSION = '4.13.5';
6
6
  //# sourceMappingURL=version.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "moflo",
3
- "version": "4.13.3",
3
+ "version": "4.13.5",
4
4
  "description": "MoFlo — AI agent orchestration for Claude Code. A standalone, opinionated toolkit with semantic memory, learned routing, gates, spells, and the /flo issue-execution skill.",
5
5
  "main": "dist/src/cli/index.js",
6
6
  "type": "module",
@@ -99,7 +99,7 @@
99
99
  "@typescript-eslint/parser": "^8.65.0",
100
100
  "eslint": "^10.8.0",
101
101
  "glob": "^11.1.0",
102
- "moflo": "^4.13.2",
102
+ "moflo": "^4.13.4",
103
103
  "tsx": "^4.21.0",
104
104
  "typescript": "^5.9.3",
105
105
  "vitest": "^4.0.0"