@takagaki/cortex-decisions-viewer 0.12.118 → 0.12.119

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,7 +40,7 @@ exports.DECISION_FIRMNESS = exports.PROJECT_ICON_EXTS = exports.MIRROR_RELS = vo
40
40
  exports.sanitizeBody = sanitizeBody;
41
41
  exports.findProjectIcon = findProjectIcon;
42
42
  exports.readFleetSources = readFleetSources;
43
- exports.attachRederived = attachRederived;
43
+ exports.orderRecordsByLedgers = orderRecordsByLedgers;
44
44
  exports.humanTouchedOf = humanTouchedOf;
45
45
  exports.attachHumanTouched = attachHumanTouched;
46
46
  exports.rawFrontmatterDate = rawFrontmatterDate;
@@ -114,6 +114,7 @@ const supersedes_1 = require("./supersedes");
114
114
  const confirmed_1 = require("./confirmed");
115
115
  const minify_1 = require("./minify");
116
116
  const render_1 = require("./render");
117
+ const ledger_dir_1 = require("./ledger-dir");
117
118
  /**
118
119
  * この画面を作ったパッケージの版。**package.json を唯一の出どころにする。**
119
120
  * 別に持つと、公開した版と画面に出る版がずれる(しかも気づけない)。
@@ -706,85 +707,16 @@ async function readFleetSources(repoRoot) {
706
707
  return { externalSources, slackWorkspaces, internalSources, pipelines, capabilities, fleetGeneratedAt, secretSources, secretSpaces, checks, githubOwners, backlogTarget, tools };
707
708
  }
708
709
  /**
709
- * 区画のレコードに帯を載せ、用語集の並びを活性度にする(#665 便 12b)。
710
+ * 区画のレコードを活性度・強さの順に並べる(用語集だけが動く・#665 便 12b → #1094 便 e4 で帯を外した)。
710
711
  *
711
- * 🔴 **帳簿が無い案件では、渡された配列をそのまま返す**(帯のキーも付かない・並びも従来のまま)。
712
- * 44 案件のうち帳簿を持つのは栓 `rederive` を入れた案件だけなので、ここが「何もしない」に
713
- * 倒れることが**既定の経路**である。
714
- *
715
- * 🔴 **突き合わせはビルド時にここで閉じる**(`proposals` と同じ)。画面側でパスを組み立て直すと、
716
- * `repoPathOf` が `editUrl` 由来なので **`repoBaseUrl` が無い案件で帯が黙って消える**。
712
+ * 🔴 **帳簿が無い案件では、渡された配列をそのまま返す**(並びも従来のまま)。
717
713
  */
