@askdkc/kiokuko 0.1.16 → 0.1.17

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 (96) hide show
  1. package/README.ja.md +3 -3
  2. package/README.ko.md +2 -2
  3. package/README.md +9 -7
  4. package/README.zh-CN.md +3 -3
  5. package/dist/agent-file/render.js +2 -2
  6. package/dist/agent-file/render.js.map +1 -1
  7. package/dist/akinator/agent-task.d.ts +4 -3
  8. package/dist/akinator/agent-task.d.ts.map +1 -1
  9. package/dist/akinator/agent-task.js +8 -6
  10. package/dist/akinator/agent-task.js.map +1 -1
  11. package/dist/akinator/capabilities.d.ts +36 -3
  12. package/dist/akinator/capabilities.d.ts.map +1 -1
  13. package/dist/akinator/capabilities.js +171 -10
  14. package/dist/akinator/capabilities.js.map +1 -1
  15. package/dist/cli.d.ts.map +1 -1
  16. package/dist/cli.js +40 -9
  17. package/dist/cli.js.map +1 -1
  18. package/dist/context/broker.d.ts +2 -0
  19. package/dist/context/broker.d.ts.map +1 -1
  20. package/dist/context/broker.js +39 -20
  21. package/dist/context/broker.js.map +1 -1
  22. package/dist/context/delivery.d.ts +2 -1
  23. package/dist/context/delivery.d.ts.map +1 -1
  24. package/dist/context/delivery.js +12 -9
  25. package/dist/context/delivery.js.map +1 -1
  26. package/dist/context/feedback.d.ts.map +1 -1
  27. package/dist/context/feedback.js +21 -7
  28. package/dist/context/feedback.js.map +1 -1
  29. package/dist/context/origin.d.ts +9 -0
  30. package/dist/context/origin.d.ts.map +1 -0
  31. package/dist/context/origin.js +15 -0
  32. package/dist/context/origin.js.map +1 -0
  33. package/dist/context/ranking.d.ts +3 -1
  34. package/dist/context/ranking.d.ts.map +1 -1
  35. package/dist/context/ranking.js +9 -2
  36. package/dist/context/ranking.js.map +1 -1
  37. package/dist/context/scoped-broker.d.ts +4 -3
  38. package/dist/context/scoped-broker.d.ts.map +1 -1
  39. package/dist/context/scoped-broker.js +27 -34
  40. package/dist/context/scoped-broker.js.map +1 -1
  41. package/dist/ledger/archive.d.ts.map +1 -1
  42. package/dist/ledger/archive.js +58 -16
  43. package/dist/ledger/archive.js.map +1 -1
  44. package/dist/ledger/maintenance.d.ts.map +1 -1
  45. package/dist/ledger/maintenance.js +17 -4
  46. package/dist/ledger/maintenance.js.map +1 -1
  47. package/dist/mcp/bounded-stdio-transport.d.ts +23 -0
  48. package/dist/mcp/bounded-stdio-transport.d.ts.map +1 -0
  49. package/dist/mcp/bounded-stdio-transport.js +109 -0
  50. package/dist/mcp/bounded-stdio-transport.js.map +1 -0
  51. package/dist/mcp/server.d.ts.map +1 -1
  52. package/dist/mcp/server.js +12 -27
  53. package/dist/mcp/server.js.map +1 -1
  54. package/dist/memory/curator.d.ts +45 -1
  55. package/dist/memory/curator.d.ts.map +1 -1
  56. package/dist/memory/curator.js +178 -32
  57. package/dist/memory/curator.js.map +1 -1
  58. package/dist/memory/federated-retrieval.d.ts +67 -0
  59. package/dist/memory/federated-retrieval.d.ts.map +1 -0
  60. package/dist/memory/federated-retrieval.js +328 -0
  61. package/dist/memory/federated-retrieval.js.map +1 -0
  62. package/dist/memory/portability.d.ts +10 -0
  63. package/dist/memory/portability.d.ts.map +1 -0
  64. package/dist/memory/portability.js +44 -0
  65. package/dist/memory/portability.js.map +1 -0
  66. package/dist/memory/retrieval.d.ts +11 -0
  67. package/dist/memory/retrieval.d.ts.map +1 -1
  68. package/dist/memory/retrieval.js +29 -9
  69. package/dist/memory/retrieval.js.map +1 -1
  70. package/dist/memory/scoped-memory.d.ts +6 -12
  71. package/dist/memory/scoped-memory.d.ts.map +1 -1
  72. package/dist/memory/scoped-memory.js +24 -48
  73. package/dist/memory/scoped-memory.js.map +1 -1
  74. package/dist/memory/structured-memory.d.ts +6 -0
  75. package/dist/memory/structured-memory.d.ts.map +1 -1
  76. package/dist/memory/structured-memory.js +23 -1
  77. package/dist/memory/structured-memory.js.map +1 -1
  78. package/dist/repository/project-fingerprint.d.ts +21 -0
  79. package/dist/repository/project-fingerprint.d.ts.map +1 -0
  80. package/dist/repository/project-fingerprint.js +150 -0
  81. package/dist/repository/project-fingerprint.js.map +1 -0
  82. package/dist/setup/render.js +2 -2
  83. package/dist/setup/render.js.map +1 -1
  84. package/dist/web/i18n.d.ts +24 -8
  85. package/dist/web/i18n.d.ts.map +1 -1
  86. package/dist/web/i18n.js +96 -32
  87. package/dist/web/i18n.js.map +1 -1
  88. package/dist/web/server.d.ts.map +1 -1
  89. package/dist/web/server.js +72 -4
  90. package/dist/web/server.js.map +1 -1
  91. package/dist/web/ui.d.ts.map +1 -1
  92. package/dist/web/ui.js +317 -57
  93. package/dist/web/ui.js.map +1 -1
  94. package/migrations/008_federated_memory.sql +46 -0
  95. package/package.json +3 -1
  96. package/templates/AGENTS.md +2 -2
package/dist/web/ui.js CHANGED
@@ -65,11 +65,18 @@ export const WEB_HTML = String.raw `<!doctype html>
65
65
  .entry-card:hover,.entry-card.selected { border-color:#93c5fd; box-shadow:0 8px 20px rgba(37,99,235,.10); }
66
66
  .entry-meta { display:flex; gap:7px; align-items:center; flex-wrap:wrap; color:var(--muted); font-size:12px; }
67
67
  .badge { border-radius:999px; padding:3px 8px; background:#f1f5f9; color:#475569; font-weight:700; }
68
+ .badge.origin-project { background:#dbeafe; color:#1d4ed8; }
69
+ .badge.origin-ecosystem { background:#dcfce7; color:#166534; }
70
+ .badge.origin-global { background:#fef3c7; color:#92400e; }
68
71
  .badge.verified { background:#dcfce7; color:#166534; }
69
72
  .badge.candidate { background:#fef3c7; color:#92400e; }
70
73
  .badge.superseded { background:#fee2e2; color:#991b1b; }
71
74
  .entry-card h3 { margin:9px 0 6px; font-size:16px; }
72
75
  .snippet { margin:0; color:var(--muted); display:-webkit-box; -webkit-line-clamp:3; -webkit-box-orient:vertical; overflow:hidden; white-space:pre-wrap; }
76
+ .federated-card { border-left:4px solid #22c55e; }
77
+ .federated-card.global { border-left-color:#f59e0b; }
78
+ .federated-source,.selection-reasons { margin-top:8px; color:var(--muted); font-size:12px; white-space:pre-wrap; overflow-wrap:anywhere; }
79
+ .federated-detail { display:grid; gap:12px; }
73
80
  .tags { display:flex; gap:6px; flex-wrap:wrap; margin-top:10px; }
74
81
  .tag { border:0; background:transparent; color:var(--accent); font-size:12px; padding:0; }
75
82
  .tag:hover { text-decoration:underline; }
@@ -106,19 +113,33 @@ export const WEB_HTML = String.raw `<!doctype html>
106
113
  .curator-close { display:grid; place-items:center; flex:0 0 auto; width:44px; height:44px; border:1px solid var(--line); border-radius:12px; background:var(--panel); color:var(--muted); font-size:26px; line-height:1; }
