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 .gittensory.yml.example
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,14 @@ review:
# null. Default: null/false — byte-identical. (#2995)
# culture_profile: false

# Per-repo FORCE-OFF for the self-improvement/auto-tune cron pass (#4104) -- `false` excludes this repo from
# tuning even though it's otherwise agent-configured and the operator's GITTENSORY_REVIEW_SELFTUNE flag is on.
# Deliberately FORCE-OFF-ONLY (no `true` override): unlike most `review.*` toggles above, this does NOT live
# under a per-repo allowlist -- selftune's own scoping is whether the repo already opted into the
# acting-autonomy surface, a separate consent boundary this key must not bypass. Bool or null. Default:
# null/true -- no change to today's agent-configured-repos-only behavior.
# selftune: false

# Repeat-false-positive suppression (#2179, part of #1964). Bool | null. Default: null/false — byte-identical
# (no suppression-store read, no matching). Also requires the operator's GITTENSORY_REVIEW_MEMORY env flag to
# be on -- this manifest field alone cannot enable it. When both are on, an advisory (non-blocking) AI finding
Expand Down Expand Up @@ -889,6 +897,11 @@ settings:
# # (recent_merged_pull_requests). Reference-only grounding, never a gate/scoring input; requires the operator
# # flag GITTENSORY_REVIEW_CULTURE_PROFILE. Bool or null. Default: null/false. (#2995)
# culture_profile: false
# # Per-repo FORCE-OFF for the self-improvement/auto-tune cron pass (#4104) -- false excludes this repo from
# # tuning even though it's otherwise agent-configured and the operator flag is on. FORCE-OFF-ONLY, no true
# # override -- selftune's own scoping is the acting-autonomy consent boundary, not a per-repo allowlist. Bool
# # or null. Default: null/true -- no change to today's agent-configured-repos-only behavior.
# selftune: false
# # When true (AND the operator's GITTENSORY_REVIEW_MEMORY env flag is also on), an advisory (non-blocking)
# # AI finding is matched against this repo's stored review_suppression signals (a maintainer's own past
# # false-positive dismissals) before it is surfaced, and demoted/dropped on a match. ADVISORY-ONLY: never
Expand Down
13 changes: 13 additions & 0 deletions config/examples/gittensory.full.yml
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,14 @@ review:
# null. Default: null/false — byte-identical. (#2995)
# culture_profile: false

# Per-repo FORCE-OFF for the self-improvement/auto-tune cron pass (#4104) -- `false` excludes this repo from
# tuning even though it's otherwise agent-configured and the operator's GITTENSORY_REVIEW_SELFTUNE flag is on.
# Deliberately FORCE-OFF-ONLY (no `true` override): unlike most `review.*` toggles above, this does NOT live
# under a per-repo allowlist -- selftune's own scoping is whether the repo already opted into the
# acting-autonomy surface, a separate consent boundary this key must not bypass. Bool or null. Default:
# null/true -- no change to today's agent-configured-repos-only behavior.
# selftune: false

# Repeat-false-positive suppression (#2179, part of #1964). Bool | null. Default: null/false — byte-identical
# (no suppression-store read, no matching). Also requires the operator's GITTENSORY_REVIEW_MEMORY env flag to
# be on -- this manifest field alone cannot enable it. When both are on, an advisory (non-blocking) AI finding
Expand Down Expand Up @@ -902,6 +910,11 @@ settings:
# # (recent_merged_pull_requests). Reference-only grounding, never a gate/scoring input; requires the operator
# # flag GITTENSORY_REVIEW_CULTURE_PROFILE. Bool or null. Default: null/false. (#2995)
# culture_profile: false
# # Per-repo FORCE-OFF for the self-improvement/auto-tune cron pass (#4104) -- false excludes this repo from
# # tuning even though it's otherwise agent-configured and the operator flag is on. FORCE-OFF-ONLY, no true
# # override -- selftune's own scoping is the acting-autonomy consent boundary, not a per-repo allowlist. Bool
# # or null. Default: null/true -- no change to today's agent-configured-repos-only behavior.
# selftune: false
# # When true (AND the operator's GITTENSORY_REVIEW_MEMORY env flag is also on), an advisory (non-blocking)
# # AI finding is matched against this repo's stored review_suppression signals (a maintainer's own past
# # false-positive dismissals) before it is surfaced, and demoted/dropped on a match. ADVISORY-ONLY: never
Expand Down
28 changes: 24 additions & 4 deletions packages/gittensory-engine/src/focus-manifest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,11 @@ export type FocusManifestGateConfig = {
// shape as rag/reputation. `screenshots` has its own richer `visual:` block instead (review.visual.enabled,
// #4083) since it carries more than a single boolean. contentLane got its own richer `contentLane:` block below
// (#2435) instead of a boolean here, since it resolves to a whole RegistryLaneSpec, not an on/off toggle — see
// resolveRegistryLaneSpec in review/content-lane/spec-resolver.ts.
// resolveRegistryLaneSpec in review/content-lane/spec-resolver.ts. `selftune` (#4104) ALSO deliberately lives
// outside this block, as its own top-level `review.selftune` field below — it has no `GITTENSORY_REVIEW_REPOS`
// allowlist to fall back to (its own repo scoping is `isAgentConfigured`, a different consent boundary), so it
// doesn't fit this resolver's env-kill-switch → override → allowlist-default shape; see `selfTuneRepos` in
// `review/selftune-wire.ts`.
export const CONVERGED_FEATURE_KEYS = ["rag", "reputation", "unifiedComment", "safety", "grounding"] as const;
export type ConvergedFeatureKey = (typeof CONVERGED_FEATURE_KEYS)[number];

Expand Down Expand Up @@ -454,6 +458,16 @@ export type FocusManifestReviewConfig = {
* field only opts THIS repo in once the capability itself is enabled). null/false (default, absent) = no
* section appended = byte-identical behavior. */
cultureProfile: boolean | null;
/** `review.selftune` (#4104): explicit per-repo FORCE-OFF for the self-improvement/auto-tune cron pass
* (`runSelfTune`, `src/review/selftune-wire.ts`) — `false` excludes this repo from tuning even though it's
* otherwise agent-configured (`isAgentConfigured`) and the global `GITTENSORY_REVIEW_SELFTUNE` kill-switch is
* on. Deliberately FORCE-OFF-ONLY (no `true` override): forcing a NON-agent-configured repo INTO tuning would
* bypass that separate, broader acting-autonomy consent boundary, which this key must not touch. Unlike
* `impactMap`/`cultureProfile` above, there is no `GITTENSORY_REVIEW_REPOS` allowlist fallback for selftune —
* its own scoping is `isAgentConfigured`, not the cutover allowlist — so this does NOT live under the generic
* `features:` block/`resolveConvergedFeature` (see `CONVERGED_FEATURE_KEYS`'s own comment). null/true
* (default, absent) ⇒ no change to today's agent-configured-repos-only behavior. */
selftune: boolean | null;
/** `review.memory` (#2179, config slice of #1964): when true, gates repeat-false-positive SUPPRESSION —
* before an advisory (non-blocking) AI finding is surfaced in the unified review comment, it is matched
* against this repo's stored `review_suppression` signals (a maintainer's own past false-positive
Expand Down Expand Up @@ -873,7 +887,7 @@ const EMPTY_MANIFEST: FocusManifest = {
publicNotes: [],
gate: { ...EMPTY_GATE_CONFIG },
settings: {},
review: { present: false, footerText: null, note: null, fields: {}, enrichmentAnalyzers: {}, profile: null, tone: null, securityFocus: null, inlineComments: null, fixHandoff: null, autoMergeSummary: null, suggestions: null, changedFilesSummary: null, effortScore: null, testGeneration: null, impactMap: null, cultureProfile: null, reviewMemory: null, findingCategories: null, inlineCommentsPerCategory: null, minFindingSeverity: null, maxFindings: { ...EMPTY_MAX_FINDINGS_CONFIG }, commentVerbosity: null, pathInstructions: [], instructions: null, excludePaths: [], pathFilters: [], preMergeChecks: [], autoReview: { ...EMPTY_AUTO_REVIEW_CONFIG }, labelingRules: [], aiModel: { ...EMPTY_SELF_HOST_AI_MODEL_CONFIG }, visual: { ...EMPTY_VISUAL_CONFIG }, linkedIssueSatisfaction: null, sharedConfigSource: null },
review: { present: false, footerText: null, note: null, fields: {}, enrichmentAnalyzers: {}, profile: null, tone: null, securityFocus: null, inlineComments: null, fixHandoff: null, autoMergeSummary: null, suggestions: null, changedFilesSummary: null, effortScore: null, testGeneration: null, impactMap: null, cultureProfile: null, selftune: null, reviewMemory: null, findingCategories: null, inlineCommentsPerCategory: null, minFindingSeverity: null, maxFindings: { ...EMPTY_MAX_FINDINGS_CONFIG }, commentVerbosity: null, pathInstructions: [], instructions: null, excludePaths: [], pathFilters: [], preMergeChecks: [], autoReview: { ...EMPTY_AUTO_REVIEW_CONFIG }, labelingRules: [], aiModel: { ...EMPTY_SELF_HOST_AI_MODEL_CONFIG }, visual: { ...EMPTY_VISUAL_CONFIG }, linkedIssueSatisfaction: null, sharedConfigSource: null },
features: { ...EMPTY_FEATURES_CONFIG },
contentLane: { ...EMPTY_CONTENT_LANE_CONFIG },
repoDocGeneration: { ...EMPTY_REPO_DOC_GENERATION_CONFIG },
Expand Down Expand Up @@ -903,7 +917,7 @@ function emptyManifest(source: FocusManifestSource, warnings: string[] = []): Fo
warnings,
gate: { ...EMPTY_GATE_CONFIG },
settings: {},
review: { present: false, footerText: null, note: null, fields: {}, enrichmentAnalyzers: {}, profile: null, tone: null, securityFocus: null, inlineComments: null, fixHandoff: null, autoMergeSummary: null, suggestions: null, changedFilesSummary: null, effortScore: null, testGeneration: null, impactMap: null, cultureProfile: null, reviewMemory: null, findingCategories: null, inlineCommentsPerCategory: null, minFindingSeverity: null, maxFindings: { ...EMPTY_MAX_FINDINGS_CONFIG }, commentVerbosity: null, pathInstructions: [], instructions: null, excludePaths: [], pathFilters: [], preMergeChecks: [], autoReview: { ...EMPTY_AUTO_REVIEW_CONFIG }, labelingRules: [], aiModel: { ...EMPTY_SELF_HOST_AI_MODEL_CONFIG }, visual: { ...EMPTY_VISUAL_CONFIG }, linkedIssueSatisfaction: null, sharedConfigSource: null },
review: { present: false, footerText: null, note: null, fields: {}, enrichmentAnalyzers: {}, profile: null, tone: null, securityFocus: null, inlineComments: null, fixHandoff: null, autoMergeSummary: null, suggestions: null, changedFilesSummary: null, effortScore: null, testGeneration: null, impactMap: null, cultureProfile: null, selftune: null, reviewMemory: null, findingCategories: null, inlineCommentsPerCategory: null, minFindingSeverity: null, maxFindings: { ...EMPTY_MAX_FINDINGS_CONFIG }, commentVerbosity: null, pathInstructions: [], instructions: null, excludePaths: [], pathFilters: [], preMergeChecks: [], autoReview: { ...EMPTY_AUTO_REVIEW_CONFIG }, labelingRules: [], aiModel: { ...EMPTY_SELF_HOST_AI_MODEL_CONFIG }, visual: { ...EMPTY_VISUAL_CONFIG }, linkedIssueSatisfaction: null, sharedConfigSource: null },
features: { ...EMPTY_FEATURES_CONFIG },
contentLane: { ...EMPTY_CONTENT_LANE_CONFIG },
repoDocGeneration: { ...EMPTY_REPO_DOC_GENERATION_CONFIG },
Expand Down Expand Up @@ -1907,7 +1921,7 @@ function parsePublicSafeText(value: JsonValue | undefined, field: string, warnin
* throws; invalid/unsafe values are dropped with warnings.
*/
function parseReviewConfig(value: JsonValue | undefined, warnings: string[]): FocusManifestReviewConfig {
const empty: FocusManifestReviewConfig = { present: false, footerText: null, note: null, fields: {}, enrichmentAnalyzers: {}, profile: null, tone: null, securityFocus: null, inlineComments: null, fixHandoff: null, autoMergeSummary: null, suggestions: null, changedFilesSummary: null, effortScore: null, testGeneration: null, impactMap: null, cultureProfile: null, reviewMemory: null, findingCategories: null, inlineCommentsPerCategory: null, minFindingSeverity: null, maxFindings: { ...EMPTY_MAX_FINDINGS_CONFIG }, commentVerbosity: null, pathInstructions: [], instructions: null, excludePaths: [], pathFilters: [], preMergeChecks: [], autoReview: { ...EMPTY_AUTO_REVIEW_CONFIG }, labelingRules: [], aiModel: { ...EMPTY_SELF_HOST_AI_MODEL_CONFIG }, visual: { ...EMPTY_VISUAL_CONFIG }, linkedIssueSatisfaction: null, sharedConfigSource: null };
const empty: FocusManifestReviewConfig = { present: false, footerText: null, note: null, fields: {}, enrichmentAnalyzers: {}, profile: null, tone: null, securityFocus: null, inlineComments: null, fixHandoff: null, autoMergeSummary: null, suggestions: null, changedFilesSummary: null, effortScore: null, testGeneration: null, impactMap: null, cultureProfile: null, selftune: null, reviewMemory: null, findingCategories: null, inlineCommentsPerCategory: null, minFindingSeverity: null, maxFindings: { ...EMPTY_MAX_FINDINGS_CONFIG }, commentVerbosity: null, pathInstructions: [], instructions: null, excludePaths: [], pathFilters: [], preMergeChecks: [], autoReview: { ...EMPTY_AUTO_REVIEW_CONFIG }, labelingRules: [], aiModel: { ...EMPTY_SELF_HOST_AI_MODEL_CONFIG }, visual: { ...EMPTY_VISUAL_CONFIG }, linkedIssueSatisfaction: null, sharedConfigSource: null };
if (value === undefined || value === null) return empty;
if (typeof value !== "object" || Array.isArray(value)) {
warnings.push(`Manifest field "review" must be a mapping; ignoring it.`);
Expand Down Expand Up @@ -1952,6 +1966,7 @@ function parseReviewConfig(value: JsonValue | undefined, warnings: string[]): Fo
const testGeneration = normalizeOptionalBoolean(r.test_generation, "review.test_generation", warnings);
const impactMap = normalizeOptionalBoolean(r.impact_map, "review.impact_map", warnings);
const cultureProfile = normalizeOptionalBoolean(r.culture_profile, "review.culture_profile", warnings);
const selftune = normalizeOptionalBoolean(r.selftune, "review.selftune", warnings);
const reviewMemory = normalizeOptionalBoolean(r.memory, "review.memory", warnings);
const findingCategories = normalizeOptionalBoolean(r.finding_categories, "review.finding_categories", warnings);
const inlineCommentsPerCategory = normalizeOptionalNonNegativeInt(
Expand Down Expand Up @@ -1993,6 +2008,7 @@ function parseReviewConfig(value: JsonValue | undefined, warnings: string[]): Fo
testGeneration !== null ||
impactMap !== null ||
cultureProfile !== null ||
selftune !== null ||
reviewMemory !== null ||
findingCategories !== null ||
inlineCommentsPerCategory !== null ||
Expand Down Expand Up @@ -2031,6 +2047,7 @@ function parseReviewConfig(value: JsonValue | undefined, warnings: string[]): Fo
effortScore,
impactMap,
cultureProfile,
selftune,
reviewMemory,
findingCategories,
inlineCommentsPerCategory,
Expand Down Expand Up @@ -2125,6 +2142,7 @@ function computeReviewConfigPresent(review: Omit<FocusManifestReviewConfig, "pre
review.testGeneration !== null ||
review.impactMap !== null ||
review.cultureProfile !== null ||
review.selftune !== null ||
review.reviewMemory !== null ||
review.findingCategories !== null ||
review.inlineCommentsPerCategory !== null ||
Expand Down Expand Up @@ -2169,6 +2187,7 @@ export function overlayReviewConfig(
testGeneration: pickOverlayNullable(override.testGeneration, base.testGeneration),
impactMap: pickOverlayNullable(override.impactMap, base.impactMap),
cultureProfile: pickOverlayNullable(override.cultureProfile, base.cultureProfile),
selftune: pickOverlayNullable(override.selftune, base.selftune),
reviewMemory: pickOverlayNullable(override.reviewMemory, base.reviewMemory),
findingCategories: pickOverlayNullable(override.findingCategories, base.findingCategories),
inlineCommentsPerCategory: pickOverlayNullable(override.inlineCommentsPerCategory, base.inlineCommentsPerCategory),
Expand Down Expand Up @@ -2658,6 +2677,7 @@ export function reviewConfigToJson(review: FocusManifestReviewConfig): JsonValue
if (review.testGeneration !== null) out.test_generation = review.testGeneration;
if (review.impactMap !== null) out.impact_map = review.impactMap;
if (review.cultureProfile !== null) out.culture_profile = review.cultureProfile;
if (review.selftune !== null) out.selftune = review.selftune;
if (review.reviewMemory !== null) out.memory = review.reviewMemory;
if (review.findingCategories !== null) out.finding_categories = review.findingCategories;
if (review.inlineCommentsPerCategory !== null) out.inline_comments_per_category = review.inlineCommentsPerCategory;
Expand Down
19 changes: 17 additions & 2 deletions src/review/selftune-wire.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import { listRepositories } from "../db/repositories";
import { isAgentConfigured } from "../settings/autonomy";
import { resolveRepositorySettings } from "../settings/repository-settings";
import { buildRepoOutcomeCalibration } from "../services/outcome-calibration";
import { loadRepoFocusManifest } from "../signals/focus-manifest-loader";
import { errorMessage } from "../utils/json";
import { computeTuningRecommendations, type GateEvalReport, type GateEvalRow } from "./auto-tune";
import { runAutoApplyRecommendations, type StorageEnv } from "./auto-apply";
Expand Down Expand Up @@ -88,14 +89,28 @@ async function buildEvalRow(env: Env, repoFullName: string): Promise<GateEvalRow
}

/** The registered, agent-configured repos to tune over — SAME scoping the ops scan + regate sweep use (only
* repos that opt into the acting-autonomy surface). A repo whose settings blip is skipped, never aborts. */
* repos that opt into the acting-autonomy surface). A repo whose settings blip is skipped, never aborts.
*
* Per-repo opt-out (#4104): unlike rag/reputation/grounding, selftune has no `GITTENSORY_REVIEW_REPOS`
* allowlist to fall back to — every agent-configured repo is already IN by default once the global flag is
* on. So this doesn't fit `resolveConvergedFeature`'s env-kill-switch → override → allowlist-default shape;
* there is no allowlist. Instead, deliberately FORCE-OFF-ONLY (mirroring the `safety` feature's asymmetric
* precedent, #2269, just in the opposite direction): an explicit per-repo `.gittensory.yml`
* `review.selftune: false` excludes that one repo from the tuning pass even though it's otherwise
* agent-configured. There is no `true` override — forcing a NON-agent-configured repo INTO the tuning pass
* would bypass its owner's separate, broader acting-autonomy consent (`isAgentConfigured`), an unrelated
* safety boundary this config key must not touch. Unset (the default) changes nothing. A manifest-load error
* fails open (repo stays included), matching the existing settings-blip fail-safe below. */
async function selfTuneRepos(env: Env): Promise<string[]> {
const repos = (await listRepositories(env)).filter((repo) => repo.isRegistered);
const configured: string[] = [];
for (const repo of repos) {
try {
const settings = await resolveRepositorySettings(env, repo.fullName);
if (isAgentConfigured(settings.autonomy)) configured.push(repo.fullName);
if (!isAgentConfigured(settings.autonomy)) continue;
const manifest = await loadRepoFocusManifest(env, repo.fullName).catch(() => null);
if (manifest?.review.selftune === false) continue; // explicit per-repo opt-out
configured.push(repo.fullName);
} catch {
/* a settings blip on one repo must not abort the whole tuning pass */
}
Expand Down
Loading