@remnic/core 9.26.0 → 9.27.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 (145) hide show
  1. package/dist/access-admin-ops-surface.d.ts +5 -5
  2. package/dist/access-admin-ops-surface.js +7 -7
  3. package/dist/access-authorization-probe.d.ts +5 -5
  4. package/dist/access-authorization-probe.js +8 -8
  5. package/dist/access-boundary.d.ts +5 -5
  6. package/dist/access-boundary.js +7 -7
  7. package/dist/access-cli.js +27 -27
  8. package/dist/access-http-lcm-compaction.d.ts +5 -5
  9. package/dist/access-http.d.ts +5 -5
  10. package/dist/access-http.js +12 -12
  11. package/dist/access-identity-continuity-surface.d.ts +4 -4
  12. package/dist/access-identity-continuity-surface.js +7 -7
  13. package/dist/access-lcm-surface.d.ts +5 -5
  14. package/dist/access-lcm-surface.js +7 -7
  15. package/dist/access-mcp.d.ts +5 -5
  16. package/dist/access-mcp.js +10 -10
  17. package/dist/access-namespace-preflight.js +7 -7
  18. package/dist/access-observe-write-surface.d.ts +5 -5
  19. package/dist/access-observe-write-surface.js +7 -7
  20. package/dist/access-operations-batch.js +8 -8
  21. package/dist/access-operations.d.ts +5 -5
  22. package/dist/access-operations.js +9 -9
  23. package/dist/access-recall-concurrency.d.ts +5 -5
  24. package/dist/access-recall-concurrency.js +7 -7
  25. package/dist/access-recall-response.d.ts +5 -5
  26. package/dist/access-recall-response.js +7 -7
  27. package/dist/access-recall-surface.d.ts +5 -5
  28. package/dist/access-recall-surface.js +7 -7
  29. package/dist/{access-service-DL8FJnTj.d.ts → access-service-B4-Fu9x2.d.ts} +3 -3
  30. package/dist/access-service.d.ts +5 -5
  31. package/dist/access-service.js +7 -7
  32. package/dist/access-surface-catalog.d.ts +5 -5
  33. package/dist/access-wearables-meetings-surface.d.ts +2 -2
  34. package/dist/bootstrap.d.ts +4 -4
  35. package/dist/briefing.d.ts +1 -1
  36. package/dist/briefing.js +2 -2
  37. package/dist/buffer.d.ts +1 -1
  38. package/dist/causal-consolidation.js +3 -3
  39. package/dist/{chunk-E6EAB4J6.js → chunk-2RAC4JIG.js} +6 -6
  40. package/dist/{chunk-HBVOJNV4.js → chunk-4MJZTF42.js} +2 -2
  41. package/dist/{chunk-WMDRLTNA.js → chunk-5BRT4Z2I.js} +2 -2
  42. package/dist/{chunk-SN3ZESWQ.js → chunk-BHLN7L7M.js} +3 -3
  43. package/dist/{chunk-7XXCIO6X.js → chunk-BNGPHGVZ.js} +2 -2
  44. package/dist/{chunk-IHMXPG4P.js → chunk-CI26JDZA.js} +2 -2
  45. package/dist/{chunk-UZK7VVPD.js → chunk-CIQXI47W.js} +671 -9
  46. package/dist/chunk-CIQXI47W.js.map +1 -0
  47. package/dist/{chunk-LIIQPDFN.js → chunk-EOLC6EXV.js} +2 -2
  48. package/dist/{chunk-MIFF37NN.js → chunk-EUSLKI4X.js} +2 -2
  49. package/dist/{chunk-PQPSZICJ.js → chunk-EYJSW2RV.js} +2 -2
  50. package/dist/{chunk-Y6E37WRG.js → chunk-FRVTCCGD.js} +2 -2
  51. package/dist/{chunk-4KKV7W62.js → chunk-HEXSNYJD.js} +3 -3
  52. package/dist/{chunk-YN4AUGNF.js → chunk-HORJVJ6K.js} +13 -13
  53. package/dist/{chunk-NKDVXEFU.js → chunk-JDOJZTFF.js} +2 -2
  54. package/dist/{chunk-EXM2Q555.js → chunk-MEBQFQ26.js} +2 -2
  55. package/dist/{chunk-F6LSNPPB.js → chunk-MG6MNSP4.js} +5 -5
  56. package/dist/{chunk-2XP3PWL3.js → chunk-NUWMZIYZ.js} +5 -5
  57. package/dist/{chunk-PX7VDW7R.js → chunk-RGJBM7Z3.js} +15 -15
  58. package/dist/{chunk-ZHP4SUPB.js → chunk-RNY3BO35.js} +2 -2
  59. package/dist/{chunk-XAAIUXJO.js → chunk-RPA2RX5P.js} +2 -2
  60. package/dist/{chunk-67DSDRUB.js → chunk-RSYJV25N.js} +5 -5
  61. package/dist/{chunk-M2KGXXTL.js → chunk-U3TH5Q2Q.js} +2 -2
  62. package/dist/{chunk-VGEH3STZ.js → chunk-UT3OQPFA.js} +2 -2
  63. package/dist/{chunk-H2PBM562.js → chunk-WEAO45M4.js} +2 -2
  64. package/dist/{chunk-KPWLZBXZ.js → chunk-XKO7BM2C.js} +2 -2
  65. package/dist/{chunk-FE5FKTCI.js → chunk-XQ4EH6DI.js} +2 -2
  66. package/dist/{chunk-XNUBF6RF.js → chunk-YSGBD3QN.js} +2 -2
  67. package/dist/{cli-BoouX6LX.d.ts → cli-Bz7X7dGx.d.ts} +3 -3
  68. package/dist/cli.d.ts +6 -6
  69. package/dist/cli.js +21 -21
  70. package/dist/compounding/engine.d.ts +1 -1
  71. package/dist/compounding/engine.js +2 -2
  72. package/dist/connectors/codex-materialize-runner.js +2 -2
  73. package/dist/connectors/index.js +2 -2
  74. package/dist/consolidation-provenance-check.d.ts +1 -1
  75. package/dist/consolidation-undo.d.ts +1 -1
  76. package/dist/contradiction/index.d.ts +1 -1
  77. package/dist/corpus-watermark.js +4 -4
  78. package/dist/entity-retrieval.d.ts +1 -1
  79. package/dist/entity-retrieval.js +2 -2
  80. package/dist/explicit-capture.d.ts +4 -4
  81. package/dist/index.d.ts +10 -10
  82. package/dist/index.js +27 -27
  83. package/dist/maintenance/memory-governance.js +2 -2
  84. package/dist/maintenance/rebuild-memory-lifecycle-ledger.d.ts +1 -1
  85. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +2 -2
  86. package/dist/maintenance/rebuild-memory-projection.d.ts +1 -1
  87. package/dist/maintenance/rebuild-memory-projection.js +3 -3
  88. package/dist/{maintenance-DD22oIx9.d.ts → maintenance-DgnD2z8S.d.ts} +1 -1
  89. package/dist/mcp-memory-inspector-app.d.ts +5 -5
  90. package/dist/memory-worth-outcomes.d.ts +1 -1
  91. package/dist/namespaces/migrate.d.ts +1 -1
  92. package/dist/namespaces/migrate.js +3 -3
  93. package/dist/namespaces/storage.d.ts +1 -1
  94. package/dist/namespaces/storage.js +2 -2
  95. package/dist/operator-doctor-corpus.js +5 -5
  96. package/dist/operator-toolkit.d.ts +1 -1
  97. package/dist/operator-toolkit.js +8 -8
  98. package/dist/orchestration/compression-guideline-coordinator.d.ts +1 -1
  99. package/dist/orchestration/maintenance.d.ts +2 -2
  100. package/dist/orchestration/maintenance.js +7 -7
  101. package/dist/{orchestrator-CFJ_TG6D.d.ts → orchestrator-rIHmHpv1.d.ts} +3 -3
  102. package/dist/orchestrator.d.ts +4 -4
  103. package/dist/orchestrator.js +27 -27
  104. package/dist/semantic-consolidation.js +3 -3
  105. package/dist/semantic-rule-promotion.js +2 -2
  106. package/dist/semantic-rule-verifier.js +2 -2
  107. package/dist/{service-Z8uzoAST.d.ts → service-cTvil7D_.d.ts} +1 -1
  108. package/dist/{storage-BZVMn8yJ.d.ts → storage-C9ibpTme.d.ts} +0 -3
  109. package/dist/storage.d.ts +1 -1
  110. package/dist/storage.js +1 -1
  111. package/dist/temporal-supersession.d.ts +1 -1
  112. package/dist/tier-migration.d.ts +1 -1
  113. package/dist/verified-recall.js +2 -2
  114. package/package.json +2 -2
  115. package/src/storage/profile-header.ts +897 -0
  116. package/src/storage-profile-timestamp.test.ts +3290 -0
  117. package/src/storage.ts +5 -8
  118. package/src/testing/subjects/profile-header-write.test.ts +71 -0
  119. package/dist/chunk-UZK7VVPD.js.map +0 -1
  120. /package/dist/{chunk-E6EAB4J6.js.map → chunk-2RAC4JIG.js.map} +0 -0
  121. /package/dist/{chunk-HBVOJNV4.js.map → chunk-4MJZTF42.js.map} +0 -0
  122. /package/dist/{chunk-WMDRLTNA.js.map → chunk-5BRT4Z2I.js.map} +0 -0
  123. /package/dist/{chunk-SN3ZESWQ.js.map → chunk-BHLN7L7M.js.map} +0 -0
  124. /package/dist/{chunk-7XXCIO6X.js.map → chunk-BNGPHGVZ.js.map} +0 -0
  125. /package/dist/{chunk-IHMXPG4P.js.map → chunk-CI26JDZA.js.map} +0 -0
  126. /package/dist/{chunk-LIIQPDFN.js.map → chunk-EOLC6EXV.js.map} +0 -0
  127. /package/dist/{chunk-MIFF37NN.js.map → chunk-EUSLKI4X.js.map} +0 -0
  128. /package/dist/{chunk-PQPSZICJ.js.map → chunk-EYJSW2RV.js.map} +0 -0
  129. /package/dist/{chunk-Y6E37WRG.js.map → chunk-FRVTCCGD.js.map} +0 -0
  130. /package/dist/{chunk-4KKV7W62.js.map → chunk-HEXSNYJD.js.map} +0 -0
  131. /package/dist/{chunk-YN4AUGNF.js.map → chunk-HORJVJ6K.js.map} +0 -0
  132. /package/dist/{chunk-NKDVXEFU.js.map → chunk-JDOJZTFF.js.map} +0 -0
  133. /package/dist/{chunk-EXM2Q555.js.map → chunk-MEBQFQ26.js.map} +0 -0
  134. /package/dist/{chunk-F6LSNPPB.js.map → chunk-MG6MNSP4.js.map} +0 -0
  135. /package/dist/{chunk-2XP3PWL3.js.map → chunk-NUWMZIYZ.js.map} +0 -0
  136. /package/dist/{chunk-PX7VDW7R.js.map → chunk-RGJBM7Z3.js.map} +0 -0
  137. /package/dist/{chunk-ZHP4SUPB.js.map → chunk-RNY3BO35.js.map} +0 -0
  138. /package/dist/{chunk-XAAIUXJO.js.map → chunk-RPA2RX5P.js.map} +0 -0
  139. /package/dist/{chunk-67DSDRUB.js.map → chunk-RSYJV25N.js.map} +0 -0
  140. /package/dist/{chunk-M2KGXXTL.js.map → chunk-U3TH5Q2Q.js.map} +0 -0
  141. /package/dist/{chunk-VGEH3STZ.js.map → chunk-UT3OQPFA.js.map} +0 -0
  142. /package/dist/{chunk-H2PBM562.js.map → chunk-WEAO45M4.js.map} +0 -0
  143. /package/dist/{chunk-KPWLZBXZ.js.map → chunk-XKO7BM2C.js.map} +0 -0
  144. /package/dist/{chunk-FE5FKTCI.js.map → chunk-XQ4EH6DI.js.map} +0 -0
  145. /package/dist/{chunk-XNUBF6RF.js.map → chunk-YSGBD3QN.js.map} +0 -0
