Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions .codecov.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Codecov Configuration for RepForge (Devasy/RepForge)
codecov:
require_ci_to_pass: yes

coverage:
precision: 2
round: down
range: "70...100"

status:
project:
default:
target: auto
threshold: 1%
patch:
default:
target: auto

ignore:
- "**/*.g.dart"
- "**/*.freezed.dart"
- "workout-logger/test/**/*"
19 changes: 15 additions & 4 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ on:
- main
workflow_dispatch:

concurrency:
group: release-${{ github.ref }}
cancel-in-progress: false

jobs:
release:
name: Build and Release APK
Expand All @@ -18,17 +22,20 @@ jobs:

steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v7

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Workflow relevant lines:"
for f in .github/workflows/release.yml .github/workflows/test.yml; do
  if [ -f "$f" ]; then
    echo "--- $f"
    grep -n 'uses:\|pin|full commit|commit hash' "$f" || true
  fi
done

echo
echo "All action refs in workflows:"
python3 - <<'PY'
import pathlib, re
for p in sorted(pathlib.Path('.github/workflows').glob('*.yml')) + sorted(pathlib.Path('.github/workflows').glob('*.yaml')):
    for i,line in enumerate(p.read_text().splitlines(),1):
        m=re.search(r'uses:\s*([^#\s]+)\s*#?\s*(.*)', line.strip())
        if m:
            print(f"{p}:{i}: {m.group(1)}")
PY

Repository: Devasy/RepForge

Length of output: 1190


🌐 Web query:

GitHub Actions recommended secure workflow pinning commit SHA version tags

💡 Result:

To secure GitHub Actions workflows against supply chain attacks, GitHub and security experts strongly recommend pinning actions to a full-length (40-character) commit SHA [1][2][3][4]. Why Pinning to Commit SHAs is Necessary Standard version tags (e.g., @v4) are mutable references [3][4][5]. If an action's repository is compromised, an attacker can overwrite these tags to point to malicious code, which your workflows would then automatically execute [6][3][4][7]. Because Git tags can be moved or deleted, they do not guarantee that the code running today is the same code you audited previously [8][5][9]. A full-length commit SHA, by contrast, is a unique, immutable cryptographic digest [6][8][9]. Pinning to a specific SHA ensures your workflow always executes the exact code you intended, preventing silent, malicious updates [6][4][5][9]. Recommended Implementation To maintain both security and ease of maintenance, follow these best practices: 1. Use Full-Length SHAs: Always use the 40-character full commit SHA. Never use shortened SHAs, as they are susceptible to collision attacks [1][2][8]. 2. Keep Human-Readable References: Include the version tag as a trailing comment in your workflow file (e.g., uses: actions/checkout@a1b2c3d4e5f6... # v4.2.0) [3][4][9]. This allows automated dependency management tools like Dependabot and Renovate to detect new versions and open pull requests, ensuring you can update your actions securely while maintaining immutability [3][4][7]. 3. Enforce via Policy: If you are an administrator, you can enforce this security standard across your organization or enterprise by enabling the GitHub Actions policy that requires actions to be pinned to a full-length commit SHA [6][2][5]. When enabled, any workflow using a mutable reference (tags or branches) will fail [6][5]. Security Note: While SHA pinning prevents tag-retargeting attacks, it does not protect against code that was already malicious at the time of the initial pin [7]. Always continue to vet the actions and authors you choose to include in your pipelines [10][11].

Citations:


🌐 Web query:

actions/checkout release v5 v4 tag commit SHA

💡 Result:

In the actions/checkout repository, both v4 and v5 are major release versions [1][2]. The transition from v4 to v5 primarily involved updating the underlying Node.js runtime from Node.js 20 to Node.js 24 [3][4][5]. This update requires the use of GitHub Actions runners with version v2.327.1 or higher [5]. Regarding the use of "tag" or "commit SHA" with these versions: 1. Usage: You can reference any specific version (v4, v5, v6, v7, etc.) in your workflow file using the syntax actions/checkout@v [4][6][2]. 2. Specifying Ref/SHA: Regardless of the action version used (@v4, @v5, etc.), you can checkout a specific branch, tag, or commit SHA by using the ref input [2][7]. Example: - uses: actions/checkout@v5 with: ref: By default, the action checks out the ref or SHA that triggered the workflow [5][2]. Specifying a ref in the with block allows you to override this default behavior [2][7].

Citations:


Pin the remaining GitHub Actions to full commit hashes.

The listed mutable action tags can move or be replaced, changing pipeline behavior without a repository commit. Pin each action to a full 40-character commit SHA and keep the release name in a trailing comment.

🧰 Tools
🪛 zizmor (1.29.0)

[error] 25-25: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

📍 Affects 2 files
  • .github/workflows/release.yml#L25-L25 (this comment)
  • .github/workflows/release.yml#L31-L31
  • .github/workflows/release.yml#L37-L37
  • .github/workflows/release.yml#L164-L164
  • .github/workflows/test.yml#L29-L29
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/release.yml at line 25, Pin every listed GitHub Action to
a full 40-character commit SHA instead of a mutable tag, preserving the action
release name in a trailing comment. Apply this to .github/workflows/release.yml
lines 25-25, 31-31, 37-37, and 164-164, and .github/workflows/test.yml line
29-29.

