@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,634 @@
1
+ import { replaceManagedBlock } from "./templates.js";
2
+ import { withApplicability, applicabilityOf, inferApplicability, buildTaskContext } from "./applicability.js";
3
+ import { withTrustedKnowledgeIntent } from './knowledge-authority.js';
4
+ import { recordRetrievalEvidence, recordSyncEvidenceLocked } from "./memory-pressure.js";
5
+ import { publicDiagnostic } from "./diagnostics.js";
6
+ import { explicitProjectRoot } from "./project-root.js";
7
+ import { reviewSessionKnowledge } from "./session-review.js";
8
+ import { assertNoSecrets } from "./secrets.js";
9
+ import { intelligenceTelemetry } from "./intelligence.js";
10
+ import { planAssistedSync, observationEvidence, observationKey } from "./knowledge-engine.js";
11
+ import { withSyncLock, recoverSyncTransaction, commitSyncTransaction, safeProjectPath, fileHash } from "./sync-transaction.js";
12
+ import crypto from "node:crypto";
13
+ import fs from "node:fs/promises";
14
+ import path from "node:path";
15
+ import { listProjectSkills, skillCounts } from "./skills.js";
16
+ import { consolidateSessions } from "./consolidation.js";
17
+ import { PRODERO_SCHEMA_VERSION, PRODERO_KNOWLEDGE_SCHEMA_VERSION, PRODERO_VERSION } from "./constants.js";
18
+ import { prepareContext, MAX_CONTEXT_RELEVANT_ITEMS } from "./context.js";
19
+ import { getBootstrapContext } from "./bootstrap.js";
20
+ import { associatePreparedContext } from "./context-snapshot.js";
21
+ import { sortableId } from "./ids.js";
22
+ import { buildIndex } from "./index.js";
23
+ import { assertEnglishProse } from "./language.js";
24
+ import { loadKnowledgeGraph } from "./knowledge-graph.js";
25
+ import { loadMemoryCatalog, memoryHealth } from "./memory-index.js";
26
+ import { forgetMemory, getMemory, searchMemory } from "./memory-operations.js";
27
+ import { migrateProject, recoverProjectMigration, requireCurrentProject } from "./migrations.js";
28
+ import { listKnowledge } from "./memory.js";
29
+ import { safeRelative } from "./plugin-files.js";
30
+ import { exactKnowledge } from "./semantic-safety.js";
31
+ import { applyKnowledgePromotion, previewKnowledgePromotion, promotionRecommendations } from "./promotion.js";
32
+ import { discoverSessions } from "./session-index.js";
33
+ import { closeSession, createSession, readSession, recordSessionObservations, updateSessionTaskContext } from "./sessions.js";
34
+ import { DEFAULT_CONTEXT_BUDGET_TOKENS, DEFAULT_REQUIRED_CONTEXT_BUDGET_TOKENS, memoryCompression, summarizeSessionMemory, TOKEN_COUNT_METHOD } from "./telemetry.js";
35
+ import { INDEX_TEMPLATE } from "./templates.js";
36
+ import { CANONICAL_ID_PATTERN, ensureDerivedStateRoot, checkedDataPath, ensureProjectDirectories, ensureRuntimeDirectories, ensureLocalStateIgnored, exists, projectDataPath, readConfig, sha256, writeFileAtomic, writeConfig, } from "./storage.js";
37
+ export { PRODERO_SCHEMA_VERSION, PRODERO_VERSION } from "./constants.js";
38
+ export { createSession, parseSession, SESSION_SECTIONS } from "./sessions.js";
39
+ export { getContext } from "./context.js";
40
+ export { emptyBootstrapContext, getBootstrapContext } from "./bootstrap.js";
41
+ export { buildIndex } from "./index.js";
42
+ export { listKnowledge, parseKnowledge } from "./memory.js";
43
+ export { searchMemory } from "./memory-operations.js";
44
+ export async function findProjectRoot(start = process.cwd()) {
45
+ // Adapter discovery is explicit: Core must never widen the selected boundary.
46
+ return explicitProjectRoot(path.resolve(start));
47
+ }
48
+ async function updateManagedAgents(root) {
49
+ const agentsPath = path.join(root, "AGENTS.md");
50
+ const existing = (await exists(agentsPath)) ? await fs.readFile(agentsPath, "utf8") : "";
51
+ const next = replaceManagedBlock(existing);
52
+ if (next !== existing)
53
+ await writeFileAtomic(agentsPath, next);
54
+ }
55
+ async function timed(phase, instrument, operation) {
56
+ const started = performance.now();
57
+ try {
58
+ return await operation();
59
+ }
60
+ finally {
61
+ instrument?.({ phase, durationMs: performance.now() - started });
62
+ }
63
+ }
64
+ export async function getFastProjectStatus(start = process.cwd(), options = {}) {
65
+ return timed("fast-status", options.instrument, async () => {
66
+ const root = await findProjectRoot(start);
67
+ const [configExists, git, agentsFile] = await Promise.all([
68
+ exists(projectDataPath(root, "config.json")),
69
+ exists(path.join(root, ".git")),
70
+ exists(path.join(root, "AGENTS.md")),
71
+ ]);
72
+ let config = null;
73
+ if (configExists) {
74
+ try {
75
+ config = await readConfig(root);
76
+ }
77
+ catch (error) {
78
+ if (error instanceof Error && error.message.startsWith("PRODERO_SCHEMA_NEWER:"))
79
+ throw error;
80
+ // Detailed hydration reports config corruption after the App shell renders.
81
+ }
82
+ }
83
+ const canonicalStructure = configExists && (await Promise.all([
84
+ "index.md", "memory", "sessions", "skills", "checkpoints",
85
+ ].map((entry) => exists(projectDataPath(root, entry))))).every(Boolean);
86
+ return {
87
+ root,
88
+ name: path.basename(root),
89
+ initialized: configExists,
90
+ git,
91
+ agentsFile,
92
+ schemaVersion: config?.schemaVersion ?? null,
93
+ canonicalStructure,
94
+ };
95
+ });
96
+ }
97
+ export async function getDetailedProjectStatus(start = process.cwd(), options = {}) {
98
+ const started = performance.now();
99
+ try {
100
+ const root = await findProjectRoot(start);
101
+ const config = await readConfig(root);
102
+ const statusReads = [
103
+ config ? (async () => {
104
+ const phaseStarted = performance.now();
105
+ const result = await discoverSessions(root, { persistCache: options.persistCache });
106
+ options.instrument?.({ phase: "session-index", durationMs: performance.now() - phaseStarted, cache: result.cacheState, filesScanned: result.sessionFilesScanned, bodiesRead: result.sessionBodiesRead });
107
+ return result;
108
+ })() : null,
109
+ config ? (async () => {
110
+ const phaseStarted = performance.now();
111
+ const result = await loadMemoryCatalog(root, { persistCache: options.persistCache });
112
+ options.instrument?.({ phase: "memory-index", durationMs: performance.now() - phaseStarted, cache: result.cacheState, filesScanned: result.filesScanned, bodiesRead: result.bodiesIndexed });
113
+ return result;
114
+ })() : null,
115
+ getFastProjectStatus(root),
116
+ listProjectSkills(root),
117
+ ];
118
+ let statusResults;
119
+ try {
120
+ statusResults = await Promise.all(statusReads);
121
+ }
122
+ catch (error) {
123
+ // Readers can finish derived-cache writes after a sibling fails (including a
124
+ // pending lifecycle guard). Do not return while those writes are still running.
125
+ await Promise.allSettled(statusReads);
126
+ throw error;
127
+ }
128
+ const [discovery, catalog, fast, skills] = statusResults;
129
+ const telemetryStarted = performance.now();
130
+ const knowledge = catalog?.summaries ?? [];
131
+ const graph = config && catalog ? await loadKnowledgeGraph(root, catalog, { persistCache: options.persistCache }) : null;
132
+ const disputedIds = new Set(knowledge.filter((item) => item.status === "disputed").map((item) => item.id));
133
+ const conflictPairs = new Set();
134
+ for (const item of knowledge.filter((candidate) => candidate.status === "disputed")) {
135
+ for (const related of item.related.filter((id) => disputedIds.has(id)))
136
+ conflictPairs.add([item.id, related].sort().join(":"));
137
+ if (!item.related.some((id) => disputedIds.has(id)))
138
+ conflictPairs.add(item.id);
139
+ }
140
+ const counts = skillCounts(skills);
141
+ const recommendations = config ? await promotionRecommendations(root, { persistCache: options.persistCache }) : [];
142
+ const status = {
143
+ ...fast,
144
+ knowledgeCount: knowledge.filter((item) => item.status === "active" || item.status === "disputed").length,
145
+ localKnowledgeCount: knowledge.filter((item) => item.layer === "local" && (item.status === "active" || item.status === "disputed")).length,
146
+ versionedKnowledgeCount: knowledge.filter((item) => item.layer === "versioned" && (item.status === "active" || item.status === "disputed")).length,
147
+ promotionRecommendedCount: recommendations.filter((item) => item.recommended).length,
148
+ sessionCount: discovery?.sessions.length ?? 0,
149
+ activeSessionCount: discovery?.sessions.filter((session) => session.status !== "closed").length ?? 0,
150
+ pendingSessionCount: discovery?.sessions.filter((session) => session.pending).length ?? 0,
151
+ skillCount: counts.active,
152
+ skillCandidateCount: counts.candidate,
153
+ skillDismissedCount: counts.dismissed,
154
+ checkpointCount: discovery?.checkpointCount ?? 0,
155
+ conflictCount: conflictPairs.size,
156
+ lastSyncAt: discovery?.lastSyncAt ?? null,
157
+ memoryHealth: memoryHealth(knowledge),
158
+ knowledgeGraph: graph?.diagnostics ?? { cacheState: "missing", nodeCount: 0, edgeCount: 0, brokenReferences: 0, conflictEdges: 0 },
159
+ };
160
+ options.instrument?.({ phase: "telemetry", durationMs: performance.now() - telemetryStarted });
161
+ return status;
162
+ }
163
+ finally {
164
+ options.instrument?.({ phase: "detailed-status", durationMs: performance.now() - started });
165
+ }
166
+ }
167
+ /** Complete status remains the compatibility API for correctness-oriented callers. */
168
+ export async function getProjectStatus(start = process.cwd(), options = {}) {
169
+ return getDetailedProjectStatus(start, options);
170
+ }
171
+ export async function initializeProject(start = process.cwd()) {
172
+ const root = await findProjectRoot(start);
173
+ await recoverProjectMigration(root);
174
+ const existing = await readConfig(root);
175
+ await ensureProjectDirectories(root);
176
+ if (existing)
177
+ await migrateProject(root, existing);
178
+ const config = existing ?? {
179
+ schemaVersion: PRODERO_SCHEMA_VERSION,
180
+ knowledgeSchemaVersion: PRODERO_KNOWLEDGE_SCHEMA_VERSION,
181
+ initializedWith: PRODERO_VERSION,
182
+ lastUpdatedWith: PRODERO_VERSION,
183
+ projectId: crypto.randomUUID(),
184
+ createdAt: new Date().toISOString(),
185
+ context: { max_tokens: DEFAULT_CONTEXT_BUDGET_TOKENS, max_items: MAX_CONTEXT_RELEVANT_ITEMS, required_max_tokens: DEFAULT_REQUIRED_CONTEXT_BUDGET_TOKENS },
186
+ };
187
+ await writeConfig(root, config);
188
+ const indexPath = await checkedDataPath(root, "index.md");
189
+ if (!(await exists(indexPath)))
190
+ await fs.writeFile(indexPath, INDEX_TEMPLATE);
191
+ await updateManagedAgents(root);
192
+ await ensureLocalStateIgnored(root);
193
+ await buildIndex(root);
194
+ return getProjectStatus(root);
195
+ }
196
+ export async function autoMigrateProject(start = process.cwd()) {
197
+ const root = await findProjectRoot(start);
198
+ await recoverProjectMigration(root);
199
+ const config = await readConfig(root);
200
+ if (!config)
201
+ return { migrated: false, from: null, to: PRODERO_SCHEMA_VERSION, backup: null };
202
+ await ensureRuntimeDirectories(root);
203
+ const result = await migrateProject(root, config);
204
+ if (result.migrated) {
205
+ await updateManagedAgents(root);
206
+ await ensureLocalStateIgnored(root);
207
+ await buildIndex(root);
208
+ }
209
+ return result;
210
+ }
211
+ const INTEGRATION_FILES = ["AGENTS.md", ".gitignore", ".prodero/config.json", ".prodero/index.md"];
212
+ async function integrationRevision(root, relative) {
213
+ const target = path.join(root, relative);
214
+ return (await exists(target)) ? sha256(await fs.readFile(target, "utf8")) : null;
215
+ }
216
+ /** Explicitly reconcile runtime-owned project integration without rewriting knowledge. */
217
+ export async function refreshProjectIntegration(start = process.cwd()) {
218
+ const root = await findProjectRoot(start);
219
+ const config = await readConfig(root);
220
+ if (!config)
221
+ throw new Error("Prodero is not initialized for this project.");
222
+ const before = new Map(await Promise.all(INTEGRATION_FILES.map(async (relative) => [relative, await integrationRevision(root, relative)])));
223
+ const migration = await migrateProject(root, config);
224
+ await updateManagedAgents(root);
225
+ await ensureLocalStateIgnored(root);
226
+ await buildIndex(root);
227
+ const changedFiles = [];
228
+ for (const relative of INTEGRATION_FILES)
229
+ if (before.get(relative) !== await integrationRevision(root, relative))
230
+ changedFiles.push(relative);
231
+ return { root, migration, changedFiles };
232
+ }
233
+ const PENDING_INTEGRATION_REFRESH = ".cache/runtime-maintenance.json";
234
+ export async function scheduleProjectIntegrationRefresh(start, target) {
235
+ const root = await findProjectRoot(start);
236
+ if (!(await readConfig(root)))
237
+ return;
238
+ await ensureDerivedStateRoot(root);
239
+ await writeFileAtomic(projectDataPath(root, PENDING_INTEGRATION_REFRESH), `${JSON.stringify({ formatVersion: 1, target, requestedAt: new Date().toISOString() }, null, 2)}\n`);
240
+ }
241
+ export async function applyPendingProjectIntegrationRefresh(start = process.cwd()) {
242
+ const root = await findProjectRoot(start), marker = projectDataPath(root, PENDING_INTEGRATION_REFRESH);
243
+ if (!(await exists(marker)))
244
+ return null;
245
+ const raw = JSON.parse(await fs.readFile(marker, "utf8"));
246
+ if (raw.formatVersion !== 1)
247
+ throw new Error("Invalid pending Prodero project-maintenance marker.");
248
+ const result = await refreshProjectIntegration(root);
249
+ await fs.rm(marker, { force: true });
250
+ return result;
251
+ }
252
+ export async function syncProject(start = process.cwd(), options = {}) {
253
+ const root = await findProjectRoot(start);
254
+ if (!(await readConfig(root)))
255
+ throw new Error("Prodero is not initialized for this project.");
256
+ await requireCurrentProject(root);
257
+ await autoMigrateProject(root);
258
+ return withSyncLock(root, async () => { await recoverSyncTransaction(root); return syncProjectLocked(root, options); });
259
+ }
260
+ async function syncProjectLocked(start, options) {
261
+ const root = await findProjectRoot(start);
262
+ if (!(await readConfig(root)))
263
+ throw new Error("Prodero is not initialized for this project.");
264
+ const discovery = await discoverSessions(root, { loadPendingBodies: true, onlyClosed: options.onlyClosed });
265
+ const states = discovery.sessions.filter((state) => state.pending && (!options.onlyClosed || state.status === "closed") && (!options.sessionId || state.id === options.sessionId) && (state.id !== options.excludeSessionId || state.status === "closed"));
266
+ const config = (await readConfig(root));
267
+ const mode = config.knowledgeEngine?.mode ?? "deterministic";
268
+ if (mode !== "deterministic" && mode !== "assisted")
269
+ throw new Error("Invalid knowledgeEngine.mode");
270
+ const provider = mode === "assisted" ? options.provider : undefined;
271
+ const intelligence = intelligenceTelemetry(provider, mode === "assisted" && !provider ? "Intelligence provider unavailable; conservative deterministic fallback" : undefined);
272
+ const emptyDiagnostics = {
273
+ sessionFilesScanned: discovery.sessionFilesScanned,
274
+ sessionBodiesRead: discovery.sessionBodiesRead,
275
+ sessionBodiesConsolidated: 0,
276
+ checkpointFilesRead: discovery.checkpointFilesRead,
277
+ memoryFilesScanned: 0,
278
+ memoryBodiesIndexed: 0,
279
+ memoryBodiesLoaded: 0,
280
+ relatedCandidatesSelectedMax: 0,
281
+ relatedCandidateLimit: 24,
282
+ compactedSegments: 0,
283
+ conservativeMergesSkipped: 0,
284
+ };
285
+ if (!states.length) {
286
+ const catalog = await loadMemoryCatalog(root);
287
+ emptyDiagnostics.memoryFilesScanned = catalog.filesScanned;
288
+ emptyDiagnostics.memoryBodiesIndexed = catalog.bodiesIndexed;
289
+ await buildIndex(root);
290
+ return {
291
+ checkpoint: null, intelligence, consolidation: provider ? "assisted-v1" : "deterministic-v1", sessions: 0, candidates: 0, created: 0, consolidated: 0, duplicates: 0, superseded: 0, conflicts: 0, diagnostics: emptyDiagnostics,
292
+ sessionsProcessed: 0, observationsProcessed: 0, knowledgeCreated: 0, skillCandidatesCreated: 0, skillCandidateTokensAdded: 0, knowledgeConsolidated: 0, knowledgeSuperseded: 0,
293
+ rawSessionTokens: 0, rawKnowledgeTokens: 0, rawSkillCandidateTokens: 0, canonicalTokensAdded: 0, compressionRatio: null, memoryCompression: memoryCompression(0, 0), tokenCountMethod: TOKEN_COUNT_METHOD, estimated: true,
294
+ };
295
+ }
296
+ const now = new Date();
297
+ const sessions = states.map((state) => state.session);
298
+ const plan = provider ? await planAssistedSync(root, sessions, provider, now, discovery.processedObservations) : null;
299
+ // Recheck snapshots after reasoning: new observations must never be checkpointed accidentally.
300
+ for (const state of states)
301
+ if (await fileHash(await safeProjectPath(root, `sessions/${state.path}`)) !== state.sha256)
302
+ throw new Error("Session changed during Sync; retry with the new evidence");
303
+ const processedSources = new Set(observationEvidence(sessions).filter((entry) => discovery.processedObservations.has(observationKey(entry))).map((entry) => entry.id));
304
+ const consolidation = plan?.result ?? await consolidateSessions(root, sessions, now, undefined, processedSources);
305
+ const id = sortableId("chk", now);
306
+ const summary = {
307
+ sessions: states.length,
308
+ candidates: consolidation.candidates,
309
+ created: consolidation.created.length,
310
+ consolidated: consolidation.consolidated.length,
311
+ duplicates: consolidation.duplicates.length,
312
+ superseded: consolidation.superseded.length,
313
+ conflicts: consolidation.conflictCount,
314
+ };
315
+ const checkpoint = {
316
+ id,
317
+ createdAt: now.toISOString(),
318
+ previousCheckpoint: discovery.latestCheckpointId,
319
+ sessions: states.map((state) => ({ id: state.id, path: state.path, sha256: state.sha256 })),
320
+ consolidation: plan ? "assisted-v1" : "deterministic-v1",
321
+ intelligence: plan?.intelligence ?? intelligence,
322
+ reasoning: plan?.reasoning,
323
+ instructionCandidates: plan?.instructionCandidates ?? consolidation.instructionCandidates,
324
+ knowledgeBefore: plan?.knowledgeBefore,
325
+ observationHashes: plan?.observationHashes ?? observationEvidence(sessions).map(observationKey),
326
+ createdMemory: consolidation.created,
327
+ createdSkills: consolidation.createdSkills,
328
+ updatedMemory: consolidation.updated,
329
+ supersededMemory: consolidation.superseded,
330
+ duplicateMemory: consolidation.duplicates,
331
+ conflicts: consolidation.conflicts,
332
+ summary,
333
+ };
334
+ const checkpointText = `${JSON.stringify(checkpoint, null, 2)}\n`;
335
+ // Recheck after deterministic planning too, before the durable journal is created.
336
+ for (const state of states)
337
+ if (await fileHash(await safeProjectPath(root, `sessions/${state.path}`)) !== state.sha256)
338
+ throw new Error("Session changed during Sync; retry with the new evidence");
339
+ const policyTransitions = await commitSyncTransaction(root, [...(plan?.writes ?? consolidation.writes ?? []), { path: `checkpoints/${id}.json`, before: null, after: checkpointText }]);
340
+ consolidation.consolidated = consolidation.consolidated.filter(id => !policyTransitions.preservedIds.includes(id));
341
+ summary.consolidated = consolidation.consolidated.length;
342
+ if (policyTransitions.createdIds) {
343
+ consolidation.created = policyTransitions.createdIds;
344
+ summary.created = consolidation.created.length;
345
+ }
346
+ if (policyTransitions.supersededIds) {
347
+ consolidation.superseded = policyTransitions.supersededIds;
348
+ summary.superseded = consolidation.superseded.length;
349
+ }
350
+ // Observational sidecar failure must never change the committed Sync outcome.
351
+ if (consolidation.memoryPressure)
352
+ await recordSyncEvidenceLocked(root, {
353
+ ...consolidation.memoryPressure, checkpoint: id,
354
+ sessionIds: states.slice(0, 128).map(state => state.id), sessionIdsDropped: Math.max(0, states.length - 128),
355
+ });
356
+ const refreshed = await discoverSessions(root);
357
+ await buildIndex(root);
358
+ const compression = memoryCompression(consolidation.rawKnowledgeTokens, consolidation.canonicalTokensAdded);
359
+ return {
360
+ checkpoint: id,
361
+ policyTransitions,
362
+ consolidation: plan ? "assisted-v1" : "deterministic-v1",
363
+ intelligence: plan?.intelligence ?? intelligence,
364
+ ...summary,
365
+ sessionsProcessed: states.length,
366
+ observationsProcessed: consolidation.observationsProcessed,
367
+ knowledgeCreated: consolidation.created.length,
368
+ skillCandidatesCreated: consolidation.createdSkills.length,
369
+ skillCandidateTokensAdded: consolidation.skillCandidateTokensAdded,
370
+ knowledgeConsolidated: consolidation.consolidated.length,
371
+ knowledgeSuperseded: consolidation.superseded.length,
372
+ rawSessionTokens: consolidation.rawSessionTokens,
373
+ rawKnowledgeTokens: consolidation.rawKnowledgeTokens,
374
+ rawSkillCandidateTokens: consolidation.rawSkillCandidateTokens,
375
+ canonicalTokensAdded: consolidation.canonicalTokensAdded,
376
+ compressionRatio: compression.kind === "compression" ? compression.ratio : null,
377
+ memoryCompression: compression,
378
+ tokenCountMethod: TOKEN_COUNT_METHOD,
379
+ estimated: true,
380
+ diagnostics: {
381
+ sessionFilesScanned: discovery.sessionFilesScanned,
382
+ sessionBodiesRead: discovery.sessionBodiesRead,
383
+ sessionBodiesConsolidated: states.length,
384
+ checkpointFilesRead: discovery.checkpointFilesRead + refreshed.checkpointFilesRead,
385
+ memoryFilesScanned: consolidation.memoryFilesScanned,
386
+ memoryBodiesIndexed: consolidation.memoryBodiesIndexed,
387
+ memoryBodiesLoaded: consolidation.memoryBodiesLoaded,
388
+ relatedCandidatesSelectedMax: consolidation.relatedCandidatesSelectedMax,
389
+ relatedCandidateLimit: 24,
390
+ compactedSegments: consolidation.compactedSegments,
391
+ conservativeMergesSkipped: consolidation.conservativeMergesSkipped,
392
+ },
393
+ };
394
+ }
395
+ export async function resolveProjectContext(start = process.cwd(), query) {
396
+ const root = await findProjectRoot(start);
397
+ return prepareContext(root, { query });
398
+ }
399
+ /** Lightweight recall never initializes, migrates, syncs, or opens a session. */
400
+ export async function recallProject(start, query, purpose = "explain") {
401
+ if (!query.trim())
402
+ throw new Error("Recall query must not be empty.");
403
+ const root = await findProjectRoot(start);
404
+ return prepareContext(root, { query, purpose });
405
+ }
406
+ export async function resolveProjectBootstrapContext(start = process.cwd(), scope) {
407
+ const root = await findProjectRoot(start);
408
+ if (!(await readConfig(root)))
409
+ throw new Error("Prodero is not initialized for this project.");
410
+ return getBootstrapContext(root, scope);
411
+ }
412
+ export async function prepareProject(start = process.cwd(), options = {}) {
413
+ assertNoSecrets([options.task, options.agent]);
414
+ const root = await findProjectRoot(start);
415
+ await requireCurrentProject(root);
416
+ await recoverProjectMigration(root);
417
+ if (!(await readConfig(root)))
418
+ await initializeProject(root);
419
+ await requireCurrentProject(root);
420
+ await autoMigrateProject(root);
421
+ // Recover durable snapshots of interrupted sessions without closing live sessions.
422
+ // A session explicitly resumed by this prepare remains untouched.
423
+ const automaticSync = await syncProject(root, { excludeSessionId: options.sessionId, provider: options.provider });
424
+ const discovery = await discoverSessions(root);
425
+ let retrievalEvidence;
426
+ const context = await prepareContext(root, {
427
+ query: options.task,
428
+ onRetrievalEvidence: evidence => { retrievalEvidence = evidence; },
429
+ purpose: options.purpose ?? "work",
430
+ taskContext: options.taskContext,
431
+ pendingSessions: discovery.sessions.filter((candidate) => candidate.pending).length,
432
+ onRequiredOmitted: options.onRequiredOmitted,
433
+ });
434
+ context.agentUsage = options.agentUsage ?? null;
435
+ if (automaticSync.intelligence?.audit.status === "attention" && !context.attention.some((item) => item.kind === "conflict")) {
436
+ context.attention.push({ kind: "memory-health", severity: "important", title: "Knowledge review needed", detail: "The affected-area audit left uncertainty or reached its review budget. Original evidence is preserved." });
437
+ }
438
+ let session = options.sessionId ? await readSession(root, options.sessionId) : undefined;
439
+ if (session?.status === "closed")
440
+ session = undefined;
441
+ if (!session)
442
+ session = await createSession(root, { agent: options.agent, taskContext: context.taskContext });
443
+ else if (context.taskContext)
444
+ session = await updateSessionTaskContext(root, session.id, context.taskContext);
445
+ associatePreparedContext(context, session.id);
446
+ const memoryTelemetry = retrievalEvidence ? await recordRetrievalEvidence(root, session.id, retrievalEvidence) : "unavailable";
447
+ const status = await getProjectStatus(root);
448
+ const uiState = { blocking: context.policyReadiness?.blocking ?? false, view: context.attention.length ? "attention" : "context-ready", attention: context.attention };
449
+ return { memoryTelemetry, project: status, session: { id: session.id, path: `.prodero/sessions/${session.path}`, status: session.status }, automaticSync, context, proderoMetrics: context.metrics, agentUsage: context.agentUsage, uiState };
450
+ }
451
+ export async function recordProjectSession(start, sessionId, entries) {
452
+ const root = await findProjectRoot(start);
453
+ if (!(await readConfig(root)))
454
+ throw new Error("Prodero is not initialized for this project.");
455
+ await requireCurrentProject(root);
456
+ await autoMigrateProject(root);
457
+ return recordSessionObservations(root, sessionId, entries);
458
+ }
459
+ export async function closeProjectSession(start, sessionId, finalEntries = [], sync = true, _provider) {
460
+ const root = await findProjectRoot(start);
461
+ if (!(await readConfig(root)))
462
+ throw new Error("Prodero is not initialized for this project.");
463
+ await requireCurrentProject(root);
464
+ await autoMigrateProject(root);
465
+ const closed = await closeSession(root, sessionId, finalEntries);
466
+ // Persist the immutable observation batch before Sync. Closed means recording ended;
467
+ // only the checkpoint certifies synchronization. Errors propagate, leaving recovery evidence.
468
+ const synchronization = sync ? await syncProject(root, { onlyClosed: true, sessionId }) : null;
469
+ const learned = {
470
+ ...summarizeSessionMemory(closed.session),
471
+ syncState: synchronization ? "synchronized" : "pending-auto-sync",
472
+ synchronization,
473
+ };
474
+ // Review is optional: an unavailable catalogue must not undo successful close.
475
+ let review;
476
+ try {
477
+ const inspected = await reviewSessionKnowledge(root, sessionId);
478
+ if (inspected.consolidated)
479
+ learned.syncState = "synchronized";
480
+ review = { root, sessionId, ...inspected.summary, consolidated: inspected.consolidated };
481
+ }
482
+ catch (error) {
483
+ review = { root, sessionId, total: learned.observations, needsReview: 1, automatic: 0, consolidated: Boolean(synchronization), error: publicDiagnostic(error) };
484
+ }
485
+ return { ...closed, synchronization, learned, review, proderoMetrics: { tokenCountMethod: TOKEN_COUNT_METHOD, estimated: true, rawSessionTokens: learned.rawSessionTokens }, agentUsage: null, uiState: { view: "session-learned" } };
486
+ }
487
+ const KIND_CATEGORY = { rule: "instruction", decision: "decision", fact: "state", lesson: "lesson" };
488
+ const KIND_OBSERVATION = { rule: "decision", decision: "decision", fact: "state_change", lesson: "lesson" };
489
+ export async function rememberProjectKnowledge(start, input, provider) {
490
+ assertNoSecrets(input);
491
+ const root = await findProjectRoot(start);
492
+ if (!(await readConfig(root)))
493
+ await initializeProject(root);
494
+ await requireCurrentProject(root);
495
+ await autoMigrateProject(root);
496
+ const content = input.content?.trim();
497
+ if (!content)
498
+ throw new Error("Remember requires non-empty durable content.");
499
+ if (content.length > 10_000)
500
+ throw new Error("Remember content may not exceed 10,000 characters.");
501
+ if (input.title && input.title.trim().length > 200)
502
+ throw new Error("Remember title may not exceed 200 characters.");
503
+ assertEnglishProse([input.title, content], "Remembered Prodero knowledge");
504
+ const beforeKnowledge = await listKnowledge(root);
505
+ const replaced = input.replaceId ? await getMemory(root, input.replaceId) : undefined;
506
+ if (replaced && replaced.status !== "active" && replaced.status !== "disputed")
507
+ throw new Error("Only active or disputed knowledge can be updated.");
508
+ const relationships = [...new Map((input.relationships ?? replaced?.relations ?? []).map((relation) => [`${relation.type}:${relation.target}`, relation])).values()]
509
+ .sort((a, b) => a.type.localeCompare(b.type) || a.target.localeCompare(b.target));
510
+ if (relationships.length > 12)
511
+ throw new Error("Remember supports at most 12 explicit relationships.");
512
+ for (const relation of relationships) {
513
+ if (!["related_to", "depends_on", "implements"].includes(relation.type) || !CANONICAL_ID_PATTERN.test(relation.target))
514
+ throw new Error("Remember relationship is malformed.");
515
+ const target = beforeKnowledge.find((item) => item.id === relation.target);
516
+ if (!target || (target.status !== "active" && target.status !== "disputed"))
517
+ throw new Error(`Remember relationship target is not current canonical knowledge: ${relation.target}`);
518
+ }
519
+ const parentContext = input.sessionId ? (await readSession(root, input.sessionId)).taskContext : undefined;
520
+ const applicability = input.applicability ?? (replaced ? applicabilityOf(replaced) : input.scope === 'path' ? { kind: 'scoped', paths: [safeRelative(input.path ?? '')] } : input.scope ? { kind: 'project' } : inferApplicability(content, input.taskContext ? buildTaskContext('', input.taskContext) : parentContext));
521
+ const projected = withApplicability({ scope: replaced?.scope ?? 'global', scopeRef: replaced?.scopeRef ?? null, scopeKind: replaced?.scopeKind }, applicability);
522
+ const scope = projected.scope, scopeRef = projected.scopeRef ?? undefined;
523
+ const kind = input.kind ?? (replaced?.category === "instruction" ? "rule" : replaced?.category === "state" ? "fact" : replaced?.category === "lesson" ? "lesson" : "decision");
524
+ const priority = input.importance ?? replaced?.priority ?? 'normal';
525
+ const session = await createSession(root, { agent: "conversational-memory", parentSessionId: input.sessionId });
526
+ const recorded = await recordSessionObservations(root, session.id, [{
527
+ type: KIND_OBSERVATION[kind],
528
+ content,
529
+ title: input.title?.trim() || replaced?.title,
530
+ scopeHint: scope,
531
+ scopeRef,
532
+ applicability,
533
+ ...(projected.scopeKind ? { scopeKind: projected.scopeKind } : {}),
534
+ categoryHint: KIND_CATEGORY[kind],
535
+ importanceHint: priority,
536
+ supersedes: replaced ? [replaced.id] : undefined,
537
+ relations: relationships,
538
+ }]);
539
+ await closeSession(root, session.id);
540
+ const synchronization = await syncProject(root, { onlyClosed: true, provider, sessionId: session.id });
541
+ const provenance = `${session.id}#${recorded.recorded[0].id}`;
542
+ const item = (await listKnowledge(root)).find((candidate) => candidate.derivedFrom.includes(provenance))
543
+ ?? beforeKnowledge.find((candidate) => candidate.scope === scope && candidate.scopeRef === (scopeRef ?? null) && candidate.category === KIND_CATEGORY[kind] && exactKnowledge(candidate.content) === exactKnowledge(content));
544
+ if (!item)
545
+ throw new Error("Remember synchronized but did not resolve a canonical knowledge item.");
546
+ const id = item.id;
547
+ const previous = beforeKnowledge.find((candidate) => candidate.id === id);
548
+ const sameRelations = JSON.stringify(previous?.relations ?? []) === JSON.stringify(item.relations ?? []);
549
+ const replacementApplied = !!replaced && item.supersedes.includes(replaced.id);
550
+ const outcome = replacementApplied ? "updated" : !previous ? "created" : previous.content === item.content && sameRelations ? "duplicate" : "merged";
551
+ const layer = (await loadMemoryCatalog(root)).summaries.find((candidate) => candidate.id === item.id)?.layer ?? "local";
552
+ return {
553
+ item,
554
+ layer,
555
+ outcome,
556
+ replacedId: replacementApplied ? replaced.id : null,
557
+ preservedId: replaced && !replacementApplied ? replaced.id : null,
558
+ sessionId: session.id,
559
+ parentSessionId: session.parentSessionId ?? null,
560
+ observationId: recorded.recorded[0].id,
561
+ checkpoint: synchronization.checkpoint,
562
+ synchronization,
563
+ };
564
+ }
565
+ /** This validates binding, not authenticity. Only the embedding host can attest intent. */
566
+ export function matchesCurrentUserRule(input, intent) {
567
+ return intent.mandatory === true && !!intent.turnId?.trim() && !!intent.userText?.trim() && intent.content === input.content && (input.scope === undefined || input.scope === intent.scope) && input.path === intent.path && (input.importance === undefined || input.importance === intent.importance) && (input.applicability === undefined || JSON.stringify(input.applicability) === JSON.stringify(intent.applicability));
568
+ }
569
+ /** Trusted host adapter only. Tool arguments never call this path on their own. */
570
+ export async function rememberCurrentUserRule(root, input, intent) {
571
+ if (!matchesCurrentUserRule(input, intent))
572
+ throw new Error('Current user intent does not match the requested knowledge');
573
+ return rememberAuthorizedRule(root, input, intent, `current-user-turn:${intent.turnId}: ${intent.userText}`);
574
+ }
575
+ /** Explicit chat command, authorized by the calling assistant from the user's request.
576
+ * This is model-mediated provenance, not a claim of authenticated host attestation. */
577
+ export async function rememberChatRule(root, input, userInstruction) {
578
+ if (input.kind !== 'rule' || !userInstruction.trim())
579
+ throw new Error('A Required chat rule needs the explicit user request.');
580
+ assertNoSecrets(userInstruction);
581
+ const scope = input.scope ?? 'project';
582
+ if (scope === 'path' && !input.path?.trim())
583
+ throw new Error('Path-scoped Required rules need a path.');
584
+ return rememberAuthorizedRule(root, input, { content: input.content, scope, path: input.path, importance: input.importance, applicability: input.applicability }, `chat-user-request: ${userInstruction.trim()}`);
585
+ }
586
+ async function rememberAuthorizedRule(root, input, intent, evidence) {
587
+ const scope = intent.scope === 'path' ? 'module' : 'global';
588
+ const priority = intent.importance ?? 'normal';
589
+ const applicability = intent.applicability ?? (intent.scope === 'path' ? { kind: 'scoped', paths: [intent.path] } : { kind: 'project' });
590
+ const proposed = withApplicability({ id: 'mem_intent', version: 3, category: 'instruction', contextPolicy: 'required', authority: 'explicit_user', scope, scopeRef: intent.path ?? null, ...(intent.scope === 'path' ? { scopeKind: 'path' } : {}), priority, status: 'active', title: input.title ?? '', content: intent.content, createdAt: '', updatedAt: '', sourceSessions: [], derivedFrom: [], supersedes: [], related: [] }, applicability);
591
+ return withTrustedKnowledgeIntent(root, proposed, { authority: 'explicit_user', evidence }, () => rememberProjectKnowledge(root, { ...input, scope: intent.scope, path: intent.path, importance: intent.importance, applicability, kind: 'rule' }));
592
+ }
593
+ export async function searchProjectMemory(start, query, options = {}) {
594
+ const root = await findProjectRoot(start);
595
+ if (!(await readConfig(root)))
596
+ throw new Error("Prodero is not initialized for this project.");
597
+ await requireCurrentProject(root);
598
+ await autoMigrateProject(root);
599
+ const normalizedQuery = query.trim();
600
+ if (!normalizedQuery)
601
+ throw new Error("Memory search requires a non-empty query.");
602
+ const normalizedOptions = { ...options };
603
+ if (options.scope === "path") {
604
+ if (!options.path?.trim())
605
+ throw new Error("Path-scoped memory search requires a repository-relative path.");
606
+ normalizedOptions.path = safeRelative(options.path.trim().replace(/^\.\//, "").replace(/\/$/, ""));
607
+ }
608
+ return { query: normalizedQuery, matches: await searchMemory(root, normalizedQuery, normalizedOptions) };
609
+ }
610
+ export async function forgetProjectKnowledge(start, input) {
611
+ const root = await findProjectRoot(start);
612
+ if (!(await readConfig(root)))
613
+ throw new Error("Prodero is not initialized for this project.");
614
+ await requireCurrentProject(root);
615
+ await autoMigrateProject(root);
616
+ return forgetMemory(root, input);
617
+ }
618
+ export async function previewProjectKnowledgePromotion(start, options = {}) {
619
+ const root = await findProjectRoot(start);
620
+ if (!(await readConfig(root)))
621
+ throw new Error("Prodero is not initialized for this project.");
622
+ await requireCurrentProject(root);
623
+ await autoMigrateProject(root);
624
+ return previewKnowledgePromotion(root, options);
625
+ }
626
+ export async function promoteProjectKnowledge(start, preview, approved) {
627
+ const root = await findProjectRoot(start);
628
+ if (!(await readConfig(root)))
629
+ throw new Error("Prodero is not initialized for this project.");
630
+ await requireCurrentProject(root);
631
+ await autoMigrateProject(root);
632
+ return applyKnowledgePromotion(root, preview, approved);
633
+ }
634
+ //# sourceMappingURL=project.js.map