Releases: iwe-org/iwe
Releases · iwe-org/iwe
Release list
v0.12.0
iwe
Added
refs_textmarkdown option —preserve(default) keeps each markdown link's text as written;normalizemakesiwe normalizeand document output rewrite the text to the linked document's title.iwe stats similarity— list mutually near-identical page pairs across the store, each pair once and tab-separated in alphabetical order (forward matches computed once per page, concurrently).iwe statsgains anOrphanssection listing every page with no incoming links (indexpages are exempt as intentional entry points); per-document stats (-k) gain aSimilar pageline (markdown) /similarPagesarray (JSON/YAML) of near-identical documents.iwe schema validate— validate documents against the schemas bound to them by the[schemas]config section (each entry names a schema file in.iwe/schemas/and a glob that binds it to document keys). Reports violations as-f text(default) or-f json, and accepts the universal filter flags to scope the check. Exits1when any document has violations,2on a config or schema-file error,0when clean. Bareiwe schemastill infers the frontmatter schema.iwe schema validate --schema-file <PATH>— validate the selected documents (-kor the filter flags) against a schema file directly, bypassing the[schemas]config bindings, for ad-hoc checks; violations are reported under the file's stem.iwe schema validate --explain— print the binding trace (which section and block bound to which schema entry,additionalfor the rest) instead of validating, to see how the greedy matcher reads a document against a schema.
Changed
iwe normalizeand document rendering keep markdown link text as written by default; setrefs_texttonormalizeto rewrite each link's text to the linked document's title (previously always rewritten).update --strictanddelete --strictnow reject a write that would leave a touched document violating its bound schema, aborting with exit2and the violation report before anything is written; previously--strictenforced only the--expectguards.update --strictanddelete --strictalso print non-blocking stats warnings to stderr after the schema gate — orphan pages and dangling links across the store, plus (onupdate) near-identical pages among the changed documents. They never change the exit code or block the write.
iwes
Added
refs_textmarkdown option — set tonormalizeto make document formatting rewrite each markdown link's text to the linked document's title;preserve(default) keeps the text as written.
Changed
- Document formatting keeps markdown link text as written by default (previously each link's text was rewritten to the linked document's title on format).
iwec
Added
iwe_create,iwe_update, andiwe_query(update/delete) now return non-blocking stats warnings alongside a successful result — orphan pages, dangling links, and (on create/update) near-identical pages — aswarning:content blocks, each finding reported once per session. They never block a write; schema validation remains the only hard reject.iwe_statswith akeynow returns asimilarPagesarray of documents near-identical to that page.
Changed
- Markdown link text in rendered document content is kept as written by default; set the
refs_textmarkdown option tonormalizeto rewrite each link's text to the linked document's title as before. - Mutating tools (
iwe_create,iwe_update,iwe_delete,iwe_query,iwe_rename,iwe_extract,iwe_inline,iwe_attach) now reject a change that would leave a touched document violating its bound schema, returning a detailed error carrying the readable report and a structuredviolationspayload; nothing is written and the in-memory graph is left untouched.iwe_normalizeandiwe_squashare unaffected.
liwe
Added
refs_textfield onMarkdownOptionsandDjotOptions— aRefsText(preserveby default, ornormalize) that controls whether a regular markdown link's text is rewritten to the linked document's title; read throughFormatOptions::refs_text(), withInlinesContextandGraphgainingnormalize_ref_text.schemamodule — document-schema validation:compile_schemacompiles a YAML/JSON schema (frontmatter JSON Schema plus an ordered section tree withheader,maxTokens,maxDepth,minContains/maxContains,allSections,additionalSections) into aCompiledSchema, andCompiledSchema::validatechecks aDocumentand returnsViolations carrying a breadcrumb, hint, schema pointer, and keyword. Unknown keywords are load errors (SchemaError).schemavalidation reaches block content: the document and every section, quote, and list item carryblocks/additionalBlocks/allBlocks— an ordered, greedy-matched array of block schemas discriminated bytype(paragraph,bullet-list,ordered-list,code,quote,table,rule, or a list of these for a disjunction), each withtext/langidentity,maxTokens,minContains/maxContains, listitems/minItems/maxItems, and quote/item recursion;DocumentandSectionnow carry ablocks: Vec<Block>field andCrumbgainsBlock(usize)/Item(usize). An inclusion link is matched as aparagraph.CompiledSchema::explainrenders the binding trace — which section and block bound to which schema entry,additionalfor the rest — for aDocument. Asectionsorblocksarray with an unreachable entry (a wildcard that is not last, or an exact duplicate of an earlier entry) is a load error.
Changed
- A regular markdown link's text is kept as written when rendering a document; set
refs_texttonormalizeto re-derive it from the linked document's title (previously the text was always re-derived).
diwe
Added
config::RefsTextre-export and therefs_textfield it sits on (MarkdownOptions/DjotOptions) — selects whether a markdown link's text is preserved (default) or normalized to the linked document's title.statsfindings functions —graph_findings(whole-store orphan and dangling-linkFindings, discriminated byRule) andmutation_findings(the same plus a similar-page check for the created/updated keys), withorphan_keysand the now-publicbroken_linksbehind them.stats::SimilarityIndex— a search index plus per-key token counts, built once per run (SimilarityIndex::build) and reused forsimilar(key)(near-identical pages for one key, asstats::SimilarPage { key, score }) andpairs()(every mutually-similar pair across the store, each once, computed concurrently). Duplicate detection uses mutual BM25 similarity with a token-size gate and a high threshold.GraphStatistics.orphans— the list of orphan keys behind the existingorphaned_documentscount;stats::KeyStatisticsReportpairs aKeyStatisticswith its similar pages.indexpages (rootindexor any<dir>/index) are treated as intentional entry points and excluded from both the orphan list and the count.search::Bm25Indexpoint-score API —similar_to(key, floor)(documents whose self-normalized score againstkey's own embedding clears a floor, self excluded),self_score(key), andscore_between(query_key, doc_key);search_query::corpus_textis now public.[schemas]config binding —config::SchemaBindingandconfig::Patternstypes and theConfiguration.schemasmap bind document schemas to document keys by glob (a single glob or a list). The newschemamodule resolves and runs them:schema::SchemaBindingsmatches a key to its schema names, andschema::validate_documentscompiles the bound schema files, validates a set of documents, and returns aschema::KeyReportper(key, schema)with violations.schema::validate_pending_documents(andschema::validate_pending_documents_in, which takes an explicit schemas directory) validate a set of pending(Key, content)documents against their bound schemas by building a throwaway graph, so a change can be checked before it is written.schema::pending_from_changescollects the touched documents from aChangesset,schema::render_reports_textrenders aKeyReportlist as text, andconfig::schemas_dir_inresolves the schemas directory under a given base path.schema::validate_documents_against_file— validate documents against one schema file directly, ignoring the[schemas]config bindings; reports are keyed by the file's stem.
Changed
searchnow orders tied scores deterministically (score descending, then key ascending); previously ties came back in arbitrary order.
v0.11.0
iwe
Added
new --key <KEY>— create a document at an explicit key, bypassing the template's key derivation. Subdirectory keys (e.g.people/ada) are allowed; omit the file extension.new --if-exists fail— report an error and exit non-zero when the document already exists. It is the default when--keyis given (an explicit key asserts an identity), where previously an existing key silently gained a-1suffix.retrieve --expand-includes/--expand-included-by/--expand-references/--expand-referenced-by— one flag per expansion direction, each taking an optional depth (bare flag = one level,0= unbounded, omitted = not followed).--expand-referenced-by(pull documents that reference a seed) and transitive--expand-referencesare new directions.retrieve --lexical/--fuzzy— a one-shot form that searches for seed documents within the candidate set (-k/--filter/ anchors) and then expands the graph around the ordered seeds.retrieve --max-documents N— cap the number of documents returned after expansion, trimming periphery documents first (0= unlimited).
Changed
retrieve --limitnow caps the selected seed documents before expansion — top-N by relevance when searching, the first N of the selection otherwise (previously it capped the number of documents returned after expansion; use--max-documentsfor that).retrieveno longer expands by default: with no--expand-*flag (and no deprecated flag) it returns the requested document(s) only. The previous implicit-d 1 -c 1is now written explicitly as--expand-includes 1 --expand-included-by 1.
Deprecated
retrieve -d/--depth,-c/--context,-l/--links— retained as hidden aliases for--expand-includes N/--expand-included-by N/--expand-references 1(keeping their legacy0= off meaning). Passing one together with its--expand-*counterpart is an error.
iwec
Added
iwe_creategains an optionalkeyparameter — create a document at an explicit key instead of a title-derived slug. Derive it from stable metadata (entity name, session date); subdirectory keys (e.g.people/ada) are allowed; omit the file extension. Creation fails if a document with that key already exists.iwe_queryfindoperations accept asearchclause (search: { lexical, fuzzy }) — relevance selection that restricts and orders results; alexicalquery with no searchable terms returns an empty array plus a warning content block.iwe_retrievegainssearch/fuzzy(seed queries),expand(object overincludes/includedBy/references/referencedBy→ integer depths,0= unbounded), andmax_documents(cap the documents returned after expansion). With a search query the tool finds seed documents within the candidate set (keys+ selector) and expands the graph around the ordered seeds.
Changed
iwe_retrievelimitnow caps the seed documents before expansion (top-N by relevance when searching, the first N of the selection otherwise); usemax_documentsfor the post-expansion document cap.iwe_retrieveno longer expands by default — omitexpandand it returns the requested document(s) only (previously the implicit behavior was one level of children and parents).iwe_deleteandiwe_querydeletes now also remove any parent directory left empty by a removed document, matching the CLI (previously empty directories were kept).
Deprecated
iwe_retrievedepth/context/links— retained as aliases forexpand'sincludes/includedBy/references; passingexpandtogether with any of them is an error.
liwe
Added
Changes::merge— fold anotherChangesinto this one, deduplicating by key so a later update replaces an earlier one for the same document.operationsgains the section/reference selection helpers (sections,select_section,references,select_reference,SelectError,SectionRef,InclusionRef) andattach_reference/AttachTarget, so all operations live inliwe::operationsalongsideextract,inline,delete, andrename.searchclause onFindOp(SearchSpec { lexical, fuzzy }) — a relevance stage that restricts membership to documents matching the query and supplies the default ordering, jointly withfilter;search+sortkeeps membership from search whilesortsupplies the order. The ranking logic (query::search::ranked/matched, RRF fusion) is now a shared stage used by bothDocumentFinderand the query engine.RetrieveOptionsexpansion generalized to four edge-named depths —includes,included_by,references,referenced_by(u32,0= off,UNBOUNDED= no limit) — replacing thedepth/context/linksfields. Inbound-reference expansion (referenced_by) and transitive outbound-reference expansion (references> 1) are now expressible;retrieve::expand_depthmaps an--expandvalue (0= unbounded) to a depth.format::DocumentFormattrait (read/write/write_skip_frontmatter) as the format boundary, with the built-inMarkdownFormatand (feature-gated)DjotFormatimplementations and aformat::format_forconstructor.query::QueryScoresandquery::execute_with_scores— the query engine now ranks asearchclause from caller-injected per-key relevance scores instead of computing them itself, keeping the kernel free of any search index.
Changed
- Djot support is now behind an opt-in
djotcargo feature;jotdownis an optional dependency and thedjotmodule compiles only when the feature is enabled. Markdown remains built in. Enablefeatures = ["djot"]to read and write.djdocuments. - The BM25 search index left the kernel:
Graphno longer builds or holds one, soGraph::search,has_search_index,search_scores, andlexical_query_has_termsare removed, andGraph::from_state/Graph::importno longer take asearch_languageargument. The BM25 index and thesearchscoring (ranked/matched) moved to thediwecrate;query::SearchSpecstays as the DSL type. Graph::has_search_index()reports whether the graph carries a BM25 index; running afindwith asearchclause against a graph without one is an execution-time error (EvalError::SearchIndexMissing).RetrieveOptions.limitnow caps the seed set beforeDocumentReader::retrieve_manyexpands (it was a post-expansion cap on the number of documents returned); the post-expansion cap moves to the newRetrieveOptions.max_documentsfield. Both areOption<usize>,None/Some(0)= unlimited.EdgeRefmoved fromretrievetoquery::edges.
Removed
- The engine modules (
find,retrieve,stats,tokens,fs,file) and the.iwe/config.tomlmapping (Configuration,LibraryOptions,CompletionOptions,SearchOptions,Command,ActionDefinitionand its variants,NoteTemplate,load_config) moved to the newdiwecrate;liwekeeps the document kernel and the format/option types (Format,FormatOptions,MarkdownOptions,DjotOptions,FormattingOptions,LinkType,InlineType,TargetType,Operation) inliwe::model::config. Graph::from_path— the filesystem-loading constructor moved todiwe; build aStatefrom disk and callGraph::from_state.
diwe
Added
diweis the IWE engine library carved out ofliwe. It carries the app-facing layer:find(BM25 / fuzzy search),retrieve(document expansion with token budgeting),stats,tokens,fs(filesystem / workspace loading),graph_from_path, and the.iwe/config.tomlmapping (config::Configuration,config::load_config). It depends onliwefor the document kernel and re-exportsliwe's format/option types fromdiwe::config.search(the BM25 index) andsearch_query(BM25 + fuzzy resolvers, RRF fusion,build_index,ranked/matched, and anexecutewrapper that resolves a query'ssearchclause into scores and injects them into theliweengine).DocumentFinder::with_indextakes a caller-built index.fs::apply_changes— write aChangesset to a workspace (creates, updates, and removals), pruning any parent directories left empty by a removal.
v0.10.0
iwe
Added
refs_pathmarkdown option — set it toabsoluteto write links as root-absolute paths (/dir/note.md) on normalize, instead of the default paths relative to the linking document.
Fixed
- Root-absolute links (a leading
/, such as/dir/note.md) and links carrying a#fragmentnow resolve from any directory. Previously such links were dropped from the graph unless the linking file sat at the library root, sotree,stats,retrieve, and backlinks under-reported references.
iwes
Added
refs_pathmarkdown option —absolutemakes document formatting and link completion write links as root-absolute paths (/dir/note.md) instead of paths relative to the linking document.
Fixed
- Root-absolute links (a leading
/) and links carrying a#fragmentnow resolve from any directory, so backlinks, go-to-definition, and completions see references that were previously dropped unless the linking file sat at the library root. - The link code action writes the new link relative to the current document and honors the
refs_pathsetting — previously it wrote the target's full library path, producing a broken link when invoked from a document in a subdirectory.
liwe
Added
RefsPathenum and arefs_pathfield onMarkdownOptions/DjotOptions(defaultRefsPath::Relative), surfaced throughFormatOptions::refs_path();RefsPath::Absoluterenders regular links as root-absolute paths (/dir/note.md) instead of paths relative to the linking document.Key::link_url(relative_to, refs_path)builds a regular link's path for the givenRefsPath, so every link-writing path shares one implementation.
Fixed
Key::from_rel_link_urlresolves a regular link with a leading/from the library root regardless of the linking document's directory (previously a leading/only resolved from a document at the root), and strips a trailing#fragmentbefore computing the key sonote.md#sectionresolves to the same key asnote.md.
v0.9.0
iwe
Added
--project/--add-fieldsaccept block-addressed sources:{ $content: PREDICATE }narrows a document's body to the selected blocks (rendered at their original depth),$blocks/{ $blocks: PREDICATE }lists each selected block astype/path/textdata, and{ $matches: REGEX }greps matching lines with their section paths.findmarkdown output renders$blocksand$matchesentries one line each askey › section path › text, and switches to the fenced-block form with the narrowed body when a parameterized$contentfield is projected.--projectaccepts a bare block predicate —--project '$header: {}'renders each document's body narrowed to the selected blocks (the headers-only form) underkeyandcontentfields, so--format json/yamloutput keeps the document identity that the markdown fence already carries.updategains a block-edit flag per operator —--replace,--replace-text,--insert-before,--insert-after,--append,--delete— each taking a{ <selector>, payload }mapping and composing with--set/--unsetinto one atomic update. A validation failure (unmetexpect, overlapping selections, incompatible target) prints the offending blocks and exits non-zero without writing.--replace-textaccepts afrom-less argument ({ $header: Goals, to: Aims }) that rewrites the block's entire own text — the clean way to rename a header or restate a line.- A block edit targeting a
$headeracts on the heading line alone:--delete '{ $header: Goals }'dissolves the section (contents re-attach to the parent and re-level) and--replace '{ $header: Goals, content: "## Aims" }'retitles it (contents kept), while--delete '{ $section: Goals }'removes the whole tree.--insert-after '{ $header: Goals, content: ... }'adds content at the top of the section, below the heading line. updateanddeletegain--expect— a document-level guard asserting the number of matched documents (Nor{ min, max }); on a mismatch the command lists the matched documents askey › titleand exits non-zero without writing. Both also gain--strict, which requires anexpectguard on every mutating application (the document-level--expectand each block operator'sexpect) and aborts before writing if any is missing;--dry-runis exempt so counts can be learned.find --blocks PREDadds ablocksfield listing each block matching the predicate (lowers toaddFields: { blocks: { $blocks: PRED } }), andfind --matches PATTERNrestricts results to documents whose content matches PATTERN and adds amatchesgrep field (lowers to a$contentmembership filter plusaddFields: { matches: { $matches: PATTERN } }).find --filteraccepts the$contentblock-membership operator —--filter '$content: { $header: Status }'selects documents that contain at least one block satisfying the predicate.
Changed
update -k/--keyis repeatable, matchingfind: one key lowers to$eq, two or more to$in(body-overwrite mode still takes exactly one). Previously it accepted a single key only.updatewrites only documents whose rendered content actually changes and reports honestly —Updated N document(s)when every matched document changed,Matched N document(s), M changedotherwise (No documents matchedwhen none) — so a no-op edit (e.g.expect: 0) leaves the file, and its mtime, untouched.
Fixed
--project/--add-fieldsnow parse the argument as a YAML mapping whenever it contains a:or{, and report a parse error on malformed input instead of silently falling back to the comma list. Previously an unbraced multi-field mapping like--project 'a: { $content: ... }, b: { $content: ... }'failed the YAML parse, degraded to the comma list, and emitteda: null, b: nullwith no error. The comma list keeps thename,name=source, and bare$selectorforms; write multi-field or block projections as a braced mapping.
iwec
Added
iwe_querytool runs an IWE query/block-selection operation document —operationisfind/count/update/deleteanddocumentis the operation as a YAML string. It exposes the$contentmembership filter, the$content/$blocks/$matchesprojection sources, and the block update operators ($replace,$replaceText,$insertBefore,$insertAfter,$append,$delete).findandcountread;updateapplies frontmatter and block edits;deleteremoves documents with reference cleanup. The tool is always strict: every mutating application must carry anexpectguard or the operation is refused with the missing guards named.update/deleteacceptdry_runto preview without writing.
liwe
Added
query::blockmodule with theBlockPredicategrammar for addressing blocks inside a document: text and regex predicates,$within/$containsaxes, per-type operators ($section,$header,$paragraph,$item,$list,$quote,$code,$table,$ref,$hr),$references, and$and/$or/$norcomposition.- Block-addressed projection sources:
{ $content: PREDICATE }renders the selected blocks,$blocksreports each selected block astype/path/text, and{ $matches: REGEX }greps matching lines with their section paths — all evaluated through the newquery::block_eval::BlockIndex. IntoBlockPredicatetrait accepting scalar shorthands wherever a block predicate is expected.FindOp::add_fieldssets an additive (addFields) projection;CountOpandDeleteOpimplementFrom<FindOp>, reinterpreting a built find with its projection dropped.projectaccepts a$-prefixed block predicate mapping (project: { $header: {} }), lowering it to akeyfield and acontentfield carrying the narrowed body.- Block update operators in the
updatedocument —$replace,$replaceText,$insertBefore,$insertAfter,$append,$delete— each pairing a block predicate selector with its payload and an optionalexpectguard, validated and applied atomically; represented byBlockUpdate,BlockUpdateOp, andExpectonUpdate::block_ops. - Unit block operators act on their target as selected: a
$headernode covers its heading line alone ($deletedissolves the section, a heading$replaceretitles it) while a$sectioncovers the whole tree. UpdateOpandDeleteOpcarry an optionalexpectguard asserting the number of matched documents; on violation nothing is written and the error lists each matched document.$contentfilter operator (Filter::Content) matches documents holding at least one block satisfying a block predicate; it composes with every other filter clause.query::strict_guard_violationsnames the mutating applications that lack anexpectguard.
Changed
query::executereturnsResult<Outcome, block_update::EvalError>(wasOutcome) so a failed block update reports its validation error instead of writing;find,count, anddeletealways returnOk.Updatecarriesblock_ops: Vec<BlockUpdate>alongside the frontmatteroperators.Projectioncarries abase: ProjectionBase(Empty/Frontmatter/Document) in place ofmode: ProjectionMode, soFindOp::projectis a plainProjection(wasOption<Projection>);Projection::document_fields()replacesProjection::default_for_find().ProjectionContextis constructed withProjectionContext::new(graph, key)(was a public struct literal).
Fixed
- A mis-typed
projectmapping now reports a parse error instead of being silently read as a comma list of frontmatter field names and yieldingnull.
Removed
query::preludemodule (with itsWithFiltertrait) — the Rust builder functions moved into the test suite; constructOperation,FindOp, andFilterdirectly.query::project::apply_projection_or_default—apply_projectioncovers the no-projection case.
v0.8.0
iwe
Added
findgains explicit--fuzzy(subsequence match on document title and key) and--lexical(BM25 full-text scoring over title and body) query flags; supplying both fuses the two result sets with Reciprocal Rank Fusion. Set the stemming language for lexical search with[search] languagein.iwe/config.toml.find --lexicalprints a warning when the query reduces to only stop words after stemming, so an empty result set is explained instead of looking like an empty index.
Fixed
findandretrievetruncation warning now suggests only the limits that actually apply (--limit,--max-tokens,--max-document-tokens) instead of always naming--max-tokens, which does nothing for a metadata-only index bounded by--limit.
Deprecated
find's bare positional query defaults to fuzzy matching and now prints a warning on stderr; it will be removed in a future release. Use--fuzzyor--lexicalinstead.
iwes
Changed
- Workspace-symbol search fuses fuzzy matching with BM25 full-text relevance (over document title and body) using Reciprocal Rank Fusion, so a query term in a document's body can lift it above an equally-fuzzy result.
iwec
Changed
iwe_findreplaces itsqueryparameter with explicitfuzzy(match on document title and key) andlexical(BM25 full-text over title and body) parameters; supplying both fuses the results with Reciprocal Rank Fusion. Set the stemming language for lexical search with[search] languagein the configuration.
liwe
Added
search::Bm25IndexplusGraph::searchandGraph::search_scoresprovide BM25 full-text ranking over document title and body; the index is built and kept in sync by the ingestion pipeline (insert_document/update_document/remove_document).Graph::to_plain_textrenders a document to plain text (markup stripped, link display text kept, code and table cells included);Node::plain_textnow also covers table cells.[search]configuration table with alanguagefield (one of 17 stemming languages, defaultenglish), exposed throughConfiguration::search_language.search::rrf_weightandsearch::RRF_Kfor Reciprocal Rank Fusion of ranked result lists.Graph::lexical_query_has_terms(backed byBm25Index::has_query_terms) reports whether a lexical query keeps any searchable terms after stop-word removal and stemming.
Changed
Graph::from_stateandGraph::from_pathtake an extraOption<Language>argument that enables search indexing when set (Noneskips it);Graph::importis unchanged and keeps indexing off.FindOptionscarries separatefuzzyandlexicalquery fields (was a singlequery); when both are set the finder fuses the two rankings with Reciprocal Rank Fusion.
v0.7.0
iwe
Added
retrieve --limit, and--max-tokens/--max-document-tokensonretrieveandfind, to bound output for context-limited callers.0disables a limit. Awarning:line is printed to stderr when output is truncated.
Changed
findmarkdown output is now a compact index (one line per document) instead of full document blocks; a document body is rendered only when the projection includes$content(via--project/--add-fields). Useretrievefor full content.
Removed
retrieve --no-content— removed;retrievealways returns content. Usefindfor a metadata-only index.retrieve --dry-run— removed.
iwec
Added
iwe_retrieveandiwe_findacceptmax_tokens/max_document_tokens(andiwe_retrievealimit) to bound output; all are unlimited unless set. When a limit trims the output, the tool appends a second content block with a JSON truncation summary (truncated,emitted,matched,clipped,tokens,budget,hint) alongside the unchanged primary JSON.
Removed
iwe_retrievetoolno_contentparameter — removed; the tool always returns content.
liwe
Added
tokensmodule (count_tokens,truncate_to_tokens) backed bytiktoken-rs.RetrieveOptionsgainslimit,max_tokens,max_document_tokens;FindOptionsgainsmax_tokens,max_document_tokens;RetrieveOutput/FindOutputgain atruncationsummary.
Removed
RetrieveOptions::no_contentfield — removed;DocumentReaderalways populatescontent.
v0.6.1
iwe
Fixed
retrieve --backlinkscan now be turned off:--backlinks falsedisables incoming references, while a bare--backlinks(and the default) still includes them. Previously the flag was stuck on and--backlinks falsewas rejected outright.stats -k <key>accepts a key written with a.md/.djextension (stats -k note.md) instead of reporting the document as not found.find --format keyscombined with--projectnow prints the matched keys instead of nothing.attachreports an error and exits instead of crashing when an action'skey_templateordocument_templateis malformed;attach --listand--dry-runare affected too.schemaandfind --filter '{$type: datetime}'no longer crash when a document holds a datetime value with multibyte characters.
iwes
Fixed
- Renaming a wiki link (
[[target]]or[[target|label]]) now selects the target for editing instead of an empty spot at the closing brackets. - Positions in a document that starts with an empty frontmatter (
---/---) are no longer shifted up by two lines, so goto-definition, hover, rename, and code actions land on the right line. - A failed rename (for example when the target file name is already taken) is now returned as a proper LSP error response instead of an empty success, so the editor surfaces the message to the user instead of silently doing nothing.
- Link completion no longer leaves a stray
[behind when the cursor sits after trailing spaces; the completion is inserted at the cursor instead of overwriting part of an earlier word. - Find references invoked on a link now reports references to the linked document (rather than the current document) when the request asks to include the declaration.
- An unknown LSP request now returns a
MethodNotFounderror instead of panicking the request handler and leaving the client waiting for a response that never arrives. - Formatting a document that is not part of the library (a file outside the library path, or a brand-new unsaved file) no longer crashes the server; it returns no edits.
- A transform action environment value that contains non-ASCII characters no longer crashes code action resolution.
- A long editing session on a document that contains a table no longer grows the server's memory without bound; the table's lines are released each time the document is re-parsed.
Removed
- The advertised
workspace/executeCommandcapability, which offered an unimplementedgeneratecommand that had no handler.
iwec
Fixed
iwe_normalizenow actually reformats documents on disk: it compares each document's normalized form against the file's current contents and rewrites the ones that differ, reporting an accuratenormalizedcount instead of always returning0.iwe_createrejects a title with no alphanumeric characters (e.g."!!!") instead of writing an empty-named file.iwe_attachand theiwe://configresource return an error instead of crashing the server when an attach action's template is malformed.- The file watcher maps a file named
note.md.mdto the keynote.mdinstead ofnote, matching how the graph loads documents from disk. - Repeatedly saving a watched document that contains a table no longer leaks memory as the server re-parses it.
liwe
Fixed
DocumentInline::key_rangenow returns the target range for wiki links ([[target]]and[[target|label]]); it previously assumed[text](url)syntax and returned an empty range at the wrong offset.- The markdown reader now offsets line positions by the stripped empty frontmatter (
---/---), so block and inline ranges point at the original document lines instead of being two lines too low. - Sorting a field that mixes value types (e.g. some documents with
priority: 3and others withpriority: high) now produces a stable, deterministic order: values are grouped by type before being compared, so cross-type pairs no longer collapse to "equal" and scramble the result. - Numeric filters compare integers exactly instead of routing everything through 64-bit floats, so large whole numbers past 2^53 (like
id: 9007199254740993) are no longer treated as equal to their neighbours. - A
not-a-numberfilter target (.nan) no longer compares as equal to every number, so range operators like$gte: .nanmatch nothing instead of matching everything. - Datetime detection in query filters no longer crashes on a value with multibyte characters in the timezone tail (for example
2026-04-26T10:30:00+0é9). - Code blocks whose content contains a run of the fence token are now written with a longer fence, so the block no longer terminates early and leaks its content into the surrounding text.
- A paragraph that starts with an escaped
\*keeps its backslash when written, so it is not re-parsed as a bullet list on the next pass. - A document key strips only one file extension, so a file named
note.md.mdmaps to the keynote.mdinstead of collapsing ontonoteand being duplicated on write. - Deleting a table node now releases its header and row lines back to the arena; repeatedly re-parsing a document that contains a table no longer leaks those line slots and grows memory without bound.
v0.6.0
iwe
Added
preserve_newlinesconfig option keeps each line of a paragraph on its own line duringnormalizeinstead of joining them with spaces, so source files written with one sentence per line (semantic line breaks) survive formatting (default off).
Fixed
normalizeno longer collapses nested or multi-paragraph djot list items onto one line; the blank line that keeps them separate is preserved so the list survives repeated runs.- Commands no longer crash on a djot document that contains a reference link definition or a definition list.
normalizekeeps the word boundary at a hard line break instead of running the surrounding words together.normalizepreserves djot task list checkboxes (- [ ]/- [x]), display math ($$), and autolinks (<url>) instead of mangling them.
iwes
Added
preserve_newlinesconfig option keeps each line of a paragraph on its own line when formatting instead of joining them with spaces, so documents written with one sentence per line (semantic line breaks) survive format-on-save (default off).
Fixed
- Formatting a djot document no longer collapses nested or multi-paragraph list items into the parent item; the blank line separating them is kept so the list structure survives format-on-save.
- The server no longer crashes when parsing a djot document that contains a reference link definition or a definition list.
- Formatting keeps the word boundary at a hard line break instead of running the surrounding words together.
- Formatting preserves djot task list checkboxes (
- [ ]/- [x]), display math ($$), and autolinks (<url>) instead of mangling them.
liwe
Added
FormattingOptionsgains apreserve_newlinesfield (defaultfalse); when enabled, a soft line break inside a paragraph is read asDocumentInline::SoftBreakand written back as a plain newline instead of being collapsed into a space, so the source line layout survives normalization.
Changed
Inline::Mathnow carries aMathType(Inline::Math(MathType, String)), distinguishing inline from display math.
Fixed
- The djot writer now leaves a blank line before a nested list or any second block inside a list item, so list items with sub-lists or extra paragraphs round-trip instead of collapsing into the item's first line.
- The djot reader no longer panics on a document that contains a reference link definition or a definition list; the orphaned text is dropped instead of crashing the parser.
- A hard line break now becomes a space when line breaks aren't preserved, instead of running the words on either side together.
- Djot task list items (
- [ ]/- [x]) round-trip instead of having the checkbox escaped and the item text split onto a separate line. - Djot display math (
$$) is no longer written back as inline math ($). - Djot autolinks (
<url>) round-trip instead of being expanded to a full[url](url)link.
v0.5.0
iwe
Added
formatconfig option (markdown|djot, defaultmarkdown) selects the document format, with a matching[djot]options table. Withformat = "djot",iwereads, normalizes, exports, and creates djot documents and works with.djfiles instead of.md.
iwes
Added
format = "djot"in the configuration makes the server read, format, and write djot documents and map file URIs using the.djextension (default remainsmarkdownwith.md).
Fixed
- The server no longer leaks memory as documents are edited and saved; each update used to retain the previous version's graph data, growing memory without bound over a long session.
iwec
Added
--hostflag sets the address the HTTP transport binds to (default127.0.0.1); pass--host 0.0.0.0to accept connections from other machines.format = "djot"in the configuration makes the server read, write, and watch djot.djdocuments (default remainsmarkdownwith.md).
Fixed
- The server no longer leaks memory while watching a folder; repeatedly saving documents used to grow memory without bound and could exhaust RAM and swap over a long session.
liwe
Added
FormatOptions(Markdown(MarkdownOptions)|Djot(DjotOptions)) bundles the document format with its formatting options; it is what the graph reads and writes through.Graph::new_with_options,from_state,from_path, andimportacceptimpl Into<FormatOptions>.DjotReader/DjotWriterparse and serialize djot documents so the graph round-trips a.djdocument back to djot, andConfigurationgains a top-levelformatselector, adjot: DjotOptionstable, andConfiguration::format_options().InlineandDocumentInlinegainSpan,Mark,Insert,Delete, andSymbolvariants, and aninline::Attributestype, so djot's bracketed attribute spans, highlight/insert/delete marks, and symbols round-trip losslessly through the graph.
Changed
NodeIter::to_text(parent, &FormatOptions)replaces the markdown-specificto_markdown, serializing to whichever format theFormatOptionscarries.Key::to_pathand thefsdiscovery and write helpers (walk_md_paths,new_for_path,write_file,write_store_at_path) now take aFormatso document files use the configured extension (.mdor.dj).
Fixed
- Updating or removing a document now reclaims the graph nodes, lines, and reference-index entries that belonged to its previous version, so a long-lived
Graphno longer grows without bound as the same documents are edited over and over.
v0.4.0
iwe
Fixed
- Normalization now preserves escaped Markdown literals (such as
\*text\*, a leading\#, or\[label\](url)) instead of dropping the backslashes and re-interpreting the text as live markup.
iwes
Fixed
- Documents with Windows line endings are no longer stripped of their frontmatter when edited or saved, and code action ranges no longer drift one column per line on such documents.
- Renaming a link target in a document inside a subfolder no longer deletes the target file and replaces it with an empty one; the new file is written to the correct folder with the original content, and backlinks are updated to a valid path.
- Formatting and code actions no longer turn escaped literal text into live Markdown; an escaped
\*text\*,\#, or\[label\](url)keeps its escapes, and a list item written as\[ \]is no longer rewritten into a task checkbox.
iwec
Added
--transportflag selects how the server is served:stdio(default, unchanged) orhttp. With--transport httpthe server listens for Streamable HTTP connections athttp://127.0.0.1:<port>/mcp, with--portsetting the port (default8000). The server speaks plain HTTP and binds to localhost only; put a reverse proxy in front of it for TLS or remote access.
liwe
Fixed
MarkdownReadernow normalizes Windows line endings before parsing, so documents with\r\nline endings keep their frontmatter and report correct positions (previously frontmatter was dropped and positions drifted one column per line).- The Markdown writer now re-escapes special characters in text, so escaped literals such as
\*text\*, a leading\#,\[label\](url), and1\.survive normalization instead of turning back into emphasis, headings, links, or list markers; inline code containing a backtick is fenced with enough backticks to render intact, and a list item written as an escaped\[ \]is no longer mistaken for a task checkbox.