@prodero/curator 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (137) hide show
  1. package/ARCHITECTURE.md +298 -0
  2. package/CHANGELOG.md +16 -0
  3. package/CONTRIBUTING.md +9 -0
  4. package/LICENSE +21 -0
  5. package/README.md +94 -2
  6. package/SECURITY.md +7 -0
  7. package/bin/prodero +7 -0
  8. package/dist/cli.js +228 -0
  9. package/dist/context-instructions.js +2 -0
  10. package/dist/contracts.js +50 -0
  11. package/dist/conversation-presentation.js +262 -0
  12. package/dist/core/agent-instructions.js +39 -0
  13. package/dist/core/applicability-label.js +11 -0
  14. package/dist/core/applicability.js +143 -0
  15. package/dist/core/bootstrap.js +129 -0
  16. package/dist/core/consolidation.js +315 -0
  17. package/dist/core/constants.js +21 -0
  18. package/dist/core/context-allocation.js +54 -0
  19. package/dist/core/context-snapshot.js +12 -0
  20. package/dist/core/context.js +489 -0
  21. package/dist/core/curation-clauses.js +68 -0
  22. package/dist/core/curation-evidence.js +98 -0
  23. package/dist/core/curation-graphical.js +105 -0
  24. package/dist/core/curation-review-presentation.js +4 -0
  25. package/dist/core/curation-review-state.js +62 -0
  26. package/dist/core/curation-review.js +573 -0
  27. package/dist/core/curation-revision.js +106 -0
  28. package/dist/core/curation-semantic-contract.js +239 -0
  29. package/dist/core/curation-semantic-jobs.js +280 -0
  30. package/dist/core/diagnostics.js +17 -0
  31. package/dist/core/format.js +114 -0
  32. package/dist/core/ids.js +6 -0
  33. package/dist/core/imports.js +169 -0
  34. package/dist/core/index.js +93 -0
  35. package/dist/core/instruction-review.js +86 -0
  36. package/dist/core/integrity-files.js +174 -0
  37. package/dist/core/intelligence.js +95 -0
  38. package/dist/core/knowledge-analysis.js +46 -0
  39. package/dist/core/knowledge-authority.js +143 -0
  40. package/dist/core/knowledge-curation.js +316 -0
  41. package/dist/core/knowledge-engine.js +475 -0
  42. package/dist/core/knowledge-graph.js +191 -0
  43. package/dist/core/knowledge-lifecycle.js +212 -0
  44. package/dist/core/knowledge-management.js +113 -0
  45. package/dist/core/knowledge-migration.js +84 -0
  46. package/dist/core/knowledge-policy.js +38 -0
  47. package/dist/core/knowledge-provenance.js +90 -0
  48. package/dist/core/knowledge-write-policy.js +85 -0
  49. package/dist/core/language.js +14 -0
  50. package/dist/core/matching.js +6 -0
  51. package/dist/core/memory-index.js +239 -0
  52. package/dist/core/memory-intelligence.js +288 -0
  53. package/dist/core/memory-link-repair.js +66 -0
  54. package/dist/core/memory-operations.js +122 -0
  55. package/dist/core/memory-pressure.js +212 -0
  56. package/dist/core/memory-sync-curator.js +62 -0
  57. package/dist/core/memory-sync.js +463 -0
  58. package/dist/core/memory-transformation-bundle.js +163 -0
  59. package/dist/core/memory-transformation-proposal.js +135 -0
  60. package/dist/core/memory-transformation.js +108 -0
  61. package/dist/core/memory.js +186 -0
  62. package/dist/core/migrations.js +569 -0
  63. package/dist/core/path-scope.js +27 -0
  64. package/dist/core/plugin-files.js +116 -0
  65. package/dist/core/project-root.js +39 -0
  66. package/dist/core/project.js +634 -0
  67. package/dist/core/promotion.js +279 -0
  68. package/dist/core/reconciliation.js +61 -0
  69. package/dist/core/secrets.js +67 -0
  70. package/dist/core/semantic-safety.js +33 -0
  71. package/dist/core/session-index.js +112 -0
  72. package/dist/core/session-review.js +150 -0
  73. package/dist/core/sessions.js +289 -0
  74. package/dist/core/settings.js +76 -0
  75. package/dist/core/skill-curation.js +239 -0
  76. package/dist/core/skill-polish.js +41 -0
  77. package/dist/core/skills.js +368 -0
  78. package/dist/core/storage.js +408 -0
  79. package/dist/core/sync-transaction.js +254 -0
  80. package/dist/core/telemetry.js +64 -0
  81. package/dist/core/templates.js +53 -0
  82. package/dist/core/types.js +2 -0
  83. package/dist/core/update.js +320 -0
  84. package/dist/curation-review-adapter.js +92 -0
  85. package/dist/diagnostic-boundary.js +54 -0
  86. package/dist/filesystem/permissions.js +39 -0
  87. package/dist/hosts/adapters.js +119 -0
  88. package/dist/hosts/files.js +133 -0
  89. package/dist/hosts/manager.js +150 -0
  90. package/dist/hosts/public-entry.js +47 -0
  91. package/dist/hosts/runtime-command.js +66 -0
  92. package/dist/hosts/runtime.js +139 -0
  93. package/dist/hosts/wizard.js +114 -0
  94. package/dist/knowledge-table.js +38 -0
  95. package/dist/mcp-development.js +69 -0
  96. package/dist/mcp-workspace.js +141 -0
  97. package/dist/memory-presentation.js +172 -0
  98. package/dist/plugins/api.js +2 -0
  99. package/dist/plugins/context.js +20 -0
  100. package/dist/plugins/mcp.js +72 -0
  101. package/dist/plugins/registry.js +106 -0
  102. package/dist/presentation-cards.js +32 -0
  103. package/dist/presentation-server.js +79 -0
  104. package/dist/presentation-trace.js +174 -0
  105. package/dist/presentation.js +412 -0
  106. package/dist/providers/mcp-sampling.js +45 -0
  107. package/dist/server.js +656 -0
  108. package/dist/settings-definition.js +22 -0
  109. package/dist/skill-instructions.js +2 -0
  110. package/dist-ui/index.html +214 -0
  111. package/docs/APPLICABILITY.md +151 -0
  112. package/docs/COMPATIBILITY.md +49 -0
  113. package/docs/CONTEXT-SELECTION-V2.md +61 -0
  114. package/docs/CURATION-APP-REVIEW.md +156 -0
  115. package/docs/CURATION-CLAUSE-REFINEMENT.md +45 -0
  116. package/docs/IMPORTER-CURATION.md +142 -0
  117. package/docs/KNOWLEDGE-AUTHORITY.md +165 -0
  118. package/docs/KNOWLEDGE-SCHEMA-V2.md +150 -0
  119. package/docs/MIGRATIONS.md +99 -0
  120. package/docs/PRIVACY.md +28 -0
  121. package/docs/RECOVERY.md +56 -0
  122. package/docs/RELEASING.md +40 -0
  123. package/docs/hosts.md +134 -0
  124. package/docs/plugins.md +83 -0
  125. package/docs/ui-themes.md +43 -0
  126. package/instructions/AGENTS.md +33 -0
  127. package/package.json +88 -4
  128. package/plugins/migrate_ai_studio/README.md +101 -0
  129. package/plugins/migrate_ai_studio/index.cjs +396 -0
  130. package/plugins/migrate_ai_studio/instructions/audit.md +25 -0
  131. package/plugins/migrate_ai_studio/instructions/classify.md +41 -0
  132. package/plugins/migrate_ai_studio/instructions/migrate.md +34 -0
  133. package/plugins/migrate_ai_studio/instructions/verify.md +20 -0
  134. package/plugins/migrate_ai_studio/plugin.json +11 -0
  135. package/plugins/migrate_ai_studio/schema.cjs +20 -0
  136. package/plugins/migrate_ai_studio/ui/dist/index.html +77 -0
  137. package/prodero.release.json +5 -0
