@ctxo/cli 0.14.0 → 0.15.0
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/README.md +10 -1
- package/dist/{chunk-ZL36YS2V.js → chunk-A4URKKPP.js} +3 -177
- package/dist/{chunk-SRQZDGFZ.js → chunk-BBSJB4CG.js} +2 -2
- package/dist/{chunk-X4TVFJ4C.js → chunk-EC6INGPV.js} +1 -1
- package/dist/{chunk-7HSA5JVP.js → chunk-I7RPXT6L.js} +193 -1
- package/dist/chunk-IT426P3F.js +1036 -0
- package/dist/{chunk-G4BMKCKG.js → chunk-JDL6GGTO.js} +16 -5
- package/dist/{cli-router-AZCDNX5R.js → cli-router-W56INOQ3.js} +10 -10
- package/dist/index.js +3 -3
- package/dist/internal.d.ts +90 -3
- package/dist/internal.js +4 -4
- package/dist/{json-index-reader-DBW32TCN.js → json-index-reader-DKEIP2QR.js} +2 -2
- package/dist/{mcp-server-6YG2QE6E.js → mcp-server-IYCCE74E.js} +178 -446
- package/dist/module-host-KTGDODES.js +13 -0
- package/llms-full.txt +68 -3
- package/llms.txt +14 -2
- package/package.json +4 -3
- package/dist/atomic-write-BAFH464Q.js.map +0 -1
- package/dist/chunk-2Q76LH2H.js.map +0 -1
- package/dist/chunk-5DSKZJNN.js.map +0 -1
- package/dist/chunk-72CEQEQD.js.map +0 -1
- package/dist/chunk-7HSA5JVP.js.map +0 -1
- package/dist/chunk-G4BMKCKG.js.map +0 -1
- package/dist/chunk-L2CAUER6.js +0 -508
- package/dist/chunk-L2CAUER6.js.map +0 -1
- package/dist/chunk-LAX6VVBU.js.map +0 -1
- package/dist/chunk-PSKDDLSF.js.map +0 -1
- package/dist/chunk-RFEXMBTG.js.map +0 -1
- package/dist/chunk-SRQZDGFZ.js.map +0 -1
- package/dist/chunk-X4TVFJ4C.js.map +0 -1
- package/dist/chunk-ZL36YS2V.js.map +0 -1
- package/dist/cli-router-AZCDNX5R.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/internal.js.map +0 -1
- package/dist/json-index-reader-DBW32TCN.js.map +0 -1
- package/dist/mcp-server-6YG2QE6E.js.map +0 -1
- package/dist/module-host-KNMKS62R.js +0 -13
- package/dist/module-host-KNMKS62R.js.map +0 -1
- package/dist/module-loader-B3LYF3W7.js.map +0 -1
- package/dist/plugin-discovery-XRXI3IJF.js.map +0 -1
- package/dist/staleness-detector-4HJYBQ7Z.js.map +0 -1
package/README.md
CHANGED
|
@@ -36,7 +36,7 @@ It's not a skill gap. It's a **sensory gap**. The agent is navigating your codeb
|
|
|
36
36
|
|
|
37
37
|
The core shift: your agent stops reacting to files it stumbles into and starts planning from a complete map. **Blast radius before the edit. Git intent before the bug fix. Importer list before the rename.**
|
|
38
38
|
|
|
39
|
-
Ctxo indexes your repo once, kept fresh by file watchers and git hooks, into a deterministic graph: every symbol, every edge (imports, calls, extends, implements, uses, and the data edges reads, writes and executes), every relevant git commit with intent classified, every anti-pattern. All exposed through 14 semantic MCP tools
|
|
39
|
+
Ctxo indexes your repo once, kept fresh by file watchers and git hooks, into a deterministic graph: every symbol, every edge (imports, calls, extends, implements, uses, and the data edges reads, writes and executes), every relevant git commit with intent classified, every anti-pattern. All exposed through 14 semantic MCP tools, plus 2 domain tools with `@ctxo/domain`.
|
|
40
40
|
|
|
41
41
|
One `get_blast_radius` call replaces an entire ripgrep/read spiral. One `get_pr_impact` replaces a full review session of "wait, what calls this?"
|
|
42
42
|
|
|
@@ -154,6 +154,15 @@ ctxo domain rules --write # path-scoped rules that point the age
|
|
|
154
154
|
ctxo domain discover # propose tracker prefixes and test conventions from the repo
|
|
155
155
|
```
|
|
156
156
|
|
|
157
|
+
With cards in place, `ctxo index` links each rule to the tests that pin it and the code that implements it, and the agent sees the rules while it works: `get_blast_radius` lists the rules a change may break and the cards to update, `get_why_context` the decisions behind them, `get_context_for_task` a card excerpt, `get_pr_impact` the contexts touched and the rules without tests. Two tools answer domain questions directly:
|
|
158
|
+
|
|
159
|
+
| Tool | Answers |
|
|
160
|
+
| --- | --- |
|
|
161
|
+
| `get_domain_context` | The card of the context that owns a symbol or file: terms, rules with their tests and code, contracts, decisions |
|
|
162
|
+
| `get_rule` | One rule by ID: the tests that pin it (and how each was found), the symbols that implement it, the decisions behind it |
|
|
163
|
+
|
|
164
|
+
`get_ranked_context` with `scope: "domain"` searches the cards and the documents they link to.
|
|
165
|
+
|
|
157
166
|
See [Ctxo domain conventions v1](docs/features/domain-knowledge.md#ctxo-domain-conventions-v1) for the registry, the card template and the adoption ladder.
|
|
158
167
|
|
|
159
168
|
## Supported Languages
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
DATA_EDGE_KINDS,
|
|
4
4
|
createTestFileClassifier,
|
|
5
5
|
filesWithTestAnnotations
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-I7RPXT6L.js";
|
|
7
7
|
import {
|
|
8
8
|
compareCodepoint
|
|
9
9
|
} from "./chunk-72CEQEQD.js";
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
EDGE_KINDS,
|
|
13
13
|
EdgeKindSchema,
|
|
14
14
|
JsonIndexReader
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-EC6INGPV.js";
|
|
16
16
|
import {
|
|
17
17
|
atomicWrite
|
|
18
18
|
} from "./chunk-RFEXMBTG.js";
|
|
@@ -1816,184 +1816,10 @@ function fileOf(symbolId) {
|
|
|
1816
1816
|
return symbolId.split("::")[0];
|
|
1817
1817
|
}
|
|
1818
1818
|
|
|
1819
|
-
// src/core/search/symbol-tokenizer.ts
|
|
1820
|
-
var STOP_WORDS = /* @__PURE__ */ new Set([
|
|
1821
|
-
"get",
|
|
1822
|
-
"set",
|
|
1823
|
-
"is",
|
|
1824
|
-
"has",
|
|
1825
|
-
"can",
|
|
1826
|
-
"do",
|
|
1827
|
-
"the",
|
|
1828
|
-
"a",
|
|
1829
|
-
"an",
|
|
1830
|
-
"of",
|
|
1831
|
-
"to",
|
|
1832
|
-
"in",
|
|
1833
|
-
"for",
|
|
1834
|
-
"on",
|
|
1835
|
-
"with",
|
|
1836
|
-
"at",
|
|
1837
|
-
"by",
|
|
1838
|
-
"from",
|
|
1839
|
-
"as",
|
|
1840
|
-
"into",
|
|
1841
|
-
"not",
|
|
1842
|
-
"no",
|
|
1843
|
-
"or"
|
|
1844
|
-
]);
|
|
1845
|
-
var DEFAULT_OPTIONS = {
|
|
1846
|
-
includeStopWords: false,
|
|
1847
|
-
includeOriginal: true,
|
|
1848
|
-
includeFilePath: false
|
|
1849
|
-
};
|
|
1850
|
-
var SymbolTokenizer = class _SymbolTokenizer {
|
|
1851
|
-
options;
|
|
1852
|
-
constructor(options) {
|
|
1853
|
-
this.options = { ...DEFAULT_OPTIONS, ...options };
|
|
1854
|
-
}
|
|
1855
|
-
/**
|
|
1856
|
-
* Tokenize a symbol name into searchable tokens.
|
|
1857
|
-
*
|
|
1858
|
-
* Examples:
|
|
1859
|
-
* "getCoChangeMetrics" → ["get", "co", "change", "metrics"] (or without stop words: ["co", "change", "metrics"])
|
|
1860
|
-
* "SqliteStorageAdapter" → ["sqlite", "storage", "adapter"]
|
|
1861
|
-
* "i_storage_port" → ["i", "storage", "port"]
|
|
1862
|
-
* "BM25Scorer" → ["bm", "25", "scorer"]
|
|
1863
|
-
* "DB_PASSWORD" → ["db", "password"]
|
|
1864
|
-
*/
|
|
1865
|
-
tokenize(symbolName, filePath) {
|
|
1866
|
-
const splitTokens = _SymbolTokenizer.splitName(symbolName);
|
|
1867
|
-
const lowered = splitTokens.map((t) => t.toLowerCase());
|
|
1868
|
-
const tokens = this.options.includeStopWords ? lowered : lowered.filter((t) => !STOP_WORDS.has(t));
|
|
1869
|
-
if (this.options.includeOriginal) {
|
|
1870
|
-
const original = symbolName.toLowerCase();
|
|
1871
|
-
if (!tokens.includes(original)) {
|
|
1872
|
-
tokens.unshift(original);
|
|
1873
|
-
}
|
|
1874
|
-
}
|
|
1875
|
-
if (this.options.includeFilePath && filePath) {
|
|
1876
|
-
const pathTokens = _SymbolTokenizer.tokenizeFilePath(filePath);
|
|
1877
|
-
for (const pt of pathTokens) {
|
|
1878
|
-
if (!tokens.includes(pt)) {
|
|
1879
|
-
tokens.push(pt);
|
|
1880
|
-
}
|
|
1881
|
-
}
|
|
1882
|
-
}
|
|
1883
|
-
return tokens;
|
|
1884
|
-
}
|
|
1885
|
-
/**
|
|
1886
|
-
* Tokenize a search query. Queries use stop words and don't include originals.
|
|
1887
|
-
*/
|
|
1888
|
-
tokenizeQuery(query) {
|
|
1889
|
-
const words = query.trim().split(/\s+/);
|
|
1890
|
-
const tokens = [];
|
|
1891
|
-
for (const word of words) {
|
|
1892
|
-
const split = _SymbolTokenizer.splitName(word);
|
|
1893
|
-
for (const t of split) {
|
|
1894
|
-
const lowered = t.toLowerCase();
|
|
1895
|
-
if (lowered.length > 0 && !tokens.includes(lowered)) {
|
|
1896
|
-
tokens.push(lowered);
|
|
1897
|
-
}
|
|
1898
|
-
}
|
|
1899
|
-
}
|
|
1900
|
-
return tokens;
|
|
1901
|
-
}
|
|
1902
|
-
/**
|
|
1903
|
-
* Split a name into constituent words based on naming convention boundaries.
|
|
1904
|
-
*
|
|
1905
|
-
* Rules:
|
|
1906
|
-
* 1. Split on underscores and hyphens (snake_case, kebab-case)
|
|
1907
|
-
* 2. Split on camelCase/PascalCase boundaries (uppercase after lowercase)
|
|
1908
|
-
* 3. Split acronym boundaries (e.g., "HTMLParser" → ["HTML", "Parser"])
|
|
1909
|
-
* 4. Split on digit ↔ letter boundaries
|
|
1910
|
-
*/
|
|
1911
|
-
static splitName(name) {
|
|
1912
|
-
if (!name || name.length === 0) return [];
|
|
1913
|
-
const segments = name.split(/[_\-./\\]+/).filter((s) => s.length > 0);
|
|
1914
|
-
const tokens = [];
|
|
1915
|
-
for (const segment of segments) {
|
|
1916
|
-
tokens.push(..._SymbolTokenizer.splitCamelCase(segment));
|
|
1917
|
-
}
|
|
1918
|
-
return tokens.filter((t) => t.length > 0);
|
|
1919
|
-
}
|
|
1920
|
-
/**
|
|
1921
|
-
* Split a single segment on camelCase/PascalCase/digit boundaries.
|
|
1922
|
-
*/
|
|
1923
|
-
static splitCamelCase(segment) {
|
|
1924
|
-
const tokens = [];
|
|
1925
|
-
let current = "";
|
|
1926
|
-
for (let i = 0; i < segment.length; i++) {
|
|
1927
|
-
const ch = segment[i];
|
|
1928
|
-
const prev = i > 0 ? segment[i - 1] : "";
|
|
1929
|
-
if (current.length > 0) {
|
|
1930
|
-
const prevIsDigit = isDigit(prev);
|
|
1931
|
-
const currIsDigit = isDigit(ch);
|
|
1932
|
-
if (prevIsDigit !== currIsDigit) {
|
|
1933
|
-
tokens.push(current);
|
|
1934
|
-
current = ch;
|
|
1935
|
-
continue;
|
|
1936
|
-
}
|
|
1937
|
-
}
|
|
1938
|
-
if (current.length > 0 && isLower(prev) && isUpper(ch)) {
|
|
1939
|
-
tokens.push(current);
|
|
1940
|
-
current = ch;
|
|
1941
|
-
continue;
|
|
1942
|
-
}
|
|
1943
|
-
if (current.length > 1 && isUpper(ch) === false && isUpper(prev) && !isDigit(ch)) {
|
|
1944
|
-
const lastChar = current[current.length - 1];
|
|
1945
|
-
if (isUpper(lastChar)) {
|
|
1946
|
-
tokens.push(current.slice(0, -1));
|
|
1947
|
-
current = lastChar + ch;
|
|
1948
|
-
continue;
|
|
1949
|
-
}
|
|
1950
|
-
}
|
|
1951
|
-
current += ch;
|
|
1952
|
-
}
|
|
1953
|
-
if (current.length > 0) {
|
|
1954
|
-
tokens.push(current);
|
|
1955
|
-
}
|
|
1956
|
-
return tokens;
|
|
1957
|
-
}
|
|
1958
|
-
/**
|
|
1959
|
-
* Extract searchable tokens from a file path.
|
|
1960
|
-
* "src/core/search/symbol-tokenizer.ts" → ["src", "core", "search", "symbol", "tokenizer", "ts"]
|
|
1961
|
-
*/
|
|
1962
|
-
static tokenizeFilePath(filePath) {
|
|
1963
|
-
const withoutExt = filePath.replace(/\.[^.]+$/, "");
|
|
1964
|
-
const segments = withoutExt.split(/[/\\]+/);
|
|
1965
|
-
const tokens = [];
|
|
1966
|
-
for (const seg of segments) {
|
|
1967
|
-
const parts = seg.split(/[_\-.]+/).filter((s) => s.length > 0);
|
|
1968
|
-
for (const p of parts) {
|
|
1969
|
-
const lowered = p.toLowerCase();
|
|
1970
|
-
if (lowered.length > 0 && !tokens.includes(lowered)) {
|
|
1971
|
-
tokens.push(lowered);
|
|
1972
|
-
}
|
|
1973
|
-
}
|
|
1974
|
-
}
|
|
1975
|
-
return tokens;
|
|
1976
|
-
}
|
|
1977
|
-
/** Check if a token is a stop word */
|
|
1978
|
-
static isStopWord(token) {
|
|
1979
|
-
return STOP_WORDS.has(token.toLowerCase());
|
|
1980
|
-
}
|
|
1981
|
-
};
|
|
1982
|
-
function isUpper(ch) {
|
|
1983
|
-
return ch >= "A" && ch <= "Z";
|
|
1984
|
-
}
|
|
1985
|
-
function isLower(ch) {
|
|
1986
|
-
return ch >= "a" && ch <= "z";
|
|
1987
|
-
}
|
|
1988
|
-
function isDigit(ch) {
|
|
1989
|
-
return ch >= "0" && ch <= "9";
|
|
1990
|
-
}
|
|
1991
|
-
|
|
1992
1819
|
export {
|
|
1993
1820
|
detectWorkspace,
|
|
1994
1821
|
buildCoChangeMatrix,
|
|
1995
1822
|
CoChangeReader,
|
|
1996
|
-
SymbolTokenizer,
|
|
1997
1823
|
CommunitySnapshotWriter,
|
|
1998
1824
|
SqliteStorageAdapter,
|
|
1999
1825
|
RevertDetector,
|
|
@@ -2007,4 +1833,4 @@ export {
|
|
|
2007
1833
|
findCoChangedFilesForBlastRadius,
|
|
2008
1834
|
ContextsViewReader
|
|
2009
1835
|
};
|
|
2010
|
-
//# sourceMappingURL=chunk-
|
|
1836
|
+
//# sourceMappingURL=chunk-A4URKKPP.js.map
|
|
@@ -96,7 +96,7 @@ async function loadModulesFor(projectRoot, mode, options = {}) {
|
|
|
96
96
|
import("./plugin-discovery-XRXI3IJF.js")
|
|
97
97
|
]);
|
|
98
98
|
if (resolvePackageCandidates(DOMAIN_MODULE_PACKAGE, projectRoot, options.cliAnchor).length === 0) return [];
|
|
99
|
-
const { createModuleHost } = await import("./module-host-
|
|
99
|
+
const { createModuleHost } = await import("./module-host-KTGDODES.js");
|
|
100
100
|
const { modules } = await loadCtxoModules(createModuleHost({ projectRoot, mode }), { cliAnchor: options.cliAnchor });
|
|
101
101
|
return modules;
|
|
102
102
|
} catch (err) {
|
|
@@ -125,4 +125,4 @@ export {
|
|
|
125
125
|
loadModulesFor,
|
|
126
126
|
runFastRoute
|
|
127
127
|
};
|
|
128
|
-
//# sourceMappingURL=chunk-
|
|
128
|
+
//# sourceMappingURL=chunk-BBSJB4CG.js.map
|
|
@@ -6,6 +6,197 @@ import {
|
|
|
6
6
|
createLogger
|
|
7
7
|
} from "./chunk-5DSKZJNN.js";
|
|
8
8
|
|
|
9
|
+
// src/core/search/symbol-tokenizer.ts
|
|
10
|
+
var STOP_WORDS = /* @__PURE__ */ new Set([
|
|
11
|
+
"get",
|
|
12
|
+
"set",
|
|
13
|
+
"is",
|
|
14
|
+
"has",
|
|
15
|
+
"can",
|
|
16
|
+
"do",
|
|
17
|
+
"the",
|
|
18
|
+
"a",
|
|
19
|
+
"an",
|
|
20
|
+
"of",
|
|
21
|
+
"to",
|
|
22
|
+
"in",
|
|
23
|
+
"for",
|
|
24
|
+
"on",
|
|
25
|
+
"with",
|
|
26
|
+
"at",
|
|
27
|
+
"by",
|
|
28
|
+
"from",
|
|
29
|
+
"as",
|
|
30
|
+
"into",
|
|
31
|
+
"not",
|
|
32
|
+
"no",
|
|
33
|
+
"or"
|
|
34
|
+
]);
|
|
35
|
+
var DEFAULT_OPTIONS = {
|
|
36
|
+
includeStopWords: false,
|
|
37
|
+
includeOriginal: true,
|
|
38
|
+
includeFilePath: false
|
|
39
|
+
};
|
|
40
|
+
var SymbolTokenizer = class _SymbolTokenizer {
|
|
41
|
+
options;
|
|
42
|
+
constructor(options) {
|
|
43
|
+
this.options = { ...DEFAULT_OPTIONS, ...options };
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Tokenize a symbol name into searchable tokens.
|
|
47
|
+
*
|
|
48
|
+
* Examples:
|
|
49
|
+
* "getCoChangeMetrics" → ["get", "co", "change", "metrics"] (or without stop words: ["co", "change", "metrics"])
|
|
50
|
+
* "SqliteStorageAdapter" → ["sqlite", "storage", "adapter"]
|
|
51
|
+
* "i_storage_port" → ["i", "storage", "port"]
|
|
52
|
+
* "BM25Scorer" → ["bm", "25", "scorer"]
|
|
53
|
+
* "DB_PASSWORD" → ["db", "password"]
|
|
54
|
+
*/
|
|
55
|
+
tokenize(symbolName, filePath) {
|
|
56
|
+
const splitTokens = _SymbolTokenizer.splitName(symbolName);
|
|
57
|
+
const lowered = splitTokens.map((t) => t.toLowerCase());
|
|
58
|
+
const tokens = this.options.includeStopWords ? lowered : lowered.filter((t) => !STOP_WORDS.has(t));
|
|
59
|
+
if (this.options.includeOriginal) {
|
|
60
|
+
const original = symbolName.toLowerCase();
|
|
61
|
+
if (!tokens.includes(original)) {
|
|
62
|
+
tokens.unshift(original);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if (this.options.includeFilePath && filePath) {
|
|
66
|
+
const pathTokens = _SymbolTokenizer.tokenizeFilePath(filePath);
|
|
67
|
+
for (const pt of pathTokens) {
|
|
68
|
+
if (!tokens.includes(pt)) {
|
|
69
|
+
tokens.push(pt);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return tokens;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Tokenize prose (a document title or body). Words are runs of letters, digits, `_` and `-`; each word is
|
|
77
|
+
* split like a symbol name (camelCase, snake_case, rule IDs such as `LEND-03`) and lowercased. Stop words
|
|
78
|
+
* are dropped; repeats are kept, so BM25 sees the term frequency.
|
|
79
|
+
*
|
|
80
|
+
* "A member holds LoanPolicy LEND-03" → ["member", "holds", "loan", "policy", "lend", "03"]
|
|
81
|
+
*/
|
|
82
|
+
tokenizeText(text) {
|
|
83
|
+
const tokens = [];
|
|
84
|
+
for (const word of text.split(/[^\p{L}\p{N}_-]+/u)) {
|
|
85
|
+
if (word.length === 0) continue;
|
|
86
|
+
for (const part of _SymbolTokenizer.splitName(word)) {
|
|
87
|
+
const lowered = part.toLowerCase();
|
|
88
|
+
if (!STOP_WORDS.has(lowered)) tokens.push(lowered);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return tokens;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Tokenize a search query. Queries use stop words and don't include originals.
|
|
95
|
+
*/
|
|
96
|
+
tokenizeQuery(query) {
|
|
97
|
+
const words = query.trim().split(/\s+/);
|
|
98
|
+
const tokens = [];
|
|
99
|
+
for (const word of words) {
|
|
100
|
+
const split = _SymbolTokenizer.splitName(word);
|
|
101
|
+
for (const t of split) {
|
|
102
|
+
const lowered = t.toLowerCase();
|
|
103
|
+
if (lowered.length > 0 && !tokens.includes(lowered)) {
|
|
104
|
+
tokens.push(lowered);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return tokens;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Split a name into constituent words based on naming convention boundaries.
|
|
112
|
+
*
|
|
113
|
+
* Rules:
|
|
114
|
+
* 1. Split on underscores and hyphens (snake_case, kebab-case)
|
|
115
|
+
* 2. Split on camelCase/PascalCase boundaries (uppercase after lowercase)
|
|
116
|
+
* 3. Split acronym boundaries (e.g., "HTMLParser" → ["HTML", "Parser"])
|
|
117
|
+
* 4. Split on digit ↔ letter boundaries
|
|
118
|
+
*/
|
|
119
|
+
static splitName(name) {
|
|
120
|
+
if (!name || name.length === 0) return [];
|
|
121
|
+
const segments = name.split(/[_\-./\\]+/).filter((s) => s.length > 0);
|
|
122
|
+
const tokens = [];
|
|
123
|
+
for (const segment of segments) {
|
|
124
|
+
tokens.push(..._SymbolTokenizer.splitCamelCase(segment));
|
|
125
|
+
}
|
|
126
|
+
return tokens.filter((t) => t.length > 0);
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Split a single segment on camelCase/PascalCase/digit boundaries.
|
|
130
|
+
*/
|
|
131
|
+
static splitCamelCase(segment) {
|
|
132
|
+
const tokens = [];
|
|
133
|
+
let current = "";
|
|
134
|
+
for (let i = 0; i < segment.length; i++) {
|
|
135
|
+
const ch = segment[i];
|
|
136
|
+
const prev = i > 0 ? segment[i - 1] : "";
|
|
137
|
+
if (current.length > 0) {
|
|
138
|
+
const prevIsDigit = isDigit(prev);
|
|
139
|
+
const currIsDigit = isDigit(ch);
|
|
140
|
+
if (prevIsDigit !== currIsDigit) {
|
|
141
|
+
tokens.push(current);
|
|
142
|
+
current = ch;
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
if (current.length > 0 && isLower(prev) && isUpper(ch)) {
|
|
147
|
+
tokens.push(current);
|
|
148
|
+
current = ch;
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
if (current.length > 1 && isUpper(ch) === false && isUpper(prev) && !isDigit(ch)) {
|
|
152
|
+
const lastChar = current[current.length - 1];
|
|
153
|
+
if (isUpper(lastChar)) {
|
|
154
|
+
tokens.push(current.slice(0, -1));
|
|
155
|
+
current = lastChar + ch;
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
current += ch;
|
|
160
|
+
}
|
|
161
|
+
if (current.length > 0) {
|
|
162
|
+
tokens.push(current);
|
|
163
|
+
}
|
|
164
|
+
return tokens;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Extract searchable tokens from a file path.
|
|
168
|
+
* "src/core/search/symbol-tokenizer.ts" → ["src", "core", "search", "symbol", "tokenizer", "ts"]
|
|
169
|
+
*/
|
|
170
|
+
static tokenizeFilePath(filePath) {
|
|
171
|
+
const withoutExt = filePath.replace(/\.[^.]+$/, "");
|
|
172
|
+
const segments = withoutExt.split(/[/\\]+/);
|
|
173
|
+
const tokens = [];
|
|
174
|
+
for (const seg of segments) {
|
|
175
|
+
const parts = seg.split(/[_\-.]+/).filter((s) => s.length > 0);
|
|
176
|
+
for (const p of parts) {
|
|
177
|
+
const lowered = p.toLowerCase();
|
|
178
|
+
if (lowered.length > 0 && !tokens.includes(lowered)) {
|
|
179
|
+
tokens.push(lowered);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return tokens;
|
|
184
|
+
}
|
|
185
|
+
/** Check if a token is a stop word */
|
|
186
|
+
static isStopWord(token) {
|
|
187
|
+
return STOP_WORDS.has(token.toLowerCase());
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
function isUpper(ch) {
|
|
191
|
+
return ch >= "A" && ch <= "Z";
|
|
192
|
+
}
|
|
193
|
+
function isLower(ch) {
|
|
194
|
+
return ch >= "a" && ch <= "z";
|
|
195
|
+
}
|
|
196
|
+
function isDigit(ch) {
|
|
197
|
+
return ch >= "0" && ch <= "9";
|
|
198
|
+
}
|
|
199
|
+
|
|
9
200
|
// src/core/co-change/co-change-window.ts
|
|
10
201
|
var SINCE_PATTERN = /^(\d+)([dwmy])$/;
|
|
11
202
|
var SECONDS_PER_DAY = 86400;
|
|
@@ -1022,6 +1213,7 @@ var SymbolGraph = class {
|
|
|
1022
1213
|
};
|
|
1023
1214
|
|
|
1024
1215
|
export {
|
|
1216
|
+
SymbolTokenizer,
|
|
1025
1217
|
parseCoChangeSince,
|
|
1026
1218
|
windowCutoff,
|
|
1027
1219
|
selectWindow,
|
|
@@ -1046,4 +1238,4 @@ export {
|
|
|
1046
1238
|
isDataResourceKind,
|
|
1047
1239
|
SymbolGraph
|
|
1048
1240
|
};
|
|
1049
|
-
//# sourceMappingURL=chunk-
|
|
1241
|
+
//# sourceMappingURL=chunk-I7RPXT6L.js.map
|