Skip to content

fix(recall): resolve 7 knowledge retrieval bugs (#79-#85) - #91

Merged
jeff-r2026 merged 2 commits into
Tencent:mainfrom
m0Nst3r873:fix/recall-bugs
Jul 1, 2026
Merged

fix(recall): resolve 7 knowledge retrieval bugs (#79-#85)#91
jeff-r2026 merged 2 commits into
Tencent:mainfrom
m0Nst3r873:fix/recall-bugs

Conversation

@m0Nst3r873

@m0Nst3r873 m0Nst3r873 commented Jul 1, 2026

Copy link
Copy Markdown
Collaborator

Summary

Fixes 7 recall/knowledge-graph bugs reported in #79-#85, plus reviewer cleanup.

#79--depth parameter now effective

depth Budget Behavior
route 1500 tokens Title + path only
context 5000 (default) ~300-char keyword snippet
lookup 20000 Full content per result

Snippet field added to formatResults output — no truncation applied (length controlled by depth budget in queryCodeKnowledge).

#80 — codebase score normalization

Replace min-max (top result always = 10) with log-dampening:
Math.min(10, Math.log2(score + 1) * 2) — preserves relative ordering without pinning.

#81hasWiki path

Use resolved wikiRoot (from team-repo config) instead of hardcoded cwd/teamwiki.

#82buildConfidence semantics

Change from arithmetic mean (more evidence → lower score) to max(weights) — strongest evidence determines confidence. This is a deliberate design change: multiple supporting factors should not dilute confidence.

#83 — search-index shrink protection

Relax from "skip if new < 50% of old" to "skip only if new is empty". Allows legitimate file deletions to be reflected in the index.

#84 — codebase graph retrieval

  • 84-1: loadWikiPages now recurses into subdirectories via collectMdFiles
  • 84-2/4: Unified tokenizer (src/utils/tokenizer.ts) shared by search-index and code-knowledge-recall. Adds camelCase splitting to search-index. Truncates input to 100K chars to prevent Intl.Segmenter OOM.
  • OOM fix: PageDoc.tokens replaced with uniqueTokens: Set<string> built via the shared tokenize function (same tokenizer for index-time and query-time)

#85 — auto-recall upvote scope

Use autoDetectInit() instead of requireInit() so votes are written to the correct scope (project or user).

Additional cleanup

  • SEARCH_INDEX_VERSION bumped 4→5 (triggers rebuild for camelCase tokenizer change)
  • README --dir entry added (EN + CN)
  • callClaude JSDoc timeout corrected to 600000
  • Redundant dynamic import of autoPushTeamRepo removed

Review feedback addressed

Issue Fix
snippet truncated to 500 chars contradicts lookup's 20K budget Removed formatResults truncation — snippet length now fully controlled by depth budget
index-time (fastTokens regex) vs query-time (tokenize Segmenter) produce different tokens → skewed BM25 Deleted fastTokens; index-time now uses the same shared tokenize as query-time
rawTokenCount counts full doc while token Set was capped at 5000 → inconsistent dl rawTokenCount now truncates to 100K chars (same limit as tokenize)

E2E verification

Test Result
recall (default heap, no --max-old-space-size) ✅ No OOM
recall --depth route ✅ Title + path only
recall --depth context ✅ Keyword snippet
recall --depth lookup ✅ Full content
import --from-org (11 repos, --skip-enrich) ✅ 10s
graph integrity (1179 nodes, 794 edges)
codebase --lint

Test plan

  • npx tsc --noEmit — zero errors
  • npx vitest run — 1676 passed (130 files)
  • E2E: recall no OOM under default heap
  • E2E: --depth route/context/lookup produce different Snippet output
  • E2E: import + graph aggregation unaffected
  • Index-time and query-time use identical tokenizer (no df/idf skew)

Closes #79, #80, #81, #82, #83, #84, #85

…ut, dead code

- Add `teamai import --dir <path>` to README command tables (EN + CN)
- Fix callClaude JSDoc: timeout default is 600000 (10min), not 120000
- Remove redundant dynamic import of autoPushTeamRepo (already static)
- Replace dead 'workspace' union member with 'dir' in ImportSession.mode

@jeff-r2026 jeff-r2026 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated review pass (medium effort). Three items — one functional contradiction with #79, plus two ranking-consistency issues in the new codebase tokenizer path. Left inline; none are blocking.

Comment thread src/recall.ts
Comment on lines +75 to +79
if (entry.snippet) {
const maxLen = entry.snippet.length > 500 ? 500 : entry.snippet.length;
const snippet = entry.snippet.slice(0, maxLen);
lines.push(`Snippet: ${snippet}${entry.snippet.length > maxLen ? '...' : ''}`);
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

--depth lookup output is silently truncated here. This 500-char cap applies to every snippet, including codebase results. But queryCodeKnowledge gives lookup a 20000-token budget and slices up to ~16k chars per page (code-knowledge-recall.ts:300), so the #79 behavior ("full content per result") never reaches the user — all depths render ≤500 chars. Either make the cap depth-aware (raise/skip it for lookup), or drop the per-page budget in queryCodeKnowledge if 500 chars is the intended ceiling; right now the two disagree.

Separately, the maxLen juggling is redundant and can be simplified:

Suggested change
if (entry.snippet) {
const maxLen = entry.snippet.length > 500 ? 500 : entry.snippet.length;
const snippet = entry.snippet.slice(0, maxLen);
lines.push(`Snippet: ${snippet}${entry.snippet.length > maxLen ? '...' : ''}`);
}
if (entry.snippet) {
const snippet = entry.snippet.slice(0, 500);
lines.push(`Snippet: ${snippet}${entry.snippet.length > 500 ? '...' : ''}`);
}

Comment thread src/code-knowledge-recall.ts Outdated
path: `evidence/code/${relativePath}`,
title,
content,
uniqueTokens: new Set(fastTokens(content)),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Docs and queries are tokenized by two different tokenizers. Pages are indexed with fastTokens (regex split) here, which builds df, but the query is tokenized with the shared tokenize (Intl.Segmenter + camelCase) at line 267. A query term like getuserid (Segmenter keeps it whole) is absent from df — docs only stored get/user/id — so df.get(token)=0 yields max idf, while countOccurrences still matches it in raw content (tf>0). Net effect: whole-word identifiers and CJK whole-words get inflated idf and skewed BM25 ranking.

This also cuts against the PR's stated goal of a unified tokenizer — for this module it introduces a second, divergent one. If fastTokens exists only to dodge the Segmenter OOM, prefer having the shared tokenize produce the same token surface (it already truncates to 100K chars) so index-time and query-time terms line up.

Comment thread src/code-knowledge-recall.ts Outdated
function fastTokens(text: string): string[] {
const prepared = text.replace(/([a-z])([A-Z])/g, '$1_$2').toLowerCase();
const words = prepared.split(/[^a-z0-9一-鿿]+/).filter((w) => w.length >= 2);
const tokens: string[] = words.slice(0, 5000);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fastTokens caps at 5000 words, but rawTokenCount (used as BM25 dl at line 97) counts the whole document. For an evidence/code page over 5000 words, df/uniqueTokens only reflect the first 5000 words while length-normalization uses the full dl — query terms in the tail are missing from df (idf inflated, same failure mode as the tokenizer mismatch), and dl vs the token set are computed inconsistently. If the cap is just an OOM safeguard, apply the same truncation to rawTokenCount (or drop the cap) so the two stay in sync.

Tencent#79 — --depth parameter now produces visibly different output:
  - route (1500 token budget): title + path only
  - context (5000, default): 300-char snippet
  - lookup (20000): full content per result
  - Snippet field shown in formatResults (up to 500 chars)

Tencent#80 — codebase score normalization: replace min-max (always=10) with
  log-dampening: Math.min(10, Math.log2(score+1) * 2)

Tencent#81 — hasWiki path: use resolved wikiRoot instead of cwd/teamwiki

Tencent#82 — buildConfidence: change from arithmetic mean (dilutes with more
  evidence) to max(weights) (strongest evidence determines confidence)

Tencent#83 — search-index shrink protection: relax from 50% threshold to
  only guard against empty index overwriting non-empty

Tencent#84 — codebase graph retrieval:
  - 84-1: loadWikiPages now recurses into subdirectories via collectMdFiles
  - 84-2/4: unified tokenizer (src/utils/tokenizer.ts) shared by
    search-index and code-knowledge-recall; adds camelCase splitting;
    truncates input to 100K chars to prevent Intl.Segmenter OOM
  - OOM fix: PageDoc.tokens replaced with uniqueTokens Set built via
    fastTokens (regex-only, no Segmenter) to avoid 216-file memory spike

Tencent#85-2 — auto-recall upvote: use autoDetectInit() instead of
  requireInit() so votes go to the correct scope

Additional:
- SEARCH_INDEX_VERSION bumped 4→5 (triggers rebuild for camelCase tokenizer)
- README --dir entry added (EN + CN)
- callClaude JSDoc timeout updated to 600000
- Redundant dynamic import of autoPushTeamRepo removed

Closes Tencent#79, Tencent#80, Tencent#81, Tencent#82, Tencent#83, Tencent#84, Tencent#85
@jeff-r2026
jeff-r2026 merged commit d9dc56f into Tencent:main Jul 1, 2026
7 checks passed
jeff-r2026 added a commit that referenced this pull request Jul 1, 2026
#73/#77 fixed recall's dual-scope merge and #91 fixed auto-recall's
upvote scope, but issue #85 flagged four more spots where project vs
user scope still leaked into each other:

- hooks-cmd.ts: `hooks inject`/`hooks remove` reconciled the user-home
  copy of team hooks using the PROJECT's manifest instead of the
  user's own, diverging from pull.ts's per-scope reconcile and risking
  duplicate injection / wrongful cleanup of shared tool settings files.
- tags.ts: subscribe/unsubscribe (and list/add/remove) always read and
  wrote ~/.teamai/config.yaml, ignoring project-scope installs.
- contribute.ts: wrote new learnings to the team repo but never
  rebuilt the local search index, so `recall` couldn't see a
  contribution until the next `pull`.
- types.ts/config.ts: getTeamaiHome()/resolveBaseDir() silently fell
  back to the user home directory when a project LocalConfig was
  missing `projectRoot` (e.g. a pre-migration config.yaml). Now
  detectProjectConfig()/loadLocalConfigForScope() backfill projectRoot
  from the directory the config was actually loaded from, and the
  scope helpers throw instead of silently degrading if it's ever
  still missing.

Added an e2e suite (scope-isolation-e2e-issue85.test.ts) that drives
the built CLI against real git fixtures to cover all four fixes
end-to-end, plus unit coverage in hooks-cmd.test.ts/scope.test.ts.

Closes #85

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
@hsuchifeng hsuchifeng mentioned this pull request Jul 3, 2026
3 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[bug] recall --depth 参数完全无效,且 depth budget 顺序写反、lookup 非图遍历

2 participants