Skip to content

fix(many): deepsource issue collection 2 - #165

Merged
YaoYinYing merged 23 commits into
mainfrom
fix-deepsource-issue-collection-2
Feb 24, 2026
Merged

fix(many): deepsource issue collection 2#165
YaoYinYing merged 23 commits into
mainfrom
fix-deepsource-issue-collection-2

Conversation

@YaoYinYing

@YaoYinYing YaoYinYing commented Feb 24, 2026

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

  • New Features

    • Dashboard: Logout button; animated theme toggle with new icons (☀ ☾ ◐)
    • Server: MAXMEM support, runner/server image selection via env; batch task deletion API
  • Bug Fixes

    • Stronger task ID/path validation and safer artifact deletion; UI readability tweaks
  • Documentation

    • Added DELETE examples; updated README badges and changelog entries
  • Chores

    • CI workflows pinned for deterministic runs; Docker Compose and image/env wiring improvements
  • Tests

    • Added tests for logout, delete behaviors and transient auth handling

@coderabbitai

coderabbitai Bot commented Feb 24, 2026

Copy link
Copy Markdown

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR pins CI workflow actions to specific commit SHAs, hardens server path handling and task-ID validation, changes delete endpoints to use DELETE, adds a logout route and animated theme toggle, propagates new env vars (including MAXMEM) through Docker/compose, tightens shell scripts, refactors small utilities, updates docs/tests, and tweaks badges/changelog.

Changes

