@takagaki/cortex-decisions-viewer 0.4.73 → 0.4.75

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
@@ -249,17 +249,8 @@ async function readFleetSources(repoRoot) {
249
249
  applicable: typeof p.applicable === "boolean" ? p.applicable : undefined,
250
250
  }))
251
251
  : undefined;
252
- // 能力→ツールの宣言マップ(例: { チャット: "slack", 開発: "none" })。"none" は未使用のグレー行表示に使う
253
- let tools;
254
- if (obj.tools != null && typeof obj.tools === "object" && !Array.isArray(obj.tools)) {
255
- tools = {};
256
- for (const [k, v] of Object.entries(obj.tools)) {
257
- if (typeof v === "string")
258
- tools[k] = v;
259
- }
260
- if (!Object.keys(tools).length)
261
- tools = undefined;
262
- }
252
+ // **宣言(tools)は読まない(#125 ⑤)。** engine が出力から落としたので、
253
+ // 残っているのは再生成前の古いスナップショットだけ。読まずに捨てる。
263
254
  // 能力ごとの導出済みの状態(#125①が載せ、#125②で画面の正本になった)。
264
255
  //
265
256
  // **列挙し忘れは静かに消える。** このマッピングは素通しではなく明示の許可リストで、
@@ -313,7 +304,7 @@ async function readFleetSources(repoRoot) {
313
304
  const secretSources = obj.secretSources && typeof obj.secretSources === "object"
314
305
  ? obj.secretSources
315
306
  : undefined;
316
- return { externalSources, internalSources, pipelines, tools, capabilities, fleetGeneratedAt, secretSources, checks, githubOwners, backlogTarget };
307
+ return { externalSources, internalSources, pipelines, capabilities, fleetGeneratedAt, secretSources, checks, githubOwners, backlogTarget };
317
308
  }
318
309
  /** パスがディレクトリとして存在するか */
