@theglitchking/semantic-pages 0.6.0 → 0.6.2
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/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/dist/{chunk-P7BHYKVE.js → chunk-BJPL3RYK.js} +17 -7
- package/dist/chunk-BJPL3RYK.js.map +1 -0
- package/dist/{chunk-XNEE7Z2S.js → chunk-UCATYEVZ.js} +2 -2
- package/dist/chunk-UCATYEVZ.js.map +1 -0
- package/dist/cli/index.js +3 -2
- package/dist/cli/index.js.map +1 -1
- package/dist/core/index.js +2 -2
- package/dist/indexer-6QLY6KBC.js +7 -0
- package/dist/mcp/server.js +14 -7
- package/dist/mcp/server.js.map +1 -1
- package/package.json +2 -1
- package/skills/semantic-first/SKILL.md +117 -0
- package/skills/semantic-first/evals/evals.json +37 -0
- package/skills/semantic-first/references/vault-frontmatter.md +133 -0
- package/dist/chunk-P7BHYKVE.js.map +0 -1
- package/dist/chunk-XNEE7Z2S.js.map +0 -1
- package/dist/indexer-UEYFOOER.js +0 -7
- /package/dist/{indexer-UEYFOOER.js.map → indexer-6QLY6KBC.js.map} +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@theglitchking/semantic-pages",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.2",
|
|
4
4
|
"description": "Semantic search + knowledge graph MCP server for markdown vaults. Claude Code plugin with auto-wiring for .claude/.vault and read-only .documentation companion when hit-em-with-the-docs is installed.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/core/index.js",
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
"dist",
|
|
18
18
|
".claude-plugin",
|
|
19
19
|
"hooks",
|
|
20
|
+
"skills/semantic-first",
|
|
20
21
|
"README.md",
|
|
21
22
|
"LICENSE"
|
|
22
23
|
],
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: semantic-first
|
|
3
|
+
description: Route documentation lookups, internal procedure questions, repo architectural/vocabulary/convention questions, and research tasks through the semantic-pages and semantic-vault MCP servers BEFORE falling back to any other search strategy. Use this skill whenever the user asks how something works in this repo, what the convention is, where a procedure or guide lives, what the project calls something, why code is structured a certain way, OR asks any research question — comparative evaluations ("is there a better X"), tool/library/API recommendations ("what's the best X for Y"), best-of surveys, or any "look into X" / "research X" / "investigate X" phrasing. Also use for all research note-taking — findings go into .claude/.vault as markdown with structured frontmatter, not scattered across the conversation. Triggers on prose/conceptual questions about a codebase or domain, not on pure code-symbol lookups (those are Grep jobs). Ships with the semantic-pages plugin and probes MCP availability at runtime — the docs-lookup flow degrades gracefully when hit-em-with-the-docs is not installed, and the vault-research flow degrades gracefully when semantic-pages itself isn't wired up.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# semantic-first
|
|
7
|
+
|
|
8
|
+
Route documentation and research through the semantic-pages ecosystem before anything else.
|
|
9
|
+
|
|
10
|
+
## Why this skill exists
|
|
11
|
+
|
|
12
|
+
The semantic-pages plugin installs one or two MCP servers depending on what else is present:
|
|
13
|
+
|
|
14
|
+
- **`semantic-vault`** — semantic + keyword search and full read/write over `.claude/.vault/`, a research scratchpad that persists across sessions. Available whenever the semantic-pages plugin is installed.
|
|
15
|
+
- **`semantic-pages`** — the same engine pointed at a read-only docs index (by default `.documentation/`). Available only when the companion plugin `hit-em-with-the-docs` is also installed, since that's the plugin that owns the docs directory structure.
|
|
16
|
+
|
|
17
|
+
Without explicit routing, Claude's default reflex is to `Grep`/`Glob` the repo when the user asks a conceptual question, and to dump web-research findings straight into the chat where they're lost when the session ends. Both defaults waste the infrastructure the user set up. This skill corrects that.
|
|
18
|
+
|
|
19
|
+
## Two flows, gated independently
|
|
20
|
+
|
|
21
|
+
This skill has two flows, and they depend on different MCP servers. Probe each one independently — don't assume that if one works the other does, and don't no-op the whole skill just because half of it is unavailable.
|
|
22
|
+
|
|
23
|
+
| Flow | What it does | Depends on | Tool namespace |
|
|
24
|
+
|---|---|---|---|
|
|
25
|
+
| **A. Docs lookup** | Find how something works in this repo | `hit-em-with-the-docs` being installed | `mcp__semantic-pages__*` |
|
|
26
|
+
| **B. Research notes** | Capture findings to a persistent vault | `semantic-pages` being installed (which it is, since this skill ships with it — but the user can still have the MCP misconfigured) | `mcp__semantic-vault__*` |
|
|
27
|
+
|
|
28
|
+
## Flow A — documentation / procedure / repo-nuance lookups
|
|
29
|
+
|
|
30
|
+
**Triggers on prose questions about the current repo or project:**
|
|
31
|
+
|
|
32
|
+
- "How does this repo handle X?"
|
|
33
|
+
- "Where's the guide for Y?"
|
|
34
|
+
- "What's our process for Z?"
|
|
35
|
+
- "Why is this structured this way?"
|
|
36
|
+
- "What does this project call X?"
|
|
37
|
+
- "Is there a convention for Y?"
|
|
38
|
+
- "Is there a `<thing>` in this repo that does X?" — note this is **prose about whether a capability exists**, which semantic-pages answers well from design docs, even though a naive read would send it to Grep.
|
|
39
|
+
|
|
40
|
+
**Does NOT trigger on code-symbol lookups** like "where is function `parseAuth` defined" or "show me the `User` class" — those are `Grep`/`Glob` jobs. The line is *prose vs. symbol*: if the answer is a line or two of prose that would typically live in a design doc, README, or procedure, this flow applies. If the answer is a specific file path and line number that only exists in source code, it doesn't.
|
|
41
|
+
|
|
42
|
+
**When in doubt, try semantic-pages first anyway.** The cost of one extra `search_hybrid` call is tiny; the cost of grep-spelunking for something that's documented in plain English is several wasted minutes.
|
|
43
|
+
|
|
44
|
+
**Procedure:**
|
|
45
|
+
|
|
46
|
+
1. **Probe availability** once per task. Call `mcp__semantic-pages__get_stats` with no arguments. If the tool is not in the session, or `get_stats` errors, or the stats show an empty/missing index, drop to [Fallback: docs lookup](#fallback-docs-lookup) below. Otherwise the index is live and you can search it.
|
|
47
|
+
2. **Search with hybrid retrieval.** Use `mcp__semantic-pages__search_hybrid` with the user's question (or a condensed form of it) as the query. Hybrid search combines semantic embeddings with keyword matching and outperforms either individually for most real questions. Keep `top_k` modest (5–10); you can always widen if the first pass is thin.
|
|
48
|
+
3. **Read the top hits in full.** For each promising result, call `mcp__semantic-pages__read_note`. Do not answer from search snippets alone — snippets lose surrounding context and produce confident-sounding wrong answers. Reading 2–3 full notes is almost always cheaper than correcting a wrong answer later.
|
|
49
|
+
4. **Traverse the graph when the question is multi-part.** If a note references `[[wikilinks]]` or has `dependencies:` / `related:` frontmatter, use `mcp__semantic-pages__forwardlinks` / `backlinks` to follow the chain. This is especially valuable for questions like "how does X work end-to-end" or "what's the deploy process for each environment" — where the full answer is spread across linked notes.
|
|
50
|
+
5. **Answer from what you read, with filename citations.** Tell the user which notes you pulled the answer from so they can jump to the source. If the answer genuinely isn't in the index, say so explicitly — don't backfill with guesses or pivot silently to web search.
|
|
51
|
+
|
|
52
|
+
### Fallback: docs lookup
|
|
53
|
+
|
|
54
|
+
If step 1 indicates the `semantic-pages` MCP is unavailable (the `hit-em-with-the-docs` plugin isn't installed, the `.mcp.json` isn't wired up, or the index is empty), degrade:
|
|
55
|
+
|
|
56
|
+
- Tell the user, in one sentence, that the semantic docs index isn't available and you're falling back to direct file search. Visibility matters — it lets them fix the config and get the fast path back.
|
|
57
|
+
- Use `Glob` to enumerate `*.md` files in likely locations: `docs/`, `documentation/`, `.documentation/`, `README.md`, `CONTRIBUTING.md`, `.claude/`, and the repo root.
|
|
58
|
+
- Use `Grep` for keywords from the question across those files.
|
|
59
|
+
- If nothing promising turns up in markdown, *then* expand to `Grep` over source code as a last resort.
|
|
60
|
+
|
|
61
|
+
This is strictly worse than semantic search — but a visible fallback is much better than a silent one.
|
|
62
|
+
|
|
63
|
+
## Flow B — research tasks (internal or external)
|
|
64
|
+
|
|
65
|
+
**Triggers on evaluative, comparative, or exploratory questions:**
|
|
66
|
+
|
|
67
|
+
- "Is there a better X than Y?"
|
|
68
|
+
- "What's the best X for Y?"
|
|
69
|
+
- "Look into X."
|
|
70
|
+
- "Research Y."
|
|
71
|
+
- "Investigate how other projects solve Z."
|
|
72
|
+
- Any request that would naturally involve `WebSearch` / `WebFetch`.
|
|
73
|
+
|
|
74
|
+
**Also triggers on mixed questions** like "how does this repo use X, and is there a better alternative?" — handle the repo half via Flow A, then continue into Flow B for the alternatives half. The two flows compose naturally.
|
|
75
|
+
|
|
76
|
+
**Procedure:**
|
|
77
|
+
|
|
78
|
+
1. **If the question has a repo-context component, do Flow A first.** "How do we currently handle X" before "is there a better X" — you can't evaluate alternatives without understanding the current solution.
|
|
79
|
+
2. **Probe vault availability.** Call `mcp__semantic-vault__get_stats`. If it errors or the tool isn't present, drop to [Fallback: vault notes](#fallback-vault-notes). Otherwise the vault is live.
|
|
80
|
+
3. **Check the vault for prior research.** Call `mcp__semantic-vault__search_hybrid` with the topic. If past-you already investigated this, reuse the note — update it in place via `mcp__semantic-vault__update_note` rather than starting over. This is the main reason the vault exists.
|
|
81
|
+
4. **Do the actual research.** Use `WebSearch` / `WebFetch` / whatever tools are appropriate. Read multiple sources where you can — a single source is a single opinion.
|
|
82
|
+
5. **Write the findings to `.claude/.vault` as you go.** Use `mcp__semantic-vault__create_note` (or `update_note`). The full frontmatter format is in `references/vault-frontmatter.md` — read that file once per session before writing your first note.
|
|
83
|
+
6. **Link the note.** Use `[[wikilinks]]` to related notes in the body, and fill in `dependencies` / `related` / `supersedes` in the frontmatter so future sessions can traverse the graph. Unlinked notes are graveyards — they get written once and never found again.
|
|
84
|
+
7. **Answer the user from the note you just wrote.** The note is the canonical artifact; the chat response is a short summary with a pointer to the filename. The user's future self (or another session) can re-read the note; they can't re-read the chat.
|
|
85
|
+
|
|
86
|
+
### Fallback: vault notes
|
|
87
|
+
|
|
88
|
+
If step 2 indicates the `semantic-vault` MCP is unavailable but `.claude/.vault/` exists as a directory (or you can create it), use the `Write` tool directly — still follow the frontmatter format in `references/vault-frontmatter.md`. A session without the MCP server still has a filesystem; don't skip note-taking just because the semantic indexing won't happen. The index can be rebuilt later; lost notes can't.
|
|
89
|
+
|
|
90
|
+
If neither the MCP nor a filesystem `.claude/.vault/` directory is available *and* you can't create it (e.g., permission error), tell the user and put the findings directly in the chat as a clearly-marked "research findings" block — better than losing them entirely.
|
|
91
|
+
|
|
92
|
+
## The vault frontmatter is non-negotiable
|
|
93
|
+
|
|
94
|
+
Every `.md` file created in `.claude/.vault/` must start with YAML frontmatter. See `references/vault-frontmatter.md` for the full field reference and rationale. The short version is that the format is adapted from the 22-field docs frontmatter used elsewhere in the semantic-pages ecosystem, with the RAG-chunker fields dropped and two research-specific fields (`source`, `confidence`) added. A note without frontmatter won't index properly and becomes invisible to future searches — which defeats the whole point of writing it.
|
|
95
|
+
|
|
96
|
+
## Interaction with other Claude systems
|
|
97
|
+
|
|
98
|
+
- **The memory system** (`~/.claude/projects/**/memory/`) is for durable facts about the user and the project (who they are, what they prefer, what constraints are in play). It is NOT for research artifacts. Research goes in the vault; facts go in memory. When in doubt: *is this a body of findings on a specific topic?* → vault. *Is this a fact that would be useful to a completely different task?* → memory.
|
|
99
|
+
- **`Grep`/`Glob`** are for code-symbol lookups and as a Flow A fallback. Not the default for conceptual prose questions when semantic-pages is available.
|
|
100
|
+
- **`WebSearch`/`WebFetch`** are research *inputs* — their outputs should flow into vault notes, not into the conversation only. The chat response summarizes; the note preserves.
|
|
101
|
+
|
|
102
|
+
## What success looks like
|
|
103
|
+
|
|
104
|
+
- User asks "how does this repo handle auth?" → you call `search_hybrid`, read 2–3 notes, answer with filename citations. Tool call budget: 3–5. No `Grep`.
|
|
105
|
+
- User asks "what's the best CRM for small businesses?" → you search the vault for prior research (none), do web research, write `.claude/.vault/crm-smb-evaluation.md` with full frontmatter, answer from the note.
|
|
106
|
+
- User asks the same CRM question a week later → you find the existing note in the vault, skim it, either answer directly or update it with fresh findings and bump `last_updated`.
|
|
107
|
+
- User installs the skill in a repo without `hit-em-with-the-docs` → Flow A probe fails, you tell the user once, fall back to Glob/Grep for docs lookup. Flow B (vault) still works because `semantic-vault` is available.
|
|
108
|
+
- User's `.mcp.json` is broken entirely → both probes fail, you tell them, fall back to filesystem for both flows. You don't pretend everything's fine.
|
|
109
|
+
|
|
110
|
+
## What failure looks like
|
|
111
|
+
|
|
112
|
+
- Grep'ing the repo for a conceptual question when `mcp__semantic-pages__search_hybrid` is sitting right there.
|
|
113
|
+
- Writing research findings into the chat response and leaving no artifact on disk.
|
|
114
|
+
- Writing vault notes without frontmatter, or with ad-hoc frontmatter that won't index.
|
|
115
|
+
- Silently no-op'ing when an MCP server is missing instead of telling the user and falling back.
|
|
116
|
+
- Triggering on code-symbol lookups ("where is `parseAuth` defined") and wasting tool calls on semantic search for something `Grep` would answer in 100ms.
|
|
117
|
+
- Assuming both MCP servers travel together. They don't — the vault ships with this plugin, the docs index ships with `hit-em-with-the-docs`. Probe them separately.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"skill_name": "semantic-first",
|
|
3
|
+
"evals": [
|
|
4
|
+
{
|
|
5
|
+
"id": 1,
|
|
6
|
+
"name": "flow-a-capability-existence-user-delete",
|
|
7
|
+
"flow": "A",
|
|
8
|
+
"prompt": "Is there a user delete in this repo that deletes a user AND their data completely?",
|
|
9
|
+
"expected_output": "Ambiguous-on-purpose case: a naive read sends this to Grep for a delete symbol, but the answer (whether the capability exists and how it's designed) lives in prose in design docs. The skill should try mcp__semantic-pages__search_hybrid first and only fall through to code search if the docs don't answer it. Success = used semantic search first, cited specific note filenames, did not start with grep.",
|
|
10
|
+
"files": []
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"id": 2,
|
|
14
|
+
"name": "flow-a-multi-part-infra-question",
|
|
15
|
+
"flow": "A",
|
|
16
|
+
"prompt": "What kind of git method is being used, and how many environments and domains/subdomains are there in each environment, and what are they?",
|
|
17
|
+
"expected_output": "Multi-part factual question where the answer is spread across several docs (git workflow, environments, domains all documented separately). Skill should use search_hybrid, read multiple notes, and ideally traverse via forwardlinks/backlinks to link the pieces together. Should cite each note used. No grepping source code. Success = pulled from multiple notes, composed a complete multi-part answer, cited filenames.",
|
|
18
|
+
"files": []
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"id": 3,
|
|
22
|
+
"name": "flow-b-pure-external-research",
|
|
23
|
+
"flow": "B",
|
|
24
|
+
"prompt": "What's the best CRM platform for a small business team of 10?",
|
|
25
|
+
"expected_output": "Pure external research, no repo context. Skill should: (1) search the vault for prior research on CRM evaluation, (2) do web research via WebSearch/WebFetch, (3) write a new .md file to .claude/.vault/ with full frontmatter (all required fields from references/vault-frontmatter.md), (4) answer from the note with a filename pointer. Success = a file was created at .claude/.vault/<slug>.md, frontmatter has all required fields with sensible values, note body has actual research content (not a stub), chat response cites the note filename.",
|
|
26
|
+
"files": []
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"id": 4,
|
|
30
|
+
"name": "flow-b-mixed-composition",
|
|
31
|
+
"flow": "B",
|
|
32
|
+
"prompt": "We use HashiCorp Vault for secrets management in this repo — is there a better alternative we should consider?",
|
|
33
|
+
"expected_output": "Hardest test: requires composing Flow A (find how the repo currently uses HashiCorp Vault, cite docs) with Flow B (research alternatives via web search, write vault note). The skill should NOT pick one flow and drop the other. Success = (1) semantic-pages was searched for current usage and specific notes were cited, (2) web research was performed for alternatives, (3) a .md file was written to .claude/.vault/ with full frontmatter and source: mixed (since it combines docs-lookup and web-research), (4) final answer references both the repo's current setup and the researched alternatives with explicit confidence, (5) the vault note contains a 'what we currently do' section drawn from Flow A findings.",
|
|
34
|
+
"files": []
|
|
35
|
+
}
|
|
36
|
+
]
|
|
37
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# Vault note frontmatter — field reference
|
|
2
|
+
|
|
3
|
+
Every `.md` file written into `.claude/.vault/` must start with YAML frontmatter in this exact format. The schema is adapted from the 22-field docs frontmatter used by the semantic-pages ecosystem, with RAG-chunker fields removed (they're meaningless for scratchpad notes) and two research-specific fields added (`source`, `confidence`) because research artifacts need epistemic provenance.
|
|
4
|
+
|
|
5
|
+
## The template
|
|
6
|
+
|
|
7
|
+
Copy this and fill it in for every new note. Don't skip fields — if a field doesn't apply, use `null`, `[]`, or the default shown in the comment. Missing fields break graph traversal and semantic indexing.
|
|
8
|
+
|
|
9
|
+
```yaml
|
|
10
|
+
---
|
|
11
|
+
title: "Note Title"
|
|
12
|
+
tier: research # research | investigation | scratch | finding | decision
|
|
13
|
+
topic: null # short kebab-case topic, e.g. "hashicorp-vault", "crm-evaluation", or null
|
|
14
|
+
domains: # free-form list; common values: security, backend, frontend, devops, infrastructure, product, tooling, ai
|
|
15
|
+
- security
|
|
16
|
+
audience: # who would find this note useful; free-form: self | team | all | <role>
|
|
17
|
+
- self
|
|
18
|
+
tags: # free-form keyword tags; used by search_hybrid
|
|
19
|
+
- secrets
|
|
20
|
+
- vault
|
|
21
|
+
status: active # active | superseded | draft | archived
|
|
22
|
+
last_updated: '2026-04-15' # ISO date, quoted string
|
|
23
|
+
source: web-research # web-research | docs-lookup | conversation | experimentation | mixed
|
|
24
|
+
complexity: low # low | medium | high — how hard the topic is to reason about
|
|
25
|
+
confidence: medium # low | medium | high — how much future-you should trust these notes
|
|
26
|
+
keywords: # precise search terms (more specific than tags)
|
|
27
|
+
- hashicorp-vault
|
|
28
|
+
- secret-management
|
|
29
|
+
dependencies: [] # notes that should be read first (filenames or [[wikilinks]])
|
|
30
|
+
related: [] # parallel/complementary notes
|
|
31
|
+
supersedes: [] # notes this replaces (old investigations that are now wrong)
|
|
32
|
+
summary: One-sentence description of what this note contains.
|
|
33
|
+
owner: self # self | team | <username>
|
|
34
|
+
---
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Field-by-field rationale
|
|
38
|
+
|
|
39
|
+
| Field | Purpose | Notes |
|
|
40
|
+
|---|---|---|
|
|
41
|
+
| `title` | Human-readable. Shows up in search results. | Be specific: "HashiCorp Vault vs Infisical comparison" beats "Secrets research". |
|
|
42
|
+
| `tier` | What kind of note this is. | `research` = multi-source investigation. `investigation` = debugging a specific problem. `scratch` = quick notes, low confidence. `finding` = a conclusion distilled from research. `decision` = an architectural choice and its rationale. |
|
|
43
|
+
| `topic` | A coarse grouping tag. | Kebab-case. `null` if the note doesn't cluster with others. |
|
|
44
|
+
| `domains` | Broad technical areas. | Free-form list. Multiple is fine. Used for filtering. |
|
|
45
|
+
| `audience` | Who benefits from reading this. | Default `[self]`. Use `[team]` or `[all]` only if the note is a finished artifact worth sharing. |
|
|
46
|
+
| `tags` | Free-form keywords. | These are the primary filter dimension in `search_hybrid`. Err on the side of more tags. |
|
|
47
|
+
| `status` | Is this note still valid? | `active` for current work. `superseded` when replaced by a newer investigation (fill `supersedes` on the new note). `draft` for in-progress. `archived` when deliberately retired. |
|
|
48
|
+
| `last_updated` | ISO date, quoted. | Bump this on every edit, even small ones. |
|
|
49
|
+
| `source` | Where the information came from. | `web-research` = Google/WebFetch. `docs-lookup` = pulled from semantic-pages. `conversation` = came from the user in chat. `experimentation` = ran something and observed results. `mixed` = combination. This is epistemic provenance and matters for re-reading later. |
|
|
50
|
+
| `complexity` | How hard is this topic. | Affects reading-time expectations and whether future-you should budget extra time. |
|
|
51
|
+
| `confidence` | How much to trust this note. | The single most valuable field for research notes. `low` confidence on a CRM recommendation after reading one blog post is useful information; pretending it's `high` is worse than not writing the note at all. |
|
|
52
|
+
| `keywords` | Precise search terms. | More specific than `tags` — actual names, product names, technical terms that exact-match searches will hit. |
|
|
53
|
+
| `dependencies` | Prerequisite notes. | `[[wikilinks]]` or filenames. Lets the graph traversal follow "what should I read first?". |
|
|
54
|
+
| `related` | Parallel notes. | Complementary topics, not prerequisites. |
|
|
55
|
+
| `supersedes` | Notes this replaces. | When you revisit a topic and the old note is now wrong, list it here and mark the old note's `status: superseded`. |
|
|
56
|
+
| `summary` | One sentence. | This is what shows up in search snippets — make it dense and informative. |
|
|
57
|
+
| `owner` | Who wrote it. | `self` by default. `team` for shared findings. `<username>` if you want to attribute. |
|
|
58
|
+
|
|
59
|
+
## What got dropped from the 22-field docs format, and why
|
|
60
|
+
|
|
61
|
+
The published-docs format includes these fields that are *not* in the vault format:
|
|
62
|
+
|
|
63
|
+
- `feature` → merged into `topic` and `tags`. Research notes rarely map to a single feature.
|
|
64
|
+
- `version` → research notes aren't versioned artifacts; they're snapshots in time.
|
|
65
|
+
- `size_kb`, `line_count`, `read_time_min` → auto-computed by the docs metadata generator for RAG chunking. Irrelevant for scratchpad notes.
|
|
66
|
+
- `load_priority` → used by published docs to hint load order; not meaningful for vault notes.
|
|
67
|
+
- `chunk_strategy` → RAG chunking hint; not used by the vault indexer in the same way.
|
|
68
|
+
|
|
69
|
+
If the user ever decides to promote a vault note to published docs, they'll need to add these fields back. Until then, they're noise.
|
|
70
|
+
|
|
71
|
+
## Two fields added to the vault format
|
|
72
|
+
|
|
73
|
+
- `source` — where the findings came from. Not in the docs format because published docs are the source of truth. Research notes aren't — knowing whether a conclusion is based on a blog post, a spec, or an experiment is critical for judging its weight later.
|
|
74
|
+
- `confidence` — how confident past-you was. Research notes lose value quickly if future-you can't tell whether the author was confident or speculating. A note that says "this library is the best choice" with `confidence: low` is actionable; one without confidence information is a trap.
|
|
75
|
+
|
|
76
|
+
## Example: fully filled-in note
|
|
77
|
+
|
|
78
|
+
```yaml
|
|
79
|
+
---
|
|
80
|
+
title: "HashiCorp Vault vs Infisical for SMB secret management"
|
|
81
|
+
tier: research
|
|
82
|
+
topic: secrets-management
|
|
83
|
+
domains:
|
|
84
|
+
- security
|
|
85
|
+
- devops
|
|
86
|
+
audience:
|
|
87
|
+
- self
|
|
88
|
+
- team
|
|
89
|
+
tags:
|
|
90
|
+
- secrets
|
|
91
|
+
- hashicorp-vault
|
|
92
|
+
- infisical
|
|
93
|
+
- comparison
|
|
94
|
+
status: active
|
|
95
|
+
last_updated: '2026-04-15'
|
|
96
|
+
source: mixed
|
|
97
|
+
complexity: medium
|
|
98
|
+
confidence: medium
|
|
99
|
+
keywords:
|
|
100
|
+
- hashicorp-vault
|
|
101
|
+
- infisical
|
|
102
|
+
- secret-management
|
|
103
|
+
- smb
|
|
104
|
+
dependencies: []
|
|
105
|
+
related:
|
|
106
|
+
- secrets-rotation-patterns.md
|
|
107
|
+
supersedes: []
|
|
108
|
+
summary: Comparison of HashiCorp Vault and Infisical for small-to-medium business secret management, with cost, ops overhead, and feature tradeoffs.
|
|
109
|
+
owner: self
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
# HashiCorp Vault vs Infisical for SMB secret management
|
|
113
|
+
|
|
114
|
+
## TL;DR
|
|
115
|
+
|
|
116
|
+
<one paragraph conclusion>
|
|
117
|
+
|
|
118
|
+
## What we currently do
|
|
119
|
+
|
|
120
|
+
<if Flow A surfaced this, summarize from semantic-pages findings and cite the docs>
|
|
121
|
+
|
|
122
|
+
## Options evaluated
|
|
123
|
+
|
|
124
|
+
### HashiCorp Vault
|
|
125
|
+
...
|
|
126
|
+
|
|
127
|
+
### Infisical
|
|
128
|
+
...
|
|
129
|
+
|
|
130
|
+
## Recommendation
|
|
131
|
+
|
|
132
|
+
<with explicit confidence level>
|
|
133
|
+
```
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/core/embedder.ts","../src/core/graph.ts","../src/core/vector.ts","../src/core/search-text.ts","../src/core/crud.ts","../src/core/frontmatter.ts","../src/core/watcher.ts"],"sourcesContent":["import { AutoTokenizer } from \"@huggingface/transformers\";\nimport { readFile, writeFile, mkdir } from \"node:fs/promises\";\nimport { join, dirname } from \"node:path\";\nimport { existsSync, createWriteStream } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { Readable } from \"node:stream\";\nimport { pipeline as streamPipeline } from \"node:stream/promises\";\nimport { Worker } from \"node:worker_threads\";\nimport { fileURLToPath } from \"node:url\";\n\n// MiniLM-L6-v2 is the default: ~3 min to index 2,853 chunks on CPU vs ~16 min for nomic.\n// nomic-embed-text-v1.5 gives higher quality embeddings but is much slower on CPU.\nconst DEFAULT_MODEL = \"sentence-transformers/all-MiniLM-L6-v2\";\nconst CACHE_DIR = join(homedir(), \".semantic-pages\", \"models\");\n// Default to 1 worker (serial). Worker threads only help on memory-rich machines\n// (each worker loads its own ONNX session). On typical dev machines\n// with <4 GB free RAM, parallel workers cause swap thrashing and are slower.\n// Enable with --workers N when you have sufficient RAM.\nconst DEFAULT_WORKERS = 1;\n// batch=16 is optimal for MiniLM on CPU (short sequences, low padding waste).\n// For larger models (nomic 768d), batch=1 is faster due to padding overhead.\nconst DEFAULT_BATCH_SIZE = 16;\n// Quantized ONNX: faster on CPU but not all models have a quantized variant.\n// MiniLM fp32 is already fast enough; nomic benefits from quantized.\n// Falls back to fp32 automatically if quantized file is not available.\nconst DEFAULT_QUANTIZED = false;\n\n// Full-precision ONNX model subpaths\nconst ONNX_MODEL_PATHS: Record<string, string> = {\n \"nomic-ai/nomic-embed-text-v1.5\": \"onnx/model.onnx\",\n \"sentence-transformers/all-MiniLM-L6-v2\": \"onnx/model.onnx\",\n};\n\n// Quantized (int8) ONNX model subpaths — faster on CPU, ~same quality\nconst ONNX_QUANTIZED_MODEL_PATHS: Record<string, string> = {\n \"nomic-ai/nomic-embed-text-v1.5\": \"onnx/model_quantized.onnx\",\n \"sentence-transformers/all-MiniLM-L6-v2\": \"onnx/model_quantized.onnx\",\n};\n\ninterface OrtSession {\n run(feeds: Record<string, unknown>): Promise<Record<string, { data: Float32Array; dims: number[] }>>;\n inputNames: string[];\n outputNames: string[];\n}\n\ninterface OrtModule {\n InferenceSession: {\n create(path: string, options?: Record<string, unknown>): Promise<OrtSession>;\n };\n Tensor: new (type: string, data: ArrayLike<number | bigint>, dims: number[]) => unknown;\n}\n\ntype RuntimeLabel = \"native\" | \"wasm\";\n\nasync function resolveOnnxRuntime(): Promise<{ ort: OrtModule; label: RuntimeLabel }> {\n try {\n const ort = await import(\"onnxruntime-node\");\n return { ort: ort as unknown as OrtModule, label: \"native\" };\n } catch {\n const ort = await import(\"onnxruntime-web\");\n return { ort: ort as unknown as OrtModule, label: \"wasm\" };\n }\n}\n\nasync function downloadFile(url: string, destPath: string): Promise<void> {\n const response = await fetch(url);\n if (!response.ok) throw new Error(`Download failed (${response.status}): ${url}`);\n if (!response.body) throw new Error(`No response body: ${url}`);\n const fileStream = createWriteStream(destPath);\n await streamPipeline(Readable.fromWeb(response.body as never), fileStream);\n}\n\nexport class Embedder {\n private model: string;\n private session: OrtSession | null = null;\n private tokenizer: Awaited<ReturnType<typeof AutoTokenizer.from_pretrained>> | null = null;\n private ort: OrtModule | null = null;\n private dimensions = 0;\n private runtimeLabel: RuntimeLabel = \"wasm\";\n private initialized = false;\n private numWorkers: number;\n private batchSize: number;\n private quantized: boolean;\n private modelPath = \"\";\n\n constructor(\n model: string = DEFAULT_MODEL,\n numWorkers: number = DEFAULT_WORKERS,\n batchSize: number = DEFAULT_BATCH_SIZE,\n quantized: boolean = DEFAULT_QUANTIZED\n ) {\n this.model = model;\n this.numWorkers = numWorkers;\n this.batchSize = batchSize;\n this.quantized = quantized;\n }\n\n async init(): Promise<void> {\n if (this.initialized) return;\n\n const modelDir = join(CACHE_DIR, this.model.replace(/\\//g, \"--\"));\n await mkdir(modelDir, { recursive: true });\n\n // Resolve ONNX runtime (native C++ or WASM fallback)\n const { ort, label } = await resolveOnnxRuntime();\n this.ort = ort;\n this.runtimeLabel = label;\n\n // Download ONNX model if not cached. Tries quantized first if requested,\n // falls back to fp32 if the quantized file is not available for this model.\n let useQuantized = this.quantized;\n const modelFileName = useQuantized ? \"model_quantized.onnx\" : \"model.onnx\";\n this.modelPath = join(modelDir, modelFileName);\n const modelPath = this.modelPath;\n if (!existsSync(modelPath)) {\n const pathMap = useQuantized ? ONNX_QUANTIZED_MODEL_PATHS : ONNX_MODEL_PATHS;\n const onnxSubpath = pathMap[this.model] ?? (useQuantized ? \"onnx/model_quantized.onnx\" : \"onnx/model.onnx\");\n const url = `https://huggingface.co/${this.model}/resolve/main/${onnxSubpath}`;\n process.stderr.write(`Downloading ONNX model: ${this.model} (${useQuantized ? \"quantized\" : \"fp32\"})...\\n`);\n try {\n await downloadFile(url, modelPath);\n } catch (err: any) {\n if (useQuantized && err?.message?.includes(\"404\")) {\n // Quantized not available for this model — fall back to fp32\n process.stderr.write(`Quantized model not available, falling back to fp32\\n`);\n useQuantized = false;\n this.modelPath = join(modelDir, \"model.onnx\");\n const fp32Subpath = ONNX_MODEL_PATHS[this.model] ?? \"onnx/model.onnx\";\n const fp32Url = `https://huggingface.co/${this.model}/resolve/main/${fp32Subpath}`;\n process.stderr.write(`Downloading ONNX model: ${this.model} (fp32)...\\n`);\n await downloadFile(fp32Url, this.modelPath);\n } else {\n throw err;\n }\n }\n process.stderr.write(`Model downloaded to ${modelDir}\\n`);\n }\n // Load tokenizer (uses HF transformers tokenizer infrastructure)\n this.tokenizer = await AutoTokenizer.from_pretrained(this.model, {\n cache_dir: CACHE_DIR,\n });\n\n // Create ONNX inference session (use this.modelPath — may have been updated by fallback)\n this.session = await ort.InferenceSession.create(this.modelPath, {\n executionProviders: [label === \"native\" ? \"cpu\" : \"wasm\"],\n });\n\n // Determine dimensions from a test embedding\n const test = await this.embed(\"test\");\n this.dimensions = test.length;\n this.initialized = true;\n\n const modelShort = this.model.split(\"/\").pop() ?? this.model;\n process.stderr.write(`Embedder ready (${modelShort}, ${label}, ${this.dimensions}d, batch=${this.batchSize})\\n`);\n }\n\n async embed(text: string): Promise<Float32Array> {\n if (!this.session || !this.tokenizer || !this.ort)\n throw new Error(\"Embedder not initialized. Call init() first.\");\n\n // Tokenize\n const encoded = await this.tokenizer(text, {\n padding: true,\n truncation: true,\n max_length: 512,\n return_tensor: false,\n });\n\n const inputIdsRaw: number[] = Array.from(encoded.input_ids.data ?? encoded.input_ids);\n const attentionMaskRaw: number[] = Array.from(encoded.attention_mask.data ?? encoded.attention_mask);\n const seqLen = inputIdsRaw.length;\n\n // Build ONNX input tensors (most models expect int64)\n const inputIds = new this.ort.Tensor(\n \"int64\",\n BigInt64Array.from(inputIdsRaw.map(BigInt)),\n [1, seqLen]\n );\n const attentionMask = new this.ort.Tensor(\n \"int64\",\n BigInt64Array.from(attentionMaskRaw.map(BigInt)),\n [1, seqLen]\n );\n\n const feeds: Record<string, unknown> = { input_ids: inputIds, attention_mask: attentionMask };\n\n // Some models need token_type_ids\n if (this.session.inputNames.includes(\"token_type_ids\")) {\n feeds.token_type_ids = new this.ort.Tensor(\n \"int64\",\n new BigInt64Array(seqLen),\n [1, seqLen]\n );\n }\n\n // Run inference\n const output = await this.session.run(feeds);\n const outputTensor = output[this.session.outputNames[0]];\n const hiddenSize = outputTensor.dims[outputTensor.dims.length - 1];\n\n // Mean pooling with attention mask + L2 normalization\n return this.meanPoolAndNormalize(outputTensor.data, attentionMaskRaw, seqLen, hiddenSize);\n }\n\n private meanPoolAndNormalize(\n embeddings: Float32Array,\n attentionMask: number[],\n seqLen: number,\n hiddenSize: number\n ): Float32Array {\n const result = new Float32Array(hiddenSize);\n let maskSum = 0;\n\n for (let t = 0; t < seqLen; t++) {\n const mask = attentionMask[t];\n maskSum += mask;\n const offset = t * hiddenSize;\n for (let d = 0; d < hiddenSize; d++) {\n result[d] += embeddings[offset + d] * mask;\n }\n }\n\n if (maskSum > 0) {\n for (let d = 0; d < hiddenSize; d++) result[d] /= maskSum;\n }\n\n // L2 normalize\n let norm = 0;\n for (let d = 0; d < hiddenSize; d++) norm += result[d] * result[d];\n norm = Math.sqrt(norm);\n if (norm > 0) {\n for (let d = 0; d < hiddenSize; d++) result[d] /= norm;\n }\n\n return result;\n }\n\n // Mean pool + L2 normalize the output of a batched ONNX forward pass.\n // outputData: flat Float32Array of shape [batchSize, seqLen, hiddenSize]\n // maskData: flat number[] of shape [batchSize, seqLen]\n private meanPoolAndNormalizeMany(\n outputData: Float32Array,\n maskData: number[],\n batchSize: number,\n seqLen: number,\n hiddenSize: number\n ): Float32Array[] {\n const results: Float32Array[] = [];\n for (let b = 0; b < batchSize; b++) {\n const result = new Float32Array(hiddenSize);\n let maskSum = 0;\n for (let t = 0; t < seqLen; t++) {\n const mask = maskData[b * seqLen + t];\n maskSum += mask;\n const offset = (b * seqLen + t) * hiddenSize;\n for (let d = 0; d < hiddenSize; d++) {\n result[d] += outputData[offset + d] * mask;\n }\n }\n if (maskSum > 0) {\n for (let d = 0; d < hiddenSize; d++) result[d] /= maskSum;\n }\n // L2 normalize\n let norm = 0;\n for (let d = 0; d < hiddenSize; d++) norm += result[d] * result[d];\n norm = Math.sqrt(norm);\n if (norm > 0) {\n for (let d = 0; d < hiddenSize; d++) result[d] /= norm;\n }\n results.push(result);\n }\n return results;\n }\n\n // Embed a sub-batch of texts (length <= batchSize) in a single ONNX forward pass.\n //\n // Tokenizes each text individually (well-defined output format for all HF versions),\n // then manually pads to the longest sequence in the batch and builds a [n, seqLen]\n // tensor for one ONNX call. The speedup is from batching the ONNX inference —\n // individual tokenization is negligible (<1ms each).\n private async embedSubBatch(texts: string[]): Promise<Float32Array[]> {\n if (!this.session || !this.tokenizer || !this.ort)\n throw new Error(\"Embedder not initialized. Call init() first.\");\n\n const n = texts.length;\n\n // Tokenize each text individually — avoids ambiguous batch tokenizer output format\n const encodings = await Promise.all(\n texts.map((text) =>\n this.tokenizer!(text, {\n padding: false,\n truncation: true,\n max_length: 512,\n return_tensor: false,\n })\n )\n );\n\n // Extract flat token arrays and find max sequence length for this batch\n const tokenized = encodings.map((enc) => ({\n ids: Array.from(enc.input_ids.data ?? enc.input_ids) as number[],\n mask: Array.from(enc.attention_mask.data ?? enc.attention_mask) as number[],\n }));\n const seqLen = Math.max(...tokenized.map((t) => t.ids.length));\n\n // Build flat padded tensors [n * seqLen] — pad with 0 (PAD token, zero attention)\n const flatIds = new BigInt64Array(n * seqLen);\n const flatMask = new BigInt64Array(n * seqLen);\n const flatMaskNums = new Array<number>(n * seqLen).fill(0);\n\n for (let i = 0; i < n; i++) {\n const { ids, mask } = tokenized[i];\n for (let j = 0; j < ids.length; j++) {\n flatIds[i * seqLen + j] = BigInt(ids[j]);\n flatMask[i * seqLen + j] = BigInt(mask[j]);\n flatMaskNums[i * seqLen + j] = mask[j];\n }\n // Positions beyond ids.length remain 0 (padding)\n }\n\n // Build batched ONNX tensors [n, seqLen]\n const inputIds = new this.ort.Tensor(\"int64\", flatIds, [n, seqLen]);\n const attentionMask = new this.ort.Tensor(\"int64\", flatMask, [n, seqLen]);\n\n const feeds: Record<string, unknown> = { input_ids: inputIds, attention_mask: attentionMask };\n\n if (this.session.inputNames.includes(\"token_type_ids\")) {\n feeds.token_type_ids = new this.ort.Tensor(\n \"int64\",\n new BigInt64Array(n * seqLen),\n [n, seqLen]\n );\n }\n\n // Single forward pass → output shape [n, seqLen, hiddenSize]\n const output = await this.session.run(feeds);\n const outputTensor = output[this.session.outputNames[0]];\n const hiddenSize = outputTensor.dims[outputTensor.dims.length - 1];\n\n return this.meanPoolAndNormalizeMany(\n outputTensor.data,\n flatMaskNums,\n n,\n seqLen,\n hiddenSize\n );\n }\n\n async embedBatch(\n texts: string[],\n onProgress?: (embedded: number, total: number, subBatch?: Float32Array[]) => Promise<void> | void\n ): Promise<Float32Array[]> {\n // Workers path (disabled by default; kept for --workers N users)\n if (this.numWorkers > 1 && texts.length >= this.numWorkers * 2) {\n return this.embedBatchParallel(texts, onProgress);\n }\n\n // True batched inference: slice into sub-batches and run one ONNX call each\n const results: Float32Array[] = [];\n for (let i = 0; i < texts.length; i += this.batchSize) {\n const subBatch = texts.slice(i, i + this.batchSize);\n const embeddings = await this.embedSubBatch(subBatch);\n results.push(...embeddings);\n await onProgress?.(Math.min(i + subBatch.length, texts.length), texts.length, embeddings);\n }\n return results;\n }\n\n private async embedBatchParallel(\n texts: string[],\n onProgress?: (embedded: number, total: number, subBatch?: Float32Array[]) => Promise<void> | void\n ): Promise<Float32Array[]> {\n // Resolve worker script path. With tsup splitting, embedder code may land\n // in a top-level chunk (dist/chunk-*.js) rather than dist/core/index.js,\n // so we try two locations: adjacent to this chunk, and dist/core/ relative\n // to the package root (two levels up from a dist/ chunk file).\n const thisDir = dirname(fileURLToPath(import.meta.url));\n let workerPath = join(thisDir, \"embed-worker.js\");\n if (!existsSync(workerPath)) {\n // Chunk file at dist/ level — worker is in dist/core/\n workerPath = join(thisDir, \"core\", \"embed-worker.js\");\n }\n\n if (!existsSync(workerPath)) {\n // Fallback to serial batched if worker script not found\n process.stderr.write(\"Worker script not found, falling back to batched embedding\\n\");\n return this.embedBatch(texts, onProgress);\n }\n\n // Split texts into chunks for each worker\n const chunkSize = Math.ceil(texts.length / this.numWorkers);\n const chunks: { texts: string[]; startIndex: number }[] = [];\n for (let i = 0; i < texts.length; i += chunkSize) {\n chunks.push({ texts: texts.slice(i, i + chunkSize), startIndex: i });\n }\n\n const allResults = new Array<Float32Array>(texts.length);\n let totalDone = 0;\n\n const workerPromises = chunks.map((chunk) => {\n return new Promise<void>((resolve, reject) => {\n const worker = new Worker(workerPath, {\n workerData: {\n modelPath: this.modelPath,\n modelName: this.model,\n cacheDir: CACHE_DIR,\n runtimeLabel: this.runtimeLabel,\n batchSize: this.batchSize,\n },\n });\n\n worker.on(\"message\", (msg: any) => {\n if (msg.type === \"ready\") {\n worker.postMessage({ type: \"embed\", texts: chunk.texts, startIndex: chunk.startIndex });\n } else if (msg.type === \"progress\") {\n totalDone++;\n onProgress?.(totalDone, texts.length);\n } else if (msg.type === \"result\") {\n for (let i = 0; i < msg.embeddings.length; i++) {\n allResults[chunk.startIndex + i] = new Float32Array(msg.embeddings[i]);\n }\n worker.terminate();\n resolve();\n } else if (msg.type === \"error\") {\n worker.terminate();\n reject(new Error(msg.error));\n }\n });\n\n worker.on(\"error\", (err) => {\n worker.terminate();\n reject(err);\n });\n });\n });\n\n await Promise.all(workerPromises);\n return allResults;\n }\n\n getDimensions(): number {\n return this.dimensions;\n }\n\n getModel(): string {\n return this.model;\n }\n\n getRuntime(): RuntimeLabel {\n return this.runtimeLabel;\n }\n\n async saveEmbeddings(\n embeddings: Map<string, Float32Array>,\n indexPath: string\n ): Promise<void> {\n const entries: Array<{ key: string; data: number[] }> = [];\n for (const [key, vec] of embeddings) {\n entries.push({ key, data: Array.from(vec) });\n }\n await writeFile(join(indexPath, \"embeddings.json\"), JSON.stringify(entries));\n }\n\n async loadEmbeddings(\n indexPath: string\n ): Promise<Map<string, Float32Array>> {\n const filePath = join(indexPath, \"embeddings.json\");\n if (!existsSync(filePath)) return new Map();\n\n const raw = await readFile(filePath, \"utf-8\");\n const entries: Array<{ key: string; data: number[] }> = JSON.parse(raw);\n const map = new Map<string, Float32Array>();\n for (const entry of entries) {\n map.set(entry.key, new Float32Array(entry.data));\n }\n return map;\n }\n}\n","import Graph from \"graphology\";\nimport { bfsFromNode } from \"graphology-traversal\";\nimport { bidirectional } from \"graphology-shortest-path\";\nimport type { IndexedDocument, GraphNode, GraphEdge, GraphStats } from \"./types.js\";\nimport { readFile, writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { existsSync } from \"node:fs\";\n\n/**\n * Tags shared by more than this many docs are too generic to be meaningful\n * graph edges (e.g. \"api\", \"testing\"). Only specific/rare tags create edges.\n * Docs without frontmatter are unaffected — their inline #tags still participate\n * but only if those tags are specific enough to pass this threshold.\n */\nconst TAG_EDGE_MAX_DOCS = 15;\n\nexport class GraphBuilder {\n private graph: Graph;\n\n constructor() {\n this.graph = new Graph({ type: \"directed\", multi: false });\n }\n\n buildFromDocuments(documents: IndexedDocument[]): void {\n this.graph.clear();\n\n // Add nodes — store optional metadata fields when present\n for (const doc of documents) {\n this.graph.addNode(doc.path, {\n title: doc.title,\n tags: doc.tags,\n ...(doc.status !== undefined && { status: doc.status }),\n ...(doc.loadPriority !== undefined && { loadPriority: doc.loadPriority }),\n });\n }\n\n const pathLookup = new Map<string, string>();\n for (const doc of documents) {\n const nameNoExt = doc.path.replace(/\\.md$/, \"\");\n const basename = nameNoExt.split(\"/\").pop()!;\n pathLookup.set(basename.toLowerCase(), doc.path);\n pathLookup.set(nameNoExt.toLowerCase(), doc.path);\n }\n\n // Add wikilink edges (weight 1.0)\n for (const doc of documents) {\n for (const link of doc.wikilinks) {\n const target = pathLookup.get(link.toLowerCase());\n if (target && target !== doc.path && !this.graph.hasEdge(doc.path, target)) {\n this.graph.addEdge(doc.path, target, { type: \"wikilink\", weight: 1.0 });\n }\n }\n }\n\n // Add related_docs edges (weight 1.0) — only present when frontmatter has related_docs\n // Falls back gracefully: docs without this field are simply skipped\n for (const doc of documents) {\n if (!doc.relatedDocs?.length) continue;\n for (const ref of doc.relatedDocs) {\n // Try full normalized path first, then basename — same strategy as wikilinks\n const normalized = ref.replace(/\\.md$/, \"\").toLowerCase();\n const base = normalized.split(\"/\").pop()!;\n const target = pathLookup.get(normalized) ?? pathLookup.get(base);\n if (target && target !== doc.path && !this.graph.hasEdge(doc.path, target)) {\n this.graph.addEdge(doc.path, target, { type: \"related\", weight: 1.0 });\n }\n }\n }\n\n // Add tag-based edges (weight 0.5) — capped at TAG_EDGE_MAX_DOCS per tag.\n // Generic tags shared by many docs (e.g. \"api\", \"testing\") create noise;\n // only specific tags (e.g. \"qdrant\", \"celery\") create meaningful connections.\n const tagToNotes = new Map<string, string[]>();\n for (const doc of documents) {\n for (const tag of doc.tags) {\n const notes = tagToNotes.get(tag) ?? [];\n notes.push(doc.path);\n tagToNotes.set(tag, notes);\n }\n }\n\n for (const [, notes] of tagToNotes) {\n if (notes.length >= TAG_EDGE_MAX_DOCS) continue; // too generic — skip\n for (let i = 0; i < notes.length; i++) {\n for (let j = i + 1; j < notes.length; j++) {\n if (!this.graph.hasEdge(notes[i], notes[j])) {\n this.graph.addEdge(notes[i], notes[j], { type: \"tag\", weight: 0.5 });\n }\n if (!this.graph.hasEdge(notes[j], notes[i])) {\n this.graph.addEdge(notes[j], notes[i], { type: \"tag\", weight: 0.5 });\n }\n }\n }\n }\n }\n\n backlinks(notePath: string): GraphNode[] {\n if (!this.graph.hasNode(notePath)) return [];\n return this.graph.inNeighbors(notePath).map((n) => this.nodeToGraphNode(n));\n }\n\n forwardlinks(notePath: string): GraphNode[] {\n if (!this.graph.hasNode(notePath)) return [];\n return this.graph.outNeighbors(notePath).map((n) => this.nodeToGraphNode(n));\n }\n\n findPath(from: string, to: string): string[] | null {\n if (!this.graph.hasNode(from) || !this.graph.hasNode(to)) return null;\n const path = bidirectional(this.graph, from, to);\n return path;\n }\n\n searchGraph(concept: string, maxDepth = 2, limit = 20): GraphNode[] {\n const lc = concept.toLowerCase();\n\n const directMatches = new Set<string>(\n this.graph.nodes().filter((n) => {\n const attrs = this.graph.getNodeAttributes(n);\n const title = typeof attrs.title === \"string\" ? attrs.title : String(attrs.title ?? \"\");\n return (\n n.toLowerCase().includes(lc) ||\n title.toLowerCase().includes(lc) ||\n (attrs.tags as string[] | undefined)?.some((t) => t.toLowerCase().includes(lc))\n );\n })\n );\n\n const visited = new Set<string>();\n for (const start of directMatches) {\n bfsFromNode(this.graph, start, (_node, _attr, depth) => {\n if (visited.size >= limit * 3) return true; // gather 3x limit before ranking\n visited.add(_node);\n return depth >= maxDepth;\n });\n }\n\n // Sort: direct matches first, then by loadPriority desc, then by degree desc\n // deprecated/archived nodes sort to the end\n const ranked = [...visited].sort((a, b) => {\n const aAttrs = this.graph.getNodeAttributes(a);\n const bAttrs = this.graph.getNodeAttributes(b);\n const aDeprecated = aAttrs.status === \"deprecated\" || aAttrs.status === \"archived\" ? 1 : 0;\n const bDeprecated = bAttrs.status === \"deprecated\" || bAttrs.status === \"archived\" ? 1 : 0;\n if (aDeprecated !== bDeprecated) return aDeprecated - bDeprecated;\n const aDirect = directMatches.has(a) ? 0 : 1;\n const bDirect = directMatches.has(b) ? 0 : 1;\n if (aDirect !== bDirect) return aDirect - bDirect;\n const aPriority = (aAttrs.loadPriority as number | undefined) ?? 5;\n const bPriority = (bAttrs.loadPriority as number | undefined) ?? 5;\n if (aPriority !== bPriority) return bPriority - aPriority;\n return this.graph.degree(b) - this.graph.degree(a);\n });\n\n return ranked.slice(0, limit).map((n) => this.nodeToGraphNode(n));\n }\n\n statistics(): GraphStats {\n const nodes = this.graph.order;\n const edges = this.graph.size;\n const orphans = this.graph.nodes().filter(\n (n) => this.graph.degree(n) === 0\n );\n\n const connections = this.graph.nodes().map((n) => ({\n path: n,\n connections: this.graph.degree(n),\n }));\n connections.sort((a, b) => b.connections - a.connections);\n\n const maxPossibleEdges = nodes * (nodes - 1);\n const density = maxPossibleEdges > 0 ? edges / maxPossibleEdges : 0;\n\n return {\n totalNodes: nodes,\n totalEdges: edges,\n orphanCount: orphans.length,\n mostConnected: connections.slice(0, 10),\n density,\n };\n }\n\n private nodeToGraphNode(nodePath: string): GraphNode {\n const attrs = this.graph.getNodeAttributes(nodePath);\n return {\n path: nodePath,\n title: typeof attrs.title === \"string\" ? attrs.title : nodePath,\n tags: (attrs.tags as string[]) || [],\n linkCount: this.graph.outDegree(nodePath),\n backlinkCount: this.graph.inDegree(nodePath),\n ...(attrs.loadPriority !== undefined && { loadPriority: attrs.loadPriority as number }),\n ...(attrs.status !== undefined && { status: attrs.status as string }),\n };\n }\n\n async save(indexPath: string): Promise<void> {\n const data = this.graph.export();\n await writeFile(join(indexPath, \"graph.json\"), JSON.stringify(data));\n }\n\n async load(indexPath: string): Promise<boolean> {\n const filePath = join(indexPath, \"graph.json\");\n if (!existsSync(filePath)) return false;\n\n const raw = await readFile(filePath, \"utf-8\");\n const data = JSON.parse(raw);\n this.graph.import(data);\n return true;\n }\n\n getGraph(): Graph {\n return this.graph;\n }\n}\n","import hnswlib from \"hnswlib-node\";\nconst { HierarchicalNSW } = hnswlib;\nimport { readFile, writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { existsSync } from \"node:fs\";\nimport type { SearchResult } from \"./types.js\";\n\ninterface ChunkMeta {\n docPath: string;\n chunkIndex: number;\n text: string;\n}\n\nexport class VectorIndex {\n private index: InstanceType<typeof HierarchicalNSW> | null = null;\n private dimensions: number;\n private chunkMeta: ChunkMeta[] = [];\n\n constructor(dimensions: number) {\n this.dimensions = dimensions;\n }\n\n build(\n embeddings: Float32Array[],\n meta: ChunkMeta[]\n ): void {\n if (embeddings.length === 0) {\n this.index = null;\n this.chunkMeta = [];\n return;\n }\n\n this.index = new HierarchicalNSW(\"cosine\", this.dimensions);\n this.index.initIndex(embeddings.length);\n\n for (let i = 0; i < embeddings.length; i++) {\n this.index.addPoint(Array.from(embeddings[i]), i);\n }\n\n this.chunkMeta = meta;\n }\n\n search(queryEmbedding: Float32Array, k: number = 10): SearchResult[] {\n if (!this.index || this.chunkMeta.length === 0) return [];\n\n const numResults = Math.min(k, this.chunkMeta.length);\n const result = this.index.searchKnn(Array.from(queryEmbedding), numResults);\n\n const seen = new Set<string>();\n const results: SearchResult[] = [];\n\n for (let i = 0; i < result.neighbors.length; i++) {\n const idx = result.neighbors[i];\n const meta = this.chunkMeta[idx];\n if (!meta || seen.has(meta.docPath)) continue;\n seen.add(meta.docPath);\n\n results.push({\n path: meta.docPath,\n title: meta.docPath,\n score: 1 - result.distances[i],\n snippet: meta.text.slice(0, 200),\n matchedChunk: meta.text,\n });\n }\n\n return results;\n }\n\n async save(indexPath: string): Promise<void> {\n if (!this.index) return;\n\n this.index.writeIndexSync(join(indexPath, \"hnsw.bin\"));\n await writeFile(\n join(indexPath, \"hnsw-meta.json\"),\n JSON.stringify(this.chunkMeta)\n );\n }\n\n async load(indexPath: string): Promise<boolean> {\n const hnswPath = join(indexPath, \"hnsw.bin\");\n const metaPath = join(indexPath, \"hnsw-meta.json\");\n\n if (!existsSync(hnswPath) || !existsSync(metaPath)) return false;\n\n const raw = await readFile(metaPath, \"utf-8\");\n this.chunkMeta = JSON.parse(raw);\n\n this.index = new HierarchicalNSW(\"cosine\", this.dimensions);\n this.index.initIndex(this.chunkMeta.length);\n this.index.readIndexSync(hnswPath);\n\n return true;\n }\n\n getChunkMeta(): ChunkMeta[] {\n return this.chunkMeta;\n }\n}\n","import type { IndexedDocument, SearchResult, SearchTextOptions } from \"./types.js\";\nimport { minimatch } from \"minimatch\";\n\nexport class TextSearch {\n private documents: IndexedDocument[] = [];\n\n setDocuments(documents: IndexedDocument[]): void {\n this.documents = documents;\n }\n\n search(options: SearchTextOptions): SearchResult[] {\n const { pattern, regex, caseSensitive, pathGlob, tagFilter, limit = 20 } = options;\n\n let matcher: (text: string) => { matched: boolean; index: number };\n\n if (regex) {\n const flags = caseSensitive ? \"g\" : \"gi\";\n const re = new RegExp(pattern, flags);\n matcher = (text) => {\n re.lastIndex = 0;\n const m = re.exec(text);\n return { matched: !!m, index: m?.index ?? -1 };\n };\n } else {\n const needle = caseSensitive ? pattern : pattern.toLowerCase();\n matcher = (text) => {\n const haystack = caseSensitive ? text : text.toLowerCase();\n const idx = haystack.indexOf(needle);\n return { matched: idx >= 0, index: idx };\n };\n }\n\n const results: SearchResult[] = [];\n\n for (const doc of this.documents) {\n // Path filter\n if (pathGlob && !minimatch(doc.path, pathGlob)) continue;\n\n // Tag filter\n if (tagFilter?.length) {\n const hasTag = tagFilter.some((t) => doc.tags.includes(t));\n if (!hasTag) continue;\n }\n\n const { matched, index } = matcher(doc.content);\n if (!matched) continue;\n\n const snippetStart = Math.max(0, index - 80);\n const snippetEnd = Math.min(doc.content.length, index + 120);\n const snippet = doc.content.slice(snippetStart, snippetEnd).trim();\n\n results.push({\n path: doc.path,\n title: doc.title,\n score: 1.0,\n snippet: (snippetStart > 0 ? \"...\" : \"\") + snippet + (snippetEnd < doc.content.length ? \"...\" : \"\"),\n });\n\n if (results.length >= limit) break;\n }\n\n return results;\n }\n}\n","import { readFile, writeFile, unlink, rename, mkdir } from \"node:fs/promises\";\nimport { dirname, join, relative } from \"node:path\";\nimport { existsSync } from \"node:fs\";\nimport { glob } from \"glob\";\nimport matter from \"gray-matter\";\nimport type { UpdateNoteOptions } from \"./types.js\";\n\nexport class NoteCrud {\n private notesPath: string;\n\n constructor(notesPath: string) {\n this.notesPath = notesPath;\n }\n\n async create(\n relativePath: string,\n content: string,\n frontmatter?: Record<string, unknown>\n ): Promise<string> {\n const absPath = join(this.notesPath, relativePath);\n if (existsSync(absPath)) {\n throw new Error(`Note already exists: ${relativePath}`);\n }\n\n await mkdir(dirname(absPath), { recursive: true });\n\n // Merge auto-stamps with caller-supplied frontmatter (caller wins on conflicts)\n const stamps = computeStamps(content);\n const merged = { ...stamps, ...(frontmatter ?? {}) };\n const fileContent = matter.stringify(content, merged);\n\n await writeFile(absPath, fileContent, \"utf-8\");\n return relativePath;\n }\n\n async read(relativePath: string): Promise<string> {\n const absPath = join(this.notesPath, relativePath);\n return readFile(absPath, \"utf-8\");\n }\n\n async readMultiple(paths: string[]): Promise<Map<string, string>> {\n const results = new Map<string, string>();\n await Promise.all(\n paths.map(async (p) => {\n try {\n const content = await this.read(p);\n results.set(p, content);\n } catch {\n results.set(p, `[Error: could not read ${p}]`);\n }\n })\n );\n return results;\n }\n\n async update(\n relativePath: string,\n content: string,\n options: UpdateNoteOptions\n ): Promise<void> {\n const absPath = join(this.notesPath, relativePath);\n if (!existsSync(absPath)) {\n throw new Error(`Note does not exist: ${relativePath}`);\n }\n\n const existing = await readFile(absPath, \"utf-8\");\n\n let updated: string;\n\n switch (options.mode) {\n case \"overwrite\":\n updated = content;\n break;\n\n case \"append\":\n updated = existing + \"\\n\" + content;\n break;\n\n case \"prepend\": {\n const { data, content: body } = matter(existing);\n const newBody = content + \"\\n\" + body;\n updated = Object.keys(data).length > 0 ? matter.stringify(newBody, data) : newBody;\n break;\n }\n\n case \"patch-by-heading\": {\n if (!options.heading) throw new Error(\"patch-by-heading requires a heading\");\n updated = this.patchByHeading(existing, options.heading, content);\n break;\n }\n }\n\n // Re-parse the final content and inject updated stamps\n // (preserves all existing frontmatter, only overwrites the three auto fields)\n const { data: existingFm, content: body } = matter(updated);\n const stamps = computeStamps(body);\n const newFm = { ...existingFm, ...stamps };\n await writeFile(absPath, matter.stringify(body, newFm), \"utf-8\");\n }\n\n async delete(relativePath: string): Promise<void> {\n const absPath = join(this.notesPath, relativePath);\n if (!existsSync(absPath)) {\n throw new Error(`Note does not exist: ${relativePath}`);\n }\n await unlink(absPath);\n }\n\n async move(fromPath: string, toPath: string): Promise<void> {\n const absFrom = join(this.notesPath, fromPath);\n const absTo = join(this.notesPath, toPath);\n\n if (!existsSync(absFrom)) {\n throw new Error(`Note does not exist: ${fromPath}`);\n }\n if (existsSync(absTo)) {\n throw new Error(`Destination already exists: ${toPath}`);\n }\n\n await mkdir(dirname(absTo), { recursive: true });\n await rename(absFrom, absTo);\n\n // Update wikilinks in other files that reference the old path\n await this.updateWikilinksAfterMove(fromPath, toPath);\n }\n\n private async updateWikilinksAfterMove(\n oldPath: string,\n newPath: string\n ): Promise<void> {\n const oldName = oldPath.replace(/\\.md$/, \"\").split(\"/\").pop()!;\n const newName = newPath.replace(/\\.md$/, \"\").split(\"/\").pop()!;\n\n if (oldName === newName) return;\n\n const files = await glob(\"**/*.md\", { cwd: this.notesPath });\n\n for (const file of files) {\n const absPath = join(this.notesPath, file);\n const content = await readFile(absPath, \"utf-8\");\n\n const pattern = new RegExp(`\\\\[\\\\[${escapeRegex(oldName)}(\\\\|[^\\\\]]*)?\\\\]\\\\]`, \"g\");\n if (!pattern.test(content)) continue;\n\n const updated = content.replace(pattern, `[[${newName}$1]]`);\n await writeFile(absPath, updated, \"utf-8\");\n }\n }\n\n private patchByHeading(\n content: string,\n heading: string,\n newContent: string\n ): string {\n const lines = content.split(\"\\n\");\n const headingPattern = new RegExp(`^#{1,6}\\\\s+${escapeRegex(heading)}\\\\s*$`, \"i\");\n\n let headingIndex = -1;\n let headingLevel = 0;\n\n for (let i = 0; i < lines.length; i++) {\n if (headingPattern.test(lines[i])) {\n headingIndex = i;\n const match = lines[i].match(/^(#{1,6})\\s+/);\n headingLevel = match ? match[1].length : 1;\n break;\n }\n }\n\n if (headingIndex === -1) {\n throw new Error(`Heading not found: ${heading}`);\n }\n\n // Find the end of this section (next heading of same or higher level)\n let endIndex = lines.length;\n for (let i = headingIndex + 1; i < lines.length; i++) {\n const match = lines[i].match(/^(#{1,6})\\s+/);\n if (match && match[1].length <= headingLevel) {\n endIndex = i;\n break;\n }\n }\n\n const before = lines.slice(0, headingIndex + 1);\n const after = lines.slice(endIndex);\n\n return [...before, \"\", newContent, \"\", ...after].join(\"\\n\");\n }\n}\n\nfunction escapeRegex(str: string): string {\n return str.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\n/**\n * Compute the three auto-stamp fields from note body text.\n * These are always factual/computable — no editorial judgment required.\n * - last_updated: today's date (YYYY-MM-DD)\n * - word_count: whitespace-separated token count\n * - estimated_read_time: assumes ~200 wpm average reading speed\n */\nfunction computeStamps(body: string): Record<string, unknown> {\n const today = new Date().toISOString().slice(0, 10); // YYYY-MM-DD\n const wordCount = body.trim() ? body.trim().split(/\\s+/).length : 0;\n const minutes = Math.max(1, Math.ceil(wordCount / 200));\n return {\n last_updated: today,\n word_count: wordCount,\n estimated_read_time: `${minutes} min read`,\n };\n}\n","import { readFile, writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { glob } from \"glob\";\nimport matter from \"gray-matter\";\n\nexport class FrontmatterManager {\n private notesPath: string;\n\n constructor(notesPath: string) {\n this.notesPath = notesPath;\n }\n\n async get(relativePath: string): Promise<Record<string, unknown>> {\n const absPath = join(this.notesPath, relativePath);\n const raw = await readFile(absPath, \"utf-8\");\n const { data } = matter(raw);\n return data;\n }\n\n async update(\n relativePath: string,\n fields: Record<string, unknown>\n ): Promise<void> {\n const absPath = join(this.notesPath, relativePath);\n const raw = await readFile(absPath, \"utf-8\");\n const { data, content } = matter(raw);\n\n for (const [key, value] of Object.entries(fields)) {\n if (value === null || value === undefined) {\n delete data[key];\n } else {\n data[key] = value;\n }\n }\n\n const updated = matter.stringify(content, data);\n await writeFile(absPath, updated, \"utf-8\");\n }\n}\n\nexport class TagManager {\n private notesPath: string;\n\n constructor(notesPath: string) {\n this.notesPath = notesPath;\n }\n\n async list(relativePath: string): Promise<string[]> {\n const absPath = join(this.notesPath, relativePath);\n const raw = await readFile(absPath, \"utf-8\");\n const { data, content } = matter(raw);\n\n const fmTags = Array.isArray(data.tags) ? (data.tags as string[]) : [];\n const inlineTags = [...content.matchAll(/(?:^|\\s)#([a-zA-Z][\\w-/]*)/g)].map(\n (m) => m[1]\n );\n\n return [...new Set([...fmTags, ...inlineTags])];\n }\n\n async add(relativePath: string, tags: string[]): Promise<void> {\n const absPath = join(this.notesPath, relativePath);\n const raw = await readFile(absPath, \"utf-8\");\n const { data, content } = matter(raw);\n\n const existing = Array.isArray(data.tags) ? (data.tags as string[]) : [];\n const merged = [...new Set([...existing, ...tags])];\n data.tags = merged;\n\n const updated = matter.stringify(content, data);\n await writeFile(absPath, updated, \"utf-8\");\n }\n\n async remove(relativePath: string, tags: string[]): Promise<void> {\n const absPath = join(this.notesPath, relativePath);\n const raw = await readFile(absPath, \"utf-8\");\n const { data, content } = matter(raw);\n\n // Remove from frontmatter\n if (Array.isArray(data.tags)) {\n data.tags = (data.tags as string[]).filter((t) => !tags.includes(t));\n }\n\n // Remove inline tags\n let updatedContent = content;\n for (const tag of tags) {\n const pattern = new RegExp(`(^|\\\\s)#${escapeRegex(tag)}(?=\\\\s|$)`, \"g\");\n updatedContent = updatedContent.replace(pattern, \"$1\");\n }\n\n const updated = matter.stringify(updatedContent, data);\n await writeFile(absPath, updated, \"utf-8\");\n }\n\n async renameVaultWide(oldTag: string, newTag: string): Promise<number> {\n const files = await glob(\"**/*.md\", { cwd: this.notesPath });\n let count = 0;\n\n for (const file of files) {\n const absPath = join(this.notesPath, file);\n const raw = await readFile(absPath, \"utf-8\");\n const { data, content } = matter(raw);\n let changed = false;\n\n // Rename in frontmatter\n if (Array.isArray(data.tags)) {\n const idx = (data.tags as string[]).indexOf(oldTag);\n if (idx >= 0) {\n (data.tags as string[])[idx] = newTag;\n changed = true;\n }\n }\n\n // Rename inline tags\n const pattern = new RegExp(`(^|\\\\s)#${escapeRegex(oldTag)}(?=\\\\s|$)`, \"g\");\n const updatedContent = content.replace(pattern, `$1#${newTag}`);\n if (updatedContent !== content) changed = true;\n\n if (changed) {\n const updated = matter.stringify(updatedContent, data);\n await writeFile(absPath, updated, \"utf-8\");\n count++;\n }\n }\n\n return count;\n }\n}\n\nfunction escapeRegex(str: string): string {\n return str.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n","import { watch, type FSWatcher } from \"chokidar\";\nimport { EventEmitter } from \"node:events\";\n\nexport interface WatcherEvents {\n changed: (paths: string[]) => void;\n error: (error: Error) => void;\n}\n\nexport class Watcher extends EventEmitter {\n private notesPath: string;\n private fsWatcher: FSWatcher | null = null;\n private debounceMs: number;\n private pendingChanges = new Set<string>();\n private debounceTimer: ReturnType<typeof setTimeout> | null = null;\n private readyPromise: Promise<void> | null = null;\n private usePolling: boolean;\n\n constructor(notesPath: string, debounceMs: number = 500, usePolling: boolean = false) {\n super();\n this.notesPath = notesPath;\n this.debounceMs = debounceMs;\n this.usePolling = usePolling;\n }\n\n start(): void {\n if (this.fsWatcher) return;\n\n this.fsWatcher = watch(\"**/*.md\", {\n cwd: this.notesPath,\n ignoreInitial: true,\n followSymlinks: true,\n ignored: [\n \"**/node_modules/**\",\n \"**/.semantic-pages-index/**\",\n \"**/.git/**\",\n ],\n ...(this.usePolling ? { usePolling: true, interval: 100 } : {}),\n });\n\n this.readyPromise = new Promise<void>((resolve) => {\n this.fsWatcher!.on(\"ready\", resolve);\n });\n\n this.fsWatcher.on(\"add\", (path) => this.enqueue(path));\n this.fsWatcher.on(\"change\", (path) => this.enqueue(path));\n this.fsWatcher.on(\"unlink\", (path) => this.enqueue(path));\n this.fsWatcher.on(\"error\", (err) => this.emit(\"error\", err));\n }\n\n async ready(): Promise<void> {\n if (this.readyPromise) await this.readyPromise;\n }\n\n stop(): void {\n if (this.debounceTimer) clearTimeout(this.debounceTimer);\n this.fsWatcher?.close();\n this.fsWatcher = null;\n this.pendingChanges.clear();\n }\n\n private enqueue(path: string): void {\n this.pendingChanges.add(path);\n\n if (this.debounceTimer) clearTimeout(this.debounceTimer);\n this.debounceTimer = setTimeout(() => {\n const paths = [...this.pendingChanges];\n this.pendingChanges.clear();\n this.emit(\"changed\", paths);\n }, this.debounceMs);\n }\n}\n"],"mappings":"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","names":["readFile","writeFile","join","existsSync","readFile","writeFile","join","existsSync","readFile","writeFile","mkdir","dirname","join","existsSync","body","readFile","writeFile","join","glob","matter","escapeRegex"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/core/indexer.ts"],"sourcesContent":["import { unified } from \"unified\";\nimport remarkParse from \"remark-parse\";\nimport remarkWikiLink from \"remark-wiki-link\";\nimport matter from \"gray-matter\";\nimport { glob } from \"glob\";\nimport { readFile, stat } from \"node:fs/promises\";\nimport { basename, join, relative } from \"node:path\";\nimport type { IndexedDocument } from \"./types.js\";\n\nconst CHUNK_TARGET_CHARS = 2000; // ~512 tokens\n\nexport class Indexer {\n private notesPath: string;\n private processor: any;\n\n constructor(notesPath: string) {\n this.notesPath = notesPath;\n this.processor = unified().use(remarkParse).use(remarkWikiLink);\n }\n\n async indexAll(): Promise<IndexedDocument[]> {\n const files = await glob(\"**/*.md\", { cwd: this.notesPath });\n const docs = await Promise.all(\n files.map((file) => this.indexFile(join(this.notesPath, file), file))\n );\n return docs;\n }\n\n async indexFile(\n absolutePath: string,\n relativePath: string\n ): Promise<IndexedDocument> {\n const [raw, fileStat] = await Promise.all([\n readFile(absolutePath, \"utf-8\"),\n stat(absolutePath),\n ]);\n const { data: frontmatter, content } = matter(raw);\n const tree = this.processor.parse(content);\n\n const wikilinks = this.extractWikilinks(tree);\n const tags = this.extractTags(content, frontmatter);\n const headers = this.extractHeaders(tree);\n const plainText = this.stripMarkdown(content);\n const chunks = this.chunkText(plainText);\n\n const title =\n (frontmatter.title as string) ||\n headers[0] ||\n basename(relativePath, \".md\");\n\n // Resolve modification time: prefer frontmatter date fields over fs.stat\n // Supports hit-em-with-the-docs (last_updated) and common alternatives\n const mtime = this.resolveMtime(frontmatter, fileStat.mtime);\n\n // Optional hit-em-with-the-docs fields (only populated when present)\n const loadPriority =\n typeof frontmatter.load_priority === \"number\"\n ? Math.min(10, Math.max(1, frontmatter.load_priority))\n : undefined;\n const status =\n typeof frontmatter.status === \"string\" ? frontmatter.status : undefined;\n const tier =\n typeof frontmatter.tier === \"string\" ? frontmatter.tier : undefined;\n const domains = Array.isArray(frontmatter.domains)\n ? (frontmatter.domains as string[])\n : undefined;\n const purpose =\n typeof frontmatter.purpose === \"string\" ? frontmatter.purpose : undefined;\n const relatedDocs = Array.isArray(frontmatter.related_docs)\n ? (frontmatter.related_docs as string[]).filter((v) => typeof v === \"string\")\n : undefined;\n\n return {\n path: relativePath,\n title,\n content: plainText,\n frontmatter,\n wikilinks,\n tags,\n headers,\n chunks,\n mtime,\n ...(loadPriority !== undefined && { loadPriority }),\n ...(status !== undefined && { status }),\n ...(tier !== undefined && { tier }),\n ...(domains !== undefined && { domains }),\n ...(purpose !== undefined && { purpose }),\n ...(relatedDocs !== undefined && { relatedDocs }),\n };\n }\n\n /**\n * Resolve the best available modification date for a document.\n * Priority: last_updated → updated → date → lastmod → fs.stat mtime\n * Accepts YYYY-MM-DD strings or full ISO timestamps.\n */\n private resolveMtime(\n frontmatter: Record<string, unknown>,\n statMtime: Date\n ): string {\n const candidates = [\n frontmatter.last_updated,\n frontmatter.updated,\n frontmatter.date,\n frontmatter.lastmod,\n ];\n for (const val of candidates) {\n if (!val) continue;\n const str = val instanceof Date ? val.toISOString() : String(val);\n const parsed = new Date(str);\n if (!isNaN(parsed.getTime())) return parsed.toISOString();\n }\n return statMtime.toISOString();\n }\n\n private extractWikilinks(tree: any): string[] {\n const links: string[] = [];\n const walk = (node: any) => {\n if (node.type === \"wikiLink\") {\n links.push(node.value || node.data?.alias || \"\");\n }\n if (node.children) {\n for (const child of node.children) walk(child);\n }\n };\n walk(tree);\n return [...new Set(links.filter(Boolean))];\n }\n\n private extractTags(content: string, frontmatter: Record<string, unknown>): string[] {\n const inlineTags = [...content.matchAll(/(?:^|\\s)#([a-zA-Z][\\w-/]*)/g)].map(\n (m) => m[1]\n );\n\n const fmTags = Array.isArray(frontmatter.tags)\n ? (frontmatter.tags as string[])\n : [];\n\n return [...new Set([...fmTags, ...inlineTags])];\n }\n\n private extractHeaders(tree: any): string[] {\n const headers: string[] = [];\n const walk = (node: any) => {\n if (node.type === \"heading\") {\n const text = this.nodeToText(node);\n if (text) headers.push(text);\n }\n if (node.children) {\n for (const child of node.children) walk(child);\n }\n };\n walk(tree);\n return headers;\n }\n\n private nodeToText(node: any): string {\n if (node.type === \"text\") return node.value;\n if (node.children) return node.children.map((c: any) => this.nodeToText(c)).join(\"\");\n return \"\";\n }\n\n private stripMarkdown(content: string): string {\n return content\n .replace(/```[\\s\\S]*?```/g, \"\")\n .replace(/`[^`]+`/g, \"\")\n .replace(/!\\[.*?\\]\\(.*?\\)/g, \"\")\n .replace(/\\[([^\\]]+)\\]\\(.*?\\)/g, \"$1\")\n .replace(/#{1,6}\\s+/g, \"\")\n .replace(/[*_~]{1,3}/g, \"\")\n .replace(/>\\s+/g, \"\")\n .replace(/\\|.*\\|/g, \"\")\n .replace(/-{3,}/g, \"\")\n .replace(/\\n{3,}/g, \"\\n\\n\")\n .trim();\n }\n\n chunkText(text: string): string[] {\n if (text.length <= CHUNK_TARGET_CHARS) return [text];\n\n const sentences = text.match(/[^.!?\\n]+[.!?\\n]+|[^.!?\\n]+$/g) || [text];\n const chunks: string[] = [];\n let current = \"\";\n\n for (const sentence of sentences) {\n if (current.length + sentence.length > CHUNK_TARGET_CHARS && current) {\n chunks.push(current.trim());\n current = \"\";\n }\n current += sentence;\n }\n if (current.trim()) chunks.push(current.trim());\n\n return chunks;\n }\n}\n"],"mappings":";;;;;;;AAAA,SAAS,eAAe;AACxB,OAAO,iBAAiB;AACxB,OAAO,oBAAoB;AAC3B,OAAO,YAAY;AACnB,SAAS,YAAY;AACrB,SAAS,UAAU,YAAY;AAC/B,SAAS,UAAU,YAAsB;AAGzC,IAAM,qBAAqB;AAEpB,IAAM,UAAN,MAAc;AAAA,EACX;AAAA,EACA;AAAA,EAER,YAAY,WAAmB;AAC7B,SAAK,YAAY;AACjB,SAAK,YAAY,QAAQ,EAAE,IAAI,WAAW,EAAE,IAAI,cAAc;AAAA,EAChE;AAAA,EAEA,MAAM,WAAuC;AAC3C,UAAM,QAAQ,MAAM,KAAK,WAAW,EAAE,KAAK,KAAK,UAAU,CAAC;AAC3D,UAAM,OAAO,MAAM,QAAQ;AAAA,MACzB,MAAM,IAAI,CAAC,SAAS,KAAK,UAAU,KAAK,KAAK,WAAW,IAAI,GAAG,IAAI,CAAC;AAAA,IACtE;AACA,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,UACJ,cACA,cAC0B;AAC1B,UAAM,CAAC,KAAK,QAAQ,IAAI,MAAM,QAAQ,IAAI;AAAA,MACxC,SAAS,cAAc,OAAO;AAAA,MAC9B,KAAK,YAAY;AAAA,IACnB,CAAC;AACD,UAAM,EAAE,MAAM,aAAa,QAAQ,IAAI,OAAO,GAAG;AACjD,UAAM,OAAO,KAAK,UAAU,MAAM,OAAO;AAEzC,UAAM,YAAY,KAAK,iBAAiB,IAAI;AAC5C,UAAM,OAAO,KAAK,YAAY,SAAS,WAAW;AAClD,UAAM,UAAU,KAAK,eAAe,IAAI;AACxC,UAAM,YAAY,KAAK,cAAc,OAAO;AAC5C,UAAM,SAAS,KAAK,UAAU,SAAS;AAEvC,UAAM,QACH,YAAY,SACb,QAAQ,CAAC,KACT,SAAS,cAAc,KAAK;AAI9B,UAAM,QAAQ,KAAK,aAAa,aAAa,SAAS,KAAK;AAG3D,UAAM,eACJ,OAAO,YAAY,kBAAkB,WACjC,KAAK,IAAI,IAAI,KAAK,IAAI,GAAG,YAAY,aAAa,CAAC,IACnD;AACN,UAAM,SACJ,OAAO,YAAY,WAAW,WAAW,YAAY,SAAS;AAChE,UAAM,OACJ,OAAO,YAAY,SAAS,WAAW,YAAY,OAAO;AAC5D,UAAM,UAAU,MAAM,QAAQ,YAAY,OAAO,IAC5C,YAAY,UACb;AACJ,UAAM,UACJ,OAAO,YAAY,YAAY,WAAW,YAAY,UAAU;AAClE,UAAM,cAAc,MAAM,QAAQ,YAAY,YAAY,IACrD,YAAY,aAA0B,OAAO,CAAC,MAAM,OAAO,MAAM,QAAQ,IAC1E;AAEJ,WAAO;AAAA,MACL,MAAM;AAAA,MACN;AAAA,MACA,SAAS;AAAA,MACT;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,GAAI,iBAAiB,UAAa,EAAE,aAAa;AAAA,MACjD,GAAI,WAAW,UAAa,EAAE,OAAO;AAAA,MACrC,GAAI,SAAS,UAAa,EAAE,KAAK;AAAA,MACjC,GAAI,YAAY,UAAa,EAAE,QAAQ;AAAA,MACvC,GAAI,YAAY,UAAa,EAAE,QAAQ;AAAA,MACvC,GAAI,gBAAgB,UAAa,EAAE,YAAY;AAAA,IACjD;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,aACN,aACA,WACQ;AACR,UAAM,aAAa;AAAA,MACjB,YAAY;AAAA,MACZ,YAAY;AAAA,MACZ,YAAY;AAAA,MACZ,YAAY;AAAA,IACd;AACA,eAAW,OAAO,YAAY;AAC5B,UAAI,CAAC,IAAK;AACV,YAAM,MAAM,eAAe,OAAO,IAAI,YAAY,IAAI,OAAO,GAAG;AAChE,YAAM,SAAS,IAAI,KAAK,GAAG;AAC3B,UAAI,CAAC,MAAM,OAAO,QAAQ,CAAC,EAAG,QAAO,OAAO,YAAY;AAAA,IAC1D;AACA,WAAO,UAAU,YAAY;AAAA,EAC/B;AAAA,EAEQ,iBAAiB,MAAqB;AAC5C,UAAM,QAAkB,CAAC;AACzB,UAAM,OAAO,CAAC,SAAc;AAC1B,UAAI,KAAK,SAAS,YAAY;AAC5B,cAAM,KAAK,KAAK,SAAS,KAAK,MAAM,SAAS,EAAE;AAAA,MACjD;AACA,UAAI,KAAK,UAAU;AACjB,mBAAW,SAAS,KAAK,SAAU,MAAK,KAAK;AAAA,MAC/C;AAAA,IACF;AACA,SAAK,IAAI;AACT,WAAO,CAAC,GAAG,IAAI,IAAI,MAAM,OAAO,OAAO,CAAC,CAAC;AAAA,EAC3C;AAAA,EAEQ,YAAY,SAAiB,aAAgD;AACnF,UAAM,aAAa,CAAC,GAAG,QAAQ,SAAS,6BAA6B,CAAC,EAAE;AAAA,MACtE,CAAC,MAAM,EAAE,CAAC;AAAA,IACZ;AAEA,UAAM,SAAS,MAAM,QAAQ,YAAY,IAAI,IACxC,YAAY,OACb,CAAC;AAEL,WAAO,CAAC,GAAG,oBAAI,IAAI,CAAC,GAAG,QAAQ,GAAG,UAAU,CAAC,CAAC;AAAA,EAChD;AAAA,EAEQ,eAAe,MAAqB;AAC1C,UAAM,UAAoB,CAAC;AAC3B,UAAM,OAAO,CAAC,SAAc;AAC1B,UAAI,KAAK,SAAS,WAAW;AAC3B,cAAM,OAAO,KAAK,WAAW,IAAI;AACjC,YAAI,KAAM,SAAQ,KAAK,IAAI;AAAA,MAC7B;AACA,UAAI,KAAK,UAAU;AACjB,mBAAW,SAAS,KAAK,SAAU,MAAK,KAAK;AAAA,MAC/C;AAAA,IACF;AACA,SAAK,IAAI;AACT,WAAO;AAAA,EACT;AAAA,EAEQ,WAAW,MAAmB;AACpC,QAAI,KAAK,SAAS,OAAQ,QAAO,KAAK;AACtC,QAAI,KAAK,SAAU,QAAO,KAAK,SAAS,IAAI,CAAC,MAAW,KAAK,WAAW,CAAC,CAAC,EAAE,KAAK,EAAE;AACnF,WAAO;AAAA,EACT;AAAA,EAEQ,cAAc,SAAyB;AAC7C,WAAO,QACJ,QAAQ,mBAAmB,EAAE,EAC7B,QAAQ,YAAY,EAAE,EACtB,QAAQ,oBAAoB,EAAE,EAC9B,QAAQ,wBAAwB,IAAI,EACpC,QAAQ,cAAc,EAAE,EACxB,QAAQ,eAAe,EAAE,EACzB,QAAQ,SAAS,EAAE,EACnB,QAAQ,WAAW,EAAE,EACrB,QAAQ,UAAU,EAAE,EACpB,QAAQ,WAAW,MAAM,EACzB,KAAK;AAAA,EACV;AAAA,EAEA,UAAU,MAAwB;AAChC,QAAI,KAAK,UAAU,mBAAoB,QAAO,CAAC,IAAI;AAEnD,UAAM,YAAY,KAAK,MAAM,+BAA+B,KAAK,CAAC,IAAI;AACtE,UAAM,SAAmB,CAAC;AAC1B,QAAI,UAAU;AAEd,eAAW,YAAY,WAAW;AAChC,UAAI,QAAQ,SAAS,SAAS,SAAS,sBAAsB,SAAS;AACpE,eAAO,KAAK,QAAQ,KAAK,CAAC;AAC1B,kBAAU;AAAA,MACZ;AACA,iBAAW;AAAA,IACb;AACA,QAAI,QAAQ,KAAK,EAAG,QAAO,KAAK,QAAQ,KAAK,CAAC;AAE9C,WAAO;AAAA,EACT;AACF;","names":[]}
|
package/dist/indexer-UEYFOOER.js
DELETED
|
File without changes
|