local-codebase-indexer 0.3.0__tar.gz → 0.3.2__tar.gz
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.
- {local_codebase_indexer-0.3.0/local_codebase_indexer.egg-info → local_codebase_indexer-0.3.2}/PKG-INFO +1 -1
- {local_codebase_indexer-0.3.0 → local_codebase_indexer-0.3.2}/TODO.md +1 -0
- {local_codebase_indexer-0.3.0 → local_codebase_indexer-0.3.2}/local_codebase_indexer/__init__.py +1 -1
- {local_codebase_indexer-0.3.0 → local_codebase_indexer-0.3.2}/local_codebase_indexer/cli.py +17 -0
- {local_codebase_indexer-0.3.0 → local_codebase_indexer-0.3.2}/local_codebase_indexer/core.py +1 -1
- {local_codebase_indexer-0.3.0 → local_codebase_indexer-0.3.2}/local_codebase_indexer/query.py +59 -5
- {local_codebase_indexer-0.3.0 → local_codebase_indexer-0.3.2/local_codebase_indexer.egg-info}/PKG-INFO +1 -1
- {local_codebase_indexer-0.3.0 → local_codebase_indexer-0.3.2}/server.json +2 -2
- {local_codebase_indexer-0.3.0 → local_codebase_indexer-0.3.2}/tests/test_dataaccess.py +30 -0
- {local_codebase_indexer-0.3.0 → local_codebase_indexer-0.3.2}/tests/test_indexer.py +27 -0
- {local_codebase_indexer-0.3.0 → local_codebase_indexer-0.3.2}/AGENTS.md +0 -0
- {local_codebase_indexer-0.3.0 → local_codebase_indexer-0.3.2}/CLAUDE.md +0 -0
- {local_codebase_indexer-0.3.0 → local_codebase_indexer-0.3.2}/MANIFEST.in +0 -0
- {local_codebase_indexer-0.3.0 → local_codebase_indexer-0.3.2}/README.md +0 -0
- {local_codebase_indexer-0.3.0 → local_codebase_indexer-0.3.2}/README.zh-CN.md +0 -0
- {local_codebase_indexer-0.3.0 → local_codebase_indexer-0.3.2}/README.zh-TW.md +0 -0
- {local_codebase_indexer-0.3.0 → local_codebase_indexer-0.3.2}/benchmarks/README.md +0 -0
- {local_codebase_indexer-0.3.0 → local_codebase_indexer-0.3.2}/benchmarks/baseline.json +0 -0
- {local_codebase_indexer-0.3.0 → local_codebase_indexer-0.3.2}/benchmarks/corpus/Auth/LoginService.cs +0 -0
- {local_codebase_indexer-0.3.0 → local_codebase_indexer-0.3.2}/benchmarks/corpus/Controllers/LotController.cs +0 -0
- {local_codebase_indexer-0.3.0 → local_codebase_indexer-0.3.2}/benchmarks/corpus/Controllers/ScrapController.cs +0 -0
- {local_codebase_indexer-0.3.0 → local_codebase_indexer-0.3.2}/benchmarks/corpus/Data/MesDbContext.cs +0 -0
- {local_codebase_indexer-0.3.0 → local_codebase_indexer-0.3.2}/benchmarks/corpus/Legacy/frmChangeLine.frm +0 -0
- {local_codebase_indexer-0.3.0 → local_codebase_indexer-0.3.2}/benchmarks/corpus/Models/DrillBreakEvent.cs +0 -0
- {local_codebase_indexer-0.3.0 → local_codebase_indexer-0.3.2}/benchmarks/corpus/Models/Lot.cs +0 -0
- {local_codebase_indexer-0.3.0 → local_codebase_indexer-0.3.2}/benchmarks/corpus/Models/ScrapRecord.cs +0 -0
- {local_codebase_indexer-0.3.0 → local_codebase_indexer-0.3.2}/benchmarks/corpus/Printing/LabelPrinter.cs +0 -0
- {local_codebase_indexer-0.3.0 → local_codebase_indexer-0.3.2}/benchmarks/corpus/Repositories/LotRepository.cs +0 -0
- {local_codebase_indexer-0.3.0 → local_codebase_indexer-0.3.2}/benchmarks/corpus/Repositories/ScrapRepository.cs +0 -0
- {local_codebase_indexer-0.3.0 → local_codebase_indexer-0.3.2}/benchmarks/corpus/Services/IScrapService.cs +0 -0
- {local_codebase_indexer-0.3.0 → local_codebase_indexer-0.3.2}/benchmarks/corpus/Services/InspectionService.cs +0 -0
- {local_codebase_indexer-0.3.0 → local_codebase_indexer-0.3.2}/benchmarks/corpus/Services/LotService.cs +0 -0
- {local_codebase_indexer-0.3.0 → local_codebase_indexer-0.3.2}/benchmarks/corpus/Services/ScrapService.cs +0 -0
- {local_codebase_indexer-0.3.0 → local_codebase_indexer-0.3.2}/benchmarks/corpus/Sql/MES_Lot.sql +0 -0
- {local_codebase_indexer-0.3.0 → local_codebase_indexer-0.3.2}/benchmarks/corpus/Sql/usp_Inspection_Report.sql +0 -0
- {local_codebase_indexer-0.3.0 → local_codebase_indexer-0.3.2}/benchmarks/corpus/Sql/usp_Lot_UpdateStatus.sql +0 -0
- {local_codebase_indexer-0.3.0 → local_codebase_indexer-0.3.2}/benchmarks/corpus/Sql/usp_Scrap_Insert.sql +0 -0
- {local_codebase_indexer-0.3.0 → local_codebase_indexer-0.3.2}/benchmarks/corpus/Utils/DateRangeHelper.cs +0 -0
- {local_codebase_indexer-0.3.0 → local_codebase_indexer-0.3.2}/benchmarks/corpus/Utils/PartNumberDecoder.cs +0 -0
- {local_codebase_indexer-0.3.0 → local_codebase_indexer-0.3.2}/benchmarks/corpus/Web/scrapForm.ts +0 -0
- {local_codebase_indexer-0.3.0 → local_codebase_indexer-0.3.2}/benchmarks/corpus/Web.config +0 -0
- {local_codebase_indexer-0.3.0 → local_codebase_indexer-0.3.2}/benchmarks/corpus/tools/export_report.py +0 -0
- {local_codebase_indexer-0.3.0 → local_codebase_indexer-0.3.2}/benchmarks/queries_stdlib.json +0 -0
- {local_codebase_indexer-0.3.0 → local_codebase_indexer-0.3.2}/benchmarks/queries_synthetic.json +0 -0
- {local_codebase_indexer-0.3.0 → local_codebase_indexer-0.3.2}/benchmarks/run_benchmark.py +0 -0
- {local_codebase_indexer-0.3.0 → local_codebase_indexer-0.3.2}/index.md +0 -0
- {local_codebase_indexer-0.3.0 → local_codebase_indexer-0.3.2}/llms.txt +0 -0
- {local_codebase_indexer-0.3.0 → local_codebase_indexer-0.3.2}/local_codebase_indexer/__main__.py +0 -0
- {local_codebase_indexer-0.3.0 → local_codebase_indexer-0.3.2}/local_codebase_indexer/callgraph.py +0 -0
- {local_codebase_indexer-0.3.0 → local_codebase_indexer-0.3.2}/local_codebase_indexer/cscalls.py +0 -0
- {local_codebase_indexer-0.3.0 → local_codebase_indexer-0.3.2}/local_codebase_indexer/csdata.py +0 -0
- {local_codebase_indexer-0.3.0 → local_codebase_indexer-0.3.2}/local_codebase_indexer/dataflow.py +0 -0
- {local_codebase_indexer-0.3.0 → local_codebase_indexer-0.3.2}/local_codebase_indexer/db.py +0 -0
- {local_codebase_indexer-0.3.0 → local_codebase_indexer-0.3.2}/local_codebase_indexer/embedder.py +0 -0
- {local_codebase_indexer-0.3.0 → local_codebase_indexer-0.3.2}/local_codebase_indexer/langparse.py +0 -0
- {local_codebase_indexer-0.3.0 → local_codebase_indexer-0.3.2}/local_codebase_indexer/mcp_server.py +0 -0
- {local_codebase_indexer-0.3.0 → local_codebase_indexer-0.3.2}/local_codebase_indexer/scanner.py +0 -0
- {local_codebase_indexer-0.3.0 → local_codebase_indexer-0.3.2}/local_codebase_indexer/sqlaccess.py +0 -0
- {local_codebase_indexer-0.3.0 → local_codebase_indexer-0.3.2}/local_codebase_indexer/synonyms.py +0 -0
- {local_codebase_indexer-0.3.0 → local_codebase_indexer-0.3.2}/local_codebase_indexer.egg-info/SOURCES.txt +0 -0
- {local_codebase_indexer-0.3.0 → local_codebase_indexer-0.3.2}/local_codebase_indexer.egg-info/dependency_links.txt +0 -0
- {local_codebase_indexer-0.3.0 → local_codebase_indexer-0.3.2}/local_codebase_indexer.egg-info/entry_points.txt +0 -0
- {local_codebase_indexer-0.3.0 → local_codebase_indexer-0.3.2}/local_codebase_indexer.egg-info/requires.txt +0 -0
- {local_codebase_indexer-0.3.0 → local_codebase_indexer-0.3.2}/local_codebase_indexer.egg-info/top_level.txt +0 -0
- {local_codebase_indexer-0.3.0 → local_codebase_indexer-0.3.2}/pyproject.toml +0 -0
- {local_codebase_indexer-0.3.0 → local_codebase_indexer-0.3.2}/requirements.txt +0 -0
- {local_codebase_indexer-0.3.0 → local_codebase_indexer-0.3.2}/setup.cfg +0 -0
- {local_codebase_indexer-0.3.0 → local_codebase_indexer-0.3.2}/tests/test_onnx_plumbing.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: local-codebase-indexer
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2
|
|
4
4
|
Summary: Local codebase indexer and context provider for AI coding assistants: MCP server and CLI with SQL data-access and C# call relations. No cloud, no server.
|
|
5
5
|
Author: sfy257000
|
|
6
6
|
Project-URL: Homepage, https://github.com/sfy257000/local-codebase-indexer
|
|
@@ -4,6 +4,7 @@ Priorities come from the benchmark (`benchmarks/run_benchmark.py`, baseline in `
|
|
|
4
4
|
|
|
5
5
|
## Next
|
|
6
6
|
|
|
7
|
+
0. **Publish to the MCP registry** (not done yet). PyPI 0.3.0 is out and carries the `mcp-name` marker; `server.json` is ready. Run `mcp-publisher login github` then `mcp-publisher publish` from the repo folder, then verify with `curl "https://registry.modelcontextprotocol.io/v0.1/servers?search=local-codebase-indexer"`. Repeat after each release (bump both versions in `server.json`).
|
|
7
8
|
1. **Check a real embedding model on your own machine** (this environment cannot reach Hugging Face, so the ONNX path is only tested with a tiny generated model). Export a model such as `all-MiniLM-L6-v2` (mean pooling) or `bge-small-en-v1.5` (`INDEXER_ONNX_POOLING=cls`, `INDEXER_ONNX_QUERY_PREFIX="Represent this sentence for searching relevant passages: "`), set `INDEXER_ONNX_MODEL`, and run `python benchmarks/run_benchmark.py --compare benchmarks/baseline.json`.
|
|
8
9
|
Evidence: `synonym` Recall@5 is only 0.30 (hashing embedder plus the dictionary, which cannot cover free paraphrase). Whether a real model fixes this is not measured.
|
|
9
10
|
2. **Resolve `FROM a JOIN b ON ..., c`** (a comma list after a JOIN loses `c`), non-sink SQL proc-name literals (`Run("usp_X")`), and EF query tracing (only the table mapping is recorded now).
|
|
@@ -58,8 +58,25 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
58
58
|
return p
|
|
59
59
|
|
|
60
60
|
|
|
61
|
+
def _fix_stdio(serving: bool) -> None:
|
|
62
|
+
"""MCP speaks UTF-8 JSON; the Windows locale codec (cp950, cp1252) would garble Chinese queries. On a console,
|
|
63
|
+
keep its encoding but never crash on a character it cannot show."""
|
|
64
|
+
for name in ("stdin", "stdout", "stderr"):
|
|
65
|
+
stream = getattr(sys, name, None)
|
|
66
|
+
if stream is None or not hasattr(stream, "reconfigure"):
|
|
67
|
+
continue
|
|
68
|
+
try:
|
|
69
|
+
if serving:
|
|
70
|
+
stream.reconfigure(encoding="utf-8", errors="replace")
|
|
71
|
+
elif name != "stdin":
|
|
72
|
+
stream.reconfigure(errors="replace")
|
|
73
|
+
except (OSError, ValueError):
|
|
74
|
+
pass
|
|
75
|
+
|
|
76
|
+
|
|
61
77
|
def main(argv: list[str] | None = None) -> int:
|
|
62
78
|
args = build_parser().parse_args(argv)
|
|
79
|
+
_fix_stdio(serving=args.cmd == "serve")
|
|
63
80
|
root = os.path.abspath(args.root)
|
|
64
81
|
|
|
65
82
|
if args.cmd == "serve":
|
{local_codebase_indexer-0.3.0 → local_codebase_indexer-0.3.2}/local_codebase_indexer/core.py
RENAMED
|
@@ -61,7 +61,7 @@ def git_info(root: str) -> dict | None:
|
|
|
61
61
|
"""HEAD commit, branch and number of uncommitted paths; None outside a git work tree."""
|
|
62
62
|
def run(*args: str) -> str | None:
|
|
63
63
|
try:
|
|
64
|
-
r = subprocess.run(["git", "-C", root, *args], capture_output=True, text=True, timeout=15)
|
|
64
|
+
r = subprocess.run(["git", "-C", root, *args], capture_output=True, text=True, encoding="utf-8", errors="replace", timeout=15)
|
|
65
65
|
except (OSError, subprocess.SubprocessError):
|
|
66
66
|
return None
|
|
67
67
|
return r.stdout if r.returncode == 0 else None
|
{local_codebase_indexer-0.3.0 → local_codebase_indexer-0.3.2}/local_codebase_indexer/query.py
RENAMED
|
@@ -94,6 +94,7 @@ class CodebaseTools:
|
|
|
94
94
|
terms.append(t)
|
|
95
95
|
ranked: dict[str, list[int]] = {"keyword": [], "symbol": [], "vector": []}
|
|
96
96
|
ranks_override: dict[str, dict[int, int]] = {}
|
|
97
|
+
exact_ids: set[int] = set() # chunks whose symbol name contains a full identifier typed in the query
|
|
97
98
|
|
|
98
99
|
# keyword layer: FTS5 / bm25 over chunk text
|
|
99
100
|
if terms:
|
|
@@ -125,6 +126,19 @@ class CodebaseTools:
|
|
|
125
126
|
):
|
|
126
127
|
if _has_word(t, r["file_path"]):
|
|
127
128
|
scores[r[0]] += 1
|
|
129
|
+
# a full identifier typed by the user (SFC_MLot) must match names containing it whole, whatever the
|
|
130
|
+
# case or underscores (SFC_Mlot, SFC_MLotRepository); sub-word matches alone would favour every *Lot
|
|
131
|
+
for ident in self._whole_identifiers(query):
|
|
132
|
+
squashed = re.sub(r"[^a-z0-9]", "", ident.lower())
|
|
133
|
+
like = f"%{squashed}%"
|
|
134
|
+
for r in self.conn.execute(
|
|
135
|
+
"SELECT c.rowid FROM chunk_metadata c JOIN symbols s ON s.id = c.symbol_id "
|
|
136
|
+
"WHERE replace(lower(s.symbol_name), '_', '') LIKE ? LIMIT 500", (like,)):
|
|
137
|
+
scores[r[0]] += 3
|
|
138
|
+
exact_ids.add(r[0])
|
|
139
|
+
for r in self.conn.execute(
|
|
140
|
+
"SELECT rowid FROM chunk_metadata WHERE replace(lower(file_path), '_', '') LIKE ? LIMIT 300", (like,)):
|
|
141
|
+
scores[r[0]] += 2
|
|
128
142
|
ordered = sorted(scores.items(), key=lambda kv: (-kv[1], kv[0]))[:pool]
|
|
129
143
|
ranked["symbol"] = [rid for rid, _ in ordered]
|
|
130
144
|
# equal match counts must share a rank, otherwise arbitrary row order decides
|
|
@@ -154,13 +168,26 @@ class CodebaseTools:
|
|
|
154
168
|
fused[rid] += LAYER_WEIGHTS[name] / (RRF_K + rank)
|
|
155
169
|
via[rid].append(name)
|
|
156
170
|
top_ids = [rid for rid, _ in sorted(fused.items(), key=lambda kv: -kv[1])]
|
|
171
|
+
if exact_ids: # a typed identifier beats sub-word noise (every *Lot class), ties keep the fused order
|
|
172
|
+
top_ids = [r for r in top_ids if r in exact_ids] + [r for r in top_ids if r not in exact_ids]
|
|
157
173
|
|
|
158
174
|
results, seen_spans = [], set()
|
|
159
|
-
|
|
175
|
+
routed, bare = self._route_data_access(query)
|
|
176
|
+
routed_hits = []
|
|
177
|
+
for acc in routed[:limit]:
|
|
160
178
|
hit = self._chunk_result(acc)
|
|
161
179
|
if hit is not None and (hit["file_path"], hit["start_line"], hit["end_line"]) not in seen_spans:
|
|
162
180
|
seen_spans.add((hit["file_path"], hit["start_line"], hit["end_line"]))
|
|
163
|
-
|
|
181
|
+
routed_hits.append(hit)
|
|
182
|
+
if bare and routed_hits and top_ids:
|
|
183
|
+
# only a name was typed: the definition (best ordinary match) first, then who touches it
|
|
184
|
+
first = self._ordinary_hit(top_ids[0], fused, via)
|
|
185
|
+
if first is not None:
|
|
186
|
+
span = (first["file_path"], first["start_line"], first["end_line"])
|
|
187
|
+
routed_hits = [h for h in routed_hits if (h["file_path"], h["start_line"], h["end_line"]) != span]
|
|
188
|
+
results.append(first)
|
|
189
|
+
seen_spans.add(span)
|
|
190
|
+
results.extend(routed_hits)
|
|
164
191
|
for rid in top_ids:
|
|
165
192
|
if len(results) >= limit:
|
|
166
193
|
break
|
|
@@ -194,14 +221,39 @@ class CodebaseTools:
|
|
|
194
221
|
return {"query": query, "results": results, "related_files": self._related_files(results[:3])}
|
|
195
222
|
|
|
196
223
|
# ------------------------------------------------------------------ data access routing
|
|
224
|
+
def _ordinary_hit(self, rid: int, fused: dict, via: dict) -> dict | None:
|
|
225
|
+
row = self.conn.execute(
|
|
226
|
+
"SELECT c.file_path, c.start_line, c.end_line, c.raw_text, s.symbol_type, s.qualified_name, s.signature "
|
|
227
|
+
"FROM chunk_metadata c LEFT JOIN symbols s ON s.id = c.symbol_id WHERE c.rowid=?", (rid,)).fetchone()
|
|
228
|
+
if row is None:
|
|
229
|
+
return None
|
|
230
|
+
lines = textwrap.dedent(row["raw_text"].rstrip()).split("\n")
|
|
231
|
+
return {"file_path": row["file_path"], "start_line": row["start_line"], "end_line": row["end_line"],
|
|
232
|
+
"symbol": row["qualified_name"], "symbol_type": row["symbol_type"], "signature": row["signature"],
|
|
233
|
+
"score": round(fused[rid], 5), "matched_by": via[rid],
|
|
234
|
+
"snippet": "\n".join(lines[:12]) + ("\n..." if len(lines) > 12 else "")}
|
|
235
|
+
|
|
236
|
+
@staticmethod
|
|
237
|
+
def _whole_identifiers(query: str) -> list[str]:
|
|
238
|
+
"""Identifier-looking tokens (underscore, digit or camelCase inside) of at least 5 characters."""
|
|
239
|
+
out = []
|
|
240
|
+
for ident in re.findall(r"[A-Za-z_][\w$]*", query):
|
|
241
|
+
if len(ident) >= 5 and (re.search(r"[_\d]", ident[1:]) or
|
|
242
|
+
(re.search(r"[a-z]", ident) and re.search(r"[A-Z]", ident[1:]))):
|
|
243
|
+
out.append(ident)
|
|
244
|
+
return out
|
|
245
|
+
|
|
197
246
|
_OBJECT_HINT = re.compile(r"\b(table|tables|procedure|procedures|proc|stored|view|views|sp)\b|資料表|預存程序|存储过程|数据表", re.I)
|
|
198
247
|
|
|
199
|
-
def _route_data_access(self, query: str) -> list[dict]:
|
|
248
|
+
def _route_data_access(self, query: str) -> tuple[list[dict], bool]:
|
|
200
249
|
"""Queries such as "what writes to MES_Lot" name a known table / procedure and an intent. A ranked chunk
|
|
201
250
|
list cannot enumerate every writer, so those queries get the accessors from the data access index first."""
|
|
202
251
|
intents = dataflow.detect_intents(query)
|
|
252
|
+
bare = False
|
|
203
253
|
if not intents:
|
|
204
|
-
|
|
254
|
+
if len(query.split()) > 3:
|
|
255
|
+
return [], False
|
|
256
|
+
intents, bare = {"touch"}, True
|
|
205
257
|
keys = dataflow.known_object_keys(self.conn)
|
|
206
258
|
hinted = bool(self._OBJECT_HINT.search(query))
|
|
207
259
|
found: list[str] = []
|
|
@@ -218,7 +270,9 @@ class CodebaseTools:
|
|
|
218
270
|
if sig not in seen:
|
|
219
271
|
seen.add(sig)
|
|
220
272
|
out.append(acc)
|
|
221
|
-
|
|
273
|
+
if bare: # leave room for the ordinary name matches (the class, the repository, the file)
|
|
274
|
+
out = out[:3]
|
|
275
|
+
return out, bare
|
|
222
276
|
|
|
223
277
|
def _chunk_result(self, acc: dict) -> dict | None:
|
|
224
278
|
conn = self.conn
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: local-codebase-indexer
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2
|
|
4
4
|
Summary: Local codebase indexer and context provider for AI coding assistants: MCP server and CLI with SQL data-access and C# call relations. No cloud, no server.
|
|
5
5
|
Author: sfy257000
|
|
6
6
|
Project-URL: Homepage, https://github.com/sfy257000/local-codebase-indexer
|
|
@@ -7,12 +7,12 @@
|
|
|
7
7
|
"url": "https://github.com/sfy257000/local-codebase-indexer",
|
|
8
8
|
"source": "github"
|
|
9
9
|
},
|
|
10
|
-
"version": "0.3.
|
|
10
|
+
"version": "0.3.2",
|
|
11
11
|
"packages": [
|
|
12
12
|
{
|
|
13
13
|
"registryType": "pypi",
|
|
14
14
|
"identifier": "local-codebase-indexer",
|
|
15
|
-
"version": "0.3.
|
|
15
|
+
"version": "0.3.2",
|
|
16
16
|
"runtimeHint": "uvx",
|
|
17
17
|
"transport": {
|
|
18
18
|
"type": "stdio"
|
|
@@ -112,3 +112,33 @@ def test_synonym_expansion_and_project_override(tmp_path):
|
|
|
112
112
|
e2 = Expander(str(tmp_path))
|
|
113
113
|
assert "wpnlot" in e2.expand("在製批 查詢")
|
|
114
114
|
assert "proc" not in e2.expand("SP list")
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def test_bare_table_name_finds_its_repository_not_every_lot_class(tmp_path):
|
|
118
|
+
"""Real-world miss: searching SFC_MLot ranked OutsourceLot / PNLToPCSLot above SFC_MLotRepository because the
|
|
119
|
+
sub-word 'lot' matched everything, and the table was spelled SFC_Mlot in the SQL string."""
|
|
120
|
+
(tmp_path / "Repo").mkdir()
|
|
121
|
+
(tmp_path / "Models").mkdir()
|
|
122
|
+
(tmp_path / "Repo" / "SFC_MLotRepository.cs").write_text("""using System.Threading.Tasks;
|
|
123
|
+
using Dapper;
|
|
124
|
+
namespace E {
|
|
125
|
+
public class SFC_MLotRepository : ISFC_MLotRepository {
|
|
126
|
+
private readonly System.Data.IDbConnection _db;
|
|
127
|
+
public Task<int> Deduct(decimal qty) {
|
|
128
|
+
string sql = @" UPDATE SFC_Mlot SET Qty = Qty - @qty WHERE PostFlag = 'W'; DELETE FROM SFC_Mlot WHERE Qty <= 0;";
|
|
129
|
+
return _db.ExecuteAsync(sql, new { qty });
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
public interface ISFC_MLotRepository { Task<int> Deduct(decimal qty); }
|
|
133
|
+
}
|
|
134
|
+
""", encoding="utf-8")
|
|
135
|
+
lots = "".join(f"public class Lot{i} : SFCLot {{ }} public class ReworkLot{i} : SFCLot {{ }}\n" for i in range(8))
|
|
136
|
+
(tmp_path / "Models" / "SFCLot.cs").write_text(
|
|
137
|
+
"namespace E { public partial class SFCLot { }\n" + lots + "public class OutsourceLot : SFCLot { } }", encoding="utf-8")
|
|
138
|
+
t = CodebaseTools(str(tmp_path))
|
|
139
|
+
for q in ("SFC_MLot", "SFC_Mlot", "sfc_mlot"):
|
|
140
|
+
top = t.semantic_search(q, limit=5)["results"]
|
|
141
|
+
assert top[0]["file_path"].endswith("SFC_MLotRepository.cs"), q
|
|
142
|
+
assert sum(r["file_path"].endswith("SFC_MLotRepository.cs") for r in top) >= 3, q
|
|
143
|
+
assert any((r.get("data_access") or {}).get("access") == "write" for r in top), q
|
|
144
|
+
t.indexer.close()
|
|
@@ -330,3 +330,30 @@ def test_benchmark_corpus_is_valid_and_floor_holds(tmp_path):
|
|
|
330
330
|
cats = json.loads(out.read_text(encoding="utf-8"))["suites"]["synthetic"]["categories"]
|
|
331
331
|
assert cats["exact_identifier"]["recall5"] == 1.0
|
|
332
332
|
assert cats["chinese"]["recall5"] == 1.0
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
def test_git_output_is_decoded_as_utf8_not_the_locale(tmp_path, monkeypatch):
|
|
336
|
+
"""On Windows the locale codec (cp950) cannot decode git's UTF-8 output and the reader thread crashed."""
|
|
337
|
+
import subprocess
|
|
338
|
+
|
|
339
|
+
import local_codebase_indexer.core as core
|
|
340
|
+
seen = {}
|
|
341
|
+
real = subprocess.run
|
|
342
|
+
|
|
343
|
+
def spy(*a, **kw):
|
|
344
|
+
seen.update(kw)
|
|
345
|
+
return real(*a, **kw)
|
|
346
|
+
|
|
347
|
+
monkeypatch.setattr(subprocess, "run", spy)
|
|
348
|
+
core.git_info(str(tmp_path))
|
|
349
|
+
assert seen.get("encoding") == "utf-8" and seen.get("errors") == "replace"
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
def test_mcp_serve_reads_utf8_even_when_locale_is_cp950(project):
|
|
353
|
+
q = json.dumps({"jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": {
|
|
354
|
+
"name": "codebase_semantic_search", "arguments": {"query": "批號 LotNo", "limit": 2}}}, ensure_ascii=False)
|
|
355
|
+
env = dict(os.environ, PYTHONIOENCODING="cp950")
|
|
356
|
+
r = subprocess.run([sys.executable, "-m", "local_codebase_indexer", "--root", str(project), "serve"],
|
|
357
|
+
input=(q + "\n").encode("utf-8"), capture_output=True, env=env, cwd=ROOT, timeout=60)
|
|
358
|
+
out = json.loads(r.stdout.decode("utf-8"))
|
|
359
|
+
assert "result" in out and "error" not in out, r.stderr.decode("utf-8", "replace")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{local_codebase_indexer-0.3.0 → local_codebase_indexer-0.3.2}/benchmarks/corpus/Auth/LoginService.cs
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{local_codebase_indexer-0.3.0 → local_codebase_indexer-0.3.2}/benchmarks/corpus/Data/MesDbContext.cs
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{local_codebase_indexer-0.3.0 → local_codebase_indexer-0.3.2}/benchmarks/corpus/Models/Lot.cs
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{local_codebase_indexer-0.3.0 → local_codebase_indexer-0.3.2}/benchmarks/corpus/Sql/MES_Lot.sql
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{local_codebase_indexer-0.3.0 → local_codebase_indexer-0.3.2}/benchmarks/corpus/Web/scrapForm.ts
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{local_codebase_indexer-0.3.0 → local_codebase_indexer-0.3.2}/benchmarks/queries_stdlib.json
RENAMED
|
File without changes
|
{local_codebase_indexer-0.3.0 → local_codebase_indexer-0.3.2}/benchmarks/queries_synthetic.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{local_codebase_indexer-0.3.0 → local_codebase_indexer-0.3.2}/local_codebase_indexer/__main__.py
RENAMED
|
File without changes
|
{local_codebase_indexer-0.3.0 → local_codebase_indexer-0.3.2}/local_codebase_indexer/callgraph.py
RENAMED
|
File without changes
|
{local_codebase_indexer-0.3.0 → local_codebase_indexer-0.3.2}/local_codebase_indexer/cscalls.py
RENAMED
|
File without changes
|
{local_codebase_indexer-0.3.0 → local_codebase_indexer-0.3.2}/local_codebase_indexer/csdata.py
RENAMED
|
File without changes
|
{local_codebase_indexer-0.3.0 → local_codebase_indexer-0.3.2}/local_codebase_indexer/dataflow.py
RENAMED
|
File without changes
|
|
File without changes
|
{local_codebase_indexer-0.3.0 → local_codebase_indexer-0.3.2}/local_codebase_indexer/embedder.py
RENAMED
|
File without changes
|
{local_codebase_indexer-0.3.0 → local_codebase_indexer-0.3.2}/local_codebase_indexer/langparse.py
RENAMED
|
File without changes
|
{local_codebase_indexer-0.3.0 → local_codebase_indexer-0.3.2}/local_codebase_indexer/mcp_server.py
RENAMED
|
File without changes
|
{local_codebase_indexer-0.3.0 → local_codebase_indexer-0.3.2}/local_codebase_indexer/scanner.py
RENAMED
|
File without changes
|
{local_codebase_indexer-0.3.0 → local_codebase_indexer-0.3.2}/local_codebase_indexer/sqlaccess.py
RENAMED
|
File without changes
|
{local_codebase_indexer-0.3.0 → local_codebase_indexer-0.3.2}/local_codebase_indexer/synonyms.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|