@s-hirano-ist/s-scripts 1.26.8 → 4.0.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.
Files changed (180) hide show
  1. package/README.md +2 -0
  2. package/dist/chunk-5OXUZW2Q.js +590 -0
  3. package/dist/chunk-7PVU2Z32.js +71 -0
  4. package/dist/chunk-AJMTRCPJ.js +15 -0
  5. package/dist/chunk-AZF2R2BF.js +85 -0
  6. package/dist/chunk-F4J6JBR4.js +115 -0
  7. package/dist/chunk-HDYQV2Y2.js +58 -0
  8. package/dist/chunk-HIK7SX34.js +85 -0
  9. package/dist/chunk-IWNZQXDG.js +7660 -0
  10. package/dist/chunk-J4VLH4VA.js +267 -0
  11. package/dist/chunk-JZOR5GA2.js +42 -0
  12. package/dist/chunk-KKZP7ZF3.js +77 -0
  13. package/dist/chunk-N6TZF755.js +102 -0
  14. package/dist/chunk-S5WJ5VXW.js +85 -0
  15. package/dist/chunk-UXGLXHQ6.js +40 -0
  16. package/dist/cleanup-minio-images.js +115 -92
  17. package/dist/enrich-books.js +87 -78
  18. package/dist/fetch-articles.js +101 -90
  19. package/dist/fetch-books.js +147 -128
  20. package/dist/fetch-images.js +88 -72
  21. package/dist/fetch-notes.js +66 -54
  22. package/dist/find-duplicate-json-articles.js +100 -97
  23. package/dist/fix-book-frontmatter.js +111 -99
  24. package/dist/fix-image-formats.js +81 -65
  25. package/dist/ingest-articles.js +30 -249
  26. package/dist/ingest-books.js +61 -318
  27. package/dist/ingest-images.js +33 -184
  28. package/dist/ingest-notes.js +41 -171
  29. package/dist/rag/delete-collection.js +32 -22
  30. package/dist/rag/ingest.js +371 -92
  31. package/dist/rag/search.js +80 -76
  32. package/dist/reset-articles.js +57 -60
  33. package/dist/reset-books.js +57 -59
  34. package/dist/reset-images.js +57 -59
  35. package/dist/reset-notes.js +57 -59
  36. package/dist/revert-articles.js +53 -55
  37. package/dist/revert-books.js +53 -53
  38. package/dist/revert-images.js +53 -53
  39. package/dist/revert-notes.js +53 -53
  40. package/dist/s-content-format.js +103 -13
  41. package/dist/update-json-articles.js +101 -97
  42. package/dist/update-raw-articles.js +130 -130
  43. package/dist/validate-json-articles.js +129 -130
  44. package/package.json +13 -50
  45. package/dist/cleanup-minio-images.d.ts +0 -3
  46. package/dist/cleanup-minio-images.d.ts.map +0 -1
  47. package/dist/cleanup-minio-images.js.map +0 -1
  48. package/dist/enrich-books.d.ts +0 -3
  49. package/dist/enrich-books.d.ts.map +0 -1
  50. package/dist/enrich-books.js.map +0 -1
  51. package/dist/fetch-articles.d.ts +0 -3
  52. package/dist/fetch-articles.d.ts.map +0 -1
  53. package/dist/fetch-articles.js.map +0 -1
  54. package/dist/fetch-books.d.ts +0 -3
  55. package/dist/fetch-books.d.ts.map +0 -1
  56. package/dist/fetch-books.js.map +0 -1
  57. package/dist/fetch-images.d.ts +0 -3
  58. package/dist/fetch-images.d.ts.map +0 -1
  59. package/dist/fetch-images.js.map +0 -1
  60. package/dist/fetch-notes.d.ts +0 -3
  61. package/dist/fetch-notes.d.ts.map +0 -1
  62. package/dist/fetch-notes.js.map +0 -1
  63. package/dist/find-duplicate-json-articles.d.ts +0 -3
  64. package/dist/find-duplicate-json-articles.d.ts.map +0 -1
  65. package/dist/find-duplicate-json-articles.js.map +0 -1
  66. package/dist/fix-book-frontmatter.d.ts +0 -3
  67. package/dist/fix-book-frontmatter.d.ts.map +0 -1
  68. package/dist/fix-book-frontmatter.js.map +0 -1
  69. package/dist/fix-image-formats.d.ts +0 -3
  70. package/dist/fix-image-formats.d.ts.map +0 -1
  71. package/dist/fix-image-formats.js.map +0 -1
  72. package/dist/infrastructures/articles-command-repository.d.ts +0 -57
  73. package/dist/infrastructures/articles-command-repository.d.ts.map +0 -1
  74. package/dist/infrastructures/articles-command-repository.js +0 -68
  75. package/dist/infrastructures/articles-command-repository.js.map +0 -1
  76. package/dist/infrastructures/books-command-repository.d.ts +0 -34
  77. package/dist/infrastructures/books-command-repository.d.ts.map +0 -1
  78. package/dist/infrastructures/books-command-repository.js +0 -53
  79. package/dist/infrastructures/books-command-repository.js.map +0 -1
  80. package/dist/infrastructures/cache-invalidation-client.d.ts +0 -9
  81. package/dist/infrastructures/cache-invalidation-client.d.ts.map +0 -1
  82. package/dist/infrastructures/cache-invalidation-client.js +0 -21
  83. package/dist/infrastructures/cache-invalidation-client.js.map +0 -1
  84. package/dist/infrastructures/cache-invalidation-client.test.d.ts +0 -2
  85. package/dist/infrastructures/cache-invalidation-client.test.d.ts.map +0 -1
  86. package/dist/infrastructures/cache-invalidation-client.test.js +0 -25
  87. package/dist/infrastructures/cache-invalidation-client.test.js.map +0 -1
  88. package/dist/infrastructures/images-command-repository.d.ts +0 -34
  89. package/dist/infrastructures/images-command-repository.d.ts.map +0 -1
  90. package/dist/infrastructures/images-command-repository.js +0 -53
  91. package/dist/infrastructures/images-command-repository.js.map +0 -1
  92. package/dist/infrastructures/notes-command-repository.d.ts +0 -34
  93. package/dist/infrastructures/notes-command-repository.d.ts.map +0 -1
  94. package/dist/infrastructures/notes-command-repository.js +0 -53
  95. package/dist/infrastructures/notes-command-repository.js.map +0 -1
  96. package/dist/ingest-articles.d.ts +0 -3
  97. package/dist/ingest-articles.d.ts.map +0 -1
  98. package/dist/ingest-articles.js.map +0 -1
  99. package/dist/ingest-books.d.ts +0 -3
  100. package/dist/ingest-books.d.ts.map +0 -1
  101. package/dist/ingest-books.js.map +0 -1
  102. package/dist/ingest-images.d.ts +0 -3
  103. package/dist/ingest-images.d.ts.map +0 -1
  104. package/dist/ingest-images.js.map +0 -1
  105. package/dist/ingest-notes.d.ts +0 -3
  106. package/dist/ingest-notes.d.ts.map +0 -1
  107. package/dist/ingest-notes.js.map +0 -1
  108. package/dist/lib/glob-paths.d.ts +0 -6
  109. package/dist/lib/glob-paths.d.ts.map +0 -1
  110. package/dist/lib/glob-paths.js +0 -9
  111. package/dist/lib/glob-paths.js.map +0 -1
  112. package/dist/lib/glob-paths.test.d.ts +0 -2
  113. package/dist/lib/glob-paths.test.d.ts.map +0 -1
  114. package/dist/lib/glob-paths.test.js +0 -30
  115. package/dist/lib/glob-paths.test.js.map +0 -1
  116. package/dist/lib/html-charset.d.ts +0 -4
  117. package/dist/lib/html-charset.d.ts.map +0 -1
  118. package/dist/lib/html-charset.js +0 -33
  119. package/dist/lib/html-charset.js.map +0 -1
  120. package/dist/lib/html-charset.test.d.ts +0 -2
  121. package/dist/lib/html-charset.test.d.ts.map +0 -1
  122. package/dist/lib/html-charset.test.js +0 -27
  123. package/dist/lib/html-charset.test.js.map +0 -1
  124. package/dist/rag/delete-collection.d.ts +0 -3
  125. package/dist/rag/delete-collection.d.ts.map +0 -1
  126. package/dist/rag/delete-collection.js.map +0 -1
  127. package/dist/rag/ingest-config.d.ts +0 -8
  128. package/dist/rag/ingest-config.d.ts.map +0 -1
  129. package/dist/rag/ingest-config.js +0 -8
  130. package/dist/rag/ingest-config.js.map +0 -1
  131. package/dist/rag/ingest.d.ts +0 -3
  132. package/dist/rag/ingest.d.ts.map +0 -1
  133. package/dist/rag/ingest.js.map +0 -1
  134. package/dist/rag/search.d.ts +0 -3
  135. package/dist/rag/search.d.ts.map +0 -1
  136. package/dist/rag/search.js.map +0 -1
  137. package/dist/reset-articles.d.ts +0 -3
  138. package/dist/reset-articles.d.ts.map +0 -1
  139. package/dist/reset-articles.js.map +0 -1
  140. package/dist/reset-books.d.ts +0 -3
  141. package/dist/reset-books.d.ts.map +0 -1
  142. package/dist/reset-books.js.map +0 -1
  143. package/dist/reset-images.d.ts +0 -3
  144. package/dist/reset-images.d.ts.map +0 -1
  145. package/dist/reset-images.js.map +0 -1
  146. package/dist/reset-notes.d.ts +0 -3
  147. package/dist/reset-notes.d.ts.map +0 -1
  148. package/dist/reset-notes.js.map +0 -1
  149. package/dist/revert-articles.d.ts +0 -3
  150. package/dist/revert-articles.d.ts.map +0 -1
  151. package/dist/revert-articles.js.map +0 -1
  152. package/dist/revert-books.d.ts +0 -3
  153. package/dist/revert-books.d.ts.map +0 -1
  154. package/dist/revert-books.js.map +0 -1
  155. package/dist/revert-images.d.ts +0 -3
  156. package/dist/revert-images.d.ts.map +0 -1
  157. package/dist/revert-images.js.map +0 -1
  158. package/dist/revert-notes.d.ts +0 -3
  159. package/dist/revert-notes.d.ts.map +0 -1
  160. package/dist/revert-notes.js.map +0 -1
  161. package/dist/s-content-format-runner.d.ts +0 -10
  162. package/dist/s-content-format-runner.d.ts.map +0 -1
  163. package/dist/s-content-format-runner.js +0 -76
  164. package/dist/s-content-format-runner.js.map +0 -1
  165. package/dist/s-content-format.d.ts +0 -3
  166. package/dist/s-content-format.d.ts.map +0 -1
  167. package/dist/s-content-format.js.map +0 -1
  168. package/dist/s-content-format.test.d.ts +0 -2
  169. package/dist/s-content-format.test.d.ts.map +0 -1
  170. package/dist/s-content-format.test.js +0 -99
  171. package/dist/s-content-format.test.js.map +0 -1
  172. package/dist/update-json-articles.d.ts +0 -3
  173. package/dist/update-json-articles.d.ts.map +0 -1
  174. package/dist/update-json-articles.js.map +0 -1
  175. package/dist/update-raw-articles.d.ts +0 -3
  176. package/dist/update-raw-articles.d.ts.map +0 -1
  177. package/dist/update-raw-articles.js.map +0 -1
  178. package/dist/validate-json-articles.d.ts +0 -3
  179. package/dist/validate-json-articles.d.ts.map +0 -1
  180. package/dist/validate-json-articles.js.map +0 -1
