@imunitic/synapse 0.0.1-test.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/Index.md.template +23 -0
  2. package/bin/synapse-hook.cjs +19 -0
  3. package/bin/synapse-setup.cjs +420 -0
  4. package/bin/synapse.cjs +20 -0
  5. package/commands/synapse-design-note.md +229 -0
  6. package/commands/synapse-init.md +354 -0
  7. package/commands/synapse-note.md +196 -0
  8. package/commands/synapse-rebuild-diff.md +314 -0
  9. package/commands/synapse-rebuild-full.md +152 -0
  10. package/commands/synapse-status.md +144 -0
  11. package/commands/synapse-task-note.md +133 -0
  12. package/commands/synapse-vault-tidy.md +187 -0
  13. package/harness/claude/hooks.json +54 -0
  14. package/harness/codex/hooks.json +54 -0
  15. package/harness/codex/skills/synapse-design-note/SKILL.md +236 -0
  16. package/harness/codex/skills/synapse-init/SKILL.md +354 -0
  17. package/harness/codex/skills/synapse-note/SKILL.md +212 -0
  18. package/harness/codex/skills/synapse-rebuild-diff/SKILL.md +315 -0
  19. package/harness/codex/skills/synapse-rebuild-full/SKILL.md +149 -0
  20. package/harness/codex/skills/synapse-status/SKILL.md +146 -0
  21. package/harness/codex/skills/synapse-task-note/SKILL.md +133 -0
  22. package/harness/codex/skills/synapse-vault-tidy/SKILL.md +187 -0
  23. package/harness/opencode/plugin/synapse.js +164 -0
  24. package/lib/obsidian-mcp-refresh.cjs +303 -0
  25. package/lib/resolve-binaries.cjs +54 -0
  26. package/package.json +26 -0
  27. package/skills/synapse-node/SKILL.md +211 -0
  28. package/skills/synapse-node-authoring/SKILL.md +188 -0
  29. package/skills/synapse-node-format/SKILL.md +205 -0
  30. package/skills/synapse-orientation/SKILL.md +468 -0
  31. package/skills/synapse-query/SKILL.md +99 -0
  32. package/skills/synapse-task/SKILL.md +261 -0
  33. package/skills/synapse-vault/SKILL.md +107 -0
  34. package/synapse-claude.md +220 -0
  35. package/synapse-fence-languages.conf.template +24 -0
  36. package/synapse-ignore-files.conf.template +45 -0
  37. package/synapse-module-boilerplate.conf.template +24 -0
  38. package/synapse-projects.conf.template +14 -0
  39. package/synapse-prompt-stopwords.conf.template +594 -0
  40. package/synapse.conf.template +23 -0
