openwriter 0.18.0 → 0.19.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.
@@ -129,12 +129,12 @@ export function listDocuments() {
129
129
  // tag overlay from one HTTP round-trip instead of N. The server already
130
130
  // has the parsed frontmatter in hand here; emitting tags is free.
131
131
  ...(Array.isArray(data.tags) && data.tags.length > 0 ? { tags: data.tags } : {}),
132
- // Enrichment fields — also free at this point since data is in hand.
133
- // See brief 2026-05-18-frontmatter-enrichment-system.
132
+ // Enrichment fields — free at this point since data is in hand.
133
+ // v0.19.0 schema: only logline (LLM) + status (agent) + enrichmentStale
134
+ // (system) are surfaced. domain / concepts / docRole stayed on disk for
135
+ // legacy docs but are no longer read (lazy migration via mark_enriched).
136
+ // See brief 2026-05-21-simplify-enrichment-schema-three-fields.
134
137
  ...(typeof data.logline === 'string' && data.logline ? { logline: data.logline } : {}),
135
- ...(typeof data.domain === 'string' && data.domain ? { domain: data.domain } : {}),
136
- ...(Array.isArray(data.concepts) && data.concepts.length > 0 ? { concepts: data.concepts } : {}),
137
- ...(typeof data.docRole === 'string' && data.docRole ? { docRole: data.docRole } : {}),
138
138
  ...(typeof data.status === 'string' && data.status ? { status: data.status } : {}),
139
139
  ...(data.enrichmentStale === true ? { enrichmentStale: true } : {}),
140
140
  };
