@imunitic/synapse 0.4.2 → 0.4.3

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/Index.md.template CHANGED
@@ -3,20 +3,20 @@ title: "Index"
3
3
  ---
4
4
 
5
5
  # Index
6
- Map of the note folders and what each is for. This is the Obsidian second-brain vault, reached through the `synapse` CLI (see the Synapse repo's `CLAUDE.md`).
6
+ Map of the note folders and what each is for. This is the second-brain vault, reached through the `synapse` CLI (see the Synapse repo's `CLAUDE.md`).
7
7
 
8
8
  This index is agent-maintained: whenever an agent creates a new top-level folder (folder depth is capped at two levels, i.e. `folder/subfolder`), it must add a one-line section for it here in the same edit. Treat this file as out of date if a folder exists on disk with no matching section below -- fix the drift rather than working around it.
9
9
 
10
10
  This is a starter index, not a fixed schema. `research/`, `scratchpad/`, and `inbox/` below are yours to rename, restructure, or drop -- an agent reads this file as the authority on what your folders are and mean, not the other way around. `designs/`, `tasks/`, and `synapse/` aren't listed yet because nothing has created one on this vault -- `/synapse-design-note`, `/synapse-task-note`, and `/synapse-init` each add their own section here the first time they actually create that folder.
11
11
 
12
12
  ## research/
13
- Standalone research/reading notes -- general research about a topic, project-related or not: language features, library evaluations, design patterns, reference material. Filenames are plain, human-readable titles.
13
+ Standalone research/reading notes -- general research about a topic, project-related or not: language features, library evaluations, design patterns, reference material. Filenames are plain, human-readable titles. Created via `/synapse-note` (bare mode), which declares `schema: vault-note/v1`.
14
14
 
15
15
  ## scratchpad/
16
- Notes for iterating on an idea just to see whether it works -- high-churn and exploratory, not yet worth filing under a settled category. A note here has a high chance of either being moved into a proper folder once it solidifies, or being deleted wholesale once the idea is settled one way or the other. Filenames are plain, human-readable titles.
16
+ Notes for iterating on an idea just to see whether it works -- high-churn and exploratory, not yet worth filing under a settled category. A note here has a high chance of either being moved into a proper folder once it solidifies, or being deleted wholesale once the idea is settled one way or the other. Filenames are plain, human-readable titles. Created via `/synapse-note` (bare mode), which declares `schema: vault-note/v1`.
17
17
 
18
18
  ## inbox/
19
- A staging area for notes that need more input or reflection before they're settled -- most often the result of being asked to find something and write it down as a note, without yet knowing where it properly belongs, or that otherwise don't cleanly fit `research/` or `scratchpad/`. Filenames are plain, human-readable titles, same as every other folder -- no numbering scheme. Review periodically and move settled notes into a proper category; this folder is meant to stay small.
19
+ A staging area for notes that need more input or reflection before they're settled -- most often the result of being asked to find something and write it down as a note, without yet knowing where it properly belongs, or that otherwise don't cleanly fit `research/` or `scratchpad/`. Filenames are plain, human-readable titles, same as every other folder -- no numbering scheme. Created via `/synapse-note` (bare mode), which declares `schema: vault-note/v1`. Review periodically and move settled notes into a proper category; this folder is meant to stay small.
20
20
 
21
21
  A second kind of note also lives here: standalone ideas that surface mid-discussion (with you, or an agent reasoning out loud) and are worth keeping without yet warranting a full design note of their own -- a small, self-contained "worth doing someday" item rather than an open question tied to a bigger design. Filenames are plain, human-readable titles like any other inbox note. Loosely ticket-shaped (a one-line summary, why it came up, rough acceptance criteria) is a fine, optional format for these -- not a requirement, just a convenient shape for something meant to be picked up and acted on later.
22
22
 
@@ -31,10 +31,10 @@ Not every design discussion ends with something to build. See `Status: Reference
31
31
 
32
32
  ## Determining the project
33
33
 
34
- Every design note is tagged with the project it belongs to both in the title
35
- (`{PROJECT} {Topic}`) and as `project: {prefix}` in frontmatter (the same short prefix
36
- `/synapse-note --task` uses for task IDs) so a flat `designs/` folder still reads clearly, and
37
- both note kinds can be filtered together via `synapse vault-search`.
34
+ Every design note records the project it belongs to as `project: {prefix}` in frontmatter (the
35
+ same short prefix `/synapse-note --task` uses for task IDs) that field also decides the note's
36
+ `designs/{project}/` subfolder, so design notes and task notes can be filtered together via
37
+ `synapse vault-search`. The title itself is plain `{Topic}` — no project prefix, no id.
38
38
 
39
39
  Same resolution `/synapse-note` uses for a missing task ID (its "Resolving a missing task ID"),
40
40
  reading the same file:
@@ -56,7 +56,7 @@ reading the same file:
56
56
  check both fields (`synapse vault-search --fields frontmatter.task_id,frontmatter.note_id`, filter
57
57
  both for `{prefix}-\d+`, take the highest number found across both, add 1; start at 1 if none exist
58
58
  yet). This is frontmatter only — unlike a task note, a design note's title and filename stay
59
- exactly `{PROJECT} — {Topic}`, with no id prepended.
59
+ exactly `{Topic}`, with no id and no project prefix prepended.
60
60
 
61
61
  Never hardcode a specific project/prefix pair in this command's own instructions — the conf file is
62
62
  machine-local and deliberately outside the portable Synapse package, so projects from
@@ -156,11 +156,11 @@ judgment call, not the automatic result of an `## Approach` section existing. Be
156
156
  blocking question is fine to carry forward), but a note with a *blocking* open question is not
157
157
  `Ready`, no matter how developed the rest of it is.
158
158
 
159
- - **Both hold** → `Status: Ready`. Confirm: "Design note ready: `designs/{title}.md`.
159
+ - **Both hold** → `Status: Ready`. Confirm: "Design note ready: `designs/{project}/{title}.md`.
160
160
  Whenever you're ready to implement, generate the task with
161
161
  `/synapse-task-note \"{topic}\"` — no rush, nothing here expires."
162
162
  - **Genuinely nothing to build** → `Status: Reference`. Confirm: "Design note concluded as
163
- Reference: `designs/{title}.md`. No task note needed."
163
+ Reference: `designs/{project}/{title}.md`. No task note needed."
164
164
  - **Anything else** (the approach is still hedged, or an open question blocks it) → stays
165
165
  `Status: Discussing`. This is the default outcome, not a fallback to apologize for — most
166
166
  conversations end here, and that's fine; nothing here expires either.
@@ -178,7 +178,7 @@ so the `## Status` line is the only lifecycle marker that matters. It simply sta
178
178
  ```
179
179
  ---
180
180
  schema: vault-design-note/v1
181
- title: "{PROJECT} — {Topic}"
181
+ title: "{Topic}"
182
182
  project: {prefix}
183
183
  note_id: {id}
184
184
  created: "{now}"
@@ -186,7 +186,7 @@ updated: "{now}"
186
186
  tags: [{comma-separated configured tags, or empty}]
187
187
  ---
188
188
 
189
- # {PROJECT} — {Topic}
189
+ # {Topic}
190
190
 
191
191
  ## Status
192
192
  Discussing | Ready | Reference
@@ -197,16 +197,21 @@ Discussing | Ready | Reference
197
197
  ## Approach
198
198
  {Chosen approach}
199
199
 
200
- ### Alternatives considered (optional)
200
+ ### Alternatives considered
201
201
  - {Option}: why not
202
202
 
203
203
  ## Constraints
204
204
  {Hard constraints, non-negotiables}
205
205
 
206
- ## Open Questions (optional)
206
+ ## Open Questions
207
207
  - {Anything deferred or unresolved}
208
208
  ```
209
209
 
210
+ `### Alternatives considered` and `## Open Questions` are both optional per the schema — omit
211
+ either section entirely rather than leaving it as an empty heading, and never write it as `##
212
+ Open Questions (optional)`: the schema and `/synapse-status`'s own query both match this exact
213
+ heading text, and a suffixed one is invisible to both.
214
+
210
215
  Any string scalar that is all digits (e.g. a numeric-looking title) must be quoted: an
211
216
  unquoted all-digit value is parsed as an integer, which fails a `type: string` schema
212
217
  field. `title`, `project`, `note_id`, and both timestamps are quoted above.
@@ -220,8 +225,10 @@ edit history here worth tracking separately.
220
225
 
221
226
  ## Filename
222
227
 
223
- `designs/{PROJECT}{Topic}.md` — sanitize filesystem-illegal characters (`/ : * ? " < > |`). No
224
- slug, no numbering vault filenames are the title itself.
228
+ `designs/{project}/{Topic}.md` — grouped one level deeper into a per-project
229
+ subfolder named by the note's own `project:` frontmatter value (e.g. `designs/eon/`,
230
+ `designs/synapse/`), the same mapping `tasks/{project}/` uses. Sanitize filesystem-illegal
231
+ characters (`/ : * ? " < > |`). No slug, no numbering — vault filenames are the title itself.
225
232
 
