@specdevship/project-docs-core
v0.3.0
Published
Sync engine, rules, link formats and PRIVATE.md decoder shared by Project Docs' VS Code extension, CLI and Obsidian plugin.
Maintainers
Readme
@specdevship/project-docs-core
The sync engine behind Project Docs: it keeps a repo's docs folder and a folder in an Obsidian vault in sync on the same machine. The VS Code extension, the project-docs command line and the Obsidian plugin all use it, so they apply exactly the same rules.
It provides:
- the three-way sync planner and its executor, with a journal so an interrupted pass can be finished safely;
- the privacy guards that keep private notes, their names and real values out of the repo;
- the link records (
.project-docs.json,.project-docs.local.json) and the link note; - per-file sync rules read from frontmatter;
- a PRIVATE.md decoder that passes the standard's conformance cases;
- wikilink conversion, line-ending handling, and the cross-process lock.
It's published for the extension, the plugin and the CLI. Most people want the extension or the command line instead.
Requires Node 22 or newer. It's an ES module.
Stability
Versions before 1.0 are prereleases: any 0.x release may change the API. Pin an exact version.
Entry points
The package root exports everything, including the engine. The Obsidian plugin never runs sync passes, so the parts it needs also have their own entry points:
| Import | What it's for |
| ------------------------------------------ | ---------------------------------------------------------------------------------------------- |
| @specdevship/project-docs-core/rules | Reading and changing a note's sync rule and other frontmatter properties. |
| @specdevship/project-docs-core/records | The link records and the link note: reading them, and the obsidian:// and vscode:// links. |
| @specdevship/project-docs-core/privatemd | Decoding PRIVATE.md, restoring real values, and finding or replacing real values. |
| @specdevship/project-docs-core/guards | The private-subfolder test and the private-files list format. |
| @specdevship/project-docs-core/links | Finding, resolving and converting links. |
| @specdevship/project-docs-core/status | The files the engine and the plugin share in <vault folder>/.project-docs/. |
Sync rules (/rules)
readRule(text)returns the note'sproject-docsrule ("sync"when there's none), or anInvalidRulewith a message; test it withisInvalidRule.setRule(text, rule)changes only theproject-docsproperty and leaves the rest of the file byte-identical.SYNC_RULESlists the rules;RULE_PROPERTYis the property's name.readProperty,setPropertiesandremovePropertiesdo the same for any frontmatter property. They throwFrontmatterErroron frontmatter they can't edit safely.
Link records and the link note (/records)
readLink(repoRoot)reads both records and reports whether the repo is linked.readLinkNoteRepo(linkNotePath)returns the repo path a link note records.obsidianOpenUri(notePath, wsl)andvscodeOpenUri(repoPath, wsl)build the links between the two apps, including from a WSL repo.validateCommittedandvalidateLocalcheck a record against its schema.
PRIVATE.md (/privatemd)
parsePlaceholders(text)reads the## Placeholderstable into a mapping, and throwsPrivateMdError(with akind) for anything the standard calls an error.loadPlaceholders(file)does the same for a file path.restore(text, mapping)replaces each placeholder with its real value, applying the standard's boundary rule.findRealValues(text, mapping)andfindRealValueRanges(text, mapping)find real values in text; their results name placeholders only, never the values.replaceRealValues(text, mapping)puts the placeholders back.placeholderPattern(placeholder)is the decoder's own regular expression source for one placeholder, with the standard's boundary rule, for checking that a rendered view has no placeholders left.
Private files (/guards)
inPrivateSubfolder(key, privateSubfolder)tells whether a vault key is inside the private subfolder.parsePrivateFilesList(text)readsprivate-files.json. A list it can't read comes back asINVALID_PRIVATE_FILES, and the engine then holds back every attachment.
Links (/links)
findLinks, createResolver and convertWikilinks find a note's links, resolve them the way Obsidian does, and convert wikilinks to Markdown links.
Shared vault files (/status)
statusFilePath(vaultFolderPath)andparseStatusFile(text)read the engine'sstatus.json: one item per problem, each with akind,path,message,reasonsand an optionalfix. It never contains real values.parseStatusFilereturnsnullfor a missing or unreadable file, or one in a newer format.privateFilesPath(vaultFolderPath)andserializePrivateFilesList(files)write the private-files list, which the plugin owns. The engine only reads it.confirmationsPath(vaultFolderPath)andserializeConfirmations(files)writeconfirmations.json: the notes the user agreed in Obsidian may stop being private, each as{ path, sha256 }. The hash ismarkdownHash(text)of the note's content at that moment, also exported here. The engine syncs a held note only while its path and hash match an entry, so a confirmation never covers a later edit. Remove an entry once its note has synced or been deleted.
License
MIT
