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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions .cursor/commands/pr-review.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
---
description: Review a PR by number - checkout, compare against main, run security checks, and return merge guidance
args:
- name: pr
description: PR number (e.g. 42)
required: true
---

Review PR #{{pr}} with this workflow:

## 1. Gather context

Run in parallel:
- `gh pr view {{pr}} --json title,body,headRefName,baseRefName,files,additions,deletions,author`
- Read relevant baseline files in current `main` (affected frontend components, tauri commands, workflows, tests)
- Read translation/i18n and release policy docs if touched (`CONTRIBUTING.md`, `docs/choices.md`, `docs/breadcrumbs.md`)

## 2. Checkout and read complete diff

- `gh pr checkout {{pr}}`
- Read all changed files in the PR, not only highlighted hunks
- Validate tests and workflows impacted by the change

## 3. Review checklist

Check:
- Correctness and regression risk (frontend + tauri + swift bridge where applicable)
- Security and privacy impact (permissions, commands, external calls, secrets)
- i18n consistency (no mixed-language output for selected app language)
- CI compatibility (coverage thresholds, clippy, build)
- Contributor ergonomics (docs/scripts updated when contributor workflow changes)
- Test coverage for changed behavior (new branches and failure paths)

## 4. Classify findings

Group by severity:
- Blocker: security/data loss/broken release behavior
- High: likely user-visible bug or broken contributor flow
- Medium: convention mismatch, missing test, maintainability risk
- Low: minor cleanup/nits

## 5. Present result and action options

Return:
- Findings first, sorted by severity, with file references
- Explicit recommendation:
1. Request changes
2. Comment-only follow-up
3. Safe to merge

If user chooses review posting:
- Use `gh pr review {{pr}} --comment` (or `--request-changes` if needed)
- Keep comment concise and actionable

After review actions:
- `git checkout main`
142 changes: 142 additions & 0 deletions .github/workflows/codex-feedback-loop.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
name: Codex Feedback Loop

on:
pull_request_review:
types: [submitted]
issue_comment:
types: [created]
workflow_dispatch:
inputs:
pr_number:
description: "PR number to process (required for manual runs)"
required: true
type: string
review_id:
description: "Optional review id (limits findings to one review)"
required: false
type: string

permissions:
contents: read
issues: write
pull-requests: write

jobs:
request-codex-fix:
if: >-
github.event_name == 'workflow_dispatch' ||
(github.event_name == 'pull_request_review' &&
github.event.review.user.login == 'chatgpt-codex-connector[bot]' &&
github.event.review.state == 'commented') ||
(github.event_name == 'issue_comment' &&
github.event.issue.pull_request &&
startsWith(github.event.comment.body, '/codex-fix'))
runs-on: ubuntu-latest
steps:
- name: Resolve PR and review context
id: ctx
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
PR="${{ inputs.pr_number }}"
REVIEW_ID="${{ inputs.review_id }}"
elif [ "${{ github.event_name }}" = "pull_request_review" ]; then
PR="${{ github.event.pull_request.number }}"
REVIEW_ID="${{ github.event.review.id }}"
else
PR="${{ github.event.issue.number }}"
REVIEW_ID=""
fi

if [ -z "$PR" ]; then
echo "Missing PR number."
exit 1
fi

STATE=$(gh pr view "$PR" --repo "$GITHUB_REPOSITORY" --json state --jq .state)
echo "pr_number=$PR" >> "$GITHUB_OUTPUT"
echo "review_id=$REVIEW_ID" >> "$GITHUB_OUTPUT"
echo "pr_state=$STATE" >> "$GITHUB_OUTPUT"

- name: Stop if PR is not open
if: steps.ctx.outputs.pr_state != 'OPEN'
run: echo "PR #${{ steps.ctx.outputs.pr_number }} is not open; skipping."

- name: Collect codex findings
if: steps.ctx.outputs.pr_state == 'OPEN'
id: findings
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ steps.ctx.outputs.pr_number }}
REVIEW_ID: ${{ steps.ctx.outputs.review_id }}
run: |
if [ -n "$REVIEW_ID" ]; then
RAW=$(gh api "repos/$GITHUB_REPOSITORY/pulls/$PR_NUMBER/reviews/$REVIEW_ID/comments" --paginate)
FINDINGS=$(echo "$RAW" | jq '[.[] | select(.user.login=="chatgpt-codex-connector[bot]")]')
else
RAW=$(gh api "repos/$GITHUB_REPOSITORY/pulls/$PR_NUMBER/comments" --paginate)
FINDINGS=$(echo "$RAW" | jq '[.[] | select(.user.login=="chatgpt-codex-connector[bot]")]')
fi

COUNT=$(echo "$FINDINGS" | jq 'length')
echo "count=$COUNT" >> "$GITHUB_OUTPUT"

if [ "$COUNT" -eq 0 ]; then
echo "No codex review findings."
exit 0
fi

