@takagaki/cortex-decisions-viewer 0.4.19 → 0.4.21

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
@@ -106,6 +106,58 @@ function resolveKnowledgeRoot(decisionsDirAbs) {
106
106
  }
107
107
  return null;
108
108
  }
109
+ /**
110
+ * リポジトリルートの fleet-status.json から externalSources / internalSources / pipelines を読む。
111
+ * ファイルが無い・パース不能・当該フィールドが無い場合は undefined を返し、
112
+ * ビューア側で「データソースと自動化」セクションを出さない(後方互換・旧fleet-statusで壊れない)。
113
+ */
114
+ async function readFleetSources(repoRoot) {
115
+ let json;
116
+ try {
117
+ const raw = await node_fs_1.promises.readFile(path.join(repoRoot, "fleet-status.json"), "utf8");
118
+ json = JSON.parse(raw);
119
+ }
120
+ catch {
121
+ return {}; // 無い・壊れている → セクション非表示
122
+ }
123
+ if (json == null || typeof json !== "object")
124
+ return {};
125
+ const obj = json;
126
+ const externalSources = Array.isArray(obj.externalSources)
127
+ ? obj.externalSources
128
+ .filter((s) => s != null && typeof s === "object")
129
+ .map((s) => ({
130
+ type: String(s.type ?? ""),
131
+ name: String(s.name ?? ""),
132
+ ref: s.ref != null ? String(s.ref) : undefined,
133
+ url: s.url != null ? String(s.url) : undefined,
134
+ gold: typeof s.gold === "boolean" ? s.gold : undefined,
135
+ notify: typeof s.notify === "boolean" ? s.notify : undefined,
136
+ gate: s.gate != null ? String(s.gate) : undefined,
137
+ }))
138
+ : undefined;
139
+ const internalSources = Array.isArray(obj.internalSources)
140
+ ? obj.internalSources
141
+ .filter((s) => s != null && typeof s === "object")
142
+ .map((s) => ({
143
+ kind: String(s.kind ?? ""),
144
+ tool: String(s.tool ?? ""),
145
+ label: String(s.label ?? ""),
146
+ url: s.url != null ? String(s.url) : undefined,
147
+ lastSync: s.lastSync != null ? String(s.lastSync) : undefined,
148
+ }))
149
+ : undefined;
150
+ const pipelines = Array.isArray(obj.pipelines)
151
+ ? obj.pipelines
152
+ .filter((p) => p != null && typeof p === "object")
153
+ .map((p) => ({
154
+ id: String(p.id ?? ""),
155
+ label: String(p.label ?? ""),
156
+ lastSuccess: p.lastSuccess != null ? String(p.lastSuccess) : undefined,
157
+ }))
158
+ : undefined;
159
+ return { externalSources, internalSources, pipelines };
160
+ }
109
161
  /** ディレクトリ内の .md を汎用レコードとして読み込む(存在しなければ空配列) */
