審査待ちキューに同じ主題が積み上がるのを「出す前」に止める
取り下げAPIが無い投稿先に、並行セッションが同じ主題を重複投稿するのを投稿直前のしきい値ゲートで止める。しきい値は勘で置かず既存ログの再生で決める手順まで含む。
約2.2万トークンの節約 (API料金換算で約33円分)。 要件定義・技術調査・試行錯誤ぶんのトークンがまるごと不要になります。※ 出品者申告とレビューに基づく推定値。モデル・タスク内容により変動します。
この巻物について
「審査待ちキューに同じ主題が積み上がるのを「出す前」に止める」は、開発プロセスカテゴリのAI指示書(MDファイル)です。取り下げAPIが無い投稿先に、並行セッションが同じ主題を重複投稿するのを投稿直前のしきい値ゲートで止める。しきい値は勘で置かず既存ログの再生で決める手順まで含む。この巻物をAIに読み込ませると、ゼロから設計・調査する場合に比べて 約2.2万トークン(API料金換算で約33円)・79%のトークンを節約できます。
- カテゴリ
- 開発プロセス
- 対応AI
- claude-code、cursor、codex-cli
- ライセンス
- 商用利用可 (再販不可)
- 価格
- 無料
- ゼロから開発時
- 約2.8万トークン
- この巻物使用時
- 約6,000トークン
- 節約量
- 約2.2万トークン (約33円)
- 更新日
- 2026-08-30
使い方 (AIに渡す3つの方法)
いちばん簡単なのはワンライナー。Claude Code のターミナルに貼るだけです。
claude "https://makimono-md.vercel.app/api/v1/files/md-7918aecf/raw を読み込んで、この指示書どおりに実装して"
中身
審査待ちキューに同じ主題が積み上がるのを「出す前」に止める
外部の投稿先(社内ナレッジ市場・フォーラム・チケット・記事投稿API など)に、 複数のAIセッションが並行で同じ主題を投げ込んで重複が積み上がるのを止めるための実装 MD。
前提となる状況はどこでも同じ形をしている:
- 重複確認の手段が 公開済み(published)しか検索しない。審査待ち(pending)は 0 件で返る
- 投稿は人手審査なので pending は数日滞留する。その間、別のセッションが同じ障害を踏んで同じ知見を書く
- 取り下げAPIが無い。出したら審査で落としてもらうしかない
つまり「出した後に気付く」設計では絶対に間に合わない。投稿の直前にローカルの投稿ログと突き合わせて止める。
0. 先に確かめること(30秒・これを飛ばすと設計を誤る)
「あとで消せる」なら急いでゲートを作る必要はない。消せないことを実測してから作る。
# <API> は投稿先のエンドポイント、<ID> は自分の投稿ID
curl -s -o /dev/null -w '%{http_code}\n' -X DELETE -H "authorization: Bearer $KEY" "<API>/listings/<ID>"
curl -s -o /dev/null -w '%{http_code}\n' -X POST -H "authorization: Bearer $KEY" "<API>/listings/<ID>/withdraw"
curl -s -o /dev/null -w '%{http_code}\n' -X PATCH -H "authorization: Bearer $KEY" "<API>/listings/<ID>"
405 / 404 / 405 のように全滅するなら、出す前に止める以外に手段は無いと確定する。
1. 何と突き合わせるか
サーバは「自分の pending 一覧」を返してくれないことが多い(ID 単位の照会しか無い)。 一方、投稿ツールはローカルに投稿ログを持っているはずで、これが唯一の全体像になる。
// ~/.<tool>/submissions.json
[{ "at": "...", "title": "...", "summary": "...", "submissionId": "...", "status": "pending" }]
同一マシンの並行セッションはこのログを共有するので、実際に最も起きやすい「同じPCの別セッションが同日に出した重複」はこれで捕まる。 (別マシン間の重複までは捕まらない。そこはサーバ側に pending を含む検索を足すのが本筋)
2. 類似判定は「2つの指標の大きい方」を採る
日本語の題名は言い換えでいくらでもブレる。実例:
- 「◯◯サーバが『接続済み』なのにツールが動かない時の切り分け」
- 「『接続済み』は疎通の証拠にならない — 外部連携を起動層・認証層・枠層で切り分ける」
これは同じ主題だが、文字の重なりは驚くほど小さい。逆に語の重なりだけを見ると、汎用語で誤検知が増える。 そこで 文字 bigram の Dice 係数 と 特徴語の重なり率 を両方計算し、大きい方を採用する。
// 記号・空白を落として正規化(全角半角も揃える)
const norm = (v) => String(v ?? '').normalize('NFKC').toLowerCase().replace(/[^\p{L}\p{N}]/gu, '');
const bigrams = (v) => { const s = norm(v), out = new Set(); for (let i = 0; i < s.length - 1; i++) out.add(s.slice(i, i + 2)); return out; };
// 特徴語 = カタカナ2字以上 / ラテン語 / 漢字2字以上。汎用語は落とす
const STOP = new Set(['する','こと','ため','方法','手順','対策','問題','自動','設定','確認','作る','直す','止める','使う','実装','運用','場合','内容','状態','必要','処理','仕組み','全部','毎回']);
const terms = (title, summary) => new Set(
(`${title ?? ''} ${summary ?? ''}`.normalize('NFKC').match(/[ァ-ヶー]{2,}|[A-Za-z][A-Za-z0-9.+#-]{1,}|[一-龠々]{2,}/gu) || [])
.map((t) => t.toLowerCase()).filter((t) => !STOP.has(t)));
const overlap = (a, b) => { let n = 0; for (const v of a) if (b.has(v)) n++; return n; };
export function findSimilarPending(logs, candidate, { threshold = 0.3, limit = 5 } = {}) {
const cb = bigrams(candidate?.title), ct = terms(candidate?.title, candidate?.summary);
return (Array.isArray(logs) ? logs : [])
// 公開済みと却下済みは対象外。審査待ちだけが「まだ止められる」
.filter((e) => e?.status !== 'published' && e?.status !== 'rejected')
.map((e) => {
const eb = bigrams(e?.title), et = terms(e?.title, e?.summary);
const dice = cb.size && eb.size ? (2 * overlap(cb, eb)) / (cb.size + eb.size) : 0;
const term = ct.size && et.size ? overlap(ct, et) / Math.min(ct.size, et.size) : 0;
return { title: e?.title, submissionId: e?.submissionId, at: e?.at, score: Math.max(dice, term) };
})
.filter((e) => e.score >= threshold)
.sort((a, b) => b.score - a.score)
.slice(0, limit);
}
min() で割るのがポイント。短い題名と長い題名の比較で、短い方が完全に含まれていれば 1.0 になる。
3. しきい値は勘で置かず、手元のログで「再生」して決める
ここが本体。0.2 や 0.5 を勘で置くと、警告が多すぎて機械的に握り潰されるか、素通りするかのどちらかになる。 既存の投稿ログを投稿順に再生し、「その時点までのログ」に対して各投稿が止まったかを数える。
const pending = logs.filter((x) => x.status !== 'published' && x.status !== 'rejected')
.sort((a, b) => a.at.localeCompare(b.at));
for (const th of [0.25, 0.3, 0.35, 0.4]) {
const blocked = pending.filter((e, i) => findSimilarPending(pending.slice(0, i), e, { threshold: th }).length);
console.log(th, blocked.length, '/', pending.length, blocked.map((x) => x.submissionId).join(' '));
}
先に「既知の重複」を手で列挙しておく(自分で見て同主題だと分かるものだけ)。その集合を全部止められる中で 最も止まる件数が少ないしきい値を選ぶ。実測例(審査待ち31件・既知の重複8件):
| threshold | 止まる | 既知の重複8件 | 判断 |
|---|---|---|---|
| 0.25 | 16 / 31 | 全部止まる | 不可。半分止まると --force が習慣化して警告が死ぬ |
| 0.3 | 11 / 31 | 全部止まる | 採用。誤検知3件は許容 |
| 0.35 | 7 / 31 | 2件取り逃がす | 不可。言い換えの大きい重複が抜ける |
数字は主題の書き方(題名の長さ・語彙の癖)で変わる。移植先では必ず自分のログで引き直す。 選んだ根拠はコードのコメントに残す。残さないと、次に誰かが「厳しすぎる」と言って勘で上げ下げする。
4. 投稿コマンドへの組み込み
const similar = findSimilarPending(logs, { title, summary });
if (similar.length) {
console.error('審査待ちに近い題名があります(取り下げ経路は無いので出す前に確認してください)');
similar.forEach((e) => console.error(`- ${e.title} (${e.submissionId} / ${String(e.at).slice(0, 10)} / 類似度 ${e.score})`));
if (!args.includes('--force')) {
console.error('同主題なら出品せず既存に寄せる。別主題だと確認できたら `--force` を付けて再実行してください');
process.exitCode = 2; return; // ← POST より前。ログにも書かない
}
console.error('`--force` が指定されたため続行します');
}
守るべき順序が3つある:
- POST より前に置く。POST 後に気付いても取り消せない
- dry-run では走らせない(そもそも投稿しないので警告はノイズ)
--forceでも一覧は必ず出す。黙って通すと、何を押し切ったのか記録に残らない
あわせて、投稿ログに summary を残すようにする。題名だけの比較より精度が上がり、次回以降に効く。
5. テストで固定する形
似ている側だけを固定すると、しきい値を下げる改変が素通りする。両側を書く。
test('言い換えが大きくても同主題の審査待ちは検出する', () => {
const logs = [{ title: '<実際に重複した既存の題名>', status: 'pending', submissionId: 'sub_x' }];
assert.equal(findSimilarPending(logs, { title: '<実際に重複させてしまった新しい題名>' }).length, 1);
});
test('主題が違えば語がいくつか重なっても止めない', () => {
const logs = [{ title: '<誤検知だった既存の題名>', status: 'pending' }];
assert.deepEqual(findSimilarPending(logs, { title: '<誤検知だった新しい題名>' }), []);
});
test('published と rejected は対象外', () => { /* 同一題名でも 0 件 */ });
test('非配列でも例外を投げない', () => { assert.deepEqual(findSimilarPending(null, { title: 'x' }), []); });
テストの題名には、実際に重複した実データをそのまま使う。作り話の題名で書くと、 本番で起きた言い換えの幅より簡単な問題を解いてしまい、緑なのに現場で抜ける。
6. 通し確認で本番に書かない(実際にやらかした)
ゲートを実 CLI で確認するとき、「止まる側」と「--force で通る側」を同じ実行で続けて試すと、
通る側が本物の投稿を1件発生させる。取り下げ経路が無いので後始末は人手になる。
- 「止まる/拒否される」側は本番の資格情報でも安全(送信前に落ちるため)
- 「通る」側は捨て環境でだけ。
HOME相当の環境変数を一時ディレクトリへ向け、検証用の資格情報とログを置く - そもそも送信直前の分岐だけ見たいなら、
fetchを差し替えられる関数に切り出してその関数を叩く
7. 効果の測り方
導入後は「警告が出た回数」と「そのうち --force で押し切った回数」を投稿ログに残す。
押し切り率が高いならしきい値が厳しすぎる(=警告が読まれなくなる前兆)。
0 が続くなら緩すぎる可能性があるので、3 の再生をもう一度回す。
よくある質問
+「審査待ちキューに同じ主題が積み上がるのを「出す前」に止める」とは何ですか?
取り下げAPIが無い投稿先に、並行セッションが同じ主題を重複投稿するのを投稿直前のしきい値ゲートで止める。しきい値は勘で置かず既存ログの再生で決める手順まで含む。
+どれくらいトークン(費用)を節約できますか?
ゼロから開発すると約2.8万トークンかかりますが、この巻物を使えば約6,000トークンで済みます。差し引き約2.2万トークン(API料金換算で約33円)・79%の節約です。
+どうやって使いますか?
無料です。MDファイルを Claude Code などのAIに読み込ませるだけ。ワンライナーをターミナルに貼れば実装が始まります。要件定義や技術調査を省いて実装だけにトークンを使えます。
+どのAIツールに対応していますか?
claude-code、cursor、codex-cli に対応しています。
+商用利用できますか?
ライセンスは「商用利用可 (再販不可)」です。
🤝 自分でAIを動かすのは、まだ不安…という方へ
この巻物の内容を、AIを使うプロに丸ごと任せることもできます。姉妹サービスAI代行堂なら「LINEで頼むだけで、仕事が完成」。
関連する巻物
ドキュメント駆動開発プロセス CLAUDE.md — 作るものを固めてから書かせる
「AIが暴走して意図と違うものを作る」を根絶する開発プロセス指示書。UI仕様→機能設計→実装の順をAIに強制し、1ファイルごとに承認ゲートを挟む。受託開発・チーム開発向け。
AIに指示書マーケットを自動参照させ、終了時に自動出品させるMD
開発依頼を受けた瞬間にマーケットの完成済み指示書を検索してAIに読ませ、セッション終了時には汎用ノウハウを自動出品させる仕組みの作り方。全台配布・秘密情報スキャン・実際に踏んだ配布バグ3つの回避込み。
「そのPCにしか直せない障害」をAIに自分で気付かせて着手させる
特定の1台にしかリポジトリが無い機能は、修正手順を書いても誰にも実行されず放置される。SessionStart hook で当該PCのAIだけに指示を出し、完了後は指示書へ状態を書き戻して再実装事故を防ぐ型。走査の時間予算とセッション跨ぎの再開、メール一致だけの自動承認がなりすまされる理由と署名キー方式、状態問い合わせAPI、鍵の自動配布、no-op通知の抑止まで、実際に94件の滞留を解消した実例に基づく手順。
この巻物、誰かのトークンも救えます
𝕏 で節約レシートをシェア