@takagaki/cortex-decisions-viewer 0.12.80 → 0.12.81
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 +10 -1
- package/dist/engagement.js +209 -0
- package/dist/rederivation.js +2 -2
- package/package.json +1 -1
package/dist/build.js
CHANGED
|
@@ -58,6 +58,7 @@ const gray_matter_1 = __importDefault(require("gray-matter"));
|
|
|
58
58
|
const marked_1 = require("marked");
|
|
59
59
|
const sanitize_html_1 = __importDefault(require("sanitize-html"));
|
|
60
60
|
const repo_1 = require("./repo");
|
|
61
|
+
const engagement_1 = require("./engagement");
|
|
61
62
|
const rederivation_1 = require("./rederivation");
|
|
62
63
|
// 直近のGold昇格で追加されたレコードのパス集合。レコード生成の各所から参照するのでモジュールスコープに置く
|
|
63
64
|
// (引数で引き回すと呼び出し側の改修が広範囲になる)。build() の冒頭で1回だけ解決する。
|
|
@@ -2051,6 +2052,9 @@ async function build(opts) {
|
|
|
2051
2052
|
blobs: { read: 0, skipped: 0 },
|
|
2052
2053
|
}
|
|
2053
2054
|
: null;
|
|
2055
|
+
// 関与の帳簿(便 14b・#709)。関係者の一覧を目次と同じ「関わりの多い順」に並べる材料。
|
|
2056
|
+
// **読めない・使えないなら `null`** で、関係者の並びは今までと 1 バイトも同じ
|
|
2057
|
+
const engagement = await (0, engagement_1.readEngagementLedger)(repoRoot);
|
|
2054
2058
|
const decisions = [];
|
|
2055
2059
|
for (const fileName of mdFiles) {
|
|
2056
2060
|
const raw = await node_fs_1.promises.readFile(path.join(dirAbs, fileName), "utf8");
|
|
@@ -2106,7 +2110,12 @@ async function build(opts) {
|
|
|
2106
2110
|
const records = await loadGenericRecords(recordsDir, exclude, repo.baseUrl, repo.branch);
|
|
2107
2111
|
// 「人が確認した版から変わりました」の帯と、用語集の並び(#665 便 12b)。
|
|
2108
2112
|
// **帳簿を持たない案件では 1 件も付かず、並びもそのまま**(下の attachRederived)
|
|
2109
|
-
const
|
|
2113
|
+
const rederived = await attachRederived(records, recordsDir, rederive, opts.generatedAt);
|
|
2114
|
+
// 関係者だけ、関与の多い順(engine の目次と同じ `order`・便 14b)。用語集の並び
|
|
2115
|
+
// (`orderByActivity`)とは帳簿も式も別で、区画が重ならないので順番に当てても干渉しない
|
|
2116
|
+
const ordered = e.name === "Members"
|
|
2117
|
+
? (0, engagement_1.orderMembersByEngagement)(rederived, (r) => r.id, engagement, opts.generatedAt)
|
|
2118
|
+
: rederived;
|
|
2110
2119
|
// レコード0件でもタブは出す(空状態から「+AIで登録」できる導線になる)。
|
|
2111
2120
|
// records/ 自体が無いディレクトリ(その区画が未導入の案件)はタブにしない
|
|
2112
2121
|
if (records.length || (await isDirectory(recordsDir))) {
|
|
@@ -0,0 +1,209 @@
|
|
|
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.weightOf = exports.WEIGHTS = exports.HALF_LIFE_DAYS = exports.ENGAGEMENT_LEDGER_VERSION = exports.ENGAGEMENT_LEDGER_PATH = void 0;
|
|
7
|
+
exports.scoreOf = scoreOf;
|
|
8
|
+
exports.engagementOrder = engagementOrder;
|
|
9
|
+
exports.parseEngagementLedger = parseEngagementLedger;
|
|
10
|
+
exports.readEngagementLedger = readEngagementLedger;
|
|
11
|
+
exports.orderMembersByEngagement = orderMembersByEngagement;
|
|
12
|
+
/**
|
|
13
|
+
* 関係者の一覧を「関わりの多い順」に並べる(便 14b・#709)。
|
|
14
|
+
*
|
|
15
|
+
* ## 写しの正本は engine
|
|
16
|
+
*
|
|
17
|
+
* 式の正本は cortex-engine の `scripts/engagement/tiers.mjs`(`scoreOf` と `assignTiers` の
|
|
18
|
+
* `order`)と `scripts/engagement/weights.mjs`(重みの表・半減期)。記憶の目次
|
|
19
|
+
* (`Cortex/Index.md` の関係者の節・`scripts/memory-index.mjs` の `memberRows`)は正本を直に
|
|
20
|
+
* 呼んでいるので、**画面の並びがここでずれると、同じ人が画面と目次で別の順に出る**。
|
|
21
|
+
* どちらも正しく描画され、エラーも空欄も出ない——だから写しの契約を表で持つ
|
|
22
|
+
* (`test/member-engagement-parity.test.mjs` が engine の
|
|
23
|
+
* `test/fixtures/engagement-tiers-contract.json` を 1 バイト違わず写した表を読む)。
|
|
24
|
+
*
|
|
25
|
+
* **写すのは並び(`order`)だけ。** 段階(`tiers`)・群(`groups`)・上位集合(`top_set`)・
|
|
26
|
+
* 食い違い(`mismatch`)・不感帯(`basis`)は画面が使わない。とくに `order` は便 #543 で群分けを
|
|
27
|
+
* やめて「score 降順・同点は `person` 昇順」だけになったので、**不感帯の基準にも段階にも
|
|
28
|
+
* 依存しない**。使わない式を写すと、正本が直した日に写しの直し忘れが静かに残る。
|
|
29
|
+
*
|
|
30
|
+
* ## 🔴 帳簿が無い・壊れている・版が違う案件は「今までと 1 バイトも同じ画面」
|
|
31
|
+
*
|
|
32
|
+
* 読み方は目次(`parseLedgerText` と `memberRows`)と同じ規律:
|
|
33
|
+
* - 無い・JSON として読めない・オブジェクトでない・`version` が違う → 並べ替えない
|
|
34
|
+
* - **読めても使える行が 0 件なら並べ替えない**(観測が始まったばかりの案件の
|
|
35
|
+
* `{"version":1,"people":[]}`。目次が「本式と名乗らない」のと同じ線)
|
|
36
|
+
* - 壊れた行(`null`・`person` が文字列でない・`id_kind`/`id` が無い)は**その行だけ捨てる**
|
|
37
|
+
* (行の中の `counts_by_month` は均さない=正本と同じ値を渡す)
|
|
38
|
+
*
|
|
39
|
+
* ## 🔴 時計を中で読まない
|
|
40
|
+
*
|
|
41
|
+
* `now` は引数(ビルドの `generatedAt`)。半減期が全員共通なので、全ての月が過去なら `now` を
|
|
42
|
+
* 動かしても順は変わらないが、先付けの月(経過日数 0 に切り上げる)があると変わる。
|
|
43
|
+
*/
|
|
44
|
+
const promises_1 = __importDefault(require("node:fs/promises"));
|
|
45
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
46
|
+
/** 帳簿の置き場(engine `scripts/engagement/keys.mjs` の `LEDGER_PATH`) */
|
|
47
|
+
exports.ENGAGEMENT_LEDGER_PATH = ".cortex/gold-member-engagement.json";
|
|
48
|
+
/** 帳簿の版(engine `scripts/memory-index.mjs` の `LEDGER_VERSION`。違ったら読まない) */
|
|
49
|
+
exports.ENGAGEMENT_LEDGER_VERSION = 1;
|
|
50
|
+
/** 半減期(日)。engine `weights.mjs` の `HALF_LIFE_DAYS`(正本 6-B「半減期 90 日固定」) */
|
|
51
|
+
exports.HALF_LIFE_DAYS = 90;
|
|
52
|
+
/**
|
|
53
|
+
* 行為 1 件の重み。engine `weights.mjs` の `WEIGHTS` の写し(22 値・**表に無い `kind` は 0**)。
|
|
54
|
+
*
|
|
55
|
+
* 🔴 契約の表(fixture)は `message.posted` しか使っていないので、**ここの写し間違いは表では
|
|
56
|
+
* 捕まらない**。重みの番人は `test/member-engagement-parity.test.mjs` の `[重み]` が持つ。
|
|
57
|
+
*/
|
|
58
|
+
exports.WEIGHTS = Object.freeze({
|
|
59
|
+
"message.posted": 1.0,
|
|
60
|
+
"message.edited": 0.3,
|
|
61
|
+
"message.deleted": 0,
|
|
62
|
+
"comment.posted": 1.0,
|
|
63
|
+
"comment.edited": 0.3,
|
|
64
|
+
"comment.deleted": 0,
|
|
65
|
+
"comment.resolved": 1.0,
|
|
66
|
+
"issue.created": 1.5,
|
|
67
|
+
"issue.updated": 0.3,
|
|
68
|
+
"issue.closed": 1.0,
|
|
69
|
+
"page.revised": 1.5,
|
|
70
|
+
"page.deleted": 0,
|
|
71
|
+
"commit.pushed": 1.0,
|
|
72
|
+
"pr.opened": 1.5,
|
|
73
|
+
"pr.merged": 1.5,
|
|
74
|
+
"pr.closed": 1.0,
|
|
75
|
+
"review.submitted": 1.5,
|
|
76
|
+
"utterance.spoken": 1.0,
|
|
77
|
+
"meeting.held": 1.0,
|
|
78
|
+
"record.confirmed": 2.0,
|
|
79
|
+
"record.edited": 2.0,
|
|
80
|
+
"record.deleted": 0,
|
|
81
|
+
});
|
|
82
|
+
const DAY_MS = 86400000;
|
|
83
|
+
const cmp = (a, b) => (a < b ? -1 : a > b ? 1 : 0);
|
|
84
|
+
/** 1 件の重み。語彙外は 0(engine `weightOf`) */
|
|
85
|
+
const weightOf = (kind) => Object.prototype.hasOwnProperty.call(exports.WEIGHTS, kind) ? exports.WEIGHTS[kind] : 0;
|
|
86
|
+
exports.weightOf = weightOf;
|
|
87
|
+
/** 月の中央日(`YYYY-MM` → その月の 15 日 12:00 UTC・engine `monthCenterMs`) */
|
|
88
|
+
const monthCenterMs = (month) => Date.parse(`${month}-15T12:00:00Z`);
|
|
89
|
+
/**
|
|
90
|
+
* score = Σ_月 Σ_kind w × n × 0.5^(その月の中央日からの経過日数 / 90)(engine `scoreOf` の写し)。
|
|
91
|
+
*
|
|
92
|
+
* 🔴 **足す順を正本と同じに決め打つ**(月昇順 × `kind` 昇順)。浮動小数の和は順序で最下位
|
|
93
|
+
* ビットが変わるので、順を変えると同点の判定が正本とずれうる。
|
|
94
|
+
* 未来の月は経過日数 0 に切り上げる(増幅させない)。
|
|
95
|
+
*/
|
|
96
|
+
function scoreOf(countsByMonth, nowIso) {
|
|
97
|
+
const nowMs = Date.parse(nowIso);
|
|
98
|
+
let total = 0;
|
|
99
|
+
for (const month of Object.keys(countsByMonth || {}).sort(cmp)) {
|
|
100
|
+
const elapsedDays = Math.max(0, (nowMs - monthCenterMs(month)) / DAY_MS);
|
|
101
|
+
const decay = Math.pow(0.5, elapsedDays / exports.HALF_LIFE_DAYS);
|
|
102
|
+
const byKind = (countsByMonth || {})[month] || {};
|
|
103
|
+
for (const kind of Object.keys(byKind).sort(cmp))
|
|
104
|
+
total += (0, exports.weightOf)(kind) * Number(byKind[kind] || 0) * decay;
|
|
105
|
+
}
|
|
106
|
+
return total;
|
|
107
|
+
}
|
|
108
|
+
/** engine `unresolvedPersonOf`(順位に混ぜる鍵。画面には出ない) */
|
|
109
|
+
const unresolvedPersonOf = (u) => `unresolved:${String(u.id_kind)}:${String(u.id)}`;
|
|
110
|
+
/**
|
|
111
|
+
* 並び(engine `assignTiers(...).order` の写し)。**score 降順・同点は `person` 昇順。**
|
|
112
|
+
*
|
|
113
|
+
* 名簿に無い発言者(`unresolved:`)も**順位には混ぜる**(正本と同じ母集団で並べる)。
|
|
114
|
+
* 画面に出ないので位置が空くだけで、名簿の人どうしの前後は変わらない。
|
|
115
|
+
*/
|
|
116
|
+
function engagementOrder(ledger, nowIso) {
|
|
117
|
+
const entries = [
|
|
118
|
+
...ledger.people.map((p) => ({ person: p.person, counts: p.counts_by_month || {} })),
|
|
119
|
+
...ledger.unresolved.map((u) => ({ person: unresolvedPersonOf(u), counts: u.counts_by_month || {} })),
|
|
120
|
+
];
|
|
121
|
+
const scores = new Map();
|
|
122
|
+
for (const e of entries)
|
|
123
|
+
scores.set(e.person, scoreOf(e.counts, nowIso));
|
|
124
|
+
return entries
|
|
125
|
+
.slice()
|
|
126
|
+
.sort((a, b) => (scores.get(b.person) - scores.get(a.person)) || cmp(a.person, b.person))
|
|
127
|
+
.map((e) => e.person);
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* 帳簿の生テキストを読む。**読めない・使えないときは `null`**(=並べ替えない)。
|
|
131
|
+
*
|
|
132
|
+
* 規律は engine の目次と同じ(`parseLedgerText` → `memberRows` の行の篩 → 使える行 0 件なら
|
|
133
|
+
* 本式と名乗らない)。
|
|
134
|
+
*/
|
|
135
|
+
function parseEngagementLedger(text) {
|
|
136
|
+
if (text === null || text === undefined || String(text).trim() === "")
|
|
137
|
+
return null;
|
|
138
|
+
let parsed;
|
|
139
|
+
try {
|
|
140
|
+
parsed = JSON.parse(String(text));
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
146
|
+
return null;
|
|
147
|
+
const obj = parsed;
|
|
148
|
+
if (Number(obj.version) !== exports.ENGAGEMENT_LEDGER_VERSION)
|
|
149
|
+
return null;
|
|
150
|
+
const rowsOf = (value) => (Array.isArray(value) ? value : []).filter((v) => Boolean(v) && typeof v === "object");
|
|
151
|
+
const peopleRows = rowsOf(obj.people);
|
|
152
|
+
const unresolvedRows = rowsOf(obj.unresolved);
|
|
153
|
+
// 使える行の数は篩う前で数える(目次の `usableRows` と同じ線)。篩ったあとに 0 人になっても
|
|
154
|
+
// 画面は元の並びのまま(全員が「帳簿にいない人」になるだけ)なので、結果はどちらでも同じ
|
|
155
|
+
if (peopleRows.length + unresolvedRows.length === 0)
|
|
156
|
+
return null;
|
|
157
|
+
// 🔴 **`counts_by_month` は均さずにそのまま渡す**(実装レビュー 🟡1)。文字列などの壊れた値を
|
|
158
|
+
// ここで `{}` に直すと、正本(`p.counts_by_month || {}` をそのまま `scoreOf` に渡す)と
|
|
159
|
+
// 並びが食い違う。壊れた値で並びが崩れるなら、直すのは正本の側(申し送り)
|
|
160
|
+
return {
|
|
161
|
+
people: peopleRows
|
|
162
|
+
.filter((p) => typeof p.person === "string" && p.person !== "")
|
|
163
|
+
.map((p) => ({ person: p.person, counts_by_month: p.counts_by_month })),
|
|
164
|
+
unresolved: unresolvedRows
|
|
165
|
+
.filter((u) => u.id_kind != null && u.id != null)
|
|
166
|
+
.map((u) => ({ id_kind: u.id_kind, id: u.id, counts_by_month: u.counts_by_month })),
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
/** 案件リポから帳簿を読む(無ければ `null`・例外にしない) */
|
|
170
|
+
async function readEngagementLedger(repoRoot) {
|
|
171
|
+
let text;
|
|
172
|
+
try {
|
|
173
|
+
text = await promises_1.default.readFile(node_path_1.default.join(repoRoot, exports.ENGAGEMENT_LEDGER_PATH), "utf8");
|
|
174
|
+
}
|
|
175
|
+
catch {
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
return parseEngagementLedger(text);
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* 関係者の記録を帳簿の並びに並べ替える。
|
|
182
|
+
*
|
|
183
|
+
* - 帳簿が `null` → **同じ配列をそのまま返す**(1 バイトも触らない)
|
|
184
|
+
* - 帳簿にいる人(`member:<id>` = 記録の `id`)は `order` の位置順
|
|
185
|
+
* - 帳簿にいない人は**末尾**へ、**元の並びのまま**(安定)
|
|
186
|
+
* - 🔴 **確認済みかどうか(`status`)は見ない**(#690 と同じ・オーナー決定 2026-09-21
|
|
187
|
+
* 「アクティブ、ドラフト関係なく」)。受け取りもしない
|
|
188
|
+
*
|
|
189
|
+
* @param idOf 記録の `id`(frontmatter の `id`。帳簿の `person` と同じ綴り)
|
|
190
|
+
*/
|
|
191
|
+
function orderMembersByEngagement(records, idOf, ledger, nowIso) {
|
|
192
|
+
if (!ledger || !records.length)
|
|
193
|
+
return records;
|
|
194
|
+
const position = new Map();
|
|
195
|
+
engagementOrder(ledger, nowIso).forEach((person, i) => {
|
|
196
|
+
if (!position.has(person))
|
|
197
|
+
position.set(person, i);
|
|
198
|
+
});
|
|
199
|
+
return records
|
|
200
|
+
.map((r, i) => ({ r: r, i: i, pos: position.get(idOf(r)) }))
|
|
201
|
+
.sort((a, b) => {
|
|
202
|
+
if (a.pos != null && b.pos != null && a.pos !== b.pos)
|
|
203
|
+
return a.pos - b.pos;
|
|
204
|
+
if ((a.pos == null) !== (b.pos == null))
|
|
205
|
+
return a.pos == null ? 1 : -1;
|
|
206
|
+
return a.i - b.i;
|
|
207
|
+
})
|
|
208
|
+
.map((s) => s.r);
|
|
209
|
+
}
|
package/dist/rederivation.js
CHANGED
|
@@ -51,7 +51,7 @@ const commit_classify_1 = require("./commit-classify");
|
|
|
51
51
|
exports.LEDGER_VERSION = 1;
|
|
52
52
|
/** 帯を出す区画。**用語とメンバーだけ**(Decisions は積み上げ・Rules は便 13) */
|
|
53
53
|
const BAND_SECTION_RE = /^Cortex\/(?:Glossary|Members)\/records\/[^/]+\.md$/;
|
|
54
|
-
/**
|
|
54
|
+
/** 並びを変える区画。**用語集だけ**(関係者の並びは関与の帳簿で `engagement.ts` が決める・便 14b) */
|
|
55
55
|
const ORDER_SECTION_RE = /^Cortex\/Glossary\/records\/[^/]+\.md$/;
|
|
56
56
|
/** ACT-R base-level の減衰(#432 裁定 5-5: d = 0.5) */
|
|
57
57
|
const ACTR_DECAY = 0.5;
|
|
@@ -493,7 +493,7 @@ function rederivedBandOf(recordPath, status, ledgers, identities, verdict, miss)
|
|
|
493
493
|
* - 活性度を持つレコードが 1 件も無い区画は **1 バイトも触らない**(帳簿が無い案件は従来順)
|
|
494
494
|
* - 活性度を知らないレコードは**末尾**へ、**元の並びのまま**(安定)
|
|
495
495
|
* - 同じ活性度なら元の並び(日付降順 → タイトル昇順がそのまま残る)
|
|
496
|
-
* -
|
|
496
|
+
* - 用語集だけ(関係者の並びは `engagement.ts`・便 14b。Decisions と Rules は対象外)
|
|
497
497
|
*
|
|
498
498
|
* ℹ️ `ledgers` と `statusOf` は**受け取り続ける**。`ledgers` は活性度のために要る。
|
|
499
499
|
* `statusOf` は並びに使わなくなったが、**引数から落とすと「群分けを足し戻す」退行が
|
package/package.json
CHANGED