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

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".

インストール方法を見る

含まれるファイル(1)

  • SKILL.md34.1 KB

SKILL.md(原文)

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

Follow CLAUDE.md Writing Standards strictly. No hedge words, lead with insights/numbers, tables over prose, every sentence must earn its place.

Build a comprehensive catalyst calendar aggregating every upcoming event across the portfolio.

Progress Reporting Contract (MANDATORY)

/catalyst is a long-running command (~71 thesis + macro reads, FMP fetches, retries) whose intermediate work would otherwise stay silent until the final Phase 6 report — a 5–30+ minute silent window during which errors, retries, FMP timeouts, and degraded-mode downshifts are invisible. To close this gap, emit a one-line status message as plain text between tool calls (not buried inside Bash echo, not as a tool argument) at every phase boundary and every retry / error / degraded-path event.

Why plain text, not Bash echo: Bash echo is visible but rendered inside collapsed tool-output blocks. Plain text between tool calls is the primary signal the user reads to track progress mid-run. Keep the existing echo statements (they're the audit trail) AND add plain-text emits (they're the live progress feed) — both are required.

Format:

  • Phase start: 📍 Phase X.Y: [what's starting + expected scale] — e.g. 📍 Phase 1: launching parallel read batch (65 theses + 6 macros)
  • Phase complete: ✅ Phase X.Y: [what completed + key metric] — e.g. ✅ Phase 2a: FMP matched 58/64 tickers, 6 queued for WebSearch
  • Soft failure / degraded path: ⚠️ Phase X.Y: [what failed + what's happening next] — e.g. ⚠️ Phase 2a: FMP first attempt failed (rc=99 empty array), retrying in 5s
  • Hard failure / abort: ❌ Phase X.Y: [what failed + abort reason] — e.g. ❌ Phase 0.2: snapshot cp failed, aborting before any destructive write

Cadence: every phase boundary (Phase 0.1, 0.2, 0.3, 1, 2a, 2b, 3, 4, 5, 6) + every retry / error / cap-activation event. Aim for one status line every 10–30 seconds of wall-clock work; silences longer than that mean the user wonders if the skill is stuck.

Do not emit per-file progress within a parallel batch (e.g., "read thesis 1 of 65", "WebSearch 3 of 20"). Batch-level start/complete is enough; per-file noise drowns the signal.

Reference scale (2026-05-23 portfolio): 65 theses, 6 macro notes, ~64 active/monitoring tickers for FMP enrichment. Phases 1 and 2 are the two longest segments — emit start AND complete messages for both even if they feel redundant.

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

0.1: Acquire vault lock

Acquire a vault-wide scope lock per .claude/skills/_shared/preflight.md Procedure 1. Timeout budget: 5 minutes. Capture the token, verify ownership (Procedure 1.5) at every subsequent Bash block, release in the final reporting Bash block.

📍 Emit before: 📍 Phase 0.1: acquiring vault-wide lock ✅ Emit after: ✅ Phase 0.1: lock acquired (token <token>) ⚠️ Emit on stale-lock break-in or contention: ⚠️ Phase 0.1: existing lock found — [stale (broken) | active (waiting Xs)]

0.2: Snapshot existing _catalyst.md (mandatory pre-regenerate snapshot — H2 fix: hard-abort on failure)

Before regenerating _catalyst.md, snapshot the prior version. If the snapshot cp fails, HARD ABORT /catalyst — do not proceed to regenerate, because a regenerate failure after a failed pre-snapshot loses the prior calendar with no recovery path.

📍 Emit before: 📍 Phase 0.2: snapshotting prior _catalyst.md ✅ Emit after: ✅ Phase 0.2: snapshot written to _Archive/Snapshots/_catalyst (pre-catalyst <date>-<HHMMSS>).md ℹ️ Emit when no prior file: ℹ️ Phase 0.2: no prior _catalyst.md — first run, snapshot skipped ❌ Emit on hard abort: ❌ Phase 0.2: snapshot failed (<cp error | empty file>) — aborting before any destructive write

HHMMSS=$(date +%H%M%S)
mkdir -p _Archive/Snapshots
SNAPSHOT_PATH="_Archive/Snapshots/_catalyst (pre-catalyst YYYY-MM-DD-$HHMMSS).md"

if [ -f "_catalyst.md" ]; then
  if ! cp "_catalyst.md" "$SNAPSHOT_PATH"; then
    echo "SNAPSHOT_FAILED|$SNAPSHOT_PATH"
    exit 1
  fi
  # Verify the copy actually landed with non-zero size (cp can silently no-op on some filesystems)
  if [ ! -s "$SNAPSHOT_PATH" ]; then
    echo "SNAPSHOT_EMPTY|$SNAPSHOT_PATH"
    rm -f "$SNAPSHOT_PATH"
    exit 1
  fi
fi

Then add snapshot frontmatter to the newly created file:

snapshot_of: "[[_catalyst.md]]"
snapshot_date: YYYY-MM-DD
snapshot_trigger: catalyst
snapshot_batch: catalyst-YYYY-MM-DD-HHMMSS

Snapshot failure handling (H2): if either the cp fails or the resulting file is empty, abort with:

❌ /catalyst pre-regenerate snapshot failed: [path]
   Reason: [cp error | empty file after copy]

No regeneration attempted. The current _catalyst.md is unchanged. Fix the
underlying filesystem issue (disk space, permissions, _Archive/Snapshots/
directory writability) and re-run /catalyst. Prior calendar is intact.

Exit the skill cleanly — do NOT proceed to Phase 1. The prior _catalyst.md is preserved at its current path; the next successful /catalyst run will snapshot and regenerate.

Frontmatter-add failure: if the cp succeeds but the subsequent frontmatter Edit fails, proceed with regeneration anyway — the snapshot file content is preserved (bit-exact copy of the prior _catalyst.md), just without the snapshot_of: metadata. /rollback can still restore it via direct cp-back; it won't appear in /rollback cascade detection by batch ID, but the user can locate it by filename pattern (_catalyst (pre-catalyst *)).

Why the hard abort matters: prior behavior proceeded with regeneration after silent cp failure. If web search then failed mid-regenerate, the partial calendar overwrote the prior version with no snapshot to roll back to. Daily backup was the only fallback. Hard-abort guarantees the prior calendar is always recoverable from the pre-regenerate snapshot before any destructive write.

If no prior _catalyst.md exists (first run), skip the snapshot step (the [ -f "_catalyst.md" ] guard handles this) and proceed to Phase 1.

0.3: FMP API key probe (soft — degrade-on-failure)

Try to load the FMP API key. Unlike /numbers and /transcript, /catalyst does NOT abort on FMP failure — it falls back to WebSearch (the prior behavior). Capture the result as a flag for Phase 2.

📍 Emit before: 📍 Phase 0.3: probing FMP API key ✅ Emit on success: ✅ Phase 0.3: FMP key loaded — Phase 2a will use FMP universe endpoint ⚠️ Emit on miss: ⚠️ Phase 0.3: no FMP key — Phase 2 will use WebSearch fallback only (capped at 20)

FMP_OK=0
if [ -f .data/config.json ]; then
  API_KEY=$(jq -r '.fmp_api_key // empty' .data/config.json)   # jq, not sed -E '\s' (BSD sed unsupported → whole-line key → rc=3 curls)
  if [ -n "$API_KEY" ] && [ "$API_KEY" != "null" ]; then
    FMP_OK=1
    echo "FMP_OK"
  fi
fi
[ "$FMP_OK" = "0" ] && echo "FMP_UNAVAILABLE — Phase 2 will use WebSearch fallback only"

Record FMP_OK and API_KEY for Phase 2. The skill's primary deliverable (the calendar) is functional in either mode — FMP is a speed/precision upgrade, not a correctness gate.

Quartr probe (soft, same discipline): per _shared/quartr.md §1–§2, ToolSearch-load mcp__quartr__search_companies,mcp__quartr__list_events; set QUARTR_OK=1 on success, 0 otherwise (emit ⚠️ no Quartr — Phase 2a-bis skipped, WebSearch fallback takes the full miss queue). Quartr feeds Phase 2a-bis (structured earnings dates for FMP-miss tickers — precisely the non-US listings FMP's universe calendar drops — plus non-earnings catalyst events FMP cannot see at all).

Phase 1: Load the Portfolio (Parallel Full Reads)

📍 Emit before: 📍 Phase 1: launching parallel read batch (<N> theses + <M> macros = <N+M> Reads in one round-trip) ✅ Emit after: ✅ Phase 1: <N+M> files loaded — moving to extraction ⚠️ Emit if any Read fails: ⚠️ Phase 1: <K>/<N+M> reads failed (<filenames>) — continuing with available files

Issue all thesis Reads AND all macro Reads in a single parallel tool-call batch — one message with ~71 Read invocations (65 theses + 6 macros at current scale; scales with vault), not serial loops. This collapses the I/O into one round-trip while preserving full-file context: the LLM sees every thesis body in full so cross-section catalyst signal (e.g., a regulatory date mentioned in Bull Case, a product launch referenced in Business Model) isn't lost to over-aggressive section pre-filtering.

1.1 Parallel batch composition

  • Read every Theses/*.md in full (all files — draft, active, monitoring; every thesis can carry catalysts).
  • Read every Macro & Technology/*.md in full (bounded set, ~6 files — rate decisions, geopolitical deadlines, commodity contract expirations, AI bubble risk, stablecoin regulation, etc.).

All reads land in one round-trip. Do NOT serialize per-file.

1.2 Extract from the pulled content

Within each thesis, prioritize Catalysts, Risks, and the last 5 Log entries (highest-density sections for event extraction) but maintain peripheral awareness of other sections — a catalyst may surface in Bull/Bear Case, Summary, or Business Model when the author didn't duplicate it into the Catalysts list. Full-file context is kept intentionally; /catalyst favors completeness over narrow extraction.

Per thesis:

  • Catalysts section: event description, approximate date or date range (if stated), expected impact direction (positive / negative / uncertain), magnitude estimate (major / minor).
  • Conviction Triggers section (when present — ~half the theses): extract each → HIGH if / → LOW if / → CLOSE if statement and pair it with any catalyst whose outcome would test it. This feeds the Thesis test column (Phase 4) — the calendar's falsification layer. The 000660 thesis's Catalysts table ("Positive if SK Hynix maintains ≥60% share") is the in-vault exemplar of the target pairing.
  • Log entries: recently noted upcoming events (scan the last ~5 date headers; older entries typically describe past events).
  • Risks section: risk events with timing (e.g., "regulatory decision expected Q2").
  • Elsewhere in the thesis: flag any date-anchored event or upcoming catalyst mentioned in Bull Case, Bear Case, Summary, Industry Context, or Business Model that's absent from the Catalysts section — surface these as candidates the thesis may need to formalize.
  • Macro notes: cross-portfolio macro events (rate decisions, geopolitical deadlines, commodity contract expirations).

Phase 2: Enrich and Deduplicate

📍 Emit at Phase 2 start: 📍 Phase 2: enriching catalysts with FMP earnings dates (FMP_OK=<0|1>)

  • Where catalyst dates are vague ("Q2 2026", "H2"), convert to approximate calendar dates

  • Identify catalysts that affect MULTIPLE theses (e.g., an earnings report from a major customer that affects several supplier theses)

  • Flag catalysts that are stale — events that appear to have already passed based on their dates vs today's date

  • Enrich with upcoming earnings dates — FMP-first, WebSearch fallback.

    2a. FMP universe call (when FMP_OK=1 from Phase 0.3)

    📍 Emit before: 📍 Phase 2a: calling FMP universe earnings calendar (TODAY → +90d) ✅ Emit after success: ✅ Phase 2a: FMP returned <rows> earnings rows, matched <hits>/<active+monitoring> theses ⚠️ Emit on retry: ⚠️ Phase 2a: FMP attempt 1 failed (rc=<rc>) — retrying in 5s then ✅ Phase 2a: FMP retry succeeded OR ⚠️ Phase 2a: FMP retry also failed — downshifting to WebSearch fallback (capped at 20)

    Single curl to the universe earnings calendar over the next 90 days. One round-trip replaces what would otherwise be ~64 WebSearches (one per active/monitoring thesis). Retry-once on transient failure before downshifting to the WebSearch fallback storm — empirically (2026-05-23 incident: two consecutive /catalyst runs at 14:50 and 15:12 timed out at ~9 min each because Phase 2a hit transient FMP failures and downshifted to a full 64-ticker WebSearch storm; a 5-second retry would have salvaged both runs since FMP was healthy by 15:21).

    TODAY=$(date +%Y-%m-%d)
    END=$(date -v+90d +%Y-%m-%d 2>/dev/null || date -d '+90 days' +%Y-%m-%d)
    FMP_URL="https://financialmodelingprep.com/stable/earnings-calendar?from=$TODAY&to=$END&apikey=$API_KEY"
    
    fetch_fmp() {
      curl -sf --max-time 20 --connect-timeout 5 "$FMP_URL" > /tmp/catalyst_earnings.json
      local rc=$?
      if [ $rc -ne 0 ]; then return $rc; fi
      # Empty array is a known intermittent failure mode — treat as soft failure for retry purposes
      local rows
      rows=$(jq 'length' /tmp/catalyst_earnings.json 2>/dev/null)
      if [ "$rows" = "0" ] || [ -z "$rows" ]; then return 99; fi
      return 0
    }
    
    FMP_FETCH_RC=0
    if ! fetch_fmp; then
      FMP_FETCH_RC=$?
      echo "⚠️ FMP first attempt failed (rc=$FMP_FETCH_RC). Retrying after 5s..."
      sleep 5
      if ! fetch_fmp; then
        FMP_FETCH_RC=$?
        echo "⚠️ FMP retry failed (rc=$FMP_FETCH_RC). Downshifting to capped WebSearch fallback."
        FMP_OK=0
      else
        echo "✅ FMP retry succeeded."
      fi
    fi
    

    Timeout rationale: FMP's universe earnings-calendar endpoint can TCP-establish and then stall mid-stream from networks with degraded routes (observed 2026-05-23: 500–600ms RTT, intermittent recv failures, HTTP:000 SIZE:0 responses). Without --max-time, curl defaults to no transfer timeout — a stalled connection blocks /catalyst until the kernel TCP keepalive eventually closes the socket (5–10 min), which surfaces to the user as a skill stall with no progress signal. --max-time 20 caps total transfer at 20s; --connect-timeout 5 caps TCP handshake at 5s. Both well above healthy-path latency (~2s end-to-end) but tight enough to deterministically fall through to the retry / WebSearch path below.

    Retry rationale: prior design said "Do NOT retry — the universe endpoint failing usually means the API key is wrong or quota is exhausted, neither of which a retry fixes." Reality: the dominant failure mode observed in production is transient (intermittent network blips, brief upstream slowness, momentary empty-array responses) — exactly the failure class a single 5-second retry fixes. Persistent failures (wrong key, exhausted quota) fail both attempts and still downshift correctly. Worst-case Phase 2a cost rises from 20s → 45s (initial 20s timeout + 5s sleep + 20s retry timeout), which is a rounding error against the 5-minute lock budget — and orders of magnitude cheaper than the 9-minute WebSearch storm the retry prevents.

    Parse the JSON (array of {symbol, date, epsEstimated, revenueEstimated, time}). Build a symbol → next_earning map by sorting each symbol's entries ascending by date and taking the first row.

    For each thesis with status: active | monitoring, look up its ticker in the map:

    • Hit: record the date + EPS/revenue estimates + reporting time (BMO/AMC). Source label: FMP.
    • Miss: queue the ticker for the WebSearch fallback (typically non-US listings — 000660.KS, 6981.T, ASM.AS, BESI.AS, AIXA.DE, GAW.L, IQE.L, SIVE.ST, YAL.AX — which FMP's universe calendar doesn't always cover; per-ticker coverage works via the transcript endpoint but the calendar endpoint is US-biased).

    FMP call failure handling (after retry):

    • Both attempts fail (HTTP error, curl error, or empty array) → FMP_OK=0 set by the retry block above. Phase 2b enters capped-fallback mode. Do NOT retry a third time — by this point the failure is persistent (API key wrong, quota exhausted, or sustained upstream outage), and further retries cost time without changing outcome.

    2a-bis. Quartr per-ticker events for the miss queue (when QUARTR_OK=1)

    📍 Emit before: 📍 Phase 2a-bis: Quartr event lookup for <queue_size> FMP-miss tickers ✅ Emit after: ✅ Phase 2a-bis: <hits> earnings dates resolved via Quartr, <extras> non-earnings events captured, <remainder> tickers → WebSearch queue

    Runs on the Phase 2a miss queue (or the full active+monitoring list when FMP_OK=0) BEFORE any WebSearch. Per queued ticker, per _shared/quartr.md §3: resolve companyId (quartr_id: frontmatter → .data/quartr_ids.json cache → search_companies by company name with verification; cache verified hits), then:

    mcp__quartr__list_events {companyId: ID, expand: [], startDate: TODAY, order: "asc", limit: 10}
    
    • Next earnings: first event with parentEventType: "earnings_call" → record date (+ exact datetime — Quartr carries call time, so BMO/AMC falls out of the timestamp) with source label Quartr. Remove the ticker from the WebSearch queue.
    • Non-earnings catalyst events (bonus rows no other source provides): any capital_markets_day / investor_day / analyst_day / r_and_d_day / annual_general_meeting / guidance event inside the 90-day window → add as its own calendar row, type tagged (CMD, AGM, guidance), source Quartr. Plain conference appearances are NOT calendar rows (too noisy — a fireside is not a catalyst) unless the thesis's Catalysts section already names that conference.
    • No confident company match / no events: ticker stays in the WebSearch queue (QUARTR_MISS — never guess).

    Cost profile: resolution is one search_companies per ticker ONCE ever (the ID cache persists); steady-state is one list_events per queued ticker (~5-9 international names on a healthy FMP run). No retry loops — a failed call sends the ticker to the WebSearch queue. This phase typically empties the queue that previously consumed the WebSearch cap: coverage confirmed 2026-09-09 for the hard cases (KOSDAQ 036930, TWSE 2383/8069, .NS, .ST, .L listings).

    2a-ter. Conference clustering (when QUARTR_OK=1 and .data/quartr_watchlist.json exists)

    📍 Emit before: 📍 Phase 2a-ter: clustering upcoming conference appearances (watchlist <id>) ✅ Emit after: ✅ Phase 2a-ter: <N> conference events grouped into <M> conferences, <K> multi-name clusters

    A conference where several portfolio names present in the same week is a catalyst in its own right (management access, cross-company signal, sector-wide narrative moves) — and no other source sees it. One call against the /event-sweep-maintained watchlist (read-only per that skill's constraint #3; state file absent → skip with ℹ️ no Quartr watchlist — run /event-sweep --setup, no per-ticker fan-out substitute):

    mcp__quartr__list_events {watchlistId, startDate: TODAY, endDate: TODAY+90d, eventTypes: ["conference", "fireside_chat", "capital_markets_day", "investor_day", "analyst_day", "r_and_d_day"], expand: [], limit: 200, order: "asc"}
    

    Group returned events by conference title. For clusters of ≥2 portfolio names, optionally get_conference (via the event's conference linkage where available; cap 5 calls) for full presenter counts/dates. Feed Phase 4 two ways: (1) each CMD/investor-day/analyst-day event becomes its own calendar row (type-tagged, source Quartr — these are per-company catalysts regardless of clustering); (2) multi-name conference clusters render in the new **Conference clusters (next 90d)** block of _catalyst.md (see Phase 4 Structure) with conference name, dates, and the portfolio names presenting. Plain single-name conference appearances still do NOT become calendar rows (the 2a-bis noise rule stands) — they surface only inside a multi-name cluster where the co-presence is the signal.

    2b. WebSearch fallback (CAPPED — hard limit 20 per run)

    📍 Emit before: 📍 Phase 2b: WebSearch fallback for <queue_size> tickers (cap=20, mode=<healthy-int'l | overflow | full-fallback>) ⚠️ Emit when cap activates: ⚠️ Phase 2b: queue (<queue_size>) exceeds cap — running top 20 by conviction+recency, surfacing <queue_size-20> dropped tickers in Notes block ✅ Emit after: ✅ Phase 2b: <N> WebSearches complete, <hits> dates extracted

    For the ticker set still uncovered after Phases 2a + 2a-bis (with QUARTR_OK=0 and FMP_OK=0 this is the full active+monitoring list) — send WebSearch invocations in parallel: up to 25 invocations per message, then the next batch, never serialized.

    Query pattern: "[Company name] next earnings date 2026".

    Hard cap: 20 WebSearches per run, prioritized by conviction. Empirically (2026-05-23 incident) an uncapped 64-ticker fallback storm took >9 minutes and exceeded the host kill budget, leaving Phase 4 unreached and a stale .vault-lock orphaned at vault root. The cap bounds worst-case Phase 2b runtime at ~3 minutes (20 calls × ~10s each, internally rate-limited by the WebSearch backend) so total /catalyst runtime stays under the 5-minute lock budget even in full-fallback mode.

    Prioritization (when queue size > 20):

    1. Sort by conviction (high → medium → low → unset)
    2. Within conviction tier, sort by frontmatter date descending (most recent thesis first — these are most likely to drive near-term decisions)
    3. Take the top 20
    4. Surface the dropped tickers in Phase 4's Notes block under the calendar: ⚠️ Earnings dates for [N] tickers not refreshed this run (FMP fallback cap): [ticker list]. Re-run /catalyst when FMP is healthy.

    Run modes by queue size:

    • Healthy FMP path (FMP_OK=1, queue ≤ 20): run all WebSearches (typically 5–9 international tickers). No cap activation; no warning.
    • Healthy FMP path with overflow (FMP_OK=1, queue > 20): rare — would only happen if FMP universe call returns partial data missing many US listings. Cap activates, surface warning.
    • Full fallback (FMP_OK=0, queue = all 64 active/monitoring): cap activates. Run top 20 by conviction + recency. Calendar carries ⚠️ FMP unavailable — calendar reflects 20/64 active+monitoring tickers; remainder pulled from existing thesis Catalysts sections only. Re-run when FMP is healthy. at the top of the file.

    2c. Record source per ticker

    In the in-memory earnings map, tag every entry with its source (FMP, Quartr, or WebSearch). Phase 4's _catalyst.md Notes column surfaces this — FMP entries get a confirmed via FMP tag, Quartr entries confirmed via Quartr (equal confidence — structured event data, and the only source carrying exact call datetimes for non-US listings), WebSearch entries get est. via news search. Lets the user weight the calendar's confidence at a glance.

    2d. Clean up

    rm -f /tmp/catalyst_earnings.json
    

Phase 3: Analyse Catalyst Clusters

📍 Emit before: 📍 Phase 3: clustering catalysts (concentration, gaps, asymmetric, cross-thesis) ✅ Emit after: ✅ Phase 3: <N> clusters identified, <M> no-catalyst theses, <K> cross-thesis events (3+ tickers)

  • Concentration risk: Are there weeks where multiple catalysts cluster? This creates portfolio-level volatility
  • Catalyst gaps: Which theses have NO near-term catalyst? Flag as thesis drift risk — a position with no catalyst is dead capital
  • Asymmetric events: Which catalysts have the widest range of possible outcomes? These are the highest-attention-priority events
  • Cross-thesis catalysts: Events that simultaneously affect 3+ positions (e.g., a macro data release, a major customer's earnings)

Phase 4: Output

📍 Emit before: 📍 Phase 4: writing _catalyst.md (6 tables + Notes block) ✅ Emit after: ✅ Phase 4: _catalyst.md written (<word_count> words, <total_events> events across <unique_tickers> tickers)

Snapshot already taken in Phase 0.2 with the mandatory hard-abort guard (§1.3 invariant). Do NOT re-snapshot here — a second cp without the hard-abort/non-empty check would silently overwrite Phase 0.2's protective snapshot under filesystem pressure, destroying the recovery path. The Phase 0.2 snapshot covers the pre-regenerate state for the entire run.

Save/update the catalyst calendar at _catalyst.md (overwrite each run — this is a regenerated utility file like _hot.md, not a research note):

---
date: YYYY-MM-DD
tags: [meta, catalyst-calendar]
---

Structure:

Next 2 Weeks (day-by-day detail)

DateTicker(s)EventExpected ImpactMagnitudeThesis testNotes

Weeks 3-4

Week ofTicker(s)EventExpected ImpactThesis testNotes

Conference clusters (next 90d) (present only when Phase 2a-ter ran and found ≥1 multi-name cluster)

ConferenceDatesPortfolio names presentingNotes

Cluster rows carry the Quartr conference/event links; a cluster within the next 2 weeks also earns a one-line mention in the relevant day rows of the Next 2 Weeks table (part of [Conference] cluster — see Conference clusters). Post-event, /event-sweep's weekly digest picks up the transcripts — the calendar's job ends at the appointment.

Thesis test column (falsification layer — READING PROTOCOL). Format: [observable + threshold] → [confirms Insight #n | fires "→ LOW/HIGH/CLOSE if …" trigger of [[TICKER]] | untested-unmatched | untested-no-trigger]. Populate from the Phase 1.2 Conviction-Triggers extraction — the thesis is already fully loaded, zero extra reads. This converts the calendar from a when-and-which-way schedule into a pre-registered hypothesis-test schedule: for each event, what outcome would CONFIRM or REFUTE which thesis.

Split the null case — the two "untested" values mean different things and imply different fixes:

  • untested-unmatched — the thesis HAS ## Conviction Triggers but none matches this event. Signal: the trigger set doesn't cover this event's risk → suggest a /deepen TICKER "Conviction Triggers" to add one. Actionable gap.
  • untested-no-trigger — the thesis has NO ## Conviction Triggers section at all. Signal: nothing defined can falsify the thesis → the event is unmoored from any pre-registered test. This is the stronger /prune + /lint #60 signal; suggest creating triggers first.

A portfolio heavy in untested-no-trigger means the theses lack falsification machinery entirely; a portfolio heavy in untested-unmatched means the machinery exists but has coverage holes. Do not collapse them into one bucket — the distinction is the whole point.

Earnings-within-14-days → /transcript prompt. For any earnings event in the Next 2 Weeks table (Phase 2 enrichment supplied the date), tag its Notes cell → run /transcript TICKER once reported. /transcript extracts management-language deltas + Q&A tone + trigger touches — the vault's highest-signal, most-underused earnings input (it has run once, ever). The catalyst calendar is the natural place to surface that a transcript will be pullable within days, so the earnings event actually gets ingested instead of passing unremarked. Collect these into the Phase 6.2 report as a Transcript-ready this fortnight: line.

Month 2-3 (grouped by week)

Approximate DateTicker(s)EventNotes

No Catalyst Identified

TickerLast Catalyst DateDays SinceFalsifiable?Status
[Theses with no upcoming catalyst — flag for attention. Falsifiable? = does the thesis have a ## Conviction Triggers section (yes/no)? A thesis with NO catalyst AND NO triggers is doubly dead capital — nothing scheduled can move it and nothing defined can kill it: the strongest /prune candidate signal this calendar produces.]

Stale Catalysts (events that appear to have passed)

TickerEventListed DateAction Needed
[Catalysts whose dates have passed — thesis needs updating]

Cross-Thesis Events [Events affecting 3+ theses, with impact assessment for each]

Phase 5: Update _hot.md

📍 Emit before: 📍 Phase 5: updating _hot.md (Active Research Thread + Open Questions for <K> no-catalyst tickers) ✅ Emit after: ✅ Phase 5: _hot.md updated (<word_count> words, <new_open_qs> new Open Questions added) ⚠️ Emit if word cap pruning triggers: ⚠️ Phase 5: _hot.md over soft cap (8,000w) — pruning <section> oldest entries until under cap ❌ Emit if hard cap hit: ❌ Phase 5: _hot.md over hard cap (10,000w) after pruning — aborting _hot.md update per contract

Read _hot.md then edit (do NOT touch Latest Sync or Sync Archive — those are /sync-owned). If _hot.md does not exist, create it per the CLAUDE.md Rule #9 schema.

  1. Active Research Thread: Same-topic continuation — if the current thread already covers catalyst-calendar work, append a dated line (YYYY-MM-DD: refreshed catalyst calendar — [key takeaway, e.g. "2 dense weeks ahead, 3 theses with no catalyst"]). New topic: compress the outgoing thread into a single *Previous:* entry (date + one-phrase summary). Write the new thread focused on catalyst state: YYYY-MM-DD: refreshed catalyst calendar — next 2 weeks = [N] catalysts across [M] theses; [K] no-catalyst theses flagged for review. Append *Previous:* line(s) — max 5, drop oldest.

  2. Open Questions: For every ticker flagged under "No Catalyst Identified" in the catalyst calendar output, add a question (if not already present): - [[TICKER]]: what catalyst in next 90 days could reignite this thesis? (flagged by /catalyst YYYY-MM-DD). Dedupe by question pattern, not by ticker presence — only suppress this entry if an existing Open Question already contains BOTH the ticker wikilink AND the substring catalyst in next 90 days could reignite this thesis. Other pre-existing questions about the ticker (e.g., from /thesis, /stress-test, /surface) must not suppress the catalyst-specific question; a thesis can legitimately have multiple open questions at once, and over-broad dedup would silently drop the catalyst gap from the user's attention surface.

  3. Recent Conviction Changes: not touched by /catalyst.

  4. Portfolio Snapshot: not touched by /catalyst.

Word cap: After edits, check total word count. If over 4,000 words (soft cap per _shared/hot-md-contract.md), prune ## Sync Archive entries (oldest first), then *Previous:* lines in Active Research Thread (oldest first), until under cap. If over 5,000 (hard cap), abort _hot.md update per contract.

Phase 6: Report + lock release

📍 Emit at Phase 6 start: 📍 Phase 6: releasing lock and composing final report ✅ Emit after lock release: ✅ Phase 6: lock released cleanly (token <token>) — or ⚠️ Phase 6: lock token mismatch — leaving lock in place if §6.1 found a mismatch.

Runs in the main conversation (no fork) — the Phase 6 report renders directly in chat. The ~71 full thesis + macro reads (~400K tokens at current vault scale) consume main-session context, so run /catalyst in a fresh conversation when context budget matters; _catalyst.md persists to disk as the authoritative calendar for later reading.

6.1 Lock release (MUST run before the report, even if Phases 1–5 had non-fatal errors)

Issue the lock-release Bash block FIRST in Phase 6 — before composing the report. The trap-on-INT/TERM cleanup from Phase 0.1 only fires on signal-based termination; if the run is killed via SIGKILL (host timeout enforcement) or exits cleanly, the trap never runs. Explicit release here is the primary cleanup path; trap is the fallback.

# Verify token ownership before deletion (Procedure 1.5 — never delete a lock you don't own)
LOCK_FILE=".vault-lock"
EXPECTED_TOKEN="<token captured from Phase 0.1>"
if [ -f "$LOCK_FILE" ]; then
  ACTUAL_TOKEN=$(grep '^token:' "$LOCK_FILE" | sed 's/token: //')
  if [ "$ACTUAL_TOKEN" = "$EXPECTED_TOKEN" ]; then
    rm -f "$LOCK_FILE"
    echo "✅ Released vault-wide lock (token $EXPECTED_TOKEN)"
  else
    echo "⚠️ Lock token mismatch (expected $EXPECTED_TOKEN, found $ACTUAL_TOKEN). NOT releasing — another skill owns this lock now (likely because timeout_at passed and another skill broke in). Leaving in place."
  fi
else
  echo "ℹ️ Lock file already removed (trap may have fired, or skill ran past timeout_at and was reaped)."
fi

Why this matters: empirically (2026-05-23 incident) when the /catalyst subagent was killed at 9 min by host timeout, the trap-on-INT/TERM cleanup did not fire and .vault-lock was left orphaned at vault root for ~10 minutes until manually removed. While the orphaned lock's timeout_at had already passed (so other skills would treat it as stale via §1.3a's expiry check), it remained as clutter and a source of operator confusion. Explicit release closes the gap between "skill ran successfully" and "lock is gone".

6.2 Report

Report to user: next 2 weeks of catalysts, any dangerous clusters, and the list of theses with no catalyst (these need attention or pruning). Include one line summarizing the _hot.md update: _hot.md: Active Research Thread refreshed; [K] no-catalyst tickers added to Open Questions. Point the user to [[_catalyst.md]] for the full calendar tables (Next 2 Weeks, Weeks 3-4, Month 2-3, No Catalyst Identified, Stale Catalysts, Cross-Thesis Events).

Add a Transcript-ready this fortnight: [TICKER (date), …] — run /transcript after each reports line whenever any earnings event lands in the next 14 days (from the Phase 4 tagging). Omit the line entirely when none do — no noise on quiet fortnights.

If Phase 2b's WebSearch cap activated, surface the degraded-mode warning in the report header: ⚠️ FMP unavailable this run — calendar reflects top-20-by-conviction WebSearch results; [N] tickers carry pre-existing thesis Catalysts data only. Re-run /catalyst when FMP is healthy for full refresh.

レビュー

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

同じリポジトリのスキル

概要と使いどころ

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日 更新

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日 更新

compare

無料

Side-by-side competitive comparison of two or more companies. Use when user says "compare", "X vs Y", "which is better", or asks about competitive dynamics between specific companies.

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

jameswong2011/InvestmentVault32026年9月9日 更新

jameswong2011 のスキルをすべて見る

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