The
HUMAN_INPUTmarker 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 Mechanism of Self-Contradiction in G1 (HUMAN_INPUT) and G2 (Dead Link)
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_INPUTmarkers 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:
- Preprocess the text to remove Markdown code contexts (fenced blocks / inline code) before searching.
- 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.
| Rule | Content | Reason |
|---|---|---|
| G3 | Tag SSOT consistency | The tags field in frontmatter rarely has explanations in code context. |
| G5 | Reviewer presence check | Structured fields in frontmatter don’t enter code context. |
| G7 | scheduledAt mandatory | Checking for the presence of a frontmatter field won’t be confused with prose. |
| G9 | authorSlug presence | Frontmatter 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.
| Rule | Content | Analysis Target |
|---|---|---|
| G1 | Remaining HUMAN_INPUT | Body (after stripping, limited to 3 formats) |
| G2 | Dead 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.
| Rule | Content | Analysis Method |
|---|---|---|
| G10 | Brand rule check for self-reference | 3 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
| Level | Application | Method |
|---|---|---|
| L1 | Frontmatter field inspection | Plain match |
| L2 | Placeholder detection in body | stripCodeContexts + format-limited regex |
| L3 | Context-dependent checks in body | allow-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.