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
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
"ui:version-audit": "node scripts/check-ui-mcp-version-copy.mjs",
"docs:drift-check": "node scripts/check-docs-drift.mjs",
"manifest:drift-check": "tsx scripts/check-manifest-drift.mjs",
"engine-parity:drift-check": "tsx scripts/check-engine-parity.ts",
"ui:deploy": "npm run ui:build && npm run ui:deploy:built",
"ui:deploy:built": "wrangler deploy --config apps/gittensory-ui/dist/server/wrangler.json",
"ui:version:built": "wrangler versions upload --config apps/gittensory-ui/dist/server/wrangler.json",
Expand All @@ -75,7 +76,7 @@
"test:smoke:observability": "node scripts/smoke-observability-traces.mjs",
"test:smoke:browser:install": "playwright install chromium",
"test:smoke:browser": "node scripts/smoke-ui-browser.mjs",
"test:ci": "git diff --check && npm run actionlint && npm run db:migrations:check && npm run db:schema-drift:check && npm run selfhost:env-reference:check && npm run selfhost:validate-observability && npm run cf-typegen:check && npm run typecheck && npm run test:coverage && npm run test:engine-parity && npm run test --workspace @jsonbored/gittensory-engine && npm run test:workers && npm run build:mcp && npm run test:mcp-pack && npm run build:miner && npm run test:miner-pack && npm run rees:test && npm run ui:openapi:check && npm run ui:openapi:settings-parity && npm run ui:version-audit && npm run docs:drift-check && npm run manifest:drift-check && npm run command-reference:check && npm run ui:lint && npm run ui:typecheck && npm run ui:test && npm run ui:build",
"test:ci": "git diff --check && npm run actionlint && npm run db:migrations:check && npm run db:schema-drift:check && npm run selfhost:env-reference:check && npm run selfhost:validate-observability && npm run cf-typegen:check && npm run typecheck && npm run test:coverage && npm run test:engine-parity && npm run test --workspace @jsonbored/gittensory-engine && npm run test:workers && npm run build:mcp && npm run test:mcp-pack && npm run build:miner && npm run test:miner-pack && npm run rees:test && npm run ui:openapi:check && npm run ui:openapi:settings-parity && npm run ui:version-audit && npm run docs:drift-check && npm run manifest:drift-check && npm run engine-parity:drift-check && npm run command-reference:check && npm run ui:lint && npm run ui:typecheck && npm run ui:test && npm run ui:build",
"test:release": "npm run test:ci && npm run changelog:check",
"test:release:mcp": "npm run test:ci",
"test:watch": "vitest",
Expand Down
3 changes: 3 additions & 0 deletions packages/gittensory-engine/src/review/guardrail-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ export const ENGINE_DECISION_GUARDRAIL_GLOBS = [
"src/github/pr-actions.ts",
"src/github/app.ts",
"src/github/backfill.ts",
// #4197: writes a real commit onto a CONTRIBUTOR's own PR branch (not a branch gittensory owns) — the same
// guardrail tier as pr-actions.ts/app.ts for the same reason, a new GitHub-write surface.
"src/github/e2e-test-commit.ts",
"src/scoring/**",
"src/auth/**",
"src/review/safety.ts",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,9 @@ function normalizeMapping(input: unknown, index: number, warnings: string[]): Li
return null;
}
// Unlike `removeOtherTypeLabels`, a malformed value here can only ever be warned-and-defaulted (never
// dropped) -- defaulting to `undefined`/strict is always the SAFE direction, so there is no silent-flip
// risk that would justify discarding an otherwise-valid mapping over it. Mirrors `src/review/linked-issue-
// label-propagation.ts`'s copy of this normalizer.
// dropped) -- defaulting to `undefined`/strict is always the SAFE direction (no mapping accidentally
// starts trusting maintainer-authored issues), so there is no silent-flip risk that would justify
// discarding an otherwise-valid mapping over it.
let trustMaintainerAuthoredIssue: boolean | undefined;
if (record.trustMaintainerAuthoredIssue !== undefined) {
if (typeof record.trustMaintainerAuthoredIssue === "boolean") {
Expand Down
20 changes: 18 additions & 2 deletions packages/gittensory-engine/src/settings/pr-type-label.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
// moves away from it. Public + neutral categorization (NOT the reputation signal). Review-time +
// independent of the gate / autonomy / dry-run (matches reviewbot, where auto-label runs at review
// start). Fail-safe.
import type { LinkedIssueLabelPropagationConfig, PrTypeLabelSet } from "../types/manifest-deps-types.js";
import type { LinkedIssueLabelPropagationConfig, LinkedIssueLabelPropagationMapping, PrTypeLabelSet } from "../types/manifest-deps-types.js";

export type { PrTypeLabelSet } from "../types/manifest-deps-types.js";

Expand Down Expand Up @@ -156,9 +156,25 @@ export function resolvePrTypeLabel(input: {

if (input.propagation?.enabled) {
const wanted = new Set((input.linkedIssueLabels ?? []).map((label) => label.toLowerCase()));
// Collect EVERY mapping the linked issue's labels satisfy, not just the first. An exclusive mapping
// (removeOtherTypeLabels: true -- e.g. bug/feature, genuinely mutually-exclusive categories) still only
// ever lets the FIRST-configured match win, same precedence as before. But an additive mapping (e.g.
// priority -- a maintainer-hand-picked reward tag that coexists WITH whichever type already applies, not a
// type of its own) must compose with that winner instead of being skipped just because an earlier mapping
// in the array already matched and returned. Before this, an additive match was unreachable whenever the
// SAME linked issue also carried a label an earlier (exclusive) mapping matched -- the overwhelmingly common
// case for gittensor:priority, which is applied ALONGSIDE gittensor:bug/gittensor:feature on the issue, never
// instead of it (#priority-linked-issue-gate).
let exclusiveMatch: LinkedIssueLabelPropagationMapping | undefined;
const additiveMatches: LinkedIssueLabelPropagationMapping[] = [];
for (const mapping of input.propagation.mappings) {
if (!wanted.has(mapping.issueLabel.toLowerCase())) continue;
return mapping.removeOtherTypeLabels ? decide([mapping.prLabel], "propagation_exclusive") : decide([titleLabel, mapping.prLabel], "propagation_additive");
if (mapping.removeOtherTypeLabels) exclusiveMatch ??= mapping;
else additiveMatches.push(mapping);
}
if (exclusiveMatch || additiveMatches.length > 0) {
const applyLabels = [exclusiveMatch ? exclusiveMatch.prLabel : titleLabel, ...additiveMatches.map((mapping) => mapping.prLabel)];
return decide(applyLabels, exclusiveMatch ? "propagation_exclusive" : "propagation_additive");
}
}
return decide([titleLabel], "title");
Expand Down
1 change: 1 addition & 0 deletions packages/gittensory-miner/expected-engine.version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
0.2.0
23 changes: 23 additions & 0 deletions packages/gittensory-miner/lib/status.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,26 @@ export function runStatus(args?: string[], env?: Record<string, string | undefin
export function runDoctorChecks(env?: Record<string, string | undefined>): DoctorCheck[];

export function runDoctor(args?: string[], env?: Record<string, string | undefined>): number;

export function readInstalledEnginePackageVersionFromPaths(
resolvedEntry: string,
workspacePkg: string,
deps?: { existsSync: (path: string) => boolean; readFileSync: (path: string, encoding: "utf8") => string },
): string | null;

export function readInstalledEnginePackageVersion(): string | null;

export function readExpectedEnginePackageVersionFromPaths(
monorepoEnginePkg: string,
pinFile: string,
deps?: { existsSync: (path: string) => boolean; readFileSync: (path: string, encoding: "utf8") => string },
): string | null;

export function readExpectedEnginePackageVersion(): string | null;

export function compareInstalledEngineVersion(installed: string, expected: string): -1 | 0 | 1;

export function buildEngineVersionSkewCheck(
readInstalled?: () => string | null,
readExpected?: () => string | null,
): DoctorCheck;
125 changes: 124 additions & 1 deletion packages/gittensory-miner/lib/status.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { createRequire } from "node:module";
import { homedir } from "node:os";
import { join } from "node:path";
Expand Down Expand Up @@ -45,6 +45,128 @@ function readEngineVersion() {
}
}

export function readInstalledEnginePackageVersionFromPaths(
resolvedEntry,
workspacePkg,
deps = { existsSync, readFileSync },
) {
try {
for (const pkgJson of [join(resolvedEntry, "..", "package.json"), join(resolvedEntry, "..", "..", "package.json")]) {
if (deps.existsSync(pkgJson)) {
const version = JSON.parse(deps.readFileSync(pkgJson, "utf8")).version;
if (version) return version;
}
}
} catch {
// fall through to monorepo workspace fallback
}
if (deps.existsSync(workspacePkg)) {
try {
return JSON.parse(deps.readFileSync(workspacePkg, "utf8")).version ?? null;
} catch {
return null;
}
}
return null;
}

/** Installed @jsonbored/gittensory-engine semver from node_modules (not the declared dependency range). */
export function readInstalledEnginePackageVersion() {
try {
return readInstalledEnginePackageVersionFromPaths(
require.resolve(ENGINE_PACKAGE),
join(__dirname, "../../gittensory-engine/package.json"),
);
} catch {
const workspacePkg = join(__dirname, "../../gittensory-engine/package.json");
if (existsSync(workspacePkg)) {
try {
return JSON.parse(readFileSync(workspacePkg, "utf8")).version ?? null;
} catch {
return null;
}
}
return null;
}
}

/** Expected minimum engine semver: monorepo engine package.json when present, else the shipped pin file. */
export function readExpectedEnginePackageVersionFromPaths(
monorepoEnginePkg,
pinFile,
deps = { existsSync, readFileSync },
) {
if (deps.existsSync(monorepoEnginePkg)) {
try {
return JSON.parse(deps.readFileSync(monorepoEnginePkg, "utf8")).version ?? null;
} catch {
return null;
}
}
try {
const pinned = deps.readFileSync(pinFile, "utf8").trim();
return pinned || null;
} catch {
return null;
}
}

export function readExpectedEnginePackageVersion() {
return readExpectedEnginePackageVersionFromPaths(
join(__dirname, "../../gittensory-engine/package.json"),
join(__dirname, "../expected-engine.version"),
);
}

function parseSemverCore(version) {
const match = String(version).trim().match(/^(\d+)\.(\d+)\.(\d+)/);
if (!match) return null;
return [Number(match[1]), Number(match[2]), Number(match[3])];
}

/** Returns -1 when installed is behind expected, 0 when equal, 1 when ahead. */
export function compareInstalledEngineVersion(installed, expected) {
const installedCore = parseSemverCore(installed);
const expectedCore = parseSemverCore(expected);
if (!installedCore || !expectedCore) return -1;
for (let index = 0; index < 3; index += 1) {
if (installedCore[index] < expectedCore[index]) return -1;
if (installedCore[index] > expectedCore[index]) return 1;
}
return 0;
}

export function buildEngineVersionSkewCheck(
readInstalled = readInstalledEnginePackageVersion,
readExpected = readExpectedEnginePackageVersion,
) {
const installed = readInstalled();
const expected = readExpected();
if (!expected) {
return { name: "engine-version-skew", ok: true, detail: "expected engine version unavailable (skipped)" };
}
if (!installed) {
return {
name: "engine-version-skew",
ok: false,
detail: `${ENGINE_PACKAGE} not installed (cannot verify version skew)`,
};
}
const comparison = compareInstalledEngineVersion(installed, expected);
return {
name: "engine-version-skew",
ok: comparison >= 0,
detail:
comparison < 0
? `installed ${installed} is behind expected ${expected}`
: `installed ${installed} (${comparison === 0 ? "matches" : "ahead of"} expected ${expected})`,
};
}

function checkEngineVersionSkew() {
return buildEngineVersionSkewCheck();
}

/** The minimum Node major version from the package's `engines.node` floor (e.g. ">=22.13.0" → 22). */
function requiredNodeMajor() {
const engines = require("../package.json").engines;
Expand Down Expand Up @@ -122,6 +244,7 @@ export function runDoctorChecks(env = process.env) {
ok: engineVersion !== null,
detail: engineVersion ? `${ENGINE_PACKAGE} ${engineVersion}` : `${ENGINE_PACKAGE} not resolvable`,
},
checkEngineVersionSkew(),
checkStateDirWritable(resolveMinerStateDir(env)),
checkLaptopStateSqlite(env),
checkDockerPresent(),
Expand Down
3 changes: 2 additions & 1 deletion packages/gittensory-miner/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@
},
"files": [
"bin",
"lib"
"lib",
"expected-engine.version"
],
"scripts": {
"build": "node --check bin/gittensory-miner.js && node --check lib/version.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/claim-ledger-cli.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.js"
Expand Down
Loading
Loading