Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
b3f8973
ChatGPT_Document critical evaluation of termux-monorepo
timerloggedout-spec Aug 2, 2026
6cd6642
Document repository audit and recommendations
timerloggedout-spec Aug 2, 2026
23d63a8
Create initial proposal for ChatGPT integration
timerloggedout-spec Aug 2, 2026
e0e4e42
docs(ops): add Lean Termux Monorepo operating rules
timerloggedout-spec Aug 3, 2026
ccc5e0b
perf: SQLite batching + shared connections for multi-agent indexing (…
timerloggedout-spec Aug 4, 2026
552c826
ux: flicker-free rich Live telemetry dashboard for termux-multi-agent…
timerloggedout-spec Aug 4, 2026
fb248b2
feat(agents): enable auto-Jules + Linear feedback sync on default branch
timerloggedout-spec Aug 4, 2026
fb33a70
feat(wiki): DeepWiki → GitHub Wiki mirror via Actions
timerloggedout-spec Aug 4, 2026
b9ed731
Expand README with Termux setup instructions
timerloggedout-spec Aug 4, 2026
51d6b6a
Update README with workflow development details
timerloggedout-spec Aug 4, 2026
61683a0
Add high priority note for Render initialization
timerloggedout-spec Aug 4, 2026
a65bb22
docs: promote ArchW1z process + CONSENSUS to master
timerloggedout-spec Aug 5, 2026
4b15d4f
feat(agents): activate free-tier Jules + Gemini issue workflows on de…
timerloggedout-spec Aug 5, 2026
977dffa
fix: implement robust curl_cffi import fallback for Termux compatibility
google-labs-jules[bot] Aug 6, 2026
ca9f5fa
fix: implement robust curl_cffi fallback and resolve Termux dlopen sy…
google-labs-jules[bot] Aug 6, 2026
61ee27a
fix: implement requests fallback for curl_cffi and resolve Linear API…
google-labs-jules[bot] Aug 6, 2026
25f0f30
fix: secure local permissions and resolve Linear sync issue
google-labs-jules[bot] Aug 6, 2026
a154337
fix: implement requests fallback, resolve Linear sync, secure local p…
google-labs-jules[bot] Aug 6, 2026
58cc35a
fix: implement requests fallback, secure permissions, and splice exch…
google-labs-jules[bot] Aug 6, 2026
ec7718b
fix: implement requests fallback, secure permissions, and splice exch…
google-labs-jules[bot] Aug 6, 2026
0802ee0
Update nexuscli/cli/main.py
timerloggedout-spec Aug 6, 2026
08959e4
Update nexuscli/core/api.py
timerloggedout-spec Aug 6, 2026
d4a8f20
fix: implement requests fallback, secure permissions, and splice exch…
google-labs-jules[bot] Aug 6, 2026
ea4aa2d
fix: requests fallback for curl_cffi on Termux, Linear GHA sync, Aiki…
google-labs-jules[bot] Aug 6, 2026
4a0dee7
fix: requests fallback for curl_cffi on Termux, Linear GHA sync, Aiki…
google-labs-jules[bot] Aug 6, 2026
da9d0b7
Update nexuscli/cli/main.py
timerloggedout-spec Aug 7, 2026
36c365f
Update nexuscli/core/api.py
timerloggedout-spec Aug 7, 2026
37c478d
feat: agent quota throttling, session continuation, and load balancing
tembo[bot] Aug 7, 2026
9d98336
fix(quota): correct Gemini free-tier limits (RPM + RPD, not 20/day)
timerloggedout-spec Aug 7, 2026
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
3 changes: 3 additions & 0 deletions .Jules/palette.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
## 2026-08-01 - Flicker-Free Real-Time CLI Dashboards with Rich Live
**Learning:** Terminal dashboards that clear the screen using raw ANSI escape codes (`\033[H\033[J`) or `clear` commands create severe flicker and redraw lag. This harms cognitive accessibility and visual appeal. Using `rich.live.Live` with high-level structural layout (`Table`, `Panel`, `Text`) ensures updates are drawn to the screen differential/flicker-free, and handles terminal exits cleanly.
**Action:** Always prefer `rich.live.Live` (or similar differential-updating curses-like tools) for terminal UI dashboards that require frequent, real-time telemetry updates.
21 changes: 21 additions & 0 deletions .coderabbit.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json
# Agent: Grok — autofix enabled so CodeRabbit applies without manual loops.
reviews:
profile: assertive
high_level_summary: true
request_changes_workflow: false
auto_review:
enabled: true
drafts: true
auto_incremental_review: true
base_branches:
- master
- master-staging
- feature/skyhook
finishing_touches:
autofix:
enabled: true
auto_apply_labels: false

chat:
auto_reply: true
142 changes: 142 additions & 0 deletions .github/actions/gemini-quota-gate/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
name: 'Gemini Quota Gate'
description: |
Throttle Gemini free-tier API calls so CI does not exhaust quotas.
Free-tier limits are multi-dimensional (RPM + TPM + RPD), NOT a hard
20/day cap. Typical Flash free tier is ~15 RPM and ~1000–1500 RPD
(varies by model; Pro is lower). This gate tracks a per-day counter
with a safety margin (default 900 RPD) and supports multi-key rotation.
When exhausted, sets skip=true and posts a graceful comment instead of
hard-failing with TerminalQuotaError. Pair with continue-on-error on
the Gemini CLI step for transient RPM 429s within the day.

inputs:
daily-limit:
description: 'Max Gemini free-tier requests per day (safety margin on RPD; default 900)'
required: false
default: '900'
pr-number:
description: 'PR or issue number for the graceful skip comment'
required: false
default: ''
command:
description: 'The Gemini command (review/triage/invoke) for the skip message'
required: false
default: 'review'
has-backup-key:
description: 'Set to "true" if GEMINI_API_KEY_BACKUP secret is configured'
required: false
default: 'false'

outputs:
skip:
description: '"true" if quota exhausted and the job should be skipped'
value: ${{ steps.check.outputs.skip }}
remaining:
description: 'Remaining quota for today'
value: ${{ steps.check.outputs.remaining }}
use_backup_key:
description: '"true" if the backup key should be used instead of primary'
value: ${{ steps.check.outputs.use_backup_key }}

runs:
using: composite
steps:
- name: Get Pacific date key (RPD resets midnight PT)
id: date
run: echo "key=$(TZ=America/Los_Angeles date +%Y-%m-%d)" >> "$GITHUB_OUTPUT"
shell: bash

- name: Restore quota counter
id: restore
uses: actions/cache@v4
with:
path: /tmp/gemini-quota
key: gemini-quota-${{ steps.date.outputs.key }}
lookup-only: true
enableCrossOsArchive: true
Comment on lines +49 to +56

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.

🔴 Daily request counter is never read back, so the throttle never actually stops anything

The saved usage counter is only checked for existence and never downloaded (lookup-only: true at .github/actions/gemini-quota-gate/action.yml:55), so every run starts counting from zero and the limit is never reached.
Impact: The new throttle does nothing — the daily free-tier allowance can still be blown through, which is exactly the failure this change is meant to prevent.

Mechanism: lookup-only cache restore plus an immutable save key

actions/cache@v4 with lookup-only: true skips extracting the archive, so /tmp/gemini-quota/counter.txt never exists on disk. CURRENT=$(cat "$COUNTER_FILE" 2>/dev/null || echo "0") (.github/actions/gemini-quota-gate/action.yml:74) therefore always yields 0, CURRENT -ge LIMIT is never true, and skip is always false.

A second, compounding problem: the save step (.github/actions/gemini-quota-gate/action.yml:111-117) always writes the exact key gemini-quota-<date>. GitHub Actions cache entries are immutable, so after the first save of the day every subsequent save is a no-op ("cache already exists"). Even with lookup-only removed, the counter would be frozen at 1 for the whole day. A working scheme needs a unique key per run (e.g. gemini-quota-<date>-<run_id>-<run_attempt>) plus restore-keys: gemini-quota-<date>- for a full restore.

Note also that the backup-key branch writes backup_counter.txt into a directory where the primary counter.txt was never restored, so a save from that branch would drop the primary count entirely.

Prompt for agents
The quota gate composite action at .github/actions/gemini-quota-gate/action.yml cannot persist its daily counter. Two independent problems: (1) the restore step uses actions/cache@v4 with lookup-only: true, which only checks for cache existence and never extracts /tmp/gemini-quota, so counter.txt is always missing and CURRENT is always 0 — the gate never trips; (2) the save step always writes the same immutable key gemini-quota-<date>, and GitHub cache entries cannot be overwritten, so only the first save of the day sticks. Rework the caching to use actions/cache/restore with a prefix restore-keys (e.g. restore-keys: gemini-quota-<date>-) and a unique save key per run (e.g. gemini-quota-<date>-<run_id>-<run_attempt>), and drop lookup-only. Also make sure the primary counter file is written/preserved when the backup-key rotation branch runs, otherwise saving the directory loses the primary count. Note that concurrent workflow runs will still race on this counter; consider whether a best-effort approximation is acceptable or whether a stronger store (e.g. a repository variable via the API) is needed.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.


- name: Check / update daily Gemini quota
id: check
env:
DAILY_LIMIT: ${{ inputs.daily-limit }}
PR_NUMBER: ${{ inputs.pr-number }}
COMMAND: ${{ inputs.command }}
HAS_BACKUP_KEY: ${{ inputs.has-backup-key }}
run: |
set -eu

COUNTER_DIR="/tmp/gemini-quota"
COUNTER_FILE="${COUNTER_DIR}/counter.txt"
BACKUP_FILE="${COUNTER_DIR}/backup_counter.txt"

mkdir -p "$COUNTER_DIR"

CURRENT=$(cat "$COUNTER_FILE" 2>/dev/null || echo "0")
LIMIT="$DAILY_LIMIT"
REMAINING=$(( LIMIT - CURRENT ))
Comment on lines +74 to +76

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.

📝 Info: Counter file content is used in arithmetic without validation

CURRENT=$(cat "$COUNTER_FILE" ...) is fed straight into $(( LIMIT - CURRENT )) under set -eu. A corrupted or partially-restored cache file (empty, trailing text) makes the arithmetic expansion fail and aborts the composite action, which makes the whole Gemini job fail — the exact hard-failure mode this PR is meant to eliminate. Normalizing with a case/regex check and defaulting to 0 would keep the gate fail-open.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.


echo "Gemini quota (primary key): $CURRENT / $LIMIT used today (RPD safety margin), $REMAINING remaining"
echo "Note: RPM (~15/min Flash free) is enforced by the API; this gate tracks daily RPD only."

if [ "$CURRENT" -ge "$LIMIT" ]; then
if [ "$HAS_BACKUP_KEY" = "true" ]; then
BACKUP_CURRENT=$(cat "$BACKUP_FILE" 2>/dev/null || echo "0")
BACKUP_REMAINING=$(( LIMIT - BACKUP_CURRENT ))
echo "Gemini quota (backup key): $BACKUP_CURRENT / $LIMIT, $BACKUP_REMAINING remaining"

if [ "$BACKUP_CURRENT" -lt "$LIMIT" ]; then
echo "Primary key exhausted — rotating to backup key"
NEW=$(( BACKUP_CURRENT + 1 ))
echo "$NEW" > "$BACKUP_FILE"
echo "skip=false" >> "$GITHUB_OUTPUT"
echo "remaining=$(( LIMIT - NEW ))" >> "$GITHUB_OUTPUT"
echo "use_backup_key=true" >> "$GITHUB_OUTPUT"
exit 0
fi
Comment on lines +87 to +95

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.

🔍 Backup-key rotation counts against the primary limit and can never recover

Rotation only triggers once the primary counter has reached the limit, and the primary counter is never decremented or reset except by the day key rolling over. Because the same file is only written in one branch at a time and the primary file is not re-emitted in the rotation branch, saving the directory after a rotation can drop counter.txt altogether — which would silently reset the primary count to 0 on the next run and stop rotation. Worth restructuring so both counters are always written before the save.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

fi
Comment thread
devin-ai-integration[bot] marked this conversation as resolved.

echo "skip=true" >> "$GITHUB_OUTPUT"
echo "remaining=0" >> "$GITHUB_OUTPUT"
echo "use_backup_key=false" >> "$GITHUB_OUTPUT"
echo "::warning::Gemini free-tier daily RPD safety margin exhausted ($CURRENT/$LIMIT). Skipping to avoid TerminalQuotaError. RPD resets at midnight Pacific."
else
NEW=$(( CURRENT + 1 ))
echo "$NEW" > "$COUNTER_FILE"
echo "skip=false" >> "$GITHUB_OUTPUT"
echo "remaining=$(( LIMIT - NEW ))" >> "$GITHUB_OUTPUT"
echo "use_backup_key=false" >> "$GITHUB_OUTPUT"
fi
shell: bash

- name: Save quota counter
if: always() && steps.check.outputs.skip != 'true'
uses: actions/cache/save@v4
with:
path: /tmp/gemini-quota
key: gemini-quota-${{ steps.date.outputs.key }}
enableCrossOsArchive: true

- name: Post graceful skip comment
if: steps.check.outputs.skip == 'true' && inputs.pr-number != ''
uses: actions/github-script@v7
with:
script: |
const prNumber = Number(process.env.PR_NUMBER);
if (!prNumber) return;
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
body: [
'<!-- gemini-quota-skip -->',
`⚠️ **Gemini CLI** ${process.env.COMMAND || 'review'} skipped — free-tier daily RPD safety margin exhausted.`,
'',
'Limits are **RPM + TPM + RPD** (not a hard 20/day). RPD resets at midnight Pacific.',
'This is **not a failure** — CI stays green. Other agents (CodeRabbit, Jules) continue.',
'',
'To increase capacity: paid key, or `GEMINI_API_KEY_BACKUP` for multi-key rotation.',
].join('\n'),
});
env:
PR_NUMBER: ${{ inputs.pr-number }}
COMMAND: ${{ inputs.command }}
191 changes: 191 additions & 0 deletions .github/workflows/agent-feedback-linear-sync.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
name: Agent feedback → Linear subtasks

