@ncukondo/search-hub 0.16.0 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/dist/cli/commands/query/assess.d.ts +15 -0
  2. package/dist/cli/commands/query/assess.d.ts.map +1 -0
  3. package/dist/cli/commands/query/assess.js +38 -0
  4. package/dist/cli/commands/query/assess.js.map +1 -0
  5. package/dist/cli/commands/query/iteration-log.d.ts +58 -0
  6. package/dist/cli/commands/query/iteration-log.d.ts.map +1 -0
  7. package/dist/cli/commands/query/iteration-log.js +115 -0
  8. package/dist/cli/commands/query/iteration-log.js.map +1 -0
  9. package/dist/cli/commands/query/log.d.ts +9 -0
  10. package/dist/cli/commands/query/log.d.ts.map +1 -0
  11. package/dist/cli/commands/query/log.js +64 -0
  12. package/dist/cli/commands/query/log.js.map +1 -0
  13. package/dist/cli/commands/review/extract.d.ts.map +1 -1
  14. package/dist/cli/commands/review/extract.js +10 -2
  15. package/dist/cli/commands/review/extract.js.map +1 -1
  16. package/dist/cli/commands/review/finalize.d.ts +2 -0
  17. package/dist/cli/commands/review/finalize.d.ts.map +1 -1
  18. package/dist/cli/commands/review/finalize.js +12 -1
  19. package/dist/cli/commands/review/finalize.js.map +1 -1
  20. package/dist/cli/commands/review/init.d.ts.map +1 -1
  21. package/dist/cli/commands/review/init.js +5 -21
  22. package/dist/cli/commands/review/init.js.map +1 -1
  23. package/dist/cli/commands/review/schema.d.ts +199 -0
  24. package/dist/cli/commands/review/schema.d.ts.map +1 -0
  25. package/dist/cli/commands/review/schema.js +77 -0
  26. package/dist/cli/commands/review/schema.js.map +1 -0
  27. package/dist/cli/commands/review/types.d.ts +15 -70
  28. package/dist/cli/commands/review/types.d.ts.map +1 -1
  29. package/dist/cli/commands/review/types.js +2 -0
  30. package/dist/cli/commands/review/types.js.map +1 -1
  31. package/dist/cli/index.d.ts.map +1 -1
  32. package/dist/cli/index.js +89 -4
  33. package/dist/cli/index.js.map +1 -1
  34. package/dist/cli/suggestions/rules.d.ts.map +1 -1
  35. package/dist/cli/suggestions/rules.js +22 -2
  36. package/dist/cli/suggestions/rules.js.map +1 -1
  37. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"init.js","sources":["../../../../src/cli/commands/review/init.ts"],"sourcesContent":["/**\n * review init command - Generate reviews.yaml from session results\n */\n\nimport { join, dirname } from 'node:path';\nimport { writeFile, mkdir, access, copyFile } from 'node:fs/promises';\nimport { stringify as stringifyYaml } from 'yaml';\nimport type { Article, Author, ProviderName } from '../../../providers/base/types.js';\nimport { loadSession } from '../../../session/manager.js';\nimport { loadResults } from '../../../session/results-io.js';\nimport { deduplicateForReview } from './dedup.js';\nimport type { ArticleEntry, ReviewFile, MergedSource } from './types.js';\n\nexport interface ReviewInitOptions {\n sessionId: string;\n force?: boolean;\n}\n\nexport interface ReviewInitResult {\n reviewsPath: string;\n articleCount: number;\n duplicatesRemoved: number;\n}\n\n/**\n * Format authors array to string\n */\nfunction formatAuthors(authors: Author[]): string {\n return authors\n .map((a) => {\n const parts: string[] = [];\n if (a.family) parts.push(a.family);\n if (a.given) parts.push(a.given.charAt(0));\n return parts.join(' ');\n })\n .join(', ');\n}\n\n/**\n * Extract year from publication date\n */\nfunction extractYear(publicationDate?: string): string | undefined {\n if (!publicationDate) return undefined;\n const year = publicationDate.substring(0, 4);\n return /^\\d{4}$/.test(year) ? year : undefined;\n}\n\n/**\n * Convert Article to ArticleEntry for review file\n */\nfunction articleToEntry(article: Article & { mergedFrom?: MergedSource[] }): ArticleEntry {\n const entry: ArticleEntry = {\n title: article.title,\n reviews: [],\n };\n\n // Add identifiers\n if (article.doi) entry.doi = article.doi;\n if (article.pmid) entry.pmid = article.pmid;\n if (article.scopusId) entry.scopusId = article.scopusId;\n if (article.arxivId) entry.arxivId = article.arxivId;\n if (article.ericId) entry.ericId = article.ericId;\n\n // Add bibliographic info\n if (article.authors && article.authors.length > 0) {\n entry.authors = formatAuthors(article.authors);\n }\n const year = extractYear(article.publicationDate);\n if (year) entry.year = year;\n if (article.abstract) entry.abstract = article.abstract;\n\n // Add deduplication tracking\n if (article.mergedFrom && article.mergedFrom.length > 0) {\n entry.mergedFrom = article.mergedFrom;\n }\n\n return entry;\n}\n\n/**\n * Find the schema file location (in the package)\n */\nasync function findSchemaSource(): Promise<string> {\n // Try relative to this file (src/cli/commands/review -> schemas)\n const possiblePaths = [\n join(dirname(import.meta.url.replace('file://', '')), '../../../../schemas/review.schema.json'),\n join(process.cwd(), 'schemas/review.schema.json'),\n ];\n\n for (const path of possiblePaths) {\n try {\n await access(path);\n return path;\n } catch {\n // Try next path\n }\n }\n\n throw new Error('Could not find review.schema.json');\n}\n\n/**\n * Execute review init command\n */\nexport async function executeReviewInit(\n options: ReviewInitOptions,\n sessionsDir: string\n): Promise<ReviewInitResult> {\n const sessionDir = join(sessionsDir, options.sessionId);\n\n // Load session file\n const session = await loadSession(options.sessionId, sessionsDir);\n\n // Check if .internal/reviews.yaml already exists\n const internalDir = join(sessionDir, '.internal');\n const reviewsPath = join(internalDir, 'reviews.yaml');\n try {\n await access(reviewsPath);\n if (!options.force) {\n throw new Error(`reviews.yaml already exists. Use --force to overwrite.`);\n }\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== 'ENOENT') {\n throw err;\n }\n }\n\n // Create .internal/ directory\n await mkdir(internalDir, { recursive: true });\n\n // Load all results from session\n const allArticles: Article[] = [];\n const providers = Object.keys(session.databases) as ProviderName[];\n\n for (const provider of providers) {\n const dbStatus = session.databases[provider];\n if (!dbStatus) continue;\n const articles = await loadResults(sessionDir, provider);\n allArticles.push(...articles);\n }\n\n // Deduplicate with mergedFrom tracking\n const { articles: dedupedArticles, duplicatesRemoved } = deduplicateForReview(allArticles);\n\n // Convert to ArticleEntry format\n const articleEntries = dedupedArticles.map(articleToEntry);\n\n // Build review file\n const reviewFile: ReviewFile = {\n sessionId: options.sessionId,\n articles: articleEntries,\n };\n\n // Generate YAML with schema reference comment\n const yamlContent = stringifyYaml(reviewFile, {\n lineWidth: 0, // Disable line wrapping\n });\n\n // Add schema reference comment at top (local copy alongside reviews.yaml)\n const schemaComment = `# yaml-language-server: $schema=./review.schema.json\\n`;\n\n // Replace empty reviews arrays with commented example\n const reviewsExample = `reviews:\n # - reviewer: human:your-name\n # decision: include # include / exclude / uncertain\n # comment: reason`;\n const finalContent = schemaComment + yamlContent.replace(\n /reviews: \\[\\]/g,\n reviewsExample\n );\n\n // Write reviews.yaml\n await writeFile(reviewsPath, finalContent, 'utf-8');\n\n // Copy schema file to .internal/ alongside reviews.yaml\n const schemaDestPath = join(internalDir, 'review.schema.json');\n\n try {\n const schemaSourcePath = await findSchemaSource();\n await copyFile(schemaSourcePath, schemaDestPath);\n } catch {\n // If we can't find the schema file, skip copying\n // This might happen in test environments\n }\n\n return {\n reviewsPath,\n articleCount: articleEntries.length,\n duplicatesRemoved,\n };\n}\n"],"names":["stringifyYaml"],"mappings":"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"}
