@cachly-dev/mcp-server 0.10.117 → 0.10.119

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 (44) hide show
  1. package/README.md +9 -5
  2. package/dist/src/ambient-cli.d.ts +85 -0
  3. package/dist/src/ambient-cli.js +199 -0
  4. package/dist/src/ambient-cli.js.map +1 -0
  5. package/dist/src/ambient-hooks.d.ts +128 -0
  6. package/dist/src/ambient-hooks.js +269 -0
  7. package/dist/src/ambient-hooks.js.map +1 -0
  8. package/dist/src/ambient-ledger.d.ts +19 -0
  9. package/dist/src/ambient-ledger.js +95 -0
  10. package/dist/src/ambient-ledger.js.map +1 -0
  11. package/dist/src/ambient-recall.d.ts +59 -0
  12. package/dist/src/ambient-recall.js +96 -0
  13. package/dist/src/ambient-recall.js.map +1 -0
  14. package/dist/src/doctor.d.ts +47 -0
  15. package/dist/src/doctor.js +247 -0
  16. package/dist/src/doctor.js.map +1 -0
  17. package/dist/src/editor.d.ts +9 -0
  18. package/dist/src/editor.js +63 -0
  19. package/dist/src/editor.js.map +1 -0
  20. package/dist/src/first-contact.d.ts +56 -0
  21. package/dist/src/first-contact.js +108 -0
  22. package/dist/src/first-contact.js.map +1 -0
  23. package/dist/src/funnel-milestones.d.ts +5 -0
  24. package/dist/src/funnel-milestones.js +48 -0
  25. package/dist/src/funnel-milestones.js.map +1 -0
  26. package/dist/src/handlers/brain.d.ts +1 -0
  27. package/dist/src/handlers/brain.js +45 -8
  28. package/dist/src/handlers/brain.js.map +1 -1
  29. package/dist/src/handlers/fedbrain.js +48 -16
  30. package/dist/src/handlers/fedbrain.js.map +1 -1
  31. package/dist/src/handlers/syndicate.js +91 -1
  32. package/dist/src/handlers/syndicate.js.map +1 -1
  33. package/dist/src/handlers/team.d.ts +10 -0
  34. package/dist/src/handlers/team.js +45 -1
  35. package/dist/src/handlers/team.js.map +1 -1
  36. package/dist/src/index.d.ts +27 -5
  37. package/dist/src/index.js +293 -28
  38. package/dist/src/index.js.map +1 -1
  39. package/dist/src/tools.d.ts +5 -0
  40. package/dist/src/tools.js +12 -1
  41. package/dist/src/tools.js.map +1 -1
  42. package/glama.json +1 -1
  43. package/package.json +2 -2
  44. package/server.json +4 -4
@@ -2,13 +2,20 @@ import { execSync } from 'node:child_process';
2
2
  import { calculateConfidence, confidenceBadge, STRUCTURED_TEMPLATES, CONFIDENCE_WARN_VALUE, CONFIDENCE_STALE_VALUE, CONFIDENCE_WARN_DAYS } from '../confidence.js';
3
3
  import { ckgSlug, extractProblemConcept, ckgUpsertNode, ckgUpdateEdge, ckgUpsertPersonNode, ckgUpsertFileNode, ckgRecordCollaboration, ckgUpsertServiceNode } from '../ckg.js';
4
4
  import { safeJsonParse, scanKeys } from '../utils.js';
5
- import { getRole, ROLE_BADGE, getScopes, lessonVisibleToScope } from './team.js';
5
+ import { getRole, ROLE_BADGE, getScopes, lessonVisibleToScope, reviewModeEnabled, storeLessonProposal } from './team.js';
6
6
  import { keywordSearch, tokenize, splitMultiQuery, levenshtein, indexVocab as _indexVocab } from '../search.js';
7
7
  import { rerankByQuality } from '../rerank.js';
8
8
  import { computeEmbedding, hasEmbedProvider } from '../embeddings.js';
9
9
  // ── Changelog (shown once per version in session_start) ──────────────────────
