ai-lens 0.8.115 → 0.8.117

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/.commithash CHANGED
@@ -1 +1 @@
1
- 3067749
1
+ 28976f3
package/CHANGELOG.md CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  History of changes to the `ai-lens` CLI package on npm. New entries go on top. Format: `## X.Y.Z — YYYY-MM-DD`, followed by user-facing bullets.
4
4
 
5
+ ## 0.8.117 — 2026-07-01
6
+ - fix(team-memory): never inject team memory in projects outside your `projects` filter — an unrelated repo now gets nothing, even if it's on your machine
7
+
8
+ ## 0.8.116 — 2026-07-01
9
+ - fix(team-memory): only inject memory that is genuinely relevant to your prompt — a vague or off-topic prompt now injects nothing, instead of dumping arbitrary team conventions
10
+
5
11
  ## 0.8.115 — 2026-07-01
6
12
  - feat(team-memory): inject relevant team memory per prompt (matched to what you're asking) instead of a blind dump at session start
7
13
  - feat(team-memory): injected memory carries a hint to report it as wrong/outdated via the `ai_lens_memory_report_wrong` MCP tool
package/client/capture.js CHANGED
@@ -1745,13 +1745,14 @@ export function maybeEmitSessionStartMemoryIndex(primary, { now = Date.now() } =
1745
1745
  // exactly why the number is deferred to live tuning, not treated as final. Bounded
1746
1746
  // blast radius (budget below + phase-N unshown-gate) keeps a mis-tuned floor from
1747
1747
  // flooding — worst case is 1–2 extra short memories, never a dump.
1748
- const MEMORY_MATCH_MIN_SCORE = 1.5;
1749
- // Budget: never inject more than this many REPO matches per prompt (keeps a prompt
1750
- // from being flooded; team-general in phase-1 is a small separate allowance).
1748
+ // Calibrated on live prod pairs (analytics pilot, 2026-07): genuine matches score
1749
+ // ≫3 (specific-term prompts hit 3–13), noise clusters at 1.5–2.0. 3.0 cleanly
1750
+ // separates them precision over recall BY DESIGN: silence beats a context-rotting
1751
+ // near-miss. Tunable down with PostHog match data if it proves too quiet.
1752
+ const MEMORY_MATCH_MIN_SCORE = 3.0;
1753
+ // Budget: never inject more than this many memories per prompt (repo OR team-general
1754
+ // alike) — keeps a prompt light even on a broad match.
1751
1755
  const MEMORY_INJECT_BUDGET = 2;
1752
- // Phase-1 team-general allowance (cross-repo conventions) — kept small so the very
1753
- // first prompt stays light (total phase-1 items ≈ budget + this ≤ ~3–4).
1754
- const MEMORY_PHASE1_TEAM_GENERAL = 2;
1755
1756
  const MEMORY_INJECT_MAX_LEN = 1500;
1756
1757
  const MEMORY_INJECT_HEADER = 'AI Lens team memory — relevant to this prompt (full text via ai_lens_memory_read):';
1757
1758
  // Bound the per-session shown-set (safety valve; sessions are naturally bounded).
@@ -1852,6 +1853,14 @@ export function maybeEmitUserPromptMemoryInject(primary, { now = Date.now() } =
1852
1853
  // Per-person opt-out (client-side, privacy-first): no render, no annotation.
1853
1854
  if (existsSync(MEMORY_OPT_OUT_PATH)) return NONE;
1854
1855
 
1856
+ // Respect the projects filter. The primed pool is machine-GLOBAL, but a match
1857
+ // must NOT surface in an UNTRACKED project — an unrelated repo the user doesn't
1858
+ // monitor for AI Lens (e.g. a GPU/ASR build that has nothing to do with the team).
1859
+ // This mirrors the project_filter drop the caller applies to event shipping; it
1860
+ // just runs earlier here because the inject is emitted BEFORE that drop. No filter
1861
+ // / no project_path → monitored (inject allowed — unchanged for single-project users).
1862
+ if (!isProjectMonitored(primary.project_path, primary.workspace_roots, getMonitoredProjects())) return NONE;
1863
+
1855
1864
  // Load the primed pool. Empty/absent cache = not eligible (server only primes
1856
1865
  // for treatment ∧ not opt-out) ⇒ this is a clean no-op. Also enforce the same
1857
1866
  // freshness TTL the SessionStart render used (an offline client must not match
@@ -1876,41 +1885,23 @@ export function maybeEmitUserPromptMemoryInject(primary, { now = Date.now() } =
1876
1885
 
1877
1886
  if (!promptText) { commitCount(); return NONE; }
1878
1887
 
1879
- const repo = entries.filter(e => e && e.delivery !== 'team');
1880
- const teamGeneral = entries.filter(e => e && e.delivery === 'team');
1881
-
1882
1888
  // Score with the local BM25-lite scorer over the whole pool.
1883
1889
  const scored = scoreCandidates(promptText, entries);
1884
1890
  const byId = new Map(entries.map(e => [e.id, e]));
1885
1891
  const scoreOf = new Map(scored.map(s => [s.id, s.score]));
1886
1892
 
1887
- let picked = []; // entries to inject
1888
- let hadTeamGeneral = false;
1889
-
1890
- if (promptIndex === 0) {
1891
- // Phase-1: strongest intent signaltop repo matches by score (≥ threshold)
1892
- // PLUS team-general (cross-repo conventions are always apt on the first prompt,
1893
- // regardless of BM25). Keep total light.
1894
- const topRepo = scored
1895
- .filter(s => s.score >= MEMORY_MATCH_MIN_SCORE)
1896
- .map(s => byId.get(s.id))
1897
- .filter(e => e && e.delivery !== 'team')
1898
- .slice(0, MEMORY_INJECT_BUDGET);
1899
- const tg = teamGeneral.slice(0, MEMORY_PHASE1_TEAM_GENERAL);
1900
- hadTeamGeneral = tg.length > 0;
1901
- // De-dupe (a team-general could also appear in topRepo if mis-tagged).
1902
- const seen = new Set();
1903
- picked = [...topRepo, ...tg].filter(e => e && !seen.has(e.id) && seen.add(e.id));
1904
- } else {
1905
- // Phase-N: surgical — only NEW (unshown) candidates crossing the threshold to
1906
- // THIS prompt. No blanket team-general re-inject (already shown in phase-1).
1907
- picked = scored
1908
- .filter(s => s.score >= MEMORY_MATCH_MIN_SCORE && !shown[s.id])
1909
- .map(s => byId.get(s.id))
1910
- .filter(Boolean)
1911
- .slice(0, MEMORY_INJECT_BUDGET);
1912
- hadTeamGeneral = picked.some(e => e.delivery === 'team');
1913
- }
1893
+ // Relevance-gated selection (UNIFORM across phases): inject ONLY candidates —
1894
+ // repo OR team-general — that clear MEMORY_MATCH_MIN_SCORE for THIS prompt and
1895
+ // were not already shown this session. NO unconditional team-general dump: a
1896
+ // low-signal prompt ("давай делай", "continue") scores ~0 → injects NOTHING.
1897
+ // Precision over recall BY DESIGN silence beats context-rot. `promptIndex`
1898
+ // still advances via commitCount() for telemetry, but no longer gates selection.
1899
+ const picked = scored
1900
+ .filter(s => s.score >= MEMORY_MATCH_MIN_SCORE && !shown[s.id])
1901
+ .map(s => byId.get(s.id))
1902
+ .filter(Boolean)
1903
+ .slice(0, MEMORY_INJECT_BUDGET);
1904
+ const hadTeamGeneral = picked.some(e => e && e.delivery === 'team');
1914
1905
 
1915
1906
  if (picked.length === 0) { commitCount(); return NONE; }
1916
1907
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-lens",
3
- "version": "0.8.115",
3
+ "version": "0.8.117",
4
4
  "type": "module",
5
5
  "description": "Centralized session analytics for AI coding tools",
6
6
  "bin": {