@ncukondo/search-hub 0.16.0 → 0.17.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/dist/cli/commands/review/extract.d.ts.map +1 -1
- package/dist/cli/commands/review/extract.js +10 -2
- package/dist/cli/commands/review/extract.js.map +1 -1
- package/dist/cli/commands/review/finalize.d.ts +2 -0
- package/dist/cli/commands/review/finalize.d.ts.map +1 -1
- package/dist/cli/commands/review/finalize.js +12 -1
- package/dist/cli/commands/review/finalize.js.map +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +14 -3
- package/dist/cli/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"extract.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/review/extract.ts"],"names":[],"mappings":"AAAA;;GAEG;AAKH,OAAO,EAAsD,KAAK,YAAY,EAAE,KAAK,WAAW,EAAE,MAAM,YAAY,CAAC;AAErH,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,CAAC;AAE9D,MAAM,WAAW,oBAAoB;IACnC,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,YAAY,EAAE,CAAC;IACxB,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,kGAAkG;IAClG,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,+EAA+E;IAC/E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gFAAgF;IAChF,IAAI,EAAE,MAAM,CAAC;IACb,4FAA4F;IAC5F,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAGD,MAAM,WAAW,mBAAmB;IAClC,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;CACvB;AAgLD;;GAEG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAU/C;AAED;;GAEG;AACH,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,oBAAoB,EAC7B,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,mBAAmB,CAAC,
|
|
1
|
+
{"version":3,"file":"extract.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/review/extract.ts"],"names":[],"mappings":"AAAA;;GAEG;AAKH,OAAO,EAAsD,KAAK,YAAY,EAAE,KAAK,WAAW,EAAE,MAAM,YAAY,CAAC;AAErH,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,CAAC;AAE9D,MAAM,WAAW,oBAAoB;IACnC,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,YAAY,EAAE,CAAC;IACxB,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,kGAAkG;IAClG,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,+EAA+E;IAC/E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gFAAgF;IAChF,IAAI,EAAE,MAAM,CAAC;IACb,4FAA4F;IAC5F,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAGD,MAAM,WAAW,mBAAmB;IAClC,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;CACvB;AAgLD;;GAEG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAU/C;AAED;;GAEG;AACH,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,oBAAoB,EAC7B,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,mBAAmB,CAAC,CAkH9B"}
|
|
@@ -167,10 +167,14 @@ async function executeReviewExtract(options, sessionsDir) {
|
|
|
167
167
|
};
|
|
168
168
|
const yamlContent = stringify(outputFile, { lineWidth: 0 });
|
|
169
169
|
const decisionComment = getDecisionInlineComment(options.basis);
|
|
170
|
-
|
|
170
|
+
let yamlWithComments = yamlContent.replace(
|
|
171
171
|
/^(\s*-?\s*)decision: uncertain$/gm,
|
|
172
172
|
`$1decision: uncertain ${decisionComment}`
|
|
173
173
|
);
|
|
174
|
+
yamlWithComments = yamlWithComments.replace(
|
|
175
|
+
/^(\s*)comment: ""$/gm,
|
|
176
|
+
'$1comment: "" # reason for decision'
|
|
177
|
+
);
|
|
174
178
|
const guidanceComment = getBasisGuidanceComment(options.basis);
|
|
175
179
|
finalContent = guidanceComment + yamlWithComments;
|
|
176
180
|
} else {
|
|
@@ -180,10 +184,14 @@ async function executeReviewExtract(options, sessionsDir) {
|
|
|
180
184
|
articles: paginated.map((article) => buildFinalizeArticle(article, options.basis))
|
|
181
185
|
};
|
|
182
186
|
const yamlContent = stringify(outputFile, { lineWidth: 0 });
|
|
183
|
-
|
|
187
|
+
let yamlWithComments = yamlContent.replace(
|
|
184
188
|
/^(\s*)finalDecision: null$/gm,
|
|
185
189
|
"$1finalDecision: # include / exclude"
|
|
186
190
|
);
|
|
191
|
+
yamlWithComments = yamlWithComments.replace(
|
|
192
|
+
/^(\s*)reviews: \[\]$/gm,
|
|
193
|
+
"$1reviews: [] # add new reviews here"
|
|
194
|
+
);
|
|
187
195
|
const guidanceComment = getFinalDecisionGuidanceComment();
|
|
188
196
|
finalContent = guidanceComment + yamlWithComments;
|
|
189
197
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"extract.js","sources":["../../../../src/cli/commands/review/extract.ts"],"sourcesContent":["/**\n * review extract command - Extract subset of articles for distributed review\n */\n\nimport { join, dirname } from 'node:path';\nimport { readFile, writeFile, mkdir, copyFile, access } from 'node:fs/promises';\nimport { parse as parseYaml, stringify as stringifyYaml } from 'yaml';\nimport { classifyStatus, type ReviewFile, type ArticleEntry, type ReviewStatus, type ReviewBasis } from './types.js';\n\nexport type SortOption = 'year' | 'title' | 'random' | 'none';\n\nexport interface ReviewExtractOptions {\n sessionId: string;\n filter?: ReviewStatus[];\n sort?: SortOption;\n seed?: number;\n limit?: number;\n offset?: number;\n /** Basis for the review (title, abstract, fulltext). When specified, outputs screening format. */\n basis?: ReviewBasis;\n /** Reviewer identifier (e.g., \"ai:claude\"). Required for all extract modes. */\n reviewer?: string;\n /** Name for the review subset (output goes to for-review/<name>/review.yaml) */\n name: string;\n /** When true, outputs final decision format with reviewHistory and finalDecision fields. */\n finalize?: boolean;\n}\n\n\nexport interface ReviewExtractResult {\n outputPath: string;\n extractedCount: number;\n totalMatching: number;\n}\n\n/**\n * Load review file from session directory\n */\nasync function loadReviewFile(sessionDir: string): Promise<ReviewFile> {\n const reviewsPath = join(sessionDir, '.internal', 'reviews.yaml');\n const content = await readFile(reviewsPath, 'utf-8');\n return parseYaml(content) as ReviewFile;\n}\n\n/**\n * Seeded random number generator (Fisher-Yates shuffle with LCG)\n */\nfunction seededShuffle<T>(array: T[], seed: number): T[] {\n const result = [...array];\n let currentSeed = seed;\n\n // Linear congruential generator\n function random(): number {\n currentSeed = (currentSeed * 1664525 + 1013904223) % 4294967296;\n return currentSeed / 4294967296;\n }\n\n // Fisher-Yates shuffle\n for (let i = result.length - 1; i > 0; i--) {\n const j = Math.floor(random() * (i + 1));\n [result[i], result[j]] = [result[j]!, result[i]!];\n }\n\n return result;\n}\n\n/**\n * Get the best identifier for an article (doi > pmid > scopusId > arxivId > ericId > title)\n */\nfunction getArticleId(article: ArticleEntry): string {\n if (article.doi) return article.doi;\n if (article.pmid) return article.pmid;\n if (article.scopusId) return article.scopusId;\n if (article.arxivId) return article.arxivId;\n if (article.ericId) return article.ericId;\n return article.title;\n}\n\nfunction getBasisGuidanceComment(basis: ReviewBasis): string {\n const schemaLine = '# yaml-language-server: $schema=./review.schema.json';\n switch (basis) {\n case 'title':\n return [\n schemaLine,\n '# Screening by title only.',\n '# Mark clearly irrelevant items as \"exclude\" with a comment explaining the reason.',\n '# Leave everything else as \"uncertain\".',\n '',\n ].join('\\n');\n case 'abstract':\n return [\n schemaLine,\n '# Screening by title and abstract.',\n '# You should be able to decide \"include\" or \"exclude\" for most items at this stage.',\n '# Mark remaining ambiguous items as \"uncertain\" with a comment explaining why.',\n '',\n ].join('\\n');\n case 'fulltext':\n return [\n schemaLine,\n '# Screening by full text. This is the final decision stage.',\n '# Decide \"include\" or \"exclude\" for each item.',\n '# Use \"uncertain\" only when absolutely unavoidable, with a comment explaining why.',\n '',\n ].join('\\n');\n }\n}\n\nfunction getDecisionInlineComment(basis: ReviewBasis): string {\n switch (basis) {\n case 'title':\n return '# exclude / uncertain';\n case 'abstract':\n case 'fulltext':\n return '# include / exclude / uncertain';\n }\n}\n\n/** Build a screening article for --basis mode: only include fields relevant to the basis */\nfunction buildScreeningArticle(article: ArticleEntry, basis: ReviewBasis): ArticleEntry {\n // Start with identifiers\n const result: ArticleEntry = { title: article.title, reviews: [] };\n if (article.doi) result.doi = article.doi;\n if (article.pmid) result.pmid = article.pmid;\n if (article.scopusId) result.scopusId = article.scopusId;\n if (article.arxivId) result.arxivId = article.arxivId;\n if (article.ericId) result.ericId = article.ericId;\n\n // Include abstract for abstract and fulltext basis\n if ((basis === 'abstract' || basis === 'fulltext') && article.abstract) {\n result.abstract = article.abstract;\n }\n\n // Include fulltext ref for fulltext basis\n if (basis === 'fulltext' && article.fulltext) {\n result.fulltext = article.fulltext;\n }\n\n // Pre-populate reviews (reviewer omitted; filled from top-level field on merge)\n result.reviews = [{ decision: 'uncertain' as const, comment: '' } as ArticleEntry['reviews'][0]];\n\n return result;\n}\n\n/** Build a finalize article with reviewHistory and finalDecision, optionally scoped by basis */\nfunction buildFinalizeArticle(article: ArticleEntry, basis?: ReviewBasis): ArticleEntry {\n const result: ArticleEntry = { title: article.title, reviews: [] };\n\n // Always include identifiers\n if (article.doi) result.doi = article.doi;\n if (article.pmid) result.pmid = article.pmid;\n if (article.scopusId) result.scopusId = article.scopusId;\n if (article.arxivId) result.arxivId = article.arxivId;\n if (article.ericId) result.ericId = article.ericId;\n\n // Always include bibliographic metadata\n if (article.authors) result.authors = article.authors;\n if (article.year) result.year = article.year;\n\n // Scope content by basis (or include all if no basis)\n if (!basis || basis === 'abstract' || basis === 'fulltext') {\n if (article.abstract) result.abstract = article.abstract;\n }\n if (!basis || basis === 'fulltext') {\n if (article.fulltext) result.fulltext = article.fulltext;\n }\n\n // Add reviewHistory (existing reviews, read-only)\n result.reviewHistory = article.reviews ?? [];\n\n // Empty reviews for new reviews\n result.reviews = [];\n\n // Null finalDecision as placeholder\n result.finalDecision = null;\n\n return result;\n}\n\nfunction getFinalDecisionGuidanceComment(): string {\n return [\n '# yaml-language-server: $schema=./review.schema.json',\n '# Final decision file: set finalDecision on each article',\n '# Valid decisions: include / exclude / null',\n '',\n ].join('\\n');\n}\n\n/**\n * Sort articles based on sort option\n */\nfunction sortArticles(articles: ArticleEntry[], sort: SortOption, seed?: number): ArticleEntry[] {\n switch (sort) {\n case 'year':\n return [...articles].sort((a, b) => {\n const yearA = a.year ?? '';\n const yearB = b.year ?? '';\n return yearA.localeCompare(yearB);\n });\n case 'title':\n return [...articles].sort((a, b) => a.title.localeCompare(b.title));\n case 'random':\n return seededShuffle(articles, seed ?? Date.now());\n case 'none':\n default:\n return articles;\n }\n}\n\n/**\n * Validate the name parameter for extract\n */\nexport function validateName(name: string): void {\n if (!name || name.trim() === '') {\n throw new Error('--name must not be empty');\n }\n if (name.includes('/') || name.includes('\\\\')) {\n throw new Error(`--name must not contain path separators: \"${name}\"`);\n }\n if (name.includes('..')) {\n throw new Error(`--name must not contain \"..\": \"${name}\"`);\n }\n}\n\n/**\n * Execute review extract command\n */\nexport async function executeReviewExtract(\n options: ReviewExtractOptions,\n sessionsDir: string\n): Promise<ReviewExtractResult> {\n validateName(options.name);\n\n const sessionDir = join(sessionsDir, options.sessionId);\n const outputPath = join(sessionDir, 'for-review', options.name, 'review.yaml');\n const reviewFile = await loadReviewFile(sessionDir);\n\n // Filter articles by status\n const reviewers = reviewFile.reviewers;\n let filtered: ArticleEntry[];\n if (options.filter && options.filter.length > 0) {\n filtered = reviewFile.articles.filter((article) => {\n const status = classifyStatus(article, reviewers);\n return options.filter!.includes(status);\n });\n } else {\n filtered = [...reviewFile.articles];\n }\n\n const totalMatching = filtered.length;\n\n // Sort articles\n const sorted = sortArticles(filtered, options.sort ?? 'none', options.seed);\n\n // Apply pagination\n let paginated = sorted;\n if (options.offset !== undefined && options.offset > 0) {\n paginated = paginated.slice(options.offset);\n }\n if (options.limit !== undefined && options.limit > 0) {\n paginated = paginated.slice(0, options.limit);\n }\n\n if (!options.reviewer) {\n throw new Error('--reviewer is required for review file extract');\n }\n\n let finalContent: string;\n\n if (options.basis && !options.finalize) {\n // Screening mode: basis-scoped content with pre-populated reviews\n const outputFile: ReviewFile = {\n sessionId: options.sessionId,\n basis: options.basis,\n reviewer: options.reviewer,\n articles: paginated.map((article) => buildScreeningArticle(article, options.basis!)),\n };\n\n const yamlContent = stringifyYaml(outputFile, { lineWidth: 0 });\n\n // Add decision inline comments\n const decisionComment = getDecisionInlineComment(options.basis);\n const yamlWithComments = yamlContent.replace(\n /^(\\s*-?\\s*)decision: uncertain$/gm,\n `$1decision: uncertain ${decisionComment}`\n );\n\n const guidanceComment = getBasisGuidanceComment(options.basis);\n finalContent = guidanceComment + yamlWithComments;\n } else {\n // Final decision mode: --finalize, or no --basis (backward compat)\n const outputFile: ReviewFile = {\n sessionId: options.sessionId,\n reviewer: options.reviewer,\n articles: paginated.map((article) => buildFinalizeArticle(article, options.basis)),\n };\n\n const yamlContent = stringifyYaml(outputFile, { lineWidth: 0 });\n\n // Replace finalDecision: null with a commented placeholder for user guidance\n const yamlWithComments = yamlContent.replace(\n /^(\\s*)finalDecision: null$/gm,\n '$1finalDecision: # include / exclude'\n );\n\n const guidanceComment = getFinalDecisionGuidanceComment();\n finalContent = guidanceComment + yamlWithComments;\n }\n\n // Ensure output directory exists\n const outputDir = dirname(outputPath);\n await mkdir(outputDir, { recursive: true });\n\n // Write output YAML\n await writeFile(outputPath, finalContent, 'utf-8');\n\n // Copy schema file to output directory if it exists\n const schemaSourcePath = join(sessionDir, '.internal', 'review.schema.json');\n const schemaDestPath = join(outputDir, 'review.schema.json');\n\n try {\n await access(schemaSourcePath);\n await copyFile(schemaSourcePath, schemaDestPath);\n } catch {\n // Schema file doesn't exist, skip copying\n }\n\n return {\n outputPath,\n extractedCount: paginated.length,\n totalMatching,\n };\n}\n"],"names":["parseYaml","stringifyYaml"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"extract.js","sources":["../../../../src/cli/commands/review/extract.ts"],"sourcesContent":["/**\n * review extract command - Extract subset of articles for distributed review\n */\n\nimport { join, dirname } from 'node:path';\nimport { readFile, writeFile, mkdir, copyFile, access } from 'node:fs/promises';\nimport { parse as parseYaml, stringify as stringifyYaml } from 'yaml';\nimport { classifyStatus, type ReviewFile, type ArticleEntry, type ReviewStatus, type ReviewBasis } from './types.js';\n\nexport type SortOption = 'year' | 'title' | 'random' | 'none';\n\nexport interface ReviewExtractOptions {\n sessionId: string;\n filter?: ReviewStatus[];\n sort?: SortOption;\n seed?: number;\n limit?: number;\n offset?: number;\n /** Basis for the review (title, abstract, fulltext). When specified, outputs screening format. */\n basis?: ReviewBasis;\n /** Reviewer identifier (e.g., \"ai:claude\"). Required for all extract modes. */\n reviewer?: string;\n /** Name for the review subset (output goes to for-review/<name>/review.yaml) */\n name: string;\n /** When true, outputs final decision format with reviewHistory and finalDecision fields. */\n finalize?: boolean;\n}\n\n\nexport interface ReviewExtractResult {\n outputPath: string;\n extractedCount: number;\n totalMatching: number;\n}\n\n/**\n * Load review file from session directory\n */\nasync function loadReviewFile(sessionDir: string): Promise<ReviewFile> {\n const reviewsPath = join(sessionDir, '.internal', 'reviews.yaml');\n const content = await readFile(reviewsPath, 'utf-8');\n return parseYaml(content) as ReviewFile;\n}\n\n/**\n * Seeded random number generator (Fisher-Yates shuffle with LCG)\n */\nfunction seededShuffle<T>(array: T[], seed: number): T[] {\n const result = [...array];\n let currentSeed = seed;\n\n // Linear congruential generator\n function random(): number {\n currentSeed = (currentSeed * 1664525 + 1013904223) % 4294967296;\n return currentSeed / 4294967296;\n }\n\n // Fisher-Yates shuffle\n for (let i = result.length - 1; i > 0; i--) {\n const j = Math.floor(random() * (i + 1));\n [result[i], result[j]] = [result[j]!, result[i]!];\n }\n\n return result;\n}\n\n/**\n * Get the best identifier for an article (doi > pmid > scopusId > arxivId > ericId > title)\n */\nfunction getArticleId(article: ArticleEntry): string {\n if (article.doi) return article.doi;\n if (article.pmid) return article.pmid;\n if (article.scopusId) return article.scopusId;\n if (article.arxivId) return article.arxivId;\n if (article.ericId) return article.ericId;\n return article.title;\n}\n\nfunction getBasisGuidanceComment(basis: ReviewBasis): string {\n const schemaLine = '# yaml-language-server: $schema=./review.schema.json';\n switch (basis) {\n case 'title':\n return [\n schemaLine,\n '# Screening by title only.',\n '# Mark clearly irrelevant items as \"exclude\" with a comment explaining the reason.',\n '# Leave everything else as \"uncertain\".',\n '',\n ].join('\\n');\n case 'abstract':\n return [\n schemaLine,\n '# Screening by title and abstract.',\n '# You should be able to decide \"include\" or \"exclude\" for most items at this stage.',\n '# Mark remaining ambiguous items as \"uncertain\" with a comment explaining why.',\n '',\n ].join('\\n');\n case 'fulltext':\n return [\n schemaLine,\n '# Screening by full text. This is the final decision stage.',\n '# Decide \"include\" or \"exclude\" for each item.',\n '# Use \"uncertain\" only when absolutely unavoidable, with a comment explaining why.',\n '',\n ].join('\\n');\n }\n}\n\nfunction getDecisionInlineComment(basis: ReviewBasis): string {\n switch (basis) {\n case 'title':\n return '# exclude / uncertain';\n case 'abstract':\n case 'fulltext':\n return '# include / exclude / uncertain';\n }\n}\n\n/** Build a screening article for --basis mode: only include fields relevant to the basis */\nfunction buildScreeningArticle(article: ArticleEntry, basis: ReviewBasis): ArticleEntry {\n // Start with identifiers\n const result: ArticleEntry = { title: article.title, reviews: [] };\n if (article.doi) result.doi = article.doi;\n if (article.pmid) result.pmid = article.pmid;\n if (article.scopusId) result.scopusId = article.scopusId;\n if (article.arxivId) result.arxivId = article.arxivId;\n if (article.ericId) result.ericId = article.ericId;\n\n // Include abstract for abstract and fulltext basis\n if ((basis === 'abstract' || basis === 'fulltext') && article.abstract) {\n result.abstract = article.abstract;\n }\n\n // Include fulltext ref for fulltext basis\n if (basis === 'fulltext' && article.fulltext) {\n result.fulltext = article.fulltext;\n }\n\n // Pre-populate reviews (reviewer omitted; filled from top-level field on merge)\n result.reviews = [{ decision: 'uncertain' as const, comment: '' } as ArticleEntry['reviews'][0]];\n\n return result;\n}\n\n/** Build a finalize article with reviewHistory and finalDecision, optionally scoped by basis */\nfunction buildFinalizeArticle(article: ArticleEntry, basis?: ReviewBasis): ArticleEntry {\n const result: ArticleEntry = { title: article.title, reviews: [] };\n\n // Always include identifiers\n if (article.doi) result.doi = article.doi;\n if (article.pmid) result.pmid = article.pmid;\n if (article.scopusId) result.scopusId = article.scopusId;\n if (article.arxivId) result.arxivId = article.arxivId;\n if (article.ericId) result.ericId = article.ericId;\n\n // Always include bibliographic metadata\n if (article.authors) result.authors = article.authors;\n if (article.year) result.year = article.year;\n\n // Scope content by basis (or include all if no basis)\n if (!basis || basis === 'abstract' || basis === 'fulltext') {\n if (article.abstract) result.abstract = article.abstract;\n }\n if (!basis || basis === 'fulltext') {\n if (article.fulltext) result.fulltext = article.fulltext;\n }\n\n // Add reviewHistory (existing reviews, read-only)\n result.reviewHistory = article.reviews ?? [];\n\n // Empty reviews for new reviews\n result.reviews = [];\n\n // Null finalDecision as placeholder\n result.finalDecision = null;\n\n return result;\n}\n\nfunction getFinalDecisionGuidanceComment(): string {\n return [\n '# yaml-language-server: $schema=./review.schema.json',\n '# Final decision file: set finalDecision on each article',\n '# Valid decisions: include / exclude / null',\n '',\n ].join('\\n');\n}\n\n/**\n * Sort articles based on sort option\n */\nfunction sortArticles(articles: ArticleEntry[], sort: SortOption, seed?: number): ArticleEntry[] {\n switch (sort) {\n case 'year':\n return [...articles].sort((a, b) => {\n const yearA = a.year ?? '';\n const yearB = b.year ?? '';\n return yearA.localeCompare(yearB);\n });\n case 'title':\n return [...articles].sort((a, b) => a.title.localeCompare(b.title));\n case 'random':\n return seededShuffle(articles, seed ?? Date.now());\n case 'none':\n default:\n return articles;\n }\n}\n\n/**\n * Validate the name parameter for extract\n */\nexport function validateName(name: string): void {\n if (!name || name.trim() === '') {\n throw new Error('--name must not be empty');\n }\n if (name.includes('/') || name.includes('\\\\')) {\n throw new Error(`--name must not contain path separators: \"${name}\"`);\n }\n if (name.includes('..')) {\n throw new Error(`--name must not contain \"..\": \"${name}\"`);\n }\n}\n\n/**\n * Execute review extract command\n */\nexport async function executeReviewExtract(\n options: ReviewExtractOptions,\n sessionsDir: string\n): Promise<ReviewExtractResult> {\n validateName(options.name);\n\n const sessionDir = join(sessionsDir, options.sessionId);\n const outputPath = join(sessionDir, 'for-review', options.name, 'review.yaml');\n const reviewFile = await loadReviewFile(sessionDir);\n\n // Filter articles by status\n const reviewers = reviewFile.reviewers;\n let filtered: ArticleEntry[];\n if (options.filter && options.filter.length > 0) {\n filtered = reviewFile.articles.filter((article) => {\n const status = classifyStatus(article, reviewers);\n return options.filter!.includes(status);\n });\n } else {\n filtered = [...reviewFile.articles];\n }\n\n const totalMatching = filtered.length;\n\n // Sort articles\n const sorted = sortArticles(filtered, options.sort ?? 'none', options.seed);\n\n // Apply pagination\n let paginated = sorted;\n if (options.offset !== undefined && options.offset > 0) {\n paginated = paginated.slice(options.offset);\n }\n if (options.limit !== undefined && options.limit > 0) {\n paginated = paginated.slice(0, options.limit);\n }\n\n if (!options.reviewer) {\n throw new Error('--reviewer is required for review file extract');\n }\n\n let finalContent: string;\n\n if (options.basis && !options.finalize) {\n // Screening mode: basis-scoped content with pre-populated reviews\n const outputFile: ReviewFile = {\n sessionId: options.sessionId,\n basis: options.basis,\n reviewer: options.reviewer,\n articles: paginated.map((article) => buildScreeningArticle(article, options.basis!)),\n };\n\n const yamlContent = stringifyYaml(outputFile, { lineWidth: 0 });\n\n // Add decision inline comments\n const decisionComment = getDecisionInlineComment(options.basis);\n let yamlWithComments = yamlContent.replace(\n /^(\\s*-?\\s*)decision: uncertain$/gm,\n `$1decision: uncertain ${decisionComment}`\n );\n\n // Add comment field inline guidance\n yamlWithComments = yamlWithComments.replace(\n /^(\\s*)comment: \"\"$/gm,\n '$1comment: \"\" # reason for decision'\n );\n\n const guidanceComment = getBasisGuidanceComment(options.basis);\n finalContent = guidanceComment + yamlWithComments;\n } else {\n // Final decision mode: --finalize, or no --basis (backward compat)\n const outputFile: ReviewFile = {\n sessionId: options.sessionId,\n reviewer: options.reviewer,\n articles: paginated.map((article) => buildFinalizeArticle(article, options.basis)),\n };\n\n const yamlContent = stringifyYaml(outputFile, { lineWidth: 0 });\n\n // Replace finalDecision: null with a commented placeholder for user guidance\n let yamlWithComments = yamlContent.replace(\n /^(\\s*)finalDecision: null$/gm,\n '$1finalDecision: # include / exclude'\n );\n\n // Add reviews array inline guidance\n yamlWithComments = yamlWithComments.replace(\n /^(\\s*)reviews: \\[\\]$/gm,\n '$1reviews: [] # add new reviews here'\n );\n\n const guidanceComment = getFinalDecisionGuidanceComment();\n finalContent = guidanceComment + yamlWithComments;\n }\n\n // Ensure output directory exists\n const outputDir = dirname(outputPath);\n await mkdir(outputDir, { recursive: true });\n\n // Write output YAML\n await writeFile(outputPath, finalContent, 'utf-8');\n\n // Copy schema file to output directory if it exists\n const schemaSourcePath = join(sessionDir, '.internal', 'review.schema.json');\n const schemaDestPath = join(outputDir, 'review.schema.json');\n\n try {\n await access(schemaSourcePath);\n await copyFile(schemaSourcePath, schemaDestPath);\n } catch {\n // Schema file doesn't exist, skip copying\n }\n\n return {\n outputPath,\n extractedCount: paginated.length,\n totalMatching,\n };\n}\n"],"names":["parseYaml","stringifyYaml"],"mappings":"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"}
|
|
@@ -3,6 +3,7 @@ export interface ReviewFinalizeOptions {
|
|
|
3
3
|
sessionId: string;
|
|
4
4
|
dryRun?: boolean;
|
|
5
5
|
minReviewers?: number;
|
|
6
|
+
decision?: 'include' | 'exclude';
|
|
6
7
|
}
|
|
7
8
|
export interface ReviewFinalizeResult {
|
|
8
9
|
includedCount: number;
|
|
@@ -18,5 +19,6 @@ export declare function executeReviewFinalize(options: ReviewFinalizeOptions, se
|
|
|
18
19
|
*/
|
|
19
20
|
export declare function formatFinalizeOutput(result: ReviewFinalizeResult, options?: {
|
|
20
21
|
dryRun?: boolean;
|
|
22
|
+
decision?: 'include' | 'exclude';
|
|
21
23
|
}): string;
|
|
22
24
|
//# sourceMappingURL=finalize.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"finalize.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/review/finalize.ts"],"names":[],"mappings":"AAAA;;GAEG;AAKH,OAAO,EAAmC,KAAK,YAAY,EAAE,MAAM,YAAY,CAAC;AAEhF,MAAM,WAAW,qBAAqB;IACpC,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"finalize.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/review/finalize.ts"],"names":[],"mappings":"AAAA;;GAEG;AAKH,OAAO,EAAmC,KAAK,YAAY,EAAE,MAAM,YAAY,CAAC;AAEhF,MAAM,WAAW,qBAAqB;IACpC,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,SAAS,GAAG,SAAS,CAAC;CAClC;AAED,MAAM,WAAW,oBAAoB;IACnC,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC;CAC/C;AAcD;;GAEG;AACH,wBAAsB,qBAAqB,CACzC,OAAO,EAAE,qBAAqB,EAC9B,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,oBAAoB,CAAC,CAwD/B;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,oBAAoB,EAC5B,OAAO,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,EAAE,SAAS,GAAG,SAAS,CAAA;CAAE,GAC/D,MAAM,CAuCR"}
|
|
@@ -28,6 +28,11 @@ async function executeReviewFinalize(options, sessionsDir) {
|
|
|
28
28
|
for (const article of reviewFile.articles) {
|
|
29
29
|
const status = classifyStatus(article, reviewers);
|
|
30
30
|
if (status === "agreed-include" || status === "agreed-exclude") {
|
|
31
|
+
const consensusDecision = status === "agreed-include" ? "include" : "exclude";
|
|
32
|
+
if (options.decision && options.decision !== consensusDecision) {
|
|
33
|
+
result.skippedByStatus[status]++;
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
31
36
|
const reviews = article.reviews ?? [];
|
|
32
37
|
const uniqueReviewers = new Set(reviews.map((r) => r.reviewer));
|
|
33
38
|
if (uniqueReviewers.size < minReviewers) {
|
|
@@ -35,7 +40,7 @@ async function executeReviewFinalize(options, sessionsDir) {
|
|
|
35
40
|
continue;
|
|
36
41
|
}
|
|
37
42
|
if (!options.dryRun) {
|
|
38
|
-
article.finalDecision =
|
|
43
|
+
article.finalDecision = consensusDecision;
|
|
39
44
|
}
|
|
40
45
|
if (status === "agreed-include") {
|
|
41
46
|
result.includedCount++;
|
|
@@ -75,6 +80,12 @@ function formatFinalizeOutput(result, options) {
|
|
|
75
80
|
if (result.skippedByStatus.conflicting > 0) {
|
|
76
81
|
skippedParts.push(`${result.skippedByStatus.conflicting} conflicting`);
|
|
77
82
|
}
|
|
83
|
+
if (options?.decision && result.skippedByStatus["agreed-include"] > 0) {
|
|
84
|
+
skippedParts.push(`${result.skippedByStatus["agreed-include"]} agreed-include (filtered)`);
|
|
85
|
+
}
|
|
86
|
+
if (options?.decision && result.skippedByStatus["agreed-exclude"] > 0) {
|
|
87
|
+
skippedParts.push(`${result.skippedByStatus["agreed-exclude"]} agreed-exclude (filtered)`);
|
|
88
|
+
}
|
|
78
89
|
if (skippedParts.length > 0) {
|
|
79
90
|
lines.push(`Skipped: ${skippedParts.join(", ")}`);
|
|
80
91
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"finalize.js","sources":["../../../../src/cli/commands/review/finalize.ts"],"sourcesContent":["/**\n * review finalize command - Auto-set finalDecision for articles with consensus\n */\n\nimport { join } from 'node:path';\nimport { readFile, writeFile } from 'node:fs/promises';\nimport { parse as parseYaml, stringify as stringifyYaml } from 'yaml';\nimport { classifyStatus, type ReviewFile, type ReviewStatus } from './types.js';\n\nexport interface ReviewFinalizeOptions {\n sessionId: string;\n dryRun?: boolean;\n minReviewers?: number;\n}\n\nexport interface ReviewFinalizeResult {\n includedCount: number;\n excludedCount: number;\n skippedByStatus: Record<ReviewStatus, number>;\n}\n\nfunction createEmptySkippedByStatus(): Record<ReviewStatus, number> {\n return {\n pending: 0,\n incomplete: 0,\n uncertain: 0,\n 'agreed-include': 0,\n 'agreed-exclude': 0,\n conflicting: 0,\n finalized: 0,\n };\n}\n\n/**\n * Execute review finalize command\n */\nexport async function executeReviewFinalize(\n options: ReviewFinalizeOptions,\n sessionsDir: string\n): Promise<ReviewFinalizeResult> {\n const sessionDir = join(sessionsDir, options.sessionId);\n const reviewsPath = join(sessionDir, '.internal', 'reviews.yaml');\n const content = await readFile(reviewsPath, 'utf-8');\n const reviewFile = parseYaml(content) as ReviewFile;\n\n const reviewers = reviewFile.reviewers ?? [];\n const minReviewers = options.minReviewers ?? 1;\n\n const result: ReviewFinalizeResult = {\n includedCount: 0,\n excludedCount: 0,\n skippedByStatus: createEmptySkippedByStatus(),\n };\n\n for (const article of reviewFile.articles) {\n const status = classifyStatus(article, reviewers);\n\n if (status === 'agreed-include' || status === 'agreed-exclude') {\n // Check minimum reviewer count\n const reviews = article.reviews ?? [];\n const uniqueReviewers = new Set(reviews.map((r) => r.reviewer));\n if (uniqueReviewers.size < minReviewers) {\n result.skippedByStatus[status]++;\n continue;\n }\n\n if (!options.dryRun) {\n article.finalDecision =
|
|
1
|
+
{"version":3,"file":"finalize.js","sources":["../../../../src/cli/commands/review/finalize.ts"],"sourcesContent":["/**\n * review finalize command - Auto-set finalDecision for articles with consensus\n */\n\nimport { join } from 'node:path';\nimport { readFile, writeFile } from 'node:fs/promises';\nimport { parse as parseYaml, stringify as stringifyYaml } from 'yaml';\nimport { classifyStatus, type ReviewFile, type ReviewStatus } from './types.js';\n\nexport interface ReviewFinalizeOptions {\n sessionId: string;\n dryRun?: boolean;\n minReviewers?: number;\n decision?: 'include' | 'exclude';\n}\n\nexport interface ReviewFinalizeResult {\n includedCount: number;\n excludedCount: number;\n skippedByStatus: Record<ReviewStatus, number>;\n}\n\nfunction createEmptySkippedByStatus(): Record<ReviewStatus, number> {\n return {\n pending: 0,\n incomplete: 0,\n uncertain: 0,\n 'agreed-include': 0,\n 'agreed-exclude': 0,\n conflicting: 0,\n finalized: 0,\n };\n}\n\n/**\n * Execute review finalize command\n */\nexport async function executeReviewFinalize(\n options: ReviewFinalizeOptions,\n sessionsDir: string\n): Promise<ReviewFinalizeResult> {\n const sessionDir = join(sessionsDir, options.sessionId);\n const reviewsPath = join(sessionDir, '.internal', 'reviews.yaml');\n const content = await readFile(reviewsPath, 'utf-8');\n const reviewFile = parseYaml(content) as ReviewFile;\n\n const reviewers = reviewFile.reviewers ?? [];\n const minReviewers = options.minReviewers ?? 1;\n\n const result: ReviewFinalizeResult = {\n includedCount: 0,\n excludedCount: 0,\n skippedByStatus: createEmptySkippedByStatus(),\n };\n\n for (const article of reviewFile.articles) {\n const status = classifyStatus(article, reviewers);\n\n if (status === 'agreed-include' || status === 'agreed-exclude') {\n // Check decision filter\n const consensusDecision = status === 'agreed-include' ? 'include' : 'exclude';\n if (options.decision && options.decision !== consensusDecision) {\n result.skippedByStatus[status]++;\n continue;\n }\n\n // Check minimum reviewer count\n const reviews = article.reviews ?? [];\n const uniqueReviewers = new Set(reviews.map((r) => r.reviewer));\n if (uniqueReviewers.size < minReviewers) {\n result.skippedByStatus[status]++;\n continue;\n }\n\n if (!options.dryRun) {\n article.finalDecision = consensusDecision;\n }\n\n if (status === 'agreed-include') {\n result.includedCount++;\n } else {\n result.excludedCount++;\n }\n } else {\n result.skippedByStatus[status]++;\n }\n }\n\n // Write back if not dry-run\n if (!options.dryRun) {\n const yamlContent = stringifyYaml(reviewFile, { lineWidth: 0 });\n const schemaComment = `# yaml-language-server: $schema=./review.schema.json\\n`;\n await writeFile(reviewsPath, schemaComment + yamlContent, 'utf-8');\n }\n\n return result;\n}\n\n/**\n * Format finalize result as human-readable string\n */\nexport function formatFinalizeOutput(\n result: ReviewFinalizeResult,\n options?: { dryRun?: boolean; decision?: 'include' | 'exclude' }\n): string {\n const lines: string[] = [];\n\n if (options?.dryRun) {\n lines.push('Dry run - no changes made');\n lines.push('');\n }\n\n const total = result.includedCount + result.excludedCount;\n lines.push(`Finalized ${total} articles (${result.includedCount} include, ${result.excludedCount} exclude)`);\n\n // Build skipped summary (only non-zero, non-agreed statuses)\n const skippedParts: string[] = [];\n if (result.skippedByStatus.pending > 0) {\n skippedParts.push(`${result.skippedByStatus.pending} pending`);\n }\n if (result.skippedByStatus.incomplete > 0) {\n skippedParts.push(`${result.skippedByStatus.incomplete} incomplete`);\n }\n if (result.skippedByStatus.uncertain > 0) {\n skippedParts.push(`${result.skippedByStatus.uncertain} uncertain`);\n }\n if (result.skippedByStatus.conflicting > 0) {\n skippedParts.push(`${result.skippedByStatus.conflicting} conflicting`);\n }\n\n // Show filtered-out agreed counts when --decision is active\n if (options?.decision && result.skippedByStatus['agreed-include'] > 0) {\n skippedParts.push(`${result.skippedByStatus['agreed-include']} agreed-include (filtered)`);\n }\n if (options?.decision && result.skippedByStatus['agreed-exclude'] > 0) {\n skippedParts.push(`${result.skippedByStatus['agreed-exclude']} agreed-exclude (filtered)`);\n }\n\n if (skippedParts.length > 0) {\n lines.push(`Skipped: ${skippedParts.join(', ')}`);\n }\n\n return lines.join('\\n');\n}\n"],"names":["parseYaml","stringifyYaml"],"mappings":"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"}
|
package/dist/cli/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":";AAQA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAqLpC;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,0BAA0B;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gCAAgC;IAChC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,4BAA4B;IAC5B,OAAO,EAAE,OAAO,CAAC;IACjB,wCAAwC;IACxC,KAAK,EAAE,OAAO,CAAC;IACf,qEAAqE;IACrE,KAAK,EAAE,OAAO,CAAC;CAChB;AAED;;GAEG;AACH,wBAAgB,aAAa,IAAI,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":";AAQA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAqLpC;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,0BAA0B;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gCAAgC;IAChC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,4BAA4B;IAC5B,OAAO,EAAE,OAAO,CAAC;IACjB,wCAAwC;IACxC,KAAK,EAAE,OAAO,CAAC;IACf,qEAAqE;IACrE,KAAK,EAAE,OAAO,CAAC;CAChB;AAED;;GAEG;AACH,wBAAgB,aAAa,IAAI,OAAO,CAu9EvC;AAED;;GAEG;AACH,wBAAsB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAG1C"}
|
package/dist/cli/index.js
CHANGED
|
@@ -1790,13 +1790,21 @@ Examples:
|
|
|
1790
1790
|
process.exitCode = EXIT_CODES.SESSION_ERROR;
|
|
1791
1791
|
}
|
|
1792
1792
|
});
|
|
1793
|
-
reviewCommand.command("finalize").description("Auto-set finalDecision for articles with reviewer consensus").requiredOption("--session <id>", "session ID").option("--dry-run", "preview without changes", false).option("--min-reviewers <n>", "minimum agreeing reviewers needed", "1").action(async (options) => {
|
|
1793
|
+
reviewCommand.command("finalize").description("Auto-set finalDecision for articles with reviewer consensus").requiredOption("--session <id>", "session ID").option("--dry-run", "preview without changes", false).option("--min-reviewers <n>", "minimum agreeing reviewers needed", "1").option("--decision <type>", "only finalize this decision type (include or exclude)").action(async (options) => {
|
|
1794
1794
|
const globalOpts = program.opts();
|
|
1795
1795
|
try {
|
|
1796
1796
|
const sessionsDir = await getSessionsDir(globalOpts);
|
|
1797
|
+
if (options.decision && options.decision !== "include" && options.decision !== "exclude") {
|
|
1798
|
+
if (!globalOpts.quiet) {
|
|
1799
|
+
console.error(`Error: --decision must be "include" or "exclude", got "${options.decision}"`);
|
|
1800
|
+
}
|
|
1801
|
+
process.exitCode = EXIT_CODES.GENERAL_ERROR;
|
|
1802
|
+
return;
|
|
1803
|
+
}
|
|
1797
1804
|
const finalizeOptions = {
|
|
1798
1805
|
sessionId: options.session,
|
|
1799
|
-
...options.dryRun && { dryRun: options.dryRun }
|
|
1806
|
+
...options.dryRun && { dryRun: options.dryRun },
|
|
1807
|
+
...options.decision && { decision: options.decision }
|
|
1800
1808
|
};
|
|
1801
1809
|
const minReviewers = parseInt(options.minReviewers, 10);
|
|
1802
1810
|
if (!Number.isNaN(minReviewers) && minReviewers > 1) {
|
|
@@ -1804,7 +1812,10 @@ Examples:
|
|
|
1804
1812
|
}
|
|
1805
1813
|
const result = await executeReviewFinalize(finalizeOptions, sessionsDir);
|
|
1806
1814
|
if (!globalOpts.quiet) {
|
|
1807
|
-
console.log(formatFinalizeOutput(result, {
|
|
1815
|
+
console.log(formatFinalizeOutput(result, {
|
|
1816
|
+
dryRun: options.dryRun,
|
|
1817
|
+
...finalizeOptions.decision && { decision: finalizeOptions.decision }
|
|
1818
|
+
}));
|
|
1808
1819
|
if (!options.dryRun) {
|
|
1809
1820
|
const statusResult = await executeReviewStatus({ sessionId: options.session }, sessionsDir);
|
|
1810
1821
|
const suggestion = formatSuggestion(getSuggestion({
|
package/dist/cli/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../../src/cli/index.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * CLI entry point for search-hub.\n */\nimport { config as loadDotenv } from 'dotenv';\n\n// Load .env file as early as possible, before any config loading\nloadDotenv({ quiet: true });\nimport { Command } from 'commander';\nimport { VERSION } from '../version.js';\nimport { init } from './commands/init.js';\nimport { EXIT_CODES } from './exit-codes.js';\nimport { loadConfig, saveConfig, getDefaultConfig, type Config } from '../config/index.js';\nimport { getDefaultConfigPath } from '../config/paths.js';\nimport {\n viewConfig,\n viewConfigKey,\n setConfigKey,\n} from './commands/config.js';\nimport {\n validateQueryCommand,\n formatValidateResult,\n formatVocabValidationOutput,\n hasVocabErrors,\n detectSchemaLink,\n} from './commands/query/validate.js';\nimport { MeSHLookupClient } from '../query/mesh-lookup.js';\nimport { RateLimiter } from '../providers/base/rate-limiter.js';\nimport { VocabCache } from '../query/vocab-cache.js';\nimport {\n createEricCountValidator,\n createEmtreeCountValidator,\n type CountVocabValidator,\n} from '../query/vocab-validator.js';\nimport { createProviderInstance } from './commands/search-executor.js';\nimport {\n translateQueryCommand,\n formatTranslateResult,\n} from './commands/query/translate.js';\nimport {\n inspectQueryCommand,\n formatInspectOutput,\n} from './commands/query/inspect.js';\nimport {\n generateQueryTemplate,\n writeQueryTemplate,\n} from './commands/query/init.js';\nimport type { ProviderName } from '../providers/base/types.js';\nimport {\n listSessionsForDisplay,\n getSessionDetails,\n computeDeduplicationStats,\n formatSessionList,\n formatSessionDetails,\n} from './commands/status.js';\nimport {\n parseSearchOptions,\n validateSearchInput,\n formatDryRunOutput,\n formatCountOnlyOutput,\n formatPreviewOutput,\n formatShortKeywordWarning,\n} from './commands/search.js';\nimport { executeSearch, executeCountOnly, executePreview } from './commands/search-executor.js';\nimport {\n parseResumeOptions,\n validateResumeInput,\n getResumableProvidersForCommand,\n} from './commands/resume.js';\nimport { executeResume } from './commands/resume-executor.js';\nimport { formatVerboseProviderDetails } from './commands/search-utils.js';\nimport {\n parseExportOptions,\n validateExportInput,\n formatIds,\n formatJson,\n formatJsonl,\n formatCslJson,\n deduplicateArticles,\n type JsonExportMetadata,\n} from './commands/export.js';\nimport {\n computeSummary,\n formatSummary,\n formatSummaryJson,\n} from './commands/summary.js';\nimport {\n parseResultsOptions,\n validateResultsInput,\n formatResultsList,\n formatResultsJson,\n} from './commands/results.js';\nimport { filterByQuery } from './commands/query-filter.js';\nimport {\n loadNotes,\n addNote,\n addAssessment,\n formatNotesList,\n formatAllSessionNotes,\n type SessionNotes,\n} from './commands/notes.js';\nimport {\n computeDiff,\n computeQueryDiff,\n formatDiff,\n formatDiffJson,\n type ShowFilter,\n} from './commands/diff.js';\nimport {\n mergeArticles,\n validateMergeSources,\n createMergedSession,\n formatMergeOutput as formatSessionMergeOutput,\n formatMergeJson as formatSessionMergeJson,\n} from './commands/merge.js';\n\nimport {\n executeReviewInit,\n type ReviewInitOptions,\n} from './commands/review/init.js';\nimport {\n executeReviewStatus,\n formatStatusOutput,\n type ReviewStatusOptions,\n} from './commands/review/status.js';\nimport {\n executeReviewList,\n formatListOutput,\n type ReviewListOptions,\n type ListFilter,\n} from './commands/review/list.js';\nimport {\n executeReviewExtract,\n type ReviewExtractOptions,\n type SortOption,\n} from './commands/review/extract.js';\nimport {\n executeReviewMerge,\n formatMergeOutput,\n type ReviewMergeOptions,\n} from './commands/review/merge.js';\nimport {\n executeReviewMark,\n type ReviewMarkOptions,\n} from './commands/review/mark.js';\nimport {\n executeReviewExport,\n formatExportOutput,\n type ReviewExportOptions,\n type ExportFormat as ReviewExportFormat,\n type ExportFilter as ReviewExportFilter,\n} from './commands/review/export.js';\nimport {\n executeReviewFinalize,\n formatFinalizeOutput,\n type ReviewFinalizeOptions,\n} from './commands/review/finalize.js';\nimport { type ReviewStatus } from './commands/review/types.js';\nimport { registerFulltextCommands } from './commands/fulltext/index.js';\n\nimport {\n parseRegisterOptions,\n validateRegisterInput,\n formatRegistrationSummary,\n formatDryRunOutput as formatRegisterDryRunOutput,\n hasReviewFile,\n getReviewSummary,\n getIncludedArticles,\n formatReviewRequiredMessage,\n formatNoIncludedArticlesError,\n formatPendingWarning,\n formatIgnoringReviewsNote,\n confirmPrompt,\n} from './commands/register.js';\nimport { formatSuggestion } from './suggestions/index.js';\nimport { getSuggestion } from './suggestions/rules.js';\nimport { registerArticles, saveRegistrationRecord } from '../integration/register.js';\nimport { checkRefAvailable, checkNpmAvailable, installRefManager } from '../integration/ref-cli.js';\nimport { loadSession, sessionExists, listSessions } from '../session/manager.js';\nimport { parseQueryFile, detectShortKeywords } from '../query/parser.js';\nimport { writeFile, readFile } from 'node:fs/promises';\nimport { realpathSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { getSessionsDir } from './utils/sessions-dir.js';\nimport { expandPath } from '../utils/path.js';\nimport { loadSessionArticles, loadSessionQuery } from './commands/session-utils.js';\nimport { parseIdentifierFile, checkCoverage, formatCheckResult, formatCheckResultJson } from './commands/check.js';\n\n/**\n * Global CLI options available to all commands.\n */\nexport interface GlobalOptions {\n /** Path to config file */\n config?: string;\n /** Path to session directory */\n sessionDir?: string;\n /** Enable verbose output */\n verbose: boolean;\n /** Suppress all output except errors */\n quiet: boolean;\n /** Enable color output (default: true, use --no-color to disable) */\n color: boolean;\n}\n\n/**\n * Create and configure the CLI program.\n */\nexport function createProgram(): Command {\n const program = new Command();\n\n program\n .name('search-hub')\n .version(VERSION)\n .description(\n 'CLI tool for systematic literature searching across multiple academic databases'\n )\n .option('-c, --config <path>', 'path to config file')\n .option('--session-dir <path>', 'path to session directory')\n .option('-v, --verbose', 'enable verbose output', false)\n .option('--quiet', 'suppress all output except errors', false)\n .option('--no-color', 'disable color output')\n .addHelpText('after', `\nWorkflow:\n 1. query init → edit → validate / --dry-run Query preparation\n 2. search --preview → search Preview & execute\n 3. results / summary / diff / check Inspect & verify\n 4. review init → extract → merge → status Systematic review\n 5. register / export Output\n\n Iterate: search → results -q → check → diff Query refinement\n\nQuick Start:\n $ search-hub query init -o search.yaml # Create query template\n $ search-hub search search.yaml --count-only # Check hit counts\n $ search-hub search search.yaml # Execute search\n $ search-hub results <session> # Review titles`);\n\n // Register init command\n program\n .command('init')\n .description('Initialize configuration directory')\n .option('-f, --force', 'overwrite existing configuration', false)\n .addHelpText('after', `\nExamples:\n $ search-hub init # Initialize with default settings\n $ search-hub init --force # Overwrite existing configuration`)\n .action(async (options: { force: boolean }) => {\n const globalOpts = program.opts() as GlobalOptions;\n try {\n const result = await init({ force: options.force });\n if (!globalOpts.quiet) {\n if (result.success) {\n console.log(result.message);\n } else {\n console.error(result.message);\n }\n }\n process.exitCode = result.success ? EXIT_CODES.SUCCESS : EXIT_CODES.CONFIG_ERROR;\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error(\n 'Error:',\n error instanceof Error ? error.message : error\n );\n }\n process.exitCode = EXIT_CODES.GENERAL_ERROR;\n }\n });\n\n // Register config command\n program\n .command('config')\n .description('View and edit configuration')\n .argument('[key]', 'configuration key to view or set')\n .argument('[value]', 'value to set for the key')\n .addHelpText('after', `\nExamples:\n $ search-hub config # Show all config\n $ search-hub config providers.pubmed # Show PubMed config\n $ search-hub config providers.pubmed.api_key KEY # Set API key`)\n .action(async (key?: string, value?: string) => {\n const globalOpts = program.opts() as GlobalOptions;\n try {\n // Load config - use default if no config file exists\n let config;\n try {\n config = await loadConfig(\n globalOpts.config ? { explicitConfigPath: globalOpts.config } : {}\n );\n } catch {\n config = getDefaultConfig();\n }\n\n if (!key) {\n // View all config\n if (!globalOpts.quiet) {\n console.log(viewConfig(config));\n }\n } else if (!value) {\n // View specific key\n const result = viewConfigKey(config, key);\n if (result.success) {\n if (!globalOpts.quiet) {\n console.log(result.value);\n }\n } else {\n if (!globalOpts.quiet) {\n console.error(`Error: ${result.error}`);\n }\n process.exitCode = EXIT_CODES.CONFIG_ERROR;\n return;\n }\n } else {\n // Set key value\n const result = setConfigKey(config, key, value);\n if (result.success) {\n // Save the modified config to file\n const configPath = globalOpts.config ? expandPath(globalOpts.config) : getDefaultConfigPath();\n try {\n await saveConfig(config, { path: configPath });\n if (!globalOpts.quiet) {\n console.log(`Set ${key} = ${result.value}`);\n console.log(`Saved to ${configPath}`);\n }\n } catch (saveError) {\n if (!globalOpts.quiet) {\n console.error(\n `Error saving config: ${saveError instanceof Error ? saveError.message : saveError}`\n );\n }\n process.exitCode = EXIT_CODES.CONFIG_ERROR;\n return;\n }\n } else {\n if (!globalOpts.quiet) {\n console.error(`Error: ${result.error}`);\n }\n process.exitCode = EXIT_CODES.CONFIG_ERROR;\n return;\n }\n }\n process.exitCode = EXIT_CODES.SUCCESS;\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error(\n 'Error:',\n error instanceof Error ? error.message : error\n );\n }\n process.exitCode = EXIT_CODES.CONFIG_ERROR;\n }\n });\n\n // Register query command group\n const queryCommand = program\n .command('query')\n .description('Query file utilities')\n .addHelpText('after', `\nQuery YAML format (minimal):\n name: my_search\n query:\n - field: title_abstract\n terms:\n keywords: [\"term1\", \"term2\"]\n operator: OR\n\nUse \"search-hub query init\" to generate a template.`);\n\n queryCommand\n .command('validate')\n .description('Validate query YAML file (auto-checks controlled vocabulary)')\n .argument('<file>', 'path to query YAML file')\n .option('--no-vocab', 'skip controlled vocabulary validation')\n .option('--no-cache', 'skip vocabulary lookup cache')\n .addHelpText('after', `\nExamples:\n $ search-hub query validate ./diabetes-ai.yaml\n $ search-hub query validate ./diabetes-ai.yaml --no-vocab # Skip MeSH check\n $ search-hub query validate ./diabetes-ai.yaml --no-cache # Ignore cache`)\n .action(async (file: string, opts: { vocab?: boolean; cache?: boolean }) => {\n const globalOpts = program.opts() as GlobalOptions;\n try {\n const noVocab = opts.vocab === false;\n const noCache = opts.cache === false;\n\n let cache: VocabCache | undefined;\n if (!noVocab && !noCache) {\n cache = new VocabCache();\n await cache.load();\n }\n\n const hasSchema = await detectSchemaLink(file);\n\n if (noVocab) {\n const result = await validateQueryCommand(file, { noVocab });\n\n if (!globalOpts.quiet) {\n let output = formatValidateResult(result, file);\n const suggestion = formatSuggestion(getSuggestion({\n command: 'query validate',\n queryFile: file,\n validationSuccess: result.success,\n hasSchemaLink: hasSchema,\n }));\n if (suggestion) output += '\\n' + suggestion;\n console.log(output);\n }\n process.exitCode = !result.success\n ? EXIT_CODES.QUERY_ERROR\n : EXIT_CODES.SUCCESS;\n return;\n }\n\n const rateLimiter = new RateLimiter({ tokensPerSecond: 3 });\n const meshClient = new MeSHLookupClient({\n rateLimiter,\n ...(cache ? { cache } : {}),\n });\n\n // Create count validators for ERIC/Emtree\n const countValidators: CountVocabValidator[] = [];\n let config: Config | undefined;\n try {\n config = await loadConfig(\n globalOpts.config ? { explicitConfigPath: globalOpts.config } : {}\n );\n } catch {\n // Config not available — skip count validators\n }\n\n if (config) {\n const ericProvider = createProviderInstance('eric', config);\n if (ericProvider) {\n countValidators.push(\n createEricCountValidator(ericProvider, cache ? { cache } : undefined)\n );\n }\n\n const scopusProvider = createProviderInstance('scopus', config);\n if (scopusProvider) {\n countValidators.push(\n createEmtreeCountValidator(scopusProvider, cache ? { cache } : undefined)\n );\n }\n }\n\n const result = await validateQueryCommand(file, {\n meshClient,\n ...(countValidators.length > 0 ? { countValidators } : {}),\n });\n\n if (cache) {\n await cache.save();\n }\n\n if (!globalOpts.quiet) {\n let output = formatValidateResult(result, file);\n if (result.vocabResult) {\n output += formatVocabValidationOutput(result.vocabResult);\n }\n const suggestion = formatSuggestion(getSuggestion({\n command: 'query validate',\n queryFile: file,\n validationSuccess: result.success && !hasVocabErrors(result),\n hasSchemaLink: hasSchema,\n }));\n if (suggestion) output += '\\n' + suggestion;\n console.log(output);\n }\n process.exitCode =\n !result.success || hasVocabErrors(result)\n ? EXIT_CODES.QUERY_ERROR\n : EXIT_CODES.SUCCESS;\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error(\n 'Error:',\n error instanceof Error ? error.message : error\n );\n }\n process.exitCode = EXIT_CODES.QUERY_ERROR;\n }\n });\n\n queryCommand\n .command('translate')\n .description('Show translated queries for each database')\n .argument('<file>', 'path to query YAML file')\n .option('--db <provider>', 'show translation for specific provider only')\n .addHelpText('after', `\nExamples:\n $ search-hub query translate ./diabetes-ai.yaml # All databases\n $ search-hub query translate ./diabetes-ai.yaml --db pubmed # PubMed only`)\n .action(async (file: string, options: { db?: string }) => {\n const globalOpts = program.opts() as GlobalOptions;\n try {\n const translateOptions = options.db\n ? { providers: [options.db as ProviderName] }\n : {};\n const result = await translateQueryCommand(file, translateOptions);\n if (!globalOpts.quiet) {\n console.log(formatTranslateResult(result, file));\n }\n process.exitCode = result.success\n ? EXIT_CODES.SUCCESS\n : EXIT_CODES.QUERY_ERROR;\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error(\n 'Error:',\n error instanceof Error ? error.message : error\n );\n }\n process.exitCode = EXIT_CODES.QUERY_ERROR;\n }\n });\n\n queryCommand\n .command('inspect')\n .description('Show how a query resolves per provider (block replacements and added filters)')\n .argument('<file>', 'path to query YAML file')\n .option('--db <provider>', 'show resolution for specific provider only')\n .addHelpText('after', `\nExamples:\n $ search-hub query inspect ./diabetes-ai.yaml # All databases\n $ search-hub query inspect ./diabetes-ai.yaml --db pubmed # PubMed only`)\n .action(async (file: string, options: { db?: string }) => {\n const globalOpts = program.opts() as GlobalOptions;\n try {\n const inspectOptions = options.db\n ? { providers: [options.db as ProviderName] }\n : {};\n const result = await inspectQueryCommand(file, inspectOptions);\n if (!result.success) {\n if (!globalOpts.quiet) {\n console.error(`\\u2717 Failed to inspect: ${file}\\n Error: ${result.error}`);\n }\n process.exitCode = EXIT_CODES.QUERY_ERROR;\n return;\n }\n if (!globalOpts.quiet) {\n console.log(formatInspectOutput(result.result!));\n }\n process.exitCode = EXIT_CODES.SUCCESS;\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error(\n 'Error:',\n error instanceof Error ? error.message : error\n );\n }\n process.exitCode = EXIT_CODES.QUERY_ERROR;\n }\n });\n\n queryCommand\n .command('init')\n .description('Generate a template query YAML file')\n .option('-o, --output <path>', 'write to file (default: stdout)')\n .option('--force', 'overwrite existing file', false)\n .action(async (options: { output?: string; force?: boolean }) => {\n const globalOpts = program.opts() as GlobalOptions;\n try {\n if (options.output) {\n const result = await writeQueryTemplate(options);\n if (!globalOpts.quiet) {\n if (result.success) {\n console.log(result.message);\n } else {\n console.error(result.message);\n }\n }\n process.exitCode = result.success ? EXIT_CODES.SUCCESS : EXIT_CODES.GENERAL_ERROR;\n } else {\n const template = generateQueryTemplate();\n console.log(template);\n process.exitCode = EXIT_CODES.SUCCESS;\n }\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error('Error:', error instanceof Error ? error.message : error);\n }\n process.exitCode = EXIT_CODES.GENERAL_ERROR;\n }\n });\n\n // Register status command\n program\n .command('status')\n .description('Show session status and statistics')\n .argument('[session-id]', 'session ID to show details for')\n .option('--json', 'output as JSON')\n .option('--all', 'include completed sessions')\n .addHelpText('after', `\nExamples:\n $ search-hub status # List recent sessions\n $ search-hub status 20240115_diabetes-ai_a3f2 # Show session details\n $ search-hub status --json # JSON output for scripting`)\n .action(async (sessionId?: string, options?: { json?: boolean; all?: boolean }) => {\n const globalOpts = program.opts() as GlobalOptions;\n try {\n const sessionsDir = await getSessionsDir(globalOpts);\n const formatOpts = { json: options?.json ?? false };\n\n if (sessionId) {\n // Show specific session details\n const result = await getSessionDetails(sessionId, sessionsDir);\n if (result.success && result.session) {\n // Compute deduplication stats\n try {\n const rawSession = await loadSession(sessionId, sessionsDir);\n const dedupStats = await computeDeduplicationStats(sessionId, sessionsDir, rawSession);\n result.session.uniqueArticles = dedupStats.uniqueArticles;\n result.session.duplicatesRemoved = dedupStats.duplicatesRemoved;\n } catch {\n // Dedup stats are optional - don't fail the command\n }\n if (!globalOpts.quiet) {\n console.log(formatSessionDetails(result.session, formatOpts));\n }\n } else {\n if (!globalOpts.quiet) {\n console.error(`Error: ${result.error}`);\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n return;\n }\n } else {\n // List sessions\n const listOpts = { all: options?.all ?? false };\n const sessions = await listSessionsForDisplay(sessionsDir, listOpts);\n if (!globalOpts.quiet) {\n console.log(formatSessionList(sessions, formatOpts));\n }\n }\n process.exitCode = EXIT_CODES.SUCCESS;\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error(\n 'Error:',\n error instanceof Error ? error.message : error\n );\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n }\n });\n\n // Register search command\n program\n .command('search')\n .description('Execute search across databases')\n .argument('[query-file]', 'path to query YAML file')\n .option('--db <providers>', 'target specific database(s), comma-separated')\n .option('--query <string>', 'direct query in database-native syntax (advanced; requires --db; prefer YAML files)')\n .option('--name <string>', 'session name')\n .option('--max-results <n>', 'limit results per database')\n .option('--dry-run', 'show translated queries without executing')\n .option('--count-only', 'get hit counts without downloading results')\n .option('--preview', 'get hit counts and first 5 titles without creating session')\n .option('--skip-connection-test', 'skip API connection test during dry-run')\n .option('--no-resume', 'start fresh even if session exists')\n .option('--strict', 'require all targeted databases to succeed (exit non-zero on partial failure)')\n .addHelpText('after', `\nWorkflow position:\n query validate → [this command: search] → results / summary / diff\n\nExamples:\n $ search-hub search ./diabetes-ai.yaml # Search all databases\n $ search-hub search ./query.yaml --db pubmed,eric # Specific databases\n $ search-hub search --db pubmed --query \"diabetes[tiab]\" # Direct query\n $ search-hub search ./query.yaml --dry-run # Preview translations\n $ search-hub search ./query.yaml --count-only # Get hit counts only\n $ search-hub search ./query.yaml --max-results 100 # Limit results\n\nQuery features (use \"query init\" to see full template):\n filters: year_from, year_to, language, publication_types\n exclude: NOT terms per block (terms.exclude)\n mesh/eric: controlled vocabulary (terms.mesh, terms.eric)\n providers: per-database block replacements and filter additions`)\n .action(\n async (\n queryFile?: string,\n options?: {\n db?: string;\n query?: string;\n name?: string;\n maxResults?: string;\n dryRun?: boolean;\n countOnly?: boolean;\n preview?: boolean;\n skipConnectionTest?: boolean;\n resume?: boolean;\n strict?: boolean;\n }\n ) => {\n const globalOpts = program.opts() as GlobalOptions;\n try {\n // Parse and validate options\n const searchOpts = parseSearchOptions(queryFile, {\n db: options?.db,\n query: options?.query,\n name: options?.name,\n maxResults: options?.maxResults,\n dryRun: options?.dryRun,\n countOnly: options?.countOnly,\n preview: options?.preview,\n noResume: options?.resume === false,\n strict: options?.strict,\n });\n\n const validation = validateSearchInput(searchOpts);\n if (!validation.valid) {\n if (!globalOpts.quiet) {\n console.error(`Error: ${validation.error}`);\n }\n process.exitCode = EXIT_CODES.GENERAL_ERROR;\n return;\n }\n\n // Check for short keywords and display warning\n if (searchOpts.queryFile && !globalOpts.quiet) {\n try {\n const ast = await parseQueryFile(searchOpts.queryFile);\n const shortKeywords = detectShortKeywords(ast);\n if (shortKeywords.length > 0) {\n console.error(formatShortKeywordWarning(shortKeywords));\n console.error('');\n }\n } catch {\n // Ignore parse errors here - they'll be caught later during execution\n }\n }\n\n // Handle dry-run mode\n if (searchOpts.dryRun) {\n // Try to load config for provider readiness display\n let dryRunConfig: Config | undefined;\n try {\n dryRunConfig = await loadConfig(globalOpts.config ? { explicitConfigPath: globalOpts.config } : {});\n } catch {\n // Config unavailable, readiness section will be omitted\n }\n\n if (searchOpts.queryFile) {\n // Translate from file\n const translateOpts = searchOpts.providers\n ? { providers: searchOpts.providers }\n : {};\n const result = await translateQueryCommand(\n searchOpts.queryFile,\n translateOpts\n );\n if (result.success && result.translations) {\n const translations = Object.entries(result.translations).map(\n ([provider, t]) => ({ provider, query: t.native })\n );\n const providers = translations.map(t => t.provider) as ProviderName[];\n if (!globalOpts.quiet) {\n const dryRunOpts = dryRunConfig\n ? { config: dryRunConfig, providers, skipConnectionTest: options?.skipConnectionTest }\n : {};\n console.log(await formatDryRunOutput(translations, dryRunOpts));\n }\n } else {\n if (!globalOpts.quiet) {\n console.error(`Error: ${result.error}`);\n }\n process.exitCode = EXIT_CODES.QUERY_ERROR;\n return;\n }\n } else if (searchOpts.directQuery && searchOpts.providers) {\n // Direct query\n const translations = [\n {\n provider: searchOpts.providers[0]!,\n query: searchOpts.directQuery,\n },\n ];\n if (!globalOpts.quiet) {\n const dryRunOpts = dryRunConfig\n ? { config: dryRunConfig, providers: searchOpts.providers as ProviderName[], skipConnectionTest: options?.skipConnectionTest }\n : {};\n console.log(await formatDryRunOutput(translations, dryRunOpts));\n }\n }\n process.exitCode = EXIT_CODES.SUCCESS;\n return;\n }\n\n // Handle preview mode\n if (searchOpts.preview) {\n let previewConfig;\n try {\n previewConfig = await loadConfig(globalOpts.config ? { explicitConfigPath: globalOpts.config } : {});\n } catch {\n previewConfig = getDefaultConfig();\n }\n\n const previews = await executePreview(searchOpts, previewConfig);\n\n if (previews.length === 0) {\n if (!globalOpts.quiet) {\n console.error('Error: No providers enabled or selected');\n }\n process.exitCode = EXIT_CODES.GENERAL_ERROR;\n return;\n }\n\n if (!globalOpts.quiet) {\n console.log(formatPreviewOutput(previews, searchOpts.queryFile));\n }\n\n const previewHasErrors = previews.some((p) => p.error);\n const previewAllFailed = previews.every((p) => p.error);\n if (previewAllFailed) {\n process.exitCode = EXIT_CODES.NETWORK_ERROR;\n } else if (previewHasErrors && searchOpts.strict) {\n process.exitCode = EXIT_CODES.NETWORK_ERROR;\n } else {\n process.exitCode = EXIT_CODES.SUCCESS;\n }\n if (previewHasErrors && !previewAllFailed && !globalOpts.quiet) {\n const failed = previews.filter((p) => p.error).map((p) => `${p.provider}: ${p.error}`);\n console.warn(`\\nWarning: Some providers failed:\\n ${failed.join('\\n ')}`);\n }\n return;\n }\n\n // Handle count-only mode\n if (searchOpts.countOnly) {\n let countConfig;\n try {\n countConfig = await loadConfig(globalOpts.config ? { explicitConfigPath: globalOpts.config } : {});\n } catch {\n countConfig = getDefaultConfig();\n }\n\n const counts = await executeCountOnly(searchOpts, countConfig);\n\n if (counts.length === 0) {\n if (!globalOpts.quiet) {\n console.error('Error: No providers enabled or selected');\n }\n process.exitCode = EXIT_CODES.GENERAL_ERROR;\n return;\n }\n\n if (!globalOpts.quiet) {\n console.log(formatCountOnlyOutput(counts, searchOpts.queryFile));\n const suggestion = formatSuggestion(getSuggestion({\n command: 'search --count-only',\n queryFile: searchOpts.queryFile,\n }));\n if (suggestion) console.log(suggestion);\n }\n\n const countHasErrors = counts.some((c) => c.error);\n const countAllFailed = counts.every((c) => c.error);\n if (countAllFailed) {\n process.exitCode = EXIT_CODES.NETWORK_ERROR;\n } else if (countHasErrors && searchOpts.strict) {\n process.exitCode = EXIT_CODES.NETWORK_ERROR;\n } else {\n process.exitCode = EXIT_CODES.SUCCESS;\n }\n if (countHasErrors && !countAllFailed && !globalOpts.quiet) {\n const failed = counts.filter((c) => c.error).map((c) => `${c.provider}: ${c.error}`);\n console.warn(`\\nWarning: Some providers failed:\\n ${failed.join('\\n ')}`);\n }\n return;\n }\n\n // Non-dry-run: actual search execution\n const sessionsDir = await getSessionsDir(globalOpts);\n let config;\n try {\n config = await loadConfig(\n globalOpts.config ? { explicitConfigPath: globalOpts.config } : {}\n );\n } catch {\n config = getDefaultConfig();\n }\n\n const showProgress = !globalOpts.quiet && process.stdout.isTTY;\n const result = await executeSearch(\n searchOpts,\n sessionsDir,\n config,\n showProgress\n );\n\n if (result.success) {\n if (!globalOpts.quiet) {\n console.log(`\\nSearch completed. Session: ${result.sessionId}`);\n if (result.results) {\n for (const [provider, stats] of Object.entries(result.results)) {\n if (stats.error) {\n console.log(` ${provider}: FAILED - ${stats.error}`);\n } else {\n console.log(` ${provider}: ${stats.retrieved} results`);\n }\n }\n }\n // Show warning for partial success\n if (result.sessionStatus === 'partial' && result.results) {\n const failed = Object.entries(result.results)\n .filter(([, s]) => s.error)\n .map(([p, s]) => `${p}: ${s.error}`);\n if (failed.length > 0) {\n console.warn(`\\nWarning: Some providers failed:\\n ${failed.join('\\n ')}`);\n }\n }\n // Show next step suggestions\n if (result.sessionId) {\n const sessions = await listSessions(sessionsDir);\n const suggestionCmd = searchOpts.directQuery ? 'search --query' : 'search';\n const suggestion = formatSuggestion(getSuggestion({\n command: suggestionCmd,\n sessionId: result.sessionId,\n sessionStatus: result.sessionStatus,\n sessionCount: sessions.length,\n }));\n if (suggestion) console.log(suggestion);\n }\n }\n process.exitCode = EXIT_CODES.SUCCESS;\n } else {\n if (!globalOpts.quiet) {\n console.error(`Error: ${result.error}`);\n if (globalOpts.verbose && result.results) {\n console.error(formatVerboseProviderDetails(result.results));\n }\n }\n process.exitCode = EXIT_CODES.NETWORK_ERROR;\n }\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error(\n 'Error:',\n error instanceof Error ? error.message : error\n );\n }\n process.exitCode = EXIT_CODES.GENERAL_ERROR;\n }\n }\n );\n\n // Register resume command\n program\n .command('resume')\n .description('Resume an interrupted search session')\n .argument('<session-id>', 'session ID to resume')\n .option('--db <providers>', 'resume only specific database(s)')\n .option('--retry-failed', 'retry failed databases')\n .addHelpText('after', `\nExamples:\n $ search-hub resume 20240115_diabetes-ai_a3f2 # Resume session\n $ search-hub resume SESSION_ID --retry-failed # Retry failed databases\n $ search-hub resume SESSION_ID --db scopus # Resume specific database`)\n .action(\n async (\n sessionId: string,\n options?: { db?: string; retryFailed?: boolean }\n ) => {\n const globalOpts = program.opts() as GlobalOptions;\n try {\n // Parse and validate options\n const resumeOpts = parseResumeOptions(sessionId, {\n db: options?.db,\n retryFailed: options?.retryFailed,\n });\n\n const validation = validateResumeInput(resumeOpts);\n if (!validation.valid) {\n if (!globalOpts.quiet) {\n console.error(`Error: ${validation.error}`);\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n return;\n }\n\n const sessionsDir = await getSessionsDir(globalOpts);\n\n // Get resumable providers\n const result = await getResumableProvidersForCommand(\n sessionId,\n sessionsDir,\n {\n providers: resumeOpts.providers,\n retryFailed: resumeOpts.retryFailed,\n }\n );\n\n if (!result.success) {\n if (!globalOpts.quiet) {\n console.error(`Error: ${result.error}`);\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n return;\n }\n\n if (!result.providers || result.providers.length === 0) {\n if (!globalOpts.quiet) {\n console.log('No providers need resuming for this session.');\n }\n process.exitCode = EXIT_CODES.SUCCESS;\n return;\n }\n\n // Show resumable providers\n if (!globalOpts.quiet) {\n console.log(`Resuming session ${sessionId} with ${result.providers.length} provider(s):`);\n for (const p of result.providers) {\n const details = p.cursor\n ? `cursor: ${p.cursor}`\n : p.pageNumber\n ? `page: ${p.pageNumber}`\n : '';\n console.log(` - ${p.provider}: ${p.strategy}${details ? ` (${details})` : ''}`);\n }\n console.log('');\n }\n\n // Execute resume\n let config;\n try {\n config = await loadConfig(\n globalOpts.config ? { explicitConfigPath: globalOpts.config } : {}\n );\n } catch {\n config = getDefaultConfig();\n }\n\n const showProgress = !globalOpts.quiet && process.stdout.isTTY;\n const execResult = await executeResume(\n resumeOpts,\n sessionsDir,\n config,\n showProgress\n );\n\n if (execResult.success) {\n if (!globalOpts.quiet) {\n console.log(`\\nResume completed. ${execResult.resumed} provider(s) resumed.`);\n if (execResult.results) {\n for (const [provider, stats] of Object.entries(execResult.results)) {\n console.log(` ${provider}: ${stats.retrieved} results`);\n }\n }\n }\n process.exitCode = EXIT_CODES.SUCCESS;\n } else {\n if (!globalOpts.quiet) {\n console.error(`Error: ${execResult.error}`);\n if (globalOpts.verbose && execResult.results) {\n console.error(formatVerboseProviderDetails(execResult.results));\n }\n }\n process.exitCode = EXIT_CODES.NETWORK_ERROR;\n }\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error(\n 'Error:',\n error instanceof Error ? error.message : error\n );\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n }\n }\n );\n\n // Register export command\n program\n .command('export')\n .description('Export session results to various formats')\n .argument('<session-id>', 'session ID to export')\n .option('--format <fmt>', 'output format: ids, json, jsonl, csl-json', 'jsonl')\n .option('-o, --output <path>', 'output file path (default: stdout)')\n .option('--id-type <type>', 'for ids format: doi, pmid, all')\n .option('--no-dedup', 'disable deduplication of results')\n .option('-q, --query <expr>', 'filter results with query expression')\n .addHelpText('after', `\nExamples:\n $ search-hub export SESSION_ID # JSONL to stdout\n $ search-hub export SESSION_ID --format json # JSON to stdout\n $ search-hub export SESSION_ID -q \"year:2023\" # Filter by query\n $ search-hub export SESSION_ID -q \"author:smith\" --format ids # Filtered IDs\n $ search-hub export SESSION_ID --format json -o results.json # JSON to file\n $ search-hub export SESSION_ID --format ids --id-type doi # Export DOIs to stdout\n $ search-hub export SESSION_ID --no-dedup # Export without deduplication\n $ search-hub export SESSION_ID --format jsonl | jq '.title' # Pipe to jq\n\nQuery syntax:\n Free text diabetes Search title and abstract\n title:VALUE title:learning Title substring\n abstract:VALUE abstract:randomized Abstract substring\n author:VALUE author:tanaka Author name substring\n journal:VALUE journal:lancet Journal name substring\n year:VALUE year:2023 Exact year\n year:FROM-TO year:2020-2024 Year range\n doi:VALUE doi:10.1001/xxx DOI exact match\n pmid:VALUE pmid:12345678 PMID exact match\n source:VALUE source:pubmed Provider exact match\n\n Multiple terms: different fields = AND, same field = OR`)\n .action(\n async (\n sessionId: string,\n options?: {\n format?: string;\n output?: string;\n idType?: string;\n dedup?: boolean;\n query?: string;\n }\n ) => {\n const globalOpts = program.opts() as GlobalOptions;\n try {\n // Parse and validate options\n const exportOpts = parseExportOptions(sessionId, {\n format: options?.format,\n output: options?.output,\n idType: options?.idType,\n });\n\n const validation = validateExportInput(exportOpts);\n if (!validation.valid) {\n if (!globalOpts.quiet) {\n console.error(`Error: ${validation.error}`);\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n return;\n }\n\n const sessionsDir = await getSessionsDir(globalOpts);\n\n // Load session\n let session;\n try {\n session = await loadSession(sessionId, sessionsDir);\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error(\n `Error: ${error instanceof Error ? error.message : 'Failed to load session'}`\n );\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n return;\n }\n\n // Collect articles from result files\n const articles = await loadSessionArticles(session, sessionId, sessionsDir);\n\n // Deduplicate articles unless --no-dedup is set\n const shouldDedup = options?.dedup !== false;\n let exportArticles: typeof articles;\n let duplicatesRemoved = 0;\n\n if (shouldDedup) {\n const dedupResult = deduplicateArticles(articles);\n exportArticles = dedupResult.articles;\n duplicatesRemoved = dedupResult.duplicatesRemoved;\n } else {\n exportArticles = articles;\n }\n\n // Apply -q filter\n const preFilterCount = exportArticles.length;\n let hasFilter = false;\n if (options?.query) {\n exportArticles = filterByQuery(exportArticles, options.query);\n hasFilter = true;\n }\n\n // Format output\n let output: string;\n if (exportOpts.format === 'ids') {\n output = formatIds(exportArticles, exportOpts.idType ?? 'all');\n } else if (exportOpts.format === 'json') {\n // Build per-database article counts\n const databases: Record<string, number> = {};\n for (const article of exportArticles) {\n databases[article.source] = (databases[article.source] ?? 0) + 1;\n }\n const metadata: JsonExportMetadata = {\n sessionId: session.id,\n sessionName: session.name,\n createdAt: session.createdAt,\n databases,\n };\n output = formatJson(exportArticles, metadata);\n } else if (exportOpts.format === 'csl-json') {\n output = formatCslJson(exportArticles);\n } else {\n output = formatJsonl(exportArticles);\n }\n\n // Write to file or stdout\n if (exportOpts.outputPath) {\n await writeFile(exportOpts.outputPath, output, 'utf-8');\n if (!globalOpts.quiet) {\n let message: string;\n if (hasFilter) {\n message = `Exported ${exportArticles.length} articles (filtered from ${preFilterCount}) to ${exportOpts.outputPath}`;\n } else {\n message = `Exported ${exportArticles.length} articles to ${exportOpts.outputPath}`;\n }\n if (duplicatesRemoved > 0) {\n message += ` (${duplicatesRemoved} duplicate${duplicatesRemoved === 1 ? '' : 's'} removed)`;\n }\n console.error(message);\n }\n } else {\n process.stdout.write(output + '\\n');\n if (!globalOpts.quiet) {\n const parts: string[] = [];\n if (hasFilter) {\n parts.push(`filtered from ${preFilterCount} to ${exportArticles.length} articles`);\n }\n if (duplicatesRemoved > 0) {\n parts.push(`${duplicatesRemoved} duplicate${duplicatesRemoved === 1 ? '' : 's'} removed`);\n }\n if (parts.length > 0) {\n console.error(`(${parts.join(', ')})`);\n }\n }\n }\n\n process.exitCode = EXIT_CODES.SUCCESS;\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error(\n 'Error:',\n error instanceof Error ? error.message : error\n );\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n }\n }\n );\n\n // Register summary command\n program\n .command('summary')\n .description('Show statistical summary of session results')\n .argument('<session-id>', 'session ID to summarize')\n .option('--json', 'output as JSON')\n .addHelpText('after', `\\nExamples:\n $ search-hub summary 20240115_diabetes-ai_a3f2 # Human-readable summary\n $ search-hub summary 20240115_diabetes-ai_a3f2 --json # JSON output`)\n .action(\n async (\n sessionId: string,\n options?: { json?: boolean }\n ) => {\n const globalOpts = program.opts() as GlobalOptions;\n try {\n const sessionsDir = await getSessionsDir(globalOpts);\n\n // Load session\n let session;\n try {\n session = await loadSession(sessionId, sessionsDir);\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error(\n `Error: ${error instanceof Error ? error.message : 'Failed to load session'}`\n );\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n return;\n }\n\n // Collect articles from result files\n const allArticles = await loadSessionArticles(session, sessionId, sessionsDir);\n\n // Deduplicate\n const dedupResult = deduplicateArticles(allArticles);\n\n // Compute summary\n const summary = computeSummary(allArticles, dedupResult.articles, {\n sessionId,\n sessionName: session.name,\n });\n\n // Format output\n if (options?.json) {\n console.log(formatSummaryJson(summary));\n } else {\n if (!globalOpts.quiet) {\n console.log(formatSummary(summary));\n }\n }\n\n process.exitCode = EXIT_CODES.SUCCESS;\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error(\n 'Error:',\n error instanceof Error ? error.message : error\n );\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n }\n }\n );\n\n // Register results command\n program\n .command('results')\n .description('List articles from a session with title, year, and journal')\n .argument('<session-id>', 'session ID to list results from')\n .option('--limit <n>', 'maximum number of results to show')\n .option('--offset <n>', 'skip first n results')\n .option('--json', 'output as JSON array')\n .option('--fields <fields>', 'fields to display (comma-separated)')\n .option('-q, --query <expr>', 'filter results with query expression')\n .option('--abstract', 'show abstracts with results')\n .option('--abstract-length <n>', 'maximum abstract length in characters (default: 300)')\n .addHelpText('after', `\nExamples:\n $ search-hub results SESSION_ID # List all articles\n $ search-hub results SESSION_ID --limit 20 # First 20 articles\n $ search-hub results SESSION_ID -q \"diabetes\" # Free text filter\n $ search-hub results SESSION_ID -q \"author:smith year:2023\" # Combined filter\n $ search-hub results SESSION_ID -q \"doi:10.1001/xxx\" # Exact ID match\n $ search-hub results SESSION_ID --json # JSON output\n $ search-hub results SESSION_ID -q \"source:pubmed\" # Only PubMed\n $ search-hub results SESSION_ID --abstract # Show abstracts\n\nQuery syntax:\n Free text diabetes Search title and abstract\n title:VALUE title:learning Title substring\n abstract:VALUE abstract:randomized Abstract substring\n author:VALUE author:tanaka Author name substring\n journal:VALUE journal:lancet Journal name substring\n year:VALUE year:2023 Exact year\n year:FROM-TO year:2020-2024 Year range\n doi:VALUE doi:10.1001/xxx DOI exact match\n pmid:VALUE pmid:12345678 PMID exact match\n source:VALUE source:pubmed Provider exact match\n\n Multiple terms: different fields = AND, same field = OR`)\n .action(\n async (\n sessionId: string,\n options?: {\n limit?: string;\n offset?: string;\n json?: boolean;\n fields?: string;\n query?: string;\n abstract?: boolean;\n abstractLength?: string;\n }\n ) => {\n const globalOpts = program.opts() as GlobalOptions;\n try {\n // Parse and validate options\n const resultsOpts = parseResultsOptions(sessionId, {\n limit: options?.limit,\n offset: options?.offset,\n json: options?.json,\n fields: options?.fields,\n query: options?.query,\n abstract: options?.abstract,\n abstractLength: options?.abstractLength,\n });\n\n const validation = validateResultsInput(resultsOpts);\n if (!validation.valid) {\n if (!globalOpts.quiet) {\n console.error(`Error: ${validation.error}`);\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n return;\n }\n\n const sessionsDir = await getSessionsDir(globalOpts);\n\n // Load session\n let session;\n try {\n session = await loadSession(sessionId, sessionsDir);\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error(\n `Error: ${error instanceof Error ? error.message : 'Failed to load session'}`\n );\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n return;\n }\n\n // Collect articles from result files\n const articles = await loadSessionArticles(session, sessionId, sessionsDir);\n\n // Deduplicate articles\n const dedupResult = deduplicateArticles(articles);\n let displayArticles = dedupResult.articles;\n\n // Apply -q filter\n let filteredFrom: number | undefined;\n if (resultsOpts.query) {\n const preFilterCount = displayArticles.length;\n displayArticles = filterByQuery(displayArticles, resultsOpts.query);\n if (displayArticles.length !== preFilterCount) {\n filteredFrom = preFilterCount;\n }\n }\n\n // Apply pagination\n const total = displayArticles.length;\n const offset = resultsOpts.offset ?? 0;\n if (offset > 0) {\n displayArticles = displayArticles.slice(offset);\n }\n if (resultsOpts.limit !== undefined && resultsOpts.limit > 0) {\n displayArticles = displayArticles.slice(0, resultsOpts.limit);\n }\n\n // Format output\n if (resultsOpts.json) {\n console.log(formatResultsJson(displayArticles));\n } else {\n if (!globalOpts.quiet) {\n console.log(formatResultsList(displayArticles, {\n sessionId,\n sessionName: session.name,\n total,\n offset,\n filteredFrom,\n showAbstract: resultsOpts.showAbstract,\n abstractLength: resultsOpts.abstractLength,\n }));\n }\n }\n\n process.exitCode = EXIT_CODES.SUCCESS;\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error(\n 'Error:',\n error instanceof Error ? error.message : error\n );\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n }\n }\n );\n\n // Register diff command\n program\n .command('diff')\n .description('Compare results between two sessions')\n .argument('<session-id-1>', 'first session ID')\n .argument('<session-id-2>', 'second session ID')\n .option('--show <section>', 'show only specific section: added, removed, or common')\n .option('--json', 'output as JSON')\n .option('--no-query-diff', 'hide query changes section')\n .addHelpText('after', `\nExamples:\n $ search-hub diff session-v1 session-v2 # Compare two sessions\n $ search-hub diff session-v1 session-v2 --show added # Show only added articles\n $ search-hub diff session-v1 session-v2 --show removed # Show only removed articles\n $ search-hub diff session-v1 session-v2 --json # JSON output for scripting\n $ search-hub diff session-v1 session-v2 --no-query-diff # Hide query changes\n\nQuery Refinement Workflow:\n 1. Search with broad query: search-hub search v1.yaml --max-results 100\n 2. Create refined query: cp v1.yaml v2.yaml && edit v2.yaml\n 3. Search with refined query: search-hub search v2.yaml --max-results 100\n 4. Compare results: search-hub diff <session-v1> <session-v2> --show removed\n 5. Review excluded articles to verify refinement quality`)\n .action(\n async (\n sessionId1: string,\n sessionId2: string,\n options?: { show?: string; json?: boolean; queryDiff?: boolean }\n ) => {\n const globalOpts = program.opts() as GlobalOptions;\n try {\n // Validate --show option\n const validShowValues: ShowFilter[] = ['added', 'removed', 'common'];\n let showFilter: ShowFilter | undefined;\n if (options?.show) {\n if (!validShowValues.includes(options.show as ShowFilter)) {\n if (!globalOpts.quiet) {\n console.error(`Error: Invalid --show value: ${options.show}. Valid values are: ${validShowValues.join(', ')}`);\n }\n process.exitCode = EXIT_CODES.GENERAL_ERROR;\n return;\n }\n showFilter = options.show as ShowFilter;\n }\n\n const sessionsDir = await getSessionsDir(globalOpts);\n const noQueryDiff = options?.queryDiff === false;\n\n // Load both sessions\n let session1, session2;\n try {\n session1 = await loadSession(sessionId1, sessionsDir);\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error(\n `Error loading session 1: ${error instanceof Error ? error.message : 'Failed to load session'}`\n );\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n return;\n }\n\n try {\n session2 = await loadSession(sessionId2, sessionsDir);\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error(\n `Error loading session 2: ${error instanceof Error ? error.message : 'Failed to load session'}`\n );\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n return;\n }\n\n // Collect articles from both sessions\n const articles1 = await loadSessionArticles(session1, sessionId1, sessionsDir);\n const articles2 = await loadSessionArticles(session2, sessionId2, sessionsDir);\n\n // Deduplicate each session's articles before diffing\n const dedup1 = deduplicateArticles(articles1);\n const dedup2 = deduplicateArticles(articles2);\n\n // Compute article diff\n const diff = computeDiff(dedup1.articles, dedup2.articles);\n\n // Load and compute query diff (unless disabled)\n let queryDiff;\n let showQueryDiffPlaceholder = false;\n if (!noQueryDiff) {\n const query1 = await loadSessionQuery(sessionId1, sessionsDir);\n const query2 = await loadSessionQuery(sessionId2, sessionsDir);\n if (query1 && query2) {\n queryDiff = computeQueryDiff(query1, query2);\n } else {\n // At least one query is missing - show placeholder\n showQueryDiffPlaceholder = true;\n }\n }\n\n // Format and output\n const formatOptions = { queryDiff, noQueryDiff, showQueryDiffPlaceholder };\n if (options?.json) {\n console.log(formatDiffJson(diff, sessionId1, sessionId2, showFilter, formatOptions));\n } else {\n if (!globalOpts.quiet) {\n console.log(formatDiff(diff, sessionId1, sessionId2, showFilter, formatOptions));\n\n // Show suggestions\n const suggestion = formatSuggestion(getSuggestion({\n command: 'diff',\n sessionId: sessionId2,\n diffSession1Id: sessionId1,\n diffAddedCount: diff.added.length,\n diffRemovedCount: diff.removed.length,\n }));\n if (suggestion) {\n console.log(suggestion);\n }\n }\n }\n\n process.exitCode = EXIT_CODES.SUCCESS;\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error(\n 'Error:',\n error instanceof Error ? error.message : error\n );\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n }\n }\n );\n\n // Register check command\n program\n .command('check')\n .description('Verify coverage of known articles against session results')\n .argument('<session-id>', 'session ID to check against')\n .option('--file <path>', 'file with identifiers (one per line)')\n .option('--doi <ids>', 'comma-separated DOIs to check')\n .option('--pmid <ids>', 'comma-separated PMIDs to check')\n .option('--json', 'output as JSON')\n .option('--missing-only', 'show only missing identifiers')\n .addHelpText('after', `\nExamples:\n $ search-hub check SESSION --file known-dois.txt # Check from file\n $ search-hub check SESSION --doi \"10.1001/jama.2023.12345\" # Check single DOI\n $ search-hub check SESSION --pmid \"37654321,36543210\" # Check PMIDs\n $ search-hub check SESSION --file refs.txt --json # JSON output\n $ search-hub check SESSION --file refs.txt --missing-only # Only missing\n\nInput file format (one identifier per line):\n 10.1001/jama.2023.12345 DOI (starts with \"10.\")\n 37654321 PMID (numeric only)\n DOI:10.1038/s41586-023-xxxxx DOI (explicit prefix)\n PMID:36543210 PMID (explicit prefix)\n arxiv:2301.12345 arXiv ID (explicit prefix)\n # comment Comments and empty lines ignored`)\n .action(\n async (\n sessionId: string,\n options?: { file?: string; doi?: string; pmid?: string; json?: boolean; missingOnly?: boolean }\n ) => {\n const globalOpts = program.opts() as GlobalOptions;\n try {\n // Parse identifiers from input sources\n let identifiers;\n let source: string;\n\n if (options?.file) {\n const filePath = expandPath(options.file);\n let content: string;\n try {\n content = await readFile(filePath, 'utf-8');\n } catch {\n if (!globalOpts.quiet) {\n console.error(`Error: File not found: ${filePath}`);\n }\n process.exitCode = EXIT_CODES.GENERAL_ERROR;\n return;\n }\n try {\n identifiers = parseIdentifierFile(content);\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error(`Error: ${error instanceof Error ? error.message : 'Failed to parse identifier file'}`);\n }\n process.exitCode = EXIT_CODES.GENERAL_ERROR;\n return;\n }\n source = options.file;\n } else if (options?.doi || options?.pmid) {\n const lines: string[] = [];\n if (options.doi) {\n lines.push(...options.doi.split(',').map(d => d.trim()).filter(Boolean));\n }\n if (options.pmid) {\n lines.push(...options.pmid.split(',').map(p => `PMID:${p.trim()}`).filter(Boolean));\n }\n identifiers = parseIdentifierFile(lines.join('\\n'));\n source = 'command line';\n } else {\n if (!globalOpts.quiet) {\n console.error('Error: Provide --file, --doi, or --pmid');\n }\n process.exitCode = EXIT_CODES.GENERAL_ERROR;\n return;\n }\n\n if (identifiers.length === 0) {\n if (!globalOpts.quiet) {\n console.error('Error: No identifiers found in input');\n }\n process.exitCode = EXIT_CODES.GENERAL_ERROR;\n return;\n }\n\n // Load session\n const sessionsDir = await getSessionsDir(globalOpts);\n let session;\n try {\n session = await loadSession(sessionId, sessionsDir);\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error(\n `Error: ${error instanceof Error ? error.message : 'Failed to load session'}`\n );\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n return;\n }\n\n // Load articles and check coverage\n const articles = await loadSessionArticles(session, sessionId, sessionsDir);\n const result = checkCoverage(articles, identifiers);\n\n // Format output\n if (options?.json) {\n console.log(formatCheckResultJson(result, { sessionId, source }));\n } else {\n if (!globalOpts.quiet) {\n console.log(formatCheckResult(result, {\n sessionId,\n source,\n missingOnly: options?.missingOnly,\n }));\n }\n }\n\n process.exitCode = EXIT_CODES.SUCCESS;\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error(\n 'Error:',\n error instanceof Error ? error.message : error\n );\n }\n process.exitCode = EXIT_CODES.GENERAL_ERROR;\n }\n }\n );\n\n // Register merge command\n program\n .command('merge')\n .description('Merge results from multiple search sessions')\n .argument('<session-ids...>', 'two or more session IDs to merge')\n .option('--name <string>', 'name for merged session')\n .option('--dry-run', 'show what would be merged without creating session')\n .option('--json', 'output as JSON')\n .addHelpText('after', `\nExamples:\n $ search-hub merge session-v4 session-v9 # Merge two sessions\n $ search-hub merge session-v4 session-v9 --name combined # Merge with custom name\n $ search-hub merge session-a session-b session-c # Merge three sessions\n $ search-hub merge session-v4 session-v9 --dry-run # Preview merge`)\n .action(\n async (\n sessionIds: string[],\n options?: { name?: string; dryRun?: boolean; json?: boolean }\n ) => {\n const globalOpts = program.opts() as GlobalOptions;\n try {\n if (sessionIds.length < 2) {\n if (!globalOpts.quiet) {\n console.error('Error: At least two session IDs are required for merge');\n }\n process.exitCode = EXIT_CODES.GENERAL_ERROR;\n return;\n }\n\n const sessionsDir = await getSessionsDir(globalOpts);\n\n // Load all source sessions\n const sessions = new Map<string, ReturnType<typeof loadSession> extends Promise<infer T> ? T : never>();\n for (const sessionId of sessionIds) {\n try {\n const session = await loadSession(sessionId, sessionsDir);\n sessions.set(sessionId, session);\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error(\n `Error loading session '${sessionId}': ${error instanceof Error ? error.message : 'Failed to load session'}`\n );\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n return;\n }\n }\n\n // Validate sources\n const validation = validateMergeSources(sessions);\n if (!validation.valid) {\n if (!globalOpts.quiet) {\n console.error(`Error: ${validation.error}`);\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n return;\n }\n\n // Load articles from all sessions\n const sessionArticles = new Map<string, Awaited<ReturnType<typeof loadSessionArticles>>>();\n for (const [sessionId, session] of sessions) {\n const articles = await loadSessionArticles(session, sessionId, sessionsDir);\n sessionArticles.set(sessionId, articles);\n }\n\n // Merge articles\n const mergeResult = mergeArticles(sessionArticles);\n\n // Build output data\n const sources = [...sessions.entries()].map(([id, session]) => ({\n id,\n name: session.name,\n count: mergeResult.perSession.get(id) ?? 0,\n }));\n\n const byProviderCounts = new Map<string, number>();\n for (const [provider, articles] of mergeResult.byProvider) {\n byProviderCounts.set(provider, articles.length);\n }\n\n // Auto-generate name if not provided\n const firstSession = sessions.values().next().value;\n const mergeName = options?.name ?? (firstSession ? firstSession.name + '-merged' : 'merged');\n\n if (options?.dryRun) {\n // Dry run - show preview without creating session\n const outputData = {\n sessionId: '(dry-run)',\n totalBefore: mergeResult.totalBefore,\n totalAfter: mergeResult.totalAfter,\n duplicatesRemoved: mergeResult.duplicatesRemoved,\n sources,\n byProvider: byProviderCounts,\n };\n if (options.json) {\n console.log(formatSessionMergeJson(outputData));\n } else if (!globalOpts.quiet) {\n console.log(formatSessionMergeOutput(outputData));\n }\n process.exitCode = EXIT_CODES.SUCCESS;\n return;\n }\n\n // Create merged session\n const sessionSources = [...sessions.entries()].map(([id, session]) => ({\n id,\n name: session.name,\n }));\n\n const mergedSession = await createMergedSession({\n name: mergeName,\n sources: sessionSources,\n byProvider: mergeResult.byProvider,\n totalRetrieved: mergeResult.totalAfter,\n sessionsDir,\n sourceSessionIds: sessionIds,\n });\n\n // Format output\n const outputData = {\n sessionId: mergedSession.id,\n totalBefore: mergeResult.totalBefore,\n totalAfter: mergeResult.totalAfter,\n duplicatesRemoved: mergeResult.duplicatesRemoved,\n sources,\n byProvider: byProviderCounts,\n };\n\n if (options?.json) {\n console.log(formatSessionMergeJson(outputData));\n } else if (!globalOpts.quiet) {\n console.log(formatSessionMergeOutput(outputData));\n\n // Show suggestions\n const suggestion = getSuggestion({\n command: 'merge',\n sessionId: mergedSession.id,\n });\n const suggestionText = formatSuggestion(suggestion);\n if (suggestionText) {\n console.log(suggestionText);\n }\n }\n\n process.exitCode = EXIT_CODES.SUCCESS;\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error(\n 'Error:',\n error instanceof Error ? error.message : error\n );\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n }\n }\n );\n\n // Register register command\n program\n .command('register')\n .description('Register results with reference-manager')\n .argument('<session-id>', 'session ID to register')\n .option('--db <providers>', 'register only specific database(s)')\n .option('--dry-run', 'show what would be registered without executing', false)\n .option('--with-abstracts', 'also update abstracts via ref update', false)\n .option('--reviewed', 'register only articles with finalDecision=include', false)\n .option('--all', 'register all articles (ignore reviews)', false)\n .option('--force', 'skip confirmation prompts', false)\n .option('--no-attach-fulltext', 'skip automatic fulltext attachment')\n .addHelpText('after', `\nExamples:\n $ search-hub register SESSION_ID # Register all results\n $ search-hub register SESSION_ID --with-abstracts\n $ search-hub register SESSION_ID --dry-run # Preview only\n $ search-hub register SESSION_ID --no-attach-fulltext # Skip fulltext attachment\n\nWith review workflow:\n $ search-hub register SESSION_ID --reviewed # Register only included articles\n $ search-hub register SESSION_ID --all # Register all (ignore reviews)`)\n .action(\n async (\n sessionId: string,\n options?: {\n db?: string;\n dryRun?: boolean;\n withAbstracts?: boolean;\n reviewed?: boolean;\n all?: boolean;\n force?: boolean;\n attachFulltext?: boolean;\n }\n ) => {\n const globalOpts = program.opts() as GlobalOptions;\n try {\n // Parse and validate options\n const registerOpts = parseRegisterOptions(sessionId, {\n db: options?.db,\n dryRun: options?.dryRun,\n withAbstracts: options?.withAbstracts,\n reviewed: options?.reviewed,\n all: options?.all,\n force: options?.force,\n });\n\n const validation = validateRegisterInput(registerOpts);\n if (!validation.valid) {\n if (!globalOpts.quiet) {\n console.error(`Error: ${validation.error}`);\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n return;\n }\n\n // Check if ref command is available\n const refAvailable = await checkRefAvailable();\n if (!refAvailable && !registerOpts.dryRun) {\n if (!globalOpts.quiet) {\n console.error('Error: reference-manager (ref) command not found.\\n');\n console.error('reference-manager is required to register search results.');\n console.error('Would you like to install it now? (npm i -g @ncukondo/reference-manager) [Y/n]: ');\n }\n\n // For non-interactive mode, suggest installation\n const npmAvailable = await checkNpmAvailable();\n if (!npmAvailable) {\n if (!globalOpts.quiet) {\n console.error('\\nError: npm command not found.');\n console.error('Please install Node.js first: https://nodejs.org/');\n }\n process.exitCode = EXIT_CODES.GENERAL_ERROR;\n return;\n }\n\n // Try to install\n try {\n if (!globalOpts.quiet) {\n console.log('\\nInstalling reference-manager...');\n }\n await installRefManager();\n if (!globalOpts.quiet) {\n console.log('✓ reference-manager installed successfully.\\n');\n }\n } catch (installError) {\n if (!globalOpts.quiet) {\n console.error(\n `\\nFailed to install reference-manager: ${installError instanceof Error ? installError.message : 'Unknown error'}`\n );\n }\n process.exitCode = EXIT_CODES.GENERAL_ERROR;\n return;\n }\n }\n\n const sessionsDir = await getSessionsDir(globalOpts);\n\n // Load session\n let session;\n try {\n session = await loadSession(sessionId, sessionsDir);\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error(\n `Error: ${error instanceof Error ? error.message : 'Failed to load session'}`\n );\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n return;\n }\n\n // Check for review file and handle --reviewed/--all flags\n const reviewExists = await hasReviewFile(sessionId, sessionsDir);\n let articles: import('../providers/base/types.js').Article[];\n\n if (registerOpts.reviewed) {\n // --reviewed: only include reviewed articles\n if (!reviewExists) {\n if (!globalOpts.quiet) {\n console.error('Error: No reviews.yaml found for this session.');\n console.error('Run \"search-hub review init --session ' + sessionId + '\" first.');\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n return;\n }\n\n const summary = await getReviewSummary(sessionId, sessionsDir);\n if (summary.included === 0) {\n if (!globalOpts.quiet) {\n console.error(formatNoIncludedArticlesError(summary, sessionId));\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n return;\n }\n\n // Warn about pending articles (unless --force or --dry-run)\n if (summary.pending > 0 && !registerOpts.force && !registerOpts.dryRun) {\n if (!globalOpts.quiet) {\n console.log(formatPendingWarning(summary));\n }\n // Wait for user confirmation\n const confirmed = await confirmPrompt();\n if (!confirmed) {\n process.exitCode = EXIT_CODES.SUCCESS;\n return;\n }\n }\n\n articles = await getIncludedArticles(sessionId, sessionsDir);\n } else if (reviewExists && !registerOpts.all) {\n // reviews.yaml exists but no flag specified\n const summary = await getReviewSummary(sessionId, sessionsDir);\n if (!globalOpts.quiet) {\n console.log(formatReviewRequiredMessage(summary, sessionId));\n }\n process.exitCode = EXIT_CODES.GENERAL_ERROR;\n return;\n } else {\n // --all or no reviews.yaml: collect all articles from result files\n if (reviewExists && !globalOpts.quiet) {\n const summary = await getReviewSummary(sessionId, sessionsDir);\n console.log(formatIgnoringReviewsNote(summary.total));\n }\n articles = await loadSessionArticles(session, sessionId, sessionsDir, registerOpts.providers);\n }\n\n // Dry run mode\n if (registerOpts.dryRun) {\n if (!globalOpts.quiet) {\n console.log(formatRegisterDryRunOutput(articles));\n }\n process.exitCode = EXIT_CODES.SUCCESS;\n return;\n }\n\n // Register articles\n if (!globalOpts.quiet) {\n console.log(`Registering ${articles.length} references to reference-manager...`);\n }\n\n const sessionDir = join(sessionsDir, sessionId);\n const registerOptions: import('../integration/register.js').RegisterOptions = {\n sessionId,\n sessionDir,\n withAbstracts: registerOpts.withAbstracts,\n ...(options?.attachFulltext === false ? { noAttachFulltext: true } : {}),\n };\n if (!globalOpts.quiet) {\n registerOptions.onProgress = (current, total) => {\n process.stdout.write(`\\rProgress: ${current}/${total}`);\n };\n }\n const record = await registerArticles(articles, registerOptions);\n\n // Save registration record\n await saveRegistrationRecord(sessionDir, record);\n\n if (!globalOpts.quiet) {\n console.log('\\n');\n console.log(formatRegistrationSummary(record.summary));\n\n // Show fulltext attach summary\n if (record.fulltext) {\n const ft = record.fulltext.summary;\n console.log('\\nFulltext attachment results:');\n if (ft.attached > 0) {\n const totalFiles = record.fulltext.attached.reduce((sum, a) => sum + a.files.length, 0);\n console.log(` ✓ ${ft.attached} articles attached (${totalFiles} files)`);\n }\n if (ft.skipped > 0) {\n console.log(` ⚠ ${ft.skipped} skipped`);\n }\n if (ft.failed > 0) {\n console.log(` ✗ ${ft.failed} failed`);\n }\n }\n\n console.log(`\\nResults saved to: ${join(sessionDir, 'registration.json')}`);\n\n // Show next step suggestions\n const suggestion = formatSuggestion(getSuggestion({\n command: 'register',\n sessionId,\n hasReviews: reviewExists,\n }));\n if (suggestion) console.log(suggestion);\n }\n\n process.exitCode = EXIT_CODES.SUCCESS;\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error(\n 'Error:',\n error instanceof Error ? error.message : error\n );\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n }\n }\n );\n\n // Register review command group\n const reviewCommand = program\n .command('review')\n .description('Article review workflow for systematic literature review')\n .addHelpText('after', `\nExamples:\n $ search-hub review init --session SESSION_ID # Initialize reviews.yaml\n $ search-hub review status --session SESSION_ID # Show review progress\n $ search-hub review list --session SESSION_ID --filter pending # List articles\n $ search-hub review extract --session SESSION_ID --name title-screening # Extract for review\n $ search-hub review merge --session SESSION_ID --name title-screening # Merge reviews\n $ search-hub review export --session SESSION_ID --only included -o included.yaml`);\n\n reviewCommand\n .command('init')\n .description('Generate reviews.yaml from deduplicated search results')\n .requiredOption('--session <id>', 'session ID')\n .option('-f, --force', 'overwrite existing reviews.yaml', false)\n .action(async (options: { session: string; force: boolean }) => {\n const globalOpts = program.opts() as GlobalOptions;\n try {\n const sessionsDir = await getSessionsDir(globalOpts);\n const initOptions: ReviewInitOptions = {\n sessionId: options.session,\n ...(options.force && { force: options.force }),\n };\n const result = await executeReviewInit(initOptions, sessionsDir);\n if (!globalOpts.quiet) {\n console.log(`Created ${result.reviewsPath}`);\n console.log(` Articles: ${result.articleCount}`);\n if (result.duplicatesRemoved > 0) {\n console.log(` Duplicates removed: ${result.duplicatesRemoved}`);\n }\n }\n process.exitCode = EXIT_CODES.SUCCESS;\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error('Error:', error instanceof Error ? error.message : error);\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n }\n });\n\n reviewCommand\n .command('status')\n .description('Show review progress summary')\n .requiredOption('--session <id>', 'session ID')\n .option('--json', 'output as JSON')\n .action(async (options: { session: string; json?: boolean }) => {\n const globalOpts = program.opts() as GlobalOptions;\n try {\n const sessionsDir = await getSessionsDir(globalOpts);\n const statusOptions: ReviewStatusOptions = {\n sessionId: options.session,\n };\n const result = await executeReviewStatus(statusOptions, sessionsDir);\n if (!globalOpts.quiet) {\n if (options.json) {\n console.log(JSON.stringify(result, null, 2));\n } else {\n console.log(formatStatusOutput(result));\n const suggestion = formatSuggestion(getSuggestion({\n command: 'review status',\n sessionId: options.session,\n reviewStatus: result,\n }));\n if (suggestion) console.log(suggestion);\n }\n }\n process.exitCode = EXIT_CODES.SUCCESS;\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error('Error:', error instanceof Error ? error.message : error);\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n }\n });\n\n reviewCommand\n .command('list')\n .description('List articles with optional filtering')\n .requiredOption('--session <id>', 'session ID')\n .option('--filter <type>', 'filter by status: pending, incomplete, uncertain, agreed-include, agreed-exclude, conflicting, finalized, all', 'all')\n .option('--json', 'output as JSON')\n .action(async (options: { session: string; filter?: string; json?: boolean }) => {\n const globalOpts = program.opts() as GlobalOptions;\n try {\n const validFilters: ListFilter[] = ['pending', 'incomplete', 'uncertain', 'agreed-include', 'agreed-exclude', 'conflicting', 'finalized', 'all'];\n const filter = (options.filter ?? 'all') as ListFilter;\n if (!validFilters.includes(filter)) {\n if (!globalOpts.quiet) {\n console.error(`Error: Invalid filter '${options.filter}'. Valid values: ${validFilters.join(', ')}`);\n }\n process.exitCode = EXIT_CODES.GENERAL_ERROR;\n return;\n }\n\n const sessionsDir = await getSessionsDir(globalOpts);\n const listOptions: ReviewListOptions = {\n sessionId: options.session,\n filter,\n };\n const result = await executeReviewList(listOptions, sessionsDir);\n if (!globalOpts.quiet) {\n if (options.json) {\n console.log(JSON.stringify(result, null, 2));\n } else {\n console.log(formatListOutput(result));\n }\n }\n process.exitCode = EXIT_CODES.SUCCESS;\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error('Error:', error instanceof Error ? error.message : error);\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n }\n });\n\n reviewCommand\n .command('extract')\n .description('Extract subset to for-review/<name>/review.yaml for distributed review')\n .requiredOption('--session <id>', 'session ID')\n .requiredOption('--name <name>', 'name for the review subset (output: for-review/<name>/review.yaml)')\n .option('--filter <types>', 'filter by status (comma-separated): pending, incomplete, uncertain, agreed-include, agreed-exclude, conflicting, finalized')\n .option('--sort <method>', 'sort method: year, title, random, none', 'none')\n .option('--limit <n>', 'limit number of articles')\n .option('--offset <n>', 'skip first n articles')\n .option('--seed <n>', 'random seed for reproducible sorting')\n .option('--basis <type>', 'basis for review: title, abstract, or fulltext')\n .option('--reviewer <id>', 'reviewer identifier (e.g., \"ai:claude\")')\n .option('--finalize', 'extract for final decision (includes reviewHistory and finalDecision)')\n .action(async (options: {\n session: string;\n name: string;\n filter?: string;\n sort?: string;\n limit?: string;\n offset?: string;\n seed?: string;\n basis?: string;\n reviewer?: string;\n finalize?: boolean;\n }) => {\n const globalOpts = program.opts() as GlobalOptions;\n try {\n const validSorts: SortOption[] = ['year', 'title', 'random', 'none'];\n const sort = (options.sort ?? 'none') as SortOption;\n if (!validSorts.includes(sort)) {\n if (!globalOpts.quiet) {\n console.error(`Error: Invalid sort '${options.sort}'. Valid values: ${validSorts.join(', ')}`);\n }\n process.exitCode = EXIT_CODES.GENERAL_ERROR;\n return;\n }\n\n const sessionsDir = await getSessionsDir(globalOpts);\n const extractOptions: ReviewExtractOptions = {\n sessionId: options.session,\n name: options.name,\n sort,\n };\n\n if (options.filter) {\n extractOptions.filter = options.filter.split(',').map(s => s.trim()) as ReviewStatus[];\n }\n if (options.limit) {\n extractOptions.limit = parseInt(options.limit, 10);\n }\n if (options.offset) {\n extractOptions.offset = parseInt(options.offset, 10);\n }\n if (options.seed) {\n extractOptions.seed = parseInt(options.seed, 10);\n }\n\n // Reviewer is required for all extract modes\n if (!options.reviewer) {\n if (!globalOpts.quiet) {\n console.error('Error: --reviewer is required');\n }\n process.exitCode = EXIT_CODES.GENERAL_ERROR;\n return;\n }\n extractOptions.reviewer = options.reviewer;\n\n // Handle basis option\n if (options.basis) {\n const validBasis = ['title', 'abstract', 'fulltext'];\n if (!validBasis.includes(options.basis)) {\n if (!globalOpts.quiet) {\n console.error(`Error: Invalid basis '${options.basis}'. Valid values: ${validBasis.join(', ')}`);\n }\n process.exitCode = EXIT_CODES.GENERAL_ERROR;\n return;\n }\n extractOptions.basis = options.basis as 'title' | 'abstract' | 'fulltext';\n }\n\n if (options.finalize) {\n extractOptions.finalize = true;\n }\n\n const result = await executeReviewExtract(extractOptions, sessionsDir);\n if (!globalOpts.quiet) {\n console.log(`Extracted ${result.extractedCount} of ${result.totalMatching} articles to ${result.outputPath}`);\n const suggestion = formatSuggestion(getSuggestion({\n command: 'review extract',\n sessionId: options.session,\n extractName: options.name,\n extractedCount: result.extractedCount,\n totalMatching: result.totalMatching,\n extractLimit: extractOptions.limit,\n extractOffset: extractOptions.offset,\n }));\n if (suggestion) console.log(suggestion);\n }\n process.exitCode = EXIT_CODES.SUCCESS;\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error('Error:', error instanceof Error ? error.message : error);\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n }\n });\n\n reviewCommand\n .command('merge')\n .description('Merge edited file back into main reviews.yaml')\n .requiredOption('--session <id>', 'session ID')\n .requiredOption('--name <name>', 'name of the review subset to merge (reads from for-review/<name>/review.yaml)')\n .option('--dry-run', 'show changes without applying', false)\n .action(async (options: { session: string; name: string; dryRun: boolean }) => {\n const globalOpts = program.opts() as GlobalOptions;\n try {\n const sessionsDir = await getSessionsDir(globalOpts);\n const mergeOptions: ReviewMergeOptions = {\n sessionId: options.session,\n name: options.name,\n ...(options.dryRun && { dryRun: options.dryRun }),\n };\n const result = await executeReviewMerge(mergeOptions, sessionsDir);\n if (!globalOpts.quiet) {\n console.log(formatMergeOutput(result, options.dryRun));\n if (!options.dryRun) {\n const statusResult = await executeReviewStatus({ sessionId: options.session }, sessionsDir);\n const suggestion = formatSuggestion(getSuggestion({\n command: 'review merge',\n sessionId: options.session,\n reviewStatus: statusResult,\n }));\n if (suggestion) console.log(suggestion);\n }\n }\n process.exitCode = EXIT_CODES.SUCCESS;\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error('Error:', error instanceof Error ? error.message : error);\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n }\n });\n\n reviewCommand\n .command('mark')\n .description('Mark decisions in work files')\n .requiredOption('--file <path>', 'path to work file')\n .option('--id <id>', 'article ID to mark')\n .option('--decision <decision>', 'decision: include, exclude, or uncertain')\n .option('--comment <text>', 'optional comment')\n .action(async (options: {\n file: string;\n id?: string;\n decision?: string;\n comment?: string;\n }) => {\n const globalOpts = program.opts() as GlobalOptions;\n try {\n // Validate decision if provided\n const validDecisions = ['include', 'exclude', 'uncertain'];\n if (options.decision && !validDecisions.includes(options.decision)) {\n if (!globalOpts.quiet) {\n console.error(`Error: Invalid decision '${options.decision}'. Valid values: ${validDecisions.join(', ')}`);\n }\n process.exitCode = EXIT_CODES.GENERAL_ERROR;\n return;\n }\n\n // Validate required options\n if (!options.id || !options.decision) {\n if (!globalOpts.quiet) {\n console.error('Error: --id and --decision must be specified');\n }\n process.exitCode = EXIT_CODES.GENERAL_ERROR;\n return;\n }\n\n const markOptions: ReviewMarkOptions = {\n file: options.file,\n id: options.id,\n decision: options.decision as 'include' | 'exclude' | 'uncertain',\n };\n\n if (options.comment) markOptions.comment = options.comment;\n\n const result = await executeReviewMark(markOptions);\n if (!globalOpts.quiet) {\n console.log(`Marked ${result.marked} article(s)`);\n for (const warning of result.warnings) {\n console.warn(`Warning: ${warning}`);\n }\n }\n process.exitCode = EXIT_CODES.SUCCESS;\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error('Error:', error instanceof Error ? error.message : error);\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n }\n });\n\n reviewCommand\n .command('export')\n .description('Export articles based on final decision')\n .requiredOption('--session <id>', 'session ID')\n .requiredOption('--only <filter>', 'export filter: included or excluded')\n .requiredOption('-o, --output <path>', 'output file path')\n .option('--format <fmt>', 'output format: yaml, json, jsonl', 'yaml')\n .action(async (options: {\n session: string;\n only: string;\n output: string;\n format?: string;\n }) => {\n const globalOpts = program.opts() as GlobalOptions;\n try {\n const validOnlyValues: ReviewExportFilter[] = ['included', 'excluded'];\n const only = options.only as ReviewExportFilter;\n if (!validOnlyValues.includes(only)) {\n if (!globalOpts.quiet) {\n console.error(`Error: Invalid --only value '${options.only}'. Valid values: ${validOnlyValues.join(', ')}`);\n }\n process.exitCode = EXIT_CODES.GENERAL_ERROR;\n return;\n }\n\n const validFormats: ReviewExportFormat[] = ['yaml', 'json', 'jsonl'];\n const format = (options.format ?? 'yaml') as ReviewExportFormat;\n if (!validFormats.includes(format)) {\n if (!globalOpts.quiet) {\n console.error(`Error: Invalid format '${options.format}'. Valid values: ${validFormats.join(', ')}`);\n }\n process.exitCode = EXIT_CODES.GENERAL_ERROR;\n return;\n }\n\n const sessionsDir = await getSessionsDir(globalOpts);\n const exportOptions: ReviewExportOptions = {\n sessionId: options.session,\n only,\n output: options.output,\n format,\n };\n const result = await executeReviewExport(exportOptions, sessionsDir);\n if (!globalOpts.quiet) {\n console.log(formatExportOutput(result));\n }\n process.exitCode = EXIT_CODES.SUCCESS;\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error('Error:', error instanceof Error ? error.message : error);\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n }\n });\n\n reviewCommand\n .command('finalize')\n .description('Auto-set finalDecision for articles with reviewer consensus')\n .requiredOption('--session <id>', 'session ID')\n .option('--dry-run', 'preview without changes', false)\n .option('--min-reviewers <n>', 'minimum agreeing reviewers needed', '1')\n .action(async (options: { session: string; dryRun: boolean; minReviewers: string }) => {\n const globalOpts = program.opts() as GlobalOptions;\n try {\n const sessionsDir = await getSessionsDir(globalOpts);\n const finalizeOptions: ReviewFinalizeOptions = {\n sessionId: options.session,\n ...(options.dryRun && { dryRun: options.dryRun }),\n };\n const minReviewers = parseInt(options.minReviewers, 10);\n if (!Number.isNaN(minReviewers) && minReviewers > 1) {\n finalizeOptions.minReviewers = minReviewers;\n }\n const result = await executeReviewFinalize(finalizeOptions, sessionsDir);\n if (!globalOpts.quiet) {\n console.log(formatFinalizeOutput(result, { dryRun: options.dryRun }));\n if (!options.dryRun) {\n const statusResult = await executeReviewStatus({ sessionId: options.session }, sessionsDir);\n const suggestion = formatSuggestion(getSuggestion({\n command: 'review finalize',\n sessionId: options.session,\n reviewStatus: statusResult,\n }));\n if (suggestion) console.log(suggestion);\n }\n }\n process.exitCode = EXIT_CODES.SUCCESS;\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error('Error:', error instanceof Error ? error.message : error);\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n }\n });\n\n // Register notes command group\n const notesCommand = program\n .command('notes')\n .description('Manage session notes and assessments')\n .addHelpText('after', `\nExamples:\n $ search-hub notes list SESSION_ID # List notes for a session\n $ search-hub notes add SESSION_ID \"my note\" # Add a note\n $ search-hub notes add SESSION_ID --file assessment.md # Add from file\n $ search-hub notes assess SESSION_ID --precision \"~54%\" --verdict good --comment \"Good results\"\n $ search-hub notes list --all # Show notes from all sessions`);\n\n notesCommand\n .command('list')\n .description('List notes for a session or all sessions')\n .argument('[session-id]', 'session ID')\n .option('--all', 'show notes from all sessions')\n .option('--json', 'output as JSON')\n .action(async (sessionId?: string, options?: { all?: boolean; json?: boolean }) => {\n const globalOpts = program.opts() as GlobalOptions;\n try {\n const sessionsDir = await getSessionsDir(globalOpts);\n const formatOpts = { json: options?.json ?? false };\n\n if (options?.all) {\n // Cross-session notes view\n const summaries = await listSessions(sessionsDir);\n const allNotes: SessionNotes[] = [];\n\n for (const summary of summaries) {\n const sessionNotesDir = join(sessionsDir, summary.id);\n const notes = await loadNotes(sessionNotesDir);\n allNotes.push({\n sessionId: summary.id,\n sessionName: summary.name,\n notes,\n });\n }\n\n if (!globalOpts.quiet) {\n console.log(formatAllSessionNotes(allNotes, formatOpts));\n }\n process.exitCode = EXIT_CODES.SUCCESS;\n return;\n }\n\n if (!sessionId) {\n if (!globalOpts.quiet) {\n console.error('Error: session-id is required (or use --all)');\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n return;\n }\n\n if (!(await sessionExists(sessionId, sessionsDir))) {\n if (!globalOpts.quiet) {\n console.error(`Error: session '${sessionId}' not found`);\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n return;\n }\n\n // List notes for a specific session\n const sessionDir = join(sessionsDir, sessionId);\n const notes = await loadNotes(sessionDir);\n if (!globalOpts.quiet) {\n console.log(formatNotesList(notes, formatOpts));\n }\n process.exitCode = EXIT_CODES.SUCCESS;\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error(\n 'Error:',\n error instanceof Error ? error.message : error\n );\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n }\n });\n\n notesCommand\n .command('add')\n .description('Add a note to a session')\n .argument('<session-id>', 'session ID')\n .argument('[text]', 'note text')\n .option('--file <path>', 'read note text from a file instead')\n .action(async (sessionId: string, text?: string, options?: { file?: string }) => {\n const globalOpts = program.opts() as GlobalOptions;\n try {\n const sessionsDir = await getSessionsDir(globalOpts);\n const sessionDir = join(sessionsDir, sessionId);\n\n if (!(await sessionExists(sessionId, sessionsDir))) {\n if (!globalOpts.quiet) {\n console.error(`Error: session '${sessionId}' not found`);\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n return;\n }\n\n let noteText: string;\n if (options?.file) {\n noteText = (await readFile(options.file, 'utf-8')).trim();\n } else if (text) {\n noteText = text;\n } else {\n if (!globalOpts.quiet) {\n console.error('Error: note text or --file is required');\n }\n process.exitCode = EXIT_CODES.GENERAL_ERROR;\n return;\n }\n\n await addNote(sessionDir, noteText);\n\n if (!globalOpts.quiet) {\n console.log('Note added.');\n }\n process.exitCode = EXIT_CODES.SUCCESS;\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error(\n 'Error:',\n error instanceof Error ? error.message : error\n );\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n }\n });\n\n notesCommand\n .command('assess')\n .description('Add a structured assessment to a session')\n .argument('<session-id>', 'session ID')\n .option('--precision <value>', 'estimated precision (e.g., \"~54%\", \"15/28\")')\n .option('--verdict <value>', 'quality judgment: good, refine, reject')\n .option('--comment <text>', 'free text explanation')\n .action(async (sessionId: string, options?: { precision?: string; verdict?: string; comment?: string }) => {\n const globalOpts = program.opts() as GlobalOptions;\n try {\n if (!options?.precision && !options?.verdict && !options?.comment) {\n if (!globalOpts.quiet) {\n console.error('Error: at least one of --precision, --verdict, or --comment is required');\n }\n process.exitCode = EXIT_CODES.GENERAL_ERROR;\n return;\n }\n\n const sessionsDir = await getSessionsDir(globalOpts);\n const sessionDir = join(sessionsDir, sessionId);\n\n if (!(await sessionExists(sessionId, sessionsDir))) {\n if (!globalOpts.quiet) {\n console.error(`Error: session '${sessionId}' not found`);\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n return;\n }\n\n await addAssessment(sessionDir, {\n precision: options?.precision,\n verdict: options?.verdict,\n comment: options?.comment,\n });\n\n if (!globalOpts.quiet) {\n console.log('Assessment added.');\n }\n process.exitCode = EXIT_CODES.SUCCESS;\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error(\n 'Error:',\n error instanceof Error ? error.message : error\n );\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n }\n });\n\n // Register fulltext command group (init, sync, convert, check)\n registerFulltextCommands(program, getSessionsDir);\n\n return program;\n}\n\n/**\n * Main entry point for CLI execution.\n */\nexport async function main(): Promise<void> {\n const program = createProgram();\n await program.parseAsync(process.argv);\n}\n\n// Run main if executed directly\nconst currentFile = fileURLToPath(import.meta.url);\nconst executedFile = process.argv[1];\nif (executedFile) {\n if (realpathSync(executedFile) === realpathSync(currentFile)) {\n main().catch((error) => {\n console.error('Fatal error:', error);\n process.exit(EXIT_CODES.GENERAL_ERROR);\n });\n }\n}\n"],"names":["loadDotenv","config","result","outputData","formatSessionMergeJson","formatSessionMergeOutput","formatRegisterDryRunOutput","formatMergeOutput","notes"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../../src/cli/index.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * CLI entry point for search-hub.\n */\nimport { config as loadDotenv } from 'dotenv';\n\n// Load .env file as early as possible, before any config loading\nloadDotenv({ quiet: true });\nimport { Command } from 'commander';\nimport { VERSION } from '../version.js';\nimport { init } from './commands/init.js';\nimport { EXIT_CODES } from './exit-codes.js';\nimport { loadConfig, saveConfig, getDefaultConfig, type Config } from '../config/index.js';\nimport { getDefaultConfigPath } from '../config/paths.js';\nimport {\n viewConfig,\n viewConfigKey,\n setConfigKey,\n} from './commands/config.js';\nimport {\n validateQueryCommand,\n formatValidateResult,\n formatVocabValidationOutput,\n hasVocabErrors,\n detectSchemaLink,\n} from './commands/query/validate.js';\nimport { MeSHLookupClient } from '../query/mesh-lookup.js';\nimport { RateLimiter } from '../providers/base/rate-limiter.js';\nimport { VocabCache } from '../query/vocab-cache.js';\nimport {\n createEricCountValidator,\n createEmtreeCountValidator,\n type CountVocabValidator,\n} from '../query/vocab-validator.js';\nimport { createProviderInstance } from './commands/search-executor.js';\nimport {\n translateQueryCommand,\n formatTranslateResult,\n} from './commands/query/translate.js';\nimport {\n inspectQueryCommand,\n formatInspectOutput,\n} from './commands/query/inspect.js';\nimport {\n generateQueryTemplate,\n writeQueryTemplate,\n} from './commands/query/init.js';\nimport type { ProviderName } from '../providers/base/types.js';\nimport {\n listSessionsForDisplay,\n getSessionDetails,\n computeDeduplicationStats,\n formatSessionList,\n formatSessionDetails,\n} from './commands/status.js';\nimport {\n parseSearchOptions,\n validateSearchInput,\n formatDryRunOutput,\n formatCountOnlyOutput,\n formatPreviewOutput,\n formatShortKeywordWarning,\n} from './commands/search.js';\nimport { executeSearch, executeCountOnly, executePreview } from './commands/search-executor.js';\nimport {\n parseResumeOptions,\n validateResumeInput,\n getResumableProvidersForCommand,\n} from './commands/resume.js';\nimport { executeResume } from './commands/resume-executor.js';\nimport { formatVerboseProviderDetails } from './commands/search-utils.js';\nimport {\n parseExportOptions,\n validateExportInput,\n formatIds,\n formatJson,\n formatJsonl,\n formatCslJson,\n deduplicateArticles,\n type JsonExportMetadata,\n} from './commands/export.js';\nimport {\n computeSummary,\n formatSummary,\n formatSummaryJson,\n} from './commands/summary.js';\nimport {\n parseResultsOptions,\n validateResultsInput,\n formatResultsList,\n formatResultsJson,\n} from './commands/results.js';\nimport { filterByQuery } from './commands/query-filter.js';\nimport {\n loadNotes,\n addNote,\n addAssessment,\n formatNotesList,\n formatAllSessionNotes,\n type SessionNotes,\n} from './commands/notes.js';\nimport {\n computeDiff,\n computeQueryDiff,\n formatDiff,\n formatDiffJson,\n type ShowFilter,\n} from './commands/diff.js';\nimport {\n mergeArticles,\n validateMergeSources,\n createMergedSession,\n formatMergeOutput as formatSessionMergeOutput,\n formatMergeJson as formatSessionMergeJson,\n} from './commands/merge.js';\n\nimport {\n executeReviewInit,\n type ReviewInitOptions,\n} from './commands/review/init.js';\nimport {\n executeReviewStatus,\n formatStatusOutput,\n type ReviewStatusOptions,\n} from './commands/review/status.js';\nimport {\n executeReviewList,\n formatListOutput,\n type ReviewListOptions,\n type ListFilter,\n} from './commands/review/list.js';\nimport {\n executeReviewExtract,\n type ReviewExtractOptions,\n type SortOption,\n} from './commands/review/extract.js';\nimport {\n executeReviewMerge,\n formatMergeOutput,\n type ReviewMergeOptions,\n} from './commands/review/merge.js';\nimport {\n executeReviewMark,\n type ReviewMarkOptions,\n} from './commands/review/mark.js';\nimport {\n executeReviewExport,\n formatExportOutput,\n type ReviewExportOptions,\n type ExportFormat as ReviewExportFormat,\n type ExportFilter as ReviewExportFilter,\n} from './commands/review/export.js';\nimport {\n executeReviewFinalize,\n formatFinalizeOutput,\n type ReviewFinalizeOptions,\n} from './commands/review/finalize.js';\nimport { type ReviewStatus } from './commands/review/types.js';\nimport { registerFulltextCommands } from './commands/fulltext/index.js';\n\nimport {\n parseRegisterOptions,\n validateRegisterInput,\n formatRegistrationSummary,\n formatDryRunOutput as formatRegisterDryRunOutput,\n hasReviewFile,\n getReviewSummary,\n getIncludedArticles,\n formatReviewRequiredMessage,\n formatNoIncludedArticlesError,\n formatPendingWarning,\n formatIgnoringReviewsNote,\n confirmPrompt,\n} from './commands/register.js';\nimport { formatSuggestion } from './suggestions/index.js';\nimport { getSuggestion } from './suggestions/rules.js';\nimport { registerArticles, saveRegistrationRecord } from '../integration/register.js';\nimport { checkRefAvailable, checkNpmAvailable, installRefManager } from '../integration/ref-cli.js';\nimport { loadSession, sessionExists, listSessions } from '../session/manager.js';\nimport { parseQueryFile, detectShortKeywords } from '../query/parser.js';\nimport { writeFile, readFile } from 'node:fs/promises';\nimport { realpathSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { getSessionsDir } from './utils/sessions-dir.js';\nimport { expandPath } from '../utils/path.js';\nimport { loadSessionArticles, loadSessionQuery } from './commands/session-utils.js';\nimport { parseIdentifierFile, checkCoverage, formatCheckResult, formatCheckResultJson } from './commands/check.js';\n\n/**\n * Global CLI options available to all commands.\n */\nexport interface GlobalOptions {\n /** Path to config file */\n config?: string;\n /** Path to session directory */\n sessionDir?: string;\n /** Enable verbose output */\n verbose: boolean;\n /** Suppress all output except errors */\n quiet: boolean;\n /** Enable color output (default: true, use --no-color to disable) */\n color: boolean;\n}\n\n/**\n * Create and configure the CLI program.\n */\nexport function createProgram(): Command {\n const program = new Command();\n\n program\n .name('search-hub')\n .version(VERSION)\n .description(\n 'CLI tool for systematic literature searching across multiple academic databases'\n )\n .option('-c, --config <path>', 'path to config file')\n .option('--session-dir <path>', 'path to session directory')\n .option('-v, --verbose', 'enable verbose output', false)\n .option('--quiet', 'suppress all output except errors', false)\n .option('--no-color', 'disable color output')\n .addHelpText('after', `\nWorkflow:\n 1. query init → edit → validate / --dry-run Query preparation\n 2. search --preview → search Preview & execute\n 3. results / summary / diff / check Inspect & verify\n 4. review init → extract → merge → status Systematic review\n 5. register / export Output\n\n Iterate: search → results -q → check → diff Query refinement\n\nQuick Start:\n $ search-hub query init -o search.yaml # Create query template\n $ search-hub search search.yaml --count-only # Check hit counts\n $ search-hub search search.yaml # Execute search\n $ search-hub results <session> # Review titles`);\n\n // Register init command\n program\n .command('init')\n .description('Initialize configuration directory')\n .option('-f, --force', 'overwrite existing configuration', false)\n .addHelpText('after', `\nExamples:\n $ search-hub init # Initialize with default settings\n $ search-hub init --force # Overwrite existing configuration`)\n .action(async (options: { force: boolean }) => {\n const globalOpts = program.opts() as GlobalOptions;\n try {\n const result = await init({ force: options.force });\n if (!globalOpts.quiet) {\n if (result.success) {\n console.log(result.message);\n } else {\n console.error(result.message);\n }\n }\n process.exitCode = result.success ? EXIT_CODES.SUCCESS : EXIT_CODES.CONFIG_ERROR;\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error(\n 'Error:',\n error instanceof Error ? error.message : error\n );\n }\n process.exitCode = EXIT_CODES.GENERAL_ERROR;\n }\n });\n\n // Register config command\n program\n .command('config')\n .description('View and edit configuration')\n .argument('[key]', 'configuration key to view or set')\n .argument('[value]', 'value to set for the key')\n .addHelpText('after', `\nExamples:\n $ search-hub config # Show all config\n $ search-hub config providers.pubmed # Show PubMed config\n $ search-hub config providers.pubmed.api_key KEY # Set API key`)\n .action(async (key?: string, value?: string) => {\n const globalOpts = program.opts() as GlobalOptions;\n try {\n // Load config - use default if no config file exists\n let config;\n try {\n config = await loadConfig(\n globalOpts.config ? { explicitConfigPath: globalOpts.config } : {}\n );\n } catch {\n config = getDefaultConfig();\n }\n\n if (!key) {\n // View all config\n if (!globalOpts.quiet) {\n console.log(viewConfig(config));\n }\n } else if (!value) {\n // View specific key\n const result = viewConfigKey(config, key);\n if (result.success) {\n if (!globalOpts.quiet) {\n console.log(result.value);\n }\n } else {\n if (!globalOpts.quiet) {\n console.error(`Error: ${result.error}`);\n }\n process.exitCode = EXIT_CODES.CONFIG_ERROR;\n return;\n }\n } else {\n // Set key value\n const result = setConfigKey(config, key, value);\n if (result.success) {\n // Save the modified config to file\n const configPath = globalOpts.config ? expandPath(globalOpts.config) : getDefaultConfigPath();\n try {\n await saveConfig(config, { path: configPath });\n if (!globalOpts.quiet) {\n console.log(`Set ${key} = ${result.value}`);\n console.log(`Saved to ${configPath}`);\n }\n } catch (saveError) {\n if (!globalOpts.quiet) {\n console.error(\n `Error saving config: ${saveError instanceof Error ? saveError.message : saveError}`\n );\n }\n process.exitCode = EXIT_CODES.CONFIG_ERROR;\n return;\n }\n } else {\n if (!globalOpts.quiet) {\n console.error(`Error: ${result.error}`);\n }\n process.exitCode = EXIT_CODES.CONFIG_ERROR;\n return;\n }\n }\n process.exitCode = EXIT_CODES.SUCCESS;\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error(\n 'Error:',\n error instanceof Error ? error.message : error\n );\n }\n process.exitCode = EXIT_CODES.CONFIG_ERROR;\n }\n });\n\n // Register query command group\n const queryCommand = program\n .command('query')\n .description('Query file utilities')\n .addHelpText('after', `\nQuery YAML format (minimal):\n name: my_search\n query:\n - field: title_abstract\n terms:\n keywords: [\"term1\", \"term2\"]\n operator: OR\n\nUse \"search-hub query init\" to generate a template.`);\n\n queryCommand\n .command('validate')\n .description('Validate query YAML file (auto-checks controlled vocabulary)')\n .argument('<file>', 'path to query YAML file')\n .option('--no-vocab', 'skip controlled vocabulary validation')\n .option('--no-cache', 'skip vocabulary lookup cache')\n .addHelpText('after', `\nExamples:\n $ search-hub query validate ./diabetes-ai.yaml\n $ search-hub query validate ./diabetes-ai.yaml --no-vocab # Skip MeSH check\n $ search-hub query validate ./diabetes-ai.yaml --no-cache # Ignore cache`)\n .action(async (file: string, opts: { vocab?: boolean; cache?: boolean }) => {\n const globalOpts = program.opts() as GlobalOptions;\n try {\n const noVocab = opts.vocab === false;\n const noCache = opts.cache === false;\n\n let cache: VocabCache | undefined;\n if (!noVocab && !noCache) {\n cache = new VocabCache();\n await cache.load();\n }\n\n const hasSchema = await detectSchemaLink(file);\n\n if (noVocab) {\n const result = await validateQueryCommand(file, { noVocab });\n\n if (!globalOpts.quiet) {\n let output = formatValidateResult(result, file);\n const suggestion = formatSuggestion(getSuggestion({\n command: 'query validate',\n queryFile: file,\n validationSuccess: result.success,\n hasSchemaLink: hasSchema,\n }));\n if (suggestion) output += '\\n' + suggestion;\n console.log(output);\n }\n process.exitCode = !result.success\n ? EXIT_CODES.QUERY_ERROR\n : EXIT_CODES.SUCCESS;\n return;\n }\n\n const rateLimiter = new RateLimiter({ tokensPerSecond: 3 });\n const meshClient = new MeSHLookupClient({\n rateLimiter,\n ...(cache ? { cache } : {}),\n });\n\n // Create count validators for ERIC/Emtree\n const countValidators: CountVocabValidator[] = [];\n let config: Config | undefined;\n try {\n config = await loadConfig(\n globalOpts.config ? { explicitConfigPath: globalOpts.config } : {}\n );\n } catch {\n // Config not available — skip count validators\n }\n\n if (config) {\n const ericProvider = createProviderInstance('eric', config);\n if (ericProvider) {\n countValidators.push(\n createEricCountValidator(ericProvider, cache ? { cache } : undefined)\n );\n }\n\n const scopusProvider = createProviderInstance('scopus', config);\n if (scopusProvider) {\n countValidators.push(\n createEmtreeCountValidator(scopusProvider, cache ? { cache } : undefined)\n );\n }\n }\n\n const result = await validateQueryCommand(file, {\n meshClient,\n ...(countValidators.length > 0 ? { countValidators } : {}),\n });\n\n if (cache) {\n await cache.save();\n }\n\n if (!globalOpts.quiet) {\n let output = formatValidateResult(result, file);\n if (result.vocabResult) {\n output += formatVocabValidationOutput(result.vocabResult);\n }\n const suggestion = formatSuggestion(getSuggestion({\n command: 'query validate',\n queryFile: file,\n validationSuccess: result.success && !hasVocabErrors(result),\n hasSchemaLink: hasSchema,\n }));\n if (suggestion) output += '\\n' + suggestion;\n console.log(output);\n }\n process.exitCode =\n !result.success || hasVocabErrors(result)\n ? EXIT_CODES.QUERY_ERROR\n : EXIT_CODES.SUCCESS;\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error(\n 'Error:',\n error instanceof Error ? error.message : error\n );\n }\n process.exitCode = EXIT_CODES.QUERY_ERROR;\n }\n });\n\n queryCommand\n .command('translate')\n .description('Show translated queries for each database')\n .argument('<file>', 'path to query YAML file')\n .option('--db <provider>', 'show translation for specific provider only')\n .addHelpText('after', `\nExamples:\n $ search-hub query translate ./diabetes-ai.yaml # All databases\n $ search-hub query translate ./diabetes-ai.yaml --db pubmed # PubMed only`)\n .action(async (file: string, options: { db?: string }) => {\n const globalOpts = program.opts() as GlobalOptions;\n try {\n const translateOptions = options.db\n ? { providers: [options.db as ProviderName] }\n : {};\n const result = await translateQueryCommand(file, translateOptions);\n if (!globalOpts.quiet) {\n console.log(formatTranslateResult(result, file));\n }\n process.exitCode = result.success\n ? EXIT_CODES.SUCCESS\n : EXIT_CODES.QUERY_ERROR;\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error(\n 'Error:',\n error instanceof Error ? error.message : error\n );\n }\n process.exitCode = EXIT_CODES.QUERY_ERROR;\n }\n });\n\n queryCommand\n .command('inspect')\n .description('Show how a query resolves per provider (block replacements and added filters)')\n .argument('<file>', 'path to query YAML file')\n .option('--db <provider>', 'show resolution for specific provider only')\n .addHelpText('after', `\nExamples:\n $ search-hub query inspect ./diabetes-ai.yaml # All databases\n $ search-hub query inspect ./diabetes-ai.yaml --db pubmed # PubMed only`)\n .action(async (file: string, options: { db?: string }) => {\n const globalOpts = program.opts() as GlobalOptions;\n try {\n const inspectOptions = options.db\n ? { providers: [options.db as ProviderName] }\n : {};\n const result = await inspectQueryCommand(file, inspectOptions);\n if (!result.success) {\n if (!globalOpts.quiet) {\n console.error(`\\u2717 Failed to inspect: ${file}\\n Error: ${result.error}`);\n }\n process.exitCode = EXIT_CODES.QUERY_ERROR;\n return;\n }\n if (!globalOpts.quiet) {\n console.log(formatInspectOutput(result.result!));\n }\n process.exitCode = EXIT_CODES.SUCCESS;\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error(\n 'Error:',\n error instanceof Error ? error.message : error\n );\n }\n process.exitCode = EXIT_CODES.QUERY_ERROR;\n }\n });\n\n queryCommand\n .command('init')\n .description('Generate a template query YAML file')\n .option('-o, --output <path>', 'write to file (default: stdout)')\n .option('--force', 'overwrite existing file', false)\n .action(async (options: { output?: string; force?: boolean }) => {\n const globalOpts = program.opts() as GlobalOptions;\n try {\n if (options.output) {\n const result = await writeQueryTemplate(options);\n if (!globalOpts.quiet) {\n if (result.success) {\n console.log(result.message);\n } else {\n console.error(result.message);\n }\n }\n process.exitCode = result.success ? EXIT_CODES.SUCCESS : EXIT_CODES.GENERAL_ERROR;\n } else {\n const template = generateQueryTemplate();\n console.log(template);\n process.exitCode = EXIT_CODES.SUCCESS;\n }\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error('Error:', error instanceof Error ? error.message : error);\n }\n process.exitCode = EXIT_CODES.GENERAL_ERROR;\n }\n });\n\n // Register status command\n program\n .command('status')\n .description('Show session status and statistics')\n .argument('[session-id]', 'session ID to show details for')\n .option('--json', 'output as JSON')\n .option('--all', 'include completed sessions')\n .addHelpText('after', `\nExamples:\n $ search-hub status # List recent sessions\n $ search-hub status 20240115_diabetes-ai_a3f2 # Show session details\n $ search-hub status --json # JSON output for scripting`)\n .action(async (sessionId?: string, options?: { json?: boolean; all?: boolean }) => {\n const globalOpts = program.opts() as GlobalOptions;\n try {\n const sessionsDir = await getSessionsDir(globalOpts);\n const formatOpts = { json: options?.json ?? false };\n\n if (sessionId) {\n // Show specific session details\n const result = await getSessionDetails(sessionId, sessionsDir);\n if (result.success && result.session) {\n // Compute deduplication stats\n try {\n const rawSession = await loadSession(sessionId, sessionsDir);\n const dedupStats = await computeDeduplicationStats(sessionId, sessionsDir, rawSession);\n result.session.uniqueArticles = dedupStats.uniqueArticles;\n result.session.duplicatesRemoved = dedupStats.duplicatesRemoved;\n } catch {\n // Dedup stats are optional - don't fail the command\n }\n if (!globalOpts.quiet) {\n console.log(formatSessionDetails(result.session, formatOpts));\n }\n } else {\n if (!globalOpts.quiet) {\n console.error(`Error: ${result.error}`);\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n return;\n }\n } else {\n // List sessions\n const listOpts = { all: options?.all ?? false };\n const sessions = await listSessionsForDisplay(sessionsDir, listOpts);\n if (!globalOpts.quiet) {\n console.log(formatSessionList(sessions, formatOpts));\n }\n }\n process.exitCode = EXIT_CODES.SUCCESS;\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error(\n 'Error:',\n error instanceof Error ? error.message : error\n );\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n }\n });\n\n // Register search command\n program\n .command('search')\n .description('Execute search across databases')\n .argument('[query-file]', 'path to query YAML file')\n .option('--db <providers>', 'target specific database(s), comma-separated')\n .option('--query <string>', 'direct query in database-native syntax (advanced; requires --db; prefer YAML files)')\n .option('--name <string>', 'session name')\n .option('--max-results <n>', 'limit results per database')\n .option('--dry-run', 'show translated queries without executing')\n .option('--count-only', 'get hit counts without downloading results')\n .option('--preview', 'get hit counts and first 5 titles without creating session')\n .option('--skip-connection-test', 'skip API connection test during dry-run')\n .option('--no-resume', 'start fresh even if session exists')\n .option('--strict', 'require all targeted databases to succeed (exit non-zero on partial failure)')\n .addHelpText('after', `\nWorkflow position:\n query validate → [this command: search] → results / summary / diff\n\nExamples:\n $ search-hub search ./diabetes-ai.yaml # Search all databases\n $ search-hub search ./query.yaml --db pubmed,eric # Specific databases\n $ search-hub search --db pubmed --query \"diabetes[tiab]\" # Direct query\n $ search-hub search ./query.yaml --dry-run # Preview translations\n $ search-hub search ./query.yaml --count-only # Get hit counts only\n $ search-hub search ./query.yaml --max-results 100 # Limit results\n\nQuery features (use \"query init\" to see full template):\n filters: year_from, year_to, language, publication_types\n exclude: NOT terms per block (terms.exclude)\n mesh/eric: controlled vocabulary (terms.mesh, terms.eric)\n providers: per-database block replacements and filter additions`)\n .action(\n async (\n queryFile?: string,\n options?: {\n db?: string;\n query?: string;\n name?: string;\n maxResults?: string;\n dryRun?: boolean;\n countOnly?: boolean;\n preview?: boolean;\n skipConnectionTest?: boolean;\n resume?: boolean;\n strict?: boolean;\n }\n ) => {\n const globalOpts = program.opts() as GlobalOptions;\n try {\n // Parse and validate options\n const searchOpts = parseSearchOptions(queryFile, {\n db: options?.db,\n query: options?.query,\n name: options?.name,\n maxResults: options?.maxResults,\n dryRun: options?.dryRun,\n countOnly: options?.countOnly,\n preview: options?.preview,\n noResume: options?.resume === false,\n strict: options?.strict,\n });\n\n const validation = validateSearchInput(searchOpts);\n if (!validation.valid) {\n if (!globalOpts.quiet) {\n console.error(`Error: ${validation.error}`);\n }\n process.exitCode = EXIT_CODES.GENERAL_ERROR;\n return;\n }\n\n // Check for short keywords and display warning\n if (searchOpts.queryFile && !globalOpts.quiet) {\n try {\n const ast = await parseQueryFile(searchOpts.queryFile);\n const shortKeywords = detectShortKeywords(ast);\n if (shortKeywords.length > 0) {\n console.error(formatShortKeywordWarning(shortKeywords));\n console.error('');\n }\n } catch {\n // Ignore parse errors here - they'll be caught later during execution\n }\n }\n\n // Handle dry-run mode\n if (searchOpts.dryRun) {\n // Try to load config for provider readiness display\n let dryRunConfig: Config | undefined;\n try {\n dryRunConfig = await loadConfig(globalOpts.config ? { explicitConfigPath: globalOpts.config } : {});\n } catch {\n // Config unavailable, readiness section will be omitted\n }\n\n if (searchOpts.queryFile) {\n // Translate from file\n const translateOpts = searchOpts.providers\n ? { providers: searchOpts.providers }\n : {};\n const result = await translateQueryCommand(\n searchOpts.queryFile,\n translateOpts\n );\n if (result.success && result.translations) {\n const translations = Object.entries(result.translations).map(\n ([provider, t]) => ({ provider, query: t.native })\n );\n const providers = translations.map(t => t.provider) as ProviderName[];\n if (!globalOpts.quiet) {\n const dryRunOpts = dryRunConfig\n ? { config: dryRunConfig, providers, skipConnectionTest: options?.skipConnectionTest }\n : {};\n console.log(await formatDryRunOutput(translations, dryRunOpts));\n }\n } else {\n if (!globalOpts.quiet) {\n console.error(`Error: ${result.error}`);\n }\n process.exitCode = EXIT_CODES.QUERY_ERROR;\n return;\n }\n } else if (searchOpts.directQuery && searchOpts.providers) {\n // Direct query\n const translations = [\n {\n provider: searchOpts.providers[0]!,\n query: searchOpts.directQuery,\n },\n ];\n if (!globalOpts.quiet) {\n const dryRunOpts = dryRunConfig\n ? { config: dryRunConfig, providers: searchOpts.providers as ProviderName[], skipConnectionTest: options?.skipConnectionTest }\n : {};\n console.log(await formatDryRunOutput(translations, dryRunOpts));\n }\n }\n process.exitCode = EXIT_CODES.SUCCESS;\n return;\n }\n\n // Handle preview mode\n if (searchOpts.preview) {\n let previewConfig;\n try {\n previewConfig = await loadConfig(globalOpts.config ? { explicitConfigPath: globalOpts.config } : {});\n } catch {\n previewConfig = getDefaultConfig();\n }\n\n const previews = await executePreview(searchOpts, previewConfig);\n\n if (previews.length === 0) {\n if (!globalOpts.quiet) {\n console.error('Error: No providers enabled or selected');\n }\n process.exitCode = EXIT_CODES.GENERAL_ERROR;\n return;\n }\n\n if (!globalOpts.quiet) {\n console.log(formatPreviewOutput(previews, searchOpts.queryFile));\n }\n\n const previewHasErrors = previews.some((p) => p.error);\n const previewAllFailed = previews.every((p) => p.error);\n if (previewAllFailed) {\n process.exitCode = EXIT_CODES.NETWORK_ERROR;\n } else if (previewHasErrors && searchOpts.strict) {\n process.exitCode = EXIT_CODES.NETWORK_ERROR;\n } else {\n process.exitCode = EXIT_CODES.SUCCESS;\n }\n if (previewHasErrors && !previewAllFailed && !globalOpts.quiet) {\n const failed = previews.filter((p) => p.error).map((p) => `${p.provider}: ${p.error}`);\n console.warn(`\\nWarning: Some providers failed:\\n ${failed.join('\\n ')}`);\n }\n return;\n }\n\n // Handle count-only mode\n if (searchOpts.countOnly) {\n let countConfig;\n try {\n countConfig = await loadConfig(globalOpts.config ? { explicitConfigPath: globalOpts.config } : {});\n } catch {\n countConfig = getDefaultConfig();\n }\n\n const counts = await executeCountOnly(searchOpts, countConfig);\n\n if (counts.length === 0) {\n if (!globalOpts.quiet) {\n console.error('Error: No providers enabled or selected');\n }\n process.exitCode = EXIT_CODES.GENERAL_ERROR;\n return;\n }\n\n if (!globalOpts.quiet) {\n console.log(formatCountOnlyOutput(counts, searchOpts.queryFile));\n const suggestion = formatSuggestion(getSuggestion({\n command: 'search --count-only',\n queryFile: searchOpts.queryFile,\n }));\n if (suggestion) console.log(suggestion);\n }\n\n const countHasErrors = counts.some((c) => c.error);\n const countAllFailed = counts.every((c) => c.error);\n if (countAllFailed) {\n process.exitCode = EXIT_CODES.NETWORK_ERROR;\n } else if (countHasErrors && searchOpts.strict) {\n process.exitCode = EXIT_CODES.NETWORK_ERROR;\n } else {\n process.exitCode = EXIT_CODES.SUCCESS;\n }\n if (countHasErrors && !countAllFailed && !globalOpts.quiet) {\n const failed = counts.filter((c) => c.error).map((c) => `${c.provider}: ${c.error}`);\n console.warn(`\\nWarning: Some providers failed:\\n ${failed.join('\\n ')}`);\n }\n return;\n }\n\n // Non-dry-run: actual search execution\n const sessionsDir = await getSessionsDir(globalOpts);\n let config;\n try {\n config = await loadConfig(\n globalOpts.config ? { explicitConfigPath: globalOpts.config } : {}\n );\n } catch {\n config = getDefaultConfig();\n }\n\n const showProgress = !globalOpts.quiet && process.stdout.isTTY;\n const result = await executeSearch(\n searchOpts,\n sessionsDir,\n config,\n showProgress\n );\n\n if (result.success) {\n if (!globalOpts.quiet) {\n console.log(`\\nSearch completed. Session: ${result.sessionId}`);\n if (result.results) {\n for (const [provider, stats] of Object.entries(result.results)) {\n if (stats.error) {\n console.log(` ${provider}: FAILED - ${stats.error}`);\n } else {\n console.log(` ${provider}: ${stats.retrieved} results`);\n }\n }\n }\n // Show warning for partial success\n if (result.sessionStatus === 'partial' && result.results) {\n const failed = Object.entries(result.results)\n .filter(([, s]) => s.error)\n .map(([p, s]) => `${p}: ${s.error}`);\n if (failed.length > 0) {\n console.warn(`\\nWarning: Some providers failed:\\n ${failed.join('\\n ')}`);\n }\n }\n // Show next step suggestions\n if (result.sessionId) {\n const sessions = await listSessions(sessionsDir);\n const suggestionCmd = searchOpts.directQuery ? 'search --query' : 'search';\n const suggestion = formatSuggestion(getSuggestion({\n command: suggestionCmd,\n sessionId: result.sessionId,\n sessionStatus: result.sessionStatus,\n sessionCount: sessions.length,\n }));\n if (suggestion) console.log(suggestion);\n }\n }\n process.exitCode = EXIT_CODES.SUCCESS;\n } else {\n if (!globalOpts.quiet) {\n console.error(`Error: ${result.error}`);\n if (globalOpts.verbose && result.results) {\n console.error(formatVerboseProviderDetails(result.results));\n }\n }\n process.exitCode = EXIT_CODES.NETWORK_ERROR;\n }\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error(\n 'Error:',\n error instanceof Error ? error.message : error\n );\n }\n process.exitCode = EXIT_CODES.GENERAL_ERROR;\n }\n }\n );\n\n // Register resume command\n program\n .command('resume')\n .description('Resume an interrupted search session')\n .argument('<session-id>', 'session ID to resume')\n .option('--db <providers>', 'resume only specific database(s)')\n .option('--retry-failed', 'retry failed databases')\n .addHelpText('after', `\nExamples:\n $ search-hub resume 20240115_diabetes-ai_a3f2 # Resume session\n $ search-hub resume SESSION_ID --retry-failed # Retry failed databases\n $ search-hub resume SESSION_ID --db scopus # Resume specific database`)\n .action(\n async (\n sessionId: string,\n options?: { db?: string; retryFailed?: boolean }\n ) => {\n const globalOpts = program.opts() as GlobalOptions;\n try {\n // Parse and validate options\n const resumeOpts = parseResumeOptions(sessionId, {\n db: options?.db,\n retryFailed: options?.retryFailed,\n });\n\n const validation = validateResumeInput(resumeOpts);\n if (!validation.valid) {\n if (!globalOpts.quiet) {\n console.error(`Error: ${validation.error}`);\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n return;\n }\n\n const sessionsDir = await getSessionsDir(globalOpts);\n\n // Get resumable providers\n const result = await getResumableProvidersForCommand(\n sessionId,\n sessionsDir,\n {\n providers: resumeOpts.providers,\n retryFailed: resumeOpts.retryFailed,\n }\n );\n\n if (!result.success) {\n if (!globalOpts.quiet) {\n console.error(`Error: ${result.error}`);\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n return;\n }\n\n if (!result.providers || result.providers.length === 0) {\n if (!globalOpts.quiet) {\n console.log('No providers need resuming for this session.');\n }\n process.exitCode = EXIT_CODES.SUCCESS;\n return;\n }\n\n // Show resumable providers\n if (!globalOpts.quiet) {\n console.log(`Resuming session ${sessionId} with ${result.providers.length} provider(s):`);\n for (const p of result.providers) {\n const details = p.cursor\n ? `cursor: ${p.cursor}`\n : p.pageNumber\n ? `page: ${p.pageNumber}`\n : '';\n console.log(` - ${p.provider}: ${p.strategy}${details ? ` (${details})` : ''}`);\n }\n console.log('');\n }\n\n // Execute resume\n let config;\n try {\n config = await loadConfig(\n globalOpts.config ? { explicitConfigPath: globalOpts.config } : {}\n );\n } catch {\n config = getDefaultConfig();\n }\n\n const showProgress = !globalOpts.quiet && process.stdout.isTTY;\n const execResult = await executeResume(\n resumeOpts,\n sessionsDir,\n config,\n showProgress\n );\n\n if (execResult.success) {\n if (!globalOpts.quiet) {\n console.log(`\\nResume completed. ${execResult.resumed} provider(s) resumed.`);\n if (execResult.results) {\n for (const [provider, stats] of Object.entries(execResult.results)) {\n console.log(` ${provider}: ${stats.retrieved} results`);\n }\n }\n }\n process.exitCode = EXIT_CODES.SUCCESS;\n } else {\n if (!globalOpts.quiet) {\n console.error(`Error: ${execResult.error}`);\n if (globalOpts.verbose && execResult.results) {\n console.error(formatVerboseProviderDetails(execResult.results));\n }\n }\n process.exitCode = EXIT_CODES.NETWORK_ERROR;\n }\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error(\n 'Error:',\n error instanceof Error ? error.message : error\n );\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n }\n }\n );\n\n // Register export command\n program\n .command('export')\n .description('Export session results to various formats')\n .argument('<session-id>', 'session ID to export')\n .option('--format <fmt>', 'output format: ids, json, jsonl, csl-json', 'jsonl')\n .option('-o, --output <path>', 'output file path (default: stdout)')\n .option('--id-type <type>', 'for ids format: doi, pmid, all')\n .option('--no-dedup', 'disable deduplication of results')\n .option('-q, --query <expr>', 'filter results with query expression')\n .addHelpText('after', `\nExamples:\n $ search-hub export SESSION_ID # JSONL to stdout\n $ search-hub export SESSION_ID --format json # JSON to stdout\n $ search-hub export SESSION_ID -q \"year:2023\" # Filter by query\n $ search-hub export SESSION_ID -q \"author:smith\" --format ids # Filtered IDs\n $ search-hub export SESSION_ID --format json -o results.json # JSON to file\n $ search-hub export SESSION_ID --format ids --id-type doi # Export DOIs to stdout\n $ search-hub export SESSION_ID --no-dedup # Export without deduplication\n $ search-hub export SESSION_ID --format jsonl | jq '.title' # Pipe to jq\n\nQuery syntax:\n Free text diabetes Search title and abstract\n title:VALUE title:learning Title substring\n abstract:VALUE abstract:randomized Abstract substring\n author:VALUE author:tanaka Author name substring\n journal:VALUE journal:lancet Journal name substring\n year:VALUE year:2023 Exact year\n year:FROM-TO year:2020-2024 Year range\n doi:VALUE doi:10.1001/xxx DOI exact match\n pmid:VALUE pmid:12345678 PMID exact match\n source:VALUE source:pubmed Provider exact match\n\n Multiple terms: different fields = AND, same field = OR`)\n .action(\n async (\n sessionId: string,\n options?: {\n format?: string;\n output?: string;\n idType?: string;\n dedup?: boolean;\n query?: string;\n }\n ) => {\n const globalOpts = program.opts() as GlobalOptions;\n try {\n // Parse and validate options\n const exportOpts = parseExportOptions(sessionId, {\n format: options?.format,\n output: options?.output,\n idType: options?.idType,\n });\n\n const validation = validateExportInput(exportOpts);\n if (!validation.valid) {\n if (!globalOpts.quiet) {\n console.error(`Error: ${validation.error}`);\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n return;\n }\n\n const sessionsDir = await getSessionsDir(globalOpts);\n\n // Load session\n let session;\n try {\n session = await loadSession(sessionId, sessionsDir);\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error(\n `Error: ${error instanceof Error ? error.message : 'Failed to load session'}`\n );\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n return;\n }\n\n // Collect articles from result files\n const articles = await loadSessionArticles(session, sessionId, sessionsDir);\n\n // Deduplicate articles unless --no-dedup is set\n const shouldDedup = options?.dedup !== false;\n let exportArticles: typeof articles;\n let duplicatesRemoved = 0;\n\n if (shouldDedup) {\n const dedupResult = deduplicateArticles(articles);\n exportArticles = dedupResult.articles;\n duplicatesRemoved = dedupResult.duplicatesRemoved;\n } else {\n exportArticles = articles;\n }\n\n // Apply -q filter\n const preFilterCount = exportArticles.length;\n let hasFilter = false;\n if (options?.query) {\n exportArticles = filterByQuery(exportArticles, options.query);\n hasFilter = true;\n }\n\n // Format output\n let output: string;\n if (exportOpts.format === 'ids') {\n output = formatIds(exportArticles, exportOpts.idType ?? 'all');\n } else if (exportOpts.format === 'json') {\n // Build per-database article counts\n const databases: Record<string, number> = {};\n for (const article of exportArticles) {\n databases[article.source] = (databases[article.source] ?? 0) + 1;\n }\n const metadata: JsonExportMetadata = {\n sessionId: session.id,\n sessionName: session.name,\n createdAt: session.createdAt,\n databases,\n };\n output = formatJson(exportArticles, metadata);\n } else if (exportOpts.format === 'csl-json') {\n output = formatCslJson(exportArticles);\n } else {\n output = formatJsonl(exportArticles);\n }\n\n // Write to file or stdout\n if (exportOpts.outputPath) {\n await writeFile(exportOpts.outputPath, output, 'utf-8');\n if (!globalOpts.quiet) {\n let message: string;\n if (hasFilter) {\n message = `Exported ${exportArticles.length} articles (filtered from ${preFilterCount}) to ${exportOpts.outputPath}`;\n } else {\n message = `Exported ${exportArticles.length} articles to ${exportOpts.outputPath}`;\n }\n if (duplicatesRemoved > 0) {\n message += ` (${duplicatesRemoved} duplicate${duplicatesRemoved === 1 ? '' : 's'} removed)`;\n }\n console.error(message);\n }\n } else {\n process.stdout.write(output + '\\n');\n if (!globalOpts.quiet) {\n const parts: string[] = [];\n if (hasFilter) {\n parts.push(`filtered from ${preFilterCount} to ${exportArticles.length} articles`);\n }\n if (duplicatesRemoved > 0) {\n parts.push(`${duplicatesRemoved} duplicate${duplicatesRemoved === 1 ? '' : 's'} removed`);\n }\n if (parts.length > 0) {\n console.error(`(${parts.join(', ')})`);\n }\n }\n }\n\n process.exitCode = EXIT_CODES.SUCCESS;\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error(\n 'Error:',\n error instanceof Error ? error.message : error\n );\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n }\n }\n );\n\n // Register summary command\n program\n .command('summary')\n .description('Show statistical summary of session results')\n .argument('<session-id>', 'session ID to summarize')\n .option('--json', 'output as JSON')\n .addHelpText('after', `\\nExamples:\n $ search-hub summary 20240115_diabetes-ai_a3f2 # Human-readable summary\n $ search-hub summary 20240115_diabetes-ai_a3f2 --json # JSON output`)\n .action(\n async (\n sessionId: string,\n options?: { json?: boolean }\n ) => {\n const globalOpts = program.opts() as GlobalOptions;\n try {\n const sessionsDir = await getSessionsDir(globalOpts);\n\n // Load session\n let session;\n try {\n session = await loadSession(sessionId, sessionsDir);\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error(\n `Error: ${error instanceof Error ? error.message : 'Failed to load session'}`\n );\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n return;\n }\n\n // Collect articles from result files\n const allArticles = await loadSessionArticles(session, sessionId, sessionsDir);\n\n // Deduplicate\n const dedupResult = deduplicateArticles(allArticles);\n\n // Compute summary\n const summary = computeSummary(allArticles, dedupResult.articles, {\n sessionId,\n sessionName: session.name,\n });\n\n // Format output\n if (options?.json) {\n console.log(formatSummaryJson(summary));\n } else {\n if (!globalOpts.quiet) {\n console.log(formatSummary(summary));\n }\n }\n\n process.exitCode = EXIT_CODES.SUCCESS;\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error(\n 'Error:',\n error instanceof Error ? error.message : error\n );\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n }\n }\n );\n\n // Register results command\n program\n .command('results')\n .description('List articles from a session with title, year, and journal')\n .argument('<session-id>', 'session ID to list results from')\n .option('--limit <n>', 'maximum number of results to show')\n .option('--offset <n>', 'skip first n results')\n .option('--json', 'output as JSON array')\n .option('--fields <fields>', 'fields to display (comma-separated)')\n .option('-q, --query <expr>', 'filter results with query expression')\n .option('--abstract', 'show abstracts with results')\n .option('--abstract-length <n>', 'maximum abstract length in characters (default: 300)')\n .addHelpText('after', `\nExamples:\n $ search-hub results SESSION_ID # List all articles\n $ search-hub results SESSION_ID --limit 20 # First 20 articles\n $ search-hub results SESSION_ID -q \"diabetes\" # Free text filter\n $ search-hub results SESSION_ID -q \"author:smith year:2023\" # Combined filter\n $ search-hub results SESSION_ID -q \"doi:10.1001/xxx\" # Exact ID match\n $ search-hub results SESSION_ID --json # JSON output\n $ search-hub results SESSION_ID -q \"source:pubmed\" # Only PubMed\n $ search-hub results SESSION_ID --abstract # Show abstracts\n\nQuery syntax:\n Free text diabetes Search title and abstract\n title:VALUE title:learning Title substring\n abstract:VALUE abstract:randomized Abstract substring\n author:VALUE author:tanaka Author name substring\n journal:VALUE journal:lancet Journal name substring\n year:VALUE year:2023 Exact year\n year:FROM-TO year:2020-2024 Year range\n doi:VALUE doi:10.1001/xxx DOI exact match\n pmid:VALUE pmid:12345678 PMID exact match\n source:VALUE source:pubmed Provider exact match\n\n Multiple terms: different fields = AND, same field = OR`)\n .action(\n async (\n sessionId: string,\n options?: {\n limit?: string;\n offset?: string;\n json?: boolean;\n fields?: string;\n query?: string;\n abstract?: boolean;\n abstractLength?: string;\n }\n ) => {\n const globalOpts = program.opts() as GlobalOptions;\n try {\n // Parse and validate options\n const resultsOpts = parseResultsOptions(sessionId, {\n limit: options?.limit,\n offset: options?.offset,\n json: options?.json,\n fields: options?.fields,\n query: options?.query,\n abstract: options?.abstract,\n abstractLength: options?.abstractLength,\n });\n\n const validation = validateResultsInput(resultsOpts);\n if (!validation.valid) {\n if (!globalOpts.quiet) {\n console.error(`Error: ${validation.error}`);\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n return;\n }\n\n const sessionsDir = await getSessionsDir(globalOpts);\n\n // Load session\n let session;\n try {\n session = await loadSession(sessionId, sessionsDir);\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error(\n `Error: ${error instanceof Error ? error.message : 'Failed to load session'}`\n );\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n return;\n }\n\n // Collect articles from result files\n const articles = await loadSessionArticles(session, sessionId, sessionsDir);\n\n // Deduplicate articles\n const dedupResult = deduplicateArticles(articles);\n let displayArticles = dedupResult.articles;\n\n // Apply -q filter\n let filteredFrom: number | undefined;\n if (resultsOpts.query) {\n const preFilterCount = displayArticles.length;\n displayArticles = filterByQuery(displayArticles, resultsOpts.query);\n if (displayArticles.length !== preFilterCount) {\n filteredFrom = preFilterCount;\n }\n }\n\n // Apply pagination\n const total = displayArticles.length;\n const offset = resultsOpts.offset ?? 0;\n if (offset > 0) {\n displayArticles = displayArticles.slice(offset);\n }\n if (resultsOpts.limit !== undefined && resultsOpts.limit > 0) {\n displayArticles = displayArticles.slice(0, resultsOpts.limit);\n }\n\n // Format output\n if (resultsOpts.json) {\n console.log(formatResultsJson(displayArticles));\n } else {\n if (!globalOpts.quiet) {\n console.log(formatResultsList(displayArticles, {\n sessionId,\n sessionName: session.name,\n total,\n offset,\n filteredFrom,\n showAbstract: resultsOpts.showAbstract,\n abstractLength: resultsOpts.abstractLength,\n }));\n }\n }\n\n process.exitCode = EXIT_CODES.SUCCESS;\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error(\n 'Error:',\n error instanceof Error ? error.message : error\n );\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n }\n }\n );\n\n // Register diff command\n program\n .command('diff')\n .description('Compare results between two sessions')\n .argument('<session-id-1>', 'first session ID')\n .argument('<session-id-2>', 'second session ID')\n .option('--show <section>', 'show only specific section: added, removed, or common')\n .option('--json', 'output as JSON')\n .option('--no-query-diff', 'hide query changes section')\n .addHelpText('after', `\nExamples:\n $ search-hub diff session-v1 session-v2 # Compare two sessions\n $ search-hub diff session-v1 session-v2 --show added # Show only added articles\n $ search-hub diff session-v1 session-v2 --show removed # Show only removed articles\n $ search-hub diff session-v1 session-v2 --json # JSON output for scripting\n $ search-hub diff session-v1 session-v2 --no-query-diff # Hide query changes\n\nQuery Refinement Workflow:\n 1. Search with broad query: search-hub search v1.yaml --max-results 100\n 2. Create refined query: cp v1.yaml v2.yaml && edit v2.yaml\n 3. Search with refined query: search-hub search v2.yaml --max-results 100\n 4. Compare results: search-hub diff <session-v1> <session-v2> --show removed\n 5. Review excluded articles to verify refinement quality`)\n .action(\n async (\n sessionId1: string,\n sessionId2: string,\n options?: { show?: string; json?: boolean; queryDiff?: boolean }\n ) => {\n const globalOpts = program.opts() as GlobalOptions;\n try {\n // Validate --show option\n const validShowValues: ShowFilter[] = ['added', 'removed', 'common'];\n let showFilter: ShowFilter | undefined;\n if (options?.show) {\n if (!validShowValues.includes(options.show as ShowFilter)) {\n if (!globalOpts.quiet) {\n console.error(`Error: Invalid --show value: ${options.show}. Valid values are: ${validShowValues.join(', ')}`);\n }\n process.exitCode = EXIT_CODES.GENERAL_ERROR;\n return;\n }\n showFilter = options.show as ShowFilter;\n }\n\n const sessionsDir = await getSessionsDir(globalOpts);\n const noQueryDiff = options?.queryDiff === false;\n\n // Load both sessions\n let session1, session2;\n try {\n session1 = await loadSession(sessionId1, sessionsDir);\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error(\n `Error loading session 1: ${error instanceof Error ? error.message : 'Failed to load session'}`\n );\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n return;\n }\n\n try {\n session2 = await loadSession(sessionId2, sessionsDir);\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error(\n `Error loading session 2: ${error instanceof Error ? error.message : 'Failed to load session'}`\n );\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n return;\n }\n\n // Collect articles from both sessions\n const articles1 = await loadSessionArticles(session1, sessionId1, sessionsDir);\n const articles2 = await loadSessionArticles(session2, sessionId2, sessionsDir);\n\n // Deduplicate each session's articles before diffing\n const dedup1 = deduplicateArticles(articles1);\n const dedup2 = deduplicateArticles(articles2);\n\n // Compute article diff\n const diff = computeDiff(dedup1.articles, dedup2.articles);\n\n // Load and compute query diff (unless disabled)\n let queryDiff;\n let showQueryDiffPlaceholder = false;\n if (!noQueryDiff) {\n const query1 = await loadSessionQuery(sessionId1, sessionsDir);\n const query2 = await loadSessionQuery(sessionId2, sessionsDir);\n if (query1 && query2) {\n queryDiff = computeQueryDiff(query1, query2);\n } else {\n // At least one query is missing - show placeholder\n showQueryDiffPlaceholder = true;\n }\n }\n\n // Format and output\n const formatOptions = { queryDiff, noQueryDiff, showQueryDiffPlaceholder };\n if (options?.json) {\n console.log(formatDiffJson(diff, sessionId1, sessionId2, showFilter, formatOptions));\n } else {\n if (!globalOpts.quiet) {\n console.log(formatDiff(diff, sessionId1, sessionId2, showFilter, formatOptions));\n\n // Show suggestions\n const suggestion = formatSuggestion(getSuggestion({\n command: 'diff',\n sessionId: sessionId2,\n diffSession1Id: sessionId1,\n diffAddedCount: diff.added.length,\n diffRemovedCount: diff.removed.length,\n }));\n if (suggestion) {\n console.log(suggestion);\n }\n }\n }\n\n process.exitCode = EXIT_CODES.SUCCESS;\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error(\n 'Error:',\n error instanceof Error ? error.message : error\n );\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n }\n }\n );\n\n // Register check command\n program\n .command('check')\n .description('Verify coverage of known articles against session results')\n .argument('<session-id>', 'session ID to check against')\n .option('--file <path>', 'file with identifiers (one per line)')\n .option('--doi <ids>', 'comma-separated DOIs to check')\n .option('--pmid <ids>', 'comma-separated PMIDs to check')\n .option('--json', 'output as JSON')\n .option('--missing-only', 'show only missing identifiers')\n .addHelpText('after', `\nExamples:\n $ search-hub check SESSION --file known-dois.txt # Check from file\n $ search-hub check SESSION --doi \"10.1001/jama.2023.12345\" # Check single DOI\n $ search-hub check SESSION --pmid \"37654321,36543210\" # Check PMIDs\n $ search-hub check SESSION --file refs.txt --json # JSON output\n $ search-hub check SESSION --file refs.txt --missing-only # Only missing\n\nInput file format (one identifier per line):\n 10.1001/jama.2023.12345 DOI (starts with \"10.\")\n 37654321 PMID (numeric only)\n DOI:10.1038/s41586-023-xxxxx DOI (explicit prefix)\n PMID:36543210 PMID (explicit prefix)\n arxiv:2301.12345 arXiv ID (explicit prefix)\n # comment Comments and empty lines ignored`)\n .action(\n async (\n sessionId: string,\n options?: { file?: string; doi?: string; pmid?: string; json?: boolean; missingOnly?: boolean }\n ) => {\n const globalOpts = program.opts() as GlobalOptions;\n try {\n // Parse identifiers from input sources\n let identifiers;\n let source: string;\n\n if (options?.file) {\n const filePath = expandPath(options.file);\n let content: string;\n try {\n content = await readFile(filePath, 'utf-8');\n } catch {\n if (!globalOpts.quiet) {\n console.error(`Error: File not found: ${filePath}`);\n }\n process.exitCode = EXIT_CODES.GENERAL_ERROR;\n return;\n }\n try {\n identifiers = parseIdentifierFile(content);\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error(`Error: ${error instanceof Error ? error.message : 'Failed to parse identifier file'}`);\n }\n process.exitCode = EXIT_CODES.GENERAL_ERROR;\n return;\n }\n source = options.file;\n } else if (options?.doi || options?.pmid) {\n const lines: string[] = [];\n if (options.doi) {\n lines.push(...options.doi.split(',').map(d => d.trim()).filter(Boolean));\n }\n if (options.pmid) {\n lines.push(...options.pmid.split(',').map(p => `PMID:${p.trim()}`).filter(Boolean));\n }\n identifiers = parseIdentifierFile(lines.join('\\n'));\n source = 'command line';\n } else {\n if (!globalOpts.quiet) {\n console.error('Error: Provide --file, --doi, or --pmid');\n }\n process.exitCode = EXIT_CODES.GENERAL_ERROR;\n return;\n }\n\n if (identifiers.length === 0) {\n if (!globalOpts.quiet) {\n console.error('Error: No identifiers found in input');\n }\n process.exitCode = EXIT_CODES.GENERAL_ERROR;\n return;\n }\n\n // Load session\n const sessionsDir = await getSessionsDir(globalOpts);\n let session;\n try {\n session = await loadSession(sessionId, sessionsDir);\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error(\n `Error: ${error instanceof Error ? error.message : 'Failed to load session'}`\n );\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n return;\n }\n\n // Load articles and check coverage\n const articles = await loadSessionArticles(session, sessionId, sessionsDir);\n const result = checkCoverage(articles, identifiers);\n\n // Format output\n if (options?.json) {\n console.log(formatCheckResultJson(result, { sessionId, source }));\n } else {\n if (!globalOpts.quiet) {\n console.log(formatCheckResult(result, {\n sessionId,\n source,\n missingOnly: options?.missingOnly,\n }));\n }\n }\n\n process.exitCode = EXIT_CODES.SUCCESS;\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error(\n 'Error:',\n error instanceof Error ? error.message : error\n );\n }\n process.exitCode = EXIT_CODES.GENERAL_ERROR;\n }\n }\n );\n\n // Register merge command\n program\n .command('merge')\n .description('Merge results from multiple search sessions')\n .argument('<session-ids...>', 'two or more session IDs to merge')\n .option('--name <string>', 'name for merged session')\n .option('--dry-run', 'show what would be merged without creating session')\n .option('--json', 'output as JSON')\n .addHelpText('after', `\nExamples:\n $ search-hub merge session-v4 session-v9 # Merge two sessions\n $ search-hub merge session-v4 session-v9 --name combined # Merge with custom name\n $ search-hub merge session-a session-b session-c # Merge three sessions\n $ search-hub merge session-v4 session-v9 --dry-run # Preview merge`)\n .action(\n async (\n sessionIds: string[],\n options?: { name?: string; dryRun?: boolean; json?: boolean }\n ) => {\n const globalOpts = program.opts() as GlobalOptions;\n try {\n if (sessionIds.length < 2) {\n if (!globalOpts.quiet) {\n console.error('Error: At least two session IDs are required for merge');\n }\n process.exitCode = EXIT_CODES.GENERAL_ERROR;\n return;\n }\n\n const sessionsDir = await getSessionsDir(globalOpts);\n\n // Load all source sessions\n const sessions = new Map<string, ReturnType<typeof loadSession> extends Promise<infer T> ? T : never>();\n for (const sessionId of sessionIds) {\n try {\n const session = await loadSession(sessionId, sessionsDir);\n sessions.set(sessionId, session);\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error(\n `Error loading session '${sessionId}': ${error instanceof Error ? error.message : 'Failed to load session'}`\n );\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n return;\n }\n }\n\n // Validate sources\n const validation = validateMergeSources(sessions);\n if (!validation.valid) {\n if (!globalOpts.quiet) {\n console.error(`Error: ${validation.error}`);\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n return;\n }\n\n // Load articles from all sessions\n const sessionArticles = new Map<string, Awaited<ReturnType<typeof loadSessionArticles>>>();\n for (const [sessionId, session] of sessions) {\n const articles = await loadSessionArticles(session, sessionId, sessionsDir);\n sessionArticles.set(sessionId, articles);\n }\n\n // Merge articles\n const mergeResult = mergeArticles(sessionArticles);\n\n // Build output data\n const sources = [...sessions.entries()].map(([id, session]) => ({\n id,\n name: session.name,\n count: mergeResult.perSession.get(id) ?? 0,\n }));\n\n const byProviderCounts = new Map<string, number>();\n for (const [provider, articles] of mergeResult.byProvider) {\n byProviderCounts.set(provider, articles.length);\n }\n\n // Auto-generate name if not provided\n const firstSession = sessions.values().next().value;\n const mergeName = options?.name ?? (firstSession ? firstSession.name + '-merged' : 'merged');\n\n if (options?.dryRun) {\n // Dry run - show preview without creating session\n const outputData = {\n sessionId: '(dry-run)',\n totalBefore: mergeResult.totalBefore,\n totalAfter: mergeResult.totalAfter,\n duplicatesRemoved: mergeResult.duplicatesRemoved,\n sources,\n byProvider: byProviderCounts,\n };\n if (options.json) {\n console.log(formatSessionMergeJson(outputData));\n } else if (!globalOpts.quiet) {\n console.log(formatSessionMergeOutput(outputData));\n }\n process.exitCode = EXIT_CODES.SUCCESS;\n return;\n }\n\n // Create merged session\n const sessionSources = [...sessions.entries()].map(([id, session]) => ({\n id,\n name: session.name,\n }));\n\n const mergedSession = await createMergedSession({\n name: mergeName,\n sources: sessionSources,\n byProvider: mergeResult.byProvider,\n totalRetrieved: mergeResult.totalAfter,\n sessionsDir,\n sourceSessionIds: sessionIds,\n });\n\n // Format output\n const outputData = {\n sessionId: mergedSession.id,\n totalBefore: mergeResult.totalBefore,\n totalAfter: mergeResult.totalAfter,\n duplicatesRemoved: mergeResult.duplicatesRemoved,\n sources,\n byProvider: byProviderCounts,\n };\n\n if (options?.json) {\n console.log(formatSessionMergeJson(outputData));\n } else if (!globalOpts.quiet) {\n console.log(formatSessionMergeOutput(outputData));\n\n // Show suggestions\n const suggestion = getSuggestion({\n command: 'merge',\n sessionId: mergedSession.id,\n });\n const suggestionText = formatSuggestion(suggestion);\n if (suggestionText) {\n console.log(suggestionText);\n }\n }\n\n process.exitCode = EXIT_CODES.SUCCESS;\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error(\n 'Error:',\n error instanceof Error ? error.message : error\n );\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n }\n }\n );\n\n // Register register command\n program\n .command('register')\n .description('Register results with reference-manager')\n .argument('<session-id>', 'session ID to register')\n .option('--db <providers>', 'register only specific database(s)')\n .option('--dry-run', 'show what would be registered without executing', false)\n .option('--with-abstracts', 'also update abstracts via ref update', false)\n .option('--reviewed', 'register only articles with finalDecision=include', false)\n .option('--all', 'register all articles (ignore reviews)', false)\n .option('--force', 'skip confirmation prompts', false)\n .option('--no-attach-fulltext', 'skip automatic fulltext attachment')\n .addHelpText('after', `\nExamples:\n $ search-hub register SESSION_ID # Register all results\n $ search-hub register SESSION_ID --with-abstracts\n $ search-hub register SESSION_ID --dry-run # Preview only\n $ search-hub register SESSION_ID --no-attach-fulltext # Skip fulltext attachment\n\nWith review workflow:\n $ search-hub register SESSION_ID --reviewed # Register only included articles\n $ search-hub register SESSION_ID --all # Register all (ignore reviews)`)\n .action(\n async (\n sessionId: string,\n options?: {\n db?: string;\n dryRun?: boolean;\n withAbstracts?: boolean;\n reviewed?: boolean;\n all?: boolean;\n force?: boolean;\n attachFulltext?: boolean;\n }\n ) => {\n const globalOpts = program.opts() as GlobalOptions;\n try {\n // Parse and validate options\n const registerOpts = parseRegisterOptions(sessionId, {\n db: options?.db,\n dryRun: options?.dryRun,\n withAbstracts: options?.withAbstracts,\n reviewed: options?.reviewed,\n all: options?.all,\n force: options?.force,\n });\n\n const validation = validateRegisterInput(registerOpts);\n if (!validation.valid) {\n if (!globalOpts.quiet) {\n console.error(`Error: ${validation.error}`);\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n return;\n }\n\n // Check if ref command is available\n const refAvailable = await checkRefAvailable();\n if (!refAvailable && !registerOpts.dryRun) {\n if (!globalOpts.quiet) {\n console.error('Error: reference-manager (ref) command not found.\\n');\n console.error('reference-manager is required to register search results.');\n console.error('Would you like to install it now? (npm i -g @ncukondo/reference-manager) [Y/n]: ');\n }\n\n // For non-interactive mode, suggest installation\n const npmAvailable = await checkNpmAvailable();\n if (!npmAvailable) {\n if (!globalOpts.quiet) {\n console.error('\\nError: npm command not found.');\n console.error('Please install Node.js first: https://nodejs.org/');\n }\n process.exitCode = EXIT_CODES.GENERAL_ERROR;\n return;\n }\n\n // Try to install\n try {\n if (!globalOpts.quiet) {\n console.log('\\nInstalling reference-manager...');\n }\n await installRefManager();\n if (!globalOpts.quiet) {\n console.log('✓ reference-manager installed successfully.\\n');\n }\n } catch (installError) {\n if (!globalOpts.quiet) {\n console.error(\n `\\nFailed to install reference-manager: ${installError instanceof Error ? installError.message : 'Unknown error'}`\n );\n }\n process.exitCode = EXIT_CODES.GENERAL_ERROR;\n return;\n }\n }\n\n const sessionsDir = await getSessionsDir(globalOpts);\n\n // Load session\n let session;\n try {\n session = await loadSession(sessionId, sessionsDir);\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error(\n `Error: ${error instanceof Error ? error.message : 'Failed to load session'}`\n );\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n return;\n }\n\n // Check for review file and handle --reviewed/--all flags\n const reviewExists = await hasReviewFile(sessionId, sessionsDir);\n let articles: import('../providers/base/types.js').Article[];\n\n if (registerOpts.reviewed) {\n // --reviewed: only include reviewed articles\n if (!reviewExists) {\n if (!globalOpts.quiet) {\n console.error('Error: No reviews.yaml found for this session.');\n console.error('Run \"search-hub review init --session ' + sessionId + '\" first.');\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n return;\n }\n\n const summary = await getReviewSummary(sessionId, sessionsDir);\n if (summary.included === 0) {\n if (!globalOpts.quiet) {\n console.error(formatNoIncludedArticlesError(summary, sessionId));\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n return;\n }\n\n // Warn about pending articles (unless --force or --dry-run)\n if (summary.pending > 0 && !registerOpts.force && !registerOpts.dryRun) {\n if (!globalOpts.quiet) {\n console.log(formatPendingWarning(summary));\n }\n // Wait for user confirmation\n const confirmed = await confirmPrompt();\n if (!confirmed) {\n process.exitCode = EXIT_CODES.SUCCESS;\n return;\n }\n }\n\n articles = await getIncludedArticles(sessionId, sessionsDir);\n } else if (reviewExists && !registerOpts.all) {\n // reviews.yaml exists but no flag specified\n const summary = await getReviewSummary(sessionId, sessionsDir);\n if (!globalOpts.quiet) {\n console.log(formatReviewRequiredMessage(summary, sessionId));\n }\n process.exitCode = EXIT_CODES.GENERAL_ERROR;\n return;\n } else {\n // --all or no reviews.yaml: collect all articles from result files\n if (reviewExists && !globalOpts.quiet) {\n const summary = await getReviewSummary(sessionId, sessionsDir);\n console.log(formatIgnoringReviewsNote(summary.total));\n }\n articles = await loadSessionArticles(session, sessionId, sessionsDir, registerOpts.providers);\n }\n\n // Dry run mode\n if (registerOpts.dryRun) {\n if (!globalOpts.quiet) {\n console.log(formatRegisterDryRunOutput(articles));\n }\n process.exitCode = EXIT_CODES.SUCCESS;\n return;\n }\n\n // Register articles\n if (!globalOpts.quiet) {\n console.log(`Registering ${articles.length} references to reference-manager...`);\n }\n\n const sessionDir = join(sessionsDir, sessionId);\n const registerOptions: import('../integration/register.js').RegisterOptions = {\n sessionId,\n sessionDir,\n withAbstracts: registerOpts.withAbstracts,\n ...(options?.attachFulltext === false ? { noAttachFulltext: true } : {}),\n };\n if (!globalOpts.quiet) {\n registerOptions.onProgress = (current, total) => {\n process.stdout.write(`\\rProgress: ${current}/${total}`);\n };\n }\n const record = await registerArticles(articles, registerOptions);\n\n // Save registration record\n await saveRegistrationRecord(sessionDir, record);\n\n if (!globalOpts.quiet) {\n console.log('\\n');\n console.log(formatRegistrationSummary(record.summary));\n\n // Show fulltext attach summary\n if (record.fulltext) {\n const ft = record.fulltext.summary;\n console.log('\\nFulltext attachment results:');\n if (ft.attached > 0) {\n const totalFiles = record.fulltext.attached.reduce((sum, a) => sum + a.files.length, 0);\n console.log(` ✓ ${ft.attached} articles attached (${totalFiles} files)`);\n }\n if (ft.skipped > 0) {\n console.log(` ⚠ ${ft.skipped} skipped`);\n }\n if (ft.failed > 0) {\n console.log(` ✗ ${ft.failed} failed`);\n }\n }\n\n console.log(`\\nResults saved to: ${join(sessionDir, 'registration.json')}`);\n\n // Show next step suggestions\n const suggestion = formatSuggestion(getSuggestion({\n command: 'register',\n sessionId,\n hasReviews: reviewExists,\n }));\n if (suggestion) console.log(suggestion);\n }\n\n process.exitCode = EXIT_CODES.SUCCESS;\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error(\n 'Error:',\n error instanceof Error ? error.message : error\n );\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n }\n }\n );\n\n // Register review command group\n const reviewCommand = program\n .command('review')\n .description('Article review workflow for systematic literature review')\n .addHelpText('after', `\nExamples:\n $ search-hub review init --session SESSION_ID # Initialize reviews.yaml\n $ search-hub review status --session SESSION_ID # Show review progress\n $ search-hub review list --session SESSION_ID --filter pending # List articles\n $ search-hub review extract --session SESSION_ID --name title-screening # Extract for review\n $ search-hub review merge --session SESSION_ID --name title-screening # Merge reviews\n $ search-hub review export --session SESSION_ID --only included -o included.yaml`);\n\n reviewCommand\n .command('init')\n .description('Generate reviews.yaml from deduplicated search results')\n .requiredOption('--session <id>', 'session ID')\n .option('-f, --force', 'overwrite existing reviews.yaml', false)\n .action(async (options: { session: string; force: boolean }) => {\n const globalOpts = program.opts() as GlobalOptions;\n try {\n const sessionsDir = await getSessionsDir(globalOpts);\n const initOptions: ReviewInitOptions = {\n sessionId: options.session,\n ...(options.force && { force: options.force }),\n };\n const result = await executeReviewInit(initOptions, sessionsDir);\n if (!globalOpts.quiet) {\n console.log(`Created ${result.reviewsPath}`);\n console.log(` Articles: ${result.articleCount}`);\n if (result.duplicatesRemoved > 0) {\n console.log(` Duplicates removed: ${result.duplicatesRemoved}`);\n }\n }\n process.exitCode = EXIT_CODES.SUCCESS;\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error('Error:', error instanceof Error ? error.message : error);\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n }\n });\n\n reviewCommand\n .command('status')\n .description('Show review progress summary')\n .requiredOption('--session <id>', 'session ID')\n .option('--json', 'output as JSON')\n .action(async (options: { session: string; json?: boolean }) => {\n const globalOpts = program.opts() as GlobalOptions;\n try {\n const sessionsDir = await getSessionsDir(globalOpts);\n const statusOptions: ReviewStatusOptions = {\n sessionId: options.session,\n };\n const result = await executeReviewStatus(statusOptions, sessionsDir);\n if (!globalOpts.quiet) {\n if (options.json) {\n console.log(JSON.stringify(result, null, 2));\n } else {\n console.log(formatStatusOutput(result));\n const suggestion = formatSuggestion(getSuggestion({\n command: 'review status',\n sessionId: options.session,\n reviewStatus: result,\n }));\n if (suggestion) console.log(suggestion);\n }\n }\n process.exitCode = EXIT_CODES.SUCCESS;\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error('Error:', error instanceof Error ? error.message : error);\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n }\n });\n\n reviewCommand\n .command('list')\n .description('List articles with optional filtering')\n .requiredOption('--session <id>', 'session ID')\n .option('--filter <type>', 'filter by status: pending, incomplete, uncertain, agreed-include, agreed-exclude, conflicting, finalized, all', 'all')\n .option('--json', 'output as JSON')\n .action(async (options: { session: string; filter?: string; json?: boolean }) => {\n const globalOpts = program.opts() as GlobalOptions;\n try {\n const validFilters: ListFilter[] = ['pending', 'incomplete', 'uncertain', 'agreed-include', 'agreed-exclude', 'conflicting', 'finalized', 'all'];\n const filter = (options.filter ?? 'all') as ListFilter;\n if (!validFilters.includes(filter)) {\n if (!globalOpts.quiet) {\n console.error(`Error: Invalid filter '${options.filter}'. Valid values: ${validFilters.join(', ')}`);\n }\n process.exitCode = EXIT_CODES.GENERAL_ERROR;\n return;\n }\n\n const sessionsDir = await getSessionsDir(globalOpts);\n const listOptions: ReviewListOptions = {\n sessionId: options.session,\n filter,\n };\n const result = await executeReviewList(listOptions, sessionsDir);\n if (!globalOpts.quiet) {\n if (options.json) {\n console.log(JSON.stringify(result, null, 2));\n } else {\n console.log(formatListOutput(result));\n }\n }\n process.exitCode = EXIT_CODES.SUCCESS;\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error('Error:', error instanceof Error ? error.message : error);\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n }\n });\n\n reviewCommand\n .command('extract')\n .description('Extract subset to for-review/<name>/review.yaml for distributed review')\n .requiredOption('--session <id>', 'session ID')\n .requiredOption('--name <name>', 'name for the review subset (output: for-review/<name>/review.yaml)')\n .option('--filter <types>', 'filter by status (comma-separated): pending, incomplete, uncertain, agreed-include, agreed-exclude, conflicting, finalized')\n .option('--sort <method>', 'sort method: year, title, random, none', 'none')\n .option('--limit <n>', 'limit number of articles')\n .option('--offset <n>', 'skip first n articles')\n .option('--seed <n>', 'random seed for reproducible sorting')\n .option('--basis <type>', 'basis for review: title, abstract, or fulltext')\n .option('--reviewer <id>', 'reviewer identifier (e.g., \"ai:claude\")')\n .option('--finalize', 'extract for final decision (includes reviewHistory and finalDecision)')\n .action(async (options: {\n session: string;\n name: string;\n filter?: string;\n sort?: string;\n limit?: string;\n offset?: string;\n seed?: string;\n basis?: string;\n reviewer?: string;\n finalize?: boolean;\n }) => {\n const globalOpts = program.opts() as GlobalOptions;\n try {\n const validSorts: SortOption[] = ['year', 'title', 'random', 'none'];\n const sort = (options.sort ?? 'none') as SortOption;\n if (!validSorts.includes(sort)) {\n if (!globalOpts.quiet) {\n console.error(`Error: Invalid sort '${options.sort}'. Valid values: ${validSorts.join(', ')}`);\n }\n process.exitCode = EXIT_CODES.GENERAL_ERROR;\n return;\n }\n\n const sessionsDir = await getSessionsDir(globalOpts);\n const extractOptions: ReviewExtractOptions = {\n sessionId: options.session,\n name: options.name,\n sort,\n };\n\n if (options.filter) {\n extractOptions.filter = options.filter.split(',').map(s => s.trim()) as ReviewStatus[];\n }\n if (options.limit) {\n extractOptions.limit = parseInt(options.limit, 10);\n }\n if (options.offset) {\n extractOptions.offset = parseInt(options.offset, 10);\n }\n if (options.seed) {\n extractOptions.seed = parseInt(options.seed, 10);\n }\n\n // Reviewer is required for all extract modes\n if (!options.reviewer) {\n if (!globalOpts.quiet) {\n console.error('Error: --reviewer is required');\n }\n process.exitCode = EXIT_CODES.GENERAL_ERROR;\n return;\n }\n extractOptions.reviewer = options.reviewer;\n\n // Handle basis option\n if (options.basis) {\n const validBasis = ['title', 'abstract', 'fulltext'];\n if (!validBasis.includes(options.basis)) {\n if (!globalOpts.quiet) {\n console.error(`Error: Invalid basis '${options.basis}'. Valid values: ${validBasis.join(', ')}`);\n }\n process.exitCode = EXIT_CODES.GENERAL_ERROR;\n return;\n }\n extractOptions.basis = options.basis as 'title' | 'abstract' | 'fulltext';\n }\n\n if (options.finalize) {\n extractOptions.finalize = true;\n }\n\n const result = await executeReviewExtract(extractOptions, sessionsDir);\n if (!globalOpts.quiet) {\n console.log(`Extracted ${result.extractedCount} of ${result.totalMatching} articles to ${result.outputPath}`);\n const suggestion = formatSuggestion(getSuggestion({\n command: 'review extract',\n sessionId: options.session,\n extractName: options.name,\n extractedCount: result.extractedCount,\n totalMatching: result.totalMatching,\n extractLimit: extractOptions.limit,\n extractOffset: extractOptions.offset,\n }));\n if (suggestion) console.log(suggestion);\n }\n process.exitCode = EXIT_CODES.SUCCESS;\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error('Error:', error instanceof Error ? error.message : error);\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n }\n });\n\n reviewCommand\n .command('merge')\n .description('Merge edited file back into main reviews.yaml')\n .requiredOption('--session <id>', 'session ID')\n .requiredOption('--name <name>', 'name of the review subset to merge (reads from for-review/<name>/review.yaml)')\n .option('--dry-run', 'show changes without applying', false)\n .action(async (options: { session: string; name: string; dryRun: boolean }) => {\n const globalOpts = program.opts() as GlobalOptions;\n try {\n const sessionsDir = await getSessionsDir(globalOpts);\n const mergeOptions: ReviewMergeOptions = {\n sessionId: options.session,\n name: options.name,\n ...(options.dryRun && { dryRun: options.dryRun }),\n };\n const result = await executeReviewMerge(mergeOptions, sessionsDir);\n if (!globalOpts.quiet) {\n console.log(formatMergeOutput(result, options.dryRun));\n if (!options.dryRun) {\n const statusResult = await executeReviewStatus({ sessionId: options.session }, sessionsDir);\n const suggestion = formatSuggestion(getSuggestion({\n command: 'review merge',\n sessionId: options.session,\n reviewStatus: statusResult,\n }));\n if (suggestion) console.log(suggestion);\n }\n }\n process.exitCode = EXIT_CODES.SUCCESS;\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error('Error:', error instanceof Error ? error.message : error);\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n }\n });\n\n reviewCommand\n .command('mark')\n .description('Mark decisions in work files')\n .requiredOption('--file <path>', 'path to work file')\n .option('--id <id>', 'article ID to mark')\n .option('--decision <decision>', 'decision: include, exclude, or uncertain')\n .option('--comment <text>', 'optional comment')\n .action(async (options: {\n file: string;\n id?: string;\n decision?: string;\n comment?: string;\n }) => {\n const globalOpts = program.opts() as GlobalOptions;\n try {\n // Validate decision if provided\n const validDecisions = ['include', 'exclude', 'uncertain'];\n if (options.decision && !validDecisions.includes(options.decision)) {\n if (!globalOpts.quiet) {\n console.error(`Error: Invalid decision '${options.decision}'. Valid values: ${validDecisions.join(', ')}`);\n }\n process.exitCode = EXIT_CODES.GENERAL_ERROR;\n return;\n }\n\n // Validate required options\n if (!options.id || !options.decision) {\n if (!globalOpts.quiet) {\n console.error('Error: --id and --decision must be specified');\n }\n process.exitCode = EXIT_CODES.GENERAL_ERROR;\n return;\n }\n\n const markOptions: ReviewMarkOptions = {\n file: options.file,\n id: options.id,\n decision: options.decision as 'include' | 'exclude' | 'uncertain',\n };\n\n if (options.comment) markOptions.comment = options.comment;\n\n const result = await executeReviewMark(markOptions);\n if (!globalOpts.quiet) {\n console.log(`Marked ${result.marked} article(s)`);\n for (const warning of result.warnings) {\n console.warn(`Warning: ${warning}`);\n }\n }\n process.exitCode = EXIT_CODES.SUCCESS;\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error('Error:', error instanceof Error ? error.message : error);\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n }\n });\n\n reviewCommand\n .command('export')\n .description('Export articles based on final decision')\n .requiredOption('--session <id>', 'session ID')\n .requiredOption('--only <filter>', 'export filter: included or excluded')\n .requiredOption('-o, --output <path>', 'output file path')\n .option('--format <fmt>', 'output format: yaml, json, jsonl', 'yaml')\n .action(async (options: {\n session: string;\n only: string;\n output: string;\n format?: string;\n }) => {\n const globalOpts = program.opts() as GlobalOptions;\n try {\n const validOnlyValues: ReviewExportFilter[] = ['included', 'excluded'];\n const only = options.only as ReviewExportFilter;\n if (!validOnlyValues.includes(only)) {\n if (!globalOpts.quiet) {\n console.error(`Error: Invalid --only value '${options.only}'. Valid values: ${validOnlyValues.join(', ')}`);\n }\n process.exitCode = EXIT_CODES.GENERAL_ERROR;\n return;\n }\n\n const validFormats: ReviewExportFormat[] = ['yaml', 'json', 'jsonl'];\n const format = (options.format ?? 'yaml') as ReviewExportFormat;\n if (!validFormats.includes(format)) {\n if (!globalOpts.quiet) {\n console.error(`Error: Invalid format '${options.format}'. Valid values: ${validFormats.join(', ')}`);\n }\n process.exitCode = EXIT_CODES.GENERAL_ERROR;\n return;\n }\n\n const sessionsDir = await getSessionsDir(globalOpts);\n const exportOptions: ReviewExportOptions = {\n sessionId: options.session,\n only,\n output: options.output,\n format,\n };\n const result = await executeReviewExport(exportOptions, sessionsDir);\n if (!globalOpts.quiet) {\n console.log(formatExportOutput(result));\n }\n process.exitCode = EXIT_CODES.SUCCESS;\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error('Error:', error instanceof Error ? error.message : error);\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n }\n });\n\n reviewCommand\n .command('finalize')\n .description('Auto-set finalDecision for articles with reviewer consensus')\n .requiredOption('--session <id>', 'session ID')\n .option('--dry-run', 'preview without changes', false)\n .option('--min-reviewers <n>', 'minimum agreeing reviewers needed', '1')\n .option('--decision <type>', 'only finalize this decision type (include or exclude)')\n .action(async (options: { session: string; dryRun: boolean; minReviewers: string; decision?: string }) => {\n const globalOpts = program.opts() as GlobalOptions;\n try {\n const sessionsDir = await getSessionsDir(globalOpts);\n if (options.decision && options.decision !== 'include' && options.decision !== 'exclude') {\n if (!globalOpts.quiet) {\n console.error(`Error: --decision must be \"include\" or \"exclude\", got \"${options.decision}\"`);\n }\n process.exitCode = EXIT_CODES.GENERAL_ERROR;\n return;\n }\n const finalizeOptions: ReviewFinalizeOptions = {\n sessionId: options.session,\n ...(options.dryRun && { dryRun: options.dryRun }),\n ...(options.decision && { decision: options.decision as 'include' | 'exclude' }),\n };\n const minReviewers = parseInt(options.minReviewers, 10);\n if (!Number.isNaN(minReviewers) && minReviewers > 1) {\n finalizeOptions.minReviewers = minReviewers;\n }\n const result = await executeReviewFinalize(finalizeOptions, sessionsDir);\n if (!globalOpts.quiet) {\n console.log(formatFinalizeOutput(result, {\n dryRun: options.dryRun,\n ...(finalizeOptions.decision && { decision: finalizeOptions.decision }),\n }));\n if (!options.dryRun) {\n const statusResult = await executeReviewStatus({ sessionId: options.session }, sessionsDir);\n const suggestion = formatSuggestion(getSuggestion({\n command: 'review finalize',\n sessionId: options.session,\n reviewStatus: statusResult,\n }));\n if (suggestion) console.log(suggestion);\n }\n }\n process.exitCode = EXIT_CODES.SUCCESS;\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error('Error:', error instanceof Error ? error.message : error);\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n }\n });\n\n // Register notes command group\n const notesCommand = program\n .command('notes')\n .description('Manage session notes and assessments')\n .addHelpText('after', `\nExamples:\n $ search-hub notes list SESSION_ID # List notes for a session\n $ search-hub notes add SESSION_ID \"my note\" # Add a note\n $ search-hub notes add SESSION_ID --file assessment.md # Add from file\n $ search-hub notes assess SESSION_ID --precision \"~54%\" --verdict good --comment \"Good results\"\n $ search-hub notes list --all # Show notes from all sessions`);\n\n notesCommand\n .command('list')\n .description('List notes for a session or all sessions')\n .argument('[session-id]', 'session ID')\n .option('--all', 'show notes from all sessions')\n .option('--json', 'output as JSON')\n .action(async (sessionId?: string, options?: { all?: boolean; json?: boolean }) => {\n const globalOpts = program.opts() as GlobalOptions;\n try {\n const sessionsDir = await getSessionsDir(globalOpts);\n const formatOpts = { json: options?.json ?? false };\n\n if (options?.all) {\n // Cross-session notes view\n const summaries = await listSessions(sessionsDir);\n const allNotes: SessionNotes[] = [];\n\n for (const summary of summaries) {\n const sessionNotesDir = join(sessionsDir, summary.id);\n const notes = await loadNotes(sessionNotesDir);\n allNotes.push({\n sessionId: summary.id,\n sessionName: summary.name,\n notes,\n });\n }\n\n if (!globalOpts.quiet) {\n console.log(formatAllSessionNotes(allNotes, formatOpts));\n }\n process.exitCode = EXIT_CODES.SUCCESS;\n return;\n }\n\n if (!sessionId) {\n if (!globalOpts.quiet) {\n console.error('Error: session-id is required (or use --all)');\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n return;\n }\n\n if (!(await sessionExists(sessionId, sessionsDir))) {\n if (!globalOpts.quiet) {\n console.error(`Error: session '${sessionId}' not found`);\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n return;\n }\n\n // List notes for a specific session\n const sessionDir = join(sessionsDir, sessionId);\n const notes = await loadNotes(sessionDir);\n if (!globalOpts.quiet) {\n console.log(formatNotesList(notes, formatOpts));\n }\n process.exitCode = EXIT_CODES.SUCCESS;\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error(\n 'Error:',\n error instanceof Error ? error.message : error\n );\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n }\n });\n\n notesCommand\n .command('add')\n .description('Add a note to a session')\n .argument('<session-id>', 'session ID')\n .argument('[text]', 'note text')\n .option('--file <path>', 'read note text from a file instead')\n .action(async (sessionId: string, text?: string, options?: { file?: string }) => {\n const globalOpts = program.opts() as GlobalOptions;\n try {\n const sessionsDir = await getSessionsDir(globalOpts);\n const sessionDir = join(sessionsDir, sessionId);\n\n if (!(await sessionExists(sessionId, sessionsDir))) {\n if (!globalOpts.quiet) {\n console.error(`Error: session '${sessionId}' not found`);\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n return;\n }\n\n let noteText: string;\n if (options?.file) {\n noteText = (await readFile(options.file, 'utf-8')).trim();\n } else if (text) {\n noteText = text;\n } else {\n if (!globalOpts.quiet) {\n console.error('Error: note text or --file is required');\n }\n process.exitCode = EXIT_CODES.GENERAL_ERROR;\n return;\n }\n\n await addNote(sessionDir, noteText);\n\n if (!globalOpts.quiet) {\n console.log('Note added.');\n }\n process.exitCode = EXIT_CODES.SUCCESS;\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error(\n 'Error:',\n error instanceof Error ? error.message : error\n );\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n }\n });\n\n notesCommand\n .command('assess')\n .description('Add a structured assessment to a session')\n .argument('<session-id>', 'session ID')\n .option('--precision <value>', 'estimated precision (e.g., \"~54%\", \"15/28\")')\n .option('--verdict <value>', 'quality judgment: good, refine, reject')\n .option('--comment <text>', 'free text explanation')\n .action(async (sessionId: string, options?: { precision?: string; verdict?: string; comment?: string }) => {\n const globalOpts = program.opts() as GlobalOptions;\n try {\n if (!options?.precision && !options?.verdict && !options?.comment) {\n if (!globalOpts.quiet) {\n console.error('Error: at least one of --precision, --verdict, or --comment is required');\n }\n process.exitCode = EXIT_CODES.GENERAL_ERROR;\n return;\n }\n\n const sessionsDir = await getSessionsDir(globalOpts);\n const sessionDir = join(sessionsDir, sessionId);\n\n if (!(await sessionExists(sessionId, sessionsDir))) {\n if (!globalOpts.quiet) {\n console.error(`Error: session '${sessionId}' not found`);\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n return;\n }\n\n await addAssessment(sessionDir, {\n precision: options?.precision,\n verdict: options?.verdict,\n comment: options?.comment,\n });\n\n if (!globalOpts.quiet) {\n console.log('Assessment added.');\n }\n process.exitCode = EXIT_CODES.SUCCESS;\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error(\n 'Error:',\n error instanceof Error ? error.message : error\n );\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n }\n });\n\n // Register fulltext command group (init, sync, convert, check)\n registerFulltextCommands(program, getSessionsDir);\n\n return program;\n}\n\n/**\n * Main entry point for CLI execution.\n */\nexport async function main(): Promise<void> {\n const program = createProgram();\n await program.parseAsync(process.argv);\n}\n\n// Run main if executed directly\nconst currentFile = fileURLToPath(import.meta.url);\nconst executedFile = process.argv[1];\nif (executedFile) {\n if (realpathSync(executedFile) === realpathSync(currentFile)) {\n main().catch((error) => {\n console.error('Fatal error:', error);\n process.exit(EXIT_CODES.GENERAL_ERROR);\n });\n }\n}\n"],"names":["loadDotenv","config","result","outputData","formatSessionMergeJson","formatSessionMergeOutput","formatRegisterDryRunOutput","formatMergeOutput","notes"],"mappings":"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"}
|