@takagaki/cortex-decisions-viewer 0.12.146 → 0.12.150

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
@@ -43,7 +43,6 @@ exports.readFleetSources = readFleetSources;
43
43
  exports.orderRecordsByLedgers = orderRecordsByLedgers;
44
44
  exports.rawFrontmatterDate = rawFrontmatterDate;
45
45
  exports.attachSupersedes = attachSupersedes;
46
- exports.readGoldProposals = readGoldProposals;
47
46
  exports.buildHomeGrounding = buildHomeGrounding;
48
47
  exports.collectMeetings = collectMeetings;
49
48
  exports.findBacklogDir = findBacklogDir;
@@ -78,28 +77,6 @@ const story_1 = require("./story");
78
77
  const ranking_1 = require("./ranking");
79
78
  const hands_1 = require("./hands");
80
79
  const rederivation_1 = require("./rederivation");
81
- // AIからの更新提案(`.cortex/gold-proposals.json` 由来)を、レコードのパスで引ける形にしたもの。
82
- // **レコードを組み立てるその場で、
83
- // リポジトリルート相対のパスを鍵に引く」もので、引き回すと parseDecision / parseGeneric の
84
- // 呼び出し側を全部触ることになる。build() の冒頭で毎回入れ替える(同一プロセスで
85
- // 2回ビルドしても前回の値が残らないように)。
86
- let PROPOSALS_BY_PATH = new Map();
87
- // 実際にレコードへ載せられたパス。**載らなかったぶんを数えるために要る**——
88
- // 提案の path が1文字違うだけで画面から静かに消えるので、件数をビルドログに出す
89
- let PROPOSAL_PATHS_MATCHED = new Set();
90
- /**
91
- * このレコードに載せる提案を取り出し、「突き合わせできた」印を付ける。
92
- *
93
- * **提案が無ければ undefined を返す**(空配列にしない)。空配列だと画面側が
94
- * 「提案0件」の節を出す・バッジを出す、という判定を書き分けねばならなくなる。
95
- */
96
- function takeProposals(filePath) {
97
- const hit = PROPOSALS_BY_PATH.get(filePath);
98
- if (!hit)
99
- return undefined;
100
- PROPOSAL_PATHS_MATCHED.add(filePath);
101
- return hit;
102
- }
103
80
  const external_id_1 = require("./external-id");
104
81
  const home_project_1 = require("./home-project");
105
82
  const supersedes_1 = require("./supersedes");
@@ -127,6 +104,27 @@ const DEFAULT_EXCLUDE = ["README.md", "template.md"];
127
104
  * `supersedes` の写しで、描くと同じ置き換えが逆向きにもう 1 本出る。frontmatter のパネルには今までどおり出る
128
105
  */
129
106
  exports.MIRROR_RELS = new Set(["superseded_by"]);
107
+ /**
108
+ * ストーリーの名前のリンクの材料(`story.screen-name-links`)。関係者・用語・ルールの記録ごとに、表示名と別名・同義語を集める。
109
+ * 画面に載る記録(`sections` に残った物)だけなので、取り下げ・削除された記録の id は表に無く、リンクにならない。
110
+ * 関係者の題は姓名の間に空白があることがあるので、空白を除いた形も名前に足す
111
+ */
112
+ function storyNameSources(sections) {
113
+ const out = new Map();
114
+ const list = (v) => (Array.isArray(v) ? v.filter((x) => typeof x === "string") : []);
115
+ for (const sec of sections) {
116
+ for (const r of sec.records) {
117
+ const kind = r.id.split(":")[0];
118
+ if (kind !== "member" && kind !== "term" && kind !== "rule")
119
+ continue;
120
+ const names = [r.title, ...list(r.fm.aliases), ...list(r.fm.synonyms)];
121
+ if (kind === "member")
122
+ names.push(r.title.replace(/\s+/g, ""));
123
+ out.set(r.id, { id: r.id, names });
124
+ }
125
+ }
126
+ return out;
127
+ }
130
128
  /** frontmatterのrelationsを安全に正規化する */
131
129
  function toRelations(value) {
132
130
  if (!Array.isArray(value))
@@ -789,130 +787,6 @@ function attachSupersedes(decisions, rawDateOf = (d) => d.fm.date, pathOf = () =
789
787
  d.supersedesIds = replaced;
790
788
  }
791
789
  }
792
- /**
793
- * AIからの更新提案(`.cortex/gold-proposals.json`)を読む。
794
- *
795
- * 夜間パイプライン(エンジン側)が書いたものを、レコードのパスで引ける形にして返す。
796
- *
797
- * **読み方は readFleetSources と同じ流儀**——フィールドは明示で列挙し、素通しはしない。
798
- * この JSON はエンジン側が単独で版を上げるので、**画面が知らないキーが必ず先に入る**。
799
- * 素通しにすると、公開範囲フィルタを通していない生の断片(内部限定の引用など)が
800
- * 知らないうちに顧客の画面へ載りうる。**知らないものは載せない**が唯一の安全側。
801
- *
802
- * **無い・壊れているときは提案ゼロで正常継続する。** 提案はあくまで付加情報で、
803
- * これが読めないことでレコードの表示まで止めるのは釣り合わない。
804
- */
805
- /** 文字列で中身があるときだけ返す(数・オブジェクトを String() して載せない) */
806
- function optionalText(v) {
807
- return typeof v === "string" && v.trim() !== "" ? v : "";
808
- }
809
- /**
810
- * engine の `normalized_match`(表記ゆれ)のカードの理由の文に入っている句。画面から外す印(#1094 便 35a-1)。
811
- * 🔴 engine の `scripts/silver/build.mjs` の `WHY_NOT_DECIDED.normalized_match` の文を変えるときはここも揃える(片方だけだと静かに外れなくなる)
812
- */
813
- const NORMALIZE_CARD_WHY = /区切り・姓と名の順・会社名を除くと同じ名前/;
814
- async function readGoldProposals(repoRoot, ledgerDir) {
815
- const empty = new Map();
816
- let json;
817
- try {
818
- const raw = await node_fs_1.promises.readFile(path.join((0, ledger_dir_1.ledgerDirOf)(repoRoot, ledgerDir), "gold-proposals.json"), "utf8");
819
- json = JSON.parse(raw);
820
- }
821
- catch (e) {
822
- // 「まだ夜間パイプラインが動いていない」が既定の状態なので、無いことは警告しない。
823
- // **壊れている(JSON として読めない)ときだけ**ビルドログに残す——静かに0件になると、
824
- // 提案が出ない理由が「作られていない」のか「読めていない」のか切り分けられない
825
- if (e?.code !== "ENOENT") {
826
- console.warn(`gold-proposals.json を読めませんでした(提案は0件として続けます): ${String(e)}`);
827
- }
828
- return empty;
829
- }
830
- if (json == null || typeof json !== "object" || Array.isArray(json)) {
831
- console.warn("gold-proposals.json の形式が想定と違います(提案は0件として続けます)");
832
- return empty;
833
- }
834
- const obj = json;
835
- // **版が違うものは読まない。** エンジン側が構造を変えたときに、古い読み手が
836
- // 部分的に解釈して中途半端な表示を出すより、出さないほうがよい
837
- if (obj.version !== 1) {
838
- console.warn(`gold-proposals.json の version が未対応です(提案は0件として続けます): ${String(obj.version)}`);
839
- return empty;
840
- }
841
- // **配列でないときも1行残す。** 他の壊れ方(JSON として読めない・version 違い)は
842
- // 警告を出しており、ここだけ黙ると「読めたが0件」と区別が付かない
843
- if (!Array.isArray(obj.proposals)) {
844
- console.warn(`gold-proposals.json の proposals が配列ではありません(提案は0件として続けます): ${typeof obj.proposals}`);
845
- return empty;
846
- }
847
- const byPath = new Map();
848
- for (const p of obj.proposals) {
849
- if (p == null || typeof p !== "object" || Array.isArray(p))
850
- continue;
851
- const row = p;
852
- // path はレコードとの突き合わせ鍵。**無ければどこにも出せない**ので捨てる
853
- const recordPath = row.path != null ? String(row.path) : "";
854
- const field = row.field != null ? String(row.field) : "";
855
- if (!recordPath || !field)
856
- continue;
857
- // 根拠。**source は必ずある / quote は無いことがある**(公開範囲フィルタで落ちる)。
858
- // quote が無いだけで根拠ごと捨てない——「どこ由来か」は残す価値がある
859
- const evidence = Array.isArray(row.evidence)
860
- ? row.evidence
861
- .filter((v) => v != null && typeof v === "object" && !Array.isArray(v))
862
- .map((v) => ({
863
- source: v.source != null ? String(v.source) : "",
864
- quote: v.quote != null ? String(v.quote) : undefined,
865
- }))
866
- .filter((v) => v.source !== "")
867
- : [];
868
- // **短縮記法(`field,`)にしないこと。** fleet-passthrough のテストが
869
- // 「画面が読むフィールドを build.ts が受け渡しているか」を `名前:` の字面で走査しており、
870
- // 短縮記法にすると、その守りの外へ静かに落ちる
871
- const proposal = {
872
- field: field,
873
- current: row.current != null ? String(row.current) : "",
874
- proposed: row.proposed != null ? String(row.proposed) : "",
875
- evidence: evidence,
876
- reason: row.reason != null ? String(row.reason) : "",
877
- createdAt: row.createdAt != null ? String(row.createdAt) : "",
878
- };
879
- // 名寄せの提案にだけ付く欄(engine の便 A・#704)。**文字列で中身があるときだけ**載せる
880
- // ——無い提案にキーを生やすと、他の提案まで形が変わる(許可リストの守りのテストが見ている)。
881
- // 🔴 **根拠の url(「その発言を開く」)はまだ通さない**(決定ログ 20260920-686.md の
882
- // 「提案カードに新しい欄を 1 つも通さない」の url の項は生きている。20260923-704.md)
883
- const kind = optionalText(row.kind);
884
- if (kind)
885
- proposal.kind = kind;
886
- const claimKey = optionalText(row.claim_key);
887
- if (claimKey)
888
- proposal.claim_key = claimKey;
889
- const displayName = optionalText(row.display_name);
890
- if (displayName)
891
- proposal.display_name = displayName;
892
- const question = optionalText(row.question);
893
- if (question)
894
- proposal.question = question;
895
- const why = optionalText(row.why);
896
- if (why)
897
- proposal.why = why;
898
- // その場で記録できるカードの印(cortex-engine #699 F7b)。**値は "portal" だけ**を通す
899
- // (語彙の外は無い扱い=今の指示文のボタンのまま)。20260920-686 の「新しい欄を通さない」を
900
- // この 1 欄だけ覆した(決定ログ 20260925-699-f7b.md)
901
- if (row.decide === "portal")
902
- proposal.decide = "portal";
903
- // 表記ゆれ(区切り・姓と名の順・会社名を直すと名簿に当たる)の名寄せカードは出さない(cortex-engine #1094 便 35a-1・
904
- // 10/5 オーナー: 画面に人が押す操作を残さない)。engine は今後これを数えるだけにして自動で結ぶ。
905
- // 🔴 見分けは engine の理由の文(`normalized_match` の `why`)。ほかの名寄せカードは今までどおり出す
906
- if (proposal.kind === "identity" && NORMALIZE_CARD_WHY.test(proposal.why ?? ""))
907
- continue;
908
- const list = byPath.get(recordPath);
909
- if (list)
910
- list.push(proposal);
911
- else
912
- byPath.set(recordPath, [proposal]);
913
- }
914
- return byPath;
915
- }
916
790
  // ---- Home(案件の自己記述)--------------------------------------------------
917
791
  //
918
792
  // Home.md は「案件そのものの説明」に絞る再構成の途中にある(engine 側で進行中)。
@@ -1114,7 +988,6 @@ async function parseGeneric(raw, fileName, repoRelDir, repoBaseUrl, branch, sect
1114
988
  editUrl,
1115
989
  rtype: data.type != null ? String(data.type) : "",
1116
990
  rstatus: data.status != null ? String(data.status) : "",
1117
- proposals: takeProposals(filePath),
1118
991
  relations: toRelations(data.relations),
1119
992
  source: data.source != null ? String(data.source) : "",
1120
993
  bodyLinks: extractBodyLinks(content),
@@ -2360,11 +2233,8 @@ async function buildSite(opts, views) {
2360
2233
  const fleet = await readFleetSources(repoRoot);
2361
2234
  const evidenceBook = knowledgeRoot ? await buildEvidenceBook(repoRoot, targetUrls, fleet.externalSources, ledgerDir) : (0, evidence_names_1.emptyEvidenceBook)();
2362
2235
  const refs = createRefResolver({ repoRoot, targetUrls, repoBaseUrl: repo.baseUrl, branch: repo.branch, book: evidenceBook });
2363
- // AIからの更新提案。**レコードを組み立てる前に読む**——各レコードが自分の分を
2364
- // takeProposals で引き取るので、後から読んでも載せる先が既に閉じている。
2365
- // 同一プロセスで2回ビルドしても前回の値が残らないよう、毎回入れ替える
2366
- PROPOSALS_BY_PATH = await readGoldProposals(repoRoot, ledgerDir);
2367
- PROPOSAL_PATHS_MATCHED = new Set();
2236
+ // 名寄せのカード(`.cortex/gold-proposals.json`)は読まない(cortex-engine #1094 B-viewer V4・member.confirm-store)。
2237
+ // 名寄せの問いは画面がポータルから受け取る(intake の identity-asks)。古いカードが案件リポに残っていても載せない
2368
2238
  // 用語集の並びの帳簿(活性度・強さ #790)。**帳簿が無い案件では並び替えを作らない**。
2369
2239
  // 🔴 **強さ(`state` が on のときだけ中身が入る)だけの案件でも器を作る**——作らないと、活性度の帳簿を
2370
2240
  // 持たない案件で用語集が目次(engine の `termRows`)と違う順になる。
@@ -2481,25 +2351,6 @@ async function buildSite(opts, views) {
2481
2351
  });
2482
2352
  // 期間フィルタ用の月(YYYY-MM)を日付から導出し、降順(新しい順)にする
2483
2353
  const months = uniqueSorted(decisions.map((d) => d.date.slice(0, 7)).filter((m) => /^\d{4}-\d{2}$/.test(m))).reverse();
2484
- // **突き合わなかった提案の件数をビルドログに出す。** path が1文字違うだけで
2485
- // 画面から静かに消えるので、出さないと「提案が出ない」の原因に辿り着けない。
2486
- // 表示からは落とす(どのレコードの話か分からない提案は、出しても行動につながらない)
2487
- const unmatched = [...PROPOSALS_BY_PATH.keys()].filter((k) => !PROPOSAL_PATHS_MATCHED.has(k));
2488
- if (unmatched.length) {
2489
- console.warn(`gold-proposals.json: レコードに突き合わない提案が ${unmatched.length} 件ありました(表示しません): ` +
2490
- unmatched.slice(0, 5).join(", ") + (unmatched.length > 5 ? " ほか" : ""));
2491
- }
2492
- // 画面に出さなくなった提案(名寄せ以外・#1094 35d)の件数。**人の操作は足さず、ログに 1 行だけ残す**
2493
- // (kind が identity でない提案は、記録に載っていても画面のどこにも出ない)
2494
- let hiddenProposals = 0;
2495
- for (const [k, list] of PROPOSALS_BY_PATH) {
2496
- if (!PROPOSAL_PATHS_MATCHED.has(k))
2497
- continue;
2498
- hiddenProposals += list.filter((p) => p.kind !== "identity").length;
2499
- }
2500
- if (hiddenProposals) {
2501
- console.warn(`gold-proposals.json: 名寄せ以外の提案が ${hiddenProposals} 件あります(画面には出しません)`);
2502
- }
2503
2354
  const graph = buildGraph(decisions, home, graphSections, targetUrls, repo.baseUrl, evidenceBook);
2504
2355
  computeLayout(graph); // 各ノードに x/y を書き込む(クライアントはこれを初期位置に使いウォームアップ省略)
2505
2356
  // 点の明るさと大きさ(engine #551)。順位のファイルが読めない案件では 1 点も付かない(今までと同じ画面)
@@ -2517,7 +2368,9 @@ async function buildSite(opts, views) {
2517
2368
  // ストーリーの日々の動き(#901 V2-5)。帳簿で on の日のファイルが無ければ null(キーごと出さない)
2518
2369
  const story = await (0, story_1.readStory)(repoRoot, renderGroundingSection, (p) => repo.baseUrl ? `${repo.baseUrl}/edit/${repo.branch}/${encodePath(p)}` : null,
2519
2370
  // 動きのない日を埋める終点=ビルドの時刻の日本時間の日付(時計を注入できる)
2520
- storyToday, ledgerDir);
2371
+ storyToday, ledgerDir,
2372
+ // 名前のリンク(story.screen-name-links): 日の relations の関係者・用語・ルールを、画面にある記録の表示名・別名・同義語で本文に当てる
2373
+ (targets) => (0, story_1.storyNameLinks)(targets, storyNameSources(sections)));
2521
2374
  // 週・月のあらすじ(#901)。週・月のファイルが無ければ null(キーごと出さない=今までと同じ画面)
2522
2375
  const storyDigest = await (0, story_1.readStoryDigest)(repoRoot, renderGroundingSection, (p) => repo.baseUrl ? `${repo.baseUrl}/edit/${repo.branch}/${encodePath(p)}` : null, storyToday, ledgerDir);
2523
2376
  // 案件アイコン(#348)。Cortex構成でなければ undefined(旧レイアウトには置き場が無い)
@@ -2677,7 +2530,6 @@ async function parseDecision(raw, fileName, repoRelDir, repoBaseUrl, branch, ref
2677
2530
  bodyId: "",
2678
2531
  searchText: toSearchText(content),
2679
2532
  rstatus: data.status != null ? String(data.status) : "",
2680
- proposals: takeProposals(filePath),
2681
2533
  fileName,
2682
2534
  editUrl,
2683
2535
  fm: sanitizeFm(data),
@@ -17,6 +17,7 @@ exports.readChangeLog = readChangeLog;
17
17
  * ## 🔴 読めなければ空(縮退は 1 つの形)
18
18
  *
19
19
  * 帳簿が無い・壊れ・版違い・形違いは `null`(画面は「更新履歴はまだありません」。今の欄には戻さない)。
20
+ * 栓 `GOLD_CHANGE_LOG` が off の案件は帳簿そのものが無いので、いつもこの形になる。
20
21
  * 1 行でも契約に反すれば全体を捨てる(engine は書く前に同じ基準で検査するので、viewer を engine より厳しくしない)。
21
22
  *
22
23
  * ## 🔴 禁止語の表は写さない
@@ -36,9 +37,13 @@ const ledger_dir_1 = require("./ledger-dir");
36
37
  exports.CHANGE_LOG_FILE = "gold-change-log.json";
37
38
  /** 形の版(engine の `CHANGE_LOG_VERSION`。違ったら読まない) */
38
39
  exports.CHANGE_LOG_VERSION = 1;
39
- /** 1 記録で画面に渡す行数の上限(窓の `per_record` と同じ 50) */
40
+ /**
41
+ * 1 記録で画面に渡す行数の上限(窓の `per_record` と同じ 50)。帳簿の窓(直近 180 日・1 記録 50 行)と同じく
42
+ * **画面の表示の絞り**で、記録の移り変わりそのものは git の履歴に残る(記憶の仕様 `common.delete-cap-exceptions`・
43
+ * 画面は「GitHub の履歴」へ逃げ道を出す)
44
+ */
40
45
  exports.CHANGE_LOG_MAX_ROWS = 50;
41
- /** `why` は 1 行 3 本まで(engine の `WHY_MAX`) */
46
+ /** `why` は 1 行 3 本まで(engine の `WHY_MAX`)。これも画面の表示の絞り(`common.delete-cap-exceptions`・元のリンクはコミットに残る) */
42
47
  const WHY_MAX = 3;
43
48
  /** 載る記録のパス(4 区画の記録。README・雛形・入れ子は載らない) */
44
49
  const RECORD_PATH = /^Cortex\/(Decisions|Glossary|Members|Rules)\/records\/[^/]+\.md$/;
@@ -315,6 +315,12 @@ exports.PERMISSIONS_DATA = {
315
315
  "access": "write",
316
316
  "accessText": "書く",
317
317
  "purpose": "定時の処理を設定する"
318
+ },
319
+ {
320
+ "spell": "https://www.googleapis.com/auth/script.send_mail",
321
+ "access": "write",
322
+ "accessText": "書く",
323
+ "purpose": "どのプロジェクトにも連携されなかった会議を、メモの持ち主(主催者)へ1回だけ知らせる(社内の持ち主だけ・会議名だけで本文は載せません)"
318
324
  }
319
325
  ]
320
326
  },
