@ncukondo/search-hub 0.18.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.
Files changed (83) hide show
  1. package/dist/cli/commands/register.d.ts +1 -1
  2. package/dist/cli/commands/register.js.map +1 -1
  3. package/dist/cli/commands/related.d.ts +66 -0
  4. package/dist/cli/commands/related.d.ts.map +1 -0
  5. package/dist/cli/commands/related.js +161 -0
  6. package/dist/cli/commands/related.js.map +1 -0
  7. package/dist/cli/commands/review/extract.d.ts.map +1 -1
  8. package/dist/cli/commands/review/extract.js +15 -5
  9. package/dist/cli/commands/review/extract.js.map +1 -1
  10. package/dist/cli/commands/review/finalize.js +6 -6
  11. package/dist/cli/commands/review/finalize.js.map +1 -1
  12. package/dist/cli/commands/review/init.d.ts +2 -3
  13. package/dist/cli/commands/review/init.d.ts.map +1 -1
  14. package/dist/cli/commands/review/init.js +1 -0
  15. package/dist/cli/commands/review/init.js.map +1 -1
  16. package/dist/cli/commands/review/list.d.ts +1 -1
  17. package/dist/cli/commands/review/list.d.ts.map +1 -1
  18. package/dist/cli/commands/review/list.js.map +1 -1
  19. package/dist/cli/commands/review/next-steps.d.ts +4 -1
  20. package/dist/cli/commands/review/next-steps.d.ts.map +1 -1
  21. package/dist/cli/commands/review/next-steps.js +53 -19
  22. package/dist/cli/commands/review/next-steps.js.map +1 -1
  23. package/dist/cli/commands/review/schema.d.ts +8 -0
  24. package/dist/cli/commands/review/schema.d.ts.map +1 -1
  25. package/dist/cli/commands/review/schema.js +3 -0
  26. package/dist/cli/commands/review/schema.js.map +1 -1
  27. package/dist/cli/commands/review/status.d.ts +5 -3
  28. package/dist/cli/commands/review/status.d.ts.map +1 -1
  29. package/dist/cli/commands/review/status.js +16 -14
  30. package/dist/cli/commands/review/status.js.map +1 -1
  31. package/dist/cli/commands/review/types.d.ts +7 -6
  32. package/dist/cli/commands/review/types.d.ts.map +1 -1
  33. package/dist/cli/commands/review/types.js +6 -9
  34. package/dist/cli/commands/review/types.js.map +1 -1
  35. package/dist/cli/commands/search-executor.d.ts.map +1 -1
  36. package/dist/cli/commands/search-executor.js +3 -2
  37. package/dist/cli/commands/search-executor.js.map +1 -1
  38. package/dist/cli/commands/search.d.ts +2 -0
  39. package/dist/cli/commands/search.d.ts.map +1 -1
  40. package/dist/cli/commands/search.js +3 -0
  41. package/dist/cli/commands/search.js.map +1 -1
  42. package/dist/cli/index.d.ts.map +1 -1
  43. package/dist/cli/index.js +131 -6
  44. package/dist/cli/index.js.map +1 -1
  45. package/dist/cli/suggestions/rules.d.ts.map +1 -1
  46. package/dist/cli/suggestions/rules.js +19 -3
  47. package/dist/cli/suggestions/rules.js.map +1 -1
  48. package/dist/providers/arxiv/provider.d.ts.map +1 -1
  49. package/dist/providers/arxiv/provider.js +7 -4
  50. package/dist/providers/arxiv/provider.js.map +1 -1
  51. package/dist/providers/base/types.d.ts +8 -0
  52. package/dist/providers/base/types.d.ts.map +1 -1
  53. package/dist/providers/base/types.js.map +1 -1
  54. package/dist/providers/eric/provider.d.ts +3 -0
  55. package/dist/providers/eric/provider.d.ts.map +1 -1
  56. package/dist/providers/eric/provider.js +11 -0
  57. package/dist/providers/eric/provider.js.map +1 -1
  58. package/dist/providers/pubmed/client.d.ts +15 -1
  59. package/dist/providers/pubmed/client.d.ts.map +1 -1
  60. package/dist/providers/pubmed/client.js +64 -1
  61. package/dist/providers/pubmed/client.js.map +1 -1
  62. package/dist/providers/pubmed/index.d.ts +2 -2
  63. package/dist/providers/pubmed/index.d.ts.map +1 -1
  64. package/dist/providers/pubmed/parser.d.ts +8 -1
  65. package/dist/providers/pubmed/parser.d.ts.map +1 -1
  66. package/dist/providers/pubmed/parser.js +23 -1
  67. package/dist/providers/pubmed/parser.js.map +1 -1
  68. package/dist/providers/pubmed/provider.d.ts.map +1 -1
  69. package/dist/providers/pubmed/provider.js +8 -2
  70. package/dist/providers/pubmed/provider.js.map +1 -1
  71. package/dist/providers/pubmed/types.d.ts +29 -0
  72. package/dist/providers/pubmed/types.d.ts.map +1 -1
  73. package/dist/providers/scopus/client.d.ts +2 -0
  74. package/dist/providers/scopus/client.d.ts.map +1 -1
  75. package/dist/providers/scopus/client.js +3 -0
  76. package/dist/providers/scopus/client.js.map +1 -1
  77. package/dist/providers/scopus/provider.d.ts.map +1 -1
  78. package/dist/providers/scopus/provider.js +7 -1
  79. package/dist/providers/scopus/provider.js.map +1 -1
  80. package/dist/session/types.d.ts +13 -1
  81. package/dist/session/types.d.ts.map +1 -1
  82. package/dist/session/types.js.map +1 -1
  83. 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 } 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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"}
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, ReviewMode, MergedSource } from './types.js';\n\nexport interface ReviewInitOptions {\n sessionId: string;\n mode?: ReviewMode;\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 ...(options.mode && { mode: options.mode }),\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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"}
@@ -1,5 +1,5 @@
1
1
  import { ReviewStatus } from './types.js';
2
- export type ListFilter = 'pending' | 'incomplete' | 'uncertain' | 'agreed-include' | 'agreed-exclude' | 'conflicting' | 'finalized' | 'all';
2
+ export type ListFilter = 'pending' | 'incomplete' | 'all-uncertain' | 'agreed-include' | 'agreed-exclude' | 'divided' | 'finalized' | 'all';
3
3
  export interface ReviewListOptions {
4
4
  sessionId: string;
5
5
  filter?: ListFilter;
@@ -1 +1 @@
1
- {"version":3,"file":"list.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/review/list.ts"],"names":[],"mappings":"AAAA;;GAEG;AAKH,OAAO,EAAmC,KAAK,YAAY,EAAE,MAAM,YAAY,CAAC;AAEhF,MAAM,MAAM,UAAU,GAClB,SAAS,GACT,YAAY,GACZ,WAAW,GACX,gBAAgB,GAChB,gBAAgB,GAChB,aAAa,GACb,WAAW,GACX,KAAK,CAAC;AAEV,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,UAAU,CAAC;CACrB;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,YAAY,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,SAAS,GAAG,SAAS,CAAC;CACvC;AAED,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,UAAU,CAAC;IACnB,QAAQ,EAAE,eAAe,EAAE,CAAC;CAC7B;AAWD;;GAEG;AACH,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,iBAAiB,EAC1B,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,gBAAgB,CAAC,CAuC3B;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,gBAAgB,GAAG,MAAM,CAkBjE"}
1
+ {"version":3,"file":"list.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/review/list.ts"],"names":[],"mappings":"AAAA;;GAEG;AAKH,OAAO,EAAmC,KAAK,YAAY,EAAE,MAAM,YAAY,CAAC;AAEhF,MAAM,MAAM,UAAU,GAClB,SAAS,GACT,YAAY,GACZ,eAAe,GACf,gBAAgB,GAChB,gBAAgB,GAChB,SAAS,GACT,WAAW,GACX,KAAK,CAAC;AAEV,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,UAAU,CAAC;CACrB;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,YAAY,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,SAAS,GAAG,SAAS,CAAC;CACvC;AAED,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,UAAU,CAAC;IACnB,QAAQ,EAAE,eAAe,EAAE,CAAC;CAC7B;AAWD;;GAEG;AACH,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,iBAAiB,EAC1B,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,gBAAgB,CAAC,CAuC3B;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,gBAAgB,GAAG,MAAM,CAkBjE"}
@@ -1 +1 @@
1
- {"version":3,"file":"list.js","sources":["../../../../src/cli/commands/review/list.ts"],"sourcesContent":["/**\n * review list command - List articles with optional filtering\n */\n\nimport { join } from 'node:path';\nimport { readFile } from 'node:fs/promises';\nimport { parse as parseYaml } from 'yaml';\nimport { classifyStatus, type ReviewFile, type ReviewStatus } from './types.js';\n\nexport type ListFilter =\n | 'pending'\n | 'incomplete'\n | 'uncertain'\n | 'agreed-include'\n | 'agreed-exclude'\n | 'conflicting'\n | 'finalized'\n | 'all';\n\nexport interface ReviewListOptions {\n sessionId: string;\n filter?: ListFilter;\n}\n\nexport interface ArticleListItem {\n title: string;\n pmid?: string;\n doi?: string;\n scopusId?: string;\n arxivId?: string;\n ericId?: string;\n year?: string;\n status: ReviewStatus;\n reviewCount: number;\n finalDecision?: 'include' | 'exclude';\n}\n\nexport interface ReviewListResult {\n sessionId: string;\n filter: ListFilter;\n articles: ArticleListItem[];\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 list command\n */\nexport async function executeReviewList(\n options: ReviewListOptions,\n sessionsDir: string\n): Promise<ReviewListResult> {\n const sessionDir = join(sessionsDir, options.sessionId);\n const reviewFile = await loadReviewFile(sessionDir);\n const filter = options.filter ?? 'all';\n\n const articles: ArticleListItem[] = [];\n\n const reviewers = reviewFile.reviewers;\n for (const article of reviewFile.articles) {\n const status = classifyStatus(article, reviewers);\n\n // Apply filter\n if (filter !== 'all' && status !== filter) {\n continue;\n }\n\n const item: ArticleListItem = {\n title: article.title,\n status,\n reviewCount: (article.reviews ?? []).length,\n };\n\n // Add optional identifiers\n if (article.pmid) item.pmid = article.pmid;\n if (article.doi) item.doi = article.doi;\n if (article.scopusId) item.scopusId = article.scopusId;\n if (article.arxivId) item.arxivId = article.arxivId;\n if (article.ericId) item.ericId = article.ericId;\n if (article.year) item.year = article.year;\n if (article.finalDecision) item.finalDecision = article.finalDecision;\n\n articles.push(item);\n }\n\n return {\n sessionId: options.sessionId,\n filter,\n articles,\n };\n}\n\n/**\n * Format list result as human-readable string\n */\nexport function formatListOutput(result: ReviewListResult): string {\n if (result.articles.length === 0) {\n return `No articles found matching filter: ${result.filter}`;\n }\n\n const lines: string[] = [];\n lines.push(`${result.articles.length} articles (filter: ${result.filter})`);\n lines.push('');\n\n for (const article of result.articles) {\n const id = article.pmid ?? article.doi ?? article.scopusId ?? article.arxivId ?? article.ericId ?? '-';\n const year = article.year ?? '-';\n const decision = article.finalDecision ? ` [${article.finalDecision}]` : '';\n lines.push(`[${article.status}] ${article.title}`);\n lines.push(` ID: ${id} | Year: ${year} | Reviews: ${article.reviewCount}${decision}`);\n }\n\n return lines.join('\\n');\n}\n"],"names":["parseYaml"],"mappings":";;;;AA8CA,eAAe,eAAe,YAAyC;AACrE,QAAM,cAAc,KAAK,YAAY,aAAa,cAAc;AAChE,QAAM,UAAU,MAAM,SAAS,aAAa,OAAO;AACnD,SAAOA,MAAU,OAAO;AAC1B;AAKA,eAAsB,kBACpB,SACA,aAC2B;AAC3B,QAAM,aAAa,KAAK,aAAa,QAAQ,SAAS;AACtD,QAAM,aAAa,MAAM,eAAe,UAAU;AAClD,QAAM,SAAS,QAAQ,UAAU;AAEjC,QAAM,WAA8B,CAAA;AAEpC,QAAM,YAAY,WAAW;AAC7B,aAAW,WAAW,WAAW,UAAU;AACzC,UAAM,SAAS,eAAe,SAAS,SAAS;AAGhD,QAAI,WAAW,SAAS,WAAW,QAAQ;AACzC;AAAA,IACF;AAEA,UAAM,OAAwB;AAAA,MAC5B,OAAO,QAAQ;AAAA,MACf;AAAA,MACA,cAAc,QAAQ,WAAW,IAAI;AAAA,IAAA;AAIvC,QAAI,QAAQ,KAAM,MAAK,OAAO,QAAQ;AACtC,QAAI,QAAQ,IAAK,MAAK,MAAM,QAAQ;AACpC,QAAI,QAAQ,SAAU,MAAK,WAAW,QAAQ;AAC9C,QAAI,QAAQ,QAAS,MAAK,UAAU,QAAQ;AAC5C,QAAI,QAAQ,OAAQ,MAAK,SAAS,QAAQ;AAC1C,QAAI,QAAQ,KAAM,MAAK,OAAO,QAAQ;AACtC,QAAI,QAAQ,cAAe,MAAK,gBAAgB,QAAQ;AAExD,aAAS,KAAK,IAAI;AAAA,EACpB;AAEA,SAAO;AAAA,IACL,WAAW,QAAQ;AAAA,IACnB;AAAA,IACA;AAAA,EAAA;AAEJ;AAKO,SAAS,iBAAiB,QAAkC;AACjE,MAAI,OAAO,SAAS,WAAW,GAAG;AAChC,WAAO,sCAAsC,OAAO,MAAM;AAAA,EAC5D;AAEA,QAAM,QAAkB,CAAA;AACxB,QAAM,KAAK,GAAG,OAAO,SAAS,MAAM,sBAAsB,OAAO,MAAM,GAAG;AAC1E,QAAM,KAAK,EAAE;AAEb,aAAW,WAAW,OAAO,UAAU;AACrC,UAAM,KAAK,QAAQ,QAAQ,QAAQ,OAAO,QAAQ,YAAY,QAAQ,WAAW,QAAQ,UAAU;AACnG,UAAM,OAAO,QAAQ,QAAQ;AAC7B,UAAM,WAAW,QAAQ,gBAAgB,KAAK,QAAQ,aAAa,MAAM;AACzE,UAAM,KAAK,IAAI,QAAQ,MAAM,KAAK,QAAQ,KAAK,EAAE;AACjD,UAAM,KAAK,SAAS,EAAE,YAAY,IAAI,eAAe,QAAQ,WAAW,GAAG,QAAQ,EAAE;AAAA,EACvF;AAEA,SAAO,MAAM,KAAK,IAAI;AACxB;"}
1
+ {"version":3,"file":"list.js","sources":["../../../../src/cli/commands/review/list.ts"],"sourcesContent":["/**\n * review list command - List articles with optional filtering\n */\n\nimport { join } from 'node:path';\nimport { readFile } from 'node:fs/promises';\nimport { parse as parseYaml } from 'yaml';\nimport { classifyStatus, type ReviewFile, type ReviewStatus } from './types.js';\n\nexport type ListFilter =\n | 'pending'\n | 'incomplete'\n | 'all-uncertain'\n | 'agreed-include'\n | 'agreed-exclude'\n | 'divided'\n | 'finalized'\n | 'all';\n\nexport interface ReviewListOptions {\n sessionId: string;\n filter?: ListFilter;\n}\n\nexport interface ArticleListItem {\n title: string;\n pmid?: string;\n doi?: string;\n scopusId?: string;\n arxivId?: string;\n ericId?: string;\n year?: string;\n status: ReviewStatus;\n reviewCount: number;\n finalDecision?: 'include' | 'exclude';\n}\n\nexport interface ReviewListResult {\n sessionId: string;\n filter: ListFilter;\n articles: ArticleListItem[];\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 list command\n */\nexport async function executeReviewList(\n options: ReviewListOptions,\n sessionsDir: string\n): Promise<ReviewListResult> {\n const sessionDir = join(sessionsDir, options.sessionId);\n const reviewFile = await loadReviewFile(sessionDir);\n const filter = options.filter ?? 'all';\n\n const articles: ArticleListItem[] = [];\n\n const reviewers = reviewFile.reviewers;\n for (const article of reviewFile.articles) {\n const status = classifyStatus(article, reviewers);\n\n // Apply filter\n if (filter !== 'all' && status !== filter) {\n continue;\n }\n\n const item: ArticleListItem = {\n title: article.title,\n status,\n reviewCount: (article.reviews ?? []).length,\n };\n\n // Add optional identifiers\n if (article.pmid) item.pmid = article.pmid;\n if (article.doi) item.doi = article.doi;\n if (article.scopusId) item.scopusId = article.scopusId;\n if (article.arxivId) item.arxivId = article.arxivId;\n if (article.ericId) item.ericId = article.ericId;\n if (article.year) item.year = article.year;\n if (article.finalDecision) item.finalDecision = article.finalDecision;\n\n articles.push(item);\n }\n\n return {\n sessionId: options.sessionId,\n filter,\n articles,\n };\n}\n\n/**\n * Format list result as human-readable string\n */\nexport function formatListOutput(result: ReviewListResult): string {\n if (result.articles.length === 0) {\n return `No articles found matching filter: ${result.filter}`;\n }\n\n const lines: string[] = [];\n lines.push(`${result.articles.length} articles (filter: ${result.filter})`);\n lines.push('');\n\n for (const article of result.articles) {\n const id = article.pmid ?? article.doi ?? article.scopusId ?? article.arxivId ?? article.ericId ?? '-';\n const year = article.year ?? '-';\n const decision = article.finalDecision ? ` [${article.finalDecision}]` : '';\n lines.push(`[${article.status}] ${article.title}`);\n lines.push(` ID: ${id} | Year: ${year} | Reviews: ${article.reviewCount}${decision}`);\n }\n\n return lines.join('\\n');\n}\n"],"names":["parseYaml"],"mappings":";;;;AA8CA,eAAe,eAAe,YAAyC;AACrE,QAAM,cAAc,KAAK,YAAY,aAAa,cAAc;AAChE,QAAM,UAAU,MAAM,SAAS,aAAa,OAAO;AACnD,SAAOA,MAAU,OAAO;AAC1B;AAKA,eAAsB,kBACpB,SACA,aAC2B;AAC3B,QAAM,aAAa,KAAK,aAAa,QAAQ,SAAS;AACtD,QAAM,aAAa,MAAM,eAAe,UAAU;AAClD,QAAM,SAAS,QAAQ,UAAU;AAEjC,QAAM,WAA8B,CAAA;AAEpC,QAAM,YAAY,WAAW;AAC7B,aAAW,WAAW,WAAW,UAAU;AACzC,UAAM,SAAS,eAAe,SAAS,SAAS;AAGhD,QAAI,WAAW,SAAS,WAAW,QAAQ;AACzC;AAAA,IACF;AAEA,UAAM,OAAwB;AAAA,MAC5B,OAAO,QAAQ;AAAA,MACf;AAAA,MACA,cAAc,QAAQ,WAAW,IAAI;AAAA,IAAA;AAIvC,QAAI,QAAQ,KAAM,MAAK,OAAO,QAAQ;AACtC,QAAI,QAAQ,IAAK,MAAK,MAAM,QAAQ;AACpC,QAAI,QAAQ,SAAU,MAAK,WAAW,QAAQ;AAC9C,QAAI,QAAQ,QAAS,MAAK,UAAU,QAAQ;AAC5C,QAAI,QAAQ,OAAQ,MAAK,SAAS,QAAQ;AAC1C,QAAI,QAAQ,KAAM,MAAK,OAAO,QAAQ;AACtC,QAAI,QAAQ,cAAe,MAAK,gBAAgB,QAAQ;AAExD,aAAS,KAAK,IAAI;AAAA,EACpB;AAEA,SAAO;AAAA,IACL,WAAW,QAAQ;AAAA,IACnB;AAAA,IACA;AAAA,EAAA;AAEJ;AAKO,SAAS,iBAAiB,QAAkC;AACjE,MAAI,OAAO,SAAS,WAAW,GAAG;AAChC,WAAO,sCAAsC,OAAO,MAAM;AAAA,EAC5D;AAEA,QAAM,QAAkB,CAAA;AACxB,QAAM,KAAK,GAAG,OAAO,SAAS,MAAM,sBAAsB,OAAO,MAAM,GAAG;AAC1E,QAAM,KAAK,EAAE;AAEb,aAAW,WAAW,OAAO,UAAU;AACrC,UAAM,KAAK,QAAQ,QAAQ,QAAQ,OAAO,QAAQ,YAAY,QAAQ,WAAW,QAAQ,UAAU;AACnG,UAAM,OAAO,QAAQ,QAAQ;AAC7B,UAAM,WAAW,QAAQ,gBAAgB,KAAK,QAAQ,aAAa,MAAM;AACzE,UAAM,KAAK,IAAI,QAAQ,MAAM,KAAK,QAAQ,KAAK,EAAE;AACjD,UAAM,KAAK,SAAS,EAAE,YAAY,IAAI,eAAe,QAAQ,WAAW,GAAG,QAAQ,EAAE;AAAA,EACvF;AAEA,SAAO,MAAM,KAAK,IAAI;AACxB;"}
@@ -1,8 +1,11 @@
1
1
  import { ReviewStatusResult } from './status.js';