Source: Linters/SAST tools

with:
fetch-depth: 0 # Fetch all history for proper versioning
token: ${{ secrets.GITHUB_TOKEN }}

- name: Set up Java
uses: actions/setup-java@v4
uses: actions/setup-java@v5
with:
distribution: 'temurin'
java-version: '17'

- name: Setup Gradle Build Cache
uses: gradle/actions/setup-gradle@v6

- name: Set up Flutter
uses: subosito/flutter-action@v2
with:
Expand Down Expand Up @@ -117,6 +124,10 @@ jobs:

- name: Decode release keystore
run: |
if [ -z "${{ secrets.KEYSTORE_BASE64 }}" ]; then
echo "Error: KEYSTORE_BASE64 secret is not configured in repository secrets."
exit 1
fi
Comment on lines +127 to +130

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Validate all release-signing secrets before the build.

Lines 127-130 validate only KEYSTORE_BASE64. The Gradle signing contract also requires KEY_STORE_PASSWORD, KEY_ALIAS, and KEY_PASSWORD. If any of these values are missing, Gradle falls back to the debug key and the workflow can publish debug-signed APKs as a release.

Proposed fix
       - name: Decode release keystore
+        env:
+          KEYSTORE_BASE64: ${{ secrets.KEYSTORE_BASE64 }}
+          KEY_STORE_PASSWORD: ${{ secrets.KEY_STORE_PASSWORD }}
+          KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
+          KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
         run: |
-          if [ -z "${{ secrets.KEYSTORE_BASE64 }}" ]; then
-            echo "Error: KEYSTORE_BASE64 secret is not configured in repository secrets."
-            exit 1
-          fi
-          echo "${{ secrets.KEYSTORE_BASE64 }}" | base64 --decode > /tmp/repforge-release.jks
+          : "${KEYSTORE_BASE64:?KEYSTORE_BASE64 is not configured}"
+          : "${KEY_STORE_PASSWORD:?KEY_STORE_PASSWORD is not configured}"
+          : "${KEY_ALIAS:?KEY_ALIAS is not configured}"
+          : "${KEY_PASSWORD:?KEY_PASSWORD is not configured}"
+          printf '%s' "$KEYSTORE_BASE64" | base64 --decode > /tmp/repforge-release.jks
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if [ -z "${{ secrets.KEYSTORE_BASE64 }}" ]; then
echo "Error: KEYSTORE_BASE64 secret is not configured in repository secrets."
exit 1
fi
- name: Decode release keystore
env:
KEYSTORE_BASE64: ${{ secrets.KEYSTORE_BASE64 }}
KEY_STORE_PASSWORD: ${{ secrets.KEY_STORE_PASSWORD }}
KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
run: |
: "${KEYSTORE_BASE64:?KEYSTORE_BASE64 is not configured}"
: "${KEY_STORE_PASSWORD:?KEY_STORE_PASSWORD is not configured}"
: "${KEY_ALIAS:?KEY_ALIAS is not configured}"
: "${KEY_PASSWORD:?KEY_PASSWORD is not configured}"
printf '%s' "$KEYSTORE_BASE64" | base64 --decode > /tmp/repforge-release.jks
🧰 Tools
🪛 zizmor (1.29.0)

[warning] 127-127: code injection via template expansion (template-injection): may expand into attacker-controllable code

(template-injection)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/release.yml around lines 127 - 130, Extend the
release-signing validation block in the workflow to check KEYSTORE_BASE64,
KEY_STORE_PASSWORD, KEY_ALIAS, and KEY_PASSWORD before the build. Fail
immediately with a clear configuration error if any required secret is empty,
preventing the Gradle release task from proceeding with debug signing.

echo "${{ secrets.KEYSTORE_BASE64 }}" | base64 --decode > /tmp/repforge-release.jks

- name: Build APK
Expand All @@ -126,7 +137,7 @@ jobs:
KEY_STORE_PASSWORD: ${{ secrets.KEY_STORE_PASSWORD }}
KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
run: flutter build apk --release --split-per-abi
run: flutter build apk --release --split-per-abi --obfuscate --split-debug-info=build/app/outputs/symbols

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== files =="
git ls-files | rg '(^|/)release\.yml$|workflow|flutter|build|artifact|symbol|crash' || true

echo
echo "== release workflow around build/artifact/release =="
if [ -f .github/workflows/release.yml ]; then
  nl -ba .github/workflows/release.yml | sed -n '1,240p'
fi

echo
echo "== search for build/app/outputs/symbols and symbol upload mentions =="
rg -n "split-debug-info|build/app/outputs/symbols|flutter symbol|firebase crash|sentry|crash" .github . -g '!**/*.png' -g '!**/*.svg' || true

echo
echo "== workflow uses/references related to symbols/artifacts =="
rg -n "upload-artifact|download-artifact|split-debug-info|split-per-abi|obfuscate" . -g '*.yml' -g '*.yaml' || true

Repository: Devasy/RepForge

Length of output: 840


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== release workflow lines =="
if [ -f .github/workflows/release.yml ]; then
  awk '{printf "%6d\t%s\n", NR, $0}' .github/workflows/release.yml | sed -n '1,240p'
fi

