diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..c166278 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,47 @@ +# CLAUDE.md + +Working notes for AI agents in this repo (the Python Scout engine/plugin). + +## Fixtures must be anonymized — this repo and its two siblings are public + +Scout runs against a real person's vault, so anything lifted from it into a +fixture or an inline test string **must be scrubbed before it lands**. All three +Scout repos — this one, `Scout` (desktop), and `scout-iOS-app` — are public. + +- **No real identifiers.** Strip company/product names, real coworker names, real + Linear IDs, GitHub repos, and Slack workspaces/channels. Use the shared + stand-ins so fixtures stay internally consistent: + - People: `Alex` / `Priya` / `Sam`; comment author `alex`. + - Linear: `PROJ-1234` (neutral prefixes like `OPS-`, `DESK-` for variety) — + never the real team prefixes (`AI-`, `KAI-`, `ST-`, …). + - GitHub: `example-org/`. + - Slack: `acme-co.slack.com/archives/C0123456789/p1700000000000000`. + - Vendors/products: a generic noun, not the brand. +- **Anonymize content, not structure.** Keep the tokens the parser is tested on + (synthetic `[#TAG]` prefixes, `**bold**`, `_(italic)_`, `[[wikilinks]]`, + ` — ` separators, `` `code` ``). Only swap the words around them. +- **Don't hardcode personal config in source.** The Linear deep-link workspace is + read from `SCOUT_LINEAR_WORKSPACE` (default `your-workspace`), not baked in; + keep new connector/workspace specifics config- or env-driven the same way. +- **Preserve legitimate attribution** — NOT leaks, leave them: `pyproject` authors, + `.claude-plugin/marketplace.json` / `plugin.json` owner, `LICENSE`, and the + project's own `github.com//…` URLs (including the self-update URL). + +### `parser-corpus.json` is ONE byte-identical file living in three repos + +`engine/tests/fixtures/contract/parser-corpus.json` is the **canonical** copy; +`Scout` (desktop) and `scout-iOS-app` vendor byte-identical copies, checksum-guarded +on both the Python and Swift sides — so you cannot edit just one copy. On any change +(anonymizing counts): + +1. Edit the corpus here; keep every `expected` field consistent with the parser + (`pytest tests/unit/test_parser_contract.py` is the judge). +2. Copy it byte-for-byte into the sibling checkouts (cloned alongside this repo): + - `../Scout/ScoutTests/Fixtures/parser-corpus.json` + - `../scout-ios/ScoutMobileTests/Fixtures/parser-corpus.json` +3. Update BOTH checksum guards to the new `shasum -a 256` of the file: + - `EXPECTED_SHA256` in `engine/tests/unit/test_parser_corpus_checksum.py` + - `canonicalSHA256` in `../Scout/ScoutTests/ActionItems/ParserContractTests.swift` +4. Verify all three: `pytest tests/unit/test_parser_contract.py + tests/unit/test_parser_corpus_checksum.py`, plus each Swift repo's + `ParserContractTests`. diff --git a/README.md b/README.md index 75246dc..df47b6f 100644 --- a/README.md +++ b/README.md @@ -473,4 +473,4 @@ Scout is open-source under the [MIT License](LICENSE). - **[Terms of Use](TERMS.md)** — free, open-source, provided as-is. ([web version](https://raven-scout.github.io/scout-plugin/terms.html)) - **[Security Policy](https://github.com/Raven-Scout/.github/blob/main/SECURITY.md)** · **[Code of Conduct](https://github.com/Raven-Scout/.github/blob/main/CODE_OF_CONDUCT.md)** -Scout is an independent project and is not affiliated with, endorsed by, or sponsored by Anthropic, Microsoft, Keboola, or any other company. +Scout is an independent project and is not affiliated with, endorsed by, or sponsored by Anthropic, Microsoft, or any other company. diff --git a/TERMS.md b/TERMS.md index 6533162..6c4314f 100644 --- a/TERMS.md +++ b/TERMS.md @@ -43,7 +43,7 @@ By using Scout, you agree that you are responsible for: Scout integrates with third-party services and runs on Anthropic's Claude. Those services are controlled by their respective providers and governed by their own terms and privacy policies; we are not responsible for them. Scout is an **independent project** and is **not affiliated with, -endorsed by, or sponsored by** Anthropic, Microsoft, Keboola, Slack, Google, Linear, GitHub, or +endorsed by, or sponsored by** Anthropic, Microsoft, Slack, Google, Linear, GitHub, or any other company. All product names, logos, and trademarks are the property of their respective owners and are used only to describe interoperability. diff --git a/commands/scout-work.md b/commands/scout-work.md index d62a3cb..152af65 100644 --- a/commands/scout-work.md +++ b/commands/scout-work.md @@ -31,7 +31,7 @@ The goal: the user decides, Scout drafts and executes. Never send a message, mer 4. **Skip** items from the **🟢 Watching** section — those are monitoring, not actionable. -5. **Skip** items explicitly described as blocked by another item (e.g., "Blocked by AI-2630", "Waiting on Legal review"). +5. **Skip** items explicitly described as blocked by another item (e.g., "Blocked by PROJ-2630", "Waiting on Legal review"). 6. Present a numbered summary: ``` diff --git a/docs/assets/scout-demo.svg b/docs/assets/scout-demo.svg index 3e47e9d..fb79103 100644 --- a/docs/assets/scout-demo.svg +++ b/docs/assets/scout-demo.svg @@ -65,7 +65,7 @@ LINEAR - Ticket moved to Done by Marcus, + Ticket moved to Done by Sam, yesterday 6:12 pm. GITHUB diff --git a/docs/hybrid.css b/docs/hybrid.css index 13b3eb0..283d1ff 100644 --- a/docs/hybrid.css +++ b/docs/hybrid.css @@ -44,7 +44,7 @@ h1 em{font-style:italic;color:var(--acc);} .ctas{margin-top:32px;display:flex;gap:12px;flex-wrap:wrap;} .note{margin-top:16px;font-family:var(--mono);font-size:11.5px;color:var(--ink-3);} -/* pull quote — Jordan's verbatim words */ +/* pull quote — verbatim words */ .pull{margin:46px 0;padding:6px 0 6px 26px;border-left:3px solid var(--acc);} .pull q,.pull .q{display:block;font-family:var(--serif);font-style:italic;font-size:26px;line-height:1.36;color:var(--ink);quotes:none;} .pull .by{margin-top:12px;font-family:var(--mono);font-size:11.5px;letter-spacing:.04em;color:var(--ink-3);text-transform:uppercase;} diff --git a/docs/index.html b/docs/index.html index af1fee0..4a6f7a8 100644 --- a/docs/index.html +++ b/docs/index.html @@ -417,7 +417,7 @@

There's always mo

Disagreement is the signal

When tools conflict, it shows both sides.

It won't pick a winner. That contradiction is usually the most important thing in your day — and what every other tool hides.

