@prodero/curator 0.0.0-stage → 0.2.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 (148) hide show
  1. package/ARCHITECTURE.md +298 -0
  2. package/CHANGELOG.md +33 -0
  3. package/CONTRIBUTING.md +9 -0
  4. package/LICENSE +21 -0
  5. package/README.md +93 -2
  6. package/SECURITY.md +7 -0
  7. package/bin/prodero +7 -0
  8. package/dist/cli.js +266 -0
  9. package/dist/context-instructions.js +2 -0
  10. package/dist/contracts.js +51 -0
  11. package/dist/conversation-presentation.js +297 -0
  12. package/dist/core/agent-instructions.js +52 -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 +117 -0
  24. package/dist/core/curation-question.js +28 -0
  25. package/dist/core/curation-review-presentation.js +4 -0
  26. package/dist/core/curation-review-state.js +62 -0
  27. package/dist/core/curation-review.js +573 -0
  28. package/dist/core/curation-revision.js +106 -0
  29. package/dist/core/curation-semantic-contract.js +242 -0
  30. package/dist/core/curation-semantic-jobs.js +340 -0
  31. package/dist/core/diagnostics.js +17 -0
  32. package/dist/core/format.js +114 -0
  33. package/dist/core/ids.js +6 -0
  34. package/dist/core/imports.js +169 -0
  35. package/dist/core/index.js +93 -0
  36. package/dist/core/instruction-review.js +86 -0
  37. package/dist/core/integrity-files.js +174 -0
  38. package/dist/core/intelligence.js +95 -0
  39. package/dist/core/knowledge-analysis.js +46 -0
  40. package/dist/core/knowledge-authority.js +143 -0
  41. package/dist/core/knowledge-curation.js +316 -0
  42. package/dist/core/knowledge-engine.js +475 -0
  43. package/dist/core/knowledge-graph.js +192 -0
  44. package/dist/core/knowledge-lifecycle.js +212 -0
  45. package/dist/core/knowledge-management.js +113 -0
  46. package/dist/core/knowledge-migration.js +84 -0
  47. package/dist/core/knowledge-policy.js +38 -0
  48. package/dist/core/knowledge-provenance.js +90 -0
  49. package/dist/core/knowledge-write-policy.js +85 -0
  50. package/dist/core/language.js +14 -0
  51. package/dist/core/local-preferences.js +76 -0
  52. package/dist/core/matching.js +6 -0
  53. package/dist/core/memory-index.js +241 -0
  54. package/dist/core/memory-intelligence.js +288 -0
  55. package/dist/core/memory-link-repair.js +70 -0
  56. package/dist/core/memory-operations.js +122 -0
  57. package/dist/core/memory-pressure.js +212 -0
  58. package/dist/core/memory-sync-curator.js +82 -0
  59. package/dist/core/memory-sync.js +558 -0
  60. package/dist/core/memory-transformation-bundle.js +163 -0
  61. package/dist/core/memory-transformation-proposal.js +135 -0
  62. package/dist/core/memory-transformation.js +108 -0
  63. package/dist/core/memory.js +186 -0
  64. package/dist/core/migrations.js +569 -0
  65. package/dist/core/npm-update.js +289 -0
  66. package/dist/core/path-scope.js +27 -0
  67. package/dist/core/plugin-files.js +116 -0
  68. package/dist/core/project-root.js +39 -0
  69. package/dist/core/project.js +634 -0
  70. package/dist/core/promotion.js +279 -0
  71. package/dist/core/reconciliation.js +61 -0
  72. package/dist/core/secrets.js +67 -0
  73. package/dist/core/semantic-safety.js +33 -0
  74. package/dist/core/session-index.js +116 -0
  75. package/dist/core/session-review.js +150 -0
  76. package/dist/core/sessions.js +289 -0
  77. package/dist/core/settings.js +94 -0
  78. package/dist/core/skill-curation.js +239 -0
  79. package/dist/core/skill-polish.js +41 -0
  80. package/dist/core/skills.js +387 -0
  81. package/dist/core/storage.js +462 -0
  82. package/dist/core/sync-transaction.js +290 -0
  83. package/dist/core/telemetry.js +64 -0
  84. package/dist/core/templates.js +53 -0
  85. package/dist/core/types.js +2 -0
  86. package/dist/core/update.js +326 -0
  87. package/dist/curation-review-adapter.js +92 -0
  88. package/dist/diagnostic-boundary.js +55 -0
  89. package/dist/filesystem/permissions.js +39 -0
  90. package/dist/hosts/adapters.js +119 -0
  91. package/dist/hosts/files.js +133 -0
  92. package/dist/hosts/manager.js +150 -0
  93. package/dist/hosts/public-entry.js +47 -0
  94. package/dist/hosts/runtime-command.js +66 -0
  95. package/dist/hosts/runtime.js +139 -0
  96. package/dist/hosts/wizard.js +114 -0
  97. package/dist/knowledge-table.js +38 -0
  98. package/dist/mcp-development.js +69 -0
  99. package/dist/mcp-workspace.js +141 -0
  100. package/dist/memory-presentation.js +182 -0
  101. package/dist/memory-sync-operation.js +88 -0
  102. package/dist/plugins/api.js +2 -0
  103. package/dist/plugins/context.js +20 -0
  104. package/dist/plugins/mcp.js +72 -0
  105. package/dist/plugins/registry.js +106 -0
  106. package/dist/presentation-cards.js +32 -0
  107. package/dist/presentation-server.js +94 -0
  108. package/dist/presentation-trace.js +174 -0
  109. package/dist/presentation.js +443 -0
  110. package/dist/providers/mcp-sampling.js +45 -0
  111. package/dist/server.js +664 -0
  112. package/dist/settings-definition.js +22 -0
  113. package/dist/skill-instructions.js +2 -0
  114. package/dist/status-presentation.js +20 -0
  115. package/dist/text-layout.js +278 -0
  116. package/dist/update-command.js +53 -0
  117. package/dist/update-instructions.js +4 -0
  118. package/dist-ui/index.html +216 -0
  119. package/docs/APPLICABILITY.md +151 -0
  120. package/docs/COMPATIBILITY.md +68 -0
  121. package/docs/CONTEXT-SELECTION-V2.md +61 -0
  122. package/docs/CURATION-APP-REVIEW.md +156 -0
  123. package/docs/CURATION-CLAUSE-REFINEMENT.md +45 -0
  124. package/docs/IMPORTER-CURATION.md +142 -0
  125. package/docs/KNOWLEDGE-AUTHORITY.md +165 -0
  126. package/docs/KNOWLEDGE-SCHEMA-V2.md +150 -0
  127. package/docs/MIGRATIONS.md +103 -0
  128. package/docs/PRIVACY.md +28 -0
  129. package/docs/RECOVERY.md +58 -0
  130. package/docs/RELEASING.md +98 -0
  131. package/docs/hosts.md +152 -0
  132. package/docs/images/prodero-context-ready-cropped.png +0 -0
  133. package/docs/images/prodero-overview-cropped.png +0 -0
  134. package/docs/images/prodero-session-learned-cropped.png +0 -0
  135. package/docs/plugins.md +83 -0
  136. package/docs/ui-themes.md +49 -0
  137. package/instructions/AGENTS.md +39 -0
  138. package/package.json +92 -4
  139. package/plugins/migrate_ai_studio/README.md +101 -0
  140. package/plugins/migrate_ai_studio/index.cjs +396 -0
  141. package/plugins/migrate_ai_studio/instructions/audit.md +25 -0
  142. package/plugins/migrate_ai_studio/instructions/classify.md +41 -0
  143. package/plugins/migrate_ai_studio/instructions/migrate.md +34 -0
  144. package/plugins/migrate_ai_studio/instructions/verify.md +20 -0
  145. package/plugins/migrate_ai_studio/plugin.json +11 -0
  146. package/plugins/migrate_ai_studio/schema.cjs +20 -0
  147. package/plugins/migrate_ai_studio/ui/dist/index.html +77 -0
  148. package/prodero.release.json +5 -0
