plugins/foundry/skills/research-curate/SKILL.md
Walk thoughts/shared/research/ and thoughts/shared/plans/, score each doc's staleness, regenerate INDEX.md, and append LLM-surfaced contradictions to CONTRADICTIONS.md (append-only). Source docs are never modified. Classification: current (age<90d AND refs valid), needs-review (age>=90d OR broken refs), likely-stale (age>=180d AND no recent activity). Inventory is deterministic; contradiction detection runs one LLM call per cluster (CTL-467 + CTL-468 / Initiative 4 Phase 1+2).
npx skillsauth add coalesce-labs/catalyst research-curateInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Regenerates INDEX.md inventories for thoughts/shared/research/ and
thoughts/shared/plans/, then clusters docs by topic and asks an LLM to
identify contradicting claims, appending findings to CONTRADICTIONS.md.
Source markdown is never modified — only INDEX.md (overwritten) and
CONTRADICTIONS.md (appended) are written.
date, topic,
tags), counts words, extracts file:line references (file.ext:N pattern).HEAD, and counts last-30d git log hits for the doc's top-3 tags.current — age < 90d AND all refs resolve at HEADneeds-review — age >= 90d OR at least one broken reflikely-stale — age >= 180d AND zero recent topic activity<dir>/INDEX.md with three sections sorted by
date descending. Output is fully deterministic.tags ∪ topic, computes
pairwise Jaccard similarity, unions docs with score ≥ 0.4 via union-find.
Clusters of size 3–10 are kept (smaller dropped, larger capped at 10).
For each cluster, one LLM call (default claude -p) receives a
token-bounded prompt (1500 chars/doc) asking for contradictions. New
findings are appended to <dir>/CONTRADICTIONS.md under a
## YYYY-MM-DD — <topic> heading. Existing entries are never modified.# Curate both directories in-place (inventory + contradictions)
bash plugins/dev/scripts/research-curate/run.sh thoughts/shared/research
bash plugins/dev/scripts/research-curate/run.sh thoughts/shared/plans
# Inventory only — skip the LLM-driven contradiction pass
bash plugins/dev/scripts/research-curate/run.sh --skip-contradictions \
thoughts/shared/research
# Dry run — INDEX written to /tmp; contradictions never touched
bash plugins/dev/scripts/research-curate/run.sh --dry-run thoughts/shared/research
Each invocation prints summary lines:
thoughts/shared/research: current=42 needs-review=83 likely-stale=80 → thoughts/shared/research/INDEX.md
thoughts/shared/research: contradictions clusters=5 appended=3 → thoughts/shared/research/CONTRADICTIONS.md
Projected cost for the weekly Routine: ~$0.38/run (75K input tokens × $5/M), ~$1.50/month.
When invoked as /catalyst-foundry:research-curate, run both directories
sequentially and report the summary lines back to the user.
set -euo pipefail
# Backing scripts live in catalyst-dev (the shared framework core). Resolve them
# (and fail fast with a clear message if catalyst-dev is not installed).
source "${CLAUDE_PLUGIN_ROOT:-plugins/foundry}/scripts/require-catalyst-dev.sh" \
"${CLAUDE_PLUGIN_ROOT:-plugins/foundry}" || exit 1
SCRIPT="${CATALYST_DEV_SCRIPTS}/research-curate/run.sh"
bash "$SCRIPT" thoughts/shared/research
bash "$SCRIPT" thoughts/shared/plans
Source-doc immutability is enforced by inventory.sh (which only ever reads
source markdown), run.sh (which writes exclusively to INDEX.md or a
/tmp/ dry-run path), and append-contradictions.sh (which opens
CONTRADICTIONS.md in append-mode only). The test suite asserts this
property against fixture repos via before/after hash comparisons.
bash plugins/dev/scripts/__tests__/research-curate-inventory.test.sh
bash plugins/dev/scripts/__tests__/research-curate-contradictions.test.sh
development
Migrate a single-harness repo to the dual-harness layout so both Claude Code and Codex load the same instructions and skills — AGENTS.md as the portable canonical doc, a thin CLAUDE.md `@AGENTS.md` bridge, and a `.agents/skills` dir with a `.claude/skills` symlink onto it. Use when asked to migrate to dual-harness, make this repo work in both Claude and Codex, or for agent metadata cleanup.
tools
Goal-driven senior-engineer pipeline-unstick sweep (CTL-1176 rung 3). Given the stuck/failed/needs-human set (or ONE ticket handed by the recovery router), its GOAL is to get the pipeline MOVING again — not to fix one ticket's review findings (that is phase-remediate). It runs AFTER the eyes (diagnostician evidence) and the hands (deterministic unstuck-sweep seams) have already tried, and it CONSUMES their output from a recovery-pass.json brief rather than re-diagnosing or redoing their narrow work. It acts like a senior engineer with full tool access — it resolves merge conflicts, rebases, force-pushes, merges green PRs, and re-dispatches stalled phases AUTONOMOUSLY — and escalates to the operator ONLY for a genuine value judgment / something that degrades other functionality / a real cost-benefit trade-off / a serious architecture change / an ADR conflict. On escalation it AUTHORS the operator inbox row + the push notification (executive-voiced). Dispatched as a `claude --bg` job by phase-agent-dispatch via slash command, AND invocable bare by the operator as a sweep — hence `user-invocable: true`. Ships behind CATALYST_RECOVERY_PASS (off by default — no live behavior change until shadow/enforce).
tools
Diagnose and fix Catalyst setup issues. Validates tools, database, config, OTel, direnv, and thoughts. Automatically fixes what it can — creates directories, initializes the database, sets WAL mode, runs migrations. Use for new installs, upgrades, or when something isn't working.
tools
--- name: phase-triage description: Phase agent that triages a Linear ticket — expands acronyms, classifies (feature/bug/docs/refactor/chore), identifies genuine blockers (a semantic second-pass over the backlog — NOT a prose scrape; CTL-838), estimates scope, writes triage.json, and posts a triage analysis comment to Linear. Triage completion is signaled by that comment plus the local triage.json — there is no `triaged` label. Emits phase.triage.complete.<TICKET> on success and phase.triage.fai