本文へ移動
cccskills
無料GitHub で公開

sync

Synchronize recent research learnings across all affected theses, sector notes, and macro documents. Use after a research session or when user says "sync", "propagate", or "update everything".

インストール方法を見る

含まれるファイル(2)

  • SKILL.md68.0 KB
  • RATIONALE.md24.6 KB

SKILL.md(原文)

インストールする前に、エージェントに与えられる指示の中身を確認できます。

Propagate recent research insights across all affected vault documents. For each affected note, think through the full implications of new research on every substantive section — not just append links.

Design rationale in .claude/skills/sync/RATIONALE.md (§N.M anchors).

Shared contracts this skill consumes:

  • .claude/skills/_shared/preflight.md — vault locks, rename markers, name sanitization, section probes.
  • .claude/skills/_shared/wikilink-forms.md — the 5 canonical wikilink forms every matcher must cover.
  • .claude/skills/_shared/hot-md-contract.md — _hot.md compression budget and section ordering.
  • .claude/skills/_shared/log-prefixes.md — registry of Log-entry prefixes carrying cross-skill semantics.
  • .claude/skills/_shared/sector-resolution.md — thesis sector: → sector note resolution.
  • .claude/skills/_shared/mental-models-section.md — the ## Mental Models section schema, the MANDATORY /Mental Models reading gate, and the merge/idempotency write procedure.

Step 0: Pre-flight (MANDATORY — runs before Step 1)

0.1: Acquire vault lock

  • /sync (default) and /sync all: acquire vault-wide scope lock per preflight Procedure 1. Timeout: 15 min (/sync all), 5 min (default).
  • /sync TICKER: acquire ticker:TICKER scope lock. Timeout: 5 min.

Capture the token at Step 0.1, verify ownership (Procedure 1.5) at every Bash block, release via rm -f "$LOCK_FILE" in the final reporting Bash block.

0/1 Parallel Probe Directive

Step 0.2 (rename-marker glob), Step 1.1 (find -newer), and Step 1.2 (Read _graph.md) are independent read-only probes that fire AFTER lock acquisition and BEFORE any mutation. Issue them as a single parallel tool-call batch — one message containing the .rename_incomplete.* glob, the find -newer .last_sync Bash, and the _graph.md Read. Ownership verification on subsequent Bash blocks (per preflight §1.5) still runs on every mutation block; this batch only collapses three sequential read round-trips into one. On rare lock-collision edge cases where another skill is mid-release, the wasted work is negligible (milliseconds of find + one Read).

0.2: Rename-marker check

  • /sync TICKER: preflight Procedure 2. Hard-block if .rename_incomplete.TICKER exists.
  • /sync / /sync all: glob .rename_incomplete.*. Hard-block if ANY marker exists — these modes propagate across the vault; a single mid-rename ticker would split inbound wikilinks across old/new names.

0.3: Metadata boundary

/sync does NOT write to _graph.md. Graph maintenance is owned exclusively by /graph (run /graph last after this skill). /sync reads _graph.md as an input; never writes it.

Sync Modes

Parse $ARGUMENTS:

  • /sync (default) — Graph-assisted targeted sync. Uses _graph.md if present; file-direct fallback only when graph returns empty for a given source (§2.2). Use after post-ingest propagation.
  • /sync all — Full brute-force sync via graph-cached two-pass triage. Use after major research sessions or monthly maintenance.
  • /sync TICKER — Ticker-scoped sync. Reads the thesis directly and reconstructs adjacency from outbound wikilinks. No graph dependency.

Watermark

Change detection uses .last_sync as the timestamp watermark. Touch policy:

Mode.last_sync behavior.sync_all_fresh marker
/sync (default)Touch at end—
/sync allTouch at endWrite at end (triggers /graph full rebuild)
/sync TICKERDo NOT touch (§6.1)—

First run: if .last_sync does not exist, create it with epoch timestamp: touch -t 197001010000 .last_sync. This applies to ALL modes (including ticker-scoped — epoch placeholder preserves baseline without advancing the watermark).


Step 1: Identify Changes

1.1: Find changed files

If $ARGUMENTS specifies files, use those. Otherwise:

find Research/ Theses/ "Macro & Technology/" Sectors/ -newer .last_sync -name '*.md'

If no changed files AND no files specified → report "Nothing changed since last sync" and stop (skip Step 2.9 manifest — no-op runs are pure read).

1.2: Resolve propagation targets per changed research note

For each changed research note, the target set is the UNION of every thesis plausibly affected.

Primary resolution — graph-assisted: if _graph.md exists, look up the research note in each thesis's research: adjacency. Collect matches.

File-direct fallback (T7.2 — conditional): run ONLY when graph-assisted returned EMPTY for this research note OR _graph.md is missing/unparseable. Resolves via:

  • (a) Research note's ticker: frontmatter matching Theses/[TICKER] - *.md
  • (b) Outbound [[Theses/...]] wikilinks in the research note body
  • (c) tags: containing ticker-shaped tokens (all-uppercase, 1-5 chars) matching Theses/[TICKER] - *.md

When graph returned non-empty, trust the graph — see §2.2 for why. /lint #23 catches residual reverse-index drift.

1.2.5: Resolve SECTOR + MACRO propagation targets per changed research note

Step 1.2 resolves research note → thesis targets. This step does the same for sector and macro targets — explicitly at Step 1 time, not lazily inside Step 4.-1 / Step 5.-1 gates. Symmetric with 1.2; prevents the cognitive shortcut where thesis-idempotent research notes cause Step 4 / Step 5 to be erroneously skipped via mental over-reach of the Step 2 source-dedup clause (see §1.11 for the 2026-04-22 failure case that motivated this step).

For each changed research note, compute two maps:

