Skip to content

Conversation

@marcusquinn
Copy link
Owner

@marcusquinn marcusquinn commented Feb 8, 2026

Summary

  • Adds .github/workflows/issue-sync.yml with 3 jobs for automated bi-directional sync between TODO.md/PLANS.md and GitHub Issues
  • Enhances compose_issue_body() to render related PRD/task file content as collapsible <details> blocks (instead of bare links)

GHA Workflow Jobs

Job Trigger What it does
sync-on-push Push to main (TODO.md, PLANS.md, todo/tasks/**) Runs closepushenrichpull, commits back
sync-on-issue Issue events (opened, closed, reopened, edited) Runs pull to sync refs to TODO.md
manual-sync workflow_dispatch dropdown Runs selected command (status/pull/push/close/enrich)

Script Changes

  • find_related_files() — follows explicit ref:todo/tasks/ references + greps for task ID mentions
  • extract_file_summary() — extracts first 30 lines from PRD/task files (skips YAML frontmatter)
  • compose_issue_body() — renders file content in collapsible <details> blocks with fallback to links

Testing

  • bash -n syntax check: pass
  • shellcheck -S error: zero violations
  • Smoke test: parse t131 correctly renders PRD content in composed body

Closes subtasks t020.2 (GHA workflow) and t020.3 (PRD content sync).

Summary by CodeRabbit

  • New Features
    • Enhanced GitHub Issues with inline content summaries in Related Files sections, replacing plain file listings with collapsible detail blocks.
    • Introduced automated bi-directional synchronization between TODO.md and GitHub Issues, enabling automatic task and issue management.
    • Added manual sync capability via workflow dispatch for user-triggered synchronization operations.

… (t020.2, t020.3)

- Add .github/workflows/issue-sync.yml with 3 jobs: sync-on-push (auto-sync
  on TODO/PLANS changes), sync-on-issue (pull refs on issue events), and
  manual-sync (workflow_dispatch with command dropdown)
- Update compose_issue_body() to render related PRD/task file content as
  collapsible <details> blocks instead of bare links
- Includes find_related_files() with explicit ref: following and grep fallback
- extract_file_summary() extracts first 30 lines (skips YAML frontmatter)
@gemini-code-assist
Copy link

Warning

You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again!

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 8, 2026

Walkthrough

Enhancements to shell script for improved task file lookup with inline content summaries, plus a new GitHub Actions workflow enabling bi-directional synchronization between TODO.md and GitHub Issues with multiple trigger points and retry mechanisms.

Changes

Cohort / File(s) Summary
Helper Script Enhancement
.agents/scripts/issue-sync-helper.sh
Added find_related_files() to resolve explicit TODO.md references and deduplicate results; introduced extract_file_summary() to generate concise task file summaries with frontmatter handling; enriched compose_issue_body() to render related files with inline content summaries in collapsible HTML detail blocks.
CI/CD Synchronization Workflow
.github/workflows/issue-sync.yml
New workflow file with three jobs: sync-on-push (triggered on main branch changes to TODO.md, PLANS.md, tasks), sync-on-issue (triggered on GitHub issue events with task ID validation), and manual-sync (workflow_dispatch with command execution). Includes GH_TOKEN authentication, Git configuration, GitHub Actions commit filtering, retry logic for push failures, and verbose logging.

Sequence Diagram(s)

sequenceDiagram
    participant GitHub as GitHub
    participant Workflow as GitHub Actions Workflow
    participant Git as Git Repo
    participant Helper as Helper Script
    participant Issues as GitHub Issues

    rect rgba(100, 200, 150, 0.5)
    Note over GitHub,Helper: Push/Issue Event Trigger
    GitHub->>Workflow: trigger (push/issue/dispatch)
    end

    rect rgba(150, 150, 200, 0.5)
    Note over Workflow,Helper: Sync Operation
    Workflow->>Git: checkout & config
    Workflow->>Helper: execute sync command
    Helper->>Git: read TODO.md & tasks
    Helper->>Issues: query/create/update issues
    Helper->>Git: extract file summaries
    end

    rect rgba(200, 150, 100, 0.5)
    Note over Git,GitHub: Persist Changes
    Helper->>Git: update TODO.md with refs
    Workflow->>Git: commit (if modified)
    Git->>Workflow: push (with retry logic)
    Workflow->>GitHub: update remote
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

Poem

📋✨ TODO and Issues dance in sync,

Bi-directional flows with a DevOps wink,

Helper scripts pull summaries tight,

Workflows automate day and night! 🚀

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main changes: adding a GitHub Actions workflow and PRD content rendering for issue-sync, with the task reference (t020) for traceability.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/t020-issue-sync-gha

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.

@github-actions
Copy link

github-actions bot commented Feb 8, 2026

🔍 Code Quality Report

�[0;35m[MONITOR]�[0m Code Review Monitoring Report

�[0;34m[INFO]�[0m Latest Quality Status:
SonarCloud: 0 bugs, 0 vulnerabilities, 33 code smells

�[0;34m[INFO]�[0m Recent monitoring activity:
Sun Feb 8 01:13:00 UTC 2026: Code review monitoring started
Sun Feb 8 01:13:01 UTC 2026: SonarCloud - Bugs: 0, Vulnerabilities: 0, Code Smells: 33

📈 Current Quality Metrics

  • BUGS: 0
  • CODE SMELLS: 33
  • VULNERABILITIES: 0

Generated on: Sun Feb 8 01:13:03 UTC 2026


Generated by AI DevOps Framework Code Review Monitoring

@sonarqubecloud
Copy link

sonarqubecloud bot commented Feb 8, 2026

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In @.agents/scripts/issue-sync-helper.sh:
- Around line 437-450: The parser can silently skip the whole file if a starting
frontmatter delimiter is never closed because in_content remains "true"; update
.agents/scripts/issue-sync-helper.sh to detect this EOF state and recover: after
the while IFS= read -r line loop ends, check if in_content == "true" and if so
log a warning about malformed frontmatter, reset in_content and past_frontmatter
to their default values, and re-run the line-processing logic for the file (or
re-open the file and reprocess) so the file is treated as having no frontmatter
rather than producing an empty summary; reference the in_content and
past_frontmatter variables and the existing read loop in your fix.

In @.github/workflows/issue-sync.yml:
- Around line 41-51: The run block in the "Check commit author" step (id:
check-author) directly interpolates github.event.head_commit.author.name into
the shell, allowing script injection; instead set AUTHOR via the step's env:
(e.g., AUTHOR: ${{ github.event.head_commit.author.name }}) and then reference
the safe $AUTHOR inside the run script, ensuring you keep it quoted when
compared (e.g., if [[ "$AUTHOR" == "GitHub Actions" ]] ...]) so the value is not
re-parsed by the shell.
- Around line 26-28: The current concurrency block uses group: ${{
github.workflow }}-${{ github.ref }} with cancel-in-progress: true which causes
runs from different events (e.g., push and issues) to share the same group and
cancel each other; change the concurrency grouping to include the event name or
job identifier (e.g., use ${{ github.event_name }} or include matrix/job name)
so issue-sync runs are scoped separately from push runs, or alternatively set
cancel-in-progress: false to allow queued runs to complete; update the
concurrency block (the group and cancel-in-progress entries) accordingly to use
${{ github.event_name }} or a per-job token to avoid canceling in-flight issue
syncs.
- Around line 140-153: The step "Check issue title format" is currently
interpolating github.event.issue.title directly into the run: block (variable
TITLE="${{ github.event.issue.title }}"), which allows script injection; fix it
by moving the issue title into a safe environment variable and always expand it
quoted inside the shell: in the check-issue step set env: ISSUE_TITLE: ${{
github.event.issue.title }} and in the run block use TITLE="$ISSUE_TITLE"
(always quote expansions like "$TITLE" and "$ISSUE_TITLE"), then perform the
pattern checks and TASK_ID extraction from the quoted variable (e.g., echo
"$TITLE" | grep -qE '^t[0-9]+' and TASK_ID=$(echo "$TITLE" | grep -oE
'^t[0-9]+(\.[0-9]+)*')), and use printf '%s' "$TITLE" when printing to avoid
executing content.
🧹 Nitpick comments (3)
.github/workflows/issue-sync.yml (3)

100-121: Retry logic is solid; consider logging rebase failures.

The 3-attempt retry with increasing backoff is a good pattern for handling concurrent pushes. The [skip ci] commit message is a nice secondary loop-prevention measure alongside the author check.

One note: Line 111 silences rebase failures with || true. If a conflict occurs, the subsequent push will fail with a confusing error. Consider logging the rebase outcome:

-              git pull --rebase origin main || true
+              if ! git pull --rebase origin main; then
+                echo "Warning: rebase failed, aborting rebase"
+                git rebase --abort 2>/dev/null || true
+              fi

242-250: Pass inputs.command through an env variable for defense-in-depth.

While the choice type constrains the value server-side, the same env-variable pattern used elsewhere is a good habit — and keeps the security posture consistent across all jobs in this workflow.

Proposed fix
      - name: Run sync command
        run: |
          chmod +x .agents/scripts/issue-sync-helper.sh
          chmod +x .agents/scripts/shared-constants.sh
-          COMMAND="${{ github.event.inputs.command }}"
+          COMMAND="$SYNC_COMMAND"
           echo "=== Running: issue-sync-helper.sh $COMMAND ==="
           bash .agents/scripts/issue-sync-helper.sh "$COMMAND" --verbose
         env:
           GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+          SYNC_COMMAND: ${{ github.event.inputs.command }}

180-196: closed event doesn't auto-mark TODO.md task as done — verify this is intentional.

When an issue is closed on GitHub (e.g., manually by a developer), the workflow only logs a message and doesn't update TODO.md. This means closing an issue on GitHub won't mark the corresponding task as [x] in TODO.md, creating silent drift. If this is the intended "GitHub → TODO.md is manual" design, consider documenting it in the workflow comments or the help text.

Comment on lines +437 to +450
while IFS= read -r line; do
# Skip YAML frontmatter
if [[ "$line" == "---" ]] && [[ "$past_frontmatter" == "false" ]]; then
if [[ "$in_content" == "true" ]]; then
past_frontmatter=true
in_content=false
continue
fi
in_content=true
continue
fi
if [[ "$in_content" == "true" ]]; then
continue
fi
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Malformed frontmatter (single ---) silently produces empty summary.

If a file contains an opening --- but no closing ---, in_content remains true for the entire file and every line is skipped, yielding an empty summary with no indication of why. This is a silent failure mode that could be confusing during debugging.

Consider adding a safeguard — e.g., if EOF is reached while in_content is still true, assume no frontmatter was present and re-read (or reset and reprocess).

As per coding guidelines, .agents/scripts/*.sh: "Reliability and robustness" and "Clear logging and feedback".

🤖 Prompt for AI Agents
In @.agents/scripts/issue-sync-helper.sh around lines 437 - 450, The parser can
silently skip the whole file if a starting frontmatter delimiter is never closed
because in_content remains "true"; update .agents/scripts/issue-sync-helper.sh
to detect this EOF state and recover: after the while IFS= read -r line loop
ends, check if in_content == "true" and if so log a warning about malformed
frontmatter, reset in_content and past_frontmatter to their default values, and
re-run the line-processing logic for the file (or re-open the file and
reprocess) so the file is treated as having no frontmatter rather than producing
an empty summary; reference the in_content and past_frontmatter variables and
the existing read loop in your fix.

Comment on lines +26 to +28
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Concurrency group may cause missed syncs for issue events.

Both push and issues events on main resolve to the same github.ref (refs/heads/main), so they share a single concurrency group. With cancel-in-progress: true, rapid-fire issue events (or a push arriving during an issue sync) will cancel in-flight runs, potentially losing ref:GH# updates.

Consider scoping the concurrency group per job or per event type, e.g.:

Proposed fix
 concurrency:
-  group: ${{ github.workflow }}-${{ github.ref }}
+  group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }}
   cancel-in-progress: true

Alternatively, set cancel-in-progress: false if you'd prefer queued runs to complete rather than be cancelled.

📝 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
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
concurrency:
group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }}
cancel-in-progress: true
🤖 Prompt for AI Agents
In @.github/workflows/issue-sync.yml around lines 26 - 28, The current
concurrency block uses group: ${{ github.workflow }}-${{ github.ref }} with
cancel-in-progress: true which causes runs from different events (e.g., push and
issues) to share the same group and cancel each other; change the concurrency
grouping to include the event name or job identifier (e.g., use ${{
github.event_name }} or include matrix/job name) so issue-sync runs are scoped
separately from push runs, or alternatively set cancel-in-progress: false to
allow queued runs to complete; update the concurrency block (the group and
cancel-in-progress entries) accordingly to use ${{ github.event_name }} or a
per-job token to avoid canceling in-flight issue syncs.

Comment on lines +41 to +51
- name: Check commit author
id: check-author
run: |
# Prevent infinite loops: skip if this commit was made by GitHub Actions
AUTHOR="${{ github.event.head_commit.author.name }}"
if [[ "$AUTHOR" == "GitHub Actions" ]] || [[ "$AUTHOR" == "github-actions[bot]" ]]; then
echo "skip=true" >> "$GITHUB_OUTPUT"
echo "Skipping: commit was made by GitHub Actions (loop prevention)"
else
echo "skip=false" >> "$GITHUB_OUTPUT"
fi
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Script injection vulnerability: github.event.head_commit.author.name interpolated directly in run: block.

Confirmed by actionlint and Codacy. A crafted commit author name (e.g., containing "; malicious_command; echo ") would execute arbitrary code in the runner. Pass it through an environment variable instead.

Proposed fix
      - name: Check commit author
        id: check-author
        run: |
          # Prevent infinite loops: skip if this commit was made by GitHub Actions
-          AUTHOR="${{ github.event.head_commit.author.name }}"
+          AUTHOR="$COMMIT_AUTHOR"
           if [[ "$AUTHOR" == "GitHub Actions" ]] || [[ "$AUTHOR" == "github-actions[bot]" ]]; then
             echo "skip=true" >> "$GITHUB_OUTPUT"
             echo "Skipping: commit was made by GitHub Actions (loop prevention)"
           else
             echo "skip=false" >> "$GITHUB_OUTPUT"
           fi
+        env:
+          COMMIT_AUTHOR: ${{ github.event.head_commit.author.name }}
📝 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
- name: Check commit author
id: check-author
run: |
# Prevent infinite loops: skip if this commit was made by GitHub Actions
AUTHOR="${{ github.event.head_commit.author.name }}"
if [[ "$AUTHOR" == "GitHub Actions" ]] || [[ "$AUTHOR" == "github-actions[bot]" ]]; then
echo "skip=true" >> "$GITHUB_OUTPUT"
echo "Skipping: commit was made by GitHub Actions (loop prevention)"
else
echo "skip=false" >> "$GITHUB_OUTPUT"
fi
- name: Check commit author
id: check-author
run: |
# Prevent infinite loops: skip if this commit was made by GitHub Actions
AUTHOR="$COMMIT_AUTHOR"
if [[ "$AUTHOR" == "GitHub Actions" ]] || [[ "$AUTHOR" == "github-actions[bot]" ]]; then
echo "skip=true" >> "$GITHUB_OUTPUT"
echo "Skipping: commit was made by GitHub Actions (loop prevention)"
else
echo "skip=false" >> "$GITHUB_OUTPUT"
fi
env:
COMMIT_AUTHOR: ${{ github.event.head_commit.author.name }}
🧰 Tools
🪛 actionlint (1.7.10)

[error] 43-43: "github.event.head_commit.author.name" is potentially untrusted. avoid using it directly in inline scripts. instead, pass it through an environment variable. see https://docs.github.com/en/actions/reference/security/secure-use#good-practices-for-mitigating-script-injection-attacks for more details

(expression)

🪛 GitHub Check: Codacy Static Code Analysis

[failure] 43-43: .github/workflows/issue-sync.yml#L43
Using variable interpolation ${{...}} with github context data in a run: step could allow an attacker to inject their own code into the runner.

🤖 Prompt for AI Agents
In @.github/workflows/issue-sync.yml around lines 41 - 51, The run block in the
"Check commit author" step (id: check-author) directly interpolates
github.event.head_commit.author.name into the shell, allowing script injection;
instead set AUTHOR via the step's env: (e.g., AUTHOR: ${{
github.event.head_commit.author.name }}) and then reference the safe $AUTHOR
inside the run script, ensuring you keep it quoted when compared (e.g., if [[
"$AUTHOR" == "GitHub Actions" ]] ...]) so the value is not re-parsed by the
shell.

Comment on lines +140 to +153
- name: Check issue title format
id: check-issue
run: |
TITLE="${{ github.event.issue.title }}"
# Only sync issues with t-number prefix (our convention)
if echo "$TITLE" | grep -qE '^t[0-9]+'; then
echo "sync=true" >> "$GITHUB_OUTPUT"
TASK_ID=$(echo "$TITLE" | grep -oE '^t[0-9]+(\.[0-9]+)*')
echo "task_id=$TASK_ID" >> "$GITHUB_OUTPUT"
echo "Issue #${{ github.event.issue.number }} matches task $TASK_ID"
else
echo "sync=false" >> "$GITHUB_OUTPUT"
echo "Issue #${{ github.event.issue.number }} does not have t-number prefix, skipping"
fi
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Script injection vulnerability: github.event.issue.title interpolated directly in run: block.

Same class of vulnerability as the commit author injection. Any GitHub user who can create an issue can inject arbitrary shell commands via a crafted issue title.

Proposed fix
      - name: Check issue title format
        id: check-issue
        run: |
-          TITLE="${{ github.event.issue.title }}"
+          TITLE="$ISSUE_TITLE"
           # Only sync issues with t-number prefix (our convention)
           if echo "$TITLE" | grep -qE '^t[0-9]+'; then
             echo "sync=true" >> "$GITHUB_OUTPUT"
             TASK_ID=$(echo "$TITLE" | grep -oE '^t[0-9]+(\.[0-9]+)*')
             echo "task_id=$TASK_ID" >> "$GITHUB_OUTPUT"
-            echo "Issue #${{ github.event.issue.number }} matches task $TASK_ID"
+            echo "Issue #${ISSUE_NUMBER} matches task $TASK_ID"
           else
             echo "sync=false" >> "$GITHUB_OUTPUT"
-            echo "Issue #${{ github.event.issue.number }} does not have t-number prefix, skipping"
+            echo "Issue #${ISSUE_NUMBER} does not have t-number prefix, skipping"
           fi
+        env:
+          ISSUE_TITLE: ${{ github.event.issue.title }}
+          ISSUE_NUMBER: ${{ github.event.issue.number }}
📝 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
- name: Check issue title format
id: check-issue
run: |
TITLE="${{ github.event.issue.title }}"
# Only sync issues with t-number prefix (our convention)
if echo "$TITLE" | grep -qE '^t[0-9]+'; then
echo "sync=true" >> "$GITHUB_OUTPUT"
TASK_ID=$(echo "$TITLE" | grep -oE '^t[0-9]+(\.[0-9]+)*')
echo "task_id=$TASK_ID" >> "$GITHUB_OUTPUT"
echo "Issue #${{ github.event.issue.number }} matches task $TASK_ID"
else
echo "sync=false" >> "$GITHUB_OUTPUT"
echo "Issue #${{ github.event.issue.number }} does not have t-number prefix, skipping"
fi
- name: Check issue title format
id: check-issue
run: |
TITLE="$ISSUE_TITLE"
# Only sync issues with t-number prefix (our convention)
if echo "$TITLE" | grep -qE '^t[0-9]+'; then
echo "sync=true" >> "$GITHUB_OUTPUT"
TASK_ID=$(echo "$TITLE" | grep -oE '^t[0-9]+(\.[0-9]+)*')
echo "task_id=$TASK_ID" >> "$GITHUB_OUTPUT"
echo "Issue #${ISSUE_NUMBER} matches task $TASK_ID"
else
echo "sync=false" >> "$GITHUB_OUTPUT"
echo "Issue #${ISSUE_NUMBER} does not have t-number prefix, skipping"
fi
env:
ISSUE_TITLE: ${{ github.event.issue.title }}
ISSUE_NUMBER: ${{ github.event.issue.number }}
🧰 Tools
🪛 actionlint (1.7.10)

[error] 142-142: "github.event.issue.title" is potentially untrusted. avoid using it directly in inline scripts. instead, pass it through an environment variable. see https://docs.github.com/en/actions/reference/security/secure-use#good-practices-for-mitigating-script-injection-attacks for more details

(expression)

🤖 Prompt for AI Agents
In @.github/workflows/issue-sync.yml around lines 140 - 153, The step "Check
issue title format" is currently interpolating github.event.issue.title directly
into the run: block (variable TITLE="${{ github.event.issue.title }}"), which
allows script injection; fix it by moving the issue title into a safe
environment variable and always expand it quoted inside the shell: in the
check-issue step set env: ISSUE_TITLE: ${{ github.event.issue.title }} and in
the run block use TITLE="$ISSUE_TITLE" (always quote expansions like "$TITLE"
and "$ISSUE_TITLE"), then perform the pattern checks and TASK_ID extraction from
the quoted variable (e.g., echo "$TITLE" | grep -qE '^t[0-9]+' and
TASK_ID=$(echo "$TITLE" | grep -oE '^t[0-9]+(\.[0-9]+)*')), and use printf '%s'
"$TITLE" when printing to avoid executing content.

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