hafez 1.2.0 → 1.3.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.
@@ -1,19 +1,23 @@
1
- // src/sections.ts
2
- // Pure string→string functions for Brief and Next Actions section manipulation.
3
- import { ALL_CONTRACTS, SUBTYPE_SECTIONS } from './contracts.js';
1
+ // src/document.ts
2
+ // The Document module (see CONTEXT.md): owns the body of a vault file —
3
+ // structural sections, session-log entries, wiki-links. Absorbs the former
4
+ // sections.ts and parser.ts; every body read or mutation goes through the
5
+ // single structural-heading model defined here. Pure string→string.
6
+ import { ALL_CONTRACTS, SUBTYPE_SECTIONS, SESSION_SECTIONS } from './contracts.js';
4
7
  const UNCHECKED_RE = /^- \[ \] (.+)$/gm;
5
8
  /**
6
- * Known structural section headings in Hafez entities and knowledge notes.
7
- * Derived from contracts.ts (defaultSections + optionalSections) plus
8
- * auto-generated sections not tracked in contracts (Related, Summary).
9
- * Only these are treated as section boundaries. Any other `## ` heading
10
- * inside section content (e.g. in a brief or session log) is preserved as-is.
9
+ * Known structural section headings in Hafez entities, knowledge notes, and
10
+ * session files. Derived from contracts.ts (defaultSections + optionalSections
11
+ * + session sections) plus auto-generated sections not tracked in contracts
12
+ * (Related). Only these are treated as section boundaries. Any other `## `
13
+ * heading inside section content (e.g. in a brief or session log) is
14
+ * preserved as-is.
11
15
  */
12
16
  export const STRUCTURAL_SECTIONS = new Set([
13
17
  ...ALL_CONTRACTS.flatMap(c => [...c.defaultSections, ...c.optionalSections]),
14
18
  ...Object.values(SUBTYPE_SECTIONS).flat(),
19
+ ...SESSION_SECTIONS,
15
20
  'Related', // auto-generated by syncRelatedSection
16
- 'Summary', // used in session files
17
21
  ]);
18
22
  /**
19
23
  * Find the next structural `## Heading` boundary after a given position.
@@ -32,31 +36,176 @@ export function findNextStructuralHeading(body, from) {
32
36
  }
33
37
  return -1;
34
38
  }
35
- // --- Internal helpers ---
36
39
  /**
37
40
  * Find a ## Heading section and its content.
38
- * Returns { start, end, content } or null if not found.
41
+ * Returns { start, headingEnd, end, content } or null if not found.
39
42
  * Matches heading exactly (e.g. "## Brief" won't match "## Briefing").
43
+ * Trailing whitespace on the heading line is tolerated — the splitter's
44
+ * boundary tokenizer trims headings, so anything it treats as a boundary
45
+ * must also be findable here (a hand-edited `## Session Log ` otherwise
46
+ * gets a duplicate section and archival never fires).
40
47
  * Only treats known structural headings as section boundaries -- content
41
48
  * inside a section may contain arbitrary `## ` headings without corruption.
42
49
  */
43
50
  export function findSection(body, heading) {
44
- const re = new RegExp(`(^|\\n)(## ${escapeRegex(heading)})(?:\\n|$)`);
51
+ const re = new RegExp(`(^|\\n)(## ${escapeRegex(heading)}[ \\t]*)(?:\\n|$)`);
45
52
  const match = re.exec(body);
46
53
  if (!match)
47
54
  return null;
48
55
  const start = match.index + match[1].length;
49
- const afterHeading = start + match[2].length + 1;
50
- // Find the next structural ## heading or EOF
51
- const nextHeading = findNextStructuralHeading(body, afterHeading);
56
+ const headingEnd = start + match[2].length;
57
+ const afterHeading = headingEnd + 1;
58
+ // Find the next structural ## heading or EOF. Scan from the heading's own
59
+ // trailing newline (afterHeading - 1), not past it — otherwise a structural
60
+ // heading on the immediately following line (no blank line between, as in
61
+ // hand-edited files) is invisible and mutators swallow the next section.
62
+ const nextHeading = findNextStructuralHeading(body, Math.max(0, afterHeading - 1));
52
63
  const end = nextHeading === -1 ? body.length : nextHeading;
53
64
  const rawContent = body.slice(afterHeading, end);
54
65
  const content = rawContent.replace(/^\n+/, '').replace(/\n+$/, '');
55
- return { start, end, content };
66
+ return { start, headingEnd, end, content };
56
67
  }
