@companion-ai/feynman 0.5.28 → 0.5.29
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/RELEASES.md
CHANGED
|
@@ -6,6 +6,12 @@ GitHub release notes are generated from the matching `## vX.Y.Z` section in this
|
|
|
6
6
|
|
|
7
7
|
## Unreleased
|
|
8
8
|
|
|
9
|
+
## v0.5.29 - 2026-10-07
|
|
10
|
+
|
|
11
|
+
- **PubMed titles and abstracts keep their italic terms.** Text inside inline tags such as `<i>BRCA1</i>` was dropped, so a title read "patient with germline mutation" instead of "germline BRCA1 mutation", and gene names went missing from abstracts. Thanks to @KennyMcSimpson (#328).
|
|
12
|
+
- **PubMed authors keep all their affiliations.** An author listed with two or more affiliations came back with none. Thanks to @KennyMcSimpson (#327).
|
|
13
|
+
- Security: GHSA-6qxp-vccf-f47h (MCP SDK OAuth client). Feynman's own copies of `@modelcontextprotocol/sdk` move to 1.32.1. pi-web-access still pins 1.27.1 for its separate `npx pi-web-access` MCP server, which Feynman does not run and which does not use the affected OAuth client; it is updated in nicobailon/pi-web-access#522.
|
|
14
|
+
|
|
9
15
|
## v0.5.28 - 2026-10-07
|
|
10
16
|
|
|
11
17
|
- **Pi 1.0.4, pi-subagents 0.76.1, pi-web-access 0.37.0.**
|
|
@@ -8,6 +8,14 @@ const xmlParser = new XMLParser({
|
|
|
8
8
|
removeNSPrefix: true,
|
|
9
9
|
});
|
|
10
10
|
|
|
11
|
+
// Object-mode parsing groups tags and loses the order of mixed inline text.
|
|
12
|
+
const orderedTextParser = new XMLParser({
|
|
13
|
+
preserveOrder: true,
|
|
14
|
+
removeNSPrefix: true,
|
|
15
|
+
trimValues: false,
|
|
16
|
+
parseTagValue: false,
|
|
17
|
+
});
|
|
18
|
+
|
|
11
19
|
function recordValue(value: unknown): Record<string, unknown> {
|
|
12
20
|
return value && typeof value === "object" && !Array.isArray(value) ? value as Record<string, unknown> : {};
|
|
13
21
|
}
|
|
@@ -49,6 +57,31 @@ function textValue(value: unknown): string | undefined {
|
|
|
49
57
|
return parts.length ? parts.join(" ").replace(/\s+/g, " ").trim() : undefined;
|
|
50
58
|
}
|
|
51
59
|
|
|
60
|
+
function orderedChildren(value: unknown, tag: string): unknown[] {
|
|
61
|
+
return listValue(value).flatMap((node) => {
|
|
62
|
+
const children = recordValue(node)[tag];
|
|
63
|
+
return children === undefined ? [] : [children];
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function orderedText(value: unknown): string {
|
|
68
|
+
if (typeof value === "string" || typeof value === "number") return String(value);
|
|
69
|
+
if (Array.isArray(value)) return value.map((node) => orderedText(node)).join("");
|
|
70
|
+
return Object.entries(recordValue(value))
|
|
71
|
+
.filter(([key]) => key !== ":@" && !key.startsWith("?"))
|
|
72
|
+
.map(([, children]) => orderedText(children))
|
|
73
|
+
.join("");
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function orderedTextValue(value: unknown): string | undefined {
|
|
77
|
+
return orderedText(value).replace(/\s+/g, " ").trim() || undefined;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function orderedPubmedArticles(xml: string): unknown[] {
|
|
81
|
+
const set = orderedChildren(orderedTextParser.parse(xml), "PubmedArticleSet")[0];
|
|
82
|
+
return orderedChildren(set, "PubmedArticle");
|
|
83
|
+
}
|
|
84
|
+
|
|
52
85
|
function dateObject(value: unknown): Record<string, string> | undefined {
|
|
53
86
|
const record = recordValue(value);
|
|
54
87
|
const monthMap: Record<string, string> = {
|
|
@@ -101,7 +134,8 @@ function parseAuthors(value: unknown): Array<Record<string, unknown>> {
|
|
|
101
134
|
const author = recordValue(item);
|
|
102
135
|
const collective = textValue(author.CollectiveName);
|
|
103
136
|
if (collective) return { collectiveName: collective, affiliations: [] };
|
|
104
|
-
const affiliations = listValue(
|
|
137
|
+
const affiliations = listValue(author.AffiliationInfo)
|
|
138
|
+
.flatMap((info) => listValue(recordValue(info).Affiliation))
|
|
105
139
|
.map((affiliation) => textValue(affiliation))
|
|
106
140
|
.filter((affiliation): affiliation is string => Boolean(affiliation));
|
|
107
141
|
return prune({
|
|
@@ -113,15 +147,17 @@ function parseAuthors(value: unknown): Array<Record<string, unknown>> {
|
|
|
113
147
|
});
|
|
114
148
|
}
|
|
115
149
|
|
|
116
|
-
function parsePubmedArticle(value: unknown): Record<string, unknown> {
|
|
150
|
+
function parsePubmedArticle(value: unknown, orderedArticle: unknown): Record<string, unknown> {
|
|
117
151
|
const articleRoot = recordValue(value);
|
|
118
152
|
const citation = recordValue(articleRoot.MedlineCitation);
|
|
119
153
|
const article = recordValue(citation.Article);
|
|
120
154
|
const journal = recordValue(article.Journal);
|
|
121
155
|
const issue = recordValue(journal.JournalIssue);
|
|
122
|
-
const
|
|
123
|
-
const
|
|
124
|
-
|
|
156
|
+
const orderedCitation = orderedChildren(orderedArticle, "MedlineCitation")[0];
|
|
157
|
+
const orderedContent = orderedChildren(orderedCitation, "Article")[0];
|
|
158
|
+
const orderedAbstract = orderedChildren(orderedContent, "Abstract")[0];
|
|
159
|
+
const abstractParts = orderedChildren(orderedAbstract, "AbstractText")
|
|
160
|
+
.map((part) => orderedTextValue(part))
|
|
125
161
|
.filter((part): part is string => Boolean(part));
|
|
126
162
|
const publicationTypes = listValue(recordValue(article.PublicationTypeList).PublicationType)
|
|
127
163
|
.map((item) => textValue(item))
|
|
@@ -140,7 +176,7 @@ function parsePubmedArticle(value: unknown): Record<string, unknown> {
|
|
|
140
176
|
pmid: identifiers.pmid,
|
|
141
177
|
pmcid: identifiers.pmc,
|
|
142
178
|
doi: identifiers.doi,
|
|
143
|
-
title:
|
|
179
|
+
title: orderedTextValue(orderedChildren(orderedContent, "ArticleTitle")[0]),
|
|
144
180
|
abstract: abstractParts.length ? abstractParts.join("\n") : undefined,
|
|
145
181
|
journal: prune({
|
|
146
182
|
title: textValue(journal.Title),
|
|
@@ -160,7 +196,8 @@ function parsePubmedArticle(value: unknown): Record<string, unknown> {
|
|
|
160
196
|
export function parsePubmedArticles(xml: string): Record<string, unknown>[] {
|
|
161
197
|
const parsed = recordValue(xmlParser.parse(xml));
|
|
162
198
|
const set = recordValue(parsed.PubmedArticleSet);
|
|
163
|
-
|
|
199
|
+
const orderedArticles = orderedPubmedArticles(xml);
|
|
200
|
+
return listValue(set.PubmedArticle).map((article, index) => parsePubmedArticle(article, orderedArticles[index]));
|
|
164
201
|
}
|
|
165
202
|
|
|
166
203
|
function jatsArticleId(meta: Record<string, unknown>, type: string): string | undefined {
|
|
@@ -238,8 +275,9 @@ export function copyrightFromPubmedXml(xml: string): Map<string, Record<string,
|
|
|
238
275
|
const map = new Map<string, Record<string, unknown>>();
|
|
239
276
|
const parsed = recordValue(xmlParser.parse(xml));
|
|
240
277
|
const set = recordValue(parsed.PubmedArticleSet);
|
|
241
|
-
|
|
242
|
-
|
|
278
|
+
const orderedArticles = orderedPubmedArticles(xml);
|
|
279
|
+
for (const [index, rawArticle] of listValue(set.PubmedArticle).entries()) {
|
|
280
|
+
const article = parsePubmedArticle(rawArticle, orderedArticles[index]);
|
|
243
281
|
const pmid = stringValue(article.pmid);
|
|
244
282
|
if (!pmid) continue;
|
|
245
283
|
const pubmedArticle = recordValue(rawArticle);
|