@@ -0,0 +1,475 @@
1
+ import { sameApplicability, withApplicability } from "./applicability.js";
2
+ import { inferredKnowledge } from './knowledge-authority.js';
3
+ import { readConfig } from './storage.js';
4
+ import { inheritKnowledgePolicy } from "./knowledge-policy.js";
5
+ import { reconcileCandidate, normalizeReconciliation, actionClassification } from './reconciliation.js';
6
+ import fs from 'node:fs/promises';
7
+ import { sessionCandidates } from './consolidation.js';
8
+ import { loadMemoryCatalog } from './memory-index.js';
9
+ import { renderKnowledge, parseKnowledge } from './memory.js';
10
+ import { exactKnowledge, semanticRisk, isExtractivelyGrounded } from './semantic-safety.js';
11
+ import { renderSkill, skillInputSchema, parseSkill } from './skills.js';
12
+ import { sortableId } from './ids.js';
13
+ import { normalized, tokens } from './matching.js';
14
+ import { sha256 } from './storage.js';
15
+ import { safeProjectPath, fileHash } from './sync-transaction.js';
16
+ import { estimateTokens, knowledgeText, MAX_CANONICAL_ITEM_TOKENS } from './telemetry.js';
17
+ import { INTELLIGENCE_LIMITS as L, IntelligenceError, intelligenceTelemetry, requestIntelligence, distillSchema, auditSchema, } from './intelligence.js';
18
+ const unique = (values) => [...new Set(values)].sort();
19
+ const sameBoundary = (a, b) => a.category === b.category && a.scope === b.scope && a.scopeRef === b.scopeRef && a.scopeKind === b.scopeKind && sameApplicability(a, b);
20
+ const priority = { contextual: 0, normal: 1, important: 2, critical: 3 };
21
+ export function observationEvidence(sessions) {
22
+ return sessions.flatMap(sessionCandidates).map((c) => ({ id: c.derivedFrom, sessionId: c.sessionId,
23
+ kind: c.skill ? 'skill_candidate' : c.instructionCandidate ? 'instruction_candidate' : 'knowledge',
24
+ title: c.title, content: c.content, category: c.category, scope: c.scope, scopeRef: c.scopeRef, scopeKind: c.scopeKind, priority: c.priority, applicability: c.applicability, supersedes: c.supersedes }));
25
+ }
26
+ export function observationKey(o) { return sha256(JSON.stringify(o)); }
27
+ /** Build a fully validated, in-memory canonical overlay; no canonical writes or provider filesystem access. */
28
+ export async function planAssistedSync(root, sessions, provider, now = new Date(), processed = new Set(), options = {}) {
29
+ const evidence = observationEvidence(sessions).filter((o) => !processed.has(observationKey(o)));
30
+ const catalog = await loadMemoryCatalog(root);
31
+ const telemetry = intelligenceTelemetry(provider);
32
+ telemetry.distillation.sessions = new Set(evidence.map((o) => o.sessionId)).size;
33
+ telemetry.distillation.observations = evidence.length;
34
+ const result = { candidates: 0, created: [], createdSkills: [], skillCandidateTokensAdded: 0, updated: [], consolidated: [], superseded: [], duplicates: [], conflicts: [], conflictCount: 0, memoryFilesScanned: catalog.filesScanned, memoryBodiesIndexed: catalog.bodiesIndexed, memoryBodiesLoaded: 0, relatedCandidatesSelectedMax: 0, observationsProcessed: evidence.length, rawSessionTokens: 0, rawKnowledgeTokens: 0, rawSkillCandidateTokens: 0, canonicalTokensAdded: 0, compactedSegments: 0, conservativeMergesSkipped: 0 };
35
+ for (const o of evidence) {
36
+ const n = estimateTokens(o.content);
37
+ result.rawSessionTokens += n;
38
+ if (o.kind === 'skill_candidate')
39
+ result.rawSkillCandidateTokens += n;
40
+ else
41
+ result.rawKnowledgeTokens += n;
42
+ }
43
+ const overlay = new Map();
44
+ const originals = new Map();
45
+ const touched = new Set();
46
+ const beforeItems = new Map();
47
+ const changedActions = [];
48
+ const reasoning = [];
49
+ const instructionCandidates = [];
50
+ const skillWrites = [];
51
+ const allCandidates = new Map();
52
+ const observationMap = new Map(evidence.map((o) => [o.id, o]));
53
+ const timestamp = now.toISOString();
54
+ const fail = (message) => { throw new Error(`Invalid intelligence proposal: ${message}. No semantic plan was committed; sessions remain pending.`); };
55
+ async function load(id) {
56
+ if (overlay.has(id))
57
+ return overlay.get(id);
58
+ const indexed = catalog.summaries.find((item) => item.id === id);
59
+ if (!indexed)
60
+ return fail('memory is absent from the unified catalog');
61
+ const target = await safeProjectPath(root, indexed.relativePath);
62
+ const body = await fs.readFile(target, "utf8");
63
+ const before = sha256(body);
64
+ const item = parseKnowledge(body, `${id}.md`, (await readConfig(root))?.knowledgeSchemaVersion);
65
+ catalog.bodiesLoaded++;
66
+ if (item.id !== id)
67
+ fail('memory ID does not match filename');
68
+ originals.set(id, { hash: before, tokens: estimateTokens(knowledgeText(item)) });
69
+ beforeItems.set(id, structuredClone(item));
70
+ overlay.set(id, item);
71
+ return item;
72
+ }
73
+ function validateCanonical(item, sources) {
74
+ if (estimateTokens(knowledgeText(item)) > MAX_CANONICAL_ITEM_TOKENS)
75
+ fail('canonical item exceeds size budget');
76
+ if (!sources.every((s) => sameBoundary(item, s) && sameApplicability(item, s)))
77
+ fail('scope/category escalation or incompatible boundary');
78
+ if (priority[item.priority] > Math.max(...sources.map((s) => priority[s.priority])))
79
+ fail('priority escalation');
80
+ if (/[\r\n]/.test(item.title))
81
+ fail('multiline title');
82
+ }
83
+ function mark(item) { item.updatedAt = timestamp; touched.add(item.id); }
84
+ function provenance(items) {
85
+ return { evidence: items.flatMap((s) => "derivedFrom" in s ? (s.evidence ?? s.derivedFrom.map((ref) => ({ kind: "session_observation", ref }))) : s.sources.map((ref) => ({ kind: "session_observation", ref }))), sourceSessions: unique(items.flatMap((s) => 'sourceSessions' in s ? s.sourceSessions : s.sources.map((id) => observationMap.get(id).sessionId))), derivedFrom: unique(items.flatMap((s) => 'derivedFrom' in s ? s.derivedFrom : s.sources)) };
86
+ }
87
+ function create(candidate, status) {
88
+ const id = sortableId('mem', now);
89
+ const item = { id, version: 3, applicability: candidate.applicability ?? { kind: 'legacy' }, title: candidate.title, content: candidate.content, scope: candidate.scope, scopeRef: candidate.scopeRef, scopeKind: candidate.scopeKind, category: candidate.category, priority: candidate.priority, status, createdAt: timestamp, updatedAt: timestamp, ...provenance([candidate]), supersedes: [], related: [] };
90
+ Object.assign(item, withApplicability(inferredKnowledge(item), item.applicability));
91
+ validateCanonical(item, [candidate]);
92
+ overlay.set(id, item);
93
+ originals.set(id, { hash: null, tokens: 0 });
94
+ touched.add(id);
95
+ result.created.push(id);
96
+ return item;
97
+ }
98
+ async function related(candidate) {
99
+ // Rank the staged overlay and both stored layers together before bounding.
100
+ const summaries = catalog.summaries.filter(m => !touched.has(m.id));
101
+ const staged = [...touched].map(id => overlay.get(id)).map(m => ({ ...m,
102
+ tokens: [...tokens(`${m.title} ${m.content} ${m.scopeRef ?? ''}`)],
103
+ normalizedTitle: normalized(m.title), normalizedContent: normalized(m.content) }));
104
+ const shadowed = new Set([...summaries, ...staged].filter(m => ['active', 'disputed'].includes(m.status)).flatMap(m => m.supersedes));
105
+ const queryTokens = tokens(`${candidate.title} ${candidate.content}`);
106
+ const queryTitle = normalized(candidate.title), queryContent = normalized(candidate.content);
107
+ const ordered = [...summaries, ...staged]
108
+ .filter(m => ['active', 'disputed'].includes(m.status) &&
109
+ (candidate.supersedes.includes(m.id) || (!shadowed.has(m.id) && sameBoundary(m, candidate))))
110
+ .map(m => ({ id: m.id, score: (candidate.supersedes.includes(m.id) ? 10000 : 0)
111
+ + m.tokens.filter(t => queryTokens.has(t)).length * 10
112
+ + (queryTitle && m.normalizedTitle === queryTitle ? 1000 : 0)
113
+ + (queryContent && m.normalizedContent === queryContent ? 2000 : 0) }))
114
+ .sort((a, b) => b.score - a.score || a.id.localeCompare(b.id)).map(m => m.id);
115
+ const bounded = [];
116
+ for (const id of ordered.slice(0, L.relatedMemories)) {
117
+ const item = await load(id);
118
+ if (!['active', 'disputed'].includes(item.status))
119
+ continue;
120
+ if (estimateTokens(JSON.stringify({ candidate, related: [...bounded, item] })) <= L.reconcileInputTokens)
121
+ bounded.push(item);
122
+ }
123
+ if (candidate.supersedes.some((id) => !bounded.some((m) => m.id === id)))
124
+ fail('explicit supersession target absent from bounded context');
125
+ result.relatedCandidatesSelectedMax = Math.max(result.relatedCandidatesSelectedMax, bounded.length);
126
+ return bounded;
127
+ }
128
+ function apply(action, available, candidate) {
129
+ action = normalizeReconciliation(action, Boolean(candidate));
130
+ if (new Set(action.sources).size !== action.sources.length || action.sources.some((id) => !available.has(id)))
131
+ fail('unknown, duplicate or out-of-area source ID');
132
+ if (candidate && !action.sources.includes(candidate.id))
133
+ fail('candidate evidence omitted');
134
+ if (candidate?.supersedes.length && !['SUPERSEDE', 'CONFLICT', 'UNCERTAIN'].includes(action.action))
135
+ fail('explicit supersession cannot be ignored');
136
+ const memories = action.sources.filter((id) => id !== candidate?.id).map((id) => overlay.get(id) ?? fail('nonexistent memory'));
137
+ if (memories.some((m) => !['active', 'disputed'].includes(m.status)))
138
+ fail('illegal historical state transition');
139
+ if (action.action !== 'SUPERSEDE' && action.replacements)
140
+ fail('replacement evidence on a non-supersession action');
141
+ if (action.action !== 'MERGE' && action.canonical !== null)
142
+ fail('canonical content only allowed for MERGE');
143
+ const targets = [];
144
+ if (action.action === 'CREATE') {
145
+ if (!candidate || memories.length)
146
+ fail('CREATE sources');
147
+ targets.push(create(candidate, 'active').id);
148
+ }
149
+ else if (action.action === 'MERGE' || action.action === 'NO_CHANGE') {
150
+ if (memories.length !== (candidate ? 1 : 2))
151
+ fail('merge/duplicate requires two sources');
152
+ const incoming = candidate ?? memories[1];
153
+ const target = memories[0];
154
+ if (!candidate && memories.some(m => m.contextPolicy !== target.contextPolicy || m.authority !== target.authority))
155
+ fail('merge changes context policy or authority');
156
+ if (options.instructionIds && new Set(memories.map(m => catalog.summaries.find(s => s.id === m.id).layer)).size > 1)
157
+ fail('optimization cannot cross publication layers');
158
+ if (options.instructionIds && memories.some(m => (m.relations ?? []).some(r => r.type === 'contradicts')))
159
+ fail('instruction conflict requires review');
160
+ if (memories.some((m) => m.status !== 'active'))
161
+ fail('cannot accept disputed knowledge via merge');
162
+ if (!sameBoundary(incoming, target) || semanticRisk(incoming.content, target.content) || (incoming.category === 'instruction' && incoming.priority !== target.priority))
163
+ fail('unsafe semantic merge or duplicate');
164
+ const targetVersioned = catalog.summaries.find((item) => item.id === target.id)?.layer === 'versioned';
165
+ if (action.action === 'MERGE') {
166
+ if (!action.canonical)
167
+ fail('missing canonical merge');
168
+ action.canonical.applicability ??= target.applicability;
169
+ action.canonical.scopeKind ??= target.scopeKind;
170
+ validateCanonical(action.canonical, [incoming, target]);
171
+ if (incoming.category === 'instruction' && action.canonical.priority !== incoming.priority)
172
+ fail('merge changes mandatory policy priority');
173
+ if (!isExtractivelyGrounded(action.canonical.content, [incoming.content, target.content]))
174
+ fail("merge invents factual sentences");
175
+ if (![incoming.content, target.content].every((content) => isExtractivelyGrounded(content, [action.canonical.content])))
176
+ fail("merge drops source constraints");
177
+ // Protected constraints cannot disappear from the combined result.
178
+ if (semanticRisk(`${target.content}\n${incoming.content}`, action.canonical.content))
179
+ fail('merge loses protected constraints');
180
+ if (targetVersioned && candidate) {
181
+ const local = create(candidate, 'active');
182
+ Object.assign(local, action.canonical);
183
+ inheritKnowledgePolicy(local, [target]);
184
+ Object.assign(local, provenance([target, candidate]));
185
+ local.supersedes = unique([...local.supersedes, target.id]);
186
+ mark(local);
187
+ result.consolidated.push(local.id);
188
+ targets.push(target.id, local.id);
189
+ reasoning.push({ phase: 'reconcile', action: action.action, sources: action.sources, targets, reason: action.reason, classification: actionClassification(action) });
190
+ return targets;
191
+ }
192
+ Object.assign(target, action.canonical);
193
+ result.consolidated.push(target.id);
194
+ }
195
+ else {
196
+ if (!isExtractivelyGrounded(incoming.content, [target.content]))
197
+ fail("NO_CHANGE would discard new evidence");
198
+ result.duplicates.push(target.id);
199
+ if (targetVersioned && candidate) {
200
+ targets.push(target.id);
201
+ reasoning.push({ phase: 'reconcile', action: action.action, sources: action.sources, targets, reason: action.reason, classification: actionClassification(action) });
202
+ return targets;
203
+ }
204
+ }
205
+ Object.assign(target, provenance([target, incoming]));
206
+ if (!candidate) {
207
+ const source = incoming;
208
+ target.supersedes = unique([...target.supersedes, ...source.supersedes]);
209
+ target.related = unique([...target.related, ...source.related]);
210
+ target.relations = [...new Map([...(target.relations ?? []), ...(source.relations ?? [])].map(r => [`${r.type}:${r.target}`, r])).values()];
211
+ }
212
+ mark(target);
213
+ targets.push(target.id);
214
+ if (!candidate) {
215
+ const incoming = memories[1];
216
+ incoming.status = action.action === 'MERGE' ? 'consolidated' : 'duplicate';
217
+ incoming.related = unique([...incoming.related, target.id]);
218
+ mark(incoming);
219
+ target.related = unique([...target.related, incoming.id]);
220
+ targets.push(incoming.id);
221
+ }
222
+ }
223
+ else if (action.action === 'SUPERSEDE') {
224
+ if (!memories.length || (!candidate && memories.length !== 2))
225
+ fail('supersession sources');
226
+ const newer = candidate ? create(candidate, 'active') : memories[0];
227
+ const older = candidate ? memories : memories.slice(1);
228
+ if (candidate)
229
+ inheritKnowledgePolicy(newer, older);
230
+ const allowed = candidate?.supersedes ?? newer.supersedes;
231
+ const inferred = older.filter(old => !allowed.includes(old.id));
232
+ if (action.replacements?.some(r => !inferred.some(old => old.id === r.target)) ||
233
+ new Set(action.replacements?.map(r => r.target)).size !== (action.replacements?.length ?? 0))
234
+ fail('invalid replacement targets');
235
+ for (const old of inferred) {
236
+ const proof = action.replacements?.find(r => r.target === old.id);
237
+ // The model decides semantic identity/completeness. Core verifies bounded IDs,
238
+ // source authority, full old-body coverage and actual transition evidence.
239
+ if (!candidate || !proof || !sameBoundary(candidate, old) ||
240
+ exactKnowledge(proof.previousQuote) !== exactKnowledge(old.content) ||
241
+ !candidate.content.includes(proof.replacementQuote) ||
242
+ !candidate.content.includes(proof.transitionQuote) ||
243
+ !/\b(now|no longer|implemented|completed|replaced|changed|migrated|previously|formerly)\b/i.test(proof.transitionQuote) ||
244
+ !candidate.evidence.some(e => e.quote.includes(proof.transitionQuote)))
245
+ fail('supersession lacks explicit evidence or validated transition');
246
+ }
247
+ for (const old of older) {
248
+ if (catalog.summaries.find((item) => item.id === old.id)?.layer !== 'versioned') {
249
+ old.status = 'superseded';
250
+ old.related = unique([...old.related, newer.id]);
251
+ mark(old);
252
+ result.superseded.push(old.id);
253
+ }
254
+ newer.supersedes = unique([...newer.supersedes, old.id, ...old.supersedes]);
255
+ targets.push(old.id);
256
+ }
257
+ mark(newer);
258
+ targets.push(newer.id);
259
+ }
260
+ else {
261
+ if (action.action === 'LINK' && !memories.length)
262
+ fail('LINK requires related knowledge');
263
+ const items = candidate ? [...memories, create(candidate, action.action === 'LINK' ? 'active' : 'disputed')] : memories;
264
+ if (!items.length)
265
+ fail('empty conflict/uncertainty');
266
+ for (const item of items) {
267
+ const writable = catalog.summaries.find((candidate) => candidate.id === item.id)?.layer !== 'versioned';
268
+ if (writable) {
269
+ if (action.action !== 'LINK') {
270
+ item.status = 'disputed';
271
+ result.conflicts.push(item.id);
272
+ }
273
+ item.related = unique([...item.related, ...items.filter((m) => m.id !== item.id).map((m) => m.id)]);
274
+ mark(item);
275
+ }
276
+ targets.push(item.id);
277
+ }
278
+ if (action.action !== 'LINK')
279
+ result.conflictCount++;
280
+ }
281
+ reasoning.push({ phase: candidate ? 'reconcile' : 'audit', action: action.action, sources: action.sources, targets, reason: action.reason, classification: actionClassification(action) });
282
+ return targets;
283
+ }
284
+ function validateCandidate(c, batch) {
285
+ if (allCandidates.has(c.id) || !/^candidate_[A-Za-z0-9_-]+$/.test(c.id))
286
+ fail('candidate ID');
287
+ const sources = c.sources.map((id) => batch.find((o) => o.id === id) ?? fail('unknown observation reference'));
288
+ if (unique(c.sources).length !== c.sources.length || sources.some((o) => o.kind !== c.kind))
289
+ fail('candidate source/type mismatch');
290
+ if (c.kind !== 'knowledge' && sources.length !== 1)
291
+ fail('review candidates retain one source identity');
292
+ validateCanonical(c, sources);
293
+ for (const source of sources) {
294
+ if (!c.evidence.some((e) => e.source === source.id && source.content.includes(e.quote)))
295
+ fail('missing exact source evidence');
296
+ }
297
+ if (c.evidence.some((e) => !sources.some((s) => s.id === e.source && s.content.includes(e.quote))))
298
+ fail('invented evidence quote');
299
+ if (!isExtractivelyGrounded(c.content, sources.map((s) => s.content)))
300
+ fail("unsupported factual sentence");
301
+ if (semanticRisk(sources.map((s) => s.content).join('\n'), c.content))
302
+ fail('distillation changes protected constraints');
303
+ const explicit = sources.flatMap((s) => s.supersedes);
304
+ if (c.supersedes.some((id) => !explicit.includes(id)))
305
+ fail('invented supersession');
306
+ allCandidates.set(c.id, c);
307
+ }
308
+ async function saveCandidate(c) {
309
+ result.candidates++;
310
+ if (c.kind === 'instruction_candidate') {
311
+ instructionCandidates.push(c);
312
+ return;
313
+ }
314
+ if (c.kind === 'skill_candidate') {
315
+ const source = observationMap.get(c.sources[0]);
316
+ const id = `skill_${sha256(source.id).slice(0, 32)}`;
317
+ const relative = `local/skills/${id}.md`;
318
+ const target = await safeProjectPath(root, relative);
319
+ const before = await fileHash(target);
320
+ if (before) {
321
+ if (!parseSkill(await fs.readFile(target, 'utf8'), `${id}.md`).derivedFrom.includes(source.id))
322
+ fail('skill provenance collision');
323
+ return;
324
+ }
325
+ const original = sessions.flatMap(sessionCandidates).find((o) => o.derivedFrom === source.id);
326
+ const skill = { ...skillInputSchema.parse({ title: c.title, description: original.description ?? c.content.slice(0, 300), content: c.content, scope: c.scopeRef ?? 'project', triggers: original.triggers }), id, path: relative, status: 'candidate', origin: 'session-inference', sourceSessions: [source.sessionId], derivedFrom: [source.id], proposedReason: original.proposedReason ?? null, reviewedAt: null, reviewReason: null, createdAt: timestamp, updatedAt: timestamp };
327
+ skillWrites.push({ path: relative, before: null, after: renderSkill(skill) });
328
+ result.createdSkills.push(id);
329
+ result.skillCandidateTokensAdded += estimateTokens(skill.content);
330
+ return;
331
+ }
332
+ const neighbors = await related(c);
333
+ telemetry.reconciliation.comparisons += neighbors.length;
334
+ const exact = !c.supersedes.length && neighbors.find((m) => m.status === 'active' && sameBoundary(m, c) && exactKnowledge(m.content) === exactKnowledge(c.content));
335
+ const action = exact ? { action: 'NO_CHANGE', sources: [c.id, exact.id], canonical: null, classification: 'duplicate', reason: 'Exact content equality; retain additional source evidence.' } : await reconcileCandidate(c, neighbors, provider, telemetry);
336
+ telemetry.reconciliation.actions[action.action]++;
337
+ apply(action, new Set([c.id, ...neighbors.map((m) => m.id)]), c);
338
+ if (action.action !== 'NO_CHANGE')
339
+ changedActions.push(action);
340
+ }
341
+ // Exact duplicates bypass both model phases and still retain source provenance.
342
+ const pending = [];
343
+ for (const o of evidence) {
344
+ if (o.kind !== 'knowledge') {
345
+ pending.push(o);
346
+ continue;
347
+ }
348
+ const c = { ...o, id: `candidate_exact_${sha256(o.id).slice(0, 16)}`, sources: [o.id], evidence: [{ source: o.id, quote: o.content }] };
349
+ const neighbors = await related(c);
350
+ const exact = !o.supersedes.length && neighbors.find((m) => m.status === 'active' && sameBoundary(m, o) && exactKnowledge(m.content) === exactKnowledge(o.content));
351
+ if (!exact) {
352
+ pending.push(o);
353
+ continue;
354
+ }
355
+ result.candidates++;
356
+ telemetry.reconciliation.actions.NO_CHANGE++;
357
+ apply({ action: 'NO_CHANGE', sources: [c.id, exact.id], canonical: null, classification: 'duplicate', reason: 'Exact content equality; no model call needed.' }, new Set([c.id, exact.id]), c);
358
+ }
359
+ let batch = [];
360
+ async function flush() {
361
+ if (!batch.length)
362
+ return;
363
+ const distilled = distillSchema.parse(await requestIntelligence(provider, 'distill', { observations: batch }, telemetry));
364
+ // Batch-local model IDs are made globally unique without trusting them as disk IDs.
365
+ const seen = new Set();
366
+ for (const c of distilled.candidates) {
367
+ if (seen.has(c.id))
368
+ fail('duplicate candidate ID');
369
+ seen.add(c.id);
370
+ c.id = `candidate_${telemetry.distillation.calls}_${c.id}`;
371
+ c.applicability ??= batch.find(o => c.sources.includes(o.id))?.applicability;
372
+ c.scopeKind ??= batch.find(o => c.sources.includes(o.id))?.scopeKind;
373
+ validateCandidate(c, batch);
374
+ }
375
+ if (batch.some((o) => !distilled.candidates.some((c) => c.sources.includes(o.id))))
376
+ fail('distillation dropped an observation');
377
+ telemetry.distillation.candidates += distilled.candidates.length;
378
+ for (const c of distilled.candidates)
379
+ await saveCandidate(c);
380
+ batch = [];
381
+ }
382
+ for (const o of pending) {
383
+ if (estimateTokens(JSON.stringify({ observations: [o] })) > L.distillInputTokens)
384
+ throw new IntelligenceError('distill', 'single observation exceeds budget; preserve pending evidence');
385
+ if (batch.length >= L.observationsPerBatch || estimateTokens(JSON.stringify({ observations: [...batch, o] })) > L.distillInputTokens)
386
+ await flush();
387
+ batch.push(o);
388
+ }
389
+ await flush();
390
+ const instructionAudit = options.instructionIds;
391
+ if (instructionAudit && (sessions.length || instructionAudit.length > L.auditItems || new Set(instructionAudit).size !== instructionAudit.length))
392
+ fail('invalid explicit instruction area');
393
+ if (instructionAudit)
394
+ for (const id of instructionAudit) {
395
+ const summary = catalog.summaries.find(m => m.id === id);
396
+ if (!summary || summary.category !== 'instruction' || summary.status !== 'active')
397
+ fail('instruction optimization requires current instructions');
398
+ }
399
+ const meaningful = changedActions.length;
400
+ if (instructionAudit?.length || meaningful && (changedActions.some((a) => ['MERGE', 'SUPERSEDE', 'CONFLICT', 'LINK', 'UNCERTAIN'].includes(a.action)) || touched.size >= L.auditTriggerItems)) {
401
+ const auditItems = [];
402
+ const changedIds = [...touched];
403
+ const neighborIds = changedIds.flatMap((id) => {
404
+ const item = overlay.get(id);
405
+ return [...item.related, ...catalog.selectRelated({ ...item, explicitIds: item.related }, L.relatedMemories).map((m) => m.id)];
406
+ });
407
+ const ids = instructionAudit ?? unique([...changedIds, ...neighborIds]);
408
+ const ordered = instructionAudit ?? unique(changedIds).concat(ids.filter((id) => !touched.has(id)));
409
+ // The evidence and change records are bounded together with the neighborhood.
410
+ const auditEvidence = [];
411
+ const changes = [];
412
+ const budgetFits = (items, ev = auditEvidence, acts = changes) => estimateTokens(JSON.stringify({ items, evidence: ev, changes: acts, truncated: true })) <= L.auditInputTokens;
413
+ for (const id of ordered.slice(0, L.auditItems)) {
414
+ const item = await load(id);
415
+ if (!budgetFits([...auditItems, item]))
416
+ break;
417
+ auditItems.push(item);
418
+ }
419
+ const area = new Set(auditItems.map((i) => i.id));
420
+ for (const e of evidence.filter((o) => auditItems.some((i) => i.derivedFrom.includes(o.id)))) {
421
+ if (budgetFits(auditItems, [...auditEvidence, e]))
422
+ auditEvidence.push(e);
423
+ }
424
+ for (const a of changedActions) {
425
+ if (changes.length < L.auditItems && budgetFits(auditItems, auditEvidence, [...changes, a]))
426
+ changes.push(a);
427
+ }
428
+ const truncated = auditItems.length < ids.length || auditEvidence.length < evidence.length || changes.length < changedActions.length;
429
+ const input = { ...(instructionAudit ? { purpose: 'instruction_optimization' } : {}), items: auditItems, evidence: auditEvidence, changes, truncated };
430
+ telemetry.audit.triggered = true;
431
+ telemetry.audit.items = auditItems.length;
432
+ telemetry.audit.truncated = truncated;
433
+ const audit = auditSchema.parse(await requestIntelligence(provider, 'audit', input, telemetry));
434
+ if (audit.status === 'clean' && (audit.issues.length || audit.actions.length))
435
+ fail('clean audit contains issues/actions');
436
+ if (audit.status === 'changes_proposed' && !audit.actions.length)
437
+ fail('audit status/action mismatch');
438
+ for (const issue of audit.issues)
439
+ if (issue.sources.some((id) => !area.has(id)))
440
+ fail('out-of-area audit issue');
441
+ // Apply to the overlay; any later validation error discards the whole plan.
442
+ for (const action of audit.actions) {
443
+ if (instructionAudit && (!['MERGE', 'NO_CHANGE'].includes(action.action) || action.sources.some(id => audit.issues.some(i => i.kind !== 'fragmentation' && i.kind !== 'duplicate' && i.sources.includes(id)))))
444
+ fail('optimization cannot resolve conflicts or change instruction authority');
445
+ apply(action, area);
446
+ telemetry.audit.applied++;
447
+ }
448
+ const unresolved = audit.issues.filter((issue) => !audit.actions.some((a) => ['MERGE', 'SUPERSEDE', 'NO_CHANGE'].includes(a.action) && issue.sources.every((id) => a.sources.includes(id))));
449
+ for (const issue of instructionAudit ? [] : unresolved)
450
+ apply({ action: 'UNCERTAIN', classification: issue.kind === 'contradiction' && issue.sources.length > 1 ? 'potential_conflict' : 'unresolved', sources: issue.sources, canonical: null, reason: issue.reason }, area);
451
+ if (!instructionAudit && audit.status === 'uncertain' && !unresolved.length) {
452
+ const activeIds = auditItems.filter((m) => ['active', 'disputed'].includes(m.status)).map((m) => m.id);
453
+ if (activeIds.length)
454
+ apply({ action: 'UNCERTAIN', sources: activeIds, canonical: null, reason: 'Audit could not establish coherence; retain all evidence.' }, area);
455
+ }
456
+ telemetry.audit.issues = audit.issues.length;
457
+ telemetry.audit.proposed = audit.actions.length;
458
+ telemetry.audit.status = unresolved.length || audit.status === 'uncertain' || truncated || auditItems.some((item) => item.status === 'disputed') || [...touched].some((id) => overlay.get(id).status === 'disputed') ? 'attention' : 'coherent';
459
+ }
460
+ const writes = [...skillWrites];
461
+ for (const id of touched) {
462
+ const item = overlay.get(id);
463
+ if (originals.get(id).hash !== null)
464
+ result.updated.push(id);
465
+ // Existing bodies are retained unchanged on supersession; provenance is only unioned.
466
+ writes.push({ path: options.instructionIds ? catalog.summaries.find(m => m.id === id).relativePath : `local/memory/${id}.md`, before: originals.get(id).hash, after: renderKnowledge(item) });
467
+ const delta = estimateTokens(knowledgeText(item)) - originals.get(id).tokens;
468
+ result.canonicalTokensAdded += Math.max(0, delta);
469
+ }
470
+ for (const key of ['created', 'updated', 'consolidated', 'superseded', 'duplicates', 'conflicts'])
471
+ result[key] = unique(result[key]);
472
+ result.memoryBodiesLoaded = catalog.bodiesLoaded;
473
+ return { result, writes, intelligence: telemetry, reasoning, instructionCandidates, observationHashes: evidence.map(observationKey), knowledgeBefore: [...touched].flatMap((id) => beforeItems.has(id) ? [beforeItems.get(id)] : []) };
474
+ }
475
+ //# sourceMappingURL=knowledge-engine.js.map