From f576a78afbc0a71e945751460445983239ee9531 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20V=C3=BDborn=C3=BD?= Date: Sat, 27 Jun 2026 15:04:04 +0200 Subject: [PATCH 1/5] chore: anonymize Keboola/personal data; de-hardcode Linear deep-links Public repo; fixtures/tests plus two functional spots carried real Keboola/personal data from the live vault. Functional: - render.py Linear-ID detection generalized from the hardcoded team allowlist (AI|ST|SUPPORT|LDRS|KAI|DATA)-\d+ to [A-Z]{2,10}-\d+, matching scout-app's reference detector. - linear.app/keboola deep-link workspace now reads from the SCOUT_LINEAR_WORKSPACE env var (default 'your-workspace'). Set it to your workspace slug to retain working Linear deep links. Data: - Anonymize unit-test fixtures/strings (jordan -> alex personas, Linear IDs -> PROJ-*, kai-* -> generic), rename KB fixture people/jordan.md -> alex.md, neutralize a captured cwd path, reword two 'Jordan's dev-machine' comments and a 'Keboola SAML' connector note. - Drop Keboola from the README/TERMS non-affiliation disclaimers. - Sync canonical parser-corpus.json with the Swift copies; update EXPECTED_SHA256 -> 745dc8f8... Preserved as legitimate attribution: pyproject authors, marketplace/plugin owner + github.com/jordanrburger/... URLs (incl. self-update), LICENSE. --- README.md | 2 +- TERMS.md | 2 +- commands/scout-work.md | 2 +- engine/scout/action_items/render.py | 20 ++++--- engine/scout/connectors.yaml | 4 +- engine/scout/scripts/connectors_snapshot.py | 2 +- engine/scout/scripts/schedule_snapshot.py | 2 +- .../fixtures/contract/parser-corpus.json | 60 +++++++++---------- .../kb-sample/people/{jordan.md => alex.md} | 4 +- engine/tests/fixtures/stop-payload.json | 2 +- .../parity/test_kb_pre_filter_parity.bats | 4 +- engine/tests/unit/test_action_items_common.py | 6 +- .../unit/test_action_items_delete_comment.py | 16 ++--- .../unit/test_action_items_edit_comment.py | 10 ++-- engine/tests/unit/test_action_items_render.py | 4 +- engine/tests/unit/test_bootstrap_upgrade.py | 2 +- engine/tests/unit/test_hooks_kb_pre_filter.py | 2 +- engine/tests/unit/test_kb_ontology.py | 6 +- .../tests/unit/test_parser_corpus_checksum.py | 2 +- engine/tests/unit/test_schedule_loader.py | 2 +- templates/scripts/recurring-task-status.py | 4 +- 21 files changed, 82 insertions(+), 76 deletions(-) rename engine/tests/fixtures/kb-sample/people/{jordan.md => alex.md} (83%) 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/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() From 51621ea556f3fe8a32a8e951ac41560a48b1d436 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20V=C3=BDborn=C3=BD?= Date: Sat, 27 Jun 2026 15:08:02 +0200 Subject: [PATCH 2/5] docs(claude): add CLAUDE.md with fixture-anonymization + corpus-sync rule --- CLAUDE.md | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 CLAUDE.md 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`. From 8e99ee15d8a5a814facf0565549e02bf912fc521 Mon Sep 17 00:00:00 2001 From: Jordan Burger Date: Sat, 27 Jun 2026 09:55:43 -0400 Subject: [PATCH 3/5] chore: anonymize remaining web-surface leaks (terms.html, hybrid.css) The fixture/test anonymization removed Keboola from TERMS.md and README.md but the rendered terms.html copy served via GitHub Pages still listed it, leaving the published page contradicting its source. Drop "Keboola" from the non-affiliation disclaimer to match TERMS.md. Also genericize a stylesheet comment that named a real person. Co-Authored-By: Claude Opus 4.8 (1M context) --- docs/hybrid.css | 2 +- docs/terms.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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/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

From 19c59add3c6b1528d44a4f784074af81aed61311 Mon Sep 17 00:00:00 2001 From: Jordan Burger Date: Sat, 27 Jun 2026 10:21:52 -0400 Subject: [PATCH 4/5] chore: genericize maintainer paths and name refs in design docs Replace hardcoded `/Users/jordanburger/...` working-directory paths with the `~/scout-plugin` convention already used elsewhere in these docs, and genericize standalone "Jordan" references to "the maintainer" (and the `{{USER_NAME}}` substitution example to the "Alex" persona used in the anonymized fixtures). Legitimate `jordanrburger/...` repo/attribution URLs are left intact. Co-Authored-By: Claude Opus 4.8 (1M context) --- ...6-06-02-release-and-distribution-system.md | 2 +- docs/plans/2026-06-10-batch-1-quick-wins.md | 4 +-- ...6-06-02-release-and-distribution-system.md | 2 +- .../2026-06-14-connector-probe-overlay.md | 6 ++-- .../plans/2026-06-15-batch-3-high-severity.md | 36 +++++++++---------- ...6-06-16-scoutctl-phases-backport-design.md | 2 +- 6 files changed, 26 insertions(+), 26 deletions(-) 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 From 3d506332a2d6d2067decffa223dad3b0263c21b9 Mon Sep 17 00:00:00 2001 From: Jordan Burger Date: Sat, 27 Jun 2026 12:04:52 -0400 Subject: [PATCH 5/5] chore: use documented persona Sam in demo (was Marcus) Align the landing-page demo and hero image with the persona set documented in CLAUDE.md (Alex / Priya / Sam). Marcus was fictional already; this just keeps the illustrative names consistent with the project's own convention. Co-Authored-By: Claude Opus 4.8 (1M context) --- docs/assets/scout-demo.svg | 2 +- docs/index.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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/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