Skip to content

feat(pip): add pyproject.toml support via pip --dry-run --report#465

Open
ruromero wants to merge 4 commits intoguacsec:mainfrom
ruromero:TC-4065
Open

feat(pip): add pyproject.toml support via pip --dry-run --report#465
ruromero wants to merge 4 commits intoguacsec:mainfrom
ruromero:TC-4065

Conversation

@ruromero
Copy link
Copy Markdown
Collaborator

@ruromero ruromero commented Apr 10, 2026

Summary

  • Add Python_pip_pyproject provider that resolves PEP 621 pyproject.toml dependencies using pip install --dry-run --ignore-installed --report
  • Fallback provider when no lock file (uv.lock/poetry.lock) is found — registered after uv/poetry in the provider matching order
  • Supports TRUSTIFY_DA_PIP_REPORT env var override for testing without pip installed
  • Fix _getDependencyData signature in Python_pip_pyproject to match base class 4-param contract (TC-4098)
  • Fix _parsePipReport to only exclude the root entry from the graph, not all dir_info entries — preserves local/path dependencies (TC-4099)

Details

The pip report JSON provides:

  • Resolved versions for all packages
  • requires_dist per package for building the dependency tree
  • requested: true/false for root project identification

Extras-only dependencies (e.g. PySocks; extra == "socks") are filtered out.
Only PEP 621 format ([project.dependencies]) is in scope — poetry-format pyproject.toml should use the lock-file-based provider.

Implements TC-4065

Test plan

  • Stack analysis produces correct SBOM with transitive deps
  • Component analysis produces correct SBOM with direct deps only
  • Direct and transitive deps correctly classified
  • Extras-only deps filtered from tree
  • exhortignore marker excludes deps from both analysis types
  • Name canonicalization (charset_normalizercharset-normalizer)
  • validateLockFile always returns true (fallback)
  • Existing uv/poetry tests unaffected (44 tests passing)

🤖 Generated with Claude Code

@qodo-code-review
Copy link
Copy Markdown

Review Summary by Qodo

Add pip provider for PEP 621 pyproject.toml and workspace support

✨ Enhancement 🧪 Tests

Grey Divider

Walkthroughs

Description
• Add Python_pip_pyproject provider for PEP 621 pyproject.toml without lock files
• Resolve dependencies using pip install --dry-run --ignore-installed --report
• Implement workspace/monorepo support with lock file discovery and boundary detection
• Add comprehensive test coverage for pip provider and workspace scenarios
Diagram
flowchart LR
  A["pyproject.toml<br/>PEP 621 format"] -->|"pip --dry-run<br/>--report"| B["pip report<br/>JSON"]
  B -->|"Parse & build<br/>dependency graph"| C["Python_pip_pyproject<br/>Provider"]
  C -->|"Fallback when<br/>no lock file"| D["SBOM<br/>with transitive deps"]
  E["Workspace detection<br/>uv/poetry markers"] -->|"Walk up directory<br/>tree"| F["Find lock file<br/>or boundary"]
  F -->|"Resolve to<br/>correct lock dir"| G["Accurate analysis<br/>per package"]
Loading

Grey Divider

File Changes

1. src/provider.js ⚙️ Configuration changes +2/-0

Register new pip pyproject provider

src/provider.js


2. src/providers/python_pip_pyproject.js ✨ Enhancement +132/-0

New pip provider for PEP 621 pyproject.toml

src/providers/python_pip_pyproject.js


3. src/providers/base_pyproject.js ✨ Enhancement +68/-3

Add workspace detection and lock file discovery

src/providers/base_pyproject.js


View more (23)
4. src/providers/python_uv.js ✨ Enhancement +32/-2

Support editable installs in workspace members

src/providers/python_uv.js


5. test/providers/python_pyproject.test.js 🧪 Tests +306/-0

Add pip provider and workspace tests

test/providers/python_pyproject.test.js


6. test/providers/tst_manifests/pyproject/pip_pep621/pyproject.toml 🧪 Tests +7/-0

Test fixture for pip PEP 621 project