57
68
  function escapeRegex(s) {
58
69
  return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
59
70
  }
71
+ /**
72
+ * Split a body into preamble + structural sections. Non-structural `## `
73
+ * headings stay inside the enclosing section's content — sections are
74
+ * atomic units of authored content, never split at user headings.
75
+ * Headings and content come back trimmed; reassemble with
76
+ * `## ${heading}\n\n${content}` joined by blank lines.
77
+ */
78
+ export function splitStructural(body) {
79
+ const marks = [];
80
+ const re = /(^|\n)## ([^\n]*)/g;
81
+ let m;
82
+ while ((m = re.exec(body)) !== null) {
83
+ const heading = m[2].trim();
84
+ if (!STRUCTURAL_SECTIONS.has(heading))
85
+ continue;
86
+ marks.push({ start: m.index + m[1].length, contentStart: m.index + m[0].length, heading });
87
+ }
88
+ if (marks.length === 0)
89
+ return { preamble: body.trim(), sections: [] };
90
+ const preamble = body.slice(0, marks[0].start).trim();
91
+ const sections = marks.map((mk, i) => ({
92
+ heading: mk.heading,
93
+ content: body.slice(mk.contentStart, i + 1 < marks.length ? marks[i + 1].start : body.length).trim(),
94
+ }));
95
+ return { preamble, sections };
96
+ }
97
+ // --- Generic section mutation ---
98
+ /**
99
+ * Replace a structural section's content, or create the section: before
100
+ * `insertBefore` when that section exists, at EOF otherwise.
101
+ */
102
+ export function setSection(body, heading, content, insertBefore) {
103
+ const existing = findSection(body, heading);
104
+ if (existing) {
105
+ return body.slice(0, existing.start) + `## ${heading}\n\n${content}\n` + body.slice(existing.end);
106
+ }
107
+ if (insertBefore) {
108
+ const target = findSection(body, insertBefore);
109
+ if (target) {
110
+ return body.slice(0, target.start) + `## ${heading}\n\n${content}\n\n` + body.slice(target.start);
111
+ }
112
+ }
113
+ return body + `\n\n## ${heading}\n\n${content}\n`;
114
+ }
115
+ /** Append content at the end of a structural section (creating it at EOF if absent). */
116
+ export function appendToSection(body, heading, content) {
117
+ const section = findSection(body, heading);
118
+ if (!section)
119
+ return body + `\n\n## ${heading}\n\n${content}\n`;
120
+ if (section.end === body.length)
121
+ return body + '\n' + content + '\n';
122
+ return body.slice(0, section.end) + '\n' + content + body.slice(section.end);
123
+ }
124
+ /** Insert a formatted session-log entry newest-first (creating the section at EOF if absent). */
125
+ export function prependSessionLogEntry(body, entry) {
126
+ const section = findSection(body, 'Session Log');
127
+ if (!section)
128
+ return body + `\n\n## Session Log\n\n${entry}\n`;
129
+ const insertPos = section.headingEnd;
130
+ return body.slice(0, insertPos) + '\n\n' + entry + '\n' + body.slice(insertPos);
131
+ }
132
+ /** Body up to the real Session Log section — the `summary` read depth. */
133
+ export function bodyBeforeSessionLog(body) {
134
+ const section = findSection(body, 'Session Log');
135
+ return section ? body.slice(0, section.start).trim() : body;
136
+ }
137
+ // --- Session log parsing ---
138
+ const HEADING_RE = /^### (\d{4}-\d{2}-\d{2}) (?:—|--) (.+?)(?:\s+\[(\w+)\])?$/;
139
+ export function parseSessionLogHeading(line) {
140
+ const match = line.match(HEADING_RE);
141
+ if (!match)
142
+ return null;
143
+ return {
144
+ date: match[1],
145
+ agent: match[2],
146
+ type: match[3] || 'progress'
147
+ };
148
+ }
149
+ export function formatSessionLogEntry(entry) {
150
+ const today = new Date().toISOString().slice(0, 10);
151
+ const lines = [`### ${today} — ${entry.agent} [${entry.type}]`, `Summary: ${entry.summary}`];
152
+ if (entry.body)
153
+ lines.push(entry.body);
154
+ return lines.join('\n');
155
+ }
156
+ /** Entries inside the real Session Log section only — entry-shaped lines
157
+ * elsewhere in the body (e.g. quoted in a Brief) do not count. */
158
+ export function countSessionLogEntries(body) {
159
+ const section = findSection(body, 'Session Log');
160
+ if (!section)
161
+ return 0;
162
+ return (section.content.match(/^### \d{4}-\d{2}-\d{2} (?:—|--)/gm) || []).length;
163
+ }
164
+ export function parseSessionLog(body) {
165
+ const section = findSection(body, 'Session Log');
166
+ if (!section)
167
+ return [];
168
+ const entries = [];
169
+ const lines = section.content.split('\n');
170
+ let current = null;
171
+ let summaryLine = null;
172
+ for (const line of lines) {
173
+ const heading = parseSessionLogHeading(line);
174
+ if (heading) {
175
+ if (current && summaryLine) {
176
+ entries.push({ ...current, summary: summaryLine });
177
+ }
178
+ current = heading;
179
+ summaryLine = null;
180
+ }
181
+ else if (current && !summaryLine && line.startsWith('Summary: ')) {
182
+ summaryLine = line.slice('Summary: '.length);
183
+ }
184
+ }
185
+ if (current && summaryLine) {
186
+ entries.push({ ...current, summary: summaryLine });
187
+ }
188
+ return entries;
189
+ }
190
+ export function extractOldestSessionLogEntry(body) {
191
+ const section = findSection(body, 'Session Log');
192
+ if (!section)
193
+ return null;
194
+ // Work on the raw slice between the heading and the section's structural
195
+ // end, so the extracted entry can never swallow a following section
196
+ // (e.g. an auto-appended Related) into the archive.
197
+ const contentStart = section.headingEnd;
198
+ const region = body.slice(contentStart, section.end);
199
+ const entryStarts = [...region.matchAll(/^### \d{4}-\d{2}-\d{2} (?:—|--)/gm)];
200
+ if (entryStarts.length < 2)
201
+ return null;
202
+ // Entries are newest-first in the file, so the oldest is the last match
203
+ const oldestStart = entryStarts[entryStarts.length - 1].index;
204
+ const entry = region.slice(oldestStart).trim();
205
+ const remaining = body.slice(0, contentStart) + region.slice(0, oldestStart) + body.slice(section.end);
206
+ return { entry, remaining };
207
+ }
208
+ // --- Section removal / insertion helpers (Brief, Next Actions) ---
60
209
  /**
61
210
  * Remove a section (heading + content) from body, cleaning up extra whitespace.
62
211
  */
@@ -121,9 +270,9 @@ function insertSectionAfter(body, heading, content, after, beforeFallbacks) {
121
270
  function normalizeForMatch(s) {
122
271
  return s
123
272
  .trim()
124
- .replace(/[\u2018\u2019]/g, "'")
125
- .replace(/[\u201C\u201D]/g, '"')
126
- .replace(/\u2014/g, '-');
273
+ .replace(/[‘’]/g, "'")
274
+ .replace(/[“”]/g, '"')
275
+ .replace(/—/g, '-');
127
276
  }
128
277
  /**
129
278
  * Match an action by case-insensitive substring against unchecked items.
@@ -251,9 +400,11 @@ export function deriveSummary(kind, fm, body) {
251
400
  }
252
401
  return firstNonEmptyLine(findSection(body, 'Summary')?.content);
253
402
  }
254
- // --- Related section functions ---
255
- /** Matches [[slug]] or [[slug|display text]], capturing slug in group 1. Non-global — wrap with `new RegExp(WIKI_LINK_RE.source, 'g')` for iteration. */
256
- export const WIKI_LINK_RE = /\[\[([^\]|]+)(?:\|[^\]]+)?\]\]/;
403
+ // --- Wiki-links ---
404
+ /** The one wiki-link syntax the vault recognizes: [[slug]] or
405
+ * [[slug|display text]] — slug in group 1, display text in group 2.
406
+ * Non-global — wrap with `new RegExp(WIKI_LINK_RE.source, 'g')` for iteration. */
407
+ export const WIKI_LINK_RE = /\[\[([^\]|]+)(?:\|([^\]]+))?\]\]/;
257
408
  export function syncRelatedSection(body, frontmatter) {
258
409
  const targets = [];
259
410
  if (frontmatter.parent)
@@ -7,24 +7,12 @@
7
7
  // regenerated indexes) until the bundle directory is deleted and re-exported.
8
8
  import { readdirSync, writeFileSync, existsSync, mkdirSync } from 'fs';
9
9
  import { join, resolve, sep, basename } from 'path';
10
- import { parseFilePath, serializeFile } from './vault.js';
11
- import { deriveSummary } from './sections.js';
12
- const KIND_DIRS = [
13
- { dir: 'entities', kind: 'entity' },
14
- { dir: 'knowledge', kind: 'knowledge' },
15
- { dir: 'sessions', kind: 'session' },
16
- ];
17
- // Local capturing variant of the shared WIKI_LINK_RE — captures display text in
18
- // group 2, which the shared regex's consumers don't need.
19
- const OKF_LINK_RE = /\[\[([^\]|]+)(?:\|([^\]]+))?\]\]/g;
20
- function scanDir(dirPath) {
21
- try {
22
- return readdirSync(dirPath).filter(f => f.endsWith('.md')).map(f => join(dirPath, f));
23
- }
24
- catch {
25
- return [];
26
- }
27
- }
10
+ import { parseFilePath, serializeFile, scanVaultDir, kindDir } from './vault.js';
11
+ import { deriveSummary, WIKI_LINK_RE } from './document.js';
12
+ const SOURCE_KINDS = ['entity', 'knowledge', 'session'];
13
+ const KIND_DIRS = SOURCE_KINDS.map(kind => ({ dir: kindDir(kind), kind }));
14
+ // The shared wiki-link syntax (document.ts) — group 1 slug, group 2 display.
15
+ const OKF_LINK_RE = new RegExp(WIKI_LINK_RE.source, 'g');
28
16
  function mapFrontmatter(doc) {
29
17
  const { fm, kind, body } = doc;
30
18
  const mapped = {};
@@ -88,7 +76,7 @@ export function exportOkf(vaultPath, outDir) {
88
76
  // Enumerate and parse. Sessions are not in SQLite — disk enumeration is required.
89
77
  const docs = [];
90
78
  for (const { dir, kind } of KIND_DIRS) {
91
- for (const filePath of scanDir(join(resolvedVault, dir))) {
79
+ for (const filePath of scanVaultDir(join(resolvedVault, dir))) {
92
80
  const slug = basename(filePath, '.md');
93
81
  const relFile = `${dir}/${slug}.md`;
94
82
  let fm, body;
@@ -160,9 +148,9 @@ export function exportOkf(vaultPath, outDir) {
160
148
  }
161
149
  // Root index.md — the only place okf_version may appear
162
150
  const counts = [
163
- ['entities', report.entities],
164
- ['knowledge', report.knowledge],
165
- ['sessions', report.sessions],
151
+ [kindDir('entity'), report.entities],
152
+ [kindDir('knowledge'), report.knowledge],
153
+ [kindDir('session'), report.sessions],
166
154
  ];
167
155
  const rootLines = ['# Vault Export', ''];
168
156
  for (const [dir, count] of counts) {
package/dist/git.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { type GitConfig, type ChangelogEntry } from './types.js';
1
+ import { type GitConfig, type ChangelogEntry, type Journal } from './types.js';
2
2
  /**
3
3
  * Bidirectional sync: pull remote changes, push local commits.
4
4
  * All work must be committed before calling — no file save/restore needed.
@@ -8,13 +8,6 @@ export declare function gitSync(vaultPath: string): Promise<{
8
8
  pushed: boolean;
9
9
  remote: boolean;
10
10
  }>;
11
- /**
12
- * Unstage files (best effort). Used by batch rollback: a commit that fails
13
- * after `git add` leaves the new blobs staged in .git/index, and the next
14
- * successful commit would publish the rolled-back content under the wrong
15
- * message. Clearing the index entries restores worktree/HEAD/index agreement.
16
- */
17
- export declare function gitUnstage(vaultPath: string, files: string[]): Promise<void>;
18
11
  export declare function gitChangelog(vaultPath: string, since: string): Promise<ChangelogEntry[]>;
19
12
  /**
20
13
  * Latest git time per file (repo-relative, forward-slash paths). Used to
@@ -24,3 +17,9 @@ export declare function gitChangelog(vaultPath: string, since: string): Promise<
24
17
  */
25
18
  export declare function gitFileTimes(vaultPath: string, mode: 'modified' | 'added'): Promise<Map<string, string>>;
26
19
  export declare function gitCommitAndPush(vaultPath: string, files: string[], message: string, config?: GitConfig): Promise<void>;
20
+ /**
21
+ * The production Journal (see CONTEXT.md): vault persistence backed by git.
22
+ * Local-only vaults (no origin remote) commit silently without pushing —
23
+ * that behavior lives in gitCommitAndPush/gitSync, inside this adapter.
24
+ */
25
+ export declare function createGitJournal(vaultPath: string, config?: GitConfig): Journal;
package/dist/git.js CHANGED
@@ -3,6 +3,7 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
3
3
  import { dirname, join } from 'node:path';
4
4
  import { simpleGit } from 'simple-git';
5
5
  import { mergeVaultContent } from './merge.js';
6
+ import { VAULT_FILE_RE, INDEXED_DIR_PATHSPECS, classifyIndexedRelPath } from './vault.js';
6
7
  import { HafezError } from './types.js';
7
8
  const logMerge = (msg) => process.stderr.write(`[hafez-merge] ${msg}\n`);
8
9
  const MAX_PUSH_RETRIES = 3;
@@ -40,7 +41,6 @@ async function ensureCleanState(git, vaultPath) {
40
41
  }
41
42
  }
42
43
  }
43
- const VAULT_FILE_RE = /^(?:entities|knowledge|sessions)\/.+\.md$/;
44
44
  /**
45
45
  * True if the vault repo has an `origin` remote. A local-only vault (the
46
46
  * default the README setup produces) has none — every pull/push must be
@@ -253,12 +253,13 @@ export async function gitSync(vaultPath) {
253
253
  return { pulled, pushed: true, remote: true };
254
254
  }
255
255
  /**
256
- * Unstage files (best effort). Used by batch rollback: a commit that fails
257
- * after `git add` leaves the new blobs staged in .git/index, and the next
258
- * successful commit would publish the rolled-back content under the wrong
259
- * message. Clearing the index entries restores worktree/HEAD/index agreement.
256
+ * Unstage files (best effort). Used by the journal adapter's commit-failure
257
+ * repair: a commit that fails after `git add` leaves the new blobs staged in
258
+ * .git/index, and the next successful commit would publish rolled-back
259
+ * content under the wrong message. Clearing the index entries restores
260
+ * worktree/HEAD/index agreement.
260
261
  */
261
- export async function gitUnstage(vaultPath, files) {
262
+ async function gitUnstage(vaultPath, files) {
262
263
  try {
263
264
  await simpleGit(vaultPath).raw(['reset', '-q', 'HEAD', '--', ...files]);
264
265
  }
@@ -290,7 +291,7 @@ export async function gitChangelog(vaultPath, since) {
290
291
  try {
291
292
  output = await git.raw([
292
293
  'log', `--since=${normalizedSince}`, '--name-status',
293
- '--pretty=format:%H|%aI|%s', '--', 'entities/', 'knowledge/',
294
+ '--pretty=format:%H|%aI|%s', '--', ...INDEXED_DIR_PATHSPECS,
294
295
  ]);
295
296
  }
296
297
  catch {
@@ -320,20 +321,10 @@ export async function gitChangelog(vaultPath, since) {
320
321
  // dirs and would otherwise surface as changelog rows.
321
322
  if (!filePath.endsWith('.md'))
322
323
  continue;
323
- let kind;
324
- let slug;
325
- if (filePath.startsWith('entities/')) {
326
- kind = 'entity';
327
- slug = filePath.replace('entities/', '').replace('.md', '');
328
- }
329
- else if (filePath.startsWith('knowledge/')) {
330
- kind = 'knowledge';
331
- slug = filePath.replace('knowledge/', '').replace('.md', '');
332
- }
333
- else {
324
+ const classified = classifyIndexedRelPath(filePath);
325
+ if (!classified)
334
326
  continue;
335
- }
336
- entries.push({ slug, kind, operation, timestamp, commit_message });
327
+ entries.push({ slug: classified.slug, kind: classified.kind, operation, timestamp, commit_message });
337
328
  }
338
329
  }
339
330
  return entries;
@@ -348,7 +339,7 @@ export async function gitFileTimes(vaultPath, mode) {
348
339
  const git = simpleGit(vaultPath);
349
340
  let output;
350
341
  try {
351
- const args = ['log', '--pretty=format:%x00%aI', '--name-only', '--', 'entities/', 'knowledge/'];
342
+ const args = ['log', '--pretty=format:%x00%aI', '--name-only', '--', ...INDEXED_DIR_PATHSPECS];
352
343
  if (mode === 'added')
353
344
  args.splice(1, 0, '--diff-filter=A');
354
345
  output = await git.raw(args);
@@ -405,3 +396,30 @@ export async function gitCommitAndPush(vaultPath, files, message, config = {}) {
405
396
  return;
406
397
  await pushResolving(git, vaultPath, (conflictFiles) => `Git conflict with remote (files: ${conflictFiles.join(', ')}). Changes saved locally.`, `Git push failed after ${MAX_PUSH_RETRIES} attempts. Changes saved locally.`);
407
398
  }
399
+ /**
400
+ * The production Journal (see CONTEXT.md): vault persistence backed by git.
401
+ * Local-only vaults (no origin remote) commit silently without pushing —
402
+ * that behavior lives in gitCommitAndPush/gitSync, inside this adapter.
403
+ */
404
+ export function createGitJournal(vaultPath, config = {}) {
405
+ return {
406
+ async commit(written, deleted, message) {
407
+ const files = [...written, ...deleted];
408
+ try {
409
+ await gitCommitAndPush(vaultPath, files, message, config);
410
+ }
411
+ catch (err) {
412
+ // Commit-stage failure: nothing landed, but `git add` may have staged
413
+ // the new blobs — clean up our own partial state before rethrowing.
414
+ // Push-stage failure keeps the local commit (GIT_PUSH_FAILED).
415
+ if (!(err instanceof HafezError && err.code === 'GIT_PUSH_FAILED')) {
416
+ await gitUnstage(vaultPath, files);
417
+ }
418
+ throw err;
419
+ }
420
+ },
421
+ sync: () => gitSync(vaultPath),
422
+ changelog: (since) => gitChangelog(vaultPath, since),
423
+ fileTimes: (mode) => gitFileTimes(vaultPath, mode),
424
+ };
425
+ }
package/dist/index.d.ts CHANGED
@@ -1,7 +1,6 @@
1
1
  import type { Hafez, HafezConfig } from './types.js';
2
2
  export declare function createHafez(config: HafezConfig): Hafez;
3
3
  export { HafezError } from './types.js';
4
- export type { Hafez, HafezConfig, HafezErrorCode, EntityType, EntityStatus, EntityQueryOpts, KnowledgeQueryOpts, EntityFrontmatter, KnowledgeFrontmatter, ParsedFile, SessionLogEntry, UpdateFields, UpdateResult, CreateEntityFields, CreateKnowledgeFields, QueryFilter, QueryResult, KnowledgeQueryResult, UnifiedResult, ValidationReport, BatchOperation, BatchResult, ReadDepth, ConfidenceLevel, LinkRelation, GitConfig, SearchResult, VaultStats, ChangelogEntry, KnowledgeSubtype } from './types.js';
5
- export { getBrief, getNextActions, findSection } from './sections.js';
6
- export { parseSessionLogHeading, parseSessionLog } from './parser.js';
7
- export { formatStats, formatChangelog, formatValidation } from './cli/format.js';
4
+ export type { Hafez, HafezConfig, HafezErrorCode, EntityType, EntityStatus, EntityQueryOpts, KnowledgeQueryOpts, EntityFrontmatter, KnowledgeFrontmatter, ParsedFile, SessionLogEntry, UpdateFields, UpdateResult, CreateEntityFields, CreateKnowledgeFields, QueryFilter, QueryResult, KnowledgeQueryResult, UnifiedResult, ValidationReport, BatchOperation, BatchResult, ReadDepth, ConfidenceLevel, LinkRelation, GitConfig, Journal, SearchResult, VaultStats, ChangelogEntry, KnowledgeSubtype } from './types.js';
5
+ export { getBrief, getNextActions, findSection } from './document.js';
6
+ export { parseSessionLogHeading, parseSessionLog } from './document.js';