graphify-php 0.0.5__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. graphify_php-0.0.5/.github/workflows/ci.yml +40 -0
  2. graphify_php-0.0.5/.github/workflows/pages.yml +62 -0
  3. graphify_php-0.0.5/.github/workflows/release.yml +72 -0
  4. graphify_php-0.0.5/.github/workflows/stage-build.yml +99 -0
  5. graphify_php-0.0.5/.github/workflows/stage-security.yml +146 -0
  6. graphify_php-0.0.5/.github/workflows/stage-tests.yml +65 -0
  7. graphify_php-0.0.5/.gitignore +17 -0
  8. graphify_php-0.0.5/.gitleaks.toml +14 -0
  9. graphify_php-0.0.5/.graphifyignore +29 -0
  10. graphify_php-0.0.5/.trufflehog-exclude.txt +19 -0
  11. graphify_php-0.0.5/LICENSE +201 -0
  12. graphify_php-0.0.5/PKG-INFO +218 -0
  13. graphify_php-0.0.5/README.md +197 -0
  14. graphify_php-0.0.5/docs/_config.yml +3 -0
  15. graphify_php-0.0.5/docs/architecture.md +121 -0
  16. graphify_php-0.0.5/docs/assets/css/style.scss +442 -0
  17. graphify_php-0.0.5/docs/configuration.md +98 -0
  18. graphify_php-0.0.5/docs/evaluation.md +122 -0
  19. graphify_php-0.0.5/docs/index.md +60 -0
  20. graphify_php-0.0.5/docs/install.md +188 -0
  21. graphify_php-0.0.5/docs/phpstan.md +93 -0
  22. graphify_php-0.0.5/docs/setup.md +89 -0
  23. graphify_php-0.0.5/eval/README.md +163 -0
  24. graphify_php-0.0.5/eval/__init__.py +6 -0
  25. graphify_php-0.0.5/eval/canaries.py +172 -0
  26. graphify_php-0.0.5/eval/fixtures/chained.php +30 -0
  27. graphify_php-0.0.5/eval/fixtures/ctor_body_assign.php +32 -0
  28. graphify_php-0.0.5/eval/fixtures/declared_property.php +27 -0
  29. graphify_php-0.0.5/eval/fixtures/dynamic.php +51 -0
  30. graphify_php-0.0.5/eval/fixtures/handler_shapes.php +124 -0
  31. graphify_php-0.0.5/eval/fixtures/inheritance.php +29 -0
  32. graphify_php-0.0.5/eval/fixtures/interfaces.php +34 -0
  33. graphify_php-0.0.5/eval/fixtures/locals_new.php +28 -0
  34. graphify_php-0.0.5/eval/fixtures/mail.php +19 -0
  35. graphify_php-0.0.5/eval/fixtures/nullable_union.php +25 -0
  36. graphify_php-0.0.5/eval/fixtures/promoted.php +22 -0
  37. graphify_php-0.0.5/eval/fixtures/same_name_traps.php +56 -0
  38. graphify_php-0.0.5/eval/fixtures/secrets.php +39 -0
  39. graphify_php-0.0.5/eval/fixtures/services.yaml +10 -0
  40. graphify_php-0.0.5/eval/fixtures/traits.php +30 -0
  41. graphify_php-0.0.5/eval/reference_set.py +694 -0
  42. graphify_php-0.0.5/eval/score.py +435 -0
  43. graphify_php-0.0.5/php/README.md +106 -0
  44. graphify_php-0.0.5/php/autoload.php +27 -0
  45. graphify_php-0.0.5/php/src/CallGraph/CallEdge.php +91 -0
  46. graphify_php-0.0.5/php/src/CallGraph/CallEdgeRule.php +61 -0
  47. graphify_php-0.0.5/php/src/CallGraph/CallEdgeWriter.php +19 -0
  48. graphify_php-0.0.5/php/src/CallGraph/JsonLinesWriter.php +64 -0
  49. graphify_php-0.0.5/php/src/CallGraph/MethodCallCollector.php +163 -0
  50. graphify_php-0.0.5/pyproject.toml +76 -0
  51. graphify_php-0.0.5/src/graphify_php/__init__.py +144 -0
  52. graphify_php-0.0.5/src/graphify_php/cli.py +355 -0
  53. graphify_php-0.0.5/src/graphify_php/config/__init__.py +47 -0
  54. graphify_php-0.0.5/src/graphify_php/config/class_lookup.py +85 -0
  55. graphify_php-0.0.5/src/graphify_php/config/discovery.py +55 -0
  56. graphify_php-0.0.5/src/graphify_php/config/document.py +29 -0
  57. graphify_php-0.0.5/src/graphify_php/config/emit.py +117 -0
  58. graphify_php-0.0.5/src/graphify_php/config/environment.py +108 -0
  59. graphify_php-0.0.5/src/graphify_php/config/facts.py +97 -0
  60. graphify_php-0.0.5/src/graphify_php/config/messaging.py +142 -0
  61. graphify_php-0.0.5/src/graphify_php/config/ports.py +23 -0
  62. graphify_php-0.0.5/src/graphify_php/config/safe_yaml.py +127 -0
  63. graphify_php-0.0.5/src/graphify_php/config_resolver.py +164 -0
  64. graphify_php-0.0.5/src/graphify_php/container/__init__.py +50 -0
  65. graphify_php-0.0.5/src/graphify_php/container/facts.py +178 -0
  66. graphify_php-0.0.5/src/graphify_php/container/layer.py +71 -0
  67. graphify_php-0.0.5/src/graphify_php/container/locator.py +74 -0
  68. graphify_php-0.0.5/src/graphify_php/container/overlay.py +149 -0
  69. graphify_php-0.0.5/src/graphify_php/container/ports.py +40 -0
  70. graphify_php-0.0.5/src/graphify_php/container/reader.py +330 -0
  71. graphify_php-0.0.5/src/graphify_php/container/reasons.py +31 -0
  72. graphify_php-0.0.5/src/graphify_php/graph_adapter.py +520 -0
  73. graphify_php-0.0.5/src/graphify_php/ports.py +174 -0
  74. graphify_php-0.0.5/src/graphify_php/resolver.py +519 -0
  75. graphify_php-0.0.5/src/graphify_php/shrink.py +413 -0
  76. graphify_php-0.0.5/src/graphify_php/shrink_cli.py +150 -0
  77. graphify_php-0.0.5/src/graphify_php/sources/phpstan_source.py +641 -0
  78. graphify_php-0.0.5/src/graphify_php/sources/route_source.py +105 -0
  79. graphify_php-0.0.5/src/graphify_php/sources/tree_sitter_source.py +428 -0
  80. graphify_php-0.0.5/src/graphify_php/status.py +136 -0
  81. graphify_php-0.0.5/src/graphify_php/syntax/call_sites.py +117 -0
  82. graphify_php-0.0.5/src/graphify_php/syntax/class_index.py +121 -0
  83. graphify_php-0.0.5/src/graphify_php/syntax/collectors.py +351 -0
  84. graphify_php-0.0.5/src/graphify_php/syntax/facts.py +171 -0
  85. graphify_php-0.0.5/src/graphify_php/syntax/file_scan.py +158 -0
  86. graphify_php-0.0.5/src/graphify_php/syntax/names.py +49 -0
  87. graphify_php-0.0.5/src/graphify_php/syntax/nodes.py +209 -0
  88. graphify_php-0.0.5/src/graphify_php/syntax/routes.py +288 -0
  89. graphify_php-0.0.5/tests/fixtures/shrink/ckeditor/new_graph.json +1 -0
  90. graphify_php-0.0.5/tests/fixtures/shrink/ckeditor/new_manifest.json +1 -0
  91. graphify_php-0.0.5/tests/fixtures/shrink/ckeditor/old_graph.json +1 -0
  92. graphify_php-0.0.5/tests/fixtures/shrink/ckeditor/old_manifest.json +1 -0
  93. graphify_php-0.0.5/tests/fixtures/shrink/ckeditor/present.txt +24 -0
  94. graphify_php-0.0.5/tests/fixtures/shrink/games/new_graph.json +1 -0
  95. graphify_php-0.0.5/tests/fixtures/shrink/games/new_manifest.json +1 -0
  96. graphify_php-0.0.5/tests/fixtures/shrink/games/old_graph.json +1 -0
  97. graphify_php-0.0.5/tests/fixtures/shrink/games/old_manifest.json +1 -0
  98. graphify_php-0.0.5/tests/fixtures/shrink/games/present.txt +33 -0
  99. graphify_php-0.0.5/tests/test_adapter_graph.py +337 -0
  100. graphify_php-0.0.5/tests/test_cli_end_to_end.py +281 -0
  101. graphify_php-0.0.5/tests/test_cli_gate.py +319 -0
  102. graphify_php-0.0.5/tests/test_cli_shrink_gate.py +239 -0
  103. graphify_php-0.0.5/tests/test_config_readers.py +316 -0
  104. graphify_php-0.0.5/tests/test_config_resolver.py +251 -0
  105. graphify_php-0.0.5/tests/test_container_integration.py +94 -0
  106. graphify_php-0.0.5/tests/test_container_layer.py +98 -0
  107. graphify_php-0.0.5/tests/test_container_overlay.py +138 -0
  108. graphify_php-0.0.5/tests/test_container_reader.py +309 -0
  109. graphify_php-0.0.5/tests/test_eval_canaries.py +96 -0
  110. graphify_php-0.0.5/tests/test_eval_reference_set.py +91 -0
  111. graphify_php-0.0.5/tests/test_eval_score.py +285 -0
  112. graphify_php-0.0.5/tests/test_phpstan_integration.py +368 -0
  113. graphify_php-0.0.5/tests/test_phpstan_source.py +669 -0
  114. graphify_php-0.0.5/tests/test_resolver_precedence.py +639 -0
  115. graphify_php-0.0.5/tests/test_shrink.py +680 -0
  116. graphify_php-0.0.5/tests/test_shrink_cli.py +213 -0
  117. graphify_php-0.0.5/tests/test_syntax_routes.py +457 -0
  118. graphify_php-0.0.5/tests/test_syntax_scan.py +576 -0
  119. graphify_php-0.0.5/tests/test_tree_sitter_source.py +1704 -0