test/providers/tst_manifests/pyproject/pip_pep621/pyproject.toml


7. test/providers/tst_manifests/pyproject/pip_pep621/pip_report.json 🧪 Tests +62/-0

Mock pip report for testing

test/providers/tst_manifests/pyproject/pip_pep621/pip_report.json


8. test/providers/tst_manifests/pyproject/pip_pep621/expected_stack_sbom.json 🧪 Tests +85/-0

Expected SBOM with transitive dependencies

test/providers/tst_manifests/pyproject/pip_pep621/expected_stack_sbom.json


9. test/providers/tst_manifests/pyproject/pip_pep621/expected_component_sbom.json 🧪 Tests +36/-0

Expected SBOM with direct dependencies only

test/providers/tst_manifests/pyproject/pip_pep621/expected_component_sbom.json


10. test/providers/tst_manifests/pyproject/pip_pep621_ignore/pyproject.toml 🧪 Tests +7/-0

Test fixture with exhortignore marker

test/providers/tst_manifests/pyproject/pip_pep621_ignore/pyproject.toml


11. test/providers/tst_manifests/pyproject/pip_pep621_ignore/pip_report.json 🧪 Tests +62/-0

Mock pip report for ignore test

test/providers/tst_manifests/pyproject/pip_pep621_ignore/pip_report.json


12. test/providers/tst_manifests/pyproject/uv_workspace/pyproject.toml 🧪 Tests +13/-0

UV workspace root with members

test/providers/tst_manifests/pyproject/uv_workspace/pyproject.toml


13. test/providers/tst_manifests/pyproject/uv_workspace/packages/mid-pkg/pyproject.toml 🧪 Tests +9/-0

UV workspace member with dependency

test/providers/tst_manifests/pyproject/uv_workspace/packages/mid-pkg/pyproject.toml


14. test/providers/tst_manifests/pyproject/uv_workspace/packages/sub-pkg/pyproject.toml 🧪 Tests +6/-0

UV workspace leaf package

test/providers/tst_manifests/pyproject/uv_workspace/packages/sub-pkg/pyproject.toml


15. test/providers/tst_manifests/pyproject/uv_workspace/expected_stack_sbom.json 🧪 Tests +198/-0

Expected SBOM for UV workspace root

test/providers/tst_manifests/pyproject/uv_workspace/expected_stack_sbom.json


16. test/providers/tst_manifests/pyproject/poetry_workspace/pyproject.toml 🧪 Tests +8/-0

Poetry workspace root configuration

test/providers/tst_manifests/pyproject/poetry_workspace/pyproject.toml


17. test/providers/tst_manifests/pyproject/poetry_workspace/packages/sub-pkg/pyproject.toml 🧪 Tests +7/-0

Poetry workspace member package

test/providers/tst_manifests/pyproject/poetry_workspace/packages/sub-pkg/pyproject.toml


18. test/providers/tst_manifests/pyproject/poetry_workspace/expected_stack_sbom.json 🧪 Tests +85/-0

Expected SBOM for poetry workspace

test/providers/tst_manifests/pyproject/poetry_workspace/expected_stack_sbom.json


19. test/providers/tst_manifests/pyproject/poetry_workspace/expected_component_sbom.json Additional files +36/-0

...

test/providers/tst_manifests/pyproject/poetry_workspace/expected_component_sbom.json


20. test/providers/tst_manifests/pyproject/poetry_workspace/packages/sub-pkg/expected_component_sbom.json Additional files +36/-0

...

test/providers/tst_manifests/pyproject/poetry_workspace/packages/sub-pkg/expected_component_sbom.json


21. test/providers/tst_manifests/pyproject/poetry_workspace/packages/sub-pkg/expected_stack_sbom.json Additional files +85/-0

...

test/providers/tst_manifests/pyproject/poetry_workspace/packages/sub-pkg/expected_stack_sbom.json


22. test/providers/tst_manifests/pyproject/uv_workspace/expected_component_sbom.json Additional files +48/-0

...

test/providers/tst_manifests/pyproject/uv_workspace/expected_component_sbom.json


