@takagaki/cortex-decisions-viewer 0.12.31 → 0.12.32
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/render.js +305 -25
- package/package.json +1 -1
package/dist/render.js
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.UNSETTLED_LIMITS = exports.LIVE_CONFIRM_LIMITS = void 0;
|
|
3
|
+
exports.UNSETTLED_LIMITS = exports.ALREADY_REGISTERED = exports.LIVE_CONFIRM_LIMITS = exports.CLAUDE_CODE_LINK = void 0;
|
|
4
4
|
exports.renderSite = renderSite;
|
|
5
5
|
exports.renderSiteWithAssets = renderSiteWithAssets;
|
|
6
6
|
exports.viewerAssetSources = viewerAssetSources;
|
|
7
7
|
exports.viewerAssetNames = viewerAssetNames;
|
|
8
|
+
exports.isRepositorySlug = isRepositorySlug;
|
|
9
|
+
exports.devRepositoryRefs = devRepositoryRefs;
|
|
10
|
+
exports.claudeCodeRepositories = claudeCodeRepositories;
|
|
11
|
+
exports.claudeCodeUrl = claudeCodeUrl;
|
|
12
|
+
exports.isAlreadyRegistered = isAlreadyRegistered;
|
|
8
13
|
exports.sanitizeRegisteredNow = sanitizeRegisteredNow;
|
|
9
14
|
exports.sanitizeStateNow = sanitizeStateNow;
|
|
10
15
|
exports.overlayStateOnFleet = overlayStateOnFleet;
|
|
@@ -53,21 +58,27 @@ function renderSite(site, assets) {
|
|
|
53
58
|
.replace(/^https?:\/\/github\.com\//, "")
|
|
54
59
|
.replace(/\.git$/, "")
|
|
55
60
|
: "";
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
61
|
+
// 組み立ての正本は claudeCodeUrl 1か所(#404)。ここは薄い包みに留める。
|
|
62
|
+
// `?? []` は必須——build.ts は fleet-status.json が無い案件で externalSources を持たない
|
|
63
|
+
const codeUrl = (prompt) => claudeCodeUrl(prompt, repoSlug, site.externalSources ?? []);
|
|
64
|
+
/** ヘッダー「AIに聞く」の指示文。data-ai-prompt に同じ値を残し、接続状況が届いたら href を組み直す */
|
|
65
|
+
const ASK_PROMPT = "このプロジェクトの概要について教えてください。";
|
|
66
|
+
const askUrl = codeUrl(ASK_PROMPT);
|
|
60
67
|
// ※ 投入フォーム設定 site.intake は build.ts が --intake-url/--intake-token/--project-key から付与する。
|
|
61
68
|
const dataJson = JSON.stringify(site).replace(/</g, "\\u003c");
|
|
62
69
|
const safeTitle = escapeHtml(site.title);
|
|
63
70
|
// 接続マップ(操作タブ最上部のSVG図)。ビルド時に生成し<template>で埋め込む。fleet情報が無ければ出さない
|
|
64
71
|
const connMap = renderConnectionMap(site);
|
|
65
|
-
// チュートリアル(Home
|
|
66
|
-
|
|
72
|
+
// チュートリアル(Homeタブ下部の「はじめに」セクション)。**URLは焼き込まず指示文だけ渡す**(#404)
|
|
73
|
+
// ——URLは画面側が codeUrl で組む(登録した開発リポジトリを当日から載せるため)。
|
|
74
|
+
// 🔴 **この .replace(/</g, "\\u003c") を消さないこと。** </script> によるブレイクアウトを
|
|
75
|
+
// 防いでいる。指示文には案件データ(決定のタイトル)が入り、#404 以降は
|
|
76
|
+
// encodeURIComponent を通らない生の値がそのまま JSON に載る
|
|
77
|
+
const tutorialJson = JSON.stringify(buildTutorialData(site, repoSlug)).replace(/</g, "\\u003c");
|
|
67
78
|
const githubBtn = site.repoBaseUrl
|
|
68
79
|
? `<a class="gh-btn" href="${escapeHtml(site.repoBaseUrl)}" target="_blank" rel="noopener" title="コンテキストリポジトリをGitHubで開く">${GITHUB_ICON}</a>`
|
|
69
80
|
: "";
|
|
70
|
-
const askBtn = `<a class="ask-ai-btn" href="${escapeHtml(askUrl)}" target="_blank" rel="noopener" title="Claude Code Web
|
|
81
|
+
const askBtn = `<a class="ask-ai-btn" href="${escapeHtml(askUrl)}" data-ai-prompt="${escapeHtml(ASK_PROMPT)}" target="_blank" rel="noopener" title="Claude Code Web でこの案件のリポジトリと、登録済みの開発リポジトリを開き、プロジェクトについて質問・調査できます">💬 プロジェクトについてAIに聞く</a>`;
|
|
71
82
|
// CSPでインラインが拒否される環境向けの外部参照形。省略時は従来どおり埋め込む
|
|
72
83
|
const styleTag = assets
|
|
73
84
|
? `<link rel="stylesheet" href="${escapeHtml(assets.css)}" />`
|
|
@@ -342,6 +353,111 @@ function sourceAdapter(type) {
|
|
|
342
353
|
return "github";
|
|
343
354
|
return "";
|
|
344
355
|
}
|
|
356
|
+
// ---------------------------------------------------------------------------
|
|
357
|
+
// Claude Code への導線(#404)— **組み立てる場所は下の claudeCodeUrl 1か所だけ**
|
|
358
|
+
//
|
|
359
|
+
// 画面には Claude Code を開くボタンが7つある(ヘッダーの「AIに聞く」・Home の「挑戦する」・
|
|
360
|
+
// 各区画の「+AIで登録」「AIで編集」「AIで適用」・Home の「AIで編集」「AIで書く」)。
|
|
361
|
+
// かつては7か所が別々に URL を組み立て、案件のコンテキストリポジトリ1本だけを渡していた。
|
|
362
|
+
// **ボタンが増えるたびに写しが増える形**だったので、組み立てを1関数に寄せ、
|
|
363
|
+
// 「ここ以外に claude.ai/code と書いたら赤くなる」テスト(claude-code-link)を付けてある。
|
|
364
|
+
//
|
|
365
|
+
// **クロージャを持たない function 宣言に保つこと**(.toString() でクライアントへ渡す)。
|
|
366
|
+
// ---------------------------------------------------------------------------
|
|
367
|
+
/**
|
|
368
|
+
* 説明用の素のリンク(リポジトリ指定なし)。「この仕組みについて」の Claude Code と、
|
|
369
|
+
* 案件リポジトリが owner/repo の形に解決できないときのフォールバックが使う。
|
|
370
|
+
*
|
|
371
|
+
* ⚠ **定数そのものを export してはいけない。** `export const` は CommonJS へ落ちるときに
|
|
372
|
+
* 参照が `exports.X` へ書き換えられ、**toString() で流し込んだ先には exports が無いので
|
|
373
|
+
* クライアントJSが丸ごと落ちる**(LIVE_CONFIRM_LIMITS の項に経緯がある)。
|
|
374
|
+
*/
|
|
375
|
+
const CLAUDE_CODE_HOME = "https://claude.ai/code/";
|
|
376
|
+
/** 素のリンクを**テストから固定するための入れ物**(LIVE_CONFIRM_LIMITS と同じ作法) */
|
|
377
|
+
exports.CLAUDE_CODE_LINK = { home: CLAUDE_CODE_HOME };
|
|
378
|
+
/**
|
|
379
|
+
* owner/repo の形か(ポータル web/src/features/harness/harness-link.ts の REPOSITORY_SLUG と同じ形)。
|
|
380
|
+
*
|
|
381
|
+
* ⚠ **波括弧を含む量指定子({2,} など)を書かないこと。** 配信スクリプトから
|
|
382
|
+
* この関数のソースを切り出すテストが、波括弧の対応で本体の末尾を決めている。
|
|
383
|
+
*/
|
|
384
|
+
function isRepositorySlug(ref) {
|
|
385
|
+
if (typeof ref !== "string")
|
|
386
|
+
return false;
|
|
387
|
+
if (!/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(ref))
|
|
388
|
+
return false;
|
|
389
|
+
// 🔴 **パスに見える値を通さない**(`../evil` / `./x` / `a/..` / `.git/config`)。
|
|
390
|
+
// クエリの値なので実害は無い(受け取る側でパスとして解決されない)が、
|
|
391
|
+
// **「形の検査」だと言っている以上、形として通してはいけない**——通していると、
|
|
392
|
+
// この関数を別の用途(ファイルパスの組み立て等)に流用した人が静かに踏む。
|
|
393
|
+
// 所有者は「.」で始まれない(GitHub の規則)。リポジトリ名は `.github` のように
|
|
394
|
+
// 「.」で始まれるので、そちらは**点だけの区切り**だけを弾く。
|
|
395
|
+
var parts = ref.split("/");
|
|
396
|
+
if (parts[0].charAt(0) === ".")
|
|
397
|
+
return false;
|
|
398
|
+
return !/^\.+$/.test(parts[1]);
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* 接続済みの開発リポジトリの ref を**登録順**に取り出す。
|
|
402
|
+
*
|
|
403
|
+
* 🔴 **絞りは sourceAdapter(正本・#153)に任せる。** ここに独自の if 連鎖を書くと、
|
|
404
|
+
* 次のアダプタが増えたときに片方だけ直り、一覧に出ているリポジトリが AI 導線から消える。
|
|
405
|
+
*
|
|
406
|
+
* 🔴 **読み取り停止中(goldState === "excluded")も載せる。** 読み取りを止めているのは
|
|
407
|
+
* 夜間の取り込みの話で、人が AI に見せたいかどうかとは別(オーナー決定・ポータルの
|
|
408
|
+
* ハーネスの導線と同じ規則)。
|
|
409
|
+
*/
|
|
410
|
+
function devRepositoryRefs(externals) {
|
|
411
|
+
var out = [];
|
|
412
|
+
(externals || []).forEach(function (s) {
|
|
413
|
+
if (!s || sourceAdapter(s.type) !== "github")
|
|
414
|
+
return;
|
|
415
|
+
if (!isRepositorySlug(s.ref))
|
|
416
|
+
return;
|
|
417
|
+
out.push(s.ref);
|
|
418
|
+
});
|
|
419
|
+
return out;
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* repositories= に載せる並び。**案件リポ → 開発リポ(登録順)**。
|
|
423
|
+
*
|
|
424
|
+
* 🔴 **大小文字を無視して重複排除・先勝ち。** 案件リポ自身が開発リポとしても登録されて
|
|
425
|
+
* いる案件があり、そのまま並べると同じリポジトリが2回載る。
|
|
426
|
+
* 🔴 **形の壊れた開発リポは、その1件だけ落とす。** 登録は人が書く JSON なので、
|
|
427
|
+
* 1行の書き間違いでボタンごと死なせない。
|
|
428
|
+
* 🔴 **案件リポが owner/repo の形でないときは空を返す**(呼び出し側が素のリンクへ倒す)。
|
|
429
|
+
* ここで案件リポだけを落とすと「開発リポだけが開く」URL ができ、ヘッダーの
|
|
430
|
+
* 「このプロジェクトについてAIに聞く」が**別のリポジトリを開く**。
|
|
431
|
+
*/
|
|
432
|
+
function claudeCodeRepositories(projectRepo, devRefs) {
|
|
433
|
+
if (!isRepositorySlug(projectRepo))
|
|
434
|
+
return [];
|
|
435
|
+
var out = [String(projectRepo)];
|
|
436
|
+
var seen = [String(projectRepo).toLowerCase()];
|
|
437
|
+
(devRefs || []).forEach(function (ref) {
|
|
438
|
+
if (!isRepositorySlug(ref))
|
|
439
|
+
return;
|
|
440
|
+
var k = String(ref).toLowerCase();
|
|
441
|
+
if (seen.indexOf(k) >= 0)
|
|
442
|
+
return;
|
|
443
|
+
seen.push(k);
|
|
444
|
+
out.push(String(ref));
|
|
445
|
+
});
|
|
446
|
+
return out;
|
|
447
|
+
}
|
|
448
|
+
/**
|
|
449
|
+
* 🔴 **Claude Code への URL を組み立ててよい唯一の場所。**
|
|
450
|
+
* ここ以外に claude.ai/code の文字列を書かないこと(guard テストが見張っている)。
|
|
451
|
+
*
|
|
452
|
+
* repositories はカンマ区切りで**素のまま**載せる(公式の例がその形で、スラッシュも
|
|
453
|
+
* エンコードしない)。指示文だけ encodeURIComponent する。
|
|
454
|
+
*/
|
|
455
|
+
function claudeCodeUrl(prompt, projectRepo, externals) {
|
|
456
|
+
var repos = claudeCodeRepositories(projectRepo, devRepositoryRefs(externals));
|
|
457
|
+
if (!repos.length)
|
|
458
|
+
return CLAUDE_CODE_HOME;
|
|
459
|
+
return "https://claude.ai/code?repositories=" + repos.join(",") + "&prompt=" + encodeURIComponent(prompt);
|
|
460
|
+
}
|
|
345
461
|
/**
|
|
346
462
|
* 外部ソースの行が「この能力・このアダプタのタイル」に属するか。
|
|
347
463
|
*
|
|
@@ -585,6 +701,34 @@ const REGISTERED_REQUIRED = {
|
|
|
585
701
|
};
|
|
586
702
|
/** 開発の種別に許す値。**知らない値は落とす**(画面のラベル・振り分けが決まらない) */
|
|
587
703
|
const REGISTERED_DEV_TYPES = ["github-issues", "github-discussions"];
|
|
704
|
+
/**
|
|
705
|
+
* 「もう登録されている」という断りの語尾。**正本は intake の `ALREADY_REGISTERED_SUFFIX`**
|
|
706
|
+
* (`infra/lambda/intake/logic.mjs`)で、ここはその写し。一致は
|
|
707
|
+
* `viewer/test/already-registered-parity.test.mjs` が見張っている。
|
|
708
|
+
*
|
|
709
|
+
* ⚠ **定数そのものを export しない**(CommonJS で `exports.X` に化け、流し込んだ先で落ちる)。
|
|
710
|
+
* テストから読む値は下の `ALREADY_REGISTERED` 経由。
|
|
711
|
+
*/
|
|
712
|
+
const ALREADY_REGISTERED_SUFFIX = "既に登録されています";
|
|
713
|
+
/** 語尾を**テストから固定するための入れ物**(LIVE_CONFIRM_LIMITS と同じ作法) */
|
|
714
|
+
exports.ALREADY_REGISTERED = { suffix: ALREADY_REGISTERED_SUFFIX };
|
|
715
|
+
/**
|
|
716
|
+
* サーバの断りが「もう登録されている」か。
|
|
717
|
+
*
|
|
718
|
+
* 🔴 **これは失敗の判定ではなく、事実の判定(#409)。** サーバがこう答えた時点で、
|
|
719
|
+
* その接続先は登録ファイルに確かに在る。画面はこれを受けて**一覧に行を出す**——
|
|
720
|
+
* 「一覧は空なのに、追加すると既に登録されていると断られる」という袋小路を作らない。
|
|
721
|
+
*
|
|
722
|
+
* 🔴 **語尾で見る(能力ごとの主語まで写さない)。** 「このチャンネルは」「このスペースは」
|
|
723
|
+
* まで写すと、主語を1つ増やした日に画面だけ古い写しを持ち、その能力だけ袋小路が残る。
|
|
724
|
+
*
|
|
725
|
+
* **クロージャを持たない純関数に保つこと**(`.toString()` でクライアントへ渡す)。
|
|
726
|
+
*/
|
|
727
|
+
function isAlreadyRegistered(message) {
|
|
728
|
+
if (typeof message !== "string")
|
|
729
|
+
return false;
|
|
730
|
+
return message.indexOf(ALREADY_REGISTERED_SUFFIX) >= 0;
|
|
731
|
+
}
|
|
588
732
|
/**
|
|
589
733
|
* 応答の registeredNow を、画面が扱ってよい形へ畳む。**通らなかったものは黙って落とす。**
|
|
590
734
|
*
|
|
@@ -2002,7 +2146,7 @@ function renderConnectionMap(site) {
|
|
|
2002
2146
|
* 各チャレンジのプロンプトは固定文(ビューアコードに実案件名は残さない)。
|
|
2003
2147
|
* チャレンジ4だけは site.decisions から根拠付きの最新決定を選び、そのtitle/idを埋め込む。
|
|
2004
2148
|
*/
|
|
2005
|
-
function buildTutorialData(site,
|
|
2149
|
+
function buildTutorialData(site, repoSlug) {
|
|
2006
2150
|
const c4 = tutorialChallenge4Prompt(site.decisions);
|
|
2007
2151
|
const specs = [
|
|
2008
2152
|
{
|
|
@@ -2087,7 +2231,7 @@ function buildTutorialData(site, codeUrl, repoSlug) {
|
|
|
2087
2231
|
n: i + 1,
|
|
2088
2232
|
title: s.title,
|
|
2089
2233
|
desc: s.desc,
|
|
2090
|
-
|
|
2234
|
+
prompt: s.prompt,
|
|
2091
2235
|
})),
|
|
2092
2236
|
};
|
|
2093
2237
|
}
|
|
@@ -3422,6 +3566,14 @@ var CAP_STATE_TEXT = ${JSON.stringify(CAP_STATE_TEXT)};
|
|
|
3422
3566
|
var CAP_STATE_MARK = ${JSON.stringify(CAP_STATE_MARK)};
|
|
3423
3567
|
${externalCapability.toString()}
|
|
3424
3568
|
${sourceAdapter.toString()}
|
|
3569
|
+
// Claude Code への導線(#404)。**組み立ての正本は上の claudeCodeUrl だけ。**
|
|
3570
|
+
// 🔴 この定数行を落とすと、流し込んだ関数が未定義の CLAUDE_CODE_HOME を参照して
|
|
3571
|
+
// 画面のスクリプトが丸ごと死ぬ(定数は export できないので、値はここで注入する)
|
|
3572
|
+
var CLAUDE_CODE_HOME = ${JSON.stringify(CLAUDE_CODE_HOME)};
|
|
3573
|
+
${isRepositorySlug.toString()}
|
|
3574
|
+
${devRepositoryRefs.toString()}
|
|
3575
|
+
${claudeCodeRepositories.toString()}
|
|
3576
|
+
${claudeCodeUrl.toString()}
|
|
3425
3577
|
${sourceMatchesAdapter.toString()}
|
|
3426
3578
|
${adapterList.toString()}
|
|
3427
3579
|
${adapterEntry.toString()}
|
|
@@ -3442,6 +3594,10 @@ var REGISTERED_CAP_NAMES = ${JSON.stringify(REGISTERED_CAP_NAMES)};
|
|
|
3442
3594
|
var REGISTERED_FIELDS = ${JSON.stringify(REGISTERED_FIELDS)};
|
|
3443
3595
|
var REGISTERED_REQUIRED = ${JSON.stringify(REGISTERED_REQUIRED)};
|
|
3444
3596
|
var REGISTERED_DEV_TYPES = ${JSON.stringify(REGISTERED_DEV_TYPES)};
|
|
3597
|
+
// 「もう登録されている」という断りの語尾(#409)。**この定数行を落とすと、
|
|
3598
|
+
// 流し込んだ isAlreadyRegistered が未定義を参照して画面のスクリプトが丸ごと死ぬ**
|
|
3599
|
+
var ALREADY_REGISTERED_SUFFIX = ${JSON.stringify(ALREADY_REGISTERED_SUFFIX)};
|
|
3600
|
+
${isAlreadyRegistered.toString()}
|
|
3445
3601
|
var UNSETTLED_TTL_MS = ${JSON.stringify(UNSETTLED_TTL_MS)};
|
|
3446
3602
|
var UNSETTLED_POLL_MS = ${JSON.stringify(UNSETTLED_POLL_MS)};
|
|
3447
3603
|
${sanitizeRegisteredNow.toString()}
|
|
@@ -3888,10 +4044,33 @@ ${deriveCapTile.toString()}
|
|
|
3888
4044
|
var REPO_SLUG = SITE.repoBaseUrl
|
|
3889
4045
|
? SITE.repoBaseUrl.replace(/^https?:\\/\\/github\\.com\\//, "").replace(/\\.git$/, "")
|
|
3890
4046
|
: "";
|
|
4047
|
+
// 組み立ての正本は claudeCodeUrl(上で流し込んだビルド時の関数・#404)。
|
|
4048
|
+
// 開発リポジトリは **まとめ表ではなく画面と同じ値**(まとめ表+登録の即時値+仮置き)から取る
|
|
4049
|
+
// ——焼き込むと、登録した開発リポジトリがその日いっぱい AI 導線に載らない。
|
|
4050
|
+
// 🔴 **var MERGE_KEYS の宣言より上に codeUrl を呼ぶトップレベル文を足さないこと。**
|
|
4051
|
+
// fleetVal は MERGE_KEYS を読むが、var の初期化は宣言行に達するまで走らない
|
|
4052
|
+
// (関数宣言の巻き上げでは undefined を読む)。呼び出しは全て描画(route)以降に置く
|
|
3891
4053
|
function codeUrl(prompt) {
|
|
3892
|
-
return REPO_SLUG
|
|
3893
|
-
|
|
3894
|
-
|
|
4054
|
+
return claudeCodeUrl(prompt, REPO_SLUG, fleetVal("externalSources") || []);
|
|
4055
|
+
}
|
|
4056
|
+
// AI 導線の a はここでだけ作る。href を決めてよいのは codeUrl だけ(#404)。
|
|
4057
|
+
// 指示文を属性に残すのは、接続状況の応答が届いたときに href を組み直すため(syncAiLinks)
|
|
4058
|
+
function aiLink(cls, prompt, text) {
|
|
4059
|
+
var a = el("a", { class: cls, href: codeUrl(prompt), target: "_blank", rel: "noopener", text: text });
|
|
4060
|
+
a.setAttribute("data-ai-prompt", prompt);
|
|
4061
|
+
return a;
|
|
4062
|
+
}
|
|
4063
|
+
// 接続状況が届いた・保存が効いたときに、画面に出ている AI 導線の href を組み直す。
|
|
4064
|
+
// 🔴 **pendingOpsNow の中(仮置きの失効)からは呼ばない**——codeUrl が fleetVal を
|
|
4065
|
+
// 呼ぶので再入する。仮置きが失効して行が減ったときの貼り替えは行わない
|
|
4066
|
+
// (15分後の話で、その頃には接続状況の応答が届いている)
|
|
4067
|
+
// 🔴 **document を素で引かない(app.ownerDocument を使う)。** ここは接続状況の応答が
|
|
4068
|
+
// 届いたときに走るので、**画面が畳まれた後に届く**ことがある(テストの window.close()・
|
|
4069
|
+
// 実機のタブを閉じた直後)。掴んだノード越しなら、その後でも例外を投げずに済む
|
|
4070
|
+
// ——投げると応答の後段(取り直しの停止など)が丸ごと落ちる
|
|
4071
|
+
function syncAiLinks() {
|
|
4072
|
+
var as = app.ownerDocument.querySelectorAll("a[data-ai-prompt]");
|
|
4073
|
+
for (var i = 0; i < as.length; i++) as[i].href = codeUrl(as[i].getAttribute("data-ai-prompt"));
|
|
3895
4074
|
}
|
|
3896
4075
|
// editUrl(.../edit/<branch>/<path>)からリポジトリ相対のファイルパスを取り出す(プロンプトで対象を指し示すため)
|
|
3897
4076
|
function repoPathOf(rec) {
|
|
@@ -4022,7 +4201,7 @@ ${deriveCapTile.toString()}
|
|
|
4022
4201
|
if (!g) return null;
|
|
4023
4202
|
var prompt = aiHead(g.skill) + g.add + AI_COMMON;
|
|
4024
4203
|
return el("div", { class: "ai-add-bar" }, [
|
|
4025
|
-
|
|
4204
|
+
aiLink("ai-add-btn", prompt, "+ AIで登録"),
|
|
4026
4205
|
]);
|
|
4027
4206
|
}
|
|
4028
4207
|
// 各Goldレコードの「AIで編集」ボタン(GitHubで編集の隣)。スキルの無い型では null
|
|
@@ -4034,7 +4213,7 @@ ${deriveCapTile.toString()}
|
|
|
4034
4213
|
var target = (pathRef ? pathRef + " と " : "") + g.dir + "/README.md";
|
|
4035
4214
|
var subject = "この" + g.noun + "「" + title + "」" + (pathRef ? "" : "(" + rec.id + ")");
|
|
4036
4215
|
var prompt = aiHead(g.skill) + g.edit(target, subject) + AI_COMMON;
|
|
4037
|
-
return
|
|
4216
|
+
return aiLink("ai-edit-btn", prompt, "AIで編集");
|
|
4038
4217
|
}
|
|
4039
4218
|
|
|
4040
4219
|
// ---------- AIからの更新提案(.cortex/gold-proposals.json 由来) ----------
|
|
@@ -4094,7 +4273,7 @@ ${deriveCapTile.toString()}
|
|
|
4094
4273
|
);
|
|
4095
4274
|
lines.push("書き込む前に、変更内容を提示して承認を得てください。");
|
|
4096
4275
|
var prompt = aiHead(g.skill) + lines.join("\\n") + AI_COMMON;
|
|
4097
|
-
return
|
|
4276
|
+
return aiLink("ai-apply-btn", prompt, "AIで適用");
|
|
4098
4277
|
}
|
|
4099
4278
|
|
|
4100
4279
|
/**
|
|
@@ -4342,7 +4521,7 @@ ${deriveCapTile.toString()}
|
|
|
4342
4521
|
challenges.forEach(function (c) {
|
|
4343
4522
|
var done = tutIsDone(c.n);
|
|
4344
4523
|
var card = el("div", { class: "tut-card" + (done ? " tut-done" : "") });
|
|
4345
|
-
var btn =
|
|
4524
|
+
var btn = aiLink("tut-btn", c.prompt, "🚀 挑戦する");
|
|
4346
4525
|
var cb = el("input", { type: "checkbox" });
|
|
4347
4526
|
cb.checked = done;
|
|
4348
4527
|
var check = el("label", { class: "tut-check" }, [cb, el("span", { text: "完了" })]);
|
|
@@ -4805,7 +4984,7 @@ ${deriveCapTile.toString()}
|
|
|
4805
4984
|
// (renderSiteIcon)——全タブから同じ場所で触れる形にした
|
|
4806
4985
|
head.appendChild(titleWrap);
|
|
4807
4986
|
var homeEditRow = el("div", { class: "detail-actions-row" }, [
|
|
4808
|
-
|
|
4987
|
+
aiLink("ai-edit-btn", HOME_EDIT_PROMPT, "AIで編集"),
|
|
4809
4988
|
]);
|
|
4810
4989
|
if (h.editUrl) {
|
|
4811
4990
|
homeEditRow.appendChild(el("a", { class: "edit-btn", href: h.editUrl, target: "_blank", rel: "noopener", text: "GitHubで編集" }));
|
|
@@ -4860,7 +5039,7 @@ ${deriveCapTile.toString()}
|
|
|
4860
5039
|
]),
|
|
4861
5040
|
]);
|
|
4862
5041
|
var actions = el("div", { class: "home-unfilled-actions" }, [
|
|
4863
|
-
|
|
5042
|
+
aiLink("ai-add-btn", HOME_WRITE_PROMPT, "AIで書く"),
|
|
4864
5043
|
]);
|
|
4865
5044
|
if (SITE.home && SITE.home.editUrl) {
|
|
4866
5045
|
actions.appendChild(el("a", { class: "edit-btn", href: SITE.home.editUrl, target: "_blank", rel: "noopener", text: "GitHubで編集" }));
|
|
@@ -7238,15 +7417,58 @@ ${deriveCapTile.toString()}
|
|
|
7238
7417
|
return sourceAdapter(s.type) === kind;
|
|
7239
7418
|
});
|
|
7240
7419
|
}
|
|
7241
|
-
/**
|
|
7420
|
+
/**
|
|
7421
|
+
* 行の見た目を決める値だけを並べる(時刻など毎回変わる値は入れない)。
|
|
7422
|
+
* 🔴 **「確かめられたか」も混ぜる(#409)。** 混ぜないと、応答が届いて
|
|
7423
|
+
* 注記の要否が変わっても署名が同じままで、**一覧が作り直されない**
|
|
7424
|
+
*(0件の一覧は行が増えないので、行だけを見ていると変化が現れない)
|
|
7425
|
+
*/
|
|
7242
7426
|
function externalsSig(kind, extra) {
|
|
7243
|
-
return JSON.stringify([liveExternals(kind), extra]);
|
|
7427
|
+
return JSON.stringify([liveExternals(kind), extra, registrationUnreadable()]);
|
|
7244
7428
|
}
|
|
7245
7429
|
/**
|
|
7246
7430
|
* **空の箱を置いてよいのは、そこへ足せる案件だけ。** 投入の口を持たない構成
|
|
7247
7431
|
* (素の Decisions ビュー)で行も無い能力に空の箱を積むと、押しても何も無いモーダルになる。
|
|
7248
7432
|
*/
|
|
7249
7433
|
function liveListWanted(kind) { return !!SITE.intake || liveExternals(kind).length > 0; }
|
|
7434
|
+
/**
|
|
7435
|
+
* 🔴 **「1件も登録されていない」と「確かめられなかった」を分ける(#409)。**
|
|
7436
|
+
*
|
|
7437
|
+
* サーバは登録ファイルを読めなかった能力を、**キーごと応答から落とす**
|
|
7438
|
+
*(「読めなかった」を「0件」と混ぜないため。intake の buildRegisteredNow)。
|
|
7439
|
+
* ところが画面はそれを「足す行が無い」としか受け取っていなかったので、
|
|
7440
|
+
* **確かめられなかった結果が「登録されていません」として出ていた**。
|
|
7441
|
+
* オーナーはそれを見て未登録だと読み、追加して「既に登録されています」と断られた。
|
|
7442
|
+
*
|
|
7443
|
+
* 🔴 **能力ごとのキーの有無で判定してはいけない(実装レビューの指摘)。** サーバは
|
|
7444
|
+
* 「読めなかった」ときだけでなく **「そのファイルがまだ無い」ときにも同じくキーを落とす**
|
|
7445
|
+
* (読んだ本文が null でも undefined でも、文字列でなければ同じ枝に落ちる)。
|
|
7446
|
+
* キーの有無で見ると、**その能力をまだ使っていない案件**——つまり立ち上げ直後の、
|
|
7447
|
+
* この画面が一番よく見られる状態——に**永久に消えない誤警報**が出る
|
|
7448
|
+
* (「再読み込みしてください」と言われても、設定ファイルは再読み込みでは生えない)。
|
|
7449
|
+
* しかも製品としては、その状態は「登録されていません」と言い切ると決めてある
|
|
7450
|
+
* (engine の capabilities: ファイルの有無は実装の都合で、利用者にはどちらも「まだ登録していない」)。
|
|
7451
|
+
*
|
|
7452
|
+
* **だから応答まるごとで見る。** 登録ファイルの一覧(registeredNow)と
|
|
7453
|
+
* 能力の状態(capabilitiesNow)は**サーバの同じ return から出る**ので、
|
|
7454
|
+
* **両方とも無いときだけ**が「そもそも読みに行けなかった」——今回の症状の
|
|
7455
|
+
* 3経路(ツリー取得の失敗・ツリーの打ち切り・応答そのものの失敗)は全部これに当たる。
|
|
7456
|
+
*
|
|
7457
|
+
* ⚠ この粒度では、**ファイル1本だけが読めなかった回**(8秒の打ち切り・単発の失敗)は
|
|
7458
|
+
* 拾えない。そちらはサーバがログを出す経路なので、まず記録から追えるようにしてある。
|
|
7459
|
+
*/
|
|
7460
|
+
function registrationUnreadable() {
|
|
7461
|
+
return liveAnswered && LIVE_REGISTERED === null && LIVE_CAPS === null;
|
|
7462
|
+
}
|
|
7463
|
+
/**
|
|
7464
|
+
* 空の一覧の代わりに出す1行。**帯は症状・行は一手・繰り返さない。**
|
|
7465
|
+
* 空の一覧そのものを出さない——今回オーナーが踏んだのは「空の一覧を見て未登録だと読んだ」ことなので。
|
|
7466
|
+
* 応答がまだ届いていない間は出さない(確かめる前に「確かめられなかった」と言わない)。
|
|
7467
|
+
*/
|
|
7468
|
+
function unverifiedListNote(noun) {
|
|
7469
|
+
if (!registrationUnreadable()) return null;
|
|
7470
|
+
return el("div", { class: "ds-note", text: "登録されている" + noun + "を確認できませんでした。再読み込みしてください。" });
|
|
7471
|
+
}
|
|
7250
7472
|
// **箱にはアダプタのタグを付ける(#153)。** pushCap の無タグは「どのタイルにも出す」
|
|
7251
7473
|
// 既定なので、タグを忘れると Slack の一覧が Google Chat モーダルに丸ごと出る
|
|
7252
7474
|
if (liveListWanted("slack")) pushCap("チャット", liveList(
|
|
@@ -7254,7 +7476,11 @@ ${deriveCapTile.toString()}
|
|
|
7254
7476
|
function () { return externalsSig("slack", slackWorkspaces()); },
|
|
7255
7477
|
function (box) {
|
|
7256
7478
|
var items = liveExternals("slack");
|
|
7257
|
-
if (!items.length)
|
|
7479
|
+
if (!items.length) {
|
|
7480
|
+
var noteSlack = unverifiedListNote("チャンネル");
|
|
7481
|
+
if (noteSlack) box.appendChild(noteSlack);
|
|
7482
|
+
return;
|
|
7483
|
+
}
|
|
7258
7484
|
var meta = DS_TYPE["slack"] || { icon: "🔗", label: "Slack" };
|
|
7259
7485
|
slackRows(items, meta).forEach(function (n) { box.appendChild(n); });
|
|
7260
7486
|
},
|
|
@@ -7266,7 +7492,11 @@ ${deriveCapTile.toString()}
|
|
|
7266
7492
|
function () { return externalsSig("google-chat", null); },
|
|
7267
7493
|
function (box) {
|
|
7268
7494
|
var items = liveExternals("google-chat");
|
|
7269
|
-
if (!items.length)
|
|
7495
|
+
if (!items.length) {
|
|
7496
|
+
var noteGchat = unverifiedListNote("スペース");
|
|
7497
|
+
if (noteGchat) box.appendChild(noteGchat);
|
|
7498
|
+
return;
|
|
7499
|
+
}
|
|
7270
7500
|
var meta = DS_TYPE["google-chat"] || { icon: "💬", label: "Google Chat" };
|
|
7271
7501
|
items.forEach(function (s) { box.appendChild(extSourceCard(s, meta)); });
|
|
7272
7502
|
},
|
|
@@ -7276,7 +7506,11 @@ ${deriveCapTile.toString()}
|
|
|
7276
7506
|
function () { return externalsSig("github", githubOwnersView() || null); },
|
|
7277
7507
|
function (box) {
|
|
7278
7508
|
var items = liveExternals("github");
|
|
7279
|
-
if (!items.length)
|
|
7509
|
+
if (!items.length) {
|
|
7510
|
+
var noteDev = unverifiedListNote("リポジトリ");
|
|
7511
|
+
if (noteDev) box.appendChild(noteDev);
|
|
7512
|
+
return;
|
|
7513
|
+
}
|
|
7280
7514
|
// **meta は行ごとに引く**(issues と discussions でアイコンとラベルが違う)
|
|
7281
7515
|
githubRows(items).forEach(function (n) { box.appendChild(n); });
|
|
7282
7516
|
},
|
|
@@ -9858,6 +10092,37 @@ ${deriveCapTile.toString()}
|
|
|
9858
10092
|
msg.textContent = "✗ " + (j.message || "保存できませんでした。");
|
|
9859
10093
|
return;
|
|
9860
10094
|
}
|
|
10095
|
+
// 🔴 **「既に登録されています」は失敗ではない(#409)。**
|
|
10096
|
+
//
|
|
10097
|
+
// サーバがそう答えた時点で、その接続先は**登録ファイルに確かに在る**。
|
|
10098
|
+
// それまでは赤い「✗」を出して終わっていたので、**一覧が空のまま**の画面で
|
|
10099
|
+
// 追加した人は「登録されているらしいが、どこにも出ていない」という袋小路に落ちた
|
|
10100
|
+
// (オーナーが cortex-test で踏んだ症状そのもの)。
|
|
10101
|
+
// **サーバが教えてくれた事実を、そのまま一覧に反映する。**
|
|
10102
|
+
//
|
|
10103
|
+
// 仮置き(#388)に足すのは、保存が効いたときと同じ扱いでよい——
|
|
10104
|
+
// 「登録ファイルに在る」という主張はどちらも同じで、仮置きは本物が追いつけば消える。
|
|
10105
|
+
if (isAlreadyRegistered(j.message)) {
|
|
10106
|
+
var known = notePendingOp(payload, hooks && typeof hooks.pendingTargets === "function" ? hooks.pendingTargets() : null, j);
|
|
10107
|
+
if (known) {
|
|
10108
|
+
// 帯は症状ではなく**いまの事実**。行が横に出るので、繰り返さない
|
|
10109
|
+
msg.className = "ds-toggle-msg is-ok";
|
|
10110
|
+
msg.textContent = "✓ " + j.message + "(一覧に出しました)";
|
|
10111
|
+
repaintOpenLists();
|
|
10112
|
+
endBusy();
|
|
10113
|
+
// 🔴 **成功と同じ後始末をする(実装レビューの指摘)。** onDone は入力欄を空にする
|
|
10114
|
+
// ——空にしないと、行が出たと言いながら URL が欄に残り、**もう一度押させる形**になる
|
|
10115
|
+
// (そのうえ「触っている最中」と見なされて、節ごとの差し替えが以後ずっと見送られる)。
|
|
10116
|
+
// 仮置きの entry を渡すので、控えの二重表示にはならない。
|
|
10117
|
+
// ⚠ **節ごとの差し替えは呼ばない。** 入力欄と結果メッセージを抱えたこの節は
|
|
10118
|
+
// 「触っている最中」なので、あちらは必ず早期 return する(呼んでも何も起きない)。
|
|
10119
|
+
// 一覧を追いつかせているのは上の repaintOpenLists。
|
|
10120
|
+
if (onDone) onDone(j, { pending: known });
|
|
10121
|
+
// **登録の即時読み取りを取りに行く。** 仮置きは繋ぎで、本物はサーバが持っている
|
|
10122
|
+
loadLiveFleet(true);
|
|
10123
|
+
return;
|
|
10124
|
+
}
|
|
10125
|
+
}
|
|
9861
10126
|
// サーバの文言をそのまま出す(貼り方の案内・取り違えの警告を含んでいる)。
|
|
9862
10127
|
// **例外は「他の人が先に変えていた」だけ**——サーバは再読み込みを案内するが、
|
|
9863
10128
|
// 画面は既に追いついているので、済んでいることをやらせる案内になる(#127)
|
|
@@ -9915,6 +10180,7 @@ ${deriveCapTile.toString()}
|
|
|
9915
10180
|
}
|
|
9916
10181
|
repaintOpenLists();
|
|
9917
10182
|
refreshDataSources();
|
|
10183
|
+
syncAiLinks();
|
|
9918
10184
|
}
|
|
9919
10185
|
if (hooks && typeof hooks.onSaved === "function") hooks.onSaved(j);
|
|
9920
10186
|
// **保存が効いたら、いまの登録を読み直す(段階1)。** 一覧はまとめ表(夜間バッチ)から
|
|
@@ -10928,6 +11194,10 @@ ${deriveCapTile.toString()}
|
|
|
10928
11194
|
repaintOpenLists();
|
|
10929
11195
|
// まだ足りない行があるなら取り直しを続ける(無くなったら止める)
|
|
10930
11196
|
refreshStateTimer();
|
|
11197
|
+
// 開発リポジトリが増えていれば AI 導線の href を組み直す(#404)。
|
|
11198
|
+
// 🔴 **後段の仕事より後ろに置く。** ここで投げると、図の復帰・取り直しタイマーが
|
|
11199
|
+
// 丸ごと落ちる(href を貼り替えるだけの処理に、そこまで巻き込ませない)
|
|
11200
|
+
syncAiLinks();
|
|
10931
11201
|
// 取得中に保存が効いていたら、迂回付きでもう一度取りに行く
|
|
10932
11202
|
if (freshWanted) loadLiveFleet();
|
|
10933
11203
|
})
|
|
@@ -10950,6 +11220,12 @@ ${deriveCapTile.toString()}
|
|
|
10950
11220
|
// **失敗しても取り直しは続ける**(期限が来れば止まる)。ここで止めると、
|
|
10951
11221
|
// 一時的な失敗のあと画面が二度と追いつかない
|
|
10952
11222
|
refreshStateTimer();
|
|
11223
|
+
// 🔴 **ここでも AI 導線を貼り替える(#404)。** 「失敗」に見えて、
|
|
11224
|
+
// **登録の即時値(registeredNow)は既に読めている**ことがある——まとめ表
|
|
11225
|
+
// (fleet-status.json)をまだ持たない案件は、上で fleet が無いと投げてここへ来る。
|
|
11226
|
+
// つまり**立ち上げ直後の案件=開発リポジトリを登録したばかりの案件**では、
|
|
11227
|
+
// この行だけがヘッダーの導線を追いつかせる。**後段の仕事より後ろに置く**(上と同じ理由)
|
|
11228
|
+
syncAiLinks();
|
|
10953
11229
|
// 取得中に保存が効いていたら、迂回付きでもう一度取りに行く
|
|
10954
11230
|
if (freshWanted) loadLiveFleet();
|
|
10955
11231
|
});
|
|
@@ -11003,7 +11279,7 @@ ${deriveCapTile.toString()}
|
|
|
11003
11279
|
el("span", { text: ": 精製された確定情報を読む" }),
|
|
11004
11280
|
]));
|
|
11005
11281
|
var codeLi = el("li", {}, [
|
|
11006
|
-
el("a", { href:
|
|
11282
|
+
el("a", { href: CLAUDE_CODE_HOME, target: "_blank", rel: "noopener", text: "Claude Code" }),
|
|
11007
11283
|
el("span", { text: ": 議事録・課題・資料の生データまで含めて、AIに質問・調査する" }),
|
|
11008
11284
|
]);
|
|
11009
11285
|
ways.appendChild(codeLi);
|
|
@@ -14016,6 +14292,10 @@ ${deriveCapTile.toString()}
|
|
|
14016
14292
|
// 保存直後の mirror とメニューの開閉が、タブを移るたびに畳まれる
|
|
14017
14293
|
renderSiteIcon();
|
|
14018
14294
|
route();
|
|
14295
|
+
// AI 導線の href を起動時に1回だけ組み直す(#404)。仮置き(localStorage)は
|
|
14296
|
+
// ビルド時には存在しないので、**焼いた href は起動の時点で既に古いことがある**。
|
|
14297
|
+
// 🔴 route() の後に置く——ヘッダー以外の導線は route() が描くまで DOM に無い
|
|
14298
|
+
syncAiLinks();
|
|
14019
14299
|
/**
|
|
14020
14300
|
* 接続状況の先読み(#264)。**開いたタブに関係なく、起動時に1回だけ取りに行く。**
|
|
14021
14301
|
*
|
package/package.json
CHANGED