gabion 0.1.0__tar.gz → 0.1.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 (173) hide show
  1. gabion-0.1.5/.github/workflows/auto-test-tag.yml +72 -0
  2. {gabion-0.1.0 → gabion-0.1.5}/.github/workflows/ci.yml +31 -14
  3. gabion-0.1.5/.github/workflows/mirror-next.yml +32 -0
  4. {gabion-0.1.0 → gabion-0.1.5}/.github/workflows/pr-dataflow-grammar.yml +63 -5
  5. gabion-0.1.5/.github/workflows/promote-release.yml +64 -0
  6. gabion-0.1.5/.github/workflows/release-pypi.yml +89 -0
  7. gabion-0.1.5/.github/workflows/release-tag.yml +30 -0
  8. gabion-0.1.5/.github/workflows/release-testpypi.yml +92 -0
  9. {gabion-0.1.0 → gabion-0.1.5}/.gitignore +1 -0
  10. {gabion-0.1.0 → gabion-0.1.5}/AGENTS.md +7 -1
  11. {gabion-0.1.0 → gabion-0.1.5}/CONTRIBUTING.md +34 -6
  12. {gabion-0.1.0 → gabion-0.1.5}/PKG-INFO +21 -4
  13. {gabion-0.1.0 → gabion-0.1.5}/POLICY_SEED.md +101 -2
  14. {gabion-0.1.0 → gabion-0.1.5}/README.md +15 -3
  15. gabion-0.1.5/baselines/dataflow_baseline.txt +5 -0
  16. {gabion-0.1.0 → gabion-0.1.5}/docs/allowed_actions.txt +1 -0
  17. gabion-0.1.5/docs/coverage_semantics.md +134 -0
  18. gabion-0.1.5/docs/influence_index.md +59 -0
  19. {gabion-0.1.0 → gabion-0.1.5}/docs/publishing_practices.md +40 -1
  20. {gabion-0.1.0 → gabion-0.1.5}/docs/sppf_checklist.md +13 -5
  21. {gabion-0.1.0 → gabion-0.1.5}/docs/synthesis_payload.md +5 -2
  22. gabion-0.1.5/gabion.toml +7 -0
  23. gabion-0.1.5/glossary.md +679 -0
  24. gabion-0.1.5/out/out-1.md +98 -0
  25. gabion-0.1.5/out/out-2.md +171 -0
  26. {gabion-0.1.0 → gabion-0.1.5}/pyproject.toml +9 -1
  27. gabion-0.1.5/pytest.ini +3 -0
  28. gabion-0.1.5/requirements.lock +75 -0
  29. {gabion-0.1.0 → gabion-0.1.5}/scripts/checks.sh +5 -1
  30. {gabion-0.1.0 → gabion-0.1.5}/scripts/docflow_audit.py +94 -4
  31. {gabion-0.1.0 → gabion-0.1.5}/scripts/pin_actions.py +1 -1
  32. {gabion-0.1.0 → gabion-0.1.5}/scripts/policy_check.py +260 -5
  33. gabion-0.1.5/scripts/release_read_project_version.py +33 -0
  34. gabion-0.1.5/scripts/release_set_test_version.py +58 -0
  35. gabion-0.1.5/scripts/release_tag.py +86 -0
  36. gabion-0.1.5/scripts/release_verify_pypi_tag.py +75 -0
  37. gabion-0.1.5/scripts/release_verify_test_tag.py +38 -0
  38. {gabion-0.1.0 → gabion-0.1.5}/src/gabion/__init__.py +1 -1
  39. {gabion-0.1.0 → gabion-0.1.5}/src/gabion/analysis/dataflow_audit.py +315 -88
  40. {gabion-0.1.0 → gabion-0.1.5}/src/gabion/analysis/visitors.py +80 -0
  41. {gabion-0.1.0 → gabion-0.1.5}/src/gabion/cli.py +367 -144
  42. {gabion-0.1.0 → gabion-0.1.5}/src/gabion/config.py +8 -0
  43. {gabion-0.1.0 → gabion-0.1.5}/src/gabion/lsp_client.py +3 -2
  44. {gabion-0.1.0 → gabion-0.1.5}/src/gabion/refactor/engine.py +214 -23
  45. {gabion-0.1.0 → gabion-0.1.5}/src/gabion/refactor/model.py +1 -0
  46. {gabion-0.1.0 → gabion-0.1.5}/src/gabion/schema.py +2 -0
  47. {gabion-0.1.0 → gabion-0.1.5}/src/gabion/server.py +10 -7
  48. {gabion-0.1.0 → gabion-0.1.5}/src/gabion/synthesis/merge.py +0 -2
  49. {gabion-0.1.0 → gabion-0.1.5}/src/gabion/synthesis/model.py +1 -0
  50. gabion-0.1.5/tests/test_alias_return_propagation.py +48 -0
  51. gabion-0.1.5/tests/test_cli_commands.py +275 -0
  52. gabion-0.1.5/tests/test_cli_helpers.py +164 -0
  53. gabion-0.1.5/tests/test_cli_payloads.py +170 -0
  54. gabion-0.1.5/tests/test_config_edges.py +23 -0
  55. gabion-0.1.5/tests/test_constant_flow_audit.py +150 -0
  56. gabion-0.1.5/tests/test_dataflow_audit_edges.py +768 -0
  57. gabion-0.1.5/tests/test_dataflow_audit_flows.py +155 -0
  58. gabion-0.1.5/tests/test_dataflow_audit_run.py +112 -0
  59. gabion-0.1.5/tests/test_dataflow_class_resolution.py +100 -0
  60. gabion-0.1.5/tests/test_dataflow_dataclass_bundles.py +150 -0
  61. gabion-0.1.5/tests/test_dataflow_grouping.py +97 -0
  62. gabion-0.1.5/tests/test_dataflow_helpers.py +453 -0
  63. gabion-0.1.5/tests/test_dataflow_kitchen_sink.py +192 -0
  64. gabion-0.1.5/tests/test_dataflow_main.py +26 -0
  65. gabion-0.1.5/tests/test_dataflow_misc_edges.py +51 -0
  66. gabion-0.1.5/tests/test_dataflow_report_helpers.py +107 -0
  67. gabion-0.1.5/tests/test_dataflow_resolve_callee.py +272 -0
  68. gabion-0.1.5/tests/test_dataflow_run.py +124 -0
  69. gabion-0.1.5/tests/test_dataflow_run_edges.py +316 -0
  70. gabion-0.1.5/tests/test_knob_merge.py +48 -0
  71. gabion-0.1.5/tests/test_lsp_client_errors.py +21 -0
  72. gabion-0.1.5/tests/test_lsp_client_rpc.py +81 -0
  73. gabion-0.1.5/tests/test_lsp_client_run_command_di.py +59 -0
  74. gabion-0.1.5/tests/test_misc_coverage.py +156 -0
  75. {gabion-0.1.0 → gabion-0.1.5}/tests/test_refactor_engine.py +32 -0
  76. gabion-0.1.5/tests/test_refactor_engine_edges.py +129 -0
  77. gabion-0.1.5/tests/test_refactor_engine_helpers.py +108 -0
  78. gabion-0.1.5/tests/test_refactor_engine_more.py +320 -0
  79. gabion-0.1.5/tests/test_refactor_idempotency.py +87 -0
  80. gabion-0.1.5/tests/test_server_execute_command.py +136 -0
  81. gabion-0.1.5/tests/test_server_execute_command_edges.py +232 -0
  82. gabion-0.1.5/tests/test_server_helpers.py +55 -0
  83. {gabion-0.1.0 → gabion-0.1.5}/tests/test_synthesis_merge.py +6 -4
  84. {gabion-0.1.0 → gabion-0.1.5}/tests/test_synthesis_naming.py +13 -4
  85. {gabion-0.1.0 → gabion-0.1.5}/tests/test_synthesis_protocols.py +15 -0
  86. gabion-0.1.5/tests/test_type_flow_audit.py +52 -0
  87. gabion-0.1.5/tests/test_unused_arg_audit.py +189 -0
  88. gabion-0.1.5/tests/test_visitors_edges.py +409 -0
  89. gabion-0.1.5/tests/test_visitors_unit.py +187 -0
  90. gabion-0.1.0/.github/workflows/release-test.yml +0 -34
  91. gabion-0.1.0/.github/workflows/release.yml +0 -32
  92. gabion-0.1.0/gabion.toml +0 -4
  93. gabion-0.1.0/glossary.md +0 -221
  94. gabion-0.1.0/pytest.ini +0 -2
  95. gabion-0.1.0/tests/test_unused_arg_audit.py +0 -92
  96. {gabion-0.1.0 → gabion-0.1.5}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  97. {gabion-0.1.0 → gabion-0.1.5}/.github/ISSUE_TEMPLATE/sppf-node.yml +0 -0
  98. {gabion-0.1.0 → gabion-0.1.5}/.github/actions/gabion/README.md +0 -0
  99. {gabion-0.1.0 → gabion-0.1.5}/.github/actions/gabion/action.yml +0 -0
  100. {gabion-0.1.0 → gabion-0.1.5}/.github/actions/gabion/run.sh +0 -0
  101. {gabion-0.1.0 → gabion-0.1.5}/LICENSE +0 -0
  102. {gabion-0.1.0 → gabion-0.1.5}/Makefile +0 -0
  103. {gabion-0.1.0 → gabion-0.1.5}/artifacts/.gitkeep +0 -0
  104. {gabion-0.1.0 → gabion-0.1.5}/docs/doer_judge_witness.md +0 -0
  105. {gabion-0.1.0 → gabion-0.1.5}/docs/pinning_actions.md +0 -0
  106. {gabion-0.1.0 → gabion-0.1.5}/docs/workflows/gabion_action_example.yml +0 -0
  107. {gabion-0.1.0 → gabion-0.1.5}/extensions/vscode/README.md +0 -0
  108. {gabion-0.1.0 → gabion-0.1.5}/extensions/vscode/extension.js +0 -0
  109. {gabion-0.1.0 → gabion-0.1.5}/extensions/vscode/package.json +0 -0
  110. {gabion-0.1.0 → gabion-0.1.5}/in/AGENTS.md +0 -0
  111. {gabion-0.1.0 → gabion-0.1.5}/in/CONTRIBUTING.md +0 -0
  112. {gabion-0.1.0 → gabion-0.1.5}/in/README.md +0 -0
  113. {gabion-0.1.0 → gabion-0.1.5}/in/ci-milestones.yml +0 -0
  114. {gabion-0.1.0 → gabion-0.1.5}/in/dataflow_grammar_audit.py +0 -0
  115. {gabion-0.1.0 → gabion-0.1.5}/in/in-1.md +0 -0
  116. {gabion-0.1.0 → gabion-0.1.5}/in/in-10.md +0 -0
  117. {gabion-0.1.0 → gabion-0.1.5}/in/in-11.md +0 -0
  118. {gabion-0.1.0 → gabion-0.1.5}/in/in-12.md +0 -0
  119. {gabion-0.1.0 → gabion-0.1.5}/in/in-13.md +0 -0
  120. {gabion-0.1.0 → gabion-0.1.5}/in/in-14.md +0 -0
  121. {gabion-0.1.0 → gabion-0.1.5}/in/in-2.md +0 -0
  122. {gabion-0.1.0 → gabion-0.1.5}/in/in-3.md +0 -0
  123. {gabion-0.1.0 → gabion-0.1.5}/in/in-4.md +0 -0
  124. {gabion-0.1.0 → gabion-0.1.5}/in/in-5.md +0 -0
  125. {gabion-0.1.0 → gabion-0.1.5}/in/in-6.md +0 -0
  126. {gabion-0.1.0 → gabion-0.1.5}/in/in-7.md +0 -0
  127. {gabion-0.1.0 → gabion-0.1.5}/in/in-8.md +0 -0
  128. {gabion-0.1.0 → gabion-0.1.5}/in/in-9.md +0 -0
  129. {gabion-0.1.0 → gabion-0.1.5}/in/inspiration.md +0 -0
  130. {gabion-0.1.0 → gabion-0.1.5}/in/policy_check.py +0 -0
  131. {gabion-0.1.0 → gabion-0.1.5}/in/pr-dataflow-grammar.yml +0 -0
  132. {gabion-0.1.0 → gabion-0.1.5}/mise.toml +0 -0
  133. {gabion-0.1.0 → gabion-0.1.5}/scripts/audit_snapshot.sh +0 -0
  134. {gabion-0.1.0 → gabion-0.1.5}/scripts/bootstrap.sh +0 -0
  135. {gabion-0.1.0 → gabion-0.1.5}/scripts/clean_artifacts.sh +0 -0
  136. {gabion-0.1.0 → gabion-0.1.5}/scripts/dataflow_grammar_audit.py +0 -0
  137. {gabion-0.1.0 → gabion-0.1.5}/scripts/install_hooks.sh +0 -0
  138. {gabion-0.1.0 → gabion-0.1.5}/scripts/latest_snapshot.sh +0 -0
  139. {gabion-0.1.0 → gabion-0.1.5}/scripts/lsp_smoke_test.py +0 -0
  140. {gabion-0.1.0 → gabion-0.1.5}/scripts/run_tests.sh +0 -0
  141. {gabion-0.1.0 → gabion-0.1.5}/scripts/sppf_sync.py +0 -0
  142. {gabion-0.1.0 → gabion-0.1.5}/src/gabion/__main__.py +0 -0
  143. {gabion-0.1.0 → gabion-0.1.5}/src/gabion/analysis/__init__.py +0 -0
  144. {gabion-0.1.0 → gabion-0.1.5}/src/gabion/analysis/engine.py +0 -0
  145. {gabion-0.1.0 → gabion-0.1.5}/src/gabion/analysis/model.py +0 -0
  146. {gabion-0.1.0 → gabion-0.1.5}/src/gabion/refactor/__init__.py +0 -0
  147. {gabion-0.1.0 → gabion-0.1.5}/src/gabion/synthesis/__init__.py +0 -0
  148. {gabion-0.1.0 → gabion-0.1.5}/src/gabion/synthesis/naming.py +0 -0
  149. {gabion-0.1.0 → gabion-0.1.5}/src/gabion/synthesis/protocols.py +0 -0
  150. {gabion-0.1.0 → gabion-0.1.5}/src/gabion/synthesis/schedule.py +0 -0
  151. {gabion-0.1.0 → gabion-0.1.5}/tests/test_alias_attribute.py +0 -0
  152. {gabion-0.1.0 → gabion-0.1.5}/tests/test_alias_rename_invariance.py +0 -0
  153. {gabion-0.1.0 → gabion-0.1.5}/tests/test_alias_unpacking.py +0 -0
  154. {gabion-0.1.0 → gabion-0.1.5}/tests/test_baseline_ratchet.py +0 -0
  155. {gabion-0.1.0 → gabion-0.1.5}/tests/test_class_method_propagation.py +0 -0
  156. {gabion-0.1.0 → gabion-0.1.5}/tests/test_code_action_stub.py +0 -0
  157. {gabion-0.1.0 → gabion-0.1.5}/tests/test_config_declarations_anywhere.py +0 -0
  158. {gabion-0.1.0 → gabion-0.1.5}/tests/test_config_defaults.py +0 -0
  159. {gabion-0.1.0 → gabion-0.1.5}/tests/test_config_fields.py +0 -0
  160. {gabion-0.1.0 → gabion-0.1.5}/tests/test_dataclass_call_bundles.py +0 -0
  161. {gabion-0.1.0 → gabion-0.1.5}/tests/test_decorator_transparency.py +0 -0
  162. {gabion-0.1.0 → gabion-0.1.5}/tests/test_import_shadowing.py +0 -0
  163. {gabion-0.1.0 → gabion-0.1.5}/tests/test_lsp_smoke.py +0 -0
  164. {gabion-0.1.0 → gabion-0.1.5}/tests/test_nested_resolution.py +0 -0
  165. {gabion-0.1.0 → gabion-0.1.5}/tests/test_param_spans.py +0 -0
  166. {gabion-0.1.0 → gabion-0.1.5}/tests/test_refactor_plan.py +0 -0
  167. {gabion-0.1.0 → gabion-0.1.5}/tests/test_star_import_resolution.py +0 -0
  168. {gabion-0.1.0 → gabion-0.1.5}/tests/test_synthesis_merge_integration.py +0 -0
  169. {gabion-0.1.0 → gabion-0.1.5}/tests/test_synthesis_schedule.py +0 -0
  170. {gabion-0.1.0 → gabion-0.1.5}/tests/test_synthesis_stubs.py +0 -0
  171. {gabion-0.1.0 → gabion-0.1.5}/tests/test_synthesis_tiers.py +0 -0
  172. {gabion-0.1.0 → gabion-0.1.5}/tests/test_synthesis_types.py +0 -0
  173. {gabion-0.1.0 → gabion-0.1.5}/tests/test_wildcard_forwarding.py +0 -0