226
233
  ---
227
234
 
@@ -242,12 +249,12 @@ Claude: [searches designs/ for existing matches — none found]
242
249
 
243
250
  User: Replacing the AggregatingMergeTree rollup resolution with direct-value storage.
244
251
 
245
- Claude: [creates designs/{PROJECT}Rollup direct storage.md, Status: Discussing]
252
+ Claude: [creates designs/{project}/Rollup direct storage.md, Status: Discussing]
246
253
 
247
254
  What's driving this over just tuning the current argMaxMerge query?
248
255
  ...
249
256
  User: The resolution cost alone dominates the query, tuning won't fix that. Mark it ready.
250
257
 
251
- Claude: Design note ready: designs/{PROJECT}Rollup direct storage.md.
258
+ Claude: Design note ready: designs/{project}/Rollup direct storage.md.
252
259
  Generate the task with /synapse-task-note "rollup direct storage" whenever you're ready.
253
260
  ```
@@ -100,7 +100,7 @@ yours and cannot be scripted because what counts as signal differs per codebase.
100
100
  **The work directory** defaults to `~/.cache/synapse/work/{repo}@{branch}/`, created on demand, and
101
101
  holds `manifest.tsv`, `all.txt`, `lists/`, the authored `b-NN.md` bodies and the coverage files. Override with `$SYNAPSE_WORK_DIR` if you need to. Two things never to do: point it
102
102
  at the repo (`synapse` runs from inside the repo, so its working files would land in the user's
103
- checkout) or at the vault (Obsidian would index a file list that runs to six figures of lines).
103
+ checkout) or at the vault (a file list that runs to six figures of lines has no business inside it).
104
104
  It is deliberately persistent rather than a temp dir, so a later run finds the previous manifest
105
105
  instead of re-deriving the clustering.
106
106
  - **Interpretation (only you can do this):** deciding what the nodes *are*, and writing their prose.
@@ -289,8 +289,8 @@ emit into tool calls than read into a window. Never hand-author those.
289
289
  **Then verify, before reporting success.** Three checks, all cheap:
290
290
  - `synapse query stale` must print nothing. (40s for a 125k-file namespace.)
291
291
  - Every `[[wikilink]]` in the namespace must resolve to a file that exists — extract them all and
292
- test `-f "$link.md"`. Nothing else catches a broken link, since Obsidian treats it as a link to
293
- a note not yet created.
292
+ test `-f "$link.md"`. Nothing else catches a broken link -- a wikilink to a note that does not
293
+ exist yet just fails silently.
294
294
  - Every node file must appear in `Index.md`. An unlisted node exists but is invisible to a reader.
295
295
 
296
296
  ```yaml
@@ -13,7 +13,7 @@ If `$ARGUMENTS` starts with `--search` → **search mode**: see "Search mode" be
13
13
  Otherwise, split `$ARGUMENTS` on `--task`:
14
14
 
15
15
  - If `--task` is present → **task mode**: scaffold the note as a tracked task, following the `synapse-task` skill's conventions. Task notes always live under `tasks/`.
16
- - Otherwise → **bare mode**: create an empty node (title + frontmatter only). Which category folder it lands in is resolved from `Index.md`, per "Choosing a category (bare mode only)" below — not a fixed set.
16
+ - Otherwise → **bare mode**: create an empty node (title + frontmatter only). Which category folder it lands in is resolved from `Index.md`, per "Choosing a category (bare mode only)" below — not a fixed set. The typical categories are `research/`, `scratchpad/`, or `inbox/` — any plain note that isn't a task or design note.
17
17
 
18
18
  The title is everything before `--task` (trimmed). Example:
19
19
 
@@ -97,8 +97,8 @@ this command when it does. What no longer happens is arriving here merely becaus
97
97
  ### 1. Size the job before doing any of it
98
98
 
99
99
  ```sh
100
- ~/.synapse query drift
101
- ~/.synapse query grounding
100
+ synapse query drift
101
+ synapse query grounding
102
102
  ```
103
103
 
104
104
  Report what it says, in the human's terms, **before** touching anything: how far the baseline is from
@@ -118,7 +118,7 @@ Two answers change the plan:
118
118
  ### 2. Mechanical phase — always, and cheap
119
119
 
120
120
  ```sh
121
- ~/.synapse build-lists --reenumerate
121
+ synapse build-lists --reenumerate
122
122
  ```
123
123
 
124
124
  `--reenumerate` matters here: without it an existing `all.txt` is reused, so a branch switch would be
@@ -136,7 +136,7 @@ move a lot and some may reach zero.
136
136
  Then rebuild the reverse index so the hook and the read path agree with the new enumeration:
137
137
 
138
138
  ```sh
139
- ~/.synapse build-index
139
+ synapse build-index
140
140
  ```
141
141
 
142
142
  ### 3. Triage each flagged node — reseat, patch, or re-orient
@@ -193,8 +193,8 @@ straight back stores a quote of a file as it looked at the old baseline, present
193
193
  current. So rebuild the directive from the pointer the writer recorded:
194
194
 
195
195
  ```sh
196
- ~/.synapse query field "{Node}" crux_path
197
- ~/.synapse query field "{Node}" crux_lines
196
+ synapse query field "{Node}" crux_path
197
+ synapse query field "{Node}" crux_lines
198
198
  ```
199
199
 
200
200
  and replace the fenced block with `<!-- crux: <crux_path> <crux_lines> -->` so it is cut from the
@@ -208,7 +208,7 @@ its directives are stripped from the body, so a recovered body contains none —
208
208
  the node's provenance is gone with no error. Recover the pointers per node:
209
209
 
210
210
  ```sh
211
- ~/.synapse query grounding "{Node}" --list # path<TAB>lines
211
+ synapse query grounding "{Node}" --list # path<TAB>lines
212
212
  ```
213
213
 
214
214
  and re-emit a `<!-- grounded_in: <path> <lines> -->` for each. Run `synapse query grounding` before
@@ -256,7 +256,7 @@ selection. Never pipe an unbounded `git diff <commit>..HEAD` into a context wind
256
256
  ### 4. Write each rebuilt node
257
257
 
258
258
  ```sh
259
- ~/.synapse write-node --title "{Node}" --summary "{one line}" \
259
+ synapse write-node --title "{Node}" --summary "{one line}" \
260
260
  --paths "$W/lists/NN.txt" --body "$W/body.md"
261
261
  ```
262
262
 
@@ -267,17 +267,17 @@ not merely stale, if the subsystem's shape differs on this line.
267
267
  ### 5. Rebuild the projections and verify
268
268
 
269
269
  ```sh
270
- ~/.synapse build-index
271
- ~/.synapse build-project-index
272
- ~/.synapse query drift # expect silence
273
- ~/.synapse query stale # expect silence
274
- ~/.synapse query grounding # expect silence: re-pointed, not dropped
275
- ~/.synapse query links --check # expect silence: no dangling targets
270
+ synapse build-index
271
+ synapse build-project-index
272
+ synapse query drift # expect silence
273
+ synapse query stale # expect silence
274
+ synapse query grounding # expect silence: re-pointed, not dropped
275
+ synapse query links --check # expect silence: no dangling targets
276
276
  ```
277
277
 
278
278
  `links --check` covers what used to be a manual instruction here: a broken `[[wikilink]]` is a valid
279
- link to a not-yet-existing note, so Obsidian renders it without complaint and nothing else in the
280
- system notices. It now reports `Node<TAB>relation -> Target (no such node)` per dangling edge.
279
+ link to a not-yet-existing note, silently, and nothing else in the system notices. It now reports
280
+ `Node<TAB>relation -> Target (no such node)` per dangling edge.
281
281
 
282
282
  One check is still yours, because no command performs it: **every node file appears in `Index.md`**. An
283
283
  unlisted node exists and is reachable by search, but is invisible to anyone reading the map.
@@ -54,7 +54,7 @@ Check whether `synapse/{repo}@{branch}/Index.md` exists.
54
54
  ### 2. Preview the wipe and get explicit confirmation
55
55
 
56
56
  ```sh
57
- ~/.synapse graph-wipe --dry-run
57
+ synapse graph-wipe --dry-run
58
58
  ```
59
59
 
60
60
  Report its output plainly: node count, and — the one thing this step exists to surface — how many
@@ -75,7 +75,7 @@ notes attached, which the human hasn't seen a number for yet.
75
75
  Once confirmed:
76
76
 
77
77
  ```sh
78
- ~/.synapse graph-wipe
78
+ synapse graph-wipe
79
79
  ```
80
80
 
81
81
  This deletes `synapse/{repo}@{branch}/` and, if any node had non-empty `## Notes`, first dumps that
@@ -26,7 +26,7 @@ Use those instead of reinventing a parallel view.
26
26
 
27
27
  ## Prerequisites
28
28
 
29
- - Requires a matching Obsidian design note (`designs/`) with `Status: Ready`.
29
+ - Requires a matching design note (`designs/`) with `Status: Ready`.
30
30
  - No matching note → "No Ready design note found for '{topic}'. Run
