Skip to content

Commit af2d93e

Browse files
merge main into feat/miner-metadata-opportunity-ranker
Resolve miner CLI conflicts: foundation status stays top-level; manage portfolio status routes via manage status. Co-authored-by: Cursor <cursoragent@cursor.com>
2 parents feadad1 + cea0943 commit af2d93e

75 files changed

Lines changed: 3029 additions & 259 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.

grafana/dashboards/gittensory.json

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2786,6 +2786,163 @@
27862786
"refId": "A"
27872787
}
27882788
]
2789+
},
2790+
{
2791+
"collapsed": false,
2792+
"gridPos": { "h": 1, "w": 24, "x": 0, "y": 182 },
2793+
"id": 152,
2794+
"title": "Backlog-vs-Fresh-Intake Lane Fairness (#selfhost-lane-observability)",
2795+
"type": "row"
2796+
},
2797+
{
2798+
"datasource": { "type": "prometheus", "uid": "${DS_PROMETHEUS}" },
2799+
"fieldConfig": {
2800+
"defaults": {
2801+
"color": { "mode": "thresholds" },
2802+
"thresholds": {
2803+
"mode": "absolute",
2804+
"steps": [
2805+
{ "color": "green", "value": null },
2806+
{ "color": "yellow", "value": 10 },
2807+
{ "color": "red", "value": 30 }
2808+
]
2809+
},
2810+
"unit": "short"
2811+
}
2812+
},
2813+
"gridPos": { "h": 4, "w": 4, "x": 0, "y": 183 },
2814+
"id": 153,
2815+
"options": {
2816+
"colorMode": "background",
2817+
"graphMode": "none",
2818+
"justifyMode": "auto",
2819+
"orientation": "auto",
2820+
"reduceOptions": { "calcs": ["lastNotNull"], "fields": "", "values": false },
2821+
"textMode": "auto"
2822+
},
2823+
"title": "Backlog-Convergence Pending",
2824+
"type": "stat",
2825+
"targets": [
2826+
{
2827+
"datasource": { "type": "prometheus", "uid": "${DS_PROMETHEUS}" },
2828+
"expr": "gittensory_queue_backlog_convergence_pending",
2829+
"legendFormat": "backlog pending"
2830+
}
2831+
]
2832+
},
2833+
{
2834+
"datasource": { "type": "prometheus", "uid": "${DS_PROMETHEUS}" },
2835+
"fieldConfig": {
2836+
"defaults": {
2837+
"color": { "mode": "thresholds" },
2838+
"thresholds": {
2839+
"mode": "absolute",
2840+
"steps": [
2841+
{ "color": "green", "value": null },
2842+
{ "color": "yellow", "value": 10 },
2843+
{ "color": "red", "value": 30 }
2844+
]
2845+
},
2846+
"unit": "short"
2847+
}
2848+
},
2849+
"gridPos": { "h": 4, "w": 4, "x": 4, "y": 183 },
2850+
"id": 154,
2851+
"options": {
2852+
"colorMode": "background",
2853+
"graphMode": "none",
2854+
"justifyMode": "auto",
2855+
"orientation": "auto",
2856+
"reduceOptions": { "calcs": ["lastNotNull"], "fields": "", "values": false },
2857+
"textMode": "auto"
2858+
},
2859+
"title": "Fresh-Intake Pending",
2860+
"type": "stat",
2861+
"targets": [
2862+
{
2863+
"datasource": { "type": "prometheus", "uid": "${DS_PROMETHEUS}" },
2864+
"expr": "gittensory_queue_fresh_intake_pending",
2865+
"legendFormat": "fresh pending"
2866+
}
2867+
]
2868+
},
2869+
{
2870+
"datasource": { "type": "prometheus", "uid": "${DS_PROMETHEUS}" },
2871+
"fieldConfig": {
2872+
"defaults": { "color": { "mode": "palette-classic" }, "custom": { "lineWidth": 2, "fillOpacity": 10 }, "unit": "short" }
2873+
},
2874+
"gridPos": { "h": 6, "w": 16, "x": 8, "y": 183 },
2875+
"id": 155,
2876+
"options": {
2877+
"legend": { "calcs": ["lastNotNull"], "displayMode": "table", "placement": "bottom" },
2878+
"tooltip": { "mode": "multi", "sort": "desc" }
2879+
},
2880+
"title": "GitHub REST Rate Limit Remaining (by scope)",
2881+
"description": "The NEWEST observed x-ratelimit-remaining per key_scope -- unlike gittensory_github_rest_rate_limit_observations_total (a bucketed rate() counter), this is the actual current remaining count.",
2882+
"type": "timeseries",
2883+
"targets": [
2884+
{
2885+
"datasource": { "type": "prometheus", "uid": "${DS_PROMETHEUS}" },
2886+
"expr": "gittensory_github_rest_rate_limit_remaining",
2887+
"legendFormat": "{{key_scope}}",
2888+
"refId": "A"
2889+
}
2890+
]
2891+
},
2892+
{
2893+
"datasource": { "type": "prometheus", "uid": "${DS_PROMETHEUS}" },
2894+
"fieldConfig": {
2895+
"defaults": { "color": { "mode": "palette-classic" }, "custom": { "lineWidth": 2, "fillOpacity": 10 }, "unit": "ops" }
2896+
},
2897+
"gridPos": { "h": 6, "w": 12, "x": 0, "y": 189 },
2898+
"id": 156,
2899+
"options": {
2900+
"legend": { "calcs": ["lastNotNull"], "displayMode": "table", "placement": "bottom" },
2901+
"tooltip": { "mode": "multi", "sort": "desc" }
2902+
},
2903+
"title": "Foreground Claims by Lane (rate)",
2904+
"type": "timeseries",
2905+
"targets": [
2906+
{
2907+
"datasource": { "type": "prometheus", "uid": "${DS_PROMETHEUS}" },
2908+
"expr": "sum by (lane) (rate(gittensory_jobs_claimed_by_lane_total[5m])) or vector(0)",
2909+
"legendFormat": "{{lane}}",
2910+
"refId": "A"
2911+
}
2912+
]
2913+
},
2914+
{
2915+
"datasource": { "type": "prometheus", "uid": "${DS_PROMETHEUS}" },
2916+
"fieldConfig": { "defaults": { "unit": "short" } },
2917+
"gridPos": { "h": 6, "w": 12, "x": 12, "y": 189 },
2918+
"id": 157,
2919+
"options": {
2920+
"showHeader": true,
2921+
"cellHeight": "sm",
2922+
"footer": { "show": false, "reducer": ["sum"], "fields": "" }
2923+
},
2924+
"title": "Top Repos by Backlog Depth",
2925+
"description": "Top-10 repos by backlog-convergence pending depth, recomputed fresh every scrape -- bounded so a large multi-repo self-host install never explodes this panel's series count.",
2926+
"type": "table",
2927+
"targets": [
2928+
{
2929+
"datasource": { "type": "prometheus", "uid": "${DS_PROMETHEUS}" },
2930+
"expr": "gittensory_queue_backlog_by_repo",
2931+
"format": "table",
2932+
"instant": true,
2933+
"legendFormat": "__auto",
2934+
"refId": "A"
2935+
}
2936+
],
2937+
"transformations": [
2938+
{
2939+
"id": "organize",
2940+
"options": {
2941+
"excludeByName": { "Time": true, "__name__": true, "job": true, "instance": true },
2942+
"indexByName": { "repo": 0, "Value": 1 }
2943+
}
2944+
}
2945+
]
27892946
}
27902947
],
27912948
"refresh": "30s",
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import type { MinerGoalSpec } from "./miner-goal-spec.js";
2+
3+
export type GoalModelInput = {
4+
candidatePaths: string[];
5+
candidateLabels: string[];
6+
goalSpec: MinerGoalSpec;
7+
};
8+
9+
function normalizeLabels(labels: readonly string[]): string[] {
10+
return labels
11+
.filter((label): label is string => typeof label === "string")
12+
.map((label) => label.trim().toLowerCase())
13+
.filter(Boolean);
14+
}
15+
16+
function normalizePathForMatch(path: string): string {
17+
return String(path ?? "").replace(/\\/g, "/").toLowerCase();
18+
}
19+
20+
function compileGlobMatcher(pattern: string): (path: string) => boolean {
21+
const normalizedPattern = normalizePathForMatch(pattern);
22+
if (!normalizedPattern) return () => false;
23+
let regex = "^";
24+
for (let i = 0; i < normalizedPattern.length; i++) {
25+
const ch = normalizedPattern[i];
26+
const next = normalizedPattern[i + 1];
27+
if (ch === "*" && next === "*") {
28+
const afterDoubleStar = normalizedPattern[i + 2];
29+
if (afterDoubleStar === "/") {
30+
regex += "(?:.*/)?";
31+
i += 2;
32+
} else {
33+
regex += ".*";
34+
i++;
35+
}
36+
} else if (ch === "*") {
37+
regex += "[^/]*";
38+
} else if (ch === "?") {
39+
regex += "[^/]";
40+
} else if (/[.+^$(){}|[\]\\]/.test(ch ?? "")) {
41+
regex += "\\" + ch;
42+
} else {
43+
regex += ch;
44+
}
45+
}
46+
regex += "$";
47+
const compiled = new RegExp(regex);
48+
return (path: string) => {
49+
const normalized = normalizePathForMatch(path);
50+
if (!normalized) return false;
51+
return compiled.test(normalized);
52+
};
53+
}
54+
55+
function matchesAnyLabel(candidateLabels: readonly string[], goalLabels: readonly string[]): boolean {
56+
if (goalLabels.length === 0) return false;
57+
const normalizedCandidate = normalizeLabels(candidateLabels);
58+
const normalizedGoal = normalizeLabels(goalLabels);
59+
return normalizedGoal.some((label) => normalizedCandidate.includes(label));
60+
}
61+
62+
function matchesAnyPath(candidatePaths: readonly string[], goalPaths: readonly string[]): boolean {
63+
if (goalPaths.length === 0) return false;
64+
return goalPaths.some((pattern) => {
65+
const matcher = compileGlobMatcher(pattern);
66+
return candidatePaths.some((path) => matcher(path));
67+
});
68+
}
69+
70+
export function computeLaneFit(input: GoalModelInput): number {
71+
const { candidatePaths, candidateLabels, goalSpec } = input;
72+
if (matchesAnyPath(candidatePaths, goalSpec.blockedPaths)) {
73+
return 0;
74+
}
75+
if (matchesAnyLabel(candidateLabels, goalSpec.blockedLabels)) {
76+
return 0;
77+
}
78+
const hasPathCriteria = goalSpec.wantedPaths.length > 0;
79+
const hasLabelCriteria = goalSpec.preferredLabels.length > 0;
80+
if (!hasPathCriteria && !hasLabelCriteria) {
81+
return 0.5;
82+
}
83+
const pathMatches = hasPathCriteria && matchesAnyPath(candidatePaths, goalSpec.wantedPaths);
84+
const labelMatches = hasLabelCriteria && matchesAnyLabel(candidateLabels, goalSpec.preferredLabels);
85+
if (!pathMatches && !labelMatches) {
86+
return 0;
87+
}
88+
const activeDimensions = (hasPathCriteria ? 1 : 0) + (hasLabelCriteria ? 1 : 0);
89+
const matchedDimensions = (pathMatches ? 1 : 0) + (labelMatches ? 1 : 0);
90+
return matchedDimensions / activeDimensions;
91+
}

