@imunitic/synapse 0.0.1-test.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.
Files changed (40) hide show
  1. package/Index.md.template +23 -0
  2. package/bin/synapse-hook.cjs +19 -0
  3. package/bin/synapse-setup.cjs +420 -0
  4. package/bin/synapse.cjs +20 -0
  5. package/commands/synapse-design-note.md +229 -0
  6. package/commands/synapse-init.md +354 -0
  7. package/commands/synapse-note.md +196 -0
  8. package/commands/synapse-rebuild-diff.md +314 -0
  9. package/commands/synapse-rebuild-full.md +152 -0
  10. package/commands/synapse-status.md +144 -0
  11. package/commands/synapse-task-note.md +133 -0
  12. package/commands/synapse-vault-tidy.md +187 -0
  13. package/harness/claude/hooks.json +54 -0
  14. package/harness/codex/hooks.json +54 -0
  15. package/harness/codex/skills/synapse-design-note/SKILL.md +236 -0
  16. package/harness/codex/skills/synapse-init/SKILL.md +354 -0
  17. package/harness/codex/skills/synapse-note/SKILL.md +212 -0
  18. package/harness/codex/skills/synapse-rebuild-diff/SKILL.md +315 -0
  19. package/harness/codex/skills/synapse-rebuild-full/SKILL.md +149 -0
  20. package/harness/codex/skills/synapse-status/SKILL.md +146 -0
  21. package/harness/codex/skills/synapse-task-note/SKILL.md +133 -0
  22. package/harness/codex/skills/synapse-vault-tidy/SKILL.md +187 -0
  23. package/harness/opencode/plugin/synapse.js +164 -0
  24. package/lib/obsidian-mcp-refresh.cjs +303 -0
  25. package/lib/resolve-binaries.cjs +54 -0
  26. package/package.json +26 -0
  27. package/skills/synapse-node/SKILL.md +211 -0
  28. package/skills/synapse-node-authoring/SKILL.md +188 -0
  29. package/skills/synapse-node-format/SKILL.md +205 -0
  30. package/skills/synapse-orientation/SKILL.md +468 -0
  31. package/skills/synapse-query/SKILL.md +99 -0
  32. package/skills/synapse-task/SKILL.md +261 -0
  33. package/skills/synapse-vault/SKILL.md +107 -0
  34. package/synapse-claude.md +220 -0
  35. package/synapse-fence-languages.conf.template +24 -0
  36. package/synapse-ignore-files.conf.template +45 -0
  37. package/synapse-module-boilerplate.conf.template +24 -0
  38. package/synapse-projects.conf.template +14 -0
  39. package/synapse-prompt-stopwords.conf.template +594 -0
  40. package/synapse.conf.template +23 -0