23. test/providers/tst_manifests/pyproject/uv_workspace/packages/mid-pkg/expected_component_sbom.json Additional files +36/-0

...

test/providers/tst_manifests/pyproject/uv_workspace/packages/mid-pkg/expected_component_sbom.json


24. test/providers/tst_manifests/pyproject/uv_workspace/packages/mid-pkg/expected_stack_sbom.json Additional files +98/-0

...

test/providers/tst_manifests/pyproject/uv_workspace/packages/mid-pkg/expected_stack_sbom.json


25. test/providers/tst_manifests/pyproject/uv_workspace/packages/sub-pkg/expected_component_sbom.json Additional files +36/-0

...

test/providers/tst_manifests/pyproject/uv_workspace/packages/sub-pkg/expected_component_sbom.json


26. test/providers/tst_manifests/pyproject/uv_workspace/packages/sub-pkg/expected_stack_sbom.json Additional files +85/-0

...

test/providers/tst_manifests/pyproject/uv_workspace/packages/sub-pkg/expected_stack_sbom.json


Grey Divider

Qodo Logo

@qodo-code-review
Copy link
Copy Markdown

qodo-code-review bot commented Apr 10, 2026

Code Review by Qodo

🐞 Bugs (4)   📘 Rule violations (0)   📎 Requirement gaps (0)   🎨 UX Issues (0)
🐞\ ≡ Correctness (2) ☼ Reliability (2)

Grey Divider


Action required

1. Poetry uses wrong project 🐞
Description
Base_pyproject now calls _getDependencyData() using lockFileDir rather than the provided manifest’s
directory, but Python_poetry derives its dependency data only from that passed directory and does
not use the parsed member pyproject. When a workspace member pyproject.toml relies on a poetry.lock
in a parent directory, dependency resolution can be performed against the parent project instead of
the requested member manifest, producing an incorrect SBOM.
Code

src/providers/base_pyproject.js[R351-352]

+		let lockFileDir = this._findLockFileDir(manifestDir, opts) || manifestDir
+		let { directDeps, graph } = await this._getDependencyData(lockFileDir, parsed, opts)
Evidence
_createSbom() reads/parses the requested manifest, but then switches the dependency-resolution
working directory to the lockfile directory (lockFileDir) before calling _getDependencyData(). The
Poetry provider ignores the parsed manifest entirely and runs its dependency commands using only the
passed manifestDir/cwd, so when Base_pyproject passes lockFileDir, Python_poetry has no way to scope
resolution to the originally requested member manifest.

src/providers/base_pyproject.js[346-353]
src/providers/python_poetry.js[17-42]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`Base_pyproject._createSbom()` now calls `_getDependencyData()` with `lockFileDir`, which changes the effective project context for providers that derive dependency data from the working directory (notably `Python_poetry`). This can cause member-manifest analysis to resolve dependencies for the lockfile directory’s project instead of the requested `pyproject.toml`.

## Issue Context
- `Base_pyproject._createSbom()` parses the requested manifest, but passes `lockFileDir` into `_getDependencyData()`.
- `Python_poetry._getDependencyData()` ignores `parsed` and uses `manifestDir` to run `poetry show ...`.

## Fix Focus Areas
- src/providers/base_pyproject.js[346-353]
- src/providers/python_poetry.js[17-42]

## Suggested fix approach
- Change the contract so providers receive both:
 - `projectDir` (directory of the requested manifest)
 - `lockFileDir` (directory where the lock file was found)
- Update `Base_pyproject._createSbom()` to call something like `_getDependencyData({ projectDir: manifestDir, lockFileDir }, parsed, opts)` (or add an extra parameter).
- For `Python_poetry`, ensure commands are executed in `projectDir` (or explicitly point Poetry at the requested project if it supports a directory flag), while still using the correct lockfile location as needed.
- Keep `Python_uv` behavior using `lockFileDir` if that’s required for workspace export, but do so explicitly via the new parameters.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Pip root misidentified 🐞
Description
Python_pip_pyproject identifies the root entry by checking download_info.dir_info and excludes all
dir_info entries from the graph, which can misidentify the root and drop local/path dependencies.
Pip reports already contain a requested flag suitable for reliable root identification, so the
current approach can produce an incorrect dependency tree and wrong direct/transitive
classification.
Code