packages/gittensory-engine/src/governor-ledger.ts

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import { isDeepStrictEqual } from "node:util";
2-
31
/** Immutable governor decision vocabulary — unknown values fail closed before insert. */
42
export const GOVERNOR_LEDGER_EVENT_TYPES = Object.freeze([
53
"allowed",
@@ -46,6 +44,27 @@ function normalizeOptionalRepoFullName(repoFullName: unknown): string | null {
4644
return `${owner}/${repo}`;
4745
}
4846

47+
// Structural equality between a JSON.parse() result and the plain object it was stringified from. One side is
48+
// always JSON-safe (parsed from JSON text); this only needs to compare plain objects/arrays/primitives, not the
49+
// full generality of node:util's isDeepStrictEqual (no Dates/RegExp/Maps/getters/symbols to worry about) — this
50+
// package's tsconfig deliberately sets `types: []` (no Node ambient types leak into its public .d.ts surface),
51+
// so importing "node:util" here isn't viable; a small local check avoids that entirely.
52+
function jsonRoundTripEqual(a: unknown, b: unknown): boolean {
53+
if (a === b) return true;
54+
if (typeof a !== typeof b || a === null || b === null) return false;
55+
if (typeof a !== "object") return false;
56+
const aIsArray = Array.isArray(a);
57+
if (aIsArray !== Array.isArray(b)) return false;
58+
if (aIsArray) {
59+
const bArr = b as unknown[];
60+
const aArr = a as unknown[];
61+
return aArr.length === bArr.length && aArr.every((value, index) => jsonRoundTripEqual(value, bArr[index]));
62+
}
63+
const aKeys = Object.keys(a as object);
64+
const bRecord = b as Record<string, unknown>;
65+
return aKeys.length === Object.keys(bRecord).length && aKeys.every((key) => Object.hasOwn(bRecord, key) && jsonRoundTripEqual((a as Record<string, unknown>)[key], bRecord[key]));
66+
}
67+
4968
function serializePayload(payload: unknown): string {
5069
if (payload === undefined) return "{}";
5170
if (payload === null || typeof payload !== "object" || Array.isArray(payload)) {
@@ -57,7 +76,7 @@ function serializePayload(payload: unknown): string {
5776
} catch {
5877
throw new Error("invalid_payload");
5978
}
60-
if (!isDeepStrictEqual(JSON.parse(json), payload)) {
79+
if (!jsonRoundTripEqual(JSON.parse(json), payload)) {
6180
throw new Error("invalid_payload");
6281
}
6382
return json;

packages/gittensory-engine/src/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@ export {
4545
type FreshnessIssue,
4646
} from "./opportunity-freshness.js";
4747
export { computeOpportunityCompetition } from "./opportunity-competition.js";
48+
export {
49+
computeLaneFit,
50+
type GoalModelInput,
51+
} from "./goal-model.js";
4852
export {
4953
classifyContributorFit,
5054
type ContributorFit,

packages/gittensory-engine/src/opportunity-freshness.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,9 @@ function pickTimestamp(issue: FreshnessIssue): string | null {
2727
}
2828

2929
function issueAgeDays(value: string | null, nowMs: number): number {
30-
if (!value) return 0;
30+
if (!value) return Number.POSITIVE_INFINITY;
3131
const parsed = Date.parse(value);
32-
if (!Number.isFinite(parsed)) return 0;
32+
if (!Number.isFinite(parsed)) return Number.POSITIVE_INFINITY;
3333
return Math.floor((nowMs - parsed) / 86_400_000);
3434
}
3535

0 commit comments

Comments
 (0)