@@ -0,0 +1,72 @@
1
+ name: auto-test-tag
2
+
3
+ on:
4
+ workflow_run:
5
+ workflows: ["mirror-next"]
6
+ types: [completed]
7
+
8
+ permissions:
9
+ contents: write
10
+
11
+ jobs:
12
+ tag:
13
+ runs-on: ubuntu-latest
14
+ if: >
15
+ github.event.workflow_run.conclusion == 'success' &&
16
+ github.event.workflow_run.head_branch == 'main' &&
17
+ (github.event.workflow_run.actor.login == github.repository_owner ||
18
+ github.event.workflow_run.actor.login == 'github-actions[bot]')
19
+ steps:
20
+ - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
21
+ with:
22
+ fetch-depth: 0
23
+ ref: ${{ github.event.workflow_run.head_sha }}
24
+ - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065
25
+ with:
26
+ python-version: "3.11"
27
+ - name: Verify next mirrors main
28
+ run: |
29
+ git fetch origin main next --tags
30
+ RUN_SHA=$(git rev-parse HEAD)
31
+ MAIN_SHA=$(git rev-parse origin/main)
32
+ NEXT_SHA=$(git rev-parse origin/next)
33
+ if [ "$RUN_SHA" != "$MAIN_SHA" ]; then
34
+ echo "Next must mirror main before tagging."
35
+ echo "workflow=$RUN_SHA"
36
+ echo "main=$MAIN_SHA"
37
+ exit 1
38
+ fi
39
+ if [ "$NEXT_SHA" != "$MAIN_SHA" ]; then
40
+ echo "Next must mirror main before tagging."
41
+ echo "next=$NEXT_SHA"
42
+ echo "main=$MAIN_SHA"
43
+ exit 1
44
+ fi
45
+ - name: Read version
46
+ id: version
47
+ run: python scripts/release_read_project_version.py --output "$GITHUB_OUTPUT"
48
+ - name: Create test tag if missing
49
+ id: tag
50
+ run: |
51
+ STAMP="$(date -u +%Y%m%dT%H%M%SZ)"
52
+ TAG="test-v${{ steps.version.outputs.version }}+${STAMP}"
53
+ TARGET="$(git rev-parse origin/next)"
54
+ if git rev-parse -q --verify "refs/tags/$TAG" >/dev/null; then
55
+ echo "Tag already exists: $TAG"
56
+ echo "tag=" >> "$GITHUB_OUTPUT"
57
+ exit 0
58
+ fi
59
+ git config user.name "github-actions[bot]"
60
+ git config user.email "github-actions[bot]@users.noreply.github.com"
61
+ git tag -a "$TAG" -m "Test release $TAG" "$TARGET"
62
+ git push origin "refs/tags/$TAG"
63
+ echo "tag=$TAG" >> "$GITHUB_OUTPUT"
64
+ - name: Write tag artifact
65
+ if: steps.tag.outputs.tag != ''
66
+ run: echo "${{ steps.tag.outputs.tag }}" > tag.txt
67
+ - name: Upload tag artifact
68
+ if: steps.tag.outputs.tag != ''
69
+ uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
70
+ with:
71
+ name: auto-test-tag
72
+ path: tag.txt
@@ -4,9 +4,6 @@ on:
4
4
  push:
