diff --git a/.Jules/palette.md b/.Jules/palette.md new file mode 100644 index 000000000..5874142c0 --- /dev/null +++ b/.Jules/palette.md @@ -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. diff --git a/.coderabbit.yaml b/.coderabbit.yaml new file mode 100644 index 000000000..1c7e29659 --- /dev/null +++ b/.coderabbit.yaml @@ -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 diff --git a/.github/actions/gemini-quota-gate/action.yml b/.github/actions/gemini-quota-gate/action.yml new file mode 100644 index 000000000..b502850b1 --- /dev/null +++ b/.github/actions/gemini-quota-gate/action.yml @@ -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 + + - 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 )) + + 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 + fi + + 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 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 }} diff --git a/.github/workflows/agent-feedback-linear-sync.yml b/.github/workflows/agent-feedback-linear-sync.yml new file mode 100644 index 000000000..37a4da346 --- /dev/null +++ b/.github/workflows/agent-feedback-linear-sync.yml @@ -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 || '', '') && + !contains(github.event.comment.body || '', '') + ) || + ( + 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' + ) + ) + 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 ', + ].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)); + } + } diff --git a/.github/workflows/agent-jules-on-issues.yml b/.github/workflows/agent-jules-on-issues.yml new file mode 100644 index 000000000..19a0ccf82 --- /dev/null +++ b/.github/workflows/agent-jules-on-issues.yml @@ -0,0 +1,250 @@ +name: Jules on Issues (label / @jules) + +# Native Jules already responds when the GitHub App sees label "jules". +# This workflow adds: +# 1) Explicit acknowledge + structured prompt via jules-invoke (if JULES_API_KEY set) +# 2) Fallback @jules comment so the App path still fires without API key +# 3) Prior open-PR inventory injected into prompt (coordination) +# 4) marker so Gemini does not claim the same files +# +# Requires: Jules GitHub App installed on this repo +# Optional: secrets.JULES_API_KEY for google-labs-code/jules-invoke +# +# SECURITY: Issue triggers are restricted — untrusted openers cannot burn quota. + +on: + issues: + types: [labeled, opened] + issue_comment: + types: [created] + +concurrency: + group: jules-issue-${{ github.event.issue.number || github.run_id }} + cancel-in-progress: false + +jobs: + jules-on-label: + if: | + github.event_name == 'issues' && + github.event.action == 'labeled' && + ( + github.event.label.name == 'jules' || + github.event.label.name == 'Jules' || + github.event.label.name == 'JULES' + ) + runs-on: ubuntu-latest + permissions: + contents: read + issues: write + pull-requests: read + steps: + - name: Acknowledge (👀) + inventory open agent PRs + id: coord + uses: actions/github-script@v7 + with: + script: | + const issue = context.payload.issue.number; + try { + await github.rest.reactions.createForIssue({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue, + content: 'eyes', + }); + } catch (e) { + core.info('Reaction skip: ' + e.message); + } + const marker = ''; + const { data: comments } = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue, + per_page: 50, + }); + if (comments.some(c => c.body && c.body.includes(marker))) { + core.info('Already acknowledged'); + } else { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue, + body: [ + marker, + `👀 **Jules summoned** on issue #${issue} via label \`jules\`.`, + ``, + `Jules (Google Labs) will plan → implement in a Cloud VM → open a PR.`, + `Coordination: prior open agent PRs are injected into the task prompt.`, + ``, + `Tracking: ${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`, + ].join('\n'), + }); + } + const { data: prs } = await github.rest.pulls.list({ + owner: context.repo.owner, + repo: context.repo.repo, + state: 'open', + per_page: 50, + }); + const agentLogins = ['google-labs-jules[bot]', 'devin-ai-integration[bot]', 'coderabbitai[bot]', 'ecc-tools[bot]']; + const relevant = prs.filter(p => { + const u = (p.user && p.user.login) || ''; + const body = (p.body || '') + ' ' + (p.title || ''); + return agentLogins.some(a => u.includes(a.split('[')[0])) || + body.includes(`#${issue}`) || + body.toLowerCase().includes(`issue ${issue}`); + }).slice(0, 15); + const inventory = relevant.map(p => + `- PR #${p.number} @${p.user.login}: ${p.title} (base=${p.base.ref} head=${p.head.ref})` + ).join('\n') || '(none matching)'; + core.setOutput('prior_prs', inventory); + + - name: Invoke Jules API (optional) + if: ${{ secrets.JULES_API_KEY != '' }} + continue-on-error: true + uses: google-labs-code/jules-invoke@v1 + with: + jules_api_key: ${{ secrets.JULES_API_KEY }} + starting_branch: master-staging + prompt: | + You are Jules working on termux-monorepo. Read AGENTS.md and GEMINI.md if present. + + ## Issue #${{ github.event.issue.number }}: ${{ github.event.issue.title }} + + ${{ github.event.issue.body }} + + ## Open agent / related PRs (DO NOT overlap files) + ${{ steps.coord.outputs.prior_prs }} + + ## COORDINATION RULES (mandatory) + 1. Before changing any file, respect the open PRs listed above — do not modify files already present in those diffs unless that PR is closed/superseded. + 2. Prefer disjoint file sets vs Gemini / other agents on the same issue. + 3. After opening a PR, post a comment on the issue with: + + claimed_by: jules + issue: ${{ github.event.issue.number }} + files: + pr: + 4. Base branch: master-staging. Minimal diffs. No secrets. Respect repo gates. + 5. If another agent already claimed the core work, review their PR instead of duplicating. + + ## Instructions + 1. Diagnose root cause; prefer minimal diffs. + 2. Preserve Sentinel 0o600/0o700 patterns if touching credentials/session paths. + 3. Open a PR; cite Implements if an ITEMS.md id applies. + 4. Run or respect repo gates (repo_gate / termux_smoke) where possible. + 5. Do not commit secrets or Class 3/4 artifacts. + + - name: Fallback @jules ping (App path) + if: ${{ secrets.JULES_API_KEY == '' }} + uses: actions/github-script@v7 + with: + script: | + const issue = context.payload.issue.number; + const marker = ''; + const { data: comments } = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue, + per_page: 30, + }); + if (comments.some(c => c.body && c.body.includes(marker))) return; + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue, + body: [ + marker, + `@jules Please implement a fix for this issue.`, + ``, + `Base your work on **master-staging**. Prefer minimal diffs; respect AGENTS.md.`, + `Check open agent PRs first — do not edit files already claimed by another agent on this issue.`, + `Open a PR when done and post on the issue.`, + ].join('\n'), + }); + + jules-on-mention: + if: | + github.event_name == 'issue_comment' && + !github.event.issue.pull_request && + github.event.sender.type == 'User' && + contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.comment.author_association) && + ( + startsWith(github.event.comment.body, '@jules') || + startsWith(github.event.comment.body, '@Jules') + ) + runs-on: ubuntu-latest + permissions: + contents: read + issues: write + pull-requests: read + steps: + - name: React 👀 on comment + inventory PRs + id: coord + uses: actions/github-script@v7 + with: + script: | + try { + await github.rest.reactions.createForIssueComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: context.payload.comment.id, + content: 'eyes', + }); + } catch (e) { + core.info('Reaction skip: ' + e.message); + } + const issue = context.payload.issue.number; + const { data: prs } = await github.rest.pulls.list({ + owner: context.repo.owner, + repo: context.repo.repo, + state: 'open', + per_page: 50, + }); + const agentLogins = ['google-labs-jules', 'devin-ai-integration', 'coderabbitai', 'ecc-tools']; + const relevant = prs.filter(p => { + const u = (p.user && p.user.login) || ''; + const body = (p.body || '') + ' ' + (p.title || ''); + return agentLogins.some(a => u.includes(a)) || + body.includes(`#${issue}`) || + body.toLowerCase().includes(`issue ${issue}`); + }).slice(0, 15); + const inventory = relevant.map(p => + `- PR #${p.number} @${p.user.login}: ${p.title}` + ).join('\n') || '(none matching)'; + core.setOutput('prior_prs', inventory); + + - name: Ensure jules label + uses: actions/github-script@v7 + with: + script: | + try { + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.payload.issue.number, + labels: ['jules'], + }); + } catch (e) { + core.info('Label add skip: ' + e.message); + } + + - name: Invoke Jules API on mention (optional) + if: ${{ secrets.JULES_API_KEY != '' }} + continue-on-error: true + uses: google-labs-code/jules-invoke@v1 + with: + jules_api_key: ${{ secrets.JULES_API_KEY }} + starting_branch: master-staging + prompt: | + Issue #${{ github.event.issue.number }}: ${{ github.event.issue.title }} + + ${{ github.event.issue.body }} + + User request: + ${{ github.event.comment.body }} + + ## Open agent / related PRs (DO NOT overlap files) + ${{ steps.coord.outputs.prior_prs }} + + COORDINATION: Prefer disjoint files vs other agents. Post after opening a PR. + Follow AGENTS.md. Base branch master-staging. Minimal diffs. Open a PR. diff --git a/.github/workflows/agent-load-balancer.yml b/.github/workflows/agent-load-balancer.yml new file mode 100644 index 000000000..74848dcd3 --- /dev/null +++ b/.github/workflows/agent-load-balancer.yml @@ -0,0 +1,227 @@ +name: '⚖️ Agent Load Balancer' + +# Capacity-aware dispatch router for all free-tier AI agents. +# Routes review / triage / invoke tasks to the agent with the most headroom, +# falling back gracefully when quotas are exhausted. +# +# Agent capacity model (free-tier, conservative): +# Gemini: 20 req/day, 1 concurrent (quota-gate throttles to 18) +# Jules: 15/day, 3 concurrent (sessions span multiple PR revisions) +# CodeRabbit: unlimited per-PR (always available as fallback) +# Devin: on-demand (invoked via @devin mentions) +# Copilot: on-demand (invoked via @copilot mentions) +# +# Implements: agent-quota-loadbalancer QL-04 + +on: + workflow_call: + inputs: + command: + description: 'review | triage | invoke' + type: string + required: true + pr_number: + type: string + required: false + default: '' + issue_number: + type: string + required: false + default: '' + additional_context: + type: string + required: false + default: '' + prior_prs: + type: string + required: false + default: 'none' + +defaults: + run: + shell: bash + +jobs: + # ── Capacity probe ───────────────────────────────────────────── + # Check Gemini quota availability before dispatching. + # Jules and CodeRabbit are always considered available (their own + # platforms handle throttling internally). + probe-capacity: + runs-on: ubuntu-latest + permissions: + contents: read + issues: write + pull-requests: write + actions: write + outputs: + gemini_available: ${{ steps.gemini-probe.outputs.available }} + gemini_remaining: ${{ steps.gemini-probe.outputs.remaining }} + recommended_agent: ${{ steps.route.outputs.agent }} + reason: ${{ steps.route.outputs.reason }} + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: Probe Gemini quota + id: gemini-probe + uses: ./.github/actions/gemini-quota-gate + with: + daily-limit: '18' + pr-number: '' + command: 'probe' + has-backup-key: ${{ secrets.GEMINI_API_KEY_BACKUP != '' }} + continue-on-error: true + + - name: Route to best available agent + id: route + uses: actions/github-script@v7 + env: + GEMINI_AVAILABLE: ${{ steps.gemini-probe.outputs.skip != 'true' }} + GEMINI_REMAINING: ${{ steps.gemini-probe.outputs.remaining || '0' }} + COMMAND: ${{ inputs.command }} + with: + script: | + const geminiAvailable = process.env.GEMINI_AVAILABLE === 'true'; + const geminiRemaining = Number(process.env.GEMINI_REMAINING || '0'); + const command = process.env.COMMAND || 'review'; + + // Capacity priority for each command type: + // review: Gemini (if available) → CodeRabbit (always) → human ping + // triage: Gemini (if available) → Jules label → human ping + // invoke: Gemini (if available) → Jules → human ping + let agent, reason; + + if (command === 'review') { + if (geminiAvailable && geminiRemaining > 2) { + agent = 'gemini'; + reason = `Gemini has ${geminiRemaining} quota remaining — primary reviewer`; + } else if (geminiAvailable) { + agent = 'coderabbit'; + reason = `Gemini low (${geminiRemaining} left) — CodeRabbit is primary, Gemini assists`; + } else { + agent = 'coderabbit'; + reason = 'Gemini quota exhausted — CodeRabbit is sole reviewer (always available)'; + } + } else if (command === 'triage') { + if (geminiAvailable && geminiRemaining > 2) { + agent = 'gemini'; + reason = `Gemini has ${geminiRemaining} quota remaining — primary triage`; + } else { + agent = 'jules-label'; + reason = 'Gemini quota exhausted — routing triage to Jules (label + @jules ping)'; + } + } else { + if (geminiAvailable) { + agent = 'gemini'; + reason = `Gemini available (${geminiRemaining} remaining) — on-demand invoke`; + } else { + agent = 'jules'; + reason = 'Gemini quota exhausted — routing invoke to Jules'; + } + } + + core.setOutput('agent', agent); + core.setOutput('reason', reason); + core.info(`Load balancer: ${agent} — ${reason}`); + + - name: Post routing decision + if: inputs.pr_number != '' || inputs.issue_number != '' + uses: actions/github-script@v7 + env: + PR_NUMBER: ${{ inputs.pr_number }} + ISSUE_NUMBER: ${{ inputs.issue_number }} + AGENT: ${{ steps.route.outputs.agent }} + REASON: ${{ steps.route.outputs.reason }} + GEMINI_REMAINING: ${{ steps.gemini-probe.outputs.remaining || '0' }} + with: + script: | + const num = Number(process.env.PR_NUMBER || process.env.ISSUE_NUMBER); + if (!num) return; + const marker = ''; + const { data: comments } = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: num, + per_page: 10, + }); + // Don't spam — only post if no recent routing comment + const recent = comments.filter(c => c.body?.includes(marker)); + if (recent.length > 0) { + const age = Date.now() - new Date(recent[recent.length - 1].created_at).getTime(); + if (age < 5 * 60 * 1000) { + core.info('Recent load-balancer comment exists — skipping'); + return; + } + } + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: num, + body: [ + marker, + `⚖️ **Agent Load Balancer** routed this to **${process.env.AGENT}**.`, + '', + `> ${process.env.REASON}`, + '', + `Gemini quota: ${process.env.GEMINI_REMAINING} remaining today (limit 18, resets 00:00 UTC).`, + `Other agents (CodeRabbit, Jules) continue independently.`, + ].join('\n'), + }); + + # ── Dispatch to Gemini (if routed) ──────────────────────────── + gemini-dispatch: + needs: probe-capacity + if: needs.probe-capacity.outputs.recommended_agent == 'gemini' + uses: ./.github/workflows/gemini-review.yml + permissions: + contents: read + issues: write + pull-requests: write + with: + additional_context: ${{ inputs.additional_context }} + prior_prs: ${{ inputs.prior_prs }} + secrets: inherit + + # ── Fallback: ping Jules for triage/invoke when Gemini is down ─ + jules-fallback: + needs: probe-capacity + if: | + needs.probe-capacity.outputs.recommended_agent == 'jules' || + needs.probe-capacity.outputs.recommended_agent == 'jules-label' + runs-on: ubuntu-latest + permissions: + contents: read + issues: write + pull-requests: read + steps: + - name: Ping Jules as fallback + uses: actions/github-script@v7 + env: + ISSUE_NUMBER: ${{ inputs.issue_number }} + PR_NUMBER: ${{ inputs.pr_number }} + COMMAND: ${{ inputs.command }} + REASON: ${{ needs.probe-capacity.outputs.reason }} + with: + script: | + const num = Number(process.env.PR_NUMBER || process.env.ISSUE_NUMBER); + if (!num) return; + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: num, + body: [ + '', + `@jules **Fallback dispatch** — Gemini quota exhausted.`, + '', + `> ${process.env.REASON}`, + '', + `Command: ${process.env.COMMAND}`, + `Please handle this ${process.env.COMMAND} request. CodeRabbit continues to review the PR diff independently.`, + ].join('\n'), + }); + + # ── CodeRabbit is always available — no action needed ────────── + # CodeRabbit runs on every PR automatically via its GitHub App. + # When the load balancer routes to 'coderabbit', no extra dispatch + # is needed — it's already reviewing. diff --git a/.github/workflows/agent-review-auto-jules.yml b/.github/workflows/agent-review-auto-jules.yml new file mode 100644 index 000000000..a4342da80 --- /dev/null +++ b/.github/workflows/agent-review-auto-jules.yml @@ -0,0 +1,171 @@ +name: Agent review → auto Jules + +# When CodeRabbit, Devin, or other review bots leave feedback, automatically +# summon Jules to apply fixes. Operator no longer comments "@jules resolve". +# +# Requires: secrets.JULES_API_KEY (optional but recommended for invoke path) +# Jules GitHub App must have repo access. Prefer Reactive Mode OFF so Jules +# also picks up the posted @jules comment on PRs it owns. + +on: + pull_request_review: + types: [submitted] + pull_request_review_comment: + types: [created] + issue_comment: + types: [created] + +concurrency: + group: agent-auto-jules-${{ github.event.pull_request.number || github.event.issue.number || github.run_id }} + cancel-in-progress: false + +jobs: + detect-bot-feedback: + if: | + ( + 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') || + contains(github.event.review.user.login, 'copilot') || + github.event.review.user.type == 'Bot' + ) + ) || + ( + 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, '') + ) || + ( + github.event_name == 'issue_comment' && + github.event.issue.pull_request && + ( + contains(github.event.comment.user.login, 'coderabbit') || + contains(github.event.comment.user.login, 'devin') + ) && + !contains(github.event.comment.body, '') + ) + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: write + issues: write + outputs: + pr_number: ${{ steps.meta.outputs.pr_number }} + head_ref: ${{ steps.meta.outputs.head_ref }} + should_invoke: ${{ steps.meta.outputs.should_invoke }} + steps: + - name: Resolve PR metadata + id: meta + uses: actions/github-script@v7 + with: + script: | + const event = context.eventName; + let prNumber = null; + if (event === 'pull_request_review') { + prNumber = context.payload.pull_request.number; + } else if (event === 'pull_request_review_comment') { + prNumber = context.payload.pull_request.number; + } else if (event === 'issue_comment') { + prNumber = context.payload.issue.number; + } + if (!prNumber) { + core.setOutput('should_invoke', 'false'); + return; + } + const { data: pr } = await github.rest.pulls.get({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: prNumber, + }); + core.setOutput('pr_number', String(prNumber)); + core.setOutput('head_ref', pr.head.ref); + core.setOutput('base_ref', pr.base.ref); + core.setOutput('should_invoke', 'true'); + + - name: Post @jules resolve (idempotent window) + if: steps.meta.outputs.should_invoke == 'true' + uses: actions/github-script@v7 + with: + script: | + const prNumber = Number('${{ steps.meta.outputs.pr_number }}'); + const head = '${{ steps.meta.outputs.head_ref }}'; + const marker = ''; + const { data: comments } = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: prNumber, + per_page: 50, + }); + const recent = comments + .filter(c => c.body && c.body.includes(marker)) + .sort((a, b) => new Date(b.created_at) - new Date(a.created_at))[0]; + if (recent) { + const ageMs = Date.now() - new Date(recent.created_at).getTime(); + if (ageMs < 20 * 60 * 1000) { + core.info('Debounced: recent agent-auto-jules comment exists'); + return; + } + } + const bot = + context.payload.review?.user?.login || + context.payload.comment?.user?.login || + 'review-bot'; + const snippet = ( + context.payload.review?.body || + context.payload.comment?.body || + '' + ).slice(0, 1200); + const path = context.payload.comment?.path || ''; + const body = [ + marker, + `@jules **Auto-resolve** (GHA agent-review-auto-jules) — do not wait for a human ping.`, + ``, + `Bot feedback from **${bot}** on PR #${prNumber} (branch \`${head}\`).`, + path ? `File: \`${path}\`` : '', + ``, + `### Feedback excerpt`, + snippet ? '```' : '', + snippet || '(see review threads)', + snippet ? '```' : '', + ``, + `### Instructions`, + `1. Address **all open review threads** on this PR (CodeRabbit, Devin, Copilot, etc.).`, + `2. Prefer minimal diffs; preserve Sentinel 0o600/0o700 if those files are touched.`, + `3. Push commits to branch \`${head}\`. Do not retarget away from the PR base without cause.`, + `4. If conflicts with base exist, resolve them.`, + `5. Skip pure nits only if they conflict with security/gates; otherwise apply autofixes.`, + ``, + `Agent: Grok orchestration · Profile: https://x.com/grok`, + ].filter(Boolean).join('\n'); + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: prNumber, + body, + }); + + invoke-jules-api: + needs: detect-bot-feedback + if: needs.detect-bot-feedback.outputs.should_invoke == 'true' && vars.JULES_AUTO_INVOKE != '0' + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - name: Jules API session (if key present) + env: + JULES_API_KEY: ${{ secrets.JULES_API_KEY }} + PR: ${{ needs.detect-bot-feedback.outputs.pr_number }} + HEAD: ${{ needs.detect-bot-feedback.outputs.head_ref }} + run: | + if [ -z "$JULES_API_KEY" ]; then + echo "No JULES_API_KEY — relying on @jules PR comment path only" + exit 0 + fi + echo "Jules API invoke is best-effort; @jules comment is the reliable trigger." diff --git a/.github/workflows/gemini-dispatch.yml b/.github/workflows/gemini-dispatch.yml new file mode 100644 index 000000000..844d898cf --- /dev/null +++ b/.github/workflows/gemini-dispatch.yml @@ -0,0 +1,181 @@ +name: '🔀 Gemini Dispatch (free-tier agentic)' + +# Central router for free-tier Gemini CLI agentic CI/CD. +# Requires: secrets.GEMINI_API_KEY (Google AI Studio free quota) +# Complements existing Jules + CodeRabbit stack. +# +# Ack pattern: posts 👀 on the triggering comment (same UX as Jules/CodeRabbit). +# Coordination: prior open agent PRs are passed into triage/review/invoke prompts. +# Quota: child workflows include a quota-gate that skips gracefully when the +# free-tier daily limit is hit — CI stays green, other agents continue. +# Load balancing: routes to the agent with the most headroom (see +# agent-load-balancer.yml for capacity-aware dispatch). + +on: + pull_request: + types: [opened, synchronize, ready_for_review] + issues: + types: [opened, reopened] + issue_comment: + types: [created] + pull_request_review_comment: + types: [created] + pull_request_review: + types: [submitted] + +defaults: + run: + shell: bash + +jobs: + dispatch: + if: | + ( + github.event_name == 'pull_request' && + github.event.pull_request.head.repo.fork == false && + github.event.pull_request.draft == false + ) || ( + github.event_name == 'issues' && + contains(fromJSON('["opened", "reopened"]'), github.event.action) + ) || ( + github.event.sender.type == 'User' && + startsWith(github.event.comment.body || github.event.review.body || '', '@gemini-cli') && + contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.comment.author_association || github.event.review.author_association || github.event.issue.author_association) + ) + runs-on: ubuntu-latest + permissions: + contents: read + issues: write + pull-requests: write + outputs: + command: ${{ steps.extract.outputs.command }} + additional_context: ${{ steps.extract.outputs.additional_context }} + prior_prs: ${{ steps.extract.outputs.prior_prs }} + issue_number: ${{ github.event.pull_request.number || github.event.issue.number }} + steps: + - name: Extract command + prior PR inventory + id: extract + uses: actions/github-script@v7 + env: + EVENT_TYPE: ${{ github.event_name }}.${{ github.event.action }} + REQUEST: ${{ github.event.comment.body || github.event.review.body || github.event.issue.body || '' }} + with: + script: | + const eventType = process.env.EVENT_TYPE; + const request = process.env.REQUEST || ''; + if (eventType === 'pull_request.opened' || eventType === 'pull_request.synchronize' || eventType === 'pull_request.ready_for_review') { + core.setOutput('command', 'review'); + } else if (['issues.opened', 'issues.reopened'].includes(eventType)) { + core.setOutput('command', 'triage'); + } else if (request.startsWith('@gemini-cli /review')) { + core.setOutput('command', 'review'); + core.setOutput('additional_context', request.replace(/^@gemini-cli \/review/, '').trim()); + } else if (request.startsWith('@gemini-cli /triage')) { + core.setOutput('command', 'triage'); + } else if (request.startsWith('@gemini-cli')) { + core.setOutput('command', 'invoke'); + core.setOutput('additional_context', request.replace(/^@gemini-cli/, '').trim()); + } else { + core.setOutput('command', 'none'); + } + + try { + const issueNum = context.payload.pull_request?.number || context.payload.issue?.number; + const { data: prs } = await github.rest.pulls.list({ + owner: context.repo.owner, + repo: context.repo.repo, + state: 'open', + per_page: 40, + }); + const agentHints = ['jules', 'devin', 'coderabbit', 'ecc-tools', 'gemini']; + const relevant = prs.filter(p => { + const u = ((p.user && p.user.login) || '').toLowerCase(); + const body = ((p.body || '') + ' ' + (p.title || '')).toLowerCase(); + return agentHints.some(a => u.includes(a)) || + (issueNum && (body.includes('#' + issueNum) || body.includes('issue ' + issueNum))); + }).slice(0, 12); + const inventory = relevant.map(p => + `PR #${p.number} @${p.user.login}: ${p.title}` + ).join(' | ') || 'none'; + core.setOutput('prior_prs', inventory); + } catch (e) { + core.setOutput('prior_prs', 'unavailable'); + } + + - name: Acknowledge with 👀 + comment + if: steps.extract.outputs.command != 'none' + uses: actions/github-script@v7 + with: + script: | + const cmd = '${{ steps.extract.outputs.command }}'; + const number = context.payload.pull_request?.number || context.payload.issue?.number; + if (context.payload.comment?.id) { + try { + await github.rest.reactions.createForIssueComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: context.payload.comment.id, + content: 'eyes', + }); + } catch (e) { + core.info('Comment reaction skip: ' + e.message); + } + } else if (number) { + try { + await github.rest.reactions.createForIssue({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: number, + content: 'eyes', + }); + } catch (e) { + core.info('Issue reaction skip: ' + e.message); + } + } + if (context.payload.comment || context.payload.review) { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: number, + body: `🤖 **Gemini CLI** received your request (\`${cmd}\`). Tracking: ${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`, + }); + } + + triage: + needs: dispatch + if: needs.dispatch.outputs.command == 'triage' + uses: ./.github/workflows/gemini-triage.yml + permissions: + contents: read + issues: write + pull-requests: write + with: + additional_context: ${{ needs.dispatch.outputs.additional_context }} + prior_prs: ${{ needs.dispatch.outputs.prior_prs }} + secrets: inherit + + review: + needs: dispatch + if: needs.dispatch.outputs.command == 'review' + uses: ./.github/workflows/gemini-review.yml + permissions: + contents: read + issues: write + pull-requests: write + with: + additional_context: ${{ needs.dispatch.outputs.additional_context }} + prior_prs: ${{ needs.dispatch.outputs.prior_prs }} + secrets: inherit + + invoke: + needs: dispatch + if: needs.dispatch.outputs.command == 'invoke' + uses: ./.github/workflows/gemini-invoke.yml + permissions: + contents: read + issues: write + pull-requests: write + with: + additional_context: ${{ needs.dispatch.outputs.additional_context }} + prior_prs: ${{ needs.dispatch.outputs.prior_prs }} + secrets: inherit diff --git a/.github/workflows/gemini-invoke.yml b/.github/workflows/gemini-invoke.yml new file mode 100644 index 000000000..44c2911db --- /dev/null +++ b/.github/workflows/gemini-invoke.yml @@ -0,0 +1,68 @@ +name: '▶️ Gemini Invoke (on-demand free-tier teammate)' + +on: + workflow_call: + inputs: + additional_context: + type: string + required: false + prior_prs: + type: string + required: false + default: 'none' + +concurrency: + group: gemini-invoke-${{ github.event.pull_request.number || github.event.issue.number || github.run_id }} + cancel-in-progress: false + +jobs: + invoke: + runs-on: ubuntu-latest + permissions: + contents: read + issues: write + pull-requests: write + actions: write + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: Gemini quota gate + id: quota + uses: ./.github/actions/gemini-quota-gate + with: + daily-limit: '18' + pr-number: ${{ github.event.pull_request.number || github.event.issue.number || '' }} + command: 'invoke' + has-backup-key: ${{ secrets.GEMINI_API_KEY_BACKUP != '' }} + + - name: Run Gemini CLI assistant + if: steps.quota.outputs.skip != 'true' + uses: google-github-actions/run-gemini-cli@v0 + env: + GEMINI_CLI_TRUST_WORKSPACE: 'true' + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + gemini_api_key: ${{ steps.quota.outputs.use_backup_key == 'true' && secrets.GEMINI_API_KEY_BACKUP || secrets.GEMINI_API_KEY }} + github_pr_number: ${{ github.event.pull_request.number }} + github_issue_number: ${{ github.event.issue.number }} + prompt: | + You are the on-demand free-tier Gemini CLI teammate for termux-monorepo. + Read GEMINI.md and AGENTS.md first. + + User request / additional context: + ${{ inputs.additional_context }} + + ## Open agent / related PRs (coordination — DO NOT overlap files) + ${{ inputs.prior_prs }} + + ## COORDINATION RULES + 1. Jules is the primary *builder* (creates branches/PRs). You are primarily triage/review/analysis unless the user explicitly asks you to implement and write is possible. + 2. Do not edit files already changed in open agent PRs for this issue. + 3. If you open or recommend a PR, suggest posting with claimed_by: gemini and file list. + 4. Prefer reviewing Jules/Devin PRs over duplicating their work. + + Perform the request within free-tier limits. Prefer analysis + concrete suggestions or minimal patches. + If code changes are required and you cannot push, describe the exact diff and next steps for Jules or a human. + Never invent work outside the proposal registry process. Never commit secrets. diff --git a/.github/workflows/gemini-review.yml b/.github/workflows/gemini-review.yml new file mode 100644 index 000000000..cf496c5b4 --- /dev/null +++ b/.github/workflows/gemini-review.yml @@ -0,0 +1,170 @@ +name: '🔍 Gemini PR Review (free-tier)' + +on: + workflow_call: + inputs: + additional_context: + type: string + required: false + prior_prs: + type: string + required: false + default: 'none' + +concurrency: + group: gemini-review-${{ github.event.pull_request.number || github.run_id }} + cancel-in-progress: true + +jobs: + review: + runs-on: ubuntu-latest + permissions: + contents: read + issues: write + pull-requests: write + actions: write + outputs: + skipped: ${{ steps.quota.outputs.skip }} + reviewed_sha: ${{ steps.session.outputs.reviewed_sha }} + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + fetch-depth: 0 + + # ── Session continuation ────────────────────────────────────── + # Cache the last-reviewed SHA per PR so synchronize pushes skip + # if the commit hasn't changed, and only re-review changed files. + - name: Session continuation — check last reviewed SHA + id: session + uses: actions/github-script@v7 + with: + script: | + const prNumber = context.payload.pull_request?.number; + const headSha = context.payload.pull_request?.head?.sha || ''; + if (!prNumber) { + core.setOutput('already_reviewed', 'false'); + core.setOutput('reviewed_sha', ''); + core.setOutput('changed_files', ''); + return; + } + const cacheKey = `gemini-review-${prNumber}-sha`; + // Check if we already reviewed this exact SHA + try { + const { data: comments } = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: prNumber, + per_page: 100, + }); + const reviewComments = comments.filter(c => + c.body?.includes('/)) + .sort((a, b) => new Date(b.created_at) - new Date(a.created_at)); + if (shaComments.length > 0) { + const m = shaComments[0].body.match(//); + prevSha = m ? m[1] : ''; + } + } catch (e) {} + // Get changed files since last review (or all files if first review) + let changedFiles = ''; + if (prevSha && headSha) { + try { + const { data: comparison } = await github.rest.repos.compareCommits({ + owner: context.repo.owner, + repo: context.repo.repo, + base: prevSha, + head: headSha, + }); + changedFiles = comparison.files?.map(f => f.filename).join(', ') || ''; + } catch (e) { + core.info('Diff comparison skip: ' + e.message); + } + } + core.setOutput('already_reviewed', 'false'); + core.setOutput('reviewed_sha', headSha); + core.setOutput('changed_files', changedFiles); + core.setOutput('prev_sha', prevSha); + + - name: Skip if already reviewed + if: steps.session.outputs.already_reviewed == 'true' + run: | + echo "Already reviewed this SHA — skipping Gemini review to save quota." + echo "skip=true" >> "$GITHUB_ENV" + + # ── Quota gate ──────────────────────────────────────────────── + - name: Gemini quota gate + id: quota + if: steps.session.outputs.already_reviewed != 'true' + uses: ./.github/actions/gemini-quota-gate + with: + daily-limit: '18' + pr-number: ${{ github.event.pull_request.number || '' }} + command: 'review' + has-backup-key: ${{ secrets.GEMINI_API_KEY_BACKUP != '' }} + + - name: Run Gemini CLI PR review + if: | + steps.session.outputs.already_reviewed != 'true' && + steps.quota.outputs.skip != 'true' + uses: google-github-actions/run-gemini-cli@v0 + env: + GEMINI_CLI_TRUST_WORKSPACE: 'true' + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + gemini_api_key: ${{ steps.quota.outputs.use_backup_key == 'true' && secrets.GEMINI_API_KEY_BACKUP || secrets.GEMINI_API_KEY }} + github_pr_number: ${{ github.event.pull_request.number }} + prompt: | + You are the free-tier agentic PR reviewer for termux-monorepo. + Read GEMINI.md and AGENTS.md. + + Review this pull request for: + - Correctness and regressions + - Security (especially credentials, session stores, chmod 0o600/0o700 patterns) + - Alignment with AGENTS.md hard rules (master-staging, gates, no Class 3/4 artifacts) + - Minimal-diff preference + - Overlap with other open agent PRs: ${{ inputs.prior_prs }} + + ${{ steps.session.outputs.changed_files != '' && format('## Changed files since last review\nOnly these files changed since your last review — focus here:\n{0}', steps.session.outputs.changed_files) || '## Full review (first pass on this PR)' }} + + Post a concise review comment. Do not approve or request changes via the formal review API unless clearly authorized; prefer a comment. + Coordinate with CodeRabbit (already reviewing) and Jules (may auto-fix later). + If this is a Jules PR, focus on gaps Jules may have missed rather than rewriting the same work. + + End your comment with: + + Additional context: ${{ inputs.additional_context }} + + - name: Record reviewed SHA (session bookmark) + if: | + steps.session.outputs.already_reviewed != 'true' && + steps.quota.outputs.skip != 'true' && + always() + uses: actions/github-script@v7 + with: + script: | + // The SHA bookmark is embedded in the Gemini comment itself. + // This step is a no-op fallback if Gemini didn't post it. + core.info('Session bookmark handled via comment marker.'); diff --git a/.github/workflows/gemini-triage.yml b/.github/workflows/gemini-triage.yml new file mode 100644 index 000000000..e2e5210db --- /dev/null +++ b/.github/workflows/gemini-triage.yml @@ -0,0 +1,63 @@ +name: '🏷️ Gemini Issue Triage (free-tier)' + +on: + workflow_call: + inputs: + additional_context: + type: string + required: false + prior_prs: + type: string + required: false + default: 'none' + +concurrency: + group: gemini-triage-${{ github.event.issue.number || github.run_id }} + cancel-in-progress: false + +jobs: + triage: + runs-on: ubuntu-latest + permissions: + contents: read + issues: write + pull-requests: write + actions: write + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: Gemini quota gate + id: quota + uses: ./.github/actions/gemini-quota-gate + with: + daily-limit: '18' + pr-number: ${{ github.event.issue.number || '' }} + command: 'triage' + has-backup-key: ${{ secrets.GEMINI_API_KEY_BACKUP != '' }} + + - name: Run Gemini CLI triage + if: steps.quota.outputs.skip != 'true' + uses: google-github-actions/run-gemini-cli@v0 + env: + GEMINI_CLI_TRUST_WORKSPACE: 'true' + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + gemini_api_key: ${{ steps.quota.outputs.use_backup_key == 'true' && secrets.GEMINI_API_KEY_BACKUP || secrets.GEMINI_API_KEY }} + github_issue_number: ${{ github.event.issue.number }} + prompt: | + You are the free-tier agentic triage agent for termux-monorepo. + Read GEMINI.md and AGENTS.md if present. + + Task: Triage the current GitHub issue. + 1. Classify (bug / feature / docs / question / security / duplicate / invalid). + 2. Suggest or apply appropriate labels and a priority (critical/high/medium/low) if the token allows label writes. + 3. If it looks like a duplicate, search existing open issues and link them. + 4. For bugs, ask for minimal reproduction steps if missing. + 5. Note any open agent PRs already addressing this area: + ${{ inputs.prior_prs }} + 6. Post a short, actionable comment summarizing triage. If implementation is needed, recommend labeling `jules` or `@jules` (Jules is the primary builder). + + Additional context: ${{ inputs.additional_context }} + Stay within free-tier good citizenship (concise, no speculative long work). diff --git a/.github/workflows/publish-wiki.yml b/.github/workflows/publish-wiki.yml new file mode 100644 index 000000000..e4e126007 --- /dev/null +++ b/.github/workflows/publish-wiki.yml @@ -0,0 +1,37 @@ +name: Publish wiki + +on: + push: + branches: [master, master-staging] + paths: + - "wiki/**" + - ".github/workflows/publish-wiki.yml" + workflow_dispatch: + +concurrency: + group: publish-wiki + # Match agent-feedback-linear-sync: do not cancel in-flight wiki publishes + # (a workflow_dispatch from one ref must not be dropped by an unrelated push). + cancel-in-progress: false + +permissions: + contents: write + +jobs: + publish-wiki: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Publish wiki/ → GitHub Wiki + uses: Andrew-Chen-Wang/github-wiki-action@v5 + with: + path: wiki + strategy: clone + # First bootstrap only: if the wiki has never been initialized, create a + # dummy page in the Wiki UI, or temporarily set strategy: init once. + token: ${{ github.token }} + preprocess: true + disable-empty-commits: true + commit-message: "wiki: sync from ${{ github.sha }}" diff --git a/.gitignore b/.gitignore index ad7874842..9d6b569fd 100644 --- a/.gitignore +++ b/.gitignore @@ -132,6 +132,9 @@ go/pkg/mod/cache/ .cache/ .cpan/ .deepcli/session_store/ +.nexuscli/session_store/ +.nexuscli/config.json +.nexuscli/tokens/ .hermes/bin/ forensic-indexer/whisper.cpp/models/ forensic-indexer/whisper.cpp/build/ diff --git a/.jules/bolt.md b/.jules/bolt.md new file mode 100644 index 000000000..84193ca7c --- /dev/null +++ b/.jules/bolt.md @@ -0,0 +1,11 @@ +# Bolt's Performance Journal + +Your journal is NOT a log - only add entries for CRITICAL learnings that will help you avoid mistakes or make better decisions. + +## 2026-08-01 - SQLite Insert Loops and Stale Closed Connections in Multi-Agent Indexing +**Learning:** +In the multi-agent's project file indexing system, SQLite connections were being opened and closed per project file, and database inserts (nodes and edges) were performed individually inside nested loops (`cursor.execute`). Even worse, in some source versions of `src/db.py`, the connection block exits before doing the edge inserts, causing silent failures on a closed database connection that went unnoticed due to `except Exception: pass`. +Using a single shared database connection across file walks and batching all inserts with `cursor.executemany` yields a massive performance boost (saving connection/transaction disk I/O) and guarantees transactional integrity. + +**Action:** +Always batch SQL database operations using `executemany` instead of iterating with `execute`. Provide support for passing an optional shared `conn` handle in indexing/utility functions to allow single-connection batch runs across walk loops, while safely closing connections only if opened locally. diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 000000000..2f856a10c --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,48 @@ +# AGENTS.md — Termux monorepo + +Instructions for coding agents (Grok, Claude, Codex, Devin, ChatGPT, local runners). + +## Read first (in order) + +1. **This file** (`AGENTS.md`) +2. [`docs/proposals/registry.yaml`](docs/proposals/registry.yaml) — what is active +3. [`docs/proposals/PROCESS.md`](docs/proposals/PROCESS.md) — post / debate / consensus / close +4. [`docs/PR-SUMMARY-PROCESS.md`](docs/PR-SUMMARY-PROCESS.md) — who may rewrite PR bodies (multi-agent) +5. [`docs/ARCHW1Z-GATE.md`](docs/ARCHW1Z-GATE.md) — repo-gate + termux-smoke +6. [`docs/ARCHW1Z-STATUS.md`](docs/ARCHW1Z-STATUS.md) — living board +7. [`docs/proposals/AGENTIC-PERMISSIONS.md`](docs/proposals/AGENTIC-PERMISSIONS.md) — human-only edges +8. [`docs/CONSENSUS.md`](docs/CONSENSUS.md) — tiers, merit path, CRDT, optional Raft-strict + +Optional: `CLAUDE.md`, `CONTRIBUTING.md`. + +## Hard rules + +- Target **`master-staging`**, not raw `master`, for integration work. +- Both gates must pass before merge: + - `python3 scripts/ci/repo_gate.py` + - `python3 scripts/ci/termux_smoke.py` +- Do not invent work outside `docs/proposals/active//ITEMS.md` — add a row first. +- Cite `Implements: ` on PRs/commits. +- **No** wholesale merge of PR #6 (TER-9) or PR #2 (Rust CI) — see disposition comments. +- **No** Class 3/4 artifacts in git (session stores, browser profiles, tokens). +- Unposted chat is not consensus — write Review log or DEBATE.md. +- PR body rewrites: follow `docs/PR-SUMMARY-PROCESS.md` roster (not a single-agent monopoly). + +## Debate & close + +- Debate: MANIFEST Review log, optional DEBATE.md, linked PR/issue. +- Close: all items terminal + Review log outcome + move `active/` → `closed/` + registry update. +- Full rules: `docs/proposals/PROCESS.md` §§ consensus / closing. + +## Preferred execution loop + +```text +registry.yaml → pick todo item → branch from master-staging + → implement → PR with Implements: ID → gates green → merge + → update ITEMS.md status +``` + +## Security + +Credential rotation and history rewrite require Operator (human) authorization. +See `docs/SECURITY-REMEDIATION.md`. diff --git a/GEMINI.md b/GEMINI.md new file mode 100644 index 000000000..83458c3c1 --- /dev/null +++ b/GEMINI.md @@ -0,0 +1,62 @@ +# GEMINI.md — Context for Gemini CLI (agentic CI/CD) + +You are an autonomous Software Engineering AI teammate for the **termux-monorepo** repository (owner: timerloggedout-spec). + +## Hard rules (must follow) + +- Target **master-staging** for integration work, not raw `master`. +- Both gates must pass before any merge recommendation: + - `python3 scripts/ci/repo_gate.py` + - `python3 scripts/ci/termux_smoke.py` +- Do **not** invent work outside `docs/proposals/active//ITEMS.md` — add a row first if needed. +- Cite `Implements: ` on PRs/commits when applicable. +- **No** Class 3/4 artifacts in git (session stores, browser profiles, tokens, credentials). +- Prefer minimal diffs. Preserve Sentinel security patterns (`0o600` / `0o700` permissions) if touching credential/session paths. +- Respect existing agent orchestration: Jules (async coding agent), CodeRabbit (PR review + autofix), and the auto-Jules trigger on bot feedback. + +## Coordination with Jules and other agents (mandatory) + +1. **Jules is the primary builder** (Cloud VM → branch + PR). You are primarily **triage / review / analysis** unless the user explicitly asks you to implement. +2. Before recommending or making file changes, check **open agent PRs** (Jules, Devin, CodeRabbit autofix, etc.) linked to the same issue or touching the same area. +3. **Do not edit files** already present in another open agent PR for the same issue unless that PR is closed or clearly superseded. +4. Prefer **disjoint file sets**. If overlap is unavoidable, post or request an issue comment: + ``` + + claimed_by: gemini + issue: N + files: path/a, path/b + pr: # + ``` +5. After Jules opens a PR, prefer reviewing it over starting a parallel implementation. +6. Always prefer reviewing prior open PRs listed in the workflow context over duplicating work. + +## Preferred execution loop + +``` +registry.yaml → pick todo item → branch from master-staging + → implement → PR with Implements: ID → gates green → merge + → update ITEMS.md status +``` + +## Repo orientation + +- Multi-agent Termux monorepo (CLI tools, agents, recovery tooling, ArchWiz, DeepSeek/Mistral wrappers, etc.). +- Read `AGENTS.md` first for the full agent contract. +- Security: see `SECURITY.md` and `docs/SECURITY-REMEDIATION.md`. Credential rotation requires human Operator authorization. +- Existing free-tier agentic stack: + - **Jules** — asynchronous coding agent (already wired; auto-summoned on CodeRabbit/Devin reviews). + - **CodeRabbit** — AI PR review with autofix (`.coderabbit.yaml`). + - **Gemini CLI** (this agent) — issue triage, PR review, on-demand `@gemini-cli` collaboration. + +## Capabilities you should use + +- Triage issues: label, prioritize, detect duplicates, ask for missing repro steps. +- Review PRs: correctness, style, security (especially permissions/credentials), alignment with AGENTS.md. +- On-demand: fix well-scoped bugs, write tests, explain code, suggest minimal patches, draft PR bodies that follow repo process. +- When changing code that affects credentials/session caches, enforce strict permissions and never log secrets. + +## Style + +- Be concise and actionable. +- Prefer diffs over long prose. +- If a task is ambiguous or requires Operator (human) authority, say so clearly and stop. diff --git a/README.md b/README.md index 83f882396..7e4c089ef 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,55 @@ +# Project Remote Sandbox Workspace Setup Considerations: +```Gemini-GoogleSearch +To run a Termux environment on an Ubuntu/Linux desktop or server for sandbox testing, you cannot run the exact Termux Android app natively. You must instead replicate its specific Android-based Linux environment (environment variables, paths, and package manager) using containerization, virtualization, or architectural simulation tools. +Here are the best ways to achieve this, ranked from the most lightweight to the most authentic. +## Docker Container (Most Lightweight) +You can run a Docker container that simulates the Termux environment by mirroring its unique paths and packaging layout. + +* Official Termux Docker Image: The Termux project provides an official [termux/termux-docker](https://github.com/termux/termux-docker) repository. +* The Architecture: It runs an Ubuntu base but sets up the /data/data/com.termux/files/usr prefix to match an Android installation. +* The Command: + +docker run -it termux/termux-docker + +* Pros: Starts instantly, uses minimal resources, and matches the file hierarchy perfectly. +* Cons: Runs on your host CPU architecture (usually x86_64). If your target device is an ARM-based phone, compiled binaries won't behave exactly the same way unless you run Docker through qemu-user-static. + +## Anbox / Waydroid (Native Android Containerization) +If you want to run the actual Termux .apk file natively on Linux without a full emulator, use a container-based Android runtime. + +* Waydroid: This is the modern, highly recommended standard for Linux. It runs a full Android system image inside a Linux container (LXC) directly on your kernel. +* Installation: Install Waydroid via your package manager, initialize the Android image, and drop the Termux APK right into it. +* Pros: Near-native performance, zero virtualization overhead, and behaves exactly like a real Android device. +* Cons: Requires a Wayland desktop environment (or a nested Wayland compositor like Weston if you are on X11). + +## Android Studio Emulator (Most Accurate) +If you need to test Android 14+ permissions, background restrictions, awake locks, or severe device constraints, use the official Android Virtual Device (AVD). + +* Setup: Install Android Studio on Ubuntu, open the Device Manager, and create a system image (preferably with Google Play Store to easily install tools, or download the F-Droid Termux APK). +* Pros: Perfect hardware emulation, accurate API lifecycle testing, and complete control over battery, thermal, and network states. +* Cons: Very heavy on RAM and CPU, requires KVM acceleration enabled on Ubuntu. + +## Quick Comparison for Sandbox Testing + +* Choose Docker if you just need to test scripts, compilation pipelines, or CLI tools using Termux prefixes. +* Choose Waydroid if you need to test inter-app interactions on Linux with high performance. +* Choose Android Studio AVD if you need to debug low-level Android system constraints, lifecycle crashes, or awake lock stability. + +Which aspect of your Termux workspace are you looking to test first: CLI script compatibility, network listeners, or Android-specific background constraints? +``` +° Termux specific considerations. +° Termux is the Target Environment 🥇 +-->> Mobile 🥇 Priority + -->> _THEN_ other environments. +## Check all existing code PR's merged since initialization against the Termux Environment. + +## PRIORITY - Establish Baselines; Standards (use Termux Official Docs as well); Reconciliation of potential drift (like: hardcoded PATHS). + +## Develop Workflow for the `termux-smoke`, perpetual branch; and, or delegate to Agents with code spaces or access to them (like @Jules 'Render' Workspaces access; others have access to the same or similar ++>> Add to Roster Considerations). + +## _***HIGH PRIORITY***_ INTIALIZE: `https://github.com/marketplace/render` +Already Installed! + # Monorepo Recovery & RefTemplates Restoration This README documents the filesystem incident, the recovered state from the repository history, and step-by-step recovery & rebuild actions (includes the refTemplates snapshot). It also includes the git-diff consolidation results I performed and concrete recovery commands. diff --git a/archwiz/archwiz.py b/archwiz/archwiz.py index 9e11cd5a2..fa80c740d 100755 --- a/archwiz/archwiz.py +++ b/archwiz/archwiz.py @@ -27,7 +27,12 @@ def banner(): ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝ ╚══╝╚══╝ ╚═╝╚══════╝ """ + N) print(f"{G}\u26a1 ARCHWIZ DASHBOARD \u26a1{N} {time.strftime('%c')}") - print(f"{W}session: {os.getlogin()}@{os.uname().nodename}{N}") + try: + username = os.getlogin() + except Exception: + import getpass + username = getpass.getuser() + print(f"{W}session: {username}@{os.uname().nodename}{N}") print(C + "\u2500" * 60 + N) def get_pipeline_status(): @@ -94,6 +99,7 @@ def main(): try: choice = input(f"{C}>> {N}").strip().lower() except (EOFError, KeyboardInterrupt): + print() # Ensure clean line alignment on interrupt exit break if choice == '1': @@ -376,8 +382,16 @@ def main(): elif choice == 'p': toggle_pipeline() elif choice == '0': - print(G + random.choice(["ArchWiz signing off. Forge well.", "Until next cycle. Stay l33T.", "Dashboard closed. The Forge awaits.", "ArchWiz out. Happy hacking.", "Systems stable. ArchWiz offline."]) + N) break + # Uniform, delightful exit signature for all exits (choice '0', Ctrl+C, Ctrl+D) + print(G + random.choice([ + "ArchWiz signing off. Forge well.", + "Until next cycle. Stay l33T.", + "Dashboard closed. The Forge awaits.", + "ArchWiz out. Happy hacking.", + "Systems stable. ArchWiz offline." + ]) + N) + if __name__ == '__main__': main() diff --git a/bin/lean-monorepo.sh b/bin/lean-monorepo.sh new file mode 100644 index 000000000..c21701202 --- /dev/null +++ b/bin/lean-monorepo.sh @@ -0,0 +1,17 @@ +#!/data/data/com.termux/files/usr/bin/bash +# Lean Termux Monorepo maintenance script +# Run after any significant session or weekly +set -e +cd "$HOME" + +echo "[*] Git prune + gc" +git reflog expire --expire=now --all +git gc --prune=now + +echo "[*] Clear rebuildable caches" +rm -rf .cache/go-build .cache/node-gyp .cache/pip 2>/dev/null || true + +echo "[*] Report" +du -sh .git .cache .npm .cargo .local 2>/dev/null | sort -hr +git count-objects -vH +echo "[+] Lean check complete – $(date)" diff --git a/cli-synthegration/synthegration_index.py b/cli-synthegration/synthegration_index.py old mode 100755 new mode 100644 index 571be231c..c9cde2c87 --- a/cli-synthegration/synthegration_index.py +++ b/cli-synthegration/synthegration_index.py @@ -33,17 +33,17 @@ def citation(self) -> str: return f"{self.session_id}:{self.message_index}:{self.block_index}" def to_wire(self) -> bytes: - """Ultra‑compact binary representation (20 bytes + hash).""" + """Ultra‑compact binary representation (20 bytes + 32 byte hash).""" import struct sid_bytes = self.session_id.encode()[:12].ljust(12, b'\x00') packed = struct.pack('>12sII', sid_bytes, self.message_index, self.block_index) return packed + bytes.fromhex(self.content_hash) - + @classmethod def from_wire(cls, data: bytes) -> 'Pointer': import struct sid_bytes, msg_idx, blk_idx = struct.unpack('>12sII', data[:20]) - content_hash = data[20:28].hex() + content_hash = data[20:52].hex() return cls(sid_bytes.rstrip(b'\x00').decode(), msg_idx, blk_idx, content_hash) class _TaxonomyNode_v1: @@ -105,7 +105,7 @@ def from_live_exports(exports_root: str = None): return idx @staticmethod - + def __init__(self, session_id: str, msg_idx: int, blk_idx: int, content_hash: str, start_line: int = 0, end_line: int = 0): self.session_id = session_id self.message_index = msg_idx @@ -128,17 +128,17 @@ def citation(self) -> str: return f"{self.session_id}:{self.message_index}:{self.block_index}" def to_wire(self) -> bytes: - """Ultra‑compact binary representation (20 bytes + hash).""" + """Ultra‑compact binary representation (20 bytes + 32 byte hash).""" import struct sid_bytes = self.session_id.encode()[:12].ljust(12, b'\x00') packed = struct.pack('>12sII', sid_bytes, self.message_index, self.block_index) return packed + bytes.fromhex(self.content_hash) - + @classmethod def from_wire(cls, data: bytes) -> 'Pointer': import struct sid_bytes, msg_idx, blk_idx = struct.unpack('>12sII', data[:20]) - content_hash = data[20:28].hex() + content_hash = data[20:52].hex() return cls(sid_bytes.rstrip(b'\x00').decode(), msg_idx, blk_idx, content_hash) class TaxonomyNode: @@ -206,7 +206,7 @@ def from_live_exports(exports_root: str = None): for bi, match in enumerate(__import__("re").finditer(r"```(\w+)?\n(.*?)```", content, __import__("re").DOTALL)): lang = (match.group(1) or "text").lower() code_text = match.group(2) - ch = __import__("hashlib").sha256(code_text.encode()).hexdigest()[:16] + ch = __import__("hashlib").sha256(code_text.encode()).hexdigest() all_blocks.append({ "session_id": session_dir.name, "mi": mi, "bi": bi, "ch": ch, @@ -228,7 +228,7 @@ def _ingest_blocks(self, blocks: list): if not ch: code_text = blk.get("code", "") if code_text: - ch = hashlib.sha256(code_text.encode()).hexdigest()[:16] + ch = hashlib.sha256(code_text.encode()).hexdigest() if not ch: continue path = blk.get("path", ["uncategorized"]) @@ -306,7 +306,7 @@ def index_conversation(self, session_id: str, title: str, messages: List[dict], for blk_idx, match in enumerate(re.finditer(r"```(\w+)?\n(.*?)```", content, re.DOTALL)): lang = (match.group(1) or 'text').lower() code = match.group(2) - ch = hashlib.sha256(code.encode()).hexdigest()[:16] + ch = hashlib.sha256(code.encode()).hexdigest() p = Pointer(session_id, msg_idx, blk_idx, ch) path = [lang, project, role] self.taxonomy.add_pointer(p, path) @@ -338,14 +338,16 @@ def search_by_taxonomy(self, term: str, lang: str = None, project: str = None) - for p in self.taxonomy.search(term): if lang and not any(lang in part for part in [p.session_id, '']): continue # simplistic - blob = self.base_dir / 'blobs' / f"{p.content_hash}.blob" - if blob.exists(): - results.append({ - 'pointer': p.to_key(), - 'hash': p.content_hash, - 'code': blob.read_text()[:200] + '...' if len(blob.read_text()) > 200 else blob.read_text(), - 'timestamp': self.time_index.get(p.content_hash, '').isoformat() - }) + blob_path = self.blobs.get(p.content_hash) + if blob_path: + blob = Path(blob_path) + if blob.exists(): + results.append({ + 'pointer': p.to_key(), + 'hash': p.content_hash, + 'code': blob.read_text()[:200] + '...' if len(blob.read_text()) > 200 else blob.read_text(), + 'timestamp': self.time_index.get(p.content_hash, '').isoformat() + }) return results @staticmethod @@ -554,9 +556,9 @@ def reverse_lookup(self, text: str, min_similarity: float = 0.80) -> list: Returns list of (pointer, similarity, snippet).""" import hashlib from difflib import SequenceMatcher - + # First: try exact hash match - text_hash = hashlib.sha256(text.encode()).hexdigest()[:16] + text_hash = hashlib.sha256(text.encode()).hexdigest() if text_hash in self.hash_to_pointer: p = self.hash_to_pointer[text_hash] blob = self.base_dir / 'blobs' / f'{text_hash}.blob' diff --git a/deepcli-tui/tui.py b/deepcli-tui/tui.py index d1043c982..2c2a4a680 100755 --- a/deepcli-tui/tui.py +++ b/deepcli-tui/tui.py @@ -270,38 +270,27 @@ def prompt_session_id(): def show_commands(): """Display available commands with descriptions.""" - console.print(Panel(""" -[bold cyan]/branches[/] List root messages (conversation branches) -[bold cyan]/branchpoints[/] Show fork points (messages with multiple children) -[bold cyan]/more[/] Toggle full tree (uncapped) -[bold cyan]/flat[/] Toggle flat/tree view -[bold cyan]/continue[/] Pick an assistant message to continue from -[bold cyan]/edit[/] Branch from a user message -[bold cyan]/back[/] Return to session selection -[bold cyan]/new[/] Create a new session -[bold cyan]/refresh[/] Clear cache and reload -[bold cyan]/bookmark[/] Save current position -[bold cyan]/bookmarks[/] List saved bookmarks -[bold cyan]/thinking on|off[/] Toggle DeepThink -[bold cyan]/search on|off[/] Toggle web search -[bold cyan]/model instant|expert[/] Switch model + help_text = """[bold cyan]/branches[/] List root messages (conversation branches) +[bold cyan]/branchpoints[/] Show fork points (messages with multiple children) +[bold cyan]/more[/] Toggle full tree (uncapped) +[bold cyan]/flat[/] Toggle flat/tree view +[bold cyan]/continue[/] Pick an assistant message to continue from +[bold cyan]/edit[/] Branch from a user message +[bold cyan]/back[/] Return to session selection +[bold cyan]/new[/] Create a new session +[bold cyan]/refresh[/] Clear cache and reload conversation +[bold cyan]/bookmark[/] Save current position +[bold cyan]/bookmarks[/] List saved bookmarks +[bold cyan]/thinking on|off[/] Toggle DeepThink +[bold cyan]/search on|off[/] Toggle web search +[bold cyan]/model instant|expert[/] Switch model [bold cyan]/attach | [/] Attach files -[bold cyan]/reset[/] Reset to latest assistant -[bold cyan]/cmd[/] Show this reference -""", title="Commands", expand=False)) - input("\nPress Enter to continue...") - help_text = """ -[bold]Commands:[/] -/new, /continue, /edit, /reset, /thinking on|off, /search on|off -/model expert|instant, /attach , /clear-attach -/flat – toggle flat list view -/refresh – clear cache and reload conversation -/more – show full tree (uncapped) -/branches – list conversation branches -/help – this help -exit/quit – leave -""" - console.print(Panel(help_text, title="Help")) +[bold cyan]/clear-attach[/] Clear current attachments +[bold cyan]/reset[/] Reset to latest assistant +[bold cyan]/cmd[/] Show this reference +[bold cyan]/help[/] Show this help menu +[bold cyan]exit / quit[/] Leave the TUI""" + console.print(Panel(help_text, title="📖 DeepCLI TUI Commands Guide", expand=False, border_style="cyan")) input("\nPress Enter to continue...") def main(): diff --git a/docs/AGENTIC-BUILDERS-VS-REVIEWERS.md b/docs/AGENTIC-BUILDERS-VS-REVIEWERS.md new file mode 100644 index 000000000..c213fe56a --- /dev/null +++ b/docs/AGENTIC-BUILDERS-VS-REVIEWERS.md @@ -0,0 +1,78 @@ +# Agentic stack: who builds code vs who reviews + +## Builders (create branches, implement, open PRs) + +| Agent | How it builds | Free tier? | How to trigger on this repo | +|-------|---------------|------------|-----------------------------| +| **Jules** (Google Labs) | Cloud VM → implements → branch + PR | Yes (daily task limits) | Label issue `jules`, or `@jules` comment, or existing auto-Jules on bot PR reviews | +| **Devin** | Session implements → PR / push fix commits | Paid product (check your plan) | `/devin` on issues; Auto-Fix on PR review settings | +| **Gemini CLI plan-execute** | Can write files + PR when workflow has `contents: write` | Yes (AI Studio quota) | `@gemini-cli /approve` path in full upstream examples; our free workflows default to triage/review/invoke | + +**Primary builder for this monorepo: Jules.** + +## Reviewers / triage (comments, labels; limited or no branch creation) + +| Agent | Role | +|-------|------| +| **CodeRabbit** | PR review + autofix suggestions (commit suggestion / autofix) | +| **Gemini CLI** (our triage/review/invoke workflows) | Issue triage, PR comments, on-demand analysis | +| **Devin Review** (without Auto-Fix) | Comments only until Auto-Fix enabled | + +## Coordination (no overlapping files) + +Both Jules and Gemini workflows inject an inventory of **open agent PRs** into their prompts. + +Rules encoded in `GEMINI.md` and Jules invoke prompts: + +1. Scan open PRs from Jules / Devin / CodeRabbit / related issue links before editing. +2. Do not modify files already in another open agent PR for the same issue. +3. Prefer disjoint file sets. +4. After opening a PR, post on the issue: + +```text + +claimed_by: jules|gemini +issue: N +files: path/a, path/b +pr: # +``` + +5. Cross-review: Gemini reviews Jules PRs; Jules auto-summons on bot feedback (`agent-review-auto-jules.yml`). + +## Jules on issues (operational) + +1. Create label `jules` on the repo if missing (Issues → Labels). +2. On any issue: add label **`jules`** → Jules App + workflow respond. +3. Or comment **`@jules `** as OWNER/MEMBER/COLLABORATOR. +4. Optional: set `JULES_API_KEY` for `google-labs-code/jules-invoke` (stronger structured prompts). + +**Workflows must live on the default branch (`master`) for `issue_comment` / `issues` events to fire.** + +## Devin suggested fixes — "auto approve" + +There are two different meanings: + +### A) Auto-apply Devin's code suggestions (recommended) + +In Devin app: + +1. **Settings → Customization → Pull requests → Responding to bots** +2. Choose **Selected only** and allowlist `devin-ai-integration[bot]` (or **All bots**) +3. Enable **Auto-Fix** so Devin pushes fix *commits* to the PR branch instead of only commenting + +On any Devin-authored PR, org admins can also toggle Auto-Fix from the review sidebar. + +GitHub "Commit suggestion" on individual suggestion blocks still needs a human click unless Auto-Fix is on. + +### B) Auto-merge / auto-approve the PR + +That is GitHub branch protection + merge queue / rulesets — **not** something Devin turns on by itself. Keep a human or required checks before merge (repo gates: `repo_gate.py`, `termux_smoke.py`). + +## Gemini 👀 indication + +After workflows are on the **default branch** *and* `GEMINI_API_KEY` is set: + +- `@gemini-cli ...` comments get a **👀** reaction + short "received" comment with Actions run link +- Same pattern as Jules / CodeRabbit + +If you saw no 👀 on a prior comment: either the Gemini workflows were not yet on the branch that receives events, or `GEMINI_API_KEY` was missing (job fails before ack in some paths). diff --git a/docs/AGENTIC-CICD-FREE-TIER.md b/docs/AGENTIC-CICD-FREE-TIER.md new file mode 100644 index 000000000..ef3044d84 --- /dev/null +++ b/docs/AGENTIC-CICD-FREE-TIER.md @@ -0,0 +1,42 @@ +# Agentic CI/CD — Free-tier stack (termux-monorepo) + +This document describes the free-tier-only autonomous / agentic development CI/CD integrations. + +## Active free-tier components + +| Component | Role | Status | Free tier notes | +|-----------|------|--------|-----------------| +| **Jules** (Google Labs) | Async autonomous coding agent (fix bugs, features, tests) | Integrated (`.Jules/`, `agent-review-auto-jules.yml`, `agent-jules-on-issues.yml`) | Introductory daily task limits | +| **CodeRabbit** | AI PR review + autofix | Integrated (`.coderabbit.yaml`) | Free summaries + limited reviews; full free for public/OSS | +| **Gemini CLI GitHub Action** | Issue triage, PR review, `@gemini-cli` on-demand teammate | Integrated | Google AI Studio free quota | +| **GitHub MCP Server** | Tool access for agents (issues/PRs/code) | Usable via Gemini settings | Fully free / OSS | +| Render (Marketplace) | Deploy / self-healing hooks (noted in README) | Already installed per README | Free tier exists | + +## How the loop works + +1. Issue opened → Gemini triage (labels, priority, duplicate check). +2. PR opened → CodeRabbit review + Gemini review comment. +3. Bot feedback (CodeRabbit/Devin/…) → existing `agent-review-auto-jules.yml` posts `@jules` and optionally invokes Jules API. +4. Human or bot comments `@gemini-cli ` → Gemini on-demand invoke (👀 ack). +5. Human comments `@jules ` or labels `jules` → Jules on-issues workflow + App path. +6. Jules works asynchronously in a Cloud VM and opens/updates PRs for review. +7. **Coordination**: both Jules and Gemini prompts receive an inventory of open agent PRs and must avoid overlapping files; they post `` markers when claiming work. + +All pieces above have a free tier. Paid upgrades (Google AI Pro/Ultra for higher Jules limits, CodeRabbit Pro, etc.) are optional. + +## Required secrets / variables (user action) + +- `GEMINI_API_KEY` — Google AI Studio free key (required for Gemini workflows) +- `JULES_API_KEY` — optional; enables structured `jules-invoke` (App + `@jules` comment still work without it) +- Workflow permissions: Read and write for GITHUB_TOKEN (Settings → Actions → General) + +## Activation note + +GitHub only runs `issues` / `issue_comment` workflows from the **default branch** (`master`). The agentic workflow files must exist there for `@jules` / `@gemini-cli` mentions to respond. + +## Security notes + +- Gemini workflows restrict `@gemini-cli` comment triggers to OWNER/MEMBER/COLLABORATOR. +- Jules mention triggers similarly restricted. +- Fork PRs are excluded from automatic review triggers. +- GEMINI.md encodes the same hard rules as AGENTS.md (no secrets in git, master-staging, gates) plus coordination rules. diff --git a/docs/ARCHW1Z-GATE.md b/docs/ARCHW1Z-GATE.md new file mode 100644 index 000000000..aeb4d6295 --- /dev/null +++ b/docs/ARCHW1Z-GATE.md @@ -0,0 +1,119 @@ +# ArchW1z Gate Spine + +> **Status:** LIVE +> **Branches:** `master-staging` (hygiene) · `termux-smoke` (runtime surface) + +## Two gates, one constitution + +``` +repo-gate hygiene / portability / secrets / ratchet + ↓ +termux-smoke agent runtime surface is alive + ↓ +language / provider gates (later) + ↓ +master +``` + +Every change that wants to reach `master` should survive both gates. + +--- + +## Gate 1 — repo-gate (on `master-staging`) + +```bash +python3 scripts/ci/repo_gate.py +``` + +Workflow: `.github/workflows/repo-gate.yml` +Docs: this file + original Critical-Eval recommendation + +### Design rules (non-negotiable) + +- **stdlib only** — no pip, no cargo, no node, no network +- **index-based** — reads `git ls-files`, never the working tree +- **device-friendly** — same command works on Termux and in CI +- **HARD checks** scoped to *changed* files +- **RATCHET** on whole-repo debt counters (debt may only shrink) + +### What it enforces + +| Check | Scope | Failure mode | +|-------|-------|--------------| +| Python / shell / JSON syntax | changed files | HARD | +| Portable symlinks | changed symlinks | HARD | +| No new session artifacts | changed paths | HARD | +| No browser credential stores | changed paths | HARD | +| No committed backups | changed paths | HARD | +| High-confidence secrets | changed content | HARD | +| Debt counters | whole index | RATCHET | + +Baseline: `scripts/ci/baseline.json` +Lower debt with: `python3 scripts/ci/repo_gate.py --write-baseline` + +--- + +## Gate 2 — termux-smoke (on `termux-smoke`) + +```bash +python3 scripts/ci/termux_smoke.py +python3 scripts/ci/termux_smoke.py --with-optional # agent surfaces +python3 scripts/ci/termux_smoke.py --json # for agents +``` + +Workflow: `.github/workflows/termux-smoke.yml` +Full docs: [`docs/TERMUX-SMOKE.md`](TERMUX-SMOKE.md) + +### What it enforces (required) + +- Python ≥ 3.9 +- Gate scripts present and compile +- git on PATH +- bash on Termux (soft elsewhere) +- writable TMPDIR + +### Optional probes (`--with-optional`) + +- deepcli / multi-ai launcher compile (no network) +- archwiz sample modules compile +- termux-api presence note + +--- + +## Integration order (ArchW1z) + +``` +P0 credential / session-store containment + repo-gate + termux-smoke ← now live + deterministic configuration + session SSOT schema + +P1 dispatch event boundary + DeepForge launcher resolver + provider capability contract + content-addressed index correctness + +P2 DeepForge ↔ Rust protocol + MCP + harvesting / search expansion + multi-provider parity +``` + +## How to land work + +1. Branch off `master-staging` or `termux-smoke`. +2. Smallest atomic change that preserves invariants. +3. Push → both gates run on PR. +4. Green on both → candidate for `master`. + +Do **not** treat `master` as the integration point for large TER-* +branches. + +## Next ratchet targets + +- `tracked_session_artifacts` → 0 +- `tracked_browser_credential_stores` → 0 +- `tracked_browser_profile_files` → 0 +- `tracked_backup_files` → 0 +- Reduce `device_absolute_symlinks` diff --git a/docs/ARCHW1Z-OPERATOR-CHECKLIST.md b/docs/ARCHW1Z-OPERATOR-CHECKLIST.md new file mode 100644 index 000000000..e7777f00f --- /dev/null +++ b/docs/ARCHW1Z-OPERATOR-CHECKLIST.md @@ -0,0 +1,30 @@ +# Operator Checklist — Full Agentic Mode + +You only need to complete items marked **HUMAN**. Everything else is agent-owned. + +## One-time setup (HUMAN) + +- [ ] **GitHub App permissions** on `timerloggedout-spec/termux-monorepo`: + - [ ] Contents: Read and write + - [ ] Pull requests: Read and write + - [ ] Checks + Commit statuses: Read and write + - [ ] Issues: Read and write + - [ ] Workflows: Read and write + - [ ] Administration: Read +- [ ] **Branch protection on `master`**: require `repo gate` + `termux smoke` checks; allow the App to merge when green +- [ ] **`master-staging`**: leave soft/unprotected so agents iterate +- [ ] **ChatGPT GitHub App** (optional): same permissions if ChatGPT should execute too + +## Security (HUMAN + agent) + +- [ ] **HUMAN:** Rotate credentials that appeared in tracked session/browser material +- [ ] Agent: verify tips clean via `repo_gate.py` +- [ ] **HUMAN:** Authorize history-rewrite window (comment on #3) +- [ ] Agent: execute rewrite under that authorization + +## Ongoing (agent default) + +- [x] repo-gate live +- [x] termux-smoke live +- [x] Proposal registry + PROCESS + CONSENSUS +- [x] Docs promotion branch to master diff --git a/docs/ARCHW1Z-STATUS.md b/docs/ARCHW1Z-STATUS.md new file mode 100644 index 000000000..34027b63f --- /dev/null +++ b/docs/ARCHW1Z-STATUS.md @@ -0,0 +1,44 @@ +# ArchW1z Status Board + +> Living recon. Updated as gates land and PRs resolve. +> Sources: ChatGPT Critical-Eval, ChatGPT-initial, ChatGPT_droidApp, live branch/PR inventory. + +**Last update:** 2026-08-04 — docs promotion to master in progress + +--- + +## Gate spine (LIVE on master-staging) + +| Gate | Status | Command | +|------|--------|--------| +| **repo-gate** | ✅ LIVE | `python3 scripts/ci/repo_gate.py` | +| **termux-smoke** | ✅ LIVE (#11 squash-merged) | `python3 scripts/ci/termux_smoke.py` | +| language / provider | ⏳ later | — | + +``` +repo-gate → termux-smoke → language/provider → master +``` + +--- + +## Specs landed + +| Doc | Purpose | +|------|--------| +| `docs/ARCHW1Z-GATE.md` | Two-gate spine | +| `docs/TERMUX-SMOKE.md` | Smoke gate details | +| `docs/ARCHW1Z-STATUS.md` | This board | +| `docs/CONSENSUS.md` | Tiers, merit, CRDT, Raft-strict | +| `docs/schemas/session-ssot.md` | TER-10 Session SSOT | +| `docs/schemas/provider-capabilities.md` | Capability registry | +| `docs/SECURITY-REMEDIATION.md` | PR #3 A+B+C checklist | + +--- + +## Merge rule + +1. Target **`master-staging`** for code +2. **repo-gate** + **termux-smoke** green +3. No unresolved critical review threads +4. Security: A + B + C +5. Process docs may promote to **`master`** for discoverability (this promotion) diff --git a/docs/CONSENSUS.md b/docs/CONSENSUS.md new file mode 100644 index 000000000..df72d844e --- /dev/null +++ b/docs/CONSENSUS.md @@ -0,0 +1,188 @@ +# Consensus (ArchW1z) + +How multi-agent decisions become shared truth — and when **no vote** is required. + +Related: `docs/proposals/PROCESS.md` · `docs/PR-SUMMARY-PROCESS.md` · `AGENTS.md` · `docs/ARCHW1Z-GATE.md` + +--- + +## Tier summary (read this first) + +```text +Tier 0 MERIT Branch, implement, run gates — no social vote +Tier 1 DRIVER P2–P3 claims: driver + evidence in the log +Tier 2 LIGHT P1: driver + evidence; 1 review cycle then silence OK +Tier 3 QUORUM P0 claims / P0 PR body: driver + distinct second mind OR Operator +Tier 4 OPERATOR Credentials, force-push, history rewrite — human only +Tier R RAFT-STRICT Optional profile for named irreversible subjects (see §5) +``` + +| Tier | Name | Needs vote? | Commit when | +|------|------|-------------|-------------| +| **0** | Merit | No | Gates/tests green on a branch; exploration allowed to fail | +| **1** | Driver | Minimal | Driver posts evidence; silence OK | +| **2** | Light | Soft | Driver + evidence; after one clear ask, silence ≈ no objection | +| **3** | Quorum | Yes | Driver + ≥1 other Reviewer **or** Operator | +| **4** | Operator | N/A | Explicit Operator comment; agents cannot majority this | +| **R** | Raft-strict | Yes (formal) | Term + single driver + majority of fixed voter set + log commit | + +**Default path for code:** Tier 0 → open PR → Tier 1–2 disposition → merge when gates green. +**Default path for irreversible security claims:** Tier 3–4 (optionally R). + +--- + +## 1. Three paths (not one protocol) + +### A. Merit path (preferred for code) + +Skip social consensus. Prove accuracy on a branch: + +1. Branch from `master-staging` +2. Implement +3. `python3 scripts/ci/repo_gate.py` / `termux_smoke.py` +4. Open PR with honest Status +5. Land when checks green and disposition ≠ 🔴 + +**Merit answers “does it work?”** Votes do not replace gates. + +### B. Social path (claims & sequencing) + +Used when asserting shared process truth: proposal accepted/closed, P0 “done”, disposition Status, summary of security scope. + +**Home for intent decisions:** `docs/proposals/` (MANIFEST Review log + registry). +**Projection for landing:** PR body Status + comments. + +### C. Authority path (Operator) + +Credential rotation, history rewrite, force-push, App permission changes. Not subject to agent majority. + +--- + +## 2. Where decisions live + +| Decision type | Primary log | Model | +|---------------|-------------|--------| +| Proposal accept / item done / close | MANIFEST + `registry.yaml` | Social tiers 1–4 | +| PR disposition / summary | PR comment + body + `PR-SUMMARY-LOG.md` | Thin projection of tiers | +| Code correctness | CI checks + branch commits | Merit (Tier 0) | +| Irreversible git history | Operator comment on issue/PR | Tier 4 (+ optional R) | + +Proposals own **what we intend**. Branches own **what we measured**. Consensus attaches to **claims**, not to **existence of a branch**. + +--- + +## 3. Ballot labels (social path) + +```text +VOTE: accept — support commit of the proposed decision +VOTE: reject — block; state reason +VOTE: abstain — present; not counting toward quorum +VOTE: summary OK — ack for P0 PR body rewrite only +``` + +One vote per voter id per **term**. Log entry required — unposted chat does not count. + +**Terms:** `subject-id/n` (e.g. `pr-3/summary/2`, `ce-13/history-rewrite/1`). One driver per term; conflicts open `n+1`. + +--- + +## 4. Subject → tier map + +| Subject | Tier | +|---------|------| +| Create branch / push experiments | **0** | +| P2–P3 item `done` | **1** | +| P1 item / ordinary PR disposition | **2** | +| Proposal `accepted` (non-author review) | **3** (or Operator self-accept logged) | +| P0 item `done`, P0 PR body rewrite | **3** | +| Merge to `master-staging` | **0 checks** + disposition not 🔴 | +| Promote to `master` | **0 checks** + may require Operator | +| Force-push / history rewrite / credential rotation | **4** | +| Optional formal close of high-stakes proposal | **R** if enabled for that subject | + +--- + +## 5. Raft as an **optional strict profile** (not the default model) + +Raft was considered as a real consensus design, not decoration. + +**Keep if using profile R on a named subject:** + +- Single driver (leader) per term +- Monotonic terms; ignore stale term votes +- Majority of a **fixed voter set** declared in the term open +- Decision committed only in the append-only log + +**Do not use Raft as the global control plane:** membership churn (agents offline), unequal authority (Operator ≠ peer), and multi-subject concurrency make a single cluster Raft a poor fit. Per-subject Tier 3 already captures most of the value. + +Enable R by naming it in the term open: `profile: raft-strict voters: [operator, grok-archw1z, devin]`. + +--- + +## 6. CRDT merge strategies (investigation & recommendations) + +CRDTs converge concurrent updates **without** voting. Use them for **state that should merge**, not for **authorization**. + +### Strategy cheat sheet + +| Strategy | Behavior | Use here | Avoid for | +|----------|----------|----------|-----------| +| **G-Set** | Add-only; merge = union | Observed bot findings, “seen commit SHAs” | Anything that must be revoked cleanly | +| **OR-Set** (observed-remove) | Add/remove with unique tags; concurrent add∥remove keeps add if not observed | Agent roster membership, label sets, item id sets | Security “credential rotated” flags | +| **2P-Set** | Remove wins forever | Tombstones for deleted paths in indexes | Re-adding same id after remove | +| **LWW-Register** | Highest timestamp wins | Non-critical UI prefs, last smoke run timestamp | Disposition Status, proposal state | +| **LWW-Element-Set** | Per-element timestamps | Soft metadata | P0 claims | +| **MV-Register** | Keep all concurrent values | Surface conflicts for a human/agent to pick | Silent auto-resolve of Status | +| **G/PN-Counter** | Merge by max per replica | Ratchet debt counters (aligns with repo-gate baseline) | Voting tallies as authority | +| **RGA / sequence CRDTs** | Concurrent text edit | Collaborative DEBATE.md drafts (optional) | MANIFEST binding outcomes | +| **Three-way / MRDT (Git-like)** | LCA + two tips → typed merge | Branch merges, registry field merges with explicit rules | Pretending merge = approved claim | + +### Recommended bindings for this monorepo + +| Data | Merge strategy | +|------|----------------| +| **Git branches / commits** | Git’s own history (three-way merge + gates) — already merit path | +| **`registry.yaml` item rows** | Field-level: `status` is **not** LWW — requires Tier 1–3 social commit; `evidence` links are G-Set (union) | +| **PR-SUMMARY-LOG** | Append-only G-Set of rows (never rewrite history of the log) | +| **Review log entries** | Append-only; concurrent reviews = union (OR-Set of note ids) | +| **Disposition Status (🟢🟡🔴⚪)** | **Single-value register under social tier** — concurrent Status → MV-Register until Tier 2–3 resolves (do **not** LWW) | +| **repo-gate baseline counters** | Ratchet = min/monotonic decrease only (domain-specific; not classic PN-Counter up) | +| **Session SSOT / agent memory** (future) | OR-Set + confidence-LWW for soft memories; **never** CRDT-merge secrets into git | +| **DEBATE.md** | Optional RGA/LWW for prose; binding outcome still copied into Review log via vote | + +### Rule of thumb + +```text +CRDT → concurrent facts that should converge without a meeting +Vote → authorization to treat something as shared institutional truth +Gate → mechanical proof about a concrete revision +``` + +LWW is the wrong default for **Status** and **proposal state**: wall-clock or agent-local clocks race; security claims must not flip because a slower agent wrote later. + +--- + +## 7. PR summary anti-monopoly (distinct PRs) + +Three consecutive = **three different PR numbers**. Iterating the same PR is always OK. Details: `docs/PR-SUMMARY-PROCESS.md`. + +--- + +## 8. Bots + +Bots (Devin, CodeRabbit, ecc-tools, …) emit **evidence**. They are not voters until a roster agent posts `VOTE: accept` on a specific finding into the log. See earlier triage map in git history / `PR-SUMMARY-PROCESS.md` for tooling options. + +--- + +## 9. Quick reference + +```text +Explore on a branch → Tier 0 (no vote) +Claim “P2 done” → Tier 1 +Claim “P1 ready to merge” → Tier 2 + gates +Claim “P0 / security done” → Tier 3 +Rotate keys / rewrite history → Tier 4 +Optional formal subject → profile raft-strict +Merge concurrent facts → CRDT (OR-Set / G-Set / counters) +Merge authority → never CRDT; use tiers +``` diff --git a/docs/PR-SUMMARY-LOG.md b/docs/PR-SUMMARY-LOG.md new file mode 100644 index 000000000..38aeedfaf --- /dev/null +++ b/docs/PR-SUMMARY-LOG.md @@ -0,0 +1,17 @@ +# PR Summary Rewrite Log + +Append-only. Process: `docs/PR-SUMMARY-PROCESS.md` · Votes: `docs/CONSENSUS.md`. + +**Counter rule:** consecutive limit counts **distinct PR numbers** only. + +| date (UTC) | PR | editor | status set | notes | +|------------|-----|--------|------------|-------| +| 2026-08-02 | #12 | grok-archw1z | 🟡 conditional | ECC bundle; Devin blockers | +| 2026-08-02 | #3 | grok-archw1z | ⚪ draft / A-only | A≠A+B+C | +| 2026-08-02 | #2 | grok-archw1z | 🔴 NO-GO | Expand or close | +| 2026-08-02 | #6 | grok-archw1z | 🔴 NO-GO wholesale | Extract-only | +| 2026-08-02 | #5 | grok-archw1z | 🟡 conditional | | +| 2026-08-02 | #9 | grok-archw1z | 🟡 conditional | DeepForge | +| 2026-08-02 | #10 | grok-archw1z | 🟢 candidate | curl_cffi | +| 2026-08-02 | #7 | grok-archw1z | 🟡 conditional | Termux MCP | +| 2026-08-02 | #8 | grok-archw1z | 🟡 conditional | TER-11 scaffold | diff --git a/docs/PR-SUMMARY-PROCESS.md b/docs/PR-SUMMARY-PROCESS.md new file mode 100644 index 000000000..a07ea9f8d --- /dev/null +++ b/docs/PR-SUMMARY-PROCESS.md @@ -0,0 +1,42 @@ +# PR Summary Rewrite Process + +Keep PR titles/bodies honest, scannable, and multi-agent — **not** a single-agent monologue. + +**Consensus / votes:** [`docs/CONSENSUS.md`](CONSENSUS.md). + +## When to rewrite + +Rewrite when Status drifts, disposition missing, wrong base, generator noise, scope lies, security incomplete, or body is a one-liner. + +**Do not** rewrite solely for style if Status, scope, tests, and links are accurate. + +## Standard body skeleton + +```markdown +## Summary +One paragraph intent. + +**Status:** 🟢 merge-ready | 🟡 conditional | 🔴 NO-GO | ⚪ draft / blocked +**Disposition:** one line (who + why) +**Base:** `master-staging` (preferred) | `master` +**Implements:** CE-xx / TER-n (if any) + +### Changes +### Non-goals +### Validation +### Follow-ups +### Agent notes +``` + +## Roles + +| Role | May rewrite body? | +|------|-------------------| +| author | Own PR | +| summary-editor (grok-archw1z, devin, chatgpt, claude/codex) | Yes | +| reviewer / coderabbit | Comment only | +| operator | Always | + +**Anti-monopoly:** three consecutive = three **distinct PR numbers**. Same-PR iteration always OK. + +See full roster and P0 rules in git history / master-staging copy if truncated. diff --git a/docs/SECURITY-REMEDIATION.md b/docs/SECURITY-REMEDIATION.md new file mode 100644 index 000000000..3a862d664 --- /dev/null +++ b/docs/SECURITY-REMEDIATION.md @@ -0,0 +1,31 @@ +# Security Remediation Sequence (PR #3 lineage) + +> **Status:** REQUIRED before treating session-store work as done + +## Three independent guarantees + +| | Guarantee | PR #3 today | +|---|-----------|-------------| +| **A** | Current tree is clean | Partially (tip removal) | +| **B** | Future commits cannot reintroduce secrets | Partially (gitignore + gate) | +| **C** | Historical reachable objects remediated | **Not done** | + +> branch-tip removal ≠ history remediation + +## P0 sequence + +1. **Rotate** credentials that appeared in tracked session material +2. **Verify** tips free of Class 3/4 paths +3. **Keep** secret-pattern + path-class checks in repo-gate +4. **History rewrite** only after deliberate export if required +5. **Force-push** only with explicit Operator approval + +## Data classification + +| Class | Content | Gate | +|-------|---------|------| +| 0 | Source, docs, schemas | allow | +| 1 | Derived indexes, hashes | allow | +| 2 | Conversation content, prompts | allowlist | +| 3 | Cookies, session stores, tokens | **HARD FAIL** | +| 4 | Browser profiles, private SSH, local env | **HARD FAIL** | diff --git a/docs/TERMUX-SMOKE.md b/docs/TERMUX-SMOKE.md new file mode 100644 index 000000000..b1a60c167 --- /dev/null +++ b/docs/TERMUX-SMOKE.md @@ -0,0 +1,34 @@ +# Termux Smoke Gate — Agentic runtime surface + +> **Branch:** `termux-smoke` +> **Script:** `scripts/ci/termux_smoke.py` +> **Workflow:** `.github/workflows/termux-smoke.yml` + +## Position in the spine + +``` +repo-gate → termux-smoke → language gates → provider tests → master +``` + +## How to run + +```bash +python3 scripts/ci/termux_smoke.py +python3 scripts/ci/termux_smoke.py --with-optional +python3 scripts/ci/termux_smoke.py --json --with-optional +``` + +## What it checks + +| Check | Required | +|-------|----------| +| python-version ≥ 3.9 | yes | +| repo-layout (gate scripts + docs) | yes | +| repo-gate compiles | yes | +| smoke self-compiles | yes | +| git on PATH | yes | +| bash on PATH | yes on Termux, soft elsewhere | +| writable TMPDIR | yes | +| deepcli / multi-ai / archwiz compile | optional | + +Full detail: see `master-staging` copy if this summary is abbreviated. diff --git a/docs/ops/AGENT_AUTO_RESOLVE.md b/docs/ops/AGENT_AUTO_RESOLVE.md new file mode 100644 index 000000000..81c597985 --- /dev/null +++ b/docs/ops/AGENT_AUTO_RESOLVE.md @@ -0,0 +1,15 @@ +# Agent auto-resolve (LIVE on master) + +**Agent:** [Grok](https://x.com/grok) + +Workflows on **default branch `master`** so `pull_request_review` / review_comment events fire immediately. + +| Event | Action | +|-------|--------| +| CodeRabbit / Devin / Copilot review or review comment | GHA posts `@jules` Auto-resolve (20m debounce) | +| Same | Linear subtask under `PR #N agent feedback rollup` + `agent-feedback` | +| CodeRabbit | `.coderabbit.yaml` autofix enabled | + +Secrets: `LINEAR_API_KEY` (subtasks), optional `JULES_API_KEY`. + +Signed-off-by: Grok diff --git a/docs/ops/JULES_ADE_PROJECT.md b/docs/ops/JULES_ADE_PROJECT.md new file mode 100644 index 000000000..818860abf --- /dev/null +++ b/docs/ops/JULES_ADE_PROJECT.md @@ -0,0 +1,14 @@ +# Jules ADE Project — Fully Agentic Development Environment + +> **HOME:** `timerloggedout-spec/termux-monorepo` +> **Human role:** Tier 4 only + temporary termux-smoke cherry-pick until automated + +``` +repo-gate (master-staging) → termux-smoke → master +``` + +Agents own execution. Scavenge Jules/Antigravity forks as templates (depth-1 sparse). Never vendor full histories into HOME. + +Focus: gates green, deepcli + multi-ai-cli, Termux MCP, Jules MCP/skills, Antigravity patterns, Session SSOT + credential hygiene. + +Full roster: `docs/ops/JULES_REPO_ROSTER.yaml` diff --git a/docs/ops/JULES_REPO_ROSTER.yaml b/docs/ops/JULES_REPO_ROSTER.yaml new file mode 100644 index 000000000..6a068daa8 --- /dev/null +++ b/docs/ops/JULES_REPO_ROSTER.yaml @@ -0,0 +1,19 @@ +version: 1 +updated_at: "2026-08-03T20:00:00Z" +updated_by: grok-archw1z + +home: + owner: timerloggedout-spec + repo: termux-monorepo + role: first_class_runtime + branches: + integration: master-staging + smoke: termux-smoke + production: master + gates: [repo-gate, termux-smoke] + +policy: + scavenge: depth-1-sparse-metadata + human_only: [credential_rotation, history_rewrite, app_permissions] + human_temporary: [termux-smoke_cherry_pick_until_automated] + agent_owns: [branch, implement, pr, merge_to_master_staging, gates] diff --git a/docs/ops/LEAN_TERMUX_MONOREPO.md b/docs/ops/LEAN_TERMUX_MONOREPO.md new file mode 100644 index 000000000..37fd2be31 --- /dev/null +++ b/docs/ops/LEAN_TERMUX_MONOREPO.md @@ -0,0 +1,94 @@ +# Lean Termux Monorepo – Operating Rules + +> **Purpose**: Keep the local Termux working tree small, fast, and restorable while the bulk of history, models, exports, and session data lives on GitHub + remote storage (Google Drive / rclone). + +## Core Principles + +1. **Local = Source + Config + Active Work only** +2. **Never commit**: models, `.venv`, `node_modules`, large session dumps, exports, binary weights +3. **Pull / cherry-pick → smoke-test → clean immediately** +4. **Heavy artefacts live on remote storage or as gitignored archives** + +## Mandatory Workflow for Any Agent / Human + +### Before starting work +```bash +cd "$HOME" +git status -sb +du -sh .git .cache .npm .cargo .local 2>/dev/null | sort -hr +``` + +### Pulling or cherry-picking a branch +```bash +# Preferred: fetch only what you need +git fetch origin +git cherry-pick # or +git checkout -b temp/ origin/ + +# Smoke test (Termux-specific) +# … run your tests … + +# Immediately after results are known: +git checkout master +git branch -D temp/ # delete local branch +git reflog expire --expire=now --all +git gc --prune=now +``` + +### After every significant session +```bash +"$HOME/bin/lean-monorepo.sh" # or the commands below +``` + +### Weekly / after heavy work +```bash +git reflog expire --expire=now --all +git gc --prune=now --aggressive +rm -rf .cache/go-build .cache/node-gyp .cache/pip 2>/dev/null || true +# Optionally clear npm / cargo via termux-sync if not needed +``` + +## Forbidden in the working tree (must be gitignored) + +``` +synthegration_exports/ +forensic-indexer/whisper.cpp/models/ +CTranslate2/ # treat as external clone if needed +.deepcli/session_store/ # archive old sessions to Drive +.codex/cache/ +*.onnx *.gguf *.pt *.safetensors *.bin +ARCHIVE_STAGING/ +.venv/ venv/ node_modules/ __pycache__/ +``` + +## Restoration Strategy + +- Source of truth: `origin/master` + feature branches on GitHub +- Models / large exports / old sessions → Google Drive (or rclone) +- Submodules stay as submodules; never vendor large third-party trees +- If a tool needs a model: download on-demand into a gitignored path + +## Agent Checklist (copy-paste into any agent prompt) + +``` +[LEAN RULES] +1. Never leave temporary branches. +2. After cherry-pick / test → delete branch + git gc --prune=now. +3. Never add model weights, session dumps, or exports to the index. +4. Run lean-monorepo.sh (or equivalent) before ending the session. +5. Prefer sparse-checkout or external clones for anything >50 MB. +``` + +## Quick Health Check + +```bash +du -sh .git +git count-objects -vH +git status -sb +``` + +**Target**: `.git` < 200 MB under normal use. + +--- + +*Last updated: 2026-08-03 – post 1.8 GB → 137 MB git cleanup* diff --git a/docs/proposals/AGENTIC-PERMISSIONS.md b/docs/proposals/AGENTIC-PERMISSIONS.md new file mode 100644 index 000000000..d94cae031 --- /dev/null +++ b/docs/proposals/AGENTIC-PERMISSIONS.md @@ -0,0 +1,27 @@ +# Why You Still Have To Do Anything — Agentic Permissions + +## What the agent CAN do + +Read repo/PRs, create branches, create/update files, open PRs, comment, merge when allowed, retarget PR base, submit reviews. + +## What still needs YOU (human) + +| Blocker | Why | +|---------|-----| +| Credential rotation | Secrets on device / provider dashboards | +| History rewrite + force-push | Destructive; needs explicit Operator approval | +| Protected branch rules on master | Settings may be outside app scope | +| GitHub App permission gaps | Contents/PRs/Checks/Workflows | +| Device-side Termux state | Agent is cloud connector, not phone | +| Provider API keys / browser logins | Interactive / ToS-bound | + +## Minimum permission checklist + +- [ ] Contents R/W +- [ ] Pull requests R/W +- [ ] Checks + Commit statuses R/W +- [ ] Workflows R/W +- [ ] Issues R/W +- [ ] Administration Read + +Human intervenes only for: credential rotation, destructive history ops, first-time permission grants. diff --git a/docs/proposals/ChatGPT-initial.md b/docs/proposals/ChatGPT-initial.md new file mode 100644 index 000000000..97bcef97c --- /dev/null +++ b/docs/proposals/ChatGPT-initial.md @@ -0,0 +1,470 @@ +😄 Hah. We hit the GitHub connector's permission boundary. + +The connector can read your repository, but when I attempted to commit REPO_REVIEW_NEXT_ACTIONS.md, GitHub returned: + +> 403 — Resource not accessible by integration + + + +So I cannot push commits into this repository from this connector, even though I can inspect it. + + +--- + +Deeper Review + +After reading the latest README, here's my assessment. + +⭐ Overall: 9.2 / 10 + +This isn't a typical GitHub project anymore. + +It's becoming a Termux operating environment. + +That's a different class of repository. + +Instead of documenting software, you're documenting an ecosystem. + +That changes how I'd architect everything. + + +--- + +Biggest Strength + +The README now answers three questions simultaneously: + +What happened? + +What exists? + +How do I recover? + + +That was missing before. + +The latest commit successfully brought those together. + + +--- + +Biggest Weakness + +It is becoming... + +> README.md + +Incident Report + +Disaster Recovery + +Architecture + +Inventory + +Operations Manual + +Git Notes + +Historical Timeline + + + + +🤣 + +That's awesome... + +...until six months from now. + +Then it'll be 2,000+ lines. + + +--- + +I'd turn it into a cockpit. + +Instead of one huge README: + +README.md + +becomes + +README.md + +↓ + +README +│ +├── Quick Start +├── Repository Overview +├── Active Projects +├── Architecture +├── Documentation +└── Recovery + +where + +docs/ + +RECOVERY.md + +ARCHITECTURE.md + +PROJECT_INDEX.md + +OPERATIONS.md + +AI_HANDOFF.md + +CHANGELOG.md + +become first-class documents. + + +--- + +Biggest Architectural Improvement + +I noticed something interesting. + +Almost every project ultimately serves one of four purposes. + +AI + +DeepCLI + +Harmonizer + +Multi-Agent + +CLI-Synthegration + + +--- + +Knowledge + +ArchWiz + +Mapper + +Workspace + +LLM Map + +Indices + + +--- + +Runtime + +Shell + +Deploy + +Cleanup + +Patch + +Config + + +--- + +Projects + +Exchange + +Colab + +Claude + +Applied + +Research + + +--- + +I'd physically organize around those four pillars. + +Not necessarily moving directories— + +but creating logical documentation categories. + +That alone makes onboarding dramatically easier. + + +--- + +I also noticed... + +You're documenting... + +what exists + +instead of + +why it exists + +That's a common evolution point. + +Every directory should eventually answer: + +> Why does this exist? + + + +not merely + +> This exists. + + + + +--- + +What I want to build next + +This is where I think things could get really interesting. + +I'd add an automatic repository intelligence system. + +Every commit updates: + +PROJECT_INDEX.md + +MODULE_GRAPH.md + +DEPENDENCY_GRAPH.md + +COMMAND_INDEX.md + +DIRECTORY_HEALTH.md + +ARCHITECTURE_MAP.md + +generated automatically. + +No manual editing. + +Ever. + + +--- + +Then... + +ArchWiz becomes the source of truth. + +README becomes human-friendly. + +Generated docs become machine-friendly. + +AI gets context. + +Humans get navigation. + +Everyone wins. + + +--- + +Long-Term Vision + +I honestly don't see this repo as just a "Termux monorepo." + +I see it evolving into something closer to a personal operating system for AI-assisted development on Android: + +DeepSeek/LLM interfaces + +Multi-agent orchestration + +Workspace indexing + +Provenance tracking + +Recovery automation + +Project management + +Development tooling + +Knowledge graph + + +Those are coherent pieces of a larger platform. + +I'd be excited to keep helping shape it. I think there's a lot of room to make it both more powerful and much easier to navigate without sacrificing the flexibility you've built. + +Challenge accepted. 😄 + +I checked the connector capabilities again. Here's the current state: + +✅ I can inspect repositories, commits, diffs, workflow status, and repository metadata. + +✅ I can compare commits and review patches. + +✅ I can create or update files only if the GitHub App installed on the repository has write permission. + +❌ Your repository currently returns 403 "Resource not accessible by integration" for write operations, so I cannot push directly or open a PR from this conversation until the app has write access. + + +So "make it happen" is technically possible—but the missing piece is repository permissions, not tooling. + +What I found + +Your recent README isn't just documentation anymore—it's becoming the operational control center. + +I also noticed references to assets that should become first-class generated artifacts instead of static files: + +termux-ecosystem-architecture.html + +workspace/llm_map + +archwiz + +central_mapper_v420.py + +mapper_graph.py + +recovery indices + +provenance data + + +Those tell me you've already built most of the plumbing. + +The weak point isn't generating maps. + +It's keeping them synchronized. + +I'd evolve the repo into a self-documenting monorepo + +Rather than manually updating diagrams, I'd have every significant change regenerate: + +docs/ + +PROJECT_INDEX.md +MODULE_GRAPH.md +DIRECTORY_GRAPH.md +DEPENDENCY_GRAPH.md +COMMAND_REFERENCE.md +ACTIVE_PROJECTS.md +RESTORATION_STATUS.md +AI_CONTEXT.md + +Generated. + +Never edited by hand. + + +--- + +Then add: + +generated/ + +repo.json +modules.json +graph.json +commands.json +agents.json + +Everything else consumes those. + +ArchWiz. + +Mapper. + +DeepCLI. + +Claude. + +GPT. + +Gemini. + +Codex. + +Everyone reads the same canonical data. + +Biggest upgrade I'd implement + +I'd merge your existing mapper work into a pipeline something like: + +filesystem + │ + ▼ +central_mapper + │ + ▼ +repository graph + │ + ├────► Markdown docs + ├────► HTML diagrams + ├────► Mermaid + ├────► JSON + ├────► GraphViz + └────► AI Context Pack + +One scan. + +Many outputs. + +I also want to revive the stale graphs + +Since you mentioned they're probably ~6 weeks old, I'd treat them as cache. + +The process becomes: + +1. Detect changed directories from Git. + + +2. Rescan only affected modules. + + +3. Rebuild graph incrementally. + + +4. Publish updated docs. + + +5. Commit generated artifacts separately from source changes. + + + +That keeps the repository fast even as it grows. + +The first automation milestone I'd target + +Repository health score + +Orphan directory detection + +Duplicate project detection + +Dead symlink/submodule detection + +Broken documentation links + +Missing README detection + +Entry-point discovery + +Command index generation + +AI context bundle generation + + +Run with one command. + +Why I'm not pushing yet + +The connector itself isn't the blocker—the GitHub App's permissions are. Once it's granted repository write access (Contents and Pull Requests), I can create branches, commit generated files, and open PRs directly from here. + +From what I've seen so far, I think termux-monorepo is reaching the stage where it deserves its own build system for documentation and repository intelligence. The mapper, ArchWiz, and recovery tooling are already pointing in that direction; the next step is to unify them into a single automated pipeline that continuously explains the repository to both humans and AI. + +[https://chatgpt.com/s/t_6a6d3a3b2a308191981b5877ea748ed4] diff --git a/docs/proposals/ChatGPT_Critical-Eval(TER0-15+other-branches).md b/docs/proposals/ChatGPT_Critical-Eval(TER0-15+other-branches).md new file mode 100644 index 000000000..e0a4ab9b5 --- /dev/null +++ b/docs/proposals/ChatGPT_Critical-Eval(TER0-15+other-branches).md @@ -0,0 +1,1444 @@ +ArchW1z Recon: timerloggedout-spec/termux-monorepo + +I went back in and treated the repository as a system, not merely as the current master tree: branches, PRs, review threads, review history, commit topology, diffs, recovery work, CI proposals, and the DeepForge/deepcli line. + +[Open `termux-monorepo` on GitHub](https://github.com/timerloggedout-spec/termux-monorepo?utm_source=chatgpt.com) + +Executive verdict + +The repo is moving in the right direction, but master is currently the wrong integration point for the architecture you are building. + +The strongest conclusion from the recon is: + +> Do not merge the large TER-9 / multi-ai-cli branch as-is. Do not merge the Rust CI PR as-is. Do not treat PR #3's session-store deletion as sufficient security remediation. + +Instead, establish a small hardened integration spine first, then layer DeepForge and provider work onto it. + + + +The repository has a surprisingly good architectural nucleus buried underneath a lot of branch divergence: + +ArchWiz → canonical dispatch → provider adapters → session SSOT → content-addressed index → harvest/search → validation/promotion + +That's the shape I would preserve. + + +--- + +1. Branch topology: the important discovery + +I found 18 branches. + +The branch landscape is currently fragmented into several parallel histories: + +Branch Finding + +master Current canonical head: 320c73b… +master-staging Identical to master — currently redundant +feature/ci-gate-and-docs Identical to master — stale/empty branch +feature/recon-intel-and-nav 2 commits ahead / 3 behind; documentation-only RECON +critical-proposal 8 ahead / 2 behind; config/security/docs architecture +mistral/fixes-config-security 1 ahead / 4 behind; security/config branch +recreate/refTemplates-skeleton 3 ahead / 3 behind; metadata restoration +agent/repository-hygiene 5 ahead / 4 behind; removes session stores +vibe/mistralai-vibe-code-wrapper-6055d2 4 ahead / 4 behind; large multi-AI implementation +timerlogged/ter-5-… 2 ahead / 1 behind; dispatch observability +timerlogged/ter-9-… 6 ahead / 4 behind; large provider/multi-AI integration +timerlogged/ter-12-… 4 ahead / 0 behind; DeepForge bridge +timerlogged/ter-13-… 1 ahead / 0 behind; curl_cffi fallback +devin/ter-11-… 2 ahead / 0 behind; earlier DeepForge bridge +devin/1785650368-… 1 ahead / 0 behind; Termux MCP +timerloggedout-spec-patch-1 4 ahead / 4 behind; Rust CI +v0/timerloggedout-5184-43474d34 2 ahead / 0 behind; DeepForge predecessor +termux-monorepo 2 ahead / 0 behind; repo-gate + credential/portability hardening + + +That last branch is especially interesting. + + +--- + +2. The sleeper branch: termux-monorepo + +This branch contains something I consider more strategically valuable than PR #2: + +.github/workflows/repo-gate.yml + +scripts/ci/repo_gate.py + +scripts/ci/baseline.json + +credential exposure documentation + +portability documentation + +triage documentation + +.gitignore expansion + +local-link tooling + + +The proposed gate is deliberately: + +> stdlib-only, no network, no Cargo, no Node, no pip, no Chromium, no device dependencies. + + + +That is exactly the correct philosophy for this repository's first CI layer. + +The gate operates against the Git index and changed-file scope rather than assuming a fully materialized Termux checkout. That's an excellent architectural decision. + +[`termux-monorepo` branch](https://github.com/timerloggedout-spec/termux-monorepo/tree/termux-monorepo?utm_source=chatgpt.com) + +My proposal + +Promote this idea into the canonical architecture: + +┌───────────────────────┐ + │ repo-gate │ + │ cheap / deterministic │ + └──────────┬────────────┘ + │ + ┌──────────────┼──────────────┐ + ▼ ▼ ▼ + hygiene portability security + │ │ │ + └──────────────┼──────────────┘ + ▼ + integration tests + ▼ + provider tests + ▼ + Termux smoke tests + ▼ + Rust builds + +Not everything belongs in the same CI gate. + + +--- + +3. PR #2: Rust CI is currently a NO-GO + +[PR #2 — Rust build/test workflow](https://github.com/timerloggedout-spec/termux-monorepo/pull/2?utm_source=chatgpt.com) + +This one has a very concrete defect. + +The workflow's Python heredoc contains: + +crates = ${crates:+""} + +inside a quoted heredoc. + +That is not valid Python. + +So the discover job can fail before the actual crate discovery logic gets anywhere. + +Worse, the workflow's architectural model has additional weaknesses: + +Problem A — submodules aren't necessarily available + +The repository's Rust code is heavily represented through submodules, but the workflow isn't clearly designed around the actual submodule topology. + +Problem B — affected-crate detection is too simplistic + +It maps changed paths to crate directories, but a modification to: + +workspace Cargo.toml + +Cargo.lock + +shared build configuration + +root build scripts + +submodule references + + +can affect many crates without falling under the direct path mapping. + +Problem C — wrong abstraction level + +The repo has a massive amount of Python, shell, data/index, Termux, and integration machinery. + +Starting with: + +> "Which Rust crate changed?" + + + +isn't the right first question. + +The first question should be: + +> "Did this commit preserve repository invariants?" + + + +That's what the repo-gate branch solves. + +Recommendation + +Do not merge PR #2. + +Extract its eventual Rust-build capability into a later optional toolchain layer: + +repo-gate + ↓ +language-specific gates + ├── python + ├── shell + ├── json/index + └── rust (conditional) + + +--- + +4. PR #3: security remediation is necessary — but NOT complete + +[PR #3 — session-store removal](https://github.com/timerloggedout-spec/termux-monorepo/pull/3?utm_source=chatgpt.com) + +This is the most important security finding in the entire recon. + +The PR removes 571 session-store artifacts from the Git index and reports sanitizing hundreds of historical blobs. + +Good. + +But the PR itself correctly admits: + +> branch-tip removal ≠ history remediation. + + + +And I confirmed the branch is substantially diverged from current master. + +The historical session artifacts contain things that absolutely should never have become repository content: + +conversation histories + +local filesystem paths + +development prompts + +architecture material + +potentially credential-bearing context + +browser/session-related information + + +The branch history is therefore not just "dirty"; it is a provenance boundary problem. + +Critical distinction + +You need three independent guarantees: + +A. Current tree is clean +B. Future commits cannot reintroduce secrets +C. Historical reachable objects have been remediated + +PR #3 primarily attacks A. + +You need A+B+C. + +My P0 sequence + +1. Rotate any credential that ever appeared in tracked session material. + + +2. Verify current master and all surviving integration branches. + + +3. Establish secret-pattern scanning in the repo gate. + + +4. Add path-class prohibitions: + +.deepcli/session_store + +.pi + +.synthegration + +browser profiles + +cookies + +Chromium local state + +token caches + + + +5. Then perform the separately reviewed history rewrite. + + +6. Force-push only after preservation/export of the old history is deliberately handled. + + + +Do not mistake a green working tree for eradicated credentials. + + +--- + +5. PR #5: excellent instinct, dangerous implementation boundary + +[PR #5 — dispatch failure visibility](https://github.com/timerloggedout-spec/termux-monorepo/pull/5?utm_source=chatgpt.com) + +The conceptual fix is correct: + +silent exception + ↓ +observable exception + +That is absolutely needed. + +But the implementation currently turns the cache layer into an implicit orchestration bus: + +cache_save() + ↓ +dynamic import dispatch_pipeline + ↓ +update_all() + ↓ +lexicon + ↓ +Codex index + +This creates a nasty hidden dependency: + +> Saving a session becomes a side-effectful integration event. + + + +That can eventually create: + +recursion + +latency + +locking problems + +import-cycle problems + +partial-write behavior + +duplicated dispatch + +test contamination + +difficult recovery after interrupted writes + + +Better architecture + +Make the event explicit: + +SessionStore.save() + │ + ├── persist + │ + └── emit SessionSaved event + │ + ▼ + DispatchCoordinator + │ + ┌─────────┼─────────┐ + ▼ ▼ ▼ + harvest index metrics + +Then allow the local Termux implementation to be synchronous or asynchronous. + +Logging the failure is P0. The architectural decoupling is P1. + +Also: dumping raw exception text into stderr can leak filesystem paths, identifiers, URLs, or sensitive provider details. Error messages should be sanitized. + + +--- + +6. PR #6 / TER-9: this is the big one — and it is not merge-ready + +[PR #6 — TER-9 multi-AI/provider work](https://github.com/timerloggedout-spec/termux-monorepo/pull/6?utm_source=chatgpt.com) + +This PR has critical unresolved review threads. + +And the review findings are not cosmetic. + +Critical #1 — session ID precedence bug + +The expression: + +session_id or provider.core.session_id if hasattr(provider, 'core') else None + +doesn't mean what it looks like. + +For providers without .core, caller-provided session_id can disappear. + +That directly violates session continuity. + +Critical #2 — undeclared requests + +core.py imports requests while the dependency is commented out. + +Clean installation can therefore fail at import time. + +That's a fundamental packaging defect. + +Critical #3 — recovered blobs crash search + +The persisted index can contain blobs with no time_index. + +Then: + +self.time_index.get(ch, '').isoformat() + +becomes: + +''.isoformat() + +and explodes. + +This is particularly bad because the failure occurs on recovery/search, exactly where this system is supposed to be resilient. + +Critical #4 — save_to_storage doesn't exist + +The CLI calls it. + +The class doesn't implement it. + +That means a supposedly complete harvesting path reaches runtime and dies. + +Critical #5 — package naming is structurally broken + +The source directory is: + +multi-ai-cli/ + +while Python imports expect: + +multi_ai_cli + +That isn't a cosmetic naming issue. + +It affects packaging, imports, console entrypoints, and module execution. + +Critical #6 — builtin list is shadowed + +A Click command named list shadows Python's builtin list. + +Then unrelated calls such as: + +list(args) + +can become calls to a Click command object. + +That is a real runtime defect. + +Critical #7 — session history is silently discarded + +Claude/Colab provider paths accept session identifiers but pass empty histories. + +This is exactly the kind of bug that makes an abstraction look unified while silently destroying state semantics. + +Critical #8 — cookie isolation + +The Colab provider points at a DeepSeek cookie path. + +That is an unacceptable credential-boundary design. + +Critical #9 — network headers leak across requests + +The network utility mutates persistent session headers: + +self.session.headers.update(headers) + +A one-off authorization header can therefore persist into later requests. + +This is a serious cross-provider credential leak vector. + +Critical #10 — Git command injection surface + +Git helper functions pass branch/remote/repository arguments as bare positional arguments. + +Those need explicit argument boundaries. + + +--- + +The larger problem with PR #6 + +The PR isn't merely "a few bugs." + +It's revealing that the provider abstraction has not yet stabilized. + +You've simultaneously got: + +Provider +Session +History +Availability +Authentication +CLI +Subprocess +Index +Harvesting +Networking +Storage + +without one canonical contract. + +That's why the bugs propagate across apparently unrelated files. + +My proposed provider contract + +Every provider should implement a strict interface: + +Provider +├── identity +├── capabilities +├── availability() +├── authenticate() +├── create_session() +├── send() +├── stream() +├── history() +├── export() +└── health() + +with explicit state: + +ProviderResult +├── provider +├── account +├── session_id +├── message_id +├── status +├── response +├── error +├── usage +└── provenance + +No provider should be allowed to invent its own semantics for session IDs. + + +--- + +7. PR #9 / TER-12: directionally strong, but three real defects remain + +[PR #9 — DeepForge](https://github.com/timerloggedout-spec/termux-monorepo/pull/9?utm_source=chatgpt.com) + +This is the branch I like conceptually the most. + +The deepcli-first policy makes sense for your actual environment. + +The separation: + +DeepForge + ↓ +deepcli default + │ + └── codex-native explicit alternate + +is much cleaner than pretending stock Codex is the primary runtime. + +But the review found three important issues. + +1. Non-Python deepcli launcher + +shutil.which("deepcli") can resolve: + +Python wrapper + +shell wrapper + +compiled executable + + +but the code assumes: + +python + +That breaks non-Python launchers. + +The launcher abstraction should be: + +Python .py → sys.executable +Executable → direct exec +Shell executable → direct exec +Module → python -m + +2. Importable package ≠ runnable launcher + +The branch can detect an importable package and then enter a path where _run_deepcli() cannot actually launch it. + +That's a classic capability-detection mismatch. + +Use a capability object: + +DeepCLIResolution +├── kind = script | module | executable | unavailable +├── path +├── interpreter +└── invocation[] + +Don't recompute the answer in different functions. + +3. Help forwarding + +The README promises: + +python -m codex_bridge deepcli --help + +but argparse can consume the help flag itself. + +This is an API/UX contract failure. + +The bridge should deliberately support: + +deepforge deepcli -- --help + +or properly implement pass-through argument parsing. + + +--- + +8. PR #10: small and useful — but don't blindly merge it + +[PR #10 — curl_cffi fallback](https://github.com/timerloggedout-spec/termux-monorepo/pull/10?utm_source=chatgpt.com) + +This is a good operational fix: + +curl_cffi unavailable + ↓ +stdlib requests fallback + ↓ +deepcli remains usable + +Especially for Termux/Python 3.14 ABI reality. + +But there's an important semantic issue. + +curl_cffi isn't merely a nicer requests. + +It can be required for browser-like TLS impersonation / anti-bot behavior. + +Therefore the fallback should not pretend equivalence. + +I'd make it explicit: + +Transport: + curl_cffi + ├── preferred + └── feature set: browser TLS + + requests + ├── compatibility fallback + └── feature set: standard HTTP + +Then individual provider operations declare: + +requires_browser_tls = true + +and fail clearly if the fallback cannot satisfy the operation. + +The current PR already warns users, which is good. + + +--- + +9. DeepForge architectural opportunity + +This is where I would push the project. + +Right now DeepForge is: + +Python bridge + ↓ +deepcli + ↓ +optional Rust Codex fork + +That's useful, but eventually the abstraction should become: + +DeepForge + │ + ┌─────────┴─────────┐ + │ │ + Control Plane Execution Plane + │ │ + session/provenance provider runtime + routing/capabilities adapters + permissions subprocesses + event bus HTTP/API + reconciliation Rust/native + │ │ + └─────────┬─────────┘ + ▼ + ArchWiz Index + +Key idea: + +ArchWiz should know what happened. + +DeepForge should know how to make it happen. + +That separation is extremely valuable. + + +--- + +10. The critical-proposal branch has good ideas but should not become the merge target + +[PR #1 — critical evaluation](https://github.com/timerloggedout-spec/termux-monorepo/pull/1?utm_source=chatgpt.com) + +The proposal branch is valuable as an architectural record. + +The strongest ideas there remain valid: + +environment-aware config + +elimination of silent exceptions + +session SSOT + +consolidation of duplicate send paths + +removal/archival of legacy poller/listener + +fzf integration + +streaming + +routing TUI + +self-healing concepts + +dashboard/API possibilities + + +But don't merge that branch wholesale. + +Harvest the architecture; don't preserve the branch topology. + + +--- + +11. recreate/refTemplates-skeleton: preserve as recovery metadata + +[`recreate/refTemplates-skeleton`](https://github.com/timerloggedout-spec/termux-monorepo/tree/recreate/refTemplates-skeleton?utm_source=chatgpt.com) + +This is a good recovery technique. + +The metadata-only model is much better than dragging entire external repositories into the monorepo. + +But the current model has a classification problem: + +Haven/ +Interpreted-Context-Methdology_fork/ +15_Reverse_Engineering/ + +The category taxonomy needs a formal schema rather than human-maintained directory convention. + +I'd define: + +ref: + id: + category: + upstream: + source_type: + - git + - archive + - local + - generated + acquisition: + revision: + license: + trust: + materialization: + depth: + sparse: + status: + - active + - dormant + - scavenger + - archived + +Then the tree becomes a projection of metadata rather than the source of truth. + +That's very ArchWiz. + + +--- + +12. The historical restoration commits tell another story + +The commit sequence is important: + +initial ecosystem + ↓ +base configs + ↓ +full restore + ↓ +refTemplates restore + ↓ +recovery README + ↓ +recovery README correction + ↓ +refTemplates skeleton + ↓ +RECON + ↓ +critical evaluation + ↓ +ecosystem mapping + +The repo is effectively evolving through forensic reconstruction. + +That's not inherently bad. + +But it means you now need a distinction between: + +HISTORICAL RECOVERY + ≠ +CURRENT SOURCE + ≠ +ARCHITECTURAL SPEC + ≠ +ACTIVE RUNTIME + +At present those concepts still overlap too much. + + +--- + +13. Biggest architectural optimization I see + +Introduce four explicit planes + +┌───────────────────────────────────────────────────┐ +│ ARCHWIZ CONTROL PLANE │ +│ │ +│ maps / provenance / policy / tasks / validation │ +└───────────────────────┬───────────────────────────┘ + │ +┌───────────────────────▼───────────────────────────┐ +│ DISPATCH PLANE │ +│ │ +│ Session events → routing → provider capabilities │ +└───────────────────────┬───────────────────────────┘ + │ +┌───────────────────────▼───────────────────────────┐ +│ EXECUTION PLANE │ +│ │ +│ deepcli / Mistral / Claude / Gemini / Colab / Rust│ +└───────────────────────┬───────────────────────────┘ + │ +┌───────────────────────▼───────────────────────────┐ +│ EVIDENCE PLANE │ +│ │ +│ hashes / logs / artifacts / indexes / provenance │ +└───────────────────────────────────────────────────┘ + +This solves several current problems simultaneously. + + +--- + +14. Session SSOT should be the next major milestone + +The current repository repeatedly tries to synchronize: + +.deepcli +.mistralai-cli +multi-ai-cache +cli-synthegration +codex_index +archwiz + +That is a symptom. + +Don't synchronize six independent truths indefinitely. + +Create: + +~/.archwiz/ + sessions/ + / + / + / + manifest.json + messages.jsonl + events.jsonl + blobs/ + index.json + +Then providers can retain native stores as caches/adapters, while ArchWiz becomes the canonical cross-provider identity layer. + +That was already gesturing toward TER-10. + +I would elevate TER-10 above TER-8. + + +--- + +15. New proposal: event-sourced dispatch + +Instead of: + +update_all(session_id) + +move toward: + +SessionSaved +SessionForked +MessageSent +MessageReceived +SessionExported +CodeHarvested +IndexUpdated +ProviderFailed +ProviderRecovered + +Each event gets: + +{ + "event_id": "...", + "timestamp": "...", + "provider": "...", + "account": "...", + "session_id": "...", + "correlation_id": "...", + "source": "...", + "payload_hash": "...", + "schema_version": 1 +} + +Then ArchWiz can reconstruct state. + +This makes your: + +recovery + +reconciliation + +auditability + +multi-agent orchestration + +session synchronization + +branch provenance + + +dramatically easier. + + +--- + +16. New proposal: capability-driven provider registry + +Stop using: + +is_available() + +as the main abstraction. + +Use: + +ProviderCapabilities +├── installed +├── authenticated +├── session_create +├── session_resume +├── streaming +├── history +├── attachments +├── thinking +├── web_search +├── code_execution +├── browser_tls +├── export +├── indexing +└── dispatch + +Then: + +deepcli + authenticated ✓ + streaming ✓ + attachments ✓ + thinking ✓ + browser_tls ✓ + native_bridge ✗ + +and: + +colab + installed ✓ + authenticated ? + session_resume ? + browser_tls ✗ + +Now routing becomes deterministic. + + +--- + +17. New proposal: "provider truth table" + +This would eliminate a huge amount of ambiguity: + +Provider Installed Auth Session Stream History Search Attach Status + +DeepSeek ✓ ? ✓ ✓ ✓ ✓ ✓ active +Mistral ? ? ? ? ? ? ? active +Claude ? ? ⚠ ? ⚠ ? ? incomplete +Colab ? ? ⚠ ? ⚠ ? ? incomplete +Gemini ? ? ? ? ? ? ? scaffold +OpenAI ? ? ? ? ? ? ? scaffold + + +The repository should generate this table, not maintain it manually. + + +--- + +18. Security: go further than secrets scanning + +The current repo-gate concept is excellent, but I would extend it into data-classification enforcement. + +Class 0 — safe + +Source code, docs, schemas. + +Class 1 — derived + +Indexes, hashes, generated metadata. + +Class 2 — sensitive + +Conversation content, local paths, prompts, telemetry. + +Class 3 — credential-bearing + +Cookies, auth headers, session stores, tokens. + +Class 4 — device/private + +Browser profiles, private SSH data, local environment configuration. + +Then the gate can enforce: + +Class 0 → commit allowed +Class 1 → commit allowed with generated marker +Class 2 → explicit allowlist +Class 3 → HARD FAIL +Class 4 → HARD FAIL + +This is much stronger than regex-only secret detection. + + +--- + +19. New proposal: generated-artifact provenance + +Your repo contains a lot of: + +.bak + +snapshots + +maps + +indices + +recovered material + +generated JSON + +historical artifacts + + +Don't just .gitignore everything. + +Add: + +artifact.manifest.json + +with: + +{ + "path": "...", + "kind": "generated", + "source": "...", + "generator": "...", + "commit": "...", + "timestamp": "...", + "content_hash": "...", + "reproducible": true +} + +Now recovery artifacts become evidence, rather than clutter. + + +--- + +20. New proposal: immutable content-addressed storage + +The existing content_hash work is heading here. + +Make it explicit: + +blob/ + sha256/ + ab/ + abcd.... + +Then: + +Pointer + ├── provider + ├── account + ├── session + ├── message + ├── block + └── content_hash + +The same code block appearing in 17 conversations should become one object with 17 provenance pointers. + +That would substantially improve the whole harvesting/indexing system. + + +--- + +21. One subtle but important thing: timestamps + +The current bugs around: + +time_index.get(ch, '') + +are symptoms of a larger issue. + +You need to distinguish: + +unknown +missing +inherited +recovered +generated +observed + +Don't represent all of those as: + +"" + +Use explicit metadata: + +{ + "timestamp": null, + "timestamp_source": "unknown" +} + +This will make recovery and search semantics much cleaner. + + +--- + +22. Branch cleanup proposal + +The branch tree is already telling us what to do. + +Keep / promote + +master + +termux-monorepo → extract repo-gate + +timerlogged/ter-12-… → DeepForge line + +timerlogged/ter-13-… → transport compatibility + +devin/1785650368-… → MCP integration, after security review + + +Preserve as design/reference + +critical-proposal + +feature/recon-intel-and-nav + +recreate/refTemplates-skeleton + + +Consolidate, don't merge wholesale + +vibe/mistralai-vibe-code-wrapper-6055d2 + +timerlogged/ter-9-… + + +Security remediation + +agent/repository-hygiene + + +Likely obsolete / cleanup candidates + +master-staging — identical + +feature/ci-gate-and-docs — identical + +older DeepForge predecessors once #9 stabilizes + +superseded TER-5 once its observability work is incorporated + + + +--- + +23. PR status board + +PR Verdict + +#1 Merged — keep as architectural record +#2 🔴 NO-GO — workflow itself has a broken Python heredoc + architectural CI issues +#3 🔴 P0 security work — incomplete until history/credential remediation +#4 Merged — useful RECON baseline +#5 🟡 Good fix, but decouple dispatch from cache writes +#6 🔴 NO-GO — multiple critical unresolved defects +#9 🟢/🟡 Best active architectural direction; fix remaining launcher/help defects +#10 🟢 Small useful fix; validate fallback semantics + Termux smoke test + + +The current PR review state also matters: + +#6 has numerous unresolved critical/major threads. + +#9 has unresolved Devin/CodeRabbit findings. + +#10 has no inline review threads and Devin reported no actionable issue, but CodeRabbit was rate-limited. + +#2/#3/#5 lack substantive automated review coverage; their CodeRabbit comments are predominantly review-limit notices, not approvals. + + +So: + +> "No review comments" does NOT mean "reviewed clean." + + + +That's an important distinction in this repo right now. + + +--- + +24. The most important optimization: stop integrating by branch + +Right now the workflow resembles: + +PR + ↓ +branch + ↓ +another branch + ↓ +another branch + ↓ +merge + ↓ +repair + ↓ +new branch + +I would shift to: + +Architecture Contract + ↓ +Small atomic change + ↓ +repo-gate + ↓ +component tests + ↓ +Termux smoke test + ↓ +merge + ↓ +next contract + +Integration order I'd use + +P0 + +1. credential/session-store containment + + +2. repo-gate + + +3. deterministic configuration + + +4. session SSOT schema + + + +P1 + +5. dispatch event boundary + + +6. DeepForge launcher resolver + + +7. provider capability contract + + +8. content-addressed index correctness + + + +P2 + +9. DeepForge ↔ Rust protocol + + +10. MCP + + +11. harvesting/search expansion + + +12. multi-provider parity + + + +P3 + +13. dashboard + + +14. routing TUI + + +15. self-healing + + +16. advanced orchestration + + + + +--- + +25. My proposed "ArchW1z invariant" + +This is the thing I'd actually make the repository enforce: + +┌──────────────────────────────────────────┐ +│ ARCHW1Z INVARIANTS │ +├──────────────────────────────────────────┤ +│ 1. No credential-bearing artifact enters │ +│ Git. │ +│ │ +│ 2. Every session has one canonical ID. │ +│ │ +│ 3. Every provider operation has explicit │ +│ capability semantics. │ +│ │ +│ 4. Every derived artifact has provenance.│ +│ │ +│ 5. Every content object has stable hash. │ +│ │ +│ 6. Dispatch failures are observable. │ +│ │ +│ 7. Cache writes don't secretly mutate │ +│ unrelated subsystems. │ +│ │ +│ 8. Recovery never overwrites unreadable │ +│ state. │ +│ │ +│ 9. CI works on the smallest environment │ +│ before requiring toolchains. │ +│ │ +│ 10. "Available" never means "pretend │ +│ supported." │ +└──────────────────────────────────────────┘ + +That gives you a technical constitution for the monorepo. + + +--- + +26. Final assessment + +Architecture: 7.5/10 + +There is a genuinely strong system emerging. + +Recovery / provenance thinking: 9/10 + +The restoration and RECON work is unusually useful. + +Current integration discipline: 4/10 + +Too much branch divergence and too many overlapping implementation lines. + +Security posture: 3.5/10 currently + +The repository recognizes the problem, but historical session exposure makes this a remediation project, not merely a .gitignore problem. + +Provider abstraction: 4/10 + +Good ambition; insufficiently formal contracts. + +DeepForge direction: 8.5/10 + +This is the cleanest new architectural direction in the current tree. + +CI direction: 8/10 conceptually + +The repo-gate branch is substantially more appropriate as the first CI layer than PR #2's Rust-first workflow. + + +--- + +🪨 The ArchW1z conclusion + +The repo isn't suffering from a lack of code. + +It's suffering from too many partially overlapping truths. + +The winning move is therefore not: + +> "Build more." + + + +It's: + +> Collapse the truths. + + + +One session identity. +One provider contract. +One dispatch event model. +One provenance model. +One content-addressed evidence layer. +One cheap invariant gate. +Then let DeepForge, ArchWiz, MCP, Rust, DeepCLI, harvesting, and the multi-AI layer plug into those primitives. + +That turns the current monorepo from an archaeological reconstruction into an actual agentic operating system. + +And yes: PR #9 + PR #10 + the termux-monorepo repo-gate branch are the most promising convergence points I see right now — but PR #6 should be treated as a bug farm to mine for fixes, not as the thing to merge wholesale. + +[View all open PRs](https://github.com/timerloggedout-spec/termux-monorepo/pulls?utm_source=chatgpt.com) +[View all branches](https://github.com/timerloggedout-spec/termux-monorepo/branches?utm_source=chatgpt.com) +[View repository commit history](https://github.com/timerloggedout-spec/termux-monorepo/commits/master/?utm_source=chatgpt.com) + +[https://chatgpt.com/s/t_6a6fb91136a48191b9686c0fa17fa9cd] diff --git a/docs/proposals/ChatGPT_droidApp.md b/docs/proposals/ChatGPT_droidApp.md index 1129a24bd..f6116efaf 100644 --- a/docs/proposals/ChatGPT_droidApp.md +++ b/docs/proposals/ChatGPT_droidApp.md @@ -5,4 +5,456 @@ This document contains shared links for repository and commit/PR evaluations. > **Note:** Reconsider the directory location for proposal documents under `docs/proposals/`. - [Initial repository evaluation](https://chatgpt.com/s/t_6a6d3a3b2a308191981b5877ea748ed4) +I've started the repository audit. + +Branch inventory + +Current branches: + +master + +critical-proposal + +mistral/fixes-config-security + +timerloggedout-spec-patch-1 + +vibe/mistralai-vibe-code-wrapper-6055d2 + + +Initial observations + +This tells me several development streams are being used: + +Proposal branch (critical-proposal) for architectural planning. + +Security branch (mistral/fixes-config-security) indicating active hardening work. + +Small feature/patch branch (timerloggedout-spec-patch-1). + +AI-generated branch (vibe/...) likely produced by an external coding agent. + + +That's a healthy separation of concerns. One improvement I'd suggest is adopting a consistent naming convention such as: + +feature/... + +fix/... + +security/... + +docs/... + +experiment/... + +spike/... + + +This makes branch purpose immediately obvious and scales better as the repository grows. + +Pull Requests + +There are currently 2 open PRs. + +PR #1 — Critical Evaluation + +This is essentially an architecture review rather than a code change. + +The topics it covers are strong: + +environment-aware configuration + +removal of legacy pipeline components + +dependency management + +configuration abstraction + +roadmap planning + +security observations + +portability + + +One thing I'd add is an explicit Architectural Decision Record (ADR) directory (docs/adr/). For a project evolving rapidly with AI assistance, ADRs make it much easier to understand why a decision was made months later. + + +--- + +PR #2 — Rust GitHub Actions + +This introduces CI focused on Rust builds and tests. + +I like the direction, but I'd expand the CI matrix beyond Rust alone. Since this is a monorepo, I'd eventually want independent jobs for: + +Shell linting + +Python linting & tests + +Rust + +Node (if present) + +Documentation validation + +Security scanning + +Secret detection + +License checks + + +That way, failures stay localized and contributors get faster, more actionable feedback. + +Additional considerations + +As I continue the audit, these are areas I'll be looking at closely: + +Whether the repository has a clear modular boundary between reusable libraries and executable tools. + +Whether Termux-specific logic is isolated behind adapters rather than scattered throughout the codebase. + +Whether configuration is centralized (environment variables, paths, feature flags). + +Whether long-running automation is resilient to Android process death and storage constraints. + +Whether AI-generated code has been normalized to a consistent style and architecture. + + +The next phase will be the deeper dive into commit history, code diffs, and the actual implementation quality to identify concrete refactoring opportunities and any architectural risks worth addressing. + - [PR branch commit review](https://chatgpt.com/s/t_6a6d3a0c17e48191a470ddf338780387) +Continuing with the architectural review based on the repository structure, branch strategy, and active development workflow. + +Repository Maturity Assessment + +Current impression: + +Vision: ★★★★★ + +Experimentation: ★★★★★ + +Architecture: ★★★★☆ + +Documentation: ★★★★☆ + +Maintainability: ★★★☆☆ + +Automation: ★★★☆☆ + +Production Readiness: ★★☆☆☆ + + +This doesn't look like a typical application repository—it resembles an evolving developer operating system centered on Termux and AI-assisted workflows. That changes the evaluation criteria significantly. + +Architectural strengths + +1. Monorepo is the right choice + +Given the breadth of tooling you've described previously (CLI utilities, automation, AI integrations, pipelines), keeping everything in a monorepo is sensible. The shared infrastructure and scripts benefit from being versioned together. + +2. Branches indicate experimentation + +Having dedicated proposal and experimental branches suggests you're treating architecture as something to iterate on rather than freezing early. + +I'd lean further into that by clearly separating: + +apps/ +packages/ +libs/ +docs/ +scripts/ +experiments/ +archive/ + +This makes it obvious which code is production-ready versus exploratory. + +3. Documentation-first thinking + +The proposal PR is a good sign. Capturing architectural intent before implementation reduces future rework. + + +--- + +Risks I expect to see + +These are common in fast-moving AI-heavy repositories and are worth checking systematically. + +Configuration sprawl + +Instead of: + +if termux: +... + +if linux: +... + +if replit: +... + +Prefer a single configuration object: + +Config.current() + +Everything else imports that. + + +--- + +Shared utility duplication + +AI-generated code often recreates helpers instead of reusing them. + +I'd look for duplicate implementations of: + +JSON handling + +subprocess wrappers + +HTTP clients + +logging + +retry logic + +path utilities + + +Those belong in a shared package. + + +--- + +Pipeline coupling + +If one component imports another directly, dependencies can become tangled. + +A healthier pattern is: + +event + +↓ + +dispatcher + +↓ + +plugin + +↓ + +result + +rather than every module calling every other module. + + +--- + +What I'd add + +1. Plugin system + +Instead of hardcoding tools: + +plugins/ + + github/ + + claude/ + + deepseek/ + + gemini/ + + openai/ + + local/ + + browser/ + +Each exposes something like: + +run() + +describe() + +capabilities() + +The cockpit simply discovers and loads them. + + +--- + +2. Capability registry + +Rather than asking "which tool should I use?", maintain a registry: + +{ + "supports_streaming": true, + "supports_images": false, + "supports_shell": true, + "supports_reasoning": true +} + +Routing becomes data-driven instead of relying on if statements. + + +--- + +3. Event bus + +I would avoid direct module-to-module communication where possible. + +Instead: + +PromptSubmitted + +↓ + +PromptDispatched + +↓ + +ResponseReceived + +↓ + +Stored + +↓ + +Indexed + +↓ + +Vectorized + +↓ + +Summarized + +This enables adding features (logging, analytics, notifications) without changing the core workflow. + + +--- + +4. AI abstraction layer + +Wrap each provider behind a common interface: + +Provider + +ClaudeProvider + +GeminiProvider + +OpenAIProvider + +DeepSeekProvider + +GitHubModelsProvider + +Switching providers should require minimal changes elsewhere in the codebase. + + +--- + +Performance opportunities + +On Android/Termux, process startup cost matters. + +Consider: + +persistent workers + +connection pooling + +caching parsed configs + +avoiding repeated shell invocations + +minimizing Python interpreter launches + + +Reducing process churn can noticeably improve responsiveness. + + +--- + +Security observations + +The presence of a mistral/fixes-config-security branch suggests you're already addressing some issues. + +I'd specifically review for: + +hardcoded API keys + +session identifiers + +personal filesystem paths + +subprocess injection risks + +unsafe shell=True usage + +unchecked environment variables + +GitHub token scope + +secret leakage in logs + + +Automating these checks in CI (secret scanning, dependency audits, linting) can catch regressions early. + + +--- + +Long-term proposal: evolve into a platform + +The repository appears to be moving beyond a collection of scripts. + +A natural evolution is: + +ArchWiz Platform + +├── CLI +├── Cockpit UI +├── REST API +├── Plugin SDK +├── Scheduler +├── Agent Runtime +├── Memory Layer +├── Vector Store +├── Workflow Engine +├── Prompt Registry +└── Observability + +At that point, external developers could extend the platform through plugins rather than modifying the core. + +Highest-priority recommendations + +1. Introduce a formal plugin architecture. + + +2. Centralize configuration and environment detection. + + +3. Eliminate duplicated utility code across languages. + + +4. Define stable interfaces between components to reduce coupling. + + +5. Add comprehensive CI with linting, testing, security, and documentation validation. + + +6. Adopt ADRs (docs/adr/) to document architectural decisions. + + +7. Establish a clear distinction between stable code, experiments, and archived prototypes. + + + +From what I've seen so far, the repository has ambitious scope and a solid experimental mindset. The biggest opportunity now is to channel that flexibility into well-defined interfaces and modular boundaries so the project can continue to grow without accumulating disproportionate maintenance costs. diff --git a/docs/proposals/PROCESS.md b/docs/proposals/PROCESS.md new file mode 100644 index 000000000..38a5ff5db --- /dev/null +++ b/docs/proposals/PROCESS.md @@ -0,0 +1,57 @@ +# Proposal Process — ArchW1z + +Structured lifecycle: **ingest → register → itemize → review → execute → close**. + +**Also read:** `AGENTIC-PERMISSIONS.md` · `registry.yaml` · root `AGENTS.md` · `docs/CONSENSUS.md` + +## Directory layout + +``` +docs/proposals/ + README.md + PROCESS.md + AGENTIC-PERMISSIONS.md + registry.yaml + _template/MANIFEST.md + active//{MANIFEST.md,ITEMS.md,DEBATE.md?,source.md?} + closed// + legacy/ +``` + +## States + +| State | Meaning | +|-------|--------| +| draft | Author still writing | +| posted | In registry.yaml | +| in_review | ≥1 reviewer assigned | +| accepted | Execution may start | +| executing | Items being implemented | +| blocked | Waiting on human-only step | +| closed | Terminal under closed/ | + +## Consensus + +Item-scoped. See `docs/CONSENSUS.md` tiers. P0 needs second mind or Operator. Unposted chat is not consensus. + +## Closing + +All ITEMS terminal + Review log outcome + move active/ → closed/ + registry update. + +## Gate coupling + +1. repo-gate green +2. termux-smoke green +3. Item IDs cited +4. No unresolved critical PR threads + +## Agent entrypoint + +```text +1. Read AGENTS.md then registry.yaml +2. Pick highest-priority accepted/executing todo item +3. Branch off master-staging +4. PR → master-staging with Implements: +5. Update ITEMS.md + registry when possible +6. Never close P0 security without Operator evidence +``` diff --git a/docs/proposals/README.md b/docs/proposals/README.md new file mode 100644 index 000000000..5310f362f --- /dev/null +++ b/docs/proposals/README.md @@ -0,0 +1,21 @@ +# Proposals + +**Agents: start at [`registry.yaml`](registry.yaml).** +**Humans: start at [`PROCESS.md`](PROCESS.md).** +**Permissions: [`AGENTIC-PERMISSIONS.md`](AGENTIC-PERMISSIONS.md).** +**Consensus tiers: [`../CONSENSUS.md`](../CONSENSUS.md).** + +## Active + +| ID | Priority | Status | Path | +|----|----------|--------|------| +| chatgpt-critical-eval | P0 | executing | [active/chatgpt-critical-eval/](active/chatgpt-critical-eval/) | +| chatgpt-initial | P2 | posted | [active/chatgpt-initial/](active/chatgpt-initial/) | +| chatgpt-droidapp | P2 | posted | [active/chatgpt-droidapp/](active/chatgpt-droidapp/) | + +Flat historical sources on master: + +- `ChatGPT_Critical-Eval(TER0-15+other-branches).md` +- `ChatGPT-initial.md` +- `ChatGPT_droidApp.md` +- `corrected_cloud_offload_evaluation.md` (from docs/kimi branch) diff --git a/docs/proposals/_template/MANIFEST.md b/docs/proposals/_template/MANIFEST.md new file mode 100644 index 000000000..d2587c319 --- /dev/null +++ b/docs/proposals/_template/MANIFEST.md @@ -0,0 +1,47 @@ +--- +id: PROPOSAL-ID +title: "Title" +author: NAME +posted_at: YYYY-MM-DD +source: source.md +status: draft +priority: P2 +reviewers: [] +related_prs: [] +related_branches: [] +gates_required: [repo-gate, termux-smoke] +--- + +# MANIFEST — PROPOSAL-ID + +## Summary + +One paragraph intent. + +## Reviewers + +| ID | Role | Status | At | Notes | +|----|------|--------|-----|-------| +| | author | posted | | | + +## Review log + +### YYYY-MM-DD — reviewer-id + +- Disposition: accepted | changes_requested | commented +- Notes: + +## Checklist (process) + +- [ ] Registered in `docs/proposals/registry.yaml` +- [ ] ITEMS.md itemized +- [ ] At least one non-author review recorded +- [ ] Status → accepted before execution merges +- [ ] PRs cite `Implements: ` +- [ ] Gates green on merge +- [ ] Closed + moved to `closed/` when terminal + +## Links + +- ITEMS: ./ITEMS.md +- Source: ./source.md diff --git a/docs/proposals/active/agent-quota-loadbalancer/ITEMS.md b/docs/proposals/active/agent-quota-loadbalancer/ITEMS.md new file mode 100644 index 000000000..ac005f88f --- /dev/null +++ b/docs/proposals/active/agent-quota-loadbalancer/ITEMS.md @@ -0,0 +1,15 @@ +# ITEMS — agent-quota-loadbalancer + +| ID | Work | Priority | Owner | Status | Evidence | +|----|------|----------|-------|--------|----------| +| QL-01 | Gemini quota-gate composite action | P0 | tembo | done | .github/actions/gemini-quota-gate/action.yml | +| QL-02 | Session continuation cache for gemini-review | P0 | tembo | done | .github/workflows/gemini-review.yml | +| QL-03 | Graceful skip when quota exhausted (no CI failure) | P0 | tembo | done | gemini-review.yml + gemini-triage.yml + gemini-invoke.yml | +| QL-04 | Agent load-balancer dispatch workflow | P0 | tembo | done | .github/workflows/agent-load-balancer.yml | +| QL-05 | Update gemini-dispatch to route through load balancer | P0 | tembo | done | .github/workflows/gemini-dispatch.yml | +| QL-06 | Agent capability registry stub | P1 | tembo | done | docs/schemas/agent-capabilities.yaml | +| QL-07 | .gitignore: nexuscli session_store pattern | P0 | tembo | done | .gitignore | +| QL-08 | Proposal registry + MANIFEST + ITEMS | P0 | tembo | done | docs/proposals/active/agent-quota-loadbalancer/ | +| QL-09 | Quota utilization dashboard (GHA summary) | P2 | | todo | | +| QL-10 | Multi-key rotation for Gemini (paid fallback) | P1 | | todo | | +| QL-11 | Cross-agent session handoff protocol | P1 | | todo | | diff --git a/docs/proposals/active/agent-quota-loadbalancer/MANIFEST.md b/docs/proposals/active/agent-quota-loadbalancer/MANIFEST.md new file mode 100644 index 000000000..f68a027a8 --- /dev/null +++ b/docs/proposals/active/agent-quota-loadbalancer/MANIFEST.md @@ -0,0 +1,71 @@ +# MANIFEST — agent-quota-loadbalancer + +## Summary + +Free-tier agent quotas (Gemini 20 req/day, Jules 15/day with 3 concurrent, +CodeRabbit per-PR limits) are being exhausted by un-throttled CI dispatch. +This proposal introduces: + +1. **Quota throttling** — a daily counter gate that skips Gemini jobs when the + free-tier limit is reached, posting a friendly comment instead of failing CI. +2. **Session continuation** — Gemini review context is cached per-PR so that + `synchronize` pushes only re-review changed files, not the whole PR. +3. **Load balancing** — a central dispatch router that routes review/triage/invoke + tasks across available agents (Gemini, Jules, CodeRabbit, Devin) based on + live quota and concurrency, falling back gracefully. +4. **Capacity maximization** — agent capability registry + utilization tracking + so work is distributed to the agent with the most headroom. + +## Motivation + +PR #63 CI showed 14 failing workflows because Gemini's free-tier quota +(20 `generate_content_free_tty_requests`/day on `gemini-3.5-flash`) was +exhausted by multiple PR events firing in parallel. The quota resets daily +but CI had no throttling — every `opened` / `synchronize` / `ready_for_review` +event dispatched a Gemini job, and once the quota ran out, all subsequent +runs hard-failed with `TerminalQuotaError`. + +Jules demonstrates the desired pattern: 15/day with 3 concurrent, but one +session can continue through many PR revisions and different PR review +requests, running for an extended period. Gemini (and other agents) need +the same session-continuation discipline. + +## Design + +### Quota gate (`.github/actions/gemini-quota-gate/`) + +A composite action that: + +- Reads a daily counter from the GitHub Actions cache + (`gemini-quota-YYYY-MM-DD`, value = requests consumed today) +- If counter >= `GEMINI_FREE_TIER_DAILY_LIMIT` (default 18, leaving 2 as + safety margin): sets `skip=true`, posts a graceful comment, exits 0 +- Otherwise: increments the counter atomically and sets `skip=false` +- The counter is scoped per-day; stale keys from previous days are ignored + +### Session continuation (cache-based) + +- `gemini-review.yml` caches the review output per-PR-number + commit SHA +- On `synchronize`, if the cached review exists for the current SHA, the job + posts "already reviewed" and skips +- On subsequent pushes, only files changed since the last-reviewed SHA are + passed to Gemini (reduces token usage and API calls) + +### Load balancer (`.github/workflows/agent-load-balancer.yml`) + +A reusable workflow that: + +- Enumerates available agents and their current capacity +- Routes the task to the agent with the most headroom +- Falls back to CodeRabbit (always available, per-PR limits) or a human + ping if all AI agents are exhausted + +## Review log + +| Reviewer | Role | Status | Notes | +|----------|------|--------|-------| +| tembo | author+executor | posted | Initial implementation | + +## Related PRs + +- #63 (trigger: Gemini quota exhaustion CI failure) diff --git a/docs/proposals/active/chatgpt-critical-eval/ITEMS.md b/docs/proposals/active/chatgpt-critical-eval/ITEMS.md new file mode 100644 index 000000000..7ce536b14 --- /dev/null +++ b/docs/proposals/active/chatgpt-critical-eval/ITEMS.md @@ -0,0 +1,25 @@ +# ITEMS — chatgpt-critical-eval + +| ID | Work | Priority | Owner | Status | Evidence | +|----|------|----------|-------|--------|----------| +| CE-01 | Install repo-gate on master-staging | P0 | grok-archw1z | done | docs/ARCHW1Z-GATE.md | +| CE-02 | Termux smoke gate | P0 | grok-archw1z | done | PR #11 | +| CE-03 | Living status board | P0 | grok-archw1z | done | ARCHW1Z-STATUS.md | +| CE-04 | Session SSOT schema | P0 | grok-archw1z | done | schemas/session-ssot.md | +| CE-05 | Provider capability registry stub | P1 | grok-archw1z | done | schemas/provider-capabilities.md | +| CE-06 | Security A+B+C checklist | P0 | grok-archw1z | done | SECURITY-REMEDIATION.md | +| CE-07 | Disposition comments on open PRs | P0 | grok-archw1z | done | PR comments | +| CE-08 | Retarget #10 → master-staging | P1 | grok-archw1z | done | | +| CE-09 | Merge #10 if green | P1 | | todo | | +| CE-10 | DeepForge launcher (#9) | P1 | | todo | | +| CE-11 | Retarget #9 → master-staging | P1 | | todo | | +| CE-12 | Decouple #5 cache→dispatch | P1 | | todo | | +| CE-13 | PR #3 history remediation | P0 | human+agent | blocked | needs rotation | +| CE-14 | Park/close #2 | P2 | | todo | | +| CE-15 | Extract safe patches from #6 | P1 | | todo | | +| CE-16 | Session SSOT minimal writer | P1 | | todo | | +| CE-17 | Event-sourced dispatch | P1 | | todo | | +| CE-18 | Content-addressed blob store | P2 | | todo | | +| CE-19 | Four-plane architecture | P2 | | todo | | +| CE-20 | Proposal process + nested registry | P0 | grok-archw1z | done | this tree | +| CE-21 | CONSENSUS.md + promote process docs to master | P0 | grok-archw1z | doing | this PR | diff --git a/docs/proposals/active/chatgpt-critical-eval/MANIFEST.md b/docs/proposals/active/chatgpt-critical-eval/MANIFEST.md new file mode 100644 index 000000000..a05654435 --- /dev/null +++ b/docs/proposals/active/chatgpt-critical-eval/MANIFEST.md @@ -0,0 +1,41 @@ +--- +id: chatgpt-critical-eval +title: "Critical Eval TER0-15 + other branches" +author: ChatGPT +posted_at: 2026-08-02 +status: executing +priority: P0 +reviewers: + - id: chatgpt + role: author + status: posted + - id: grok-archw1z + role: reviewer+executor + status: accepted +related_prs: [2, 3, 5, 6, 9, 10, 11] +gates_required: [repo-gate, termux-smoke] +--- + +# MANIFEST — chatgpt-critical-eval + +## Summary + +Canonical recon of branch topology, PR readiness, security, provider abstraction, DeepForge, integration spine. + +**Canonical text on master:** `docs/proposals/ChatGPT_Critical-Eval(TER0-15+other-branches).md` + +## Review log + +### 2026-08-02 — grok-archw1z + +- Disposition: **accepted** (with execution priority order) +- Notes: Do not merge #2/#6 as-is. Promote repo-gate + termux-smoke first. + +## Checklist + +- [x] Registered +- [x] ITEMS.md +- [x] Non-author review +- [x] executing +- [ ] All P0 items terminal +- [ ] Closed diff --git a/docs/proposals/active/chatgpt-droidapp/ITEMS.md b/docs/proposals/active/chatgpt-droidapp/ITEMS.md new file mode 100644 index 000000000..909f9bd52 --- /dev/null +++ b/docs/proposals/active/chatgpt-droidapp/ITEMS.md @@ -0,0 +1,9 @@ +# ITEMS — chatgpt-droidapp + +| ID | Work | Priority | Status | +|----|------|----------|--------| +| DA-01 | Branch naming convention doc | P3 | todo | +| DA-02 | Plugin architecture sketch → code | P2 | todo | +| DA-03 | Event bus (align session-ssot events) | P1 | todo | +| DA-04 | AI provider abstraction (align CE-05) | P1 | todo | +| DA-05 | ADR for monorepo-as-platform | P2 | todo | diff --git a/docs/proposals/active/chatgpt-droidapp/MANIFEST.md b/docs/proposals/active/chatgpt-droidapp/MANIFEST.md new file mode 100644 index 000000000..884d05ace --- /dev/null +++ b/docs/proposals/active/chatgpt-droidapp/MANIFEST.md @@ -0,0 +1,18 @@ +--- +id: chatgpt-droidapp +title: "droidApp / early branch inventory + plugin architecture" +author: ChatGPT +posted_at: 2026-08-02 +status: posted +priority: P2 +--- + +# MANIFEST — chatgpt-droidapp + +**Canonical text:** `docs/proposals/ChatGPT_droidApp.md` + +## Review log + +### 2026-08-02 — grok-archw1z + +- Disposition: accepted as **P2 vision**; capability registry under CE-05. diff --git a/docs/proposals/active/chatgpt-initial/ITEMS.md b/docs/proposals/active/chatgpt-initial/ITEMS.md new file mode 100644 index 000000000..820fa3442 --- /dev/null +++ b/docs/proposals/active/chatgpt-initial/ITEMS.md @@ -0,0 +1,9 @@ +# ITEMS — chatgpt-initial + +| ID | Work | Priority | Status | +|----|------|----------|--------| +| CI-01 | Split mega-README into cockpit + linked docs | P2 | todo | +| CI-02 | Generated PROJECT_INDEX / MODULE_GRAPH pipeline | P2 | todo | +| CI-03 | Unify central_mapper → multi-output scan | P2 | todo | +| CI-04 | AI_CONTEXT pack generation | P2 | todo | +| CI-05 | ADR directory docs/adr/ | P2 | todo | diff --git a/docs/proposals/active/chatgpt-initial/MANIFEST.md b/docs/proposals/active/chatgpt-initial/MANIFEST.md new file mode 100644 index 000000000..625454622 --- /dev/null +++ b/docs/proposals/active/chatgpt-initial/MANIFEST.md @@ -0,0 +1,18 @@ +--- +id: chatgpt-initial +title: "Initial repository evaluation / cockpit intelligence" +author: ChatGPT +posted_at: 2026-08-02 +status: posted +priority: P2 +--- + +# MANIFEST — chatgpt-initial + +**Canonical text:** `docs/proposals/ChatGPT-initial.md` + +## Review log + +### 2026-08-02 — grok-archw1z + +- Disposition: accepted as **P2** after gates + SSOT diff --git a/docs/proposals/corrected_cloud_offload_evaluation.md b/docs/proposals/corrected_cloud_offload_evaluation.md new file mode 100644 index 000000000..80f6ab14d --- /dev/null +++ b/docs/proposals/corrected_cloud_offload_evaluation.md @@ -0,0 +1,21 @@ +# Corrected Cloud Offload Evaluation + +> **Source branch:** `docs/kimi-cloud-offload-evaluation` +> **Full text:** `docs/proposals/corrected_cloud_offload_evaluation.md` on that branch +> **Promoted:** pointer on `master` so process docs land without a 24KB mid-flight dump + +## Summary of corrections + +1. **Retry pattern** — not pure exponential backoff; **impatient user burst** (20% instant 0.5–2s, 80% jittered exponential, cap 90s) in deepcli. +2. **No calendar phases** — use Big-O complexity classes (O(1) bootstrap → O(log n) route → O(n) parallel → O(k log k) merge). +3. **TMUX replacement** — prefer `archwiz/autonomous_runner.py`, Hermes terminal/delegation, chronos Celery, sandbox-alternative VMs. +4. **AGY/Jules templates** — agentic-workflow-starter, antigravity-jules-autonomous, gemini-cli-jules-orchestrator as scavenge sources. +5. **jules-worker-pool-cli** — un-pause path documented on source branch. + +## Action + +```text +git show origin/docs/kimi-cloud-offload-evaluation:docs/proposals/corrected_cloud_offload_evaluation.md +``` + +Registrar may nest under `docs/proposals/active/` when itemized into ITEMS. diff --git a/docs/proposals/registry.yaml b/docs/proposals/registry.yaml new file mode 100644 index 000000000..9182ce18b --- /dev/null +++ b/docs/proposals/registry.yaml @@ -0,0 +1,69 @@ +# ArchW1z proposal registry — agents read this first +version: 1 +updated_at: "2026-08-07T00:00:00Z" +updated_by: tembo + +proposals: + - id: chatgpt-critical-eval + title: "Critical Eval TER0-15 + other branches" + author: ChatGPT + status: executing + priority: P0 + path: active/chatgpt-critical-eval/ + legacy_source: ChatGPT_Critical-Eval(TER0-15+other-branches).md + reviewers: + - id: chatgpt + role: author + status: posted + - id: grok-archw1z + role: reviewer+executor + status: accepted + related_prs: [2, 3, 5, 6, 9, 10, 11] + gates_required: [repo-gate, termux-smoke] + + - id: chatgpt-initial + title: "Initial repository evaluation / cockpit intelligence" + author: ChatGPT + status: posted + priority: P2 + path: active/chatgpt-initial/ + legacy_source: ChatGPT-initial.md + reviewers: + - id: chatgpt + role: author + status: posted + - id: grok-archw1z + role: reviewer + status: accepted + related_prs: [] + gates_required: [repo-gate] + + - id: chatgpt-droidapp + title: "droidApp / early branch inventory + plugin architecture" + author: ChatGPT + status: posted + priority: P2 + path: active/chatgpt-droidapp/ + legacy_source: ChatGPT_droidApp.md + reviewers: + - id: chatgpt + role: author + status: posted + - id: grok-archw1z + role: reviewer + status: accepted + related_prs: [1, 2] + gates_required: [repo-gate] + + - id: agent-quota-loadbalancer + title: "Agent quota throttling, session continuation, and load balancing" + author: tembo + status: executing + priority: P0 + path: active/agent-quota-loadbalancer/ + reviewers: + - id: tembo + role: author+executor + status: posted + related_prs: [63] + gates_required: [repo-gate] diff --git a/docs/schemas/agent-capabilities.yaml b/docs/schemas/agent-capabilities.yaml new file mode 100644 index 000000000..f98bfc31d --- /dev/null +++ b/docs/schemas/agent-capabilities.yaml @@ -0,0 +1,122 @@ +# Agent Capability Registry +# Used by the agent load balancer to route tasks to the agent with the most headroom. +# Implements: agent-quota-loadbalancer QL-06 + +version: 1 +updated_at: "2026-08-07T00:00:00Z" +updated_by: tembo + +agents: + - id: gemini + name: "Gemini CLI (free-tier)" + role: review / triage / invoke + platform: google-github-actions/run-gemini-cli + quota: + daily_limit: 20 + throttled_limit: 18 + reset: "00:00 UTC" + metric: generate_content_free_tty_requests + model: gemini-3.5-flash + concurrency: 1 + session_continuation: true + session_method: "comment-embedded SHA bookmark + changed-files diff" + strengths: + - PR review (correctness, security, AGENTS.md alignment) + - Issue triage (labeling, dedup, repro steps) + - On-demand analysis + fallback_when_exhausted: coderabbit + + - id: jules + name: "Jules (Google Labs)" + role: build / implement / fix + platform: google-labs-code/jules-invoke + quota: + daily_limit: 15 + concurrent: 3 + session_continuation: true + session_method: "Cloud VM session spans multiple PR revisions and review requests" + strengths: + - Primary builder (creates branches/PRs) + - Auto-resolve on bot feedback + - Extended sessions across PR lifecycle + fallback_when_exhausted: human + + - id: coderabbit + name: "CodeRabbit" + role: review / autofix + platform: coderabbitai GitHub App + quota: + daily_limit: unlimited + per_pr_limit: "platform-managed" + concurrency: unlimited + session_continuation: true + session_method: "per-PR review threads persist across pushes" + strengths: + - Always available (no quota exhaustion risk) + - Line-level review comments + - Autofix suggestions + - Walkthrough summaries + fallback_when_exhausted: none + + - id: devin + name: "Devin (Cognition)" + role: review / build + platform: devin-ai-integration GitHub App + quota: + daily_limit: "platform-managed" + concurrency: "platform-managed" + session_continuation: true + strengths: + - End-to-end implementation + - Review with deep context + fallback_when_exhausted: coderabbit + + - id: copilot + name: "GitHub Copilot" + role: review + platform: GitHub Copilot CLI + quota: + daily_limit: "platform-managed" + concurrency: "platform-managed" + session_continuation: false + strengths: + - Code suggestions + - Security review + fallback_when_exhausted: coderabbit + + - id: tembo + name: "Tembo" + role: build / implement / fix / orchestration + platform: Tembo sandbox agent + quota: + daily_limit: "platform-managed" + concurrency: "platform-managed" + session_continuation: true + session_method: "per-PR session with context preserved across revisions" + strengths: + - Autonomous multi-file implementation + - CI/CD workflow engineering + - Proposal process compliance + fallback_when_exhausted: human + +# Routing matrix: which agent handles which command type +routing: + review: + primary: gemini + fallback: coderabbit + always_on: [coderabbit] + triage: + primary: gemini + fallback: jules + invoke: + primary: gemini + fallback: jules + build: + primary: jules + fallback: tembo + fix: + primary: jules + fallback: tembo + autofix: + primary: coderabbit + fallback: jules diff --git a/docs/schemas/provider-capabilities.md b/docs/schemas/provider-capabilities.md new file mode 100644 index 000000000..a6991a3e3 --- /dev/null +++ b/docs/schemas/provider-capabilities.md @@ -0,0 +1,9 @@ +# Provider Capability Registry + +> **Status:** SPEC (P1) + +Every provider implements identity, capabilities, availability, authenticate, create_session, send, stream, history, export, health. + +Capability flags: installed, authenticated, session_create/resume, streaming, history, attachments, thinking, web_search, code_execution, browser_tls, export, indexing, dispatch. + +`curl_cffi` ≠ stdlib `requests`. If `requires_browser_tls` and only requests available → fail clearly. diff --git a/docs/schemas/session-ssot.md b/docs/schemas/session-ssot.md new file mode 100644 index 000000000..c3d668659 --- /dev/null +++ b/docs/schemas/session-ssot.md @@ -0,0 +1,20 @@ +# Session SSOT Schema + +> **Status:** SPEC (P0) + +## Canonical layout + +``` +~/.archwiz/sessions//// + manifest.json + messages.jsonl + events.jsonl + blobs/ +``` + +Providers may retain native stores as caches. ArchWiz is the cross-provider identity layer. + +Never hardcode `/data/data/com.termux/...` in committed code. +Never represent missing timestamps as `""` — use `null` + `timestamp_source`. + +See master-staging for full JSON field definitions. diff --git a/exchanges/yobit.py b/exchanges/yobit.py deleted file mode 100644 index a90560c9d..000000000 --- a/exchanges/yobit.py +++ /dev/null @@ -1,50 +0,0 @@ -import hmac -import hashlib -import time -import requests -from urllib.parse import urlencode - -class YobitAPI: - def __init__(self, api_key, api_secret): - self.api_key = api_key - self.api_secret = api_secret - self.base_url = "https://yobit.net/tapi" - self.nonce = int(time.time()) - - def _generate_signature(self, params): - body = urlencode(params) - return hmac.new( - self.api_secret.encode(), - body.encode(), - hashlib.sha512 - ).hexdigest() - - def trade(self, pair, order_type, amount, rate): - self.nonce += 1 - params = { - 'method': 'Trade', - 'nonce': self.nonce, - 'pair': pair, - 'type': order_type, - 'amount': str(amount), - 'rate': str(rate) - } - headers = { - 'Key': self.api_key, - 'Sign': self._generate_signature(params) - } - response = requests.post(self.base_url, data=params, headers=headers) - return response.json() - - def get_balance(self): - self.nonce += 1 - params = { - 'method': 'getInfo', - 'nonce': self.nonce - } - headers = { - 'Key': self.api_key, - 'Sign': self._generate_signature(params) - } - response = requests.post(self.base_url, data=params, headers=headers) - return response.json() \ No newline at end of file diff --git a/nexuscli/README.md b/nexuscli/README.md new file mode 100644 index 000000000..a16064cfb --- /dev/null +++ b/nexuscli/README.md @@ -0,0 +1,162 @@ +# NexusCLI + +> **A fast, lightweight, Termux-optimized CLI/TUI for agent interactions.** +> Powered by `curl_cffi` and reverse-engineered DeepSeek API endpoints. + +--- + +## 🚀 Features + +- **Blazing Fast**: Uses `curl_cffi` for high-performance API calls. +- **Termux Optimized**: Designed for mobile/Termux environments. +- **Lightweight**: No Playwright or heavy dependencies. +- **Phased Architecture**: Modular design for instant execution. +- **Session Management**: Create, list, and manage chat sessions. +- **Interactive Chat**: Real-time streaming responses. +- **Export Capabilities**: Export conversations to Markdown or JSON. + +--- + +## 📦 Installation + +### 1. Clone the Repository + +```bash +cd ~/termux-monorepo +git checkout vibe/deepcode-cli_phased-fusion_fc54fa +``` + +### 2. Install Dependencies + +```bash +pip install curl_cffi rich +``` + +### 3. Set Up API Token + +```bash +export NEXUSCLI_TOKEN="" +# Or save in config +echo '{"token": ""}' > ~/.nexuscli/config.json +``` + +--- + +## 🎯 Usage + +### Basic Commands + +| Command | Description | +|---------|-------------| +| `nexuscli sessions` | List all sessions | +| `nexuscli new-session` | Create a new session | +| `nexuscli chat --last` | Start interactive chat | +| `nexuscli send --prompt "Hello" --last` | Send a single message | +| `nexuscli export --last --format markdown` | Export session history | + +### Examples + +```bash +# List all sessions +nexuscli sessions + +# Create a new expert session +nexuscli new-session --model expert --save + +# Start interactive chat with last session +nexuscli chat --last --thinking + +# Send a single message +nexuscli send --prompt "Explain Python decorators" --last + +# Export session to Markdown +nexuscli export --last --format markdown --output conversation.md +``` + +--- + +## 🏗️ Project Structure + +``` +nexuscli/ +├── __init__.py +├── core/ +│ ├── __init__.py +│ └── api.py # Core API wrapper (reverse-engineered) +├── cli/ +│ ├── __init__.py +│ └── main.py # CLI entry point +├── nexuscli.py # Launcher script +├── pow_solver.js # POW solver for API challenges +└── README.md +``` + +--- + +## 🔧 Configuration + +### Config File + +Located at `~/.nexuscli/config.json`: + +```json +{ + "token": "", + "last_session": "" +} +``` + +### Environment Variables + +- `NEXUSCLI_TOKEN` or `DEEPSEEK_TOKEN`: API token +- `NEXUSCLI_BASE_URL`: Custom API base URL (default: `https://chat.deepseek.com`) + +--- + +## 🤖 API Endpoints + +The following endpoints are reverse-engineered and used: + +| Endpoint | Description | +|----------|-------------| +| `/api/v0/chat_session/create` | Create a new chat session | +| `/api/v0/chat_session/fetch_page` | Fetch all sessions | +| `/api/v0/chat/history_messages` | Get session history | +| `/api/v0/chat/create_pow_challenge` | Get POW challenge | +| `/api/v0/chat/completion` | Send message (streaming) | +| `/api/v0/file/upload_file` | Upload a file | +| `/api/v0/share/create` | Create a share | +| `/api/v0/share/fork` | Fork a conversation | + +--- + +## 📜 License + +MIT License. See [LICENSE](../../LICENSE) for details. + +--- + +## 🙌 Contributing + +1. Fork the repository +2. Create a feature branch (`git checkout -b feature/amazing-feature`) +3. Commit your changes (`git commit -m 'Add amazing feature'`) +4. Push to the branch (`git push origin feature/amazing-feature`) +5. Open a Pull Request + +--- + +## 🔗 Related Projects + +- [termux-monorepo](https://github.com/timerloggedout-spec/termux-monorepo) +- [deepcli](https://github.com/timerloggedout-spec/termux-monorepo/tree/master/deepcli) +- [multi-ai-cli](https://github.com/timerloggedout-spec/termux-monorepo/tree/master/multi-ai-cli) +- [termux-multi-agent](https://github.com/timerloggedout-spec/termux-monorepo/tree/master/termux-multi-agent) + +--- + +## 🎨 Branding + +**NexusCLI** is part of the **Termux Monorepo** ecosystem. + +> **"All for One; and, One for All!"** 🚀 diff --git a/nexuscli/__init__.py b/nexuscli/__init__.py new file mode 100644 index 000000000..d21b443bd --- /dev/null +++ b/nexuscli/__init__.py @@ -0,0 +1,9 @@ +#!/usr/bin/env python3 +""" +NexusCLI +A fast, lightweight, Termux-optimized CLI/TUI for agent interactions. +Uses curl_cffi for high-performance API calls. +""" + +__version__ = "0.1.0" +__author__ = "Termux Monorepo Team" diff --git a/nexuscli/cli/__init__.py b/nexuscli/cli/__init__.py new file mode 100644 index 000000000..ffefa101d --- /dev/null +++ b/nexuscli/cli/__init__.py @@ -0,0 +1,8 @@ +#!/usr/bin/env python3 +""" +CLI module for DeepCode-CLI Phased Nexus. +""" + +from .main import main + +__all__ = ["main"] diff --git a/nexuscli/cli/main.py b/nexuscli/cli/main.py new file mode 100644 index 000000000..c3287399f --- /dev/null +++ b/nexuscli/cli/main.py @@ -0,0 +1,248 @@ +#!/usr/bin/env python3 +""" +Main CLI entry point for DeepCode-CLI Phased Nexus. +""" + +import argparse +import sys +import os +from pathlib import Path +from rich.console import Console +from rich.panel import Panel +from rich.prompt import Prompt, Confirm + +# Add parent directory to path for imports +sys.path.insert(0, str(Path(__file__).parent.parent)) + +from core.api import ( + get_token, + create_session, + fetch_sessions, + get_history, + stream_completion, + send_message, + export_markdown, + export_json, +) + +console = Console() + + +def print_banner(): + """Print the CLI banner.""" + banner = """ + ███╗ ██╗██╗ ██╗███████╗██╗ ██╗ + ████╗ ██║██║ ██╔╝██╔════╝╚██╗ ██╔╝ + ██╔██╗ ██║█████╔╝ █████╗ ╚████╔╝ + ██║╚██╗██║██╔═██╗ ██╔══╝ ╚██╔╝ + ██║ ╚████║██║ ██╗███████╗ ██║ + ╚═╝ ╚═══╝╚═╝ ╚═╝╚══════╝ ╚═╝ + """ + console.print(Panel.fit(banner, title="[bold cyan]NexusCLI[/]", border_style="cyan")) + + +def cmd_sessions(args): + """List all sessions.""" + token = get_token() + sessions = fetch_sessions(token) + if not sessions: + console.print("[yellow]No sessions found.[/]") + return + console.print("[bold]Sessions:[/]") + for session in sessions: + session_id = session.get("id", "N/A") + title = session.get("title", "Untitled") + console.print(f" - [cyan]{session_id}[/] | {title}") + + +def cmd_new_session(args): + """Create a new session.""" + token = get_token() + model_type = args.model or "expert" + session_id = create_session(token, model_type=model_type) + console.print(f"[green]New session created: {session_id}[/]") + if args.save: + cfg = {} + cfg_dir = Path.home() / ".deepcode-cli" + cfg_dir.mkdir(parents=True, exist_ok=True) + try: + cfg_dir.chmod(0o700) + except Exception: + pass + cfg_file = cfg_dir / "config.json" + if cfg_file.exists(): + cfg = json.loads(cfg_file.read_text()) + if args.save: + cfg_path = Path.home() / ".nexuscli" / "config.json" + cfg_path.parent.mkdir(parents=True, exist_ok=True) + cfg = {} + if cfg_path.exists(): + cfg = json.loads(cfg_path.read_text()) + cfg["last_session"] = session_id + cfg_path.write_text(json.dumps(cfg, indent=2)) + console.print("[yellow]Saved as last_session.[/]") + + +def cmd_chat(args): + """Start an interactive chat.""" + token = get_token() + session_id = args.session_id or (args.last and get_last_session()) + if not session_id: + console.print("[red]No session ID provided. Use --session-id or --last.[/]") + return + + console.print(f"[bold]Chat Session: {session_id}[/]") + + # Load history + history = get_history(token, session_id) + for msg in history: + role = msg.get("role", "user") + content = msg.get("content", "") + if role == "user": + console.print(f"[blue]You:[/] {content}") + else: + console.print(f"[green]Assistant:[/] {content}") + + # Interactive loop + parent_message_id = None + while True: + try: + prompt = Prompt.ask("[bold cyan]You[/]") + if prompt.lower() in ["exit", "quit", "q"]: + break + + console.print("[bold green]Assistant:[/] ", end="") + stream_completion( + token=token, + prompt=prompt, + session_id=session_id, + parent_message_id=parent_message_id, + thinking=args.thinking, + search=args.search, + ) + console.print() + parent_message_id = None # Reset for next message + + except KeyboardInterrupt: + console.print("\n[yellow]Interrupted. Exiting...[/]") + break + + +def cmd_send(args): + """Send a single message.""" + token = get_token() + session_id = args.session_id or get_last_session() + if not session_id: + console.print("[red]No session ID provided.[/]") + return + + response = send_message( + token=token, + session_id=session_id, + prompt=args.prompt, + thinking=args.thinking, + search=args.search, + ) + console.print(f"[green]Response:[/] {response}") + + +def cmd_export(args): + """Export session history.""" + token = get_token() + session_id = args.session_id or get_last_session() + if not session_id: + console.print("[red]No session ID provided.[/]") + return + + if args.format == "markdown": + content = export_markdown(token, session_id) + else: + content = export_json(token, session_id) + + if args.output: + with open(args.output, "w") as f: + f.write(content) + try: + os.chmod(args.output, 0o600) + except Exception: + pass + console.print(f"[green]Exported to {args.output}[/]") + else: + console.print(content) + + +def get_last_session(): + """Get the last session ID from config.""" + cfg_path = Path.home() / ".nexuscli" / "config.json" + if cfg_path.exists(): + cfg = json.loads(cfg_path.read_text()) + return cfg.get("last_session") + return None + + +def main(): + """Main CLI entry point.""" + import json + + parser = argparse.ArgumentParser( + description="NexusCLI - A fast, lightweight CLI for agent interactions.", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=""" +Examples: + nexuscli sessions List all sessions + nexuscli new-session Create a new session + nexuscli chat --last Start chat with last session + nexuscli send --prompt "Hello" --last + nexuscli export --last --format markdown + """ + ) + + subparsers = parser.add_subparsers(dest="command", help="Available commands") + + # Sessions command + sessions_parser = subparsers.add_parser("sessions", help="List all sessions") + sessions_parser.set_defaults(func=cmd_sessions) + + # New session command + new_session_parser = subparsers.add_parser("new-session", help="Create a new session") + new_session_parser.add_argument("--model", type=str, default="expert", help="Model type (expert/instant)") + new_session_parser.add_argument("--save", action="store_true", help="Save as last session") + new_session_parser.set_defaults(func=cmd_new_session) + + # Chat command + chat_parser = subparsers.add_parser("chat", help="Start an interactive chat") + chat_parser.add_argument("--session-id", type=str, help="Session ID") + chat_parser.add_argument("--last", action="store_true", help="Use last session") + chat_parser.add_argument("--thinking", action="store_true", help="Enable thinking mode") + chat_parser.add_argument("--search", action="store_true", help="Enable search mode") + chat_parser.set_defaults(func=cmd_chat) + + # Send command + send_parser = subparsers.add_parser("send", help="Send a single message") + send_parser.add_argument("--prompt", type=str, required=True, help="Prompt to send") + send_parser.add_argument("--session-id", type=str, help="Session ID") + send_parser.add_argument("--last", action="store_true", help="Use last session") + send_parser.add_argument("--thinking", action="store_true", help="Enable thinking mode") + send_parser.add_argument("--search", action="store_true", help="Enable search mode") + send_parser.set_defaults(func=cmd_send) + + # Export command + export_parser = subparsers.add_parser("export", help="Export session history") + export_parser.add_argument("--session-id", type=str, help="Session ID") + export_parser.add_argument("--last", action="store_true", help="Use last session") + export_parser.add_argument("--format", type=str, default="markdown", choices=["markdown", "json"], help="Export format") + export_parser.add_argument("--output", type=str, help="Output file path") + export_parser.set_defaults(func=cmd_export) + + args = parser.parse_args() + + if not hasattr(args, "func"): + parser.print_help() + return + + print_banner() + args.func(args) + + +if __name__ == "__main__": + main() diff --git a/nexuscli/core/__init__.py b/nexuscli/core/__init__.py new file mode 100644 index 000000000..e05e4153e --- /dev/null +++ b/nexuscli/core/__init__.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python3 +""" +Core API wrapper for NexusCLI. +Reverse-engineered from deepcli/core.py with optimizations for Termux. +""" + +from .api import ( + get_session, + solve_pow, + create_session, + fetch_sessions, + get_history, + get_pow_challenge, + upload_file, + wait_for_file, + branch_conversation, + stream_completion, + send_message, + chat_completion, + export_markdown, + export_json, +) + +__all__ = [ + "get_session", + "solve_pow", + "create_session", + "fetch_sessions", + "get_history", + "get_pow_challenge", + "upload_file", + "wait_for_file", + "branch_conversation", + "stream_completion", + "send_message", + "chat_completion", + "export_markdown", + "export_json", +] diff --git a/nexuscli/core/api.py b/nexuscli/core/api.py new file mode 100644 index 000000000..3fe20bf55 --- /dev/null +++ b/nexuscli/core/api.py @@ -0,0 +1,459 @@ +#!/usr/bin/env python3 +""" +Core API wrapper for NexusCLI. +Optimized for Termux with curl_cffi and lightweight patterns. +""" + +import os +import json +import base64 +import time +import subprocess +import random +from pathlib import Path +from typing import Optional, List, Dict, Any +from curl_cffi import requests as curl_requests +import requests as http_requests +from rich.console import Console + +console = Console() + +# Configuration +CONFIG_DIR = Path.home() / ".nexuscli" +CONFIG_FILE = CONFIG_DIR / "config.json" +WASM_SOLVER = Path(__file__).parent.parent / "pow_solver.js" +BASE_URL = "https://chat.deepseek.com" + +CONFIG_DIR.mkdir(parents=True, exist_ok=True) +try: + CONFIG_DIR.chmod(0o700) +except Exception: + pass + +# Persistent session (cookies preserved across API calls) +_session: Optional[curl_requests.Session] = None +_sessions: Dict[str, curl_requests.Session] = {} + +# ---------- Cache Helpers ---------- + +def _cache_path(session_id: str, account: str = "primary") -> str: + store_dir = os.path.join(os.path.expanduser("~/.nexuscli/session_store"), account) + os.makedirs(store_dir, exist_ok=True) + try: + os.chmod(os.path.dirname(store_dir), 0o700) + os.chmod(store_dir, 0o700) + except Exception: + pass + return os.path.join(store_dir, f"{session_id}.json") + + +def _cache_load(session_id: str, account: str = "primary") -> Optional[List[Dict[str, Any]]]: + path = _cache_path(session_id, account) + if os.path.exists(path): + with open(path) as f: + return json.load(f) + return None + + +def _cache_save(session_id: str, messages: List[Dict[str, Any]], account: str = "primary"): + path = _cache_path(session_id, account) + os.makedirs(os.path.dirname(path), exist_ok=True) + try: + os.chmod(os.path.dirname(path), 0o700) + except Exception: + pass + with open(path, 'w') as f: + json.dump(messages, f, indent=2) + try: + os.chmod(path, 0o600) + except Exception: + pass + + +# ---------- Config Helpers ---------- + +def load_config() -> Dict[str, Any]: + if CONFIG_FILE.exists(): + return json.loads(CONFIG_FILE.read_text()) + return {} + + +def save_config(cfg: Dict[str, Any]): + CONFIG_FILE.write_text(json.dumps(cfg, indent=2)) + try: + CONFIG_FILE.chmod(0o600) + except Exception: + pass + + +def get_token() -> str: + token = os.environ.get("NEXUSCLI_TOKEN") or os.environ.get("DEEPSEEK_TOKEN") + if not token: + cfg = load_config() + token = cfg.get("token") + if not token: + console.print("[red]No token found. Run 'nexuscli import-session' or set NEXUSCLI_TOKEN[/]") + raise ValueError("No API token found") + return token + + +# ---------- HTTP Session ---------- + +def get_session(token: str, cookie: str = None) -> curl_requests.Session: + global _session, _sessions + cache_key = (token[:20] + '_' + (cookie or ''))[:30] + + if cache_key in _sessions: + _session = _sessions[cache_key] + _session.headers["Authorization"] = f"Bearer {token}" + else: + _session = curl_requests.Session() + _session.headers.update({ + "User-Agent": "Mozilla/5.0 (Linux; Android 10; Termux) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36", + "Accept": "*/*", + "Accept-Language": "en-US,en;q=0.5", + "Authorization": f"Bearer {token}", + "X-Client-Platform": "web", + "X-Client-Version": "1.3.0-nexuscli", + "X-App-Version": "20241129.1", + "X-Client-Locale": "en_US", + "Origin": BASE_URL, + "Referer": f"{BASE_URL}/", + "sec-ch-ua": '"Not)A;Brand";v="8", "Chromium";v="138"', + "sec-ch-ua-mobile": "?1", + "sec-ch-ua-platform": '"Android"', + "sec-fetch-dest": "empty", + "sec-fetch-mode": "cors", + "sec-fetch-site": "same-origin", + }) + _sessions[cache_key] = _session + + if cookie: + _session.cookies.set("ds_session_id", cookie.split("=", 1)[1] if "=" in cookie else cookie) + return _session + + +# ---------- POW Solver ---------- + +def solve_pow(challenge: dict) -> str: + inp = json.dumps(challenge) + try: + proc = subprocess.run( + ["node", str(WASM_SOLVER)], + input=inp, + capture_output=True, + text=True, + timeout=10 + ) + if proc.returncode != 0: + raise RuntimeError(f"POW solver error: {proc.stderr.strip()}") + answer = int(proc.stdout.strip()) + except Exception as e: + console.print(f"[red]POW solving failed: {e}[/]") + raise + + payload = { + "algorithm": challenge.get("algorithm", "DeepSeekHashV1"), + "challenge": challenge["challenge"], + "salt": challenge["salt"], + "answer": answer, + "signature": challenge["signature"], + "target_path": challenge.get("target_path", "/api/v0/chat/completion") + } + return base64.b64encode(json.dumps(payload).encode()).decode() + + +# ---------- API Wrappers ---------- + +def create_session(token: str, model_type: str = "expert", cookie: str = None) -> str: + s = get_session(token, cookie=cookie) + if cookie: + console.print(f"[DEBUG] create_session using cookie: {cookie[:30]}...") + r = s.post(f"{BASE_URL}/api/v0/chat_session/create", json={"character_id": None, "model_type": model_type}) + console.print(f"[yellow]create_session status: {r.status_code}[/]") + r.raise_for_status() + return r.json()["data"]["biz_data"]["id"] + + +def fetch_sessions(token: str) -> List[Dict[str, Any]]: + s = get_session(token) + r = s.get(f"{BASE_URL}/api/v0/chat_session/fetch_page") + r.raise_for_status() + data = r.json()["data"]["biz_data"] + return data.get("chat_sessions", data.get("sessions", [])) + + +def get_history(token: str, session_id: str, force_refresh: bool = False, account: str = "primary") -> List[Dict[str, Any]]: + if not force_refresh: + cached = _cache_load(session_id, account) + if cached is not None: + return cached + s = get_session(token) + r = s.get(f"{BASE_URL}/api/v0/chat/history_messages?chat_session_id={session_id}") + if r.status_code != 200: + console.print(f"[red]Failed to fetch history (status {r.status_code}): {r.text[:200]}[/]") + r.raise_for_status() + data = r.json()["data"]["biz_data"]["chat_messages"] + _cache_save(session_id, data, account) + return data + + +def get_pow_challenge(token: str, target_path="/api/v0/chat/completion") -> dict: + s = get_session(token) + r = s.post(f"{BASE_URL}/api/v0/chat/create_pow_challenge", json={"target_path": target_path}) + r.raise_for_status() + return r.json()["data"]["biz_data"]["challenge"] + + +def upload_file(token: str, session_id: str, file_path: str) -> Optional[str]: + if not Path(file_path).exists(): + console.print(f"[red]File not found: {file_path}[/]") + return None + challenge = get_pow_challenge(token, "/api/v0/file/upload_file") + pow_header = solve_pow(challenge) + + s = get_session(token) + s.headers["X-Ds-Pow-Response"] = pow_header + with open(file_path, "rb") as f: + file_bytes = f.read() + upload_headers = {k: v for k, v in s.headers.items()} + upload_headers["X-Ds-Pow-Response"] = pow_header + r = http_requests.post( + f"{BASE_URL}/api/v0/file/upload_file", + files={"file": (Path(file_path).name, file_bytes, "application/octet-stream")}, + headers=upload_headers + ) + r.raise_for_status() + file_id = r.json().get("data", {}).get("biz_data", {}).get("id") or r.json().get("data", {}).get("file_id") + console.print(f"[green]File uploaded, ID: {file_id}[/]") + return file_id + + +def wait_for_file(token: str, file_id: str, timeout=30): + s = get_session(token) + start = time.time() + while time.time() - start < timeout: + r = s.get(f"{BASE_URL}/api/v0/file/fetch_files?file_ids={file_id}") + r.raise_for_status() + status = r.json()["data"]["biz_data"]["files"][0]["status"] + if status == "SUCCESS": + return True + time.sleep(1) + console.print(f"[yellow]File {file_id} processing timed out[/]") + return False + + +def branch_conversation(token: str, session_id: str, message_id: str) -> Optional[str]: + s = get_session(token) + session_referer = f"{BASE_URL}/a/chat/s/{session_id}" + history = get_history(token, session_id, force_refresh=True) + msg_map = {m["message_id"]: m for m in history} + target = msg_map.get(message_id) + if not target or target.get("role", "").upper() != "ASSISTANT": + console.print("[red]Branch target must be an ASSISTANT message.[/]") + return None + parent_id = target.get("parent_id") + if not parent_id or parent_id not in msg_map: + console.print("[red]Could not find parent USER message for branching.[/]") + return None + if msg_map[parent_id].get("role", "").upper() != "USER": + console.print("[red]Parent message is not a USER message. Cannot branch.[/]") + return None + + share_payload = {"chat_session_id": session_id, "message_ids": [parent_id, message_id]} + share_r = s.post(f"{BASE_URL}/api/v0/share/create", json=share_payload, headers={"Referer": session_referer}) + share_r.raise_for_status() + share_id = share_r.json()["data"]["biz_data"]["share_id"] + + fork_payload = {"share_id": share_id} + fork_r = s.post(f"{BASE_URL}/api/v0/share/fork", json=fork_payload, headers={"Referer": session_referer}) + fork_r.raise_for_status() + new_sid = fork_r.json()["data"]["biz_data"]["chat_session_id"] + console.print(f"[green]Branched to new session: {new_sid}[/]") + return new_sid + + +# ---------- Streaming Completion ---------- + +def stream_completion( + token: str, + prompt: str, + session_id: str, + parent_message_id: Optional[str] = None, + thinking: bool = False, + search: bool = False, + file_ids: Optional[List[str]] = None, + auto_retry: bool = True, + max_retries: int = 8, +): + challenge = get_pow_challenge(token, "/api/v0/chat/completion") + pow_header = solve_pow(challenge) + + payload = { + "chat_session_id": session_id, + "parent_message_id": int(parent_message_id) if parent_message_id else None, + "prompt": prompt, + "ref_file_ids": file_ids or [], + "thinking_enabled": thinking, + "search_enabled": search, + "stream": True + } + + base_sess = get_session(token) + headers = base_sess.headers.copy() + headers["X-Ds-Pow-Response"] = pow_header + headers["Content-Type"] = "application/json" + headers["Accept"] = "text/event-stream" + + url = f"{BASE_URL}/api/v0/chat/completion" + retries = 0 + base_delay = 2 + + while retries < max_retries: + try: + resp = base_sess.post(url, json=payload, headers=headers, stream=True) + body_preview = resp.text[:200] if hasattr(resp, 'text') else '' + + if 'Update to the latest version' in body_preview: + console.print("[yellow]Expert mode unavailable – retrying as instant.[/]") + payload['thinking_enabled'] = False + payload['search_enabled'] = False + retries += 1 + time.sleep(1) + continue + + if resp.status_code in (403, 503): + retries += 1 + delay = base_delay * (2 ** min(retries, 5)) + random.uniform(0, base_delay) + delay = min(delay, 90) + console.print(f"[yellow]Server busy/blocked, retrying in {delay:.1f}s (attempt {retries})...[/]") + time.sleep(delay) + continue + + if resp.status_code != 200: + body = resp.text[:300] + retries += 1 + delay = base_delay * (2 ** min(retries, 5)) + random.uniform(0, 3) + delay = min(delay, 90) + console.print(f"[red]API error {resp.status_code}: {body}[/] retry in {delay:.1f}s") + if 'expert' in body.lower() or 'upgrade' in body.lower(): + console.print("[yellow]Expert mode unavailable – falling back to instant.[/]") + payload['thinking_enabled'] = False + payload['search_enabled'] = False + time.sleep(delay) + continue + + # Parse SSE manually + raw = resp.content.decode('utf-8', errors='replace') + for line in raw.split('\n'): + if not line.strip(): + continue + if line.startswith('data:'): + try: + data = json.loads(line[5:].strip()) + if isinstance(data, dict): + chunk = data.get("v") or data.get("content") + if chunk and isinstance(chunk, str) and chunk != "FINISHED": + console.print(chunk, end="") + except json.JSONDecodeError: + pass + return + + except Exception as e: + retries += 1 + delay = min(10 * retries, 60) + console.print(f"[red]Request error: {e}. Retrying in {delay}s...[/]") + time.sleep(delay) + + console.print("[red]Failed after multiple retries.[/]") + + +# ---------- Non-Streaming Send ---------- + +def send_message( + token: str, + session_id: str, + prompt: str, + parent_message_id: str = None, + thinking: bool = False, + search: bool = False, +) -> str: + challenge = get_pow_challenge(token, '/api/v0/chat/completion') + pow_header = solve_pow(challenge) + payload = { + 'chat_session_id': session_id, + 'parent_message_id': parent_message_id, + 'prompt': prompt, + 'ref_file_ids': [], + 'thinking_enabled': thinking, + 'search_enabled': search, + 'stream': False, + } + s = get_session(token) + headers = s.headers.copy() + headers['X-Ds-Pow-Response'] = pow_header + headers['Content-Type'] = 'application/json' + resp = http_requests.post(f'{BASE_URL}/api/v0/chat/completion', json=payload, headers=headers) + resp.raise_for_status() + data = resp.json() + return data['choices'][0]['message']['content'] + + +# ---------- Chat Completion Wrapper ---------- + +def chat_completion( + token: str, + prompt: str, + session_id: str, + parent_message_id: Optional[str] = None, + thinking: bool = False, + search: bool = False, + file_ids: Optional[List[str]] = None, + auto_continue: bool = True, + max_continues: int = 3, +) -> str: + import io + from contextlib import redirect_stdout + + full_output = "" + current_parent = parent_message_id + + for cont in range(max_continues + 1): + f = io.StringIO() + with redirect_stdout(f): + stream_completion( + token, prompt if cont == 0 else "", + session_id, current_parent, + thinking, search, file_ids, auto_retry=True + ) + chunk = f.getvalue() + full_output += chunk + + if not auto_continue: + break + + # Check if server asked for continue (simplified) + if "auto_resume" in chunk.lower(): + break + + return full_output + + +# ---------- Export Utilities ---------- + +def export_markdown(token: str, session_id: str) -> str: + messages = get_history(token, session_id) + md = "" + for msg in messages: + role = msg.get("role", "user") + content = msg.get("content", "") + if role == "user": + md += f"### 👤 You\n{content}\n\n" + else: + md += f"### 🤖 DeepSeek\n{content}\n\n" + return md + + +def export_json(token: str, session_id: str) -> str: + messages = get_history(token, session_id) + return json.dumps(messages, indent=2) diff --git a/nexuscli/nexuscli.py b/nexuscli/nexuscli.py new file mode 100644 index 000000000..952754653 --- /dev/null +++ b/nexuscli/nexuscli.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python3 +""" +NexusCLI Launcher +""" + +import sys +from pathlib import Path + +# Ensure the package is importable +sys.path.insert(0, str(Path(__file__).parent)) + +from cli.main import main + +if __name__ == "__main__": + main() diff --git a/nexuscli/pow_solver.js b/nexuscli/pow_solver.js new file mode 100644 index 000000000..0e32f9d20 --- /dev/null +++ b/nexuscli/pow_solver.js @@ -0,0 +1,52 @@ +// POW Solver for DeepSeek API +// Used by deepcode-cli to solve Proof-of-Work challenges + +const crypto = require('crypto'); + +function solveDeepSeekHashV1(challenge, salt, signature, targetPath) { + const target = targetPath || '/api/v0/chat/completion'; + let answer = 0; + const prefix = crypto.createHash('sha256') + .update(challenge + target + salt + signature) + .digest('hex') + .substring(0, 16); + + while (true) { + const input = prefix + answer.toString(); + const hash = crypto.createHash('sha256').update(input).digest('hex'); + if (hash.startsWith('000000')) { + return answer; + } + answer++; + } +} + +const input = JSON.parse(require('fs').readFileSync(0, 'utf-8').trim()); +const challenge = input.challenge; +const salt = input.salt; +const signature = input.signature; +const targetPath = input.target_path || '/api/v0/chat/completion'; +const algorithm = input.algorithm || 'DeepSeekHashV1'; + +let answer; +if (algorithm === 'DeepSeekHashV1') { + answer = solveDeepSeekHashV1(challenge, salt, signature, targetPath); +} else { + // Fallback for other algorithms + answer = 0; + const prefix = crypto.createHash('sha256') + .update(challenge + targetPath + salt + signature) + .digest('hex') + .substring(0, 16); + + while (true) { + const inputStr = prefix + answer.toString(); + const hash = crypto.createHash('sha256').update(inputStr).digest('hex'); + if (hash.startsWith('000000')) { + break; + } + answer++; + } +} + +console.log(answer); diff --git a/nexuscli/requirements.txt b/nexuscli/requirements.txt new file mode 100644 index 000000000..b58f96341 --- /dev/null +++ b/nexuscli/requirements.txt @@ -0,0 +1,14 @@ +# NexusCLI Dependencies +# Optimized for Termux and lightweight execution + +# Core dependencies +curl_cffi>=0.5.0 +rich>=13.0.0 + +# Optional: For file uploads +requests>=2.31.0 + +# Development dependencies (optional) +# pytest>=7.0.0 +# black>=23.0.0 +# flake8>=6.0.0 diff --git a/src/context_collector.py b/src/context_collector.py index ced7dac15..f2a28bac6 100644 --- a/src/context_collector.py +++ b/src/context_collector.py @@ -54,12 +54,21 @@ def generate_ast_skeleton(self, file_relative_path): return f"// Unable to trace AST module boundary map for {file_relative_path}" def assemble_minimized_bundle(self, active_target_file): + """ + Assemble a minimized context bundle containing dependent-file structures and the active file's full source. + + Parameters: + active_target_file (str): Relative path of the file to include as the active editing target. + + Returns: + str: Formatted architecture context containing dependency skeletons and the active file source. + """ dependencies = self.find_dependent_files(active_target_file) bundle = ["=== CODEBASE ARCHITECTURE SUBSTRUCTURE CONTEXT ==="] for dep in dependencies: skeleton = self.generate_ast_skeleton(dep) - bundle.append(f"\n\n{skeleton}\n") + bundle.append(f'\n\n{skeleton}\n') with open(os.path.join(self.workspace, active_target_file), 'r') as f: full_source = f.read() - bundle.append(f"\n\n{full_source}\n") - return "\n".join(bundle) \ No newline at end of file + bundle.append(f'\n\n{full_source}\n') + return "\n".join(bundle) diff --git a/src/db.py b/src/db.py index 3c8ee3d38..a1fbbb55c 100644 --- a/src/db.py +++ b/src/db.py @@ -7,6 +7,9 @@ DB_PATH = "local_repo.db" def init_db(): + """ + Create the SQLite database and required tables if they do not already exist. + """ with sqlite3.connect(DB_PATH) as conn: cursor = conn.cursor() cursor.execute(''' @@ -35,26 +38,25 @@ def init_db(): verdict TEXT, timestamp DATETIME DEFAULT CURRENT_TIMESTAMP )''') + cursor.execute(''' + CREATE VIRTUAL TABLE IF NOT EXISTS messages_fts USING fts5( + content, + session_id UNINDEXED, + msg_idx UNINDEXED + )''') conn.commit() -from pathlib import Path -from datetime import datetime, timezone - - # Also write to run_history.jsonl for ForeSight - rh_file = Path.home() / 'termux-multi-agent/run_history.jsonl' - rh_file.parent.mkdir(parents=True, exist_ok=True) - with open(rh_file, 'a') as rhf: - json.dump({ - "target_file": target_file, - "verdict": verdict, - "attempt": attempt, - "patch": patch[:200], - "errors": errors[:200] if errors else "", - "timestamp": datetime.now(timezone.utc).isoformat() - }, rhf) - rhf.write('\n') - def log_attempt_telemetry(target_file, attempt, patch, errors, verdict): + """ + Record a patch attempt and its outcome in the run history. + + Parameters: + target_file: Path of the file targeted by the attempt. + attempt: Attempt number. + patch: Patch content associated with the attempt. + errors: Errors reported for the attempt. + verdict: Outcome assigned to the attempt. + """ with sqlite3.connect(DB_PATH) as conn: cursor = conn.cursor() cursor.execute( @@ -63,7 +65,33 @@ def log_attempt_telemetry(target_file, attempt, patch, errors, verdict): ) conn.commit() -def index_project_file(workspace_root, relative_path): + # Also write to run_history.jsonl for ForeSight + try: + from pathlib import Path + from datetime import datetime, timezone + rh_file = Path.home() / 'termux-multi-agent/run_history.jsonl' + rh_file.parent.mkdir(parents=True, exist_ok=True) + with open(rh_file, 'a') as rhf: + json.dump({ + "target_file": target_file, + "verdict": verdict, + "attempt": attempt, + "patch": patch[:200], + "errors": errors[:200] if errors else "", + "timestamp": datetime.now(timezone.utc).isoformat() + }, rhf) + rhf.write('\n') + except Exception: + pass + +def index_project_file(workspace_root, relative_path, conn=None): + """ + Index a supported project file's code elements and import relationships in the database. + + Parameters: + workspace_root: Root directory of the project. + relative_path: File path relative to the project root. + """ abs_path = os.path.join(workspace_root, relative_path) ext = os.path.splitext(relative_path)[1] lang_map = {'.py': 'python', '.js': 'javascript', '.mjs': 'javascript', '.rs': 'rust'} @@ -71,31 +99,110 @@ def index_project_file(workspace_root, relative_path): if not lang: return try: + # Run first ast-grep to scan nodes output = subprocess.check_output(["ast-grep", "scan", "--json", abs_path], text=True) nodes = json.loads(output) - with sqlite3.connect(DB_PATH) as conn: - cursor = conn.cursor() - for node in nodes: - node_id = f"{relative_path}:{node.get('range', {}).get('start', {}).get('line', 0)}" - cursor.execute( - "INSERT OR REPLACE INTO nodes VALUES (?, ?, ?, ?, ?, ?)", - (node_id, relative_path, lang, node.get('kind'), node.get('text', '')[:50], - node.get('range', {}).get('start', {}).get('line', 0)) - ) + + # Run second ast-grep to scan imports import_pattern = "import $MOD from '$PATH'" if lang == 'javascript' else "import $MOD" import_output = subprocess.check_output( ["ast-grep", "scan", "--pattern", import_pattern, "--json", abs_path], text=True ) import_nodes = json.loads(import_output) + + # Batch node database entries + node_data = [] + for node in nodes: + node_id = f"{relative_path}:{node.get('range', {}).get('start', {}).get('line', 0)}" + node_data.append(( + node_id, relative_path, lang, node.get('kind'), node.get('text', '')[:50], + node.get('range', {}).get('start', {}).get('line', 0) + )) + + # Batch import edge database entries + edge_data = [] for imp in import_nodes: imp_text = imp.get('text', '') - quoted_paths = re.findall(r"['"](.*?)['"]", imp_text) + quoted_paths = re.findall(r'["\'](.*?)["\']', imp_text) for target in quoted_paths: clean_target = target.lstrip('./').replace('.js', '').replace('.py', '') - cursor.execute( - "INSERT OR IGNORE INTO edges VALUES (?, ?, ?)", - (relative_path, clean_target, "imports") - ) - conn.commit() + edge_data.append((relative_path, clean_target, "imports")) + + # Database transaction using batch executemany for high performance + close_conn = False + if conn is None: + conn = sqlite3.connect(DB_PATH) + close_conn = True + + try: + cursor = conn.cursor() + if node_data: + cursor.executemany("INSERT OR REPLACE INTO nodes VALUES (?, ?, ?, ?, ?, ?)", node_data) + if edge_data: + cursor.executemany("INSERT OR IGNORE INTO edges VALUES (?, ?, ?)", edge_data) + if close_conn: + conn.commit() + finally: + if close_conn: + conn.close() except Exception: - pass \ No newline at end of file + pass + +def batch_insert_fts_messages(messages, conn=None): + """ + Insert message records into the full-text search table in a single batch. + + Parameters: + messages (iterable): Message dictionaries with `content`, `session_id`, and + `msg_idx` fields, or three-item iterables containing those values. + """ + data = [] + for msg in messages: + if isinstance(msg, dict): + content = msg.get('content', '') + session_id = msg.get('session_id', '') + msg_idx = msg.get('msg_idx', 0) + else: + content, session_id, msg_idx = msg + data.append((content, session_id, msg_idx)) + + close_conn = False + if conn is None: + conn = sqlite3.connect(DB_PATH) + close_conn = True + try: + cursor = conn.cursor() + if data: + cursor.executemany("INSERT INTO messages_fts(content, session_id, msg_idx) VALUES (?, ?, ?)", data) + if close_conn: + conn.commit() + finally: + if close_conn: + conn.close() + +def search_fts_messages(query, limit=10, conn=None): + """ + Search indexed messages for matches in their content. + + Parameters: + query (str): FTS5 query used to match message content. + limit (int): Maximum number of matching messages to return. + conn: Optional SQLite database connection. + + Returns: + list: Tuples containing a highlighted content snippet, session ID, and message index. + """ + close_conn = False + if conn is None: + conn = sqlite3.connect(DB_PATH) + close_conn = True + try: + cursor = conn.cursor() + cursor.execute( + "SELECT snippet(messages_fts, 0, '', '', '…', 40), session_id, msg_idx FROM messages_fts WHERE content MATCH ? LIMIT ?", + (query, limit) + ) + return cursor.fetchall() + finally: + if close_conn: + conn.close() diff --git a/termux-multi-agent/dashboard.py b/termux-multi-agent/dashboard.py index f313419d8..1bead032e 100644 --- a/termux-multi-agent/dashboard.py +++ b/termux-multi-agent/dashboard.py @@ -1,11 +1,24 @@ +#!/usr/bin/env python3 import time import os import json +import sys +from datetime import datetime -TELEMETRY_LOG = "agent_telemetry_stream.json" +try: + from rich.console import Console, Group + from rich.table import Table + from rich.panel import Panel + from rich.live import Live + from rich.text import Text + from rich.box import ROUNDED +except ImportError: + # Clean fallback warning + print("[ERROR] 'rich' library is required. Please run: pip install rich") + sys.exit(1) -def clear_screen(): - print("\033[H\033[J", end="") +TELEMETRY_LOG = "agent_telemetry_stream.json" +console = Console() def read_latest_telemetry(): if not os.path.exists(TELEMETRY_LOG): @@ -16,54 +29,136 @@ def read_latest_telemetry(): for line in f: if not line.strip(): continue - entry = json.loads(line) - target = entry.get("target") or "System" - active_jobs[target] = entry + try: + entry = json.loads(line) + target = entry.get("target") or "System" + active_jobs[target] = entry + except json.JSONDecodeError: + continue except Exception: pass - return list(active_jobs.values()) - -def render_dashboard(): - clear_screen() - print("=" * 65) - print(" ⚡ TERMUX MULTI-AGENT PARALLEL TELEMETRY DASHBOARD ⚡ ") - print("=" * 65) - print(f" Last Sync: {time.strftime('%Y-%m-%d %H:%M:%S')}") - print("-" * 65) - print(f"{'TARGET FILE':<20} | {'AGENT':<16} | {'TRY':<4} | {'STATUS':<15}") - print("-" * 65) + # Sort by timestamp so the list ordering is consistent/predictable + return sorted(active_jobs.values(), key=lambda x: x.get("timestamp", "")) +def make_dashboard(): + # Read data jobs = read_latest_telemetry() + + # Header info + now_str = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + header_text = Text() + header_text.append("⚡ TERMUX MULTI-AGENT PARALLEL TELEMETRY ⚡\n", style="bold yellow") + header_text.append(f"Last Sync: {now_str} | File: {TELEMETRY_LOG}", style="dim") + + header_panel = Panel( + header_text, + box=ROUNDED, + border_style="yellow", + expand=True, + ) + if not jobs: - print(" [ Waiting for background agent pipelines to initialize... ]") + # Beautiful empty state + empty_text = Text() + empty_text.append("\n[ Waiting for background agent pipelines to initialize... ]\n\n", style="italic cyan") + empty_text.append("To start the multi-agent orchestration pipeline, run:\n", style="dim") + empty_text.append(" ./run_agent.sh\n", style="bold green") + empty_text.append("\nThis dashboard will automatically update once events are received.\n", style="dim") + empty_text.append("Press Ctrl+C to exit.", style="dim red") + + body_panel = Panel( + empty_text, + title="System Status", + box=ROUNDED, + border_style="cyan", + expand=True + ) + return Panel( + Group( + header_panel, + body_panel + ), + box=ROUNDED, + border_style="dim" + ) + + # Beautiful table + table = Table(box=ROUNDED, border_style="dim", expand=True) + table.add_column("Target File", style="bold cyan", no_wrap=True) + table.add_column("Agent", style="bold magenta") + table.add_column("Try", justify="center", style="yellow") + table.add_column("Status", justify="center") + table.add_column("Last Message", style="white") + table.add_column("Timestamp", style="dim", justify="right") + for job in jobs: target = job.get("target") or "Global" - if len(target) > 18: - target = "..." + target[-15:] + # truncate long targets cleanly + if len(target) > 25: + target = "..." + target[-22:] + agent = job.get("agent", "Unknown") attempt = str(job.get("attempt") or "-") level = job.get("level", "INFO") + message = job.get("message", "") + timestamp = job.get("timestamp", "") + if timestamp: + # Format time if it has full date/time + try: + dt = datetime.strptime(timestamp, "%Y-%m-%d %H:%M:%S") + timestamp = dt.strftime("%H:%M:%S") + except ValueError: + pass + # Beautiful styled status tag if level == "SUCCESS": - status_str = "\033[92mSUCCESS\033[0m" + status_str = Text("SUCCESS", style="bold green") elif level == "RETRY": - status_str = "\033[93mRETRYING\033[0m" + status_str = Text("RETRYING", style="bold yellow") elif level == "CRITICAL": - status_str = "\033[91mCRITICAL\033[0m" + status_str = Text("CRITICAL", style="bold red") else: - status_str = "\033[94mPROCESSING\033[0m" + status_str = Text("PROCESSING", style="bold blue") + + table.add_row( + target, + agent, + attempt, + status_str, + message, + timestamp + ) + + footer_text = Text("\nPress Ctrl+C to exit Dashboard Viewer.", style="dim italic red") - print(f"{target:<20} | {agent:<16} | {attempt:<4} | {status_str:<15}") - print(f" ↳ Msg: {job.get('message', '')[:60]}") - print("-" * 65) + return Panel( + Group( + header_panel, + table, + footer_text + ), + box=ROUNDED, + border_style="blue", + title="Agent Live Monitor" + ) def main(): try: - while True: - render_dashboard() - time.sleep(1.0) + # Use Live rendering for smooth, flicker-free updates + with Live(make_dashboard(), refresh_per_second=1, screen=True) as live: + while True: + time.sleep(1.0) + live.update(make_dashboard()) except KeyboardInterrupt: - print("\nExiting Dashboard Viewer.") + # Clear screen and say goodbye gracefully + console.clear() + console.print(Panel( + "[bold green]Thank you for using Termux Multi-Agent Dashboard![/bold green]\n" + "Stay productive and keep building! ⚡🚀", + title="Exiting Dashboard", + border_style="green", + expand=False + )) if __name__ == '__main__': - main() \ No newline at end of file + main() diff --git a/termux-multi-agent/provision_agent.py b/termux-multi-agent/provision_agent.py index 6152a3012..0ff8bd294 100644 --- a/termux-multi-agent/provision_agent.py +++ b/termux-multi-agent/provision_agent.py @@ -70,7 +70,7 @@ def log_attempt_telemetry(target_file, attempt, patch, errors, verdict): ) conn.commit() -def index_project_file(workspace_root, relative_path): +def index_project_file(workspace_root, relative_path, conn=None): abs_path = os.path.join(workspace_root, relative_path) ext = os.path.splitext(relative_path)[1] lang_map = {'.py': 'python', '.js': 'javascript', '.mjs': 'javascript', '.rs': 'rust'} @@ -78,32 +78,52 @@ def index_project_file(workspace_root, relative_path): if not lang: return try: + # Run first ast-grep to scan nodes output = subprocess.check_output(["ast-grep", "scan", "--json", abs_path], text=True) nodes = json.loads(output) - with sqlite3.connect(DB_PATH) as conn: - cursor = conn.cursor() - for node in nodes: - node_id = f"{relative_path}:{node.get('range', {}).get('start', {}).get('line', 0)}" - cursor.execute( - "INSERT OR REPLACE INTO nodes VALUES (?, ?, ?, ?, ?, ?)", - (node_id, relative_path, lang, node.get('kind'), node.get('text', '')[:50], - node.get('range', {}).get('start', {}).get('line', 0)) - ) + + # Run second ast-grep to scan imports import_pattern = "import $MOD from '$PATH'" if lang == 'javascript' else "import $MOD" import_output = subprocess.check_output( ["ast-grep", "scan", "--pattern", import_pattern, "--json", abs_path], text=True ) import_nodes = json.loads(import_output) + + # Batch node database entries + node_data = [] + for node in nodes: + node_id = f"{relative_path}:{node.get('range', {}).get('start', {}).get('line', 0)}" + node_data.append(( + node_id, relative_path, lang, node.get('kind'), node.get('text', '')[:50], + node.get('range', {}).get('start', {}).get('line', 0) + )) + + # Batch import edge database entries + edge_data = [] for imp in import_nodes: imp_text = imp.get('text', '') quoted_paths = re.findall(r"['\"](.*?)['\"]", imp_text) for target in quoted_paths: clean_target = target.lstrip('./').replace('.js', '').replace('.py', '') - cursor.execute( - "INSERT OR IGNORE INTO edges VALUES (?, ?, ?)", - (relative_path, clean_target, "imports") - ) - conn.commit() + edge_data.append((relative_path, clean_target, "imports")) + + # Database transaction using batch executemany for high performance + close_conn = False + if conn is None: + conn = sqlite3.connect(DB_PATH) + close_conn = True + + try: + cursor = conn.cursor() + if node_data: + cursor.executemany("INSERT OR REPLACE INTO nodes VALUES (?, ?, ?, ?, ?, ?)", node_data) + if edge_data: + cursor.executemany("INSERT OR IGNORE INTO edges VALUES (?, ?, ?)", edge_data) + if close_conn: + conn.commit() + finally: + if close_conn: + conn.close() except Exception: pass""", "src/sandbox.py": """import subprocess @@ -388,10 +408,23 @@ def main(): print("[+] Re-run the script or trigger run_agent.sh to start the operational pipeline loop.") sys.exit(0) - for root, _, files in os.walk(workspace_path): - for file in files: - rel_path = os.path.relpath(os.path.join(root, file), workspace_path) - index_project_file(workspace_path, rel_path) + # 1nd3x 4ll pr0j3ct f1l3s (using a single shared sqlite3 connection for speed) + import sqlite3 + from src.db import DB_PATH + try: + conn = sqlite3.connect(DB_PATH) + for root, _, files in os.walk(workspace_path): + for file in files: + rel_path = os.path.relpath(os.path.join(root, file), workspace_path) + index_project_file(workspace_path, rel_path, conn=conn) + conn.commit() + except Exception: + pass + finally: + try: + conn.close() + except Exception: + pass target_file = "test_script.py" refactor_goal = "Refactor compute to intercept and handle ZeroDivisionError scenario profiles cleanly." diff --git a/termux-multi-agent/run.py b/termux-multi-agent/run.py index 7e63ca410..779ebaa52 100644 --- a/termux-multi-agent/run.py +++ b/termux-multi-agent/run.py @@ -67,6 +67,11 @@ def get_refactor_goal(workspace_path): return default_goal def main(): + """ + Run the refactoring pipeline for the configured workspace and target file. + + Creates a default workspace and sample target when the workspace is missing. Otherwise, indexes workspace files, determines the refactoring goal, collects relevant code context, and runs the pipeline with Python compilation validation. + """ init_db() workspace_path = os.environ.get("TASK_WORKSPACE", "/data/data/com.termux/files/home/termux-multi-agent/workspace") @@ -78,11 +83,23 @@ def main(): print("[+] Re-run the script or trigger run_agent.sh to start the operational pipeline loop.") sys.exit(0) - # 1nd3x 4ll pr0j3ct f1l3s - for root, _, files in os.walk(workspace_path): - for file in files: - rel_path = os.path.relpath(os.path.join(root, file), workspace_path) - index_project_file(workspace_path, rel_path) + # 1nd3x 4ll pr0j3ct f1l3s (using a single shared sqlite3 connection for speed) + import sqlite3 + from src.db import DB_PATH + try: + conn = sqlite3.connect(DB_PATH) + for root, _, files in os.walk(workspace_path): + for file in files: + rel_path = os.path.relpath(os.path.join(root, file), workspace_path) + index_project_file(workspace_path, rel_path, conn=conn) + conn.commit() + except Exception: + pass + finally: + try: + conn.close() + except Exception: + pass # D3t3rm1n3 t4rg3t f1l3 target_name = os.environ.get("TARGET_FILE", "test_script.py") diff --git a/termux-multi-agent/src/db.py b/termux-multi-agent/src/db.py index a1efbc1eb..29debf0c9 100644 --- a/termux-multi-agent/src/db.py +++ b/termux-multi-agent/src/db.py @@ -7,6 +7,9 @@ DB_PATH = "local_repo.db" def init_db(): + """ + Create the SQLite database tables used for code indexing, run history, and message search. + """ with sqlite3.connect(DB_PATH) as conn: cursor = conn.cursor() cursor.execute(''' @@ -35,9 +38,25 @@ def init_db(): verdict TEXT, timestamp DATETIME DEFAULT CURRENT_TIMESTAMP )''') + cursor.execute(''' + CREATE VIRTUAL TABLE IF NOT EXISTS messages_fts USING fts5( + content, + session_id UNINDEXED, + msg_idx UNINDEXED + )''') conn.commit() def log_attempt_telemetry(target_file, attempt, patch, errors, verdict): + """ + Record an execution attempt and its outcome in the run history. + + Parameters: + target_file (str): Path of the file targeted by the attempt. + attempt (int): Attempt number. + patch (str): Patch content associated with the attempt. + errors (str): Errors recorded during the attempt. + verdict (str): Outcome assigned to the attempt. + """ with sqlite3.connect(DB_PATH) as conn: cursor = conn.cursor() cursor.execute( @@ -46,7 +65,18 @@ def log_attempt_telemetry(target_file, attempt, patch, errors, verdict): ) conn.commit() -def index_project_file(workspace_root, relative_path): +def index_project_file(workspace_root, relative_path, conn=None): + """ + Index a supported project file and record its code nodes and import relationships. + + Parameters: + workspace_root (str): Root directory containing the project file. + relative_path (str): File path relative to the workspace root. + conn (sqlite3.Connection, optional): Database connection to use for storing + indexed data. + + Unsupported file types and indexing failures are ignored. + """ abs_path = os.path.join(workspace_root, relative_path) ext = os.path.splitext(relative_path)[1] lang_map = {'.py': 'python', '.js': 'javascript', '.mjs': 'javascript', '.rs': 'rust'} @@ -54,32 +84,115 @@ def index_project_file(workspace_root, relative_path): if not lang: return try: - output = subprocess.check_output(["ast-grep", "run", "--pattern", ".*", "--json", abs_path], cwd="/data/data/com.termux/files/home/termux-multi-agent", text=True) + # Run first ast-grep to scan nodes (matching specific pattern or wildcard) + output = subprocess.check_output( + ["ast-grep", "run", "--pattern", ".*", "--json", abs_path], + cwd="/data/data/com.termux/files/home/termux-multi-agent", + text=True + ) nodes = json.loads(output) - with sqlite3.connect(DB_PATH) as conn: - cursor = conn.cursor() - for node in nodes: - node_id = f"{relative_path}:{node.get('range', {}).get('start', {}).get('line', 0)}" - cursor.execute( - "INSERT OR REPLACE INTO nodes VALUES (?, ?, ?, ?, ?, ?)", - (node_id, relative_path, lang, node.get('kind'), node.get('text', '')[:50], node.get('range', {}).get('start', {}).get('line', 0)) - ) + + # Run second ast-grep to scan imports import_pattern = "import $MOD from '$PATH'" if lang == 'javascript' else "import $MOD" import_output = subprocess.check_output( - ["ast-grep", "run", "--pattern", import_pattern, "--json", abs_path], cwd="/data/data/com.termux/files/home/termux-multi-agent", text=True + ["ast-grep", "run", "--pattern", import_pattern, "--json", abs_path], + cwd="/data/data/com.termux/files/home/termux-multi-agent", + text=True ) import_nodes = json.loads(import_output) - with sqlite3.connect(DB_PATH) as conn: + + # Batch node database entries + node_data = [] + for node in nodes: + node_id = f"{relative_path}:{node.get('range', {}).get('start', {}).get('line', 0)}" + node_data.append(( + node_id, relative_path, lang, node.get('kind'), node.get('text', '')[:50], + node.get('range', {}).get('start', {}).get('line', 0) + )) + + # Batch import edge database entries + edge_data = [] + for imp in import_nodes: + imp_text = imp.get('text', '') + quoted_paths = re.findall(r'["\'](.*?)["\']', imp_text) + for target in quoted_paths: + clean_target = target.lstrip('./').replace('.js', '').replace('.py', '') + edge_data.append((relative_path, clean_target, "imports")) + + # Database transaction using batch executemany for high performance + close_conn = False + if conn is None: + conn = sqlite3.connect(DB_PATH) + close_conn = True + + try: cursor = conn.cursor() - for imp in import_nodes: - imp_text = imp.get('text', '') - quoted_paths = re.findall(r'["\'](.*?)["\']', imp_text) - for target in quoted_paths: - clean_target = target.lstrip('./').replace('.js', '').replace('.py', '') - cursor.execute( - "INSERT OR IGNORE INTO edges VALUES (?, ?, ?)", - (relative_path, clean_target, "imports") - ) - conn.commit() + if node_data: + cursor.executemany("INSERT OR REPLACE INTO nodes VALUES (?, ?, ?, ?, ?, ?)", node_data) + if edge_data: + cursor.executemany("INSERT OR IGNORE INTO edges VALUES (?, ?, ?)", edge_data) + if close_conn: + conn.commit() + finally: + if close_conn: + conn.close() except Exception: pass + +def batch_insert_fts_messages(messages, conn=None): + """ + Insert message records into the full-text search index. + + Parameters: + messages: Message dictionaries or tuples containing content, session ID, and message index. + conn: Optional SQLite database connection to use. + """ + data = [] + for msg in messages: + if isinstance(msg, dict): + content = msg.get('content', '') + session_id = msg.get('session_id', '') + msg_idx = msg.get('msg_idx', 0) + else: + content, session_id, msg_idx = msg + data.append((content, session_id, msg_idx)) + + close_conn = False + if conn is None: + conn = sqlite3.connect(DB_PATH) + close_conn = True + try: + cursor = conn.cursor() + if data: + cursor.executemany("INSERT INTO messages_fts(content, session_id, msg_idx) VALUES (?, ?, ?)", data) + if close_conn: + conn.commit() + finally: + if close_conn: + conn.close() + +def search_fts_messages(query, limit=10, conn=None): + """ + Search indexed messages using an FTS5 query. + + Parameters: + query: The FTS5 query expression. + limit: Maximum number of matching messages to return. + + Returns: + A list of tuples containing highlighted message snippets, session IDs, and message indexes. + """ + close_conn = False + if conn is None: + conn = sqlite3.connect(DB_PATH) + close_conn = True + try: + cursor = conn.cursor() + cursor.execute( + "SELECT snippet(messages_fts, 0, '', '', '…', 40), session_id, msg_idx FROM messages_fts WHERE content MATCH ? LIMIT ?", + (query, limit) + ) + return cursor.fetchall() + finally: + if close_conn: + conn.close() diff --git a/termux-multi-agent/workspace/run.py b/termux-multi-agent/workspace/run.py index 7e63ca410..24920fd3f 100644 --- a/termux-multi-agent/workspace/run.py +++ b/termux-multi-agent/workspace/run.py @@ -67,6 +67,14 @@ def get_refactor_goal(workspace_path): return default_goal def main(): + """ + Run the refactoring pipeline for the configured workspace and target file. + + Initializes the project database, indexes workspace files, determines the + refactoring goal, collects codebase context, and invokes the agent with Python + compilation as the validation command. Creates a default workspace and sample + target file when the configured workspace does not exist. + """ init_db() workspace_path = os.environ.get("TASK_WORKSPACE", "/data/data/com.termux/files/home/termux-multi-agent/workspace") @@ -78,11 +86,23 @@ def main(): print("[+] Re-run the script or trigger run_agent.sh to start the operational pipeline loop.") sys.exit(0) - # 1nd3x 4ll pr0j3ct f1l3s - for root, _, files in os.walk(workspace_path): - for file in files: - rel_path = os.path.relpath(os.path.join(root, file), workspace_path) - index_project_file(workspace_path, rel_path) + # 1nd3x 4ll pr0j3ct f1l3s (using a single shared sqlite3 connection for speed) + import sqlite3 + from src.db import DB_PATH + try: + conn = sqlite3.connect(DB_PATH) + for root, _, files in os.walk(workspace_path): + for file in files: + rel_path = os.path.relpath(os.path.join(root, file), workspace_path) + index_project_file(workspace_path, rel_path, conn=conn) + conn.commit() + except Exception: + pass + finally: + try: + conn.close() + except Exception: + pass # D3t3rm1n3 t4rg3t f1l3 target_name = os.environ.get("TARGET_FILE", "test_script.py") diff --git a/tests/test_db_optimized.py b/tests/test_db_optimized.py new file mode 100644 index 000000000..ecb429b0a --- /dev/null +++ b/tests/test_db_optimized.py @@ -0,0 +1,109 @@ +import os +import sqlite3 +import pytest +import subprocess +from src.db import init_db, index_project_file, DB_PATH + +def test_db_operations(tmp_path, monkeypatch): + # Set DB path to a temporary one + temp_db = tmp_path / "test_repo.db" + monkeypatch.setattr("src.db.DB_PATH", str(temp_db)) + + # Initialize the DB + init_db() + + # Check that database and tables are created + assert temp_db.exists() + conn = sqlite3.connect(str(temp_db)) + cursor = conn.cursor() + cursor.execute("SELECT name FROM sqlite_master WHERE type='table';") + tables = [r[0] for r in cursor.fetchall()] + assert "nodes" in tables + assert "edges" in tables + assert "run_history" in tables + conn.close() + +def test_index_project_file_no_ast_grep(tmp_path, monkeypatch): + # Ensure no-op when ast-grep isn't there + temp_db = tmp_path / "test_repo.db" + monkeypatch.setattr("src.db.DB_PATH", str(temp_db)) + init_db() + + # Create a dummy python file + test_file = tmp_path / "test_file.py" + test_file.write_text("def test(): pass\n") + + # Since ast-grep is not installed/functional in this sandbox, it should catch Exception and pass gracefully + # Calling index_project_file should not crash + index_project_file(str(tmp_path), "test_file.py") + +def test_index_project_file_success(tmp_path, monkeypatch): + temp_db = tmp_path / "test_repo.db" + monkeypatch.setattr("src.db.DB_PATH", str(temp_db)) + init_db() + + # Create dummy files + test_file = tmp_path / "test_file.py" + test_file.write_text("import 'os'\n") + + # Mock subprocess.check_output to return mock nodes and mock imports + call_count = 0 + def mock_check_output(cmd, **kwargs): + nonlocal call_count + call_count += 1 + if call_count == 1: + # Nodes + return '[{"kind": "class", "range": {"start": {"line": 10}}, "text": "class Test"}]' + else: + # Imports + return '[{"text": "import \'os\'"}]' + + monkeypatch.setattr(subprocess, "check_output", mock_check_output) + + # Call indexing + index_project_file(str(tmp_path), "test_file.py") + + # Verify that nodes and edges are populated using executemany batch writes + conn = sqlite3.connect(str(temp_db)) + cursor = conn.cursor() + cursor.execute("SELECT * FROM nodes;") + nodes = cursor.fetchall() + assert len(nodes) == 1 + assert nodes[0][1] == "test_file.py" + assert nodes[0][3] == "class" + assert nodes[0][4] == "class Test" + + cursor.execute("SELECT * FROM edges;") + edges = cursor.fetchall() + assert len(edges) == 1 + assert edges[0][0] == "test_file.py" + assert edges[0][1] == "os" + conn.close() + +def test_fts5_virtual_table(tmp_path, monkeypatch): + temp_db = tmp_path / "test_repo_fts.db" + monkeypatch.setattr("src.db.DB_PATH", str(temp_db)) + init_db() + + from src.db import batch_insert_fts_messages, search_fts_messages + + # Batch insert mock messages + messages = [ + {"content": "How to dispatch a task into the queue", "session_id": "sess-01", "msg_idx": 1}, + {"content": "Optimizing SQLite loops with shared connections", "session_id": "sess-02", "msg_idx": 5}, + {"content": "Irrelevant content message block", "session_id": "sess-03", "msg_idx": 12} + ] + batch_insert_fts_messages(messages) + + # Query for "dispatch" + results = search_fts_messages("dispatch") + assert len(results) == 1 + assert "sess-01" in results[0][1] + assert results[0][2] == 1 + assert "dispatch" in results[0][0] + + # Query for "SQLite" + results = search_fts_messages("SQLite") + assert len(results) == 1 + assert "sess-02" in results[0][1] + assert "SQLite" in results[0][0] diff --git a/wiki/Architecture.md b/wiki/Architecture.md new file mode 100644 index 000000000..c036539b8 --- /dev/null +++ b/wiki/Architecture.md @@ -0,0 +1,62 @@ +# Architecture + +High-level layout of `timerloggedout-spec/termux-monorepo`. + +Visual overview (in-repo): `termux-ecosystem-architecture.html` +(Terminal → ArchWiz / Harmonizer / DeepSeek CLI-TUI / Central Mapper / Multi-Agent → DeepSeek API, local cache, workspace). + +Prefer **TOOL_INDEX** + **CONCEPT_INDEX** under `archwiz/` for tool-level truth. + +## Core automation & agents + +### deepcli/ + +DeepSeek-oriented CLI (sessions, streaming send, thinking mode, attach, fork, export). + +- Entry: `deepcli.py`, `deepapi.py`, `deepseek_proxy.py` +- Related: `deepcli-tui/`, `.deepcli/`, `deepseek-cli/` + +### termux-multi-agent/ + +Multi-agent orchestration for Termux: provision, run, patch, dashboard, Cedar MCP. + +| Path | Role | +|------|------| +| `provision_agent.py` | Agent provisioning | +| `run.py` / `run_agent.sh` | Run loop | +| `dashboard.py` | Status / control UI | +| `patch_files.py` | Patch application | +| `cedar-mcp-server.js` | CedarScript MCP server | + +### cli-synthegration/ + +Conversation synthesis: branching, export, account/token management, metrics, Chronos, Cedar bridge. + +### archwiz/ + +ArchWizard — indexing, provenance, recovery indices, automation cockpit. + +- Docs: `TOOL_INDEX.md`, `CONCEPT_INDEX.md`, `REFERENCE_HUB.md`, `METHODOLOGY_INDEX.md`, `PROCEDURES.md`, … +- Role: dashboard + pipeline control + +## Harmonizer, multi-AI, swarm + +| Directory | Role | +|-----------|------| +| `harmonizer-prod_cli/` | Production Harmonizer CLI | +| `harmony_hub/` | Harmony hub integration | +| `multi-ai-cli/` | Multi-model CLI surface | +| `commingle-swarm/` | Template / scavenge-only external clone | + +## Mapping & workspace + +- `central_mapper_v420.py`, `mapper_graph.py` +- `workspace/` (incl. llm_map indices) +- `sandbox/`, `src/`, `bin/`, `config/` + +## Notes + +- `refTemplates/` on `master` is largely a stub; full metadata skeleton lives on `recreate/refTemplates-skeleton`. +- Submodules: prefer selective depth-1 updates; avoid recursive `submodule update` by default. + +For recovery history and prioritized actions, see the root README and `docs/RECON.md`. diff --git a/wiki/DeepWiki-Mirror.md b/wiki/DeepWiki-Mirror.md new file mode 100644 index 000000000..e944d063f --- /dev/null +++ b/wiki/DeepWiki-Mirror.md @@ -0,0 +1,57 @@ +# DeepWiki → GitHub Wiki mirror + +## Source + +| Source | URL | +|--------|-----| +| Private Devin Wiki | https://app.devin.ai/org/timerloggedout-spec/wiki/timerloggedout-spec/termux-monorepo | +| Public DeepWiki | https://deepwiki.com/timerloggedout-spec/termux-monorepo | + +Devin generates structured docs (architecture, modules, diagrams). GitHub Wiki is a separate git repo (`*.wiki.git`) of Markdown pages. There is no official one-click sync; this repo uses a **folder + Action** pattern. + +## How the mirror works + +1. **Source of truth in-repo:** the `wiki/` directory (Markdown pages). +2. **Publisher:** `.github/workflows/publish-wiki.yml` runs `Andrew-Chen-Wang/github-wiki-action@v5` on pushes to `master` / `master-staging` that touch `wiki/**`, or via **workflow_dispatch**. +3. **Target:** the repository’s GitHub Wiki (`https://github.com/timerloggedout-spec/termux-monorepo/wiki`). + +### One-time bootstrap (required) + +GitHub only creates the `*.wiki.git` backend after the first wiki page exists: + +1. Open the repo → **Wiki** tab. +2. Create any page (e.g. title `Home`, body `bootstrap`). +3. Merge this feature branch (or push `wiki/` to `master`). +4. Run **Actions → Publish wiki → Run workflow** (or push any change under `wiki/`). + +If the first Action run fails because the wiki was empty, temporarily set `strategy: init` in the workflow (force-push), run once, then switch back to `strategy: clone`. + +## Refreshing content from Devin DeepWiki + +Private Devin Wiki pages are not exposed to the public DeepWiki MCP without indexing/auth. Practical refresh paths: + +1. **Manual / browser** + Open the Devin Wiki, export or copy pages (Chrome “DeepWiki to Markdown” extensions exist), save as `wiki/.md`. + +2. **CLI exporters** (public DeepWiki once indexed) + - `dw2md timerloggedout-spec/termux-monorepo -o /tmp/out.md` + - Other tools: `deepwiki-to-md`, interactive exporters on GitHub. + +3. **Ask Devin** + In a Devin session: “Export the current wiki pages for this repo as Markdown files suitable for a GitHub Wiki (Home.md + one file per page).” Commit the result under `wiki/`. + +4. **Conventions** + - `Home.md` is the landing page (not `README.md`). + - Filenames become titles (`My-Page.md` → “My Page”). + - Avoid `\ / : * ? " < > |` in titles. + - Mermaid fenced blocks usually render. + - Prefer relative wiki links: `[Architecture](Architecture)`. + +## Steering Devin’s generation + +Optional repo file `.devin/wiki.json` (repo notes + optional explicit `pages` list) steers regeneration inside Devin. That does not automatically update this GitHub Wiki; re-export into `wiki/` after regenerating. + +## Related + +- Workflow: `.github/workflows/publish-wiki.yml` +- Action docs: https://github.com/Andrew-Chen-Wang/github-wiki-action diff --git a/wiki/Home.md b/wiki/Home.md new file mode 100644 index 000000000..2b5107bc4 --- /dev/null +++ b/wiki/Home.md @@ -0,0 +1,36 @@ +# termux-monorepo + +> GitHub Wiki mirror of the Devin DeepWiki for this monorepo. + +**Live AI wiki (source of truth for generated docs):** + +- Private Devin Wiki: [app.devin.ai …/termux-monorepo](https://app.devin.ai/org/timerloggedout-spec/wiki/timerloggedout-spec/termux-monorepo) +- Public DeepWiki (if indexed): [deepwiki.com/timerloggedout-spec/termux-monorepo](https://deepwiki.com/timerloggedout-spec/termux-monorepo) + +This GitHub Wiki is kept in-repo under `wiki/` and published by `.github/workflows/publish-wiki.yml` on every push that touches `wiki/**`. + +--- + +## TL;DR + +Monorepo recovery cockpit + live project map for Termux / DeepSeek / multi-agent tooling. + +- Prefer **ArchWiz indices** for day-to-day navigation (see [Navigation](Navigation)). +- Core surfaces: `deepcli/`, `termux-multi-agent/`, `cli-synthegration/`, `archwiz/`, harmonizer / multi-AI / swarm trees. +- Recovery & refTemplates history: root `README.md` and `docs/RECON.md`. + +--- + +## Quick links + +| Page | Purpose | +|------|---------| +| [Architecture](Architecture) | Ecosystem layout and main components | +| [Navigation](Navigation) | SSOT ladder + command table | +| [DeepWiki-Mirror](DeepWiki-Mirror) | How this mirror is maintained | + +--- + +## Status + +Initial seed from repository README (2026-08). Re-export pages from Devin DeepWiki into `wiki/*.md` and push to `master` (or `master-staging`) to refresh. diff --git a/wiki/Navigation.md b/wiki/Navigation.md new file mode 100644 index 000000000..8f35588c1 --- /dev/null +++ b/wiki/Navigation.md @@ -0,0 +1,29 @@ +# Navigation + +Single source of truth ladder when orienting in the tree. + +| Priority | Start here | What you get | +|----------|------------|--------------| +| 1 | `archwiz/TOOL_INDEX.md` | 28 tools / 7 categories | +| 2 | `archwiz/CONCEPT_INDEX.md` | Concepts + status + backlog | +| 3 | `archwiz/REFERENCE_HUB.md` | Links to manifests and maps | +| 4 | `archwiz/METHODOLOGY_INDEX.md` | Approaches tried / what stuck | +| 5 | `archwiz/PROCEDURES.md`, `ARCHWIZARD_TASKS.md` | Runbooks and tasks | +| 6 | `docs/RECON.md` | Branch/PR critique, proposals | +| 7 | `replit.md` on `critical-proposal` | Critical path / config issues | +| 8 | `_Entry+ReadMe.md` | One-line command → entry table | +| 9 | `termux-ecosystem-architecture.html` | Visual ecosystem diagram | +| 10 | `refTemplates/README_RECOVERY.md` (skeleton branch) | Metadata-only restore policy | + +## Quick commands + +| What you want to do | Where you start | +|---------------------|-----------------| +| Research a file's history | `archaeo ` | +| Check impact before changing | `oracle ` | +| Make a change | `dispatch ` or `agent-shell run ` | +| Validate & promote | `validate_promotion.py` → promote | +| Rebuild indices | `map-build && map-func && fore` | +| Open cockpit | `python3 archwiz/archwiz.py` | + +Also see root `README.md` and `_Entry+ReadMe.md`. diff --git a/wiki/_Sidebar.md b/wiki/_Sidebar.md new file mode 100644 index 000000000..b30d6c5a6 --- /dev/null +++ b/wiki/_Sidebar.md @@ -0,0 +1,10 @@ +**termux-monorepo** + +- [Home](Home) +- [Architecture](Architecture) +- [Navigation](Navigation) +- [DeepWiki Mirror](DeepWiki-Mirror) + +--- + +[Devin Wiki ↗](https://app.devin.ai/org/timerloggedout-spec/wiki/timerloggedout-spec/termux-monorepo) diff --git a/workspace/llm_map/master_tasks.json b/workspace/llm_map/master_tasks.json index dc4039239..875589ed4 100644 --- a/workspace/llm_map/master_tasks.json +++ b/workspace/llm_map/master_tasks.json @@ -689,7 +689,7 @@ "priority": "medium", "assigned_agent": "developer", "account": "primary", - "status": "pending", + "status": "done", "sandbox": "~/sandbox/arch-015", "branch_from": "root", "target_file": "",