@@ -0,0 +1,40 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [master]
6
+ pull_request:
7
+ # pip-audit answers a question no push can ask: a CVE gets published against
8
+ # a dependency tree that has not changed. The nightly run keeps that check
9
+ # alive on a repository that stands still.
10
+ schedule:
11
+ - cron: '0 3 * * *'
12
+ workflow_dispatch:
13
+
14
+ permissions:
15
+ contents: read
16
+
17
+ # A newer push to the same branch or pull request makes the run in flight
18
+ # pointless, so cancel it.
19
+ concurrency:
20
+ group: ci-${{ github.ref }}
21
+ cancel-in-progress: true
22
+
23
+ # Three stages in order: security, tests, build. Jobs inside a stage run in
24
+ # parallel; the stages are sequential, so a leaked secret stops the run before
25
+ # anything is tested and a failing test stops it before anything is built.
26
+ #
27
+ # No smoke stage: tests/test_cli_end_to_end.py already runs the real graphify
28
+ # in-process against a generated PHP project, so a separate end-to-end job
29
+ # would repeat what the test matrix proves on every Python version.
30
+ jobs:
31
+ security:
32
+ uses: ./.github/workflows/stage-security.yml
33
+
34
+ tests:
35
+ needs: security
36
+ uses: ./.github/workflows/stage-tests.yml
37
+
38
+ build:
39
+ needs: tests
40
+ uses: ./.github/workflows/stage-build.yml
@@ -0,0 +1,62 @@
1
+ name: Pages
2
+
3
+ # The site is the Jekyll build of docs/ alone. Only changes to docs/ or to this
4
+ # workflow can change it, so other pushes skip the deploy.
5
+ on:
6
+ push:
7
+ branches:
8
+ - master
9
+ paths:
10
+ - "docs/**"
11
+ - ".github/workflows/pages.yml"
12
+ workflow_dispatch:
13
+
14
+ # Default deny; each job below requests only what it needs.
15
+ permissions: {}
16
+
17
+ # One deployment at a time, and never cancel one in flight: a cancelled deploy
18
+ # can leave the site half-published. Queued runs still collapse.
19
+ concurrency:
20
+ group: pages
21
+ cancel-in-progress: false
22
+
23
+ jobs:
24
+ build:
25
+ runs-on: ubuntu-latest
26
+ permissions:
27
+ contents: read
28
+ steps:
29
+ # Always master: a manual dispatch from another branch would otherwise
30
+ # publish unreviewed docs to the public site.
31
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
32
+ with:
33
+ ref: master
34
+ persist-credentials: false
35
+
36
+ - uses: actions/configure-pages@45bfe0192ca1faeb007ade9deae92b16b8254a0d # v6.0.0
37
+
38
+ - name: Build the site
39
+ # docs/kb is a symlink to a private vault; _config.yml excludes it so
40
+ # the build cannot publish it even if the link is ever committed.
41
+ uses: actions/jekyll-build-pages@44a6e6beabd48582f863aeeb6cb2151cc1716697 # v1.0.13
42
+ with:
43
+ source: ./docs
44
+ destination: ./_site
45
+
46
+ - uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0
47
+ with:
48
+ path: ./_site
49
+
50
+ deploy:
51
+ needs: build
52
+ runs-on: ubuntu-latest
53
+ permissions:
54
+ pages: write
55
+ id-token: write
56
+ contents: read
57
+ environment:
58
+ name: github-pages
59
+ url: ${{ steps.deployment.outputs.page_url }}
60
+ steps:
61
+ - id: deployment
62
+ uses: actions/deploy-pages@368f82528645a54fb793d4d04e342629a3f51346 # v5.0.1
@@ -0,0 +1,72 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*.*.*"
7
+
8
+ # Default deny; each job below requests only what it needs.
9
+ permissions: {}
10
+
11
+ # No `concurrency` block, unlike ci.yml: cancelling a publish half-way is worse
12
+ # than paying for the runner.
13
+
14
+ # The same three stage workflows ci.yml runs, with the publish on top, so a
15
+ # release passes exactly the checks a pull request does.
16
+ jobs:
17
+ security:
18
+ permissions:
19
+ contents: read
20
+ uses: ./.github/workflows/stage-security.yml
21
+
22
+ tests:
23
+ needs: security
24
+ permissions:
25
+ contents: read
26
+ uses: ./.github/workflows/stage-tests.yml
27
+
28
+ # stage-build takes a bare PEP 440 version, not a tag, so that it stays usable
29
+ # from ci.yml where no tag exists. Stripping the leading "v" needs a shell:
30
+ # GitHub's expression syntax has no substring operator.
31
+ version:
32
+ runs-on: ubuntu-latest
33
+ permissions: {}
34
+ outputs:
35
+ value: ${{ steps.strip.outputs.value }}
36
+ steps:
37
+ - id: strip
38
+ env:
39
+ TAG: ${{ github.ref_name }}
40
+ run: echo "value=${TAG#v}" >> "$GITHUB_OUTPUT"
41
+
42
+ build:
43
+ needs: [tests, version]
44
+ permissions:
45
+ contents: read
46
+ uses: ./.github/workflows/stage-build.yml
47
+ with:
48
+ # Turns on the check that the version hatch-vcs computed from git history
49
+ # equals the tag being released.
50
+ expected_version: ${{ needs.version.outputs.value }}
51
+
52
+ # Trusted publishing: PyPI trusts this workflow and the `pypi` environment
53
+ # through OIDC, so no API token is stored in the repository.
54
+ pypi:
55
+ needs: build
56
+ runs-on: ubuntu-latest
57
+ environment: pypi
58
+ permissions:
59
+ id-token: write
60
+ steps:
61
+ # The files stage-build produced and installed from a clean venv. Nothing
62
+ # is rebuilt here: a second build could differ from the one the gates ran.
63
+ - name: Download dist artifacts
64
+ uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
65
+ with:
66
+ name: dist
67
+ path: dist
68
+
69
+ - name: Publish to PyPI
70
+ run: |
71
+ python3 -m pip install "uv==0.12.5"
72
+ python3 -m uv publish --trusted-publishing always dist/*
@@ -0,0 +1,99 @@
1
+ name: stage-build
2
+
3
+ on:
4
+ workflow_call:
5
+ inputs:
6
+ expected_version:
7
+ description: >
8
+ PEP 440 version the built wheel must carry, without a leading "v".
9
+ Empty (the default) skips the check: a pull request has no tag.
10
+ type: string
11
+ default: ""
12
+
13
+ permissions:
14
+ contents: read
15
+
16
+ jobs:
17
+ # Every other job installs with `pip install -e .`, which imports straight
18
+ # from src/ and never builds anything. A package left out of
19
+ # [tool.hatch.build.targets.wheel] or a console script removed from
20
+ # [project.scripts] produces a wheel that installs cleanly and does nothing.
21
+ # This job is the gate for that.
22
+ build:
23
+ runs-on: ubuntu-latest
24
+ permissions:
25
+ contents: read
26
+ steps:
27
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
28
+ with:
29
+ # hatch-vcs derives the version from `git describe`. A shallow clone
30
+ # has no tags and would build the fallback version instead.
31
+ fetch-depth: 0
32
+
33
+ - name: Set up Python
34
+ uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
35
+ with:
36
+ python-version: "3.12"
37
+
38
+ - name: Build sdist + wheel
39
+ run: |
40
+ python -m pip install --upgrade build
41
+ python -m build
42
+
43
+ - name: Built version matches the release tag
44
+ # PyPI never accepts the same version twice, so a wheel that does not
45
+ # carry the tag's version must stop here, before anything ships.
46
+ if: inputs.expected_version != ''
47
+ env:
48
+ EXPECTED: ${{ inputs.expected_version }}
49
+ run: |
50
+ built=$(ls dist/*.whl | grep -oP 'graphify_php-\K[^-]+(?=-py3)')
51
+ echo "expected=${EXPECTED} built=${built}"
52
+ if [ "$built" != "$EXPECTED" ]; then
53
+ echo "::error::Built version '${built}' does not match the tag version '${EXPECTED}'."
54
+ exit 1
55
+ fi
56
+
57
+ - name: Install the built wheel into a clean venv and run the entry point
58
+ # A fresh venv outside the checkout has no src/ on sys.path, so the
59
+ # import and the console script can only resolve against what the wheel
60
+ # shipped. Run from $RUNNER_TEMP for the same reason.
61
+ #
62
+ # The wheel is installed alone, without graphify, so `--help` proves
63
+ # the console script and the package import stand on their own. A real
64
+ # build still needs graphify, which the test matrix covers.
65
+ run: |
66
+ set -euo pipefail
67
+ python -m venv "$RUNNER_TEMP/wheelenv"
68
+ "$RUNNER_TEMP/wheelenv/bin/pip" install --upgrade pip
69
+ "$RUNNER_TEMP/wheelenv/bin/pip" install dist/*.whl
70
+ cd "$RUNNER_TEMP"
71
+ "$RUNNER_TEMP/wheelenv/bin/python" -c "import graphify_php; print(graphify_php.__name__)"
72
+ "$RUNNER_TEMP/wheelenv/bin/graphify-php-build" --help
73
+
74
+ - name: The wheel carries the PHPStan extension
75
+ # Without php/ in the wheel the PHPStan source finds no autoload file and
76
+ # drops out quietly, leaving tree-sitter alone. The lookup must resolve
77
+ # inside the installed package, not in this checkout.
78
+ run: |
79
+ set -euo pipefail
80
+ cd "$RUNNER_TEMP"
81
+ "$RUNNER_TEMP/wheelenv/bin/python" - <<'PY'
82
+ from graphify_php.sources.phpstan_source import extension_autoload_file
83
+
84
+ path = extension_autoload_file()
85
+ assert path is not None, "php/autoload.php is missing from the wheel"
86
+ assert "site-packages" in str(path), path
87
+ assert (path.parent / "src" / "CallGraph").is_dir(), path
88
+ print(path)
89
+ PY
90
+
91
+ - name: Upload dist artifacts
92
+ # release.yml publishes these files rather than building its own copy,
93
+ # so what a release ships is the artifact this job just proved
94
+ # installable.
95
+ uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
96
+ with:
97
+ name: dist
98
+ path: dist/
99
+ if-no-files-found: error
@@ -0,0 +1,146 @@
1
+ name: stage-security
2
+
3
+ on:
4
+ workflow_call:
5
+
6
+ permissions:
7
+ contents: read
8
+
9
+ jobs:
10
+ gitleaks:
11
+ runs-on: ubuntu-latest
12
+ permissions:
13
+ contents: read
14
+ steps:
15
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
16
+ with:
17
+ fetch-depth: 0
18
+
19
+ - name: gitleaks — scan git history for hardcoded secrets
20
+ # Plain OSS gitleaks CLI, not gitleaks/gitleaks-action: that Action now
21
+ # requires a paid GITLEAKS_LICENSE for GitHub Organizations. The CLI
22
+ # itself stays free/open-source.
23
+ # The sha256 below is pinned from the official release checksums file:
24
+ # https://github.com/gitleaks/gitleaks/releases/download/v8.30.1/gitleaks_8.30.1_checksums.txt
25
+ # .gitleaks.toml at the repository root extends the default rules with
26
+ # a path allowlist for the planted canaries (see that file).
27
+ run: |
28
+ curl -sSfL -o gitleaks_8.30.1_linux_x64.tar.gz \
29
+ https://github.com/gitleaks/gitleaks/releases/download/v8.30.1/gitleaks_8.30.1_linux_x64.tar.gz
30
+ echo "551f6fc83ea457d62a0d98237cbad105af8d557003051f41f3e7ca7b3f2470eb gitleaks_8.30.1_linux_x64.tar.gz" | sha256sum -c -
31
+ tar -xzf gitleaks_8.30.1_linux_x64.tar.gz gitleaks
32
+ ./gitleaks detect --source . --no-banner --redact
33
+
34
+ trufflehog:
35
+ runs-on: ubuntu-latest
36
+ permissions:
37
+ contents: read
38
+ steps:
39
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
40
+ with:
41
+ fetch-depth: 0
42
+
43
+ # The pull-request scan reports the `unknown` result class, the history
44
+ # scan does not. An `unknown` result is a string that looks like a
45
+ # credential and cannot be verified. On a pull request it is in the diff
46
+ # under review and can still be changed; in old history it can only be
47
+ # silenced by rewriting the commits that contain it, which would fail this
48
+ # job on every future run of a repository that leaks nothing.
49
+ #
50
+ # Both scans report every verified result, from every detector. A real
51
+ # leak fails this job wherever it lands.
52
+ - name: trufflehog — scan PR commits for verified and unknown secrets (pull_request)
53
+ if: github.event_name == 'pull_request'
54
+ uses: trufflesecurity/trufflehog@363923b901c911a9164f50b6c423f47c15372b1c # v3.97.4
55
+ with:
56
+ path: ./
57
+ base: ${{ github.event.pull_request.base.sha }}
58
+ head: ${{ github.event.pull_request.head.sha }}
59
+ # --exclude-paths names the two files that plant fake credentials on
60
+ # purpose; .trufflehog-exclude.txt explains which and why. Excluding
61
+ # those paths is what keeps the `unknown` class switched on for every
62
+ # other file in the diff.
63
+ extra_args: --results=verified,unknown --exclude-paths=.trufflehog-exclude.txt
64
+
65
+ # Runs on push, and on the nightly schedule and manual dispatch too.
66
+ - name: trufflehog — scan full git history for verified secrets
67
+ if: github.event_name != 'pull_request'
68
+ uses: trufflesecurity/trufflehog@363923b901c911a9164f50b6c423f47c15372b1c # v3.97.4
69
+ with:
70
+ path: ./
71
+ base: ""
72
+ head: ${{ github.ref_name }}
73
+ # No --exclude-paths here: this scan reports only verified results,
74
+ # and a verified credential must fail the job whatever file it sits
75
+ # in, canary fixture included.
76
+ extra_args: --results=verified
77
+
78
+ pip-audit:
79
+ runs-on: ubuntu-latest
80
+ permissions:
81
+ contents: read
82
+ steps:
83
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
84
+
85
+ - name: Set up Python
86
+ uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
87
+ with:
88
+ python-version: "3.12"
89
+ cache: "pip"
90
+
91
+ - name: Install graphify-php with test and lint extras
92
+ # The package has no runtime dependencies; the test extra is what pulls
93
+ # graphifyy and its tree-sitter grammars, so this is the tree worth
94
+ # auditing.
95
+ run: pip install -e ".[test,lint]"
96
+
97
+ - name: pip-audit — audit installed Python dependencies for known CVEs
98
+ # pip-audit exits 1 when it finds known vulnerabilities — that MUST
99
+ # fail the job. Only other nonzero exit codes (infra/network errors,
100
+ # e.g. the PyPI advisory DB being unreachable) are downgraded to a
101
+ # logged warning so a transient hiccup doesn't block the pipeline.
102
+ run: |
103
+ pip install pip-audit
104
+ set +e
105
+ pip-audit
106
+ code=$?
107
+ set -e
108
+ case "$code" in
109
+ 0)
110
+ echo "pip-audit: no known vulnerabilities found."
111
+ ;;
112
+ 1)
113
+ echo "::error::pip-audit found known vulnerabilities in installed dependencies."
114
+ exit 1
115
+ ;;
116
+ *)
117
+ echo "::warning::pip-audit exited with code $code (infra/network error, not a vulnerability finding); not failing the job."
118
+ ;;
119
+ esac
120
+
121
+ php-lint:
122
+ runs-on: ubuntu-latest
123
+ permissions:
124
+ contents: read
125
+ steps:
126
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
127
+
128
+ - name: Set up PHP
129
+ # The SHA is the one symfony/symfony's own workflows pin for 2.37.2; it
130
+ # was copied from a local checkout, not verified against the release
131
+ # page from this machine.
132
+ uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # 2.37.2
133
+ with:
134
+ # php/README.md verifies the collector against PHP 8.4.
135
+ php-version: "8.4"
136
+ coverage: none
137
+ tools: none
138
+
139
+ - name: php -l — syntax-check the PHPStan collector
140
+ # The Python suite only exercises php/ through PHPStan, which CI does
141
+ # not have (see stage-tests.yml), so this is the one check that runs
142
+ # against the PHP files on every push.
143
+ run: |
144
+ set -euo pipefail
145
+ find php/src -name '*.php' -print0 | xargs -0 -n1 php -l
146
+ php -l php/autoload.php
@@ -0,0 +1,65 @@
1
+ name: stage-tests
2
+
3
+ on:
4
+ workflow_call:
5
+
6
+ permissions:
7
+ contents: read
8
+
9
+ jobs:
10
+ lint:
11
+ runs-on: ubuntu-latest
12
+ permissions:
13
+ contents: read
14
+ steps:
15
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
16
+
17
+ - name: Set up Python
18
+ uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
19
+ with:
20
+ python-version: "3.12"
21
+ cache: "pip"
22
+
23
+ - name: Install graphify-php with lint extras
24
+ run: pip install -e ".[lint]"
25
+
26
+ - name: ruff — lint
27
+ run: ruff check .
28
+
29
+ - name: ruff — format check
30
+ run: ruff format --check .
31
+
32
+ test:
33
+ runs-on: ubuntu-latest
34
+ permissions:
35
+ contents: read
36
+ strategy:
37
+ # One Python version failing must not hide the state of the others.
38
+ fail-fast: false
39
+ matrix:
40
+ python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
41
+
42
+ steps:
43
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
44
+
45
+ - name: Set up Python ${{ matrix.python-version }}
46
+ uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
47
+ with:
48
+ python-version: ${{ matrix.python-version }}
49
+ cache: "pip"
50
+
51
+ - name: Install graphify-php with test extras
52
+ # graphifyy brings tree-sitter and tree-sitter-php with it; without
53
+ # them syntax/nodes.py sets the parser modules to None and the
54
+ # tree-sitter tests fail rather than skip.
55
+ run: pip install -e ".[test]"
56
+
57
+ - name: Run the test suite
58
+ # Two files skip themselves here because GRAPHIFY_PHP_REFERENCE_PROJECT
59
+ # is unset: tests/test_phpstan_integration.py needs
60
+ # <project>/vendor/bin/phpstan plus php on PATH, and
61
+ # tests/test_container_integration.py needs that project's compiled
62
+ # container at <project>/var/cache/dev/App_KernelDevDebugContainer.xml.
63
+ # Running them in CI would need a checked-out Symfony project with a
64
+ # composer-installed PHPStan to point the variable at.
65
+ run: python -m pytest -rs
@@ -0,0 +1,17 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ .pytest_cache/
4
+ *.egg-info/
5
+ build/
6
+ dist/
7
+
8
+ # Written by the resolver during a build and read by the wrapper's publish gate.
9
+ # It is per-build state, not source: committing it would let yesterday's record
10
+ # satisfy today's gate, which is the failure the gate exists to catch.
11
+ .graphify-php-status.json
12
+ php-call-graph.jsonl
13
+ graphify-out
14
+ graphify-out/
15
+ docs/kb
16
+ .serena
17
+ CLAUDE.md
@@ -0,0 +1,14 @@
1
+ # Keeps gitleaks' default rule set and allowlists the two files that plant fake
2
+ # credentials on purpose: eval/fixtures/secrets.php and eval/canaries.py (see
3
+ # .trufflehog-exclude.txt for what they hold and why). Allowlisting by path
4
+ # rather than by .gitleaksignore fingerprint, because a fingerprint names one
5
+ # commit and one line, and every edit to those files would mint a new one.
6
+ [extend]
7
+ useDefault = true
8
+
9
+ [[allowlists]]
10
+ description = "Planted CANARY values that tests/test_eval_canaries.py checks never leak"
11
+ paths = [
12
+ '''^eval/fixtures/secrets\.php$''',
13
+ '''^eval/canaries\.py$''',
14
+ ]
@@ -0,0 +1,29 @@
1
+ .idea/
2
+ .claude/
3
+ .github/
4
+ .git/
5
+ .cache/
6
+ .pytest_cache/
7
+ .serena/
8
+
9
+ docs/
10
+ eval/fixtures/
11
+ build/
12
+ dist/
13
+
14
+ graphify-out
15
+ graphify-out/
16
+
17
+ *.png
18
+ *.jpg
19
+ *.jpeg
20
+ *.gif
21
+ *.svg
22
+ *.generated.py
23
+ *.min.js
24
+ *.pem
25
+ .env
26
+ .env.*
27
+ .npmrc
28
+ auth.json
29
+ LICENSE
@@ -0,0 +1,19 @@
1
+ # Paths the pull-request trufflehog scan must not read. One Go regex per line,
2
+ # matched against the file path relative to the repository root. Only the
3
+ # pull-request scan uses this file; the full-history scan reports verified
4
+ # results from every path, these two included.
5
+ #
6
+ # eval/fixtures/secrets.php plants fake credentials — an API token constant, a
7
+ # DSN with an inline password, an environment-variable fallback and an
8
+ # attribute argument — and eval/canaries.py lists the same values so that
9
+ # tests/test_eval_canaries.py can prove none of them ever reaches a graph, a
10
+ # status record or a log. Every value carries the marker CANARY, points at a
11
+ # host that does not exist and opens nothing. Without this exclusion trufflehog
12
+ # reports them as unknown-class results and fails the job on a repository that
13
+ # leaks nothing. The values are deliberately not repeated here, so that
14
+ # excluding them does not turn this file into the next finding.
15
+ #
16
+ # Only these two paths are excluded, and only for the unknown class. Every
17
+ # other file in a pull request keeps both the verified and the unknown class.
18
+ (^|/)eval/fixtures/secrets\.php$
19
+ (^|/)eval/canaries\.py$