echo
echo "== search for symbol-related references =="
rg -n "split-debug-info|build/app/outputs/symbols|flutter symbol|firebase crash|sentry|crash|upload-artifact|download-artifact|split-per-abi|obfuscate" .github workout-logger scripts . -g '*.yml' -g '*.yaml' -g '*.py' -g '*.gradle*' -g '*.dart' -g '!**/*.png' -g '!**/*.svg' || true

Repository: Devasy/RepForge

Length of output: 10250


🌐 Web query:

Flutter build apk --split-debug-info output location documentation

💡 Result:

When running a Flutter build with the --split-debug-info flag, the output location for debug symbols is determined by the directory path you explicitly provide as an argument to the flag [1][2]. The flag syntax is: flutter build --split-debug-info=/ [1][2] Key details regarding the output location: - User-Defined Path: You specify the directory path where Flutter should save the symbol files [1][2]. For example, using --split-debug-info=out/android will create a directory named out/android (relative to your project root) and place the generated symbol files there [3][2]. - Not Automatic: Flutter does not place these symbols in a default build folder unless you explicitly direct it to do so (e.g., --split-debug-info=build/app/outputs/symbols) [3]. - Purpose: This command generates symbol map files (e.g., app.android-arm.symbols), which are essential for de-obfuscating stack traces from crash reports [1][4][5]. - Persistence: It is recommended to store these symbols outside of ephemeral build directories (like the standard build/ folder), as they may be cleared by a flutter clean command [4]. Developers often commit these files to version control or an external symbol storage service to ensure they are available for future debugging sessions [4].

Citations:


Persist the obfuscation symbol files after the APK builds.

Line 140 writes split-debug-info files to build/app/outputs/symbols, but the following steps only upload and release APK files. Upload or store those symbol files in protected long-term storage before the workflow ends.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/release.yml at line 140, Update the release workflow step
following the Flutter build command to persist the generated files from
build/app/outputs/symbols in protected long-term storage before the workflow
ends, alongside the existing APK artifacts without changing the obfuscation
build configuration.


- name: Rename APKs
run: |
Expand All @@ -150,7 +161,7 @@ jobs:

- name: Create GitHub Release
if: github.event_name == 'push' && steps.commit_version.outputs.committed == 'true'
uses: softprops/action-gh-release@v2
uses: softprops/action-gh-release@v3
with:
tag_name: v${{ steps.version.outputs.value }}
name: RepForge v${{ steps.version.outputs.value }}
Expand Down
22 changes: 18 additions & 4 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,31 @@ name: Test

on:
push:
branches: [main]
branches:
- main
- 'r[0-9]+.[0-9]+.*'
pull_request:
branches: [main]
branches:
- main
- 'r[0-9]+.[0-9]+.*'
release:
types: [published]

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

jobs:
test:
name: Analyze & Test
runs-on: ubuntu-latest

permissions:
contents: read

steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@v7

- name: Set up Flutter
id: flutter-action
Expand All @@ -28,13 +39,14 @@ jobs:
pub-cache-key: "flutter-pub-:os:-:channel:-:version:-:arch:-${{ hashFiles('workout-logger/pubspec.lock') }}"

- name: Install dependencies
if: steps.flutter-action.outputs.PUB-CACHE-HIT != 'true'
if: steps.flutter-action.outputs.CACHE-HIT != 'true'
working-directory: ./workout-logger
run: flutter pub get
Comment on lines +42 to 44

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== workflow excerpt =="
sed -n '1,140p' .github/workflows/test.yml 2>/dev/null || true

echo
echo "== related files =="
git ls-files | rg '(^|/)(pubspec\.yaml|\.dart_tool/package_config\.json|workflows/test\.yml)$' || true

echo
echo "== pubspec/package_config references =="
rg -n "pubspec|package_config|flutter pub get|CACHE-HIT|flutter-analyz|flutter test" .github workout-logger pubspec.yaml 2>/dev/null || true

echo
echo "== git status =="
git status --short

Repository: Devasy/RepForge

Length of output: 3559


🌐 Web query:

subosito flutter-action flutter analyze test cache true outputs CACHE-HIT

💡 Result:

The subosito/flutter-action GitHub Action provides CACHE-HIT and PUB-CACHE-HIT outputs that allow you to determine whether the Flutter SDK or pub dependencies were successfully retrieved from the cache [1][2][3]. These outputs are available starting from version v2.21.0 [2]. They directly reflect the cache-hit output from the underlying actions/cache steps [4][5][3]. Key details regarding these outputs: - CACHE-HIT: Indicates whether the Flutter SDK cache was a hit [1][3]. - PUB-CACHE-HIT: Indicates whether the pub dependencies cache was a hit [1][3]. - Behavior: These outputs return 'true' if there is an exact match for the cache key, and an empty string if there is a cache miss [4][6][5]. Example usage: You can use these outputs to conditionally execute steps in your workflow, such as skipping dependency installation when a cache hit occurs [7][3]: steps: - name: Set up Flutter uses: subosito/flutter-action@v2 id: flutter-action with: channel: stable cache: true - name: Conditionally run dependencies installation if: steps.flutter-action.outputs.PUB-CACHE-HIT!= 'true' run: flutter pub get - name: Continue with build run: flutter build apk Note that you must provide an id to the flutter-action step (e.g., id: flutter-action) to reference these outputs in subsequent steps [7][3].

