Skip to content

fix(detect): gate the bare out dir on build-output evidence (#3347) - #3424

Open
abhay-codes07 wants to merge 2 commits into
Graphify-Labs:v8from
abhay-codes07:fix/out-dir-evidence
Open

fix(detect): gate the bare out dir on build-output evidence (#3347)#3424
abhay-codes07 wants to merge 2 commits into
Graphify-Labs:v8from
abhay-codes07:fix/out-dir-evidence

Conversation

@abhay-codes07

Copy link
Copy Markdown
Contributor

Closes #3347.

The problem

_SKIP_DIRS contains the bare name out, matched unconditionally — so in a hexagonal / ports-and-adapters codebase, adapter/out/ and port/out/ (the entire outbound layer: persistence adapters, entities, outbound ports) are silently dropped from the scan. On the reporting repo that was 196 of 655 .java files — 30%, including every @Entity class — with exit code 0 and no warning.

_is_noise_dir already gates three ambiguous names on corroborating evidence, keeping the directory when it cannot verify: env/.env/*_env on venv markers (#2058), coverage on report artifacts (#2339), snapshots on .snap files (#1666). out got none of it.

The change

out becomes the fourth gated name. _has_build_output_markers prunes it only on real generated-output evidence:

  • the IntelliJ compile layout (out/production/ subdir);
  • a Next.js static export (out/_next/ subtree);
  • compiled artifacts (.class, .jar, .o, .obj, .exe, .dll, .pyc, .js.map, .css.map, .d.ts) in the top two directory levels — a bounded os.scandir probe, same cost profile as the venv/coverage gates.

An out that cannot be verified is kept — the same keep-on-doubt bias every other gated name has, and pruned-as-noise dirs stay traceable via pruned_noise_dirs (#2058). build, dist and target remain unconditional; the wider question of gating those is #2479's, and this PR deliberately stays on the out case where the dropped set is an architectural layer under a mainstream source convention rather than an incidental directory.

Tests

tests/test_out_dir_evidence.py — 5 tests: adapter/out/ and port/out/ holding only source stay in the corpus (the issue's shape, and the test that fails
on the unpatched tree); IntelliJ out/production/, a next export out/, and a TS outDir with .js.map one level down are all still pruned; and build/dist/target stay unconditionally pruned. The detect suites are unchanged (584 passed; the two failing hook tests fail identically on clean v8 on this Windows machine); the full suite matches a fresh same-version v8 (0.9.56) baseline.

🤖 Generated with Claude Code

https://claude.ai/code/session_01JJbLfztSxm2tH5cJwBbe9q

Copilot AI lite review requested due to automatic review settings September 8, 2026 18:23

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@graphify-labs graphify-labs 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.

Graphify reviewed this change.

Worth a look — the grounded gate found no coupling regressions or blocking issues, but 2 advisory finding(s) below merit a look before merge.

Formal verification. No changes could be formally verified in this run.


Graphify review — findings

Gates pruning of a bare out/ directory on actual build-output evidence instead of removing it by name, so hexagonal codebases' adapter/out/ and port/out/ source layers stay in the scan. A new _has_build_output_markers prunes only when it finds an IntelliJ production/ layout, a Next.js _next/ export tree, or compiled artifacts (.class, .js.map, .d.ts, etc.) in the top two levels, and keeps the directory when the parent can't be verified. build, dist, and target still prune unconditionally.

Worth a look

  • Build markers after the first 20 subdirectories are ignoredgraphify/detect.py · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • Bare out directories are no longer excluded from detect resultsgraphify/detect.py:830 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 2165 functions depend on the 134 functions this change touches.

Health — this change adds coupling hotspots:

  • new: extract() — 559 callers, 43 callees
  • new: _rebuild_code() — 115 callers, 51 callees
  • new: detect() — 110 callers, 15 callees
  • new: _extract_generic() — 18 callers, 26 callees
  • new: save_manifest() — 40 callers, 11 callees
  • new: extract_files_direct() — 17 callers, 20 callees
  • new: extract_js() — 85 callers, 4 callees
  • new: extract_xaml() — 19 callers, 17 callees
  • …and 44 more — each is listed as a finding

Verification — 2165 functions in the blast radius were not formally verified this run (proofs are advisory here).

Gate & verification

graphify gate

PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.

Advisory (not blocking):

  • verification_scope: 775 function(s) in the blast radius were not formally verified this run

Test selection

Test selection

51 of 260 test file(s) selected (20%) via static blast radius.

  • tests/test_astro_extraction.py — impact
  • tests/test_atomic_writes.py — impact
  • tests/test_build.py — impact
  • tests/test_charmap_encoding.py — impact
  • tests/test_chunking.py — impact
  • tests/test_cjs_module_extension.py — impact
  • tests/test_claude_cli_backend.py — impact
  • tests/test_cpp_nested_and_cli.py — impact
  • tests/test_detect.py — impact
  • tests/test_dotnet.py — impact
  • tests/test_evidence_binding.py — impact
  • tests/test_extract.py — impact
  • tests/test_extract_cli.py — impact
  • tests/test_file_slice.py — impact
  • tests/test_ignore_file_encoding.py — impact
  • tests/test_image_vision.py — impact
  • tests/test_import_extension_resolution.py — impact
  • tests/test_incremental_mtime_collision.py — impact
  • tests/test_indirect_dispatch.py — impact
  • tests/test_indirect_dispatch_assign_return.py — impact
  • tests/test_indirect_dispatch_getattr.py — impact
  • tests/test_js_exported_scalar_bindings.py — impact
  • tests/test_languages.py — impact
  • tests/test_llm_backends.py — impact
  • tests/test_long_path_hashing.py — impact
  • tests/test_manifest_ingest.py — impact
  • tests/test_mcp_ingest.py — impact
  • tests/test_multilang.py — impact
  • tests/test_non_regular_files.py — impact
  • tests/test_office_incremental.py — impact
  • tests/test_office_limits.py — impact
  • tests/test_ollama.py — impact
  • tests/test_out_dir_evidence.py — impact, changed-test
  • tests/test_oversized_document_slicing.py — impact
  • tests/test_package_json_subpath_imports.py — impact
  • tests/test_pdf_slicing.py — impact
  • tests/test_pdf_token_estimate.py — impact
  • tests/test_phantom_external_import.py — impact
  • tests/test_pipeline.py — impact
  • tests/test_python_underscore_resolution.py — impact
  • tests/test_rationale.py — impact
  • tests/test_ruby_resolution.py — impact
  • tests/test_scala_self_type.py — impact
  • tests/test_stale_prune.py — impact
  • tests/test_swift_computed_properties.py — impact
  • tests/test_ts_new_expression_calls.py — impact
  • tests/test_typescript_module_extensions.py — impact
  • tests/test_unmapped_at_alias_resolution.py — impact
  • tests/test_vue_extraction.py — impact
  • tests/test_watch.py — impact
  • … and 1 more

Selection is safe under the controlled-regression assumption; always-run tests + a periodic full run are the backstops. Advisory — it never changes the check verdict.

Formal verification

Could not verify: Could not verify \_is\_noise\_dir.

The verifier did not have enough to check \_is\_noise\_dir, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: parameter `parent` is annotated `'Path | None'` — outside the synthesizable primitive/collection set

· 52 more finding(s) on lines outside this diff (see the check run).

…the first 20

Review follow-up: a wide TS outDir keeping compiled files only under a
late-sorted module dir slipped past the 20-subdirectory cap. The level-two
probe now covers every subdirectory, bounded by total entries scanned.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JJbLfztSxm2tH5cJwBbe9q
@abhay-codes07

Copy link
Copy Markdown
Contributor Author

On the two advisories: the 20-subdirectory cap was a fair catch — ff0a8f6 replaces it with a probe over every subdirectory bounded by total entries scanned (4000), with a test putting the only .js.map under the 31st-sorted subdir. The second finding describes the PR's intended behavior rather than a defect: an unverifiable out is now kept on purpose, matching the keep-on-doubt bias the env/coverage/snapshots gates already have — the trade is discussed in the PR body, and pruned dirs stay traceable via pruned_noise_dirs.

@graphify-labs graphify-labs 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.

Graphify reviewed this change.

Worth a look — the grounded gate found no coupling regressions or blocking issues, but 3 advisory finding(s) below merit a look before merge.

Formal verification. No changes could be formally verified in this run.


Graphify review — findings

Gates pruning of bare out/ directories on build-output evidence instead of removing them by name, so hexagonal source layers like adapter/out/ and port/out/ stay in the scan. _has_build_output_markers treats an IntelliJ production/ dir, a next export _next/ tree, or compiled artifacts (.class, .js.map, .pyc, etc.) in the top two levels as proof it's generated, probing one level into subdirs under a 4000-entry scan budget; an out/ that can't be verified is kept, matching the keep-on-doubt bias of the existing env/coverage/snapshots gates. build, dist, and target still prune unconditionally.

Worth a look

  • Wide-outDir budget probe order is nondeterministic (os.scandir order), so late-artifact detection is not guaranteedgraphify/detect.py · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • Nested source out/production packages are pruned as IntelliJ outputgraphify/detect.py:928 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • Build-output probe follows directory symlinks outside the scan treegraphify/detect.py:934 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 2167 functions depend on the 136 functions this change touches.

Health — this change adds coupling hotspots:

  • new: extract() — 559 callers, 43 callees
  • new: _rebuild_code() — 115 callers, 51 callees
  • new: detect() — 110 callers, 15 callees
  • new: _extract_generic() — 18 callers, 26 callees
  • new: save_manifest() — 40 callers, 11 callees
  • new: extract_files_direct() — 17 callers, 20 callees
  • new: extract_js() — 85 callers, 4 callees
  • new: extract_xaml() — 19 callers, 17 callees
  • …and 44 more — each is listed as a finding

Verification — 2167 functions in the blast radius were not formally verified this run (proofs are advisory here).

Gate & verification

graphify gate

PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.

Advisory (not blocking):

  • verification_scope: 777 function(s) in the blast radius were not formally verified this run

Test selection

Test selection

51 of 260 test file(s) selected (20%) via static blast radius.

  • tests/test_astro_extraction.py — impact
  • tests/test_atomic_writes.py — impact
  • tests/test_build.py — impact
  • tests/test_charmap_encoding.py — impact
  • tests/test_chunking.py — impact
  • tests/test_cjs_module_extension.py — impact
  • tests/test_claude_cli_backend.py — impact
  • tests/test_cpp_nested_and_cli.py — impact
  • tests/test_detect.py — impact
  • tests/test_dotnet.py — impact
  • tests/test_evidence_binding.py — impact
  • tests/test_extract.py — impact
  • tests/test_extract_cli.py — impact
  • tests/test_file_slice.py — impact
  • tests/test_ignore_file_encoding.py — impact
  • tests/test_image_vision.py — impact
  • tests/test_import_extension_resolution.py — impact
  • tests/test_incremental_mtime_collision.py — impact
  • tests/test_indirect_dispatch.py — impact
  • tests/test_indirect_dispatch_assign_return.py — impact
  • tests/test_indirect_dispatch_getattr.py — impact
  • tests/test_js_exported_scalar_bindings.py — impact
  • tests/test_languages.py — impact
  • tests/test_llm_backends.py — impact
  • tests/test_long_path_hashing.py — impact
  • tests/test_manifest_ingest.py — impact
  • tests/test_mcp_ingest.py — impact
  • tests/test_multilang.py — impact
  • tests/test_non_regular_files.py — impact
  • tests/test_office_incremental.py — impact
  • tests/test_office_limits.py — impact
  • tests/test_ollama.py — impact
  • tests/test_out_dir_evidence.py — impact, changed-test
  • tests/test_oversized_document_slicing.py — impact
  • tests/test_package_json_subpath_imports.py — impact
  • tests/test_pdf_slicing.py — impact
  • tests/test_pdf_token_estimate.py — impact
  • tests/test_phantom_external_import.py — impact
  • tests/test_pipeline.py — impact
  • tests/test_python_underscore_resolution.py — impact
  • tests/test_rationale.py — impact
  • tests/test_ruby_resolution.py — impact
  • tests/test_scala_self_type.py — impact
  • tests/test_stale_prune.py — impact
  • tests/test_swift_computed_properties.py — impact
  • tests/test_ts_new_expression_calls.py — impact
  • tests/test_typescript_module_extensions.py — impact
  • tests/test_unmapped_at_alias_resolution.py — impact
  • tests/test_vue_extraction.py — impact
  • tests/test_watch.py — impact
  • … and 1 more

Selection is safe under the controlled-regression assumption; always-run tests + a periodic full run are the backstops. Advisory — it never changes the check verdict.

Formal verification

Could not verify: Could not verify \_is\_noise\_dir.

The verifier did not have enough to check \_is\_noise\_dir, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: parameter `parent` is annotated `'Path | None'` — outside the synthesizable primitive/collection set

· 52 more finding(s) on lines outside this diff (see the check run).

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.

detect: bare out in _SKIP_DIRS silently drops the entire outbound layer of hexagonal codebases (196/655 .java files)

2 participants