5
5
  branches:
6
6
  - stage
7
- pull_request:
8
- branches:
9
- - main
10
7
  workflow_dispatch:
11
8
 
12
9
  permissions:
@@ -26,14 +23,18 @@ jobs:
26
23
  uses: jdx/mise-action@6d1e696aa24c1aa1bcc1adea0212707c71ab78a8
27
24
  - name: Install toolchain
28
25
  run: mise install
29
- - name: Install package
30
- run: mise exec -- python -m pip install -e .
31
- - name: Install test deps
32
- run: mise exec -- python -m pip install pytest
33
- - name: Policy check (workflows)
26
+ - name: Create venv
34
27
  run: |
35
- mise exec -- python -m pip install pyyaml
36
- mise exec -- python scripts/policy_check.py --workflows
28
+ mise exec -- python -m venv .venv
29
+ echo "VIRTUAL_ENV=$PWD/.venv" >> $GITHUB_ENV
30
+ echo "$PWD/.venv/bin" >> $GITHUB_PATH
31
+ - name: Install dependencies (locked)
32
+ run: |
33
+ .venv/bin/python -m pip install --upgrade pip uv
34
+ .venv/bin/uv pip sync requirements.lock
35
+ .venv/bin/uv pip install -e .
36
+ - name: Policy check (workflows)
37
+ run: .venv/bin/python scripts/policy_check.py --workflows
37
38
  - name: Policy check (posture)