@@ -0,0 +1,212 @@
1
+ /** Bounded, observational evidence. Nothing here chooses or mutates knowledge. */
2
+ import fs from 'node:fs/promises';
3
+ import { z } from 'zod';
4
+ import { checkedDataPath, ensureDataDirectory, writeFileAtomic, sha256, parseCheckpointData } from './storage.js';
5
+ import { withSyncLock } from './sync-transaction.js';
6
+ export const MEMORY_PRESSURE_LIMITS = { sessions: 32, syncs: 32, records: 128, relationships: 128, bytes: 1048576 };
7
+ export const MEMORY_PRESSURE_PATH = 'local/memory-pressure.json';
8
+ const reasons = ['selected', 'marginal-value', 'context-budget', 'required-budget', 'item-limit', 'rank-window', 'inactive', 'superseded', 'scope-mismatch', 'no-match'];
9
+ const count = z.number().finite().nonnegative();
10
+ const id = z.string().max(256);
11
+ const recordSchema = z.object({
12
+ localityKey: z.string().regex(/^[a-f0-9]{64}$/), id, layer: z.enum(['local', 'versioned']), title: z.string().max(160), scope: z.string().max(32), scopeRef: z.string().max(256).nullable(), scopeKind: z.enum(['path', 'topic']).optional(),
13
+ category: z.string().max(32), status: z.string().max(32), estimatedTokens: count, related: z.array(id).max(8),
14
+ }).strict();
15
+ export function pressureRecord(item) {
16
+ return { localityKey: sha256(JSON.stringify([item.scope, item.scopeRef, item.scopeKind ?? null, item.category])), id: item.id, layer: item.layer, title: item.title.slice(0, 160), scope: item.scope, scopeRef: item.scopeRef?.slice(0, 256) ?? null, ...(item.scopeKind ? { scopeKind: item.scopeKind } : {}), category: item.category, status: item.status, estimatedTokens: item.estimatedTokens,
17
+ related: [...new Set([...item.related, ...item.supersedes, ...item.relations.map(r => r.target)])].slice(0, 8) };
18
+ }
19
+ const selectionSchema = recordSchema.extend({ bucket: z.enum(['required', 'contextual', 'filtered']), reason: z.enum(reasons), score: z.number().finite().nullable(), rankScore: z.number().finite().nullable(), signals: z.array(z.object({ kind: z.string().max(32), score: z.number().finite() }).strict()).max(12) }).strict();
20
+ const totalsSchema = z.object({ records: count, tokens: count }).strict();
21
+ const retrievalSchema = z.object({
22
+ queryHash: z.string().regex(/^[a-f0-9]{64}$/), purpose: z.string().max(16),
23
+ budgets: z.object({ contextBudgetTokens: count, requiredContextBudgetTokens: count, maxRequiredItems: count, maxContextItems: count }).strict(),
24
+ totals: z.record(z.enum(reasons), totalsSchema), requiredTruncated: z.boolean(), requiredOmitted: count,
25
+ records: z.array(selectionSchema).max(MEMORY_PRESSURE_LIMITS.records), detailsDropped: count,
26
+ }).strict();
27
+ /** Called at existing selection/filter branches; never evaluates relevance itself. */
28
+ export class RetrievalEvidenceCollector {
29
+ samples = {};
30
+ totals = {};
31
+ requiredOmitted = 0;
32
+ observe(item, bucket, reason, score = null, rankScore = null, signals = []) {
33
+ const total = this.totals[reason] ??= { records: 0, tokens: 0 };
34
+ total.records++;
35
+ total.tokens += item.estimatedTokens;
36
+ if (bucket === 'required' && reason !== 'selected')
37
+ this.requiredOmitted++;
38
+ const sample = this.samples[reason] ??= [];
39
+ if (sample.length < MEMORY_PRESSURE_LIMITS.records)
40
+ sample.push({ ...pressureRecord(item), bucket, reason, score, rankScore, signals: signals.slice(0, 12).map(({ kind, score }) => ({ kind, score })) });
41
+ }
42
+ finish(query, purpose, budgets) {
43
+ // Retain selected and budget-pressure details before lower-ranked/filter samples.
44
+ const records = ['selected', 'required-budget', 'context-budget', 'item-limit', 'marginal-value', 'rank-window', 'scope-mismatch', 'inactive', 'superseded', 'no-match'].flatMap(r => this.samples[r] ?? []).slice(0, MEMORY_PRESSURE_LIMITS.records);
45
+ return { queryHash: sha256(query.trim()), purpose, budgets, totals: this.totals, requiredTruncated: this.requiredOmitted > 0, requiredOmitted: this.requiredOmitted, records, detailsDropped: Object.values(this.totals).reduce((n, t) => n + (t?.records ?? 0), 0) - records.length };
46
+ }
47
+ }
48
+ const categories = ['state_transition', 'refinement', 'duplicate', 'supporting', 'potential_conflict', 'unresolved', 'independent'];
49
+ const relationshipSchema = z.object({ source: id, target: recordSchema, classification: z.enum(categories) }).strict();
50
+ const syncSchema = z.object({ checkpoint: id, sessionIds: z.array(id).max(128), sessionIdsDropped: count,
51
+ changes: z.array(recordSchema.extend({ actions: z.array(z.enum(['created', 'updated', 'merged', 'superseded'])).max(4) }).strict()).max(MEMORY_PRESSURE_LIMITS.records),
52
+ totals: z.object({ created: count, updated: count, merged: count, superseded: count }).strict(), changesDropped: count,
53
+ reconciliation: z.array(relationshipSchema).max(MEMORY_PRESSURE_LIMITS.relationships), classifications: z.record(z.enum(categories), count), relationshipsDropped: count,
54
+ }).strict();
55
+ export class SyncEvidenceCollector {
56
+ relationships = [];
57
+ classifications = {};
58
+ observe(source, target, classification) {
59
+ this.classifications[classification] = (this.classifications[classification] ?? 0) + 1;
60
+ if (this.relationships.length < MEMORY_PRESSURE_LIMITS.relationships)
61
+ this.relationships.push({ source, target, classification });
62
+ }
63
+ finish(records, ids) {
64
+ const sets = { created: new Set(ids.created), updated: new Set(ids.updated), merged: new Set(ids.consolidated), superseded: new Set(ids.superseded) };
65
+ const changes = records.map(record => ({ ...record, actions: Object.keys(sets).filter(key => sets[key].has(record.id)) })).filter(r => r.actions.length);
66
+ return { changes: changes.slice(0, MEMORY_PRESSURE_LIMITS.records), totals: { created: sets.created.size, updated: sets.updated.size, merged: sets.merged.size, superseded: sets.superseded.size }, changesDropped: Math.max(0, changes.length - MEMORY_PRESSURE_LIMITS.records),
67
+ reconciliation: this.relationships, classifications: this.classifications, relationshipsDropped: Object.values(this.classifications).reduce((n, c) => n + (c ?? 0), 0) - this.relationships.length };
68
+ }
69
+ }
70
+ const sessionSchema = retrievalSchema.extend({ sessionId: id }).strict();
71
+ const storeSchema = z.object({ version: z.literal(1), sessionFloor: id.nullable().default(null), syncFloor: id.nullable().default(null), sessions: z.array(sessionSchema).max(MEMORY_PRESSURE_LIMITS.sessions), syncs: z.array(syncSchema).max(MEMORY_PRESSURE_LIMITS.syncs) }).strict();
72
+ const empty = () => ({ version: 1, sessionFloor: null, syncFloor: null, sessions: [], syncs: [] });
73
+ async function readStore(root) {
74
+ try {
75
+ const target = await checkedDataPath(root, MEMORY_PRESSURE_PATH);
76
+ if ((await fs.stat(target)).size > MEMORY_PRESSURE_LIMITS.bytes)
77
+ return { data: empty(), status: 'unavailable' };
78
+ const parsed = storeSchema.safeParse(JSON.parse(await fs.readFile(target, 'utf8')));
79
+ return parsed.success ? { data: parsed.data, status: 'available' } : { data: empty(), status: 'unavailable' };
80
+ }
81
+ catch (error) {
82
+ return { data: empty(), status: error.code === 'ENOENT' ? 'missing' : 'unavailable' };
83
+ }
84
+ }
85
+ function evictOldest(state, kind) {
86
+ const removed = kind === 'sessions' ? state.sessions.pop()?.sessionId : state.syncs.pop()?.checkpoint;
87
+ const field = kind === 'sessions' ? 'sessionFloor' : 'syncFloor';
88
+ if (removed && (!state[field] || removed.localeCompare(state[field]) > 0))
89
+ state[field] = removed;
90
+ }
91
+ async function update(root, mutate) {
92
+ try {
93
+ const current = await readStore(root);
94
+ if (current.status === 'unavailable')
95
+ return 'unavailable';
96
+ const before = JSON.stringify(current.data);
97
+ mutate(current.data);
98
+ current.data.sessions.sort((a, b) => b.sessionId.localeCompare(a.sessionId));
99
+ while (current.data.sessions.length > MEMORY_PRESSURE_LIMITS.sessions)
100
+ evictOldest(current.data, 'sessions');
101
+ current.data.syncs.sort((a, b) => b.checkpoint.localeCompare(a.checkpoint));
102
+ while (current.data.syncs.length > MEMORY_PRESSURE_LIMITS.syncs)
103
+ evictOldest(current.data, 'syncs');
104
+ let text = JSON.stringify(storeSchema.parse(current.data));
105
+ while (Buffer.byteLength(text) > MEMORY_PRESSURE_LIMITS.bytes) {
106
+ if (current.data.sessions.length >= current.data.syncs.length)
107
+ evictOldest(current.data, 'sessions');
108
+ else
109
+ evictOldest(current.data, 'syncs');
110
+ text = JSON.stringify(current.data);
111
+ }
112
+ if (text !== before) {
113
+ await ensureDataDirectory(root, 'local');
114
+ await writeFileAtomic(await checkedDataPath(root, MEMORY_PRESSURE_PATH), text);
115
+ }
116
+ return 'recorded';
117
+ }
118
+ catch {
119
+ return 'unavailable';
120
+ }
121
+ }
122
+ export async function recordRetrievalEvidence(root, sessionId, evidence) {
123
+ try {
124
+ return await withSyncLock(root, () => update(root, state => { if (state.sessionFloor && sessionId.localeCompare(state.sessionFloor) <= 0)
125
+ return; state.sessions = state.sessions.filter(s => s.sessionId !== sessionId); state.sessions.push({ ...evidence, sessionId }); }), { waitMs: 5000 });
126
+ }
127
+ catch {
128
+ return 'unavailable';
129
+ }
130
+ }
131
+ /** Caller holds the existing Sync lock. Only publish after its checkpoint committed. */
132
+ export async function recordSyncEvidenceLocked(root, evidence) {
133
+ return update(root, state => { if (state.syncFloor && evidence.checkpoint.localeCompare(state.syncFloor) <= 0)
134
+ return; state.syncs = state.syncs.filter(s => s.checkpoint !== evidence.checkpoint); state.syncs.push(evidence); });
135
+ }
136
+ /** Read-only recent evidence, never a maintenance recommendation. No knowledge bodies read. */
137
+ export async function getMemoryPressureSnapshot(root) {
138
+ const { data, status } = await readStore(root);
139
+ const checkpoints = [];
140
+ let checkpointCoverage = 'available';
141
+ try {
142
+ const directory = await checkedDataPath(root, 'checkpoints');
143
+ const names = (await fs.readdir(directory)).filter(n => /^chk_[a-zA-Z0-9_-]+\.json$/.test(n)).sort().reverse().slice(0, MEMORY_PRESSURE_LIMITS.syncs);
144
+ for (const name of names)
145
+ checkpoints.push(parseCheckpointData(await fs.readFile(await checkedDataPath(root, 'checkpoints', name), 'utf8'), name));
146
+ }
147
+ catch {
148
+ checkpointCoverage = 'unavailable';
149
+ }
150
+ const committed = new Set(checkpoints.map(c => c.id));
151
+ const syncs = data.syncs.filter(s => committed.has(s.checkpoint));
152
+ const records = new Map();
153
+ const groups = new Map();
154
+ const group = (r) => {
155
+ const key = r.localityKey;
156
+ let g = groups.get(key);
157
+ if (!g) {
158
+ g = { scope: r.scope, scopeRef: r.scopeRef, scopeKind: r.scopeKind, category: r.category, records: new Set(), sessions: new Set(), selected: new Set(), budgetOmitted: new Set(), budgetSessions: new Set(), created: new Set(), updated: new Set(), superseded: new Set(), classifications: {} };
159
+ groups.set(key, g);
160
+ }
161
+ g.records.add(`${r.layer}:${r.id}`);
162
+ return g;
163
+ };
164
+ for (const session of data.sessions)
165
+ for (const r of session.records) {
166
+ if (r.bucket === 'filtered')
167
+ continue;
168
+ const key = `${r.layer}:${r.id}`;
169
+ let evidence = records.get(key);
170
+ if (!evidence) {
171
+ evidence = { record: r, consideredSessions: new Set(), selectedSessions: new Set(), budgetOmissionSessions: new Set() };
172
+ records.set(key, evidence);
173
+ }
174
+ evidence.consideredSessions.add(session.sessionId);
175
+ const g = group(r);
176
+ g.sessions.add(session.sessionId);
177
+ if (r.reason === 'selected') {
178
+ evidence.selectedSessions.add(session.sessionId);
179
+ g.selected.add(key);
180
+ }
181
+ if (r.reason === 'context-budget' || r.reason === 'required-budget') {
182
+ evidence.budgetOmissionSessions.add(session.sessionId);
183
+ g.budgetOmitted.add(key);
184
+ g.budgetSessions.add(session.sessionId);
185
+ }
186
+ }
187
+ const classifications = {};
188
+ for (const sync of syncs) {
189
+ for (const [kind, n] of Object.entries(sync.classifications))
190
+ classifications[kind] = (classifications[kind] ?? 0) + (n ?? 0);
191
+ for (const change of sync.changes) {
192
+ const g = group(change);
193
+ for (const action of change.actions)
194
+ if (action !== 'merged')
195
+ g[action].add(`${change.layer}:${change.id}`);
196
+ }
197
+ for (const rel of sync.reconciliation) {
198
+ const g = group(rel.target);
199
+ g.classifications[rel.classification] = (g.classifications[rel.classification] ?? 0) + 1;
200
+ }
201
+ }
202
+ return { version: 1, status, checkpointCoverage, limits: MEMORY_PRESSURE_LIMITS, sessions: data.sessions, syncs,
203
+ coverage: { sessionFloor: data.sessionFloor, syncFloor: data.syncFloor, sessions: data.sessions.length, checkpoints: checkpoints.length, checkpointsWithoutDetails: checkpoints.filter(c => !syncs.some(s => s.checkpoint === c.id)).map(c => c.id), retrievalDetailsDropped: data.sessions.reduce((n, s) => n + s.detailsDropped, 0) },
204
+ totals: { relevantConsiderations: data.sessions.reduce((n, s) => n + Object.entries(s.totals).filter(([r]) => ['selected', 'marginal-value', 'context-budget', 'required-budget', 'item-limit', 'rank-window'].includes(r)).reduce((n, [, v]) => n + (v?.records ?? 0), 0), 0),
205
+ selectedKnowledgeTokens: data.sessions.reduce((n, s) => n + (s.totals.selected?.tokens ?? 0), 0),
206
+ budgetOmittedKnowledgeTokens: data.sessions.reduce((n, s) => n + (s.totals['context-budget']?.tokens ?? 0) + (s.totals['required-budget']?.tokens ?? 0), 0),
207
+ repeatedBudgetOmittedRecords: [...records.values()].filter(r => r.budgetOmissionSessions.size > 1).length,
208
+ requiredTruncatedSessions: data.sessions.filter(s => s.requiredTruncated).length, newKnowledge: checkpoints.reduce((n, c) => n + (c.createdMemory?.length ?? 0), 0), updatedKnowledge: checkpoints.reduce((n, c) => n + (c.updatedMemory?.length ?? 0), 0), supersededKnowledge: checkpoints.reduce((n, c) => n + (c.supersededMemory?.length ?? 0), 0), classifications },
209
+ records: [...records.values()].map(r => ({ ...r.record, consideredSessions: r.consideredSessions.size, selectedSessions: r.selectedSessions.size, budgetOmissionSessions: r.budgetOmissionSessions.size })),
210
+ groups: [...groups.entries()].map(([key, g]) => ({ key, scope: g.scope, scopeRef: g.scopeRef, scopeKind: g.scopeKind, category: g.category, records: [...g.records], retrievalSessions: g.sessions.size, selectedRecords: g.selected.size, budgetOmittedRecords: g.budgetOmitted.size, budgetOmissionSessions: g.budgetSessions.size, newRecords: g.created.size, updatedRecords: g.updated.size, supersededRecords: g.superseded.size, classifications: g.classifications })), };
211
+ }
212
+ //# sourceMappingURL=memory-pressure.js.map
@@ -0,0 +1,82 @@
1
+ /** Sync orchestration adapter. Questions, answers and jobs belong to Curator. */
2
+ import { createCurationReview, resumeCurationReview, latestCurationReview, loadCurationReview, assertCurationHead, CurationReviewError } from './curation-review.js';
3
+ import { planKnowledgeCuration } from './knowledge-curation.js';
4
+ import { graphicalReviewPayload } from './curation-graphical.js';
5
+ import { prepareSemanticCuration, inspectSemanticCuration, curationAnswers } from './curation-semantic-jobs.js';
6
+ import { prepareMemoryTransformation, validateMemoryTransformationProposal } from './memory-transformation-proposal.js';
7
+ import { previewMemoryTransformation } from './memory-transformation.js';
8
+ import { scopeEffect, semanticHash } from './curation-semantic-contract.js';
9
+ import { deterministicBaseline } from './curation-revision.js';
10
+ /** Explicit expiry refresh carries staged semantics and non-approval decisions
11
+ * into a fresh revision. It neither reopens a question nor records an apply. */
12
+ export async function refreshSyncQuestions(root, sourceIds, input) {
13
+ const review = await loadCurationReview(root, input.reviewId, { allowExpired: true }), revision = review.snapshot.revision;
14
+ await assertCurationHead(root, review);
15
+ if (revision.id !== input.reviewRevisionId || review.snapshot.planHash !== input.planHash || review.state.revision !== input.decisionRevision || semanticHash(await curationAnswers(root, revision.lineageId)) !== input.answerLogHash)
16
+ throw new CurationReviewError('review_changed', 'This saved question changed. Inspect current progress.');
17
+ const payload = await graphicalReviewPayload(root, review), baseline = deterministicBaseline(revision);
18
+ if (payload.projection.lifecycle !== 'stale')
19
+ throw new CurationReviewError('review_changed', 'This review has not expired. Inspect its current questions.');
20
+ if ((await planKnowledgeCuration(root, { memorySyncSources: sourceIds })).planId !== baseline.planId)
21
+ throw new CurationReviewError('semantic_stale', 'Knowledge or evidence changed. Re-analyze before refreshing this question.');
22
+ const next = await createCurationReview(root, { parent: review, operationId: `question_refresh_${semanticHash([revision.id, input.decisionRevision, input.answerLogHash])}`, applied: [] }, sourceIds);
23
+ return graphicalReviewPayload(root, next);
24
+ }
25
+ import { sameApplicability } from './applicability.js';
26
+ export async function prepareSyncCuration(root, sourceIds, reviewId, retry = false) {
27
+ const previous = reviewId ? undefined : await latestCurationReview(root, (await planKnowledgeCuration(root, { memorySyncSources: sourceIds })).planId);
28
+ const review = reviewId ? await resumeCurationReview(root, reviewId) : previous ?? await createCurationReview(root, undefined, sourceIds);
29
+ const payload = await graphicalReviewPayload(root, review), g = payload.graphical;
30
+ if (g.activeQuestionCount || g.deferredQuestionIds.length)
31
+ return { reviewId: review.snapshot.reviewId, payload };
32
+ // Replaying Prepare after Retry must return its live attempt, not the old initial request.
33
+ if (g.continuationJobId)
34
+ return { reviewId: review.snapshot.reviewId, payload, job: await inspectSemanticCuration(root, g.continuationJobId) };
35
+ const failed = retry ? g.jobs.find(j => !j.suspended && ['rejected', 'stale'].includes(j.status)) : undefined;
36
+ const job = await prepareSemanticCuration(root, { reviewId: review.snapshot.reviewId, reviewRevisionId: review.snapshot.revision.id, componentId: failed?.componentId ?? review.snapshot.revision.dependencies.groups[0].component, requestId: failed ? `sync_retry_${failed.id}` : `sync_${review.snapshot.reviewId}`, reason: failed ? (failed.status === 'rejected' ? 'validation_correction' : 'resume') : 'initial', ...(failed ? failed.status === 'rejected' ? { correctionForJobId: failed.id } : { resumeForJobId: failed.id } : {}), limits: { maxItems: 24 } });
37
+ return { reviewId: review.snapshot.reviewId, payload, job };
38
+ }
39
+ export async function inspectSyncCuration(root, reviewId) {
40
+ return graphicalReviewPayload(root, await resumeCurationReview(root, reviewId));
41
+ }
42
+ /** Reuse the existing Sync validator/writer; Curator staging is never authority.
43
+ * Protected, shared or scope-changing outputs stay current. */
44
+ export async function previewSyncCuration(root, candidateId, payload) {
45
+ const typed = payload.review.snapshot.revision.typedPlan;
46
+ if (typed.kind !== 'semantic-v2')
47
+ return undefined;
48
+ const analysis = typed.analyses.at(-1);
49
+ const proposal = analysis.proposal;
50
+ if (proposal.issues.length || proposal.additionalEvidenceRequired.length || proposal.limitations.length || proposal.sources.some(s => s.disposition === 'unresolved'))
51
+ return undefined;
52
+ const prepared = await prepareMemoryTransformation(root, candidateId), b = prepared.bundle;
53
+ if (!prepared.canPropose)
54
+ throw new Error('Complete transformation evidence is required');
55
+ if (analysis.bundle.expectedSources.length !== b.sourceIds.length || analysis.bundle.expectedSources.some(s => b.revisions.active[s.id] !== s.revision))
56
+ throw new Error('Sync analysis sources changed');
57
+ const eligible = proposal.outputs.filter(o => o.sourceIds.every(id => {
58
+ const s = b.activeSources.find(s => s.id === id), first = b.activeSources.find(s => s.id === o.sourceIds[0]);
59
+ if (!s || !first || s.protected || s.layer !== 'local' || s.knowledge.status !== 'active' || s.knowledge.contextPolicy !== 'relevant' || s.knowledge.authority !== 'inferred')
60
+ return false;
61
+ const legacyRepresentable = s.knowledge.applicability?.kind !== 'legacy' || s.knowledge.scope === 'global' || s.knowledge.scope === 'module' && s.knowledge.scopeKind === 'path';
62
+ return legacyRepresentable && sameApplicability(s.knowledge, first.knowledge) && scopeEffect(s.knowledge, o.applicability) === 'unchanged';
63
+ }));
64
+ // A source with several outputs is replaced only when every output is eligible.
65
+ let allowed = eligible;
66
+ while (true) {
67
+ const next = allowed.filter(o => o.sourceIds.every(id => proposal.sources.find(s => s.sourceId === id).outputKeys.every(k => allowed.some(a => a.key === k))));
68
+ if (next.length === allowed.length)
69
+ break;
70
+ allowed = next;
71
+ }
72
+ const records = allowed.map(o => {
73
+ const source = b.activeSources.find(s => s.id === o.sourceIds[0]).knowledge;
74
+ return { key: o.key.replace(/^out_/, 'draft_'), category: source.category, priority: source.priority, title: o.title, body: o.body, sourceIds: o.sourceIds, applicability: o.applicability, scope: source.scope === 'global' ? 'global' : source.scopeKind === 'path' ? 'path' : 'project', ...(source.scopeKind === 'path' ? { path: source.scopeRef } : {}) };
75
+ });
76
+ const transformed = { ...prepared.binding, records, dispositions: b.sourceIds.map(sourceId => {
77
+ const keys = allowed.filter(o => o.sourceIds.includes(sourceId)).map(o => o.key.replace(/^out_/, 'draft_'));
78
+ return { sourceId, action: keys.length ? 'consolidate' : 'keep', proposedKeys: keys, rationale: proposal.sources.find(s => s.sourceId === sourceId).rationale, coverage: 'Validated Curator clause coverage; protected and shared sources remain current.', omittedAsRedundant: [], unresolvedInformation: [] };
79
+ }), summary: `${proposal.rationale}\nCurator review: ${payload.review.snapshot.reviewId}; revision: ${payload.review.snapshot.revision.id}; answer events: ${analysis.bundle.answers.map(a => a.id).join(', ') || 'none'}.`, warnings: [], additionalEvidenceRequired: [] };
80
+ return previewMemoryTransformation(root, await validateMemoryTransformationProposal(root, transformed));
81
+ }
82
+ //# sourceMappingURL=memory-sync-curator.js.map