31
31
  `/synapse-design-note \"{topic}\"` first." Never generate a checklist from scratch.
32
32
  - Matching note but `Status: Discussing` → "Design note for '{topic}' is still in Discussing. Finish
@@ -99,8 +99,8 @@ printf '> Compiled task: [[%s]]\n\n' "{task note title}" | \
99
99
 
100
100
  A small annotation line, same idea as any other cross-reference you'd drop near a note's title. This
101
101
  is a one-time link; the design note's `Status` itself does not change and does not need to track the
102
- task's progress — Obsidian's own backlinks panel (or a plain `synapse vault-search-text` for the task
103
- note's title, on a disk-backed vault) already surfaces the live task note from the design note, and
102
+ task's progress — a plain `synapse vault-search-text` for the task
103
+ note's title already surfaces the live task note from the design note, and
104
104
  the task's `status:` frontmatter is the authoritative progress signal from here on.
105
105
 
106
106
  ## Confirm
@@ -122,7 +122,7 @@ command doesn't set or watch that field itself.
122
122
  ```
123
123
  User: /synapse-task-note "rollup direct storage"
124
124
 
125
- Claude: [reads designs/{PROJECT}Rollup direct storage.md, Status: Ready]
125
+ Claude: [reads designs/{project}/Rollup direct storage.md, Status: Ready]
126
126
  [no existing linked task note]
127
127
 
128
128
  Compiled checklist:
@@ -133,8 +133,8 @@ Claude: [reads designs/{PROJECT} — Rollup direct storage.md, Status: Ready]
133
133
 
134
134
  Project already known: {PROJECT} → {prefix}-005.
135
135
 
136
- Created: tasks/{PROJECT}/Rollup direct storage implementation.md (task_id: {prefix}-005)
137
- Linked back from designs/{PROJECT}Rollup direct storage.md.
136
+ Created: tasks/{project}/Rollup direct storage implementation.md (task_id: {prefix}-005)
137
+ Linked back from designs/{project}/Rollup direct storage.md.
138
138
 
139
139
  Status transitions happen automatically via the synapse-task skill once you start work.
140
140
  ```
@@ -29,15 +29,14 @@ of scope — foundational files, not taxonomy notes.
29
29
  ## Prerequisites
30
30
 
31
31
  Requires the `synapse` CLI on `PATH`, resolving a vault with a working `LinkGraph`
32
- (`synapse vault-backlinks`/`vault-links`/`vault-unresolved`/`vault-orphans`/`vault-deadends` — see
33
- `sb Obsidian CLI as ObsidianStore's transport` and `sb DiskStore's own index, Obsidian becomes
34
- optional`). Both real coding-vault backends have one now — `obsidian` reaching the CLI when
35
- reachable and falling back to `disk`'s own implementation otherwise, `disk` always local. If
36
- `SYNAPSE_VAULT_INTEGRATIONS` ever resolves to a backend with no `LinkGraph` at all, these commands exit 1
37
- saying so; stop and report that rather than falling back to anything else.
32
+ (`synapse vault-backlinks`/`vault-links`/`vault-unresolved`/`vault-orphans`/`vault-deadends`).
33
+ `DiskStore`, the one real backend, always has one -- case-insensitive wikilink resolution over the
34
+ vault folder directly, no external dependency. If `SYNAPSE_VAULT_INTEGRATIONS` ever resolves to a
35
+ backend with no `LinkGraph` at all, these commands exit 1 saying so; stop and report that rather
36
+ than falling back to anything else.
38
37
 
39
38
  This command reaches the vault store only through the `synapse` CLI's `vault-*` subcommands, the
40
- same door every other skill uses — no MCP tool, no direct `ObsidianStore` call. `vault-links`/
39
+ same door every other skill uses — no MCP tool, no direct store call. `vault-links`/
41
40
  `vault-backlinks` each answer for one note at a time, so Step 1's inventory sweep runs one pair per
42
41
  note in scope: `2N` process spawns for an `N`-note vault. Acceptable for an on-demand, rare command.
43
42
  Step 3's broken-link history check is a plain `git log` call (via Bash, not a compiled tool)
@@ -87,10 +86,9 @@ guessing:
87
86
  - Missing `created` → `stat.ctime`, formatted `YYYY-MM-DD HH:MM` to match every other note's
88
87
  convention.
89
88
 
90
- Apply via read-modify-write on the whole file (`synapse vault-read` → edit the one frontmatter line
91
- in the returned content `synapse vault-write` the whole file back) never `vault-patch` with
92
- `--frontmatter`, which re-serializes the entire YAML block and silently reformats unrelated fields,
93
- the same hazard `synapse-vault`/`synapse-task` already document.
89
+ Apply with `synapse frontmatter set <path> <key> <value>` (or `vault-patch --frontmatter`, which
90
+ delegates to the same byte-preserving mechanism) either writes exactly this one field and leaves
91
+ every other line untouched, the way `synapse-vault`/`synapse-task` already document.
94
92
 
95
93
  ## Step 3: Note-health findings (reported, not fixed)
96
94
 
@@ -200,4 +198,4 @@ Print a short summary directly in the response, not left only in tool-call outpu
200
198
  under a `/loop` the user sets up themselves.
201
199
  - Every step above goes through the `synapse` CLI's `vault-*` subcommands, or (Step 3's broken-link
202
200
  history check only) a plain `git log` via Bash against the vault's own local repo; this command
203
- never calls an `mcp__obsidian__*` tool or `ObsidianStore` directly.
201
+ never calls a store implementation directly.
@@ -38,10 +38,10 @@ There is no flag syntax here — read intent from how the user asks:
38
38
 
39
39
  ## Determining the project
40
40
 