1
+ {"version":3,"file":"init.js","sources":["../../../../src/cli/commands/review/init.ts"],"sourcesContent":["/**\n * review init command - Generate reviews.yaml from session results\n */\n\nimport { join } from 'node:path';\nimport { writeFile, mkdir, access } from 'node:fs/promises';\nimport { stringify as stringifyYaml } from 'yaml';\nimport type { Article, Author, ProviderName } from '../../../providers/base/types.js';\nimport { loadSession } from '../../../session/manager.js';\nimport { loadResults } from '../../../session/results-io.js';\nimport { deduplicateForReview } from './dedup.js';\nimport { generateReviewJSONSchema } from './schema.js';\nimport type { ArticleEntry, ReviewFile, MergedSource } from './types.js';\n\nexport interface ReviewInitOptions {\n sessionId: string;\n force?: boolean;\n}\n\nexport interface ReviewInitResult {\n reviewsPath: string;\n articleCount: number;\n duplicatesRemoved: number;\n}\n\n/**\n * Format authors array to string\n */\nfunction formatAuthors(authors: Author[]): string {\n return authors\n .map((a) => {\n const parts: string[] = [];\n if (a.family) parts.push(a.family);\n if (a.given) parts.push(a.given.charAt(0));\n return parts.join(' ');\n })\n .join(', ');\n}\n\n/**\n * Extract year from publication date\n */\nfunction extractYear(publicationDate?: string): string | undefined {\n if (!publicationDate) return undefined;\n const year = publicationDate.substring(0, 4);\n return /^\\d{4}$/.test(year) ? year : undefined;\n}\n\n/**\n * Convert Article to ArticleEntry for review file\n */\nfunction articleToEntry(article: Article & { mergedFrom?: MergedSource[] }): ArticleEntry {\n const entry: ArticleEntry = {\n title: article.title,\n reviews: [],\n };\n\n // Add identifiers\n if (article.doi) entry.doi = article.doi;\n if (article.pmid) entry.pmid = article.pmid;\n if (article.scopusId) entry.scopusId = article.scopusId;\n if (article.arxivId) entry.arxivId = article.arxivId;\n if (article.ericId) entry.ericId = article.ericId;\n\n // Add bibliographic info\n if (article.authors && article.authors.length > 0) {\n entry.authors = formatAuthors(article.authors);\n }\n const year = extractYear(article.publicationDate);\n if (year) entry.year = year;\n if (article.abstract) entry.abstract = article.abstract;\n\n // Add deduplication tracking\n if (article.mergedFrom && article.mergedFrom.length > 0) {\n entry.mergedFrom = article.mergedFrom;\n }\n\n return entry;\n}\n\n/**\n * Execute review init command\n */\nexport async function executeReviewInit(\n options: ReviewInitOptions,\n sessionsDir: string\n): Promise<ReviewInitResult> {\n const sessionDir = join(sessionsDir, options.sessionId);\n\n // Load session file\n const session = await loadSession(options.sessionId, sessionsDir);\n\n // Check if .internal/reviews.yaml already exists\n const internalDir = join(sessionDir, '.internal');\n const reviewsPath = join(internalDir, 'reviews.yaml');\n try {\n await access(reviewsPath);\n if (!options.force) {\n throw new Error(`reviews.yaml already exists. Use --force to overwrite.`);\n }\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== 'ENOENT') {\n throw err;\n }\n }\n\n // Create .internal/ directory\n await mkdir(internalDir, { recursive: true });\n\n // Load all results from session\n const allArticles: Article[] = [];\n const providers = Object.keys(session.databases) as ProviderName[];\n\n for (const provider of providers) {\n const dbStatus = session.databases[provider];\n if (!dbStatus) continue;\n const articles = await loadResults(sessionDir, provider);\n allArticles.push(...articles);\n }\n\n // Deduplicate with mergedFrom tracking\n const { articles: dedupedArticles, duplicatesRemoved } = deduplicateForReview(allArticles);\n\n // Convert to ArticleEntry format\n const articleEntries = dedupedArticles.map(articleToEntry);\n\n // Build review file\n const reviewFile: ReviewFile = {\n sessionId: options.sessionId,\n articles: articleEntries,\n };\n\n // Generate YAML with schema reference comment\n const yamlContent = stringifyYaml(reviewFile, {\n lineWidth: 0, // Disable line wrapping\n });\n\n // Add schema reference comment at top (local copy alongside reviews.yaml)\n const schemaComment = `# yaml-language-server: $schema=./review.schema.json\\n`;\n\n // Replace empty reviews arrays with commented example\n const reviewsExample = `reviews:\n # - reviewer: human:your-name\n # decision: include # include / exclude / uncertain\n # comment: reason`;\n const finalContent = schemaComment + yamlContent.replace(\n /reviews: \\[\\]/g,\n reviewsExample\n );\n\n // Write reviews.yaml\n await writeFile(reviewsPath, finalContent, 'utf-8');\n\n // Generate and write schema file to .internal/ alongside reviews.yaml\n const schemaDestPath = join(internalDir, 'review.schema.json');\n const jsonSchema = generateReviewJSONSchema();\n await writeFile(schemaDestPath, JSON.stringify(jsonSchema, null, 2) + '\\n', 'utf-8');\n\n return {\n reviewsPath,\n articleCount: articleEntries.length,\n duplicatesRemoved,\n };\n}\n"],"names":["stringifyYaml"],"mappings":"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"}
@@ -0,0 +1,199 @@
1
+ /**
2
+ * Zod schemas for review workflow types.
3
+ *
4
+ * Single source of truth for:
5
+ * - TypeScript types (via z.infer<>)
6
+ * - JSON Schema for IDE autocompletion (via z.toJSONSchema())
7
+ *
8
+ * Follows the pattern established in src/query/json-schema.ts.
9
+ */
10
+ import * as z from 'zod';
11
+ export declare const reviewDecisionSchema: z.ZodEnum<{
12
+ exclude: "exclude";
13
+ include: "include";
14
+ uncertain: "uncertain";
15
+ }>;
16
+ export declare const reviewBasisSchema: z.ZodEnum<{
17
+ title: "title";
18
+ abstract: "abstract";
19
+ fulltext: "fulltext";
20
+ }>;
21
+ export declare const reviewSchema: z.ZodObject<{
22
+ reviewer: z.ZodString;
23
+ decision: z.ZodOptional<z.ZodEnum<{
24
+ exclude: "exclude";
25
+ include: "include";
26
+ uncertain: "uncertain";
27
+ }>>;
28
+ basis: z.ZodOptional<z.ZodEnum<{
29
+ title: "title";
30
+ abstract: "abstract";
31
+ fulltext: "fulltext";
32
+ }>>;
33
+ comment: z.ZodOptional<z.ZodString>;
34
+ timestamp: z.ZodOptional<z.ZodString>;
35
+ }, z.core.$strict>;
36
+ export declare const mergedSourceSchema: z.ZodObject<{
37
+ source: z.ZodString;
38
+ pmid: z.ZodOptional<z.ZodString>;
39
+ doi: z.ZodOptional<z.ZodString>;
40
+ scopusId: z.ZodOptional<z.ZodString>;
41
+ arxivId: z.ZodOptional<z.ZodString>;
42
+ ericId: z.ZodOptional<z.ZodString>;
43
+ }, z.core.$strict>;
44
+ export declare const articleFulltextRefSchema: z.ZodObject<{
45
+ dirName: z.ZodString;
46
+ hasFiles: z.ZodObject<{
47
+ pdf: z.ZodBoolean;
48
+ xml: z.ZodBoolean;
49
+ html: z.ZodBoolean;
50
+ markdown: z.ZodBoolean;
51
+ }, z.core.$strict>;
52
+ }, z.core.$strict>;
53
+ export declare const articleEntrySchema: z.ZodObject<{
54
+ doi: z.ZodOptional<z.ZodString>;
55
+ pmid: z.ZodOptional<z.ZodString>;
56
+ scopusId: z.ZodOptional<z.ZodString>;
57
+ arxivId: z.ZodOptional<z.ZodString>;
58
+ ericId: z.ZodOptional<z.ZodString>;
59
+ title: z.ZodString;
60
+ authors: z.ZodOptional<z.ZodString>;
61
+ year: z.ZodOptional<z.ZodString>;
62
+ abstract: z.ZodOptional<z.ZodString>;
63
+ mergedFrom: z.ZodOptional<z.ZodArray<z.ZodObject<{
64
+ source: z.ZodString;
65
+ pmid: z.ZodOptional<z.ZodString>;
66
+ doi: z.ZodOptional<z.ZodString>;
67
+ scopusId: z.ZodOptional<z.ZodString>;
68
+ arxivId: z.ZodOptional<z.ZodString>;
69
+ ericId: z.ZodOptional<z.ZodString>;
70
+ }, z.core.$strict>>>;
71
+ reviews: z.ZodArray<z.ZodObject<{
72
+ reviewer: z.ZodString;
73
+ decision: z.ZodOptional<z.ZodEnum<{
74
+ exclude: "exclude";
75
+ include: "include";
76
+ uncertain: "uncertain";
77
+ }>>;
78
+ basis: z.ZodOptional<z.ZodEnum<{
79
+ title: "title";
80
+ abstract: "abstract";
81
+ fulltext: "fulltext";
82
+ }>>;
83
+ comment: z.ZodOptional<z.ZodString>;
84
+ timestamp: z.ZodOptional<z.ZodString>;
85
+ }, z.core.$strict>>;
86
+ reviewHistory: z.ZodOptional<z.ZodArray<z.ZodObject<{
87
+ reviewer: z.ZodString;
88
+ decision: z.ZodOptional<z.ZodEnum<{
89
+ exclude: "exclude";
90
+ include: "include";
91
+ uncertain: "uncertain";
92
+ }>>;
93
+ basis: z.ZodOptional<z.ZodEnum<{
94
+ title: "title";
95
+ abstract: "abstract";
96
+ fulltext: "fulltext";
97
+ }>>;
98
+ comment: z.ZodOptional<z.ZodString>;
99
+ timestamp: z.ZodOptional<z.ZodString>;
100
+ }, z.core.$strict>>>;
101
+ finalDecision: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<"include">, z.ZodLiteral<"exclude">, z.ZodNull]>>;
102
+ fulltext: z.ZodOptional<z.ZodObject<{
103
+ dirName: z.ZodString;
104
+ hasFiles: z.ZodObject<{
105
+ pdf: z.ZodBoolean;
106
+ xml: z.ZodBoolean;
107
+ html: z.ZodBoolean;
108
+ markdown: z.ZodBoolean;
109
+ }, z.core.$strict>;
110
+ }, z.core.$strict>>;
111
+ }, z.core.$strict>;
112
+ export declare const reviewerRecordSchema: z.ZodObject<{
113
+ name: z.ZodString;
114
+ basis: z.ZodEnum<{
115
+ title: "title";
116
+ abstract: "abstract";
117
+ fulltext: "fulltext";
118
+ }>;
119
+ }, z.core.$strict>;
120
+ export declare const reviewFileSchema: z.ZodObject<{
121
+ sessionId: z.ZodString;
122
+ criteria: z.ZodOptional<z.ZodString>;
123
+ reviewer: z.ZodOptional<z.ZodString>;
124
+ basis: z.ZodOptional<z.ZodEnum<{
125
+ title: "title";
126
+ abstract: "abstract";
127
+ fulltext: "fulltext";
128
+ }>>;
129
+ articles: z.ZodArray<z.ZodObject<{
130
+ doi: z.ZodOptional<z.ZodString>;
131
+ pmid: z.ZodOptional<z.ZodString>;
132
+ scopusId: z.ZodOptional<z.ZodString>;
133
+ arxivId: z.ZodOptional<z.ZodString>;
134
+ ericId: z.ZodOptional<z.ZodString>;
135
+ title: z.ZodString;
136
+ authors: z.ZodOptional<z.ZodString>;
137
+ year: z.ZodOptional<z.ZodString>;
138
+ abstract: z.ZodOptional<z.ZodString>;
139
+ mergedFrom: z.ZodOptional<z.ZodArray<z.ZodObject<{
140
+ source: z.ZodString;
141
+ pmid: z.ZodOptional<z.ZodString>;
142
+ doi: z.ZodOptional<z.ZodString>;
143
+ scopusId: z.ZodOptional<z.ZodString>;
144
+ arxivId: z.ZodOptional<z.ZodString>;
145
+ ericId: z.ZodOptional<z.ZodString>;
146
+ }, z.core.$strict>>>;
147
+ reviews: z.ZodArray<z.ZodObject<{
148
+ reviewer: z.ZodString;
149
+ decision: z.ZodOptional<z.ZodEnum<{
150
+ exclude: "exclude";
151
+ include: "include";
152
+ uncertain: "uncertain";
153
+ }>>;
154
+ basis: z.ZodOptional<z.ZodEnum<{
155
+ title: "title";
156
+ abstract: "abstract";
157
+ fulltext: "fulltext";
158
+ }>>;
159
+ comment: z.ZodOptional<z.ZodString>;
160
+ timestamp: z.ZodOptional<z.ZodString>;
161
+ }, z.core.$strict>>;
162
+ reviewHistory: z.ZodOptional<z.ZodArray<z.ZodObject<{
163
+ reviewer: z.ZodString;
164
+ decision: z.ZodOptional<z.ZodEnum<{
165
+ exclude: "exclude";
166
+ include: "include";
167
+ uncertain: "uncertain";
168
+ }>>;
169
+ basis: z.ZodOptional<z.ZodEnum<{
170
+ title: "title";
171
+ abstract: "abstract";
172
+ fulltext: "fulltext";
173
+ }>>;
174
+ comment: z.ZodOptional<z.ZodString>;
175
+ timestamp: z.ZodOptional<z.ZodString>;
176
+ }, z.core.$strict>>>;
177
+ finalDecision: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<"include">, z.ZodLiteral<"exclude">, z.ZodNull]>>;
178
+ fulltext: z.ZodOptional<z.ZodObject<{
179
+ dirName: z.ZodString;
180
+ hasFiles: z.ZodObject<{
181
+ pdf: z.ZodBoolean;
182
+ xml: z.ZodBoolean;
183
+ html: z.ZodBoolean;
184
+ markdown: z.ZodBoolean;
185
+ }, z.core.$strict>;
186
+ }, z.core.$strict>>;
187
+ }, z.core.$strict>>;
188
+ reviewers: z.ZodOptional<z.ZodArray<z.ZodObject<{
189
+ name: z.ZodString;
190
+ basis: z.ZodEnum<{
191
+ title: "title";
192
+ abstract: "abstract";
193
+ fulltext: "fulltext";
194
+ }>;
195
+ }, z.core.$strict>>>;
196
+ }, z.core.$strict>;
197
+ /** Generate a JSON Schema from the review file Zod schema. */
198
+ export declare function generateReviewJSONSchema(): Record<string, unknown>;
199
+ //# sourceMappingURL=schema.d.ts.map
@@ -0,0 +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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAU6B,CAAC;AAE3D,8DAA8D;AAC9D,wBAAgB,wBAAwB,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAIlE"}
@@ -0,0 +1,77 @@
1
+ import * as z from "zod";
2
+ const reviewDecisionSchema = z.enum(["include", "exclude", "uncertain"]);
3
+ const reviewBasisSchema = z.enum(["title", "abstract", "fulltext"]);
4
+ const reviewSchema = z.object({
5
+ reviewer: z.string().describe("Reviewer identifier (e.g., 'gpt-4o', 'claude-sonnet', 'human:tanaka')"),
6
+ decision: reviewDecisionSchema.describe("Assessment decision").optional(),
7
+ basis: reviewBasisSchema.describe("Basis of the decision (what information was used)").optional(),
8
+ comment: z.string().describe("Optional comment or reason").optional(),
9
+ timestamp: z.string().datetime().describe("ISO 8601 timestamp").optional()
10
+ }).strict().describe("Individual assessment of an article");
11
+ const mergedSourceSchema = z.object({
12
+ source: z.string().describe("Database source (e.g., 'pubmed', 'scopus')"),
13
+ pmid: z.string().optional(),
14
+ doi: z.string().optional(),
15
+ scopusId: z.string().optional(),
16
+ arxivId: z.string().optional(),
17
+ ericId: z.string().optional()
18
+ }).strict().describe("Source information for merged duplicates");
19
+ const articleFulltextRefSchema = z.object({
20
+ dirName: z.string(),
21
+ hasFiles: z.object({
22
+ pdf: z.boolean(),
23
+ xml: z.boolean(),
24
+ html: z.boolean(),
25
+ markdown: z.boolean()
26
+ }).strict()
27
+ }).strict();
28
+ const articleEntrySchema = z.object({
29
+ // Identifiers
30
+ doi: z.string().describe("Digital Object Identifier").optional(),
31
+ pmid: z.string().describe("PubMed ID").optional(),
32
+ scopusId: z.string().describe("Scopus ID").optional(),
33
+ arxivId: z.string().describe("arXiv ID").optional(),
34
+ ericId: z.string().describe("ERIC ID").optional(),
35
+ // Bibliographic info
36
+ title: z.string().describe("Article title"),
37
+ authors: z.string().describe("Authors (formatted string)").optional(),
38
+ year: z.string().describe("Publication year").optional(),
39
+ abstract: z.string().describe("Article abstract").optional(),
40
+ // Deduplication tracking
41
+ mergedFrom: z.array(mergedSourceSchema).describe("Sources this article was merged from during deduplication").optional(),
42
+ // Review data
43
+ reviews: z.array(reviewSchema).describe("List of assessments"),
44
+ reviewHistory: z.array(reviewSchema).describe("Historical reviews (only in extracted ReviewFiles, never in master file)").optional(),
45
+ finalDecision: z.union([z.literal("include"), z.literal("exclude"), z.null()]).describe("Final inclusion/exclusion decision (null in extracted files)").optional(),
46
+ // Fulltext reference
47
+ fulltext: articleFulltextRefSchema.optional()
48
+ }).strict().describe("Article with identifiers, bibliographic info, and reviews");
49
+ const reviewerRecordSchema = z.object({
50
+ name: z.string().describe("Reviewer identifier"),
51
+ basis: reviewBasisSchema.describe("Basis level at which the reviewer participated")
52
+ }).strict().describe("Record of a reviewer's participation at a specific basis level");
53
+ const reviewFileSchema = z.object({
54
+ sessionId: z.string().describe("Session identifier"),
55
+ criteria: z.string().describe("Path to inclusion criteria file").optional(),
56
+ reviewer: z.string().describe("Reviewer identifier (only in extracted ReviewFiles)").optional(),
57
+ basis: reviewBasisSchema.describe("Basis level for screening (only in extracted ReviewFiles)").optional(),
58
+ articles: z.array(articleEntrySchema).describe("List of articles with review data"),
59
+ reviewers: z.array(reviewerRecordSchema).describe("Registry of reviewers who participated at each basis level").optional()
60
+ }).strict().describe("Schema for article review workflow tracking");
61
+ function generateReviewJSONSchema() {
62
+ return z.toJSONSchema(reviewFileSchema, {
63
+ target: "draft-7"
64
+ });
65
+ }
66
+ export {
67
+ articleEntrySchema,
68
+ articleFulltextRefSchema,
69
+ generateReviewJSONSchema,
70
+ mergedSourceSchema,
71
+ reviewBasisSchema,
72
+ reviewDecisionSchema,
73
+ reviewFileSchema,
74
+ reviewSchema,
75
+ reviewerRecordSchema
76
+ };
77
+ //# sourceMappingURL=schema.js.map
@@ -0,0 +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,74 +1,19 @@
1
- import { ArticleFulltextRef } from '@ncukondo/academic-fulltext';
2
- export type ReviewDecision = 'include' | 'exclude' | 'uncertain';
1
+ import { reviewDecisionSchema, reviewBasisSchema, reviewSchema, mergedSourceSchema, articleEntrySchema, reviewerRecordSchema, reviewFileSchema } from './schema.js';
3
2
  /**
4
- * Basis of the review decision (what information was used)
5
- */
6
- export type ReviewBasis = 'title' | 'abstract' | 'fulltext';
7
- /**
8
- * Individual assessment of an article by a reviewer
9
- */
10
- export interface Review {
11
- /** Reviewer identifier: "human:name" or "ai:name" */
12
- reviewer: string;
13
- /** Assessment decision */
14
- decision?: ReviewDecision;
15
- /** Basis of the decision (what information was used) */
16
- basis?: ReviewBasis;
17
- /** Optional comment or reason */
18
- comment?: string;
19
- /** ISO 8601 timestamp (optional - auto-assigned on merge if not provided) */
20
- timestamp?: string;
21
- }
22
- /**
23
- * Source information for merged duplicates
24
- */
25
- export interface MergedSource {
26
- source: string;
27
- pmid?: string;
28
- doi?: string;
29
- scopusId?: string;
30
- arxivId?: string;
31
- ericId?: string;
32
- }
33
- /**
34
- * Article entry with identifiers, bibliographic info, and reviews
35
- */
36
- export interface ArticleEntry {
37
- doi?: string;
38
- pmid?: string;
39
- scopusId?: string;
40
- arxivId?: string;
41
- ericId?: string;
42
- title: string;
43
- authors?: string;
44
- year?: string;
45
- abstract?: string;
46
- mergedFrom?: MergedSource[];
47
- reviews: Review[];
48
- /** Historical reviews (only in extracted ReviewFiles, never in master file) */
49
- reviewHistory?: Review[];
50
- finalDecision?: 'include' | 'exclude' | null;
51
- fulltext?: ArticleFulltextRef;
52
- }
53
- /**
54
- * Top-level structure of the reviews.yaml file
55
- */
56
- export interface ReviewerRecord {
57
- name: string;
58
- basis: ReviewBasis;
59
- }
60
- export interface ReviewFile {
61
- sessionId: string;
62
- /** Path to inclusion criteria file */
63
- criteria?: string;
64
- /** Reviewer identifier (only in extracted ReviewFiles) */
65
- reviewer?: string;
66
- /** Basis level for screening (only in extracted ReviewFiles) */
67
- basis?: ReviewBasis;
68
- articles: ArticleEntry[];
69
- /** Registry of reviewers who participated at each basis level */
70
- reviewers?: ReviewerRecord[];
71
- }
3
+ * Review workflow types for article assessment tracking.
4
+ *
5
+ * Core data types (ReviewDecision, ReviewBasis, Review, MergedSource,
6
+ * ArticleEntry, ReviewerRecord, ReviewFile) are derived from Zod schemas
7
+ * in schema.ts — single source of truth for types and JSON Schema.
8
+ */
9
+ import * as z from 'zod';
10
+ export type ReviewDecision = z.infer<typeof reviewDecisionSchema>;
11
+ export type ReviewBasis = z.infer<typeof reviewBasisSchema>;
12
+ export type Review = z.infer<typeof reviewSchema>;
13
+ export type MergedSource = z.infer<typeof mergedSourceSchema>;
14
+ export type ArticleEntry = z.infer<typeof articleEntrySchema>;
15
+ export type ReviewerRecord = z.infer<typeof reviewerRecordSchema>;
16
+ export type ReviewFile = z.infer<typeof reviewFileSchema>;
72
17
  /**
73
18
  * Work file article entry for AI agent workflow
74
19
  */
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/review/types.ts"],"names":[],"mappings":"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"}
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,WAAW,GACX,gBAAgB,GAChB,gBAAgB,GAChB,aAAa,GACb,WAAW,CAAC;AAEhB;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAC5B,KAAK,EAAE,YAAY,EACnB,mBAAmB,CAAC,EAAE,cAAc,EAAE,GACrC,YAAY,CAsHd"}
@@ -1,3 +1,5 @@
1
+ import "zod";
2
+ import "./schema.js";
1
3
  const BASIS_RANK = {
2
4
  title: 1,
3
5
  abstract: 2,
@@ -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\nimport type { ArticleFulltextRef } from '@ncukondo/academic-fulltext';\n\nexport type ReviewDecision = 'include' | 'exclude' | 'uncertain';\n\n/**\n * Basis of the review decision (what information was used)\n */\nexport type ReviewBasis = 'title' | 'abstract' | 'fulltext';\n\n/**\n * Individual assessment of an article by a reviewer\n */\nexport interface Review {\n /** Reviewer identifier: \"human:name\" or \"ai:name\" */\n reviewer: string;\n /** Assessment decision */\n decision?: ReviewDecision;\n /** Basis of the decision (what information was used) */\n basis?: ReviewBasis;\n /** Optional comment or reason */\n comment?: string;\n /** ISO 8601 timestamp (optional - auto-assigned on merge if not provided) */\n timestamp?: string;\n}\n\n/**\n * Source information for merged duplicates\n */\nexport interface MergedSource {\n source: string;\n pmid?: string;\n doi?: string;\n scopusId?: string;\n arxivId?: string;\n ericId?: string;\n}\n\n/**\n * Article entry with identifiers, bibliographic info, and reviews\n */\nexport interface ArticleEntry {\n // Identifiers (at least one required for matching)\n doi?: string;\n pmid?: string;\n scopusId?: string;\n arxivId?: string;\n ericId?: string;\n\n // Bibliographic info (for reviewer reference)\n title: string;\n authors?: string;\n year?: string;\n abstract?: string;\n\n // Deduplication tracking\n mergedFrom?: MergedSource[];\n\n // Review data\n reviews: Review[];\n /** Historical reviews (only in extracted ReviewFiles, never in master file) */\n reviewHistory?: Review[];\n finalDecision?: 'include' | 'exclude' | null;\n\n // Fulltext reference (set by fulltext init/sync)\n fulltext?: ArticleFulltextRef;\n}\n\n/**\n * Top-level structure of the reviews.yaml file\n */\nexport interface ReviewerRecord {\n name: string;\n basis: ReviewBasis;\n}\n\nexport interface ReviewFile {\n sessionId: string;\n /** Path to inclusion criteria file */\n criteria?: string;\n /** Reviewer identifier (only in extracted ReviewFiles) */\n reviewer?: string;\n /** Basis level for screening (only in extracted ReviewFiles) */\n basis?: ReviewBasis;\n articles: ArticleEntry[];\n /** Registry of reviewers who participated at each basis level */\n reviewers?: ReviewerRecord[];\n}\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 | 'uncertain'\n | 'agreed-include'\n | 'agreed-exclude'\n | 'conflicting'\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. include AND exclude present? → conflicting\n * 5. Any uncertain? → uncertain\n * 6. All include? → agreed-include\n * 7. All exclude? → agreed-exclude\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 for conflicts: both include and exclude present among effective decisions\n const hasInclude = effectiveDecisions.includes('include');\n const hasExclude = effectiveDecisions.includes('exclude');\n if (hasInclude && hasExclude) {\n return 'conflicting';\n }\n\n // 5. Any effective uncertain?\n const hasUncertain = effectiveDecisions.includes('uncertain');\n if (hasUncertain) {\n return 'uncertain';\n }\n\n // 6. All include?\n if (effectiveDecisions.every((d) => d === 'include')) {\n return 'agreed-include';\n }\n\n // 7. All exclude (only remaining possibility after ruling out conflicts and uncertain)\n return 'agreed-exclude';\n}\n"],"names":[],"mappings":"AAwHA,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,QAAM,aAAa,mBAAmB,SAAS,SAAS;AACxD,QAAM,aAAa,mBAAmB,SAAS,SAAS;AACxD,MAAI,cAAc,YAAY;AAC5B,WAAO;AAAA,EACT;AAGA,QAAM,eAAe,mBAAmB,SAAS,WAAW;AAC5D,MAAI,cAAc;AAChB,WAAO;AAAA,EACT;AAGA,MAAI,mBAAmB,MAAM,CAAC,MAAM,MAAM,SAAS,GAAG;AACpD,WAAO;AAAA,EACT;AAGA,SAAO;AACT;"}
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 | 'uncertain'\n | 'agreed-include'\n | 'agreed-exclude'\n | 'conflicting'\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. include AND exclude present? → conflicting\n * 5. Any uncertain? → uncertain\n * 6. All include? → agreed-include\n * 7. All exclude? → agreed-exclude\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 for conflicts: both include and exclude present among effective decisions\n const hasInclude = effectiveDecisions.includes('include');\n const hasExclude = effectiveDecisions.includes('exclude');\n if (hasInclude && hasExclude) {\n return 'conflicting';\n }\n\n // 5. Any effective uncertain?\n const hasUncertain = effectiveDecisions.includes('uncertain');\n if (hasUncertain) {\n return 'uncertain';\n }\n\n // 6. All include?\n if (effectiveDecisions.every((d) => d === 'include')) {\n return 'agreed-include';\n }\n\n // 7. All exclude (only remaining possibility after ruling out conflicts and uncertain)\n return 'agreed-exclude';\n}\n"],"names":[],"mappings":";;AAsDA,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,QAAM,aAAa,mBAAmB,SAAS,SAAS;AACxD,QAAM,aAAa,mBAAmB,SAAS,SAAS;AACxD,MAAI,cAAc,YAAY;AAC5B,WAAO;AAAA,EACT;AAGA,QAAM,eAAe,mBAAmB,SAAS,WAAW;AAC5D,MAAI,cAAc;AAChB,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":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":";AAQA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAqLpC;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,0BAA0B;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gCAAgC;IAChC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,4BAA4B;IAC5B,OAAO,EAAE,OAAO,CAAC;IACjB,wCAAwC;IACxC,KAAK,EAAE,OAAO,CAAC;IACf,qEAAqE;IACrE,KAAK,EAAE,OAAO,CAAC;CAChB;AAED;;GAEG;AACH,wBAAgB,aAAa,IAAI,OAAO,CA28EvC;AAED;;GAEG;AACH,wBAAsB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAG1C"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":";AAQA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA8LpC;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,0BAA0B;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gCAAgC;IAChC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,4BAA4B;IAC5B,OAAO,EAAE,OAAO,CAAC;IACjB,wCAAwC;IACxC,KAAK,EAAE,OAAO,CAAC;IACf,qEAAqE;IACrE,KAAK,EAAE,OAAO,CAAC;CAChB;AAED;;GAEG;AACH,wBAAgB,aAAa,IAAI,OAAO,CAkjFvC;AAED;;GAEG;AACH,wBAAsB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAG1C"}