@@ -0,0 +1,897 @@
1
+ const LAST_UPDATED_HEADER = /^\*Last updated:[^*]*\*$/;
2
+ const PROFILE_TITLE = /^ {0,3}#(?:[ \t]+|$)/;
3
+ const MARKDOWN_HEADING = /^ {0,3}#{1,6}(?:[ \t]+|$)/;
4
+ const MARKDOWN_LIST_ITEM = /^(?:[-+*]|\d{1,9}[.)])[ \t]+/;
5
+ const MARKDOWN_LIST_ITEM_CAN_INTERRUPT = /^(?:[-+*]|1[.)])[ \t]+/;
6
+ const MARKDOWN_THEMATIC_BREAK = /^(?:(?:\*[ \t]*){3,}|(?:-[ \t]*){3,}|(?:_[ \t]*){3,})$/;
7
+ const MARKDOWN_SETEXT_UNDERLINE = /^(?:=+|-+)$/;
8
+ const MARKDOWN_BLOCK_QUOTE = /^>/;
9
+ const MARKDOWN_LINK_REFERENCE =
10
+ /^\[(?:\\.|[^\\\[\]])+\]:\s*(?:<[^>\n]*>|([^\s]+))(?:\s+(?:"[^"\n]*"|'[^'\n]*'|\([^)\n]*\)))?$/;
11
+ const MARKDOWN_LINK_REFERENCE_LABEL = /^\[(?:\\.|[^\\\[\]])+\]:\s*$/;
12
+ const MARKDOWN_LINK_REFERENCE_CONTINUATION =
13
+ /^ {1,3}(?:"[^"\n]*"|'[^'\n]*'|\([^)\n]*\))$/;
14
+ const MARKDOWN_LINK_REFERENCE_DESTINATION_CONTINUATION =
15
+ /^ {1,3}(?:<[^>\n]*>|(?!["'(])[^\s]+(?:\s+(?:"[^"\n]*"|'[^'\n]*'|\([^)\n]*\)))?)$/;
16
+ const UTF8_BOM = "\uFEFF";
17
+
18
+ type FenceMarker = {
19
+ indentation: number;
20
+ character: string;
21
+ length: number;
22
+ trailing: string;
23
+ };
24
+
25
+ function isLastUpdatedHeader(line: string): boolean {
26
+ const withoutBom = line.startsWith(UTF8_BOM) ? line.slice(1) : line;
27
+ if (isIndentedCodeLine(withoutBom)) return false;
28
+ return LAST_UPDATED_HEADER.test(withoutBom.trim());
29
+ }
30
+
31
+ function isIndentedCodeLine(line: string): boolean {
32
+ let indentation = 0;
33
+ while (line[indentation] === " ") indentation += 1;
34
+ return indentation >= 4 || line[indentation] === "\t";
35
+ }
36
+
37
+ function getFenceMarker(line: string): FenceMarker | null {
38
+ let indentation = 0;
39
+ while (line[indentation] === " ") indentation += 1;
40
+ if (indentation > 3 || line[indentation] === "\t") return null;
41
+ const trimmed = line.slice(indentation);
42
+ const character = trimmed[0];
43
+ if (character !== "`" && character !== "~") return null;
44
+ let length = 0;
45
+ while (trimmed[length] === character) length += 1;
46
+ if (length < 3) return null;
47
+ const trailing = trimmed.slice(length);
48
+ if (character === "`" && trailing.includes("`")) return null;
49
+ return { indentation, character, length, trailing };
50
+ }
51
+
52
+ function isClosingFence(fence: FenceMarker, openFence: FenceMarker): boolean {
53
+ return (
54
+ fence.character === openFence.character &&
55
+ fence.length >= openFence.length &&
56
+ fence.trailing.trim() === ""
57
+ );
58
+ }
59
+
60
+ const RAW_HTML_BLOCK_TAGS = ["pre", "textarea", "script", "style"] as const;
61
+ const HTML_BLOCK_TAGS = [
62
+ "address",
63
+ "article",
64
+ "aside",
65
+ "base",
66
+ "basefont",
67
+ "blockquote",
68
+ "body",
69
+ "caption",
70
+ "center",
71
+ "col",
72
+ "colgroup",
73
+ "dd",
74
+ "details",
75
+ "dialog",
76
+ "dir",
77
+ "div",
78
+ "dl",
79
+ "dt",
80
+ "fieldset",
81
+ "figcaption",
82
+ "figure",
83
+ "footer",
84
+ "form",
85
+ "frame",
86
+ "frameset",
87
+ "h1",
88
+ "h2",
89
+ "h3",
90
+ "h4",
91
+ "h5",
92
+ "h6",
93
+ "head",
94
+ "header",
95
+ "hgroup",
96
+ "hr",
97
+ "html",
98
+ "iframe",
99
+ "legend",
100
+ "li",
101
+ "link",
102
+ "main",
103
+ "menu",
104
+ "menuitem",
105
+ "nav",
106
+ "noframes",
107
+ "ol",
108
+ "optgroup",
109
+ "option",
110
+ "p",
111
+ "param",
112
+ "search",
113
+ "section",
114
+ "summary",
115
+ "table",
116
+ "tbody",
117
+ "td",
118
+ "tfoot",
119
+ "th",
120
+ "thead",
121
+ "title",
122
+ "tr",
123
+ "track",
124
+ "ul",
125
+ ] as const;
126
+
127
+ type HtmlBlock = {
128
+ endMarker: string | null;
129
+ endsAtBlankLine: boolean;
130
+ tagName: string | null;
131
+ depth: number;
132
+ };
133
+ type OpenHtmlBlock = HtmlBlock & {
134
+ listIndentation: number | null;
135
+ };
136
+
137
+ function findHtmlBlockTag(line: string, tags: readonly string[]): string | null {
138
+ const isClosing = line.startsWith("</");
139
+ const nameStart = isClosing ? 2 : 1;
140
+ for (const tag of tags) {
141
+ const nextCharacter = line[nameStart + tag.length];
142
+ const isSelfClosingSlash =
143
+ !isClosing && nextCharacter === "/" && line[nameStart + tag.length + 1] === ">";
144
+ if (
145
+ line.startsWith(`${isClosing ? "</" : "<"}${tag}`) &&
146
+ (nextCharacter === undefined ||
147
+ nextCharacter === ">" ||
148
+ isSelfClosingSlash ||
149
+ nextCharacter === " " ||
150
+ nextCharacter === "\t")
151
+ ) {
152
+ return tag;
153
+ }
154
+ }
155
+ return null;
156
+ }
157
+
158
+ type HtmlTag = {
159
+ name: string;
160
+ isClosing: boolean;
161
+ isSelfClosing: boolean;
162
+ };
163
+
164
+ function isAsciiLetter(character: string): boolean {
165
+ return (
166
+ (character >= "a" && character <= "z") ||
167
+ (character >= "A" && character <= "Z")
168
+ );
169
+ }
170
+
171
+ function isHtmlTagNameCharacter(character: string): boolean {
172
+ return (
173
+ isAsciiLetter(character) ||
174
+ (character >= "0" && character <= "9") ||
175
+ character === "-"
176
+ );
177
+ }
178
+ const HTML_ATTRIBUTES =
179
+ /^(?:[A-Za-z_:][A-Za-z0-9_.:-]*(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s"'=<>`]+))?)(?:\s+[A-Za-z_:][A-Za-z0-9_.:-]*(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s"'=<>`]+))?)*$/;
180
+
181
+ function hasValidHtmlTagAttributes(attributeText: string, isClosing: boolean): boolean {
182
+ const trimmed = attributeText.trim();
183
+ if (isClosing || trimmed === "") return trimmed === "";
184
+ const withoutSelfClosingSlash = trimmed.endsWith("/")
185
+ ? trimmed.slice(0, -1).trimEnd()
186
+ : trimmed;
187
+ return withoutSelfClosingSlash === "" || HTML_ATTRIBUTES.test(withoutSelfClosingSlash);
188
+ }
189
+
190
+ function findCompleteHtmlTag(line: string): HtmlTag | null {
191
+ let index = 1;
192
+ const isClosing = line[index] === "/";
193
+ if (isClosing) index += 1;
194
+ const nameStart = index;
195
+ if (!isAsciiLetter(line[index] ?? "")) return null;
196
+ while (isHtmlTagNameCharacter(line[index] ?? "")) index += 1;
197
+ const nextCharacter = line[index] ?? "";
198
+ if (
199
+ nextCharacter !== ">" &&
200
+ nextCharacter !== "/" &&
201
+ nextCharacter !== " " &&
202
+ nextCharacter !== "\t"
203
+ ) {
204
+ return null;
205
+ }
206
+ const name = line.slice(nameStart, index).toLowerCase();
207
+ let quote: string | null = null;
208
+ for (; index < line.length; index += 1) {
209
+ const character = line[index]!;
210
+ if (quote) {
211
+ if (character === quote) quote = null;
212
+ continue;
213
+ }
214
+ if (character === "\"" || character === "'") {
215
+ quote = character;
216
+ continue;
217
+ }
218
+ if (character === ">" && line.slice(index + 1).trim() === "") {
219
+ const tagBody = line.slice(nameStart, index).trimEnd();
220
+ if (!hasValidHtmlTagAttributes(tagBody.slice(name.length), isClosing)) return null;
221
+ return {
222
+ name,
223
+ isClosing,
224
+ isSelfClosing: !isClosing && tagBody.endsWith("/"),
225
+ };
226
+ }
227
+ }
228
+ return null;
229
+ }
230
+ function findHtmlTagPrefix(line: string): HtmlTag | null {
231
+ let quote: string | null = null;
232
+ for (let index = 1; index < line.length; index += 1) {
233
+ const character = line[index]!;
234
+ if (quote) {
235
+ if (character === quote) quote = null;
236
+ continue;
237
+ }
238
+ if (character === "\"" || character === "'") {
239
+ quote = character;
240
+ continue;
241
+ }
242
+ if (character !== ">") continue;
243
+ return findCompleteHtmlTag(line.slice(0, index + 1));
244
+ }
245
+ return null;
246
+ }
247
+
248
+ function isRawHtmlBlockTerminator(line: string): boolean {
249
+ const normalizedLine = line.toLowerCase();
250
+ return RAW_HTML_BLOCK_TAGS.some((name) =>
251
+ hasRawHtmlBlockEndMarker(normalizedLine, `</${name}>`),
252
+ );
253
+ }
254
+
255
+ function findHtmlTags(line: string): HtmlTag[] {
256
+ const tags: HtmlTag[] = [];
257
+ let index = 0;
258
+ while (index < line.length) {
259
+ if (line[index] !== "<") {
260
+ index += 1;
261
+ continue;
262
+ }
263
+
264
+ let cursor = index + 1;
265
+ const isClosing = line[cursor] === "/";
266
+ if (isClosing) cursor += 1;
267
+ const nameStart = cursor;
268
+ if (!isAsciiLetter(line[cursor] ?? "")) {
269
+ index += 1;
270
+ continue;
271
+ }
272
+ while (isHtmlTagNameCharacter(line[cursor] ?? "")) cursor += 1;
273
+ const name = line.slice(nameStart, cursor).toLowerCase();
274
+ let quote: string | null = null;
275
+ for (; cursor < line.length; cursor += 1) {
276
+ const character = line[cursor] ?? "";
277
+ if (quote) {
278
+ if (character === quote) quote = null;
279
+ continue;
280
+ }
281
+ if (character === "\"" || character === "'") {
282
+ quote = character;
283
+ continue;
284
+ }
285
+ if (character !== ">") continue;
286
+ const tagBody = line.slice(nameStart, cursor).trimEnd();
287
+ tags.push({
288
+ name,
289
+ isClosing,
290
+ isSelfClosing: !isClosing && tagBody.endsWith("/"),
291
+ });
292
+ index = cursor + 1;
293
+ break;
294
+ }
295
+ if (cursor >= line.length) break;
296
+ }
297
+ return tags;
298
+ }
299
+
300
+ function updateHtmlBlockDepth(
301
+ htmlBlock: OpenHtmlBlock,
302
+ line: string,
303
+ skipFirstOpening: boolean,
304
+ ): OpenHtmlBlock | null {
305
+ if (!htmlBlock.tagName) return htmlBlock;
306
+ let depth = htmlBlock.depth;
307
+ let skippedFirstOpening = !skipFirstOpening;
308
+ for (const tag of findHtmlTags(line)) {
309
+ if (tag.name !== htmlBlock.tagName) continue;
310
+ if (tag.isClosing) {
311
+ depth -= 1;
312
+ } else if (!tag.isSelfClosing) {
313
+ if (!skippedFirstOpening) {
314
+ skippedFirstOpening = true;
315
+ continue;
316
+ }
317
+ depth += 1;
318
+ }
319
+ }
320
+ if (depth > 0) return { ...htmlBlock, depth };
321
+ return htmlBlock.endsAtBlankLine
322
+ ? { ...htmlBlock, endMarker: null, endsAtBlankLine: true, tagName: null, depth: 0 }
323
+ : null;
324
+ }
325
+
326
+ function hasSpacedSelfClosingSlash(line: string): boolean {
327
+ return /\s\/\s*>/.test(line);
328
+ }
329
+ function findHtmlBlockStart(
330
+ normalizedLine: string,
331
+ trimmedLine: string,
332
+ allowGenericHtmlBlock: boolean,
333
+ ): HtmlBlock | null {
334
+ const rawTag = findHtmlBlockTag(normalizedLine, RAW_HTML_BLOCK_TAGS);
335
+ if (rawTag) {
336
+ const completeTag =
337
+ findCompleteHtmlTag(trimmedLine) ?? findHtmlTagPrefix(trimmedLine);
338
+ if (!completeTag && normalizedLine.startsWith("</")) return null;
339
+ if (completeTag?.isSelfClosing && !hasSpacedSelfClosingSlash(trimmedLine)) return null;
340
+ const endMarker = `</${rawTag}>`;
341
+ if (hasHtmlBlockEndMarker(normalizedLine, endMarker)) {
342
+ if (!completeTag || completeTag.isClosing) return null;
343
+ return { endMarker, endsAtBlankLine: false, tagName: null, depth: 0 };
344
+ }
345
+ return { endMarker, endsAtBlankLine: false, tagName: null, depth: 0 };
346
+ }
347
+ if (normalizedLine.startsWith("<!--")) {
348
+ return { endMarker: "-->", endsAtBlankLine: false, tagName: null, depth: 0 };
349
+ }
350
+ if (normalizedLine.startsWith("<?")) {
351
+ return { endMarker: "?>", endsAtBlankLine: false, tagName: null, depth: 0 };
352
+ }
353
+ if (trimmedLine.startsWith("<![CDATA[")) {
354
+ return { endMarker: "]]>", endsAtBlankLine: false, tagName: null, depth: 0 };
355
+ }
356
+ const declarationFirst = trimmedLine[2];
357
+ if (
358
+ trimmedLine.startsWith("<!") &&
359
+ declarationFirst &&
360
+ declarationFirst >= "A" &&
361
+ declarationFirst <= "Z"
362
+ ) {
363
+ return { endMarker: ">", endsAtBlankLine: false, tagName: null, depth: 0 };
364
+ }
365
+ const blockTag = findHtmlBlockTag(normalizedLine, HTML_BLOCK_TAGS);
366
+ if (blockTag) {
367
+ const completeTag =
368
+ findCompleteHtmlTag(trimmedLine) ?? findHtmlTagPrefix(trimmedLine);
369
+ if (!completeTag) {
370
+ return { endMarker: null, endsAtBlankLine: true, tagName: null, depth: 0 };
371
+ }
372
+ if (completeTag.isSelfClosing) {
373
+ return { endMarker: null, endsAtBlankLine: true, tagName: null, depth: 0 };
374
+ }
375
+ return { endMarker: `</${blockTag}>`, endsAtBlankLine: true, tagName: blockTag, depth: 1 };
376
+ }
377
+ if (!allowGenericHtmlBlock) return null;
378
+ const genericTag = findCompleteHtmlTag(trimmedLine);
379
+ if (!genericTag) return null;
380
+ if (genericTag.isClosing || genericTag.isSelfClosing) {
381
+ return { endMarker: null, endsAtBlankLine: true, tagName: null, depth: 0 };
382
+ }
383
+ return { endMarker: `</${genericTag.name}>`, endsAtBlankLine: true, tagName: genericTag.name, depth: 1 };
384
+ }
385
+
386
+
387
+ type ProfileLine = {
388
+ content: string;
389
+ ending: string;
390
+ };
391
+
392
+ function parseProfileLines(content: string): ProfileLine[] {
393
+ const segments = content.split(/(\r\n|\n|\r)/);
394
+ const lines: ProfileLine[] = [];
395
+ for (let index = 0; index < segments.length; index += 2) {
396
+ lines.push({
397
+ content: segments[index] ?? "",
398
+ ending: segments[index + 1] ?? "",
399
+ });
400
+ }
401
+ return lines;
402
+ }
403
+
404
+ function renderProfileLines(lines: ProfileLine[]): string {
405
+ let rendered = "";
406
+ for (const line of lines) {
407
+ rendered += line.content + line.ending;
408
+ }
409
+ return rendered;
410
+ }
411
+
412
+ function findFrontmatterEnd(lines: ProfileLine[]): number {
413
+ const firstLine = lines[0]?.content.startsWith(UTF8_BOM) ? lines[0].content.slice(1) : lines[0]?.content;
414
+ if (firstLine !== "---") return -1;
415
+ for (let index = 1; index < lines.length; index += 1) {
416
+ const line = lines[index]?.content.trimEnd();
417
+ if (line === "---" || line === "...") return index;
418
+ }
419
+ return -1;
420
+ }
421
+
422
+ function hasHtmlBlockEndMarker(line: string, endMarker: string): boolean {
423
+ return line.includes(endMarker);
424
+ }
425
+
426
+ function shouldCloseHtmlBlock(line: string, endMarker: string): boolean {
427
+ const normalizedLine = line.trimStart().toLowerCase();
428
+ return normalizedLine.includes(endMarker.toLowerCase());
429
+ }
430
+
431
+
432
+ function hasRawHtmlBlockEndMarker(line: string, endMarker: string): boolean {
433
+ return line.includes(endMarker);
434
+ }
435
+ function canStartGenericHtmlBlock(
436
+ lines: ProfileLine[],
437
+ index: number,
438
+ previousHtmlTerminator: boolean,
439
+ ): boolean {
440
+ const previousLine = lines[index - 1]?.content.trim() ?? "";
441
+ if (previousLine === "") return true;
442
+ return (
443
+ isMetadataBoundary(previousLine, previousHtmlTerminator) &&
444
+ !isLastUpdatedHeader(previousLine) &&
445
+ !MARKDOWN_LIST_ITEM.test(previousLine) &&
446
+ !MARKDOWN_BLOCK_QUOTE.test(previousLine)
447
+ );
448
+ }
449
+
450
+ function isHtmlBlockBoundary(
451
+ lines: ProfileLine[],
452
+ index: number,
453
+ htmlTerminatorIndexes: ReadonlySet<number>,
454
+ ): boolean {
455
+ if (htmlTerminatorIndexes.has(index)) return true;
456
+ const line = lines[index]?.content ?? "";
457
+ const parserLine = index === 0 && line.startsWith(UTF8_BOM) ? line.slice(1) : line;
458
+ if (isIndentedCodeLine(parserLine) || getFenceMarker(parserLine)) return false;
459
+ const trimmedLine = parserLine.trimStart();
460
+ return (
461
+ findHtmlBlockStart(
462
+ trimmedLine.toLowerCase(),
463
+ trimmedLine,
464
+ canStartGenericHtmlBlock(lines, index, htmlTerminatorIndexes.has(index - 1)),
465
+ ) !== null
466
+ );
467
+ }
468
+
469
+ function visitProfileMetadataLines(
470
+ lines: ProfileLine[],
471
+ visit: (line: string, index: number) => void,
472
+ onHtmlBlockTerminator?: (index: number) => void,
473
+ onFenceBlockTerminator?: (index: number) => void,
474
+ ): void {
475
+ const frontmatterEnd = findFrontmatterEnd(lines);
476
+ const htmlTerminatorIndexes = new Set<number>();
477
+ const markHtmlTerminator = (index: number): void => {
478
+ htmlTerminatorIndexes.add(index);
479
+ onHtmlBlockTerminator?.(index);
480
+ };
481
+ let openFence: (FenceMarker & { listIndentation: number | null }) | null = null;
482
+ let openHtmlBlock: OpenHtmlBlock | null = null;
483
+ for (let index = 0; index < lines.length; index += 1) {
484
+ const line = lines[index]?.content ?? "";
485
+ const parserLine = index === 0 && line.startsWith(UTF8_BOM) ? line.slice(1) : line;
486
+ if (index <= frontmatterEnd) continue;
487
+ const fence = getFenceMarker(parserLine);
488
+ if (openFence) {
489
+ const indentation = parserLine.length - parserLine.trimStart().length;
490
+ const leavesListContainer =
491
+ openFence.listIndentation !== null &&
492
+ parserLine.trim() !== "" &&
493
+ indentation < openFence.listIndentation;
494
+ if (!leavesListContainer) {
495
+ if (fence && isClosingFence(fence, openFence)) openFence = null;
496
+ continue;
497
+ }
498
+ openFence = null;
499
+ if (index > 0) onFenceBlockTerminator?.(index - 1);
500
+ }
501
+ const trimmedLine = parserLine.trimStart();
502
+ const normalizedLine = trimmedLine.toLowerCase();
503
+ if (openHtmlBlock) {
504
+ const indentation = parserLine.length - parserLine.trimStart().length;
505
+ const leavesListContainer =
506
+ openHtmlBlock.listIndentation !== null &&
507
+ parserLine.trim() !== "" &&
508
+ indentation < openHtmlBlock.listIndentation;
509
+ if (!leavesListContainer) {
510
+ if (openHtmlBlock.endsAtBlankLine && normalizedLine === "") {
511
+ openHtmlBlock = null;
512
+ continue;
513
+ }
514
+ if (openHtmlBlock.tagName) {
515
+ const nextHtmlBlock = updateHtmlBlockDepth(openHtmlBlock, normalizedLine, false);
516
+ if (!nextHtmlBlock) markHtmlTerminator(index);
517
+ openHtmlBlock = nextHtmlBlock;
518
+ continue;
519
+ }
520
+ if (openHtmlBlock.endMarker && shouldCloseHtmlBlock(normalizedLine, openHtmlBlock.endMarker)) {
521
+ openHtmlBlock = null;
522
+ markHtmlTerminator(index);
523
+ }
524
+ continue;
525
+ }
526
+ openHtmlBlock = null;
527
+ if (index > 0) markHtmlTerminator(index - 1);
528
+ }
529
+ if (isIndentedCodeLine(parserLine)) continue;
530
+ if (fence) {
531
+ openFence = {
532
+ ...fence,
533
+ listIndentation: isNestedListContent(lines, index, fence.indentation)
534
+ ? fence.indentation
535
+ : null,
536
+ };
537
+ continue;
538
+ }
539
+ const htmlBlock = findHtmlBlockStart(
540
+ normalizedLine,
541
+ trimmedLine,
542
+ canStartGenericHtmlBlock(lines, index, htmlTerminatorIndexes.has(index - 1)),
543
+ );
544
+ if (htmlBlock) {
545
+ markHtmlTerminator(index);
546
+ const indentation = parserLine.length - parserLine.trimStart().length;
547
+ const htmlBlockWithContainer = {
548
+ ...htmlBlock,
549
+ listIndentation: isNestedListContent(lines, index, indentation) ? indentation : null,
550
+ };
551
+ if (htmlBlock.tagName) {
552
+ openHtmlBlock = updateHtmlBlockDepth(htmlBlockWithContainer, normalizedLine, true);
553
+ if (!openHtmlBlock) markHtmlTerminator(index);
554
+ } else if (htmlBlock.endMarker && shouldCloseHtmlBlock(normalizedLine, htmlBlock.endMarker)) {
555
+ openHtmlBlock = null;
556
+ markHtmlTerminator(index);
557
+ } else {
558
+ openHtmlBlock = htmlBlockWithContainer;
559
+ }
560
+ continue;
561
+ }
562
+ visit(line, index);
563
+ }
564
+ }
565
+
566
+ function isCompletedIndentedCodeBlock(lines: ProfileLine[], index: number): boolean {
567
+ if (!isIndentedCodeLine(lines[index - 1]?.content ?? "")) return false;
568
+ let sawBlank = false;
569
+ for (let previousIndex = index - 2; previousIndex >= 0; previousIndex -= 1) {
570
+ const previousLine = lines[previousIndex]?.content ?? "";
571
+ if (previousLine.trim() === "") {
572
+ sawBlank = true;
573
+ continue;
574
+ }
575
+ if (isIndentedCodeLine(previousLine)) continue;
576
+ return sawBlank;
577
+ }
578
+ return true;
579
+ }
580
+
581
+ function isListContinuation(lines: ProfileLine[], index: number, indentation: number): boolean {
582
+ for (let previousIndex = index - 1; previousIndex >= 0; previousIndex -= 1) {
583
+ const previousLine = lines[previousIndex]?.content ?? "";
584
+ if (previousLine.trim() === "") return false;
585
+ const previousIndentation = previousLine.length - previousLine.trimStart().length;
586
+ if (previousIndentation > indentation) continue;
587
+ const previousBoundaryLine = previousLine.trimStart();
588
+ if (MARKDOWN_LIST_ITEM.test(previousBoundaryLine)) return true;
589
+ if (
590
+ MARKDOWN_HEADING.test(previousBoundaryLine) ||
591
+ MARKDOWN_THEMATIC_BREAK.test(previousBoundaryLine) ||
592
+ MARKDOWN_BLOCK_QUOTE.test(previousBoundaryLine) ||
593
+ getFenceMarker(previousLine) !== null
594
+ ) {
595
+ return false;
596
+ }
597
+ }
598
+ return false;
599
+ }
600
+
601
+ function isNestedListContent(lines: ProfileLine[], index: number, indentation: number): boolean {
602
+ for (let previousIndex = index - 1; previousIndex >= 0; previousIndex -= 1) {
603
+ const previousLine = lines[previousIndex]?.content ?? "";
604
+ if (previousLine.trim() === "") continue;
605
+ const previousIndentation = previousLine.length - previousLine.trimStart().length;
606
+ if (previousIndentation >= indentation) continue;
607
+ if (
608
+ MARKDOWN_LIST_ITEM.test(previousLine.trimStart()) ||
609
+ isListContinuation(lines, previousIndex, indentation)
610
+ ) {
611
+ return true;
612
+ }
613
+ break;
614
+ }
615
+ return false;
616
+ }
617
+
618
+ function isValidBareLinkDestination(destination: string): boolean {
619
+ let parentheses = 0;
620
+ for (let index = 0; index < destination.length; index += 1) {
621
+ const character = destination[index];
622
+ if (character === "\\") {
623
+ if (index + 1 >= destination.length) return false;
624
+ index += 1;
625
+ continue;
626
+ }
627
+ if (character === "<" || character === ">") return false;
628
+ if (character === "(") {
629
+ parentheses += 1;
630
+ } else if (character === ")") {
631
+ if (parentheses === 0) return false;
632
+ parentheses -= 1;
633
+ }
634
+ }
635
+ return parentheses === 0;
636
+ }
637
+
638
+ function isMarkdownLinkReferenceDefinition(line: string): boolean {
639
+ const match = MARKDOWN_LINK_REFERENCE.exec(line);
640
+ return match !== null && (match[1] === undefined || isValidBareLinkDestination(match[1]));
641
+ }
642
+
643
+ function isMarkdownLinkReferenceDefinitionEnd(lines: ProfileLine[], index: number): boolean {
644
+ const line = lines[index]?.content ?? "";
645
+ const boundaryLine = /^ {0,3}(?=\S)/.test(line) ? line.trimStart() : line;
646
+ if (isMarkdownLinkReferenceDefinition(boundaryLine)) return true;
647
+ if (MARKDOWN_LINK_REFERENCE_CONTINUATION.test(line)) {
648
+ const previousLine = lines[index - 1]?.content ?? "";
649
+ const previousBoundaryLine =
650
+ /^ {0,3}(?=\S)/.test(previousLine) ? previousLine.trimStart() : previousLine;
651
+ if (isMarkdownLinkReferenceDefinition(previousBoundaryLine)) return true;
652
+ const destinationLine = lines[index - 1]?.content ?? "";
653
+ if (!MARKDOWN_LINK_REFERENCE_DESTINATION_CONTINUATION.test(destinationLine)) return false;
654
+ const labelLine = lines[index - 2]?.content ?? "";
655
+ const labelBoundaryLine =
656
+ /^ {0,3}(?=\S)/.test(labelLine) ? labelLine.trimStart() : labelLine;
657
+ return MARKDOWN_LINK_REFERENCE_LABEL.test(labelBoundaryLine);
658
+ }
659
+ if (!MARKDOWN_LINK_REFERENCE_DESTINATION_CONTINUATION.test(line)) return false;
660
+ const previousLine = lines[index - 1]?.content ?? "";
661
+ const previousBoundaryLine =
662
+ /^ {0,3}(?=\S)/.test(previousLine) ? previousLine.trimStart() : previousLine;
663
+ return MARKDOWN_LINK_REFERENCE_LABEL.test(previousBoundaryLine);
664
+ }
665
+
666
+ function isProfileTitleLine(lines: ProfileLine[], index: number, line: string): boolean {
667
+ const titleLine = index === 0 && line.startsWith(UTF8_BOM) ? line.slice(1) : line;
668
+ if (!PROFILE_TITLE.test(titleLine)) return false;
669
+ const indentation = titleLine.length - titleLine.trimStart().length;
670
+ if (indentation === 0 || index === 0) return true;
671
+ const previousLine = lines[index - 1]?.content.trim() ?? "";
672
+ return previousLine === ""
673
+ ? !isNestedListContent(lines, index, indentation)
674
+ : previousLine === "---" || previousLine === "...";
675
+ }
676
+
677
+ function findProfileTitleIndex(lines: ProfileLine[]): number {
678
+ let titleIndex = -1;
679
+ visitProfileMetadataLines(lines, (line, index) => {
680
+ if (titleIndex >= 0) return;
681
+ if (isProfileTitleLine(lines, index, line)) titleIndex = index;
682
+ });
683
+ return titleIndex;
684
+ }
685
+
686
+ function isSetextHeadingText(line: string): boolean {
687
+ const withoutBom = line.startsWith(UTF8_BOM) ? line.slice(1) : line;
688
+ const boundaryLine = withoutBom.trim();
689
+ return (
690
+ boundaryLine !== "" &&
691
+ !MARKDOWN_HEADING.test(boundaryLine) &&
692
+ !MARKDOWN_LIST_ITEM.test(boundaryLine) &&
693
+ !MARKDOWN_BLOCK_QUOTE.test(boundaryLine) &&
694
+ !MARKDOWN_THEMATIC_BREAK.test(boundaryLine) &&
695
+ !isMarkdownLinkReferenceDefinition(boundaryLine) &&
696
+ getFenceMarker(withoutBom) === null
697
+ );
698
+ }
699
+
700
+ function isMetadataBoundary(
701
+ line: string,
702
+ htmlTerminator = false,
703
+ allowSetext = true,
704
+ allowParagraphListInterrupt = false,
705
+ setextPreviousLine = "",
706
+ ): boolean {
707
+ const listPattern = allowParagraphListInterrupt
708
+ ? MARKDOWN_LIST_ITEM_CAN_INTERRUPT
709
+ : MARKDOWN_LIST_ITEM;
710
+ const boundaryLine = /^ {0,3}(?=\S)/.test(line) ? line.trimStart() : line;
711
+ const isSetextUnderline =
712
+ !allowSetext &&
713
+ MARKDOWN_SETEXT_UNDERLINE.test(boundaryLine) &&
714
+ isSetextHeadingText(setextPreviousLine);
715
+ return (
716
+ line.trim() === "" ||
717
+ MARKDOWN_HEADING.test(boundaryLine) ||
718
+ listPattern.test(boundaryLine) ||
719
+ (allowSetext && MARKDOWN_SETEXT_UNDERLINE.test(boundaryLine)) ||
720
+ (MARKDOWN_THEMATIC_BREAK.test(boundaryLine) && !isSetextUnderline) ||
721
+ MARKDOWN_BLOCK_QUOTE.test(boundaryLine) ||
722
+ isMarkdownLinkReferenceDefinition(boundaryLine) ||
723
+ isLastUpdatedHeader(line) ||
724
+ getFenceMarker(line) !== null ||
725
+ htmlTerminator
726
+ );
727
+ }
728
+
729
+ function isStandaloneMetadataLine(
730
+ lines: ProfileLine[],
731
+ index: number,
732
+ frontmatterEnd: number,
733
+ htmlTerminatorIndexes: ReadonlySet<number>,
734
+ fenceTerminatorIndexes: ReadonlySet<number>,
735
+ standaloneHeaderIndexes: ReadonlySet<number>,
736
+ ): boolean {
737
+ const currentLine = lines[index]?.content ?? "";
738
+ const currentWithoutBom = currentLine.startsWith(UTF8_BOM)
739
+ ? currentLine.slice(1)
740
+ : currentLine;
741
+ const indentation = currentWithoutBom.length - currentWithoutBom.trimStart().length;
742
+ const previousLine = lines[index - 1]?.content.trimEnd() ?? "";
743
+ const nextLine = lines[index + 1]?.content.trimEnd() ?? "";
744
+ const previousWithoutBom = previousLine.startsWith(UTF8_BOM)
745
+ ? previousLine.slice(1)
746
+ : previousLine;
747
+ const nextWithoutBom = nextLine.startsWith(UTF8_BOM) ? nextLine.slice(1) : nextLine;
748
+ const previousLinkReferenceBoundary = isMarkdownLinkReferenceDefinitionEnd(lines, index - 1);
749
+ const previousMetadataBoundary =
750
+ isCompletedIndentedCodeBlock(lines, index) ||
751
+ previousLinkReferenceBoundary ||
752
+ fenceTerminatorIndexes.has(index - 1) ||
753
+ (isMetadataBoundary(
754
+ previousWithoutBom,
755
+ htmlTerminatorIndexes.has(index - 1),
756
+ ) &&
757
+ (!isLastUpdatedHeader(previousWithoutBom) || standaloneHeaderIndexes.has(index - 1)));
758
+ const nextHtmlBlockBoundary = isHtmlBlockBoundary(
759
+ lines,
760
+ index + 1,
761
+ htmlTerminatorIndexes,
762
+ );
763
+ const nextMetadataBoundary =
764
+ nextHtmlBlockBoundary ||
765
+ isMetadataBoundary(
766
+ nextWithoutBom,
767
+ htmlTerminatorIndexes.has(index + 1),
768
+ false,
769
+ true,
770
+ currentWithoutBom,
771
+ );
772
+ const previousContainerMarker =
773
+ !MARKDOWN_THEMATIC_BREAK.test(previousWithoutBom) &&
774
+ (MARKDOWN_LIST_ITEM.test(previousWithoutBom) || MARKDOWN_BLOCK_QUOTE.test(previousWithoutBom));
775
+ const nestedListContent =
776
+ indentation > 0 && isNestedListContent(lines, index, indentation);
777
+ const startsBlock =
778
+ (index === 0 || index === frontmatterEnd + 1 || previousMetadataBoundary) &&
779
+ !previousContainerMarker &&
780
+ !nestedListContent;
781
+ const endsBlock = index === lines.length - 1 || nextMetadataBoundary;
782
+ const compactHeaderAfterTitle = isProfileTitleLine(lines, index - 1, previousWithoutBom);
783
+ const compactHeaderAfterHeader =
784
+ isLastUpdatedHeader(previousWithoutBom) && standaloneHeaderIndexes.has(index - 1);
785
+ return startsBlock && (endsBlock || compactHeaderAfterTitle || compactHeaderAfterHeader);
786
+ }
787
+
788
+ function findProfileHeaderIndexes(lines: ProfileLine[]): number[] {
789
+ const indexes: number[] = [];
790
+ const frontmatterEnd = findFrontmatterEnd(lines);
791
+ const htmlTerminatorIndexes = new Set<number>();
792
+ const fenceTerminatorIndexes = new Set<number>();
793
+ const standaloneHeaderIndexes = new Set<number>();
794
+ visitProfileMetadataLines(
795
+ lines,
796
+ (line, index) => {
797
+ if (
798
+ isLastUpdatedHeader(line) &&
799
+ isStandaloneMetadataLine(
800
+ lines,
801
+ index,
802
+ frontmatterEnd,
803
+ htmlTerminatorIndexes,
804
+ fenceTerminatorIndexes,
805
+ standaloneHeaderIndexes,
806
+ )
807
+ ) {
808
+ indexes.push(index);
809
+ standaloneHeaderIndexes.add(index);
810
+ }
811
+ },
812
+ (index) => htmlTerminatorIndexes.add(index),
813
+ (index) => fenceTerminatorIndexes.add(index),
814
+ );
815
+ return indexes;
816
+ }
817
+
818
+ function insertHeaderAfter(lines: ProfileLine[], index: number, header: string): void {
819
+ const insertAt = index + 1;
820
+ const ending = lines[index]?.ending || lines[insertAt]?.ending || "\n";
821
+ if (lines[insertAt]?.content.trim() === "") {
822
+ lines.splice(
823
+ insertAt + 1,
824
+ 0,
825
+ { content: header, ending },
826
+ { content: "", ending },
827
+ );
828
+ } else {
829
+ lines.splice(
830
+ insertAt,
831
+ 0,
832
+ { content: "", ending },
833
+ { content: header, ending },
834
+ { content: "", ending },
835
+ );
836
+ }
837
+ }
838
+
839
+ function prependHeader(lines: ProfileLine[], header: string): string {
840
+ const frontmatterEnd = findFrontmatterEnd(lines);
841
+ if (frontmatterEnd >= 0) {
842
+ insertHeaderAfter(lines, frontmatterEnd, header);
843
+ return renderProfileLines(lines);
844
+ }
845
+ const ending = lines[0]?.ending || "\n";
846
+ if (lines[0]?.content.startsWith(UTF8_BOM)) {
847
+ lines[0].content = lines[0].content.slice(1);
848
+ lines.unshift(
849
+ { content: `${UTF8_BOM}${header}`, ending },
850
+ { content: "", ending },
851
+ );
852
+ return renderProfileLines(lines);
853
+ }
854
+ lines.unshift(
855
+ { content: header, ending },
856
+ { content: "", ending },
857
+ );
858
+ return renderProfileLines(lines);
859
+ }
860
+
861
+ export function renderProfileWithLastUpdated(content: string, updatedAt: string): string {
862
+ const lines = parseProfileLines(content);
863
+ const header = `*Last updated: ${updatedAt}*`;
864
+ const titleIndex = findProfileTitleIndex(lines);
865
+ const headerIndexes = findProfileHeaderIndexes(lines);
866
+
867
+ if (headerIndexes.length > 0) {
868
+ const firstHeaderIndex = headerIndexes[0];
869
+ if (firstHeaderIndex === undefined) return renderProfileLines(lines);
870
+ const canonicalIndex =
871
+ titleIndex >= 0
872
+ ? headerIndexes.find((index) => index > titleIndex) ?? firstHeaderIndex
873
+ : firstHeaderIndex;
874
+ const preserveBomAtFileStart = headerIndexes.some((index) =>
875
+ lines[index].content.startsWith(UTF8_BOM),
876
+ );
877
+ const bomPrefix =
878
+ canonicalIndex === 0 && lines[canonicalIndex].content.startsWith(UTF8_BOM)
879
+ ? UTF8_BOM
880
+ : "";
881
+ lines[canonicalIndex].content = `${bomPrefix}${header}`;
882
+ for (let index = headerIndexes.length - 1; index >= 0; index -= 1) {
883
+ const headerIndex = headerIndexes[index];
884
+ if (headerIndex === undefined || headerIndex === canonicalIndex) continue;
885
+ lines.splice(headerIndex, 1);
886
+ }
887
+ if (preserveBomAtFileStart && !lines[0].content.startsWith(UTF8_BOM)) {
888
+ lines[0].content = `${UTF8_BOM}${lines[0].content}`;
889
+ }
890
+ return renderProfileLines(lines);
891
+ }
892
+
893
+ if (titleIndex < 0) return prependHeader(lines, header);
894
+
895
+ insertHeaderAfter(lines, titleIndex, header);
896
+ return renderProfileLines(lines);
897
+ }