oh-my-second-brain 0.1.9 → 0.2.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.
- package/CHANGELOG.md +89 -0
- package/adapters/claude-code/.claude-plugin/plugin.json +1 -1
- package/adapters/claude-code/skills/link/SKILL.md +87 -0
- package/adapters/codex/.codex-plugin/plugin.json +1 -1
- package/adapters/codex/skills/oms-link/SKILL.md +35 -0
- package/adapters/hermes/README.md +2 -0
- package/adapters/hermes/manifest.json +1 -1
- package/adapters/hermes/skills/link/SKILL.md +18 -0
- package/core/ontology/concepts/term.yaml +12 -0
- package/core/ontology/taxonomy.yaml +3 -0
- package/core/skills/link/SKILL.md +130 -0
- package/dist/capture/safe.d.ts +32 -2
- package/dist/capture/safe.js +223 -37
- package/dist/capture/safe.js.map +1 -1
- package/dist/cli/args.d.ts +2 -0
- package/dist/cli/args.js +6 -0
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/global-writeback.d.ts +31 -0
- package/dist/cli/global-writeback.js +73 -0
- package/dist/cli/global-writeback.js.map +1 -0
- package/dist/cli/host-commands.d.ts +30 -0
- package/dist/cli/host-commands.js +89 -0
- package/dist/cli/host-commands.js.map +1 -0
- package/dist/cli/linkify.d.ts +23 -0
- package/dist/cli/linkify.js +118 -0
- package/dist/cli/linkify.js.map +1 -0
- package/dist/cli/oms.d.ts +1 -0
- package/dist/cli/oms.js +52 -66
- package/dist/cli/oms.js.map +1 -1
- package/dist/cli/usage.js +11 -1
- package/dist/cli/usage.js.map +1 -1
- package/dist/conventions/write-protocol.d.ts +39 -0
- package/dist/conventions/write-protocol.js +44 -0
- package/dist/conventions/write-protocol.js.map +1 -0
- package/dist/core/runtime/assets.d.ts +6 -0
- package/dist/core/runtime/assets.js +27 -0
- package/dist/core/runtime/assets.js.map +1 -1
- package/dist/engine/graph/resolver.d.ts +17 -0
- package/dist/engine/graph/resolver.js +46 -11
- package/dist/engine/graph/resolver.js.map +1 -1
- package/dist/engine/linkify/apply.d.ts +56 -0
- package/dist/engine/linkify/apply.js +65 -0
- package/dist/engine/linkify/apply.js.map +1 -0
- package/dist/engine/linkify/josa.d.ts +58 -0
- package/dist/engine/linkify/josa.js +133 -0
- package/dist/engine/linkify/josa.js.map +1 -0
- package/dist/engine/linkify/mask.d.ts +29 -0
- package/dist/engine/linkify/mask.js +173 -0
- package/dist/engine/linkify/mask.js.map +1 -0
- package/dist/engine/linkify/suggest.d.ts +52 -0
- package/dist/engine/linkify/suggest.js +181 -0
- package/dist/engine/linkify/suggest.js.map +1 -0
- package/dist/engine/linkify/types.d.ts +68 -0
- package/dist/engine/linkify/types.js +12 -0
- package/dist/engine/linkify/types.js.map +1 -0
- package/dist/harness/surface-registry.d.ts +1 -0
- package/dist/harness/surface-registry.js +27 -0
- package/dist/harness/surface-registry.js.map +1 -1
- package/dist/harness/validation.js +30 -1
- package/dist/harness/validation.js.map +1 -1
- package/dist/install/claude-marketplace.d.ts +44 -0
- package/dist/install/claude-marketplace.js +89 -0
- package/dist/install/claude-marketplace.js.map +1 -0
- package/dist/install/claude.js +14 -7
- package/dist/install/claude.js.map +1 -1
- package/dist/install/codex.d.ts +15 -0
- package/dist/install/codex.js +15 -0
- package/dist/install/codex.js.map +1 -1
- package/dist/install/common.d.ts +11 -2
- package/dist/install/common.js +19 -12
- package/dist/install/common.js.map +1 -1
- package/dist/install/hermes.js +15 -15
- package/dist/install/hermes.js.map +1 -1
- package/dist/install/hosts.js +31 -15
- package/dist/install/hosts.js.map +1 -1
- package/dist/link/global-config.d.ts +22 -0
- package/dist/link/global-config.js +69 -0
- package/dist/link/global-config.js.map +1 -0
- package/dist/link/link.d.ts +6 -3
- package/dist/link/link.js +12 -2
- package/dist/link/link.js.map +1 -1
- package/dist/mcp/link-tools.d.ts +81 -0
- package/dist/mcp/link-tools.js +157 -0
- package/dist/mcp/link-tools.js.map +1 -0
- package/dist/mcp/server.d.ts +7 -0
- package/dist/mcp/server.js +107 -8
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/update-notice.d.ts +51 -0
- package/dist/mcp/update-notice.js +196 -0
- package/dist/mcp/update-notice.js.map +1 -0
- package/dist/update/update.d.ts +12 -0
- package/dist/update/update.js +2 -2
- package/dist/update/update.js.map +1 -1
- package/docs/install.md +38 -2
- package/docs/release.md +109 -17
- package/package.json +4 -2
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [Unreleased]
|
|
4
|
+
|
|
5
|
+
## [0.2.0] - 2026-08-19
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- The npm package now ships `CHANGELOG.md`, so release notes are available offline and in version control.
|
|
9
|
+
- Releases are published by CI from `oms-v*` tags with npm provenance and an auto-generated GitHub Release whose notes come from the CHANGELOG.
|
|
10
|
+
- Maintainers release with a single command: `npm run release -- <X.Y.Z>` rolls the `[Unreleased]` section into a versioned entry, bumps all version carriers (package.json, plugin manifests), commits, tags, and pushes atomically.
|
|
11
|
+
|
|
12
|
+
#### Note linking
|
|
13
|
+
|
|
14
|
+
- `term` is now a first-class concept in the core ontology, bound to a `terms/` folder. A term note is the one place you define a piece of vocabulary, and its new `aliases` frontmatter field lists every other way you write that word.
|
|
15
|
+
- Two MCP tools turn those terms into links. `oms_link_suggest` is read-only: it ranks the spans in a note that could point at a term note and hands back a hash of the content it looked at. `oms_link_apply` writes, but only the candidates you accepted, and only while that hash still matches, so a note you edited in the meantime is never overwritten by a stale suggestion.
|
|
16
|
+
- `oms linkify [--folder <f>] [--apply] [--yes]` does the same job in bulk over notes you already have. It reports and changes nothing by default; mutation needs both `--apply` and `--yes`.
|
|
17
|
+
- Matching understands Korean josa, so `아타락시아를` links as `[[ataraxia|아타락시아]]를` instead of being skipped for not matching the bare term.
|
|
18
|
+
- A note-linking skill ships to Claude, Codex, and Hermes, so each host knows the suggest-review-apply loop without you explaining it every session.
|
|
19
|
+
|
|
20
|
+
#### Updates and install
|
|
21
|
+
|
|
22
|
+
- A root `.claude-plugin/marketplace.json` makes OMS discoverable through Claude Code's native plugin marketplace. Claude installs now go through `claude plugin marketplace add` plus `claude plugin install oms@oms`, and fall back to the local plugin path when the marketplace flow can't complete, so offline and dev checkouts still work.
|
|
23
|
+
- The MCP server tells you when a newer version exists. It reads a 24-hour cache at boot and appends one line to its `instructions`; the registry lookup happens in a bounded background refresh, never on the startup path. `OMS_UPDATE_NOTICE=0` turns it off.
|
|
24
|
+
- A test and a CI release-tag guard now check that `marketplace.json` and `package.json` agree on the version, so a release can't ship a marketplace manifest pointing at the wrong build.
|
|
25
|
+
|
|
26
|
+
### Changed
|
|
27
|
+
|
|
28
|
+
- Wikilinks resolve through frontmatter `aliases`. `[[some-alias]]` used to resolve to nothing; it now finds the note that claims that alias, which means alias links count as real graph edges during retrieval.
|
|
29
|
+
- Installing several hosts at once no longer stops at the first failure. Each runtime is isolated, so a broken Codex config can't cost you your Claude and Hermes install.
|
|
30
|
+
- Hermes config writes are an upsert instead of a full overwrite: your comments and key ordering in `~/.hermes/config.yaml` survive an install or update.
|
|
31
|
+
- Claude's third-party marketplace auto-update stays off unless you turn it on. Install prints how to enable `extraKnownMarketplaces.<name>.autoUpdate` in `~/.claude/settings.json` rather than flipping it for you; the `claude` CLI owns that setting.
|
|
32
|
+
|
|
33
|
+
### Fixed
|
|
34
|
+
|
|
35
|
+
- The MCP server reports its real package version instead of a hardcoded `0.0.0`, so host-side version checks and bug reports show what you're actually running.
|
|
36
|
+
|
|
37
|
+
## [0.1.9] - 2026-08-14
|
|
38
|
+
|
|
39
|
+
### Added
|
|
40
|
+
- MCP `write` is now the single vault write window, with `mode: create | append | update`, returning `ask`, `inbox`, `written`, or `rejected` so the agent always knows what happened to a note (#52).
|
|
41
|
+
- A contract gate validates required fields, types, enums, and routing law before anything touches the vault. Extra keys survive the round trip (`additionalProperties: preserve`).
|
|
42
|
+
- Thin write skills for each host: `/oms-write` on Claude, `$oms-write` on Codex, and `write` on Hermes.
|
|
43
|
+
|
|
44
|
+
### Changed
|
|
45
|
+
- **Breaking:** capture skills are gone (`/oms-capture`, `$oms-capture`, Hermes `capture`), along with the MCP aliases `oms_capture_prepare` and `oms_capture_commit`. After upgrading, reinstall the host adapters with `oms update --yes` or `oms install --runtime <host> --vault <path> --yes`.
|
|
46
|
+
|
|
47
|
+
### Fixed
|
|
48
|
+
- Transitive production advisories cleared through same-major overrides for `hono`, `@hono/node-server`, `body-parser`, `fast-uri`, `ip-address`, `nanoid`, and `tar`. No new runtime dependencies were added.
|
|
49
|
+
|
|
50
|
+
## [0.1.8] - 2026-06-17
|
|
51
|
+
|
|
52
|
+
### Fixed
|
|
53
|
+
- Upstage Solar embeddings work again: the model id `solar-embedding-1-passage` didn't exist, so every embedding call returned HTTP 400. It's now `embedding-passage` (4096d).
|
|
54
|
+
- `embed()` guards its inputs. Empty input becomes a zero vector, and input over 4000 tokens is shrunk and retried, so one oversized or empty chunk no longer fails a whole vault sync.
|
|
55
|
+
- Transitive high-severity advisory in `hono` (pulled in by `@modelcontextprotocol/sdk`) resolved via `overrides: hono ^4.12.25`.
|
|
56
|
+
|
|
57
|
+
### Changed
|
|
58
|
+
- Claude Code, Codex, and Hermes adapter manifests are synced to 0.1.8.
|
|
59
|
+
|
|
60
|
+
## [0.1.7] - 2026-06-05
|
|
61
|
+
|
|
62
|
+
> No GitHub Release was published for the `oms-v0.1.7` tag. This section is reconstructed from the commits between `oms-v0.1.6` and `oms-v0.1.7`.
|
|
63
|
+
|
|
64
|
+
### Added
|
|
65
|
+
- Live graph retrieval plus fail-soft qmd fusion, so retrieval keeps working when the optional qmd side is unavailable.
|
|
66
|
+
- MCP retrieval context surfaced to hosts.
|
|
67
|
+
|
|
68
|
+
### Changed
|
|
69
|
+
- The npm package root is the runtime asset root: built releases resolve `core/` and `adapters/` from the package root, matching the source layout.
|
|
70
|
+
- `oh-my-second-brain` becomes the canonical repository, npm package, and installed command, with `oms` kept as a compatibility alias for existing MCP, skill, and vault `.oms` surfaces.
|
|
71
|
+
- Install docs point at 0.1.7 so the one-line and npm install examples resolve to the published version.
|
|
72
|
+
- The release workflow no longer requires an `NPM_TOKEN` preflight, allowing npm trusted publishing over OIDC while still using `NODE_AUTH_TOKEN` when the secret exists.
|
|
73
|
+
|
|
74
|
+
### Fixed
|
|
75
|
+
- Frontmatter diagnostics are tolerant: malformed frontmatter no longer blocks retrieve or build paths.
|
|
76
|
+
|
|
77
|
+
## [0.1.6] - 2026-06-02
|
|
78
|
+
|
|
79
|
+
### Changed
|
|
80
|
+
- The project is published to npm as `oh-my-second-brain`, while `oms` stays the CLI, MCP, skill, and repo slug.
|
|
81
|
+
- The installer defaults to the published npm package instead of `npx` against GitHub release URLs.
|
|
82
|
+
- Host MCP registration now points at the installed `oms mcp --vault ...` command.
|
|
83
|
+
|
|
84
|
+
## [0.1.5] - 2026-06-02
|
|
85
|
+
|
|
86
|
+
### Changed
|
|
87
|
+
- Oh My Second Brain is the project and display name; `oms` remains the short technical slug for the package, CLI, MCP server, skills, and release assets.
|
|
88
|
+
- Human-facing docs, adapter manifests, host shims, skills, CLI output, MCP tool titles, and installer text all use the Oh My Second Brain name.
|
|
89
|
+
- Release package URLs point at `oms-v0.1.5` / `oms-0.1.5.tgz`.
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: oms-link
|
|
3
|
+
description: Two-path note-linking skill — Path A (pre-write): compose a new note body that already carries [[wikilinks]] to existing term notes; Path B (retrofit): add missing links to existing notes via oms linkify or oms_link_apply.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Skill: oms-link (Claude Code)
|
|
7
|
+
|
|
8
|
+
Two-path skill — thin pointer to `core/skills/link`. Requires `OMS_VAULT`.
|
|
9
|
+
|
|
10
|
+
## Invocation
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
/link
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Which path?
|
|
17
|
+
|
|
18
|
+
Check **before acting**:
|
|
19
|
+
- **Path A (pre-write):** you are about to write the note → learn the term
|
|
20
|
+
vocabulary first, then draft a body that already contains `[[wikilinks]]`.
|
|
21
|
+
- **Path B (retrofit):** the note already exists → `oms linkify` or
|
|
22
|
+
MCP `oms_link_suggest` → `oms_link_apply`.
|
|
23
|
+
|
|
24
|
+
These paths are **mutually exclusive**.
|
|
25
|
+
|
|
26
|
+
## The link universe
|
|
27
|
+
|
|
28
|
+
Only notes bound to the `term` concept in `vault/.oms/taxonomy.yaml` are link
|
|
29
|
+
targets; their surface forms are the basename and the frontmatter `aliases`.
|
|
30
|
+
`0 term note(s) available as link targets` means the vault has no term layer —
|
|
31
|
+
build one with the `wiki` skill first.
|
|
32
|
+
|
|
33
|
+
Three engine rules, not negotiable:
|
|
34
|
+
- **Surface-anchored** — a link exists only where the term literally appears.
|
|
35
|
+
- **First occurrence only** — one link per target note per body.
|
|
36
|
+
- **Ambiguity is reported, never resolved** — `ambiguous: true` plus `rivalPaths`
|
|
37
|
+
means ask the user or skip the span.
|
|
38
|
+
|
|
39
|
+
Frontmatter, code, existing links, headings, URLs, and tags are masked out; a
|
|
40
|
+
"missing" link inside any of them is correct.
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Path A — pre-write linking
|
|
45
|
+
|
|
46
|
+
*(Only while drafting, so no retrofit pass is ever needed for this note.)*
|
|
47
|
+
|
|
48
|
+
### Agent-guided steps
|
|
49
|
+
|
|
50
|
+
1. Call MCP `oms_link_suggest` on an existing note in the same folder (or
|
|
51
|
+
`oms linkify --folder <folder>` in report mode) and read the `targetPath`
|
|
52
|
+
list — that is the vault's current term vocabulary.
|
|
53
|
+
2. Draft the body with those exact surface forms, writing `[[wikilinks]]` inline
|
|
54
|
+
at the first mention of each term.
|
|
55
|
+
3. Commit via MCP `write`. Host `Write`/`Edit` never touches vault `.md`.
|
|
56
|
+
4. Re-check with `oms_link_suggest`; zero candidates means the draft was already
|
|
57
|
+
fully linked.
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## Path B — retrofit existing notes
|
|
62
|
+
|
|
63
|
+
*(Only when the note is already on disk. Report first, always.)*
|
|
64
|
+
|
|
65
|
+
### Batch
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
oms linkify --folder notes # report only, writes nothing
|
|
69
|
+
oms linkify --folder notes --apply --yes # rewrite in place
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
`--apply` without `--yes` refuses before reading any note and writes nothing.
|
|
73
|
+
Show the report and get agreement before adding `--yes`. Writes go through the
|
|
74
|
+
capture kernel, so path safety and the concept contract hold.
|
|
75
|
+
|
|
76
|
+
### Single note
|
|
77
|
+
|
|
78
|
+
1. `oms_link_suggest { notePath, folder? }` → candidates + `baseContentHash`.
|
|
79
|
+
2. Present the candidates; drop ambiguous ones the user does not resolve.
|
|
80
|
+
3. `oms_link_apply { notePath, baseContentHash, candidateIds, folder? }` — pass
|
|
81
|
+
back the same `baseContentHash` and the same `folder` scope.
|
|
82
|
+
|
|
83
|
+
`oms_link_apply` refuses without writing on `note-changed`, `candidate-drift`,
|
|
84
|
+
or overlapping candidates. Re-run `oms_link_suggest` for fresh offsets instead
|
|
85
|
+
of retrying stale ids.
|
|
86
|
+
|
|
87
|
+
After a retrofit run, run `oms doctor` (non-blocking, exits 0).
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "oms",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Oh My Second Brain convention layer for Obsidian vaults \u2014 Codex native rules, skills, and MCP adapter.",
|
|
5
5
|
"_note": "oms install writes Codex MCP config, installs ~/.codex/rules/oms.md, and installs ~/.codex/skills/oms-*.",
|
|
6
6
|
"skills": "./skills/",
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: oms-link
|
|
3
|
+
description: Two-path note-linking skill — Path A (pre-write): compose a new note body that already carries [[wikilinks]] to existing term notes; Path B (retrofit): add missing links to existing notes via oms linkify or oms_link_apply.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# oms-link
|
|
7
|
+
|
|
8
|
+
Thin pointer to `core/skills/link`. Requires `OMS_VAULT`.
|
|
9
|
+
|
|
10
|
+
Determine which path applies before acting. These paths are **mutually exclusive**.
|
|
11
|
+
|
|
12
|
+
Link targets are ONLY notes bound to the `term` concept in `vault/.oms/taxonomy.yaml`; their surface forms are the basename and the frontmatter `aliases`. Three engine rules hold in both paths: a link exists only where the term literally appears (surface-anchored, never invented by retrieval); first occurrence per target note only; ambiguity (`ambiguous: true` + `rivalPaths`) is reported and never auto-resolved — ask the user or skip the span. Frontmatter, code, existing links, headings, URLs, and tags are masked out, so a "missing" link inside any of them is correct.
|
|
13
|
+
|
|
14
|
+
## Path A — pre-write linking
|
|
15
|
+
|
|
16
|
+
*(Use while drafting, so the note lands already linked and needs no retrofit.)*
|
|
17
|
+
|
|
18
|
+
1. Call MCP `oms_link_suggest` on an existing note in the same folder (or run `oms linkify --folder <folder>` in report mode) and read the `targetPath` list — that is the vault's current term vocabulary.
|
|
19
|
+
2. Draft the body with those exact surface forms, writing `[[wikilinks]]` inline at the first mention of each term.
|
|
20
|
+
3. Commit via MCP `write`. Host file edits never touch vault `.md`.
|
|
21
|
+
4. Re-check with `oms_link_suggest`: zero candidates means the draft was already fully linked.
|
|
22
|
+
|
|
23
|
+
NOTE: `0 term note(s) available as link targets` means the vault has no term layer yet — build one with the `oms-wiki` skill first.
|
|
24
|
+
|
|
25
|
+
## Path B — retrofit existing notes
|
|
26
|
+
|
|
27
|
+
*(Use when the note is already on disk. Report first, always.)*
|
|
28
|
+
|
|
29
|
+
1. `oms linkify --folder <folder>` — report only, writes nothing.
|
|
30
|
+
2. Show the report and get agreement before mutating anything.
|
|
31
|
+
3. Whole folder → `oms linkify --folder <folder> --apply --yes`. `--apply` without `--yes` refuses before reading any note and writes nothing.
|
|
32
|
+
4. Per-note acceptance → `oms_link_suggest { notePath, folder? }` returns candidates plus `baseContentHash`; drop unresolved ambiguous ones; then `oms_link_apply { notePath, baseContentHash, candidateIds, folder? }` with the SAME `baseContentHash` and the SAME `folder` scope.
|
|
33
|
+
5. Run `oms doctor` after a retrofit run (non-blocking, exits 0).
|
|
34
|
+
|
|
35
|
+
NOTE: `oms_link_apply` refuses without writing on `note-changed`, `candidate-drift`, or overlapping candidates. Re-run `oms_link_suggest` for fresh offsets instead of retrying stale ids. Every write — CLI or MCP — goes through the capture kernel, so path safety and the concept contract hold.
|
|
@@ -6,3 +6,5 @@ Installed by `oms install --runtime hermes` into:
|
|
|
6
6
|
- `~/.hermes/config.yaml` as `mcp_servers.oms`
|
|
7
7
|
|
|
8
8
|
The skill bundle mirrors Oh My Second Brain's write/retrieve/setup/doctor lifecycle and uses the Oh My Second Brain MCP server for runtime operations.
|
|
9
|
+
|
|
10
|
+
Unlike Claude Code (`claude plugin install`), Hermes exposes no native marketplace or plugin-update command, so this adapter stays OMS-managed: `oms update` reconciles it by re-running the same install path. `mcp_servers.oms` is edited surgically, leaving the rest of `~/.hermes/config.yaml` — including comments and key ordering — untouched.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "oms",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Oh My Second Brain convention layer for Obsidian vaults \u2014 Hermes skill bundle and MCP adapter.",
|
|
5
5
|
"_note": "oms install writes ~/.hermes/config.yaml mcp_servers.oms and installs skills under ~/.hermes/skills/knowledge-management/oms/.",
|
|
6
6
|
"skills": "./skills/"
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: link
|
|
3
|
+
description: Two-path note-linking skill — Path A (pre-write): compose a new note body that already carries [[wikilinks]] to existing term notes; Path B (retrofit): add missing links to existing notes via oms linkify or oms_link_apply.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# oms-link
|
|
7
|
+
|
|
8
|
+
Thin pointer to `core/skills/link`. Requires `OMS_VAULT`.
|
|
9
|
+
|
|
10
|
+
Link targets are ONLY notes bound to the `term` concept in `vault/.oms/taxonomy.yaml`; their surface forms are the basename and the frontmatter `aliases`. Three engine rules hold in both paths: a link exists only where the term literally appears (surface-anchored — retrieval never invents one); first occurrence per target note only; ambiguity (`ambiguous: true` + `rivalPaths`) is reported, never auto-resolved — ask the user or skip the span. Frontmatter, code, existing links, headings, URLs, and tags are masked out, so a "missing" link inside any of them is correct.
|
|
11
|
+
|
|
12
|
+
**Path A (pre-write) — use while drafting the note:** Call MCP `oms_link_suggest` on an existing note in the same folder (or run `oms linkify --folder <folder>` in report mode) and read the `targetPath` list — that is the vault's current term vocabulary. Draft the body with those exact surface forms, writing `[[wikilinks]]` inline at the first mention of each term, then commit via MCP `write` (host file edits never touch vault `.md`). Re-check with `oms_link_suggest`: zero candidates means the draft was already fully linked.
|
|
13
|
+
|
|
14
|
+
NOTE: `0 term note(s) available as link targets` means the vault has no term layer yet — build one with the `wiki` skill first.
|
|
15
|
+
|
|
16
|
+
**Path B (retrofit) — use when the note already exists:** Run `oms linkify --folder <folder>` first; it reports only and writes nothing. Show the report and get agreement before mutating. For a whole folder use `oms linkify --folder <folder> --apply --yes` — `--apply` without `--yes` refuses before reading any note and writes nothing. For per-note acceptance call `oms_link_suggest { notePath, folder? }` for candidates plus `baseContentHash`, drop unresolved ambiguous ones, then `oms_link_apply { notePath, baseContentHash, candidateIds, folder? }` with the SAME `baseContentHash` and the SAME `folder` scope. Run `oms doctor` afterwards (non-blocking, exits 0).
|
|
17
|
+
|
|
18
|
+
NOTE: `oms_link_apply` refuses without writing on `note-changed`, `candidate-drift`, or overlapping candidates. Re-run `oms_link_suggest` for fresh offsets instead of retrying stale ids. Every write — CLI or MCP — goes through the capture kernel, so path safety and the concept contract hold. The two paths are mutually exclusive.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
concept: term
|
|
2
|
+
intent: "A named unit of vocabulary the vault defines once and links to everywhere else."
|
|
3
|
+
folder: terms
|
|
4
|
+
fields:
|
|
5
|
+
- name: title
|
|
6
|
+
type: string
|
|
7
|
+
required: true
|
|
8
|
+
intent: "The canonical name of the term; the wikilink target other notes point at."
|
|
9
|
+
- name: aliases
|
|
10
|
+
type: list
|
|
11
|
+
required: false
|
|
12
|
+
intent: "Alternative surface forms and synonyms that should link back to this term."
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: link
|
|
3
|
+
version: 0.1.0
|
|
4
|
+
description: Two-path note-linking skill — Path A (pre-write): compose a new note body that already carries [[wikilinks]] to existing term notes; Path B (retrofit): add missing links to notes that already exist, via oms linkify or oms_link_apply.
|
|
5
|
+
trigger: /link
|
|
6
|
+
tags: [link, wikilink, linkify, term, backlink, second-brain, oms]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## link
|
|
10
|
+
|
|
11
|
+
Keep the vault interlinked. There is no autolink daemon — follow this recipe as
|
|
12
|
+
the agent. Requires `OMS_VAULT` (or a resolved vault target).
|
|
13
|
+
|
|
14
|
+
## Which path?
|
|
15
|
+
|
|
16
|
+
Determine which path applies **before acting**:
|
|
17
|
+
|
|
18
|
+
| | Path A — pre-write | Path B — retrofit |
|
|
19
|
+
|---|---|---|
|
|
20
|
+
| **When to use** | You are about to write or rewrite a note | The note already exists on disk |
|
|
21
|
+
| **Entry point** | MCP `oms_link_suggest` on a sibling note, then MCP `write` | `oms linkify` (report) → `oms linkify --apply --yes`, or MCP `oms_link_suggest` → `oms_link_apply` |
|
|
22
|
+
| **Writes to** | The new note only | Existing notes in scope |
|
|
23
|
+
| **Prerequisite** | Term notes exist to link at | Term notes exist to link at |
|
|
24
|
+
|
|
25
|
+
These paths are **mutually exclusive and never combined in a single invocation.**
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## The link universe — term notes only
|
|
30
|
+
|
|
31
|
+
A link target is a note bound to the `term` concept in `vault/.oms/taxonomy.yaml`.
|
|
32
|
+
Notes bound to any other concept are never proposed as targets, in either path.
|
|
33
|
+
Surface forms of a target are its **basename** and its frontmatter **`aliases`**.
|
|
34
|
+
|
|
35
|
+
If a run reports `0 term note(s) available as link targets`, the vault has no
|
|
36
|
+
term layer yet — build one with the `wiki` skill (Path B) first. Linking is not
|
|
37
|
+
the tool that creates vocabulary; it is the tool that connects it.
|
|
38
|
+
|
|
39
|
+
## Three rules the engine enforces — do not fight them
|
|
40
|
+
|
|
41
|
+
1. **Surface-anchored.** A candidate exists only because the term's basename or
|
|
42
|
+
alias literally appears in the note. Retrieval never invents a link. Do not
|
|
43
|
+
hand-add a `[[link]]` to a note whose title does not appear in the text —
|
|
44
|
+
write the sentence that mentions it instead.
|
|
45
|
+
2. **First occurrence only.** One link per target note per body. A term linked
|
|
46
|
+
ten times is noise, not navigation.
|
|
47
|
+
3. **Ambiguity is reported, never auto-resolved.** When 2+ notes claim one span,
|
|
48
|
+
the candidate carries `ambiguous: true` and every `rivalPaths` entry. Ask the
|
|
49
|
+
user which note is meant, or skip the span. Never guess.
|
|
50
|
+
|
|
51
|
+
Protected regions are masked out before matching: frontmatter, fenced and inline
|
|
52
|
+
code, existing wikilinks and markdown links, image embeds, HTML, headings, URLs,
|
|
53
|
+
block ids, and tags. A "missing" link inside any of those is correct behavior.
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Path A — pre-write linking
|
|
58
|
+
|
|
59
|
+
*(Use while drafting. The goal is that the body arrives on disk already linked,
|
|
60
|
+
so no retrofit pass is ever needed for this note.)*
|
|
61
|
+
|
|
62
|
+
### Agent-guided steps
|
|
63
|
+
|
|
64
|
+
1. Establish the vocabulary **before** drafting: call MCP `oms_link_suggest` on
|
|
65
|
+
an existing note in the same folder (or run `oms linkify --folder <folder>`
|
|
66
|
+
in report mode) and read the `targetPath` list. That list IS the vault's
|
|
67
|
+
current term vocabulary.
|
|
68
|
+
2. Draft the body using those exact surface forms, and write `[[wikilinks]]`
|
|
69
|
+
inline as you compose — first mention of each term only.
|
|
70
|
+
3. Commit through MCP `write`. Host `Write`/`Edit` never touches vault `.md`.
|
|
71
|
+
4. Re-check with `oms_link_suggest` on the committed note. Zero candidates means
|
|
72
|
+
the draft was already fully linked; remaining candidates are terms you missed.
|
|
73
|
+
|
|
74
|
+
### Example agent steps (Path A)
|
|
75
|
+
|
|
76
|
+
```
|
|
77
|
+
User: "Write a note about our retrieval pipeline."
|
|
78
|
+
|
|
79
|
+
1. oms_link_suggest { notePath: "notes/software-engineering/indexing.md" }
|
|
80
|
+
→ targets: notes/terms/embedding.md, notes/terms/reranker.md, notes/terms/chunk.md
|
|
81
|
+
2. Draft body mentioning embedding / reranker / chunk, each first mention
|
|
82
|
+
written as [[embedding]], [[reranker]], [[chunk]]
|
|
83
|
+
3. write { mode: "create", notePath: "notes/software-engineering/retrieval-pipeline.md", ... }
|
|
84
|
+
4. oms_link_suggest on the new note → 0 candidates ✓
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## Path B — retrofit existing notes
|
|
90
|
+
|
|
91
|
+
*(Use when the note is already on disk. Report first, always.)*
|
|
92
|
+
|
|
93
|
+
### Batch — `oms linkify` (whole vault or one folder)
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
oms linkify --folder notes # report only, writes nothing
|
|
97
|
+
oms linkify --folder notes --apply --yes # rewrite in place
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
`--apply` without `--yes` refuses before reading a single note and writes
|
|
101
|
+
nothing. Show the user the report and get agreement before adding `--yes`.
|
|
102
|
+
Every write still goes through the capture kernel, so path safety and the
|
|
103
|
+
concept contract hold exactly as they do for MCP `write`.
|
|
104
|
+
|
|
105
|
+
### Single note — `oms_link_suggest` → `oms_link_apply`
|
|
106
|
+
|
|
107
|
+
Use when the user wants to accept some candidates and reject others:
|
|
108
|
+
|
|
109
|
+
1. `oms_link_suggest { notePath, folder? }` → candidates + `baseContentHash`.
|
|
110
|
+
2. Present the candidates; drop ambiguous ones the user does not resolve.
|
|
111
|
+
3. `oms_link_apply { notePath, baseContentHash, candidateIds, folder? }` —
|
|
112
|
+
pass back the **same** `baseContentHash` and the **same** `folder` scope.
|
|
113
|
+
|
|
114
|
+
`oms_link_apply` refuses without writing when the note changed since the
|
|
115
|
+
suggest call (`note-changed`), when a candidate's text moved (`candidate-drift`),
|
|
116
|
+
or when accepted candidates overlap. A refusal is a correct outcome: re-run
|
|
117
|
+
`oms_link_suggest` for fresh offsets rather than retrying the stale ids.
|
|
118
|
+
|
|
119
|
+
### Example agent steps (Path B)
|
|
120
|
+
|
|
121
|
+
```
|
|
122
|
+
User: "Link up the notes I wrote last week."
|
|
123
|
+
|
|
124
|
+
1. oms linkify --folder notes ← report only
|
|
125
|
+
→ 14 candidate(s) across 9 note(s); 2 marked ambiguous
|
|
126
|
+
2. Show the report; ask which note the 2 ambiguous spans mean
|
|
127
|
+
3. Accepted whole folder → oms linkify --folder notes --apply --yes
|
|
128
|
+
Accepted per-note → oms_link_suggest → oms_link_apply with the chosen ids
|
|
129
|
+
4. Run `oms doctor` (non-blocking, exits 0)
|
|
130
|
+
```
|
package/dist/capture/safe.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type WriteContractViolation, type WriteFieldDescriptor } from "../conventions/write-contract.js";
|
|
2
|
-
import type
|
|
2
|
+
import { type WriteRejection, type WriteReceipt, type WriteTargetSource } from "../conventions/write-protocol.js";
|
|
3
|
+
import type { Concept, Ontology } from "../core/ontology/types.js";
|
|
3
4
|
export type CapturePrepareAction = "ready" | "ask-missing-fields" | "route-to-inbox";
|
|
4
5
|
export type CaptureWriteMode = "create" | "append";
|
|
5
6
|
export type WriteMode = "create" | "append" | "update";
|
|
@@ -23,8 +24,13 @@ export interface CapturePlan {
|
|
|
23
24
|
violations: WriteContractViolation[];
|
|
24
25
|
reason?: string;
|
|
25
26
|
}
|
|
27
|
+
export interface WriteTarget {
|
|
28
|
+
vault: string;
|
|
29
|
+
source: WriteTargetSource;
|
|
30
|
+
}
|
|
26
31
|
export interface CaptureCommitInput {
|
|
27
32
|
vault: string;
|
|
33
|
+
source: WriteTargetSource;
|
|
28
34
|
ontology: Ontology;
|
|
29
35
|
notePath: string;
|
|
30
36
|
frontmatter: Record<string, unknown>;
|
|
@@ -37,7 +43,7 @@ export interface CaptureCommitResult {
|
|
|
37
43
|
notePath: string;
|
|
38
44
|
}
|
|
39
45
|
export interface WriteNoteInput {
|
|
40
|
-
|
|
46
|
+
target: WriteTarget;
|
|
41
47
|
ontology: Ontology;
|
|
42
48
|
mode: WriteMode;
|
|
43
49
|
dryRun: boolean;
|
|
@@ -47,6 +53,12 @@ export interface WriteNoteInput {
|
|
|
47
53
|
notePath?: string;
|
|
48
54
|
frontmatter?: Record<string, unknown>;
|
|
49
55
|
body?: string;
|
|
56
|
+
/**
|
|
57
|
+
* Test-only DI seam for the postcondition read-back. Defaults to reading the
|
|
58
|
+
* persisted file with node:fs/promises. Exists so tests can force a
|
|
59
|
+
* postcondition failure deterministically without mocking node:fs/promises.
|
|
60
|
+
*/
|
|
61
|
+
readBack?: (fullPath: string) => Promise<string>;
|
|
50
62
|
}
|
|
51
63
|
export interface WriteNoteResult {
|
|
52
64
|
status: WriteStatus;
|
|
@@ -59,8 +71,26 @@ export interface WriteNoteResult {
|
|
|
59
71
|
missingFields: string[];
|
|
60
72
|
violations: WriteContractViolation[];
|
|
61
73
|
reason?: string;
|
|
74
|
+
rejection?: WriteRejection;
|
|
75
|
+
receipt?: WriteReceipt;
|
|
62
76
|
}
|
|
63
77
|
export declare function safeVaultNotePath(vault: string, notePath: string): string;
|
|
64
78
|
export declare function prepareCapture(input: CapturePrepareInput): CapturePlan;
|
|
79
|
+
export interface StagedEvaluation {
|
|
80
|
+
ok: boolean;
|
|
81
|
+
violations: WriteContractViolation[];
|
|
82
|
+
frontmatter: Record<string, unknown>;
|
|
83
|
+
body: string;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Acceptance Criteria - staged evaluation (pre-persist).
|
|
87
|
+
*
|
|
88
|
+
* Parses the note content that is ABOUT to be written and re-runs the kernel
|
|
89
|
+
* write contract against the RENDERED frontmatter. This catches divergence
|
|
90
|
+
* between the caller-supplied frontmatter and what actually lands on disk
|
|
91
|
+
* (yaml round-trip artifacts such as `NaN` rendering as `.nan` and parsing
|
|
92
|
+
* back as `null`).
|
|
93
|
+
*/
|
|
94
|
+
export declare function evaluateStagedNote(stagedContent: string, concept: Concept, notePath: string, strictZones: ReadonlySet<string>): StagedEvaluation;
|
|
65
95
|
export declare function writeNote(input: WriteNoteInput): Promise<WriteNoteResult>;
|
|
66
96
|
export declare function commitCapture(input: CaptureCommitInput): Promise<CaptureCommitResult>;
|