gozu 0.3.7__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 (156) hide show
  1. gozu-0.3.7/.dockerignore +12 -0
  2. gozu-0.3.7/.env.example +58 -0
  3. gozu-0.3.7/.github/workflows/ci.yml +32 -0
  4. gozu-0.3.7/.github/workflows/release.yml +69 -0
  5. gozu-0.3.7/.github/workflows/sonar-to-jira-main.yml +69 -0
  6. gozu-0.3.7/.gitignore +17 -0
  7. gozu-0.3.7/CLAUDE.md +187 -0
  8. gozu-0.3.7/Dockerfile +37 -0
  9. gozu-0.3.7/PKG-INFO +490 -0
  10. gozu-0.3.7/README.md +470 -0
  11. gozu-0.3.7/alembic.ini +160 -0
  12. gozu-0.3.7/docker-compose.yml +140 -0
  13. gozu-0.3.7/pyproject.toml +62 -0
  14. gozu-0.3.7/pytest.ini +5 -0
  15. gozu-0.3.7/sonar-project.properties +9 -0
  16. gozu-0.3.7/src/cli/__init__.py +3 -0
  17. gozu-0.3.7/src/cli/config_cmd/__init__.py +92 -0
  18. gozu-0.3.7/src/cli/config_cmd/edit.py +223 -0
  19. gozu-0.3.7/src/cli/config_lookup.py +87 -0
  20. gozu-0.3.7/src/cli/crash_handler.py +62 -0
  21. gozu-0.3.7/src/cli/help_links.py +38 -0
  22. gozu-0.3.7/src/cli/init_wizard/__init__.py +359 -0
  23. gozu-0.3.7/src/cli/init_wizard/env_step.py +85 -0
  24. gozu-0.3.7/src/cli/init_wizard/jira_step.py +87 -0
  25. gozu-0.3.7/src/cli/init_wizard/llm_step.py +20 -0
  26. gozu-0.3.7/src/cli/init_wizard/semgrep_step.py +21 -0
  27. gozu-0.3.7/src/cli/init_wizard/sonar_cloud.py +78 -0
  28. gozu-0.3.7/src/cli/init_wizard/sonar_local.py +118 -0
  29. gozu-0.3.7/src/cli/init_wizard/summary.py +50 -0
  30. gozu-0.3.7/src/cli/main.py +291 -0
  31. gozu-0.3.7/src/cli/prerequisites/__init__.py +22 -0
  32. gozu-0.3.7/src/cli/prerequisites/archive.py +106 -0
  33. gozu-0.3.7/src/cli/prerequisites/java.py +116 -0
  34. gozu-0.3.7/src/cli/prerequisites/semgrep.py +61 -0
  35. gozu-0.3.7/src/cli/prerequisites/sonar_scanner.py +108 -0
  36. gozu-0.3.7/src/cli/prerequisites/trivy.py +136 -0
  37. gozu-0.3.7/src/cli/prompts.py +49 -0
  38. gozu-0.3.7/src/cli/report.py +78 -0
  39. gozu-0.3.7/src/cli/scan_runner/__init__.py +197 -0
  40. gozu-0.3.7/src/cli/scan_runner/config_fields.py +28 -0
  41. gozu-0.3.7/src/cli/scan_runner/config_select.py +64 -0
  42. gozu-0.3.7/src/cli/scan_runner/scanner_exec.py +147 -0
  43. gozu-0.3.7/src/cli/scan_runner/semgrep_exec.py +24 -0
  44. gozu-0.3.7/src/cli/scan_runner/workflow_trigger.py +129 -0
  45. gozu-0.3.7/src/cli/stack/__init__.py +187 -0
  46. gozu-0.3.7/src/cli/stack/_stackfiles/Dockerfile +1 -0
  47. gozu-0.3.7/src/cli/stack/_stackfiles/__init__.py +0 -0
  48. gozu-0.3.7/src/cli/stack/_stackfiles/alembic.ini +1 -0
  49. gozu-0.3.7/src/cli/stack/_stackfiles/db/init_sonarqube_db.sql +29 -0
  50. gozu-0.3.7/src/cli/stack/_stackfiles/db/migrations/README +1 -0
  51. gozu-0.3.7/src/cli/stack/_stackfiles/db/migrations/env.py +85 -0
  52. gozu-0.3.7/src/cli/stack/_stackfiles/db/migrations/script.py.mako +28 -0
  53. gozu-0.3.7/src/cli/stack/_stackfiles/db/migrations/versions/0001_initial_schema.py +71 -0
  54. gozu-0.3.7/src/cli/stack/_stackfiles/db/migrations/versions/0002_add_project_key.py +32 -0
  55. gozu-0.3.7/src/cli/stack/_stackfiles/db/migrations/versions/0003_add_sonar_plan_and_branches.py +35 -0
  56. gozu-0.3.7/src/cli/stack/_stackfiles/db/migrations/versions/0004_add_ticket_claims.py +55 -0
  57. gozu-0.3.7/src/cli/stack/_stackfiles/db/migrations/versions/0005_add_ticket_destinations.py +67 -0
  58. gozu-0.3.7/src/cli/stack/_stackfiles/db/migrations/versions/0006_add_ticket_claims_status.py +34 -0
  59. gozu-0.3.7/src/cli/stack/_stackfiles/db/migrations/versions/0007_add_last_scan_tracking.py +38 -0
  60. gozu-0.3.7/src/cli/stack/_stackfiles/db/migrations/versions/0008_add_ticket_cap.py +32 -0
  61. gozu-0.3.7/src/cli/stack/_stackfiles/docker-compose.yml +1 -0
  62. gozu-0.3.7/src/cli/stack/_stackfiles/pyproject.toml +1 -0
  63. gozu-0.3.7/src/cli/stack/_stackfiles/uv.lock +1 -0
  64. gozu-0.3.7/src/cli/stack/backup.py +77 -0
  65. gozu-0.3.7/src/cli/stack/cleanup.py +123 -0
  66. gozu-0.3.7/src/cli/stack/files.py +104 -0
  67. gozu-0.3.7/src/cli/stack/profiles.py +140 -0
  68. gozu-0.3.7/src/cli/stack/status_report.py +59 -0
  69. gozu-0.3.7/src/cli/stack/webhooks.py +53 -0
  70. gozu-0.3.7/src/cli/stack/wipe.py +117 -0
  71. gozu-0.3.7/src/cli/status.py +44 -0
  72. gozu-0.3.7/src/cli/wizard_engine.py +99 -0
  73. gozu-0.3.7/src/config/__init__.py +3 -0
  74. gozu-0.3.7/src/config/connection.py +41 -0
  75. gozu-0.3.7/src/config/crypto.py +60 -0
  76. gozu-0.3.7/src/config/migrations.py +100 -0
  77. gozu-0.3.7/src/config/store.py +221 -0
  78. gozu-0.3.7/src/config/ticket_destinations.py +122 -0
  79. gozu-0.3.7/src/core/__init__.py +3 -0
  80. gozu-0.3.7/src/core/constants.py +11 -0
  81. gozu-0.3.7/src/core/errors.py +55 -0
  82. gozu-0.3.7/src/core/models.py +94 -0
  83. gozu-0.3.7/src/dev/seed_test_config.py +97 -0
  84. gozu-0.3.7/src/github_action/__init__.py +3 -0
  85. gozu-0.3.7/src/github_action/main.py +180 -0
  86. gozu-0.3.7/src/llm/__init__.py +2 -0
  87. gozu-0.3.7/src/llm/enrich.py +147 -0
  88. gozu-0.3.7/src/llm/factory.py +19 -0
  89. gozu-0.3.7/src/receiver/__init__.py +3 -0
  90. gozu-0.3.7/src/receiver/app.py +141 -0
  91. gozu-0.3.7/src/receiver/starter.py +49 -0
  92. gozu-0.3.7/src/receiver/verify_signature.py +19 -0
  93. gozu-0.3.7/src/scanner/__init__.py +3 -0
  94. gozu-0.3.7/src/scanner/base.py +116 -0
  95. gozu-0.3.7/src/scanner/factory.py +78 -0
  96. gozu-0.3.7/src/scanner/html_text.py +63 -0
  97. gozu-0.3.7/src/scanner/screenshot.py +124 -0
  98. gozu-0.3.7/src/scanner/semgrep_local.py +186 -0
  99. gozu-0.3.7/src/scanner/sonarqube_classify.py +50 -0
  100. gozu-0.3.7/src/scanner/sonarqube_cloud.py +98 -0
  101. gozu-0.3.7/src/scanner/sonarqube_common.py +187 -0
  102. gozu-0.3.7/src/scanner/sonarqube_local.py +29 -0
  103. gozu-0.3.7/src/scanner/trivy_client.py +173 -0
  104. gozu-0.3.7/src/scripts/__init__.py +3 -0
  105. gozu-0.3.7/src/scripts/bootstrap_env.py +150 -0
  106. gozu-0.3.7/src/scripts/env_ports.py +70 -0
  107. gozu-0.3.7/src/scripts/fernet_safety.py +72 -0
  108. gozu-0.3.7/src/scripts/paths.py +29 -0
  109. gozu-0.3.7/src/temporal/__init__.py +3 -0
  110. gozu-0.3.7/src/temporal/activities/__init__.py +10 -0
  111. gozu-0.3.7/src/temporal/activities/capture_and_attach_screenshot.py +67 -0
  112. gozu-0.3.7/src/temporal/activities/create_tickets.py +148 -0
  113. gozu-0.3.7/src/temporal/activities/fetch_findings.py +74 -0
  114. gozu-0.3.7/src/temporal/activities/reconcile_resolved_findings.py +139 -0
  115. gozu-0.3.7/src/temporal/data_converter.py +11 -0
  116. gozu-0.3.7/src/temporal/models/__init__.py +6 -0
  117. gozu-0.3.7/src/temporal/models/create_tickets.py +20 -0
  118. gozu-0.3.7/src/temporal/models/fetch_findings.py +30 -0
  119. gozu-0.3.7/src/temporal/models/reconcile_resolved_findings.py +19 -0
  120. gozu-0.3.7/src/temporal/models/screenshot_attach.py +20 -0
  121. gozu-0.3.7/src/temporal/models/sonar_to_jira.py +51 -0
  122. gozu-0.3.7/src/temporal/worker.py +77 -0
  123. gozu-0.3.7/src/temporal/workflows/__init__.py +6 -0
  124. gozu-0.3.7/src/temporal/workflows/multi_branch_scan.py +90 -0
  125. gozu-0.3.7/src/temporal/workflows/reconcile_only.py +54 -0
  126. gozu-0.3.7/src/temporal/workflows/scan_to_ticket.py +208 -0
  127. gozu-0.3.7/src/ticket/AtlassianDocumentFormat.py +31 -0
  128. gozu-0.3.7/src/ticket/TicketClient.py +71 -0
  129. gozu-0.3.7/src/ticket/__init__.py +3 -0
  130. gozu-0.3.7/src/ticket/claims.py +134 -0
  131. gozu-0.3.7/src/ticket/factory.py +52 -0
  132. gozu-0.3.7/src/ticket/jira_client.py +434 -0
  133. gozu-0.3.7/src/ticket/jira_sprint.py +82 -0
  134. gozu-0.3.7/tests/__init__.py +4 -0
  135. gozu-0.3.7/tests/cli/__init__.py +0 -0
  136. gozu-0.3.7/tests/cli/test_scan_runner_trivy.py +132 -0
  137. gozu-0.3.7/tests/cli/test_trivy_prerequisite.py +67 -0
  138. gozu-0.3.7/tests/github_action/__init__.py +3 -0
  139. gozu-0.3.7/tests/github_action/test_main.py +203 -0
  140. gozu-0.3.7/tests/llm/__init__.py +2 -0
  141. gozu-0.3.7/tests/llm/test_enrich.py +181 -0
  142. gozu-0.3.7/tests/scanner/__init__.py +3 -0
  143. gozu-0.3.7/tests/scanner/test_factory.py +37 -0
  144. gozu-0.3.7/tests/scanner/test_html_text.py +39 -0
  145. gozu-0.3.7/tests/scanner/test_screenshot.py +202 -0
  146. gozu-0.3.7/tests/scanner/test_semgrep_local.py +193 -0
  147. gozu-0.3.7/tests/scanner/test_trivy_client.py +248 -0
  148. gozu-0.3.7/tests/scanner/test_trivy_integration.py +113 -0
  149. gozu-0.3.7/tests/temporal/__init__.py +4 -0
  150. gozu-0.3.7/tests/temporal/activities/__init__.py +4 -0
  151. gozu-0.3.7/tests/temporal/activities/test_capture_and_attach_screenshot.py +153 -0
  152. gozu-0.3.7/tests/temporal/activities/test_create_tickets.py +234 -0
  153. gozu-0.3.7/tests/temporal/activities/test_fetch_findings.py +61 -0
  154. gozu-0.3.7/tests/ticket/__init__.py +2 -0
  155. gozu-0.3.7/tests/ticket/test_jira_client.py +314 -0
  156. gozu-0.3.7/uv.lock +1279 -0
