Skip to content

fix(roles): recognize pyproject.toml console-script entrypoints - #2535

Merged
carlos-alm merged 3 commits into
mainfrom
fix/issue-2408
Aug 16, 2026
Merged

fix(roles): recognize pyproject.toml console-script entrypoints#2535
carlos-alm merged 3 commits into
mainfrom
fix/issue-2408

Conversation

@carlos-alm

Copy link
Copy Markdown
Contributor

Summary

Closes #2408

A Python project whose only program entrypoint is a packaging-declared console script ([project.scripts], [project.gui-scripts], or [tool.poetry.scripts] in pyproject.toml) had neither an if __name__ == "__main__": guard nor a __main__.py, so #2392's role classification still reported zero entry symbols for it — exactly the "where does this start?" question a new agent asks first.

  • pyproject.toml is re-parsed fresh on every build (no per-file evidence table needed, unlike guard-call attribution) — it's a single, cheap-to-reread file, so attribution runs unconditionally each build and self-corrects when the declared scripts change.
  • Attribution is scoped to entrypoint_source_file = 'pyproject.toml', so it takes precedence over — but never clobbers — a target already attributed by guard-call evidence.
  • Resolution reuses the existing resolvePythonImportPath/resolve_python_import_path machinery via a synthetic <rootDir>/pyproject.toml anchor, so src/ layouts, flat layouts, and pyproject.toml-configured import roots all resolve correctly with no new resolution logic.
  • Mirrored in both engines: TS (resolve.ts, builder/entrypoints.ts, builder/stages/build-edges.ts, builder/incremental.ts) and Rust (resolve.rs, builder/entrypoints.rs, builder/pipeline.rs).
  • While building this out I found and removed a stale per-root_dir cache (_pyprojectScriptsCache / pyproject_scripts_cache) that had no repeat-call benefit (this resolver runs at most once per build) but did introduce real staleness across repeated buildGraph/native-pipeline calls in the same process — removed rather than patched, since the function is already cheap enough to just re-read every call.
  • Also fixed an unrelated stray NUL byte that had crept into a template literal in resolve.ts, which was silently making every grep/ugrep search against that file return nothing (file treated as binary).

Test plan

  • New Rust unit tests: resolve_pyproject_script_entrypoints (6 tests: src-layout resolution, gui/poetry tables, table-shaped-entry skip, dedup, missing/no-scripts, unresolvable module) and apply_pyproject_script_attribution (6 tests: marks, clears stale, no-clobber, precedence, empty case)
  • New TS unit tests mirroring the above in tests/unit/resolve.test.ts
  • New dual-engine (wasm/native) integration test tests/integration/issue-2408-pyproject-script-entrypoints.test.ts: classification for all three script tables, non-clobbering, precedence, and incremental-rebuild staleness clearing
  • Manually verified native ⇄ WASM parity on a scratch fixture (entrypoint=1, role='entry', entrypoint_source_file='pyproject.toml' on both engines)
  • Revert-verify: confirmed every new test fails against the pre-fix source, then passes again once restored
  • Full suite green: cargo test (1056 passed), cargo clippy -- -D warnings, cargo fmt --check, npm test (5351 passed), npm run lint, codegraph diff-impact --staged -T

A Python project whose only entrypoint is a packaging-declared console
script ([project.scripts], [project.gui-scripts], or
[tool.poetry.scripts]) had no guard call and no __main__.py, so #2392's
role classification still reported zero entry symbols for it.

pyproject.toml is re-parsed fresh on every build rather than cached as
per-file evidence like a guard call, since it's a single cheap-to-reread
file — attribution runs unconditionally each build and self-corrects
when the declared scripts change. Attribution is scoped to
entrypoint_source_file = 'pyproject.toml' so it takes precedence over,
but never clobbers, a guard-attributed target.

docs check acknowledged

Impact: 12 functions changed, 15 affected
@greptile-apps

greptile-apps Bot commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR adds console-script entrypoint recognition from pyproject.toml across the TypeScript/WASM and native Rust graph builders, including fresh manifest resolution during incremental rebuilds.

  • Resolves [project.scripts], [project.gui-scripts], and [tool.poetry.scripts] targets through existing Python import resolution.
  • Projects resolved targets into entrypoint metadata and role classification while preserving guard-call attribution.
  • Clears Python resolution caches before deletion-triggered watch refreshes, addressing the previously reported stale-root path.
  • Adds mirrored unit and integration coverage across native and WASM engines.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains; the previously reported deletion-path stale-root issue is addressed by clearing both TypeScript and native Python resolution caches before fresh attribution, with old and new target files included in role reclassification.

