diff --git a/.gittensory.yml.example b/.gittensory.yml.example index 3b493d64b3..f4304f1233 100644 --- a/.gittensory.yml.example +++ b/.gittensory.yml.example @@ -544,20 +544,6 @@ review: # (CI green, gate passing, mergeable-clean, valid linked issue). SURFACE ONLY — never changes the decision. # auto_merge_summary: false - # Deterministic label suggestions (#2045). Each rule SUGGESTS a non-scoring label when a PR matches ALL of the - # `when` criteria it sets (at least one is required): when_paths (any changed path matches a glob), title_contains, - # description_contains (both case-insensitive). Suggestions are advisory; they are auto-applied only when the repo's - # autoLabelEnabled is on. Reserved `gittensor:` labels (scoring/type) are refused. Empty/unset ⇒ no suggestions. - labeling_rules: - - label: area:docs - when_paths: - - "docs/**" - - "**/*.md" - - label: needs:migration - when_paths: - - "migrations/**" - description_contains: schema - settings: # Who receives the public PR comment. # off | detected_contributors_only | all_prs. Default: detected_contributors_only. @@ -1008,13 +994,6 @@ settings: # enrichment: # deep-nesting: true # error-swallow: false -# # Deterministic {label, when} rules that SUGGEST a non-scoring label when a PR's changed paths/ -# # title/description match ALL of the rule's `when` criteria. Auto-applied only when the repo's -# # autoLabelEnabled is set. Reserved gittensor:* labels are refused at parse. Empty ⇒ no suggestion. -# # (This block is also active, uncommented, near the top of the live `review:` section above.) -# labeling_rules: -# - label: area:docs -# when_paths: ["docs/**", "**/*.md"] # # Per-repo self-host reviewer model/effort overrides (claude-code / codex). Self-host only; a hosted # # (Workers-AI) repo ignores this entirely. All-null/default ⇒ the operator's global env vars apply. # ai_model: diff --git a/config/examples/README.md b/config/examples/README.md index 4f364e81b9..e7ca184d83 100644 --- a/config/examples/README.md +++ b/config/examples/README.md @@ -45,7 +45,7 @@ From highest to lowest priority: 3. **Private shared base** (`${GITTENSORY_REPO_CONFIG_DIR}/_shared/.gittensory.yml`, #1959) — the lowest-priority private layer, deep-merged under both **1** and **2**. An operator running many repos writes a house review policy (e.g. a default `review.tone`, `path_filters`, or - `labeling_rules`) here **once** instead of copy-pasting it into every repo's per-repo file or + `exclude_paths`) here **once** instead of copy-pasting it into every repo's per-repo file or the global default. `.yaml`/`.json` are accepted, same as every other candidate. Absent (the default, common case) ⇒ byte-identical behavior to the pre-#1959 2-layer chain. 4. When **none** of the three private layers above exists, the loader falls back to the **public @@ -144,7 +144,7 @@ settings: An operator running **many** repos through the same self-host instance can express one house review policy — e.g. a default `review.tone`, a baseline `path_filters`/`wantedPaths` set, or -common `labeling_rules` — **once**, instead of copy-pasting it into every repo's per-repo file or +common `exclude_paths` — **once**, instead of copy-pasting it into every repo's per-repo file or even the global default. That policy lives at: ``` diff --git a/config/examples/gittensory.full.yml b/config/examples/gittensory.full.yml index af3e99eeec..d2296f3be1 100644 --- a/config/examples/gittensory.full.yml +++ b/config/examples/gittensory.full.yml @@ -557,20 +557,6 @@ review: # (CI green, gate passing, mergeable-clean, valid linked issue). SURFACE ONLY — never changes the decision. # auto_merge_summary: false - # Deterministic label suggestions (#2045). Each rule SUGGESTS a non-scoring label when a PR matches ALL of the - # `when` criteria it sets (at least one is required): when_paths (any changed path matches a glob), title_contains, - # description_contains (both case-insensitive). Suggestions are advisory; they are auto-applied only when the repo's - # autoLabelEnabled is on. Reserved `gittensor:` labels (scoring/type) are refused. Empty/unset ⇒ no suggestions. - labeling_rules: - - label: area:docs - when_paths: - - "docs/**" - - "**/*.md" - - label: needs:migration - when_paths: - - "migrations/**" - description_contains: schema - settings: # Who receives the public PR comment. # off | detected_contributors_only | all_prs. Default: detected_contributors_only. @@ -1021,13 +1007,6 @@ settings: # enrichment: # deep-nesting: true # error-swallow: false -# # Deterministic {label, when} rules that SUGGEST a non-scoring label when a PR's changed paths/ -# # title/description match ALL of the rule's `when` criteria. Auto-applied only when the repo's -# # autoLabelEnabled is set. Reserved gittensor:* labels are refused at parse. Empty ⇒ no suggestion. -# # (This block is also active, uncommented, near the top of the live `review:` section above.) -# labeling_rules: -# - label: area:docs -# when_paths: ["docs/**", "**/*.md"] # # Per-repo self-host reviewer model/effort overrides (claude-code / codex). Self-host only; a hosted # # (Workers-AI) repo ignores this entirely. All-null/default ⇒ the operator's global env vars apply. # ai_model: diff --git a/packages/gittensory-engine/src/focus-manifest.ts b/packages/gittensory-engine/src/focus-manifest.ts index 924210b14c..793cd6dbab 100644 --- a/packages/gittensory-engine/src/focus-manifest.ts +++ b/packages/gittensory-engine/src/focus-manifest.ts @@ -621,11 +621,6 @@ export type FocusManifestReviewConfig = { /** `review.auto_review`: deterministic eligibility filters that skip the AI review (never a gate failure). * Empty/default ⇒ every PR is reviewed (byte-identical). (#1954 / #2038–#2041) */ autoReview: AutoReviewConfig; - /** `review.labeling_rules`: deterministic `{label, when}` rules that SUGGEST a non-scoring label when a PR's - * changed paths / title / description match. Surfaced as advisory suggestions, and auto-applied only when the - * repo's `autoLabelEnabled` is set. Reserved `gittensor:` labels are refused at parse. Empty (default) ⇒ no - * suggestion (byte-identical). (#2045, part of #1959) */ - labelingRules: LabelingRule[]; /** `review.ai_model`: per-repo self-host reviewer model/effort overrides (claude-code / codex). Self-host only * — a hosted (Workers-AI) repo ignores this entirely. All-null (default, absent) ⇒ the operator's global * CLAUDE_AI_MODEL/CLAUDE_AI_EFFORT/CODEX_AI_MODEL/CODEX_AI_EFFORT env vars apply unchanged (byte-identical). @@ -660,15 +655,6 @@ export type CommentVerbosity = (typeof COMMENT_VERBOSITY_LEVELS)[number]; export const E2E_TEST_DELIVERY_MODES = ["comment", "commit"] as const; export type E2eTestDeliveryMode = (typeof E2E_TEST_DELIVERY_MODES)[number]; -/** One `review.labeling_rules[]` entry: a non-reserved `label` plus the deterministic `when` criteria that must ALL - * match for it to fire. A rule always has at least one criterion (enforced at parse). */ -export type LabelingRule = { - label: string; - whenPaths: string[]; - titleContains: string | null; - 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 @@ -1043,7 +1029,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, impactMap: null, cultureProfile: null, selftune: null, reviewMemory: null, findingCategories: null, inlineCommentsPerCategory: null, minFindingSeverity: null, maxFindings: { ...EMPTY_MAX_FINDINGS_CONFIG }, commentVerbosity: null, e2eTestDelivery: null, e2eTestAutoTrigger: 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, impactMap: null, cultureProfile: null, selftune: null, reviewMemory: null, findingCategories: null, inlineCommentsPerCategory: null, minFindingSeverity: null, maxFindings: { ...EMPTY_MAX_FINDINGS_CONFIG }, commentVerbosity: null, e2eTestDelivery: null, e2eTestAutoTrigger: null, pathInstructions: [], instructions: null, excludePaths: [], pathFilters: [], preMergeChecks: [], autoReview: { ...EMPTY_AUTO_REVIEW_CONFIG }, 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 }, @@ -1074,7 +1060,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, impactMap: null, cultureProfile: null, selftune: null, reviewMemory: null, findingCategories: null, inlineCommentsPerCategory: null, minFindingSeverity: null, maxFindings: { ...EMPTY_MAX_FINDINGS_CONFIG }, commentVerbosity: null, e2eTestDelivery: null, e2eTestAutoTrigger: 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, impactMap: null, cultureProfile: null, selftune: null, reviewMemory: null, findingCategories: null, inlineCommentsPerCategory: null, minFindingSeverity: null, maxFindings: { ...EMPTY_MAX_FINDINGS_CONFIG }, commentVerbosity: null, e2eTestDelivery: null, e2eTestAutoTrigger: null, pathInstructions: [], instructions: null, excludePaths: [], pathFilters: [], preMergeChecks: [], autoReview: { ...EMPTY_AUTO_REVIEW_CONFIG }, 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 }, @@ -2179,7 +2165,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, impactMap: null, cultureProfile: null, selftune: null, reviewMemory: null, findingCategories: null, inlineCommentsPerCategory: null, minFindingSeverity: null, maxFindings: { ...EMPTY_MAX_FINDINGS_CONFIG }, commentVerbosity: null, e2eTestDelivery: null, e2eTestAutoTrigger: 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, impactMap: null, cultureProfile: null, selftune: null, reviewMemory: null, findingCategories: null, inlineCommentsPerCategory: null, minFindingSeverity: null, maxFindings: { ...EMPTY_MAX_FINDINGS_CONFIG }, commentVerbosity: null, e2eTestDelivery: null, e2eTestAutoTrigger: null, pathInstructions: [], instructions: null, excludePaths: [], pathFilters: [], preMergeChecks: [], autoReview: { ...EMPTY_AUTO_REVIEW_CONFIG }, 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.`); @@ -2247,7 +2233,6 @@ function parseReviewConfig(value: JsonValue | undefined, warnings: string[]): Fo const pathFilters = parseReviewPathFilters(r.path_filters, warnings); const preMergeChecks = parseReviewPreMergeChecks(r.pre_merge_checks, warnings); const autoReview = parseAutoReviewConfig(r.auto_review, warnings); - const labelingRules = parseReviewLabelingRules(r.labeling_rules, warnings); const aiModel = parseSelfHostAiModelConfig(r.ai_model, warnings); const visual = parseVisualConfig(r.visual, warnings); const linkedIssueSatisfaction = normalizeOptionalEnum(r.linkedIssueSatisfaction, "review.linkedIssueSatisfaction", LINKED_ISSUE_SATISFACTION_MODES, warnings); @@ -2281,7 +2266,6 @@ function parseReviewConfig(value: JsonValue | undefined, warnings: string[]): Fo pathFilters.length > 0 || preMergeChecks.length > 0 || autoReviewPresent(autoReview) || - labelingRules.length > 0 || selfHostAiModelPresent(aiModel) || visualConfigPresent(visual) || linkedIssueSatisfaction !== null || @@ -2320,7 +2304,6 @@ function parseReviewConfig(value: JsonValue | undefined, warnings: string[]): Fo excludePaths, pathFilters, preMergeChecks, - labelingRules, sharedConfigSource: null, }; } @@ -2421,7 +2404,6 @@ function computeReviewConfigPresent(review: Omit 0 || review.preMergeChecks.length > 0 || autoReviewPresent(review.autoReview) || - review.labelingRules.length > 0 || selfHostAiModelPresent(review.aiModel) || visualConfigPresent(review.visual) || review.linkedIssueSatisfaction !== null || @@ -2467,7 +2449,6 @@ export function overlayReviewConfig( pathFilters: pickOverlayStringList(override.pathFilters, base.pathFilters), preMergeChecks: override.preMergeChecks.length > 0 ? [...override.preMergeChecks] : [...base.preMergeChecks], autoReview: overlayAutoReviewConfig(base.autoReview, override.autoReview), - labelingRules: override.labelingRules.length > 0 ? [...override.labelingRules] : [...base.labelingRules], aiModel: overlaySelfHostAiModelConfig(base.aiModel, override.aiModel), visual: overlayVisualConfig(base.visual, override.visual), linkedIssueSatisfaction: pickOverlayNullable(override.linkedIssueSatisfaction, base.linkedIssueSatisfaction), @@ -2501,48 +2482,6 @@ function parseMaxFindingsConfig(value: JsonValue | undefined, warnings: string[] }; } -/** The reserved label namespace Gittensor uses for scoring/type/priority (`gittensor:bug`, `gittensor:feature`, - * `gittensor:priority`, …). A maintainer's `labeling_rules` must not drive these — they're managed by the scorer - * and the type-labeler, never by ad-hoc manifest rules — so any `gittensor:`-prefixed label is refused at parse. */ -const RESERVED_LABEL_PREFIX = "gittensor:"; - -function parseReviewLabelingRules(value: JsonValue | undefined, warnings: string[]): LabelingRule[] { - if (value === undefined || value === null) return []; - if (!Array.isArray(value)) { - warnings.push(`Manifest "review.labeling_rules" must be a list of rules; ignoring it.`); - return []; - } - const out: LabelingRule[] = []; - for (const [index, entry] of value.entries()) { - if (out.length >= MAX_PATH_INSTRUCTIONS) { - warnings.push(`Manifest "review.labeling_rules" is capped at ${MAX_PATH_INSTRUCTIONS} entries; dropping the rest.`); - break; - } - if (entry === null || typeof entry !== "object" || Array.isArray(entry)) { - warnings.push(`Manifest "review.labeling_rules[${index}]" must be a mapping; ignoring it.`); - continue; - } - const e = entry as Record; - const label = e.label === undefined || e.label === null ? null : parsePublicSafeText(e.label, `review.labeling_rules[${index}].label`, warnings); - if (label === null) { - if (e.label === undefined || e.label === null) warnings.push(`Manifest "review.labeling_rules[${index}].label" is required; ignoring the entry.`); - continue; // non-string / empty / not-public-safe already warned by parsePublicSafeText - } - if (label.toLowerCase().startsWith(RESERVED_LABEL_PREFIX)) { - warnings.push(`Manifest "review.labeling_rules[${index}].label" ("${label}") uses the reserved "${RESERVED_LABEL_PREFIX}" namespace; ignoring the entry.`); - continue; - } - const titleContains = e.title_contains === undefined || e.title_contains === null ? null : parsePublicSafeText(e.title_contains, `review.labeling_rules[${index}].title_contains`, warnings); - const descriptionContains = e.description_contains === undefined || e.description_contains === null ? null : parsePublicSafeText(e.description_contains, `review.labeling_rules[${index}].description_contains`, warnings); - const whenPaths = parseManifestGlobList(e.when_paths, `review.labeling_rules[${index}].when_paths`, warnings); - if (whenPaths.length === 0 && titleContains === null && descriptionContains === null) { - warnings.push(`Manifest "review.labeling_rules[${index}]" needs at least one of when_paths / title_contains / description_contains; ignoring it.`); - continue; - } - out.push({ label, whenPaths, titleContains, descriptionContains }); - } - return out; -} function autoReviewPresent(config: AutoReviewConfig): boolean { return ( @@ -3020,15 +2959,6 @@ export function reviewConfigToJson(review: FocusManifestReviewConfig): JsonValue } if (Object.keys(review.fields).length > 0) out.fields = { ...review.fields } as Record; if (Object.keys(review.enrichmentAnalyzers).length > 0) out.enrichment = { ...review.enrichmentAnalyzers } as Record; - if (review.labelingRules.length > 0) { - out.labeling_rules = review.labelingRules.map((rule) => { - const entry: Record = { label: rule.label }; - if (rule.whenPaths.length > 0) entry.when_paths = [...rule.whenPaths]; - if (rule.titleContains !== null) entry.title_contains = rule.titleContains; - if (rule.descriptionContains !== null) entry.description_contains = rule.descriptionContains; - return entry; - }); - } if (selfHostAiModelPresent(review.aiModel)) { const aiModel: Record = {}; if (review.aiModel.claudeModel !== null) aiModel.claude_model = review.aiModel.claudeModel; diff --git a/packages/gittensory-engine/src/index.ts b/packages/gittensory-engine/src/index.ts index 276a1e1cf2..b3e21244c1 100644 --- a/packages/gittensory-engine/src/index.ts +++ b/packages/gittensory-engine/src/index.ts @@ -490,7 +490,6 @@ export { type FocusManifestMaintainerRecapConfig, type FocusManifestSettings, type FocusManifestSource, - type LabelingRule, type LinkedIssueSatisfactionMode, type MaxFindingsConfig, type PreMergeCheck, diff --git a/src/signals/focus-manifest.ts b/src/signals/focus-manifest.ts index f0baa78437..82e754c4d1 100644 --- a/src/signals/focus-manifest.ts +++ b/src/signals/focus-manifest.ts @@ -59,7 +59,6 @@ export { type FocusManifestMaintainerRecapConfig, type FocusManifestSettings, type FocusManifestSource, - type LabelingRule, type LinkedIssueSatisfactionMode, type MaxFindingsConfig, type PreMergeCheck, diff --git a/test/unit/focus-manifest-engine-branch-coverage.test.ts b/test/unit/focus-manifest-engine-branch-coverage.test.ts index e2d43d874b..6cae914e12 100644 --- a/test/unit/focus-manifest-engine-branch-coverage.test.ts +++ b/test/unit/focus-manifest-engine-branch-coverage.test.ts @@ -33,79 +33,6 @@ describe("focus-manifest engine branch coverage (#2280)", () => { expect(withUnknown.warnings.some((w) => w.includes('unknown analyzer "notARealAnalyzer"'))).toBe(true); }); - it("validates review.labeling_rules entries and reserved gittensor: labels", () => { - const parsed = parseFocusManifest({ - review: { - labeling_rules: "not-a-list", - }, - }); - expect(parsed.review.labelingRules).toEqual([]); - expect(parsed.warnings.some((w) => w.includes("labeling_rules") && w.includes("list"))).toBe(true); - - const capped = parseFocusManifest({ - review: { - labeling_rules: Array.from({ length: 51 }, (_, index) => ({ - label: `area:${index}`, - when_paths: ["src/**"], - })), - }, - }); - expect(capped.review.labelingRules).toHaveLength(50); - expect(capped.warnings.some((w) => w.includes("capped at 50"))).toBe(true); - - const withMissingLabel = parseFocusManifest({ - review: { - labeling_rules: [{ when_paths: ["src/**"] }], - }, - }); - expect(withMissingLabel.review.labelingRules).toEqual([]); - expect(withMissingLabel.warnings.some((w) => w.includes(".label\" is required"))).toBe(true); - - const withRules = parseFocusManifest({ - review: { - labeling_rules: [ - "not-a-mapping", - { label: "gittensor:priority", when_paths: ["src/**"] }, - { label: "area:ui", when_paths: ["src/**"] }, - { label: "area:docs", title_contains: "docs" }, - { label: "area:empty" }, - ], - }, - }); - expect(withRules.review.labelingRules).toEqual([ - { label: "area:ui", whenPaths: ["src/**"], titleContains: null, descriptionContains: null }, - { label: "area:docs", whenPaths: [], titleContains: "docs", descriptionContains: null }, - ]); - expect(withRules.warnings.some((w) => w.includes("labeling_rules[0]") && w.includes("mapping"))).toBe(true); - expect(withRules.warnings.some((w) => w.includes('reserved "gittensor:"'))).toBe(true); - expect(withRules.warnings.some((w) => w.includes("needs at least one of when_paths"))).toBe(true); - }); - - it("serializes labeling_rules optional fields through reviewConfigToJson", () => { - const manifest = parseFocusManifest({ - review: { - labeling_rules: [ - { - label: "area:ui", - when_paths: ["src/**"], - title_contains: "feat", - description_contains: "screenshot", - }, - ], - }, - }); - expect(reviewConfigToJson(manifest.review)).toEqual({ - labeling_rules: [ - { - label: "area:ui", - when_paths: ["src/**"], - title_contains: "feat", - description_contains: "screenshot", - }, - ], - }); - }); - it("rejects manifest content whose UTF-8 byte length exceeds MAX_FOCUS_MANIFEST_BYTES", () => { const oversized = `wantedPaths:\n - ${"x".repeat(MAX_FOCUS_MANIFEST_BYTES)}`; const parsed = parseFocusManifestContent(oversized); @@ -177,7 +104,6 @@ describe("focus-manifest engine branch coverage (#2280)", () => { fixHandoff: true, auto_merge_summary: false, enrichment: { dependency: true }, - labeling_rules: [{ label: "area:ui", title_contains: "ui" }], linkedIssueSatisfaction: "advisory", visual: { routes: { max_routes: 3 } }, }, @@ -186,22 +112,11 @@ describe("focus-manifest engine branch coverage (#2280)", () => { fixHandoff: true, auto_merge_summary: false, enrichment: { dependency: true }, - labeling_rules: [{ label: "area:ui", title_contains: "ui" }], linkedIssueSatisfaction: "advisory", visual: { routes: { max_routes: 3 } }, }); }); - it("warns when a labeling rule entry omits label entirely", () => { - const parsed = parseFocusManifest({ - review: { - labeling_rules: [{ when_paths: ["src/**"] }, { label: null, when_paths: ["docs/**"] }], - }, - }); - expect(parsed.review.labelingRules).toEqual([]); - expect(parsed.warnings.filter((w) => w.includes(".label")).length).toBeGreaterThanOrEqual(2); - }); - it("covers remaining serializer and parser branch edges", () => { const slopScoreOnly = parseFocusManifest({ gate: { slop: { minScore: 55 } } }); expect(gateConfigToJson(slopScoreOnly.gate)).toEqual({ slop: { minScore: 55 } }); @@ -212,23 +127,6 @@ describe("focus-manifest engine branch coverage (#2280)", () => { expect(invalidEnrichmentFlag.review.enrichmentAnalyzers).toEqual({}); expect(invalidEnrichmentFlag.warnings.some((w) => w.includes("review.enrichment.dependency"))).toBe(true); - const missingLabelKey = parseFocusManifest({ - review: { labeling_rules: [{ when_paths: ["src/**"] }] }, - }); - expect(missingLabelKey.warnings.some((w) => w.includes('.label" is required'))).toBe(true); - - const explicitNullLabel = parseFocusManifest({ - review: { labeling_rules: [{ label: null, when_paths: ["src/**"] }] }, - }); - expect(explicitNullLabel.warnings.some((w) => w.includes('.label" is required'))).toBe(true); - - const notPublicSafeLabel = parseFocusManifest({ - review: { labeling_rules: [{ label: "reward farming", when_paths: ["src/**"] }] }, - }); - expect(notPublicSafeLabel.review.labelingRules).toEqual([]); - expect(notPublicSafeLabel.warnings.some((w) => w.includes("review.labeling_rules[0].label"))).toBe(true); - expect(notPublicSafeLabel.warnings.some((w) => w.includes('.label" is required'))).toBe(false); - const emptyTemplate = parseFocusManifest({ review: { visual: { preview: { url_template: "" } } }, }); @@ -238,12 +136,5 @@ describe("focus-manifest engine branch coverage (#2280)", () => { review: { instructions: "Prefer small diffs." }, }); expect(reviewConfigToJson(withInstructions.review)).toEqual({ instructions: "Prefer small diffs." }); - - const pathsOnlyRule = parseFocusManifest({ - review: { labeling_rules: [{ label: "area:ui", when_paths: ["src/**"] }] }, - }); - expect(reviewConfigToJson(pathsOnlyRule.review)).toEqual({ - labeling_rules: [{ label: "area:ui", when_paths: ["src/**"] }], - }); }); }); diff --git a/test/unit/focus-manifest.test.ts b/test/unit/focus-manifest.test.ts index d9593e8e67..2d54f6d7f7 100644 --- a/test/unit/focus-manifest.test.ts +++ b/test/unit/focus-manifest.test.ts @@ -400,7 +400,6 @@ describe(".gittensory.yml.example field-exhaustiveness (#1670)", () => { pathFilters: "path_filters:", preMergeChecks: "pre_merge_checks:", autoReview: "auto_review:", - labelingRules: "labeling_rules:", aiModel: "ai_model:", visual: "visual:", linkedIssueSatisfaction: "linkedIssueSatisfaction:", @@ -832,7 +831,7 @@ describe("compileFocusManifestPolicy", () => { publicNotes: ["Keep PRs focused.", "Maximize your reward payout"], gate: { present: false, enabled: null, checkMode: null, pack: null, linkedIssue: null, duplicates: null, readinessMode: null, readinessMinScore: null, slopMode: null, slopMinScore: null, slopAiAdvisory: null, sizeMode: null, lockfileIntegrityMode: null, aiReviewMode: null, aiReviewByok: null, aiReviewProvider: null, aiReviewModel: null, aiReviewAllAuthors: null, aiReviewCloseConfidence: null, aiReviewLowConfidenceDisposition: null, aiReviewCombine: null, aiReviewOnMerge: null, aiReviewReviewers: null, mergeReadiness: null, selfAuthoredLinkedIssue: null, linkedIssueSatisfaction: null, manifestPolicy: null, dryRun: null, firstTimeContributorGrace: null, premergeContentRecheck: null, requireFreshRebaseWindowMinutes: null, claMode: null, claConsentPhrase: null, claCheckRunName: null, claCheckRunAppSlug: null, expectedCiContexts: null, aiJudgmentBlockersMode: null, copycatMode: null, copycatMinScore: null }, 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, impactMap: null, cultureProfile: null, selftune: null, reviewMemory: null, findingCategories: null, inlineCommentsPerCategory: null, minFindingSeverity: null, maxFindings: { blockers: null, nits: null }, commentVerbosity: null, e2eTestDelivery: null, e2eTestAutoTrigger: 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, impactMap: null, cultureProfile: null, selftune: null, reviewMemory: null, findingCategories: null, inlineCommentsPerCategory: null, minFindingSeverity: null, maxFindings: { blockers: null, nits: null }, commentVerbosity: null, e2eTestDelivery: null, e2eTestAutoTrigger: null, pathInstructions: [], instructions: null, excludePaths: [], pathFilters: [], preMergeChecks: [], autoReview: { ...EMPTY_AUTO_REVIEW_CONFIG }, aiModel: { ...EMPTY_SELF_HOST_AI_MODEL_CONFIG }, visual: { ...EMPTY_VISUAL_CONFIG }, linkedIssueSatisfaction: null, sharedConfigSource: null }, features: { present: false, rag: null, reputation: null, unifiedComment: null, safety: null, grounding: null, e2eTests: null, screenshots: null, improvementSignal: null }, contentLane: { present: false, entryFileGlob: null, providerFileGlob: null, artifactGlob: null, collectionField: null, maxAppendedEntries: null, duplicateKeyFields: [], validatorId: null }, repoDocGeneration: { present: false, enabled: false, scope: ["agents"], allowOverwriteExisting: false, refreshIntervalDays: 7 }, diff --git a/test/unit/signals-coverage.test.ts b/test/unit/signals-coverage.test.ts index a55c9708d9..75fbaad8f8 100644 --- a/test/unit/signals-coverage.test.ts +++ b/test/unit/signals-coverage.test.ts @@ -1141,7 +1141,7 @@ describe("signal coverage edge cases", () => { collisions: buildCollisionReport(directRepo.fullName, [], [currentPr]), preflight: buildPreflightResult({ repoFullName: directRepo.fullName, title: "Fix isolated issue", body: "Fixes #99", linkedIssues: [99] }, directRepo, [], [currentPr]), settings: gateSettings, - review: { present: true, footerText: "Reviewed by the Acme maintainer bot.", note: "Run npm test before pushing.", fields: { relatedWork: false }, enrichmentAnalyzers: {}, profile: null, tone: null, securityFocus: null, inlineComments: null, fixHandoff: null, autoMergeSummary: null, suggestions: null, changedFilesSummary: null, effortScore: null, impactMap: null, cultureProfile: null, selftune: null, reviewMemory: null, findingCategories: null, inlineCommentsPerCategory: null, minFindingSeverity: null, maxFindings: { blockers: null, nits: null }, commentVerbosity: null, e2eTestDelivery: null, e2eTestAutoTrigger: null, pathInstructions: [], instructions: null, excludePaths: [], pathFilters: [], preMergeChecks: [], autoReview: { skipDrafts: null, cadence: null, ignoreAuthors: [], ignoreTitleKeywords: [], skipLabels: [], skipDocsOnly: null, maxAddedLines: 0, maxFiles: 0, baseBranches: [], autoPauseAfterReviewedCommits: null }, labelingRules: [], aiModel: { claudeModel: null, claudeEffort: null, codexModel: null, codexEffort: null, ollamaModel: null, openaiModel: null, openaiCompatibleModel: null, anthropicModel: null }, visual: { productionUrl: null, preview: { urlTemplate: null }, routes: { paths: [], maxRoutes: null }, themes: [], gif: false, enabled: null, themeStorageKey: null, actionsFallback: false }, linkedIssueSatisfaction: null, sharedConfigSource: null }, + review: { present: true, footerText: "Reviewed by the Acme maintainer bot.", note: "Run npm test before pushing.", fields: { relatedWork: false }, enrichmentAnalyzers: {}, profile: null, tone: null, securityFocus: null, inlineComments: null, fixHandoff: null, autoMergeSummary: null, suggestions: null, changedFilesSummary: null, effortScore: null, impactMap: null, cultureProfile: null, selftune: null, reviewMemory: null, findingCategories: null, inlineCommentsPerCategory: null, minFindingSeverity: null, maxFindings: { blockers: null, nits: null }, commentVerbosity: null, e2eTestDelivery: null, e2eTestAutoTrigger: null, pathInstructions: [], instructions: null, excludePaths: [], pathFilters: [], preMergeChecks: [], autoReview: { skipDrafts: null, cadence: null, ignoreAuthors: [], ignoreTitleKeywords: [], skipLabels: [], skipDocsOnly: null, maxAddedLines: 0, maxFiles: 0, baseBranches: [], autoPauseAfterReviewedCommits: null }, aiModel: { claudeModel: null, claudeEffort: null, codexModel: null, codexEffort: null, ollamaModel: null, openaiModel: null, openaiCompatibleModel: null, anthropicModel: null }, visual: { productionUrl: null, preview: { urlTemplate: null }, routes: { paths: [], maxRoutes: null }, themes: [], gif: false, enabled: null, themeStorageKey: null, actionsFallback: false }, linkedIssueSatisfaction: null, sharedConfigSource: null }, aiReview: { notes: "The change is focused.\n\n**Nits (2)**\n- Add a test for the edge case.\n- Keep the validator helper scoped." }, }); expect(customizedComment).toContain("Reviewed by the Acme maintainer bot."); // custom footer lead