Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions packages/gittensory-miner/lib/calibration/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export type {
CalibrationReport,
CalibrationRow,
ObservedOutcomeRecord,
PredictedVerdictRecord,
} from "./types.js";

export {
isCalibrationReport,
isCalibrationRow,
isObservedOutcomeRecord,
isPredictedVerdictRecord,
} from "./types.js";
6 changes: 6 additions & 0 deletions packages/gittensory-miner/lib/calibration/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export {
isCalibrationReport,
isCalibrationRow,
isObservedOutcomeRecord,
isPredictedVerdictRecord,
} from "./types.js";
46 changes: 46 additions & 0 deletions packages/gittensory-miner/lib/calibration/types.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/** A single gate-prediction row the miner will replay against observed outcomes. */
export type PredictedVerdictRecord = {
targetId: string;
project: string;
predictedDecision: string;
recordedAt: string;
source?: string;
};

/** The realized human outcome for a previously predicted target. */
export type ObservedOutcomeRecord = {
targetId: string;
project: string;
outcomeDecision: string;
recordedAt: string;
};

/** Per-project confusion-matrix row — field names mirror `GateEvalRow` in `src/review/parity.ts`. */
export type CalibrationRow = {
project: string;
wouldMerge: number;
mergeConfirmed: number;
mergeFalse: number;
wouldClose: number;
closeConfirmed: number;
closeFalse: number;
hold: number;
decided: number;
mergePrecision: number | null;
closePrecision: number | null;
};

/** Aggregate calibration report over one or more projects. */
export type CalibrationReport = {
rows: CalibrationRow[];
/** True once at least one project has enough decided samples to read meaningfully. */
hasSignal: boolean;
};

export function isPredictedVerdictRecord(value: unknown): value is PredictedVerdictRecord;

export function isObservedOutcomeRecord(value: unknown): value is ObservedOutcomeRecord;

export function isCalibrationRow(value: unknown): value is CalibrationRow;

export function isCalibrationReport(value: unknown): value is CalibrationReport;
74 changes: 74 additions & 0 deletions packages/gittensory-miner/lib/calibration/types.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// Shared calibration shapes for the miner self-improvement phase (#2332). Types-only scaffolding —
// report/ledger/metrics issues build on this module. Field names mirror `GateEvalRow` /
// `GateEvalReport` in `src/review/parity.ts` for easy mental mapping without importing cloud code.

function isNonEmptyString(value) {
return typeof value === "string" && value.trim().length > 0;
}

function isOptionalString(value) {
return value === undefined || isNonEmptyString(value);
}

function isNonNegativeInteger(value) {
return typeof value === "number" && Number.isInteger(value) && value >= 0;
}

function isNullableRatio(value) {
return value === null || (typeof value === "number" && Number.isFinite(value) && value >= 0 && value <= 1);
}

/** @param {unknown} value */
export function isPredictedVerdictRecord(value) {
if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
const record = /** @type {Record<string, unknown>} */ (value);
return (
isNonEmptyString(record.targetId)
&& isNonEmptyString(record.project)
&& isNonEmptyString(record.predictedDecision)
&& isNonEmptyString(record.recordedAt)
&& isOptionalString(record.source)
);
}

/** @param {unknown} value */
export function isObservedOutcomeRecord(value) {
if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
const record = /** @type {Record<string, unknown>} */ (value);
return (
isNonEmptyString(record.targetId)
&& isNonEmptyString(record.project)
&& isNonEmptyString(record.outcomeDecision)
&& isNonEmptyString(record.recordedAt)
);
}

/** @param {unknown} value */
export function isCalibrationRow(value) {
if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
const row = /** @type {Record<string, unknown>} */ (value);
return (
isNonEmptyString(row.project)
&& isNonNegativeInteger(row.wouldMerge)
&& isNonNegativeInteger(row.mergeConfirmed)
&& isNonNegativeInteger(row.mergeFalse)
&& isNonNegativeInteger(row.wouldClose)
&& isNonNegativeInteger(row.closeConfirmed)
&& isNonNegativeInteger(row.closeFalse)
&& isNonNegativeInteger(row.hold)
&& isNonNegativeInteger(row.decided)
&& isNullableRatio(row.mergePrecision)
&& isNullableRatio(row.closePrecision)
);
}