107
114
  .curator-close:hover { border-color:#93c5fd; background:var(--accent-soft); color:var(--accent); }
108
115
  .curator-status { margin:0 0 12px; }
116
+ .curator-filters { display:grid; grid-template-columns:repeat(auto-fit,minmax(150px,1fr)); gap:8px; margin:0 0 14px; padding:12px; border:1px solid var(--line); border-radius:12px; background:var(--surface); }
117
+ .curator-filter { display:grid; gap:4px; color:var(--muted); font-size:11px; font-weight:800; }
118
+ .curator-filter-check { display:flex; align-items:center; gap:6px; color:var(--muted); font-size:12px; font-weight:700; }
119
+ .curator-filter-check input { width:16px; height:16px; }
120
+ .curator-filter-tags { grid-column:1 / -1; display:flex; flex-wrap:wrap; gap:7px; padding-top:4px; }
121
+ .curator-filter-tags .tag.active { font-weight:800; text-decoration:underline; }
109
122
  .curator-list { display:grid; gap:12px; }
110
123
  .curator-card { border:1px solid var(--line); border-radius:14px; padding:16px; background:var(--panel); }
111
- .curator-card.selected { border-color:#93c5fd; box-shadow:0 8px 20px rgba(37,99,235,.10); }
112
- .curator-select { display:flex; gap:8px; align-items:center; color:var(--muted); font-size:13px; font-weight:700; }
113
- .curator-select input { width:17px; height:17px; accent-color:#2563eb; }
114
124
  .curator-workspace { overflow-wrap:anywhere; }
115
125
  .curator-card h3 { margin:8px 0; font-size:17px; }
116
126
  .curator-overview { margin:8px 0 12px; color:var(--muted); white-space:pre-wrap; line-height:1.55; }
127
+ .curator-card-actions { display:flex; flex-wrap:wrap; gap:8px; margin:12px 0 0; }
128
+ .curator-review-details { margin-top:12px; }
117
129
  .curator-draft { margin:12px 0; padding:12px; border:1px solid var(--line); border-radius:12px; background:var(--surface); }
118
130
  .curator-draft h4 { margin:0 0 10px; font-size:14px; }
119
131
  .curator-draft-label { margin:10px 0 4px; color:var(--muted); font-size:12px; font-weight:700; }
120
132
  .curator-draft-value { margin:0; white-space:pre-wrap; overflow-wrap:anywhere; font:inherit; font-size:13px; line-height:1.5; }
121
133
  .curator-reasons { color:var(--muted); font-size:12px; white-space:pre-wrap; }
134
+ .curator-confirm-modal { position:absolute; inset:0; z-index:2; display:grid; place-items:center; padding:24px; background:rgba(15,23,42,.58); }
135
+ .curator-confirm-dialog { width:min(680px,calc(100vw - 32px)); max-height:min(760px,calc(100vh - 48px)); display:flex; flex-direction:column; min-height:0; background:var(--panel); box-shadow:0 28px 90px rgba(15,23,42,.34); }
136
+ .curator-confirm-dialog .panel-body { min-height:0; overflow-y:auto; }
137
+ .curator-confirm-grid { display:grid; grid-template-columns:1fr 1fr; gap:10px; margin:0 0 14px; }
138
+ .curator-confirm-field { min-width:0; padding:11px 12px; border:1px solid var(--line); border-radius:12px; background:var(--surface); }
139
+ .curator-confirm-field dt { margin:0 0 5px; color:var(--muted); font-size:12px; font-weight:800; }
140
+ .curator-confirm-field dd { margin:0; overflow-wrap:anywhere; white-space:pre-wrap; }
141
+ .curator-confirm-draft { max-height:280px; overflow:auto; margin:0; padding:12px; border:1px solid var(--line); border-radius:12px; background:var(--surface); white-space:pre-wrap; overflow-wrap:anywhere; font:inherit; font-size:13px; line-height:1.5; }
142
+ .curator-confirm-actions { display:flex; justify-content:flex-end; gap:8px; margin-top:14px; }
122
143
  @keyframes curator-backdrop-in { from { opacity:0; } to { opacity:1; } }
123
144
  @keyframes curator-backdrop-out { from { opacity:1; } to { opacity:0; } }
124
145
  @keyframes curator-dialog-in { from { opacity:0; transform:translateY(10px) scale(.98); } to { opacity:1; transform:translateY(0) scale(1); } }
@@ -128,7 +149,7 @@ export const WEB_HTML = String.raw `<!doctype html>
128
149
  }
129
150
  @media (max-width:680px) { .operator-grid { grid-template-columns:1fr; } }
130
151
  @media (max-width:1050px) { .layout { grid-template-columns:180px minmax(280px,1fr); } .editor-panel { grid-column:1 / -1; } }
131
- @media (max-width:680px) { .shell { padding:16px; } .topbar { display:block; position:relative; } .brand { padding-right:56px; } .topbar-side { margin-top:16px; align-self:auto; } .language-picker { position:absolute; top:0; right:0; } .toolbar { justify-content:stretch; margin-top:0; } .topbar-side .control,.topbar-side .search { flex:1 1 100%; width:100%; } .search { min-width:0; } .layout { grid-template-columns:1fr; } .genres-panel { order:0; } .list-panel { order:1; } .editor-panel { order:2; } .entry-list { max-height:none; } .curator-dialog-head { flex-wrap:wrap; } .curator-header-actions { flex:1 1 100%; width:100%; margin-left:0; } .curator-actions { justify-content:flex-end; } }
152
+ @media (max-width:680px) { .shell { padding:16px; } .topbar { display:block; position:relative; } .brand { padding-right:56px; } .topbar-side { margin-top:16px; align-self:auto; } .language-picker { position:absolute; top:0; right:0; } .toolbar { justify-content:stretch; margin-top:0; } .topbar-side .control,.topbar-side .search { flex:1 1 100%; width:100%; } .search { min-width:0; } .layout { grid-template-columns:1fr; } .genres-panel { order:0; } .list-panel { order:1; } .editor-panel { order:2; } .entry-list { max-height:none; } .curator-dialog-head { flex-wrap:wrap; } .curator-header-actions { flex:1 1 100%; width:100%; margin-left:0; } .curator-actions { justify-content:flex-end; } .curator-confirm-grid { grid-template-columns:1fr; } .curator-confirm-actions { flex-direction:column-reverse; } .curator-confirm-actions .button { width:100%; } }
132
153
  </style>
133
154
  </head>
134
155
  <body>
@@ -188,11 +209,34 @@ export const WEB_HTML = String.raw `<!doctype html>
188
209
  </div>
189
210
  <div class="panel-body">
190
211
  <p id="curator-description" class="subtitle" data-i18n="curatorDescription">Review reusable knowledge candidates and add them to global memory.</p>
191
- <div id="curator-selection-count" class="curator-selection-count curator-selection-summary">0 selected</div>
212
+ <div id="curator-filters" class="curator-filters" aria-label="Curator filters"></div>
213
+ <div class="curator-selection-summary"><span id="curator-result-count" class="curator-selection-count">0 candidates</span></div>
192
214
  <div id="curator-status" class="status" role="status" aria-live="polite"></div>
193
215
  <div id="curator-list" class="curator-list"></div>
194
216
  </div>
195
217
  </section>
218
+ <div id="curator-confirm-modal" class="curator-confirm-modal" hidden>
219
+ <section id="curator-confirm-panel" class="panel curator-confirm-dialog" role="alertdialog" aria-modal="true" aria-labelledby="curator-confirm-title" aria-describedby="curator-confirm-description">
220
+ <div class="panel-head">
221
+ <h2 id="curator-confirm-title" data-i18n="curatorConfirmTitle">Confirm Global memory addition</h2>
222
+ </div>
223
+ <div class="panel-body">
224
+ <p id="curator-confirm-description" class="subtitle" data-i18n="curatorConfirmDescription">Verify the source, applicability, and complete regenerated draft.</p>
225
+ <dl class="curator-confirm-grid">
226
+ <div class="curator-confirm-field"><dt data-i18n="curatorConfirmSource">Source project</dt><dd id="curator-confirm-source"></dd></div>
227
+ <div class="curator-confirm-field"><dt data-i18n="curatorConfirmSkill">Skill</dt><dd id="curator-confirm-skill"></dd></div>
228
+ <div class="curator-confirm-field"><dt data-i18n="curatorConfirmApplicability">Applicability</dt><dd id="curator-confirm-applicability"></dd></div>
229
+ </dl>
230
+ <h3 data-i18n="curatorConfirmDraft">Draft to add</h3>
231
+ <pre id="curator-confirm-draft" class="curator-confirm-draft"></pre>
232
+ <div id="curator-confirm-status" class="status curator-status" role="status" aria-live="polite"></div>
233
+ <div class="curator-confirm-actions">
234
+ <button id="curator-confirm-cancel" class="button" type="button" data-i18n="curatorCancel">Cancel</button>
235
+ <button id="curator-confirm-submit" class="button primary" type="button" data-i18n="curatorConfirmAction">Add this draft to Global</button>
236
+ </div>
237
+ </div>
238
+ </section>
239
+ </div>
196
240
  </div>
197
241
  <script>
198
242
  const i18n = ${WEB_I18N_CONFIG};
@@ -217,7 +261,8 @@ export const WEB_HTML = String.raw `<!doctype html>
217
261
  const botModes = [
218
262
  ['bot:common', 'bot.common'], ['bot:researcher', 'bot.researcher'], ['bot:builder', 'bot.builder'], ['bot:reviewer', 'bot.reviewer'], ['bot:devops', 'bot.devops'], ['bot:writer', 'bot.writer'], ['bot:analyst', 'bot.analyst']
219
263
  ];
220
- const state = { locale: initialLocale, workspace: '', kind: 'all', tag: '', query: '', entries: [], tags: [], selected: null, runs: [], selectedRun: null, curatorCandidates: [], curatorSelected: new Set(), curatorGlobalized: new Set(), curatorOpen: false, curatorPreviousFocus: null, localizedStatus: null };
264
+ const curatorUrl = new URLSearchParams(location.search);
265
+ const state = { locale: initialLocale, workspace: '', kind: 'all', tag: '', query: '', entries: [], recallItems: [], tags: [], selected: null, selectedRecall: null, runs: [], selectedRun: null, curatorCandidates: [], curatorGlobalized: new Set(), curatorNextCursor: null, curatorTotalApproximate: 0, curatorOpen: false, curatorPreviousFocus: null, curatorPendingCandidate: null, curatorConfirmationTrigger: null, curatorGlobalizeBusy: false, localizedStatus: null, curatorSearch: curatorUrl.get('search') || '', curatorProject: curatorUrl.get('project') || '', curatorTags: new Set(curatorUrl.getAll('tag')), curatorFramework: curatorUrl.get('framework') || '', curatorLanguage: curatorUrl.get('language') || '', curatorMemoryClass: curatorUrl.get('memoryClass') || '', curatorTier: curatorUrl.get('tier') || '', curatorReady: curatorUrl.get('ready') === 'true', curatorIncludeGlobalized: curatorUrl.get('includeGlobalized') === 'true', curatorFacets: null };
221
266
  const $ = (id) => document.getElementById(id);
222
267
  const t = (key, parameters = {}) => {
223
268
  const template = i18n.messages[state.locale]?.[key] ?? i18n.messages[i18n.defaultLocale]?.[key] ?? key;
@@ -230,6 +275,7 @@ export const WEB_HTML = String.raw `<!doctype html>
230
275
  const setI18nText = (element, key) => { element.dataset.i18n = key; element.textContent = t(key); return element; };
231
276
  const labelForKind = (kind) => { const item = kinds.find(([value]) => value === kind); return item ? t(item[1]) : kind; };
232
277
  const labelForStatus = (status) => i18n.messages[state.locale]?.['status.' + status] ?? status;
278
+ const labelForOrigin = (origin) => t(origin === 'ecosystem' ? 'originEcosystem' : origin === 'global' ? 'originGlobal' : 'originProject');
233
279
  const updateEditorState = () => {
234
280
  const element = $('editor-state');
235
281
  if (state.selected) { delete element.dataset.i18n; element.dataset.i18nStatus = state.selected.status; element.textContent = labelForStatus(state.selected.status); }
@@ -298,6 +344,55 @@ export const WEB_HTML = String.raw `<!doctype html>
298
344
  if (blocked) appMain.setAttribute('aria-hidden', 'true');
299
345
  else appMain.removeAttribute('aria-hidden');
300
346
  };
347
+ function restoreCuratorUrl() {
348
+ const params = new URLSearchParams(location.search);
349
+ state.curatorSearch = params.get('search') || '';
350
+ state.curatorProject = params.get('project') || '';
351
+ state.curatorTags = new Set(params.getAll('tag'));
352
+ state.curatorFramework = params.get('framework') || '';
353
+ state.curatorLanguage = params.get('language') || '';
354
+ state.curatorMemoryClass = params.get('memoryClass') || '';
355
+ state.curatorTier = params.get('tier') || '';
356
+ state.curatorReady = params.get('ready') === 'true';
357
+ state.curatorIncludeGlobalized = params.get('includeGlobalized') === 'true';
358
+ }
359
+ function syncCuratorUrl(push = false) {
360
+ const params = new URLSearchParams(location.search);
361
+ for (const key of ['search', 'project', 'framework', 'language', 'memoryClass', 'tier', 'ready', 'includeGlobalized', 'tag']) params.delete(key);
362
+ if (state.curatorSearch) params.set('search', state.curatorSearch);
363
+ if (state.curatorProject) params.set('project', state.curatorProject);
364
+ if (state.curatorFramework) params.set('framework', state.curatorFramework);
365
+ if (state.curatorLanguage) params.set('language', state.curatorLanguage);
366
+ if (state.curatorMemoryClass) params.set('memoryClass', state.curatorMemoryClass);
367
+ if (state.curatorTier) params.set('tier', state.curatorTier);
368
+ if (state.curatorReady) params.set('ready', 'true');
369
+ if (state.curatorIncludeGlobalized) params.set('includeGlobalized', 'true');
370
+ for (const tag of state.curatorTags) params.append('tag', tag);
371
+ const nextUrl = location.pathname + (params.toString() ? '?' + params.toString() : '');
372
+ if (push) history.pushState(null, '', nextUrl); else history.replaceState(null, '', nextUrl);
373
+ }
374
+ function curatorFilterSelect(label, values, value, onChange) {
375
+ const wrapper = document.createElement('label'); wrapper.className = 'curator-filter';
376
+ const text = document.createElement('span'); text.textContent = label;
377
+ const select = document.createElement('select');
378
+ const all = document.createElement('option'); all.value = ''; all.textContent = 'All'; select.append(all);
379
+ for (const item of values || []) { const option = document.createElement('option'); option.value = item.value || item.workspace; option.textContent = item.name || item.value || item.workspace; select.append(option); }
380
+ select.value = value || ''; select.addEventListener('change', () => onChange(select.value)); wrapper.append(text, select); return wrapper;
381
+ }
382
+ function renderCuratorFilters() {
383
+ const root = $('curator-filters'); if (!root) return;
384
+ const facets = state.curatorFacets || {};
385
+ const search = document.createElement('input'); search.type = 'search'; search.placeholder = 'Search candidates'; search.value = state.curatorSearch; search.addEventListener('input', () => { state.curatorSearch = search.value.trim(); clearTimeout(state.curatorSearchTimer); state.curatorSearchTimer = setTimeout(() => { syncCuratorUrl(true); loadCurator(); }, 180); });
386
+ const project = curatorFilterSelect('Project', facets.projects, state.curatorProject, (value) => { state.curatorProject = value; syncCuratorUrl(true); loadCurator(); });
387
+ const framework = curatorFilterSelect('Framework', facets.frameworks, state.curatorFramework, (value) => { state.curatorFramework = value; syncCuratorUrl(true); loadCurator(); });
388
+ const language = curatorFilterSelect('Language', facets.languages, state.curatorLanguage, (value) => { state.curatorLanguage = value; syncCuratorUrl(true); loadCurator(); });
389
+ const memoryClass = curatorFilterSelect('Memory class', facets.memoryClasses, state.curatorMemoryClass, (value) => { state.curatorMemoryClass = value; syncCuratorUrl(true); loadCurator(); });
390
+ const readiness = curatorFilterSelect('Readiness', [{ value: 'portable', name: 'Portable' }, { value: 'repeated', name: 'Repeated' }, { value: 'observed', name: 'Observed' }], state.curatorTier, (value) => { state.curatorTier = value; syncCuratorUrl(true); loadCurator(); });
391
+ const ready = document.createElement('label'); ready.className = 'curator-filter-check'; const readyInput = document.createElement('input'); readyInput.type = 'checkbox'; readyInput.checked = state.curatorReady; readyInput.addEventListener('change', () => { state.curatorReady = readyInput.checked; syncCuratorUrl(true); loadCurator(); }); ready.append(readyInput, document.createTextNode('Skill-ready'));
392
+ const globalized = document.createElement('label'); globalized.className = 'curator-filter-check'; const globalizedInput = document.createElement('input'); globalizedInput.type = 'checkbox'; globalizedInput.checked = state.curatorIncludeGlobalized; globalizedInput.addEventListener('change', () => { state.curatorIncludeGlobalized = globalizedInput.checked; syncCuratorUrl(true); loadCurator(); }); globalized.append(globalizedInput, document.createTextNode('Show already globalized'));
393
+ const tags = document.createElement('div'); tags.className = 'curator-filter-tags'; for (const facet of facets.tags || []) { const button = document.createElement('button'); button.type = 'button'; button.className = 'tag' + (state.curatorTags.has(facet.value) ? ' active' : ''); button.textContent = '#' + facet.value + ' (' + facet.count + ')'; button.addEventListener('click', () => { if (state.curatorTags.has(facet.value)) state.curatorTags.delete(facet.value); else state.curatorTags.add(facet.value); syncCuratorUrl(true); loadCurator(); }); tags.append(button); }
394
+ root.replaceChildren(search, project, framework, language, memoryClass, readiness, ready, globalized, tags);
395
+ }
301
396
  function openCurator() {
302
397
  if (state.curatorOpen) { $('curator-close')?.focus(); return; }
303
398
  state.curatorPreviousFocus = document.activeElement instanceof HTMLElement ? document.activeElement : null;
@@ -309,6 +404,8 @@ export const WEB_HTML = String.raw `<!doctype html>
309
404
  requestAnimationFrame(() => $('curator-close')?.focus());
310
405
  }
311
406
  function closeCurator() {
407
+ if (state.curatorGlobalizeBusy) return;
408
+ if (state.curatorPendingCandidate) closeCuratorConfirmation(false);
312
409
  const modal = $('curator-modal');
313
410
  if (modal.hidden) return;
314
411
  state.curatorOpen = false;
@@ -369,10 +466,34 @@ export const WEB_HTML = String.raw `<!doctype html>
369
466
  if (tagButtons.length > 0) root.append(filterGroup(t('crossTagFilterTitle'), tagButtons));
370
467
  }
371
468
 
469
+ function renderFederatedItems() {
470
+ const list = $('entry-list');
471
+ list.replaceChildren(...state.recallItems.map((item) => {
472
+ const card = document.createElement('article'); card.className = 'entry-card federated-card ' + (item.origin === 'global' ? 'global' : '');
473
+ const meta = document.createElement('div'); meta.className = 'entry-meta';
474
+ const origin = document.createElement('span'); origin.className = 'badge origin-' + item.origin; origin.textContent = labelForOrigin(item.origin);
475
+ const kind = document.createElement('span'); kind.className = 'badge'; kind.textContent = labelForKind(item.kind);
476
+ const status = document.createElement('span'); status.className = 'badge ' + item.status; status.textContent = labelForStatus(item.status);
477
+ meta.append(origin, kind, status);
478
+ const title = document.createElement('h3'); title.textContent = item.title;
479
+ const snippet = document.createElement('p'); snippet.className = 'snippet'; snippet.textContent = item.snippet;
480
+ const source = document.createElement('div'); source.className = 'federated-source';
481
+ const sourceLines = [];
482
+ if (item.sourceWorkspace) sourceLines.push(t('sourceWorkspace', { workspace: item.sourceWorkspace }));
483
+ if (item.sourceProject) sourceLines.push(t('sourceProject', { project: item.sourceProject }));
484
+ source.textContent = sourceLines.join('\n');
485
+ const tags = document.createElement('div'); tags.className = 'tags'; item.tags.forEach((tag) => { const tagItem = document.createElement('span'); tagItem.className = 'tag'; tagItem.textContent = '#' + tag; tags.append(tagItem); });
486
+ const reasons = document.createElement('div'); reasons.className = 'selection-reasons'; reasons.textContent = [t('selectionReasons'), ...item.selectionReasons.map((reason) => '• ' + reason)].join('\n');
487
+ card.append(meta, title, snippet, source, tags, reasons); card.addEventListener('click', () => { state.selected = null; state.selectedRecall = item; renderEntries(); renderEditor(); }); return card;
488
+ }));
489
+ }
490
+
372
491
  function renderEntries() {
373
- $('result-count').textContent = tp('entryCount', state.entries.length);
374
- $('list-title').textContent = state.tag ? '#' + state.tag : (state.kind === 'all' ? t('entriesTitle') : labelForKind(state.kind));
492
+ const displayed = state.recallItems.length > 0 ? state.recallItems.length : state.entries.length;
493
+ $('result-count').textContent = tp('entryCount', displayed);
494
+ $('list-title').textContent = state.recallItems.length > 0 ? t('entriesTitle') : (state.tag ? '#' + state.tag : (state.kind === 'all' ? t('entriesTitle') : labelForKind(state.kind)));
375
495
  const list = $('entry-list');
496
+ if (state.recallItems.length > 0) { renderFederatedItems(); return; }
376
497
  if (!state.entries.length) {
377
498
  const empty = document.createElement('div'); empty.className = 'editor-empty'; setI18nText(empty, 'noEntries'); list.replaceChildren(empty); return;
378
499
  }
@@ -390,7 +511,26 @@ export const WEB_HTML = String.raw `<!doctype html>
390
511
  }));
391
512
  }
