@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,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/`.
|