src/providers/python_pip_pyproject.js[R63-75]

+		let rootEntry = packages.find(p => p.download_info?.dir_info !== undefined)
+		let rootRequires = rootEntry?.metadata?.requires_dist || []
+
+		let directDepNames = new Set()
+		for (let req of rootRequires) {
+			if (this._hasExtraMarker(req)) { continue }
+			let name = this._extractDepName(req)
+			if (name) { directDepNames.add(this._canonicalize(name)) }
+		}
+
+		let graph = new Map()
+		let nonRootPackages = packages.filter(p => p.download_info?.dir_info === undefined)
+
Evidence
The implementation selects the root via packages.find(...dir_info...) and builds the graph only
from packages where dir_info is undefined. The recorded pip report fixture demonstrates that pip
provides an explicit requested boolean for entries, but the implementation does not use it;
relying on dir_info is not unique to the root if local/path dependencies exist.

src/providers/python_pip_pyproject.js[59-99]
test/providers/tst_manifests/pyproject/pip_pep621/pip_report.json[4-13]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`Python_pip_pyproject._parsePipReport()` treats the first `download_info.dir_info` entry as the root and removes all `dir_info` entries from the dependency graph. This can misidentify the root project and omit legitimate local/path dependencies.

## Issue Context
Pip report entries contain a `requested` flag (seen in the repo’s pip report fixture) that can be used to identify the root request more reliably than `dir_info`.

## Fix Focus Areas
- src/providers/python_pip_pyproject.js[59-99]
- test/providers/tst_manifests/pyproject/pip_pep621/pip_report.json[4-13]

## Suggested fix approach
- Identify the root entry using `requested: true` (and/or matching the parsed project name), not `dir_info`.
- Build the graph from all non-root packages, including those with `dir_info` (local/path deps), instead of excluding them wholesale.
- Only exclude the actual root project from the graph, not every `dir_info` entry.
- Add/extend tests with a pip report containing a local/path dependency that also has `dir_info` to ensure it stays in the graph and is correctly classified.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

3. Poetry boundary not enforced 🐞
Description
Base_pyproject only treats a Poetry workspace boundary as present when both [tool.poetry] and
poetry.lock exist, so a Poetry project root without poetry.lock will not stop the upward search.
This can cause lockfile discovery to continue into parent directories and potentially select an
unrelated poetry.lock.
Code

src/providers/base_pyproject.js[R95-97]

+			if (content.tool?.poetry && fs.existsSync(path.join(dir, 'poetry.lock'))) {
+				return true
+			}
Evidence
_findLockFileDir() stops searching when _isWorkspaceRoot() returns true; for Poetry,
_isWorkspaceRoot() currently requires both content.tool?.poetry and an on-disk poetry.lock. In
contrast, the JavaScript workspace boundary logic stops at workspace markers even without a lockfile
present, preventing accidental walk-up into unrelated parent projects.

src/providers/base_pyproject.js[77-101]
src/providers/base_javascript.js[114-143]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`Base_pyproject._isWorkspaceRoot()` only considers a Poetry workspace boundary when `[tool.poetry]` is present *and* `poetry.lock` exists. If a Poetry root is missing its lock file, `_findLockFileDir()` may walk above the project root and accidentally match a parent directory’s lock file.

## Issue Context
Other ecosystems in this repo treat workspace markers as hard boundaries even when the lock file is absent.

## Fix Focus Areas
- src/providers/base_pyproject.js[77-101]

## Suggested fix approach
- Treat the presence of `[tool.poetry]` in `pyproject.toml` as a workspace boundary regardless of whether `poetry.lock` exists.
- Keep the existing uv boundary behavior.
- If you still need the current behavior for some reason, add a guard to prevent escaping beyond the nearest directory containing a `pyproject.toml` with `[tool.poetry]`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


4. Editable TOML can crash 🐞
Description
Python_uv parses a member pyproject.toml for editable installs without guarding against TOML parse
failures. A malformed or unreadable member pyproject.toml will throw and abort dependency parsing
for the whole project.
Code