@@ -0,0 +1,315 @@
1
+ ---
2
+ name: synapse-rebuild-diff
3
+ description: Manually bring a repo's Synapse namespace back in line after major same-branch drift — a pull, a rebase, or a long absence. Triages each drifted node into reseat / patch-from-diff / re-orient rather than rebuilding everything. Refuses outright on a cross-branch mismatch; for a full rebuild from scratch, use the synapse-rebuild-full skill instead.
4
+ ---
5
+
6
+ # Synapse Rebuild Diff: Reconcile a Namespace After Same-Branch Drift
7
+
8
+ The synapse-init skill builds a namespace. The two staleness tiers and `synapse query drift` *detect*
9
+ that it has moved. This skill is the deliberate, human-invoked repair for the case where enough has
10
+ moved that lazy per-read regeneration is the wrong instrument.
11
+
12
+ **Same-branch only.** Every scenario below happens on the branch the namespace already describes —
13
+ a pull, a rebase, time passing, your own hand-written commits. None of them involve the current
14
+ checkout being on a *different* branch than the namespace's own recorded `branch:` field. If it is,
15
+ this skill refuses outright rather than attempting a diff — see the branch-identity check under
16
+ Prerequisites. Comparing one branch's tree against another isn't drift, it's just two unrelated
17
+ states, and none of the triage classes below (reseat / patch / re-orient) were built for that.
18
+
19
+ ## When to run it
20
+
21
+ Manually, when you already expect major drift:
22
+
23
+ - **A plain `pull`** that landed a meaningful number of commits — fast-forward, same branch throughout.
24
+ - **A `pull --rebase`** — your local commits get new SHAs, so `synapse query drift` will likely
25
+ report the baseline as "not an ancestor of HEAD". That's expected here, not a sign of anything
26
+ wrong: you were on the same branch the whole time, only its history got rewritten.
27
+ - **A large merge landing in your current branch** — another branch's tip merged in via `git merge`.
28
+ Still same-branch throughout: `HEAD` gains a merge commit, it doesn't move to a different branch.
29
+ - **A long absence** — weeks or months of other people's commits landed while you were elsewhere, on
30
+ the same branch.
31
+ - **You wrote a lot of code by hand.** The plainest case and probably the most common: days of ordinary
32
+ work in your own branch, in your own editor, with no model involved. Tier 1 only fires on an edit
33
+ *in this session*, so none of it was flagged as it happened. `stale` will still catch content
34
+ changes to files a node already claims — but it reports a rename as "gone", and a **newly added
35
+ file it cannot see at all**, because a path in no node's `sources` has nothing to compare against.
36
+ Real feature work adds files, so this is exactly where the graph goes quietly out of date. Same
37
+ applies to anything else that bypasses the session: an IDE refactor (which produces both bad cases
38
+ at once — renames *and* new paths), a `sed -i`, generated code that the build rewrote from a
39
+ schema, a dependency bump, or a moved submodule pointer.
40
+
41
+ Do **not** run it after an ordinary pull. Tier 1 flags what this session edited, the `synapse-node`
42
+ skill regenerates a node lazily when its body is actually needed, and `synapse query drift` is the
43
+ cheap check that tells you whether anything more is warranted. This skill exists for when the answer
44
+ is clearly yes.
45
+
46
+ **A large job is the expected outcome, not a warning sign.** On a monorepo with a hundred thousand
47
+ files and heavy traffic, most of the graph moving at once is simply what the situation looks like, and
48
+ forty nodes in *re-orient* is a normal shape for this skill rather than a reason to hesitate. Nothing
49
+ invokes this automatically — a human asked for it, knowing their own repo and why they are here. So
50
+ report the size, then **do the work**. Do not recommend against a rebuild on the grounds that it is
51
+ expensive, do not offer a reduced version of it unasked, and do not describe replacing the graph as
52
+ destructive: replacing it is the entire point. Volunteer a smaller option only where a *correctness*
53
+ reason argues for one, and even then do the full job if the human says so.
54
+
55
+ **One mechanical fact about branches, because it is not guessable.** A namespace is keyed by repo
56
+ *and branch* (`synapse/{repo}@{branch}/`), so each branch has its own or has none. A branch switch
57
+ therefore no longer invalidates anything: the graph you built on the mainline stays intact and keeps
58
+ describing the mainline, and the branch you switched to simply has no namespace until someone runs
59
+ the synapse-init skill there. That is an ordinary state, not a problem to fix.
60
+
61
+ So the massive-drift case this skill exists for is now the *unusual* one rather than the norm. It
62
+ still happens — a branch can be checked out inside any worktree, and a long-lived branch gets rebased
63
+ onto a moved trunk, which leaves the recorded baseline off the current line exactly as a branch switch
64
+ used to. Read a "not an ancestor of HEAD" warning as "history moved under this graph", and reach for
65
+ this skill when it does. What no longer happens is arriving here merely because you changed branch.
66
+
67
+ ## Prerequisites
68
+
69
+ - The namespace must exist. If `synapse/{repo}@{branch}/Index.md` is absent, this is a first build — use
70
+ the synapse-init skill.
71
+ - **Branch-identity check — hard stop, run this before anything else, including drift/grounding.**
72
+ Compare the current checkout's branch against the namespace's own recorded `branch:` frontmatter
73
+ field:
74
+ ```sh
75
+ current_branch="$(git symbolic-ref --short HEAD)"
76
+ ns_branch="$(grep -m1 '^branch:' "synapse/{repo}@{branch}/Index.md" \
77
+ | sed -e 's/^branch: *//' -e 's/^"//' -e 's/"$//')"
78
+ ```
79
+ If they don't match, **refuse immediately** — do not run `synapse query drift`, do not read
80
+ anything else. Say plainly that this namespace describes a different branch than the current
81
+ checkout, and point at the synapse-init skill (if the current branch has no namespace of its own)
82
+ or at checking out the branch/worktree the namespace actually describes. This is a distinct, harder
83
+ check than the "baseline is not an ancestor of HEAD" ancestry signal below — that one is a *soft*,
84
+ informational finding (expected after a same-branch `pull --rebase`); this one is a hard refusal,
85
+ because it is not this checkout's namespace to diff at all. Never conflate the two: a non-ancestor
86
+ baseline on a branch-identity match still proceeds normally, per the "One mechanical fact about
87
+ branches" section above.
88
+ - The work directory (`$SYNAPSE_WORK_DIR`, default `~/.claude/synapse-work/{repo}@{branch}/`) ideally
89
+ holds the `manifest.tsv` from the original build. Without it, new paths cannot be classified as
90
+ auto-claimable, and clustering decisions have to be re-derived — say so rather than proceeding as if
91
+ nothing were missing. `synapse/{repo}@{branch}/_manifest.tsv` is the fallback copy.
92
+ - Read `synapse/{repo}@{branch}/_profile.txt` if it exists, before triaging anything. It records the
93
+ aggregations that carried signal for this repo and the searches that came back empty.
94
+
95
+ ## Procedure
96
+
97
+ ### 1. Size the job before doing any of it
98
+
99
+ ```sh
100
+ ~/.synapse query drift
101
+ ~/.synapse query grounding
102
+ ```
103
+
104
+ Report what it says, in the human's terms, **before** touching anything: how far the baseline is from
105
+ HEAD (and whether it is an ancestor at all), how many nodes are flagged in each class, and how many
106
+ added paths need a decision. Silence means nothing to rebuild — say so and stop, including when the
107
+ repo is behind its upstream: drift prints that only alongside a finding, because an accurate graph
108
+ plus unpulled commits is nothing to repair yet.
109
+
110
+ Two answers change the plan:
111
+
112
+ - **"baseline … is not an ancestor of HEAD"** — a branch switch or a reset. The file-level diff is
113
+ still exactly right (it compares trees, not history), but expect deletions to dominate: files that
114
+ exist on the built line and simply are not here.
115
+ - **"no commit recorded"** or **"baseline … not in local history"** — those nodes cannot be diffed at
116
+ all. They go straight to the *re-orient* class in step 3; there is no cheaper option for them.
117
+
118
+ ### 2. Mechanical phase — always, and cheap
119
+
120
+ ```sh
121
+ ~/.synapse build-lists --reenumerate
122
+ ```
123
+
124
+ `--reenumerate` matters here: without it an existing `all.txt` is reused, so a branch switch would be
125
+ invisible to enumeration. **Read the coverage report.** On a branch switch, per-node list sizes will
126
+ move a lot and some may reach zero.
127
+
128
+ - **A node whose list is now empty** means that subsystem does not exist on this branch. **Do not
129
+ write it** — `synapse write-node` refuses an empty path list, and that refusal is correct. Report
130
+ the node and leave it in place, untouched. **Never delete a node to tidy up a branch switch:**
131
+ `## Notes` is human-authored, lives outside the generated fence, and is unrecoverable.
132
+ - **Unclaimed added paths** are a judgment call: widen an existing manifest line where a path belongs
133
+ to a cluster that already exists, and leave a genuinely new subsystem for a new manifest line and
134
+ its own node. Re-run `synapse build-lists` after editing the manifest, and check coverage again.
135
+
136
+ Then rebuild the reverse index so the hook and the read path agree with the new enumeration:
137
+
138
+ ```sh
139
+ ~/.synapse build-index
140
+ ```
141
+
142
+ ### 3. Triage each flagged node — reseat, patch, or re-orient
143
+
144
+ **Hard requirement for the *re-orient* class, not a recommendation: invoke the `synapse-orientation`
145
+ skill before writing one sentence of new prose for that node.** Reading the node's own source
146
+ directly and reasoning from what you already know about the codebase is not a substitute, no matter
147
+ how accurate or hard-won that knowledge is — a node's premises being re-derived is the same problem
148
+ a first build faces, and it gets the same required technique, every time, not just when nothing
149
+ faster is available. The reason this can't be skipped even by a confident, correct reading of the
150
+ source: the skill's own verification step is to *run* something real (`synapse tags`/`synapse query`
151
+ against a real sample) and judge the actual output, not what the source implies the output should
152
+ be — and only an executed check can surface a gap in the tooling itself, one the source reading alone
153
+ has no way to reveal, however carefully it's done. A node whose own subject is the tagging or
154
+ extraction pipeline is exactly the case most tempting to shortcut this way, and exactly the case
155
+ where doing so costs the most.
156
+
157
+ `synapse-node-format` is the other skill to load before triaging, for every class, not just
158
+ *re-orient*: the node contract — frontmatter, the crux pointer, `## Links`, `grounded_in` — the same
159
+ one the synapse-init skill and the `synapse-node` skill write against.
160
+
161
+ **The principle: compute new prose from the diff, not by re-reading the node's sources.** A node
162
+ covering 15,000 files where 12 changed already has prose encoding the other 14,988. Re-reading it all
163
+ is the expensive mistake this skill exists to avoid, and it also throws away hard-won findings the
164
+ diff has nothing to say about.
165
+
166
+ **Take the nodes that lost files first, and keep the full list of deleted paths in front of you for
167
+ every node after that.** A deletion in one node is routinely the other half of an addition in
168
+ another — a type moved from the binary into a library, a module promoted out of a `util`. Triaged in
169
+ drift's arbitrary order, the node that *gained* the file is patched first, with no way to know the
170
+ file came from anywhere, and the patch fills the gap with a guess. One cheap list, read once:
171
+
172
+ ```sh
173
+ git diff --diff-filter=D --name-only -M <commit>..HEAD
174
+ ```
175
+
176
+ **Size each node by changed lines, not by changed files.** Drift reports file counts because that is
177
+ what it can compute without a diff, but a file count saturates immediately in a repo of small
178
+ modules: a node of 9 files with 5 touched reads as 56% when the actual change is 199 lines out of
179
+ 2,077, and every one of them a formatting or import edit. Get the real ratio before choosing:
180
+
181
+ ```sh
182
+ git diff --numstat <commit>..HEAD -- $(tr '\n' ' ' < "$W/lists/NN.txt") \
183
+ | awk '{a += $1; d += $2} END {print a + d}'
184
+ ```
185
+
186
+ against the node's own line count, and use `synapse query sources "{Node}" --count` for the file
187
+ count drift's numbers are relative to. Then pick one of three strategies and **say which one you
188
+ picked and why**:
189
+
190
+ **Restore the crux directive before writing any node back.** `synapse query body` returns the
191
+ *expanded* crux — the fenced code the writer sliced — not the directive that produced it. Writing that
192
+ straight back stores a quote of a file as it looked at the old baseline, presented as if it were
193
+ current. So rebuild the directive from the pointer the writer recorded:
194
+
195
+ ```sh
196
+ ~/.synapse query field "{Node}" crux_path
197
+ ~/.synapse query field "{Node}" crux_lines
198
+ ```
199
+
200
+ and replace the fenced block with `<!-- crux: <crux_path> <crux_lines> -->` so it is cut from the
201
+ current file. One judgment goes with it: re-slicing the same range is only safe if that file did not
202
+ change. If it did, the line numbers may now point at something else entirely — treat the crux as
203
+ needing a fresh pointer, exactly as the prose needs a fresh sentence. A node with no `crux_path` had
204
+ `none`, and stays that way.
205
+
206
+ **The same applies to groundings, and forgetting them loses more.** `grounded_in` is frontmatter and
207
+ its directives are stripped from the body, so a recovered body contains none — write it back as-is and
208
+ the node's provenance is gone with no error. Recover the pointers per node:
209
+
210
+ ```sh
211
+ ~/.synapse query grounding "{Node}" --list # path<TAB>lines
212
+ ```
213
+
214
+ and re-emit a `<!-- grounded_in: <path> <lines> -->` for each. Run `synapse query grounding` before
215
+ triaging: it is cheaper than the diff and sharper than a churn ratio. A **`moved`** line hands you the
216
+ corrected range outright, no reading. A **`changed`** line points at evidence that no longer says what
217
+ the summary claims — which is a better reason to re-read a node than any percentage, because it names
218
+ the sentence at risk rather than the volume of change around it.
219
+
220
+ **Reseat** — renames only, no content change. No reading at all. Recover the existing prose with
221
+ `synapse query body "{Node}"`, drop its trailing `## Sources` block (the writer regenerates that),
222
+ re-enumerate so the list holds the new paths, and write it back. Repeating this is safe: the writer
223
+ trims the body's leading and trailing blank lines, so a reseat is idempotent rather than accreting
224
+ padding each time. The concept did not change; only paths moved. This also
225
+ works on a machine that never built the namespace, because the body came from the node itself rather
226
+ than from a work-dir file.
227
+
228
+ **Patch from the diff** — a small fraction of the node's *lines* changed (rule of thumb: under ~15%),
229
+ and the file its `crux` quotes still exists. Read three things and nothing else:
230
+
231
+ 1. the current prose — `synapse query body "{Node}"`;
232
+ 2. `git diff --name-status -M <commit>..HEAD` restricted to that node's paths, for *which* files moved;
233
+ 3. hunks for a **bounded** selection of those files — always including any file the `crux` quotes.
234
+
235
+ Then amend only the sentences the diff contradicts, and keep everything else verbatim.
236
+
237
+ **A patch may say what the node now contains. It must not say where something came from** unless the
238
+ rename is in the diff it read. Provenance is the one claim a node-restricted diff structurally cannot
239
+ support: the other end of the move is in a different node's paths, so the diff shows an unexplained
240
+ new file, and the plausible local origin is an invention. `git`'s rename detection is not a
241
+ safety net here — a file that moved between modules and was rewritten on the way lands below the
242
+ similarity threshold and shows up as a delete in one node and an add in another even under
243
+ `-M --find-copies-harder`. Which is why deletions get read first, below.
244
+
245
+ **Re-orient** — a large fraction changed, the baseline is unusable, or there is a structural signal:
246
+ the `crux` file is gone or renamed, whole modules entered or left the node, or a package root changed
247
+ name. Here the prose's premises are suspect, so patching would preserve a claim that is no longer
248
+ true. Re-run this node's aggregations from `_profile.txt` (path-level, so cheap even on a hub node),
249
+ read the few load-bearing files the aggregations point at, and re-author as the synapse-init skill
250
+ would.
251
+
252
+ **The diff must be projected as carefully as `sources` is.** `git diff` with hunks across a hub node's
253
+ paths over hundreds of commits runs to megabytes — the same constraint that makes `sources` unreadable
254
+ applies to the diff. So: `--name-status` for names, `--stat` to size it, and hunks only for a bounded
255
+ selection. Never pipe an unbounded `git diff <commit>..HEAD` into a context window.
256
+
257
+ ### 4. Write each rebuilt node
258
+
259
+ ```sh
260
+ ~/.synapse write-node --title "{Node}" --summary "{one line}" \
261
+ --paths "$W/lists/NN.txt" --body "$W/body.md"
262
+ ```
263
+
264
+ It re-records `commit`, so this checkout becomes the node's new baseline — which is what makes the next
265
+ `drift` meaningful. **Re-check the one-line `summary`**: after a branch switch it can be wrong in kind,
266
+ not merely stale, if the subsystem's shape differs on this line.
267
+
268
+ ### 5. Rebuild the projections and verify
269
+
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
277
+ ```
278
+
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
281
+ system notices. It now reports `Node<TAB>relation -> Target (no such node)` per dangling edge.
282
+
283
+ One check is still yours, because no command performs it: **every node file appears in `Index.md`**. An
284
+ unlisted node exists and is reachable by search, but is invisible to anyone reading the map.
285
+
286
+ ### 6. Report what happened
287
+
288
+ Per node: the strategy chosen and why. Plus what was deliberately left alone — nodes drift did not
289
+ flag, and nodes whose sources vanished on this branch. A rebuild that silently re-authored forty nodes
290
+ is indistinguishable, from the outside, from one that did nothing.
291
+
292
+ ## Guardrails
293
+
294
+ - **Never `pull`, `fetch --prune`, `rebase`, `reset` or `checkout`.** The human chose this checkout;
295
+ this skill describes and records it. Report how far behind the upstream ref is and stop there.
296
+ - **Never re-read a node's full sources to patch a small diff.** That is the specific waste this
297
+ skill is built to avoid.
298
+ - **Know what patching cannot fix.** It keeps every sentence the diff does not contradict, so a claim
299
+ that was wrong when the node was *built* survives every future patch untouched — the diff has
300
+ nothing to say about a statement that was never true. Patching is therefore only as good as the
301
+ baseline prose, and a node's most likely error is not drift but an explanation invented at build
302
+ time. The `crux` is no longer the exposure it was — `synapse write-node` slices it out of the
303
+ file from a `<!-- crux: path start-end -->` directive, so it is verbatim by construction rather than
304
+ by instruction. What remains unguarded is the prose. A sentence asserting a *mechanism* ("X is
305
+ behind a mutex, which is why Y")
306
+ deserves more suspicion than one asserting structure — if the diff touches its file at all, verify
307
+ it rather than carrying it over.
308
+ - **Never write a node with an empty path list**, and never delete a node whose sources vanished — its
309
+ `## Notes` is human-authored and outside the generated fence.
310
+ - **Never hand-write frontmatter or the `## Sources` mirror.** `synapse write-node` owns them; doing
311
+ it by hand cannot scale to a hub node and silently drops `summary` and `commit`.
312
+ - **Never rebuild a node drift did not flag.** Regeneration has real cost and it is not free of risk —
313
+ each rewrite is a chance to lose a good sentence.
314
+ - **Say when the graph now describes a different branch than it did before**, in the final report. That
315
+ fact outlives the session, and the next reader has no other way to know.
@@ -0,0 +1,149 @@
1
+ ---
2
+ name: synapse-rebuild-full
3
+ description: Wipe a repo's Synapse namespace and rebuild it from scratch via the synapse-init skill, for the case where diff-driven triage isn't the right tool — the graph has drifted too far, or a clean rebuild is just wanted directly. Preserves any hand-written `## Notes` content first and auto-merges what it can back into the new nodes. For ordinary same-branch drift, use the synapse-rebuild-diff skill instead — it's cheaper and never deletes a node outright.
4
+ ---
5
+
6
+ # Synapse Rebuild Full: Wipe and Rebuild a Namespace From Scratch
7
+
8
+ The synapse-rebuild-diff skill triages drift node by node — reseat, patch, or re-orient — and never
9
+ deletes anything. This skill is the other tool: it deletes the current namespace outright and
10
+ rebuilds it from nothing via the synapse-init skill's own First-time-build procedure. Reach for it
11
+ when the graph has drifted past the point where triage is worth it (most nodes would land in
12
+ *re-orient* anyway), when the namespace is corrupted or was built badly, or when a clean rebuild is
13
+ simply what's wanted — never as a reflex for ordinary drift, which the synapse-rebuild-diff skill
14
+ handles more cheaply and without touching anything irreplaceable.
15
+
16
+ **Unlike the synapse-rebuild-diff skill, this skill does not care which branch is checked out beyond
17
+ the ordinary sense.** It isn't diffing against anything — it resolves `{repo}@{branch}` for
18
+ whatever's currently checked out and rebuilds *that* namespace, exactly as the synapse-init skill
19
+ does. There is no branch-identity guardrail here because there is nothing to compare against; the
20
+ branch you're on is simply the branch being rebuilt.
21
+
22
+ ## When this runs
23
+
24
+ Invoked whenever the user wants a repo's Synapse namespace wiped and rebuilt from scratch — no
25
+ arguments to parse, always operates on the repo and branch containing the current working directory.
26
+
27
+ ## Prerequisites
28
+
29
+ - Requires the `obsidian` MCP server (`mcp__obsidian__*` tools). If unreachable, say so and stop —
30
+ same requirement the synapse-init skill has.
31
+ - Must be run from inside a git repository, on a named branch (not detached `HEAD`) — same
32
+ requirement the synapse-init skill has, since `synapse_namespace` needs a branch to key on.
33
+
34
+ ## Procedure
35
+
36
+ ### 1. Resolve the namespace
37
+
38
+ Same resolution the synapse-init skill uses: repo root (`git rev-parse --show-toplevel`), namespace
39
+ key (`{repo}@{branch}`, printed by `synapse namespace`), remote (for the index note's
40
+ verification field).
41
+
42
+ Check whether `synapse/{repo}@{branch}/Index.md` exists.
43
+
44
+ - **Doesn't exist** → there is nothing to wipe. This is just a first build, not a rebuild — hand off
45
+ directly to the synapse-init skill and stop here. Do not run the wipe step at all in this case; it
46
+ would only fail on a directory that isn't there.
47
+ - **Exists, `remote` mismatches** → belongs to a different repo sharing this key. Same refusal the
48
+ synapse-init skill gives in this case: stop, name both remotes, do not touch it.
49
+ - **Exists, `remote` matches** → continue to step 2.
50
+
51
+ ### 2. Preview the wipe and get explicit confirmation
52
+
53
+ ```sh
54
+ ~/.synapse graph-wipe --dry-run
55
+ ```
56
+
57
+ Report its output plainly: node count, and — the one thing this step exists to surface — how many
58
+ nodes carry hand-written `## Notes` content that's about to be deleted, and which ones. `## Notes` is
59
+ human-authored, lives outside every generated fence, and no rebuild regenerates it; a wipe is the one
60
+ operation in this pair of skills that actually deletes files rather than overwriting them with
61
+ preservation, so it earns an explicit stop here that the synapse-rebuild-diff skill deliberately does
62
+ not have.
63
+
64
+ **Get an explicit yes before continuing.** This is a hard-to-reverse filesystem operation on content
65
+ that includes irreplaceable human prose — do not proceed past this point on an assumption, even if
66
+ the human is the one who asked for this rebuild in the first place. Asking for it signals intent to
67
+ rebuild; it is not itself confirmation of a delete that touches N nodes with hand-written notes
68
+ attached, which the human hasn't seen a number for yet.
69
+
70
+ ### 3. Wipe
71
+
72
+ Once confirmed:
73
+
74
+ ```sh
75
+ ~/.synapse graph-wipe
76
+ ```
77
+
78
+ This deletes `synapse/{repo}@{branch}/` and, if any node had non-empty `## Notes`, first dumps that
79
+ content verbatim to `scratchpad/{repo}@{branch} — preserved notes before full rebuild.md`. See
80
+ `synapse graph-wipe`'s own header for the exact mechanics (belt-and-braces path check, same
81
+ discipline `synapse graph-clean` uses for its own deletion).
82
+
83
+ ### 4. Rebuild from scratch
84
+
85
+ Run the synapse-init skill's **First-time build** procedure (its steps 1–8) against the now-empty
86
+ namespace, by reference rather than repeating it here — enumerate, read hint files, orientation pass,
87
+ cluster into `manifest.tsv`, gate, write each node, build `_index.bin`, build `Index.md`. Same
88
+ procedure, same judgment calls, nothing rebuild-specific about this phase: from the namespace's
89
+ perspective this is identical to a first build, because as of step 3 it is one.
90
+
91
+ ### 5. Merge preserved notes back
92
+
93
+ Skip this step entirely if step 2 found nothing preserved (no staging note was created).
94
+
95
+ **Only once the new `Index.md` exists** — not during clustering, not node-by-node as nodes are
96
+ written. Read the staging note (`scratchpad/{repo}@{branch} — preserved notes before full rebuild.md`)
97
+ and classify each preserved note's old title + content against the finished new node list, the same
98
+ technique the synapse-init skill's `_unassigned` sweep already uses for classifying files against an
99
+ existing node list: read the note against the new summaries, judge which node it best fits.
100
+
101
+ - **Confident match** → append the note's content into that node's `## Notes` section (every node
102
+ written by `synapse write-node` already carries one, empty if nothing else was there — never a
103
+ "create the section" case) with a one-line provenance breadcrumb: `(carried over from "{old node
104
+ title}" during full rebuild on {date})`. The note is losing its original context by moving to a new
105
+ home, and that breadcrumb is the only way a future reader recovers why it's there.
106
+
107
+ **Report the placement even though it succeeded.** Say which old node's notes went to which new
108
+ node, for every single one, not only the ones that failed to place. A wrong auto-placement is most
109
+ dangerous exactly when it's silent — this is the one class of content in the whole system marked
110
+ irreplaceable, and "it succeeded" is not the same claim as "it succeeded correctly."
111
+ - **No confident match** → leave it in the staging note, and say why: no equivalent concept survived
112
+ the re-cluster, or more than one new node looked equally plausible. Do not guess past a stated
113
+ uncertainty here — a note in the wrong node is worse than a note sitting in scratchpad waiting for a
114
+ human to place it.
115
+
116
+ Once every preserved note has been classified: if every one found a confident home, delete the
117
+ staging note — nothing is left needing manual attention. If any remain unplaced, leave the staging
118
+ note live containing only the leftovers, trimmed of everything that did get merged.
119
+
120
+ ### 6. Report
121
+
122
+ - Old node count vs. new node count.
123
+ - Whether any notes were preserved, and the outcome of every single one from step 5 (merged where, or
124
+ left for manual placement and why) — not just a summary count.
125
+ - If the staging note still exists, say so explicitly and give its path — it needs a human look.
126
+
127
+ ## Guardrails
128
+
129
+ - **Never wipe without running `--dry-run` first and getting explicit confirmation on its output.**
130
+ The preview step exists specifically so "how many notes are about to be deleted" is answered before
131
+ it happens, not after.
132
+ - **Never invent a placement for a preserved note that isn't a confident match.** Leaving it in
133
+ scratchpad, flagged, is the correct outcome when nothing else is — don't fill the gap with a guess
134
+ to make the report look cleaner.
135
+ - **Never merge into a node's generated region.** The merge target is always `## Notes`, appended, never
136
+ touching anything inside the `<!-- synapse:generated:start -->`…`<!-- synapse:generated:end -->`
137
+ fence — that region belongs to `synapse write-node` alone.
138
+ - **Never treat this as the default repair path.** The synapse-rebuild-diff skill is cheaper,
139
+ preserves every node rather than deleting them, and is the right tool for ordinary drift. Use this
140
+ skill when triage genuinely isn't worth it, not as a heavier habit that replaces the lighter one.
141
+
142
+ ## Integration
143
+
144
+ - Delegates the actual rebuild to the synapse-init skill's First-time-build procedure — this skill
145
+ owns only the wipe-with-preservation step before it and the note-merge step after it.
146
+ - The wipe itself is `synapse graph-wipe` (via `synapse graph-wipe`), mirroring
147
+ `synapse graph-clean` as the only other destructive tool in the system.
148
+ - Resolves the namespace the same way the synapse-init and synapse-rebuild-diff skills do -- one
149
+ chain, in `core/identity.zig` — never re-derives repo/branch/remote independently.
@@ -0,0 +1,146 @@
1
+ ---
2
+ name: synapse-status
3
+ description: Print a read-only report of what in Synapse Vault currently needs a human decision -- design notes still Discussing, design notes marked Ready with no compiled task yet, design notes with open questions, task notes with unchecked items, and task notes stuck in REVIEW. Use whenever the user wants a status check on the vault ("what's outstanding", "what needs my attention", "vault status", "what did we leave open"). Never modifies anything -- a report only, not a task-management action. Not for creating, continuing, or listing a specific note kind (that's the synapse-note/synapse-design-note/synapse-task-note skills' own --list modes) -- this is the one cross-cutting view over all of them at once.
4
+ ---
5
+
6
+ # Synapse Status: Vault-Wide Attention Report
7
+
8
+ A read-only sweep over Synapse Vault answering one question: what currently needs a human decision?
9
+ Five categories, one pass, printed as plain chat text -- an org-agenda-style check-in, not a document
10
+ to hand to someone else and not a live dashboard (a published Artifact has no route to the local
11
+ Obsidian REST API, so nothing here is ever presented that way). Run it whenever the user asks for a
12
+ vault status check, or on a recurring schedule the user has set up themselves -- never wired into
13
+ session start: two of the five categories need a per-note body read, not just a frontmatter check,
14
+ and unlike the vault's own index note this report is a periodic human check-in, not something the
15
+ agent needs injected every session to behave correctly.
16
+
17
+ Scoped to `designs/`/`tasks/` only -- the two folders design-note creation and task-note creation
18
+ structurally require, so every Synapse install has them in the same shape. The free-form taxonomy
19
+ (`inbox/`/`research/`/`scratchpad/`) is per-install customizable (see the vault's index note), not
20
+ guaranteed to exist or mean the same thing across installs, and `inbox/` specifically is for the
21
+ vault owner's own periodic look on their own schedule -- this report doesn't cover it.
22
+
23
+ ## When this runs
24
+
25
+ Invoked whenever the user wants a status check on the vault ("what's outstanding", "what needs my
26
+ attention", "vault status", "what did we leave open") -- there is no argument to parse, every run
27
+ produces the same five-category sweep.
28
+
29
+ ## Prerequisites
30
+
31
+ Requires the `obsidian` MCP server (`mcp__obsidian__*` tools). If unreachable, say so and stop --
32
+ there is no local-file fallback.
33
+
34
+ ## Producing the report
35
+
36
+ Run all five queries in parallel where the tool call shape allows it; none depends on another's
37
+ result.
38
+
39
+ **1. Design notes still `Discussing`.** Design notes carry status in-body under `## Status`, not in
40
+ frontmatter (unlike task notes) -- a content match, scoped to `designs/`:
41
+
42
+ ```
43
+ {"and": [
44
+ {"glob": ["designs/*", {"var": "path"}]},
45
+ {"regexp": ["## Status\\nDiscussing", {"var": "content"}]}
46
+ ]}
47
+ ```
48
+
49
+ **2. `Ready` design notes with no compiled task yet.** The design-compilation skill's own "Linking
50
+ back" step patches a compiled design note with a `> Compiled task: [[...]]` line right after its
51
+ title -- "`Ready` and missing that line" is a direct signal, not fuzzy title-matching against
52
+ `tasks/`:
53
+
54
+ ```
55
+ {"and": [
56
+ {"glob": ["designs/*", {"var": "path"}]},
57
+ {"regexp": ["## Status\\nReady", {"var": "content"}]},
58
+ {"!": [{"regexp": ["Compiled task:", {"var": "content"}]}]}
59
+ ]}
60
+ ```
61
+
62
+ **3. Design notes (any status) with a non-empty `## Open Questions`.** Match the heading followed by
63
+ at least one bullet -- a heading with nothing under it (fully pruned, per the design-note skill's own
64
+ Ready-gate convention) doesn't count as open. Since this section spans every status, each line in the
65
+ composed report also shows *which* status the note is currently in:
66
+
67
+ ```
68
+ {"and": [
69
+ {"glob": ["designs/*", {"var": "path"}]},
70
+ {"regexp": ["## Open Questions\\n- ", {"var": "content"}]}
71
+ ]}
72
+ ```
73
+
74
+ `regexp` is boolean-only (no captured groups), so getting each match's actual status needs a second
75
+ pass. Run the same query three more times, `and`-ed with `{"regexp": ["## Status\\nDiscussing", ...]}`
76
+ / `Ready` / `Reference` respectively, to sort the matches from the first query into the three known
77
+ statuses without a body read. **A design note written before `## Status` was standardized on those
78
+ exact three words can carry free text there instead** (e.g. `Superseded by [[...]]`) -- it matches
79
+ the first query but none of the three status-scoped ones. Whatever's left over after removing the
80
+ Discussing/Ready/Reference matches from the first query's full result set is exactly this case:
81
+ report those under a fourth bucket, "Other", rather than silently dropping them -- surfacing an odd
82
+ note beats losing it, the same reasoning behind reporting a 0-unchecked task instead of hiding it
83
+ (see Query 4 below).
84
+
85
+ **4. Open task notes with at least one unchecked item.** Task notes carry `status:` in frontmatter,
86
+ unlike design notes -- filter there first:
87
+
88
+ ```
89
+ {"in": [{"var": "frontmatter.status"}, ["TODO", "IN-PROGRESS"]]}
90
+ ```
91
+
92
+ Then `mcp__obsidian__vault_read` each match and count `- [ ]` lines in the body. A match with zero
93
+ unchecked lines (a checklist that's fully checked but hasn't been promoted to `REVIEW` yet) is still
94
+ worth surfacing -- report it under this section with its count shown as 0, rather than silently
95
+ dropping it, since that state itself is worth a human noticing.
96
+
97
+ **5. Task notes stuck in `REVIEW`.** Frontmatter-only, no body read needed -- a fully-checked
98
+ checklist waiting specifically on human sign-off, since the task-status skill deliberately never
99
+ promotes a note past `REVIEW` on its own:
100
+
101
+ ```
102
+ {"==": [{"var": "frontmatter.status"}, "REVIEW"]}
103
+ ```
104
+
105
+ ## Composing the report
106
+
107
+ One section per category, in the order above. Each line names the note (title, or filename if no
108
+ `title` frontmatter) plus the one identifying detail that category needs. The Open Questions section
109
+ is the one place a note's status also belongs on the line -- every other section's heading already
110
+ implies it (the "Discussing" section only ever holds `Discussing` notes), but Open Questions spans
111
+ every status (`Discussing`/`Ready`/`Reference`/the "Other" catch-all from Query 3 above), so put the
112
+ status first, before the title, so it's the first thing scanned:
113
+
114
+ ```
115
+ ## Discussing
116
+ - {title}
117
+
118
+ ## Ready, not yet compiled
119
+ - {title}
120
+
121
+ ## Open questions
122
+ - **{status}** — {title}
123
+
124
+ ## In progress (unchecked items)
125
+ - {title} ({N} unchecked)
126
+
127
+ ## Awaiting review
128
+ - {title}
129
+ ```
130
+
131
+ Omit a section entirely when it has zero matches -- matching the note-listing skill's own convention
132
+ of leaving out empty headers -- rather than printing five headers with nothing under most of them. If
133
+ every category is empty, say so in one line ("Vault is clear -- nothing outstanding.") instead of five
134
+ empty headers.
135
+
136
+ Print the report directly in the response, not left only in tool-call output the user would have to
137
+ go dig for.
138
+
139
+ ## Constraints
140
+
141
+ - Read-only end to end. Never calls `vault_write`/`vault_patch`/`vault_move`/`vault_delete`/
142
+ `vault_copy` -- if a step here ever seems to need one, that step is out of scope, not a case to
143
+ special-case around.
144
+ - No Artifact/web-UI output. No session-start wiring. Not a new binary CLI subcommand -- every
145
+ category above is a plain mechanical query already reachable through `mcp__obsidian__*` tools.
146
+ - Scoped to `designs/`/`tasks/` only -- never `inbox/`/`research/`/`scratchpad/`.