Cohort / File(s) Summary
GitHub Actions workflows
​.github/workflows/docker-image.yml, ​.github/workflows/lint_badge.yml, ​.github/workflows/schedule-update-actions.yml, ​.github/workflows/semantic-pr-check.yml, ​.github/workflows/unit_tests_tag.yml
Pin action references to exact commit SHAs (actions/checkout, codecov, updater, etc.); add updater input for PR title; inline comments retain original tags.
Server app & templates
server/pssm_gremlin/pssm_gremlin.py, server/pssm_gremlin/templates/pssm_gremlin_dashboard.html, server/pssm_gremlin/templates/create_task.html
Add path-safety helpers (_path_is_within, _safe_join, _normalize_task_id), normalize/validate task IDs, restrict methods (DELETE for delete, POST for cancel), add logout route, change frontend deleteFile to use DELETE, and add theme-toggle animation + logout UI.
Docker / env / compose
server/.env.example, server/.env.test (deleted), server/docker-compose.yml, server/docker/server/Dockerfile
Introduce/propagate SERVER_IMAGE, RUNNER_IMAGE, LOG_DIR, USERS_FILE, PORT, MAXMEM, GUNICORN_WORKERS, WORKER_CONCURRENCY; propagate PORT into build/runtime; Dockerfile adds ARG PORT and wraps gunicorn with exec and default.
Server scripts & runner
server/REvoDesign_PSSM_GREMLIN.sh, server/run/restart_pssm_flask.sh, server/README.md, server/README.legacy.md
Shell script hardening (quoting, readarray, input validation, maxmem handling, robust path resolution); restart script forces DOMAIN=0.0.0.0; docs updated with MAXMEM and task-delete API examples.
Tests
tests/server/test_pssm_gremlin.py
Tests updated to assert theme toggle/logout UI, propagate MAXMEM in fixtures, change delete tests to use DELETE, add invalid-id and batch-delete normalization tests, and improve readiness/401-retry logic.
Top-level docs & badges
CHANGELOG.md, README.md
Changelog entries for UI/runner/env changes; README badge split into separate Runner and Server image badges.
Utilities & small refactors
src/REvoDesign/tools/utils.py, src/REvoDesign/shortcuts/dialog_hooks.py, src/REvoDesign/logger/logger.py, src/REvoDesign/basic/extensions.py, src/REvoDesign/clients/QtSocketConnector.py, src/REvoDesign/clusters/combine_positions.py, src/REvoDesign/tools/*
Convert lambdas to named functions with type hints, add lazy wrapper re-exports, fix basename_stem to choose longest extension, add small logging statements, remove unused local helper, and reorder imports.
Tooling scripts
tools/copyright.js, tools/release_tag.sh
Replace Node recursive assembly with shell find/cat pipeline; make release_tag.sh more portable and quoted (portable shebang, sed escaping, gsed checks).
Repo housekeeping
.gitignore
Add server/.env.local to .gitignore and remove server/.env.test.

Sequence Diagram(s)

mermaid
sequenceDiagram
participant Browser as "Browser (Client)"
participant Dashboard as "Dashboard JS"
participant Server as "Flask App"
participant Storage as "Filesystem / RESULTS_FOLDER"
participant Auth as "Auth (Basic / Identity)"
Note over Browser,Dashboard: User requests task deletion
Browser->>Dashboard: click delete (md5)
Dashboard->>Server: HTTP DELETE /PSSM_GREMLIN/api/delete/ (with credentials)
Server->>Auth: verify identity (in-process / Basic)
Note right of Server: _normalize_task_id(md5) -> validate
alt id invalid
Server-->>Dashboard: 400 Bad Request
else id valid
Server->>Server: _safe_join(BASE, task_dir); _path_is_within check
alt outside RESULTS_FOLDER
Server-->>Dashboard: 403 Forbidden
else inside RESULTS_FOLDER
Server->>Storage: remove artifacts (safe delete)
Server-->>Dashboard: 200 OK (deleted list)
end
end

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰
Workflows pinned with tidy paws,
Paths made safe by careful laws.
Logout button hops away,
Themes that flip and scripts that play.
I nibble bugs and leave a tiny cause. 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 7.94% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix(many): deepsource issue collection 2' refers to fixing deepsource issues across multiple files, which accurately reflects the changeset containing numerous bug fixes, security hardening, refactoring, and improvements across the codebase.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix-deepsource-issue-collection-2

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@deepsource-io

deepsource-io Bot commented Feb 24, 2026

Copy link
Copy Markdown

DeepSource Code Review

We reviewed changes in 45f2ace...7dcd420 on this pull request. Below is the summary for the review, and you can see the individual issues we found as inline review comments.

See full review on DeepSource ↗

PR Report Card

Overall Grade  

Focus Area: Hygiene
Security  

Reliability  

Complexity  

Hygiene  

Code Review Summary

Analyzer Status Updated (UTC) Details
Python Feb 24, 2026 3:01p.m. Review ↗

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8c5225b35b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread tools/copyright.js Outdated
Comment on lines +10 to +12
"xargs -0 cat",
"> ./program.docx",
].join(" | ");

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Remove extra pipe before output redirection

The command array currently joins into find ... -print0 | xargs -0 cat | > ./program.docx, and that trailing pipe makes > ./program.docx its own (empty) pipeline stage instead of redirecting cat output. In practice this truncates/creates program.docx but writes no source content, so the script now produces an empty file even when Python files are found.

Useful? React with 👍 / 👎.

@qltysh

qltysh Bot commented Feb 24, 2026

Copy link
Copy Markdown

❌ 1 blocking issue (6 total)

Tool Category Rule Count
ruff Lint types\.SimpleNamespace imported but unused 1
qlty Structure Function with many returns (count = 9): upload_file 3
radarlint-python Lint Define a constant instead of duplicating this literal "Invalid task id" 5 times. 1
qlty Structure Function with high complexity (count = 26): delete_tasks_batch 1

def run_gremlin(md5sum):
md5sum = _normalize_task_id(md5sum)
if md5sum is None:
return jsonify({"status": "bad_request", "message": "Invalid task id"}), 400

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Define a constant instead of duplicating this literal "Invalid task id" 5 times. [radarlint-python:python:S1192]

@coderabbitai

coderabbitai Bot commented Feb 24, 2026

Copy link
Copy Markdown

Caution

Failed to replace (edit) comment. This is likely due to insufficient permissions or the comment being deleted.

Error details
{"name":"HttpError","status":500,"request":{"method":"PATCH","url":"https://api.github.com/repos/YaoYinYing/REvoDesign/issues/comments/3950347828","headers":{"accept":"application/vnd.github.v3+json","user-agent":"octokit.js/0.0.0-development octokit-core.js/7.0.6 Node.js/24","authorization":"token [REDACTED]","content-type":"application/json; charset=utf-8"},"body":{"body":"<!-- This is an auto-generated comment: summarize by coderabbit.ai -->\n<!-- walkthrough_start -->\n\n<details>\n<summary>📝 Walkthrough</summary>\n\n## Walkthrough\n\nThe PR consolidates multiple improvements: pinning GitHub Actions to specific commit SHAs across workflows, hardening the Flask server with path validation and task ID normalization, expanding documentation with deletion endpoints, improving shell script safety through systematic quoting, refactoring lambda expressions to named functions, adding logout UI functionality, and enhancing test coverage for validation and deletion operations.\n\n## Changes\n\n|Cohort / File(s)|Summary|\n|---|---|\n|**GitHub Actions Pinning** <br> `.github/workflows/docker-image.yml`, `lint_badge.yml`, `schedule-update-actions.yml`, `semantic-pr-check.yml`, `unit_tests_tag.yml`|Replaced version tag references with exact commit SHAs across multiple workflows, pinning actions to specific revisions for reproducibility and supply chain security.|\n|**Server Environment Configuration** <br> `server/.env.example`, `server/.env.test`, `server/docker-compose.yml`, `server/docker/server/Dockerfile`|Removed DOMAIN environment variable, restructured and expanded `.env.test` with new sections for image names, runner identity, logs, database URIs, and consolidated Redis settings. Updated docker-compose and Dockerfile to centralize PORT configuration via shell expansion with fallback defaults.|\n|**Server Flask Application Hardening** <br> `server/pssm_gremlin/pssm_gremlin.py`|Introduced path safety utilities (`_path_is_within`, `_safe_join`), added task ID normalization enforcing 32-hex-digit pattern, replaced unsafe path construction with validated joining, hardened endpoints to validate and normalize parameters, improved error handling for invalid task IDs, added explicit access control checks.|\n|**Frontend UI Changes** <br> `server/pssm_gremlin/templates/pssm_gremlin_dashboard.html`|Added logout button and `triggerLogout()` function navigating to logout endpoint, switched single and batch task deletion HTTP methods from POST to DELETE for RESTful compliance.|\n|**Server Documentation & Scripts** <br> `server/README.md`, `server/README.legacy.md`, `server/run/restart_pssm_flask.sh`|Added API documentation for single-task and batch task deletion endpoints with curl examples, forced DOMAIN binding to `0.0.0.0` in restart script (removed parameter expansion fallback).|\n|**Shell Script Safety** <br> `server/REvoDesign_PSSM_GREMLIN.sh`, `tools/release_tag.sh`|Applied systematic quoting of variables, command substitutions, and array expansions throughout; replaced `command -v` with redirected stderr/stdout checks; improved path handling with `readlink -f` on quoted operands; enhanced portability and safety.|\n|**Python Refactoring & Utilities** <br> `src/REvoDesign/logger/logger.py`, `src/REvoDesign/shortcuts/dialog_hooks.py`, `src/REvoDesign/clients/QtSocketConnector.py`, `src/REvoDesign/basic/extensions.py`, `src/REvoDesign/tools/cgo_utils.py`, `src/REvoDesign/tools/package_manager.py`, `src/REvoDesign/tools/safe_pickle.py`, `src/REvoDesign/tools/utils.py`|Converted lambda expressions to named functions with explicit type annotations, removed unused local helper function, added debug logging, improved exception handling specificity, introduced delegation wrappers for command execution and worker thread pooling.|\n|**Documentation & Badges** <br> `README.md`|Updated Docker image size badges, replacing single server image badge with two non-root variant badges (Runner and Server).|\n|**Test Coverage** <br> `tests/server/test_pssm_gremlin.py`|Updated deletion tests to use DELETE HTTP method, added test for invalid task ID rejection (400 response), added test for batch deletion normalization and guard logic including mixed-case handling and edge cases, extended dashboard rendering assertions to verify logout button presence.|\n|**Tools** <br> `tools/copyright.js`|Replaced Node.js-based filesystem traversal with shell pipeline using `find`, `cat`, and output redirection executed via Bash subprocess, simplifying and reducing code complexity.|\n\n## Estimated code review effort\n\n🎯 4 (Complex) | ⏱️ ~60 minutes\n\n## Possibly related PRs\n\n- YaoYinYing/REvoDesign#164: Both PRs modify the server pssm_gremlin module with task-id validation, deletion handling, and safe path construction, making them directly related at the implementation level.\n\n## Poem\n\n> 🐰 *Paths now guarded with care,*  \n> *Tasks ID-checked everywhere,*  \n> *Shells quoting true,*  \n> *Logout buttons anew,*  \n> *Security's answer to prayer!* ✨\n\n</details>\n\n<!-- walkthrough_end -->\n\n\n<!-- pre_merge_checks_walkthrough_start -->\n\n<details>\n<summary>🚥 Pre-merge checks | ✅ 2 | ❌ 1</summary>\n\n### ❌ Failed checks (1 warning)\n\n|     Check name     | Status     | Explanation                                                                          | Resolution                                                                         |\n| :----------------: | :--------- | :----------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------- |\n| Docstring Coverage | ⚠️ Warning | Docstring coverage is 9.26% which is insufficient. The required threshold is 80.00%. | Write docstrings for the functions missing them to satisfy the coverage threshold. |\n\n<details>\n<summary>✅ Passed checks (2 passed)</summary>\n\n|     Check name    | Status   | Explanation                                                                                                                                                                                            |\n| :---------------: | :------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| Description Check | ✅ Passed | Check skipped - CodeRabbit’s high-level summary is enabled.                                                                                                                                            |\n|    Title check    | ✅ Passed | The title 'fix(many): deepsource issue collection 2' directly reflects the PR's purpose—addressing a collection of DeepSource code quality issues across multiple files as part of a follow-up effort. |\n\n</details>\n\n<sub>✏️ Tip: You can configure your own custom pre-merge checks in the settings.</sub>\n\n</details>\n\n<!-- pre_merge_checks_walkthrough_end -->\n\n<!-- finishing_touch_checkbox_start -->\n\n<details>\n<summary>✨ Finishing Touches</summary>\n\n- [ ] <!-- {\"checkboxId\": \"7962f53c-55bc-4827-bfbf-6a18da830691\"} --> 📝 Generate docstrings (stacked PR)\n- [ ] <!-- {\"checkboxId\": \"3e1879ae-f29b-4d0d-8e06-d12b7ba33d98\"} --> 📝 Generate docstrings (commit on current branch)\n<details>\n<summary>🧪 Generate unit tests (beta)</summary>\n\n- [ ] <!-- {\"checkboxId\": \"f47ac10b-58cc-4372-a567-0e02b2c3d479\", \"radioGroupId\": \"utg-output-choice-group-unknown_comment_id\"} -->   Create PR with unit tests\n- [ ] <!-- {\"checkboxId\": \"07f1e7d6-8a8e-4e23-9900-8731c2c87f58\", \"radioGroupId\": \"utg-output-choice-group-unknown_comment_id\"} -->   Post copyable unit tests in a comment\n- [ ] <!-- {\"checkboxId\": \"6ba7b810-9dad-11d1-80b4-00c04fd430c8\", \"radioGroupId\": \"utg-output-choice-group-unknown_comment_id\"} -->   Commit unit tests in branch `fix-deepsource-issue-collection-2`\n\n</details>\n\n</details>\n\n<!-- finishing_touch_checkbox_end -->\n\n<!-- tips_start -->\n\n---\n\nThanks for using [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=YaoYinYing/REvoDesign&utm_content=165)! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.\n\n<details>\n<summary>❤️ Share</summary>\n\n- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)\n- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)\n- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)\n- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)\n\n</details>\n\n<sub>Comment `@coderabbitai help` to get the list of available commands and usage tips.</sub>\n\n<!-- tips_end -->\n\n<!-- internal state start -->\n\n\n<!-- DwQgtGAEAqAWCWBnSTIEMB26CuAXA9mAOYCmGJATmriQCaQDG+Ats2bgFyQAOFk+AIwBWJBrngA3EsgEBPRvlqU0AgfFwA6NPEgQAfACgjoCEejqANiS4AzeAA8AFM0yyAlFyUluifNgoMJCiIiNhBTBZWYvD4WABMBgBy2MwClFwAjABsAKwGAKoASgAyXLC4uD4cAPTVROqw2AIaTMzVAJpo+O3wGD0YRNWFAKIS+AAi0vBEGNXc2JHV2Xn5iOmQnd29/UQGAMp+AUECVBgMsLYOYF4+h4FgSKEkYBFR4rFgCdBoFKS4kCdMOcuC5evtcNRsIguPhuGQDIUSBJ4CQAO6UaGQAzFFQkCyYgwAYQoJGodHQnEgcQADHEsmBaZ8ACzQakATg4AGYmRw6QAtIz6YzgKBkej4Gw4AjEMjKGj0VpsDCU3j8YSicRSGTyJhKKiqdRaHRCkxQOCoVCYKWEUjkKjyhSsdhcKioyChVg/eRyBR6lRqTTaXRgQzC0wGDT1XCNATVVH4CgAaxsFnwqMQ1Vo+AYicoDxcpA0smYFg4BgARJWDABiauQACCAEkZXayfQPS4KPIJYxYJhSIgjAAFXrkei4eOQADi6gAEk0G9FYsgSTZKGRAsgbBQWJBbZR4AxIFIKIgYlgIURkARICR7GgxI7mOpIHtZ/XoQYoITYKJE35/gfd4sGwbhaDbSBt13IDzwzc4/wAgABCQmUgG8YOXap4JzJCsjZOI2QAdk5EhOVoNlOUIjICIEBgBBsJlqVyOISAENkbDiGwsgyNIGBsTkBLiSA6xQjQMg0Qiv0gcZs1zPh5wESAlEQBgKHgbhgPQJcQLAiCoOYHgSBoCgwCRTAMyzHNKBja5pFU9TgOQzk0PwQzjNMiRzMzWTrKaWyVLUjTz0Q2gGCyQibDQWhCOpakMhIGw2RIAAOUiGGopk6MItAsiZEhsloakcrI5LcuE49OSJWJcB3CxINTN1MHoH4iBSdgVxIUEQLOPsBjoABufgMAseRoyCElfAsPBzxc904QYeA7CPRV1GQVBzn7OgNCMIxawbCxjOoWDZrGpTRAsH4juXfhJTvbgEwdBMeCaCxD1vZV1BRQcpMSVz5gEV6GGqO6HroUyPtweQzxmSESV7TbkCUGgxC2oxil6aR4b62guAAajiapPiMYZEHEFwHV1cakRRN0EpsB6uAAWToeAUgrKsv3DSMGiaOME2TBqM1e5UAH0BCiwti1LdnyxrOsmxbOVyQ7L0bqxgdBUgRFuAuzc0N/ac5wXettOQbD/zwd0aG4SCdwMqFpC4DCMDg38cLwZDUJvB3MWd12EI9vCCOI0jyMo6i2Vo+jGOY1j2M47jeP4wTIFRBp0CwXphfCFglX+QAcAhEpkxIkwBcAgAGlvGw12iKQRp4UdeiIWa0Dm0RFrelbNEgX7+DGvh4yTFM0yt7xkCe1N6iPH5wl60haG27EMbNue6DxjJCYyYnSfgcnyUpyASWRNEq/pihKWZ2hWeYGXBS5qMYz5ofBeqFTf1oBZnlA8CaDAP2ixLGWSsss9oK33PaZWKROzdklBtPq30oDa11uSZ2h8ErrjOJjNO0Zbz3kfF3V875PyQF0FpYC/t3b/AwCPH2kAg5ERImRCiVEaJ0QYkxHILE2IcS4jxUQydORCUcL0UmpJxSSlEuJQibhtokKgIgNAUVmCJgyBvB+fk/ZgBPGeD438yR8GoW6WhuU0jJR4jlCiyVOQ2DMRxNITJCLhWpCRHKOQYoMEyqVUqQRhEuxoFFNWEhqQaGSmJGRUlGwXgNno4yo9uCVyikoZqkByBul6PMFUCwLAixJAARzCKTEW4hcBWC4OWBaXB8i6RoIbXAClFzkNmjiGgpNIAADUMTnnLIvXa8sDpymOjeU6SgGAXXtMdHsINz7kiev9QG71ilfU1r9cgaNl7qzXpAfGhMEgGBJmTCCB8j401PgzSAl9r6305mAIw3Noy80HgLNMGY1guA+gwMAvAXhu0TAA6WwC5YNmbOAiCKsuxqzgRrAwcAgiPOHm6URNtEDYPgteA2aA3kYFmM7MArzMDiA+fMSIYA8kFMAtpNBa4SSYNtruCQOQNAMucuhNuC0lpPhfH2RAsBICOGpIROIAlkoRUUYlAQnJkpRQSg4wiOQGC0CZHEPitAcgZByDkOgWQ4rSNTggKwPAJqUGRAMfWQRkRngBkES8T52AaBgAgFeCMhr11OneICaDJpSHoNomaPZTrO0GtQvuv4+CT0PJXdJeBECVyekwZUtV6ojxnpAZgigO6ox2lAXuUz5RzBeoecGxT5AjLGVdF2qd1wprTXYDN6NyCOuxuvTe28DkU0UFTY+tNq5nIuWzAF1zbnqNjHCl+2AMDqCKdIXAiAiloCIH8oBVZQHAtlBA9sUDVY9khdITWSCHzKyRMoOqM46nG1Nqnfm8KyEzVXBgvW+k9yrrepeZAjgSCRg0JXYJ1ExKVxQn+uIf7OR/qZG4Wabr8G5xfG+D8lc6YakkHieQ3BG4mujPgNY17ro3kQPNDuR5jk6Jdna80yA0DcB1l9WahIgR4kgEOY5fhkCFDHdGyAP4A64ErmsXAoEwC/iilYEI1wkA6zQLIP+2luNGT48+cdcbwKV0pkwCQkngIJIwPQEkF0HSIsQHaqSjNehPVRAgFp3B93VGzuDK+JqopCChLgPOTtaBJPQGhKg8BhbN1SdnCk71xGQS87CkNQReDUyYy60Lqc0DIGoeQXlaBq4ajRUEOwGA0B1Xzi4XMjArCYFLnE/geAMnhJ2qA/p4zsOuWGedS6jTJn2HutM8UfA5lvXYJ9HdP1YgkDWfWjZOMtlZEJlkFtu9DntrQZ20558mYsz7RzCANyETDHrOMRmwwNDMBxrfZdis13ug3eCrdq9vpVJ/uSU6ewjWUGkj5PgjYCzHAlpjXoWs1sba2ztssiDvDIPHAbM8Ax9VrAoCeFAz33TwAAF4vdoKQVO6cJyuVSQCV7W4nrxZJfgfA/wJsDl5SxrFd2mqvluxQcJUALsQVOuLBHQQijFDI5pyAF00j4i0kwCgtmiAjUriSMTC00MG3C8iJjkBZBdFkL0GXAxqhH3bdDDAnyQjMGICSEs72CewvTtjncePjw/HgPi/TO1AX1kq2W68NWDYlvqxM26TXQateegDDrEMlk9dWYCxmmBFpTsgAAMWCw2DLI04cUH65jbdQ3cYbzAFvPZO894Kim8ck+dMe0LZvv25bRgwcnmqBoMgEgS/3mYDrawe35YrtbLp47MCNnfURKmr1prpIAHlGZNkSO9ZEO4MB5yN2pFQoOjLiBNY4cY3fe8AF5gmL40NSMDD7TqF7u3GuwrUqsYEDa5QXyDh9+cSVte160zsd6UJFBY/w1CaabgCEgfZxd8B9HiXevQjoi6CHdQGL579ed0BXMJo2N8tjVm4hxO9ChoBIAx1Y8ekLcrdGkhk7c6td9x4ndmtHo2t80jxOtxBusoBHBe5t0bdcFsC6A813d8CIZi10Drc3Belzl/c1xWkQ99V6xw9ZBI9o8G1548ZqRm1k9W19509qZM9u05tzkc8rl88DAN8KBi9S8NAWlOAa8gUDtQVG8IUztNZIkapFBsA9ZW41J4I+BS94BB9h8t9ph/Ay0UAsBFDlCMAy81CAR5BElH81hz0z5Ic50ggMs2A2NFDD4x0lIrCNQExZBK5J5kALNoxK4f4VBYtGdChGw2M0A8Bfx3kHCHZTwgsrANN6BmBb91J9VEQr5EASU8RQUJ8m59MsQ/shdMZToxcYgoRIBihO8pwRZxhGxCgkccFW40cEieUx09QeALpZAiAdwJjX4KdJ1SYrN8ArxtooB6xXNmN8hEhEhhhCgaidNyR4AlA6CM56BZi/AkV6iBhX1Cgdi9jCgRZ8hGxxhK57jdj9iRYpxXj3iHivj8g9h9jEh6xNs/jPinipxChO98ghwqcGwtj3QKcU0jIopqBW4fDyEuAgTCg2kvjGwe8pxhhwTHiRYCT6wiTK4cS8Snj+jCgST9ikSKgGjiikTwc7tYQBlYh6o51X0hx8gAAhYoRsQkPo+sN8AUzvesQoN4hsDbRsRIZ4nEvYeExmfALUHgB6BQDAbfewzSFlNHakxknjSfK8AEVMHMDw4AoAqAmAuAzTO7a7fYmkpEilMnTXdUx/NoxjToggbgMAKwOuejaA2A6/XoT6WIdYrWTqdUzGGfHvBUkfE3S1KMzY2gZAQExkh8TcM2WIXU3fY8E3SATMwoPYEWIPRsYoYYc47UyaE4skZjFmao7TCCJnO44YfosspnSuAU6EgAaQBJKHeOGD2HyGKGgBFgFPrEJAHMSHGDAwmMdINjR2mIAijM7xPEywsErmGUWklBJATCIH9zhxZ3oDuiamQEsOsPYG1PzIcL8ItMy2qF6BoFmIcNCM8leh/hmmwR5S8OAkyzOhvwOlPKtgoGMN4xJAuLmJQ1uMQIq0OhQNtyCHtwwLVhzRmVwJoIWS62+l7nayBgwvoHrCHEbDOlGQd2unRDhgQOYL93HTYP+A4KCC4Myx4MoD4MGzxjiCZEJkqhEImzbSUGmxOSz2kN7VzyW3DGcJGHW02w0CsCPIYFkG212wBX2xBQb09BO1gT0KkjTJQRSRPkxJmnLEmCsBqVbm+EQETCGJ5QYCZ3LFmhks+3ksUofBUp23dFgDTEf1bnGGGCrOgGrNJUDzdUr31T8I9IgPc2Bz5ytVi1suRFblOhIrIrFHuhfMgGqCHD2D2EZm+JGEZmFMSGqHI3gEzDxCMhIGqGAAhBsrAB2xyD0DssgFnGgGgCHEgAFNizeiyLGlyOAijMiQ71CAoDmIfxNShACO8gYC3Aah3INnGFi1gAEHwB+HbAQ25MMQNWkHagoP/3+HqtsrYAhGSKCzxHTLgwwDanGQGC4CD3FOgHrEKMCIxRIErkZnGByA9CpKaGfAqHJHrC42D3DO5SBpBoAHUtyYBd4PrXwIReNMjWcZJURhp1r6A61EwBd/sHxH8nosiiA84vTRcfTkAQU6plJ7IgpIzzcEKuTy1UCUKGCGssCXd+AsL5kCCvcs1es6LWDA9mKw82LeCl4BtY88Y2RCZqRxtU9fQO1RKpCL5ZC89pKKchhXLvs1Kl1a8tCtLoFdCEYzA7c9zrTrxJw0c0q4CFFEcTLqsO9ZKvslJsw2oPoTUlALKZoMr8AXzPwoBzLqqhoEqbLeU4qrAwBjqGxSKPBHDDCP4TDpJArhhgq0F8lA8bwcq8qCqoThhiqFSyqUNKqLKaqABvZ6vYPskWL6nIAAX1avFjPGnmyKjIDpqVKIOnKODsTFfXFlwHOCjsbBjpfJ3HjsxlbigL2FgNCtaQztyvysKtzpKoLoqo9sDt/MgAACk9hO8+81raB5AS7ywmqPREBywOAABtDQK+gAXTrrJwbr6ubuhV/EwztvLWTXIzhAdOahsFiTp2oH7tAlTACXCqr1AqzBSUN1TSvhsHkDvCQFNO1JoGVA5rZ1WI63xDRGi3e2GRdrzjtVIKmyenAo+jYGb3QDhmHqMMCFoEGliHrksldsRt9UlDR29t9prNAf1QYH8DqkVAvPgr6UQsGWQvItLVZooPZtmTwJwsIIQR7j5rFpj1XjjwyEImltlsm2Eozy7TPmVqvkW1lgHQUPVpGDGEmGVxFjnuzqKpKo0G5UXRAV1s0sgW0qbzIN3Vxpoe1KvgAr4diHAl7D/FpQMgAANFonwyctEfG0B+oBrQmW53Qks7soLIiUYFQflIBwnYFc4omJAYnIA9BKqJBqgbrIgqQ9AAAyDIQaeJqMvdbxu8GqVBRrBBx/BTVuUvFJd65Ade0wsRH4KgCTBulBCjHcB8C4EJrJ+B0RWgEWDpkWUvEWII6QOfAAEkcA6ccPpmDFL2QAAB90BURbKAByEu3gLKtZjIGuk5twBJm8UJkkNE8a8TXQf4GZ+UeZgJtARZ1w5ZnpyAYAQFzZ757Z1yCAPZyAQ5tAY5yAM5i5lBq5m5u5qMv3YS1MWEJMsfTGBRKlK0/JPHPyl5+QdQBmzEUJvwpZjIRwyANZ85jDC1KwL5zTH5pZlZxAc+xCW+waLMBJ3iFgTGClp6Klml8sOl+6EIeAS1Zl8CX5iQf54Izl2+8sHl/ABJsnM8Z8MZeuPw0JpZuIUJ4ayvHcdvUQHyyCBMcmRBsnAlxB0zMgNBbAx/emCY/vKw2IPOcls11ydLegOlqluu0vPl0QAV5APV84VycsX12lkugN/vcsUJ1k0JrZmCTyGpf1v5654N1oQVlN2uMkSAMV2NzNmuhNqMvYCEFlnnWHckSgHcPgYIm2tLS16gU0rgcN81u6DUY4rASyOSfwwsbN0N6ZiNwtrt9Jmlvtu7HXDQMtqSQkBMEkCdgKByHgagHlCafAKaYCdtkYNpTvAKvYRsKcRUj4vYQkdIocCcocesaAWcdZxwJ52gYWWysASUDZq+CgFZ2llfO52aUJvdg9kc4909nY89y96929+9jZp9l93QSUMVxwT979sV6kcsNwdDw1/S1zZWCzYXZub2JttCJp9t1YAIrgNZ6kYATvK9xsHevYPQId4IrJsj0gCj6kQFmj6AOjxIBjxNms+zRzckFsx6Gm3tuyQKRpbMhMXnEaKMmnB0a/LIg6HgRl2Lf4MYylO9IId7X8CwG2GgewSkB9UJj9qw79jZ0zQ8HlHwKVi6UmNwP9h5szr996wtjZ/h1naJ2zgGdTjDlFqSYYM4VMNYegXz1pe/VWTTvUSQBw902oh0b1t7LAW1se1nTz9dAQHeXjLE28D9SuUJ8L3AMWXoB95DtzyzhAfunz+z3ARz+51yQr2rkrjAOfRD8rshxDjL3QfJmrvz9DhNpN71wtjjHMR/Ir48TpWICjxwOlorlrmuuYNT1pLRKbjADDhrkd818sUbxMcb2ryb08c8Gbot+b+/Rbvr0mJy1bo72IDbqMoLs+bx2151kzfmR/T9qI8FMY0CkT8kBF6ddtjSWAB92D3oV9994Hpzxr4H0HsRODt99z4HzDob0dyGoIZNHa97NZ4HjQTbjtyN9HihwIkebH3HudqANFsLHcHMhMQ4iCHNXMl2BB9geuB2SAVL71TLApLgd9edArnNacWxhUkWQkesYoUUwkfkkWRIfIRmdZjAXgbMfHwXnOvOxUsXiX0X6X2X+XsVxXmnwbmszVrzH4HV4hhLq7WAJ9t168lBzyUfS1M3KACtpqDamt+gfeoI59a3sRSR8+GQC02ywjoITno3KaMe1SBllNMoqvLFp3+T6pckB3jojqQTcH/gBgHh8ajcVo1yVL01Kw/gOEU4K6vL/nrJyKUmH5mwbgOH9PjACH2lqviEaHyv9TmvuvmD+HjPxHsVlvtATD4a+Nah2tprQ6jn7AQlk1Pwz7sQaI8hsnDLZEI83L0J+YblP1kusdSIxKakEWWgAQA/qwmu/qfHjf2AegItnf1cNkffw/4/igUt/jsnZNv14H/H+VJH6MCnyAAwke4wmIWZ6iIUGL3OzKzlxZ3YU+2LSAL1GfaP53sHxBeurxrJIDrGXAahBQBcCvQ4csAkeD9xrJ2B9U7LYnrWRqgQU6AlcKEI/keY99G+8HdzhkCN6FdUiKHK5kbzJzmxnWoeD5rn1aph8fu9TLxldinTIBXouWbJpAHPrn1IAIABgWs3HafNmACiSANfWvq1MciCTdemH2gHKgw2ETaQbIIYFitFBdAEWMoMH6qD1BpqDAFh2aIA4wK7TWIAwDJAZYpOrOMdDoONzYs1oWADLr03Thh8SA+SWLlYD0E0t264gOPgDFkivpYAq1V6NOmqDxCiBsSPTJXDV4lVHCYwFwephrLQAg8Egalpp2mqkB4SDTckOLDhiadb01KPWO9jWCbk6ok8GsppzjSkxwKFKbOOQXZ5/5DwL4VLmxj56fosmRbFDJRnIA5JpAj/UtisUGBX08e4GF2P4Bzg59HwmnOAd5iEGPJyQ6WQCvnGOoix0sSAd+IVgIDGEeUN4AQeu3bbnD+6YrcYdnCmEzpP25YOYdUEOHHDwatAfHncJ5RjDyiGMZ4TMPeGfCwa78OdoCmYKW5hG9tWrBRTQqNZKCruAirIx5oKMfcwAN5AHkKRECSAhgRIDvWGAGBgA1QbEYxSOHBZOKEtYbATDABapNGQlBWpIT0ZcBZw0wWAHITVrsklCU7EyK0AlbvopYjjC3HXiVjro3GhteBJrAMr0A2Grhd1kPhvLQCneJ0IHH2CgqQAAAGmAGGCJA2k5pWSNiWdL4lCSxJLWP8SeLklKSXRHon0QGKVwSyZZCslWUrgz4ZyXxH4rKQ+KkkSyIJMEhaIhKFUYSQ4SuLaWgBCC285IH0aaIpLVk186omeGeQVF2878QfXlEGlTB9Q+AYZMcKahJDwlZR2pbPnUIkx/cuejvITFwCnA7ERS0BRUpDWgIDlSyNZRsYUGbGi8d6hIIoCMESCEh2gw/OOoAPoDhiaySnW/OHwKQWsDEJ8EhnaHp4OgTinWGtAUUcAxinifo0EuaPXHBjYSYGMnDJCsh8BLioEXlO6I7Fej4SI1dEIpELyHhjg2ALzLQASQ4d6A2BNdiEGjBzEiAlw1yAIEfEWBmovwTEOGIo4l1QJYAZKNSCgk11h+FaW8UanvFwYmsTUY4lDjWBvAZoPoLcr5RNSjibwvQFMGEBpQkMyY8NMnKfhHEhk1ROcT0Kzj8x+EpwO/bnFgEAJNw4JOwvgHeMCBKYVGA7KmFSj4EPpFcykaYCrh8CIB1csxTqMLFxQU4wA+uXHJwCOIz0D8SIJXGJNVySSNcMk3oHJJ5EKSPgBuf4I4CDTWpd4lGOgGVmhHIERGV+Fmo7n944E3cXNT3N1hWR9YlG/BTZPjCEJgAmQjIsQtowkK6Ns8BjSSkY3kLOE+RCxHkdUEPFyQ8RIojSqum0KSjTsRtKSK3ljLEVCgU4LvAmT7xk5VoQFZTv8HDFz4oJUEwaFQ1Hq5T8po40ya5HHEHQwMN4QIPGkyw1tNS58W8nYV3yJ9Ls9AQkF9VmgkNpwzEhMFgGSqxU9OdUVEFQAozrAH0rcOfu83sCiApB5YVqPJimnlhK4CwwtmAAED7TC2S+RfBwDpbhjS2B0m+rNHPrlhuUp08sC8Gek7TDwU03QG6CEjHTIA505fJdPAkhkOAkE6CdSDroSTmAIsaSVrgwAcBP65Ya+i+KAKrSEoZUnqf8EiiRBxYlpHsFVI472ssAo41AKxiMhRkYUGMgEL0CAI7VtMVGbkhiTmkUFzIP5dOKjOAog1MsxkEmjnBH51Qr0ZOOtk9E2FEs/ArOd8Zu23bnhECSBWEYzVEaoVrc6FZ3C1lQaojuaRBDEZ5LrTKNNoceTkITACkCU5aRyEKbNn0aXJVaK2ZwpDOhma5hYS3SSXbN0kYANA3AWQMlOcapT9am6XSplPsH7pkka0o7AICV45lraARacSgFOJFo0G+ARMKePXp6Sw50gZAGlV8GQARYj5bJPkRFhLi6CEaY1p6RNTYEVAXmdQJ4VZxCy+AIsgYIOIAHeNRi67JJmuEhhwFxAiQr6FwBFhjE85M6X8r0ErgixIBIsIQD7QwCsllmLbHASQFnQ2U85z496GPm8I1RDw/wT8vWU0g9g8RiAWQKIgMgECyckdV4s7wRJuZTCggRzGhESp/9xgkDLAV1JhwOFow1Ad6E9zHqQBBE/GO8CJijBrtAaX7Gqa4S6n0AT5kDN0DLkupazWqLgyIBiFgFNRvMt4cagmFPkVDmotAfjNmEcEDBjpqRN8c3PaHkDfCT0IBhjTYwTRb8yNICecEQwBCcEw85JqPPHlLCxqYWddh1C6h2V3sthHfOSDWkJgvog0iCHPMTAiwYc6kHuc3Pi5HF6AYaI8DeA3mXZr5IdMBe6W0CYZuSvQJRY4QyRsYxcnWKai7GSb/z7KTzIalJAU7CclJVaMdP8AEXgoMJW1C8D6xho4zg+rkFKp1BtgOL5AM0mgJXjxGRgJ83iz9o4DAyEz+A+mP4GGgwDhLgg9pNAJ5FN4pkpIFbalEQAGr8KqqlldLvlhuo2x5FXAJRQW0oUqcHFVGMnBIptg/ccaNgdnqxhMXGSAy8AXLJ+2oV7gqAgQepZEHkCbCdOwCr8iotspgKP4akOzOfEWjupV6FiqALOA2qyhNqTQ4PPZ1spzEWks0TAdgO6lk4SlbdFVB6DXZUBTq8Ci5k9BvAYsE5PgHGpBXHQmpRkpIPgIxA47Vzx4mcQZScVvmnzHuCYbxtmVTlIME05sS8mbxJZOFD0VAOqOw3CGPsx0dyogDjVCAgUkiaYdGlFCUy0ZtyZ0YuguTiqZytlj82eYcM+Vk5RFIsf5SEHJWRA0wqMP7DsLC4AMeUMymaI4AP45KiViVGdL3XOBtTUc087qWAofSRoQaNldSNkJAUdKkkQDQ8AWzGWP4U5krOuVlOEGe80m/wDDG7ObljyRErVRhWuGYXvY/CZC9FR6ioWskS+EdG+XiNMWIBBolEg6v0PeYoKa5iC17nwG0UgLhlXyiIbH31TQrp0qZWgA5lma4JAgYnBBZpiQVk5J49QGfsK2dXugOpxuDDLymGERoPloC8YGxjHRPsYBO8qgcYrXDYqjIsEP9KnLox+ob51fGpM02Z7kJVSxmPgBNGIWQV8akofObHOjWrFY1zcNIC/hiB8AQ+jhT5DTwBX5E6gMFNBKvLMh1QNW/ucQHDjkXdr8aJWPAIvHppoUmaYjSiuWiRFSNOaHuRZJrI8kEM/oeBYGCrMS5nFlcsMLBJWkok40oxi8p6NA3TQLx9odUNHHpxL6vopF0YPuSLAHkTzM5I87VcBqnkPyZ5ZKk4vuMobKhKA4eXlOFjsAbT6AfTbqHqBUgJgSA+41nBA2oT/BBchqFBkRQHrN5F4Os7yXHllQ7JkogUtPMFJmxiVKQ7In8VyOtnq1bZMMh2QErEwtJHZUM7jb0AP4rU1qG1DQOUEAQaEwE3s1xgbQynSjsO6ZdzGjmKCrEAIxZMinGkMJ1RHA/4ioNySTlLqiAAEAUrgHW40thkom9ajzlgFiJKA9c0fmRjQYmbLYI6EeNhNcx+UPM0wUgBQDU2ubcA8S+pWcH1J9gqESS6YA2VmiZ156mQ/OqpFJA0BRFQ0OYQBEGhpxNRW6gLRpv00EBppRZK0IehQavQQBDm5VS0URg5Kb0wQslG1Q6pdVTqPlegH4TDrPBI6zK7kg+gnqwEbwAVIKltgq3IIqtntbktPX+DtVOqKJdDC1qejcqmV1WrrXbGDKT1Zo/W5OoNrmW45u6cBAzsuRPg5bLYeWrRVgHjILt4NyoNTVKnHCuRYFdUVeUQD82HagthYxEqdGe0Ag8A+WpHOMubjvY1qOCV6D+MAh4afgwfX8GQzfpsZegoybAEAW5wTR7ok1ZuMVrvzUyGigjfaHLPILwjxGjk0jdI2woaz5GRYkLdpFuFqRHtlAZ7eEoSbvZQmNstXM7NhkfDvFKkgTczuFgibuUYmnnBJqcwWBQmlipPkNkm2NajIzWqOW1stUh0Zlv+TTJlRQYmcet+PdbcFTp1YAGdnGpnUJtmC8b2dXG+2cJvAg86bNC8STYLt5Stz+6DzWLTY0Xr51yqRdaqtUDpbH0UgNdUJkwWpwi62RDW6bZLr8Lzbi1NSf1VM1CYq7/2au4YBrqyaM6nZuu1nRFQbIc7dd3O1ambv50lgEmjga3b+KyZ27kBS9J3XLq91Kb/uMjF4reCsDOYui6mo7V9sM3pxPloTSeKZvM2x6tdcUw3S7KT18bpAqeo3RgHT287zdAunPaiPjLV7Oo7Ab3ZAEhqREhsowG8qVuQZ3ZdWbevAGZtsE0tQmZ26qOwCu1JIEmK7DSGrC71F4e9LO/XSnqv1c6TdGe8TRbqF1eSuKWyASMIX2SCUgpzI0KeJRVpSUONcUkhgrinQ/BiutslMIlXsYXBpNYow7GCncZ6UoAI1BgDtmeEQhz4i1IIPGV7ylSyiJqeRTFio1TNqBdmfAypxmkWZjl1VCwihOZ7clp8s+BUm1zpa4GFSIM/6eDPQ4txuoCmCMohtizK5h8TBwqW1y4PocSMDqNBFGNSzoA1AiQ+QH4SK32A2mJqdg33k/JhBeUM0q8h6xvKzI1IT0E0g0V5VP48BmA/hf4EfzNrwDUhoIKONrnNxNc2gctPAT4mOAKpRbCCfjNLYyJ7UzNHWPgGLA3kCN+GBwt0JkOuHRqbnXqsJQfQaGTmyALQ8cBIBurvGN4Lg5jphFksaJ26xEWzVVmE7XJR6+Rk1PIBMFfcAtdgqHlYoR4OKr+mkfHk/0p4tGv+82fNnCnsaC86tU/UkJyyzyLMi+qunsHrDDyLJVgN2R7LgN605Nvs5vMbQGWfRH5DhP1AbD5yCBAKq+tdsMfOqohYslKICNhpa3LbEt3+ZuEVsrxtztjFEpacjqDooAAls0NafXCIWdDrWQdUyDXpX0INWq6+Nzrxirx2obsbyAlKBW00Jor0Lh97O4b1kUb1kTRhPLsi/0mzxCTGpWp0ctmAGC8AQDWuYymAzBqgD9QikZzIBEZ9M7sz2ZoRcYSj5NfsxTVABDwbTnN/4v7axNYHvVh5Tx+1iSBjRYBIhndFNIyukDYH5au1cXFCDZ6hcO8dgU8P8A2hdLYkaxoIFmIHD/ByYtCk1E0zJMzQP4VqGrGgD24mpz61Ia+ugF/p3ZfAkyk1NhLZwYoBAgTewFwDCGOB7A/himdundAooX6o1LUrmFGiuQwhGYwM/afAi4b6AtCU6D0rqgan34uCZBkRjZxkBMlPKSJYgH/wDAcaQuR/LGbMFNMTTZp9ermbmZNM7U52nTYmkahVyE1ThqIzCZ6hwm6aQjXI1usVkSMCdB62gqUeWSKNKNb+7ZETGNltGRKLIsKVicinSVcTZjCYASdmCjIUQeg6oAAEVcABwKyLgHO3kB5+FAKY1SZk3145jOlBY+XqDmsRsAzcFoZEtbjzmbyL8/HGRgsBPtPC2RTrLkPJBmY5D/VHIgSnvILVHWusbze0QlzUIwAsIKQ6gAGOIxpVb5qUN+ZlWaRW23i6dGqELwwDfyGmyFE4L5mVnUGfayLQmASRnh4jy2zqOoA+NKBWTKJEIAEWyO2S4RaBBEUrL3VFHOzaI49Yoz2j0UcRTFWo9wVFp9nETLR0QgxvaPMbMThjO+NbKnOjAZzyuOYX5vksObKTMxmk0dnSn0moUAc7xo0KPRVoP44dQMnRguipBAmuYgQ/Qp5R9CFo9ihKJBHgJYkDAciNnAgypXZIY1lALOYek5w7VW4SvEvnZdC01bblj+B6QFQFL5Apwz0hUkHk7zPTIa0pRIAqUiuVxyw+xaEoUGekXtGw3HTXojNkSkJV9rlrOd2o8twJyA3lpNAak5J8AydmkEkMFeNOPTcAWYPAM9NJh6gKAz0vEc9II2sRttz04yXlccsFWXLW5Yq1TooBHCW2ACmdPFlHm+AsAPlqq/5dqtBX/A8KraZqyrzPSBRVgewM9KRjaArAtAIa05b+DzN/A1KYrmVbxCeWgyS1vy+vvsu6mqqq/L0q5HOslj2A41vzXdbxCbNeo5VhtZgLAoQV7CdUA8jzgPD3VH011y699ZutuWvLVadvCtLtMmXW4QhsScPhfKeKzoV8N8y1uevckzLeQrqTMEfzr08cIaFyW9HTlRlSCl+C5egyPBPQAIObW1QFe0hdS250J8tLVtCHsBK4wa9aeFhCDJ8iyKzIm4FeugiJ/EgWYyw6diy0XsdeRts/jsvWYVabXZ3CprCDzE2nCYk29btqGnttCrY19y5NcMv4hadu+xANJfxNyXLbilnc+7ISZYgnL+kM26NciA/WPL1t5AHPnRtK2uAoV4YOFeSuFtorsVlK/FcKCJXEgkd1K4UHSuZX0iOV8XojPdvDWoABAdttfmcuFILbJVq215cQDhKOACTeq+tZoFh2I7UVxIDFbisJWkrz0tK9ATTvZWRSmd6+kLsZMG3ocMMSCkEBiSbJW9PttyyXfmaA28Q5dv9vTvttAxpzFjMSS7ZdtTHs7ntu2N7aLu+3Lb09zAOVcDvB3wIodpqy1dwBtXmrdbLq8Fh6t44+r8cga0pKzskIc7LkfO7ZfNt72p7iNue5XanU12TUoTB6e1YAhX2Ort9qwPfZoBrUn7KVwa73akj62ZbhtoeysJNttgd7xXYuxNamsPyZrU84fUIAWu22F7Dt2S6veduW2N7b9reywGwdFX97Z8K1hiCIfzXuSQdxW6fc2sTGSAO1lgFXn2spXDrwWE673boekI87WTAu9/cnt4OWHiF08Ow5IexAK7VdmTF+1ruPS+HAjiKneAOuokxHWd5BwPZvXD3MH8odtp9fhuixEbf1iwADcPt4h57muxe3icoeEnqHJd2hx7dIRe2smNjnPnY5ntI2gyXD0M9edCdcAgnV1v26XbrhOOScFgP9n49zv4BP7koWJwjdCcOOknQNgB9Xa0fAPsnosfe9bfycuOX9oTGp0YBqcv7gAhIvYqGAEsqN14Bs/yfRrFM6MOjbVDkd0YUIUOV7hJ7lA9B4ZJCr4mWVYiLB8rxyKT0x9Sl7IPO0n5jHjIbYHKRLLLuHaAPBdKe/XwL16VlywPQXSy7CDbmRLzVqfH6OrAHX7NCLIDhAZwCNSsh8gg3PodDCz0WT+oDGxZlgzrRkI4R30A1qRjI09n2puHiW0zNH79Qu7gHec1QzTPYG7Lkjo7TjyYyAavtaebjr0o2sWbpO/fOuiSYY0rDaMJpOJz3wFjoDJJjCJcZZLUSJNOpgmSNFlzt2+AUp0Xi63LnNq++FxQEReSh/TZuf58VycWmxIXI8Ip+WgemIypinREgQ+jQMLxzr7LRwI9KqqmxJDyq+lUFk0xFXJJY11NFEE/gzphNSvGgIauW2txtniTKW1zbC2vzJXW4amcPOwHZJDXogY13nOH1mu0jcS8sI4C3L+dUGJ6iJCPzqlGU3QqI1a1RXThHObLjF9wQqHywkg+AEJrdpWb+ekJzrY10l8PvJfxKQwsL3l2abvOQQjIqKM6DevpdrBGXJhVnI68gCCv8rUAbN77cEAiAxACraQOK8ah2u1rxT5uIq+CXFcVX5Ydtylk1fCu9X6r8hD2/czRusAkr9pjtmHddvy7arzCcuEndZuAXY18d2IApdLWF3tzja0O+Vc9NVX+76dJq+fp7VyGcaE8P8Gtd3RQCx0XG90zYCLzIYcIP+Fijxx6RznrJaKjpwio3OG3PoWqcYUfxxuT3VsMkJ6xVstmFZDk66Mxecnqy3J8jZmDNutJj2C751gfsC9JaTWI294il4W55cfPO97j5e7Odfg+Vz44ziyCbkngzPtt8zvuzIRw+UTMnsNtlVW6Za5uF55HlqpR4RfUehndH0Z4x6jSZgWP0z2Z93Q3uGYJdigXD0NlCb4eAXor2d2Bgo9vOqPdtyT3Jek991ZPkz1j4p448qfuPr43j76z1euvzBW7D10Ji9c9ydw5ruJa47j3GfV7pnpj3J6mdEA2Pcz5T5T1U8YKkkvH1t9kiE/5u9Pongz+J6M9L2ZLwz2YAF/M/yeQvVn8L1x8l08eZHtl2LyLCvdruyHbjvzyM4Y9meJnOX0L0p7ds2fCvdn4r1k93e+2dPsESr757S+O3/PtXwLxZ4U/sf8v2H1r9F/a98e936oA9714Z3VfMvQ37L8F8a/WfGjbTrZDxU3jInWjTIkc3/otkSXjGNH9L3R4IBbs4IJmkWNNHxC7mVLsmlZ0ebWfZT28p0bOXZv05PXUHQp3MGLAWCKQRES4zOSMjiA5AaoJAd9AEb7fckgLkpnUII7wBW8ggMOF4LjmrZuCgg3JU6HGiCBjVIogQGspK8Mp7BuAXzkyfYErgxFIAMOCJcjnWOIY/BW7BMHakbDIXoqgFNgOZE+0WAsupArHn4nxQm5FOaq+uIc+ufWWFAMnL/BspwYGw2VDAcH5D/x+1bc+cGV6HJnOMd5wsAo5H/QBhyMBMsPDHTDNCPkGwmmVAWH04SYBwhT1KFo1DALIKzQ03/MhqMVmnQg/TowHpdbAut+slXUqhneDacB+vhyflaO7ZwuiOwnsY6AIi2lmW3+osufA5U195L6IFeaTkqgqiPMcrCqNdqEgqjhPhRvznqDJrYoE5v0NPCaDP36OB+8Up9jnCnKRoEqMbqlZrZlD7usKPoeZGxOns+QxwxG3h7mBLPyiN7+YezoyMeUPCfFpbfcYfkvIEOYO89OxLMhLo1bJxP9fPHeu3HPiDmAPhEwARcwZgACKu2FnOtak097Ut0njzFMqvw7/BxO/V4ZtdzBRfPMubLNBsF8nW1Aj/B+lFAbuX6UvXYyHAoz9HajiJC2SGn5hKAOABt5v/cal/9B7RDX6UF4Jyjls/eHsCzBH8AjQQA65BRkBV03K9CegF3QCjwtX8chjSBZAAJn1hUAGNSbgNBAZRADEAsYn2N5APm3Hh+4NOEwwO1GVQsAM/fAOd8bwdW1Q9u/bP3H9SjGf11lG0LZATwk8FE2HMV/DEzX9xzSS038PHDLw+Fd/F5CYUUMHMEmNlLRZ0v9lna/1WdkDM+UMpqEHwhtQUGLdV0DEwfVAskHoIQRaJkkDFhth3sZz30tZ5JAFcsaVNDTZlixJACCBNKYViaxX3bkjIMLjDAFkBHABYXhJEgQZj8CO8O8DDVNIWs0iVHArF3cxoGT+BpYRYREA6E15OgHyBFeQ8HsCHNBzwooQgKZlyFNTXtWptzkIwisBfoXACDwxZWgGGAE1MnCexsCToPrZZoe/x6CHoPoKehHANUi8CWgtoImIRgt/F9cLjEOSqCR/IYPPgZgus3LQVMdcFoBgbYgOmtEGdoRZ4zgMFRiMyGFIO8A0g11RtN0xJqWt9AKT00oDqApP1jJrJHrG1skA422d8kYbtgXhqROfw6cxsJfx/1DvXpwkoBnM7wG9CTS7z387veZz3N4DNKRv81nFAzDdABc2iL83QBaU/p6/RpAaElvTQKu9qgaEIe8nLEhi+Y6JWgEcAAAKhag2MCkIpDEwfY2AkY6VelX48+NdhzAj/N5FP8NAEkIy5m3MImH1OJIpF94oodz3ugt2SkOpDK4WkPpDqQpkNetotG8Dw5D/UgGP8MsPzW5Cx0QDWgDJrL8TERRQ3fyjIBSBoIxClpAoi2tzeCgAZDmocphagmGHHQNgEdaQCR0gCJUI5CT/PzUhwq8POCVlOXda0L4m1aQCoUGbVyGps7sMgIHVUGV3xwtBA1yA+ZH8eRX4CJvNTyK9NPWyx5DcmTTAlDgJKULpCGQq8B89FvLfw0DIQjMEJDmvCL1s8pvVMP3JNQwUN1CRQ01138swq8BzCZQxkIk8iwi7y0CCQjuQ29WnPWXXg6ROQP29AQxQNZFlAk73kI1CF5HVo1CHuR10h9B70MD9zcURMCXvMwPQV6tKbU60/BLcgzkdLSFWI5SYLcGW0etNbSToU6PwlOhC9eLVKoS9dlWqAS6d3WYBA2BXXHlOZSrCwsKzAWSrkmsL4Lg8kaG1TGkEoN4A+xJ6HpSRJQTQ8FPkixNHDUIuAOcLa02VYuimEO3adC9clFaDRu1DuRaFGhwtEPW81scJqi9VPlBt2eVWqatU6JywenCmE06K7kDU3MOCKnQEIqdDFhGVZCOqpoZbAA2oZ0JqAg1tlE8kWYJmcwQOUUgWaD2UXsPugW1RtRa35Un5YCDqBuI2zXkV01WHSAJazHsGfBUNF4FSJK4KznMx90dNR0V2ANSBFNOGK4yhhV5W4jtVxbAPiGgT3f7nExgGFrRRBAJV9Dl1F5MSWONK4AjSmsJiSuDPg1AHDgs14uJFUQZI6atXfRAuUk2/ojw+U1jIqARHBvAyTDB3/JzwQCheJqgdqmKokGG8lQAxbG8iSxYkalBi4p8B/VH0wMbCVsjEGAqJpQewTfXeZvjFBk+V5fKmFwxlwY4EUAVKQzEbUlIdEmqAr0RQSVk+bVNXy5cENQwI4HnFNXEEggcsA8iOAdLG+EnKQQA3x6ABoQhVAKIQ0oB61ddWbNN1ZDwTcNbZETVlxA3WykhoAQPBTCC7RCKbgmWOXVQiUsDCJAUsI5wFiB/TBInOAdySvD/VYAAsOnDYpIvDnC79XoHy8Lo1pCujbLOcPm0OI5LVageI8lV1cCVGeRnRSQc4BEifqFIFejG+EgAedGVL6O4Afov6NEEAYygFZ1CkYGNdlmvKowYpBaXixFoGjfsOkDcYLIGSg+KAZxLCsIWEFkBKdcoA0ASHWENmNnvJA0ykKhZzV+glAPmOqIRmFyKEw95J42aZtEQClLcn2ZACHBZAdDCwAd5e0kmIwQ7fwE47jZTTGgi+bTX2pFQncDfJmADQEsh7AVqkZk8QCwF2d/udSA/xyAewwjcxTWhGjZmbGCymNXqNjFyEXfZwVcEC2MaGYAgPFmCXZkLACAyRZoJXgtirY7MCp9cED1wdAZpVuB6puUbynti5oGFjzEH0cWOh9IkNSJxY+HSBhVxI8WMNUN/gSiOQBazIZEoAtfGpC60jrFYX4CPJYwHJFWIvEUMBXQwsFUcMAEkTJFqjYrjxEfggcJkDeKMAEogunU2XRNxwkEI38DADmO0xSQNYFnR50Bxke9jAxAylFNLaMkq1RqX8HFgTUPpirRqhB6BgF4A8LFiRtFbMAcJHAasBAACQ+2yJNegYGFcJ0cblHhJlqS0L0lcMduHZRazHaloQWrel0GFifCOJSwaWbrmBVBoAqPZJkAE9nyAkSYzTeh9NGBWXADg/4DAF50dZz1gxMd7BVF9ULGxmBPWGsn4M/GCy0n4H7WbSwFMQIEjvkiEzGH5YyGMPkKDrIql2NwFrIJhzA4sEeAL4AdHlHj9emL5wowfnVJQ2JESGD1SZI4maGBUo5brlqiifRwCvArJa2kfwimJQBKYymOqDiAqmcSDSVK2cCGrYTyahMQZ3sYbkiiyEgiWC44dK1E8wkFRtgCILLd7EGFnA4bQiJI418A7JshO+MaRT40uXpci2BhNLZU4RaX8sbwRHQ1AO8CNjWBwVMLi/wuwOCW3QWhUe2zU1gZAGCSOyUJP2CQBEpCUMnoZOWQRbwXxnBNlwFtTkMfUWIEzAC2ONDfM3BY6Al9KMKXxwTA1EW28kWhS31QRxhF2P1MzE9hQAV36VnC6gIQd7B3B/xVpFSFobbF3ThpJbgGEUNlP+WtQzjfUnz4p+GtQNhrUFZlsBltCQDWZUkEWCqSXFQtj2SS6A5KOTS2VFl6iHlTAFPEewXDDxpwBegBwSKGdoMO5EzAUS4SGZYZNTk50diSbMsdJD3slDokQNH8ToonUw8ezH3E4th40Gk4I+LBmIRM5/DIA6cUUmeLRNFaeeIAMJzUMFNAAsNWH6obQK/0VBnQQ+BhY1w70ER8/QA0EDBjQEMBJERQDlGK5yXKYU7QzBTF3+AhQBlIgBIAZKAYBOEcH3FQcgNIE4hqIaCS1RnENAEogsgXKBsQ0AZKDv48oWgE4gSAH6XpTuUqAC7hhPVlJpg8zLznpS8U8LHMFKAFUOBUuTcA2DBQwEumGtywJACHBCgAUiD46ABdidBlQIcAwx5QM+kghMsNYHLgbU0ZwWBaAR1Nkh7Ur1KxlfUm1KQANyOthOJTiMNJ9SPqG1KvhaAYnEPF0lBol24vU8gUTSSEOaJOJiccwBKQSATNIQjwKHNMLZk01NIk4HIc8BLTvUzBj9Tc0l9joAMiJ4EQB0lL1MrBG0wtlq5duAoKoUvU8+mGsSEa1JIQx0wtnNgEgtgE7SLGamk0hM07tPHSmrSEFPpS0sIEXSx08sD/wT/YCE7Tdud0D24lpegG/B20QoH9B1AQAEwCWuI5EAyZGx3jUAMgGxZUAjdNzToGfhzKQGQ+FX2lh0zdMEVY1TLF24p099IrTq0sTllhx0mug3TR08dInSfkIDM7TC07hh+Rv0mDMLYa4rNLLSX0sdnH4d0rpDKQKZYpH1QTmFDWcBXAOULHg7gHThCBtDV4GcUqQE5k8SNQeuFXA3gOQ3tSkjZ6AoBBRQABQCRJFfdyDWjIpQewSYG8ADgfwCJ8D4fJB5sSWajMj4dwaoIFM4+LWIONqDdVUlBW4emGpVUQMAFPFmNWdiwyj6dtE7TP0puBQyYM8sCXY8yfqWAzw08tKXS/0r/AsBAM96k7TCM/hx/TIMn9Ogyl0ydJcyykQ8XYTm4Bdk3JSAMzKXT0M2wATSDM7dIaTYgGdOzBAshQBCyqMyADZANAOkAABSXVGs4ElERGwBq4foVtQYfUlEX19YCaB8pAJBJSgll8akAyz9Mn9NzTJZaaDiyykSGhBcUKBLKsizSK8INgF3ZAGfBFVAjgh1nKI6EQBYGGJISjI5XUNGdAJerNQzDMpQGMyfgL9IMyHM8PGczp0spEshAs0+g8zhrJGRtTaue1MQzgM8sCZA3EDIEygEoTkDQBCIEgDZA0ADICV8BAVKGigaQWKEjgBIHhHlTSoZVKogsgCVCgkMgDiCohaATkE4QMgakEVQBAOkAYgzMuaM6zftYLOUBSAAwgQ1MsV3hoAvU7zLQzTGc70sZrGIvQVIYDbHIazC2AgAhALAFBw1cuAYDDJyo2c5wX1owALK6zV0v6Q8yN0tV2AMx0UAw5T5wp2SgMbKEnK4Acc3NIpzMsanPIQvUjIAMy+spnNgAWc37TZyZaCDM5zdYjQOJML1BM1ggpjUnPmzxcqnPOdpc2XMZyGgRXIaJpcjnJ/THpXEKy96vNbzy93ZPXPMyDcyXNggvUtkBNzUHRAHlzzc24ktzVc63PVy6PbxwmtdckXPpzXco3K4AcgL3NNhfcxHItyuAFXLHTPM8LNxCOYkeTsD9A2QGdyl0qPO9yvUwDHpy5cs3MTz/c5PKtz08zsLksbzRcxXM1zXMA3NYgLcwIAz/PPM3SC8mnKpA488hATy5qVnK9SU8khDTzN0+PUE0h9QfRdlw8yAFFzyc/90NzC8zICyBe82CH7ydsgPNTy1cjPO7Cyw3PIjz9chfLdyt3WnNXzlwdfMHzMgKvM7ziY5wiBiFw6fKdyD8l3KPzo81LLPyXYC/KVyh86/NzTg8uSw5je42eU5D1Qp/NnzI81/KXzP5D/J9yy8gfO/yr8iDOGsa6AwBQKNU3amNTfgWeTNSxQS1PQLCUqRQdhzU5LQ5S8CgwEPojsrIlC5gaREDZTaAF1IBoF2OxTPpwZMMCgACCizCILIo35mPSQwIAA=== -->\n\n<!-- internal state end -->"},"request":{"retryCount":3,"retries":3,"retryAfter":16}}}

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🧹 Nitpick comments (9)
src/REvoDesign/shortcuts/dialog_hooks.py (3)

45-46: get_all_object_names() and get_all_objects() are identical.

Both return cmd.get_names("objects"). One should delegate to the other to avoid duplication.

♻️ Proposed fix
 def get_all_objects():
-    return cmd.get_names("objects")
+    return get_all_object_names()

Also applies to: 53-54

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/REvoDesign/shortcuts/dialog_hooks.py` around lines 45 - 46,
get_all_object_names() and get_all_objects() are duplicates; change one to
delegate to the other to remove redundancy. For example, keep the canonical
implementation using cmd.get_names("objects") in get_all_objects() (or
get_all_object_names()) and make the other function simply return
get_all_objects() (or get_all_object_names()); update both occurrences
referenced by the symbols get_all_object_names and get_all_objects so only one
contains the direct cmd.get_names call.

27-29: get_designable_chain_ids() and get_all_chain_ids() are identical.

Both functions have the exact same body. One should delegate to the other, or they should be consolidated into a single function.

♻️ Proposed fix
 def get_all_chain_ids() -> list[str]:
-    designable = ConfigBus().get_value("designable_sequences", dict, reject_none=True, cfg="runtime")
-    return list(designable.keys())
+    return get_designable_chain_ids()

Also applies to: 40-42

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/REvoDesign/shortcuts/dialog_hooks.py` around lines 27 - 29, The two
functions get_designable_chain_ids and get_all_chain_ids share identical bodies;
remove duplication by making one delegate to the other (e.g., implement
get_all_chain_ids() to return get_designable_chain_ids()) or consolidate them
into a single function and update all call sites; ensure the unique logic of
reading ConfigBus().get_value("designable_sequences", dict, reject_none=True,
cfg="runtime") remains in only one function (referenced by the function names
get_designable_chain_ids and get_all_chain_ids).

32-33: Nit: prefer unpacking over list concatenation.

Per the Ruff linter hint (RUF005), consider using unpacking syntax for clarity.

♻️ Proposed fix
 def get_selections() -> list[str]:
-    return [""] + list(cmd.get_names("selections"))
+    return ["", *list(cmd.get_names("selections"))]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/REvoDesign/shortcuts/dialog_hooks.py` around lines 32 - 33, In
get_selections(), replace the list concatenation that builds [""] +
list(cmd.get_names("selections")) with list unpacking to satisfy RUF005;
specifically, return a new list that starts with the empty string and unpacks
the iterable from cmd.get_names("selections") (i.e., use unpacking with the
get_selections function and cmd.get_names call).
src/REvoDesign/tools/utils.py (1)

40-49: Wrappers lose type information from the original signatures.

The originals in package_manager.py have detailed @overload signatures. These *args, **kwargs wrappers erase all type hints, so callers importing from utils get no IDE autocompletion or type checking. Consider re-exporting with type stubs or forwarding the overload signatures.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/REvoDesign/tools/utils.py` around lines 40 - 49, The wrapper functions
run_command and run_worker_thread_in_pool currently use *args/**kwargs which
strips the original `@overload` type signatures from package_manager; fix by
preserving and forwarding the original signatures: either re-export the
functions directly (from .package_manager import run_command,
run_worker_thread_in_pool) so type checkers see the original overloads, or copy
the `@overload` declarations from package_manager into utils and implement thin
forwarding bodies that call _run_command/_run_worker_thread_in_pool; reference
the original symbols run_command and run_worker_thread_in_pool when making the
change so IDEs/type checkers pick up the proper signatures.
server/pssm_gremlin/pssm_gremlin.py (3)

534-538: tempfile.gettempdir() fallback is safer than the previous approach.

Good improvement. Note that except BaseException on line 536 (pre-existing) is overly broad — it catches SystemExit and KeyboardInterrupt in addition to OSError. If you ever revisit this block, narrowing to OSError would be more precise.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/pssm_gremlin/pssm_gremlin.py` around lines 534 - 538, The try/except
around computing _ROOT_MOUNT_DIRECTORY currently catches BaseException which is
too broad; change the except to catch OSError (or more specific exceptions like
OSError and IOError if needed) so only login/filesystem errors are handled, keep
the fallback to tempfile.gettempdir() and the os.makedirs(_ROOT_MOUNT_DIRECTORY,
exist_ok=True) call unchanged, and ensure the logic still assigns
_ROOT_MOUNT_DIRECTORY when os.getlogin() fails.

1391-1428: Batch delete still uses POST — inconsistent HTTP method with single-task DELETE.

The batch delete endpoint (/PSSM_GREMLIN/api/delete) at line 1261 uses methods=["POST"], which is reasonable since it accepts a JSON body with multiple IDs. However, having DELETE for single-task and POST for batch-task on the same base path (/api/delete) is a minor REST design inconsistency. This isn't a bug — just a design note. If you choose to keep it as-is (which is fine for pragmatic reasons — DELETE with a JSON body is not universally well-supported), consider documenting the rationale.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/pssm_gremlin/pssm_gremlin.py` around lines 1391 - 1428, The
batch-delete route at "/PSSM_GREMLIN/api/delete" currently only accepts POST
while single-item delete uses DELETE; to make the HTTP methods consistent either
(A) allow DELETE for the batch endpoint by changing its route decorator to
accept methods=["POST","DELETE"] so clients can use DELETE with a JSON body, or
(B) rename the batch endpoint to a distinct path such as
"/PSSM_GREMLIN/api/delete/batch" (keeping methods=["POST"]) and add a short
comment documenting why POST is used for batch; update any clients/tests that
call "/PSSM_GREMLIN/api/delete" accordingly.

589-607: Good defense-in-depth for artifact deletion, with one observation.

The root/home directory guard (line 595) and the _path_is_within check (line 598) are good layered protections. Note that for legacy rows with result_dir outside RESULTS_FOLDER, the code logs a warning but still proceeds with shutil.rmtree. This is a deliberate backward-compatibility choice. Consider whether legacy paths should also be checked against a broader deny-list (e.g., /etc, /usr, /var) to further limit blast radius.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/pssm_gremlin/pssm_gremlin.py` around lines 589 - 607, The deletion
logic in _delete_task_artifacts allows legacy result_dir values outside
app.config["RESULTS_FOLDER"] to be removed after only checking for root/home;
extend the safety checks by rejecting additional system-critical directories
(e.g., /etc, /usr, /var, /bin, /sbin) before calling shutil.rmtree. Update
_delete_task_artifacts to build a deny-list of absolute paths (using
os.path.abspath) and check safe_result_dir against that list (in addition to the
existing root/home guards and _path_is_within check), logging and skipping
deletion when safe_result_dir matches any deny-list entry.
server/docker-compose.yml (1)

49-49: Inconsistent default image tag between web and worker services.

Line 49 (web) still defaults to revodesign-pssm-gremlin-server-non-root:latest, while the shared env (line 4) and the updated worker (line 83) both default to revodesign-pssm-gremlin-server-non-root without the :latest tag. While Docker treats an untagged image as :latest implicitly, the textual mismatch can confuse operators.

Also applies to: 83-83

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/docker-compose.yml` at line 49, The web service image default includes
a trailing :latest while the shared env and worker use the untagged form; update
the image declarations for both the web and worker services to use the same
default token (${SERVER_IMAGE:-revodesign-pssm-gremlin-server-non-root}) so the
textual defaults match (i.e., remove the explicit :latest from the web service
or make both include :latest—prefer aligning to the shared env which is
untagged); modify the image lines referenced for the web and worker services
accordingly.
server/pssm_gremlin/templates/pssm_gremlin_dashboard.html (1)

1374-1389: Logout via HTTP Basic Auth credential override — works but is inherently browser-dependent.

The technique of sending a request with bogus credentials ("logout":"logout") to invalidate the browser's cached Basic Auth is a well-known workaround. Be aware that some browsers (notably certain Safari versions) may not reliably clear the cached credentials this way. There's no universal client-side logout for HTTP Basic Auth.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/pssm_gremlin/templates/pssm_gremlin_dashboard.html` around lines 1374
- 1389, The client-side logout in function triggerLogout uses bogus HTTP Basic
Auth credentials (xhr.open(..., "logout","logout")) which is unreliable across
browsers; replace this approach by having triggerLogout perform a plain redirect
to a server-side logout endpoint (e.g., POST/GET to "/PSSM_GREMLIN/logout" or to
"/PSSM_GREMLIN/create_task" that your backend handles) and implement server-side
logic to invalidate any server session or return a 401/WWW-Authenticate header
to force credential re-prompt; update triggerLogout to remove the bogus
credentials, send a simple fetch/redirect to the new logout endpoint, and ensure
the server-side logout handler clears auth state or returns 401 so browsers will
stop using cached Basic Auth.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@server/.env.test`:
- Around line 29-33: Update the incorrect section comment above the runner
environment variables: replace the "## redis settings" comment with a clear "##
Runner identity settings" (or similar) so it correctly describes the block
containing RUNNER_UID, RUNNER_GID, RUNNER_USERNAME and RUNNER_GROUP; ensure the
new comment sits immediately above those variables and remove any leftover
copy-paste text referring to Redis.

In `@server/docker/server/Dockerfile`:
- Line 38: The CMD in the Dockerfile currently runs Gunicorn via a shell wrapper
so Gunicorn is not PID 1; update the CMD so the shell execs Gunicorn (i.e.,
prefix the gunicorn invocation with exec inside the existing "sh -c" command) so
that the gunicorn process (pssm_gremlin:app) becomes PID 1 and receives signals
for proper graceful shutdown and signal handling.

In `@server/run/restart_pssm_flask.sh`:
- Line 206: The script currently forces DOMAIN="0.0.0.0", overriding any
env-provided value; change this to only set the default when DOMAIN is unset or
empty (e.g., use shell parameter expansion or a conditional assignment) so a
user-provided DOMAIN in the env file is preserved and any printed URLs reflect
the actual DOMAIN value; update the DOMAIN assignment site in
restart_pssm_flask.sh and any places that print the URL to rely on that
preserved DOMAIN variable.

In `@server/scripts/make_paired_MSA_simple.py`:
- Line 105: The diff shows creation of the variable paired_data in
make_paired_MSA_simple.py; ensure you ran the project's pre-commit hooks and
linters before pushing: run pre-commit install (once) and then pre-commit run
--all-files (or make black) to apply formatting and lint fixes, re-run tests,
and amend the commit so paired_data and surrounding code conform to the
repository style rules.

In `@src/REvoDesign/shortcuts/dialog_hooks.py`:
- Around line 36-37: The function find_all_small_molecules_in_protein currently
returns find_small_molecules_in_protein("(all)") or None which collapses an
empty list (no matches) into None and loses the distinction from the None result
when no selection was provided; update find_all_small_molecules_in_protein to
return the raw result of find_small_molecules_in_protein("(all)") (i.e., remove
the "or None") so [] is preserved, or if collapsing is intentional add a clear
comment explaining why the empty-list -> None coercion is desired and
acceptable.
- Around line 23-24: get_fasta_writer_choices currently reads the private
SeqIO._FormatToWriter dict; replace this fragile private-API access with a safe
approach: attempt to read SeqIO._FormatToWriter inside a try block but fall back
to a hardcoded list of known FASTA writers (e.g., "fasta", "fasta-2line") if
that attribute is missing or raises; update the function
get_fasta_writer_choices to prefer the dynamic list when available and return
the fallback list on any exception to avoid depending on Biopython internals.

In `@tools/copyright.js`:
- Around line 8-16: The shell pipeline string in the command variable is ending
with a stray pipe before the redirection, producing invalid syntax; fix the
command construction used by command (and thus spawnSync) so the redirection is
applied to the pipeline output rather than piped into a bare redirection token —
e.g. build the string as "find ./src/REvoDesign -type f -name '*.py' -print0 |
xargs -0 cat > ./program.docx" (remove the trailing " | " before ">
./program.docx" or move the ">" onto the last pipeline segment) so
spawnSync("bash", ["-lc", command], ...) receives a valid shell command.

---

Nitpick comments:
In `@server/docker-compose.yml`:
- Line 49: The web service image default includes a trailing :latest while the
shared env and worker use the untagged form; update the image declarations for
both the web and worker services to use the same default token
(${SERVER_IMAGE:-revodesign-pssm-gremlin-server-non-root}) so the textual
defaults match (i.e., remove the explicit :latest from the web service or make
both include :latest—prefer aligning to the shared env which is untagged);
modify the image lines referenced for the web and worker services accordingly.

In `@server/pssm_gremlin/pssm_gremlin.py`:
- Around line 534-538: The try/except around computing _ROOT_MOUNT_DIRECTORY
currently catches BaseException which is too broad; change the except to catch
OSError (or more specific exceptions like OSError and IOError if needed) so only
login/filesystem errors are handled, keep the fallback to tempfile.gettempdir()
and the os.makedirs(_ROOT_MOUNT_DIRECTORY, exist_ok=True) call unchanged, and
ensure the logic still assigns _ROOT_MOUNT_DIRECTORY when os.getlogin() fails.
- Around line 1391-1428: The batch-delete route at "/PSSM_GREMLIN/api/delete"
currently only accepts POST while single-item delete uses DELETE; to make the
HTTP methods consistent either (A) allow DELETE for the batch endpoint by
changing its route decorator to accept methods=["POST","DELETE"] so clients can
use DELETE with a JSON body, or (B) rename the batch endpoint to a distinct path
such as "/PSSM_GREMLIN/api/delete/batch" (keeping methods=["POST"]) and add a
short comment documenting why POST is used for batch; update any clients/tests
that call "/PSSM_GREMLIN/api/delete" accordingly.
- Around line 589-607: The deletion logic in _delete_task_artifacts allows
legacy result_dir values outside app.config["RESULTS_FOLDER"] to be removed
after only checking for root/home; extend the safety checks by rejecting
additional system-critical directories (e.g., /etc, /usr, /var, /bin, /sbin)
before calling shutil.rmtree. Update _delete_task_artifacts to build a deny-list
of absolute paths (using os.path.abspath) and check safe_result_dir against that
list (in addition to the existing root/home guards and _path_is_within check),
logging and skipping deletion when safe_result_dir matches any deny-list entry.

In `@server/pssm_gremlin/templates/pssm_gremlin_dashboard.html`:
- Around line 1374-1389: The client-side logout in function triggerLogout uses
bogus HTTP Basic Auth credentials (xhr.open(..., "logout","logout")) which is
unreliable across browsers; replace this approach by having triggerLogout
perform a plain redirect to a server-side logout endpoint (e.g., POST/GET to
"/PSSM_GREMLIN/logout" or to "/PSSM_GREMLIN/create_task" that your backend
handles) and implement server-side logic to invalidate any server session or
return a 401/WWW-Authenticate header to force credential re-prompt; update
triggerLogout to remove the bogus credentials, send a simple fetch/redirect to
the new logout endpoint, and ensure the server-side logout handler clears auth
state or returns 401 so browsers will stop using cached Basic Auth.

In `@src/REvoDesign/shortcuts/dialog_hooks.py`:
- Around line 45-46: get_all_object_names() and get_all_objects() are
duplicates; change one to delegate to the other to remove redundancy. For
example, keep the canonical implementation using cmd.get_names("objects") in
get_all_objects() (or get_all_object_names()) and make the other function simply
return get_all_objects() (or get_all_object_names()); update both occurrences
referenced by the symbols get_all_object_names and get_all_objects so only one
contains the direct cmd.get_names call.
- Around line 27-29: The two functions get_designable_chain_ids and
get_all_chain_ids share identical bodies; remove duplication by making one
delegate to the other (e.g., implement get_all_chain_ids() to return
get_designable_chain_ids()) or consolidate them into a single function and
update all call sites; ensure the unique logic of reading
ConfigBus().get_value("designable_sequences", dict, reject_none=True,
cfg="runtime") remains in only one function (referenced by the function names
get_designable_chain_ids and get_all_chain_ids).
- Around line 32-33: In get_selections(), replace the list concatenation that
builds [""] + list(cmd.get_names("selections")) with list unpacking to satisfy
RUF005; specifically, return a new list that starts with the empty string and
unpacks the iterable from cmd.get_names("selections") (i.e., use unpacking with
the get_selections function and cmd.get_names call).

In `@src/REvoDesign/tools/utils.py`:
- Around line 40-49: The wrapper functions run_command and
run_worker_thread_in_pool currently use *args/**kwargs which strips the original
`@overload` type signatures from package_manager; fix by preserving and forwarding
the original signatures: either re-export the functions directly (from
.package_manager import run_command, run_worker_thread_in_pool) so type checkers
see the original overloads, or copy the `@overload` declarations from
package_manager into utils and implement thin forwarding bodies that call
_run_command/_run_worker_thread_in_pool; reference the original symbols
run_command and run_worker_thread_in_pool when making the change so IDEs/type
checkers pick up the proper signatures.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 45f2ace and 8c5225b.

📒 Files selected for processing (28)
  • .github/workflows/docker-image.yml
  • .github/workflows/lint_badge.yml
  • .github/workflows/schedule-update-actions.yml
  • .github/workflows/semantic-pr-check.yml
  • .github/workflows/unit_tests_tag.yml
  • README.md
  • server/.env.example
  • server/.env.test
  • server/README.legacy.md
  • server/README.md
  • server/REvoDesign_PSSM_GREMLIN.sh
  • server/docker-compose.yml
  • server/docker/server/Dockerfile
  • server/pssm_gremlin/pssm_gremlin.py
  • server/pssm_gremlin/templates/pssm_gremlin_dashboard.html
  • server/run/restart_pssm_flask.sh
  • server/scripts/make_paired_MSA_simple.py
  • src/REvoDesign/basic/extensions.py
  • src/REvoDesign/clients/QtSocketConnector.py
  • src/REvoDesign/logger/logger.py
  • src/REvoDesign/shortcuts/dialog_hooks.py
  • src/REvoDesign/tools/cgo_utils.py
  • src/REvoDesign/tools/package_manager.py
  • src/REvoDesign/tools/safe_pickle.py
  • src/REvoDesign/tools/utils.py
  • tests/server/test_pssm_gremlin.py
  • tools/copyright.js
  • tools/release_tag.sh
💤 Files with no reviewable changes (2)
  • server/.env.example
  • src/REvoDesign/tools/cgo_utils.py

Comment thread server/.env.test Outdated
Comment on lines 29 to 33
## redis settings
RUNNER_UID=503
RUNNER_GID=20
RUNNER_USERNAME=revodesign
RUNNER_GROUP=revodesign_appgroup

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Incorrect section comment: "redis settings" should be "Runner identity settings".

Line 29 says ## redis settings but the block below defines RUNNER_UID, RUNNER_GID, RUNNER_USERNAME, and RUNNER_GROUP. Looks like a copy-paste from the actual Redis section at line 19.

Proposed fix
-## redis settings
+## Runner identity settings
 RUNNER_UID=503
📝 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
## redis settings
RUNNER_UID=503
RUNNER_GID=20
RUNNER_USERNAME=revodesign
RUNNER_GROUP=revodesign_appgroup
## Runner identity settings
RUNNER_UID=503
RUNNER_GID=20
RUNNER_USERNAME=revodesign
RUNNER_GROUP=revodesign_appgroup
🧰 Tools
🪛 dotenv-linter (4.0.0)

[warning] 31-31: [UnorderedKey] The RUNNER_GID key should go before the RUNNER_UID key

(UnorderedKey)


[warning] 33-33: [UnorderedKey] The RUNNER_GROUP key should go before the RUNNER_UID key

(UnorderedKey)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/.env.test` around lines 29 - 33, Update the incorrect section comment
above the runner environment variables: replace the "## redis settings" comment
with a clear "## Runner identity settings" (or similar) so it correctly
describes the block containing RUNNER_UID, RUNNER_GID, RUNNER_USERNAME and
RUNNER_GROUP; ensure the new comment sits immediately above those variables and
remove any leftover copy-paste text referring to Redis.

Comment thread server/docker/server/Dockerfile Outdated
set -u

DOMAIN="${DOMAIN:-0.0.0.0}"
DOMAIN="0.0.0.0"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Avoid overriding a user-provided DOMAIN. This change ignores any DOMAIN set in the env file and can print a misleading URL.

🛠️ Suggested fix
-DOMAIN="0.0.0.0"
+DOMAIN="${DOMAIN:-0.0.0.0}"
📝 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
DOMAIN="0.0.0.0"
DOMAIN="${DOMAIN:-0.0.0.0}"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/run/restart_pssm_flask.sh` at line 206, The script currently forces
DOMAIN="0.0.0.0", overriding any env-provided value; change this to only set the
default when DOMAIN is unset or empty (e.g., use shell parameter expansion or a
conditional assignment) so a user-provided DOMAIN in the env file is preserved
and any printed URLs reflect the actual DOMAIN value; update the DOMAIN
assignment site in restart_pssm_flask.sh and any places that print the URL to
rely on that preserved DOMAIN variable.

# wrt += '/'.join([query[i] for i in tags])+'\n'
paired_data = []
paired_data.append((9999, "query", "/".join([query[i] for i in tags])))
paired_data = [(9999, "query", "/".join([query[i] for i in tags]))]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Please confirm pre-commit hooks and linting were run.
As per coding guidelines, "Enable pre-commit hooks with pre-commit install and run pre-commit run --all-files or make black before pushing to ensure formatting and linting."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/scripts/make_paired_MSA_simple.py` at line 105, The diff shows
creation of the variable paired_data in make_paired_MSA_simple.py; ensure you
ran the project's pre-commit hooks and linters before pushing: run pre-commit
install (once) and then pre-commit run --all-files (or make black) to apply
formatting and lint fixes, re-run tests, and amend the commit so paired_data and
surrounding code conform to the repository style rules.

Comment on lines +23 to +24
def get_fasta_writer_choices() -> list[str]:
return [fmt for fmt in SeqIO._FormatToWriter.keys() if fmt.startswith("fas")]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

Biopython SeqIO _FormatToWriter public API alternative

💡 Result:

Bio.SeqIO._FormatToWriter is a private/internal implementation detail. The supported public alternatives are:

  1. Use the public high-level writer API (recommended):
from Bio import SeqIO
SeqIO.write(records, out_handle, "fasta")   # or "genbank", "fastq", ...

This is Biopython’s documented public way to write sequence records by format string. [1]

  1. For a single record, use SeqRecord.format():
text = record.format("fasta")

(Or write that string to a handle yourself for simple sequential formats.) [2]

  1. If you specifically need a “writer class” (like _FormatToWriter gave you), import the format-specific *Writer class directly from the relevant Bio.SeqIO.*IO module, e.g.:
from Bio.SeqIO.FastaIO import FastaWriter
writer = FastaWriter(out_handle)
writer.write_file(records)

These writer classes are part of the documented API surface (e.g., FastaWriter, GenBankWriter, etc.), and they implement the SequenceWriter interface. [3]

Bottom line: there isn’t a stable public “format string → writer class” mapping; use SeqIO.write(...) unless you have a strong reason to instantiate a specific *Writer class. [1][3]

Sources: [1] Bio.SeqIO docs (SeqIO.write), [2] Biopython Tutorial (SeqRecord.format / writing), [3] Bio.SeqIO API listing of writer classes & SequenceWriter interface.


Accessing SeqIO._FormatToWriter is relying on a private/internal API.

_FormatToWriter is a private attribute of Biopython's SeqIO module and is not part of the stable public API. Biopython does not provide a public method to enumerate available writer formats—SeqIO.write() accepts format strings but doesn't expose a way to list them programmatically. This could break without notice on a Biopython upgrade.

Consider hardcoding the known FASTA format variants (e.g., "fasta", "fasta-2line") instead, or wrap the private access in a try-except with a fallback.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/REvoDesign/shortcuts/dialog_hooks.py` around lines 23 - 24,
get_fasta_writer_choices currently reads the private SeqIO._FormatToWriter dict;
replace this fragile private-API access with a safe approach: attempt to read
SeqIO._FormatToWriter inside a try block but fall back to a hardcoded list of
known FASTA writers (e.g., "fasta", "fasta-2line") if that attribute is missing
or raises; update the function get_fasta_writer_choices to prefer the dynamic
list when available and return the fallback list on any exception to avoid
depending on Biopython internals.

Comment thread src/REvoDesign/shortcuts/dialog_hooks.py
Comment thread tools/copyright.js
[ci skip]
[ci skip]

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (8)
src/REvoDesign/tools/measure_utils.py (5)

648-648: ⚠️ Potential issue | 🟡 Minor

Return type annotation -> Measurement is wrong; the function returns list[Measurement].

Line 673 assigns hits = Measurement.from_session_names(...) which returns list[Measurement], and line 718 returns hits directly. The annotation should be corrected.

🐛 Proposed fix
-def read_measurement(start: str | int, debug: int = 0) -> Measurement:
+def read_measurement(start: str | int, debug: int = 0) -> list[Measurement]:

Also applies to: 718-718

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/REvoDesign/tools/measure_utils.py` at line 648, The return type
annotation on read_measurement is incorrect: the function populates hits via
Measurement.from_session_names(...) which returns a list[Measurement] and then
returns hits; update the signature of read_measurement to return
list[Measurement] (or Sequence[Measurement]) instead of Measurement, ensuring
the annotation matches the actual returned value from
Measurement.from_session_names and the variable hits.

691-691: ⚠️ Potential issue | 🟡 Minor

Typo: "skiping""skipping".

📝 Proposed fix
-                    print(f"[DEBUG] skiping {a.resi} to avoid duplicates")
+                    print(f"[DEBUG] skipping {a.resi} to avoid duplicates")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/REvoDesign/tools/measure_utils.py` at line 691, Fix the typo in the debug
message that prints duplicate-skipping info: change the string in the print call
that references a.resi from "[DEBUG] skiping {a.resi} to avoid duplicates" to
use the correct spelling "skipping" so it reads "[DEBUG] skipping {a.resi} to
avoid duplicates"; locate the print statement that uses a.resi in
measure_utils.py and update only the text in that print/log call.

675-678: ⚠️ Potential issue | 🟡 Minor

Error message always shows [] — the list comprehension over an empty hits is a no-op.

When not hits is True, hits is guaranteed empty, so [m.name for m in hits] is always [].

🐛 Proposed fix
     if not hits:
         raise ValueError(
-            f"measurement not found in session {[m.name for m in hits]}",
+            "no measurements found in the current session",
         )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/REvoDesign/tools/measure_utils.py` around lines 675 - 678, The error
message uses [m.name for m in hits] which is always [] when not hits; change the
ValueError to report useful context by listing available measurement names from
the collection you searched (e.g., session.measurements or the variable that
holds all measurements) and/or the search criteria, not from hits; update the
raise in the same block (the code that checks "if not hits") to include a
descriptive message with the actual available measurement names and/or the
requested measurement identifier instead of iterating over hits.

711-712: ⚠️ Potential issue | 🟠 Major

IndexError if any measurement resolves to fewer than 2 atoms.

x[0] and x[1] in the list comprehensions are unconditional. If a measurement's atoms() resolves to 0 or 1 atoms (e.g., due to an (unresolved) fallback), this crashes at runtime.

🐛 Proposed fix
-    atom_a = [f"'{x[0]}'" for x in pairs.values()]
-    atom_b = [f"'{x[1]}'" for x in pairs.values()]
+    atom_a = [f"'{x[0]}'" for x in pairs.values() if len(x) >= 2]
+    atom_b = [f"'{x[1]}'" for x in pairs.values() if len(x) >= 2]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/REvoDesign/tools/measure_utils.py` around lines 711 - 712, The list
comprehensions for atom_a and atom_b assume every entry in pairs.values() has at
least two items, which raises IndexError when atoms() resolves to 0 or 1 atoms;
update the logic that builds atom_a/atom_b to skip or handle short tuples (e.g.,
filter pairs.values() by len(x) >= 2) before accessing x[0] and x[1], or provide
a safe fallback value for unresolved measurements so atom_a and atom_b only
reference valid pair entries (target symbols: pairs, atom_a, atom_b, and any
code that calls atoms()).

486-625: 🛠️ Refactor suggestion | 🟠 Major

Remove dead code methods _build_uniqueid_to_atom_map and _resolve_by_coords.

Both methods are never called and appear to be remnants of an earlier design. Additionally, _build_uniqueid_to_atom_map contradicts the explicit design decision at line 279 ("do NOT fall back to 'id' or 'serial'") by iterating through those exact attributes as fallback options. The uid parameter in _resolve_by_coords is also unused in the method body.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/REvoDesign/tools/measure_utils.py` around lines 486 - 625, Remove the two
dead helper methods _build_uniqueid_to_atom_map and _resolve_by_coords from the
class: delete their entire method definitions (they are unused), and update any
code that might reference them (search for _build_uniqueid_to_atom_map and
_resolve_by_coords and remove or replace calls). Also ensure you do not
reintroduce the forbidden fallback behavior (do not add checks for 'id' or
'serial' anywhere) and remove the unused uid parameter consideration—if any
logic depended on resolving by coords, implement a clear, intentional
replacement rather than restoring these methods.
server/docker-compose.yml (2)

105-119: ⚠️ Potential issue | 🟡 Minor

Runner image variable name mismatch (PSSM_GREMLIN_RUNNER_IMAGE vs RUNNER_IMAGE).

x-common-env defines RUNNER_IMAGE, but runner.image uses PSSM_GREMLIN_RUNNER_IMAGE. Unless this is intentional for backwards compatibility, it’s easy to set the “wrong” var and wonder why it didn’t apply.

Possible fix (use RUNNER_IMAGE consistently)
-    image: ${PSSM_GREMLIN_RUNNER_IMAGE:-revodesign-pssm-gremlin-non-root}
+    image: ${RUNNER_IMAGE:-revodesign-pssm-gremlin-non-root}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/docker-compose.yml` around lines 105 - 119, The runner service is
using image: ${PSSM_GREMLIN_RUNNER_IMAGE:-revodesign-pssm-gremlin-non-root}
while x-common-env defines RUNNER_IMAGE, causing a mismatch; update the runner
service (service name "runner") to use the same variable name as x-common-env
(RUNNER_IMAGE) or add a consistent fallback so both names map to the same value
(e.g., replace PSSM_GREMLIN_RUNNER_IMAGE with RUNNER_IMAGE or wire RUNNER_IMAGE
to PSSM_GREMLIN_RUNNER_IMAGE) to ensure the expected env var controls the image.

34-38: ⚠️ Potential issue | 🟠 Major

group_add: ["0"] is a meaningful privilege relaxation; document or scope it.

Adding the root group can unintentionally broaden access to mounted volumes / host resources (depending on ownership/mode). If this is only to handle docker.sock ownership edge cases, consider:

  • making it conditional via env (ex: EXTRA_GROUPS), or
  • adding a comment explaining why it’s required.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/docker-compose.yml` around lines 34 - 38, The docker-compose anchor
x-docker-socket-access currently forces group_add to include "0" (root) which
widens privileges; update the x-docker-socket-access anchor to avoid
unconditional root group addition by either making the extra group conditional
via an environment variable (e.g., read EXTRA_GROUPS or DOCKER_EXTRA_GROUPS and
only include "0" when explicitly set) or add an explicit comment above
x-docker-socket-access explaining why group_add: ["0"] is necessary and the
security tradeoffs; reference the x-docker-socket-access anchor and the
group_add key when making the change so callers of the anchor can opt in instead
of receiving root group access by default.
server/REvoDesign_PSSM_GREMLIN.sh (1)

11-33: ⚠️ Potential issue | 🟡 Minor

Conda env auto-detection can re-activate multiple envs (break only exits inner loop).

Right now break on Line 27 exits only the inner loop (env_2), so if multiple possible_conda_env_names exist, the script may activate more than once. If the intent is “activate first match and stop”, consider a break 2 or a flag.

Proposed fix (stop after first match)
-  for env_1 in "${possible_conda_env_names[@]}"; do
-  for env_2 in "${existed_conda_env_names[@]}"; do
+  for env_1 in "${possible_conda_env_names[@]}"; do
+    for env_2 in "${existed_conda_env_names[@]}"; do
       if [[ "$env_1" == "$env_2" ]]; then
         echo "find ${env_1} env"
         conda activate "${env_1}"
-        break
+        break 2
       fi
     done
   done
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/REvoDesign_PSSM_GREMLIN.sh` around lines 11 - 33, The nested loop over
possible_conda_env_names/env_2 can activate multiple conda envs because the
plain `break` only exits the inner loop; change the control flow in the
activation block to stop after the first successful activation — either replace
`break` with `break 2` to exit both loops immediately, or set a flag (e.g.,
activated=true) after `conda activate "${env_1}"` and break the inner loop, then
test the flag after the inner loop to break the outer loop; update references in
the script where possible_conda_env_names, env_1, env_2, and the `break` are
used.
♻️ Duplicate comments (8)
server/scripts/make_paired_MSA_simple.py (1)

105-105: Reminder to confirm pre-commit hooks and linting were run.
As per coding guidelines, "Enable pre-commit hooks with pre-commit install and run pre-commit run --all-files or make black before pushing to ensure formatting and linting."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/scripts/make_paired_MSA_simple.py` at line 105, Ensure pre-commit
hooks and linters were executed before pushing: run `pre-commit install` and
then either `pre-commit run --all-files` or `make black` (and any other
configured linters) and fix any reported issues in
server/scripts/make_paired_MSA_simple.py, especially around the paired_data
assignment and surrounding formatting to satisfy the repo’s hooks.
tools/copyright.js (1)

8-12: ⚠️ Potential issue | 🔴 Critical

Invalid shell pipeline — | > ./program.docx is still not fixed.

.join(" | ") produces:

find ./src/REvoDesign -type f -name '*.py' -print0 | xargs -0 cat | > ./program.docx

| > is not valid shell syntax; bash will error out every time this runs.

🛠️ Proposed fix
-const command = [
-  "find ./src/REvoDesign -type f -name '*.py' -print0",
-  "xargs -0 cat",
-  "> ./program.docx",
-].join(" | ");
+const command =
+  "find ./src/REvoDesign -type f -name '*.py' -print0 | xargs -0 cat > ./program.docx";
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tools/copyright.js` around lines 8 - 12, The current command array used to
build the shell line (the variable "command") is joined with " | " causing the
redirection string "> ./program.docx" to be prefixed by a pipe and produce an
invalid "`| >`" token; fix it by constructing the command so redirection is
appended without being joined by a pipe — e.g., join only the pipeline-producing
parts (the find and xargs/cat pieces) with " | " and then append the redirect ">
./program.docx" to the resulting string (i.e., do not include the redirection as
an element that gets joined with " | ").
src/REvoDesign/shortcuts/dialog_hooks.py (2)

36-37: or None still collapses empty-list results.

This keeps the semantic change where [] becomes None.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/REvoDesign/shortcuts/dialog_hooks.py` around lines 36 - 37, The helper
function find_all_small_molecules_in_protein currently appends "or None" which
converts an empty list result from find_small_molecules_in_protein("(all)") into
None; remove the "or None" so find_all_small_molecules_in_protein simply returns
the list returned by find_small_molecules_in_protein("(all)") (preserving
empty-list semantics) and keep the call site/function name
find_small_molecules_in_protein unchanged.

23-24: Private Biopython API still used for FASTA writers.
This remains tied to SeqIO._FormatToWriter, which is not a stable public API.

Biopython SeqIO._FormatToWriter public API alternatives
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/REvoDesign/shortcuts/dialog_hooks.py` around lines 23 - 24,
get_fasta_writer_choices currently depends on the private SeqIO._FormatToWriter
mapping; replace that with a safe discovery approach: create a minimal SeqRecord
and attempt SeqIO.write(record, io.StringIO(), fmt) for a curated list of
candidate format names (e.g., "fasta", "fasta-2line", any other known fasta
variants your project needs), collect formats where the write succeeds and
filter those that startwith("fas"), and return that list from
get_fasta_writer_choices; this removes reliance on SeqIO._FormatToWriter while
keeping the function signature and behavior.
server/.env.test (1)

29-34: ⚠️ Potential issue | 🟡 Minor

Wrong section header: runner identity isn’t “redis settings”.

This looks like a copy/paste header and makes the file harder to scan.

Proposed fix
-## redis settings
+## Runner identity settings
 RUNNER_UID=503
 RUNNER_GID=20
 RUNNER_USERNAME=revodesign
 RUNNER_GROUP=revodesign_appgroup
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/.env.test` around lines 29 - 34, The section header "## redis
settings" is incorrect for the runner identity variables; change that header to
something accurate like "## runner settings" or "## runner identity" so the
RUNNER_UID, RUNNER_GID, RUNNER_USERNAME, and RUNNER_GROUP entries are correctly
labeled and the .env.test file is easier to scan.
server/run/restart_pssm_flask.sh (1)

195-210: ⚠️ Potential issue | 🟡 Minor

DOMAIN override + printed URL likely misleading (0.0.0.0 isn’t a “clickable” address).

Even if services bind to 0.0.0.0, users typically access via 127.0.0.1, hostname, or remote IP. Consider:

  • keep DOMAIN="${DOMAIN:-0.0.0.0}", and/or
  • print both bind address and a suggested access URL (ex: http://127.0.0.1:${PORT}/...).
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/run/restart_pssm_flask.sh` around lines 195 - 210, The current
cmd_restart function sets DOMAIN="0.0.0.0" and prints a misleading clickable
URL; instead preserve any existing ENV value and show both the bind address and
a suggested access URL: keep DOMAIN="${DOMAIN:-0.0.0.0}" (so it respects
ENV_FILE sourced earlier), keep PORT="${PORT:-8080}", and change the echo output
in cmd_restart to print the bind address (DOMAIN) and a user-friendly suggested
access URL using 127.0.0.1 (e.g.
http://127.0.0.1:${PORT}/PSSM_GREMLIN/dashboard) so users can click or copy a
working URL while still showing the actual bind address.
server/docker/server/Dockerfile (1)

38-38: ⚠️ Potential issue | 🟠 Major

Use exec so Gunicorn becomes PID 1 (signal handling / graceful shutdown).

Current CMD ["sh","-c","gunicorn ..."] keeps the shell as PID 1.

Proposed fix
-CMD ["sh", "-c", "gunicorn -w 2 -b 0.0.0.0:${PORT:-8080} pssm_gremlin:app"]
+CMD ["sh", "-c", "exec gunicorn -w 2 -b 0.0.0.0:${PORT:-8080} pssm_gremlin:app"]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/docker/server/Dockerfile` at line 38, The Dockerfile CMD currently
invokes a shell which stays PID 1 and prevents Gunicorn from receiving signals;
update the CMD that runs "gunicorn -w 2 -b 0.0.0.0:${PORT:-8080}
pssm_gremlin:app" so Gunicorn becomes PID 1—either prepend "exec" to the shell
command invoked by CMD or switch to the exec/JSON form to invoke gunicorn
directly (so pssm_gremlin:app is started by gunicorn as PID 1 and gets proper
signal handling).
server/pssm_gremlin/pssm_gremlin.py (1)

1025-1027: "Invalid task id" string literal is still duplicated five times across routes

The same inline message appears in run_gremlin (line 1027), get_results (line 1066), download_results (line 1084), cancel_task (line 1129), and delete_task (line 1230). Define it as a module-level constant and reference it from each site.

Also applies to: 1064-1066, 1082-1084, 1127-1129, 1228-1230

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/pssm_gremlin/pssm_gremlin.py` around lines 1025 - 1027, The "Invalid
task id" literal is duplicated across multiple route handlers; define a
module-level constant (e.g., INVALID_TASK_ID_MSG = "Invalid task id") at top of
pssm_gremlin.py and replace the inline string in all places that validate
_normalize_task_id (where md5sum is checked) — specifically in run_gremlin,
get_results, download_results, cancel_task, and delete_task — so each return
uses jsonify({"status": "bad_request", "message": INVALID_TASK_ID_MSG}), 400
instead of the hardcoded literal.
🧹 Nitpick comments (12)
src/REvoDesign/clients/QtSocketConnector.py (1)

560-561: Redundant if guard — the condition is always True at this point.

Both paths inside the preceding if client not in self.meetingroom.current_clients: block (lines 505–558) end with an explicit return, so any execution that reaches line 560 is guaranteed to be the already-authenticated case. The wrapping if client in self.meetingroom.current_clients: check is tautological and can be dropped (or replaced with a plain comment/else).

♻️ Proposed simplification
-        if client in self.meetingroom.current_clients:
-            logging.debug("Client already authenticated; skip duplicate auth flow.")
+        else:
+            logging.debug("Client already authenticated; skip duplicate auth flow.")

Or, since the first block always returns, a bare statement is equally clear:

-        if client in self.meetingroom.current_clients:
-            logging.debug("Client already authenticated; skip duplicate auth flow.")
+        # Reaching here means client is already authenticated; nothing to do.
+        logging.debug("Client already authenticated; skip duplicate auth flow.")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/REvoDesign/clients/QtSocketConnector.py` around lines 560 - 561, In the
method inside QtSocketConnector where you handle authentication (the block that
checks client not in self.meetingroom.current_clients and returns), remove the
redundant "if client in self.meetingroom.current_clients:" guard and replace it
with a single unconditional logging.debug call (or an else comment) since any
execution reaching that point is already the authenticated case; update the code
that currently logs "Client already authenticated; skip duplicate auth flow." to
run directly without the tautological condition, keeping the log message and
surrounding context intact.
src/REvoDesign/basic/extensions.py (1)

147-147: Optional: add -> str return type annotation to basename_stem.

The method always returns a str (or raises), but the signature lacks the annotation, breaking consistency with the rest of the type-annotated codebase.

✏️ Proposed annotation
-    def basename_stem(self, fname: str):
+    def basename_stem(self, fname: str) -> str:
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/REvoDesign/basic/extensions.py` at line 147, The method basename_stem is
missing a return type annotation; update the function signature of
basename_stem(self, fname: str) to include -> str so it reads
basename_stem(self, fname: str) -> str to reflect that it always returns a
string and keep type annotations consistent across the codebase.
tools/copyright.js (1)

14-14: Drop the -l (login-shell) flag from the bash invocation.

-l sources /etc/profile and user profile files, making execution environment-dependent. A plain -c is sufficient for this pipeline.

♻️ Proposed fix
-const result = spawnSync("bash", ["-lc", command], { stdio: "inherit" });
+const result = spawnSync("bash", ["-c", command], { stdio: "inherit" });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tools/copyright.js` at line 14, The bash invocation in the spawnSync call
uses the login-shell flag "-l" which makes the environment dependent; update the
spawnSync call that currently uses spawnSync("bash", ["-lc", command], { stdio:
"inherit" }) to remove the "-l" so it becomes spawnSync("bash", ["-c", command],
{ stdio: "inherit" }), keeping the same stdio options and the same local
variable name (result) so behavior and error handling around spawnSync remain
unchanged.
tools/release_tag.sh (1)

27-29: Prefer -z for empty-string checks inside [[ ]] — consistent with this PR's quoting goals.

[[ ! $new_version ]] works in bash but is inconsistent with the quoting discipline applied elsewhere in this PR. The idiomatic form also makes intent clearer.

♻️ Proposed refactor
-if [[ ! $new_version || ! $old_version || ! $new_date  ]]; then
+if [[ -z "$new_version" || -z "$old_version" || -z "$new_date" ]]; then
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tools/release_tag.sh` around lines 27 - 29, Replace the empty-string checks
using negation with explicit -z tests to follow the quoting/idiom used
elsewhere: change the condition in release_tag.sh that currently checks
new_version, old_version, and new_date via [[ ! $new_version || ! $old_version
|| ! $new_date ]] to use [[ -z "$new_version" || -z "$old_version" || -z
"$new_date" ]], preserving the echo and exit behavior when any of new_version,
old_version, or new_date is empty.
src/REvoDesign/tools/measure_utils.py (1)

337-337: _atoms_cache should be excluded from dataclass-generated methods.

As a plain dataclass field, _atoms_cache is exposed in __init__, __repr__, and __eq__, which is not the intent for a private mutable cache. Use field with init=False / repr=False / compare=False.

♻️ Proposed fix
-    _atoms_cache: list[AtomDescriptor] | None = None
+    _atoms_cache: list[AtomDescriptor] | None = field(
+        default=None, init=False, repr=False, compare=False
+    )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/REvoDesign/tools/measure_utils.py` at line 337, _atoms_cache is currently
declared as a plain dataclass field and thus appears in
__init__/__repr__/__eq__; change its declaration to use
dataclasses.field(default=None, init=False, repr=False, compare=False) so it is
excluded from generated methods and remains a private mutable cache; import
field from dataclasses if not already and keep the type annotation
(list[AtomDescriptor] | None) but assign the field(...) default instead of a
bare None.
server/pssm_gremlin/templates/pssm_gremlin_dashboard.html (2)

1138-1184: Animation timer bookkeeping is reasonable; consider clearing pending timeouts on non-animated sync.

If syncThemeToggle(false) is called while an animation timeout is pending, the timeout still fires later and mutates state (is-animating). Not a huge deal, but you could clear any existing timer unconditionally at the start of syncThemeToggle() to avoid edge flicker.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/pssm_gremlin/templates/pssm_gremlin_dashboard.html` around lines 1138
- 1184, The syncThemeToggle function can leave a pending animation timeout that
later removes the "is-animating" class even when called without animation; to
fix, at the start of syncThemeToggle (before any potential re-animation) check
button.dataset.animTimer, parse it to a number, and if non-zero call
window.clearTimeout(existingTimer) and clear button.dataset.animTimer (and
ensure "is-animating" is removed), so pending timers are always cancelled when
syncing without animate; reference syncThemeToggle, button.dataset.animTimer,
is-animating, and the timer creation/clearing logic to implement this
unconditional cleanup.

129-192: Theme toggle animation + icon layer looks clean; watch browser support for color-mix / backdrop-filter.

The UI/UX improvements are good. If this dashboard is expected to run on older browsers (or embedded webviews), consider a fallback for color-mix() and backdrop-filter so contrast stays acceptable.

Also applies to: 946-962, 766-857

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/pssm_gremlin/templates/pssm_gremlin_dashboard.html` around lines 129 -
192, Provide fallback styles for browsers that lack support for color-mix() and
backdrop-filter: ensure .theme-toggle::before and .theme-toggle .theme-icon
declare explicit solid background-color and contrasting color (or a simpler
gradient) before using advanced properties, and add a `@supports` not
(backdrop-filter: none) block to reduce or remove blur/brightness filters for
unsupported browsers; update .theme-toggle.mode-light/.mode-dark/.mode-auto
variants to include these fallback colors so icon contrast remains acceptable
when color-mix/ backdrop-filter aren’t available.
tests/server/test_pssm_gremlin.py (1)

1063-1106: Batch delete normalization test is valuable; consider adding an assertion for duplicate handling semantics.

You pass md5sum twice (uppercased + padded + raw duplicate). It might be useful to explicitly assert the API reports the duplicate only once (which you do via deleted == [md5sum]) and that ignored/not_found don’t include normalized duplicates (guards future regressions).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/server/test_pssm_gremlin.py` around lines 1063 - 1106, The test
test_batch_delete_guards_and_normalizes_each_md5sum should explicitly assert
duplicate handling: after calling client.post with duplicate representations of
md5sum, add assertions that the normalized md5sum appears only once in
payload["deleted"] (already present) and also verify that payload["ignored"],
payload["not_found"], and payload["forbidden"] do not contain any normalized
forms of the duplicated md5sum (e.g., ensure none of these lists include md5sum
or its uppercased/padded variants) so duplicates aren't mistakenly categorized
elsewhere; update assertions after the POST and before checking the task via
module.task_store.get_task(md5sum).
server/REvoDesign_PSSM_GREMLIN.sh (1)

145-226: Array-based command execution is a solid hardening step; consider consolidating log redirections.

Using local -a cmd=(...) and "${cmd[@]}" reduces injection risk and quoting bugs. One small maintainability win: build log paths as a single quoted string ("${pipline_res_dir}/log/${instance}_...") rather than mixing quoted/unquoted segments.

Also applies to: 228-271

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/REvoDesign_PSSM_GREMLIN.sh` around lines 145 - 226, The RUN_GREMLIN
function uses array-based commands correctly but mixes quoted/unquoted segments
when redirecting logs; define full quoted log path variables (e.g.,
out_log="${pipline_res_dir}/log/${instance}_gremlin_hhblits.log"
err_log="${pipline_res_dir}/log/${instance}_gremlin_hhblits.err") and use those
variables for all redirections instead of concatenating quoted and unquoted
pieces, then apply this pattern consistently for hhblits, hhfilter,
fasta_lower_char_rm.py, and GREMLIN_TFv1 invocations (all places building
"${pipline_res_dir}/log/${instance}_...") to ensure consistent quoting and
easier maintenance.
server/.env.test (1)

3-6: Fix dotenv-linter key ordering warnings.

dotenv-linter reports 6 UnorderedKey violations in this file (lines 5, 9, 21, 26, 31, 33). While the file has good logical grouping by section, keys within each section should be alphabetically ordered: RUNNER_IMAGE before SERVER_IMAGE, LOG_DIR before SERVER_DIR, BROKER_URL before REDIS_URL, GUNICORN_WORKERS before NPROC, and RUNNER_GID/RUNNER_GROUP before RUNNER_UID. Additionally, line 29's comment says "redis settings" but contains runner configuration variables—update the comment for clarity.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/.env.test` around lines 3 - 6, Reorder the keys within each logical
section of server/.env.test to satisfy dotenv-linter alphabetical rules: place
RUNNER_IMAGE before SERVER_IMAGE, LOG_DIR before SERVER_DIR, BROKER_URL before
REDIS_URL, GUNICORN_WORKERS before NPROC, and ensure RUNNER_GID and RUNNER_GROUP
appear before RUNNER_UID; also update the comment on the runner block (currently
"redis settings") to accurately describe runner configuration. Locate the
variables RUNNER_IMAGE, SERVER_IMAGE, LOG_DIR, SERVER_DIR, BROKER_URL,
REDIS_URL, GUNICORN_WORKERS, NPROC, RUNNER_GID, RUNNER_GROUP, and RUNNER_UID and
adjust ordering and the comment accordingly to remove UnorderedKey violations.
server/pssm_gremlin/pssm_gremlin.py (2)

380-384: Ruff TRY003: long ValueError messages on lines 383 and 442 should be moved into a custom exception class (or shortened)

Static analysis flags both raises as TRY003. The simplest fix is to extract a small sentinel exception or shorten the inline message.

♻️ Minimal fix – custom exception
+class PathEscapeError(ValueError):
+    """Raised when a path would escape its configured base directory."""
+
+
 def _safe_join(base_dir: str, *parts: str) -> str:
     candidate = os.path.abspath(os.path.join(base_dir, *parts))
     if not _path_is_within(base_dir, candidate):
-        raise ValueError(f"Path escapes configured base directory: {candidate}")
+        raise PathEscapeError(f"Path escapes base directory: {candidate!r}")
     return candidate


 def _task_zip_path(task: Any) -> str:
     raw_task_id = task if isinstance(task, str) else task["md5sum"]
     task_id = _normalize_task_id(raw_task_id)
     if task_id is None:
-        raise ValueError(f"Invalid task id for result archive: {raw_task_id!r}")
+        raise ValueError(f"Invalid task id: {raw_task_id!r}")
     return _safe_join(app.config["RESULTS_FOLDER"], f"{task_id}_PSSM_GREMLIN_results.zip")

As per coding guidelines, pre-commit run --all-files (or make black) must be run before pushing to ensure formatting and linting.

Also applies to: 438-443

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/pssm_gremlin/pssm_gremlin.py` around lines 380 - 384, Replace the long
inline ValueError messages in _safe_join (and the similar raise in the block
around lines 438-443) with a small custom exception class (e.g.,
PathEscapeError) or shorten the message to a brief sentinel; specifically,
define a new exception (class PathEscapeError(ValueError): pass) near the top of
the module, then change the raises in _safe_join and the other location to raise
PathEscapeError("path escapes base") or a similarly short message, and run the
repo linters/formatters (pre-commit / make black) before committing.

367-391: $ anchor in _TASK_ID_PATTERN is redundant when used with fullmatch, and [A-F] is dead after .lower()

Two minor issues in the pattern/normaliser pair:

  • _TASK_ID_PATTERN.fullmatch(...) already anchors both ends, so the trailing $ in r"[a-fA-F0-9]{32}$" is redundant.
  • _normalize_task_id calls .lower() before matching, so [A-F] is never matched.
♻️ Suggested cleanup
-_TASK_ID_PATTERN = re.compile(r"[a-fA-F0-9]{32}$")
+_TASK_ID_PATTERN = re.compile(r"[a-f0-9]{32}")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/pssm_gremlin/pssm_gremlin.py` around lines 367 - 391, The regex
_TASK_ID_PATTERN is overly specific and partly redundant given
_normalize_task_id lowercases input and code uses fullmatch; update the pattern
to only match lowercase hex and drop the trailing anchor (e.g., change the
compiled pattern in _TASK_ID_PATTERN from r"[a-fA-F0-9]{32}$" to
r"[a-f0-9]{32}") so fullmatch with the lowercased value in _normalize_task_id
works correctly and the unused uppercase range and trailing '$' are removed.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@server/.env.test`:
- Around line 7-18: The .env.test currently contains hard-coded absolute local
paths (SERVER_DIR, LOG_DIR, DB_UNIREF30, DB_UNIREF90, USERS_FILE) which are
non-portable and leak local filesystem info; replace these values with
repo-relative placeholders or env-template variables (e.g.,
SERVER_DIR=./server_test, LOG_DIR=./server_test/logs,
DB_UNIREF30=./data/miniuc30 or DB_UNIREF30=${PROJECT_ROOT}/data/miniuc30,
USERS_FILE=./config/users.template.txt) and add a short comment explaining
callers should copy and fill with their local absolute paths in a non-committed
.env.local; ensure the variable names (SERVER_DIR, LOG_DIR, DB_UNIREF30,
DB_UNIREF90, USERS_FILE) remain unchanged so code loading these env vars
continues to work.

In `@server/pssm_gremlin/pssm_gremlin.py`:
- Around line 595-600: The deletion currently calls
shutil.rmtree(safe_result_dir, ...) unconditionally for any non-root-like
safe_result_dir; move the shutil.rmtree invocation so it only runs when the path
is confirmed inside the configured results folder by
_path_is_within(app.config["RESULTS_FOLDER"], safe_result_dir). Concretely,
change the control flow around safe_result_dir, so if safe_result_dir is
root-like you warn and skip, else if _path_is_within(...) is false you log a
warning and skip deletion, and only call shutil.rmtree when _path_is_within(...)
returns true; if you need to allow legacy external paths, implement an explicit
allow-list check instead of deleting by default.

In `@server/pssm_gremlin/templates/pssm_gremlin_dashboard.html`:
- Around line 1516-1531: Replace the unreliable XMLHttpRequest-based logout in
the triggerLogout function with a client redirect to a new server-driven logout
endpoint (e.g., navigate to "/PSSM_GREMLIN/logout") and implement that endpoint
server-side to respond with 401 and a WWW-Authenticate header plus a simple "You
are logged out" confirmation page; also guard the
document.getElementById("logoutBtn") usage with a null check before attaching
event listeners to avoid runtime errors. Ensure references: update triggerLogout
to perform window.location.href = "/PSSM_GREMLIN/logout" and create a server
handler for the "/PSSM_GREMLIN/logout" path that returns 401 + WWW-Authenticate
and a logout confirmation body, and add a null-guard where
getElementById("logoutBtn") is used.

In `@server/REvoDesign_PSSM_GREMLIN.sh`:
- Around line 36-37: The script currently calls readlink -f on possibly-empty
variables (e.g. REVODESIGN_RUNSCRIPT_PATH assignment and later
fasta_fp=$(readlink -f "$fasta")), which under set -e causes the script to exit
if -i (input fasta) is omitted; after parsing options with getopts validate that
required inputs (the variable parsed into fasta via -i) are present and
non-empty and call the usage/exit routine if missing, or defer set -e until
after validation; specifically, add an explicit check for the variable name used
for the -i option (fasta) right after getopts (and before fasta_fp=$(readlink -f
"$fasta")) and if empty print usage and exit, or alternatively guard readlink
calls with a test like [ -n "$fasta" ] && fasta_fp=$(readlink -f "$fasta") so
readlink is never invoked on an empty string.

In `@tools/copyright.js`:
- Around line 15-17: When handling the child spawn result in the block that
checks if (result.status !== 0), also check for spawn-level errors via
result.error and print a diagnostic before exiting; update the logic around
result.status and process.exit(result.status || 1) to detect if result.error
exists and call console.error (or the project's logger) with a clear message and
the Error object, then exit with the existing fallback exit code so the error
isn't silently swallowed.

In `@tools/release_tag.sh`:
- Line 39: The sed commands that interpolate $new_version and $old_version use
unescaped version strings so dots act as regex wildcards; update the script to
escape literal dots in those variables before passing them to sed (e.g.,
transform new_version and old_version into escaped_version by replacing '.' with
'\.'), then use the escaped variables in the existing "${SED}" -i 's/.../../'
invocations (the lines that reference $new_version and $old_version) so sed
treats dots literally.

---

Outside diff comments:
In `@server/docker-compose.yml`:
- Around line 105-119: The runner service is using image:
${PSSM_GREMLIN_RUNNER_IMAGE:-revodesign-pssm-gremlin-non-root} while
x-common-env defines RUNNER_IMAGE, causing a mismatch; update the runner service
(service name "runner") to use the same variable name as x-common-env
(RUNNER_IMAGE) or add a consistent fallback so both names map to the same value
(e.g., replace PSSM_GREMLIN_RUNNER_IMAGE with RUNNER_IMAGE or wire RUNNER_IMAGE
to PSSM_GREMLIN_RUNNER_IMAGE) to ensure the expected env var controls the image.
- Around line 34-38: The docker-compose anchor x-docker-socket-access currently
forces group_add to include "0" (root) which widens privileges; update the
x-docker-socket-access anchor to avoid unconditional root group addition by
either making the extra group conditional via an environment variable (e.g.,
read EXTRA_GROUPS or DOCKER_EXTRA_GROUPS and only include "0" when explicitly
set) or add an explicit comment above x-docker-socket-access explaining why
group_add: ["0"] is necessary and the security tradeoffs; reference the
x-docker-socket-access anchor and the group_add key when making the change so
callers of the anchor can opt in instead of receiving root group access by
default.

In `@server/REvoDesign_PSSM_GREMLIN.sh`:
- Around line 11-33: The nested loop over possible_conda_env_names/env_2 can
activate multiple conda envs because the plain `break` only exits the inner
loop; change the control flow in the activation block to stop after the first
successful activation — either replace `break` with `break 2` to exit both loops
immediately, or set a flag (e.g., activated=true) after `conda activate
"${env_1}"` and break the inner loop, then test the flag after the inner loop to
break the outer loop; update references in the script where
possible_conda_env_names, env_1, env_2, and the `break` are used.

In `@src/REvoDesign/tools/measure_utils.py`:
- Line 648: The return type annotation on read_measurement is incorrect: the
function populates hits via Measurement.from_session_names(...) which returns a
list[Measurement] and then returns hits; update the signature of
read_measurement to return list[Measurement] (or Sequence[Measurement]) instead
of Measurement, ensuring the annotation matches the actual returned value from
Measurement.from_session_names and the variable hits.
- Line 691: Fix the typo in the debug message that prints duplicate-skipping
info: change the string in the print call that references a.resi from "[DEBUG]
skiping {a.resi} to avoid duplicates" to use the correct spelling "skipping" so
it reads "[DEBUG] skipping {a.resi} to avoid duplicates"; locate the print
statement that uses a.resi in measure_utils.py and update only the text in that
print/log call.
- Around line 675-678: The error message uses [m.name for m in hits] which is
always [] when not hits; change the ValueError to report useful context by
listing available measurement names from the collection you searched (e.g.,
session.measurements or the variable that holds all measurements) and/or the
search criteria, not from hits; update the raise in the same block (the code
that checks "if not hits") to include a descriptive message with the actual
available measurement names and/or the requested measurement identifier instead
of iterating over hits.
- Around line 711-712: The list comprehensions for atom_a and atom_b assume
every entry in pairs.values() has at least two items, which raises IndexError
when atoms() resolves to 0 or 1 atoms; update the logic that builds
atom_a/atom_b to skip or handle short tuples (e.g., filter pairs.values() by
len(x) >= 2) before accessing x[0] and x[1], or provide a safe fallback value
for unresolved measurements so atom_a and atom_b only reference valid pair
entries (target symbols: pairs, atom_a, atom_b, and any code that calls
atoms()).
- Around line 486-625: Remove the two dead helper methods
_build_uniqueid_to_atom_map and _resolve_by_coords from the class: delete their
entire method definitions (they are unused), and update any code that might
reference them (search for _build_uniqueid_to_atom_map and _resolve_by_coords
and remove or replace calls). Also ensure you do not reintroduce the forbidden
fallback behavior (do not add checks for 'id' or 'serial' anywhere) and remove
the unused uid parameter consideration—if any logic depended on resolving by
coords, implement a clear, intentional replacement rather than restoring these
methods.

---

Duplicate comments:
In `@server/.env.test`:
- Around line 29-34: The section header "## redis settings" is incorrect for the
runner identity variables; change that header to something accurate like "##
runner settings" or "## runner identity" so the RUNNER_UID, RUNNER_GID,
RUNNER_USERNAME, and RUNNER_GROUP entries are correctly labeled and the
.env.test file is easier to scan.

In `@server/docker/server/Dockerfile`:
- Line 38: The Dockerfile CMD currently invokes a shell which stays PID 1 and
prevents Gunicorn from receiving signals; update the CMD that runs "gunicorn -w
2 -b 0.0.0.0:${PORT:-8080} pssm_gremlin:app" so Gunicorn becomes PID 1—either
prepend "exec" to the shell command invoked by CMD or switch to the exec/JSON
form to invoke gunicorn directly (so pssm_gremlin:app is started by gunicorn as
PID 1 and gets proper signal handling).

In `@server/pssm_gremlin/pssm_gremlin.py`:
- Around line 1025-1027: The "Invalid task id" literal is duplicated across
multiple route handlers; define a module-level constant (e.g.,
INVALID_TASK_ID_MSG = "Invalid task id") at top of pssm_gremlin.py and replace
the inline string in all places that validate _normalize_task_id (where md5sum
is checked) — specifically in run_gremlin, get_results, download_results,
cancel_task, and delete_task — so each return uses jsonify({"status":
"bad_request", "message": INVALID_TASK_ID_MSG}), 400 instead of the hardcoded
literal.

In `@server/run/restart_pssm_flask.sh`:
- Around line 195-210: The current cmd_restart function sets DOMAIN="0.0.0.0"
and prints a misleading clickable URL; instead preserve any existing ENV value
and show both the bind address and a suggested access URL: keep
DOMAIN="${DOMAIN:-0.0.0.0}" (so it respects ENV_FILE sourced earlier), keep
PORT="${PORT:-8080}", and change the echo output in cmd_restart to print the
bind address (DOMAIN) and a user-friendly suggested access URL using 127.0.0.1
(e.g. http://127.0.0.1:${PORT}/PSSM_GREMLIN/dashboard) so users can click or
copy a working URL while still showing the actual bind address.

In `@server/scripts/make_paired_MSA_simple.py`:
- Line 105: Ensure pre-commit hooks and linters were executed before pushing:
run `pre-commit install` and then either `pre-commit run --all-files` or `make
black` (and any other configured linters) and fix any reported issues in
server/scripts/make_paired_MSA_simple.py, especially around the paired_data
assignment and surrounding formatting to satisfy the repo’s hooks.

In `@src/REvoDesign/shortcuts/dialog_hooks.py`:
- Around line 36-37: The helper function find_all_small_molecules_in_protein
currently appends "or None" which converts an empty list result from
find_small_molecules_in_protein("(all)") into None; remove the "or None" so
find_all_small_molecules_in_protein simply returns the list returned by
find_small_molecules_in_protein("(all)") (preserving empty-list semantics) and
keep the call site/function name find_small_molecules_in_protein unchanged.
- Around line 23-24: get_fasta_writer_choices currently depends on the private
SeqIO._FormatToWriter mapping; replace that with a safe discovery approach:
create a minimal SeqRecord and attempt SeqIO.write(record, io.StringIO(), fmt)
for a curated list of candidate format names (e.g., "fasta", "fasta-2line", any
other known fasta variants your project needs), collect formats where the write
succeeds and filter those that startwith("fas"), and return that list from
get_fasta_writer_choices; this removes reliance on SeqIO._FormatToWriter while
keeping the function signature and behavior.

In `@tools/copyright.js`:
- Around line 8-12: The current command array used to build the shell line (the
variable "command") is joined with " | " causing the redirection string ">
./program.docx" to be prefixed by a pipe and produce an invalid "`| >`" token;
fix it by constructing the command so redirection is appended without being
joined by a pipe — e.g., join only the pipeline-producing parts (the find and
xargs/cat pieces) with " | " and then append the redirect "> ./program.docx" to
the resulting string (i.e., do not include the redirection as an element that
gets joined with " | ").

---

Nitpick comments:
In `@server/.env.test`:
- Around line 3-6: Reorder the keys within each logical section of
server/.env.test to satisfy dotenv-linter alphabetical rules: place RUNNER_IMAGE
before SERVER_IMAGE, LOG_DIR before SERVER_DIR, BROKER_URL before REDIS_URL,
GUNICORN_WORKERS before NPROC, and ensure RUNNER_GID and RUNNER_GROUP appear
before RUNNER_UID; also update the comment on the runner block (currently "redis
settings") to accurately describe runner configuration. Locate the variables
RUNNER_IMAGE, SERVER_IMAGE, LOG_DIR, SERVER_DIR, BROKER_URL, REDIS_URL,
GUNICORN_WORKERS, NPROC, RUNNER_GID, RUNNER_GROUP, and RUNNER_UID and adjust
ordering and the comment accordingly to remove UnorderedKey violations.

In `@server/pssm_gremlin/pssm_gremlin.py`:
- Around line 380-384: Replace the long inline ValueError messages in _safe_join
(and the similar raise in the block around lines 438-443) with a small custom
exception class (e.g., PathEscapeError) or shorten the message to a brief
sentinel; specifically, define a new exception (class
PathEscapeError(ValueError): pass) near the top of the module, then change the
raises in _safe_join and the other location to raise PathEscapeError("path
escapes base") or a similarly short message, and run the repo linters/formatters
(pre-commit / make black) before committing.
- Around line 367-391: The regex _TASK_ID_PATTERN is overly specific and partly
redundant given _normalize_task_id lowercases input and code uses fullmatch;
update the pattern to only match lowercase hex and drop the trailing anchor
(e.g., change the compiled pattern in _TASK_ID_PATTERN from r"[a-fA-F0-9]{32}$"
to r"[a-f0-9]{32}") so fullmatch with the lowercased value in _normalize_task_id
works correctly and the unused uppercase range and trailing '$' are removed.

In `@server/pssm_gremlin/templates/pssm_gremlin_dashboard.html`:
- Around line 1138-1184: The syncThemeToggle function can leave a pending
animation timeout that later removes the "is-animating" class even when called
without animation; to fix, at the start of syncThemeToggle (before any potential
re-animation) check button.dataset.animTimer, parse it to a number, and if
non-zero call window.clearTimeout(existingTimer) and clear
button.dataset.animTimer (and ensure "is-animating" is removed), so pending
timers are always cancelled when syncing without animate; reference
syncThemeToggle, button.dataset.animTimer, is-animating, and the timer
creation/clearing logic to implement this unconditional cleanup.
- Around line 129-192: Provide fallback styles for browsers that lack support
for color-mix() and backdrop-filter: ensure .theme-toggle::before and
.theme-toggle .theme-icon declare explicit solid background-color and
contrasting color (or a simpler gradient) before using advanced properties, and
add a `@supports` not (backdrop-filter: none) block to reduce or remove
blur/brightness filters for unsupported browsers; update
.theme-toggle.mode-light/.mode-dark/.mode-auto variants to include these
fallback colors so icon contrast remains acceptable when color-mix/
backdrop-filter aren’t available.

In `@server/REvoDesign_PSSM_GREMLIN.sh`:
- Around line 145-226: The RUN_GREMLIN function uses array-based commands
correctly but mixes quoted/unquoted segments when redirecting logs; define full
quoted log path variables (e.g.,
out_log="${pipline_res_dir}/log/${instance}_gremlin_hhblits.log"
err_log="${pipline_res_dir}/log/${instance}_gremlin_hhblits.err") and use those
variables for all redirections instead of concatenating quoted and unquoted
pieces, then apply this pattern consistently for hhblits, hhfilter,
fasta_lower_char_rm.py, and GREMLIN_TFv1 invocations (all places building
"${pipline_res_dir}/log/${instance}_...") to ensure consistent quoting and
easier maintenance.

In `@src/REvoDesign/basic/extensions.py`:
- Line 147: The method basename_stem is missing a return type annotation; update
the function signature of basename_stem(self, fname: str) to include -> str so
it reads basename_stem(self, fname: str) -> str to reflect that it always
returns a string and keep type annotations consistent across the codebase.

In `@src/REvoDesign/clients/QtSocketConnector.py`:
- Around line 560-561: In the method inside QtSocketConnector where you handle
authentication (the block that checks client not in
self.meetingroom.current_clients and returns), remove the redundant "if client
in self.meetingroom.current_clients:" guard and replace it with a single
unconditional logging.debug call (or an else comment) since any execution
reaching that point is already the authenticated case; update the code that
currently logs "Client already authenticated; skip duplicate auth flow." to run
directly without the tautological condition, keeping the log message and
surrounding context intact.

In `@src/REvoDesign/tools/measure_utils.py`:
- Line 337: _atoms_cache is currently declared as a plain dataclass field and
thus appears in __init__/__repr__/__eq__; change its declaration to use
dataclasses.field(default=None, init=False, repr=False, compare=False) so it is
excluded from generated methods and remains a private mutable cache; import
field from dataclasses if not already and keep the type annotation
(list[AtomDescriptor] | None) but assign the field(...) default instead of a
bare None.

In `@tests/server/test_pssm_gremlin.py`:
- Around line 1063-1106: The test
test_batch_delete_guards_and_normalizes_each_md5sum should explicitly assert
duplicate handling: after calling client.post with duplicate representations of
md5sum, add assertions that the normalized md5sum appears only once in
payload["deleted"] (already present) and also verify that payload["ignored"],
payload["not_found"], and payload["forbidden"] do not contain any normalized
forms of the duplicated md5sum (e.g., ensure none of these lists include md5sum
or its uppercased/padded variants) so duplicates aren't mistakenly categorized
elsewhere; update assertions after the POST and before checking the task via
module.task_store.get_task(md5sum).

In `@tools/copyright.js`:
- Line 14: The bash invocation in the spawnSync call uses the login-shell flag
"-l" which makes the environment dependent; update the spawnSync call that
currently uses spawnSync("bash", ["-lc", command], { stdio: "inherit" }) to
remove the "-l" so it becomes spawnSync("bash", ["-c", command], { stdio:
"inherit" }), keeping the same stdio options and the same local variable name
(result) so behavior and error handling around spawnSync remain unchanged.

In `@tools/release_tag.sh`:
- Around line 27-29: Replace the empty-string checks using negation with
explicit -z tests to follow the quoting/idiom used elsewhere: change the
condition in release_tag.sh that currently checks new_version, old_version, and
new_date via [[ ! $new_version || ! $old_version || ! $new_date ]] to use [[ -z
"$new_version" || -z "$old_version" || -z "$new_date" ]], preserving the echo
and exit behavior when any of new_version, old_version, or new_date is empty.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 45f2ace and 29332b2.

📒 Files selected for processing (31)
  • .github/workflows/docker-image.yml
  • .github/workflows/lint_badge.yml
  • .github/workflows/schedule-update-actions.yml
  • .github/workflows/semantic-pr-check.yml
  • .github/workflows/unit_tests_tag.yml
  • CHANGELOG.md
  • README.md
  • server/.env.example
  • server/.env.test
  • server/README.legacy.md
  • server/README.md
  • server/REvoDesign_PSSM_GREMLIN.sh
  • server/docker-compose.yml
  • server/docker/server/Dockerfile
  • server/pssm_gremlin/pssm_gremlin.py
  • server/pssm_gremlin/templates/pssm_gremlin_dashboard.html
  • server/run/restart_pssm_flask.sh
  • server/scripts/make_paired_MSA_simple.py
  • src/REvoDesign/basic/extensions.py
  • src/REvoDesign/clients/QtSocketConnector.py
  • src/REvoDesign/clusters/combine_positions.py
  • src/REvoDesign/logger/logger.py
  • src/REvoDesign/shortcuts/dialog_hooks.py
  • src/REvoDesign/tools/cgo_utils.py
  • src/REvoDesign/tools/measure_utils.py
  • src/REvoDesign/tools/package_manager.py
  • src/REvoDesign/tools/safe_pickle.py
  • src/REvoDesign/tools/utils.py
  • tests/server/test_pssm_gremlin.py
  • tools/copyright.js
  • tools/release_tag.sh
💤 Files with no reviewable changes (2)
  • server/.env.example
  • src/REvoDesign/tools/cgo_utils.py

Comment thread server/.env.test Outdated
Comment thread server/pssm_gremlin/pssm_gremlin.py
Comment on lines +1516 to +1531
function triggerLogout() {
const targetPath = "/PSSM_GREMLIN/create_task";
const xhr = new XMLHttpRequest();
xhr.open("GET", targetPath, true, "logout", "logout");
xhr.onload = () => {
window.location.href = targetPath;
};
xhr.onerror = () => {
window.location.href = targetPath;
};
try {
xhr.send();
} catch (error) {
window.location.href = targetPath;
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

git ls-files | grep -i "pssm_gremlin_dashboard.html"

Repository: YaoYinYing/REvoDesign

Length of output: 122


🏁 Script executed:

wc -l server/pssm_gremlin/templates/pssm_gremlin_dashboard.html

Repository: YaoYinYing/REvoDesign

Length of output: 127


🏁 Script executed:

sed -n '1510,1535p' server/pssm_gremlin/templates/pssm_gremlin_dashboard.html

Repository: YaoYinYing/REvoDesign

Length of output: 741


🏁 Script executed:

sed -n '1615,1625p' server/pssm_gremlin/templates/pssm_gremlin_dashboard.html

Repository: YaoYinYing/REvoDesign

Length of output: 429


🌐 Web query:

XMLHttpRequest.open() with username password parameter for Basic Auth logout browser compatibility Chrome Firefox

💡 Result:

  • XMLHttpRequest.open(method, url, async, user, password) is still part of the web API (supported in the interface/signature), but you can’t rely on it for HTTP Basic Auth behavior to be consistent across browsers/versions. (developer.mozilla.org)
  • In particular, Chrome has had real-world cases where the user/password arguments are ignored and the browser shows the Basic Auth prompt anyway, breaking “silent login” flows that worked elsewhere. (peteyat.es)
  • Don’t use http(s)://user:pass@host/ as a workaround: putting credentials in the URL is deprecated, and Chrome may ignore/reject it. (stackoverflow.com)

What to do instead (works in Chrome + Firefox)

Send the header yourself (XHR or fetch):

const token = btoa(`${user}:${pass}`);
const xhr = new XMLHttpRequest();
xhr.open("GET", "/protected");
xhr.setRequestHeader("Authorization", `Basic ${token}`);
xhr.send();

(Equivalent with fetch: headers: { Authorization: "Basic " + token }.)

“Logout” with Basic Auth (Chrome/Firefox reality)

  • There is no reliable, standards-based way for a web page to force-clear the browser’s cached Basic Auth credentials; “logout” is the classic Basic Auth “roach motel” problem. (stackoverflow.com)
  • Common hacks (vary by browser/version):
    • Return 401 Unauthorized with WWW-Authenticate to force a re-prompt; some browsers clear credentials if the user cancels, but it’s not dependable. (the-art-of-web.com)
    • Change the auth realm (e.g., per-session realm) so cached credentials no longer apply—messy but sometimes effective. (the-art-of-web.com)

Practical recommendation

If you need a predictable “Log out” button across Chrome + Firefox, don’t use browser-managed Basic Auth for an interactive web app—use cookie/session (or token) auth, where logout is under application control. (stackoverflow.com)

Citations:


Logout implementation is unreliable for Basic Auth; server-driven approach required.

The XMLHttpRequest.open("GET", targetPath, true, "logout", "logout") approach does not reliably clear cached Basic Auth credentials. Browsers (especially Chrome) ignore the username/password parameters and continue to supply cached credentials automatically. There is no standards-based way to force-clear cached Basic Auth from the browser, making the redirect to create_task likely to re-authenticate immediately and appear as if logout failed.

Instead, add a real /PSSM_GREMLIN/logout endpoint that returns 401 Unauthorized with WWW-Authenticate header and a "You are logged out" confirmation page, then navigate there.

Also, document.getElementById("logoutBtn") at line 1618 should be guarded against null to prevent a runtime error in template variants:

Suggested null-guard
-    document.getElementById("logoutBtn").addEventListener("click", () => {
-      triggerLogout();
-    });
+    const logoutBtn = document.getElementById("logoutBtn");
+    if (logoutBtn) {
+      logoutBtn.addEventListener("click", () => {
+        triggerLogout();
+      });
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/pssm_gremlin/templates/pssm_gremlin_dashboard.html` around lines 1516
- 1531, Replace the unreliable XMLHttpRequest-based logout in the triggerLogout
function with a client redirect to a new server-driven logout endpoint (e.g.,
navigate to "/PSSM_GREMLIN/logout") and implement that endpoint server-side to
respond with 401 and a WWW-Authenticate header plus a simple "You are logged
out" confirmation page; also guard the document.getElementById("logoutBtn")
usage with a null check before attaching event listeners to avoid runtime
errors. Ensure references: update triggerLogout to perform window.location.href
= "/PSSM_GREMLIN/logout" and create a server handler for the
"/PSSM_GREMLIN/logout" path that returns 401 + WWW-Authenticate and a logout
confirmation body, and add a null-guard where getElementById("logoutBtn") is
used.

Comment thread server/REvoDesign_PSSM_GREMLIN.sh
Comment thread tools/copyright.js
Comment on lines +15 to +17
if (result.status !== 0) {
process.exit(result.status || 1);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Surface spawn-level errors (result.error).

When bash itself can't be found or the OS rejects the spawn, result.status is null and result.error is a Node.js Error. The || 1 fallback exits correctly, but the error is silently swallowed — no diagnostic is printed because stdio: "inherit" only wires the child's stdio, not Node.js-level errors.

🛠️ Proposed fix
 if (result.status !== 0) {
+  if (result.error) {
+    console.error("Failed to spawn bash:", result.error.message);
+  }
   process.exit(result.status || 1);
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tools/copyright.js` around lines 15 - 17, When handling the child spawn
result in the block that checks if (result.status !== 0), also check for
spawn-level errors via result.error and print a diagnostic before exiting;
update the logic around result.status and process.exit(result.status || 1) to
detect if result.error exists and call console.error (or the project's logger)
with a clear message and the Error object, then exit with the existing fallback
exit code so the error isn't silently swallowed.

Comment thread tools/release_tag.sh

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
server/REvoDesign_PSSM_GREMLIN.sh (1)

278-287: ⚠️ Potential issue | 🟠 Major

Avoid readlink -f before the output directory exists.

With set -e, readlink -f fails on non-existent paths, so the default output directory (and any new user-specified directory) will terminate the script before mkdir -p runs.

Suggested fix
 if [[ -z "${output_dir:-}" ]]; then
   fasta_fn=$(basename "${fasta_fp}")
   instance=${fasta_fn%.fasta}
-  pipline_res_dir=$(readlink -f "${instance}"_GREMLIN_PSSM_output)
+  pipline_res_dir="${instance}_GREMLIN_PSSM_output"
 else
-  pipline_res_dir=$(readlink -f "$output_dir")
+  pipline_res_dir="$output_dir"
 fi
 
-mkdir -p "$pipline_res_dir"/log
+mkdir -p "$pipline_res_dir"/log
+pipline_res_dir=$(readlink -f "$pipline_res_dir")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/REvoDesign_PSSM_GREMLIN.sh` around lines 278 - 287, The script calls
readlink -f to canonicalize pipline_res_dir before ensuring the directory
exists, which fails under set -e for non-existent paths (see variables
pipline_res_dir, output_dir, fasta_fp, instance, fasta_fn); change the logic to
determine the desired path string then mkdir -p that path first and only
afterwards canonicalize it (or use readlink/realpath with the
-m/--canonicalize-missing option if available) so the script creates the
directory before attempting to resolve it and avoids early termination.
♻️ Duplicate comments (2)
server/.env.test (1)

30-34: ⚠️ Potential issue | 🟡 Minor

Fix the runner identity section header.

This block defines RUNNER_UID/GID/USERNAME/GROUP but the header still says “redis settings,” which is misleading.

Suggested fix
-## redis settings
+## Runner identity settings
 RUNNER_UID=503
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/.env.test` around lines 30 - 34, The header above the runner identity
env vars is incorrect; replace the misleading "redis settings" header with a
clear section header for the runner identity so it matches the variables
RUNNER_UID, RUNNER_GID, RUNNER_USERNAME, and RUNNER_GROUP; update the comment
line immediately above those variables to something like "runner identity" or
"runner settings" to accurately describe the block.
src/REvoDesign/shortcuts/dialog_hooks.py (1)

23-24: ⚠️ Potential issue | 🟠 Major

Avoid private Biopython API usage for FASTA writers.

SeqIO._FormatToWriter is internal and can change without notice. Prefer a public API or keep a defensive fallback list to avoid breaking on Biopython upgrades.

Suggested fix (fallback if private API changes)
 def get_fasta_writer_choices() -> list[str]:
-    return [fmt for fmt in SeqIO._FormatToWriter.keys() if fmt.startswith("fas")]
+    try:
+        formats = SeqIO._FormatToWriter.keys()
+    except Exception:
+        return ["fasta", "fasta-2line"]
+    return [fmt for fmt in formats if fmt.startswith("fas")]
Biopython SeqIO _FormatToWriter public API alternative
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/REvoDesign/shortcuts/dialog_hooks.py` around lines 23 - 24, The function
get_fasta_writer_choices currently reads the private SeqIO._FormatToWriter
mapping; change it to avoid the private API by trying to access
SeqIO._FormatToWriter inside a try/except and, if that attribute is missing or
not mapping-like, return a defensive hard-coded fallback list of common FASTA
writer formats (e.g. "fasta", "fasta-2line") so upgrades to Biopython won't
break; update the function name reference get_fasta_writer_choices and the SeqIO
access to implement this try/except + fallback logic.
🧹 Nitpick comments (3)
server/pssm_gremlin/pssm_gremlin.py (1)

367-392: Run pre-commit hooks before pushing.

As per coding guidelines, Enable pre-commit hooks with pre-commit install and run pre-commit run --all-files or make black before pushing to ensure formatting and linting.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/pssm_gremlin/pssm_gremlin.py` around lines 367 - 392, Run the
project's pre-commit hooks and format/lint the changed files: run `pre-commit
install` (once) and then `pre-commit run --all-files` or `make black` to apply
formatting and fix linter issues for the modified functions like
_path_is_within, _safe_join, and _normalize_task_id so the commit adheres to the
repository’s style checks before pushing.
src/REvoDesign/shortcuts/dialog_hooks.py (1)

27-33: Run pre-commit hooks before pushing.

As per coding guidelines, Enable pre-commit hooks with pre-commit install and run pre-commit run --all-files or make black before pushing to ensure formatting and linting.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/REvoDesign/shortcuts/dialog_hooks.py` around lines 27 - 33, Pre-commit
hooks were not run before pushing; run and enable them and fix any
formatting/lint failures: run `pre-commit install` in the repo, execute
`pre-commit run --all-files` (or `make black` if preferred), and address any
reported issues in the code paths shown (functions get_designable_chain_ids and
get_selections in dialog_hooks.py) until all hooks pass, then commit the fixes
and push.
tests/server/test_pssm_gremlin.py (1)

1-18: Run focused tests and pre-commit for this update.

As per coding guidelines, Enable pre-commit hooks with pre-commit install and run pre-commit run --all-files or make black before pushing to ensure formatting and linting; and Run make kw-test PYTEST_KW='<keyword>' for fast and specified testing.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/server/test_pssm_gremlin.py` around lines 1 - 18, Run the project's
pre-commit hooks and focused tests before pushing: in the repository run
`pre-commit install` then `pre-commit run --all-files` (or `make black` to fix
formatting), and to run fast/specific tests for this test module
(test_pssm_gremlin) use `make kw-test PYTEST_KW='<keyword>'` replacing <keyword>
with the test marker or name you want to run; fix any lint/format/test failures
reported and re-run the pre-commit/tests until clean.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@server/REvoDesign_PSSM_GREMLIN.sh`:
- Around line 278-287: The script calls readlink -f to canonicalize
pipline_res_dir before ensuring the directory exists, which fails under set -e
for non-existent paths (see variables pipline_res_dir, output_dir, fasta_fp,
instance, fasta_fn); change the logic to determine the desired path string then
mkdir -p that path first and only afterwards canonicalize it (or use
readlink/realpath with the -m/--canonicalize-missing option if available) so the
script creates the directory before attempting to resolve it and avoids early
termination.

---

Duplicate comments:
In `@server/.env.test`:
- Around line 30-34: The header above the runner identity env vars is incorrect;
replace the misleading "redis settings" header with a clear section header for
the runner identity so it matches the variables RUNNER_UID, RUNNER_GID,
RUNNER_USERNAME, and RUNNER_GROUP; update the comment line immediately above
those variables to something like "runner identity" or "runner settings" to
accurately describe the block.

In `@src/REvoDesign/shortcuts/dialog_hooks.py`:
- Around line 23-24: The function get_fasta_writer_choices currently reads the
private SeqIO._FormatToWriter mapping; change it to avoid the private API by
trying to access SeqIO._FormatToWriter inside a try/except and, if that
attribute is missing or not mapping-like, return a defensive hard-coded fallback
list of common FASTA writer formats (e.g. "fasta", "fasta-2line") so upgrades to
Biopython won't break; update the function name reference
get_fasta_writer_choices and the SeqIO access to implement this try/except +
fallback logic.

---

Nitpick comments:
In `@server/pssm_gremlin/pssm_gremlin.py`:
- Around line 367-392: Run the project's pre-commit hooks and format/lint the
changed files: run `pre-commit install` (once) and then `pre-commit run
--all-files` or `make black` to apply formatting and fix linter issues for the
modified functions like _path_is_within, _safe_join, and _normalize_task_id so
the commit adheres to the repository’s style checks before pushing.

In `@src/REvoDesign/shortcuts/dialog_hooks.py`:
- Around line 27-33: Pre-commit hooks were not run before pushing; run and
enable them and fix any formatting/lint failures: run `pre-commit install` in
the repo, execute `pre-commit run --all-files` (or `make black` if preferred),
and address any reported issues in the code paths shown (functions
get_designable_chain_ids and get_selections in dialog_hooks.py) until all hooks
pass, then commit the fixes and push.

In `@tests/server/test_pssm_gremlin.py`:
- Around line 1-18: Run the project's pre-commit hooks and focused tests before
pushing: in the repository run `pre-commit install` then `pre-commit run
--all-files` (or `make black` to fix formatting), and to run fast/specific tests
for this test module (test_pssm_gremlin) use `make kw-test
PYTEST_KW='<keyword>'` replacing <keyword> with the test marker or name you want
to run; fix any lint/format/test failures reported and re-run the
pre-commit/tests until clean.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 29332b2 and 8377377.

📒 Files selected for processing (9)
  • CHANGELOG.md
  • server/.env.test
  • server/REvoDesign_PSSM_GREMLIN.sh
  • server/docker-compose.yml
  • server/pssm_gremlin/pssm_gremlin.py
  • server/pssm_gremlin/templates/pssm_gremlin_dashboard.html
  • src/REvoDesign/shortcuts/dialog_hooks.py
  • tests/server/test_pssm_gremlin.py
  • tools/release_tag.sh
🚧 Files skipped from review as they are similar to previous changes (2)
  • CHANGELOG.md
  • tools/release_tag.sh

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
server/REvoDesign_PSSM_GREMLIN.sh (1)

285-291: ⚠️ Potential issue | 🟠 Major

Avoid readlink -f on paths with non-existent parent directories when set -e is enabled.

With set -e enabled in this script, readlink -f exits with failure if any parent directory doesn't exist. This breaks the -o flag flow when users specify nested output directories that don't yet exist (e.g., -o outputs/exp1/results). The current code calls readlink -f on lines 288 and 290 before mkdir -p on line 293, triggering this failure.

Create the directory first, then resolve its canonical path:

Safer output-dir resolution
if [[ -z "${output_dir:-}" ]]; then
  fasta_fn=$(basename "${fasta_fp}")
  instance=${fasta_fn%.fasta}
-  pipline_res_dir=$(readlink -f "${instance}"_GREMLIN_PSSM_output)
+  pipline_res_dir="${instance}_GREMLIN_PSSM_output"
else
-  pipline_res_dir=$(readlink -f "$output_dir")
+  pipline_res_dir="$output_dir"
fi
+mkdir -p "$pipline_res_dir"
+pipline_res_dir=$(readlink -f "$pipline_res_dir")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/REvoDesign_PSSM_GREMLIN.sh` around lines 285 - 291, The script
currently calls readlink -f on pipline_res_dir (in both branches where
output_dir is empty or set) before ensuring parent directories exist, which
fails under set -e for nested non-existent paths; fix by creating the directory
first (use mkdir -p on the target pipline_res_dir or its parent) and only then
call readlink -f to canonicalize it; update the branches that set
pipline_res_dir (using fasta_fp/fasta_fn/instance when output_dir is empty and
using output_dir when provided) so they mkdir -p the intended directory before
resolving it and ensure pipline_res_dir ends up with the absolute path after
resolution.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@server/.env.example`:
- Around line 24-27: Reorder the environment keys so MAXMEM appears before NPROC
in the .env.example to satisfy dotenv-linter's alphabetical/key-order rule;
update the block containing MAXMEM and NPROC by moving the "MAXMEM=64" line
above "NPROC=4" (keep surrounding comments intact) so the file shows MAXMEM
first and NPROC second.

In `@server/docker-compose.yml`:
- Around line 35-40: The x-docker-socket-access anchor currently grants both the
DOCKER_GID and the root group ("0"), which contradicts the least-privilege
comment; update the x-docker-socket-access anchor (the group_add list referenced
by services) to remove the "0" entry so only "${DOCKER_GID:-998}" is granted,
ensuring Docker socket access uses the non-root group only.

---

Outside diff comments:
In `@server/REvoDesign_PSSM_GREMLIN.sh`:
- Around line 285-291: The script currently calls readlink -f on pipline_res_dir
(in both branches where output_dir is empty or set) before ensuring parent
directories exist, which fails under set -e for nested non-existent paths; fix
by creating the directory first (use mkdir -p on the target pipline_res_dir or
its parent) and only then call readlink -f to canonicalize it; update the
branches that set pipline_res_dir (using fasta_fp/fasta_fn/instance when
output_dir is empty and using output_dir when provided) so they mkdir -p the
intended directory before resolving it and ensure pipline_res_dir ends up with
the absolute path after resolution.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8377377 and bb1e773.

📒 Files selected for processing (9)
  • .gitignore
  • CHANGELOG.md
  • server/.env.example
  • server/.env.test
  • server/README.md
  • server/REvoDesign_PSSM_GREMLIN.sh
  • server/docker-compose.yml
  • server/pssm_gremlin/pssm_gremlin.py
  • tests/server/test_pssm_gremlin.py
💤 Files with no reviewable changes (1)
  • server/.env.test

Comment thread server/.env.example
Comment on lines 24 to +27
## Maximum number to run the server: NPROC * WORKER_CONCURRENCY
NPROC=4
## Maximum memory, at GB
MAXMEM=64

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Reorder MAXMEM ahead of NPROC to satisfy dotenv-linter.
The linter warning will persist until the key order is corrected.

♻️ Suggested reorder
-## Maximum number to run the server: NPROC * WORKER_CONCURRENCY
-NPROC=4
-## Maximum memory, at GB
-MAXMEM=64
+## Maximum memory, at GB
+MAXMEM=64
+## Maximum number to run the server: NPROC * WORKER_CONCURRENCY
+NPROC=4
📝 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
## Maximum number to run the server: NPROC * WORKER_CONCURRENCY
NPROC=4
## Maximum memory, at GB
MAXMEM=64
## Maximum memory, at GB
MAXMEM=64
## Maximum number to run the server: NPROC * WORKER_CONCURRENCY
NPROC=4
🧰 Tools
🪛 dotenv-linter (4.0.0)

[warning] 27-27: [UnorderedKey] The MAXMEM key should go before the NPROC key

(UnorderedKey)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/.env.example` around lines 24 - 27, Reorder the environment keys so
MAXMEM appears before NPROC in the .env.example to satisfy dotenv-linter's
alphabetical/key-order rule; update the block containing MAXMEM and NPROC by
moving the "MAXMEM=64" line above "NPROC=4" (keep surrounding comments intact)
so the file shows MAXMEM first and NPROC second.

Comment thread server/docker-compose.yml
Comment on lines 35 to 40
x-docker-socket-access: &docker-socket-access
# Least privilege by default: only grant Docker socket group access.
# Do not add root group access ("0") here unless you explicitly accept the security tradeoff.
group_add:
- "${DOCKER_GID:-998}"
- "0"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Drop root group (0) from docker socket access by default.
The comment says least-privilege, but root group is still granted. This weakens the intended hardening and expands access to the Docker socket.

🔒 Suggested change
   group_add:
     - "${DOCKER_GID:-998}"
-    - "0"
📝 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
x-docker-socket-access: &docker-socket-access
# Least privilege by default: only grant Docker socket group access.
# Do not add root group access ("0") here unless you explicitly accept the security tradeoff.
group_add:
- "${DOCKER_GID:-998}"
- "0"
x-docker-socket-access: &docker-socket-access
# Least privilege by default: only grant Docker socket group access.
# Do not add root group access ("0") here unless you explicitly accept the security tradeoff.
group_add:
- "${DOCKER_GID:-998}"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/docker-compose.yml` around lines 35 - 40, The x-docker-socket-access
anchor currently grants both the DOCKER_GID and the root group ("0"), which
contradicts the least-privilege comment; update the x-docker-socket-access
anchor (the group_add list referenced by services) to remove the "0" entry so
only "${DOCKER_GID:-998}" is granted, ensuring Docker socket access uses the
non-root group only.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
tests/server/test_pssm_gremlin.py (1)

1-16: ⚠️ Potential issue | 🟡 Minor

Run pre-commit and targeted kw-test for this test update.
Please run pre-commit install and pre-commit run --all-files (or make black), and execute make kw-test PYTEST_KW='pssm_gremlin' (or the most relevant keyword) to validate these changes.
As per coding guidelines, "Enable pre-commit hooks with pre-commit install and run pre-commit run --all-files or make black before pushing to ensure formatting and linting" and "Run make kw-test PYTEST_KW='<keyword>' for fast and specified testing".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/server/test_pssm_gremlin.py` around lines 1 - 16, Run the required
pre-commit formatting and the targeted keyword test for the updated test file
tests/server/test_pssm_gremlin.py: install pre-commit hooks with "pre-commit
install" then run "pre-commit run --all-files" (or run "make black" if you
prefer) to fix lint/format issues, and finally execute the focused test suite
with "make kw-test PYTEST_KW='pssm_gremlin'" to validate the changes to
test_pssm_gremlin.py.
♻️ Duplicate comments (1)
tools/copyright.js (1)

13-15: ⚠️ Potential issue | 🟡 Minor

Surface spawn failures from spawnSync (result.error).
If bash cannot be spawned, result.status is null and the failure is silent; log result.error before exiting.

🛠️ Suggested fix
 if (result.status !== 0) {
+  if (result.error) {
+    console.error("Failed to spawn bash:", result.error.message);
+  }
   process.exit(result.status || 1);
 }
Node.js child_process spawnSync result.error when spawn fails
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tools/copyright.js` around lines 13 - 15, When using spawnSync to run the
bash command in tools/copyright.js, handle the case where the child cannot be
spawned by checking result.error in addition to result.status; if result.error
is present, log or print the error (including context such as the command
string) before calling process.exit with result.status || 1 so the spawn failure
is surfaced; update the block around the spawnSync call (the result variable and
its error handling) to detect result.error and include it in the exit path.
🧹 Nitpick comments (1)
server/pssm_gremlin/templates/create_task.html (1)

616-654: Respect prefers-reduced-motion for the flip animation.

Consider skipping the animation when users request reduced motion.

♿ Suggested guard for reduced motion
   function syncThemeToggle(animate = false) {
     const button = document.getElementById("themeToggle");
     if (!button) return;
     const mode = document.documentElement.dataset.themeMode || getStoredThemeMode();
     const effectiveTheme = document.documentElement.dataset.theme || resolveThemeMode(mode);
     const icon = themeModeIcon[mode] || themeModeIcon.auto;
@@
-    if (animate) {
+    const prefersReducedMotion = window.matchMedia &&
+      window.matchMedia("(prefers-reduced-motion: reduce)").matches;
+    if (animate && !prefersReducedMotion) {
       button.classList.remove("is-animating");
       void button.offsetWidth;
       button.classList.add("is-animating");
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/pssm_gremlin/templates/create_task.html` around lines 616 - 654, The
flip animation in syncThemeToggle currently always runs when animate=true;
update syncThemeToggle (and any callers like applyThemeMode that pass animate)
to respect the user's prefers-reduced-motion setting by checking
window.matchMedia('(prefers-reduced-motion: reduce)').matches and treating that
as animate=false; if reduced motion is requested, skip adding/removing the
"is-animating" class, avoid setting/clearing dataset.animTimer, and do not call
the offsetWidth forced reflow so no animation is triggered. Ensure
applyThemeMode's animate argument is still supported but suppressed when
reduced-motion is true.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tools/copyright.js`:
- Around line 6-11: Run the repository pre-commit hooks and formatters for the
changes to tools/copyright.js: install hooks with `pre-commit install` and then
execute `pre-commit run --all-files` (or run `make black`) so linting/formatting
will be applied to this script (notably the code that defines `command` and uses
`spawnSync`); after fixing any reported issues, re-stage the file and push the
commit.

---

Outside diff comments:
In `@tests/server/test_pssm_gremlin.py`:
- Around line 1-16: Run the required pre-commit formatting and the targeted
keyword test for the updated test file tests/server/test_pssm_gremlin.py:
install pre-commit hooks with "pre-commit install" then run "pre-commit run
--all-files" (or run "make black" if you prefer) to fix lint/format issues, and
finally execute the focused test suite with "make kw-test
PYTEST_KW='pssm_gremlin'" to validate the changes to test_pssm_gremlin.py.

---

Duplicate comments:
In `@tools/copyright.js`:
- Around line 13-15: When using spawnSync to run the bash command in
tools/copyright.js, handle the case where the child cannot be spawned by
checking result.error in addition to result.status; if result.error is present,
log or print the error (including context such as the command string) before
calling process.exit with result.status || 1 so the spawn failure is surfaced;
update the block around the spawnSync call (the result variable and its error
handling) to detect result.error and include it in the exit path.

---

Nitpick comments:
In `@server/pssm_gremlin/templates/create_task.html`:
- Around line 616-654: The flip animation in syncThemeToggle currently always
runs when animate=true; update syncThemeToggle (and any callers like
applyThemeMode that pass animate) to respect the user's prefers-reduced-motion
setting by checking window.matchMedia('(prefers-reduced-motion:
reduce)').matches and treating that as animate=false; if reduced motion is
requested, skip adding/removing the "is-animating" class, avoid setting/clearing
dataset.animTimer, and do not call the offsetWidth forced reflow so no animation
is triggered. Ensure applyThemeMode's animate argument is still supported but
suppressed when reduced-motion is true.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bb1e773 and 62b25e2.

📒 Files selected for processing (5)
  • CHANGELOG.md
  • server/docker/server/Dockerfile
  • server/pssm_gremlin/templates/create_task.html
  • tests/server/test_pssm_gremlin.py
  • tools/copyright.js
🚧 Files skipped from review as they are similar to previous changes (1)
  • server/docker/server/Dockerfile

Comment thread tools/copyright.js
Comment on lines +6 to +11
const { spawnSync } = require("child_process");

const command = [
"find ./src/REvoDesign -type f -name '*.py' -print0",
"xargs -0 cat > ./program.docx",
].join(" | ");

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Run pre-commit hooks before pushing.
Please run pre-commit install and pre-commit run --all-files (or make black) so lint/format checks cover this script.
As per coding guidelines, "Enable pre-commit hooks with pre-commit install and run pre-commit run --all-files or make black before pushing to ensure formatting and linting".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tools/copyright.js` around lines 6 - 11, Run the repository pre-commit hooks
and formatters for the changes to tools/copyright.js: install hooks with
`pre-commit install` and then execute `pre-commit run --all-files` (or run `make
black`) so linting/formatting will be applied to this script (notably the code
that defines `command` and uses `spawnSync`); after fixing any reported issues,
re-stage the file and push the commit.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
src/REvoDesign/shortcuts/dialog_hooks.py (1)

23-24: ⚠️ Potential issue | 🟠 Major

SeqIO._FormatToWriter still relies on a private Biopython API.

This was flagged in a previous review and remains unaddressed.

Biopython 1.81 SeqIO._FormatToWriter private attribute public alternative
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/REvoDesign/shortcuts/dialog_hooks.py` around lines 23 - 24, The code is
reading the private Biopython registry SeqIO._FormatToWriter in
get_fasta_writer_choices; replace this private API usage by returning an
explicit, maintained list of supported FASTA-related writer format strings (e.g.
["fasta","fastq","fastq-sanger", ...]) instead of touching
SeqIO._FormatToWriter, and update get_fasta_writer_choices to return that list
(or to filter a defined FASTA_FORMATS constant) so we no longer depend on the
private SeqIO._FormatToWriter symbol.
🧹 Nitpick comments (4)
src/REvoDesign/shortcuts/dialog_hooks.py (3)

27-43: get_designable_chain_ids and get_all_chain_ids are identical — consolidate.

Both functions have exactly the same body; having two separate names for the same logic is a DRY violation and risks divergence later.

♻️ Proposed refactor
 def get_designable_chain_ids() -> list[str]:
     designable = ConfigBus().get_value("designable_sequences", dict, reject_none=True, cfg="runtime")
     return list(designable.keys())


-def get_all_chain_ids() -> list[str]:
-    designable = ConfigBus().get_value("designable_sequences", dict, reject_none=True, cfg="runtime")
-    return list(designable.keys())
+def get_all_chain_ids() -> list[str]:
+    return get_designable_chain_ids()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/REvoDesign/shortcuts/dialog_hooks.py` around lines 27 - 43, Consolidate
the duplicate logic in get_designable_chain_ids and get_all_chain_ids by
implementing the config lookup in a single place: keep one primary function
(e.g., get_designable_chain_ids) that calls
ConfigBus().get_value("designable_sequences", dict, reject_none=True,
cfg="runtime") and returns list(designable.keys()), and make get_all_chain_ids
simply delegate to that primary function (or vice versa) so there is one source
of truth for fetching chain ids and no duplicated bodies; update any references
if necessary to use the delegating function name.

46-55: get_all_object_names and get_all_objects are identical — consolidate; also add missing return type hints.

get_all_object_names and get_all_objects both return cmd.get_names("objects") with no difference. Additionally, get_all_object_names, get_all_selections, and get_all_objects are the only functions in this file without return type annotations.

♻️ Proposed refactor
-def get_all_object_names():
+def get_all_object_names() -> list[str]:
     return cmd.get_names("objects")


-def get_all_selections():
+def get_all_selections() -> list[str]:
     return cmd.get_names("selections")


-def get_all_objects():
-    return cmd.get_names("objects")
+def get_all_objects() -> list[str]:
+    return get_all_object_names()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/REvoDesign/shortcuts/dialog_hooks.py` around lines 46 - 55, The two
functions get_all_object_names and get_all_objects are duplicates; consolidate
them by keeping a single canonical function name (choose one, e.g.,
get_all_object_names) and remove the other, then update any internal call-sites
to use the retained name (search for get_all_objects). Also add explicit return
type annotations to the three functions in this file—get_all_object_names,
get_all_selections, and the removed/renamed function if you temporarily keep
it—using the appropriate type (e.g., -> list[str] or Sequence[str]) to match
cmd.get_names return; ensure imports/types are adjusted if necessary.

32-33: Prefer unpacking over list concatenation (Ruff RUF005).

♻️ Proposed fix
 def get_selections() -> list[str]:
-    return [""] + list(cmd.get_names("selections"))
+    return ["", *cmd.get_names("selections")]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/REvoDesign/shortcuts/dialog_hooks.py` around lines 32 - 33, The
get_selections function uses list concatenation ([""] +
list(cmd.get_names("selections"))) which Ruff flags; change it to use sequence
unpacking by returning ["", *cmd.get_names("selections")] (or ["",
*list(cmd.get_names("selections"))] if cmd.get_names isn't already iterable) so
the empty string is prepended via unpacking in the get_selections function.
tests/server/test_pssm_gremlin.py (1)

1702-1723: Minor: time.time is not mocked, making the test timeout-sensitive.

The test sets timeout=5.0 but only mocks time.sleep, not time.time. The stub resolves on the 2nd auth attempt (after the readiness probe succeeds), so in practice the real wall-clock should stay well within 5 seconds. However, on an extremely loaded CI runner, this could theoretically flake. If you ever see intermittent failures here, mocking time.time with a controlled clock would fix it.

Otherwise the test logic is correct and effectively validates the retry path.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/server/test_pssm_gremlin.py` around lines 1702 - 1723, The test
test_wait_for_server_ready_retries_transient_401 is mocking time.sleep but not
time.time, making the timeout calculation in _wait_for_server_ready sensitive to
real wall-clock delays; update the test to monkeypatch time.time to a
controlled, incrementing fake clock (used alongside the existing mocked sleep)
so timeout checks in _wait_for_server_ready use the deterministic fake time;
reference the test name, the _StubSession, and _wait_for_server_ready when
making this change so you replace time.time with a predictable stub (e.g., a
closure or generator-backed callable) that advances in step with the mocked
sleep.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@src/REvoDesign/shortcuts/dialog_hooks.py`:
- Around line 23-24: The code is reading the private Biopython registry
SeqIO._FormatToWriter in get_fasta_writer_choices; replace this private API
usage by returning an explicit, maintained list of supported FASTA-related
writer format strings (e.g. ["fasta","fastq","fastq-sanger", ...]) instead of
touching SeqIO._FormatToWriter, and update get_fasta_writer_choices to return
that list (or to filter a defined FASTA_FORMATS constant) so we no longer depend
on the private SeqIO._FormatToWriter symbol.

---

Nitpick comments:
In `@src/REvoDesign/shortcuts/dialog_hooks.py`:
- Around line 27-43: Consolidate the duplicate logic in get_designable_chain_ids
and get_all_chain_ids by implementing the config lookup in a single place: keep
one primary function (e.g., get_designable_chain_ids) that calls
ConfigBus().get_value("designable_sequences", dict, reject_none=True,
cfg="runtime") and returns list(designable.keys()), and make get_all_chain_ids
simply delegate to that primary function (or vice versa) so there is one source
of truth for fetching chain ids and no duplicated bodies; update any references
if necessary to use the delegating function name.
- Around line 46-55: The two functions get_all_object_names and get_all_objects
are duplicates; consolidate them by keeping a single canonical function name
(choose one, e.g., get_all_object_names) and remove the other, then update any
internal call-sites to use the retained name (search for get_all_objects). Also
add explicit return type annotations to the three functions in this
file—get_all_object_names, get_all_selections, and the removed/renamed function
if you temporarily keep it—using the appropriate type (e.g., -> list[str] or
Sequence[str]) to match cmd.get_names return; ensure imports/types are adjusted
if necessary.
- Around line 32-33: The get_selections function uses list concatenation ([""] +
list(cmd.get_names("selections"))) which Ruff flags; change it to use sequence
unpacking by returning ["", *cmd.get_names("selections")] (or ["",
*list(cmd.get_names("selections"))] if cmd.get_names isn't already iterable) so
the empty string is prepended via unpacking in the get_selections function.

In `@tests/server/test_pssm_gremlin.py`:
- Around line 1702-1723: The test
test_wait_for_server_ready_retries_transient_401 is mocking time.sleep but not
time.time, making the timeout calculation in _wait_for_server_ready sensitive to
real wall-clock delays; update the test to monkeypatch time.time to a
controlled, incrementing fake clock (used alongside the existing mocked sleep)
so timeout checks in _wait_for_server_ready use the deterministic fake time;
reference the test name, the _StubSession, and _wait_for_server_ready when
making this change so you replace time.time with a predictable stub (e.g., a
closure or generator-backed callable) that advances in step with the mocked
sleep.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 62b25e2 and 56f88bd.

📒 Files selected for processing (4)
  • CHANGELOG.md
  • server/docker-compose.yml
  • src/REvoDesign/shortcuts/dialog_hooks.py
  • tests/server/test_pssm_gremlin.py
✅ Files skipped from review due to trivial changes (1)
  • CHANGELOG.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • server/docker-compose.yml

_wait_for_server_ready
test_wait_for_server_ready_retries_transient_401
from collections.abc import Iterable
from dataclasses import dataclass
from pathlib import Path
from types import SimpleNamespace

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

types.SimpleNamespace imported but unused [ruff:F401]

[ci skip]
@YaoYinYing
YaoYinYing merged commit 954ebd9 into main Feb 24, 2026
3 of 5 checks passed
@YaoYinYing
YaoYinYing deleted the fix-deepsource-issue-collection-2 branch February 24, 2026 15:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant