@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.
- package/ARCHITECTURE.md +298 -0
- package/CHANGELOG.md +33 -0
- package/CONTRIBUTING.md +9 -0
- package/LICENSE +21 -0
- package/README.md +93 -2
- package/SECURITY.md +7 -0
- package/bin/prodero +7 -0
- package/dist/cli.js +266 -0
- package/dist/context-instructions.js +2 -0
- package/dist/contracts.js +51 -0
- package/dist/conversation-presentation.js +297 -0
- package/dist/core/agent-instructions.js +52 -0
- package/dist/core/applicability-label.js +11 -0
- package/dist/core/applicability.js +143 -0
- package/dist/core/bootstrap.js +129 -0
- package/dist/core/consolidation.js +315 -0
- package/dist/core/constants.js +21 -0
- package/dist/core/context-allocation.js +54 -0
- package/dist/core/context-snapshot.js +12 -0
- package/dist/core/context.js +489 -0
- package/dist/core/curation-clauses.js +68 -0
- package/dist/core/curation-evidence.js +98 -0
- package/dist/core/curation-graphical.js +117 -0
- package/dist/core/curation-question.js +28 -0
- package/dist/core/curation-review-presentation.js +4 -0
- package/dist/core/curation-review-state.js +62 -0
- package/dist/core/curation-review.js +573 -0
- package/dist/core/curation-revision.js +106 -0
- package/dist/core/curation-semantic-contract.js +242 -0
- package/dist/core/curation-semantic-jobs.js +340 -0
- package/dist/core/diagnostics.js +17 -0
- package/dist/core/format.js +114 -0
- package/dist/core/ids.js +6 -0
- package/dist/core/imports.js +169 -0
- package/dist/core/index.js +93 -0
- package/dist/core/instruction-review.js +86 -0
- package/dist/core/integrity-files.js +174 -0
- package/dist/core/intelligence.js +95 -0
- package/dist/core/knowledge-analysis.js +46 -0
- package/dist/core/knowledge-authority.js +143 -0
- package/dist/core/knowledge-curation.js +316 -0
- package/dist/core/knowledge-engine.js +475 -0
- package/dist/core/knowledge-graph.js +192 -0
- package/dist/core/knowledge-lifecycle.js +212 -0
- package/dist/core/knowledge-management.js +113 -0
- package/dist/core/knowledge-migration.js +84 -0
- package/dist/core/knowledge-policy.js +38 -0
- package/dist/core/knowledge-provenance.js +90 -0
- package/dist/core/knowledge-write-policy.js +85 -0
- package/dist/core/language.js +14 -0
- package/dist/core/local-preferences.js +76 -0
- package/dist/core/matching.js +6 -0
- package/dist/core/memory-index.js +241 -0
- package/dist/core/memory-intelligence.js +288 -0
- package/dist/core/memory-link-repair.js +70 -0
- package/dist/core/memory-operations.js +122 -0
- package/dist/core/memory-pressure.js +212 -0
- package/dist/core/memory-sync-curator.js +82 -0
- package/dist/core/memory-sync.js +558 -0
- package/dist/core/memory-transformation-bundle.js +163 -0
- package/dist/core/memory-transformation-proposal.js +135 -0
- package/dist/core/memory-transformation.js +108 -0
- package/dist/core/memory.js +186 -0
- package/dist/core/migrations.js +569 -0
- package/dist/core/npm-update.js +289 -0
- package/dist/core/path-scope.js +27 -0
- package/dist/core/plugin-files.js +116 -0
- package/dist/core/project-root.js +39 -0
- package/dist/core/project.js +634 -0
- package/dist/core/promotion.js +279 -0
- package/dist/core/reconciliation.js +61 -0
- package/dist/core/secrets.js +67 -0
- package/dist/core/semantic-safety.js +33 -0
- package/dist/core/session-index.js +116 -0
- package/dist/core/session-review.js +150 -0
- package/dist/core/sessions.js +289 -0
- package/dist/core/settings.js +94 -0
- package/dist/core/skill-curation.js +239 -0
- package/dist/core/skill-polish.js +41 -0
- package/dist/core/skills.js +387 -0
- package/dist/core/storage.js +462 -0
- package/dist/core/sync-transaction.js +290 -0
- package/dist/core/telemetry.js +64 -0
- package/dist/core/templates.js +53 -0
- package/dist/core/types.js +2 -0
- package/dist/core/update.js +326 -0
- package/dist/curation-review-adapter.js +92 -0
- package/dist/diagnostic-boundary.js +55 -0
- package/dist/filesystem/permissions.js +39 -0
- package/dist/hosts/adapters.js +119 -0
- package/dist/hosts/files.js +133 -0
- package/dist/hosts/manager.js +150 -0
- package/dist/hosts/public-entry.js +47 -0
- package/dist/hosts/runtime-command.js +66 -0
- package/dist/hosts/runtime.js +139 -0
- package/dist/hosts/wizard.js +114 -0
- package/dist/knowledge-table.js +38 -0
- package/dist/mcp-development.js +69 -0
- package/dist/mcp-workspace.js +141 -0
- package/dist/memory-presentation.js +182 -0
- package/dist/memory-sync-operation.js +88 -0
- package/dist/plugins/api.js +2 -0
- package/dist/plugins/context.js +20 -0
- package/dist/plugins/mcp.js +72 -0
- package/dist/plugins/registry.js +106 -0
- package/dist/presentation-cards.js +32 -0
- package/dist/presentation-server.js +94 -0
- package/dist/presentation-trace.js +174 -0
- package/dist/presentation.js +443 -0
- package/dist/providers/mcp-sampling.js +45 -0
- package/dist/server.js +664 -0
- package/dist/settings-definition.js +22 -0
- package/dist/skill-instructions.js +2 -0
- package/dist/status-presentation.js +20 -0
- package/dist/text-layout.js +278 -0
- package/dist/update-command.js +53 -0
- package/dist/update-instructions.js +4 -0
- package/dist-ui/index.html +216 -0
- package/docs/APPLICABILITY.md +151 -0
- package/docs/COMPATIBILITY.md +68 -0
- package/docs/CONTEXT-SELECTION-V2.md +61 -0
- package/docs/CURATION-APP-REVIEW.md +156 -0
- package/docs/CURATION-CLAUSE-REFINEMENT.md +45 -0
- package/docs/IMPORTER-CURATION.md +142 -0
- package/docs/KNOWLEDGE-AUTHORITY.md +165 -0
- package/docs/KNOWLEDGE-SCHEMA-V2.md +150 -0
- package/docs/MIGRATIONS.md +103 -0
- package/docs/PRIVACY.md +28 -0
- package/docs/RECOVERY.md +58 -0
- package/docs/RELEASING.md +98 -0
- package/docs/hosts.md +152 -0
- package/docs/images/prodero-context-ready-cropped.png +0 -0
- package/docs/images/prodero-overview-cropped.png +0 -0
- package/docs/images/prodero-session-learned-cropped.png +0 -0
- package/docs/plugins.md +83 -0
- package/docs/ui-themes.md +49 -0
- package/instructions/AGENTS.md +39 -0
- package/package.json +92 -4
- package/plugins/migrate_ai_studio/README.md +101 -0
- package/plugins/migrate_ai_studio/index.cjs +396 -0
- package/plugins/migrate_ai_studio/instructions/audit.md +25 -0
- package/plugins/migrate_ai_studio/instructions/classify.md +41 -0
- package/plugins/migrate_ai_studio/instructions/migrate.md +34 -0
- package/plugins/migrate_ai_studio/instructions/verify.md +20 -0
- package/plugins/migrate_ai_studio/plugin.json +11 -0
- package/plugins/migrate_ai_studio/schema.cjs +20 -0
- package/plugins/migrate_ai_studio/ui/dist/index.html +77 -0
- package/prodero.release.json +5 -0
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
import { parseObservationAnnotations as annotations } from "./format.js";
|
|
2
|
+
import { validateApplicability, withApplicability, sameApplicability } from "./applicability.js";
|
|
3
|
+
import { inferredKnowledge } from './knowledge-authority.js';
|
|
4
|
+
import { inheritKnowledgePolicy } from "./knowledge-policy.js";
|
|
5
|
+
import { SyncEvidenceCollector, pressureRecord } from "./memory-pressure.js";
|
|
6
|
+
import { deterministicClassification, reconciliationSignal } from "./reconciliation.js";
|
|
7
|
+
import { assertCompatibleProject } from "./storage.js";
|
|
8
|
+
import { assertNoSecrets } from "./secrets.js";
|
|
9
|
+
import { exactKnowledge, semanticRisk, evidenceSentences } from "./semantic-safety.js";
|
|
10
|
+
import { createSkillCandidate } from "./skills.js";
|
|
11
|
+
import { sortableId } from "./ids.js";
|
|
12
|
+
import { tokens } from "./matching.js";
|
|
13
|
+
import { loadMemoryCatalog, MAX_RELATED_MEMORY_CANDIDATES } from "./memory-index.js";
|
|
14
|
+
import { renderKnowledge, parseKnowledge } from "./memory.js";
|
|
15
|
+
import { safeProjectPath, fileHash } from "./sync-transaction.js";
|
|
16
|
+
import { estimateTokens, knowledgeText } from "./telemetry.js";
|
|
17
|
+
const SECTION_DEFAULTS = {
|
|
18
|
+
Decisions: { category: "decision", priority: "important", scope: "global" },
|
|
19
|
+
Lessons: { category: "lesson", priority: "normal", scope: "task" },
|
|
20
|
+
Discoveries: { category: "state", priority: "normal", scope: "task" },
|
|
21
|
+
"Project State Changes": { category: "state", priority: "normal", scope: "global" },
|
|
22
|
+
"Global Instruction Candidates": { category: "instruction", priority: "important", scope: "global" },
|
|
23
|
+
"Skill Candidates": { category: "instruction", priority: "contextual", scope: "task" },
|
|
24
|
+
};
|
|
25
|
+
function similarity(a, b) {
|
|
26
|
+
const left = tokens(a);
|
|
27
|
+
const right = tokens(b);
|
|
28
|
+
if (!left.size || !right.size)
|
|
29
|
+
return { jaccard: 0, coverage: 0 };
|
|
30
|
+
const intersection = [...left].filter((token) => right.has(token)).length;
|
|
31
|
+
return { jaccard: intersection / (left.size + right.size - intersection), coverage: intersection / Math.min(left.size, right.size) };
|
|
32
|
+
}
|
|
33
|
+
function asScope(value, fallback) { return value === "module" || value === "task" || value === "global" ? value : fallback; }
|
|
34
|
+
function asPriority(value, fallback) { return value === "critical" || value === "important" || value === "normal" || value === "contextual" ? value : fallback; }
|
|
35
|
+
function asCategory(value, fallback) {
|
|
36
|
+
return value === "principle" || value === "invariant" || value === "decision" || value === "constraint" || value === "lesson" || value === "state" || value === "instruction" ? value : fallback;
|
|
37
|
+
}
|
|
38
|
+
function titleFrom(content) {
|
|
39
|
+
const clean = content.replace(/[`*_]/g, "").replace(/\s+/g, " ").trim();
|
|
40
|
+
const sentence = clean.match(/^(.{1,100}?[.!?])(?:\s|$)/)?.[1] ?? clean;
|
|
41
|
+
return sentence.length > 100 ? `${sentence.slice(0, 97).trim()}…` : sentence;
|
|
42
|
+
}
|
|
43
|
+
export function sessionCandidates(session) {
|
|
44
|
+
assertNoSecrets(session);
|
|
45
|
+
const candidates = [];
|
|
46
|
+
for (const [section, items] of Object.entries(session.sections)) {
|
|
47
|
+
const defaults = SECTION_DEFAULTS[section];
|
|
48
|
+
if (!defaults)
|
|
49
|
+
continue;
|
|
50
|
+
items.forEach((raw, index) => {
|
|
51
|
+
const parsed = annotations(raw);
|
|
52
|
+
if (!parsed.content)
|
|
53
|
+
return;
|
|
54
|
+
const explicitFromText = [...parsed.content.matchAll(/\b(?:replaces|supersedes)\s+(mem_[a-zA-Z0-9_]+)/gi)].map((match) => match[1]);
|
|
55
|
+
let triggers = [];
|
|
56
|
+
try {
|
|
57
|
+
const value = JSON.parse(parsed.metadata.triggers ?? "[]");
|
|
58
|
+
if (Array.isArray(value))
|
|
59
|
+
triggers = value.filter((v) => typeof v === "string");
|
|
60
|
+
}
|
|
61
|
+
catch { /* Legacy observations have no structured triggers. */ }
|
|
62
|
+
let relations = [];
|
|
63
|
+
try {
|
|
64
|
+
const value = JSON.parse(parsed.metadata.relations ?? "[]");
|
|
65
|
+
if (Array.isArray(value))
|
|
66
|
+
relations = value.filter((v) => Boolean(v && typeof v === "object" && typeof v.type === "string" && typeof v.target === "string"));
|
|
67
|
+
}
|
|
68
|
+
catch { /* Legacy observations have no structured relationships. */ }
|
|
69
|
+
candidates.push({
|
|
70
|
+
...(parsed.metadata.applicability ? { applicability: validateApplicability(JSON.parse(parsed.metadata.applicability)) } : {}),
|
|
71
|
+
instructionCandidate: section === "Global Instruction Candidates",
|
|
72
|
+
skill: section === "Skill Candidates",
|
|
73
|
+
description: parsed.metadata.description,
|
|
74
|
+
triggers,
|
|
75
|
+
proposedReason: parsed.metadata.proposed_reason,
|
|
76
|
+
scope: asScope(parsed.metadata.scope, defaults.scope),
|
|
77
|
+
scopeRef: parsed.metadata.scope_ref ?? null,
|
|
78
|
+
...(parsed.metadata.scope_kind ? { scopeKind: parsed.metadata.scope_kind } : {}),
|
|
79
|
+
category: asCategory(parsed.metadata.category, defaults.category),
|
|
80
|
+
priority: asPriority(parsed.metadata.priority, defaults.priority),
|
|
81
|
+
title: parsed.metadata.title ?? titleFrom(parsed.content),
|
|
82
|
+
content: parsed.content,
|
|
83
|
+
sessionId: session.id,
|
|
84
|
+
derivedFrom: `${session.id}#${parsed.metadata.id ?? `${section.replace(/\s+/g, "-").toLowerCase()}-${index + 1}`}`,
|
|
85
|
+
supersedes: unique([...(parsed.metadata.supersedes?.split(",").map((value) => value.trim()).filter(Boolean) ?? []), ...explicitFromText]),
|
|
86
|
+
relations,
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
return candidates.map(c => c.applicability && !c.skill && !c.instructionCandidate ? withApplicability(c, c.applicability) : c);
|
|
91
|
+
}
|
|
92
|
+
function unique(values) { return [...new Set(values)].sort(); }
|
|
93
|
+
function uniqueRelations(values) {
|
|
94
|
+
return [...new Map(values.map((relation) => [`${relation.type}:${relation.target}`, relation])).values()]
|
|
95
|
+
.sort((a, b) => a.type.localeCompare(b.type) || a.target.localeCompare(b.target));
|
|
96
|
+
}
|
|
97
|
+
function explicitSuperseded(candidate, active) {
|
|
98
|
+
return active.filter((item) => candidate.supersedes.includes(item.id) || new RegExp(`\\b(?:replaces|supersedes)\\s+${item.id.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`, "i").test(candidate.content));
|
|
99
|
+
}
|
|
100
|
+
/** Core-owned signals; similarity and merge guards are never conflict verdicts. */
|
|
101
|
+
export function candidateReviewSignals(candidate, related) {
|
|
102
|
+
const signals = [];
|
|
103
|
+
if (candidate.instructionCandidate)
|
|
104
|
+
signals.push({ kind: 'instruction-candidate', detail: 'Proposed instruction; not an active required policy.', severity: 'info', requiresReview: true });
|
|
105
|
+
if (candidate.skill)
|
|
106
|
+
signals.push({ kind: 'skill-candidate', detail: 'Proposed skill; activation requires separate review.', severity: 'info', requiresReview: true });
|
|
107
|
+
for (const old of related) {
|
|
108
|
+
const category = deterministicClassification(candidate, old);
|
|
109
|
+
// Retrieval can include loose neighbors; keep unrelated observations quiet.
|
|
110
|
+
if (category === 'unresolved' && similarity(candidate.content, old.content).coverage < 0.3)
|
|
111
|
+
continue;
|
|
112
|
+
signals.push(reconciliationSignal(category, old));
|
|
113
|
+
}
|
|
114
|
+
return signals;
|
|
115
|
+
}
|
|
116
|
+
function mergeEligible(candidate, old) {
|
|
117
|
+
if (semanticRisk(candidate.content, old.content))
|
|
118
|
+
return false;
|
|
119
|
+
const incoming = evidenceSentences(candidate.content);
|
|
120
|
+
const existing = evidenceSentences(old.content);
|
|
121
|
+
// Only an unchanged sentence prefix plus complementary complete sentences is safe without reasoning.
|
|
122
|
+
const smaller = incoming.length < existing.length ? incoming : existing;
|
|
123
|
+
const larger = incoming.length < existing.length ? existing : incoming;
|
|
124
|
+
return smaller.length > 0 && smaller.every((sentence, index) => sentence === larger[index])
|
|
125
|
+
&& estimateTokens(`${old.title}\n${old.content}\n${candidate.content}`) <= 2_000;
|
|
126
|
+
}
|
|
127
|
+
function segments(value) {
|
|
128
|
+
return value.split(/(?:\r?\n){2,}|(?<=[.!?])\s+(?=[\p{Lu}\p{N}`])/u).map((part) => part.trim()).filter(Boolean);
|
|
129
|
+
}
|
|
130
|
+
function compactContribution(existing, incoming) {
|
|
131
|
+
const kept = segments(existing);
|
|
132
|
+
const seen = new Set(kept.map(exactKnowledge));
|
|
133
|
+
const added = [];
|
|
134
|
+
let removed = 0;
|
|
135
|
+
for (const segment of segments(incoming)) {
|
|
136
|
+
const key = exactKnowledge(segment);
|
|
137
|
+
if (!key || seen.has(key)) {
|
|
138
|
+
removed += 1;
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
seen.add(key);
|
|
142
|
+
kept.push(segment);
|
|
143
|
+
added.push(segment);
|
|
144
|
+
}
|
|
145
|
+
return { content: kept.join("\n\n"), added: added.join("\n\n"), removed };
|
|
146
|
+
}
|
|
147
|
+
async function deterministicConsolidate(root, sessions, now = new Date(), processedSources = new Set()) {
|
|
148
|
+
const candidates = sessions.flatMap(sessionCandidates).filter((candidate) => !processedSources.has(candidate.derivedFrom));
|
|
149
|
+
const catalog = await loadMemoryCatalog(root);
|
|
150
|
+
const writes = new Map();
|
|
151
|
+
const pressure = new SyncEvidenceCollector();
|
|
152
|
+
const changedRecords = new Map();
|
|
153
|
+
const stageKnowledge = async (item) => {
|
|
154
|
+
const relative = `local/memory/${item.id}.md`;
|
|
155
|
+
const after = renderKnowledge(item);
|
|
156
|
+
parseKnowledge(after, `${item.id}.md`);
|
|
157
|
+
const existing = writes.get(relative);
|
|
158
|
+
const before = existing ? existing.before : catalog.originalHashes.get(item.id) ?? await fileHash(await safeProjectPath(root, relative));
|
|
159
|
+
writes.set(relative, { path: relative, before, after });
|
|
160
|
+
catalog.stage(item, "local");
|
|
161
|
+
changedRecords.set(item.id, pressureRecord({ ...item, layer: "local", estimatedTokens: estimateTokens(knowledgeText(item)), relations: item.relations ?? [] }));
|
|
162
|
+
};
|
|
163
|
+
const result = {
|
|
164
|
+
candidates: 0, created: [], createdSkills: [], skillCandidateTokensAdded: 0, updated: [], consolidated: [], superseded: [], duplicates: [], conflicts: [], conflictCount: 0,
|
|
165
|
+
memoryFilesScanned: catalog.filesScanned, memoryBodiesIndexed: catalog.bodiesIndexed, memoryBodiesLoaded: 0, relatedCandidatesSelectedMax: 0,
|
|
166
|
+
observationsProcessed: 0, rawSessionTokens: 0, rawKnowledgeTokens: 0, rawSkillCandidateTokens: 0, canonicalTokensAdded: 0,
|
|
167
|
+
compactedSegments: 0, conservativeMergesSkipped: 0,
|
|
168
|
+
};
|
|
169
|
+
result.candidates = candidates.length;
|
|
170
|
+
result.observationsProcessed = candidates.length;
|
|
171
|
+
result.rawKnowledgeTokens = candidates.filter((candidate) => !candidate.skill).reduce((total, candidate) => total + estimateTokens(candidate.content), 0);
|
|
172
|
+
result.rawSkillCandidateTokens = candidates.filter((candidate) => candidate.skill).reduce((total, candidate) => total + estimateTokens(candidate.content), 0);
|
|
173
|
+
result.rawSessionTokens = candidates.reduce((total, candidate) => total + estimateTokens(candidate.content), 0);
|
|
174
|
+
for (const candidate of candidates) {
|
|
175
|
+
if (candidate.instructionCandidate) {
|
|
176
|
+
(result.instructionCandidates ??= []).push({ ...candidate, id: `candidate_${result.instructionCandidates?.length ?? 0}`, kind: "instruction_candidate", sources: [candidate.derivedFrom], evidence: [{ source: candidate.derivedFrom, quote: candidate.content }] });
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
if (candidate.skill) {
|
|
180
|
+
const saved = await createSkillCandidate(root, {
|
|
181
|
+
title: candidate.title,
|
|
182
|
+
description: candidate.description ?? candidate.content.replace(/\s+/g, " ").slice(0, 300),
|
|
183
|
+
content: candidate.content,
|
|
184
|
+
scope: candidate.scopeRef ?? "project",
|
|
185
|
+
triggers: candidate.triggers,
|
|
186
|
+
}, { sessionId: candidate.sessionId, derivedFrom: candidate.derivedFrom, proposedReason: candidate.proposedReason }, now, writes);
|
|
187
|
+
if (saved.created) {
|
|
188
|
+
result.createdSkills.push(saved.skill.id);
|
|
189
|
+
result.skillCandidateTokensAdded += estimateTokens(saved.skill.content);
|
|
190
|
+
}
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
const selected = catalog.selectRelated({
|
|
194
|
+
scope: candidate.scope,
|
|
195
|
+
scopeRef: candidate.scopeRef,
|
|
196
|
+
scopeKind: candidate.scopeKind,
|
|
197
|
+
applicability: candidate.applicability ?? { kind: "legacy" },
|
|
198
|
+
category: candidate.category,
|
|
199
|
+
title: candidate.title,
|
|
200
|
+
content: candidate.content,
|
|
201
|
+
explicitIds: candidate.supersedes,
|
|
202
|
+
}, MAX_RELATED_MEMORY_CANDIDATES);
|
|
203
|
+
// An explicit replacement must survive the complete write path. Never silently
|
|
204
|
+
// drop a missing/historical target and save an unrelated active record instead.
|
|
205
|
+
const unavailable = candidate.supersedes.filter(id => !selected.some(item => item.id === id));
|
|
206
|
+
if (unavailable.length)
|
|
207
|
+
throw new Error(`Explicit supersedes target is missing or no longer current: ${unavailable.join(", ")}. Session remains pending; use current memory IDs.`);
|
|
208
|
+
result.relatedCandidatesSelectedMax = Math.max(result.relatedCandidatesSelectedMax, selected.length);
|
|
209
|
+
const active = await Promise.all(selected.map((entry) => catalog.load(entry.id)));
|
|
210
|
+
for (const signal of candidateReviewSignals(candidate, active)) {
|
|
211
|
+
const target = selected.find(item => item.id === signal.memory?.id);
|
|
212
|
+
if (target)
|
|
213
|
+
pressure.observe(candidate.derivedFrom, pressureRecord(target), signal.kind);
|
|
214
|
+
}
|
|
215
|
+
const localIds = new Set(selected.filter((entry) => entry.layer === "local").map((entry) => entry.id));
|
|
216
|
+
const exact = active.find((item) => item.scope === candidate.scope && item.scopeRef === candidate.scopeRef && item.scopeKind === candidate.scopeKind && item.category === candidate.category && sameApplicability(item, candidate) && exactKnowledge(item.content) === exactKnowledge(candidate.content));
|
|
217
|
+
if (exact && !candidate.supersedes.length) {
|
|
218
|
+
if (localIds.has(exact.id)) {
|
|
219
|
+
exact.sourceSessions = unique([...exact.sourceSessions, candidate.sessionId]);
|
|
220
|
+
exact.derivedFrom = unique([...exact.derivedFrom, candidate.derivedFrom]);
|
|
221
|
+
exact.relations = uniqueRelations([...(exact.relations ?? []), ...candidate.relations]);
|
|
222
|
+
exact.updatedAt = now.toISOString();
|
|
223
|
+
await stageKnowledge(exact);
|
|
224
|
+
result.updated.push(exact.id);
|
|
225
|
+
}
|
|
226
|
+
result.duplicates.push(exact.id);
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
const supersededTargets = explicitSuperseded(candidate, active);
|
|
230
|
+
const replaced = supersededTargets.filter((item) => localIds.has(item.id));
|
|
231
|
+
const id = sortableId("mem", now);
|
|
232
|
+
const item = {
|
|
233
|
+
id,
|
|
234
|
+
scope: candidate.scope,
|
|
235
|
+
scopeRef: candidate.scopeRef,
|
|
236
|
+
scopeKind: candidate.scopeKind,
|
|
237
|
+
applicability: candidate.applicability ?? { kind: "legacy" },
|
|
238
|
+
version: 3,
|
|
239
|
+
category: candidate.category,
|
|
240
|
+
priority: candidate.priority,
|
|
241
|
+
status: "active",
|
|
242
|
+
title: candidate.title,
|
|
243
|
+
content: candidate.content,
|
|
244
|
+
createdAt: now.toISOString(),
|
|
245
|
+
updatedAt: now.toISOString(),
|
|
246
|
+
sourceSessions: [candidate.sessionId],
|
|
247
|
+
derivedFrom: [candidate.derivedFrom],
|
|
248
|
+
supersedes: unique(supersededTargets.flatMap((old) => [old.id, ...old.supersedes])),
|
|
249
|
+
related: [],
|
|
250
|
+
relations: uniqueRelations(candidate.relations),
|
|
251
|
+
};
|
|
252
|
+
Object.assign(item, withApplicability(inferredKnowledge(item), item.applicability));
|
|
253
|
+
inheritKnowledgePolicy(item, supersededTargets);
|
|
254
|
+
if (supersededTargets.length) {
|
|
255
|
+
for (const old of replaced) {
|
|
256
|
+
old.status = "superseded";
|
|
257
|
+
old.updatedAt = now.toISOString();
|
|
258
|
+
old.related = unique([...old.related, id]);
|
|
259
|
+
await stageKnowledge(old);
|
|
260
|
+
result.superseded.push(old.id);
|
|
261
|
+
result.updated.push(old.id);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
else {
|
|
265
|
+
const sameBoundary = active.filter((old) => localIds.has(old.id) && old.scope === candidate.scope && old.scopeRef === candidate.scopeRef && old.scopeKind === candidate.scopeKind && old.category === candidate.category && sameApplicability(old, candidate));
|
|
266
|
+
const overlapping = sameBoundary.find((old) => mergeEligible(candidate, old));
|
|
267
|
+
if (overlapping) {
|
|
268
|
+
const compacted = compactContribution(overlapping.content, candidate.content);
|
|
269
|
+
overlapping.content = compacted.content;
|
|
270
|
+
result.compactedSegments += compacted.removed;
|
|
271
|
+
result.canonicalTokensAdded += estimateTokens(compacted.added);
|
|
272
|
+
overlapping.sourceSessions = unique([...overlapping.sourceSessions, candidate.sessionId]);
|
|
273
|
+
overlapping.derivedFrom = unique([...overlapping.derivedFrom, candidate.derivedFrom]);
|
|
274
|
+
overlapping.relations = uniqueRelations([...(overlapping.relations ?? []), ...candidate.relations]);
|
|
275
|
+
overlapping.updatedAt = now.toISOString();
|
|
276
|
+
await stageKnowledge(overlapping);
|
|
277
|
+
result.updated.push(overlapping.id);
|
|
278
|
+
if (compacted.added)
|
|
279
|
+
result.consolidated.push(overlapping.id);
|
|
280
|
+
else
|
|
281
|
+
result.duplicates.push(overlapping.id);
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
if (sameBoundary.some((old) => {
|
|
285
|
+
const score = similarity(`${candidate.title} ${candidate.content}`, `${old.title} ${old.content}`);
|
|
286
|
+
return score.jaccard >= 0.35 || score.coverage >= 0.5;
|
|
287
|
+
}))
|
|
288
|
+
result.conservativeMergesSkipped += 1;
|
|
289
|
+
// Similarity and protected-token changes only limit safe merging. Without
|
|
290
|
+
// semantic evidence, keep both records active for later reconciliation.
|
|
291
|
+
}
|
|
292
|
+
await stageKnowledge(item);
|
|
293
|
+
result.created.push(id);
|
|
294
|
+
result.canonicalTokensAdded += estimateTokens(knowledgeText(item));
|
|
295
|
+
}
|
|
296
|
+
result.created = unique(result.created);
|
|
297
|
+
result.updated = unique(result.updated);
|
|
298
|
+
result.consolidated = unique(result.consolidated);
|
|
299
|
+
result.superseded = unique(result.superseded);
|
|
300
|
+
result.duplicates = unique(result.duplicates);
|
|
301
|
+
result.conflicts = unique(result.conflicts);
|
|
302
|
+
result.memoryBodiesLoaded = catalog.bodiesLoaded;
|
|
303
|
+
result.writes = [...writes.values()];
|
|
304
|
+
result.memoryPressure = pressure.finish([...changedRecords.values()], result);
|
|
305
|
+
return result;
|
|
306
|
+
}
|
|
307
|
+
export const deterministicConsolidator = {
|
|
308
|
+
name: "deterministic-v1",
|
|
309
|
+
consolidate: deterministicConsolidate,
|
|
310
|
+
};
|
|
311
|
+
export async function consolidateSessions(root, sessions, now = new Date(), consolidator = deterministicConsolidator, processedSources = new Set()) {
|
|
312
|
+
await assertCompatibleProject(root);
|
|
313
|
+
return consolidator.consolidate(root, sessions, now, processedSources);
|
|
314
|
+
}
|
|
315
|
+
//# sourceMappingURL=consolidation.js.map
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
const packageIdentity = JSON.parse(readFileSync(new URL("../../package.json", import.meta.url), "utf8"));
|
|
3
|
+
const releaseIdentity = JSON.parse(readFileSync(new URL("../../prodero.release.json", import.meta.url), "utf8"));
|
|
4
|
+
if (releaseIdentity.formatVersion !== 1 || !Number.isInteger(releaseIdentity.schemaVersion) || releaseIdentity.schemaVersion < 1 || !Number.isInteger(releaseIdentity.knowledgeSchemaVersion) || releaseIdentity.knowledgeSchemaVersion < 1)
|
|
5
|
+
throw new Error("Invalid prodero.release.json");
|
|
6
|
+
export const PRODERO_VERSION = packageIdentity.version;
|
|
7
|
+
export const PRODERO_SCHEMA_VERSION = releaseIdentity.schemaVersion;
|
|
8
|
+
export const PRODERO_KNOWLEDGE_SCHEMA_VERSION = releaseIdentity.knowledgeSchemaVersion;
|
|
9
|
+
export const PRODERO_PACKAGE_NAME = packageIdentity.name;
|
|
10
|
+
export const PRODERO_DIRECTORY = ".prodero";
|
|
11
|
+
export const MANAGED_START = "<!-- prodero:start -->";
|
|
12
|
+
export const MANAGED_END = "<!-- prodero:end -->";
|
|
13
|
+
export const PROJECT_DIRECTORIES = [
|
|
14
|
+
"memory",
|
|
15
|
+
"sessions",
|
|
16
|
+
"skills",
|
|
17
|
+
"checkpoints",
|
|
18
|
+
"local/memory",
|
|
19
|
+
".cache",
|
|
20
|
+
];
|
|
21
|
+
//# sourceMappingURL=constants.js.map
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/** Resource allocation only: applicability, authority and ranking remain in Core retrieval. */
|
|
2
|
+
export const REQUIRED_HARD_TOKENS = 16_000;
|
|
3
|
+
export const REQUIRED_HARD_ITEMS = 128;
|
|
4
|
+
export const RELEVANT_HARD_ITEMS = 128;
|
|
5
|
+
export const SKILL_HARD_TOKENS = 1_500;
|
|
6
|
+
export class ContextAllocation {
|
|
7
|
+
total;
|
|
8
|
+
reserve;
|
|
9
|
+
used = 0;
|
|
10
|
+
requiredTokens = 0;
|
|
11
|
+
requiredItems = 0;
|
|
12
|
+
skillTokens = 0;
|
|
13
|
+
skillItems = 0;
|
|
14
|
+
relevantItems = 0;
|
|
15
|
+
constructor(total, reserve) {
|
|
16
|
+
this.total = total;
|
|
17
|
+
this.reserve = reserve;
|
|
18
|
+
}
|
|
19
|
+
get remaining() { return Math.max(0, this.total - this.used); }
|
|
20
|
+
take(bucket, cost) {
|
|
21
|
+
if (bucket === 'required' && this.requiredTokens + cost > REQUIRED_HARD_TOKENS)
|
|
22
|
+
return 'required-token-ceiling';
|
|
23
|
+
if (bucket === 'required' && this.requiredItems >= REQUIRED_HARD_ITEMS)
|
|
24
|
+
return 'required-item-ceiling';
|
|
25
|
+
if (bucket === 'relevant' && this.relevantItems >= RELEVANT_HARD_ITEMS)
|
|
26
|
+
return 'relevant-item-ceiling';
|
|
27
|
+
if (bucket === 'skill' && this.skillItems >= 4)
|
|
28
|
+
return 'skill-item-ceiling';
|
|
29
|
+
if (bucket === 'skill' && this.skillTokens + cost > SKILL_HARD_TOKENS)
|
|
30
|
+
return 'skill-token-ceiling';
|
|
31
|
+
if (cost > this.remaining)
|
|
32
|
+
return 'total-capacity';
|
|
33
|
+
this.used += cost;
|
|
34
|
+
if (bucket === 'required') {
|
|
35
|
+
this.requiredTokens += cost;
|
|
36
|
+
this.requiredItems++;
|
|
37
|
+
}
|
|
38
|
+
if (bucket === 'relevant')
|
|
39
|
+
this.relevantItems++;
|
|
40
|
+
if (bucket === 'skill') {
|
|
41
|
+
this.skillTokens += cost;
|
|
42
|
+
this.skillItems++;
|
|
43
|
+
}
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/** Approximate information novelty, not semantic equivalence. Never applied to Required. */
|
|
48
|
+
export function marginalValue(words, query, supplied, rankScore, peakScore) {
|
|
49
|
+
const content = [...new Set(words.filter(t => !query.has(t) && !/^\d+$/.test(t)))];
|
|
50
|
+
const novel = content.filter(t => !supplied.has(t));
|
|
51
|
+
const fraction = content.length ? novel.length / content.length : 0;
|
|
52
|
+
return { useful: rankScore >= Math.max(12, peakScore * 0.3) && novel.length >= 3 && fraction >= 0.2, novelTerms: novel.length, novelFraction: fraction };
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=context-allocation.js.map
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** Associate the exact resolver result with its prepared session without copying canonical bodies. */
|
|
2
|
+
export function associatePreparedContext(context, sessionId) {
|
|
3
|
+
context.snapshot = { version: 1, sessionId, task: context.query };
|
|
4
|
+
return context;
|
|
5
|
+
}
|
|
6
|
+
/** UI-safe, I/O-free lookup. Missing old/disposable state never triggers context resolution. */
|
|
7
|
+
export function preparedContextSnapshot(context) {
|
|
8
|
+
if (!context || context.snapshot?.version !== 1 || !context.snapshot.sessionId)
|
|
9
|
+
return null;
|
|
10
|
+
return context;
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=context-snapshot.js.map
|