research-copilot 0.2.16 → 0.2.17
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "research-copilot",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.17",
|
|
4
4
|
"description": "AI-powered research assistant for scientists — literature search, data analysis, academic writing, and project management",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
"app/build/",
|
|
13
13
|
"app/package.json",
|
|
14
14
|
"lib/skills/",
|
|
15
|
+
"scripts/",
|
|
15
16
|
"README.md",
|
|
16
17
|
"LICENSE"
|
|
17
18
|
],
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Backfill paper artifacts from existing literature-run review.json files.
|
|
3
|
+
// Usage: node scripts/backfill-papers.mjs <projectPath>
|
|
4
|
+
import { readFileSync, writeFileSync, mkdirSync, readdirSync, existsSync } from 'fs'
|
|
5
|
+
import { join } from 'path'
|
|
6
|
+
import { randomUUID } from 'crypto'
|
|
7
|
+
|
|
8
|
+
const projectPath = process.argv[2]
|
|
9
|
+
if (!projectPath) {
|
|
10
|
+
console.error('Usage: node scripts/backfill-papers.mjs <projectPath>')
|
|
11
|
+
process.exit(1)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const litRunsDir = join(projectPath, '.research-pilot/literature-runs')
|
|
15
|
+
const papersDir = join(projectPath, '.research-pilot/artifacts/papers')
|
|
16
|
+
mkdirSync(papersDir, { recursive: true })
|
|
17
|
+
|
|
18
|
+
// Load existing papers for dedup
|
|
19
|
+
const existingPapers = []
|
|
20
|
+
if (existsSync(papersDir)) {
|
|
21
|
+
for (const f of readdirSync(papersDir)) {
|
|
22
|
+
if (!f.endsWith('.json')) continue
|
|
23
|
+
try {
|
|
24
|
+
const p = JSON.parse(readFileSync(join(papersDir, f), 'utf-8'))
|
|
25
|
+
existingPapers.push(p)
|
|
26
|
+
} catch {}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function normalizeTitle(t) {
|
|
31
|
+
return t.toLowerCase().replace(/[^\w\s]/g, ' ').replace(/\s+/g, ' ').trim()
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function isDuplicate(title, year) {
|
|
35
|
+
const nt = normalizeTitle(title)
|
|
36
|
+
return existingPapers.some(p => {
|
|
37
|
+
const pt = normalizeTitle(p.title)
|
|
38
|
+
if (pt !== nt) return false
|
|
39
|
+
if (!year || !p.year) return true
|
|
40
|
+
return p.year === year
|
|
41
|
+
})
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function generateCiteKey(authors, year) {
|
|
45
|
+
const firstAuthor = (authors?.[0] ?? 'unknown').split(/\s+/).pop()?.toLowerCase() ?? 'unknown'
|
|
46
|
+
return `${firstAuthor}${year ?? 'nd'}`
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const AUTO_SAVE_THRESHOLD = 7
|
|
50
|
+
let saved = 0
|
|
51
|
+
let skippedDup = 0
|
|
52
|
+
let skippedScore = 0
|
|
53
|
+
|
|
54
|
+
if (!existsSync(litRunsDir)) {
|
|
55
|
+
console.error(`No literature-runs dir at ${litRunsDir}`)
|
|
56
|
+
process.exit(1)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const runs = readdirSync(litRunsDir)
|
|
60
|
+
for (const runId of runs) {
|
|
61
|
+
const reviewPath = join(litRunsDir, runId, 'review.json')
|
|
62
|
+
if (!existsSync(reviewPath)) continue
|
|
63
|
+
|
|
64
|
+
let data
|
|
65
|
+
try {
|
|
66
|
+
data = JSON.parse(readFileSync(reviewPath, 'utf-8'))
|
|
67
|
+
} catch { continue }
|
|
68
|
+
|
|
69
|
+
const papers = data.review?.relevantPapers ?? []
|
|
70
|
+
const subTopics = data.plan?.subTopics ?? []
|
|
71
|
+
const roundLabel = `R-${runId}`
|
|
72
|
+
|
|
73
|
+
for (const paper of papers) {
|
|
74
|
+
if ((paper.relevanceScore ?? 0) < AUTO_SAVE_THRESHOLD) {
|
|
75
|
+
skippedScore++
|
|
76
|
+
continue
|
|
77
|
+
}
|
|
78
|
+
if (isDuplicate(paper.title, paper.year)) {
|
|
79
|
+
skippedDup++
|
|
80
|
+
continue
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const authors = paper.authors?.length > 0 ? paper.authors : ['Unknown']
|
|
84
|
+
const citeKey = generateCiteKey(authors, paper.year)
|
|
85
|
+
const doi = (paper.doi ?? '').trim() || `unknown:${citeKey}`
|
|
86
|
+
const now = new Date().toISOString()
|
|
87
|
+
const id = randomUUID()
|
|
88
|
+
|
|
89
|
+
// Match subtopic
|
|
90
|
+
const matchedSubTopic = subTopics.find(st =>
|
|
91
|
+
paper.relevanceJustification?.toLowerCase().includes(st.name.toLowerCase())
|
|
92
|
+
)?.name
|
|
93
|
+
|
|
94
|
+
const artifact = {
|
|
95
|
+
id,
|
|
96
|
+
type: 'paper',
|
|
97
|
+
title: paper.title,
|
|
98
|
+
authors,
|
|
99
|
+
abstract: paper.abstract ?? '',
|
|
100
|
+
citeKey,
|
|
101
|
+
doi,
|
|
102
|
+
bibtex: `@article{${citeKey},\n title = {${paper.title}},\n author = {${authors.join(' and ')}},${paper.year ? `\n year = {${paper.year}},` : ''}${paper.venue ? `\n journal = {${paper.venue}},` : ''}${doi ? `\n doi = {${doi}},` : ''}${paper.url ? `\n url = {${paper.url}},` : ''}\n}`,
|
|
103
|
+
year: paper.year ?? undefined,
|
|
104
|
+
venue: paper.venue ?? undefined,
|
|
105
|
+
url: paper.url ?? undefined,
|
|
106
|
+
tags: [],
|
|
107
|
+
provenance: {
|
|
108
|
+
source: 'agent',
|
|
109
|
+
sessionId: 'backfill',
|
|
110
|
+
agentId: 'literature-team',
|
|
111
|
+
extractedFrom: 'agent-response'
|
|
112
|
+
},
|
|
113
|
+
createdAt: now,
|
|
114
|
+
updatedAt: now,
|
|
115
|
+
externalSource: paper.source,
|
|
116
|
+
relevanceScore: paper.relevanceScore,
|
|
117
|
+
citationCount: paper.citationCount ?? undefined,
|
|
118
|
+
relevanceJustification: paper.relevanceJustification,
|
|
119
|
+
subTopic: matchedSubTopic,
|
|
120
|
+
addedInRound: roundLabel,
|
|
121
|
+
addedByTask: 'deep_literature_study',
|
|
122
|
+
identityConfidence: paper.doi ? 'high' : 'medium',
|
|
123
|
+
semanticScholarId: paper.source === 'semantic_scholar' ? paper.id : undefined,
|
|
124
|
+
arxivId: paper.source === 'arxiv' ? paper.id : undefined,
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Remove undefined keys for clean JSON
|
|
128
|
+
for (const [k, v] of Object.entries(artifact)) {
|
|
129
|
+
if (v === undefined) delete artifact[k]
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const filePath = join(papersDir, `${id}.json`)
|
|
133
|
+
writeFileSync(filePath, JSON.stringify(artifact, null, 2), 'utf-8')
|
|
134
|
+
|
|
135
|
+
// Track for dedup within this run
|
|
136
|
+
existingPapers.push(artifact)
|
|
137
|
+
saved++
|
|
138
|
+
console.log(` + ${paper.title.slice(0, 80)} (score: ${paper.relevanceScore})`)
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
console.log(`\nDone: ${saved} papers saved, ${skippedDup} duplicates skipped, ${skippedScore} below threshold (< ${AUTO_SAVE_THRESHOLD})`)
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { chmodSync, readdirSync, statSync } from 'node:fs'
|
|
2
|
+
import { createRequire } from 'node:module'
|
|
3
|
+
import path from 'node:path'
|
|
4
|
+
|
|
5
|
+
// spawn-helper is only used on Unix platforms; Windows uses conpty.
|
|
6
|
+
if (process.platform === 'win32') process.exit(0)
|
|
7
|
+
|
|
8
|
+
const require = createRequire(import.meta.url)
|
|
9
|
+
|
|
10
|
+
try {
|
|
11
|
+
// Resolve from package.json so we rely only on node-pty's public layout,
|
|
12
|
+
// not internal modules or runtime-loaded native bindings.
|
|
13
|
+
const ptyDir = path.dirname(require.resolve('node-pty/package.json'))
|
|
14
|
+
const prebuildsDir = path.join(ptyDir, 'prebuilds')
|
|
15
|
+
|
|
16
|
+
for (const entry of readdirSync(prebuildsDir, { withFileTypes: true })) {
|
|
17
|
+
if (!entry.isDirectory()) continue
|
|
18
|
+
const helper = path.join(prebuildsDir, entry.name, 'spawn-helper')
|
|
19
|
+
try {
|
|
20
|
+
const stat = statSync(helper)
|
|
21
|
+
if ((stat.mode & 0o111) !== 0o111) {
|
|
22
|
+
chmodSync(helper, stat.mode | 0o755)
|
|
23
|
+
console.log(`[postinstall] fixed node-pty helper permissions: ${helper}`)
|
|
24
|
+
}
|
|
25
|
+
} catch {
|
|
26
|
+
// Arch dirs without spawn-helper (e.g., win32-*) — skip silently.
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
} catch (error) {
|
|
30
|
+
const message = error instanceof Error ? error.message : String(error)
|
|
31
|
+
console.warn(`[postinstall] skipped node-pty helper permission fix: ${message}`)
|
|
32
|
+
}
|