src/providers/python_uv.js[R71-88]

+			if (child.type === 'global_opt') {
+				let optNode = child.children.find(c => c.type === 'option')
+				let pathNode = child.children.find(c => c.type === 'path')
+				if (optNode?.text === '-e' && pathNode && manifestDir) {
+					let memberDir = path.resolve(manifestDir, pathNode.text)
+					let memberManifest = path.join(memberDir, 'pyproject.toml')
+					if (fs.existsSync(memberManifest)) {
+						let memberParsed = parseToml(fs.readFileSync(memberManifest, 'utf-8'))
+						let name = memberParsed.project?.name || memberParsed.tool?.poetry?.name
+						let version = memberParsed.project?.version || memberParsed.tool?.poetry?.version
+						if (name && version) {
+							let key = this._canonicalize(name)
+							currentPkg = { name, version, parents: new Set() }
+							packages.set(key, currentPkg)
+							collectingVia = false
+							continue
+						}
+					}
Evidence
The new editable-install handling reads and parses memberManifest with parseToml(...) without a
try/catch, unlike Base_pyproject’s similar TOML parsing which explicitly catches parse errors. This
makes uv workspace parsing fragile when encountering a bad member manifest.

src/providers/python_uv.js[70-89]
src/providers/base_pyproject.js[90-101]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`Python_uv._parseUvExport()` attempts to parse editable member `pyproject.toml` without error handling. Any TOML parse error will throw and fail the entire analysis.

## Issue Context
`Base_pyproject._isWorkspaceRoot()` already demonstrates the preferred pattern: wrap TOML parsing in a try/catch and treat failures as non-fatal.

## Fix Focus Areas
- src/providers/python_uv.js[70-89]

## Suggested fix approach
- Wrap `parseToml(fs.readFileSync(...))` in a try/catch.
- On failure, fall back to the existing behavior for that node (e.g., set `currentPkg = null` and continue), rather than throwing.
- Consider also guarding `fs.readFileSync` in case of permissions or transient FS issues.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

Comment on lines +351 to +352
let lockFileDir = this._findLockFileDir(manifestDir, opts) || manifestDir
let { directDeps, graph } = await this._getDependencyData(lockFileDir, parsed, opts)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

1. Poetry uses wrong project 🐞 Bug ≡ Correctness

Base_pyproject now calls _getDependencyData() using lockFileDir rather than the provided manifest’s
directory, but Python_poetry derives its dependency data only from that passed directory and does
not use the parsed member pyproject. When a workspace member pyproject.toml relies on a poetry.lock
in a parent directory, dependency resolution can be performed against the parent project instead of
the requested member manifest, producing an incorrect SBOM.
Agent Prompt
## Issue description
`Base_pyproject._createSbom()` now calls `_getDependencyData()` with `lockFileDir`, which changes the effective project context for providers that derive dependency data from the working directory (notably `Python_poetry`). This can cause member-manifest analysis to resolve dependencies for the lockfile directory’s project instead of the requested `pyproject.toml`.

## Issue Context
- `Base_pyproject._createSbom()` parses the requested manifest, but passes `lockFileDir` into `_getDependencyData()`.
- `Python_poetry._getDependencyData()` ignores `parsed` and uses `manifestDir` to run `poetry show ...`.

## Fix Focus Areas
- src/providers/base_pyproject.js[346-353]
- src/providers/python_poetry.js[17-42]

## Suggested fix approach
- Change the contract so providers receive both:
  - `projectDir` (directory of the requested manifest)
  - `lockFileDir` (directory where the lock file was found)
- Update `Base_pyproject._createSbom()` to call something like `_getDependencyData({ projectDir: manifestDir, lockFileDir }, parsed, opts)` (or add an extra parameter).
- For `Python_poetry`, ensure commands are executed in `projectDir` (or explicitly point Poetry at the requested project if it supports a directory flag), while still using the correct lockfile location as needed.
- Keep `Python_uv` behavior using `lockFileDir` if that’s required for workspace export, but do so explicitly via the new parameters.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

[sdlc-workflow/verify-pr] Classified as code change request — sub-task TC-4098 created to address this feedback.

Comment on lines +63 to +75
let rootEntry = packages.find(p => p.download_info?.dir_info !== undefined)
let rootRequires = rootEntry?.metadata?.requires_dist || []

let directDepNames = new Set()
for (let req of rootRequires) {
if (this._hasExtraMarker(req)) { continue }
let name = this._extractDepName(req)
if (name) { directDepNames.add(this._canonicalize(name)) }
}

let graph = new Map()
let nonRootPackages = packages.filter(p => p.download_info?.dir_info === undefined)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

2. Pip root misidentified 🐞 Bug ≡ Correctness

Python_pip_pyproject identifies the root entry by checking download_info.dir_info and excludes all
dir_info entries from the graph, which can misidentify the root and drop local/path dependencies.
Pip reports already contain a requested flag suitable for reliable root identification, so the
current approach can produce an incorrect dependency tree and wrong direct/transitive
classification.
Agent Prompt
## Issue description
`Python_pip_pyproject._parsePipReport()` treats the first `download_info.dir_info` entry as the root and removes all `dir_info` entries from the dependency graph. This can misidentify the root project and omit legitimate local/path dependencies.

## Issue Context
Pip report entries contain a `requested` flag (seen in the repo’s pip report fixture) that can be used to identify the root request more reliably than `dir_info`.

## Fix Focus Areas
- src/providers/python_pip_pyproject.js[59-99]
- test/providers/tst_manifests/pyproject/pip_pep621/pip_report.json[4-13]

## Suggested fix approach
- Identify the root entry using `requested: true` (and/or matching the parsed project name), not `dir_info`.
- Build the graph from all non-root packages, including those with `dir_info` (local/path deps), instead of excluding them wholesale.
- Only exclude the actual root project from the graph, not every `dir_info` entry.
- Add/extend tests with a pip report containing a local/path dependency that also has `dir_info` to ensure it stays in the graph and is correctly classified.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

[sdlc-workflow/verify-pr] Classified as code change request — sub-task TC-4099 created to address this feedback.

@ruromero
Copy link
Copy Markdown
Collaborator Author

ruromero commented Apr 14, 2026

Verification Report — TC-4065

Check Result Details
Review Feedback ⚠️ WARN 2 code change requests → sub-tasks TC-4098, TC-4099
Root-Cause Investigation ⚠️ WARN 1 convention gap (TC-4098: base class directory semantics undocumented), 1 skill gap (TC-4099: broad filter removed all dir_info entries instead of just root)
Scope Containment ⚠️ WARN PR includes TC-4039 workspace support files (out of scope for TC-4065)
Diff Size ⚠️ WARN +2064 lines — includes changes from both TC-4039 and TC-4065
Commit Traceability ⚠️ WARN Commit 1 references TC-4039 only; TC-4065 changes are in commit 2
Sensitive Patterns ✅ PASS No credentials, tokens, or secrets detected
CI Status ⚠️ WARN Pre-existing infrastructure failure (node-gyp build error on @aspect-dev/snoopy — unrelated to this PR)
Acceptance Criteria ✅ PASS 6/6 criteria satisfied
Test Quality ✅ PASS Tests follow project conventions (Mocha TDD/Chai), have doc comments, cover all acceptance criteria
Verification Commands N/A None specified in task

Root-Cause Analysis

TC-4098 — _createSbom passes lockFileDir instead of projectDir

  • Classification: Convention gap
  • The base class _createSbom() was designed for lock-file providers (uv/poetry) where lockFileDir is the correct argument. The pip fallback provider needs the project directory (where pyproject.toml lives) because pip install . runs relative to CWD. This architectural distinction is not documented in CONVENTIONS.md.
  • Recommendation: Document the directory semantics in CONVENTIONS.md — lock-file providers receive the lock file directory, while non-lock-file providers need the project directory.

TC-4099 — _parsePipReport excludes all dir_info entries

  • Classification: Skill gap (implement-task phase)
  • The task description correctly states "Skip the root project entry" (singular), but the implementation used packages.filter(p => p.download_info?.dir_info === undefined) which removes ALL entries with dir_info, not just the root. The Java implementation correctly handles this by only skipping the first dir_info entry.
  • Phase: implement-task — the task description was accurate but the implementation used a broad filter instead of targeting only the first match.

Overall: ❌ FAIL

Blocking issues:

  1. Two code change requests require sub-tasks before merge
  2. Commit traceability partially missing (commit 1 covers TC-4039 only)

This comment was AI-generated by sdlc-workflow/verify-pr v0.5.11.

@ruromero ruromero changed the base branch from main to TC-4039 April 14, 2026 09:08
Add Python_pip_pyproject provider that resolves PEP 621 pyproject.toml
dependencies using `pip install --dry-run --ignore-installed --report`.
This is the fallback provider when no lock file (uv.lock/poetry.lock)
is found, enabling analysis of standard pyproject.toml projects
without requiring uv or poetry.

The pip report JSON provides resolved versions, requires_dist for
building the dependency tree, and requested flags for direct vs
transitive classification. Extras-only deps are filtered out.

Supports TRUSTIFY_DA_PIP_REPORT env var for testing without pip.

Implements TC-4065

Assisted-by: Claude Code
@ruromero ruromero changed the base branch from TC-4039 to main April 14, 2026 09:31
@ruromero
Copy link
Copy Markdown
Collaborator Author

ruromero commented Apr 14, 2026

Verification Report for TC-4065 (commit 7a2cffa)

Check Result Details
Review Feedback ⚠️ WARN 2 code change requests from prior run → sub-tasks TC-4098, TC-4099
Root-Cause Investigation DONE Convention gap (TC-4102: document directory semantics), Skill gap (TC-4103: filter uniqueness for singular entries)
Scope Containment ✅ PASS 9 files, all within task scope
Diff Size ✅ PASS +515/-0, 9 files — proportionate for new provider + tests + fixtures
Commit Traceability ✅ PASS Single commit with "Implements TC-4065"
Sensitive Patterns ✅ PASS No credentials or secrets (env var references are false positives)
CI Status ⚠️ WARN PR title and commit message checks pass; lint/test workflow pending on rebased commit
Acceptance Criteria ✅ PASS 6/6 criteria satisfied
Test Quality ✅ PASS All 8 pip tests have doc comments, no repetitive parameterization candidates
Verification Commands N/A None specified in task

Overall: ⚠️ WARN

The PR is clean after rebase — single commit, correct scope, all acceptance criteria met. Two blocking sub-tasks (TC-4098, TC-4099) from review feedback remain open. CI lint/test workflow has not yet run on the rebased commit (7a2cffa).


This comment was AI-generated by sdlc-workflow/verify-pr v0.5.11.

…se class

Python_pip_pyproject._getDependencyData() had only 3 parameters
(manifestDir, parsed, opts) instead of the 4 defined by the base class
(manifestDir, workspaceDir, parsed, opts). When called from _createSbom()
with 4 arguments, workspaceDir was silently received as parsed, and
parsed was received as opts, causing argument misalignment.

Add the missing _workspaceDir parameter to match the base class contract
and sibling providers (python_uv, python_poetry).

Implements TC-4098

Assisted-by: Claude Code
…ir_info entries

_parsePipReport() filtered nonRootPackages by checking
p.download_info?.dir_info === undefined, which excluded ALL packages
with dir_info (including local/path dependencies). Changed to filter
by identity (p !== rootEntry) so only the specific root project entry
is excluded, matching the Java implementation's behavior.

Implements TC-4099

Assisted-by: Claude Code
@ruromero
Copy link
Copy Markdown
Collaborator Author

/review

Extract shared SBOM_CASES constant and use forEach loops to eliminate
repetitive stack/component test pairs across uv, poetry, and pip suites.
Parameterize validateLockFile and workspace tests similarly. Add JSDoc
comments to all 39 test functions.

Implements TC-4065

Co-Authored-By: Claude Opus 4.6 <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.

1 participant