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
1 change: 1 addition & 0 deletions apps/gittensory-ui/src/routes/docs.privacy-security.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ GITTENSORY_REVIEW_SCREENSHOTS="true" # before/after visual capture f
GITTENSORY_REVIEW_E2E_TESTS="true" # AI-generated E2E test coverage (needs features.e2eTests too)

# Global (cron / endpoint) flags, not scoped by GITTENSORY_REVIEW_REPOS.
GITTENSORY_REVIEW_CONTINUOUS="true" # fleet-wide default: re-review on every push (else one-shot)
GITTENSORY_REVIEW_OPS="true" # read-only anomaly scan + outcome stats endpoint
GITTENSORY_REVIEW_SELFTUNE="true" # self-tightening tuning loop, never loosens
GITTENSORY_REVIEW_PARITY_AUDIT="true" # shadow-record gate-decision parity readiness
Expand Down
12 changes: 12 additions & 0 deletions apps/gittensory-ui/src/routes/docs.tuning.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,18 @@ function Tuning() {
needs its own <code>features.e2eTests: true</code> override in{" "}
<code>.gittensory.yml</code> before the feature is active for it. Per-PR.
</li>
<li>
<code>GITTENSORY_REVIEW_CONTINUOUS</code> — fleet-wide default AI review re-trigger
cadence. Off by default (one-shot): AI-generated content (main review, slop advisory,
linked-issue satisfaction) is produced once per PR and never regenerated automatically
afterward — only an explicit maintainer retrigger (the PR-panel checkbox, or{" "}
<code>@gittensory review</code> as a maintainer) spends a fresh call. Truthy switches the
fleet default to continuous — every push/CI-completion/sweep re-runs AI content
generation. A repo's own <code>review.auto_review.cadence</code> in{" "}
<code>.gittensory.yml</code> always overrides this default, in either direction. Never
affects the deterministic gate (CI status, mergeability, static-rule blockers), which
always re-evaluates regardless.
</li>
<li>
<code>GITTENSORY_REVIEW_RAG</code> — retrieval-augmented context: queries the codebase
vector index for related code and docs (callers, related modules, existing conventions)
Expand Down
20 changes: 20 additions & 0 deletions packages/gittensory-engine/src/focus-manifest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -644,10 +644,25 @@ export type LabelingRule = {
descriptionContains: string | null;
};

/** `review.auto_review.cadence` (#one-shot-review-cadence). `one_shot` = the AI-generated content (main review,
* slop advisory, linked-issue satisfaction) is produced once per PR and never automatically regenerated
* afterward — not on a new push, not on CI-check completion, not on a scheduled sweep tick; only an explicit
* maintainer retrigger (the PR-panel checkbox or `@gittensory review` as a maintainer) spends a fresh call.
* `continuous` = the traditional behavior — every trigger re-runs AI content generation, subject to each
* feature's own head-SHA cache. Orthogonal to `aiReviewMode`'s enforcement-strictness axis (off/advisory/
* block) — the deterministic gate (CI status, mergeability, static-rule blockers) is NEVER affected by this
* and always re-evaluates on every pass regardless of cadence. */
export const AI_REVIEW_CADENCES = ["one_shot", "continuous"] as const;
export type AiReviewCadence = (typeof AI_REVIEW_CADENCES)[number];

/** Per-repo AI review eligibility knobs under `review.auto_review`. Unset fields are byte-identical defaults. */
export type AutoReviewConfig = {
/** `review.auto_review.skip_drafts`: when true, draft PRs skip AI review. null (default) ⇒ drafts reviewed as today. (#2038) */
skipDrafts: boolean | null;
/** `review.auto_review.cadence`: per-repo override of the AI review re-trigger cadence. null (default) ⇒
* inherit the operator's fleet-wide GITTENSORY_REVIEW_CONTINUOUS default (itself "one_shot" when unset).
* (#one-shot-review-cadence) */
cadence: AiReviewCadence | null;
/** `review.auto_review.ignore_authors`: author-login globs whose PRs skip AI review. Empty ⇒ every author. (#2039) */
ignoreAuthors: string[];
/** `review.auto_review.ignore_title_keywords`: case-insensitive title substrings that skip AI review. Empty ⇒ no skip. (#2040) */
Expand Down Expand Up @@ -677,6 +692,7 @@ export const EMPTY_MAX_FINDINGS_CONFIG: MaxFindingsConfig = { blockers: null, ni

export const EMPTY_AUTO_REVIEW_CONFIG: AutoReviewConfig = {
skipDrafts: null,
cadence: null,
ignoreAuthors: [],
ignoreTitleKeywords: [],
skipLabels: [],
Expand Down Expand Up @@ -2299,6 +2315,7 @@ function overlayMaxFindingsConfig(base: MaxFindingsConfig, override: MaxFindings
function overlayAutoReviewConfig(base: AutoReviewConfig, override: AutoReviewConfig): AutoReviewConfig {
return {
skipDrafts: pickOverlayNullable(override.skipDrafts, base.skipDrafts),
cadence: pickOverlayNullable(override.cadence, base.cadence),
ignoreAuthors: pickOverlayStringList(override.ignoreAuthors, base.ignoreAuthors),
ignoreTitleKeywords: pickOverlayStringList(override.ignoreTitleKeywords, base.ignoreTitleKeywords),
skipLabels: pickOverlayStringList(override.skipLabels, base.skipLabels),
Expand Down Expand Up @@ -2493,6 +2510,7 @@ function parseReviewLabelingRules(value: JsonValue | undefined, warnings: string
function autoReviewPresent(config: AutoReviewConfig): boolean {
return (
config.skipDrafts !== null ||
config.cadence !== null ||
config.ignoreAuthors.length > 0 ||
config.ignoreTitleKeywords.length > 0 ||
config.skipLabels.length > 0 ||
Expand All @@ -2514,6 +2532,7 @@ function parseAutoReviewConfig(value: JsonValue | undefined, warnings: string[])
const record = value as Record<string, JsonValue>;
return {
skipDrafts: normalizeOptionalBoolean(record.skip_drafts, "review.auto_review.skip_drafts", warnings),
cadence: normalizeOptionalEnum(record.cadence, "review.auto_review.cadence", AI_REVIEW_CADENCES, warnings),
ignoreAuthors: parseManifestGlobList(record.ignore_authors, "review.auto_review.ignore_authors", warnings),
ignoreTitleKeywords: parseAutoReviewTitleKeywords(record.ignore_title_keywords, warnings),
skipLabels: parseAutoReviewSkipLabels(record.skip_labels, warnings),
Expand Down Expand Up @@ -2937,6 +2956,7 @@ export function reviewConfigToJson(review: FocusManifestReviewConfig): JsonValue
if (autoReviewPresent(review.autoReview)) {
const autoReview: Record<string, JsonValue> = {};
if (review.autoReview.skipDrafts !== null) autoReview.skip_drafts = review.autoReview.skipDrafts;
if (review.autoReview.cadence !== null) autoReview.cadence = review.autoReview.cadence;
if (review.autoReview.ignoreAuthors.length > 0) autoReview.ignore_authors = [...review.autoReview.ignoreAuthors];
if (review.autoReview.ignoreTitleKeywords.length > 0) autoReview.ignore_title_keywords = [...review.autoReview.ignoreTitleKeywords];
if (review.autoReview.skipLabels.length > 0) autoReview.skip_labels = [...review.autoReview.skipLabels];
Expand Down
32 changes: 32 additions & 0 deletions src/db/repositories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4759,6 +4759,19 @@ export async function putCachedAiSlopAdvisory(
.run();
}

/** #one-shot-review-cadence: does at least one slop-advisory row exist for this PR, regardless of head SHA?
* Consulted ONLY when the resolved AI review cadence is "one_shot" — an existing row means this PR already
* had its one-shot slop pass, so a later automatic trigger (push/CI-completion/sweep) must not spend another
* LLM call. Existence-only (no reuse-for-display): mirrors the pre-existing commitThresholdReached silent-skip
* precedent for this same feature, which also does not resurface a prior finding once paused. */
export async function hasPublishedAiSlopAdvisory(env: Env, repoFullName: string, pullNumber: number): Promise<boolean> {
const row = await env.DB
.prepare("SELECT 1 AS present FROM ai_slop_cache WHERE repo_full_name = ? AND pull_number = ? LIMIT 1")
.bind(repoFullName, pullNumber)
.first<{ present: number }>();
return Boolean(row);
}

/** #linked-issue-satisfaction-cache: the stored linked-issue satisfaction result for (repo, pull, head SHA,
* linked issue number), or null on a miss. Mirrors getCachedAiSlopAdvisory -- every stored row is
* unconditionally durable (no cacheable/allowNonCacheable/maxAgeMs dimension). A nullish head SHA is always a
Expand Down Expand Up @@ -4814,6 +4827,25 @@ export async function putCachedLinkedIssueSatisfaction(
.run();
}

/** #one-shot-review-cadence: does a linked-issue satisfaction row exist for this PR + linked issue number,
* regardless of head SHA? Consulted ONLY when the resolved AI review cadence is "one_shot", mirroring
* hasPublishedAiSlopAdvisory. Scoped ADDITIONALLY to linkedIssueNumber (not just the PR) — a PR's primary
* linked issue can change between passes (see linked_issue_satisfaction_cache's own doc comment), and a
* newly-linked issue has never been assessed, so it must still get its own first pass under one-shot mode
* rather than being silently blocked by an unrelated issue's prior assessment. */
export async function hasPublishedLinkedIssueSatisfaction(
env: Env,
repoFullName: string,
pullNumber: number,
linkedIssueNumber: number,
): Promise<boolean> {
const row = await env.DB
.prepare("SELECT 1 AS present FROM linked_issue_satisfaction_cache WHERE repo_full_name = ? AND pull_number = ? AND linked_issue_number = ? LIMIT 1")
.bind(repoFullName, pullNumber, linkedIssueNumber)
.first<{ present: number }>();
return Boolean(row);
}

/** #4499 (grounding-file-content-cache): the stored file content for (repo, path, head SHA), or null on a
* miss. Unlike linked_issue_satisfaction_cache, every stored row is durable with NO input-fingerprint
* dimension -- file content at an immutable head SHA has exactly one correct value, so a hit is always safe
Expand Down
9 changes: 9 additions & 0 deletions src/env.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,15 @@ declare global {
* E2E test coverage feature. Default OFF — unset/false the feature is never active for any repo regardless
* of a per-repo `features.e2eTests` override. */
GITTENSORY_REVIEW_E2E_TESTS?: string;
/** #one-shot-review-cadence: the operator's FLEET-WIDE default for AI review re-trigger cadence, consulted
* only when a repo's `.gittensory.yml review.auto_review.cadence` is unset (a per-repo value always wins
* regardless of this flag — see resolveAiReviewCadence). Default OFF (unset/false) ⇒ "one_shot": the
* AI-generated content (main review, slop advisory, linked-issue satisfaction) freezes after its first
* pass for every repo, and only an explicit maintainer retrigger spends a fresh call. Truthy ⇒
* "continuous": the traditional behavior where every push/CI-completion/sweep trigger re-runs AI content
* generation, for operators who prefer that over one-shot. Never affects the deterministic gate (CI
* status, mergeability, static-rule blockers), which always re-evaluates regardless of this flag. */
GITTENSORY_REVIEW_CONTINUOUS?: string;
/** Convergence (reputation): when truthy, the INTERNAL-only ported submitter-reputation signal extends the
* AI-spend gate — a new / burst / low-reputation submitter is downgraded to a deterministic-only review
* (the AI neurons are skipped), and the per-(project, submitter) outcome is recorded after the gate
Expand Down
Loading
Loading