319
310
  async function isDirectory(dirAbs) {
@@ -1193,7 +1184,6 @@ async function build(opts) {
1193
1184
  secretSources: fleet.secretSources,
1194
1185
  internalSources: fleet.internalSources,
1195
1186
  pipelines: fleet.pipelines,
1196
- tools: fleet.tools,
1197
1187
  capabilities: fleet.capabilities,
1198
1188
  checks: fleet.checks,
1199
1189
  githubOwners: fleet.githubOwners,
package/dist/render.js CHANGED
@@ -132,15 +132,19 @@ const CM_CAPS = [
132
132
  { cap: "チャット", icon: "💬", pipe: null },
133
133
  { cap: "開発", icon: "🐙", pipe: null },
134
134
  ];
135
- /** ツールid→表示名(接続マップのノードラベル用) */
135
+ /**
136
+ * ツールid→表示名(接続マップのノードラベル用)。
137
+ *
138
+ * **キーは ADAPTER_CATALOG の値だけ。** かつては jira / teams / box / local / none も
139
+ * 持っていたが、これらは宣言(Home.md の `tools`)にしか現れない値だった。宣言を撤去した
140
+ * いま、ここに来るのはカタログのアダプタだけ(`capabilityAdapter` がカタログ外を弾く)。
141
+ * **未対応ツールの表示名を復活させないこと**——名前を出した瞬間「対応している」と
142
+ * 読まれるのが #124 の症状そのもので、状態を出す場所すら無いまま名前だけが並ぶ。
143
+ */
136
144
  const CM_TOOL_LABEL = {
137
145
  backlog: "Backlog",
138
- jira: "Jira",
139
146
  "google-meet": "Google Meet",
140
- teams: "Teams",
141
147
  "google-drive": "Google Drive",
142
- box: "Box",
143
- local: "ローカル",
144
148
  figma: "Figma",
145
149
  slack: "Slack",
146
150
  github: "GitHub",
@@ -178,8 +182,8 @@ function sourceNeedsAttention(s) {
178
182
  * **クロージャを持たない純関数に保つこと**——外の変数を掴むと直列化した先で壊れる。
179
183
  *
180
184
  * 返す値:
181
- * "off" … 未使用(tools が none / enabled:false / 使っている証拠が無い)
182
- * "idle" … 宣言はあるが一度も繋がっていない(画面表記は「未接続」)
185
+ * "off" … 未使用(enabled:false / 使っている証拠が無い)
186
+ * "idle" … 設定はあるが一度も繋がっていない(画面表記は「未接続」)
183
187
  * "warn" … 問題あり
184
188
  * "ok" … 健全
185
189
  *
@@ -189,7 +193,7 @@ function sourceNeedsAttention(s) {
189
193
  * APIキー未投入で sync-backlog が失敗し、課題管理が警告色で線付きになった)。
190
194
  *
191
195
  * @param cap 能力名
192
- * @param ctx 生のフリートデータ { tools, internals, externals, pipes }。
196
+ * @param ctx 生のフリートデータ { capabilities, internals, externals, pipes }。
193
197
  * **解決済みの値を渡させない。** 振り分けや regex を呼び出し側に持たせると、
194
198
  * 呼び出し側ごとに写経が生まれ、片方が書き忘れると判定が静かに変わる。
195
199
  */
@@ -311,7 +315,6 @@ function capabilityState(cap, ctx) {
311
315
  // (読み手は未使用を信じて押さないので、行が消えるより悪い)
312
316
  const liveExts = ctx.externals.filter(function (s) { return externalCapability(s.type) === cap; });
313
317
  const isLive = cap === "チャット" || cap === "開発" || cap === CAP_OTHER;
314
- const toolVal = ctx.tools[cap];
315
318
  const internal = ctx.internals.filter(function (s) { return s.kind === cap; })[0];
316
319
  const pipeRe = PIPE[cap];
317
320
  // ---- #125②: 導出(capabilities)があるならそれが状態の正本 ----
@@ -346,12 +349,16 @@ function capabilityState(cap, ctx) {
346
349
  // 答えが割れることはない。**この2つを分けるなら、写像とここの両方を直すこと。**
347
350
  return "idle";
348
351
  }
349
- // ---- ここから下は capabilities を持たない古い fleet-status.json のための劣化表示 ----
350
- // 使用中か: tools宣言がnone以外 / internalSourceが存在(enabled:false除く) / ライブ参照の実体がある
351
- const used = (toolVal != null && toolVal !== "none") ||
352
- (internal != null && internal.enabled !== false) ||
352
+ // ---- ここから下は capabilities を持たない fleet-status.json のための防御 ----
353
+ //
354
+ // **宣言(tools)はもう見ない(#125 ⑤)。** 艦隊の fleet-status.json は全案件が
355
+ // capabilities を持つので、通常ここへは来ない。残してあるのは、生成前・取得失敗・
356
+ // 途中で壊れたデータで**画面が落ちない**ようにするためだけ——実体(同期ソースの行・
357
+ // ライブ参照)だけで判定し、無ければ「まだ繋がっていない」側へ倒す。
358
+ // **ここに宣言を読む道を足さないこと。** 足した瞬間、実態と食い違える経路が復活する。
359
+ const used = (internal != null && internal.enabled !== false) ||
353
360
  (isLive && liveExts.length > 0);
354
- const off = toolVal === "none" || (internal != null && internal.enabled === false) || !used;
361
+ const off = (internal != null && internal.enabled === false) || !used;
355
362
  // 問題あり: ライブ参照はgate≠okが1件以上、同期ソースは対応パイプラインの直近runがfailure
356
363
  const warn = !off &&
357
364
  (isLive
@@ -395,21 +402,38 @@ function capabilityState(cap, ctx) {
395
402
  : !syncedForReal);
396
403
  return off ? "off" : idle ? "idle" : warn ? "warn" : "ok";
397
404
  }
405
+ /**
406
+ * `repoBaseUrl`(https://github.com/owner/repo)からリポジトリ名だけを取り出す。
407
+ *
408
+ * **owner は出さない。** 画面は1案件ぶんしか映さないので、どの組織かは常に同じ——
409
+ * 出しても名前が長くなるだけで、読み手の判断は変わらない。
410
+ * 取れない形(URLが無い・GitHub 以外・末尾が空)では空文字を返し、呼び出し側が落とす。
411
+ *
412
+ * **github.com のURLだけを受ける。** 隣に GitHub のロゴを置くので、別ホストの名前を
413
+ * 通すとロゴが嘘になる。
414
+ */
415
+ function repoNameOf(repoBaseUrl) {
416
+ if (!repoBaseUrl)
417
+ return "";
418
+ const m = /^https?:\/\/github\.com\/(.+)$/.exec(String(repoBaseUrl));
419
+ if (!m)
420
+ return "";
421
+ // **末尾スラッシュを先に落とす。** 後にすると `repo.git/` の `.git` が残る
422
+ const slug = m[1].replace(/\/+$/, "").replace(/\.git$/, "");
423
+ const name = slug.split("/").pop() || "";
424
+ // owner だけの URL(github.com/owner)を名前と取り違えない
425
+ return slug.includes("/") ? name : "";
426
+ }
398
427
  function renderConnectionMap(site) {
399
428
  const internals = site.internalSources ?? [];
400
429
  const externals = site.externalSources ?? [];
401
430
  const pipes = site.pipelines ?? [];
402
- const tools = site.tools ?? {};
403
431
  const caps = site.capabilities ?? {};
404
432
  // **fleet のデータが1つでも取れているか、だけで判定する(#125②)。**
405
433
  // 素の Decisions ビュー(fleet-status.json が無い構成)ではマップを出さない現行の意図は
406
- // そのまま。`tools` を条件から外さないのは、capabilities を持たない古いスナップショットで
407
- // マップが丸ごと消えるのを防ぐため(移行中は両方が現物として存在する)。
408
- if (!internals.length &&
409
- !externals.length &&
410
- !pipes.length &&
411
- !Object.keys(tools).length &&
412
- !Object.keys(caps).length) {
434
+ // そのまま。宣言(`tools`)は条件から外した(⑤)——艦隊の fleet-status.json
435
+ // すべて capabilities を持つので、宣言はもうマップの有無を左右しない。
436
+ if (!internals.length && !externals.length && !pipes.length && !Object.keys(caps).length) {
413
437
  return null;
414
438
  }
415
439
  const hasCaps = Object.keys(caps).length > 0;
@@ -421,13 +445,12 @@ function renderConnectionMap(site) {
421
445
  const REPO_X = W / 2;
422
446
  CM_CAPS.forEach((c, i) => {
423
447
  const cx = 80 + i * 160;
424
- const toolVal = tools[c.cap];
425
448
  const internal = internals.find((s) => s.kind === c.cap);
426
449
  const isLive = c.cap === "チャット" || c.cap === "開発";
427
450
  const liveExts = c.cap === "チャット" ? slackExts : c.cap === "開発" ? ghExts : [];
428
451
  // **判定は capabilityState に一本化している。** 画面側(クライアントJS)も同じ関数を
429
452
  // 呼ぶので、ここで写経すると「同じ画面でマップが⚠️・直下が『接続OK』」が復活する。
430
- const st = capabilityState(c.cap, { tools, capabilities: caps, internals, externals, pipes });
453
+ const st = capabilityState(c.cap, { capabilities: caps, internals, externals, pipes });
431
454
  const off = st === "off";
432
455
  const idle = st === "idle";
433
456
  const warn = st === "warn";
@@ -437,22 +460,19 @@ function renderConnectionMap(site) {
437
460
  // ツールは「その能力をいま何で実現しているか」という差し替え可能なアダプタで、
438
461
  // 主語ではない。ツール名を主にすると、Teams のように複数の能力に使われるものが
439
462
  // 同じ名前で並び(会議とチャットに Teams が2つ)、何が繋がっているのか読めなくなる。
440
- // "none"(未使用の宣言)はツール名ではないので出さない。そのまま表示すると
441
- // 「ツール: none」という読めない行になる(実際にそう出ていた)
442
463
  //
443
- // **導出があるならカタログのアダプタを常に出す(#125②)。** 未接続でも「何で繋ぐのか」が
444
- // 分からないと入口にならない。カタログ外の値(teams 等)は capabilityAdapter が弾く。
464
+ // **カタログのアダプタを常に出す(#125②)。** 未接続でも「何で繋ぐのか」が
465
+ // 分からないと入口にならない。カタログ外の値は capabilityAdapter が弾く。
466
+ // capabilities を持たないデータでは、実体(同期ソースの行・ライブ参照)から拾う(⑤)。
445
467
  const rawTool = hasCaps
446
468
  ? capabilityAdapter(c.cap, caps)
447
- : toolVal && toolVal !== "none"
448
- ? toolVal
449
- : internal && internal.tool && internal.tool !== "none"
450
- ? internal.tool
451
- : isLive && liveExts.length
452
- ? c.cap === "チャット"
453
- ? "slack"
454
- : "github"
455
- : "";
469
+ : internal && internal.tool && internal.tool !== "none"
470
+ ? internal.tool
471
+ : isLive && liveExts.length
472
+ ? c.cap === "チャット"
473
+ ? "slack"
474
+ : "github"
475
+ : "";
456
476
  const toolName = rawTool ? CM_TOOL_LABEL[rawTool] ?? rawTool : "";
457
477
  const label = `${c.icon} ${c.cap}`;
458
478
  // 2行目=ツール名(小さく)/3行目=状態(未使用・自動連携なし・ライブ参照・最終同期)
@@ -508,10 +528,26 @@ function renderConnectionMap(site) {
508
528
  links.push(`<path class="cm-link ${state}" d="M ${cx} 78 C ${cx} 152, ${REPO_X} 162, ${REPO_X} 238"></path>`);
509
529
  }
510
530
  });
511
- const repoTitle = site.title.length > 24 ? site.title.slice(0, 23) + "…" : site.title;
531
+ // **中央ノードは GitHub のリポジトリ名そのもの(オーナー指示・2026-08-12)。**
532
+ // 以前は「📦 コンテキストリポジトリ(案件の表示名)」だったが、役割名は図の文脈から自明で、
533
+ // 表示名は上下のノードとも重複していた。実際に開く先はリポジトリなので、その名前で呼ぶ。
534
+ // 名前は repoBaseUrl から導出する(新しいデータは足さない)。取れない構成では
535
+ // 案件の表示名へ落とす——名前の無い箱にしない。
536
+ const repoName = repoNameOf(site.repoBaseUrl) || site.title;
537
+ const repoLabel = repoName.length > 24 ? repoName.slice(0, 23) + "…" : repoName;
538
+ // アイコンと名前を**まとめて中央へ置く**。text-anchor=middle だけだとアイコンの分だけ
539
+ // 全体が右に寄るので、合計幅を見積もってから左端を決める。日本語が混じる案件名へ
540
+ // フォールバックした場合に備え、全角は倍幅で数える(14px/700 の実測に合わせた概算)
541
+ const ICON = 18;
542
+ const GAP = 8;
543
+ const textW = [...repoLabel].reduce((w, ch) => w + (ch.charCodeAt(0) < 128 ? 8.2 : 14), 0);
544
+ const startX = REPO_X - (ICON + GAP + textW) / 2;
512
545
  const repo = `<g class="cm-repo">` +
513
546
  `<rect x="${REPO_X - 170}" y="240" width="340" height="52" rx="12"></rect>` +
514
- `<text class="cm-repo-label" x="${REPO_X}" y="272" text-anchor="middle">📦 コンテキストリポジトリ(${escapeHtml(repoTitle)})</text>` +
547
+ // ロゴはビルド時に埋め込む(ヘッダ右上・開発ソースのカードと同じ GITHUB_ICON)。外部通信は起きない。
548
+ // 入れ子の svg を transform で置き、色は cm-repo-logo の color を継ぐ(fill="currentColor")
549
+ `<g class="cm-repo-logo" transform="translate(${startX.toFixed(1)}, ${266 - ICON / 2 - 1})">${GITHUB_ICON}</g>` +
550
+ `<text class="cm-repo-label" x="${(startX + ICON + GAP).toFixed(1)}" y="272">${escapeHtml(repoLabel)}</text>` +
515
551
  `</g>`;
516
552
  return (`<svg class="cm-svg" viewBox="0 0 ${W} 312" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="データソースとの接続マップ">` +
517
553
  links.join("") +
@@ -554,15 +590,14 @@ function buildTutorialData(site, codeUrl, repoSlug) {
554
590
  },
555
591
  ];
556
592
  // ここから先は「書き込み・外部連携」の体験。案件の構成に無い能力のチャレンジは出さない(表示条件つき)
557
- const tools = site.tools ?? {};
558
593
  const caps = site.capabilities ?? {};
559
594
  const internals = site.internalSources ?? [];
560
595
  const externals = site.externalSources ?? [];
561
596
  /**
562
- * その能力が配線済みか。**導出(capabilities)があるならそれだけで決める(#125②)。**
597
+ * その能力が配線済みか。**導出(capabilities)だけで決める(#125②)。**
563
598
  * 宣言でチャレンジを出していた頃は、Backlog を宣言しているだけで鍵も入っていない案件に
564
599
  * 「記票してみよう」が出ていた——押すと必ず失敗する体験を最初の5分に置くことになる。
565
- * 導出が無い古いデータでは null を返し、呼び出し側が従来の判定へ落ちる。
600
+ * 導出が無いデータでは null を返し、呼び出し側が実体(行の有無)だけで判断する(⑤)。
566
601
  */
567
602
  const capOn = (cap) => {
568
603
  const s = capabilityRawState(cap, caps);
@@ -578,8 +613,7 @@ function buildTutorialData(site, codeUrl, repoSlug) {
578
613
  }
579
614
  // Backlogに記票する体験(課題管理にBacklogを使う案件のみ出す)
580
615
  const usesBacklog = capOn("課題管理") ??
581
- (tools["課題管理"] === "backlog" ||
582
- internals.some((s) => s.kind === "課題管理" && s.tool === "backlog" && s.enabled !== false));
616
+ internals.some((s) => s.kind === "課題管理" && s.tool === "backlog" && s.enabled !== false);
583
617
  if (usesBacklog) {
584
618
  specs.push({
585
619
  title: "Backlogに記票してみよう",
@@ -588,9 +622,7 @@ function buildTutorialData(site, codeUrl, repoSlug) {
588
622
  });
589
623
  }
590
624
  // 画面インベントリを読む体験(デザインを同期している案件のみ出す)
591
- const usesDesign = capOn("デザイン") ??
592
- ((tools["デザイン"] != null && tools["デザイン"] !== "none") ||
593
- internals.some((s) => s.kind === "デザイン" && s.enabled !== false));
625
+ const usesDesign = capOn("デザイン") ?? internals.some((s) => s.kind === "デザイン" && s.enabled !== false);
594
626
  if (usesDesign) {
595
627
  specs.push({
596
628
  title: "デザインを読んでみよう",
@@ -599,7 +631,7 @@ function buildTutorialData(site, codeUrl, repoSlug) {
599
631
  });
600
632
  }
601
633
  // 完了をSlackに知らせる体験(チャットにSlackを宣言している案件のみ出す。最終チャレンジ)
602
- const usesSlack = capOn("チャット") ?? (tools["チャット"] === "slack" || externals.some((s) => s.type === "slack"));
634
+ const usesSlack = capOn("チャット") ?? externals.some((s) => s.type === "slack");
603
635
  if (usesSlack) {
604
636
  specs.push({
605
637
  title: "Slackに完了を知らせよう",
@@ -641,7 +673,7 @@ function tutorialChallenge4Prompt(decisions) {
641
673
  * 規約はプロンプトに書き写さず、規約のあるファイル(README.md・template.md)をAIに読ませ、
642
674
  * 中身は利用者に聞き返させる(AI導線ボタンのプロンプトと同じ方針)。
643
675
  * 規約がどこにも無い手順(外部サービスへのつなぎ方・投稿経路の判定)だけを骨子として残す。
644
- * スキルが使える環境ではそちらを優先させる旨を冒頭に添える。
676
+ * 対応するスキルがある操作は、スキルが使える環境ではそちらを優先させる旨を冒頭に添える。
645
677
  */
646
678
  const TUT_MEMBER_PROMPT = [
647
679
  "(update-member スキルが使える環境なら、そのスキルの手順を優先してください。)",
@@ -652,16 +684,13 @@ const TUT_MEMBER_PROMPT = [
652
684
  "書き込む前に、作成・変更する内容を提示して承認を得てください。",
653
685
  ].join("\n");
654
686
  const TUT_BACKLOG_PROMPT = [
655
- "(backlog-push スキルが使える環境なら、そのスキルの手順を優先してください。)",
656
- "",
657
687
  "Backlogの課題に、わたしの名義でコメントを1件投稿してください。Backlogの画面を開かずにAIから記票する練習です。",
658
688
  "どの課題に何を書くかは、まずわたしに質問してください(課題は 課題管理/issues/ から選べます。課題キーは各ファイルの「基本情報 > 課題キー」)。",
659
689
  "",
660
690
  "投稿経路は次の優先順で判定し、Backlog上の名義とあわせて先に伝えてください。",
661
691
  "1. mcp__backlog__ で始まるツール(あなた本人の名義。手元にAPIキーは不要)",
662
692
  "2. 環境変数 DOMAIN / PROJECT_KEY / BACKLOG_API_KEY があれば Backlog REST API を直接呼ぶ(キーの持ち主の名義)",
663
- "3. 課題管理/backlog-proxy.json(url / projectKey / token)の中央プロキシ(共有bot名義になるため、本文の冒頭に自分の名前を記名する)",
664
- "4. どれも無ければ、つなぎ方(手元の Claude Code なら /mcp コマンドから backlog、クラウドならセッションのコネクタ設定)を案内して止まる",
693
+ "3. どちらも無ければ、つなぎ方(手元の Claude Code なら /mcp コマンドから backlog、クラウドならセッションのコネクタ設定)を案内して止まる",
665
694
  "",
666
695
  "投稿する前に、課題キー・本文・経路・名義を提示して承認を得てください。投稿後は投稿先のURLを報告してください。",
667
696
  "今回はコメントの追加だけです。課題の削除・クローズ・属性変更はしないでください。",
@@ -1135,6 +1164,8 @@ a.ds-name:hover { text-decoration: underline; }
1135
1164
  @media (prefers-reduced-motion: reduce) { .cm-link.cm-ok { animation: none; } }
1136
1165
  .cm-repo rect { fill: var(--accent-weak); stroke: var(--accent); stroke-width: 1.5; }
1137
1166
  .cm-repo-label { font-size: 14px; font-weight: 700; fill: #1e3a8a; }
1167
+ /* GitHubロゴは fill="currentColor" なので、色は color で渡す(ラベルと同色に揃える) */
1168
+ .cm-repo-logo { color: #1e3a8a; }
1138
1169
  /* 未使用(非活性)行: アダプターとして存在するが、この案件では設定されていないもの */
1139
1170
  .ds-card.ds-inactive, .ds-pipe.ds-inactive { background: #f8f9fa; border-style: dashed; box-shadow: none; }
1140
1171
  .ds-inactive .ds-icon { filter: grayscale(1); opacity: .55; }
@@ -1466,11 +1497,16 @@ ${capabilityState.toString()}
1466
1497
  }
1467
1498
  // Cortex自体を操作するアクションと、接続・自動化の情報の受け皿(取り込み・接続マップ・データソース)。
1468
1499
  // 投入フォーム設定 or fleet-status由来の接続情報のどちらかがあれば出す。
1469
- // **fleet のデータが取れているか、だけで決める(#125②)。** 能力の宣言(tools)は廃止したので
1470
- // capabilities を条件に足す。**常に真にはしない**——fleet-status.json を持たない素の
1471
- // Decisions ビューでは操作タブそのものを出さない現行の意図を保つ
1500
+ // **fleet のデータが取れているか、だけで決める(#125②)。** 能力の宣言(tools)は撤去した(⑤)。
1501
+ // **常に真にはしない**——fleet-status.json を持たない素の
1502
+ // Decisions ビューでは操作タブそのものを出さない現行の意図を保つ。
1503
+ //
1504
+ // **鍵(secretKinds)も候補に数える。** 下の節(buildDataSources)は鍵だけの案件でも
1505
+ // 出す作りだが、タブが立たなければその判定に辿り着けない。宣言を撤去するまでは
1506
+ // 空の宣言オブジェクトが truthy でここを通していたため、鍵だけの案件が通っていたのは
1507
+ // **偶然**だった(テストもその偶然に乗っていた)。候補として明示する
1472
1508
  var HAS_FLEET = !!((SITE.internalSources || []).length || (SITE.externalSources || []).length ||
1473
- (SITE.pipelines || []).length || SITE.capabilities || SITE.tools);
1509
+ (SITE.pipelines || []).length || SITE.capabilities || (SITE.secretKinds || []).length);
1474
1510
  if (SITE.intake || HAS_FLEET) {
1475
1511
  TABS.push({ key: "ops", label: "操作" });
1476
1512
  }
@@ -2021,23 +2057,18 @@ ${capabilityState.toString()}
2021
2057
  * 誤った封鎖を生んでいた(実際に Slack を渡されているのに追加できない案件があった)。
2022
2058
  * カタログはこの製品が実装しているアダプタの定義なので、押して必ず失敗することはない。
2023
2059
  *
2024
- * **capabilities を持たない古いデータでは、従来どおり宣言で見る。** 移行前の
2025
- * スナップショットには宣言しか無く、そこで全面開放すると別ツール宣言の案件に
2026
- * 必ず400になるボタンが復活する。
2060
+ * **宣言によるゲートは撤去した(⑤)。** capabilities を持たないデータでも
2061
+ * カタログだけで判断する——判定の道は1本にする。
2027
2062
  */
2028
2063
  function capabilityUsable(cap, tool) {
2029
- var caps = fleetVal("capabilities") || {};
2030
- if (Object.keys(caps).length) return (ADAPTER_CATALOG[cap] || []).indexOf(tool) >= 0;
2031
- var v = (fleetVal("tools") || {})[cap];
2032
- return !v || v === "none" || v === tool;
2064
+ return (ADAPTER_CATALOG[cap] || []).indexOf(tool) >= 0;
2033
2065
  }
2034
2066
 
2035
2067
  /** 組み立てだけを行う(設置は呼び出し側)。ライブ差し替えで同じ節を作り直すため分けている */
2036
2068
  /** ツールid→表示名(クライアント側。接続マップの CM_TOOL_LABEL と同じ内容) */
2037
2069
  var CM_TOOL_LABEL_C = {
2038
- backlog: "Backlog", jira: "Jira", "google-meet": "Google Meet", teams: "Teams",
2039
- "google-drive": "Google Drive", box: "Box", local: "ローカル", figma: "Figma",
2040
- slack: "Slack", github: "GitHub", none: "",
2070
+ backlog: "Backlog", "google-meet": "Google Meet", "google-drive": "Google Drive",
2071
+ figma: "Figma", slack: "Slack", github: "GitHub",
2041
2072
  };
2042
2073
 
2043
2074
  /**
@@ -2076,11 +2107,6 @@ ${capabilityState.toString()}
2076
2107
  // 能力ごとの導出済みの状態(#125② の正本)。持たない古いデータでは {}
2077
2108
  var caps = fleetVal("capabilities") || {};
2078
2109
  var hasCaps = Object.keys(caps).length > 0;
2079
- // 能力→ツールの宣言(例: チャット: "none")。**capabilities が無いときだけ効く劣化表示。**
2080
- // "none" の能力は「未使用」のグレー行で存在だけ見せる
2081
- var tools = (!hasCaps && fleetVal("tools")) || {};
2082
- var chatOff = tools["チャット"] === "none";
2083
- var devOff = tools["開発"] === "none";
2084
2110
  // **この節に入るものが1つでもあるなら出す。**
2085
2111
  //
2086
2112
  // 条件を「データ行があるか」で書いていて、2回続けて漏らした——
@@ -2090,9 +2116,8 @@ ${capabilityState.toString()}
2090
2116
  //
2091
2117
  // 個別に足し続けると次も漏らすので、**この節に出す候補を列挙して判定する**形にする。
2092
2118
  var hasSecrets = ((SITE.secretKinds || []).filter(function (k) { return SECRET_FORMS[k]; })).length > 0;
2093
- // **fleet のデータが取れているか、だけを見る(#125②)。** 宣言(tools)は廃止したが、
2094
- // capabilities を持たない古いスナップショットで節ごと消えないよう条件には残す
2095
- var hasCapModel = hasCaps || Object.keys(tools).length > 0;
2119
+ // **fleet のデータが取れているか、だけを見る(#125②)。** 宣言(tools)は撤去した(⑤)
2120
+ var hasCapModel = hasCaps;
2096
2121
  // **投入の口も候補。** fleet-status.json はGitHub Actionsが夜間に生成するので、
2097
2122
  // 立ち上げ直後の案件には存在しない。それを理由に節ごと消すと、
2098
2123
  // 文字起こし・資料を入れる口が**一番必要な瞬間に**画面から無くなる
@@ -2249,7 +2274,7 @@ ${capabilityState.toString()}
2249
2274
  });
2250
2275
  }
2251
2276
 
2252
- if (sources.length || chatOff || devOff) {
2277
+ if (sources.length) {
2253
2278
  // **見出しは付けない。** #101 で行は能力ごとのモーダルへ移ったので、
2254
2279
  // ここに見出しを置くと中身ゼロの見出しが節の先頭に立つ(実際に8案件すべてで出ていた)
2255
2280
  // ツール(type)別にグループ化し、多いグループ(4件以上)は折りたたむ
@@ -2261,9 +2286,6 @@ ${capabilityState.toString()}
2261
2286
  g.items.push(s);
2262
2287
  });
2263
2288
  var UNIT = { "slack": "チャンネル", "github-issues": "リポジトリ", "github-discussions": "リポジトリ" };
2264
- // チャット未使用: Slackグループの位置(先頭)に「存在するが未設定」のグレー行を出す
2265
- // **アンカーは渡さない**(タイルが飛び先。同 §id の重複を作らない)
2266
- if (chatOff) pushCap("チャット", inactiveSourceRow("💬", "チャット", null));
2267
2289
  groups.forEach(function (g) {
2268
2290
  var meta = DS_TYPE[g.type] || { icon: "🔗", label: g.type || "その他" };
2269
2291
  var cards = el("div", { class: "ds-list" });
@@ -2300,8 +2322,6 @@ ${capabilityState.toString()}
2300
2322
  }
2301
2323
  });
2302
2324
  }
2303
- // 開発未使用: グレー行も能力の受け皿へ
2304
- if (devOff) pushCap("開発", inactiveSourceRow("🐙", "GitHub", null));
2305
2325
 
2306
2326
  // **追加フォームはグループの外に置く。** グループは0件だと描かれず、4件以上だと
2307
2327
  // 畳まれる(既定は閉)。中に入れると**新規案件(0件)=一番追加したい場面**で消える。
@@ -2453,10 +2473,11 @@ ${capabilityState.toString()}
2453
2473
  // 両方に出すと、同じ失敗が1画面に2回並ぶ。実行状況はパイプライン節に任せ、
2454
2474
  // ここでは直し方だけをその行に添える(下の renderPipelines で使う)
2455
2475
  var PIPE_CHECK = { "update-gold": "nightly_decisionlog", "sync-backlog": "nightly_backlog", "sync-designs": "figma_sync" };
2476
+ // **engine が出しているチェックだけを並べる。** meeting_minutes / channels_json は
2477
+ // 同語反復(適用されるときは必ず ok)だったので engine が撤去した(#125 ⑤)。
2478
+ // 割り当ての無いチェックは消えず、下の「セットアップの残り」へ落ちる
2456
2479
  var CHECK_CAP = {
2457
2480
  backlog_secrets: "課題管理", backlog_synced: "課題管理",
2458
- meeting_minutes: "会議",
2459
- channels_json: "チャット",
2460
2481
  figma_token: "デザイン", figma_inventory: "デザイン",
2461
2482
  dev_sources_registered: "開発", dev_issues_derived: "開発",
2462
2483
  };
@@ -2518,12 +2539,12 @@ ${capabilityState.toString()}
2518
2539
  // 上のマップと直下の一覧で語が割れない
2519
2540
  parts.push((CAP_STATE_MARK[st] || "") + (CAP_STATE_TEXT[derived] || "状態が判定できません"));
2520
2541
  } else {
2521
- // **off には2つの意味が同居している。** 「使わないと宣言した」(tools が none)と
2542
+ // **off には2つの意味が同居している。** 「意図的に止めている」(enabled:false)と
2522
2543
  // 「まだ何も分かっていない」(fleet-status.json を夜間バッチがまだ作っていない)。
2523
2544
  // 後者を「未使用」と書くと、立ち上げ直後の案件で顧客が
2524
2545
  // 「Backlog も Slack も使わない案件だ」と読む——繋ぐ前提で契約しているのに
2525
- var declaredOff = tools[cap] === "none" || (internal != null && internal.enabled === false);
2526
- parts.push(st === "off" && !declaredOff ? "未設定" : STATE_LABEL[st] || "");
2546
+ var intentionalOff = internal != null && internal.enabled === false;
2547
+ parts.push(st === "off" && !intentionalOff ? "未設定" : STATE_LABEL[st] || "");
2527
2548
  }
2528
2549
  // 読み取り範囲。**この画面は顧客も見る。何をGoldに読んでいるかの開示が主眼**
2529
2550
  //
@@ -2567,11 +2588,9 @@ ${capabilityState.toString()}
2567
2588
  // ——未接続のタイルは「これから繋げる入口」で、消すと繋ぎ方を知る場所が無くなる。
2568
2589
  // カタログは製品の定義なので、案件データが何であってもここは変わらない
2569
2590
  var adapters = (hasCaps && ADAPTER_CATALOG[cap]) || [];
2570
- // 旧データ(capabilities 無し)は従来どおり「宣言があれば行が無くても出す」
2571
- var declared = tools[cap] != null;
2572
- // **手動投入はアダプタに依存しない。** 宣言や行の有無で弾くと、
2591
+ // **手動投入はアダプタに依存しない。** 行の有無で弾くと、
2573
2592
  // fleet-status.json がまだ無い新規案件で投入の口ごと消える
2574
- if (!adapters.length && !items.length && !declared && !manualBox[cap]) return;
2593
+ if (!adapters.length && !items.length && !manualBox[cap]) return;
2575
2594
  anyCap = true;
2576
2595
  sec.appendChild(el("div", { class: "ds-cap", text: cap }));
2577
2596
  if (adapters.length) {
@@ -2582,11 +2601,11 @@ ${capabilityState.toString()}
2582
2601
  adapters.forEach(function (a, ai) {
2583
2602
  renderAdapterTile(cap, items, a, a === active ? capabilityDisplayState(cap, caps) : "unset", ai);
2584
2603
  });
2585
- } else if (items.length || declared) {
2586
- // 旧データ: アダプタのタイルは宣言か行があるときだけ。手動投入だけが理由でここへ来た
2587
- // 新規案件に、中身の無い「会議」タイルを立てても押す先が無い
2588
- var legacyTool = tools[cap] && tools[cap] !== "none" ? tools[cap] : "";
2589
- renderAdapterTile(cap, items, legacyTool, null, 0);
2604
+ } else if (items.length) {
2605
+ // capabilities を持たないデータ: アダプタのタイルは行があるときだけ。手動投入だけが
2606
+ // 理由でここへ来た新規案件に、中身の無い「会議」タイルを立てても押す先が無い。
2607
+ // アダプタ名は名乗らない(宣言を撤去したので、名乗れる根拠がここには無い・⑤)
2608
+ renderAdapterTile(cap, items, "", null, 0);
2590
2609
  }
2591
2610
  renderManualTile(cap);
2592
2611
  });
@@ -2594,7 +2613,7 @@ ${capabilityState.toString()}
2594
2613
  /**
2595
2614
  * 能力×アダプタ1件のタイル。
2596
2615
  *
2597
- * @param adapter アダプタ識別子(カタログ由来。旧データでは宣言 tools 由来)
2616
+ * @param adapter アダプタ識別子(カタログ由来。capabilities が無いデータでは空)
2598
2617
  * @param derived engine が導出した5値。旧データ(capabilities 無し)では null
2599
2618
  * @param ai その能力の中での並び順。先頭だけが接続マップの飛び先(ds-<能力>)になる
2600
2619
  */
@@ -2603,7 +2622,7 @@ ${capabilityState.toString()}
2603
2622
  var st = derived === "unset" && capabilityDisplayState(cap, caps) !== "unset"
2604
2623
  // カタログにあるが導出が答えていないアダプタ(今日は発生しない。#107 の前方互換)
2605
2624
  ? "idle"
2606
- : capabilityState(cap, { tools: tools, capabilities: caps, internals: internals, externals: sources, pipes: pipes });
2625
+ : capabilityState(cap, { capabilities: caps, internals: internals, externals: sources, pipes: pipes });
2607
2626
  // **ロゴはタイルの左に独立して置く。** 名前の行に入れると名前の高さに
2608
2627
  // 縛られて小さくなる。ロゴで能力を見分けられるのが一覧の主な用途
2609
2628
  var logo = ADAPTER_ICONS[adapter];
@@ -2650,7 +2669,8 @@ ${capabilityState.toString()}
2650
2669
  // **注記の条件は、入力欄を出す条件と同じにする(0.4.71)。** 別々にすると
2651
2670
  // 「入力欄はあるのに未設定だと言われない」「言われるのに入力欄が無い」のどちらかが起きる。
2652
2671
  // 0.4.70 は unset で入力欄を畳んでいたので注記も抑えていたが、案A で欄を出すようにしたため揃えた
2653
- var tokenRelevant = derived != null ? derived !== "off" : tools[cap] !== "none";
2672
+ // 導出が無いデータでは判断材料が無いので、注記を出す側(=入力欄と揃う側)に倒す
2673
+ var tokenRelevant = derived != null ? derived !== "off" : true;
2654
2674
  if (!tokenIsFallback && tokenRelevant && sk && (SITE.secretKinds || []).indexOf(sk) >= 0 && !((fleetVal("secretSources") || {})[sk])) {
2655
2675
  body.appendChild(el("div", { class: "ds-tile-note", text: "⚠ トークンが未設定です" }));
2656
2676
  }
@@ -2775,17 +2795,8 @@ ${capabilityState.toString()}
2775
2795
  var sec = buildDataSources();
2776
2796
  if (sec) app.appendChild(sec);
2777
2797
  }
2778
- // 未使用能力のプレースホルダ行(アダプターとして存在するが、この案件では設定されていないもの)
2779
- function inactiveSourceRow(icon, typeLabel, anchorId) {
2780
- return el("div", { class: "ds-card ds-inactive", id: anchorId }, [
2781
- el("div", { class: "ds-card-main" }, [
2782
- el("span", { class: "ds-icon", text: icon }),
2783
- el("span", { class: "ds-type", text: typeLabel }),
2784
- el("span", { class: "ds-name", text: "未使用" }),
2785
- ]),
2786
- el("span", { class: "ds-inactive-note", text: "この案件では設定されていません" }),
2787
- ]);
2788
- }
2798
+ // **「未使用」のグレー行は持たない(#125 ⑤)。** 宣言が none の能力にだけ出していた行で、
2799
+ // 宣言を撤去したいま出す条件が無い。未接続の能力は能力タイル(常時表示)が状態と入口を持つ。
2789
2800
  // 外部ソース1件のカード(グループ化表示・展開表示の両方から使う)
2790
2801
  /**
2791
2802
  * Gold昇格の切り替えボタン。押すと設定ファイル(channels.json)が書き換わる。
@@ -3159,9 +3170,9 @@ ${capabilityState.toString()}
3159
3170
  /**
3160
3171
  * 外部ソースの追加フォーム(Slack・GitHub)。
3161
3172
  *
3162
- * **カタログでゲートする(#125②)。** 宣言(tools)は廃止した。古いデータのときだけ
3163
- * 従来どおり宣言を見る(capabilityUsable の項を参照)——別のツールが宣言されている
3164
- * 案件ではサーバが必ず400で断るので、押せるのに必ず失敗するボタンを出さない。
3173
+ * **カタログでゲートする(#125②)。** 宣言(tools)によるゲートは撤去した(⑤)。
3174
+ * カタログはこの製品が実装しているアダプタの定義なので、出したボタンが
3175
+ * 「押せるのに必ず失敗する」ことはない。
3165
3176
  */
3166
3177
  function renderExternalAdders() {
3167
3178
  var box = el("div", {});
@@ -3993,8 +4004,11 @@ ${capabilityState.toString()}
3993
4004
  // 画面で分かるようにする(接続状況は夜間バッチのスナップショットなので、
3994
4005
  // 保存の直後は前日の値のまま。時刻は上の節に出している)。
3995
4006
  var src = (fleetVal("secretSources") || {})[kind] || "";
4007
+ // **前方一致で見る。** #105 の移行期は「Secrets Manager(旧名)」も来る。
4008
+ // 完全一致にすると、旧名で動いている案件だけが repo secret と同じ見た目になり、
4009
+ // 「管理下にあるのに未移行」という一番知りたい状態が画面から消える
3996
4010
  var state = src
3997
- ? el("p", { class: "sec-state" + (src === "Secrets Manager" ? " is-managed" : ""),
4011
+ ? el("p", { class: "sec-state" + (src.indexOf("Secrets Manager") === 0 ? " is-managed" : ""),
3998
4012
  text: "現在: " + src + " から取得中" })
3999
4013
  : el("p", { class: "sec-state is-none", text: "現在: 未設定" });
4000
4014
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@takagaki/cortex-decisions-viewer",
3
- "version": "0.4.73",
3
+ "version": "0.4.75",
4
4
  "description": "Cortexの意思決定記録(Decisions/*.md)を静的サイトにビルドして閲覧する。各レコードに「Edit on GitHub」リンクを付与する。",
5
5
  "license": "UNLICENSED",
6
6
  "private": false,