392
513
 
514
+ function renderFederatedDetail() {
515
+ const editor = $('editor'); const item = state.selectedRecall;
516
+ if (!item) return;
517
+ const origin = document.createElement('span'); origin.className = 'badge origin-' + item.origin; origin.textContent = labelForOrigin(item.origin);
518
+ const heading = document.createElement('h3'); heading.textContent = item.title;
519
+ const meta = document.createElement('div'); meta.className = 'entry-meta'; meta.append(origin);
520
+ if (item.sourceWorkspace) { const source = document.createElement('span'); source.className = 'badge'; source.textContent = t('sourceWorkspace', { workspace: item.sourceWorkspace }); meta.append(source); }
521
+ if (item.sourceProject) { const source = document.createElement('span'); source.className = 'badge'; source.textContent = t('sourceProject', { project: item.sourceProject }); meta.append(source); }
522
+ const notice = document.createElement('div'); notice.className = 'notice'; notice.textContent = t('federatedReadOnly');
523
+ const detail = document.createElement('div'); detail.className = 'federated-detail';
524
+ const snippet = document.createElement('div'); snippet.className = 'detail-text'; snippet.textContent = item.snippet;
525
+ const reasons = detailBlock('selectionReasons', item.selectionReasons.join('\n'));
526
+ const tags = detailBlock('commaSeparatedTags', item.tags.map((tag) => '#' + tag).join(' '));
527
+ detail.append(snippet, reasons, tags);
528
+ const stateBadge = $('editor-state'); delete stateBadge.dataset.i18n; delete stateBadge.dataset.i18nStatus; stateBadge.textContent = labelForOrigin(item.origin);
529
+ editor.replaceChildren(meta, heading, notice, detail);
530
+ }
531
+
393
532
  function renderEditor() {
533
+ if (state.selectedRecall) { renderFederatedDetail(); return; }
394
534
  const editor = $('editor');
395
535
  const entry = state.selected;
396
536
  updateEditorState();
@@ -418,34 +558,106 @@ export const WEB_HTML = String.raw `<!doctype html>
418
558
  }); editor.replaceChildren(form);
419
559
  }
420
560
 
421
- async function selectEntry(id) { try { const result = await api('/api/entries/' + encodeURIComponent(id) + '?workspace=' + encodeURIComponent(state.workspace)); state.selected = result.entry; renderEntries(); renderEditor(); } catch (error) { setStatus(error.message, true); } }
561
+ async function selectEntry(id) { try { const result = await api('/api/entries/' + encodeURIComponent(id) + '?workspace=' + encodeURIComponent(state.workspace)); state.selectedRecall = null; state.selected = result.entry; renderEntries(); renderEditor(); } catch (error) { setStatus(error.message, true); } }
422
562
 
423
563
  const curatorCandidateKey = (candidate) => candidate.workspace + '\u0000' + candidate.entryId;
424
564
 
425
- async function applySelectedCurator() {
426
- const selected = state.curatorCandidates.filter((candidate) => state.curatorSelected.has(curatorCandidateKey(candidate)) && !state.curatorGlobalized.has(curatorCandidateKey(candidate)));
427
- if (!selected.length) return;
428
- let added = 0;
429
- let failed = 0;
430
- for (const candidate of selected) {
431
- try {
432
- await api('/api/curator/globalize?workspace=' + encodeURIComponent(candidate.workspace), {
433
- method: 'POST',
434
- headers: { 'content-type': 'application/json' },
435
- body: JSON.stringify({ workspace: candidate.workspace, entryId: candidate.entryId, expectedRevision: candidate.revision }),
436
- });
437
- state.curatorGlobalized.add(curatorCandidateKey(candidate));
438
- added += 1;
439
- } catch (error) {
440
- failed += 1;
441
- setStatus(error.message, true);
442
- }
565
+ function setCuratorConfirmationStatus(message, error = false) {
566
+ const element = $('curator-confirm-status');
567
+ element.textContent = message;
568
+ element.className = error ? 'status notice error curator-status' : 'status curator-status';
569
+ }
570
+
571
+ function openCuratorConfirmation(candidate, trigger) {
572
+ if (state.curatorGlobalizeBusy || state.curatorGlobalized.has(curatorCandidateKey(candidate))) return;
573
+ state.curatorPendingCandidate = candidate;
574
+ state.curatorConfirmationTrigger = trigger instanceof HTMLElement ? trigger : null;
575
+ $('curator-confirm-source').textContent = candidate.projectName || candidate.workspace;
576
+ $('curator-confirm-skill').textContent = candidate.skillName;
577
+ $('curator-confirm-applicability').textContent = candidate.applicability ? JSON.stringify(candidate.applicability, null, 2) : t('curatorApplicabilityNone');
578
+ $('curator-confirm-draft').textContent = [
579
+ t('curatorDraftTitle') + '\n' + candidate.draft.title,
580
+ t('curatorDraftSummary') + '\n' + candidate.draft.summary,
581
+ t('curatorDraftBody') + '\n' + candidate.draft.body,
582
+ t('curatorDraftVersion') + '\n' + candidate.draft.version,
583
+ ].join('\n\n');
584
+ setCuratorConfirmationStatus('');
585
+ const panel = $('curator-panel');
586
+ panel.inert = true;
587
+ panel.setAttribute('aria-hidden', 'true');
588
+ const submit = $('curator-confirm-submit');
589
+ submit.disabled = false;
590
+ setI18nText(submit, 'curatorConfirmAction');
591
+ $('curator-confirm-cancel').disabled = false;
592
+ $('curator-confirm-modal').hidden = false;
593
+ requestAnimationFrame(() => submit.focus());
594
+ }
595
+
596
+ function closeCuratorConfirmation(restoreFocus = true) {
597
+ if (state.curatorGlobalizeBusy) return;
598
+ $('curator-confirm-modal').hidden = true;
599
+ const panel = $('curator-panel');
600
+ panel.inert = false;
601
+ panel.removeAttribute('aria-hidden');
602
+ const trigger = state.curatorConfirmationTrigger;
603
+ state.curatorPendingCandidate = null;
604
+ state.curatorConfirmationTrigger = null;
605
+ setCuratorConfirmationStatus('');
606
+ if (restoreFocus) requestAnimationFrame(() => {
607
+ if (trigger?.isConnected) trigger.focus();
608
+ else $('curator-close')?.focus();
609
+ });
610
+ }
611
+
612
+ function handleCuratorConfirmationKeydown(event) {
613
+ event.stopPropagation();
614
+ if (event.key === 'Escape') { event.preventDefault(); closeCuratorConfirmation(); return; }
615
+ if (event.key !== 'Tab') return;
616
+ const panel = $('curator-confirm-panel');
617
+ const focusable = [...panel.querySelectorAll('button:not([disabled]), [tabindex]:not([tabindex="-1"])')];
618
+ if (!focusable.length) { event.preventDefault(); return; }
619
+ const first = focusable[0];
620
+ const last = focusable.at(-1);
621
+ if (event.shiftKey && document.activeElement === first) { event.preventDefault(); last.focus(); }
622
+ else if (!event.shiftKey && document.activeElement === last) { event.preventDefault(); first.focus(); }
623
+ }
624
+
625
+ async function globalizePendingCurator() {
626
+ const candidate = state.curatorPendingCandidate;
627
+ if (!candidate || state.curatorGlobalizeBusy) return;
628
+ state.curatorGlobalizeBusy = true;
629
+ const panel = $('curator-confirm-panel');
630
+ const cancel = $('curator-confirm-cancel');
631
+ const submit = $('curator-confirm-submit');
632
+ panel.setAttribute('aria-busy', 'true');
633
+ cancel.disabled = true;
634
+ submit.disabled = true;
635
+ setI18nText(submit, 'curatorGlobalizing');
636
+ setCuratorConfirmationStatus(t('curatorGlobalizing'));
637
+ try {
638
+ await api('/api/curator/globalize?workspace=' + encodeURIComponent(candidate.workspace), {
639
+ method: 'POST',
640
+ headers: { 'content-type': 'application/json' },
641
+ body: JSON.stringify({ workspace: candidate.workspace, entryId: candidate.entryId, expectedRevision: candidate.revision }),
642
+ });
643
+ state.curatorGlobalized.add(curatorCandidateKey(candidate));
644
+ state.curatorGlobalizeBusy = false;
645
+ closeCuratorConfirmation(false);
646
+ await loadCurator();
647
+ await loadEntries();
648
+ setLocalizedStatus('curatorAdded');
649
+ setCuratorStatus(t('curatorAdded'));
650
+ requestAnimationFrame(() => $('curator-close')?.focus());
651
+ } catch (error) {
652
+ const message = error instanceof Error ? error.message : t('curatorGlobalizeFailed');
653
+ setCuratorConfirmationStatus(t('curatorGlobalizeFailed') + ' ' + message, true);
654
+ } finally {
655
+ state.curatorGlobalizeBusy = false;
656
+ panel.removeAttribute('aria-busy');
657
+ cancel.disabled = false;
658
+ submit.disabled = false;
659
+ setI18nText(submit, 'curatorConfirmAction');
443
660
  }
444
- state.curatorSelected.clear();
445
- await loadCurator();
446
- await loadEntries();
447
- if (added > 0 && failed === 0) { setLocalizedStatus('curatorBatchAdded', { count: added }); setCuratorStatus(t('curatorBatchAdded', { count: added })); }
448
- else if (added > 0) { setLocalizedStatus('curatorBatchPartial', { added, failed }, true); setCuratorStatus(t('curatorBatchPartial', { added, failed }), true); }
449
661
  }
450
662
 
451
663
  function renderCurator() {
@@ -453,25 +665,16 @@ export const WEB_HTML = String.raw `<!doctype html>
453
665
  const modal = $('curator-modal');
454
666
  modal.hidden = false;
455
667
  const root = $('curator-list');
456
- const selectable = state.curatorCandidates.filter((candidate) => !state.curatorGlobalized.has(curatorCandidateKey(candidate)));
457
- $('curator-selection-count').textContent = t('curatorSelected', { count: state.curatorSelected.size });
668
+ $('curator-result-count').textContent = t('curatorResultCount', { count: state.curatorTotalApproximate || state.curatorCandidates.length });
458
669
  const actionRoot = $('curator-actions'); actionRoot.replaceChildren();
459
- const selectAll = document.createElement('button'); selectAll.type = 'button'; selectAll.className = 'button'; setI18nText(selectAll, 'curatorSelectAll'); selectAll.disabled = selectable.length === 0;
460
- selectAll.addEventListener('click', () => { selectable.forEach((candidate) => state.curatorSelected.add(curatorCandidateKey(candidate))); renderCurator(); });
461
- const clearSelection = document.createElement('button'); clearSelection.type = 'button'; clearSelection.className = 'button'; setI18nText(clearSelection, 'curatorClearSelection'); clearSelection.disabled = state.curatorSelected.size === 0;
462
- clearSelection.addEventListener('click', () => { state.curatorSelected.clear(); renderCurator(); });
463
- const apply = document.createElement('button'); apply.type = 'button'; apply.className = 'button primary'; setI18nText(apply, 'curatorApplySelected'); apply.disabled = state.curatorSelected.size === 0;
464
- apply.addEventListener('click', () => applySelectedCurator());
465
- actionRoot.append(selectAll, clearSelection, apply);
670
+ if (state.curatorNextCursor) {
671
+ const more = document.createElement('button'); more.type = 'button'; more.className = 'button'; setI18nText(more, 'curatorLoadMore'); more.addEventListener('click', () => loadCurator(true)); actionRoot.append(more);
672
+ }
466
673
  if (!state.curatorCandidates.length) { const empty = document.createElement('div'); empty.className = 'editor-empty'; setI18nText(empty, 'noCuratorCandidates'); root.replaceChildren(empty); return; }
467
- const cards = state.curatorCandidates.map((candidate) => {
674
+ const cards = state.curatorCandidates.map((candidate, index) => {
468
675
  const card = document.createElement('article'); card.className = 'curator-card';
469
676
  const candidateKey = curatorCandidateKey(candidate);
470
677
  const done = state.curatorGlobalized.has(candidateKey);
471
- const selectLabel = document.createElement('label'); selectLabel.className = 'curator-select';
472
- const checkbox = document.createElement('input'); checkbox.type = 'checkbox'; checkbox.checked = state.curatorSelected.has(candidateKey); checkbox.disabled = done;
473
- checkbox.addEventListener('change', () => { if (checkbox.checked) state.curatorSelected.add(candidateKey); else state.curatorSelected.delete(candidateKey); renderCurator(); });
474
- const selectText = document.createElement('span'); selectText.textContent = done ? t('globalized') : t('curatorSelectSkill'); selectLabel.append(checkbox, selectText);
475
678
  const meta = document.createElement('div'); meta.className = 'entry-meta';
476
679
  const kind = document.createElement('span'); kind.className = 'badge'; kind.textContent = labelForKind(candidate.kind);
477
680
  const score = document.createElement('span'); score.className = 'badge'; score.textContent = t('curatorScore', { score: candidate.score });
@@ -480,6 +683,7 @@ export const WEB_HTML = String.raw `<!doctype html>
480
683
  if (candidate.knowledge && candidate.knowledge.skillReady) { const ready = document.createElement('span'); ready.className = 'badge'; setI18nText(ready, 'curatorSkillReady'); meta.append(ready); }
481
684
  const heading = document.createElement('h3'); heading.textContent = candidate.skillName;
482
685
  const overview = document.createElement('div'); overview.className = 'curator-overview'; overview.textContent = candidate.overview.join('\n');
686
+ const tags = document.createElement('div'); tags.className = 'tags'; (candidate.tags || []).forEach((tag) => { const tagItem = document.createElement('span'); tagItem.className = 'tag'; tagItem.textContent = '#' + tag; tags.append(tagItem); });
483
687
  const knowledge = document.createElement('div'); knowledge.className = 'curator-reasons'; knowledge.textContent = candidate.knowledge ? [
484
688
  t('curatorEvidence', { hits: candidate.knowledge.qualifiedHits, runs: candidate.knowledge.independentRuns, workspaces: candidate.knowledge.independentWorkspaces }),
485
689
  t('curatorSilo', { value: candidate.knowledge.averageCompleteness }),
@@ -506,22 +710,52 @@ export const WEB_HTML = String.raw `<!doctype html>
506
710
  draft.append(label, content);
507
711
  }
508
712
  const reasons = document.createElement('div'); reasons.className = 'curator-reasons'; reasons.textContent = [...(candidate.reasons || []), ...(candidate.warnings || []).map((warning) => t('curatorWarning', { warning }))].join('\n');
509
- card.classList.toggle('selected', checkbox.checked);
510
- card.append(selectLabel, meta, heading, overview, knowledge, draft, reasons); return card;
713
+ const details = document.createElement('div'); details.id = 'curator-review-' + index; details.className = 'curator-review-details'; details.hidden = true; details.append(knowledge, draft, reasons);
714
+ const actions = document.createElement('div'); actions.className = 'curator-card-actions';
715
+ const review = document.createElement('button'); review.type = 'button'; review.className = 'button'; review.setAttribute('aria-controls', details.id); review.setAttribute('aria-expanded', 'false'); setI18nText(review, 'curatorReview');
716
+ review.addEventListener('click', () => { details.hidden = !details.hidden; review.setAttribute('aria-expanded', String(!details.hidden)); setI18nText(review, details.hidden ? 'curatorReview' : 'curatorHideReview'); });
717
+ const globalize = document.createElement('button'); globalize.type = 'button'; globalize.className = 'button primary'; globalize.disabled = done; setI18nText(globalize, done ? 'globalized' : 'curatorGlobalizeCandidate');
718
+ globalize.addEventListener('click', () => openCuratorConfirmation(candidate, globalize));
719
+ actions.append(review, globalize);
720
+ card.append(meta, heading, overview, tags, actions, details); return card;
511
721
  });
512
- root.replaceChildren(toolbar, ...cards);
722
+ root.replaceChildren(...cards);
513
723
  }
514
724
 
515
- async function loadCurator() {
516
- openCurator();
725
+ async function loadCurator(append = false) {
726
+ // Keep the documented legacy URL shape recognizable for clients: /api/curator/candidates?limit=50
727
+ if (!append) {
728
+ openCurator();
729
+ state.curatorCandidates = [];
730
+ state.curatorNextCursor = null;
731
+ state.curatorTotalApproximate = 0;
732
+ } else if (!state.curatorNextCursor) return;
517
733
  const panel = $('curator-panel');
518
734
  panel.setAttribute('aria-busy', 'true');
519
735
  setCuratorStatus(t('curatorLoading'));
520
736
  try {
521
- const result = await api('/api/curator/candidates?limit=50');
522
- state.curatorCandidates = result.candidates || [];
523
- const available = new Set(state.curatorCandidates.map((candidate) => curatorCandidateKey(candidate)));
524
- state.curatorSelected = new Set([...state.curatorSelected].filter((key) => available.has(key)));
737
+ if (!append) {
738
+ const facetParams = new URLSearchParams({ workspace: 'all' });
739
+ if (state.curatorIncludeGlobalized) facetParams.set('includeGlobalized', 'true');
740
+ const facetResult = await api('/api/curator/facets?' + facetParams);
741
+ state.curatorFacets = facetResult.facets || {};
742
+ renderCuratorFilters();
743
+ }
744
+ const params = new URLSearchParams({ workspace: 'all', limit: '50' });
745
+ if (state.curatorProject) params.set('project', state.curatorProject);
746
+ if (state.curatorSearch) params.set('search', state.curatorSearch);
747
+ if (state.curatorFramework) params.set('framework', state.curatorFramework);
748
+ if (state.curatorLanguage) params.set('language', state.curatorLanguage);
749
+ if (state.curatorMemoryClass) params.set('memoryClass', state.curatorMemoryClass);
750
+ if (state.curatorTier) params.set('tier', state.curatorTier);
751
+ if (state.curatorReady) params.set('skillReadyOnly', 'true');
752
+ if (state.curatorIncludeGlobalized) params.set('includeGlobalized', 'true');
753
+ for (const tag of state.curatorTags) params.append('tag', tag);
754
+ if (append && state.curatorNextCursor) params.set('cursor', state.curatorNextCursor);
755
+ const result = await api('/api/curator/candidates?' + params);
756
+ state.curatorCandidates = append ? [...state.curatorCandidates, ...(result.candidates || [])] : (result.candidates || []);
757
+ state.curatorNextCursor = result.nextCursor || null;
758
+ state.curatorTotalApproximate = Number(result.totalApproximate || state.curatorCandidates.length);
525
759
  setCuratorStatus('');
526
760
  renderCurator();
527
761
  }
@@ -582,7 +816,29 @@ export const WEB_HTML = String.raw `<!doctype html>
582
816
  } catch (error) { setStatus(error.message, true); }
583
817
  }
584
818
 
585
- async function loadEntries() { if (!state.workspace) return; try { const params = new URLSearchParams({ workspace: state.workspace }); if (state.kind !== 'all') params.set('kind', state.kind); if (state.tag) params.set('tag', state.tag); if (state.query) params.set('q', state.query); const result = await api('/api/entries?' + params); state.entries = result.entries; if (state.selected && !state.entries.some((entry) => entry.id === state.selected.id)) state.selected = null; renderEntries(); renderFilters(); if (!state.selected && state.entries[0]) await selectEntry(state.entries[0].id); else renderEditor(); setLocalizedCountStatus('displayedCount', result.entries.length); } catch (error) { setStatus(error.message, true); } }
819
+ async function loadEntries() {
820
+ if (!state.workspace) return;
821
+ try {
822
+ const params = new URLSearchParams({ workspace: state.workspace });
823
+ if (state.kind !== 'all') params.set('kind', state.kind);
824
+ if (state.tag) params.set('tag', state.tag);
825
+ if (state.query) params.set('q', state.query);
826
+ const result = await api('/api/entries?' + params);
827
+ state.entries = result.entries;
828
+ state.recallItems = [];
829
+ state.selectedRecall = null;
830
+ if (state.query && state.kind === 'all' && !state.tag) {
831
+ const recallParams = new URLSearchParams({ workspace: state.workspace, q: state.query, limit: '100' });
832
+ const recalled = await api('/api/memory/recall?' + recallParams);
833
+ state.recallItems = recalled.combined?.items || [];
834
+ state.selected = null;
835
+ } else if (state.selected && !state.entries.some((entry) => entry.id === state.selected.id)) state.selected = null;
836
+ renderEntries(); renderFilters();
837
+ if (!state.recallItems.length && !state.selected && state.entries[0]) await selectEntry(state.entries[0].id);
838
+ else renderEditor();
839
+ setLocalizedCountStatus('displayedCount', state.recallItems.length || result.entries.length);
840
+ } catch (error) { setStatus(error.message, true); }
841
+ }
586
842
  async function loadTags() { if (!state.workspace) return; try { const result = await api('/api/tags?workspace=' + encodeURIComponent(state.workspace)); state.tags = result.tags; renderFilters(); } catch (error) { setStatus(error.message, true); } }
587
843
  async function loadWorkspaces() { try { const result = await api('/api/workspaces'); const select = $('workspace'); select.replaceChildren(...result.workspaces.map((item) => { const option = document.createElement('option'); option.value = item.workspace; option.textContent = (item.displayName || item.workspace) + ' (' + item.count + ')'; return option; })); if (!state.workspace && result.workspaces[0]) state.workspace = result.workspaces[0].workspace; select.value = state.workspace; if (state.workspace) { await loadTags(); await loadEntries(); await loadRuns(); } if (state.curatorOpen) await loadCurator(); else if (!state.workspace) setLocalizedStatus('noWorkspace', {}, true); } catch (error) { setStatus(error.message, true); } }
588
844
  $('language-toggle').addEventListener('click', () => {
@@ -606,10 +862,14 @@ export const WEB_HTML = String.raw `<!doctype html>
606
862
  });
607
863
  document.addEventListener('click', (event) => { if (!$('language-picker')?.contains(event.target)) setLanguageMenuOpen(false); });
608
864
  document.addEventListener('focusin', (event) => { if (!$('language-picker')?.contains(event.target)) setLanguageMenuOpen(false); });
609
- $('workspace').addEventListener('change', (event) => { if (state.curatorOpen) closeCurator(); state.workspace = event.target.value; state.selected = null; state.selectedRun = null; state.runs = []; state.tag = ''; state.curatorCandidates = []; state.curatorSelected = new Set(); state.curatorGlobalized = new Set(); state.curatorOpen = false; loadTags().then(loadEntries).then(loadRuns); });
865
+ $('workspace').addEventListener('change', (event) => { if (state.curatorOpen) closeCurator(); state.workspace = event.target.value; state.selected = null; state.selectedRecall = null; state.selectedRun = null; state.runs = []; state.tag = ''; state.curatorCandidates = []; state.curatorGlobalized = new Set(); state.curatorNextCursor = null; state.curatorTotalApproximate = 0; state.curatorOpen = false; loadTags().then(loadEntries).then(loadRuns); });
610
866
  $('curator-button').addEventListener('click', () => loadCurator());
611
867
  $('curator-close').addEventListener('click', () => closeCurator());
868
+ $('curator-confirm-cancel').addEventListener('click', () => closeCuratorConfirmation());
869
+ $('curator-confirm-submit').addEventListener('click', () => globalizePendingCurator());
870
+ $('curator-confirm-modal').addEventListener('keydown', handleCuratorConfirmationKeydown);
612
871
  $('curator-modal').addEventListener('keydown', handleCuratorKeydown);
872
+ window.addEventListener('popstate', () => { restoreCuratorUrl(); if (state.curatorOpen) loadCurator(); });
613
873
  $('refresh').addEventListener('click', () => loadWorkspaces());
614
874
  let searchTimer; $('search').addEventListener('input', (event) => { clearTimeout(searchTimer); state.query = event.target.value.trim(); searchTimer = setTimeout(() => loadEntries(), 180); });
615
875
  applyTranslations();
@@ -1 +1 @@
1
- {"version":3,"file":"ui.js","sourceRoot":"","sources":["../../src/web/ui.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAE7F,MAAM,eAAe,GAAG,IAAI,CAAC,SAAS,CAAC;IACrC,aAAa,EAAE,kBAAkB;IACjC,YAAY,EAAE,iBAAiB;IAC/B,OAAO,EAAE,WAAW;IACpB,QAAQ,EAAE,YAAY;CACvB,CAAC;KACC,UAAU,CAAC,GAAG,EAAE,SAAS,CAAC;KAC1B,UAAU,CAAC,GAAG,EAAE,SAAS,CAAC;KAC1B,UAAU,CAAC,GAAG,EAAE,SAAS,CAAC;KAC1B,UAAU,CAAC,QAAQ,EAAE,SAAS,CAAC;KAC/B,UAAU,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;AAEnC,MAAM,CAAC,MAAM,QAAQ,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;mBAyLf,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAqa1B,CAAC"}
1
+ {"version":3,"file":"ui.js","sourceRoot":"","sources":["../../src/web/ui.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAE7F,MAAM,eAAe,GAAG,IAAI,CAAC,SAAS,CAAC;IACrC,aAAa,EAAE,kBAAkB;IACjC,YAAY,EAAE,iBAAiB;IAC/B,OAAO,EAAE,WAAW;IACpB,QAAQ,EAAE,YAAY;CACvB,CAAC;KACC,UAAU,CAAC,GAAG,EAAE,SAAS,CAAC;KAC1B,UAAU,CAAC,GAAG,EAAE,SAAS,CAAC;KAC1B,UAAU,CAAC,GAAG,EAAE,SAAS,CAAC;KAC1B,UAAU,CAAC,QAAQ,EAAE,SAAS,CAAC;KAC/B,UAAU,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;AAEnC,MAAM,CAAC,MAAM,QAAQ,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;mBAqOf,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QA6nB1B,CAAC"}
@@ -0,0 +1,46 @@
1
+ -- Federated retrieval metadata. Existing structured scopes remain valid and
2
+ -- legacy project entries stay project-only unless explicit applicability is
3
+ -- present; no entry rows are rewritten by this migration.
4
+ CREATE TABLE repository_fingerprints (
5
+ repository_id TEXT PRIMARY KEY
6
+ REFERENCES repositories(repository_id)
7
+ ON DELETE CASCADE,
8
+ fingerprint_json TEXT NOT NULL,
9
+ manifest_digest TEXT NOT NULL,
10
+ updated_at TEXT NOT NULL
11
+ );
12
+
13
+ CREATE INDEX IF NOT EXISTS idx_entry_search_signals_type_value_entry
14
+ ON entry_search_signals(signal_type, normalized_value, entry_id);
15
+
16
+ CREATE INDEX IF NOT EXISTS idx_entries_workspace_status
17
+ ON entries(workspace, status);
18
+
19
+ -- Version 2 only allowed project/global delivery origins. Rebuild the small
20
+ -- projection table so context deliveries can retain ecosystem provenance.
21
+ CREATE TABLE context_delivery_entries_federated (
22
+ delivery_id TEXT NOT NULL REFERENCES context_deliveries(delivery_id) ON DELETE CASCADE,
23
+ entry_id TEXT NOT NULL,
24
+ entry_revision INTEGER NOT NULL CHECK (entry_revision > 0),
25
+ rank INTEGER NOT NULL CHECK (rank > 0),
26
+ score_components_json TEXT NOT NULL,
27
+ selection_reason_json TEXT NOT NULL,
28
+ origin_scope TEXT NOT NULL DEFAULT 'project' CHECK (origin_scope IN ('project', 'ecosystem', 'global')),
29
+ PRIMARY KEY (delivery_id, entry_id),
30
+ FOREIGN KEY (entry_id, entry_revision)
31
+ REFERENCES entry_revisions(entry_id, revision)
32
+ );
33
+
34
+ INSERT INTO context_delivery_entries_federated (
35
+ delivery_id, entry_id, entry_revision, rank,
36
+ score_components_json, selection_reason_json, origin_scope
37
+ )
38
+ SELECT delivery_id, entry_id, entry_revision, rank,
39
+ score_components_json, selection_reason_json, origin_scope
40
+ FROM context_delivery_entries;
41
+
42
+ DROP TABLE context_delivery_entries;
43
+ ALTER TABLE context_delivery_entries_federated RENAME TO context_delivery_entries;
44
+
45
+ CREATE INDEX idx_context_delivery_entries_entry
46
+ ON context_delivery_entries(entry_id, entry_revision);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@askdkc/kiokuko",
3
- "version": "0.1.16",
3
+ "version": "0.1.17",
4
4
  "description": "Model-agnostic external memory for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {
@@ -36,11 +36,13 @@
36
36
  "@modelcontextprotocol/sdk": "^1.30.0",
37
37
  "commander": "^14.0.1",
38
38
  "jsonc-parser": "^3.3.1",
39
+ "semver": "^7.8.5",
39
40
  "yaml": "^2.9.0",
40
41
  "zod": "^4.4.3"
41
42
  },
42
43
  "devDependencies": {
43
44
  "@types/node": "^24.0.0",
45
+ "@types/semver": "^7.8.0",
44
46
  "tsx": "^4.20.3",
45
47
  "typescript": "^5.9.2"
46
48
  }