2
+ import { ReviewMode } from './types.js';
2
3
  import { Suggestion, SuggestionResult } from '../../suggestions/types.js';
3
4
  export interface ReviewNextStepsContext {
4
5
  sessionId: string;
5
6
  statusResult: ReviewStatusResult;
7
+ /** Review mode: screening (default) or picking */
8
+ mode?: ReviewMode;
6
9
  /** Extract name for batch continuation */
7
10
  extractName?: string;
8
11
  /** Number of articles extracted in current batch */
@@ -33,7 +36,7 @@ export declare function computeBatchContinuation(params: BatchContinuationParams
33
36
  * Evaluation order (top-to-bottom, first match wins for primary suggestion):
34
37
  * 1. pending > 0 → extract for title screening
35
38
  * 2. agreed > 0 → finalize consensus articles
36
- * 3. (conflicting + uncertain + incomplete) > 0 → extract for next basis level
39
+ * 3. (divided + allUncertain + incomplete) > 0 → extract for next basis level
37
40
  * 4. all finalized → register accepted articles
38
41
  *
39
42
  * Batch continuation is appended to seeAlso when applicable.
@@ -1 +1 @@
1
- {"version":3,"file":"next-steps.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/review/next-steps.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAEtD,OAAO,KAAK,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AAE/E,MAAM,WAAW,sBAAsB;IACrC,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,kBAAkB,CAAC;IACjC,0CAA0C;IAC1C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,oDAAoD;IACpD,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,yCAAyC;IACzC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,4BAA4B;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,6BAA6B;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,uBAAuB;IACtC,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC7B;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,uBAAuB,GAAG,UAAU,GAAG,IAAI,CAU3F;AAaD;;;;;;;;;;GAUG;AACH,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,sBAAsB,GAAG,gBAAgB,GAAG,IAAI,CA+D5F"}
1
+ {"version":3,"file":"next-steps.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/review/next-steps.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACtD,OAAO,KAAK,EAAe,UAAU,EAAE,MAAM,YAAY,CAAC;AAC1D,OAAO,KAAK,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AAE/E,MAAM,WAAW,sBAAsB;IACrC,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,kBAAkB,CAAC;IACjC,kDAAkD;IAClD,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,0CAA0C;IAC1C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,oDAAoD;IACpD,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,yCAAyC;IACzC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,4BAA4B;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,6BAA6B;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,uBAAuB;IACtC,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC7B;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,uBAAuB,GAAG,UAAU,GAAG,IAAI,CAU3F;AAaD;;;;;;;;;;GAUG;AACH,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,sBAAsB,GAAG,gBAAgB,GAAG,IAAI,CA4G5F"}
@@ -15,35 +15,69 @@ function detectNextBasis(reviewers) {
15
15
  return "fulltext";
16
16
  }
17
17
  function generateReviewNextSteps(ctx) {
18
- const { sessionId, statusResult: rs } = ctx;
18
+ const { sessionId, statusResult: rs, mode = "screening" } = ctx;
19
19
  if (rs.total === 0) return null;
20
20
  const result = { next: [], seeAlso: [] };
21
- if (rs.pending > 0) {
22
- result.next.push({
23
- command: `search-hub review extract --session ${sessionId} --basis title --filter pending --reviewer "<name>" --name title-screening`,
24
- description: `Extract ${rs.pending} pending articles for title screening`
25
- });
26
- } else {
27
- const agreed = rs.agreedInclude + rs.agreedExclude;
28
- if (agreed > 0) {
21
+ if (mode === "picking") {
22
+ if (rs.pending > 0) {
29
23
  result.next.push({
30
- command: `search-hub review finalize --session ${sessionId}`,
31
- description: `Finalize ${agreed} articles with consensus`
24
+ command: `search-hub review extract --session ${sessionId} --basis title --filter pending --reviewer "<name>" --name title-picking`,
25
+ description: `Extract ${rs.pending} pending articles for title review`
32
26
  });
33
- } else if (rs.conflicting > 0 || rs.uncertain > 0 || rs.incomplete > 0) {
34
- const unresolved = rs.conflicting + rs.uncertain + rs.incomplete;
27
+ } else if (rs.agreedInclude > 0 || rs.allUncertain > 0) {
35
28
  const nextBasis = detectNextBasis(rs.reviewers);
29
+ const parts = [];
30
+ if (rs.agreedInclude > 0) parts.push(`${rs.agreedInclude} picked`);
31
+ if (rs.allUncertain > 0) parts.push(`${rs.allUncertain} uncertain`);
32
+ const description = `${parts.join(" + ")} — confirm at ${nextBasis} level`;
36
33
  result.next.push({
37
- command: `search-hub review extract --session ${sessionId} --filter conflicting,uncertain,incomplete --basis ${nextBasis} --reviewer "<name>" --name ${nextBasis}-screening`,
38
- description: `${unresolved} articles need ${nextBasis}-level review`
34
+ command: `search-hub review extract --session ${sessionId} --filter agreed-include,all-uncertain --basis ${nextBasis} --reviewer "<name>" --name ${nextBasis}-screening`,
35
+ description
39
36
  });
40
- } else if (rs.finalized > 0 && rs.finalized === rs.total) {
37
+ } else {
38
+ const agreed = rs.agreedInclude + rs.agreedExclude;
39
+ if (agreed > 0) {
40
+ result.next.push({
41
+ command: `search-hub review finalize --session ${sessionId}`,
42
+ description: `Finalize ${agreed} articles with consensus`
43
+ });
44
+ } else if (rs.finalized > 0 && rs.finalized === rs.total) {
45
+ result.next.push({
46
+ command: `search-hub review export --session ${sessionId} --only included`,
47
+ description: `${rs.included} articles ready for export`
48
+ });
49
+ } else {
50
+ return null;
51
+ }
52
+ }
53
+ } else {
54
+ if (rs.pending > 0) {
41
55
  result.next.push({
42
- command: `search-hub register ${sessionId} --reviewed`,
43
- description: "Register accepted articles"
56
+ command: `search-hub review extract --session ${sessionId} --basis title --filter pending --reviewer "<name>" --name title-screening`,
57
+ description: `Extract ${rs.pending} pending articles for title screening`
44
58
  });
45
59
  } else {
46
- return null;
60
+ const agreed = rs.agreedInclude + rs.agreedExclude;
61
+ if (agreed > 0) {
62
+ result.next.push({
63
+ command: `search-hub review finalize --session ${sessionId}`,
64
+ description: `Finalize ${agreed} articles with consensus`
65
+ });
66
+ } else if (rs.divided > 0 || rs.allUncertain > 0 || rs.incomplete > 0) {
67
+ const unresolved = rs.divided + rs.allUncertain + rs.incomplete;
68
+ const nextBasis = detectNextBasis(rs.reviewers);
69
+ result.next.push({
70
+ command: `search-hub review extract --session ${sessionId} --filter divided,all-uncertain,incomplete --basis ${nextBasis} --reviewer "<name>" --name ${nextBasis}-screening`,
71
+ description: `${unresolved} articles need ${nextBasis}-level review`
72
+ });
73
+ } else if (rs.finalized > 0 && rs.finalized === rs.total) {
74
+ result.next.push({
75
+ command: `search-hub register ${sessionId} --reviewed`,
76
+ description: "Register accepted articles"
77
+ });
78
+ } else {
79
+ return null;
80
+ }
47
81
  }
48
82
  }
49
83
  if (ctx.limit !== void 0 && ctx.extractedCount !== void 0 && ctx.totalMatching !== void 0) {
@@ -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. (conflicting + uncertain + 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 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. conflicting, uncertain, or incomplete > 0: suggest further review\n else if (rs.conflicting > 0 || rs.uncertain > 0 || rs.incomplete > 0) {\n const unresolved = rs.conflicting + rs.uncertain + rs.incomplete;\n const nextBasis = detectNextBasis(rs.reviewers);\n result.next.push({\n command: `search-hub review extract --session ${sessionId} --filter conflicting,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 // 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":"AAqCO,SAAS,yBAAyB,QAAoD;AAC3F,QAAM,cAAc,OAAO,UAAU,KAAK,OAAO;AACjD,QAAM,YAAY,OAAO,gBAAgB;AACzC,MAAI,aAAa,EAAG,QAAO;AAE3B,QAAM,WAAW,OAAO,cAAc,GAAG,OAAO,WAAW,UAAU;AACrE,SAAO;AAAA,IACL,SAAS,uCAAuC,OAAO,SAAS,aAAa,UAAU,YAAY,OAAO,KAAK,WAAW,QAAQ;AAAA,IAClI,aAAa,GAAG,SAAS;AAAA,EAAA;AAE7B;AAMA,SAAS,gBAAgB,WAAyD;AAChF,QAAM,QAAQ,IAAI,IAAI,UAAU,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC;AACnD,MAAI,CAAC,MAAM,IAAI,OAAO,EAAG,QAAO;AAChC,MAAI,CAAC,MAAM,IAAI,UAAU,EAAG,QAAO;AACnC,SAAO;AACT;AAaO,SAAS,wBAAwB,KAAsD;AAC5F,QAAM,EAAE,WAAW,cAAc,GAAA,IAAO;AAExC,MAAI,GAAG,UAAU,EAAG,QAAO;AAE3B,QAAM,SAA2B,EAAE,MAAM,CAAA,GAAI,SAAS,CAAA,EAAC;AAGvD,MAAI,GAAG,UAAU,GAAG;AAClB,WAAO,KAAK,KAAK;AAAA,MACf,SAAS,uCAAuC,SAAS;AAAA,MACzD,aAAa,WAAW,GAAG,OAAO;AAAA,IAAA,CACnC;AAAA,EACH,OAEK;AACH,UAAM,SAAS,GAAG,gBAAgB,GAAG;AACrC,QAAI,SAAS,GAAG;AACd,aAAO,KAAK,KAAK;AAAA,QACf,SAAS,wCAAwC,SAAS;AAAA,QAC1D,aAAa,YAAY,MAAM;AAAA,MAAA,CAChC;AAAA,IACH,WAES,GAAG,cAAc,KAAK,GAAG,YAAY,KAAK,GAAG,aAAa,GAAG;AACpE,YAAM,aAAa,GAAG,cAAc,GAAG,YAAY,GAAG;AACtD,YAAM,YAAY,gBAAgB,GAAG,SAAS;AAC9C,aAAO,KAAK,KAAK;AAAA,QACf,SAAS,uCAAuC,SAAS,sDAAsD,SAAS,+BAA+B,SAAS;AAAA,QAChK,aAAa,GAAG,UAAU,kBAAkB,SAAS;AAAA,MAAA,CACtD;AAAA,IACH,WAES,GAAG,YAAY,KAAK,GAAG,cAAc,GAAG,OAAO;AACtD,aAAO,KAAK,KAAK;AAAA,QACf,SAAS,uBAAuB,SAAS;AAAA,QACzC,aAAa;AAAA,MAAA,CACd;AAAA,IACH,OAEK;AACH,aAAO;AAAA,IACT;AAAA,EACF;AAGA,MACE,IAAI,UAAU,UACd,IAAI,mBAAmB,UACvB,IAAI,kBAAkB,QACtB;AACA,UAAM,QAAQ,yBAAyB;AAAA,MACrC;AAAA,MACA,aAAa,IAAI;AAAA,MACjB,gBAAgB,IAAI;AAAA,MACpB,eAAe,IAAI;AAAA,MACnB,OAAO,IAAI;AAAA,MACX,QAAQ,IAAI;AAAA,IAAA,CACb;AACD,QAAI,MAAO,QAAO,QAAQ,KAAK,KAAK;AAAA,EACtC;AAEA,SAAO;AACT;"}
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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAU6B,CAAC;AAE3D,8DAA8D;AAC9D,wBAAgB,wBAAwB,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAIlE"}
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
  }
@@ -7,15 +7,17 @@ export interface ReviewStatusResult {
7
7
  total: number;
8
8
  pending: number;
9
9
  incomplete: number;
10
- uncertain: number;
10
+ allUncertain: number;
11
11
  agreedInclude: number;
12
12
  agreedExclude: number;
13
- conflicting: number;
13
+ divided: number;
14
14
  finalized: number;
15
15
  included: number;
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;AAElF,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,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,gDAAgD;IAChD,SAAS,EAAE,cAAc,EAAE,CAAC;CAC7B;AAWD;;GAEG;AACH,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,mBAAmB,EAC5B,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,kBAAkB,CAAC,CAwD7B;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,kBAAkB,GAAG,MAAM,CAuBrE"}
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"}
@@ -14,10 +14,10 @@ async function executeReviewStatus(options, sessionsDir) {
14
14
  const counts = {
15
15
  pending: 0,
16
16
  incomplete: 0,
17
- uncertain: 0,
17
+ allUncertain: 0,
18
18
  agreedInclude: 0,
19
19
  agreedExclude: 0,
20
- conflicting: 0,
20
+ divided: 0,
21
21
  finalized: 0,
22
22
  included: 0,
23
23
  excluded: 0
@@ -31,8 +31,8 @@ async function executeReviewStatus(options, sessionsDir) {
31
31
  case "incomplete":
32
32
  counts.incomplete++;
33
33
  break;
34
- case "uncertain":
35
- counts.uncertain++;
34
+ case "all-uncertain":
35
+ counts.allUncertain++;
36
36
  break;
37
37
  case "agreed-include":
38
38
  counts.agreedInclude++;
@@ -40,8 +40,8 @@ async function executeReviewStatus(options, sessionsDir) {
40
40
  case "agreed-exclude":
41
41
  counts.agreedExclude++;
42
42
  break;
43
- case "conflicting":
44
- counts.conflicting++;
43
+ case "divided":
44
+ counts.divided++;
45
45
  break;
46
46
  case "finalized":
47
47
  counts.finalized++;
@@ -57,21 +57,23 @@ 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
- `Review Progress: ${id}`,
68
- ` Total: ${result.total}`,
69
- ` Pending: ${result.pending}`,
70
- ` Incomplete: ${result.incomplete}`,
71
- ` Uncertain: ${result.uncertain}`,
72
- ` Agreed: ${agreed} (include: ${result.agreedInclude}, exclude: ${result.agreedExclude})`,
73
- ` Conflicting: ${result.conflicting}`,
74
- ` Finalized: ${result.finalized} (include: ${result.included}, exclude: ${result.excluded})`
69
+ header,
70
+ ` Total: ${result.total}`,
71
+ ` Pending: ${result.pending}`,
72
+ ` Incomplete: ${result.incomplete}`,
73
+ ` All-uncertain: ${result.allUncertain}`,
74
+ ` Agreed: ${agreed} (include: ${result.agreedInclude}, exclude: ${result.agreedExclude})`,
75
+ ` Divided: ${result.divided}`,
76
+ ` Finalized: ${result.finalized} (include: ${result.included}, exclude: ${result.excluded})`
75
77
  ];
76
78
  if (result.reviewers.length > 0) {
77
79
  lines.push("");
@@ -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 uncertain: number;\n agreedInclude: number;\n agreedExclude: number;\n conflicting: 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 uncertain: 0,\n agreedInclude: 0,\n agreedExclude: 0,\n conflicting: 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 'uncertain':\n counts.uncertain++;\n break;\n case 'agreed-include':\n counts.agreedInclude++;\n break;\n case 'agreed-exclude':\n counts.agreedExclude++;\n break;\n case 'conflicting':\n counts.conflicting++;\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 lines = [\n `Review Progress: ${id}`,\n ` Total: ${result.total}`,\n ` Pending: ${result.pending}`,\n ` Incomplete: ${result.incomplete}`,\n ` Uncertain: ${result.uncertain}`,\n ` Agreed: ${agreed} (include: ${result.agreedInclude}, exclude: ${result.agreedExclude})`,\n ` Conflicting: ${result.conflicting}`,\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":";;;;AAgCA,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,WAAW;AAAA,IACX,eAAe;AAAA,IACf,eAAe;AAAA,IACf,aAAa;AAAA,IACb,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,GAAG;AAAA,EAAA;AAEP;AAKO,SAAS,mBAAmB,QAAoC;AACrE,QAAM,KAAK,OAAO;AAClB,QAAM,SAAS,OAAO,gBAAgB,OAAO;AAC7C,QAAM,QAAQ;AAAA,IACZ,oBAAoB,EAAE;AAAA,IACtB,oBAAoB,OAAO,KAAK;AAAA,IAChC,oBAAoB,OAAO,OAAO;AAAA,IAClC,oBAAoB,OAAO,UAAU;AAAA,IACrC,oBAAoB,OAAO,SAAS;AAAA,IACpC,oBAAoB,MAAM,eAAe,OAAO,aAAa,cAAc,OAAO,aAAa;AAAA,IAC/F,oBAAoB,OAAO,WAAW;AAAA,IACtC,oBAAoB,OAAO,SAAS,eAAe,OAAO,QAAQ,cAAc,OAAO,QAAQ;AAAA,EAAA;AAGjG,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
+ {"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>;
@@ -41,7 +42,7 @@ export declare function basisRank(basis: ReviewBasis | undefined): number;
41
42
  /**
42
43
  * Review status classification (7-state model)
43
44
  */
44
- export type ReviewStatus = 'pending' | 'incomplete' | 'uncertain' | 'agreed-include' | 'agreed-exclude' | 'conflicting' | 'finalized';
45
+ export type ReviewStatus = 'pending' | 'incomplete' | 'all-uncertain' | 'agreed-include' | 'agreed-exclude' | 'divided' | 'finalized';
45
46
  /**
46
47
  * Classify the review status of an article entry
47
48
  *
@@ -49,10 +50,10 @@ export type ReviewStatus = 'pending' | 'incomplete' | 'uncertain' | 'agreed-incl
49
50
  * 1. finalDecision set? → finalized
50
51
  * 2. No reviews? → pending
51
52
  * 3. Registered reviewer missing? → incomplete
52
- * 4. include AND exclude present? conflicting
53
- * 5. Any uncertain? uncertain
54
- * 6. All include? → agreed-include
55
- * 7. All exclude? agreed-exclude
53
+ * 4. All uncertain? all-uncertain
54
+ * 5. All include? agreed-include
55
+ * 6. All exclude? → agreed-exclude
56
+ * 7. Any mix of decisions? divided
56
57
  */
57
58
  export declare function classifyStatus(entry: ArticleEntry, registeredReviewers?: ReviewerRecord[]): ReviewStatus;
58
59
  //# sourceMappingURL=types.d.ts.map
@@ -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,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
+ {"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"}
@@ -69,19 +69,16 @@ function classifyStatus(entry, registeredReviewers) {
69
69
  if (effectiveDecisions.length === 0) {
70
70
  return "pending";
71
71
  }
72
- const hasInclude = effectiveDecisions.includes("include");
73
- const hasExclude = effectiveDecisions.includes("exclude");
74
- if (hasInclude && hasExclude) {
75
- return "conflicting";
76
- }
77
- const hasUncertain = effectiveDecisions.includes("uncertain");
78
- if (hasUncertain) {
79
- return "uncertain";
72
+ if (effectiveDecisions.every((d) => d === "uncertain")) {
73
+ return "all-uncertain";
80
74
  }
81
75
  if (effectiveDecisions.every((d) => d === "include")) {
82
76
  return "agreed-include";
83
77
  }
84
- return "agreed-exclude";
78
+ if (effectiveDecisions.every((d) => d === "exclude")) {
79
+ return "agreed-exclude";
80
+ }
81
+ return "divided";
85
82
  }
86
83
  export {
87
84
  basisRank,
@@ -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 | '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
+ {"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,CAwVhC;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"}
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"}