sector_targets_per_research_note[note]:

  • (a) sector: frontmatter → resolve via .claude/skills/_shared/sector-resolution.md to a Sectors/*.md path.
  • (b) Body wikilinks matching any of the 5 canonical forms from _shared/wikilink-forms.md against [[Sectors/...]] / [[Sectors/name]] / [[Sectors/name.md]] — resolved via sector-resolution contract.

Union (a) ∪ (b); dedup on resolved path.

macro_targets_per_research_note[note]:

  • (a) Body wikilinks matching [[Macro & Technology/...]] forms (5 canonical forms).
  • (b) source_type: scenario frontmatter OR tags: containing macro → body-scan for any Macro & Technology/ references (treat as macro-focused note even without explicit body wikilink).
  • (c) Optional macro: frontmatter field if the note convention uses it.

Union across (a)/(b)/(c); dedup on resolved path.

These maps are inputs to Step 4.-1 condition (ii) and Step 5.-1 condition (ii) as direct lookups — not re-derived at gate time. Empty set is a valid value (research note has no sector/macro relevance); the gate still fires its skip path correctly in that case.

Authoritative-guard boundary: Step 4.-1 / 5.-1 remain the authoritative skip gates. Step 1.2.5 only makes the gate's inputs explicit and first-class. Do not duplicate the gate logic here.

1.3: Resolve propagation targets per changed macro note (batched — T7.6)

Reverse-index resolution (primary): Use _graph.md's Reverse Index: Macro → Theses table for each changed macro.

Body-grep fallback (T7.6 — one batched regex): ALWAYS runs to catch asymmetric adjacency (thesis wikilinks macro but macro doesn't reciprocate — §2.1). For a set of changed macro basenames {X, Y, Z}, issue ONE Grep:

Grep pattern='\[\[(Macro & Technology/)?(X|Y|Z)(\.md)?(#[^\]|]+)?(\|[^\]]+)?\]\]' path='Theses/' glob='*.md' output_mode='files_with_matches'

(Basenames regex-escaped; alternation built from the changed-macro set.) Output gives file → needs Grep match re-parse to identify which macro(s); a second pass on the returned file subset extracts specific basename matches. Two Grep calls total, regardless of changed-macro count. Dedup against reverse-index results.

1.4: Resolve propagation targets per changed sector note

Graph-assisted only. Sector notes are required by template to list ## Active Theses with every active thesis. /lint #2 catches missing MOC entries.

1.5: Closed-status filter (T7.7 — single Grep)

Before detailed idempotency work, filter out theses whose status is closed. ONE Grep vault-wide:

Grep pattern='^status: closed' path='Theses/' glob='*.md' output_mode='files_with_matches'

For every thesis in the closed set that also appears in a target set from 1.2/1.3/1.4:

⚠️ Skipped [TICKER] — status: closed but file remains in Theses/ (failed archive move). Complete: mv "Theses/[file]" "_Archive/[file]" → /graph last. Or reopen: /status TICKER status closed→active [rationale].

1.6: Missing-file resilience

If a thesis listed in graph adjacency no longer exists on disk (archived since last graph rebuild):

⚠️ Skipped [filename] — file not found (likely archived). Run /graph last to refresh the dependency map.

Research notes with no resolvable target via 1.2:

ℹ️ [filename] — no propagation target found. Consider adding ticker: frontmatter or [[Theses/...]] wikilinks, or run /thesis [TICKER] to create the missing thesis.

1.7: Unified dedup + per-thesis idempotency (T7.1)

For each changed research note, run ONE Grep pass to find every thesis that might already reference it — using the 5 wikilink forms from _shared/wikilink-forms.md. This single scan serves Case 2a, Case 2b, and Check 3 simultaneously (§1.5).

Per-research-note primary grep (base-name escaped for regex metacharacters):

Grep pattern='\[\[(Research/)?<base-escaped>(\.md)?(#[^\]|]+)?(\|[^\]]+)?\]\]' path='Theses/' glob='*.md' output_mode='files_with_matches'

Log-section confirmation (semantics preservation): the primary grep matches the wikilink ANYWHERE in the thesis file, but idempotency is defined by prior Log entries, NOT by wikilinks in ## Related Research (the user may have manually wikilinked without a Log entry, in which case /sync should still write the Log entry). Confirm in-Log presence via one Bash block across the primary match set:

# ESC_PATTERN is the same regex as the primary grep, escaped for awk.
for f in <files-returned-by-primary-grep>; do
  awk '/^## Log/{in_log=1; next}
       in_log && /^## /{exit}
       in_log && ENVIRON["ESC_PATTERN_MATCH"] ~ $0{found=1}
       END{if(found) print FILENAME}' "$f"
done

Alternatively (cleaner): use grep -n to get the line number of each primary-grep match, then intersect with the ## Log section's line range (extractable via grep -n '^## ' "$f" once per file). Either approach confirms "is match line within ## Log section" — files where the only match lies in ## Related Research or elsewhere are NOT in the confirmed-Log set.

The result — wikilink_match_set — is the set of thesis files with confirmed in-Log references to this research note. These are the already-propagated targets.

This two-pass structure keeps the token-saving win of T7.1 (primary grep is O(1) per research note vs old O(N) Read per candidate) while preserving the original Log-section-only idempotency semantics.

Now classify the research note's propagated_to: frontmatter:

Case 2a — Field absent: no producer-side dedup info. For each ticker in the Step 1.2 target set:

  • If that thesis is in wikilink_match_set → treat as already-propagated; skip. Log: ℹ️ Log-history backfill skip: [TICKER] already has Log entry referencing [research-note]. propagated_to: absent; retry suppressed.
  • Otherwise → check Step 1.8 secondary keys; if any match → skip (rename/move case). Else → this ticker needs propagation; proceed to Step 2.
  • After successful propagation completes, backfill the research note's propagated_to: frontmatter as a union of wikilink_match_set (resolved to tickers) + freshly-propagated tickers + any secondary-key-matched tickers. See Atomicity rule below.

Case 2b — Field present and non-empty (e.g., propagated_to: [TICKER1, TICKER2]):

  • Tickers in propagated_to: → skip. Log: ℹ️ Skipped [TICKER] — producer skill already propagated (in propagated_to).
  • Augmented targets (tickers in Step 1.2 target set but NOT in propagated_to:): for each, check wikilink_match_set. If present → skip (already propagated via another path). If absent → check Step 1.8 secondary keys; if any match → skip; else → propagate. Log: ➕ Augmented propagation: [TICKER] — not in propagated_to but resolved via [wikilink | tag | ticker frontmatter].

Case 2c — Field present and empty (propagated_to: []): terminal skip for ALL targets. Log once: ℹ️ Skipped all propagation for [research-note] — empty propagated_to: signals producer-side terminal decision. Do NOT evaluate augmented targets. Producer must explicitly opt-out with [] to trigger this case (§1.6).

1.8: Secondary-key fallback for wikilink misses (T7.1 refinement)

When a Step 1.2 target thesis is NOT in wikilink_match_set but the user may have renamed the research note (wikilink drift), check stable identity keys in order. Reads candidate thesis Log section plus at most one research-note frontmatter per candidate.

Key A — source: URL match: if the current research note's source: is a URL (not "vault synthesis"), scan the thesis's ## Log section for Log entries whose cited research notes share the same URL. Requires cross-read of candidate research notes' frontmatter. Match → treat as already-propagated.

Key B — date: + ticker: tuple match: for vault-synthesis notes without URLs (/scenario, /stress-test, /compare outputs). Scan thesis Log for entries citing Research/ notes with the same date: AND same ticker: tuple.

Key C — tags: intersection + date match: weakest. Current note's tags include ticker-shaped tokens overlapping thesis's ticker:, AND thesis Log has an entry citing a Research/ note whose tags: include the same token AND that entry's date matches current note's date:.

On any match: ℹ️ Skipped duplicate propagation for [TICKER] — Log references equivalent research note ([key type]: source URL | date+ticker | tags). Current: [[Research/current-name]]. Prior match: [detail].

Secondary keys fire rarely (only on rename/move events); their cost is bounded by the size of Step 1.2 target set minus wikilink_match_set.

1.9: Atomicity rule for propagated_to: updates

Update the research note's propagated_to: frontmatter ONLY after EVERY target thesis's Log entry has landed successfully for this research note in this run. If any Edit failed:

  • Do NOT update propagated_to: at all for this research note.
  • The next /sync retries the missing target via Case 2a flow.

If every append succeeded, union existing propagated_to: with the newly-propagated tickers AND with any tickers found via Case 2a backfill scan. This moves Case 2a retries into Case 2b on future runs, eliminating the scan cost.

Atomicity scope: ONE research note. Partial success across multiple research notes in the same run is fine — each gets its own all-or-nothing decision.

Per-research-note append-failure reporting: list failed targets in Step 8 under propagated_to: update skipped.

1.10: Mode-specific resolution continued

All mode (graph-cached two-pass — T7.3)

Pass 1 — graph-cached triage (no thesis file reads):

  1. Read _graph.md ONCE. Parse the Adjacency Index with status: and log_tail: fields.
  2. Read ALL macro notes in full (bounded set, ~6 files).
  3. For each thesis, classify using graph data alone:
    • High delta (any of):
      • Self-modified: thesis file mtime newer than .last_sync. Captures manual edits with no Log entry (§3.1). Always escalates; read full thesis in Pass 2.
      • Directly referenced by a changed research note (from Step 1.2 results), OR
      • Shares sector with changed content (graph reverse index), OR
      • Has cross-thesis links to changed theses (graph adjacency), OR
      • log_tail: shows recent conviction/status/reaffirm prefix within last 14 days.
    • Low delta: same macro exposure or adjacent sector, no direct reference, not self-modified. Read Summary + Non-consensus Insights + Outstanding Questions + Bull/Bear Case only.
    • No delta: no plausible propagation path, not self-modified. Do NOT read.

Pre-T7.3 fallback: if _graph.md lacks log_tail: fields, extract frontmatter + Summary + last 3 Log entries per thesis in one batched Bash call as a transient in-memory substitute. Full AWK implementation in RATIONALE §10 (transitional — only fires if a pre-T7.3 _graph.md is restored from backup).

Pass 2 — deep read for High-delta only (single parallel batch):

Issue ALL Pass 2 reads as a single parallel tool-call batch — one message containing every High-delta full-thesis Read, every Low-delta section-slice Bash, and every sector Read. Do NOT serialize per-thesis. A typical 15-25-file Pass 2 lands in one round-trip instead of 15-25 sequential rounds.

  • For each High-delta thesis: read the full file (Step 3a may further scope per T7.5 — see Step 3).
  • For each Low-delta thesis: read only the named sections via section-slice Bash. All Low-delta section-slices can be consolidated into a single Bash block that iterates the Low-delta list — one tool call regardless of Low-delta count.
  • For sectors containing at least one High-delta thesis: read in full (parallel Read inside the same batch as the thesis Reads).
  • Macro notes: already read in Pass 1 — do NOT re-read.

This typically reduces deep reads from ~58 files to 15-25 AND collapses those 15-25 into one round-trip. Combined with Pass 1 being a single batch, total Phase 1 cost is ~2 round-trips regardless of vault size.

Ticker-scoped mode

  1. Locate via Theses/TICKER - *.md prefix glob.
    • No match, check _Archive/: ⚠️ [TICKER] is closed — file lives in _Archive/. To reopen: /rollback TICKER → select pre-status snapshot → /sync TICKER.
    • Not anywhere: ⚠️ No thesis found for [TICKER]. Run /thesis [TICKER] to create it.
    • Multiple matches: ⚠️ Multiple Theses/ files match prefix "[TICKER]" — [list]. Resolve filename collision before re-running.
  2. Read frontmatter. If status: closed but still in Theses/: ⚠️ [TICKER] has status: closed but file remains in Theses/ (failed archive move). ...
  3. Reconstruct adjacency from the thesis file (do NOT read _graph.md — file-direct is the only path in ticker mode):
    • Outbound [[wikilinks]] categorized by directory (Sectors/, Macro & Technology/, Theses/, Research/).
    • Scan Research/ for files with ticker: TICKER in frontmatter.
  4. Read all resolved files. Ignore .last_sync watermark (timestamp-independent).
  5. Proceed to Step 2.

Step 2: Deep Implication Analysis

Mental Models reading gate (MANDATORY — before enumerating implications). Complete the mental-models read per _shared/mental-models-section.md NOW, not at Step 3b write time: read Mental Models/Generalist - Overview.md (always) + the matching Industry - file(s) for the sectors in scope + any Lens - file the changed sources touch (once per run, cached). The models must inform WHAT gets propagated — implication generation is where the vault's edge (non-consensus connections, inflection-point triggers) either happens or doesn't; a write-time-only read arrives after the analytical work is done. Apply the READING PROTOCOL: implications are hypotheses to test, base-rate runs adversarially, cross-source agreement triggers a hunt for the falsifying datapoint. (Step 3b/4b then merge any fired triggers into ## Mental Models sections per the contract — unchanged.)

Maximum reasoning effort here. For each source note read fully and analyse:

  • Factual deltas: new data points, metrics, quotes, events.
  • Assumption challenges: which existing thesis assumptions does this validate, weaken, invalidate? Name the assumption.
  • Second-order implications: what follows that isn't explicit?
  • Competitive dynamics shifts: does this change relative positioning within a sector?
  • Macro linkages: does micro-level data have macro implications, or vice versa?

Source deduplication (ANALYSIS-SCOPE ONLY): if a thesis appears as a changed source AND research notes referencing that thesis are also in the changed set, treat the research notes as primary sources for Step 2 reading — skip the thesis's Log entries citing those research notes during analysis (they duplicate the research note's content).

This rule does NOT suppress Step 4 sector or Step 5 macro propagation. Sector and macro propagation are gated separately at §4.-1 and §5.-1 against the research note's own sector_targets_per_research_note / macro_targets_per_research_note sets resolved at Step 1.2.5. Source-dedup only changes HOW YOU READ sources during analysis; it does not change WHAT YOU ACT ON downstream.

Common mis-application (from 2026-04-22 failure, §1.11): concluding "all thesis targets are idempotent per Step 1.7 → nothing to propagate" and skipping Step 4/5 entirely. This is wrong. Thesis idempotency (Step 1.7) and sector/macro propagation (Step 4/5) are independent propagation paths. A research note that has already been fully propagated to its thesis targets may still have pending sector or macro propagation — always evaluate Step 4.-1 and Step 5.-1 against the research note's own target maps from Step 1.2.5, regardless of thesis-target idempotency.

Propagation quality depends on this step's depth.


Step 2.5: Classify Change Sources (skill-origin gate)

Gates Step 4 / Step 5 — a thesis-only change driven by a skill that already handled downstream sector/macro state should not trigger redundant /sync re-edits.

Classification method (T7.3 — graph-cached)

For each changed thesis, read the most recent Log entry from _graph.md's log_tail: field (single-line lookup, zero file reads). If log_tail: is missing (pre-T7.3 graph) OR the thesis isn't in the graph, fall back to one bounded Bash call:

# Batch-extract most-recent Log bullet for up to N theses.
# CONVENTION: Log entries are append-only with newest at the BOTTOM
# (CLAUDE.md Tier 2 §2: "only append new dated entries at the end").
# Track the LAST date+bullet pair seen, emit in END.
# CRITICAL: do NOT `exit` on first bullet match — that returns the OLDEST
# entry and silently misclassifies theses whose initial Log entry is a
# skill-origin prefix (e.g., "Initial thesis created") but have since had
# research-driven entries appended (the AVGO/MRVL/CRWD/PANW failure mode
# observed 2026-04-26).
for f in Theses/TICKER1 - Name.md Theses/TICKER2 - Name.md; do
  echo "=== $f ==="
  awk '/^## Log/{in_log=1; next}
       in_log && /^## /{exit}
       in_log && /^### [0-9]{4}-/{date=$2; next}
       in_log && date != "" && /^- /{last_date=date; last_bullet=$0}
       END{if(last_date) print last_date" | "last_bullet}' "$f"
done

Classification rule

For each changed thesis:

  • Research-driven (default): at least one research note in the changed-file set resolves (via Step 1.2) to this thesis, OR recent Log entries reference changed research notes. Proceed through Step 3/4/5 normally.
  • Skill-origin: thesis is self-modified (mtime > .last_sync) AND most-recent Log entry matches a skill-origin prefix from _shared/log-prefixes.md (registry §5 Conviction reaffirmed, §6 Status change: conviction, §7 Status change:, §8 CLOSED, §9 Prune upgrade, §11 Initial thesis created, §12 ROLLBACK to snapshot, §13 Cross-thesis closure: / Cross-thesis closures:, §14 Scenario REVERSED, §15 Renamed file:, §16 Comparison , §17 Callout sweep:, §18 Numbers refresh:, §19 Cross-thesis signal via, §20 Metrics synced:) AND no research note in the changed-file set resolves to this thesis.
  • Mixed: research-note source AND skill-origin Log prefix → treat as research-driven.

Body-change override (2026-07-08 — closes the silent-skip failure mode)

The skill-origin classification keys purely on the most-recent Log prefix. Its known failure (CLAUDE.md Workflow Rule #6; §1.11): the user manually edits a thesis body (Bull Case, Risks, Industry Context…) but forgets to append a Log entry, so the most-recent prefix is still a prior skill's — the edit is misclassified skill-origin and sector/macro propagation is silently skipped. Previously this surfaced only as a passive Step 8 report line; now it is an active gate.

For each thesis about to be classified skill-origin, detect whether its body changed outside ## Log since the last sync:

  • Primary (git): git diff --unified=0 HEAD -- "Theses/TICKER - Name.md" (the vault is a git repo). Determine whether any changed hunk falls outside the ## Log section — parse hunk line ranges against the ## Log start line (awk: line number of ^## Log). Changes only within ## Log → genuinely skill-origin (skill Log-append). Any change above ## Log (or to a non-Log section) → body edit.
  • Fallback (no git / not a repo / git unavailable): diff the current body-outside-## Log against the most-recent prior (pre-sync …) snapshot for this thesis in _Archive/Snapshots/ (awk-extract everything before ^## Log from both, compare). No prior snapshot → cannot determine; retain skill-origin classification and rely on the Step 8 report (current behavior — no regression).

On body-edit detection, do NOT silently reclassify and do NOT silently skip. Surface an inline prompt:

⚠️ [TICKER] classified skill-origin (most-recent Log prefix "[prefix]"), but its body changed
outside ## Log since the last sync — likely a manual edit with no Log entry (Workflow Rule #6).
Propagate this thesis's changes to sector/macro notes as research-driven? [Y/n]
(Y = treat as research-driven and propagate — recommended; n = keep skill-origin, skip sector/macro)

Default Y (mirrors the Mixed → research-driven precedent). On Y, move TICKER out of skill_origin_theses and process it through Steps 3/4/5 normally; also recommend the user append a Manual edit: Log entry so future syncs classify it correctly without the prompt. On n, leave it skill-origin. Batch the prompt if multiple theses trip it (one consolidated question listing all affected tickers).

Cost containment: the git diff runs ONLY for theses that would otherwise be classified skill-origin (typically 0–few per sync), never for research-driven theses. False-positive guard: if the user hasn't committed since a prior in-session skill edit, git diff HEAD may include that skill's own body edits — the prompt (not silent reclassification) is what makes this safe, since the user confirms.

Output

Maintain in-memory accumulator skill_origin_theses: [TICKER, ...]. Steps 4 and 5 consult this before per-sector / per-macro propagation. The Body-change override runs before this accumulator is finalized, so any prompted-Y thesis is already removed.

Registry-driven design

The skill-origin enumeration above is a view of _shared/log-prefixes.md's skill-origin entries. /lint #29 verifies registry/enumeration alignment. When a new skill-origin prefix is added: update the registry first, then this enumeration.


Step 2.9: Write Sync Batch Manifest — Skeleton

Mandatory BEFORE any vault state mutation. Runs once per non-no-op run, immediately before Step 3.

2.9a: Batch ID and skeleton path

Generate HHMMSS via date +%H%M%S once at the start of Step 2.9. This is THE canonical batch ID — Step 3c reuses it for all Tier A snapshots; Step 7.5 flips the same manifest.

Batch ID format:

  • /sync (default) and /sync all: sync-YYYY-MM-DD-HHMMSS
  • /sync TICKER: sync-TICKER-YYYY-MM-DD-HHMMSS — prevents collisions between simultaneous /sync NVDA and /sync AMAT

Skeleton path:

_Archive/Snapshots/_sync-manifest (sync-YYYY-MM-DD-HHMMSS).md       # default / all
_Archive/Snapshots/_sync-manifest (sync-TICKER-YYYY-MM-DD-HHMMSS).md # ticker-scoped

2.9b: Write skeleton

---
type: sync-manifest
batch: sync-YYYY-MM-DD-HHMMSS
mode: default | all | ticker-scoped
status: in-progress
date: YYYY-MM-DD
---

# Sync Batch Manifest (in-progress)

Manifest written at Step 2.9 before any file modifications. Populated at
phase boundaries (end of Step 3, 4, 5) then flipped to status: completed
at Step 7.5.

If this file persists with status: in-progress, the sync crashed or was
interrupted. Recovery: inspect the sections below, then /rollback to
restore any Tier A snapshots listed.

## Theses with snapshots taken (Tier A)
_populated at end of Step 3_

## Theses with Log-only appends (Tier B)
_populated at end of Step 3_

## Sector notes touched
_populated at end of Step 4_

## Macro notes touched
_populated at end of Step 5_

## Source research notes processed
_populated at end of Step 3_

2.9c: Skeleton write failure — hard abort

❌ Sync manifest skeleton write failed: [path]
   Reason: [error]

No vault state has been modified yet. Fix the filesystem issue (disk
space, permissions, concurrent writer) and re-run /sync.

Exit. Do NOT proceed.

2.9d: Phase-boundary checkpoints (T7.4)

Manifest writes happen at 4 discrete points — NOT per event. This is the T7.4 optimization: ~4 Edits on the manifest instead of ~49 for a typical /sync all run. In-memory accumulators per Step 7.5d collect events during each phase; phase-end Edit flushes all accumulated entries at once.

CheckpointFires atWrites
Checkpoint AEnd of Step 3 (all theses done)Populates "Theses with snapshots", "Theses with Log-only appends", "Source research notes processed"
Checkpoint BEnd of Step 4 (all sectors done)Populates "Sector notes touched"
Checkpoint CEnd of Step 5 (all macros done)Populates "Macro notes touched"
Step 7.5 flipAfter all writes completeFlips status: in-progress → status: completed + completed_date:

See §5.3 for crash-recovery implications. The phase-boundary design accepts intra-phase granularity loss (mid-Step-3 crash means that phase's theses appear only via snapshot file glob, not via manifest). /rollback remains correct in both cases.


Step 3: Update Thesis Notes

3a: Section-scoped thesis read (T7.5)

Read the thesis with scope driven by Step 3b's impact map. Procedure:

  1. From Step 2's analysis, predict which thesis sections need updates (use the table in Step 3b).
  2. If predicted updates touch ≥5 sections, read the full thesis — working memory benefits from complete context.
  3. If predicted updates touch <5 sections, read targeted sections via Bash extract:
# Example: read frontmatter + Summary + Bull Case + Risks for one thesis
awk '
  /^---$/ && !fm_done { in_fm=!in_fm; print; if(!in_fm) fm_done=1; next }
  in_fm { print; next }
  /^## Summary/,/^## /{if($0!~/^## /||$0~/^## Summary/) print}
  /^## Bull Case/,/^## /{if($0!~/^## /||$0~/^## Bull Case/) print}
  /^## Risks/,/^## /{if($0!~/^## /||$0~/^## Risks/) print}
  /^## Log/{in_log=1}
  in_log{print}
' "Theses/TICKER - Name.md"

Always include frontmatter (for conviction/status) and full ## Log section (for drift detection in Step 3e). The Log is the expensive part — its tail is what drift analysis needs.

When section-scoping is skipped (read full):

  • /sync TICKER mode — always read in full (ticker-scoped cost is already bounded).
  • High-delta self-modified thesis in /sync all (body may have arbitrary user changes).

3b: Analyse Impact Per Section

Map Step 2's insights against each thesis section. Be selective.

SectionUpdate when...
SummaryCore investment case shifted — change in thesis direction or key assumption
Non-consensus InsightsNew evidence strengthens/weakens existing insight, OR genuinely new angle
Outstanding QuestionsQuestion answered (mark resolved + date), OR new questions raised
Business ModelStructural change to revenue model, product mix, go-to-market, segment economics
Industry ContextCompetitive positioning, market share, pricing power shifted
Bull CaseNew supporting evidence, or bull scenario element validated/invalidated
Bear CaseNew counter-evidence, or bear scenario element validated/invalidated
CatalystsNew catalyst identified, or existing catalyst resolved — mark outcome
RisksNew risk emerged, existing risk probability changed materially, or risk retired
Conviction TriggersNew evidence validates/invalidates a trigger, OR threshold needs updating
Mental ModelsA new research-activated /Mental Models trigger fires, an existing trigger's read materially changes, or models newly agree (disconfirm check) — per _shared/mental-models-section.md. High selectivity: most syncs leave it untouched.

Do NOT update sections where the delta is trivial. Thesis contains synthesised conclusions, not duplicated research.

Self-contradiction check (source-thesis disconfirm — applies the Step 4b sweep discipline inward). For each section a propagated datapoint would update, determine whether the new evidence CONTRADICTS what that section — or the thesis's Bull Case / Bear Case / Non-consensus Insights / Conviction Triggers — already asserts, not merely whether it adds to it. A propagated figure that undercuts an existing claim must SURFACE, never be smoothed into the prose silently: accumulate it and emit in the Step 8 report as ⚠️ [TICKER] §[Section]: new evidence contradicts existing claim "[…]" — reconcile, don't silently merge. Step 4b forces this disconfirm question against every OTHER thesis in the sector; this forces it against the source thesis /sync is writing INTO — the one place the cross-thesis sweep never covers.

Mental Models section — before writing it, read the relevant /Mental Models files and apply the READING PROTOCOL per _shared/mental-models-section.md (MANDATORY reading gate). Merge new triggers as hypotheses-to-test; never overwrite; idempotent on model + trigger name. An unchanged section is the correct outcome for most runs.

3c: Pre-Edit Safety — Snapshot

Classify edits by tier:

  • Tier A — snapshot required: editing or rewriting existing text in Summary, Non-consensus Insights, Outstanding Questions, Business Model, Industry Context, Bull Case, Bear Case, Conviction Triggers, Mental Models (rewriting or striking through existing entries).
  • Tier B — no snapshot: appending new Catalysts, Risks, Conviction Triggers, or Mental Models trigger lines; appending to Log / Related Research; updating Key Metrics numbers.

If any Tier A edits planned, snapshot ALL Tier A theses in one Bash block using background shell jobs + wait (mirrors /compare 5.5a and /rollback Step 4 patterns — each cp is independent; sequential execution over N theses wastes N-1 round-trips):

mkdir -p _Archive/Snapshots
# For each thesis T in tier_a_thesis_snapshots (from 3g accumulator), fire cp in background:
cp "Theses/TICKER1 - Name1.md" "_Archive/Snapshots/TICKER1 - Name1 (pre-sync YYYY-MM-DD-HHMMSS).md" &
cp "Theses/TICKER2 - Name2.md" "_Archive/Snapshots/TICKER2 - Name2 (pre-sync YYYY-MM-DD-HHMMSS).md" &
cp "Theses/TICKER3 - Name3.md" "_Archive/Snapshots/TICKER3 - Name3 (pre-sync YYYY-MM-DD-HHMMSS).md" &
wait
# Check exit status of background jobs before proceeding

For single-thesis Tier A (/sync TICKER or default sync with one affected thesis), this collapses to a single foreground cp. The batch form only matters for multi-thesis /sync all runs.

Then issue all frontmatter-addition Edits as a single parallel tool-call batch (one Edit per snapshot, all in the same message):

snapshot_of: "[[Theses/TICKER - Company Name]]"
snapshot_date: YYYY-MM-DD
snapshot_trigger: sync
snapshot_batch: sync-YYYY-MM-DD-HHMMSS

Batch ID reused from Step 2.9. Proceed with edits to the ORIGINAL.

3d: Apply Substantive Edits

  • Integrate, don't append — weave new evidence into existing prose. No "Update (date):" blocks within sections.
  • Carry provenance tags on propagated figures — per _shared/provenance-tags.md, when a quantitative claim (price, margin, yield, share, growth rate) moves from a research note's ## Evidence into a thesis or sector body, its provenance tag travels with the number. If inline space is tight, at minimum preserve the weak-sourcing signal ([1×: source] / [est.]); never launder a single-sourced or estimated figure into a bare, authoritative-looking assertion in a thesis spine.
  • New directional claims cite or propose a trigger — when this sync introduces a NEW Bull Case, Bear Case, or Non-consensus Insight claim (not merely strengthening one that already exists), it must either cite the existing ## Conviction Triggers entry the claim bears on OR flag that a new trigger should be added. A new directional claim with no falsification hook is exactly what /lint #60 and the _shared/trigger-touch.md contract exist to prevent — surface the missing-trigger case in the Step 8 report as a suggestion (→ [TICKER] §[Bull|Bear|Non-consensus]: new claim "[…]" has no matching Conviction Trigger — add one via /deepen Conviction Triggers). Never launder an unfalsifiable directional claim into the thesis spine.
  • Preserve voice and structure.
  • Net deletions are OK — if new evidence invalidates a prior claim, remove the claim.
  • Mark resolved Outstanding Questions: ~~Original question~~ → Resolved YYYY-MM-DD: [answer].
  • Resolve absorbed open findings — per _shared/followups-contract.md, when a substantive edit absorbs an existing _followups.md open finding into a body section (a /surface opportunity woven into Bull Case, a /retro divergence resolved by the new evidence), move the matching ## Open entry to ## Resolved with outcome absorbed. Grep _followups.md ## Open for entries on the affected ticker, match by topic. Never delete — Open→Resolved only. Non-fatal: on failure keep the entry Open and report it in the Step 8 report.

3e: Mechanical Updates (every time)

  • Key Metrics: update table if new numbers.

  • Related Research: add wikilink to new research note.

  • Conviction trigger check: if new data satisfies a predefined trigger: ⚡ Trigger hit: [quote the trigger] — [what happened]. This is the shared _shared/trigger-touch.md check applied to propagated research — a touched or crossed trigger is FLAG-ONLY: surface it, never auto-execute /status (conviction change is Tier-3, human-gated). Distinct from drift detection below — a trigger touch is a single-datapoint event; drift is the aggregate Log-sentiment window.

  • Conviction drift detection — see _shared/log-prefixes.md for authoritative prefix list. /lint #29 verifies consistency. Scan Log backward from most recent, excluding entries that begin with (unconditional exclusions):

    • "Stress test" (registry §1)
    • "Deepening" (registry §2)
    • "Cross-thesis closure:" / "Cross-thesis closures:" (registry §13)
    • "Scenario REVERSED" (registry §14)
    • "Renamed file:" (registry §15)
    • "Callout sweep:" (registry §17)
    • "Numbers refresh:" (registry §18 — a metrics refresh is hygiene, not conviction sentiment; without exclusion each refresh consumes a drift-window slot)
    • "Metrics synced:" (registry §20 — a /deepen --sync-metrics reconciliation is hygiene, same rationale as Numbers refresh:; without exclusion each sync consumes a drift-window slot)

    Conditionally exclude entries beginning with "Deepened" or "↳ CORRECTION: Deepened" within 14 calendar days of a "Stress test" entry (registry §3-§4; deepened_exclusion_days in .drift-config.md, default 14).

    Anchor handling: if "Conviction reaffirmed" (registry §5) or "Status change: conviction" (registry §6) found, anchor the window there — only count entries after. If fewer than 3 entries after anchor, drift detection has insufficient data. If no anchor, use last 5 non-excluded entries.

    Threshold (tunable via .drift-config.md — schema in INFRASTRUCTURE.md §2.7):

    • Base: 3/5 weakening (if conviction: high) or 3/5 strengthening (if conviction: low).

    • Post-stress-test suppression: if "Stress test" entry within last 30 days, raise to 4/5.

    • Missing config → defaults. Malformed → ⚠️ .drift-config.md malformed — using defaults.

    • conviction: high + threshold weakening → ⚠️ Conviction drift — [N]/[window_size] recent updates flagged headwinds ([threshold] required, post-stress-test suppression: yes|no). Reassess. To acknowledge: /status TICKER reaffirm [rationale]

    • conviction: low + threshold strengthening → 📈 Positive drift — [N]/[window_size] recent updates supportive. Reassess. To acknowledge: /status TICKER reaffirm [rationale]

3f: Log Entry (last)

Append to Log:

### YYYY-MM-DD
- [[Research/source-note]]: [what changed] — [conviction impact: unchanged/strengthened/weakened + 1 reason]

Max 2 lines. Log is an audit trail; analysis lives in the updated sections above.

3g: Accumulate manifest entries (in-memory until Checkpoint A)

For each thesis processed in Step 3:

  • Tier A edit → append (path, snapshot_path) to tier_a_thesis_snapshots.
  • Tier B only (Log append without snapshot) → append (path, log_entry_text, reason) to tier_b_thesis_log_appends where reason is cross-thesis propagation | augmented target | other.
  • For each source research note triggering this propagation → append to source_research_notes (dedup at checkpoint time).

Checkpoint A fires once at end of Step 3 — one Edit populating all three manifest sections from the accumulators.


Step 4: Update Sector Notes

4.-1: Skill-origin gate

Consult skill_origin_theses from Step 2.5. A sector's "affected thesis set" is every changed thesis whose sector: frontmatter resolves to this sector (via _graph.md reverse index OR _shared/sector-resolution.md fallback).

Gate rule: skip Step 4 entirely for this sector ONLY IF BOTH conditions hold:

  • (i) EVERY thesis in the affected set is in skill_origin_theses, AND
  • (ii) this sector's path does NOT appear in any entry of sector_targets_per_research_note (computed at Step 1.2.5).

If either condition fails, proceed to Step 4.0. Log (skip path): ℹ️ Skipped sector update [Sector Name] — all [N] affected thesis changes are skill-origin AND no co-changed research note resolves to this sector per Step 1.2.5.

Condition (ii) is NOT satisfied by thesis-target idempotency (Step 1.7 Case 2a/2b/2c). Idempotent thesis propagation and empty sector target set are independent facts. The 2026-04-22 failure case collapsed them incorrectly — see §1.11.

Mixed set: at least one research-driven thesis OR research note resolves to sector → proceed per §4.2.

4.0: Per-source idempotency check

Before snapshotting, check if this sector already received propagation for the source research note today.

  1. Read the sector's ## Related Research section. Check for the source research note's wikilink (any of 5 forms per _shared/wikilink-forms.md).
    • Wikilink absent → first-time propagation; proceed to 4a + 4b.
    • Wikilink present → continue to step 2.
  2. Read the sector's ## Log section. Scan for ### YYYY-MM-DD entry where entry_date == today AND entry references the source research note's wikilink.
    • Today-dated entry references this source → skip 4a + 4b. Log: ℹ️ Skipped sector update [Sector Name] — Log already contains today's entry for [research-note] (likely propagated by earlier /sync TICKER in this session).
    • No today-dated entry → propagation may have happened earlier; proceed normally (4b's deep-analysis gates the actual edit on real delta — §1.7, §1.8).

4a: Pre-Edit Snapshot

If edits modify existing analytical text (competitive dynamics, value chain, sector observations, comparison narratives), snapshot ALL Tier A sectors in one Bash block using background shell jobs + wait:

# For each sector in tier_a_sector_snapshots, fire cp in background:
cp "Sectors/Sector A.md" "_Archive/Snapshots/Sector A (pre-sync YYYY-MM-DD-HHMMSS).md" &
cp "Sectors/Sector B.md" "_Archive/Snapshots/Sector B (pre-sync YYYY-MM-DD-HHMMSS).md" &
wait

Then issue all frontmatter-addition Edits as a single parallel tool-call batch.

Add to each snapshot's frontmatter:

snapshot_of: "[[Sectors/Sector Name]]"
snapshot_date: YYYY-MM-DD
snapshot_trigger: sync
snapshot_batch: sync-YYYY-MM-DD-HHMMSS

Skip snapshot if only adding wikilinks.

4b: Apply Updates

  • Add new research notes to research listing.
  • Update competitive dynamics if relative positioning changed.
  • Update value chain analysis if supply chain relationships shifted.
  • Revise sector-level observations if cross-company patterns emerged.
  • Update company comparison tables with new data points.
  • Cross-thesis contradiction sweep (not just source-ticker additive). Propagation defaults to strengthening the SOURCE ticker; force the peer question explicitly. For each OTHER Active Thesis in this sector, ask: does this research validate or contradict one of its named assumptions? A datapoint that is bullish for the source is often bearish for a peer (share gain = share loss elsewhere; a moat confirmed upstream = margin risk downstream). When the answer is "contradicts," the signal must SURVIVE the run — three concrete actions: (1) accumulate it as a tuple (peer_ticker, named assumption, validates|contradicts, one-line mechanism) in a running cross_thesis_contradictions list; (2) append a Tier B Log entry on the peer thesis (no snapshot — additive Log-only): - Cross-thesis signal via [[Research/source note]]: [assumption] contradicted by [source ticker] evidence — [mechanism]. Review §Risks/§Bear Case. (this is the only path by which propagation can ever weaken a thesis — without it /sync is structurally bull-additive); (3) report the full list as a Cross-thesis contradictions: line in the Step 8 report. If the sector's priced-in read shifted, ALSO update the sector's ## Investor heuristics (what consensus believes / where it's wrong: the vault's stated edge). Do NOT silently leave peers stale because the research arrived under the source ticker.
  • Update the ## Mental Models section when new cross-company evidence activates or retires a sector-level model trigger, or changes its read — read the relevant /Mental Models files first and merge per _shared/mental-models-section.md (high selectivity; most syncs leave it untouched).
    • Empty-scaffold exception (CHG-14) — selectivity governs populated sections. If the ## Mental Models section is still the unpopulated template scaffold (only the three <!-- … --> placeholder bullets) AND the run has qualifying content — a model trigger fired this run, OR existing sector-body content that plainly activates a model (e.g. a documented named layer-monopoly fires Value Layer Monopoly §1) — then first-population is MANDATORY (Tier B, no snapshot), NOT an "empty section → trivial delta → skip". That skip is exactly what left the ABF Substrates sector note scaffold-empty through 5 consecutive /sync runs. Normal selectivity (do not churn already-populated sections) is unchanged. Full rule: _shared/mental-models-section.md Selectivity step.

4c: Post-Edit Verification (Edit-return inspection — no re-read)

Inspect each Edit tool call's return value (the post-edit content snippet it emits). The Edit tool reports success iff the replacement landed, and the return value shows the new content; a separate re-read adds a round-trip without adding information. Verify each edited section from the Edit-return content:

  1. Ends with complete sentence (not mid-word, unmatched **).
  2. No incomplete table rows (line starts | but doesn't end |).
  3. No trailing conjunction, preposition, comma, or opening bracket.

On failure: ⚠️ Sector note may contain a partial edit in [section]. Snapshot available: [[_Archive/Snapshots/...]]. Review manually or /rollback.

Re-reads are only required if an Edit-return is genuinely ambiguous (e.g., the replacement context was truncated in the tool's response); in that case, issue a targeted section Read — not a full-file Read.

4d: Accumulate manifest entries

For each sector touched:

  • Tier A (4a snapshot taken + 4b analytical edit) → (path, snapshot_path) to tier_a_sector_snapshots.
  • Tier B (4b wikilink-only edit) → path to tier_b_sector_appends.

Checkpoint B fires once at end of Step 4 — one Edit populating ## Sector notes touched.


Step 5: Update Macro Notes

5.-1: Skill-origin gate

Consult skill_origin_theses. A macro's "affected thesis set" is every changed thesis that wikilinks this macro (via _graph.md Macro reverse index OR Step 1.3 body-grep fallback).

Gate rule: skip Step 5 entirely for this macro ONLY IF BOTH conditions hold:

  • (i) EVERY thesis in the affected set is in skill_origin_theses, AND
  • (ii) this macro's path does NOT appear in any entry of macro_targets_per_research_note (computed at Step 1.2.5).

If either condition fails, proceed to Step 5.0. Log (skip path): ℹ️ Skipped macro update [Macro Note] — all [N] affected thesis changes are skill-origin AND no co-changed research note resolves to this macro per Step 1.2.5.

Condition (ii) is NOT satisfied by thesis-target idempotency. See §1.11 for the 2026-04-22 failure case that motivated the explicit Step 1.2.5 / lookup-based gate separation.

Mixed set: proceed normally.

5.0: Per-source idempotency check

Before snapshotting:

  1. Wikilink presence — scan macro body for the source research note's wikilink.
    • Absent → first-time propagation; proceed to 5a + 5b.
    • Present → continue to step 2.
  2. Log section check — if macro has ## Log, scan for ### YYYY-MM-DD entry with entry_date == today referencing the source.
    • Today-dated entry → skip 5a + 5b. Log: ℹ️ Skipped macro update [Macro Note] — Log already contains today's entry for [research-note].
    • No today-dated entry → skip step 3; proceed to 5a + 5b.
    • No ## Log section → fall through to step 3.
  3. Mtime fallback (only when step 2 reported no ## Log): if wikilink present AND mtime > .last_sync, skip. Log: ℹ️ Skipped macro update [Macro Note] — wikilink present and mtime > .last_sync (no Log section to confirm); likely propagated within current sync window. Forced re-propagation: /sync all or manual edit.

See §1.10 for mtime fallback trade-offs.

5a: Pre-Edit Snapshot

If edits modify existing analytical text (scenario analysis, probability weightings, trading/allocation implications), snapshot ALL Tier A macro notes in one Bash block using background shell jobs + wait:

# For each macro in tier_a_macro_snapshots, fire cp in background:
cp "Macro & Technology/Note A.md" "_Archive/Snapshots/Note A (pre-sync YYYY-MM-DD-HHMMSS).md" &
cp "Macro & Technology/Note B.md" "_Archive/Snapshots/Note B (pre-sync YYYY-MM-DD-HHMMSS).md" &
wait

Then issue all frontmatter-addition Edits as a single parallel tool-call batch.

Add frontmatter to each snapshot:

snapshot_of: "[[Macro & Technology/Note Name]]"
snapshot_date: YYYY-MM-DD
snapshot_trigger: sync
snapshot_batch: sync-YYYY-MM-DD-HHMMSS

Skip snapshot if only adding wikilinks.

5b: Apply Updates

  • Assess macro-micro linkage: does new micro-level research validate, weaken, or complicate the macro framework?
  • Update scenario analysis if new data changes probability weightings.
  • Add wikilinks to new research.
  • Revise trading/allocation implications if macro thesis shifted.

5c: Accumulate manifest entries

For each macro touched:

  • Tier A → (path, snapshot_path) to tier_a_macro_snapshots.
  • Tier B → path to tier_b_macro_appends.

Checkpoint C fires once at end of Step 5.


Step 6: Update _hot.md (M5 — all-or-nothing atomicity)

Follow .claude/skills/_shared/hot-md-contract.md for all writes — compression, per-section budgets, truncation-marker avoidance, cap handling defined there.

If _hot.md does not exist, create with sections: ## Active Research Thread, ## Latest Sync, ## Sync Archive, ## Recent Conviction Changes, ## Open Questions, ## Portfolio Snapshot. Frontmatter: date: YYYY-MM-DD, tags: [meta, hot-cache].

M5 atomicity rule

All-or-nothing: either every planned section write lands, or none of them do. Prior spec wrote sections 1-6 sequentially then checked the hard cap — so a run hitting the hard cap left _hot.md with sections 1-3 updated and sections 4-6 at their prior (stale) state.

The M5-compliant order is: read → stage in memory → pre-check cap → compress if needed → pre-check again → commit writes OR abort before any write.

6.0: Read _hot.md into memory (non-destructive)

Read current file. Parse sections. Note the current word count of each of the 6 canonical sections.

6.1: Stage planned deltas per section (still non-destructive)

For each planned write, compute the projected section text in memory (do not Edit yet):

  1. Update date: in frontmatter and heading.
  2. Active Research Thread — same-ticker continuation: append dated line to existing thread. New topic: compress outgoing thread into *Previous:* entry (date + one-phrase summary); max 5 *Previous:* lines; drop oldest. See hot-md-contract.md §5.
  3. Latest Sync — replace entirely. One bullet per thesis updated, max 1 line:
    - **[TICKER]**: [what changed] — [conviction impact]
    
  4. Sync Archive — compress outgoing Latest Sync into a single summary line; prepend to Sync Archive. Max 3 entries.
  5. Open Questions — two-stage:
    • 5a. Mark resolved Outstanding Questions: if any resolved on theses during Step 3d, stage the update. If conviction triggers or drift flagged in Step 3e, stage the reassessment question.

    • 5b. Auto-resolve /catalyst no-catalyst questions (§7.1). Scan staged ## Open Questions for the /catalyst-emitted pattern:

      - [[TICKER]]: what catalyst in next 90 days could reignite this thesis? (flagged by /catalyst YYYY-MM-DD)
      

      For each match, resolve if the linked thesis now has a forward catalyst. Method:

      1. Extract TICKER from wikilink.
      2. Read Theses/TICKER - *.md's ## Catalysts section.
      3. Parse each catalyst entry for a date token (YYYY-MM-DD, Q3 2026, October 2026, next [N] weeks/months).
      4. Resolve to absolute date (use start of period for ranges).
      5. If ANY parsed date >= today, thesis has a forward catalyst → stage removal (or strikethrough) of the Open Question entry.
        • Default: drop the bullet in staged text.
        • Alternative (if _hot.md already shows ~~strikethrough~~ → Resolved pattern): - ~~[[TICKER]]: what catalyst...~~ → Resolved YYYY-MM-DD: forward catalyst now exists ([summary]).

      Skip safety: past-dated catalysts only → question stays. Missing/unparseable ## Catalysts → ℹ️ Cannot auto-resolve /catalyst question for [TICKER] — Catalysts section missing or unparseable. (emit to Step 8 report; do NOT stage a write that can't be computed cleanly).

      Report resolved tickers in Step 8 under /catalyst Open Questions auto-resolved.

  6. Recent Conviction Changes — if triggers hit (Step 3e ⚡) or drift flagged (Step 3e ⚠️/📈), stage the entry.

6.2: Pre-check projected word count (no writes yet)

Compute projected_total = sum(staged section word counts) + frontmatter + heading overhead.

  • projected_total ≤ soft_cap (8000): proceed to 6.4 direct commit.
  • soft_cap < projected_total ≤ hard_cap (10000): proceed to 6.3 staged compression.
  • projected_total > hard_cap (10000): attempt 6.3 staged compression. If still over hard cap after full compression trigger order, ABORT all _hot.md writes for this run (§6.5) before any Edit lands. /hot.md retains its pre-run state. Report the abort in Step 8.

6.3: Staged compression (applied to staged text, not to the file)

Apply compression trigger order from _shared/hot-md-contract.md to the staged section buffers:

  1. Drop oldest Sync Archive entry from staged Sync Archive.
  2. Drop oldest *Previous:* line from staged Active Research Thread.
  3. Merge duplicate Open Questions in staged Open Questions.
  4. Drop staged Open Questions cohorts older than 14 days (compress each cohort to one-line pointer to thesis ## Outstanding Questions).
  5. Roster-compress staged Recent Conviction Changes entries older than 30 days (one-liner pointing to thesis §Log + body).
  6. Aggressive RCC fallback (only if still over hard cap after step 5): progressively roster-compress oldest RCC entries (younger than 30 days) until under hard cap OR only the 5 most-recent entries remain verbatim.

After each step, recompute projected_total. Stop when under soft cap. If all six exhausted and projected_total > hard_cap, return to 6.2's abort path.

6.4: Commit all section writes (single composite Edit — M5 atomicity)

Only reached when projected_total ≤ hard_cap. The prior six-Edits form serialized the all-or-nothing commit into six dependent tool calls, any of which could fail mid-sequence and leave partial state. Per .claude/skills/_shared/hot-md-contract.md §5, section ORDER inside _hot.md is not load-bearing (no consumer reads sections in sequence). The commit therefore compresses to one composite Edit that replaces the entire post-frontmatter region with the staged content — a single tool-call round-trip instead of six, and true atomicity (the Edit either lands whole or does not land at all).

Procedure:

  1. Construct the full post-frontmatter region in memory from the Step 6.1 staged section buffers:
    • Updated date: and main heading.
    • All six sections in canonical order: ## Active Research Thread, ## Latest Sync, ## Sync Archive, ## Recent Conviction Changes, ## Open Questions, ## Portfolio Snapshot (mirrors the _hot.md schema from CLAUDE.md Rule #9).
  2. Anchor the single Edit on the closing frontmatter delimiter ---\n plus the current post-frontmatter content:
    • old_string = the closing ---\n followed by the CURRENT post-frontmatter content (captured at Step 6.0's Read).
    • new_string = the closing ---\n followed by the STAGED post-frontmatter content.
  3. Inspect the Edit-return content to confirm all six sections landed and the compression-trigger-order outputs (§6.3) are reflected.

If the composite Edit fails (rare — pre-checks at 6.2 should make this the pathological case only, and anchor uniqueness is guaranteed because the closing ---\n appears exactly once in a well-formed file), report the failure and do NOT fall back to sequential Edits. _hot.md retains pre-run state; primary sync propagation in Steps 3-5 is unaffected. /lint #35 catches any post-run schema drift; /lint #42 catches truncation markers.

Frontmatter-only update fast path: if date: is the only field that changed between the read and the staged output (body sections all byte-identical after compression), skip the composite region Edit and issue a single targeted frontmatter Edit on the date: line. One-line Edits are cheaper than whole-region replacements when the body didn't actually change.

6.5: Hard-cap abort report

If 6.2/6.3 aborted before any write:

⚠️ _hot.md hard cap (10000 words) exceeded after all compression triggers — no _hot.md writes committed this /sync run.
   Current: [X] words. Projected with planned writes: [Y] words. Over cap by: [Z] words.
   
   Pre-run _hot.md state preserved. Resolution options:
     (a) Run /lint #42 to surface accumulated truncation/compression markers.
     (b) Manually compress _hot.md (drop oldest Sync Archive entries, *Previous:* lines).
     (c) Accept the session-context degradation for this run; primary sync propagation already succeeded.

Primary sync operation (Steps 3-5) remains valid; only _hot.md is skipped. Preserves the contract's principle: session-context degradation is tolerable; primary operation correctness is not.


Step 7: Touch Watermark + Graph-Rebuild Marker

Runs after ALL content writes (Steps 3-6) complete, before Step 7.5.

Ordering invariant (C4)

.sync_all_fresh writes BEFORE .last_sync touch. If either fails, this ordering preserves correctness:

  • Marker write fails, watermark not yet advanced → next /sync re-reads everything (safe — wasted work but no missed rebuild).
  • Marker wrote, watermark touch fails → /graph still forces full rebuild from marker; next /sync re-reads (safe — doubly covered).
  • Reverse order would produce the failure path where watermark advances but marker absent → /graph last skips full rebuild → "no delta" theses unreconciled in _graph.md until next /sync all.

Graph-rebuild marker — /sync all only (writes FIRST per C4 ordering)

Skip for /sync (default) and /sync TICKER. For /sync all:

touch .sync_all_fresh

/graph reads this marker at Watermark Resolution and forces full rebuild regardless of mode, then deletes the marker after a successful write. See §5.4.

Mode-conditional watermark (writes AFTER marker per C4 ordering)

  • /sync / /sync all: touch .last_sync. If prior marker write failed, watermark stays unset and next run re-processes.
    touch .last_sync
    
  • /sync TICKER: do NOT touch (§6.1). Report Watermark: untouched (ticker-scoped mode preserves baseline for next default /sync).
    • First-run exception: if .last_sync absent, create epoch placeholder:
      touch -t 197001010000 .last_sync
      
      Report Watermark: epoch placeholder created.

Step 7.5: Finalize Sync Batch Manifest — Flip to completed

Mandatory for every mode when Step 2.9 skeleton was written. Skip only on pure no-op.

7.5a: Manifest path (from Step 2.9a)

Reuse the batch ID captured at Step 2.9a. Path:

_Archive/Snapshots/_sync-manifest (sync-YYYY-MM-DD-HHMMSS).md

7.5b: Flip status frontmatter

Edit manifest frontmatter:

  • status: in-progress → status: completed
  • Add completed_date: YYYY-MM-DD

Expected frontmatter post-flip:

---
type: sync-manifest
batch: sync-YYYY-MM-DD-HHMMSS
mode: default | all | ticker-scoped
status: completed
date: YYYY-MM-DD
completed_date: YYYY-MM-DD
---

Phase checkpoints A/B/C have already populated the body. No body Edit at this step.

7.5c: Verify flip landed (Edit-return inspection — no re-read)

Inspect the 7.5b Edit tool call's return value. The Edit tool reports success iff the replacement landed; the return value shows the post-edit frontmatter snippet. Confirm from the Edit-return content:

  • status: completed present
  • status: in-progress absent
  • completed_date: present and equals today

On success: proceed to Step 8.

On verification failure (Edit-return indicates the replacement did not produce the expected frontmatter, or the Edit tool returned an error): do NOT retry aggressively. Report ⚠️ Sync manifest status flip failed — manifest at [path] remains status: in-progress despite successful sync completion. /lint #41 will flag this as Important until manually resolved. Manual fix: edit the manifest and replace status: in-progress with status: completed (add completed_date: today). Continue to Step 8.

7.5d: Tracking accumulators (implementation reference)

Throughout Steps 3-5, maintain in-memory accumulators flushed at phase checkpoints:

AccumulatorPopulated whenFlushed at
tier_a_thesis_snapshots: [(path, snapshot_path)]Step 3c snapshotCheckpoint A
tier_b_thesis_log_appends: [(path, log_entry_text, reason)]Step 3f without snapshotCheckpoint A
source_research_notes: [path]Step 3 starts on a research noteCheckpoint A (dedup)
tier_a_sector_snapshots: [(path, snapshot_path)]Step 4a snapshotCheckpoint B
tier_b_sector_appends: [(path)]Step 4b wikilink-onlyCheckpoint B
tier_a_macro_snapshots: [(path, snapshot_path)]Step 5a snapshotCheckpoint C
tier_b_macro_appends: [(path)]Step 5b wikilink-onlyCheckpoint C

If the run completes with all accumulators empty (pure no-op reached Step 3 but no edits landed): skip Step 7.5 entirely. In practice this is rare since Step 1 catches the pure no-op earlier.

7.5e: Lifecycle

The manifest is a non-snapshot artifact (type: sync-manifest, no snapshot_date:):

  • /clean Step 2a's non-snapshot guard skips it for snapshot-age-based cleanup.
  • /lint #41 distinguishes three states: status: in-progress → Important; status: completed → ages per 90/180 day tiers; missing → no-op.
  • Persists indefinitely until manually deleted OR /lint #41 ages it out.

Step 8: Report (T7.8 — emit only non-empty lines)

Produce a compact report. Emit only lines with non-empty data. Do NOT include "If none, omit this line" scaffold; if the field is empty, skip the line entirely. The LLM should inspect each field before deciding to write a line, not write a template and then prune.

Always emitted:

  • Mode: default | all | ticker-scoped
  • Files read: [count]
  • Files updated: [count]
  • Watermark: touched (default/all) | untouched (ticker-scoped mode preserves baseline for next default /sync) | epoch placeholder created
  • Final reminder: → Run /graph last to update the dependency map. Recommended after every /sync.

Conditionally emitted — include only if data is non-empty:

FieldEmit when
Snapshots createdat least one Tier A snapshot (theses/sectors/macros) — list paths
Thesis updatesat least one thesis touched — one bullet per thesis: [TICKER]: [sections] — [conviction impact]. Snapshot: [[...]] or N/A
Sector note updatesat least one sector touched — one-line summary each
Cross-thesis contradictionsat least one Step 4b sweep tuple accumulated — list each as [peer]: [assumption] [validates|contradicts] via [source ticker] — [mechanism] (Tier B peer Log entries were appended for contradicts)
Macro note updatesat least one macro touched — one-line summary each
Deduplication skipsat least one Step 1.7 wikilink-presence skip
Sector skill-origin skipsat least one Step 4.-1 skip
Macro skill-origin skipsat least one Step 5.-1 skip
Skill-origin classified thesesat least one thesis classified as skill-origin in Step 2.5 — list each as [TICKER]: most-recent Log prefix "[prefix]" (sector/macro propagation skipped). The Step 2.5 Body-change override now actively catches the manual-edit-without-Log-entry case (CLAUDE.md Workflow Rule #6): if a skill-origin thesis's body changed outside ## Log since last sync, the user was prompted mid-run and (on Y) it was reclassified research-driven — so a thesis remaining in this skill-origin list either had only Log-section changes (correct skip) or the user chose n at the prompt (or the no-git fallback found no prior snapshot to compare). For any surprise here, append a Manual edit: ... Log entry and re-run /sync. Also surface Body-change overrides prompted: [tickers, Y/n each] when the override fired.
Log-history backfill skipsat least one Case 2a skip (Step 1.7)
Sector idempotency skipsat least one Step 4.0 skip
propagated_to: skipsat least one Case 2b skip
Terminal-skip notesat least one Case 2c (empty propagated_to: [])
Augmented targetsat least one augmented ticker
propagated_to: updatesat least one research note had its frontmatter updated
propagated_to: update skippedat least one research note had an append-failure blocking atomic update
Closed-status skipsStep 1.5 caught a status: closed thesis still in Theses/
Missing-file skipsgraph-referenced thesis missing from disk
Unresolved research notesno propagation target found for at least one note
Conviction triggers hitStep 3e ⚡ fired on at least one thesis
Conviction drift flags raisedStep 3e ⚠️/📈 fired
/catalyst Open Questions auto-resolvedStep 6 #5b resolved at least one
New connections discoveredat least one surprising link noted during analysis

Final line (always): → Run /graph last to update the dependency map. Recommended after every /sync.

レビュー

まだレビューはありません。使ってみた感想をお寄せください。

同じリポジトリのスキル

概要と使いどころ

Sweep addressed callouts older than a threshold (default 180 days) into the target note's

日本語の概要は準備中です。原文の説明を表示しています。

jameswong2011/InvestmentVault32026年9月9日 更新

Extract a thesis's load-bearing assumptions and scan for internal contradictions — the falsifiable claims the bull case requires, what would falsify each, and where the thesis's own Risks / Bear Case / research already argue against it. Use when user says "assumptions", "what must be true", "load-bearing assumptions", or "does [TICKER] contradict itself".

日本語の概要は準備中です。原文の説明を表示しています。

jameswong2011/InvestmentVault32026年9月9日 更新

brief

無料

Generate a concise 1-page investment brief from a thesis — the elevator pitch version. Use when user says "brief", "summarise thesis", "pitch me [TICKER]", "one-pager", or "investment memo".

日本語の概要は準備中です。原文の説明を表示しています。

jameswong2011/InvestmentVault32026年9月9日 更新

catalyst

無料

Extract and maintain a cross-portfolio catalyst calendar from all thesis notes. Use when user says "catalyst", "calendar", "what's coming up", "upcoming events", or "what could move the portfolio".

日本語の概要は準備中です。原文の説明を表示しています。

jameswong2011/InvestmentVault32026年9月9日 更新

clean

無料

Delete old snapshots from _Archive/Snapshots/. Accepts a day threshold (default 180). Also `daily-intel [days]` mode — prune auto-generated n8n scanning surfaces (news briefs, X dashboards) from Daily Intel/ older than 90 days. Use when user says "clean", "clean snapshots", "delete old snapshots", "clean daily intel", or when /lint flags stale snapshots.

日本語の概要は準備中です。原文の説明を表示しています。

jameswong2011/InvestmentVault32026年9月9日 更新

clean

無料

Delete old snapshots from _Archive/Snapshots/. Accepts a day threshold (default 180). Also `daily-intel [days]` mode — prune auto-generated n8n scanning surfaces (news briefs, X dashboards) from Daily Intel/ older than 90 days. Use when user says "clean", "clean snapshots", "delete old snapshots", "clean daily intel", or when $lint flags stale snapshots.

日本語の概要は準備中です。原文の説明を表示しています。

jameswong2011/InvestmentVault32026年9月9日 更新

jameswong2011 のスキルをすべて見る

このスキルの問題を報告する