iranti 0.2.51 → 0.3.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.
Files changed (163) hide show
  1. package/README.md +30 -17
  2. package/dist/scripts/api-key-create.js +1 -1
  3. package/dist/scripts/api-key-list.js +1 -1
  4. package/dist/scripts/api-key-revoke.js +1 -1
  5. package/dist/scripts/claude-code-memory-hook.js +116 -30
  6. package/dist/scripts/codex-setup.js +86 -4
  7. package/dist/scripts/iranti-cli.js +1359 -57
  8. package/dist/scripts/iranti-mcp.js +578 -75
  9. package/dist/scripts/seed.js +11 -6
  10. package/dist/scripts/setup.js +1 -1
  11. package/dist/src/api/healthChecks.d.ts +29 -0
  12. package/dist/src/api/healthChecks.d.ts.map +1 -0
  13. package/dist/src/api/healthChecks.js +72 -0
  14. package/dist/src/api/healthChecks.js.map +1 -0
  15. package/dist/src/api/middleware/validation.d.ts +22 -0
  16. package/dist/src/api/middleware/validation.d.ts.map +1 -1
  17. package/dist/src/api/middleware/validation.js +93 -3
  18. package/dist/src/api/middleware/validation.js.map +1 -1
  19. package/dist/src/api/routes/knowledge.d.ts.map +1 -1
  20. package/dist/src/api/routes/knowledge.js +53 -0
  21. package/dist/src/api/routes/knowledge.js.map +1 -1
  22. package/dist/src/api/routes/memory.d.ts.map +1 -1
  23. package/dist/src/api/routes/memory.js +73 -9
  24. package/dist/src/api/routes/memory.js.map +1 -1
  25. package/dist/src/api/server.js +38 -43
  26. package/dist/src/api/server.js.map +1 -1
  27. package/dist/src/attendant/AttendantInstance.d.ts +135 -2
  28. package/dist/src/attendant/AttendantInstance.d.ts.map +1 -1
  29. package/dist/src/attendant/AttendantInstance.js +1836 -93
  30. package/dist/src/attendant/AttendantInstance.js.map +1 -1
  31. package/dist/src/attendant/index.d.ts +1 -1
  32. package/dist/src/attendant/index.d.ts.map +1 -1
  33. package/dist/src/attendant/index.js +1 -1
  34. package/dist/src/attendant/index.js.map +1 -1
  35. package/dist/src/attendant/registry.d.ts.map +1 -1
  36. package/dist/src/attendant/registry.js +2 -0
  37. package/dist/src/attendant/registry.js.map +1 -1
  38. package/dist/src/chat/index.d.ts +23 -0
  39. package/dist/src/chat/index.d.ts.map +1 -1
  40. package/dist/src/chat/index.js +111 -22
  41. package/dist/src/chat/index.js.map +1 -1
  42. package/dist/src/generated/prisma/browser.d.ts +5 -0
  43. package/dist/src/generated/prisma/browser.d.ts.map +1 -1
  44. package/dist/src/generated/prisma/client.d.ts +5 -0
  45. package/dist/src/generated/prisma/client.d.ts.map +1 -1
  46. package/dist/src/generated/prisma/commonInputTypes.d.ts +48 -0
  47. package/dist/src/generated/prisma/commonInputTypes.d.ts.map +1 -1
  48. package/dist/src/generated/prisma/internal/class.d.ts +11 -0
  49. package/dist/src/generated/prisma/internal/class.d.ts.map +1 -1
  50. package/dist/src/generated/prisma/internal/class.js +4 -4
  51. package/dist/src/generated/prisma/internal/class.js.map +1 -1
  52. package/dist/src/generated/prisma/internal/prismaNamespace.d.ts +92 -1
  53. package/dist/src/generated/prisma/internal/prismaNamespace.d.ts.map +1 -1
  54. package/dist/src/generated/prisma/internal/prismaNamespace.js +17 -2
  55. package/dist/src/generated/prisma/internal/prismaNamespace.js.map +1 -1
  56. package/dist/src/generated/prisma/internal/prismaNamespaceBrowser.d.ts +16 -0
  57. package/dist/src/generated/prisma/internal/prismaNamespaceBrowser.d.ts.map +1 -1
  58. package/dist/src/generated/prisma/internal/prismaNamespaceBrowser.js +17 -2
  59. package/dist/src/generated/prisma/internal/prismaNamespaceBrowser.js.map +1 -1
  60. package/dist/src/generated/prisma/models/StaffEvent.d.ts +1184 -0
  61. package/dist/src/generated/prisma/models/StaffEvent.d.ts.map +1 -0
  62. package/dist/src/generated/prisma/models/StaffEvent.js +3 -0
  63. package/dist/src/generated/prisma/models/StaffEvent.js.map +1 -0
  64. package/dist/src/generated/prisma/models.d.ts +1 -0
  65. package/dist/src/generated/prisma/models.d.ts.map +1 -1
  66. package/dist/src/lib/assistantCheckpoint.d.ts +21 -0
  67. package/dist/src/lib/assistantCheckpoint.d.ts.map +1 -0
  68. package/dist/src/lib/assistantCheckpoint.js +143 -0
  69. package/dist/src/lib/assistantCheckpoint.js.map +1 -0
  70. package/dist/src/lib/autoRemember.d.ts +15 -0
  71. package/dist/src/lib/autoRemember.d.ts.map +1 -1
  72. package/dist/src/lib/autoRemember.js +433 -71
  73. package/dist/src/lib/autoRemember.js.map +1 -1
  74. package/dist/src/lib/cliHelpCatalog.d.ts.map +1 -1
  75. package/dist/src/lib/cliHelpCatalog.js +23 -11
  76. package/dist/src/lib/cliHelpCatalog.js.map +1 -1
  77. package/dist/src/lib/cliHelpRenderer.d.ts +1 -0
  78. package/dist/src/lib/cliHelpRenderer.d.ts.map +1 -1
  79. package/dist/src/lib/cliHelpRenderer.js +4 -0
  80. package/dist/src/lib/cliHelpRenderer.js.map +1 -1
  81. package/dist/src/lib/commandErrors.d.ts +5 -1
  82. package/dist/src/lib/commandErrors.d.ts.map +1 -1
  83. package/dist/src/lib/commandErrors.js +250 -17
  84. package/dist/src/lib/commandErrors.js.map +1 -1
  85. package/dist/src/lib/createFirstPartyIranti.d.ts.map +1 -1
  86. package/dist/src/lib/createFirstPartyIranti.js +1 -0
  87. package/dist/src/lib/createFirstPartyIranti.js.map +1 -1
  88. package/dist/src/lib/dbStaffEventEmitter.d.ts +2 -0
  89. package/dist/src/lib/dbStaffEventEmitter.d.ts.map +1 -1
  90. package/dist/src/lib/dbStaffEventEmitter.js +15 -0
  91. package/dist/src/lib/dbStaffEventEmitter.js.map +1 -1
  92. package/dist/src/lib/hostMemoryFormatting.d.ts +25 -0
  93. package/dist/src/lib/hostMemoryFormatting.d.ts.map +1 -0
  94. package/dist/src/lib/hostMemoryFormatting.js +55 -0
  95. package/dist/src/lib/hostMemoryFormatting.js.map +1 -0
  96. package/dist/src/lib/issueFacts.d.ts +37 -0
  97. package/dist/src/lib/issueFacts.d.ts.map +1 -0
  98. package/dist/src/lib/issueFacts.js +72 -0
  99. package/dist/src/lib/issueFacts.js.map +1 -0
  100. package/dist/src/lib/llm.d.ts +8 -0
  101. package/dist/src/lib/llm.d.ts.map +1 -1
  102. package/dist/src/lib/llm.js +33 -0
  103. package/dist/src/lib/llm.js.map +1 -1
  104. package/dist/src/lib/packageRoot.d.ts +2 -0
  105. package/dist/src/lib/packageRoot.d.ts.map +1 -0
  106. package/dist/src/lib/packageRoot.js +22 -0
  107. package/dist/src/lib/packageRoot.js.map +1 -0
  108. package/dist/src/lib/projectLearning.d.ts +21 -0
  109. package/dist/src/lib/projectLearning.d.ts.map +1 -0
  110. package/dist/src/lib/projectLearning.js +357 -0
  111. package/dist/src/lib/projectLearning.js.map +1 -0
  112. package/dist/src/lib/protocolEnforcement.d.ts +29 -0
  113. package/dist/src/lib/protocolEnforcement.d.ts.map +1 -0
  114. package/dist/src/lib/protocolEnforcement.js +124 -0
  115. package/dist/src/lib/protocolEnforcement.js.map +1 -0
  116. package/dist/src/lib/providers/claude.js +1 -1
  117. package/dist/src/lib/providers/claude.js.map +1 -1
  118. package/dist/src/lib/router.js +1 -1
  119. package/dist/src/lib/router.js.map +1 -1
  120. package/dist/src/lib/runtimeEnv.d.ts.map +1 -1
  121. package/dist/src/lib/runtimeEnv.js +8 -3
  122. package/dist/src/lib/runtimeEnv.js.map +1 -1
  123. package/dist/src/lib/scaffoldCloseout.d.ts +27 -0
  124. package/dist/src/lib/scaffoldCloseout.d.ts.map +1 -0
  125. package/dist/src/lib/scaffoldCloseout.js +139 -0
  126. package/dist/src/lib/scaffoldCloseout.js.map +1 -0
  127. package/dist/src/lib/semanticFactTags.d.ts +10 -0
  128. package/dist/src/lib/semanticFactTags.d.ts.map +1 -0
  129. package/dist/src/lib/semanticFactTags.js +166 -0
  130. package/dist/src/lib/semanticFactTags.js.map +1 -0
  131. package/dist/src/lib/sessionLedger.d.ts +94 -0
  132. package/dist/src/lib/sessionLedger.d.ts.map +1 -0
  133. package/dist/src/lib/sessionLedger.js +997 -0
  134. package/dist/src/lib/sessionLedger.js.map +1 -0
  135. package/dist/src/lib/sharedStateInvalidation.d.ts +10 -0
  136. package/dist/src/lib/sharedStateInvalidation.d.ts.map +1 -0
  137. package/dist/src/lib/sharedStateInvalidation.js +184 -0
  138. package/dist/src/lib/sharedStateInvalidation.js.map +1 -0
  139. package/dist/src/lib/staffEventsTable.d.ts +3 -0
  140. package/dist/src/lib/staffEventsTable.d.ts.map +1 -0
  141. package/dist/src/lib/staffEventsTable.js +58 -0
  142. package/dist/src/lib/staffEventsTable.js.map +1 -0
  143. package/dist/src/librarian/index.d.ts.map +1 -1
  144. package/dist/src/librarian/index.js +113 -2
  145. package/dist/src/librarian/index.js.map +1 -1
  146. package/dist/src/library/client.d.ts +6 -1
  147. package/dist/src/library/client.d.ts.map +1 -1
  148. package/dist/src/library/client.js +21 -7
  149. package/dist/src/library/client.js.map +1 -1
  150. package/dist/src/library/embeddings.d.ts +9 -1
  151. package/dist/src/library/embeddings.d.ts.map +1 -1
  152. package/dist/src/library/embeddings.js +28 -3
  153. package/dist/src/library/embeddings.js.map +1 -1
  154. package/dist/src/library/queries.d.ts.map +1 -1
  155. package/dist/src/library/queries.js +263 -46
  156. package/dist/src/library/queries.js.map +1 -1
  157. package/dist/src/sdk/index.d.ts +52 -1
  158. package/dist/src/sdk/index.d.ts.map +1 -1
  159. package/dist/src/sdk/index.js +546 -98
  160. package/dist/src/sdk/index.js.map +1 -1
  161. package/package.json +24 -3
  162. package/prisma/migrations/20260331101500_add_staff_events_ledger/migration.sql +24 -0
  163. package/prisma/schema.prisma +22 -0