110
162
  async function loadGenericRecords(dirAbs, exclude, repoBaseUrl, branch) {
111
163
  let entries;
@@ -866,6 +918,8 @@ async function build(opts) {
866
918
  const contentEntries = await scanContent(repoRoot, knowledgeRoot ? path.basename(knowledgeRoot) : null, repo.baseUrl, repo.branch);
867
919
  const graph = buildGraph(decisions, home, sections, contentEntries, targetUrls, repo.baseUrl, repo.branch);
868
920
  computeLayout(graph); // 各ノードに x/y を書き込む(クライアントはこれを初期位置に使いウォームアップ省略)
921
+ // データソースと自動化(fleet-status.json 由来)。無ければ undefined でセクション非表示。
922
+ const fleet = await readFleetSources(repoRoot);
869
923
  const site = {
870
924
  title: opts.title,
871
925
  generatedAt: opts.generatedAt,
@@ -884,6 +938,9 @@ async function build(opts) {
884
938
  projectKey: opts.projectKey,
885
939
  }
886
940
  : undefined,
941
+ externalSources: fleet.externalSources,
942
+ internalSources: fleet.internalSources,
943
+ pipelines: fleet.pipelines,
887
944
  };
888
945
  // サムネイル(デザイン画面等)をサイトに同梱し、ノードのパスをサイト内相対に書き換える
889
946
  await node_fs_1.promises.mkdir(outAbs, { recursive: true });
package/dist/render.js CHANGED
@@ -324,6 +324,41 @@ a:hover { text-decoration: underline; }
324
324
  }
325
325
  .edit-btn:hover { border-color: var(--accent); color: var(--accent); text-decoration: none; }
326
326
  .section-label { font-size: 12px; font-weight: 700; color: var(--muted); letter-spacing: .04em; text-transform: uppercase; margin: 24px 0 8px; }
327
+ /* ---- データソースと自動化 ---- */
328
+ .datasources { margin-top: 34px; }
329
+ .datasources-head { font-size: 18px; font-weight: 700; color: var(--text); margin: 0 0 4px; }
330
+ .datasources-intro { color: var(--muted); font-size: 13.5px; line-height: 1.8; margin: 0 0 14px; }
331
+ .ds-sub { font-size: 13px; font-weight: 700; color: var(--muted); margin: 20px 0 8px; }
332
+ .ds-list { display: flex; flex-direction: column; gap: 10px; }
333
+ .ds-card {
334
+ background: var(--surface); border: 1px solid var(--border); border-radius: 12px;
335
+ padding: 14px 16px; box-shadow: var(--shadow);
336
+ display: flex; align-items: center; flex-wrap: wrap; gap: 8px 12px;
337
+ }
338
+ .ds-card-main { display: flex; align-items: center; gap: 10px; flex: 1 1 auto; min-width: 0; }
339
+ .ds-icon { font-size: 18px; line-height: 1; flex: 0 0 auto; }
340
+ .ds-type { font-size: 12px; font-weight: 600; color: var(--muted); flex: 0 0 auto; }
341
+ .ds-name { font-size: 14.5px; font-weight: 600; color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
342
+ a.ds-name { color: var(--accent); }
343
+ a.ds-name:hover { text-decoration: underline; }
344
+ .ds-note { color: var(--muted); font-size: 12px; line-height: 1.7; margin: 6px 0 0; }
345
+ .ds-sync { font-size: 12.5px; color: var(--muted); font-variant-numeric: tabular-nums; flex: 0 0 auto; }
346
+ .ds-badges { display: flex; align-items: center; flex-wrap: wrap; gap: 6px; flex: 0 0 auto; }
347
+ .badge.ds-gold { background: #fef3c7; color: #92400e; border: 1px solid #fcd34d; }
348
+ .badge.ds-excluded { background: #eceef1; color: #6b7280; }
349
+ .badge.ds-notify { background: #eff6ff; color: #1e40af; border: 1px solid #bfdbfe; }
350
+ .ds-gate { display: inline-flex; align-items: center; gap: 5px; font-size: 12.5px; font-weight: 600; flex: 0 0 auto; }
351
+ .ds-gate::before { content: ""; width: 8px; height: 8px; border-radius: 999px; background: currentColor; }
352
+ .ds-gate-ok { color: #1f7a45; }
353
+ .ds-gate-warn { color: #b45309; }
354
+ .ds-gate-muted { color: #6b7280; }
355
+ .ds-pipes { display: flex; flex-direction: column; gap: 8px; }
356
+ .ds-pipe {
357
+ display: flex; align-items: baseline; flex-wrap: wrap; gap: 4px 12px;
358
+ padding: 10px 14px; background: var(--surface); border: 1px solid var(--border); border-radius: 10px; box-shadow: var(--shadow);
359
+ }
360
+ .ds-pipe-label { font-size: 14px; font-weight: 600; color: var(--text); }
361
+ .ds-pipe-time { font-size: 12.5px; color: var(--muted); font-variant-numeric: tabular-nums; }
327
362
  /* ---- 操作タブ ---- */
328
363
  .ops-intro { color: var(--muted); font-size: 14px; line-height: 1.8; margin: 4px 0 8px; }
329
364
  .ops-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 14px; }
@@ -642,6 +677,111 @@ const CLIENT_JS = `
642
677
  var homeBody = el("div", { class: "body" });
643
678
  app.appendChild(homeBody);
644
679
  loadBody(h, homeBody);
680
+ renderDataSources();
681
+ }
682
+
683
+ // ---------- データソースと自動化(fleet-status.json 由来。無ければ非表示) ----------
684
+ // 顧客も読む面なので内部用語(cron・stub等)を避け「毎晩自動で〜」調にする。
685
+ var DS_TYPE = {
686
+ "slack": { icon: "💬", label: "Slack" },
687
+ "github-issues": { icon: "🐙", label: "GitHub Issues" },
688
+ "github-discussions": { icon: "💠", label: "GitHub Discussions" },
689
+ };
690
+ var DS_GATE = {
691
+ "ok": { text: "接続OK", cls: "ds-gate-ok" },
692
+ "not_in_channel": { text: "bot未招待", cls: "ds-gate-warn" },
693
+ "unreachable": { text: "アクセス不可", cls: "ds-gate-warn" },
694
+ "no_token": { text: "トークン未設定", cls: "ds-gate-muted" },
695
+ "unknown": { text: "不明", cls: "ds-gate-muted" },
696
+ };
697
+ var DS_KIND_ICON = {
698
+ "課題管理": "📋",
699
+ "会議": "🗒️",
700
+ "共有資料": "📎",
701
+ "デザイン": "🎨",
702
+ "開発": "🐙",
703
+ "チャット": "💬",
704
+ };
705
+ function dsFmtTime(iso) {
706
+ var d = new Date(iso);
707
+ if (isNaN(d.getTime())) return "";
708
+ var hh = ("0" + d.getHours()).slice(-2);
709
+ var mm = ("0" + d.getMinutes()).slice(-2);
710
+ return (d.getMonth() + 1) + "/" + d.getDate() + " " + hh + ":" + mm;
711
+ }
712
+ // 名前表示: url があれば新しいタブで正本を開くリンクにする
713
+ function dsNameEl(text, url) {
714
+ if (url) return el("a", { class: "ds-name", href: url, target: "_blank", rel: "noopener", text: text });
715
+ return el("span", { class: "ds-name", text: text });
716
+ }
717
+ function renderDataSources() {
718
+ var internals = SITE.internalSources || [];
719
+ var sources = SITE.externalSources || [];
720
+ var pipes = SITE.pipelines || [];
721
+ if (!internals.length && !sources.length && !pipes.length) return; // すべて無ければセクションごと出さない
722
+
723
+ var sec = el("section", { class: "datasources" });
724
+ sec.appendChild(el("h2", { class: "datasources-head", text: "データソースと自動化" }));
725
+ sec.appendChild(el("p", { class: "datasources-intro", text: "このCortexが接続している情報源と、毎晩自動でGoldに昇格する処理の一覧です。" }));
726
+
727
+ if (internals.length) {
728
+ sec.appendChild(el("div", { class: "ds-sub", text: "リポジトリ内の情報源(自動同期)" }));
729
+ var ilist = el("div", { class: "ds-list" });
730
+ internals.forEach(function (s) {
731
+ var card = el("div", { class: "ds-card" }, [
732
+ el("div", { class: "ds-card-main" }, [
733
+ el("span", { class: "ds-icon", text: DS_KIND_ICON[s.kind] || "📁" }),
734
+ el("span", { class: "ds-type", text: s.kind || "" }),
735
+ dsNameEl(s.label || "", s.url),
736
+ ]),
737
+ s.lastSync && dsFmtTime(s.lastSync)
738
+ ? el("span", { class: "ds-sync", text: "最終同期: " + dsFmtTime(s.lastSync) })
739
+ : null,
740
+ ]);
741
+ ilist.appendChild(card);
742
+ });
743
+ sec.appendChild(ilist);
744
+ sec.appendChild(el("p", { class: "ds-note", text: "これらの更新は毎晩の Gold 昇格が読み取ります。" }));
745
+ }
746
+
747
+ if (sources.length) {
748
+ sec.appendChild(el("div", { class: "ds-sub", text: "外部ソース(ライブ参照)" }));
749
+ var list = el("div", { class: "ds-list" });
750
+ sources.forEach(function (s) {
751
+ var meta = DS_TYPE[s.type] || { icon: "🔗", label: s.type || "その他" };
752
+ var badges = el("div", { class: "ds-badges" });
753
+ if (s.gold === true) badges.appendChild(el("span", { class: "badge ds-gold", text: "Gold昇格の読み取り対象" }));
754
+ else if (s.gold === false) badges.appendChild(el("span", { class: "badge ds-excluded", text: "除外" }));
755
+ if (s.notify === true) badges.appendChild(el("span", { class: "badge ds-notify", text: "通知先" }));
756
+ var gate = DS_GATE[s.gate] || DS_GATE["unknown"];
757
+ var gateEl = el("span", { class: "ds-gate " + gate.cls, text: gate.text });
758
+ var card = el("div", { class: "ds-card" }, [
759
+ el("div", { class: "ds-card-main" }, [
760
+ el("span", { class: "ds-icon", text: meta.icon }),
761
+ el("span", { class: "ds-type", text: meta.label }),
762
+ dsNameEl(s.name || "", s.url),
763
+ ]),
764
+ badges,
765
+ gateEl,
766
+ ]);
767
+ list.appendChild(card);
768
+ });
769
+ sec.appendChild(list);
770
+ }
771
+
772
+ if (pipes.length) {
773
+ sec.appendChild(el("div", { class: "ds-sub", text: "自動化パイプライン" }));
774
+ var plist = el("div", { class: "ds-pipes" });
775
+ pipes.forEach(function (p) {
776
+ var t = p.lastSuccess ? dsFmtTime(p.lastSuccess) : "";
777
+ var children = [ el("span", { class: "ds-pipe-label", text: p.label || p.id || "" }) ];
778
+ if (t) children.push(el("span", { class: "ds-pipe-time", text: "最終成功: " + t }));
779
+ plist.appendChild(el("div", { class: "ds-pipe" }, children));
780
+ });
781
+ sec.appendChild(plist);
782
+ }
783
+
784
+ app.appendChild(sec);
645
785
  }
646
786
 
647
787
  // ---------- 操作タブ(Cortex自体への操作。まずは取り込み。今後この受け皿に機能を足す) ----------
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@takagaki/cortex-decisions-viewer",
3
- "version": "0.4.19",
3
+ "version": "0.4.21",
4
4
  "description": "Cortexの意思決定記録(Decisions/*.md)を静的サイトにビルドして閲覧する。各レコードに「Edit on GitHub」リンクを付与する。",
5
5
  "license": "UNLICENSED",
6
6
  "private": false,