@takagaki/cortex-decisions-viewer 0.12.76 → 0.12.78

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/build.js CHANGED
@@ -40,6 +40,7 @@ exports.PROJECT_ICON_EXTS = void 0;
40
40
  exports.sanitizeBody = sanitizeBody;
41
41
  exports.findProjectIcon = findProjectIcon;
42
42
  exports.readFleetSources = readFleetSources;
43
+ exports.attachRederived = attachRederived;
43
44
  exports.readGoldProposals = readGoldProposals;
44
45
  exports.buildHomeGrounding = buildHomeGrounding;
45
46
  exports.collectMeetings = collectMeetings;
@@ -672,7 +673,9 @@ async function readFleetSources(repoRoot) {
672
673
  * 🔴 **突き合わせはビルド時にここで閉じる**(`proposals` と同じ)。画面側でパスを組み立て直すと、
673
674
  * `repoPathOf` が `editUrl` 由来なので **`repoBaseUrl` が無い案件で帯が黙って消える**。
674
675
  */
675
- async function attachRederived(records, recordsDir, ctx, nowIso) {
676
+ async function attachRederived(records, recordsDir, ctx, nowIso,
677
+ // 番人テストが「git に渡った key」を数えるための差し替え口。本番は常に既定の `readBlobsAt`
678
+ readBlobs = repo_1.readBlobsAt) {
676
679
  if (!ctx)
677
680
  return records;
678
681
  // 🔴 **レコードのパスは `repoRoot` から作る**(`toRepoRelative` の `process.cwd()` 起点で
@@ -684,32 +687,46 @@ async function attachRederived(records, recordsDir, ctx, nowIso) {
684
687
  // ——ここで別のものを渡すと、画面の札と食い違う(「未確認」の札が付いた記録に帯が出る)
685
688
  const statusOf = (r) => r.rstatus;
686
689
  // 決定表の行 8(中身が変わったか)の材料。**印の候補になったレコードのぶんだけ**、
687
- // `<基準コミット>:<path>` をまとめて 1 回引く。
690
+ // 今の版を読み、`<基準コミット>:<path>` をまとめて 1 回引く。
688
691
  // 🔴 **キーが 0 件の区画では git を起こさない**(この関数は区画ごとに呼ばれる)
689
692
  const keyOf = new Map();
690
- for (const r of records) {
691
- const key = (0, rederivation_1.rederivedContentKey)(pathOf(r), statusOf(r), ctx.ledgers);
692
- if (key)
693
- keyOf.set(r, key);
694
- }
695
- const bases = keyOf.size ? (0, repo_1.readBlobsAt)(ctx.repoRoot, [...keyOf.values()]) : new Map();
696
693
  // 今の版は**作業ツリーから読む**——画面が描いているのはこのファイルであって HEAD ではない
697
694
  const now = new Map();
698
- for (const r of keyOf.keys()) {
695
+ for (const r of records) {
696
+ const key = (0, rederivation_1.rederivedContentKey)(pathOf(r), statusOf(r), ctx.ledgers);
697
+ if (!key)
698
+ continue;
699
+ const p = pathOf(r);
699
700
  try {
700
- now.set(pathOf(r), await node_fs_1.promises.readFile(path.join(ctx.repoRoot, pathOf(r)), "utf8"));
701
+ now.set(p, await node_fs_1.promises.readFile(path.join(ctx.repoRoot, p), "utf8"));
701
702
  }
702
703
  catch {
703
704
  // 読めなければ「引けなかった」側(下の verdict が unknown に倒す)
704
705
  }
706
+ // 🔴 **帳簿の指紋が今の中身と一致する記録は git の key に入れない**(engine #546)。
707
+ // 答えは下の verdict で `same` になるので、基準版を引く意味が無い——ここで外さないと、
708
+ // engine が指紋を書いても用語 1400 件の案件で毎晩 1400 個の blob を読み続ける。
709
+ // 指紋が無い・食い違う・今の版が読めない記録は今までどおり git へ落とす
710
+ const nowRaw = now.get(p);
711
+ const sha = ctx.ledgers.confirmation.get(p)?.contentSha;
712
+ if (nowRaw != null && sha && sha === (0, rederivation_1.contentShaOf)(nowRaw)) {
713
+ if (ctx.blobs)
714
+ ctx.blobs.skipped++;
715
+ continue;
716
+ }
717
+ keyOf.set(r, key);
705
718
  }
719
+ if (ctx.blobs)
720
+ ctx.blobs.read += keyOf.size;
721
+ const bases = keyOf.size ? readBlobs(ctx.repoRoot, [...keyOf.values()]) : new Map();
706
722
  const verdict = (p, baseCommit) => {
707
723
  const nowRaw = now.get(p);
708
724
  if (nowRaw == null) {
709
725
  ctx.miss.unreadable.add(p);
710
726
  return "unknown";
711
727
  }
712
- // 🔴 **帳簿が中身の指紋を持つならそれを先に使う**(engine が後の便で書く予定)。
728
+ // 🔴 **帳簿が中身の指紋を持つならそれを先に使う**(engine #546 が書く。一致した記録は
729
+ // 上で git の key から外してある)。
713
730
  // 一致したときだけ「変わっていない」と言い、食い違ったら git の二段目へ落とす——
714
731
  // engine が別の綴りで指紋を作っていた場合に、全レコードへ誤報が出るのを避ける
715
732
  const conf = ctx.ledgers.confirmation.get(p);
@@ -968,10 +985,18 @@ async function loadGenericRecords(dirAbs, exclude, repoBaseUrl, branch) {
968
985
  records.push(await parseGeneric(raw, e.name, repoRelDir, repoBaseUrl, branch));
969
986
  }
970
987
  // 日付降順 → タイトル昇順(用語集のように日付が同一でも安定して並ぶ)
988
+ //
989
+ // 🔴 **第 2 鍵(コード順)を外さない**(#576)。`localeCompare(…, "ja")` は**別の綴りでも
990
+ // 0 を返す**(実測: `が`/`ガ`・`サーバ`/`さーば`・`AI`/`AI`・`あ行`/`ア行`)。0 のまま
991
+ // 安定ソートに任せると、その 2 語の順を決めるのは**`readdir` が返した順**になり、Node は
992
+ // その順を保証しない——中身を 1 文字も変えていない日に、用語集の並びが環境で変わる。
993
+ // cortex-engine の記憶の目次(`scripts/memory-index.mjs` の `cmpTerm`)はこの並びに
994
+ // **上位 20 件の集合**を合わせているので、ここが揺れると目次との一致も揺れる。
995
+ // 引き分けだったところに順を決めるだけで、**決まっていた並びは 1 つも変えていない**。
971
996
  records.sort((a, b) => {
972
997
  if (a.date !== b.date)
973
998
  return a.date < b.date ? 1 : -1;
974
- return a.title.localeCompare(b.title, "ja");
999
+ return a.title.localeCompare(b.title, "ja") || (a.title < b.title ? -1 : a.title > b.title ? 1 : 0);
975
1000
  });
976
1001
  return records;
977
1002
  }
@@ -1959,6 +1984,7 @@ async function build(opts) {
1959
1984
  repoRoot: repoRoot,
1960
1985
  identities: ledgers.confirmation.size ? (0, repo_1.collectGoldCommitIdentities)(repoRoot) : new Map(),
1961
1986
  miss: { unresolved: new Set(), unreadable: new Set() },
1987
+ blobs: { read: 0, skipped: 0 },
1962
1988
  }
1963
1989
  : null;
1964
1990
  const decisions = [];
@@ -2051,6 +2077,12 @@ async function build(opts) {
2051
2077
  `(浅い clone か、確認の後に改名・削除された。変わったかを言えないので印は出しません): ` +
2052
2078
  paths.slice(0, 3).join(", ") + (paths.length > 3 ? " ほか" : ""));
2053
2079
  }
2080
+ // 🔴 **指紋(engine #546)が効いているかを 1 行残す。** 一致した記録は git を読まないので、
2081
+ // 「読んだ件数」が候補の数より減っていれば効いている。警告ではないので console.log
2082
+ if (rederive?.blobs && rederive.blobs.read + rederive.blobs.skipped > 0) {
2083
+ console.log(`gold-confirmation.json: 人が確認した版の中身を git から ${rederive.blobs.read} 件読みました` +
2084
+ `(中身の指紋が一致した ${rederive.blobs.skipped} 件は読んでいません)`);
2085
+ }
2054
2086
  const unmatched = [...PROPOSALS_BY_PATH.keys()].filter((k) => !PROPOSAL_PATHS_MATCHED.has(k));
2055
2087
  if (unmatched.length) {
2056
2088
  console.warn(`gold-proposals.json: レコードに突き合わない提案が ${unmatched.length} 件ありました(表示しません): ` +
@@ -141,7 +141,7 @@ async function readRederivationLedgers(repoRoot) {
141
141
  const changedBy = Array.isArray(row?.changed_by)
142
142
  ? row.changed_by.map((s) => String(s)).filter((s) => s !== "")
143
143
  : [];
144
- // 中身の指紋は**文字列のときだけ**持つ(engine がまだ書かないので、いまは常に無い)
144
+ // 中身の指紋は**文字列のときだけ**持つ(engine #546 が書く。古い帳簿の行には無い)
145
145
  const contentSha = typeof confirmed.content_sha === "string" ? confirmed.content_sha : "";
146
146
  out.confirmation.set(p, {
147
147
  at: confirmed.at != null ? String(confirmed.at) : "",
@@ -311,9 +311,12 @@ function sameRederivedContent(a, b) {
311
311
  /**
312
312
  * 「説明文と本文」の指紋。**帳簿の `confirmed.content_sha` と突き合わせる綴り**。
313
313
  *
314
- * 🔴 engine がこの欄を書き始めるまで、この値と比べる相手は存在しない。**一致したときだけ
314
+ * 🔴 比べる相手は engine #546 が書く `confirmed.content_sha`。**一致したときだけ
315
315
  * 「変わっていない」と言い、食い違ったら git へ落とす**(`attachRederived`)——engine が
316
316
  * 別の綴りで指紋を作っていた場合に、全レコードへ誤報が出るのを避けるため。
317
+ *
318
+ * engine の写し: `cortex-engine/scripts/rederive/content-sha.mjs`・一致の番人:
319
+ * `cortex-engine/test/rederive-history.test.mjs` の #546 の節(ここを変えたら engine の `VIEWER_SHA` を計算し直す)
317
320
  */
318
321
  function contentShaOf(raw) {
319
322
  const c = comparableContent(raw);
@@ -462,23 +465,39 @@ function rederivedBandOf(recordPath, status, ledgers, identities, verdict, miss)
462
465
  };
463
466
  }
464
467
  /**
465
- * 用語集の並び(#432 裁定 5-5)。**確認済みは先頭群・群の中は活性度の降順。**
468
+ * 用語集の並び(#432 裁定 5-5 → #690 で群分けを撤廃)。**活性度の降順だけで決める。**
469
+ *
470
+ * 🔴 **確認済みかどうかは並びに使わない**(便 #690・オーナー決定 2026-09-21 逐語
471
+ * 「触れられた回数とか、携わってる人、携わってる回数が多い人が上に出てくる方がいい。
472
+ * **アクティブ、ドラフト関係なく**」)。engine の関係者の並び(`scripts/engagement/tiers.mjs`
473
+ * の `order`・便 #543)に当てたのと同じ形で、**同じ画面の中で並び方を揃える**。
474
+ *
475
+ * 前はここで「確認済みが先頭群」を先に見ていたので、**群の境目が活性度より強く**、
476
+ * ほとんど触られていない確認済みの語が先頭に並び、**いま出てきたばかりの下書きが下に沈んだ**。
477
+ * 理由は 2 つ:
478
+ * - 「確認済みかどうか」は**札で分かる**(`render.ts` の「AI生成・未確認」)ので、
479
+ * 並びで二重に表現する必要がない。下書きが上に来るのは「**新しく出てきた言葉**」なので、
480
+ * むしろ見たいもの
481
+ * - 🔴 **この並びは「索引の上から N 件だけを AI に読ませる」構想の土台**(engine #544)。
482
+ * 「上に来るものが重要」でなければ、その仕組みが成り立たない
466
483
  *
467
- * 🔴 **「確認済み」は `status` だけで決まる**(#667 のオーナー決定)。**帳簿は見ない**
468
- * ——先頭群に来るのは「AI生成・未確認」の札が付かない記録で、画面の札と 1 文字も同じ判定
469
- * (`isConfirmedStatus`)を使う。前は `ledgers.confirmation.has(p)`(=人が 1 度でも触ったか)
470
- * で決めていたので、**札は「未確認」なのに先頭群に居る**記録が出ていた(実測: cortex-context
471
- * の用語集で 25 件)。帳簿の役目は「どの版に戻すか」だけになった。
484
+ * 🔴 **`isConfirmedStatus` は消さない**——このファイルの**印の門(`rederivedContentKey`)と
485
+ * 帯の門(`rederivedBandOf`)**が使う。`render.ts` の札は `rec.rstatus === "draft"` の
486
+ * **別実装**で、2 つが一致していることは `rederived-mark-ui.test.mjs` の `[札との一致]` が見る。
487
+ * ここで変えるのは**並びだけ**で、札・帯・印・プロンプトに渡る材料は 1 つも変えていない。
472
488
  *
473
489
  * - 活性度を持つレコードが 1 件も無い区画は **1 バイトも触らない**(帳簿が無い案件は従来順)
474
- * - 活性度を知らないレコードは群の末尾へ、**元の並びのまま**(安定)
490
+ * - 活性度を知らないレコードは**末尾**へ、**元の並びのまま**(安定)
491
+ * - 同じ活性度なら元の並び(日付降順 → タイトル昇順がそのまま残る)
475
492
  * - 用語集だけ(メンバーの並びは便 14・Decisions と Rules は対象外)
476
493
  *
477
- * ℹ️ `ledgers` は活性度のために引き続き受け取る(`ledgers.activity` だけを渡す形にすると
478
- * 「帳簿の判定に戻す」退行がコンパイルで止まる代わりに、**変異検証でそれを実演できなくなる**)。
494
+ * ℹ️ `ledgers` と `statusOf` は**受け取り続ける**。`ledgers` は活性度のために要る。
495
+ * `statusOf` は並びに使わなくなったが、**引数から落とすと「群分けを足し戻す」退行が
496
+ * コンパイルで止まる代わりに、変異検証でそれを実演できなくなる**(`ledgers` と同じ流儀)。
497
+ * 番人(`rederivation-ledgers.test.mjs` の `[並び]`)はこの 1 行の足し戻しで赤くなる。
479
498
  *
480
499
  * @param records 区画のレコード(`repoPathOf` で引けるパスを持つもの。並びは変えずに新しい配列を返す)
481
- * @param statusOf そのレコードの `rstatus`(`String()` 済み)を引く関数
500
+ * @param statusOf そのレコードの `rstatus`(`String()` 済み)を引く関数。**並びには使わない**(上の ℹ️)
482
501
  */
483
502
  function orderByActivity(records, pathOf, statusOf, ledgers, nowIso) {
484
503
  if (!records.length)
@@ -490,7 +509,6 @@ function orderByActivity(records, pathOf, statusOf, ledgers, nowIso) {
490
509
  return {
491
510
  r: r,
492
511
  i: i,
493
- confirmed: inSection && isConfirmedStatus(statusOf(r)),
494
512
  score: inSection ? activityScore(ledgers.activity.get(p), nowIso) : null,
495
513
  };
496
514
  });
@@ -499,15 +517,12 @@ function orderByActivity(records, pathOf, statusOf, ledgers, nowIso) {
499
517
  return scored
500
518
  .slice()
501
519
  .sort((a, b) => {
502
- // 1. 確認済みは先頭群(群の境目は活性度より強い)
503
- if (a.confirmed !== b.confirmed)
504
- return a.confirmed ? -1 : 1;
505
- // 2. 群の中は活性度の降順。知らないものは群の末尾
520
+ // 1. 活性度の降順だけ(便 #690・確認済みかどうかでは分けない)。知らないものは末尾
506
521
  if (a.score != null && b.score != null && a.score !== b.score)
507
522
  return b.score - a.score;
508
523
  if ((a.score == null) !== (b.score == null))
509
524
  return a.score == null ? 1 : -1;
510
- // 3. 同点は元の並び(安定・日付降順 → タイトル昇順がそのまま残る)
525
+ // 2. 同点は元の並び(安定・日付降順 → タイトル昇順がそのまま残る)
511
526
  return a.i - b.i;
512
527
  })
513
528
  .map((s) => s.r);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@takagaki/cortex-decisions-viewer",
3
- "version": "0.12.76",
3
+ "version": "0.12.78",
4
4
  "description": "Cortexの意思決定記録(Decisions/*.md)を静的サイトにビルドして閲覧する。各レコードに「Edit on GitHub」リンクを付与する。",
5
5
  "license": "UNLICENSED",
6
6
  "private": false,