@takagaki/cortex-decisions-viewer 0.12.142 → 0.12.145
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 +14 -121
- package/dist/rederivation.js +1 -58
- package/dist/render.js +113 -248
- package/dist/repo.js +1 -62
- package/dist/story.js +57 -22
- package/dist/view-dir.js +2 -2
- package/package.json +1 -1
package/dist/repo.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.resolveRepo = resolveRepo;
|
|
4
|
-
exports.latestGeneratedPaths = latestGeneratedPaths;
|
|
5
4
|
exports.normalizeToHttps = normalizeToHttps;
|
|
6
5
|
exports.classifyCommit = classifyCommit;
|
|
7
6
|
exports.lastSubstantiveUpdate = lastSubstantiveUpdate;
|
|
@@ -18,64 +17,6 @@ function resolveRepo(opts) {
|
|
|
18
17
|
const remote = tryGitRemote(opts.cwd);
|
|
19
18
|
return { baseUrl: remote ? normalizeToHttps(remote) : null, branch };
|
|
20
19
|
}
|
|
21
|
-
/**
|
|
22
|
-
* 直近のGold昇格で追加されたレコードのパス集合を返す。
|
|
23
|
-
*
|
|
24
|
-
* 「最新の生成物」をビューアで区別するために使う。レコードの frontmatter の `date` は
|
|
25
|
-
* **決定日**(過去の議事録から起こした決定は古い日付になる)なので、生成のタイミングは
|
|
26
|
-
* git 側からしか分からない。
|
|
27
|
-
*
|
|
28
|
-
* 判定: 夜間パイプラインのコミット(メッセージが「自動追記」を含む)のうち最新の1回を探し、
|
|
29
|
-
* そのコミットで **追加(A)** されたファイルを返す。同じ夜に複数フェーズが別コミットで入るため、
|
|
30
|
-
* 最新コミットと同じ「日」のパイプラインコミットまでをまとめて1回の実行として扱う。
|
|
31
|
-
*
|
|
32
|
-
* clone が浅い(--depth 1 等)・git が無い環境では**空集合を返す**(ラベルが出ないだけで壊れない)。
|
|
33
|
-
*
|
|
34
|
-
* 🔴 **パスは `-z`(NUL区切り)で取り、NFC に正規化してから集合に入れる。**
|
|
35
|
-
* - `-z` … 既定の git(`core.quotePath=true`)は非ASCIIを含むパスを
|
|
36
|
-
* `"Cortex/Rules/records/\345\261\245…md"` と引用符+8進エスケープで出す。レコードは
|
|
37
|
-
* **日本語のファイル名**なので、そのまま入れると照合側(readdir の生 UTF-8)と
|
|
38
|
-
* 一致せず「最新」ラベルが**1件も出なくなる**(#328 で実際に踏んだ)。
|
|
39
|
-
* - NFC … リポジトリのパスは NFC が正(cortex-engine PR #211
|
|
40
|
-
* https://github.com/classmethod-internal/cortex-engine/pull/211)。#211 以前に
|
|
41
|
-
* NFD で入ったコミットが履歴に残っているので、照合の手前で揃える。
|
|
42
|
-
*/
|
|
43
|
-
function latestGeneratedPaths(cwd) {
|
|
44
|
-
const empty = new Set();
|
|
45
|
-
try {
|
|
46
|
-
// 夜間パイプラインのコミットを新しい順に数件。--grep はコミットメッセージの規約に依存するため、
|
|
47
|
-
// 変わっても「ラベルが出ない」だけで済むように例外は握る。
|
|
48
|
-
const log = (0, node_child_process_1.execFileSync)("git", ["log", "-n", "20", "--grep=自動追記", "--pretty=format:%H %cI", "--", "Cortex/"], { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
49
|
-
if (!log)
|
|
50
|
-
return empty;
|
|
51
|
-
const rows = log.split("\n").map((l) => {
|
|
52
|
-
const [sha, iso] = l.trim().split(/\s+/);
|
|
53
|
-
return { sha, day: (iso || "").slice(0, 10) };
|
|
54
|
-
});
|
|
55
|
-
const latestDay = rows[0].day;
|
|
56
|
-
if (!latestDay)
|
|
57
|
-
return empty;
|
|
58
|
-
const out = new Set();
|
|
59
|
-
for (const r of rows) {
|
|
60
|
-
if (r.day !== latestDay)
|
|
61
|
-
break; // 直近の実行日ぶんだけ
|
|
62
|
-
const files = (0, node_child_process_1.execFileSync)("git", ["show", "-z", "--no-color", "--diff-filter=A", "--name-only", "--pretty=format:", r.sha], { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
|
|
63
|
-
for (const f of files.split("\0")) {
|
|
64
|
-
// 空の `--pretty=format:` では先頭に改行は出ない(git 2.39 で実測)が、format が
|
|
65
|
-
// 非空だと `<ヘッダ>\n<最初のパス>` と改行で区切られる。将来ヘッダを足しても
|
|
66
|
-
// パスが壊れないよう**先頭の改行だけ**落とす(trim だと名前の前後の空白まで削り、
|
|
67
|
-
// その名前のレコードが照合できなくなる)
|
|
68
|
-
const p = f.replace(/^\n+/, "");
|
|
69
|
-
if (p)
|
|
70
|
-
out.add(p.normalize("NFC"));
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
return out;
|
|
74
|
-
}
|
|
75
|
-
catch {
|
|
76
|
-
return empty; // 浅いclone・git無し・メッセージ規約の変更 → ラベルなしで続行
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
20
|
function tryGitRemote(cwd) {
|
|
80
21
|
try {
|
|
81
22
|
return (0, node_child_process_1.execSync)("git config --get remote.origin.url", {
|
|
@@ -148,9 +89,7 @@ function classifyCommit(id) {
|
|
|
148
89
|
// 出る(この関数が防ごうとしている誤帰属の、ちょうど逆向き)。
|
|
149
90
|
// 規約側は「Gold自動追記: …」「週次整理: …」の形なので、接頭辞で足りる。
|
|
150
91
|
//
|
|
151
|
-
// ⚠️ **見るのは件名(%s)だけ。**
|
|
152
|
-
// `--grep` は**メッセージ本文も**対象なので、判定の範囲が揃っていない。
|
|
153
|
-
// 本文まで足すと複数行になって1行1コミットのパースが壊れるため、ここは件名に留める。
|
|
92
|
+
// ⚠️ **見るのは件名(%s)だけ。** 本文まで足すと複数行になって1行1コミットのパースが壊れるため、件名に留める。
|
|
154
93
|
// 件名に規約が無く本文にだけある形で engine が回っても、**規則3(bot 名義)で拾える**
|
|
155
94
|
// ——夜間パイプラインは GitHub Actions の bot 名義でコミットするため。
|
|
156
95
|
const scope = /^([^::]{1,40})[::]/.exec(subject);
|
package/dist/story.js
CHANGED
|
@@ -7,6 +7,9 @@ exports.STORY_DIGEST_EMPTY_LINE = exports.STORY_MAX_MONTHS = exports.STORY_MAX_W
|
|
|
7
7
|
exports.parseStoryLedger = parseStoryLedger;
|
|
8
8
|
exports.partialDaysOf = partialDaysOf;
|
|
9
9
|
exports.onDaysOf = onDaysOf;
|
|
10
|
+
exports.successDaysOf = successDaysOf;
|
|
11
|
+
exports.nightResultOf = nightResultOf;
|
|
12
|
+
exports.splitFrontmatter = splitFrontmatter;
|
|
10
13
|
exports.storyDayMarkdown = storyDayMarkdown;
|
|
11
14
|
exports.isEngineQuietDay = isEngineQuietDay;
|
|
12
15
|
exports.todayJst = todayJst;
|
|
@@ -133,6 +136,32 @@ function onDaysOf(ledger) {
|
|
|
133
136
|
}
|
|
134
137
|
return out;
|
|
135
138
|
}
|
|
139
|
+
function successDaysOf(ledger) {
|
|
140
|
+
const partial = partialDaysOf(ledger);
|
|
141
|
+
return new Set([...onDaysOf(ledger)].filter((d) => !partial.has(d)));
|
|
142
|
+
}
|
|
143
|
+
function nightResultOf(ledger) {
|
|
144
|
+
const on = onDaysOf(ledger);
|
|
145
|
+
if (!on.size)
|
|
146
|
+
return null;
|
|
147
|
+
return { firstOn: [...on].reduce((a, d) => (d < a ? d : a)), ok: successDaysOf(ledger) };
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* 日・週・月のページの先頭の frontmatter(`---` で囲んだ塊)を本文から分ける(純関数)。
|
|
151
|
+
* 日のページは `date` と `relations` を持ちうる(B-story)。画面には出さない。frontmatter が無い・閉じていないページは本文のまま返す。
|
|
152
|
+
* YAML としては読まない(壊れた YAML のページでも本文を落とさない)
|
|
153
|
+
*/
|
|
154
|
+
function splitFrontmatter(content) {
|
|
155
|
+
const text = String(content ?? "").replace(/^\ufeff/, "");
|
|
156
|
+
const m = text.match(/^---[ \t]*\r?\n(?:([\s\S]*?)\r?\n)?---[ \t]*(?:\r?\n|$)/);
|
|
157
|
+
if (!m)
|
|
158
|
+
return { frontmatter: null, body: text };
|
|
159
|
+
// 先頭が水平線の `---` のページを frontmatter と取り違えて本文を落とさない: 中身が空か、最初の空でない行が `key:` の形のときだけ分ける
|
|
160
|
+
const first = (m[1] ?? "").split(/\r?\n/).find((l) => l.trim() !== "");
|
|
161
|
+
if (first !== undefined && !/^[A-Za-z_][\w-]*\s*:/.test(first))
|
|
162
|
+
return { frontmatter: null, body: text };
|
|
163
|
+
return { frontmatter: m[1] ?? "", body: text.slice(m[0].length) };
|
|
164
|
+
}
|
|
136
165
|
/** 曜日つきの見出し(`# 2026-09-24(木)`)の文字。無ければ日付だけ */
|
|
137
166
|
function headingOf(lines, day) {
|
|
138
167
|
for (const l of lines) {
|
|
@@ -175,13 +204,13 @@ function withoutEmptyBoldHeads(lines) {
|
|
|
175
204
|
/**
|
|
176
205
|
* Days 1 枚の本文を、画面に出す Markdown にする(純関数)。
|
|
177
206
|
*
|
|
178
|
-
* - 先頭の `# 日付(曜)` は見出しとして別に使うので本文から除く
|
|
179
|
-
* - 印の行は捨てる。`## 人の追記`
|
|
207
|
+
* - 先頭の frontmatter(`date`・`relations`)は本文から分けて出さない。先頭の `# 日付(曜)` は見出しとして別に使うので本文から除く
|
|
208
|
+
* - 印の行は捨てる。`## 人の追記` の見出しは出さない(古いファイルに節が残っていれば、中身だけ本文の続きに出す)
|
|
180
209
|
* - 人の追記の後ろに足された「後から届いた記録」(人が機械の塊を直した日)は、人の追記より前に寄せる
|
|
181
210
|
* - 機械の語が紛れた行は行ごと伏せる(機械の行だけ)。中身の無い見出しは落とし、空になった日は 1 行の案内にする
|
|
182
211
|
*/
|
|
183
212
|
function storyDayMarkdown(content, day) {
|
|
184
|
-
const lines =
|
|
213
|
+
const lines = splitFrontmatter(content).body.split("\n").map((l) => l.replace(/\r$/, ""));
|
|
185
214
|
const heading = headingOf(lines, day);
|
|
186
215
|
// 先頭の `# ` 見出し(最初の空でない行だけ)を除く
|
|
187
216
|
const first = lines.findIndex((l) => l.trim() !== "");
|
|
@@ -216,8 +245,9 @@ function storyDayMarkdown(content, day) {
|
|
|
216
245
|
// 🔴 人の追記: 落とすのは**印の行だけ**(人が書いた行を画面が黙って消さない・実装レビュー 🟠2)
|
|
217
246
|
const humanKept = human.filter((l) => !STORY_MARKER_RE.test(l.trim()));
|
|
218
247
|
const out = withoutEmptySections(withoutEmptyBoldHeads([...clean(main), ...clean(lateAfter)]));
|
|
248
|
+
// 「人の追記」の節は置かない(仕様 7-5)。古いファイルに節が残っていても、見出しだけ外して中身は出す
|
|
219
249
|
if (humanKept.some((l) => l.trim() !== ""))
|
|
220
|
-
out.push("",
|
|
250
|
+
out.push("", ...humanKept);
|
|
221
251
|
// 空行の続きを 1 行に・前後の空行を落とす
|
|
222
252
|
const md = out.join("\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
223
253
|
// 伏せて空になった日は、見出しだけのカードにしない
|
|
@@ -225,7 +255,7 @@ function storyDayMarkdown(content, day) {
|
|
|
225
255
|
}
|
|
226
256
|
/** 「大きな動きなし」の 1 行だけの日か(人の追記が 1 行でもあれば違う。機械の印・空行は数えない) */
|
|
227
257
|
function isEngineQuietDay(content) {
|
|
228
|
-
const lines =
|
|
258
|
+
const lines = splitFrontmatter(content).body.split("\n").map((l) => l.replace(/\r$/, "").trim());
|
|
229
259
|
const at = lines.findIndex((l) => l === HUMAN_HEADING);
|
|
230
260
|
const main = (at < 0 ? lines : lines.slice(0, at)).filter((l) => l !== "" && !STORY_MARKER_RE.test(l) && !/^#\s/.test(l));
|
|
231
261
|
const human = at < 0 ? [] : lines.slice(at + 1).filter((l) => l !== "" && !STORY_MARKER_RE.test(l));
|
|
@@ -246,19 +276,23 @@ const mdOf = (day) => `${Number(day.slice(5, 7))}/${Number(day.slice(8, 10))}`;
|
|
|
246
276
|
* 動きのない日の行(純関数)。`from`〜`to`(新しい方が `to`)を 1 行にする。1 日なら `2026-09-18(金)`、
|
|
247
277
|
* 続くなら `9/10〜9/18`。文言(「は動きはありませんでした」)は画面が付ける
|
|
248
278
|
*/
|
|
249
|
-
function quietStoryRow(from, to,
|
|
279
|
+
function quietStoryRow(from, to, failed = false) {
|
|
250
280
|
const heading = from === to ? `${to}(${WEEKDAYS[new Date(utcMs(to)).getUTCDay()]})` : `${mdOf(from)}〜${mdOf(to)}`;
|
|
251
|
-
return { date: to, ...(from === to ? {} : { fromDate: from }), heading: heading, bodyHtml: "", quiet: true, ...(
|
|
281
|
+
return { date: to, ...(from === to ? {} : { fromDate: from }), heading: heading, bodyHtml: "", quiet: true, ...(failed ? { failed: true } : {}) };
|
|
252
282
|
}
|
|
253
283
|
/**
|
|
254
284
|
* ページのある日(`pages`)の間と、一番古いページから `today` までの日付の抜けを、
|
|
255
285
|
* 動きのない日の行で埋めて新しい順に返す。連続する `STORY_COLLAPSE_QUIET_DAYS` 日以上は 1 行に畳む。
|
|
256
286
|
* 🔴 ページより新しい日(今日まで)も埋める。`pages` が空なら空(タブを出さない)
|
|
257
287
|
*
|
|
258
|
-
* `
|
|
259
|
-
*
|
|
288
|
+
* `night`=夜の更新の結果(`nightResultOf`)。ページの無い日は、更新が**成功した日**なら「動きはありませんでした」、
|
|
289
|
+
* 成功していない日(帳簿に行が無い・shadow・途中まで。今日は除く)なら「この日の更新に失敗しています」(`failed`・仕様 7-4)。
|
|
290
|
+
* 🔴 今日(`today`)にページが無く、帳簿にも成功の行がないときは、行を出さない(その日の夜の更新の前で、失敗でも動きなしでもない)。
|
|
291
|
+
* 昨日以前は今までどおり。
|
|
292
|
+
* 例外(仕様に書かれていない入力): 帳簿が読めない(`null`)ときは全部今までどおり「動きはありませんでした」。
|
|
293
|
+
* 帳簿で on の一番古い日より前の日は「失敗」と出さない(まだ始めていなかった日)
|
|
260
294
|
*/
|
|
261
|
-
function withQuietDays(pages, today,
|
|
295
|
+
function withQuietDays(pages, today, night = null) {
|
|
262
296
|
if (!pages.length)
|
|
263
297
|
return [];
|
|
264
298
|
const have = new Map(pages.map((p) => [p.date, p]));
|
|
@@ -267,17 +301,17 @@ function withQuietDays(pages, today, lastDay = null) {
|
|
|
267
301
|
const newest = dates.reduce((a, d) => (d > a ? d : a));
|
|
268
302
|
const last = today > newest ? today : newest;
|
|
269
303
|
const out = [];
|
|
270
|
-
let run = []; //
|
|
271
|
-
const
|
|
304
|
+
let run = []; // 新しい日から並ぶ動きのない日(失敗した日とは混ぜない)
|
|
305
|
+
const failedOf = (d) => night !== null && d >= night.firstOn && !night.ok.has(d);
|
|
272
306
|
const flush = () => {
|
|
273
307
|
if (!run.length)
|
|
274
308
|
return;
|
|
275
|
-
const
|
|
309
|
+
const failed = failedOf(run[0]);
|
|
276
310
|
if (run.length >= exports.STORY_COLLAPSE_QUIET_DAYS)
|
|
277
|
-
out.push(quietStoryRow(run[run.length - 1], run[0],
|
|
311
|
+
out.push(quietStoryRow(run[run.length - 1], run[0], failed));
|
|
278
312
|
else
|
|
279
313
|
for (const d of run)
|
|
280
|
-
out.push(quietStoryRow(d, d,
|
|
314
|
+
out.push(quietStoryRow(d, d, failed));
|
|
281
315
|
run = [];
|
|
282
316
|
};
|
|
283
317
|
for (let ms = utcMs(last); ms >= utcMs(oldest); ms -= 86400000) {
|
|
@@ -287,8 +321,12 @@ function withQuietDays(pages, today, lastDay = null) {
|
|
|
287
321
|
flush();
|
|
288
322
|
out.push(page);
|
|
289
323
|
}
|
|
324
|
+
else if (night !== null && d === today && !night.ok.has(d)) {
|
|
325
|
+
// 今日(JST)はその日の夜の更新の前なので、失敗とも「動きはありませんでした」とも言わない(行を出さない)
|
|
326
|
+
flush();
|
|
327
|
+
}
|
|
290
328
|
else {
|
|
291
|
-
if (run.length &&
|
|
329
|
+
if (run.length && failedOf(run[0]) !== failedOf(d))
|
|
292
330
|
flush();
|
|
293
331
|
run.push(d);
|
|
294
332
|
}
|
|
@@ -397,11 +435,8 @@ ledgerDir) {
|
|
|
397
435
|
out.push({ date: f.day, heading: heading, bodyHtml: await renderMarkdown(markdown), ...(editUrl ? { editUrl: editUrl } : {}) });
|
|
398
436
|
}
|
|
399
437
|
// 一番古い「動きのある日」から今日まで、日付の抜けを動きのない日の行で埋める。動きのある日が 1 つも無ければ出さない
|
|
400
|
-
//
|
|
401
|
-
|
|
402
|
-
const partial = partialDaysOf(ledger);
|
|
403
|
-
const lastDay = [...on].filter((d) => !partial.has(d)).reduce((a, d) => (a === null || d > a ? d : a), null);
|
|
404
|
-
return out.length ? withQuietDays(out, today, lastDay) : null;
|
|
438
|
+
// 抜けの日は、帳簿の「成功した日」(行があり・on で・途中まででない日)なら動きなし、そうでなければ更新の失敗として出す
|
|
439
|
+
return out.length ? withQuietDays(out, today, nightResultOf(ledger)) : null;
|
|
405
440
|
}
|
|
406
441
|
// ---------------------------------------------------------------------------
|
|
407
442
|
// 週・月のあらすじ(#901・engine の週・月の書き手が書く `Cortex/Story/Weeks`・`Cortex/Story/Months`)
|
|
@@ -455,7 +490,7 @@ function withoutHiddenBoldGroups(lines) {
|
|
|
455
490
|
* - `**この週の局面**: …` の行を局面(文字)として抜き出し、残りを流れ(Markdown)にする
|
|
456
491
|
*/
|
|
457
492
|
function storyDigestParts(content) {
|
|
458
|
-
const lines =
|
|
493
|
+
const lines = splitFrontmatter(content).body.split("\n").map((l) => l.replace(/\r$/, ""));
|
|
459
494
|
const first = lines.findIndex((l) => l.trim() !== "");
|
|
460
495
|
const body = first >= 0 && /^#\s/.test(lines[first]) ? lines.slice(first + 1) : lines;
|
|
461
496
|
// 🔴 人の追記は、機械の塊の**終わりの印より後ろ**の `## 人の追記` からだけ取る(塊の中に見出しの行が紛れても、
|
package/dist/view-dir.js
CHANGED
|
@@ -32,7 +32,7 @@ exports.viewCountsLine = viewCountsLine;
|
|
|
32
32
|
* ## 名前(engine の宣言表 `publish: "viewer"` の行・V10a の `VIEW_HELD`)
|
|
33
33
|
*
|
|
34
34
|
* - 読む 11 本のうち、記憶側が置くのは 8 本+順位 1 本(`VIEW_NAMES`)
|
|
35
|
-
* - 名寄せの提案(`gold-proposals`・A1 の答えまで)と確認の帳簿(`gold-confirmation
|
|
35
|
+
* - 名寄せの提案(`gold-proposals`・A1 の答えまで)と確認の帳簿(`gold-confirmation`・再導出の帯の読み手 `readRederivationLedgers` が読む)は置かれないので、いつも `.cortex` から読む
|
|
36
36
|
*/
|
|
37
37
|
const node_child_process_1 = require("node:child_process");
|
|
38
38
|
const node_crypto_1 = require("node:crypto");
|
|
@@ -55,7 +55,7 @@ exports.VIEW_NAMES = Object.freeze([
|
|
|
55
55
|
/** 置かれない名前(いつも `.cortex` から読む)と理由 */
|
|
56
56
|
exports.VIEW_HELD = Object.freeze({
|
|
57
57
|
"gold-proposals": "名寄せの提案のカード(A1 の答えまで .cortex)",
|
|
58
|
-
"gold-confirmation": "
|
|
58
|
+
"gold-confirmation": "人が確認した版と機械の書き直し(再導出の帯の材料・readRederivationLedgers)",
|
|
59
59
|
});
|
|
60
60
|
/** 目次の名前(engine `VIEW_INDEX_NAME`) */
|
|
61
61
|
exports.VIEW_INDEX_FILE = "view-index.json";
|
package/package.json
CHANGED