Citations:


🌐 Web query:

site:github.com/subosito/flutter-action action.yaml CACHE-HIT PUB-CACHE-HIT cache

💡 Result:

In the context of the subosito/flutter-action GitHub Action, CACHE-HIT and PUB-CACHE-HIT are output variables that provide information about the status of the Flutter SDK and Dart pub dependencies caches, respectively [1][2]. These outputs are defined in the action's configuration (action.yaml) to allow users to conditionally execute steps based on whether a cache was restored successfully [1][2]. Specifically: CACHE-HIT: Indicates whether the Flutter SDK cache was a hit (returns 'true' if there is an exact match for the cache key) [1]. PUB-CACHE-HIT: Indicates whether the Dart pub dependencies cache was a hit (returns 'true' if there is an exact match for the cache key) [1]. These values are sourced directly from the cache-hit output of the underlying actions/cache step used within the action [3][4]. They are commonly used in workflow YAML files to skip redundant installation or setup steps, such as bootstrapping dependencies, when a valid cache already exists [2][5]. Example usage in a workflow: - name: Set up Flutter uses: subosito/flutter-action@v2 id: flutter-action with: cache: true pub-cache: true - name: Conditionally run setup if: steps.flutter-action.outputs.PUB-CACHE-HIT!= 'true' run: dart pub get

Citations:


🌐 Web query:

site:docs.dart.dev packages flutter test analyze package_config.json pub cache

💡 Result:

Relevant official Dart/Flutter documentation:

  • Packages: use pubspec.yaml and dart pub get to resolve dependencies. [1]
  • Flutter package commands: use flutter pub, flutter analyze, and flutter test; Flutter recommends these instead of the equivalent Dart commands in Flutter projects. [2]
  • Static analysis: dart analyze [DIRECTORY|FILE] analyzes Dart source code. [3]
  • Testing: Flutter tests typically live under test/; run them with flutter test. [4]
  • package_config.json: Pub generates .dart_tool/package_config.json, mapping package names to their locations; workspaces use one shared file. [5]
  • Pub cache: recent Dart versions include dart pub cache gc to remove packages no longer referenced by current projects. [6]

The original query returned no exact docs.dart.dev match; current documentation is primarily on dart.dev and docs.flutter.dev.


Run dependency resolution when the Flutter SDK cache hits.

CACHE-HIT reports whether the Flutter SDK cache was restored. This skips flutter pub get even when the project pub cache was not restored, leaving .dart_tool/package_config.json unresolved for flutter analyze and flutter test. Check steps.flutter-action.outputs.PUB-CACHE-HIT instead, or always run flutter pub get for clean runners.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/test.yml around lines 42 - 44, Update the condition
guarding the flutter pub get step to use
steps.flutter-action.outputs.PUB-CACHE-HIT rather than CACHE-HIT, so dependency
resolution runs when the project pub cache was not restored while preserving the
existing working-directory and command.


- name: Analyze
working-directory: ./workout-logger
run: |
set -o pipefail
# Only fail on errors, ignore warnings and info messages
flutter analyze --no-fatal-infos --no-fatal-warnings | tee analyze_output.txt

Expand All @@ -53,3 +65,5 @@ jobs:
with:
files: workout-logger/coverage/lcov.info
token: ${{ secrets.CODECOV_TOKEN }}
slug: Devasy/RepForge

Comment on lines +68 to +69

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Remove the added empty line.

The configured YAML linter rejects the empty line after slug.

🧰 Tools
🪛 YAMLlint (1.37.1)

[error] 69-69: too many blank lines (1 > 0)

(empty-lines)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/test.yml around lines 68 - 69, Remove the empty line
immediately following the slug entry in the workflow YAML, leaving the slug
configuration and surrounding formatting otherwise unchanged.

Source: Linters/SAST tools

6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,9 @@ repforge_backup_*.json

# Claude Code project memory & session files
.claude/

# Hive test databases and temporary directories
*.hive
tmp_hive_*/
**/tmp_hive_*/

14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
<p>
<a href="https://flutter.dev/"><img src="https://img.shields.io/badge/Flutter-%2302569B.svg?style=for-the-badge&logo=Flutter&logoColor=white" alt="Flutter Badge"/></a>
<a href="https://dart.dev/"><img src="https://img.shields.io/badge/Dart-0175C2.svg?style=for-the-badge&logo=dart&logoColor=white" alt="Dart Badge"/></a>
<a href="https://github.com/Devasy23/Workout-logger/stargazers"><img src="https://img.shields.io/github/stars/Devasy23/Workout-logger?style=for-the-badge&color=yellow" alt="Stars Badge"/></a>
<a href="https://github.com/Devasy23/Workout-logger/network/members"><img src="https://img.shields.io/github/forks/Devasy23/Workout-logger?style=for-the-badge&color=orange" alt="Forks Badge"/></a>
<a href="https://github.com/Devasy23/Workout-logger/issues"><img src="https://img.shields.io/github/issues/Devasy23/Workout-logger?style=for-the-badge&color=red" alt="Issues Badge"/></a>
<a href="https://github.com/Devasy/RepForge/stargazers"><img src="https://img.shields.io/github/stars/Devasy/RepForge?style=for-the-badge&color=yellow" alt="Stars Badge"/></a>
<a href="https://github.com/Devasy/RepForge/network/members"><img src="https://img.shields.io/github/forks/Devasy/RepForge?style=for-the-badge&color=orange" alt="Forks Badge"/></a>
<a href="https://github.com/Devasy/RepForge/issues"><img src="https://img.shields.io/github/issues/Devasy/RepForge?style=for-the-badge&color=red" alt="Issues Badge"/></a>
<a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/License-MIT-green.svg?style=for-the-badge" alt="License Badge"/></a>
</p>
</div>
Expand Down Expand Up @@ -49,8 +49,8 @@ Want to take RepForge for a spin or contribute? Follow these steps to build the

1. **Clone the repository:**
```bash
git clone https://github.com/Devasy23/Workout-logger.git
cd Workout-logger/workout-logger
git clone https://github.com/Devasy/RepForge.git
cd RepForge/workout-logger
```

2. **Install dependencies:**
Expand Down Expand Up @@ -84,7 +84,7 @@ We believe in the power of open-source! Whether you want to fix a bug, add a fea
5. Open a **Pull Request** and describe your changes.

### What to work on?
Check out the **[Issues](https://github.com/Devasy23/Workout-logger/issues)** tab! If you have a new idea, feel free to open a new issue for a feature request or bug report before starting your work. Whether it's a UI tweak, performance upgrade, or a brand new workout mode, we'd love to see it!
Check out the **[Issues](https://github.com/Devasy/RepForge/issues)** tab! If you have a new idea, feel free to open a new issue for a feature request or bug report before starting your work. Whether it's a UI tweak, performance upgrade, or a brand new workout mode, we'd love to see it!

### Development Guidelines
- Follow standard Flutter and Dart formatting (`flutter format .`).
Expand All @@ -101,7 +101,7 @@ This project is a personal workout tracking application.

**Devasy Patel**
- Email: patel.devasy.23@gmail.com
- GitHub: [@Devasy23](https://github.com/Devasy23)
- GitHub: [@Devasy](https://github.com/Devasy)

---
<div align="center">
Expand Down
16 changes: 16 additions & 0 deletions workout-logger/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,19 @@ app.*.map.json
/android/app/debug
/android/app/profile
/android/app/release

# Signing Keystore & Credentials
**/android/key.properties
*.jks
*.keystore
*.p12
.env
.env.*

# Hive test databases and temporary directories
*.hive
tmp_hive_*/
**/tmp_hive_*/



42 changes: 28 additions & 14 deletions workout-logger/android/app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,25 +1,28 @@
import com.android.build.gradle.internal.api.ApkVariantOutputImpl
import java.io.FileInputStream
import java.util.Properties
Comment on lines +2 to +3

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Locate build.gradle.kts files:"
fd -a 'build\.gradle\.kts$' . | sed 's#^\./##' | head -50

echo
echo "Inspect workout-logger/android/app/build.gradle.kts:"
wc -l workout-logger/android/app/build.gradle.kts
sed -n '1,90p' workout-logger/android/app/build.gradle.kts

echo
echo "Search all keyProperties.load/FileInputStream patterns:"
rg -n "keyProperties\.load|FileInputStream|load\(FileInputStream|inputStream\(\)\.use|use \{" -S workout-logger/android/app/build.gradle.kts .

Repository: Devasy/RepForge

Length of output: 4815


🌐 Web query:

Kotlin File.inputStream() use close input stream kotlin.io use

💡 Result:

In Kotlin, the .use() extension function is the idiomatic way to manage resources, such as those returned by File.inputStream(), ensuring they are closed automatically [1][2]. When you call File.inputStream().use { ... }, the following occurs: 1. The inputStream() method creates a FileInputStream [3][4]. 2. The use function executes the provided lambda block on that stream [1][2]. 3. Once the block finishes—whether it completes successfully or throws an exception—the use function automatically calls .close() on the resource [1][2]. This mechanism is equivalent to Java's "try-with-resources" statement [2]. It is essential for preventing resource leaks, as it guarantees the stream is closed even if an error occurs during processing [1][5]. Note that use does not swallow exceptions; if the code inside the lambda throws an exception, it will propagate out of the use block after the resource has been closed [6][7]. If you need to handle potential errors, you should wrap the use call in a try-catch block [8][6]. Example usage: File("example.txt").inputStream.use { inputStream -> val content = inputStream.bufferedReader.use { it.readText } println(content) }

Citations:


Close the properties file stream.

FileInputStream remains open after Properties.load. Use a scoped stream around keyPropertiesFile.inputStream() and remove the unused FileInputStream import.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@workout-logger/android/app/build.gradle.kts` around lines 2 - 3, Update the
properties-loading logic to use a scoped stream around
keyPropertiesFile.inputStream(), ensuring the stream closes after
Properties.load completes, and remove the unused FileInputStream import.


plugins {
id("com.android.application")
id("kotlin-android")
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
// kotlin-android is injected automatically by Flutter's built-in Kotlin support.
// (android.builtInKotlin=true in gradle.properties)
id("dev.flutter.flutter-gradle-plugin")
}

android {
namespace = "com.devasy.repforge"
compileSdk = 36
compileSdkExtension = 19
compileSdk = 37

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🔴 Critical | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

rg -n \
  'com\.android\.application.*version|compileSdk|targetSdk|distributionUrl' \
  workout-logger/android/settings.gradle.kts \
  workout-logger/android/app/build.gradle.kts \
  workout-logger/android/gradle/wrapper/gradle-wrapper.properties

Repository: Devasy/RepForge

Length of output: 546


🌐 Web query:

Android Gradle Plugin 8.11.1 compileSdk targetSdk API 37 support

💡 Result:

Android Gradle Plugin (AGP) 8.11.1 does not support compiling against or targeting API 37 [1][2][3]. According to official Android documentation, AGP 8.11 supports a maximum API level of 36 [1][2]. Specifically, the minimum AGP version required to support API 37 is 9.1.1 [3][4]. If your project requires API 37 (Android 17), you must upgrade to a compatible version of the Android Gradle Plugin, such as AGP 9.1.1 or higher, and ensure you are using the corresponding version of Android Studio [3][5][4].

Citations:


🌐 Web query:

Android Gradle Plugin 9.1.1 Gradle 9.3.1 source compatibility API 37

💡 Result:

As of August 7, 2026, Android Gradle Plugin (AGP) 9.1.1 is fully compatible with Gradle 9.3.1 and supports Android API level 37.0 [1][2][3]. The following compatibility details apply: - Gradle Compatibility: AGP 9.1.1 explicitly requires a minimum Gradle version of 9.3.1 [1][2]. - API Level Support: AGP 9.1.1 provides support for Android API level 37.0 and below [1][2]. - Java Compatibility: Gradle 9.3.1 requires Java Development Kit (JDK) version 17 or higher to run [4]. It is recommended to use the default version (9.3.1) for both the plugin and Gradle for optimal performance and compatibility [1][2].

Citations:


🌐 Web query:

Android Gradle Plugin 8.11.1 Gradle compatibility

💡 Result:

The Android Gradle Plugin (AGP) version 8.11 requires a minimum Gradle version of 8.13 [1][2][3]. Official Android developer documentation specifies that AGP 8.11 must be used with Gradle 8.13 [1][2]. While release notes for AGP 8.11.0 (and by extension 8.11.1) explicitly list Gradle 8.13 as both the minimum and default version [2][3], the broader compatibility table confirms that AGP 8.11 requires Gradle 8.13 [1]. Note that Gradle compatibility requirements are generally tied to the major and minor version of the Android Gradle Plugin (e.g., AGP 8.11.x) [1][2]. [1][2][3]

Citations:


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

echo "== relevant Gradle files =="
for f in \
  workout-logger/android/settings.gradle.kts \
  workout-logger/android/app/build.gradle.kts \
  workout-logger/android/gradle/wrapper/gradle-wrapper.properties
do
  echo "--- $f"
  sed -n '1,90p' "$f"
done

Repository: Devasy/RepForge

Length of output: 5149


Upgrade AGP before targeting Android API 37.

workout-logger/android/settings.gradle.kts pins Android Gradle Plugin 8.11.1. AGP 8.11 supports API 36; AGP 9.1.1 is the minimum version that supports API 37 and requires Gradle 9.3.1. With the current AGP 8.11.1 pin, set compileSdk and targetSdk to 36, or upgrade AGP with its matching Gradle wrapper. Also applies to targetSdk at line 64.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@workout-logger/android/app/build.gradle.kts` at line 14, Align Android SDK
targets with the pinned AGP 8.11.1 by changing compileSdk and targetSdk in the
Android build configuration to 36. Do not target API 37 unless you also upgrade
the AGP version in the settings plugin declaration and the Gradle wrapper to
their compatible versions.

Source: MCP tools

ndkVersion = flutter.ndkVersion

compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}

kotlinOptions {
jvmTarget = JavaVersion.VERSION_11.toString()
kotlin {
compilerOptions {
jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17
}
}

// Strip AGP's "Dependency metadata" signing block from the APK. It embeds a
Expand All @@ -33,11 +36,16 @@ android {

signingConfigs {
create("release") {
val keystorePath = System.getenv("KEYSTORE_PATH")
val storePass = System.getenv("KEY_STORE_PASSWORD")
val alias = System.getenv("KEY_ALIAS")
val keyPass = System.getenv("KEY_PASSWORD")
if (keystorePath != null && storePass != null && alias != null && keyPass != null) {
val keyProperties = Properties()
val keyPropertiesFile = rootProject.file("key.properties")
if (keyPropertiesFile.exists()) {
keyProperties.load(FileInputStream(keyPropertiesFile))
}
val keystorePath = System.getenv("KEYSTORE_PATH") ?: keyProperties.getProperty("storeFile")
val storePass = System.getenv("KEY_STORE_PASSWORD") ?: keyProperties.getProperty("storePassword")
val alias = System.getenv("KEY_ALIAS") ?: keyProperties.getProperty("keyAlias")
val keyPass = System.getenv("KEY_PASSWORD") ?: keyProperties.getProperty("keyPassword")
if (!keystorePath.isNullOrEmpty() && !storePass.isNullOrEmpty() && !alias.isNullOrEmpty() && !keyPass.isNullOrEmpty()) {
Comment on lines +44 to +48

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

for name in KEYSTORE_PATH KEY_STORE_PASSWORD KEY_ALIAS KEY_PASSWORD; do
  if [[ -z "${!name:-}" ]]; then
    printf 'Missing release signing input: %s\n' "$name" >&2
    exit 1
  fi
done

test -f "$KEYSTORE_PATH"

Repository: Devasy/RepForge

Length of output: 198


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

echo "== build.gradle.kts =="
wc -l workout-logger/android/app/build.gradle.kts
sed -n '1,140p' workout-logger/android/app/build.gradle.kts

echo
echo "== release workflow candidates =="
fd -a 'release.*' .github/workflows | sed 's#^\./##' || true
for f in $(fd 'release.*' .github/workflows 2>/dev/null || true); do
  echo "--- $f"
  wc -l "$f"
  sed -n '1,220p' "$f"
done

echo
echo "== signing config usages =="
rg -n "signingConfigs|release|debug|KEYSTORE|storeFile|keyAlias|KEY_STORE_PASSWORD|KEY_PASSWORD|KEYSTORE_BASE64" workout-logger/android .github/workflows -S

Repository: Devasy/RepForge

Length of output: 16872


🌐 Web query:

Android Gradle plugin SigningConfig signing config signingWith

💡 Result:

There is no property, method, or DSL element named "signingWith" within the Android Gradle Plugin's SigningConfig API [1][2][3][4]. The confusion likely stems from the standard way to associate a signing configuration with a build type, which uses the property name signingConfig [5]. To configure signing in your Android project, you define a SigningConfig object within the android block of your build.gradle file and then assign it to a build type using the signingConfig property [5]. Example usage: android { signingConfigs { release { storeFile file("my-release-key.jks") storePassword "password" keyAlias "my-alias" keyPassword "password" } } buildTypes { release { // This is the correct property, not "signingWith" signingConfig signingConfigs.release } } } The SigningConfig API itself provides properties such as keyAlias, keyPassword, storeFile, storePassword, and methods to enable specific signature schemes (e.g., enableV1Signing, enableV2Signing) [1][2][3][4]. The initWith method is also available for copying properties from another existing SigningConfig [1][3][4].

Citations:


Fail closed for missing release signing inputs.

The env variable fallback uses ?:, so an empty KEY_STORE_PASSWORD, KEY_ALIAS, or KEY_PASSWORD skips the release config and assigns the debug signing config at line 98. The release workflow only validates KEYSTORE_BASE64; add non-empty validation for all four signing inputs and the decoded keystore file before the build. Keep the debug fallback behind an explicit local-only property instead of relying on missing env vars.

🧰 Tools
🪛 detekt (1.23.8)

[warning] 48-48: This condition is too complex (4). Defined complexity threshold for conditions is set to '4'

(detekt.complexity.ComplexCondition)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@workout-logger/android/app/build.gradle.kts` around lines 44 - 48, The
release signing setup around keystorePath, storePass, alias, and keyPass must
fail closed when any signing input is missing or empty, including validating the
decoded keystore file before building. Treat blank environment variables as
unavailable, require non-empty values for all four inputs, and only allow the
debug signing fallback when an explicit local-only property is enabled; do not
infer local mode from missing environment variables.

Sources: MCP tools, Linters/SAST tools

storeFile = file(keystorePath)
storePassword = storePass
keyAlias = alias
Expand All @@ -53,7 +61,7 @@ android {
// supported. If downgrading, remove the health_connector dependency and
// all HealthConnectService usages, then restore minSdk to flutter.minSdkVersion.
minSdk = 26
targetSdk = 36
targetSdk = 37
versionCode = flutter.versionCode
versionName = flutter.versionName
// App display name; overridden per build type below so debug installs
Expand All @@ -71,6 +79,12 @@ android {
manifestPlaceholders["appLabel"] = "RepForge (Debug)"
}
release {
isMinifyEnabled = true
isShrinkResources = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
// Uses the production EC P-256 keystore when KEYSTORE_PATH env var is set
// (CI injects it via GitHub Secrets). Falls back to the debug key for a
// local `flutter run --release` without env vars configured.
Expand Down
14 changes: 14 additions & 0 deletions workout-logger/android/app/proguard-rules.pro
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Suppress missing class warnings for Play Core deferred components in Flutter engine
-dontwarn com.google.android.play.core.**

# Flutter Wrapper Rules
-keep class io.flutter.app.** { *; }
-keep class io.flutter.plugin.** { *; }
-keep class io.flutter.util.** { *; }
-keep class io.flutter.view.** { *; }
-keep class io.flutter.embedding.** { *; }
-keep class io.flutter.provider.** { *; }
-keep class io.flutter.plugin.editing.** { *; }

# Keep Native plugins and Health Connect interfaces
-dontwarn com.google.android.gms.**
4 changes: 4 additions & 0 deletions workout-logger/android/gradle.properties
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
android.useAndroidX=true
android.enableJetifier=true
# This builtInKotlin flag was added automatically by Flutter migrator
android.builtInKotlin=true
# This newDsl flag was added automatically by Flutter migrator
android.newDsl=true
Comment on lines +4 to +7

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🔴 Critical | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== files =="
git ls-files | rg '(^|/)gradle\.properties$|(^|/)build\.gradle\.kts$|(^|/)settings\.gradle\.kts$|flutter\.dart$|pubspec\.yaml$|packages\.txt$|\.flutter$|android/build\.gradle\.kts$|workout-logger' | sed -n '1,160p'

echo
echo "== workout-logger files =="
git ls-files workout-logger | sed -n '1,200p'

echo
echo "== relevant files =="
for f in workout-logger/android/gradle.properties workout-logger/android/settings.gradle.kts workout-logger/android/app/build.gradle.kts; do
  if [ -f "$f" ]; then
    echo "--- $f ($(wc -l < "$f") lines) ---"
    cat -n "$f" | sed -n '1,120p'
  fi
done

echo
echo "== git diff stat =="
git diff --stat || true

echo
echo "== diff relevant files =="
git diff -- workout-logger/android/gradle.properties workout-logger/android/settings.gradle.kts workout-logger/android/app/build.gradle.kts | sed -n '1,240p' || true

echo
echo "== search flutter/AGP pins =="
rg -n 'flutter|version:|android\.gradle\.plugin|com\.android\.application|kotlin-android|builtInKotlin|newDsl' workout-logger pubspec.yaml packages.yaml README.md 2>/dev/null | sed -n '1,240p' || true

Repository: Devasy/RepForge

Length of output: 43278


🌐 Web query:

Flutter Android Gradle builtInKotlin android.newDsl 3.47 AGP 9.0

💡 Result:

With the release of Android Gradle Plugin (AGP) 9.0, Android introduced built-in Kotlin support and a new Domain-Specific Language (DSL) [1][2]. Flutter 3.47 is the version that officially introduced support for enabling these AGP 9.0 features [3]. Key technical details regarding these configurations include: 1. Built-in Kotlin: AGP 9.0 enables built-in Kotlin by default, which removes the need to explicitly apply the kotlin-android (KGP) plugin [1][2]. If a project is not yet ready to migrate, it can opt out by setting android.builtInKotlin=false in the gradle.properties file [1][2]. 2. New AGP DSL: AGP 9.0 transitions to a new DSL interface [2]. To maintain backward compatibility with legacy plugins and build logic, projects can temporarily opt out of the new DSL by setting android.newDsl=false in gradle.properties [2][4]. 3. Flutter Integration: Historically, Flutter tooling automatically applied the Kotlin Gradle Plugin (KGP) to Android subprojects [3][5]. Because the legacy KGP is incompatible with the new AGP 9.0 "built-in" behavior, Flutter 3.47 introduced specific support to allow developers to opt into built-in Kotlin [3]. If you are using Flutter 3.47 or later and wish to enable these features, you must update your gradle.properties to: android.builtInKotlin=true android.newDsl=true Note that all plugins and dependencies within your project must also be migrated to support built-in Kotlin; otherwise, you may encounter build failures [6]. If your project or its plugins are not yet compatible, you must continue to use the legacy configuration (android.builtInKotlin=false and android.newDsl=false) until migrations are completed [7][8]. The Android team has stated that support for these legacy opt-out flags will be removed in AGP 10.0 [1][9].

Citations:


Keep the built-in Kotlin migration, Flutter version, and AGP version consistent.

These flags enable AGP built-in Kotlin/new DSL support, but the repository still pins Flutter 3.44.8 and AGP 8.11.1. Upgrade the Android toolchain to AGP 9+ with Flutter 3.47+, or keep the migration flags disabled and retain the existing legacy Kotlin Gradle Plugin configuration.

📍 Affects 2 files
  • workout-logger/android/gradle.properties#L4-L7 (this comment)
  • workout-logger/android/app/build.gradle.kts#L5-L10
  • workout-logger/android/app/build.gradle.kts#L22-L25
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@workout-logger/android/gradle.properties` around lines 4 - 7, Keep the
Android toolchain consistent with the built-in Kotlin/new DSL flags: either
upgrade Flutter to 3.47+ and AGP to 9+ across
workout-logger/android/gradle.properties lines 4-7 and
workout-logger/android/app/build.gradle.kts lines 5-10 and 22-25, or disable
those flags and retain the existing legacy Kotlin Gradle Plugin configuration at
all listed sites.

Source: MCP tools

Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.1-all.zip
8 changes: 8 additions & 0 deletions workout-logger/android/key.properties.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Local Android Release Keystore Configuration
# Fill in your local keystore path and passwords below.
# Note: This file should NEVER be committed to Git.

storeFile=C:/path/to/your/upload-keystore.jks
storePassword=your_store_password
keyAlias=your_key_alias
keyPassword=your_key_password
4 changes: 2 additions & 2 deletions workout-logger/android/settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ pluginManagement {

plugins {
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
id("com.android.application") version "8.9.1" apply false
id("org.jetbrains.kotlin.android") version "2.1.0" apply false
id("com.android.application") version "8.11.1" apply false
id("org.jetbrains.kotlin.android") version "2.2.20" apply false
}

include(":app")
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,4 @@ RepForge is fully offline by default. The optional AI Coach feature sends data t

LICENSE

Apache-2.0. Source code: https://github.com/Devasy23/Workout-logger
Apache-2.0. Source code: https://github.com/Devasy/RepForge
Loading
Loading