@@ -30,7 +30,7 @@ exports.orderByActivity = orderByActivity;
30
30
  * ## 🔴 帳簿が無い・壊れている・古い形の案件は「今までと 1 バイトも同じ画面」
31
31
  *
32
32
  * 44 案件に配られる画面で、帳簿を持つのは栓 `rederive` を入れた案件だけ(既定オフ)。
33
- * **読めないものは空**として正常に続ける(`readGoldProposals` と同じ規律)。空のときは
33
+ * **読めないものは空**として正常に続ける(無い・壊れた帳簿で画面を止めない規律)。空のときは
34
34
  * 帯の器も並び替えも一切作らない——「知らないことは、キーが無いことで表す」。
35
35
  *
36
36
  * ## 🔴 生成時刻を持ち込まない・score を焼かない
@@ -123,7 +123,7 @@ async function readOne(ledgerDir, file, then = "帯と並びは出さずに続
123
123
  }
124
124
  const asRecord = (v) => v != null && typeof v === "object" && !Array.isArray(v) ? v : null;
125
125
  /**
126
- * 3 帳簿を読む。**フィールドは明示で列挙し、素通しはしない**(`readGoldProposals` の流儀)——
126
+ * 3 帳簿を読む。**フィールドは明示で列挙し、素通しはしない**(知らない欄を画面へ素通しにしない流儀)——
127
127
  * engine は単独で版を上げるので、画面が知らないキーが必ず先に入る。
128
128
  */
129
129
  async function readRederivationLedgers(repoRoot, ledgerDirArg) {