@takagaki/cortex-decisions-viewer 0.12.86 → 0.12.88
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 +126 -57
- package/dist/external-id.js +2 -2
- package/dist/rederivation.js +77 -109
- package/dist/render.js +62 -27
- package/dist/repo.js +1 -70
- package/package.json +1 -1
- package/dist/commit-classify.js +0 -157
package/dist/build.js
CHANGED
|
@@ -48,6 +48,7 @@ exports.readGoldProposals = readGoldProposals;
|
|
|
48
48
|
exports.buildHomeGrounding = buildHomeGrounding;
|
|
49
49
|
exports.collectMeetings = collectMeetings;
|
|
50
50
|
exports.findBacklogDir = findBacklogDir;
|
|
51
|
+
exports.figmaUrlFromRef = figmaUrlFromRef;
|
|
51
52
|
exports.bodyFileSlug = bodyFileSlug;
|
|
52
53
|
exports.bodyStampId = bodyStampId;
|
|
53
54
|
exports.bodyStampComment = bodyStampComment;
|
|
@@ -715,30 +716,35 @@ readBlobs = repo_1.readBlobsAt) {
|
|
|
715
716
|
// 🔴 **「確認済み」は `status` だけで決まる**(#667)。帯も並びも同じ `rstatus` を見る
|
|
716
717
|
// ——ここで別のものを渡すと、画面の札と食い違う(「未確認」の札が付いた記録に帯が出る)
|
|
717
718
|
const statusOf = (r) => r.rstatus;
|
|
718
|
-
// 決定表の行
|
|
719
|
-
//
|
|
719
|
+
// 決定表の行 5〜7 の材料。**帯の候補になったレコードのぶんだけ**今の版を読み、指紋を作る。
|
|
720
|
+
// 帳簿に人が確認した版の指紋が無い候補だけ、`<基準コミット>:<path>` をまとめて 1 回 git から引く。
|
|
720
721
|
// 🔴 **キーが 0 件の区画では git を起こさない**(この関数は区画ごとに呼ばれる)
|
|
721
722
|
const keyOf = new Map();
|
|
722
723
|
// 今の版は**作業ツリーから読む**——画面が描いているのはこのファイルであって HEAD ではない
|
|
723
724
|
const now = new Map();
|
|
725
|
+
const nowSha = new Map();
|
|
724
726
|
for (const r of records) {
|
|
725
727
|
const key = (0, rederivation_1.rederivedContentKey)(pathOf(r), statusOf(r), ctx.ledgers);
|
|
726
728
|
if (!key)
|
|
727
729
|
continue;
|
|
728
730
|
const p = pathOf(r);
|
|
731
|
+
let nowRaw;
|
|
729
732
|
try {
|
|
730
|
-
|
|
733
|
+
nowRaw = await node_fs_1.promises.readFile(path.join(ctx.repoRoot, p), "utf8");
|
|
731
734
|
}
|
|
732
735
|
catch {
|
|
733
|
-
//
|
|
736
|
+
// 読めなければ「読めなかった」側(`rederivedBandOf` が帯を出さずに数える)
|
|
737
|
+
continue;
|
|
734
738
|
}
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
if (
|
|
739
|
+
now.set(p, nowRaw);
|
|
740
|
+
const sha = (0, rederivation_1.contentShaOf)(nowRaw);
|
|
741
|
+
nowSha.set(p, sha);
|
|
742
|
+
const conf = ctx.ledgers.confirmation.get(p);
|
|
743
|
+
// 🔴 **今の版が機械の書き直しの版でなければ git を読まない**(帯は出ないと決まっている)。
|
|
744
|
+
// 人が確認した版の指紋が帳簿にあれば、それで決まるので git を読まない(engine #546)
|
|
745
|
+
if (sha !== conf?.rewritten?.contentSha)
|
|
746
|
+
continue;
|
|
747
|
+
if (conf.contentSha) {
|
|
742
748
|
if (ctx.blobs)
|
|
743
749
|
ctx.blobs.skipped++;
|
|
744
750
|
continue;
|
|
@@ -750,28 +756,18 @@ readBlobs = repo_1.readBlobsAt) {
|
|
|
750
756
|
const bases = keyOf.size ? readBlobs(ctx.repoRoot, [...keyOf.values()]) : new Map();
|
|
751
757
|
const verdict = (p, baseCommit) => {
|
|
752
758
|
const nowRaw = now.get(p);
|
|
753
|
-
if (nowRaw == null) {
|
|
754
|
-
ctx.miss.unreadable.add(p);
|
|
755
|
-
return "unknown";
|
|
756
|
-
}
|
|
757
|
-
// 🔴 **帳簿が中身の指紋を持つならそれを先に使う**(engine #546 が書く。一致した記録は
|
|
758
|
-
// 上で git の key から外してある)。
|
|
759
|
-
// 一致したときだけ「変わっていない」と言い、食い違ったら git の二段目へ落とす——
|
|
760
|
-
// engine が別の綴りで指紋を作っていた場合に、全レコードへ誤報が出るのを避ける
|
|
761
|
-
const conf = ctx.ledgers.confirmation.get(p);
|
|
762
|
-
if (conf && conf.contentSha && conf.contentSha === (0, rederivation_1.contentShaOf)(nowRaw))
|
|
763
|
-
return "same";
|
|
764
759
|
const baseRaw = bases.get(`${baseCommit}:${p}`);
|
|
765
|
-
if (baseRaw == null) {
|
|
760
|
+
if (nowRaw == null || baseRaw == null) {
|
|
766
761
|
// 浅い clone の窓の外・確認の後に改名された・基準コミットにこの path が無い。
|
|
767
|
-
//
|
|
762
|
+
// **帯は出さない**(誤報より無言)が、静かに消える経路なのでログの器に数える
|
|
768
763
|
ctx.miss.unreadable.add(p);
|
|
769
764
|
return "unknown";
|
|
770
765
|
}
|
|
771
766
|
return (0, rederivation_1.sameRederivedContent)(baseRaw, nowRaw) ? "same" : "changed";
|
|
772
767
|
};
|
|
768
|
+
const nowShaOf = (p) => nowSha.get(p) ?? null;
|
|
773
769
|
for (const r of records) {
|
|
774
|
-
const band = (0, rederivation_1.rederivedBandOf)(pathOf(r), statusOf(r), ctx.ledgers,
|
|
770
|
+
const band = (0, rederivation_1.rederivedBandOf)(pathOf(r), statusOf(r), ctx.ledgers, nowShaOf, verdict, ctx.miss);
|
|
775
771
|
// **印が立たないレコードにはキーごと付けない**(空の器を通さない)
|
|
776
772
|
if (band)
|
|
777
773
|
r.rederived = band;
|
|
@@ -1453,16 +1449,28 @@ async function buildTargetUrlMap(repoRoot, repoBaseUrl, branch) {
|
|
|
1453
1449
|
// - 参照ID: `design:xxx:103:1836`
|
|
1454
1450
|
// - [Figmaで開く](https://www.figma.com/design/...)
|
|
1455
1451
|
// 以前は `data.type === "design"` を見ていたので、この解決は**一度も効いていなかった**。
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1452
|
+
//
|
|
1453
|
+
// #727 K10 で、コメントだけを取り込む案件(栓 `design_comments_only`)は inventory を持たず、
|
|
1454
|
+
// `デザイン/指摘/<ファイル>/README.md`(`design:{fileKey}` と「Figmaで開く」)と
|
|
1455
|
+
// 月ごとの `YYYY-MM.md`(スレッドごとの `figma-comment:` と「Figmaでこの指摘を開く」)になる。
|
|
1456
|
+
// 移行期は両方ありうるので**両方歩く**。inventory を先に歩き、同じ ID は先勝ち
|
|
1457
|
+
// (=inventory があれば今までと同じ URL。1バイトも変えない)。
|
|
1458
|
+
// 表に無い `design:` / `figma-comment:` は `figmaUrlFromRef` がキーから組む(解決器の側)。
|
|
1459
|
+
for (const sub of ["inventory", "指摘"]) {
|
|
1460
|
+
for (const file of await walkMd(path.join(repoRoot, designDir, sub))) {
|
|
1461
|
+
try {
|
|
1462
|
+
const body = await node_fs_1.promises.readFile(file, "utf8");
|
|
1463
|
+
const id = body.match(/^- 参照ID: `(design:[^`]+)`\s*$/m);
|
|
1464
|
+
const url = body.match(/^- \[Figmaで開く\]\((https?:[^)]+)\)\s*$/m);
|
|
1465
|
+
if (id && url && !map.has(id[1]))
|
|
1466
|
+
map.set(id[1], url[1]);
|
|
1467
|
+
for (const [cid, curl] of figmaCommentLinks(body))
|
|
1468
|
+
if (!map.has(cid))
|
|
1469
|
+
map.set(cid, curl);
|
|
1470
|
+
}
|
|
1471
|
+
catch {
|
|
1472
|
+
// 読めないファイルはスキップ
|
|
1473
|
+
}
|
|
1466
1474
|
}
|
|
1467
1475
|
}
|
|
1468
1476
|
// minute:{定例名}:{YYYYMMDD} → 議事録ファイルのGitHubリンク
|
|
@@ -1485,6 +1493,70 @@ async function buildTargetUrlMap(repoRoot, repoBaseUrl, branch) {
|
|
|
1485
1493
|
}
|
|
1486
1494
|
return map;
|
|
1487
1495
|
}
|
|
1496
|
+
/**
|
|
1497
|
+
* 指摘のスレッドから `figma-comment:{fileKey}:{commentId}` → 「Figmaでこの指摘を開く」の URL を拾う。
|
|
1498
|
+
*
|
|
1499
|
+
* engine の `render_thread` の形(`### 未解決: …` の見出し・`- 参照ID:`・`- [Figmaでこの指摘を開く](…)`)。
|
|
1500
|
+
* **対は同じスレッドの中でだけ組む。** 次の `### ` 見出しを越えて拾うと、リンクの欠けた
|
|
1501
|
+
* スレッドに隣のスレッドの URL を付けてしまう(押すと別の指摘へ飛ぶ——エラーの出ない壊れ方)。
|
|
1502
|
+
*/
|
|
1503
|
+
function figmaCommentLinks(body) {
|
|
1504
|
+
const out = [];
|
|
1505
|
+
let pending = null;
|
|
1506
|
+
for (const line of body.split("\n")) {
|
|
1507
|
+
if (/^#{1,3}\s/.test(line)) {
|
|
1508
|
+
pending = null;
|
|
1509
|
+
continue;
|
|
1510
|
+
}
|
|
1511
|
+
const id = line.match(/^- 参照ID: `(figma-comment:[^`]+)`\s*$/);
|
|
1512
|
+
if (id) {
|
|
1513
|
+
pending = id[1];
|
|
1514
|
+
continue;
|
|
1515
|
+
}
|
|
1516
|
+
const url = line.match(/^- \[Figmaでこの指摘を開く\]\((https?:[^)]+)\)\s*$/);
|
|
1517
|
+
if (url && pending) {
|
|
1518
|
+
out.push([pending, url[1]]);
|
|
1519
|
+
pending = null;
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
return out;
|
|
1523
|
+
}
|
|
1524
|
+
/** Figma のファイルキー(engine `sync_designs.py` の `REAL_FIGMA_KEY` と同じ形) */
|
|
1525
|
+
const FIGMA_KEY = /^[A-Za-z0-9_-]{8,}$/;
|
|
1526
|
+
/** Figma のノード ID(`103:1836`。URL では `103-1836` と書く) */
|
|
1527
|
+
const FIGMA_NODE = /^\d+:\d+$/;
|
|
1528
|
+
/**
|
|
1529
|
+
* `design:` / `figma-comment:` の参照から、**キーとノードだけで** Figma の URL を組む(#727 K10)。
|
|
1530
|
+
*
|
|
1531
|
+
* コメントだけを取り込む案件には画面ごとの inventory が無いので、Gold の
|
|
1532
|
+
* `design:{fileKey}:{nodeId}` を表から引けない。画面の本体は Figma にあるので、キーとノードが
|
|
1533
|
+
* 揃っていれば URL は決まる(スラグは Figma が読まないので付けない)。
|
|
1534
|
+
* - `design:{key}:{node}` → `https://www.figma.com/design/{key}?node-id={node の : を -}`
|
|
1535
|
+
* - `design:{key}` → `https://www.figma.com/design/{key}`
|
|
1536
|
+
* - `figma-comment:{key}:{id}` → `https://www.figma.com/design/{key}#{id}`
|
|
1537
|
+
* (フラグメントはサーバーに送られないので、外れてもファイルには着く。engine の `comment_link` と同じ倒し方)
|
|
1538
|
+
*
|
|
1539
|
+
* 🔴 **形の合わないものは組まない(null)。** キー・ノードに任意の文字列を通すと、
|
|
1540
|
+
* `design:../../x` や `design:abc:1:2?x=` から**押せそうで 404** の URL ができる。
|
|
1541
|
+
* 壊れたリンクは、無いより悪い。
|
|
1542
|
+
*/
|
|
1543
|
+
function figmaUrlFromRef(ref) {
|
|
1544
|
+
const d = ref.match(/^design:([^:]+)(?::(.+))?$/);
|
|
1545
|
+
if (d) {
|
|
1546
|
+
const [, key, node] = d;
|
|
1547
|
+
if (!FIGMA_KEY.test(key))
|
|
1548
|
+
return null;
|
|
1549
|
+
if (node === undefined)
|
|
1550
|
+
return `https://www.figma.com/design/${key}`;
|
|
1551
|
+
if (!FIGMA_NODE.test(node))
|
|
1552
|
+
return null;
|
|
1553
|
+
return `https://www.figma.com/design/${key}?node-id=${node.replace(":", "-")}`;
|
|
1554
|
+
}
|
|
1555
|
+
const c = ref.match(/^figma-comment:([^:]+):(\d+)$/);
|
|
1556
|
+
if (c && FIGMA_KEY.test(c[1]))
|
|
1557
|
+
return `https://www.figma.com/design/${c[1]}#${c[2]}`;
|
|
1558
|
+
return null;
|
|
1559
|
+
}
|
|
1488
1560
|
/** 画像・添付など、グラフのノードにしないリンクの拡張子 */
|
|
1489
1561
|
const NON_DOC_LINK = /\.(png|jpe?g|gif|svg|webp|ico|zip|mp4|mov|heic)([?#]|$)/i;
|
|
1490
1562
|
/** 本文markdownからhttp(s)リンクを抽出する(重複除去・画像等は除外) */
|
|
@@ -1667,9 +1739,13 @@ function buildGraph(decisions, home, sections, content, targetUrls, repoBaseUrl,
|
|
|
1667
1739
|
return resolved;
|
|
1668
1740
|
let group = "phantom";
|
|
1669
1741
|
let label = target;
|
|
1670
|
-
if (target.startsWith("minute:") ||
|
|
1742
|
+
if (target.startsWith("minute:") ||
|
|
1743
|
+
target.startsWith("material:") ||
|
|
1744
|
+
target.startsWith("design:") ||
|
|
1745
|
+
target.startsWith("figma-comment:")) {
|
|
1671
1746
|
group = "silver";
|
|
1672
|
-
|
|
1747
|
+
// 表に無い design: / figma-comment: はキーから組む(詳細画面の resolve と同じ順)
|
|
1748
|
+
const url = targetUrls.get(target) ?? figmaUrlFromRef(target);
|
|
1673
1749
|
if (url)
|
|
1674
1750
|
return addNode({ id: target, label, group, url });
|
|
1675
1751
|
}
|
|
@@ -2127,8 +2203,8 @@ async function build(opts) {
|
|
|
2127
2203
|
// 同一プロセスで2回ビルドしても前回の値が残らないよう、毎回入れ替える
|
|
2128
2204
|
PROPOSALS_BY_PATH = await readGoldProposals(repoRoot);
|
|
2129
2205
|
PROPOSAL_PATHS_MATCHED = new Set();
|
|
2130
|
-
// 再導出の 3 帳簿(#665 便 12b
|
|
2131
|
-
//
|
|
2206
|
+
// 再導出の 3 帳簿(#665 便 12b)。**帳簿が無い案件では帯も並び替えも作らない**。
|
|
2207
|
+
// 人か機械かの見分けは engine が帳簿(`rewritten`)に書くので、画面は git の素性を読まない(#799)
|
|
2132
2208
|
const ledgers = await (0, rederivation_1.readRederivationLedgers)(repoRoot);
|
|
2133
2209
|
const rederive = ledgers.confirmation.size || ledgers.activity.size
|
|
2134
2210
|
? {
|
|
@@ -2136,10 +2212,9 @@ async function build(opts) {
|
|
|
2136
2212
|
// 🔴 **`repoRoot` で走らせる**(`collectAiChangeHistory` の `process.cwd()` に倣わない)。
|
|
2137
2213
|
// あちらは「リポジトリルートで実行すること」が前提の既存の制約だが、こちらは repoRoot を
|
|
2138
2214
|
// 既に知っている(帳簿をそこから読んでいる)。Decisions ディレクトリから CLI を叩いた
|
|
2139
|
-
//
|
|
2215
|
+
// ときも今の版と基準版が引け、帯が黙って消えない
|
|
2140
2216
|
repoRoot: repoRoot,
|
|
2141
|
-
|
|
2142
|
-
miss: { unresolved: new Set(), unreadable: new Set() },
|
|
2217
|
+
miss: { unreadable: new Set() },
|
|
2143
2218
|
blobs: { read: 0, skipped: 0 },
|
|
2144
2219
|
}
|
|
2145
2220
|
: null;
|
|
@@ -2243,30 +2318,20 @@ async function build(opts) {
|
|
|
2243
2318
|
// **突き合わなかった提案の件数をビルドログに出す。** path が1文字違うだけで
|
|
2244
2319
|
// 画面から静かに消えるので、出さないと「提案が出ない」の原因に辿り着けない。
|
|
2245
2320
|
// 表示からは落とす(どのレコードの話か分からない提案は、出しても行動につながらない)
|
|
2246
|
-
// 🔴 **帯が静かに消える経路を 1
|
|
2247
|
-
//
|
|
2248
|
-
// 画面には出さない(利用者が動ける症状ではない)が、ログに無いと「帯が出ない」の原因を
|
|
2249
|
-
// 「夜間がまだ書いていない」と切り分けられない
|
|
2250
|
-
if (rederive && rederive.miss.unresolved.size) {
|
|
2251
|
-
const shas = [...rederive.miss.unresolved];
|
|
2252
|
-
console.warn(`gold-confirmation.json: 変更のコミット ${shas.length} 件を git の履歴で引けませんでした` +
|
|
2253
|
-
`(浅い clone か履歴の窓の外。人か機械か見分けられないので帯は出しません): ` +
|
|
2254
|
-
shas.slice(0, 3).map((s) => s.slice(0, 8)).join(", ") + (shas.length > 3 ? " ほか" : ""));
|
|
2255
|
-
}
|
|
2256
|
-
// 🔴 **印が静かに消えるもう 1 つの経路を 1 行残す。** 人が確認した版の中身を git から
|
|
2257
|
-
// 引けないと「説明文が変わったか」を言えないので、印は出さない(誤報より無言)。
|
|
2321
|
+
// 🔴 **帯が静かに消える経路を 1 行残す。** 今の版か、人が確認した版の中身を
|
|
2322
|
+
// 引けないと「説明文が変わったか」を言えないので、帯は出さない(誤報より無言)。
|
|
2258
2323
|
// 出さないと「印が出ない」の原因を「中身が変わっていない」と切り分けられない
|
|
2259
2324
|
if (rederive && rederive.miss.unreadable.size) {
|
|
2260
2325
|
const paths = [...rederive.miss.unreadable];
|
|
2261
|
-
console.warn(`gold-confirmation.json:
|
|
2262
|
-
`(浅い clone
|
|
2326
|
+
console.warn(`gold-confirmation.json: 人が確認した版か今の版の中身を ${paths.length} 件引けませんでした` +
|
|
2327
|
+
`(浅い clone か、確認の後に改名・削除された。変わったかを言えないので帯は出しません): ` +
|
|
2263
2328
|
paths.slice(0, 3).join(", ") + (paths.length > 3 ? " ほか" : ""));
|
|
2264
2329
|
}
|
|
2265
2330
|
// 🔴 **指紋(engine #546)が効いているかを 1 行残す。** 一致した記録は git を読まないので、
|
|
2266
2331
|
// 「読んだ件数」が候補の数より減っていれば効いている。警告ではないので console.log
|
|
2267
2332
|
if (rederive?.blobs && rederive.blobs.read + rederive.blobs.skipped > 0) {
|
|
2268
2333
|
console.log(`gold-confirmation.json: 人が確認した版の中身を git から ${rederive.blobs.read} 件読みました` +
|
|
2269
|
-
|
|
2334
|
+
`(中身の指紋で決めた ${rederive.blobs.skipped} 件は読んでいません)`);
|
|
2270
2335
|
}
|
|
2271
2336
|
const unmatched = [...PROPOSALS_BY_PATH.keys()].filter((k) => !PROPOSAL_PATHS_MATCHED.has(k));
|
|
2272
2337
|
if (unmatched.length) {
|
|
@@ -2490,6 +2555,10 @@ function createRefResolver(opts) {
|
|
|
2490
2555
|
const known = targetUrls.get(trimmed);
|
|
2491
2556
|
if (known)
|
|
2492
2557
|
return { label: trimmed, url: known };
|
|
2558
|
+
// inventory の無い案件(#727 K10)の design: / figma-comment: は、キーとノードから組む
|
|
2559
|
+
const figma = figmaUrlFromRef(trimmed);
|
|
2560
|
+
if (figma)
|
|
2561
|
+
return { label: trimmed, url: figma };
|
|
2493
2562
|
// 実在するファイルだけ blob URL にする
|
|
2494
2563
|
if (repoBaseUrl && exists(trimmed)) {
|
|
2495
2564
|
return { label: trimmed, url: `${repoBaseUrl}/blob/${branch}/${encodePath(trimmed)}` };
|
package/dist/external-id.js
CHANGED
|
@@ -106,8 +106,8 @@ function splitTypedId(v) {
|
|
|
106
106
|
* 両方がこれを引く。
|
|
107
107
|
*
|
|
108
108
|
* 🔴 画面の中では `identities` と呼ばない(`linkedIds` / `.linked-ids` / `.li-*`)。
|
|
109
|
-
* `identities`
|
|
110
|
-
|
|
109
|
+
* `identities` という語は、このリポジトリでは `GoldCommitIdentity`
|
|
110
|
+
*(帯のコミットの筆者・#799 で撤去)の意味で使っていた——同じ語を 2 つの意味で使わない。
|
|
111
111
|
*/
|
|
112
112
|
exports.FM_LINKED_IDS = "identities";
|
|
113
113
|
/**
|
package/dist/rederivation.js
CHANGED
|
@@ -24,7 +24,7 @@ exports.humanFollowupOf = humanFollowupOf;
|
|
|
24
24
|
*
|
|
25
25
|
* | 帳簿 | 何を持つか | 書き手 |
|
|
26
26
|
* | --- | --- | --- |
|
|
27
|
-
* | `.cortex/gold-confirmation.json` |
|
|
27
|
+
* | `.cortex/gold-confirmation.json` | レコードごとの「人が確認した版」と、その後の機械の書き直しの最新(`rewritten`・中身の指紋つき・#799) | 夜間(全置換・git から作り直せる) |
|
|
28
28
|
* | `.cortex/gold-rederivation.json` | 書き直しの根拠の件数・shadow の「書くつもりだった差分」 | 夜間(追記・区画ごとの節) |
|
|
29
29
|
* | `.cortex/gold-activity.json` | 「最後に見た日」と触れた日の列(+観測の到達点 `cursor`) | 夜間(追記・剪定つき) |
|
|
30
30
|
*
|
|
@@ -46,7 +46,6 @@ const node_crypto_1 = __importDefault(require("node:crypto"));
|
|
|
46
46
|
const promises_1 = __importDefault(require("node:fs/promises"));
|
|
47
47
|
const node_path_1 = __importDefault(require("node:path"));
|
|
48
48
|
const gray_matter_1 = __importDefault(require("gray-matter"));
|
|
49
|
-
const commit_classify_1 = require("./commit-classify");
|
|
50
49
|
/** 帳簿の版(違ったら空として扱う・engine `LEDGER_VERSION`) */
|
|
51
50
|
exports.LEDGER_VERSION = 1;
|
|
52
51
|
/** 帯を出す区画。**用語とメンバーだけ**(Decisions は積み上げ・Rules は便 13) */
|
|
@@ -147,6 +146,16 @@ async function readRederivationLedgers(repoRoot) {
|
|
|
147
146
|
: [];
|
|
148
147
|
// 中身の指紋は**文字列のときだけ**持つ(engine #546 が書く。古い帳簿の行には無い)
|
|
149
148
|
const contentSha = typeof confirmed.content_sha === "string" ? confirmed.content_sha : "";
|
|
149
|
+
// 機械の書き直し(#799)。**オブジェクトのときだけ**持つ。欄が無い(#799 より前の帳簿)・
|
|
150
|
+
// null(確認の後に機械の書き直しが無い)はどちらもキーごと無い=帯は出ない
|
|
151
|
+
const rw = asRecord(row?.rewritten);
|
|
152
|
+
const rewritten = rw
|
|
153
|
+
? {
|
|
154
|
+
at: typeof rw.at === "string" ? rw.at : "",
|
|
155
|
+
commit: typeof rw.commit === "string" ? rw.commit : "",
|
|
156
|
+
contentSha: typeof rw.content_sha === "string" ? rw.content_sha : "",
|
|
157
|
+
}
|
|
158
|
+
: null;
|
|
150
159
|
out.confirmation.set(p, {
|
|
151
160
|
at: confirmed.at != null ? String(confirmed.at) : "",
|
|
152
161
|
by: confirmed.by != null ? String(confirmed.by) : "",
|
|
@@ -154,6 +163,13 @@ async function readRederivationLedgers(repoRoot) {
|
|
|
154
163
|
commit: commit,
|
|
155
164
|
changedBy: changedBy,
|
|
156
165
|
...(contentSha ? { contentSha: contentSha } : {}),
|
|
166
|
+
...(rewritten ? { rewritten: rewritten } : {}),
|
|
167
|
+
// 🔴 **厳密に true のときだけ**("true" や 1 を削除と読まない)
|
|
168
|
+
...(confirmed.deleted === true ? { deleted: true } : {}),
|
|
169
|
+
// 基準コミットでの path(改名の前の確認)。**空でない文字列で、今の path と違うときだけ**持つ
|
|
170
|
+
...(typeof confirmed.path === "string" && confirmed.path !== "" && confirmed.path !== p
|
|
171
|
+
? { basePath: confirmed.path }
|
|
172
|
+
: {}),
|
|
157
173
|
});
|
|
158
174
|
}
|
|
159
175
|
}
|
|
@@ -350,9 +366,10 @@ function sameRederivedContent(a, b) {
|
|
|
350
366
|
/**
|
|
351
367
|
* 「説明文と本文」の指紋。**帳簿の `confirmed.content_sha` と突き合わせる綴り**。
|
|
352
368
|
*
|
|
353
|
-
* 🔴 比べる相手は engine
|
|
354
|
-
*
|
|
355
|
-
*
|
|
369
|
+
* 🔴 比べる相手は engine が書く 2 つの指紋——`rewritten.content_sha`(機械が書き直した版・#799)と
|
|
370
|
+
* `confirmed.content_sha`(人が確認した版・#546)。帯は「今の指紋 = 書き直した版 ≠ 確認した版」の
|
|
371
|
+
* ときだけ出る(`rederivedBandOf`)。**綴りがずれると「今 = 書き直した版」が当たらなくなり、
|
|
372
|
+
* 帯は出ない側に倒れる**(誤報より無言)。
|
|
356
373
|
*
|
|
357
374
|
* engine の写し: `cortex-engine/scripts/rederive/content-sha.mjs`・一致の番人:
|
|
358
375
|
* `cortex-engine/test/rederive-history.test.mjs` の #546 の節(ここを変えたら engine の `VIEWER_SHA` を計算し直す)
|
|
@@ -362,12 +379,12 @@ function contentShaOf(raw) {
|
|
|
362
379
|
return node_crypto_1.default.createHash("sha256").update(c.description + "\u0000" + c.body, "utf8").digest("hex");
|
|
363
380
|
}
|
|
364
381
|
/**
|
|
365
|
-
*
|
|
366
|
-
*
|
|
382
|
+
* そのレコードが帯の**候補**か(決定表の行 0〜4・中身を読む前の安い行だけ)。候補なら
|
|
383
|
+
* `<基準コミット>:<path>`(人が確認した版を git から引くときの key)、候補でなければ null。
|
|
367
384
|
*
|
|
368
|
-
* 🔴
|
|
369
|
-
*
|
|
370
|
-
*
|
|
385
|
+
* 🔴 **ここで候補を絞ってから今の版を読む**——全レコードぶん作業ツリーを読むと、
|
|
386
|
+
* 用語が 1400 件ある案件で毎晩 1400 件を読むことになる。git に渡るのはさらにその中の
|
|
387
|
+
* 「帳簿に人が確認した版の指紋が無い」記録だけ(`attachRederived`)。
|
|
371
388
|
*/
|
|
372
389
|
function rederivedContentKey(recordPath, status, ledgers) {
|
|
373
390
|
if (!isConfirmedStatus(status))
|
|
@@ -375,132 +392,83 @@ function rederivedContentKey(recordPath, status, ledgers) {
|
|
|
375
392
|
if (!BAND_SECTION_RE.test(recordPath))
|
|
376
393
|
return null;
|
|
377
394
|
const conf = ledgers.confirmation.get(recordPath);
|
|
378
|
-
if (!conf || !conf.commit ||
|
|
395
|
+
if (!conf || !conf.commit || conf.deleted || conf.basePath)
|
|
396
|
+
return null;
|
|
397
|
+
const rw = conf.rewritten;
|
|
398
|
+
if (!rw || !rw.contentSha || !rw.at)
|
|
379
399
|
return null;
|
|
380
400
|
return `${conf.commit}:${recordPath}`;
|
|
381
401
|
}
|
|
382
402
|
/**
|
|
383
|
-
* そのレコードに帯を立てるか(#
|
|
403
|
+
* そのレコードに帯を立てるか(#799 の**決定表**・上から順に評価し、当たった行で終わる)。
|
|
384
404
|
*
|
|
385
405
|
* | 順 | 条件 | 結果 |
|
|
386
406
|
* | --- | --- | --- |
|
|
387
|
-
* |
|
|
388
|
-
* | 1 | path が用語・メンバーのレコードでない |
|
|
389
|
-
* | 2 | `confirmation`
|
|
390
|
-
* | 3 |
|
|
391
|
-
* | 4 | `
|
|
392
|
-
* |
|
|
393
|
-
* |
|
|
394
|
-
* |
|
|
395
|
-
* |
|
|
396
|
-
* |
|
|
397
|
-
*
|
|
398
|
-
*
|
|
399
|
-
|
|
400
|
-
*
|
|
401
|
-
*
|
|
402
|
-
*
|
|
403
|
-
* 後に機械のコミットがその path に 1 件でもあるか」だけで立てていた——**ファイルの中身を
|
|
404
|
-
* 1 バイトも見ていなかった**ので、移行でフロントマターの使わない 1 行(`side: cm`)が
|
|
405
|
-
* 消えただけの回にも、関係者 11 人の画面に「変わりました」が出た。
|
|
407
|
+
* | 0 | その記録の `status` が `draft`(=「AI生成・未確認」の札が出る) | 出さない(#667) |
|
|
408
|
+
* | 1 | path が用語・メンバーのレコードでない | 出さない |
|
|
409
|
+
* | 2 | `confirmation` に行が無い/基準コミットが空 | 出さない |
|
|
410
|
+
* | 3 | **基準コミットがこの path を消した(`confirmed.deleted`)/基準コミットでの path が今と違う(`confirmed.path`・改名の前の確認)** | **出さない** |
|
|
411
|
+
* | 4 | **`rewritten` が無い(古い帳簿)・null・指紋が空・日時が空** | **出さない** |
|
|
412
|
+
* | 5 | 今の版が読めない | 出さない(`miss.unreadable` に数える) |
|
|
413
|
+
* | 6 | **今の版の指紋 ≠ `rewritten.content_sha`**(後から人や機械が直した) | **出さない** |
|
|
414
|
+
* | 7a | 帳簿に人が確認した版の指紋がある: **今の指紋 = その指紋**(元に戻った) | **出さない** |
|
|
415
|
+
* | 7b | 指紋が無い(engine が自信を持てない形・#546 より前): git の基準版と比べて `changed` でない | 出さない |
|
|
416
|
+
* | 8 | 根拠は、根拠の帳簿の行の `commit` が `rewritten.commit` と同じときだけ出す | 出す/出さない |
|
|
417
|
+
*
|
|
418
|
+
* 🔴 **人か機械かを画面が見分けない**(#799)。前は `changed_by` の sha ごとに git の素性
|
|
419
|
+
*(名義・件名・trailer)を引き、engine の分類の**写し**で機械の書き直しを拾っていた。
|
|
420
|
+
* 写しはずれる(App 名義の画面操作を機械と読む日が来る)ので、見分けは engine に 1 か所で
|
|
421
|
+
* 寄せ、画面は「今の版が、engine が機械の書き直しと呼んだ版そのものか」だけを指紋で見る。
|
|
422
|
+
* 後から人や別の機械が直していれば指紋が合わないので、「戻す」が人の編集を消すことはない。
|
|
406
423
|
*
|
|
407
424
|
* @param status その記録の `rstatus`(`String()` 済み)。**2 番目の必須引数**にしてある——
|
|
408
425
|
* 末尾の任意引数にすると、渡し忘れが「行 0 の門が静かに開く」になる(設計レビュー 🔴3)
|
|
409
|
-
* @param
|
|
410
|
-
* 🔴
|
|
411
|
-
*
|
|
426
|
+
* @param nowShaOf 今の版(作業ツリー)の `contentShaOf`。読めなければ null。
|
|
427
|
+
* 🔴 **必須引数**——任意にすると、渡し忘れが「指紋を見ずに出す」に静かに倒れる
|
|
428
|
+
* @param verdict 行 7b の答えを引く関数(`(recordPath, baseCommit) => ContentVerdict`)。
|
|
429
|
+
* 帳簿に人が確認した版の指紋がある記録では**呼ばない**
|
|
412
430
|
* @param miss 立たなかった理由を数える器(呼び出し側で 1 つ作って全レコードで共有する)
|
|
413
431
|
*/
|
|
414
|
-
function rederivedBandOf(recordPath, status, ledgers,
|
|
415
|
-
// 0.
|
|
416
|
-
|
|
417
|
-
if (!isConfirmedStatus(status))
|
|
418
|
-
return null;
|
|
419
|
-
if (!BAND_SECTION_RE.test(recordPath))
|
|
432
|
+
function rederivedBandOf(recordPath, status, ledgers, nowShaOf, verdict, miss) {
|
|
433
|
+
// 0〜4. 中身を読む前の門(候補の絞り込みと同じ 1 つの関数に寄せる。2 つに増やすと一致が崩れる)
|
|
434
|
+
if (!rederivedContentKey(recordPath, status, ledgers))
|
|
420
435
|
return null;
|
|
421
436
|
const conf = ledgers.confirmation.get(recordPath);
|
|
422
|
-
|
|
423
|
-
|
|
437
|
+
const rw = conf.rewritten;
|
|
438
|
+
// 5. 今の版の指紋
|
|
439
|
+
const now = nowShaOf(recordPath);
|
|
440
|
+
if (now == null) {
|
|
441
|
+
if (miss)
|
|
442
|
+
miss.unreadable.add(recordPath);
|
|
424
443
|
return null;
|
|
425
|
-
// 確認日が読めない帳簿では「後か前か」を言えないので立てない(順序の確かめが要)
|
|
426
|
-
const confirmedMs = Date.parse(conf.at);
|
|
427
|
-
if (!Number.isFinite(confirmedMs))
|
|
428
|
-
return null;
|
|
429
|
-
let newest = null;
|
|
430
|
-
/** 確認より後の**人**のコミットの最新(帳簿が前の夜のもので、基準点がまだ動いていない夜) */
|
|
431
|
-
let newestHuman = 0;
|
|
432
|
-
for (const sha of conf.changedBy) {
|
|
433
|
-
// 4a. 基準コミット自身が並んでいる帳簿(作り直し前・手で書いたもの)に引っかからない
|
|
434
|
-
if (sha === conf.commit)
|
|
435
|
-
continue;
|
|
436
|
-
const id = identities.get(sha);
|
|
437
|
-
if (!id) {
|
|
438
|
-
// 4b. 窓の外・浅い clone = 見分けられない(不明)。**画面には出さずログに数える**
|
|
439
|
-
//(設計レビュー 🟡10: 帯が静かに消える経路がこれ)
|
|
440
|
-
if (miss)
|
|
441
|
-
miss.unresolved.add(sha);
|
|
442
|
-
continue;
|
|
443
|
-
}
|
|
444
|
-
// 4e. 🔴 **確認より後であることを git の日付(epoch)で確かめる。** `changed_by` は engine が
|
|
445
|
-
// 「基準点より後」で作るが、帳簿は前の夜のものが残りうる(全置換に失敗した回・人が後から
|
|
446
|
-
// 確認して基準点が動いた回)。ここを外すと**確認より前の変更で帯が立つ**。
|
|
447
|
-
// ⚠️ 「後か」の真の正本は engine が見た git のトポロジ順(`history.mjs:141-145`)で、
|
|
448
|
-
// ここは**安全網**。author 日付は rebase・cherry-pick で前後しうるので、
|
|
449
|
-
// ずれたときは「立てない」側に倒れる(設計レビュー 🟡12)
|
|
450
|
-
const ms = Date.parse(id.at);
|
|
451
|
-
if (!Number.isFinite(ms) || ms <= confirmedMs)
|
|
452
|
-
continue;
|
|
453
|
-
// 4c・4d. 機械の書き直しだけを残す(件名は分類と同じものを渡す)
|
|
454
|
-
const classified = (0, commit_classify_1.classifyGoldCommit)({
|
|
455
|
-
authorName: id.authorName,
|
|
456
|
-
authorEmail: id.authorEmail,
|
|
457
|
-
subject: id.subject,
|
|
458
|
-
body: id.body,
|
|
459
|
-
});
|
|
460
|
-
if (!(0, commit_classify_1.isMachineRewrite)(classified, id.subject)) {
|
|
461
|
-
if (classified.by === "human" && ms > newestHuman)
|
|
462
|
-
newestHuman = ms;
|
|
463
|
-
continue;
|
|
464
|
-
}
|
|
465
|
-
if (!newest || ms > newest.ms)
|
|
466
|
-
newest = { at: id.at, ms: ms };
|
|
467
444
|
}
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
// 置くので、正しい帳簿の `changed_by` に人のコミットは入らない(`history.mjs:141`)。
|
|
472
|
-
// だが前の夜の帳簿が残った朝には「機械の書き直し → その後の人の編集」が並びうる。
|
|
473
|
-
// そこで帯を立てると、**「戻す」がその人の新しい編集ごと基準コミットへ戻す**——この機能で
|
|
474
|
-
// いちばん重い壊れ方なので、既定(立てない側)に倒す。engine が次の夜に基準点を動かす
|
|
475
|
-
if (newestHuman > newest.ms)
|
|
445
|
+
// 6. 🔴 **今の版が、engine が「機械の書き直し」と呼んだ版そのものか。** 違えば、その後に
|
|
446
|
+
// 人か別の機械が直している——そこで帯を出すと「戻す」がその直しごと消す
|
|
447
|
+
if (now !== rw.contentSha)
|
|
476
448
|
return null;
|
|
477
|
-
//
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
449
|
+
// 7. 人が確認した版から中身が変わっているか
|
|
450
|
+
if (conf.contentSha) {
|
|
451
|
+
// 7a. 機械が書き直したが、中身は人が確認した版と同じ(書き戻った・欄だけ触った)
|
|
452
|
+
if (now === conf.contentSha)
|
|
453
|
+
return null;
|
|
454
|
+
}
|
|
455
|
+
else if (verdict(recordPath, conf.commit) !== "changed") {
|
|
456
|
+
// 7b. 指紋が無い行は git の基準版と比べる(`same` も `unknown` も出さない)
|
|
482
457
|
return null;
|
|
483
|
-
|
|
458
|
+
}
|
|
459
|
+
// 8. 根拠の件数は「この書き直しのコミットの記録」から出す(別の夜の数字を並べない)
|
|
484
460
|
const red = ledgers.rederivation.get(recordPath);
|
|
485
|
-
const evidence = red && red.evidence != null && red.commit &&
|
|
486
|
-
? red.evidence
|
|
487
|
-
: null;
|
|
488
|
-
// 7. 基準コミットが削除のコミットか(窓の外なら分からないので false 側=リンクは出す。
|
|
489
|
-
// プロンプト側に「取れなかったときの逃げ道」を書いてある)
|
|
490
|
-
const base = identities.get(conf.commit);
|
|
491
|
-
const baseDeleted = base ? commit_classify_1.DELETE_SUBJECT_RE.test(base.subject) : false;
|
|
461
|
+
const evidence = red && red.evidence != null && red.commit && rw.commit && red.commit === rw.commit ? red.evidence : null;
|
|
492
462
|
return {
|
|
493
463
|
confirmedAt: conf.at,
|
|
494
464
|
// 🔴 **券の無い確認では名前を器に入れない**(設計レビュー 🟡4)。画面が出さないだけだと、
|
|
495
|
-
// 名前は `site-data` の JSON
|
|
496
|
-
// の `Operated-By`「値そのものを持ち回さない」と同じ流儀)。しかも engine の `by` は
|
|
465
|
+
// 名前は `site-data` の JSON として全案件のページに配られ続ける。しかも engine の `by` は
|
|
497
466
|
// `confirmer || authorName` の合成で、**券が無ければ「ただ触った人」でしかない**
|
|
498
467
|
confirmedBy: conf.verified ? conf.by : "",
|
|
499
468
|
confirmedVerified: conf.verified,
|
|
500
469
|
baseCommit: conf.commit,
|
|
501
|
-
changedAt:
|
|
470
|
+
changedAt: rw.at,
|
|
502
471
|
...(evidence != null ? { evidence: evidence } : {}),
|
|
503
|
-
...(baseDeleted ? { baseDeleted: true } : {}),
|
|
504
472
|
};
|
|
505
473
|
}
|
|
506
474
|
/**
|
package/dist/render.js
CHANGED
|
@@ -4237,12 +4237,14 @@ function buildTutorialData(site, repoSlug) {
|
|
|
4237
4237
|
prompt: TUT_BACKLOG_PROMPT,
|
|
4238
4238
|
});
|
|
4239
4239
|
}
|
|
4240
|
-
//
|
|
4240
|
+
// デザインの指摘を読む体験(デザインを同期している案件のみ出す)。
|
|
4241
|
+
// #727 K10 で取り込むのは Figma のコメント(デザイン/指摘/)だけになり、画面の本体は
|
|
4242
|
+
// AI が Figma の MCP で開く。移行期は inventory が残る案件もあるので、文言はどちらでも嘘にしない
|
|
4241
4243
|
const usesDesign = capOn("デザイン") ?? internals.some((s) => s.kind === "デザイン" && s.enabled !== false);
|
|
4242
4244
|
if (usesDesign) {
|
|
4243
4245
|
specs.push({
|
|
4244
4246
|
title: "デザインを読んでみよう",
|
|
4245
|
-
desc: "
|
|
4247
|
+
desc: "Figmaの指摘から1画面を選んで理解する",
|
|
4246
4248
|
prompt: TUT_DESIGN_PROMPT,
|
|
4247
4249
|
});
|
|
4248
4250
|
}
|
|
@@ -4310,10 +4312,11 @@ const TUT_BACKLOG_PROMPT = [
|
|
|
4310
4312
|
"今回はコメントの追加だけです。課題の削除・クローズ・属性変更はしないでください。",
|
|
4311
4313
|
].join("\n");
|
|
4312
4314
|
const TUT_DESIGN_PROMPT = [
|
|
4313
|
-
"
|
|
4314
|
-
"
|
|
4315
|
-
"
|
|
4316
|
-
"
|
|
4315
|
+
"デザイン/指摘/ に取り込んだ Figma のコメント(Figmaファイルごとのディレクトリに月ごとの md)から指摘を1つ選び、それがどの画面への指摘なのかを説明してください。各指摘には安定ID(figma-comment:{fileKey}:{commentId})と、その画面をFigmaで開くリンクがあります。",
|
|
4316
|
+
"画面そのものは取り込んでいません。Figma の MCP で開いて読んでください(プロジェクトによっては デザイン/inventory/ が残っているので、あれば手がかりに使ってかまいません)。Figma の MCP が使えないときは、そう伝えてリンクを示すところで止めてください。",
|
|
4317
|
+
"どの指摘・画面が見たいかは、まずわたしに質問してください(希望が無ければ未解決の指摘をあなたが選んでかまいません)。",
|
|
4318
|
+
"画面の役割・主な要素と、指摘が何を求めているかを説明し、関連する決定・用語・課題があれば安定IDをたどって補足し、最後にFigmaで実物を開くリンクを提示してください。",
|
|
4319
|
+
"読むだけの練習です。デザイン/ の下はFigmaからの同期で上書きされる生成物なので手で編集せず、Figma にコメントも書き込まないでください。",
|
|
4317
4320
|
].join("\n");
|
|
4318
4321
|
const TUT_SLACK_PROMPT = [
|
|
4319
4322
|
"Cortex のチュートリアルを完了したことを、このプロジェクトの Slack チャンネルに投稿してください。最後のステップです。",
|
|
@@ -5303,6 +5306,7 @@ a.ds-tile:hover { text-decoration: none; }
|
|
|
5303
5306
|
/* 案件カレンダーの行(#428)。**カレンダー ID は空白の無い長い連なり**で、
|
|
5304
5307
|
失敗の理由(detail)も長さが読めない。ここを外すと会議のモーダルが横に溢れる */
|
|
5305
5308
|
.mt-cal-banner, .mt-cal-note, .mt-cal-warn, .mt-cal-primary, .mt-cal-sub, .mt-cal-id .rt-copy-text,
|
|
5309
|
+
.mt-cal-head, .mt-cal-case,
|
|
5306
5310
|
/* 会議カードの事実の1行(#438)。どれも長い1文なので、折り返さないと横に溢れる */
|
|
5307
5311
|
.mt-scope, .mt-cal-lag, .mt-cal-off {
|
|
5308
5312
|
min-width: 0; white-space: normal; overflow-wrap: anywhere;
|
|
@@ -5553,6 +5557,11 @@ a.ds-tile:hover { text-decoration: none; }
|
|
|
5553
5557
|
.mt-cal-sub { display: block; margin: 10px 0 0; font-size: 12px; color: var(--muted); text-decoration: underline; }
|
|
5554
5558
|
/* 事実の1行(色・作成中・連絡先)。操作ではないので弱く出す */
|
|
5555
5559
|
.mt-cal-note { color: var(--muted); margin: 6px 0 0; }
|
|
5560
|
+
/* 作成済みの欄の見出しと、2 通りの手順の小見出し(#764・オーナー承認済みの文面)。
|
|
5561
|
+
**帯にしない**——症状ではなく手順の区切り。太字だけで分け、2 つ目の前は少し空ける */
|
|
5562
|
+
.mt-cal-head { color: var(--text); font-weight: 700; margin: 0; }
|
|
5563
|
+
.mt-cal-case { color: var(--text); font-weight: 700; margin: 12px 0 0; }
|
|
5564
|
+
.mt-cal-case-note { font-weight: 400; color: var(--muted); }
|
|
5556
5565
|
/* 作成済みだが共有の更新が終わっていない、の1行。**帯にしない**(カレンダーは使える) */
|
|
5557
5566
|
.mt-cal-warn { color: #92400e; margin: 6px 0 0; }
|
|
5558
5567
|
.mt-cal-act { margin-top: 8px; }
|
|
@@ -9734,10 +9743,12 @@ ${calendarAddUrl.toString()}
|
|
|
9734
9743
|
wrap.appendChild(meetingCalendarSection(s));
|
|
9735
9744
|
// 🔴 **カレンダーの箱(.mt-cal)の外に置く。** あそこは liveList で、差し替えのたびに
|
|
9736
9745
|
// 中身を作り直す——中に入れると、状況が届いた瞬間にこの1行だけが消える。
|
|
9737
|
-
//
|
|
9746
|
+
// 直前に作った会議が入らない相談は実際に来るので、待ちがあることを先に書いておく。
|
|
9747
|
+
// 🔴 **文面はオーナー承認済み(#764・2026-09-25)。** 2 通りの手順のどちらにも
|
|
9748
|
+
// 効く「取り込み完了の見分け方」を兼ねる
|
|
9738
9749
|
wrap.appendChild(el("p", {
|
|
9739
9750
|
class: "mt-cal-lag",
|
|
9740
|
-
text: "
|
|
9751
|
+
text: "どちらの方法でも、数分後に予定のゲストに「cortex-notetaker」が入れば取り込み完了です。直前に作った会議は間に合わないことがあります。",
|
|
9741
9752
|
}));
|
|
9742
9753
|
} else {
|
|
9743
9754
|
// 帯は症状・行は一手。ここでの一手は「担当者に聞く」しかない——画面から
|
|
@@ -9827,9 +9838,17 @@ ${calendarAddUrl.toString()}
|
|
|
9827
9838
|
}
|
|
9828
9839
|
|
|
9829
9840
|
if (cal.phase === "ready") {
|
|
9841
|
+
// 🔴 **文面はオーナー承認済み(#764・2026-09-25)。一字一句このまま。**
|
|
9842
|
+
// 見出しの下を「あなたが会議を作るとき」「ほかの人が作った会議のとき」の 2 つに分け、
|
|
9843
|
+
// 住所とコピーは**後者の中の 1 か所だけ**に置く(自分で作る人は住所を使わない。
|
|
9844
|
+
// 2 か所に出すと、どちらの手順で使うものか読めなくなる)。
|
|
9845
|
+
// 「〇〇(このプロジェクト)」には案件の表示名を入れる(題が無い案件は「このプロジェクト」)
|
|
9846
|
+
var projectName = String(SITE.title || "").trim() || "このプロジェクト";
|
|
9847
|
+
box.appendChild(el("p", { class: "mt-cal-head", text: "🗓 会議を Cortex に取り込むには" }));
|
|
9848
|
+
box.appendChild(el("p", { class: "mt-cal-case", text: "あなたが会議を作るとき" }));
|
|
9830
9849
|
// 🔴 **説明の1行は、その操作の直前に置く(#452)。** 押す前に読むものなので、
|
|
9831
9850
|
// 操作の後ろに置くと「押してから理由を知る」順になる
|
|
9832
|
-
box.appendChild(el("p", { class: "mt-cal-note", text: "
|
|
9851
|
+
box.appendChild(el("p", { class: "mt-cal-note", text: "1. 予定を作るときに、カレンダーの欄で「" + projectName + "」を選びます。" }));
|
|
9833
9852
|
// 手順の画像。**上の1文だけで手順が完結する**(読み上げ・画像が出ない環境)ので、
|
|
9834
9853
|
// alt にも同じ内容を1文で書く。寸法は属性で明示——書かないと、モーダルを開いた
|
|
9835
9854
|
// 瞬間に画像の高さのぶん下の操作がずれる(Google Chat の手順と同じ理由)。
|
|
@@ -9843,17 +9862,39 @@ ${calendarAddUrl.toString()}
|
|
|
9843
9862
|
// 見た目は「この案件のカレンダーを作る」= .ds-toggle-btn を**そのまま借りる**
|
|
9844
9863
|
// ——色・枠・丸みを写すと値が2枚になり、片方だけ直る。.mt-cal-primary は
|
|
9845
9864
|
// リンク固有の分(inline-block・下線を外す)だけを足す
|
|
9846
|
-
box.appendChild(el("p", { class: "mt-cal-note", text: "
|
|
9865
|
+
box.appendChild(el("p", { class: "mt-cal-note", text: "2. 選択肢に出てこないときは、最初に 1 回だけ[Google カレンダーに追加 →]を押します。" }));
|
|
9847
9866
|
box.appendChild(el("a", {
|
|
9848
9867
|
class: "ds-toggle-btn mt-cal-primary", href: calendarAddUrl(cal.calendarId), target: "_blank", rel: "noopener",
|
|
9849
9868
|
text: "Google カレンダーに追加 →",
|
|
9850
9869
|
}));
|
|
9851
|
-
// **見分けの付くクラスを付ける。** 同じカードに copyRow が2つ(bot のアカウントと
|
|
9852
|
-
// カレンダー ID)並ぶので、素の .rt-copy-text ではどちらか分からない
|
|
9853
|
-
box.appendChild(el("div", { class: "mt-cal-id" }, [copyRow(cal.calendarId)]));
|
|
9854
9870
|
// 色は**見る人ごとの設定**で、仕組みでは揃えられない(#418 の調査4)。
|
|
9855
|
-
// 質問にはせず、事実として1行だけ置く(オーナー決定 A-5:
|
|
9871
|
+
// 質問にはせず、事実として1行だけ置く(オーナー決定 A-5: 画面に計器は出さない)。
|
|
9872
|
+
// 🔴 **追加したカレンダーの話なので主ボタンの直後(#764)。** 住所の近くに
|
|
9873
|
+
// 置くと、住所の説明のように読める。承認済みの文面の外の 1 行(分類B・決定ログ)
|
|
9856
9874
|
box.appendChild(el("p", { class: "mt-cal-note", text: "色はご自身のカレンダーで変えられます。" }));
|
|
9875
|
+
// 🔴 **主ボタンと格を分ける(#452)。** 同じ大きさ・同じ色で2本並ぶと、
|
|
9876
|
+
// 必ず押すもの(Google カレンダーに追加)がどれか分からない。予定を作るのは
|
|
9877
|
+
// たまにしかしないので、小さく薄い文字リンクで出す。
|
|
9878
|
+
// **「あなたが会議を作るとき」の最後に置く(#764)。** 予定を作る側の近道なので、
|
|
9879
|
+
// 住所の下に置くと「ほかの人が作った会議」の話に読める
|
|
9880
|
+
box.appendChild(el("a", {
|
|
9881
|
+
class: "mt-cal-sub", href: calendarTemplateUrl(cal.calendarId), target: "_blank", rel: "noopener",
|
|
9882
|
+
text: "このプロジェクトのミーティングを作成 ↗",
|
|
9883
|
+
}));
|
|
9884
|
+
|
|
9885
|
+
// 主催者が別の人(社外・ポータルに居ない人)の会議でも、招待された人が自分のカレンダーで
|
|
9886
|
+
// 予定を編集して住所をゲストに足せば取り込まれる(2026-09-25 オーナー実測)
|
|
9887
|
+
box.appendChild(el("p", { class: "mt-cal-case" }, [
|
|
9888
|
+
document.createTextNode("ほかの人が作った会議のとき"),
|
|
9889
|
+
el("span", { class: "mt-cal-case-note", text: "(社外の方やポータルに入っていない方が主催の会議など)" }),
|
|
9890
|
+
]));
|
|
9891
|
+
box.appendChild(el("p", { class: "mt-cal-note", text: "1. 招待された予定を開いて編集し、下の住所を「ゲストを追加」に貼って保存します。" }));
|
|
9892
|
+
box.appendChild(el("p", { class: "mt-cal-note", text: "2. 定例の会議は「すべての予定」を選びます。" }));
|
|
9893
|
+
// **見分けの付くクラスを付ける。** 同じカードに copyRow が2つ(bot のアカウントと
|
|
9894
|
+
// カレンダー ID)並ぶので、素の .rt-copy-text ではどちらか分からない。
|
|
9895
|
+
// 🔴 **住所のコピーはカード全体でここ 1 か所だけ(#764)**
|
|
9896
|
+
box.appendChild(el("div", { class: "mt-cal-id" }, [copyRow(cal.calendarId)]));
|
|
9897
|
+
box.appendChild(el("p", { class: "mt-cal-note", text: "※ ゲスト欄では名前ではなく住所のまま表示されることがありますが、問題ありません。" }));
|
|
9857
9898
|
// 🔴 **「共有を更新」のボタンと説明はここに在った(#452)。#544 で外した。**
|
|
9858
9899
|
// ポータル側が**メンバーの増減のたびに**共有を直させるようになったので、
|
|
9859
9900
|
// 押す理由が無くなった(取りこぼしは毎時の便が最長 1 時間で埋める)。
|
|
@@ -9868,13 +9909,6 @@ ${calendarAddUrl.toString()}
|
|
|
9868
9909
|
if (cal.note) {
|
|
9869
9910
|
box.appendChild(el("p", { class: "mt-cal-warn", text: "共有の更新が終わっていません(" + cal.note + ")。" }));
|
|
9870
9911
|
}
|
|
9871
|
-
// 🔴 **主ボタンと格を分ける(#452)。** 同じ大きさ・同じ色で2本並ぶと、
|
|
9872
|
-
// 必ず押すもの(Google カレンダーに追加)がどれか分からない。予定を作るのは
|
|
9873
|
-
// たまにしかしないので、一番下に小さく薄い文字リンクで出す
|
|
9874
|
-
box.appendChild(el("a", {
|
|
9875
|
-
class: "mt-cal-sub", href: calendarTemplateUrl(cal.calendarId), target: "_blank", rel: "noopener",
|
|
9876
|
-
text: "このプロジェクトのミーティングを作成 ↗",
|
|
9877
|
-
}));
|
|
9878
9912
|
return;
|
|
9879
9913
|
}
|
|
9880
9914
|
|
|
@@ -12288,7 +12322,7 @@ ${calendarAddUrl.toString()}
|
|
|
12288
12322
|
"会議": { icon: "🎙", desc: "文字起こしから議事録を生成" },
|
|
12289
12323
|
"共有資料": { icon: "📁", desc: "資料をMarkdown化して取り込み" },
|
|
12290
12324
|
"チャット": { icon: "💬", desc: "チャンネルの会話をGoldの材料に" },
|
|
12291
|
-
"デザイン": { icon: "🎨", desc: "
|
|
12325
|
+
"デザイン": { icon: "🎨", desc: "Figmaのコメント(指摘)の取り込み" },
|
|
12292
12326
|
"開発": { icon: "🐙", desc: "IssuesをGoldの材料に" },
|
|
12293
12327
|
"ハーネス連携": { icon: "🧰", desc: "スキルカタログを開く(Claude Code で使う/定期実行にする)" },
|
|
12294
12328
|
};
|
|
@@ -14996,15 +15030,16 @@ ${calendarAddUrl.toString()}
|
|
|
14996
15030
|
var lines = [
|
|
14997
15031
|
"「" + (f.name || f.key) + "」をデザイン同期の対象から外します。",
|
|
14998
15032
|
"",
|
|
14999
|
-
"
|
|
15033
|
+
"次回の同期から、このファイルのコメントは取り込まれなくなります。",
|
|
15000
15034
|
];
|
|
15001
15035
|
// **最後の1件は結果が違う。** 同期は空の設定だと何もせずに終わるので、
|
|
15002
|
-
//
|
|
15036
|
+
// 取り込み済みの分(デザイン/指摘/、移行前の案件は デザイン/inventory/)が
|
|
15037
|
+
// 消えずに残る(sync_designs.py は
|
|
15003
15038
|
// 全再生成の手前で早期returnする)。ここで伝えないと放置される。
|
|
15004
15039
|
if (figsLeft === 1) {
|
|
15005
15040
|
lines.push(
|
|
15006
15041
|
"",
|
|
15007
|
-
"これが最後の1
|
|
15042
|
+
"これが最後の1件です。外すとデザイン同期は何もしなくなりますが、すでに取り込んだ分(デザイン/ の下)はリポジトリに残ります(不要なら手で削除してください)。",
|
|
15008
15043
|
);
|
|
15009
15044
|
}
|
|
15010
15045
|
lines.push("", "外しますか?");
|
|
@@ -18269,7 +18304,7 @@ ${calendarAddUrl.toString()}
|
|
|
18269
18304
|
/**
|
|
18270
18305
|
* 2 つの日時が同じ瞬間か。**印の行を決めるのに使う。**
|
|
18271
18306
|
*
|
|
18272
|
-
* 🔴 突き合わせるのは rec.rederived.changedAt
|
|
18307
|
+
* 🔴 突き合わせるのは rec.rederived.changedAt(帳簿の rewritten.at=機械の書き直しの日時・#799)と
|
|
18273
18308
|
* 変更履歴の at。**器にはコミットの sha が 1 つも入っていない**(evidence は
|
|
18274
18309
|
* 根拠の**件数**であって sha ではない)ので、sha では突き合わせられない。
|
|
18275
18310
|
* 綴りが違っても同じ瞬間なら当てる(どちらも git の author 日付だが、器の出どころが別)
|
|
@@ -18324,7 +18359,7 @@ ${calendarAddUrl.toString()}
|
|
|
18324
18359
|
// 🔴 **この時点では、どのプロジェクトにもこの欄はまだ無い。** 無ければ節ごと出さない
|
|
18325
18360
|
// (今までと同じに見えること)のが、この節のいちばん大事な性質。
|
|
18326
18361
|
// 🔴 **画面の中では linkedIds / .linked-ids / .li-* と呼ぶ。** フロントマターの欄名の綴りは
|
|
18327
|
-
// GoldCommitIdentity
|
|
18362
|
+
// GoldCommitIdentity(帯のコミットの筆者・#799 で撤去)と同じ語だったので、画面の名前には持ち込まない。
|
|
18328
18363
|
// 欄名を綴るのは FM_LINKED_IDS 1 本だけ(書く側も同じ定数を引く。正本は src/external-id.ts)
|
|
18329
18364
|
var FM_LINKED_IDS = ${JSON.stringify(external_id_1.FM_LINKED_IDS)};
|
|
18330
18365
|
// 道具の読める名前。🔴 **語彙の外の種類は、その綴りをそのまま出す**
|
package/dist/repo.js
CHANGED
|
@@ -7,7 +7,6 @@ exports.normalizeToHttps = normalizeToHttps;
|
|
|
7
7
|
exports.classifyCommit = classifyCommit;
|
|
8
8
|
exports.isFrontmatterKeyRename = isFrontmatterKeyRename;
|
|
9
9
|
exports.collectAiChangeHistory = collectAiChangeHistory;
|
|
10
|
-
exports.collectGoldCommitIdentities = collectGoldCommitIdentities;
|
|
11
10
|
exports.readBlobsAt = readBlobsAt;
|
|
12
11
|
exports.lastSubstantiveUpdate = lastSubstantiveUpdate;
|
|
13
12
|
const node_child_process_1 = require("node:child_process");
|
|
@@ -761,81 +760,13 @@ const LAST_UPDATE_RENAME_OPT = "--find-renames=50%";
|
|
|
761
760
|
* ここでは使わない——差分の取得にもう1回 git を呼ぶ割に、Home.md ではマイグレーションの
|
|
762
761
|
* 件名規約で落ちる形しか観測されていない(適用しないと決めた・設計レビュー承認済み)。
|
|
763
762
|
*/
|
|
764
|
-
/* ===========================================================================
|
|
765
|
-
* レコードを触ったコミットの素性(帯「人が確認した版から変わりました」の材料・#665)
|
|
766
|
-
* ===========================================================================
|
|
767
|
-
* `.cortex/gold-confirmation.json` の `changed_by` は **sha だけ**を持つ。人が確認した
|
|
768
|
-
* 版から「**機械が**書き直したか」を見分けるには、その sha の author・件名・本文
|
|
769
|
-
*(`Operated-By:` trailer)が要る——帳簿には無いので git から取る。
|
|
770
|
-
*
|
|
771
|
-
* 🔴 **レコード数に依存しない 1 回の `git log`。** 「ビルド1回につき git は2回」の規律
|
|
772
|
-
*(`collectAiChangeHistory`)に**3 回目**を足す形になるが、per-path で呼ばない点は保つ。
|
|
773
|
-
* sha を並べて `git log --no-walk <sha…>` にしない——sha の数(実データで 54・多い案件では
|
|
774
|
-
* 数百)でコマンドラインが伸び、ARG_MAX と「履歴に無い sha で log 全体が落ちる」に当たる。
|
|
775
|
-
*/
|
|
776
|
-
/**
|
|
777
|
-
* 素性を読むコミット数の上限。
|
|
778
|
-
*
|
|
779
|
-
* ビューアのビルドは **fetch-depth 2000** で checkout する(engine `build-viewer.yml`)ので、
|
|
780
|
-
* それ以上遡っても届かない。窓の外・浅い clone の sha は**見分けられない=「不明」**に倒れ、
|
|
781
|
-
* 帯は立たない(既定は不明・#432 裁定 5-7)。差分や本文を読まない軽い pass なので、
|
|
782
|
-
* 履歴の窓とそろえて広く取る。
|
|
783
|
-
*/
|
|
784
|
-
const GOLD_IDENTITY_MAX_COMMITS = 2000;
|
|
785
|
-
/** 本文(`%B`)の前に置く印。件名の区切り(US)とは別の文字にする(件名にも本文にも現れない) */
|
|
786
|
-
const RSEP = "";
|
|
787
|
-
/**
|
|
788
|
-
* レコードを触ったコミットの素性を sha で引ける表にする(新しい順に上限まで)。
|
|
789
|
-
*
|
|
790
|
-
* git が無い・リポジトリでない・浅い clone で届かない sha は**表に載らない**
|
|
791
|
-
*(呼び出し側は「載っていない sha は不明」として扱うこと)。
|
|
792
|
-
*/
|
|
793
|
-
function collectGoldCommitIdentities(cwd) {
|
|
794
|
-
const out = gitLog(cwd, [
|
|
795
|
-
"log",
|
|
796
|
-
`-n${GOLD_IDENTITY_MAX_COMMITS}`,
|
|
797
|
-
"--no-color",
|
|
798
|
-
// 本文まで読むので、コミットの境目(NUL)と本文の始まり(RSEP)を別の印で固定する。
|
|
799
|
-
// 件名に US が混ざっても壊れないよう、下で 5 つ目以降を件名として繋ぎ直す
|
|
800
|
-
`--format=%x00%H%x1f%aI%x1f%an%x1f%ae%x1f%s%x1e%B`,
|
|
801
|
-
"--",
|
|
802
|
-
RECORD_PATHSPEC,
|
|
803
|
-
], "レコードを触ったコミットの素性");
|
|
804
|
-
const map = new Map();
|
|
805
|
-
// 🔴 **行で切らない(NUL で切る)。** 本文は複数行なので、1 行 1 コミットの読み方は使えない
|
|
806
|
-
for (const chunk of out.split(NUL)) {
|
|
807
|
-
if (!chunk)
|
|
808
|
-
continue;
|
|
809
|
-
const at = chunk.indexOf(RSEP);
|
|
810
|
-
const head = at === -1 ? chunk : chunk.slice(0, at);
|
|
811
|
-
// 本文の末尾の改行は git が付けるもので、trailer の判定(行頭 ^)には影響しない
|
|
812
|
-
const body = at === -1 ? "" : chunk.slice(at + RSEP.length);
|
|
813
|
-
const parts = head.split(US);
|
|
814
|
-
const sha = parts[0] || "";
|
|
815
|
-
if (!sha)
|
|
816
|
-
continue;
|
|
817
|
-
// 同じ sha が 2 度出ることは無いが、出ても新しい側(先に出たもの)を残す
|
|
818
|
-
if (map.has(sha))
|
|
819
|
-
continue;
|
|
820
|
-
map.set(sha, {
|
|
821
|
-
sha: sha,
|
|
822
|
-
at: parts[1] || "",
|
|
823
|
-
authorName: parts[2] || "",
|
|
824
|
-
authorEmail: parts[3] || "",
|
|
825
|
-
subject: parts.slice(4).join(US),
|
|
826
|
-
body: body,
|
|
827
|
-
});
|
|
828
|
-
}
|
|
829
|
-
return map;
|
|
830
|
-
}
|
|
831
763
|
/**
|
|
832
764
|
* 指定のコミットのファイルの中身を**まとめて 1 回**読む(`git cat-file --batch`)。
|
|
833
765
|
*
|
|
834
766
|
* 🔴 **レコードごとにプロセスを起こさない。** 帯(印)の判定は「人が確認した版と今の版で
|
|
835
767
|
* 説明文・本文が変わったか」を見るので、確認の基準コミットのレコードを読む必要がある。
|
|
836
768
|
* ここで `git show` をレコードごとに叩くと、用語が 1400 件ある案件でビルドが 1400 回
|
|
837
|
-
* git
|
|
838
|
-
* 依存しない」の規律を保つため、key を stdin に流し込む `--batch` を 1 回だけ使う。
|
|
769
|
+
* git を起こす——「git の呼び出しはレコード数に依存しない」の規律を保つため、key を stdin に流し込む `--batch` を 1 回だけ使う。
|
|
839
770
|
*
|
|
840
771
|
* @param keys `<commit>:<path>` の列(重複は畳む)。**空なら git を起こさない**。
|
|
841
772
|
* 🔴 **改行を含む key は git に渡す前に落とす**(`--batch` の問い合わせは 1 行 1 件なので、
|
package/package.json
CHANGED
package/dist/commit-classify.js
DELETED
|
@@ -1,157 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* コミットを **人/機械/不明** の 3 値に分ける(#665 便 12b・#432 裁定 5-4 / 5-7)。
|
|
4
|
-
*
|
|
5
|
-
* ## 🔴 これは写しで、正本は engine にある
|
|
6
|
-
*
|
|
7
|
-
* 正本: cortex-engine `scripts/observe/commit-classify.mjs`。表(入力 → 3 値):
|
|
8
|
-
* cortex-engine `test/observe-commit-classify.test.mjs` の 12 行。
|
|
9
|
-
* **写すのはこの表で、実装を書き写すのではない**——同じ入力を両方に流して突き合わせる
|
|
10
|
-
* (`connection-state-keys.mjs` の写しと同じ流儀)。tools 側の表は
|
|
11
|
-
* `test/fixtures/commit-classify-contract.json` にあり、`commit-classify-parity.test.mjs`
|
|
12
|
-
* が全行を流す。**engine が判定を変えたらこの fixture を差し替える**(テストは書き換えない)。
|
|
13
|
-
*
|
|
14
|
-
* 🔴 **tools 側で規則を足さない。** 「再導出:」のような件名を足したくなっても、足す先は
|
|
15
|
-
* engine の正本(写しに足すと、正本と写しが逆向きにずれて契約テストが意味を失う)。
|
|
16
|
-
*
|
|
17
|
-
* ## なぜ `repo.ts` の `classifyCommit` と別に置くのか
|
|
18
|
-
*
|
|
19
|
-
* `repo.ts` のものは**別の問い**に答える 3 値(human / migration / ai)で、画面の
|
|
20
|
-
* 「AI による最近の変更」が使っている。あれを揃えるのは #646 の便で、この便では触らない
|
|
21
|
-
* ——揃えると 44 案件の既存の画面(履歴の節)が同時に変わる。ここは**新しい帯の条件だけ**が
|
|
22
|
-
* 使う判定で、**既定は「不明」**(証拠が無ければ倒さない)。
|
|
23
|
-
*
|
|
24
|
-
* ## 裁定(2026-09-17・#432 のコメント 5-4)
|
|
25
|
-
*
|
|
26
|
-
* > Claude Code 経由の push も「人が書いた」に数える(責任の所在で決める。人の名義で本人が
|
|
27
|
-
* > 結果を見て push している)。件数の閾値は設けない。「機械」に数えるのは**夜間処理・
|
|
28
|
-
* > マイグレーション・一括取り込みだけ**。
|
|
29
|
-
*/
|
|
30
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
31
|
-
exports.DELETE_SUBJECT_RE = exports.CONFIRM_SUBJECT_RE = exports.MIGRATION_REASON = void 0;
|
|
32
|
-
exports.looksLikeMigrationSubject = looksLikeMigrationSubject;
|
|
33
|
-
exports.operatedByKind = operatedByKind;
|
|
34
|
-
exports.classifyGoldCommit = classifyGoldCommit;
|
|
35
|
-
exports.isMachineRewrite = isMachineRewrite;
|
|
36
|
-
/**
|
|
37
|
-
* 機械の名義(**この 3 経路だけ**が機械)。`why` は `reason` にそのまま出る。
|
|
38
|
-
*
|
|
39
|
-
* 🔴 **`reason` の綴りは帯の条件が読む**(マイグレーションだけは帯を立てない・#665)。
|
|
40
|
-
* 綴りを変えるなら正本と fixture と `MIGRATION_REASON` を同時に見ること。
|
|
41
|
-
*/
|
|
42
|
-
const MACHINE_SUBJECTS = [
|
|
43
|
-
{ re: /^自動追記/, why: "夜間処理" },
|
|
44
|
-
{ re: /^自動生成/, why: "夜間処理" },
|
|
45
|
-
{ re: /^週次整理/, why: "夜間処理(週次)" },
|
|
46
|
-
{ re: /^(?:マイグレーション|migrate:|chore\(migrate\))/i, why: "マイグレーション" },
|
|
47
|
-
{ re: /^一括取り込み/, why: "一括取り込み" },
|
|
48
|
-
];
|
|
49
|
-
/** マイグレーションの `reason`。**帯を立てない側に倒す鍵の 1 枚目**(#665 決定表 4d) */
|
|
50
|
-
exports.MIGRATION_REASON = "マイグレーション";
|
|
51
|
-
/**
|
|
52
|
-
* 🔴 **「引っ越し」の綴りの 2 枚目**(`repo.ts:230` の写し・設計レビュー 🔴1)。
|
|
53
|
-
*
|
|
54
|
-
* 正本の `MACHINE_SUBJECTS` のマイグレーションの行は件名の**頭**を `マイグレーション` /
|
|
55
|
-
* `migrate:` / `chore(migrate)` で見るが、**艦隊の実際の件名は
|
|
56
|
-
* `エンジンマイグレーションを適用(schema_version更新)`** で、頭が「エンジン」なので
|
|
57
|
-
* 当たらない(実測: レコードを触った 127 件のうち 93 件が「機械の名義」の枝に落ち、
|
|
58
|
-
* engine の `isMigration()` では 0 件になる=`changed_by` から除かれていない)。
|
|
59
|
-
*
|
|
60
|
-
* この 1 枚が無いと、**引っ越しの翌朝に確認済みのレコード全件へ帯が出る**——実データの
|
|
61
|
-
* コミットの 53.6% が引っ越しで、中身は 1 文字も変わっていない。
|
|
62
|
-
*
|
|
63
|
-
* ⚠️ これは**正本に足すべき規則**(申し送り済み)。正本が拾うようになってもこの枚は
|
|
64
|
-
* 害にならない(両方が「引っ越し」と言うだけ)ので、**消すのは正本が実件名を拾ったあと**。
|
|
65
|
-
* 綴りは画面の「AI による最近の変更」が使う `repo.ts` の判定とそろえてある——ずれると
|
|
66
|
-
* 「履歴からは隠れているのに帯は立つ」という食い違った 1 ページができる(🔴7)。
|
|
67
|
-
*/
|
|
68
|
-
const MIGRATION_SUBJECT_RE = /^(エンジン)?マイグレーション|^スキーマ移行|^schema[_-]?version|^(apply\s+)?[a-z ]*migration/i;
|
|
69
|
-
/** 件名が「データ構造の引っ越し」の規約か(`repo.ts` の綴り) */
|
|
70
|
-
function looksLikeMigrationSubject(subject) {
|
|
71
|
-
return MIGRATION_SUBJECT_RE.test(String(subject || ""));
|
|
72
|
-
}
|
|
73
|
-
/** 機械の名義(名前)。`…[bot]` は GitHub のアプリ名義の綴り */
|
|
74
|
-
const MACHINE_NAME_RE = /(?:^|\s)(?:github-actions|dependabot|renovate)(?:\[bot\])?$|\[bot\]$/i;
|
|
75
|
-
/**
|
|
76
|
-
* 「人の名義とは言えない」メール。`[bot]@users.noreply.github.com` は機械の名義だが、
|
|
77
|
-
* `actions@github.com` のような汎用の名義は「誰が押したか分からない」=**不明**に倒す。
|
|
78
|
-
*/
|
|
79
|
-
const BOT_EMAIL_RE = /\[bot\]@users\.noreply\.github\.com$/i;
|
|
80
|
-
const ANONYMOUS_EMAIL_RE = /^(?:actions@github\.com|noreply@github\.com|no-reply@github\.com)$/i;
|
|
81
|
-
/** 人が Gold を直した印(tools の intake が書く件名。`repo.ts` と同じ綴り) */
|
|
82
|
-
exports.CONFIRM_SUBJECT_RE = /^Gold確認[::]/;
|
|
83
|
-
exports.DELETE_SUBJECT_RE = /^(?:レコード削除|Gold削除)/;
|
|
84
|
-
/** `Operated-By:` trailer の値から**経路だけ**を取る(値そのものは持ち回さない・PII) */
|
|
85
|
-
function operatedByKind(trailerValue) {
|
|
86
|
-
const v = String(trailerValue || "").trim();
|
|
87
|
-
if (!v)
|
|
88
|
-
return "none";
|
|
89
|
-
return /^basic:/.test(v) ? "basic" : "portal";
|
|
90
|
-
}
|
|
91
|
-
/**
|
|
92
|
-
* コミット 1 件を 3 値に分ける(正本と同じ順序・同じ規則)。
|
|
93
|
-
*
|
|
94
|
-
* 名前を `classifyCommit` にしない——`repo.ts` に同名で**別の問い**に答える関数があり、
|
|
95
|
-
* 取り違えると画面の履歴の節と帯の条件が静かに入れ替わる。
|
|
96
|
-
*/
|
|
97
|
-
function classifyGoldCommit({ authorName = "", authorEmail = "", committerName: _committerName = "", subject = "", body = "", } = {}) {
|
|
98
|
-
const trailer = (String(body).match(/^Operated-By:\s*(.+)$/m) || [])[1] || "";
|
|
99
|
-
const kind = operatedByKind(trailer);
|
|
100
|
-
// **検証済みか**は名乗り券の有無だけで決める(`basic:` は券が無いときの既定=自己申告)
|
|
101
|
-
const verified = kind === "portal";
|
|
102
|
-
const confirmer = (String(subject).match(exports.CONFIRM_SUBJECT_RE)
|
|
103
|
-
? String(subject).replace(exports.CONFIRM_SUBJECT_RE, "").trim()
|
|
104
|
-
: null) || null;
|
|
105
|
-
const out = (by, reason) => ({
|
|
106
|
-
by,
|
|
107
|
-
verified: by === "human" ? verified : false,
|
|
108
|
-
reason,
|
|
109
|
-
operatedByKind: kind,
|
|
110
|
-
confirmer,
|
|
111
|
-
});
|
|
112
|
-
// ---- 1. 機械(夜間処理・マイグレーション・一括取り込みだけ) ----
|
|
113
|
-
const machineSubject = MACHINE_SUBJECTS.find((m) => m.re.test(String(subject)));
|
|
114
|
-
if (machineSubject)
|
|
115
|
-
return out("machine", machineSubject.why);
|
|
116
|
-
if (MACHINE_NAME_RE.test(String(authorName).trim()))
|
|
117
|
-
return out("machine", "機械の名義");
|
|
118
|
-
if (BOT_EMAIL_RE.test(String(authorEmail).trim()))
|
|
119
|
-
return out("machine", "機械の名義(メール)");
|
|
120
|
-
// ---- 2. 人(**Claude Code 経由の push もここ**・裁定 5-4) ----
|
|
121
|
-
// 🔴 `Co-Authored-By: Claude` / `Generated with Claude Code` を**機械の証拠にしない**。
|
|
122
|
-
// 人の名義で本人が結果を見て push しているので、責任の所在は人にある
|
|
123
|
-
if (String(subject).match(exports.CONFIRM_SUBJECT_RE))
|
|
124
|
-
return out("human", "人が確認した(件名)");
|
|
125
|
-
if (exports.DELETE_SUBJECT_RE.test(String(subject)))
|
|
126
|
-
return out("human", "人が消した(件名)");
|
|
127
|
-
if (kind !== "none")
|
|
128
|
-
return out("human", "画面からの操作(Operated-By)");
|
|
129
|
-
const name = String(authorName).trim();
|
|
130
|
-
const email = String(authorEmail).trim();
|
|
131
|
-
if (name && email && !ANONYMOUS_EMAIL_RE.test(email))
|
|
132
|
-
return out("human", "人の名義の push");
|
|
133
|
-
// ---- 3. 既定は不明(裁定 5-7) ----
|
|
134
|
-
// 名義が空・汎用の名義(`actions@github.com`)は「誰が押したか分からない」
|
|
135
|
-
return out("unknown", "名義から人か機械か決められない");
|
|
136
|
-
}
|
|
137
|
-
/**
|
|
138
|
-
* **帯を立てる側の機械の書き直しか**(#665 決定表 4c・4d)。
|
|
139
|
-
*
|
|
140
|
-
* 機械の 3 経路(夜間処理・引っ越し・一括取り込み)のうち**引っ越しだけを外す**。実データの
|
|
141
|
-
* コミットの 53.6% が引っ越しで、中身は 1 文字も変わっていない(`repo.ts:199-207` の実測)
|
|
142
|
-
* ので、これで帯を立てると全レコードに帯が出る。engine も `changed_by` を作るときに
|
|
143
|
-
* `isMigration()` を当てているが(`history.mjs:145`)、**それは正本の綴りしか拾わない**うえに
|
|
144
|
-
* **古い帳簿・作り直し前の帳簿を読む夜がある**ので、写し側でも外す。
|
|
145
|
-
*
|
|
146
|
-
* 🔴 **判定は 2 枚の OR**(設計レビュー 🔴1): 正本の `reason` と `repo.ts` の綴り。
|
|
147
|
-
* **どちらかが「引っ越し」と言えば立てない**(片方だけに頼ると実件名が素通りする)。
|
|
148
|
-
*/
|
|
149
|
-
function isMachineRewrite(c, subject) {
|
|
150
|
-
if (c.by !== "machine")
|
|
151
|
-
return false;
|
|
152
|
-
if (c.reason === exports.MIGRATION_REASON)
|
|
153
|
-
return false;
|
|
154
|
-
if (looksLikeMigrationSubject(subject))
|
|
155
|
-
return false;
|
|
156
|
-
return true;
|
|
157
|
-
}
|