Skip to content

feat: add phase 4 GitHub Projects provider#2558

Closed
MohAnghabo wants to merge 10 commits into
pingdotgg:mainfrom
MohAnghabo:feature/t3-kanban-phase-4-github-projects
Closed

feat: add phase 4 GitHub Projects provider#2558
MohAnghabo wants to merge 10 commits into
pingdotgg:mainfrom
MohAnghabo:feature/t3-kanban-phase-4-github-projects

Conversation

@MohAnghabo
Copy link
Copy Markdown

@MohAnghabo MohAnghabo commented May 6, 2026

Summary

  • Add a server-side GitHub Projects provider for Phase 4 live Kanban integration.
  • Support gh auth readiness, Project/field/item reads, GitHub item to Kanban task mapping, confirmation-gated status updates, and confirmation-gated issue comments.
  • Tighten the Kanban task updated contract to ISO datetime and align mock fixtures with the shared schema.

Linked Work

  • Refs MohAnghabo/ai-starter-pro#43
  • Spec: docs/tasks/t3-kanban-project-console.md
  • Completed: Phase 4 GitHub Projects read/write provider slice.
  • Out of scope: end-to-end web/RPC wiring, manual Project write smoke, rate-limit/backoff hardening, duplicate comment suppression, and persistent audit records.

Testing Guide

  1. bun check
  2. bun run --cwd apps/server test -- GitHubProjectsProvider
  3. bun run --cwd apps/server typecheck
  4. bun run --cwd packages/contracts test -- kanbanConsole
  5. bun run --cwd apps/web test -- kanbanConsoleMock
  6. Read-only smoke: gh auth status and gh project list --owner MohAnghabo --limit 20 --format json

Risks and Rollback

  • Risk: GitHub CLI JSON shapes may vary across Project item content types; synthetic fixtures cover the expected issue-linked shape, but additional live shapes may need follow-up mapping.
  • Risk: This PR is above the 400 LOC target because Phase 4 provider behavior and its synthetic tests are kept together for reviewability.
  • Safety: GitHub Project status updates and issue comments require explicit confirmation before writing.
  • Rollback: Revert this PR to remove the provider and restore the prior mock-only Phase 3 contract timestamp behavior.

Readiness Checklist

  • Relevant Markdown docs updated where needed
  • No documentation impact
  • Tests added or updated for this change
  • No test impact
  • All required CI checks are green
  • GitHub issue linked
  • Durable spec path linked when applicable
  • Task execution log updated when applicable

Note

Medium Risk
Introduces a new GitHub Projects read/write surface (via gh) plus new GitHub Actions workflows; incorrect CLI JSON assumptions or workflow misconfiguration could cause flaky behavior or unintended automation, though writes are confirmation-gated and the AI loop is disabled by default.

Overview
Adds a server-side GitHubProjectsProvider that uses the GitHub CLI to check auth readiness, list Projects/fields/items, map Project items into KanbanConsoleTasks, and (when explicitly confirmed) update Project status and post issue comments.

