@takagaki/cortex-decisions-viewer 0.12.89 → 0.12.91
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 +5 -0
- package/dist/confirmed.js +87 -0
- package/dist/rederivation.js +12 -9
- package/dist/render.js +153 -23
- package/dist/supersedes.js +12 -5
- package/package.json +1 -1
package/dist/build.js
CHANGED
|
@@ -972,6 +972,11 @@ async function readGoldProposals(repoRoot) {
|
|
|
972
972
|
const why = optionalText(row.why);
|
|
973
973
|
if (why)
|
|
974
974
|
proposal.why = why;
|
|
975
|
+
// その場で記録できるカードの印(cortex-engine #699 F7b)。**値は "portal" だけ**を通す
|
|
976
|
+
// (語彙の外は無い扱い=今の指示文のボタンのまま)。20260920-686 の「新しい欄を通さない」を
|
|
977
|
+
// この 1 欄だけ覆した(決定ログ 20260925-699-f7b.md)
|
|
978
|
+
if (row.decide === "portal")
|
|
979
|
+
proposal.decide = "portal";
|
|
975
980
|
const list = byPath.get(recordPath);
|
|
976
981
|
if (list)
|
|
977
982
|
list.push(proposal);
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* 「人が確認したか/機械が触ってよいか」を 1 か所で答える口(cortex-engine #699 F2b・案 1)。
|
|
4
|
+
*
|
|
5
|
+
* ## 🔴 これは写しで、正本は engine にある
|
|
6
|
+
*
|
|
7
|
+
* 正本: cortex-engine `scripts/gold/confirmed.mjs`(`machineOwned`・`humanOwned`・`CONFIRMED_STATUSES`)。
|
|
8
|
+
* もう 1 つの写し: このリポの `infra/lambda/intake/confirmed.mjs`(intake はビューアを import できない)。
|
|
9
|
+
* **題材で一致を確かめる**: engine の `test/fixtures/confirmation-status-contract.json` をファイルごと
|
|
10
|
+
* `viewer/test/fixtures/` と `infra/test/fixtures/` に写し、`test/confirmation-status-parity.test.mjs`
|
|
11
|
+
* (intake は `infra/test/intake-delete-record-logic.test.mjs`)が全ケースを流す。
|
|
12
|
+
* 規則を変えるときは engine の正本・2 つの写し・題材を同じ日に直す(**tools 側で規則を足さない**)。
|
|
13
|
+
*
|
|
14
|
+
* ## 規則(記録の `status` の生の値だけを見る。確認の帳簿は見ない)
|
|
15
|
+
*
|
|
16
|
+
* - `machineOwned(path, status)` … 機械の下書き。`status === "draft"`(**完全一致・trim も小文字化もしない**)
|
|
17
|
+
* - `humanOwned(path, status)` … 人が確認した。status がその区画の語彙から draft を除いた値
|
|
18
|
+
* 決定・ルール → `active` / 用語 → `active`・`superseded` / 関係者 → `active`・`inactive`
|
|
19
|
+
* 区画が分からない path → `active`・`superseded`・`inactive`
|
|
20
|
+
* - どちらでもない値(空・前後に空白・語彙の外)は人のものでも機械のものでもない
|
|
21
|
+
* - status に文字列・null・undefined 以外(真偽値など)が来たら投げる(今の式を渡す古い呼び方を黙って通さない)
|
|
22
|
+
*
|
|
23
|
+
* 区画は path の `Cortex/<区画>/`(先頭か `/` の直後)で決める。ビューアで path が手元に無い記録は、
|
|
24
|
+
* 呼び出し側が `Cortex/<区画>/` を渡して区画だけを伝える(engine の supersedes-index の置き場と同じ意味)。
|
|
25
|
+
*
|
|
26
|
+
* ## 🔴 クライアント(`render.ts` の `CLIENT_JS`)へ `.toString()` で埋め込む
|
|
27
|
+
*
|
|
28
|
+
* **下の関数は全部クロージャを持たない function 宣言に保つこと。** 参照してよいのはこのファイルの
|
|
29
|
+
* 別の関数(同じく流し込む)だけで、定数は関数の中に書く。`export const` を関数から参照すると
|
|
30
|
+
* CommonJS で `exports.X` に書き換わり、流し込んだ先で落ちる(`LIVE_CONFIRM_LIMITS` の項に経緯)。
|
|
31
|
+
* 外へ見せる表(`CONFIRMED_STATUSES`)は関数から作る入れ物で、関数の側はそれを読まない。
|
|
32
|
+
*/
|
|
33
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
34
|
+
exports.CONFIRMED_STATUSES_ANY = exports.CONFIRMED_STATUSES = void 0;
|
|
35
|
+
exports.sectionOfPath = sectionOfPath;
|
|
36
|
+
exports.confirmedStatusesOf = confirmedStatusesOf;
|
|
37
|
+
exports.confirmedStatusChecked = confirmedStatusChecked;
|
|
38
|
+
exports.machineOwned = machineOwned;
|
|
39
|
+
exports.humanOwned = humanOwned;
|
|
40
|
+
/** path から区画を引く(`Cortex/<区画>/` を含むか。先頭か `/` の直後。分からなければ `null`) */
|
|
41
|
+
function sectionOfPath(p) {
|
|
42
|
+
const s = typeof p === "string" ? p.replace(/\\/g, "/") : "";
|
|
43
|
+
// 🔴 順番は engine の `Object.keys(CONFIRMED_STATUSES)` と同じ(決定・ルール・用語・関係者)
|
|
44
|
+
const sections = ["Decisions", "Rules", "Glossary", "Members"];
|
|
45
|
+
for (let i = 0; i < sections.length; i++) {
|
|
46
|
+
const section = sections[i];
|
|
47
|
+
if (s.indexOf("Cortex/" + section + "/") === 0 || s.indexOf("/Cortex/" + section + "/") !== -1)
|
|
48
|
+
return section;
|
|
49
|
+
}
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
/** 区画の「人が確認した」status(その区画の語彙から draft を除いた値)。区画が分からなければ 4 区画の和 */
|
|
53
|
+
function confirmedStatusesOf(section) {
|
|
54
|
+
if (section === "Decisions" || section === "Rules")
|
|
55
|
+
return ["active"];
|
|
56
|
+
if (section === "Glossary")
|
|
57
|
+
return ["active", "superseded"];
|
|
58
|
+
if (section === "Members")
|
|
59
|
+
return ["active", "inactive"];
|
|
60
|
+
return ["active", "superseded", "inactive"];
|
|
61
|
+
}
|
|
62
|
+
/** status の型を確かめる(文字列・null・undefined のほかは投げる) */
|
|
63
|
+
function confirmedStatusChecked(status) {
|
|
64
|
+
if (status === null || status === undefined || typeof status === "string")
|
|
65
|
+
return status;
|
|
66
|
+
throw new TypeError("confirmed: status は文字列で渡してください(実際: " + typeof status + ")");
|
|
67
|
+
}
|
|
68
|
+
/** 機械が触ってよい(下書き)か。`status === "draft"`(完全一致・trim しない) */
|
|
69
|
+
function machineOwned(_path, status) {
|
|
70
|
+
return confirmedStatusChecked(status) === "draft";
|
|
71
|
+
}
|
|
72
|
+
/** 人が確認したか。status がその区画の語彙から draft を除いた値か(完全一致・trim しない) */
|
|
73
|
+
function humanOwned(p, status) {
|
|
74
|
+
const s = confirmedStatusChecked(status);
|
|
75
|
+
if (typeof s !== "string")
|
|
76
|
+
return false;
|
|
77
|
+
return confirmedStatusesOf(sectionOfPath(p)).indexOf(s) !== -1;
|
|
78
|
+
}
|
|
79
|
+
/** 区画ごとの「人が確認した」status(engine の `CONFIRMED_STATUSES` と同じ形・テストと読み手のための入れ物) */
|
|
80
|
+
exports.CONFIRMED_STATUSES = Object.freeze({
|
|
81
|
+
Decisions: Object.freeze(confirmedStatusesOf("Decisions")),
|
|
82
|
+
Rules: Object.freeze(confirmedStatusesOf("Rules")),
|
|
83
|
+
Glossary: Object.freeze(confirmedStatusesOf("Glossary")),
|
|
84
|
+
Members: Object.freeze(confirmedStatusesOf("Members")),
|
|
85
|
+
});
|
|
86
|
+
/** 区画が分からない path の「人が確認した」status(4 区画の和) */
|
|
87
|
+
exports.CONFIRMED_STATUSES_ANY = Object.freeze(confirmedStatusesOf(null));
|
package/dist/rederivation.js
CHANGED
|
@@ -46,6 +46,7 @@ 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 confirmed_1 = require("./confirmed");
|
|
49
50
|
/** 帳簿の版(違ったら空として扱う・engine `LEDGER_VERSION`) */
|
|
50
51
|
exports.LEDGER_VERSION = 1;
|
|
51
52
|
/** 帯を出す区画。**用語とメンバーだけ**(Decisions は積み上げ・Rules は便 13) */
|
|
@@ -63,19 +64,21 @@ const MIN_ELAPSED_DAYS = 0.5;
|
|
|
63
64
|
* その記録が「確認済み」か。**「AI生成・未確認」の札(`render.ts` の `draftBadge`)の裏返し**
|
|
64
65
|
* ——札が出ない記録が確認済み側(#667 のオーナー決定「確認済みは `status` だけで判断する」)。
|
|
65
66
|
*
|
|
66
|
-
* 🔴
|
|
67
|
+
* 🔴 **口 `confirmed.ts` の `!machineOwned` そのもの**(#699 F2b・機械が触る前の守りは `!machineOwned`)。
|
|
68
|
+
* 札も同じ口(`machineOwned`・完全一致)に聞くので、ここで trim も小文字化もしない。
|
|
67
69
|
* 1 文字でも緩めると「札は出ないのに未確認扱い」「札は出るのに確認済み扱い」が生まれる。
|
|
68
70
|
* `status` 欄が無い記録(`rstatus` が `""`)と `superseded` は**確認済み側**——札が出ないから。
|
|
69
71
|
*
|
|
70
72
|
* 🔴 **渡すのは `String()` 済みの `rstatus` だけ**(`build.ts:1048`)。生の frontmatter
|
|
71
|
-
* (`rec.fm.status`)を渡さない——YAML
|
|
73
|
+
* (`rec.fm.status`)を渡さない——YAML は真偽値も配列も返すので、札と同じ文字列にならない
|
|
74
|
+
* (口は文字列・null・undefined のほかを投げる)。
|
|
72
75
|
*
|
|
73
76
|
* ⚠️ **「確認済み」の門はもう 1 つある。** 削除の口の出し分けは `render.ts` の
|
|
74
|
-
* `humanConfirmedStatus
|
|
75
|
-
*
|
|
77
|
+
* `humanConfirmedStatus`(口の `humanOwned`・区画の語彙)で、`""` の扱いがここと逆
|
|
78
|
+
* (確認済みとして**扱う**側は `humanOwned`、機械が触る前の**守り**は `!machineOwned` という割り当て)。
|
|
76
79
|
*/
|
|
77
|
-
function isConfirmedStatus(status) {
|
|
78
|
-
return
|
|
80
|
+
function isConfirmedStatus(status, recordPath = null) {
|
|
81
|
+
return !(0, confirmed_1.machineOwned)(recordPath, status);
|
|
79
82
|
}
|
|
80
83
|
const emptyLedgers = () => ({
|
|
81
84
|
confirmation: new Map(),
|
|
@@ -387,7 +390,7 @@ function contentShaOf(raw) {
|
|
|
387
390
|
* 「帳簿に人が確認した版の指紋が無い」記録だけ(`attachRederived`)。
|
|
388
391
|
*/
|
|
389
392
|
function rederivedContentKey(recordPath, status, ledgers) {
|
|
390
|
-
if (!isConfirmedStatus(status))
|
|
393
|
+
if (!isConfirmedStatus(status, recordPath))
|
|
391
394
|
return null;
|
|
392
395
|
if (!BAND_SECTION_RE.test(recordPath))
|
|
393
396
|
return null;
|
|
@@ -489,8 +492,8 @@ function rederivedBandOf(recordPath, status, ledgers, nowShaOf, verdict, miss) {
|
|
|
489
492
|
* 「上に来るものが重要」でなければ、その仕組みが成り立たない
|
|
490
493
|
*
|
|
491
494
|
* 🔴 **`isConfirmedStatus` は消さない**——このファイルの**印の門(`rederivedContentKey`)と
|
|
492
|
-
* 帯の門(`rederivedBandOf`)**が使う。`render.ts`
|
|
493
|
-
*
|
|
495
|
+
* 帯の門(`rederivedBandOf`)**が使う。`render.ts` の札は同じ口(`confirmed.ts` の `machineOwned`)を
|
|
496
|
+
* **流し込んだ別の実体**で、2 つが一致していることは `rederived-mark-ui.test.mjs` の `[札との一致]` が見る。
|
|
494
497
|
* ここで変えるのは**並びだけ**で、札・帯・印・プロンプトに渡る材料は 1 つも変えていない。
|
|
495
498
|
*
|
|
496
499
|
* - 活性度を持つレコードが 1 件も無い区画は **1 バイトも触らない**(帳簿が無い案件は従来順)
|
package/dist/render.js
CHANGED
|
@@ -80,6 +80,8 @@ const node_crypto_1 = require("node:crypto");
|
|
|
80
80
|
const google_chat_guide_images_1 = require("./google-chat-guide-images");
|
|
81
81
|
const meet_guide_images_1 = require("./meet-guide-images");
|
|
82
82
|
const figma_guide_images_1 = require("./figma-guide-images");
|
|
83
|
+
// 「人が確認したか/機械が触ってよいか」の口(#699 F2b)。正本は engine の confirmed.mjs・判定は流し込むだけ
|
|
84
|
+
const confirmed_1 = require("./confirmed");
|
|
83
85
|
// フロントマターの「つながっている ID」の欄名(#686)。**綴りはここ 1 本**——読む側(画面)も
|
|
84
86
|
// 書く側も同じ定数を引く(欄名は engine 側でまだ確定していない)
|
|
85
87
|
// `<種類>:<値>` の形(#686 C4)。名寄せの提案を「同じ人ですか」の問いに変えてよいかの門は
|
|
@@ -4996,6 +4998,14 @@ button.site-icon[aria-busy="true"] .site-icon-plus { opacity: 0; }
|
|
|
4996
4998
|
.ai-same-btn:hover { background: rgba(194,94,63,0.92); text-decoration: none; }
|
|
4997
4999
|
.ai-diff-btn { background: #fff; color: var(--text); border: 1px solid var(--border); }
|
|
4998
5000
|
.ai-diff-btn:hover { background: #f3f4f6; text-decoration: none; }
|
|
5001
|
+
/* その場で記録するボタン(#699 F7b)。見た目は指示文のリンクと同じ */
|
|
5002
|
+
.ip-decide-btn { cursor: pointer; font-family: inherit; line-height: inherit; }
|
|
5003
|
+
.ip-decide-btn:disabled { opacity: 0.6; cursor: default; }
|
|
5004
|
+
.ip-choice > button { align-self: flex-start; }
|
|
5005
|
+
.ip-decide-msg { flex-basis: 100%; font-size: 12.5px; line-height: 1.6; }
|
|
5006
|
+
.ip-decide-msg:empty { display: none; }
|
|
5007
|
+
.ip-decide-msg.is-error { color: #b45309; }
|
|
5008
|
+
.ip-decided { font-size: 13px; font-weight: 600; color: #15803d; }
|
|
4999
5009
|
/* 「AIで編集」と同じ見た目にそろえる(同じ行き先・同じ仕組みなので、別物に見せない) */
|
|
5000
5010
|
.ai-apply-btn {
|
|
5001
5011
|
display: inline-block; padding: 6px 14px; border-radius: 999px;
|
|
@@ -5936,6 +5946,13 @@ var CAP_STATE_TEXT = ${JSON.stringify(CAP_STATE_TEXT)};
|
|
|
5936
5946
|
var CAP_STATE_MARK = ${JSON.stringify(CAP_STATE_MARK)};
|
|
5937
5947
|
${externalCapability.toString()}
|
|
5938
5948
|
${sourceAdapter.toString()}
|
|
5949
|
+
// 「人が確認したか/機械が触ってよいか」の口(#699 F2b・正本は engine の confirmed.mjs・写しは confirmed.ts)。
|
|
5950
|
+
// 札・[確認済みにする]・「未確認のみ」・削除の可否は**全部これに聞く**(rstatus と直接比べない)
|
|
5951
|
+
${confirmed_1.sectionOfPath.toString()}
|
|
5952
|
+
${confirmed_1.confirmedStatusesOf.toString()}
|
|
5953
|
+
${confirmed_1.confirmedStatusChecked.toString()}
|
|
5954
|
+
${confirmed_1.machineOwned.toString()}
|
|
5955
|
+
${confirmed_1.humanOwned.toString()}
|
|
5939
5956
|
// Claude Code への導線(#404)。**組み立ての正本は上の claudeCodeUrl だけ。**
|
|
5940
5957
|
// 🔴 この定数行を落とすと、流し込んだ関数が未定義の CLAUDE_CODE_HOME を参照して
|
|
5941
5958
|
// 画面のスクリプトが丸ごと死ぬ(定数は export できないので、値はここで注入する)
|
|
@@ -6345,7 +6362,7 @@ ${calendarAddUrl.toString()}
|
|
|
6345
6362
|
}
|
|
6346
6363
|
// 未確認(status: draft = AI生成・人間未確認)の印。Gold層の4型(決定事項・用語集・メンバー・ルール)共通で、active・status無しには何も出さない
|
|
6347
6364
|
function draftBadge(rec) {
|
|
6348
|
-
return rec && rec.rstatus
|
|
6365
|
+
return rec && machineOwned(confirmPathOf(rec), rec.rstatus) ? el("span", { class: "badge draft", text: "AI生成・未確認" }) : null;
|
|
6349
6366
|
}
|
|
6350
6367
|
// 直近のGold昇格で作られたレコードの印。レコードの date は決定日なので、日付順に並べても
|
|
6351
6368
|
// 「今回の実行で何が増えたか」は分からない。ビルド時に git から判定した結果(isLatest)を出す
|
|
@@ -6362,7 +6379,7 @@ ${calendarAddUrl.toString()}
|
|
|
6362
6379
|
// 同じ場面の同じ判断なので、口を離さない。**この帯は draft にしか出ない**ので、
|
|
6363
6380
|
// 人が確認済みにした記録には削除の口ごと現れない(守りはサーバ側にもある)。
|
|
6364
6381
|
function draftNote(rec) {
|
|
6365
|
-
if (!rec || rec.rstatus
|
|
6382
|
+
if (!rec || !machineOwned(confirmPathOf(rec), rec.rstatus)) return null;
|
|
6366
6383
|
var path = promotableRecordPath(rec);
|
|
6367
6384
|
var note = el("div", { class: "draft-note" }, [
|
|
6368
6385
|
el("span", { text: "⚠️" }),
|
|
@@ -6662,6 +6679,8 @@ ${calendarAddUrl.toString()}
|
|
|
6662
6679
|
// 決定以外の一覧(タブを移っても絞り込みは残る)では絞らない——全件が消えないように
|
|
6663
6680
|
if (listState.status === RETIRED_FILTER) return rec && rec._kind === "decision" ? isRetired(rec) : true;
|
|
6664
6681
|
if (Object.prototype.hasOwnProperty.call(FOLLOWUP_FILTER_LABEL, listState.status)) return withFollowup ? matchesFollowupFilter(rec, listState.status) : true;
|
|
6682
|
+
// 「未確認のみ」は口に聞く(札と同じ答え)。それ以外の値は従来どおりの素の比較
|
|
6683
|
+
if (listState.status === "draft") return machineOwned(confirmPathOf(rec), rec.rstatus);
|
|
6665
6684
|
return rec.rstatus === listState.status;
|
|
6666
6685
|
}
|
|
6667
6686
|
/* -------------------------------------------------------------------------
|
|
@@ -7017,7 +7036,7 @@ ${calendarAddUrl.toString()}
|
|
|
7017
7036
|
|
|
7018
7037
|
// 未確認が1件も無い一覧では絞り込みを出さない
|
|
7019
7038
|
function hasUnverified(items) {
|
|
7020
|
-
return (items || []).some(function (r) { return r.rstatus
|
|
7039
|
+
return (items || []).some(function (r) { return machineOwned(confirmPathOf(r), r.rstatus); });
|
|
7021
7040
|
}
|
|
7022
7041
|
|
|
7023
7042
|
// ---------- AI導線(Gold層への人間の書き込み口。claude.ai/code へのディープリンク) ----------
|
|
@@ -7115,6 +7134,18 @@ ${calendarAddUrl.toString()}
|
|
|
7115
7134
|
var m = rec.editUrl.match(/\\/edit\\/[^/]+\\/(.+)$/);
|
|
7116
7135
|
return m ? decodeURIComponent(m[1]) : "";
|
|
7117
7136
|
}
|
|
7137
|
+
/**
|
|
7138
|
+
* 口(machineOwned・humanOwned)へ渡す path。記録の path が引ければそれ、引けなければ
|
|
7139
|
+
* **区画だけを 「Cortex/<区画>/」 の形で伝える**(engine の supersedes-index が決定の区画で聞くのと同じ意味)。
|
|
7140
|
+
* 区画も分からない記録(Home など)は null(口は区画不明として答える)
|
|
7141
|
+
*/
|
|
7142
|
+
function confirmPathOf(rec) {
|
|
7143
|
+
var p = repoPathOf(rec);
|
|
7144
|
+
if (p) return p;
|
|
7145
|
+
if (rec && rec._kind === "decision") return "Cortex/Decisions/";
|
|
7146
|
+
if (rec && rec._kind === "generic" && rec._section) return "Cortex/" + rec._section + "/";
|
|
7147
|
+
return null;
|
|
7148
|
+
}
|
|
7118
7149
|
// Gold型(ディレクトリ名)→ 書き込みプロンプトの対応。決定事項/用語集/メンバー/ルールの4型のみ。
|
|
7119
7150
|
// レポート等スキルの無いディレクトリは対象外(ボタンを出さない)。
|
|
7120
7151
|
// プロンプトには規約を書き写さない。各区画の README.md(規約)と template.md(雛形)をAIに読ませ、
|
|
@@ -7341,7 +7372,7 @@ ${calendarAddUrl.toString()}
|
|
|
7341
7372
|
// ——のではなく**明示的に据え置きを指示する**。指示が無いと、AIが「AIが書いたのだから
|
|
7342
7373
|
// draft にすべき」と判断して人間の確認を取り消す形になる(実際に起こりうる読み方)
|
|
7343
7374
|
lines.push(
|
|
7344
|
-
rec.rstatus
|
|
7375
|
+
machineOwned(confirmPathOf(rec), rec.rstatus)
|
|
7345
7376
|
? "あなた(人間)が内容を確認して編集したので、status が draft なら active に上げてください。"
|
|
7346
7377
|
: "この記録は確認済み(status: active)です。**適用後も status は active のままにしてください**(draft に戻さないでください)。",
|
|
7347
7378
|
);
|
|
@@ -7630,7 +7661,7 @@ ${calendarAddUrl.toString()}
|
|
|
7630
7661
|
/** どちらのボタンにも付ける締め(status の据え置き/昇格と、書き込む前の承認) */
|
|
7631
7662
|
function ipTail(rec) {
|
|
7632
7663
|
return [
|
|
7633
|
-
rec.rstatus
|
|
7664
|
+
machineOwned(confirmPathOf(rec), rec.rstatus)
|
|
7634
7665
|
? "あなた(人間)が内容を確認して編集したので、status が draft なら active に上げてください。"
|
|
7635
7666
|
: "この記録は確認済み(status: active)です。**適用後も status は active のままにしてください**(draft に戻さないでください)。",
|
|
7636
7667
|
"書き込む前に、変更内容を提示して承認を得てください。",
|
|
@@ -7862,6 +7893,109 @@ ${calendarAddUrl.toString()}
|
|
|
7862
7893
|
}
|
|
7863
7894
|
return ul;
|
|
7864
7895
|
}
|
|
7896
|
+
/** 今の 2 つの選択(AI への指示文のリンク+押すと何が起きるか) */
|
|
7897
|
+
function ipAiChoices(rec, sectionName, one, typed) {
|
|
7898
|
+
return [
|
|
7899
|
+
el("div", { class: "ip-choice" }, [
|
|
7900
|
+
ipSameBtn(rec, sectionName, rec.title, one, typed),
|
|
7901
|
+
el("div", { class: "ip-choice-note", text: "承認すると、翌晩からこの人の活動としてまとめて数えます" }),
|
|
7902
|
+
]),
|
|
7903
|
+
el("div", { class: "ip-choice" }, [
|
|
7904
|
+
ipDiffBtn(rec, sectionName, rec.title, one, typed),
|
|
7905
|
+
el("div", { class: "ip-choice-note", text: "承認すると、翌晩からこの人に結び付けません" }),
|
|
7906
|
+
]),
|
|
7907
|
+
];
|
|
7908
|
+
}
|
|
7909
|
+
/**
|
|
7910
|
+
* その場で記録できるか(cortex-engine #699 F7b)。engine がカードに decide: "portal" を付け、
|
|
7911
|
+
* claim_key があり、intake の口と名乗り券(ポータルの Cookie)があるときだけ。
|
|
7912
|
+
* 🔴 **券が無いときは今の指示文のリンクに倒す**(押しても ACTOR_REQUIRED で断られるだけ)
|
|
7913
|
+
*/
|
|
7914
|
+
function ipCanDecide(one) {
|
|
7915
|
+
return !!(one && one.decide === "portal" && ipText(one.claim_key) && SITE.intake && actorTicket());
|
|
7916
|
+
}
|
|
7917
|
+
/**
|
|
7918
|
+
* 断られたときの 1 行(code ごとに一手)。**帯は症状・行は一手・繰り返さない。**
|
|
7919
|
+
* final: 押し直しても同じ答えになる(ボタンを畳む)/false: 時間をおけば通りうる(ボタンを残す)
|
|
7920
|
+
*/
|
|
7921
|
+
var IP_DECIDE_DENIED = {
|
|
7922
|
+
SELF_DECIDED: { text: "ご本人が決めています", final: true },
|
|
7923
|
+
ADMIN_DECIDED: { text: "管理者が決めています", final: true },
|
|
7924
|
+
FORBIDDEN: { text: "この案件のメンバーだけが決められます", final: true },
|
|
7925
|
+
ROLE_FORBIDDEN: { text: "この案件のメンバーだけが決められます", final: true },
|
|
7926
|
+
EMAIL_ID: { text: "メールの ID はご本人が決めます", final: true },
|
|
7927
|
+
TENANT_MISMATCH: { text: "別の組織の人です。管理者に頼んでください", final: true },
|
|
7928
|
+
LINKED_TO_OTHER: { text: "ほかの人に結ばれています。管理者に頼んでください", final: true },
|
|
7929
|
+
DENIED_FIRST: { text: "先に「別人」と決まっています。管理者に頼んでください", final: true },
|
|
7930
|
+
ACTOR_REQUIRED: { text: "ポータルからこの案件を開き直してください", final: true },
|
|
7931
|
+
ACTOR_INVALID: { text: "ポータルからこの案件を開き直してください", final: true },
|
|
7932
|
+
ACTOR_PROJECT_MISMATCH: { text: "ポータルからこの案件を開き直してください", final: true },
|
|
7933
|
+
ACCESS_REVOKED: { text: "ポータルからこの案件を開き直してください", final: true },
|
|
7934
|
+
};
|
|
7935
|
+
var IP_DECIDE_RETRY = "いま記録できません。時間をおいて押してください";
|
|
7936
|
+
/**
|
|
7937
|
+
* [同じ人です][別人です]を intake の identity-decide を呼ぶボタンにする(#699 F7b)。
|
|
7938
|
+
* 押した後はカードの中でその場で「記録しました(翌晩から反映)」に置き換える。
|
|
7939
|
+
* SUBJECT_UNRESOLVED(ポータルが 1 人に決められない)は今の指示文のリンクに戻す
|
|
7940
|
+
*/
|
|
7941
|
+
function ipDecideActions(rec, sectionName, one, typed) {
|
|
7942
|
+
var claimKey = one.claim_key;
|
|
7943
|
+
var box = el("div", { class: "ip-actions ip-decide" });
|
|
7944
|
+
var msg = el("div", { class: "ip-decide-msg" });
|
|
7945
|
+
var sameBtn = el("button", { type: "button", class: "ai-same-btn ip-decide-btn", text: "同じ人です" });
|
|
7946
|
+
var diffBtn = el("button", { type: "button", class: "ai-diff-btn ip-decide-btn", text: "別人です" });
|
|
7947
|
+
var busy = false;
|
|
7948
|
+
function settle(node) {
|
|
7949
|
+
while (box.firstChild) box.removeChild(box.firstChild);
|
|
7950
|
+
box.appendChild(node);
|
|
7951
|
+
}
|
|
7952
|
+
function press(decision, btn) {
|
|
7953
|
+
if (busy) return;
|
|
7954
|
+
busy = true;
|
|
7955
|
+
var other = btn === sameBtn ? diffBtn : sameBtn;
|
|
7956
|
+
other.disabled = true;
|
|
7957
|
+
var endBusy = beginBusy(btn, "記録中…");
|
|
7958
|
+
msg.textContent = "";
|
|
7959
|
+
var done = function (res) {
|
|
7960
|
+
var j = (res && res.j) || {};
|
|
7961
|
+
if (res && res.ok && j.ok === true) {
|
|
7962
|
+
settle(el("div", { class: "ip-decided", text: "記録しました(翌晩から反映)" }));
|
|
7963
|
+
return;
|
|
7964
|
+
}
|
|
7965
|
+
var code = typeof j.code === "string" ? j.code : "";
|
|
7966
|
+
if (code === "SUBJECT_UNRESOLVED") {
|
|
7967
|
+
box.className = "ip-actions";
|
|
7968
|
+
while (box.firstChild) box.removeChild(box.firstChild);
|
|
7969
|
+
ipAiChoices(rec, sectionName, one, typed).forEach(function (n) { box.appendChild(n); });
|
|
7970
|
+
return;
|
|
7971
|
+
}
|
|
7972
|
+
var denied = Object.prototype.hasOwnProperty.call(IP_DECIDE_DENIED, code) ? IP_DECIDE_DENIED[code] : null;
|
|
7973
|
+
if (denied && denied.final) {
|
|
7974
|
+
settle(el("div", { class: "ip-decide-msg is-error", text: denied.text }));
|
|
7975
|
+
return;
|
|
7976
|
+
}
|
|
7977
|
+
endBusy();
|
|
7978
|
+
other.disabled = false;
|
|
7979
|
+
busy = false;
|
|
7980
|
+
msg.className = "ip-decide-msg is-error";
|
|
7981
|
+
msg.textContent = IP_DECIDE_RETRY;
|
|
7982
|
+
};
|
|
7983
|
+
intakeJson(callIntake("identity-decide", { op: "identity-decide", claim_key: claimKey, decision: decision }))
|
|
7984
|
+
.then(done, function () { done({ ok: false, j: { code: "UNAVAILABLE" } }); });
|
|
7985
|
+
}
|
|
7986
|
+
sameBtn.addEventListener("click", function () { press("confirm", sameBtn); });
|
|
7987
|
+
diffBtn.addEventListener("click", function () { press("deny", diffBtn); });
|
|
7988
|
+
box.appendChild(el("div", { class: "ip-choice" }, [
|
|
7989
|
+
sameBtn,
|
|
7990
|
+
el("div", { class: "ip-choice-note", text: "押すと記録します。翌晩からこの人の活動としてまとめて数えます" }),
|
|
7991
|
+
]));
|
|
7992
|
+
box.appendChild(el("div", { class: "ip-choice" }, [
|
|
7993
|
+
diffBtn,
|
|
7994
|
+
el("div", { class: "ip-choice-note", text: "押すと記録します。翌晩からこの人に結び付けません" }),
|
|
7995
|
+
]));
|
|
7996
|
+
box.appendChild(msg);
|
|
7997
|
+
return box;
|
|
7998
|
+
}
|
|
7865
7999
|
/** 「同じ人ですか」のカード 1 枚。**ボタンはカードごと**(束ねる側からは外してある) */
|
|
7866
8000
|
function ipCard(rec, sectionName, one, typed) {
|
|
7867
8001
|
// 見出し(常に画面で組む)→ 理由の 1 行 → 根拠 → 2 つの選択 → くわしく(残りの文と問い合わせ用の ID)
|
|
@@ -7873,16 +8007,11 @@ ${calendarAddUrl.toString()}
|
|
|
7873
8007
|
]);
|
|
7874
8008
|
var ev = proposalEvidenceList(one);
|
|
7875
8009
|
if (ev) item.appendChild(ev);
|
|
7876
|
-
|
|
7877
|
-
|
|
7878
|
-
|
|
7879
|
-
|
|
7880
|
-
|
|
7881
|
-
el("div", { class: "ip-choice" }, [
|
|
7882
|
-
ipDiffBtn(rec, sectionName, rec.title, one, typed),
|
|
7883
|
-
el("div", { class: "ip-choice-note", text: "承認すると、翌晩からこの人に結び付けません" }),
|
|
7884
|
-
]),
|
|
7885
|
-
]));
|
|
8010
|
+
// その場で記録できるカード(decide: "portal"・名乗り券あり)だけ intake を呼ぶボタンにする(#699 F7b)。
|
|
8011
|
+
// それ以外は今の指示文のリンクのまま(1 バイトも変えない)
|
|
8012
|
+
item.appendChild(ipCanDecide(one)
|
|
8013
|
+
? ipDecideActions(rec, sectionName, one, typed)
|
|
8014
|
+
: el("div", { class: "ip-actions" }, ipAiChoices(rec, sectionName, one, typed)));
|
|
7886
8015
|
// 🔴 **ID の値は「くわしく」の中にだけ置く**(問い合わせ用。判断は見出しと根拠でする)。
|
|
7887
8016
|
// リンクにしてよい形(許可リストで検査・Slack と Backlog は行き先のドメインが 1 つに
|
|
7888
8017
|
// 決まるときだけ)は「つながっている ID」と同じ規則で決める
|
|
@@ -19018,14 +19147,13 @@ ${calendarAddUrl.toString()}
|
|
|
19018
19147
|
if (meta && meta.parentNode) meta.parentNode.insertBefore(stale, meta.nextSibling);
|
|
19019
19148
|
}
|
|
19020
19149
|
/**
|
|
19021
|
-
*
|
|
19022
|
-
*
|
|
19023
|
-
* inactive
|
|
19150
|
+
* 人の確認印が付いた記録か。**口の humanOwned に聞く**(#699 F2b・intake の deletableRecordStatus も
|
|
19151
|
+
* 同じ口の写しに聞く)。区画の語彙から draft を除いた値(決定・ルール active/用語 active・superseded/
|
|
19152
|
+
* 関係者 active・inactive)。ここは「押しても断られるボタンを出さない」ための
|
|
19024
19153
|
* 出し分けで、**守りはサーバ側**(promote の PROMOTABLE_PATH と同じ関係)。
|
|
19025
19154
|
*/
|
|
19026
|
-
function humanConfirmedStatus(rec) {
|
|
19027
|
-
|
|
19028
|
-
return s === "active" || s === "superseded" || s === "inactive";
|
|
19155
|
+
function humanConfirmedStatus(rec, path) {
|
|
19156
|
+
return !!rec && humanOwned(path || confirmPathOf(rec), rec.rstatus);
|
|
19029
19157
|
}
|
|
19030
19158
|
/**
|
|
19031
19159
|
* 削除の口(ボタンと、その結果を出す行)。**置き場は呼び出し側が決める。**
|
|
@@ -19039,9 +19167,11 @@ ${calendarAddUrl.toString()}
|
|
|
19039
19167
|
* - 詳細ページの操作列 …… 警告帯が出ない記録。置き場が他に無いのでここに寄せる
|
|
19040
19168
|
*/
|
|
19041
19169
|
function buildDeleteControls(rec) {
|
|
19042
|
-
|
|
19170
|
+
// 🔴 path を先に引く——削除できる記録は path が必ず引けるので、口には区画入りの path で聞ける
|
|
19171
|
+
//(intake と同じ答え。区画の分からない path で聞くと決定の superseded まで確認済みに数える)
|
|
19043
19172
|
var path = deletableRecordPath(rec);
|
|
19044
19173
|
if (!path) return null;
|
|
19174
|
+
if (humanConfirmedStatus(rec, path)) return null;
|
|
19045
19175
|
// **語尾の「…」は 2026-08-29 のオーナーFBで外した。** 二択の相方(「確認済みにする」)と
|
|
19046
19176
|
// 語の形が揃わず、片方だけが下書きのように見えていた。押した先に確認モーダルが必ず出ることは
|
|
19047
19177
|
// 語尾ではなく操作そのもの(confirmDanger を await する下の行)で担保している
|
|
@@ -19063,7 +19193,7 @@ ${calendarAddUrl.toString()}
|
|
|
19063
19193
|
* 見た目は控えめな文字のボタンのままにして、隣の実体のあるボタンと重さを分ける。
|
|
19064
19194
|
*/
|
|
19065
19195
|
function attachDeleteToEditRow(rec, editRow) {
|
|
19066
|
-
if (!rec || rec.rstatus
|
|
19196
|
+
if (!rec || machineOwned(confirmPathOf(rec), rec.rstatus)) return null;
|
|
19067
19197
|
var del = buildDeleteControls(rec);
|
|
19068
19198
|
if (!del) return null;
|
|
19069
19199
|
editRow.appendChild(del.btn);
|
package/dist/supersedes.js
CHANGED
|
@@ -3,7 +3,6 @@ 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.CONFIRMED_STATUS = void 0;
|
|
7
6
|
exports.decisionKeyOf = decisionKeyOf;
|
|
8
7
|
exports.decisionDateOf = decisionDateOf;
|
|
9
8
|
exports.supersedesIndex = supersedesIndex;
|
|
@@ -23,14 +22,20 @@ exports.retiredIdsOf = retiredIdsOf;
|
|
|
23
22
|
*
|
|
24
23
|
* 1. 置き換えの正本は**新しい側の `relations[supersedes]` だけ**。古い側の `superseded_by`
|
|
25
24
|
* (engine の夜間の精製が書く写し)は逆引きの材料にしない
|
|
26
|
-
* 2. **確認済み=新しい側の `status` が `active
|
|
25
|
+
* 2. **確認済み=新しい側の `status` が `active`**(口 `confirmed.ts` の `humanOwned`・決定の区画の答え)。
|
|
26
|
+
* 空・`draft`・語彙の外は「未確認」
|
|
27
27
|
* 3. **置き換えられた(もう有効でない・retired)=確認済みの置き換えが 1 本以上**
|
|
28
28
|
* 4. 置き換え先は**全部・新しい順**(決定日の降順 → ID の降順・同じ ID は 1 回)
|
|
29
29
|
* 5. 自分自身を指す・実在しない ID は数えない。古い側の `status` は問わない
|
|
30
30
|
*/
|
|
31
31
|
const node_path_1 = __importDefault(require("node:path"));
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
const confirmed_1 = require("./confirmed");
|
|
33
|
+
/**
|
|
34
|
+
* 口に渡す path が無い・決定の区画の外のときの置き場(逆引きに来るのは決定だけなので、決定の区画の答えで聞く)。
|
|
35
|
+
* 🔴 区画の分からない path のまま聞くと、決定の語彙に無い `superseded`・`inactive` まで確認済みに数える
|
|
36
|
+
* (engine の `DECISIONS_ANCHOR` と同じ)
|
|
37
|
+
*/
|
|
38
|
+
const DECISIONS_ANCHOR = "Cortex/Decisions/records/";
|
|
34
39
|
/**
|
|
35
40
|
* 決定を指す文字列を突き合わせの鍵にする(`decision:` の頭・パス・`.md` を落とす)。
|
|
36
41
|
* engine は `path.basename`(Linux で走る=posix)なので、ここも posix で固定する
|
|
@@ -56,6 +61,8 @@ function normalize(d) {
|
|
|
56
61
|
key: decisionKeyOf(id),
|
|
57
62
|
date: decisionDateOf(r.date, id),
|
|
58
63
|
status: typeof r.status === "string" ? r.status.trim() : "",
|
|
64
|
+
// 口(`confirmed.ts`)へ渡すだけ(逆引きの出力には載せない・題材の契約は変わらない)
|
|
65
|
+
path: typeof r.path === "string" && r.path.includes("Cortex/Decisions/") ? r.path : DECISIONS_ANCHOR,
|
|
59
66
|
supersedes: (Array.isArray(r.supersedes) ? r.supersedes : []).map(decisionKeyOf).filter(Boolean),
|
|
60
67
|
};
|
|
61
68
|
}
|
|
@@ -91,7 +98,7 @@ function supersedesIndex(decisions) {
|
|
|
91
98
|
const entry = out.get(old.key) || { id: old.id, by: [], retired: false };
|
|
92
99
|
if (entry.by.some((x) => decisionKeyOf(x.id) === d.key))
|
|
93
100
|
continue;
|
|
94
|
-
const confirmed = d.
|
|
101
|
+
const confirmed = (0, confirmed_1.humanOwned)(d.path, d.status);
|
|
95
102
|
entry.by.push({ id: d.id, date: d.date, status: d.status, confirmed });
|
|
96
103
|
if (confirmed)
|
|
97
104
|
entry.retired = true;
|
package/package.json
CHANGED