718
- async function attachRederived(records, recordsDir, ctx, nowIso,
719
- // 番人テストが「git に渡った key」を数えるための差し替え口。本番は常に既定の `readBlobsAt`
720
- readBlobs = repo_1.readBlobsAt) {
714
+ function orderRecordsByLedgers(records, recordsDir, ctx, nowIso) {
721
715
  if (!ctx)
722
716
  return records;
723
- // 🔴 **レコードのパスは `repoRoot` から作る**(`toRepoRelative` の `process.cwd()` 起点で
724
- // 作らない)。帳簿のキーは常にリポジトリルート相対(`Cortex/Glossary/records/…`)なので、
725
- // Decisions ディレクトリから CLI を叩いた案件では cwd 起点だと 1 件も突き合わない
726
717
  const recordsRel = path.relative(ctx.repoRoot, recordsDir).split(path.sep).join("/");
727
718
  const pathOf = (r) => (recordsRel ? `${recordsRel}/${r.fileName}` : r.fileName);
728
- // 🔴 **「確認済み」は `status` だけで決まる**(#667)。帯も並びも同じ `rstatus` を見る
729
- // ——ここで別のものを渡すと、画面の札と食い違う(「未確認」の札が付いた記録に帯が出る)
730
- const statusOf = (r) => r.rstatus;
731
- // 決定表の行 5〜7 の材料。**帯の候補になったレコードのぶんだけ**今の版を読み、指紋を作る。
732
- // 帳簿に人が確認した版の指紋が無い候補だけ、`<基準コミット>:<path>` をまとめて 1 回 git から引く。
733
- // 🔴 **キーが 0 件の区画では git を起こさない**(この関数は区画ごとに呼ばれる)
734
- const keyOf = new Map();
735
- // 今の版は**作業ツリーから読む**——画面が描いているのはこのファイルであって HEAD ではない
736
- const now = new Map();
737
- const nowSha = new Map();
738
- for (const r of records) {
739
- const key = (0, rederivation_1.rederivedContentKey)(pathOf(r), statusOf(r), ctx.ledgers);
740
- if (!key)
741
- continue;
742
- const p = pathOf(r);
743
- let nowRaw;
744
- try {
745
- nowRaw = await node_fs_1.promises.readFile(path.join(ctx.repoRoot, p), "utf8");
746
- }
747
- catch {
748
- // 読めなければ「読めなかった」側(`rederivedBandOf` が帯を出さずに数える)
749
- continue;
750
- }
751
- now.set(p, nowRaw);
752
- const sha = (0, rederivation_1.contentShaOf)(nowRaw);
753
- nowSha.set(p, sha);
754
- const conf = ctx.ledgers.confirmation.get(p);
755
- // 🔴 **今の版が機械の書き直しの版でなければ git を読まない**(帯は出ないと決まっている)。
756
- // 人が確認した版の指紋が帳簿にあれば、それで決まるので git を読まない(engine #546)
757
- if (sha !== conf?.rewritten?.contentSha)
758
- continue;
759
- if (conf.contentSha) {
760
- if (ctx.blobs)
761
- ctx.blobs.skipped++;
762
- continue;
763
- }
764
- keyOf.set(r, key);
765
- }
766
- if (ctx.blobs)
767
- ctx.blobs.read += keyOf.size;
768
- const bases = keyOf.size ? readBlobs(ctx.repoRoot, [...keyOf.values()]) : new Map();
769
- const verdict = (p, baseCommit) => {
770
- const nowRaw = now.get(p);
771
- const baseRaw = bases.get(`${baseCommit}:${p}`);
772
- if (nowRaw == null || baseRaw == null) {
773
- // 浅い clone の窓の外・確認の後に改名された・基準コミットにこの path が無い。
774
- // **帯は出さない**(誤報より無言)が、静かに消える経路なのでログの器に数える
775
- ctx.miss.unreadable.add(p);
776
- return "unknown";
777
- }
778
- return (0, rederivation_1.sameRederivedContent)(baseRaw, nowRaw) ? "same" : "changed";
779
- };
780
- const nowShaOf = (p) => nowSha.get(p) ?? null;
781
- for (const r of records) {
782
- const band = (0, rederivation_1.rederivedBandOf)(pathOf(r), statusOf(r), ctx.ledgers, nowShaOf, verdict, ctx.miss);
783
- // **印が立たないレコードにはキーごと付けない**(空の器を通さない)
784
- if (band)
785
- r.rederived = band;
786
- }
787
- return (0, rederivation_1.orderByActivity)(records, pathOf, statusOf, ctx.ledgers, nowIso);
719
+ return (0, rederivation_1.orderByActivity)(records, pathOf, (r) => r.rstatus, ctx.ledgers, nowIso);
788
720
  }
789
721
  /**
790
722
  * 記録に人の手があるか(cortex-engine #1094 段 4・削除の確認の文面を替える材料)。
@@ -824,7 +756,8 @@ function attachHumanTouched(records, recordsDir, repoRoot, confirmation) {
824
756
  *
825
757
  * 🔴 **逆引きは `supersedes.ts`(engine `scripts/gold/supersedes-index.mjs` の写し)だけで組む**。
826
758
  * 材料は新しい側の `relations[supersedes]` だけで、古い側の `superseded_by`(夜間の精製が書く写し)は
827
- * 読まない。確認済み=新しい側の `status` が `active`。
759
+ * 読まない。確定=古い側に人の手が無い(status が人の値でない)か、新しい側を人が確認した(cortex-engine #1094 段 4)。
760
+ * 口には記録の path(`Cortex/Decisions/records/…`・`repoRoot` 起点)を渡す。
828
761
  *
829
762
  * 置き換えが無い決定には**キーごと付けない**(今までと同じ SiteData)
830
763
  */
@@ -846,9 +779,11 @@ function rawFrontmatterDate(raw) {
846
779
  v = v.replace(/\s+#.*$/, "").trim();
847
780
  return v;
848
781
  }
849
- function attachSupersedes(decisions, rawDateOf = (d) => d.fm.date) {
782
+ function attachSupersedes(decisions, rawDateOf = (d) => d.fm.date, pathOf = () => undefined) {
850
783
  const input = decisions.map((d) => ({
851
784
  id: d.id,
785
+ // 口(`humanOwned`)に区画を伝える path。無ければ写しが決定の区画で聞く
786
+ path: pathOf(d),
852
787
  // 🔴 **frontmatter に書かれたままの date の文字列**(`rawFrontmatterDate`)。gray-matter は時刻・時差つきの
853
788
  // date を Date に読み、sanitizeFm は UTC の日付にするので、`2026-07-01T01:00:00+09:00` が 6/30 にずれる。
854
789
  // engine は日付を Date にしない読み方(CORE_SCHEMA)で書かれた文字列の先頭 8 桁を使う。
@@ -908,11 +843,16 @@ function attachSupersedes(decisions, rawDateOf = (d) => d.fm.date) {
908
843
  function optionalText(v) {
909
844
  return typeof v === "string" && v.trim() !== "" ? v : "";
910
845
  }
911
- async function readGoldProposals(repoRoot) {
846
+ /**
847
+ * engine の `normalized_match`(表記ゆれ)のカードの理由の文に入っている句。画面から外す印(#1094 便 35a-1)。
848
+ * 🔴 engine の `scripts/silver/build.mjs` の `WHY_NOT_DECIDED.normalized_match` の文を変えるときはここも揃える(片方だけだと静かに外れなくなる)
849
+ */
850
+ const NORMALIZE_CARD_WHY = /区切り・姓と名の順・会社名を除くと同じ名前/;
851
+ async function readGoldProposals(repoRoot, ledgerDir) {
912
852
  const empty = new Map();
913
853
  let json;
914
854
  try {
915
- const raw = await node_fs_1.promises.readFile(path.join(repoRoot, ".cortex", "gold-proposals.json"), "utf8");
855
+ const raw = await node_fs_1.promises.readFile(path.join((0, ledger_dir_1.ledgerDirOf)(repoRoot, ledgerDir), "gold-proposals.json"), "utf8");
916
856
  json = JSON.parse(raw);
917
857
  }
918
858
  catch (e) {
@@ -997,6 +937,11 @@ async function readGoldProposals(repoRoot) {
997
937
  // この 1 欄だけ覆した(決定ログ 20260925-699-f7b.md)
998
938
  if (row.decide === "portal")
999
939
  proposal.decide = "portal";
940
+ // 表記ゆれ(区切り・姓と名の順・会社名を直すと名簿に当たる)の名寄せカードは出さない(cortex-engine #1094 便 35a-1・
941
+ // 10/5 オーナー: 画面に人が押す操作を残さない)。engine は今後これを数えるだけにして自動で結ぶ。
942
+ // 🔴 見分けは engine の理由の文(`normalized_match` の `why`)。ほかの名寄せカードは今までどおり出す
943
+ if (proposal.kind === "identity" && NORMALIZE_CARD_WHY.test(proposal.why ?? ""))
944
+ continue;
1000
945
  const list = byPath.get(recordPath);
1001
946
  if (list)
1002
947
  list.push(proposal);
@@ -1525,7 +1470,7 @@ async function buildTargetUrlMap(repoRoot, repoBaseUrl, branch) {
1525
1470
  * 🔴 **`buildTargetUrlMap` は触らない**(移る間だけ残す表・詳細画面の飛び先の正本)。ここは名前と、
1526
1471
  * 写しのパス → 根拠の ID の対応だけを持つ。資料の題名を Drive の鍵にも載せるために、表の URL を読むだけ
1527
1472
  */
1528
- async function buildEvidenceBook(repoRoot, targetUrls, externalSources = []) {
1473
+ async function buildEvidenceBook(repoRoot, targetUrls, externalSources = [], ledgerDir) {
1529
1474
  const book = (0, evidence_names_1.emptyEvidenceBook)();
1530
1475
  const rel = (file) => path.relative(repoRoot, file).split(path.sep).join("/");
1531
1476
  const heading = (text) => text.match(/^#\s+(.+?)\s*$/m)?.[1] ?? null;
@@ -1540,7 +1485,7 @@ async function buildEvidenceBook(repoRoot, targetUrls, externalSources = []) {
1540
1485
  // 片付けで移した議事録のリンク(URL の鍵 → 「定例名 日付」・cortex-engine #968 R-6・A-R6-3)。表が無い・読めない案件は今どおり
1541
1486
  // (種類の既定の名前)。資料より先に載せる(同じ鍵に資料の題名が先に付かないように)
1542
1487
  try {
1543
- const table = JSON.parse(await node_fs_1.promises.readFile(path.join(repoRoot, evidence_names_1.MOVED_MEETINGS_FILE), "utf8"));
1488
+ const table = JSON.parse(await node_fs_1.promises.readFile(path.join((0, ledger_dir_1.ledgerDirOf)(repoRoot, ledgerDir), evidence_names_1.MOVED_MEETINGS_FILE), "utf8"));
1544
1489
  for (const [url, name] of (0, evidence_names_1.movedMeetingNamesOf)(table))
1545
1490
  putName(foldKeyOf(url), name);
1546
1491
  }
@@ -2421,43 +2366,34 @@ async function build(opts) {
2421
2366
  // 解決するので、あとから作ると空の表・空の実在判定が渡り、**実在するパスまで
2422
2367
  // 「リンクにしない」に倒れて全滅する**(以前は順序が逆だった)。
2423
2368
  const repoRoot = knowledgeRoot ? path.dirname(knowledgeRoot) : path.dirname(dirAbs);
2369
+ // 帳簿 9 本の置き場(既定は `<repoRoot>/.cortex`)。読み口はこの 1 つから読む
2370
+ const ledgerDir = (0, ledger_dir_1.ledgerDirOf)(repoRoot, opts.ledgerDir);
2424
2371
  const targetUrls = knowledgeRoot
2425
2372
  ? await buildTargetUrlMap(repoRoot, repo.baseUrl, repo.branch)
2426
2373
  : new Map();
2427
2374
  // fleet-status.json は 1 回だけ読む(読み手が警告を出すので、2 回読むと警告が 2 行になる)。
2428
2375
  // 名前の帳(チャンネル名)と、下の「データソースと自動化」で同じものを使う
2429
2376
  const fleet = await readFleetSources(repoRoot);
2430
- const evidenceBook = knowledgeRoot ? await buildEvidenceBook(repoRoot, targetUrls, fleet.externalSources) : (0, evidence_names_1.emptyEvidenceBook)();
2377
+ const evidenceBook = knowledgeRoot ? await buildEvidenceBook(repoRoot, targetUrls, fleet.externalSources, ledgerDir) : (0, evidence_names_1.emptyEvidenceBook)();
2431
2378
  const refs = createRefResolver({ repoRoot, targetUrls, repoBaseUrl: repo.baseUrl, branch: repo.branch, book: evidenceBook });
2432
2379
  // AIからの更新提案。**レコードを組み立てる前に読む**——各レコードが自分の分を
2433
2380
  // takeProposals で引き取るので、後から読んでも載せる先が既に閉じている。
2434
2381
  // 同一プロセスで2回ビルドしても前回の値が残らないよう、毎回入れ替える
2435
- PROPOSALS_BY_PATH = await readGoldProposals(repoRoot);
2382
+ PROPOSALS_BY_PATH = await readGoldProposals(repoRoot, ledgerDir);
2436
2383
  PROPOSAL_PATHS_MATCHED = new Set();
2437
- // 再導出の 3 帳簿(#665 便 12b)と用語の強さ(#790)。**帳簿が無い案件では帯も並び替えも作らない**。
2438
- // 人か機械かの見分けは engine が帳簿(`rewritten`)に書くので、画面は git の素性を読まない(#799)
2439
- //
2384
+ // 用語集の並びの帳簿(活性度・強さ #790)。**帳簿が無い案件では並び替えを作らない**。
2440
2385
  // 🔴 **強さ(`state` が on のときだけ中身が入る)だけの案件でも器を作る**——作らないと、活性度の帳簿を
2441
- // 持たない案件で用語集が目次(engine の `termRows`)と違う順になる。帯は `confirmation` の行が無ければ
2442
- // 1 件も立たない(`rederivedContentKey` の門)ので、器を作っても帯・印は変わらない
2443
- const ledgers = await (0, rederivation_1.readRederivationLedgers)(repoRoot);
2444
- const rederive = ledgers.confirmation.size || ledgers.activity.size || ledgers.strength.size
2445
- ? {
2446
- ledgers: ledgers,
2447
- // 🔴 **`repoRoot` で走らせる**(`collectAiChangeHistory` の `process.cwd()` に倣わない)。
2448
- // あちらは「リポジトリルートで実行すること」が前提の既存の制約だが、こちらは repoRoot を
2449
- // 既に知っている(帳簿をそこから読んでいる)。Decisions ディレクトリから CLI を叩いた
2450
- // ときも今の版と基準版が引け、帯が黙って消えない
2451
- repoRoot: repoRoot,
2452
- miss: { unreadable: new Set() },
2453
- blobs: { read: 0, skipped: 0 },
2454
- }
2386
+ // 持たない案件で用語集が目次(engine の `termRows`)と違う順になる。
2387
+ // 確認の帳簿(`confirmation`)は削除の確認の一言(`humanTouchedOf`)が使うが、並びには関係しない
2388
+ const ledgers = await (0, rederivation_1.readRederivationLedgers)(repoRoot, ledgerDir);
2389
+ const rederive = ledgers.activity.size || ledgers.strength.size
2390
+ ? { ledgers: ledgers, repoRoot: repoRoot }
2455
2391
  : null;
2456
2392
  // 関与の帳簿(便 14b・#709)。関係者の一覧を目次と同じ「関わりの多い順」に並べる材料。
2457
2393
  // **読めない・使えないなら `null`** で、関係者の並びは今までと 1 バイトも同じ
2458
- const engagement = await (0, engagement_1.readEngagementLedger)(repoRoot);
2394
+ const engagement = await (0, engagement_1.readEngagementLedger)(repoRoot, ledgerDir);
2459
2395
  // 目次と同じ順位(engine #551)。**読めないなら `null`** で、つながり画面の点は今までどおり一律
2460
- const memoryRanking = await (0, ranking_1.readMemoryRanking)(repoRoot);
2396
+ const memoryRanking = await (0, ranking_1.readMemoryRanking)(repoRoot, ledgerDir);
2461
2397
  // 記録の id → リポジトリルート相対のパス(順位のファイルの鍵と突き合わせる。`repoRoot` 起点・cwd 起点にしない)
2462
2398
  const recordRelPaths = new Map();
2463
2399
  const relOf = (dir, fileName) => {
@@ -2479,7 +2415,7 @@ async function build(opts) {
2479
2415
  // 人の手(削除の確認の文面を替える材料・cortex-engine #1094 段 4)。**置き換えより前に・repoRoot で**
2480
2416
  attachHumanTouched(decisions, dirAbs, repoRoot, ledgers.confirmation);
2481
2417
  // 置き換えの逆引き(#699 F3)。**置き換えの無い案件では 1 件も付かない**
2482
- attachSupersedes(decisions, (d) => rawDates.get(d) ?? d.fm.date);
2418
+ attachSupersedes(decisions, (d) => rawDates.get(d) ?? d.fm.date, (d) => relOf(dirAbs, d.fileName));
2483
2419
  let home = null;
2484
2420
  let homeGrounding;
2485
2421
  let homeUpdatedAt;
@@ -2521,9 +2457,8 @@ async function build(opts) {
2521
2457
  const records = await loadGenericRecords(recordsDir, exclude, repo.baseUrl, repo.branch);
2522
2458
  // 人の手(削除の確認の文面を替える材料・cortex-engine #1094 段 4)
2523
2459
  attachHumanTouched(records, recordsDir, repoRoot, ledgers.confirmation);
2524
- // 「人が確認した版から変わりました」の帯と、用語集の並び(#665 便 12b)。
2525
- // **帳簿を持たない案件では 1 件も付かず、並びもそのまま**(下の attachRederived)
2526
- const rederived = await attachRederived(records, recordsDir, rederive, opts.generatedAt);
2460
+ // 用語集の並び(活性度・強さ)。**帳簿を持たない案件では並びもそのまま**
2461
+ const rederived = orderRecordsByLedgers(records, recordsDir, rederive, opts.generatedAt);
2527
2462
  // 関係者だけ、関与の多い順(engine の目次と同じ `order`・便 14b)。用語集の並び
2528
2463
  // (`orderByActivity`)とは帳簿も式も別で、区画が重ならないので順番に当てても干渉しない
2529
2464
  const ordered = e.name === "Members"
@@ -2558,21 +2493,6 @@ async function build(opts) {
2558
2493
  // **突き合わなかった提案の件数をビルドログに出す。** path が1文字違うだけで
2559
2494
  // 画面から静かに消えるので、出さないと「提案が出ない」の原因に辿り着けない。
2560
2495
  // 表示からは落とす(どのレコードの話か分からない提案は、出しても行動につながらない)
2561
- // 🔴 **帯が静かに消える経路を 1 行残す。** 今の版か、人が確認した版の中身を
2562
- // 引けないと「説明文が変わったか」を言えないので、帯は出さない(誤報より無言)。
2563
- // 出さないと「印が出ない」の原因を「中身が変わっていない」と切り分けられない
2564
- if (rederive && rederive.miss.unreadable.size) {
2565
- const paths = [...rederive.miss.unreadable];
2566
- console.warn(`gold-confirmation.json: 人が確認した版か今の版の中身を ${paths.length} 件引けませんでした` +
2567
- `(浅い clone か、確認の後に改名・削除された。変わったかを言えないので帯は出しません): ` +
2568
- paths.slice(0, 3).join(", ") + (paths.length > 3 ? " ほか" : ""));
2569
- }
2570
- // 🔴 **指紋(engine #546)が効いているかを 1 行残す。** 一致した記録は git を読まないので、
2571
- // 「読んだ件数」が候補の数より減っていれば効いている。警告ではないので console.log
2572
- if (rederive?.blobs && rederive.blobs.read + rederive.blobs.skipped > 0) {
2573
- console.log(`gold-confirmation.json: 人が確認した版の中身を git から ${rederive.blobs.read} 件読みました` +
2574
- `(中身の指紋で決めた ${rederive.blobs.skipped} 件は読んでいません)`);
2575
- }
2576
2496
  const unmatched = [...PROPOSALS_BY_PATH.keys()].filter((k) => !PROPOSAL_PATHS_MATCHED.has(k));
2577
2497
  if (unmatched.length) {
2578
2498
  console.warn(`gold-proposals.json: レコードに突き合わない提案が ${unmatched.length} 件ありました(表示しません): ` +
@@ -2608,9 +2528,9 @@ async function build(opts) {
2608
2528
  // ストーリーの日々の動き(#901 V2-5)。帳簿で on の日のファイルが無ければ null(キーごと出さない)
2609
2529
  const story = await (0, story_1.readStory)(repoRoot, renderGroundingSection, (p) => repo.baseUrl ? `${repo.baseUrl}/edit/${repo.branch}/${encodePath(p)}` : null,
2610
2530
  // 動きのない日を埋める終点=ビルドの時刻の日本時間の日付(時計を注入できる)
2611
- storyToday);
2531
+ storyToday, ledgerDir);
2612
2532
  // 週・月のあらすじ(#901)。週・月のファイルが無ければ null(キーごと出さない=今までと同じ画面)
2613
- const storyDigest = await (0, story_1.readStoryDigest)(repoRoot, renderGroundingSection, (p) => repo.baseUrl ? `${repo.baseUrl}/edit/${repo.branch}/${encodePath(p)}` : null, storyToday);
2533
+ const storyDigest = await (0, story_1.readStoryDigest)(repoRoot, renderGroundingSection, (p) => repo.baseUrl ? `${repo.baseUrl}/edit/${repo.branch}/${encodePath(p)}` : null, storyToday, ledgerDir);
2614
2534
  // 案件アイコン(#348)。Cortex構成でなければ undefined(旧レイアウトには置き場が無い)
2615
2535
  const projectIcon = await findProjectIcon(knowledgeRoot);
2616
2536
  const site = {
package/dist/cli.js CHANGED
@@ -22,6 +22,7 @@ program
22
22
  .option("--secret-kinds <kinds>", "鍵の入力欄を出す連携(カンマ区切り。例: figma,backlog)")
23
23
  .option("--webhook-url <url>", "Backlog Webhook 受信のLambda URL(環境変数 WEBHOOK_URL でも可)")
24
24
  .option("--webhook-token <token>", "リアルタイム同期の**案件別**トークン(環境変数 WEBHOOK_TOKEN でも可。艦隊共通トークンを渡さないこと)")
25
+ .option("--ledger-dir <dir>", "ビューアが読む帳簿(.cortex の 9 本)の置き場(未指定なら <リポジトリルート>/.cortex)")
25
26
  .action(async (dir, opts) => {
26
27
  try {
27
28
  const result = await (0, build_1.build)({
@@ -38,6 +39,7 @@ program
38
39
  secretKinds: opts.secretKinds,
39
40
  webhookUrl: opts.webhookUrl ?? process.env.WEBHOOK_URL,
40
41
  webhookToken: opts.webhookToken ?? process.env.WEBHOOK_TOKEN,
42
+ ledgerDir: opts.ledgerDir,
41
43
  });
42
44
  console.log(`✓ ${result.count} 件のレコードを ${result.outDir} にビルドしました`);
43
45
  }
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.weightOf = exports.WEIGHTS = exports.HALF_LIFE_DAYS = exports.ENGAGEMENT_LEDGER_VERSION = exports.ENGAGEMENT_LEDGER_PATH = void 0;
6
+ exports.weightOf = exports.WEIGHTS = exports.HALF_LIFE_DAYS = exports.ENGAGEMENT_LEDGER_VERSION = exports.ENGAGEMENT_LEDGER_FILE = void 0;
7
7
  exports.scoreOf = scoreOf;
8
8
  exports.engagementOrder = engagementOrder;
9
9
  exports.parseEngagementLedger = parseEngagementLedger;
@@ -43,8 +43,9 @@ exports.orderMembersByEngagement = orderMembersByEngagement;
43
43
  */
44
44
  const promises_1 = __importDefault(require("node:fs/promises"));
45
45
  const node_path_1 = __importDefault(require("node:path"));
46
+ const ledger_dir_1 = require("./ledger-dir");
46
47
  /** 帳簿の置き場(engine `scripts/engagement/keys.mjs` の `LEDGER_PATH`) */
47
- exports.ENGAGEMENT_LEDGER_PATH = ".cortex/gold-member-engagement.json";
48
+ exports.ENGAGEMENT_LEDGER_FILE = "gold-member-engagement.json";
48
49
  /** 帳簿の版(engine `scripts/memory-index.mjs` の `LEDGER_VERSION`。違ったら読まない) */
49
50
  exports.ENGAGEMENT_LEDGER_VERSION = 1;
50
51
  /** 半減期(日)。engine `weights.mjs` の `HALF_LIFE_DAYS`(正本 6-B「半減期 90 日固定」) */
@@ -187,10 +188,10 @@ function parseEngagementLedger(text) {
187
188
  };
188
189
  }
189
190
  /** 案件リポから帳簿を読む(無ければ `null`・例外にしない) */
190
- async function readEngagementLedger(repoRoot) {
191
+ async function readEngagementLedger(repoRoot, ledgerDir) {
191
192
  let text;
192
193
  try {
193
- text = await promises_1.default.readFile(node_path_1.default.join(repoRoot, exports.ENGAGEMENT_LEDGER_PATH), "utf8");
194
+ text = await promises_1.default.readFile(node_path_1.default.join((0, ledger_dir_1.ledgerDirOf)(repoRoot, ledgerDir), exports.ENGAGEMENT_LEDGER_FILE), "utf8");
194
195
  }
195
196
  catch {
196
197
  return null;
@@ -60,7 +60,7 @@ function minuteLabel(id) {
60
60
  * 片付けで移した会議の表(engine が目次と同じコミットで置く・cortex-engine #968 R-6・A-R6-3)。
61
61
  * 写しを片付けた案件だけが持つ。無ければ今どおり(移した議事録のリンクは種類の既定の名前で出る)
62
62
  */
63
- exports.MOVED_MEETINGS_FILE = ".cortex/mirror-moved-meetings.json";
63
+ exports.MOVED_MEETINGS_FILE = "mirror-moved-meetings.json";
64
64
  /**
65
65
  * 移した会議の表 → `[移した URL, 会議の名前(「定例名 日付」)]` の並び。形が違う表・行は読まない(名前を当て推量しない)。
66
66
  * 名前は `minuteLabel`(`minute:` の根拠と同じ 1 本)で作る——同じ会議が `minute:` で書かれても URL で書かれても同じ名前になる
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.DEFAULT_LEDGER_DIR_NAME = void 0;
7
+ exports.defaultLedgerDir = defaultLedgerDir;
8
+ exports.ledgerDirOf = ledgerDirOf;
9
+ /**
10
+ * ビューアが読む帳簿(9 本)の置き場(cortex-engine #1094 便 3-11 の tools(T1))。
11
+ *
12
+ * 既定は `<リポジトリルート>/.cortex`(今までと同じ)。`--ledger-dir` で別のフォルダを指せる。
13
+ * ビューアは保管庫(S3)も栓も知らない(AWS SDK を足さない)。どこから持ってくるかは呼び出し側(workflow)の仕事で、
14
+ * ここは「そのフォルダの下の `<名前>.json` を読む」だけ。
15
+ *
16
+ * 🔴 `.cortex` という綴りの読み口は**ここ 1 か所だけ**(番人 `test/ledger-dir.test.mjs` が数える)。
17
+ */
18
+ const node_path_1 = __importDefault(require("node:path"));
19
+ /** 帳簿の置き場の既定の名前(リポジトリルートの下) */
20
+ exports.DEFAULT_LEDGER_DIR_NAME = ".cortex";
21
+ /** 既定の置き場: `<repoRoot>/.cortex` */
22
+ function defaultLedgerDir(repoRoot) {
23
+ return node_path_1.default.join(repoRoot, exports.DEFAULT_LEDGER_DIR_NAME);
24
+ }
25
+ /** 呼び出し側が渡した置き場。無ければ既定 */
26
+ function ledgerDirOf(repoRoot, ledgerDir) {
27
+ return ledgerDir ? node_path_1.default.resolve(ledgerDir) : defaultLedgerDir(repoRoot);
28
+ }
package/dist/ranking.js CHANGED
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.MEMORY_RANKING_SECTIONS = exports.MEMORY_RANKING_VERSION = exports.MEMORY_RANKING_PATH = void 0;
6
+ exports.MEMORY_RANKING_SECTIONS = exports.MEMORY_RANKING_VERSION = exports.MEMORY_RANKING_FILE = void 0;
7
7
  exports.parseMemoryRanking = parseMemoryRanking;
8
8
  exports.readMemoryRanking = readMemoryRanking;
9
9
  exports.glowByPath = glowByPath;
@@ -25,8 +25,9 @@ exports.glowByPath = glowByPath;
25
25
  */
26
26
  const promises_1 = __importDefault(require("node:fs/promises"));
27
27
  const node_path_1 = __importDefault(require("node:path"));
28
+ const ledger_dir_1 = require("./ledger-dir");
28
29
  /** 置き場(engine `scripts/memory-ranking.mjs` の `RANKING_PATH`) */
29
- exports.MEMORY_RANKING_PATH = ".cortex/memory-ranking.json";
30
+ exports.MEMORY_RANKING_FILE = "memory-ranking.json";
30
31
  /** 形の版(engine の `RANKING_VERSION`。違ったら読まない) */
31
32
  exports.MEMORY_RANKING_VERSION = 1;
32
33
  /** 載る区画(engine の `RANKING_SECTIONS`) */
@@ -57,17 +58,17 @@ function parseMemoryRanking(text) {
57
58
  }
58
59
  }
59
60
  /** 案件リポから読む。無い → `null`(黙る)・壊れている → `null`(build のログに 1 行) */
60
- async function readMemoryRanking(repoRoot) {
61
+ async function readMemoryRanking(repoRoot, ledgerDir) {
61
62
  let text;
62
63
  try {
63
- text = await promises_1.default.readFile(node_path_1.default.join(repoRoot, exports.MEMORY_RANKING_PATH), "utf8");
64
+ text = await promises_1.default.readFile(node_path_1.default.join((0, ledger_dir_1.ledgerDirOf)(repoRoot, ledgerDir), exports.MEMORY_RANKING_FILE), "utf8");
64
65
  }
65
66
  catch {
66
67
  return null;
67
68
  }
68
69
  const parsed = parseMemoryRanking(text);
69
70
  if (!parsed)
70
- console.warn(`${node_path_1.default.basename(exports.MEMORY_RANKING_PATH)}: 読めないので、つながり画面の点は一律の明るさで出します`);
71
+ console.warn(`${exports.MEMORY_RANKING_FILE}: 読めないので、つながり画面の点は一律の明るさで出します`);
71
72
  return parsed;
72
73
  }
73
74
  /**
@@ -13,8 +13,6 @@ exports.withoutFollowupHeadingText = withoutFollowupHeadingText;
13
13
  exports.comparableContent = comparableContent;
14
14
  exports.sameRederivedContent = sameRederivedContent;
15
15
  exports.contentShaOf = contentShaOf;
16
- exports.rederivedContentKey = rederivedContentKey;
17
- exports.rederivedBandOf = rederivedBandOf;
18
16
  exports.orderByActivity = orderByActivity;
19
17
  exports.followupDateOf = followupDateOf;
20
18
  exports.humanFollowupOf = humanFollowupOf;
@@ -48,10 +46,9 @@ const promises_1 = __importDefault(require("node:fs/promises"));
48
46
  const node_path_1 = __importDefault(require("node:path"));
49
47
  const gray_matter_1 = __importDefault(require("gray-matter"));
50
48
  const confirmed_1 = require("./confirmed");
49
+ const ledger_dir_1 = require("./ledger-dir");
51
50
  /** 帳簿の版(違ったら空として扱う・engine `LEDGER_VERSION`) */
52
51
  exports.LEDGER_VERSION = 1;
53
- /** 帯を出す区画。**用語とメンバーだけ**(Decisions は積み上げ・Rules は便 13) */
54
- const BAND_SECTION_RE = /^Cortex\/(?:Glossary|Members)\/records\/[^/]+\.md$/;
55
52
  /** 並びを変える区画。**用語集だけ**(関係者の並びは関与の帳簿で `engagement.ts` が決める・便 14b) */
56
53
  const ORDER_SECTION_RE = /^Cortex\/Glossary\/records\/[^/]+\.md$/;
57
54
  /** ACT-R base-level の減衰(#432 裁定 5-5: d = 0.5) */
@@ -112,10 +109,10 @@ function parseLedger(text) {
112
109
  *
113
110
  * @param then 警告の括弧の中(読めなかったあと画面がどうなるか)。強さの帳簿は帯を持たないので文言を変える
114
111
  */
115
- async function readOne(repoRoot, file, then = "帯と並びは出さずに続けます") {
112
+ async function readOne(ledgerDir, file, then = "帯と並びは出さずに続けます") {
116
113
  let raw;
117
114
  try {
118
- raw = await promises_1.default.readFile(node_path_1.default.join(repoRoot, ".cortex", file), "utf8");
115
+ raw = await promises_1.default.readFile(node_path_1.default.join(ledgerDir, file), "utf8");
119
116
  }
120
117
  catch (e) {
121
118
  if (e?.code !== "ENOENT") {
@@ -135,9 +132,10 @@ const asRecord = (v) => v != null && typeof v === "object" && !Array.isArray(v)
135
132
  * 3 帳簿を読む。**フィールドは明示で列挙し、素通しはしない**(`readGoldProposals` の流儀)——
136
133
  * engine は単独で版を上げるので、画面が知らないキーが必ず先に入る。
137
134
  */
138
- async function readRederivationLedgers(repoRoot) {
135
+ async function readRederivationLedgers(repoRoot, ledgerDirArg) {
136
+ const ledgerDir = (0, ledger_dir_1.ledgerDirOf)(repoRoot, ledgerDirArg);
139
137
  const out = (0, exports.emptyLedgers)();
140
- const conf = await readOne(repoRoot, "gold-confirmation.json");
138
+ const conf = await readOne(ledgerDir, "gold-confirmation.json");
141
139
  const records = asRecord(conf?.records);
142
140
  if (records) {
143
141
  for (const [p, v] of Object.entries(records)) {
@@ -145,9 +143,7 @@ async function readRederivationLedgers(repoRoot) {
145
143
  const confirmed = asRecord(row?.confirmed);
146
144
  if (!confirmed)
147
145
  continue;
148
- // 🔴 **基準コミットが空の行も持つ**(実装レビュー)。帯は作れない(差分も戻すも
149
- // 指す先が無い)が、行そのものは落とさずに持つ——落とす判断は帯の側
150
- // (`rederivedBandOf`)に 1 か所で寄せる。
146
+ // 🔴 **基準コミットが空の行も持つ**(実装レビュー)。行そのものは落とさずに持つ。
151
147
  // ⚠️ #667 より前は「並びが『人が確認した』事実として使う」ためでもあったが、**並びは
152
148
  // もう帳簿を見ない**(確認済みかどうかは `status` だけで決まる・`orderByActivity`)。
153
149
  // この帳簿の役目は「どの版に戻すか」で、「確認済みかどうか」ではない
@@ -184,7 +180,7 @@ async function readRederivationLedgers(repoRoot) {
184
180
  });
185
181
  }
186
182
  }
187
- const red = await readOne(repoRoot, "gold-rederivation.json");
183
+ const red = await readOne(ledgerDir, "gold-rederivation.json");
188
184
  const redSections = asRecord(red?.sections);
189
185
  if (redSections) {
190
186
  for (const section of Object.values(redSections)) {
@@ -204,7 +200,7 @@ async function readRederivationLedgers(repoRoot) {
204
200
  }
205
201
  }
206
202
  }
207
- const act = await readOne(repoRoot, "gold-activity.json");
203
+ const act = await readOne(ledgerDir, "gold-activity.json");
208
204
  const actSections = asRecord(act?.sections);
209
205
  if (actSections) {
210
206
  for (const section of Object.values(actSections)) {
@@ -229,7 +225,7 @@ async function readRederivationLedgers(repoRoot) {
229
225
  }
230
226
  // 用語の強さ(#790)。**engine の目次(`scripts/memory-index.mjs` の `termStrengthLookup`)の写し**
231
227
  const strengthThen = "用語集は今の並びのまま続けます";
232
- const strength = await readOne(repoRoot, exports.TERM_STRENGTH_FILE, strengthThen);
228
+ const strength = await readOne(ledgerDir, exports.TERM_STRENGTH_FILE, strengthThen);
233
229
  if (strength) {
234
230
  const terms = strength.terms;
235
231
  // 🔴 **engine と同じ判定**(`body.version !== 1`(厳密)・`!body.terms || typeof body.terms !== "object"`)。
@@ -464,9 +460,8 @@ function sameRederivedContent(a, b) {
464
460
  * 「説明文と本文」の指紋。**帳簿の `confirmed.content_sha` と突き合わせる綴り**。
465
461
  *
466
462
  * 🔴 比べる相手は engine が書く 2 つの指紋——`rewritten.content_sha`(機械が書き直した版・#799)と
467
- * `confirmed.content_sha`(人が確認した版・#546)。帯は「今の指紋 = 書き直した版 ≠ 確認した版」の
468
- * ときだけ出る(`rederivedBandOf`)。**綴りがずれると「今 = 書き直した版」が当たらなくなり、
469
- * 帯は出ない側に倒れる**(誤報より無言)。
463
+ * `confirmed.content_sha`(人が確認した版・#546)。画面の帯は #1094 便 e4 で撤去したが、
464
+ * 指紋の綴りは engine との一致テストが見るので残す。
470
465
  *
471
466
  * engine の写し: `cortex-engine/scripts/rederive/content-sha.mjs`・一致の番人:
472
467
  * `cortex-engine/test/rederive-history.test.mjs` の #546 の節(ここを変えたら engine の `VIEWER_SHA` を計算し直す)
@@ -475,99 +470,6 @@ function contentShaOf(raw, options = {}) {
475
470
  const c = comparableContent(raw, options);
476
471
  return node_crypto_1.default.createHash("sha256").update(c.description + "\u0000" + c.body, "utf8").digest("hex");
477
472
  }
478
- /**
479
- * そのレコードが帯の**候補**か(決定表の行 0〜4・中身を読む前の安い行だけ)。候補なら
480
- * `<基準コミット>:<path>`(人が確認した版を git から引くときの key)、候補でなければ null。
481
- *
482
- * 🔴 **ここで候補を絞ってから今の版を読む**——全レコードぶん作業ツリーを読むと、
483
- * 用語が 1400 件ある案件で毎晩 1400 件を読むことになる。git に渡るのはさらにその中の
484
- * 「帳簿に人が確認した版の指紋が無い」記録だけ(`attachRederived`)。
485
- */
486
- function rederivedContentKey(recordPath, status, ledgers) {
487
- if (!isConfirmedStatus(status, recordPath))
488
- return null;
489
- if (!BAND_SECTION_RE.test(recordPath))
490
- return null;
491
- const conf = ledgers.confirmation.get(recordPath);
492
- if (!conf || !conf.commit || conf.deleted || conf.basePath)
493
- return null;
494
- const rw = conf.rewritten;
495
- if (!rw || !rw.contentSha || !rw.at)
496
- return null;
497
- return `${conf.commit}:${recordPath}`;
498
- }
499
- /**
500
- * そのレコードに帯を立てるか(#799 の**決定表**・上から順に評価し、当たった行で終わる)。
501
- *
502
- * | 順 | 条件 | 結果 |
503
- * | --- | --- | --- |
504
- * | 0 | その記録の `status` が `draft`(=「AI生成・未確認」の札が出る) | 出さない(#667) |
505
- * | 1 | path が用語・メンバーのレコードでない | 出さない |
506
- * | 2 | `confirmation` に行が無い/基準コミットが空 | 出さない |
507
- * | 3 | **基準コミットがこの path を消した(`confirmed.deleted`)/基準コミットでの path が今と違う(`confirmed.path`・改名の前の確認)** | **出さない** |
508
- * | 4 | **`rewritten` が無い(古い帳簿)・null・指紋が空・日時が空** | **出さない** |
509
- * | 5 | 今の版が読めない | 出さない(`miss.unreadable` に数える) |
510
- * | 6 | **今の版の指紋 ≠ `rewritten.content_sha`**(後から人や機械が直した) | **出さない** |
511
- * | 7a | 帳簿に人が確認した版の指紋がある: **今の指紋 = その指紋**(元に戻った) | **出さない** |
512
- * | 7b | 指紋が無い(engine が自信を持てない形・#546 より前): git の基準版と比べて `changed` でない | 出さない |
513
- * | 8 | 根拠は、根拠の帳簿の行の `commit` が `rewritten.commit` と同じときだけ出す | 出す/出さない |
514
- *
515
- * 🔴 **人か機械かを画面が見分けない**(#799)。前は `changed_by` の sha ごとに git の素性
516
- *(名義・件名・trailer)を引き、engine の分類の**写し**で機械の書き直しを拾っていた。
517
- * 写しはずれる(App 名義の画面操作を機械と読む日が来る)ので、見分けは engine に 1 か所で
518
- * 寄せ、画面は「今の版が、engine が機械の書き直しと呼んだ版そのものか」だけを指紋で見る。
519
- * 後から人や別の機械が直していれば指紋が合わないので、「戻す」が人の編集を消すことはない。
520
- *
521
- * @param status その記録の `rstatus`(`String()` 済み)。**2 番目の必須引数**にしてある——
522
- * 末尾の任意引数にすると、渡し忘れが「行 0 の門が静かに開く」になる(設計レビュー 🔴3)
523
- * @param nowShaOf 今の版(作業ツリー)の `contentShaOf`。読めなければ null。
524
- * 🔴 **必須引数**——任意にすると、渡し忘れが「指紋を見ずに出す」に静かに倒れる
525
- * @param verdict 行 7b の答えを引く関数(`(recordPath, baseCommit) => ContentVerdict`)。
526
- * 帳簿に人が確認した版の指紋がある記録では**呼ばない**
527
- * @param miss 立たなかった理由を数える器(呼び出し側で 1 つ作って全レコードで共有する)
528
- */
529
- function rederivedBandOf(recordPath, status, ledgers, nowShaOf, verdict, miss) {
530
- // 0〜4. 中身を読む前の門(候補の絞り込みと同じ 1 つの関数に寄せる。2 つに増やすと一致が崩れる)
531
- if (!rederivedContentKey(recordPath, status, ledgers))
532
- return null;
533
- const conf = ledgers.confirmation.get(recordPath);
534
- const rw = conf.rewritten;
535
- // 5. 今の版の指紋
536
- const now = nowShaOf(recordPath);
537
- if (now == null) {
538
- if (miss)
539
- miss.unreadable.add(recordPath);
540
- return null;
541
- }
542
- // 6. 🔴 **今の版が、engine が「機械の書き直し」と呼んだ版そのものか。** 違えば、その後に
543
- // 人か別の機械が直している——そこで帯を出すと「戻す」がその直しごと消す
544
- if (now !== rw.contentSha)
545
- return null;
546
- // 7. 人が確認した版から中身が変わっているか
547
- if (conf.contentSha) {
548
- // 7a. 機械が書き直したが、中身は人が確認した版と同じ(書き戻った・欄だけ触った)
549
- if (now === conf.contentSha)
550
- return null;
551
- }
552
- else if (verdict(recordPath, conf.commit) !== "changed") {
553
- // 7b. 指紋が無い行は git の基準版と比べる(`same` も `unknown` も出さない)
554
- return null;
555
- }
556
- // 8. 根拠の件数は「この書き直しのコミットの記録」から出す(別の夜の数字を並べない)
557
- const red = ledgers.rederivation.get(recordPath);
558
- const evidence = red && red.evidence != null && red.commit && rw.commit && red.commit === rw.commit ? red.evidence : null;
559
- return {
560
- confirmedAt: conf.at,
561
- // 🔴 **券の無い確認では名前を器に入れない**(設計レビュー 🟡4)。画面が出さないだけだと、
562
- // 名前は `site-data` の JSON として全案件のページに配られ続ける。しかも engine の `by` は
563
- // `confirmer || authorName` の合成で、**券が無ければ「ただ触った人」でしかない**
564
- confirmedBy: conf.verified ? conf.by : "",
565
- confirmedVerified: conf.verified,
566
- baseCommit: conf.commit,
567
- changedAt: rw.at,
568
- ...(evidence != null ? { evidence: evidence } : {}),
569
- };
570
- }
571
473
  /**
572
474
  * 用語集の並び(#432 裁定 5-5 → #690 で群分けを撤廃)。**活性度の降順だけで決める。**
573
475
  *
@@ -585,9 +487,8 @@ function rederivedBandOf(recordPath, status, ledgers, nowShaOf, verdict, miss) {
585
487
  * - 🔴 **この並びは「索引の上から N 件だけを AI に読ませる」構想の土台**(engine #544)。
586
488
  * 「上に来るものが重要」でなければ、その仕組みが成り立たない
587
489
  *
588
- * 🔴 **`isConfirmedStatus` は消さない**——このファイルの**印の門(`rederivedContentKey`)と
589
- * 帯の門(`rederivedBandOf`)**が使う。`render.ts` の札は同じ口(`confirmed.ts` の `machineOwned`)を
590
- * **流し込んだ別の実体**で、2 つが一致していることは `rederived-mark-ui.test.mjs` の `[札との一致]` が見る。
490
+ * 🔴 **`isConfirmedStatus` は消さない**——`render.ts` の札は同じ口(`confirmed.ts` の `machineOwned`)を
491
+ * **流し込んだ別の実体**で、2 つが一致していることは `confirmed-label-and-no-band.test.mjs` の `[札との一致]` が見る。
591
492
  * ここで変えるのは**並びだけ**で、札・帯・印・プロンプトに渡る材料は 1 つも変えていない。
592
493
  *
593
494
  * - 活性度を持つレコードが 1 件も無い区画は活性度の段では **1 バイトも触らない**(帳簿が無い案件は従来順。