@shuji-bonji/rfcxml-mcp 0.6.13 → 0.6.53
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/README.ja.md +178 -50
- package/README.md +181 -51
- package/dist/cli/prefetch.d.ts +6 -1
- package/dist/cli/prefetch.d.ts.map +1 -1
- package/dist/cli/prefetch.js +53 -23
- package/dist/cli/prefetch.js.map +1 -1
- package/dist/config.d.ts +0 -2
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +3 -2
- package/dist/config.js.map +1 -1
- package/dist/constants.d.ts +16 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +80 -7
- package/dist/constants.js.map +1 -1
- package/dist/services/checklist-generator.d.ts.map +1 -1
- package/dist/services/checklist-generator.js +72 -7
- package/dist/services/checklist-generator.js.map +1 -1
- package/dist/services/rfc-fetcher.d.ts +19 -1
- package/dist/services/rfc-fetcher.d.ts.map +1 -1
- package/dist/services/rfc-fetcher.js +116 -19
- package/dist/services/rfc-fetcher.js.map +1 -1
- package/dist/services/rfc-service.d.ts +11 -1
- package/dist/services/rfc-service.d.ts.map +1 -1
- package/dist/services/rfc-service.js +45 -5
- package/dist/services/rfc-service.js.map +1 -1
- package/dist/services/rfc-text-parser.d.ts.map +1 -1
- package/dist/services/rfc-text-parser.js +1594 -88
- package/dist/services/rfc-text-parser.js.map +1 -1
- package/dist/services/rfcxml-parser.d.ts.map +1 -1
- package/dist/services/rfcxml-parser.js +366 -98
- package/dist/services/rfcxml-parser.js.map +1 -1
- package/dist/tools/definitions.d.ts +8 -0
- package/dist/tools/definitions.d.ts.map +1 -1
- package/dist/tools/definitions.js +17 -1
- package/dist/tools/definitions.js.map +1 -1
- package/dist/tools/handlers.d.ts +24 -12
- package/dist/tools/handlers.d.ts.map +1 -1
- package/dist/tools/handlers.js +161 -36
- package/dist/tools/handlers.js.map +1 -1
- package/dist/types/index.d.ts +22 -2
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utils/cache.d.ts +19 -0
- package/dist/utils/cache.d.ts.map +1 -1
- package/dist/utils/cache.js +32 -0
- package/dist/utils/cache.js.map +1 -1
- package/dist/utils/disk-cache.d.ts +10 -6
- package/dist/utils/disk-cache.d.ts.map +1 -1
- package/dist/utils/disk-cache.js +13 -9
- package/dist/utils/disk-cache.js.map +1 -1
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/logger.js +3 -1
- package/dist/utils/logger.js.map +1 -1
- package/dist/utils/requirement-extractor.d.ts.map +1 -1
- package/dist/utils/requirement-extractor.js +413 -21
- package/dist/utils/requirement-extractor.js.map +1 -1
- package/dist/utils/section.d.ts.map +1 -1
- package/dist/utils/section.js +11 -0
- package/dist/utils/section.js.map +1 -1
- package/dist/utils/statement-matcher.d.ts +69 -4
- package/dist/utils/statement-matcher.d.ts.map +1 -1
- package/dist/utils/statement-matcher.js +524 -48
- package/dist/utils/statement-matcher.js.map +1 -1
- package/dist/utils/text.d.ts +62 -0
- package/dist/utils/text.d.ts.map +1 -1
- package/dist/utils/text.js +273 -18
- package/dist/utils/text.js.map +1 -1
- package/package.json +5 -2
package/dist/constants.js
CHANGED
|
@@ -47,7 +47,10 @@ export const REQUIREMENT_KEYWORDS = [
|
|
|
47
47
|
* `extractRequirementsFromSections` の重複排除で解決している。
|
|
48
48
|
*/
|
|
49
49
|
export function createRequirementRegex() {
|
|
50
|
-
|
|
50
|
+
// 2 語のキーワードは 72 桁の折り返しで改行をまたぐ。RFC 3261 の
|
|
51
|
+
// `"NOT\nRECOMMENDED"` を `RECOMMENDED` 単独で拾うと、引用符の判定が外れる。
|
|
52
|
+
const alternation = REQUIREMENT_KEYWORDS.map((keyword) => keyword.replace(/ /g, '\\s+')).join('|');
|
|
53
|
+
return new RegExp(`\\b(${alternation})\\b`, 'g');
|
|
51
54
|
}
|
|
52
55
|
/**
|
|
53
56
|
* Keyword regex (matches longest first)
|
|
@@ -83,7 +86,35 @@ export function createRFCReferenceRegex() {
|
|
|
83
86
|
* Section reference pattern (e.g., "Section 1.2", "section 3.4.5")
|
|
84
87
|
*/
|
|
85
88
|
export function createSectionReferenceRegex() {
|
|
86
|
-
|
|
89
|
+
// 最上位の番号は 2 桁まで。RFC 4949 は米国法を
|
|
90
|
+
// `Section 111(d)` `Section 552 (FOIA)` `Title 40 U.S.C. Section 1552` と
|
|
91
|
+
// 引くので、3 桁以上の番号を節として拾っていた(実測: RFC 127 本で 5 件、
|
|
92
|
+
// 3 桁以上の節を持つ RFC は 1 本も無い)。
|
|
93
|
+
// 後ろに数字が続かないことを求める。求めないと `111` の頭 2 桁を採る。
|
|
94
|
+
//
|
|
95
|
+
// 複数形も見る。`Sections 4.1 and 4.2` `Sections 4.1, 4.2, and 4.3` は
|
|
96
|
+
// 列挙であり、`[Ss]ection\s+` だけでは 1 件も拾えなかった(RFC 9110 の
|
|
97
|
+
// .txt で 4 か所)。2 つ目以降は `SECTION_LIST_TAIL` で取る。
|
|
98
|
+
return new RegExp(`[Ss]ections?\\s+(\\d{1,2}(?:\\.\\d+)*)(?!\\d)(${SECTION_LIST_TAIL})`, 'g');
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* 節番号の列挙の 2 つ目以降。`, 4.2` `, and 4.3` ` and 4.2` ` or 4.2`。
|
|
102
|
+
*
|
|
103
|
+
* 番号のあとに括弧の補足が入ることがある。RFC 7519 §11 は
|
|
104
|
+
* `Sections 10.12 ("JSON Security Considerations") and 10.13 ("Unicode
|
|
105
|
+
* Comparison Security Considerations") of [JWS]` と書く。補足を認めないと
|
|
106
|
+
* 10.12 だけがこの RFC の節として残る(RFC 7519 に §10.12 は無い)。
|
|
107
|
+
*
|
|
108
|
+
* 読点だけで続く番号は、`Section 3, 2 octets long` のように文の途中で読点が
|
|
109
|
+
* 来るものと区別がつかない。そのため読点の形では、番号のあとに列挙の続き
|
|
110
|
+
* (読点・`and`・`or`)か文の切れ目(句点・セミコロン・閉じ括弧・末尾)が
|
|
111
|
+
* 来ることを求める。`and` / `or` で続く形にはこれを課さない。
|
|
112
|
+
*/
|
|
113
|
+
export const SECTION_LIST_TAIL = '(?:(?:\\s*\\([^()]*\\))?(?:\\s+(?:and|or)\\s+|,\\s*(?:and|or)\\s+)\\d{1,2}(?:\\.\\d+)*(?!\\d)' +
|
|
114
|
+
'|,\\s*\\d{1,2}(?:\\.\\d+)*(?!\\d)(?=\\s*(?:[,.;:)]|$|and\\b|or\\b)))*';
|
|
115
|
+
/** 列挙の中の節番号を全部取り出す。 */
|
|
116
|
+
export function splitSectionList(list) {
|
|
117
|
+
return list.match(/\d+(?:\.\d+)*/g) ?? [];
|
|
87
118
|
}
|
|
88
119
|
/**
|
|
89
120
|
* 別文書の節を指す言い回し。
|
|
@@ -100,8 +131,21 @@ export function createSectionReferenceRegex() {
|
|
|
100
131
|
*/
|
|
101
132
|
export function createExternalSectionRegexes() {
|
|
102
133
|
return [
|
|
134
|
+
// 文書名を二重引用符で書く形。1980〜90 年代の RFC は角括弧の目印を使わず、
|
|
135
|
+
// 題名をそのまま引く。RFC 1191 §3.1 は
|
|
136
|
+
// `Section 4.2.2.6 of "Requirements for Internet Hosts -- Communication
|
|
137
|
+
// Layers"`
|
|
138
|
+
// と書く。RFC 1191 に §4.2.2.6 は無い。
|
|
139
|
+
{
|
|
140
|
+
pattern: new RegExp(String.raw `[Ss]ections?\s+(\d+(?:\.\d+)*${SECTION_LIST_TAIL})\.?\s+of\s+"([^"]{4,120})"`, 'g'),
|
|
141
|
+
sectionGroup: 1,
|
|
142
|
+
documentGroup: 2,
|
|
143
|
+
},
|
|
144
|
+
// 番号のあとに句点が入ることがある。RFC 8628 §3.1 は
|
|
145
|
+
// `as described in Section 3.2.1. of [RFC6749].` と書く。句点を認めて
|
|
146
|
+
// いなかったため、RFC 8628 に無い §3.2.1 をこの RFC の節として返していた。
|
|
103
147
|
{
|
|
104
|
-
pattern:
|
|
148
|
+
pattern: new RegExp(String.raw `[Ss]ections?\s+(\d+(?:\.\d+)*${SECTION_LIST_TAIL})\.?\s+of\s+\[([^\]]+)\]`, 'g'),
|
|
105
149
|
sectionGroup: 1,
|
|
106
150
|
documentGroup: 2,
|
|
107
151
|
},
|
|
@@ -109,25 +153,54 @@ export function createExternalSectionRegexes() {
|
|
|
109
153
|
// "GET_MAXSIZES in Section 3.4 of RFC 1122."
|
|
110
154
|
// "as explained in RFC 6691, Section 3.1."
|
|
111
155
|
{
|
|
112
|
-
pattern:
|
|
156
|
+
pattern: new RegExp(String.raw `[Ss]ections?\s+(\d+(?:\.\d+)*${SECTION_LIST_TAIL})\.?\s+of\s+(RFC\s*\d+)`, 'gi'),
|
|
113
157
|
sectionGroup: 1,
|
|
114
158
|
documentGroup: 2,
|
|
115
159
|
},
|
|
116
160
|
{
|
|
117
|
-
pattern:
|
|
161
|
+
pattern: new RegExp(String.raw `(RFC\s*\d+),?\s+[Ss]ections?\s+(\d+(?:\.\d+)*${SECTION_LIST_TAIL})(?=[\s,.;)]|$)`, 'gi'),
|
|
118
162
|
sectionGroup: 2,
|
|
119
163
|
documentGroup: 1,
|
|
120
164
|
},
|
|
165
|
+
// 読点は無くてもよい。RFC 6749 は "([RFC3986] Section 3.4)" と書く。
|
|
166
|
+
// 読点を必須にしていたため、この形が「この RFC の §3.4」として扱われ、
|
|
167
|
+
// `get_related_sections` が実在しない節を返していた(RFC 6749 に §3.4 は無い)。
|
|
168
|
+
//
|
|
169
|
+
// 番号のあとにコロンが来ることがある。RFC 6376 §3.2 は
|
|
170
|
+
// `Quoted-Printable [RFC2045], Section 6.7: any character MAY be encoded`
|
|
171
|
+
// と書く。コロンを認めていなかったため、RFC 2045 の §6.7 が
|
|
172
|
+
// 「この RFC の §6.7」になっていた(RFC 6376 に §6.7 は無い)。
|
|
121
173
|
{
|
|
122
|
-
pattern:
|
|
174
|
+
pattern: new RegExp(String.raw `\[([^\]]+)\],?\s+[Ss]ections?\s+(\d+(?:\.\d+)*${SECTION_LIST_TAIL})(?=[\s,.;:)]|$)`, 'g'),
|
|
123
175
|
sectionGroup: 2,
|
|
124
176
|
documentGroup: 1,
|
|
125
177
|
},
|
|
178
|
+
// 前置詞が `in` の形。RFC 5751 §2.5 は
|
|
179
|
+
// `- Message Digest (section (Section 11.2 in [CMS])`
|
|
180
|
+
// と書く。`of` だけを見ていたため、RFC 5751 に無い §11.2 を返していた。
|
|
126
181
|
{
|
|
127
|
-
pattern:
|
|
182
|
+
pattern: new RegExp(String.raw `[Ss]ections?\s+(\d+(?:\.\d+)*${SECTION_LIST_TAIL})\.?\s+in\s+\[([^\]]+)\]`, 'g'),
|
|
183
|
+
sectionGroup: 1,
|
|
184
|
+
documentGroup: 2,
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
pattern: new RegExp(String.raw `\[([^\]]+)\]\s+\([Ss]ections?\s+(\d+(?:\.\d+)*${SECTION_LIST_TAIL})\)`, 'g'),
|
|
128
188
|
sectionGroup: 2,
|
|
129
189
|
documentGroup: 1,
|
|
130
190
|
},
|
|
191
|
+
// 番号と `of` の間に補足が入り、文書名が角括弧の手前に置かれる形。
|
|
192
|
+
// "as specified in Section 15.12 ("The JSON Object") of ECMAScript 5.1
|
|
193
|
+
// [ECMAScript]"(RFC 7519 §4)
|
|
194
|
+
// 番号だけを見ると、この RFC の §15.12 を指していると読める。RFC 7519 に
|
|
195
|
+
// §15.12 は無く、`get_related_sections` が実在しない節を返していた。
|
|
196
|
+
//
|
|
197
|
+
// `of` のあとに置けるのは、版番号を含む語 3 つまでとする。句点で終わる語は
|
|
198
|
+
// 認めない("of this specification. See [RFC1234]" を拾わないため)。
|
|
199
|
+
{
|
|
200
|
+
pattern: new RegExp(String.raw `[Ss]ections?\s+(\d+(?:\.\d+)*${SECTION_LIST_TAIL})\s*(?:\([^)]*\)\s*)?of\s+(?:[A-Za-z0-9][\w/-]*(?:\.\d+)*\s+){0,3}\[([^\]]+)\]`, 'g'),
|
|
201
|
+
sectionGroup: 1,
|
|
202
|
+
documentGroup: 2,
|
|
203
|
+
},
|
|
131
204
|
];
|
|
132
205
|
}
|
|
133
206
|
// RFC-related configuration is centralized in config.ts
|
package/dist/constants.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,2CAA2C;AAC3C,oBAAoB;AACpB,2CAA2C;AAE3C;;;GAGG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG;IAC/B,+BAA+B;IAC/B,GAAG,EAAE,CAAC;IACN,wDAAwD;IACxD,GAAG,EAAE,KAAK;CACF,CAAC;AAEX,2CAA2C;AAC3C,6BAA6B;AAC7B,2CAA2C;AAE3C;;;GAGG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAuB;IACtD,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,OAAO;IACP,YAAY;IACZ,QAAQ;IACR,aAAa;IACb,iBAAiB;IACjB,KAAK;IACL,UAAU;CACX,CAAC;AAEF;;;;;;;;;;GAUG;AACH,MAAM,UAAU,sBAAsB;IACpC,
|
|
1
|
+
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"checklist-generator.d.ts","sourceRoot":"","sources":["../../src/services/checklist-generator.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,uBAAuB,EAAiB,MAAM,mBAAmB,CAAC;AAE7F;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,WAAW,EAAE,CAAC;IACpB,MAAM,EAAE,WAAW,EAAE,CAAC;IACtB,GAAG,EAAE,WAAW,EAAE,CAAC;CACpB;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,YAAY,EAAE,WAAW,EAAE,GAAG,sBAAsB,CAUxF;AAED;;GAEG;AACH,wBAAgB,YAAY,CAC1B,YAAY,EAAE,WAAW,EAAE,EAC3B,IAAI,CAAC,EAAE,QAAQ,GAAG,QAAQ,GAAG,MAAM,GAClC,WAAW,EAAE,
|
|
1
|
+
{"version":3,"file":"checklist-generator.d.ts","sourceRoot":"","sources":["../../src/services/checklist-generator.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,uBAAuB,EAAiB,MAAM,mBAAmB,CAAC;AAE7F;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,WAAW,EAAE,CAAC;IACpB,MAAM,EAAE,WAAW,EAAE,CAAC;IACtB,GAAG,EAAE,WAAW,EAAE,CAAC;CACpB;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,YAAY,EAAE,WAAW,EAAE,GAAG,sBAAsB,CAUxF;AAED;;GAEG;AACH,wBAAgB,YAAY,CAC1B,YAAY,EAAE,WAAW,EAAE,EAC3B,IAAI,CAAC,EAAE,QAAQ,GAAG,QAAQ,GAAG,MAAM,GAClC,WAAW,EAAE,CAuBf;AAaD;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,EACb,YAAY,EAAE,WAAW,EAAE,EAC3B,IAAI,CAAC,EAAE,QAAQ,GAAG,QAAQ,GAAG,MAAM,GAClC,uBAAuB,CAazB;AAiGD;;GAEG;AACH,wBAAgB,yBAAyB,CAAC,SAAS,EAAE,uBAAuB,GAAG,MAAM,CAoCpF;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,uBAAuB,GAAG,cAAc,CAOpF"}
|
|
@@ -19,11 +19,20 @@ export function filterByRole(requirements, role) {
|
|
|
19
19
|
return requirements;
|
|
20
20
|
}
|
|
21
21
|
return requirements.filter((r) => {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
22
|
+
// 主語が取れないときは要件文そのものを見る。主語だけを見ていたため
|
|
23
|
+
// `role: "client"` に「サーバの話だけを書いた要件」が 865 件(8.9%)
|
|
24
|
+
// 残っていた(RFC 64 本・要件 9,684 件)。
|
|
25
|
+
const subject = r.subject?.toLowerCase() ?? '';
|
|
26
|
+
const haystack = subject || (r.text ?? '').toLowerCase();
|
|
27
|
+
// 複数形も見る。`\bserver\b` は "servers" に当たらない。実測(RFC 64 本・
|
|
28
|
+
// 要件 9,684 件)で、複数形だけで役割を書く要件が 449 件(4.6%)あり、
|
|
29
|
+
// どちらの role にも残っていた。
|
|
30
|
+
const mentionsClient = /\bclients?\b|\buser agents?\b/.test(haystack);
|
|
31
|
+
const mentionsServer = /\bservers?\b|\bproxy\b|\bproxies\b|\bgateways?\b/.test(haystack);
|
|
32
|
+
// どちらにも触れないものは、どちらの実装にも関わりうるので残す
|
|
33
|
+
if (!mentionsClient && !mentionsServer)
|
|
34
|
+
return true;
|
|
35
|
+
return role === 'client' ? mentionsClient : mentionsServer;
|
|
27
36
|
});
|
|
28
37
|
}
|
|
29
38
|
/**
|
|
@@ -62,9 +71,65 @@ export function generateChecklist(rfcNumber, title, requirements, role) {
|
|
|
62
71
|
* ついての項目なのか読み取れなかった。
|
|
63
72
|
*/
|
|
64
73
|
function renderChecklistItem(item) {
|
|
65
|
-
const { level,
|
|
66
|
-
return `- [ ] **${level}** ${
|
|
74
|
+
const { level, section } = item.requirement;
|
|
75
|
+
return `- [ ] **${level}** ${withAntecedent(item.requirement)} (§${section})`;
|
|
67
76
|
}
|
|
77
|
+
/**
|
|
78
|
+
* 指示語で始まる要件文に、直前の文を足す。
|
|
79
|
+
*
|
|
80
|
+
* チェックリストはレベルごとに並べ替えるので、原文で隣にあった文が離れる。
|
|
81
|
+
* RFC 6455 §4.1 の
|
|
82
|
+
*
|
|
83
|
+
* > The request MAY include a header field with the name |Sec-WebSocket-Protocol|.
|
|
84
|
+
* > **The elements that comprise this value MUST be non-empty strings** …
|
|
85
|
+
*
|
|
86
|
+
* は、前の文が MAY・この文が MUST なので、チェックリストでは別の節に分かれる。
|
|
87
|
+
* **「この値」が何を指すのか読めなくなる。** RFC 9110 §7.1 の
|
|
88
|
+
* `These forms MUST NOT be used with other methods.` も同じで、
|
|
89
|
+
* 「これらの形式」が何かは前の文にある。
|
|
90
|
+
*
|
|
91
|
+
* 実測(RFC 67 本・要件 9,870 件): 指示語で始まる要件は 57 件。
|
|
92
|
+
*
|
|
93
|
+
* 要件文そのもの(`text`)は書き換えない。RFC が書いた通りの文であり、
|
|
94
|
+
* 前の文を足すのは読み手のための編集だからである。ここは表示だけを直す。
|
|
95
|
+
*/
|
|
96
|
+
function withAntecedent(requirement) {
|
|
97
|
+
const text = requirement.text.trim();
|
|
98
|
+
if (!DEICTIC_OPENER.test(text))
|
|
99
|
+
return text;
|
|
100
|
+
const context = requirement.fullContext ?? '';
|
|
101
|
+
const at = context.indexOf(text);
|
|
102
|
+
if (at <= 0)
|
|
103
|
+
return text;
|
|
104
|
+
const before = context.slice(0, at).trim();
|
|
105
|
+
const previous = lastSentence(before);
|
|
106
|
+
if (!previous || previous.length > ANTECEDENT_MAX_LENGTH)
|
|
107
|
+
return text;
|
|
108
|
+
// 案内の文を足しても指すものは分からない。RFC 9110 §7.1 の
|
|
109
|
+
// `These forms MUST NOT be used with other methods.` の直前は
|
|
110
|
+
// "See the respective method definitions for details." で、指すものは
|
|
111
|
+
// さらに前の段落にある。足さずに、そのまま出す。
|
|
112
|
+
if (POINTER_SENTENCE.test(previous))
|
|
113
|
+
return text;
|
|
114
|
+
return `${previous} ${text}`;
|
|
115
|
+
}
|
|
116
|
+
/** 直前の 1 文を返す。取れなければ空文字。 */
|
|
117
|
+
function lastSentence(text) {
|
|
118
|
+
const parts = text.split(/(?<=[.!?])\s+/).filter((part) => part.trim().length > 0);
|
|
119
|
+
return parts.length > 0 ? parts[parts.length - 1].trim() : '';
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* 指示語・接続表現で始まる書き出し。これで始まる文は、単独では何を指すか読めない。
|
|
123
|
+
*
|
|
124
|
+
* `Otherwise, the recipient SHOULD process the Range header field as requested.`
|
|
125
|
+
* は、何でなければそうするのかが前の文にある(RFC 9110 §13.1.5)。
|
|
126
|
+
* 実測(RFC 67 本): 指示語 50 件、接続表現 76 件。
|
|
127
|
+
*/
|
|
128
|
+
const DEICTIC_OPENER = /^(?:The (?:value|elements|contents|format|length|meaning|syntax|name|type|order|result)s?\s+(?:of\s+)?(?:that|this|these|those|it|its)\b|This\s+(?:value|field|header|parameter|option|attribute|element|version|rule|requirement)\b|These\s+(?:values|fields|elements|forms|rules)\b|Such\b|To do so\b|In (?:this|that|such) cases?\b|In this case\b|Otherwise\b|To that end\b|If so\b)/i;
|
|
129
|
+
/** 案内だけの文。足しても指すものが分からない。 */
|
|
130
|
+
const POINTER_SENTENCE = /^(?:See|Refer|For (?:more|further|details|the)|Note that)\b/i;
|
|
131
|
+
/** 足す前の文の長さの上限。長い文を足すと、項目が読めなくなる。 */
|
|
132
|
+
const ANTECEDENT_MAX_LENGTH = 200;
|
|
68
133
|
/**
|
|
69
134
|
* 同じ行が 2 度出ないようにする。
|
|
70
135
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"checklist-generator.js","sourceRoot":"","sources":["../../src/services/checklist-generator.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAaH;;GAEG;AACH,MAAM,UAAU,oBAAoB,CAAC,YAA2B;IAC9D,MAAM,IAAI,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CACrC,CAAC,MAAM,EAAE,UAAU,EAAE,UAAU,EAAE,OAAO,EAAE,WAAW,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CACzE,CAAC;IACF,MAAM,MAAM,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CACvC,CAAC,QAAQ,EAAE,YAAY,EAAE,aAAa,EAAE,iBAAiB,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAC7E,CAAC;IACF,MAAM,GAAG,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC;IAE9E,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC;AAC/B,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,YAAY,CAC1B,YAA2B,EAC3B,IAAmC;IAEnC,IAAI,CAAC,IAAI,IAAI,IAAI,KAAK,MAAM,EAAE,CAAC;QAC7B,OAAO,YAAY,CAAC;IACtB,CAAC;IAED,OAAO,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE;QAC/B,MAAM,OAAO,GAAG,CAAC,CAAC,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC;QAC/C,IAAI,IAAI,
|
|
1
|
+
{"version":3,"file":"checklist-generator.js","sourceRoot":"","sources":["../../src/services/checklist-generator.ts"],"names":[],"mappings":"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"}
|
|
@@ -50,11 +50,22 @@ export interface FetchRFCMetadataOptions {
|
|
|
50
50
|
* documentauthor list, N for each person), but those are cached aggressively.
|
|
51
51
|
*/
|
|
52
52
|
export declare function fetchRFCMetadata(rfcNumber: number, options?: FetchRFCMetadataOptions): Promise<RFCMetadata>;
|
|
53
|
+
/**
|
|
54
|
+
* Options for {@link fetchRFCText}. Same meaning as {@link FetchRFCXMLOptions}.
|
|
55
|
+
*/
|
|
56
|
+
export interface FetchRFCTextOptions {
|
|
57
|
+
/**
|
|
58
|
+
* When true, bypass the in-memory LRU cache and the disk cache and force a
|
|
59
|
+
* fresh network fetch. `rfcxml-prefetch --force` uses this; v0.6.52 まで
|
|
60
|
+
* テキストにはこの選択肢が無く、`--force` でもディスクから返っていた(Issue #17)。
|
|
61
|
+
*/
|
|
62
|
+
forceFresh?: boolean;
|
|
63
|
+
}
|
|
53
64
|
/**
|
|
54
65
|
* Fetch RFC text (parallel fetch)
|
|
55
66
|
* Sends concurrent requests to multiple sources and returns the first successful response
|
|
56
67
|
*/
|
|
57
|
-
export declare function fetchRFCText(rfcNumber: number): Promise<string>;
|
|
68
|
+
export declare function fetchRFCText(rfcNumber: number, options?: FetchRFCTextOptions): Promise<string>;
|
|
58
69
|
/**
|
|
59
70
|
* Doc event entry exposed to handlers.
|
|
60
71
|
*/
|
|
@@ -124,6 +135,13 @@ export declare function isRFCXMLAvailable(rfcNumber: number): boolean;
|
|
|
124
135
|
export declare class RFCXMLNotAvailableError extends Error {
|
|
125
136
|
readonly rfcNumber: number;
|
|
126
137
|
readonly isOldRFC: boolean;
|
|
138
|
+
/**
|
|
139
|
+
* すべての取得元が 404 だったか(Issue #20)。
|
|
140
|
+
* `true` なら「その番号の XML は公開されていない」。`false` なら 5xx・
|
|
141
|
+
* タイムアウト・DNS など一時的な失敗を含むので、`getParsedRFC` は番号を
|
|
142
|
+
* 問わずテキストを試す。
|
|
143
|
+
*/
|
|
144
|
+
readonly notFound: boolean;
|
|
127
145
|
readonly suggestion: string;
|
|
128
146
|
constructor(rfcNumber: number, originalErrors?: string[]);
|
|
129
147
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rfc-fetcher.d.ts","sourceRoot":"","sources":["../../src/services/rfc-fetcher.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"rfc-fetcher.d.ts","sourceRoot":"","sources":["../../src/services/rfc-fetcher.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAyDhF;;;;GAIG;AACH,wBAAgB,wBAAwB,IAAI,IAAI,CAG/C;AASD;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC;;;;OAIG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,WAAW,CAC/B,SAAS,EAAE,MAAM,EACjB,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,MAAM,CAAC,CAsBjB;AAsCD;;;;;GAKG;AACH,MAAM,WAAW,uBAAuB;IACtC,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED;;;;;;;GAOG;AACH,wBAAsB,gBAAgB,CACpC,SAAS,EAAE,MAAM,EACjB,OAAO,GAAE,uBAA4B,GACpC,OAAO,CAAC,WAAW,CAAC,CAYtB;AAqED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC;;;;OAIG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED;;;GAGG;AACH,wBAAsB,YAAY,CAChC,SAAS,EAAE,MAAM,EACjB,OAAO,GAAE,mBAAwB,GAChC,OAAO,CAAC,MAAM,CAAC,CAuBjB;AA0GD;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;CACpB;AA4BD;;;;GAIG;AACH,wBAAsB,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAmCvE;AAED;;;;;;;GAOG;AACH,wBAAsB,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,SAAK,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC,CAkB5F;AAuBD;;;GAGG;AACH,wBAAsB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAiCvF;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,SAAS,GAAG,SAAS,CAAC;CACrC;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC,CAiClF;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAE5D;AAED;;GAEG;AACH,qBAAa,uBAAwB,SAAQ,KAAK;IAChD,SAAgB,SAAS,EAAE,MAAM,CAAC;IAClC,SAAgB,QAAQ,EAAE,OAAO,CAAC;IAClC;;;;;OAKG;IACH,SAAgB,QAAQ,EAAE,OAAO,CAAC;IAClC,SAAgB,UAAU,EAAE,MAAM,CAAC;gBAEvB,SAAS,EAAE,MAAM,EAAE,cAAc,GAAE,MAAM,EAAO;CAsC7D;AAED;;;GAGG;AACH,wBAAgB,UAAU,IAAI,IAAI,CASjC"}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* RFC Fetcher Service
|
|
3
3
|
* RFCXML source fetching and cache management
|
|
4
4
|
*/
|
|
5
|
-
import { LRUCache } from '../utils/cache.js';
|
|
5
|
+
import { InFlightMap, LRUCache } from '../utils/cache.js';
|
|
6
6
|
import { DiskCache } from '../utils/disk-cache.js';
|
|
7
7
|
import { fetchFromMultipleSources } from '../utils/fetch.js';
|
|
8
8
|
import { logger } from '../utils/logger.js';
|
|
@@ -11,6 +11,11 @@ import { CACHE_CONFIG, RFC_XML_SOURCES, RFC_TEXT_SOURCES, DATATRACKER_API, HTTP_
|
|
|
11
11
|
const xmlCache = new LRUCache(CACHE_CONFIG.xml);
|
|
12
12
|
const textCache = new LRUCache(CACHE_CONFIG.text);
|
|
13
13
|
const metadataCache = new LRUCache(CACHE_CONFIG.metadata);
|
|
14
|
+
// 同じ RFC への同時呼び出しを 1 本にまとめる(Issue #15)。結果が入るまでの
|
|
15
|
+
// あいだ LRU は空なので、これが無いと呼び出しの数だけ取りに行く。
|
|
16
|
+
const xmlInFlight = new InFlightMap();
|
|
17
|
+
const textInFlight = new InFlightMap();
|
|
18
|
+
const metadataInFlight = new InFlightMap();
|
|
14
19
|
/**
|
|
15
20
|
* Disk-backed XML cache (Phase 3). Lazily initialized so tests / the prefetch
|
|
16
21
|
* CLI can mutate `RFCXML_CACHE_DIR` before any fetch happens. `null` when the
|
|
@@ -20,12 +25,26 @@ const metadataCache = new LRUCache(CACHE_CONFIG.metadata);
|
|
|
20
25
|
* tests when changing the env var mid-run.
|
|
21
26
|
*/
|
|
22
27
|
let diskCacheInstance;
|
|
28
|
+
let textDiskCacheInstance;
|
|
23
29
|
function getDiskCache() {
|
|
24
30
|
if (diskCacheInstance === undefined) {
|
|
25
31
|
diskCacheInstance = DiskCache.fromEnv();
|
|
26
32
|
}
|
|
27
33
|
return diskCacheInstance;
|
|
28
34
|
}
|
|
35
|
+
/**
|
|
36
|
+
* テキストのディスクキャッシュ。
|
|
37
|
+
*
|
|
38
|
+
* RFCXML が無い RFC(RFC 8650 より前のほとんど)はテキストで読む。v0.6.14 まで
|
|
39
|
+
* テキストはメモリの LRU にしか入らず、MCP サーバを再起動するたびに取り直して
|
|
40
|
+
* いた。RFC 1122 の本文は 200 KB あり、`rfcxml-prefetch` を通しても効かなかった。
|
|
41
|
+
*/
|
|
42
|
+
function getTextDiskCache() {
|
|
43
|
+
if (textDiskCacheInstance === undefined) {
|
|
44
|
+
textDiskCacheInstance = DiskCache.fromEnv('text');
|
|
45
|
+
}
|
|
46
|
+
return textDiskCacheInstance;
|
|
47
|
+
}
|
|
29
48
|
/**
|
|
30
49
|
* Reset the lazily-initialized disk cache. Tests use this to re-read the
|
|
31
50
|
* `RFCXML_CACHE_DIR` env var after mutating it. Not exposed in the MCP tool
|
|
@@ -33,6 +52,7 @@ function getDiskCache() {
|
|
|
33
52
|
*/
|
|
34
53
|
export function resetDiskCacheForTesting() {
|
|
35
54
|
diskCacheInstance = undefined;
|
|
55
|
+
textDiskCacheInstance = undefined;
|
|
36
56
|
}
|
|
37
57
|
// Person info is shared across many RFCs (a single author appears in many RFCs),
|
|
38
58
|
// so a dedicated cache pays off — keep it in-process LRU.
|
|
@@ -52,10 +72,14 @@ const authorsCache = new LRUCache({ maxSize: 100, name: 'AuthorsCache' });
|
|
|
52
72
|
* call short-circuits at the in-memory layer.
|
|
53
73
|
*/
|
|
54
74
|
export async function fetchRFCXML(rfcNumber, options = {}) {
|
|
55
|
-
if (
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
75
|
+
if (options.forceFresh) {
|
|
76
|
+
// 取り直しはキャッシュもまとめ合わせも通さない(prefetch の --force)。
|
|
77
|
+
return fetchRFCXMLFromNetwork(rfcNumber);
|
|
78
|
+
}
|
|
79
|
+
const cached = xmlCache.get(rfcNumber);
|
|
80
|
+
if (cached)
|
|
81
|
+
return cached;
|
|
82
|
+
return xmlInFlight.share(rfcNumber, async () => {
|
|
59
83
|
const disk = getDiskCache();
|
|
60
84
|
if (disk) {
|
|
61
85
|
const fromDisk = await disk.get(rfcNumber);
|
|
@@ -65,7 +89,13 @@ export async function fetchRFCXML(rfcNumber, options = {}) {
|
|
|
65
89
|
return fromDisk;
|
|
66
90
|
}
|
|
67
91
|
}
|
|
68
|
-
|
|
92
|
+
return fetchRFCXMLFromNetwork(rfcNumber);
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* RFCXML をネットワークから取り、両方のキャッシュに入れる。
|
|
97
|
+
*/
|
|
98
|
+
async function fetchRFCXMLFromNetwork(rfcNumber) {
|
|
69
99
|
// Build source list
|
|
70
100
|
const sources = Object.entries(RFC_XML_SOURCES).map(([name, urlFn]) => ({
|
|
71
101
|
name,
|
|
@@ -112,20 +142,29 @@ export async function fetchRFCMetadata(rfcNumber, options = {}) {
|
|
|
112
142
|
if (cached && (!options.includeAuthors || cached.authors.length > 0)) {
|
|
113
143
|
return cached;
|
|
114
144
|
}
|
|
145
|
+
// 著者の有無で求めるものが違うので、鍵に含める。
|
|
146
|
+
const key = `${rfcNumber}:${options.includeAuthors ? 'authors' : 'core'}`;
|
|
147
|
+
return metadataInFlight.share(key, () => fetchRFCMetadataUncached(rfcNumber, options));
|
|
148
|
+
}
|
|
149
|
+
async function fetchRFCMetadataUncached(rfcNumber, options) {
|
|
115
150
|
const [coreResult, authorsResult] = await Promise.allSettled([
|
|
116
151
|
fetchDocumentCore(rfcNumber),
|
|
117
152
|
options.includeAuthors ? fetchAuthors(rfcNumber) : Promise.resolve([]),
|
|
118
153
|
]);
|
|
119
|
-
//
|
|
154
|
+
// Datatracker に届かなかった。category / stream は**付けない**。
|
|
155
|
+
// v0.6.52 までは 'info' / 'IETF' を既定値にしていたため、RFC 9112
|
|
156
|
+
// (Proposed Standard)が API 不達のときに info として返り、取れなかった
|
|
157
|
+
// ことが応答のどこにも出なかった。理由は `datatrackerError` に載せ、
|
|
158
|
+
// `get_rfc_structure` が `_sourceNote` に書く。キャッシュには入れない。
|
|
120
159
|
if (coreResult.status === 'rejected') {
|
|
121
|
-
|
|
160
|
+
const reason = coreResult.reason instanceof Error ? coreResult.reason.message : String(coreResult.reason);
|
|
161
|
+
logger.warn(`RFC ${rfcNumber}`, `Metadata fetch failed: ${reason}`);
|
|
122
162
|
return {
|
|
123
163
|
number: rfcNumber,
|
|
124
164
|
title: `RFC ${rfcNumber}`,
|
|
125
165
|
authors: authorsResult.status === 'fulfilled' ? authorsResult.value : [],
|
|
126
166
|
datatrackerUpdated: '',
|
|
127
|
-
|
|
128
|
-
stream: 'IETF',
|
|
167
|
+
datatrackerError: reason,
|
|
129
168
|
};
|
|
130
169
|
}
|
|
131
170
|
const core = coreResult.value;
|
|
@@ -139,6 +178,10 @@ export async function fetchRFCMetadata(rfcNumber, options = {}) {
|
|
|
139
178
|
stream: mapStream(core.stream ?? null),
|
|
140
179
|
abstract: core.abstract || undefined,
|
|
141
180
|
};
|
|
181
|
+
if (metadata.category === undefined)
|
|
182
|
+
delete metadata.category;
|
|
183
|
+
if (metadata.stream === undefined)
|
|
184
|
+
delete metadata.stream;
|
|
142
185
|
metadataCache.set(rfcNumber, metadata);
|
|
143
186
|
return metadata;
|
|
144
187
|
}
|
|
@@ -153,12 +196,31 @@ async function fetchDocumentCore(rfcNumber) {
|
|
|
153
196
|
* Fetch RFC text (parallel fetch)
|
|
154
197
|
* Sends concurrent requests to multiple sources and returns the first successful response
|
|
155
198
|
*/
|
|
156
|
-
export async function fetchRFCText(rfcNumber) {
|
|
157
|
-
|
|
199
|
+
export async function fetchRFCText(rfcNumber, options = {}) {
|
|
200
|
+
if (options.forceFresh) {
|
|
201
|
+
return fetchRFCTextFromNetwork(rfcNumber);
|
|
202
|
+
}
|
|
158
203
|
const cached = textCache.get(rfcNumber);
|
|
159
204
|
if (cached) {
|
|
160
205
|
return cached;
|
|
161
206
|
}
|
|
207
|
+
return textInFlight.share(rfcNumber, async () => {
|
|
208
|
+
const disk = getTextDiskCache();
|
|
209
|
+
if (disk) {
|
|
210
|
+
const fromDisk = await disk.get(rfcNumber);
|
|
211
|
+
if (fromDisk) {
|
|
212
|
+
textCache.set(rfcNumber, fromDisk);
|
|
213
|
+
logger.info(`RFC ${rfcNumber}`, `Text loaded from disk cache (${disk.dir})`);
|
|
214
|
+
return fromDisk;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return fetchRFCTextFromNetwork(rfcNumber);
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* テキストをネットワークから取り、両方のキャッシュに入れる。
|
|
222
|
+
*/
|
|
223
|
+
async function fetchRFCTextFromNetwork(rfcNumber) {
|
|
162
224
|
// Build source list
|
|
163
225
|
const sources = Object.entries(RFC_TEXT_SOURCES).map(([name, urlFn]) => ({
|
|
164
226
|
name,
|
|
@@ -171,6 +233,9 @@ export async function fetchRFCText(rfcNumber) {
|
|
|
171
233
|
validate: (t) => t.includes('Request for Comments') || t.includes('RFC '),
|
|
172
234
|
});
|
|
173
235
|
textCache.set(rfcNumber, text);
|
|
236
|
+
const disk = getTextDiskCache();
|
|
237
|
+
if (disk)
|
|
238
|
+
await disk.set(rfcNumber, text);
|
|
174
239
|
logger.info(`RFC ${rfcNumber}`, `Text fetched from ${source}`);
|
|
175
240
|
return text;
|
|
176
241
|
}
|
|
@@ -395,21 +460,43 @@ export function isRFCXMLAvailable(rfcNumber) {
|
|
|
395
460
|
export class RFCXMLNotAvailableError extends Error {
|
|
396
461
|
rfcNumber;
|
|
397
462
|
isOldRFC;
|
|
463
|
+
/**
|
|
464
|
+
* すべての取得元が 404 だったか(Issue #20)。
|
|
465
|
+
* `true` なら「その番号の XML は公開されていない」。`false` なら 5xx・
|
|
466
|
+
* タイムアウト・DNS など一時的な失敗を含むので、`getParsedRFC` は番号を
|
|
467
|
+
* 問わずテキストを試す。
|
|
468
|
+
*/
|
|
469
|
+
notFound;
|
|
398
470
|
suggestion;
|
|
399
471
|
constructor(rfcNumber, originalErrors = []) {
|
|
400
472
|
const threshold = RFC_CONFIG.xmlAvailableFrom;
|
|
401
473
|
const isOldRFC = rfcNumber < threshold;
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
474
|
+
// すべての取得元が 404 を返したなら、その番号の RFC は公開されていない。
|
|
475
|
+
// 「Check network connection」と案内すると、利用者は無いものを探しに行く。
|
|
476
|
+
// `fetchFromMultipleSources` は取得元ごとの失敗を `;` で連ねて 1 本の文にする
|
|
477
|
+
// (`All sources failed: [rfcEditor] HTTP 503; [datatracker] HTTP 404`)ので、
|
|
478
|
+
// 取得元ごとに分けてから見る。
|
|
479
|
+
const perSource = originalErrors.flatMap((error) => error.split(/;\s*/));
|
|
480
|
+
const allNotFound = perSource.length > 0 && perSource.every((error) => /\b404\b/.test(error));
|
|
481
|
+
const reason = allNotFound
|
|
482
|
+
? 'No RFC with that number is published'
|
|
483
|
+
: isOldRFC
|
|
484
|
+
? `Old RFC (< ${threshold}), XML may not be available`
|
|
485
|
+
: 'Network error';
|
|
486
|
+
const suggestion = allNotFound
|
|
487
|
+
? `Every source returned 404 for RFC ${rfcNumber}. Check the RFC number.`
|
|
488
|
+
: isOldRFC
|
|
489
|
+
? `RFC ${rfcNumber} was published before RFCXML v3 format, XML may not be available. ` +
|
|
490
|
+
`Consider using text format (use ietf MCP get_ietf_doc).`
|
|
491
|
+
: `Failed to fetch RFC ${rfcNumber} XML. Check network connection.`;
|
|
406
492
|
super(`Could not fetch RFC ${rfcNumber} XML.\n` +
|
|
407
|
-
`Reason: ${
|
|
493
|
+
`Reason: ${reason}\n` +
|
|
408
494
|
`Suggestion: ${suggestion}` +
|
|
409
495
|
(originalErrors.length > 0 ? `\nDetails: ${originalErrors.join(', ')}` : ''));
|
|
410
496
|
this.name = 'RFCXMLNotAvailableError';
|
|
411
497
|
this.rfcNumber = rfcNumber;
|
|
412
498
|
this.isOldRFC = isOldRFC;
|
|
499
|
+
this.notFound = allNotFound;
|
|
413
500
|
this.suggestion = suggestion;
|
|
414
501
|
}
|
|
415
502
|
}
|
|
@@ -423,6 +510,9 @@ export function clearCache() {
|
|
|
423
510
|
metadataCache.clear();
|
|
424
511
|
authorsCache.clear();
|
|
425
512
|
personCache.clear();
|
|
513
|
+
xmlInFlight.clear();
|
|
514
|
+
textInFlight.clear();
|
|
515
|
+
metadataInFlight.clear();
|
|
426
516
|
}
|
|
427
517
|
// Helper functions
|
|
428
518
|
/**
|
|
@@ -433,6 +523,9 @@ export function clearCache() {
|
|
|
433
523
|
* the API actually returns today, so the original string-comparison logic was
|
|
434
524
|
* silently falling through to `'info'` for every RFC. We extract the trailing
|
|
435
525
|
* slug when given a URI and normalize.
|
|
526
|
+
*
|
|
527
|
+
* 対応表に無い値(RFC 1 の `unkn`)は `undefined` を返す。`'info'` に落とすと、
|
|
528
|
+
* Datatracker が「不明」と言っているものを Informational として返すことになる。
|
|
436
529
|
*/
|
|
437
530
|
function mapCategory(stdLevel) {
|
|
438
531
|
const slug = (extractTrailingSlug(stdLevel) ?? stdLevel ?? '').toLowerCase();
|
|
@@ -453,13 +546,17 @@ function mapCategory(stdLevel) {
|
|
|
453
546
|
case 'hist':
|
|
454
547
|
case 'historic':
|
|
455
548
|
return 'historic';
|
|
456
|
-
|
|
549
|
+
case 'inf':
|
|
550
|
+
case 'informational':
|
|
457
551
|
return 'info';
|
|
552
|
+
default:
|
|
553
|
+
return undefined;
|
|
458
554
|
}
|
|
459
555
|
}
|
|
460
556
|
/**
|
|
461
557
|
* Map a Datatracker `stream` value to the RFCMetadata stream.
|
|
462
558
|
* See {@link mapCategory} for why URI-form normalization is required.
|
|
559
|
+
* 対応表に無い値(RFC 1 の `legacy`)は `undefined` を返す。
|
|
463
560
|
*/
|
|
464
561
|
function mapStream(stream) {
|
|
465
562
|
const slug = (extractTrailingSlug(stream) ?? stream ?? '').toLowerCase();
|
|
@@ -476,7 +573,7 @@ function mapStream(stream) {
|
|
|
476
573
|
case 'editorial':
|
|
477
574
|
return 'editorial';
|
|
478
575
|
default:
|
|
479
|
-
return
|
|
576
|
+
return undefined;
|
|
480
577
|
}
|
|
481
578
|
}
|
|
482
579
|
//# sourceMappingURL=rfc-fetcher.js.map
|