fix(ext): correct anti-trigger logic for code/quote blocks + blocker parsing

- trigger-detector: strip code blocks before pattern matching
  (triggers outside code blocks now fire)
- trigger-detector: use code-stripped content for quote and
  reported-speech anti-trigger checks
- state-injector: strip markdown list prefix from blocker text

Tests: 27/27 trigger-detector, 17/17 state-injector passing
This commit is contained in:
张宗平
2026-06-10 20:31:56 +08:00
parent 0dd652948e
commit 0869060284
2 changed files with 21 additions and 24 deletions
@@ -53,7 +53,10 @@ export function readGoalState(cwd: string): GoalState | null {
const passedCriteria = countCheckedBoxes(criteriaSection); const passedCriteria = countCheckedBoxes(criteriaSection);
const blockerSection = extractSection(content, "Blocked"); const blockerSection = extractSection(content, "Blocked");
const blocker = blockerSection.trim() || undefined; const blockerRaw = blockerSection.trim() || undefined;
const blocker = blockerRaw
? blockerRaw.replace(/^[-*]\s*/, "").trim() || undefined
: undefined;
const progressSection = extractSection(content, "Progress Log"); const progressSection = extractSection(content, "Progress Log");
const progressLog = extractIterations(progressSection); const progressLog = extractIterations(progressSection);
@@ -70,28 +70,34 @@ const REPORTED_SPEECH_MARKERS = [
* Returns null if no trigger or if anti-trigger rules match. * Returns null if no trigger or if anti-trigger rules match.
*/ */
export function detectTrigger(input: string): TriggerMatch | null { export function detectTrigger(input: string): TriggerMatch | null {
// Anti-trigger: code block // Anti-trigger: code block — remove code blocks from input before checking
if (/```[\s\S]*?```/.test(input) && containsTriggerInCodeBlock(input)) { // for trigger phrases, so triggers outside code blocks still fire.
return null; const inputWithoutCode = input.replace(/```[\s\S]*?```/g, "");
}
// Anti-trigger: markdown quote block — only block if trigger phrase appears // Anti-trigger: markdown quote block — only block if trigger phrase appears
// exclusively inside quote lines (no trigger in non-quote content) // exclusively inside quote lines (no trigger in non-quote content)
const nonQuoteLines = input.split("\n").filter((line) => !/^\s*>/.test(line)); const nonQuoteLines = inputWithoutCode
.split("\n")
.filter((line) => !/^\s*>/.test(line));
const nonQuoteContent = nonQuoteLines.join("\n"); const nonQuoteContent = nonQuoteLines.join("\n");
const hasTriggerOutsideQuote = TRIGGER_PATTERNS.some((p) => p.test(nonQuoteContent)); const hasTriggerOutsideQuote = TRIGGER_PATTERNS.some((p) =>
p.test(nonQuoteContent)
);
if (!hasTriggerOutsideQuote) { if (!hasTriggerOutsideQuote) {
// All trigger phrases are inside quotes — block // All trigger phrases are inside quotes or absent — block
return null; return null;
} }
// Anti-trigger: reported speech // Use the code-stripped, non-quote content for further checks
const lowerInput = input.toLowerCase(); const effectiveInput = nonQuoteContent;
// Anti-trigger: reported speech — check per-sentence
const lowerInput = effectiveInput.toLowerCase();
const hasReportedSpeech = REPORTED_SPEECH_MARKERS.some((marker) => const hasReportedSpeech = REPORTED_SPEECH_MARKERS.some((marker) =>
lowerInput.includes(marker) lowerInput.includes(marker)
); );
if (hasReportedSpeech) { if (hasReportedSpeech) {
const sentences = input.split(/[.!?。!?]/); const sentences = effectiveInput.split(/[.!?。!?]/);
for (const sentence of sentences) { for (const sentence of sentences) {
const lowerSentence = sentence.toLowerCase(); const lowerSentence = sentence.toLowerCase();
const hasMarker = REPORTED_SPEECH_MARKERS.some((m) => const hasMarker = REPORTED_SPEECH_MARKERS.some((m) =>
@@ -114,7 +120,7 @@ export function detectTrigger(input: string): TriggerMatch | null {
// Try to match trigger pattern // Try to match trigger pattern
for (const pattern of TRIGGER_PATTERNS) { for (const pattern of TRIGGER_PATTERNS) {
const match = input.match(pattern); const match = effectiveInput.match(pattern);
if (match) { if (match) {
const fullText = match[1].trim(); const fullText = match[1].trim();
@@ -155,18 +161,6 @@ export function detectTrigger(input: string): TriggerMatch | null {
return null; return null;
} }
function containsTriggerInCodeBlock(input: string): boolean {
const codeBlocks = input.match(/```[\s\S]*?```/g) || [];
for (const block of codeBlocks) {
for (const pattern of TRIGGER_PATTERNS) {
if (pattern.test(block)) {
return true;
}
}
}
return false;
}
function cleanGoalText(text: string): string { function cleanGoalText(text: string): string {
return text.replace(/[.,;:。;,]+$/, "").trim(); return text.replace(/[.,;:。;,]+$/, "").trim();
} }