{"id":20098368,"url":"https://github.com/wikibonsai/semtree","last_synced_at":"2025-07-31T11:13:32.558Z","repository":{"id":142458048,"uuid":"608243542","full_name":"wikibonsai/semtree","owner":"wikibonsai","description":"Utilities to build a semantic tree (in markdown).","archived":false,"fork":false,"pushed_at":"2025-06-25T19:24:34.000Z","size":476,"stargazers_count":7,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-07-26T04:44:35.113Z","etag":null,"topics":["markdown","semantic-tree","wikibonsai"],"latest_commit_sha":null,"homepage":"https://www.npmjs.com/package/semtree","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/wikibonsai.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-03-01T16:05:10.000Z","updated_at":"2025-06-25T19:24:37.000Z","dependencies_parsed_at":"2024-01-13T17:13:42.886Z","dependency_job_id":"9cb6d214-c42f-4660-a137-74053fe9f734","html_url":"https://github.com/wikibonsai/semtree","commit_stats":{"total_commits":25,"total_committers":2,"mean_commits":12.5,"dds":"0.040000000000000036","last_synced_commit":"3fc937ef1eaf88742ce2bf9d6356b606fa112027"},"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/wikibonsai/semtree","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wikibonsai%2Fsemtree","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wikibonsai%2Fsemtree/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wikibonsai%2Fsemtree/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wikibonsai%2Fsemtree/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/wikibonsai","download_url":"https://codeload.github.com/wikibonsai/semtree/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wikibonsai%2Fsemtree/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":268028081,"owners_count":24183764,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-07-31T02:00:08.723Z","response_time":66,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["markdown","semantic-tree","wikibonsai"],"created_at":"2024-11-13T17:03:16.823Z","updated_at":"2025-07-31T11:13:32.542Z","avatar_url":"https://github.com/wikibonsai.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# semtree\n\n[![A WikiBonsai Project](https://img.shields.io/badge/%F0%9F%8E%8B-A%20WikiBonsai%20Project-brightgreen)](https://github.com/wikibonsai/wikibonsai)\n[![NPM package](https://img.shields.io/npm/v/semtree)](https://npmjs.org/package/semtree)\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"./semtree.svg\" width=\"300\" height=\"300\"/\u003e\n\u003c/p\u003e\n\n\u003e “It is important to view knowledge as a sort of semantic tree. Make sure you understand the fundamental principles, i.e., the trunk and big branches before you get into the leaves/details or there is nothing for them to hang on to.”\n\u003e \n\u003e ~ [Elon Musk](https://www.reddit.com/r/IAmA/comments/2rgsan/comment/cnfre0a/?utm_source=share\u0026utm_medium=web2x\u0026context=3)\n\n\u003e \"'First principles' means: Break things down to the fundamental axiomatic elements that are most likely to be true and reason up from there as cogently as possible -- as opposed to reasoning by analysis or metaphor.\"\n\u003e \n\u003e ~ [Elon Musk](https://youtu.be/cFIlta1GkiE?si=dVxwck2nb-gGOKqQ\u0026t=1566)\n\n`semtree` is a utility to construct a semantic tree from word lists/indexes which may span multiple objects -- the most likely setup being multiple filenames which map to their file content.\n\n`semtree` itself is essentially a collection of functions to facilitate the cultivation of this tree, namely with [`lint()`](#lintcontent-string--recordstring-string-opts-lintopts-void--string), [`create()`](#createroot-string-content-recordstring-string-opts-semtreeopts--defaultopts-semtree--string), [`update()`](#updatetree-semtree-subroot-string-content-recordstring-string-opts-semtreeopts--defaultopts-semtree--string), or [`print()`](#printtree-semtree-print-boolean--true-string--undefined), and handles the build process via a [state machine](https://mfaani.com/posts/interviewing/how-understanding-state-machines-helps-with-building-trees-and-graphs/).\n\nThis package can be used in conjunction with [treehouze](https://github.com/wikibonsai/treehouze) and is compatible with [`[[wikirefs]]`](https://github.com/wikibonsai/wikirefs), [caml](https://github.com/wikibonsai/caml-mkdn) and [yaml](https://yaml.org/) syntaxes.\n\nSee [context](#context) for more about why and how this package is useful.\n\n🌳 Cultivate a \"semantic tree\" or \"knowledge bonsai\" in your [🎋 WikiBonsai](https://github.com/wikibonsai/wikibonsai) digital garden.\n\n## Install\n\nInstall with [npm](https://docs.npmjs.com/cli/v9/commands/npm-install):\n\n```\nnpm install semtree\n```\n\n## Use\n\nSay we have the following two markdown files:\n\n```markdown\n// file: fname-a\n\n- [[node-1]]\n  - [[node-1a]]\n- [[node-2]]\n  - [[node-2a]]\n  - [[node-2b]]\n- [[fname-b]]\n```\n\n```markdown\n// file: fname-b\n\n- [[node-3]]\n- [[node-4]]\n```\n\nIf we wanted to create a single tree from both of these files, we can use `semtree` like so:\n\n```js\nimport * as semtree from 'semtree';\n\nlet opts = {\n  wikiLink: true, // defaults to 'true'\n};\nconst rootName: string | undefined = 'fname-a';\n// read in files and create a record where\n// keys are filenames and values are the file's content\nconst semTreeText: Record\u003cstring, string\u003e = {\n  // key: filename; value: file content\n  'fname-a':\n`- [[node-1]]\n  - [[node-1a]]\n  - [[node-2]]\n    - [[node-2a]]\n      - [[node-2b]]\n      - [[fname-b]]\n`,\n  'fname-b':\n`- [[node-3]]\n- [[node-4]]\n`,\n};\nconst tree = semtree.create(semTreeText, rootName, opts);\n```\n\nWhich will create a tree that looks like:\n\n```mermaid\ngraph TD;\n  node-1--\u003enode-1a;\n  node-1--\u003enode-2;\n  node-1--\u003efname-b;\n  node-2--\u003enode-2a;\n  node-2--\u003enode-2b;\n  fname-b--\u003enode-3;\n  fname-b--\u003enode-4;\n```\n\n## Parsing, Syntax, and Validity\n\nTree requirements are sparse because the idea is to allow the end-user to determine the shape and structure of their tree in their markdown files. This package merely creates a single, virtual tree so as to better present that unified structure to the end-user.\n\nParsing:\n\n- Semtree will check for delimiters delineating where the index content is:\n  ```markdown\n  : title : index file\n\n  This is some markdown text.\n\n  \u003c!--semtree--\u003e\n  - [[node-a]]\n    - [[node-b]]\n    - [[node-c]]\n  \u003c!--/semtree--\u003e\n  ```\n  - This content would return the following as the file content relevant for tree-building:\n  ```markdown\n  - [[node-a]]\n    - [[node-b]]\n    - [[node-c]]\n  ```\n- If delimiters do not exist any attribute metadata in [`caml`](https://github.com/wikibonsai/caml-mkdn) or [`yaml`]() format will be stripped.\n  ```markdown\n  ---\n  subject: semantic tree\n  ---\n  : title : index file\n\n  - [[node-a]]\n    - [[node-b]]\n    - [[node-c]]\n  ```\n  - Here, the same content would be identified as the tree content since `caml` and `yaml` attrs would be stripped:\n  ```markdown\n  - [[node-a]]\n    - [[node-b]]\n    - [[node-c]]\n  ```\n\nSyntax:\n\n- Indentation size defaults to `2` `'space'`s. (see options [`indentKind`](#indentkind-space--tab--space) and [`indentSize`](#indentsize-number--2)).\n- Markdown bullets (`-*+`) are optional (see option [`mkdnBullet`](#mkdnbullet-boolean--true)).\n- `[[wikilink]]` syntax is optional (see option [`wikiLink`](#wikilink-boolean--true)).\n\nValidity:\n\n- Every node in the tree should be unique; e.g. each list-item's text should be unique.\n- Must be a directed-acyclic-graph (DAG).\n- Each level can have any number of nodes.\n\n## API\n\n### TreeNode\n\nEach node in the tree contains:\n\n```ts\nexport interface TreeNode {\n  text: string;\n  ancestors: string[];\n  children: string[];\n  // custom data\n  [key: string]: any;\n}\n```\n\n`ancestors`: An array of strings that are the text of other nodes in the tree. Represents ancestors of the current node from the root node following the ancestral path to the current node.\n\n`children`: An array of strings that are the text of other nodes in the tree. Represents children of the current node.\n\n`text`: Contains the node text, which should be unique across all nodes in the tree and is used as an identifier in each nodes' other properties `ancestors` and `children`.\n\nFinally, custom data is supported.\n\n### SemTree\n\nThe full `SemTree` looks like this:\n\n```ts\ninterface SemTree {\n  root: string;\n  nodes: TreeNode[];\n  trunk: string[];\n  petioleMap: Record\u003cstring, string\u003e;\n  orphan: string[];\n}\n```\n\n`root`: The `text` of the root node.\n\n`nodes`: Contains a flat array of all the `TreeNode`s in the tree.\n\n`trunk`: An array of `text` names of all the index/branch nodes (which typically correspond to the keys of the `content` hash).\n\n`petioleMap`: A hash whose keys are the `text` names of all the nodes in the tree and the values are the `text` names of the index/branch node those keys appeared in (e.g. key `node-1` yields value `fname-a` from the example above because `node-1` appears in `fname-a`).\n\n('petiole': \"A leaf petiole is a thin stalk that connects a leaf blade to a stem\")\n\n`orphan`: An array of `text` names of any unprocessed index/branch nodes from the `content` hash keys not processed after calling [`create()`](#createroot-string-content-recordstring-string-opts-semtreeopts--defaultopts-semtree--string) or [`update()`](#updatetree-semtree-subroot-string-content-recordstring-string-opts-semtreeopts--defaultopts-semtree--string).\n\n### `create(root: string, content: Record\u003cstring, string\u003e, opts: SemTreeOpts): SemTree | string;`\n\nCreate a tree from a given `Record`, where keys represent nodes in a tree and values represent multiple values in the tree (such as filenames and their content) and build a tree from them. Will return a tree instance upon successful creation. Will return an error string otherwise, for example if there are duplicates found in the tree.\n\n#### Parameters\n\n##### `content: Record\u003cstring, string\u003e`\n\nA `Record` whose keys are entities (such as files) and values are content strings of those entities.\n\n##### `root: string`\n\nName of the root node of the tree.\n\n##### `opts: SemTreeOpts`\n\nOptions object -- see [options](#Options) below.\n\n### `lint(content: string | Record\u003cstring, string\u003e, opts: LintOpts): void | string`\n\nLint a file's content or a record of multiple files' file content.\n\nChecks for:\n\n- Duplicates / cycles\n- Spaces / tabs\n- Inconsistent indentation\n- Over-indentation\n- [Markdown bullets](#mkdnbullet-boolean)\n- [WikiLink](#wikilink-boolean)\n- Lists files that weren't linked in the tree\n\n(Note: Lint line numbers returned will be offset by wherever the target semtree content started within the file. If the content starts at line 5 and the linter says an error occurred on line 1, then the error probably occurs on line 6 of the file.)\n\n#### Parameters\n\n##### `content: string | Record\u003cstring, string\u003e`\n\nA content string or a `Record` whose keys are entities (such as files) and values are content strings of those entities.\n\n##### `opts: LintOpts`\n\nLint options:\n\n###### `indentKind?: 'space' | 'tab'`\n\nKind of indentation -- either 'space's or 'tab's.\n\n###### `indentSize?: number`\n\nNumber of indentations (spaces or tabs) which represent each level in the tree.\n\n###### `mkdnBullet?: boolean`\n\nWhether the linter should check for markdown bullets (`-`, `*`, `+`)  and print a warning if any nodes are missing them.\n\n###### `wikiLink?: boolean`\n\nWhether the linter should check for `[[wikilink]]` and print a warning if any nodes are missing them.\n\n###### `root?: string`\n\nThe root filename is needed to print the names of any orphan (unprocessed / unlinked) index / trunk files.\n\n### `print(tree: SemTree, print: boolean = true): string | undefined`\n\nPrint the contents of a tree to console logs and return the string if there was a valid tree to print. Returns `undefined` if the tree is invalid.\n\nExample output:\n\n```\nbk.how-to-read-a-book\n├── demanding-reader\n|   └── active-reading\n|       ├── reading-comprehension\n|       └── the-art-of-reading\n└── 4-levels-of-reading\n    ├── elementary-reading\n    ├── inspectional-reading\n    ├── analytical-reading\n    └── syntopical-reading\n```\n\n#### Parameters\n\n##### `tree: SemTree`\n\nAn instance of a [`SemTree`](#semtree-1).\n\n##### `print: boolean = true`\n\nSeeing this to `false` will suppress printing the tree to the console log and just return the string representation.\n\n### `update(tree: SemTree, subroot: string, content: Record\u003cstring, string\u003e, opts?: SemTreeOpts): SemTree | string;`\n\nA method to update a subtree within the semantic tree. (Best used to update individual `index` documents.) The given `tree` will be directly updated and the updated subtree nodes will be returned separately by `update()`.\n\n#### Parameters\n\n##### `tree: SemTree`\n\nA [`SemTree`](#semtree-1) object.\n\n##### `content: Record\u003cstring, string\u003e`\n\nA `Record` whose keys are entities (such as filenames) and values are content strings of those entities (such as file content).\n\n##### `subroot: string`\n\nName of the subroot node of the subtree to be replaced.\n\n##### `opts: SemTreeOpts`\n\nOptions object -- see [options](#Options) below.\n\n## Options\n\n### Config\n\n#### `virtualTrunk: boolean`\n\nWhether or not to include the semtree/index files themselves as nodes in the tree. This option is a useful toggle between 'tree-building' (non-virtual to allow for index/trunk file traversal) and 'tree-viewing' (virtual to eliminate unnecessary index/trunk files) states. Default is `false`. Best used for things like static site generation where updates are not a usual occurrence.\n\nNote: If `virtualTrunk` is set to `true`, the resulting tree will not be updatable via the `update` function.\n\n### Text / Lint\n\n#### `delimiter: string = 'semtree'`\n\nThe delimiter string to look for when identifying semtree indexes within a markdown file. Defaults to `'semtree'`.\n\n#### `indentKind: 'space' | 'tab' = 'space'`\n\nThe kind of whitespace expected for indentation of each level of the tree. The default is `'space'`.\n\n#### `indentSize: number = 2`\n\nThe size of each indentation level in the tree -- corresponds to number of spaces or tabs. The default is 2.\n\n#### `mkdnBullet: boolean = true`\n\nWhether or not to expect markdown bullets (`- `, `* `, `+ `).\n\n#### `wikiLink: boolean = true`\n\nWhether or not to expect `[[wikilink square brackets]]`. Default is `true`.\n\n### Functions\n\nOption functions are useful when keeping the state of the tree in-sync with some other source like an index or database.\n\n#### `graft: (parentText: string, childText: string) =\u003e void`\n\nA function to execute when each node is added to the tree.\n\n#### `prune: (parentText: string, childText: string) =\u003e void`\n\nA function to execute when each node is removed from the tree.\n\n#### `setRoot: (text: string) =\u003e void`\n\nA function that can return/operate on the text of the root of the tree when it is being set.\n\n## Context\n\n\u003e A semantic tree wends through concepts in semantic space, like a melody winds through harmonies in music.\n\nIn [personal knowledge management (pkm)](https://en.wikipedia.org/wiki/Personal_knowledge_management) systems, there are sometimes mechanisms to facilitate the creation and management of hierarchical structures: [Tag hierarchies](https://orgmode.org/manual/Tag-Hierarchy.html), [dynamic tables of contents](https://tiddlywiki.com/static/Table-of-Contents%2520Macros.html), note [metadata](https://github.com/SkepticMystic/breadcrumbs), [namespacing](https://github.com/wikibonsai/jekyll-namespaces), even using the [directory system itself](https://github.com/xpgo/obsidian-folder-note-plugin), adding a [folgezettel](https://zettelkasten.de/folgezettel/) to a [zettelkasten](https://zettelkasten.de/), are all attempts to create one unified hierarchy from one's atomic notes.\n\nBut none of these solutions accommodate the specific aim of trying to build a single \"semantic tree\" very well: Tag hierarchies and namespacing both suffer from branch length problems -- namespaces generally require the entire branch be spelled out to represent a node accurately, which restricts branch size and thus the size of the whole tree. Metadata pointers is better, but because relationships are built one by one between notes, making large changes to the tree itself is burdensome and visualizing the entire tree at once requires imagination. Using the file directory itself runs into The Duplicate Folder Problem, where using paths to represent branches would contain needless duplicates which correspond to a file of the same name at the same level.\n\nThis implementation attempts to ameliorate these issues with the primary focus on facilitating semantic tree cultivation.\n\nSide-Note: If you already have a collection of markdown notes, good candidates for index/tree(trunk) files might be \"[zettelkasten hubs](https://zettelkasten.de/posts/zettelkasten-hubs/)\" or \"[maps of content](https://notes.linkingyourthinking.com/Cards/MOCs+Overview)\" (will likely require some tweaking to fit the model required by this package).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwikibonsai%2Fsemtree","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwikibonsai%2Fsemtree","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwikibonsai%2Fsemtree/lists"}