Skip to content

B3: refactor into src-layout package with subcommand CLI#3

Merged
jcddc83 merged 2 commits into
mainfrom
claude/package-refactor
May 19, 2026
Merged

B3: refactor into src-layout package with subcommand CLI#3
jcddc83 merged 2 commits into
mainfrom
claude/package-refactor

Conversation

@jcddc83

@jcddc83 jcddc83 commented May 18, 2026

Copy link
Copy Markdown
Owner

Summary

Reorganises the five top-level scripts into a substack_link_checker package under src/, exposed as a single CLI with subcommands:

substack-link-checker check          (was: python substack_link_checker.py)
substack-link-checker compare        (was: python compare_posts.py)
substack-link-checker import         (was: python import_checked_posts.py)
substack-link-checker fetch-archive  (was: python fetch_archive_urls.py)
substack-link-checker demo           (was: python demo_link_checker.py)

Also python -m substack_link_checker ... works.

Layout

src/substack_link_checker/
  __init__.py       version + public API re-exports
  __main__.py       python -m entry point
  cli.py            top-level subcommand dispatcher
  _cli_check.py     parser + main for the `check` subcommand
  checker.py        core SubstackLinkChecker class
  compare.py
  demo.py
  fetch_archive.py
  import_history.py

Back-compat

The four helper scripts at the root (compare_posts.py, import_checked_posts.py, fetch_archive_urls.py, demo_link_checker.py) remain as thin shims that prepend src/ to sys.path and delegate. So existing user commands and the bundled PowerShell scheduled task keep working unchanged.

Breaking change

python substack_link_checker.py ... no longer works — the filename collides with the new package name and the two cannot coexist on Python's import path. Migration:

Old New
python substack_link_checker.py ... substack-link-checker check ...
or python -m substack_link_checker check ...

run_link_checker.ps1 already updated. README has a "Migrating from v1.0.0" section.

pyproject changes

  • package-dir = { "" = "src" } + packages.find.where = ["src"] for src-layout
  • Entry point: substack-link-checker = "substack_link_checker.cli:main"
  • pytest moved to importlib import mode so the package resolves to the installed location, not the root-level shims

Tests