38
39
  if: github.event_name == 'push'
39
40
  env:
@@ -43,15 +44,25 @@ jobs:
43
44
  echo "POLICY_GITHUB_TOKEN not set; skipping posture check."
44
45
  exit 0
45
46
  fi
46
- mise exec -- python scripts/policy_check.py --posture
47
+ .venv/bin/python scripts/policy_check.py --posture
47
48
  - name: Dataflow audit
48
- run: mise exec -- python -m gabion check
49
+ run: |
50
+ mkdir -p artifacts/audit_reports
51
+ .venv/bin/python -m gabion check \
52
+ --report artifacts/audit_reports/dataflow_report.md \
53
+ --baseline baselines/dataflow_baseline.txt \
54
+ || (cat artifacts/audit_reports/dataflow_report.md; exit 1)
49
55
  - name: Docflow audit
50
- run: mise exec -- python scripts/docflow_audit.py --root . --fail-on-violations
56
+ run: .venv/bin/python scripts/docflow_audit.py --root . --fail-on-violations
51
57
  - name: Tests
52
58
  run: |
53
59
  mkdir -p artifacts/test_runs
54
- mise exec -- python -m pytest \
60
+ .venv/bin/python -m pytest \
61
+ --cov=src/gabion \
62
+ --cov-report=term-missing \
63
+ --cov-report=xml:artifacts/test_runs/coverage.xml \
64
+ --cov-report=html:artifacts/test_runs/htmlcov \
65
+ --cov-fail-under=100 \
55
66
  --junitxml artifacts/test_runs/junit.xml \
