@@ -150,10 +150,11 @@ function resolveSection(body, headings) {
150150}
151151
152152/**
153- * True when the body has at least one non-empty h2–h4 section with enough
154- * detail. Used for soft-pass only — unstructured length alone is not enough.
153+ * True when the body has multiple non-empty h2–h4 sections with enough detail.
154+ * Soft-pass only — unstructured length alone is not enough, and a single
155+ * arbitrary heading must not bypass the quality gate (Codex on #564).
155156 */
156- function hasSubstantialStructuredContent ( body , minSectionLen = 40 ) {
157+ function hasSubstantialStructuredContent ( body , minSectionLen = 40 , minRichSections = 2 ) {
157158 if ( typeof body !== "string" ) return false ;
158159 const lines = body . split ( "\n" ) ;
159160 let capturing = false ;
@@ -173,7 +174,7 @@ function hasSubstantialStructuredContent(body, minSectionLen = 40) {
173174 if ( capturing ) bucket . push ( line ) ;
174175 }
175176 if ( capturing ) flush ( ) ;
176- return richSections >= 1 ;
177+ return richSections >= minRichSections ;
177178}
178179
179180// ---------------------------------------------------------------------------
@@ -541,7 +542,6 @@ function validateIssue(issue) {
541542 const reasons = [ ] ;
542543 const guidance = [ ] ;
543544 let softPass = false ;
544- const titleLower = title . toLowerCase ( ) ;
545545
546546 if ( ! kind ) {
547547 const failure = untemplatedIssueFailure ( issue ) ;
@@ -578,9 +578,11 @@ function validateIssue(issue) {
578578 goal !== null || blocker !== null || behaviour !== null || example !== null ;
579579
580580 if ( emptyCore . length > 0 ) {
581+ // Soft-pass rich non-template bodies once kind is already feature (title
582+ // prefix, enhancement label, or stored kind). Do not require the title to
583+ // keep a `[Feature]:` prefix — maintainer retitles must not re-arm closure.
581584 const canSoftPass =
582585 ! mappedHeadingPresent &&
583- titleLower . startsWith ( "[feature]:" ) &&
584586 hasSubstantialStructuredContent ( body ) ;
585587 if ( canSoftPass ) {
586588 softPass = true ;
@@ -625,10 +627,14 @@ function validateIssue(issue) {
625627 const os = extractSection ( body , "Operating system" ) ?? extractSection ( body , "OS" ) ;
626628
627629 if ( isEmpty ( summary ) && isEmpty ( repro ) ) {
630+ // Soft-pass substantial non-English / freeform structured reports once
631+ // kind is already bug (label, stored kind, or prior `[Bug]:` detection).
632+ // Requiring the title to keep a `[Bug]:` prefix caused #545: a maintainer
633+ // retitle of an already detailed report was treated as empty Summary/
634+ // Reproduction and auto-closed.
628635 const canSoftPass =
629636 summary === null &&
630637 repro === null &&
631- titleLower . startsWith ( "[bug]:" ) &&
632638 hasSubstantialStructuredContent ( body ) ;
633639 if ( canSoftPass ) {
634640 softPass = true ;
@@ -687,27 +693,39 @@ function validateIssue(issue) {
687693 if ( version !== null && isRawPlaceholder ( version ) === false && isEmpty ( version ) ) emptyCore . push ( "OpenCodex version" ) ;
688694 if ( endpoint !== null && isEmpty ( endpoint ) ) emptyCore . push ( "endpoint or capability" ) ;
689695 if ( emptyCore . length > 0 ) {
690- reasons . push ( `Required sections are missing or empty: ${ emptyCore . join ( ", " ) } .` ) ;
691- guidance . push ( "Describe both the current and expected behaviour." ) ;
696+ // Same soft-pass as bug/feature: label- or maintainer-scoped provider
697+ // reports often use non-English structured headings after a retitle.
698+ const mappedHeadingPresent =
699+ current !== null || expected !== null || repro !== null || response !== null || docs !== null ||
700+ provider !== null || version !== null || endpoint !== null ;
701+ const canSoftPass =
702+ ! mappedHeadingPresent &&
703+ hasSubstantialStructuredContent ( body ) ;
704+ if ( canSoftPass ) {
705+ softPass = true ;
706+ } else {
707+ reasons . push ( `Required sections are missing or empty: ${ emptyCore . join ( ", " ) } .` ) ;
708+ guidance . push ( "Describe both the current and expected behaviour." ) ;
709+ }
692710 }
693711
694- if ( ! isEmpty ( current ) && ! isEmpty ( expected ) && canonicalise ( current ) === canonicalise ( expected ) ) {
712+ if ( ! softPass && ! isEmpty ( current ) && ! isEmpty ( expected ) && canonicalise ( current ) === canonicalise ( expected ) ) {
695713 reasons . push ( "Current and expected behaviour are effectively identical." ) ;
696714 guidance . push ( "Explain the difference between what happens now and what should happen." ) ;
697715 }
698716
699717 const allSections = [ current , expected , repro , response ] . filter ( ( s ) => ! isEmpty ( s ) ) ;
700- if ( allSections . length >= 2 && allRepeatTitle ( allSections , title ) ) {
718+ if ( ! softPass && allSections . length >= 2 && allRepeatTitle ( allSections , title ) ) {
701719 reasons . push ( "All sections merely repeat the issue title." ) ;
702720 guidance . push ( "Add specific detail in each section." ) ;
703721 }
704722
705- if ( isEmpty ( repro ) && isEmpty ( response ) ) {
723+ if ( ! softPass && isEmpty ( repro ) && isEmpty ( response ) ) {
706724 reasons . push ( "Both the request/reproduction and the actual response/error are absent." ) ;
707725 guidance . push ( "Include at least a minimal redacted request or the actual error output." ) ;
708726 }
709727
710- if ( isEmpty ( docs ) ) {
728+ if ( ! softPass && isEmpty ( docs ) ) {
711729 reasons . push ( "Upstream documentation is empty without stating that no public specification exists." ) ;
712730 guidance . push ( "Add a URL to the provider specification, or state that no public spec exists." ) ;
713731 }
0 commit comments