@@ -1,107 +1,386 @@
1
1
  #!/usr/bin/env node
2
- import { parseJsonArticle, parseMarkdown, } from "@s-hirano-ist/s-search/chunker";
3
- import { ingestChunks, pruneOrphans } from "@s-hirano-ist/s-search/ingest";
4
- import { ensureCollection, getCollectionStats, } from "@s-hirano-ist/s-search/qdrant-client";
5
- import { readFileSync } from "node:fs";
6
- import { globPaths } from "../lib/glob-paths.js";
7
- import { INGEST_CONFIG } from "./ingest-config.js";
8
- /**
9
- * List all files to process with content type information
10
- */
11
- async function listFiles() {
12
- const files = [];
13
- // Articles (JSON)
14
- const articleFiles = await globPaths(INGEST_CONFIG.paths.articles);
15
- for (const path of articleFiles) {
16
- files.push({ path, type: "json", contentType: "articles" });
17
- }
18
- // Notes (Markdown)
19
- const noteFiles = await globPaths(INGEST_CONFIG.paths.notes);
20
- for (const path of noteFiles) {
21
- files.push({ path, type: "markdown", contentType: "notes" });
22
- }
23
- // Books (Markdown)
24
- const bookFiles = await globPaths(INGEST_CONFIG.paths.books);
25
- for (const path of bookFiles) {
26
- files.push({ path, type: "markdown", contentType: "books" });
27
- }
28
- return files;
2
+ import { createRequire as __cliCreateRequire } from "node:module"; const require = __cliCreateRequire(import.meta.url);
3
+ import {
4
+ parseMarkdownFrontmatter
5
+ } from "../chunk-HDYQV2Y2.js";
6
+ import {
7
+ globPaths
8
+ } from "../chunk-AJMTRCPJ.js";
9
+ import {
10
+ RAG_CONFIG,
11
+ deletePointsByChunkIds,
12
+ ensureCollection,
13
+ getCollectionStats,
14
+ getExistingHashes,
15
+ listAllChunkIds,
16
+ upsertPoints
17
+ } from "../chunk-J4VLH4VA.js";
18
+ import "../chunk-JZOR5GA2.js";
19
+
20
+ // ../search/dist/chunker.js
21
+ import { createHash } from "node:crypto";
22
+ function generateHash(content) {
23
+ return createHash("sha256").update(content).digest("hex").slice(0, 16);
29
24
  }
30
- /**
31
- * Parse a single file into chunks
32
- */
33
- function parseFile(file) {
34
- const content = readFileSync(file.path, "utf-8");
35
- if (file.type === "json") {
36
- return parseJsonArticle(file.path, content);
37
- }
38
- return parseMarkdown(file.path, content, file.contentType);
25
+ function generateChunkId(docId, index) {
26
+ return `${docId}#${index}`;
39
27
  }
40
- /**
41
- * CLI entry point: list files, parse into chunks, delegate to ingestChunks
42
- */
43
- async function ingest(flags) {
44
- console.log("Starting ingest...\n");
45
- if (flags.dryRun) {
46
- console.log("Dry-run mode: no writes to Qdrant\n");
47
- }
48
- else {
49
- await ensureCollection();
50
- const initialStats = await getCollectionStats();
51
- console.log(`Initial points count: ${initialStats.pointsCount}\n`);
28
+ function parseJsonArticle(filePath, content) {
29
+ const json = JSON.parse(content);
30
+ const docId = `file:${filePath}`;
31
+ const chunks = [];
32
+ for (let i = 0; i < json.body.length; i++) {
33
+ const item = json.body[i];
34
+ const textParts = [];
35
+ if (item.title)
36
+ textParts.push(item.title);
37
+ if (item.ogTitle && item.ogTitle !== item.title)
38
+ textParts.push(item.ogTitle);
39
+ if (item.ogDescription)
40
+ textParts.push(item.ogDescription);
41
+ if (item.quote)
42
+ textParts.push(item.quote);
43
+ const text = textParts.join("\n");
44
+ if (!text.trim())
45
+ continue;
46
+ const chunkId = generateChunkId(docId, i);
47
+ chunks.push({
48
+ type: "bookmark_json",
49
+ content_type: "articles",
50
+ top_heading: json.heading,
51
+ doc_id: docId,
52
+ chunk_id: chunkId,
53
+ title: item.title || item.ogTitle || "Untitled",
54
+ url: item.url,
55
+ heading_path: [json.heading],
56
+ text,
57
+ content_hash: generateHash(text)
58
+ });
59
+ }
60
+ return chunks;
61
+ }
62
+ function parseFrontmatter(content) {
63
+ const parsed = parseMarkdownFrontmatter(content);
64
+ const frontmatter = {
65
+ heading: typeof parsed.data.heading === "string" ? parsed.data.heading : "unknown"
66
+ };
67
+ if (typeof parsed.data.description === "string") {
68
+ frontmatter.description = parsed.data.description;
69
+ }
70
+ if (typeof parsed.data.draft === "boolean") {
71
+ frontmatter.draft = parsed.data.draft;
72
+ }
73
+ return { frontmatter, body: parsed.content };
74
+ }
75
+ function pushPreambleSection(state) {
76
+ if (state.preambleContent.trim()) {
77
+ state.sections.push({
78
+ headingPath: [],
79
+ title: "",
80
+ content: state.preambleContent,
81
+ level: 0
82
+ });
83
+ }
84
+ }
85
+ function handleHeadingLine(state, headingMatch) {
86
+ if (!state.preambleDone) {
87
+ state.preambleDone = true;
88
+ pushPreambleSection(state);
89
+ }
90
+ if (state.currentSection?.content.trim()) {
91
+ state.sections.push(state.currentSection);
92
+ }
93
+ const level = headingMatch[1].length;
94
+ const title = headingMatch[2];
95
+ const headingStack = state.headingStack;
96
+ let top = headingStack.at(-1);
97
+ while (top && top.level >= level) {
98
+ headingStack.pop();
99
+ top = headingStack.at(-1);
100
+ }
101
+ headingStack.push({ level, title });
102
+ state.currentSection = {
103
+ headingPath: headingStack.map((h) => h.title),
104
+ title,
105
+ content: "",
106
+ level
107
+ };
108
+ }
109
+ function splitMarkdownByHeadings(content) {
110
+ const state = {
111
+ sections: [],
112
+ currentSection: null,
113
+ headingStack: [],
114
+ // Preamble buffer (content before first H2/H3)
115
+ preambleContent: "",
116
+ preambleDone: false
117
+ };
118
+ for (const line of content.split("\n")) {
119
+ if (/^#\s+\S/u.test(line)) {
120
+ continue;
52
121
  }
53
- if (flags.force) {
54
- console.log("Force mode enabled: skipping change detection\n");
122
+ const headingMatch = /^(#{2,3})\s+(\S.*)$/u.exec(line);
123
+ if (headingMatch) {
124
+ handleHeadingLine(state, headingMatch);
125
+ } else if (!state.preambleDone) {
126
+ state.preambleContent += `${line}
127
+ `;
128
+ } else if (state.currentSection) {
129
+ state.currentSection.content += `${line}
130
+ `;
55
131
  }
56
- const files = await listFiles();
57
- const articleCount = files.filter((f) => f.contentType === "articles").length;
58
- const noteCount = files.filter((f) => f.contentType === "notes").length;
59
- const bookCount = files.filter((f) => f.contentType === "books").length;
60
- console.log(`Found ${files.length} files to process`);
61
- console.log(` - Articles: ${articleCount}`);
62
- console.log(` - Notes: ${noteCount}`);
63
- console.log(` - Books: ${bookCount}\n`);
64
- console.log("Parsing files...");
65
- const allChunks = [];
66
- for (const file of files) {
67
- try {
68
- const chunks = parseFile(file);
69
- allChunks.push(...chunks);
70
- }
71
- catch (error) {
72
- console.error(`Error parsing ${file.path}:`, error);
73
- }
132
+ }
133
+ if (!state.preambleDone) {
134
+ pushPreambleSection(state);
135
+ }
136
+ if (state.currentSection?.content.trim()) {
137
+ state.sections.push(state.currentSection);
138
+ }
139
+ return state.sections;
140
+ }
141
+ function splitByParagraphs(text, maxLength) {
142
+ if (text.length <= maxLength) {
143
+ return [text];
144
+ }
145
+ const paragraphs = text.split(/\n{2,}/u);
146
+ const chunks = [];
147
+ let currentChunk = "";
148
+ for (const para of paragraphs) {
149
+ if (currentChunk.length + para.length > maxLength && currentChunk) {
150
+ chunks.push(currentChunk.trim());
151
+ currentChunk = para;
152
+ } else {
153
+ currentChunk += (currentChunk ? "\n\n" : "") + para;
74
154
  }
75
- console.log(`Total chunks parsed: ${allChunks.length}`);
76
- if (flags.dryRun) {
77
- console.log("Dry-run complete. Skipping upsert and prune.");
78
- return;
155
+ }
156
+ if (currentChunk.trim()) {
157
+ chunks.push(currentChunk.trim());
158
+ }
159
+ return chunks;
160
+ }
161
+ function parseMarkdownWithDocId(docId, content, contentType = "notes") {
162
+ const { frontmatter, body } = parseFrontmatter(content);
163
+ const chunks = [];
164
+ if (frontmatter.draft) {
165
+ return [];
166
+ }
167
+ const sections = splitMarkdownByHeadings(body);
168
+ let chunkIndex = 0;
169
+ for (const section of sections) {
170
+ const textChunks = splitByParagraphs(section.content, RAG_CONFIG.chunking.maxChunkLength);
171
+ for (const text of textChunks) {
172
+ if (!text.trim())
173
+ continue;
174
+ const isPreamble = section.level === 0 && section.title === "";
175
+ const sectionTitle = isPreamble ? frontmatter.description ?? frontmatter.heading : section.title;
176
+ const fullHeadingPath = isPreamble ? [frontmatter.heading] : [frontmatter.heading, ...section.headingPath];
177
+ const chunkId = generateChunkId(docId, chunkIndex);
178
+ chunks.push({
179
+ type: "markdown_note",
180
+ content_type: contentType,
181
+ top_heading: frontmatter.heading,
182
+ doc_id: docId,
183
+ chunk_id: chunkId,
184
+ title: sectionTitle,
185
+ heading_path: fullHeadingPath,
186
+ text,
187
+ content_hash: generateHash(text)
188
+ });
189
+ chunkIndex++;
79
190
  }
80
- const result = await ingestChunks(allChunks, { force: flags.force });
81
- const pruneResult = await pruneOrphans(allChunks.map((c) => c.chunk_id));
82
- const finalStats = await getCollectionStats();
83
- console.log(`\nFinal points count: ${finalStats.pointsCount}`);
84
- console.log(`Ingest completed! (${result.changedChunks} changed, ${result.skippedChunks} skipped, ${pruneResult.deletedCount} pruned)`);
191
+ }
192
+ return chunks;
85
193
  }
86
- async function main() {
87
- if (!process.env.QDRANT_URL) {
88
- console.error("QDRANT_URL environment variable is required.");
89
- process.exit(1);
194
+ function parseMarkdown(filePath, content, contentType = "notes") {
195
+ return parseMarkdownWithDocId(`file:${filePath}`, content, contentType);
196
+ }
197
+
198
+ // ../search/dist/ingest.js
199
+ import { setTimeout } from "node:timers/promises";
200
+ var BATCH_SIZE = 20;
201
+ var MAX_RETRIES = 3;
202
+ var RETRY_DELAY_MS = 2e3;
203
+ async function withRetry(fn, retries = MAX_RETRIES) {
204
+ for (let i = 0; i < retries; i++) {
205
+ try {
206
+ return await fn();
207
+ } catch (error) {
208
+ if (i === retries - 1)
209
+ throw error;
210
+ console.log(` Retry ${i + 1}/${retries} after error...`);
211
+ await setTimeout(RETRY_DELAY_MS);
90
212
  }
91
- const flags = {
92
- force: process.argv.includes("--force"),
93
- dryRun: process.argv.includes("--dry-run"),
213
+ }
214
+ throw new Error("Unreachable");
215
+ }
216
+ async function ingestChunks(chunks, options) {
217
+ console.log(`Total chunks: ${chunks.length}
218
+ `);
219
+ let changedChunks;
220
+ let skippedChunks;
221
+ if (options.force) {
222
+ changedChunks = chunks;
223
+ skippedChunks = 0;
224
+ } else {
225
+ console.log("Checking for changes...");
226
+ const chunkIds = chunks.map((c) => c.chunk_id);
227
+ const existingHashes = await getExistingHashes(chunkIds);
228
+ changedChunks = chunks.filter((chunk) => {
229
+ const existingHash = existingHashes.get(chunk.chunk_id);
230
+ return existingHash !== chunk.content_hash;
231
+ });
232
+ skippedChunks = chunks.length - changedChunks.length;
233
+ }
234
+ console.log(`Changed chunks: ${changedChunks.length}`);
235
+ console.log(`Skipped (unchanged): ${skippedChunks}
236
+ `);
237
+ if (changedChunks.length === 0) {
238
+ console.log("No changes detected. Done!");
239
+ return {
240
+ totalChunks: chunks.length,
241
+ changedChunks: 0,
242
+ skippedChunks
94
243
  };
244
+ }
245
+ console.log("Upserting to Qdrant...");
246
+ let processed = 0;
247
+ for (let i = 0; i < changedChunks.length; i += BATCH_SIZE) {
248
+ const batch = changedChunks.slice(i, i + BATCH_SIZE);
249
+ const points = batch.map((chunk) => ({
250
+ id: chunk.chunk_id,
251
+ text: chunk.text,
252
+ payload: chunk
253
+ }));
254
+ await withRetry(() => upsertPoints(points));
255
+ processed += batch.length;
256
+ console.log(` Progress: ${processed}/${changedChunks.length}`);
257
+ await setTimeout(100);
258
+ }
259
+ return {
260
+ totalChunks: chunks.length,
261
+ changedChunks: changedChunks.length,
262
+ skippedChunks
263
+ };
264
+ }
265
+ async function pruneOrphans(currentChunkIds) {
266
+ const existingIds = await listAllChunkIds();
267
+ const currentSet = new Set(currentChunkIds);
268
+ const orphanIds = existingIds.filter((id) => !currentSet.has(id));
269
+ console.log(`Existing Qdrant chunks: ${existingIds.length}`);
270
+ console.log(`Orphan chunks to delete: ${orphanIds.length}`);
271
+ if (orphanIds.length === 0) {
272
+ return { deletedCount: 0 };
273
+ }
274
+ let deleted = 0;
275
+ for (let i = 0; i < orphanIds.length; i += BATCH_SIZE) {
276
+ const batch = orphanIds.slice(i, i + BATCH_SIZE);
277
+ await withRetry(() => deletePointsByChunkIds(batch));
278
+ deleted += batch.length;
279
+ console.log(` Prune progress: ${deleted}/${orphanIds.length}`);
280
+ await setTimeout(100);
281
+ }
282
+ return { deletedCount: deleted };
283
+ }
284
+
285
+ // src/rag/ingest.ts
286
+ import { readFileSync } from "node:fs";
287
+
288
+ // src/rag/ingest-config.ts
289
+ var INGEST_CONFIG = {
290
+ paths: {
291
+ notes: "markdown/note/**/*.md",
292
+ books: "markdown/book/**/*.md",
293
+ articles: "json/article/**/*.json"
294
+ }
295
+ };
296
+
297
+ // src/rag/ingest.ts
298
+ async function listFiles() {
299
+ const files = [];
300
+ const articleFiles = await globPaths(INGEST_CONFIG.paths.articles);
301
+ for (const path of articleFiles) {
302
+ files.push({ path, type: "json", contentType: "articles" });
303
+ }
304
+ const noteFiles = await globPaths(INGEST_CONFIG.paths.notes);
305
+ for (const path of noteFiles) {
306
+ files.push({ path, type: "markdown", contentType: "notes" });
307
+ }
308
+ const bookFiles = await globPaths(INGEST_CONFIG.paths.books);
309
+ for (const path of bookFiles) {
310
+ files.push({ path, type: "markdown", contentType: "books" });
311
+ }
312
+ return files;
313
+ }
314
+ function parseFile(file) {
315
+ const content = readFileSync(file.path, "utf-8");
316
+ if (file.type === "json") {
317
+ return parseJsonArticle(file.path, content);
318
+ }
319
+ return parseMarkdown(file.path, content, file.contentType);
320
+ }
321
+ async function ingest(flags) {
322
+ console.log("Starting ingest...\n");
323
+ if (flags.dryRun) {
324
+ console.log("Dry-run mode: no writes to Qdrant\n");
325
+ } else {
326
+ await ensureCollection();
327
+ const initialStats = await getCollectionStats();
328
+ console.log(`Initial points count: ${initialStats.pointsCount}
329
+ `);
330
+ }
331
+ if (flags.force) {
332
+ console.log("Force mode enabled: skipping change detection\n");
333
+ }
334
+ const files = await listFiles();
335
+ const articleCount = files.filter((f) => f.contentType === "articles").length;
336
+ const noteCount = files.filter((f) => f.contentType === "notes").length;
337
+ const bookCount = files.filter((f) => f.contentType === "books").length;
338
+ console.log(`Found ${files.length} files to process`);
339
+ console.log(` - Articles: ${articleCount}`);
340
+ console.log(` - Notes: ${noteCount}`);
341
+ console.log(` - Books: ${bookCount}
342
+ `);
343
+ console.log("Parsing files...");
344
+ const allChunks = [];
345
+ for (const file of files) {
95
346
  try {
96
- await ingest(flags);
97
- }
98
- catch (error) {
99
- console.error("❌ エラーが発生しました:", error);
100
- process.exit(2);
347
+ const chunks = parseFile(file);
348
+ allChunks.push(...chunks);
349
+ } catch (error) {
350
+ console.error(`Error parsing ${file.path}:`, error);
101
351
  }
352
+ }
353
+ console.log(`Total chunks parsed: ${allChunks.length}`);
354
+ if (flags.dryRun) {
355
+ console.log("Dry-run complete. Skipping upsert and prune.");
356
+ return;
357
+ }
358
+ const result = await ingestChunks(allChunks, { force: flags.force });
359
+ const pruneResult = await pruneOrphans(allChunks.map((c) => c.chunk_id));
360
+ const finalStats = await getCollectionStats();
361
+ console.log(`
362
+ Final points count: ${finalStats.pointsCount}`);
363
+ console.log(
364
+ `Ingest completed! (${result.changedChunks} changed, ${result.skippedChunks} skipped, ${pruneResult.deletedCount} pruned)`
365
+ );
102
366
  }
103
- main().catch((error) => {
104
- console.error(error);
367
+ async function main() {
368
+ if (!process.env.QDRANT_URL) {
369
+ console.error("QDRANT_URL environment variable is required.");
370
+ process.exit(1);
371
+ }
372
+ const flags = {
373
+ force: process.argv.includes("--force"),
374
+ dryRun: process.argv.includes("--dry-run")
375
+ };
376
+ try {
377
+ await ingest(flags);
378
+ } catch (error) {
379
+ console.error("\u274C \u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F:", error);
105
380
  process.exit(2);
381
+ }
382
+ }
383
+ main().catch((error) => {
384
+ console.error(error);
385
+ process.exit(2);
106
386
  });
107
- //# sourceMappingURL=ingest.js.map
@@ -1,87 +1,91 @@
1
1
  #!/usr/bin/env node
2
- import { ensureCollection, search as qdrantSearch, } from "@s-hirano-ist/s-search/qdrant-client";
2
+ import { createRequire as __cliCreateRequire } from "node:module"; const require = __cliCreateRequire(import.meta.url);
3
+ import {
4
+ ensureCollection,
5
+ search
6
+ } from "../chunk-J4VLH4VA.js";
7
+ import "../chunk-JZOR5GA2.js";
8
+
9
+ // src/rag/search.ts
3
10
  function parseArgs() {
4
- const args = process.argv.slice(2);
5
- const options = {};
6
- const positional = [];
7
- for (let i = 0; i < args.length; i++) {
8
- const arg = args[i];
9
- if (arg === "--top-k" && i + 1 < args.length) {
10
- options.topK = Number(args[++i]);
11
- }
12
- else if (arg === "--type" && i + 1 < args.length) {
13
- options.type = args[++i];
14
- }
15
- else if (arg === "--heading" && i + 1 < args.length) {
16
- options.heading = args[++i];
17
- }
18
- else if (arg === "--content-type" && i + 1 < args.length) {
19
- const value = args[++i];
20
- const types = value.split(",");
21
- options.contentType = types.length === 1 ? types[0] : types;
22
- }
23
- else {
24
- positional.push(arg);
25
- }
11
+ const args = process.argv.slice(2);
12
+ const options = {};
13
+ const positional = [];
14
+ for (let i = 0; i < args.length; i++) {
15
+ const arg = args[i];
16
+ if (arg === "--top-k" && i + 1 < args.length) {
17
+ options.topK = Number(args[++i]);
18
+ } else if (arg === "--type" && i + 1 < args.length) {
19
+ options.type = args[++i];
20
+ } else if (arg === "--heading" && i + 1 < args.length) {
21
+ options.heading = args[++i];
22
+ } else if (arg === "--content-type" && i + 1 < args.length) {
23
+ const value = args[++i];
24
+ const types = value.split(",");
25
+ options.contentType = types.length === 1 ? types[0] : types;
26
+ } else {
27
+ positional.push(arg);
26
28
  }
27
- const query = positional.join(" ");
28
- if (!query) {
29
- console.error("Usage: rag-search <query> [--top-k N] [--type markdown_note|bookmark_json] [--heading HEADING] [--content-type articles|books|notes]");
30
- process.exit(1);
31
- }
32
- return { query, options };
29
+ }
30
+ const query = positional.join(" ");
31
+ if (!query) {
32
+ console.error(
33
+ "Usage: rag-search <query> [--top-k N] [--type markdown_note|bookmark_json] [--heading HEADING] [--content-type articles|books|notes]"
34
+ );
35
+ process.exit(1);
36
+ }
37
+ return { query, options };
33
38
  }
34
39
  async function searchContent(query, options = {}) {
35
- const results = await qdrantSearch(query, {
36
- topK: options.topK,
37
- filter: {
38
- type: options.type,
39
- top_heading: options.heading,
40
- content_type: options.contentType,
41
- },
42
- });
43
- return { results, query, totalResults: results.length };
44
- }
45
- async function search() {
46
- const { query, options } = parseArgs();
47
- console.log(`Searching for: "${query}"`);
48
- if (options.topK)
49
- console.log(` top-k: ${options.topK}`);
50
- if (options.type)
51
- console.log(` type: ${options.type}`);
52
- if (options.heading)
53
- console.log(` heading: ${options.heading}`);
54
- if (options.contentType)
55
- console.log(` content-type: ${Array.isArray(options.contentType) ? options.contentType.join(",") : options.contentType}`);
56
- console.log();
57
- await ensureCollection();
58
- const response = await searchContent(query, options);
59
- console.log(`Found ${response.totalResults} results:\n`);
60
- for (const [i, result] of response.results.entries()) {
61
- console.log(`--- Result ${i + 1} (score: ${result.score.toFixed(4)}) ---`);
62
- console.log(`Title: ${result.title}`);
63
- if (result.url)
64
- console.log(`URL: ${result.url}`);
65
- console.log(`Type: ${result.type}`);
66
- console.log(`Content-Type: ${result.content_type}`);
67
- console.log(`Heading: ${result.heading_path.join(" > ")}`);
68
- console.log(`\n${result.text}\n`);
40
+ const results = await search(query, {
41
+ topK: options.topK,
42
+ filter: {
43
+ type: options.type,
44
+ top_heading: options.heading,
45
+ content_type: options.contentType
69
46
  }
47
+ });
48
+ return { results, query, totalResults: results.length };
49
+ }
50
+ async function search2() {
51
+ const { query, options } = parseArgs();
52
+ console.log(`Searching for: "${query}"`);
53
+ if (options.topK) console.log(` top-k: ${options.topK}`);
54
+ if (options.type) console.log(` type: ${options.type}`);
55
+ if (options.heading) console.log(` heading: ${options.heading}`);
56
+ if (options.contentType)
57
+ console.log(
58
+ ` content-type: ${Array.isArray(options.contentType) ? options.contentType.join(",") : options.contentType}`
59
+ );
60
+ console.log();
61
+ await ensureCollection();
62
+ const response = await searchContent(query, options);
63
+ console.log(`Found ${response.totalResults} results:
64
+ `);
65
+ for (const [i, result] of response.results.entries()) {
66
+ console.log(`--- Result ${i + 1} (score: ${result.score.toFixed(4)}) ---`);
67
+ console.log(`Title: ${result.title}`);
68
+ if (result.url) console.log(`URL: ${result.url}`);
69
+ console.log(`Type: ${result.type}`);
70
+ console.log(`Content-Type: ${result.content_type}`);
71
+ console.log(`Heading: ${result.heading_path.join(" > ")}`);
72
+ console.log(`
73
+ ${result.text}
74
+ `);
75
+ }
70
76
  }
71
77
  async function main() {
72
- if (!process.env.QDRANT_URL) {
73
- throw new Error("QDRANT_URL environment variable is required.");
74
- }
75
- try {
76
- await search();
77
- }
78
- catch (error) {
79
- console.error("❌ エラーが発生しました:", error);
80
- process.exit(1);
81
- }
78
+ if (!process.env.QDRANT_URL) {
79
+ throw new Error("QDRANT_URL environment variable is required.");
80
+ }
81
+ try {
82
+ await search2();
83
+ } catch (error) {
84
+ console.error("\u274C \u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F:", error);
85
+ process.exit(1);
86
+ }
82
87
  }
83
88
  main().catch((error) => {
84
- console.error(error);
85
- process.exit(1);
89
+ console.error(error);
90
+ process.exit(1);
86
91
  });
87
- //# sourceMappingURL=search.js.map