@@ -0,0 +1,12 @@
1
+ # Copyright (c) 2026 Calfus Inc.
2
+ # Author: Wasiullah Rafeeq S
3
+
4
+ .git
5
+ .venv
6
+ venv
7
+ __pycache__
8
+ *.pyc
9
+ .pytest_cache
10
+ .ruff_cache
11
+ .scannerwork
12
+ .env
@@ -0,0 +1,58 @@
1
+ # Copyright (c) 2026 Calfus Inc.
2
+ # Author: Wasiullah Rafeeq S
3
+
4
+ # --- Phase 1 infrastructure (docker-compose.yml) ---
5
+ # Don't fill these in by hand - run `python src/scripts/bootstrap_env.py` once
6
+ # to generate a real .env with a random Postgres password and a fresh
7
+ # Fernet key. See README.md's "Phase 1: Infrastructure" section.
8
+ POSTGRES_USER=sonarjira
9
+ POSTGRES_PASSWORD=
10
+ POSTGRES_DB=sonarjira
11
+ POSTGRES_HOST=localhost
12
+ POSTGRES_PORT=5432
13
+
14
+ # Fernet key used by crypto_utils.py to encrypt/decrypt secrets stored in
15
+ # the configs table (sonar_token, jira_api_token, webhook_secret).
16
+ FERNET_KEY=
17
+
18
+ # Temporal server address. docker-compose.yml overrides this to
19
+ # temporal:7233 for the worker container.
20
+ TEMPORAL_HOST=localhost:7233
21
+
22
+ # --- SonarQube webhook ---
23
+ # Shared secret configured in the SonarQube webhook settings (Administration > Webhooks).
24
+ # SonarQube signs each webhook payload with this secret and sends the signature in the
25
+ # X-Sonar-Webhook-HMAC-SHA256 header.
26
+ SONAR_WEBHOOK_SECRET=changeme
27
+
28
+ # --- Scanner backend ---
29
+ # "sonarqube" = self-hosted SonarQube Community Build (SonarQubeServerClient)
30
+ # "sonarqube-cloud" = SonarQube Cloud (SonarQubeCloudClient) - not implemented yet
31
+ # (SONAR_MODE "local"/"cloud" is still read as a fallback if this is unset)
32
+ SCANNER_TYPE=sonarqube
33
+
34
+ # Base URL of your SonarQube instance
35
+ SONAR_HOST_URL=http://localhost:9000
36
+
37
+ # User token generated in SonarQube (My Account > Security > Generate Tokens)
38
+ SONAR_TOKEN=
39
+
40
+ # Only used when SONAR_MODE=cloud
41
+ SONAR_ORGANIZATION=
42
+
43
+ # --- Ticket backend ---
44
+ # "jira" = Jira Cloud (JiraClient) - the only implementation right now
45
+ TICKET_BACKEND=jira
46
+
47
+ # --- Jira ---
48
+ # Base URL of your Jira Cloud site, e.g. https://your-domain.atlassian.net
49
+ JIRA_URL=
50
+
51
+ # Email address of the Jira account the API token belongs to
52
+ JIRA_EMAIL=
53
+
54
+ # API token generated at https://id.atlassian.com/manage-profile/security/api-tokens
55
+ JIRA_API_TOKEN=
56
+
57
+ # Key of the Jira project tickets should be created in, e.g. "SONAR"
58
+ JIRA_PROJECT_KEY=
@@ -0,0 +1,32 @@
1
+ # Copyright (c) 2026 Calfus Inc.
2
+ # Author: Prakrit Mohanty
3
+ # Editor: Wasiullah Rafeeq S
4
+
5
+ name: CI
6
+
7
+ on:
8
+ push:
9
+ branches: [main]
10
+ pull_request:
11
+
12
+ jobs:
13
+ test:
14
+ runs-on: ubuntu-latest
15
+ steps:
16
+ - uses: actions/checkout@v4
17
+
18
+ - name: Install uv
19
+ uses: astral-sh/setup-uv@v3
20
+ with:
21
+ version: "latest"
22
+ enable-cache: true
23
+ cache-dependency-glob: "uv.lock"
24
+
25
+ - name: Set up Python
26
+ run: uv python install 3.12
27
+
28
+ - name: Install dependencies
29
+ run: uv sync
30
+
31
+ - name: Run tests
32
+ run: uv run pytest -v
@@ -0,0 +1,69 @@
1
+ # Copyright (c) 2026 Calfus Inc.
2
+ # Author: Prakrit Mohanty
3
+ # Editor: Wasiullah Rafeeq S
4
+
5
+ name: Release
6
+
7
+ # Triggered by pushing a version tag, e.g.:
8
+ # git tag v0.1.0 && git push origin v0.1.0
9
+ # The tag (minus its leading "v") must match pyproject.toml's [project]
10
+ # version exactly - the version-check step below fails fast if they drift,
11
+ # rather than silently publishing a release with the wrong version string.
12
+
13
+ on:
14
+ push:
15
+ tags:
16
+ - "v*"
17
+
18
+ permissions:
19
+ contents: write
20
+ id-token: write
21
+
22
+ jobs:
23
+ build-and-release:
24
+ runs-on: ubuntu-latest
25
+ steps:
26
+ - uses: actions/checkout@v4
27
+
28
+ - name: Install uv
29
+ uses: astral-sh/setup-uv@v3
30
+ with:
31
+ version: "latest"
32
+ enable-cache: true
33
+ cache-dependency-glob: "uv.lock"
34
+
35
+ - name: Set up Python
36
+ run: uv python install 3.12
37
+
38
+ - name: Check tag matches pyproject.toml version
39
+ run: |
40
+ tag_version="${GITHUB_REF_NAME#v}"
41
+ pkg_version="$(uv run python -c 'import tomllib; print(tomllib.load(open("pyproject.toml", "rb"))["project"]["version"])')"
42
+ if [ "$tag_version" != "$pkg_version" ]; then
43
+ echo "::error::Tag $GITHUB_REF_NAME (version $tag_version) doesn't match pyproject.toml's version $pkg_version"
44
+ exit 1
45
+ fi
46
+
47
+ - name: Install dependencies
48
+ run: uv sync
49
+
50
+ - name: Run tests
51
+ run: uv run pytest -v
52
+
53
+ - name: Build wheel and sdist
54
+ run: uv build
55
+
56
+ # Trusted publishing (OIDC): the "gozu" project on PyPI trusts this
57
+ # repo's release.yml, so no PyPI token/secret is needed - uv exchanges
58
+ # the id-token permission above for a short-lived upload credential.
59
+ # Runs before the GitHub Release so a failed upload leaves no release.
60
+ - name: Publish to PyPI
61
+ run: uv publish
62
+
63
+ - name: Create GitHub Release
64
+ env:
65
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
66
+ run: |
67
+ gh release create "$GITHUB_REF_NAME" dist/* \
68
+ --title "$GITHUB_REF_NAME" \
69
+ --generate-notes
@@ -0,0 +1,69 @@
1
+ # Copyright (c) 2026 Calfus Inc.
2
+ # Author: Prakrit Mohanty
3
+
4
+ name: Sonar to Jira (main)
5
+
6
+ # Reacts to SonarQube Cloud's Automatic Analysis (which scans `main` on
7
+ # every push automatically, no scan step needed here - see
8
+ # docs.sonarsource.com/sonarqube-cloud/analyzing-source-code/automatic-analysis)
9
+ # by waiting for that analysis to land, then creating Jira tickets for new
10
+ # findings - see src/github_action/main.py.
11
+ #
12
+ # Deliberately scoped to `main` only: SonarQube Cloud's Free plan rejects
13
+ # querying issue data for any other branch ("Organization is not allowed
14
+ # to access data from non main branches" - confirmed live, see
15
+ # src/cli/scan_runner/scanner_exec.py's comment), so a PR-time equivalent of
16
+ # this workflow cannot work on Free.
17
+ #
18
+ # Runs entirely inside GitHub's own runner - unlike gozu's webhook trigger
19
+ # mode, this needs no receiver reachable from the public internet, only
20
+ # outbound calls to SonarQube Cloud's and Jira's APIs.
21
+
22
+ on:
23
+ push:
24
+ branches: [main]
25
+
26
+ concurrency:
27
+ group: sonar-to-jira-main
28
+ cancel-in-progress: false
29
+
30
+ jobs:
31
+ create-tickets:
32
+ runs-on: ubuntu-latest
33
+ steps:
34
+ - uses: actions/checkout@v4
35
+
36
+ - name: Set up Python
37
+ uses: actions/setup-python@v5
38
+ with:
39
+ python-version: "3.12"
40
+
41
+ # Installs from this exact checkout, not a GitHub Release URL -
42
+ # this repo is private, and pip has no credentials to fetch a
43
+ # private repo's release asset (confirmed live: a plain
44
+ # `pip install <release url>` 404s here even though the release
45
+ # itself exists - GitHub returns 404, not 401/403, for a private
46
+ # resource fetched unauthenticated). Installing from the checkout
47
+ # also means this never drifts from whatever version is actually on
48
+ # main. Swap for `pip install gozu` once this is published to PyPI.
49
+ - name: Install gozu
50
+ run: pip install .
51
+
52
+ - name: Wait for analysis and create Jira tickets
53
+ env:
54
+ SCANNER_TYPE: sonarqube-cloud
55
+ SONAR_HOST_URL: https://sonarcloud.io
56
+ SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
57
+ SONAR_ORGANIZATION: ${{ secrets.SONAR_ORGANIZATION }}
58
+ SONAR_PROJECT_KEY: ${{ secrets.SONAR_PROJECT_KEY }}
59
+ TICKET_BACKEND: jira
60
+ JIRA_URL: ${{ secrets.JIRA_URL }}
61
+ JIRA_EMAIL: ${{ secrets.JIRA_EMAIL }}
62
+ JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }}
63
+ JIRA_PROJECT_KEY: ${{ secrets.JIRA_PROJECT_KEY }}
64
+ COMMIT_TIMESTAMP: ${{ github.event.head_commit.timestamp }}
65
+ # Optional - blank/unset disables LLM-generated ticket explanations
66
+ # for Blocker/Critical/High findings (see src/llm/enrich.py); tickets
67
+ # fall back to today's templated description either way.
68
+ ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
69
+ run: gozu-github-action
gozu-0.3.7/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ # Copyright (c) 2026 Calfus Inc.
2
+ # Author: Wasiullah Rafeeq S
3
+
4
+ .env
5
+ docker-compose.override.yml
6
+ .venv/
7
+ venv/
8
+ __pycache__/
9
+ *.pyc
10
+ .ruff_cache/
11
+ .scannerwork/
12
+
13
+ # ARCHITECTURE.md and Setup.md live here now - kept on disk for local
14
+ # reference but no longer tracked (README.md/CLAUDE.md stay at repo root,
15
+ # tracked, since GitHub's repo homepage and Claude Code's own context
16
+ # loading both depend on those two specific paths).
17
+ docs/
gozu-0.3.7/CLAUDE.md ADDED
@@ -0,0 +1,187 @@
1
+ # Project conventions
2
+
3
+ ## The product
4
+
5
+ This repo is being built into a commercial CLI tool called **Gozu** (the
6
+ name is decided, not a placeholder - use it consistently: the command is
7
+ `gozu`, host-side dirs like `~/.gozu/`). The repo/working directory stays
8
+ `sonar-to-jira` for now - don't rename it without checking first, since
9
+ that's a separate decision from the product's own name.
10
+ It's scanner- and ticket-backend-agnostic by design: users scan code with
11
+ a pluggable choice of scanners (currently SonarQube, Trivy, and Semgrep) and get
12
+ tickets auto-created in a pluggable choice of ticket-tracking backends
13
+ (currently Jira), orchestrated with Temporal. These are the first concrete
14
+ implementations, not the definition of the tool - don't write new
15
+ docs/help text that implies gozu is confined to either. The build is happening in
16
+ phases (Phase 1: Docker Compose infra + encrypted config store. Phase 2:
17
+ `gozu init`, the setup wizard - see README.md for both); check with
18
+ the user before assuming a later phase's scope (e.g. `gozu run`,
19
+ actually running a scan) is open to work on.
20
+
21
+ ## CLI conventions (Phase 2+)
22
+
23
+ - The `src/cli/` package holds everything CLI-specific: `main.py` (the typer
24
+ app), `init_wizard/`, `scan_runner/`, `prerequisites/` (each a small
25
+ package, one file per step/concern - see their own docstrings), plus
26
+ `help_links.py` and `stack.py`.
27
+ - Every interactive select/confirm prompt uses **questionary** (arrow-key
28
+ menus), not typed option strings or plain `input()` - this is a fixed
29
+ choice for this project, not a per-prompt toss-up, given how many
30
+ branching selects the wizard has.
31
+ - Packaging is **uv**, not pip/`requirements.txt` (removed). Dependencies
32
+ live in `pyproject.toml`'s `[project.dependencies]`, pinned via
33
+ `uv.lock`. Install with `uv sync`; run things with `uv run <cmd>` or an
34
+ activated `.venv`. The worker `Dockerfile` builds via `uv sync --frozen`
35
+ against the same `pyproject.toml`/`uv.lock`, not a separate
36
+ requirements file - keep it that way rather than hand-maintaining two
37
+ dependency lists.
38
+ - A credential/doc-link helper (`src/cli/help_links.py`'s `HELP_LINKS`) must
39
+ use real, current URLs looked up live (WebSearch or equivalent) - never
40
+ a plausible-looking guessed URL.
41
+
42
+ ## Naming: "configs", not "profiles"
43
+
44
+ A saved, named set of scanner + ticket-backend credentials (see
45
+ `db/migrations/versions/0001_initial_schema.py`'s `configs` table, `src/config/store.py`) is called a **config**,
46
+ never a "profile". Docker Compose already has an unrelated built-in concept
47
+ called profiles (`docker-compose.yml`'s `profiles: [...]` on the
48
+ `sonarqube` service, for conditionally starting services) - reusing "profile"
49
+ for credential sets would be confusing throughout the codebase and docs
50
+ given both concepts exist side by side in this project.
51
+
52
+ ## Data models
53
+
54
+ Use **Pydantic `BaseModel`** for every data model in this project - never
55
+ `dataclasses.dataclass` or plain classes. This includes types that cross a
56
+ Temporal workflow/activity boundary (`Finding`, `CreatedTicket`,
57
+ `TicketResult`, `SonarToJiraInput`, `ScreenshotAttachInput`) and any new
58
+ ones added later (e.g. `ScannerRequirements`, `FindingExtraction`). The
59
+ Pydantic-aware data converter (`src/temporal/data_converter.py`) serializes
60
+ `BaseModel`s across that boundary automatically - a plain dataclass or
61
+ dict does not, and needs manual conversion, which is exactly what we
62
+ removed by standardizing on Pydantic.
63
+
64
+ ## Logging
65
+
66
+ Use **exactly one** logging mechanism inside the Temporal-executed pipeline:
67
+
68
+ - `workflow.logger` (from `temporalio.workflow`) inside workflow code
69
+ (`src/temporal/workflows/`).
70
+ - `activity.logger` (from `temporalio.activity`) everywhere else that runs
71
+ inside an activity - the activity functions themselves
72
+ (`src/temporal/activities/`) and every module they call into
73
+ (`src/scanner/`, `src/ticket/`).
74
+
75
+ Both are Temporal's contextual loggers - they tag every line with
76
+ workflow/activity id, run id, attempt number, etc, and route to the same
77
+ place `workflow.logger`/`activity.logger` already do. Don't create a plain
78
+ `logging.getLogger(__name__)` logger in any file that runs inside an
79
+ activity or workflow.
80
+
81
+ The only exception is the two process entrypoints, which run before any
82
+ Temporal workflow/activity context exists: `src/receiver/app.py` (Flask, before
83
+ a workflow is started) and `src/temporal/worker.py` (bootstrap, before
84
+ `worker.run()`). Those use plain `logging.basicConfig`/`logging.getLogger`
85
+ because there's nothing to attach Temporal context to yet.
86
+
87
+ Do not introduce a third logging approach (e.g. `print`, a custom logger
88
+ wrapper, structlog, etc) anywhere in this project.
89
+
90
+ ## Code style: modular, reusable, no needless duplication
91
+
92
+ Favor small, single-responsibility functions over long ones, and pull out
93
+ a shared helper the moment the same shape of logic appears twice - don't
94
+ wait for a third copy. Concretely, follow the patterns already in this
95
+ codebase rather than inventing new ones:
96
+
97
+ - **Pure-builder + env-reading-wrapper split**, for anything that can be
98
+ configured either explicitly or from the environment: `src/scanner/factory.py`'s
99
+ `build_scanner_client(scanner_type, scanner_mode, credentials)` (no env
100
+ reads) vs `get_scanner_client()` (reads env vars, delegates to the
101
+ builder); `src/ticket/factory.py`'s `build_ticket_client()`/`get_ticket_client()`
102
+ mirror it. When adding a new backend or a new per-config code path, add
103
+ to the pure builder and let the env-reading wrapper stay a thin
104
+ translation layer - don't duplicate the branching in both.
105
+ - **Extract shared download/verify logic**, not per-tool copies: see
106
+ `src/cli/prerequisites/archive.py`'s `download_archive()`/`extract_archive()`/
107
+ `make_tree_executable()`/`verify_runnable()`, shared by `java.py`'s
108
+ `ensure_java()` and `sonar_scanner.py`'s `ensure_sonar_scanner()`. A third
109
+ `ensure_*()` for a new host dependency should reuse these, not reimplement
110
+ its own download loop. Same idea for small derived values used in more
111
+ than one place - e.g. `src/cli/scan_runner/config_fields.py`'s
112
+ `scanner_host_url(config)`, computed once and reused rather than
113
+ recomputed per call site.
114
+ - **Dedicated, narrow Pydantic models per activity boundary** (interface
115
+ segregation) rather than one bloated shared input model: `fetch_findings_activity`
116
+ takes `FetchFindingsInput`, `create_tickets_activity` takes
117
+ `CreateTicketsInput`, not the full `SonarToJiraInput` - each activity
118
+ only sees the fields it actually needs.
119
+
120
+ This isn't a call for premature abstraction - three near-identical lines
121
+ inline are still fine. It's specifically about not letting the same
122
+ piece of logic (a download-and-verify flow, a client-construction
123
+ branch, a derived value) exist in more than one place at a time.
124
+
125
+ ## File headers: copyright/author on every file, every machine
126
+
127
+ Every source file in this repo carries a header, added retroactively in
128
+ one pass and required on every new file since - this must happen the same
129
+ way regardless of who's working or which machine, not something to
130
+ remember case-by-case:
131
+
132
+ ```
133
+ # Copyright (c) 2026 Calfus Inc.
134
+ # Author: <original author's real name>
135
+ # Editor: <anyone else who has since modified this file, comma-separated>
136
+ ```
137
+
138
+ No license line - ownership is still being confirmed with the company;
139
+ don't add one until that's resolved. Comment syntax matches the file
140
+ (`#` for Python/YAML/TOML/Dockerfile/plain config files, `--` for
141
+ `.sql` files, `//` for anything C-style). Place it after a
142
+ shebang line where one exists (the shebang must stay the very first
143
+ line), before everything else - including a module docstring - otherwise.
144
+
145
+ - **Author** is whoever actually wrote the file first - for a brand new
146
+ file being created in this session, that's the user driving the
147
+ session, by their real name (check the git identity/context for this
148
+ repo rather than guessing or using "Claude"). Never invent a name.
149
+ - **Editor** is anyone else who has since modified that same file -
150
+ omit the line entirely if there isn't one yet. When someone other than
151
+ the original author changes an existing file, add their name to the
152
+ Editor line if it isn't already there (comma-separate multiple
153
+ editors); don't touch Author once it's set. Same rule as Author:
154
+ real people only, by their real name (check the git identity/context
155
+ for this repo) - never "Claude", even when Claude is the one literally
156
+ typing the edit. Claude is a tool the person is using, not a credited
157
+ contributor. If the resulting editor would be the same person as
158
+ Author, don't add an Editor line at all - that's already covered by
159
+ Author.
160
+ - **Dependency line** (optional, added under a blank `#`): a factual,
161
+ one-line "Depends on: X (Vendor) - what for" note, ONLY for a file that
162
+ directly talks to one of the four things this product's identity
163
+ depends on - SonarQube (SonarSource), Jira (Atlassian), Temporal
164
+ (Temporal Technologies), or PostgreSQL - and only when that file is
165
+ actually doing the talking (a direct API/SDK call), not just adjacent
166
+ to one. No language implying partnership, endorsement, or certification
167
+ by the third party - state the dependency, nothing more. Most files get
168
+ no dependency line at all; don't force one in where it doesn't apply.
169
+ - **In-house dependency line(s)** (same block, one line per dependency):
170
+ a file with a genuine load-bearing dependency on another module
171
+ *within this repo* also gets its own "Depends on: <repo-relative file
172
+ path> - what for" line - e.g. "Depends on: src/config/store.py - reads/
173
+ writes the configs table". Repo-relative file path, not an import
174
+ statement or module dotted-path. Same bar as the vendor line above:
175
+ only the specific module(s) a file is actually built on top of to do
176
+ its own job (what it calls into), not every module it happens to
177
+ import - a shared status/glyph/constants helper doesn't count. One
178
+ line per dependency when there's more than one. Most files still get
179
+ none.
180
+ - Markdown files (`README.md`, `ARCHITECTURE.md`, this file) are
181
+ deliberately excluded - no comment syntax was ever specified for them,
182
+ and guessing one (e.g. an HTML comment block) isn't this convention's
183
+ call to make unilaterally. Generated/vendored files (e.g. `uv.lock`)
184
+ are excluded too - never hand-edit those anyway.
185
+ - Skip this convention only if the user explicitly asks to - never
186
+ silently omit a header because a file feels too small or too generated
187
+ to bother.
gozu-0.3.7/Dockerfile ADDED
@@ -0,0 +1,37 @@
1
+ # Copyright (c) 2026 Calfus Inc.
2
+ # Author: Wasiullah Rafeeq S
3
+
4
+ FROM ghcr.io/astral-sh/uv:latest AS uv
5
+
6
+ FROM python:3.12-slim
7
+
8
+ ENV PYTHONUNBUFFERED=1
9
+ ENV PYTHONPATH=/app/src
10
+ ENV UV_PROJECT_ENVIRONMENT=/usr/local
11
+ ENV UV_LINK_MODE=copy
12
+
13
+ WORKDIR /app
14
+
15
+ COPY --from=uv /uv /uvx /usr/local/bin/
16
+
17
+ # src/scanner/screenshot.py's render_finding_snippet() needs Pygments'
18
+ # ImageFormatter to find an actual monospace font - confirmed live that
19
+ # python:3.12-slim has neither fontconfig (`fc-list`) nor any font at
20
+ # all, which makes ImageFormatter raise FileNotFoundError outright, not
21
+ # just render ugly. fonts-dejavu-core is the specific package that
22
+ # provides DejaVu Sans Mono, the font ImageFormatter looks for first;
23
+ # both packages together add a few MB, nowhere near what the Chromium
24
+ # install this replaced used to cost.
25
+ RUN apt-get update && apt-get install -y --no-install-recommends \
26
+ fontconfig fonts-dejavu-core \
27
+ && rm -rf /var/lib/apt/lists/*
28
+
29
+ COPY pyproject.toml uv.lock ./
30
+ RUN --mount=type=cache,target=/root/.cache/uv \
31
+ uv sync --frozen --no-install-project --no-group dev
32
+
33
+ COPY . .
34
+ RUN --mount=type=cache,target=/root/.cache/uv \
35
+ uv sync --frozen --no-group dev
36
+
37
+ CMD ["python", "-m", "temporal.worker"]