Skip to content

feat: add Jenkinsfile pipeline extractor - #2754

Open
rangamani54 wants to merge 1 commit into
Graphify-Labs:v8from
rangamani54:feat/jenkinsfile-extractor
Open

feat: add Jenkinsfile pipeline extractor#2754
rangamani54 wants to merge 1 commit into
Graphify-Labs:v8from
rangamani54:feat/jenkinsfile-extractor

Conversation

@rangamani54

@rangamani54 rangamani54 commented Aug 15, 2026

Copy link
Copy Markdown

Summary

  • Add a dedicated Tree-sitter Groovy extractor for extensionless Jenkinsfiles
  • Extract pipelines, stages, parallel/matrix branches, Jenkins steps, Docker images, and shared libraries
  • Extract Groovy functions defined inside Jenkinsfiles
  • Add calls edges between internal Groovy functions
  • Support syntax such as sh '...', checkout scm, docker.build(...), and @Library(...)
  • Preserve existing .groovy extraction behavior
  • Add focused tests and documentation

Graph structure

The extractor emits:

  • JenkinsPipeline
  • Jenkins stage nodes
  • JenkinsParallel and JenkinsMatrix nodes
  • Parallel/matrix branch nodes
  • Jenkins step nodes
  • Internal groovy_function nodes
  • Docker image reference nodes
  • Jenkins shared-library nodes

It emits contains, calls, uses_image, builds, and uses_library relationships.

Follow-up work

Cross-format resolvers for Dockerfiles, Kubernetes/Helm, and Terraform will be added separately. External multibranch Jenkins configuration and shared-library source resolution are intentionally outside this PR.

@rangamani54
rangamani54 force-pushed the feat/jenkinsfile-extractor branch 2 times, most recently from f1b3f78 to afa24fc Compare August 15, 2026 09:17
@rangamani54
rangamani54 marked this pull request as ready for review August 16, 2026 08:18
@rangamani54
rangamani54 force-pushed the feat/jenkinsfile-extractor branch from afa24fc to 93e9a61 Compare August 16, 2026 10:13

@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. 1 change(s) tested, no difference found (not proven).


Graphify review — findings

This PR appears to primarily involve version/changelog reshuffling and a feature addition, alongside broad test changes. - Changelog/README: Moves the 0.9.44 unreleased notes back into a 0.9.43 (unreleased) section (unreleasing what was marked 0.9.43), removes YouTube badge links from the README, and adds a README table entry documenting Jenkins Pipeline (Jenkinsfile) support. - New extractor: Adds a Jenkins/Groovy DSL extractor (graphify_extractors_jenkins_extract_jenkinsfile_walk) and related surface, touching the extractors engine and CLI/hook code paths. - affected.py: Reverts the root parameter threading on _as_repo_relative, resolve_seed, and format_affected, going back to anchoring absolute-path seeds against Path.cwd() instead of a graph-derived root. The bulk of the changed symbols are test files across install, detect, extract, languages, and hooks modules, suggesting wide test churn accompanying these changes.

Worth a look

  • resolve_seed public signature narrowed by removing root parametergraphify/affected.py:130 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review Execution auto-disposal is off for this run; enable it (with sandbox isolation) to have Graphify try to confirm or refute this automatically.
  • format_affected public signature narrowed by removing root parametergraphify/affected.py:256 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review Execution auto-disposal is off for this run; enable it (with sandbox isolation) to have Graphify try to confirm or refute this automatically.
Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 3493 functions depend on the 2392 functions this change touches.

Health — this change adds coupling hotspots:

  • new: extract() — 454 callers, 41 callees
  • new: _rebuild_code() — 95 callers, 51 callees
  • new: detect() — 93 callers, 14 callees
  • new: _extract_generic() — 18 callers, 23 callees
  • new: save_manifest() — 34 callers, 11 callees
  • new: extract_files_direct() — 17 callers, 20 callees
  • new: extract_xaml() — 19 callers, 17 callees
  • new: extract_corpus_parallel() — 26 callers, 10 callees
  • …and 73 more

Verification — 3493 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: 3493 function(s) in the blast radius were not formally verified this run

Formal verification

Could not verify: Could not verify \_as\_repo\_relative.

