@ncukondo/search-hub 0.19.0 → 0.20.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/related.d.ts +66 -0
- package/dist/cli/commands/related.d.ts.map +1 -0
- package/dist/cli/commands/related.js +161 -0
- package/dist/cli/commands/related.js.map +1 -0
- package/dist/cli/commands/review/extract.d.ts.map +1 -1
- package/dist/cli/commands/review/extract.js +15 -5
- package/dist/cli/commands/review/extract.js.map +1 -1
- package/dist/cli/commands/review/init.d.ts +2 -3
- package/dist/cli/commands/review/init.d.ts.map +1 -1
- package/dist/cli/commands/review/init.js +1 -0
- package/dist/cli/commands/review/init.js.map +1 -1
- package/dist/cli/commands/review/next-steps.d.ts +3 -0
- package/dist/cli/commands/review/next-steps.d.ts.map +1 -1
- package/dist/cli/commands/review/next-steps.js +53 -19
- package/dist/cli/commands/review/next-steps.js.map +1 -1
- package/dist/cli/commands/review/schema.d.ts +8 -0
- package/dist/cli/commands/review/schema.d.ts.map +1 -1
- package/dist/cli/commands/review/schema.js +3 -0
- package/dist/cli/commands/review/schema.js.map +1 -1
- package/dist/cli/commands/review/status.d.ts +3 -1
- package/dist/cli/commands/review/status.d.ts.map +1 -1
- package/dist/cli/commands/review/status.js +3 -1
- package/dist/cli/commands/review/status.js.map +1 -1
- package/dist/cli/commands/review/types.d.ts +2 -1
- package/dist/cli/commands/review/types.d.ts.map +1 -1
- package/dist/cli/commands/review/types.js.map +1 -1
- package/dist/cli/commands/search-executor.d.ts.map +1 -1
- package/dist/cli/commands/search-executor.js +3 -2
- package/dist/cli/commands/search-executor.js.map +1 -1
- package/dist/cli/commands/search.d.ts +2 -0
- package/dist/cli/commands/search.d.ts.map +1 -1
- package/dist/cli/commands/search.js +3 -0
- package/dist/cli/commands/search.js.map +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +128 -3
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/suggestions/rules.d.ts.map +1 -1
- package/dist/cli/suggestions/rules.js +19 -3
- package/dist/cli/suggestions/rules.js.map +1 -1
- package/dist/providers/arxiv/provider.d.ts.map +1 -1
- package/dist/providers/arxiv/provider.js +7 -4
- package/dist/providers/arxiv/provider.js.map +1 -1
- package/dist/providers/base/types.d.ts +8 -0
- package/dist/providers/base/types.d.ts.map +1 -1
- package/dist/providers/base/types.js.map +1 -1
- package/dist/providers/eric/provider.d.ts +3 -0
- package/dist/providers/eric/provider.d.ts.map +1 -1
- package/dist/providers/eric/provider.js +11 -0
- package/dist/providers/eric/provider.js.map +1 -1
- package/dist/providers/pubmed/client.d.ts +15 -1
- package/dist/providers/pubmed/client.d.ts.map +1 -1
- package/dist/providers/pubmed/client.js +64 -1
- package/dist/providers/pubmed/client.js.map +1 -1
- package/dist/providers/pubmed/index.d.ts +2 -2
- package/dist/providers/pubmed/index.d.ts.map +1 -1
- package/dist/providers/pubmed/parser.d.ts +8 -1
- package/dist/providers/pubmed/parser.d.ts.map +1 -1
- package/dist/providers/pubmed/parser.js +23 -1
- package/dist/providers/pubmed/parser.js.map +1 -1
- package/dist/providers/pubmed/provider.d.ts.map +1 -1
- package/dist/providers/pubmed/provider.js +8 -2
- package/dist/providers/pubmed/provider.js.map +1 -1
- package/dist/providers/pubmed/types.d.ts +29 -0
- package/dist/providers/pubmed/types.d.ts.map +1 -1
- package/dist/providers/scopus/client.d.ts +2 -0
- package/dist/providers/scopus/client.d.ts.map +1 -1
- package/dist/providers/scopus/client.js +3 -0
- package/dist/providers/scopus/client.js.map +1 -1
- package/dist/providers/scopus/provider.d.ts.map +1 -1
- package/dist/providers/scopus/provider.js +7 -1
- package/dist/providers/scopus/provider.js.map +1 -1
- package/dist/session/types.d.ts +13 -1
- package/dist/session/types.d.ts.map +1 -1
- package/dist/session/types.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"next-steps.js","sources":["../../../../src/cli/commands/review/next-steps.ts"],"sourcesContent":["/**\n * Dynamic next steps generation for review workflow.\n * Generates context-aware suggestions based on current article status distribution.\n */\n\nimport type { ReviewStatusResult } from './status.js';\nimport type { ReviewBasis } from './types.js';\nimport type { Suggestion, SuggestionResult } from '../../suggestions/types.js';\n\nexport interface ReviewNextStepsContext {\n sessionId: string;\n statusResult: ReviewStatusResult;\n /** Extract name for batch continuation */\n extractName?: string;\n /** Number of articles extracted in current batch */\n extractedCount?: number;\n /** Total articles matching the filter */\n totalMatching?: number;\n /** Limit used in extract */\n limit?: number;\n /** Offset used in extract */\n offset?: number;\n}\n\nexport interface BatchContinuationParams {\n sessionId: string;\n extractName?: string | undefined;\n extractedCount: number;\n totalMatching: number;\n limit: number;\n offset?: number | undefined;\n}\n\n/**\n * Compute a batch continuation suggestion when --limit was used with remaining articles.\n * Returns null if no remaining articles.\n */\nexport function computeBatchContinuation(params: BatchContinuationParams): Suggestion | null {\n const nextOffset = (params.offset ?? 0) + params.extractedCount;\n const remaining = params.totalMatching - nextOffset;\n if (remaining <= 0) return null;\n\n const nextName = params.extractName ? `${params.extractName}-next` : 'next-batch';\n return {\n command: `search-hub review extract --session ${params.sessionId} --offset ${nextOffset} --limit ${params.limit} --name ${nextName}`,\n description: `${remaining} articles remaining — extract next batch`,\n };\n}\n\n/**\n * Detect the next review basis level from reviewer registry.\n * Progression: title → abstract → fulltext\n */\nfunction detectNextBasis(reviewers: ReviewStatusResult['reviewers']): ReviewBasis {\n const bases = new Set(reviewers.map((r) => r.basis));\n if (!bases.has('title')) return 'title';\n if (!bases.has('abstract')) return 'abstract';\n return 'fulltext';\n}\n\n/**\n * Generate dynamic next steps based on review status distribution.\n *\n * Evaluation order (top-to-bottom, first match wins for primary suggestion):\n * 1. pending > 0 → extract for title screening\n * 2. agreed > 0 → finalize consensus articles\n * 3. (divided + allUncertain + incomplete) > 0 → extract for next basis level\n * 4. all finalized → register accepted articles\n *\n * Batch continuation is appended to seeAlso when applicable.\n */\nexport function generateReviewNextSteps(ctx: ReviewNextStepsContext): SuggestionResult | null {\n const { sessionId, statusResult: rs } = ctx;\n\n if (rs.total === 0) return null;\n\n const result: SuggestionResult = { next: [], seeAlso: [] };\n\n // 1. pending > 0: title screening incomplete\n
|
|
1
|
+
{"version":3,"file":"next-steps.js","sources":["../../../../src/cli/commands/review/next-steps.ts"],"sourcesContent":["/**\n * Dynamic next steps generation for review workflow.\n * Generates context-aware suggestions based on current article status distribution.\n */\n\nimport type { ReviewStatusResult } from './status.js';\nimport type { ReviewBasis, ReviewMode } from './types.js';\nimport type { Suggestion, SuggestionResult } from '../../suggestions/types.js';\n\nexport interface ReviewNextStepsContext {\n sessionId: string;\n statusResult: ReviewStatusResult;\n /** Review mode: screening (default) or picking */\n mode?: ReviewMode;\n /** Extract name for batch continuation */\n extractName?: string;\n /** Number of articles extracted in current batch */\n extractedCount?: number;\n /** Total articles matching the filter */\n totalMatching?: number;\n /** Limit used in extract */\n limit?: number;\n /** Offset used in extract */\n offset?: number;\n}\n\nexport interface BatchContinuationParams {\n sessionId: string;\n extractName?: string | undefined;\n extractedCount: number;\n totalMatching: number;\n limit: number;\n offset?: number | undefined;\n}\n\n/**\n * Compute a batch continuation suggestion when --limit was used with remaining articles.\n * Returns null if no remaining articles.\n */\nexport function computeBatchContinuation(params: BatchContinuationParams): Suggestion | null {\n const nextOffset = (params.offset ?? 0) + params.extractedCount;\n const remaining = params.totalMatching - nextOffset;\n if (remaining <= 0) return null;\n\n const nextName = params.extractName ? `${params.extractName}-next` : 'next-batch';\n return {\n command: `search-hub review extract --session ${params.sessionId} --offset ${nextOffset} --limit ${params.limit} --name ${nextName}`,\n description: `${remaining} articles remaining — extract next batch`,\n };\n}\n\n/**\n * Detect the next review basis level from reviewer registry.\n * Progression: title → abstract → fulltext\n */\nfunction detectNextBasis(reviewers: ReviewStatusResult['reviewers']): ReviewBasis {\n const bases = new Set(reviewers.map((r) => r.basis));\n if (!bases.has('title')) return 'title';\n if (!bases.has('abstract')) return 'abstract';\n return 'fulltext';\n}\n\n/**\n * Generate dynamic next steps based on review status distribution.\n *\n * Evaluation order (top-to-bottom, first match wins for primary suggestion):\n * 1. pending > 0 → extract for title screening\n * 2. agreed > 0 → finalize consensus articles\n * 3. (divided + allUncertain + incomplete) > 0 → extract for next basis level\n * 4. all finalized → register accepted articles\n *\n * Batch continuation is appended to seeAlso when applicable.\n */\nexport function generateReviewNextSteps(ctx: ReviewNextStepsContext): SuggestionResult | null {\n const { sessionId, statusResult: rs, mode = 'screening' } = ctx;\n\n if (rs.total === 0) return null;\n\n const result: SuggestionResult = { next: [], seeAlso: [] };\n\n if (mode === 'picking') {\n // Picking mode logic\n // 1. pending > 0: extract for title review\n if (rs.pending > 0) {\n result.next.push({\n command: `search-hub review extract --session ${sessionId} --basis title --filter pending --reviewer \"<name>\" --name title-picking`,\n description: `Extract ${rs.pending} pending articles for title review`,\n });\n }\n // 2. agreed-include > 0 (or all-uncertain): confirm at next basis level\n else if (rs.agreedInclude > 0 || rs.allUncertain > 0) {\n const nextBasis = detectNextBasis(rs.reviewers);\n const parts: string[] = [];\n if (rs.agreedInclude > 0) parts.push(`${rs.agreedInclude} picked`);\n if (rs.allUncertain > 0) parts.push(`${rs.allUncertain} uncertain`);\n const description = `${parts.join(' + ')} — confirm at ${nextBasis} level`;\n result.next.push({\n command: `search-hub review extract --session ${sessionId} --filter agreed-include,all-uncertain --basis ${nextBasis} --reviewer \"<name>\" --name ${nextBasis}-screening`,\n description,\n });\n }\n // 3. agreed > 0: finalize\n else {\n const agreed = rs.agreedInclude + rs.agreedExclude;\n if (agreed > 0) {\n result.next.push({\n command: `search-hub review finalize --session ${sessionId}`,\n description: `Finalize ${agreed} articles with consensus`,\n });\n }\n // 4. all finalized: export included\n else if (rs.finalized > 0 && rs.finalized === rs.total) {\n result.next.push({\n command: `search-hub review export --session ${sessionId} --only included`,\n description: `${rs.included} articles ready for export`,\n });\n }\n // No actionable state\n else {\n return null;\n }\n }\n } else {\n // Screening mode logic (default)\n // 1. pending > 0: title screening incomplete\n if (rs.pending > 0) {\n result.next.push({\n command: `search-hub review extract --session ${sessionId} --basis title --filter pending --reviewer \"<name>\" --name title-screening`,\n description: `Extract ${rs.pending} pending articles for title screening`,\n });\n }\n // 2. agreed > 0: suggest finalization\n else {\n const agreed = rs.agreedInclude + rs.agreedExclude;\n if (agreed > 0) {\n result.next.push({\n command: `search-hub review finalize --session ${sessionId}`,\n description: `Finalize ${agreed} articles with consensus`,\n });\n }\n // 3. divided, all-uncertain, or incomplete > 0: suggest further review\n else if (rs.divided > 0 || rs.allUncertain > 0 || rs.incomplete > 0) {\n const unresolved = rs.divided + rs.allUncertain + rs.incomplete;\n const nextBasis = detectNextBasis(rs.reviewers);\n result.next.push({\n command: `search-hub review extract --session ${sessionId} --filter divided,all-uncertain,incomplete --basis ${nextBasis} --reviewer \"<name>\" --name ${nextBasis}-screening`,\n description: `${unresolved} articles need ${nextBasis}-level review`,\n });\n }\n // 4. All finalized\n else if (rs.finalized > 0 && rs.finalized === rs.total) {\n result.next.push({\n command: `search-hub register ${sessionId} --reviewed`,\n description: 'Register accepted articles',\n });\n }\n // No actionable state\n else {\n return null;\n }\n }\n }\n\n // 5. Batch continuation (appended to seeAlso when applicable)\n if (\n ctx.limit !== undefined &&\n ctx.extractedCount !== undefined &&\n ctx.totalMatching !== undefined\n ) {\n const batch = computeBatchContinuation({\n sessionId,\n extractName: ctx.extractName,\n extractedCount: ctx.extractedCount,\n totalMatching: ctx.totalMatching,\n limit: ctx.limit,\n offset: ctx.offset,\n });\n if (batch) result.seeAlso.push(batch);\n }\n\n return result;\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -117,8 +117,16 @@ export declare const reviewerRecordSchema: z.ZodObject<{
|
|
|
117
117
|
fulltext: "fulltext";
|
|
118
118
|
}>;
|
|
119
119
|
}, z.core.$strict>;
|
|
120
|
+
export declare const reviewModeSchema: z.ZodEnum<{
|
|
121
|
+
screening: "screening";
|
|
122
|
+
picking: "picking";
|
|
123
|
+
}>;
|
|
120
124
|
export declare const reviewFileSchema: z.ZodObject<{
|
|
121
125
|
sessionId: z.ZodString;
|
|
126
|
+
mode: z.ZodOptional<z.ZodEnum<{
|
|
127
|
+
screening: "screening";
|
|
128
|
+
picking: "picking";
|
|
129
|
+
}>>;
|
|
122
130
|
criteria: z.ZodOptional<z.ZodString>;
|
|
123
131
|
reviewer: z.ZodOptional<z.ZodString>;
|
|
124
132
|
basis: z.ZodOptional<z.ZodEnum<{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/review/schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,CAAC,MAAM,KAAK,CAAC;AAEzB,eAAO,MAAM,oBAAoB;;;;EAA8C,CAAC;AAEhF,eAAO,MAAM,iBAAiB;;;;EAA4C,CAAC;AAE3E,eAAO,MAAM,YAAY;;;;;;;;;;;;;;kBASyB,CAAC;AAEnD,eAAO,MAAM,kBAAkB;;;;;;;kBAUwB,CAAC;AAExD,eAAO,MAAM,wBAAwB;;;;;;;;kBAY1B,CAAC;AAEZ,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBA0ByC,CAAC;AAEzE,eAAO,MAAM,oBAAoB;;;;;;;kBAM6C,CAAC;AAE/E,eAAO,MAAM,gBAAgB
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/review/schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,CAAC,MAAM,KAAK,CAAC;AAEzB,eAAO,MAAM,oBAAoB;;;;EAA8C,CAAC;AAEhF,eAAO,MAAM,iBAAiB;;;;EAA4C,CAAC;AAE3E,eAAO,MAAM,YAAY;;;;;;;;;;;;;;kBASyB,CAAC;AAEnD,eAAO,MAAM,kBAAkB;;;;;;;kBAUwB,CAAC;AAExD,eAAO,MAAM,wBAAwB;;;;;;;;kBAY1B,CAAC;AAEZ,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBA0ByC,CAAC;AAEzE,eAAO,MAAM,oBAAoB;;;;;;;kBAM6C,CAAC;AAE/E,eAAO,MAAM,gBAAgB;;;EAAmC,CAAC;AAEjE,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAW6B,CAAC;AAE3D,8DAA8D;AAC9D,wBAAgB,wBAAwB,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAIlE"}
|
|
@@ -50,8 +50,10 @@ const reviewerRecordSchema = z.object({
|
|
|
50
50
|
name: z.string().describe("Reviewer identifier"),
|
|
51
51
|
basis: reviewBasisSchema.describe("Basis level at which the reviewer participated")
|
|
52
52
|
}).strict().describe("Record of a reviewer's participation at a specific basis level");
|
|
53
|
+
const reviewModeSchema = z.enum(["screening", "picking"]);
|
|
53
54
|
const reviewFileSchema = z.object({
|
|
54
55
|
sessionId: z.string().describe("Session identifier"),
|
|
56
|
+
mode: reviewModeSchema.describe("Review mode: screening (exclusion-based) or picking (inclusion-based)").optional(),
|
|
55
57
|
criteria: z.string().describe("Path to inclusion criteria file").optional(),
|
|
56
58
|
reviewer: z.string().describe("Reviewer identifier (only in extracted ReviewFiles)").optional(),
|
|
57
59
|
basis: reviewBasisSchema.describe("Basis level for screening (only in extracted ReviewFiles)").optional(),
|
|
@@ -71,6 +73,7 @@ export {
|
|
|
71
73
|
reviewBasisSchema,
|
|
72
74
|
reviewDecisionSchema,
|
|
73
75
|
reviewFileSchema,
|
|
76
|
+
reviewModeSchema,
|
|
74
77
|
reviewSchema,
|
|
75
78
|
reviewerRecordSchema
|
|
76
79
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.js","sources":["../../../../src/cli/commands/review/schema.ts"],"sourcesContent":["/**\n * Zod schemas for review workflow types.\n *\n * Single source of truth for:\n * - TypeScript types (via z.infer<>)\n * - JSON Schema for IDE autocompletion (via z.toJSONSchema())\n *\n * Follows the pattern established in src/query/json-schema.ts.\n */\nimport * as z from 'zod';\n\nexport const reviewDecisionSchema = z.enum(['include', 'exclude', 'uncertain']);\n\nexport const reviewBasisSchema = z.enum(['title', 'abstract', 'fulltext']);\n\nexport const reviewSchema = z\n .object({\n reviewer: z.string().describe(\"Reviewer identifier (e.g., 'gpt-4o', 'claude-sonnet', 'human:tanaka')\"),\n decision: reviewDecisionSchema.describe('Assessment decision').optional(),\n basis: reviewBasisSchema.describe('Basis of the decision (what information was used)').optional(),\n comment: z.string().describe('Optional comment or reason').optional(),\n timestamp: z.string().datetime().describe('ISO 8601 timestamp').optional(),\n })\n .strict()\n .describe('Individual assessment of an article');\n\nexport const mergedSourceSchema = z\n .object({\n source: z.string().describe(\"Database source (e.g., 'pubmed', 'scopus')\"),\n pmid: z.string().optional(),\n doi: z.string().optional(),\n scopusId: z.string().optional(),\n arxivId: z.string().optional(),\n ericId: z.string().optional(),\n })\n .strict()\n .describe('Source information for merged duplicates');\n\nexport const articleFulltextRefSchema = z\n .object({\n dirName: z.string(),\n hasFiles: z\n .object({\n pdf: z.boolean(),\n xml: z.boolean(),\n html: z.boolean(),\n markdown: z.boolean(),\n })\n .strict(),\n })\n .strict();\n\nexport const articleEntrySchema = z\n .object({\n // Identifiers\n doi: z.string().describe('Digital Object Identifier').optional(),\n pmid: z.string().describe('PubMed ID').optional(),\n scopusId: z.string().describe('Scopus ID').optional(),\n arxivId: z.string().describe('arXiv ID').optional(),\n ericId: z.string().describe('ERIC ID').optional(),\n // Bibliographic info\n title: z.string().describe('Article title'),\n authors: z.string().describe('Authors (formatted string)').optional(),\n year: z.string().describe('Publication year').optional(),\n abstract: z.string().describe('Article abstract').optional(),\n // Deduplication tracking\n mergedFrom: z.array(mergedSourceSchema).describe('Sources this article was merged from during deduplication').optional(),\n // Review data\n reviews: z.array(reviewSchema).describe('List of assessments'),\n reviewHistory: z.array(reviewSchema).describe('Historical reviews (only in extracted ReviewFiles, never in master file)').optional(),\n finalDecision: z\n .union([z.literal('include'), z.literal('exclude'), z.null()])\n .describe('Final inclusion/exclusion decision (null in extracted files)')\n .optional(),\n // Fulltext reference\n fulltext: articleFulltextRefSchema.optional(),\n })\n .strict()\n .describe('Article with identifiers, bibliographic info, and reviews');\n\nexport const reviewerRecordSchema = z\n .object({\n name: z.string().describe('Reviewer identifier'),\n basis: reviewBasisSchema.describe('Basis level at which the reviewer participated'),\n })\n .strict()\n .describe('Record of a reviewer\\'s participation at a specific basis level');\n\nexport const reviewFileSchema = z\n .object({\n sessionId: z.string().describe('Session identifier'),\n criteria: z.string().describe('Path to inclusion criteria file').optional(),\n reviewer: z.string().describe('Reviewer identifier (only in extracted ReviewFiles)').optional(),\n basis: reviewBasisSchema.describe('Basis level for screening (only in extracted ReviewFiles)').optional(),\n articles: z.array(articleEntrySchema).describe('List of articles with review data'),\n reviewers: z.array(reviewerRecordSchema).describe('Registry of reviewers who participated at each basis level').optional(),\n })\n .strict()\n .describe('Schema for article review workflow tracking');\n\n/** Generate a JSON Schema from the review file Zod schema. */\nexport function generateReviewJSONSchema(): Record<string, unknown> {\n return z.toJSONSchema(reviewFileSchema, {\n target: 'draft-7',\n });\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"schema.js","sources":["../../../../src/cli/commands/review/schema.ts"],"sourcesContent":["/**\n * Zod schemas for review workflow types.\n *\n * Single source of truth for:\n * - TypeScript types (via z.infer<>)\n * - JSON Schema for IDE autocompletion (via z.toJSONSchema())\n *\n * Follows the pattern established in src/query/json-schema.ts.\n */\nimport * as z from 'zod';\n\nexport const reviewDecisionSchema = z.enum(['include', 'exclude', 'uncertain']);\n\nexport const reviewBasisSchema = z.enum(['title', 'abstract', 'fulltext']);\n\nexport const reviewSchema = z\n .object({\n reviewer: z.string().describe(\"Reviewer identifier (e.g., 'gpt-4o', 'claude-sonnet', 'human:tanaka')\"),\n decision: reviewDecisionSchema.describe('Assessment decision').optional(),\n basis: reviewBasisSchema.describe('Basis of the decision (what information was used)').optional(),\n comment: z.string().describe('Optional comment or reason').optional(),\n timestamp: z.string().datetime().describe('ISO 8601 timestamp').optional(),\n })\n .strict()\n .describe('Individual assessment of an article');\n\nexport const mergedSourceSchema = z\n .object({\n source: z.string().describe(\"Database source (e.g., 'pubmed', 'scopus')\"),\n pmid: z.string().optional(),\n doi: z.string().optional(),\n scopusId: z.string().optional(),\n arxivId: z.string().optional(),\n ericId: z.string().optional(),\n })\n .strict()\n .describe('Source information for merged duplicates');\n\nexport const articleFulltextRefSchema = z\n .object({\n dirName: z.string(),\n hasFiles: z\n .object({\n pdf: z.boolean(),\n xml: z.boolean(),\n html: z.boolean(),\n markdown: z.boolean(),\n })\n .strict(),\n })\n .strict();\n\nexport const articleEntrySchema = z\n .object({\n // Identifiers\n doi: z.string().describe('Digital Object Identifier').optional(),\n pmid: z.string().describe('PubMed ID').optional(),\n scopusId: z.string().describe('Scopus ID').optional(),\n arxivId: z.string().describe('arXiv ID').optional(),\n ericId: z.string().describe('ERIC ID').optional(),\n // Bibliographic info\n title: z.string().describe('Article title'),\n authors: z.string().describe('Authors (formatted string)').optional(),\n year: z.string().describe('Publication year').optional(),\n abstract: z.string().describe('Article abstract').optional(),\n // Deduplication tracking\n mergedFrom: z.array(mergedSourceSchema).describe('Sources this article was merged from during deduplication').optional(),\n // Review data\n reviews: z.array(reviewSchema).describe('List of assessments'),\n reviewHistory: z.array(reviewSchema).describe('Historical reviews (only in extracted ReviewFiles, never in master file)').optional(),\n finalDecision: z\n .union([z.literal('include'), z.literal('exclude'), z.null()])\n .describe('Final inclusion/exclusion decision (null in extracted files)')\n .optional(),\n // Fulltext reference\n fulltext: articleFulltextRefSchema.optional(),\n })\n .strict()\n .describe('Article with identifiers, bibliographic info, and reviews');\n\nexport const reviewerRecordSchema = z\n .object({\n name: z.string().describe('Reviewer identifier'),\n basis: reviewBasisSchema.describe('Basis level at which the reviewer participated'),\n })\n .strict()\n .describe('Record of a reviewer\\'s participation at a specific basis level');\n\nexport const reviewModeSchema = z.enum(['screening', 'picking']);\n\nexport const reviewFileSchema = z\n .object({\n sessionId: z.string().describe('Session identifier'),\n mode: reviewModeSchema.describe('Review mode: screening (exclusion-based) or picking (inclusion-based)').optional(),\n criteria: z.string().describe('Path to inclusion criteria file').optional(),\n reviewer: z.string().describe('Reviewer identifier (only in extracted ReviewFiles)').optional(),\n basis: reviewBasisSchema.describe('Basis level for screening (only in extracted ReviewFiles)').optional(),\n articles: z.array(articleEntrySchema).describe('List of articles with review data'),\n reviewers: z.array(reviewerRecordSchema).describe('Registry of reviewers who participated at each basis level').optional(),\n })\n .strict()\n .describe('Schema for article review workflow tracking');\n\n/** Generate a JSON Schema from the review file Zod schema. */\nexport function generateReviewJSONSchema(): Record<string, unknown> {\n return z.toJSONSchema(reviewFileSchema, {\n target: 'draft-7',\n });\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ReviewerRecord } from './types.js';
|
|
1
|
+
import { ReviewerRecord, ReviewMode } from './types.js';
|
|
2
2
|
export interface ReviewStatusOptions {
|
|
3
3
|
sessionId: string;
|
|
4
4
|
}
|
|
@@ -16,6 +16,8 @@ export interface ReviewStatusResult {
|
|
|
16
16
|
excluded: number;
|
|
17
17
|
/** Registered reviewers from the review file */
|
|
18
18
|
reviewers: ReviewerRecord[];
|
|
19
|
+
/** Review mode from the review file */
|
|
20
|
+
mode?: ReviewMode;
|
|
19
21
|
}
|
|
20
22
|
/**
|
|
21
23
|
* Execute review status command
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/review/status.ts"],"names":[],"mappings":"AAAA;;GAEG;AAKH,OAAO,EAAmC,KAAK,cAAc,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/review/status.ts"],"names":[],"mappings":"AAAA;;GAEG;AAKH,OAAO,EAAmC,KAAK,cAAc,EAAE,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC;AAEnG,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,gDAAgD;IAChD,SAAS,EAAE,cAAc,EAAE,CAAC;IAC5B,uCAAuC;IACvC,IAAI,CAAC,EAAE,UAAU,CAAC;CACnB;AAWD;;GAEG;AACH,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,mBAAmB,EAC5B,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,kBAAkB,CAAC,CAyD7B;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,kBAAkB,GAAG,MAAM,CAwBrE"}
|
|
@@ -57,14 +57,16 @@ async function executeReviewStatus(options, sessionsDir) {
|
|
|
57
57
|
sessionId: options.sessionId,
|
|
58
58
|
total: reviewFile.articles.length,
|
|
59
59
|
reviewers,
|
|
60
|
+
...reviewFile.mode && { mode: reviewFile.mode },
|
|
60
61
|
...counts
|
|
61
62
|
};
|
|
62
63
|
}
|
|
63
64
|
function formatStatusOutput(result) {
|
|
64
65
|
const id = result.sessionId;
|
|
65
66
|
const agreed = result.agreedInclude + result.agreedExclude;
|
|
67
|
+
const header = result.mode ? `Review Progress: ${id} (${result.mode} mode)` : `Review Progress: ${id}`;
|
|
66
68
|
const lines = [
|
|
67
|
-
|
|
69
|
+
header,
|
|
68
70
|
` Total: ${result.total}`,
|
|
69
71
|
` Pending: ${result.pending}`,
|
|
70
72
|
` Incomplete: ${result.incomplete}`,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"status.js","sources":["../../../../src/cli/commands/review/status.ts"],"sourcesContent":["/**\n * review status command - Show review progress summary\n */\n\nimport { join } from 'node:path';\nimport { readFile } from 'node:fs/promises';\nimport { parse as parseYaml } from 'yaml';\nimport { classifyStatus, type ReviewFile, type ReviewerRecord } from './types.js';\n\nexport interface ReviewStatusOptions {\n sessionId: string;\n}\n\nexport interface ReviewStatusResult {\n sessionId: string;\n total: number;\n pending: number;\n incomplete: number;\n allUncertain: number;\n agreedInclude: number;\n agreedExclude: number;\n divided: number;\n finalized: number;\n included: number;\n excluded: number;\n /** Registered reviewers from the review file */\n reviewers: ReviewerRecord[];\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 * Execute review status command\n */\nexport async function executeReviewStatus(\n options: ReviewStatusOptions,\n sessionsDir: string\n): Promise<ReviewStatusResult> {\n const sessionDir = join(sessionsDir, options.sessionId);\n const reviewFile = await loadReviewFile(sessionDir);\n\n const reviewers = reviewFile.reviewers ?? [];\n const counts = {\n pending: 0,\n incomplete: 0,\n allUncertain: 0,\n agreedInclude: 0,\n agreedExclude: 0,\n divided: 0,\n finalized: 0,\n included: 0,\n excluded: 0,\n };\n\n for (const article of reviewFile.articles) {\n const status = classifyStatus(article, reviewers);\n\n switch (status) {\n case 'pending':\n counts.pending++;\n break;\n case 'incomplete':\n counts.incomplete++;\n break;\n case 'all-uncertain':\n counts.allUncertain++;\n break;\n case 'agreed-include':\n counts.agreedInclude++;\n break;\n case 'agreed-exclude':\n counts.agreedExclude++;\n break;\n case 'divided':\n counts.divided++;\n break;\n case 'finalized':\n counts.finalized++;\n if (article.finalDecision === 'include') {\n counts.included++;\n } else {\n counts.excluded++;\n }\n break;\n }\n }\n\n return {\n sessionId: options.sessionId,\n total: reviewFile.articles.length,\n reviewers,\n ...counts,\n };\n}\n\n/**\n * Format status result as human-readable string\n */\nexport function formatStatusOutput(result: ReviewStatusResult): string {\n const id = result.sessionId;\n const agreed = result.agreedInclude + result.agreedExclude;\n const
|
|
1
|
+
{"version":3,"file":"status.js","sources":["../../../../src/cli/commands/review/status.ts"],"sourcesContent":["/**\n * review status command - Show review progress summary\n */\n\nimport { join } from 'node:path';\nimport { readFile } from 'node:fs/promises';\nimport { parse as parseYaml } from 'yaml';\nimport { classifyStatus, type ReviewFile, type ReviewerRecord, type ReviewMode } from './types.js';\n\nexport interface ReviewStatusOptions {\n sessionId: string;\n}\n\nexport interface ReviewStatusResult {\n sessionId: string;\n total: number;\n pending: number;\n incomplete: number;\n allUncertain: number;\n agreedInclude: number;\n agreedExclude: number;\n divided: number;\n finalized: number;\n included: number;\n excluded: number;\n /** Registered reviewers from the review file */\n reviewers: ReviewerRecord[];\n /** Review mode from the review file */\n mode?: ReviewMode;\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 * Execute review status command\n */\nexport async function executeReviewStatus(\n options: ReviewStatusOptions,\n sessionsDir: string\n): Promise<ReviewStatusResult> {\n const sessionDir = join(sessionsDir, options.sessionId);\n const reviewFile = await loadReviewFile(sessionDir);\n\n const reviewers = reviewFile.reviewers ?? [];\n const counts = {\n pending: 0,\n incomplete: 0,\n allUncertain: 0,\n agreedInclude: 0,\n agreedExclude: 0,\n divided: 0,\n finalized: 0,\n included: 0,\n excluded: 0,\n };\n\n for (const article of reviewFile.articles) {\n const status = classifyStatus(article, reviewers);\n\n switch (status) {\n case 'pending':\n counts.pending++;\n break;\n case 'incomplete':\n counts.incomplete++;\n break;\n case 'all-uncertain':\n counts.allUncertain++;\n break;\n case 'agreed-include':\n counts.agreedInclude++;\n break;\n case 'agreed-exclude':\n counts.agreedExclude++;\n break;\n case 'divided':\n counts.divided++;\n break;\n case 'finalized':\n counts.finalized++;\n if (article.finalDecision === 'include') {\n counts.included++;\n } else {\n counts.excluded++;\n }\n break;\n }\n }\n\n return {\n sessionId: options.sessionId,\n total: reviewFile.articles.length,\n reviewers,\n ...(reviewFile.mode && { mode: reviewFile.mode }),\n ...counts,\n };\n}\n\n/**\n * Format status result as human-readable string\n */\nexport function formatStatusOutput(result: ReviewStatusResult): string {\n const id = result.sessionId;\n const agreed = result.agreedInclude + result.agreedExclude;\n const header = result.mode ? `Review Progress: ${id} (${result.mode} mode)` : `Review Progress: ${id}`;\n const lines = [\n header,\n ` Total: ${result.total}`,\n ` Pending: ${result.pending}`,\n ` Incomplete: ${result.incomplete}`,\n ` All-uncertain: ${result.allUncertain}`,\n ` Agreed: ${agreed} (include: ${result.agreedInclude}, exclude: ${result.agreedExclude})`,\n ` Divided: ${result.divided}`,\n ` Finalized: ${result.finalized} (include: ${result.included}, exclude: ${result.excluded})`,\n ];\n\n if (result.reviewers.length > 0) {\n lines.push('');\n lines.push('Reviewers:');\n for (const r of result.reviewers) {\n lines.push(` ${r.name} (${r.basis})`);\n }\n }\n\n return lines.join('\\n');\n}\n"],"names":["parseYaml"],"mappings":";;;;AAkCA,eAAe,eAAe,YAAyC;AACrE,QAAM,cAAc,KAAK,YAAY,aAAa,cAAc;AAChE,QAAM,UAAU,MAAM,SAAS,aAAa,OAAO;AACnD,SAAOA,MAAU,OAAO;AAC1B;AAKA,eAAsB,oBACpB,SACA,aAC6B;AAC7B,QAAM,aAAa,KAAK,aAAa,QAAQ,SAAS;AACtD,QAAM,aAAa,MAAM,eAAe,UAAU;AAElD,QAAM,YAAY,WAAW,aAAa,CAAA;AAC1C,QAAM,SAAS;AAAA,IACb,SAAS;AAAA,IACT,YAAY;AAAA,IACZ,cAAc;AAAA,IACd,eAAe;AAAA,IACf,eAAe;AAAA,IACf,SAAS;AAAA,IACT,WAAW;AAAA,IACX,UAAU;AAAA,IACV,UAAU;AAAA,EAAA;AAGZ,aAAW,WAAW,WAAW,UAAU;AACzC,UAAM,SAAS,eAAe,SAAS,SAAS;AAEhD,YAAQ,QAAA;AAAA,MACN,KAAK;AACH,eAAO;AACP;AAAA,MACF,KAAK;AACH,eAAO;AACP;AAAA,MACF,KAAK;AACH,eAAO;AACP;AAAA,MACF,KAAK;AACH,eAAO;AACP;AAAA,MACF,KAAK;AACH,eAAO;AACP;AAAA,MACF,KAAK;AACH,eAAO;AACP;AAAA,MACF,KAAK;AACH,eAAO;AACP,YAAI,QAAQ,kBAAkB,WAAW;AACvC,iBAAO;AAAA,QACT,OAAO;AACL,iBAAO;AAAA,QACT;AACA;AAAA,IAAA;AAAA,EAEN;AAEA,SAAO;AAAA,IACL,WAAW,QAAQ;AAAA,IACnB,OAAO,WAAW,SAAS;AAAA,IAC3B;AAAA,IACA,GAAI,WAAW,QAAQ,EAAE,MAAM,WAAW,KAAA;AAAA,IAC1C,GAAG;AAAA,EAAA;AAEP;AAKO,SAAS,mBAAmB,QAAoC;AACrE,QAAM,KAAK,OAAO;AAClB,QAAM,SAAS,OAAO,gBAAgB,OAAO;AAC7C,QAAM,SAAS,OAAO,OAAO,oBAAoB,EAAE,KAAK,OAAO,IAAI,WAAW,oBAAoB,EAAE;AACpG,QAAM,QAAQ;AAAA,IACZ;AAAA,IACA,sBAAsB,OAAO,KAAK;AAAA,IAClC,sBAAsB,OAAO,OAAO;AAAA,IACpC,sBAAsB,OAAO,UAAU;AAAA,IACvC,sBAAsB,OAAO,YAAY;AAAA,IACzC,sBAAsB,MAAM,eAAe,OAAO,aAAa,cAAc,OAAO,aAAa;AAAA,IACjG,sBAAsB,OAAO,OAAO;AAAA,IACpC,sBAAsB,OAAO,SAAS,eAAe,OAAO,QAAQ,cAAc,OAAO,QAAQ;AAAA,EAAA;AAGnG,MAAI,OAAO,UAAU,SAAS,GAAG;AAC/B,UAAM,KAAK,EAAE;AACb,UAAM,KAAK,YAAY;AACvB,eAAW,KAAK,OAAO,WAAW;AAChC,YAAM,KAAK,KAAK,EAAE,IAAI,MAAM,EAAE,KAAK,GAAG;AAAA,IACxC;AAAA,EACF;AAEA,SAAO,MAAM,KAAK,IAAI;AACxB;"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { reviewDecisionSchema, reviewBasisSchema, reviewSchema, mergedSourceSchema, articleEntrySchema, reviewerRecordSchema, reviewFileSchema } from './schema.js';
|
|
1
|
+
import { reviewDecisionSchema, reviewBasisSchema, reviewModeSchema, reviewSchema, mergedSourceSchema, articleEntrySchema, reviewerRecordSchema, reviewFileSchema } from './schema.js';
|
|
2
2
|
/**
|
|
3
3
|
* Review workflow types for article assessment tracking.
|
|
4
4
|
*
|
|
@@ -9,6 +9,7 @@ import { reviewDecisionSchema, reviewBasisSchema, reviewSchema, mergedSourceSche
|
|
|
9
9
|
import * as z from 'zod';
|
|
10
10
|
export type ReviewDecision = z.infer<typeof reviewDecisionSchema>;
|
|
11
11
|
export type ReviewBasis = z.infer<typeof reviewBasisSchema>;
|
|
12
|
+
export type ReviewMode = z.infer<typeof reviewModeSchema>;
|
|
12
13
|
export type Review = z.infer<typeof reviewSchema>;
|
|
13
14
|
export type MergedSource = z.infer<typeof mergedSourceSchema>;
|
|
14
15
|
export type ArticleEntry = z.infer<typeof articleEntrySchema>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/review/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,KAAK,CAAC,MAAM,KAAK,CAAC;AACzB,OAAO,EACL,oBAAoB,EACpB,iBAAiB,EACjB,YAAY,EACZ,kBAAkB,EAClB,kBAAkB,EAClB,oBAAoB,EACpB,gBAAgB,EACjB,MAAM,aAAa,CAAC;AAErB,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAClE,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAC5D,MAAM,MAAM,MAAM,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC;AAClD,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAC9D,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAC9D,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAClE,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAE1D;;GAEG;AACH,iGAAiG;AACjG,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,wDAAwD;IACxD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,cAAc,GAAG,IAAI,CAAC;IAChC,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;GAEG;AACH,mGAAmG;AACnG,MAAM,WAAW,QAAQ;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,WAAW,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,eAAe,EAAE,CAAC;CAC7B;AAWD,wBAAgB,SAAS,CAAC,KAAK,EAAE,WAAW,GAAG,SAAS,GAAG,MAAM,CAGhE;AAED;;GAEG;AACH,MAAM,MAAM,YAAY,GACpB,SAAS,GACT,YAAY,GACZ,eAAe,GACf,gBAAgB,GAChB,gBAAgB,GAChB,SAAS,GACT,WAAW,CAAC;AAEhB;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAC5B,KAAK,EAAE,YAAY,EACnB,mBAAmB,CAAC,EAAE,cAAc,EAAE,GACrC,YAAY,CAmHd"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/review/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,KAAK,CAAC,MAAM,KAAK,CAAC;AACzB,OAAO,EACL,oBAAoB,EACpB,iBAAiB,EACjB,gBAAgB,EAChB,YAAY,EACZ,kBAAkB,EAClB,kBAAkB,EAClB,oBAAoB,EACpB,gBAAgB,EACjB,MAAM,aAAa,CAAC;AAErB,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAClE,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAC5D,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAC1D,MAAM,MAAM,MAAM,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC;AAClD,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAC9D,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAC9D,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAClE,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAE1D;;GAEG;AACH,iGAAiG;AACjG,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,wDAAwD;IACxD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,cAAc,GAAG,IAAI,CAAC;IAChC,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;GAEG;AACH,mGAAmG;AACnG,MAAM,WAAW,QAAQ;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,WAAW,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,eAAe,EAAE,CAAC;CAC7B;AAWD,wBAAgB,SAAS,CAAC,KAAK,EAAE,WAAW,GAAG,SAAS,GAAG,MAAM,CAGhE;AAED;;GAEG;AACH,MAAM,MAAM,YAAY,GACpB,SAAS,GACT,YAAY,GACZ,eAAe,GACf,gBAAgB,GAChB,gBAAgB,GAChB,SAAS,GACT,WAAW,CAAC;AAEhB;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAC5B,KAAK,EAAE,YAAY,EACnB,mBAAmB,CAAC,EAAE,cAAc,EAAE,GACrC,YAAY,CAmHd"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sources":["../../../../src/cli/commands/review/types.ts"],"sourcesContent":["/**\n * Review workflow types for article assessment tracking.\n *\n * Core data types (ReviewDecision, ReviewBasis, Review, MergedSource,\n * ArticleEntry, ReviewerRecord, ReviewFile) are derived from Zod schemas\n * in schema.ts — single source of truth for types and JSON Schema.\n */\nimport * as z from 'zod';\nimport {\n reviewDecisionSchema,\n reviewBasisSchema,\n reviewSchema,\n mergedSourceSchema,\n articleEntrySchema,\n reviewerRecordSchema,\n reviewFileSchema,\n} from './schema.js';\n\nexport type ReviewDecision = z.infer<typeof reviewDecisionSchema>;\nexport type ReviewBasis = z.infer<typeof reviewBasisSchema>;\nexport type Review = z.infer<typeof reviewSchema>;\nexport type MergedSource = z.infer<typeof mergedSourceSchema>;\nexport type ArticleEntry = z.infer<typeof articleEntrySchema>;\nexport type ReviewerRecord = z.infer<typeof reviewerRecordSchema>;\nexport type ReviewFile = z.infer<typeof reviewFileSchema>;\n\n/**\n * Work file article entry for AI agent workflow\n */\n/** @deprecated Use ReviewFile format with reviews[] instead. Kept for backward compatibility. */\nexport interface WorkFileArticle {\n id: string;\n title: string;\n abstract?: string;\n /** Fulltext directory name (only for fulltext basis) */\n fulltext?: string;\n decision: ReviewDecision | null;\n comment: string;\n}\n\n/**\n * Work file structure for AI agent workflow\n */\n/** @deprecated Use ReviewFile format with basis field instead. Kept for backward compatibility. */\nexport interface WorkFile {\n sessionId: string;\n basis: ReviewBasis;\n reviewer: string;\n articles: WorkFileArticle[];\n}\n\n/**\n * Basis priority rank: fulltext > abstract > title > undefined\n */\nconst BASIS_RANK: Record<string, number> = {\n title: 1,\n abstract: 2,\n fulltext: 3,\n};\n\nexport function basisRank(basis: ReviewBasis | undefined): number {\n if (basis === undefined) return 0;\n return BASIS_RANK[basis] ?? 0;\n}\n\n/**\n * Review status classification (7-state model)\n */\nexport type ReviewStatus =\n | 'pending'\n | 'incomplete'\n | 'all-uncertain'\n | 'agreed-include'\n | 'agreed-exclude'\n | 'divided'\n | 'finalized';\n\n/**\n * Classify the review status of an article entry\n *\n * Classification logic (in order):\n * 1. finalDecision set? → finalized\n * 2. No reviews? → pending\n * 3. Registered reviewer missing? → incomplete\n * 4. All uncertain? → all-uncertain\n * 5. All include? → agreed-include\n * 6. All exclude? → agreed-exclude\n * 7. Any mix of decisions? → divided\n */\nexport function classifyStatus(\n entry: ArticleEntry,\n registeredReviewers?: ReviewerRecord[]\n): ReviewStatus {\n // 1. Finalized takes precedence\n if (entry.finalDecision !== undefined && entry.finalDecision !== null) {\n return 'finalized';\n }\n\n // No reviews = pending (reviews can be null from YAML parsing with only comments)\n const reviews = entry.reviews ?? [];\n if (reviews.length === 0) {\n return 'pending';\n }\n\n // 3. Check for incomplete (registered reviewer missing)\n // Only check reviewers whose registered basis ≤ article's highest reviewed basis\n if (registeredReviewers && registeredReviewers.length > 0) {\n const reviewerNames = new Set(reviews.map((r) => r.reviewer));\n let highestReviewedRank = 0;\n for (const r of reviews) {\n highestReviewedRank = Math.max(highestReviewedRank, basisRank(r.basis));\n }\n // When reviews have no basis (legacy), check all registered reviewers\n const applicableReviewers = highestReviewedRank === 0\n ? registeredReviewers\n : registeredReviewers.filter(\n (reg) => basisRank(reg.basis) <= highestReviewedRank\n );\n const hasAllReviewers = applicableReviewers.every((reg) =>\n reviewerNames.has(reg.name)\n );\n if (applicableReviewers.length > 0 && !hasAllReviewers) {\n return 'incomplete';\n }\n }\n\n // Get reviews that have decisions\n const reviewsWithDecisions = reviews.filter((r) => r.decision !== undefined);\n\n if (reviewsWithDecisions.length === 0) {\n // All reviews lack a decision — treat as pending\n return 'pending';\n }\n\n // Basis-priority resolution:\n // \"uncertain\" at a lower basis means \"need more info\" (escalate).\n // A definitive decision at a higher basis resolves that uncertainty.\n //\n // Algorithm:\n // 1. Find the highest basis rank among all definitive (include/exclude) reviews\n // 2. For each reviewer, compute their effective decision:\n // - Take their highest-basis definitive decision if they have one\n // - Otherwise, keep uncertain only if their uncertain rank >= highest definitive rank\n // (i.e., no higher-basis definitive exists globally to resolve it)\n // 3. Reviewers whose only reviews are uncertain at a lower basis than the\n // highest global definitive are excluded from consensus (their uncertainty was resolved)\n\n // Find highest definitive basis rank across ALL reviews\n let highestDefinitiveRank = 0;\n for (const r of reviewsWithDecisions) {\n if (r.decision !== 'uncertain') {\n highestDefinitiveRank = Math.max(highestDefinitiveRank, basisRank(r.basis));\n }\n }\n\n // For each reviewer, compute effective decision\n const reviewerMap = new Map<string, { decision: ReviewDecision; rank: number }>();\n for (const r of reviewsWithDecisions) {\n const rank = basisRank(r.basis);\n const existing = reviewerMap.get(r.reviewer);\n if (!existing) {\n reviewerMap.set(r.reviewer, { decision: r.decision!, rank });\n } else {\n // Prefer definitive over uncertain\n if (r.decision !== 'uncertain' && existing.decision === 'uncertain') {\n reviewerMap.set(r.reviewer, { decision: r.decision!, rank });\n } else if (r.decision !== 'uncertain' && existing.decision !== 'uncertain' && rank > existing.rank) {\n // Higher-basis definitive overrides lower-basis definitive\n reviewerMap.set(r.reviewer, { decision: r.decision!, rank });\n } else if (r.decision === 'uncertain' && existing.decision === 'uncertain' && rank > existing.rank) {\n reviewerMap.set(r.reviewer, { decision: r.decision!, rank });\n }\n }\n }\n\n // Collect effective decisions, excluding reviewers whose effective decision\n // is at a lower basis than the highest global definitive\n const effectiveDecisions: ReviewDecision[] = [];\n for (const { decision, rank } of reviewerMap.values()) {\n if (rank < highestDefinitiveRank) {\n // This reviewer's decision is at a lower basis than the highest definitive — skip\n continue;\n }\n effectiveDecisions.push(decision);\n }\n\n if (effectiveDecisions.length === 0) {\n return 'pending';\n }\n\n // 4. Check all-uncertain: every effective decision is uncertain\n if (effectiveDecisions.every((d) => d === 'uncertain')) {\n return 'all-uncertain';\n }\n\n // 5. All include?\n if (effectiveDecisions.every((d) => d === 'include')) {\n return 'agreed-include';\n }\n\n // 6. All exclude?\n if (effectiveDecisions.every((d) => d === 'exclude')) {\n return 'agreed-exclude';\n }\n\n // 7. Any mix of different decisions → divided\n return 'divided';\n}\n"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"types.js","sources":["../../../../src/cli/commands/review/types.ts"],"sourcesContent":["/**\n * Review workflow types for article assessment tracking.\n *\n * Core data types (ReviewDecision, ReviewBasis, Review, MergedSource,\n * ArticleEntry, ReviewerRecord, ReviewFile) are derived from Zod schemas\n * in schema.ts — single source of truth for types and JSON Schema.\n */\nimport * as z from 'zod';\nimport {\n reviewDecisionSchema,\n reviewBasisSchema,\n reviewModeSchema,\n reviewSchema,\n mergedSourceSchema,\n articleEntrySchema,\n reviewerRecordSchema,\n reviewFileSchema,\n} from './schema.js';\n\nexport type ReviewDecision = z.infer<typeof reviewDecisionSchema>;\nexport type ReviewBasis = z.infer<typeof reviewBasisSchema>;\nexport type ReviewMode = z.infer<typeof reviewModeSchema>;\nexport type Review = z.infer<typeof reviewSchema>;\nexport type MergedSource = z.infer<typeof mergedSourceSchema>;\nexport type ArticleEntry = z.infer<typeof articleEntrySchema>;\nexport type ReviewerRecord = z.infer<typeof reviewerRecordSchema>;\nexport type ReviewFile = z.infer<typeof reviewFileSchema>;\n\n/**\n * Work file article entry for AI agent workflow\n */\n/** @deprecated Use ReviewFile format with reviews[] instead. Kept for backward compatibility. */\nexport interface WorkFileArticle {\n id: string;\n title: string;\n abstract?: string;\n /** Fulltext directory name (only for fulltext basis) */\n fulltext?: string;\n decision: ReviewDecision | null;\n comment: string;\n}\n\n/**\n * Work file structure for AI agent workflow\n */\n/** @deprecated Use ReviewFile format with basis field instead. Kept for backward compatibility. */\nexport interface WorkFile {\n sessionId: string;\n basis: ReviewBasis;\n reviewer: string;\n articles: WorkFileArticle[];\n}\n\n/**\n * Basis priority rank: fulltext > abstract > title > undefined\n */\nconst BASIS_RANK: Record<string, number> = {\n title: 1,\n abstract: 2,\n fulltext: 3,\n};\n\nexport function basisRank(basis: ReviewBasis | undefined): number {\n if (basis === undefined) return 0;\n return BASIS_RANK[basis] ?? 0;\n}\n\n/**\n * Review status classification (7-state model)\n */\nexport type ReviewStatus =\n | 'pending'\n | 'incomplete'\n | 'all-uncertain'\n | 'agreed-include'\n | 'agreed-exclude'\n | 'divided'\n | 'finalized';\n\n/**\n * Classify the review status of an article entry\n *\n * Classification logic (in order):\n * 1. finalDecision set? → finalized\n * 2. No reviews? → pending\n * 3. Registered reviewer missing? → incomplete\n * 4. All uncertain? → all-uncertain\n * 5. All include? → agreed-include\n * 6. All exclude? → agreed-exclude\n * 7. Any mix of decisions? → divided\n */\nexport function classifyStatus(\n entry: ArticleEntry,\n registeredReviewers?: ReviewerRecord[]\n): ReviewStatus {\n // 1. Finalized takes precedence\n if (entry.finalDecision !== undefined && entry.finalDecision !== null) {\n return 'finalized';\n }\n\n // No reviews = pending (reviews can be null from YAML parsing with only comments)\n const reviews = entry.reviews ?? [];\n if (reviews.length === 0) {\n return 'pending';\n }\n\n // 3. Check for incomplete (registered reviewer missing)\n // Only check reviewers whose registered basis ≤ article's highest reviewed basis\n if (registeredReviewers && registeredReviewers.length > 0) {\n const reviewerNames = new Set(reviews.map((r) => r.reviewer));\n let highestReviewedRank = 0;\n for (const r of reviews) {\n highestReviewedRank = Math.max(highestReviewedRank, basisRank(r.basis));\n }\n // When reviews have no basis (legacy), check all registered reviewers\n const applicableReviewers = highestReviewedRank === 0\n ? registeredReviewers\n : registeredReviewers.filter(\n (reg) => basisRank(reg.basis) <= highestReviewedRank\n );\n const hasAllReviewers = applicableReviewers.every((reg) =>\n reviewerNames.has(reg.name)\n );\n if (applicableReviewers.length > 0 && !hasAllReviewers) {\n return 'incomplete';\n }\n }\n\n // Get reviews that have decisions\n const reviewsWithDecisions = reviews.filter((r) => r.decision !== undefined);\n\n if (reviewsWithDecisions.length === 0) {\n // All reviews lack a decision — treat as pending\n return 'pending';\n }\n\n // Basis-priority resolution:\n // \"uncertain\" at a lower basis means \"need more info\" (escalate).\n // A definitive decision at a higher basis resolves that uncertainty.\n //\n // Algorithm:\n // 1. Find the highest basis rank among all definitive (include/exclude) reviews\n // 2. For each reviewer, compute their effective decision:\n // - Take their highest-basis definitive decision if they have one\n // - Otherwise, keep uncertain only if their uncertain rank >= highest definitive rank\n // (i.e., no higher-basis definitive exists globally to resolve it)\n // 3. Reviewers whose only reviews are uncertain at a lower basis than the\n // highest global definitive are excluded from consensus (their uncertainty was resolved)\n\n // Find highest definitive basis rank across ALL reviews\n let highestDefinitiveRank = 0;\n for (const r of reviewsWithDecisions) {\n if (r.decision !== 'uncertain') {\n highestDefinitiveRank = Math.max(highestDefinitiveRank, basisRank(r.basis));\n }\n }\n\n // For each reviewer, compute effective decision\n const reviewerMap = new Map<string, { decision: ReviewDecision; rank: number }>();\n for (const r of reviewsWithDecisions) {\n const rank = basisRank(r.basis);\n const existing = reviewerMap.get(r.reviewer);\n if (!existing) {\n reviewerMap.set(r.reviewer, { decision: r.decision!, rank });\n } else {\n // Prefer definitive over uncertain\n if (r.decision !== 'uncertain' && existing.decision === 'uncertain') {\n reviewerMap.set(r.reviewer, { decision: r.decision!, rank });\n } else if (r.decision !== 'uncertain' && existing.decision !== 'uncertain' && rank > existing.rank) {\n // Higher-basis definitive overrides lower-basis definitive\n reviewerMap.set(r.reviewer, { decision: r.decision!, rank });\n } else if (r.decision === 'uncertain' && existing.decision === 'uncertain' && rank > existing.rank) {\n reviewerMap.set(r.reviewer, { decision: r.decision!, rank });\n }\n }\n }\n\n // Collect effective decisions, excluding reviewers whose effective decision\n // is at a lower basis than the highest global definitive\n const effectiveDecisions: ReviewDecision[] = [];\n for (const { decision, rank } of reviewerMap.values()) {\n if (rank < highestDefinitiveRank) {\n // This reviewer's decision is at a lower basis than the highest definitive — skip\n continue;\n }\n effectiveDecisions.push(decision);\n }\n\n if (effectiveDecisions.length === 0) {\n return 'pending';\n }\n\n // 4. Check all-uncertain: every effective decision is uncertain\n if (effectiveDecisions.every((d) => d === 'uncertain')) {\n return 'all-uncertain';\n }\n\n // 5. All include?\n if (effectiveDecisions.every((d) => d === 'include')) {\n return 'agreed-include';\n }\n\n // 6. All exclude?\n if (effectiveDecisions.every((d) => d === 'exclude')) {\n return 'agreed-exclude';\n }\n\n // 7. Any mix of different decisions → divided\n return 'divided';\n}\n"],"names":[],"mappings":";;AAwDA,MAAM,aAAqC;AAAA,EACzC,OAAO;AAAA,EACP,UAAU;AAAA,EACV,UAAU;AACZ;AAEO,SAAS,UAAU,OAAwC;AAChE,MAAI,UAAU,OAAW,QAAO;AAChC,SAAO,WAAW,KAAK,KAAK;AAC9B;AA0BO,SAAS,eACd,OACA,qBACc;AAEd,MAAI,MAAM,kBAAkB,UAAa,MAAM,kBAAkB,MAAM;AACrE,WAAO;AAAA,EACT;AAGA,QAAM,UAAU,MAAM,WAAW,CAAA;AACjC,MAAI,QAAQ,WAAW,GAAG;AACxB,WAAO;AAAA,EACT;AAIA,MAAI,uBAAuB,oBAAoB,SAAS,GAAG;AACzD,UAAM,gBAAgB,IAAI,IAAI,QAAQ,IAAI,CAAC,MAAM,EAAE,QAAQ,CAAC;AAC5D,QAAI,sBAAsB;AAC1B,eAAW,KAAK,SAAS;AACvB,4BAAsB,KAAK,IAAI,qBAAqB,UAAU,EAAE,KAAK,CAAC;AAAA,IACxE;AAEA,UAAM,sBAAsB,wBAAwB,IAChD,sBACA,oBAAoB;AAAA,MAClB,CAAC,QAAQ,UAAU,IAAI,KAAK,KAAK;AAAA,IAAA;AAEvC,UAAM,kBAAkB,oBAAoB;AAAA,MAAM,CAAC,QACjD,cAAc,IAAI,IAAI,IAAI;AAAA,IAAA;AAE5B,QAAI,oBAAoB,SAAS,KAAK,CAAC,iBAAiB;AACtD,aAAO;AAAA,IACT;AAAA,EACF;AAGA,QAAM,uBAAuB,QAAQ,OAAO,CAAC,MAAM,EAAE,aAAa,MAAS;AAE3E,MAAI,qBAAqB,WAAW,GAAG;AAErC,WAAO;AAAA,EACT;AAgBA,MAAI,wBAAwB;AAC5B,aAAW,KAAK,sBAAsB;AACpC,QAAI,EAAE,aAAa,aAAa;AAC9B,8BAAwB,KAAK,IAAI,uBAAuB,UAAU,EAAE,KAAK,CAAC;AAAA,IAC5E;AAAA,EACF;AAGA,QAAM,kCAAkB,IAAA;AACxB,aAAW,KAAK,sBAAsB;AACpC,UAAM,OAAO,UAAU,EAAE,KAAK;AAC9B,UAAM,WAAW,YAAY,IAAI,EAAE,QAAQ;AAC3C,QAAI,CAAC,UAAU;AACb,kBAAY,IAAI,EAAE,UAAU,EAAE,UAAU,EAAE,UAAW,MAAM;AAAA,IAC7D,OAAO;AAEL,UAAI,EAAE,aAAa,eAAe,SAAS,aAAa,aAAa;AACnE,oBAAY,IAAI,EAAE,UAAU,EAAE,UAAU,EAAE,UAAW,MAAM;AAAA,MAC7D,WAAW,EAAE,aAAa,eAAe,SAAS,aAAa,eAAe,OAAO,SAAS,MAAM;AAElG,oBAAY,IAAI,EAAE,UAAU,EAAE,UAAU,EAAE,UAAW,MAAM;AAAA,MAC7D,WAAW,EAAE,aAAa,eAAe,SAAS,aAAa,eAAe,OAAO,SAAS,MAAM;AAClG,oBAAY,IAAI,EAAE,UAAU,EAAE,UAAU,EAAE,UAAW,MAAM;AAAA,MAC7D;AAAA,IACF;AAAA,EACF;AAIA,QAAM,qBAAuC,CAAA;AAC7C,aAAW,EAAE,UAAU,KAAA,KAAU,YAAY,UAAU;AACrD,QAAI,OAAO,uBAAuB;AAEhC;AAAA,IACF;AACA,uBAAmB,KAAK,QAAQ;AAAA,EAClC;AAEA,MAAI,mBAAmB,WAAW,GAAG;AACnC,WAAO;AAAA,EACT;AAGA,MAAI,mBAAmB,MAAM,CAAC,MAAM,MAAM,WAAW,GAAG;AACtD,WAAO;AAAA,EACT;AAGA,MAAI,mBAAmB,MAAM,CAAC,MAAM,MAAM,SAAS,GAAG;AACpD,WAAO;AAAA,EACT;AAGA,MAAI,mBAAmB,MAAM,CAAC,MAAM,MAAM,SAAS,GAAG;AACpD,WAAO;AAAA,EACT;AAGA,SAAO;AACT;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"search-executor.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/search-executor.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,oBAAoB,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACpF,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AACpD,OAAO,KAAK,EAEV,QAAQ,EACR,YAAY,EAEb,MAAM,+BAA+B,CAAC;AAyBvC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AAIrE;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAAC;IACnG,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,kBAAkB,CAAC,EAAE,kBAAkB,CAAC;IACxC,aAAa,EAAE,WAAW,GAAG,SAAS,GAAG,QAAQ,CAAC;CACnD;AAOD;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAOhF;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,YAAY,EAClB,MAAM,EAAE,MAAM,GACb,QAAQ,GAAG,IAAI,CA2DjB;AA2CD;;GAEG;AACH,wBAAsB,aAAa,CACjC,OAAO,EAAE,oBAAoB,EAC7B,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,MAAM,EACd,YAAY,UAAO,GAClB,OAAO,CAAC,qBAAqB,CAAC,
|
|
1
|
+
{"version":3,"file":"search-executor.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/search-executor.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,oBAAoB,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACpF,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AACpD,OAAO,KAAK,EAEV,QAAQ,EACR,YAAY,EAEb,MAAM,+BAA+B,CAAC;AAyBvC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AAIrE;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAAC;IACnG,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,kBAAkB,CAAC,EAAE,kBAAkB,CAAC;IACxC,aAAa,EAAE,WAAW,GAAG,SAAS,GAAG,QAAQ,CAAC;CACnD;AAOD;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAOhF;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,YAAY,EAClB,MAAM,EAAE,MAAM,GACb,QAAQ,GAAG,IAAI,CA2DjB;AA2CD;;GAEG;AACH,wBAAsB,aAAa,CACjC,OAAO,EAAE,oBAAoB,EAC7B,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,MAAM,EACd,YAAY,UAAO,GAClB,OAAO,CAAC,qBAAqB,CAAC,CAuVhC;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,oBAAoB,EAC7B,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,WAAW,EAAE,CAAC,CAwExB;AAGD;;GAEG;AACH,wBAAsB,cAAc,CAClC,OAAO,EAAE,oBAAoB,EAC7B,MAAM,EAAE,MAAM,EACd,SAAS,SAAI,GACZ,OAAO,CAAC,aAAa,EAAE,CAAC,CAqF1B"}
|
|
@@ -255,7 +255,8 @@ async function executeSearch(options, sessionsDir, config, showProgress = true)
|
|
|
255
255
|
let totalHits = 0;
|
|
256
256
|
progress?.update(providerName, 0, 0, "in_progress");
|
|
257
257
|
const searchOptions = {
|
|
258
|
-
maxResults: options.maxResults ?? config.providers[providerName].max_results
|
|
258
|
+
maxResults: options.maxResults ?? config.providers[providerName].max_results,
|
|
259
|
+
...options.sort && { sort: options.sort }
|
|
259
260
|
};
|
|
260
261
|
for await (const article of provider.search(translatedQuery, searchOptions)) {
|
|
261
262
|
retrievedCount++;
|
|
@@ -289,7 +290,7 @@ async function executeSearch(options, sessionsDir, config, showProgress = true)
|
|
|
289
290
|
},
|
|
290
291
|
sessionsDir
|
|
291
292
|
);
|
|
292
|
-
const providerWarnings =
|
|
293
|
+
const providerWarnings = provider.getWarnings?.();
|
|
293
294
|
results[providerName] = { hits: totalHits, retrieved: retrievedCount, ...providerWarnings && providerWarnings.length > 0 && { warnings: providerWarnings } };
|
|
294
295
|
} catch (error) {
|
|
295
296
|
const errorMessage = error instanceof Error ? error.message : isProviderError(error) ? error.message : String(error);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"search-executor.js","sources":["../../../src/cli/commands/search-executor.ts"],"sourcesContent":["/**\n * Search executor for CLI search command.\n *\n * Handles the actual execution of searches across multiple providers,\n * including session creation, progress display, and result storage.\n */\nimport { readFile, writeFile, appendFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { createHash } from 'node:crypto';\nimport type { SearchCommandOptions, CountResult, PreviewResult } from './search.js';\nimport type { Config } from '../../config/index.js';\nimport type {\n Article,\n Provider,\n ProviderName,\n TranslatedQuery,\n} from '../../providers/base/types.js';\nimport { isProviderError } from '../../providers/base/types.js';\nimport type { QueryAST } from '../../query/types.js';\nimport { parseQueryString } from '../../query/index.js';\nimport { resolveForProvider } from '../../query/resolver.js';\nimport {\n createSession,\n updateDatabaseStatus,\n updateSessionStatus,\n} from '../../session/manager.js';\nimport { MultiProviderProgress } from '../utils/progress.js';\nimport { PubMedProvider } from '../../providers/pubmed/provider.js';\nimport type { PubMedConfig } from '../../providers/pubmed/types.js';\nimport { ERICProvider } from '../../providers/eric/provider.js';\nimport { ArxivProvider } from '../../providers/arxiv/provider.js';\nimport { ScopusProvider } from '../../providers/scopus/provider.js';\nimport type { ScopusConfig } from '../../providers/scopus/types.js';\nimport { translateQuery as translatePubmed } from '../../providers/pubmed/translator.js';\nimport { translateQuery as translateEric } from '../../providers/eric/translator.js';\nimport { translateQuery as translateArxiv } from '../../providers/arxiv/translator.js';\nimport { translateQuery as translateScopus } from '../../providers/scopus/translator.js';\nimport { stringify as stringifyYaml } from 'yaml';\nimport { registerArticles, saveRegistrationRecord } from '../../integration/register.js';\nimport { buildFailureErrorMessage, buildPartialErrorMessage } from './search-utils.js';\nimport { getConfigDir } from '../../config/paths.js';\nimport type { RegistrationRecord } from '../../integration/types.js';\nimport { checkRefAvailable } from '../../integration/ref-cli.js';\nimport { convertResultsToYaml, loadResults } from '../../session/results-io.js';\n\n/**\n * Result of a search execution.\n */\nexport interface SearchExecutionResult {\n success: boolean;\n sessionId?: string;\n results?: Record<string, { hits: number; retrieved: number; error?: string; warnings?: string[] }>;\n error?: string;\n autoRegisterResult?: RegistrationRecord;\n sessionStatus: 'completed' | 'partial' | 'failed';\n}\n\n/**\n * Available providers that are implemented.\n */\nconst IMPLEMENTED_PROVIDERS: ProviderName[] = ['pubmed', 'eric', 'arxiv', 'scopus'];\n\n/**\n * Check if a provider has the required configuration (e.g., API keys).\n * Providers that require no special configuration always return true.\n */\nexport function isProviderConfigured(name: ProviderName, config: Config): boolean {\n switch (name) {\n case 'scopus':\n return !!config.providers.scopus.api_key;\n default:\n return true; // pubmed, eric, arxiv require no API key\n }\n}\n\n/**\n * Create a provider instance for the given provider name.\n */\nexport function createProviderInstance(\n name: ProviderName,\n config: Config\n): Provider | null {\n const providerConfig = config.providers[name];\n\n switch (name) {\n case 'pubmed': {\n if (!providerConfig.email) {\n const configPath = getConfigDir();\n console.warn(\n `Warning: No email configured for PubMed.\\n` +\n ` → Edit ${configPath}/config.toml and set providers.pubmed.email\\n` +\n ` → Or run: search-hub config providers.pubmed.email \"your@email.com\"`\n );\n }\n const pubmedOpts: PubMedConfig = {\n email: providerConfig.email ?? 'search-hub@example.com',\n rateLimit: providerConfig.rate_limit,\n timeout: providerConfig.timeout,\n retries: providerConfig.retries,\n };\n if (providerConfig.api_key) {\n pubmedOpts.apiKey = providerConfig.api_key;\n }\n return new PubMedProvider(pubmedOpts);\n }\n case 'eric':\n return new ERICProvider({\n rateLimit: providerConfig.rate_limit,\n timeout: providerConfig.timeout,\n retries: providerConfig.retries,\n });\n case 'arxiv':\n return new ArxivProvider({\n rateLimit: providerConfig.rate_limit,\n timeout: providerConfig.timeout,\n retries: providerConfig.retries,\n });\n case 'scopus': {\n if (!providerConfig.api_key) {\n console.warn(\n `Warning: Scopus requires an API key. Set providers.scopus.api_key in config.\\n` +\n ` → Get an API key at https://dev.elsevier.com/\\n` +\n ` → Run: search-hub config providers.scopus.api_key \"your-key\"`\n );\n return null;\n }\n const scopusOpts: ScopusConfig = {\n apiKey: providerConfig.api_key,\n rateLimit: providerConfig.rate_limit,\n timeout: providerConfig.timeout,\n retries: providerConfig.retries,\n };\n if (providerConfig.inst_token) {\n scopusOpts.instToken = providerConfig.inst_token;\n }\n return new ScopusProvider(scopusOpts);\n }\n default:\n throw new Error(`Provider '${name}' is not implemented`);\n }\n}\n\n/**\n * Translate a query AST for a specific provider.\n * Resolves provider-specific blocks/filters before translation.\n */\nfunction translateQueryForProvider(\n ast: QueryAST,\n provider: ProviderName\n): TranslatedQuery {\n const resolved = resolveForProvider(ast, provider);\n switch (provider) {\n case 'pubmed':\n return translatePubmed(resolved);\n case 'eric':\n return translateEric(resolved);\n case 'arxiv':\n return translateArxiv(resolved);\n case 'scopus':\n return translateScopus(resolved);\n default:\n throw new Error(`No translator for provider '${provider}'`);\n }\n}\n\n/**\n * Get enabled providers from config, optionally filtered by user selection.\n */\nfunction getEnabledProviders(\n config: Config,\n requestedProviders?: ProviderName[]\n): ProviderName[] {\n const enabledInConfig = IMPLEMENTED_PROVIDERS.filter(\n (name) => config.providers[name].enabled\n );\n\n if (requestedProviders && requestedProviders.length > 0) {\n return requestedProviders.filter((p) => enabledInConfig.includes(p));\n }\n\n return enabledInConfig;\n}\n\n/**\n * Execute a search across multiple providers.\n */\nexport async function executeSearch(\n options: SearchCommandOptions,\n sessionsDir: string,\n config: Config,\n showProgress = true\n): Promise<SearchExecutionResult> {\n let ast: QueryAST | undefined;\n let queryContent: string;\n let queryFile: string;\n\n // Handle direct query mode\n if (options.directQuery && options.providers && options.providers.length === 1) {\n queryFile = 'direct-query';\n\n // For direct query, we create a minimal AST structure\n ast = {\n name: options.sessionName ?? 'direct-query',\n blocks: [\n {\n id: 'direct',\n field: 'all',\n terms: { keywords: [options.directQuery] },\n operator: 'AND',\n },\n ],\n filters: {},\n providers: {},\n };\n\n // Generate YAML safely using yaml library to handle special characters\n queryContent = stringifyYaml({\n name: ast.name,\n blocks: ast.blocks,\n filters: ast.filters,\n });\n } else if (options.queryFile) {\n // Parse query file\n try {\n queryContent = await readFile(options.queryFile, 'utf-8');\n ast = parseQueryString(queryContent);\n queryFile = options.queryFile;\n } catch (error) {\n return {\n success: false,\n sessionStatus: 'failed',\n error: `Failed to parse query file: ${error instanceof Error ? error.message : error}`,\n };\n }\n } else {\n return {\n success: false,\n sessionStatus: 'failed',\n error: 'Either queryFile or directQuery with provider is required',\n };\n }\n\n // Determine which providers to use\n let providers = getEnabledProviders(config, options.providers);\n\n // In default mode (no --db), skip unconfigured providers with a warning\n const isExplicitSelection = options.providers && options.providers.length > 0;\n if (!isExplicitSelection) {\n const skipped: ProviderName[] = [];\n providers = providers.filter((name) => {\n if (!isProviderConfigured(name, config)) {\n skipped.push(name);\n return false;\n }\n return true;\n });\n for (const name of skipped) {\n console.warn(\n `Skipping ${name}: API key not configured (use --db ${name} to force)`\n );\n }\n }\n\n if (providers.length === 0) {\n return {\n success: false,\n sessionStatus: 'failed',\n error: 'No providers enabled or selected',\n };\n }\n\n // Create query hash\n const queryHash = createHash('sha256').update(queryContent).digest('hex').slice(0, 8);\n\n // Create session\n let session;\n try {\n const sessionOpts: Parameters<typeof createSession>[0] = {\n name: options.sessionName ?? ast.name,\n queryFile,\n queryContent,\n queryHash,\n targets: providers,\n sessionsDir,\n };\n if (ast.description) {\n sessionOpts.description = ast.description;\n }\n session = await createSession(sessionOpts);\n } catch (error) {\n return {\n success: false,\n sessionStatus: 'failed',\n error: `Failed to create session: ${error instanceof Error ? error.message : error}`,\n };\n }\n\n const sessionId = session.id;\n const results: Record<string, { hits: number; retrieved: number; error?: string; warnings?: string[] }> = {};\n\n // Create progress display if enabled\n let progress: MultiProviderProgress | undefined;\n if (showProgress && process.stdout.isTTY) {\n progress = new MultiProviderProgress(providers);\n }\n\n // Execute search for each provider\n for (const providerName of providers) {\n try {\n // Create provider instance\n const provider = createProviderInstance(providerName, config);\n\n // Skip provider if it could not be created (e.g. missing configuration)\n if (provider === null) {\n const configError = `${providerName}: provider configuration incomplete. See warning above for details.`;\n results[providerName] = { hits: 0, retrieved: 0, error: configError };\n await updateDatabaseStatus(\n sessionId,\n providerName,\n {\n status: 'failed',\n completedAt: new Date().toISOString(),\n error: {\n code: 'CONFIG_ERROR',\n message: configError,\n retryable: false,\n },\n },\n sessionsDir\n );\n continue;\n }\n\n // Translate query\n let translatedQuery: TranslatedQuery;\n if (options.directQuery && options.providers?.length === 1) {\n // For direct query, use the native query string directly\n translatedQuery = {\n native: options.directQuery,\n provider: providerName,\n };\n } else {\n translatedQuery = translateQueryForProvider(ast, providerName);\n }\n\n // Write translated query to session\n const queryPath = join(sessionsDir, sessionId, `${providerName}_query.txt`);\n await writeFile(queryPath, translatedQuery.native, 'utf-8');\n\n // Update database status to in_progress\n await updateDatabaseStatus(\n sessionId,\n providerName,\n {\n status: 'in_progress',\n startedAt: new Date().toISOString(),\n },\n sessionsDir\n );\n\n // Prepare results file path\n const resultsPath = join(sessionsDir, sessionId, `${providerName}_results.jsonl`);\n\n // Execute search\n let retrievedCount = 0;\n let totalHits = 0;\n\n progress?.update(providerName, 0, 0, 'in_progress');\n\n const searchOptions = {\n maxResults: options.maxResults ?? config.providers[providerName].max_results,\n };\n\n for await (const article of provider.search(translatedQuery, searchOptions)) {\n retrievedCount++;\n\n // Write article to JSONL file\n await appendFile(resultsPath, JSON.stringify(article) + '\\n', 'utf-8');\n\n // Update progress (estimate total from first batch)\n if (totalHits === 0) {\n // Estimate total - this is provider-dependent, we'll use retrieved count as minimum\n totalHits = Math.max(retrievedCount * 10, 100);\n }\n progress?.update(providerName, retrievedCount, totalHits, 'in_progress');\n }\n\n // Update final totals\n totalHits = retrievedCount; // Use actual count as final total\n\n // Mark as completed\n progress?.complete(providerName);\n\n // Convert JSONL to YAML for human-readable view\n const yamlFilename = `${providerName}_results.yaml`;\n const yamlPath = join(sessionsDir, sessionId, yamlFilename);\n await convertResultsToYaml(resultsPath, yamlPath, {\n provider: providerName,\n queryName: ast.name,\n });\n\n // Update database status\n await updateDatabaseStatus(\n sessionId,\n providerName,\n {\n status: 'completed',\n completedAt: new Date().toISOString(),\n totalHits,\n retrievedCount,\n files: {\n query: `${providerName}_query.txt`,\n results: `${providerName}_results.jsonl`,\n resultsYaml: yamlFilename,\n },\n },\n sessionsDir\n );\n\n // Collect warnings if provider supports them\n const providerWarnings = 'getWarnings' in provider && typeof (provider as any).getWarnings === 'function'\n ? (provider as any).getWarnings() as string[]\n : undefined;\n results[providerName] = { hits: totalHits, retrieved: retrievedCount, ...(providerWarnings && providerWarnings.length > 0 && { warnings: providerWarnings }) };\n } catch (error) {\n const errorMessage = error instanceof Error\n ? error.message\n : isProviderError(error)\n ? error.message\n : String(error);\n\n progress?.fail(providerName, errorMessage);\n\n // Update database status with error\n await updateDatabaseStatus(\n sessionId,\n providerName,\n {\n status: 'failed',\n completedAt: new Date().toISOString(),\n error: {\n code: 'SEARCH_ERROR',\n message: errorMessage,\n retryable: true,\n },\n },\n sessionsDir\n );\n\n results[providerName] = { hits: 0, retrieved: 0, error: errorMessage };\n }\n }\n\n // Stop progress display\n progress?.stop();\n\n // Determine overall session status\n const anyFailed = providers.some((p) => {\n const r = results[p];\n return r && r.error !== undefined;\n });\n const anySucceeded = providers.some((p) => {\n const r = results[p];\n return r && r.retrieved > 0;\n });\n\n let sessionStatus: 'completed' | 'partial' | 'failed';\n if (!anyFailed) {\n sessionStatus = 'completed';\n } else if (anySucceeded) {\n sessionStatus = 'partial';\n } else {\n sessionStatus = 'failed';\n }\n\n // Update session status\n await updateSessionStatus(sessionId, sessionStatus, sessionsDir);\n\n if (sessionStatus === 'failed') {\n return {\n success: false,\n sessionId,\n sessionStatus,\n results,\n error: buildFailureErrorMessage(results),\n };\n }\n\n // In strict mode, partial success is treated as failure\n if (options.strict && sessionStatus === 'partial') {\n return {\n success: false,\n sessionId,\n sessionStatus,\n results,\n error: buildPartialErrorMessage(results),\n };\n }\n\n // Auto-register if enabled\n let autoRegisterResult: RegistrationRecord | undefined;\n if (\n config.integration.reference_manager.enabled &&\n config.integration.reference_manager.auto_register\n ) {\n const refAvailable = await checkRefAvailable();\n if (refAvailable) {\n // Load all articles from results files\n const allArticles = await loadArticlesFromSession(sessionsDir, sessionId, providers);\n\n if (allArticles.length > 0) {\n autoRegisterResult = await registerArticles(allArticles, {\n sessionId,\n sessionDir: join(sessionsDir, sessionId),\n withAbstracts: config.integration.reference_manager.with_abstracts,\n });\n\n // Save registration record\n await saveRegistrationRecord(join(sessionsDir, sessionId), autoRegisterResult);\n }\n }\n }\n\n const result: SearchExecutionResult = {\n success: true,\n sessionId,\n results,\n sessionStatus,\n };\n\n if (autoRegisterResult) {\n result.autoRegisterResult = autoRegisterResult;\n }\n\n return result;\n}\n\n/**\n * Execute count-only mode: get hit counts from each provider without downloading results.\n * No session is created.\n */\nexport async function executeCountOnly(\n options: SearchCommandOptions,\n config: Config\n): Promise<CountResult[]> {\n let ast: QueryAST | undefined;\n\n // Handle direct query mode\n if (options.directQuery && options.providers && options.providers.length === 1) {\n ast = {\n name: options.sessionName ?? 'direct-query',\n blocks: [\n {\n id: 'direct',\n field: 'all',\n terms: { keywords: [options.directQuery] },\n operator: 'AND',\n },\n ],\n filters: {},\n providers: {},\n };\n } else if (options.queryFile) {\n const queryContent = await readFile(options.queryFile, 'utf-8');\n ast = parseQueryString(queryContent);\n } else {\n return [];\n }\n\n // Determine which providers to use\n let providers = getEnabledProviders(config, options.providers);\n\n // In default mode (no --db), skip unconfigured providers\n const isExplicitSelection = options.providers && options.providers.length > 0;\n if (!isExplicitSelection) {\n providers = providers.filter((name) => isProviderConfigured(name, config));\n }\n\n if (providers.length === 0) {\n return [];\n }\n\n // Execute count for each provider concurrently\n const results: CountResult[] = await Promise.all(\n providers.map(async (providerName): Promise<CountResult> => {\n try {\n const provider = createProviderInstance(providerName, config);\n if (provider === null) {\n return { provider: providerName, count: 0, error: 'Provider configuration incomplete' };\n }\n\n // Translate query\n let translatedQuery: TranslatedQuery;\n if (options.directQuery && options.providers?.length === 1) {\n translatedQuery = {\n native: options.directQuery,\n provider: providerName,\n };\n } else {\n translatedQuery = translateQueryForProvider(ast!, providerName);\n }\n\n const count = await provider.count(translatedQuery);\n return { provider: providerName, count };\n } catch (error) {\n const errorMessage = error instanceof Error\n ? error.message\n : isProviderError(error)\n ? error.message\n : String(error);\n return { provider: providerName, count: 0, error: errorMessage };\n }\n })\n );\n\n return results;\n}\n\n\n/**\n * Execute preview mode: get counts and first few titles without creating a session.\n */\nexport async function executePreview(\n options: SearchCommandOptions,\n config: Config,\n maxTitles = 5\n): Promise<PreviewResult[]> {\n let ast: QueryAST | undefined;\n\n // Handle direct query mode\n if (options.directQuery && options.providers && options.providers.length === 1) {\n ast = {\n name: options.sessionName ?? 'direct-query',\n blocks: [\n {\n id: 'direct',\n field: 'all',\n terms: { keywords: [options.directQuery] },\n operator: 'AND',\n },\n ],\n filters: {},\n providers: {},\n };\n } else if (options.queryFile) {\n const queryContent = await readFile(options.queryFile, 'utf-8');\n ast = parseQueryString(queryContent);\n } else {\n return [];\n }\n\n // Determine which providers to use\n let providers = getEnabledProviders(config, options.providers);\n\n // In default mode (no --db), skip unconfigured providers\n const isExplicitSelection = options.providers && options.providers.length > 0;\n if (!isExplicitSelection) {\n providers = providers.filter((name) => isProviderConfigured(name, config));\n }\n\n if (providers.length === 0) {\n return [];\n }\n\n // Execute preview for each provider concurrently\n const results: PreviewResult[] = await Promise.all(\n providers.map(async (providerName): Promise<PreviewResult> => {\n try {\n const provider = createProviderInstance(providerName, config);\n if (provider === null) {\n return { provider: providerName, count: 0, titles: [], error: 'Provider configuration incomplete' };\n }\n\n // Translate query\n let translatedQuery: TranslatedQuery;\n if (options.directQuery && options.providers?.length === 1) {\n translatedQuery = {\n native: options.directQuery,\n provider: providerName,\n };\n } else {\n translatedQuery = translateQueryForProvider(ast!, providerName);\n }\n\n // Get count first\n const count = await provider.count(translatedQuery);\n\n // Collect first few articles for titles\n const titles: string[] = [];\n const searchOptions = { maxResults: maxTitles };\n\n for await (const article of provider.search(translatedQuery, searchOptions)) {\n titles.push(article.title);\n if (titles.length >= maxTitles) {\n break;\n }\n }\n\n return { provider: providerName, count, titles };\n } catch (error) {\n const errorMessage = error instanceof Error\n ? error.message\n : isProviderError(error)\n ? error.message\n : String(error);\n return { provider: providerName, count: 0, titles: [], error: errorMessage };\n }\n })\n );\n\n return results;\n}\n\n/**\n * Load all articles from a session's results files (YAML preferred, JSONL fallback).\n */\nasync function loadArticlesFromSession(\n sessionsDir: string,\n sessionId: string,\n providers: ProviderName[]\n): Promise<Article[]> {\n const articles: Article[] = [];\n const sessionDir = join(sessionsDir, sessionId);\n\n for (const provider of providers) {\n const providerArticles = await loadResults(sessionDir, provider);\n articles.push(...providerArticles);\n }\n\n return articles;\n}\n"],"names":["translatePubmed","translateEric","translateArxiv","translateScopus","stringifyYaml"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"search-executor.js","sources":["../../../src/cli/commands/search-executor.ts"],"sourcesContent":["/**\n * Search executor for CLI search command.\n *\n * Handles the actual execution of searches across multiple providers,\n * including session creation, progress display, and result storage.\n */\nimport { readFile, writeFile, appendFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { createHash } from 'node:crypto';\nimport type { SearchCommandOptions, CountResult, PreviewResult } from './search.js';\nimport type { Config } from '../../config/index.js';\nimport type {\n Article,\n Provider,\n ProviderName,\n TranslatedQuery,\n} from '../../providers/base/types.js';\nimport { isProviderError } from '../../providers/base/types.js';\nimport type { QueryAST } from '../../query/types.js';\nimport { parseQueryString } from '../../query/index.js';\nimport { resolveForProvider } from '../../query/resolver.js';\nimport {\n createSession,\n updateDatabaseStatus,\n updateSessionStatus,\n} from '../../session/manager.js';\nimport { MultiProviderProgress } from '../utils/progress.js';\nimport { PubMedProvider } from '../../providers/pubmed/provider.js';\nimport type { PubMedConfig } from '../../providers/pubmed/types.js';\nimport { ERICProvider } from '../../providers/eric/provider.js';\nimport { ArxivProvider } from '../../providers/arxiv/provider.js';\nimport { ScopusProvider } from '../../providers/scopus/provider.js';\nimport type { ScopusConfig } from '../../providers/scopus/types.js';\nimport { translateQuery as translatePubmed } from '../../providers/pubmed/translator.js';\nimport { translateQuery as translateEric } from '../../providers/eric/translator.js';\nimport { translateQuery as translateArxiv } from '../../providers/arxiv/translator.js';\nimport { translateQuery as translateScopus } from '../../providers/scopus/translator.js';\nimport { stringify as stringifyYaml } from 'yaml';\nimport { registerArticles, saveRegistrationRecord } from '../../integration/register.js';\nimport { buildFailureErrorMessage, buildPartialErrorMessage } from './search-utils.js';\nimport { getConfigDir } from '../../config/paths.js';\nimport type { RegistrationRecord } from '../../integration/types.js';\nimport { checkRefAvailable } from '../../integration/ref-cli.js';\nimport { convertResultsToYaml, loadResults } from '../../session/results-io.js';\n\n/**\n * Result of a search execution.\n */\nexport interface SearchExecutionResult {\n success: boolean;\n sessionId?: string;\n results?: Record<string, { hits: number; retrieved: number; error?: string; warnings?: string[] }>;\n error?: string;\n autoRegisterResult?: RegistrationRecord;\n sessionStatus: 'completed' | 'partial' | 'failed';\n}\n\n/**\n * Available providers that are implemented.\n */\nconst IMPLEMENTED_PROVIDERS: ProviderName[] = ['pubmed', 'eric', 'arxiv', 'scopus'];\n\n/**\n * Check if a provider has the required configuration (e.g., API keys).\n * Providers that require no special configuration always return true.\n */\nexport function isProviderConfigured(name: ProviderName, config: Config): boolean {\n switch (name) {\n case 'scopus':\n return !!config.providers.scopus.api_key;\n default:\n return true; // pubmed, eric, arxiv require no API key\n }\n}\n\n/**\n * Create a provider instance for the given provider name.\n */\nexport function createProviderInstance(\n name: ProviderName,\n config: Config\n): Provider | null {\n const providerConfig = config.providers[name];\n\n switch (name) {\n case 'pubmed': {\n if (!providerConfig.email) {\n const configPath = getConfigDir();\n console.warn(\n `Warning: No email configured for PubMed.\\n` +\n ` → Edit ${configPath}/config.toml and set providers.pubmed.email\\n` +\n ` → Or run: search-hub config providers.pubmed.email \"your@email.com\"`\n );\n }\n const pubmedOpts: PubMedConfig = {\n email: providerConfig.email ?? 'search-hub@example.com',\n rateLimit: providerConfig.rate_limit,\n timeout: providerConfig.timeout,\n retries: providerConfig.retries,\n };\n if (providerConfig.api_key) {\n pubmedOpts.apiKey = providerConfig.api_key;\n }\n return new PubMedProvider(pubmedOpts);\n }\n case 'eric':\n return new ERICProvider({\n rateLimit: providerConfig.rate_limit,\n timeout: providerConfig.timeout,\n retries: providerConfig.retries,\n });\n case 'arxiv':\n return new ArxivProvider({\n rateLimit: providerConfig.rate_limit,\n timeout: providerConfig.timeout,\n retries: providerConfig.retries,\n });\n case 'scopus': {\n if (!providerConfig.api_key) {\n console.warn(\n `Warning: Scopus requires an API key. Set providers.scopus.api_key in config.\\n` +\n ` → Get an API key at https://dev.elsevier.com/\\n` +\n ` → Run: search-hub config providers.scopus.api_key \"your-key\"`\n );\n return null;\n }\n const scopusOpts: ScopusConfig = {\n apiKey: providerConfig.api_key,\n rateLimit: providerConfig.rate_limit,\n timeout: providerConfig.timeout,\n retries: providerConfig.retries,\n };\n if (providerConfig.inst_token) {\n scopusOpts.instToken = providerConfig.inst_token;\n }\n return new ScopusProvider(scopusOpts);\n }\n default:\n throw new Error(`Provider '${name}' is not implemented`);\n }\n}\n\n/**\n * Translate a query AST for a specific provider.\n * Resolves provider-specific blocks/filters before translation.\n */\nfunction translateQueryForProvider(\n ast: QueryAST,\n provider: ProviderName\n): TranslatedQuery {\n const resolved = resolveForProvider(ast, provider);\n switch (provider) {\n case 'pubmed':\n return translatePubmed(resolved);\n case 'eric':\n return translateEric(resolved);\n case 'arxiv':\n return translateArxiv(resolved);\n case 'scopus':\n return translateScopus(resolved);\n default:\n throw new Error(`No translator for provider '${provider}'`);\n }\n}\n\n/**\n * Get enabled providers from config, optionally filtered by user selection.\n */\nfunction getEnabledProviders(\n config: Config,\n requestedProviders?: ProviderName[]\n): ProviderName[] {\n const enabledInConfig = IMPLEMENTED_PROVIDERS.filter(\n (name) => config.providers[name].enabled\n );\n\n if (requestedProviders && requestedProviders.length > 0) {\n return requestedProviders.filter((p) => enabledInConfig.includes(p));\n }\n\n return enabledInConfig;\n}\n\n/**\n * Execute a search across multiple providers.\n */\nexport async function executeSearch(\n options: SearchCommandOptions,\n sessionsDir: string,\n config: Config,\n showProgress = true\n): Promise<SearchExecutionResult> {\n let ast: QueryAST | undefined;\n let queryContent: string;\n let queryFile: string;\n\n // Handle direct query mode\n if (options.directQuery && options.providers && options.providers.length === 1) {\n queryFile = 'direct-query';\n\n // For direct query, we create a minimal AST structure\n ast = {\n name: options.sessionName ?? 'direct-query',\n blocks: [\n {\n id: 'direct',\n field: 'all',\n terms: { keywords: [options.directQuery] },\n operator: 'AND',\n },\n ],\n filters: {},\n providers: {},\n };\n\n // Generate YAML safely using yaml library to handle special characters\n queryContent = stringifyYaml({\n name: ast.name,\n blocks: ast.blocks,\n filters: ast.filters,\n });\n } else if (options.queryFile) {\n // Parse query file\n try {\n queryContent = await readFile(options.queryFile, 'utf-8');\n ast = parseQueryString(queryContent);\n queryFile = options.queryFile;\n } catch (error) {\n return {\n success: false,\n sessionStatus: 'failed',\n error: `Failed to parse query file: ${error instanceof Error ? error.message : error}`,\n };\n }\n } else {\n return {\n success: false,\n sessionStatus: 'failed',\n error: 'Either queryFile or directQuery with provider is required',\n };\n }\n\n // Determine which providers to use\n let providers = getEnabledProviders(config, options.providers);\n\n // In default mode (no --db), skip unconfigured providers with a warning\n const isExplicitSelection = options.providers && options.providers.length > 0;\n if (!isExplicitSelection) {\n const skipped: ProviderName[] = [];\n providers = providers.filter((name) => {\n if (!isProviderConfigured(name, config)) {\n skipped.push(name);\n return false;\n }\n return true;\n });\n for (const name of skipped) {\n console.warn(\n `Skipping ${name}: API key not configured (use --db ${name} to force)`\n );\n }\n }\n\n if (providers.length === 0) {\n return {\n success: false,\n sessionStatus: 'failed',\n error: 'No providers enabled or selected',\n };\n }\n\n // Create query hash\n const queryHash = createHash('sha256').update(queryContent).digest('hex').slice(0, 8);\n\n // Create session\n let session;\n try {\n const sessionOpts: Parameters<typeof createSession>[0] = {\n name: options.sessionName ?? ast.name,\n queryFile,\n queryContent,\n queryHash,\n targets: providers,\n sessionsDir,\n };\n if (ast.description) {\n sessionOpts.description = ast.description;\n }\n session = await createSession(sessionOpts);\n } catch (error) {\n return {\n success: false,\n sessionStatus: 'failed',\n error: `Failed to create session: ${error instanceof Error ? error.message : error}`,\n };\n }\n\n const sessionId = session.id;\n const results: Record<string, { hits: number; retrieved: number; error?: string; warnings?: string[] }> = {};\n\n // Create progress display if enabled\n let progress: MultiProviderProgress | undefined;\n if (showProgress && process.stdout.isTTY) {\n progress = new MultiProviderProgress(providers);\n }\n\n // Execute search for each provider\n for (const providerName of providers) {\n try {\n // Create provider instance\n const provider = createProviderInstance(providerName, config);\n\n // Skip provider if it could not be created (e.g. missing configuration)\n if (provider === null) {\n const configError = `${providerName}: provider configuration incomplete. See warning above for details.`;\n results[providerName] = { hits: 0, retrieved: 0, error: configError };\n await updateDatabaseStatus(\n sessionId,\n providerName,\n {\n status: 'failed',\n completedAt: new Date().toISOString(),\n error: {\n code: 'CONFIG_ERROR',\n message: configError,\n retryable: false,\n },\n },\n sessionsDir\n );\n continue;\n }\n\n // Translate query\n let translatedQuery: TranslatedQuery;\n if (options.directQuery && options.providers?.length === 1) {\n // For direct query, use the native query string directly\n translatedQuery = {\n native: options.directQuery,\n provider: providerName,\n };\n } else {\n translatedQuery = translateQueryForProvider(ast, providerName);\n }\n\n // Write translated query to session\n const queryPath = join(sessionsDir, sessionId, `${providerName}_query.txt`);\n await writeFile(queryPath, translatedQuery.native, 'utf-8');\n\n // Update database status to in_progress\n await updateDatabaseStatus(\n sessionId,\n providerName,\n {\n status: 'in_progress',\n startedAt: new Date().toISOString(),\n },\n sessionsDir\n );\n\n // Prepare results file path\n const resultsPath = join(sessionsDir, sessionId, `${providerName}_results.jsonl`);\n\n // Execute search\n let retrievedCount = 0;\n let totalHits = 0;\n\n progress?.update(providerName, 0, 0, 'in_progress');\n\n const searchOptions = {\n maxResults: options.maxResults ?? config.providers[providerName].max_results,\n ...(options.sort && { sort: options.sort }),\n };\n\n for await (const article of provider.search(translatedQuery, searchOptions)) {\n retrievedCount++;\n\n // Write article to JSONL file\n await appendFile(resultsPath, JSON.stringify(article) + '\\n', 'utf-8');\n\n // Update progress (estimate total from first batch)\n if (totalHits === 0) {\n // Estimate total - this is provider-dependent, we'll use retrieved count as minimum\n totalHits = Math.max(retrievedCount * 10, 100);\n }\n progress?.update(providerName, retrievedCount, totalHits, 'in_progress');\n }\n\n // Update final totals\n totalHits = retrievedCount; // Use actual count as final total\n\n // Mark as completed\n progress?.complete(providerName);\n\n // Convert JSONL to YAML for human-readable view\n const yamlFilename = `${providerName}_results.yaml`;\n const yamlPath = join(sessionsDir, sessionId, yamlFilename);\n await convertResultsToYaml(resultsPath, yamlPath, {\n provider: providerName,\n queryName: ast.name,\n });\n\n // Update database status\n await updateDatabaseStatus(\n sessionId,\n providerName,\n {\n status: 'completed',\n completedAt: new Date().toISOString(),\n totalHits,\n retrievedCount,\n files: {\n query: `${providerName}_query.txt`,\n results: `${providerName}_results.jsonl`,\n resultsYaml: yamlFilename,\n },\n },\n sessionsDir\n );\n\n // Collect warnings if provider supports them\n const providerWarnings = provider.getWarnings?.();\n results[providerName] = { hits: totalHits, retrieved: retrievedCount, ...(providerWarnings && providerWarnings.length > 0 && { warnings: providerWarnings }) };\n } catch (error) {\n const errorMessage = error instanceof Error\n ? error.message\n : isProviderError(error)\n ? error.message\n : String(error);\n\n progress?.fail(providerName, errorMessage);\n\n // Update database status with error\n await updateDatabaseStatus(\n sessionId,\n providerName,\n {\n status: 'failed',\n completedAt: new Date().toISOString(),\n error: {\n code: 'SEARCH_ERROR',\n message: errorMessage,\n retryable: true,\n },\n },\n sessionsDir\n );\n\n results[providerName] = { hits: 0, retrieved: 0, error: errorMessage };\n }\n }\n\n // Stop progress display\n progress?.stop();\n\n // Determine overall session status\n const anyFailed = providers.some((p) => {\n const r = results[p];\n return r && r.error !== undefined;\n });\n const anySucceeded = providers.some((p) => {\n const r = results[p];\n return r && r.retrieved > 0;\n });\n\n let sessionStatus: 'completed' | 'partial' | 'failed';\n if (!anyFailed) {\n sessionStatus = 'completed';\n } else if (anySucceeded) {\n sessionStatus = 'partial';\n } else {\n sessionStatus = 'failed';\n }\n\n // Update session status\n await updateSessionStatus(sessionId, sessionStatus, sessionsDir);\n\n if (sessionStatus === 'failed') {\n return {\n success: false,\n sessionId,\n sessionStatus,\n results,\n error: buildFailureErrorMessage(results),\n };\n }\n\n // In strict mode, partial success is treated as failure\n if (options.strict && sessionStatus === 'partial') {\n return {\n success: false,\n sessionId,\n sessionStatus,\n results,\n error: buildPartialErrorMessage(results),\n };\n }\n\n // Auto-register if enabled\n let autoRegisterResult: RegistrationRecord | undefined;\n if (\n config.integration.reference_manager.enabled &&\n config.integration.reference_manager.auto_register\n ) {\n const refAvailable = await checkRefAvailable();\n if (refAvailable) {\n // Load all articles from results files\n const allArticles = await loadArticlesFromSession(sessionsDir, sessionId, providers);\n\n if (allArticles.length > 0) {\n autoRegisterResult = await registerArticles(allArticles, {\n sessionId,\n sessionDir: join(sessionsDir, sessionId),\n withAbstracts: config.integration.reference_manager.with_abstracts,\n });\n\n // Save registration record\n await saveRegistrationRecord(join(sessionsDir, sessionId), autoRegisterResult);\n }\n }\n }\n\n const result: SearchExecutionResult = {\n success: true,\n sessionId,\n results,\n sessionStatus,\n };\n\n if (autoRegisterResult) {\n result.autoRegisterResult = autoRegisterResult;\n }\n\n return result;\n}\n\n/**\n * Execute count-only mode: get hit counts from each provider without downloading results.\n * No session is created.\n */\nexport async function executeCountOnly(\n options: SearchCommandOptions,\n config: Config\n): Promise<CountResult[]> {\n let ast: QueryAST | undefined;\n\n // Handle direct query mode\n if (options.directQuery && options.providers && options.providers.length === 1) {\n ast = {\n name: options.sessionName ?? 'direct-query',\n blocks: [\n {\n id: 'direct',\n field: 'all',\n terms: { keywords: [options.directQuery] },\n operator: 'AND',\n },\n ],\n filters: {},\n providers: {},\n };\n } else if (options.queryFile) {\n const queryContent = await readFile(options.queryFile, 'utf-8');\n ast = parseQueryString(queryContent);\n } else {\n return [];\n }\n\n // Determine which providers to use\n let providers = getEnabledProviders(config, options.providers);\n\n // In default mode (no --db), skip unconfigured providers\n const isExplicitSelection = options.providers && options.providers.length > 0;\n if (!isExplicitSelection) {\n providers = providers.filter((name) => isProviderConfigured(name, config));\n }\n\n if (providers.length === 0) {\n return [];\n }\n\n // Execute count for each provider concurrently\n const results: CountResult[] = await Promise.all(\n providers.map(async (providerName): Promise<CountResult> => {\n try {\n const provider = createProviderInstance(providerName, config);\n if (provider === null) {\n return { provider: providerName, count: 0, error: 'Provider configuration incomplete' };\n }\n\n // Translate query\n let translatedQuery: TranslatedQuery;\n if (options.directQuery && options.providers?.length === 1) {\n translatedQuery = {\n native: options.directQuery,\n provider: providerName,\n };\n } else {\n translatedQuery = translateQueryForProvider(ast!, providerName);\n }\n\n const count = await provider.count(translatedQuery);\n return { provider: providerName, count };\n } catch (error) {\n const errorMessage = error instanceof Error\n ? error.message\n : isProviderError(error)\n ? error.message\n : String(error);\n return { provider: providerName, count: 0, error: errorMessage };\n }\n })\n );\n\n return results;\n}\n\n\n/**\n * Execute preview mode: get counts and first few titles without creating a session.\n */\nexport async function executePreview(\n options: SearchCommandOptions,\n config: Config,\n maxTitles = 5\n): Promise<PreviewResult[]> {\n let ast: QueryAST | undefined;\n\n // Handle direct query mode\n if (options.directQuery && options.providers && options.providers.length === 1) {\n ast = {\n name: options.sessionName ?? 'direct-query',\n blocks: [\n {\n id: 'direct',\n field: 'all',\n terms: { keywords: [options.directQuery] },\n operator: 'AND',\n },\n ],\n filters: {},\n providers: {},\n };\n } else if (options.queryFile) {\n const queryContent = await readFile(options.queryFile, 'utf-8');\n ast = parseQueryString(queryContent);\n } else {\n return [];\n }\n\n // Determine which providers to use\n let providers = getEnabledProviders(config, options.providers);\n\n // In default mode (no --db), skip unconfigured providers\n const isExplicitSelection = options.providers && options.providers.length > 0;\n if (!isExplicitSelection) {\n providers = providers.filter((name) => isProviderConfigured(name, config));\n }\n\n if (providers.length === 0) {\n return [];\n }\n\n // Execute preview for each provider concurrently\n const results: PreviewResult[] = await Promise.all(\n providers.map(async (providerName): Promise<PreviewResult> => {\n try {\n const provider = createProviderInstance(providerName, config);\n if (provider === null) {\n return { provider: providerName, count: 0, titles: [], error: 'Provider configuration incomplete' };\n }\n\n // Translate query\n let translatedQuery: TranslatedQuery;\n if (options.directQuery && options.providers?.length === 1) {\n translatedQuery = {\n native: options.directQuery,\n provider: providerName,\n };\n } else {\n translatedQuery = translateQueryForProvider(ast!, providerName);\n }\n\n // Get count first\n const count = await provider.count(translatedQuery);\n\n // Collect first few articles for titles\n const titles: string[] = [];\n const searchOptions = { maxResults: maxTitles };\n\n for await (const article of provider.search(translatedQuery, searchOptions)) {\n titles.push(article.title);\n if (titles.length >= maxTitles) {\n break;\n }\n }\n\n return { provider: providerName, count, titles };\n } catch (error) {\n const errorMessage = error instanceof Error\n ? error.message\n : isProviderError(error)\n ? error.message\n : String(error);\n return { provider: providerName, count: 0, titles: [], error: errorMessage };\n }\n })\n );\n\n return results;\n}\n\n/**\n * Load all articles from a session's results files (YAML preferred, JSONL fallback).\n */\nasync function loadArticlesFromSession(\n sessionsDir: string,\n sessionId: string,\n providers: ProviderName[]\n): Promise<Article[]> {\n const articles: Article[] = [];\n const sessionDir = join(sessionsDir, sessionId);\n\n for (const provider of providers) {\n const providerArticles = await loadResults(sessionDir, provider);\n articles.push(...providerArticles);\n }\n\n return articles;\n}\n"],"names":["translatePubmed","translateEric","translateArxiv","translateScopus","stringifyYaml"],"mappings":"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"}
|
|
@@ -7,6 +7,7 @@ export interface SearchCommandOptions {
|
|
|
7
7
|
providers?: ProviderName[];
|
|
8
8
|
sessionName?: string;
|
|
9
9
|
maxResults?: number;
|
|
10
|
+
sort?: 'relevance' | 'date';
|
|
10
11
|
dryRun?: boolean;
|
|
11
12
|
countOnly?: boolean;
|
|
12
13
|
preview?: boolean;
|
|
@@ -18,6 +19,7 @@ export interface CommandLineOptions {
|
|
|
18
19
|
query?: string | undefined;
|
|
19
20
|
name?: string | undefined;
|
|
20
21
|
maxResults?: string | undefined;
|
|
22
|
+
sort?: string | undefined;
|
|
21
23
|
dryRun?: boolean | undefined;
|
|
22
24
|
countOnly?: boolean | undefined;
|
|
23
25
|
preview?: boolean | undefined;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/search.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AAC1E,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAIpD,MAAM,WAAW,oBAAoB;IACnC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,YAAY,EAAE,CAAC;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,kBAAkB;IACjC,EAAE,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACxB,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,MAAM,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC7B,SAAS,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAChC,OAAO,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC9B,QAAQ,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC/B,MAAM,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CAC9B;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,OAAO,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,YAAY,EAAE,CAAC;IAC3B,kBAAkB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CAC1C;AAED,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,OAAO,EAAE,kBAAkB,GAC1B,oBAAoB,
|
|
1
|
+
{"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/search.ts"],"names":[],"mappings":"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"}
|
|
@@ -32,6 +32,9 @@ function parseSearchOptions(queryFile, options) {
|
|
|
32
32
|
if (options.strict) {
|
|
33
33
|
result.strict = true;
|
|
34
34
|
}
|
|
35
|
+
if (options.sort === "relevance" || options.sort === "date") {
|
|
36
|
+
result.sort = options.sort;
|
|
37
|
+
}
|
|
35
38
|
return result;
|
|
36
39
|
}
|
|
37
40
|
function validateSearchInput(options) {
|