@equationalapplications/core-okf 5.3.0 → 5.4.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/README.md CHANGED
@@ -4,11 +4,11 @@
4
4
 
5
5
  ## Overview
6
6
 
7
- A zero-dependency library for parsing and producing [Open Knowledge Format (OKF) v0.1](https://github.com/GoogleCloudPlatform/knowledge-catalog/tree/main/okf) bundles. This package provides the raw primitives to work with OKF frontmatter, concept documents, and index/log files, completely decoupled from any specific database or data model.
7
+ A zero-dependency library for parsing and producing [Open Knowledge Format (OKF) v0.1 and v0.2](https://github.com/GoogleCloudPlatform/knowledge-catalog/tree/main/okf) bundles. This package provides the raw primitives to work with OKF frontmatter, concept documents, and index/log files, completely decoupled from any specific database or data model.
8
8
 
9
9
  For a ready-made `MemoryDump` ⇄ OKF bundle adapter, see the [OKF Import/Export section in `@equationalapplications/core-llm-wiki`](https://github.com/equationalapplications/expo-llm-wiki/blob/main/packages/core/README.md#okf-importexport).
10
10
 
11
- For the llm-wiki interoperability profile (llm-wiki/1), see [`docs/okf-profile.md`](https://github.com/equationalapplications/expo-llm-wiki/blob/main/docs/okf-profile.md). Conformance fixtures live in [`fixtures/`](./fixtures/).
11
+ For the llm-wiki interoperability profiles (`llm-wiki/1` and `llm-wiki/2`), see [`docs/okf-profile.md`](https://github.com/equationalapplications/expo-llm-wiki/blob/main/docs/okf-profile.md). Conformance fixtures live in [`fixtures/`](./fixtures/).
12
12
 
13
13
  ## Installation
14
14
 
@@ -75,3 +75,15 @@ const links = extractMarkdownLinks('See [preferences](facts/fact_abc.md) for mor
75
75
  | [@equationalapplications/core-llm-tools](https://github.com/equationalapplications/expo-llm-wiki/blob/main/packages/core-llm-tools/README.md) | Gemini tool schemas and capability injector |
76
76
  | [**@equationalapplications/core-okf**](https://github.com/equationalapplications/expo-llm-wiki/blob/main/packages/okf/README.md) | Zero-dependency Open Knowledge Format (OKF) v0.1 primitives — parse and produce interoperable knowledge bundles. |
77
77
  | [@equationalapplications/schema-org-llm-wiki](https://github.com/equationalapplications/expo-llm-wiki/blob/main/packages/schema-org/README.md) | Curated schema.org warm-agent ontology manifest |
78
+
79
+ ## OKF v0.2 conformance
80
+
81
+ This package conforms to [OKF v0.2](https://github.com/GoogleCloudPlatform/knowledge-catalog/blob/main/okf/SPEC.md). New helpers for v0.2:
82
+
83
+ - `deriveTrustTier(verified)` — derive `'unverified' | 'machine-confirmed' | 'human-reviewed'`.
84
+ - `isStaleAfter(staleAfter, now)` — staleness check against an absolute `YYYY-MM-DD` cutoff.
85
+ - `parseVerifiedFlexible(value)` — accept either an array or a bare `{ by, at }` mapping.
86
+ - `extractFootnotes(body)` / `serializeFootnotes(footnotes)` — preserve footnote attribution verbatim.
87
+ - `parseFlowMapping(text)` / `parseFlowSequence(text)` — flow parsing with at most one level of mapping-value nesting (no anchors/aliases outside quoted strings).
88
+
89
+ The `serializeFrontmatter` function emits plain objects as flow mappings and actor strings (containing `/` or `:`) via `serializeActorString`. Anchor/alias expansion is rejected; only **unquoted** `&` or `*` characters are treated as opaque (quoted spans are excluded from the scan, so URL query strings like `"https://x/a?p=1&q=2"` remain valid).
package/dist/index.d.mts CHANGED
@@ -1,5 +1,7 @@
1
1
  type OkfFrontmatterScalar = string | number | boolean | null;
2
- type OkfFrontmatterValue = OkfFrontmatterScalar | OkfFrontmatterScalar[];
2
+ type OkfFrontmatterValue = OkfFrontmatterScalar | OkfFrontmatterScalar[] | {
3
+ [key: string]: OkfFrontmatterValue | undefined;
4
+ } | OkfFrontmatterValue[];
3
5
  interface OkfFrontmatter {
4
6
  type: string;
5
7
  title?: string;
@@ -30,8 +32,54 @@ interface OkfMarkdownLink {
30
32
  text: string;
31
33
  path: string;
32
34
  }
35
+ type OkfStatus = 'draft' | 'stable' | 'deprecated';
36
+ type OkfActorKind = 'agent' | 'human' | 'process';
37
+ interface OkfGenerated {
38
+ by: string;
39
+ at: string;
40
+ }
41
+ interface OkfVerifiedEntry {
42
+ by: string;
43
+ at: string;
44
+ [key: string]: OkfFrontmatterScalar;
45
+ }
46
+ type OkfVerified = OkfVerifiedEntry[];
47
+ interface OkfSourceUsageWindow {
48
+ from: string;
49
+ to: string;
50
+ [key: string]: OkfFrontmatterScalar;
51
+ }
52
+ interface OkfSource {
53
+ id?: string;
54
+ resource: string;
55
+ title?: string;
56
+ author?: string;
57
+ usage_count?: number;
58
+ last_modified?: string;
59
+ usage_window?: OkfSourceUsageWindow;
60
+ [key: string]: OkfFrontmatterValue | undefined;
61
+ }
62
+ interface OkfFootnote {
63
+ id: string;
64
+ body: string;
65
+ }
33
66
 
67
+ declare function serializeActorString(value: string): string;
34
68
  declare function serializeFrontmatter(fm: OkfFrontmatter): string;
69
+ /**
70
+ * `depth` counts levels of **mapping-value nesting** only: it increments when a
71
+ * flow MAPPING's key has a value that is itself a flow collection, and is
72
+ * rejected once it would exceed 1 (spec §2.6's "at most one level of nesting").
73
+ * It does NOT increment when a flow SEQUENCE's item is itself a mapping or
74
+ * sequence — `sources: [ {...}, {...} ]` (a flat array of objects) is the base
75
+ * shape v0.2 uses everywhere and is not "nesting" in the spec's sense. This is
76
+ * what lets `sources: [ { usage_window: { from, to } } ]` parse (sequence item
77
+ * is a mapping at depth 0; that mapping's `usage_window` value is a mapping at
78
+ * depth 1 — one level, allowed) while `{ a: { b: { c: 1 } } }` is rejected
79
+ * (`a`'s value nests at depth 1; `b`'s value would need depth 2 — rejected).
80
+ */
81
+ declare function parseFlowMapping(text: string, depth?: number): Record<string, OkfFrontmatterValue> | null;
82
+ declare function parseFlowSequence(text: string, depth?: number): OkfFrontmatterValue[] | null;
35
83
  /**
36
84
  * Parses the subset of YAML frontmatter that {@link serializeFrontmatter} produces:
37
85
  * scalar string/number/boolean/null values, quoted strings, and block string-lists.
@@ -105,4 +153,31 @@ declare function isAllowedOkfPath(filePath: string): boolean;
105
153
  */
106
154
  declare function extractMarkdownLinks(body: string): OkfMarkdownLink[];
107
155
 
108
- export { type OkfFile, type OkfFrontmatter, type OkfFrontmatterScalar, type OkfFrontmatterValue, type OkfIndexEntry, type OkfIndexSection, type OkfLogEntry, type OkfMarkdownLink, appendEventIdComment, appendRelatedSection, buildConceptDocument, buildEntityIndexMd, buildIndexMd, buildLogMd, buildRootIndexMd, extractMarkdownLinks, isAllowedOkfPath, parseConcept, parseEntityIndexMd, parseEventIdComment, parseFrontmatter, parseLogMd, parseRootIndexMd, serializeFrontmatter, splitRelatedSection };
156
+ /**
157
+ * Extracts footnote definitions from a body. OKF v0.2 footnote attribution uses
158
+ * `[^id]` markers whose label joins to `sources[].id`. This helper parses the
159
+ * `[^id]: text` definition lines at the body tail and returns them as opaque
160
+ * text — we deliberately do NOT try to interpret which body span uses which
161
+ * footnote; the body is the source of truth and is preserved verbatim on
162
+ * round-trip.
163
+ */
164
+ declare function extractFootnotes(body: string): OkfFootnote[];
165
+ /** Joins footnote definitions back into a body block. Continuation lines are
166
+ * indented (two spaces) so that {@link extractFootnotes} — which only
167
+ * recognizes continuation lines that are indented per CommonMark — round-trips
168
+ * multi-line bodies. */
169
+ declare function serializeFootnotes(footnotes: OkfFootnote[]): string;
170
+
171
+ type TrustTier = 'unverified' | 'machine-confirmed' | 'human-reviewed';
172
+ declare function deriveTrustTier(verified: OkfVerified | undefined): TrustTier;
173
+ declare function isStaleAfter(staleAfter: string | number | null, now: number): boolean;
174
+ declare function parseVerifiedFlexible(value: unknown): OkfVerified;
175
+ declare function formatVerifiedJson(entries: OkfVerified): string;
176
+ declare function formatSourcesJson(sources: OkfSource[], sharedWindow?: OkfSourceUsageWindow | null): string;
177
+ declare function latestVerified(entries: OkfVerified | undefined, _now: number): {
178
+ by: string;
179
+ at: number;
180
+ } | null;
181
+ declare function parseCitationsList(body: string): string[];
182
+
183
+ export { type OkfActorKind, type OkfFile, type OkfFootnote, type OkfFrontmatter, type OkfFrontmatterScalar, type OkfFrontmatterValue, type OkfGenerated, type OkfIndexEntry, type OkfIndexSection, type OkfLogEntry, type OkfMarkdownLink, type OkfSource, type OkfSourceUsageWindow, type OkfStatus, type OkfVerified, type OkfVerifiedEntry, type TrustTier, appendEventIdComment, appendRelatedSection, buildConceptDocument, buildEntityIndexMd, buildIndexMd, buildLogMd, buildRootIndexMd, deriveTrustTier, extractFootnotes, extractMarkdownLinks, formatSourcesJson, formatVerifiedJson, isAllowedOkfPath, isStaleAfter, latestVerified, parseCitationsList, parseConcept, parseEntityIndexMd, parseEventIdComment, parseFlowMapping, parseFlowSequence, parseFrontmatter, parseLogMd, parseRootIndexMd, parseVerifiedFlexible, serializeActorString, serializeFootnotes, serializeFrontmatter, splitRelatedSection };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  type OkfFrontmatterScalar = string | number | boolean | null;
2
- type OkfFrontmatterValue = OkfFrontmatterScalar | OkfFrontmatterScalar[];
2
+ type OkfFrontmatterValue = OkfFrontmatterScalar | OkfFrontmatterScalar[] | {
3
+ [key: string]: OkfFrontmatterValue | undefined;
4
+ } | OkfFrontmatterValue[];
3
5
  interface OkfFrontmatter {
4
6
  type: string;
5
7
  title?: string;
@@ -30,8 +32,54 @@ interface OkfMarkdownLink {
30
32
  text: string;
31
33
  path: string;
32
34
  }
35
+ type OkfStatus = 'draft' | 'stable' | 'deprecated';
36
+ type OkfActorKind = 'agent' | 'human' | 'process';
37
+ interface OkfGenerated {
38
+ by: string;
39
+ at: string;
40
+ }
41
+ interface OkfVerifiedEntry {
42
+ by: string;
43
+ at: string;
44
+ [key: string]: OkfFrontmatterScalar;
45
+ }
46
+ type OkfVerified = OkfVerifiedEntry[];
47
+ interface OkfSourceUsageWindow {
48
+ from: string;
49
+ to: string;
50
+ [key: string]: OkfFrontmatterScalar;
51
+ }
52
+ interface OkfSource {
53
+ id?: string;
54
+ resource: string;
55
+ title?: string;
56
+ author?: string;
57
+ usage_count?: number;
58
+ last_modified?: string;
59
+ usage_window?: OkfSourceUsageWindow;
60
+ [key: string]: OkfFrontmatterValue | undefined;
61
+ }
62
+ interface OkfFootnote {
63
+ id: string;
64
+ body: string;
65
+ }
33
66
 
67
+ declare function serializeActorString(value: string): string;
34
68
  declare function serializeFrontmatter(fm: OkfFrontmatter): string;
69
+ /**
70
+ * `depth` counts levels of **mapping-value nesting** only: it increments when a
71
+ * flow MAPPING's key has a value that is itself a flow collection, and is
72
+ * rejected once it would exceed 1 (spec §2.6's "at most one level of nesting").
73
+ * It does NOT increment when a flow SEQUENCE's item is itself a mapping or
74
+ * sequence — `sources: [ {...}, {...} ]` (a flat array of objects) is the base
75
+ * shape v0.2 uses everywhere and is not "nesting" in the spec's sense. This is
76
+ * what lets `sources: [ { usage_window: { from, to } } ]` parse (sequence item
77
+ * is a mapping at depth 0; that mapping's `usage_window` value is a mapping at
78
+ * depth 1 — one level, allowed) while `{ a: { b: { c: 1 } } }` is rejected
79
+ * (`a`'s value nests at depth 1; `b`'s value would need depth 2 — rejected).
80
+ */
81
+ declare function parseFlowMapping(text: string, depth?: number): Record<string, OkfFrontmatterValue> | null;
82
+ declare function parseFlowSequence(text: string, depth?: number): OkfFrontmatterValue[] | null;
35
83
  /**
36
84
  * Parses the subset of YAML frontmatter that {@link serializeFrontmatter} produces:
37
85
  * scalar string/number/boolean/null values, quoted strings, and block string-lists.
@@ -105,4 +153,31 @@ declare function isAllowedOkfPath(filePath: string): boolean;
105
153
  */
106
154
  declare function extractMarkdownLinks(body: string): OkfMarkdownLink[];
107
155
 
108
- export { type OkfFile, type OkfFrontmatter, type OkfFrontmatterScalar, type OkfFrontmatterValue, type OkfIndexEntry, type OkfIndexSection, type OkfLogEntry, type OkfMarkdownLink, appendEventIdComment, appendRelatedSection, buildConceptDocument, buildEntityIndexMd, buildIndexMd, buildLogMd, buildRootIndexMd, extractMarkdownLinks, isAllowedOkfPath, parseConcept, parseEntityIndexMd, parseEventIdComment, parseFrontmatter, parseLogMd, parseRootIndexMd, serializeFrontmatter, splitRelatedSection };
156
+ /**
157
+ * Extracts footnote definitions from a body. OKF v0.2 footnote attribution uses
158
+ * `[^id]` markers whose label joins to `sources[].id`. This helper parses the
159
+ * `[^id]: text` definition lines at the body tail and returns them as opaque
160
+ * text — we deliberately do NOT try to interpret which body span uses which
161
+ * footnote; the body is the source of truth and is preserved verbatim on
162
+ * round-trip.
163
+ */
164
+ declare function extractFootnotes(body: string): OkfFootnote[];
165
+ /** Joins footnote definitions back into a body block. Continuation lines are
166
+ * indented (two spaces) so that {@link extractFootnotes} — which only
167
+ * recognizes continuation lines that are indented per CommonMark — round-trips
168
+ * multi-line bodies. */
169
+ declare function serializeFootnotes(footnotes: OkfFootnote[]): string;
170
+
171
+ type TrustTier = 'unverified' | 'machine-confirmed' | 'human-reviewed';
172
+ declare function deriveTrustTier(verified: OkfVerified | undefined): TrustTier;
173
+ declare function isStaleAfter(staleAfter: string | number | null, now: number): boolean;
174
+ declare function parseVerifiedFlexible(value: unknown): OkfVerified;
175
+ declare function formatVerifiedJson(entries: OkfVerified): string;
176
+ declare function formatSourcesJson(sources: OkfSource[], sharedWindow?: OkfSourceUsageWindow | null): string;
177
+ declare function latestVerified(entries: OkfVerified | undefined, _now: number): {
178
+ by: string;
179
+ at: number;
180
+ } | null;
181
+ declare function parseCitationsList(body: string): string[];
182
+
183
+ export { type OkfActorKind, type OkfFile, type OkfFootnote, type OkfFrontmatter, type OkfFrontmatterScalar, type OkfFrontmatterValue, type OkfGenerated, type OkfIndexEntry, type OkfIndexSection, type OkfLogEntry, type OkfMarkdownLink, type OkfSource, type OkfSourceUsageWindow, type OkfStatus, type OkfVerified, type OkfVerifiedEntry, type TrustTier, appendEventIdComment, appendRelatedSection, buildConceptDocument, buildEntityIndexMd, buildIndexMd, buildLogMd, buildRootIndexMd, deriveTrustTier, extractFootnotes, extractMarkdownLinks, formatSourcesJson, formatVerifiedJson, isAllowedOkfPath, isStaleAfter, latestVerified, parseCitationsList, parseConcept, parseEntityIndexMd, parseEventIdComment, parseFlowMapping, parseFlowSequence, parseFrontmatter, parseLogMd, parseRootIndexMd, parseVerifiedFlexible, serializeActorString, serializeFootnotes, serializeFrontmatter, splitRelatedSection };
package/dist/index.js CHANGED
@@ -26,6 +26,10 @@ function quoteString(value) {
26
26
  function serializeScalarString(value) {
27
27
  return needsQuoting(value) ? quoteString(value) : value;
28
28
  }
29
+ function serializeActorString(value) {
30
+ if (/[/:]/.test(value)) return quoteString(value);
31
+ return value;
32
+ }
29
33
  function serializeKey(key) {
30
34
  if (/\s/.test(key) || needsQuoting(key) || isIso8601Timestamp(key)) {
31
35
  return quoteString(key);
@@ -37,8 +41,52 @@ function serializeValue(value) {
37
41
  if (typeof value === "boolean") return String(value);
38
42
  if (typeof value === "number") return String(value);
39
43
  if (typeof value === "string") return serializeScalarString(value);
44
+ if (Array.isArray(value)) return serializeFlowSequenceValue(value);
45
+ if (typeof value === "object") return serializeFlowMappingValue(value);
40
46
  throw new Error(`Unsupported frontmatter value type: ${typeof value}`);
41
47
  }
48
+ function needsFlowQuoting(value) {
49
+ return needsQuoting(value) || /[,{}[\]/:]/.test(value);
50
+ }
51
+ function serializeFlowScalarString(value) {
52
+ return needsFlowQuoting(value) ? quoteString(value) : value;
53
+ }
54
+ function serializeFlowMappingValue(obj, depth = 0) {
55
+ const entries = Object.entries(obj).filter(([, v]) => v !== void 0);
56
+ if (entries.length === 0) return "{}";
57
+ const parts = entries.map(([k, v]) => {
58
+ let rendered;
59
+ if (typeof v === "string") rendered = serializeFlowScalarString(v);
60
+ else if (v === null || typeof v === "boolean" || typeof v === "number") rendered = serializeValue(v);
61
+ else if (Array.isArray(v)) {
62
+ if (depth >= 1) throw new Error(`serializeFlowMappingValue: nesting deeper than one level is not supported (key "${k}")`);
63
+ rendered = serializeFlowSequenceValue(v, depth + 1);
64
+ } else if (typeof v === "object") {
65
+ if (depth >= 1) throw new Error(`serializeFlowMappingValue: nesting deeper than one level is not supported (key "${k}")`);
66
+ rendered = serializeFlowMappingValue(v, depth + 1);
67
+ } else {
68
+ throw new Error(`serializeFlowMappingValue: unsupported value type for key "${k}"`);
69
+ }
70
+ return `${serializeKey(k)}: ${rendered}`;
71
+ });
72
+ return `{ ${parts.join(", ")} }`;
73
+ }
74
+ function serializeFlowSequenceValue(arr, depth = 0) {
75
+ if (arr.length === 0) return "[]";
76
+ const parts = arr.map((item) => {
77
+ if (typeof item === "string") return serializeFlowScalarString(item);
78
+ if (item === null || typeof item === "boolean" || typeof item === "number") return serializeValue(item);
79
+ if (item !== null && typeof item === "object" && !Array.isArray(item)) {
80
+ return serializeFlowMappingValue(item, depth);
81
+ }
82
+ if (Array.isArray(item)) {
83
+ if (depth >= 1) throw new Error(`serializeFlowSequenceValue: nested array deeper than one level is not supported`);
84
+ return serializeFlowSequenceValue(item, depth + 1);
85
+ }
86
+ throw new Error("serializeFlowSequenceValue: unsupported item type");
87
+ });
88
+ return `[ ${parts.join(", ")} ]`;
89
+ }
42
90
  function serializeFrontmatter(fm) {
43
91
  const lines = ["---"];
44
92
  for (const [key, value] of Object.entries(fm)) {
@@ -46,16 +94,16 @@ function serializeFrontmatter(fm) {
46
94
  if (Array.isArray(value)) {
47
95
  if (value.length === 0) {
48
96
  lines.push(`${serializeKey(key)}: []`);
97
+ } else if (value.some((item) => item !== null && typeof item === "object")) {
98
+ lines.push(`${serializeKey(key)}: ${serializeFlowSequenceValue(value)}`);
49
99
  } else {
50
100
  lines.push(`${serializeKey(key)}:`);
51
101
  for (const item of value) {
52
- if (typeof item === "string") {
53
- lines.push(` - ${serializeScalarString(item)}`);
54
- } else {
55
- lines.push(` - ${serializeValue(item)}`);
56
- }
102
+ lines.push(` - ${typeof item === "string" ? serializeScalarString(item) : serializeValue(item)}`);
57
103
  }
58
104
  }
105
+ } else if (value !== null && typeof value === "object") {
106
+ lines.push(`${serializeKey(key)}: ${serializeFlowMappingValue(value)}`);
59
107
  } else {
60
108
  lines.push(`${serializeKey(key)}: ${serializeValue(value)}`);
61
109
  }
@@ -134,6 +182,121 @@ function matchFrontmatterKeyValue(line) {
134
182
  const tail = line.slice(key.length + 1);
135
183
  return { key, value: tail.trimStart(), hasValue: tail.trim().length > 0 };
136
184
  }
185
+ function hasUnquotedAnchorOrAlias(text) {
186
+ let quote = null;
187
+ for (let i = 0; i < text.length; i++) {
188
+ const ch = text[i];
189
+ if (quote) {
190
+ if (ch === "\\" && i + 1 < text.length) {
191
+ i++;
192
+ continue;
193
+ }
194
+ if (ch === quote) quote = null;
195
+ continue;
196
+ }
197
+ if (ch === '"' || ch === "'") {
198
+ quote = ch;
199
+ continue;
200
+ }
201
+ if (ch === "&" || ch === "*") return true;
202
+ }
203
+ return false;
204
+ }
205
+ function splitFlowEntries(inner) {
206
+ const entries = [];
207
+ let buf = "";
208
+ let i = 0;
209
+ let quote = null;
210
+ let depthBrace = 0;
211
+ let depthBracket = 0;
212
+ while (i < inner.length) {
213
+ const ch = inner[i];
214
+ if (quote) {
215
+ buf += ch;
216
+ if (ch === "\\" && i + 1 < inner.length) {
217
+ buf += inner[i + 1];
218
+ i += 2;
219
+ continue;
220
+ }
221
+ if (ch === quote) quote = null;
222
+ i++;
223
+ continue;
224
+ }
225
+ if (ch === '"' || ch === "'") {
226
+ quote = ch;
227
+ buf += ch;
228
+ i++;
229
+ continue;
230
+ }
231
+ if (ch === "{") depthBrace++;
232
+ else if (ch === "}") depthBrace--;
233
+ else if (ch === "[") depthBracket++;
234
+ else if (ch === "]") depthBracket--;
235
+ if (ch === "," && depthBrace === 0 && depthBracket === 0) {
236
+ entries.push(buf.trim());
237
+ buf = "";
238
+ i++;
239
+ continue;
240
+ }
241
+ buf += ch;
242
+ i++;
243
+ }
244
+ if (buf.trim().length > 0) entries.push(buf.trim());
245
+ if (depthBrace !== 0 || depthBracket !== 0 || quote !== null) return null;
246
+ return entries;
247
+ }
248
+ function parseFlowMapping(text, depth = 0) {
249
+ const trimmed = text.trim();
250
+ if (!trimmed.startsWith("{") || !trimmed.endsWith("}")) return null;
251
+ if (hasUnquotedAnchorOrAlias(trimmed)) return null;
252
+ const inner = trimmed.slice(1, -1).trim();
253
+ if (inner.length === 0) return {};
254
+ const entries = splitFlowEntries(inner);
255
+ if (!entries) return null;
256
+ const out = {};
257
+ for (const entry of entries) {
258
+ const kv = matchFrontmatterKeyValue(entry);
259
+ if (!kv || !kv.hasValue) return null;
260
+ const v = kv.value.trim();
261
+ if (v.startsWith("{") || v.startsWith("[")) {
262
+ if (depth >= 1) return null;
263
+ const nested = v.startsWith("{") ? parseFlowMapping(v, depth + 1) : parseFlowSequence(v, depth + 1);
264
+ if (nested === null) return null;
265
+ out[parseKey(kv.key)] = nested;
266
+ continue;
267
+ }
268
+ out[parseKey(kv.key)] = parseScalarValue(v);
269
+ }
270
+ return out;
271
+ }
272
+ function parseFlowSequence(text, depth = 0) {
273
+ const trimmed = text.trim();
274
+ if (!trimmed.startsWith("[") || !trimmed.endsWith("]")) return null;
275
+ if (hasUnquotedAnchorOrAlias(trimmed)) return null;
276
+ const inner = trimmed.slice(1, -1).trim();
277
+ if (inner.length === 0) return [];
278
+ const entries = splitFlowEntries(inner);
279
+ if (!entries) return null;
280
+ const out = [];
281
+ for (const entry of entries) {
282
+ const v = entry.trim();
283
+ if (v.startsWith("{")) {
284
+ const nested = parseFlowMapping(v, depth);
285
+ if (nested === null) return null;
286
+ out.push(nested);
287
+ continue;
288
+ }
289
+ if (v.startsWith("[")) {
290
+ if (depth >= 1) return null;
291
+ const nested = parseFlowSequence(v, depth + 1);
292
+ if (nested === null) return null;
293
+ out.push(nested);
294
+ continue;
295
+ }
296
+ out.push(parseScalarValue(v));
297
+ }
298
+ return out;
299
+ }
137
300
  function parseFrontmatter(content) {
138
301
  const lines = content.split(/\r?\n/);
139
302
  const fallback = { frontmatter: { type: "" }, rest: content };
@@ -171,6 +334,34 @@ function parseFrontmatter(content) {
171
334
  frontmatter[key] = items;
172
335
  continue;
173
336
  }
337
+ const valueRaw = kv.value.trim();
338
+ if (valueRaw.startsWith("{")) {
339
+ const flowObj = parseFlowMapping(valueRaw);
340
+ if (flowObj !== null) {
341
+ frontmatter[parseKey(kv.key)] = flowObj;
342
+ i++;
343
+ continue;
344
+ }
345
+ frontmatter[parseKey(kv.key)] = null;
346
+ i++;
347
+ continue;
348
+ }
349
+ if (valueRaw.startsWith("[")) {
350
+ if (valueRaw === "[]") {
351
+ frontmatter[parseKey(kv.key)] = [];
352
+ i++;
353
+ continue;
354
+ }
355
+ const flowSeq = parseFlowSequence(valueRaw);
356
+ if (flowSeq !== null) {
357
+ frontmatter[parseKey(kv.key)] = flowSeq;
358
+ i++;
359
+ continue;
360
+ }
361
+ frontmatter[parseKey(kv.key)] = null;
362
+ i++;
363
+ continue;
364
+ }
174
365
  frontmatter[parseKey(kv.key)] = parseScalarValue(kv.value);
175
366
  i++;
176
367
  }
@@ -427,6 +618,181 @@ function extractMarkdownLinks(body) {
427
618
  return links;
428
619
  }
429
620
 
621
+ // src/footnotes.ts
622
+ var FOOTNOTE_DEFINITION = /^\[\^([^\]]+)\]:\s?(.*)$/;
623
+ function extractFootnotes(body) {
624
+ const out = [];
625
+ const lines = body.split(/\r?\n/);
626
+ let i = 0;
627
+ while (i < lines.length) {
628
+ const m = FOOTNOTE_DEFINITION.exec(lines[i]);
629
+ if (!m) {
630
+ i++;
631
+ continue;
632
+ }
633
+ const id = m[1];
634
+ const firstLine = m[2] ?? "";
635
+ const collected = [firstLine];
636
+ i++;
637
+ while (i < lines.length) {
638
+ const next = lines[i];
639
+ if (FOOTNOTE_DEFINITION.test(next)) break;
640
+ if (/^\s{2,}\S/.test(next) || /^\s{2,}/.test(next)) {
641
+ collected.push(next.replace(/^\s{2}/, ""));
642
+ i++;
643
+ continue;
644
+ }
645
+ break;
646
+ }
647
+ out.push({ id, body: collected.join("\n").trimEnd() });
648
+ }
649
+ return out;
650
+ }
651
+ function serializeFootnotes(footnotes) {
652
+ return footnotes.map((f) => {
653
+ const lines = f.body.split("\n");
654
+ const [firstLine, ...continuation] = lines;
655
+ const tail = continuation.map((line) => `
656
+ ${line}`).join("");
657
+ return `[^${f.id}]: ${firstLine ?? ""}${tail}`;
658
+ }).join("\n");
659
+ }
660
+
661
+ // src/v02-helpers.ts
662
+ function deriveTrustTier(verified) {
663
+ if (!verified || verified.length === 0) return "unverified";
664
+ for (const v of verified) {
665
+ if (typeof v?.by === "string" && v.by.startsWith("human:")) return "human-reviewed";
666
+ }
667
+ return "machine-confirmed";
668
+ }
669
+ function parseStaleAfter(staleAfter) {
670
+ if (staleAfter == null) return null;
671
+ if (typeof staleAfter === "number") return Number.isFinite(staleAfter) ? staleAfter : null;
672
+ const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(staleAfter);
673
+ if (!match) return null;
674
+ const year = Number(match[1]);
675
+ const month = Number(match[2]);
676
+ const day = Number(match[3]);
677
+ const ts = Date.UTC(year, month - 1, day);
678
+ if (!Number.isFinite(ts)) return null;
679
+ const d = new Date(ts);
680
+ if (d.getUTCFullYear() !== year || d.getUTCMonth() + 1 !== month || d.getUTCDate() !== day) {
681
+ return null;
682
+ }
683
+ return ts;
684
+ }
685
+ function isStaleAfter(staleAfter, now) {
686
+ const cutoff = parseStaleAfter(staleAfter);
687
+ if (cutoff == null) return false;
688
+ return now >= cutoff;
689
+ }
690
+ function parseVerifiedFlexible(value) {
691
+ if (value == null) return [];
692
+ if (Array.isArray(value)) {
693
+ return value.filter(
694
+ (v) => !!v && typeof v === "object" && typeof v.by === "string" && typeof v.at === "string"
695
+ );
696
+ }
697
+ if (typeof value === "object" && typeof value.by === "string" && typeof value.at === "string") {
698
+ return [value];
699
+ }
700
+ return [];
701
+ }
702
+ function formatVerifiedJson(entries) {
703
+ const sorted = [...entries].sort((a, b) => {
704
+ const ta = Date.parse(a.at);
705
+ const tb = Date.parse(b.at);
706
+ const fa = Number.isFinite(ta);
707
+ const fb = Number.isFinite(tb);
708
+ if (fa && fb) return ta - tb;
709
+ if (fa && !fb) return -1;
710
+ if (!fa && fb) return 1;
711
+ return a.at < b.at ? -1 : a.at > b.at ? 1 : 0;
712
+ });
713
+ return JSON.stringify(sorted);
714
+ }
715
+ function formatSourcesJson(sources, sharedWindow) {
716
+ const folded = sources.map(
717
+ (s) => s.usage_window ? s : sharedWindow ? { ...s, usage_window: sharedWindow } : s
718
+ );
719
+ return JSON.stringify(folded);
720
+ }
721
+ function latestVerified(entries, _now) {
722
+ if (!entries || entries.length === 0) return null;
723
+ let best = null;
724
+ let bestAt = -Infinity;
725
+ for (const e of entries) {
726
+ const ts = Date.parse(e.at);
727
+ if (Number.isFinite(ts) && ts > bestAt) {
728
+ best = e;
729
+ bestAt = ts;
730
+ }
731
+ }
732
+ if (!best) return null;
733
+ return { by: best.by, at: bestAt };
734
+ }
735
+ var URL_LINE = /^\s*-\s+(https?:\/\/\S+)\s*$/;
736
+ function parseCitationsList(body) {
737
+ const lines = body.split(/\r?\n/);
738
+ let inFence = false;
739
+ let fenceMarker = "";
740
+ let fenceLength = 0;
741
+ let startIdx = -1;
742
+ const isFenceClose = (marker, fullMatch, line) => {
743
+ if (marker[0] !== fenceMarker) return false;
744
+ if (marker.length < fenceLength) return false;
745
+ return /^\s*$/.test(line.slice(fullMatch.length));
746
+ };
747
+ for (let i = 0; i < lines.length; i++) {
748
+ const line = lines[i];
749
+ const fence = /^\s*(```+|~~~+)/.exec(line);
750
+ if (fence) {
751
+ const marker = fence[1];
752
+ if (!inFence) {
753
+ inFence = true;
754
+ fenceMarker = marker[0];
755
+ fenceLength = marker.length;
756
+ } else if (isFenceClose(marker, fence[0], line)) {
757
+ inFence = false;
758
+ fenceMarker = "";
759
+ fenceLength = 0;
760
+ }
761
+ continue;
762
+ }
763
+ if (!inFence && /^\s*#{1,2}\s*citations\s*$/i.test(line)) {
764
+ startIdx = i;
765
+ break;
766
+ }
767
+ }
768
+ if (startIdx === -1) return [];
769
+ const out = [];
770
+ for (let i = startIdx + 1; i < lines.length; i++) {
771
+ const line = lines[i];
772
+ const fence = /^\s*(```+|~~~+)/.exec(line);
773
+ if (fence) {
774
+ const marker = fence[1];
775
+ if (!inFence) {
776
+ inFence = true;
777
+ fenceMarker = marker[0];
778
+ fenceLength = marker.length;
779
+ continue;
780
+ }
781
+ if (isFenceClose(marker, fence[0], line)) {
782
+ inFence = false;
783
+ fenceMarker = "";
784
+ fenceLength = 0;
785
+ }
786
+ continue;
787
+ }
788
+ if (!inFence && /^\s*#{1,2}\s+/.test(line)) break;
789
+ if (inFence) continue;
790
+ const m = URL_LINE.exec(line);
791
+ if (m) out.push(m[1]);
792
+ }
793
+ return out;
794
+ }
795
+
430
796
  exports.appendEventIdComment = appendEventIdComment;
431
797
  exports.appendRelatedSection = appendRelatedSection;
432
798
  exports.buildConceptDocument = buildConceptDocument;
@@ -434,14 +800,26 @@ exports.buildEntityIndexMd = buildEntityIndexMd;
434
800
  exports.buildIndexMd = buildIndexMd;
435
801
  exports.buildLogMd = buildLogMd;
436
802
  exports.buildRootIndexMd = buildRootIndexMd;
803
+ exports.deriveTrustTier = deriveTrustTier;
804
+ exports.extractFootnotes = extractFootnotes;
437
805
  exports.extractMarkdownLinks = extractMarkdownLinks;
806
+ exports.formatSourcesJson = formatSourcesJson;
807
+ exports.formatVerifiedJson = formatVerifiedJson;
438
808
  exports.isAllowedOkfPath = isAllowedOkfPath;
809
+ exports.isStaleAfter = isStaleAfter;
810
+ exports.latestVerified = latestVerified;
811
+ exports.parseCitationsList = parseCitationsList;
439
812
  exports.parseConcept = parseConcept;
440
813
  exports.parseEntityIndexMd = parseEntityIndexMd;
441
814
  exports.parseEventIdComment = parseEventIdComment;
815
+ exports.parseFlowMapping = parseFlowMapping;
816
+ exports.parseFlowSequence = parseFlowSequence;
442
817
  exports.parseFrontmatter = parseFrontmatter;
443
818
  exports.parseLogMd = parseLogMd;
444
819
  exports.parseRootIndexMd = parseRootIndexMd;
820
+ exports.parseVerifiedFlexible = parseVerifiedFlexible;
821
+ exports.serializeActorString = serializeActorString;
822
+ exports.serializeFootnotes = serializeFootnotes;
445
823
  exports.serializeFrontmatter = serializeFrontmatter;
446
824
  exports.splitRelatedSection = splitRelatedSection;
447
825
  //# sourceMappingURL=index.js.map