Sibling Sync
Bilateral reconciliation of SYNERGY-*.md and UPSTREAM-*.md files between
this project and its sibling vp-* projects. Read-only by default — surfaces
drift, reciprocal gaps, stale-aligned rows, and status drift across sides
without mutating anything. The opt-in --auto-reciprocate flag writes
reciprocal entries to the sibling's SYNERGY file via per-entry confirmation.
Companion to /vendor-sync (which handles upstream → project drift); this
skill handles peer-to-peer drift between siblings registered in
.claude/synergy-registry.json.
Design Rationale
The bd v1.0.0 Integration Charter
(gastownhall/beads@5d524cf7:docs/INTEGRATION_CHARTER.md) explicitly punts
cross-tracker orchestration out of bd's scope: bd will never grow a feature
that routes a cross-project item from project A's tracker to project B's
tracker. /sibling-sync is exactly the workflow-automation layer the Charter
defers to external tools — file-based reconciliation between sibling vp-*
projects, mediated by registries and confirmation prompts rather than
synchronous tracker calls.
This mirrors the rationale already cited by /synergy-tracker for keeping
cross-project state in SYNERGY-*.md plus Basic Memory rather than in bd.
Cross-skill boundaries
/sibling-sync is a comparison and reconciliation layer that sits alongside
the per-side logging skills. It owns nothing in Basic Memory and nothing on
this project's side of the SYNERGY/UPSTREAM files.
- Does NOT write SYNERGY entries on this project's side.
/synergy-tracker
workflow 1 (Log a synergy entry) owns logging on this side.
- Does NOT pull upstream subtrees.
/vendor-sync owns subtree pulls and
the upstream → project drift workflow.
- Does NOT write Basic Memory notes.
/synergy-tracker workflow 5
(Promote to Basic Memory) owns ## Cross-Project Synergy writes
to sibling entity notes; /upstream-tracker workflow 6 (Promote to Basic
Memory) owns ## Upstream Friction writes. Basic Memory write tools are
intentionally absent from this skill's allowed-tools.
- Does NOT write
## Trend Reviews entries to SYNERGY files. Those belong
to /synergy-tracker workflow 4 (Trend review (quarterly)). Even under
--auto-reciprocate, /sibling-sync only mirrors content entries into
reciprocal sections — never trend-review summaries.
- Stale-row detection is INLINE here for the threshold values used during
comparison runs. The canonical staleness-threshold definition lives in
/synergy-tracker workflow 4 (Trend review (quarterly)) — workflow 2 (Sync
sibling SYNERGY) below cites it. Per RETRO-10 YAGNI guard: extract this to
a shared helper only when a third skill needs the same logic.
- Surfacing reciprocal-friction findings is in scope; acting on them is
not. Workflow 3 (Sync sibling UPSTREAM) Mode B (see below) reads the sibling's
UPSTREAM-<this-project>.md to surface friction the sibling tracks about
this project. Filing the resulting work as bugs/features/opportunities on
this side is /upstream-tracker workflow 1 (Log a new entry)'s job.
Annotating the sibling's entry as resolved is /upstream-tracker workflow
3 (Resolve an entry)'s job, performed on the sibling's side. /sibling-sync
reports only.
- Orchestrator role for follow-up actions (v0.14.0). Workflows 2 (Sync
sibling SYNERGY) and 3 (Sync sibling UPSTREAM) end with a per-sibling
action menu (see "Action-menu protocol" below) that delegates writes to
the owning skill (
/vp-beads:synergy-tracker,
/vp-beads:upstream-tracker) via the Skill tool, or runs bd create
directly for beads issues. /sibling-sync still owns nothing in Basic
Memory and nothing in this project's SYNERGY/UPSTREAM files —
ownership boundaries are unchanged.
Registry and path resolution
Sibling projects are declared in .claude/synergy-registry.json (array of
{name, file, remote, bm-entity, relationship, local-path?} entries). The
optional local-path field gives the on-disk path to the sibling checkout
(relative paths resolve from this project root). When absent, fall back to
../<name>/.
.claude/synergy-registry.local.json is a gitignored companion that overrides
fields in the committed registry — same per-entry merge by name pattern as
.claude/vendor-registry.local.json — and can add fully-private siblings
(see "Private sibling handling" below). Resolution order:
- Read
.claude/synergy-registry.json.
- If
.claude/synergy-registry.local.json exists, merge it on top by name
key, in two modes:
- Override mode (entry
name matches a base entry): fields in
.local.json win; absent fields keep the base value.
- Private-add mode (entry
name not in the base registry AND its file
is a PRIVATE-SYNERGY-<name>.md value): the entry is added to the
merged result as a private sibling. The PRIVATE- prefix on file is the
marker (there is no boolean); it governs the restrictions below.
- Entries in
.local.json whose name is not in the base registry and whose
file is NOT PRIVATE-SYNERGY-* are ignored (backward compatibility —
typos and accidental entries stay silent).
- For each merged entry, resolve
local-path (registry value or
../<name>/).
- If the resolved path does not exist on disk, report informatively and SKIP
that sibling. Do not error out — continue with siblings that are
accessible.
Workflow 3 (Sync sibling UPSTREAM) additionally consumes the merged
.claude/vendor-registry.json (+ .local.json) to identify shared vendor
dependencies across siblings. (The vendor registry has no private-add mode —
private siblings are synergy-registry only.)
Private sibling handling
A private sibling is one whose merged registry entry has a
file: PRIVATE-SYNERGY-<name>.md value (added via private-add mode above, or —
in principle — a base entry, which the validator forbids because it would commit
the name). Its name lives only in the gitignored .local.json and
PRIVATE-SYNERGY-<name>.md; it must never reach a committed file. This skill
therefore treats private siblings under a strict read-vs-write split, keyed on
the PRIVATE-SYNERGY-* file predicate:
- Read (allowed) — hybrid read-diff. Unlike a public sibling's
glob-discovered
PRIVATE-SYNERGY-*.md overlay (which this skill never reads),
a private sibling's PRIVATE-SYNERGY-<name>.md is its registry file, so
workflows 1 (Discover sibling(s)), 2 (Sync sibling SYNERGY), and 3 (Sync
sibling UPSTREAM, Mode A only) read it to produce read-only diff findings.
Findings appear in the ephemeral terminal report only — never written.
- Write (blocked) — every committed surface. For a
PRIVATE-SYNERGY-*-filed
sibling:
- Reciprocation: workflow 4 (Apply reciprocation batch) skips it entirely
(writing
SYNERGY-<this-project>.md on the sibling's side would expose the
relationship).
bd create: the action menu suppresses the UPSTREAM "file beads issues"
option (a committed .beads/*.jsonl entry would leak the name) — findings
stay report-only.
- Logging on this side: any "log unreciprocated entry" follow-up routes to
the gitignored
PRIVATE-SYNERGY-<name>.md, never a committed
SYNERGY-<name>.md (delegated to /synergy-tracker).
- BM promotion: never (this skill has no BM tools;
/synergy-tracker
workflow 5 (Promote to Basic Memory) skips PRIVATE-SYNERGY-* siblings).
- UPSTREAM Mode B: out of scope for private siblings — an
UPSTREAM-<name>.md filename would itself leak the name. Workflow 3 (Sync
sibling UPSTREAM) runs Mode A (shared dependencies — names no sibling) but
skips Mode B for PRIVATE-SYNERGY-* siblings.
The PRIVATE-SYNERGY-* file predicate is the single structural marker — the
same prefix that keeps content outside the SYNERGY-*.md glob also gates every
write path here.
Workflows
Determine which workflow the user needs based on their request. If ambiguous,
default to running workflow 1 (Discover sibling(s)) followed by workflow 2
(Sync sibling SYNERGY) and workflow 3 (Sync sibling UPSTREAM) as a single
report. Workflow 4 (Apply reciprocation batch) only fires under explicit
--auto-reciprocate.
1. Discover sibling(s)
Resolve which siblings will participate in this run.
Steps:
- Read
.claude/synergy-registry.json. If the file does not exist, redirect:
tell the user no sibling registry is configured and offer to invoke
/synergy-tracker workflow 1 (Log a synergy entry), which will run the
guided registry creation flow at step 1b for the first sibling. If the
user names a sibling now, follow the synergy-tracker step 1b prose from
this conversation (re-reading
skills/synergy-tracker/SKILL.md workflow 1 (Log a synergy entry) step 1b
and applying its logic in-session — Claude Code has no actual
inline-skill-invocation mechanism, so this means executing step 1b's
instructions verbatim from sibling-sync's context). After the registry
is created, resume from step 2 below. Otherwise stop, and instruct the
user to invoke /synergy-tracker directly with the sibling name and
then re-run /sibling-sync.
- If
.claude/synergy-registry.local.json exists, merge it on top per the
per-entry merge rules in the Registry section above. This includes
private siblings — .local.json-only entries whose file is
PRIVATE-SYNERGY-<name>.md, which are added to the participating set (they
read-diff like any sibling but are blocked from every committed-write path —
see "Private sibling handling").
- If the user named a specific sibling in their request or argument, filter
the merged list to that entry. Otherwise, all merged entries participate.
- For each entry, resolve
local-path → ../<name>/ fallback. Probe each
resolved path with a directory existence check.
- Build the participation lists:
- Accessible siblings (path exists) — proceed to workflow 2 (Sync
sibling SYNERGY) and workflow 3 (Sync sibling UPSTREAM) for each. Mark any
private sibling (registry
file is PRIVATE-SYNERGY-*) with a [private]
label so the user can see which relationships are private.
- Inaccessible siblings (path missing) — report them so the user
knows what was skipped, with the resolved path and a hint that
.claude/synergy-registry.local.json can override the path
- Report the participation list before continuing.
Output:
Siblings participating:
- vp-knowledge → /Users/.../vp-claude (registry local-path)
- acme-partner → /abs/path/to/acme-partner [private]
Siblings skipped (path not accessible):
- vp-other → ../vp-other (set local-path in synergy-registry.local.json)
If no siblings are accessible, stop and report. The user can either correct
the paths via .claude/synergy-registry.local.json or accept that this run
has no work to do.
2. Sync sibling SYNERGY
For each accessible sibling from workflow 1 (Discover sibling(s)), compare
the bidirectional SYNERGY files and surface drift findings. Report only —
no writes.
Steps:
Read this project's SYNERGY file for the sibling, by the registry file
value — SYNERGY-<sibling>.md for a public sibling, or
PRIVATE-SYNERGY-<sibling>.md for a private sibling (its registry file;
the hybrid read-diff exception). For a public sibling, never pull in a
glob-discovered PRIVATE-SYNERGY-*.md overlay — the PRIVATE- prefix keeps
those outside the SYNERGY-*.md namespace, so reading the committed file by
name never touches them (see /synergy-tracker ### Private overlay). For a
private sibling, all downstream findings are read-only and stay in the
ephemeral report (the write blocks in "Private sibling handling" apply). If
the file is absent, treat as zero entries and proceed (the gap will surface as
"Unreciprocated entries on sibling" if the sibling has any entries).
Read the sibling's SYNERGY-<this-project>.md at
<resolved-local-path>/SYNERGY-<this-project>.md. If absent, treat as
zero entries.
Parse each side's entries section-by-section (Shared Patterns,
Divergences, Extraction Candidates, They Have / We Don't). Build a
bidirectional entry map keyed by title, normalized per the rule in
Guidelines below (2-pass matching: deterministic lead-clause pass, then
judgment pass on residuals).
Section migration is its own signal — not silently merged. Matching
is within-section: an entry's title is paired only with same-section
titles on the sibling. If an entry has migrated sections on one side
(e.g., Shared Pattern here, Divergence on sibling — typical when one
side promoted after noticing drift), it surfaces as (a) on the
originating section and (b) on the destination section, NOT as a
finding (d) Status drift. The section migration is itself the drift
signal worth surfacing, and the (a)/(b) framing tells the user
precisely which side moved. Finding (d) applies within-section only.
Section asymmetry — excluded from findings (a)/(b): the
They Have / We Don't section is intrinsically asymmetric. Entries here
describe what the sibling has that we don't; reciprocally on the
sibling's side, the same-named section describes what we have that
they don't — a different semantic set. Bilateral title comparison is
meaningless for this section. Skip its entries when computing findings
(a) and (b). The user can read the section directly to act on adoption
candidates; logging an adoption decision is /synergy-tracker's job,
not /sibling-sync's.
Walk the merged map and classify each entry into one of four findings:
(a) Reciprocal gaps — entries on this side with no matching title
on the sibling. The sibling lacks the reciprocal entry. Candidates for
workflow 4 (Apply reciprocation batch) under --auto-reciprocate.
Excludes entries from They Have / We Don't (asymmetric — see step 3).
(b) Unreciprocated entries on sibling — entries on the sibling
with no matching title here. The user may want to invoke
/synergy-tracker workflow 1 (Log a synergy entry) to log these on
this side. /sibling-sync does NOT write to this side automatically.
Excludes entries from They Have / We Don't (asymmetric — see step 3).
(c) Stale alignment claims — entries with Status: aligned and
Last verified: more than 8 sprints old (≈ two trend-review cycles).
Inline threshold; canonical definition is /synergy-tracker workflow
4 (Trend review (quarterly)). Treat 1 sprint ≈ 2 weeks if no other
calibration is available; if the entry has no Last verified: field,
fall back to the entry's date stamp.
(d) Status drift — matched entries whose Status: field differs
across sides. Applies to two cases:
- Shared Patterns where one side records
aligned and the other
records drifting or diverging (or any disagreement on the
Status value). Often signals that one side has converged or
re-diverged without the reciprocal note being refreshed.
- Divergences with
Convergence path: adopt-theirs or
Convergence path: propose-shared where one side has moved to
adopted/converged while the other still says drifting or
similar.
Excludes Divergences with Convergence path: accept-difference —
those are intended-asymmetric and have no drift signal to flag.
Output a structured report grouped first by sibling, then by finding
category. Include each entry's title, both sides' values where they
differ, and a one-line action hint per category.
Output format (per sibling):
## SYNERGY drift — vp-knowledge
### (a) Reciprocal gaps (here, missing on sibling)
- "Hook event coverage" (Shared Patterns) — sibling has no matching entry
→ /sibling-sync --auto-reciprocate to file the reciprocal
### (b) Unreciprocated entries on sibling
- "validate-plugin tool-reference audit" (Shared Patterns) — we don't track this
→ /synergy-tracker to log on this side
### (c) Stale alignment claims (>8 sprints since Last verified)
- "PreCompact prompt command hook" — Last verified: 2026-01-15 (here),
2026-01-15 (sibling). Re-verify now.
### (d) Status drift
- "npm-run-all2 parallel check stages" (Shared Patterns) — Status here:
diverging. Status sibling: aligned. Sibling converged; refresh this row.
- "BM section ownership scheme" (Divergences, propose-shared) — Status
here: drifting. Status sibling: aligned.
- Offer follow-up actions. After workflow 3 (Sync sibling UPSTREAM)
has also finished printing for this sibling, prepare the SYNERGY tier of
the action menu (built from this workflow's findings) and let workflow 3
(Sync sibling UPSTREAM) step 6 pair it with the UPSTREAM tier into a
single
AskUserQuestion call. Do NOT issue the prompt from this step —
the prompt is dispatched from workflow 3 (Sync sibling UPSTREAM) step
6 once both reports are on screen. Full protocol — which findings map to which menu options,
the header values, the --auto-reciprocate precedence rule, and the
plugin-namespaced Skill invocations — lives in the "Action-menu
protocol" section below.
3. Sync sibling UPSTREAM
For each accessible sibling, build two kinds of UPSTREAM file pairs and
compare friction tracking on each. Same report-only contract as workflow 2
(Sync sibling SYNERGY).
Two pairing modes coexist; both can fire on a single sibling:
- Mode A — shared-dependency pairing. Both sides have
UPSTREAM-<dep>.md
with the same basename (e.g., both have UPSTREAM-basic-memory.md). The
files describe the same third-party dependency <dep>. Findings (a)–(d).
- Mode B — reciprocal sibling-friction pairing. This project has
UPSTREAM-<sibling-name>.md (friction we log about the sibling) AND/OR the
sibling has UPSTREAM-<this-name>.md (friction the sibling logs about us).
Different basenames; same bilateral relationship. Owner-side semantics
invert relative to Mode A: an entry in the sibling's UPSTREAM-<this-name>.md
with Ownership: upstream means THIS project is the upstream that must
act. Findings (e)–(h).
Steps:
Build Mode A pairs. Glob this project for UPSTREAM-*.md. Glob the
sibling's resolved local-path for UPSTREAM-*.md. Compute the
intersection by basename — each match is one Mode A pair. Record both
sides' full UPSTREAM basename lists for use in step 2.
Detect Mode B pair. Skip Mode B entirely for private siblings
(merged registry file is PRIVATE-SYNERGY-*): a Mode B file is named
UPSTREAM-<sibling-name>.md, whose filename would commit the private name.
Private siblings are SYNERGY-only; Mode A above (keyed on shared dependency
names, never the sibling) still runs. For a non-private sibling, derive this
project's canonical name per the four-tier algorithm in
skills/synergy-tracker/references/project-name-derivation.md to
compute <this-name>. Apply the same algorithm (tier 3 for the sibling
subject) to the registry name field for <sibling-name>.
Stale local-path guard: if the registry entry specifies a
local-path that does not resolve to an accessible directory, tier 1
(sibling-registry back-pointer) silently falls through to tier 2
(this project's plugin.json). Warn the user before falling through:
"Sibling local-path is not accessible — tier 1 derivation skipped;
<this-name> may diverge from how the sibling registered this project.
Update .claude/synergy-registry.local.json if the sibling moved."
Then check:
- Does the sibling have
<resolved-local-path>/UPSTREAM-<this-name>.md?
- Does this project have
UPSTREAM-<sibling-name>.md?
If either file exists, this sibling has a Mode B pair (one-sided or
two-sided). Both files absent is normal — no reciprocal friction tracked
on either side. Skip Mode B for this sibling and continue.
The Mode B file pair is <this-root>/UPSTREAM-<sibling-name>.md ↔
<sibling-root>/UPSTREAM-<this-name>.md. By construction these basenames
differ from any Mode A pair's basename (Mode A keys on shared
third-party dep names; Mode B keys on sibling project names that appear
in the synergy registry). No deduplication guard needed.
Process Mode A pairs. For each Mode A pair, read both copies and
parse entries (Bugs, Feature Requests, Upstream Opportunities,
Resolved). Build a bidirectional entry map by title using the same
2-pass matching rule as workflow 2 (deterministic lead-clause Pass 1 +
judgment Pass 2 on residuals — see Guidelines). UPSTREAM titles are
typically more structured than SYNERGY titles, so Pass 2 fires less
often, but the rule is identical for consistency. Classify each entry:
- (a) Duplicate friction — same title on both sides. Sanity check:
are the workarounds, dates, and status fields aligned? If not, it's a
candidate for category (b).
- (b) Complementary workarounds — same title both sides but the
Workaround: field (or equivalent) differs. The sibling may have
found a better mitigation. Flag for cross-pollination.
- (c) Stale entries — entries dated more than 3 months ago without a
Trend Review annotation since. Either side. Stale ≠ wrong, but worth
re-verifying.
- (d) Sibling-only entries — friction the sibling tracks for a
shared dependency that we don't. Potential adoption: invoke
/upstream-tracker workflow 7 (Sync from Basic Memory) or workflow 1
(Log a new entry) to bring matching entries over here. /sibling-sync
does NOT write here automatically.
Process Mode B pair. Read whichever side(s) of the pair exist.
Parse entries the same way as step 3. Match titles bidirectionally with
the same 2-pass rule. Classify each entry:
- (e) Sibling's unresolved friction against this project — entries
in
<sibling-root>/UPSTREAM-<this-name>.md that are NOT prefixed with
_(Resolved ...)_ and NOT in a ## Resolved section if one exists.
Ownership: upstream on these entries means THIS project owns the
fix (we are upstream from the sibling's perspective). Surface ALL
unresolved entries — every one is a request directed at us. Action
hint: file beads issues here or address inline; consider logging a
cross-reference in our UPSTREAM-<sibling-name>.md if a workaround
is built.
- (f) Our unresolved friction against the sibling — entries in
<this-root>/UPSTREAM-<sibling-name>.md that are unresolved on our
side and have no corresponding _(Resolved ...)_ annotation on
either side. Informational: documents work blocked on the sibling.
Action hint: check sibling release notes or changelog for shipped
fixes the sibling forgot to annotate.
- (g) Cross-side staleness — our entry, sibling may have shipped.
Entry in
<this-root>/UPSTREAM-<sibling-name>.md unresolved on our
side, but the sibling shows a "shipped" signal (see "What 'shipped'
means" below). Use 6 months as the look-back horizon for git-log
scanning. Action hint: re-verify against the sibling's current
release; annotate with _(Resolved ...)_ via /upstream-tracker
workflow 3 (Resolve an entry) if confirmed shipped.
- (h) Reverse cross-side staleness — sibling tracks us, we may have
shipped. Entry in
<sibling-root>/UPSTREAM-<this-name>.md unresolved
on the sibling's side, but this project shows a "shipped" signal
(recent CHANGELOG entry, _(Resolved ...)_ in our cross-reference,
or git tag/commit subject within 6 months matching the entry title).
Read-only finding: /sibling-sync cannot write the sibling's file.
Action hint: notify sibling maintainer, or raise on their side via
/upstream-tracker workflow 3 (Resolve an entry) so they can
annotate.
What "shipped" means (pinned definition for findings (g) and (h)):
a fix is shipped when (1) a CHANGELOG or _(Resolved ...)_ annotation
exists on the owner's side, OR (2) the feature/fix is referenced in a
git tag message or commit subject within the relevant release window
(use git -C <owner-path> log --oneline --since="6 months ago" as a
heuristic proxy — string-match the entry title or its lead clause; do
not parse). A Workaround: full on the filing side without a
corresponding shipped version on the owner's side is NOT sufficient;
that is the filing project's mitigation, not upstream resolution.
Output. Report Mode A findings first (grouped by sibling, then by
shared dependency, then by finding category), then Mode B findings
(grouped by sibling) under a separate header. This ordering keeps the
existing Mode A output shape intact and adds Mode B as an additive
block.
Note on UPSTREAM coverage gaps: /sibling-sync now handles two cases:
shared third-party dependencies (Mode A, basename intersection) and
reciprocal sibling-friction pairs (Mode B, inverse-name detection).
One-sided UPSTREAM files about non-sibling, non-shared dependencies are
still out of scope — those are the sibling's responsibility to discover
via /upstream-tracker workflow 7 (Sync from Basic Memory) on its own.
Output format additions for Mode B:
## UPSTREAM reciprocal-friction — vp-knowledge
(Mode B: this-side UPSTREAM-vp-knowledge.md ↔ sibling-side UPSTREAM-vp-beads.md)
### (e) Sibling's unresolved friction against this project (we should action)
- "vp-beads: new /sibling-sync skill" (Feature Requests, 2026-05-04) — sibling
marks Workaround: partial; we shipped in v0.12.0. See finding (h).
- "synergy-tracker: mandate bilateral reciprocation" (Feature Requests, 2026-05-04)
Ownership on their side: upstream (us) · Workaround on their side: full
→ file beads issue or address inline
### (f) Our unresolved friction against the sibling
- "Agent effort defaults not overridable from parent" (Feature Requests, 2026-04-05)
Ownership: upstream (them) · Workaround: none
### (g) Cross-side staleness: our entry the sibling may have shipped
- (none this run)
### (h) Reverse staleness: sibling tracks us but we may have shipped
- "vp-beads: new /sibling-sync skill" — v0.12.0 tag (2026-05-05) matches.
Sibling should annotate _(Resolved 2026-05-05, vp-beads v0.12.0)_.
→ notify sibling maintainer; cannot write their file from here
- Offer follow-up actions. This is the dispatch point. After this
workflow finishes printing (and workflow 2 (Sync sibling SYNERGY) has
already finished for the same sibling), build a single
AskUserQuestion
call combining workflow 2 (Sync sibling SYNERGY)'s SYNERGY tier with
this workflow's UPSTREAM tier. The "Action-menu protocol" section below
specifies the full UPSTREAM tier: findings (b) and (d) collapse into a
single "Update our UPSTREAM" option that delegates to
/vp-beads:upstream-tracker; finding (e) routes directly to bd create; finding (g) delegates to /vp-beads:upstream-tracker workflow
3 (Resolve an entry). Findings (a), (c), (f), and (h) are informational
and not present in the menu. If no UPSTREAM findings are actionable AND
no SYNERGY findings are actionable for this sibling, skip the prompt
for this sibling. If only one tier has actionable findings, issue a
single-question call.
4. Apply reciprocation batch
Opt-in mutation path. Only runs when the user supplies --auto-reciprocate
in their invocation, or explicitly confirms intent like "yes, apply all the
reciprocal gaps". Mirrors /upstream-tracker workflow 7 (Sync from Basic
Memory)'s per-entry confirmation pattern.
Steps:
- Exclude private siblings first. Drop any sibling whose merged registry
file is PRIVATE-SYNERGY-* before doing anything else — reciprocation would
write SYNERGY-<this-project>.md on the sibling's side and expose that the
private relationship exists. Announce each exclusion: "Skipping reciprocation
for <name> — private sibling (existence must not cross to the sibling's
side)." This guard runs at the top of the loop, before any read of the
sibling's SYNERGY file.
- Re-run workflow 2 (Sync sibling SYNERGY) finding (a) (reciprocal gaps)
for each remaining (non-private) accessible sibling, applying the stricter
matching rules from the Hard Limits section below: Pass 1 (deterministic)
matches only; any Pass 2 (judgment) matches from workflow 2 (Sync sibling
SYNERGY) are added back to the reciprocation queue with an extra
disambiguation prompt rather than suppressed silently. These are the entries
on this side that the sibling demonstrably lacks.
- For each reciprocal gap, in order:
- Read the source entry from this project's
SYNERGY-<sibling>.md
(full entry text including title, date, structured fields).
- Determine the destination file at the sibling:
<resolved-local-path>/SYNERGY-<this-project>.md (derive
<this-project> per
skills/synergy-tracker/references/project-name-derivation.md).
If it does not exist yet, plan to Write a new file using the
four-section template from
skills/synergy-tracker/references/synergy-entry-format.md.
- Determine the destination section from the source entry's section
(a Shared Pattern on this side becomes a Shared Pattern on the
sibling, etc.).
- Show the user: source entry text + destination file path +
destination section. Ask: "Write reciprocal entry to
<sibling-path>/SYNERGY-<this-project>.md under ### <Section>?
[y/n/skip-rest]".
- On
y: append the entry under the destination section using Edit
(or Write if the file is new). Replace any
_No entries yet._ placeholder in that section with the entry. Keep
the entry text as-is from this side — do not rewrite to the
sibling's voice; reciprocation IS the verification step (per
/synergy-tracker workflow 1 (Log a synergy entry) bilateral
mandate). The sibling will re-verify on their next reciprocation
pass.
- On
n or skip-rest: skip and continue (or stop the batch on
skip-rest).
- After the batch, report:
- Entries written, with destination file paths
- Entries skipped, with reason
- Verification reminder for the user: run
git status in the
sibling repo, review the appended entries, commit on that side. /sibling-sync
does not commit on the sibling's behalf. Also remind the user to file
a beads follow-up on the sibling for re-verification next sprint, per
/synergy-tracker workflow 1 (Log a synergy entry)'s reciprocation
mandate.
Hard limits on workflow 4 (Apply reciprocation batch):
- Only mirrors entries from workflow 2 (Sync sibling SYNERGY) finding (a).
Does NOT mirror UPSTREAM entries from workflow 3 (Sync sibling UPSTREAM)
—
/upstream-tracker workflow 7 (Sync from Basic Memory) is the right
channel for cross-project UPSTREAM adoption (BM is the cross-project
bridge for friction; SYNERGY is the cross-project bridge for patterns).
This applies equally to Mode A findings (a)–(d) AND Mode B findings
(e)–(h): finding (e) entries get filed natively on this side via
/upstream-tracker workflow 1 (Log a new entry), not mirrored;
finding (h) annotations get written by the sibling via their own
/upstream-tracker workflow 3 (Resolve an entry), not by us.
- Never sources a reciprocal entry from a
PRIVATE-SYNERGY-*.md private
overlay. Those entries are private to this checkout (the proprietary↔public
boundary); writing one to a sibling would leak it. This is enforced
structurally: reciprocation reads the committed SYNERGY-<project>.md (by
exact name, workflow 2 (Sync sibling SYNERGY) step 1), and the PRIVATE-
prefix keeps overlays outside the SYNERGY-*.md namespace so no glob here can
reach them. See
/synergy-tracker ### Private overlay.
- Never reciprocates for a private sibling (merged registry
file is
PRIVATE-SYNERGY-*). Step 1 of this workflow excludes them before the loop;
writing SYNERGY-<this-project>.md on the sibling's side would commit the
private relationship's existence to their repo. See "Private sibling
handling".
- Never mirrors entries from
## They Have / We Don't. The section is
intrinsically asymmetric (entries here describe sibling capabilities
WE lack; the sibling's same-named section describes the inverse
asymmetry). Workflow 2 (Sync sibling SYNERGY) already excludes this section from finding (a),
but this is restated here as a mutation-side guard: even if a future
edit relaxes the workflow 2 (Sync sibling SYNERGY) exclusion, workflow 4 (Apply reciprocation batch) must never write a
They Have / We Don't entry to the sibling.
- The reciprocal-gap list is computed using Pass 1 matches only.
Entries that paired via Pass 2 (judgment) in workflow 2 (Sync sibling SYNERGY) are added back
to the reciprocation queue and presented to the user with a flag:
"This entry may already exist on the sibling as
<pass-2-matched-title>
— does that match? [y=skip / n=write reciprocal anyway / skip-rest]".
Defaulting to caution at the mutation boundary inverts the read-only
cost asymmetry: under --auto-reciprocate, suppressing a write that
should happen (false-positive Pass 2 match) is more expensive than
proposing a duplicate the user can reject (false-negative).
- Never writes to
## Trend Reviews sections on either side.
- Never writes to this project's side. Reciprocal entries go to the
sibling only — logging on this side is
/synergy-tracker workflow 1
(Log a synergy entry)'s job.
- Never writes to Basic Memory (no BM edit tooling allowed in this skill).
BM writes are
/synergy-tracker workflow 5 (Promote to Basic Memory)
and /upstream-tracker workflow 6 (Promote to Basic Memory)'s
territory.
Action-menu protocol
This skill never writes SYNERGY/UPSTREAM/BM directly — even the menu options
dispatch to the owning skill via the Skill tool, or run bd create for
beads issues. The menu is a navigation aid, not a write path. The default
read-only contract from earlier versions still holds: a user who picks
"None" for both questions receives the report and exits without any
mutation.
After workflows 2 (Sync sibling SYNERGY) and 3 (Sync sibling UPSTREAM) have
printed their per-sibling reports, sibling-sync issues a single
AskUserQuestion call with up to two single-select questions per sibling.
The AskUserQuestion SDK contract caps options at 2-4 per question
(plus an auto "Other"); we therefore split SYNERGY and UPSTREAM into
separate questions rather than one flat menu. Skipping a question is just
selecting its "None" option; both tiers default to read-only on skip.
Two-tier menu shape
Q1 — SYNERGY follow-up (header: "Synergy", 7 chars). Options listed
only when their finding count is nonzero:
| Option |
Trigger |
Dispatch |
| 1. Apply reciprocal gaps (N) |
finding (a) > 0 |
re-enter workflow 4 (Apply reciprocation batch) in-skill — no Skill call |
| 2. Log unreciprocated sibling entries (N) |
finding (b) > 0 |
Skill(skill="/vp-beads:synergy-tracker", args=...) → workflow 1 (Log a synergy entry) |
| 0. None — synergy report only |
always |
exit SYNERGY tier without action |
Q2 — UPSTREAM follow-up (header: "Upstream", 8 chars). Options listed
only when their finding count is nonzero. Findings (b) and (d) collapse
into a single option to keep the question within the 4-option SDK cap:
| Option |
Trigger |
Dispatch |
| 1. Update our UPSTREAM (b/d, N total) |
finding (b) > 0 OR finding (d) > 0 |
Skill(skill="/vp-beads:upstream-tracker", args=...) — args route to workflow 1 (Log a new entry) and/or workflow 7 (Sync from Basic Memory) inside upstream-tracker |
| 2. File beads issues for sibling's friction (N) |
finding (e) > 0 |
Bash → bd create per entry |
| 3. Resolve cross-stale entries (N) |
finding (g) > 0 |
Skill(skill="/vp-beads:upstream-tracker", args=...) → workflow 3 (Resolve an entry) |
| 0. None — upstream report only |
always |
exit UPSTREAM tier without action |
If neither tier has actionable findings for a sibling, skip the
AskUserQuestion call entirely for that sibling. If only one tier has
actionable findings, issue a single-question call (the SDK supports 1-4
questions per call).
Private-sibling guard (no-commit-leak). When the sibling's merged registry
file is PRIVATE-SYNERGY-*, every committed-write menu option is removed
because it would commit the private name:
- Q2 option 2 (
bd create) is suppressed — a .beads/*.jsonl entry naming
the sibling would leak it. The finding stays in the report only. (Finding (e)
does not arise anyway: workflow 3 (Sync sibling UPSTREAM) skips Mode B for
private siblings.)
- Q1 option 2 (Log unreciprocated sibling entries) redirects — its
/synergy-tracker dispatch targets the gitignored PRIVATE-SYNERGY-<name>.md,
never a committed SYNERGY-<name>.md (pass the private destination in the
args prose).
- Q1 option 1 (Apply reciprocal gaps) is absent — workflow 4 (Apply
reciprocation batch) already excludes private siblings.
If that leaves no actionable options for a private sibling, skip the prompt and
present the read-only findings as report-only.
Per-action argument templates
Pass natural-language prose in the Skill tool's args field. The
delegated skill receives the prose as narrative context. Templates:
- SYNERGY 2 (Log unreciprocated sibling entries):
Log unreciprocated entries from sibling <sibling-name>: <bullet list of titles + sections>. Invoke workflow 1 (Log a synergy entry) for each.
- UPSTREAM 1 (Update our UPSTREAM, b/d collapse):
From sibling-sync findings against <sibling-name>: adopt complementary workarounds for <package, title> entries (sibling's workaround text: <quoted>); also scan sibling-only entries <package, titles>. Use workflow 1 (Log a new entry) and workflow 7 (Sync from Basic Memory) as appropriate.
- UPSTREAM 3 (Resolve cross-stale entries):
`Resolve entries in UPSTREAM-.md: . Verify against the sibling's recent changelog/tags first. Invoke workflow 3 (Resolve an ent
…(truncated)
1---2name: sibling-sync3description: Bilateral SYNERGY/UPSTREAM reconciliation across sibling projects. Use when the user wants to sync sibling SYNERGY/UPSTREAM files, compare both sides to surface drift, find reciprocation gaps (entries here but not there, or vice versa), flag stale-aligned rows, detect status drift across sides, surface friction the sibling tracks ABOUT this project (their UPSTREAM-<this-project>.md), or apply a reciprocation batch with --auto-reciprocate. Workflow 3 covers two UPSTREAM pairing modes: shared third-party dependencies AND reciprocal sibling-friction pairs (UPSTREAM-<sibling>.md here ↔ UPSTREAM-<this-project>.md there). NOT for logging entries on this side (use /synergy-tracker workflow 1 (Log a synergy entry)) — sibling-sync compares both sides without writing by default. NOT for upstream → project drift (use /vendor-sync); sibling-sync handles peer-to-peer drift between sibling vp-* projects. Trigger phrases: 'sibling sync', 'compare siblings', 'sync sibling', 'reconcile siblings', 'reciprocation gap', 'sync dr4---56# Sibling Sync78Bilateral reconciliation of `SYNERGY-*.md` and `UPSTREAM-*.md` files between9this project and its sibling vp-\* projects. Read-only by default — surfaces10drift, reciprocal gaps, stale-aligned rows, and status drift across sides11without mutating anything. The opt-in `--auto-reciprocate` flag writes12reciprocal entries to the sibling's SYNERGY file via per-entry confirmation.1314Companion to `/vendor-sync` (which handles upstream → project drift); this15skill handles peer-to-peer drift between siblings registered in16`.claude/synergy-registry.json`.1718## Design Rationale1920The bd v1.0.0 Integration Charter21(`gastownhall/beads@5d524cf7:docs/INTEGRATION_CHARTER.md`) explicitly punts22cross-tracker orchestration out of bd's scope: bd will never grow a feature23that routes a cross-project item from project A's tracker to project B's24tracker. `/sibling-sync` is exactly the workflow-automation layer the Charter25defers to external tools — file-based reconciliation between sibling vp-\*26projects, mediated by registries and confirmation prompts rather than27synchronous tracker calls.2829This mirrors the rationale already cited by `/synergy-tracker` for keeping30cross-project state in `SYNERGY-*.md` plus Basic Memory rather than in bd.3132## Cross-skill boundaries3334`/sibling-sync` is a *comparison and reconciliation* layer that sits alongside35the per-side logging skills. It owns nothing in Basic Memory and nothing on36this project's side of the SYNERGY/UPSTREAM files.3738- **Does NOT write SYNERGY entries on this project's side.** `/synergy-tracker`39 workflow 1 (Log a synergy entry) owns logging on this side.40- **Does NOT pull upstream subtrees.** `/vendor-sync` owns subtree pulls and41 the upstream → project drift workflow.42- **Does NOT write Basic Memory notes.** `/synergy-tracker` workflow 543 (Promote to Basic Memory) owns `## Cross-Project Synergy` writes44 to sibling entity notes; `/upstream-tracker` workflow 6 (Promote to Basic45 Memory) owns `## Upstream Friction` writes. Basic Memory write tools are46 intentionally absent from this skill's `allowed-tools`.47- **Does NOT write `## Trend Reviews` entries** to SYNERGY files. Those belong48 to `/synergy-tracker` workflow 4 (Trend review (quarterly)). Even under49 `--auto-reciprocate`, /sibling-sync only mirrors content entries into50 reciprocal sections — never trend-review summaries.51- **Stale-row detection is INLINE here** for the threshold values used during52 comparison runs. The canonical staleness-threshold definition lives in53 `/synergy-tracker` workflow 4 (Trend review (quarterly)) — workflow 2 (Sync54 sibling SYNERGY) below cites it. Per RETRO-10 YAGNI guard: extract this to55 a shared helper only when a third skill needs the same logic.56- **Surfacing reciprocal-friction findings is in scope; acting on them is57 not.** Workflow 3 (Sync sibling UPSTREAM) Mode B (see below) reads the sibling's58 `UPSTREAM-<this-project>.md` to surface friction the sibling tracks about59 this project. Filing the resulting work as bugs/features/opportunities on60 this side is `/upstream-tracker` workflow 1 (Log a new entry)'s job.61 Annotating the sibling's entry as resolved is `/upstream-tracker` workflow62 3 (Resolve an entry)'s job, performed on the sibling's side. /sibling-sync63 reports only.64- **Orchestrator role for follow-up actions (v0.14.0).** Workflows 2 (Sync65 sibling SYNERGY) and 3 (Sync sibling UPSTREAM) end with a per-sibling66 action menu (see "Action-menu protocol" below) that delegates writes to67 the owning skill (`/vp-beads:synergy-tracker`,68 `/vp-beads:upstream-tracker`) via the `Skill` tool, or runs `bd create`69 directly for beads issues. /sibling-sync still owns nothing in Basic70 Memory and nothing in this project's SYNERGY/UPSTREAM files —71 ownership boundaries are unchanged.7273## Registry and path resolution7475Sibling projects are declared in `.claude/synergy-registry.json` (array of76`{name, file, remote, bm-entity, relationship, local-path?}` entries). The77optional `local-path` field gives the on-disk path to the sibling checkout78(relative paths resolve from this project root). When absent, fall back to79`../<name>/`.8081`.claude/synergy-registry.local.json` is a gitignored companion that overrides82fields in the committed registry — same per-entry merge by `name` pattern as83`.claude/vendor-registry.local.json` — **and** can add fully-private siblings84(see "Private sibling handling" below). Resolution order:85861. Read `.claude/synergy-registry.json`.872. If `.claude/synergy-registry.local.json` exists, merge it on top by `name`88 key, in two modes:89 - **Override mode** (entry `name` matches a base entry): fields in90 `.local.json` win; absent fields keep the base value.91 - **Private-add mode** (entry `name` not in the base registry AND its `file`92 is a `PRIVATE-SYNERGY-<name>.md` value): the entry is **added** to the93 merged result as a private sibling. The `PRIVATE-` prefix on `file` is the94 marker (there is no boolean); it governs the restrictions below.95 - Entries in `.local.json` whose `name` is not in the base registry and whose96 `file` is NOT `PRIVATE-SYNERGY-*` are **ignored** (backward compatibility —97 typos and accidental entries stay silent).983. For each merged entry, resolve `local-path` (registry value or99 `../<name>/`).1004. If the resolved path does not exist on disk, report informatively and SKIP101 that sibling. Do not error out — continue with siblings that are102 accessible.103104Workflow 3 (Sync sibling UPSTREAM) additionally consumes the merged105`.claude/vendor-registry.json` (+ `.local.json`) to identify shared vendor106dependencies across siblings. (The vendor registry has no private-add mode —107private siblings are synergy-registry only.)108109### Private sibling handling110111A **private sibling** is one whose merged registry entry has a112`file: PRIVATE-SYNERGY-<name>.md` value (added via private-add mode above, or —113in principle — a base entry, which the validator forbids because it would commit114the name). Its `name` lives only in the gitignored `.local.json` and115`PRIVATE-SYNERGY-<name>.md`; **it must never reach a committed file.** This skill116therefore treats private siblings under a strict read-vs-write split, keyed on117the `PRIVATE-SYNERGY-*` `file` predicate:118119- **Read (allowed) — hybrid read-diff.** Unlike a *public* sibling's120 glob-discovered `PRIVATE-SYNERGY-*.md` overlay (which this skill never reads),121 a private sibling's `PRIVATE-SYNERGY-<name>.md` **is** its registry `file`, so122 workflows 1 (Discover sibling(s)), 2 (Sync sibling SYNERGY), and 3 (Sync123 sibling UPSTREAM, Mode A only) read it to produce **read-only diff findings**.124 Findings appear in the **ephemeral terminal report only** — never written.125- **Write (blocked) — every committed surface.** For a `PRIVATE-SYNERGY-*`-filed126 sibling:127 - **Reciprocation:** workflow 4 (Apply reciprocation batch) skips it entirely128 (writing `SYNERGY-<this-project>.md` on the sibling's side would expose the129 relationship).130 - **`bd create`:** the action menu suppresses the UPSTREAM "file beads issues"131 option (a committed `.beads/*.jsonl` entry would leak the name) — findings132 stay report-only.133 - **Logging on this side:** any "log unreciprocated entry" follow-up routes to134 the gitignored `PRIVATE-SYNERGY-<name>.md`, never a committed135 `SYNERGY-<name>.md` (delegated to `/synergy-tracker`).136 - **BM promotion:** never (this skill has no BM tools; `/synergy-tracker`137 workflow 5 (Promote to Basic Memory) skips `PRIVATE-SYNERGY-*` siblings).138 - **UPSTREAM Mode B:** out of scope for private siblings — an139 `UPSTREAM-<name>.md` filename would itself leak the name. Workflow 3 (Sync140 sibling UPSTREAM) runs Mode A (shared dependencies — names no sibling) but141 skips Mode B for `PRIVATE-SYNERGY-*` siblings.142143The `PRIVATE-SYNERGY-*` `file` predicate is the single structural marker — the144same prefix that keeps content outside the `SYNERGY-*.md` glob also gates every145write path here.146147## Workflows148149Determine which workflow the user needs based on their request. If ambiguous,150default to running workflow 1 (Discover sibling(s)) followed by workflow 2151(Sync sibling SYNERGY) and workflow 3 (Sync sibling UPSTREAM) as a single152report. Workflow 4 (Apply reciprocation batch) only fires under explicit153`--auto-reciprocate`.154155### 1. Discover sibling(s)156157Resolve which siblings will participate in this run.158159**Steps:**1601611. Read `.claude/synergy-registry.json`. If the file does not exist, redirect:162 tell the user no sibling registry is configured and offer to invoke163 `/synergy-tracker` workflow 1 (Log a synergy entry), which will run the164 guided registry creation flow at step 1b for the first sibling. If the165 user names a sibling now, follow the synergy-tracker step 1b prose from166 this conversation (re-reading167 `skills/synergy-tracker/SKILL.md` workflow 1 (Log a synergy entry) step 1b168 and applying its logic in-session — Claude Code has no actual169 inline-skill-invocation mechanism, so this means executing step 1b's170 instructions verbatim from sibling-sync's context). After the registry171 is created, resume from step 2 below. Otherwise stop, and instruct the172 user to invoke `/synergy-tracker` directly with the sibling name and173 then re-run `/sibling-sync`.1742. If `.claude/synergy-registry.local.json` exists, merge it on top per the175 per-entry merge rules in the Registry section above. This includes176 **private siblings** — `.local.json`-only entries whose `file` is177 `PRIVATE-SYNERGY-<name>.md`, which are added to the participating set (they178 read-diff like any sibling but are blocked from every committed-write path —179 see "Private sibling handling").1803. If the user named a specific sibling in their request or argument, filter181 the merged list to that entry. Otherwise, all merged entries participate.1824. For each entry, resolve `local-path` → `../<name>/` fallback. Probe each183 resolved path with a directory existence check.1845. Build the participation lists:185 - **Accessible siblings** (path exists) — proceed to workflow 2 (Sync186 sibling SYNERGY) and workflow 3 (Sync sibling UPSTREAM) for each. Mark any187 private sibling (registry `file` is `PRIVATE-SYNERGY-*`) with a `[private]`188 label so the user can see which relationships are private.189 - **Inaccessible siblings** (path missing) — report them so the user190 knows what was skipped, with the resolved path and a hint that191 `.claude/synergy-registry.local.json` can override the path1926. Report the participation list before continuing.193194**Output:**195196```197Siblings participating:198- vp-knowledge → /Users/.../vp-claude (registry local-path)199- acme-partner → /abs/path/to/acme-partner [private]200201Siblings skipped (path not accessible):202- vp-other → ../vp-other (set local-path in synergy-registry.local.json)203```204205If no siblings are accessible, stop and report. The user can either correct206the paths via `.claude/synergy-registry.local.json` or accept that this run207has no work to do.208209### 2. Sync sibling SYNERGY210211For each accessible sibling from workflow 1 (Discover sibling(s)), compare212the bidirectional SYNERGY files and surface drift findings. Report only —213no writes.214215**Steps:**2162171. Read this project's SYNERGY file for the sibling, by the registry `file`218 value — `SYNERGY-<sibling>.md` for a public sibling, or219 `PRIVATE-SYNERGY-<sibling>.md` for a **private** sibling (its registry `file`;220 the hybrid read-diff exception). For a *public* sibling, never pull in a221 glob-discovered `PRIVATE-SYNERGY-*.md` overlay — the `PRIVATE-` prefix keeps222 those outside the `SYNERGY-*.md` namespace, so reading the committed file by223 name never touches them (see `/synergy-tracker` `### Private overlay`). For a224 private sibling, all downstream findings are read-only and stay in the225 ephemeral report (the write blocks in "Private sibling handling" apply). If226 the file is absent, treat as zero entries and proceed (the gap will surface as227 "Unreciprocated entries on sibling" if the sibling has any entries).2282292. Read the sibling's `SYNERGY-<this-project>.md` at230 `<resolved-local-path>/SYNERGY-<this-project>.md`. If absent, treat as231 zero entries.2322333. Parse each side's entries section-by-section (Shared Patterns,234 Divergences, Extraction Candidates, They Have / We Don't). Build a235 bidirectional entry map keyed by **title**, normalized per the rule in236 Guidelines below (2-pass matching: deterministic lead-clause pass, then237 judgment pass on residuals).238239 **Section migration is its own signal — not silently merged.** Matching240 is within-section: an entry's title is paired only with same-section241 titles on the sibling. If an entry has migrated sections on one side242 (e.g., Shared Pattern here, Divergence on sibling — typical when one243 side promoted after noticing drift), it surfaces as (a) on the244 originating section and (b) on the destination section, NOT as a245 finding (d) Status drift. The section migration is itself the drift246 signal worth surfacing, and the (a)/(b) framing tells the user247 precisely which side moved. Finding (d) applies within-section only.248249 **Section asymmetry — excluded from findings (a)/(b):** the250 `They Have / We Don't` section is intrinsically asymmetric. Entries here251 describe what the *sibling* has that we don't; reciprocally on the252 sibling's side, the same-named section describes what *we* have that253 they don't — a different semantic set. Bilateral title comparison is254 meaningless for this section. Skip its entries when computing findings255 (a) and (b). The user can read the section directly to act on adoption256 candidates; logging an adoption decision is `/synergy-tracker`'s job,257 not /sibling-sync's.2582594. Walk the merged map and classify each entry into one of four findings:260261 - **(a) Reciprocal gaps** — entries on this side with no matching title262 on the sibling. The sibling lacks the reciprocal entry. Candidates for263 workflow 4 (Apply reciprocation batch) under `--auto-reciprocate`.264 Excludes entries from `They Have / We Don't` (asymmetric — see step 3).265 - **(b) Unreciprocated entries on sibling** — entries on the sibling266 with no matching title here. The user may want to invoke267 `/synergy-tracker` workflow 1 (Log a synergy entry) to log these on268 this side. /sibling-sync does NOT write to this side automatically.269 Excludes entries from `They Have / We Don't` (asymmetric — see step 3).270 - **(c) Stale alignment claims** — entries with `Status: aligned` and271 `Last verified:` more than 8 sprints old (≈ two trend-review cycles).272 Inline threshold; canonical definition is `/synergy-tracker` workflow273 4 (Trend review (quarterly)). Treat 1 sprint ≈ 2 weeks if no other274 calibration is available; if the entry has no `Last verified:` field,275 fall back to the entry's date stamp.276 - **(d) Status drift** — matched entries whose `Status:` field differs277 across sides. Applies to two cases:278279 1. **Shared Patterns** where one side records `aligned` and the other280 records `drifting` or `diverging` (or any disagreement on the281 Status value). Often signals that one side has converged or282 re-diverged without the reciprocal note being refreshed.283 2. **Divergences** with `Convergence path: adopt-theirs` or284 `Convergence path: propose-shared` where one side has moved to285 `adopted`/`converged` while the other still says `drifting` or286 similar.287288 Excludes Divergences with `Convergence path: accept-difference` —289 those are intended-asymmetric and have no drift signal to flag.2902915. Output a structured report grouped first by sibling, then by finding292 category. Include each entry's title, both sides' values where they293 differ, and a one-line action hint per category.294295**Output format (per sibling):**296297```298## SYNERGY drift — vp-knowledge299300### (a) Reciprocal gaps (here, missing on sibling)301- "Hook event coverage" (Shared Patterns) — sibling has no matching entry302 → /sibling-sync --auto-reciprocate to file the reciprocal303304### (b) Unreciprocated entries on sibling305- "validate-plugin tool-reference audit" (Shared Patterns) — we don't track this306 → /synergy-tracker to log on this side307308### (c) Stale alignment claims (>8 sprints since Last verified)309- "PreCompact prompt command hook" — Last verified: 2026-01-15 (here),310 2026-01-15 (sibling). Re-verify now.311312### (d) Status drift313- "npm-run-all2 parallel check stages" (Shared Patterns) — Status here:314 diverging. Status sibling: aligned. Sibling converged; refresh this row.315- "BM section ownership scheme" (Divergences, propose-shared) — Status316 here: drifting. Status sibling: aligned.317```3183196. **Offer follow-up actions.** After workflow 3 (Sync sibling UPSTREAM)320 has also finished printing for this sibling, prepare the SYNERGY tier of321 the action menu (built from this workflow's findings) and let workflow 3322 (Sync sibling UPSTREAM) step 6 pair it with the UPSTREAM tier into a323 single `AskUserQuestion` call. Do NOT issue the prompt from this step —324 the prompt is dispatched from workflow 3 (Sync sibling UPSTREAM) step325 6 once both reports are on screen. Full protocol — which findings map to which menu options,326 the `header` values, the `--auto-reciprocate` precedence rule, and the327 plugin-namespaced `Skill` invocations — lives in the "Action-menu328 protocol" section below.329330### 3. Sync sibling UPSTREAM331332For each accessible sibling, build two kinds of UPSTREAM file pairs and333compare friction tracking on each. Same report-only contract as workflow 2334(Sync sibling SYNERGY).335336**Two pairing modes coexist; both can fire on a single sibling:**337338- **Mode A — shared-dependency pairing.** Both sides have `UPSTREAM-<dep>.md`339 with the same basename (e.g., both have `UPSTREAM-basic-memory.md`). The340 files describe the same third-party dependency `<dep>`. Findings (a)–(d).341- **Mode B — reciprocal sibling-friction pairing.** This project has342 `UPSTREAM-<sibling-name>.md` (friction we log about the sibling) AND/OR the343 sibling has `UPSTREAM-<this-name>.md` (friction the sibling logs about us).344 Different basenames; same bilateral relationship. Owner-side semantics345 invert relative to Mode A: an entry in the sibling's `UPSTREAM-<this-name>.md`346 with `Ownership: upstream` means THIS project is the upstream that must347 act. Findings (e)–(h).348349**Steps:**3503511. **Build Mode A pairs.** Glob this project for `UPSTREAM-*.md`. Glob the352 sibling's resolved `local-path` for `UPSTREAM-*.md`. Compute the353 intersection by basename — each match is one Mode A pair. Record both354 sides' full UPSTREAM basename lists for use in step 2.3552. **Detect Mode B pair.** **Skip Mode B entirely for private siblings**356 (merged registry `file` is `PRIVATE-SYNERGY-*`): a Mode B file is named357 `UPSTREAM-<sibling-name>.md`, whose filename would commit the private name.358 Private siblings are SYNERGY-only; Mode A above (keyed on shared *dependency*359 names, never the sibling) still runs. For a non-private sibling, derive this360 project's canonical name per the four-tier algorithm in361 `skills/synergy-tracker/references/project-name-derivation.md` to362 compute `<this-name>`. Apply the same algorithm (tier 3 for the sibling363 subject) to the registry `name` field for `<sibling-name>`.364 **Stale `local-path` guard:** if the registry entry specifies a365 `local-path` that does not resolve to an accessible directory, tier 1366 (sibling-registry back-pointer) silently falls through to tier 2367 (this project's `plugin.json`). Warn the user before falling through:368 "Sibling local-path is not accessible — tier 1 derivation skipped;369 `<this-name>` may diverge from how the sibling registered this project.370 Update `.claude/synergy-registry.local.json` if the sibling moved."371 Then check:372373 - Does the sibling have `<resolved-local-path>/UPSTREAM-<this-name>.md`?374 - Does this project have `UPSTREAM-<sibling-name>.md`?375376 If either file exists, this sibling has a Mode B pair (one-sided or377 two-sided). Both files absent is normal — no reciprocal friction tracked378 on either side. Skip Mode B for this sibling and continue.379380 The Mode B file pair is `<this-root>/UPSTREAM-<sibling-name>.md` ↔381 `<sibling-root>/UPSTREAM-<this-name>.md`. By construction these basenames382 differ from any Mode A pair's basename (Mode A keys on shared383 third-party dep names; Mode B keys on sibling project names that appear384 in the synergy registry). No deduplication guard needed.3853. **Process Mode A pairs.** For each Mode A pair, read both copies and386 parse entries (Bugs, Feature Requests, Upstream Opportunities,387 Resolved). Build a bidirectional entry map by **title** using the same388 2-pass matching rule as workflow 2 (deterministic lead-clause Pass 1 +389 judgment Pass 2 on residuals — see Guidelines). UPSTREAM titles are390 typically more structured than SYNERGY titles, so Pass 2 fires less391 often, but the rule is identical for consistency. Classify each entry:392393 - **(a) Duplicate friction** — same title on both sides. Sanity check:394 are the workarounds, dates, and status fields aligned? If not, it's a395 candidate for category (b).396 - **(b) Complementary workarounds** — same title both sides but the397 `Workaround:` field (or equivalent) differs. The sibling may have398 found a better mitigation. Flag for cross-pollination.399 - **(c) Stale entries** — entries dated more than 3 months ago without a400 Trend Review annotation since. Either side. Stale ≠ wrong, but worth401 re-verifying.402 - **(d) Sibling-only entries** — friction the sibling tracks for a403 shared dependency that we don't. Potential adoption: invoke404 `/upstream-tracker` workflow 7 (Sync from Basic Memory) or workflow 1405 (Log a new entry) to bring matching entries over here. /sibling-sync406 does NOT write here automatically.4074. **Process Mode B pair.** Read whichever side(s) of the pair exist.408 Parse entries the same way as step 3. Match titles bidirectionally with409 the same 2-pass rule. Classify each entry:410411 - **(e) Sibling's unresolved friction against this project** — entries412 in `<sibling-root>/UPSTREAM-<this-name>.md` that are NOT prefixed with413 `_(Resolved ...)_` and NOT in a `## Resolved` section if one exists.414 `Ownership: upstream` on these entries means THIS project owns the415 fix (we are upstream from the sibling's perspective). Surface ALL416 unresolved entries — every one is a request directed at us. Action417 hint: file beads issues here or address inline; consider logging a418 cross-reference in our `UPSTREAM-<sibling-name>.md` if a workaround419 is built.420 - **(f) Our unresolved friction against the sibling** — entries in421 `<this-root>/UPSTREAM-<sibling-name>.md` that are unresolved on our422 side and have no corresponding `_(Resolved ...)_` annotation on423 either side. Informational: documents work blocked on the sibling.424 Action hint: check sibling release notes or changelog for shipped425 fixes the sibling forgot to annotate.426 - **(g) Cross-side staleness — our entry, sibling may have shipped.**427 Entry in `<this-root>/UPSTREAM-<sibling-name>.md` unresolved on our428 side, but the sibling shows a "shipped" signal (see "What 'shipped'429 means" below). Use 6 months as the look-back horizon for git-log430 scanning. Action hint: re-verify against the sibling's current431 release; annotate with `_(Resolved ...)_` via `/upstream-tracker`432 workflow 3 (Resolve an entry) if confirmed shipped.433 - **(h) Reverse cross-side staleness — sibling tracks us, we may have434 shipped.** Entry in `<sibling-root>/UPSTREAM-<this-name>.md` unresolved435 on the sibling's side, but this project shows a "shipped" signal436 (recent CHANGELOG entry, `_(Resolved ...)_` in our cross-reference,437 or git tag/commit subject within 6 months matching the entry title).438 Read-only finding: /sibling-sync cannot write the sibling's file.439 Action hint: notify sibling maintainer, or raise on their side via440 `/upstream-tracker` workflow 3 (Resolve an entry) so they can441 annotate.442443 **What "shipped" means** (pinned definition for findings (g) and (h)):444 a fix is shipped when (1) a CHANGELOG or `_(Resolved ...)_` annotation445 exists on the owner's side, OR (2) the feature/fix is referenced in a446 git tag message or commit subject within the relevant release window447 (use `git -C <owner-path> log --oneline --since="6 months ago"` as a448 heuristic proxy — string-match the entry title or its lead clause; do449 not parse). A `Workaround: full` on the filing side without a450 corresponding shipped version on the owner's side is NOT sufficient;451 that is the filing project's mitigation, not upstream resolution.4525. **Output.** Report Mode A findings first (grouped by sibling, then by453 shared dependency, then by finding category), then Mode B findings454 (grouped by sibling) under a separate header. This ordering keeps the455 existing Mode A output shape intact and adds Mode B as an additive456 block.457458**Note on UPSTREAM coverage gaps:** /sibling-sync now handles two cases:459shared third-party dependencies (Mode A, basename intersection) and460reciprocal sibling-friction pairs (Mode B, inverse-name detection).461One-sided UPSTREAM files about non-sibling, non-shared dependencies are462still out of scope — those are the sibling's responsibility to discover463via `/upstream-tracker` workflow 7 (Sync from Basic Memory) on its own.464465**Output format additions for Mode B:**466467```468## UPSTREAM reciprocal-friction — vp-knowledge469470(Mode B: this-side UPSTREAM-vp-knowledge.md ↔ sibling-side UPSTREAM-vp-beads.md)471472### (e) Sibling's unresolved friction against this project (we should action)473- "vp-beads: new /sibling-sync skill" (Feature Requests, 2026-05-04) — sibling474 marks Workaround: partial; we shipped in v0.12.0. See finding (h).475- "synergy-tracker: mandate bilateral reciprocation" (Feature Requests, 2026-05-04)476 Ownership on their side: upstream (us) · Workaround on their side: full477 → file beads issue or address inline478479### (f) Our unresolved friction against the sibling480- "Agent effort defaults not overridable from parent" (Feature Requests, 2026-04-05)481 Ownership: upstream (them) · Workaround: none482483### (g) Cross-side staleness: our entry the sibling may have shipped484- (none this run)485486### (h) Reverse staleness: sibling tracks us but we may have shipped487- "vp-beads: new /sibling-sync skill" — v0.12.0 tag (2026-05-05) matches.488 Sibling should annotate _(Resolved 2026-05-05, vp-beads v0.12.0)_.489 → notify sibling maintainer; cannot write their file from here490```4914926. **Offer follow-up actions.** This is the dispatch point. After this493 workflow finishes printing (and workflow 2 (Sync sibling SYNERGY) has494 already finished for the same sibling), build a single `AskUserQuestion`495 call combining workflow 2 (Sync sibling SYNERGY)'s SYNERGY tier with496 this workflow's UPSTREAM tier. The "Action-menu protocol" section below497 specifies the full UPSTREAM tier: findings (b) and (d) collapse into a498 single "Update our UPSTREAM" option that delegates to499 `/vp-beads:upstream-tracker`; finding (e) routes directly to `bd500 create`; finding (g) delegates to `/vp-beads:upstream-tracker` workflow501 3 (Resolve an entry). Findings (a), (c), (f), and (h) are informational502 and not present in the menu. If no UPSTREAM findings are actionable AND503 no SYNERGY findings are actionable for this sibling, skip the prompt504 for this sibling. If only one tier has actionable findings, issue a505 single-question call.506507### 4. Apply reciprocation batch508509Opt-in mutation path. Only runs when the user supplies `--auto-reciprocate`510in their invocation, or explicitly confirms intent like "yes, apply all the511reciprocal gaps". Mirrors `/upstream-tracker` workflow 7 (Sync from Basic512Memory)'s per-entry confirmation pattern.513514**Steps:**5155161. **Exclude private siblings first.** Drop any sibling whose merged registry517 `file` is `PRIVATE-SYNERGY-*` before doing anything else — reciprocation would518 write `SYNERGY-<this-project>.md` on the sibling's side and expose that the519 private relationship exists. Announce each exclusion: "Skipping reciprocation520 for `<name>` — private sibling (existence must not cross to the sibling's521 side)." This guard runs at the top of the loop, before any read of the522 sibling's SYNERGY file.5232. Re-run workflow 2 (Sync sibling SYNERGY) finding (a) (reciprocal gaps)524 for each remaining (non-private) accessible sibling, applying the stricter525 matching rules from the Hard Limits section below: Pass 1 (deterministic)526 matches only; any Pass 2 (judgment) matches from workflow 2 (Sync sibling527 SYNERGY) are added back to the reciprocation queue with an extra528 disambiguation prompt rather than suppressed silently. These are the entries529 on this side that the sibling demonstrably lacks.5303. For each reciprocal gap, in order:531 1. Read the source entry from this project's `SYNERGY-<sibling>.md`532 (full entry text including title, date, structured fields).533 2. Determine the destination file at the sibling:534 `<resolved-local-path>/SYNERGY-<this-project>.md` (derive535 `<this-project>` per536 `skills/synergy-tracker/references/project-name-derivation.md`).537 If it does not exist yet, plan to `Write` a new file using the538 four-section template from539 `skills/synergy-tracker/references/synergy-entry-format.md`.540 3. Determine the destination section from the source entry's section541 (a Shared Pattern on this side becomes a Shared Pattern on the542 sibling, etc.).543 4. Show the user: source entry text + destination file path +544 destination section. Ask: "Write reciprocal entry to545 `<sibling-path>/SYNERGY-<this-project>.md` under `### <Section>`?546 \[y/n/skip-rest]".547 5. On `y`: append the entry under the destination section using `Edit`548 (or `Write` if the file is new). Replace any549 `_No entries yet._` placeholder in that section with the entry. Keep550 the entry text *as-is* from this side — do not rewrite to the551 sibling's voice; reciprocation IS the verification step (per552 `/synergy-tracker` workflow 1 (Log a synergy entry) bilateral553 mandate). The sibling will re-verify on their next reciprocation554 pass.555 6. On `n` or `skip-rest`: skip and continue (or stop the batch on556 `skip-rest`).5574. After the batch, report:558 - Entries written, with destination file paths559 - Entries skipped, with reason560 - **Verification reminder for the user**: run `git status` in the561 sibling repo, review the appended entries, commit on that side. /sibling-sync562 does not commit on the sibling's behalf. Also remind the user to file563 a beads follow-up on the sibling for re-verification next sprint, per564 `/synergy-tracker` workflow 1 (Log a synergy entry)'s reciprocation565 mandate.566567**Hard limits on workflow 4 (Apply reciprocation batch):**568569- Only mirrors entries from workflow 2 (Sync sibling SYNERGY) finding (a).570 Does NOT mirror UPSTREAM entries from workflow 3 (Sync sibling UPSTREAM)571 — `/upstream-tracker` workflow 7 (Sync from Basic Memory) is the right572 channel for cross-project UPSTREAM adoption (BM is the cross-project573 bridge for friction; SYNERGY is the cross-project bridge for patterns).574 This applies equally to Mode A findings (a)–(d) AND Mode B findings575 (e)–(h): finding (e) entries get filed natively on this side via576 `/upstream-tracker` workflow 1 (Log a new entry), not mirrored;577 finding (h) annotations get written by the sibling via their own578 `/upstream-tracker` workflow 3 (Resolve an entry), not by us.579- **Never sources a reciprocal entry from a `PRIVATE-SYNERGY-*.md` private580 overlay.** Those entries are private to this checkout (the proprietary↔public581 boundary); writing one to a sibling would leak it. This is enforced582 structurally: reciprocation reads the committed `SYNERGY-<project>.md` (by583 exact name, workflow 2 (Sync sibling SYNERGY) step 1), and the `PRIVATE-`584 prefix keeps overlays outside the `SYNERGY-*.md` namespace so no glob here can585 reach them. See586 `/synergy-tracker` `### Private overlay`.587- **Never reciprocates for a private sibling** (merged registry `file` is588 `PRIVATE-SYNERGY-*`). Step 1 of this workflow excludes them before the loop;589 writing `SYNERGY-<this-project>.md` on the sibling's side would commit the590 private relationship's existence to their repo. See "Private sibling591 handling".592- Never mirrors entries from `## They Have / We Don't`. The section is593 intrinsically asymmetric (entries here describe sibling capabilities594 WE lack; the sibling's same-named section describes the inverse595 asymmetry). Workflow 2 (Sync sibling SYNERGY) already excludes this section from finding (a),596 but this is restated here as a mutation-side guard: even if a future597 edit relaxes the workflow 2 (Sync sibling SYNERGY) exclusion, workflow 4 (Apply reciprocation batch) must never write a598 `They Have / We Don't` entry to the sibling.599- The reciprocal-gap list is computed using **Pass 1 matches only**.600 Entries that paired via Pass 2 (judgment) in workflow 2 (Sync sibling SYNERGY) are added back601 to the reciprocation queue and presented to the user with a flag:602 "This entry may already exist on the sibling as `<pass-2-matched-title>`603 — does that match? \[y=skip / n=write reciprocal anyway / skip-rest]".604 Defaulting to caution at the mutation boundary inverts the read-only605 cost asymmetry: under `--auto-reciprocate`, suppressing a write that606 should happen (false-positive Pass 2 match) is more expensive than607 proposing a duplicate the user can reject (false-negative).608- Never writes to `## Trend Reviews` sections on either side.609- Never writes to this project's side. Reciprocal entries go to the610 sibling only — logging on this side is `/synergy-tracker` workflow 1611 (Log a synergy entry)'s job.612- Never writes to Basic Memory (no BM edit tooling allowed in this skill).613 BM writes are `/synergy-tracker` workflow 5 (Promote to Basic Memory)614 and `/upstream-tracker` workflow 6 (Promote to Basic Memory)'s615 territory.616617## Action-menu protocol618619This skill never writes SYNERGY/UPSTREAM/BM directly — even the menu options620dispatch to the owning skill via the `Skill` tool, or run `bd create` for621beads issues. The menu is a navigation aid, not a write path. The default622read-only contract from earlier versions still holds: a user who picks623"None" for both questions receives the report and exits without any624mutation.625626After workflows 2 (Sync sibling SYNERGY) and 3 (Sync sibling UPSTREAM) have627printed their per-sibling reports, sibling-sync issues a single628`AskUserQuestion` call with up to two single-select questions per sibling.629The `AskUserQuestion` SDK contract caps options at 2-4 per question630(plus an auto "Other"); we therefore split SYNERGY and UPSTREAM into631separate questions rather than one flat menu. Skipping a question is just632selecting its "None" option; both tiers default to read-only on skip.633634### Two-tier menu shape635636**Q1 — SYNERGY follow-up** (`header: "Synergy"`, 7 chars). Options listed637only when their finding count is nonzero:638639| Option | Trigger | Dispatch |640| ----------------------------------------- | --------------- | --------------------------------------------------------------------------------------- |641| 1. Apply reciprocal gaps (N) | finding (a) > 0 | re-enter workflow 4 (Apply reciprocation batch) in-skill — no `Skill` call |642| 2. Log unreciprocated sibling entries (N) | finding (b) > 0 | `Skill(skill="/vp-beads:synergy-tracker", args=...)` → workflow 1 (Log a synergy entry) |643| 0. None — synergy report only | always | exit SYNERGY tier without action |644645**Q2 — UPSTREAM follow-up** (`header: "Upstream"`, 8 chars). Options listed646only when their finding count is nonzero. Findings (b) and (d) collapse647into a single option to keep the question within the 4-option SDK cap:648649| Option | Trigger | Dispatch |650| ----------------------------------------------- | ---------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |651| 1. Update our UPSTREAM (b/d, N total) | finding (b) > 0 OR finding (d) > 0 | `Skill(skill="/vp-beads:upstream-tracker", args=...)` — args route to workflow 1 (Log a new entry) and/or workflow 7 (Sync from Basic Memory) inside upstream-tracker |652| 2. File beads issues for sibling's friction (N) | finding (e) > 0 | `Bash` → `bd create` per entry |653| 3. Resolve cross-stale entries (N) | finding (g) > 0 | `Skill(skill="/vp-beads:upstream-tracker", args=...)` → workflow 3 (Resolve an entry) |654| 0. None — upstream report only | always | exit UPSTREAM tier without action |655656If neither tier has actionable findings for a sibling, skip the657`AskUserQuestion` call entirely for that sibling. If only one tier has658actionable findings, issue a single-question call (the SDK supports 1-4659questions per call).660661**Private-sibling guard (no-commit-leak).** When the sibling's merged registry662`file` is `PRIVATE-SYNERGY-*`, every committed-write menu option is removed663because it would commit the private name:664665- **Q2 option 2 (`bd create`) is suppressed** — a `.beads/*.jsonl` entry naming666 the sibling would leak it. The finding stays in the report only. (Finding (e)667 does not arise anyway: workflow 3 (Sync sibling UPSTREAM) skips Mode B for668 private siblings.)669- **Q1 option 2 (Log unreciprocated sibling entries) redirects** — its670 `/synergy-tracker` dispatch targets the gitignored `PRIVATE-SYNERGY-<name>.md`,671 never a committed `SYNERGY-<name>.md` (pass the private destination in the672 `args` prose).673- **Q1 option 1 (Apply reciprocal gaps) is absent** — workflow 4 (Apply674 reciprocation batch) already excludes private siblings.675676If that leaves no actionable options for a private sibling, skip the prompt and677present the read-only findings as report-only.678679### Per-action argument templates680681Pass natural-language prose in the `Skill` tool's `args` field. The682delegated skill receives the prose as narrative context. Templates:683684- **SYNERGY 2 (Log unreciprocated sibling entries):**685 `Log unreciprocated entries from sibling <sibling-name>: <bullet list of titles + sections>. Invoke workflow 1 (Log a synergy entry) for each.`686- **UPSTREAM 1 (Update our UPSTREAM, b/d collapse):**687 `From sibling-sync findings against <sibling-name>: adopt complementary workarounds for <package, title> entries (sibling's workaround text: <quoted>); also scan sibling-only entries <package, titles>. Use workflow 1 (Log a new entry) and workflow 7 (Sync from Basic Memory) as appropriate.`688- **UPSTREAM 3 (Resolve cross-stale entries):**689 `Resolve entries in UPSTREAM-<sibling-name>.md: <titles>. Verify against the sibling's recent changelog/tags first. Invoke workflow 3 (Resolve an ent690691…(truncated)