kk-curate について
kk-curateは、Kenkeepのセッションログにある知識提案を整理し、プロジェクトのナレッジベースへ反映するスキルです。候補をバッチ処理で重複排除し、見つかった矛盾はユーザーと対話して解決します。
主な機能
- 01セッションログから提案を抽出
- 02候補のバッチレビュー
- 03追加・変更・矛盾・破棄の判断
- 04CLIによるアクションの重複排除
- 05ナレッジインデックスの再生成
- 06ユーザーとの対話による矛盾解決
活用シーン
- 01セッションで記録した知識の整理
- 02重複または価値の低い提案の除外
- 03プロジェクト知識内の矛盾の確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ e0ipso/ kenkeep --skill kk-curateインストール後、Claude Code で/kk-curateを入力して読み込まれたか確認します。
同梱ファイルbatch-agent-prompt.md
詳細
kk-curate
あなたはキュレーターです。このセッションで保留中のセッションログを読み、候補ごとにアクションを決定し、CLI プリミティブで重複除去を一度実行して、残ったアクションを curate-persist で保存し、インデックスを再生成し、表面化した矛盾をユーザーと対話的に解決します。サブエージェントもランナーもありません。キュレーションを行う LLM は、あなたです。
何もしない実行も正しい結果です。 すべての候補が言い換え、シグナルが弱いもの、またはすでにカバーされているものであれば、正しい結果は書き込みゼロで、既存のノードをそのままにすることです。実行を正当化するために編集をでっち上げてはいけません。「変更なし。ナレッジベースは最新です」と明確に報告してください。
プロジェクトルートを解決する
同梱の検出ツールを使ってリポジトリのルート(.ai/kenkeep を含むディレクトリ)を解決し、以降のすべてのコマンドで、出力されたパスを作業ディレクトリとして扱ってください。
KK_REPO_ROOT=$(node .ai/kenkeep/scripts/kk-detect-root.mjs) || exit $?
cd "$KK_REPO_ROOT" || exit $?
pwd
0. 保留中のセッションログから提案を抽出する
proposal_status: pending の各セッションログについて、キュレーションを始める前に、このセッション内で提案をインラインで抽出してください。
-
保留中のセッションログを一覧表示します。
Glob(またはls)を使って.ai/kenkeep/_sessions/*.mdを一覧表示します。各ファイルのフロントマターをReadし、proposal_status: pendingでフィルターします。captured_atの昇順に並べます。 -
該当なしの場合は終了します。 保留中のものがなければ、メッセージを出さずにステップ 1 に進みます。
-
抽出プロンプトを読み込みます。 最初に
.ai/kenkeep/.config/prompts/proposal-extract.md(リポジトリごとのオーバーライド)を読みます。そのファイルが存在しない場合は、同梱のパッケージテンプレートを読みます(インストール済み npm パッケージからの相対パスはtemplates/prompts/proposal-extract.md)。プロンプトの抽出ルールに従ってください。ここにその内容を複製してはいけません。 -
保留中のログをそれぞれ順番に処理します(
captured_atの順)。1 つのログで失敗しても、残りの処理は中止しません。 a. ファイル全体を読みます。 b. トランスクリプトのセクション(## Transcriptと## Proposalの間の内容)を抽出します。 c. プロンプトの抽出ルールを適用して、ProposalOutputSchemaに一致する JSON オブジェクトを作成します。形式は{ "practice": [...], "map": [...] }で、各エントリーは{ type, tags, title, description, body, kk_confidence }を持ちます。 d. JSON を CLI プリミティブにパイプします。echo '<json>' | npx kenkeep@latest session-log update-proposals <path> --status donee. 失敗(不正な出力、スキーマ違反、CLI エラー)の場合は、次を呼び出します。
npx kenkeep@latest session-log update-proposals <path> --status failed --error "<message>" -
少なくとも 1 件のログを処理した場合、概要を報告します。
Extracted proposals from N session(s) (M failed).(N と M は実際の件数に置き換えます)。 -
ステップ 1 に進みます。
1. 保留中のセッションログを列挙する
Glob(または ls)を使って .ai/kenkeep/sessions/*.md を一覧表示します。各ファイルのフロントマターを Read し、次の条件を満たすものだけを残します。
proposal_status: done、かつcurator_processed_atが未設定(キーがない、または空文字列)。
残ったログを captured_at の昇順に並べます。この順序を正準順序として維持してください。
該当なしの場合は終了します。 残ったログがなければ、1 行だけ出力して停止してください(以下のすべてのステップをスキップします)。
No pending session logs to curate. Nothing to do.
2. セッションを最大 10 件ずつ読み込み、キュレーターのアクション案を作成する
一度に多くのコンテキストを与えると出力品質が低下するため、作業をバッチに分けてください。1 バッチあたり最大 10 件のセッションを処理します。保留中のセッションを captured_at の順序を保ったまま、連続する最大 10 件ずつのバッチに分割します。バッチ番号は 1..N とします。
実行 ID は最初に一度だけ生成します。各バッチの一時ファイルとステップ 3 の提案ファイルでは、同じ実行 ID を再利用します。
RUN_ID=$(uuidgen 2>/dev/null || date -u +"curate-%Y%m%dT%H%M%SZ")
mkdir -p .ai/kenkeep/_logs/curator
パスを選択する:並列サブエージェントへの委任、またはインラインでの逐次処理
使用可能なツールを調べ、共通付録 .ai/kenkeep/.config/prompts/sub-agent-delegation.md に従って、並列処理またはインライン処理のパスを選択してください(調査の定義、ターンあたりの同時実行上限 5 件とウェーブルール、絶対ドラフトパス、発行済み/検証済み/無効の成果物形式)。
並列パス(利用可能な場合に推奨)
並列化の単位は 最大10セッションの1バッチ(<N> は1から採番)です。現在のウェーブの各バッチについて(オーケストレーターのターンあたり最大5バッチ):
-
絶対パスのドラフトパスを計算し、委譲する前に
issued行を追記します。N=1 # batch index DRAFT_PATH="$(pwd)/.ai/kenkeep/_logs/curator/${RUN_ID}__${N}.draft.json" echo "{\"ts\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",\"event\":\"issued\",\"runId\":\"${RUN_ID}\",\"batchN\":${N},\"sessions\":<count>}" \ >> .ai/kenkeep/_logs/curator/${RUN_ID}__${N}.jsonl -
同階層のファイル
batch-agent-prompt.mdにあるプロンプトを使用して、バッチごとにサブエージェントを1つディスパッチします。<list>はバッチ内のセッションファイルの絶対パスに、<DRAFT_PATH>は上記のDRAFT_PATHに置き換えます。エージェントはCuratorAction配列を(curator-outputに照らして検証しながら)そのパスに書き込みます。
ウェーブ内のすべてのサブエージェントが戻ったら、コレクターターンで、手作業の連結ではなく決定的プリミティブを使って、バッチごとのドラフトを集約し、検証します。
PROPOSALS=$(mktemp -t kk-curate-proposals.XXXXXX.json)
npx --yes kenkeep@latest drafts collect --run-id "$RUN_ID" --schema curator-output > "$PROPOSALS"
drafts collect はすべての ${RUN_ID}__*.draft.json を読み込み、各バッチを curator-output に照らして検証し、残った項目を標準出力のJSON配列に連結します($PROPOSALS に取り込みます)。各バッチの .jsonl に validated/invalid イベントを追記し、スキップしたバッチを標準エラー出力に報告します。1つの不正なバッチがあっても中断しません。$PROPOSALS はステップ4で使用できる状態になるため、ステップ3の再生成はスキップしてください。ステップ4の curate-dedup 呼び出しは、残ったすべてのバッチのアクションに対して1回だけ実行されます。これは現在と同じです。
インラインパス(フォールバック)
サブエージェントのディスパッチプリミティブを利用できない場合は、このセッション内で順番にドラフトを作成します(リリース済みの動作)。各バッチについて:
- バッチ内のすべてのセッションファイルを完全に
Readします。 - 各セッションのフロントマターにある
proposals:ブロックには、practice: [...]とmap: [...]の配列が含まれています。各項目は{ type, tags, title, description, body, kk_confidence }を持ちます。 - 候補ごとに(practice、mapの順に)、アクションを add、modify、contradict、drop から決定します。以下のルールを使用してください。
candidate_origin = "<session_id>:<practice|map>:<index>"を使用します。<index>は配列内のゼロ始まりの位置です。 - アクションオブジェクト(下記のスキーマ)を作成し、すべてのバッチにまたがる全アクションのセッション内リストに追加します。
すべてのバッチの処理が完了するまで、蓄積を続けてください。
アクションのルール
add — 新しい知識
候補が真に新しいものであり、既存のナレッジベースノードが同じ範囲をすでに網羅しているという強い兆候がない場合に使用します。候補が既存のノードと重複しているように見える場合は、add ではなく drop を優先してください。
追加が適切であることを示す兆候:
- トピックがナレッジベースにとって新しいものです。
- 候補に、ほかにはない内容(根拠、範囲、例)が含まれています。
- 関連する既存ノードは隣接する内容を扱っており、この内容そのものを扱っているわけではありません。
ラッパーはタイトルからスラッグを生成し、ディスク上で衝突する場合は自動的にサフィックス(-2、-3、…)を付けます。ただし、実際に重複していると感じる場合は drop を優先してください(候補が既存ノードを具体化する場合は modify を選んでください)。
add には ホームブランチ も指定します。これは、リーフが置かれる nodes/ 配下の既存フォルダーです。kk_relates_to/kk_depends_on を設定する際の推論と同じ段階で選び、アクションの home_folder フィールドに記録してください。home_folder を未設定、null、空にすると、フォールバックとして nodes/ 直下にリーフが配置されます。
modify — 既存のノードを改良する
既存のノードがすでにこのトピックを扱っているものの、候補の内容がそれを否定せずに拡張または改良する場合に使用します。
変更が適切であることを示す兆候:
- 既存のノードと同じ範囲(同じ規約、同じモジュール、同じ注意点)を扱いながら、候補が更新された例、補足された根拠、新たにサポートされたケース、欠けていた詳細、または明確化を加えている。
- 2つの内容に矛盾がなく、同時に成立する。
- 候補の内容が既存の本文に対して実質的に新しいものであり、単なる言い換えではない。
変更では、既存のリーフを現在のパスにあるまま、IDで内容をマージして上書きします(ツリー内の現在の場所にある nodes/<...>/<target_node_id>.md)。リーフの場所は変更せず、home_folder も設定しません。target_node_id は必須で、すでにディスク上に存在していなければなりません。存在しない場合、永続化ステップ(node write)で新しいノードが作成されますが、これは modify の意図とは異なります。そのため、modify アクションを出す前に、nodes/ に対して Glob を実行するか、該当するブランチの index.md を読み、対象が存在することを確認してください。
最終状態への書き換えルール。 マージ後の本文は、現在の状態を現在形で記述します。「以前は……だった」「かつては……していた」といった段落を追加せず、本文内で「プロジェクトが X から Y に移行した」と説明することも避けてください。新しい候補の情報が移行の経緯を説明するものである場合は、新しい最終状態の主張だけが見えるよう、既存のノード本文を書き換えてください。ナレッジベースが記録するのは、プロジェクトの現在の状態であり、変更履歴ではありません。
重要: 候補が既存のノードと実質的に同じ内容で、単に言い換えただけの場合は、変更せずに破棄してください。変更では、実質的に新しい情報を追加する必要があります。
変更の抑制。 .ai/kenkeep/.config/prompts/knowledge-admission.md(これらのルールの唯一の情報源)にある、共通の変更抑制基準に従ってください。書き換えよりも最小限のインプレース編集を優先し、正確なノードに不要な変更を加えないでください。編集はすべて、変更を必要とした具体的な提案または競合に結び付けてください(影響計画の形式:ソースの変更 → 影響を受けるノード → 必要な編集 → その理由)。既存のノードに書式だけの変更を加えることは避けてください。文章を整えるだけの modify は、破棄してください。
contradict — 既存ノードを否定する
候補が既存の有効なノードを直接否定する場合に使用します(同じスコープで、両方が同時に真になることはありません)。ユーザーは後でセッション内で矛盾を解消します。
矛盾が実際にあることを示す兆候:
- 既存のノードが「常に X」または「ケース Y では X を行う」と述べており、候補が「決して X しない」または「ケース Y では X を行わない」と述べている。
- 候補を生成したセッション内で、ユーザーが以前の決定を明示的に覆した。
- 候補のスコープが既存ノードのスコープと完全に重なっており、一部だけではない。
**重要:**候補のスコープが既存ノードのスコープのサブセットまたは例外である場合、これは矛盾ではありません。kk_relates_to を指定した追加(または変更)です。例:既存ノードが「デフォルトのキャッシュタグを使用する」と述べており、候補が「パーソナライズされたページでは、代わりにユーザーごとのキャッシュコンテキストを使用する」と述べている場合、どちらも真になり得ます。その場合は、contradict ではなく、kk_relates_to: [<existing node id>] を指定して add を出力してください。
矛盾によってノードファイルが変更されることはありません。重複排除プリミティブが競合を .ai/kenkeep/conflicts/<id>.md に書き込みます。競合解決フロー(以下のステップ 7)では各ファイルを確認し、ユーザーに判断を求めます。ユーザーが再実行せずに判断できるよう、proposed_node と rationale を十分に具体的な内容にしてください。
**target_node_id の選び方。**候補が主張を否定する既存ノードを 1 つ指定してください。2 つの既存ノードがどちらも重複する場合は、スコープが最も一致する方を選び、もう一方については rationale で触れてください。同じ候補に対して contradict アクションを 2 つ出力しないでください。
**rationale の記述方法。**既存のどの主張を候補が否定しているのか、また両方が同時に真になれない理由を 1~2 文で述べてください。レビュアーは最初にここを読みます。
最終状態の本文。proposed_node.body には、新しい最終状態のみを現在形で記述します。新しいノードの本文だけを読むレビュアーが、現在のルールを、最初からそのルールだったかのように理解できるようにしてください。
drop — 変更なし
候補が何の変更にもつながるべきではない場合に使用します。破棄する理由:
- 既存ノードの言い換えにすぎず、新しい情報がない。
kk_confidenceが低く、内容が曖昧である。- プロジェクト固有ではなく、一般的なプログラミング知識を記録している。
- 内部に矛盾がある、または他の箇所に存在しないものを参照している。
- 変更を中心とした表現 — 移行の説明、移行の経緯、名前変更や削除の記録、「以前は X を使っていたが、今は Y を使っている」という表現。信頼度に関係なく、自動的に破棄します。ナレッジベースが記述するのはプロジェクトの現在の最終状態であり、その歴史ではありません。
- 共通知識の採用基準で除外されているものすべて — 保守やライフサイクルに関するアクション、プロジェクトの経緯や歴史(特に計画/チケット/Issue への言及)、および一度限りの偶発的な事実を慣行のように見せたもの。.ai/kenkeep/.config/prompts/knowledge-admission.md` の基準を適用してください(6 か月後も保持する価値があるかの確認と、救済ルールも記載されています)。これらは自動的に破棄します。
- 候補の本文または説明に含まれる生産性のない出所を示す兆候:
- 断定を避けた仮の表現(「そうするかもしれない」「そうできるかもしれない」「可能性として」「その案は」など)。慣行ノードには仮説ではなくルールを記述します。
- 仮定上のもの、または実現していないものへの言及(「計画中の X」「Z を追加したら」など)。マップノードには実在するものを記述します。
- 計画またはタスクに限定した表現(「この計画では、~する」「成功基準は~」など)。
kk_confidenceが低く、根拠も具体例もない。
これらを総合的に判断し、生産性のないセッションの特徴が組み合わさっている場合は破棄してください。兆候が 1 つだけの場合は、自動的に破棄しないでください。
救済ルール。.ai/kenkeep/.config/prompts/knowledge-admission.md の救済ルールと保持基準を適用してください。候補が移行、保守アクション、または経緯を説明していても、明確で長く有効な原則や現在の状態に関する事実が含まれている場合は、その部分を抽出して、恒常的なルールまたは現在形の事実として書き直し、add または modify で保持します。候補全体が経緯そのものである場合は、破棄してください。
関連付けと配置
ナレッジベースは、nodes/ 配下にある、トピックごとに階層化されたフォルダーツリーです。ルートのインデックスノード、ブランチのインデックスノード、そして任意の深さにあるリーフで構成されます。add ごとに、クロスエッジとホームブランチの2つの出力を同時に生成する推論パスを1回だけ実行します。2回目のパスは実行しないでください。
- ツリーをたどります。 ルートのインデックスノード(
nodes/index.md)から開始し、候補に関連する要約を持つブランチのインデックスノードへ進みます。インデックスノードには子フォルダーとリーフが記載されているため、ディスカバリーと同じ方法で、既存の最も近いノートまでたどれます。 - クロスエッジを設定します。 最も近い既存リーフを起点に、
kk_relates_to(および、あるノードが別のノードに実際に依存している場合はkk_depends_on)をIDで設定します。エッジはIDで解決され、リーフの配置場所とは独立しています。特にマップノードでは、そのエンティティの変更を管理するプラクティスノードへのkk_relates_toエッジを優先してください。「これを編集するときは注意」という知識はプラクティスにあり、そのエッジによってエージェントがマップノードからその知識にたどり着けるようになります。候補またはそのセッションで具体的な編集時チェックが確認された場合は、マップ本文に「変更時に確認すること…」という短い任意の節も残します。テンプレートを埋めるために作り上げてはいけません。 - ホームブランチを順位付けします。 同じツリーのたどり方をもとに、既存のインデックスノード(フォルダー)を、サブツリーが候補のトピックにどれだけ合致するかで順位付けし、最も適合する既存フォルダーを1つ選びます。
home_folderには、nodes/からの相対的なトピックパス(例:cliやknowledge-base/index)をアクションに記録します。IDが識別子であり、選択したフォルダーに左右されることはありません。 - ルートにフォールバックします。 関連性の基準を満たす既存フォルダーがなければ、
home_folderは未設定/null/空のままにします。その場合、書き込み側はリーフをnodes/直下に配置します。これは意図的に可視化された結果であり、エラーではありません。後の再バランス処理で移動します。ルートを避けるためだけに、適合性の低いフォルダーを無理に選んではいけません。
modify、contradict、dropでは、home_folderを設定しません。modify(重複排除を伴う更新)では、フォルダー引数も移動も行わず、既存リーフをIDで指定して現在のパスに書き換えます。
制約(すべてのアクションに適用)
- プラクティスとマップの境界を越えてはいけません。 プラクティスの候補がマップノードになることも、マップの候補がプラクティスノードになることもありません。
- 無関係なノードを上書きしてはいけません。
modifyの対象は、スコープが候補と実際に一致するノードでなければなりません。一致しない場合は、add(kk_relates_to付き)またはcontradictを優先してください。 - 慎重に判断してください。
addとmodifyで迷った場合は、modifyを優先します(重複を減らせます)。modifyとdropで迷った場合は、dropを優先します(ノイズを減らせます)。 - キュレーション中にツリー構造を変更してはいけません。 キュレーションの手順(リーフの作成と保存、ステップ2~6)では、リーフを既存のフォルダーに配置します。フォルダーやブランチの作成、分割、統合は行いません。キュレーションで生じる構造上の結果は、ルートへのフォールバック(
nodes/直下へのリーフ配置)のみです。構造の変更は最終的な再バランスの段階(ステップ6b)でのみ行い、決定論的なトリガーが発動した場合に限ります。
アクションオブジェクトのスキーマ
各アクションは CuratorActionSchema に準拠します。アクションの配列が curator-output の契約です。形状を再導出するのではなく、CLI から正確な形状を取得して、重複排除の前に検証してください。
npx --yes kenkeep@latest schema curator-outputは JSON Schema(アクションオブジェクトとその入れ子のproposed_node)を出力します。$PROPOSALSを組み立てた後、npx --yes kenkeep@latest validate curator-output "$PROPOSALS"を実行してください。終了コードが 0 以外の場合は、パスが示すエラーを確認し、該当するアクションを修正して、検証に合格するまで再検証してください。
実際の意味づけは上記のとおりで、どのアクションを選ぶか(add/modify/contradict/drop)、最終状態への書き換えルール、最も狭いスコープでの矛盾の扱い、home_folder の配置は、適用する側に委ねられます。home_folder を設定するのは add のみです。modify、contradict、drop では省略し、proposed_node が null になるのは drop のみです。未知の proposed_node キーを拒否することなど、残りの要件はスキーマが検証します。
3. 提案を一時ファイルに書き込む
$RUN_ID はステップ 2 の冒頭で生成したものをここで再利用します。$SURVIVORS と、ステップ 2 の収集処理ですでに生成・入力されていない場合は $PROPOSALS も生成します。
SURVIVORS=$(mktemp -t kk-curate-survivors.XXXXXX.json)
# Only run the next two lines if you came through the inline path:
PROPOSALS=$(mktemp -t kk-curate-proposals.XXXXXX.json)
# Then Write your accumulated actions array (JSON array, top-level) to $PROPOSALS.
並列パスを経由した場合、$PROPOSALS にはスキーマ検証済みアクション配列がすでに連結されています(drafts collect による検証済みです)。ステップ 4 に進んでください。インラインパスを経由した場合は、蓄積したアクション配列(トップレベルの JSON 配列)を Write で $PROPOSALS に書き込み、その後、npx --yes kenkeep@latest validate curator-output "$PROPOSALS" で検証してください。パスが示すエラーを修正し、ステップ 4 に進む前に検証に合格するまで再検証してください。
4. プリミティブを使って重複排除し、スタンプを付与する
curate-dedup を呼び出します。
npx --yes kenkeep@latest curate-dedup \
--input "$PROPOSALS" --output "$SURVIVORS" --run-id "$RUN_ID"
重複排除はツリー全体を対象とします。既存のリーフは nodes/ 配下のすべてのフォルダー(深さを問わず)から読み込まれるため、重複は現在どこにあっても照合されます。動作はフラットなスペースの場合と変わりません。検索対象がツリー全体になるだけです。重複が見つかった場合、既存のリーフは ID に基づき、現在のパスのまま更新されます(modify)。移動は行われません。
この単一の呼び出しで、次の処理が原子的に実行されます。
- アクションの重複を排除します(バッチをまたぐ重複は統合され、信頼度の高い方が優先されます)。重複排除後に残った
addは、home_folderを変更せずに保持します。 - 重複排除後に残った
contradictアクションごとに${RUN_ID}-N形式の競合 ID を生成し、.ai/kenkeep/conflicts/<id>.mdファイルを書き込みます。 - 消費したすべての保留中のセッションログに
curator_processed_at/curator_run_idをスタンプとして付与します。 - 競合以外で残ったアクション(ノードとして永続化する必要があるアクション)を
$SURVIVORSに書き込みます。
標準出力には、次の 1 行の JSON が出力されます。
{"kept":N,"conflicts":M,"stamped":K,"runId":"..."}
これらの数値を記録して、ユーザーに報告してください。
5. curate-persist で残ったアクションを永続化する
手書きの node write ループではなく、kk-session-extract が使用するものと同じ決定論的プリミティブを使い、$SURVIVORS を 1 回で永続化します。
npx --yes kenkeep@latest curate-persist --input "$SURVIVORS"
curate-persist は、残ったアクションを curator-output 契約に照らして検証し、各アクションについて、共有ノードライターを使って add / modify を書き込みます(add は home_folder に配置され、指定されていない場合は nodes/ ルートにフォールバックします。modify はターゲットの現在のパスで ID に基づいて書き換えられ、移動しません)。drop はスキップし、contradict は拒否します(競合は curate-dedup が処理します)。標準出力には、解決済み ID と配置を含む各アクションの results と、written、dropped、failed を含む 1 つの JSON サマリーが出力されます。入力の形式が不正、または有効なアクションのいずれかが失敗した場合にのみ、0 以外の終了コードで終了します。いずれの場合も、成功した書き込みは保持されます。
サマリーを記録してください。ステップ 7 で、リーフごとの配置(home_folder または root fallback)と、アクションごとの失敗を報告します。target_node_id がディスク上に見つからない modify は、結果で失敗したアクションとして示されます。その更新が反映されなかったことをユーザーに伝えてください。
6. インデックスを再構築する
すべての書き込みの後に、次を実行します。
npx --yes kenkeep@latest index rebuild
6b. 再バランス(最終フェーズ、実行して完了)
これは curate の最終フェーズであり、ツリー構造を変更する唯一の箇所です。ここでまとめて実行します。2つ目のコマンドも、2回目の促しもありません。葉を書き込み、インデックスを再構築した後(ステップ 6)、報告の前に実行してください。
6b.1 決定論的なトリガーを実行する
トリガーは決定論的で、LLM を使用しません。Plan 1 のフォルダーごとの占有率/タグの多様性/葉のサイズのメトリクスを読み取り、ヒステリシスを組み込んだ判定ルールを適用して、安定した JSON 判定を出力します。実行して標準出力を取得してください。
npx --yes kenkeep@latest rebalance trigger
JSON 行を正確に 1 行出力します。
{"actions":[{"branch":"<path>","operation":"<split-folder|split-leaf|merge|create-branch>"}, ...]}
スキップする場合(追加コストなし)。 actions が空({"actions":[]})の場合、ツリーはヒステリシスマージンを超えてバランスが取れています。LLM によるクラスタリングのステップには進まないでください。ステップ 7 の要約に「rebalance: no structural action」と記録し、ステップ 7 に進んでください。これが一般的なケースです。curate の実行のほとんどでは何もトリガーされず、現在とまったく同じように終了します。
実行する場合。 actions が空でない場合は、6b.2 に進んでください。トリガーが指定したブランチのみを対象に検討し、決して範囲を広げないでください。
6b.2 影響を受けるブランチのみで構造操作を提案する
actions の各項目について、そのブランチだけ(指定されたフォルダーの index.md とそのリーフ、または split-leaf/create-branch の場合は指定されたリーフ)を読み、具体的な操作を決定します。これが実行全体で唯一の非決定的なステップです。非決定性は、決定的なトリガーと人間によるコミットゲートの背後に隔離されています。トリガーで指定されていないブランチには触れないでください。
各操作クラスを具体的な計画エントリーに対応付けます。操作で新しいフォルダーを作成する場合はすべて、そのフォルダーの summary も1行で作成してください。これは for more information on <summary> を完成させる名詞句/文の断片です(小文字で始め、末尾にピリオドを付けず、簡潔にします)。単なる構造ではなく、タスクを軸にしてください。フォルダー内に何があるかを示した後に、降下をトリガーすべきタスクを示す短い ; read when <task pattern> 節を付けます(例:the five harness adapters and their isolation rules; read before adding a harness or changing hook wiring)。エージェントは、これらの要約と自身のタスクを照合してルーティングするため、降下を確実にするのはトリガー節です。移動プリミティブはこの要約を新しいフォルダーのフォルダー要約サイドカーに記録し、以降の決定的な再構築でも保持します。親インデックスが Load … の降下ポインターに挿入するのも、この要約です。
- split-folder(
branchは項目が多すぎるフォルダー):そのフォルダー直下のリーフを、トピック別に2つ以上のサブフォルダーに分類します。{"operation":"split-folder","branch":"<folder>","groups":[{"subfolder":"<name>","summary":"<fragment>","ids":["<id>", ...]}, ...]}を出力します。各 ID はbranch直下に現在存在するリーフでなければなりません。各リーフは、必ず1つのサブグループに割り当てます。サブフォルダーごとにsummaryを1つ作成します。 - merge(
branchは項目が少ない/重複しているフォルダー):最適な既存の移動先フォルダーintoを選びます(トピックがそのフォルダーを包含する兄弟フォルダーまたは親フォルダー。nodes/ルートの場合は空文字列)。{"operation":"merge","branch":"<folder>","into":"<destination>"}を出力します。マージではフォルダーは作成されないため、summaryは作成しません。移動先では、そのフォルダー独自の保持された要約が使われます。 - create-branch(
branchは所属先のないルートリーフで、新しいトップレベルのトピック):新しいトップレベルフォルダー名と、そのフォルダーに属するリーフを選びます。{"operation":"create-branch","folder":"<new-top-level>","summary":"<fragment>","ids":["<id>", ...]}を出力します。 - split-leaf(
branchは2つ以上の概念を含む、肥大化した単一のリーフ):そのリーフの名前を付けたフォルダーの下に、新しいサブドキュメントを2つ以上作成します。{"operation":"split-leaf","leafId":"<old-id>","folder":"<folder>","summary":"<fragment>","children":[{"title":"...","summary":"...","body":"...","tags":["..."],"relates_to":["..."]}, ...]}を出力し、子項目は少なくとも2つにします。トップレベルのsummaryは新しいフォルダーを説明し、各子項目にはそれぞれのリーフのsummaryを設定します。プリミティブが新しい ID を発行し、古い ID からのリダイレクトを記録するため、ID は作成しないでください。
すべてのエントリーを1つの操作計画にまとめます:{"operations":[ ... ]}。それを一時ファイルに書き込みます:
REBAL_PLAN=$(mktemp -t kk-rebalance-plan.XXXXXX.json)
# Write your {"operations":[...]} plan to $REBAL_PLAN.
6b.3 移動を決定的に適用する
計画を決定的な移動プリミティブに渡します。このプリミティブは、各移動をコンテンツのバイト列と ID を保持する Git の名前変更として適用し(split-leaf では新しい ID とリダイレクトを発行)、その後、影響を受けたインデックスノードと nodes_hash を決定的に再構築します。ファイルの移動やインデックスの再生成を手動で行わないでください。
npx --yes kenkeep@latest rebalance move --input "$REBAL_PLAN"
プリミティブは、Step 7 に引き継ぐ構造サマリーを1行の JSON で出力します:
{"moves":[{"operation":"...","id":"...","from":"...","to":"...","newIds":["..."],"redirectFrom":"..."}, ...]}
これを取得します。何もコミット、追加、復元しないでください。構造上の移動とキュレーションによるリーフの書き込みは、いま、1つの未コミットの作業ツリーディフにまとまっています。人間は git commit で変更を受け入れ、パスを指定した git restore で構造上の移動のみを取り消します。
7. 概要を報告し、その後、競合に対処する
ユーザーに主要な数値(kept、conflicts、stamped、runId)、書き込まれたノード数、破棄された件数を伝えます。また、書き込まれた各リーフの配置判断も一覧にします。保存した各 add について、その ID と配置先のフォルダー(選択した home_folder、または選択されていない場合は root fallback)を報告します。各 modify については、現在のパスでそのまま更新されたことを記します。これにより、人が内容とあわせて配置を確認できます。
何もしないことも正しい結果です。 nodes_written == 0(候補がすべて破棄された)の場合、ナレッジベースはすでに最新の状態です。書き込み対象が空であることを謝るのではなく、はっきりと「変更なし。ナレッジベースは最新の状態です」と伝えてください。提案された内容に実質的な新情報がない場合、書き込み対象が空であることが望ましい結果です。
構造の概要(リバランス)。 続いて、リバランス段階(ステップ 6b)の構造の概要を、上記の内容の概要とは別に追加で出力します。これにより、人が構造差分を確認するための凡例を得られます。
- 6b.1 でアクションなしと報告された場合は、次の 1 行を出力します。
Rebalance: no structural action (tree balanced). - それ以外の場合は、
{"moves":[...]}の概要にある各移動について、操作と対象ブランチを示す 1 行を出力します。split-folder/merge/create-branchでは<id>: <from> -> <to>を示します。split-leafでは<old-id> -> <new-id>, <new-id>, ... (redirect recorded)を示します。最後に、次の文を出力します。Review the structural diff with \git diff --summary` (R entries are renames); accept by `git commit`, reject just the structural moves with a path-scoped `git restore`.`
conflicts == 0 の場合、配置行、構造の概要、概要の 1 行をこの順に出力し、そこで終了します。
Curated <nodes_written> nodes; <drops> dropped; no conflicts. Review the written files under .ai/kenkeep/nodes/.
それ以外の場合は、ステップ 7a に進みます。
7a. 保留中の競合を準備する
決定論的なプリミティブを 1 回実行し、保留中の競合を、それぞれ計算済みのデフォルト応答とともに、提示順に取得します。
npx --yes kenkeep@latest conflict prepare
これは保留中の競合ファイルを読み込み、並べ替え/グループ化します(target_node_id 順。null は最後、その後 proposed_kind、次に detected_at の順。同じ非 null の target_node_id を持つ競合が連続している場合は 1 つのグループになります)。差分率のルールに基づいて各競合のデフォルト応答を計算し、{"count":N,"conflicts":[...]} を出力します。各競合には、id、target_node_id、proposed_title、proposed_confidence、rationale、proposed_body、group_id、first_in_group、解決済みの既存ノード(グループ内では first_in_group の場合に 1 回だけ表示)、推奨される default(y/n/s)が含まれます。conflicts を指定された順に処理します。デフォルトは推奨であり、確定判断ではありません。
7b. 各競合を提示する
準備したリストのすべての競合について、次の手順を行います。
first_in_groupが true で、existingが null でない場合は、既存ノードのtitle、description、関連する本文の抜粋をグループにつき 1 回だけ表示します。- 提案された矛盾を簡潔に示します。
proposed_title、proposed_confidence、rationale、proposed_bodyを提示します。
(比較対象の既存ノードがない場合は、s が安全なデフォルトです。プリミティブにはすでに反映されています。)
7d. ユーザーに尋ね、応答を解析する
次のように、デフォルトを強調してユーザーに尋ねます。
Accept this proposal? [Y/n/s/k] (default: Y)
角括弧内では、デフォルトの文字を大文字にして視覚的に目立たせます。
応答は次のルールで解析します。
- 空欄、
y、Y、yes→yを選択します。 n、N、no→nを選択します。s、S、skip→sを選択します。k、K、keep→kを選択します。- それ以外 → 同じデフォルトを強調表示したまま、同じ競合を再度尋ねます。「問題なさそう」「はい、お願いします」「これはスキップして」などの文章から意図を推測しないでください。指定されたトークンのいずれかを必須とします。空の応答にはデフォルトを適用します。
7e. 結果を適用する
選択した応答をアクションに対応付けます。
y(提案を承認):既存のtarget_node_idノードを現在のパスでそのまま書き換えます。トピックに基づいた配置なので、Globnodes/**/<target_node_id>.mdでノードを見つけます。提案された本文とフロントマターで書き換えます(既存のtarget_node_idをスラッグとしてnode writeを実行するとIDに基づいてその場で更新されます。フロントマター全体を組み立て済みの場合は、解決したパスに直接Writeしてもかまいません)。その後、rm .ai/kenkeep/conflicts/<id>.mdを実行します。n(提案を却下):rm .ai/kenkeep/conflicts/<id>.mdを実行します。既存のノードは変更されません。s(スキップ):競合ファイルはそのままにします。status: pendingの状態を保ったまま、次回のcurateパスで再び表示されます。ファイルを編集または削除しないでください。k(記録として保持):後で確認できる履歴記録として、競合ファイルをディスク上に残します。既存のノードは変更されません。この選択はまれにのみ使用してください。
グループ内のすべての競合について決定したら、次のグループに進みます。
8. 引き継ぎ
.ai/kenkeep/以下の変更されたノードと競合ファイルを確認するようユーザーに伝えます。ENTRY.mdとGRAPH.mdはステップ6で更新済みです(リバランスフェーズが実行された場合は、リバランスの移動プリミティブによって再度更新されています)。ステップ6bで行われた構造上の移動も、同じ未コミットの差分に含まれています。すべての変更を受け入れるにはgit commitを実行し、構造上の移動だけを取り消すには、パスを指定してgit restoreを実行します。
制約
- 競合解決の応答として受け付けるのは、
y/n/s/k(またはそれぞれの長形式/デフォルトを表す空入力)のみです。言い換えた文章を回答として受け付けないでください。代わりに再入力を求めてください。 - 保留中のセッションログがない場合は、ステップ1で1行のメッセージを返して終了します。プリミティブは実行しないでください。
.ai/kenkeep/conflicts/が空の場合、またはすべてのファイルのstatusがpending以外の場合、解決する競合はありません。ステップ7の高速処理メッセージですでに対応しています。- リバランス(ステップ6b)はcurateの最終フェーズとしてのみ実行され、独立したコマンドや促しとして実行されることはありません。
rebalance triggerが{"actions":[]}を返した場合は、LLMによるクラスタリングのステップ全体をスキップし(追加コストはゼロ)、構造上のアクションがなかったことを報告してください。実行された場合は、指定されたブランチだけを対象に検討し、対象範囲を広げないでください。移動は必ずrebalance moveプリミティブだけで適用してください。ファイルを手動で移動したり、インデックスを手動で再生成したりしないでください。また、何もgit add、git commit、git restoreしないでください。 - dedupプリミティブはロックを使用せず、新しい
runIdでは冪等です。ただし、同じ$PROPOSALSで異なるrunIdを指定して再実行しないでください。消費済みセッションのスタンプが二重に付与され、競合ファイルも二重に書き込まれます。セッションごとにcurate-dedupを1回だけ呼び出してください。