-
Linearmoved to Done by Marcus, 6:12pm
GitHubPR #482 still open, 0 approvals
contradicted → held for you, never guessed
+
Linearmoved to Done by Sam, 6:12pm
GitHubPR #482 still open, 0 approvals
contradicted → held for you, never guessed
diff --git a/docs/plans/2026-06-02-release-and-distribution-system.md b/docs/plans/2026-06-02-release-and-distribution-system.md index 5696e7f..c754d73 100644 --- a/docs/plans/2026-06-02-release-and-distribution-system.md +++ b/docs/plans/2026-06-02-release-and-distribution-system.md @@ -18,7 +18,7 @@ | `engine/pyproject.toml` | `[project]` → `version = "X"` | | `engine/scout/__init__.py` | `__version__ = "X"` | -**Working directory for all commands:** `/Users/jordanburger/scout-plugin` (the `engine/.venv` is the test venv; run pytest/ruff/mypy from `engine/`). +**Working directory for all commands:** `~/scout-plugin` (the `engine/.venv` is the test venv; run pytest/ruff/mypy from `engine/`). --- diff --git a/docs/plans/2026-06-10-batch-1-quick-wins.md b/docs/plans/2026-06-10-batch-1-quick-wins.md index d24fd11..4a4a282 100644 --- a/docs/plans/2026-06-10-batch-1-quick-wins.md +++ b/docs/plans/2026-06-10-batch-1-quick-wins.md @@ -18,7 +18,7 @@ - [ ] **Step 1: Create the working branch** ```bash -cd /Users/jordanburger/scout-plugin +cd ~/scout-plugin git checkout -b fix/batch-1-quick-wins ``` @@ -789,7 +789,7 @@ Fixes #47" Expected: pytest 0 failed / 0 xfailed; ruff and mypy clean. -- [ ] **Step 2: Push and open the PR (outward-facing — confirm with Jordan if not pre-authorized)** +- [ ] **Step 2: Push and open the PR (outward-facing — confirm with the maintainer if not pre-authorized)** ```bash git push -u origin fix/batch-1-quick-wins diff --git a/docs/specs/2026-06-02-release-and-distribution-system.md b/docs/specs/2026-06-02-release-and-distribution-system.md index 08a2d44..cd71cce 100644 --- a/docs/specs/2026-06-02-release-and-distribution-system.md +++ b/docs/specs/2026-06-02-release-and-distribution-system.md @@ -75,7 +75,7 @@ Today `/scout-update` upgrades the vault only. Extend it to the single "update S 1. **Bring the plugin to latest.** Adapts to marketplace type: - GitHub marketplace (public users): `claude plugin marketplace update scout-plugin` (git pull) + reinstall. - - Directory marketplace (maintainer/Jordan, `~/scout-plugin`): `git -C ~/scout-plugin pull`; the editable venv auto-reflects it. + - Directory marketplace (maintainer, `~/scout-plugin`): `git -C ~/scout-plugin pull`; the editable venv auto-reflects it. 2. **Ensure the engine venv** via the plugin's existing `scripts/install-venv.sh`. 3. **Upgrade the vault against the _new_ plugin.** Resolve the freshly-installed plugin root (from `claude plugin list` / `installed_plugins.json`) and invoke `/.venv/bin/scoutctl bootstrap upgrade` **by absolute path** — avoiding the stale `$CLAUDE_PLUGIN_ROOT`-in-session problem, so no restart is needed. This is the existing sidecar-safe 8-stage pipeline (including the `parser.py` 3-way merge). 4. **Doctor + report** old→new version and any `.proposed-merge` sidecars. diff --git a/docs/superpowers/plans/2026-06-14-connector-probe-overlay.md b/docs/superpowers/plans/2026-06-14-connector-probe-overlay.md index 456ef4d..dcfe9f8 100644 --- a/docs/superpowers/plans/2026-06-14-connector-probe-overlay.md +++ b/docs/superpowers/plans/2026-06-14-connector-probe-overlay.md @@ -13,7 +13,7 @@ - `engine/scout/scripts/connector_probes.py` already has `load_registry(path) -> dict[str, Probe]` (single-file parser, fully tested in `engine/tests/unit/test_connector_probe_registry.py`). It raises **`ValueError`** on malformed input — existing tests assert `ValueError`, so leave that behavior intact. - `Probe` is a frozen dataclass with `.name`, `.kind` (`ProbeKind.MCP_TOOL` / `ProbeKind.BASH`), `.tool_chain: list[str]`, `.bash_command: str`, `.needs_user_input: list[str]`. `ProbeKind.MCP_TOOL.value == "mcp_tool"`, `ProbeKind.BASH.value == "bash"`. - `ConfigError` is in `scout.errors` (subclass of `ScoutError`). -- Plugin root is `Path(scout.__file__).parent.parent.parent` (verified == `/Users/jordanburger/scout-plugin`); shipped registry is at `/templates/connector-probes.yaml` (verified exists). Data dir comes from `scout.paths.data_dir()`. +- Plugin root is `Path(scout.__file__).parent.parent.parent` (verified == `~/scout-plugin`); shipped registry is at `/templates/connector-probes.yaml` (verified exists). Data dir comes from `scout.paths.data_dir()`. - The `connectors` Typer subapp is built in `engine/scout/cli.py` inside `_register_connectors()` (has `list`, `show`, `reload`, `snapshot`). **Subcommands import their heavy deps inside the function body** — a perf rule enforced by `engine/tests/perf/test_no_heavy_imports.py`. Follow that: import `connector_probes` inside the new command, not at module top. - **This branch does NOT have the hermetic-test autouse fixture** (that's in the still-open Batch 1 PR #124). Consequence #1: this plan's tests must be self-contained — pass `plugin_root=`/`data_dir=` explicitly, or set `SCOUT_DATA_DIR` — never rely on `HOME` isolation. Consequence #2: a full `pytest tests/` run on this branch shows **2 pre-existing failures** in `test_cli_schedule_subapp.py` (the live-vault slot-count tests Batch 1 fixes). Those are not introduced here; the final-verification task accounts for them. @@ -23,7 +23,7 @@ - [ ] **Step 1: Verify you're on the feature branch** -Run: `git -C /Users/jordanburger/scout-plugin branch --show-current` +Run: `git -C ~/scout-plugin branch --show-current` Expected: `feat/connector-probe-overlay`. (If not, the spec commit `e30ee4d` is on it; check it out.) --- @@ -461,7 +461,7 @@ In `commands/scout-update.md`, add a line to the upgrade report/notes section (w - [ ] **Step 4: Sanity-check the wizard command end-to-end** ```bash -cd /Users/jordanburger/scout-plugin/engine && ../.venv/bin/python -m scout connectors probe-registry --json | head -20 +cd ~/scout-plugin/engine && ../.venv/bin/python -m scout connectors probe-registry --json | head -20 ``` Expected: a JSON object including `slack`, `github`, etc. (the shipped set; no overlay on this machine unless you created one). diff --git a/docs/superpowers/plans/2026-06-15-batch-3-high-severity.md b/docs/superpowers/plans/2026-06-15-batch-3-high-severity.md index d5ed795..86d1377 100644 --- a/docs/superpowers/plans/2026-06-15-batch-3-high-severity.md +++ b/docs/superpowers/plans/2026-06-15-batch-3-high-severity.md @@ -20,8 +20,8 @@ - [ ] **Step 1: Verify branch and a green baseline** -Run: `git -C /Users/jordanburger/scout-plugin branch --show-current` → expect `fix/batch-3-high-severity`. -Run: `cd /Users/jordanburger/scout-plugin/engine && ../.venv/bin/python -m pytest tests/ -q 2>&1 | tail -2` +Run: `git -C ~/scout-plugin branch --show-current` → expect `fix/batch-3-high-severity`. +Run: `cd ~/scout-plugin/engine && ../.venv/bin/python -m pytest tests/ -q 2>&1 | tail -2` Expected: all pass, 0 failed (Batch 1's hermetic fixture means no live-vault flakes). If anything fails, STOP and report before making changes. --- @@ -128,7 +128,7 @@ def test_parse_lines_matches_parse_file(tmp_path): - [ ] **Step 2: Run them to verify they fail** -Run: `cd /Users/jordanburger/scout-plugin/engine && ../.venv/bin/python -m pytest tests/unit/test_action_items_parser.py -q -k parse_lines tests/unit/test_action_items_backfill.py -q -k "single_file_read or mid_loop"` +Run: `cd ~/scout-plugin/engine && ../.venv/bin/python -m pytest tests/unit/test_action_items_parser.py -q -k parse_lines tests/unit/test_action_items_backfill.py -q -k "single_file_read or mid_loop"` Expected: `parse_lines` test errors (`cannot import name 'parse_lines'`); `single_file_read` fails (2 reads); `mid_loop` fails (registered set missing the first prefix). - [ ] **Step 3: Add `parse_lines` to the parser (single-read seam for #41)** @@ -218,7 +218,7 @@ In `engine/scout/action_items/backfill.py`, change the import on line 41 and the - [ ] **Step 5: Run the new tests, then the affected suites** -Run: `cd /Users/jordanburger/scout-plugin/engine && ../.venv/bin/python -m pytest tests/unit/test_action_items_backfill.py tests/unit/test_action_items_parser.py tests/integration/test_post_session_backfill.py -q` +Run: `cd ~/scout-plugin/engine && ../.venv/bin/python -m pytest tests/unit/test_action_items_backfill.py tests/unit/test_action_items_parser.py tests/integration/test_post_session_backfill.py -q` Expected: all PASS. Then `../.venv/bin/python -m ruff check scout/action_items/backfill.py scout/action_items/parser.py tests/unit/test_action_items_backfill.py tests/unit/test_action_items_parser.py && ../.venv/bin/python -m ruff format --check scout/action_items/parser.py scout/action_items/backfill.py && ../.venv/bin/python -m mypy scout/action_items/parser.py scout/action_items/backfill.py` — all clean. - [ ] **Step 6: Commit** @@ -268,7 +268,7 @@ def test_load_yaml_unreadable_file_raises_configerror(tmp_path): - [ ] **Step 2: Run it to verify it fails** -Run: `cd /Users/jordanburger/scout-plugin/engine && ../.venv/bin/python -m pytest tests/unit/test_connectors_yaml.py -q -k unreadable` +Run: `cd ~/scout-plugin/engine && ../.venv/bin/python -m pytest tests/unit/test_connectors_yaml.py -q -k unreadable` Expected: FAIL — raises `FileNotFoundError`/`OSError`, not `ConfigError`. - [ ] **Step 3: Add the OSError guard** @@ -291,7 +291,7 @@ def _load_yaml(path: Path) -> dict[str, Any]: - [ ] **Step 4: Run the connectors tests** -Run: `cd /Users/jordanburger/scout-plugin/engine && ../.venv/bin/python -m pytest tests/unit/test_connectors_yaml.py -q && ../.venv/bin/python -m ruff check scout/connectors.py tests/unit/test_connectors_yaml.py && ../.venv/bin/python -m mypy scout/connectors.py` +Run: `cd ~/scout-plugin/engine && ../.venv/bin/python -m pytest tests/unit/test_connectors_yaml.py -q && ../.venv/bin/python -m ruff check scout/connectors.py tests/unit/test_connectors_yaml.py && ../.venv/bin/python -m mypy scout/connectors.py` Expected: all PASS / clean. - [ ] **Step 5: Commit** @@ -344,7 +344,7 @@ def test_bootstrap_upgrade_malformed_config_exits_configerror(tmp_path, monkeypa - [ ] **Step 2: Run it to verify it fails** -Run: `cd /Users/jordanburger/scout-plugin/engine && ../.venv/bin/python -m pytest tests/unit/test_cli.py -q -k bootstrap_upgrade_malformed` +Run: `cd ~/scout-plugin/engine && ../.venv/bin/python -m pytest tests/unit/test_cli.py -q -k bootstrap_upgrade_malformed` Expected: FAIL — exit code is not 10 (raw YAMLError → Typer maps to 1, or the internal-error code). - [ ] **Step 3: Guard the read+parse** @@ -371,7 +371,7 @@ Replace the `existing = ...` line with a guarded read (keep the `import yaml as - [ ] **Step 4: Run the CLI tests** -Run: `cd /Users/jordanburger/scout-plugin/engine && ../.venv/bin/python -m pytest tests/unit/test_cli.py -q && ../.venv/bin/python -m ruff check scout/cli.py tests/unit/test_cli.py && ../.venv/bin/python -m mypy scout/cli.py` +Run: `cd ~/scout-plugin/engine && ../.venv/bin/python -m pytest tests/unit/test_cli.py -q && ../.venv/bin/python -m ruff check scout/cli.py tests/unit/test_cli.py && ../.venv/bin/python -m mypy scout/cli.py` Expected: all PASS / clean. - [ ] **Step 5: Commit** @@ -442,7 +442,7 @@ NOTE: confirm the `KnowledgeGraph.__init__` parameter names by reading `engine/s - [ ] **Step 2: Run them to verify they fail** -Run: `cd /Users/jordanburger/scout-plugin/engine && ../.venv/bin/python -m pytest tests/unit/test_kb_ontology.py -q -k "missing_schema or malformed_schema or without_properties"` +Run: `cd ~/scout-plugin/engine && ../.venv/bin/python -m pytest tests/unit/test_kb_ontology.py -q -k "missing_schema or malformed_schema or without_properties"` Expected: missing/malformed raise `OSError`/`YAMLError` (not `KBError`); `without_properties` raises `KeyError`. - [ ] **Step 3: Add `KBSchemaError` to errors.py** @@ -479,7 +479,7 @@ And in `validate`, change the bare subscript (currently ~line 141) from `type_de - [ ] **Step 5: Run the KB tests** -Run: `cd /Users/jordanburger/scout-plugin/engine && ../.venv/bin/python -m pytest tests/unit/test_kb_ontology.py -q && ../.venv/bin/python -m ruff check scout/kb/ontology.py scout/errors.py tests/unit/test_kb_ontology.py && ../.venv/bin/python -m mypy scout/kb/ontology.py scout/errors.py` +Run: `cd ~/scout-plugin/engine && ../.venv/bin/python -m pytest tests/unit/test_kb_ontology.py -q && ../.venv/bin/python -m ruff check scout/kb/ontology.py scout/errors.py tests/unit/test_kb_ontology.py && ../.venv/bin/python -m mypy scout/kb/ontology.py scout/errors.py` Expected: all PASS / clean. - [ ] **Step 6: Commit** @@ -559,7 +559,7 @@ def test_build_terminal_applescript_prompt_is_shell_quoted(): - [ ] **Step 2: Run them to verify they fail** -Run: `cd /Users/jordanburger/scout-plugin/engine && ../.venv/bin/python -m pytest tests/unit/test_tui_spawn_cmd.py -q` +Run: `cd ~/scout-plugin/engine && ../.venv/bin/python -m pytest tests/unit/test_tui_spawn_cmd.py -q` Expected: FAIL — `ModuleNotFoundError: scout.tui.spawn_cmd`. - [ ] **Step 3: Create the pure builder module** @@ -625,7 +625,7 @@ def build_terminal_applescript(*, title: str, prompt: str) -> tuple[str, str]: - [ ] **Step 4: Run the builder tests** -Run: `cd /Users/jordanburger/scout-plugin/engine && ../.venv/bin/python -m pytest tests/unit/test_tui_spawn_cmd.py -q` +Run: `cd ~/scout-plugin/engine && ../.venv/bin/python -m pytest tests/unit/test_tui_spawn_cmd.py -q` Expected: all PASS. - [ ] **Step 5: Rewire `spawn.py` to use the builder + offload Popen** @@ -664,7 +664,7 @@ and: - [ ] **Step 6: Verify the smoke test still imports and run the builder tests once more** -Run: `cd /Users/jordanburger/scout-plugin/engine && ../.venv/bin/python -m pytest tests/unit/test_tui_spawn_cmd.py tests/unit/test_tui_smoke.py -q && ../.venv/bin/python -m ruff check scout/tui/spawn_cmd.py scout/tui/screens/spawn.py tests/unit/test_tui_spawn_cmd.py && ../.venv/bin/python -m ruff format --check scout/tui/spawn_cmd.py scout/tui/screens/spawn.py && ../.venv/bin/python -m mypy scout/tui/spawn_cmd.py` +Run: `cd ~/scout-plugin/engine && ../.venv/bin/python -m pytest tests/unit/test_tui_spawn_cmd.py tests/unit/test_tui_smoke.py -q && ../.venv/bin/python -m ruff check scout/tui/spawn_cmd.py scout/tui/screens/spawn.py tests/unit/test_tui_spawn_cmd.py && ../.venv/bin/python -m ruff format --check scout/tui/spawn_cmd.py scout/tui/screens/spawn.py && ../.venv/bin/python -m mypy scout/tui/spawn_cmd.py` Expected: builder tests PASS; `test_tui_smoke.py` PASSES or SKIPS (textual not installed → skip is fine); ruff/mypy clean. (mypy on `screens/spawn.py` may be skipped per existing `[[tool.mypy.overrides]]` for `textual.*`; only assert mypy clean on `spawn_cmd.py`.) - [ ] **Step 7: Commit** @@ -741,7 +741,7 @@ NOTE: confirm `discover_kb_files`'s parameter — the test passes the vault root - [ ] **Step 2: Run them to verify they fail** -Run: `cd /Users/jordanburger/scout-plugin/engine && ../.venv/bin/python -m pytest tests/unit/test_hooks_kb_pre_filter.py -q -k "tz_aware or symlink_loop"` +Run: `cd ~/scout-plugin/engine && ../.venv/bin/python -m pytest tests/unit/test_hooks_kb_pre_filter.py -q -k "tz_aware or symlink_loop"` Expected: `tz_aware` FAILS (naive datetime, `tzinfo is None`); `symlink_loop` FAILS by timing out (thread still alive after 10s) — or passes slowly; either way it must become fast+safe after the fix. - [ ] **Step 3: Make `parse_date` ET-aware and stop following symlinks** @@ -781,7 +781,7 @@ Keep the existing per-file filter/append logic that followed the old `for p in . - [ ] **Step 4: Run the hook tests** -Run: `cd /Users/jordanburger/scout-plugin/engine && ../.venv/bin/python -m pytest tests/unit/test_hooks_kb_pre_filter.py -q` +Run: `cd ~/scout-plugin/engine && ../.venv/bin/python -m pytest tests/unit/test_hooks_kb_pre_filter.py -q` Expected: all PASS — including the existing `test_classify_age_is_dst_correct_across_spring_forward` (the DST arithmetic still holds since `parse_date` now returns the same ET-aware value `classify` used to compute). Then `../.venv/bin/python -m ruff check scout/hooks/kb_pre_filter.py tests/unit/test_hooks_kb_pre_filter.py && ../.venv/bin/python -m mypy scout/hooks/kb_pre_filter.py` — clean. - [ ] **Step 5: Commit** @@ -851,7 +851,7 @@ NOTE: this test calls a new helper `_unique_backup_path` you will add in Step 3. - [ ] **Step 2: Run it to verify it fails** -Run: `cd /Users/jordanburger/scout-plugin/engine && ../.venv/bin/python -m pytest tests/unit/test_bootstrap_upgrade.py -q -k same_day` +Run: `cd ~/scout-plugin/engine && ../.venv/bin/python -m pytest tests/unit/test_bootstrap_upgrade.py -q -k same_day` Expected: FAIL — `_unique_backup_path` doesn't exist (AttributeError). - [ ] **Step 3: Add a unique-backup helper and use it** @@ -890,7 +890,7 @@ Then in `_stage_cat1b_runners`, replace the backup block (lines 222-226): - [ ] **Step 4: Run the bootstrap-upgrade tests** -Run: `cd /Users/jordanburger/scout-plugin/engine && ../.venv/bin/python -m pytest tests/unit/test_bootstrap_upgrade.py -q && ../.venv/bin/python -m ruff check scout/scripts/bootstrap.py tests/unit/test_bootstrap_upgrade.py && ../.venv/bin/python -m mypy scout/scripts/bootstrap.py` +Run: `cd ~/scout-plugin/engine && ../.venv/bin/python -m pytest tests/unit/test_bootstrap_upgrade.py -q && ../.venv/bin/python -m ruff check scout/scripts/bootstrap.py tests/unit/test_bootstrap_upgrade.py && ../.venv/bin/python -m mypy scout/scripts/bootstrap.py` Expected: all PASS / clean. - [ ] **Step 5: Commit** @@ -915,7 +915,7 @@ Co-Authored-By: Claude Fable 5 " - [ ] **Step 1: Full suite, lint, types** -Run: `cd /Users/jordanburger/scout-plugin/engine && ../.venv/bin/python -m pytest tests/ -q 2>&1 | tail -3` +Run: `cd ~/scout-plugin/engine && ../.venv/bin/python -m pytest tests/ -q 2>&1 | tail -3` Expected: 0 failed, 0 xfailed (this branch has Batch 1's hermetic fixture, so no live-vault flakes). Run: `../.venv/bin/python -m ruff check scout/ tests/ && ../.venv/bin/python -m ruff format --check scout/ tests/ && ../.venv/bin/python -m mypy scout/` Expected: all clean. (If `ruff format --check` flags a new file, run `../.venv/bin/python -m ruff format scout/ tests/` and amend the relevant commit.) diff --git a/docs/superpowers/specs/2026-06-16-scoutctl-phases-backport-design.md b/docs/superpowers/specs/2026-06-16-scoutctl-phases-backport-design.md index 934c009..341888a 100644 --- a/docs/superpowers/specs/2026-06-16-scoutctl-phases-backport-design.md +++ b/docs/superpowers/specs/2026-06-16-scoutctl-phases-backport-design.md @@ -12,7 +12,7 @@ `_assemble(kind)` returns `"# {kind}\n\n**BASE_DIR:** …\n"` followed by each selected phase **section body**, run through `render_template()` and joined by `\n\n`. Selection: SKILL = `core`+`connectors` (modes briefing/consolidation); DREAMING = `core`+`modes` (dreaming); RESEARCH = `core`+`research` (research); files globbed sorted, sections filtered by `requires` (enabled connector) and `mode`. -`render_template()` is **lossy**: `{{VAR}}` → concrete value (`{{USER_NAME}}`→"Jordan", `{{INSTANCE_NAME}}`→"Scout", …). Reversing the substitution is the central difficulty. +`render_template()` is **lossy**: `{{VAR}}` → concrete value (`{{USER_NAME}}`→"Alex", `{{INSTANCE_NAME}}`→"Scout", …). Reversing the substitution is the central difficulty. ### Template variables, by reversal safety diff --git a/docs/terms.html b/docs/terms.html index 7492efc..a8cfc8e 100644 --- a/docs/terms.html +++ b/docs/terms.html @@ -60,7 +60,7 @@