Extends governance/agent infrastructure: introduces .ai/rules/* guidance (PDPL/IFRS/security/environments/orchestration/PR-readiness), new Claude/Codex slash-command runbooks and wrappers, a PR readiness workflow, and optional AI review/auto-fix workflows controlled by .github/ai-loop.yml.

Tightens/aligns supporting tooling: updates AGENTS.md and PR template for readiness gates, adds .local/ to .gitignore, adjusts formatter ignore list, switches CI runners to ubuntu-24.04, and adds a browser test for Arabic/RTL toggling in KanbanConsoleMock.

Reviewed by Cursor Bugbot for commit 42069b9. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Add GitHub Projects provider and AI loop infrastructure for the Kanban Console

  • Adds GitHubProjectsProvider, an Effect-based service that wraps the GitHub CLI to list projects/fields/items, update item status, and post comments.
  • Adds a KanbanConsoleMock React component with drag-and-drop task movement, locale-aware RTL support, and a new /kanban route.
  • Introduces an AI loop system (ai-fix-router.yml, ai-fix-executor-claude.yml, ai-review.yml) that routes CI/review events to a Claude-based autofix executor; currently disabled via .github/ai-loop.yml.
  • Adds a preflight runner (scripts/preflight/) with checks for Doppler, stack selection, environment tiers, and CLI versions, plus auto-fix helpers that can write secrets to Doppler.
  • Adds governance docs (AI rules, PDPL/IFRS/security policies) and Claude/Codex command runbooks synced via sync-codex-commands.ts.
  • Risk: the AI autofix executor workflow can push commits to PR branches when enabled; the ai-loop.yml gate must remain enabled: false until intentionally activated.
📊 Macroscope summarized 42069b9. 37 files reviewed, 11 issues evaluated, 4 issues filtered, 2 comments posted

🗂️ Filtered Issues

apps/server/src/kanban/GitHubProjectsProvider.ts — 0 comments posted, 1 evaluated, 1 filtered
  • line 191: In fieldValue, the priority of the nullish coalescing on line 191 is reversed. When the GitHub CLI returns field values with structure { "field": { "name": "Status" }, "name": "In Progress" }, the expression trim(field.name) ?? trim(objectValue(field.field)?.name) evaluates field.name first, which yields the value ("In Progress") rather than the field name ("Status"). Since "in progress" is not in the wanted set for field names like "Status", the field is skipped and the lookup falls through to the fallback. The expression should be trim(objectValue(field.field)?.name) ?? trim(field.name) to prioritize the nested field.field.name (the actual field name) over field.name (which may hold the value). [ Failed validation ]
scripts/ai-loop/config.ts — 0 comments posted, 1 evaluated, 1 filtered
  • line 53: The config file path defaults to .github/ai-loop.yml (a YAML file), but the code uses JSON.parse() to parse it. YAML is a superset of JSON, meaning valid JSON is valid YAML, but not vice versa. If the config file uses any YAML-specific syntax (like unquoted strings, multi-line strings with |, anchors, etc.), JSON.parse() will throw a SyntaxError. Either the file extension should be .json, or a YAML parser library should be used. [ Failed validation ]
scripts/ai-loop/router-logic.ts — 0 comments posted, 1 evaluated, 1 filtered
  • line 56: The function checks state.last_result_fingerprint but the calling code stores the fingerprint in state.last_signal_fingerprint. In router.ts, after computing findingSetFingerprint, it is assigned to last_signal_fingerprint (e.g., last_signal_fingerprint: findingSetFingerprint). However, shouldBlockRepeatedFindingSet compares against last_result_fingerprint, which is a different field that is never populated with the finding fingerprint. This means the repeated-finding-set detection will never trigger, defeating the intended blocking behavior. [ Failed validation ]
scripts/preflight/checks/integrations.ts — 0 comments posted, 1 evaluated, 1 filtered
  • line 181: The doppler/yaml check provides a misleading hint even when the check passes. When files.length > 0 && !hasPlaceholder evaluates to true (status = "pass"), the hint is still set to "Replace placeholder Doppler project names." because the ternary at lines 182-185 only distinguishes between files.length === 0 and files.length > 0, not whether the check passed. Other checks in this file correctly use undefined for the hint when passing (e.g., line 221, line 250), but this one always provides a string hint. [ Failed validation ]

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 6, 2026

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 3314dbf8-62e2-4e2e-885f-3d187fd3ff24

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@MohAnghabo
Copy link
Copy Markdown
Author

Closed: this PR was opened against the upstream repository by mistake. The intended target is MohAnghabo/kanban-console.

@MohAnghabo MohAnghabo closed this May 6, 2026
@github-actions github-actions Bot added size:XXL 1,000+ changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list. labels May 6, 2026
Copy link
Copy Markdown
Contributor

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 42069b9. Configure here.

if (!field) continue;
const name = trim(field.name) ?? trim(objectValue(field.field)?.name);
if (name && wanted.has(name.toLowerCase())) {
return field.value ?? field.name ?? field.text ?? field.title;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

fieldValue fallback returns field name instead of value

Medium Severity

The fallback chain field.value ?? field.name ?? field.text ?? field.title uses field.name as a fallback, but field.name is the field's label (e.g. "Status", "Priority", "Agent") that was just matched on line 192. When field.value is undefined or null (e.g. a GitHub Project item with no status set), this returns the field label as the value. This causes incorrect mapping: toColumn("Status") yields "backlog", toAgent("Agent") yields "Human", and assigneeName returns "Assignee" instead of "Unassigned".

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 42069b9. Configure here.

shell: bash
run: |
findings_file="$RUNNER_TEMP/ai-loop-findings.json"
printf '%s' '${{ inputs.findings_b64 }}' | base64 --decode > "$findings_file"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Shell injection via direct input interpolation in workflow

Medium Severity

The expression '${{ inputs.findings_b64 }}' is directly interpolated into a shell run block. If the workflow_dispatch input contains a single quote, it breaks out of the quoted context and enables arbitrary command execution. The input should be passed via an environment variable instead of inline interpolation.

Fix in Cursor Fix in Web

Triggered by project rule: Bugbot Project Brief

Reviewed by Cursor Bugbot for commit 42069b9. Configure here.

};
}

if (request.toColumn === "blocked") {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟠 High src/kanbanConsoleMock.ts:586

When moving a task to "blocked" with confirmed: true, previewTaskTransition still returns requiresConfirmation: true and action: "open-action-sheet". This creates an infinite confirmation loop where users can never complete a blocked transition even after confirming. The condition at line 586 should check && !request.confirmed like the "done" case at line 574.

-  if (request.toColumn === "blocked") {
+  if (request.toColumn === "blocked" && !request.confirmed) {
🤖 Copy this AI Prompt to have your agent fix this:
In file apps/web/src/kanbanConsoleMock.ts around line 586:

When moving a task to `"blocked"` with `confirmed: true`, `previewTaskTransition` still returns `requiresConfirmation: true` and `action: "open-action-sheet"`. This creates an infinite confirmation loop where users can never complete a blocked transition even after confirming. The condition at line 586 should check `&& !request.confirmed` like the `"done"` case at line 574.

Evidence trail:
apps/web/src/kanbanConsoleMock.ts lines 574 (done case checks `!request.confirmed`), 586-595 (blocked case does NOT check `request.confirmed`), 603 (default case uses `!request.confirmed`). All at REVIEWED_COMMIT.

Comment on lines +34 to +46
if (
checks.some((check) => check.id === "stack-b/convex-deployment" && check.status === "error")
) {
fixes.push(await runProviderCli(context, deps, "convex-dev"));
}

if (hasFixableIssue(checks, "stack-b/vercel-link")) {
fixes.push(await runProviderCli(context, deps, "vercel-link"));
}

if (checks.some((check) => check.id === "stack-a/neon-url" && check.status === "error")) {
fixes.push(await runProviderCli(context, deps, "neon-project-create"));
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟢 Low fix/apply.ts:34

The conditions for stack-b/convex-deployment (line 35) and stack-a/neon-url (line 44) check only status === "error" without verifying fixable, unlike other fixes that use hasFixableIssue. This causes runProviderCli to execute even when a check explicitly reports fixable: false, running potentially expensive or destructive CLI commands for issues marked as non-auto-fixable.

-  if (
-    checks.some((check) => check.id === "stack-b/convex-deployment" && check.status === "error")
-  ) {
+  if (hasFixableIssue(checks, "stack-b/convex-deployment")) {
     fixes.push(await runProviderCli(context, deps, "convex-dev"));
   }
 
   if (hasFixableIssue(checks, "stack-b/vercel-link")) {
     fixes.push(await runProviderCli(context, deps, "vercel-link"));
   }
 
-  if (checks.some((check) => check.id === "stack-a/neon-url" && check.status === "error")) {
+  if (hasFixableIssue(checks, "stack-a/neon-url")) {
     fixes.push(await runProviderCli(context, deps, "neon-project-create"));
   }
🤖 Copy this AI Prompt to have your agent fix this:
In file scripts/preflight/fix/apply.ts around lines 34-46:

The conditions for `stack-b/convex-deployment` (line 35) and `stack-a/neon-url` (line 44) check only `status === "error"` without verifying `fixable`, unlike other fixes that use `hasFixableIssue`. This causes `runProviderCli` to execute even when a check explicitly reports `fixable: false`, running potentially expensive or destructive CLI commands for issues marked as non-auto-fixable.

Evidence trail:
scripts/preflight/fix/apply.ts lines 10-14 (hasFixableIssue checks fixable), lines 34-38 (convex-deployment bypasses fixable), lines 44-45 (neon-url bypasses fixable), lines 26,30,40 (other fixes use hasFixableIssue). scripts/preflight/checks/support.ts line 37 (fixable defaults to false). scripts/preflight/checks/stack.ts lines 99-107 (convex-deployment check never sets fixable), lines 21-23 (neon-url error check never sets fixable), line 140 (vercel-link explicitly sets fixable: !linked).

@macroscopeapp
Copy link
Copy Markdown
Contributor

macroscopeapp Bot commented May 6, 2026

Approvability

Verdict: Needs human review

1 blocking correctness issue found. Diff is too large for automated approval analysis. A human reviewer should evaluate this PR.

You can customize Macroscope's approvability policy. Learn more.

@MohAnghabo MohAnghabo deleted the feature/t3-kanban-phase-4-github-projects branch May 6, 2026 14:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL 1,000+ changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant