Skip to content

Commit e0a8aab

Browse files
authored
Merge branch 'main' into codex/fix-vulnerability-in-visual-capture-fallback
2 parents 8c7e1d4 + 63d5a0f commit e0a8aab

382 files changed

Lines changed: 29256 additions & 1491 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.claude/skills/contributing-to-gittensory/reference.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,9 @@ All tools are metadata-only (no source upload). Run in this order:
149149
6. `gittensory_predict_gate``{login, owner, repo, title, body, labels, linkedIssues}` → predicted
150150
conclusion + blockers + warnings + readiness score.
151151

152-
(Auth'd extras: `gittensory_preflight_pr` / `…_local_diff` for lane fit + collision + queue health.)
152+
(Auth'd extras: `gittensory_preflight_pr` / `…_local_diff` for lane fit + collision + queue health;
153+
`gittensory_get_pr_ai_review_findings``{login, owner, repo, pullNumber}` → structured post-submission
154+
AI-review inline findings (category/path/severity) for your own PR.)
153155

154156
---
155157

.env.example

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -298,9 +298,13 @@ REDIS_URL=redis://redis:6379 # REQUIRED for the self-host review
298298
# # Collection and schema are auto-created at startup. Off when unset.
299299
# QDRANT_API_KEY= # Bearer token for an authenticated Qdrant (cloud / on-prem). Omit for
300300
# # the local --profile qdrant container (unauthenticated).
301-
# QDRANT_DIM=768 # vector dimension of the collection (768 = nomic-embed-text:latest;
302-
# # 1024 = bge-m3/mxbai-embed-large). Must match AI_EMBED_MODEL;
303-
# # recreate the Qdrant collection when changing this after startup.
301+
# QDRANT_DIM=1024 # vector dimension of the collection (1024 = bge-m3/mxbai-embed-large,
302+
# # the recommended default -- bge-m3's longer native context window
303+
# # is a better match for RAG's ~16k-char chunk budget than smaller
304+
# # embedders; 768 = nomic-embed-text:latest, a lighter alternative).
305+
# # Must match AI_EMBED_MODEL; recreate the Qdrant collection when
306+
# # changing this after startup (existing vectors are a fixed width
307+
# # and cannot be resized in place).
304308
# AI_EMBED_BATCH=96 # items per RAG embed-provider call (#4327). Defaults to 96
305309
# # (a conservative bound sized for Workers AI's 100-item cap).
306310
# # Tune upward for GPU-accelerated self-host Ollama throughput --
@@ -566,6 +570,20 @@ REDIS_URL=redis://redis:6379 # REQUIRED for the self-host review
566570
# OLLAMA_NUM_PARALLEL=2 # concurrent requests per loaded model
567571
# OLLAMA_MAX_LOADED_MODELS=2 # distinct models kept resident at once
568572
# OLLAMA_KEEP_ALIVE=30m # how long an idle model stays loaded before eviction
573+
# OLLAMA_FLASH_ATTENTION=1 # usually auto-enabled already on supported GPUs (check the ollama
574+
# # container's boot log for "Flash Attention was auto, set to
575+
# # enabled" to confirm) -- set explicitly anyway, because it is
576+
# # REQUIRED alongside OLLAMA_KV_CACHE_TYPE below for KV-cache
577+
# # quantization to actually take effect; setting KV_CACHE_TYPE
578+
# # alone without this does nothing.
579+
# OLLAMA_KV_CACHE_TYPE=q8_0 # f16 (default) | q8_0 | q4_0. q8_0 roughly HALVES per-context KV-
580+
# # cache VRAM (confirmed on an RTX A5000: 4608 MiB at f16 -> 2448
581+
# # MiB at q8_0 for one loaded context) with published perplexity
582+
# # impact of 0.002-0.05 -- undetectable in practice. Frees real
583+
# # headroom for more concurrent models or larger contexts on a
584+
# # VRAM-constrained GPU. q4_0 quarters it further at more
585+
# # noticeable quality cost; q8_0 is the recommended default for
586+
# # self-host GPU deployments.
569587
#
570588
# Generic OpenAI-compatible reviewer (AI_PROVIDER=openai-compatible). Defaults:
571589
# OPENAI_COMPATIBLE_AI_BASE_URL=http://localhost:11434/v1, OPENAI_COMPATIBLE_AI_MODEL=llama3.1.
@@ -600,9 +618,9 @@ REDIS_URL=redis://redis:6379 # REQUIRED for the self-host review
600618
# CODEX_AI_EFFORT=medium # low | medium | high | xhigh. `max` is accepted and maps to xhigh.
601619
# CODEX_AI_TIMEOUT_MS= # override CLI timeout in ms; unset scales by effort (low/medium 120s, high 240s, xhigh 360s)
602620
# # Codex service speed is standard by default. No fast/priority tier is requested by this stack.
603-
# AI_EMBED_MODEL=nomic-embed-text:latest # embedding model for RAG (openai-compatible /embeddings). Its output
604-
# # dimension must match QDRANT_DIM. Used only when RAG is enabled
605-
# # (GITTENSORY_REVIEW_RAG + allowlist).
621+
# AI_EMBED_MODEL=bge-m3:latest # embedding model for RAG (openai-compatible /embeddings). Its output
622+
# # dimension must match QDRANT_DIM (1024 for bge-m3). Used only when
623+
# # RAG is enabled (GITTENSORY_REVIEW_RAG + allowlist).
606624
# AI_EMBED_BASE_URL= # route embeddings to a SEPARATE openai-compatible endpoint instead
607625
# # of the review chain's own AI provider — e.g. a dedicated local
608626
# # Ollama for embeddings while Claude/Codex handle review. Unset =

.github/workflows/ci.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ jobs:
7070
# build or types in ways only that full toolchain would catch.
7171
ui:
7272
- 'apps/gittensory-ui/**'
73+
- 'apps/gittensory-miner-ui/**'
7374
- 'apps/gittensory-extension/**'
7475
- 'scripts/build-extension.mjs'
7576
- 'package.json'

.gittensory-miner.yml.example

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,3 +52,11 @@ maxConcurrentClaims: 1
5252
# How strongly this repo encourages a miner to open discovery issues.
5353
# Values: encouraged | neutral | discouraged. Default: neutral.
5454
issueDiscoveryPolicy: neutral
55+
56+
# Per-repo tuning for the feasibility gate a miner consults before starting work.
57+
# `enabled` (boolean, default: true) turns the gate off entirely for this repo.
58+
# `suppressedReasons` (string list, default: []) ignores specific avoid/raise
59+
# reason codes (e.g. duplicate_cluster_high) from the gate's verdict.
60+
feasibilityGate:
61+
enabled: true
62+
suppressedReasons: []

.gittensory.yml

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -64,14 +64,24 @@ review:
6464
skip_drafts: true
6565
skip_docs_only: true
6666

67-
# Linked-issue label propagation (#priority-linked-issue-gate, #priority-linked-issue-gate-ownership): a PR
68-
# that closes/fixes/resolves an issue inherits that issue's point-bearing gittensor:* label onto the PR
69-
# itself, instead of the PR's own label being decided purely by its commit-title prefix. bug/feature are
70-
# `trustMaintainerAuthoredIssue: true` (routine categorization, no reward at stake, and the title-based
71-
# fallback already has zero equivalent verification) so they propagate even when the PR author isn't a
72-
# formal GitHub assignee of the issue — our issues are almost always maintainer-authored for open pickup and
73-
# rarely formally assigned. priority intentionally omits the flag: it is the scarce, maintainer-hand-picked
74-
# reward label, and must still require the PR author to be the issue's actual author/assignee.
67+
# Linked-issue label propagation (#priority-linked-issue-gate, #priority-linked-issue-gate-ownership,
68+
# #priority-reward-maintainer-trust): a PR that closes/fixes/resolves an issue inherits that issue's
69+
# point-bearing gittensor:* label onto the PR itself, instead of the PR's own label being decided purely by
70+
# its commit-title prefix. bug/feature are `trustMaintainerAuthoredIssue: true` (routine categorization, no
71+
# reward at stake, and the title-based fallback already has zero equivalent verification) so they propagate
72+
# even when the PR author isn't a formal GitHub assignee of the issue — our issues are almost always
73+
# maintainer-authored for open pickup and rarely formally assigned. priority is a scarce, maintainer-hand-
74+
# picked reward label, so it defaults to the strict author-or-assignee-only bar everywhere in this codebase
75+
# EXCEPT here, where we explicitly opt it into the SAME relaxation via `trustMaintainerAuthoredIssueForReward:
76+
# true`: our issues are open-pickup by design (see the flag's own doc comment in types.ts), so requiring a
77+
# literal GitHub assignee relationship -- which GitHub silently refuses for a contributor lacking push/triage
78+
# access -- meant this label could structurally never reach the external contributors it exists to reward. The
79+
# maintainer's hand-picking already happened when the issue was labeled `gittensor:priority`, not gated on
80+
# which contributor later closes it. priority is also `removeOtherTypeLabels: false` (additive) -- unlike
81+
# bug/feature, which are mutually-exclusive TYPE categories, priority is a separate reward dimension that
82+
# coexists WITH whichever type already applies (an issue is routinely both gittensor:feature AND
83+
# gittensor:priority at once); resolvePrTypeLabel composes every additive match alongside the one exclusive
84+
# winner, rather than the two categories competing for a single slot.
7585
#
7686
# Review-evasion protection: closing or converting-to-draft your OWN PR while gittensory has an active
7787
# review pass running, a prior recorded gate failure, or a repeated ready<->draft cycle on this PR, is
@@ -92,7 +102,8 @@ settings:
92102
trustMaintainerAuthoredIssue: true
93103
- issueLabel: "gittensor:priority"
94104
prLabel: "gittensor:priority"
95-
removeOtherTypeLabels: true
105+
removeOtherTypeLabels: false
106+
trustMaintainerAuthoredIssueForReward: true
96107
reviewEvasionProtection: close
97108

98109
# Repo-doc generation roadmap (#2993/#3002) — opt-in only, off by default. Uncomment to let Gittensory open a

.gittensory.yml.example

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -603,22 +603,32 @@ settings:
603603
# label like `gittensor:priority` -- it is NEVER inferred from a PR's title, changed files, AI
604604
# output, or existing PR labels, only ever copied from a linked/closing issue ("Fixes #123") that
605605
# ALREADY carries the configured issue label. Generic beyond the priority use case: any issue label
606-
# can map to any PR label. `removeOtherTypeLabels: true` REPLACES the type label entirely (bug/
607-
# feature are removed), matching how `gittensor:priority` behaves today; `false` applies the mapped
608-
# label ADDITIVELY alongside the normal title-based bug/feature label, leaving it untouched -- useful
609-
# for a mapping unrelated to the bug/feature/priority triad (e.g. a `customer:vip` issue label
610-
# copied to a `triage:vip` PR label). Disabled by default (no mappings) -- a self-hoster opts in per
611-
# repo. If your labels carry reward or moderation weight, configure this in PRIVATE per-repo/global
612-
# config (see `config/examples/README.md`) rather than the public `.gittensory.yml`, so contributors
613-
# cannot see the exact mapping rules. A per-repo `mappings` override in the private-config layer
614-
# REPLACES the global default list wholesale -- it does not merge with it.
606+
# can map to any PR label. `removeOtherTypeLabels: true` marks the mapping EXCLUSIVE -- it REPLACES
607+
# the type label entirely (bug/feature are removed), for genuinely mutually-exclusive categories; only
608+
# the FIRST-configured exclusive match wins when more than one applies. `false` (e.g. `gittensor:priority`,
609+
# which is a reward tag that coexists WITH whichever type already applies, not a type of its own) applies
610+
# the mapped label ADDITIVELY alongside whichever exclusive match (or the normal title-based bug/feature
611+
# label) already won -- every additive match composes together rather than competing for a single slot.
612+
# Each mapping normally requires the PR author to be the linked issue's own author or a GitHub assignee of
613+
# it (`trustMaintainerAuthoredIssue: true` relaxes this for a routine, non-reward category like bug/feature
614+
# -- see the field's own doc comment in `types.ts`). `trustMaintainerAuthoredIssueForReward: true` opts a
615+
# REWARD mapping like `gittensor:priority` into that same relaxation: without it, a mapping like this can
616+
# structurally never propagate to most external contributors, since GitHub silently refuses to assign
617+
# anyone lacking push/triage access to the repo -- so if your issues are open-pickup (maintainer-authored,
618+
# rarely formally assigned to a specific contributor), the reward label needs this flag to ever reach them.
619+
# Disabled by default (no mappings) -- a self-hoster opts in per repo. If your labels carry reward or
620+
# moderation weight, configure this in PRIVATE per-repo/global config (see `config/examples/README.md`)
621+
# rather than the public `.gittensory.yml`, so contributors cannot see the exact mapping rules. A per-repo
622+
# `mappings` override in the private-config layer REPLACES the global default list wholesale -- it does
623+
# not merge with it.
615624
# linkedIssueLabelPropagation:
616625
# enabled: true
617626
# mode: exclusive_type_label
618627
# mappings:
619628
# - issueLabel: gittensor:priority
620629
# prLabel: gittensor:priority
621-
# removeOtherTypeLabels: true
630+
# removeOtherTypeLabels: false
631+
# trustMaintainerAuthoredIssueForReward: true
622632

623633
# Create the label if it does not yet exist. Bool. Default: true.
624634
createMissingLabel: true
@@ -1120,3 +1130,13 @@ settings:
11201130
# reviewRecap:
11211131
# enabled: true # Bool. Default: false (no recap is ever built or posted).
11221132
# cadenceDays: 7 # Positive integer. Days of activity each recap covers. Default: 7 (weekly).
1133+
1134+
# Cross-repo maintainer recap digest (#1963, #2250): config-as-code override for the CRON-scheduled digest
1135+
# that folds gate-precision + outcome-calibration across every scanned repo into one report (distinct from
1136+
# the single-repo reviewRecap above). Operator-level, not per-repo -- only meaningful on the gittensory
1137+
# self-repo's own manifest (the repo this instance identifies as); a present block there wins over the
1138+
# GITTENSORY_MAINTAINER_RECAP / GITTENSORY_RECAP_CADENCE env vars, which stay the fallback when absent.
1139+
# maintainerRecap:
1140+
# enabled: true # Bool. Default: false (env vars decide instead).
1141+
# cadence: weekly # daily | weekly. Default: weekly. Invalid values fall back to weekly.
1142+
# channel: discord # discord (only supported channel today). Invalid values fall back to discord.
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Gittensory Miner Extension
2+
3+
Contributor-facing browser extension for GitHub **issue** pages. It is intentionally separate from
4+
[`apps/gittensory-extension/`](../gittensory-extension/) (the **Maintainer Overlay**), which injects private PR/issue
5+
context for maintainers.
6+
7+
## What it does
8+
9+
- Manifest V3 with issue-page `content_scripts`
10+
- `background.js` service worker + `content.js` message-passing
11+
- Read-only opportunity badge (score/tier + short why) for watched repositories
12+
- Options page for watched repos and a local ranked-candidate cache
13+
14+
The badge surfaces the same ranked signal as `packages/gittensory-miner/lib/opportunity-ranker.js` by reading
15+
pre-ranked candidates from browser local storage. It never writes to GitHub and omits itself when no ranked signal is
16+
available for the current issue.
17+
18+
## Local ranked cache
19+
20+
Laptop-mode installs can paste JSON from a miner `discover` run into the options page. The extension stores that list in
21+
`chrome.storage.local.rankedCandidates` and looks up the current issue there. A discovery-index URL can be saved for a
22+
future hosted client path; it is not read yet, so when only unranked hosted metadata would be available the badge
23+
degrades gracefully by staying hidden.
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import "./opportunity-badge.js";
2+
3+
const badgeApi = globalThis.__gittensoryMinerOpportunityBadge;
4+
5+
const PING_MESSAGE = "gittensory-miner:ping";
6+
const ISSUE_CONTEXT_MESSAGE = "gittensory-miner:issue-context";
7+
8+
chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
9+
if (!message || typeof message.type !== "string") return false;
10+
if (message.type === PING_MESSAGE) {
11+
sendResponse({ ok: true, payload: { ready: true } });
12+
return false;
13+
}
14+
if (message.type === ISSUE_CONTEXT_MESSAGE) {
15+
const task = loadIssueOpportunityContext(message);
16+
void task.then((payload) => sendResponse({ ok: true, payload })).catch((error) =>
17+
sendResponse({ ok: false, error: error instanceof Error ? error.message : String(error) }),
18+
);
19+
return true;
20+
}
21+
return false;
22+
});
23+
24+
async function loadIssueOpportunityContext(message) {
25+
const settings = await loadMinerExtensionSettings();
26+
const repoFullName = `${message.owner}/${message.repo}`;
27+
const watched = settings.watchedRepos.some(
28+
(repo) => repo.trim().toLowerCase() === repoFullName.toLowerCase(),
29+
);
30+
if (!watched) {
31+
return {
32+
watched: false,
33+
issueNumber: message.issueNumber,
34+
repoFullName,
35+
badge: null,
36+
status: "repo-not-watched",
37+
};
38+
}
39+
40+
const rankedCandidates = await loadRankedCandidates();
41+
const rankedEntry = badgeApi.lookupRankedOpportunity(rankedCandidates, repoFullName, message.issueNumber);
42+
if (!rankedEntry) {
43+
return {
44+
watched: true,
45+
issueNumber: message.issueNumber,
46+
repoFullName,
47+
badge: null,
48+
status: "no-signal",
49+
};
50+
}
51+
52+
return {
53+
watched: true,
54+
issueNumber: message.issueNumber,
55+
repoFullName,
56+
badge: badgeApi.formatOpportunityBadge(rankedEntry),
57+
status: "ready",
58+
};
59+
}
60+
61+
async function loadMinerExtensionSettings() {
62+
const stored = await chrome.storage.sync.get({ watchedRepos: [], discoveryIndexUrl: "" });
63+
const watchedRepos = Array.isArray(stored.watchedRepos)
64+
? stored.watchedRepos.map((value) => String(value).trim()).filter(Boolean)
65+
: [];
66+
const discoveryIndexUrl =
67+
typeof stored.discoveryIndexUrl === "string" ? stored.discoveryIndexUrl.trim() : "";
68+
return { watchedRepos, discoveryIndexUrl };
69+
}
70+
71+
async function loadRankedCandidates() {
72+
const stored = await chrome.storage.local.get({ rankedCandidates: [] });
73+
return Array.isArray(stored.rankedCandidates) ? stored.rankedCandidates : [];
74+
}
75+
76+
if (globalThis.__GITTENSORY_MINER_EXTENSION_TEST__) {
77+
globalThis.__gittensoryMinerBackgroundInternals = {
78+
PING_MESSAGE,
79+
ISSUE_CONTEXT_MESSAGE,
80+
loadIssueOpportunityContext,
81+
loadMinerExtensionSettings,
82+
loadRankedCandidates,
83+
};
84+
}

0 commit comments

Comments
 (0)