35 passing (was 29 in PR #1). Added tests/test_cli_dispatch.py with 6 new tests:

  • no-args prints help
  • --help flag
  • unknown subcommand errors
  • dispatch routes argv correctly
  • sys.argv restored after dispatch
  • --version flag

Cookie-handling tests updated to monkeypatch _cli_check instead of the old top-level module.

Stacked on #1

Based on claude/audit-github-repo-EWQDn (PR #1). Once #1 merges, GitHub should auto-rebase the diff so only this commit shows; otherwise re-target the base to main.

Test plan

  • CI green (lint + Py 3.8/3.10/3.12 + pytest + build)
  • pip install -e . then substack-link-checker --help shows all subcommands
  • python -m substack_link_checker check --help works
  • python compare_posts.py https://example.substack.com history.json (back-compat shim) works
  • On a real Substack: substack-link-checker check --base-url ... --year 2024 --limit 1 produces a CSV

Generated by Claude Code

The five top-level scripts have been reorganised into a
substack_link_checker package under src/ with a single subcommand CLI:

  substack-link-checker check         (was: python substack_link_checker.py)
  substack-link-checker compare       (was: python compare_posts.py)
  substack-link-checker import        (was: python import_checked_posts.py)
  substack-link-checker fetch-archive (was: python fetch_archive_urls.py)
  substack-link-checker demo          (was: python demo_link_checker.py)

Package layout:
  src/substack_link_checker/
    __init__.py        - version, public API re-exports
    __main__.py        - `python -m substack_link_checker ...` entry
    cli.py             - top-level subcommand dispatcher
    _cli_check.py      - argparse parser + main for the check subcommand
    checker.py         - core SubstackLinkChecker class
    compare.py
    demo.py
    fetch_archive.py
    import_history.py

Back-compat: the four helper scripts at the root (compare_posts.py,
import_checked_posts.py, fetch_archive_urls.py, demo_link_checker.py)
remain as thin shims that prepend src/ to sys.path and delegate, so
existing `python compare_posts.py ...` invocations and the bundled
PowerShell scheduled task keep working unchanged.

Breaking: `python substack_link_checker.py ...` no longer works because
the filename collides with the new package name. Migration:
`substack-link-checker check ...` or
`python -m substack_link_checker check ...`. README and CHANGELOG have a
migration table; run_link_checker.ps1 already updated.

pyproject.toml switches to src-layout (`package-dir = { "" = "src" }`,
`packages.find.where = ["src"]`) and the entry point is now
`substack_link_checker.cli:main`. pytest is moved to `importlib` import
mode so the package resolves to the installed location, not the shim
files at the project root.

Tests: 35 passing (was 29). Added tests/test_cli_dispatch.py with 6
tests covering the new subcommand router (no-arg help, --help, unknown
subcommand, dispatch, sys.argv restoration, --version). Cookie-handling
tests updated to monkeypatch _cli_check instead of the old module.

.gitignore: added *.egg-info/, build/, dist/, *.egg.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 15d4a33ce9

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread run_link_checker.ps1
# Step 1: Compare sitemap against history to find new posts
Write-Host "`nStep 1: Finding new posts..."
python compare_posts.py $SUBSTACK_URL checked_posts.json
python -m substack_link_checker compare $SUBSTACK_URL checked_posts.json

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Make scheduled task resolve src-layout package before -m

run_link_checker.ps1 now invokes python -m substack_link_checker ..., but with the new src/ layout that module is not importable from a plain repo checkout unless the environment has already run pip install -e . (or sets PYTHONPATH to src). In Task Scheduler setups that only clone the repo and run this script, step 1 fails immediately with ModuleNotFoundError, so monthly automation no longer runs. Add an explicit setup/path step in this script or call commands that work directly from the checkout.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in a9443c7. Added $env:PYTHONPATH = (Join-Path $PROJECT_DIR "src") + ";" + $env:PYTHONPATH near the top of the script so python -m substack_link_checker resolves from a fresh git clone without requiring pip install -e .. Smoke-tested by uninstalling the editable package locally — PYTHONPATH=src python -m substack_link_checker compare --help now works. Comment in the script explains the why so future readers don't strip it.


Generated by Claude Code

Comment on lines +62 to +63
# so that each module's own parser handles --help correctly with its own
# epilog, examples, etc.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Support --help consistently for delegated subcommands

The dispatcher claims each subcommand handles its own help, but compare (and similarly fetch-archive) does not use argparse and treats --help as a positional URL. So substack-link-checker compare --help is routed here and then crashes/behaves incorrectly instead of showing help. Either add real argument parsers for these subcommands or intercept --help before delegation.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in a9443c7. Converted compare.py, fetch_archive.py, and demo.py to use argparse.ArgumentParser with the same positional argument shapes (so existing invocations don't break). --help now exits 0 with proper usage for all three. The dispatcher already sets sys.argv[0] to "substack-link-checker <name>" before calling, so argparse's auto-prog shows the right invocation in the help output.

Added a parametrised regression test in tests/test_cli_dispatch.py asserting --help works for each: 38 tests passing.


Generated by Claude Code

…archive/demo

P1 (run_link_checker.ps1): the B3 refactor switched the scheduled task
to `python -m substack_link_checker ...`, but that only resolves when
the package is on sys.path. Users who just `git clone` and run the
task (the v1.0.0 workflow) hit ModuleNotFoundError. Fix: prepend
`$PROJECT_DIR\src` to PYTHONPATH near the top of the script, so a
fresh clone works without `pip install -e .`. Comment explains why.

P2 (cli.py dispatcher): compare/fetch-archive/demo subcommands didn't
support --help correctly — they read sys.argv[1] as a positional URL,
so `... compare --help` either crashed or was silently ignored. Fix:
convert each main() to use argparse with the same positional argument
shape, so existing invocations keep working but --help now prints
proper usage. The dispatcher already sets sys.argv[0] to
"substack-link-checker <name>" so argparse's auto-prog shows the right
invocation.

Also fix a stale post-success message in fetch_archive.py that printed
`python substack_link_checker.py ...` as suggested next-step guidance —
that command no longer works after B3.

Tests: parametrised regression test in tests/test_cli_dispatch.py
asserts `--help` exits 0 with proper usage output for all three
subcommands. 38 passing (35 + 3 new).
Base automatically changed from claude/audit-github-repo-EWQDn to main May 19, 2026 15:19
@jcddc83 jcddc83 merged commit ab016d8 into main May 19, 2026
@jcddc83 jcddc83 deleted the claude/package-refactor branch May 19, 2026 15:23
jcddc83 pushed a commit that referenced this pull request May 19, 2026
The CI test job has been running `python substack_link_checker.py --help`
as a post-install smoke test since PR #1. B3 (PR #3) deleted that file
when refactoring the codebase into the src-layout package, but didn't
update this step. CI has been failing on every PR since B3 merged.

Replace with two invocations against the installed console script:
- `substack-link-checker --help` (top-level dispatcher)
- `substack-link-checker check --help` (check subcommand)

This is also why the actions/checkout Dependabot PR (#7) was reported
as failing — same root cause, not the action bump itself.
jcddc83 added a commit that referenced this pull request May 19, 2026
)

* Prepare v1.1.0: bump version + fix stale troubleshooting invocations

Version bump:
- pyproject.toml: 1.0.0 -> 1.1.0
- src/substack_link_checker/__init__.py: __version__ -> 1.1.0
- CHANGELOG.md: rename [Unreleased] -> [1.1.0] - 2026-05-19, add a
  fresh empty [Unreleased] section above it

Troubleshooting fix:
- README.md Troubleshooting section's code blocks still used the
  pre-refactor `python substack_link_checker.py ...` /
  `python fetch_archive_urls.py ...` form because PR #4 was based on
  the audit branch before B3's CLI rewrite landed. Updated to the
  current `substack-link-checker check ...` and
  `substack-link-checker fetch-archive ...` invocations so a reader
  following the troubleshooting steps doesn't hit "command not found"
  on the main entry point.

Once this lands, push tag v1.1.0 to trigger the release workflow
(which builds the wheel/sdist and attaches them to the GitHub
Release).

* Fix CI smoke test (root-level substack_link_checker.py is gone)

The CI test job has been running `python substack_link_checker.py --help`
as a post-install smoke test since PR #1. B3 (PR #3) deleted that file
when refactoring the codebase into the src-layout package, but didn't
update this step. CI has been failing on every PR since B3 merged.

Replace with two invocations against the installed console script:
- `substack-link-checker --help` (top-level dispatcher)
- `substack-link-checker check --help` (check subcommand)

This is also why the actions/checkout Dependabot PR (#7) was reported
as failing — same root cause, not the action bump itself.

---------

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants