@takagaki/cortex-decisions-viewer 0.12.146 → 0.12.150
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 +26 -174
- package/dist/change-log.js +7 -2
- package/dist/permissions-data.js +6 -0
- package/dist/rederivation.js +2 -2
- package/dist/render.js +261 -716
- package/dist/repo.js +1 -1
- package/dist/story.js +119 -4
- package/dist/view-dir.js +4 -4
- package/package.json +1 -1
package/dist/repo.js
CHANGED
|
@@ -155,7 +155,7 @@ function gitLog(cwd, args, what = "git の履歴") {
|
|
|
155
155
|
//
|
|
156
156
|
// ただし「**git が無い**(ENOENT)」「**git リポジトリでない**(git の致命エラー = 128)」は
|
|
157
157
|
// **既定の状態**——手元のビルドやテストで普通に起きるので、ここで警告するとログが
|
|
158
|
-
// 常に注意で埋まる(
|
|
158
|
+
// 常に注意で埋まる(weekly-curation の「無いことは警告しない」と同じ規律)。
|
|
159
159
|
// 戻り値の契約(空文字 = 履歴なしで正常継続)はどちらでも変えない
|
|
160
160
|
const err = e;
|
|
161
161
|
if (err.code !== "ENOENT" && err.status !== 128) {
|
package/dist/story.js
CHANGED
|
@@ -3,13 +3,17 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.STORY_DIGEST_EMPTY_LINE = exports.STORY_MAX_MONTHS = exports.STORY_MAX_WEEKS = exports.STORY_MONTHS_DIR = exports.STORY_WEEKS_DIR = exports.STORY_MAX_FILE_BYTES = exports.STORY_COLLAPSE_QUIET_DAYS = exports.STORY_EMPTY_LINE = exports.STORY_MAX_DAYS = exports.STORY_DAYS_DIR = exports.STORY_LEDGER_FILE = void 0;
|
|
6
|
+
exports.STORY_DIGEST_EMPTY_LINE = exports.STORY_MAX_MONTHS = exports.STORY_MAX_WEEKS = exports.STORY_MONTHS_DIR = exports.STORY_WEEKS_DIR = exports.STORY_LINK_MIN_NAME = exports.STORY_LINK_KINDS = exports.STORY_MAX_FILE_BYTES = exports.STORY_COLLAPSE_QUIET_DAYS = exports.STORY_EMPTY_LINE = exports.STORY_MAX_DAYS = exports.STORY_DAYS_DIR = exports.STORY_LEDGER_FILE = void 0;
|
|
7
7
|
exports.parseStoryLedger = parseStoryLedger;
|
|
8
8
|
exports.partialDaysOf = partialDaysOf;
|
|
9
9
|
exports.onDaysOf = onDaysOf;
|
|
10
10
|
exports.successDaysOf = successDaysOf;
|
|
11
11
|
exports.nightResultOf = nightResultOf;
|
|
12
12
|
exports.splitFrontmatter = splitFrontmatter;
|
|
13
|
+
exports.dayRelationTargets = dayRelationTargets;
|
|
14
|
+
exports.dayPartialHour = dayPartialHour;
|
|
15
|
+
exports.storyNameLinks = storyNameLinks;
|
|
16
|
+
exports.splitByNameLinks = splitByNameLinks;
|
|
13
17
|
exports.storyDayMarkdown = storyDayMarkdown;
|
|
14
18
|
exports.isEngineQuietDay = isEngineQuietDay;
|
|
15
19
|
exports.todayJst = todayJst;
|
|
@@ -148,7 +152,7 @@ function nightResultOf(ledger) {
|
|
|
148
152
|
}
|
|
149
153
|
/**
|
|
150
154
|
* 日・週・月のページの先頭の frontmatter(`---` で囲んだ塊)を本文から分ける(純関数)。
|
|
151
|
-
* 日のページは `date
|
|
155
|
+
* 日のページは `date`・`partial_at`(途中の日だけ)・`relations` を持ちうる(B-story)。本文には出さない(`partial_at` は日本時間の時だけを見出しの横に出す・`dayPartialHour`)。frontmatter が無い・閉じていないページは本文のまま返す。
|
|
152
156
|
* YAML としては読まない(壊れた YAML のページでも本文を落とさない)
|
|
153
157
|
*/
|
|
154
158
|
function splitFrontmatter(content) {
|
|
@@ -162,6 +166,107 @@ function splitFrontmatter(content) {
|
|
|
162
166
|
return { frontmatter: null, body: text };
|
|
163
167
|
return { frontmatter: m[1] ?? "", body: text.slice(m[0].length) };
|
|
164
168
|
}
|
|
169
|
+
// ---------------------------------------------------------------------------
|
|
170
|
+
// 名前のリンク(`story.screen-name-links`)。日のページの `relations` で当てる(本文からは名前を拾わない)
|
|
171
|
+
// ---------------------------------------------------------------------------
|
|
172
|
+
/** 名前のリンクにする記録の種類(id の頭)。同じ名前が複数の種類にあるときはこの順で勝つ(関係者 → 用語 → ルール・設計 A4) */
|
|
173
|
+
exports.STORY_LINK_KINDS = ["member", "term", "rule"];
|
|
174
|
+
/** 名前の最小の長さ(1 文字の名前は本文のあちこちに当たるので当てない) */
|
|
175
|
+
exports.STORY_LINK_MIN_NAME = 2;
|
|
176
|
+
/**
|
|
177
|
+
* 日のページの frontmatter から `relations` の `target` を取る(純関数・YAML としては読まない)。
|
|
178
|
+
* `- { rel: relates_to, target: "term:設計" }` も、`- rel:` と `target:` を行で分けた形も読む。壊れた行は飛ばす
|
|
179
|
+
*/
|
|
180
|
+
function dayRelationTargets(frontmatter) {
|
|
181
|
+
if (!frontmatter)
|
|
182
|
+
return [];
|
|
183
|
+
const out = [];
|
|
184
|
+
for (const line of frontmatter.split("\n")) {
|
|
185
|
+
const m = line.match(/\btarget\s*:\s*(?:"([^"]*)"|'([^']*)'|([^\s,}"']+))/);
|
|
186
|
+
const t = (m ? (m[1] ?? m[2] ?? m[3] ?? "") : "").trim();
|
|
187
|
+
if (t && !out.includes(t))
|
|
188
|
+
out.push(t);
|
|
189
|
+
}
|
|
190
|
+
return out;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* 日のページの frontmatter の `partial_at`(途中の日の印・ISO 8601)から、日本時間の時(0〜23)を取る(純関数・YAML としては読まない)。
|
|
194
|
+
* 欄が無い・ISO として読めない日は `null`(何も出さない)。日本時間は UTC+9 固定(夏時間なし)
|
|
195
|
+
*/
|
|
196
|
+
function dayPartialHour(frontmatter) {
|
|
197
|
+
if (!frontmatter)
|
|
198
|
+
return null;
|
|
199
|
+
for (const line of frontmatter.split("\n")) {
|
|
200
|
+
const m = line.match(/^partial_at\s*:\s*(?:"([^"]*)"|'([^']*)'|([^\s#]+))/);
|
|
201
|
+
if (!m)
|
|
202
|
+
continue;
|
|
203
|
+
const raw = (m[1] ?? m[2] ?? m[3] ?? "").trim();
|
|
204
|
+
if (!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?(?:Z|[+-]\d{2}:\d{2})$/.test(raw))
|
|
205
|
+
return null;
|
|
206
|
+
const t = Date.parse(raw);
|
|
207
|
+
return Number.isFinite(t) ? new Date(t + 9 * 3600 * 1000).getUTCHours() : null;
|
|
208
|
+
}
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* その日の `relations` の target から、名前のリンクの表を作る(純関数)。
|
|
213
|
+
*
|
|
214
|
+
* - 材料(`sources`)に **無い id は捨てる**(廃止・削除された記録、実在しない id、記録ではない id=決定など)。飛び先の無い線は出さない
|
|
215
|
+
* - 同じ名前が複数の記録にあれば、関係者 → 用語 → ルールの順で勝つ。同じ種類なら先に出た target
|
|
216
|
+
* - 2 文字未満の名前は当てない。返りは名前の長い順(同じ長さは文字の順)。1 件も無ければ空
|
|
217
|
+
*/
|
|
218
|
+
function storyNameLinks(targets, sources) {
|
|
219
|
+
const kindOf = (id) => exports.STORY_LINK_KINDS.findIndex((k) => id.startsWith(`${k}:`));
|
|
220
|
+
const ids = [...new Set(targets)].filter((t) => kindOf(t) >= 0 && sources.has(t));
|
|
221
|
+
ids.sort((a, b) => kindOf(a) - kindOf(b));
|
|
222
|
+
const byName = new Map();
|
|
223
|
+
for (const id of ids) {
|
|
224
|
+
for (const raw of sources.get(id).names) {
|
|
225
|
+
const name = String(raw ?? "").trim();
|
|
226
|
+
if (name.length >= exports.STORY_LINK_MIN_NAME && !byName.has(name))
|
|
227
|
+
byName.set(name, id);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
return [...byName.entries()]
|
|
231
|
+
.map(([name, id]) => ({ name, id }))
|
|
232
|
+
.sort((a, b) => b.name.length - a.name.length || (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* 画面側の純関数: 1 つの文字列を、名前に当たる所とそれ以外に分ける。**画面の JS にそのまま埋め込む**ので、このファイルの外の物を使わない。
|
|
236
|
+
* 長い名前を先に当てる(`links` は長い順で渡す)。当たらなければ 1 件(元の文字)を返す
|
|
237
|
+
*/
|
|
238
|
+
function splitByNameLinks(text, links) {
|
|
239
|
+
if (!text || !links || !links.length)
|
|
240
|
+
return [{ text: text }];
|
|
241
|
+
const byName = {};
|
|
242
|
+
const names = [];
|
|
243
|
+
links.forEach((l) => {
|
|
244
|
+
if (l && l.name && l.id && !Object.prototype.hasOwnProperty.call(byName, l.name)) {
|
|
245
|
+
byName[l.name] = l.id;
|
|
246
|
+
names.push(l.name);
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
names.sort((a, b) => b.length - a.length);
|
|
250
|
+
// 英数字で始まる・終わる名前は、前後が英数字のときは当てない(`AI` が `MAIN` の途中に当たらない)。日本語の名前は途中でも当てる
|
|
251
|
+
const word = (n) => n.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
252
|
+
const re = new RegExp(names.map((n) => (/^[A-Za-z0-9_]/.test(n) ? "(?<![A-Za-z0-9_])" : "") + word(n) + (/[A-Za-z0-9_]$/.test(n) ? "(?![A-Za-z0-9_])" : "")).join("|"), "g");
|
|
253
|
+
const out = [];
|
|
254
|
+
let last = 0;
|
|
255
|
+
let m;
|
|
256
|
+
while ((m = re.exec(text)) !== null) {
|
|
257
|
+
if (!m[0]) {
|
|
258
|
+
re.lastIndex++;
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
if (m.index > last)
|
|
262
|
+
out.push({ text: text.slice(last, m.index) });
|
|
263
|
+
out.push({ text: m[0], id: byName[m[0]] });
|
|
264
|
+
last = m.index + m[0].length;
|
|
265
|
+
}
|
|
266
|
+
if (last < text.length)
|
|
267
|
+
out.push({ text: text.slice(last) });
|
|
268
|
+
return out.length ? out : [{ text: text }];
|
|
269
|
+
}
|
|
165
270
|
/** 曜日つきの見出し(`# 2026-09-24(木)`)の文字。無ければ日付だけ */
|
|
166
271
|
function headingOf(lines, day) {
|
|
167
272
|
for (const l of lines) {
|
|
@@ -372,7 +477,9 @@ editUrlOf = () => null,
|
|
|
372
477
|
// 日本時間の今日(テストは時計を注入する)
|
|
373
478
|
today = todayJst(),
|
|
374
479
|
// 帳簿の置き場(既定は `<repoRoot>/.cortex`)
|
|
375
|
-
ledgerDir
|
|
480
|
+
ledgerDir,
|
|
481
|
+
// 日の `relations` の target から名前のリンクの表を作る口(build が記録の表を握って渡す。無ければリンクは付けない)
|
|
482
|
+
linksOf = () => []) {
|
|
376
483
|
const ledger = await loadStoryLedger(repoRoot, true, ledgerDir);
|
|
377
484
|
if (!ledger)
|
|
378
485
|
return null;
|
|
@@ -432,7 +539,15 @@ ledgerDir) {
|
|
|
432
539
|
continue;
|
|
433
540
|
const { heading, markdown } = storyDayMarkdown(raw, f.day);
|
|
434
541
|
const editUrl = editUrlOf(f.rel);
|
|
435
|
-
|
|
542
|
+
const fm = splitFrontmatter(raw).frontmatter;
|
|
543
|
+
const links = linksOf(dayRelationTargets(fm));
|
|
544
|
+
const partialHour = dayPartialHour(fm);
|
|
545
|
+
out.push({
|
|
546
|
+
date: f.day, heading: heading, bodyHtml: await renderMarkdown(markdown),
|
|
547
|
+
...(editUrl ? { editUrl: editUrl } : {}),
|
|
548
|
+
...(partialHour !== null ? { partialHour: partialHour } : {}),
|
|
549
|
+
...(links.length ? { links: links } : {}),
|
|
550
|
+
});
|
|
436
551
|
}
|
|
437
552
|
// 一番古い「動きのある日」から今日まで、日付の抜けを動きのない日の行で埋める。動きのある日が 1 つも無ければ出さない
|
|
438
553
|
// 抜けの日は、帳簿の「成功した日」(行があり・on で・途中まででない日)なら動きなし、そうでなければ更新の失敗として出す
|
package/dist/view-dir.js
CHANGED
|
@@ -31,8 +31,9 @@ exports.viewCountsLine = viewCountsLine;
|
|
|
31
31
|
*
|
|
32
32
|
* ## 名前(engine の宣言表 `publish: "viewer"` の行・V10a の `VIEW_HELD`)
|
|
33
33
|
*
|
|
34
|
-
* - 読む
|
|
35
|
-
* -
|
|
34
|
+
* - 読む 10 本のうち、記憶側が置くのは 8 本+順位 1 本(`VIEW_NAMES`)
|
|
35
|
+
* - 確認の帳簿(`gold-confirmation`・再導出の帯の読み手 `readRederivationLedgers` が読む)は置かれないので、いつも `.cortex` から読む
|
|
36
|
+
* - 名寄せの提案(`gold-proposals`)はビューアが読まなくなった(cortex-engine #1094 B-viewer V4・問いはポータルから)。置かれても使わない
|
|
36
37
|
*/
|
|
37
38
|
const node_child_process_1 = require("node:child_process");
|
|
38
39
|
const node_crypto_1 = require("node:crypto");
|
|
@@ -40,7 +41,7 @@ const promises_1 = __importDefault(require("node:fs/promises"));
|
|
|
40
41
|
const node_os_1 = __importDefault(require("node:os"));
|
|
41
42
|
const node_path_1 = __importDefault(require("node:path"));
|
|
42
43
|
const ledger_dir_1 = require("./ledger-dir");
|
|
43
|
-
/** 記憶側が置く名前(宣言表の `publish: "viewer"` の行から、置かない
|
|
44
|
+
/** 記憶側が置く名前(宣言表の `publish: "viewer"` の行から、置かない 1 本と読まなくなった名寄せの提案を除いた 8 本と順位)。並びは名前順 */
|
|
44
45
|
exports.VIEW_NAMES = Object.freeze([
|
|
45
46
|
"gold-activity",
|
|
46
47
|
"gold-change-log",
|
|
@@ -54,7 +55,6 @@ exports.VIEW_NAMES = Object.freeze([
|
|
|
54
55
|
]);
|
|
55
56
|
/** 置かれない名前(いつも `.cortex` から読む)と理由 */
|
|
56
57
|
exports.VIEW_HELD = Object.freeze({
|
|
57
|
-
"gold-proposals": "名寄せの提案のカード(A1 の答えまで .cortex)",
|
|
58
58
|
"gold-confirmation": "人が確認した版と機械の書き直し(再導出の帯の材料・readRederivationLedgers)",
|
|
59
59
|
});
|
|
60
60
|
/** 目次の名前(engine `VIEW_INDEX_NAME`) */
|
package/package.json
CHANGED