@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.
- package/Index.md.template +23 -0
- package/bin/synapse-hook.cjs +19 -0
- package/bin/synapse-setup.cjs +420 -0
- package/bin/synapse.cjs +20 -0
- package/commands/synapse-design-note.md +229 -0
- package/commands/synapse-init.md +354 -0
- package/commands/synapse-note.md +196 -0
- package/commands/synapse-rebuild-diff.md +314 -0
- package/commands/synapse-rebuild-full.md +152 -0
- package/commands/synapse-status.md +144 -0
- package/commands/synapse-task-note.md +133 -0
- package/commands/synapse-vault-tidy.md +187 -0
- package/harness/claude/hooks.json +54 -0
- package/harness/codex/hooks.json +54 -0
- package/harness/codex/skills/synapse-design-note/SKILL.md +236 -0
- package/harness/codex/skills/synapse-init/SKILL.md +354 -0
- package/harness/codex/skills/synapse-note/SKILL.md +212 -0
- package/harness/codex/skills/synapse-rebuild-diff/SKILL.md +315 -0
- package/harness/codex/skills/synapse-rebuild-full/SKILL.md +149 -0
- package/harness/codex/skills/synapse-status/SKILL.md +146 -0
- package/harness/codex/skills/synapse-task-note/SKILL.md +133 -0
- package/harness/codex/skills/synapse-vault-tidy/SKILL.md +187 -0
- package/harness/opencode/plugin/synapse.js +164 -0
- package/lib/obsidian-mcp-refresh.cjs +303 -0
- package/lib/resolve-binaries.cjs +54 -0
- package/package.json +26 -0
- package/skills/synapse-node/SKILL.md +211 -0
- package/skills/synapse-node-authoring/SKILL.md +188 -0
- package/skills/synapse-node-format/SKILL.md +205 -0
- package/skills/synapse-orientation/SKILL.md +468 -0
- package/skills/synapse-query/SKILL.md +99 -0
- package/skills/synapse-task/SKILL.md +261 -0
- package/skills/synapse-vault/SKILL.md +107 -0
- package/synapse-claude.md +220 -0
- package/synapse-fence-languages.conf.template +24 -0
- package/synapse-ignore-files.conf.template +45 -0
- package/synapse-module-boilerplate.conf.template +24 -0
- package/synapse-projects.conf.template +14 -0
- package/synapse-prompt-stopwords.conf.template +594 -0
- 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)/
|