@takagaki/cortex-decisions-viewer 0.12.110 → 0.12.112
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 +313 -89
- package/dist/engagement.js +2 -0
- package/dist/evidence-names.js +100 -0
- package/dist/rederivation.js +31 -6
- package/dist/render.js +43 -93
- package/package.json +1 -1
package/dist/build.js
CHANGED
|
@@ -49,6 +49,7 @@ exports.buildHomeGrounding = buildHomeGrounding;
|
|
|
49
49
|
exports.collectMeetings = collectMeetings;
|
|
50
50
|
exports.findBacklogDir = findBacklogDir;
|
|
51
51
|
exports.figmaUrlFromRef = figmaUrlFromRef;
|
|
52
|
+
exports.identifyEvidence = identifyEvidence;
|
|
52
53
|
exports.bodyFileSlug = bodyFileSlug;
|
|
53
54
|
exports.bodyStampId = bodyStampId;
|
|
54
55
|
exports.bodyStampComment = bodyStampComment;
|
|
@@ -61,6 +62,7 @@ const gray_matter_1 = __importDefault(require("gray-matter"));
|
|
|
61
62
|
const marked_1 = require("marked");
|
|
62
63
|
const sanitize_html_1 = __importDefault(require("sanitize-html"));
|
|
63
64
|
const canonical_evidence_1 = require("./canonical-evidence");
|
|
65
|
+
const evidence_names_1 = require("./evidence-names");
|
|
64
66
|
const repo_1 = require("./repo");
|
|
65
67
|
const engagement_1 = require("./engagement");
|
|
66
68
|
const story_1 = require("./story");
|
|
@@ -1500,6 +1502,123 @@ async function buildTargetUrlMap(repoRoot, repoBaseUrl, branch) {
|
|
|
1500
1502
|
}
|
|
1501
1503
|
return map;
|
|
1502
1504
|
}
|
|
1505
|
+
/**
|
|
1506
|
+
* 根拠の**読める名前の帳**を組む(#968 tools 1・`evidence-names.ts`)。材料は写し(課題管理・共有資料・
|
|
1507
|
+
* デザイン)と、engine が置いた `fleet-status.json` のチャンネルの一覧。読めないファイル・無い置き場は黙って飛ばす(帳が薄くなるだけ)。
|
|
1508
|
+
*
|
|
1509
|
+
* 🔴 **`buildTargetUrlMap` は触らない**(移る間だけ残す表・詳細画面の飛び先の正本)。ここは名前と、
|
|
1510
|
+
* 写しのパス → 根拠の ID の対応だけを持つ。資料の題名を Drive の鍵にも載せるために、表の URL を読むだけ
|
|
1511
|
+
*/
|
|
1512
|
+
async function buildEvidenceBook(repoRoot, targetUrls, externalSources = []) {
|
|
1513
|
+
const book = (0, evidence_names_1.emptyEvidenceBook)();
|
|
1514
|
+
const rel = (file) => path.relative(repoRoot, file).split(path.sep).join("/");
|
|
1515
|
+
const heading = (text) => text.match(/^#\s+(.+?)\s*$/m)?.[1] ?? null;
|
|
1516
|
+
const foldKeyOf = (url) => {
|
|
1517
|
+
const c = url ? (0, canonical_evidence_1.canonicalEvidence)(url) : null;
|
|
1518
|
+
return c && c.kind !== "url" ? c.parentKey ?? c.key : null;
|
|
1519
|
+
};
|
|
1520
|
+
const putName = (key, name) => {
|
|
1521
|
+
if (key && name && !book.names.has(key))
|
|
1522
|
+
book.names.set(key, name);
|
|
1523
|
+
};
|
|
1524
|
+
// 課題(キー → 「キー 題名」)・Backlog のドキュメントと Wiki(URL の鍵 → 題名)
|
|
1525
|
+
const backlogDir = await findBacklogDir(repoRoot, "課題管理");
|
|
1526
|
+
for (const sub of BACKLOG_SECTIONS) {
|
|
1527
|
+
for (const file of await walkMd(path.join(repoRoot, backlogDir, sub))) {
|
|
1528
|
+
try {
|
|
1529
|
+
const head = (await node_fs_1.promises.readFile(file, "utf8")).slice(0, 4000);
|
|
1530
|
+
const title = heading(head);
|
|
1531
|
+
const key = head.match(/^- 課題キー: (\S+)$/m)?.[1];
|
|
1532
|
+
const link = key ? null : head.match(/\[Backlog (?:Document|Wiki) Link\]\((https?:[^)]+)\)/)?.[1];
|
|
1533
|
+
const ref = key ?? link;
|
|
1534
|
+
if (!ref)
|
|
1535
|
+
continue;
|
|
1536
|
+
if (key)
|
|
1537
|
+
putName(key.toLowerCase(), title ? `${key} ${title}` : key);
|
|
1538
|
+
else
|
|
1539
|
+
putName(foldKeyOf(link), title);
|
|
1540
|
+
book.pathRefs.set(rel(file), ref);
|
|
1541
|
+
const tail = (0, evidence_names_1.backlogTailOf)(rel(file));
|
|
1542
|
+
if (tail && !book.backlogTailRefs.has(tail))
|
|
1543
|
+
book.backlogTailRefs.set(tail, ref);
|
|
1544
|
+
}
|
|
1545
|
+
catch {
|
|
1546
|
+
// 読めないファイルはスキップ
|
|
1547
|
+
}
|
|
1548
|
+
}
|
|
1549
|
+
}
|
|
1550
|
+
// 資料(`material:` と元の Drive の鍵 → 題名・写しのパス → `material:`)
|
|
1551
|
+
for (const file of await walkMd(path.join(repoRoot, MATERIALS_DIR))) {
|
|
1552
|
+
if (path.basename(file).toLowerCase() === "link.md")
|
|
1553
|
+
continue;
|
|
1554
|
+
try {
|
|
1555
|
+
const { data } = (0, gray_matter_1.default)(await node_fs_1.promises.readFile(file, "utf8"));
|
|
1556
|
+
const stem = path.basename(file).replace(/\.md$/i, "");
|
|
1557
|
+
const id = data.type === "material" && data.id ? String(data.id) : `material:${stem}`;
|
|
1558
|
+
const title = typeof data.title === "string" && data.title.trim() ? data.title.trim() : stem;
|
|
1559
|
+
putName(id, title);
|
|
1560
|
+
putName(foldKeyOf(targetUrls.get(id)), title);
|
|
1561
|
+
book.pathRefs.set(rel(file), id);
|
|
1562
|
+
}
|
|
1563
|
+
catch {
|
|
1564
|
+
// 読めないファイルはスキップ
|
|
1565
|
+
}
|
|
1566
|
+
}
|
|
1567
|
+
// デザイン(`design:` と Figma の鍵 → 画面・ファイルの名前・写しのパス → `design:`)。
|
|
1568
|
+
// 指摘の月ごとのファイルは、同じ置き場の README の `design:`(ファイル全体)を指す
|
|
1569
|
+
for (const sub of ["inventory", "指摘"]) {
|
|
1570
|
+
const readmeIdByDir = new Map();
|
|
1571
|
+
const monthFiles = [];
|
|
1572
|
+
for (const file of await walkMd(path.join(repoRoot, DESIGN_DIR, sub))) {
|
|
1573
|
+
try {
|
|
1574
|
+
const body = await node_fs_1.promises.readFile(file, "utf8");
|
|
1575
|
+
const id = body.match(/^- 参照ID: `(design:[^`]+)`\s*$/m)?.[1];
|
|
1576
|
+
if (!id) {
|
|
1577
|
+
if (/^\d{4}-\d{2}\.md$/.test(path.basename(file)))
|
|
1578
|
+
monthFiles.push(file);
|
|
1579
|
+
continue;
|
|
1580
|
+
}
|
|
1581
|
+
const title = heading(body);
|
|
1582
|
+
putName(id, title);
|
|
1583
|
+
putName(foldKeyOf(targetUrls.get(id) ?? figmaUrlFromRef(id)), title);
|
|
1584
|
+
book.pathRefs.set(rel(file), id);
|
|
1585
|
+
if (path.basename(file).toLowerCase() === "readme.md")
|
|
1586
|
+
readmeIdByDir.set(path.dirname(file), id);
|
|
1587
|
+
}
|
|
1588
|
+
catch {
|
|
1589
|
+
// 読めないファイルはスキップ
|
|
1590
|
+
}
|
|
1591
|
+
}
|
|
1592
|
+
for (const file of monthFiles) {
|
|
1593
|
+
const id = readmeIdByDir.get(path.dirname(file));
|
|
1594
|
+
if (id)
|
|
1595
|
+
book.pathRefs.set(rel(file), id);
|
|
1596
|
+
}
|
|
1597
|
+
}
|
|
1598
|
+
// チャット(チャンネルの鍵 → `#名前`・名前 → URL)。同じ名前に違う URL が 2 本なら名前からは引かない。
|
|
1599
|
+
// 🔴 **登録簿(`チャット/channels.json`)は読まない**(engine #275・`no-registry-read.test.mjs`)。engine が
|
|
1600
|
+
// 登録簿から算出して置いた `fleet-status.json` の `externalSources` を読む
|
|
1601
|
+
const ambiguous = new Set();
|
|
1602
|
+
for (const src of externalSources) {
|
|
1603
|
+
const url = src.url ?? "";
|
|
1604
|
+
const bare = src.name.trim().replace(/^#/, "");
|
|
1605
|
+
const key = foldKeyOf(url);
|
|
1606
|
+
if (!url || !bare || !key || !(key.startsWith("slack:") || key.startsWith("gchat:")))
|
|
1607
|
+
continue;
|
|
1608
|
+
putName(key, key.startsWith("slack:") ? `#${bare}` : bare);
|
|
1609
|
+
if (!key.startsWith("slack:"))
|
|
1610
|
+
continue;
|
|
1611
|
+
const k = bare.toLowerCase();
|
|
1612
|
+
const prev = book.slackUrlsByName.get(k);
|
|
1613
|
+
if (prev !== undefined && foldKeyOf(prev) !== key)
|
|
1614
|
+
ambiguous.add(k);
|
|
1615
|
+
else
|
|
1616
|
+
book.slackUrlsByName.set(k, url);
|
|
1617
|
+
}
|
|
1618
|
+
for (const k of ambiguous)
|
|
1619
|
+
book.slackUrlsByName.delete(k);
|
|
1620
|
+
return book;
|
|
1621
|
+
}
|
|
1503
1622
|
/**
|
|
1504
1623
|
* 指摘のスレッドから `figma-comment:{fileKey}:{commentId}` → 「Figmaでこの指摘を開く」の URL を拾う。
|
|
1505
1624
|
*
|
|
@@ -1609,6 +1728,9 @@ const NO_ORIGINAL_TOOL_LABEL = "元のツールが無い根拠";
|
|
|
1609
1728
|
function readableEvidenceLabel(kind, key, fallback) {
|
|
1610
1729
|
switch (kind) {
|
|
1611
1730
|
case "backlog":
|
|
1731
|
+
// Wiki・ドキュメントの鍵(`backlog:{host}:wiki:N`)は機械の語なので、種類の名前にする
|
|
1732
|
+
if (key.startsWith("backlog:"))
|
|
1733
|
+
return key.includes(":wiki:") ? "Backlog の Wiki" : "Backlog のドキュメント";
|
|
1612
1734
|
return key.toUpperCase();
|
|
1613
1735
|
case "github":
|
|
1614
1736
|
return key.replace(/^gh:/, "");
|
|
@@ -1622,11 +1744,116 @@ function readableEvidenceLabel(kind, key, fallback) {
|
|
|
1622
1744
|
case "slack":
|
|
1623
1745
|
return "Slack のメッセージ";
|
|
1624
1746
|
case "gchat":
|
|
1625
|
-
return "Google Chat
|
|
1747
|
+
return "Google Chat のスペース";
|
|
1626
1748
|
default:
|
|
1627
1749
|
return fallback;
|
|
1628
1750
|
}
|
|
1629
1751
|
}
|
|
1752
|
+
/** 課題キーだけの値(`buildGraph` の旧経路と同じ形) */
|
|
1753
|
+
const ISSUE_KEY_ONLY_RE = /^[A-Z][A-Z0-9_]*-\d+$/;
|
|
1754
|
+
/** `owner/repo#N` だけの値(`canonicalEvidence` の `WHOLE_GH_SHORT_RE` と同じ形) */
|
|
1755
|
+
const GH_SHORT_ONLY_RE = /^[A-Za-z0-9._-]+\/[A-Za-z0-9._-]+#\d+$/;
|
|
1756
|
+
/**
|
|
1757
|
+
* 根拠の値(URL・`[ラベル](URL)`・課題キー・`owner/repo#N`・`minute:`/`material:`/`design:`/`figma-comment:`・
|
|
1758
|
+
* 写しのパス・`[slack] #名前 (…)`)から、点の鍵と読める名前を決める(#968 tools 1)。
|
|
1759
|
+
*
|
|
1760
|
+
* 🔴 **今の ID の根拠と新しい URL の根拠を同じ鍵に寄せる。** ID は対応表(`targetUrls`)で URL に引き、
|
|
1761
|
+
* `canonicalEvidence` の `key` にする(コメントは親の鍵)。写しのパスは帳で ID に引いてから同じ道を通す。
|
|
1762
|
+
* cortex の Gold は付け替えの途中で ID と URL が混ざった状態が数晩続く(便 3 の on の夜から)ので、
|
|
1763
|
+
* ここが割れると同じ課題・同じ資料の点が 2 つに分かれる。
|
|
1764
|
+
*
|
|
1765
|
+
* 根拠ではない値(Gold の ID・崩れた文字・帳に無いパス)は `null`——呼び出し側が今までどおり扱う。
|
|
1766
|
+
*/
|
|
1767
|
+
function identifyEvidence(value, ctx, humanLabel) {
|
|
1768
|
+
const { book, targetUrls, dropLink } = ctx;
|
|
1769
|
+
let raw = String(value ?? "").trim();
|
|
1770
|
+
let human = humanLabel && humanLabel.trim() ? humanLabel.trim() : undefined;
|
|
1771
|
+
if (!raw)
|
|
1772
|
+
return null;
|
|
1773
|
+
// 解決器(`createRefResolver`)と同じ受け方(URL の中の空白も受ける)
|
|
1774
|
+
const md = raw.match(/^\[([^\]]+)\]\((https?:\/\/[^)]+)\)$/i);
|
|
1775
|
+
if (md) {
|
|
1776
|
+
human = human ?? md[1].trim();
|
|
1777
|
+
raw = md[2];
|
|
1778
|
+
}
|
|
1779
|
+
const channel = (0, evidence_names_1.slackChannelOfLabel)(raw);
|
|
1780
|
+
if (channel) {
|
|
1781
|
+
const url = book.slackUrlsByName.get(channel.toLowerCase());
|
|
1782
|
+
if (!url)
|
|
1783
|
+
return { id: `slack-name:${channel.toLowerCase()}`, label: `#${channel}`, rank: 2, group: "silver", kind: "slack" };
|
|
1784
|
+
human = human ?? `#${channel}`;
|
|
1785
|
+
raw = url;
|
|
1786
|
+
}
|
|
1787
|
+
// 自分のリポジトリの写しへの blob URL は、写しのパスに戻してから引く(同じ会議・課題の点に畳む)
|
|
1788
|
+
if (ctx.blobPrefix && raw.startsWith(ctx.blobPrefix)) {
|
|
1789
|
+
try {
|
|
1790
|
+
// `{branch}/{path}`(ブランチの段を外す)
|
|
1791
|
+
raw = decodeURIComponent(raw.slice(ctx.blobPrefix.length).replace(/[?#].*$/, "").replace(/^[^/]+\//, ""));
|
|
1792
|
+
}
|
|
1793
|
+
catch {
|
|
1794
|
+
// 崩れたエスケープはそのまま
|
|
1795
|
+
}
|
|
1796
|
+
}
|
|
1797
|
+
const tail = (0, evidence_names_1.backlogTailOf)(raw);
|
|
1798
|
+
raw = book.pathRefs.get(raw) ?? (tail ? book.backlogTailRefs.get(tail) : undefined) ?? (0, evidence_names_1.minuteRefOfPath)(raw) ?? raw;
|
|
1799
|
+
const rankOf = (named) => (named ? 3 : human ? 2 : 1);
|
|
1800
|
+
if (ISSUE_KEY_ONLY_RE.test(raw)) {
|
|
1801
|
+
const key = raw.toLowerCase();
|
|
1802
|
+
const name = book.names.get(key);
|
|
1803
|
+
return { id: key, label: name ?? raw, rank: name ? 3 : 1, group: "bronze", url: dropLink(targetUrls.get(raw)), kind: "backlog" };
|
|
1804
|
+
}
|
|
1805
|
+
if (raw.startsWith("minute:")) {
|
|
1806
|
+
const named = (0, evidence_names_1.minuteLabel)(raw);
|
|
1807
|
+
return {
|
|
1808
|
+
id: (0, evidence_names_1.minuteFoldKey)(raw) ?? raw,
|
|
1809
|
+
label: named ?? human ?? "会議の議事録",
|
|
1810
|
+
rank: named ? 3 : rankOf(false),
|
|
1811
|
+
group: "silver",
|
|
1812
|
+
url: dropLink(targetUrls.get(raw)),
|
|
1813
|
+
kind: "minute",
|
|
1814
|
+
};
|
|
1815
|
+
}
|
|
1816
|
+
let idRef = null;
|
|
1817
|
+
let url = null;
|
|
1818
|
+
if (/^(material|design|figma-comment):/.test(raw)) {
|
|
1819
|
+
idRef = raw;
|
|
1820
|
+
url = targetUrls.get(raw) ?? figmaUrlFromRef(raw);
|
|
1821
|
+
}
|
|
1822
|
+
else if (/^https?:\/\//i.test(raw) || GH_SHORT_ONLY_RE.test(raw)) {
|
|
1823
|
+
url = raw;
|
|
1824
|
+
}
|
|
1825
|
+
else {
|
|
1826
|
+
return null;
|
|
1827
|
+
}
|
|
1828
|
+
const canon = url ? (0, canonical_evidence_1.canonicalEvidence)(url) : null;
|
|
1829
|
+
if (!canon || canon.kind === "url") {
|
|
1830
|
+
if (idRef) {
|
|
1831
|
+
const name = book.names.get(idRef);
|
|
1832
|
+
return { id: idRef, label: name ?? human ?? (0, evidence_names_1.idFallbackLabel)(idRef), rank: rankOf(!!name), group: "silver", url: dropLink(url), kind: "id" };
|
|
1833
|
+
}
|
|
1834
|
+
// 対応表に無い種類の URL。今までどおり構文を正規化した URL を鍵にする
|
|
1835
|
+
const id = canon ? canon.url : raw;
|
|
1836
|
+
return { id, label: human ?? raw.replace(/^https?:\/\//i, "").slice(0, 60), rank: rankOf(false), group: "external", url: dropLink(id), kind: "url" };
|
|
1837
|
+
}
|
|
1838
|
+
const foldKey = canon.parentKey ?? canon.key;
|
|
1839
|
+
// 飛び先も親(コメント・返信の断片を外した課題・Issue)にそろえる——点は親なので
|
|
1840
|
+
const link = canon.parentKey ? canon.url.replace(/#.*$/, "") : canon.url;
|
|
1841
|
+
let name = book.names.get(foldKey) ?? (idRef ? book.names.get(idRef) : undefined);
|
|
1842
|
+
if (!name && canon.kind === "slack") {
|
|
1843
|
+
const ch = book.names.get(foldKey.replace(/:p\d+$/, ""));
|
|
1844
|
+
if (ch)
|
|
1845
|
+
name = foldKey.includes(":p") ? `${ch} のメッセージ` : ch;
|
|
1846
|
+
}
|
|
1847
|
+
const fallback = canon.kind === "slack" && !foldKey.includes(":p") ? "Slack のチャンネル" : readableEvidenceLabel(canon.kind, foldKey, raw);
|
|
1848
|
+
return {
|
|
1849
|
+
id: foldKey,
|
|
1850
|
+
label: name ?? human ?? fallback,
|
|
1851
|
+
rank: rankOf(!!name),
|
|
1852
|
+
group: EVIDENCE_GROUP[canon.kind] ?? "external",
|
|
1853
|
+
url: dropLink(link),
|
|
1854
|
+
kind: canon.kind,
|
|
1855
|
+
};
|
|
1856
|
+
}
|
|
1630
1857
|
/**
|
|
1631
1858
|
* 全レコードのfrontmatter(relations / source / references)と本文リンクから関係グラフを構築する(engine #551・分類A 10/1)。
|
|
1632
1859
|
*
|
|
@@ -1644,7 +1871,7 @@ function readableEvidenceLabel(kind, key, fallback) {
|
|
|
1644
1871
|
* ほうがよい(`createRefResolver` と同じ考え方)。崩れた文字(宛先に見えない参照)も同様に、
|
|
1645
1872
|
* 点は作るが飛び先は付けない。
|
|
1646
1873
|
*/
|
|
1647
|
-
function buildGraph(decisions, home, sections, targetUrls, repoBaseUrl) {
|
|
1874
|
+
function buildGraph(decisions, home, sections, targetUrls, repoBaseUrl, book = (0, evidence_names_1.emptyEvidenceBook)()) {
|
|
1648
1875
|
const nodes = new Map();
|
|
1649
1876
|
const edges = [];
|
|
1650
1877
|
const addNode = (node) => {
|
|
@@ -1685,83 +1912,56 @@ function buildGraph(decisions, home, sections, targetUrls, repoBaseUrl) {
|
|
|
1685
1912
|
// (leaderの実測で77件)を無くすのが今回の決定の本題なので、ここも揃えて飛び先を外す
|
|
1686
1913
|
// (`graph-gold-only.test.mjs` の「議事録を根拠にした決定」で固定)。
|
|
1687
1914
|
const dropMirrorBlob = (url) => url == null || (repoBaseUrl != null && url.startsWith(`${repoBaseUrl}/blob/`)) ? undefined : url;
|
|
1688
|
-
//
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
const evidenceKeyToNodeId = new Map();
|
|
1915
|
+
// 根拠の点の名前の強さ(`EvidenceIdentity.rank`)。同じ点に強い名前が後から来たら上書きする
|
|
1916
|
+
const labelRank = new Map();
|
|
1917
|
+
const identityCtx = { book, targetUrls, dropLink: dropMirrorBlob, blobPrefix: repoBaseUrl ? `${repoBaseUrl}/blob/` : undefined };
|
|
1692
1918
|
/**
|
|
1693
|
-
*
|
|
1694
|
-
*
|
|
1919
|
+
* 根拠を、畳む鍵(`identifyEvidence`)で 1 つの点にする(#968 tools 1)。同じ課題・資料・会議を、
|
|
1920
|
+
* 課題キー・URL・コメントの URL・`material:`・写しのパスのどの形で指しても同じ点に集まる。
|
|
1921
|
+
* 先にできた点の名前が弱ければ(種類の既定・URL)、強い名前(帳の題名・人が付けたリンクの文字)で上書きし、
|
|
1922
|
+
* 飛び先が無ければ後から来た飛び先を付ける
|
|
1695
1923
|
*/
|
|
1696
|
-
const
|
|
1697
|
-
const
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
return
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
evidenceKeyToNodeId.set(foldKey, nodeId);
|
|
1714
|
-
return nodeId;
|
|
1924
|
+
const addEvidence = (ev) => {
|
|
1925
|
+
const existing = nodes.get(ev.id);
|
|
1926
|
+
// Gold の記録・宛先の引けない点と鍵がぶつかったときは、その点の名前・飛び先を書き換えない
|
|
1927
|
+
if (existing && !labelRank.has(ev.id))
|
|
1928
|
+
return ev.id;
|
|
1929
|
+
if (!existing) {
|
|
1930
|
+
addNode(ev.url ? { id: ev.id, label: ev.label, group: ev.group, url: ev.url } : { id: ev.id, label: ev.label, group: ev.group });
|
|
1931
|
+
labelRank.set(ev.id, ev.rank);
|
|
1932
|
+
return ev.id;
|
|
1933
|
+
}
|
|
1934
|
+
if (ev.rank > (labelRank.get(ev.id) ?? 0)) {
|
|
1935
|
+
existing.label = ev.label;
|
|
1936
|
+
labelRank.set(ev.id, ev.rank);
|
|
1937
|
+
}
|
|
1938
|
+
if (!existing.url && ev.url)
|
|
1939
|
+
existing.url = ev.url;
|
|
1940
|
+
return ev.id;
|
|
1715
1941
|
};
|
|
1716
1942
|
// 参照先IDから層を推定してノード化する
|
|
1717
|
-
const classifyTarget = (target) => {
|
|
1718
|
-
if (nodes.has(target))
|
|
1943
|
+
const classifyTarget = (target, humanLabel) => {
|
|
1944
|
+
if (nodes.has(target) && !labelRank.has(target))
|
|
1719
1945
|
return target;
|
|
1720
1946
|
// パス参照でも、末尾がレコードのファイル名ならレコードノードへ解決する
|
|
1721
1947
|
const base = target.split("/").pop() ?? target;
|
|
1722
1948
|
const resolved = fileNameToId.get(base);
|
|
1723
1949
|
if (resolved && nodes.has(resolved))
|
|
1724
1950
|
return resolved;
|
|
1725
|
-
let group = "phantom";
|
|
1726
|
-
let label = target;
|
|
1727
1951
|
if (target.startsWith(NO_ORIGINAL_TOOL_PREFIX)) {
|
|
1728
1952
|
// 元ツールが無い根拠(engine #968 の将来の語彙)。リンクにせず、機械の語(urn:…)を出さない
|
|
1729
1953
|
return addNode({ id: target, label: NO_ORIGINAL_TOOL_LABEL, group: "phantom" });
|
|
1730
1954
|
}
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
//
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
}
|
|
1741
|
-
else if (/^[A-Z][A-Z0-9_]*-\d+$/.test(target)) {
|
|
1742
|
-
// 課題キー → 同期mdから解決した実Backlog URLに飛べるようにする(#968 便1までは1バイトも変えない表示)。
|
|
1743
|
-
// 同じ課題が先にURLの形で現れていれば、その点に畳む
|
|
1744
|
-
group = "bronze";
|
|
1745
|
-
const foldKey = target.toLowerCase();
|
|
1746
|
-
const existing = evidenceKeyToNodeId.get(foldKey);
|
|
1747
|
-
if (existing && nodes.has(existing))
|
|
1748
|
-
return existing;
|
|
1749
|
-
const url = dropMirrorBlob(targetUrls.get(target));
|
|
1750
|
-
const nodeId = addNode(url ? { id: target, label, group, url } : { id: target, label, group });
|
|
1751
|
-
evidenceKeyToNodeId.set(foldKey, nodeId);
|
|
1752
|
-
return nodeId;
|
|
1753
|
-
}
|
|
1754
|
-
else if (/^https?:\/\//.test(target)) {
|
|
1755
|
-
return addEvidenceUrlNode(target, target.replace(/^https?:\/\//, "").slice(0, 60));
|
|
1756
|
-
}
|
|
1757
|
-
else if (target.includes("/") || target.toLowerCase().endsWith(".md")) {
|
|
1758
|
-
// リポジトリ内ファイルへの生のパス参照(同期ミラーの議事録・資料など)。
|
|
1759
|
-
// 元ツールのURLがまだ無い記録は、点にするが飛び先を付けない(#968 の書き換えを待つ)
|
|
1760
|
-
group = "silver";
|
|
1761
|
-
label = target.split("/").pop() ?? target;
|
|
1762
|
-
return addNode({ id: target, label, group });
|
|
1763
|
-
}
|
|
1764
|
-
return addNode({ id: target, label, group });
|
|
1955
|
+
const ev = identifyEvidence(target, identityCtx, humanLabel);
|
|
1956
|
+
if (ev)
|
|
1957
|
+
return addEvidence(ev);
|
|
1958
|
+
if (target.includes("/") || target.toLowerCase().endsWith(".md")) {
|
|
1959
|
+
// リポジトリ内ファイルへの生のパス参照で、帳(写しの課題・資料・議事録)に当たらないもの。
|
|
1960
|
+
// 元ツールのURLがまだ無い記録は、点にするが飛び先を付けない(#968 の書き換えを待つ)。
|
|
1961
|
+
// 名前は最後の段から拡張子を外したもの(`.md` のようなファイルの語を出さない)
|
|
1962
|
+
return addNode({ id: target, label: (0, evidence_names_1.pathFallbackLabel)(target), group: "silver" });
|
|
1963
|
+
}
|
|
1964
|
+
return addNode({ id: target, label: target, group: "phantom" });
|
|
1765
1965
|
};
|
|
1766
1966
|
const addRelationEdges = (fromId, relations) => {
|
|
1767
1967
|
for (const r of relations) {
|
|
@@ -1777,17 +1977,15 @@ function buildGraph(decisions, home, sections, targetUrls, repoBaseUrl) {
|
|
|
1777
1977
|
};
|
|
1778
1978
|
const addReferenceEdges = (fromId, references) => {
|
|
1779
1979
|
for (const ref of references) {
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
edges.push({ from: fromId, to:
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
edges.push({ from: fromId, to: classifyTarget(ref.label), rel: "reference" });
|
|
1790
|
-
}
|
|
1980
|
+
// 点の鍵は**書かれていた値**から決める(解決器の飛び先は blob の写しのこともあるので鍵にしない)。
|
|
1981
|
+
// マークダウンリンクの表示文字列(人が付けた名前)は、帳の題名が無いときの名前に使う
|
|
1982
|
+
// (`[名前](URL)` の名前は `identifyEvidence` が書かれていた値から拾う。解決器が付けた名前は
|
|
1983
|
+
// 種類の既定のこともあるので、人の名前としては渡さない)
|
|
1984
|
+
const raw = REF_RAW.get(ref);
|
|
1985
|
+
if (raw !== undefined)
|
|
1986
|
+
edges.push({ from: fromId, to: classifyTarget(raw), rel: "reference" });
|
|
1987
|
+
else
|
|
1988
|
+
edges.push({ from: fromId, to: classifyTarget(ref.url ?? ref.label, ref.url && ref.label !== ref.url ? ref.label : undefined), rel: "reference" });
|
|
1791
1989
|
}
|
|
1792
1990
|
};
|
|
1793
1991
|
const addLinkEdges = (fromId, links) => {
|
|
@@ -2165,7 +2363,11 @@ async function build(opts) {
|
|
|
2165
2363
|
const targetUrls = knowledgeRoot
|
|
2166
2364
|
? await buildTargetUrlMap(repoRoot, repo.baseUrl, repo.branch)
|
|
2167
2365
|
: new Map();
|
|
2168
|
-
|
|
2366
|
+
// fleet-status.json は 1 回だけ読む(読み手が警告を出すので、2 回読むと警告が 2 行になる)。
|
|
2367
|
+
// 名前の帳(チャンネル名)と、下の「データソースと自動化」で同じものを使う
|
|
2368
|
+
const fleet = await readFleetSources(repoRoot);
|
|
2369
|
+
const evidenceBook = knowledgeRoot ? await buildEvidenceBook(repoRoot, targetUrls, fleet.externalSources) : (0, evidence_names_1.emptyEvidenceBook)();
|
|
2370
|
+
const refs = createRefResolver({ repoRoot, targetUrls, repoBaseUrl: repo.baseUrl, branch: repo.branch, book: evidenceBook });
|
|
2169
2371
|
// AIからの更新提案。**レコードを組み立てる前に読む**——各レコードが自分の分を
|
|
2170
2372
|
// takeProposals で引き取るので、後から読んでも載せる先が既に閉じている。
|
|
2171
2373
|
// 同一プロセスで2回ビルドしても前回の値が残らないよう、毎回入れ替える
|
|
@@ -2310,10 +2512,9 @@ async function build(opts) {
|
|
|
2310
2512
|
console.warn(`gold-proposals.json: レコードに突き合わない提案が ${unmatched.length} 件ありました(表示しません): ` +
|
|
2311
2513
|
unmatched.slice(0, 5).join(", ") + (unmatched.length > 5 ? " ほか" : ""));
|
|
2312
2514
|
}
|
|
2313
|
-
const graph = buildGraph(decisions, home, graphSections, targetUrls, repo.baseUrl);
|
|
2515
|
+
const graph = buildGraph(decisions, home, graphSections, targetUrls, repo.baseUrl, evidenceBook);
|
|
2314
2516
|
computeLayout(graph); // 各ノードに x/y を書き込む(クライアントはこれを初期位置に使いウォームアップ省略)
|
|
2315
|
-
// データソースと自動化(fleet-status.json
|
|
2316
|
-
const fleet = await readFleetSources(repoRoot);
|
|
2517
|
+
// データソースと自動化(fleet-status.json 由来・上で 1 回だけ読んだもの)。無ければ undefined でセクション非表示。
|
|
2317
2518
|
// すでにある会議(投入モーダルの「会議名」の選択肢)。intake の有無に関わらず載せる——
|
|
2318
2519
|
// 出し分けは表示側の仕事で、ここで絞ると「投入の口を後から付けたら選択肢が空」になる
|
|
2319
2520
|
const meetings = await collectMeetings(repoRoot);
|
|
@@ -2495,8 +2696,16 @@ async function parseDecision(raw, fileName, repoRelDir, repoBaseUrl, branch, ref
|
|
|
2495
2696
|
...(followupHuman ? { followupHuman: followupHuman } : {}),
|
|
2496
2697
|
};
|
|
2497
2698
|
}
|
|
2699
|
+
/**
|
|
2700
|
+
* 解決した参照 → 書かれていた値(#968 tools 1)。つながり画面は点の鍵をこの値から決める
|
|
2701
|
+
* (`site-data` に載せない——機械の語を画面の材料に増やさない)
|
|
2702
|
+
*/
|
|
2703
|
+
const REF_RAW = new WeakMap();
|
|
2704
|
+
/** リポジトリのパス 1 本だけの値(`会議/…/x.md`・`tmp/a.txt`)。カンマ・読点・括弧の注記を含むものは当てない */
|
|
2705
|
+
const REPO_PATH_ONLY_RE = /^[^\s,、()()]+(?:[ _-][^,、()()/]*)*\/[^,、()()/]+\.[A-Za-z0-9]{1,5}$/;
|
|
2498
2706
|
function createRefResolver(opts) {
|
|
2499
2707
|
const { repoRoot, targetUrls, repoBaseUrl, branch } = opts;
|
|
2708
|
+
const book = opts.book ?? (0, evidence_names_1.emptyEvidenceBook)();
|
|
2500
2709
|
// **他の走査結果を流用しない。** 全md一覧(Decisions走査や、かつてのグラフ走査 `scanContent`)は
|
|
2501
2710
|
// `.md` しか歩かず一部のディレクトリを除外するので、実在する `.txt`(Geminiメモ)や
|
|
2502
2711
|
// `tmp/` 配下のファイルを「実在しない」と誤判定して**いま動いているリンクを消す**。
|
|
@@ -2520,8 +2729,15 @@ function createRefResolver(opts) {
|
|
|
2520
2729
|
seen.set(relPath, ok);
|
|
2521
2730
|
return ok;
|
|
2522
2731
|
};
|
|
2523
|
-
|
|
2524
|
-
|
|
2732
|
+
/**
|
|
2733
|
+
* 書かれていた値の読める名前(#968 tools 1)。帳の題名・会議の定例名と日付・種類の既定の名前。
|
|
2734
|
+
* **飛び先は変えない**(ここは名前だけ)。根拠でない値・汎用の URL は `null`(今までどおりの名前)
|
|
2735
|
+
*/
|
|
2736
|
+
const nameOf = (raw) => {
|
|
2737
|
+
const ev = identifyEvidence(raw, { book, targetUrls, dropLink: (u) => u ?? undefined });
|
|
2738
|
+
return ev && ev.kind !== "url" ? ev.label : null;
|
|
2739
|
+
};
|
|
2740
|
+
const resolveOne = (trimmed) => {
|
|
2525
2741
|
if (!trimmed)
|
|
2526
2742
|
return { label: trimmed, url: null };
|
|
2527
2743
|
// 元ツールが無い根拠(engine #968 の将来の語彙)。リンクにせず、機械の語(urn:…)を出さない
|
|
@@ -2540,23 +2756,31 @@ function createRefResolver(opts) {
|
|
|
2540
2756
|
if (/^https?:\/\//.test(trimmed)) {
|
|
2541
2757
|
// 裸のURL(markdownリンクでラベルを添えていないもの)は、分かる種類なら読める名前にする
|
|
2542
2758
|
// (#968 tools 1「URL → 読める名前」)。**url は変えない**(正規化で既存のリンクを壊さない)
|
|
2543
|
-
|
|
2544
|
-
const label = canon && canon.kind !== "url" ? readableEvidenceLabel(canon.kind, canon.parentKey ?? canon.key, trimmed) : trimmed;
|
|
2545
|
-
return { label, url: trimmed };
|
|
2759
|
+
return { label: nameOf(trimmed) ?? trimmed, url: trimmed };
|
|
2546
2760
|
}
|
|
2547
2761
|
// 正本のURL(課題キー・material:・design:・minute:)。**ここが「ツールへ飛ぶ」の本体**
|
|
2548
2762
|
const known = targetUrls.get(trimmed);
|
|
2549
2763
|
if (known)
|
|
2550
|
-
return { label: trimmed, url: known };
|
|
2764
|
+
return { label: nameOf(trimmed) ?? trimmed, url: known };
|
|
2551
2765
|
// inventory の無い案件(#727 K10)の design: / figma-comment: は、キーとノードから組む
|
|
2552
2766
|
const figma = figmaUrlFromRef(trimmed);
|
|
2553
2767
|
if (figma)
|
|
2554
|
-
return { label: trimmed, url: figma };
|
|
2768
|
+
return { label: nameOf(trimmed) ?? trimmed, url: figma };
|
|
2769
|
+
// リポジトリのパス 1 本だけの値は、最後の段から拡張子を外した名前で出す(ファイルの語を出さない・#968 tools 1)。
|
|
2770
|
+
// 文の中のパス・パスに注記が付いた複合形は、書かれたまま(どれを指すか画面では決められない)
|
|
2771
|
+
const pathName = REPO_PATH_ONLY_RE.test(trimmed) ? (0, evidence_names_1.pathFallbackLabel)(trimmed) : trimmed;
|
|
2555
2772
|
// 実在するファイルだけ blob URL にする
|
|
2556
2773
|
if (repoBaseUrl && exists(trimmed)) {
|
|
2557
|
-
return { label: trimmed, url: `${repoBaseUrl}/blob/${branch}/${encodePath(trimmed)}` };
|
|
2774
|
+
return { label: nameOf(trimmed) ?? pathName, url: `${repoBaseUrl}/blob/${branch}/${encodePath(trimmed)}` };
|
|
2558
2775
|
}
|
|
2559
|
-
return { label: trimmed, url: null };
|
|
2776
|
+
return { label: nameOf(trimmed) ?? pathName, url: null };
|
|
2777
|
+
};
|
|
2778
|
+
const resolve = (ref) => {
|
|
2779
|
+
const trimmed = String(ref ?? "").trim();
|
|
2780
|
+
const out = resolveOne(trimmed);
|
|
2781
|
+
if (trimmed)
|
|
2782
|
+
REF_RAW.set(out, trimmed);
|
|
2783
|
+
return out;
|
|
2560
2784
|
};
|
|
2561
2785
|
return { resolve, exists };
|
|
2562
2786
|
}
|
package/dist/engagement.js
CHANGED
|
@@ -90,6 +90,8 @@ exports.WEIGHTS = Object.freeze({
|
|
|
90
90
|
// 人が承認して push した記憶の訂正(cortex-engine #935)。同じコミットから `record.edited` も起きるので、
|
|
91
91
|
// 数えると二重になる。0
|
|
92
92
|
"correction.stated": 0,
|
|
93
|
+
// 添付の原本の行(cortex-engine #1046 便 1)。本文は元の名前だけで、記憶のどの材料にも入れない。0
|
|
94
|
+
"file.attached": 0,
|
|
93
95
|
});
|
|
94
96
|
const DAY_MS = 86400000;
|
|
95
97
|
const cmp = (a, b) => (a < b ? -1 : a > b ? 1 : 0);
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* **根拠の読める名前の帳**(cortex-engine #968 tools 1 の続き・設計は #968 の状態コメント 2026-10-02)。
|
|
4
|
+
*
|
|
5
|
+
* つながり画面の点と、詳細画面の「参照」に出す名前を、URL や機械の ID(`minute:`・`material:`・
|
|
6
|
+
* `gfile:`・写しのファイル名)ではなく、人が呼ぶ名前(課題キー+題名・資料の題名・会議の定例名と
|
|
7
|
+
* 日付・チャンネル名)にするための材料。
|
|
8
|
+
*
|
|
9
|
+
* 帳は**ビルドのたびに写し(課題管理・共有資料・デザイン)と `fleet-status.json` のチャンネルの一覧から組む**。写しを
|
|
10
|
+
* 片付けた案件では帳が薄くなるだけで、点は今までどおり種類の既定の名前(「Google ドライブの資料」等)で出る。
|
|
11
|
+
*
|
|
12
|
+
* 鍵は `canonicalEvidence` の `key`(コメントは親の鍵)。ID の根拠(課題キー・`material:` 等)も、
|
|
13
|
+
* 対応表で URL に引けるものは同じ鍵に寄せる——今の ID と新しい URL の根拠が混ざっても 1 つの点になる。
|
|
14
|
+
*/
|
|
15
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
+
exports.emptyEvidenceBook = void 0;
|
|
17
|
+
exports.minuteFoldKey = minuteFoldKey;
|
|
18
|
+
exports.minuteLabel = minuteLabel;
|
|
19
|
+
exports.minuteRefOfPath = minuteRefOfPath;
|
|
20
|
+
exports.slackChannelOfLabel = slackChannelOfLabel;
|
|
21
|
+
exports.backlogTailOf = backlogTailOf;
|
|
22
|
+
exports.idFallbackLabel = idFallbackLabel;
|
|
23
|
+
exports.pathFallbackLabel = pathFallbackLabel;
|
|
24
|
+
const emptyEvidenceBook = () => ({
|
|
25
|
+
names: new Map(),
|
|
26
|
+
pathRefs: new Map(),
|
|
27
|
+
slackUrlsByName: new Map(),
|
|
28
|
+
backlogTailRefs: new Map(),
|
|
29
|
+
});
|
|
30
|
+
exports.emptyEvidenceBook = emptyEvidenceBook;
|
|
31
|
+
/** `minute:{定例名}:{YYYYMMDD}`(定例名は `Ph.1/全体定例` のようにフェーズの段を含むことがある) */
|
|
32
|
+
const MINUTE_ID_RE = /^minute:(.+):(\d{8})$/;
|
|
33
|
+
/** 定例名の最後の段(`Ph.1/全体定例` → `全体定例`)。画面の名前に使う */
|
|
34
|
+
const lastSegment = (s) => s.split("/").filter(Boolean).pop() ?? s;
|
|
35
|
+
/** フェーズの段(`Ph.1`・`Phase2`)。会議の置き場の上の段で、会議そのものを区別しない */
|
|
36
|
+
const PHASE_SEGMENT_RE = /^ph(?:ase)?\.?\s*\d+$/i;
|
|
37
|
+
/**
|
|
38
|
+
* `minute:` の ID を畳む鍵にする。**落とすのはフェーズの段だけ**(`Ph.1/全体定例` と `全体定例` を同じ会議に)。
|
|
39
|
+
* ほかの段(`顧客A/定例` と `顧客B/定例`)は別の会議なので残す——最後の段だけで畳むと別の会議が 1 点になる。
|
|
40
|
+
* 形が違えば `null`
|
|
41
|
+
*/
|
|
42
|
+
function minuteFoldKey(id) {
|
|
43
|
+
const m = id.match(MINUTE_ID_RE);
|
|
44
|
+
if (!m)
|
|
45
|
+
return null;
|
|
46
|
+
const segs = m[1].split("/").filter(Boolean);
|
|
47
|
+
const kept = segs.filter((seg, i) => i === segs.length - 1 || !PHASE_SEGMENT_RE.test(seg));
|
|
48
|
+
return `minute:${kept.join("/")}:${m[2]}`;
|
|
49
|
+
}
|
|
50
|
+
/** `minute:` の ID → 「全体定例 2026-06-04」。形が違えば `null` */
|
|
51
|
+
function minuteLabel(id) {
|
|
52
|
+
const m = id.match(MINUTE_ID_RE);
|
|
53
|
+
if (!m)
|
|
54
|
+
return null;
|
|
55
|
+
const d = m[2];
|
|
56
|
+
return `${lastSegment(m[1])} ${d.slice(0, 4)}-${d.slice(4, 6)}-${d.slice(6, 8)}`;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* 議事録の写しのパス(`会議/{…}/{定例名}/{YYYYMMDD}/{…}minutes{…}.md`)→ `minute:{定例名}:{YYYYMMDD}`。
|
|
60
|
+
* `buildTargetUrlMap` の `minute:` の組み方と同じ規約(定例名は日付の 1 つ上の段)
|
|
61
|
+
*/
|
|
62
|
+
function minuteRefOfPath(p) {
|
|
63
|
+
const parts = p.split("/");
|
|
64
|
+
if (parts.length < 4 || parts[0] !== "会議")
|
|
65
|
+
return null;
|
|
66
|
+
const file = parts[parts.length - 1];
|
|
67
|
+
const date = parts[parts.length - 2];
|
|
68
|
+
const meeting = parts[parts.length - 3];
|
|
69
|
+
if (!/minutes/i.test(file) || !/\.md$/i.test(file) || !/^\d{8}$/.test(date) || !meeting)
|
|
70
|
+
return null;
|
|
71
|
+
return `minute:${meeting}:${date}`;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* チャットの根拠の、チャンネル名だけで書かれた形 → `名前`。取り込みが書くラベル `[slack] #名前 (N messages since …)` と、
|
|
75
|
+
* AI が書いた `slack:{名前}:{YYYYMMDD}`。形が違えば `null`(チャンネル ID の `slack:C…` は鍵なのでここでは扱わない)
|
|
76
|
+
*/
|
|
77
|
+
function slackChannelOfLabel(raw) {
|
|
78
|
+
const t = raw.trim();
|
|
79
|
+
const m = t.match(/^\[slack\]\s+#([^\s()]+)(?:\s+\(.*\))?$/i) ?? t.match(/^slack:#?([^:\s]+):\d{8}$/);
|
|
80
|
+
return m && !/^C[A-Z0-9]{6,}$/.test(m[1]) ? m[1] : null;
|
|
81
|
+
}
|
|
82
|
+
/** 課題管理の写しのパス → 区画から下(`issues/2026/x.md`)。写しの形でなければ `null` */
|
|
83
|
+
function backlogTailOf(p) {
|
|
84
|
+
const m = p.match(/^課題管理\/(?:[^/]+\/)?((?:issues|documents|wiki)\/.+)$/);
|
|
85
|
+
return m ? m[1] : null;
|
|
86
|
+
}
|
|
87
|
+
/** ID の根拠の既定の名前(題名が引けないとき)。機械の接頭辞(`material:` 等)を外す */
|
|
88
|
+
function idFallbackLabel(id) {
|
|
89
|
+
const m = id.match(/^(material|design|figma-comment):(.+)$/);
|
|
90
|
+
if (!m)
|
|
91
|
+
return id;
|
|
92
|
+
if (m[1] === "material")
|
|
93
|
+
return m[2];
|
|
94
|
+
return m[1] === "design" ? "Figma のデザイン" : "Figma のコメント";
|
|
95
|
+
}
|
|
96
|
+
/** 写しのパスの既定の名前: 最後の段から拡張子を外す(`.md` のようなファイルの語を出さない) */
|
|
97
|
+
function pathFallbackLabel(p) {
|
|
98
|
+
const base = p.split("/").filter(Boolean).pop() ?? p;
|
|
99
|
+
return base.replace(/\.[A-Za-z0-9]{1,5}$/, "") || base;
|
|
100
|
+
}
|
package/dist/rederivation.js
CHANGED
|
@@ -9,6 +9,7 @@ exports.parseLedger = parseLedger;
|
|
|
9
9
|
exports.readRederivationLedgers = readRederivationLedgers;
|
|
10
10
|
exports.parseInjectedNow = parseInjectedNow;
|
|
11
11
|
exports.activityScore = activityScore;
|
|
12
|
+
exports.withoutFollowupHeadingText = withoutFollowupHeadingText;
|
|
12
13
|
exports.comparableContent = comparableContent;
|
|
13
14
|
exports.sameRederivedContent = sameRederivedContent;
|
|
14
15
|
exports.contentShaOf = contentShaOf;
|
|
@@ -368,14 +369,15 @@ const isFollowupBegin = (line) => FOLLOWUP_BEGIN_RE.test(String(line ?? "").repl
|
|
|
368
369
|
* 開始の印の行(最初の 1 つ・行末の `\r` は無視)から、終了の印**だけ**の行までを(両端を含めて)消す。終了の印が
|
|
369
370
|
* 無ければ本文の終わりまで消す。塊の前後の空行も除き、両側に本文が残れば空行 1 行でつなぐ。そのあと末尾の空行を落とす。塊の無い本文は触らない。
|
|
370
371
|
*/
|
|
371
|
-
function withoutBlock(body, isBegin, endMarker) {
|
|
372
|
+
function withoutBlock(body, isBegin, endMarker, { keepEnd = false } = {}) {
|
|
372
373
|
const lines = body.split("\n");
|
|
373
374
|
const start = lines.findIndex((line) => isBegin(line));
|
|
374
375
|
if (start < 0)
|
|
375
376
|
return body;
|
|
376
377
|
// 行末の `\r` は見ない(CRLF の記録でも印の行として当てる。当てないと毎晩印が立つ)
|
|
377
378
|
const endRel = lines.slice(start + 1).findIndex((line) => line.replace(/\r$/, "") === endMarker);
|
|
378
|
-
|
|
379
|
+
// `keepEnd`: 終わりの行(次の節の見出し)は残す(見出しで区切る「その後」の節・#1094 便 1)
|
|
380
|
+
const end = endRel < 0 ? lines.length : start + 1 + endRel + (keepEnd ? 0 : 1);
|
|
379
381
|
const isBlank = (line) => line.trim() === "";
|
|
380
382
|
// 塊の前後の空行も一緒に除き、両側に本文が残れば空行 1 行でつなぐ(engine の書き手と同じ規則・#702 実装レビュー R1)
|
|
381
383
|
const before = lines.slice(0, start);
|
|
@@ -413,7 +415,28 @@ function stripMemberDecisions(body) {
|
|
|
413
415
|
function withoutFollowup(body) {
|
|
414
416
|
return withoutBlock(body, isFollowupBegin, FOLLOWUP_END);
|
|
415
417
|
}
|
|
416
|
-
|
|
418
|
+
/** 「その後」の節の見出し(印を外した後の形・#1094 便 1)。🔴 engine の `FOLLOWUP_HEADING_RE` の写し */
|
|
419
|
+
const FOLLOWUP_HEADING_RE = /^##\s+その後\s*$/;
|
|
420
|
+
/**
|
|
421
|
+
* 印の無い「その後」の節(`## その後` の行から、次の `## ` の行か本文の終わりまで)を除く(#1094 便 1・#551 設計 D2)。
|
|
422
|
+
* 規則は塊と同じ 1 本(`withoutBlock`・次の節の見出しは残す)。印の塊がある本文は塊の規則で先に除くので、
|
|
423
|
+
* ここは印を外した後の本文だけに効く。
|
|
424
|
+
*
|
|
425
|
+
* 🔴 **engine の写し(`scripts/rederive/content-sha.mjs` の `withoutFollowupHeadingText`)と 1 バイト一致・
|
|
426
|
+
* 番人は `test/followup-block-sha.test.mjs` の `[markerFree]`**(engine の `contentShaOf(raw, { markerFree: true })` を
|
|
427
|
+
* 実際に呼んで焼いた表 `test/fixtures/marker-free-sha-contract.json`)。変えたら両方を変えて表を焼き直す。
|
|
428
|
+
*/
|
|
429
|
+
function withoutFollowupHeadingText(body) {
|
|
430
|
+
const text = String(body ?? "");
|
|
431
|
+
const lines = text.split("\n");
|
|
432
|
+
const start = lines.findIndex((line) => FOLLOWUP_HEADING_RE.test(line.replace(/\r$/, "")));
|
|
433
|
+
if (start < 0)
|
|
434
|
+
return text;
|
|
435
|
+
const nextRel = lines.slice(start + 1).findIndex((line) => /^##\s/.test(line));
|
|
436
|
+
const endLine = nextRel < 0 ? null : lines[start + 1 + nextRel].replace(/\r$/, "");
|
|
437
|
+
return withoutBlock(text, (line) => line === lines[start], endLine === null ? "\u0000" : endLine, { keepEnd: endLine !== null });
|
|
438
|
+
}
|
|
439
|
+
function comparableContent(raw, { markerFree = false } = {}) {
|
|
417
440
|
let data = {};
|
|
418
441
|
let content = String(raw ?? "");
|
|
419
442
|
try {
|
|
@@ -428,7 +451,9 @@ function comparableContent(raw) {
|
|
|
428
451
|
return {
|
|
429
452
|
description: normalizeForCompare(String(data.description ?? "")),
|
|
430
453
|
// engine と同じ順(目録の塊 → その後の塊)
|
|
431
|
-
body: normalizeForCompare(
|
|
454
|
+
body: normalizeForCompare(markerFree
|
|
455
|
+
? withoutFollowupHeadingText(withoutFollowup(stripMemberDecisions(content)))
|
|
456
|
+
: withoutFollowup(stripMemberDecisions(content))),
|
|
432
457
|
};
|
|
433
458
|
}
|
|
434
459
|
/** 2 つの版で「説明文と本文」が同じか(frontmatter の他の欄の差は見ない) */
|
|
@@ -448,8 +473,8 @@ function sameRederivedContent(a, b) {
|
|
|
448
473
|
* engine の写し: `cortex-engine/scripts/rederive/content-sha.mjs`・一致の番人:
|
|
449
474
|
* `cortex-engine/test/rederive-history.test.mjs` の #546 の節(ここを変えたら engine の `VIEWER_SHA` を計算し直す)
|
|
450
475
|
*/
|
|
451
|
-
function contentShaOf(raw) {
|
|
452
|
-
const c = comparableContent(raw);
|
|
476
|
+
function contentShaOf(raw, options = {}) {
|
|
477
|
+
const c = comparableContent(raw, options);
|
|
453
478
|
return node_crypto_1.default.createHash("sha256").update(c.description + "\u0000" + c.body, "utf8").digest("hex");
|
|
454
479
|
}
|
|
455
480
|
/**
|
package/dist/render.js
CHANGED
|
@@ -57,7 +57,6 @@ exports.nextMemoryReflectGoldAtMs = nextMemoryReflectGoldAtMs;
|
|
|
57
57
|
exports.memoryReflectDoneAtMs = memoryReflectDoneAtMs;
|
|
58
58
|
exports.memoryReflectTextOf = memoryReflectTextOf;
|
|
59
59
|
exports.runLogUrlOf = runLogUrlOf;
|
|
60
|
-
exports.lastSyncRunUrlOf = lastSyncRunUrlOf;
|
|
61
60
|
exports.runSourceWordOf = runSourceWordOf;
|
|
62
61
|
exports.fleetLastSyncOf = fleetLastSyncOf;
|
|
63
62
|
exports.stateRefKey = stateRefKey;
|
|
@@ -2722,27 +2721,6 @@ function runLogUrlOf(row) {
|
|
|
2722
2721
|
return "";
|
|
2723
2722
|
return row.runUrl;
|
|
2724
2723
|
}
|
|
2725
|
-
/**
|
|
2726
|
-
* 「最終同期: …」の横に出すリンク先(#560)。**その時刻を書いた run のときだけ。**
|
|
2727
|
-
*
|
|
2728
|
-
* 「最終同期」は `lastOkAt`(最後に取り込めた時刻)で、行の `runUrl` は**いまの status を
|
|
2729
|
-
* 書いた run**のもの。いま `failed` の行の `lastOkAt` が最大なら、その `runUrl` は失敗した run
|
|
2730
|
-
* を指すので出さない(間違った run へ送るより、無いほうがよい)。`ok` の行でも `lastOkAt` と
|
|
2731
|
-
* `statusAt` が違う(履歴だけ先に進んだ)回は出さない。
|
|
2732
|
-
*
|
|
2733
|
-
* **クロージャを持たない純関数に保つこと**(`.toString()` でクライアントへ渡す)。
|
|
2734
|
-
*/
|
|
2735
|
-
function lastSyncRunUrlOf(row) {
|
|
2736
|
-
// **部分失敗の回も「取り込めた」(#634)。** ここを `status === "ok"` のままにすると、
|
|
2737
|
-
// 「最終同期」の時刻は部分失敗の回のものになったのに、その時刻を書いた run へ行けない
|
|
2738
|
-
// ——R9(その時刻を書いた run にだけリンクを出す)が、まさにこの便の対象の回で満たせない
|
|
2739
|
-
if (!ingestSucceeded(row))
|
|
2740
|
-
return "";
|
|
2741
|
-
// 「最終同期」として出した時刻がこの行の `at` でないなら、リンク先の run は別の回のもの
|
|
2742
|
-
if (row.lastOkAt && row.lastOkAt > row.at)
|
|
2743
|
-
return "";
|
|
2744
|
-
return runLogUrlOf(row);
|
|
2745
|
-
}
|
|
2746
2724
|
/**
|
|
2747
2725
|
* `runUrl` の無い行に添える出どころの語(#560)。リンクが出る行・語の無い書き手は空文字。
|
|
2748
2726
|
*
|
|
@@ -5716,10 +5694,6 @@ a.ds-tile:hover { text-decoration: none; }
|
|
|
5716
5694
|
.ds-all-connected { color: var(--muted); }
|
|
5717
5695
|
.ds-gate { display: inline-flex; align-items: center; gap: 5px; font-size: 12.5px; font-weight: 600; flex: 0 0 auto; }
|
|
5718
5696
|
.ds-gate-at { font-size: 11px; color: var(--muted); flex: 0 0 auto; }
|
|
5719
|
-
/* 実行ログへの導線(#560)。時刻の横の添え物なので .ds-gate-at と同じ弱さ。押せることだけ
|
|
5720
|
-
点線の下線で示す(目立たせない——案件リポジトリに入っていない人には開けない) */
|
|
5721
|
-
.ds-run-link { font-size: 11px; color: var(--muted); flex: 0 0 auto; text-decoration: underline dotted; text-underline-offset: 2px; }
|
|
5722
|
-
.ds-run-link:hover { color: var(--text); }
|
|
5723
5697
|
/* run の無い書き手の行の出どころ(「中央の処理」「その場で確認」)。同じ弱さの文字 */
|
|
5724
5698
|
.ds-run-src { font-size: 11px; color: var(--muted); flex: 0 0 auto; }
|
|
5725
5699
|
/* 接続先1件ごとの「取り込み」(段C・2026-08-24)。**「最終同期」(.ds-sync・run単位)とは
|
|
@@ -6430,7 +6404,6 @@ ${nextMemoryReflectGoldAtMs.toString()}
|
|
|
6430
6404
|
${memoryReflectDoneAtMs.toString()}
|
|
6431
6405
|
${memoryReflectTextOf.toString()}
|
|
6432
6406
|
${runLogUrlOf.toString()}
|
|
6433
|
-
${lastSyncRunUrlOf.toString()}
|
|
6434
6407
|
${runSourceWordOf.toString()}
|
|
6435
6408
|
var TILE_CAPS = ${JSON.stringify(TILE_CAPS)};
|
|
6436
6409
|
var BAD_STATE_STATUSES = ${JSON.stringify(BAD_STATE_STATUSES)};
|
|
@@ -12901,12 +12874,11 @@ ${calendarAddUrl.toString()}
|
|
|
12901
12874
|
var t = dsFmtTime(at || fleetSyncFallback(s) || "");
|
|
12902
12875
|
if (!t) return null;
|
|
12903
12876
|
var sync = el("span", { class: "ds-sync", text: "最終同期: " + t });
|
|
12904
|
-
//
|
|
12905
|
-
//
|
|
12906
|
-
//
|
|
12877
|
+
// run の無い書き手(中央 Apps Script の会議・資料の取り込み)は出どころの語を添える。
|
|
12878
|
+
// ストア由来の時刻のときだけ——まとめ表の後詰めには行が無い。行は「ingestedAtOf」 と
|
|
12879
|
+
// 同じ選び方(「ingestedRowOf」)で取る
|
|
12907
12880
|
var row = at ? ingestedRowOf(LIVE_STATE, capId, refs) : null;
|
|
12908
|
-
var
|
|
12909
|
-
var trail = url ? [runLogLink(url)] : row ? runTrailNodes(row, "") : [];
|
|
12881
|
+
var trail = row ? runTrailNodes(row) : [];
|
|
12910
12882
|
return trail.length ? frag([sync].concat(trail)) : sync;
|
|
12911
12883
|
}
|
|
12912
12884
|
|
|
@@ -14820,37 +14792,26 @@ ${calendarAddUrl.toString()}
|
|
|
14820
14792
|
}
|
|
14821
14793
|
|
|
14822
14794
|
/* -------------------------------------------------------------------------
|
|
14823
|
-
*
|
|
14795
|
+
* run の出どころの表示(#560・オーナー決定 2026-10-01: 「実行ログ ↗」は社内・お客様とも
|
|
14796
|
+
* 画面から外した)
|
|
14824
14797
|
* -------------------------------------------------------------------------
|
|
14825
|
-
*
|
|
14826
|
-
*
|
|
14827
|
-
*
|
|
14798
|
+
* run の無い書き手の行は、出どころを語で示す(「中央の処理」「その場で確認」)。run のある
|
|
14799
|
+
* 行(ingest / gold / nightly)は何も添えない——リンク先が実行用リポや案件リポの GitHub
|
|
14800
|
+
* Actions で、多くの利用者には開けないため。出すかどうかの判断は純関数
|
|
14801
|
+
* (runLogUrlOf / runSourceWordOf)が持ち、ここは描くだけ。
|
|
14828
14802
|
* ----------------------------------------------------------------------- */
|
|
14829
14803
|
|
|
14830
|
-
/** リンクの title。案件リポジトリに入っていない人には開けないので、押す前に分かるようにする */
|
|
14831
|
-
var RUN_LINK_TITLE = "GitHub Actions の実行ログを開きます(プロジェクトリポジトリへのアクセスが要ります)";
|
|
14832
|
-
/** 一覧(開始直後・run の番号がまだ無い)の title。飛び先が違うので文言も分ける */
|
|
14833
|
-
var RUN_LIST_TITLE = "GitHub Actions の実行の一覧を開きます(プロジェクトリポジトリへのアクセスが要ります)";
|
|
14834
|
-
var RUN_LIST_TEXT = "実行の一覧 ↗";
|
|
14835
|
-
|
|
14836
|
-
/** 「実行ログ ↗」(新しいタブ・title つき)。一覧のときは文言と title を一覧のものにする */
|
|
14837
|
-
function runLogLink(url, text) {
|
|
14838
|
-
var list = text === RUN_LIST_TEXT;
|
|
14839
|
-
return el("a", { class: "ds-run-link", href: url, target: "_blank", rel: "noopener", title: list ? RUN_LIST_TITLE : RUN_LINK_TITLE, text: text || "実行ログ ↗" });
|
|
14840
|
-
}
|
|
14841
|
-
|
|
14842
14804
|
/**
|
|
14843
|
-
*
|
|
14805
|
+
* 行の時刻の横に添える出どころの語。**無ければ空配列。**
|
|
14844
14806
|
* 「.ds-gate-at」「.ds-ingest」 の**兄弟**として置く(子にすると、文言の検査と
|
|
14845
14807
|
* 「いま測った結果」への差し替えが崩れる)。
|
|
14846
14808
|
*
|
|
14847
14809
|
* @param kind どの時刻に添えたか(「gate」=「◯分前に確認」)。「いま測った結果」への差し替えは
|
|
14848
|
-
* **この印のものだけ**消す——「取り込み:
|
|
14810
|
+
* **この印のものだけ**消す——「取り込み: 〜に成功」の横の語(別の行のもの)は残す
|
|
14849
14811
|
*/
|
|
14850
|
-
function runTrailNodes(row,
|
|
14851
|
-
var
|
|
14852
|
-
var
|
|
14853
|
-
var node = u ? runLogLink(u) : word ? el("span", { class: "ds-run-src", text: word }) : null;
|
|
14812
|
+
function runTrailNodes(row, kind) {
|
|
14813
|
+
var word = runSourceWordOf(row);
|
|
14814
|
+
var node = word ? el("span", { class: "ds-run-src", text: word }) : null;
|
|
14854
14815
|
if (!node) return [];
|
|
14855
14816
|
if (kind) node.setAttribute("data-trail", kind);
|
|
14856
14817
|
return [node];
|
|
@@ -14864,11 +14825,11 @@ ${calendarAddUrl.toString()}
|
|
|
14864
14825
|
}
|
|
14865
14826
|
|
|
14866
14827
|
/**
|
|
14867
|
-
*
|
|
14868
|
-
* (同じ行が 2 つのことを言わない)。「取り込み:
|
|
14828
|
+
* 「いま測った結果」に差し替えるとき、**「◯分前に確認」に添えた**古い出どころの語を外す
|
|
14829
|
+
* (同じ行が 2 つのことを言わない)。「取り込み: 〜に成功」の横の語は別の行のものなので残す
|
|
14869
14830
|
*/
|
|
14870
14831
|
function removeRunTrail(scope) {
|
|
14871
|
-
var old = scope.querySelectorAll('.ds-run-
|
|
14832
|
+
var old = scope.querySelectorAll('.ds-run-src[data-trail="gate"]');
|
|
14872
14833
|
for (var i = 0; i < old.length; i++) old[i].remove();
|
|
14873
14834
|
}
|
|
14874
14835
|
|
|
@@ -14907,7 +14868,7 @@ ${calendarAddUrl.toString()}
|
|
|
14907
14868
|
class: "ds-ingest" + (r.status === "failed" && !(f && f.partial) ? " ds-ingest-failed" : ""),
|
|
14908
14869
|
text: text,
|
|
14909
14870
|
});
|
|
14910
|
-
//
|
|
14871
|
+
// run の無い書き手は出どころの語を添える(#560)。成功でも失敗でも同じ
|
|
14911
14872
|
return frag([span].concat(runTrailNodes(r)));
|
|
14912
14873
|
}
|
|
14913
14874
|
|
|
@@ -14966,8 +14927,8 @@ ${calendarAddUrl.toString()}
|
|
|
14966
14927
|
row.appendChild(el("span", { class: "ds-gate " + gate.cls, text: gate.text }));
|
|
14967
14928
|
var when = rtAgo(r.at);
|
|
14968
14929
|
if (when) row.appendChild(el("span", { class: "ds-gate-at", text: when + "に確認" }));
|
|
14969
|
-
//
|
|
14970
|
-
runTrailNodes(r,
|
|
14930
|
+
// run の無い書き手は出どころの語を添える(#560)
|
|
14931
|
+
runTrailNodes(r, "gate").forEach(function (n) { row.appendChild(n); });
|
|
14971
14932
|
return row;
|
|
14972
14933
|
}
|
|
14973
14934
|
|
|
@@ -15116,28 +15077,15 @@ ${calendarAddUrl.toString()}
|
|
|
15116
15077
|
at: (before && before.at) || "",
|
|
15117
15078
|
startedAt: Date.now(),
|
|
15118
15079
|
message: (j && j.message) || "",
|
|
15119
|
-
// 起こした run(特定できた回)と一覧(#560)。描き直しでもリンクが消えないよう控える
|
|
15120
|
-
runUrl: (j && RUN_URL_RE.test(String(j.runUrl || ""))) ? String(j.runUrl) : "",
|
|
15121
|
-
runsUrl: (j && /^https:\\/\\/github\\.com\\/[^\\s"'<>]+$/.test(String(j.runsUrl || ""))) ? String(j.runsUrl) : "",
|
|
15122
15080
|
};
|
|
15123
15081
|
probeWaitNote(note, PROBE_WAITS[key]);
|
|
15124
15082
|
// 待っているあいだは取り直しを回す(届いた行で一覧が描き直される)
|
|
15125
15083
|
refreshStateTimer();
|
|
15126
15084
|
}
|
|
15127
15085
|
|
|
15128
|
-
/**
|
|
15129
|
-
* 待ちの 1 文と、その横の導線(#560)。**その run を特定できていればそれ、無ければ一覧。**
|
|
15130
|
-
* 開始直後は run の番号が無いことが多いので一覧へ。行が更新されて待ちが解けたら、
|
|
15131
|
-
* 行側の「実行ログ ↗」(その run)に置き換わる。run の無い経路(固定 IP・中央)は何も出ない
|
|
15132
|
-
*/
|
|
15086
|
+
/** 待ちの 1 文(#548)。「同期を開始しました」等、サーバの文言をそのまま出す */
|
|
15133
15087
|
function probeWaitNote(note, wait) {
|
|
15134
15088
|
note.textContent = (wait && wait.message) || "";
|
|
15135
|
-
if (!wait) return;
|
|
15136
|
-
var link = wait.runUrl ? runLogLink(wait.runUrl) : wait.runsUrl ? runLogLink(wait.runsUrl, RUN_LIST_TEXT) : null;
|
|
15137
|
-
if (link) {
|
|
15138
|
-
note.appendChild(document.createTextNode(" "));
|
|
15139
|
-
note.appendChild(link);
|
|
15140
|
-
}
|
|
15141
15089
|
}
|
|
15142
15090
|
|
|
15143
15091
|
/**
|
|
@@ -15216,12 +15164,7 @@ ${calendarAddUrl.toString()}
|
|
|
15216
15164
|
var phase = probeWaitPhaseOf(capId, ref);
|
|
15217
15165
|
var wait = probeWaitOf(capId, ref);
|
|
15218
15166
|
if (phase === "waiting") probeWaitNote(note, wait);
|
|
15219
|
-
else if (phase === "timeout")
|
|
15220
|
-
// 15 分待って届かないなら見に行く番。**行き先を添える**(#560)——一覧でも run でも
|
|
15221
|
-
note.textContent = PROBE_WAIT_TIMEOUT_TEXT;
|
|
15222
|
-
var to = wait && (wait.runUrl || wait.runsUrl);
|
|
15223
|
-
if (to) { note.appendChild(document.createTextNode(" ")); note.appendChild(runLogLink(to, wait.runUrl ? "実行ログ ↗" : RUN_LIST_TEXT)); }
|
|
15224
|
-
}
|
|
15167
|
+
else if (phase === "timeout") note.textContent = PROBE_WAIT_TIMEOUT_TEXT;
|
|
15225
15168
|
else note.hidden = true;
|
|
15226
15169
|
|
|
15227
15170
|
checkBtn.addEventListener("click", async function () {
|
|
@@ -16545,7 +16488,7 @@ ${calendarAddUrl.toString()}
|
|
|
16545
16488
|
// 起動できなかった案件(権限・スタブ未配布)ではリンクを出さない——押しても何も無い
|
|
16546
16489
|
if (j.runsUrl) {
|
|
16547
16490
|
msg.appendChild(document.createTextNode(" "));
|
|
16548
|
-
msg.appendChild(el("a", { href: j.runUrl || j.runsUrl, target: "_blank", rel: "noopener", title:
|
|
16491
|
+
msg.appendChild(el("a", { href: j.runUrl || j.runsUrl, target: "_blank", rel: "noopener", title: "GitHub Actions の実行ログを開きます(プロジェクトリポジトリへのアクセスが要ります)", text: "実行状況を見る →" })); // その run を特定できていればそれ、無ければ一覧
|
|
16549
16492
|
}
|
|
16550
16493
|
// **「反映の状況 →」のリンクは無くなった(#221・2026-08-25)。** 指していたのは
|
|
16551
16494
|
// 「画面を追いつかせるための fleet-status の実行」で、その起動ごと撤去した
|
|
@@ -17131,8 +17074,8 @@ ${calendarAddUrl.toString()}
|
|
|
17131
17074
|
var gateAtEl = s.gateAt
|
|
17132
17075
|
? el("span", { class: "ds-gate-at", text: rtAgo(s.gateAt) + "に確認" })
|
|
17133
17076
|
: null;
|
|
17134
|
-
//
|
|
17135
|
-
var gateTrail = s.gateAt ? runTrailNodes({ runUrl: s.gateRunUrl || "", by: s.gateBy || "" },
|
|
17077
|
+
// run の無い書き手は出どころの語を添える(#560)。overlay が写した 「gateRunUrl」 / 「gateBy」 から決める
|
|
17078
|
+
var gateTrail = s.gateAt ? runTrailNodes({ runUrl: s.gateRunUrl || "", by: s.gateBy || "" }, "gate") : [];
|
|
17136
17079
|
var card = el("div", { class: "ds-card" }, [
|
|
17137
17080
|
el("div", { class: "ds-card-main" }, [
|
|
17138
17081
|
el("span", { class: "ds-icon", text: meta.icon }),
|
|
@@ -18424,7 +18367,7 @@ ${calendarAddUrl.toString()}
|
|
|
18424
18367
|
// 起動できたときだけ、実行状況への導線を出す(鍵を入れた直後に効いたか確かめられる)
|
|
18425
18368
|
if (j.runsUrl) {
|
|
18426
18369
|
msg.appendChild(document.createTextNode(" "));
|
|
18427
|
-
msg.appendChild(el("a", { href: j.runUrl || j.runsUrl, target: "_blank", rel: "noopener", title:
|
|
18370
|
+
msg.appendChild(el("a", { href: j.runUrl || j.runsUrl, target: "_blank", rel: "noopener", title: "GitHub Actions の実行ログを開きます(プロジェクトリポジトリへのアクセスが要ります)", text: "実行状況を見る →" })); // その run を特定できていればそれ、無ければ一覧
|
|
18428
18371
|
}
|
|
18429
18372
|
// 入れた値は画面に残さない(肩越しに見えるのを防ぐ)
|
|
18430
18373
|
input.value = "";
|
|
@@ -18928,7 +18871,7 @@ ${calendarAddUrl.toString()}
|
|
|
18928
18871
|
// 置いていたため、能力モーダルの上に2枚目が重なり、Escape が下の1枚だけを
|
|
18929
18872
|
// 閉じて上が残った。isModalOpen も app しか見ないので、開いている間の
|
|
18930
18873
|
// ライブ差し替えの見送りも効いていなかった
|
|
18931
|
-
var mk = makeModal(
|
|
18874
|
+
var mk = makeModal("会議の文字起こしを追加", false);
|
|
18932
18875
|
var overlay = mk.overlay;
|
|
18933
18876
|
var modal = mk.modal;
|
|
18934
18877
|
// 🔴 **資料の案内はカードだけでは届かない**(#663・設計レビュー🔴2)。makeModal は
|
|
@@ -18949,7 +18892,7 @@ ${calendarAddUrl.toString()}
|
|
|
18949
18892
|
var allowedExt = isT
|
|
18950
18893
|
? { txt: 1, md: 1, docx: 1, doc: 1, rtf: 1, pdf: 1, vtt: 1, srt: 1 }
|
|
18951
18894
|
: { pdf: 1, docx: 1, doc: 1, pptx: 1, ppt: 1, xlsx: 1, xls: 1, csv: 1, txt: 1, md: 1, rtf: 1, png: 1, jpg: 1, jpeg: 1, gif: 1, webp: 1, svg: 1, bmp: 1, heic: 1, tiff: 1 };
|
|
18952
|
-
var allowedLabel =
|
|
18895
|
+
var allowedLabel = "テキスト・Word・PDF・字幕(.vtt/.srt)";
|
|
18953
18896
|
function extOf(name) { var m = String(name).toLowerCase().match(/\.([a-z0-9]+)$/); return m ? m[1] : ""; }
|
|
18954
18897
|
var fileInput = el("input", { type: "file", multiple: "multiple", class: "modal-file-hidden" });
|
|
18955
18898
|
var fileListEl = el("div", { class: "dropzone-files" });
|
|
@@ -19266,7 +19209,7 @@ ${calendarAddUrl.toString()}
|
|
|
19266
19209
|
status.textContent = "";
|
|
19267
19210
|
status.appendChild(el("div", { class: "modal-ok", text: allDup
|
|
19268
19211
|
? "✓ 送信しました。保存されました(この日付には既に議事録があるため、議事録の自動生成は行われません)"
|
|
19269
|
-
: "✓
|
|
19212
|
+
: "✓ 送信しました。数分後に議事録が自動生成され反映されます。" }));
|
|
19270
19213
|
var paths = (sub.j && sub.j.committed) || [];
|
|
19271
19214
|
if (paths.length && sub.j.repo) {
|
|
19272
19215
|
var br = SITE.branch || "main";
|
|
@@ -20400,12 +20343,15 @@ ${calendarAddUrl.toString()}
|
|
|
20400
20343
|
reference: { color: "#d1d5db", label: "参照", kind: "evidence" },
|
|
20401
20344
|
source: { color: "#d1d5db", label: "出典", kind: "evidence" },
|
|
20402
20345
|
link: { color: "#e2e8f0", label: "リンク", kind: "evidence" },
|
|
20346
|
+
// 話題の線(決定・ルール・関係者 → 用語・engine #551 の about)。名前の一致で機械が付けた手がかりなので、
|
|
20347
|
+
// 根拠の線と同じく細く薄い「ゆるいつながり」として描き、凡例は日本語の 1 行(栓 about_relations の on の条件)
|
|
20348
|
+
about: { color: "#ddd6fe", label: "話題", kind: "topic" },
|
|
20403
20349
|
};
|
|
20404
20350
|
var WEAK_RELS = {};
|
|
20405
20351
|
var REL_COLOR = {};
|
|
20406
20352
|
Object.keys(REL_META).forEach(function (r) {
|
|
20407
20353
|
REL_COLOR[r] = REL_META[r].color;
|
|
20408
|
-
if (REL_META[r].kind === "evidence") WEAK_RELS[r] = true;
|
|
20354
|
+
if (REL_META[r].kind === "evidence" || REL_META[r].kind === "topic") WEAK_RELS[r] = true;
|
|
20409
20355
|
});
|
|
20410
20356
|
var graphState = { showWeak: true, q: "" };
|
|
20411
20357
|
|
|
@@ -20438,7 +20384,7 @@ ${calendarAddUrl.toString()}
|
|
|
20438
20384
|
});
|
|
20439
20385
|
|
|
20440
20386
|
// 線(関係)の色凡例。グラフ内の有無に関わらず全種別を、Goldどうし/根拠の2群で表示する
|
|
20441
|
-
[["gold", "線(Goldどうし):"], ["evidence", "線(根拠):"]].forEach(function (pair) {
|
|
20387
|
+
[["gold", "線(Goldどうし):"], ["evidence", "線(根拠):"], ["topic", "線(話題):"]].forEach(function (pair) {
|
|
20442
20388
|
var kind = pair[0], heading = pair[1];
|
|
20443
20389
|
var keys = Object.keys(REL_META).filter(function (r) { return REL_META[r].kind === kind; });
|
|
20444
20390
|
if (!keys.length) return;
|
|
@@ -20455,7 +20401,7 @@ ${calendarAddUrl.toString()}
|
|
|
20455
20401
|
cb.checked = graphState.showWeak;
|
|
20456
20402
|
cb.addEventListener("change", function () { graphState.showWeak = cb.checked; renderList(); });
|
|
20457
20403
|
toggle.appendChild(cb);
|
|
20458
|
-
toggle.appendChild(document.createTextNode("
|
|
20404
|
+
toggle.appendChild(document.createTextNode("ゆるいつながり(参照・本文リンク・話題)も表示"));
|
|
20459
20405
|
legend.appendChild(toggle);
|
|
20460
20406
|
app.appendChild(legend);
|
|
20461
20407
|
|
|
@@ -20805,6 +20751,7 @@ ${calendarAddUrl.toString()}
|
|
|
20805
20751
|
based_on: "rgba(125,165,255,0.85)", derived_from: "rgba(94,234,212,0.75)",
|
|
20806
20752
|
relates_to: "rgba(148,163,184,0.6)", supersedes: "rgba(248,113,113,0.9)",
|
|
20807
20753
|
reference: "rgba(180,196,230,0.32)", source: "rgba(180,196,230,0.32)", link: "rgba(180,196,230,0.22)",
|
|
20754
|
+
about: "rgba(196,181,253,0.22)",
|
|
20808
20755
|
};
|
|
20809
20756
|
edges.forEach(function (e) {
|
|
20810
20757
|
// ホバー中: リネージに入った辺だけ、波の順に「order の低い側→高い側」へ伸びながら金色に。
|
|
@@ -21112,6 +21059,7 @@ ${calendarAddUrl.toString()}
|
|
|
21112
21059
|
var REL_LABEL = {
|
|
21113
21060
|
based_on: "根拠", derived_from: "由来", relates_to: "関連",
|
|
21114
21061
|
supersedes: "置換", superseded_by: "置換先", reference: "参照", source: "出典", link: "リンク",
|
|
21062
|
+
about: "話題",
|
|
21115
21063
|
};
|
|
21116
21064
|
var explorerState = { folder: null, path: [], q: "", sx: 0, autoRight: false };
|
|
21117
21065
|
// 索引もグラフ本体と同じ材料(削除済みを除いたもの)から作る。別々に間引くと、
|
|
@@ -21124,7 +21072,7 @@ ${calendarAddUrl.toString()}
|
|
|
21124
21072
|
(adjDir[e.from] = adjDir[e.from] || []).push({ id: e.to, rel: e.rel, dir: "out" });
|
|
21125
21073
|
(adjDir[e.to] = adjDir[e.to] || []).push({ id: e.from, rel: e.rel, dir: "in" });
|
|
21126
21074
|
});
|
|
21127
|
-
var REL_ORDER = ["supersedes", "based_on", "derived_from", "relates_to", "source", "reference", "link"];
|
|
21075
|
+
var REL_ORDER = ["supersedes", "based_on", "derived_from", "relates_to", "source", "reference", "link", "about"];
|
|
21128
21076
|
|
|
21129
21077
|
function recordText(rec) {
|
|
21130
21078
|
if (!rec) return "";
|
|
@@ -21346,8 +21294,10 @@ ${calendarAddUrl.toString()}
|
|
|
21346
21294
|
path.setAttribute("d", "M " + x1 + " " + y1 + " C " + mx + " " + y1 + ", " + mx + " " + y2 + ", " + x2 + " " + y2);
|
|
21347
21295
|
path.setAttribute("fill", "none");
|
|
21348
21296
|
path.setAttribute("stroke", REL_COLOR[rows[j].getAttribute("data-rel")] || "#cbd5e1");
|
|
21349
|
-
|
|
21350
|
-
|
|
21297
|
+
// 話題の線は細く薄く(名前の一致の手がかり・根拠の線より弱い)
|
|
21298
|
+
var weakLine = rows[j].getAttribute("data-rel") === "about";
|
|
21299
|
+
path.setAttribute("stroke-width", weakLine ? "1" : "1.5");
|
|
21300
|
+
path.setAttribute("opacity", weakLine ? "0.5" : "0.85");
|
|
21351
21301
|
lines.appendChild(path);
|
|
21352
21302
|
}
|
|
21353
21303
|
}
|
package/package.json
CHANGED