Skip to content

chore: migrate off the planning/ convention - #176

Merged
lesnik512 merged 2 commits into
mainfrom
chore/migrate-off-planning
Sep 6, 2026
Merged

chore: migrate off the planning/ convention#176
lesnik512 merged 2 commits into
mainfrom
chore/migrate-off-planning

Conversation

@lesnik512

Copy link
Copy Markdown
Member

Migrates lite-bootstrap off the planning/ convention and onto PR-body-as-spec, with CONTEXT.md owning the vocabulary and docs/adr/ holding rejected alternatives. Part of modern-python/.github#67.

This branch also carries chore(context7): drop the stale planning/ exclusion — the context7.json entry naming planning/ only becomes dead once this migration deletes the directory, so the two land together.

Replaces the two-axis planning/ + architecture/ convention with PR-body-as-spec,
CONTEXT.md for vocabulary, and docs/adr/ for rejected alternatives, per
modern-python/.github#67.

  • planning/ deleted in full, 51 files. changes/ held 21 design records whose
    content is the diff each describes, and git history is that record; audits/
    and retros/ recorded two audit arcs that shipped complete (3 criticals, 5
    design issues, 7 refactors, 8 test gaps, 10 low-priority items across fix: honor root_path in offline-docs redoc handler #89-refactor: rename OpentelemetryConfig → OpenTelemetryConfig; FreeBootstrapperConfig → FreeConfig #103
    and refactor: replace InstrumentNotReadyWarning with is_configured classmethod + summary log #107; all 26 findings of the second audit across fix: lifecycle & teardown correctness (PR1 of bug-audit-v2) #108-chore: hygiene + CI gate (PR3 of bug-audit-v2) #110), so nothing
    there is outstanding; specs/ and plans/ held one org-wide README
    standardization whose lite-bootstrap half is in README.md today. releases/
    held ten notes: 1.4.0 and 1.3.0 diffed byte-identical against gh release view apart from a trailing newline GitHub adds, and 1.1.0 differs only where
    the local copy was edited after publication to chase a planning/ path rename —
    the Releases are the record either way.
  • deferred.md's three items plus two never-scheduled findings from the second
    audit's scope appendix are drafted as issue bodies in
    ../DRAFT-ISSUES-lite-bootstrap.md, each carrying its revisit trigger. Not
    opened on GitHub.
  • planning/decisions/ held four records; the previous, interrupted attempt at
    this migration had carried only two of them across. All four are now ADRs,
    frontmatter stripped: keep-per-instrument-axis is 0002, teardown-marker limits
    is 0003 (its "Update (1.4.0)" section kept as prose), orjson-opt-in is 0005,
    otlp-http-exporter-shape is 0006. None of the four declared a supersedes or
    superseded_by relationship, so none was lost in the strip. Four more ADRs
    rescue rejections that were buried in change files: 0001 (FastMCP teardown via
    a Provider lifespan rather than app._lifespan), 0004 (trace-exclusion stays one
    method), 0007 (the zero-dependency core was attempted and failed on pydantic's
    TypedDict rule under Python < 3.12), 0008 (the structlog->Sentry seam keeps its
    startswith heuristic rather than gaining a marker key). All eight keep a
    revisit trigger.
  • architecture/ carried five prose pages. Three claims in them were enforceable
    and are now INVARIANT tests; the rest was mechanism readable from
    lite_bootstrap/ and is deleted, not relocated.
    test_importing_lite_bootstrap_needs_only_the_stdlib_and_typing_extensions
    (new tests/test_core_import_surface.py) walks every module's top-level AST and
    fails on a third-party import outside an import_checker or TYPE_CHECKING
    guard — the invariant that broke four times and the one this library actually
    controls in the free-threading story. Verified: an unguarded import orjson
    in logging_factory.py fails it, and adding a new guarded optional import plus
    a new stdlib import leaves it green.
    test_every_framework_config_post_init_cascade_reaches_base_config (new
    tests/test_config_cascade.py) spies on BaseConfig's terminator while
    constructing each framework config. Verified against the exact bug that
    shipped once: dropping CorsConfig's super().post_init() call fails it for
    FastAPIConfig and LitestarConfig, and giving SwaggerConfig a new post_init
    that does call super() leaves it green.
    test_only_the_silent_skip_path_lands_in_skipped_instruments
    (tests/test_free_bootstrap.py) pins the asymmetry CONTEXT.md defines "skipped"
    by. Verified: appending the dependency-missing case to skipped_instruments
    fails it, while adding another config-skipped instrument to FreeBootstrapper —
    the list legitimately growing — leaves it green.
    The free-threading page was judged to hold no enforceable claim of its own: its
    support matrix is a statement about upstream wheel availability, and its
    single-threaded-init section is a design stance, not a testable one. Its two
    live blockers are drafted as issues instead.
  • CONTEXT.md was written by the interrupted attempt and audited rather than
    trusted. Its "Skipped" entry claimed both skip paths appear in
    build_summary(); a dependency skip appears in neither list, and the entry is
    corrected — the same claim the new invariant test now holds. All five Avoid
    entries were audited against real usage and all five earn their listing:
    "instrumentation" and "integration" appear only in their sanctioned senses
    (opentelemetry-instrumentation-* packages and the middleware named after them;
    docs/integrations/ and Sentry's own Integration objects), with one straggler —
    docs/introduction/quickstart.md said "production instrumentation" for what are
    mostly not instrumentation at all, now "production observability". That is a
    user-visible string on the docs site.
  • README.md's extras paragraph linked architecture/free-threading.md and claimed
    no mandatory runtime dependencies. Both are fixed: the free-threading facts a
    user needs are inlined, and core's one dependency is named. That paragraph is
    user-visible on PyPI, and the old claim is what ADR-0007 exists to correct.
  • AGENTS.md gains Workflow and Where-a-fact-goes and loses the planning lanes,
    the architecture/ promotion rule, and the capability index. The admission check
    applied to itself cut eight invariant bullets and four convention bullets that
    restated a docstring, a comment already at the site, pyproject.toml or the
    justfile — the frozen/non-frozen split Python enforces on its own, the
    slots-and-bare-super comment that sits three lines above the code it explains,
    the from_dict/from_object asymmetry both docstrings state. What survives is the
    three rules invisible in the code that obeys them, and the ty suppression
    spellings the justfile does not give.
  • mkdocs.yml excludes /adr/ from the published site, matching modern-di: an
    internal decision record is not user documentation, and mkdocs --strict fails
    on a docs/ file absent from nav.
  • justfile and lint-ci drop index and check-planning (there was no check-links
    recipe); the offline lychee gate in _checks.yml replaces them
    (Add the offline link gate to the 18 green repos, and unpublish the ADRs in the two that have them .github#66). Locally it went from 8 errors to 0 over 57 unique
    links — every one of the eight lived inside planning/, and deleting it is what
    made them green.

Beyond the recipe, and the reason this is chore: and not docs: release.yml read
the Release body from planning/releases/.md and hard-required one for every
stable tag, so deleting planning/ would have failed every future stable release.
It is now byte-identical to modern-di's post-migration workflow apart from the
PyPI project name, using GitHub's generated notes; modern-di dropped the same
gate in 1ea74ee (modern-python/modern-di#449). This retires the
mandatory-curated-notes policy; a release wanting prose is edited after the fact
with gh release edit.

Verification: just lint-ci clean, just test 242 passed at 100% coverage, just
docs-build strict clean, lychee --offline 0 errors, and no remaining reference to
planning/, architecture/, check-planning, check-links or convention-version
anywhere in the tree.

The sibling commit on this branch deletes planning/, so the exclusion now
names a directory that does not exist.

Dropping the key rather than leaving an empty list: Context7's schema
(https://context7.com/schema/context7.json) declares no required properties
and gives excludeFolders a default of [], so an absent key and an empty list
are equivalent. Absent matches the repos that never carried the line.

Verification

  • just lint-ci — clean (eof-fixer, ruff format, ruff check, ty).
  • Test suite green at the repo's 100% coverage gate.
  • Offline link gate (lychee --offline --no-progress '**/*.md') — 0 errors.
  • No planning/, architecture/, check-planning, check-links or convention-version reference remains.

Note

release.yml previously read planning/releases/<tag>.md, both as a hard gate for stable tags and as the Release body source; deleting planning/ without changing it would have broken the next stable release. It now uses GitHub's generated notes, matching modern-di post-modern-python/modern-di#449. This retires the mandatory-curated-notes policy.

Replaces the two-axis planning/ + architecture/ convention with PR-body-as-spec,
CONTEXT.md for vocabulary, and docs/adr/ for rejected alternatives, per
modern-python/.github#67.

- planning/ deleted in full, 51 files. changes/ held 21 design records whose
  content is the diff each describes, and git history is that record; audits/
  and retros/ recorded two audit arcs that shipped complete (3 criticals, 5
  design issues, 7 refactors, 8 test gaps, 10 low-priority items across #89-#103
  and #107; all 26 findings of the second audit across #108-#110), so nothing
  there is outstanding; specs/ and plans/ held one org-wide README
  standardization whose lite-bootstrap half is in README.md today. releases/
  held ten notes: 1.4.0 and 1.3.0 diffed byte-identical against `gh release
  view` apart from a trailing newline GitHub adds, and 1.1.0 differs only where
  the local copy was edited after publication to chase a planning/ path rename —
  the Releases are the record either way.
- deferred.md's three items plus two never-scheduled findings from the second
  audit's scope appendix are drafted as issue bodies in
  ../DRAFT-ISSUES-lite-bootstrap.md, each carrying its revisit trigger. Not
  opened on GitHub.
- planning/decisions/ held four records; the previous, interrupted attempt at
  this migration had carried only two of them across. All four are now ADRs,
  frontmatter stripped: keep-per-instrument-axis is 0002, teardown-marker limits
  is 0003 (its "Update (1.4.0)" section kept as prose), orjson-opt-in is 0005,
  otlp-http-exporter-shape is 0006. None of the four declared a supersedes or
  superseded_by relationship, so none was lost in the strip. Four more ADRs
  rescue rejections that were buried in change files: 0001 (FastMCP teardown via
  a Provider lifespan rather than app._lifespan), 0004 (trace-exclusion stays one
  method), 0007 (the zero-dependency core was attempted and failed on pydantic's
  TypedDict rule under Python < 3.12), 0008 (the structlog->Sentry seam keeps its
  startswith heuristic rather than gaining a marker key). All eight keep a
  revisit trigger.
- architecture/ carried five prose pages. Three claims in them were enforceable
  and are now INVARIANT tests; the rest was mechanism readable from
  lite_bootstrap/ and is deleted, not relocated.
  test_importing_lite_bootstrap_needs_only_the_stdlib_and_typing_extensions
  (new tests/test_core_import_surface.py) walks every module's top-level AST and
  fails on a third-party import outside an import_checker or TYPE_CHECKING
  guard — the invariant that broke four times and the one this library actually
  controls in the free-threading story. Verified: an unguarded `import orjson`
  in logging_factory.py fails it, and adding a new *guarded* optional import plus
  a new stdlib import leaves it green.
  test_every_framework_config_post_init_cascade_reaches_base_config (new
  tests/test_config_cascade.py) spies on BaseConfig's terminator while
  constructing each framework config. Verified against the exact bug that
  shipped once: dropping CorsConfig's super().__post_init__() call fails it for
  FastAPIConfig and LitestarConfig, and giving SwaggerConfig a new __post_init__
  that does call super() leaves it green.
  test_only_the_silent_skip_path_lands_in_skipped_instruments
  (tests/test_free_bootstrap.py) pins the asymmetry CONTEXT.md defines "skipped"
  by. Verified: appending the dependency-missing case to skipped_instruments
  fails it, while adding another config-skipped instrument to FreeBootstrapper —
  the list legitimately growing — leaves it green.
  The free-threading page was judged to hold no enforceable claim of its own: its
  support matrix is a statement about upstream wheel availability, and its
  single-threaded-init section is a design stance, not a testable one. Its two
  live blockers are drafted as issues instead.
- CONTEXT.md was written by the interrupted attempt and audited rather than
  trusted. Its "Skipped" entry claimed both skip paths appear in
  build_summary(); a dependency skip appears in neither list, and the entry is
  corrected — the same claim the new invariant test now holds. All five _Avoid_
  entries were audited against real usage and all five earn their listing:
  "instrumentation" and "integration" appear only in their sanctioned senses
  (opentelemetry-instrumentation-* packages and the middleware named after them;
  docs/integrations/ and Sentry's own Integration objects), with one straggler —
  docs/introduction/quickstart.md said "production instrumentation" for what are
  mostly not instrumentation at all, now "production observability". That is a
  user-visible string on the docs site.
- README.md's extras paragraph linked architecture/free-threading.md and claimed
  no mandatory runtime dependencies. Both are fixed: the free-threading facts a
  user needs are inlined, and core's one dependency is named. That paragraph is
  user-visible on PyPI, and the old claim is what ADR-0007 exists to correct.
- AGENTS.md gains Workflow and Where-a-fact-goes and loses the planning lanes,
  the architecture/ promotion rule, and the capability index. The admission check
  applied to itself cut eight invariant bullets and four convention bullets that
  restated a docstring, a comment already at the site, pyproject.toml or the
  justfile — the frozen/non-frozen split Python enforces on its own, the
  slots-and-bare-super comment that sits three lines above the code it explains,
  the from_dict/from_object asymmetry both docstrings state. What survives is the
  three rules invisible in the code that obeys them, and the ty suppression
  spellings the justfile does not give.
- mkdocs.yml excludes /adr/ from the published site, matching modern-di: an
  internal decision record is not user documentation, and mkdocs --strict fails
  on a docs/ file absent from nav.
- justfile and lint-ci drop index and check-planning (there was no check-links
  recipe); the offline lychee gate in _checks.yml replaces them
  (modern-python/.github#66). Locally it went from 8 errors to 0 over 57 unique
  links — every one of the eight lived inside planning/, and deleting it is what
  made them green.

Beyond the recipe, and the reason this is chore: and not docs: release.yml read
the Release body from planning/releases/<tag>.md and hard-required one for every
stable tag, so deleting planning/ would have failed every future stable release.
It is now byte-identical to modern-di's post-migration workflow apart from the
PyPI project name, using GitHub's generated notes; modern-di dropped the same
gate in 1ea74ee (modern-python/modern-di#449). This retires the
mandatory-curated-notes policy; a release wanting prose is edited after the fact
with `gh release edit`.

Verification: just lint-ci clean, just test 242 passed at 100% coverage, just
docs-build strict clean, lychee --offline 0 errors, and no remaining reference to
planning/, architecture/, check-planning, check-links or convention-version
anywhere in the tree.
The sibling commit on this branch deletes planning/, so the exclusion now
names a directory that does not exist.

Dropping the key rather than leaving an empty list: Context7's schema
(https://context7.com/schema/context7.json) declares no required properties
and gives excludeFolders a default of [], so an absent key and an empty list
are equivalent. Absent matches the repos that never carried the line.
@lesnik512
lesnik512 merged commit d95e1f4 into main Sep 6, 2026
12 checks passed
@lesnik512
lesnik512 deleted the chore/migrate-off-planning branch September 6, 2026 19:14
@lesnik512 lesnik512 changed the title chore(context7): drop the stale planning/ exclusion chore: migrate off the planning/ convention Sep 6, 2026
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.

1 participant