Avoid duplicated code #255
Open
opened 2026-05-25 17:28:18 +00:00 by guettli
·
1 comment
No Branch/Tag Specified
main
issue-563-agentloop-validation
dummy-pr-test
issue-560-fix-firebase-run-url
issue-539-stable-imap-uid
issue-533-shared-email-list
plan-issue-555
drop-nix
plan-issue-484
plan-issue-539
plan-issue-535
plan-issue-474
plan-issue-533
fix-dagger-engineless-precommit
issue-521-fix-deploy-yml-wait-time-api
issue-502-fix-email-id-collision-mailbox
issue-492-eliminate-duplicate-build-runner
issue-494-website-change-detection
issue-491-parallelize-check
issue-478-fix-stalwart-dual-stack-bind
issue-475-allowed-addresses-glob
issue-473-search-result-reorder
issue-453-update-agentloop-defaults
issue-466-structured-search
issue-505-exclude-chaos-monkey-from-regular-ci
issue-509-fix-search-result-sorting
fix-ink-sparkle-remaining-tests
issue-506-fix-search-emails-tests
issue-504-runner-wait-time
issue-488-search-notes
issue-472-changelog-issue-links
issue-501-folder-search-local-sqlite
issue-486-fix-stale-test-shader-mismatch
fix/prevent-settled-search-rerun-473
issue-467-fix-search-stale-results
issue-446-installed-versions-in-changelog
issue-462-fix-pr
issue-448-chaos-monkey-test
issue-436-notes-on-emails
issue-429-unify-mail-display
issue-422-move-to-folder-create-new
issue-414-ensure-not-run-as-root
issue-424-unify-email-list-views
issue-419-trusted-senders-page
issue-425-fix-prs
test-foo
issue-421-bug-report
issue-383-fix-ci
issue-394-fix-deploy-flutter-version
issue-391-fix-ci-double-trigger
issue-376-combined-inbox-v2
issue-376-combined-inbox
issue-384-fix-open-prs
sops-migrate
issue-339-safe-first-on-imap-fetch
issue-340-try-catch-measure-height
issue-342-pin-intl-version
issue-341-guard-threademails-last
issue-335-agentloop-code-test
issue-329-fix
issue-315-fix
issue-320-fix
issue-325-fix
issue-312-fix
issue-311-fix
issue-305-fix
issue-304-fix
issue-299-fix
issue-300-fix
issue-298-fix
issue-296-fix
issue-294-fix
issue-289-fix
issue-288-fix
issue-287-fix
issue-286-fix
issue-277-fix
issue-282-fix
issue-280-fix
issue-272-fix
issue-268-fix
issue-267-fix
issue-266-fix
issue-258-fix
issue-260-fix
issue-257-fix
issue-253-fix
issue-216-fix
issue-251-fix
issue-249-fix
issue-question-fixes
issue-235-fix
issue-236-fix-v2
issue-237-fix
issue-236-fix
issue-228-fix
issue-217-fix
issue-214-fix
issue-213-fix
issue-208-fix
issue-205-fix
issue-204-fix
issue-203-fix
issue-202-fix
issue-129-fix
issue-161-fix
issue-160-fix
issue-201-fix
issue-210-fix
issue-198-fix
issue-200-fix
issue-144-fix
issue-199-fix
fix/playstore-upload-use-requests
issue-193-fix
issue-186-fix
issue-185-fix
issue-192-fix
issue-183-fix
issue-175-fix
issue-172-fix
issue-171-fix
issue-167-fix
issue-136-fix
issue-162-fix
issue-179-fix
issue-155-fix
issue-154-fix
issue-152-fix
issue-151-fix
issue-141-fix
issue-150-fix
issue-164-fix
migrate-to-dagger
task/d1-ci-matrix
task/a4-typeconverter-json
task/u7-onboarding-walkthrough
task/d3-sync-doc
task/a5-layer-boundary-lint
task/t5-golden-tests
task/p5-date-cache
task/s4-link-handling
task/p3-html-parse-isolate
task/u8-mark-all-read
task/u3-recent-searches
task/a3-jmap-injectable-http-client
task/r5-tls-error-handling
fix/playstore-redirect-retry
task/t3-repository-contract-tests
task/p2-email-list-pagination
task/p1-fts5-search
fix/playstore-upload-timeout
task/a1-email-detail-notifier
fix/upgrade-workmanager-0.9
fix/android-core-library-desugaring
task/p4-db-indexes
task/r3-html-error-boundary
task/d2-check-coverage
task/a2-email-tile
task/t4-migration-tests
task/t2-widget-tests
task/t1-email-repo-coverage
task/u6-connection-status
task/u4-push-notifications
task/u2-draft-sync
task/u1-list-unsubscribe
task/s2-hostname-validation
task/r6-reliability-fuzz-tests
task/r4-sync-error-banner
task/r2-force-resync
task/r1-undo-history-persistence
No results found.
Labels
Clear labels
NeedSupervisor
State/InProgress
State/Later
State/Planned
automerge
ci-failure
do-not-merge
loop/code
loop/code-ci-pending
loop/code-done
loop/code-in-process
loop/merge
loop/merge-done
loop/merge-in-process
loop/plan
loop/plan-done
loop/plan-in-process
Issue escalated to a human supervisor; agentloop will skip it until cleared.
Eligible for automatic merge by CI
Issue opened by agentloop to track a failing CI workflow; used for deduplication.
Plan PR — review only, do not merge.
Add to run the built-in "code" prompt; override at prompts/code.md.
Prompt "code" finished; waiting for the PR's CI to pass before advancing.
Prompt "code" finished successfully.
Agent for the "code" prompt is currently running on this issue.
Managed by agentloop
Managed by agentloop
Managed by agentloop
Add to run the built-in "plan" prompt; override at prompts/plan.md.
Prompt "plan" finished successfully.
Agent for the "plan" prompt is currently running on this issue.
No labels
State/Planned
Milestone
No items
No Milestone
Projects
Clear projects
No projects
No Assignees
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: guettli/sharedinbox#255
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
How could duplicated code in the git repo be detected/avoided?
Implementation Plan: Avoid Duplicated Code
Thanks for raising this. After exploring the codebase I found duplication in three distinct areas, ranging from easy wins to larger structural refactors. Here is what I found and how to address each.
1. CI/CD Workflow Boilerplate (highest-impact, ~180 lines)
What is duplicated:
Three step-blocks appear verbatim in every job in
.forgejo/workflows/ci.ymland.forgejo/workflows/deploy.yml(6 occurrences total):dagger,task,stunnel4, andnetcat-openbsdare installedscripts/setup_dagger_remote.shrm -rf /tmp/dagger-tls /tmp/stunnel-dagger.conf /tmp/stunnel.pidApproach – Forgejo reusable workflow:
Forgejo supports the same
workflow_calltrigger as GitHub Actions. Create a new file.forgejo/workflows/dagger-setup.yml(a reusable workflow) that encapsulates the three steps and exposes the four Dagger secrets assecrets:inputs. Each job inci.ymlanddeploy.ymlthen replaces those three steps with a singleuses:reference:Files to change:
.forgejo/workflows/dagger-setup.yml.forgejo/workflows/ci.yml(1 job).forgejo/workflows/deploy.yml(5 jobs)Risk: The
if: always()on the cleanup step must propagate correctly when calling from a parent job. Test with one job first before rolling out to all five.Open question: Forgejo's
workflow_callimplementation may lag GitHub Actions. Verify the self-hosted Forgejo instance version supports it before starting. If not supported, the fallback is to extract the three shell blocks into a singlescripts/dagger-job-setup.sh(andscripts/dagger-job-cleanup.sh) and call those scripts from each job – less elegant but zero workflow-engine risk.2. Python Subprocess Wrapper Pattern (medium, ~25 lines)
What is duplicated:
scripts/agent_loop.pyhas two helper functions (_fgj, lines 90-97, and_tea_get, lines 100-115) that share the same structure: run a subprocess, check the return code, raise aRuntimeErrorwith combined stderr/stdout, and – for_tea_get– parse JSON and check for an error envelope. The pattern will likely be repeated if more CLI wrappers are added.Approach – shared private helper:
Extract a small
_run_cmd(cmd, parse_json=False)helper at the top ofagent_loop.pythat handles the subprocess call, error raising, and optional JSON parse. Both_fgjand_tea_getbecome thin wrappers around it. No new file needed.Files to change:
scripts/agent_loop.pyRisk: Low. The only observable change is that error messages use a slightly different format; the existing tests in
scripts/test_agent_loop.pythat cover_fgj/_tea_getwill catch regressions.3. Dart Repository Implementations (larger refactor)
What is duplicated:
The eight files in
lib/data/repositories/(AccountRepositoryImpl,DraftRepositoryImpl,EmailRepositoryImpl,MailboxRepositoryImpl,SearchHistoryRepositoryImpl,ShareKeyRepositoryImpl,UndoRepositoryImpl,SyncLogRepositoryImpl) all follow the same constructor + Drift query + stream-wrapping +_toModelconversion pattern.Approach options:
Option A – Generic base class (simpler):
Introduce an abstract
DriftRepositoryBase<TRow, TModel>that captures the shared constructor and query helpers as protected methods. Each concrete class extends it and only implements the DriftTableInforeference and_toModel. This keeps all logic in plain Dart and is easy to read.Option B – Code generation (more powerful):
The project already uses
build_runnerfor Drift and Mockito. A customBuildercould generate the boilerplate entirely from the@DriftAccessorannotation. However, this adds build-system complexity and is harder to debug.Recommended approach: Option A (generic base class) as a first step. If the repository layer grows further, revisit Option B.
Files to change:
lib/data/repositories/drift_repository_base.dart*RepositoryImplfiles (reduce each by ~20-30 lines)Risk: Medium. The repositories are used throughout the app and tested via Mockito mocks generated from their interfaces – the interface contracts do not change, so most tests remain unaffected. The main risk is subtly breaking the Drift query generics; run
flutter analyzeand the full test suite after each migration.4. Static Analysis Tooling to Prevent Regression
Once the above is done, consider adding tooling to catch future duplication early:
analysis_options.yaml.yamllintwith a custom rule to flag long duplicated blocks (less precise but zero setup).A
.jscpd.jsonconfig at the repo root could limit detection tolib/andscripts/and set a minimum token threshold (e.g. 50 tokens) to avoid false positives from boilerplate.Summary / Suggested Order
agent_loop.pyI suggest tackling them in this order: items 1 and 2 are low-risk and immediately reduce maintenance surface; item 3 is the most impactful in terms of code volume but needs careful testing; item 4 locks in the gains long-term.