# Every CodeRabbit / Devin (and future bot) review comment becomes a Linear
# subtask under a per-PR parent issue, labeled agent-feedback.
# Requires: secrets.LINEAR_API_KEY

on:
pull_request_review_comment:
types: [created]
pull_request_review:
types: [submitted]

concurrency:
group: linear-feedback-${{ github.event.pull_request.number }}
cancel-in-progress: false

jobs:
sync-linear:
if: |
(
github.event_name == 'pull_request_review_comment' &&
(
contains(github.event.comment.user.login, 'coderabbit') ||
contains(github.event.comment.user.login, 'devin') ||
contains(github.event.comment.user.login, 'copilot') ||
github.event.comment.user.type == 'Bot'
) &&
!contains(github.event.comment.body || '', '<!-- agent-auto-jules -->') &&
!contains(github.event.comment.body || '', '<!-- agent-linear-synced -->')
) ||
(
github.event_name == 'pull_request_review' &&
github.event.review.state != 'approved' &&
(
contains(github.event.review.user.login, 'coderabbit') ||
contains(github.event.review.user.login, 'devin') ||
github.event.review.user.type == 'Bot'
)
)
Comment on lines +19 to +39

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.

🔍 Auto-Jules and Linear-sync workflows will react to their own bots

Both agent-review-auto-jules.yml:24-53 and agent-feedback-linear-sync.yml:19-39 trigger on any comment whose author type == 'Bot', excluding only their own marker comments. Since each workflow posts comments via GITHUB_TOKEN (github-actions[bot]) and the auto-Jules comment carries <!-- agent-auto-jules --> while the Linear sync only excludes that same marker, the Linear sync will happily create a subtask for every auto-Jules ping and for the load-balancer/quota-skip comments too, generating issue churn. Consider an allowlist of bot logins rather than a blanket type == 'Bot' check.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
steps:
- name: Sync to Linear
env:
LINEAR_API_KEY: ${{ secrets.LINEAR_API_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
uses: actions/github-script@v7
with:
script: |
const key = process.env.LINEAR_API_KEY;
if (!key) {
core.warning('LINEAR_API_KEY not set — skip Linear subtask sync');
return;
}
const pr = context.payload.pull_request;
const prNumber = pr.number;
const prUrl = pr.html_url;
const bot =
context.payload.comment?.user?.login ||
context.payload.review?.user?.login ||
'bot';
const path = context.payload.comment?.path || '';
const commentBody = (
context.payload.comment?.body ||
context.payload.review?.body ||
''
).slice(0, 3500);
const commentUrl =
context.payload.comment?.html_url ||
context.payload.review?.html_url ||
prUrl;

async function linear(query, variables) {
const res = await fetch('https://api.linear.app/graphql', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: key,
},
body: JSON.stringify({ query, variables }),
});
const json = await res.json();
if (json.errors) {
throw new Error(JSON.stringify(json.errors));
}
return json.data;
}

