opencode-agora 0.4.2 → 0.4.3

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 (184) hide show
  1. package/README.md +42 -2
  2. package/dist/cli/app.d.ts.map +1 -1
  3. package/dist/cli/app.js +14 -0
  4. package/dist/cli/app.js.map +1 -1
  5. package/dist/cli/commands/browse.d.ts.map +1 -1
  6. package/dist/cli/commands/browse.js +1 -5
  7. package/dist/cli/commands/browse.js.map +1 -1
  8. package/dist/cli/commands/capabilities.d.ts +3 -0
  9. package/dist/cli/commands/capabilities.d.ts.map +1 -0
  10. package/dist/cli/commands/capabilities.js +146 -0
  11. package/dist/cli/commands/capabilities.js.map +1 -0
  12. package/dist/cli/commands/community.d.ts.map +1 -1
  13. package/dist/cli/commands/community.js.map +1 -1
  14. package/dist/cli/commands/curate.d.ts +9 -0
  15. package/dist/cli/commands/curate.d.ts.map +1 -0
  16. package/dist/cli/commands/curate.js +62 -0
  17. package/dist/cli/commands/curate.js.map +1 -0
  18. package/dist/cli/commands/doctor.d.ts +3 -0
  19. package/dist/cli/commands/doctor.d.ts.map +1 -0
  20. package/dist/cli/commands/doctor.js +77 -0
  21. package/dist/cli/commands/doctor.js.map +1 -0
  22. package/dist/cli/commands/export.d.ts.map +1 -1
  23. package/dist/cli/commands/export.js +5 -3
  24. package/dist/cli/commands/export.js.map +1 -1
  25. package/dist/cli/commands/freeze.d.ts +3 -0
  26. package/dist/cli/commands/freeze.d.ts.map +1 -0
  27. package/dist/cli/commands/freeze.js +62 -0
  28. package/dist/cli/commands/freeze.js.map +1 -0
  29. package/dist/cli/commands/init.d.ts.map +1 -1
  30. package/dist/cli/commands/init.js +5 -1
  31. package/dist/cli/commands/init.js.map +1 -1
  32. package/dist/cli/commands/installed.d.ts +3 -0
  33. package/dist/cli/commands/installed.d.ts.map +1 -0
  34. package/dist/cli/commands/installed.js +82 -0
  35. package/dist/cli/commands/installed.js.map +1 -0
  36. package/dist/cli/commands/marketplace.d.ts.map +1 -1
  37. package/dist/cli/commands/marketplace.js.map +1 -1
  38. package/dist/cli/commands/notify.d.ts.map +1 -1
  39. package/dist/cli/commands/notify.js.map +1 -1
  40. package/dist/cli/commands/operations.d.ts.map +1 -1
  41. package/dist/cli/commands/operations.js +64 -1
  42. package/dist/cli/commands/operations.js.map +1 -1
  43. package/dist/cli/commands/ping.js +2 -1
  44. package/dist/cli/commands/ping.js.map +1 -1
  45. package/dist/cli/commands/scan.js.map +1 -1
  46. package/dist/cli/commands/sync.d.ts +3 -0
  47. package/dist/cli/commands/sync.d.ts.map +1 -0
  48. package/dist/cli/commands/sync.js +172 -0
  49. package/dist/cli/commands/sync.js.map +1 -0
  50. package/dist/cli/commands/today.d.ts.map +1 -1
  51. package/dist/cli/commands/today.js +11 -3
  52. package/dist/cli/commands/today.js.map +1 -1
  53. package/dist/cli/commands/try.d.ts +3 -0
  54. package/dist/cli/commands/try.d.ts.map +1 -0
  55. package/dist/cli/commands/try.js +157 -0
  56. package/dist/cli/commands/try.js.map +1 -0
  57. package/dist/cli/commands/watch.d.ts.map +1 -1
  58. package/dist/cli/commands/watch.js.map +1 -1
  59. package/dist/cli/commands/welcome.d.ts.map +1 -1
  60. package/dist/cli/commands/welcome.js +6 -27
  61. package/dist/cli/commands/welcome.js.map +1 -1
  62. package/dist/cli/commands-meta.d.ts +1 -1
  63. package/dist/cli/commands-meta.d.ts.map +1 -1
  64. package/dist/cli/commands-meta.js +222 -21
  65. package/dist/cli/commands-meta.js.map +1 -1
  66. package/dist/cli/completions-gen.d.ts.map +1 -1
  67. package/dist/cli/completions-gen.js +130 -52
  68. package/dist/cli/completions-gen.js.map +1 -1
  69. package/dist/cli/flags.d.ts.map +1 -1
  70. package/dist/cli/flags.js +7 -0
  71. package/dist/cli/flags.js.map +1 -1
  72. package/dist/cli/format.js +1 -1
  73. package/dist/cli/format.js.map +1 -1
  74. package/dist/cli/helpers.d.ts.map +1 -1
  75. package/dist/cli/helpers.js.map +1 -1
  76. package/dist/cli/mcp-server.d.ts +5 -0
  77. package/dist/cli/mcp-server.d.ts.map +1 -1
  78. package/dist/cli/mcp-server.js +222 -0
  79. package/dist/cli/mcp-server.js.map +1 -1
  80. package/dist/cli/pages/community.d.ts.map +1 -1
  81. package/dist/cli/pages/community.js +4 -7
  82. package/dist/cli/pages/community.js.map +1 -1
  83. package/dist/cli/pages/home.d.ts.map +1 -1
  84. package/dist/cli/pages/home.js +22 -8
  85. package/dist/cli/pages/home.js.map +1 -1
  86. package/dist/cli/pages/marketplace.d.ts.map +1 -1
  87. package/dist/cli/pages/marketplace.js +12 -9
  88. package/dist/cli/pages/marketplace.js.map +1 -1
  89. package/dist/cli/pages/news.d.ts.map +1 -1
  90. package/dist/cli/pages/news.js.map +1 -1
  91. package/dist/cli/pages/stack.d.ts +3 -0
  92. package/dist/cli/pages/stack.d.ts.map +1 -0
  93. package/dist/cli/pages/stack.js +373 -0
  94. package/dist/cli/pages/stack.js.map +1 -0
  95. package/dist/cli/pages/types.d.ts +1 -1
  96. package/dist/cli/pages/types.d.ts.map +1 -1
  97. package/dist/cli/shell.d.ts +1 -1
  98. package/dist/cli/shell.d.ts.map +1 -1
  99. package/dist/cli/shell.js +52 -2
  100. package/dist/cli/shell.js.map +1 -1
  101. package/dist/cli/tui.d.ts.map +1 -1
  102. package/dist/cli/tui.js +14 -4
  103. package/dist/cli/tui.js.map +1 -1
  104. package/dist/community/client.d.ts.map +1 -1
  105. package/dist/community/client.js +3 -1
  106. package/dist/community/client.js.map +1 -1
  107. package/dist/curator/index.d.ts +95 -0
  108. package/dist/curator/index.d.ts.map +1 -0
  109. package/dist/curator/index.js +446 -0
  110. package/dist/curator/index.js.map +1 -0
  111. package/dist/hubs/enrichment.d.ts.map +1 -1
  112. package/dist/hubs/enrichment.js.map +1 -1
  113. package/dist/index.d.ts.map +1 -1
  114. package/dist/index.js +3 -1
  115. package/dist/index.js.map +1 -1
  116. package/dist/marketplace.d.ts +2 -1
  117. package/dist/marketplace.d.ts.map +1 -1
  118. package/dist/marketplace.js +86 -17
  119. package/dist/marketplace.js.map +1 -1
  120. package/dist/outdated.d.ts.map +1 -1
  121. package/dist/outdated.js +25 -4
  122. package/dist/outdated.js.map +1 -1
  123. package/dist/scan.d.ts.map +1 -1
  124. package/dist/scan.js +83 -15
  125. package/dist/scan.js.map +1 -1
  126. package/dist/search/catalog-index.d.ts +107 -0
  127. package/dist/search/catalog-index.d.ts.map +1 -0
  128. package/dist/search/catalog-index.js +276 -0
  129. package/dist/search/catalog-index.js.map +1 -0
  130. package/dist/stack/adapters/claude-code.d.ts +3 -0
  131. package/dist/stack/adapters/claude-code.d.ts.map +1 -0
  132. package/dist/stack/adapters/claude-code.js +282 -0
  133. package/dist/stack/adapters/claude-code.js.map +1 -0
  134. package/dist/stack/adapters/cursor.d.ts +3 -0
  135. package/dist/stack/adapters/cursor.d.ts.map +1 -0
  136. package/dist/stack/adapters/cursor.js +232 -0
  137. package/dist/stack/adapters/cursor.js.map +1 -0
  138. package/dist/stack/adapters/opencode.d.ts +3 -0
  139. package/dist/stack/adapters/opencode.d.ts.map +1 -0
  140. package/dist/stack/adapters/opencode.js +177 -0
  141. package/dist/stack/adapters/opencode.js.map +1 -0
  142. package/dist/stack/adapters/windsurf.d.ts +3 -0
  143. package/dist/stack/adapters/windsurf.d.ts.map +1 -0
  144. package/dist/stack/adapters/windsurf.js +218 -0
  145. package/dist/stack/adapters/windsurf.js.map +1 -0
  146. package/dist/stack/capability-cache.d.ts +19 -0
  147. package/dist/stack/capability-cache.d.ts.map +1 -0
  148. package/dist/stack/capability-cache.js +41 -0
  149. package/dist/stack/capability-cache.js.map +1 -0
  150. package/dist/stack/doctor.d.ts +30 -0
  151. package/dist/stack/doctor.d.ts.map +1 -0
  152. package/dist/stack/doctor.js +227 -0
  153. package/dist/stack/doctor.js.map +1 -0
  154. package/dist/stack/manifest.d.ts +40 -0
  155. package/dist/stack/manifest.d.ts.map +1 -0
  156. package/dist/stack/manifest.js +342 -0
  157. package/dist/stack/manifest.js.map +1 -0
  158. package/dist/stack/mcp-probe.d.ts +23 -0
  159. package/dist/stack/mcp-probe.d.ts.map +1 -0
  160. package/dist/stack/mcp-probe.js +230 -0
  161. package/dist/stack/mcp-probe.js.map +1 -0
  162. package/dist/stack/path-resolve.d.ts +7 -0
  163. package/dist/stack/path-resolve.d.ts.map +1 -0
  164. package/dist/stack/path-resolve.js +37 -0
  165. package/dist/stack/path-resolve.js.map +1 -0
  166. package/dist/stack/registry.d.ts +11 -0
  167. package/dist/stack/registry.d.ts.map +1 -0
  168. package/dist/stack/registry.js +42 -0
  169. package/dist/stack/registry.js.map +1 -0
  170. package/dist/stack/sync.d.ts +20 -0
  171. package/dist/stack/sync.d.ts.map +1 -0
  172. package/dist/stack/sync.js +226 -0
  173. package/dist/stack/sync.js.map +1 -0
  174. package/dist/stack/types.d.ts +53 -0
  175. package/dist/stack/types.d.ts.map +1 -0
  176. package/dist/stack/types.js +2 -0
  177. package/dist/stack/types.js.map +1 -0
  178. package/dist/transcript.d.ts +14 -0
  179. package/dist/transcript.d.ts.map +1 -1
  180. package/dist/transcript.js +98 -1
  181. package/dist/transcript.js.map +1 -1
  182. package/dist/types.d.ts +4 -0
  183. package/dist/types.d.ts.map +1 -1
  184. package/package.json +2 -1
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Offline BM25 inverted-index for the Agora marketplace catalog.
3
+ *
4
+ * ## Why BM25?
5
+ * BM25 (Best Match 25) is a probabilistic term-frequency / inverse-document-frequency
6
+ * ranking function that handles two key issues with naive TF-IDF:
7
+ * 1. Term-frequency saturation — a token appearing 100× in a doc is not 100× as
8
+ * relevant as one appearing once; BM25 saturates via (tf / (tf + k1*(1-b+b*dl/avgdl))).
9
+ * 2. Document-length normalization — long documents naturally accumulate more term
10
+ * hits; the `b` parameter (0–1) penalizes them proportionally.
11
+ *
12
+ * ## Field weighting via token repetition
13
+ * Rather than maintaining separate per-field inverted lists (which complicates the
14
+ * index structure), we inflate the term-frequency bag at index time:
15
+ * - name token contributes ×3 to TF (most signal: users search by name)
16
+ * - tags token contributes ×2 (curated semantic labels)
17
+ * - id token contributes ×2 (IDs are precise matches)
18
+ * - description/author/category ×1 (background context)
19
+ *
20
+ * This is equivalent to weighted field merging and requires zero extra complexity in
21
+ * the scoring loop.
22
+ *
23
+ * ## Fully offline, zero dependencies
24
+ * Matches Wave 1 "no external accounts / no network" constraint. The index is
25
+ * rebuilt in-memory from the item list; it never persists to disk and needs no
26
+ * embedding model or API key.
27
+ */
28
+ export interface IndexableItem {
29
+ id: string;
30
+ name: string;
31
+ description: string;
32
+ author: string;
33
+ category: string;
34
+ tags: string[];
35
+ }
36
+ /** Per-document posting entry stored in the inverted list. */
37
+ interface Posting {
38
+ id: string;
39
+ tf: number;
40
+ }
41
+ export interface CatalogIndex {
42
+ /** postings[term] = array of {id, tf} for every doc containing that term */
43
+ postings: Map<string, Posting[]>;
44
+ /** df[term] = number of distinct documents containing that term */
45
+ df: Map<string, number>;
46
+ /** docLen[id] = sum of all weighted TF values for that document */
47
+ docLen: Map<string, number>;
48
+ /** average document length across all indexed documents */
49
+ avgDocLen: number;
50
+ /** total number of indexed documents */
51
+ N: number;
52
+ }
53
+ /**
54
+ * Combined English stopwords + intent/filler words.
55
+ * Intent words are stripped so that queries like
56
+ * "find a tool that talks to postgres" reduce to their content terms.
57
+ */
58
+ export declare const STOPWORDS: Set<string>;
59
+ /**
60
+ * Tokenize text for indexing or querying:
61
+ * - lowercase
62
+ * - split on non-alphanumeric runs
63
+ * - drop tokens shorter than 2 characters
64
+ * - drop STOPWORDS
65
+ *
66
+ * Deterministic: identical input always yields identical output.
67
+ */
68
+ export declare function tokenize(text: string): string[];
69
+ /**
70
+ * Developer-term synonym map. Applied ONLY during query tokenization, not at
71
+ * index time, so documents are indexed verbatim (no spurious term inflation).
72
+ *
73
+ * When a query token matches a key, its synonyms are added as additional query
74
+ * terms (deduped). This allows short aliases ("db", "k8s") to match their full
75
+ * forms in document text.
76
+ */
77
+ export declare const SYNONYMS: Record<string, string[]>;
78
+ /**
79
+ * Tokenize a query string and expand synonyms.
80
+ * Returns a deduplicated array of tokens (original + synonym expansions).
81
+ */
82
+ export declare function tokenizeQuery(text: string): string[];
83
+ /**
84
+ * Build a BM25 inverted index from a list of indexable items.
85
+ *
86
+ * Field weighting is achieved by repeating tokens in the TF bag:
87
+ * name ×3, tags ×2, id ×2, description ×1, author ×1, category ×1
88
+ */
89
+ export declare function buildIndex(items: IndexableItem[]): CatalogIndex;
90
+ /**
91
+ * Search the index with BM25 scoring.
92
+ *
93
+ * @param index - built by buildIndex()
94
+ * @param query - raw query string (will be tokenized + synonym-expanded)
95
+ * @param opts - BM25 hyperparameters (defaults: k1=1.5, b=0.75)
96
+ * @returns - scored results sorted by score descending; empty array if
97
+ * query is blank or no documents match
98
+ */
99
+ export declare function searchIndex(index: CatalogIndex, query: string, opts?: {
100
+ k1?: number;
101
+ b?: number;
102
+ }): {
103
+ id: string;
104
+ score: number;
105
+ }[];
106
+ export {};
107
+ //# sourceMappingURL=catalog-index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalog-index.d.ts","sourceRoot":"","sources":["../../src/search/catalog-index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,EAAE,CAAC;CAChB;AAED,8DAA8D;AAC9D,UAAU,OAAO;IACf,EAAE,EAAE,MAAM,CAAC;IACX,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,MAAM,WAAW,YAAY;IAC3B,4EAA4E;IAC5E,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC,CAAC;IACjC,mEAAmE;IACnE,EAAE,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACxB,mEAAmE;IACnE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5B,2DAA2D;IAC3D,SAAS,EAAE,MAAM,CAAC;IAClB,wCAAwC;IACxC,CAAC,EAAE,MAAM,CAAC;CACX;AAID;;;;GAIG;AACH,eAAO,MAAM,SAAS,EAAE,GAAG,CAAC,MAAM,CAiFhC,CAAC;AAIH;;;;;;;;GAQG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAK/C;AAID;;;;;;;GAOG;AACH,eAAO,MAAM,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAsB7C,CAAC;AAEF;;;GAGG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAYpD;AAID;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,aAAa,EAAE,GAAG,YAAY,CAqD/D;AAID;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CACzB,KAAK,EAAE,YAAY,EACnB,KAAK,EAAE,MAAM,EACb,IAAI,CAAC,EAAE;IAAE,EAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,CAAC,EAAE,MAAM,CAAA;CAAE,GACjC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,EAAE,CAoCjC"}
@@ -0,0 +1,276 @@
1
+ /**
2
+ * Offline BM25 inverted-index for the Agora marketplace catalog.
3
+ *
4
+ * ## Why BM25?
5
+ * BM25 (Best Match 25) is a probabilistic term-frequency / inverse-document-frequency
6
+ * ranking function that handles two key issues with naive TF-IDF:
7
+ * 1. Term-frequency saturation — a token appearing 100× in a doc is not 100× as
8
+ * relevant as one appearing once; BM25 saturates via (tf / (tf + k1*(1-b+b*dl/avgdl))).
9
+ * 2. Document-length normalization — long documents naturally accumulate more term
10
+ * hits; the `b` parameter (0–1) penalizes them proportionally.
11
+ *
12
+ * ## Field weighting via token repetition
13
+ * Rather than maintaining separate per-field inverted lists (which complicates the
14
+ * index structure), we inflate the term-frequency bag at index time:
15
+ * - name token contributes ×3 to TF (most signal: users search by name)
16
+ * - tags token contributes ×2 (curated semantic labels)
17
+ * - id token contributes ×2 (IDs are precise matches)
18
+ * - description/author/category ×1 (background context)
19
+ *
20
+ * This is equivalent to weighted field merging and requires zero extra complexity in
21
+ * the scoring loop.
22
+ *
23
+ * ## Fully offline, zero dependencies
24
+ * Matches Wave 1 "no external accounts / no network" constraint. The index is
25
+ * rebuilt in-memory from the item list; it never persists to disk and needs no
26
+ * embedding model or API key.
27
+ */
28
+ // ── Stopwords ─────────────────────────────────────────────────────────────────
29
+ /**
30
+ * Combined English stopwords + intent/filler words.
31
+ * Intent words are stripped so that queries like
32
+ * "find a tool that talks to postgres" reduce to their content terms.
33
+ */
34
+ export const STOPWORDS = new Set([
35
+ // English function words
36
+ 'the',
37
+ 'a',
38
+ 'an',
39
+ 'to',
40
+ 'of',
41
+ 'for',
42
+ 'and',
43
+ 'or',
44
+ 'with',
45
+ 'that',
46
+ 'this',
47
+ 'my',
48
+ 'i',
49
+ 'in',
50
+ 'on',
51
+ 'is',
52
+ 'are',
53
+ 'it',
54
+ 'by',
55
+ 'at',
56
+ 'as',
57
+ 'be',
58
+ 'was',
59
+ 'has',
60
+ 'have',
61
+ 'not',
62
+ 'its',
63
+ 'from',
64
+ 'into',
65
+ 'than',
66
+ 'but',
67
+ 'about',
68
+ // Intent / filler words common in natural-language queries
69
+ 'find',
70
+ 'search',
71
+ 'show',
72
+ 'get',
73
+ 'need',
74
+ 'want',
75
+ 'looking',
76
+ 'something',
77
+ 'anything',
78
+ 'tool',
79
+ 'tools',
80
+ 'thing',
81
+ 'things',
82
+ 'does',
83
+ 'do',
84
+ 'help',
85
+ 'please',
86
+ 'can',
87
+ 'give',
88
+ 'use',
89
+ 'using',
90
+ 'used',
91
+ 'like',
92
+ 'also',
93
+ 'which',
94
+ 'how',
95
+ 'what',
96
+ 'where',
97
+ 'when',
98
+ 'why',
99
+ 'who',
100
+ 'some',
101
+ 'any',
102
+ 'all',
103
+ 'talks',
104
+ 'talk',
105
+ 'connect',
106
+ 'connects',
107
+ 'access',
108
+ 'accesses',
109
+ 'let',
110
+ 'lets',
111
+ 'work',
112
+ 'works',
113
+ 'support',
114
+ 'supports'
115
+ ]);
116
+ // ── Tokenizer ─────────────────────────────────────────────────────────────────
117
+ /**
118
+ * Tokenize text for indexing or querying:
119
+ * - lowercase
120
+ * - split on non-alphanumeric runs
121
+ * - drop tokens shorter than 2 characters
122
+ * - drop STOPWORDS
123
+ *
124
+ * Deterministic: identical input always yields identical output.
125
+ */
126
+ export function tokenize(text) {
127
+ return text
128
+ .toLowerCase()
129
+ .split(/[^a-z0-9]+/)
130
+ .filter((t) => t.length >= 2 && !STOPWORDS.has(t));
131
+ }
132
+ // ── Synonyms (query-side expansion only) ─────────────────────────────────────
133
+ /**
134
+ * Developer-term synonym map. Applied ONLY during query tokenization, not at
135
+ * index time, so documents are indexed verbatim (no spurious term inflation).
136
+ *
137
+ * When a query token matches a key, its synonyms are added as additional query
138
+ * terms (deduped). This allows short aliases ("db", "k8s") to match their full
139
+ * forms in document text.
140
+ */
141
+ export const SYNONYMS = {
142
+ db: ['database'],
143
+ k8s: ['kubernetes'],
144
+ k8: ['kubernetes'],
145
+ postgres: ['postgresql', 'database'],
146
+ pg: ['postgresql', 'database'],
147
+ js: ['javascript'],
148
+ ts: ['typescript'],
149
+ ai: ['llm'],
150
+ auth: ['authentication'],
151
+ vcs: ['git'],
152
+ ml: ['machine', 'learning'],
153
+ api: ['integration'],
154
+ cli: ['command'],
155
+ ui: ['interface'],
156
+ gh: ['github'],
157
+ gl: ['gitlab'],
158
+ aws: ['amazon'],
159
+ gcp: ['google'],
160
+ kv: ['cache'],
161
+ nosql: ['database'],
162
+ sql: ['database']
163
+ };
164
+ /**
165
+ * Tokenize a query string and expand synonyms.
166
+ * Returns a deduplicated array of tokens (original + synonym expansions).
167
+ */
168
+ export function tokenizeQuery(text) {
169
+ const base = tokenize(text);
170
+ const expanded = new Set(base);
171
+ for (const token of base) {
172
+ const syns = SYNONYMS[token];
173
+ if (syns) {
174
+ for (const syn of syns) {
175
+ expanded.add(syn);
176
+ }
177
+ }
178
+ }
179
+ return Array.from(expanded);
180
+ }
181
+ // ── Index builder ─────────────────────────────────────────────────────────────
182
+ /**
183
+ * Build a BM25 inverted index from a list of indexable items.
184
+ *
185
+ * Field weighting is achieved by repeating tokens in the TF bag:
186
+ * name ×3, tags ×2, id ×2, description ×1, author ×1, category ×1
187
+ */
188
+ export function buildIndex(items) {
189
+ const postings = new Map();
190
+ const df = new Map();
191
+ const docLen = new Map();
192
+ let totalLen = 0;
193
+ for (const item of items) {
194
+ // Build weighted TF bag for this document
195
+ const tfBag = new Map();
196
+ function addTokens(text, weight) {
197
+ for (const token of tokenize(text)) {
198
+ tfBag.set(token, (tfBag.get(token) ?? 0) + weight);
199
+ }
200
+ }
201
+ addTokens(item.name, 3);
202
+ for (const tag of item.tags) {
203
+ addTokens(tag, 2);
204
+ }
205
+ addTokens(item.id, 2);
206
+ addTokens(item.description, 1);
207
+ addTokens(item.author, 1);
208
+ addTokens(item.category, 1);
209
+ // Document length = sum of weighted TFs
210
+ let dl = 0;
211
+ for (const tf of tfBag.values()) {
212
+ dl += tf;
213
+ }
214
+ docLen.set(item.id, dl);
215
+ totalLen += dl;
216
+ // Update postings and DF
217
+ for (const [term, tf] of tfBag.entries()) {
218
+ // DF: count this document once per term
219
+ df.set(term, (df.get(term) ?? 0) + 1);
220
+ // Postings list
221
+ let list = postings.get(term);
222
+ if (!list) {
223
+ list = [];
224
+ postings.set(term, list);
225
+ }
226
+ list.push({ id: item.id, tf });
227
+ }
228
+ }
229
+ const N = items.length;
230
+ const avgDocLen = N > 0 ? totalLen / N : 1;
231
+ return { postings, df, docLen, avgDocLen, N };
232
+ }
233
+ // ── BM25 scorer ───────────────────────────────────────────────────────────────
234
+ /**
235
+ * Search the index with BM25 scoring.
236
+ *
237
+ * @param index - built by buildIndex()
238
+ * @param query - raw query string (will be tokenized + synonym-expanded)
239
+ * @param opts - BM25 hyperparameters (defaults: k1=1.5, b=0.75)
240
+ * @returns - scored results sorted by score descending; empty array if
241
+ * query is blank or no documents match
242
+ */
243
+ export function searchIndex(index, query, opts) {
244
+ if (!query || !query.trim())
245
+ return [];
246
+ const k1 = opts?.k1 ?? 1.5;
247
+ const b = opts?.b ?? 0.75;
248
+ const { postings, df, docLen, avgDocLen, N } = index;
249
+ const queryTokens = tokenizeQuery(query);
250
+ if (queryTokens.length === 0)
251
+ return [];
252
+ // Accumulate scores per document
253
+ const scores = new Map();
254
+ for (const term of queryTokens) {
255
+ const list = postings.get(term);
256
+ if (!list)
257
+ continue;
258
+ const termDf = df.get(term) ?? 0;
259
+ // IDF with smoothing (Robertson-Sparck Jones):
260
+ // idf = log((N - df + 0.5) / (df + 0.5) + 1)
261
+ const idf = Math.log((N - termDf + 0.5) / (termDf + 0.5) + 1);
262
+ for (const { id, tf } of list) {
263
+ const dl = docLen.get(id) ?? avgDocLen;
264
+ const norm = tf / (tf + k1 * (1 - b + b * (dl / avgDocLen)));
265
+ const contribution = idf * norm;
266
+ scores.set(id, (scores.get(id) ?? 0) + contribution);
267
+ }
268
+ }
269
+ if (scores.size === 0)
270
+ return [];
271
+ return Array.from(scores.entries())
272
+ .filter(([, score]) => score > 0)
273
+ .map(([id, score]) => ({ id, score }))
274
+ .sort((a, b) => b.score - a.score);
275
+ }
276
+ //# sourceMappingURL=catalog-index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalog-index.js","sourceRoot":"","sources":["../../src/search/catalog-index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { ToolAdapter } from '../types.js';
2
+ export declare const claudeCodeAdapter: ToolAdapter;
3
+ //# sourceMappingURL=claude-code.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claude-code.d.ts","sourceRoot":"","sources":["../../../src/stack/adapters/claude-code.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAKV,WAAW,EAEZ,MAAM,aAAa,CAAC;AA4IrB,eAAO,MAAM,iBAAiB,EAAE,WA+K/B,CAAC"}
@@ -0,0 +1,282 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { homedir } from 'node:os';
3
+ import { join } from 'node:path';
4
+ import { atomicWriteFile } from '../../atomic-write.js';
5
+ function resolveHome(opts) {
6
+ return opts.home ?? homedir();
7
+ }
8
+ function resolveCwd(opts) {
9
+ return opts.cwd ?? process.cwd();
10
+ }
11
+ const REMOTE_TYPES = new Set(['sse', 'http', 'streamable-http']);
12
+ const LOCAL_TYPES = new Set(['stdio']);
13
+ /** Fix 2: robust transport detection */
14
+ function isRemoteEntry(entry) {
15
+ const t = typeof entry['type'] === 'string' ? entry['type'] : undefined;
16
+ const tr = typeof entry['transport'] === 'string' ? entry['transport'] : undefined;
17
+ const hasUrl = 'url' in entry;
18
+ const hasCommand = 'command' in entry;
19
+ if (t && REMOTE_TYPES.has(t))
20
+ return true;
21
+ if (tr && REMOTE_TYPES.has(tr))
22
+ return true;
23
+ if (t && LOCAL_TYPES.has(t))
24
+ return false;
25
+ if (tr && LOCAL_TYPES.has(tr))
26
+ return false;
27
+ if (hasCommand)
28
+ return false;
29
+ return hasUrl;
30
+ }
31
+ function parseEntry(name, entry, tool, scope, configPath) {
32
+ if (isRemoteEntry(entry)) {
33
+ return {
34
+ name,
35
+ tool,
36
+ scope,
37
+ configPath,
38
+ transport: 'remote',
39
+ url: typeof entry['url'] === 'string' ? entry['url'] : undefined,
40
+ enabled: true,
41
+ raw: entry
42
+ };
43
+ }
44
+ const cmd = typeof entry['command'] === 'string' ? entry['command'] : '';
45
+ const args = Array.isArray(entry['args']) ? entry['args'] : [];
46
+ const env = typeof entry['env'] === 'object' && entry['env'] !== null
47
+ ? entry['env']
48
+ : undefined;
49
+ const argv = [cmd, ...args];
50
+ return {
51
+ name,
52
+ tool,
53
+ scope,
54
+ configPath,
55
+ transport: 'local',
56
+ command: argv,
57
+ env,
58
+ enabled: true,
59
+ raw: entry
60
+ };
61
+ }
62
+ function readMcpServers(obj) {
63
+ if (typeof obj !== 'object' || obj === null)
64
+ return null;
65
+ const map = obj;
66
+ const result = [];
67
+ for (const [k, v] of Object.entries(map)) {
68
+ if (typeof v === 'object' && v !== null) {
69
+ result.push([k, v]);
70
+ }
71
+ }
72
+ return result;
73
+ }
74
+ function parseJson(path) {
75
+ try {
76
+ const content = readFileSync(path, 'utf8');
77
+ return JSON.parse(content);
78
+ }
79
+ catch {
80
+ return null;
81
+ }
82
+ }
83
+ /**
84
+ * Fix 1: Merge a DesiredServer into an existing entry, preserving unknown keys.
85
+ * Returns null when the server should not be written (disabled).
86
+ */
87
+ function mergeEntry(ds, existing) {
88
+ // claude-code has no enabled field — skip disabled servers
89
+ if (ds.enabled === false)
90
+ return null;
91
+ const base = existing !== undefined ? { ...existing } : {};
92
+ if (ds.url) {
93
+ // REMOTE: set url; preserve existing sse/http type if present; remove local keys
94
+ base['url'] = ds.url;
95
+ delete base['command'];
96
+ delete base['args'];
97
+ delete base['env'];
98
+ const t = typeof base['type'] === 'string' ? base['type'] : undefined;
99
+ if (t && LOCAL_TYPES.has(t))
100
+ delete base['type'];
101
+ const tr = typeof base['transport'] === 'string' ? base['transport'] : undefined;
102
+ if (tr && LOCAL_TYPES.has(tr))
103
+ delete base['transport'];
104
+ }
105
+ else {
106
+ // LOCAL: set command/args/env; remove remote keys
107
+ const [cmd, ...args] = ds.command ?? [];
108
+ base['command'] = cmd ?? '';
109
+ if (args.length > 0) {
110
+ base['args'] = args;
111
+ }
112
+ else {
113
+ delete base['args'];
114
+ }
115
+ if (ds.env && Object.keys(ds.env).length > 0) {
116
+ base['env'] = ds.env;
117
+ }
118
+ else {
119
+ delete base['env'];
120
+ }
121
+ delete base['url'];
122
+ const t = typeof base['type'] === 'string' ? base['type'] : undefined;
123
+ if (t && REMOTE_TYPES.has(t))
124
+ delete base['type'];
125
+ const tr = typeof base['transport'] === 'string' ? base['transport'] : undefined;
126
+ if (tr && REMOTE_TYPES.has(tr))
127
+ delete base['transport'];
128
+ }
129
+ return base;
130
+ }
131
+ function entriesEqual(a, b) {
132
+ return JSON.stringify(a) === JSON.stringify(b);
133
+ }
134
+ export const claudeCodeAdapter = {
135
+ id: 'claude-code',
136
+ displayName: 'Claude Code',
137
+ locations(opts) {
138
+ const cwd = resolveCwd(opts);
139
+ const home = resolveHome(opts);
140
+ return [
141
+ { path: join(cwd, '.mcp.json'), scope: 'project' },
142
+ { path: join(home, '.claude.json'), scope: 'user' }
143
+ ];
144
+ },
145
+ writeLocation(opts, scope) {
146
+ const cwd = resolveCwd(opts);
147
+ const home = resolveHome(opts);
148
+ if (scope === 'project') {
149
+ return { path: join(cwd, '.mcp.json'), scope: 'project' };
150
+ }
151
+ return { path: join(home, '.claude.json'), scope: 'user' };
152
+ },
153
+ writeServers(location, desired, opts) {
154
+ const filePath = location.path;
155
+ // Read existing file
156
+ let doc = {};
157
+ if (existsSync(filePath)) {
158
+ const raw = readFileSync(filePath, 'utf8');
159
+ let parsed;
160
+ try {
161
+ parsed = JSON.parse(raw);
162
+ }
163
+ catch (e) {
164
+ throw new Error(`claude-code config at ${filePath} is not valid JSON — refusing to overwrite: ${e instanceof Error ? e.message : String(e)}`, { cause: e });
165
+ }
166
+ if (typeof parsed === 'object' && parsed !== null) {
167
+ doc = parsed;
168
+ }
169
+ }
170
+ // Preserve all sibling keys; only mutate mcpServers
171
+ const result = { ...doc };
172
+ const existingMcp = typeof doc['mcpServers'] === 'object' && doc['mcpServers'] !== null
173
+ ? { ...doc['mcpServers'] }
174
+ : {};
175
+ const added = [];
176
+ const updated = [];
177
+ const removed = [];
178
+ const skippedDisabled = [];
179
+ const desiredMap = new Map(desired.map((d) => [d.name, d]));
180
+ const newMcp = opts.prune ? {} : { ...existingMcp };
181
+ for (const ds of desired) {
182
+ const existingEntry = typeof existingMcp[ds.name] === 'object' && existingMcp[ds.name] !== null
183
+ ? existingMcp[ds.name]
184
+ : undefined;
185
+ const mergedEntry = mergeEntry(ds, existingEntry);
186
+ if (mergedEntry === null) {
187
+ // disabled — skip writing
188
+ skippedDisabled.push(ds.name);
189
+ if (opts.prune && existingEntry !== undefined) {
190
+ delete newMcp[ds.name];
191
+ }
192
+ continue;
193
+ }
194
+ if (existingEntry === undefined) {
195
+ added.push(ds.name);
196
+ }
197
+ else {
198
+ if (!entriesEqual(existingEntry, mergedEntry)) {
199
+ updated.push(ds.name);
200
+ }
201
+ }
202
+ newMcp[ds.name] = mergedEntry;
203
+ }
204
+ if (opts.prune) {
205
+ for (const name of Object.keys(existingMcp)) {
206
+ if (!desiredMap.has(name) && !skippedDisabled.includes(name)) {
207
+ removed.push(name);
208
+ }
209
+ }
210
+ }
211
+ result['mcpServers'] = newMcp;
212
+ atomicWriteFile(filePath, JSON.stringify(result, null, 2) + '\n', 0o644);
213
+ return { added, updated, removed };
214
+ },
215
+ readServers(opts) {
216
+ const cwd = resolveCwd(opts);
217
+ const home = resolveHome(opts);
218
+ const servers = [];
219
+ // --- Project file: <cwd>/.mcp.json ---
220
+ const projectPath = join(cwd, '.mcp.json');
221
+ if (existsSync(projectPath)) {
222
+ const parsed = parseJson(projectPath);
223
+ if (parsed !== null) {
224
+ const doc = parsed;
225
+ const entries = readMcpServers(doc['mcpServers']);
226
+ if (entries) {
227
+ for (const [name, entry] of entries) {
228
+ servers.push(parseEntry(name, entry, 'claude-code', 'project', projectPath));
229
+ }
230
+ }
231
+ }
232
+ }
233
+ // --- User file: ~/.claude.json ---
234
+ const userPath = join(home, '.claude.json');
235
+ if (existsSync(userPath)) {
236
+ const parsed = parseJson(userPath);
237
+ if (parsed !== null) {
238
+ const doc = parsed;
239
+ // Top-level mcpServers (scope: user)
240
+ const topEntries = readMcpServers(doc['mcpServers']);
241
+ if (topEntries) {
242
+ for (const [name, entry] of topEntries) {
243
+ servers.push(parseEntry(name, entry, 'claude-code', 'user', userPath));
244
+ }
245
+ }
246
+ // projects[<absCwd>].mcpServers (scope: project)
247
+ const projects = doc['projects'];
248
+ if (typeof projects === 'object' && projects !== null) {
249
+ const projectsMap = projects;
250
+ const cwdEntry = projectsMap[cwd];
251
+ if (typeof cwdEntry === 'object' && cwdEntry !== null) {
252
+ const projectDoc = cwdEntry;
253
+ const projEntries = readMcpServers(projectDoc['mcpServers']);
254
+ if (projEntries) {
255
+ for (const [name, entry] of projEntries) {
256
+ // Dedupe: skip if same name+command already added from top-level
257
+ const isDupe = servers.some((s) => {
258
+ if (s.configPath !== userPath)
259
+ return false;
260
+ if (s.name !== name)
261
+ return false;
262
+ const newArgv = !isRemoteEntry(entry)
263
+ ? [
264
+ typeof entry['command'] === 'string' ? entry['command'] : '',
265
+ ...(Array.isArray(entry['args']) ? entry['args'] : [])
266
+ ]
267
+ : undefined;
268
+ return JSON.stringify(s.command) === JSON.stringify(newArgv);
269
+ });
270
+ if (!isDupe) {
271
+ servers.push(parseEntry(name, entry, 'claude-code', 'project', userPath));
272
+ }
273
+ }
274
+ }
275
+ }
276
+ }
277
+ }
278
+ }
279
+ return servers;
280
+ }
281
+ };
282
+ //# sourceMappingURL=claude-code.js.map