Markdown のインラインコード(コードスパン)の中に、`a (`b`) c` のように1重バッククォート付きの語を入れ子で書いてしまう事故があります。CommonMark の左から右への逐次解析と同長バッククォート列の対規則(§6 / §6.1)1 に当てはめると、バッククォートの総数は4個(偶数)のまま `a (` と `) c` という2つの合法なコードスパンに割れ、真ん中の b が裸のテキストとして露出します2。構文エラーにはならず、コードスパン内部の空白スタイルを検査する markdownlint の MD038(Spaces inside code span elements)3 の対象範囲からも外れるため、既存のテストをすり抜けてプロンプト雛形やドキュメントの表示を壊してしまいます。

CommonMark におけるコードスパン(Code spans)とは、前後にバッククォートが隣接しない1個以上の連続したバッククォート列(backtick string)を開始デリミタとし、それより後ろで最初に現れる同じ長さのバッククォート列を終了デリミタとして対にするインライン要素です1(なお、仕様の節番号は現行の CommonMark Spec 0.31.2 では §6.1 Code spans であり1、過去の CommonMark Spec 0.29 では §6.3 Code spans として掲載されていました4)。

AI エージェントや執筆者に対して「インラインコードの中にバッククォートを書くな」と否定形の指示(予防)を積み重ねるとコンテキスト汚染を招きます(否定指示の積層が招く Constraint Tax を検証した過去記事でも、抑制的な禁止制約を積み増す弊害を扱いました)。そのため、プロンプトでの予防ではなく出力結果を機械的に検査する Linter(検収)側に注力する方針を採りました。

ところが、AI エージェントのスキル定義を管理する個人リポジトリ(非公開、以下プロジェクトA)で 73 行の Python 製コードスパン Linter を書いたところ、当初のスタック近似が CommonMark の仕様と衝突して4つの境界ケースが浮上し、さらに異長バッククォート列の交差を許す例外処理が本物の閉じ忘れ(use `xyz` andoops “)を素通しさせるバグを踏みました2。本記事では、4つのコミットを通じて「スパン内側で奇数回現れる同長列だけを交差例外にする」実装へ到達した記録と、どこまでをヒューリスティックな Linter で追い、どこからを既知の制限としてテストで固定するかの設計判断を共有します2。

なぜ偶数個のバッククォートでコードスパンが割れ、素朴な検査をすり抜けるのか

実装のきっかけは、プロジェクトAのスキル定義ファイル(SKILL.md)の手順9に書かれた長いコマンド雛形の中に、(`ticket:` が GitHub 形式でないときは…) とバッククォート付きの語を入れたことで、外側のコードスパンが ( の直後で勝手に閉じ、雛形が途中で割れていた事故(テスト検体 BROKEN_STEP9)でした2。

CommonMark Spec 0.31.2 §6.1 では、対になる同じ長さのバッククォート列が見つからない場合、そのバッククォート列は単なるリテラル文字として扱われ、構文エラーという概念自体が存在しません1。さらに同節の規則を `a (`b`) c` のように入れ子で4個のバッククォートがある行に適用すると、1個目と2個目、3個目と4個目がそれぞれ長さ1の対を作るため、「開いたまま残るバッククォートがあるか」を見るだけでは素通りしてしまいます2。

そこで初版(2026年10月22日 21:06、73行・6テスト追加)では、行単位の検査(プロジェクトAの SKILL.md は1行1項目で書く規約のため、行をまたぐスパンは崩れ側に倒す設計)として、以下の2つの条件のいずれかに当てはまる行を「崩れ」として検出するヒューリスティックを採用しました2。

  1. 行内で同じ長さの相手が見つからず、開き待ちリスト(pending)に残ったバッククォート列がある行(閉じ忘れの検出)。
  2. スパンが閉じた直後の文字に英数字またはアンダースコア(WORD = re.compile(r"[A-Za-z0-9_]"))が隣接している行(`a (`b`) c` で最初のスパン `a (` が閉じた直後に b が続く形を拾う検出)。

以下は、初版の lint_codespans.py において、スタック(pending)で同じ長さの列を対にしつつ、閉じた直後の英数字で途中割れを検出していた broken(line) 関数の抜粋です。

引用元: プロジェクトA(非公開)の lint_codespans.py

RUN = re.compile(r"`+")
WORD = re.compile(r"[A-Za-z0-9_]")


def broken(line):
    """崩れている行か。対にならない列が残る、または閉じたスパンの直後に英数字が続く。

    後者は入れ子で途中で閉じた形(`a (`b`) c` は `a (` と `) c` の 2 スパンに割れ、
    最初のスパンの閉じバッククォートの直後に b が続く)。バッククォートの数は偶数のままなので、対にするだけでは見つからない。
    """
    pending = []  # 開き待ちの列の長さ
    for m in RUN.finditer(line):
        n = len(m.group())
        if n in pending:
            # 直近の同じ長さの列で閉じる。間にあった開き待ちはリテラルのバッククォートだった
            del pending[len(pending) - 1 - pending[::-1].index(n):]
            if WORD.match(line, m.end()):
                return True
        else:
            pending.append(n)
    return bool(pending)

pending リストに開き待ちのバッククォート長を積み、同じ長さ n が現れたら del pending[len(pending) - 1 - pending[::-1].index(n):] で直近の n 以降を切り捨て、さらに WORD.match(line, m.end()) で閉じ直後の英数字を検査しています2。

この初版コミットから4分24秒後の第2コミット(同日 21:11)で、モジュール docstring の説明を「CommonMark と同じく」から「CommonMark の code span 規則の近似として」に訂正しました2。CommonMark 本来は未対応列をリテラル扱いすること1、また閉じた直後の英数字判定は英語の複数形表記(例: `task`s)のような正しい記述でも誤検知しうることを「既知の制限」として明記しています2。そして、上記の「直近の同じ長さで閉じ、間にあった別長を巻き込んで消す」というスタック操作は、異長バッククォート列の交差を許す CommonMark の仕様と挙動が食い違っていました2。

スタック近似と CommonMark §6.1 の衝突 ── 4つの境界形と前方走査への転換

初版から6日後(2026年10月28日 15:49)の第3コミットで、初版 Linter の境界ケース4形を検証し、対の取り方とコードフェンスの閉じ判定を変更しました2。

スタック方式と前方走査の決定的な違い

初版のスタック方式は「閉じるときに間にある未対応の異長列をリテラルとして破棄する」だけでした。そのため、`abc`` `` のように「長さ1のスパン ``a b` の中に長さ2の が呑まれ、スパンが閉じた後ろにもう1つ が残る形(異長列の交差)」が来ると、内側の が破棄された後に末尾の が相手を見つけられず、pending に残って崩れと判定されてしまいます2。

CommonMark Spec 0.31.2 §6.1(および §6 の Example 327、§6.1 の Examples 347–349)では、インライン要素は左から右へ逐次解析され、開始バッククォート列は「それより後ろで最初に現れる同じ長さの列」と対になり、そのスパン内部にある異なる長さのバッククォート列は単なるリテラル文字として消費されます1。さらにスパンの外側に残った も、対応相手がなければリテラル文字になるため、この規則を当てはめると a ``b c は CommonMark 上、前半の `ab` がコードスパン、後半の c が通常テキストとなる合法な記述です。

ただし、Linter としては単なる閉じ忘れを検出したいため、未対応列をすべて無条件にリテラルとして許すわけにはいきません。そこで第3コミットでは、走査を CommonMark §6.1 どおりの「前方走査(next((k for k in range(i + 1, len(runs)) if runs[k][1] - runs[k][0] == n), None))」に改めたうえで、「直前のスパン内部に呑まれた列(inside)と同じ長さの未対応列だけは、異長列の交差として例外的に許す」という設計を採りました2。

コードフェンス長判定の修正(CommonMark §4.5)

初版はコードフェンスの開始文字(` か ~)だけを見て開閉していたため、4連バッククォート( md )のフェンス内部に3連バッククォート( ``` ````)を書くと、そこで外側のフェンスが閉じたと誤認されていました2。

CommonMark Spec 0.31.2 §4.5(Fenced code blocks、Example 124)では、コードフェンスは3個以上の連続したバッククォートまたはチルダで開始し、閉じフェンスは「開始と同じ文字かつ開始と同数以上の長さ」が必要であり、フェンス内部はインラインとして解析されません1。第3コミットではこの仕様に沿って、fence = (文字, 開きの長さ) のタプルを保持し、「開きと同じ文字で開き以上の長さ、かつ info string がない行」でのみフェンスを閉じるよう修正しました2。

解消せず「既知の制限」としてテストで固定した2つの境界ケース

残る2つの境界ケースについては、コードを修正するのではなく「既知の制限」としてテストコードで挙動を固定する判断を下しました2。

  • 割れた直後が日本語や記号の形(`a `あ` c`)の見逃し: 最初のスパン `a ` が閉じた直後の文字が あ であるため、WORD([A-Za-z0-9_])にマッチせず見逃します。しかし日本語の直後まで禁止すると、`task`です のような通常の日本語文がすべてエラーになって区別できないため、見逃す仕様として固定しました2。
  • 閉じた直後が英数字の正当表記(`task`s)の誤検知: これを許すと、本来捕まえたい `a (`b`) c` の途中割れそのものが検出できなくなるため、崩れとして報告される仕様をそのままテストで固定しました2。

交差例外が本物の閉じ忘れを素通しさせた罠と「奇数回出現の列」への限定

第3コミットで導入した inside リスト方式の交差例外には、思わぬ落とし穴が潜んでいました。第3コミットでは、スパン i から j の間にあったすべてのバッククォート列の長さを inside リストに詰め込み、後続の未対応列(unpaired)が inside に含まれていれば inside.remove(n) で1つずつ免除していました2。

ここで、use `xyz` andoops “ という行を考えます。

最初のスパン `xyz` は長さ1のバッククォートで始まり、長さ1で閉じています。その内側に書かれた2つの ( y )は、長さ1のスパン内部で完結しており(書き手の意図としても内側で対になって閉じたペアであり)、スパンの外側と交差してなどいません[^2]。ところが `inside` リスト方式では、内側にある2つの がそのまま [2, 2] として inside に蓄えられてしまいます。その結果、行の後半にある本物の閉じ忘れ oops “(未対応の長さ2)が、inside の要素を1つ消費して「交差の片割れ」として免除され、エラーにならず素通りしてしまいました2。

第3コミットからわずか15分27秒後の第4コミット(同日 16:04)で、このバグを修正しました2。スパンの内側で偶数回現れた同じ長さの列は、内側で対になっており外へはみ出していません。スパンの境界をまたいで交差しているのは、 スパンの内側で同じ長さの相手が見つからなかった列、すなわち内側で奇数回現れる列 だけです2。

素朴なスタック近似から前方走査、そして奇数回出現の交差限定へと至る3段階の実装方式と、代表的な入力パターンに対する判定結果の違いは以下の通りです。

入力パターンと意図初版: スタック方式 (pending)第3コミット: 前方走査+全内包列免除 (inside)第4コミット: 前方走査+奇数回交差のみ免除 (crossing)
`a (`b`) c`(入れ子による途中割れ:異常)⭕ 検出(閉じ直後の b で検知)⭕ 検出(閉じ直後の b で検知)⭕ 検出(閉じ直後の b で検知)
`ab` c (異長列の交差:CommonMark 上は正常)❌ 誤検知(末尾の “ が pending に残る)⭕ 通過(内側の “ が末尾を免除)⭕ 通過(内側で “ が1回=奇数回のため免除)
use `xyz` andoops “(内側ペア後の閉じ忘れ:異常)⭕ 検出(末尾の “ が pending に残る)❌ 見逃し(内側の [2, 2] が末尾の閉じ忘れを免除)⭕ 検出(内側の “ は2回=偶数回のため免除枠に入らない)
`a `あ` c`(直後が日本語の割れ)/ `task`s(直後が英数字)`task`s 誤検知のみ4分後に docstring で補足(日本語直後の制限は未記載)⭕ 両方とも既知の制限としてテストコードで明示固定⭕ 両方とも既知の制限としてテストコードで明示固定

この比較が示すように、異長列の交差(`abc`` ``)を救済しようとして内側の列を無条件に免除すると、今度はスタック方式なら捕まえられていたはずの閉じ忘れ(`` usex y z and ``oops ``)まで見逃す退行が生じます。スパン内側での出現回数の偶奇(inner.count(n2) % 2)を見ることで、内側で完結したペアの後ろにある閉じ忘れ3例(`` use x y zand ``oops ``、`` ``x`` andoops 、 a `b` c then unclosed ``)を見逃さずに検出し、閉じた交差例(`` x y` “)を通せるようになりました2。

以下は、第4コミットで完成した前方走査+奇数回交差限定(crossing)の broken(line) 関数の抜粋です。

引用元: プロジェクトA(非公開)の lint_codespans.py

def broken(line):
    """崩れている行か。対にならない列が残る、または閉じたスパンの直後に英数字が続く。

    後者は入れ子で途中で閉じた形(`a (`b`) c` は `a (` と `) c` の 2 スパンに割れ、
    最初のスパンの閉じバッククォートの直後に b が続く)。バッククォートの数は偶数のままなので、対にするだけでは見つからない。
    対にならない列は崩れだが、スパンの内側で相手の見つからなかった同じ長さの列(交差。`a ``b` c``)があるときだけ許す。
    """
    runs = [(m.start(), m.end()) for m in RUN.finditer(line)]
    crossing = []  # スパンの内側で同じ長さの相手が見つからなかった(交差した)列の長さ
    unpaired = []
    i = 0
    while i < len(runs):
        n = runs[i][1] - runs[i][0]
        # CommonMark §6.1: 開きの相手は、その列より後に最初に現れる同じ長さの列
        j = next((k for k in range(i + 1, len(runs)) if runs[k][1] - runs[k][0] == n), None)
        if j is None:
            unpaired.append(n)
            i += 1
            continue
        inner = [runs[k][1] - runs[k][0] for k in range(i + 1, j)]
        crossing += [n2 for n2 in set(inner) if inner.count(n2) % 2]
        if WORD.match(line, runs[j][1]):
            return True
        i = j + 1
    # 対にならない列は CommonMark ではリテラルだが、閉じ忘れと区別できないので崩れとみなす。
    # ただしスパンの内側で相手の見つからなかった同じ長さの列があれば、長さの違う列の交差(`a ``b` c``)として許す。
    # 内側で対になった列(`x ``y`` z` の ``)は交差ではないので、後ろの閉じ忘れを許さない
    for n in unpaired:
        if n in crossing:
            crossing.remove(n)
        else:
            return True
    return False

inner = [runs[k][1] - runs[k][0] for k in range(i + 1, j)] でスパン内部に呑まれた列の長さを集め、crossing += [n2 for n2 in set(inner) if inner.count(n2) % 2] によって奇数回出現した長さだけを crossing に追加しています2。これにより、内側で対になった偶数個の列が後続の未対応列を誤って免除する経路が塞がれました。

続いて、既知の制限と交差・閉じ忘れの境界ケースを固定した test_lint_codespans.py のテストコード抜粋を示します。

引用元: プロジェクトA(非公開)の test_lint_codespans.py

    def test_split_span_followed_by_japanese_is_missed(self):  # 既知の制限 1(#165)
        self.assertEqual(lint.lint_text("`a `あ` c`\n", "a.md"), [])

    def test_word_after_closing_backtick_is_reported(self):  # 既知の制限 2(#165)
        self.assertEqual(lint.lint_text("`task`s\n", "a.md"), ["a.md:1"])

    def test_crossing_runs_of_different_length_pass(self):  # #165-3
        self.assertEqual(lint.lint_text("`a ``b` c``\n", "a.md"), [])
        self.assertEqual(lint.lint_text("`a ``b` c\n", "a.md"), [])

    def test_unclosed_after_nested_pair_is_reported(self):  # 交差の例外は内側で対になった列に効かない
        for line in ("use `x ``y`` z` and ``oops", "`` `x` `` and `oops", "``a `b` c`` then `unclosed"):
            self.assertEqual(lint.lint_text(line + "\n", "a.md"), ["a.md:1"], line)

    def test_crossing_with_closed_pair_passes(self):
        self.assertEqual(lint.lint_text("`x ``y` ``\n", "a.md"), [])

通すべき正常系・検出すべき異常系だけでなく、「見逃す形(test_split_span_followed_by_japanese_is_missed)」や「崩れと出る形(test_word_after_closing_backtick_is_reported)」もアサーションとしてテストスイート(現行チェックアウトで全13テスト通過)に組み込むことで、Linter の境界仕様をコード上で実行可能に文書化しています2。

ヒューリスティック検収の限界と「なぜ完全なパーサーでも解けないのか」

ここまで読むと、「正規表現やインデックス走査で自作 Linter を書くのではなく、最初から CommonMark 準拠のパーサー(AST)を使えば済むのではないか?」あるいは「二値判定(exit 0 / 1)ではなく、怪しさのスコアによる順位づけにすべきではないか?」という疑問が浮かぶかもしれません。

CommonMark 準拠の構文解析と「意図した雛形の崩れ」のギャップ

確かに、コードフェンスの開閉条件(§4.5)やインラインの前方走査(§6.1)そのものは、CommonMark 準拠のパーサーを使えば仕様通りにトークン化できます1。しかし、CommonMark の仕様(§6.1)では、未対応のバッククォート列は単なるリテラル文字として扱われ、また同じ長さのバッククォート列のペアはそのままコードスパンとして成立します1。

そのため、今回の本丸である `a (`b`) c` のような入れ子割れや、`a b のような閉じ忘れは、CommonMark 仕様上それぞれ「2つの合法な code span とその間の通常テキスト」および「リテラルのバッククォートと通常テキスト」として 構文エラーなしに正常パースされます1。つまり、構文解析(パース)と「その並びが人間の意図した雛形の崩れかどうか」の異常判定は別問題であり、仮に AST を構築したとしても、隣接するノードに識別子が張り付いていないか・リテラルとして残ったバッククォートが閉じ忘れではないかを判定する段階では、結局独自のヒューリスティックな検収ルールが必要になります。

もちろん、今回作成した lint_codespans.py にも明確な適用限界(トレードオフ)があります。

  • 1行1項目の記述規約への依存: プロジェクトAの SKILL.md が1行1項目で書かれることを前提に、複数行にまたがるコードスパンをすべて崩れ側に倒しています2。複数行のコードスパンを多用する一般的な文書にはそのまま適用できません。
  • 文字種判定のトレードオフ: 割れた直後が日本語や記号になるケース(`a `あ` c`)は見逃し、逆に英文中で `task`s と書く正当な表現は崩れとして報告されます2。そのため、`task`s のような記述が頻出する英文主体のドキュメントに適用すると誤検知が生じます。

二値判定か、順位づけ(段階評価)か

また、以前短文タスクの重複検出を IDF の順位づけに転換した記事では、閾値による二値判定をやめてランキングを出す設計が有効でした。一般論として、二値判定させると見逃しは多くなりそうなので、順位にできるならその方が良い気はします。

しかし、CI の品質ゲートとして「最終的にその記述を採用するか・修正を求めるか」を決める場面では、たとえ四段階評価にしたとしても「下から2つ目までは採用(またはエラー)」と線を引けば、最初からその条件込みで二値判定しているのと結局変わりません。可逆なドキュメント検査を CI で自動実行する以上、「引っかかったときだけ exit 1 で止める」二値判定の枠組みを保ちつつ、誤検知と未検知の境界線をテストで明示する方が実務上扱いやすいと判断しました。

検収を段階的に厚くする運用 ──「学習」ではなく「判定のための辞書」を増やす

今回の4コミットの修正経過は、AI エージェント運用における品質ゲートの育て方そのものを映し出しています。

プロンプトに「バッククォートを入れ子にするな」「閉じ忘れるな」と否定形の予防ルールを書き足すのはコンテキスト汚染を招くため検収側に注力すべきですが、初期の検収は知見不足で穴だらけになる前提に立ち、振り返りを通じて検収チェックシートを段階的に厚くしていく運用が必要です。実際、初版(73行・6テスト)のコミットから4分24秒後には近似の制限を docstring に追記し、6日後の第3コミットでは境界形を検証して前方走査とフェンス長判定へ改め、さらにその15分27秒後の第4コミットでは inside リストが閉じ忘れを通す不具合を crossing(奇数回出現のみ)へ絞って計13件のテストに拡充しました2。

ここで重要なのは、こうした改修で Linter を補強する仕組みは本質的な「学習」とは違うという点です。今回の変化は学習というより、 判定のための辞書(既知の境界パターンのカタログ)が増えた と捉えています。

辞書である以上、まだテストに登録されていない未知の境界パターンは保証範囲外になりますし、逆に条件をむやみに増やせば保守コストが上がります。上流設計文書の品質ゲートを検査被覆表で整理した記事でも検査対象と検査範囲の境界を明示したように、Linter がどのパターンを通し、何を既知の制限として割り切るかをテストコード(test_lint_codespans.py の13件)として固定しておくことが、判定の辞書を安全に育てる基盤になります2。

よくある質問(FAQ)

Q. Markdown のインラインコードでバッククォートが入れ子になり表示が崩れる事故は、既存の markdownlint(MD038 等)で検出できますか?

A. MD038(Spaces inside code span elements)単体では検出できません。MD038 はコードスパンの開始・終了バッククォート直近にある不要な空白スタイルを検査するルールであり3、`a (`b`) c` のようにバッククォートの総数が偶数のまま2つの合法なコードスパン(`a (` と `) c`)に割れる事象は空白異常に該当しないためです。このような途中割れを CI で機械的に拾うには、閉じたスパンの直後に意図しない識別子(英数字等)が隣接していないかを検査する専用のルールが必要になります2。

Q. CommonMark のコードスパン解析と、単純なスタックによる括弧対応判定は何が違うのですか?

A. スタック方式は「内側のペアから先に閉じ、間にある未対応の異長列を破棄する」ように動くのに対し、CommonMark(0.31.2 §6.1)は「開始バッククォート列より後ろで最初に現れる同じ長さのバッククォート列」を前方走査で探して対にします1。この規則を `ab` c のように長さ1のスパンの中に長さ2の列が1つだけ含まれる記述に当てはめると、長さ1のスパンが先に閉じて内側の をリテラルとして呑み込み、末尾に残った も対応相手がないためリテラルとなります。Linter で内側ペア後の閉じ忘れを検出しつつこの異長列交差を通すため、前方走査で対を取り、かつスパン内側で奇数回現れた同長列だけを交差例外とする方式を採りました2。

Q. なぜ直後に英数字が続く表記(`task`s)の誤検知や、日本語直後の割れ(`a `あ` c`)の見逃しを解消しなかったのですか?

A. これらを解消しようとすると、Linter 本来の検出目的と正面から衝突するためです。`task`s を通すために「閉じバッククォート直後の英数字」を許容してしまうと、最も防ぎたい `a (`b`) c`(最初のスパンが閉じた直後に b が続く形)まで素通りしてしまいます2。逆に `a `あ` c` を拾うために閉じ直後の日本語まで禁止すると、`task`です のような通常の日本語文がすべてエラーになってしまいます2。そのため、これらはバグではなくヒューリスティックの「既知の制限」として割り切り、テストコードで挙動を固定する設計を選びました2。

まとめ

  • 偶数個のバッククォートによる途中割れは「閉じ直後の文字種」で捕まえる: `a (`b`) c` のような入れ子割れは CommonMark の規則上2つの合法なスパンになるため、未対応列の検査だけでは見つかりません。閉じたスパンの直後に英数字([A-Za-z0-9_])が続く形をヒューリスティックとして併用します12。
  • コードスパンの対判定はスタックではなく CommonMark §6.1 の前方走査に合わせる: 開始列の相手は「後ろで最初に現れる同じ長さの列」とし、コードフェンスも「開始と同じ文字かつ開始以上の長さ(§4.5)」で閉じることで、仕様との乖離による誤判定を防ぎます12。
  • 異長列の交差例外は「スパン内側で奇数回現れた列」だけに絞る: スパン内部の列を無条件に免除リスト(inside)へ入れると、内側で完結したペア(`xyz`)が後続の本物の閉じ忘れ( oops “)まで免除してしまうため、内側で相手が見つからなかった奇数回出現の列(crossing)だけを交差例外として許します2。
  • 解消すると目的が消える境界ケースは「既知の制限」としてテストに固定する: `task`s の誤検知や `a `あ` c`(直後が日本語)の見逃しのように、原理的にトレードオフとなる境界は無理にこじ開けず、テストコードで仕様として明示固定します2。
  • 検収は「学習」ではなく「判定のための辞書」として振り返りで育てる: 否定指示による予防でコンテキストを汚す代わりに検収を置きますが、初期の検収は穴がある前提に立ち、振り返りを通じて境界パターンをテストと判定辞書へ段階的に追加していきます。

AI エージェントのスキル定義や技術ドキュメントの品質ゲート設計、プロンプトの肥大化を防ぐ決定論的 Linter の構築、および振り返りを通じた CI 検収プロセスの改善について、ぜひ コンタクトフォーム よりお気軽にご相談ください。

Footnotes

  1. CommonMark Spec Version 0.31.2 John MacFarlane、CommonMark 公式仕様(Version 0.31.2、2026年11月6日参照)。インラインの左から右への逐次解析と最初の同長列での対形成は §6 Inlines(Example 327)、backtick string の定義・同じ長さの列によるコードスパンの開始と終了・未対応列のリテラル扱い・異長列を含む挙動は §6.1 Code spans(Examples 328–349)、コードフェンスの開始条件(3個以上の連続した ` または ~)と終了条件(開始と同じ文字かつ同数以上の長さ、info string なし)およびフェンス内のインライン非解析は §4.5 Fenced code blocks(Example 124)。 ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9 ↩10 ↩11 ↩12 ↩13

  2. プロジェクトA(非公開)の検証・コミット記録。筆者のローカル Git リポジトリにおける4コミットの差分(2026年10月22日〜10月28日)および2026年11月6日実施のテスト検算記録。初版の73行・6テスト追加と SKILL.md 手順9の崩れ検体 BROKEN_STEP9、4分24秒後の CommonMark 近似と既知の制限の docstring 明記、6日後の前方走査・フェンス長判定への変更と境界ケースのテスト固定、15分27秒後の inside から奇数回出現の crossing への修正と閉じ忘れテスト追加(計13件)、および現行チェックアウトでの全13テスト通過確認の記録。 ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9 ↩10 ↩11 ↩12 ↩13 ↩14 ↩15 ↩16 ↩17 ↩18 ↩19 ↩20 ↩21 ↩22 ↩23 ↩24 ↩25 ↩26 ↩27 ↩28 ↩29 ↩30 ↩31 ↩32 ↩33 ↩34 ↩35 ↩36 ↩37 ↩38

  3. markdownlint v0.41.1 — MD038: Spaces inside code span elements David Anson、markdownlint リポジトリ公式ドキュメント(v0.41.1、2026年11月6日参照)。MD038 の検査対象がコードスパンの開始・終了バッククォート直近の不要な空白スタイル(単一スペースパディング等の例外規定を含む)であり、偶数個のバッククォートによるスパン分裂を検出する規則ではないことの確認。 ↩ ↩2

  4. CommonMark Spec Version 0.29 John MacFarlane、CommonMark 公式仕様(Version 0.29、2026年11月6日参照)。0.29 において Code spans が §6.3 として掲載されていた事実および同節での backtick string の定義(現行 0.31.2 の §6.1 との節番号の対照根拠)。 ↩