Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
91e14ef
feat(ui): apply reviewed dedup decisions with receipts and recovery (…
BYK Oct 4, 2026
f873336
fix(ui): clarify duplicate apply receipts and review state (#1804)
BYK Oct 4, 2026
1a3b33a
test(ui): cover stale exclusion and unavailable storage for dedup app…
BYK Oct 4, 2026
45e1fe2
test(ui): count only the confirm-time sync read in the dedup apply test
BYK Oct 5, 2026
3f78212
fix(ui): derive the .lore.md apply consequence from the removed entries
BYK Oct 5, 2026
1a83f17
feat(ui): keep the private copy when merging into a shared entry
BYK Oct 11, 2026
03b416f
fix(ui): close the apply dialog when no accepted group remains
BYK Oct 11, 2026
569f0c6
feat(api,ui): revision-checked knowledge edit, delete and restore (#1…
BYK Oct 5, 2026
56ea3fe
fix(ui): send only changed knowledge fields so confidence is not clob…
BYK Oct 5, 2026
ad09f22
fix(ui,core): re-arm autosave and report project exports
BYK Oct 5, 2026
ec61199
fix(ui): handle deleted-entry refusals in the knowledge editor
BYK Oct 5, 2026
87871c9
fix(core): report deleted before stale revision for edit and delete
BYK Oct 5, 2026
7a6992a
feat(core): guard shared knowledge title conflicts
BYK Oct 5, 2026
f73f0b9
feat(gateway): expose shared title conflicts
BYK Oct 5, 2026
52e45b7
feat(ui): surface shared title conflicts
BYK Oct 5, 2026
255d0ee
refactor(core): share the title-key SQL helper
BYK Oct 5, 2026
4af4e25
fix(ui): refresh knowledge lists and counts after edit, delete and re…
BYK Oct 5, 2026
6d42b92
test(gateway): model legacy shared-title duplicate
BYK Oct 11, 2026
5183f11
fix(ui): keep knowledge save errors visible after the editor closes
BYK Oct 11, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions packages/core/src/curator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1052,9 +1052,9 @@ async function runInner(input: {
try {
input.signal?.throwIfAborted();
const promotion = ltm.promoteCrossProject({ dryRun: false });
if (promotion.promoted > 0) {
if (promotion.promoted > 0 || promotion.conflicts.length > 0) {
log.info(
`cross-project promotion: promoted ${promotion.promoted} entries across ${promotion.clusters.length} cluster(s)`,
`cross-project promotion: promoted ${promotion.promoted} entries, skipped ${promotion.conflicts.length} title conflict(s) across ${promotion.clusters.length} cluster(s)`,
);
}
} catch (err) {
Expand Down
11 changes: 10 additions & 1 deletion packages/core/src/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1834,13 +1834,22 @@ export function reassignKnowledge(
// across projects (reviewer NIT).
// When moving from cross_project to a project, clear the flag.
const clearCrossProject = oldProjectId === null || oldProjectId === "";
const nextCrossProject = clearCrossProject ? 0 : 1;
database.query("BEGIN IMMEDIATE").run();
try {
if (nextCrossProject === 1) {
const conflict = ltm.findSharedTitleConflict(
entry.logical_id,
entry.title,
);
if (conflict) throw new ltm.TitleConflictError(entry.title, conflict);
}

database
.query(
"UPDATE knowledge SET project_id = ?, cross_project = ? WHERE logical_id = ?",
)
.run(toId, clearCrossProject ? 0 : 1, entry.logical_id);
.run(toId, nextCrossProject, entry.logical_id);

// Clean up knowledge_transfers that became self-referential after the move.
if (clearCrossProject) {
Expand Down
29 changes: 23 additions & 6 deletions packages/core/src/import/structured.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import * as ltm from "../ltm";
import { db, ensureProject } from "../db";
import { parseImportDoc, type LoreImportDoc } from "./schema";
import { currentTenantId } from "../tenant";

export type StructuredImportOptions = {
/** Fallback project path for entries without an explicit `project`. */
Expand Down Expand Up @@ -64,25 +65,41 @@ function clampConfidence(v: number | undefined): number {
* project pool then the cross-project pool. Returns the resolved current-row id.
*/
function findExactTitle(title: string, pid: string | null): string | null {
const tenantId = currentTenantId();
const inProject =
pid !== null
? (db()
.query(
"SELECT id FROM knowledge_current WHERE project_id = ? AND LOWER(title) = LOWER(?) AND confidence > 0 LIMIT 1",
`SELECT id FROM knowledge_current
WHERE tenant_id = ? AND project_id = ?
AND ${ltm.titleKeySql("title")} = ${ltm.titleKeySql("?")}
AND confidence > 0
ORDER BY logical_id
LIMIT 1`,
)
.get(pid, title) as { id: string } | null)
.get(tenantId, pid, title) as { id: string } | null)
: (db()
.query(
"SELECT id FROM knowledge_current WHERE project_id IS NULL AND LOWER(title) = LOWER(?) AND confidence > 0 LIMIT 1",
`SELECT id FROM knowledge_current
WHERE tenant_id = ? AND project_id IS NULL
AND ${ltm.titleKeySql("title")} = ${ltm.titleKeySql("?")}
AND confidence > 0
ORDER BY logical_id
LIMIT 1`,
)
.get(title) as { id: string } | null);
.get(tenantId, title) as { id: string } | null);
if (inProject) return inProject.id;

const crossProject = db()
.query(
"SELECT id FROM knowledge_current WHERE cross_project = 1 AND LOWER(title) = LOWER(?) AND confidence > 0 LIMIT 1",
`SELECT id FROM knowledge_current
WHERE tenant_id = ? AND cross_project = 1
AND ${ltm.titleKeySql("title")} = ${ltm.titleKeySql("?")}
AND confidence > 0
ORDER BY logical_id
LIMIT 1`,
)
.get(title) as { id: string } | null;
.get(tenantId, title) as { id: string } | null;
return crossProject?.id ?? null;
}

Expand Down
1 change: 1 addition & 0 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

export * as temporal from "./temporal";
export * as ltm from "./ltm";
export * as knowledgeEdit from "./knowledge-edit";
// Client-side encryption primitives (C, #825). Namespaced `crypto` — consumers that
// also need the WebCrypto/Node global should import that from `node:crypto` to avoid
// shadowing this named import.
Expand Down
Loading
Loading