Engineering content-gen 18 min read

Advancing Pre-publish Gates: From Plain Grep to Proximity Analysis

Fix false positives in quality gates using stripCodeContexts and proximity analysis to resolve self-contradictions.

Published 2026-05-23 森本拓見

The HUMAN_INPUT marker mentioned in this article is a placeholder left by the AI writing skill to indicate where a human should later fill in a specific value (e.g., <!-- HUMAN_INPUT: Fill in numerical value -->).

When we tried to write an article titled “Why We Withdrew from AI-Generated Mass Blogging in 5 Days,” we were stopped by our own gate. The moment we wrote “7 HUMAN_INPUT markers remained” in the body, G1 (remaining HUMAN_INPUT check) reported 10 fails. This was a self-contradiction: a gate written with plain string matching blocked an article that was trying to disclose failures, because it couldn’t distinguish between an actual unfinished marker and prose explaining a failure case.

→ For the overall structure of the gate, please refer to Structural Design of the Owned Media Operation Engine mcluhan. This article focuses specifically on the limitations of plain matching in G1/G2 and the transition to context-aware analysis that respects Markdown structure.


Why False Positives Occur in Failure Disclosure Articles

The core concept of Yakumo’s owned media is “disclosing failures.” Writing about design failures in the gate or the process of improving the pipeline is the very narrative of our pillars.

However, failure disclosure articles structurally contain “strings explaining traces of failure.”

  • “7 HUMAN_INPUT markers remained in the body.”
  • “All 76 internal links referred to paths like /blog/....”

These are not the HUMAN_INPUT markers or dead links themselves, but prose talking about them. Plain string matching cannot make this distinction. body.includes('HUMAN_INPUT') returns true even if the marker is just mentioned in prose.

In the draft of the pillar article “Why We Withdrew from AI-Generated Mass Blogging in 5 Days,” G1 detected 10 false positives and G2 detected 6. In all cases, these were descriptions of failure cases getting caught by plain matches.

Difference Between Strings “Explaining” Failure in Prose and “Actual Markers”

What should be detected are “markers left as unfinished placeholders,” not “text talking about markers.” This distinction cannot be expressed with plain string matching.

There are two correct approaches:

  1. Preprocess the text to remove Markdown code contexts (fenced blocks / inline code) before searching.
  2. Limit the detection target to specific structures (e.g., HTML comment format, square bracket tag format).

By combining these two, we can distinguish between “HUMAN_INPUT as an explanation in prose” and “HUMAN_INPUT as an unfinished placeholder.”


Preprocessing that Respects Markdown Structure

Designing the stripCodeContexts Helper: Removing Fenced Code Blocks and Inline Code

The preprocessing helper stripCodeContexts removes fenced code blocks and inline code.

function stripCodeContexts(body: string): string {
  // Remove fenced code blocks (```...```)
  let result = body.replace(/```[\s\S]*?```/g, '');
  // Remove inline code (`...`)
  result = result.replace(/`[^`\n]+`/g, '');
  return result;
}

Only prose remains in the body after this processing. For an explanatory sentence like “the HUMAN_INPUT marker…”, the inline code portion enclosed in backticks is removed, turning it into “the marker…”. A plain string match searching for “HUMAN_INPUT” will no longer hit.

This fulfills the purpose of excluding prose-level mentions of HUMAN_INPUT in articles explaining failure cases.

Design Flow: Performing Regex Matching on the Stripped Body

// Refactored flow for G1 check
function checkG1(article: Article): GateResult {
  const strippedBody = stripCodeContexts(article.body);

  const HUMAN_INPUT_PATTERNS = [
    /<!--\s*HUMAN_INPUT[\s\S]*?-->/g,      // HTML comment format
    /HUMAN_INPUT\[[A-Z_]+\]/g,             // Square bracket tag format
    /^HUMAN_INPUT:/m,                      // Line start colon format
  ];

  const matches = HUMAN_INPUT_PATTERNS.flatMap(pattern =>
    [...strippedBody.matchAll(pattern)]
  );

  return matches.length > 0
    ? { result: 'fail', rule: 'G1', count: matches.length }
    : { result: 'pass', rule: 'G1' };
}

After stripping, detection is limited to three formats within the prose. Mentions outside these formats (like the prose “the HUMAN_INPUT marker is…”) are excluded from detection.


Analysis Level Classification by Rule

Level 1: Rules Sufficient with Plain String Matching (Parts of G3–G9)

Not all rules require context-aware implementation. Plain string matching is often appropriate.

RuleContentReason
G3Tag SSOT consistencyThe tags field in frontmatter rarely has explanations in code context.
G5Reviewer presence checkStructured fields in frontmatter don’t enter code context.
G7scheduledAt mandatoryChecking for the presence of a frontmatter field won’t be confused with prose.
G9authorSlug presenceFrontmatter fields are clearly structured.

Inspecting frontmatter fields with plain matches is safe. Problems occur in rules that scan the article body.

Level 2: Regex Matching After Code Context Removal (G1 / G2)

stripCodeContexts() → Regex match for specific formats only

Level 2 is appropriate for rules that detect specific “placeholders” or “paths” within the body. Stripping is necessary to avoid false positives for explanatory text in code blocks or inline code, but NLP-level analysis is not required.

RuleContentAnalysis Target
G1Remaining HUMAN_INPUTBody (after stripping, limited to 3 formats)
G2Dead links (/blog/ path)Body (after stripping, limited to Markdown link format)

Level 3: Rules Requiring Proximity Analysis (G10 Self-Reference Check)

G10 represents the top level of context-aware analysis. Determining whether words like firm or company refer to Yakumo itself requires a three-layer approach: allow-list + pattern + proximity.

Level 2 (stripping) alone is insufficient; it’s necessary to determine “who” the matched part refers to.

RuleContentAnalysis Method
G10Brand rule check for self-reference3 layers: allow-list → externalReferencePatterns → proximity analysis

Currently, scripts/blog-gate.ts implements a total of 19 rules: 13 mandatory G rules (G1–G13) and 6 W rules (W1, W2, W3, W4, W5, W7).


Implementation Example: Refactoring G1

Before: Scanning the Entire Body with body.includes

// Before refactor
function checkG1_before(body: string): boolean {
  return body.includes('HUMAN_INPUT');
}

This single line is the root of all problems. It returns true if the string HUMAN_INPUT exists anywhere in the body, without distinguishing whether it’s in a code block, inline code, or a prose explanation.

After: stripCodeContexts + Format-Limited Regex

// After refactor
function checkG1_after(body: string): GateResult {
  const stripped = stripCodeContexts(body);

  const remaining = [
    ...stripped.matchAll(/<!--\s*HUMAN_INPUT[\s\S]*?-->/g),
    ...stripped.matchAll(/HUMAN_INPUT\[[A-Z_]+\]/g),
    ...stripped.matchAll(/^HUMAN_INPUT:/mg),
  ];

  if (remaining.length > 0) {
    return {
      result: 'fail',
      rule: 'G1',
      message: `${remaining.length} HUMAN_INPUT markers remaining`,
    };
  }
  return { result: 'pass', rule: 'G1' };
}

By targeting only three formats after stripping, “prose talking about markers” is excluded, and only “markers remaining as unfinished placeholders” will trigger a fail.

The context-awareness of G1 was completed in commit 5f4289c fix(scripts/blog-gate): make HUMAN_INPUT and /blog/ detection context-aware. This commit was the starting point for the implementation that limits detection to three formats after stripping.


Test Design for Automatic False Positive Detection

Adding Meta-content Fixtures (Articles Explaining Failure Cases) to Gate Tests

The fundamental solution is to add meta-content fixtures to gate tests. Meta-content refers to articles explaining gate rules or failure cases within the body.

// tests/blog-gate.test.ts
describe('G1 - Remaining HUMAN_INPUT check', () => {
  it('fails on actual markers', () => {
    const body = '<!-- HUMAN_INPUT: Please fill in a value -->';
    expect(checkG1(body).result).toBe('fail');
  });

  it('passes on explanations in prose (meta-content support)', () => {
    const body = 'There were 7 `HUMAN_INPUT` markers remaining in the body.';
    expect(checkG1(body).result).toBe('pass');
  });

  it('passes on explanations within code blocks', () => {
    const body = '```\n<!-- HUMAN_INPUT: ... -->\n```';
    expect(checkG1(body).result).toBe('pass');
  });
});

Incorporating the structure of “making an article out of the gate’s own failure” into tests in advance guarantees that the gate won’t cause self-contradiction when next writing meta-content.

However, a dedicated test file like tests/blog-gate.test.ts has not yet been created. The meta-content fixture design shown in this article is recorded as a policy, but actual testing remains a task for the future.


Summary: Principles to Take Home

The core of designing a Markdown-aware pre-publish gate can be summarized into two points:

1. Assign appropriate analysis levels to rules

LevelApplicationMethod
L1Frontmatter field inspectionPlain match
L2Placeholder detection in bodystripCodeContexts + format-limited regex
L3Context-dependent checks in bodyallow-list + pattern + proximity

2. Add meta-content tests to the gate test suite

The self-contradiction where failure disclosure articles were rejected by the gate was born from the lack of tests for “whether the gate works correctly with meta-content fixtures.” Adding this fixture to the tests will prevent the same bottleneck during the next writing of a similar pillar.

For gate design philosophy and the trade-off between false positives and false negatives, refer to Pre-publish Gate Design: Balancing False Positives and False Negatives. The design for G10 brand rule self-reference detection is handled in Automating Brand Rule Machine Checks: Context Discrimination Patterns for Self-Reference vs. External Reference.