diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ab58d14f..5d91cd59 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -51,6 +51,9 @@ jobs: - name: Type check run: uv run mypy src/ + - name: Vendor backlinks in docs + run: uv run python scripts/backlinks.py --check + - name: Tests with coverage run: | uv run pytest \ diff --git a/.gitignore b/.gitignore index b96620b5..68362feb 100644 --- a/.gitignore +++ b/.gitignore @@ -106,6 +106,7 @@ scripts/google-sheets/portfolio-optimizer/ scripts/* !scripts/onboarding/ !scripts/qa/ +!scripts/backlinks.py # Private PII data files (real values for git-filter-repo — tracked templates available instead) scripts/qa/pii-replacements.txt scripts/qa/author-mailmap.txt diff --git a/AGENTS.md b/AGENTS.md index 55f88211..463ab9bd 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,6 +1,6 @@ # Finance Guru -Finance Guru is a self-hosted family office engine: typed Python calculators, a private SQLite ledger, and specialist agents that run inside Claude Code or Codex. It is the open-core engine behind Keepfolio. The README describes the product for people. This file is the canonical instruction set for any agent working in the repository or in an instance built from it. `CLAUDE.md` imports it and adds only what is specific to Claude Code. +Finance Guru is a self-hosted family office engine: typed Python calculators, a private [SQLite](https://www.sqlite.org/) ledger, and specialist agents that run inside [Claude Code](https://code.claude.com/) or [Codex](https://github.com/openai/codex). It is the open-core engine behind [Keepfolio](https://keepfolio.app/). The README describes the product for people. This file is the canonical instruction set for any agent working in the repository or in an instance built from it. `CLAUDE.md` imports it and adds only what is specific to Claude Code. ## The split @@ -19,7 +19,7 @@ Agents propose. Typed code computes. Every number an agent quotes comes from a c Private data lives in an instance directory outside the repository, resolved from `FIN_GURU_DATA_ROOT` or the current working directory. The instance holds `.env`, `user-profile.yaml`, `config.yaml`, `family_office.db`, broker CSV exports under `imports/`, and every artifact the engine writes. Tracked files in this repository never carry personal values. `uv run python -m src.cli.instance_init --repo .` scaffolds an instance. - Run engine commands from the instance as `uv run python -m src.` so private paths resolve from the current directory. -- `family_office.db` is the single source of truth for positions, balances, transactions, and bank transactions. `src.integrations.refresh_all` fills it from SnapTrade (brokerage) and SimpleFIN (bank and card) and raises on a partial provider response. CSV exports in `imports/` are an ingestion path into the database, not a second ledger. +- `family_office.db` is the single source of truth for positions, balances, transactions, and bank transactions. `src.integrations.refresh_all` fills it from [SnapTrade](https://snaptrade.com/) (brokerage) and [SimpleFIN](https://www.simplefin.org/) (bank and card) and raises on a partial provider response. CSV exports in `imports/` are an ingestion path into the database, not a second ledger. - The Google Sheets DataHub is retired. Do not write to Sheets, reference a spreadsheet ID, or reintroduce a gdrive MCP dependency. - Write analysis to `analysis/` as `{topic}-{YYYY-MM-DD}.md` and buy tickets to `tickets/` as `buy-ticket-{YYYY-MM-DD}-{descriptor}.md`. Markdown with YAML frontmatter, date stamp, disclaimer, and citations. - The pre-push compliance scan blocks a push that carries secrets or PII. A fresh checkout installs it with `.claude/skills/compliance-scan/scripts/install-pre-push.sh`. @@ -34,11 +34,11 @@ A scaffolded checkout-mode instance holds `.agents` and `.claude` symlinks to th ## Code -- Every calculator is three layers: a Pydantic input model in `src/models/`, a calculator class, and a `*_cli.py` wrapper in the same directory. A new tool ships all three plus a test under `tests/python/` that runs without a network. Conventions and gotchas for the Python code are in `src/CLAUDE.md`. +- Every calculator is three layers: a [Pydantic](https://docs.pydantic.dev/) input model in `src/models/`, a calculator class, and a `*_cli.py` wrapper in the same directory. A new tool ships all three plus a test under `tests/python/` that runs without a network. Conventions and gotchas for the Python code are in `src/CLAUDE.md`. - The CLI reference is `docs/reference/api.md`. Every CLI answers `--help`. -- `apps/simplefin-sync/` is a Bun workspace. Use `bun`, never npm. +- `apps/simplefin-sync/` is a [Bun](https://bun.sh/) workspace. Use `bun`, never npm. - Research skills may call the MCP servers named in their persona files. None are required to run the calculators or the sync. -- Markdown emphasis uses underscores, `_like this_`. markdownlint rule MD049 enforces it. +- Markdown emphasis uses underscores, `_like this_`. [markdownlint](https://github.com/DavidAnson/markdownlint) rule MD049 enforces it. ## Toolchain and gates @@ -52,9 +52,9 @@ uv run mypy src/ uv run pytest -m "not integration" ``` -- The pre-commit hook runs the full pytest suite with the 80% coverage gate on every commit, so a commit takes about ten seconds. Tests marked `integration` need real API keys. +- The pre-commit hook runs the full [pytest](https://docs.pytest.org/) suite with the 80% coverage gate on every commit, so a commit takes about ten seconds. Tests marked `integration` need real API keys. - Tests assume no `.env` in the repository root. A scaffolded `.env` holds placeholder strings such as `your_monthly_dividend_income_here`, and `python-dotenv` loads them, which fails the margin-metrics tests with `could not convert string to float`. Delete the file or fill in real numbers. -- Pull requests get CodeRabbit and Claude reviews. Verify each finding against the source before acting on it and dismiss a false positive with a written reason. +- Pull requests get [CodeRabbit](https://www.coderabbit.ai/) and Claude reviews. Verify each finding against the source before acting on it and dismiss a false positive with a written reason. ## Session end @@ -62,7 +62,7 @@ Work is not complete until `git push` succeeds. Before ending a session: file is ## Cursor Cloud -The startup script already runs `uv sync --dev` and `bun install`. `uv` lives at `~/.local/bin/uv` and `bun` at `~/.bun/bin/bun`. Both are on `PATH` for interactive shells only, so a fresh non-interactive shell needs `export PATH="$HOME/.local/bin:$HOME/.bun/bin:$PATH"` first. There is no long-running server. Run an analysis with `uv run python -m src.analysis.risk_metrics_cli AAPL --days 252 --benchmark SPY`, which fetches live data through yfinance with no API key, or launch the dashboard with `uv run python -m src.cli.fin_guru` and quit with `q`. +The startup script already runs `uv sync --dev` and `bun install`. `uv` lives at `~/.local/bin/uv` and `bun` at `~/.bun/bin/bun`. Both are on `PATH` for interactive shells only, so a fresh non-interactive shell needs `export PATH="$HOME/.local/bin:$HOME/.bun/bin:$PATH"` first. There is no long-running server. Run an analysis with `uv run python -m src.analysis.risk_metrics_cli AAPL --days 252 --benchmark SPY`, which fetches live data through [yfinance](https://github.com/ranaroussi/yfinance) with no API key, or launch the dashboard with `uv run python -m src.cli.fin_guru` and quit with `q`. ## Documentation diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7f5d0c26..475c9dbc 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -8,7 +8,7 @@ Short version — before you open a pull request: 2. Fork, clone, run `./setup.sh` 3. Create a feature branch off `main` 4. Run `uv run ruff format .`, `uv run ruff check .`, `uv run mypy src/`, `uv run pytest` -5. Open a PR — CodeRabbit and the Claude review bot will comment automatically +5. Open a PR — [CodeRabbit](https://www.coderabbit.ai/) and the Claude review bot will comment automatically ## Filing issues @@ -18,8 +18,8 @@ Short version — before you open a pull request: ## Style -- Python — ruff (format + lint) + mypy strict +- Python — [ruff](https://docs.astral.sh/ruff/) (format + lint) + [mypy](https://mypy-lang.org/) strict - Markdown — MD049 enforced; use underscores `_text_` not asterisks for emphasis -- Commits — conventional commits (`feat:`, `fix:`, `docs:`, `chore:`, `security:`) so release-please can cut versions +- Commits — [conventional commits](https://www.conventionalcommits.org/) (`feat:`, `fix:`, `docs:`, `chore:`, `security:`) so [release-please](https://github.com/googleapis/release-please) can cut versions See [docs/CONTRIBUTING.md](docs/CONTRIBUTING.md) for everything else. diff --git a/PRIVACY.md b/PRIVACY.md index 8e59509a..5781b3a7 100644 --- a/PRIVACY.md +++ b/PRIVACY.md @@ -9,7 +9,7 @@ Finance Guru is a **private, single-user family-office system**. This document d - All financial data stays on the owner's machine - No telemetry, no remote analytics, no third-party data sharing - Nothing personally identifying is committed to the public engine repository; the separate instance repository is local-only and has no remote -- When data _does_ leave the laptop (SnapTrade, SimpleFIN, Fidelity APIs, ITC Risk Models), it goes directly to the owner's own accounts — no intermediary +- When data _does_ leave the laptop ([SnapTrade](https://snaptrade.com/), [SimpleFIN](https://www.simplefin.org/), [Fidelity](https://www.fidelity.com/) APIs, ITC Risk Models), it goes directly to the owner's own accounts — no intermediary ## What data Finance Guru handles @@ -21,13 +21,13 @@ Finance Guru is a **private, single-user family-office system**. This document d | Bank / card spending | SimpleFIN | `family_office.db` (committed to local-only instance repository) | Local only; no remote | | Dividend events | SnapTrade activities | `family_office.db` (committed to local-only instance repository) | Local only; no remote | | User profile (risk tolerance, goals) | Interactive onboarding | `user-profile.yaml` under the instance root (committed to local-only instance repository) | Local only; no remote | -| Market data | yfinance / Finnhub / ITC | In-memory during analysis | Per-provider terms | +| Market data | [yfinance](https://github.com/ranaroussi/yfinance) / [Finnhub](https://finnhub.io/) / ITC | In-memory during analysis | Per-provider terms | ## What leaves your machine 1. _Broker and bank reads_ — SnapTrade and SimpleFIN are queried outbound to pull your own accounts. Both are read-only; nothing is written back to a broker. 2. _Market-data queries_ — yfinance, Finnhub, and ITC Risk Models APIs see which tickers you query, but not your position sizes. -3. _Claude Code session_ — the LLM provider (Anthropic) sees conversation content including any data you paste into the session. Treat Claude Code like a privileged assistant — don't paste data you wouldn't email your accountant. +3. _Claude Code session_ — the LLM provider ([Anthropic](https://www.anthropic.com/)) sees conversation content including any data you paste into the session. Treat [Claude Code](https://code.claude.com/) like a privileged assistant — don't paste data you wouldn't email your accountant. > The Google Sheets DataHub was retired 2026-07-31. Portfolio data no longer leaves the machine for Google Drive, and no `gdrive` MCP server is configured. diff --git a/README.md b/README.md index 52c1ab7a..aaf83f06 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ You paste a screenshot into a chat model and ask whether a position is too big. -Every number an agent quotes comes from a Pydantic-validated calculator with a CLI you can run yourself. Every irreversible action passes through a guardrail that blocks when its inputs are missing rather than guessing. The AI gets judgment. The math gets a test suite. +Every number an agent quotes comes from a [Pydantic](https://docs.pydantic.dev/)-validated calculator with a CLI you can run yourself. Every irreversible action passes through a guardrail that blocks when its inputs are missing rather than guessing. The AI gets judgment. The math gets a test suite.
@@ -49,7 +49,7 @@ Every number an agent quotes comes from a Pydantic-validated calculator with a C ### Prerequisites -Python 3.12 or later, [uv](https://docs.astral.sh/uv/), and Git. Bun only if you work on the SimpleFIN sync app. +[Python](https://www.python.org/) 3.12 or later, [uv](https://docs.astral.sh/uv/), and [Git](https://git-scm.com/). [Bun](https://bun.sh/) only if you work on the [SimpleFIN](https://www.simplefin.org/) sync app. ### Install @@ -82,13 +82,13 @@ Then run the `instance-onboarding` skill. It scaffolds the instance with `--plug ## What is in the box -Finance Guru is the open-core engine behind Keepfolio, a "claude-code for personal finance." This repo is the free, self-hosted, CLI-native experience: the whole engine, every skill, every agent, no withheld tier. +Finance Guru is the open-core engine behind [Keepfolio](https://keepfolio.app/), a "claude-code for personal finance." This repo is the free, self-hosted, CLI-native experience: the whole engine, every skill, every agent, no withheld tier. | Component | What it does | Why it matters | | :--- | :--- | :--- | | **Typed calculators** (`src/`) | 20 CLIs for risk, momentum, volatility, correlation, optimization, backtesting, options, factors, hedging, total return, and margin metrics. Every one is Pydantic input, calculator class, CLI wrapper. | Calculations are testable outside any AI session. The suite is 1,100+ tests behind an 80% coverage gate. | | **Private instance** | A directory outside the repo holding `family_office.db`, your `.env`, broker CSVs, and every artifact the engine writes. | No spreadsheet, no cloud sync, no vendor dashboard between you and your ledger. Tracked files never carry personal values. | -| **Sync layer** (`src/integrations/`) | `refresh_all` pulls SnapTrade (brokerage) and SimpleFIN (bank and card) into SQLite with your own read-only credentials. | Partial provider responses raise before any write. You get an error instead of a coverage ratio computed on half your accounts. | +| **Sync layer** (`src/integrations/`) | `refresh_all` pulls [SnapTrade](https://snaptrade.com/) (brokerage) and SimpleFIN (bank and card) into [SQLite](https://www.sqlite.org/) with your own read-only credentials. | Partial provider responses raise before any write. You get an error instead of a coverage ratio computed on half your accounts. | | **Fail-closed guardrails** | Concentration cap, margin coverage, and ITC risk run against trusted inputs before a buy ticket persists. | A block on missing NAV, missing rate, or an unrun Layer 3 score. A bad ticket needs a number to pass, not a prompt to agree. | | **Skills and specialists** (`.claude/`) | Skills and 11 specialist agents coordinated by a finance orchestrator. They read the DB, run the CLIs, and write Markdown into your instance. | Every output carries its data source, date stamp, and the educational-only disclaimer. You can trace any claim back to a CLI run. | | **Compliance scan on push** | A pre-push hook scans history and the diff for secrets and PII. | The repo is public. Your data is not. The hook is what keeps that true. | @@ -137,7 +137,7 @@ Local-first does not mean network-free. Market data, brokerage, and LLM integrat You are the intended operator of this engine, not an afterthought. A few things that make you effective here: -- **Read `CLAUDE.md` first.** It is the single source of truth for the skills index, the agent roster, the path variables, and the output rules. `AGENTS.md` points Codex at the same tree through the instance's `.agents` symlink. +- **Read `CLAUDE.md` first.** It is the single source of truth for the skills index, the agent roster, the path variables, and the output rules. `AGENTS.md` points [Codex](https://github.com/openai/codex) at the same tree through the instance's `.agents` symlink. - **Run `date` before any market work.** Every specialist here is expected to know the current date before it searches or analyzes. - **Never do the arithmetic yourself.** Call the CLI with `--output json` and quote what it returned. That is the whole reason the calculators exist. - **Private data lives in the instance, not the repo.** Resolve `FIN_GURU_DATA_ROOT` or the working directory, read from `family_office.db`, and write artifacts into `analysis/` or `tickets/`. Tracked files never carry personal values, and the pre-push scan will stop you if you try. @@ -173,10 +173,10 @@ The repo is public because the pattern is more useful than the portfolio. The co - [x] Three-layer calculators for risk, momentum, volatility, correlation, optimization, backtesting, options, factors, hedging, and margin - [x] Local SQLite system of record fed by SnapTrade and SimpleFIN - [x] Fail-closed buy-ticket guardrails with Layer 3 ITC authority -- [x] Diátaxis documentation site on GitHub Pages -- [ ] Claude Code plugin as the primary install path ([#131](https://github.com/AojdevStudio/Finance-Guru/issues/131)) +- [x] [Diátaxis](https://diataxis.fr/) documentation site on [GitHub Pages](https://pages.github.com/) +- [ ] [Claude Code](https://code.claude.com/) plugin as the primary install path ([#131](https://github.com/AojdevStudio/Finance-Guru/issues/131)) - [ ] Retirement-account sync migration gate ([#74](https://github.com/AojdevStudio/Finance-Guru/issues/74)) -- [ ] Automated releases through release-please ([#112](https://github.com/AojdevStudio/Finance-Guru/issues/112)) +- [ ] Automated releases through [release-please](https://github.com/googleapis/release-please) ([#112](https://github.com/AojdevStudio/Finance-Guru/issues/112)) Open items carry the `roadmap` label. Defects carry `bug`. diff --git a/docs-site/src/content/docs/explanation/architecture-and-data-flow.md b/docs-site/src/content/docs/explanation/architecture-and-data-flow.md index e831b4fd..56e9a743 100644 --- a/docs-site/src/content/docs/explanation/architecture-and-data-flow.md +++ b/docs-site/src/content/docs/explanation/architecture-and-data-flow.md @@ -27,17 +27,17 @@ The engine checkout is public code. Private data lives in a separate instance di ## The three layers -Every analysis tool follows one 3-layer pattern. Pydantic models validate inputs and outputs. Calculator classes hold the business logic. Thin CLI entry points handle input and output only. The pattern keeps calculations testable and keeps command surfaces consistent, so every tool takes a ticker, a window, and flags in the same shape. +Every analysis tool follows one 3-layer pattern. [Pydantic](https://docs.pydantic.dev/) models validate inputs and outputs. Calculator classes hold the business logic. Thin CLI entry points handle input and output only. The pattern keeps calculations testable and keeps command surfaces consistent, so every tool takes a ticker, a window, and flags in the same shape. ## Where data comes from -Two read-only integrations feed the local database. The SnapTrade modules sync brokerage positions, balances, and activities. The SimpleFIN workspace syncs bank and card transactions. Broker CSV exports remain a first-class source, dropped into the instance import directory, so the system works without any live credentials. The coordinating entry point is `src.integrations.refresh_all`, which runs every configured source and exits non-zero on a partial refresh. +Two read-only integrations feed the local database. The [SnapTrade](https://snaptrade.com/) modules sync brokerage positions, balances, and activities. The [SimpleFIN](https://www.simplefin.org/) workspace syncs bank and card transactions. Broker CSV exports remain a first-class source, dropped into the instance import directory, so the system works without any live credentials. The coordinating entry point is `src.integrations.refresh_all`, which runs every configured source and exits non-zero on a partial refresh. ## Verified implementation seams - `src/integrations/refresh_all.py` coordinates the configured refreshes. - `src/integrations/snaptrade/` contains account, position, balance, and activity synchronization surfaces. -- `src/integrations/simplefin/sync_expenses_db.py` imports configured bank and card transaction data into local SQLite. +- `src/integrations/simplefin/sync_expenses_db.py` imports configured bank and card transaction data into local [SQLite](https://www.sqlite.org/). - `src/models/`, `src/analysis/`, and the CLI entry points form the checked-in validation, business-logic, and command layers. ## What sits outside the stable core diff --git a/docs-site/src/content/docs/explanation/open-core.md b/docs-site/src/content/docs/explanation/open-core.md index 1c960ab9..b7de1544 100644 --- a/docs-site/src/content/docs/explanation/open-core.md +++ b/docs-site/src/content/docs/explanation/open-core.md @@ -9,23 +9,23 @@ Finance Guru is converting, in place, from one owner's private system into an in ## Everything is open -All skills, agents, and the Python engine ship free under the existing AGPL-3.0 license. The commercial layer is convenience, never withheld features. AGPL already prevents proprietary forks, so there is no need to hold anything back. +All skills, agents, and the Python engine ship free under the existing [AGPL-3.0](https://www.gnu.org/licenses/agpl-3.0.html) license. The commercial layer is convenience, never withheld features. AGPL already prevents proprietary forks, so there is no need to hold anything back. ## Finance Guru is the free, CLI-native experience -Finance Guru is the self-hosted product for people comfortable in a terminal. Keepfolio is the polished product for people who will not run one. The free plugin is the funnel toward that product, and the relationship is stated openly rather than hidden. +Finance Guru is the self-hosted product for people comfortable in a terminal. [Keepfolio](https://keepfolio.app/) is the polished product for people who will not run one. The free plugin is the funnel toward that product, and the relationship is stated openly rather than hidden. ## CSV-first connectivity -The system works on day one with broker CSV exports. SnapTrade and SimpleFIN live sync stay in the project as documented bring-your-own-credentials options for power users. There is no paid-API wall in the first hour. +The system works on day one with broker CSV exports. [SnapTrade](https://snaptrade.com/) and [SimpleFIN](https://www.simplefin.org/) live sync stay in the project as documented bring-your-own-credentials options for power users. There is no paid-API wall in the first hour. ## Built for technical operators -The first user is a technical operator already running an agent harness. Onboarding may assume CLI comfort, uv, and API-key literacy. That assumption keeps the docs honest about prerequisites instead of hiding them. +The first user is a technical operator already running an agent harness. Onboarding may assume CLI comfort, [uv](https://docs.astral.sh/uv/), and API-key literacy. That assumption keeps the docs honest about prerequisites instead of hiding them. ## Where the transition stands -The privacy foundation is done. The scanner, the push gates, and the data-classification policy are in place. The configurable data root and the instance scaffold shipped, so private data lives outside the repository. Plugin packaging for Claude Code and Codex is planned and being built in a separate effort. It is not on the default branch, so treat any plugin-install instructions as planned rather than shipped. Repositioning of the README for installers follows the packaging work. +The privacy foundation is done. The scanner, the push gates, and the data-classification policy are in place. The configurable data root and the instance scaffold shipped, so private data lives outside the repository. Plugin packaging for [Claude Code](https://code.claude.com/) and [Codex](https://github.com/openai/codex) is planned and being built in a separate effort. It is not on the default branch, so treat any plugin-install instructions as planned rather than shipped. Repositioning of the README for installers follows the packaging work. ## Why this order diff --git a/docs-site/src/content/docs/explanation/privacy-model.md b/docs-site/src/content/docs/explanation/privacy-model.md index 1bdb770f..9abd8670 100644 --- a/docs-site/src/content/docs/explanation/privacy-model.md +++ b/docs-site/src/content/docs/explanation/privacy-model.md @@ -9,9 +9,9 @@ Finance Guru handles the most sensitive data a household has, inside a repositor ## Data stays local -All financial data stays on the owner's machine. There is no telemetry, no remote analytics, and no third-party data sharing. Positions, balances, transactions, and dividend events land in the local, gitignored SQLite database. The profile and every generated artifact live in the instance directory, which has its own local git repository with no remote. +All financial data stays on the owner's machine. There is no telemetry, no remote analytics, and no third-party data sharing. Positions, balances, transactions, and dividend events land in the local, gitignored [SQLite](https://www.sqlite.org/) database. The profile and every generated artifact live in the instance directory, which has its own local git repository with no remote. -Data leaves the machine in only three ways. Broker and bank reads go outbound to SnapTrade and SimpleFIN, both read-only against your own accounts. Market-data providers see which tickers you query, but not your position sizes. An agent-harness session sees whatever you paste into it, so treat it like a privileged assistant and do not paste what you would not email your accountant. +Data leaves the machine in only three ways. Broker and bank reads go outbound to [SnapTrade](https://snaptrade.com/) and [SimpleFIN](https://www.simplefin.org/), both read-only against your own accounts. Market-data providers see which tickers you query, but not your position sizes. An agent-harness session sees whatever you paste into it, so treat it like a privileged assistant and do not paste what you would not email your accountant. ## The tree describes behaviour, never values diff --git a/docs-site/src/content/docs/how-to/configure-live-sync-credentials.md b/docs-site/src/content/docs/how-to/configure-live-sync-credentials.md index 7851f2e1..38f3ad53 100644 --- a/docs-site/src/content/docs/how-to/configure-live-sync-credentials.md +++ b/docs-site/src/content/docs/how-to/configure-live-sync-credentials.md @@ -5,9 +5,9 @@ sidebar: order: 2 --- -Live sync is optional. Finance Guru works with broker CSV exports alone. Configure these credentials only when you want the read-only SnapTrade brokerage sync or the SimpleFIN bank and card sync. +Live sync is optional. Finance Guru works with broker CSV exports alone. Configure these credentials only when you want the read-only [SnapTrade](https://snaptrade.com/) brokerage sync or the [SimpleFIN](https://www.simplefin.org/) bank and card sync. -Keep credentials in a local `.env` file or process environment. `.env` and the local SQLite database are gitignored. That is a safeguard, not permission to print or commit their contents. +Keep credentials in a local `.env` file or process environment. `.env` and the local [SQLite](https://www.sqlite.org/) database are gitignored. That is a safeguard, not permission to print or commit their contents. ## Prepare the environment file @@ -21,7 +21,7 @@ An instance created by the initializer already has a `.env` scaffolded from the ## SnapTrade -The read-only SnapTrade integration requires these private values before it can contact the provider. +The read-only SnapTrade integration requires these private values before it can contact the provider. Sign up for API access at [SnapTrade](https://snaptrade.com/) and take the client ID and consumer key from the [SnapTrade dashboard](https://dashboard.snaptrade.com/). The [SnapTrade API docs](https://docs.snaptrade.com/) cover registering a user, which returns the user ID and user secret. | Variable | Purpose | | --- | --- | @@ -34,7 +34,7 @@ Account roles and enabled-state are stored separately in a local account-routing ## SimpleFIN -SimpleFIN credentials are consumed by the Bun workspace under `apps/simplefin-sync/`. +SimpleFIN credentials are consumed by the [Bun](https://bun.sh/) workspace under `apps/simplefin-sync/`. Get a setup token from [SimpleFIN Bridge](https://bridge.simplefin.org/), the paid SimpleFIN service that connects to your bank and issues tokens. | Variable | Purpose | | --- | --- | diff --git a/docs-site/src/content/docs/how-to/migrate-an-instance.md b/docs-site/src/content/docs/how-to/migrate-an-instance.md index b310e611..353b3072 100644 --- a/docs-site/src/content/docs/how-to/migrate-an-instance.md +++ b/docs-site/src/content/docs/how-to/migrate-an-instance.md @@ -37,7 +37,7 @@ Move the user profile over the empty instance template. command mv "/fin-guru/data/user-profile.yaml" "/user-profile.yaml" ``` -Move the database and each SQLite sidecar that exists. +Move the database and each [SQLite](https://www.sqlite.org/) sidecar that exists. ```bash command mv "/family_office.db" "/family_office.db" @@ -46,7 +46,7 @@ command mv "/family_office.db-shm" "/family_office.db-shm" command mv "/family_office.db-journal" "/family_office.db-journal" ``` -Move the environment and SnapTrade routing files. +Move the environment and [SnapTrade](https://snaptrade.com/) routing files. ```bash command mv "/.env" "/.env" diff --git a/docs-site/src/content/docs/how-to/run-the-syncs.md b/docs-site/src/content/docs/how-to/run-the-syncs.md index 395cf710..ad77b8cd 100644 --- a/docs-site/src/content/docs/how-to/run-the-syncs.md +++ b/docs-site/src/content/docs/how-to/run-the-syncs.md @@ -5,13 +5,13 @@ sidebar: order: 3 --- -Follow this guide to refresh configured brokerage and bank data into the local, gitignored SQLite database. Credentials must already be configured. See [Configure live sync credentials](../configure-live-sync-credentials/) first. +Follow this guide to refresh configured brokerage and bank data into the local, gitignored [SQLite](https://www.sqlite.org/) database. Credentials must already be configured. See [Configure live sync credentials](../configure-live-sync-credentials/) first. Run these commands from your instance directory so the engine resolves the instance database. Sync modules run in Python's module form so package imports resolve. ## Refresh everything -The supported all-source command runs the configured SnapTrade positions and transactions sync plus the SimpleFIN expenses sync. +The supported all-source command runs the configured [SnapTrade](https://snaptrade.com/) positions and transactions sync plus the [SimpleFIN](https://www.simplefin.org/) expenses sync. ```bash uv run python -m src.integrations.refresh_all @@ -40,6 +40,6 @@ Each command supports `--help` for its full flag reference. ## Recover from a failed sync -For SnapTrade, verify the required private environment variables and the account routing file are configured. For SimpleFIN, verify the private access URL has been claimed and is available to the Bun workspace. Do not copy either credential into terminal history, issue text, or screenshots. See [Troubleshoot common failures](../troubleshoot/) for the full recovery paths. +For SnapTrade, verify the required private environment variables and the account routing file are configured. For SimpleFIN, verify the private access URL has been claimed and is available to the [Bun](https://bun.sh/) workspace. Do not copy either credential into terminal history, issue text, or screenshots. See [Troubleshoot common failures](../troubleshoot/) for the full recovery paths. _This page is built from `docs/reference/api.md` and `docs/setup/SETUP.md` in the repository._ diff --git a/docs-site/src/content/docs/how-to/troubleshoot.md b/docs-site/src/content/docs/how-to/troubleshoot.md index 47a343d9..cd07b844 100644 --- a/docs-site/src/content/docs/how-to/troubleshoot.md +++ b/docs-site/src/content/docs/how-to/troubleshoot.md @@ -16,7 +16,7 @@ python3 --version uv --version ``` -Finance Guru requires Python 3.12 or later. Install uv using the command from [its official installation guide](https://docs.astral.sh/uv/getting-started/installation/), then recreate the development environment. +Finance Guru requires Python 3.12 or later. Install [uv](https://docs.astral.sh/uv/) using the command from [its official installation guide](https://docs.astral.sh/uv/getting-started/installation/), then recreate the development environment. ```bash uv sync --dev @@ -62,7 +62,7 @@ uv run python -m src.integrations.refresh_all --show `--show` reads the current local snapshots; omit it to attempt a sync. The command returns a non-zero status when one source fails, even if another source succeeds. Check the individual source status before retrying. -For SnapTrade, verify the required private environment variables and account routing file are configured. For SimpleFIN, verify the private access URL has been claimed and is available to the Bun workspace. Do not copy either credential into terminal history, issue text, or screenshots. +For [SnapTrade](https://snaptrade.com/), verify the required private environment variables and account routing file are configured. For [SimpleFIN](https://www.simplefin.org/), verify the private access URL has been claimed and is available to the [Bun](https://bun.sh/) workspace. Do not copy either credential into terminal history, issue text, or screenshots. ## Bun workspace errors diff --git a/docs-site/src/content/docs/reference/cli.md b/docs-site/src/content/docs/reference/cli.md index 0ff68b57..8051cd21 100644 --- a/docs-site/src/content/docs/reference/cli.md +++ b/docs-site/src/content/docs/reference/cli.md @@ -9,7 +9,7 @@ This is the checked-in command inventory, not a promise that every command is co ## Command pattern -Run standalone scripts from the repository root with uv. +Run standalone scripts from the repository root with [uv](https://docs.astral.sh/uv/). ```bash uv run python path/to/script.py --help @@ -67,9 +67,9 @@ The checked-in Python and TypeScript onboarding scaffolds are transitional and a | Command | Purpose | | --- | --- | -| `uv run python -m src.integrations.refresh_all` | Refresh configured SnapTrade and SimpleFIN data into the local database. | +| `uv run python -m src.integrations.refresh_all` | Refresh configured [SnapTrade](https://snaptrade.com/) and [SimpleFIN](https://www.simplefin.org/) data into the local database. | | `uv run python -m src.integrations.snaptrade.cli` | Inspect linked SnapTrade accounts, positions, balances, or activities. | -| `uv run python -m src.integrations.snaptrade.sync_db` | Refresh SnapTrade positions and balances into local SQLite. | +| `uv run python -m src.integrations.snaptrade.sync_db` | Refresh SnapTrade positions and balances into local [SQLite](https://www.sqlite.org/). | | `uv run python -m src.integrations.snaptrade.sync_transactions_db` | Refresh SnapTrade activities into local SQLite. | | `uv run python -m src.integrations.simplefin.sync_expenses_db` | Refresh SimpleFIN transactions into local SQLite. | diff --git a/docs-site/src/content/docs/reference/data-classification.md b/docs-site/src/content/docs/reference/data-classification.md index c0efdc69..a12a7aa5 100644 --- a/docs-site/src/content/docs/reference/data-classification.md +++ b/docs-site/src/content/docs/reference/data-classification.md @@ -25,7 +25,7 @@ Evidence in a document should be written as a shape rather than an amount, becau | Class | Examples | Where it may live | | --- | --- | --- | -| SECRET | API tokens, SnapTrade and SimpleFIN credentials, private keys | Secrets manager only. Never a file, not even gitignored. | +| SECRET | API tokens, [SnapTrade](https://snaptrade.com/) and [SimpleFIN](https://www.simplefin.org/) credentials, private keys | Secrets manager only. Never a file, not even gitignored. | | PRIVATE | Account balances, equity, margin debt, income, spend totals, dividend amounts, account numbers and their last-four, provider account ids, employer names, payer names, household merchant names, share quantities, cost basis, addresses, phone numbers | Gitignored paths such as the local database and the instance directory. Never a tracked file. | | INTERNAL | Strategy layer names, bucket structure, concentration limits as percentages, category taxonomies, workflow shapes | Tracked files are acceptable. These describe method, not position. | | PUBLIC | Tickers, IRS bracket edges, published fund yields, library versions, round scenario values such as a `$50,000` portfolio or a `$500` contribution | Anywhere. | diff --git a/docs-site/src/content/docs/reference/environment-variables.md b/docs-site/src/content/docs/reference/environment-variables.md index d32ebc66..62243a9f 100644 --- a/docs-site/src/content/docs/reference/environment-variables.md +++ b/docs-site/src/content/docs/reference/environment-variables.md @@ -18,9 +18,9 @@ These variables come from the checked-in `.env.example` sample. Copy it to a loc | Variable | Purpose | | --- | --- | -| `FINNHUB_API_KEY` | Optional real-time price requests. | +| `FINNHUB_API_KEY` | Optional real-time price requests from [Finnhub](https://finnhub.io/). | | `ITC_API_KEY` | ITC risk-model requests. | -| `OPENAI_API_KEY` | Optional, for specific agent tasks. | +| `OPENAI_API_KEY` | Optional, for specific agent tasks. Issued by [OpenAI](https://platform.openai.com/api-keys). | ## SnapTrade live sync @@ -28,7 +28,7 @@ Read-only bridge. Values live only in the local `.env`. | Variable | Purpose | | --- | --- | -| `SNAPTRADE_CLIENT_ID` | SnapTrade application client identifier. | +| `SNAPTRADE_CLIENT_ID` | [SnapTrade](https://snaptrade.com/) application client identifier. | | `SNAPTRADE_CONSUMER_KEY` | SnapTrade consumer key. | | `SNAPTRADE_USER_ID` | Linked user identifier. | | `SNAPTRADE_USER_SECRET` | Linked user secret. | @@ -49,7 +49,7 @@ Optional local bank and card sync. | --- | --- | | `SMTP_SERVER`, `SMTP_PORT` | SMTP server settings for report email. | | `EMAIL_FROM`, `EMAIL_PASSWORD` | Sending address and app password. | -| `SLACK_WEBHOOK_URL` | Slack notification webhook. | +| `SLACK_WEBHOOK_URL` | [Slack](https://slack.com/) notification webhook. | ## Portfolio settings diff --git a/docs-site/src/content/docs/reference/hooks-and-skills.md b/docs-site/src/content/docs/reference/hooks-and-skills.md index dce1634b..b5ffde39 100644 --- a/docs-site/src/content/docs/reference/hooks-and-skills.md +++ b/docs-site/src/content/docs/reference/hooks-and-skills.md @@ -11,7 +11,7 @@ The checked-in `.claude/` skills, commands, and hooks are transitional implement The `.claude/hooks/` implementation is transitional and not a supported public setup dependency. It may be useful to maintainers investigating the legacy agent stack, but it does not define the stable analysis-engine contract. Do not add or configure hooks based on older examples without first checking the checked-in hook configuration and opening an issue. -The public, durable surfaces are the Python analysis engine, local database integrations, tests, and repository documentation. The standalone-app direction intentionally removes the Claude Code dependency. +The public, durable surfaces are the Python analysis engine, local database integrations, tests, and repository documentation. The standalone-app direction intentionally removes the [Claude Code](https://code.claude.com/) dependency. ## Skills @@ -21,6 +21,6 @@ This repository does not contain tracked `.agents/skills/` or `.pi/skills/` syml ## Planned plugin packaging -An installable Claude Code and Codex plugin surface is planned but not on the default branch. It will package the skills and agents with capability probes for the workflows that currently hard-require personal paid MCP integrations. Until it lands, treat any plugin-install instructions as planned, not shipped. See [the open-core position](../../explanation/open-core/) for the surrounding decisions. +An installable Claude Code and [Codex](https://github.com/openai/codex) plugin surface is planned but not on the default branch. It will package the skills and agents with capability probes for the workflows that currently hard-require personal paid MCP integrations. Until it lands, treat any plugin-install instructions as planned, not shipped. See [the open-core position](../../explanation/open-core/) for the surrounding decisions. _This page is built from `docs/reference/hooks.md` and `docs/reference/cross-harness-skills.md` in the repository._ diff --git a/docs-site/src/content/docs/reference/instance-layout.md b/docs-site/src/content/docs/reference/instance-layout.md index db9267ab..aca55573 100644 --- a/docs-site/src/content/docs/reference/instance-layout.md +++ b/docs-site/src/content/docs/reference/instance-layout.md @@ -18,9 +18,9 @@ The root is the value of `FIN_GURU_DATA_ROOT` when that variable is set and non- | `.env` | Instance environment file, loaded into the process environment. | | `user-profile.yaml` | User profile. | | `config.yaml` | Instance configuration. | -| `snaptrade-accounts.yaml` | SnapTrade account-routing configuration. | +| `snaptrade-accounts.yaml` | [SnapTrade](https://snaptrade.com/) account-routing configuration. | | `system-context.md` | Generated system context. | -| `family_office.db` | Default family-office SQLite database. | +| `family_office.db` | Default family-office [SQLite](https://www.sqlite.org/) database. | | `dividend-schedules.yaml` | Dividend schedule file. | ## Directories under the root diff --git a/docs-site/src/content/docs/tutorials/your-first-instance.md b/docs-site/src/content/docs/tutorials/your-first-instance.md index dd0e2365..4b08162e 100644 --- a/docs-site/src/content/docs/tutorials/your-first-instance.md +++ b/docs-site/src/content/docs/tutorials/your-first-instance.md @@ -9,7 +9,7 @@ An instance is the private home for your financial data. It lives outside the en ## Before you start -You need a Finance Guru engine checkout with its development environment installed. The [setup path](../../how-to/troubleshoot/) requires Python 3.12 or later and [uv](https://docs.astral.sh/uv/). In the commands below, `` is the path to your engine checkout and `` is the directory where your instance will live. +You need a Finance Guru engine checkout with its development environment installed. The [setup path](../../how-to/troubleshoot/) requires [Python](https://www.python.org/) 3.12 or later and [uv](https://docs.astral.sh/uv/). In the commands below, `` is the path to your engine checkout and `` is the directory where your instance will live. ## Step 1. Create the instance diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index 42818172..268a7eb9 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -12,7 +12,7 @@ Thanks for looking. Read the _Project State_ section below before investing time ## Project State (Read This First) -Finance Guru ships as a Claude Code and Codex plugin over a typed Python engine. Everything ships open under AGPL-3.0. The skills, agents, and Python engine are all part of the product; there is no withheld feature tier. +Finance Guru ships as a [Claude Code](https://code.claude.com/) and [Codex](https://github.com/openai/codex) plugin over a typed Python engine. Everything ships open under [AGPL-3.0](https://www.gnu.org/licenses/agpl-3.0.html). The skills, agents, and Python engine are all part of the product; there is no withheld feature tier. What this means for contributors today: @@ -91,7 +91,7 @@ PRs that look AI-generated and carry no disclosure will be asked to either discl ### Changes to internal data or paths -Anything gitignored is internal. Do not reference gitignored paths, account identifiers, or dollar amounts in code you submit. If you need test data, use synthetic data (numpy / fixtures) under `tests/python/`. +Anything gitignored is internal. Do not reference gitignored paths, account identifiers, or dollar amounts in code you submit. If you need test data, use synthetic data ([numpy](https://numpy.org/) / fixtures) under `tests/python/`. --- @@ -135,13 +135,13 @@ Additionally: - **New code requires new tests.** Any new CLI tool, calculator, or utility must ship with tests under `tests/python/` using synthetic data. Target coverage is 80%. - **No real API calls in tests.** Tests must run offline. Mark network-dependent tests with `@pytest.mark.integration` (they are skipped by default in CI). -- **Three-layer pattern.** New financial tools follow the repo architecture: Pydantic input models in `src/models/`, calculator class in `src/analysis/` (or `strategies/` or `utils/`), CLI entry point as `{tool}_cli.py`. Reference implementation: `src/analysis/risk_metrics.py` + `src/models/risk_inputs.py` + `src/analysis/risk_metrics_cli.py`. +- **Three-layer pattern.** New financial tools follow the repo architecture: [Pydantic](https://docs.pydantic.dev/) input models in `src/models/`, calculator class in `src/analysis/` (or `strategies/` or `utils/`), CLI entry point as `{tool}_cli.py`. Reference implementation: `src/analysis/risk_metrics.py` + `src/models/risk_inputs.py` + `src/analysis/risk_metrics_cli.py`. -See `src/CLAUDE.md` for conventions (Pydantic + pandas gotchas, naming, typing, docstrings). +See `src/CLAUDE.md` for conventions (Pydantic + [pandas](https://pandas.pydata.org/) gotchas, naming, typing, docstrings). ### Push gates -Two gates run before anything reaches GitHub. The pre-commit hooks (`.pre-commit-config.yaml`) run hygiene checks, ruff, mypy, pytest with coverage, and gitleaks. The privacy scanner (`.claude/skills/compliance-scan/scripts/scan.py`) runs in its `push` scope from the pre-push hook and also supports a `history` scope that scans every line a branch adds. A HIGH or CRITICAL finding blocks the push. +Two gates run before anything reaches GitHub. The pre-commit hooks (`.pre-commit-config.yaml`) run hygiene checks, [ruff](https://docs.astral.sh/ruff/), [mypy](https://mypy-lang.org/), [pytest](https://docs.pytest.org/) with coverage, and [gitleaks](https://github.com/gitleaks/gitleaks). The privacy scanner (`.claude/skills/compliance-scan/scripts/scan.py`) runs in its `push` scope from the pre-push hook and also supports a `history` scope that scans every line a branch adds. A HIGH or CRITICAL finding blocks the push. --- @@ -149,7 +149,7 @@ Two gates run before anything reaches GitHub. The pre-commit hooks (`.pre-commit Every PR must pass: -1. **CodeRabbit review** — Automated; typically comments within minutes of opening. +1. **[CodeRabbit](https://www.coderabbit.ai/) review** — Automated; typically comments within minutes of opening. 2. **Maintainer review** — The maintainer reviews every PR personally before merge. Both must approve. Respond to CodeRabbit's comments in the same way you respond to a human reviewer — accept valid feedback, push back on incorrect feedback with reasoning. @@ -178,9 +178,9 @@ PRs that produce user-facing analysis output without the disclaimer will be aske ## Style Notes -- Markdown emphasis uses underscores (`_text_`), not asterisks (`*text`*). Enforced by markdownlint (MD049). +- Markdown emphasis uses underscores (`_text_`), not asterisks (`*text`*). Enforced by [markdownlint](https://github.com/DavidAnson/markdownlint) (MD049). - Docstrings use Google style. -- Commit messages use Conventional Commits (`feat:`, `fix:`, `docs:`, `refactor:`, `test:`). +- Commit messages use [Conventional Commits](https://www.conventionalcommits.org/) (`feat:`, `fix:`, `docs:`, `refactor:`, `test:`). --- diff --git a/docs/guides/just-commands.md b/docs/guides/just-commands.md index 2b8d22bf..8b12c4f9 100644 --- a/docs/guides/just-commands.md +++ b/docs/guides/just-commands.md @@ -10,7 +10,7 @@ Finance Guru uses [just](https://github.com/casey/just) as a command launchpad. ## Agent Personas -Launch Claude Code pre-loaded with a specialist persona: +Launch [Claude Code](https://code.claude.com/) pre-loaded with a specialist persona: | Recipe | Agent | Description | |--------|-------|-------------| @@ -44,7 +44,7 @@ Load mermaid architecture diagrams into Claude Code context: ## Prerequisites - [just](https://github.com/casey/just) — `brew install just` or `cargo install just` -- [Claude Code](https://claude.ai/claude-code) — must be installed and authenticated +- [Claude Code](https://code.claude.com/) — must be installed and authenticated - Agent persona files in `.claude/agents/fg-*.md` ## Version diff --git a/docs/index.md b/docs/index.md index a5950512..ff528db7 100644 --- a/docs/index.md +++ b/docs/index.md @@ -20,7 +20,7 @@ user-guide pages. | Install or develop the checked-in engine | [Setup](setup/SETUP.md) | | Find a verified command entry point | [CLI reference](reference/api.md) | | Configure optional provider credentials | [API keys](setup/api-keys.md) | -| Connect SnapTrade or SimpleFIN live sync | [Live sync credentials](setup/live-sync-credentials.md) | +| Connect [SnapTrade](https://snaptrade.com/) or [SimpleFIN](https://www.simplefin.org/) live sync | [Live sync credentials](setup/live-sync-credentials.md) | | Resolve an environment or provider failure | [Troubleshooting](setup/TROUBLESHOOTING.md) | | Understand contribution boundaries and checks | [Contributing](CONTRIBUTING.md) | | Review data-handling boundaries | [Privacy](../PRIVACY.md) | diff --git a/docs/reference/api.md b/docs/reference/api.md index 539020fd..5b89fb28 100644 --- a/docs/reference/api.md +++ b/docs/reference/api.md @@ -13,7 +13,7 @@ may require network access or private credentials. ## Command pattern -Run standalone scripts from the repository root with uv: +Run standalone scripts from the repository root with [uv](https://docs.astral.sh/uv/): ```bash uv run python path/to/script.py --help @@ -75,9 +75,9 @@ and is not part of the command contract. | Command | Purpose | | --- | --- | -| `uv run python -m src.integrations.refresh_all` | Refresh configured SnapTrade and SimpleFIN data into the local database. | +| `uv run python -m src.integrations.refresh_all` | Refresh configured [SnapTrade](https://snaptrade.com/) and [SimpleFIN](https://www.simplefin.org/) data into the local database. | | `uv run python -m src.integrations.snaptrade.cli` | Inspect linked SnapTrade accounts, positions, balances, or activities. | -| `uv run python -m src.integrations.snaptrade.sync_db` | Refresh SnapTrade positions and balances into local SQLite. | +| `uv run python -m src.integrations.snaptrade.sync_db` | Refresh SnapTrade positions and balances into local [SQLite](https://www.sqlite.org/). | | `uv run python -m src.integrations.snaptrade.sync_transactions_db` | Refresh SnapTrade activities into local SQLite. | | `uv run python -m src.integrations.simplefin.sync_expenses_db` | Refresh SimpleFIN transactions into local SQLite. | diff --git a/docs/reference/cross-harness-skills.md b/docs/reference/cross-harness-skills.md index b2bf94f4..b69aefa4 100644 --- a/docs/reference/cross-harness-skills.md +++ b/docs/reference/cross-harness-skills.md @@ -6,7 +6,7 @@ category: reference # Skills across harnesses -Skills live in one place, `.claude/skills//SKILL.md`, and ship in the plugin. Claude Code discovers them from the plugin or from the checkout's `.claude` tree. Codex reaches the same files through the `.agents` symlink that `src.cli.instance_init` creates in a checkout-mode instance, so a Codex session started from the instance discovers skills under `./.agents/skills`. A plugin-mode instance omits the symlink because the installed plugin is the source of skills. +Skills live in one place, `.claude/skills//SKILL.md`, and ship in the plugin. [Claude Code](https://code.claude.com/) discovers them from the plugin or from the checkout's `.claude` tree. [Codex](https://github.com/openai/codex) reaches the same files through the `.agents` symlink that `src.cli.instance_init` creates in a checkout-mode instance, so a Codex session started from the instance discovers skills under `./.agents/skills`. A plugin-mode instance omits the symlink because the installed plugin is the source of skills. This repository tracks no `.agents/` or `.pi/` tree. Do not create one. A harness that needs a different discovery path gets an issue, not a second copy of the skills. diff --git a/docs/reference/wiki-evidence-ledger.md b/docs/reference/wiki-evidence-ledger.md index 8b2394a6..aee29055 100644 --- a/docs/reference/wiki-evidence-ledger.md +++ b/docs/reference/wiki-evidence-ledger.md @@ -12,19 +12,19 @@ Code and live GitHub state take precedence over older repository prose. | Claim | Status | Evidence | | --- | --- | --- | -| Finance Guru stores supported positions, balances, transactions, and bank transactions in local SQLite. | Verified | `CLAUDE.md`, `src/integrations/refresh_all.py`, and the gitignored `family_office.db` paths. | +| Finance Guru stores supported positions, balances, transactions, and bank transactions in local [SQLite](https://www.sqlite.org/). | Verified | `CLAUDE.md`, `src/integrations/refresh_all.py`, and the gitignored `family_office.db` paths. | | The all-source refresh entry point is `uv run python -m src.integrations.refresh_all`. | Verified | `src/integrations/refresh_all.py`; `--help` rendered successfully on 2026-07-31. | -| SnapTrade and SimpleFIN are external read sources that write local snapshots. | Verified | `src/integrations/snaptrade/`, `src/integrations/simplefin/`, and `apps/simplefin-sync/`. | -| The analysis engine is CLI-first and uses Pydantic models, calculators or strategies, and CLI adapters. | Verified | `src/models/`, `src/analysis/`, `src/strategies/`, `src/utils/`, and the checked-in CLI inventory. | +| [SnapTrade](https://snaptrade.com/) and [SimpleFIN](https://www.simplefin.org/) are external read sources that write local snapshots. | Verified | `src/integrations/snaptrade/`, `src/integrations/simplefin/`, and `apps/simplefin-sync/`. | +| The analysis engine is CLI-first and uses [Pydantic](https://docs.pydantic.dev/) models, calculators or strategies, and CLI adapters. | Verified | `src/models/`, `src/analysis/`, `src/strategies/`, `src/utils/`, and the checked-in CLI inventory. | | The checked-in analysis commands are all functional. | Unknown | The CLI inventory is command-by-command; `momentum_cli` and `input_validation_cli` have open defects [#108](https://github.com/AojdevStudio/Finance-Guru/issues/108) and [#120](https://github.com/AojdevStudio/Finance-Guru/issues/120). | -| The Claude Code plugin is a supported installation path. | Verified | `README.md` Quick start, `.claude-plugin/plugin.json`, and `.claude-plugin/marketplace.json`. | +| The [Claude Code](https://code.claude.com/) plugin is a supported installation path. | Verified | `README.md` Quick start, `.claude-plugin/plugin.json`, and `.claude-plugin/marketplace.json`. | | The standalone macOS application is available. | Planned | `docs/VISION.md` describes direction only; no checked-in release or production proof exists. | | Financial analysis is educational material, not investment advice. | Verified | `README.md`, `docs/CONTRIBUTING.md`, and CLI-output requirements require that boundary. | | A local test or CI pass does not prove a release, production deployment, or provider success. | Verified | Repository tests and GitHub checks verify repository behavior only; provider and release state require separate live evidence. | | The engine resolves every private path from one instance root, taken from `FIN_GURU_DATA_ROOT` or the current working directory. | Verified | `src/config/instance_paths.py` (`InstancePaths.resolve`) and the `FIN_GURU_DATA_ROOT` entry in `.env.example`. | | The instance layout is flat. The root holds `.env`, `user-profile.yaml`, `config.yaml`, `system-context.md`, `family_office.db`, `snaptrade-accounts.yaml`, `dividend-schedules.yaml`, and the directories `imports/`, `analysis/`, `tickets/`, `strategies/`, `hedging/`, `reports/`, `auto-tickets/`, `notes/`. | Verified | `InstancePaths` properties in `src/config/instance_paths.py` and the scaffold plan in `src/cli/instance_init.py`. | | An instance is created with `uv run --project "" python -m src.cli.instance_init "" --repo ""` and the initializer is idempotent. | Verified | `src/cli/instance_init.py` and step 1 of `docs/runbooks/instance-migration.md`. | -| The instance is a uv project depending on the engine, so `uv run python -m src.` works from inside it. | Verified | `_instance_pyproject` and `_instance_instructions` in `src/cli/instance_init.py`; merged PR [#135](https://github.com/AojdevStudio/Finance-Guru/pull/135). | +| The instance is a [uv](https://docs.astral.sh/uv/) project depending on the engine, so `uv run python -m src.` works from inside it. | Verified | `_instance_pyproject` and `_instance_instructions` in `src/cli/instance_init.py`; merged PR [#135](https://github.com/AojdevStudio/Finance-Guru/pull/135). | | The SnapTrade account-routing file is `snaptrade-accounts.yaml` directly under the instance root. | Verified | `InstancePaths.snaptrade_accounts` in `src/config/instance_paths.py`. The older `config/snaptrade-accounts.yaml` path in `docs/setup/api-keys.md` is stale; doc contract bugs are tracked in [#137](https://github.com/AojdevStudio/Finance-Guru/issues/137). | | `notebooks/`, `fin-guru-private/`, and `fin-guru/data/user-profile.yaml` no longer exist as data locations; their contents move into the instance. | Verified | `docs/runbooks/instance-migration.md` and the P2 landing comment on [#131](https://github.com/AojdevStudio/Finance-Guru/issues/131). | | A runbook documents moving existing private data into an instance. | Verified | `docs/runbooks/instance-migration.md`. | @@ -32,7 +32,7 @@ Code and live GitHub state take precedence over older repository prose. | Skills and agents invoke engine tools in module form so they run with the instance as the working directory. | Verified | Merged PR [#136](https://github.com/AojdevStudio/Finance-Guru/pull/136) and the P2 landing comment on [#131](https://github.com/AojdevStudio/Finance-Guru/issues/131). | | Plugin installation arrives through the Claude Code marketplace. | Planned | Phase P3 of [#131](https://github.com/AojdevStudio/Finance-Guru/issues/131); no manifest or marketplace entry is on main. | | An onboarding skill scaffolds a user instance. | Planned | Phase P3 of [#131](https://github.com/AojdevStudio/Finance-Guru/issues/131); not on main. | -| A Diátaxis documentation site is built. | Planned | Maintainer direction only; no repository source or tracking issue found on 2026-09-02. | +| A [Diátaxis](https://diataxis.fr/) documentation site is built. | Planned | Maintainer direction only; no repository source or tracking issue found on 2026-09-02. | ## Refresh procedure diff --git a/docs/runbooks/instance-migration.md b/docs/runbooks/instance-migration.md index afa17b4b..5a8deced 100644 --- a/docs/runbooks/instance-migration.md +++ b/docs/runbooks/instance-migration.md @@ -28,7 +28,7 @@ command mv "/fin-guru/data/user-profile.yaml" "/user-profile.yaml" ``` -2. Move the database and each SQLite sidecar that exists. +2. Move the database and each [SQLite](https://www.sqlite.org/) sidecar that exists. ```bash command mv "/family_office.db" "/family_office.db" @@ -37,7 +37,7 @@ command mv "/family_office.db-journal" "/family_office.db-journal" ``` -3. Move the environment and SnapTrade routing files. +3. Move the environment and [SnapTrade](https://snaptrade.com/) routing files. ```bash command mv "/.env" "/.env" diff --git a/docs/setup/SETUP.md b/docs/setup/SETUP.md index 893a442a..8f7ab474 100644 --- a/docs/setup/SETUP.md +++ b/docs/setup/SETUP.md @@ -6,19 +6,19 @@ category: setup # Finance Guru setup -This guide installs the checked-in Python and Bun workspaces. The supported +This guide installs the checked-in Python and [Bun](https://bun.sh/) workspaces. The supported data store is the local `family_office.db`, committed only to the instance's local-only repository; Google Sheets is not a data source and no Google Drive integration is required. ## Prerequisites -- Python 3.12 or later +- [Python](https://www.python.org/) 3.12 or later - [uv](https://docs.astral.sh/uv/) -- Git +- [Git](https://git-scm.com/) - Bun only when working on `apps/simplefin-sync/` -The Claude Code plugin is not required to run the Python analysis engine from +The [Claude Code](https://code.claude.com/) plugin is not required to run the Python analysis engine from a shell. ## Install @@ -49,7 +49,7 @@ The instance is a small uv project that depends on the engine, so `uv run python -m src.` works from inside it with no extra flags. Run Finance Guru sessions and sync commands from the instance directory. -For the SimpleFIN workspace, install its Bun dependencies from that workspace: +For the [SimpleFIN](https://www.simplefin.org/) workspace, install its Bun dependencies from that workspace: ```bash ( @@ -77,7 +77,7 @@ any `.env` file. See [API keys](api-keys.md) for the variables consumed by the supported integrations and the -[live sync credentials guide](live-sync-credentials.md) for SnapTrade and +[live sync credentials guide](live-sync-credentials.md) for [SnapTrade](https://snaptrade.com/) and SimpleFIN. Keep account exports, API keys, and database files out of the public engine checkout. The database is committed only to the instance's local-only repository, which has no remote. diff --git a/docs/setup/TROUBLESHOOTING.md b/docs/setup/TROUBLESHOOTING.md index ac23cae7..eb2f67e0 100644 --- a/docs/setup/TROUBLESHOOTING.md +++ b/docs/setup/TROUBLESHOOTING.md @@ -18,7 +18,7 @@ python3 --version uv --version ``` -Finance Guru requires Python 3.12 or later. Install uv using the command from +Finance Guru requires Python 3.12 or later. Install [uv](https://docs.astral.sh/uv/) using the command from [its official installation guide](https://docs.astral.sh/uv/getting-started/installation/), then recreate the development environment: @@ -77,9 +77,9 @@ uv run python -m src.integrations.refresh_all --show command returns a non-zero status when one source fails, even if another source succeeds. Check the individual source status before retrying. -For SnapTrade, verify the owner has configured the required private environment -variables and account routing file. For SimpleFIN, verify the private access -URL has been claimed and is available to the Bun workspace. Do not copy either +For [SnapTrade](https://snaptrade.com/), verify the owner has configured the required private environment +variables and account routing file. For [SimpleFIN](https://www.simplefin.org/), verify the private access +URL has been claimed and is available to the [Bun](https://bun.sh/) workspace. Do not copy either credential into terminal history, issue text, or screenshots. ## Bun workspace errors diff --git a/docs/setup/api-keys.md b/docs/setup/api-keys.md index 6fd90d31..4b0704c3 100644 --- a/docs/setup/api-keys.md +++ b/docs/setup/api-keys.md @@ -11,7 +11,7 @@ reads `.env` from the instance root, the directory named by `FIN_GURU_DATA_ROOT` or the current working directory. `instance_init` scaffolds that file from the checked-in `.env.example`, preserving real defaults and commenting out empty values and credential placeholders for you to fill in. -The `.env` file is gitignored. The local SQLite database is committed to the +The `.env` file is gitignored. The local [SQLite](https://www.sqlite.org/) database is committed to the instance's local-only repository, which has no remote, and never to the public engine checkout. @@ -26,7 +26,7 @@ public example or CI job at a real database. ## SnapTrade -The read-only SnapTrade integration requires these private values before it can +The read-only [SnapTrade](https://snaptrade.com/) integration requires these private values before it can contact the provider: | Variable | Purpose | @@ -43,7 +43,7 @@ command and the routing rules. ## SimpleFIN -SimpleFIN credentials are consumed by the Bun workspace under +[SimpleFIN](https://www.simplefin.org/) credentials are consumed by the [Bun](https://bun.sh/) workspace under `apps/simplefin-sync/`: | Variable | Purpose | @@ -67,12 +67,13 @@ bun run claim ## Required research MCP integrations -Finance Guru agent workflows require configured access to these MCP servers: +Finance Guru agent workflows require configured access to these +[Model Context Protocol](https://modelcontextprotocol.io/) (MCP) servers: -- `exa` for research and market intelligence -- `bright-data` for web scraping and extraction -- `sequential-thinking` for multi-step reasoning -- `financial-datasets` for SEC filings and financial statements +- `exa` for research and market intelligence ([Exa](https://exa.ai/)) +- `bright-data` for web scraping and extraction ([Bright Data](https://brightdata.com/)) +- `sequential-thinking` for multi-step reasoning ([reference server](https://github.com/modelcontextprotocol/servers/tree/main/src/sequentialthinking)) +- `financial-datasets` for SEC filings and financial statements ([Financial Datasets](https://www.financialdatasets.ai/)) - `web-search` for current market information These are workflow integrations, not `.env` variables. Finance Guru @@ -83,9 +84,9 @@ Google Sheets dependency to replace the local data boundary. | Variable | Used by | | --- | --- | -| `FINNHUB_API_KEY` | Optional real-time price requests. | +| `FINNHUB_API_KEY` | Optional real-time price requests from [Finnhub](https://finnhub.io/). | | `ITC_API_KEY` | ITC risk-model requests. | -| `OPENAI_API_KEY` | Legacy or optional local agent workflows when explicitly configured. | +| `OPENAI_API_KEY` | Legacy or optional local agent workflows when explicitly configured. Issued by [OpenAI](https://platform.openai.com/api-keys). | Market analysis can use public market-data sources without one of these keys, but a provider may still receive the tickers you query. See [Privacy](../../PRIVACY.md). diff --git a/docs/setup/live-sync-credentials.md b/docs/setup/live-sync-credentials.md index fc2f590a..77e25fc2 100644 --- a/docs/setup/live-sync-credentials.md +++ b/docs/setup/live-sync-credentials.md @@ -7,7 +7,7 @@ category: setup # Live sync credentials Finance Guru works CSV-first. Live sync is optional. It connects two read-only -providers, SnapTrade for brokerage data and SimpleFIN for bank and card +providers, [SnapTrade](https://snaptrade.com/) for brokerage data and [SimpleFIN](https://www.simplefin.org/) for bank and card activity. You obtain the credentials yourself and keep them in local, gitignored files. This guide names the variables each provider needs. Never write a credential value into a tracked file, an issue, or a pull request. @@ -24,6 +24,11 @@ inside the repository checkout. SnapTrade supplies brokerage positions, balances, and transactions. The integration is read-only and requires four variables in the instance `.env`. +Sign up for API access at [SnapTrade](https://snaptrade.com/) and take the +client ID and consumer key from the +[SnapTrade dashboard](https://dashboard.snaptrade.com/). The +[SnapTrade API docs](https://docs.snaptrade.com/) cover registering a user, +which returns the user ID and user secret. | Variable | Purpose | | --- | --- | @@ -52,9 +57,11 @@ after you have verified the account against a broker export. Accounts left ## SimpleFIN -SimpleFIN supplies bank and card transactions. The recommended single location +SimpleFIN supplies bank and card transactions. Get a setup token from +[SimpleFIN Bridge](https://bridge.simplefin.org/), the paid SimpleFIN service +that connects to your bank and issues tokens. The recommended single location for its long-lived credential is the instance `.env`. `refresh_all` loads that -file with override enabled before it starts Bun in `apps/simplefin-sync/`, so an +file with override enabled before it starts [Bun](https://bun.sh/) in `apps/simplefin-sync/`, so an uncommented `SIMPLEFIN_ACCESS_URL` in the instance `.env` wins. Bun reads the workspace's `apps/simplefin-sync/.env` value only when the instance leaves `SIMPLEFIN_ACCESS_URL` commented out. diff --git a/scripts/backlinks.py b/scripts/backlinks.py new file mode 100644 index 00000000..b4f84f0f --- /dev/null +++ b/scripts/backlinks.py @@ -0,0 +1,226 @@ +"""Link the first prose mention of every third-party vendor in the reader-facing docs. + +Usage: + uv run python scripts/backlinks.py # rewrite the default targets + uv run python scripts/backlinks.py --check # exit 1 when a target would change + uv run python scripts/backlinks.py docs/x.md # limit to the given files + +A vendor is linked at its first mention per file, unless an earlier link in that +file already carries the vendor name as its text. Fenced code, inline code, +front matter, headings, HTML blocks, existing links, image alt text, and bare +URLs are left alone. + +The default targets are the reader-facing guides. Dated records (docs/plans, +docs/reports, docs/solutions, docs/adr, docs/brainstorms, docs/VISION.md) are +excluded on purpose: they are historical evidence, not setup paths, and linking +them would churn files nobody maintains. +""" + +from __future__ import annotations + +import argparse +import re +import sys +from dataclasses import dataclass +from pathlib import Path + +REPO_ROOT = Path(__file__).resolve().parent.parent + + +@dataclass(frozen=True) +class Vendor: + name: str + url: str + pattern: str + + +def _vendor(name: str, url: str, pattern: str | None = None) -> Vendor: + return Vendor(name, url, pattern or rf"\b{re.escape(name)}\b") + + +VENDORS: tuple[Vendor, ...] = ( + _vendor("SnapTrade", "https://snaptrade.com/"), + _vendor("SimpleFIN", "https://www.simplefin.org/"), + _vendor("yfinance", "https://github.com/ranaroussi/yfinance"), + _vendor("Finnhub", "https://finnhub.io/"), + _vendor("Fidelity", "https://www.fidelity.com/"), + _vendor("Plaid", "https://plaid.com/"), + _vendor("Keepfolio", "https://keepfolio.app/"), + _vendor("Claude Code", "https://code.claude.com/"), + _vendor("Codex", "https://github.com/openai/codex"), + _vendor("Anthropic", "https://www.anthropic.com/"), + _vendor("OpenAI", "https://openai.com/"), + _vendor("Pydantic", "https://docs.pydantic.dev/"), + _vendor("SQLite", "https://www.sqlite.org/"), + _vendor("pandas", "https://pandas.pydata.org/"), + _vendor("NumPy", "https://numpy.org/", r"\b[Nn]um[Pp]y\b"), + _vendor("uv", "https://docs.astral.sh/uv/"), + _vendor("Bun", "https://bun.sh/"), + _vendor("ruff", "https://docs.astral.sh/ruff/"), + _vendor("mypy", "https://mypy-lang.org/"), + _vendor("pytest", "https://docs.pytest.org/"), + _vendor("gitleaks", "https://github.com/gitleaks/gitleaks"), + _vendor("markdownlint", "https://github.com/DavidAnson/markdownlint"), + _vendor("CodeRabbit", "https://www.coderabbit.ai/"), + _vendor("release-please", "https://github.com/googleapis/release-please"), + _vendor( + "conventional commits", + "https://www.conventionalcommits.org/", + r"\b[Cc]onventional [Cc]ommits\b", + ), + _vendor("Diátaxis", "https://diataxis.fr/", r"\bDi[aá]taxis\b"), + _vendor("GitHub Pages", "https://pages.github.com/"), + _vendor("Slack", "https://slack.com/"), + _vendor( + "AGPL-3.0", "https://www.gnu.org/licenses/agpl-3.0.html", r"\bAGPL(?:-3\.0)?\b" + ), +) + +DEFAULT_TARGETS: tuple[str, ...] = ( + "README.md", + "AGENTS.md", + "CONTRIBUTING.md", + "PRIVACY.md", + "docs/index.md", + "docs/CONTRIBUTING.md", + "docs/setup/*.md", + "docs/reference/*.md", + "docs/runbooks/*.md", + "docs/guides/*.md", + "docs-site/src/content/docs/**/*.md", +) + +INLINE_CODE = re.compile(r"`[^`\n]*`") +LINK = re.compile(r"!?\[(?P[^\]\n]*)\]\([^)\n]*\)") +BARE_URL = re.compile(r")]+>?") +FENCE_OPEN = re.compile(r"^\s*(`{3,}|~{3,})") +FENCE_CLOSE = re.compile(r"^\s*(`{3,}|~{3,})\s*$") +VOID_TAGS = "area|base|br|col|embed|hr|img|input|link|meta|source|track|wbr" +HTML_OPEN = re.compile(rf"<(?!(?:{VOID_TAGS})\b)[a-z][\w-]*\b[^>]*(?", re.I) +HTML_CLOSE = re.compile(r"", re.I) + + +@dataclass(frozen=True) +class Span: + start: int + end: int + link_text: bool + + +def protected_spans(line: str) -> list[Span]: + spans = [Span(m.start(), m.end(), False) for m in INLINE_CODE.finditer(line)] + spans += [Span(m.start(), m.end(), False) for m in BARE_URL.finditer(line)] + for m in LINK.finditer(line): + is_image = line[m.start()] == "!" + spans.append(Span(m.start(), m.start("text"), False)) + spans.append(Span(m.start("text"), m.end("text"), not is_image)) + spans.append(Span(m.end("text"), m.end(), False)) + return spans + + +def _covering(spans: list[Span], start: int, end: int) -> Span | None: + return next((s for s in spans if s.start <= start and end <= s.end), None) + + +def _prose_lines(lines: list[str]) -> list[int]: + """Indexes of lines eligible for linking.""" + eligible: list[int] = [] + fence: str | None = None + html_depth = 0 + in_front_matter = lines[:1] == ["---"] + for i, line in enumerate(lines): + if in_front_matter: + if i > 0 and line.strip() == "---": + in_front_matter = False + continue + if fence is not None: + marker = FENCE_CLOSE.match(line) + if ( + marker + and marker.group(1)[0] == fence[0] + and len(marker.group(1)) >= len(fence) + ): + fence = None + continue + marker = FENCE_OPEN.match(line) + if marker: + fence = marker.group(1) + continue + stripped = line.lstrip() + html_depth += len(HTML_OPEN.findall(stripped)) - len( + HTML_CLOSE.findall(stripped) + ) + if html_depth > 0 or not stripped or stripped[0] in "#<": + continue + if re.match(r"^\[[^\]]+\]:\s", stripped): + continue + eligible.append(i) + return eligible + + +def link_vendors(text: str, vendors: tuple[Vendor, ...] = VENDORS) -> str: + lines = text.split("\n") + eligible = _prose_lines(lines) + for vendor in vendors: + pattern = re.compile(vendor.pattern) + done = False + for i in eligible: + spans = protected_spans(lines[i]) + for m in pattern.finditer(lines[i]): + cover = _covering(spans, m.start(), m.end()) + if cover is None: + lines[i] = ( + f"{lines[i][: m.start()]}[{m.group(0)}]({vendor.url}){lines[i][m.end() :]}" + ) + done = True + elif cover.link_text: + done = True + if done: + break + if done: + break + return "\n".join(lines) + + +def resolve_targets(patterns: list[str]) -> list[Path]: + paths: set[Path] = set() + for pattern in patterns: + path = Path(pattern) + if path.is_file(): + paths.add(path) + else: + paths.update(p for p in REPO_ROOT.glob(pattern) if p.is_file()) + return sorted(paths) + + +def main(argv: list[str] | None = None) -> int: + parser = argparse.ArgumentParser(description=__doc__.split("\n", 1)[0]) + parser.add_argument( + "paths", nargs="*", help="files or globs; defaults to the doc surfaces" + ) + parser.add_argument( + "--check", action="store_true", help="report drift without writing" + ) + args = parser.parse_args(argv) + + changed: list[Path] = [] + for path in resolve_targets(args.paths or list(DEFAULT_TARGETS)): + original = path.read_text(encoding="utf-8") + updated = link_vendors(original) + if updated == original: + continue + changed.append(path) + if not args.check: + path.write_text(updated, encoding="utf-8") + + verb = "would change" if args.check else "updated" + for path in changed: + print(f"{verb}: {path.relative_to(REPO_ROOT) if path.is_absolute() else path}") + if args.check and changed: + print("run `uv run python scripts/backlinks.py` to link the vendor mentions") + return 1 + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tests/python/test_backlinks.py b/tests/python/test_backlinks.py new file mode 100644 index 00000000..b81be10b --- /dev/null +++ b/tests/python/test_backlinks.py @@ -0,0 +1,83 @@ +from scripts.backlinks import Vendor, link_vendors + +SNAP = (Vendor("SnapTrade", "https://snaptrade.com/", r"\bSnapTrade\b"),) + + +def test_links_first_prose_mention_only(): + text = "Sync pulls SnapTrade data.\nSnapTrade again." + assert link_vendors(text, SNAP) == ( + "Sync pulls [SnapTrade](https://snaptrade.com/) data.\nSnapTrade again." + ) + + +def test_skips_front_matter_headings_code_and_html(): + text = "\n".join( + [ + "---", + "description: SnapTrade sync", + "---", + "## SnapTrade", + "SnapTrade", + '
', + "", + "_Hero tagline naming SnapTrade._", + "", + "
", + "```bash", + "echo SnapTrade", + "```", + "Use `SnapTrade` via SnapTrade.", + ] + ) + assert link_vendors(text, SNAP).splitlines()[-1] == ( + "Use `SnapTrade` via [SnapTrade](https://snaptrade.com/)." + ) + assert link_vendors(text, SNAP).splitlines()[:-1] == text.splitlines()[:-1] + + +def test_existing_link_earlier_in_file_wins_and_result_is_idempotent(): + text = "Brokerage through [SnapTrade](https://snaptrade.com/).\nSnapTrade later." + assert link_vendors(text, SNAP) == text + linked = link_vendors("SnapTrade first.\nSnapTrade later.", SNAP) + assert link_vendors(linked, SNAP) == linked + + +def test_nested_fence_and_any_html_block_stay_protected(): + text = "\n".join( + [ + "````md", + "```bash", + "echo SnapTrade", + "```", + "SnapTrade inside the outer fence.", + "````", + "```", + "```python inside a fence is content, not a closer", + "SnapTrade still fenced.", + "```", + "
", + "SnapTrade inside a section.", + "
", + "
  • SnapTrade inside a list
", + "void tags do not open a block", + "SnapTrade in prose.", + ] + ) + assert link_vendors(text, SNAP).splitlines()[-1] == ( + "[SnapTrade](https://snaptrade.com/) in prose." + ) + assert link_vendors(text, SNAP).splitlines()[:-1] == text.splitlines()[:-1] + + +def test_image_alt_text_is_protected_but_is_not_a_vendor_link(): + text = "![SnapTrade logo](logo.png)\nSync pulls SnapTrade data." + assert link_vendors(text, SNAP) == ( + "![SnapTrade logo](logo.png)\nSync pulls [SnapTrade](https://snaptrade.com/) data." + ) + + +def test_doc_link_with_other_text_does_not_count_as_vendor_link(): + text = "See [live sync](docs/live-sync.md) for SnapTrade." + assert link_vendors(text, SNAP) == ( + "See [live sync](docs/live-sync.md) for [SnapTrade](https://snaptrade.com/)." + ) diff --git a/uv.lock b/uv.lock index 53ce82c2..14f5d113 100644 --- a/uv.lock +++ b/uv.lock @@ -739,7 +739,7 @@ wheels = [ [[package]] name = "family-office" -version = "2.3.0" +version = "2.3.1" source = { editable = "." } dependencies = [ { name = "anthropic" },