The verifier did not have enough to check \_as\_repo\_relative, 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 `root` is annotated `Path | None` — outside the synthesizable primitive/collection set

No difference found (not proven): No behavior difference found in format\_affected (not a proof).

The verifier ran both versions of format\_affected on many inputs and saw identical behavior every time. Strong evidence the change is safe, but evidence, not a proof.

Guarantee: Empirical: differential testing (both versions run on many generated inputs). A divergence on an untested input remains possible, so this is 'no counterexample found', not 'proven equivalent'.

Note: An input the sampler did not try could still differ.

Could not verify: Could not verify resolve\_seed.

The verifier did not have enough to check resolve\_seed, 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 `root` is annotated `Path | None` — outside the synthesizable primitive/collection set

Could not verify: Could not verify dispatch\_command.

The verifier did not have enough to check dispatch\_command, 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: not verifiable: all 23 sampled inputs raised on both versions — the function never executed, so 'no divergence' would be vacuous (mostly SystemExit — names the real obstacle, not a sampling gap)

Could not verify: Could not verify classify\_file.

The verifier did not have enough to check classify\_file, 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 `path` is annotated `Path` — outside the synthesizable primitive/collection set

Could not verify: Could not verify detect.

The verifier did not have enough to check detect, 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 `root` is annotated `Path` — outside the synthesizable primitive/collection set

Could not verify: Could not verify ignored\_predicate.

The verifier did not have enough to check ignored\_predicate, 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 `root` is annotated `Path` — outside the synthesizable primitive/collection set

Could not verify: Could not verify collect\_files.

The verifier did not have enough to check collect\_files, 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 `target` is annotated `Path` — outside the synthesizable primitive/collection set

Could not verify: Could not verify extract\_cpp.

The verifier did not have enough to check extract\_cpp, 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 `path` is annotated `Path` — outside the synthesizable primitive/collection set

Could not verify: Could not verify \_get\_extractor.

The verifier did not have enough to check \_get\_extractor, 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 `path` is annotated `Path` — outside the synthesizable primitive/collection set

Could not verify: Could not verify \_extract\_generic.

The verifier did not have enough to check \_extract\_generic, 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 `path` is annotated `Path` — outside the synthesizable primitive/collection set

Could not verify: Could not verify \_require\_imports\_js.

The verifier did not have enough to check \_require\_imports\_js, 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: not verifiable: all 200 sampled inputs raised on both versions — the function never executed, so 'no divergence' would be vacuous (mostly AttributeError — names the real obstacle, not a sampling gap)

Could not verify: Could not verify extract\_ocaml.

The verifier did not have enough to check extract\_ocaml, 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 `path` is annotated `Path` — outside the synthesizable primitive/collection set

Could not verify: Could not verify install.

The verifier did not have enough to check install, 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 `path` is annotated `Path` — outside the synthesizable primitive/collection set

Could not verify: Could not verify \_install\_hook.

The verifier did not have enough to check \_install\_hook, 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 `hooks_dir` is annotated `Path` — outside the synthesizable primitive/collection set

Could not verify: Could not verify status.

The verifier did not have enough to check status, 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 `path` is annotated `Path` — outside the synthesizable primitive/collection set

Could not verify: Could not verify install.

The verifier did not have enough to check install, 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: not verifiable: all 23 sampled inputs raised on both versions — the function never executed, so 'no divergence' would be vacuous (mostly SystemExit — names the real obstacle, not a sampling gap)

· 3 grounded finding(s) anchored inline below; 78 more finding(s) on lines outside this diff (see the check run).

Comment thread graphify/extract.py
@@ -4989,6 +4990,8 @@ def _is_cpp_header(path: Path) -> bool:

def _get_extractor(path: Path) -> Any | None:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Health regression_get_extractor()

fans out to 6 callees (efferent coupling); 29 callers depend on it (afferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

return f"L{node.start_point[0] + 1}"


def extract_jenkinsfile(path: Path) -> dict:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Health regressionextract_jenkinsfile()

fans out to 6 callees (efferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

add_edge(file_nid, pipeline_nid, "contains", node)
return pipeline_nid

def walk(node: Any, parent: str, *, in_steps: bool = False, current_stage: str | None = None,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Health regressionwalk()

fans out to 11 callees (efferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

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