@ncukondo/search-hub 0.10.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/commands/merge.d.ts +82 -0
- package/dist/cli/commands/merge.d.ts.map +1 -0
- package/dist/cli/commands/merge.js +221 -0
- package/dist/cli/commands/merge.js.map +1 -0
- package/dist/cli/commands/resume.d.ts.map +1 -1
- package/dist/cli/commands/resume.js +7 -0
- package/dist/cli/commands/resume.js.map +1 -1
- package/dist/cli/commands/review/extract.d.ts +3 -1
- package/dist/cli/commands/review/extract.d.ts.map +1 -1
- package/dist/cli/commands/review/extract.js +75 -45
- package/dist/cli/commands/review/extract.js.map +1 -1
- package/dist/cli/commands/review/mark.d.ts.map +1 -1
- package/dist/cli/commands/review/mark.js +69 -22
- package/dist/cli/commands/review/mark.js.map +1 -1
- package/dist/cli/commands/review/merge.d.ts.map +1 -1
- package/dist/cli/commands/review/merge.js +8 -2
- package/dist/cli/commands/review/merge.js.map +1 -1
- package/dist/cli/commands/review/types.d.ts +4 -0
- package/dist/cli/commands/review/types.d.ts.map +1 -1
- package/dist/cli/commands/review/types.js +10 -3
- package/dist/cli/commands/review/types.js.map +1 -1
- package/dist/cli/commands/status.js +1 -1
- package/dist/cli/commands/status.js.map +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +143 -4
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/suggestions/rules.d.ts.map +1 -1
- package/dist/cli/suggestions/rules.js +20 -2
- package/dist/cli/suggestions/rules.js.map +1 -1
- package/dist/cli/suggestions/types.d.ts +6 -0
- package/dist/cli/suggestions/types.d.ts.map +1 -1
- package/dist/session/types.d.ts +14 -1
- package/dist/session/types.d.ts.map +1 -1
- package/dist/session/types.js +7 -0
- package/dist/session/types.js.map +1 -0
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -23,11 +23,12 @@ import { computeSummary, formatSummaryJson, formatSummary } from "./commands/sum
|
|
|
23
23
|
import { parseResultsOptions, validateResultsInput, formatResultsJson, formatResultsList } from "./commands/results.js";
|
|
24
24
|
import { loadNotes, formatAllSessionNotes, formatNotesList, addNote, addAssessment } from "./commands/notes.js";
|
|
25
25
|
import { computeDiff, computeQueryDiff, formatDiffJson, formatDiff } from "./commands/diff.js";
|
|
26
|
+
import { validateMergeSources, mergeArticles, formatMergeJson, formatMergeOutput, createMergedSession } from "./commands/merge.js";
|
|
26
27
|
import { executeReviewInit } from "./commands/review/init.js";
|
|
27
28
|
import { executeReviewStatus, formatStatusOutput } from "./commands/review/status.js";
|
|
28
29
|
import { executeReviewList, formatListOutput } from "./commands/review/list.js";
|
|
29
30
|
import { executeReviewExtract } from "./commands/review/extract.js";
|
|
30
|
-
import { executeReviewMerge, formatMergeOutput } from "./commands/review/merge.js";
|
|
31
|
+
import { executeReviewMerge, formatMergeOutput as formatMergeOutput$1 } from "./commands/review/merge.js";
|
|
31
32
|
import { executeReviewMark } from "./commands/review/mark.js";
|
|
32
33
|
import { executeReviewExport, formatExportOutput } from "./commands/review/export.js";
|
|
33
34
|
import { executeReviewFinalize, formatFinalizeOutput } from "./commands/review/finalize.js";
|
|
@@ -295,7 +296,13 @@ Examples:
|
|
|
295
296
|
$ search-hub search --db pubmed --query "diabetes[tiab]" # Direct query
|
|
296
297
|
$ search-hub search ./query.yaml --dry-run # Preview translations
|
|
297
298
|
$ search-hub search ./query.yaml --count-only # Get hit counts only
|
|
298
|
-
$ search-hub search ./query.yaml --max-results 100 # Limit results
|
|
299
|
+
$ search-hub search ./query.yaml --max-results 100 # Limit results
|
|
300
|
+
|
|
301
|
+
Query features (use "query init" to see full template):
|
|
302
|
+
filters: year_from, year_to, language, publication_types
|
|
303
|
+
exclude: NOT terms per block (terms.exclude)
|
|
304
|
+
mesh/eric: controlled vocabulary (terms.mesh, terms.eric)
|
|
305
|
+
overrides: per-database settings (pubmed, scopus, eric, arxiv)`).action(
|
|
299
306
|
async (queryFile, options) => {
|
|
300
307
|
const globalOpts = program.opts();
|
|
301
308
|
try {
|
|
@@ -967,6 +974,135 @@ Query Refinement Workflow:
|
|
|
967
974
|
} else {
|
|
968
975
|
if (!globalOpts.quiet) {
|
|
969
976
|
console.log(formatDiff(diff, sessionId1, sessionId2, showFilter, formatOptions));
|
|
977
|
+
const suggestion = formatSuggestion(getSuggestion({
|
|
978
|
+
command: "diff",
|
|
979
|
+
sessionId: sessionId2,
|
|
980
|
+
diffSession1Id: sessionId1,
|
|
981
|
+
diffAddedCount: diff.added.length,
|
|
982
|
+
diffRemovedCount: diff.removed.length
|
|
983
|
+
}));
|
|
984
|
+
if (suggestion) {
|
|
985
|
+
console.log(suggestion);
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
process.exitCode = EXIT_CODES.SUCCESS;
|
|
990
|
+
} catch (error) {
|
|
991
|
+
if (!globalOpts.quiet) {
|
|
992
|
+
console.error(
|
|
993
|
+
"Error:",
|
|
994
|
+
error instanceof Error ? error.message : error
|
|
995
|
+
);
|
|
996
|
+
}
|
|
997
|
+
process.exitCode = EXIT_CODES.SESSION_ERROR;
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
);
|
|
1001
|
+
program.command("merge").description("Merge results from multiple search sessions").argument("<session-ids...>", "two or more session IDs to merge").option("--name <string>", "name for merged session").option("--dry-run", "show what would be merged without creating session").option("--json", "output as JSON").addHelpText("after", `
|
|
1002
|
+
Examples:
|
|
1003
|
+
$ search-hub merge session-v4 session-v9 # Merge two sessions
|
|
1004
|
+
$ search-hub merge session-v4 session-v9 --name combined # Merge with custom name
|
|
1005
|
+
$ search-hub merge session-a session-b session-c # Merge three sessions
|
|
1006
|
+
$ search-hub merge session-v4 session-v9 --dry-run # Preview merge`).action(
|
|
1007
|
+
async (sessionIds, options) => {
|
|
1008
|
+
const globalOpts = program.opts();
|
|
1009
|
+
try {
|
|
1010
|
+
if (sessionIds.length < 2) {
|
|
1011
|
+
if (!globalOpts.quiet) {
|
|
1012
|
+
console.error("Error: At least two session IDs are required for merge");
|
|
1013
|
+
}
|
|
1014
|
+
process.exitCode = EXIT_CODES.GENERAL_ERROR;
|
|
1015
|
+
return;
|
|
1016
|
+
}
|
|
1017
|
+
const sessionsDir = await getSessionsDir(globalOpts);
|
|
1018
|
+
const sessions = /* @__PURE__ */ new Map();
|
|
1019
|
+
for (const sessionId of sessionIds) {
|
|
1020
|
+
try {
|
|
1021
|
+
const session = await loadSession(sessionId, sessionsDir);
|
|
1022
|
+
sessions.set(sessionId, session);
|
|
1023
|
+
} catch (error) {
|
|
1024
|
+
if (!globalOpts.quiet) {
|
|
1025
|
+
console.error(
|
|
1026
|
+
`Error loading session '${sessionId}': ${error instanceof Error ? error.message : "Failed to load session"}`
|
|
1027
|
+
);
|
|
1028
|
+
}
|
|
1029
|
+
process.exitCode = EXIT_CODES.SESSION_ERROR;
|
|
1030
|
+
return;
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
const validation = validateMergeSources(sessions);
|
|
1034
|
+
if (!validation.valid) {
|
|
1035
|
+
if (!globalOpts.quiet) {
|
|
1036
|
+
console.error(`Error: ${validation.error}`);
|
|
1037
|
+
}
|
|
1038
|
+
process.exitCode = EXIT_CODES.SESSION_ERROR;
|
|
1039
|
+
return;
|
|
1040
|
+
}
|
|
1041
|
+
const sessionArticles = /* @__PURE__ */ new Map();
|
|
1042
|
+
for (const [sessionId, session] of sessions) {
|
|
1043
|
+
const articles = await loadSessionArticles(session, sessionId, sessionsDir);
|
|
1044
|
+
sessionArticles.set(sessionId, articles);
|
|
1045
|
+
}
|
|
1046
|
+
const mergeResult = mergeArticles(sessionArticles);
|
|
1047
|
+
const sources = [...sessions.entries()].map(([id, session]) => ({
|
|
1048
|
+
id,
|
|
1049
|
+
name: session.name,
|
|
1050
|
+
count: mergeResult.perSession.get(id) ?? 0
|
|
1051
|
+
}));
|
|
1052
|
+
const byProviderCounts = /* @__PURE__ */ new Map();
|
|
1053
|
+
for (const [provider, articles] of mergeResult.byProvider) {
|
|
1054
|
+
byProviderCounts.set(provider, articles.length);
|
|
1055
|
+
}
|
|
1056
|
+
const firstSession = sessions.values().next().value;
|
|
1057
|
+
const mergeName = options?.name ?? (firstSession ? firstSession.name + "-merged" : "merged");
|
|
1058
|
+
if (options?.dryRun) {
|
|
1059
|
+
const outputData2 = {
|
|
1060
|
+
sessionId: "(dry-run)",
|
|
1061
|
+
totalBefore: mergeResult.totalBefore,
|
|
1062
|
+
totalAfter: mergeResult.totalAfter,
|
|
1063
|
+
duplicatesRemoved: mergeResult.duplicatesRemoved,
|
|
1064
|
+
sources,
|
|
1065
|
+
byProvider: byProviderCounts
|
|
1066
|
+
};
|
|
1067
|
+
if (options.json) {
|
|
1068
|
+
console.log(formatMergeJson(outputData2));
|
|
1069
|
+
} else if (!globalOpts.quiet) {
|
|
1070
|
+
console.log(formatMergeOutput(outputData2));
|
|
1071
|
+
}
|
|
1072
|
+
process.exitCode = EXIT_CODES.SUCCESS;
|
|
1073
|
+
return;
|
|
1074
|
+
}
|
|
1075
|
+
const sessionSources = [...sessions.entries()].map(([id, session]) => ({
|
|
1076
|
+
id,
|
|
1077
|
+
name: session.name
|
|
1078
|
+
}));
|
|
1079
|
+
const mergedSession = await createMergedSession({
|
|
1080
|
+
name: mergeName,
|
|
1081
|
+
sources: sessionSources,
|
|
1082
|
+
byProvider: mergeResult.byProvider,
|
|
1083
|
+
totalRetrieved: mergeResult.totalAfter,
|
|
1084
|
+
sessionsDir,
|
|
1085
|
+
sourceSessionIds: sessionIds
|
|
1086
|
+
});
|
|
1087
|
+
const outputData = {
|
|
1088
|
+
sessionId: mergedSession.id,
|
|
1089
|
+
totalBefore: mergeResult.totalBefore,
|
|
1090
|
+
totalAfter: mergeResult.totalAfter,
|
|
1091
|
+
duplicatesRemoved: mergeResult.duplicatesRemoved,
|
|
1092
|
+
sources,
|
|
1093
|
+
byProvider: byProviderCounts
|
|
1094
|
+
};
|
|
1095
|
+
if (options?.json) {
|
|
1096
|
+
console.log(formatMergeJson(outputData));
|
|
1097
|
+
} else if (!globalOpts.quiet) {
|
|
1098
|
+
console.log(formatMergeOutput(outputData));
|
|
1099
|
+
const suggestion = getSuggestion({
|
|
1100
|
+
command: "merge",
|
|
1101
|
+
sessionId: mergedSession.id
|
|
1102
|
+
});
|
|
1103
|
+
const suggestionText = formatSuggestion(suggestion);
|
|
1104
|
+
if (suggestionText) {
|
|
1105
|
+
console.log(suggestionText);
|
|
970
1106
|
}
|
|
971
1107
|
}
|
|
972
1108
|
process.exitCode = EXIT_CODES.SUCCESS;
|
|
@@ -1258,7 +1394,7 @@ Examples:
|
|
|
1258
1394
|
process.exitCode = EXIT_CODES.SESSION_ERROR;
|
|
1259
1395
|
}
|
|
1260
1396
|
});
|
|
1261
|
-
reviewCommand.command("extract").description("Extract subset to for-review/<name>/review.yaml for distributed review").requiredOption("--session <id>", "session ID").requiredOption("--name <name>", "name for the review subset (output: for-review/<name>/review.yaml)").option("--filter <types>", "filter by status (comma-separated): pending, incomplete, uncertain, agreed-include, agreed-exclude, conflicting, finalized").option("--sort <method>", "sort method: year, title, random, none", "none").option("--limit <n>", "limit number of articles").option("--offset <n>", "skip first n articles").option("--seed <n>", "random seed for reproducible sorting").option("--basis <type>", "basis for review: title
|
|
1397
|
+
reviewCommand.command("extract").description("Extract subset to for-review/<name>/review.yaml for distributed review").requiredOption("--session <id>", "session ID").requiredOption("--name <name>", "name for the review subset (output: for-review/<name>/review.yaml)").option("--filter <types>", "filter by status (comma-separated): pending, incomplete, uncertain, agreed-include, agreed-exclude, conflicting, finalized").option("--sort <method>", "sort method: year, title, random, none", "none").option("--limit <n>", "limit number of articles").option("--offset <n>", "skip first n articles").option("--seed <n>", "random seed for reproducible sorting").option("--basis <type>", "basis for review: title, abstract, or fulltext").option("--reviewer <id>", 'reviewer identifier (e.g., "ai:claude")').option("--finalize", "extract for final decision (includes reviewHistory and finalDecision)").action(async (options) => {
|
|
1262
1398
|
const globalOpts = program.opts();
|
|
1263
1399
|
try {
|
|
1264
1400
|
const validSorts = ["year", "title", "random", "none"];
|
|
@@ -1307,6 +1443,9 @@ Examples:
|
|
|
1307
1443
|
}
|
|
1308
1444
|
extractOptions.basis = options.basis;
|
|
1309
1445
|
}
|
|
1446
|
+
if (options.finalize) {
|
|
1447
|
+
extractOptions.finalize = true;
|
|
1448
|
+
}
|
|
1310
1449
|
const result = await executeReviewExtract(extractOptions, sessionsDir);
|
|
1311
1450
|
if (!globalOpts.quiet) {
|
|
1312
1451
|
console.log(`Extracted ${result.extractedCount} of ${result.totalMatching} articles to ${result.outputPath}`);
|
|
@@ -1340,7 +1479,7 @@ Examples:
|
|
|
1340
1479
|
};
|
|
1341
1480
|
const result = await executeReviewMerge(mergeOptions, sessionsDir);
|
|
1342
1481
|
if (!globalOpts.quiet) {
|
|
1343
|
-
console.log(formatMergeOutput(result, options.dryRun));
|
|
1482
|
+
console.log(formatMergeOutput$1(result, options.dryRun));
|
|
1344
1483
|
if (!options.dryRun) {
|
|
1345
1484
|
const statusResult = await executeReviewStatus({ sessionId: options.session }, sessionsDir);
|
|
1346
1485
|
const suggestion = formatSuggestion(getSuggestion({
|