dotmem 0.0.4 → 0.0.7

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/cli.cjs CHANGED
@@ -6271,8 +6271,8 @@ var init_user_identity = __esm({
6271
6271
  "core/knowledge/user-identity.ts"() {
6272
6272
  USER_IDENTITY_KEY = "__dotmem_user_identity__";
6273
6273
  identityStore = null;
6274
- configureUserIdentityStore = /* @__PURE__ */ __name((store5) => {
6275
- identityStore = store5;
6274
+ configureUserIdentityStore = /* @__PURE__ */ __name((store6) => {
6275
+ identityStore = store6;
6276
6276
  }, "configureUserIdentityStore");
6277
6277
  requireStore = /* @__PURE__ */ __name(() => {
6278
6278
  if (!identityStore) {
@@ -8697,6 +8697,19 @@ async function readFile6(path3) {
8697
8697
  return null;
8698
8698
  }
8699
8699
  }
8700
+ async function writeFile6(path3, content) {
8701
+ const fs3 = await import('fs/promises');
8702
+ const dir = path3.replace(/[\\/][^\\/]+$/, "");
8703
+ await fs3.mkdir(dir, { recursive: true });
8704
+ const temporary = `${path3}.${process.pid}.${Date.now().toString(36)}.tmp`;
8705
+ try {
8706
+ await fs3.writeFile(temporary, content, "utf8");
8707
+ await fs3.rename(temporary, path3);
8708
+ } catch {
8709
+ await fs3.rm(temporary, { force: true }).catch(() => void 0);
8710
+ throw new Error(`Failed to write exemplars file at ${path3}`);
8711
+ }
8712
+ }
8700
8713
  function emptyStore() {
8701
8714
  return { version: EXEMPLARS_FORMAT_VERSION, exemplars: {} };
8702
8715
  }
@@ -8761,9 +8774,53 @@ async function loadExemplars() {
8761
8774
  return emptyStore();
8762
8775
  }
8763
8776
  }
8777
+ async function saveExemplars(store6) {
8778
+ const validated = validateStore(store6);
8779
+ await writeFile6(resolveExemplarsPath(), JSON.stringify(validated, null, 2));
8780
+ }
8781
+ async function addExemplar(exemplar) {
8782
+ const store6 = await loadExemplars();
8783
+ if (store6.exemplars[exemplar.id]) {
8784
+ throw new Error(`Exemplar ${exemplar.id} already exists`);
8785
+ }
8786
+ if (exemplar.sourceReference) {
8787
+ const existingRef = Object.values(store6.exemplars).find(
8788
+ (e) => e.sourceReference === exemplar.sourceReference
8789
+ );
8790
+ if (existingRef) {
8791
+ throw new Error(`Exemplar with source reference ${exemplar.sourceReference} already exists (id: ${existingRef.id})`);
8792
+ }
8793
+ }
8794
+ const full = {
8795
+ ...exemplar,
8796
+ type: "interaction-exemplar",
8797
+ revision: 1
8798
+ };
8799
+ store6.exemplars[exemplar.id] = full;
8800
+ await saveExemplars(store6);
8801
+ return full;
8802
+ }
8803
+ async function updateExemplar(id, updates) {
8804
+ const store6 = await loadExemplars();
8805
+ const existing = store6.exemplars[id];
8806
+ if (!existing) {
8807
+ throw new Error(`Exemplar ${id} not found`);
8808
+ }
8809
+ const updated = {
8810
+ ...existing,
8811
+ ...updates,
8812
+ id: existing.id,
8813
+ type: "interaction-exemplar",
8814
+ createdAt: existing.createdAt,
8815
+ revision: (updates.revision ?? existing.revision) + 1
8816
+ };
8817
+ store6.exemplars[id] = updated;
8818
+ await saveExemplars(store6);
8819
+ return updated;
8820
+ }
8764
8821
  async function searchExemplars(options = {}) {
8765
- const store5 = await loadExemplars();
8766
- const all = Object.values(store5.exemplars);
8822
+ const store6 = await loadExemplars();
8823
+ const all = Object.values(store6.exemplars);
8767
8824
  const filtered = all.filter((exemplar) => {
8768
8825
  if (options.minSignificance !== void 0 && exemplar.significance < options.minSignificance) {
8769
8826
  return false;
@@ -8823,10 +8880,14 @@ var init_exemplars = __esm({
8823
8880
  __name(joinPath9, "joinPath");
8824
8881
  __name(resolveExemplarsPath, "resolveExemplarsPath");
8825
8882
  __name(readFile6, "readFile");
8883
+ __name(writeFile6, "writeFile");
8826
8884
  __name(emptyStore, "emptyStore");
8827
8885
  __name(validateExemplar, "validateExemplar");
8828
8886
  __name(validateStore, "validateStore");
8829
8887
  __name(loadExemplars, "loadExemplars");
8888
+ __name(saveExemplars, "saveExemplars");
8889
+ __name(addExemplar, "addExemplar");
8890
+ __name(updateExemplar, "updateExemplar");
8830
8891
  __name(searchExemplars, "searchExemplars");
8831
8892
  __name(projectExemplar, "projectExemplar");
8832
8893
  __name(projectExemplarContext, "projectExemplarContext");
@@ -8849,6 +8910,19 @@ async function readFile7(path3) {
8849
8910
  return null;
8850
8911
  }
8851
8912
  }
8913
+ async function writeFile7(path3, content) {
8914
+ const fs3 = await import('fs/promises');
8915
+ const dir = path3.replace(/[\\/][^\\/]+$/, "");
8916
+ await fs3.mkdir(dir, { recursive: true });
8917
+ const temporary = `${path3}.${process.pid}.${Date.now().toString(36)}.tmp`;
8918
+ try {
8919
+ await fs3.writeFile(temporary, content, "utf8");
8920
+ await fs3.rename(temporary, path3);
8921
+ } catch {
8922
+ await fs3.rm(temporary, { force: true }).catch(() => void 0);
8923
+ throw new Error(`Failed to write personality anchor at ${path3}`);
8924
+ }
8925
+ }
8852
8926
  function emptyAnchor() {
8853
8927
  const epoch = "1970-01-01T00:00:00.000Z";
8854
8928
  return {
@@ -8981,6 +9055,33 @@ async function loadPersonalityAnchor() {
8981
9055
  return emptyAnchor();
8982
9056
  }
8983
9057
  }
9058
+ async function savePersonalityAnchor(anchor) {
9059
+ const validated = validateAnchor(anchor);
9060
+ const dirPath = resolveUserDotMemPath();
9061
+ const fs3 = await import('fs/promises');
9062
+ await fs3.mkdir(dirPath, { recursive: true });
9063
+ await writeFile7(resolvePersonalityAnchorPath(), JSON.stringify(validated, null, 2));
9064
+ }
9065
+ async function updatePersonalityAnchor(updates) {
9066
+ const existing = await loadPersonalityAnchor();
9067
+ const now2 = (/* @__PURE__ */ new Date()).toISOString();
9068
+ const updated = {
9069
+ ...existing,
9070
+ identity: {
9071
+ communicationStyle: updates.communicationStyle ?? existing.identity.communicationStyle,
9072
+ durablePreferences: updates.durablePreferences ?? existing.identity.durablePreferences,
9073
+ interactionBoundaries: updates.interactionBoundaries ?? existing.identity.interactionBoundaries
9074
+ },
9075
+ metadata: {
9076
+ ...existing.metadata,
9077
+ updatedAt: now2,
9078
+ revision: existing.metadata.revision + 1,
9079
+ sourceReferences: updates.sourceReferences ? [.../* @__PURE__ */ new Set([...existing.metadata.sourceReferences, ...updates.sourceReferences])] : existing.metadata.sourceReferences
9080
+ }
9081
+ };
9082
+ await savePersonalityAnchor(updated);
9083
+ return updated;
9084
+ }
8984
9085
  function projectPersonalityAnchor(anchor, options) {
8985
9086
  const maxChars = options?.maxCharacters ?? 4e3;
8986
9087
  const hasContent = anchor.identity.communicationStyle.length > 0 || anchor.identity.durablePreferences.length > 0 || anchor.identity.interactionBoundaries.length > 0;
@@ -9035,12 +9136,15 @@ var init_personality_anchor = __esm({
9035
9136
  __name(joinPath10, "joinPath");
9036
9137
  __name(resolvePersonalityAnchorPath, "resolvePersonalityAnchorPath");
9037
9138
  __name(readFile7, "readFile");
9139
+ __name(writeFile7, "writeFile");
9038
9140
  __name(emptyAnchor, "emptyAnchor");
9039
9141
  __name(validateCommunicationStyle, "validateCommunicationStyle");
9040
9142
  __name(validateDurablePreference, "validateDurablePreference");
9041
9143
  __name(validateInteractionBoundary, "validateInteractionBoundary");
9042
9144
  __name(validateAnchor, "validateAnchor");
9043
9145
  __name(loadPersonalityAnchor, "loadPersonalityAnchor");
9146
+ __name(savePersonalityAnchor, "savePersonalityAnchor");
9147
+ __name(updatePersonalityAnchor, "updatePersonalityAnchor");
9044
9148
  __name(projectPersonalityAnchor, "projectPersonalityAnchor");
9045
9149
  __name(calculateAnchorBudget, "calculateAnchorBudget");
9046
9150
  }
@@ -9732,27 +9836,27 @@ var init_policy2 = __esm({
9732
9836
  }
9733
9837
  }, "auditPolicyAction");
9734
9838
  resolveOverrides = /* @__PURE__ */ __name(async (namespace2, now2 = () => /* @__PURE__ */ new Date()) => {
9735
- const store5 = await getPolicyStore();
9839
+ const store6 = await getPolicyStore();
9736
9840
  const namespaces = namespace2 ? [namespace2] : exports.POLICY_NAMESPACES;
9737
- const documents = await Promise.all(namespaces.map((entry) => store5.load(entry)));
9841
+ const documents = await Promise.all(namespaces.map((entry) => store6.load(entry)));
9738
9842
  return documents.flatMap((document) => document.overrides.map(
9739
9843
  (override) => exports.resolveOverrideState(override, document.records.find((record) => record.id === override.targetPolicyId), now2)
9740
9844
  )).sort((left, right) => left.override.id.localeCompare(right.override.id));
9741
9845
  }, "resolveOverrides");
9742
9846
  policy = {
9743
9847
  async assign(input, namespace2 = "operator", basedOnRevision) {
9744
- const store5 = await getPolicyStore();
9745
- const record = await store5.assign(input, namespace2, basedOnRevision);
9848
+ const store6 = await getPolicyStore();
9849
+ const record = await store6.assign(input, namespace2, basedOnRevision);
9746
9850
  auditPolicyAction("policy.assign", record.id);
9747
9851
  return record;
9748
9852
  },
9749
9853
  async list() {
9750
- const store5 = await getPolicyStore();
9751
- return store5.list();
9854
+ const store6 = await getPolicyStore();
9855
+ return store6.list();
9752
9856
  },
9753
9857
  async remove(id, namespace2 = "operator", basedOnRevision) {
9754
- const store5 = await getPolicyStore();
9755
- await store5.remove(id, namespace2, basedOnRevision);
9858
+ const store6 = await getPolicyStore();
9859
+ await store6.remove(id, namespace2, basedOnRevision);
9756
9860
  auditPolicyAction("policy.remove", id);
9757
9861
  },
9758
9862
  /**
@@ -9766,16 +9870,16 @@ var init_policy2 = __esm({
9766
9870
  * the durable audit trail with a terminal CLEAR entry.
9767
9871
  */
9768
9872
  async clear(authority) {
9769
- const store5 = await getPolicyStore();
9770
- await store5.clear(authority);
9873
+ const store6 = await getPolicyStore();
9874
+ await store6.clear(authority);
9771
9875
  auditPolicyAction("policy.clear", "policy");
9772
9876
  },
9773
9877
  async evaluate(event, payload) {
9774
- const store5 = await getPolicyStore();
9878
+ const store6 = await getPolicyStore();
9775
9879
  const [state2, policies, overrides] = await Promise.all([
9776
9880
  loadCombinedKnowledgeState(),
9777
- store5.list(),
9778
- store5.listOverrides()
9881
+ store6.list(),
9882
+ store6.listOverrides()
9779
9883
  ]);
9780
9884
  const claims = activeClaims(state2);
9781
9885
  return exports.evaluatePolicies({ event, claims, policies, overrides, payload });
@@ -9797,14 +9901,14 @@ var init_policy2 = __esm({
9797
9901
  */
9798
9902
  override: {
9799
9903
  async grant(input, namespace2 = "operator", basedOnRevision) {
9800
- const store5 = await getPolicyStore();
9801
- const override = await store5.grantOverride(input, namespace2, basedOnRevision);
9904
+ const store6 = await getPolicyStore();
9905
+ const override = await store6.grantOverride(input, namespace2, basedOnRevision);
9802
9906
  auditPolicyAction("policy.override", override.id);
9803
9907
  return override;
9804
9908
  },
9805
9909
  async revoke(input, namespace2 = "operator", basedOnRevision) {
9806
- const store5 = await getPolicyStore();
9807
- const override = await store5.revokeOverride(input, namespace2, basedOnRevision);
9910
+ const store6 = await getPolicyStore();
9911
+ const override = await store6.revokeOverride(input, namespace2, basedOnRevision);
9808
9912
  auditPolicyAction("policy.revoke", override.id);
9809
9913
  return override;
9810
9914
  },
@@ -9812,8 +9916,8 @@ var init_policy2 = __esm({
9812
9916
  return resolveOverrides(namespace2);
9813
9917
  },
9814
9918
  async effective(event, namespace2) {
9815
- const store5 = await getPolicyStore();
9816
- const states = await store5.effectiveState(namespace2);
9919
+ const store6 = await getPolicyStore();
9920
+ const states = await store6.effectiveState(namespace2);
9817
9921
  if (event === void 0) return states;
9818
9922
  const canonical = event.trim();
9819
9923
  return states.filter((state2) => state2.event === canonical);
@@ -9826,8 +9930,8 @@ var init_policy2 = __esm({
9826
9930
  * additional best-effort sink, and this is the durable record.
9827
9931
  */
9828
9932
  async audit(namespace2) {
9829
- const store5 = await getPolicyStore();
9830
- return store5.auditTrail(namespace2);
9933
+ const store6 = await getPolicyStore();
9934
+ return store6.auditTrail(namespace2);
9831
9935
  },
9832
9936
  contract() {
9833
9937
  return exports.MEMORY_PROJECTION_CONTRACT;
@@ -11241,6 +11345,783 @@ var init_seed = __esm({
11241
11345
  }, "injectJoySeed");
11242
11346
  }
11243
11347
  });
11348
+ function generateId() {
11349
+ return `capture-${memorio.hlc.serialize(memorio.hlc.tick())}-${Math.random().toString(36).slice(2, 10)}`;
11350
+ }
11351
+ function nowIso() {
11352
+ return (/* @__PURE__ */ new Date()).toISOString();
11353
+ }
11354
+ function normalizeText(text) {
11355
+ return text.normalize("NFKC").toLocaleLowerCase("en-US").replace(/[^\w\s]/g, " ").replace(/\s+/g, " ").trim();
11356
+ }
11357
+ function tokenize(text) {
11358
+ return new Set(normalizeText(text).split(" ").filter((t) => t.length > 2));
11359
+ }
11360
+ function jaccardSimilarity(a, b) {
11361
+ const ta = tokenize(a);
11362
+ const tb = tokenize(b);
11363
+ if (ta.size === 0 && tb.size === 0) return 1;
11364
+ if (ta.size === 0 || tb.size === 0) return 0;
11365
+ const intersection = new Set([...ta].filter((x2) => tb.has(x2)));
11366
+ const union = /* @__PURE__ */ new Set([...ta, ...tb]);
11367
+ return intersection.size / union.size;
11368
+ }
11369
+ function containsPhrase2(text, phrase) {
11370
+ const t = normalizeText(text);
11371
+ const p = normalizeText(phrase);
11372
+ return t.includes(p);
11373
+ }
11374
+ function detectExplicitInstructions(text, source) {
11375
+ const instructions = [];
11376
+ for (const pattern of REMEMBER_PATTERNS) {
11377
+ if (pattern.test(text)) {
11378
+ instructions.push({
11379
+ type: "remember",
11380
+ content: text,
11381
+ source,
11382
+ timestamp: nowIso()
11383
+ });
11384
+ break;
11385
+ }
11386
+ }
11387
+ for (const pattern of DO_NOT_REMEMBER_PATTERNS) {
11388
+ if (pattern.test(text)) {
11389
+ instructions.push({
11390
+ type: "do-not-remember",
11391
+ content: text,
11392
+ source,
11393
+ timestamp: nowIso()
11394
+ });
11395
+ break;
11396
+ }
11397
+ }
11398
+ return instructions;
11399
+ }
11400
+ function classifyCategory(text, explicitInstructions) {
11401
+ const lower = text.toLocaleLowerCase("en-US");
11402
+ if (explicitInstructions.some((i2) => i2.type === "do-not-remember")) {
11403
+ return "temporary_context";
11404
+ }
11405
+ if (explicitInstructions.some((i2) => i2.type === "remember")) {
11406
+ if (/prefer|prefers|like|likes|favorite|always|never/i.test(lower)) {
11407
+ return "preference";
11408
+ }
11409
+ if (/decided|decision|agreed|chose|adopt/i.test(lower)) {
11410
+ return "decision";
11411
+ }
11412
+ return "fact";
11413
+ }
11414
+ const scores = {};
11415
+ for (const cat of Object.keys(CATEGORY_KEYWORDS)) {
11416
+ scores[cat] = CATEGORY_KEYWORDS[cat].filter((kw) => containsPhrase2(lower, kw)).length;
11417
+ }
11418
+ if (/probably|likely|maybe|perhaps|seems|appears|might|could be/i.test(lower)) {
11419
+ scores.inference += 3;
11420
+ }
11421
+ if (text.includes("?")) {
11422
+ scores.question += 3;
11423
+ }
11424
+ if (/decided|decision|agreed|chose|adopt|will use|going with|resolve|settled/i.test(lower)) {
11425
+ scores.decision += 3;
11426
+ }
11427
+ if (/prefer|prefers|like|likes|favorite|always|never|don.t like|dislike/i.test(lower)) {
11428
+ scores.preference += 3;
11429
+ }
11430
+ if (/still need to|pending|waiting for|todo|to do|unresolved|outstanding|follow.?up|blocked/i.test(lower)) {
11431
+ scores.open_loop += 3;
11432
+ }
11433
+ if (/remember when|we talked about|discussed|experienced|encountered|session|conversation/i.test(lower)) {
11434
+ scores.experience += 2;
11435
+ }
11436
+ if (/repository|architecture|deploy|ci|pipeline/i.test(lower)) {
11437
+ scores.project_context += 2;
11438
+ }
11439
+ if (/just for|this session|for now|temporary/i.test(lower)) {
11440
+ scores.temporary_context += 3;
11441
+ }
11442
+ if (scores.fact > 0 && scores.project_context > 0 && scores.fact === scores.project_context) {
11443
+ scores.fact += 1;
11444
+ }
11445
+ let bestCategory = "fact";
11446
+ let bestScore = -1;
11447
+ for (const [cat, score] of Object.entries(scores)) {
11448
+ if (score > bestScore) {
11449
+ bestScore = score;
11450
+ bestCategory = cat;
11451
+ }
11452
+ }
11453
+ if (bestScore === 0) return "fact";
11454
+ return bestCategory;
11455
+ }
11456
+ function determineEpistemicType(category, text, explicitInstructions) {
11457
+ if (explicitInstructions.some((i2) => i2.type === "remember")) {
11458
+ return "human-stated";
11459
+ }
11460
+ if (category === "inference" || /probably|likely|maybe|perhaps|seems|appears/i.test(text)) {
11461
+ return "inferred";
11462
+ }
11463
+ if (category === "question") return "observed";
11464
+ return "observed";
11465
+ }
11466
+ function determineConfidence(category, epistemicType, _text) {
11467
+ if (epistemicType === "human-stated") return "high";
11468
+ if (epistemicType === "inferred") return "low";
11469
+ if (category === "decision" || category === "preference") return "medium";
11470
+ if (category === "question") return "medium";
11471
+ if (category === "fact") return "medium";
11472
+ return "low";
11473
+ }
11474
+ function extractEntities(text) {
11475
+ const entities = [];
11476
+ const seen = /* @__PURE__ */ new Set();
11477
+ const capitalizedMatches = text.match(/\b[A-Z][a-zA-Z]+(?:\s+[A-Z][a-zA-Z]+)*\b/g) || [];
11478
+ for (const match of capitalizedMatches) {
11479
+ if (seen.has(match)) continue;
11480
+ seen.add(match);
11481
+ entities.push({
11482
+ id: `entity-${normalizeText(match)}`,
11483
+ type: "concept",
11484
+ name: match,
11485
+ aliases: [],
11486
+ relationships: []
11487
+ });
11488
+ }
11489
+ const quotedMatches = text.match(/["'`]([^"`']{2,})["'`]/g) || [];
11490
+ for (const match of quotedMatches) {
11491
+ const clean = match.replace(/["'`]/g, "");
11492
+ if (seen.has(clean)) continue;
11493
+ seen.add(clean);
11494
+ entities.push({
11495
+ id: `entity-${normalizeText(clean)}`,
11496
+ type: "concept",
11497
+ name: clean,
11498
+ aliases: [],
11499
+ relationships: []
11500
+ });
11501
+ }
11502
+ return entities;
11503
+ }
11504
+ function extractRelationships(_text, entities) {
11505
+ const relationships = [];
11506
+ if (entities.length < 2) return relationships;
11507
+ for (let i2 = 0; i2 < entities.length - 1; i2++) {
11508
+ for (let j = i2 + 1; j < entities.length; j++) {
11509
+ relationships.push({
11510
+ from: entities[i2].id,
11511
+ to: entities[j].id,
11512
+ type: "related-to",
11513
+ confidence: 0.5
11514
+ });
11515
+ }
11516
+ }
11517
+ return relationships;
11518
+ }
11519
+ async function findExistingSimilarItems(statement, category, existingItems, threshold) {
11520
+ const matches2 = [];
11521
+ for (const existing of existingItems) {
11522
+ if (existing.category !== category) continue;
11523
+ const similarity = jaccardSimilarity(statement, existing.statement);
11524
+ if (similarity >= threshold) {
11525
+ matches2.push({
11526
+ existingId: existing.id,
11527
+ existingStatement: existing.statement,
11528
+ similarity,
11529
+ matchType: similarity >= 0.95 ? "exact" : "semantic",
11530
+ category: existing.category
11531
+ });
11532
+ }
11533
+ }
11534
+ return matches2;
11535
+ }
11536
+ async function findExistingMemoryEntries(statement, category) {
11537
+ const matches2 = [];
11538
+ try {
11539
+ const all = await entries._loadAll();
11540
+ for (const entry of all) {
11541
+ if (entry.status !== "active") continue;
11542
+ if (entry.type !== category && entry.type !== "fact") continue;
11543
+ const entryText = typeof entry.value === "string" ? entry.value : JSON.stringify(entry.value);
11544
+ const similarity = jaccardSimilarity(statement, entryText);
11545
+ if (similarity >= DEFAULT_CONFIG.dedupSimilarityThreshold) {
11546
+ matches2.push({
11547
+ existingId: entry.id,
11548
+ existingStatement: entryText,
11549
+ similarity,
11550
+ matchType: similarity >= 0.95 ? "exact" : "semantic",
11551
+ category
11552
+ });
11553
+ }
11554
+ }
11555
+ } catch {
11556
+ }
11557
+ return matches2;
11558
+ }
11559
+ function shouldStoreInCoreMemory(category, epistemicType) {
11560
+ return category === "preference" && epistemicType === "human-stated";
11561
+ }
11562
+ async function routeToStorage(item, adapters, config) {
11563
+ const { category, epistemicType, statement, entities, provenance, confidence } = item;
11564
+ const memKey = `capture:${item.id}`;
11565
+ await entries.remember(memKey, {
11566
+ id: item.id,
11567
+ category,
11568
+ statement,
11569
+ entities,
11570
+ relationships: item.relationships,
11571
+ provenance,
11572
+ confidence,
11573
+ epistemicType,
11574
+ capturedAt: provenance.capturedAt
11575
+ }, {
11576
+ id: item.id,
11577
+ type: category === "preference" ? "preference" : category === "decision" ? "decision" : category === "fact" ? "fact" : "context",
11578
+ confidence: confidence === "high" ? 1 : confidence === "medium" ? 0.7 : 0.4,
11579
+ scope: category === "temporary_context" ? "session" : "local",
11580
+ tags: [category, provenance.epistemicType, provenance.source.conversationId],
11581
+ source: `capture:${provenance.source.conversationId}`
11582
+ });
11583
+ if (shouldStoreInCoreMemory(category, epistemicType)) {
11584
+ try {
11585
+ const anchor = await loadPersonalityAnchor();
11586
+ const existingPref = anchor.identity.durablePreferences.find((p) => p.statement === statement);
11587
+ if (!existingPref) {
11588
+ await updatePersonalityAnchor({
11589
+ durablePreferences: [
11590
+ ...anchor.identity.durablePreferences,
11591
+ {
11592
+ id: item.id,
11593
+ statement,
11594
+ confidence: epistemicType,
11595
+ evidence: [provenance.source.conversationId],
11596
+ firstObserved: provenance.capturedAt,
11597
+ lastConfirmed: provenance.capturedAt
11598
+ }
11599
+ ]
11600
+ });
11601
+ }
11602
+ } catch {
11603
+ }
11604
+ }
11605
+ if (category === "open_loop" && config.autoCreateLoops) {
11606
+ try {
11607
+ const existingLoops = await listLoops(true);
11608
+ const alreadyExists = existingLoops.some((l) => l.text === statement);
11609
+ if (!alreadyExists) {
11610
+ await addLoop({
11611
+ id: item.id,
11612
+ text: statement,
11613
+ waitor: "joy",
11614
+ openedAt: provenance.capturedAt
11615
+ });
11616
+ }
11617
+ } catch {
11618
+ }
11619
+ }
11620
+ if (category === "experience" && config.autoCreateExemplars) {
11621
+ try {
11622
+ await addExemplar({
11623
+ id: item.id,
11624
+ source: {
11625
+ conversationId: provenance.source.conversationId,
11626
+ messageIds: provenance.source.messageIds
11627
+ },
11628
+ pattern: statement,
11629
+ originalContent: statement,
11630
+ sourceReference: `capture:${item.id}`,
11631
+ retrievalHints: [statement, ...entities.map((e) => e.name)],
11632
+ significance: confidence === "high" ? 0.8 : confidence === "medium" ? 0.5 : 0.3,
11633
+ confidence: confidence === "high" ? 1 : confidence === "medium" ? 0.7 : 0.4,
11634
+ provenance: epistemicType,
11635
+ createdAt: provenance.capturedAt
11636
+ });
11637
+ } catch {
11638
+ }
11639
+ }
11640
+ if (config.autoCreateClaims && (category === "fact" || category === "decision")) {
11641
+ try {
11642
+ await acquire.acquire({
11643
+ id: `claim-${item.id}`,
11644
+ knowledge: { statement, category },
11645
+ applicability: { area: "project" },
11646
+ evidence: [{
11647
+ id: `ev-${item.id}`,
11648
+ source: provenance.source.conversationId,
11649
+ observedAt: provenance.capturedAt,
11650
+ supports: statement
11651
+ }],
11652
+ epistemicType,
11653
+ provenance: { source: "conversation-capture", detail: `category:${category}` },
11654
+ acquiredAt: provenance.capturedAt,
11655
+ scope: "project"
11656
+ });
11657
+ } catch {
11658
+ }
11659
+ }
11660
+ memorio.dispatch.set("dotmem:capture:item-captured", {
11661
+ detail: { id: item.id, category, statement }
11662
+ });
11663
+ }
11664
+ async function captureConversation(messages, options) {
11665
+ return captureEngine.captureConversation(messages, options);
11666
+ }
11667
+ async function captureTurn(text, options) {
11668
+ return captureEngine.captureTurn(text, options);
11669
+ }
11670
+ var CAPTURE_STATE_KEY, DEFAULT_CONFIG, CATEGORY_KEYWORDS, REMEMBER_PATTERNS, DO_NOT_REMEMBER_PATTERNS, CaptureEngine, captureEngine;
11671
+ var init_capture_engine = __esm({
11672
+ "core/memory/capture-engine.ts"() {
11673
+ init_exemplars();
11674
+ init_loops();
11675
+ init_personality_anchor();
11676
+ init_entries();
11677
+ init_acquire();
11678
+ CAPTURE_STATE_KEY = "__dotmem_capture_state__";
11679
+ DEFAULT_CONFIG = {
11680
+ minSignificance: 0.3,
11681
+ maxItemsPerConversation: 50,
11682
+ dedupSimilarityThreshold: 0.75,
11683
+ preserveConflicts: true,
11684
+ tempContextTtlMs: 24 * 60 * 60 * 1e3,
11685
+ autoCreateExemplars: true,
11686
+ autoCreateLoops: true,
11687
+ autoCreateClaims: true
11688
+ };
11689
+ CATEGORY_KEYWORDS = {
11690
+ fact: [
11691
+ "is",
11692
+ "are",
11693
+ "was",
11694
+ "were",
11695
+ "uses",
11696
+ "built with",
11697
+ "depends on",
11698
+ "located at",
11699
+ "named",
11700
+ "called",
11701
+ "version",
11702
+ "language",
11703
+ "framework",
11704
+ "test",
11705
+ "testing"
11706
+ ],
11707
+ decision: [
11708
+ "decided",
11709
+ "decision",
11710
+ "agreed",
11711
+ "chose",
11712
+ "selected",
11713
+ "adopt",
11714
+ "going with",
11715
+ "will use",
11716
+ "commit to",
11717
+ "resolve",
11718
+ "settled"
11719
+ ],
11720
+ experience: [
11721
+ "remember when",
11722
+ "we talked about",
11723
+ "discussed",
11724
+ "experienced",
11725
+ "encountered",
11726
+ "faced",
11727
+ "went through",
11728
+ "session",
11729
+ "conversation"
11730
+ ],
11731
+ question: [
11732
+ "?",
11733
+ "how do",
11734
+ "what is",
11735
+ "why",
11736
+ "when",
11737
+ "where",
11738
+ "which",
11739
+ "can you",
11740
+ "should we",
11741
+ "is it possible"
11742
+ ],
11743
+ open_loop: [
11744
+ "still need to",
11745
+ "pending",
11746
+ "waiting for",
11747
+ "todo",
11748
+ "to do",
11749
+ "open",
11750
+ "unresolved",
11751
+ "outstanding",
11752
+ "follow up",
11753
+ "blocked"
11754
+ ],
11755
+ preference: [
11756
+ "prefer",
11757
+ "prefers",
11758
+ "like",
11759
+ "likes",
11760
+ "favorite",
11761
+ "always",
11762
+ "never",
11763
+ "don't like",
11764
+ "dislike",
11765
+ "style",
11766
+ "habit"
11767
+ ],
11768
+ project_context: [
11769
+ "project",
11770
+ "repository",
11771
+ "codebase",
11772
+ "architecture",
11773
+ "stack",
11774
+ "deploy",
11775
+ "ci",
11776
+ "pipeline",
11777
+ "package"
11778
+ ],
11779
+ temporary_context: [
11780
+ "for now",
11781
+ "just for",
11782
+ "temporary",
11783
+ "this session",
11784
+ "currently",
11785
+ "right now",
11786
+ "at the moment",
11787
+ "for this task"
11788
+ ],
11789
+ inference: [
11790
+ "probably",
11791
+ "likely",
11792
+ "maybe",
11793
+ "perhaps",
11794
+ "seems",
11795
+ "appears",
11796
+ "suggests",
11797
+ "indicates",
11798
+ "implies",
11799
+ "might",
11800
+ "could be"
11801
+ ]
11802
+ };
11803
+ REMEMBER_PATTERNS = [
11804
+ /remember this/i,
11805
+ /remember that/i,
11806
+ /keep this in mind/i,
11807
+ /don't forget/i,
11808
+ /do not forget/i,
11809
+ /store this/i,
11810
+ /save this/i
11811
+ ];
11812
+ DO_NOT_REMEMBER_PATTERNS = [
11813
+ /don't remember/i,
11814
+ /do not remember/i,
11815
+ /forget this/i,
11816
+ /don't store/i,
11817
+ /do not store/i,
11818
+ /not relevant/i,
11819
+ /ignore this/i,
11820
+ /temporary only/i,
11821
+ /this session only/i
11822
+ ];
11823
+ __name(generateId, "generateId");
11824
+ __name(nowIso, "nowIso");
11825
+ __name(normalizeText, "normalizeText");
11826
+ __name(tokenize, "tokenize");
11827
+ __name(jaccardSimilarity, "jaccardSimilarity");
11828
+ __name(containsPhrase2, "containsPhrase");
11829
+ __name(detectExplicitInstructions, "detectExplicitInstructions");
11830
+ __name(classifyCategory, "classifyCategory");
11831
+ __name(determineEpistemicType, "determineEpistemicType");
11832
+ __name(determineConfidence, "determineConfidence");
11833
+ __name(extractEntities, "extractEntities");
11834
+ __name(extractRelationships, "extractRelationships");
11835
+ __name(findExistingSimilarItems, "findExistingSimilarItems");
11836
+ __name(findExistingMemoryEntries, "findExistingMemoryEntries");
11837
+ __name(shouldStoreInCoreMemory, "shouldStoreInCoreMemory");
11838
+ __name(routeToStorage, "routeToStorage");
11839
+ CaptureEngine = class {
11840
+ static {
11841
+ __name(this, "CaptureEngine");
11842
+ }
11843
+ config;
11844
+ adapters;
11845
+ sessionContexts = /* @__PURE__ */ new Map();
11846
+ constructor(config = {}, adapters) {
11847
+ this.config = { ...DEFAULT_CONFIG, ...config };
11848
+ this.adapters = {
11849
+ remember: entries.remember.bind(entries),
11850
+ recall: entries.recall.bind(entries),
11851
+ update: entries.update.bind(entries),
11852
+ addExemplar: addExemplar.bind(null),
11853
+ updateExemplar: updateExemplar.bind(null),
11854
+ addLoop: addLoop.bind(null),
11855
+ closeLoop: closeLoop.bind(null),
11856
+ updatePersonalityAnchor: updatePersonalityAnchor.bind(null),
11857
+ ...adapters
11858
+ };
11859
+ }
11860
+ /**
11861
+ * Capture information from a conversation turn.
11862
+ * Processes a single message or turn of conversation.
11863
+ */
11864
+ async captureTurn(text, options) {
11865
+ const source = {
11866
+ conversationId: options.conversationId,
11867
+ messageIds: options.messageIds ?? [],
11868
+ timestamp: nowIso(),
11869
+ turnIndex: 0
11870
+ };
11871
+ const instructions = detectExplicitInstructions(text, source);
11872
+ const hasDoNotRemember = instructions.some((i2) => i2.type === "do-not-remember");
11873
+ const hasExplicitRemember = instructions.some((i2) => i2.type === "remember");
11874
+ let created = 0, linked = 0, ignored = 0;
11875
+ if (hasDoNotRemember && !hasExplicitRemember) {
11876
+ const item2 = {
11877
+ id: generateId(),
11878
+ category: "temporary_context",
11879
+ statement: text,
11880
+ content: { text },
11881
+ entities: [],
11882
+ relationships: [],
11883
+ provenance: {
11884
+ source,
11885
+ classification: "temporary_context",
11886
+ confidence: "low",
11887
+ epistemicType: "observed",
11888
+ capturedAt: nowIso(),
11889
+ method: "automatic"
11890
+ },
11891
+ confidence: "low",
11892
+ epistemicType: "observed"
11893
+ };
11894
+ await this.storeSessionContext(options.conversationId, [item2], instructions);
11895
+ ignored++;
11896
+ return { items: [item2], instructions, created: 0, linked: 0, ignored: 1, dedupMatches: [] };
11897
+ }
11898
+ const category = classifyCategory(text, instructions);
11899
+ const epistemicType = determineEpistemicType(category, text, instructions);
11900
+ const confidence = determineConfidence(category, epistemicType);
11901
+ const entities = extractEntities(text);
11902
+ const relationships = extractRelationships(text, entities);
11903
+ const provenance = {
11904
+ source,
11905
+ classification: category,
11906
+ confidence,
11907
+ epistemicType,
11908
+ capturedAt: nowIso(),
11909
+ method: hasExplicitRemember ? "explicit" : "automatic"
11910
+ };
11911
+ const item = {
11912
+ id: generateId(),
11913
+ category,
11914
+ statement: text,
11915
+ content: { text },
11916
+ entities,
11917
+ relationships,
11918
+ provenance,
11919
+ confidence,
11920
+ epistemicType
11921
+ };
11922
+ const sessionCtx = this.sessionContexts.get(options.conversationId);
11923
+ const existingItems = sessionCtx ? sessionCtx.items : [];
11924
+ const dedupMatches = await findExistingSimilarItems(
11925
+ text,
11926
+ category,
11927
+ existingItems,
11928
+ this.config.dedupSimilarityThreshold
11929
+ );
11930
+ const memoryMatches = await findExistingMemoryEntries(text, category);
11931
+ const allMatches = [...dedupMatches, ...memoryMatches];
11932
+ let decision = "create";
11933
+ if (allMatches.length > 0) {
11934
+ const exactMatch = allMatches.find((m) => m.matchType === "exact");
11935
+ if (exactMatch) {
11936
+ decision = "link";
11937
+ linked++;
11938
+ } else {
11939
+ decision = "create";
11940
+ created++;
11941
+ }
11942
+ } else {
11943
+ created++;
11944
+ }
11945
+ const finalItem = { ...item };
11946
+ const shouldStore = decision === "create" || decision === "link";
11947
+ if (shouldStore) {
11948
+ await routeToStorage(finalItem, this.adapters, this.config);
11949
+ }
11950
+ await this.storeSessionContext(options.conversationId, [finalItem], instructions);
11951
+ return { items: [finalItem], instructions, created, linked, ignored, dedupMatches: allMatches };
11952
+ }
11953
+ /**
11954
+ * Capture information from a full conversation (array of messages).
11955
+ * Processes each message and returns aggregated results.
11956
+ */
11957
+ async captureConversation(messages, options) {
11958
+ const allItems = [];
11959
+ const allInstructions = [];
11960
+ let created = 0, updated = 0, linked = 0, ignored = 0;
11961
+ const allDedupMatches = [];
11962
+ for (const [index, msg] of messages.entries()) {
11963
+ const source = {
11964
+ conversationId: options.conversationId,
11965
+ messageIds: [msg.id ?? `msg-${index}`],
11966
+ timestamp: msg.timestamp ?? nowIso(),
11967
+ turnIndex: index
11968
+ };
11969
+ const instructions = detectExplicitInstructions(msg.content, source);
11970
+ allInstructions.push(...instructions);
11971
+ const hasDoNotRemember = instructions.some((i2) => i2.type === "do-not-remember");
11972
+ const hasExplicitRemember = instructions.some((i2) => i2.type === "remember");
11973
+ if (hasDoNotRemember && !hasExplicitRemember) {
11974
+ const item2 = {
11975
+ id: generateId(),
11976
+ category: "temporary_context",
11977
+ statement: msg.content,
11978
+ content: { text: msg.content, role: msg.role },
11979
+ entities: [],
11980
+ relationships: [],
11981
+ provenance: {
11982
+ source,
11983
+ classification: "temporary_context",
11984
+ confidence: "low",
11985
+ epistemicType: "observed",
11986
+ capturedAt: nowIso(),
11987
+ method: "automatic"
11988
+ },
11989
+ confidence: "low",
11990
+ epistemicType: "observed"
11991
+ };
11992
+ allItems.push(item2);
11993
+ ignored++;
11994
+ continue;
11995
+ }
11996
+ const category = classifyCategory(msg.content, instructions);
11997
+ const epistemicType = determineEpistemicType(category, msg.content, instructions);
11998
+ const confidence = determineConfidence(category, epistemicType, msg.content);
11999
+ const entities = extractEntities(msg.content);
12000
+ const relationships = extractRelationships(msg.content, entities);
12001
+ const provenance = {
12002
+ source,
12003
+ classification: category,
12004
+ confidence,
12005
+ epistemicType,
12006
+ capturedAt: nowIso(),
12007
+ method: hasExplicitRemember ? "explicit" : "automatic"
12008
+ };
12009
+ const item = {
12010
+ id: generateId(),
12011
+ category,
12012
+ statement: msg.content,
12013
+ content: { text: msg.content, role: msg.role },
12014
+ entities,
12015
+ relationships,
12016
+ provenance,
12017
+ confidence,
12018
+ epistemicType
12019
+ };
12020
+ const dedupMatches = await findExistingSimilarItems(
12021
+ msg.content,
12022
+ category,
12023
+ allItems,
12024
+ this.config.dedupSimilarityThreshold
12025
+ );
12026
+ const memoryMatches = await findExistingMemoryEntries(msg.content, category);
12027
+ const allMatches = [...dedupMatches, ...memoryMatches];
12028
+ allDedupMatches.push(...allMatches);
12029
+ let decision = "create";
12030
+ if (allMatches.length > 0) {
12031
+ const exactMatch = allMatches.find((m) => m.matchType === "exact");
12032
+ if (exactMatch) {
12033
+ decision = "link";
12034
+ linked++;
12035
+ } else {
12036
+ decision = "create";
12037
+ created++;
12038
+ }
12039
+ } else {
12040
+ created++;
12041
+ }
12042
+ const shouldStore = decision === "create" || decision === "link";
12043
+ if (shouldStore) {
12044
+ await routeToStorage(item, this.adapters, this.config);
12045
+ }
12046
+ if (!shouldStore) ignored++;
12047
+ allItems.push(item);
12048
+ }
12049
+ await this.storeSessionContext(options.conversationId, allItems, allInstructions);
12050
+ return {
12051
+ items: allItems,
12052
+ created,
12053
+ updated,
12054
+ linked,
12055
+ ignored,
12056
+ dedupMatches: allDedupMatches,
12057
+ provenance: allItems.map((i2) => i2.provenance)
12058
+ };
12059
+ }
12060
+ /**
12061
+ * Retrieve captured items for a conversation.
12062
+ */
12063
+ async getCapturedItems(conversationId) {
12064
+ const ctx = this.sessionContexts.get(conversationId);
12065
+ return ctx ? ctx.items : [];
12066
+ }
12067
+ /**
12068
+ * Get all captured items across all conversations.
12069
+ */
12070
+ async getAllCapturedItems() {
12071
+ const all = [];
12072
+ for (const ctx of this.sessionContexts.values()) {
12073
+ all.push(...ctx.items);
12074
+ }
12075
+ return all;
12076
+ }
12077
+ /**
12078
+ * Clear session context (for testing / cleanup).
12079
+ */
12080
+ clearSession(conversationId) {
12081
+ this.sessionContexts.delete(conversationId);
12082
+ }
12083
+ /**
12084
+ * Get capture engine configuration.
12085
+ */
12086
+ getConfig() {
12087
+ return { ...this.config };
12088
+ }
12089
+ // ---------------------------------------------------------------------------
12090
+ // Private
12091
+ // ---------------------------------------------------------------------------
12092
+ async storeSessionContext(conversationId, items, instructions) {
12093
+ let ctx = this.sessionContexts.get(conversationId);
12094
+ if (!ctx) {
12095
+ ctx = {
12096
+ conversationId,
12097
+ startedAt: nowIso(),
12098
+ items: [],
12099
+ explicitInstructions: []
12100
+ };
12101
+ this.sessionContexts.set(conversationId, ctx);
12102
+ }
12103
+ ctx.items.push(...items);
12104
+ ctx.explicitInstructions.push(...instructions);
12105
+ try {
12106
+ const stateKey = `${CAPTURE_STATE_KEY}:${conversationId}`;
12107
+ const existing = store.get(stateKey);
12108
+ let prior = [];
12109
+ if (existing) {
12110
+ try {
12111
+ prior = JSON.parse(existing);
12112
+ } catch {
12113
+ }
12114
+ }
12115
+ store.set(stateKey, JSON.stringify([...prior, ...items]));
12116
+ } catch {
12117
+ }
12118
+ }
12119
+ };
12120
+ captureEngine = new CaptureEngine();
12121
+ __name(captureConversation, "captureConversation");
12122
+ __name(captureTurn, "captureTurn");
12123
+ }
12124
+ });
11244
12125
 
11245
12126
  // core/memory/index.ts
11246
12127
  var memory_exports = {};
@@ -11268,6 +12149,7 @@ var init_memory = __esm({
11268
12149
  init_profile2();
11269
12150
  init_note();
11270
12151
  init_seed();
12152
+ init_capture_engine();
11271
12153
  init_security();
11272
12154
  init_detection_events();
11273
12155
  init_detection_journal();
@@ -11456,6 +12338,25 @@ var init_memory = __esm({
11456
12338
  async getSeedId() {
11457
12339
  return exports.SEED_ID;
11458
12340
  }
12341
+ },
12342
+ /** Automatic conversational capture engine (Phase 2 — Workstream B). */
12343
+ capture: {
12344
+ engine: captureEngine,
12345
+ async conversation(messages, options) {
12346
+ return captureConversation(messages, options);
12347
+ },
12348
+ async turn(text, options) {
12349
+ return captureTurn(text, options);
12350
+ },
12351
+ async getItems(conversationId) {
12352
+ return captureEngine.getCapturedItems(conversationId);
12353
+ },
12354
+ async getAllItems() {
12355
+ return captureEngine.getAllCapturedItems();
12356
+ },
12357
+ clearSession(conversationId) {
12358
+ captureEngine.clearSession(conversationId);
12359
+ }
11459
12360
  }
11460
12361
  };
11461
12362
  }
@@ -13333,7 +14234,7 @@ Storage and session:
13333
14234
  Utilities:
13334
14235
  codenames print reserved codenames
13335
14236
  render <view> render a markdown view from the knowledge store
13336
- spark --verify verify the dotmem-scintilla block in docs/AGENTS.md
14237
+ spark --verify verify the dotmem-scintilla block in specifications/AGENTS.md
13337
14238
  note <subcommand> store and retrieve markdown files and text blocks (add|list|show|export|forget)
13338
14239
  seed <file|-> development-only: inject the Joy initial memory seed (JHF, requires \`--dev\`)
13339
14240
 
@@ -13370,13 +14271,13 @@ Examples:
13370
14271
  npx dotmem --version
13371
14272
 
13372
14273
  Learn more:
13373
- docs/AGENTS.md AI working rules and the complete memory.* surface
13374
- docs/MEMORY.md the memory reference
14274
+ specifications/agents.md AI working rules and the complete memory.* surface
14275
+ specifications/memory.md the memory reference
13375
14276
  npx dotmem policy contract acquire --json
13376
14277
  the acquisition contract`;
13377
14278
  }
13378
14279
  __name(topLevelUsage, "topLevelUsage");
13379
- function dispatch3(command, args) {
14280
+ function dispatch4(command, args) {
13380
14281
  if (!command) {
13381
14282
  void runContextCli("current project");
13382
14283
  return;
@@ -13745,7 +14646,7 @@ ids by keeping the newest copy (by acquiredAt) and retracting the shadowed one.
13745
14646
  console.info(topLevelUsage());
13746
14647
  process2__default.default.exit(1);
13747
14648
  }
13748
- __name(dispatch3, "dispatch");
14649
+ __name(dispatch4, "dispatch");
13749
14650
  async function runCodenamesCli(args) {
13750
14651
  const json = args.includes("--json");
13751
14652
  if (json) {
@@ -14711,10 +15612,10 @@ async function runDoctorCli(args) {
14711
15612
  const policyStale = [];
14712
15613
  try {
14713
15614
  const { PolicyStore: PolicyStore2 } = await Promise.resolve().then(() => (init_policy_store(), policy_store_exports));
14714
- const store5 = new PolicyStore2();
15615
+ const store6 = new PolicyStore2();
14715
15616
  for (const ns of ["operator", "security"]) {
14716
15617
  try {
14717
- const doc = await store5.load(ns);
15618
+ const doc = await store6.load(ns);
14718
15619
  for (const record of doc.records) {
14719
15620
  if (record.condition && record.condition.kind === "payload-language") {
14720
15621
  policyStale.push({ namespace: ns, id: record.id, event: record.event, condition: JSON.stringify(record.condition) });
@@ -15570,7 +16471,7 @@ exports.detectAiEnvironment = detectAiEnvironment;
15570
16471
  exports.detectAuthorityRoleDrift = detectAuthorityRoleDrift;
15571
16472
  exports.detectIdentityFusion = detectIdentityFusion;
15572
16473
  exports.detectUnauthorizedTransitions = detectUnauthorizedTransitions;
15573
- exports.dispatch = dispatch3;
16474
+ exports.dispatch = dispatch4;
15574
16475
  exports.draftCorrectionExperience = draftCorrectionExperience;
15575
16476
  exports.encodeLogicSeed = encodeLogicSeed;
15576
16477
  exports.ensureProjectAnchor = ensureProjectAnchor;