/** @param {unknown} value */
export function isCalibrationReport(value) {
if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
const report = /** @type {Record<string, unknown>} */ (value);
return (
typeof report.hasSignal === "boolean"
&& Array.isArray(report.rows)
&& report.rows.every((row) => isCalibrationRow(row))
);
}
2 changes: 1 addition & 1 deletion packages/gittensory-miner/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
"lib"
],
"scripts": {
"build": "node --check bin/gittensory-miner.js && node --check lib/cli.js && node --check lib/deny-check.js && node --check lib/run-state-cli.js && node --check lib/update-check.js && node --check lib/opportunity-fanout.js && node --check lib/ci-poller.js && node --check lib/run-state.js && node --check lib/deny-hooks.js && node --check lib/event-ledger.js && node --check lib/event-ledger-cli.js && node --check lib/claim-ledger.js && node --check lib/claim-ledger-expiry.js && node --check lib/portfolio-queue.js && node --check lib/portfolio-queue-cli.js && node --check lib/portfolio-discovery.js && node --check lib/opportunity-ranker.js && node --check lib/plan-store.js && node --check lib/plan-store-cli.js && node --check lib/rejection-templates.js && node --check lib/governor-ledger.js && node --check lib/governor-ledger-cli.js && node --check lib/manage-status.js && node --check lib/manage-poll.js && node --check lib/status.js && node --check lib/laptop-init.js && node --check lib/replay-objective-anchor.js && node --check lib/replay-task-generation.js"
"build": "node --check bin/gittensory-miner.js && node --check lib/cli.js && node --check lib/deny-check.js && node --check lib/run-state-cli.js && node --check lib/update-check.js && node --check lib/opportunity-fanout.js && node --check lib/ci-poller.js && node --check lib/run-state.js && node --check lib/deny-hooks.js && node --check lib/event-ledger.js && node --check lib/event-ledger-cli.js && node --check lib/claim-ledger.js && node --check lib/claim-ledger-expiry.js && node --check lib/portfolio-queue.js && node --check lib/portfolio-queue-cli.js && node --check lib/portfolio-discovery.js && node --check lib/opportunity-ranker.js && node --check lib/plan-store.js && node --check lib/plan-store-cli.js && node --check lib/rejection-templates.js && node --check lib/governor-ledger.js && node --check lib/governor-ledger-cli.js && node --check lib/manage-status.js && node --check lib/manage-poll.js && node --check lib/status.js && node --check lib/laptop-init.js && node --check lib/replay-objective-anchor.js && node --check lib/replay-task-generation.js && node --check lib/calibration/types.js && node --check lib/calibration/index.js"
},
"dependencies": {
"@jsonbored/gittensory-engine": "0.1.0"
Expand Down
70 changes: 70 additions & 0 deletions test/unit/miner-calibration-types.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { describe, expect, it } from "vitest";
import {
isCalibrationReport,
isCalibrationRow,
isObservedOutcomeRecord,
isPredictedVerdictRecord,
} from "../../packages/gittensory-miner/lib/calibration/index.js";
import type {
CalibrationReport,
CalibrationRow,
ObservedOutcomeRecord,
PredictedVerdictRecord,
} from "../../packages/gittensory-miner/lib/calibration/index.js";

describe("gittensory-miner calibration types scaffold (#2332)", () => {
const predicted: PredictedVerdictRecord = {
targetId: "pr:JSONbored/gittensory#42",
project: "JSONbored/gittensory",
predictedDecision: "merge",
recordedAt: "2026-07-06T00:00:00.000Z",
source: "reviewbot",
};

const observed: ObservedOutcomeRecord = {
targetId: "pr:JSONbored/gittensory#42",
project: "JSONbored/gittensory",
outcomeDecision: "merged",
recordedAt: "2026-07-06T01:00:00.000Z",
};

const row: CalibrationRow = {
project: "JSONbored/gittensory",
wouldMerge: 10,
mergeConfirmed: 8,
mergeFalse: 2,
wouldClose: 3,
closeConfirmed: 2,
closeFalse: 1,
hold: 1,
decided: 13,
mergePrecision: 0.8,
closePrecision: 2 / 3,
};

const report: CalibrationReport = {
rows: [row],
hasSignal: true,
};

it("accepts minimal fixtures for every shared calibration shape", () => {
expect(isPredictedVerdictRecord(predicted)).toBe(true);
expect(isObservedOutcomeRecord(observed)).toBe(true);
expect(isCalibrationRow(row)).toBe(true);
expect(isCalibrationReport(report)).toBe(true);
});

it("rejects malformed prediction and outcome records", () => {
expect(isPredictedVerdictRecord(null)).toBe(false);
expect(isPredictedVerdictRecord({ ...predicted, targetId: "" })).toBe(false);
expect(isPredictedVerdictRecord({ ...predicted, source: 1 })).toBe(false);
expect(isObservedOutcomeRecord({ ...observed, outcomeDecision: "" })).toBe(false);
});

it("rejects malformed calibration rows and reports", () => {
expect(isCalibrationRow({ ...row, mergePrecision: 1.5 })).toBe(false);
expect(isCalibrationRow({ ...row, decided: -1 })).toBe(false);
expect(isCalibrationReport({ rows: [row], hasSignal: "yes" })).toBe(false);
expect(isCalibrationReport({ rows: [{ ...row, project: "" }], hasSignal: true })).toBe(false);
});
});