10
- const MCP_VERSION = '0.10.115';
10
+ const MCP_VERSION = '0.10.119';
11
11
  const WHATS_NEW = {
12
+ '0.10.119': [
13
+ `🔍 **Explainable briefings — provenance in every warning**`,
14
+ ` 📎 \`/briefing\` warnings now carry \`outcome\`, \`author\`, \`learned_at\` and \`matched_on\` (the context tokens that triggered the warning) — editors can show WHY a fix hint fired, not just what it claims.`,
15
+ ` 🧰 VS Code 0.12.0 uses this: severity + confidence in the toast, full lesson card with provenance, "Not helpful" per-file suppression, reviewable CLS auto-learns.`,
16
+ ` 🧹 CI docs now describe exactly what ships: scan mode uses the REST \`/scan\` endpoint, the GitLab template covers learn/scan/confirm.`,
17
+ ` 📊 126 MCP tools`,
18
+ ],
12
19
  '0.10.115': [
13
20
  `🔧 **\`brain_get_pref\` + \`brain_set_pref\` — persistent Brain preferences**`,
14
21
  ` 📖 \`brain_get_pref\` reads back any stored preference (omit key to list all).`,
@@ -402,6 +409,21 @@ export async function handleBrainTool(name, args, getConnection, apiFetch) {
402
409
  const { instance_id, topic, outcome, what_worked, what_failed = '', context: ctx = '', severity = 'major', file_paths = [], commands = [], tags = [], depends_on = [], author = '', visibility = 'team', service = '', service_kind = 'service', group = '', } = args;
403
410
  const redis = await getConnection(instance_id);
404
411
  const ts = new Date().toISOString();
412
+ // ── Lessons-Review workflow (P1-1) ─────────────────────────────────────
413
+ // When the org enabled pending-mode on this (shared) brain, team-visible
414
+ // lessons become proposals awaiting owner/admin approval instead of
415
+ // landing in the recall keyspace. Private notes stay direct — they never
416
+ // surface in team recall anyway. Flag absent = direct write, as before.
417
+ if (visibility !== 'private' && await reviewModeEnabled(redis, instance_id)) {
418
+ return storeLessonProposal(redis, topic, {
419
+ topic, outcome, what_worked, what_failed, context: ctx, severity,
420
+ file_paths, commands, tags, depends_on,
421
+ ...(author ? { author } : {}),
422
+ ...(service ? { service } : {}),
423
+ ...(group ? { group: String(group).toLowerCase().trim() } : {}),
424
+ visibility, recall_count: 0, ts, confidence: 1.0, version: 3,
425
+ });
426
+ }
405
427
  // ── Structured template hints ──────────────────────────────────────────
406
428
  const category = topic.split(':')[0];
407
429
  const template = STRUCTURED_TEMPLATES[category];
@@ -416,12 +438,14 @@ export async function handleBrainTool(name, args, getConnection, apiFetch) {
416
438
  // ── Deduplication + audit trail ────────────────────────────────────────
417
439
  let isUpdate = false;
418
440
  let recallCount = 0;
441
+ let lastRecalledAt;
419
442
  let auditTrail = [];
420
443
  const existingRaw = await redis.get(`cachly:lesson:best:${topic}`);
421
444
  if (existingRaw) {
422
445
  try {
423
446
  const prev = JSON.parse(existingRaw);
424
447
  recallCount = prev.recall_count ?? 0;
448
+ lastRecalledAt = prev.last_recalled_at; // learn never stamps it — only recall does
425
449
  auditTrail = prev.audit_trail ?? [];
426
450
  auditTrail.push({ ts, action: 'updated', prev_outcome: prev.outcome });
427
451
  if (auditTrail.length > 20)
@@ -521,6 +545,7 @@ export async function handleBrainTool(name, args, getConnection, apiFetch) {
521
545
  ...(group ? { group: String(group).toLowerCase().trim() } : {}),
522
546
  visibility,
523
547
  recall_count: recallCount,
548
+ ...(lastRecalledAt ? { last_recalled_at: lastRecalledAt } : {}),
524
549
  ts,
525
550
  verified_at: outcome === 'success' || outcome === 'partial' ? ts : undefined,
526
551
  confidence: 1.0,
@@ -788,11 +813,14 @@ export async function handleBrainTool(name, args, getConnection, apiFetch) {
788
813
  const ref = lesson.verified_at ?? lesson.ts;
789
814
  const ageDays = (Date.now() - new Date(ref).getTime()) / 86400000;
790
815
  const badge = confidenceBadge(confidence, ageDays);
791
- // Recall resets verified_at (confidence clock restart)
816
+ // Recall resets verified_at (confidence clock restart) and stamps
817
+ // last_recalled_at so the recall-quality dashboard can measure recency.
818
+ const recalledAt = new Date().toISOString();
792
819
  const updatedLesson = {
793
820
  ...lesson,
794
821
  recall_count: (lesson.recall_count ?? 0) + 1,
795
- verified_at: new Date().toISOString(),
822
+ verified_at: recalledAt,
823
+ last_recalled_at: recalledAt,
796
824
  confidence: 1.0,
797
825
  };
798
826
  await redis.set(`cachly:lesson:best:${topic}`, JSON.stringify(updatedLesson));
@@ -837,7 +865,9 @@ export async function handleBrainTool(name, args, getConnection, apiFetch) {
837
865
  trustBadge,
838
866
  `${badge} **Best solution for \`${topic}\`** ${sevEmoji}${lesson.severity ? ` (${lesson.severity})` : ''} · recalled ${updatedLesson.recall_count}×`,
839
867
  ``,
840
- `**What worked:** ${lesson.what_worked}`,
868
+ // what_worked may be empty for unresolved failure lessons (the API
869
+ // accepts empty what_worked when outcome=failure) — skip the line.
870
+ lesson.what_worked ? `**What worked:** ${lesson.what_worked}` : '',
841
871
  lesson.what_failed ? `**What failed (avoid this):** ${lesson.what_failed}` : '',
842
872
  lesson.context ? `**Context:** ${lesson.context}` : '',
843
873
  (lesson.file_paths ?? []).length > 0 ? `**Files:** ${(lesson.file_paths ?? []).map((f) => `\`${f}\``).join(', ')}` : '',
@@ -906,6 +936,7 @@ export async function handleBrainTool(name, args, getConnection, apiFetch) {
906
936
  // Exclude archived lessons — they are kept for audit but must not surface in recall.
907
937
  const lessonMatches = kwMatches.filter(m => m.key.startsWith('cachly:lesson:best:'));
908
938
  let crossAuthorThisCall = 0;
939
+ let successRecallsThisCall = 0;
909
940
  for (const m of lessonMatches.slice(0, 5)) {
910
941
  const existing = await redis.get(m.key).catch(() => null);
911
942
  if (existing) {
@@ -914,7 +945,8 @@ export async function handleBrainTool(name, args, getConnection, apiFetch) {
914
945
  if (lesson?.state === 'archived')
915
946
  continue;
916
947
  if (lesson) {
917
- const updated = { ...lesson, recall_count: (lesson.recall_count ?? 0) + 1, verified_at: new Date().toISOString() };
948
+ const recalledAt = new Date().toISOString();
949
+ const updated = { ...lesson, recall_count: (lesson.recall_count ?? 0) + 1, verified_at: recalledAt, last_recalled_at: recalledAt };
918
950
  redis.set(m.key, JSON.stringify(updated)).catch(() => { });
919
951
  const sev = lesson.severity;
920
952
  const savedMins = sev === 'critical' ? 240 : sev === 'major' ? 60 : 30;
@@ -934,6 +966,8 @@ export async function handleBrainTool(name, args, getConnection, apiFetch) {
934
966
  ckgUpdateEdge(redis, reqPersonId, 'collaborates', authPersonId, true).catch(() => { });
935
967
  ckgUpdateEdge(redis, authPersonId, 'collaborates', reqPersonId, true).catch(() => { });
936
968
  }
969
+ if (lesson.outcome === 'success')
970
+ successRecallsThisCall++;
937
971
  // Surface banner for proven successes (recall_count >= 1 means it's been validated)
938
972
  if (lesson.outcome === 'success' && (lesson.recall_count ?? 0) >= 1) {
939
973
  savedHere.push({
@@ -949,8 +983,11 @@ export async function handleBrainTool(name, args, getConnection, apiFetch) {
949
983
  }
950
984
  }
951
985
  // Metric 1 (time-to-first-recall): stamp the first time recall returned a
952
- // proven lesson. SET NX so only the first successful recall wins.
953
- if (savedHere.length > 0) {
986
+ // successful lesson. SET NX so only the first successful recall wins.
987
+ // Deliberately NOT gated on savedHere: savedHere requires a pre-existing
988
+ // recall_count >= 1, which would delay the stamp until the SECOND recall
989
+ // and inflate every TTFR percentile derived from it.
990
+ if (successRecallsThisCall > 0) {
954
991
  redis.set(`cachly:stats:first_recall_at:${instance_id}`, new Date().toISOString(), 'EX', 365 * 86400, 'NX').catch(() => { });
955
992
  }
956
993
  // ── Layer 2: Semantic search (parallel, optional) ────────────────────────