@imunitic/synapse 0.1.0-experimental → 0.1.2
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 +52 -13
- package/commands/synapse-init.md +1 -1
- package/commands/synapse-rebuild-diff.md +1 -1
- package/commands/synapse-status.md +19 -16
- package/harness/codex/skills/synapse-init/SKILL.md +1 -1
- package/harness/codex/skills/synapse-rebuild-diff/SKILL.md +1 -1
- package/package.json +4 -4
- package/skills/synapse-node/SKILL.md +1 -1
- package/skills/synapse-vault/SKILL.md +1 -1
package/bin/synapse-setup.cjs
CHANGED
|
@@ -80,10 +80,43 @@ function readObsidianPluginData() {
|
|
|
80
80
|
return data;
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
+
// The manifest recording exactly which names this tool wrote into a given
|
|
84
|
+
// destination on the prior run -- diffed against the current run's names
|
|
85
|
+
// so a renamed or removed skill/command is deleted instead of left behind
|
|
86
|
+
// as an orphan forever. Scoped strictly to names a previous manifest
|
|
87
|
+
// actually listed: anything else living in the same directory (a skill
|
|
88
|
+
// from a different source, or the user's own) was never in that manifest
|
|
89
|
+
// and is never touched. Lives inside `destRoot` itself, as a dotfile --
|
|
90
|
+
// no `.md` extension and not a `{name}/SKILL.md` subdirectory, so no
|
|
91
|
+
// harness's own skill/command discovery mistakes it for one of ours.
|
|
92
|
+
const MANAGED_MANIFEST = ".synapse-managed.json";
|
|
93
|
+
|
|
94
|
+
function pruneStale(destRoot, currentNames) {
|
|
95
|
+
const manifestPath = path.join(destRoot, MANAGED_MANIFEST);
|
|
96
|
+
let previous = [];
|
|
97
|
+
if (fs.existsSync(manifestPath)) {
|
|
98
|
+
try {
|
|
99
|
+
previous = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
|
|
100
|
+
} catch {
|
|
101
|
+
previous = [];
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
const current = new Set(currentNames);
|
|
105
|
+
for (const name of previous) {
|
|
106
|
+
if (current.has(name)) continue;
|
|
107
|
+
fs.rmSync(path.join(destRoot, name), { recursive: true, force: true });
|
|
108
|
+
}
|
|
109
|
+
fs.mkdirSync(destRoot, { recursive: true });
|
|
110
|
+
fs.writeFileSync(manifestPath, JSON.stringify(currentNames, null, 2) + "\n");
|
|
111
|
+
}
|
|
112
|
+
|
|
83
113
|
// Copies every shared SKILL.md into `destRoot/{name}/SKILL.md`, verbatim
|
|
84
114
|
// unless `transform` is given. Each skill gets its own uniquely-named
|
|
85
115
|
// subdirectory, so overwriting ours on every run never touches anything a
|
|
86
116
|
// harness's own skill discovery also finds there under a different name.
|
|
117
|
+
// Returns the names written -- pruning stale ones is the caller's job,
|
|
118
|
+
// since a destRoot shared with another source (Codex's own skills, on top
|
|
119
|
+
// of these) needs the combined set before it can prune safely.
|
|
87
120
|
function copySkills(destRoot, transform) {
|
|
88
121
|
const src = path.join(PKG_ROOT, "skills");
|
|
89
122
|
const names = fs
|
|
@@ -96,7 +129,7 @@ function copySkills(destRoot, transform) {
|
|
|
96
129
|
fs.mkdirSync(destDir, { recursive: true });
|
|
97
130
|
writeMaybeTransformed(path.join(src, name, "SKILL.md"), path.join(destDir, "SKILL.md"), transform);
|
|
98
131
|
}
|
|
99
|
-
return names
|
|
132
|
+
return names;
|
|
100
133
|
}
|
|
101
134
|
|
|
102
135
|
function copyCommands(destRoot, transform) {
|
|
@@ -106,7 +139,7 @@ function copyCommands(destRoot, transform) {
|
|
|
106
139
|
for (const name of names) {
|
|
107
140
|
writeMaybeTransformed(path.join(src, name), path.join(destRoot, name), transform);
|
|
108
141
|
}
|
|
109
|
-
return names
|
|
142
|
+
return names;
|
|
110
143
|
}
|
|
111
144
|
|
|
112
145
|
function writeMaybeTransformed(srcPath, destPath, transform) {
|
|
@@ -177,9 +210,13 @@ function mergeHooksInto(existingHooks, rendered, hookBin) {
|
|
|
177
210
|
function configureClaude() {
|
|
178
211
|
const hookBin = resolveHookBin();
|
|
179
212
|
|
|
180
|
-
const
|
|
181
|
-
const
|
|
182
|
-
|
|
213
|
+
const skillsDest = path.join(os.homedir(), ".claude", "skills");
|
|
214
|
+
const skillNames = copySkills(skillsDest);
|
|
215
|
+
pruneStale(skillsDest, skillNames);
|
|
216
|
+
const commandsDest = path.join(os.homedir(), ".claude", "commands");
|
|
217
|
+
const commandNames = copyCommands(commandsDest);
|
|
218
|
+
pruneStale(commandsDest, commandNames);
|
|
219
|
+
console.log(`installed ${skillNames.length} skills, ${commandNames.length} commands to ~/.claude`);
|
|
183
220
|
|
|
184
221
|
const settingsPath = path.join(os.homedir(), ".claude", "settings.json");
|
|
185
222
|
let settings = {};
|
|
@@ -283,7 +320,7 @@ function configureCodex() {
|
|
|
283
320
|
console.log(`configured ${hooksPath}`);
|
|
284
321
|
|
|
285
322
|
const skillsDestRoot = path.join(os.homedir(), ".codex", "skills");
|
|
286
|
-
const
|
|
323
|
+
const sharedNames = copySkills(skillsDestRoot);
|
|
287
324
|
const codexSkillsSrc = path.join(PKG_ROOT, "harness", "codex", "skills");
|
|
288
325
|
const codexNames = fs
|
|
289
326
|
.readdirSync(codexSkillsSrc, { withFileTypes: true })
|
|
@@ -293,7 +330,8 @@ function configureCodex() {
|
|
|
293
330
|
for (const name of codexNames) {
|
|
294
331
|
fs.cpSync(path.join(codexSkillsSrc, name), path.join(skillsDestRoot, name), { recursive: true, force: true });
|
|
295
332
|
}
|
|
296
|
-
|
|
333
|
+
pruneStale(skillsDestRoot, [...sharedNames, ...codexNames]);
|
|
334
|
+
console.log(`installed ${sharedNames.length + codexNames.length} skills to ${skillsDestRoot}`);
|
|
297
335
|
|
|
298
336
|
const pluginData = readObsidianPluginData();
|
|
299
337
|
if (!pluginData.enableInsecureServer || !pluginData.insecurePort) {
|
|
@@ -365,12 +403,13 @@ function configureOpencode() {
|
|
|
365
403
|
fs.writeFileSync(pluginDest, pluginSource);
|
|
366
404
|
console.log(`installed ${pluginDest}`);
|
|
367
405
|
|
|
368
|
-
const
|
|
369
|
-
const
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
);
|
|
373
|
-
|
|
406
|
+
const skillsDest = path.join(os.homedir(), ".config", "opencode", "skill");
|
|
407
|
+
const skillNames = copySkills(skillsDest, opencodeToolNameTransform);
|
|
408
|
+
pruneStale(skillsDest, skillNames);
|
|
409
|
+
const commandsDest = path.join(os.homedir(), ".config", "opencode", "command");
|
|
410
|
+
const commandNames = copyCommands(commandsDest, opencodeToolNameTransform);
|
|
411
|
+
pruneStale(commandsDest, commandNames);
|
|
412
|
+
console.log(`installed ${skillNames.length} skills, ${commandNames.length} commands to ~/.config/opencode`);
|
|
374
413
|
|
|
375
414
|
const pluginData = readObsidianPluginData();
|
|
376
415
|
if (!pluginData.enableInsecureServer || !pluginData.insecurePort) {
|
package/commands/synapse-init.md
CHANGED
|
@@ -97,7 +97,7 @@ yours and cannot be scripted because what counts as signal differs per codebase.
|
|
|
97
97
|
(hash, digest, `## Sources` mirror, PUT), `synapse push-nodes`, `synapse build-index`,
|
|
98
98
|
`synapse build-project-index`.
|
|
99
99
|
|
|
100
|
-
**The work directory** defaults to `~/.
|
|
100
|
+
**The work directory** defaults to `~/.cache/synapse/work/{repo}@{branch}/`, created on demand, and
|
|
101
101
|
holds `manifest.tsv`, `all.txt`, `lists/`, the authored `b-NN.md` bodies and the coverage files. Override with `$SYNAPSE_WORK_DIR` if you need to. Two things never to do: point it
|
|
102
102
|
at the repo (`synapse` runs from inside the repo, so its working files would land in the user's
|
|
103
103
|
checkout) or at the vault (Obsidian would index a file list that runs to six figures of lines).
|
|
@@ -85,7 +85,7 @@ this command when it does. What no longer happens is arriving here merely becaus
|
|
|
85
85
|
because it is not this checkout's namespace to diff at all. Never conflate the two: a non-ancestor
|
|
86
86
|
baseline on a branch-identity match still proceeds normally, per the "One mechanical fact about
|
|
87
87
|
branches" section above.
|
|
88
|
-
- The work directory (`$SYNAPSE_WORK_DIR`, default `~/.
|
|
88
|
+
- The work directory (`$SYNAPSE_WORK_DIR`, default `~/.cache/synapse/work/{repo}@{branch}/`) ideally
|
|
89
89
|
holds the `manifest.tsv` from the original build. Without it, new paths cannot be classified as
|
|
90
90
|
auto-claimable, and clustering decisions have to be re-derived — say so rather than proceeding as if
|
|
91
91
|
nothing were missing. `synapse/{repo}@{branch}/_manifest.tsv` is the fallback copy.
|
|
@@ -60,7 +60,8 @@ step patches a compiled design note with a `> Compiled task: [[...]]` line right
|
|
|
60
60
|
**3. Design notes (any status) with a non-empty `## Open Questions`.** Match the heading followed by
|
|
61
61
|
at least one bullet -- a heading with nothing under it (fully pruned, per the Ready-gate convention
|
|
62
62
|
`/synapse-design-note` now follows) doesn't count as open. Since this section spans every status,
|
|
63
|
-
each line in the composed report also shows *which* status the note is currently in
|
|
63
|
+
each line in the composed report also shows *which* status the note is currently in, and whether it
|
|
64
|
+
has a compiled task note:
|
|
64
65
|
|
|
65
66
|
```
|
|
66
67
|
{"and": [
|
|
@@ -69,16 +70,17 @@ each line in the composed report also shows *which* status the note is currently
|
|
|
69
70
|
]}
|
|
70
71
|
```
|
|
71
72
|
|
|
72
|
-
`regexp`
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
Discussing
|
|
79
|
-
|
|
80
|
-
note beats losing it, the
|
|
81
|
-
|
|
73
|
+
Do not chain further `and`/`regexp` conditions onto this query to sort matches by status -- compound
|
|
74
|
+
`regexp` conditions against `content` have been observed to return results that are logically
|
|
75
|
+
impossible (a query with strictly more AND-ed conditions returning *more* matches than one with
|
|
76
|
+
fewer), so any query beyond a single `glob` + single `regexp` pair is unverified and not to be
|
|
77
|
+
trusted here. Instead, `mcp__obsidian__vault_read` each match directly and take two things from the
|
|
78
|
+
result, no second query needed for either: the line following `## Status` in `content` (normally
|
|
79
|
+
`Discussing`/`Ready`/`Reference`, but a note written before the three-word convention can carry free
|
|
80
|
+
text instead, e.g. `Superseded by [[...]]` -- report that verbatim rather than forcing it into a
|
|
81
|
+
bucket, surfacing an odd note beats losing it) and, from the `links` array, any path under `tasks/`
|
|
82
|
+
-- that's the compiled task, if one exists, with no separate `Compiled task:` regex needed since a
|
|
83
|
+
resolved wikilink already appears in `links` regardless of where in the body it's written.
|
|
82
84
|
|
|
83
85
|
**4. Open task notes with at least one unchecked item.** Task notes carry `status:` in frontmatter,
|
|
84
86
|
unlike design notes -- filter there first:
|
|
@@ -104,10 +106,11 @@ note past `REVIEW` on its own:
|
|
|
104
106
|
|
|
105
107
|
One section per category, in the order above. Each line names the note (title, or filename if no
|
|
106
108
|
`title` frontmatter) plus the one identifying detail that category needs. The Open Questions section
|
|
107
|
-
is the one place a note's status also
|
|
108
|
-
implies
|
|
109
|
-
|
|
110
|
-
status
|
|
109
|
+
is the one place a note's status and compiled-task link also belong on the line -- every other
|
|
110
|
+
section's heading already implies status (the "Discussing" section only ever holds `Discussing`
|
|
111
|
+
notes) and compiled-task-ness (the "Ready, not yet compiled" section only ever holds notes without
|
|
112
|
+
one), but Open Questions spans every status and both compiled and uncompiled notes, so put the status
|
|
113
|
+
first, before the title, so it's the first thing scanned:
|
|
111
114
|
|
|
112
115
|
```
|
|
113
116
|
## Discussing
|
|
@@ -117,7 +120,7 @@ status first, before the title, so it's the first thing scanned:
|
|
|
117
120
|
- {title}
|
|
118
121
|
|
|
119
122
|
## Open questions
|
|
120
|
-
- **{status}** — {title}
|
|
123
|
+
- **{status}** — {title} — {compiled task title, or "not compiled"}
|
|
121
124
|
|
|
122
125
|
## In progress (unchecked items)
|
|
123
126
|
- {title} ({N} unchecked)
|
|
@@ -97,7 +97,7 @@ yours and cannot be scripted because what counts as signal differs per codebase.
|
|
|
97
97
|
(hash, digest, `## Sources` mirror, PUT), `synapse push-nodes`, `synapse build-index`,
|
|
98
98
|
`synapse build-project-index`.
|
|
99
99
|
|
|
100
|
-
**The work directory** defaults to `~/.
|
|
100
|
+
**The work directory** defaults to `~/.cache/synapse/work/{repo}@{branch}/`, created on demand, and
|
|
101
101
|
holds `manifest.tsv`, `all.txt`, `lists/`, the authored `b-NN.md` bodies and the coverage files. Override with `$SYNAPSE_WORK_DIR` if you need to. Two things never to do: point it
|
|
102
102
|
at the repo (`synapse` runs from inside the repo, so its working files would land in the user's
|
|
103
103
|
checkout) or at the vault (Obsidian would index a file list that runs to six figures of lines).
|
|
@@ -85,7 +85,7 @@ this skill when it does. What no longer happens is arriving here merely because
|
|
|
85
85
|
because it is not this checkout's namespace to diff at all. Never conflate the two: a non-ancestor
|
|
86
86
|
baseline on a branch-identity match still proceeds normally, per the "One mechanical fact about
|
|
87
87
|
branches" section above.
|
|
88
|
-
- The work directory (`$SYNAPSE_WORK_DIR`, default `~/.
|
|
88
|
+
- The work directory (`$SYNAPSE_WORK_DIR`, default `~/.cache/synapse/work/{repo}@{branch}/`) ideally
|
|
89
89
|
holds the `manifest.tsv` from the original build. Without it, new paths cannot be classified as
|
|
90
90
|
auto-claimable, and clustering decisions have to be re-derived — say so rather than proceeding as if
|
|
91
91
|
nothing were missing. `synapse/{repo}@{branch}/_manifest.tsv` is the fallback copy.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@imunitic/synapse",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
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.1.
|
|
26
|
-
"@imunitic/synapse-linux-x64": "0.1.
|
|
27
|
-
"@imunitic/synapse-linux-arm64": "0.1.
|
|
25
|
+
"@imunitic/synapse-darwin-arm64": "0.1.2",
|
|
26
|
+
"@imunitic/synapse-linux-x64": "0.1.2",
|
|
27
|
+
"@imunitic/synapse-linux-arm64": "0.1.2"
|
|
28
28
|
},
|
|
29
29
|
"license": "SEE LICENSE IN LICENSE"
|
|
30
30
|
}
|
|
@@ -76,7 +76,7 @@ needs one.
|
|
|
76
76
|
`built_at`, `commit`, `stale: false`, and preserving `## Notes` — belongs to
|
|
77
77
|
`synapse write-node`, because a hub node's `sources` can no more be *emitted* into a tool call
|
|
78
78
|
than read into a window. Let `$W` be the project's work directory,
|
|
79
|
-
`~/.
|
|
79
|
+
`~/.cache/synapse/work/{repo}@{branch}/`.
|
|
80
80
|
|
|
81
81
|
- **Get the node's path list into a file, never into context:**
|
|
82
82
|
|
|
@@ -103,5 +103,5 @@ and regenerated by the `synapse-node` skill — never hand-edited, because the w
|
|
|
103
103
|
`sources` hashes, `sources_digest` and the `## Sources` mirror, and a hand edit desynchronises
|
|
104
104
|
them. `_profile.txt` and `_manifest.tsv` are human-readable and may be edited directly. The
|
|
105
105
|
machine-only artifacts -- `_index.bin`, `_refs.tsv`, `_tags_cache.bin` -- are not here at all: they
|
|
106
|
-
live in `~/.
|
|
106
|
+
live in `~/.cache/synapse/work/{repo}@{branch}/`, because they are derived, rebuildable and large,
|
|
107
107
|
and the vault is version-controlled.
|