SUMMARY=$(echo "$FINDINGS" | jq -r '
.[:5]
| map("- `" + (.path // "unknown") + ":" + ((.line // 0) | tostring) + "` " + (.body | gsub("\\n"; " ") | .[0:180]))
| join("\n")
')

{
echo "summary<<EOF"
echo "$SUMMARY"
echo "EOF"
} >> "$GITHUB_OUTPUT"

- name: Prevent duplicate autofix request
if: steps.ctx.outputs.pr_state == 'OPEN' && steps.findings.outputs.count != '0'
id: dedupe
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ steps.ctx.outputs.pr_number }}
REVIEW_ID: ${{ steps.ctx.outputs.review_id }}
run: |
MARKER="<!-- codex-autofix:pr-${PR_NUMBER}:review-${REVIEW_ID:-latest} -->"

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 Deduplicate manual reruns by concrete review id

When /codex-fix (or workflow_dispatch without review_id) runs, REVIEW_ID is empty, so this marker falls back to review-latest; after the first manual autofix comment is posted, every later manual trigger on the same PR is suppressed, even if new Codex findings were added. This breaks the documented manual retry path because dedupe is keyed to a static token instead of the current review context.

Useful? React with 👍 / 👎.

FOUND=$(gh api "repos/$GITHUB_REPOSITORY/issues/$PR_NUMBER/comments" --paginate --jq ".[] | select(.body | contains(\"$MARKER\")) | .id" | head -n 1 || true)
echo "marker=$MARKER" >> "$GITHUB_OUTPUT"
if [ -n "$FOUND" ]; then
echo "already=true" >> "$GITHUB_OUTPUT"
else
echo "already=false" >> "$GITHUB_OUTPUT"
fi

- name: Ask codex to apply feedback
if: steps.ctx.outputs.pr_state == 'OPEN' && steps.findings.outputs.count != '0' && steps.dedupe.outputs.already != 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ steps.ctx.outputs.pr_number }}
MARKER: ${{ steps.dedupe.outputs.marker }}
SUMMARY: ${{ steps.findings.outputs.summary }}
COUNT: ${{ steps.findings.outputs.count }}
run: |
gh pr comment "$PR_NUMBER" --repo "$GITHUB_REPOSITORY" --body "$(cat <<EOF
$MARKER
Codex review findings detected ($COUNT item(s)).

@codex address that feedback

Top findings:
$SUMMARY
EOF
)"

- name: No-op summary
if: steps.ctx.outputs.pr_state == 'OPEN' && (steps.findings.outputs.count == '0' || steps.dedupe.outputs.already == 'true')
run: |
echo "No new codex autofix request posted."
6 changes: 6 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ Thanks for your interest in contributing!
- `npm run i18n:check` should pass for locale changes
- Use [conventional commits](https://www.conventionalcommits.org/) (`feat:`, `fix:`, `docs:`, `chore:`)

## Review Automation

- CI (`.github/workflows/ci.yml`) is the required merge gate for PRs
- Codex review follow-up is automated via `.github/workflows/codex-feedback-loop.yml`
- If you want to trigger codex fixes manually on a PR, comment `/codex-fix` on that PR

## Translations

- Source locale is `src/i18n/locales/en.json`
Expand Down
6 changes: 6 additions & 0 deletions docs/breadcrumbs.md
Original file line number Diff line number Diff line change
Expand Up @@ -373,3 +373,9 @@
- Added select styling in `src/App.css` and adjusted settings tests to combobox interaction in `src/components/SettingsSheet.test.tsx`.
- Updated contributor docs in `CONTRIBUTING.md` to document auto-discovery of new locale JSON files.
- Verification: `npm test` (255/255), `npm run i18n:check`, `npm run build`.
2026-02-16
- Reviewed codex feedback on PR #6 and fixed mixed-language eligibility path: `src/components/SettingsSheet.tsx` now sends resolved `appLanguage` to `get_apple_intelligence_eligibility`; `src-tauri/src/lib.rs` resolves and applies that locale to Apple Intelligence availability checks/reasons.
- Added localized backend eligibility string helpers in `src-tauri/src/i18n.rs` plus unit coverage.
- Added reusable review playbook command `.cursor/commands/pr-review.md` (OpenUsage-style process adapted for StepCast).
- Added codex follow-up automation workflow `.github/workflows/codex-feedback-loop.yml` (auto on codex review, manual `/codex-fix`, workflow_dispatch) with dedupe marker comments.
- Updated `CONTRIBUTING.md` with review automation usage and added spec `docs/specs/2026-02-16-codex-feedback-loop.md`.
5 changes: 5 additions & 0 deletions docs/choices.md
Original file line number Diff line number Diff line change
Expand Up @@ -268,3 +268,8 @@
- Default: language selection UI uses a dropdown (`<select>`) instead of segmented pills; reason: scales for many locales and avoids crowded/overflowing controls as translators add languages.
- Default: language options are sourced from dynamically discovered locale catalogs (`availableLocales`) plus `system`; reason: contributor flow should be "add locale file only" with no UI code edits.
- Default: cross-webview `language-changed` handling validates locale via `isSupportedAppLanguage` instead of hardcoded `en/de`; reason: runtime should automatically accept newly added locale files.

2026-02-16 (codex-review-loop-and-eligibility-locale-fix)
- Default: codex feedback automation is follow-up-by-comment (`@codex address that feedback`) with dedupe markers, not direct auto-commit from workflow; reason: keeps human oversight and avoids unsafe write automation in CI context.
- Default: codex follow-up workflow supports three entry points (codex review submit, `/codex-fix` comment, manual dispatch); reason: reliable trigger paths for both automatic and operator-driven remediation.
- Default: Apple Intelligence eligibility uses selected app language (resolved `en|de`) instead of backend `system_locale`; reason: prevents mixed-language eligibility reasons in localized Settings UI.
19 changes: 19 additions & 0 deletions docs/specs/2026-02-16-codex-feedback-loop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Codex Feedback Loop + Review Playbook

## Scope
- Add a reusable PR review playbook command (`.cursor/commands/pr-review.md`).
- Add automated codex follow-up workflow for review findings.
- Fix mixed-language AI eligibility reasons by honoring selected app language.

## Design
- Workflow `.github/workflows/codex-feedback-loop.yml`:
- Triggers on codex review submission, `/codex-fix` PR comments, and manual dispatch.
- Collects codex inline findings from PR review comments.
- Posts a deduplicated follow-up comment: `@codex address that feedback`.
- Settings frontend passes resolved app language into `get_apple_intelligence_eligibility`.
- Rust backend resolves that language and localizes eligibility reason strings.

## Verification
- `npm test`
- `cd src-tauri && cargo test --quiet`
- Workflow syntax sanity via local YAML inspection
79 changes: 79 additions & 0 deletions src-tauri/src/i18n.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,69 @@ pub fn auth_placeholder_description(locale: Locale) -> &'static str {
}
}