41
- Every design note is tagged with the project it belongs to both in the title
42
- (`{PROJECT} {Topic}`) and as `project: {prefix}` in frontmatter (the same short prefix the
43
- synapse-note skill's task mode uses for task IDs) so a flat `designs/` folder still reads clearly,
44
- and both note kinds can be filtered together via `synapse vault-search`.
41
+ Every design note records the project it belongs to as `project: {prefix}` in frontmatter (the
42
+ same short prefix the synapse-note skill's task mode uses for task IDs) that field also decides
43
+ the note's `designs/{project}/` subfolder, so design notes and task notes can be filtered together
44
+ via `synapse vault-search`. The title itself is plain `{Topic}` — no project prefix, no id.
45
45
 
46
46
  Same resolution the synapse-note skill uses for a missing task ID (its "Resolving a missing task
47
47
  ID"), reading the same file:
@@ -58,6 +58,12 @@ ID"), reading the same file:
58
58
  4. If nothing confidently matches, ask the user for a short project tag — plain free-text, not a
59
59
  multiple-choice list, never hinting at any other project's tag as an example — then append the
60
60
  resolved pair to the conf file.
61
+ 5. Once the prefix is known, mint the note's `note_id` the same shared-counter way the synapse-note
62
+ skill's "Resolving a missing task ID" step 6 does: `task_id` and `note_id` share one counter per
63
+ prefix, so check both fields (`synapse vault-search --fields frontmatter.task_id,frontmatter.note_id`,
64
+ filter both for `{prefix}-\d+`, take the highest number found across both, add 1; start at 1 if none
65
+ exist yet). This is frontmatter only — unlike a task note, a design note's title and filename stay
66
+ exactly `{Topic}`, with no id and no project prefix prepended.
61
67
 
62
68
  Never hardcode a specific project/prefix pair in this skill's own instructions — the conf file is
63
69
  machine-local and deliberately outside the portable Synapse package, so projects from
@@ -157,11 +163,11 @@ judgment call, not the automatic result of an `## Approach` section existing. Be
157
163
  blocking question is fine to carry forward), but a note with a *blocking* open question is not
158
164
  `Ready`, no matter how developed the rest of it is.
159
165
 
160
- - **Both hold** → `Status: Ready`. Confirm: "Design note ready: `designs/{title}.md`.
166
+ - **Both hold** → `Status: Ready`. Confirm: "Design note ready: `designs/{project}/{title}.md`.
161
167
  Whenever you're ready to implement, ask me to compile it into a task note — no rush, nothing
162
168
  here expires."
163
169
  - **Genuinely nothing to build** → `Status: Reference`. Confirm: "Design note concluded as
164
- Reference: `designs/{title}.md`. No task note needed."
170
+ Reference: `designs/{project}/{title}.md`. No task note needed."
165
171
  - **Anything else** (the approach is still hedged, or an open question blocks it) → stays
166
172
  `Status: Discussing`. This is the default outcome, not a fallback to apologize for — most
167
173
  conversations end here, and that's fine; nothing here expires either.
@@ -179,7 +185,7 @@ so the `## Status` line is the only lifecycle marker that matters. It simply sta
179
185
  ```
180
186
  ---
181
187
  schema: vault-design-note/v1
182
- title: "{PROJECT} — {Topic}"
188
+ title: "{Topic}"
183
189
  project: {prefix}
184
190
  note_id: {id}
185
191
  created: "{now}"
@@ -187,7 +193,7 @@ updated: "{now}"
187
193
  tags: [{comma-separated configured tags, or empty}]
188
194
  ---
189
195
 
190
- # {PROJECT} — {Topic}
196
+ # {Topic}
191
197
 
192
198
  ## Status
193
199
  Discussing | Ready | Reference
@@ -198,16 +204,21 @@ Discussing | Ready | Reference
198
204
  ## Approach
199
205
  {Chosen approach}
200
206
 
201
- ### Alternatives considered (optional)
207
+ ### Alternatives considered
202
208
  - {Option}: why not
203
209
 
204
210
  ## Constraints
205
211
  {Hard constraints, non-negotiables}
206
212
 
207
- ## Open Questions (optional)
213
+ ## Open Questions
208
214
  - {Anything deferred or unresolved}
209
215
  ```
210
216
 
217
+ `### Alternatives considered` and `## Open Questions` are both optional per the schema — omit
218
+ either section entirely rather than leaving it as an empty heading, and never write it as `##
219
+ Open Questions (optional)`: the schema and `/synapse-status`'s own query both match this exact
220
+ heading text, and a suffixed one is invisible to both.
221
+
211
222
  Resolve tags through the `synapse-vault` skill's configured vocabulary procedure. Fetch machine
212
223
  local time once (`date '+%Y-%m-%d %H:%M:%S %Z'`) — never infer it — and use the exact same value
213
224
  for `created` and `updated`.
@@ -217,8 +228,10 @@ edit history here worth tracking separately.
217
228
 
218
229
  ## Filename
219
230
 
220
- `designs/{PROJECT}{Topic}.md` — sanitize filesystem-illegal characters (`/ : * ? " < > |`). No
221
- slug, no numbering Obsidian filenames are the title itself.
231
+ `designs/{project}/{Topic}.md` — grouped one level deeper into a per-project
232
+ subfolder named by the note's own `project:` frontmatter value (e.g. `designs/eon/`,
233
+ `designs/synapse/`), the same mapping tasks use. Sanitize filesystem-illegal
234
+ characters (`/ : * ? " < > |`). No slug, no numbering — vault filenames are the title itself.
222
235
 
223
236
  ---
224
237
 
@@ -239,12 +252,12 @@ Codex: [searches designs/ for existing matches — none found]
239
252
 
240
253
  User: Replacing the AggregatingMergeTree rollup resolution with direct-value storage.
241
254
 
242
- Codex: [creates designs/{PROJECT}Rollup direct storage.md, Status: Discussing]
255
+ Codex: [creates designs/{project}/Rollup direct storage.md, Status: Discussing]
243
256
 
244
257
  What's driving this over just tuning the current argMaxMerge query?
245
258
  ...
246
259
  User: The resolution cost alone dominates the query, tuning won't fix that. Mark it ready.
247
260
 
248
- Codex: Design note ready: designs/{PROJECT}Rollup direct storage.md.
261
+ Codex: Design note ready: designs/{project}/Rollup direct storage.md.
249
262
  Ask me to compile the task note whenever you're ready to implement.
250
263
  ```
@@ -100,7 +100,7 @@ yours and cannot be scripted because what counts as signal differs per codebase.
100
100
  **The work directory** defaults to `~/.cache/synapse/work/{repo}@{branch}/`, created on demand, and
101
101
  holds `manifest.tsv`, `all.txt`, `lists/`, the authored `b-NN.md` bodies and the coverage files. Override with `$SYNAPSE_WORK_DIR` if you need to. Two things never to do: point it
102
102
  at the repo (`synapse` runs from inside the repo, so its working files would land in the user's
103
- checkout) or at the vault (Obsidian would index a file list that runs to six figures of lines).
103
+ checkout) or at the vault (a file list that runs to six figures of lines has no business inside it).
104
104
  It is deliberately persistent rather than a temp dir, so a later run finds the previous manifest
105
105
  instead of re-deriving the clustering.
106
106
  - **Interpretation (only you can do this):** deciding what the nodes *are*, and writing their prose.
@@ -289,8 +289,8 @@ emit into tool calls than read into a window. Never hand-author those.
289
289
  **Then verify, before reporting success.** Three checks, all cheap:
290
290
  - `synapse query stale` must print nothing. (40s for a 125k-file namespace.)
291
291
  - Every `[[wikilink]]` in the namespace must resolve to a file that exists — extract them all and
292
- test `-f "$link.md"`. Nothing else catches a broken link, since Obsidian treats it as a link to
293
- a note not yet created.
292
+ test `-f "$link.md"`. Nothing else catches a broken link -- a wikilink to a note that does not
293
+ exist yet just fails silently.
294
294
  - Every node file must appear in `Index.md`. An unlisted node exists but is invisible to a reader.
295
295
 
296
296
  ```yaml
@@ -27,7 +27,8 @@ below:
27
27
  - **Asking to create any other note** ("make a note about X", "save this as a note") → **bare
28
28
  mode**: create an empty node (title + frontmatter only). Which category folder it lands in is
29
29
  resolved from the vault's index note, per "Choosing a category (bare mode only)" below — not a
30
- fixed set.
30
+ fixed set. The typical categories are `research/`, `scratchpad/`, or `inbox/` — any plain note
31
+ that isn't a task or design note.
31
32
 
32
33
  The title is whatever the user is naming or describing, minus any mode-signaling phrasing. Example:
33
34
 
@@ -138,7 +139,7 @@ offered.
138
139
 
139
140
  Resolve this to a `category` matching the folder name exactly as
140
141
  the index note currently spells it, before moving on to the creation steps
141
- below. No project-slug question is needed here — Obsidian filenames are
142
+ below. No project-slug question is needed here — vault filenames are
142
143
  the title itself, not a slug-prefixed timestamp, so there's no separate
143
144
  namespacing concern to resolve. The note always lands flat at
144
145
  `{category}/{filename}.md` — never inferred into a subfolder such as a
@@ -97,8 +97,8 @@ this skill when it does. What no longer happens is arriving here merely because
97
97
  ### 1. Size the job before doing any of it
98
98
 
99
99
  ```sh
100
- ~/.synapse query drift
101
- ~/.synapse query grounding
100
+ synapse query drift
101
+ synapse query grounding
102
102
  ```
103
103
 
104
104
  Report what it says, in the human's terms, **before** touching anything: how far the baseline is from
@@ -118,7 +118,7 @@ Two answers change the plan:
118
118
  ### 2. Mechanical phase — always, and cheap
119
119
 
120
120
  ```sh
121
- ~/.synapse build-lists --reenumerate
121
+ synapse build-lists --reenumerate
122
122
  ```
123
123
 
124
124
  `--reenumerate` matters here: without it an existing `all.txt` is reused, so a branch switch would be
@@ -136,7 +136,7 @@ move a lot and some may reach zero.
136
136
  Then rebuild the reverse index so the hook and the read path agree with the new enumeration:
137
137
 
138
138
  ```sh
139
- ~/.synapse build-index
139
+ synapse build-index
140
140
  ```
141
141
 
142
142
  ### 3. Triage each flagged node — reseat, patch, or re-orient
@@ -193,8 +193,8 @@ straight back stores a quote of a file as it looked at the old baseline, present
193
193
  current. So rebuild the directive from the pointer the writer recorded:
194
194
 
195
195
  ```sh
196
- ~/.synapse query field "{Node}" crux_path
197
- ~/.synapse query field "{Node}" crux_lines
196
+ synapse query field "{Node}" crux_path
197
+ synapse query field "{Node}" crux_lines
198
198
  ```
199
199
 
200
200
  and replace the fenced block with `<!-- crux: <crux_path> <crux_lines> -->` so it is cut from the
@@ -208,7 +208,7 @@ its directives are stripped from the body, so a recovered body contains none —
208
208
  the node's provenance is gone with no error. Recover the pointers per node:
209
209
 
210
210
  ```sh
211
- ~/.synapse query grounding "{Node}" --list # path<TAB>lines
211
+ synapse query grounding "{Node}" --list # path<TAB>lines
212
212
  ```
213
213
 
214
214
  and re-emit a `<!-- grounded_in: <path> <lines> -->` for each. Run `synapse query grounding` before
@@ -257,7 +257,7 @@ selection. Never pipe an unbounded `git diff <commit>..HEAD` into a context wind
257
257
  ### 4. Write each rebuilt node
258
258
 
259
259
  ```sh
260
- ~/.synapse write-node --title "{Node}" --summary "{one line}" \
260
+ synapse write-node --title "{Node}" --summary "{one line}" \
261
261
  --paths "$W/lists/NN.txt" --body "$W/body.md"
262
262
  ```
263
263
 
@@ -268,16 +268,16 @@ not merely stale, if the subsystem's shape differs on this line.
268
268
  ### 5. Rebuild the projections and verify
269
269
 
270
270
  ```sh
271
- ~/.synapse build-index
272
- ~/.synapse build-project-index
273
- ~/.synapse query drift # expect silence
274
- ~/.synapse query stale # expect silence
275
- ~/.synapse query grounding # expect silence: re-pointed, not dropped
276
- ~/.synapse query links --check # expect silence: no dangling targets
271
+ synapse build-index
272
+ synapse build-project-index
273
+ synapse query drift # expect silence
274
+ synapse query stale # expect silence
275
+ synapse query grounding # expect silence: re-pointed, not dropped
276
+ synapse query links --check # expect silence: no dangling targets
277
277
  ```
278
278
 
279
279
  `links --check` covers what used to be a manual instruction here: a broken `[[wikilink]]` is a valid
280
- link to a not-yet-existing note, so Obsidian renders it without complaint and nothing else in the
280
+ link to a not-yet-existing note, silently, and nothing else in the
281
281
  system notices. It now reports `Node<TAB>relation -> Target (no such node)` per dangling edge.
282
282
 
283
283
  One check is still yours, because no command performs it: **every node file appears in `Index.md`**. An
@@ -51,7 +51,7 @@ Check whether `synapse/{repo}@{branch}/Index.md` exists.
51
51
  ### 2. Preview the wipe and get explicit confirmation
52
52
 
53
53
  ```sh
54
- ~/.synapse graph-wipe --dry-run
54
+ synapse graph-wipe --dry-run
55
55
  ```
56
56
 
57
57
  Report its output plainly: node count, and — the one thing this step exists to surface — how many
@@ -72,7 +72,7 @@ attached, which the human hasn't seen a number for yet.
72
72
  Once confirmed:
73
73
 
74
74
  ```sh
75
- ~/.synapse graph-wipe
75
+ synapse graph-wipe
76
76
  ```
77
77
 
78
78
  This deletes `synapse/{repo}@{branch}/` and, if any node had non-empty `## Notes`, first dumps that
@@ -27,7 +27,7 @@ reinventing a parallel view — this skill has no separate list/continue mode of
27
27
 
28
28
  ## Prerequisites
29
29
 
30
- - Requires a matching Obsidian design note (`designs/`) with `Status: Ready`.
30
+ - Requires a matching design note (`designs/`) with `Status: Ready`.
31
31
  - No matching note → "No Ready design note found for '{topic}'. Start or continue that design note
32
32
  first." Never generate a checklist from scratch.
33
33
  - Matching note but `Status: Discussing` → "Design note for '{topic}' is still in Discussing. Finish
@@ -123,7 +123,7 @@ skill doesn't set or watch that field itself.
123
123
  ```
124
124
  User: let's compile a task note for "rollup direct storage"
125
125
 
126
- Codex: [reads designs/{PROJECT}Rollup direct storage.md, Status: Ready]
126
+ Codex: [reads designs/{project}/Rollup direct storage.md, Status: Ready]
127
127
  [no existing linked task note]
128
128
 
129
129
  Compiled checklist:
@@ -134,8 +134,8 @@ Codex: [reads designs/{PROJECT} — Rollup direct storage.md, Status: Ready]
134
134
 
135
135
  Project already known: {PROJECT} → {prefix}-005.
136
136
 
137
- Created: tasks/{PROJECT}/Rollup direct storage implementation.md (task_id: {prefix}-005)
138
- Linked back from designs/{PROJECT}Rollup direct storage.md.
137
+ Created: tasks/{project}/Rollup direct storage implementation.md (task_id: {prefix}-005)
138
+ Linked back from designs/{project}/Rollup direct storage.md.
139
139
 
140
140
  Status transitions happen automatically via the synapse-task skill once you start work.
141
141
  ```
@@ -30,14 +30,14 @@ there is no argument to parse, every run produces the same one-pass sweep.
30
30
  ## Prerequisites
31
31
 
32
32
  Requires the `synapse` CLI on `PATH`, resolving a vault with a working link graph
33
- (`synapse vault-backlinks`/`vault-links`/`vault-unresolved`/`vault-orphans`/`vault-deadends`). Both
34
- real coding-vault backends have one `disk` (the default) always local, `obsidian` reaching a
35
- running app when reachable and falling back to `disk`'s own implementation otherwise. If
36
- `SYNAPSE_VAULT_INTEGRATIONS` ever resolves to a backend with no link graph at all, these commands exit 1
37
- saying so; stop and report that rather than falling back to anything else.
33
+ (`synapse vault-backlinks`/`vault-links`/`vault-unresolved`/`vault-orphans`/`vault-deadends`).
34
+ `disk`, the one real backend, always has one -- case-insensitive wikilink resolution over the vault
35
+ folder directly, no external dependency. If `SYNAPSE_VAULT_INTEGRATIONS` ever resolves to a backend
36
+ with no link graph at all, these commands exit 1 saying so; stop and report that rather than
37
+ falling back to anything else.
38
38
 
39
39
  This skill reaches the vault store only through the `synapse` CLI's `vault-*` subcommands, the same
40
- door every other skill uses — no MCP tool, no direct `ObsidianStore` call. `vault-links`/
40
+ door every other skill uses — no MCP tool, no direct store call. `vault-links`/
41
41
  `vault-backlinks` each answer for one note at a time, so Step 1's inventory sweep runs one pair per
42
42
  note in scope: `2N` process spawns for an `N`-note vault. Acceptable for an on-demand, rare command.
43
43
  Step 3's broken-link history check is a plain `git log` call (via the shell, not a compiled tool)
@@ -87,10 +87,9 @@ derivable without guessing:
87
87
  - Missing `created` → `stat.ctime`, formatted `YYYY-MM-DD HH:MM` to match every other note's
88
88
  convention.
89
89
 
90
- Apply via read-modify-write on the whole file (`synapse vault-read` → edit the one frontmatter line
91
- in the returned content `synapse vault-write` the whole file back) never `vault-patch` with
92
- `--frontmatter`, which re-serializes the entire YAML block and silently reformats unrelated fields,
93
- the same hazard the vault and task-status skills already document.
90
+ Apply with `synapse frontmatter set <path> <key> <value>` (or `vault-patch --frontmatter`, which
91
+ delegates to the same byte-preserving mechanism) either writes exactly this one field and leaves
92
+ every other line untouched, the way the vault and task-status skills already document.
94
93
 
95
94
  ## Step 3: Note-health findings (reported, not fixed)
96
95
 
@@ -199,4 +198,4 @@ Print a short summary directly in the response, not left only in tool-call outpu
199
198
  asked, or on a recurring cadence the user sets up themselves.
200
199
  - Every step above goes through the `synapse` CLI's `vault-*` subcommands, or (Step 3's broken-link
201
200
  history check only) a plain `git log` via the shell against the vault's own local repo; this skill
202
- never calls an `mcp__obsidian__*` tool or `ObsidianStore` directly.
201
+ never calls a store implementation directly.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@imunitic/synapse",
3
- "version": "0.4.2",
4
- "description": "Memory for Claude Code, Codex CLI, and OpenCode: a durable Obsidian vault plus a per-repo code graph.",
3
+ "version": "0.4.3",
4
+ "description": "Memory for Claude Code, Codex CLI, and OpenCode: a durable markdown vault plus a per-repo code graph.",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "https://github.com/imunitic/synapse"
@@ -23,9 +23,9 @@
23
23
  "*.conf.template"
24
24
  ],
25
25
  "optionalDependencies": {
26
- "@imunitic/synapse-darwin-arm64": "0.4.2",
27
- "@imunitic/synapse-linux-x64": "0.4.2",
28
- "@imunitic/synapse-linux-arm64": "0.4.2"
26
+ "@imunitic/synapse-darwin-arm64": "0.4.3",
27
+ "@imunitic/synapse-linux-x64": "0.4.3",
28
+ "@imunitic/synapse-linux-arm64": "0.4.3"
29
29
  },
30
30
  "license": "SEE LICENSE IN LICENSE"
31
31
  }
@@ -21,7 +21,7 @@ needs one.
21
21
 
22
22
  ## Procedure
23
23
 
24
- 1. **Verify the whole project once, with the script.** Run `~/.synapse query stale` from
24
+ 1. **Verify the whole project once, with the script.** Run `synapse query stale` from
25
25
  inside the repo. It prints one `{node title}\t{reason}` line per stale node and nothing at all
26
26
  when everything is current, so its output is the complete stale set for the project.
27
27
 
@@ -50,7 +50,7 @@ needs one.
50
50
  So skip the frontmatter entirely:
51
51
 
52
52
  ```sh
53
- ~/.synapse query body "{Node title}"
53
+ synapse query body "{Node title}"
54
54
  ```
55
55
 
56
56
  That prints only what is between the generated fences — so it excludes `## Notes` as well as the
@@ -81,11 +81,11 @@ needs one.
81
81
  - **Get the node's path list into a file, never into context:**
82
82
 
83
83
  ```sh
84
- ~/.synapse query sources "{Node title}" > "$W/paths.txt"
84
+ synapse query sources "{Node title}" > "$W/paths.txt"
85
85
  ```
86
86
 
87
87
  If `$W/manifest.tsv` exists (or the namespace has `_manifest.tsv`), prefer re-running
88
- `~/.synapse build-lists` instead and use the regenerated `lists/NN.txt`: it
88
+ `synapse build-lists` instead and use the regenerated `lists/NN.txt`: it
89
89
  re-derives every list from the clustering patterns, so files *added* since the last build are
90
90
  picked up automatically rather than sitting in `_unassigned`. `synapse query sources` can only
91
91
  return what the node already claims.
@@ -101,7 +101,7 @@ needs one.
101
101
  and discards findings the diff has nothing to say about. Project the diff as carefully as
102
102
  `sources`: names first, `--stat` to size it, hunks only for the selection.
103
103
  - Fall back to reading the files when patching cannot be justified — a large fraction changed, the
104
- `crux` file is gone, or the baseline is unusable. Then try `~/.synapse tags {path}`
104
+ `crux` file is gone, or the baseline is unusable. Then try `synapse tags {path}`
105
105
  first (exit 0 use the tags, exit 1 fall back to reading the file, exit 2 run the discovery
106
106
  procedure `/synapse-init` documents, then retry), and read the load-bearing files in full — the
107
107
  tags signal informs regrouping, it never substitutes for reading a file before rewriting its prose.
@@ -127,22 +127,22 @@ needs one.
127
127
  - **Write it back with the script:**
128
128
 
129
129
  ```sh
130
- ~/.synapse write-node --title "{Node title}" --summary "{one line}" \
130
+ synapse write-node --title "{Node title}" --summary "{one line}" \
131
131
  --paths "$W/paths.txt" --body "$W/body.md"
132
132
  ```
133
133
 
134
134
  It replaces only the generated region and re-emits everything after the closing fence verbatim,
135
135
  which is what makes the `## Notes` guarantee enforceable rather than a promise.
136
136
  - **Never hand-write the frontmatter**, with `vault_patch` at `targetType: frontmatter` or
137
- otherwise. Two reasons, both load-bearing: that patch re-serialises the whole YAML block and
138
- YAML-coerces values (an all-digit `hash` becomes `1.1111111111111112e+39`), and
139
- enumerating fields by hand is how `summary` and `commit` get silently dropped — which then breaks
140
- the next `synapse build-project-index` run, far from the cause.
137
+ otherwise. `--frontmatter` only ever writes a single scalar value, so it can't represent
138
+ `sources:`/`grounded_in:` at all, and enumerating the rest of a node's fields by hand one call
139
+ at a time is how `summary` and `commit` get silently dropped — which then breaks the next
140
+ `synapse build-project-index` run, far from the cause.
141
141
  - **`## Notes` is human-authored only.** Never write into it — not at regeneration, not to record
142
142
  what you just did. (Task notes in `tasks/` are a different artifact: the `synapse-task` skill *does*
143
143
  append there. Do not carry that habit into a Synapse node.)
144
144
  - If the node's `summary` or title changed, rebuild the index so the map matches:
145
- `~/.synapse build-project-index`.
145
+ `synapse build-project-index`.
146
146
  - **Say out loud that a regeneration happened** — e.g. "Node '{title}' was stale, regenerated
147
147
  before use." This has real latency and token cost, unlike Tier 1/2's detection; it must never
148
148
  be absorbed silently into the read.
@@ -150,13 +150,13 @@ needs one.
150
150
  - Read the bucket with a shell command, not into context — the index runs to tens of megabytes:
151
151
 
152
152
  ```sh
153
- ~/.synapse index unassigned
153
+ synapse index unassigned
154
154
  ```
155
155
 
156
156
  Empty → nothing to do, skip silently (an empty sweep isn't worth announcing).
157
157
  - Otherwise read `synapse/{project}/Index.md` for the current node list (titles + summaries).
158
158
  - Tag the whole bucket in **one** call — write the paths to a list and run
159
- `~/.synapse tags --paths {list}`, whose output is attributable (an unindented
159
+ `synapse tags --paths {list}`, whose output is attributable (an unindented
160
160
  line is a path, the tab-indented lines under it are its tags). A per-file loop costs ~33× more
161
161
  for the same answer. Fall back to a full read for ambiguous cases, then classify against that
162
162
  node list. **The judgment is which cluster a path belongs to; the bookkeeping is not yours to
@@ -167,7 +167,7 @@ needs one.
167
167
  immediately. If there is no manifest, add the path to that node's list file instead.
168
168
  - **Fits nothing** → leave it unassigned. A genuinely new subsystem wants its own manifest line
169
169
  and its own node, which is `/synapse-init` work, not a sweep.
170
- - Then rebuild the projection with `~/.synapse build-index`. **Never hand-edit
170
+ - Then rebuild the projection with `synapse build-index`. **Never hand-edit
171
171
  `_index.bin`** — it is derived, binary, and tens of megabytes; there is nothing to
172
172
  hand-edit.
173
173
  - **Announce every outcome**, same transparency rule as regeneration: which file, and which
@@ -126,8 +126,8 @@ candidate list, not something to copy verbatim.
126
126
  Never hard-wrap. Write each paragraph as one single unbroken line and let the editor soft-wrap
127
127
  it — a newline exists only where a real break is intended (between paragraphs, list items,
128
128
  headings). This is a vault-wide rule, not specific to this node, and it is not optional: a
129
- hard-wrapped paragraph renders as a ragged stack of short lines in Obsidian instead of flowing
130
- text.
129
+ hard-wrapped paragraph renders as a ragged stack of short lines in a soft-wrapping viewer instead of
130
+ flowing text.
131
131
  ```
132
132
 
133
133
  Same model as the orchestrating session, no override — matches the constraint that "a
@@ -42,15 +42,15 @@ to orient someone already inside. A node without one is an error, not a default.
42
42
 
43
43
  - **Filename/title:** short, senior-engineer-style description of the concept (e.g. "World —
44
44
  entity/component/resource core"). Filesystem-illegal characters (`/ : * ? " < > |`) are
45
- sanitized — but **reword the title instead of relying on that**, because Obsidian resolves a
46
- wikilink by *filename*, so `[[World — entity/component/resource core]]` silently resolves to
45
+ sanitized — but **reword the title instead of relying on that**, because a wikilink resolves
46
+ by *filename*, so `[[World — entity/component/resource core]]` silently resolves to
47
47
  nothing once the file becomes `...entity_component_resource core.md`. A broken wikilink is a
48
48
  valid link to a not-yet-existing note, so it fails quietly. The writer warns when a title needs
49
49
  sanitizing; treat that warning as "rename this node". Same trap when you *retitle* a node
50
50
  mid-build: inbound links already written keep pointing at the old name.
51
51
  - **`sources`:** **every** file the node covers — repo-relative path plus that file's
52
52
  `git hash-object <path>` output, run from the repo root at the moment of writing. Exhaustive,
53
- not a sample: this is a **machine** field, and it is what makes Obsidian's search able to reach
53
+ not a sample: this is a **machine** field, and it is what makes a path-based lookup able to reach
54
54
  a node from any file it covers (searching a class name that appears in no node's prose still
55
55
  finds its node via this list). Do **not** trim it to a handful of "representative" files —
56
56
  doing so silently destroys that lookup, leaves the node unable to answer "which files am I
@@ -65,17 +65,15 @@ to orient someone already inside. A node without one is an error, not a default.
65
65
  someone through this subsystem), `crux` (the few lines that carry the actual logic — **authored
66
66
  as line numbers, stored as text**: you point, the writer slices, so composing is impossible at
67
67
  authoring time and nothing decays afterwards the way a stored line number would), `links` (typed
68
- Obsidian wikilinks to other nodes in this same namespace: `depends_on`, `part_of`, `uses`, or
68
+ wikilinks to other nodes in this same namespace: `depends_on`, `part_of`, `uses`, or
69
69
  another type that fits better if one doesn't — for `depends_on`/`uses` specifically, `/synapse-init`
70
70
  computes candidates before any node exists via `synapse link-graph`; read that node's rows from
71
71
  `links.tsv` rather than guessing which siblings it relates to, `part_of` stays a judgement call
72
72
  with nothing mechanical behind it), a `## Sources` section, and an empty `## Notes` section.
73
73
  - **Break a "does N things" enumeration into real bullets, not inline `(1)/(2)/(3)`.** A sentence
74
74
  enumerating three or more parallel sub-points reads as a wall of text once each item carries its
75
- own clause or parenthetical -- the node is read by a human skimming it in Obsidian as much as by
76
- an agent (see the design note this format came from: hosting the graph as vault-readable markdown
77
- was chosen specifically so it stays "just as readable by a human directly in Obsidian as it is by
78
- Claude"), and a dense inline run-on defeats that. Use a markdown bullet list under the sentence
75
+ own clause or parenthetical -- the node is read by a human skimming it as much as by an agent,
76
+ and a dense inline run-on defeats that. Use a markdown bullet list under the sentence
79
77
  introducing them instead. This is narrow, not a general "prefer bullets" rule: an aside of one or
80
78
  two items, or connected causal narrative ("X, because Y, which is why Z"), stays flowing prose --
81
79
  over-bulleting ordinary narrative just trades one readability problem for another.
@@ -138,10 +136,10 @@ to orient someone already inside. A node without one is an error, not a default.
138
136
  per owning directory or module with a file count, `LC_ALL=C` sorted. A node covering 941 files
139
137
  would otherwise put 75 KB of paths in front of a reader who wants to know which modules are
140
138
  involved — and the frontmatter already carries every path for search, so the mirror doesn't
141
- need to repeat them. Rewritten from `sources` on every write, never hand-edited. (Obsidian's
142
- Properties panel flattens the raw `sources` field into a truncated one-line string, which is
143
- why a mirror exists at all — but that is an argument for aggregating *the mirror*, not for
144
- trimming the field.)
139
+ need to repeat them. Rewritten from `sources` on every write, never hand-edited. (A raw YAML
140
+ list in frontmatter renders as a flattened, truncated one-line string in typical note-viewer
141
+ UI, which is why a mirror exists at all — but that is an argument for aggregating *the mirror*,
142
+ not for trimming the field.)
145
143
  - **`## Notes` is human-authored only.** Claude never writes into it — not at build time, not at
146
144
  regeneration. It is created empty and preserved verbatim forever after.
147
145
  - **Fence the generated region.** Everything the generator owns sits between
@@ -116,8 +116,8 @@ script fetched from a notes vault. Begin any re-run by reading it, and improve i
116
116
  re-deriving from scratch. Nothing like it ships, because which aggregations carry signal depends
117
117
  on the codebase — a distributed one would encode the wrong ecosystem's conventions.
118
118
 
119
- `.txt`, with markdown formatting inside, for a measured reason: Obsidian indexes `.md` files as
120
- notes, so a `_profile.md` turns up in search, Quick Switcher and the graph, where it is pure noise
119
+ `.txt`, with markdown formatting inside, for a measured reason: a vault viewer indexes `.md` files
120
+ as notes, so a `_profile.md` would turn up in search and note-browsing UI, where it is pure noise
121
121
  to a human reading notes. A non-`.md` extension is invisible to all of those and still perfectly
122
122
  readable. Note the `_` prefix does *nothing* mechanically — it is only a hint to a human who sees
123
123
  the file, matching `_manifest.tsv`. Record **negative results** here too ("this abbreviation has no
@@ -32,10 +32,10 @@ already named, to locate a specific detail inside it — never as an unscoped re
32
32
  instead of asking Synapse first. If you catch yourself about to grep the whole repo before checking
33
33
  `synapse/{project}/Index.md`, stop and check the index instead.
34
34
 
35
- **Why the cost difference is real, not just tidiness.** `synapse query body <node>` never goes
36
- through Obsidian's API — it's a direct disk read that extracts only the prose between the generated
37
- fences, skipping the node's `sources` list entirely. On a hub node, going through the API instead
38
- would move that node's entire frontmatter — megabytes — to print a few hundred words. On a large
35
+ **Why the cost difference is real, not just tidiness.** `synapse query body <node>` is a direct disk
36
+ read that extracts only the prose between the generated fences, skipping the node's `sources` list
37
+ entirely. Reading the whole note instead would move that node's entire frontmatter megabytes — to
38
+ print a few hundred words. On a large
39
39
  repo (dozens to hundreds of thousands of tracked files), that difference is the entire reason a
40
40
  query stays cheap instead of dominating the turn.
41
41
 
@@ -73,9 +73,9 @@ into `status:` either — always go through this skill, which caps at
73
73
  back what you read.
74
74
 
75
75
  `synapse vault-patch <path> --frontmatter <key> --replace` is also byte-preserving now (it
76
- delegates to the same field-local mechanism `frontmatter set` uses internally), unlike the old
77
- Obsidian MCP tool of the same shape — but it only ever writes a plain scalar, and it's a full
78
- read-apply-write round trip through the patch layer for one field. `frontmatter set` stays the
76
+ delegates to the same field-local mechanism `frontmatter set` uses internally) but it only ever
77
+ writes a plain scalar, and it's a full read-apply-write round trip through the patch layer for
78
+ one field. `frontmatter set` stays the
79
79
  right tool for this step: narrower, and the one call that exists specifically for it.
80
80
  6. **For completion only:** append implementation bullets to the existing
81
81
  `## Notes` section with `synapse vault-patch`:
@@ -95,6 +95,29 @@ On a `SYNAPSE_VAULT_INTEGRATIONS=git` vault, every edit commits automatically, s
95
95
  calls, not merely a record of intentional edits. The default `disk` backend keeps no history at all,
96
96
  so this safety net only exists once that backend is chosen.
97
97
 
98
+ ## Creating notes: authoring commands, not freeform writes
99
+
100
+ Every new note is created through an authoring command, never by freeforming a file at
101
+ `vault-write`:
102
+
103
+ - `/synapse-note` (bare mode) creates plain notes in any category -- `research/`,
104
+ `scratchpad/`, `inbox/`, anything that is not a tracked task or a design note. It mints the
105
+ frontmatter (`schema`, `title`, `note_id`, `created`, `updated`, `tags`) to the note schema's
106
+ contract, including full-timestamp format.
107
+ - `/synapse-note --task` creates tracked task notes under `tasks/{project}/`.
108
+ - `/synapse-design-note` and `/synapse-task-note` handle design discussions and compiled tasks --
109
+ both delegate the file-minting mechanics to `/synapse-note` internally.
110
+
111
+ `vault-write` is for *editing existing notes*, or the rare read-modify-write round trip. If a
112
+ direct creation is ever genuinely unavoidable, follow bare mode's frontmatter contract exactly
113
+ (`schema`/`title`/`note_id`/`created`/`updated`/`tags`, full `%Y-%m-%d %H:%M:%S %Z` timestamps)
114
+ rather than improvising a shape.
115
+
116
+ Schemas are opt-in by declaration: a note whose frontmatter declares `schema:` is held to that
117
+ schema's contract on every `vault-write`/`vault-patch`; a note without the field is never
118
+ validated or failed. Legacy notes predate the feature and stay valid as they are -- no backfill
119
+ is wanted, and "helpfully" adding `schema:` to old notes is not a service to anyone.
120
+
98
121
  ## Tagging is part of writing a note, not a separate pass
99
122
 
100
123
  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.
package/synapse-claude.md CHANGED
@@ -100,27 +100,20 @@ a real yes/no answer, not a formality to wave past.
100
100
  The vault is reached through the `synapse` CLI — `synapse vault-read`/`vault-write`/`vault-list`/
101
101
  `vault-search`/`vault-search-text`/`vault-doc-map`/`vault-patch`/`vault-backlinks`/`vault-links`/
102
102
  `vault-unresolved`/`vault-orphans`/`vault-deadends`/`vault-ambiguous`/`vault-rename` — for reads
103
- *and* for writes, never by resolving a vault path or calling an `mcp__obsidian__*` tool directly.
104
- Which concrete store the CLI talks to (`SYNAPSE_VAULT_INTEGRATIONS unset`, the default; `obsidian`, opted
105
- into for a running Obsidian app's own live search relevance and graph data; or `git`, opted into for
106
- the vault to own its own version control -- commit on every write, push/pull in the background) is
107
- resolved once,
108
- inside the compiled binary, from `SYNAPSE_VAULT_INTEGRATIONS`/`SYNAPSE_VAULT_DIR` never something a skill
109
- or an agent turn needs to know or branch on. By default that means no Obsidian dependency
110
- whatsoever: `read`/`write`/`list`/`search`/the link graph/rename are all plain disk I/O and direct
111
- computation against the vault folder. Under the opted-in `obsidian` backend, `search`/the link
112
- graph/rename go through Obsidian's own CLI over its local socket instead when Obsidian is running,
113
- falling back to the same disk-backed behavior automatically and silently (a one-line stderr note,
114
- nothing an agent turn needs to react to) whenever it isn't — no precondition to check or fail on
115
- either way.
103
+ *and* for writes, never by resolving a vault path and reading or writing the note file directly.
104
+ Which concrete store the CLI talks to (`SYNAPSE_VAULT_INTEGRATIONS unset`, the default; or `git`,
105
+ opted into for the vault to own its own version control -- commit on every write, push/pull in the
106
+ background) is resolved once, inside the compiled binary, from `SYNAPSE_VAULT_INTEGRATIONS`/
107
+ `SYNAPSE_VAULT_DIR` — never something a skill or an agent turn needs to know or branch on. `read`/
108
+ `write`/`list`/`search`/the link graph/rename are all plain disk I/O and direct computation against
109
+ the vault folder, in every configuration.
116
110
 
117
111
  **Every write to a note goes through `synapse vault-write` or `vault-patch`. Never the `Write`/`Edit`
118
- tools on the on-disk path, and never a raw `mcp__obsidian__*` tool call either** not for a one-line
119
- change, and least of all when `Write`/`Edit` are already in hand from editing code earlier in the
120
- same turn, because that proximity is precisely what causes this to be violated. The vault being an
121
- ordinary directory means the wrong path *works*: Obsidian's file watcher converges, the auto-commit
122
- hook matches `Write|Edit`/`Bash` running `vault-write`/`vault-patch`, and nothing visibly breaks
123
- which is why the habit never self-corrects on its own. The reason is not a
112
+ tools on the on-disk path** not for a one-line change, and least of all when `Write`/`Edit` are
113
+ already in hand from editing code earlier in the same turn, because that proximity is precisely
114
+ what causes this to be violated. The vault being an ordinary directory means the wrong path
115
+ *works*: the auto-commit hook matches `Write|Edit`/`Bash` running `vault-write`/`vault-patch`, and
116
+ nothing visibly breaks which is why the habit never self-corrects on its own. The reason is not a
124
117
  failure mode to dodge; it is that an invariant upheld only when convenient is worth nothing. Nothing
125
118
  else in the system can rely on it, and every note then has to be re-checked by hand instead of
126
119
  trusted. Synapse's own tooling holds this line — `synapse write-node` goes through the same `Store`
@@ -129,19 +122,18 @@ differ. If the CLI itself ever fails (not installed, no vault configured), that'
129
122
  failure to report and stop on, never a reason to fall back to a raw file edit.
130
123
 
131
124
  Every shipped command and skill, `/synapse-vault-tidy` included, reaches the vault only through
132
- the `synapse` CLI's `vault-*` subcommands — none of them calls an `mcp__obsidian__*` tool.
125
+ the `synapse` CLI's `vault-*` subcommands — none of them writes to the vault folder directly.
133
126
 
134
127
  - You may create and edit notes in this vault **without asking for
135
128
  permission first**, as long as each note is placed in the folder
136
129
  matching its category per `Index.md`.
137
- - Filenames are human-readable titles (not timestamp-prefixed — Obsidian's
138
- sidebar/graph display the filename directly, so a timestamp prefix reads
139
- poorly there). Sanitize filesystem-illegal characters (`/ : * ? " < > |`)
130
+ - Filenames are human-readable titles (not timestamp-prefixed — the filename is the display title,
131
+ so a timestamp prefix reads poorly). Sanitize filesystem-illegal characters (`/ : * ? " < > |`)
140
132
  but otherwise keep the title as-is.
141
133
  - Frontmatter carries what the filename no longer does: `title`, `created`
142
134
  (real timestamp at creation time), and for task notes `task_id` /
143
135
  `status` (`TODO`/`IN-PROGRESS`/`REVIEW`/`DONE`/`CANCELED`).
144
- - Link with Obsidian wikilinks: `[[filename]]` or `[[filename|display
136
+ - Link with wikilinks: `[[filename]]` or `[[filename|display
145
137
  text]]` (no extension, exact filename minus `.md`).
146
138
  - **Never hard-wrap note bodies. A newline exists if and only if a break is
147
139
  intended in the output** — source line structure mirrors the output's block
@@ -156,16 +148,16 @@ the `synapse` CLI's `vault-*` subcommands — none of them calls an `mcp__obsidi
156
148
  makes the text **renderer-independent**: if a newline never appears where
157
149
  no break is wanted, then "does a single newline render as `<br>` or as a
158
150
  space?" never arises, and strict CommonMark, non-strict CommonMark,
159
- Obsidian, pandoc and GitHub all produce the same result — the ambiguous
151
+ pandoc and GitHub all produce the same result — the ambiguous
160
152
  input case is simply gone. This is HTML's content model applied to plain
161
153
  text: a newline is markup meaning "break here", not cosmetic formatting of
162
154
  the source file.
163
155
 
164
156
  It also keeps **line length a view decision rather than a content one**.
165
157
  Hard-wrapping is the author asserting a measure, baking one viewport into
166
- the text; unwrapped, the same bytes are correct at every width — Obsidian's
167
- "Readable line length" on or off, a narrow split pane, a wide monitor,
168
- mobile, print. Hard-wrapped prose fails both ways and is unfixable at read
158
+ the text; unwrapped, the same bytes are correct at every width — any
159
+ reader's readable-line-length setting on or off, a narrow split pane, a
160
+ wide monitor, mobile, print. Hard-wrapped prose fails both ways and is unfixable at read
169
161
  time: at 80 columns it double-wraps raggedly in a narrow pane, and sits as a
170
162
  fixed narrow ribbon in a wide one. Wanting a ~66-character measure is right
171
163
  (it's a real typographic optimum, which is why that setting exists) — put it
@@ -1,5 +1,9 @@
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.
1
+ # Read live from the Synapse package as a fallback (tier 3) -- never copied or
2
+ # seeded anywhere, and this template file is never written to. To customize,
3
+ # create your own synapse-ignore-files.conf at $XDG_CONFIG_HOME/synapse/, else
4
+ # ~/.config/synapse/ (the legacy ~/.claude/ also works); your own copy always
5
+ # wins and is never touched by Synapse.
6
+ # Edit or extend your own copy any time.
3
7
  #
4
8
  # Extra path patterns dropped at enumeration by synapse build-lists, on top
5
9
  # of the built-in exclusions (compiled objects, archives, media, model weights,
@@ -1,6 +1,9 @@
1
- # Copy this to ~/.claude/synapse-module-boilerplate.conf (setup.sh does this
2
- # for you). Never overwritten once installed -- add your own ecosystem's
3
- # conventions any time.
1
+ # Read live from the Synapse package as a fallback (tier 3) -- never copied or
2
+ # seeded anywhere, and this template file is never written to. To customize,
3
+ # create your own synapse-module-boilerplate.conf at $XDG_CONFIG_HOME/synapse/, else
4
+ # ~/.config/synapse/ (the legacy ~/.claude/ also works); your own copy always
5
+ # wins and is never touched by Synapse.
6
+ # Add your own ecosystem's conventions any time.
4
7
  #
5
8
  # Boilerplate path-segment chains that module_of() (synapse query,
6
9
  # synapse write-node's ## Sources mirror) strips through wholesale when
@@ -1,4 +1,8 @@
1
- # Copy this to ~/.claude/synapse-projects.conf (setup.sh does this for you).
1
+ # Read live from the Synapse package as a fallback (tier 3) -- never copied or
2
+ # seeded anywhere, and this template file is never written to. To customize,
3
+ # create your own synapse-projects.conf at $XDG_CONFIG_HOME/synapse/, else
4
+ # ~/.config/synapse/ (the legacy ~/.claude/ also works); your own copy always
5
+ # wins and is never touched by Synapse.
2
6
  #
3
7
  # Known project -> task-prefix mappings for THIS machine only. Deliberately
4
8
  # separate from the portable Synapse package (never committed
@@ -1,5 +1,9 @@
1
- # Copy this to ~/.claude/synapse-prompt-stopwords.conf (setup.sh does this for
2
- # you). Never overwritten once installed -- edit or extend any time.
1
+ # Read live from the Synapse package as a fallback (tier 3) -- never copied or
2
+ # seeded anywhere, and this template file is never written to. To customize,
3
+ # create your own synapse-prompt-stopwords.conf at $XDG_CONFIG_HOME/synapse/, else
4
+ # ~/.config/synapse/ (the legacy ~/.claude/ also works); your own copy always
5
+ # wins and is never touched by Synapse.
6
+ # Edit or extend your own copy any time.
3
7
  #
4
8
  # Common English function words, filtered out by `synapse vocab` before
5
9
  # building a search pattern from a raw prompt -- without this, an ordinary
@@ -5,15 +5,12 @@ SYNAPSE_VAULT_DIR="$HOME/Vault/YourVault"
5
5
  # Unset by default: reads/writes the vault folder directly, no external
6
6
  # dependency at all -- search and the link graph
7
7
  # (backlinks/links/unresolved/orphans/deadends/ambiguous) are computed by
8
- # DiskStore's own scan. A comma-separated, outer-to-inner list of
9
- # integrations layers extra behavior on top instead: "obsidian" prefers a
10
- # running external app's own live search relevance and graph data, falling
11
- # back to the same disk-backed behavior automatically whenever that app
12
- # isn't reachable (see docs/synapse/synapse-extended-store.md); "git" owns
13
- # the vault's own git lifecycle (commit on write, push once enough pile up).
14
- # "git,obsidian" combines both. The disk store itself is never named -- it's
15
- # always the implicit innermost element.
16
- #SYNAPSE_VAULT_INTEGRATIONS="obsidian"
8
+ # DiskStore's own scan. The one optional integration, "git", layers extra
9
+ # behavior on top instead: it owns the vault's own git lifecycle (commit on
10
+ # write, push once enough pile up) -- see
11
+ # docs/synapse/synapse-extended-store.md. The disk store itself is never
12
+ # named -- it's always the implicit innermost element.
13
+ #SYNAPSE_VAULT_INTEGRATIONS="git"
17
14
 
18
15
  # Where Synapse clones/builds tree-sitter grammars (shared across every
19
16
  # project -- not per-repo). Safe to leave at the default.