4. Third-party services and no affiliation

Scout integrates with third-party services and runs on Anthropic's Claude. Those services are controlled by their respective providers and governed by their own terms and privacy policies; we are not responsible for them. Scout is an independent project and is not affiliated with, endorsed by, or sponsored by Anthropic, - Microsoft, Keboola, Slack, Google, Linear, GitHub, or any other company. All product names, logos, and trademarks + Microsoft, Slack, Google, Linear, GitHub, or any other company. All product names, logos, and trademarks are the property of their respective owners and are used only to describe interoperability.

5. No warranty

diff --git a/engine/scout/action_items/render.py b/engine/scout/action_items/render.py index e859569..9d33405 100644 --- a/engine/scout/action_items/render.py +++ b/engine/scout/action_items/render.py @@ -21,6 +21,7 @@ import datetime as dt import html +import os import re from dataclasses import dataclass, field from pathlib import Path @@ -611,20 +612,25 @@ def _plain_subject(subject: str) -> str: # Deep-link detection ------------------------------------------------------ -# Linear: `AI-2619`, `ST-3853`, `SUPPORT-15915`, `LDRS-321`, `KAI-12`. The -# allowed prefixes below are the ones seen in real action items / KB; adding -# more is cheap. Note that a Linear ID also appears inside `[[AI-XXXX]]` -# wikilinks — those render as plain text, so the same regex finds both. -_LINEAR_ID_RE = re.compile(r"\b(AI|ST|SUPPORT|LDRS|KAI|DATA)-\d+\b") +# Linear IDs are any `TEAM-123` shape (2–10 uppercase letters + digits), +# mirroring scout-app's reference detector so the two stay in contract. A +# Linear ID also appears inside `[[TEAM-XXXX]]` wikilinks — those render as +# plain text, so the same regex finds both. +_LINEAR_ID_RE = re.compile(r"\b[A-Z]{2,10}-\d+\b") _GITHUB_PR_RE = re.compile(r"https://github\.com/([\w.\-]+)/([\w.\-]+)/pull/(\d+)") _SLACK_LINK_RE = re.compile(r"https://[\w.\-]+\.slack\.com/archives/[A-Z0-9]+/p\d+(?:\?[^\s)\"']+)?") +# Linear workspace slug for issue deep links (the `` in +# linear.app//issue/...). Set SCOUT_LINEAR_WORKSPACE to your workspace; +# defaults to a neutral placeholder so no real workspace is baked into source. +_LINEAR_WORKSPACE = os.environ.get("SCOUT_LINEAR_WORKSPACE", "your-workspace") + def _render_task_links(t: Task) -> str: """Emit deep-link buttons (Linear / GitHub PR / Slack) for the task. Links come from scanning the subject + body for well-known URL shapes and - Linear issue IDs. De-duplicated per-card so `[[AI-2619]]` mentioned three + Linear issue IDs. De-duplicated per-card so `[[PROJ-2619]]` mentioned three times still yields one button. """ text = f"{t.subject} {t.body}" @@ -636,7 +642,7 @@ def _render_task_links(t: Task) -> str: if key in seen: continue seen.add(key) - links.append((f"Linear {iid}", f"https://linear.app/keboola/issue/{iid}")) + links.append((f"Linear {iid}", f"https://linear.app/{_LINEAR_WORKSPACE}/issue/{iid}")) for m in _GITHUB_PR_RE.finditer(text): repo = f"{m.group(1)}/{m.group(2)}" pr = m.group(3) diff --git a/engine/scout/connectors.yaml b/engine/scout/connectors.yaml index 3fbeb36..56a360d 100644 --- a/engine/scout/connectors.yaml +++ b/engine/scout/connectors.yaml @@ -137,8 +137,8 @@ connectors: first_fix: "Run `gh auth status`; if expired, `gh auth login` to re-auth. Token lives in macOS keychain." detail: | GitHub access is via the local `gh` CLI (not an MCP). Token expiry is the usual - cause. `gh auth status` shows current state; `gh auth login` refreshes it. After a - Keboola SAML re-auth, the personal token may need a separate refresh. + cause. `gh auth status` shows current state; `gh auth login` refreshes it. After an + SSO/SAML re-auth, the personal token may need a separate refresh. mcp:claude-in-chrome: display_name: Chrome (Google Messages) diff --git a/engine/scout/scripts/connectors_snapshot.py b/engine/scout/scripts/connectors_snapshot.py index d6d5e05..12dc30d 100644 --- a/engine/scout/scripts/connectors_snapshot.py +++ b/engine/scout/scripts/connectors_snapshot.py @@ -94,7 +94,7 @@ def canonical_snapshot_path() -> Path: def app_fixture_snapshot_path() -> Path: """Return scout-app's bundled fixture path. - Best-effort secondary write target. Hardcoded to Jordan's dev-machine + Best-effort secondary write target. Assumes the conventional dev-machine layout (``~/scout-app/...``); skipped with a warning if scout-app isn't checked out at that location. """ diff --git a/engine/scout/scripts/schedule_snapshot.py b/engine/scout/scripts/schedule_snapshot.py index 1d9b19a..c02b5a6 100644 --- a/engine/scout/scripts/schedule_snapshot.py +++ b/engine/scout/scripts/schedule_snapshot.py @@ -104,7 +104,7 @@ def canonical_snapshot_path() -> Path: def app_fixture_snapshot_path() -> Path: """Return scout-app's bundled fixture path. - Best-effort secondary write target. Hardcoded to Jordan's dev-machine + Best-effort secondary write target. Assumes the conventional dev-machine layout (``~/scout-app/...``); skipped with a warning if scout-app isn't checked out at that location. """ diff --git a/engine/tests/fixtures/contract/parser-corpus.json b/engine/tests/fixtures/contract/parser-corpus.json index f9ee19c..ed81883 100644 --- a/engine/tests/fixtures/contract/parser-corpus.json +++ b/engine/tests/fixtures/contract/parser-corpus.json @@ -3,22 +3,22 @@ "entries": [ { "name": "prefixed-emoji-bold", - "line": "- [ ] [#AB30] **🔴 Validate kai-agent LangSmith tracing — [[AI-3026]]** _(carries 6/2→6/4)_ — overnight progress", + "line": "- [ ] [#AB30] **🔴 Validate the tracing pipeline — [[PROJ-3026]]** _(carries 6/2→6/4)_ — overnight progress", "expected": { "short_prefix": "AB30", - "subject": "**🔴 Validate kai-agent LangSmith tracing — [[AI-3026]]** _(carries 6/2→6/4)_", - "plain_subject": "🔴 Validate kai-agent LangSmith tracing — AI-3026 _(carries 6/2→6/4)_", + "subject": "**🔴 Validate the tracing pipeline — [[PROJ-3026]]** _(carries 6/2→6/4)_", + "plain_subject": "🔴 Validate the tracing pipeline — PROJ-3026 _(carries 6/2→6/4)_", "body": "overnight progress" }, - "_note": "Prefix changed from draft [#AI30] to [#AB30]: 'AI30' contains 'I', which is NOT in the Crockford alphabet (I/L/O/U excluded), so it could never parse as a short_prefix. subject retains '**' bold markers per the Swift reference contract; the inner ' — ' before [[AI-3026]] is inside the bold span so the split lands at the LAST top-level ' — ' (before 'overnight progress'); the italic _(carries ...)_ is not a separator so it stays in subject. Python render.py xfails this case: it does not strip the [#AB30] prefix from subject/plain_subject (no short_prefix extraction in render.py) — see test _note." + "_note": "Prefix changed from draft [#AI30] to [#AB30]: 'AI30' contains 'I', which is NOT in the Crockford alphabet (I/L/O/U excluded), so it could never parse as a short_prefix. subject retains '**' bold markers per the Swift reference contract; the inner ' — ' before [[PROJ-3026]] is inside the bold span so the split lands at the LAST top-level ' — ' (before 'overnight progress'); the italic _(carries ...)_ is not a separator so it stays in subject. Python render.py xfails this case: it does not strip the [#AB30] prefix from subject/plain_subject (no short_prefix extraction in render.py) — see test _note." }, { "name": "unprefixed-the-issue10-line", - "line": "- [ ] **🔥 🆕 Update kai-pricing-calculator-app with per-client conversion levers** _(net-new from Kai's pricing meeting)_", + "line": "- [ ] **🔥 🆕 Update the pricing calculator app with per-tenant conversion levers** _(net-new from the pricing review)_", "expected": { "short_prefix": null, - "subject": "**🔥 🆕 Update kai-pricing-calculator-app with per-client conversion levers** _(net-new from Kai's pricing meeting)_", - "plain_subject": "🔥 🆕 Update kai-pricing-calculator-app with per-client conversion levers _(net-new from Kai's pricing meeting)_", + "subject": "**🔥 🆕 Update the pricing calculator app with per-tenant conversion levers** _(net-new from the pricing review)_", + "plain_subject": "🔥 🆕 Update the pricing calculator app with per-tenant conversion levers _(net-new from the pricing review)_", "body": "" }, "_note": "No separator outside tokens, so body is empty and the whole line (bold span + trailing italic) is the subject. subject retains '**'; plain_subject strips it. Draft omitted the bold markers from subject (corrected, case a)." @@ -30,85 +30,85 @@ }, { "name": "done-with-github-pr-link", - "line": "- [x] [#PR99] **Merge the fix** — see https://github.com/keboola/keboola_com/pull/301", + "line": "- [x] [#PR99] **Merge the fix** — see https://github.com/example-org/example-repo/pull/301", "expected": { "short_prefix": "PR99", "subject": "**Merge the fix**", "plain_subject": "Merge the fix", - "body": "see https://github.com/keboola/keboola_com/pull/301" + "body": "see https://github.com/example-org/example-repo/pull/301" } }, { "name": "snooze-suffix", - "line": "- [ ] [#SNZ1] **Ping Devin** — nudge — 🛌 Snoozed until 2026-06-10", + "line": "- [ ] [#SNZ1] **Ping Sam** — nudge — 🛌 Snoozed until 2026-06-10", "expected": { "short_prefix": "SNZ1", - "subject": "**Ping Devin**", - "plain_subject": "Ping Devin", + "subject": "**Ping Sam**", + "plain_subject": "Ping Sam", "body": "nudge — 🛌 Snoozed until 2026-06-10" } }, { "name": "carry-in-was-kind", - "line": "- [ ] [#CRY2] **Reschedule Groupon** _(carried in from 2026-06-03, was urgent)_", + "line": "- [ ] [#CRY2] **Reschedule the demo** _(carried in from 2026-06-03, was urgent)_", "expected": { "short_prefix": "CRY2", - "subject": "**Reschedule Groupon** _(carried in from 2026-06-03, was urgent)_", - "plain_subject": "Reschedule Groupon _(carried in from 2026-06-03, was urgent)_", + "subject": "**Reschedule the demo** _(carried in from 2026-06-03, was urgent)_", + "plain_subject": "Reschedule the demo _(carried in from 2026-06-03, was urgent)_", "body": "" }, "_note": "No separator outside tokens (the italic _(...)_ is not a separator), so body is empty and the parenthetical stays in subject. Draft put the parenthetical in body (corrected, case a)." }, { "name": "wikilink-alias-and-code", - "line": "- [ ] [#WK01] **Check [[AI-2619|the cost ruling]]** — run `scoutctl status`", + "line": "- [ ] [#WK01] **Check [[PROJ-2619|the cost ruling]]** — run `scoutctl status`", "expected": { "short_prefix": "WK01", - "subject": "**Check [[AI-2619|the cost ruling]]**", - "plain_subject": "Check AI-2619", + "subject": "**Check [[PROJ-2619|the cost ruling]]**", + "plain_subject": "Check PROJ-2619", "body": "run `scoutctl status`" } }, { "name": "semantic-tag-4char-non-crockford", - "line": "- [ ] [#MIRO] **Miro 1:1 follow-through** — 3 quick sends", + "line": "- [ ] [#MIRO] **Team 1:1 follow-through** — 3 quick sends", "expected": { "short_prefix": "MIRO", - "subject": "**Miro 1:1 follow-through**", - "plain_subject": "Miro 1:1 follow-through", + "subject": "**Team 1:1 follow-through**", + "plain_subject": "Team 1:1 follow-through", "body": "3 quick sends" }, "_note": "4-char tag containing I and O (NOT Crockford). Exercises the widened recognition grammar (#117). subject/plain_subject xfail on Python (render.py prefix-strip #114)." }, { "name": "semantic-tag-6char", - "line": "- [ ] [#AI3026] **Validate kai-agent tracing**", + "line": "- [ ] [#AI3026] **Validate the tracing job**", "expected": { "short_prefix": "AI3026", - "subject": "**Validate kai-agent tracing**", - "plain_subject": "Validate kai-agent tracing", + "subject": "**Validate the tracing job**", + "plain_subject": "Validate the tracing job", "body": "" }, "_note": "6-char tag containing I. Length > the old 4-char limit." }, { "name": "semantic-tag-3char", - "line": "- [ ] [#RSM] Rossum SL tester", + "line": "- [ ] [#RSM] Review SL tester", "expected": { "short_prefix": "RSM", - "subject": "Rossum SL tester", - "plain_subject": "Rossum SL tester", + "subject": "Review SL tester", + "plain_subject": "Review SL tester", "body": "" }, "_note": "3-char tag, no bold, no separator." }, { "name": "semantic-tag-digit-led", - "line": "- [ ] [#5864M] **Merge ui PR** — APPROVED", + "line": "- [ ] [#5864M] **Merge the web PR** — APPROVED", "expected": { "short_prefix": "5864M", - "subject": "**Merge ui PR**", - "plain_subject": "Merge ui PR", + "subject": "**Merge the web PR**", + "plain_subject": "Merge the web PR", "body": "APPROVED" }, "_note": "Digit-led 5-char tag with one trailing letter — accepted (has a letter); pure-digit would be rejected as a GitHub ref." diff --git a/engine/tests/fixtures/kb-sample/people/jordan.md b/engine/tests/fixtures/kb-sample/people/alex.md similarity index 83% rename from engine/tests/fixtures/kb-sample/people/jordan.md rename to engine/tests/fixtures/kb-sample/people/alex.md index 1213d66..9aa8dac 100644 --- a/engine/tests/fixtures/kb-sample/people/jordan.md +++ b/engine/tests/fixtures/kb-sample/people/alex.md @@ -1,10 +1,10 @@ --- type: person -name: Jordan +name: Alex team: Engineering works_on: [scout, plugin-x] --- -# Jordan +# Alex Test fixture entity for KB ontology tests. diff --git a/engine/tests/fixtures/stop-payload.json b/engine/tests/fixtures/stop-payload.json index 11810c7..e9d927c 100644 --- a/engine/tests/fixtures/stop-payload.json +++ b/engine/tests/fixtures/stop-payload.json @@ -1,5 +1,5 @@ { "session_id": "abc-123", "transcript_path": "FIXTURE_TRANSCRIPT_PATH", - "cwd": "/Users/jordanburger/scout-app" + "cwd": "/Users/alex/scout-app" } diff --git a/engine/tests/parity/test_kb_pre_filter_parity.bats b/engine/tests/parity/test_kb_pre_filter_parity.bats index be820aa..00a9bad 100644 --- a/engine/tests/parity/test_kb_pre_filter_parity.bats +++ b/engine/tests/parity/test_kb_pre_filter_parity.bats @@ -103,8 +103,8 @@ EOF cat > "$kb/ontology/schema.md" <<'EOF' # Schema EOF - cat > "$kb/personal/jordan.md" <<'EOF' -# Jordan + cat > "$kb/personal/alex.md" <<'EOF' +# Alex EOF cat > "$kb/projects/archived/old.md" <<'EOF' # Old diff --git a/engine/tests/unit/test_action_items_common.py b/engine/tests/unit/test_action_items_common.py index 952d45f..04f8c9b 100644 --- a/engine/tests/unit/test_action_items_common.py +++ b/engine/tests/unit/test_action_items_common.py @@ -92,13 +92,13 @@ def test_resolve_target_auto_registers_prefix_in_file_but_not_idmap( items = [ ActionItem( priority="🔥", - title="Rotate Kai Pricing GitHub token", + title="Rotate vendor API token", status="open", section="Urgent", context_links=[], notes=[], details=[], - raw_line="- [ ] [#KPTK] 🔥 Rotate Kai Pricing GitHub token", + raw_line="- [ ] [#KPTK] 🔥 Rotate vendor API token", line_number=12, short_prefix="KPTK", ), @@ -114,7 +114,7 @@ def test_resolve_target_auto_registers_prefix_in_file_but_not_idmap( e2 = m2.lookup_by_prefix("KPTK") assert e2 is not None assert e2.ulid == ulid - assert e2.last_title == "Rotate Kai Pricing GitHub token" + assert e2.last_title == "Rotate vendor API token" def test_resolve_target_ambiguous_subject_raises(fake_data_dir: Path) -> None: diff --git a/engine/tests/unit/test_action_items_delete_comment.py b/engine/tests/unit/test_action_items_delete_comment.py index 04255a2..9659212 100644 --- a/engine/tests/unit/test_action_items_delete_comment.py +++ b/engine/tests/unit/test_action_items_delete_comment.py @@ -32,9 +32,9 @@ def test_delete_comment_by_index(fake_data_dir: Path, monkeypatch: pytest.Monkey fake_data_dir, "## In Progress\n\n" "- [ ] [#A3F7] task\n" - " - jordan: first\n" - " - jordan: second\n" - " - jordan: third\n" + " - alex: first\n" + " - alex: second\n" + " - alex: third\n" "- [ ] another task\n", ) monkeypatch.setattr("scout.action_items.delete_comment._today", lambda: dt.date(2026, 4, 26)) @@ -56,7 +56,7 @@ def test_delete_comment_by_text_substring(fake_data_dir: Path, monkeypatch: pyte _register_prefix(fake_data_dir) _make_daily( fake_data_dir, - "## In Progress\n\n- [ ] [#A3F7] task\n - jordan: vendor confirmed\n - jordan: legal review needed\n", + "## In Progress\n\n- [ ] [#A3F7] task\n - alex: vendor confirmed\n - alex: legal review needed\n", ) monkeypatch.setattr("scout.action_items.delete_comment._today", lambda: dt.date(2026, 4, 26)) @@ -73,7 +73,7 @@ def test_delete_comment_ignores_snoozed_until_marker(fake_data_dir: Path, monkey _register_prefix(fake_data_dir) daily = _make_daily( fake_data_dir, - "## In Progress\n\n- [ ] [#A3F7] task\n - snoozed-until: 2026-05-01\n - jordan: real comment\n", + "## In Progress\n\n- [ ] [#A3F7] task\n - snoozed-until: 2026-05-01\n - alex: real comment\n", ) monkeypatch.setattr("scout.action_items.delete_comment._today", lambda: dt.date(2026, 4, 26)) @@ -88,7 +88,7 @@ def test_delete_comment_index_out_of_range(fake_data_dir: Path, monkeypatch: pyt _register_prefix(fake_data_dir) _make_daily( fake_data_dir, - "- [ ] [#A3F7] task\n - jordan: only one\n", + "- [ ] [#A3F7] task\n - alex: only one\n", ) monkeypatch.setattr("scout.action_items.delete_comment._today", lambda: dt.date(2026, 4, 26)) @@ -100,7 +100,7 @@ def test_delete_comment_ambiguous_text(fake_data_dir: Path, monkeypatch: pytest. _register_prefix(fake_data_dir) _make_daily( fake_data_dir, - "- [ ] [#A3F7] task\n - jordan: vendor ping\n - jordan: vendor confirmed\n", + "- [ ] [#A3F7] task\n - alex: vendor ping\n - alex: vendor confirmed\n", ) monkeypatch.setattr("scout.action_items.delete_comment._today", lambda: dt.date(2026, 4, 26)) @@ -110,7 +110,7 @@ def test_delete_comment_ambiguous_text(fake_data_dir: Path, monkeypatch: pytest. def test_delete_comment_requires_exactly_one_selector(fake_data_dir: Path, monkeypatch: pytest.MonkeyPatch) -> None: _register_prefix(fake_data_dir) - _make_daily(fake_data_dir, "- [ ] [#A3F7] task\n - jordan: hi\n") + _make_daily(fake_data_dir, "- [ ] [#A3F7] task\n - alex: hi\n") monkeypatch.setattr("scout.action_items.delete_comment._today", lambda: dt.date(2026, 4, 26)) with pytest.raises(ActionItemError, match="exactly one of --index or --text"): diff --git a/engine/tests/unit/test_action_items_edit_comment.py b/engine/tests/unit/test_action_items_edit_comment.py index 4363cc5..1b57a80 100644 --- a/engine/tests/unit/test_action_items_edit_comment.py +++ b/engine/tests/unit/test_action_items_edit_comment.py @@ -30,7 +30,7 @@ def test_edit_comment_by_index_replaces_body_only(fake_data_dir: Path, monkeypat _register_prefix(fake_data_dir) daily = _make_daily( fake_data_dir, - "- [ ] [#A3F7] task\n - jordan: first draft\n - jordan: second draft\n", + "- [ ] [#A3F7] task\n - alex: first draft\n - alex: second draft\n", ) monkeypatch.setattr("scout.action_items.edit_comment._today", lambda: dt.date(2026, 4, 26)) @@ -42,7 +42,7 @@ def test_edit_comment_by_index_replaces_body_only(fake_data_dir: Path, monkeypat ) text = daily.read_text() - assert " - jordan: first final\n" in text + assert " - alex: first final\n" in text assert "first draft" not in text assert "second draft" in text # untouched assert isinstance(event, Event) @@ -73,7 +73,7 @@ def test_edit_comment_preserves_original_indent(fake_data_dir: Path, monkeypatch def test_edit_comment_rejects_empty_text(fake_data_dir: Path, monkeypatch: pytest.MonkeyPatch) -> None: _register_prefix(fake_data_dir) - _make_daily(fake_data_dir, "- [ ] [#A3F7] task\n - jordan: original\n") + _make_daily(fake_data_dir, "- [ ] [#A3F7] task\n - alex: original\n") monkeypatch.setattr("scout.action_items.edit_comment._today", lambda: dt.date(2026, 4, 26)) with pytest.raises(ActionItemError, match="new-text must not be empty"): @@ -84,7 +84,7 @@ def test_edit_comment_by_text_substring(fake_data_dir: Path, monkeypatch: pytest _register_prefix(fake_data_dir) daily = _make_daily( fake_data_dir, - "- [ ] [#A3F7] task\n - jordan: ping vendor\n - jordan: legal sign-off\n", + "- [ ] [#A3F7] task\n - alex: ping vendor\n - alex: legal sign-off\n", ) monkeypatch.setattr("scout.action_items.edit_comment._today", lambda: dt.date(2026, 4, 26)) @@ -96,5 +96,5 @@ def test_edit_comment_by_text_substring(fake_data_dir: Path, monkeypatch: pytest ) text = daily.read_text() - assert " - jordan: legal cleared 2026-04-26\n" in text + assert " - alex: legal cleared 2026-04-26\n" in text assert "ping vendor" in text diff --git a/engine/tests/unit/test_action_items_render.py b/engine/tests/unit/test_action_items_render.py index f4df218..413cb49 100644 --- a/engine/tests/unit/test_action_items_render.py +++ b/engine/tests/unit/test_action_items_render.py @@ -100,8 +100,8 @@ def test_parse_keeps_metadata_subbullets_out_of_comments(tmp_path: Path) -> None md.write_text( "# Title\n\n## 📌 Section\n\n" "- [ ] 🔴 Ship the thing\n" - " - Source: Linear (AI-3325)\n" - " - Context: [[kai-backend]]\n" + " - Source: Linear (PROJ-3325)\n" + " - Context: [[backend-service]]\n" " - scout: actually started it\n", encoding="utf-8", ) diff --git a/engine/tests/unit/test_bootstrap_upgrade.py b/engine/tests/unit/test_bootstrap_upgrade.py index c136fd2..e5eb3ac 100644 --- a/engine/tests/unit/test_bootstrap_upgrade.py +++ b/engine/tests/unit/test_bootstrap_upgrade.py @@ -295,7 +295,7 @@ def test_upgrade_migrates_legacy_wishlist_and_research(tmp_path): # Simulate a pre-migration (legacy-format) vault: plant the old single files. (vault / "docs" / "Wishlist.md").write_text( "# Wishlist\n\n" - "* **HIGH — Alpha thing** (2026-06-10 — Jordan DM) do alpha.\n" + "* **HIGH — Alpha thing** (2026-06-10 — Alex DM) do alpha.\n" "* **[in progress] MEDIUM — Beta thing** do beta.\n" ) (vault / "knowledge-base" / "research-queue.md").write_text( diff --git a/engine/tests/unit/test_hooks_kb_pre_filter.py b/engine/tests/unit/test_hooks_kb_pre_filter.py index a3e918c..d5c9ff8 100644 --- a/engine/tests/unit/test_hooks_kb_pre_filter.py +++ b/engine/tests/unit/test_hooks_kb_pre_filter.py @@ -215,7 +215,7 @@ def test_discover_excludes_personal(tmp_path): kb = _make_kb(tmp_path) (kb / "knowledge-base.md").write_text("x") (kb / "personal").mkdir() - (kb / "personal" / "jordan.md").write_text("x") + (kb / "personal" / "alex.md").write_text("x") files = discover_kb_files(tmp_path) rels = sorted(p.relative_to(tmp_path).as_posix() for p in files) diff --git a/engine/tests/unit/test_kb_ontology.py b/engine/tests/unit/test_kb_ontology.py index 0036c13..f47c521 100644 --- a/engine/tests/unit/test_kb_ontology.py +++ b/engine/tests/unit/test_kb_ontology.py @@ -18,14 +18,14 @@ def test_knowledge_graph_loads_fixture() -> None: kb_root=str(FIXTURE_DIR), ) g.load() - results = g.query(type="person", name="Jordan") + results = g.query(type="person", name="Alex") assert len(results) == 1 # Adapt assertion shape to what query() returns (dict vs object). first = results[0] if hasattr(first, "name"): - assert first.name == "Jordan" + assert first.name == "Alex" else: - assert first["name"] == "Jordan" + assert first["name"] == "Alex" def test_knowledge_graph_query_unknown_type_returns_empty() -> None: diff --git a/engine/tests/unit/test_parser_corpus_checksum.py b/engine/tests/unit/test_parser_corpus_checksum.py index c6909ed..a3fa8a8 100644 --- a/engine/tests/unit/test_parser_corpus_checksum.py +++ b/engine/tests/unit/test_parser_corpus_checksum.py @@ -19,7 +19,7 @@ CORPUS = Path(__file__).resolve().parents[1] / "fixtures" / "contract" / "parser-corpus.json" -EXPECTED_SHA256 = "4ebe8ae34a5b945bb5165ebd6bb6b818986c2cafec0ad30910bfd3fcb66e21a1" +EXPECTED_SHA256 = "745dc8f886c52cd3a2273a2f5fd76934782492b159a6f63ab0d9e6978114511f" def test_corpus_matches_canonical_checksum() -> None: diff --git a/engine/tests/unit/test_schedule_loader.py b/engine/tests/unit/test_schedule_loader.py index 7ddb6a3..b0c41f8 100644 --- a/engine/tests/unit/test_schedule_loader.py +++ b/engine/tests/unit/test_schedule_loader.py @@ -24,7 +24,7 @@ FIXTURES = Path(__file__).parent.parent / "fixtures" -def test_load_default_schedule_returns_jordan_default_slots(): +def test_load_default_schedule_returns_default_slots(): sched = load_default_schedule() keys = set(sched.keys()) # The 10 slot keys shipped in engine/scout/defaults/schedule.yaml. diff --git a/templates/scripts/recurring-task-status.py b/templates/scripts/recurring-task-status.py index 2c425af..c1fcd27 100755 --- a/templates/scripts/recurring-task-status.py +++ b/templates/scripts/recurring-task-status.py @@ -16,7 +16,7 @@ over the entity's stored `last_completed_date` and is NOT written back to the file — live evidence stays caller-side; only the date math runs here. e.g. recurring-task-status.py --date 2026-05-29 \ - --last-completed kai-builds-data-apps-weekly-update=2026-05-29 + --last-completed weekly-status-update=2026-05-29 Status semantics: done — last_completed_date is within the current cadence window @@ -289,7 +289,7 @@ def main() -> int: "keyed by file stem (slug). Repeatable. The runner fetches this from " "Linear (`get_project` -> lastUpdateAt) or Slack search, then passes it " "in so the local date math reflects live evidence without writing to the " - "entity file. Example: --last-completed kai-builds-data-apps-weekly-update=2026-05-29" + "entity file. Example: --last-completed weekly-status-update=2026-05-29" ), ) args = parser.parse_args()