pub fn ai_eligibility_requires_apple_silicon(locale: Locale) -> &'static str {
match locale {
Locale::En => "Requires Apple Silicon (M1+).",
Locale::De => "Erfordert Apple Silicon (M1+).",
}
}

pub fn ai_eligibility_unknown_macos_version(locale: Locale) -> &'static str {
match locale {
Locale::En => "Could not detect macOS version.",
Locale::De => "macOS-Version konnte nicht erkannt werden.",
}
}

pub fn ai_eligibility_requires_macos_26(locale: Locale) -> &'static str {
match locale {
Locale::En => "Requires macOS 26+.",
Locale::De => "Erfordert macOS 26+.",
}
}

pub fn ai_eligibility_check_failed(locale: Locale) -> &'static str {
match locale {
Locale::En => "Could not check Apple Intelligence availability.",
Locale::De => "Apple-Intelligence-Verfugbarkeit konnte nicht gepruft werden.",
}
}

pub fn ai_eligibility_available(locale: Locale) -> &'static str {
match locale {
Locale::En => "Available.",
Locale::De => "Verfugbar.",
}
}

pub fn ai_eligibility_not_enabled(locale: Locale) -> &'static str {
match locale {
Locale::En => "Apple Intelligence is disabled in System Settings.",
Locale::De => "Apple Intelligence ist in den Systemeinstellungen deaktiviert.",
}
}

pub fn ai_eligibility_device_not_eligible(locale: Locale) -> &'static str {
match locale {
Locale::En => "This device is not eligible for Apple Intelligence.",
Locale::De => "Dieses Gerat ist nicht fur Apple Intelligence geeignet.",
}
}

pub fn ai_eligibility_model_not_ready(locale: Locale) -> &'static str {
match locale {
Locale::En => "Apple Intelligence model is not ready yet (downloading/initializing).",
Locale::De => "Apple-Intelligence-Modell ist noch nicht bereit (Download/Initialisierung).",
}
}

pub fn ai_eligibility_unavailable(locale: Locale) -> &'static str {
match locale {
Locale::En => "Apple Intelligence unavailable.",
Locale::De => "Apple Intelligence ist nicht verfugbar.",
}
}

pub fn tray_tooltip(_locale: Locale) -> &'static str {
"StepCast"
}
Expand Down Expand Up @@ -251,4 +314,20 @@ mod tests {
"StepCast - Aufnahme läuft..."
);
}

#[test]
fn ai_eligibility_helpers_render_translated_strings() {
assert_eq!(
ai_eligibility_requires_apple_silicon(Locale::En),
"Requires Apple Silicon (M1+)."
);
assert_eq!(
ai_eligibility_requires_apple_silicon(Locale::De),
"Erfordert Apple Silicon (M1+)."
);
assert_eq!(
ai_eligibility_not_enabled(Locale::De),
"Apple Intelligence ist in den Systemeinstellungen deaktiviert."
);
}
}
Loading