@@ -17,6 +17,8 @@ exports.autoRememberAssistantFacts = autoRememberAssistantFacts;
17
17
  exports.rememberAssistantResponseFacts = rememberAssistantResponseFacts;
18
18
  exports.parseBackfillChatTranscript = parseBackfillChatTranscript;
19
19
  exports.backfillChatHistory = backfillChatHistory;
20
+ const staffEventRegistry_1 = require("./staffEventRegistry");
21
+ const semanticFactTags_1 = require("./semanticFactTags");
20
22
  exports.USER_PROMPT_AUTO_REMEMBER_SOURCE = 'UserPromptAutoRemember';
21
23
  const PERSONAL_MEMORY_KEYS = new Set([
22
24
  'name',
@@ -69,8 +71,380 @@ function buildTextFact(key, value, scope = 'personal') {
69
71
  key: cleanKey,
70
72
  value: { text: cleanValue },
71
73
  summary: `${cleanKey.replace(/_/g, ' ')} is ${cleanValue}`,
74
+ durableClass: cleanKey.startsWith('favorite_') ? 'preference' : 'profile',
72
75
  };
73
76
  }
77
+ function dedupeStrings(values) {
78
+ const seen = new Set();
79
+ const deduped = [];
80
+ for (const value of values) {
81
+ const trimmed = value.trim();
82
+ if (!trimmed)
83
+ continue;
84
+ const normalized = trimmed.toLowerCase();
85
+ if (seen.has(normalized))
86
+ continue;
87
+ seen.add(normalized);
88
+ deduped.push(trimmed);
89
+ }
90
+ return deduped;
91
+ }
92
+ function dedupeObjects(values) {
93
+ const seen = new Set();
94
+ const deduped = [];
95
+ for (const value of values) {
96
+ const identity = JSON.stringify(value);
97
+ if (seen.has(identity))
98
+ continue;
99
+ seen.add(identity);
100
+ deduped.push(value);
101
+ }
102
+ return deduped;
103
+ }
104
+ function normalizeListItems(raw) {
105
+ return raw
106
+ .split(/(?:,|;|\band\b)/i)
107
+ .map((item) => item.trim())
108
+ .filter(Boolean);
109
+ }
110
+ function trimSummary(text, max = 220) {
111
+ const normalized = text.trim();
112
+ if (normalized.length <= max)
113
+ return normalized;
114
+ return `${normalized.slice(0, max - 3).trimEnd()}...`;
115
+ }
116
+ function readStringArray(value, preferredKey) {
117
+ if (Array.isArray(value)) {
118
+ return value.map((item) => String(item ?? '').trim()).filter(Boolean);
119
+ }
120
+ if (typeof value !== 'object' || value === null)
121
+ return [];
122
+ const record = value;
123
+ const direct = record[preferredKey];
124
+ if (Array.isArray(direct)) {
125
+ return direct.map((item) => String(item ?? '').trim()).filter(Boolean);
126
+ }
127
+ if (Array.isArray(record.items)) {
128
+ return record.items.map((item) => String(item ?? '').trim()).filter(Boolean);
129
+ }
130
+ return [];
131
+ }
132
+ function readFileChangeItems(value) {
133
+ if (typeof value !== 'object' || value === null)
134
+ return [];
135
+ const record = value;
136
+ const items = Array.isArray(record.items) ? record.items : [];
137
+ return items.filter((item) => typeof item === 'object' && item !== null);
138
+ }
139
+ function readActionItems(value) {
140
+ if (typeof value !== 'object' || value === null)
141
+ return [];
142
+ const record = value;
143
+ const items = Array.isArray(record.items) ? record.items : [];
144
+ return items.filter((item) => typeof item === 'object' && item !== null);
145
+ }
146
+ function mergeFactValue(key, existing, incoming) {
147
+ if (key === 'open_risks') {
148
+ return { items: dedupeStrings([...readStringArray(existing, 'items'), ...readStringArray(incoming, 'items')]) };
149
+ }
150
+ if (key === 'important_artifacts') {
151
+ return { items: dedupeStrings([...readStringArray(existing, 'items'), ...readStringArray(incoming, 'items'), ...readStringArray(existing, 'files'), ...readStringArray(incoming, 'files')]) };
152
+ }
153
+ if (key === 'failed_paths' || key === 'alternative_routes') {
154
+ return { items: dedupeStrings([...readStringArray(existing, 'items'), ...readStringArray(incoming, 'items')]) };
155
+ }
156
+ if (key === 'recent_file_changes') {
157
+ return { items: dedupeObjects([...readFileChangeItems(existing), ...readFileChangeItems(incoming)]) };
158
+ }
159
+ if (key === 'recent_actions') {
160
+ return { items: dedupeObjects([...readActionItems(existing), ...readActionItems(incoming)]) };
161
+ }
162
+ return incoming;
163
+ }
164
+ function summarizeMergedFact(key, value, fallback) {
165
+ if (key === 'open_risks') {
166
+ const items = readStringArray(value, 'items');
167
+ return items.length > 0 ? trimSummary(`open risks include ${items.join('; ')}`) : fallback;
168
+ }
169
+ if (key === 'important_artifacts') {
170
+ const items = readStringArray(value, 'items');
171
+ return items.length > 0 ? trimSummary(`important artifacts include ${items.join('; ')}`) : fallback;
172
+ }
173
+ if (key === 'failed_paths') {
174
+ const items = readStringArray(value, 'items');
175
+ return items.length > 0 ? trimSummary(`failed paths include ${items.join('; ')}`) : fallback;
176
+ }
177
+ if (key === 'alternative_routes') {
178
+ const items = readStringArray(value, 'items');
179
+ return items.length > 0 ? trimSummary(`alternative routes include ${items.join('; ')}`) : fallback;
180
+ }
181
+ if (key === 'recent_file_changes') {
182
+ const items = readFileChangeItems(value).map((item) => {
183
+ const action = String(item.action ?? 'updated').trim();
184
+ const path = String(item.path ?? '').trim();
185
+ const toPath = String(item.toPath ?? '').trim();
186
+ const purpose = String(item.purpose ?? '').trim();
187
+ if (!path)
188
+ return '';
189
+ if (toPath)
190
+ return `${action} ${path} -> ${toPath}${purpose ? ` (${purpose})` : ''}`;
191
+ return `${action} ${path}${purpose ? ` (${purpose})` : ''}`;
192
+ }).filter(Boolean);
193
+ return items.length > 0 ? trimSummary(`recent file changes include ${items.join('; ')}`) : fallback;
194
+ }
195
+ if (key === 'recent_actions') {
196
+ const items = readActionItems(value).map((item) => {
197
+ const kind = String(item.kind ?? 'action').trim();
198
+ const summary = String(item.summary ?? '').trim();
199
+ const status = String(item.status ?? '').trim();
200
+ if (!summary)
201
+ return '';
202
+ return status ? `[${status}] ${kind}: ${summary}` : `${kind}: ${summary}`;
203
+ }).filter(Boolean);
204
+ return items.length > 0 ? trimSummary(`recent actions include ${items.join('; ')}`) : fallback;
205
+ }
206
+ return fallback;
207
+ }
208
+ function buildFactProperties(fact, phase) {
209
+ const mergeStrategy = ['open_risks', 'important_artifacts', 'recent_file_changes', 'recent_actions', 'failed_paths', 'alternative_routes'].includes(fact.key)
210
+ ? 'append_dedupe'
211
+ : 'replace';
212
+ return {
213
+ memoryScope: fact.scope,
214
+ capturePhase: phase,
215
+ durableClass: fact.durableClass,
216
+ canonicalKey: fact.key,
217
+ mergeStrategy,
218
+ ...(0, semanticFactTags_1.buildSemanticFactTags)({
219
+ memoryScope: fact.scope,
220
+ durableClass: fact.durableClass,
221
+ mergeStrategy,
222
+ }),
223
+ };
224
+ }
225
+ function extractProjectOperationalFacts(text) {
226
+ const facts = [];
227
+ const decisionMatch = text.match(/^we decided(?: that)? (.+)$/i);
228
+ if (decisionMatch) {
229
+ facts.push({
230
+ scope: 'project',
231
+ key: 'decision',
232
+ value: { text: decisionMatch[1].trim() },
233
+ summary: `decision is ${decisionMatch[1].trim()}`,
234
+ durableClass: 'decision',
235
+ });
236
+ }
237
+ const nextStepMatch = text.match(/^(?:the )?next step is (.+)$/i);
238
+ if (nextStepMatch) {
239
+ facts.push({
240
+ scope: 'project',
241
+ key: 'next_step',
242
+ value: { instruction: nextStepMatch[1].trim() },
243
+ summary: `next step is ${nextStepMatch[1].trim()}`,
244
+ durableClass: 'next_step',
245
+ });
246
+ }
247
+ const currentStepMatch = text.match(/^(?:the )?current step is (.+)$/i);
248
+ if (currentStepMatch) {
249
+ facts.push({
250
+ scope: 'project',
251
+ key: 'current_step',
252
+ value: { text: currentStepMatch[1].trim() },
253
+ summary: `current step is ${currentStepMatch[1].trim()}`,
254
+ durableClass: 'current_step',
255
+ });
256
+ }
257
+ const blockerMatch = text.match(/^(?:the )?blocker is (.+)$/i);
258
+ if (blockerMatch) {
259
+ facts.push({
260
+ scope: 'project',
261
+ key: 'blocker',
262
+ value: { text: blockerMatch[1].trim() },
263
+ summary: `blocker is ${blockerMatch[1].trim()}`,
264
+ durableClass: 'blocker',
265
+ });
266
+ }
267
+ const ownerMatch = text.match(/^(?:the )?current owner is (.+)$/i);
268
+ if (ownerMatch) {
269
+ facts.push({
270
+ scope: 'project',
271
+ key: 'current_owner',
272
+ value: { text: ownerMatch[1].trim() },
273
+ summary: `current owner is ${ownerMatch[1].trim()}`,
274
+ durableClass: 'owner',
275
+ });
276
+ }
277
+ const openRisksMatch = text.match(/^(?:the )?(?:open risks?|risks?) (?:are|include) (.+)$/i);
278
+ if (openRisksMatch) {
279
+ const items = dedupeStrings(normalizeListItems(openRisksMatch[1]));
280
+ if (items.length > 0) {
281
+ facts.push({
282
+ scope: 'project',
283
+ key: 'open_risks',
284
+ value: { items },
285
+ summary: trimSummary(`open risks include ${items.join('; ')}`),
286
+ durableClass: 'open_risks',
287
+ });
288
+ }
289
+ }
290
+ const artifactsMatch = text.match(/^(?:important )?artifacts? (?:are|include) (.+)$/i);
291
+ if (artifactsMatch) {
292
+ const items = dedupeStrings(normalizeListItems(artifactsMatch[1]));
293
+ if (items.length > 0) {
294
+ facts.push({
295
+ scope: 'project',
296
+ key: 'important_artifacts',
297
+ value: { items },
298
+ summary: trimSummary(`important artifacts include ${items.join('; ')}`),
299
+ durableClass: 'artifact',
300
+ });
301
+ }
302
+ }
303
+ const failedPathMatch = text.match(/^(?:the )?failed path (?:is|was) (.+)$/i);
304
+ if (failedPathMatch) {
305
+ const items = dedupeStrings(normalizeListItems(failedPathMatch[1]));
306
+ if (items.length > 0) {
307
+ facts.push({
308
+ scope: 'project',
309
+ key: 'failed_paths',
310
+ value: { items },
311
+ summary: trimSummary(`failed paths include ${items.join('; ')}`),
312
+ durableClass: 'failed_path',
313
+ });
314
+ }
315
+ }
316
+ const alternativeRouteMatch = text.match(/^(?:the )?alternative route (?:is|was) (.+)$/i);
317
+ if (alternativeRouteMatch) {
318
+ const items = dedupeStrings(normalizeListItems(alternativeRouteMatch[1]));
319
+ if (items.length > 0) {
320
+ facts.push({
321
+ scope: 'project',
322
+ key: 'alternative_routes',
323
+ value: { items },
324
+ summary: trimSummary(`alternative routes include ${items.join('; ')}`),
325
+ durableClass: 'alternative_route',
326
+ });
327
+ }
328
+ }
329
+ const createdFileMatch = text.match(/^file created (\S+)(?: for (.+))?$/i);
330
+ if (createdFileMatch) {
331
+ facts.push({
332
+ scope: 'project',
333
+ key: 'recent_file_changes',
334
+ value: { items: [{ action: 'created', path: createdFileMatch[1].trim(), purpose: createdFileMatch[2]?.trim() || undefined }] },
335
+ summary: trimSummary(`recent file changes include created ${createdFileMatch[1].trim()}${createdFileMatch[2]?.trim() ? ` (${createdFileMatch[2].trim()})` : ''}`),
336
+ durableClass: 'file_change',
337
+ });
338
+ }
339
+ const movedFileMatch = text.match(/^file moved (\S+) to (\S+)(?: for (.+))?$/i);
340
+ if (movedFileMatch) {
341
+ facts.push({
342
+ scope: 'project',
343
+ key: 'recent_file_changes',
344
+ value: { items: [{ action: 'moved', path: movedFileMatch[1].trim(), toPath: movedFileMatch[2].trim(), purpose: movedFileMatch[3]?.trim() || undefined }] },
345
+ summary: trimSummary(`recent file changes include moved ${movedFileMatch[1].trim()} to ${movedFileMatch[2].trim()}${movedFileMatch[3]?.trim() ? ` (${movedFileMatch[3].trim()})` : ''}`),
346
+ durableClass: 'file_change',
347
+ });
348
+ }
349
+ const renamedFileMatch = text.match(/^file renamed (\S+) to (\S+)(?: for (.+))?$/i);
350
+ if (renamedFileMatch) {
351
+ facts.push({
352
+ scope: 'project',
353
+ key: 'recent_file_changes',
354
+ value: { items: [{ action: 'renamed', path: renamedFileMatch[1].trim(), toPath: renamedFileMatch[2].trim(), purpose: renamedFileMatch[3]?.trim() || undefined }] },
355
+ summary: trimSummary(`recent file changes include renamed ${renamedFileMatch[1].trim()} to ${renamedFileMatch[2].trim()}${renamedFileMatch[3]?.trim() ? ` (${renamedFileMatch[3].trim()})` : ''}`),
356
+ durableClass: 'file_change',
357
+ });
358
+ }
359
+ const deletedFileMatch = text.match(/^file deleted (\S+)(?: because (.+))?$/i);
360
+ if (deletedFileMatch) {
361
+ facts.push({
362
+ scope: 'project',
363
+ key: 'recent_file_changes',
364
+ value: { items: [{ action: 'deleted', path: deletedFileMatch[1].trim(), purpose: deletedFileMatch[2]?.trim() || undefined }] },
365
+ summary: trimSummary(`recent file changes include deleted ${deletedFileMatch[1].trim()}${deletedFileMatch[2]?.trim() ? ` (${deletedFileMatch[2].trim()})` : ''}`),
366
+ durableClass: 'file_change',
367
+ });
368
+ }
369
+ const commandRanMatch = text.match(/^command ran (.+)$/i);
370
+ if (commandRanMatch) {
371
+ const target = commandRanMatch[1].trim();
372
+ facts.push({
373
+ scope: 'project',
374
+ key: 'recent_actions',
375
+ value: { items: [{ kind: 'command', summary: `ran ${target}`, status: 'completed', target }] },
376
+ summary: trimSummary(`recent actions include [completed] command: ran ${target}`),
377
+ durableClass: 'action_log',
378
+ });
379
+ }
380
+ const commandFailedMatch = text.match(/^command failed (.+)$/i);
381
+ if (commandFailedMatch) {
382
+ const target = commandFailedMatch[1].trim();
383
+ facts.push({
384
+ scope: 'project',
385
+ key: 'recent_actions',
386
+ value: { items: [{ kind: 'command', summary: `failed ${target}`, status: 'failed', target }] },
387
+ summary: trimSummary(`recent actions include [failed] command: failed ${target}`),
388
+ durableClass: 'action_log',
389
+ });
390
+ }
391
+ const testPassedMatch = text.match(/^test passed (.+)$/i);
392
+ if (testPassedMatch) {
393
+ const target = testPassedMatch[1].trim();
394
+ facts.push({
395
+ scope: 'project',
396
+ key: 'recent_actions',
397
+ value: { items: [{ kind: 'test', summary: `passed ${target}`, status: 'passed', target }] },
398
+ summary: trimSummary(`recent actions include [passed] test: passed ${target}`),
399
+ durableClass: 'action_log',
400
+ });
401
+ }
402
+ const testFailedMatch = text.match(/^test failed (.+)$/i);
403
+ if (testFailedMatch) {
404
+ const target = testFailedMatch[1].trim();
405
+ facts.push({
406
+ scope: 'project',
407
+ key: 'recent_actions',
408
+ value: { items: [{ kind: 'test', summary: `failed ${target}`, status: 'failed', target }] },
409
+ summary: trimSummary(`recent actions include [failed] test: failed ${target}`),
410
+ durableClass: 'action_log',
411
+ });
412
+ }
413
+ const validationPassedMatch = text.match(/^validation passed (.+)$/i);
414
+ if (validationPassedMatch) {
415
+ const target = validationPassedMatch[1].trim();
416
+ facts.push({
417
+ scope: 'project',
418
+ key: 'recent_actions',
419
+ value: { items: [{ kind: 'validation', summary: `passed ${target}`, status: 'passed', target }] },
420
+ summary: trimSummary(`recent actions include [passed] validation: passed ${target}`),
421
+ durableClass: 'action_log',
422
+ });
423
+ }
424
+ const validationFailedMatch = text.match(/^validation failed (.+)$/i);
425
+ if (validationFailedMatch) {
426
+ const target = validationFailedMatch[1].trim();
427
+ facts.push({
428
+ scope: 'project',
429
+ key: 'recent_actions',
430
+ value: { items: [{ kind: 'validation', summary: `failed ${target}`, status: 'failed', target }] },
431
+ summary: trimSummary(`recent actions include [failed] validation: failed ${target}`),
432
+ durableClass: 'action_log',
433
+ });
434
+ }
435
+ const searchRanMatch = text.match(/^search ran (.+)$/i);
436
+ if (searchRanMatch) {
437
+ const target = searchRanMatch[1].trim();
438
+ facts.push({
439
+ scope: 'project',
440
+ key: 'recent_actions',
441
+ value: { items: [{ kind: 'search', summary: `ran ${target}`, status: 'completed', target }] },
442
+ summary: trimSummary(`recent actions include [completed] search: ran ${target}`),
443
+ durableClass: 'action_log',
444
+ });
445
+ }
446
+ return facts;
447
+ }
74
448
  function isAutoRememberEnabled() {
75
449
  const raw = process.env.IRANTI_AUTO_REMEMBER?.trim().toLowerCase();
76
450
  return raw === '1' || raw === 'true' || raw === 'yes' || raw === 'on';
@@ -144,7 +518,7 @@ function classifyMemoryScope(message) {
144
518
  const normalized = normalizePrompt(message).toLowerCase();
145
519
  if (!normalized)
146
520
  return null;
147
- if (/\b(next step|blocker|decision|current owner)\b/.test(normalized)) {
521
+ if (/\b(next step|current step|blocker|decision|current owner|open risks?|artifacts?|failed path|alternative route|file (?:created|moved|renamed|deleted))\b/.test(normalized)) {
148
522
  return 'project';
149
523
  }
150
524
  if (/\b(my|me|i)\b/.test(normalized)) {
@@ -222,6 +596,30 @@ function detectMandatoryRecall(message) {
222
596
  reason: 'project_owner_recall',
223
597
  };
224
598
  }
599
+ if (/\b(?:what|remind me)(?:[^.?!]*)\bcurrent step\b/i.test(normalized)) {
600
+ return {
601
+ required: true,
602
+ scope: 'project',
603
+ key: 'current_step',
604
+ reason: 'project_current_step_recall',
605
+ };
606
+ }
607
+ if (/\b(?:what|which|remind me)(?:[^.?!]*)\bopen risks?\b/i.test(normalized)) {
608
+ return {
609
+ required: true,
610
+ scope: 'project',
611
+ key: 'open_risks',
612
+ reason: 'project_open_risks_recall',
613
+ };
614
+ }
615
+ if (/\b(?:what|which|remind me)(?:[^.?!]*)\b(?:important )?artifacts?\b/i.test(normalized)) {
616
+ return {
617
+ required: true,
618
+ scope: 'project',
619
+ key: 'important_artifacts',
620
+ reason: 'project_artifacts_recall',
621
+ };
622
+ }
225
623
  return { required: false, scope: null };
226
624
  }
227
625
  function extractExplicitPromptMemory(prompt) {
@@ -256,33 +654,7 @@ function extractExplicitPromptMemory(prompt) {
256
654
  if (preferenceMatch) {
257
655
  facts.push(buildTextFact('likes', preferenceMatch[1]));
258
656
  }
259
- const decisionMatch = stripped.match(/^we decided(?: that)? (.+)$/i);
260
- if (decisionMatch) {
261
- facts.push({
262
- scope: 'project',
263
- key: 'decision',
264
- value: { text: decisionMatch[1].trim() },
265
- summary: `decision is ${decisionMatch[1].trim()}`,
266
- });
267
- }
268
- const nextStepMatch = stripped.match(/^(?:the )?next step is (.+)$/i);
269
- if (nextStepMatch) {
270
- facts.push({
271
- scope: 'project',
272
- key: 'next_step',
273
- value: { instruction: nextStepMatch[1].trim() },
274
- summary: `next step is ${nextStepMatch[1].trim()}`,
275
- });
276
- }
277
- const blockerMatch = stripped.match(/^(?:the )?blocker is (.+)$/i);
278
- if (blockerMatch) {
279
- facts.push({
280
- scope: 'project',
281
- key: 'blocker',
282
- value: { text: blockerMatch[1].trim() },
283
- summary: `blocker is ${blockerMatch[1].trim()}`,
284
- });
285
- }
657
+ facts.push(...extractProjectOperationalFacts(stripped));
286
658
  const deduped = new Map();
287
659
  for (const fact of facts) {
288
660
  if (!fact.key || fact.summary.length > 220)
@@ -308,42 +680,7 @@ function extractExplicitAssistantMemory(response) {
308
680
  })) {
309
681
  facts.push(buildTextFact(yourFieldMatch[1], yourFieldMatch[2]));
310
682
  }
311
- const decisionMatch = lower.match(/^we decided(?: that)? (.+)$/i);
312
- if (decisionMatch) {
313
- facts.push({
314
- scope: 'project',
315
- key: 'decision',
316
- value: { text: decisionMatch[1].trim() },
317
- summary: `decision is ${decisionMatch[1].trim()}`,
318
- });
319
- }
320
- const nextStepMatch = lower.match(/^(?:the )?next step is (.+)$/i);
321
- if (nextStepMatch) {
322
- facts.push({
323
- scope: 'project',
324
- key: 'next_step',
325
- value: { instruction: nextStepMatch[1].trim() },
326
- summary: `next step is ${nextStepMatch[1].trim()}`,
327
- });
328
- }
329
- const blockerMatch = lower.match(/^(?:the )?blocker is (.+)$/i);
330
- if (blockerMatch) {
331
- facts.push({
332
- scope: 'project',
333
- key: 'blocker',
334
- value: { text: blockerMatch[1].trim() },
335
- summary: `blocker is ${blockerMatch[1].trim()}`,
336
- });
337
- }
338
- const ownerMatch = lower.match(/^(?:the )?current owner is (.+)$/i);
339
- if (ownerMatch) {
340
- facts.push({
341
- scope: 'project',
342
- key: 'current_owner',
343
- value: { text: ownerMatch[1].trim() },
344
- summary: `current owner is ${ownerMatch[1].trim()}`,
345
- });
346
- }
683
+ facts.push(...extractProjectOperationalFacts(lower));
347
684
  const deduped = new Map();
348
685
  for (const fact of facts) {
349
686
  if (!fact.key || fact.summary.length > 220)
@@ -361,7 +698,7 @@ function comparableValue(value) {
361
698
  }
362
699
  }
363
700
  async function persistExtractedFacts(params) {
364
- const { iranti, facts, agent, source, phase, confidence, entity, projectEntity, personalEntity, } = params;
701
+ const { iranti, facts, agent, source, phase, confidence, entity, projectEntity, personalEntity, ledgerContext, } = params;
365
702
  const skipped = [];
366
703
  let written = 0;
367
704
  const writtenEntities = new Set();
@@ -374,7 +711,9 @@ async function persistExtractedFacts(params) {
374
711
  continue;
375
712
  }
376
713
  const existing = await iranti.query(targetEntity, fact.key).catch(() => ({ found: false }));
377
- if (existing.found && comparableValue(existing.value) === comparableValue(fact.value)) {
714
+ const mergedValue = existing.found ? mergeFactValue(fact.key, existing.value, fact.value) : fact.value;
715
+ const mergedSummary = summarizeMergedFact(fact.key, mergedValue, fact.summary);
716
+ if (existing.found && comparableValue(existing.value) === comparableValue(mergedValue)) {
378
717
  skipped.push({ key: fact.key, reason: 'unchanged' });
379
718
  continue;
380
719
  }
@@ -384,12 +723,32 @@ async function persistExtractedFacts(params) {
384
723
  await iranti.write({
385
724
  entity: targetEntity,
386
725
  key: fact.key,
387
- value: fact.value,
388
- summary: fact.summary,
726
+ value: mergedValue,
727
+ summary: mergedSummary,
389
728
  confidence,
390
729
  source: writeSource,
391
730
  agent,
731
+ properties: buildFactProperties(fact, phase),
392
732
  });
733
+ if (phase === 'assistant_response') {
734
+ (0, staffEventRegistry_1.getStaffEventEmitter)().emit({
735
+ staffComponent: 'Attendant',
736
+ actionType: 'summary_written',
737
+ agentId: agent,
738
+ source: ledgerContext?.source?.trim() || source,
739
+ entityType: targetEntity.split('/', 2)[0],
740
+ entityId: targetEntity.split('/', 2)[1],
741
+ key: fact.key,
742
+ reason: 'strict_assistant_summary_persisted',
743
+ level: 'audit',
744
+ metadata: {
745
+ memoryScope: fact.scope,
746
+ durableClass: fact.durableClass,
747
+ capturePhase: phase,
748
+ ...(ledgerContext?.host ? { host: ledgerContext.host } : {}),
749
+ },
750
+ });
751
+ }
393
752
  written += 1;
394
753
  writtenEntities.add(targetEntity);
395
754
  }
@@ -402,7 +761,7 @@ async function persistExtractedFacts(params) {
402
761
  };
403
762
  }
404
763
  async function autoRememberPromptFacts(params) {
405
- const { iranti, prompt, agent, source, entity, projectEntity, personalEntity, confidence = 96, } = params;
764
+ const { iranti, prompt, agent, source, entity, projectEntity, personalEntity, confidence = 96, ledgerContext, } = params;
406
765
  if (!isAutoRememberEnabled()) {
407
766
  return {
408
767
  enabled: false,
@@ -422,10 +781,11 @@ async function autoRememberPromptFacts(params) {
422
781
  entity,
423
782
  projectEntity,
424
783
  personalEntity,
784
+ ledgerContext,
425
785
  });
426
786
  }
427
787
  async function autoRememberAssistantFacts(params) {
428
- const { iranti, response, agent, source, entity, projectEntity, personalEntity, confidence = 90, } = params;
788
+ const { iranti, response, agent, source, entity, projectEntity, personalEntity, confidence = 90, ledgerContext, } = params;
429
789
  if (!isAutoRememberEnabled()) {
430
790
  return {
431
791
  enabled: false,
@@ -445,10 +805,11 @@ async function autoRememberAssistantFacts(params) {
445
805
  entity,
446
806
  projectEntity,
447
807
  personalEntity,
808
+ ledgerContext,
448
809
  });
449
810
  }
450
811
  async function rememberAssistantResponseFacts(params) {
451
- const { iranti, response, agent, source, entity, projectEntity, personalEntity, confidence = 90, } = params;
812
+ const { iranti, response, agent, source, entity, projectEntity, personalEntity, confidence = 90, ledgerContext, } = params;
452
813
  return persistExtractedFacts({
453
814
  iranti,
454
815
  facts: extractExplicitAssistantMemory(response),
@@ -459,6 +820,7 @@ async function rememberAssistantResponseFacts(params) {
459
820
  entity,
460
821
  projectEntity,
461
822
  personalEntity,
823
+ ledgerContext,
462
824
  });
463
825
  }
464
826
  function parseBackfillChatTranscript(content) {