Files
wehub-resource-sync 7a0da7932b
OSV-Scanner (Scheduled) / scan-scheduled (push) Failing after 0s
Create Release / test-gate (push) Has been cancelled
Create Release / release-gate (push) Has been cancelled
Create Release / ci-gate (push) Has been cancelled
Create Release / version-check (push) Has been cancelled
Create Release / e2e-test-gate (push) Has been cancelled
Create Release / responsive-test-gate (push) Has been cancelled
Create Release / compat-test-gate (push) Has been cancelled
Create Release / compose-integration-gate (push) Has been cancelled
Create Release / vulture-gate (push) Has been cancelled
Create Release / build (push) Has been cancelled
Create Release / provenance (push) Has been cancelled
Create Release / prerelease-docker (push) Has been cancelled
Create Release / publish-docker (push) Has been cancelled
Create Release / create-release (push) Has been cancelled
Create Release / cleanup-changelog (push) Has been cancelled
Create Release / trigger-pypi (push) Has been cancelled
Create Release / monitor-pypi (push) Has been cancelled
Create Release / Clean up orphan prerelease tags and signatures (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-form] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-metrics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-workflow] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-core] (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [history-news] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [library] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [link-analytics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-core] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-lifecycle] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [error-benchmark] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) (push) Has been cancelled
Docker Tests (Consolidated) / Accessibility Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Unit Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Example Tests (push) Has been cancelled
Docker Tests (Consolidated) / Production Image Smoke Test (push) Has been cancelled
Docker Tests (Consolidated) / Infrastructure Tests (push) Has been cancelled
OSSF Scorecard / OSSF Security Scorecard Analysis (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [mobile] (push) Has been cancelled
Backwards Compatibility / Verify Encryption Constants (push) Has been cancelled
Backwards Compatibility / PyPI Version Compatibility (push) Has been cancelled
Backwards Compatibility / Database Migration Tests (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Docker Tests (Consolidated) / detect-changes (push) Has been cancelled
Docker Tests (Consolidated) / Build Test Image (push) Has been cancelled
Docker Tests (Consolidated) / All Pytest Tests + Coverage (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [accessibility] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [api-crud] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-login] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-register] (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:08:55 +08:00

189 lines
6.0 KiB
Python
Executable File

#!/usr/bin/env python3
"""Require a notes file when a file under ``advanced_search_system/`` is deleted.
Any commit that deletes a ``.py`` file under
``src/local_deep_research/advanced_search_system/`` — or renames one out of
that tree, which removes it from the tracked module — must also add or
modify a ``.md`` file under ``docs/strategies/deleted/`` in the same commit.
Rationale and template live in ``docs/strategies/deleted/README.md``.
Exempt files whose deletion does not by itself remove a component: package
``__init__.py`` aggregators. The exemption list is intentionally narrow; if
a new infra file is added (e.g. a base class in a new location), update
``EXEMPT_FILENAMES`` below. ``base_*.py`` files are NOT exempt — deleting
a base class is a significant refactor that does warrant a notes file.
"""
import subprocess
import sys
from pathlib import PurePosixPath
SCOPE_DIR = "src/local_deep_research/advanced_search_system/"
DOCS_DIR = "docs/strategies/deleted/"
EXEMPT_FILENAMES = {"__init__.py"}
# Suggested notes-file name format. PR number isn't known at commit time
# so we accept a placeholder the author fills in before pushing.
SUGGESTED_FILENAME = "pr-<number>-<short-slug>.md"
TEMPLATE = """\
# PR #<n> — <title>
Components deleted in PR #<n> (see that PR for the full pre-deletion
code — this file only summarises what was novel).
## Component: `<ClassName>`
- File deleted: `<path>` (<N> LOC at deletion).
- Reachability: <e.g. "not in factory; only referenced by its own test">.
- Closest reachable successor: `<ClassName>` (`<path>`, factory key `"<X>"`).
### Useful ideas from the pre-deletion version
- **<short name>** — <1-2 sentences on what it did, why it was
distinctive, whether it was validated>.
- **<short name>** — <...>.
### Why deletion was safe
<2-3 sentences mapping distinctive features to the successor, or
flagging at-risk items and why losing them is acceptable.>
### Recovery path
<1-2 sentences. Prefer "add a flag on the existing class" over
"restore the deleted file".>
"""
def staged_changes():
"""Yield ``(code, old_path, new_path)`` for each staged change.
For add/modify/delete, ``old_path == new_path``. For rename (R) and
copy (C), they differ; uniform unpacking lets callers use one code
path. See ``git diff --name-status`` for the format.
"""
result = subprocess.run(
["git", "diff", "--cached", "--name-status"],
capture_output=True,
text=True,
check=True,
)
for line in result.stdout.splitlines():
if not line.strip():
continue
parts = line.split("\t")
if len(parts) < 2:
continue
code = parts[0][
0
] # A / M / D / R / C (R and C carry a similarity score suffix)
if code in ("R", "C") and len(parts) >= 3:
yield code, parts[1], parts[2]
else:
yield code, parts[1], parts[1]
def _is_exempt(path: str) -> bool:
# .lower() defends against case-insensitive filesystems (macOS, Windows).
return PurePosixPath(path).name.lower() in EXEMPT_FILENAMES
def main() -> int:
deleted_files = []
doc_changes = []
for code, old_path, new_path in staged_changes():
# A rename out of SCOPE_DIR removes the component from the tracked
# tree even though the file survives elsewhere — treat as deletion.
# Renames within SCOPE_DIR are legitimate refactors; don't fire.
is_deletion_from_scope = (
code == "D" and old_path.startswith(SCOPE_DIR)
) or (
code == "R"
and old_path.startswith(SCOPE_DIR)
and not new_path.startswith(SCOPE_DIR)
)
if (
is_deletion_from_scope
and old_path.endswith(".py")
and not _is_exempt(old_path)
):
deleted_files.append(old_path)
if (
code in ("A", "M")
and new_path.startswith(DOCS_DIR)
and new_path.endswith(".md")
and PurePosixPath(new_path).name.lower() != "readme.md"
):
doc_changes.append(new_path)
if deleted_files and not doc_changes:
err = sys.stderr
print(
"\n \033[31madvanced_search_system deletion without documentation\033[0m",
file=err,
)
print(" " + "=" * 50, file=err)
print(
"\n You are deleting (or renaming out of scope) these files:",
file=err,
)
for path in deleted_files:
print(f" - {path}", file=err)
print(
f"\n To unblock this commit, add a notes file at\n"
f" {DOCS_DIR}{SUGGESTED_FILENAME}\n"
f" (or extend an existing one under {DOCS_DIR}) with the following shape:\n",
file=err,
)
for line in TEMPLATE.splitlines():
print(f" {line}" if line else "", file=err)
print(
'\n Each bullet in "Useful ideas" should answer, in 1-2',
file=err,
)
print(
" sentences:",
file=err,
)
print(
" - what the component did that was different from the successor,",
file=err,
)
print(
" - why that difference was interesting (heuristic, tuning, prompt",
file=err,
)
print(
" trick, interface gap),",
file=err,
)
print(
" - and whether it was validated or exploratory.",
file=err,
)
print(
"\n Do NOT paste verbatim prompts, docstrings, or code blocks —",
file=err,
)
print(
" git already stores them via the deletion PR/commit. Link, don't",
file=err,
)
print(
" duplicate.",
file=err,
)
print(
f"\n Full convention + rationale: {DOCS_DIR}README.md\n",
file=err,
)
return 1
return 0
if __name__ == "__main__":
sys.exit(main())