@ncukondo/search-hub 0.17.0 → 0.19.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 (44) hide show
  1. package/dist/cli/commands/query/assess.d.ts +15 -0
  2. package/dist/cli/commands/query/assess.d.ts.map +1 -0
  3. package/dist/cli/commands/query/assess.js +38 -0
  4. package/dist/cli/commands/query/assess.js.map +1 -0
  5. package/dist/cli/commands/query/iteration-log.d.ts +58 -0
  6. package/dist/cli/commands/query/iteration-log.d.ts.map +1 -0
  7. package/dist/cli/commands/query/iteration-log.js +115 -0
  8. package/dist/cli/commands/query/iteration-log.js.map +1 -0
  9. package/dist/cli/commands/query/log.d.ts +9 -0
  10. package/dist/cli/commands/query/log.d.ts.map +1 -0
  11. package/dist/cli/commands/query/log.js +64 -0
  12. package/dist/cli/commands/query/log.js.map +1 -0
  13. package/dist/cli/commands/register.d.ts +1 -1
  14. package/dist/cli/commands/register.js.map +1 -1
  15. package/dist/cli/commands/review/finalize.js +6 -6
  16. package/dist/cli/commands/review/finalize.js.map +1 -1
  17. package/dist/cli/commands/review/init.d.ts.map +1 -1
  18. package/dist/cli/commands/review/init.js +5 -21
  19. package/dist/cli/commands/review/init.js.map +1 -1
  20. package/dist/cli/commands/review/list.d.ts +1 -1
  21. package/dist/cli/commands/review/list.d.ts.map +1 -1
  22. package/dist/cli/commands/review/list.js.map +1 -1
  23. package/dist/cli/commands/review/next-steps.d.ts +1 -1
  24. package/dist/cli/commands/review/next-steps.js +3 -3
  25. package/dist/cli/commands/review/next-steps.js.map +1 -1
  26. package/dist/cli/commands/review/schema.d.ts +199 -0
  27. package/dist/cli/commands/review/schema.d.ts.map +1 -0
  28. package/dist/cli/commands/review/schema.js +77 -0
  29. package/dist/cli/commands/review/schema.js.map +1 -0
  30. package/dist/cli/commands/review/status.d.ts +2 -2
  31. package/dist/cli/commands/review/status.d.ts.map +1 -1
  32. package/dist/cli/commands/review/status.js +13 -13
  33. package/dist/cli/commands/review/status.js.map +1 -1
  34. package/dist/cli/commands/review/types.d.ts +20 -75
  35. package/dist/cli/commands/review/types.d.ts.map +1 -1
  36. package/dist/cli/commands/review/types.js +8 -9
  37. package/dist/cli/commands/review/types.js.map +1 -1
  38. package/dist/cli/index.d.ts.map +1 -1
  39. package/dist/cli/index.js +78 -4
  40. package/dist/cli/index.js.map +1 -1
  41. package/dist/cli/suggestions/rules.d.ts.map +1 -1
  42. package/dist/cli/suggestions/rules.js +22 -2
  43. package/dist/cli/suggestions/rules.js.map +1 -1
  44. package/package.json +1 -1
@@ -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;"}
@@ -33,7 +33,7 @@ export declare function computeBatchContinuation(params: BatchContinuationParams
33
33
  * Evaluation order (top-to-bottom, first match wins for primary suggestion):
34
34
  * 1. pending > 0 → extract for title screening
35
35
  * 2. agreed > 0 → finalize consensus articles
36
- * 3. (conflicting + uncertain + incomplete) > 0 → extract for next basis level
36
+ * 3. (divided + allUncertain + incomplete) > 0 → extract for next basis level
37
37
  * 4. all finalized → register accepted articles
38
38
  *
39
39
  * Batch continuation is appended to seeAlso when applicable.
@@ -30,11 +30,11 @@ function generateReviewNextSteps(ctx) {
30
30
  command: `search-hub review finalize --session ${sessionId}`,
31
31
  description: `Finalize ${agreed} articles with consensus`
32
32
  });
33
- } else if (rs.conflicting > 0 || rs.uncertain > 0 || rs.incomplete > 0) {
34
- const unresolved = rs.conflicting + rs.uncertain + rs.incomplete;
33
+ } else if (rs.divided > 0 || rs.allUncertain > 0 || rs.incomplete > 0) {
34
+ const unresolved = rs.divided + rs.allUncertain + rs.incomplete;
35
35
  const nextBasis = detectNextBasis(rs.reviewers);
36
36
  result.next.push({
37
- command: `search-hub review extract --session ${sessionId} --filter conflicting,uncertain,incomplete --basis ${nextBasis} --reviewer "<name>" --name ${nextBasis}-screening`,
37
+ command: `search-hub review extract --session ${sessionId} --filter divided,all-uncertain,incomplete --basis ${nextBasis} --reviewer "<name>" --name ${nextBasis}-screening`,
38
38
  description: `${unresolved} articles need ${nextBasis}-level review`
39
39
  });
40
40
  } else if (rs.finalized > 0 && rs.finalized === rs.total) {
@@ -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 } from './types.js';\nimport type { Suggestion, SuggestionResult } from '../../suggestions/types.js';\n\nexport interface ReviewNextStepsContext {\n sessionId: string;\n statusResult: ReviewStatusResult;\n /** Extract name for batch continuation */\n extractName?: string;\n /** Number of articles extracted in current batch */\n extractedCount?: number;\n /** Total articles matching the filter */\n totalMatching?: number;\n /** Limit used in extract */\n limit?: number;\n /** Offset used in extract */\n offset?: number;\n}\n\nexport interface BatchContinuationParams {\n sessionId: string;\n extractName?: string | undefined;\n extractedCount: number;\n totalMatching: number;\n limit: number;\n offset?: number | undefined;\n}\n\n/**\n * Compute a batch continuation suggestion when --limit was used with remaining articles.\n * Returns null if no remaining articles.\n */\nexport function computeBatchContinuation(params: BatchContinuationParams): Suggestion | null {\n const nextOffset = (params.offset ?? 0) + params.extractedCount;\n const remaining = params.totalMatching - nextOffset;\n if (remaining <= 0) return null;\n\n const nextName = params.extractName ? `${params.extractName}-next` : 'next-batch';\n return {\n command: `search-hub review extract --session ${params.sessionId} --offset ${nextOffset} --limit ${params.limit} --name ${nextName}`,\n description: `${remaining} articles remaining — extract next batch`,\n };\n}\n\n/**\n * Detect the next review basis level from reviewer registry.\n * Progression: title → abstract → fulltext\n */\nfunction detectNextBasis(reviewers: ReviewStatusResult['reviewers']): ReviewBasis {\n const bases = new Set(reviewers.map((r) => r.basis));\n if (!bases.has('title')) return 'title';\n if (!bases.has('abstract')) return 'abstract';\n return 'fulltext';\n}\n\n/**\n * Generate dynamic next steps based on review status distribution.\n *\n * Evaluation order (top-to-bottom, first match wins for primary suggestion):\n * 1. pending > 0 → extract for title screening\n * 2. agreed > 0 → finalize consensus articles\n * 3. (divided + allUncertain + incomplete) > 0 → extract for next basis level\n * 4. all finalized → register accepted articles\n *\n * Batch continuation is appended to seeAlso when applicable.\n */\nexport function generateReviewNextSteps(ctx: ReviewNextStepsContext): SuggestionResult | null {\n const { sessionId, statusResult: rs } = ctx;\n\n if (rs.total === 0) return null;\n\n const result: SuggestionResult = { next: [], seeAlso: [] };\n\n // 1. pending > 0: title screening incomplete\n 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 // 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,UAAU,KAAK,GAAG,eAAe,KAAK,GAAG,aAAa,GAAG;AACnE,YAAM,aAAa,GAAG,UAAU,GAAG,eAAe,GAAG;AACrD,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;"}
@@ -0,0 +1,199 @@
1
+ /**
2
+ * Zod schemas for review workflow types.
3
+ *
4
+ * Single source of truth for:
5
+ * - TypeScript types (via z.infer<>)
6
+ * - JSON Schema for IDE autocompletion (via z.toJSONSchema())
7
+ *
8
+ * Follows the pattern established in src/query/json-schema.ts.
9
+ */
10
+ import * as z from 'zod';
11
+ export declare const reviewDecisionSchema: z.ZodEnum<{
12
+ exclude: "exclude";
13
+ include: "include";
14
+ uncertain: "uncertain";
15
+ }>;
16
+ export declare const reviewBasisSchema: z.ZodEnum<{
17
+ title: "title";
18
+ abstract: "abstract";
19
+ fulltext: "fulltext";
20
+ }>;
21
+ export declare const reviewSchema: z.ZodObject<{
22
+ reviewer: z.ZodString;
23
+ decision: z.ZodOptional<z.ZodEnum<{
24
+ exclude: "exclude";
25
+ include: "include";
26
+ uncertain: "uncertain";
27
+ }>>;
28
+ basis: z.ZodOptional<z.ZodEnum<{
29
+ title: "title";
30
+ abstract: "abstract";
31
+ fulltext: "fulltext";
32
+ }>>;
33
+ comment: z.ZodOptional<z.ZodString>;
34
+ timestamp: z.ZodOptional<z.ZodString>;
35
+ }, z.core.$strict>;
36
+ export declare const mergedSourceSchema: z.ZodObject<{
37
+ source: z.ZodString;
38
+ pmid: z.ZodOptional<z.ZodString>;
39
+ doi: z.ZodOptional<z.ZodString>;
40
+ scopusId: z.ZodOptional<z.ZodString>;
41
+ arxivId: z.ZodOptional<z.ZodString>;
42
+ ericId: z.ZodOptional<z.ZodString>;
43
+ }, z.core.$strict>;
44
+ export declare const articleFulltextRefSchema: z.ZodObject<{
45
+ dirName: z.ZodString;
46
+ hasFiles: z.ZodObject<{
47
+ pdf: z.ZodBoolean;
48
+ xml: z.ZodBoolean;
49
+ html: z.ZodBoolean;
50
+ markdown: z.ZodBoolean;
51
+ }, z.core.$strict>;
52
+ }, z.core.$strict>;
53
+ export declare const articleEntrySchema: z.ZodObject<{
54
+ doi: z.ZodOptional<z.ZodString>;
55
+ pmid: z.ZodOptional<z.ZodString>;
56
+ scopusId: z.ZodOptional<z.ZodString>;
57
+ arxivId: z.ZodOptional<z.ZodString>;
58
+ ericId: z.ZodOptional<z.ZodString>;
59
+ title: z.ZodString;
60
+ authors: z.ZodOptional<z.ZodString>;
61
+ year: z.ZodOptional<z.ZodString>;
62
+ abstract: z.ZodOptional<z.ZodString>;
63
+ mergedFrom: z.ZodOptional<z.ZodArray<z.ZodObject<{
64
+ source: z.ZodString;
65
+ pmid: z.ZodOptional<z.ZodString>;
66
+ doi: z.ZodOptional<z.ZodString>;
67
+ scopusId: z.ZodOptional<z.ZodString>;
68
+ arxivId: z.ZodOptional<z.ZodString>;
69
+ ericId: z.ZodOptional<z.ZodString>;
70
+ }, z.core.$strict>>>;
71
+ reviews: z.ZodArray<z.ZodObject<{
72
+ reviewer: z.ZodString;
73
+ decision: z.ZodOptional<z.ZodEnum<{
74
+ exclude: "exclude";
75
+ include: "include";
76
+ uncertain: "uncertain";
77
+ }>>;
78
+ basis: z.ZodOptional<z.ZodEnum<{
79
+ title: "title";
80
+ abstract: "abstract";
81
+ fulltext: "fulltext";
82
+ }>>;
83
+ comment: z.ZodOptional<z.ZodString>;
84
+ timestamp: z.ZodOptional<z.ZodString>;
85
+ }, z.core.$strict>>;
86
+ reviewHistory: z.ZodOptional<z.ZodArray<z.ZodObject<{
87
+ reviewer: z.ZodString;
88
+ decision: z.ZodOptional<z.ZodEnum<{
89
+ exclude: "exclude";
90
+ include: "include";
91
+ uncertain: "uncertain";
92
+ }>>;
93
+ basis: z.ZodOptional<z.ZodEnum<{
94
+ title: "title";
95
+ abstract: "abstract";
96
+ fulltext: "fulltext";
97
+ }>>;
98
+ comment: z.ZodOptional<z.ZodString>;
99
+ timestamp: z.ZodOptional<z.ZodString>;
100
+ }, z.core.$strict>>>;
101
+ finalDecision: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<"include">, z.ZodLiteral<"exclude">, z.ZodNull]>>;
102
+ fulltext: z.ZodOptional<z.ZodObject<{
103
+ dirName: z.ZodString;
104
+ hasFiles: z.ZodObject<{
105
+ pdf: z.ZodBoolean;
106
+ xml: z.ZodBoolean;
107
+ html: z.ZodBoolean;
108
+ markdown: z.ZodBoolean;
109
+ }, z.core.$strict>;
110
+ }, z.core.$strict>>;
111
+ }, z.core.$strict>;
112
+ export declare const reviewerRecordSchema: z.ZodObject<{
113
+ name: z.ZodString;
114
+ basis: z.ZodEnum<{
115
+ title: "title";
116
+ abstract: "abstract";
117
+ fulltext: "fulltext";
118
+ }>;
119
+ }, z.core.$strict>;
120
+ export declare const reviewFileSchema: z.ZodObject<{
121
+ sessionId: z.ZodString;
122
+ criteria: z.ZodOptional<z.ZodString>;
123
+ reviewer: z.ZodOptional<z.ZodString>;
124
+ basis: z.ZodOptional<z.ZodEnum<{
125
+ title: "title";
126
+ abstract: "abstract";
127
+ fulltext: "fulltext";
128
+ }>>;
129
+ articles: z.ZodArray<z.ZodObject<{
130
+ doi: z.ZodOptional<z.ZodString>;
131
+ pmid: z.ZodOptional<z.ZodString>;
132
+ scopusId: z.ZodOptional<z.ZodString>;
133
+ arxivId: z.ZodOptional<z.ZodString>;
134
+ ericId: z.ZodOptional<z.ZodString>;
135
+ title: z.ZodString;
136
+ authors: z.ZodOptional<z.ZodString>;
137
+ year: z.ZodOptional<z.ZodString>;
138
+ abstract: z.ZodOptional<z.ZodString>;
139
+ mergedFrom: z.ZodOptional<z.ZodArray<z.ZodObject<{
140
+ source: z.ZodString;
141
+ pmid: z.ZodOptional<z.ZodString>;
142
+ doi: z.ZodOptional<z.ZodString>;
143
+ scopusId: z.ZodOptional<z.ZodString>;
144
+ arxivId: z.ZodOptional<z.ZodString>;
145
+ ericId: z.ZodOptional<z.ZodString>;
146
+ }, z.core.$strict>>>;
147
+ reviews: z.ZodArray<z.ZodObject<{
148
+ reviewer: z.ZodString;
149
+ decision: z.ZodOptional<z.ZodEnum<{
150
+ exclude: "exclude";
151
+ include: "include";
152
+ uncertain: "uncertain";
153
+ }>>;
154
+ basis: z.ZodOptional<z.ZodEnum<{
155
+ title: "title";
156
+ abstract: "abstract";
157
+ fulltext: "fulltext";
158
+ }>>;
159
+ comment: z.ZodOptional<z.ZodString>;
160
+ timestamp: z.ZodOptional<z.ZodString>;
161
+ }, z.core.$strict>>;
162
+ reviewHistory: z.ZodOptional<z.ZodArray<z.ZodObject<{
163
+ reviewer: z.ZodString;
164
+ decision: z.ZodOptional<z.ZodEnum<{
165
+ exclude: "exclude";
166
+ include: "include";
167
+ uncertain: "uncertain";
168
+ }>>;
169
+ basis: z.ZodOptional<z.ZodEnum<{
170
+ title: "title";
171
+ abstract: "abstract";
172
+ fulltext: "fulltext";
173
+ }>>;
174
+ comment: z.ZodOptional<z.ZodString>;
175
+ timestamp: z.ZodOptional<z.ZodString>;
176
+ }, z.core.$strict>>>;
177
+ finalDecision: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<"include">, z.ZodLiteral<"exclude">, z.ZodNull]>>;
178
+ fulltext: z.ZodOptional<z.ZodObject<{
179
+ dirName: z.ZodString;
180
+ hasFiles: z.ZodObject<{
181
+ pdf: z.ZodBoolean;
182
+ xml: z.ZodBoolean;
183
+ html: z.ZodBoolean;
184
+ markdown: z.ZodBoolean;
185
+ }, z.core.$strict>;
186
+ }, z.core.$strict>>;
187
+ }, z.core.$strict>>;
188
+ reviewers: z.ZodOptional<z.ZodArray<z.ZodObject<{
189
+ name: z.ZodString;
190
+ basis: z.ZodEnum<{
191
+ title: "title";
192
+ abstract: "abstract";
193
+ fulltext: "fulltext";
194
+ }>;
195
+ }, z.core.$strict>>>;
196
+ }, z.core.$strict>;
197
+ /** Generate a JSON Schema from the review file Zod schema. */
198
+ export declare function generateReviewJSONSchema(): Record<string, unknown>;
199
+ //# sourceMappingURL=schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/review/schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,CAAC,MAAM,KAAK,CAAC;AAEzB,eAAO,MAAM,oBAAoB;;;;EAA8C,CAAC;AAEhF,eAAO,MAAM,iBAAiB;;;;EAA4C,CAAC;AAE3E,eAAO,MAAM,YAAY;;;;;;;;;;;;;;kBASyB,CAAC;AAEnD,eAAO,MAAM,kBAAkB;;;;;;;kBAUwB,CAAC;AAExD,eAAO,MAAM,wBAAwB;;;;;;;;kBAY1B,CAAC;AAEZ,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBA0ByC,CAAC;AAEzE,eAAO,MAAM,oBAAoB;;;;;;;kBAM6C,CAAC;AAE/E,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAU6B,CAAC;AAE3D,8DAA8D;AAC9D,wBAAgB,wBAAwB,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAIlE"}
@@ -0,0 +1,77 @@
1
+ import * as z from "zod";
2
+ const reviewDecisionSchema = z.enum(["include", "exclude", "uncertain"]);
3
+ const reviewBasisSchema = z.enum(["title", "abstract", "fulltext"]);
4
+ const reviewSchema = z.object({
5
+ reviewer: z.string().describe("Reviewer identifier (e.g., 'gpt-4o', 'claude-sonnet', 'human:tanaka')"),
6
+ decision: reviewDecisionSchema.describe("Assessment decision").optional(),
7
+ basis: reviewBasisSchema.describe("Basis of the decision (what information was used)").optional(),
8
+ comment: z.string().describe("Optional comment or reason").optional(),
9
+ timestamp: z.string().datetime().describe("ISO 8601 timestamp").optional()
10
+ }).strict().describe("Individual assessment of an article");
11
+ const mergedSourceSchema = z.object({
12
+ source: z.string().describe("Database source (e.g., 'pubmed', 'scopus')"),
13
+ pmid: z.string().optional(),
14
+ doi: z.string().optional(),
15
+ scopusId: z.string().optional(),
16
+ arxivId: z.string().optional(),
17
+ ericId: z.string().optional()
18
+ }).strict().describe("Source information for merged duplicates");
19
+ const articleFulltextRefSchema = z.object({
20
+ dirName: z.string(),
21
+ hasFiles: z.object({
22
+ pdf: z.boolean(),
23
+ xml: z.boolean(),
24
+ html: z.boolean(),
25
+ markdown: z.boolean()
26
+ }).strict()
27
+ }).strict();
28
+ const articleEntrySchema = z.object({
29
+ // Identifiers
30
+ doi: z.string().describe("Digital Object Identifier").optional(),
31
+ pmid: z.string().describe("PubMed ID").optional(),
32
+ scopusId: z.string().describe("Scopus ID").optional(),
33
+ arxivId: z.string().describe("arXiv ID").optional(),
34
+ ericId: z.string().describe("ERIC ID").optional(),
35
+ // Bibliographic info
36
+ title: z.string().describe("Article title"),
37
+ authors: z.string().describe("Authors (formatted string)").optional(),
38
+ year: z.string().describe("Publication year").optional(),
39
+ abstract: z.string().describe("Article abstract").optional(),
40
+ // Deduplication tracking
41
+ mergedFrom: z.array(mergedSourceSchema).describe("Sources this article was merged from during deduplication").optional(),
42
+ // Review data
43
+ reviews: z.array(reviewSchema).describe("List of assessments"),
44
+ reviewHistory: z.array(reviewSchema).describe("Historical reviews (only in extracted ReviewFiles, never in master file)").optional(),
45
+ finalDecision: z.union([z.literal("include"), z.literal("exclude"), z.null()]).describe("Final inclusion/exclusion decision (null in extracted files)").optional(),
46
+ // Fulltext reference
47
+ fulltext: articleFulltextRefSchema.optional()
48
+ }).strict().describe("Article with identifiers, bibliographic info, and reviews");
49
+ const reviewerRecordSchema = z.object({
50
+ name: z.string().describe("Reviewer identifier"),
51
+ basis: reviewBasisSchema.describe("Basis level at which the reviewer participated")
52
+ }).strict().describe("Record of a reviewer's participation at a specific basis level");
53
+ const reviewFileSchema = z.object({
54
+ sessionId: z.string().describe("Session identifier"),
55
+ criteria: z.string().describe("Path to inclusion criteria file").optional(),
56
+ reviewer: z.string().describe("Reviewer identifier (only in extracted ReviewFiles)").optional(),
57
+ basis: reviewBasisSchema.describe("Basis level for screening (only in extracted ReviewFiles)").optional(),
58
+ articles: z.array(articleEntrySchema).describe("List of articles with review data"),
59
+ reviewers: z.array(reviewerRecordSchema).describe("Registry of reviewers who participated at each basis level").optional()
60
+ }).strict().describe("Schema for article review workflow tracking");
61
+ function generateReviewJSONSchema() {
62
+ return z.toJSONSchema(reviewFileSchema, {
63
+ target: "draft-7"
64
+ });
65
+ }
66
+ export {
67
+ articleEntrySchema,
68
+ articleFulltextRefSchema,
69
+ generateReviewJSONSchema,
70
+ mergedSourceSchema,
71
+ reviewBasisSchema,
72
+ reviewDecisionSchema,
73
+ reviewFileSchema,
74
+ reviewSchema,
75
+ reviewerRecordSchema
76
+ };
77
+ //# sourceMappingURL=schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.js","sources":["../../../../src/cli/commands/review/schema.ts"],"sourcesContent":["/**\n * Zod schemas for review workflow types.\n *\n * Single source of truth for:\n * - TypeScript types (via z.infer<>)\n * - JSON Schema for IDE autocompletion (via z.toJSONSchema())\n *\n * Follows the pattern established in src/query/json-schema.ts.\n */\nimport * as z from 'zod';\n\nexport const reviewDecisionSchema = z.enum(['include', 'exclude', 'uncertain']);\n\nexport const reviewBasisSchema = z.enum(['title', 'abstract', 'fulltext']);\n\nexport const reviewSchema = z\n .object({\n reviewer: z.string().describe(\"Reviewer identifier (e.g., 'gpt-4o', 'claude-sonnet', 'human:tanaka')\"),\n decision: reviewDecisionSchema.describe('Assessment decision').optional(),\n basis: reviewBasisSchema.describe('Basis of the decision (what information was used)').optional(),\n comment: z.string().describe('Optional comment or reason').optional(),\n timestamp: z.string().datetime().describe('ISO 8601 timestamp').optional(),\n })\n .strict()\n .describe('Individual assessment of an article');\n\nexport const mergedSourceSchema = z\n .object({\n source: z.string().describe(\"Database source (e.g., 'pubmed', 'scopus')\"),\n pmid: z.string().optional(),\n doi: z.string().optional(),\n scopusId: z.string().optional(),\n arxivId: z.string().optional(),\n ericId: z.string().optional(),\n })\n .strict()\n .describe('Source information for merged duplicates');\n\nexport const articleFulltextRefSchema = z\n .object({\n dirName: z.string(),\n hasFiles: z\n .object({\n pdf: z.boolean(),\n xml: z.boolean(),\n html: z.boolean(),\n markdown: z.boolean(),\n })\n .strict(),\n })\n .strict();\n\nexport const articleEntrySchema = z\n .object({\n // Identifiers\n doi: z.string().describe('Digital Object Identifier').optional(),\n pmid: z.string().describe('PubMed ID').optional(),\n scopusId: z.string().describe('Scopus ID').optional(),\n arxivId: z.string().describe('arXiv ID').optional(),\n ericId: z.string().describe('ERIC ID').optional(),\n // Bibliographic info\n title: z.string().describe('Article title'),\n authors: z.string().describe('Authors (formatted string)').optional(),\n year: z.string().describe('Publication year').optional(),\n abstract: z.string().describe('Article abstract').optional(),\n // Deduplication tracking\n mergedFrom: z.array(mergedSourceSchema).describe('Sources this article was merged from during deduplication').optional(),\n // Review data\n reviews: z.array(reviewSchema).describe('List of assessments'),\n reviewHistory: z.array(reviewSchema).describe('Historical reviews (only in extracted ReviewFiles, never in master file)').optional(),\n finalDecision: z\n .union([z.literal('include'), z.literal('exclude'), z.null()])\n .describe('Final inclusion/exclusion decision (null in extracted files)')\n .optional(),\n // Fulltext reference\n fulltext: articleFulltextRefSchema.optional(),\n })\n .strict()\n .describe('Article with identifiers, bibliographic info, and reviews');\n\nexport const reviewerRecordSchema = z\n .object({\n name: z.string().describe('Reviewer identifier'),\n basis: reviewBasisSchema.describe('Basis level at which the reviewer participated'),\n })\n .strict()\n .describe('Record of a reviewer\\'s participation at a specific basis level');\n\nexport const reviewFileSchema = z\n .object({\n sessionId: z.string().describe('Session identifier'),\n criteria: z.string().describe('Path to inclusion criteria file').optional(),\n reviewer: z.string().describe('Reviewer identifier (only in extracted ReviewFiles)').optional(),\n basis: reviewBasisSchema.describe('Basis level for screening (only in extracted ReviewFiles)').optional(),\n articles: z.array(articleEntrySchema).describe('List of articles with review data'),\n reviewers: z.array(reviewerRecordSchema).describe('Registry of reviewers who participated at each basis level').optional(),\n })\n .strict()\n .describe('Schema for article review workflow tracking');\n\n/** Generate a JSON Schema from the review file Zod schema. */\nexport function generateReviewJSONSchema(): Record<string, unknown> {\n return z.toJSONSchema(reviewFileSchema, {\n target: 'draft-7',\n });\n}\n"],"names":[],"mappings":"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"}
@@ -7,10 +7,10 @@ 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;
@@ -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,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,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;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"}
@@ -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++;
@@ -65,13 +65,13 @@ function formatStatusOutput(result) {
65
65
  const agreed = result.agreedInclude + result.agreedExclude;
66
66
  const lines = [
67
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})`
68
+ ` Total: ${result.total}`,
69
+ ` Pending: ${result.pending}`,
70
+ ` Incomplete: ${result.incomplete}`,
71
+ ` All-uncertain: ${result.allUncertain}`,
72
+ ` Agreed: ${agreed} (include: ${result.agreedInclude}, exclude: ${result.agreedExclude})`,
73
+ ` Divided: ${result.divided}`,
74
+ ` Finalized: ${result.finalized} (include: ${result.included}, exclude: ${result.excluded})`
75
75
  ];
76
76
  if (result.reviewers.length > 0) {
77
77
  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 } from './types.js';\n\nexport interface ReviewStatusOptions {\n sessionId: string;\n}\n\nexport interface ReviewStatusResult {\n sessionId: string;\n total: number;\n pending: number;\n incomplete: number;\n allUncertain: number;\n agreedInclude: number;\n agreedExclude: number;\n divided: number;\n finalized: number;\n included: number;\n excluded: number;\n /** Registered reviewers from the review file */\n reviewers: ReviewerRecord[];\n}\n\n/**\n * Load review file from session directory\n */\nasync function loadReviewFile(sessionDir: string): Promise<ReviewFile> {\n const reviewsPath = join(sessionDir, '.internal', 'reviews.yaml');\n const content = await readFile(reviewsPath, 'utf-8');\n return parseYaml(content) as ReviewFile;\n}\n\n/**\n * Execute review status command\n */\nexport async function executeReviewStatus(\n options: ReviewStatusOptions,\n sessionsDir: string\n): Promise<ReviewStatusResult> {\n const sessionDir = join(sessionsDir, options.sessionId);\n const reviewFile = await loadReviewFile(sessionDir);\n\n const reviewers = reviewFile.reviewers ?? [];\n const counts = {\n pending: 0,\n incomplete: 0,\n allUncertain: 0,\n agreedInclude: 0,\n agreedExclude: 0,\n divided: 0,\n finalized: 0,\n included: 0,\n excluded: 0,\n };\n\n for (const article of reviewFile.articles) {\n const status = classifyStatus(article, reviewers);\n\n switch (status) {\n case 'pending':\n counts.pending++;\n break;\n case 'incomplete':\n counts.incomplete++;\n break;\n case 'all-uncertain':\n counts.allUncertain++;\n break;\n case 'agreed-include':\n counts.agreedInclude++;\n break;\n case 'agreed-exclude':\n counts.agreedExclude++;\n break;\n case 'divided':\n counts.divided++;\n break;\n case 'finalized':\n counts.finalized++;\n if (article.finalDecision === 'include') {\n counts.included++;\n } else {\n counts.excluded++;\n }\n break;\n }\n }\n\n return {\n sessionId: options.sessionId,\n total: reviewFile.articles.length,\n reviewers,\n ...counts,\n };\n}\n\n/**\n * Format status result as human-readable string\n */\nexport function formatStatusOutput(result: ReviewStatusResult): string {\n const id = result.sessionId;\n const agreed = result.agreedInclude + result.agreedExclude;\n const lines = [\n `Review Progress: ${id}`,\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":";;;;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,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,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,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,74 +1,19 @@
1
- import { ArticleFulltextRef } from '@ncukondo/academic-fulltext';
2
- export type ReviewDecision = 'include' | 'exclude' | 'uncertain';
1
+ import { reviewDecisionSchema, reviewBasisSchema, reviewSchema, mergedSourceSchema, articleEntrySchema, reviewerRecordSchema, reviewFileSchema } from './schema.js';
3
2
  /**
4
- * Basis of the review decision (what information was used)
5
- */
6
- export type ReviewBasis = 'title' | 'abstract' | 'fulltext';
7
- /**
8
- * Individual assessment of an article by a reviewer
9
- */
10
- export interface Review {
11
- /** Reviewer identifier: "human:name" or "ai:name" */
12
- reviewer: string;
13
- /** Assessment decision */
14
- decision?: ReviewDecision;
15
- /** Basis of the decision (what information was used) */
16
- basis?: ReviewBasis;
17
- /** Optional comment or reason */
18
- comment?: string;
19
- /** ISO 8601 timestamp (optional - auto-assigned on merge if not provided) */
20
- timestamp?: string;
21
- }
22
- /**
23
- * Source information for merged duplicates
24
- */
25
- export interface MergedSource {
26
- source: string;
27
- pmid?: string;
28
- doi?: string;
29
- scopusId?: string;
30
- arxivId?: string;
31
- ericId?: string;
32
- }
33
- /**
34
- * Article entry with identifiers, bibliographic info, and reviews
35
- */
36
- export interface ArticleEntry {
37
- doi?: string;
38
- pmid?: string;
39
- scopusId?: string;
40
- arxivId?: string;
41
- ericId?: string;
42
- title: string;
43
- authors?: string;
44
- year?: string;
45
- abstract?: string;
46
- mergedFrom?: MergedSource[];
47
- reviews: Review[];
48
- /** Historical reviews (only in extracted ReviewFiles, never in master file) */
49
- reviewHistory?: Review[];
50
- finalDecision?: 'include' | 'exclude' | null;
51
- fulltext?: ArticleFulltextRef;
52
- }
53
- /**
54
- * Top-level structure of the reviews.yaml file
55
- */
56
- export interface ReviewerRecord {
57
- name: string;
58
- basis: ReviewBasis;
59
- }
60
- export interface ReviewFile {
61
- sessionId: string;
62
- /** Path to inclusion criteria file */
63
- criteria?: string;
64
- /** Reviewer identifier (only in extracted ReviewFiles) */
65
- reviewer?: string;
66
- /** Basis level for screening (only in extracted ReviewFiles) */
67
- basis?: ReviewBasis;
68
- articles: ArticleEntry[];
69
- /** Registry of reviewers who participated at each basis level */
70
- reviewers?: ReviewerRecord[];
71
- }
3
+ * Review workflow types for article assessment tracking.
4
+ *
5
+ * Core data types (ReviewDecision, ReviewBasis, Review, MergedSource,
6
+ * ArticleEntry, ReviewerRecord, ReviewFile) are derived from Zod schemas
7
+ * in schema.ts — single source of truth for types and JSON Schema.
8
+ */
9
+ import * as z from 'zod';
10
+ export type ReviewDecision = z.infer<typeof reviewDecisionSchema>;
11
+ export type ReviewBasis = z.infer<typeof reviewBasisSchema>;
12
+ export type Review = z.infer<typeof reviewSchema>;
13
+ export type MergedSource = z.infer<typeof mergedSourceSchema>;
14
+ export type ArticleEntry = z.infer<typeof articleEntrySchema>;
15
+ export type ReviewerRecord = z.infer<typeof reviewerRecordSchema>;
16
+ export type ReviewFile = z.infer<typeof reviewFileSchema>;
72
17
  /**
73
18
  * Work file article entry for AI agent workflow
74
19
  */
@@ -96,7 +41,7 @@ export declare function basisRank(basis: ReviewBasis | undefined): number;
96
41
  /**
97
42
  * Review status classification (7-state model)
98
43
  */
99
- export type ReviewStatus = 'pending' | 'incomplete' | 'uncertain' | 'agreed-include' | 'agreed-exclude' | 'conflicting' | 'finalized';
44
+ export type ReviewStatus = 'pending' | 'incomplete' | 'all-uncertain' | 'agreed-include' | 'agreed-exclude' | 'divided' | 'finalized';
100
45
  /**
101
46
  * Classify the review status of an article entry
102
47
  *
@@ -104,10 +49,10 @@ export type ReviewStatus = 'pending' | 'incomplete' | 'uncertain' | 'agreed-incl
104
49
  * 1. finalDecision set? → finalized
105
50
  * 2. No reviews? → pending
106
51
  * 3. Registered reviewer missing? → incomplete
107
- * 4. include AND exclude present? conflicting
108
- * 5. Any uncertain? uncertain
109
- * 6. All include? → agreed-include
110
- * 7. All exclude? agreed-exclude
52
+ * 4. All uncertain? all-uncertain
53
+ * 5. All include? agreed-include
54
+ * 6. All exclude? → agreed-exclude
55
+ * 7. Any mix of decisions? divided
111
56
  */
112
57
  export declare function classifyStatus(entry: ArticleEntry, registeredReviewers?: ReviewerRecord[]): ReviewStatus;
113
58
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/review/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/review/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,KAAK,CAAC,MAAM,KAAK,CAAC;AACzB,OAAO,EACL,oBAAoB,EACpB,iBAAiB,EACjB,YAAY,EACZ,kBAAkB,EAClB,kBAAkB,EAClB,oBAAoB,EACpB,gBAAgB,EACjB,MAAM,aAAa,CAAC;AAErB,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAClE,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAC5D,MAAM,MAAM,MAAM,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC;AAClD,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAC9D,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAC9D,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAClE,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAE1D;;GAEG;AACH,iGAAiG;AACjG,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,wDAAwD;IACxD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,cAAc,GAAG,IAAI,CAAC;IAChC,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;GAEG;AACH,mGAAmG;AACnG,MAAM,WAAW,QAAQ;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,WAAW,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,eAAe,EAAE,CAAC;CAC7B;AAWD,wBAAgB,SAAS,CAAC,KAAK,EAAE,WAAW,GAAG,SAAS,GAAG,MAAM,CAGhE;AAED;;GAEG;AACH,MAAM,MAAM,YAAY,GACpB,SAAS,GACT,YAAY,GACZ,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"}