@takagaki/cortex-decisions-viewer 0.12.117 → 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,8 +40,9 @@ 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;
44
- exports.attachDecisionFollowup = attachDecisionFollowup;
43
+ exports.orderRecordsByLedgers = orderRecordsByLedgers;
44
+ exports.humanTouchedOf = humanTouchedOf;
45
+ exports.attachHumanTouched = attachHumanTouched;
45
46
  exports.rawFrontmatterDate = rawFrontmatterDate;
46
47
  exports.attachSupersedes = attachSupersedes;
47
48
  exports.readGoldProposals = readGoldProposals;
@@ -110,7 +111,10 @@ function takeProposals(filePath) {
110
111
  const external_id_1 = require("./external-id");
111
112
  const home_project_1 = require("./home-project");
112
113
  const supersedes_1 = require("./supersedes");
114
+ const confirmed_1 = require("./confirmed");
115
+ const minify_1 = require("./minify");
113
116
  const render_1 = require("./render");
117
+ const ledger_dir_1 = require("./ledger-dir");
114
118
  /**
115
119
  * この画面を作ったパッケージの版。**package.json を唯一の出どころにする。**
116
120
  * 別に持つと、公開した版と画面に出る版がずれる(しかも気づけない)。
@@ -703,117 +707,57 @@ async function readFleetSources(repoRoot) {
703
707
  return { externalSources, slackWorkspaces, internalSources, pipelines, capabilities, fleetGeneratedAt, secretSources, secretSpaces, checks, githubOwners, backlogTarget, tools };
704
708
  }
705
709
  /**
706
- * 区画のレコードに帯を載せ、用語集の並びを活性度にする(#665 便 12b)。
710
+ * 区画のレコードを活性度・強さの順に並べる(用語集だけが動く・#665 便 12b → #1094 便 e4 で帯を外した)。
707
711
  *
708
- * 🔴 **帳簿が無い案件では、渡された配列をそのまま返す**(帯のキーも付かない・並びも従来のまま)。
709
- * 44 案件のうち帳簿を持つのは栓 `rederive` を入れた案件だけなので、ここが「何もしない」に
710
- * 倒れることが**既定の経路**である。
711
- *
712
- * 🔴 **突き合わせはビルド時にここで閉じる**(`proposals` と同じ)。画面側でパスを組み立て直すと、
713
- * `repoPathOf` が `editUrl` 由来なので **`repoBaseUrl` が無い案件で帯が黙って消える**。
712
+ * 🔴 **帳簿が無い案件では、渡された配列をそのまま返す**(並びも従来のまま)。
714
713
  */
715
- async function attachRederived(records, recordsDir, ctx, nowIso,
716
- // 番人テストが「git に渡った key」を数えるための差し替え口。本番は常に既定の `readBlobsAt`
717
- readBlobs = repo_1.readBlobsAt) {
714
+ function orderRecordsByLedgers(records, recordsDir, ctx, nowIso) {
718
715
  if (!ctx)
719
716
  return records;
720
- // 🔴 **レコードのパスは `repoRoot` から作る**(`toRepoRelative` の `process.cwd()` 起点で
721
- // 作らない)。帳簿のキーは常にリポジトリルート相対(`Cortex/Glossary/records/…`)なので、
722
- // Decisions ディレクトリから CLI を叩いた案件では cwd 起点だと 1 件も突き合わない
723
717
  const recordsRel = path.relative(ctx.repoRoot, recordsDir).split(path.sep).join("/");
724
718
  const pathOf = (r) => (recordsRel ? `${recordsRel}/${r.fileName}` : r.fileName);
725
- // 🔴 **「確認済み」は `status` だけで決まる**(#667)。帯も並びも同じ `rstatus` を見る
726
- // ——ここで別のものを渡すと、画面の札と食い違う(「未確認」の札が付いた記録に帯が出る)
727
- const statusOf = (r) => r.rstatus;
728
- // 決定表の行 5〜7 の材料。**帯の候補になったレコードのぶんだけ**今の版を読み、指紋を作る。
729
- // 帳簿に人が確認した版の指紋が無い候補だけ、`<基準コミット>:<path>` をまとめて 1 回 git から引く。
730
- // 🔴 **キーが 0 件の区画では git を起こさない**(この関数は区画ごとに呼ばれる)
731
- const keyOf = new Map();
732
- // 今の版は**作業ツリーから読む**——画面が描いているのはこのファイルであって HEAD ではない
733
- const now = new Map();
734
- const nowSha = new Map();
735
- for (const r of records) {
736
- const key = (0, rederivation_1.rederivedContentKey)(pathOf(r), statusOf(r), ctx.ledgers);
737
- if (!key)
738
- continue;
739
- const p = pathOf(r);
740
- let nowRaw;
741
- try {
742
- nowRaw = await node_fs_1.promises.readFile(path.join(ctx.repoRoot, p), "utf8");
743
- }
744
- catch {
745
- // 読めなければ「読めなかった」側(`rederivedBandOf` が帯を出さずに数える)
746
- continue;
747
- }
748
- now.set(p, nowRaw);
749
- const sha = (0, rederivation_1.contentShaOf)(nowRaw);
750
- nowSha.set(p, sha);
751
- const conf = ctx.ledgers.confirmation.get(p);
752
- // 🔴 **今の版が機械の書き直しの版でなければ git を読まない**(帯は出ないと決まっている)。
753
- // 人が確認した版の指紋が帳簿にあれば、それで決まるので git を読まない(engine #546)
754
- if (sha !== conf?.rewritten?.contentSha)
755
- continue;
756
- if (conf.contentSha) {
757
- if (ctx.blobs)
758
- ctx.blobs.skipped++;
759
- continue;
760
- }
761
- keyOf.set(r, key);
762
- }
763
- if (ctx.blobs)
764
- ctx.blobs.read += keyOf.size;
765
- const bases = keyOf.size ? readBlobs(ctx.repoRoot, [...keyOf.values()]) : new Map();
766
- const verdict = (p, baseCommit) => {
767
- const nowRaw = now.get(p);
768
- const baseRaw = bases.get(`${baseCommit}:${p}`);
769
- if (nowRaw == null || baseRaw == null) {
770
- // 浅い clone の窓の外・確認の後に改名された・基準コミットにこの path が無い。
771
- // **帯は出さない**(誤報より無言)が、静かに消える経路なのでログの器に数える
772
- ctx.miss.unreadable.add(p);
773
- return "unknown";
774
- }
775
- return (0, rederivation_1.sameRederivedContent)(baseRaw, nowRaw) ? "same" : "changed";
776
- };
777
- const nowShaOf = (p) => nowSha.get(p) ?? null;
778
- for (const r of records) {
779
- const band = (0, rederivation_1.rederivedBandOf)(pathOf(r), statusOf(r), ctx.ledgers, nowShaOf, verdict, ctx.miss);
780
- // **印が立たないレコードにはキーごと付けない**(空の器を通さない)
781
- if (band)
782
- r.rederived = band;
783
- }
784
- 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);
785
720
  }
786
721
  /**
787
- * 決定に「その後」(`.cortex/gold-decision-followup.json`・#705)を載せる。
722
+ * 記録に人の手があるか(cortex-engine #1094 段 4・削除の確認の文面を替える材料)。
723
+ *
724
+ * 次のどちらかで真:
725
+ * 1. status が人の値(口の `humanOwned`・区画の語彙。旧い記録の「確認済み」)
726
+ * 2. 確認の帳簿(`.cortex/gold-confirmation.json`)にその path の `confirmed` の行がある(消した行を除く)
788
727
  *
789
- * 🔴 **突き合わせはビルド時にここで閉じる**(`attachRederived` と同じ)。path は
790
- * **`repoRoot` 起点**で作る——帳簿の鍵は常にリポジトリルート相対(`Cortex/Decisions/records/…`)。
791
- * 画面に出す状態の結び付きが 1 件も無い決定には**キーごと付けない**(`shownFollowupOf`)。
728
+ * 🔴 **ビルドの git は使わない**(確定版 0 章 7)。cortex-bot の夜のコミットを人と数え、浅い clone では
729
+ * 全部が人になるため。engine の人の手の表がビューアに届く段 5 で差し替える。
730
+ * 取りこぼしの代償は「確認の一言が出ない」だけ(消せる範囲は変わらない)。
792
731
  *
793
- * @returns 帳簿の行のうち、どの決定にも突き合わなかった path(ビルドログに 1 行出す材料)
732
+ * @param p リポジトリルート相対の path(`Cortex/<区画>/records/…`)。帳簿の鍵は NFC なので NFC でも引く
733
+ * @param status その記録の `rstatus`(`String()` 済み)
794
734
  */
795
- function attachDecisionFollowup(decisions, recordsDir, repoRoot, ledger) {
735
+ function humanTouchedOf(p, status, confirmation) {
736
+ if ((0, confirmed_1.humanOwned)(p, status))
737
+ return true;
738
+ const row = confirmation.get(p) ?? confirmation.get(p.normalize("NFC"));
739
+ return !!row && row.deleted !== true;
740
+ }
741
+ /**
742
+ * 区画の記録に人の手の印(`humanTouched: true`)を載せる。**人の手が無い記録にはキーごと付けない**
743
+ * (今の SiteData の形を保つ)。path は `repoRoot` 起点で作る(帳簿の鍵はリポジトリルート相対)。
744
+ * 置き換えの逆引き(`attachSupersedes`)より前に呼ぶ。
745
+ */
746
+ function attachHumanTouched(records, recordsDir, repoRoot, confirmation) {
796
747
  const recordsRel = path.relative(repoRoot, recordsDir).split(path.sep).join("/");
797
- const matched = new Set();
798
- for (const d of decisions) {
799
- // 帳簿の鍵(git 由来)は NFC。readdir が NFD で返す環境でも突き合うように揃える
800
- const p = (recordsRel ? `${recordsRel}/${d.fileName}` : d.fileName).normalize("NFC");
801
- const row = ledger.get(p);
802
- if (!row)
803
- continue;
804
- matched.add(p);
805
- const shown = (0, rederivation_1.shownFollowupOf)(row);
806
- if (shown)
807
- d.followup = shown;
748
+ for (const r of records) {
749
+ const p = recordsRel ? `${recordsRel}/${r.fileName}` : r.fileName;
750
+ if (humanTouchedOf(p, r.rstatus, confirmation))
751
+ r.humanTouched = true;
808
752
  }
809
- return [...ledger.keys()].filter((k) => !matched.has(k.normalize("NFC")));
810
753
  }
811
754
  /**
812
755
  * 決定に置き換えの逆引き(置き換え先・置き換えた決定)を載せる(cortex-engine #699 F3・A-1')。
813
756
  *
814
757
  * 🔴 **逆引きは `supersedes.ts`(engine `scripts/gold/supersedes-index.mjs` の写し)だけで組む**。
815
758
  * 材料は新しい側の `relations[supersedes]` だけで、古い側の `superseded_by`(夜間の精製が書く写し)は
816
- * 読まない。確認済み=新しい側の `status` が `active`。
759
+ * 読まない。確定=古い側に人の手が無い(status が人の値でない)か、新しい側を人が確認した(cortex-engine #1094 段 4)。
760
+ * 口には記録の path(`Cortex/Decisions/records/…`・`repoRoot` 起点)を渡す。
817
761
  *
818
762
  * 置き換えが無い決定には**キーごと付けない**(今までと同じ SiteData)
819
763
  */
@@ -835,9 +779,11 @@ function rawFrontmatterDate(raw) {
835
779
  v = v.replace(/\s+#.*$/, "").trim();
836
780
  return v;
837
781
  }
838
- function attachSupersedes(decisions, rawDateOf = (d) => d.fm.date) {
782
+ function attachSupersedes(decisions, rawDateOf = (d) => d.fm.date, pathOf = () => undefined) {
839
783
  const input = decisions.map((d) => ({
840
784
  id: d.id,
785
+ // 口(`humanOwned`)に区画を伝える path。無ければ写しが決定の区画で聞く
786
+ path: pathOf(d),
841
787
  // 🔴 **frontmatter に書かれたままの date の文字列**(`rawFrontmatterDate`)。gray-matter は時刻・時差つきの
842
788
  // date を Date に読み、sanitizeFm は UTC の日付にするので、`2026-07-01T01:00:00+09:00` が 6/30 にずれる。
843
789
  // engine は日付を Date にしない読み方(CORE_SCHEMA)で書かれた文字列の先頭 8 桁を使う。
@@ -897,11 +843,16 @@ function attachSupersedes(decisions, rawDateOf = (d) => d.fm.date) {
897
843
  function optionalText(v) {
898
844
  return typeof v === "string" && v.trim() !== "" ? v : "";
899
845
  }
900
- 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) {
901
852
  const empty = new Map();
902
853
  let json;
903
854
  try {
904
- 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");
905
856
  json = JSON.parse(raw);
906
857
  }
907
858
  catch (e) {
@@ -986,6 +937,11 @@ async function readGoldProposals(repoRoot) {
986
937
  // この 1 欄だけ覆した(決定ログ 20260925-699-f7b.md)
987
938
  if (row.decide === "portal")
988
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;
989
945
  const list = byPath.get(recordPath);
990
946
  if (list)
991
947
  list.push(proposal);
@@ -1514,7 +1470,7 @@ async function buildTargetUrlMap(repoRoot, repoBaseUrl, branch) {
1514
1470
  * 🔴 **`buildTargetUrlMap` は触らない**(移る間だけ残す表・詳細画面の飛び先の正本)。ここは名前と、
1515
1471
  * 写しのパス → 根拠の ID の対応だけを持つ。資料の題名を Drive の鍵にも載せるために、表の URL を読むだけ
1516
1472
  */
1517
- async function buildEvidenceBook(repoRoot, targetUrls, externalSources = []) {
1473
+ async function buildEvidenceBook(repoRoot, targetUrls, externalSources = [], ledgerDir) {
1518
1474
  const book = (0, evidence_names_1.emptyEvidenceBook)();
1519
1475
  const rel = (file) => path.relative(repoRoot, file).split(path.sep).join("/");
1520
1476
  const heading = (text) => text.match(/^#\s+(.+?)\s*$/m)?.[1] ?? null;
@@ -1529,7 +1485,7 @@ async function buildEvidenceBook(repoRoot, targetUrls, externalSources = []) {
1529
1485
  // 片付けで移した議事録のリンク(URL の鍵 → 「定例名 日付」・cortex-engine #968 R-6・A-R6-3)。表が無い・読めない案件は今どおり
1530
1486
  // (種類の既定の名前)。資料より先に載せる(同じ鍵に資料の題名が先に付かないように)
1531
1487
  try {
1532
- 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"));
1533
1489
  for (const [url, name] of (0, evidence_names_1.movedMeetingNamesOf)(table))
1534
1490
  putName(foldKeyOf(url), name);
1535
1491
  }
@@ -2410,43 +2366,34 @@ async function build(opts) {
2410
2366
  // 解決するので、あとから作ると空の表・空の実在判定が渡り、**実在するパスまで
2411
2367
  // 「リンクにしない」に倒れて全滅する**(以前は順序が逆だった)。
2412
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);
2413
2371
  const targetUrls = knowledgeRoot
2414
2372
  ? await buildTargetUrlMap(repoRoot, repo.baseUrl, repo.branch)
2415
2373
  : new Map();
2416
2374
  // fleet-status.json は 1 回だけ読む(読み手が警告を出すので、2 回読むと警告が 2 行になる)。
2417
2375
  // 名前の帳(チャンネル名)と、下の「データソースと自動化」で同じものを使う
2418
2376
  const fleet = await readFleetSources(repoRoot);
2419
- 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)();
2420
2378
  const refs = createRefResolver({ repoRoot, targetUrls, repoBaseUrl: repo.baseUrl, branch: repo.branch, book: evidenceBook });
2421
2379
  // AIからの更新提案。**レコードを組み立てる前に読む**——各レコードが自分の分を
2422
2380
  // takeProposals で引き取るので、後から読んでも載せる先が既に閉じている。
2423
2381
  // 同一プロセスで2回ビルドしても前回の値が残らないよう、毎回入れ替える
2424
- PROPOSALS_BY_PATH = await readGoldProposals(repoRoot);
2382
+ PROPOSALS_BY_PATH = await readGoldProposals(repoRoot, ledgerDir);
2425
2383
  PROPOSAL_PATHS_MATCHED = new Set();
2426
- // 再導出の 3 帳簿(#665 便 12b)と用語の強さ(#790)。**帳簿が無い案件では帯も並び替えも作らない**。
2427
- // 人か機械かの見分けは engine が帳簿(`rewritten`)に書くので、画面は git の素性を読まない(#799)
2428
- //
2384
+ // 用語集の並びの帳簿(活性度・強さ #790)。**帳簿が無い案件では並び替えを作らない**。
2429
2385
  // 🔴 **強さ(`state` が on のときだけ中身が入る)だけの案件でも器を作る**——作らないと、活性度の帳簿を
2430
- // 持たない案件で用語集が目次(engine の `termRows`)と違う順になる。帯は `confirmation` の行が無ければ
2431
- // 1 件も立たない(`rederivedContentKey` の門)ので、器を作っても帯・印は変わらない
2432
- const ledgers = await (0, rederivation_1.readRederivationLedgers)(repoRoot);
2433
- const rederive = ledgers.confirmation.size || ledgers.activity.size || ledgers.strength.size
2434
- ? {
2435
- ledgers: ledgers,
2436
- // 🔴 **`repoRoot` で走らせる**(`collectAiChangeHistory` の `process.cwd()` に倣わない)。
2437
- // あちらは「リポジトリルートで実行すること」が前提の既存の制約だが、こちらは repoRoot を
2438
- // 既に知っている(帳簿をそこから読んでいる)。Decisions ディレクトリから CLI を叩いた
2439
- // ときも今の版と基準版が引け、帯が黙って消えない
2440
- repoRoot: repoRoot,
2441
- miss: { unreadable: new Set() },
2442
- blobs: { read: 0, skipped: 0 },
2443
- }
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 }
2444
2391
  : null;
2445
2392
  // 関与の帳簿(便 14b・#709)。関係者の一覧を目次と同じ「関わりの多い順」に並べる材料。
2446
2393
  // **読めない・使えないなら `null`** で、関係者の並びは今までと 1 バイトも同じ
2447
- const engagement = await (0, engagement_1.readEngagementLedger)(repoRoot);
2394
+ const engagement = await (0, engagement_1.readEngagementLedger)(repoRoot, ledgerDir);
2448
2395
  // 目次と同じ順位(engine #551)。**読めないなら `null`** で、つながり画面の点は今までどおり一律
2449
- const memoryRanking = await (0, ranking_1.readMemoryRanking)(repoRoot);
2396
+ const memoryRanking = await (0, ranking_1.readMemoryRanking)(repoRoot, ledgerDir);
2450
2397
  // 記録の id → リポジトリルート相対のパス(順位のファイルの鍵と突き合わせる。`repoRoot` 起点・cwd 起点にしない)
2451
2398
  const recordRelPaths = new Map();
2452
2399
  const relOf = (dir, fileName) => {
@@ -2465,19 +2412,10 @@ async function build(opts) {
2465
2412
  if (rd !== undefined)
2466
2413
  rawDates.set(rec, rd);
2467
2414
  }
2415
+ // 人の手(削除の確認の文面を替える材料・cortex-engine #1094 段 4)。**置き換えより前に・repoRoot で**
2416
+ attachHumanTouched(decisions, dirAbs, repoRoot, ledgers.confirmation);
2468
2417
  // 置き換えの逆引き(#699 F3)。**置き換えの無い案件では 1 件も付かない**
2469
- attachSupersedes(decisions, (d) => rawDates.get(d) ?? d.fm.date);
2470
- // 決定の「その後」(#705)。**帳簿を持たない案件では 1 件も付かず、絞り込みの印も立たない**
2471
- // (`decisionFollowup` はキーごと無い=今までと 1 バイトも同じ画面)
2472
- const followupLedger = await (0, rederivation_1.readDecisionFollowup)(repoRoot);
2473
- if (followupLedger) {
2474
- const unmatchedFollowup = attachDecisionFollowup(decisions, dirAbs, repoRoot, followupLedger);
2475
- // **突き合わなかった行を 1 行残す**(path が 1 文字違うだけで画面から静かに消えるので)
2476
- if (unmatchedFollowup.length) {
2477
- console.warn(`gold-decision-followup.json: 決定に突き合わない行が ${unmatchedFollowup.length} 件ありました(表示しません): ` +
2478
- unmatchedFollowup.slice(0, 3).join(", ") + (unmatchedFollowup.length > 3 ? " ほか" : ""));
2479
- }
2480
- }
2418
+ attachSupersedes(decisions, (d) => rawDates.get(d) ?? d.fm.date, (d) => relOf(dirAbs, d.fileName));
2481
2419
  let home = null;
2482
2420
  let homeGrounding;
2483
2421
  let homeUpdatedAt;
@@ -2517,9 +2455,10 @@ async function build(opts) {
2517
2455
  continue;
2518
2456
  const recordsDir = path.join(knowledgeRoot, e.name, "records");
2519
2457
  const records = await loadGenericRecords(recordsDir, exclude, repo.baseUrl, repo.branch);
2520
- // 「人が確認した版から変わりました」の帯と、用語集の並び(#665 便 12b)。
2521
- // **帳簿を持たない案件では 1 件も付かず、並びもそのまま**(下の attachRederived)
2522
- const rederived = await attachRederived(records, recordsDir, rederive, opts.generatedAt);
2458
+ // 人の手(削除の確認の文面を替える材料・cortex-engine #1094 段 4)
2459
+ attachHumanTouched(records, recordsDir, repoRoot, ledgers.confirmation);
2460
+ // 用語集の並び(活性度・強さ)。**帳簿を持たない案件では並びもそのまま**
2461
+ const rederived = orderRecordsByLedgers(records, recordsDir, rederive, opts.generatedAt);
2523
2462
  // 関係者だけ、関与の多い順(engine の目次と同じ `order`・便 14b)。用語集の並び
2524
2463
  // (`orderByActivity`)とは帳簿も式も別で、区画が重ならないので順番に当てても干渉しない
2525
2464
  const ordered = e.name === "Members"
@@ -2554,26 +2493,22 @@ async function build(opts) {
2554
2493
  // **突き合わなかった提案の件数をビルドログに出す。** path が1文字違うだけで
2555
2494
  // 画面から静かに消えるので、出さないと「提案が出ない」の原因に辿り着けない。
2556
2495
  // 表示からは落とす(どのレコードの話か分からない提案は、出しても行動につながらない)
2557
- // 🔴 **帯が静かに消える経路を 1 行残す。** 今の版か、人が確認した版の中身を
2558
- // 引けないと「説明文が変わったか」を言えないので、帯は出さない(誤報より無言)。
2559
- // 出さないと「印が出ない」の原因を「中身が変わっていない」と切り分けられない
2560
- if (rederive && rederive.miss.unreadable.size) {
2561
- const paths = [...rederive.miss.unreadable];
2562
- console.warn(`gold-confirmation.json: 人が確認した版か今の版の中身を ${paths.length} 件引けませんでした` +
2563
- `(浅い clone か、確認の後に改名・削除された。変わったかを言えないので帯は出しません): ` +
2564
- paths.slice(0, 3).join(", ") + (paths.length > 3 ? " ほか" : ""));
2565
- }
2566
- // 🔴 **指紋(engine #546)が効いているかを 1 行残す。** 一致した記録は git を読まないので、
2567
- // 「読んだ件数」が候補の数より減っていれば効いている。警告ではないので console.log
2568
- if (rederive?.blobs && rederive.blobs.read + rederive.blobs.skipped > 0) {
2569
- console.log(`gold-confirmation.json: 人が確認した版の中身を git から ${rederive.blobs.read} 件読みました` +
2570
- `(中身の指紋で決めた ${rederive.blobs.skipped} 件は読んでいません)`);
2571
- }
2572
2496
  const unmatched = [...PROPOSALS_BY_PATH.keys()].filter((k) => !PROPOSAL_PATHS_MATCHED.has(k));
2573
2497
  if (unmatched.length) {
2574
2498
  console.warn(`gold-proposals.json: レコードに突き合わない提案が ${unmatched.length} 件ありました(表示しません): ` +
2575
2499
  unmatched.slice(0, 5).join(", ") + (unmatched.length > 5 ? " ほか" : ""));
2576
2500
  }
2501
+ // 画面に出さなくなった提案(名寄せ以外・#1094 35d)の件数。**人の操作は足さず、ログに 1 行だけ残す**
2502
+ // (kind が identity でない提案は、記録に載っていても画面のどこにも出ない)
2503
+ let hiddenProposals = 0;
2504
+ for (const [k, list] of PROPOSALS_BY_PATH) {
2505
+ if (!PROPOSAL_PATHS_MATCHED.has(k))
2506
+ continue;
2507
+ hiddenProposals += list.filter((p) => p.kind !== "identity").length;
2508
+ }
2509
+ if (hiddenProposals) {
2510
+ console.warn(`gold-proposals.json: 名寄せ以外の提案が ${hiddenProposals} 件あります(画面には出しません)`);
2511
+ }
2577
2512
  const graph = buildGraph(decisions, home, graphSections, targetUrls, repo.baseUrl, evidenceBook);
2578
2513
  computeLayout(graph); // 各ノードに x/y を書き込む(クライアントはこれを初期位置に使いウォームアップ省略)
2579
2514
  // 点の明るさと大きさ(engine #551)。順位のファイルが読めない案件では 1 点も付かない(今までと同じ画面)
@@ -2593,9 +2528,9 @@ async function build(opts) {
2593
2528
  // ストーリーの日々の動き(#901 V2-5)。帳簿で on の日のファイルが無ければ null(キーごと出さない)
2594
2529
  const story = await (0, story_1.readStory)(repoRoot, renderGroundingSection, (p) => repo.baseUrl ? `${repo.baseUrl}/edit/${repo.branch}/${encodePath(p)}` : null,
2595
2530
  // 動きのない日を埋める終点=ビルドの時刻の日本時間の日付(時計を注入できる)
2596
- storyToday);
2531
+ storyToday, ledgerDir);
2597
2532
  // 週・月のあらすじ(#901)。週・月のファイルが無ければ null(キーごと出さない=今までと同じ画面)
2598
- 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);
2599
2534
  // 案件アイコン(#348)。Cortex構成でなければ undefined(旧レイアウトには置き場が無い)
2600
2535
  const projectIcon = await findProjectIcon(knowledgeRoot);
2601
2536
  const site = {
@@ -2622,7 +2557,6 @@ async function build(opts) {
2622
2557
  }
2623
2558
  : undefined,
2624
2559
  // 帳簿を読めた案件だけ true(無い・壊れ・版違いはキーごと出さない)
2625
- decisionFollowup: followupLedger ? true : undefined,
2626
2560
  meetings,
2627
2561
  // **URLとトークンが両方そろったときだけ出す。** 片方だけでは貼れるURLを作れないので、
2628
2562
  // 手順だけが出て「専用URLが空」という一番混乱する形になる(intake と同じ扱い)
@@ -2702,7 +2636,10 @@ async function build(opts) {
2702
2636
  // 配置側(cortex-engine の build-viewer.yml)の責務: aws s3 sync --delete が
2703
2637
  // 古いハッシュのファイルを消すこと、index.html に Cache-Control: no-cache を
2704
2638
  // 付けること(assets/ は内容ハッシュ名なので長期キャッシュでよい)。
2705
- const assetSources = (0, render_1.viewerAssetSources)();
2639
+ // 🔴 **配る JS は縮めてから名前を決める**(ハッシュは縮めた後の中身から。コメント由来の実名を配信物に載せない・#731 便 4)。
2640
+ // `viewerAssetSources()` の素の JS は埋め込み形(renderSite)とテストが読むので縮めない
2641
+ const rawAssetSources = (0, render_1.viewerAssetSources)();
2642
+ const assetSources = { css: rawAssetSources.css, js: (0, minify_1.minifyClientJs)(rawAssetSources.js) };
2706
2643
  const assets = (0, render_1.viewerAssetNames)(assetSources);
2707
2644
  await node_fs_1.promises.mkdir(path.join(outAbs, "assets"), { recursive: true });
2708
2645
  await node_fs_1.promises.writeFile(path.join(outAbs, assets.css), assetSources.css, "utf8");
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/minify.js ADDED
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MINIFY_OPTIONS = void 0;
4
+ exports.minifyClientJs = minifyClientJs;
5
+ const esbuild_1 = require("esbuild");
6
+ /**
7
+ * 画面の JS(`assets/viewer.<hash>.js`)を配る前に縮める(cortex-engine #731 便 4)。
8
+ *
9
+ * 配る JS はテンプレート文字列として書いた約 15,000 行で、コメントに案件名・社内の事情が入っている。
10
+ * そのまま書き出すと、お客様の組織の画面のソースにも読める形で載る。ここで**コメントと余分な空白を落とす**。
11
+ *
12
+ * 🔴 **空白とコメントだけを落とす(名前の短縮・構文の書き換えはしない)。** `.toString()` で埋め込んだ関数や
13
+ * 名前に頼る所を壊さないため。`charset: "utf8"` は日本語の文言を `\uXXXX` に展開させない(既定の ascii だと
14
+ * 読めず、かえって大きくなる)。`legalComments: "none"` は `/*!` 系も残さない。
15
+ *
16
+ * ⚠️ 番人(`test/shipped-js-minified.test.mjs`)は同じオプションを**わざと直書き**している(ここを再利用すると、縮めを恒等にされても緑になる)。
17
+ * ここを変えたら番人の直書きも同じにすること。
18
+ *
19
+ * 🔴 **失敗しても素のコメント入りで配らない**(例外のまま build を赤にする)。縮めに失敗した画面を黙って配ると、
20
+ * 番人(`test/shipped-js-minified.test.mjs`)の外で実名が載る。
21
+ */
22
+ exports.MINIFY_OPTIONS = {
23
+ minifyWhitespace: true,
24
+ charset: "utf8",
25
+ legalComments: "none",
26
+ };
27
+ function minifyClientJs(js) {
28
+ return (0, esbuild_1.transformSync)(js, exports.MINIFY_OPTIONS).code;
29
+ }
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
  /**