56
67
  --log-file artifacts/test_runs/pytest.log \
57
68
  --log-file-level=INFO
@@ -61,3 +72,9 @@ jobs:
61
72
  with:
62
73
  name: test-runs
63
74
  path: artifacts/test_runs
75
+ - name: Upload dataflow report
76
+ if: always()
77
+ uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
78
+ with:
79
+ name: dataflow-report
80
+ path: artifacts/audit_reports/dataflow_report.md
@@ -0,0 +1,32 @@
1
+ name: mirror-next
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+
8
+ permissions:
9
+ contents: write
10
+
11
+ jobs:
12
+ mirror:
13
+ runs-on: ubuntu-latest
14
+ if: github.ref == 'refs/heads/main' && github.actor == github.repository_owner
15
+ steps:
16
+ - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
17
+ with:
18
+ fetch-depth: 0
19
+ - name: Mirror main to next
20
+ run: |
21
+ git fetch origin main next
22
+ HEAD_SHA=$(git rev-parse HEAD)
23
+ MAIN_SHA=$(git rev-parse origin/main)
24
+ if [ "$HEAD_SHA" != "$MAIN_SHA" ]; then
25
+ echo "Mirror blocked: workflow SHA must equal current main."
26
+ echo "workflow=$HEAD_SHA"
27
+ echo "main=$MAIN_SHA"
28
+ exit 1
29
+ fi
30
+ git config user.name "github-actions[bot]"
31
+ git config user.email "github-actions[bot]@users.noreply.github.com"
32
+ git push --force-with-lease origin "$HEAD_SHA":refs/heads/next
@@ -2,11 +2,8 @@ name: pr-dataflow-grammar
2
2
 
3
3
  on:
4
4
  pull_request:
5
- paths:
6
- - "scripts/**"
7
- - "src/**"
8
- - "gabion.toml"
9
- - ".github/workflows/pr-dataflow-grammar.yml"
5
+ branches:
6
+ - main
10
7
 
11
8
  permissions:
12
9
  contents: read
@@ -15,6 +12,7 @@ jobs:
15
12
  dataflow-grammar:
16
13
  runs-on: ubuntu-latest
17
14
  permissions:
15
+ actions: read
18
16
  contents: read
19
17
  pull-requests: write
20
18
  env:
@@ -26,6 +24,66 @@ jobs:
26
24
  uses: jdx/mise-action@6d1e696aa24c1aa1bcc1adea0212707c71ab78a8
27
25
  - name: Install toolchain
28
26
  run: mise install
27
+ - name: Verify stage CI succeeded for this SHA
28
+ env:
29
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
30
+ REPO: ${{ github.repository }}
31
+ SHA: ${{ github.event.pull_request.head.sha }}
32
+ run: |
33
+ set -euo pipefail
34
+ python - <<'PY'
35
+ import json
36
+ import os
37
+ import time
38
+ import urllib.request
39
+
40
+ token = os.environ["GITHUB_TOKEN"]
41
+ repo = os.environ["REPO"]
42
+ sha = os.environ["SHA"]
43
+ deadline = time.time() + 20 * 60
44
+ url = (
45
+ f"https://api.github.com/repos/{repo}/actions/workflows/ci.yml/runs"
46
+ f"?branch=stage&per_page=50"
47
+ )
48
+
49
+ while True:
50
+ req = urllib.request.Request(
51
+ url,
52
+ headers={
53
+ "Authorization": f"Bearer {token}",
54
+ "Accept": "application/vnd.github+json",
55
+ },
56
+ )
57
+ with urllib.request.urlopen(req) as resp:
58
+ payload = json.loads(resp.read().decode("utf-8"))
59
+
60
+ runs = payload.get("workflow_runs", [])
61
+ match = next((r for r in runs if r.get("head_sha") == sha), None)
62
+ if match is None:
63
+ if time.time() > deadline:
64
+ raise SystemExit(
65
+ f"Stage CI has not run for {sha}. Push to stage and wait for CI."
66
+ )
67
+ time.sleep(15)
68
+ continue
69
+
70
+ status = match.get("status")
71
+ if status != "completed":
72
+ if time.time() > deadline:
73
+ raise SystemExit(
74
+ f"Stage CI for {sha} not complete (status={status})."
75
+ )
76
+ time.sleep(15)
77
+ continue
78
+
79
+ conclusion = match.get("conclusion")
80
+ if conclusion != "success":
81
+ raise SystemExit(
82
+ f"Stage CI for {sha} not successful (conclusion={conclusion})."
83
+ )
84
+ print(f"Stage CI OK for {sha}.")
85
+ break
86
+ PY
29
87
  - name: Install package
30
88
  run: mise exec -- python -m pip install -e .
31
89
  - name: Render dataflow grammar report