const teamId = '1e672463-31de-40b2-a378-3c03fd8f7e3b';
const parentTitle = `PR #${prNumber} agent feedback rollup`;

const search = await linear(
`query($filter: IssueFilter!) {
issues(filter: $filter, first: 5) {
nodes { id identifier title }
}
}`,
{
filter: {
title: { eq: parentTitle },
team: { id: { eq: teamId } }
}
}
);
let parentId = search.issues?.nodes?.find(
n => n.title === parentTitle
)?.id;

if (!parentId) {
const created = await linear(
`mutation($input: IssueCreateInput!) {
issueCreate(input: $input) {
success
issue { id identifier url }
}
}`,
{
input: {
teamId,
title: parentTitle,
description: [
'**Agent: Jules | Grok**',
'',
`Parent rollup for automated review feedback on ${prUrl}`,
'',
'Subtasks are created by GHA `agent-feedback-linear-sync`.',
'Jules auto-resolve via `agent-review-auto-jules`.',
'',
'Signed-off-by: Grok <grok@x.ai>',
].join('\n'),
},
}
);
parentId = created.issueCreate.issue.id;
core.info(`Created parent ${created.issueCreate.issue.identifier}`);
}

const labels = await linear(
`query { issueLabels(filter: { name: { eq: "agent-feedback" } }) { nodes { id } } }`
);
const labelId = labels.issueLabels?.nodes?.[0]?.id;

const subTitle = path
? `[${bot}] PR #${prNumber}: ${path}`
: `[${bot}] PR #${prNumber}: review feedback`;

const sub = await linear(
`mutation($input: IssueCreateInput!) {
issueCreate(input: $input) {
success
issue { id identifier url }
}
}`,
{
input: {
teamId,
parentId,
title: subTitle.slice(0, 200),
description: [
'**Agent: Jules | Grok**',
'',
`Source: ${commentUrl}`,
`PR: ${prUrl}`,
`Bot: ${bot}`,
path ? `Path: \`${path}\`` : '',
'',
'### Comment',
commentBody,
'',
'Auto-synced by agent-feedback-linear-sync.yml',
].filter(Boolean).join('\n'),
...(labelId ? { labelIds: [labelId] } : {}),
},
}
);
core.info(`Subtask ${sub.issueCreate.issue.identifier} ${sub.issueCreate.issue.url}`);

if (context.payload.comment?.id) {
try {
await github.rest.reactions.createForPullRequestReviewComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: context.payload.comment.id,
content: 'eyes',
});
} catch (e) {
core.info(String(e));
}
}
Loading
Loading