@takagaki/cortex-decisions-viewer 0.12.74 → 0.12.76

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/repo.js CHANGED
@@ -8,6 +8,7 @@ exports.classifyCommit = classifyCommit;
8
8
  exports.isFrontmatterKeyRename = isFrontmatterKeyRename;
9
9
  exports.collectAiChangeHistory = collectAiChangeHistory;
10
10
  exports.collectGoldCommitIdentities = collectGoldCommitIdentities;
11
+ exports.readBlobsAt = readBlobsAt;
11
12
  exports.lastSubstantiveUpdate = lastSubstantiveUpdate;
12
13
  const node_child_process_1 = require("node:child_process");
13
14
  /**
@@ -825,6 +826,85 @@ function collectGoldCommitIdentities(cwd) {
825
826
  }
826
827
  return map;
827
828
  }
829
+ /**
830
+ * 指定のコミットのファイルの中身を**まとめて 1 回**読む(`git cat-file --batch`)。
831
+ *
832
+ * 🔴 **レコードごとにプロセスを起こさない。** 帯(印)の判定は「人が確認した版と今の版で
833
+ * 説明文・本文が変わったか」を見るので、確認の基準コミットのレコードを読む必要がある。
834
+ * ここで `git show` をレコードごとに叩くと、用語が 1400 件ある案件でビルドが 1400 回
835
+ * git を起こす——`collectGoldCommitIdentities` と同じく「git の呼び出しはレコード数に
836
+ * 依存しない」の規律を保つため、key を stdin に流し込む `--batch` を 1 回だけ使う。
837
+ *
838
+ * @param keys `<commit>:<path>` の列(重複は畳む)。**空なら git を起こさない**。
839
+ * 🔴 **改行を含む key は git に渡す前に落とす**(`--batch` の問い合わせは 1 行 1 件なので、
840
+ * 1 つでも混ざると以降の key が**全部 1 つずつずれ**、`a.md` の中身が `b.md` として返る)
841
+ * @returns 引けた key → 中身(utf8)。**引けなかった key は入れない**——呼び出し側は
842
+ * 「入っていない = 引けなかった(窓の外・改名・削除)」として扱うこと。
843
+ * `missing` と「引けたが空」を言い分けるために、キーの有無で表す
844
+ */
845
+ function readBlobsAt(cwd, keys) {
846
+ const out = new Map();
847
+ // 🔴 **改行(LF / CR)を含む key を git に渡さない。** `cat-file --batch` の問い合わせは
848
+ // 「1 行 1 件」で、応答も**問い合わせた順**に並ぶ前提でここは読んでいる。改行入りの key が
849
+ // 1 つ混ざると git はそれを 2 件として扱い、**そこから先の対応がすべて 1 つずれる**
850
+ // ——実測で `c.md` の欄に `b.md` の中身が入った(=印の判定を他人の版と比べる)。
851
+ // ファイル名に改行を入れられる案件は稀だが、**壊れ方が「引けない」ではなく「別人の中身」**
852
+ // なので、落として「引けなかった」に倒す(呼び出し側は印を出さない側へ倒れる)
853
+ const wanted = [...new Set(keys.filter((k) => k !== "" && !/[\r\n]/.test(k)))];
854
+ if (!wanted.length)
855
+ return out;
856
+ let buf;
857
+ try {
858
+ buf = (0, node_child_process_1.execFileSync)("git",
859
+ // 出力の形を読む人の git 設定に左右させない(gitLog と同じ理由)。
860
+ // `--batch` は素の中身をそのまま出すので、ここで要るのは色と引用の抑止だけ
861
+ ["-c", "core.quotepath=false", "-c", "color.ui=false", "cat-file", "--batch"], {
862
+ cwd,
863
+ // 🔴 **`input` を渡すので `gitLog` は使えない**(あちらは stdin を ignore している)。
864
+ //
865
+ // ⚠️ **末尾の改行は「無いと最後の 1 件が読まれない」ではない**(2026-09-20 に実測で訂正)。
866
+ // git 2.43 は EOF で最後の行を読むので、付けても付けなくても応答は 1 バイトも変わらない
867
+ // ——**テストで守れる不変条件ではない**(変異を当てても緑のまま)。それでも付けるのは、
868
+ // `--batch` の入力が「1 行 1 件」だと綴りの上で言い切るため(行の途中で EOF にしない)
869
+ input: wanted.join("\n") + "\n",
870
+ stdio: ["pipe", "pipe", "ignore"],
871
+ // 🔴 **Buffer で受ける。** ヘッダの size は**バイト数**なので、文字で数えると
872
+ // 日本語のレコード(実データはほぼ全部)で切り出し位置がずれる
873
+ maxBuffer: 256 * 1024 * 1024,
874
+ });
875
+ }
876
+ catch (e) {
877
+ // git が無い(ENOENT)・リポジトリでない(128)は既定の状態なので警告しない
878
+ //(`gitLog` と同じ規律)。戻り値は空=「1 件も引けなかった」で、呼び出し側は
879
+ // 印を立てない側に倒れる(誤報より無言)
880
+ const err = e;
881
+ if (err.code !== "ENOENT" && err.status !== 128) {
882
+ console.warn(`人が確認した版の中身を git から取れませんでした(印は出さずに続けます): ${String(e)}`);
883
+ }
884
+ return out;
885
+ }
886
+ // 応答は**問い合わせた順**に並ぶ。引けた行は `<oid> blob <size>` + 中身、
887
+ // 引けない行は `<key> missing`(key をそのまま返すので oid の形に当たらない)
888
+ let pos = 0;
889
+ for (const key of wanted) {
890
+ if (pos >= buf.length)
891
+ break;
892
+ const nl = buf.indexOf(0x0a, pos);
893
+ if (nl < 0)
894
+ break;
895
+ const header = buf.toString("utf8", pos, nl);
896
+ pos = nl + 1;
897
+ const m = /^([0-9a-f]{40,64}) (\S+) (\d+)$/.exec(header);
898
+ // `missing` / `ambiguous` の行は中身が続かないので、次の key へ進むだけ
899
+ if (!m)
900
+ continue;
901
+ const size = Number(m[3]);
902
+ if (m[2] === "blob")
903
+ out.set(key, buf.toString("utf8", pos, pos + size));
904
+ pos += size + 1; // 中身の後ろに git が付ける改行
905
+ }
906
+ return out;
907
+ }
828
908
  function lastSubstantiveUpdate(cwd, repoPath) {
829
909
  // 🔴 **浅い clone を最初に弾く。** 浅い clone では境界コミットが親を持たないため、
830
910
  // このファイルを触っていない直近のコミットまで `A <path>` として現れる(実測)。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@takagaki/cortex-decisions-viewer",
3
- "version": "0.12.74",
3
+ "version": "0.12.76",
4
4
  "description": "Cortexの意思決定記録(Decisions/*.md)を静的サイトにビルドして閲覧する。各レコードに「Edit on GitHub」リンクを付与する。",
5
5
  "license": "UNLICENSED",
6
6
  "private": false,