Important Files Changed

Filename Overview
src/domain/graph/builder/incremental.ts Clears Python import-root caches before every watch-rebuild path that refreshes script attribution, including deletion and post-purge exits.
src/domain/graph/builder/entrypoints.ts Adds scoped pyproject script attribution, stale-state clearing, precedence handling, and touched-file role refresh support.
src/domain/graph/resolve.ts Parses and resolves supported pyproject script tables through existing Python import-path machinery.
crates/codegraph-core/src/domain/graph/builder/entrypoints.rs Mirrors TypeScript script attribution and stale-state handling in the native engine.
crates/codegraph-core/src/domain/graph/builder/pipeline.rs Clears native Python resolution caches per build and applies script attribution before role classification.
crates/codegraph-core/src/domain/graph/resolve.rs Mirrors pyproject script parsing and Python target resolution in Rust.
tests/integration/issue-2408-pyproject-script-entrypoints.test.ts Covers dual-engine classification, precedence, stale attribution removal, and the previously reported deletion-path cache issue.

Sequence Diagram

sequenceDiagram
  participant Build as Build/Watch Rebuild
  participant Cache as Python Root Caches
  participant Manifest as pyproject.toml
  participant Resolver as Python Resolver
  participant DB as Graph Database
  participant Roles as Role Classifier
  Build->>Cache: Clear configured and package roots
  Build->>Manifest: Parse script declarations
  Manifest->>Resolver: module:attribute targets
  Resolver->>DB: Locate matching function or method nodes
  DB->>DB: Clear stale and mark current attribution
  DB->>Roles: Reclassify touched target files
Loading

Reviews (3): Last reviewed commit: "fix(roles): clear Python import-root cac..." | Re-trigger Greptile

Comment thread src/domain/graph/builder/incremental.ts
@github-actions

Copy link
Copy Markdown
Contributor

Codegraph Impact Analysis

8 functions changed15 callers affected across 8 files

  • applyPyprojectScriptAttribution in src/domain/graph/builder/entrypoints.ts:238 (6 transitive callers)
  • refreshEntrypointAttribution in src/domain/graph/builder/incremental.ts:2282 (3 transitive callers)
  • rebuildFile in src/domain/graph/builder/incremental.ts:2336 (4 transitive callers)
  • applyEntrypointAttribution in src/domain/graph/builder/stages/build-edges.ts:1247 (3 transitive callers)
  • buildEdges in src/domain/graph/builder/stages/build-edges.ts:3061 (4 transitive callers)
  • parsePyprojectScripts in src/domain/graph/resolve.ts:959 (4 transitive callers)
  • addFrom in src/domain/graph/resolve.ts:972 (3 transitive callers)
  • resolvePyprojectScriptEntrypoints in src/domain/graph/resolve.ts:1018 (5 transitive callers)

…wn_files fixtures

Three new #2408 unit tests built known_files entries with a hardcoded
"/" join against a real std::env::temp_dir() root, which is
backslash-separated on Windows. file_exists() normalizes the candidate
path it checks but not the known_files set itself, so the mismatched
separators made every lookup miss on windows-2022 CI (0 resolved
instead of the expected count). Fixed by running the constructed
entries through normalize_known_files(), matching how real callers
already avoid this per its own doc comment.

docs check acknowledged
…exit

Greptile review: rebuildFile's two early-return branches (file
deleted, no file-node-row) called refreshEntrypointAttribution before
the common path's clearPythonImportRootsCache() call further down, so
a long-lived watch session whose pyproject.toml root config changed
would resolve script declarations against stale cached roots on those
paths — wrongly clearing (or failing to mark) a still-valid target.
Clearing first on every exit path, not just the common one, closes
the gap; added a regression test isolating the configured-roots cache
specifically (two pre-parsed files under different roots, switching
which one pythonpath names).

Mirrored the identical gap in the native full-build pipeline: it calls
resolve_imports_batch directly, bypassing the NAPI resolve_imports
wrapper's own cache-clearing (same reasoning as the pre-existing
Cargo/exports-cache clears there for #2060/#2217), so a repeated
native full build in the same process had the same exposure.

docs check acknowledged

Impact: 1 functions changed, 4 affected
@carlos-alm

Copy link
Copy Markdown
Contributor Author

@greptileai

@carlos-alm
carlos-alm merged commit 394f48d into main Aug 16, 2026
30 checks passed
@carlos-alm
carlos-alm deleted the fix/issue-2408 branch August 16, 2026 09:56
@github-actions github-actions Bot locked and limited conversation to collaborators Aug 16, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(roles): recognize pyproject [project.scripts] console-script entrypoints

1 participant