@@ -0,0 +1,64 @@
1
+ name: promote-release
2
+
3
+ on:
4
+ workflow_run:
5
+ workflows: ["release-testpypi"]
6
+ types: [completed]
7
+
8
+ permissions:
9
+ contents: write
10
+ actions: read
11
+
12
+ jobs:
13
+ promote:
14
+ runs-on: ubuntu-latest
15
+ if: >
16
+ github.event.workflow_run.conclusion == 'success' &&
17
+ (github.event.workflow_run.actor.login == github.repository_owner ||
18
+ github.event.workflow_run.actor.login == 'github-actions[bot]')
19
+ steps:
20
+ - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
21
+ with:
22
+ fetch-depth: 0
23
+ ref: ${{ github.event.workflow_run.head_sha }}
24
+ - name: Download test tag artifact
25
+ uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093
26
+ with:
27
+ name: release-testpypi-tag
28
+ run-id: ${{ github.event.workflow_run.id }}
29
+ github-token: ${{ secrets.GITHUB_TOKEN }}
30
+ path: artifacts/release-testpypi
31
+ - name: Promote next to release
32
+ run: |
33
+ git fetch origin main next release --tags
34
+ TAG=""
35
+ TAG_FILE=""
36
+ if [ -d artifacts/release-testpypi ]; then
37
+ TAG_FILE="$(find artifacts/release-testpypi -name tag.txt | head -n 1 || true)"
38
+ fi
39
+ if [ -n "$TAG_FILE" ] && [ -f "$TAG_FILE" ]; then
40
+ TAG="$(cat "$TAG_FILE")"
41
+ elif [[ "${{ github.event.workflow_run.head_branch }}" == test-v* ]]; then
42
+ TAG="${{ github.event.workflow_run.head_branch }}"
43
+ fi
44
+ if [ -z "$TAG" ]; then
45
+ echo "Missing test tag for promotion."
46
+ exit 1
47
+ fi
48
+ HEAD_SHA=$(git rev-parse HEAD)
49
+ NEXT_SHA=$(git rev-parse origin/next)
50
+ TAG_SHA=$(git rev-parse "refs/tags/$TAG^{commit}")
51
+ if [ "$HEAD_SHA" != "$TAG_SHA" ]; then
52
+ echo "Workflow head sha must match tag."
53
+ exit 1
54
+ fi
55
+ if [ "$NEXT_SHA" != "$HEAD_SHA" ]; then
56
+ echo "Next must mirror tested commit before promoting release."
57
+ echo "next=$NEXT_SHA"
58
+ echo "tested=$HEAD_SHA"
59
+ exit 1
60
+ fi
61
+ git merge-base --is-ancestor "$HEAD_SHA" origin/main
62
+ git config user.name "github-actions[bot]"
63
+ git config user.email "github-actions[bot]@users.noreply.github.com"
64
+ git push --force-with-lease origin "$HEAD_SHA":refs/heads/release
@@ -0,0 +1,89 @@
1
+ name: release-pypi
2
+ on:
3
+ push:
4
+ tags:
5
+ - "v*"
6
+ - "!test-v*"
7
+ workflow_run:
8
+ workflows: ["release-tag"]
9
+ types: [completed]
10
+ permissions:
11
+ contents: read
12
+ actions: read
13
+ id-token: write
14
+ jobs:
15
+ publish:
16
+ runs-on: ubuntu-latest
17
+ if: >
18
+ (github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')) ||
19
+ (github.event_name == 'workflow_run' &&
20
+ github.event.workflow_run.conclusion == 'success' &&
21
+ (github.event.workflow_run.actor.login == github.repository_owner ||
22
+ github.event.workflow_run.actor.login == 'github-actions[bot]'))
23
+ environment: pypi
24
+ steps:
25
+ - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
26
+ with:
27
+ fetch-depth: 0
28
+ ref: ${{ github.event.workflow_run.head_sha || github.sha }}
29
+ - name: Download release tag artifact
30
+ if: github.event_name == 'workflow_run'
31
+ uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093
32
+ with:
33
+ name: release-pypi-tag
34
+ run-id: ${{ github.event.workflow_run.id }}
35
+ github-token: ${{ secrets.GITHUB_TOKEN }}
36
+ path: artifacts/release-pypi
37
+ - name: Resolve release tag
38
+ id: tag
39
+ run: |
40
+ if [ "${{ github.event_name }}" = "push" ]; then
41
+ echo "tag=${GITHUB_REF_NAME}" >> "$GITHUB_OUTPUT"
42
+ exit 0
43
+ fi
44
+ if [ ! -f artifacts/release-pypi/tag.txt ]; then
45
+ echo "No tag artifact found; skipping."
46
+ echo "tag=" >> "$GITHUB_OUTPUT"
47
+ exit 0
48
+ fi
49
+ TAG="$(cat artifacts/release-pypi/tag.txt)"
50
+ if [ -z "$TAG" ]; then
51
+ echo "Empty tag; skipping."
52
+ echo "tag=" >> "$GITHUB_OUTPUT"
53
+ exit 0
54
+ fi
55
+ echo "tag=$TAG" >> "$GITHUB_OUTPUT"
56
+ - name: Persist release tag
57
+ if: steps.tag.outputs.tag != ''
58
+ run: |
59
+ mkdir -p artifacts/release-pypi
60
+ echo "${{ steps.tag.outputs.tag }}" > artifacts/release-pypi/tag.txt
61
+ - name: Upload release tag artifact
62
+ if: steps.tag.outputs.tag != ''
63
+ uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
64
+ with:
65
+ name: release-pypi-tag
66
+ path: artifacts/release-pypi/tag.txt
67
+ - name: Verify release tag provenance + version
68
+ if: steps.tag.outputs.tag != ''
69
+ run: python scripts/release_verify_pypi_tag.py --tag "${{ steps.tag.outputs.tag }}"
70
+ - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065
71
+ if: steps.tag.outputs.tag != ''
72
+ with:
73
+ python-version: "3.11"
74
+ - name: Build
75
+ if: steps.tag.outputs.tag != ''
76
+ run: |
77
+ python -m pip install --upgrade pip
78
+ python -m pip install build
79
+ python -m build
80
+ - name: Verify dist metadata
81
+ if: steps.tag.outputs.tag != ''
82
+ run: |
83
+ python -m pip install --upgrade twine pkginfo
84
+ python -m twine check dist/*
85
+ - name: Publish to PyPI
86
+ if: steps.tag.outputs.tag != ''
87
+ uses: pypa/gh-action-pypi-publish@03f86fee9ac21f854951f5c6e2a02c2a1324aec7
88
+ with:
89
+ verify-metadata: false
@@ -0,0 +1,30 @@
1
+ name: release-tag
2
+
3
+ on:
4
+ workflow_dispatch:
5
+ inputs:
6
+ tag:
7
+ description: "Release tag to create (vX.Y.Z or test-vX.Y.Z)"
8
+ required: true
9
+ type: string
10
+
11
+ permissions:
12
+ contents: write
13
+
14
+ jobs:
15
+ tag:
16
+ runs-on: ubuntu-latest
17
+ if: (github.ref == 'refs/heads/release' || github.ref == 'refs/heads/next') && github.actor == github.repository_owner
18
+ steps:
19
+ - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
20
+ with:
21
+ fetch-depth: 0
22
+ - name: Create and push tag
23
+ run: python scripts/release_tag.py --tag "${{ inputs.tag }}"
24
+ - name: Write release tag artifact
25
+ run: echo "${{ inputs.tag }}" > tag.txt
26
+ - name: Upload release tag artifact
27
+ uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
28
+ with:
29
+ name: release-pypi-tag
30
+ path: tag.txt
@@ -0,0 +1,92 @@
1
+ name: release-testpypi
2
+ on:
3
+ push:
4
+ tags:
5
+ - "test-v*"
6
+ workflow_run:
7
+ workflows: ["auto-test-tag"]
8
+ types: [completed]
9
+ permissions:
10
+ contents: read
11
+ actions: read
12
+ id-token: write
13
+ jobs:
14
+ publish:
15
+ runs-on: ubuntu-latest
16
+ if: >
17
+ (github.event_name == 'push' && startsWith(github.ref, 'refs/tags/test-v')) ||
18
+ (github.event_name == 'workflow_run' &&
19
+ github.event.workflow_run.conclusion == 'success' &&
20
+ (github.event.workflow_run.actor.login == github.repository_owner ||
21
+ github.event.workflow_run.actor.login == 'github-actions[bot]'))
22
+ environment: testpypi
23
+ steps:
24
+ - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
25
+ with:
26
+ fetch-depth: 0
27
+ ref: ${{ github.event.workflow_run.head_sha || github.sha }}
28
+ - name: Download tag artifact
29
+ if: github.event_name == 'workflow_run'
30
+ uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093
31
+ with:
32
+ name: auto-test-tag
33
+ run-id: ${{ github.event.workflow_run.id }}
34
+ github-token: ${{ secrets.GITHUB_TOKEN }}
35
+ path: artifacts/auto-test-tag
36
+ - name: Resolve test tag
37
+ id: tag
38
+ run: |
39
+ if [ "${{ github.event_name }}" = "push" ]; then
40
+ echo "tag=${GITHUB_REF_NAME}" >> "$GITHUB_OUTPUT"
41
+ exit 0
42
+ fi
43
+ if [ ! -f artifacts/auto-test-tag/tag.txt ]; then
44
+ echo "No tag artifact found; skipping."
45
+ echo "tag=" >> "$GITHUB_OUTPUT"
46
+ exit 0
47
+ fi
48
+ TAG="$(cat artifacts/auto-test-tag/tag.txt)"
49
+ if [ -z "$TAG" ]; then
50
+ echo "Empty tag; skipping."
51
+ echo "tag=" >> "$GITHUB_OUTPUT"
52
+ exit 0
53
+ fi
54
+ echo "tag=$TAG" >> "$GITHUB_OUTPUT"
55
+ - name: Persist test tag
56
+ if: steps.tag.outputs.tag != ''
57
+ run: |
58
+ mkdir -p artifacts/release-testpypi
59
+ echo "${{ steps.tag.outputs.tag }}" > artifacts/release-testpypi/tag.txt
60
+ - name: Upload test tag artifact
61
+ if: steps.tag.outputs.tag != ''
62
+ uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
63
+ with:
64
+ name: release-testpypi-tag
65
+ path: artifacts/release-testpypi/tag.txt
66
+ - name: Ensure tag is on main/next
67
+ if: steps.tag.outputs.tag != ''
68
+ run: python scripts/release_verify_test_tag.py --tag "${{ steps.tag.outputs.tag }}"
69
+ - name: Set version from test tag
70
+ if: steps.tag.outputs.tag != ''
71
+ run: python scripts/release_set_test_version.py --tag "${{ steps.tag.outputs.tag }}"
72
+ - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065
73
+ if: steps.tag.outputs.tag != ''
74
+ with:
75
+ python-version: "3.11"
76
+ - name: Build
77
+ if: steps.tag.outputs.tag != ''
78
+ run: |
79
+ python -m pip install --upgrade pip
80
+ python -m pip install build
81
+ python -m build
82
+ - name: Verify dist metadata
83
+ if: steps.tag.outputs.tag != ''
84
+ run: |
85
+ python -m pip install --upgrade twine pkginfo
86
+ python -m twine check dist/*
87
+ - name: Publish to TestPyPI
88
+ if: steps.tag.outputs.tag != ''
89
+ uses: pypa/gh-action-pypi-publish@03f86fee9ac21f854951f5c6e2a02c2a1324aec7
90
+ with:
91
+ repository-url: https://test.pypi.org/legacy/
92
+ verify-metadata: false
@@ -7,5 +7,6 @@ dist/
7
7
  venv/
8
8
  .env
9
9
  .pytest_cache/
10
+ .coverage
10
11
  artifacts/*
11
12
  !artifacts/.gitkeep
@@ -1,5 +1,5 @@
1
1
  ---
2
- doc_revision: 9
2
+ doc_revision: 12
3
3
  reader_reintern: "Reader-only: re-intern if doc_revision changed since you last read this doc."
4
4
  doc_id: agents
5
5
  doc_role: agent
@@ -13,6 +13,11 @@ doc_requires:
13
13
  - CONTRIBUTING.md
14
14
  - POLICY_SEED.md
15
15
  - glossary.md
16
+ doc_reviewed_as_of:
17
+ README.md: 58
18
+ CONTRIBUTING.md: 69
19
+ POLICY_SEED.md: 28
20
+ glossary.md: 13
16
21
  doc_change_protocol: "POLICY_SEED.md §6"
17
22
  doc_invariants:
18
23
  - read_policy_glossary_first
@@ -62,5 +67,6 @@ Semantic correctness is governed by `glossary.md` (co-equal contract).
62
67
  ## Doc hygiene
63
68
  - Markdown docs include a YAML front-matter block with `doc_revision`.
64
69
  - Bump `doc_revision` for conceptual changes.
70
+ - Record convergence in `doc_reviewed_as_of` (must match dependency revisions).
65
71
 
66
72
  If unsure, prefer refusal over unsafe compliance.
@@ -1,5 +1,5 @@
1
1
  ---
2
- doc_revision: 55
2
+ doc_revision: 69
3
3
  reader_reintern: "Reader-only: re-intern if doc_revision changed since you last read this doc."
4
4
  doc_id: contributing
5
5
  doc_role: guide
@@ -14,6 +14,13 @@ doc_requires:
14
14
  - AGENTS.md
15
15
  - POLICY_SEED.md
16
16
  - glossary.md
17
+ - docs/coverage_semantics.md
18
+ doc_reviewed_as_of:
19
+ README.md: 58
20
+ AGENTS.md: 12
21
+ POLICY_SEED.md: 28
22
+ glossary.md: 13
23
+ docs/coverage_semantics.md: 6
17
24
  doc_change_protocol: "POLICY_SEED.md §6"
18
25
  doc_invariants:
19
26
  - policy_glossary_handshake
@@ -66,9 +73,16 @@ Tier-3 bundles must be documented with `# dataflow-bundle:` or reified.
66
73
 
67
74
  ## Branching model (normative)
68
75
  - Routine work goes to `stage`; CI runs on every `stage` push and must be green.
76
+ - CI does not run on `main` pushes; PRs to `main` are for review and status checks.
69
77
  - `main` is protected and receives changes via PRs from `stage`.
70
- - Merges to `main` are regular merge commits (no squash).
78
+ - Merges to `main` are regular merge commits (no squash or rebase).
71
79
  - `stage` accumulates changes and may include merge commits from `main`.
80
+ - `next` mirrors `main` (no unique commits) and is updated after `main` merges.
81
+ - `release` mirrors `next` (no unique commits) and is updated only after `test-v*` succeeds.
82
+ - Test release tags are created via the `release-tag` workflow on `next`.
83
+ - Release tags are created via the `release-tag` workflow on `release` (no manual tags).
84
+ - `next` and `release` are automation-only branches. Human pushes are forbidden.
85
+ The `mirror-next` and `promote-release` workflows update them.
72
86
 
73
87
  ## Current analysis coverage (non-binding)
74
88
  These describe current coverage so contributors keep changes aligned:
@@ -176,6 +190,19 @@ To bypass hooks for a one-off command:
176
190
  GABION_SKIP_HOOKS=1 git commit
177
191
  ```
178
192
 
193
+ ## Locked dependencies
194
+ Dependencies are locked in `requirements.lock` (generated via `uv`).
195
+ Install the locked set and the editable package:
196
+ ```
197
+ mise exec -- uv pip sync requirements.lock
198
+ mise exec -- uv pip install -e .
199
+ ```
200
+ CI creates an explicit venv and installs the lock into it.
201
+ Regenerate the lockfile (after updating dependencies):
202
+ ```
203
+ uv pip compile pyproject.toml --extra dev -o requirements.lock
204
+ ```
205
+
179
206
  ## Editor integration (optional)
180
207
  The VS Code extension stub lives in `extensions/vscode` and launches the
181
208
  Gabion LSP server over stdio. It is a thin wrapper only.
@@ -189,15 +216,16 @@ mise exec -- python scripts/lsp_smoke_test.py --root .
189
216
  LSP smoke test (pytest) requires `pygls` to be installed. It will be skipped
190
217
  automatically if the dependency is missing.
191
218
 
192
- Install pytest (once):
219
+ Run tests:
193
220
  ```
194
- mise exec -- python -m pip install pytest
221
+ mise exec -- pytest
195
222
  ```
196
223
 
197
- Run tests:
224
+ Run coverage (advisory):
198
225
  ```
199
- mise exec -- pytest
226
+ mise exec -- python -m pytest --cov=src/gabion --cov-report=term-missing
200
227
  ```
228
+ Coverage meaning is defined in `docs/coverage_semantics.md`.
201
229
 
202
230
  Run tests with durable logs:
203
231
  ```