@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,316 @@
1
+ import { copyStorageTree, ensureStorageDirectory, ensureDataDirectory } from "./storage.js";
2
+ import { PRIVATE_FILE_MODE } from "../filesystem/permissions.js";
3
+ import { validateRevision, revisionHash } from './curation-revision.js';
4
+ import { renderReviewProjection } from './curation-review-state.js';
5
+ import fs from 'node:fs/promises';
6
+ import path from 'node:path';
7
+ import { loadMemoryCatalog } from './memory-index.js';
8
+ import { readConfig, checkedDataPath, sha256, listCheckpoints } from './storage.js';
9
+ import { parseKnowledge, renderKnowledge } from './memory.js';
10
+ import { KNOWLEDGE_ANALYSIS_VERSION } from './knowledge-analysis.js';
11
+ import { assertSafeCurationData, collectCurationEvidence, evidenceFor } from './curation-evidence.js';
12
+ import { refineCurationClauses } from './curation-clauses.js';
13
+ const CURATION_ENGINE = KNOWLEDGE_ANALYSIS_VERSION + '/clauses-2';
14
+ import { withApplicability, sameApplicability, narrowsApplicability, applicabilityLabel } from './applicability.js';
15
+ import { evaluateKnowledgeTransition, KnowledgePolicyReviewRequired, trustedKnowledgeBatchEvidence } from './knowledge-authority.js';
16
+ import { withSyncLock, ownsSyncLock, recoverSyncTransaction, commitSyncTransaction } from './sync-transaction.js';
17
+ import { buildIndex } from './index.js';
18
+ import { prepareContext } from './context.js';
19
+ import { sortableId } from './ids.js';
20
+ import { estimateTokens, knowledgeText } from './telemetry.js';
21
+ const digest = (value) => sha256(JSON.stringify(value));
22
+ const seal = (plan) => ({ ...plan, planId: digest(plan) });
23
+ const active = (items) => items.filter(i => i.status === 'active');
24
+ function stats(items) { const a = active([...new Map(items.map(i => [i.id, i])).values()]), required = a.filter(i => i.contextPolicy === 'required'); return { records: a.length, required: required.length, projectWideRequired: required.filter(i => i.applicability?.kind === 'project' || i.applicability?.kind === 'legacy' && i.scope === 'global').length, scopedRequired: required.filter(i => i.applicability?.kind === 'scoped').length, relevant: a.filter(i => i.contextPolicy === 'relevant').length, unresolved: a.filter(i => i.applicability?.kind === 'unresolved').length, requiredTokens: required.reduce((n, i) => n + estimateTokens(knowledgeText(i)), 0) }; }
25
+ export function corpusSummary(proposals) { return { consolidated: new Set(proposals.flatMap(p => p.outputs.filter(o => o.id.startsWith('mem_curate_shared_')).map(o => o.id))).size, before: stats(proposals.map(p => p.before)), proposed: stats(proposals.flatMap(p => p.attention.length ? p.before : p.outputs)), decomposed: proposals.filter(p => p.outputs.length > 1 && !p.attention.length).length, attention: proposals.filter(p => p.attention.length).length, unchanged: proposals.filter(p => !p.changes.length || p.attention.length).length, safe: proposals.filter(p => p.changes.length && !p.attention.length).length }; }
26
+ /** Pure read phase: cache rebuilds stay in memory, no plan/history persistence. */
27
+ export async function planKnowledgeCuration(root, options = {}) {
28
+ const config = await readConfig(root);
29
+ if (!config || config.knowledgeSchemaVersion !== 3)
30
+ throw new Error('Update knowledge format before curation');
31
+ const catalog = await loadMemoryCatalog(root, { persistCache: false });
32
+ const all = await Promise.all(catalog.summaries.filter(s => !options.memorySyncSources || options.memorySyncSources.includes(s.id)).map(s => catalog.load(s.id)));
33
+ if (options.memorySyncSources) {
34
+ const ids = [...new Set(options.memorySyncSources)].sort();
35
+ if (!ids.length || ids.length > 24 || ids.length !== options.memorySyncSources.length)
36
+ throw new Error('Invalid Sync analysis scope');
37
+ const proposals = ids.map(id => {
38
+ const before = all.find(i => i.id === id && ['active', 'disputed'].includes(i.status)), summary = catalog.summaries.find(s => s.id === id);
39
+ if (!before || !summary)
40
+ throw new Error('Sync analysis source changed');
41
+ return { id, sourceId: id, revision: catalog.originalHashes.get(id), path: summary.relativePath, before, outputs: [before], group: 'Memory', changes: [], evidence: [], attention: [], confidence: 'high', mapping: [{ start: 0, end: before.content.length, outputId: id }], consolidationSources: ids };
42
+ });
43
+ const canonicalRoot = await fs.realpath(root);
44
+ const plan = seal({ version: 1, engine: CURATION_ENGINE, project: canonicalRoot, knowledgeSchema: 3, memorySyncSources: ids, fingerprint: digest(ids.map(id => [id, catalog.originalHashes.get(id)])), proposals, summary: corpusSummary(proposals), accountedSources: ids.length, evidenceRoot: canonicalRoot, evidenceTruncated: false });
45
+ validateCurationPlan(plan);
46
+ return plan;
47
+ }
48
+ const inventory = await collectCurationEvidence(options.evidenceRoot ?? root);
49
+ const proposals = [];
50
+ for (const before of all.filter(i => i.authority === 'legacy_inherited' && !i.derivedFrom.some(ref => ref.startsWith('curation-source:')) && i.contextPolicy === 'required' && ['active', 'disputed'].includes(i.status))) {
51
+ const summary = catalog.summaries.find(s => s.id === before.id);
52
+ const parts = refineCurationClauses(before.title, before.content);
53
+ const projectEvidence = evidenceFor(before.content, inventory);
54
+ const structured = /^Decision:/m.test(before.content);
55
+ const attention = [];
56
+ const conflicts = (before.relations ?? []).filter(r => r.type === 'contradicts');
57
+ if (before.status === 'disputed' || conflicts.length)
58
+ attention.push(`Existing conflict: ${conflicts.map(r => r.target).join(', ') || 'disputed lifecycle state'}`);
59
+ if (parts.some(p => p.transient))
60
+ attention.push('Contains potentially stale historical state; verify current validity before changing protection.');
61
+ const outputs = parts.map((p, n) => {
62
+ // Parent policy is a ceiling: only a source-supported normative core inherits it.
63
+ const policy = before.contextPolicy === 'required' && p.normative ? 'required' : 'relevant';
64
+ const category = policy === 'required' ? 'instruction' : p.category;
65
+ const app = before.applicability && before.applicability.kind !== 'legacy' && before.applicability.kind !== 'unresolved' ? before.applicability : !structured || p.applicability.kind === 'unresolved' ? (before.applicability ?? { kind: 'legacy' }) : p.applicability;
66
+ return withApplicability({ ...before, id: parts.length === 1 ? before.id : `mem_curate_${digest([before.id, catalog.originalHashes.get(before.id), n]).slice(0, 24)}`, category, contextPolicy: policy, authority: 'legacy_inherited', title: p.title, content: p.content, derivedFrom: [...before.derivedFrom, ...(p.segments ?? [{ start: p.start, end: p.end }]).map(m => `curation-source:${before.id}@${catalog.originalHashes.get(before.id)}#${m.start}-${m.end}`)], related: [...new Set([...before.related, ...(parts.length > 1 ? [before.id] : [])])] }, app);
67
+ });
68
+ // Contradiction metadata is evidence of unresolved policy, never permission to demote it.
69
+ if (before.status === 'disputed' || conflicts.length)
70
+ outputs.splice(0, outputs.length, structuredClone(before));
71
+ if (structured && parts.some(p => p.applicability.kind === 'unresolved'))
72
+ attention.push('Applicability remains unresolved; review the intended project boundary.');
73
+ if (outputs.some(o => !sameApplicability(before, o) && !narrowsApplicability(before, o)))
74
+ attention.push('Proposed applicability is not provably narrower; keep the source until reviewed.');
75
+ const changes = [];
76
+ if (outputs.length > 1)
77
+ changes.push('decomposition');
78
+ if (outputs.some(i => !sameApplicability(before, i)))
79
+ changes.push('applicability-narrowing');
80
+ if (outputs.some(i => i.contextPolicy !== before.contextPolicy))
81
+ changes.push('policy-change');
82
+ if (outputs.some(i => i.category !== before.category))
83
+ changes.push('category-change');
84
+ if (!changes.length)
85
+ outputs.splice(0, outputs.length, structuredClone(before));
86
+ if (changes.length && outputs.some(i => i.contextPolicy === 'required' && (i.applicability?.kind === 'legacy' && i.scope === 'global')))
87
+ attention.push('Required project-wide boundary remains uncertain.');
88
+ const group = outputs.flatMap(i => i.applicability?.kind === 'scoped' ? i.applicability.subsystems ?? i.applicability.paths ?? i.applicability.taskKinds ?? [] : [])[0] ?? 'Project-wide';
89
+ if (outputs.some(o => o.applicability?.kind === 'scoped' && o.applicability.subsystems?.some(n => /^(?:DB|SQL|JSON|HTML|PHP|AI)$/.test(n) || /(?:Harness|Helper)$/.test(n))))
90
+ attention.push('Applicability currently names a generic technology or helper rather than a resolved subsystem; review its real work boundary.');
91
+ const hadAttention = attention.length > 0;
92
+ const investigation = hadAttention || projectEvidence.length ? {
93
+ outcome: attention.length ? (parts.length > 1 ? 'reduced-ambiguity' : 'needs-decision') : parts.every(p => p.transient) ? 'historical-only' : 'resolved-proposal',
94
+ state: 'cannot-determine',
95
+ evidence: projectEvidence,
96
+ explanation: attention.length ? 'Evidence informs the proposal but cannot authorize choosing between protected meanings.' : 'Supporting history remains traceable; static evidence does not prove test execution or production rollout. Exact batch review is still required.'
97
+ } : undefined;
98
+ const issueSourceIds = new Set([before.id, ...conflicts.map(r => r.target)]);
99
+ const issueBasis = [...issueSourceIds].sort().map(id => ({ id, revision: catalog.originalHashes.get(id) ?? 'missing' }));
100
+ proposals.push({ issueBasis, investigation, id: before.id, sourceId: before.id, revision: catalog.originalHashes.get(before.id), path: summary.relativePath, before, outputs, group, changes, evidence: [...parts.flatMap(p => p.evidence), ...projectEvidence.map(e => `${e.kind}: ${e.path}:${e.line} (${e.revision.slice(0, 12)}) ${e.excerpt}`)], attention, confidence: attention.length ? 'limited' : 'high', mapping: before.status === 'disputed' || conflicts.length ? [{ start: 0, end: before.content.length, outputId: before.id }] : parts.flatMap((p, n) => (p.segments ?? [{ start: p.start, end: p.end }]).map(m => ({ ...m, outputId: outputs[n]?.id ?? before.id }))).sort((a, b) => a.start - b.start) });
101
+ }
102
+ // Only source-identical normative cores with equal applicability/authority can
103
+ // share an output. Similar vocabulary is never a semantic equivalence proof.
104
+ const cores = new Map();
105
+ for (const p of proposals.filter(p => !p.attention.length))
106
+ for (const o of p.outputs.filter(o => o.contextPolicy === 'required')) {
107
+ const key = digest([o.content, o.applicability, o.scope, o.scopeRef, o.authority, o.priority, p.path.startsWith('local/')]);
108
+ cores.set(key, [...(cores.get(key) ?? []), { proposal: p, output: o }]);
109
+ }
110
+ for (const [key, group] of cores)
111
+ if (new Set(group.map(g => g.proposal.id)).size > 1) {
112
+ const shared = { ...group[0].output, id: `mem_curate_shared_${key.slice(0, 20)}` };
113
+ for (const field of ['sourceSessions', 'derivedFrom', 'supersedes', 'related'])
114
+ shared[field] = [...new Set(group.flatMap(g => g.output[field]))];
115
+ shared.related = [...new Set([...shared.related, ...group.map(g => g.proposal.id)])];
116
+ shared.evidence = Array.from(new Map(group.flatMap(g => (g.output.evidence ?? []).map(e => [JSON.stringify(e), e]))).values());
117
+ shared.relations = Array.from(new Map(group.flatMap(g => (g.output.relations ?? []).map(e => [JSON.stringify(e), e]))).values());
118
+ for (const g of group) {
119
+ const old = g.output.id;
120
+ g.proposal.outputs = g.proposal.outputs.map(o => o.id === old ? structuredClone(shared) : o);
121
+ g.proposal.mapping.forEach(m => { if (m.outputId === old)
122
+ m.outputId = shared.id; });
123
+ g.proposal.consolidationSources = group.map(g => g.proposal.id);
124
+ g.proposal.changes.push('normative-consolidation');
125
+ g.proposal.evidence.push('Identical normative core, applicability, authority and priority; all source provenance retained.');
126
+ }
127
+ }
128
+ const fingerprint = digest(catalog.summaries.map(s => [s.id, catalog.originalHashes.get(s.id)]).sort());
129
+ const plan = seal({ version: 1, engine: CURATION_ENGINE, project: path.resolve(root), knowledgeSchema: 3, fingerprint, proposals, summary: corpusSummary(proposals), accountedSources: proposals.length, evidenceRoot: inventory.root, evidenceTruncated: inventory.truncated, ...(inventory.sensitiveExcluded ? { evidenceExclusions: [{ reason: 'Sensitive evidence excluded', count: inventory.sensitiveExcluded }] } : {}) });
130
+ validateCurationPlan(plan);
131
+ return plan;
132
+ }
133
+ export function validateCurationPlan(plan) {
134
+ assertSafeCurationData(plan);
135
+ const { planId, ...body } = plan;
136
+ if (digest(body) !== planId)
137
+ throw new Error('Curation plan changed');
138
+ if (plan.engine !== CURATION_ENGINE || plan.knowledgeSchema !== 3 || new Set(plan.proposals.map(p => p.sourceId)).size !== plan.proposals.length || plan.accountedSources !== plan.proposals.length)
139
+ throw new Error('Invalid curation accounting');
140
+ for (const p of plan.proposals) {
141
+ if (!p.outputs.length || p.before.id !== p.sourceId)
142
+ throw new Error('Unaccounted curation source');
143
+ for (const o of p.outputs) {
144
+ const parsed = parseKnowledge(renderKnowledge(o), `${o.id}.md`, 3);
145
+ if (parsed.category !== o.category || parsed.status !== o.status || parsed.content !== o.content)
146
+ throw new Error("Invalid canonical proposal");
147
+ if (!p.before.sourceSessions.every(v => o.sourceSessions.includes(v)) || !p.before.derivedFrom.every(v => o.derivedFrom.includes(v)) || !(p.before.evidence ?? []).every(v => (o.evidence ?? []).some(e => JSON.stringify(e) === JSON.stringify(v))) || !p.before.related.every(v => o.related.includes(v)) || !p.before.supersedes.every(v => o.supersedes.includes(v)))
148
+ throw new Error('Curation provenance or relationships were lost');
149
+ if (o.authority !== p.before.authority || o.priority !== p.before.priority)
150
+ throw new Error('Curation cannot infer authority or criticality');
151
+ }
152
+ if (p.outputs.length === 1 && p.outputs[0].content.trim() !== p.before.content.trim())
153
+ throw new Error("Unaccounted source meaning");
154
+ if (p.outputs.length > 1) {
155
+ const reproduced = p.mapping.map(m => p.before.content.slice(m.start, m.end)).join('');
156
+ if (reproduced.replace(/\s+/g, ' ') !== p.before.content.trim().replace(/\s+/g, ' '))
157
+ throw new Error('Unaccounted source clauses');
158
+ for (const output of p.outputs) {
159
+ if (output.content !== p.mapping.filter(m => m.outputId === output.id).map(m => p.before.content.slice(m.start, m.end).trim()).join('\n\n'))
160
+ throw new Error('Invalid clause mapping');
161
+ }
162
+ }
163
+ }
164
+ }
165
+ function selected(plan, ids, revision) {
166
+ if (revision) {
167
+ validateRevision(revision);
168
+ if (revisionHash(revision.typedPlan.plan) !== revisionHash(plan))
169
+ throw new Error('Revision plan binding changed');
170
+ }
171
+ else
172
+ validateCurationPlan(plan);
173
+ if (!ids.length || new Set(ids).size !== ids.length)
174
+ throw new Error('Select a non-empty exact batch');
175
+ return ids.map(id => {
176
+ const p = plan.proposals.find(p => p.id === id);
177
+ if (!p || p.attention.length || !p.changes.length)
178
+ throw new Error(`Proposal needs individual analysis or has no change: ${id}`);
179
+ return p;
180
+ });
181
+ }
182
+ /** Exact review material for a trusted host adapter; never itself a capability. */
183
+ function proposalEntries(p) { return p.outputs.length === 1 && p.outputs[0].id === p.sourceId ? [{ before: p.before, after: p.outputs[0] }] : [...p.outputs.map(after => ({ before: undefined, after })), { before: p.before, after: { ...p.before, status: 'superseded', related: [...new Set([...p.before.related, ...p.outputs.map(i => i.id)])] } }]; }
184
+ export function curationReviewEntries(plan, ids, revision) { const chosen = selected(plan, ids, revision); for (const p of chosen)
185
+ if (p.consolidationSources?.some(id => !ids.includes(id)))
186
+ throw new Error('Select the complete normative consolidation group'); return [...new Map(chosen.flatMap(proposalEntries).map(e => [e.after.id, e])).values()]; }
187
+ /** A pre-commit source/evidence conflict; no curation writes have started. */
188
+ export class StaleCurationPlanError extends Error {
189
+ }
190
+ export async function applyKnowledgeCuration(root, plan, ids, receipt) {
191
+ if (plan.memorySyncSources)
192
+ throw new Error('Sync analysis cannot grant curation Apply authority');
193
+ if (path.resolve(root) !== plan.project)
194
+ throw new Error('Curation project mismatch');
195
+ const chosen = selected(plan, ids, receipt?.reviewSnapshot?.revision), entries = curationReviewEntries(plan, ids, receipt?.reviewSnapshot?.revision);
196
+ // No approved boolean can mint this capability. Fail before mutation/lock.
197
+ for (const e of entries) {
198
+ const d = evaluateKnowledgeTransition(root, e.after, e.before, { exact: true });
199
+ if (d.decision !== 'allowed')
200
+ throw new KnowledgePolicyReviewRequired(d);
201
+ }
202
+ const commit = async () => {
203
+ await recoverSyncTransaction(root);
204
+ const prior = (await listCheckpoints(root)).find(c => receipt?.reviewRevisionId ? c.curation?.review?.operationId === receipt.operationId : c.curation?.planId === plan.planId && digest(c.curation.selected) === digest(ids));
205
+ if (prior) {
206
+ await buildIndex(root);
207
+ return { checkpoint: prior.id, repeated: true };
208
+ }
209
+ for (const e of chosen.flatMap(p => p.investigation?.evidence ?? [])) {
210
+ const absolute = path.resolve(plan.evidenceRoot, e.path);
211
+ try {
212
+ if (!absolute.startsWith(path.resolve(plan.evidenceRoot) + path.sep) || await fs.realpath(absolute) !== absolute || sha256(await fs.readFile(absolute, 'utf8')) !== e.revision)
213
+ throw new StaleCurationPlanError(`Stale project evidence: ${e.path}`);
214
+ }
215
+ catch (error) {
216
+ if (error.code === 'ENOENT')
217
+ throw new StaleCurationPlanError(`Missing project evidence: ${e.path}`);
218
+ throw error;
219
+ }
220
+ }
221
+ const writes = [];
222
+ const preimages = [];
223
+ for (const p of chosen) {
224
+ const current = await fs.readFile(await checkedDataPath(root, p.path), 'utf8').catch(error => {
225
+ if (error.code === 'ENOENT')
226
+ throw new StaleCurationPlanError(`Missing curation source: ${p.sourceId}`);
227
+ throw error;
228
+ });
229
+ if (sha256(current) !== p.revision)
230
+ throw new StaleCurationPlanError(`Stale curation source: ${p.sourceId}`);
231
+ preimages.push({ path: p.path, revision: p.revision, content: current });
232
+ }
233
+ for (const e of entries) {
234
+ const p = chosen.find(p => p.sourceId === e.before?.id || p.outputs.some(i => i.id === e.after.id));
235
+ writes.push({ path: e.before ? p.path : `${p.path.startsWith('local/') ? 'local/' : ''}memory/${e.after.id}.md`, before: e.before ? p.revision : null, after: renderKnowledge(e.after) });
236
+ }
237
+ const id = sortableId('chk');
238
+ writes.push({ path: `checkpoints/${id}.json`, before: null, after: JSON.stringify({ id, createdAt: new Date().toISOString(), previousCheckpoint: null, sessions: [], createdMemory: entries.filter(e => !e.before).map(e => e.after.id), updatedMemory: ids, supersededMemory: entries.filter(e => e.after.status === 'superseded').map(e => e.after.id), duplicateMemory: [], conflicts: [], curation: { ...(receipt ? { review: receipt } : {}), reviewEvidence: trustedKnowledgeBatchEvidence(root), planId: plan.planId, engine: plan.engine, selected: ids, leftUnchanged: plan.proposals.filter(p => !ids.includes(p.id)).map(p => p.id), plan, preimages, postimages: writes.map(w => ({ path: w.path, revision: sha256(w.after), content: w.after })) } }, null, 2) + '\n' });
239
+ await commitSyncTransaction(root, writes, { policyMode: 'exact' });
240
+ await buildIndex(root);
241
+ return { checkpoint: id, repeated: false };
242
+ };
243
+ return ownsSyncLock(root) ? commit() : withSyncLock(root, commit);
244
+ }
245
+ export async function curationRecommendation(root) {
246
+ const plan = await planKnowledgeCuration(root);
247
+ const { curationReviewView } = await import('./curation-review.js');
248
+ const { review, projection } = await curationReviewView(root, plan);
249
+ return { planId: plan.planId, reviewId: review?.snapshot.reviewId, state: projection.lifecycle,
250
+ prominent: !projection.paused && (projection.needsInputNow > 0 || projection.readyChangeGroups > 0), projection };
251
+ }
252
+ export async function deferKnowledgeCuration(root, planId) {
253
+ const plan = await planKnowledgeCuration(root);
254
+ if (plan.planId !== planId)
255
+ throw new Error('Curation changed; refresh');
256
+ return withSyncLock(root, async () => { await recoverSyncTransaction(root); const id = sortableId('chk'); await commitSyncTransaction(root, [{ path: `checkpoints/${id}.json`, before: null, after: JSON.stringify({ id, createdAt: new Date().toISOString(), sessions: [], curationDefer: planId }) + '\n' }]); return { state: 'deferred' }; });
257
+ }
258
+ // Explicitly labelled examples, not a claim of representative project coverage.
259
+ export const CURATION_TASKS = ['Synthetic example: review documentation', 'Synthetic example: run unit tests', 'Synthetic example: review deployment'];
260
+ function contextMetrics(c) { return { unknownRequired: c.applicability?.unknownRequired ?? 0, applicabilityDecisions: c.applicability?.decisions, requiredIds: c.required.map(i => i.id), requiredCandidates: c.required.length + c.metrics.requiredGlobalExcludedItems, requiredCandidateTokens: c.metrics.requiredGlobalCandidateTokens, requiredSelected: c.required.length, requiredSelectedTokens: c.metrics.requiredGlobalTokens, requiredOmissions: c.metrics.requiredGlobalExcludedItems, relevantSelected: c.relevant.length, relevantOmittedByItemLimit: c.selection.omittedRelevantByItemLimit, relevantOmittedByMarginalValue: c.selection.omittedRelevantByMarginalValue ?? 0, allocation: c.allocation, totalContextTokens: c.metrics.contextTokens, pressure: c.metrics.requiredGlobalPressure, contextNeeded: c.policyReadiness?.state === 'context-needed', readiness: c.policyReadiness, limits: c.limits }; }
261
+ /** Real selector over an isolated canonical corpus; no lifecycle Prepare/Sync. */
262
+ export async function simulateKnowledgeCuration(root, plan, tasks = CURATION_TASKS, mode = 'safe-batch', selectedIds, revision) {
263
+ if (revision) {
264
+ validateRevision(revision);
265
+ if (revision.typedPlan.plan.planId !== plan.planId)
266
+ throw new Error('Simulation revision mismatch');
267
+ }
268
+ else
269
+ validateCurationPlan(plan);
270
+ if (path.resolve(root) !== plan.project)
271
+ throw new Error('Simulation project mismatch');
272
+ if (selectedIds?.length)
273
+ curationReviewEntries(plan, selectedIds, revision);
274
+ const temp = await fs.mkdtemp(path.join(await ensureDataDirectory(root, '.cache'), 'prodero-curation-simulation-'));
275
+ try {
276
+ await ensureStorageDirectory(path.join(temp, '.prodero'), true);
277
+ const config = await readConfig(root);
278
+ await fs.writeFile(path.join(temp, '.prodero/config.json'), JSON.stringify(config), { mode: PRIVATE_FILE_MODE });
279
+ // Copy only canonical stores consumed by the selector, never caches or sessions.
280
+ for (const dir of ['memory', 'local/memory', 'skills', 'local/skills']) {
281
+ const from = await checkedDataPath(root, dir);
282
+ try {
283
+ await copyStorageTree(from, path.join(temp, '.prodero', dir), true);
284
+ }
285
+ catch (e) {
286
+ if (e.code !== 'ENOENT')
287
+ throw e;
288
+ }
289
+ }
290
+ const copied = await loadMemoryCatalog(temp, { persistCache: false });
291
+ await Promise.all(copied.summaries.map(i => copied.load(i.id)));
292
+ if (digest(copied.summaries.map(i => [i.id, copied.originalHashes.get(i.id)]).sort()) !== plan.fingerprint)
293
+ throw new Error('Simulation corpus changed since planning');
294
+ const before = await Promise.all(tasks.map(async (task) => ({ task, before: contextMetrics(await prepareContext(temp, { query: task })) })));
295
+ for (const p of plan.proposals) {
296
+ if (sha256(await fs.readFile(await checkedDataPath(temp, p.path), 'utf8')) !== p.revision)
297
+ throw new Error(`Stale simulation source: ${p.id}`);
298
+ if ((mode === 'safe-batch' && p.attention.length) || !p.changes.length || (selectedIds && !selectedIds.includes(p.id)))
299
+ continue;
300
+ for (const entry of proposalEntries(p))
301
+ await fs.writeFile(await checkedDataPath(temp, entry.before ? p.path : `${p.path.startsWith('local/') ? 'local/' : ''}memory/${entry.after.id}.md`), renderKnowledge(entry.after), { mode: PRIVATE_FILE_MODE });
302
+ }
303
+ return await Promise.all(before.map(async (row) => ({ ...row, after: contextMetrics(await prepareContext(temp, { query: row.task })) })));
304
+ }
305
+ finally {
306
+ await fs.rm(temp, { recursive: true, force: true });
307
+ }
308
+ }
309
+ export function renderCurationText(plan, projection) {
310
+ return `Knowledge cleanup\n${renderReviewProjection(projection)}.\nNo knowledge is removed automatically.\n` + plan.proposals.map(p => {
311
+ const item = projection.items.find(i => i.proposalIds.includes(p.id));
312
+ const status = item.issue?.state === 'deferred' ? 'Deferred clarification' : item.disposition === 'keep_current' ? 'Keep current' : item.disposition === 'deferred' && item.outcome === 'proposed' ? 'Deferred change' : item.outcome === 'unresolved' ? 'Needs clarification' : item.outcome === 'proposed' ? 'Proposed change' : 'Unchanged';
313
+ return `${p.group} · ${p.before.title}\n${status}\nCurrently: ${p.before.contextPolicy} · ${applicabilityLabel(p.before)}\n${item.outcome === 'unresolved' ? 'No actionable suggestion is available yet.' : `Suggested: ${p.outputs.map(i => `${i.category}, ${i.contextPolicy}, ${applicabilityLabel(i)}`).join('; ')}`}\nWhy: ${p.evidence.join('; ')}`;
314
+ }).join('\n\n');
315
+ }
316
+ //# sourceMappingURL=knowledge-curation.js.map