@takagaki/cortex-decisions-viewer 0.12.85 → 0.12.86
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 +93 -2
- package/dist/engagement.js +4 -1
- package/dist/render.js +85 -1
- package/dist/supersedes.js +112 -0
- package/package.json +1 -1
package/dist/build.js
CHANGED
|
@@ -36,12 +36,14 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
36
36
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
37
|
};
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
-
exports.PROJECT_ICON_EXTS = void 0;
|
|
39
|
+
exports.PROJECT_ICON_EXTS = exports.MIRROR_RELS = void 0;
|
|
40
40
|
exports.sanitizeBody = sanitizeBody;
|
|
41
41
|
exports.findProjectIcon = findProjectIcon;
|
|
42
42
|
exports.readFleetSources = readFleetSources;
|
|
43
43
|
exports.attachRederived = attachRederived;
|
|
44
44
|
exports.attachDecisionFollowup = attachDecisionFollowup;
|
|
45
|
+
exports.rawFrontmatterDate = rawFrontmatterDate;
|
|
46
|
+
exports.attachSupersedes = attachSupersedes;
|
|
45
47
|
exports.readGoldProposals = readGoldProposals;
|
|
46
48
|
exports.buildHomeGrounding = buildHomeGrounding;
|
|
47
49
|
exports.collectMeetings = collectMeetings;
|
|
@@ -97,6 +99,7 @@ function takeProposals(filePath) {
|
|
|
97
99
|
}
|
|
98
100
|
const external_id_1 = require("./external-id");
|
|
99
101
|
const home_project_1 = require("./home-project");
|
|
102
|
+
const supersedes_1 = require("./supersedes");
|
|
100
103
|
const render_1 = require("./render");
|
|
101
104
|
/**
|
|
102
105
|
* この画面を作ったパッケージの版。**package.json を唯一の出どころにする。**
|
|
@@ -113,6 +116,11 @@ const PKG_VERSION = (() => {
|
|
|
113
116
|
}
|
|
114
117
|
})();
|
|
115
118
|
const DEFAULT_EXCLUDE = ["README.md", "template.md"];
|
|
119
|
+
/**
|
|
120
|
+
* グラフ・エクスプローラーに描かない関係(#699 F3)。`superseded_by` は engine の夜間の精製が古い側に書く
|
|
121
|
+
* `supersedes` の写しで、描くと同じ置き換えが逆向きにもう 1 本出る。frontmatter のパネルには今までどおり出る
|
|
122
|
+
*/
|
|
123
|
+
exports.MIRROR_RELS = new Set(["superseded_by"]);
|
|
116
124
|
/** frontmatterのrelationsを安全に正規化する */
|
|
117
125
|
function toRelations(value) {
|
|
118
126
|
if (!Array.isArray(value))
|
|
@@ -795,6 +803,78 @@ function attachDecisionFollowup(decisions, recordsDir, repoRoot, ledger) {
|
|
|
795
803
|
}
|
|
796
804
|
return [...ledger.keys()].filter((k) => !matched.has(k.normalize("NFC")));
|
|
797
805
|
}
|
|
806
|
+
/**
|
|
807
|
+
* 決定に置き換えの逆引き(置き換え先・置き換えた決定)を載せる(cortex-engine #699 F3・A-1')。
|
|
808
|
+
*
|
|
809
|
+
* 🔴 **逆引きは `supersedes.ts`(engine `scripts/gold/supersedes-index.mjs` の写し)だけで組む**。
|
|
810
|
+
* 材料は新しい側の `relations[supersedes]` だけで、古い側の `superseded_by`(夜間の精製が書く写し)は
|
|
811
|
+
* 読まない。確認済み=新しい側の `status` が `active`。
|
|
812
|
+
*
|
|
813
|
+
* 置き換えが無い決定には**キーごと付けない**(今までと同じ SiteData)
|
|
814
|
+
*/
|
|
815
|
+
/**
|
|
816
|
+
* frontmatter に**書かれたままの** `date` の値(トップレベルの `date:` の行・引用符と行末のコメントを外す)。
|
|
817
|
+
* 無い・frontmatter が無ければ undefined。置き換えの逆引きの決定日だけに使う(engine と同じ日付にするため)
|
|
818
|
+
*/
|
|
819
|
+
function rawFrontmatterDate(raw) {
|
|
820
|
+
const m = String(raw ?? "").replace(/^\uFEFF/, "").match(/^---[ \t]*\r?\n([\s\S]*?)\r?\n---[ \t]*(?:\r?\n|$)/);
|
|
821
|
+
if (!m)
|
|
822
|
+
return undefined;
|
|
823
|
+
const line = m[1].match(/^date:[ \t]*(.*)$/m);
|
|
824
|
+
if (!line)
|
|
825
|
+
return undefined;
|
|
826
|
+
let v = line[1].replace(/\r$/, "").trim();
|
|
827
|
+
const q = v.match(/^(["'])(.*)\1(?:\s+#.*)?$/);
|
|
828
|
+
if (q)
|
|
829
|
+
return q[2];
|
|
830
|
+
v = v.replace(/\s+#.*$/, "").trim();
|
|
831
|
+
return v;
|
|
832
|
+
}
|
|
833
|
+
function attachSupersedes(decisions, rawDateOf = (d) => d.fm.date) {
|
|
834
|
+
const input = decisions.map((d) => ({
|
|
835
|
+
id: d.id,
|
|
836
|
+
// 🔴 **frontmatter に書かれたままの date の文字列**(`rawFrontmatterDate`)。gray-matter は時刻・時差つきの
|
|
837
|
+
// date を Date に読み、sanitizeFm は UTC の日付にするので、`2026-07-01T01:00:00+09:00` が 6/30 にずれる。
|
|
838
|
+
// engine は日付を Date にしない読み方(CORE_SCHEMA)で書かれた文字列の先頭 8 桁を使う。
|
|
839
|
+
// 無ければ写しが ID の先頭 8 桁から取る
|
|
840
|
+
date: rawDateOf(d),
|
|
841
|
+
status: d.fm.status,
|
|
842
|
+
supersedes: d.relations.filter((r) => r.rel === "supersedes").map((r) => r.target),
|
|
843
|
+
}));
|
|
844
|
+
const index = (0, supersedes_1.supersedesIndex)(input);
|
|
845
|
+
const idOfKey = new Map();
|
|
846
|
+
for (const d of decisions) {
|
|
847
|
+
const k = (0, supersedes_1.decisionKeyOf)(d.id);
|
|
848
|
+
if (!idOfKey.has(k))
|
|
849
|
+
idOfKey.set(k, d.id);
|
|
850
|
+
}
|
|
851
|
+
for (const d of decisions) {
|
|
852
|
+
const key = (0, supersedes_1.decisionKeyOf)(d.id);
|
|
853
|
+
const entry = index.get(key);
|
|
854
|
+
if (entry && entry.by.length) {
|
|
855
|
+
d.supersededBy = {
|
|
856
|
+
by: entry.by.map((b) => ({ id: b.id, date: b.date, confirmed: b.confirmed })),
|
|
857
|
+
retired: entry.retired,
|
|
858
|
+
};
|
|
859
|
+
}
|
|
860
|
+
const replaced = [];
|
|
861
|
+
for (const r of d.relations) {
|
|
862
|
+
if (r.rel !== "supersedes")
|
|
863
|
+
continue;
|
|
864
|
+
const k = (0, supersedes_1.decisionKeyOf)(r.target);
|
|
865
|
+
// engine の規則と同じく、自分自身・実在しない決定は数えない
|
|
866
|
+
if (!k || k === key)
|
|
867
|
+
continue;
|
|
868
|
+
const id = idOfKey.get(k);
|
|
869
|
+
if (id === undefined)
|
|
870
|
+
continue;
|
|
871
|
+
if (!replaced.includes(id))
|
|
872
|
+
replaced.push(id);
|
|
873
|
+
}
|
|
874
|
+
if (replaced.length)
|
|
875
|
+
d.supersedesIds = replaced;
|
|
876
|
+
}
|
|
877
|
+
}
|
|
798
878
|
/**
|
|
799
879
|
* AIからの更新提案(`.cortex/gold-proposals.json`)を読む。
|
|
800
880
|
*
|
|
@@ -1509,7 +1589,8 @@ async function scanContent(repoRoot, goldDirName, repoBaseUrl, branch) {
|
|
|
1509
1589
|
}
|
|
1510
1590
|
}
|
|
1511
1591
|
for (const r of toRelations(data.relations))
|
|
1512
|
-
|
|
1592
|
+
if (!exports.MIRROR_RELS.has(r.rel))
|
|
1593
|
+
edges.push({ target: r.target, rel: r.rel });
|
|
1513
1594
|
}
|
|
1514
1595
|
else {
|
|
1515
1596
|
const keyMatch = raw.slice(0, 4000).match(/^- 課題キー: (\S+)$/m);
|
|
@@ -1618,6 +1699,9 @@ function buildGraph(decisions, home, sections, content, targetUrls, repoBaseUrl,
|
|
|
1618
1699
|
};
|
|
1619
1700
|
const addRelationEdges = (fromId, relations) => {
|
|
1620
1701
|
for (const r of relations) {
|
|
1702
|
+
// 🔴 `superseded_by` は描かない(#699 F3)。新しい側の `supersedes` の写しで、描くと同じ置き換えが 2 本になる
|
|
1703
|
+
if (exports.MIRROR_RELS.has(r.rel))
|
|
1704
|
+
continue;
|
|
1621
1705
|
edges.push({ from: fromId, to: classifyTarget(r.target), rel: r.rel });
|
|
1622
1706
|
}
|
|
1623
1707
|
};
|
|
@@ -2063,11 +2147,18 @@ async function build(opts) {
|
|
|
2063
2147
|
// **読めない・使えないなら `null`** で、関係者の並びは今までと 1 バイトも同じ
|
|
2064
2148
|
const engagement = await (0, engagement_1.readEngagementLedger)(repoRoot);
|
|
2065
2149
|
const decisions = [];
|
|
2150
|
+
const rawDates = new Map();
|
|
2066
2151
|
for (const fileName of mdFiles) {
|
|
2067
2152
|
const raw = await node_fs_1.promises.readFile(path.join(dirAbs, fileName), "utf8");
|
|
2068
2153
|
const rec = await parseDecision(raw, fileName, repoRelDir, repo.baseUrl, repo.branch, refs);
|
|
2069
2154
|
decisions.push(rec);
|
|
2155
|
+
// 置き換えの逆引きの決定日(書かれたままの文字列・SiteData には載せない)
|
|
2156
|
+
const rd = rawFrontmatterDate(raw);
|
|
2157
|
+
if (rd !== undefined)
|
|
2158
|
+
rawDates.set(rec, rd);
|
|
2070
2159
|
}
|
|
2160
|
+
// 置き換えの逆引き(#699 F3)。**置き換えの無い案件では 1 件も付かない**
|
|
2161
|
+
attachSupersedes(decisions, (d) => rawDates.get(d) ?? d.fm.date);
|
|
2071
2162
|
// 決定の「その後」(#705)。**帳簿を持たない案件では 1 件も付かず、絞り込みの印も立たない**
|
|
2072
2163
|
// (`decisionFollowup` はキーごと無い=今までと 1 バイトも同じ画面)
|
|
2073
2164
|
const followupLedger = await (0, rederivation_1.readDecisionFollowup)(repoRoot);
|
package/dist/engagement.js
CHANGED
|
@@ -50,7 +50,7 @@ exports.ENGAGEMENT_LEDGER_VERSION = 1;
|
|
|
50
50
|
/** 半減期(日)。engine `weights.mjs` の `HALF_LIFE_DAYS`(正本 6-B「半減期 90 日固定」) */
|
|
51
51
|
exports.HALF_LIFE_DAYS = 90;
|
|
52
52
|
/**
|
|
53
|
-
* 行為 1 件の重み。engine `weights.mjs` の `WEIGHTS` の写し(
|
|
53
|
+
* 行為 1 件の重み。engine `weights.mjs` の `WEIGHTS` の写し(24 値・**表に無い `kind` は 0**)。
|
|
54
54
|
*
|
|
55
55
|
* 🔴 契約の表(fixture)は `message.posted` しか使っていないので、**ここの写し間違いは表では
|
|
56
56
|
* 捕まらない**。重みの番人は `test/member-engagement-parity.test.mjs` の `[重み]` が持つ。
|
|
@@ -80,6 +80,9 @@ exports.WEIGHTS = Object.freeze({
|
|
|
80
80
|
"record.deleted": 0,
|
|
81
81
|
// 本文を取り込んだ(engine #695)。人の行為ではないので 0(話した人は `utterance.spoken` で数える)
|
|
82
82
|
"body.captured": 0,
|
|
83
|
+
// 人の宣言の指紋(cortex-engine #699 F1)。宣言の取り込みが書く行で、関与には数えない(0)。
|
|
84
|
+
// 場は宣言専用(`decl:<owner/repo>`)で帳簿にもともと入らないが、網羅のため写す
|
|
85
|
+
"decl.recorded": 0,
|
|
83
86
|
});
|
|
84
87
|
const DAY_MS = 86400000;
|
|
85
88
|
const cmp = (a, b) => (a < b ? -1 : a > b ? 1 : 0);
|
package/dist/render.js
CHANGED
|
@@ -4983,6 +4983,13 @@ button.site-icon[aria-busy="true"] .site-icon-plus { opacity: 0; }
|
|
|
4983
4983
|
.badge.followup-open { background: #eff6ff; color: #1e40af; border-color: #bfdbfe; }
|
|
4984
4984
|
.badge.followup-superseded, .badge.followup-dropped { background: #f5f5f4; color: #57534e; border-color: #d6d3d1; }
|
|
4985
4985
|
.followup-section { margin: 14px 0 4px; }
|
|
4986
|
+
/* 置き換え(#699 F3)。印は「その後」の置き換え済みと同じ色(同じ意味なので) */
|
|
4987
|
+
.badge.retired { background: #f5f5f4; color: #57534e; border: 1px solid #d6d3d1; }
|
|
4988
|
+
.supersede-section { margin: 14px 0 4px; }
|
|
4989
|
+
.supersede-list { list-style: none; margin: 0 0 8px; padding: 0; }
|
|
4990
|
+
.supersede-row { display: flex; flex-wrap: wrap; gap: 8px; align-items: baseline; font-size: 13.5px; line-height: 1.7; padding: 2px 0; }
|
|
4991
|
+
.supersede-id { font-size: 12.5px; color: var(--muted); word-break: break-all; }
|
|
4992
|
+
.supersede-unconfirmed { font-size: 12px; color: #92400e; background: #fffbeb; border: 1px solid #fde68a; border-radius: 4px; padding: 0 6px; }
|
|
4986
4993
|
.followup-human { font-size: 13.5px; margin: 0 0 6px; color: #166534; }
|
|
4987
4994
|
.followup-list, .followup-evidence { list-style: none; margin: 0 0 8px; padding: 0; }
|
|
4988
4995
|
.followup-row, .followup-evidence-row { display: flex; flex-wrap: wrap; gap: 8px; align-items: baseline; font-size: 13.5px; line-height: 1.7; padding: 2px 0; }
|
|
@@ -6556,6 +6563,7 @@ ${calendarAddUrl.toString()}
|
|
|
6556
6563
|
var FOLLOWUP_FILTER_LABEL = { "followup-done": "その後: 完了", "followup-open": "その後: 進行中", "followup-none": "その後: 記録なし" };
|
|
6557
6564
|
function statusLabel(v) {
|
|
6558
6565
|
if (Object.prototype.hasOwnProperty.call(FOLLOWUP_FILTER_LABEL, v)) return FOLLOWUP_FILTER_LABEL[v];
|
|
6566
|
+
if (v === RETIRED_FILTER) return "置き換え済み";
|
|
6559
6567
|
return v === "draft" ? "未確認のみ" : v === "proposed" ? "提案ありのみ" : v;
|
|
6560
6568
|
}
|
|
6561
6569
|
/**
|
|
@@ -6568,6 +6576,8 @@ ${calendarAddUrl.toString()}
|
|
|
6568
6576
|
var opts = [];
|
|
6569
6577
|
if (hasUnverified(items)) opts.push("draft");
|
|
6570
6578
|
if ((items || []).some(hasProposals)) opts.push("proposed");
|
|
6579
|
+
// 置き換え済み(#699 F3)。決定にしか付かない印なので、決定の一覧でだけ選択肢になる
|
|
6580
|
+
if ((items || []).some(isRetired)) opts.push(RETIRED_FILTER);
|
|
6571
6581
|
if (withFollowup) {
|
|
6572
6582
|
["followup-done", "followup-open", "followup-none"].forEach(function (v) {
|
|
6573
6583
|
if ((items || []).some(function (r) { return matchesFollowupFilter(r, v); })) opts.push(v);
|
|
@@ -6585,6 +6595,8 @@ ${calendarAddUrl.toString()}
|
|
|
6585
6595
|
function matchesStatusFilter(rec, withFollowup) {
|
|
6586
6596
|
if (!listState.status) return true;
|
|
6587
6597
|
if (listState.status === "proposed") return hasProposals(rec);
|
|
6598
|
+
// 決定以外の一覧(タブを移っても絞り込みは残る)では絞らない——全件が消えないように
|
|
6599
|
+
if (listState.status === RETIRED_FILTER) return rec && rec._kind === "decision" ? isRetired(rec) : true;
|
|
6588
6600
|
if (Object.prototype.hasOwnProperty.call(FOLLOWUP_FILTER_LABEL, listState.status)) return withFollowup ? matchesFollowupFilter(rec, listState.status) : true;
|
|
6589
6601
|
return rec.rstatus === listState.status;
|
|
6590
6602
|
}
|
|
@@ -6879,6 +6891,66 @@ ${calendarAddUrl.toString()}
|
|
|
6879
6891
|
return false;
|
|
6880
6892
|
}
|
|
6881
6893
|
|
|
6894
|
+
/* -------------------------------------------------------------------------
|
|
6895
|
+
* 置き換え(#699 F3・オーナー決定 A-1')
|
|
6896
|
+
* -------------------------------------------------------------------------
|
|
6897
|
+
* **逆引きはビルド側が組んである**(supersedes.ts=engine supersedes-index.mjs の写し)。
|
|
6898
|
+
* rec.supersededBy(置き換え先・新しい順)と rec.supersedesIds(置き換えた決定)は、無ければキーごと無い。
|
|
6899
|
+
* 🔴 古い側の superseded_by(夜間の精製が書く写し)は画面でも読まない。
|
|
6900
|
+
*/
|
|
6901
|
+
var RETIRED_FILTER = "retired";
|
|
6902
|
+
/** もう有効でない(確認済みの置き換えが 1 本以上) */
|
|
6903
|
+
function isRetired(rec) {
|
|
6904
|
+
return !!(rec && rec.supersededBy && rec.supersededBy.retired);
|
|
6905
|
+
}
|
|
6906
|
+
/** 一覧・詳細の印。「その後」の印が同じ「置き換え済み」を出しているときは並べない */
|
|
6907
|
+
function retiredBadge(rec) {
|
|
6908
|
+
if (!isRetired(rec) || followupMarkOf(rec) === "superseded") return null;
|
|
6909
|
+
return el("span", { class: "badge retired", text: "置き換え済み" });
|
|
6910
|
+
}
|
|
6911
|
+
/** 決定 1 件の行。SITE にあれば題名で詳細へのリンク、無ければ ID だけ */
|
|
6912
|
+
function supersedeRow(id, date, unconfirmed) {
|
|
6913
|
+
var rec = byId[id];
|
|
6914
|
+
var li = el("li", { class: "supersede-row" });
|
|
6915
|
+
if (rec && rec._kind === "decision") {
|
|
6916
|
+
var a = el("a", { href: "?id=" + encodeURIComponent(id), text: rec.title });
|
|
6917
|
+
a.addEventListener("click", function (ev) {
|
|
6918
|
+
if (ev.defaultPrevented || ev.button !== 0 || ev.metaKey || ev.ctrlKey || ev.shiftKey || ev.altKey) return;
|
|
6919
|
+
ev.preventDefault();
|
|
6920
|
+
go(id);
|
|
6921
|
+
});
|
|
6922
|
+
li.appendChild(a);
|
|
6923
|
+
}
|
|
6924
|
+
li.appendChild(el("span", { class: "supersede-id mono", text: (date ? date + " · " : "") + id }));
|
|
6925
|
+
if (unconfirmed) {
|
|
6926
|
+
li.appendChild(el("span", { class: "supersede-unconfirmed", title: "置き換えた側の決定が、まだ人の確認を経ていません", text: "未確認" }));
|
|
6927
|
+
}
|
|
6928
|
+
return li;
|
|
6929
|
+
}
|
|
6930
|
+
/** 詳細の節「置き換え先の決定」「置き換えた決定」。どちらも無ければ null(要素を作らない) */
|
|
6931
|
+
function supersedeSection(d) {
|
|
6932
|
+
var by = (d && d.supersededBy && d.supersededBy.by) || [];
|
|
6933
|
+
var reps = (d && d.supersedesIds) || [];
|
|
6934
|
+
if (!by.length && !reps.length) return null;
|
|
6935
|
+
var box = el("div", { class: "supersede-section" });
|
|
6936
|
+
if (by.length) {
|
|
6937
|
+
box.appendChild(el("div", { class: "section-label", text: "置き換え先の決定" }));
|
|
6938
|
+
var ul = el("ul", { class: "supersede-list supersede-by" });
|
|
6939
|
+
by.forEach(function (b) { ul.appendChild(supersedeRow(b.id, b.date, !b.confirmed)); });
|
|
6940
|
+
box.appendChild(ul);
|
|
6941
|
+
}
|
|
6942
|
+
if (reps.length) {
|
|
6943
|
+
box.appendChild(el("div", { class: "section-label", text: "置き換えた決定" }));
|
|
6944
|
+
var ul2 = el("ul", { class: "supersede-list supersede-replaces" });
|
|
6945
|
+
reps.forEach(function (id) {
|
|
6946
|
+
var r = byId[id];
|
|
6947
|
+
ul2.appendChild(supersedeRow(id, r && r._kind === "decision" ? r.date : "", false));
|
|
6948
|
+
});
|
|
6949
|
+
box.appendChild(ul2);
|
|
6950
|
+
}
|
|
6951
|
+
return box;
|
|
6952
|
+
}
|
|
6953
|
+
|
|
6882
6954
|
// 未確認が1件も無い一覧では絞り込みを出さない
|
|
6883
6955
|
function hasUnverified(items) {
|
|
6884
6956
|
return (items || []).some(function (r) { return r.rstatus === "draft"; });
|
|
@@ -18033,6 +18105,7 @@ ${calendarAddUrl.toString()}
|
|
|
18033
18105
|
draftBadge(d),
|
|
18034
18106
|
proposalBadge(d),
|
|
18035
18107
|
followupBadge(d),
|
|
18108
|
+
retiredBadge(d),
|
|
18036
18109
|
latestBadge(d),
|
|
18037
18110
|
el("span", { class: "mono", text: d.date || "----" }),
|
|
18038
18111
|
catBadge(d.category),
|
|
@@ -19614,6 +19687,7 @@ ${calendarAddUrl.toString()}
|
|
|
19614
19687
|
var meta = el("div", { class: "detail-meta" }, [
|
|
19615
19688
|
draftBadge(d),
|
|
19616
19689
|
proposalBadge(d),
|
|
19690
|
+
retiredBadge(d),
|
|
19617
19691
|
latestBadge(d),
|
|
19618
19692
|
el("span", { class: "mono", text: d.date || "----" }),
|
|
19619
19693
|
catBadge(d.category),
|
|
@@ -19659,6 +19733,10 @@ ${calendarAddUrl.toString()}
|
|
|
19659
19733
|
app.appendChild(ul);
|
|
19660
19734
|
}
|
|
19661
19735
|
|
|
19736
|
+
// 置き換え(#699 F3)。本文・参照の後、「その後の記録」の前。置き換えの無い決定では何も作らない
|
|
19737
|
+
var sup = supersedeSection(d);
|
|
19738
|
+
if (sup) app.appendChild(sup);
|
|
19739
|
+
|
|
19662
19740
|
appendRecordFooter(d);
|
|
19663
19741
|
}
|
|
19664
19742
|
|
|
@@ -19671,9 +19749,11 @@ ${calendarAddUrl.toString()}
|
|
|
19671
19749
|
}
|
|
19672
19750
|
|
|
19673
19751
|
// ---------- エクスプローラー(Miller columns: Gold層から関係を辿るカラム表示) ----------
|
|
19752
|
+
// superseded_by は**辺としては描かない**(ビルド側の MIRROR_RELS)。ここに置くのは
|
|
19753
|
+
// レコード情報(frontmatter)のパネルが英語の語のまま出さないためだけ(凡例の REL_LABEL には足さない)
|
|
19674
19754
|
var REL_LABEL = {
|
|
19675
19755
|
based_on: "根拠", derived_from: "由来", relates_to: "関連",
|
|
19676
|
-
supersedes: "置換", reference: "参照", source: "出典", link: "リンク",
|
|
19756
|
+
supersedes: "置換", superseded_by: "置換先", reference: "参照", source: "出典", link: "リンク",
|
|
19677
19757
|
};
|
|
19678
19758
|
var explorerState = { folder: null, path: [], q: "", sx: 0, autoRight: false };
|
|
19679
19759
|
// 索引もグラフ本体と同じ材料(削除済みを除いたもの)から作る。別々に間引くと、
|
|
@@ -20000,6 +20080,10 @@ ${calendarAddUrl.toString()}
|
|
|
20000
20080
|
badge.style.borderColor = REL_COLOR[r.rel] || "#cbd5e1";
|
|
20001
20081
|
row.appendChild(badge);
|
|
20002
20082
|
row.appendChild(fmTargetLink(String(r.target)));
|
|
20083
|
+
// 夜間処理が古い側に書く置換先の写しは、置き換えた側が未確認なら unconfirmed: true を持つ(#699 F3)
|
|
20084
|
+
if (r.rel === "superseded_by" && r.unconfirmed === true) {
|
|
20085
|
+
row.appendChild(el("span", { class: "supersede-unconfirmed", title: "置き換えた側の決定が、まだ人の確認を経ていません", text: "未確認" }));
|
|
20086
|
+
}
|
|
20003
20087
|
cell.appendChild(row);
|
|
20004
20088
|
});
|
|
20005
20089
|
} else if (k === FM_LINKED_IDS && Array.isArray(v)) {
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.CONFIRMED_STATUS = void 0;
|
|
7
|
+
exports.decisionKeyOf = decisionKeyOf;
|
|
8
|
+
exports.decisionDateOf = decisionDateOf;
|
|
9
|
+
exports.supersedesIndex = supersedesIndex;
|
|
10
|
+
exports.supersedesOf = supersedesOf;
|
|
11
|
+
exports.retiredIdsOf = retiredIdsOf;
|
|
12
|
+
/**
|
|
13
|
+
* 決定の置き換え(`supersedes`)の逆引き(cortex-engine #699 F3・オーナー決定 A-1'・A-1'')。
|
|
14
|
+
*
|
|
15
|
+
* ## 🔴 これは写しで、正本は engine にある
|
|
16
|
+
*
|
|
17
|
+
* 正本: cortex-engine `scripts/gold/supersedes-index.mjs`(`supersedesIndex`・`decisionKeyOf`・
|
|
18
|
+
* `decisionDateOf`)。**題材で一致を確かめる**: 両リポに同じ `supersedes-index-contract.json`
|
|
19
|
+
* (tools は `test/fixtures/`)を置き、`test/supersedes-index-parity.test.mjs` が全ケースを流す。
|
|
20
|
+
* 規則を変えるときは engine の正本・この写し・題材を同じ日に直す(**tools 側で規則を足さない**)。
|
|
21
|
+
*
|
|
22
|
+
* ## 規則(A-1')
|
|
23
|
+
*
|
|
24
|
+
* 1. 置き換えの正本は**新しい側の `relations[supersedes]` だけ**。古い側の `superseded_by`
|
|
25
|
+
* (engine の夜間の精製が書く写し)は逆引きの材料にしない
|
|
26
|
+
* 2. **確認済み=新しい側の `status` が `active`**。空・`draft`・語彙の外は「未確認」
|
|
27
|
+
* 3. **置き換えられた(もう有効でない・retired)=確認済みの置き換えが 1 本以上**
|
|
28
|
+
* 4. 置き換え先は**全部・新しい順**(決定日の降順 → ID の降順・同じ ID は 1 回)
|
|
29
|
+
* 5. 自分自身を指す・実在しない ID は数えない。古い側の `status` は問わない
|
|
30
|
+
*/
|
|
31
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
32
|
+
/** 確認済みとみなす新しい側の status(これだけ) */
|
|
33
|
+
exports.CONFIRMED_STATUS = "active";
|
|
34
|
+
/**
|
|
35
|
+
* 決定を指す文字列を突き合わせの鍵にする(`decision:` の頭・パス・`.md` を落とす)。
|
|
36
|
+
* engine は `path.basename`(Linux で走る=posix)なので、ここも posix で固定する
|
|
37
|
+
*/
|
|
38
|
+
function decisionKeyOf(ref) {
|
|
39
|
+
return node_path_1.default.posix.basename(String(ref ?? "").trim().replace(/^decision:/, ""), ".md");
|
|
40
|
+
}
|
|
41
|
+
/** `YYYY-MM-DD`。frontmatter の `date`(`20260910` / `2026-09-10`)→ ID の先頭 8 桁 → 無ければ空 */
|
|
42
|
+
function decisionDateOf(date, id) {
|
|
43
|
+
const raw = String(date ?? "").trim();
|
|
44
|
+
const ymd = raw.match(/^(\d{4})-?(\d{2})-?(\d{2})/) || decisionKeyOf(id).match(/^(\d{4})(\d{2})(\d{2})/);
|
|
45
|
+
return ymd ? `${ymd[1]}-${ymd[2]}-${ymd[3]}` : "";
|
|
46
|
+
}
|
|
47
|
+
function normalize(d) {
|
|
48
|
+
if (!d || typeof d !== "object")
|
|
49
|
+
return null;
|
|
50
|
+
const r = d;
|
|
51
|
+
const id = String(r.id ?? "").trim();
|
|
52
|
+
if (!id)
|
|
53
|
+
return null;
|
|
54
|
+
return {
|
|
55
|
+
id,
|
|
56
|
+
key: decisionKeyOf(id),
|
|
57
|
+
date: decisionDateOf(r.date, id),
|
|
58
|
+
status: typeof r.status === "string" ? r.status.trim() : "",
|
|
59
|
+
supersedes: (Array.isArray(r.supersedes) ? r.supersedes : []).map(decisionKeyOf).filter(Boolean),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
/** 新しい順(決定日の降順 → ID の降順)。日付の無いものは最後 */
|
|
63
|
+
function newestFirst(a, b) {
|
|
64
|
+
if (a.date !== b.date) {
|
|
65
|
+
if (!a.date)
|
|
66
|
+
return 1;
|
|
67
|
+
if (!b.date)
|
|
68
|
+
return -1;
|
|
69
|
+
return a.date < b.date ? 1 : -1;
|
|
70
|
+
}
|
|
71
|
+
return a.id < b.id ? 1 : a.id > b.id ? -1 : 0;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* 置き換えの逆引き。
|
|
75
|
+
*
|
|
76
|
+
* @returns `Map<古い決定の鍵, SupersedesEntry>`。置き換えが 1 本も無い決定は入らない
|
|
77
|
+
*/
|
|
78
|
+
function supersedesIndex(decisions) {
|
|
79
|
+
const src = decisions instanceof Map ? decisions.values() : decisions || [];
|
|
80
|
+
const list = [...src].map(normalize).filter((d) => d !== null);
|
|
81
|
+
const byKey = new Map();
|
|
82
|
+
for (const d of list)
|
|
83
|
+
if (!byKey.has(d.key))
|
|
84
|
+
byKey.set(d.key, d);
|
|
85
|
+
const out = new Map();
|
|
86
|
+
for (const d of list) {
|
|
87
|
+
for (const target of new Set(d.supersedes)) {
|
|
88
|
+
const old = byKey.get(target);
|
|
89
|
+
if (!old || old.key === d.key)
|
|
90
|
+
continue; // 実在しない・自分自身
|
|
91
|
+
const entry = out.get(old.key) || { id: old.id, by: [], retired: false };
|
|
92
|
+
if (entry.by.some((x) => decisionKeyOf(x.id) === d.key))
|
|
93
|
+
continue;
|
|
94
|
+
const confirmed = d.status === exports.CONFIRMED_STATUS;
|
|
95
|
+
entry.by.push({ id: d.id, date: d.date, status: d.status, confirmed });
|
|
96
|
+
if (confirmed)
|
|
97
|
+
entry.retired = true;
|
|
98
|
+
out.set(old.key, entry);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
for (const entry of out.values())
|
|
102
|
+
entry.by.sort(newestFirst);
|
|
103
|
+
return out;
|
|
104
|
+
}
|
|
105
|
+
/** その決定の逆引き(無ければ空の形) */
|
|
106
|
+
function supersedesOf(index, id) {
|
|
107
|
+
return index.get(decisionKeyOf(id)) || { id: String(id ?? ""), by: [], retired: false };
|
|
108
|
+
}
|
|
109
|
+
/** 置き換えられた(確認済みの置き換えがある)決定の ID の集合 */
|
|
110
|
+
function retiredIdsOf(index) {
|
|
111
|
+
return new Set([...index.values()].filter((e) => e.retired).map((e) => e.id));
|
|
112
|
+
}
|
package/package.json
CHANGED