@@ -0,0 +1,261 @@
1
+ ---
2
+ name: synapse-task
3
+ description: Update Obsidian task notes' status frontmatter and Notes sections, enforcing status transitions so active work is IN-PROGRESS and completed checklists move only to REVIEW (not DONE).
4
+ ---
5
+
6
+ # Synapse Task Status Skill
7
+
8
+ Tracks status for task notes created via `/synapse-note --task` and compiled via `/synapse-task-note`,
9
+ using a `status:` frontmatter field and GFM `- [ ]`/`- [x]` checklists (the vault's native format,
10
+ no conversion needed).
11
+
12
+ ## When to invoke (proactive — do not wait to be asked)
13
+
14
+ Applies to any task note, in any project — a task note is identified by having
15
+ `task_id` and `status` frontmatter fields, not by which prefix `task_id` uses
16
+ (`proj-NNN`, `sb-NNN`, or any other project's prefix from
17
+ `~/.claude/synapse-projects.conf`). Never gate this skill on a specific
18
+ prefix.
19
+
20
+ Invoke this skill **automatically** in two situations:
21
+
22
+ 1. **Starting work on a task** — as soon as the user confirms work is
23
+ beginning, before writing any code. Set `status: IN-PROGRESS` and update
24
+ `last_updated`. No notes needed at this point.
25
+
26
+ 2. **Finishing work on a task** — after all phases are committed and
27
+ the task note's checklist has been updated. Set `status: REVIEW` (if all
28
+ items checked) or `status: IN-PROGRESS` (if any remain), update
29
+ `last_updated`, and append an implementation summary to the `## Notes`
30
+ section.
31
+
32
+ Never wait to be asked — apply this skill proactively at both transitions.
33
+
34
+ ## What this skill does
35
+
36
+ - Updates the `status:` frontmatter field (`TODO` → `IN-PROGRESS` or
37
+ `REVIEW`).
38
+ - Updates `last_updated` in frontmatter.
39
+ - At task completion: appends concise implementation bullets to the
40
+ existing `## Notes` section (or creates one if none exists).
41
+
42
+ ## Status transitions
43
+
44
+ | Checklist state | `status:` value |
45
+ |-----------------|-----------------|
46
+ | Any `[ ]` unchecked | `IN-PROGRESS` |
47
+ | All `[x]` checked | `REVIEW` |
48
+
49
+ **Never set `DONE` through this workflow.** Do not manually write `DONE`
50
+ into `status:` either — always go through this skill, which caps at
51
+ `REVIEW`.
52
+
53
+ ## Procedure
54
+
55
+ 1. Find the task note: `mcp__obsidian__search_query` with
56
+ `{"==": [{"var": "frontmatter.task_id"}, "<task-id>"]}`, where `<task-id>`
57
+ is the specific task's ID (whatever prefix it uses — `proj-035`, `sb-008`,
58
+ etc.), then `mcp__obsidian__vault_read` the matched file.
59
+ 2. Inspect its checklist items (`- [ ]` / `- [x]`).
60
+ 3. Determine the new `status:` value: `IN-PROGRESS` if any unchecked,
61
+ `REVIEW` if all checked.
62
+ 4. Fetch machine local time: `date '+%Y-%m-%d %H:%M'` — never use inferred
63
+ time.
64
+ 5. Update `status:` and `last_updated:` with two `synapse frontmatter set`
65
+ calls, one per field: `synapse frontmatter set <path> status <value>`
66
+ then `synapse frontmatter set <path> last_updated "{now}"`. Each call
67
+ changes exactly that one line and nothing else, entirely inside the
68
+ compiled binary — the note's body never enters your context at all.
69
+ When the command isn't available, fall back to **read-modify-write**:
70
+ `vault_read` the file, replace the one line in the returned content,
71
+ `vault_write` the whole file back — byte-preserving, because you write
72
+ back what you read.
73
+
74
+ **Never use `vault_patch` with `targetType: frontmatter`.** It is *not*
75
+ field-local, despite reading that way: two patches
76
+ (one for `status`, one for `last_updated`) re-serialise the entire
77
+ frontmatter block — every quoted value loses its quotes (`created:
78
+ "2024-01-01 15:57"` → `created: 2024-01-01 15:57`) and a long `title:`
79
+ is folded across two lines. Both are valid YAML, so nothing breaks
80
+ loudly, but any tool string-matching `^title: "` stops matching, and
81
+ every status transition silently reformats the note. Since transitions
82
+ are this skill's main job, that reformatting would land on every task
83
+ note in the vault. `frontmatter set` exists specifically so this never
84
+ has to be reached for.
85
+ 6. **For completion only:** append implementation bullets to the existing
86
+ `## Notes` section with `mcp__obsidian__vault_patch`
87
+ (`targetType: heading`, `target: "{H1 title}::Notes"`, `operation:
88
+ append`) — append is safe here too. **The target must be the full
89
+ nested path** (`H1::Notes`), not just `"Notes"`: since `## Notes` is
90
+ nested under the top-level `# {title}` heading, the
91
+ plugin's heading lookup fails with "target not found in document" on
92
+ the bare leaf name and requires the `::`-joined path from the tool's
93
+ own docs. If no notes section exists, same call with
94
+ `createTargetIfMissing: true`.
95
+
96
+ The vault_patch hazards below are the task-note-specific instance of a general rule; the
97
+ `synapse-vault` skill carries the full list (H1 replace, nested heading paths, frontmatter
98
+ re-serialisation) for every note, not just task notes.
99
+
100
+ **Do not use `vault_patch` with `operation: replace` on the top-level
101
+ heading to edit checklist items.** "Content beneath
102
+ heading" for a top-level (`#`) heading extends through *all* nested
103
+ subheadings (including `## Notes`), not just the leading paragraph/checklist
104
+ directly under it — a replace there silently deletes everything past the
105
+ checklist, including the Notes section. To check off checklist items,
106
+ instead `vault_read` the full file, edit the `- [ ]` → `- [x]` lines in the
107
+ returned content, and `vault_write` the whole file back.
108
+
109
+ ## Notes format
110
+
111
+ Append flat bullets to the existing notes section. Keep them factual and short:
112
+
113
+ - What was implemented / changed
114
+ - Key design decisions or deviations from the spec
115
+ - Files added or modified
116
+ - Validation result (`just check` / test counts)
117
+
118
+ Avoid creating a new heading if a notes section already exists at any
119
+ level. Append to the last existing notes section.
120
+
121
+ ## Task file structure
122
+
123
+ Each task note, in any project, has **exactly one top-level heading** (the
124
+ task itself, `# {title}`). Implementation steps go as `- [ ]` checklist items
125
+ **under that heading**, not as additional headings. Do not create `##
126
+ Step` sub-headings for implementation steps.
127
+
128
+ Correct structure:
129
+ ```md
130
+ # Implement something
131
+
132
+ Description of the task.
133
+ - [ ] Step one
134
+ - [ ] Step two
135
+ - [ ] Step three
136
+ ```
137
+
138
+ Wrong structure (do not do this):
139
+ ```md
140
+ # Implement something
141
+ ## Step one
142
+ ## Step two
143
+ ```
144
+
145
+ ### Inline code examples in checklist items
146
+
147
+ Only applicable to code tasks. Non-code tasks (research, documentation,
148
+ configuration, simple one-liners) need no code blocks at all.
149
+
150
+ For code tasks, substantive checklist items (type definitions, API
151
+ surfaces, interface signatures) must include a fenced code block showing
152
+ the exact interface, directly under the checklist item text, indented to
153
+ match:
154
+
155
+ ```md
156
+ - [ ] Implement Foo
157
+
158
+ One-line description of what this covers and any non-obvious constraints.
159
+
160
+ ```ocaml
161
+ // path/to/file
162
+ interface or type definition goes here
163
+ ```
164
+ ```
165
+
166
+ Draw the signatures directly from the design document. Do not paraphrase
167
+ or abbreviate — the checklist item is the implementor's authoritative
168
+ reference.
169
+
170
+ ### Notes section (pre-implementation)
171
+
172
+ Every new task note must have a `## Notes` section, **written before
173
+ implementation begins** (not only appended after completion). For tasks
174
+ backed by a design document, populate it with:
175
+
176
+ - Design reference: file path and task ID
177
+ - Key constraints the implementor must not miss
178
+ - Deliberate exclusions (what is out of scope and why)
179
+
180
+ For research or simple tasks with no design doc, a brief one-liner stating
181
+ the goal or context is sufficient. Omit the section only if there is
182
+ genuinely nothing non-obvious to capture.
183
+
184
+ ```md
185
+ ## Notes
186
+
187
+ Design reference: docs/design/foo_design.md (task-NNN).
188
+
189
+ - Key constraint one
190
+ - Key constraint two
191
+ - Out of scope: X (reason), Y (reason)
192
+ ```
193
+
194
+ Post-implementation summaries are appended under `## Notes` as dated
195
+ sub-headings (`### YYYY-MM-DD — ...`), as shown in the completion
196
+ procedure above.
197
+
198
+ ## Creating a GitHub issue from a task
199
+
200
+ When the user asks to create a GitHub issue from a task note:
201
+
202
+ 1. **Title** — `<TASK-ID> — <description>`, where `<TASK-ID>` is the
203
+ `task_id` frontmatter field (whatever project's prefix it uses — e.g.
204
+ `proj-030`, `sb-008`) and `<description>` is the heading with any leading
205
+ task-id prefix stripped (`<TASK-ID>`, `<TASK-ID> —`, or `<TASK-ID> - `).
206
+ Example: heading `# proj-035 - Widget catalog rework` → title
207
+ `proj-035 — Widget catalog rework`.
208
+ 2. **Body** — the full content of the top-level heading section: the
209
+ description paragraph and all checklist items (with any inline code
210
+ blocks) — already GFM Markdown, so this goes straight into
211
+ `gh issue create --body-file` unchanged.
212
+ 3. **First comment** — the full content of the `## Notes` section,
213
+ including any dated sub-headings and their bullets, straight into
214
+ `gh issue comment --body-file` unchanged.
215
+ 4. **State** — `gh issue create` has no `--state` flag; every issue is
216
+ created open, then closed as a separate step if needed. Leave it open
217
+ for `TODO`, `IN-PROGRESS`, and `REVIEW`. For `DONE`, close it after
218
+ creating: `gh issue close <n>` (defaults to `state_reason: completed`).
219
+ For `CANCELED`/`CANCELLED`, close it with `gh issue close <n> --reason
220
+ "not planned"`.
221
+
222
+ ```sh
223
+ gh issue create --title "..." --repo <owner>/<repo> --body-file body.md
224
+ gh issue comment <issue-number> --repo <owner>/<repo> --body-file notes.md
225
+ ```
226
+
227
+ To edit an already-created issue/comment instead of creating a new one:
228
+ `gh issue edit <n> --body-file <file>` for the issue body; comments have no
229
+ `gh` subcommand for editing, use `gh api
230
+ repos/<owner>/<repo>/issues/comments/<comment-id> -X PATCH -f
231
+ body="$(cat <file>)"` (get the comment ID from the URL `gh issue comment`
232
+ printed when it was created, or `gh api
233
+ repos/<owner>/<repo>/issues/<n>/comments`).
234
+
235
+ ## Guardrails
236
+
237
+ - **Never write `DONE`** — not in `status:`, not manually, not through any
238
+ other path. The cap is always `REVIEW`.
239
+ - **Never `git commit` or `git push` in the project repo while a task note
240
+ tracked by this skill is anything but `DONE`.** Work through the whole
241
+ checklist stays uncommitted in the working tree — do not commit after each
242
+ checked-off item; that fragments the history and loses the thread of what
243
+ the task actually did. Commit (and push) only once the task note's
244
+ `status` is `DONE` and the human has explicitly asked for that specific
245
+ commit/push — a prior "commit and push" is not a standing license for the
246
+ next one, and setting `DONE` is itself a human action this skill never
247
+ takes (see "Never write `DONE`" above).
248
+ - **Never create a task note via a bare `vault_write`.** Always
249
+ `/synapse-note --task`, or `/synapse-task-note` when compiling one from a
250
+ `Ready` design note. A freeform write skips both the skeleton (checklist
251
+ items plus a single `## Notes` section) and every guardrail in this file —
252
+ there is no partial-credit version of following this skill.
253
+ - **Never restructure an existing task note's shape via `vault_write`/
254
+ `vault_patch` outside this skill's own procedure.** Ad hoc edits that add
255
+ new headings, drop the checklist, or otherwise diverge from the skeleton
256
+ are what this guardrail exists to prevent — not just wrong `status:`
257
+ values.
258
+ - Preserve `task_id` untouched — it is set manually (or resolved once by
259
+ `/synapse-note --task`) and must not be auto-generated or overwritten.
260
+ - If checklist content is ambiguous or missing, default to `IN-PROGRESS`.
261
+ - Keep notes wording deterministic; avoid speculative claims.
@@ -0,0 +1,107 @@
1
+ ---
2
+ name: synapse-vault
3
+ description: Synapse Vault is durable memory, and only its Index.md is ever auto-injected — every other note is pull-only. Load this BEFORE answering from your own reasoning about tooling behaviour, a past decision, a convention, a gotcha, or anything a previous session might have written down; and before creating or editing any note in the vault. Covers what to search with, and the vault_patch operations that silently destroy a note.
4
+ ---
5
+
6
+ # Synapse Vault: search it first, and do not destroy it
7
+
8
+ Knowledge, not a procedure — like `synapse-node-format`. Nothing here tells you what to do
9
+ next; it tells you what is true about the store you are about to read from or write to.
10
+
11
+ ## Only `Index.md` is pushed. Everything else is pull.
12
+
13
+ The SessionStart hook injects the vault's `Index.md` — folder layout and the namespace
14
+ catalogue. That is all. The other notes exist and are never injected, so the only thing standing
15
+ between you and a written-down answer is deciding to look.
16
+
17
+ **The failure this exists to prevent is not "not knowing". It is re-deriving, in the reply, a
18
+ thing the vault already says** — sometimes in three places at once. A wrong answer gets corrected;
19
+ a re-derived one is invisible, costs a turn, and quietly asserts that the memory system does not
20
+ work.
21
+
22
+ Search before you answer when the question is about:
23
+
24
+ - how a tool behaves, especially an option you have not used before
25
+ - whether a decision was already made, and why it went that way
26
+ - a convention, a naming rule, a threshold, a measured number
27
+ - anything that starts "I think it works like…"
28
+
29
+ ```
30
+ mcp__obsidian__search_simple full-text, relevance-ranked, with match context
31
+ mcp__obsidian__search_query JsonLogic over frontmatter, tags, links, backlinks, path globs
32
+ mcp__obsidian__vault_list when you already know roughly where it is
33
+ mcp__obsidian__vault_read with targetType/target to pull one section, not the whole note
34
+ ```
35
+
36
+ A search that returns nothing is a real result and worth one line in the note you then write —
37
+ a negative result cannot be rediscovered by searching for it.
38
+
39
+ ## What silently destroys a note
40
+
41
+ Every item below returns success.
42
+
43
+ - **`vault_patch` + `operation: replace` targeting the H1 replaces the entire document.**
44
+ `targetScope` defaults to `content` = "everything below this heading until one of the same or
45
+ higher level", and below the H1 there is no higher level, so that is the whole note. To insert
46
+ between the title and the first H2 — a status or metadata blockquote, the usual reason to touch
47
+ that region — use **`operation: prepend` against the H1**. Never `replace`.
48
+ - **Heading targets are nested paths.** An H2 is `H1 title::H2 title`; a bare `H2 title` fails
49
+ with "target not found in document", which reads like the heading is missing rather than like
50
+ the path is wrong. `mcp__obsidian__vault_get_document_map` returns the exact `::` paths.
51
+ - **`operation: append` against the bare H1 can land one heading level too deep.** When the
52
+ document's last section has a subheading of its own (e.g. `## Approach` ending in
53
+ `### Alternatives considered`), an appended `## Notes` can come back as `### Notes`, nested
54
+ under that last subsection instead of sitting as a sibling top-level heading. Target the
55
+ specific preceding heading directly, or check `mcp__obsidian__vault_get_document_map` after —
56
+ it shows the real heading tree, so a wrong level is visible immediately rather than assumed
57
+ from the call's own success response.
58
+ - **Never `vault_patch` frontmatter.** `targetType: frontmatter` reads as field-local and is not:
59
+ it re-serialises the whole YAML block, so unrelated values lose their quotes, long lines get
60
+ re-wrapped, and an array value comes back as a quoted string instead of a real YAML list. Use
61
+ `synapse frontmatter set <path> <key> <value>` (or `--add-tag`/`--remove-tag` for `tags`
62
+ specifically; `synapse frontmatter get <path> <key>` reads one back) — it changes one field in
63
+ the compiled binary, byte-preserving, without ever
64
+ pulling the note's body into your context. It only handles a flat scalar or comma-separated
65
+ array field; for anything it doesn't cover (a block-style value, or an edit outside
66
+ frontmatter), fall back to reading the file, changing the one line, and writing the whole file
67
+ back.
68
+ - **Do not patch for whitespace-precise edits.** A blank line between a heading and the block
69
+ under it is exactly what a patch may normalise away. A disk-level file edit with a unique anchor
70
+ is more predictable; Obsidian picks up external changes automatically.
71
+ - **Never hardcode the vault path.** The REST API targets whichever vault the running headless
72
+ Obsidian has open. If you must reach disk, the path comes from `OBSIDIAN_VAULT_DIR` in
73
+ `~/.claude/synapse.conf`.
74
+
75
+ ## Verify with a structural invariant, not by re-reading what you wrote
76
+
77
+ Re-reading the region you just changed can only confirm the change landed. It is silent about
78
+ everything else the operation touched, which for a destructive edit is the entire question.
79
+
80
+ Count the H2s (`awk '/^## /' file`) or compare the byte size against what you read. **A
81
+ verification that cannot fail in the direction you are worried about is not a verification.**
82
+
83
+ ## If you do destroy something
84
+
85
+ `synapse-hook db-sync` auto-commits every vault edit, so the intact version is one
86
+ `git show <sha>:<path>` away in the vault's own git history. That hook is a vault-wide undo for
87
+ destructive tool calls, not merely a record of intentional edits.
88
+
89
+ ## Tagging is part of writing a note, not a separate pass
90
+
91
+ Every note-authoring command (`synapse-note`, `synapse-design-note`, `synapse-task-note`) applies tags as one of the steps in creating or substantially updating a note. There is no separate tagging pass or command — `/synapse-vault-tidy`'s recategorization signal reads tag data, it never writes it.
92
+
93
+ The vocabulary is `synapse-tag-vocabulary.conf` — one tag per line, `#`-comments allowed, agent-maintained but freely human-editable. It resolves with the same tiered lookup `synapse-projects.conf` uses: first `$XDG_CONFIG_HOME/synapse/synapse-tag-vocabulary.conf` if `$XDG_CONFIG_HOME` is set, else `~/.config/synapse/synapse-tag-vocabulary.conf`, then `~/.claude/synapse-tag-vocabulary.conf` — read whichever of those exists first. If none exists yet, create it fresh at `$XDG_CONFIG_HOME/synapse/synapse-tag-vocabulary.conf` when `$XDG_CONFIG_HOME` is set, else `~/.config/synapse/synapse-tag-vocabulary.conf` if `~/.config` already exists as a directory, else `~/.claude/synapse-tag-vocabulary.conf` as the final fallback. It lives outside the portable Synapse package, same as `synapse-projects.conf` — never committed there, never copied between machines.
94
+
95
+ Before adding a tag to a note, read the conf and prefer an existing entry over minting a new one. Add a new entry only for a genuinely new concept the existing vocabulary doesn't cover, and append it to the conf when you do. A note's tag set is agent-owned working content: a command may add or remove a tag freely, including one a human added by hand — there is no special protection that would block reconsidering it.
96
+
97
+ Tagging many existing notes at once (a backfill) should be rare to the point of not recurring — every note created through the authoring commands above is tagged inline as part of being written, so a vault only ever needs a backfill once, for notes that predate tagging being wired in at all. If one is ever needed anyway, resolve tags mechanically first: title text, folder path, and frontmatter (`task_id` prefix, `project` field) matched against the vocabulary settle the large majority of notes with no content read at all. Read a note's body only when those signals don't already resolve to at least one vocabulary tag. A full `vault_read` on every note in a large backfill spends session context on a decision cheap signals usually already make.
98
+
99
+ ## The graph side of the vault
100
+
101
+ `synapse/{repo}@{branch}/` is not free-form notes. Nodes are written by `synapse write-node`
102
+ and regenerated by the `synapse-node` skill — never hand-edited, because the writer computes
103
+ `sources` hashes, `sources_digest` and the `## Sources` mirror, and a hand edit desynchronises
104
+ them. `_profile.txt` and `_manifest.tsv` are human-readable and may be edited directly. The
105
+ machine-only artifacts -- `_index.bin`, `_refs.tsv`, `_tags_cache.bin` -- are not here at all: they
106
+ live in `~/.claude/synapse-work/{repo}@{branch}/`, because they are derived, rebuildable and large,
107
+ and the vault is version-controlled.
@@ -0,0 +1,220 @@
1
+ # Synapse Vault as permanent memory
2
+
3
+ The user keeps a permanent, curated knowledge base — **Synapse Vault** —
4
+ as a memory system separate from and complementary to the `~/.claude`
5
+ auto-memory system: use the Vault for durable, browsable
6
+ knowledge-base notes, not for session bookkeeping. It's an Obsidian vault,
7
+ running headless at login with the Local REST API plugin installed — see
8
+ "Reading and writing the vault" below for how to reach it. A SessionStart hook
9
+ already injects the vault's `Index.md` at the start of every session, so
10
+ you shouldn't need to go read it yourself. Don't re-read it reflexively,
11
+ but do treat its injected contents as live information, not background
12
+ flavor. If the hook instead reports that the configured vault has no
13
+ `Index.md` yet, offer to seed it from the shipped default
14
+ (`${CLAUDE_PLUGIN_ROOT}/Index.md.template`) before creating or
15
+ linking any note in this session — never copy it yourself without asking;
16
+ seeding the vault's foundational bootstrap file warrants a confirmation
17
+ step the way an ordinary note-write doesn't.
18
+
19
+ **This is a primary, load-bearing memory system, not an optional nicety.**
20
+ Actively use it — don't wait to be asked. You MUST create or update a note
21
+ whenever, during a session, any of the following happens. Before deciding
22
+ which folder, check `Index.md`'s folder list for the matching category —
23
+ don't rely on categories already in memory from earlier in the session,
24
+ since a vault owner's own `Index.md` is the only authority on what exists
25
+ and what each folder means:
26
+
27
+ - A non-trivial bug is diagnosed and fixed, especially if the root cause
28
+ or the fix was non-obvious.
29
+ - The user states a preference, a decision, or a piece of standing
30
+ context ("we always do X", "the reason we do Y is...") that isn't
31
+ already captured in a note.
32
+ - A project reaches a milestone, or its direction/scope changes.
33
+ - You do research (reading docs, comparing libraries/approaches,
34
+ evaluating tradeoffs) that would be wasteful to redo from scratch next
35
+ time.
36
+ - An existing note is now stale or wrong in light of what just happened —
37
+ update it in place rather than leaving it to rot.
38
+
39
+ If none of these clearly apply, err on the side of asking yourself the
40
+ question rather than silently skipping it — a nudge hook periodically
41
+ prompts exactly this check; treat that prompt as a real question requiring
42
+ a real yes/no answer, not a formality to wave past.
43
+
44
+ - **Linking is the highest-priority step, not an afterthought.** Before
45
+ writing a new note, search for related existing notes (see "Searching
46
+ notes" below) and link to them — prefer linking over duplicating content
47
+ every time a related note exists. If genuinely nothing else applies,
48
+ link the new note back to the index itself rather than leaving it an
49
+ orphan with zero backlinks.
50
+ - **Split growing notes rather than letting one balloon.** During a long
51
+ session (e.g. a multi-hour debugging or optimization effort), don't just
52
+ keep appending every new finding to the same note indefinitely. Once a
53
+ note is accumulating genuinely separate findings/topics rather than one
54
+ cohesive update, split the new material into its own linked note instead
55
+ of growing the original further. A handful of focused, well-linked notes
56
+ is more useful later than one sprawling one — easier to search, easier to
57
+ link into from elsewhere, easier to skim.
58
+ - Folders: two are structurally fixed, and hardcoded here on purpose —
59
+ `designs/` (design directions already discussed and agreed, created by
60
+ `/synapse-design-note`) and `tasks/` (concrete tracked tasks, created
61
+ only by `/synapse-task-note` or `/synapse-note --task`, never freeform).
62
+ Every install has both; the commands that write to them assume those
63
+ exact names regardless of what a vault's `Index.md` says. Beyond those
64
+ two, `Index.md`'s own folder list is the authority on what exists and
65
+ what each one is for — check it rather than assuming a fixed set. A
66
+ fresh vault ships with `research/`, `scratchpad/`, and `inbox/` as a
67
+ starting point (see `${CLAUDE_PLUGIN_ROOT}/Index.md.template`), but a vault owner's own
68
+ `Index.md` may have renamed or restructured them; if nothing about a
69
+ note fits an existing category, use whichever folder `Index.md` itself
70
+ marks as the catch-all (`inbox/` by default) rather than forcing a bad
71
+ fit or inventing a folder for a one-off. Agents may create new folders
72
+ beyond what `Index.md` lists, but folder depth is capped at two levels
73
+ (`folder/subfolder`, never deeper). Creating a new top-level folder
74
+ requires adding a matching entry to `Index.md` in the same action — the
75
+ index must never fall behind what's actually on disk.
76
+ - **`tasks/` and `designs/` both group one level deeper by project**,
77
+ using the two-level cap above: `tasks/{project}/` and
78
+ `designs/{project}/` (e.g. `tasks/widget/`, `designs/synapse/`). A design
79
+ note already carries its project as a `project:` frontmatter field, so
80
+ its subfolder is that value directly. A task note has no such field —
81
+ decide from its task-prefix family per `Index.md`'s `tasks/` section
82
+ (`proj-NNN` → `widget`, `sb-NNN` → `synapse`) or, for a non-prefixed note,
83
+ from its title naming that same project. Either way, a note with no
84
+ established project family stays flat directly in `tasks/` or
85
+ `designs/` — don't invent a one-note subfolder.
86
+ - **`inbox/` notes are always freeform-titled, never numbered.** This
87
+ includes the standalone-idea subtype ("worth doing someday" items that
88
+ surface mid-discussion) — it gets a plain descriptive title like any
89
+ other inbox note, not a sequential id. `inbox/{high,medium,low}/`
90
+ subfolders may exist as an optional, human-managed-only triage layer —
91
+ an agent never creates them, never infers a priority, and never moves a
92
+ note into or between them. Always write a new inbox note flat into
93
+ `inbox/` root; the user triages it by hand whenever they choose to.
94
+
95
+ ## Reading and writing the vault
96
+
97
+ Obsidian runs headless at login (via a startup plugin) with the Claude
98
+ vault already open, and the Local REST API plugin is installed there —
99
+ this is the only valid way to reach the vault, for reads *and* for
100
+ writes, and it always targets whichever vault is currently open in the
101
+ running Obsidian instance, not a hardcoded path. The `obsidian` MCP
102
+ server wraps that REST API. Do not resolve or care about
103
+ `$OBSIDIAN_VAULT_DIR` (see `~/.claude/synapse.conf`) unless the MCP tools
104
+ are erroring or unavailable and you must fall back to grepping files on
105
+ disk directly — that path variable matters only for that fallback case,
106
+ since the vault is also reachable as plain files on disk at that
107
+ location.
108
+
109
+ **Every write to a note goes through `mcp__obsidian__vault_write` or
110
+ `vault_patch`. Never the `Write`/`Edit` tools on the on-disk path** — not
111
+ for a one-line change, and least of all when those tools are already in
112
+ hand from editing code earlier in the same turn, because that proximity
113
+ is precisely what causes this to be violated. The vault being an ordinary
114
+ directory means the wrong path *works*: Obsidian's file watcher
115
+ converges, the auto-commit hook matches `Write|Edit` as well as the MCP
116
+ tools, and nothing visibly breaks — which is why the habit never
117
+ self-corrects on its own. The reason is not a failure mode to dodge; it
118
+ is that an invariant upheld only when convenient is worth nothing.
119
+ Nothing else in the system can rely on it, and every note then has to be
120
+ re-checked by hand instead of trusted. Synapse's own tooling holds this
121
+ line — `synapse write-node` curls the same REST API rather than
122
+ writing files directly — so agent writes have no reason to differ.
123
+
124
+ If a project's `.claude.json` `mcpServers.obsidian` entry ever diverges
125
+ from the user-scoped one (e.g. points at the wrong vault path via a stdio
126
+ `obsidian-mcp` package instead of the REST API), that's a bug in that
127
+ project's config, not a Synapse Vault routing choice — fix it by removing
128
+ the project-level override so the correct user-scoped REST API server
129
+ applies.
130
+
131
+ - You may create and edit notes in this vault **without asking for
132
+ permission first**, as long as each note is placed in the folder
133
+ matching its category per `Index.md`.
134
+ - Filenames are human-readable titles (not timestamp-prefixed — Obsidian's
135
+ sidebar/graph display the filename directly, so a timestamp prefix reads
136
+ poorly there). Sanitize filesystem-illegal characters (`/ : * ? " < > |`)
137
+ but otherwise keep the title as-is.
138
+ - Frontmatter carries what the filename no longer does: `title`, `created`
139
+ (real timestamp at creation time), and for task notes `task_id` /
140
+ `status` (`TODO`/`IN-PROGRESS`/`REVIEW`/`DONE`/`CANCELED`).
141
+ - Link with Obsidian wikilinks: `[[filename]]` or `[[filename|display
142
+ text]]` (no extension, exact filename minus `.md`).
143
+ - **Never hard-wrap note bodies. A newline exists if and only if a break is
144
+ intended in the output** — source line structure mirrors the output's block
145
+ structure. Write each paragraph and each list item as one single unbroken
146
+ line and let the editor soft-wrap it. Sentence and clause boundaries are
147
+ *not* logical breaks: the paragraph is the unit and sentences flow within
148
+ it. Line-based constructs — headings, list items, table rows, frontmatter,
149
+ code blocks — legitimately own their newlines and keep them.
150
+
151
+ The point is not merely that rewrapping by hand is tedious (though it is,
152
+ and stray newlines are diff and search-context noise). It is that this
153
+ makes the text **renderer-independent**: if a newline never appears where
154
+ no break is wanted, then "does a single newline render as `<br>` or as a
155
+ space?" never arises, and strict CommonMark, non-strict CommonMark,
156
+ Obsidian, pandoc and GitHub all produce the same result — the ambiguous
157
+ input case is simply gone. This is HTML's content model applied to plain
158
+ text: a newline is markup meaning "break here", not cosmetic formatting of
159
+ the source file.
160
+
161
+ It also keeps **line length a view decision rather than a content one**.
162
+ Hard-wrapping is the author asserting a measure, baking one viewport into
163
+ the text; unwrapped, the same bytes are correct at every width — Obsidian's
164
+ "Readable line length" on or off, a narrow split pane, a wide monitor,
165
+ mobile, print. Hard-wrapped prose fails both ways and is unfixable at read
166
+ time: at 80 columns it double-wraps raggedly in a narrow pane, and sits as a
167
+ fixed narrow ribbon in a wide one. Wanting a ~66-character measure is right
168
+ (it's a real typographic optimum, which is why that setting exists) — put it
169
+ in the renderer, which can adapt, not in the content, which can't.
170
+
171
+ **Corollary — never fake a break.** Don't rely on a soft newline inside a
172
+ paragraph or blockquote to produce separate lines, and don't reach for
173
+ trailing double-spaces or a trailing `\`. If several entries each need
174
+ their own line, they *are* separate items: use a list, which owns its line
175
+ breaks, nested inside the blockquote if the blockquote framing is wanted
176
+ (`> - entry`). Consecutive bare `>` lines render as one flowing paragraph
177
+ under strict CommonMark, and separating them with blank `>` lines makes
178
+ them full paragraphs with paragraph spacing — neither is what "three
179
+ labelled lines" means.
180
+
181
+ Accepted costs, both tooling-side rather than content-side: a line-based
182
+ diff treats a paragraph as atomic (use word-level diffing — `--word-diff`,
183
+ or a review UI that highlights intra-line changes), and very long lines are
184
+ awkward in tools that don't soft-wrap (`less` without `-S`, narrow terminal
185
+ diffs).
186
+ - Create notes via the `/synapse-note` command. **Task notes are a hard
187
+ exception, not a style preference:** create them only with
188
+ `/synapse-note --task` (or compile one from a `Ready` design note with
189
+ `/synapse-task-note`), and make every status transition, checklist edit,
190
+ or `## Notes` append only through the `synapse-task` skill's own
191
+ procedure — never a bare `vault_write`/`vault_patch` on a task note, not
192
+ even mid-session, not even for a quick update. Bypassing the command/skill
193
+ doesn't just risk drifting from the checklist-items-plus-`## Notes`
194
+ skeleton every other task note shares — the skill is also where the
195
+ guardrails live, most importantly that `status:` never reaches `DONE`
196
+ through it, capped at `REVIEW` instead, and a freeform write has no such
197
+ cap. If the command or skill genuinely doesn't fit what a task note needs,
198
+ that's a signal to fix the command/skill, not a license to route around
199
+ it once.
200
+
201
+ ## Searching notes
202
+
203
+ Prefer the `mcp__obsidian__` MCP tools over raw file grepping — they're
204
+ live against the running vault and don't require re-deriving paths:
205
+
206
+ - `mcp__obsidian__search_simple` — full-text search with relevance scoring
207
+ and match context, for "does a note about X already exist" checks.
208
+ - `mcp__obsidian__search_query` — JsonLogic queries over note metadata
209
+ (frontmatter fields, tags, links, backlinks, path globs) when you need a
210
+ structured filter rather than free text, e.g. finding all notes with a
211
+ given `task_id` or `status`.
212
+ - `mcp__obsidian__vault_list` / `vault_read` for direct navigation when you
213
+ already know roughly where something is.
214
+
215
+ # Git commits
216
+
217
+ Do not add a `Co-Authored-By: Claude ...` trailer to commit messages.
218
+ Enforced via `attribution.commit: ""` in `~/.claude/settings.json`, which
219
+ suppresses it mechanically — this note is a backstop in case that setting
220
+ ever gets reverted or overridden by a project-level settings file.
@@ -0,0 +1,24 @@
1
+ {
2
+ ".java": "java",
3
+ ".kt": "kotlin",
4
+ ".kts": "kotlin",
5
+ ".rs": "rust",
6
+ ".py": "python",
7
+ ".ts": "typescript",
8
+ ".tsx": "typescript",
9
+ ".js": "javascript",
10
+ ".mjs": "javascript",
11
+ ".cjs": "javascript",
12
+ ".go": "go",
13
+ ".rb": "ruby",
14
+ ".sh": "bash",
15
+ ".bash": "bash",
16
+ ".ml": "ocaml",
17
+ ".mli": "ocaml",
18
+ ".sql": "sql",
19
+ ".xml": "xml",
20
+ ".yml": "yaml",
21
+ ".yaml": "yaml",
22
+ ".json": "json",
23
+ ".zig": "zig"
24
+ }
@@ -0,0 +1,45 @@
1
+ # Copy this to ~/.claude/synapse-ignore-files.conf (setup.sh does this for you).
2
+ # Never overwritten once installed -- edit or extend any time.
3
+ #
4
+ # Extra path patterns dropped at enumeration by synapse build-lists, on top
5
+ # of the built-in exclusions (compiled objects, archives, media, model weights,
6
+ # lockfiles, minified bundles, source maps). This is the repo-specific half:
7
+ # whatever *this* machine's projects carry that is noise everywhere else too.
8
+ #
9
+ # One extended regular expression per line, matched against the repo-relative
10
+ # path. Blank lines and `#` comments are ignored. Lines are OR'd together, so
11
+ # each is independent -- there is no ordering and no negation.
12
+ #
13
+ # NOT gitignore syntax, deliberately. Gitignore has anchoring rules, `**`
14
+ # globs, and negation with precedence, and a half-implementation of it that
15
+ # silently disagrees with git on an edge case would be worse than an honest
16
+ # regex list. If you want git's semantics, use git's: a tracked file can be
17
+ # removed from the graph by untracking it, and `git ls-files` already respects
18
+ # every ignore rule for untracked ones.
19
+ #
20
+ # Superseded nothing: $SYNAPSE_EXTRA_EXCLUDE_RE still works and is OR'd with
21
+ # whatever is here, so an existing one-off invocation keeps behaving the same.
22
+ #
23
+ # WHAT BELONGS HERE, and what does not. Excluding a path removes it from the
24
+ # graph entirely -- it gets no owning node, so searching its name in the vault
25
+ # finds nothing, and editing it flags nothing stale. That is right for build
26
+ # output and vendored code, which nobody asks questions about. It is wrong for
27
+ # files that are merely *uninteresting to read*: a declarative artifact whose
28
+ # edits still change behaviour wants to stay covered, so that its owning node
29
+ # is flagged when it changes, even if no summary is ever written from it.
30
+ # Coverage and relevance are separate questions; this file answers only the
31
+ # first one.
32
+ #
33
+ # Examples, all commented out -- the default is to exclude nothing extra.
34
+
35
+ # Vendored dependencies checked into the tree
36
+ # (^|/)(vendor|third_party|external)/
37
+
38
+ # Generated sources that are tracked rather than gitignored
39
+ # (^|/)(gen|generated|__generated__)/
40
+
41
+ # Fixture corpora and recorded payloads -- large, numerous, never read
42
+ # (^|/)(fixtures|testdata|__snapshots__|cassettes)/
43
+
44
+ # IDE and tooling metadata that happens to be tracked
45
+ # (^|/)\.(idea|vscode|settings)/