Lot 30 : motifs de relance personnalisés tenus par la façade

La surcharge retry_patterns (tableau JSON de regex du Go, chacune en
(?mi)) ne garde plus la conversation au Go : la façade la lit au
démarrage de sa conversation, comme le Go la compile une fois par
processus ; illisible ou vide, les motifs embarqués s'appliquent, une
regex illisible est sautée.

Les regex restent celles du Go : chat::stream::go_regex réécrit pour le
crate regex ce qui n'a pas le même sens — classes Perl et limites de mot
ASCII (\w \d \s \b et leurs contraires, dans et hors d'une classe),
\Q…\E (refusé dans une classe, comme le Go), [ && -- ~~ dans une classe,
octal permis, référence arrière refusée.

Parité : parity_retry_test.go compile côté Go une soixantaine de regex
(dont celles qu'il refuse) et note leur première correspondance sur onze
textes (accents, chiffres non ASCII, espaces insécables, crochets) ;
tests/retry.rs rejoue le journal à l'identique et vérifie la lecture de
la surcharge au démarrage.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016tcGfJWefW5kUGoC2wL8cv
This commit is contained in:
Claude committed 2026-10-16 01:24:16 +00:00
1 parent 8a61879bfd
commit e444202b4b
11 files changed
+3633 -11

No files matched your search

+3 -5
View File
@@ -48,9 +48,9 @@ func facadeChat() bool {
// facadeChatBlocker : le réglage qui garde la conversation ici, "" s'il n'y
// en a pas. Ces clés opt-in demandent une machinerie restée au Go (bloc projet
// figé, images gardées, slots du moteur, préchauffage, motifs de relance
// personnalisés). NUDGE_IN_TOOL n'en est plus (lot 23) : la façade a sa sonde
// de gabarit.
// figé, images gardées, slots du moteur, préchauffage). NUDGE_IN_TOOL n'en est
// plus (lot 23) : la façade a sa sonde de gabarit ; ni les motifs de relance
// personnalisés (retry_patterns, lot 30) : la façade les lit et les compile.
func facadeChatBlocker(cfg map[string]string) string {
switch {
case projSnapEnabled(cfg):
@@ -63,8 +63,6 @@ func facadeChatBlocker(cfg map[string]string) string {
return "SLOT_PERSIST"
case prewarmMode(cfg) != "":
return "PREWARM"
case strings.TrimSpace(getStr(bkState, "retry_patterns")) != "":
return "retry_patterns"
}
return ""
}
+6
View File
@@ -308,6 +308,12 @@ func TestFacadeReglageQuiGardeLaConversation(t *testing.T) {
if got := facadeChatBlocker(map[string]string{}); got != "" {
t.Errorf("sans réglage : %q", got)
}
// Les motifs de relance personnalisés ne gardent plus la conversation.
putStr(bkState, "retry_patterns", `["oups"]`)
if got := facadeChatBlocker(map[string]string{}); got != "" {
t.Errorf("retry_patterns : %q", got)
}
putStr(bkState, "retry_patterns", "")
// Sans LOKI_CHAT=rust : le Go, et pourquoi.
oublierDecisionFacade(t)
t.Setenv(facadeChatEnv, "")
+96
View File
@@ -0,0 +1,96 @@
package loki
import (
"os"
"regexp"
"testing"
)
// parity_retry_test.go — journal de référence des motifs retry_patterns
// (lot 30 de la migration Rust) : chaque regex, compilée par le Go en
// (?mi) comme compileRetryPatterns, et sa première correspondance sur des
// textes choisis (classes et limites de mot ASCII, texte littéral, classes
// aux caractères spéciaux, Unicode). Rangé dans
// rust/loki-rs/tests/parity_retry/patterns.json ; tests/retry.rs le rejoue.
//
// LOKI_PARITY_UPDATE=1 go test ./internal/loki -run TestPariteRetryPatterns
const parityRetryFile = "../../rust/loki-rs/tests/parity_retry/patterns.json"
func TestPariteRetryPatterns(t *testing.T) {
pats := []string{
`\w+`, `\W+`, `\d+`, `\D+`, `\s+`, `\S+`, `\bdo_\w+\(`, `\Bfoo`, `\bfoo\b`,
`[\w-]+`, `[^\s]+`, `[\W]+`, `[\d\s]+`, `[\D]+`, `[^\w\s]+`,
`\Qa.b\E`, `\Q(x`, `[\Q]-\E]+`, `a\Q\E+`,
`[a[]+`, `[a&&b]+`, `[!--]+`, `[a--]`, `[~~a]+`, `[]a]+`, `[^]a]+`,
`[[:alpha:]]+`, `[^[:alpha:]]+`, `[[:word:]]+`, `[[:^digit:]]+`,
`^oups`, `\Aoups`, `fin$`, `\pL+`, `\p{Greek}+`, `x{2,3}`, `(?s:a.b)`, `a.b`,
`(?U)a+`, `<function=\w+>`, `\{"name":\s*"(bash|read)"`, `[`, `(`, `\C`, `\z`, `a\z`,
`(?P<n>ab)`, `\x41\x{42}`, `\101`, `\1`, `(a)\1`, `\0`, `\08`, `[\101-\103]+`, `[\x41-\x43]+`, `\.\*\+`, `é+`, `ÉTÉ`,
}
inputs := []string{
"été do_it(1) éfoo foo bar_9",
"x٣42 a  \tb\n",
"axb a.b a(x a]-]b",
"x[a[ x&ab a!#-b x~a x]a [ab",
"1abc ab12 Ωμέγα",
"Bonjour\nOUPS !\nla fin",
"oups au début, puis la FIN",
"aaaa xxx a\nb aéb",
`<function=bash> {"name": "read"}`,
"AB ABC éé ÉTÉ été",
"x.*+y",
}
type match struct {
In int `json:"in"`
Match *string `json:"match"`
At int `json:"at"`
}
type entry struct {
Pattern string `json:"pattern"`
Compiles bool `json:"compiles"`
Matches []match `json:"matches"`
}
journal := map[string]any{"inputs": inputs}
var es []entry
for _, p := range pats {
e := entry{Pattern: p, Matches: []match{}}
re, err := regexp.Compile("(?mi)" + p)
if err == nil {
e.Compiles = true
for i, in := range inputs {
m := match{In: i, At: -1}
if loc := re.FindStringIndex(in); loc != nil {
s := in[loc[0]:loc[1]]
m.Match, m.At = &s, loc[0]
}
e.Matches = append(e.Matches, m)
}
}
es = append(es, e)
}
journal["patterns"] = es
got, err := parityJSON(journal)
if err != nil {
t.Fatal(err)
}
if os.Getenv("LOKI_PARITY_UPDATE") == "1" {
if err := os.MkdirAll("../../rust/loki-rs/tests/parity_retry", 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(parityRetryFile, got, 0o644); err != nil {
t.Fatal(err)
}
return
}
want, err := os.ReadFile(parityRetryFile)
if err != nil {
t.Fatalf("journal de référence absent (%v) : LOKI_PARITY_UPDATE=1 pour le créer", err)
}
if string(got) != string(want) {
t.Errorf("les motifs ne produisent plus le journal rangé dans %s.\n"+
"Si c'est voulu : LOKI_PARITY_UPDATE=1 go test ./internal/loki -run TestPariteRetryPatterns,\n"+
"puis cargo test --test retry dit si le port Rust suit.", parityRetryFile)
}
}