@@ -257,13 +257,15 @@ export function listArchivedDocuments() {
257
257
  // ============================================================================
258
258
  /** One-line footer the high-frequency MCP discovery tools (list_documents,
259
259
  * list_workspaces, get_workspace_structure) append when dirty docs exist.
260
- * Constant pressure that doesn't require hook setup. The agent's openwriter
261
- * skill teaches: see this footer spawn the enrichment minion. */
260
+ * Constant pressure that doesn't require hook setup. The footer includes
261
+ * the exact dispatch call so the acting agent's burden collapses to one
262
+ * paste — the minion is orchestrator-mode by default and self-discovers
263
+ * via list_dirty_docs once it boots. */
262
264
  export function enrichmentFooter() {
263
265
  const count = listDirtyDocs().length;
264
266
  if (count === 0)
265
267
  return '';
266
- return `\n\n⚠ ${count} doc${count === 1 ? '' : 's'} need enrichment spawn the enrichment minion (see openwriter skill docs/enrichment.md)`;
268
+ return `\n\n⚠ ${count} doc${count === 1 ? '' : 's'} need enrichment. Dispatch:\nAgent(\n subagent_type: "openwriter-enrichment-minion",\n description: "Drain stale docs",\n prompt: "Enrich stale docs.",\n run_in_background: true\n)`;
267
269
  }
268
270
  /** Session-start enrichment notice injected into the MCP server's
269
271
  * `instructions` field at boot. Every MCP client (Claude Code, Cursor, etc.)
@@ -292,8 +294,13 @@ export function buildEnrichmentInstructions() {
292
294
  return [
293
295
  '',
294
296
  `ENRICHMENT_STATUS: ${dirty.length} doc${dirty.length === 1 ? '' : 's'} need enrichment (${parts.join(', ')}).`,
295
- 'Spawn the enrichment minion using the openwriter skill\'s docs/enrichment.md as its system prompt before responding to the user\'s request.',
296
- 'The minion calls list_dirty_docs, enriches each via Haiku, calls mark_enriched, returns done.',
297
+ 'Dispatch the minion now it self-discovers via list_dirty_docs and self-bounds the batch:',
298
+ 'Agent(',
299
+ ' subagent_type: "openwriter-enrichment-minion",',
300
+ ' description: "Drain stale docs",',
301
+ ' prompt: "Enrich stale docs.",',
302
+ ' run_in_background: true',
303
+ ')',
297
304
  ].join('\n');
298
305
  }
299
306
  /** Build a Set of filenames inside workspaces with enrichmentDisabled: true.
@@ -418,10 +425,11 @@ export function crawlDocs(filter = {}) {
418
425
  const { data, content } = matter(raw);
419
426
  if (data.archivedAt)
420
427
  continue;
421
- // Apply filters
422
- if (filter.domain && data.domain !== filter.domain)
423
- continue;
424
- if (filter.docRole && data.docRole !== filter.docRole)
428
+ // Apply filters. v0.19.0 schema: status (agent-owned) replaces
429
+ // docRole / domain / concepts. Legacy fields still on disk are
430
+ // ignored by both filtering and output — they retire as docs get
431
+ // re-enriched. See brief 2026-05-21-simplify-enrichment-schema-three-fields.
432
+ if (filter.status && data.status !== filter.status)
425
433
  continue;
426
434
  if (filter.hasLogline === true && !data.logline)
427
435
  continue;
@@ -432,11 +440,6 @@ export function crawlDocs(filter = {}) {
432
440
  if (!filter.tags.every((t) => docTags.includes(t)))
433
441
  continue;
434
442
  }
435
- if (filter.concepts && filter.concepts.length > 0) {
436
- const docConcepts = Array.isArray(data.concepts) ? data.concepts : [];
437
- if (!filter.concepts.every((c) => docConcepts.includes(c)))
438
- continue;
439
- }
440
443
  const trimmed = content.trim();
441
444
  out.push({
442
445
  docId: data.docId || '',
@@ -444,10 +447,7 @@ export function crawlDocs(filter = {}) {
444
447
  title: data.title || f.replace(/\.md$/, ''),
445
448
  wordCount: trimmed ? trimmed.split(/\s+/).length : 0,
446
449
  ...(typeof data.logline === 'string' && data.logline ? { logline: data.logline } : {}),
447
- ...(typeof data.domain === 'string' && data.domain ? { domain: data.domain } : {}),
448
450
  ...(Array.isArray(data.tags) && data.tags.length > 0 ? { tags: data.tags } : {}),
449
- ...(Array.isArray(data.concepts) && data.concepts.length > 0 ? { concepts: data.concepts } : {}),
450
- ...(typeof data.docRole === 'string' && data.docRole ? { docRole: data.docRole } : {}),
451
451
  ...(typeof data.status === 'string' && data.status ? { status: data.status } : {}),
452
452
  ...(data.enrichmentStale === true ? { enrichmentStale: true } : {}),
453
453
  });
@@ -363,7 +363,7 @@ export const TOOL_REGISTRY = [
363
363
  },
364
364
  {
365
365
  name: 'list_documents',
366
- description: 'List all documents. Shows title, docId, word count, last modified, active flag, and enrichment fields (logline, domain, docRole) when present. Use the docId to target documents in other tools.',
366
+ description: 'List all documents. Shows title, docId, word count, last modified, active flag, and enrichment fields (logline, status, STALE marker) when present. Use the docId to target documents in other tools. v0.19.0: three-field enrichment schema — logline (LLM), status (agent: canonical / draft), STALE (system).',
367
367
  schema: {},
368
368
  handler: async () => {
369
369
  const docs = listDocuments();
@@ -372,10 +372,10 @@ export const TOOL_REGISTRY = [
372
372
  const id = d.docId ? ` [${d.docId}]` : '';
373
373
  const date = d.lastModified.split('T')[0];
374
374
  const enrichBits = [];
375
- if (d.domain)
376
- enrichBits.push(d.domain);
377
- if (d.docRole)
378
- enrichBits.push(d.docRole);
375
+ // v0.19.0: only canonical surfaces — draft is the default and would
376
+ // clutter the listing on every doc.
377
+ if (d.status === 'canonical')
378
+ enrichBits.push('canonical');
379
379
  if (d.enrichmentStale === true)
380
380
  enrichBits.push('STALE');
381
381
  const enrichTag = enrichBits.length > 0 ? ` (${enrichBits.join(', ')})` : '';
@@ -415,8 +415,9 @@ export const TOOL_REGISTRY = [
415
415
  content_type: z.enum(['document', 'tweet', 'reply', 'quote', 'article', 'linkedin', 'newsletter', 'blog']).describe('Required. Use "document" for plain documents. Tweet/reply/quote/article/linkedin/newsletter/blog set type-specific metadata automatically.'),
416
416
  url: z.string().optional().describe('Tweet URL — REQUIRED for content_type "reply" or "quote" (e.g. "https://x.com/user/status/123"). Sets tweetContext.url automatically. Ignored for other content types.'),
417
417
  afterId: z.string().optional().describe('Place the new doc immediately after this docId (8-char hex) or containerId inside its parent. Omit to append to the bottom of the parent (the default — matches ascending-order convention: newest at bottom). Requires workspace.'),
418
+ status: z.enum(['canonical', 'draft']).optional().describe('Agent-owned lifecycle. "canonical" = committed to spine / load-bearing for the workspace (use for Beats docs that have locked, Research Notes, Master References). "draft" = working / not load-bearing yet / scratch (DUMP docs, first-pass beats). Defaults to "draft" when omitted. Change later via set_metadata({ status: ... }) on lifecycle transitions. v0.19.0.'),
418
419
  },
419
- handler: async ({ title, path, workspace, container, empty, content_type, url, afterId }) => {
420
+ handler: async ({ title, path, workspace, container, empty, content_type, url, afterId, status }) => {
420
421
  // Require url for reply/quote
421
422
  if ((content_type === 'reply' || content_type === 'quote') && !url) {
422
423
  return { content: [{ type: 'text', text: `Error: content_type "${content_type}" requires a url parameter (e.g. "https://x.com/user/status/123").` }] };
@@ -444,18 +445,25 @@ export const TOOL_REGISTRY = [
444
445
  // Track the spinner key so catch can clear exactly this entry
445
446
  // (not siblings from a concurrent declare_writes).
446
447
  let spinnerKey = null;
448
+ // v0.19.0: agent-owned status. Defaults to "draft" when not supplied —
449
+ // canonical is reserved for docs that have committed to the workspace
450
+ // spine (Beats, Research Notes, Master References). Agent flips to
451
+ // canonical via set_metadata({ status: "canonical" }) on lifecycle
452
+ // transitions. See brief 2026-05-21-simplify-enrichment-schema-three-fields.
453
+ const statusMeta = { status: status ?? 'draft' };
447
454
  try {
448
455
  if (empty) {
449
456
  // Immediate switch — no spinner, no populate_document needed
450
457
  const result = createDocument(title, undefined, path);
451
458
  setAgentLock(result.filename);
452
- // Apply type-specific metadata
459
+ // Apply status + type-specific metadata in one merge
460
+ const initMeta = { ...statusMeta };
453
461
  if (content_type) {
454
462
  const typeMeta = resolveTypeMeta(content_type, url);
455
- if (typeMeta) {
456
- setMetadata(typeMeta);
457
- }
463
+ if (typeMeta)
464
+ Object.assign(initMeta, typeMeta);
458
465
  }
466
+ setMetadata(initMeta);
459
467
  let wsInfo = '';
460
468
  if (wsTarget) {
461
469
  // Resolve afterId: it may be a docId (8-char hex) or containerId.
@@ -478,8 +486,11 @@ export const TOOL_REGISTRY = [
478
486
  }
479
487
  // Two-step flow: create file on disk WITHOUT switching the user's view.
480
488
  // The spinner persists in the sidebar until populate_document is called.
489
+ // Merge status with any content-type metadata so it lands on the first
490
+ // disk write.
481
491
  const typeMeta = content_type ? resolveTypeMeta(content_type, url) : undefined;
482
- const result = createDocumentFile(title, path, typeMeta);
492
+ const initialMeta = { ...statusMeta, ...(typeMeta || {}) };
493
+ const result = createDocumentFile(title, path, initialMeta);
483
494
  let wsInfo = '';
484
495
  if (wsTarget) {
485
496
  const afterRef = afterId ? (filenameByDocId(afterId) ?? afterId) : null;
@@ -733,7 +744,7 @@ export const TOOL_REGISTRY = [
733
744
  },
734
745
  {
735
746
  name: 'set_metadata',
736
- description: 'Update frontmatter metadata on a document. Merges with existing metadata — only provided keys are changed. Use for summaries, character lists, tags, arc notes, or any organizational data. Saves to disk immediately.',
747
+ description: 'Update frontmatter metadata on a document. Merges with existing metadata — only provided keys are changed. Use for summaries, character lists, tags, arc notes, or any organizational data. Saves to disk immediately. Lifecycle convention (v0.19.0): use `set_metadata({ status: "canonical" })` when a doc commits to the workspace spine (Beats locks, Research Note becomes load-bearing); use `set_metadata({ status: "draft" })` when a doc is superseded or demoted. Status is the agent\'s field — the enrichment minion never writes it.',
737
748
  schema: {
738
749
  docId: z.string().describe('Target document by docId (8-char hex from list_documents).'),
739
750
  metadata: z.record(z.any()).describe('Key-value pairs to merge into frontmatter. Set a key to null to remove it.'),
@@ -801,16 +812,12 @@ export const TOOL_REGISTRY = [
801
812
  },
802
813
  {
803
814
  name: 'mark_enriched',
804
- description: 'Mark one or more documents as freshly enriched. Stamps openwriter-maintained baselines (lastEnrichedAt, lastEnrichedCharCount, lastEnrichedSentences) atomically with the supplied enrichment fields, and clears enrichmentStale. The agent never touches the sentence-hash layer — openwriter computes the baseline from current canonical content. Accepts an array so a workspace-wide sweep is one call. See brief 2026-05-18-frontmatter-enrichment-system.',
815
+ description: 'Mark one or more documents as freshly enriched. Stamps openwriter-maintained baselines (lastEnrichedAt, lastEnrichedCharCount, lastEnrichedSentences) atomically with the supplied logline, clears enrichmentStale, and retires legacy enrichment fields (domain, concepts, docRole, and any LLM-written status). The agent never touches the sentence-hash layer — openwriter computes the baseline from current canonical content. Accepts an array so a workspace-wide sweep is one call. Schema simplified in v0.19.0: only logline is LLM-written; status is now agent-owned via create_document / set_metadata; domain / concepts / docRole are gone. See brief 2026-05-21-simplify-enrichment-schema-three-fields.',
805
816
  schema: {
806
817
  docs: z.array(z.object({
807
818
  docId: z.string().describe('Target document by docId (8-char hex from list_documents).'),
808
- logline: z.string().optional().describe('Précis (non-fiction) or logline (fiction). Under 250 chars. Describe the content, not the kind of doc.'),
809
- domain: z.string().optional().describe('Single domain classification from the workspace vocab.'),
810
- concepts: z.array(z.string()).optional().describe('Named concepts the doc references.'),
811
- docRole: z.string().optional().describe('Doc role: canonical / vignette / reference / draft / chapter / beat.'),
812
- status: z.string().optional().describe('Doc status: draft / canonical / stale. Archive state is implied by archivedAt.'),
813
- })).describe('One or more docs to mark enriched. Single-doc calls are a length-1 array.'),
819
+ logline: z.string().max(150).describe('Précis (non-fiction) or logline (fiction). Under 150 chars. Describe the content, not the kind of doc.'),
820
+ }).strict()).describe('One or more docs to mark enriched. Single-doc calls are a length-1 array. Strict schema — passing domain / concepts / docRole / status will fail validation (v0.19.0 schema simplification).'),
814
821
  },
815
822
  handler: async ({ docs }) => {
816
823
  const now = new Date().toISOString();
@@ -828,23 +835,19 @@ export const TOOL_REGISTRY = [
828
835
  const blocks = tiptapToBlocks(canonical);
829
836
  const lastEnrichedSentences = harvestSentenceHashes(blocks);
830
837
  const lastEnrichedCharCount = harvestCharCount(blocks);
831
- // Build the atomic enrichment payload.
838
+ // Build the atomic enrichment payload. v0.19.0: only logline is
839
+ // LLM-written. The legacy fields (domain / concepts / docRole) get
840
+ // retired on this write — `LEGACY_FIELDS_TO_RETIRE` is deleted from
841
+ // the merged metadata so disk slowly converges to the new schema
842
+ // as each doc gets re-enriched (lazy migration path from the brief).
832
843
  const update = {
833
844
  lastEnrichedAt: now,
834
845
  lastEnrichedCharCount,
835
846
  lastEnrichedSentences,
836
847
  enrichmentStale: false,
848
+ logline: item.logline,
837
849
  };
838
- if (item.logline !== undefined)
839
- update.logline = item.logline;
840
- if (item.domain !== undefined)
841
- update.domain = item.domain;
842
- if (item.concepts !== undefined)
843
- update.concepts = item.concepts;
844
- if (item.docRole !== undefined)
845
- update.docRole = item.docRole;
846
- if (item.status !== undefined)
847
- update.status = item.status;
850
+ const LEGACY_FIELDS_TO_RETIRE = ['domain', 'concepts', 'docRole'];
848
851
  if (target.isActive) {
849
852
  // Active doc: setMetadata mutates state.metadata but doesn't bump
850
853
  // docVersion on its own — without an explicit bump, save() would
@@ -853,6 +856,9 @@ export const TOOL_REGISTRY = [
853
856
  // writeToDisk's staleness check will see the just-stamped baseline
854
857
  // (volumeRatio=1, drift=0) and NOT flip the flag back to true.
855
858
  setMetadata(update);
859
+ const liveMeta = getMetadata();
860
+ for (const k of LEGACY_FIELDS_TO_RETIRE)
861
+ delete liveMeta[k];
856
862
  bumpDocVersion();
857
863
  save();
858
864
  broadcastMetadataChanged(getMetadata());
@@ -863,6 +869,8 @@ export const TOOL_REGISTRY = [
863
869
  // serialize cycle before the new baseline lands). Disk write +
864
870
  // cache invalidation mirrors set_metadata's non-active path.
865
871
  const newMeta = { ...target.metadata, ...update };
872
+ for (const k of LEGACY_FIELDS_TO_RETIRE)
873
+ delete newMeta[k];
866
874
  const markdown = tiptapToMarkdown(target.document, target.title, newMeta);
867
875
  atomicWriteFileSync(target.filePath, markdown);
868
876
  invalidateDocCache(target.filePath);
@@ -896,13 +904,11 @@ export const TOOL_REGISTRY = [
896
904
  },
897
905
  {
898
906
  name: 'crawl',
899
- description: 'Bulk-read enriched fields per doc, filtered by criteria. The crawl primitive — agents use this to scan the workspace shelf at concept level (~150 tokens/doc) and decide which bodies to actually read. Filters compose with AND semantics. Empty filter returns every non-archived doc. No bodies, no nodes, no pending overlay.',
907
+ description: 'Bulk-read enriched fields per doc, filtered by criteria. The crawl primitive — agents use this to scan the workspace shelf at concept level (~60 tokens/doc) and decide which bodies to actually read. Filters compose with AND semantics. Empty filter returns every non-archived doc. No bodies, no nodes, no pending overlay. v0.19.0 schema: status (canonical / draft) replaces docRole / domain / concepts filters — those legacy filters were dropped because the fields they queried had no authority discipline. See brief 2026-05-21-simplify-enrichment-schema-three-fields.',
900
908
  schema: {
901
909
  workspaceFile: z.string().optional().describe('Scope to one workspace.'),
902
- domain: z.string().optional().describe('Exact domain match.'),
903
910
  tags: z.array(z.string()).optional().describe('Docs must have ALL listed tags.'),
904
- concepts: z.array(z.string()).optional().describe('Docs must reference ALL listed concepts.'),
905
- docRole: z.string().optional().describe('Exact docRole match (canonical / vignette / reference / draft / chapter / beat).'),
911
+ status: z.enum(['canonical', 'draft']).optional().describe('Agent-owned lifecycle filter. "canonical" returns the trusted-shelf docs (load-bearing for the workspace); "draft" returns working / superseded / scratch docs. The common crawl is `status: canonical`.'),
906
912
  hasLogline: z.boolean().optional().describe('True = only docs with a logline; false = only docs without one.'),
907
913
  },
908
914
  handler: async (filter) => {
@@ -953,7 +959,7 @@ export const TOOL_REGISTRY = [
953
959
  },
954
960
  {
955
961
  name: 'get_workspace_structure',
956
- description: 'Get the full structure of a workspace: tree of containers and docs, per-doc enrichment (logline, domain, tags, docRole, stale flag), plus workspace-level context (characters, settings, rules) and enrichment metadata (schema, vocab, logline). Use to understand a workspace at concept level before reading bodies.',
962
+ description: 'Get the full structure of a workspace: tree of containers and docs, per-doc enrichment (logline, status, tags, stale flag), plus workspace-level context (characters, settings, rules) and enrichment metadata (schema, vocab, logline). Use to understand a workspace at concept level before reading bodies. v0.19.0: enrichment fields shown per-doc are logline (LLM-owned), status (agent-owned: canonical / draft), tags, and the STALE marker (system-owned).',
957
963
  schema: {
958
964
  filename: z.string().describe('Workspace manifest filename (e.g. "my-novel-a1b2c3d4.json")'),
959
965
  },
@@ -970,10 +976,11 @@ export const TOOL_REGISTRY = [
970
976
  const e = enrichByFile.get(node.file);
971
977
  const tags = e?.tags ?? [];
972
978
  const enrichBits = [];
973
- if (e?.domain)
974
- enrichBits.push(e.domain);
975
- if (e?.docRole)
976
- enrichBits.push(e.docRole);
979
+ // v0.19.0: status (agent-owned) replaces domain + docRole.
980
+ // Only "canonical" is worth surfacing — draft is the default
981
+ // and would add noise on every line.
982
+ if (e?.status === 'canonical')
983
+ enrichBits.push('canonical');
977
984
  if (tags.length > 0)
978
985
  enrichBits.push(`tags: ${tags.join(', ')}`);
979
986
  if (e?.enrichmentStale === true)
@@ -1023,7 +1030,7 @@ export const TOOL_REGISTRY = [
1023
1030
  },
1024
1031
  {
1025
1032
  name: 'get_item_context',
1026
- description: 'Get progressive-disclosure context for a document: workspace-level context (characters, settings, rules, vocab), the doc\'s own enrichment (logline, domain, concepts, docRole, status), tags, and the enrichmentStale flag. Use before writing to understand context, or before reading to decide whether a body read is necessary.',
1033
+ description: 'Get progressive-disclosure context for a document: workspace-level context (characters, settings, rules, vocab), the doc\'s own enrichment (logline, status), tags, and the enrichmentStale flag. Use before writing to understand context, or before reading to decide whether a body read is necessary. v0.19.0: returns the three-field enrichment schema — logline (LLM), status (agent), enrichmentStale (system).',
1027
1034
  schema: {
1028
1035
  workspaceFile: z.string().describe('Workspace manifest filename'),
1029
1036
  docId: z.string().describe('Document docId (8-char hex from list_documents)'),
@@ -1039,14 +1046,10 @@ export const TOOL_REGISTRY = [
1039
1046
  const enriched = crawlDocs({ workspaceFile });
1040
1047
  const docEnrich = enriched.find((e) => e.filename === filename);
1041
1048
  if (docEnrich) {
1049
+ // v0.19.0 three-field schema: logline (LLM), status (agent),
1050
+ // enrichmentStale (system). domain / concepts / docRole dropped.
1042
1051
  if (docEnrich.logline)
1043
1052
  base.logline = docEnrich.logline;
1044
- if (docEnrich.domain)
1045
- base.domain = docEnrich.domain;
1046
- if (docEnrich.concepts)
1047
- base.concepts = docEnrich.concepts;
1048
- if (docEnrich.docRole)
1049
- base.docRole = docEnrich.docRole;
1050
1053
  if (docEnrich.status)
1051
1054
  base.status = docEnrich.status;
1052
1055
  if (docEnrich.enrichmentStale === true)
@@ -1811,20 +1814,18 @@ export const TOOL_REGISTRY = [
1811
1814
  const enriched = raw.slice(0, cap).map((r) => {
1812
1815
  let docId = null;
1813
1816
  let logline;
1814
- let domain;
1815
- let docRole;
1817
+ let status;
1816
1818
  let tags;
1817
1819
  try {
1818
1820
  const filePath = resolveDocPath(r.filename);
1819
1821
  const fileRaw = readFileSync(filePath, 'utf-8');
1820
1822
  const fm = matter(fileRaw);
1821
1823
  docId = fm.data?.docId || null;
1824
+ // v0.19.0 three-field schema: logline (LLM), status (agent), tags.
1822
1825
  if (typeof fm.data?.logline === 'string')
1823
1826
  logline = fm.data.logline;
1824
- if (typeof fm.data?.domain === 'string')
1825
- domain = fm.data.domain;
1826
- if (typeof fm.data?.docRole === 'string')
1827
- docRole = fm.data.docRole;
1827
+ if (typeof fm.data?.status === 'string')
1828
+ status = fm.data.status;
1828
1829
  if (Array.isArray(fm.data?.tags) && fm.data.tags.length > 0)
1829
1830
  tags = fm.data.tags;
1830
1831
  }
@@ -1837,8 +1838,7 @@ export const TOOL_REGISTRY = [
1837
1838
  snippet: r.snippet,
1838
1839
  matchedTag: r.matchedTag,
1839
1840
  ...(logline ? { logline } : {}),
1840
- ...(domain ? { domain } : {}),
1841
- ...(docRole ? { docRole } : {}),
1841
+ ...(status ? { status } : {}),
1842
1842
  ...(tags ? { tags } : {}),
1843
1843
  };
1844
1844
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openwriter",
3
- "version": "0.18.0",
3
+ "version": "0.19.0",
4
4
  "description": "The open-source writing surface for AI agents. Markdown-native editor with pending change review — your agent writes, you accept or reject.",
5
5
  "type": "module",
6
6
  "license": "MIT",