@imunitic/synapse 0.1.2 → 0.2.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/bin/synapse-setup.cjs +1 -1
- package/commands/synapse-design-note.md +20 -12
- package/commands/synapse-init.md +3 -3
- package/commands/synapse-note.md +12 -13
- package/commands/synapse-rebuild-full.md +1 -1
- package/commands/synapse-status.md +44 -32
- package/commands/synapse-task-note.md +13 -6
- package/commands/synapse-vault-tidy.md +1 -1
- package/harness/claude/hooks.json +1 -1
- package/harness/codex/hooks.json +1 -1
- package/harness/codex/skills/synapse-design-note/SKILL.md +19 -11
- package/harness/codex/skills/synapse-init/SKILL.md +3 -3
- package/harness/codex/skills/synapse-note/SKILL.md +11 -11
- package/harness/codex/skills/synapse-rebuild-full/SKILL.md +1 -1
- package/harness/codex/skills/synapse-status/SKILL.md +48 -37
- package/harness/codex/skills/synapse-task-note/SKILL.md +13 -6
- package/harness/codex/skills/synapse-vault-tidy/SKILL.md +1 -1
- package/harness/opencode/plugin/synapse.js +25 -5
- package/lib/obsidian-mcp-refresh.cjs +3 -3
- package/package.json +4 -4
- package/skills/synapse-node/SKILL.md +5 -5
- package/skills/synapse-query/SKILL.md +2 -2
- package/skills/synapse-task/SKILL.md +36 -41
- package/skills/synapse-vault/SKILL.md +45 -37
- package/synapse-claude.md +48 -48
- package/synapse.conf.template +1 -1
|
@@ -7,12 +7,12 @@ description: Print a read-only report of what in Synapse Vault currently needs a
|
|
|
7
7
|
|
|
8
8
|
A read-only sweep over Synapse Vault answering one question: what currently needs a human decision?
|
|
9
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
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
10
|
+
to hand to someone else and not a live dashboard (a published Artifact has no route to the vault, so
|
|
11
|
+
nothing here is ever presented that way). Run it whenever the user asks for a vault status check, or
|
|
12
|
+
on a recurring schedule the user has set up themselves -- never wired into session start: some of the
|
|
13
|
+
five categories need a per-note content read, not just a frontmatter check, and unlike the vault's
|
|
14
|
+
own index note this report is a periodic human check-in, not something the agent needs injected every
|
|
15
|
+
session to behave correctly.
|
|
16
16
|
|
|
17
17
|
Scoped to `designs/`/`tasks/` only -- the two folders design-note creation and task-note creation
|
|
18
18
|
structurally require, so every Synapse install has them in the same shape. The free-form taxonomy
|
|
@@ -28,78 +28,91 @@ produces the same five-category sweep.
|
|
|
28
28
|
|
|
29
29
|
## Prerequisites
|
|
30
30
|
|
|
31
|
-
Requires the `
|
|
32
|
-
|
|
31
|
+
Requires the `synapse` CLI on `PATH`. Backend-agnostic -- every query below goes through `synapse
|
|
32
|
+
vault-search`, so it works the same whether the vault is Obsidian-backed or plain-disk-backed. If the
|
|
33
|
+
CLI errors (no vault configured), say so and stop -- there is no other fallback.
|
|
33
34
|
|
|
34
35
|
## Producing the report
|
|
35
36
|
|
|
36
|
-
Run
|
|
37
|
-
|
|
37
|
+
Run each query below via `synapse vault-search --fields <fields>`, with the JsonLogic query on stdin.
|
|
38
|
+
`--fields` asks for exactly the columns the query needs back -- no separate read per match required,
|
|
39
|
+
since a matched row already carries them.
|
|
38
40
|
|
|
39
41
|
**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
|
|
42
|
+
frontmatter (unlike task notes) -- a content match, scoped to `designs/`. Needs only the title:
|
|
41
43
|
|
|
42
44
|
```
|
|
45
|
+
synapse vault-search --fields frontmatter.title <<'EOF'
|
|
43
46
|
{"and": [
|
|
44
47
|
{"glob": ["designs/*", {"var": "path"}]},
|
|
45
48
|
{"regexp": ["## Status\\nDiscussing", {"var": "content"}]}
|
|
46
49
|
]}
|
|
50
|
+
EOF
|
|
47
51
|
```
|
|
48
52
|
|
|
49
53
|
**2. `Ready` design notes with no compiled task yet.** The design-compilation skill's own "Linking
|
|
50
54
|
back" step patches a compiled design note with a `> Compiled task: [[...]]` line right after its
|
|
51
55
|
title -- "`Ready` and missing that line" is a direct signal, not fuzzy title-matching against
|
|
52
|
-
`tasks
|
|
56
|
+
`tasks/`. Needs only the title:
|
|
53
57
|
|
|
54
58
|
```
|
|
59
|
+
synapse vault-search --fields frontmatter.title <<'EOF'
|
|
55
60
|
{"and": [
|
|
56
61
|
{"glob": ["designs/*", {"var": "path"}]},
|
|
57
62
|
{"regexp": ["## Status\\nReady", {"var": "content"}]},
|
|
58
63
|
{"!": [{"regexp": ["Compiled task:", {"var": "content"}]}]}
|
|
59
64
|
]}
|
|
65
|
+
EOF
|
|
60
66
|
```
|
|
61
67
|
|
|
62
68
|
**3. Design notes (any status) with a non-empty `## Open Questions`.** Match the heading followed by
|
|
63
69
|
at least one bullet -- a heading with nothing under it (fully pruned, per the design-note skill's own
|
|
64
70
|
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
|
|
71
|
+
composed report also shows *which* status the note is currently in -- request `content` too, and pull
|
|
72
|
+
the status directly out of the returned text rather than reading the note again:
|
|
66
73
|
|
|
67
74
|
```
|
|
75
|
+
synapse vault-search --fields frontmatter.title,content <<'EOF'
|
|
68
76
|
{"and": [
|
|
69
77
|
{"glob": ["designs/*", {"var": "path"}]},
|
|
70
78
|
{"regexp": ["## Open Questions\\n- ", {"var": "content"}]}
|
|
71
79
|
]}
|
|
80
|
+
EOF
|
|
72
81
|
```
|
|
73
82
|
|
|
74
|
-
`regexp`
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
report
|
|
82
|
-
|
|
83
|
-
|
|
83
|
+
Do not chain further `and`/`regexp` conditions onto this query to sort matches by status -- compound
|
|
84
|
+
`regexp` conditions against `content` have been observed to return results that are logically
|
|
85
|
+
impossible (a query with strictly more AND-ed conditions returning *more* matches than one with
|
|
86
|
+
fewer), so any query beyond a single `glob` + single `regexp` pair is unverified and not to be
|
|
87
|
+
trusted here. Instead, take the line following `## Status` directly out of each row's `content`
|
|
88
|
+
column -- normally `Discussing`/`Ready`/`Reference`, but a design note written before that
|
|
89
|
+
three-word convention was standardized can carry free text there instead (e.g. `Superseded by
|
|
90
|
+
[[...]]`); report that verbatim rather than forcing it into a bucket, surfacing an odd note beats
|
|
91
|
+
losing it, the same reasoning behind reporting a 0-unchecked task instead of hiding it (see Query 4
|
|
92
|
+
below).
|
|
84
93
|
|
|
85
94
|
**4. Open task notes with at least one unchecked item.** Task notes carry `status:` in frontmatter,
|
|
86
|
-
unlike design notes -- filter there first
|
|
95
|
+
unlike design notes -- filter there first, and request `content` to count `- [ ]` lines directly from
|
|
96
|
+
the row rather than reading each match again:
|
|
87
97
|
|
|
88
98
|
```
|
|
99
|
+
synapse vault-search --fields frontmatter.title,content <<'EOF'
|
|
89
100
|
{"in": [{"var": "frontmatter.status"}, ["TODO", "IN-PROGRESS"]]}
|
|
101
|
+
EOF
|
|
90
102
|
```
|
|
91
103
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
dropping it, since that state itself is worth a human noticing.
|
|
104
|
+
A match with zero unchecked lines (a checklist that's fully checked but hasn't been promoted to
|
|
105
|
+
`REVIEW` yet) is still worth surfacing -- report it under this section with its count shown as 0,
|
|
106
|
+
rather than silently dropping it, since that state itself is worth a human noticing.
|
|
96
107
|
|
|
97
|
-
**5. Task notes stuck in `REVIEW`.** Frontmatter-only, no body
|
|
98
|
-
|
|
99
|
-
|
|
108
|
+
**5. Task notes stuck in `REVIEW`.** Frontmatter-only, no body needed -- a fully-checked checklist
|
|
109
|
+
waiting specifically on human sign-off, since the task-status skill deliberately never promotes a
|
|
110
|
+
note past `REVIEW` on its own:
|
|
100
111
|
|
|
101
112
|
```
|
|
113
|
+
synapse vault-search --fields frontmatter.title <<'EOF'
|
|
102
114
|
{"==": [{"var": "frontmatter.status"}, "REVIEW"]}
|
|
115
|
+
EOF
|
|
103
116
|
```
|
|
104
117
|
|
|
105
118
|
## Composing the report
|
|
@@ -108,8 +121,7 @@ One section per category, in the order above. Each line names the note (title, o
|
|
|
108
121
|
`title` frontmatter) plus the one identifying detail that category needs. The Open Questions section
|
|
109
122
|
is the one place a note's status also belongs on the line -- every other section's heading already
|
|
110
123
|
implies it (the "Discussing" section only ever holds `Discussing` notes), but Open Questions spans
|
|
111
|
-
every status
|
|
112
|
-
status first, before the title, so it's the first thing scanned:
|
|
124
|
+
every status, so put the status first, before the title, so it's the first thing scanned:
|
|
113
125
|
|
|
114
126
|
```
|
|
115
127
|
## Discussing
|
|
@@ -138,9 +150,8 @@ go dig for.
|
|
|
138
150
|
|
|
139
151
|
## Constraints
|
|
140
152
|
|
|
141
|
-
- Read-only end to end. Never calls `
|
|
142
|
-
|
|
143
|
-
special-case around.
|
|
153
|
+
- Read-only end to end. Never calls `vault-write`/`vault-patch` -- if a step here ever seems to need
|
|
154
|
+
one, that step is out of scope, not a case to special-case around.
|
|
144
155
|
- 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 `
|
|
156
|
+
category above is a plain mechanical query already reachable through `synapse vault-search`.
|
|
146
157
|
- Scoped to `designs/`/`tasks/` only -- never `inbox/`/`research/`/`scratchpad/`.
|
|
@@ -89,12 +89,19 @@ don't duplicate that scaffolding here, just supply its inputs:
|
|
|
89
89
|
|
|
90
90
|
## Linking back
|
|
91
91
|
|
|
92
|
-
After creation, patch the design note: add `> Compiled task: [[{task note title}]]` near the top
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
92
|
+
After creation, patch the design note: add `> Compiled task: [[{task note title}]]` near the top —
|
|
93
|
+
right after the `# {title}` heading, before `## Status`:
|
|
94
|
+
|
|
95
|
+
```
|
|
96
|
+
printf '> Compiled task: [[%s]]\n\n' "{task note title}" | \
|
|
97
|
+
synapse vault-patch "{design note path}" --heading "{design note title}" --prepend
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
A small annotation line, same idea as any other cross-reference you'd drop near a note's title. This
|
|
101
|
+
is a one-time link; the design note's `Status` itself does not change and does not need to track the
|
|
102
|
+
task's progress — a plain `synapse vault-search-text` for the task note's title already surfaces the
|
|
103
|
+
live task note from the design note, and the task's `status:` frontmatter is the authoritative
|
|
104
|
+
progress signal from here on.
|
|
98
105
|
|
|
99
106
|
## Confirm
|
|
100
107
|
|
|
@@ -90,7 +90,7 @@ instead of a silent edit:
|
|
|
90
90
|
local git history to say *why* it's broken instead of leaving that to guesswork — the vault is
|
|
91
91
|
usually a git repo (a db-sync hook auto-commits every agent-driven edit into it, opt-in per vault,
|
|
92
92
|
same precondition as that hook). Resolve the vault's filesystem path the same way its own conf
|
|
93
|
-
file already does (`
|
|
93
|
+
file already does (`SYNAPSE_VAULT_DIR`), skip this sub-step entirely if `{vault}/.git` doesn't
|
|
94
94
|
exist, and never let a missing/unreachable git repo block the rest of the finding — worst case
|
|
95
95
|
it's reported with no history context, same as today.
|
|
96
96
|
- `git -C {vault} log --all --diff-filter=A --name-only --pretty=format: -- "**/{target}.md"` — a
|
|
@@ -19,12 +19,21 @@
|
|
|
19
19
|
// edit: `write` (`args.filePath`, `args.content`) and `edit` (`args.filePath`,
|
|
20
20
|
// `args.oldString`, `args.newString`) -- both share `filePath`, matching
|
|
21
21
|
// Claude Code's `Write`/`Edit` sharing `tool_input.file_path`. `db_sync.zig`
|
|
22
|
-
// itself
|
|
23
|
-
//
|
|
24
|
-
// fired on the same write/edit tools,
|
|
25
|
-
// (`obsidian_vault_write`/`obsidian_vault_patch`, the same OpenCode
|
|
22
|
+
// itself is a blind `git add -A && commit` against the vault's own repo, so
|
|
23
|
+
// this plugin only needs to decide *whether* to fire it, not translate a
|
|
24
|
+
// payload -- fired on the same write/edit tools, the two vault-mutating MCP
|
|
25
|
+
// tools (`obsidian_vault_write`/`obsidian_vault_patch`, the same OpenCode
|
|
26
26
|
// tool-naming transform `mcp__obsidian__vault_write`/`vault_patch` go
|
|
27
|
-
// through elsewhere in this plugin's own skill/command prose)
|
|
27
|
+
// through elsewhere in this plugin's own skill/command prose), and `bash`
|
|
28
|
+
// (`args.command`, confirmed against `packages/core/src/tool/bash.ts` in
|
|
29
|
+
// the real `sst/opencode` source) when the command actually names `synapse
|
|
30
|
+
// vault-write`/`vault-patch` -- the CLI door skills use instead of calling
|
|
31
|
+
// the MCP tools directly (see `sb — Vault store backend selection`). Unlike
|
|
32
|
+
// Claude Code's/Codex's `hooks.json`, which can only match a tool *name* and
|
|
33
|
+
// so must widen their own matcher to `Bash` and let `db_sync.zig` filter the
|
|
34
|
+
// command text itself, this plugin has the real args in hand before ever
|
|
35
|
+
// spawning the hook, so it filters right here instead -- no spawn at all for
|
|
36
|
+
// an unrelated `bash` call.
|
|
28
37
|
//
|
|
29
38
|
// `stop-nudge`'s Claude Code trigger (`Stop`, once per turn) maps to
|
|
30
39
|
// `session.idle` -- live-verified as firing exactly once, after every tool
|
|
@@ -104,6 +113,15 @@ async function alreadyInjected(client, sessionID) {
|
|
|
104
113
|
const EDIT_TOOLS = new Set(["write", "edit"])
|
|
105
114
|
const VAULT_WRITE_TOOLS = new Set(["obsidian_vault_write", "obsidian_vault_patch"])
|
|
106
115
|
|
|
116
|
+
// A `bash` call whose command names a `synapse vault-write`/`vault-patch`
|
|
117
|
+
// invocation -- the substring check a real shell quoting/path prefix can't
|
|
118
|
+
// evade in the cases that matter (`synapse vault-write ...`, `/path/to/synapse
|
|
119
|
+
// vault-patch ...`), matching `db_sync.zig`'s own `shouldSkipBash` filter for
|
|
120
|
+
// the harnesses that can't pre-filter on args the way this plugin can.
|
|
121
|
+
function isVaultWriteCommand(command) {
|
|
122
|
+
return typeof command === "string" && (command.includes("vault-write") || command.includes("vault-patch"))
|
|
123
|
+
}
|
|
124
|
+
|
|
107
125
|
// sessionID -> nudge text from a `stop-nudge` call that had nowhere to land
|
|
108
126
|
// yet -- delivered on that session's next `chat.message`.
|
|
109
127
|
const pendingNudge = new Map()
|
|
@@ -146,6 +164,8 @@ export const Synapse = async ({ directory, client }) => {
|
|
|
146
164
|
runHook("db-sync", {});
|
|
147
165
|
} else if (VAULT_WRITE_TOOLS.has(input.tool)) {
|
|
148
166
|
runHook("db-sync", {});
|
|
167
|
+
} else if (input.tool === "bash" && isVaultWriteCommand(input.args?.command)) {
|
|
168
|
+
runHook("db-sync", {});
|
|
149
169
|
}
|
|
150
170
|
},
|
|
151
171
|
|
|
@@ -130,7 +130,7 @@ function confExpand(raw) {
|
|
|
130
130
|
|
|
131
131
|
// Mirrors core/conf.zig's `resolveExisting` (tiers 1-2 only -- this hook,
|
|
132
132
|
// like the shell script it replaces, never falls back to the plugin's own
|
|
133
|
-
// bundled template: that template's
|
|
133
|
+
// bundled template: that template's SYNAPSE_VAULT_DIR is a placeholder
|
|
134
134
|
// path, not a real vault, and silently trying it would be worse than doing
|
|
135
135
|
// nothing).
|
|
136
136
|
function resolveConfPath(name) {
|
|
@@ -150,7 +150,7 @@ function resolveConfPath(name) {
|
|
|
150
150
|
// conf file name in turn (synapse.conf, then the pre-rename second-brain.conf
|
|
151
151
|
// as a fallback), each resolved through the same tier order.
|
|
152
152
|
function resolveVaultDir() {
|
|
153
|
-
const override = process.env.
|
|
153
|
+
const override = process.env.SYNAPSE_VAULT_DIR;
|
|
154
154
|
if (override) return override;
|
|
155
155
|
|
|
156
156
|
for (const name of ["synapse.conf", "second-brain.conf"]) {
|
|
@@ -162,7 +162,7 @@ function resolveVaultDir() {
|
|
|
162
162
|
} catch {
|
|
163
163
|
continue;
|
|
164
164
|
}
|
|
165
|
-
const raw = confGet(text, "
|
|
165
|
+
const raw = confGet(text, "SYNAPSE_VAULT_DIR");
|
|
166
166
|
if (raw === null) continue;
|
|
167
167
|
const expanded = confExpand(raw);
|
|
168
168
|
if (expanded) return expanded;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@imunitic/synapse",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Memory for Claude Code, Codex CLI, and OpenCode: a durable Obsidian vault plus a per-repo code graph.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -22,9 +22,9 @@
|
|
|
22
22
|
"*.conf.template"
|
|
23
23
|
],
|
|
24
24
|
"optionalDependencies": {
|
|
25
|
-
"@imunitic/synapse-darwin-arm64": "0.
|
|
26
|
-
"@imunitic/synapse-linux-x64": "0.
|
|
27
|
-
"@imunitic/synapse-linux-arm64": "0.
|
|
25
|
+
"@imunitic/synapse-darwin-arm64": "0.2.0",
|
|
26
|
+
"@imunitic/synapse-linux-x64": "0.2.0",
|
|
27
|
+
"@imunitic/synapse-linux-arm64": "0.2.0"
|
|
28
28
|
},
|
|
29
29
|
"license": "SEE LICENSE IN LICENSE"
|
|
30
30
|
}
|
|
@@ -38,7 +38,7 @@ needs one.
|
|
|
38
38
|
namespace for this repo, or a `remote:` mismatch. Do not treat that as a passing verification —
|
|
39
39
|
either fix the cause or proceed knowing the graph is unverified, and say which.
|
|
40
40
|
|
|
41
|
-
2. **Also honour the Tier 1 flag.** `rg -m1 '^stale:' "$
|
|
41
|
+
2. **Also honour the Tier 1 flag.** `rg -m1 '^stale:' "$SYNAPSE_VAULT_DIR/synapse/{project}/{Node}.md"`
|
|
42
42
|
— one line, negligible cost. The two tiers catch different things: Tier 1 flags edits made through
|
|
43
43
|
this Claude Code session the moment they happen, the script catches everything including changes
|
|
44
44
|
the hook never saw (`git pull`, branch switch, rebase, an IDE edit). **Either one saying stale
|
|
@@ -55,13 +55,13 @@ needs one.
|
|
|
55
55
|
|
|
56
56
|
That prints only what is between the generated fences — so it excludes `## Notes` as well as the
|
|
57
57
|
frontmatter, which a raw offset read would not — and costs ~500 tokens whether the node covers 5
|
|
58
|
-
files or 941. **A full `
|
|
58
|
+
files or 941. **A full `synapse vault-read`
|
|
59
59
|
of a hub node is a mistake, not merely expensive** — it spends tens of thousands of tokens on a
|
|
60
|
-
path list you are not going to use. Use `
|
|
61
|
-
frontmatter
|
|
60
|
+
path list you are not going to use. Use `vault-read` only when you specifically need the
|
|
61
|
+
frontmatter itself.
|
|
62
62
|
|
|
63
63
|
Finding *which* node to read is a separate job, and search does it: because `sources` is
|
|
64
|
-
exhaustive, `
|
|
64
|
+
exhaustive, `synapse vault-search-text` on a class or file name locates the owning node even
|
|
65
65
|
when that name appears nowhere in any node's prose, and returns snippets rather than whole files.
|
|
66
66
|
|
|
67
67
|
For the other questions about a node, use the same tool rather than reading frontmatter:
|
|
@@ -47,7 +47,7 @@ required:
|
|
|
47
47
|
|
|
48
48
|
| Need | Reach for | Why |
|
|
49
49
|
|---|---|---|
|
|
50
|
-
| "Where does X live?" (ranked, natural-language) | `
|
|
50
|
+
| "Where does X live?" (ranked, natural-language) | `synapse vault-search-text`/`vault-search` over the vault, plus a first read of `synapse/{project}/Index.md` | Full-text, relevance-ranked. Not semantic ranking, but genuinely comparable for locating a concept. |
|
|
51
51
|
| Every occurrence of a pattern | native `grep`/`rg`, **scoped to a file Synapse already named** | Not a repo-wide first move — the deterred, last-resort case. See "Why this exists" above. |
|
|
52
52
|
| A file's API surface | read the file directly | Claude already has direct, cheap filesystem access — no separate view needed. |
|
|
53
53
|
| Who depends on a subsystem, or what it depends on | `synapse query links "{Node}" --inbound` / `--closure` | Real transitive-closure traversal over the typed relations, at node granularity. |
|
|
@@ -63,7 +63,7 @@ required:
|
|
|
63
63
|
|---|---|---|
|
|
64
64
|
| Orienting on an unfamiliar repo | `synapse/{project}/Index.md`, then the relevant node's `body` | grepping around to build a mental map by hand |
|
|
65
65
|
| Understanding a flow ("how does X work") | `synapse query body "{Node}"` for the node that covers it | reading every file the flow touches, cold |
|
|
66
|
-
| Finding where a change belongs | `
|
|
66
|
+
| Finding where a change belongs | `vault-search-text`/`Index.md` to find the owning node, then that node's `sources`/`crux_path` for the exact file(s) | a repo-wide grep for a guessed symbol name |
|
|
67
67
|
| Judging blast radius before an edit | `synapse query links "{Node}" --inbound` (or `--closure` for transitive) | assuming nothing else depends on it |
|
|
68
68
|
| You already know the exact file and line range | just fetch it (`sed`, or a direct file read) | asking Synapse a question you can already answer |
|
|
69
69
|
| Finding every occurrence of a literal pattern | native `grep`/`rg`, scoped to files Synapse already named | an unscoped repo-wide grep before consulting Synapse at all |
|
|
@@ -52,10 +52,11 @@ into `status:` either — always go through this skill, which caps at
|
|
|
52
52
|
|
|
53
53
|
## Procedure
|
|
54
54
|
|
|
55
|
-
1. Find the task note: `
|
|
56
|
-
`{"==": [{"var": "frontmatter.task_id"}, "<task-id>"]}
|
|
55
|
+
1. Find the task note: `synapse vault-search --fields content` with
|
|
56
|
+
`{"==": [{"var": "frontmatter.task_id"}, "<task-id>"]}` on stdin, where `<task-id>`
|
|
57
57
|
is the specific task's ID (whatever prefix it uses — `proj-035`, `sb-008`,
|
|
58
|
-
etc.)
|
|
58
|
+
etc.) — one call returns the matched note's path and full body together, no
|
|
59
|
+
separate read needed.
|
|
59
60
|
2. Inspect its checklist items (`- [ ]` / `- [x]`).
|
|
60
61
|
3. Determine the new `status:` value: `IN-PROGRESS` if any unchecked,
|
|
61
62
|
`REVIEW` if all checked.
|
|
@@ -67,44 +68,38 @@ into `status:` either — always go through this skill, which caps at
|
|
|
67
68
|
changes exactly that one line and nothing else, entirely inside the
|
|
68
69
|
compiled binary — the note's body never enters your context at all.
|
|
69
70
|
When the command isn't available, fall back to **read-modify-write**:
|
|
70
|
-
`
|
|
71
|
-
`
|
|
71
|
+
`synapse vault-read` the file, replace the one line in the returned content,
|
|
72
|
+
`synapse vault-write` the whole file back — byte-preserving, because you write
|
|
72
73
|
back what you read.
|
|
73
74
|
|
|
74
|
-
|
|
75
|
-
field-local
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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.
|
|
75
|
+
`synapse vault-patch <path> --frontmatter <key> --replace` is also byte-preserving now (it
|
|
76
|
+
delegates to the same field-local mechanism `frontmatter set` uses internally), unlike the old
|
|
77
|
+
Obsidian MCP tool of the same shape — but it only ever writes a plain scalar, and it's a full
|
|
78
|
+
read-apply-write round trip through the patch layer for one field. `frontmatter set` stays the
|
|
79
|
+
right tool for this step: narrower, and the one call that exists specifically for it.
|
|
85
80
|
6. **For completion only:** append implementation bullets to the existing
|
|
86
|
-
`## Notes` section with `
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
`
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
heading"
|
|
103
|
-
subheadings (including `## Notes`), not just
|
|
104
|
-
directly under it — a replace there silently deletes everything
|
|
105
|
-
checklist, including the Notes section. To check off checklist items,
|
|
106
|
-
instead `
|
|
107
|
-
returned content, and `
|
|
81
|
+
`## Notes` section with `synapse vault-patch`:
|
|
82
|
+
|
|
83
|
+
```
|
|
84
|
+
printf '%s\n' "- bullet one" "- bullet two" | \
|
|
85
|
+
synapse vault-patch "{path}" --heading "{H1 title}::Notes" --append --create
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Append is safe here too. **The target must be the full nested path** (`H1::Notes`), not just
|
|
89
|
+
`"Notes"`: since `## Notes` is nested under the top-level `# {title}` heading, a bare leaf name
|
|
90
|
+
fails with "target not found in {path}" -- check `synapse vault-doc-map <path>` rather than
|
|
91
|
+
guessing. `--create` creates the section (and any missing parent) if it doesn't exist yet.
|
|
92
|
+
|
|
93
|
+
The vault-patch hazards below are the task-note-specific instance of a general rule; the
|
|
94
|
+
`synapse-vault` skill carries the full list (H1 replace, nested heading paths, frontmatter) for
|
|
95
|
+
every note, not just task notes.
|
|
96
|
+
|
|
97
|
+
**Do not use `vault-patch --heading "{H1 title}" --replace` to edit checklist items.** A top-level
|
|
98
|
+
heading's own section extends through *all* nested subheadings (including `## Notes`), not just
|
|
99
|
+
the leading paragraph/checklist directly under it — a replace there silently deletes everything
|
|
100
|
+
past the checklist, including the Notes section. To check off checklist items,
|
|
101
|
+
instead `synapse vault-read` the full file, edit the `- [ ]` → `- [x]` lines in the
|
|
102
|
+
returned content, and `synapse vault-write` the whole file back.
|
|
108
103
|
|
|
109
104
|
## Notes format
|
|
110
105
|
|
|
@@ -245,13 +240,13 @@ repos/<owner>/<repo>/issues/<n>/comments`).
|
|
|
245
240
|
commit/push — a prior "commit and push" is not a standing license for the
|
|
246
241
|
next one, and setting `DONE` is itself a human action this skill never
|
|
247
242
|
takes (see "Never write `DONE`" above).
|
|
248
|
-
- **Never create a task note via a bare `
|
|
243
|
+
- **Never create a task note via a bare `vault-write`.** Always
|
|
249
244
|
`/synapse-note --task`, or `/synapse-task-note` when compiling one from a
|
|
250
245
|
`Ready` design note. A freeform write skips both the skeleton (checklist
|
|
251
246
|
items plus a single `## Notes` section) and every guardrail in this file —
|
|
252
247
|
there is no partial-credit version of following this skill.
|
|
253
|
-
- **Never restructure an existing task note's shape via `
|
|
254
|
-
`
|
|
248
|
+
- **Never restructure an existing task note's shape via `vault-write`/
|
|
249
|
+
`vault-patch` outside this skill's own procedure.** Ad hoc edits that add
|
|
255
250
|
new headings, drop the checklist, or otherwise diverge from the skeleton
|
|
256
251
|
are what this guardrail exists to prevent — not just wrong `status:`
|
|
257
252
|
values.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
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
|
|
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
4
|
---
|
|
5
5
|
|
|
6
6
|
# Synapse Vault: search it first, and do not destroy it
|
|
@@ -27,12 +27,18 @@ Search before you answer when the question is about:
|
|
|
27
27
|
- anything that starts "I think it works like…"
|
|
28
28
|
|
|
29
29
|
```
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
30
|
+
synapse vault-search-text <query> full-text, relevance-ranked, with match context
|
|
31
|
+
synapse vault-search --fields <f1,f2,...> JsonLogic over frontmatter, tags, content, path globs
|
|
32
|
+
synapse vault-list when you already know roughly where it is
|
|
33
|
+
synapse vault-read <path> the whole note -- no partial/targeted read exists
|
|
34
34
|
```
|
|
35
35
|
|
|
36
|
+
`vault-search`'s JsonLogic filter and `--fields` projection both stop at frontmatter/tags/content/
|
|
37
|
+
path -- there is no `links`/`backlinks`/`unresolvedLinks` equivalent (no whole-vault wikilink index
|
|
38
|
+
exists yet), and `vault-read` always returns the full body, never one section on its own -- request
|
|
39
|
+
exactly what you need with `--fields` instead of over-fetching, and pull a specific section out of
|
|
40
|
+
`vault-read`'s output locally when that's all you actually need.
|
|
41
|
+
|
|
36
42
|
A search that returns nothing is a real result and worth one line in the note you then write —
|
|
37
43
|
a negative result cannot be rediscovered by searching for it.
|
|
38
44
|
|
|
@@ -40,37 +46,39 @@ a negative result cannot be rediscovered by searching for it.
|
|
|
40
46
|
|
|
41
47
|
Every item below returns success.
|
|
42
48
|
|
|
43
|
-
- **`
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
between the title and the first H2 — a status or
|
|
47
|
-
that region — use
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
frontmatter
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
-
|
|
72
|
-
|
|
73
|
-
|
|
49
|
+
- **`vault-patch --heading "{title}" --replace` targeting the H1 replaces the entire document.**
|
|
50
|
+
A heading target's `content` scope is "everything below this heading's own line, until the next
|
|
51
|
+
heading at the same or shallower level" -- below a top-level H1 there is nothing shallower, so
|
|
52
|
+
that scope is the whole note. To insert between the title and the first H2 — a status or
|
|
53
|
+
metadata blockquote, the usual reason to touch that region — use **`--prepend` against the H1**.
|
|
54
|
+
Never `--replace` there.
|
|
55
|
+
- **Heading targets are nested paths.** An H2 under the H1 is `"{H1 title}::{H2 title}"`; a bare
|
|
56
|
+
`"{H2 title}"` fails with "target not found in {path}", which reads like the heading is missing
|
|
57
|
+
rather than like the path is wrong. `synapse vault-doc-map <path>` returns the exact `::` paths
|
|
58
|
+
(and every block id and frontmatter key too), so check it rather than guessing a path.
|
|
59
|
+
- **`--append`/`--prepend`/`--replace` splice `content` in exactly as given, with no relative
|
|
60
|
+
heading-level translation.** Unlike some patch tools, a `#` in `content` is never adjusted
|
|
61
|
+
relative to the target -- if you write `## Notes` in `content`, `## Notes` is what lands, at
|
|
62
|
+
whatever nesting level you typed. `--append` always lands as the last thing in the target's own
|
|
63
|
+
section (right before the next heading at the same level or shallower, or end of document),
|
|
64
|
+
regardless of how deeply nested the immediately preceding content happens to be -- so a wrong
|
|
65
|
+
heading level in your own `content` string is the only way to get this wrong, not an artifact of
|
|
66
|
+
where in the section you're inserting.
|
|
67
|
+
- **`vault-patch --frontmatter <key>` only ever writes a scalar.** It delegates to
|
|
68
|
+
`core.frontmatter.set` internally, so unlike a patch tool that re-serialises the whole YAML
|
|
69
|
+
block, it *is* byte-preserving — every other line survives untouched. But its `content` is
|
|
70
|
+
always written as a plain scalar, never a list, so it's the wrong tool for `tags` specifically.
|
|
71
|
+
Prefer `synapse frontmatter set <path> <key> <value>` (or `--add-tag`/`--remove-tag` for `tags`;
|
|
72
|
+
`synapse frontmatter get <path> <key>` reads one back) regardless — it's the narrower, one-field
|
|
73
|
+
tool this exists for, without a read-then-write round trip through `vault-patch` for a single
|
|
74
|
+
value. Neither tool handles a block-style value; fall back to reading the file, changing the one
|
|
75
|
+
line, and writing the whole file back for that.
|
|
76
|
+
- **Block targets (`--block <id>`) only recognize a single-line block** — a line ending in
|
|
77
|
+
` ^{id}`. A multi-line block has no target grammar here at all; fall back to a heading target
|
|
78
|
+
around it, or a disk-level edit.
|
|
79
|
+
- **The vault path is never hardcoded in a skill.** Every `vault-*` subcommand resolves it itself
|
|
80
|
+
(`SYNAPSE_VAULT_DIR`, same tiered lookup `synapse.conf` uses) — pass a vault-relative path, never
|
|
81
|
+
an absolute one you constructed by hand.
|
|
74
82
|
|
|
75
83
|
## Verify with a structural invariant, not by re-reading what you wrote
|
|
76
84
|
|
|
@@ -94,7 +102,7 @@ The vocabulary is `synapse-tag-vocabulary.conf` — one tag per line, `#`-commen
|
|
|
94
102
|
|
|
95
103
|
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
104
|
|
|
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 `
|
|
105
|
+
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
106
|
|
|
99
107
|
## The graph side of the vault
|
|
100
108
|
|