@takagaki/cortex-decisions-viewer 0.12.200 → 0.12.214
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/README.md +4 -2
- package/dist/build.js +123 -85
- package/dist/canonical-evidence.js +11 -191
- package/dist/change-log.js +1 -1
- package/dist/config-flow.js +462 -0
- package/dist/config-mode.js +57 -0
- package/dist/confirmed.js +2 -2
- package/dist/engine/decision-v2-fields.js +188 -0
- package/dist/engine/ref-tokens.js +276 -0
- package/dist/engine/related-materials-field.js +148 -0
- package/dist/engine/supersedes-index.js +257 -0
- package/dist/google-confirm-page.js +14 -3
- package/dist/ledger-dir.js +1 -0
- package/dist/memory-dir.js +40 -0
- package/dist/memory-evidence.js +12 -6
- package/dist/rederivation.js +17 -8
- package/dist/render.js +909 -78
- package/dist/story.js +13 -9
- package/dist/supersedes.js +12 -110
- package/dist/view-dir.js +120 -3
- package/package.json +2 -2
|
@@ -0,0 +1,462 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// ---------------------------------------------------------------------------
|
|
3
|
+
// 操作タブの設定の保存「保存 → 本当の結果」(cortex-engine #1911 便 DS-8a-1)
|
|
4
|
+
//
|
|
5
|
+
// 設定の正本が設定の表(`mode: "table"`)の案件だけで使う。画面が持つ設定は、サーバが返した
|
|
6
|
+
// `ConfigView` 1 つ(メモリの変数)だけで、仮置き(pending-ops.ts)とは部品を共有しない
|
|
7
|
+
// (`file` の道は全案件の組み直しが終わった日にまるごと消すので、剥がせる形に分けておく)。
|
|
8
|
+
// 契約の正本は infra の intake-config-contract(`ConfigView`・意図の形・応答の符号)。
|
|
9
|
+
//
|
|
10
|
+
// **ここは純関数だけを置く**(pending-ops.ts と同じ約束)。画面側(クライアントJS)は
|
|
11
|
+
// toString() で実体を流し込んで使い、テストは同じ実体を直接呼ぶ。
|
|
12
|
+
// **クロージャを持たないこと。** ほかの関数を呼んでよいのは、同じく流し込むこのファイルの関数だけ。
|
|
13
|
+
// ⚠ 定数を export しない(CommonJS で `exports.X` に書き換わり、流し込んだ先で落ちる)。
|
|
14
|
+
// ⚠ 正規表現は関数の中に書く(流し込むのは関数の本文だけ)。
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.configModeOf = configModeOf;
|
|
18
|
+
exports.configViewOk = configViewOk;
|
|
19
|
+
exports.configViewOfState = configViewOfState;
|
|
20
|
+
exports.configRevAccepts = configRevAccepts;
|
|
21
|
+
exports.configSameWorkspace = configSameWorkspace;
|
|
22
|
+
exports.configRowKey = configRowKey;
|
|
23
|
+
exports.configSourceRows = configSourceRows;
|
|
24
|
+
exports.configFindSource = configFindSource;
|
|
25
|
+
exports.configNotifyOf = configNotifyOf;
|
|
26
|
+
exports.configIntentOf = configIntentOf;
|
|
27
|
+
exports.configIntentSig = configIntentSig;
|
|
28
|
+
exports.configReqOk = configReqOk;
|
|
29
|
+
exports.configNewReq = configNewReq;
|
|
30
|
+
exports.configClassify = configClassify;
|
|
31
|
+
exports.configUnlinkedNotify = configUnlinkedNotify;
|
|
32
|
+
exports.configDriveFolderId = configDriveFolderId;
|
|
33
|
+
exports.configAddTarget = configAddTarget;
|
|
34
|
+
exports.configSourceUrl = configSourceUrl;
|
|
35
|
+
exports.configBacklogLabel = configBacklogLabel;
|
|
36
|
+
exports.configRemoveConfirmLines = configRemoveConfirmLines;
|
|
37
|
+
exports.configDuplicateLines = configDuplicateLines;
|
|
38
|
+
exports.configNightReadOf = configNightReadOf;
|
|
39
|
+
exports.configCapRowCount = configCapRowCount;
|
|
40
|
+
/** `op=state` / `op=config` の応答の `mode`。**無い・知らない値は `file`**(古い intake は `mode` を返さない) */
|
|
41
|
+
function configModeOf(body) {
|
|
42
|
+
return body && typeof body === "object" && body.mode === "table" ? "table" : "file";
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* 画面が描ける `ConfigView` か(契約の形の検査を、画面が読む欄だけに絞った写し)。
|
|
46
|
+
* 読めない形は受けない(`CONFIG` を壊れた値で置き換えない)。
|
|
47
|
+
*/
|
|
48
|
+
function configViewOk(v) {
|
|
49
|
+
if (!v || typeof v !== "object" || Array.isArray(v))
|
|
50
|
+
return false;
|
|
51
|
+
if (v.mode !== "table")
|
|
52
|
+
return false;
|
|
53
|
+
if (!(Number.isInteger(v.rev) && v.rev >= 0))
|
|
54
|
+
return false;
|
|
55
|
+
if (!Array.isArray(v.sources) || !Array.isArray(v.notify))
|
|
56
|
+
return false;
|
|
57
|
+
for (let i = 0; i < v.sources.length; i++) {
|
|
58
|
+
const r = v.sources[i];
|
|
59
|
+
if (!r || typeof r !== "object" || typeof r.kind !== "string" || typeof r.ref !== "string" || !r.ref)
|
|
60
|
+
return false;
|
|
61
|
+
if (!(Number.isInteger(r.ver) && r.ver >= 1))
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
for (let j = 0; j < v.notify.length; j++) {
|
|
65
|
+
const n = v.notify[j];
|
|
66
|
+
if (!n || typeof n !== "object" || typeof n.ref !== "string" || typeof n.on !== "boolean")
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
/** `op=state` の応答から `ConfigView` を取る。`table` で形が読めるときだけ。ほかは null */
|
|
72
|
+
function configViewOfState(body) {
|
|
73
|
+
if (configModeOf(body) !== "table")
|
|
74
|
+
return null;
|
|
75
|
+
return configViewOk(body.config) ? body.config : null;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* 届いた `rev` を受けてよいか。**持っている `rev` より小さければ捨てる**(保存の直後に、
|
|
79
|
+
* 先に出ていた読み取りの古い応答が後から届く形)。同じ・大きいは受ける。持っていなければ受ける
|
|
80
|
+
*/
|
|
81
|
+
function configRevAccepts(currentRev, incoming) {
|
|
82
|
+
if (!Number.isInteger(incoming))
|
|
83
|
+
return false;
|
|
84
|
+
if (!Number.isInteger(currentRev))
|
|
85
|
+
return true;
|
|
86
|
+
return incoming >= currentRev;
|
|
87
|
+
}
|
|
88
|
+
/** ワークスペースの同一視(未宣言・null・空文字は社内=同じ) */
|
|
89
|
+
function configSameWorkspace(a, b) {
|
|
90
|
+
return (a ? String(a) : "") === (b ? String(b) : "");
|
|
91
|
+
}
|
|
92
|
+
/** 行と保存中の状態を結ぶ鍵(種別・ref・ワークスペース) */
|
|
93
|
+
function configRowKey(kind, ref, workspace) {
|
|
94
|
+
return String(kind) + "|" + String(ref) + "|" + (workspace ? String(workspace) : "");
|
|
95
|
+
}
|
|
96
|
+
/** `ConfigView` の取り込む先のうち、その種別の行(並びはサーバのまま。足し算も引き算もしない) */
|
|
97
|
+
function configSourceRows(view, kind) {
|
|
98
|
+
if (!view || !Array.isArray(view.sources))
|
|
99
|
+
return [];
|
|
100
|
+
return view.sources.filter(function (r) { return r && r.kind === kind; });
|
|
101
|
+
}
|
|
102
|
+
/** その行がいまの `ConfigView` に在るか(在れば行、無ければ null) */
|
|
103
|
+
function configFindSource(view, kind, ref, workspace) {
|
|
104
|
+
const rows = configSourceRows(view, kind);
|
|
105
|
+
for (let i = 0; i < rows.length; i++) {
|
|
106
|
+
if (rows[i].ref === ref && configSameWorkspace(rows[i].workspace, workspace))
|
|
107
|
+
return rows[i];
|
|
108
|
+
}
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
/** 取り込む先の行 ↔ 通知先の行は `ref`(と `workspace`)で結ぶ。無ければ null(=通知先ではない) */
|
|
112
|
+
function configNotifyOf(view, kind, ref, workspace) {
|
|
113
|
+
if (!view || !Array.isArray(view.notify))
|
|
114
|
+
return null;
|
|
115
|
+
for (let i = 0; i < view.notify.length; i++) {
|
|
116
|
+
const n = view.notify[i];
|
|
117
|
+
if (n && n.kind === kind && n.ref === ref && configSameWorkspace(n.workspace, workspace))
|
|
118
|
+
return n;
|
|
119
|
+
}
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* 保存の意図 `{ v:1, kind, action, target, change?, baseRev, req, expectVer? }`。
|
|
124
|
+
* - remove・update の `target` は行の `ref` だけ(`{ ref }`。workspace を付けない。サーバが `ref` から行を引く)。
|
|
125
|
+
* 種別ごとの欄を持つのは add の `target` だけ(呼び手が渡す)
|
|
126
|
+
* - `change` は update だけが持つ(slack: `{ refreshName: true }`・notify: `{ value }`)
|
|
127
|
+
* - `expectVer` は行の前提を持つ操作(remove・update)だけ。add と通知先には付けない(通知先は行の版を持たない)
|
|
128
|
+
*/
|
|
129
|
+
function configIntentOf(kind, action, target, change, baseRev, req, expectVer) {
|
|
130
|
+
const out = { v: 1, kind: kind, action: action, target: target };
|
|
131
|
+
if (action === "update" && change && typeof change === "object")
|
|
132
|
+
out.change = change;
|
|
133
|
+
out.baseRev = baseRev;
|
|
134
|
+
out.req = req;
|
|
135
|
+
if (action !== "add" && kind !== "notify" && Number.isInteger(expectVer) && expectVer >= 1)
|
|
136
|
+
out.expectVer = expectVer;
|
|
137
|
+
return out;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* 意図の署名。**同じ意図の押し直しは同じ `req` を使う**ための鍵。比べるのは kind・action・target・change
|
|
141
|
+
* (target の `confirmDuplicate` は除く)。`baseRev`・`expectVer`・`req` は比べない(押し直しで新しい rev を付けてよい)
|
|
142
|
+
*/
|
|
143
|
+
function configIntentSig(kind, action, target, change) {
|
|
144
|
+
const flat = function (o) {
|
|
145
|
+
const t = o && typeof o === "object" ? o : {};
|
|
146
|
+
return Object.keys(t)
|
|
147
|
+
.filter(function (k) { return k !== "confirmDuplicate"; })
|
|
148
|
+
.sort()
|
|
149
|
+
.map(function (k) { return [k, t[k] === undefined ? null : t[k]]; });
|
|
150
|
+
};
|
|
151
|
+
return JSON.stringify([kind, action, flat(target), flat(change)]);
|
|
152
|
+
}
|
|
153
|
+
/** 冪等キーの形(8〜64 字の英数・_・-) */
|
|
154
|
+
function configReqOk(req) {
|
|
155
|
+
return typeof req === "string" && /^[A-Za-z0-9_-]{8,64}$/.test(req);
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* 新しい冪等キー。時刻(36 進)と乱数の 16 進を繋ぎ、形に合わない字を落として 8〜64 字に収める。
|
|
159
|
+
* 乱数は呼び手が渡す(ここは純関数)
|
|
160
|
+
*/
|
|
161
|
+
function configNewReq(nowMs, randomHex) {
|
|
162
|
+
const head = Math.max(0, Math.floor(Number(nowMs) || 0)).toString(36);
|
|
163
|
+
let s = ("r" + head + "-" + String(randomHex || "")).replace(/[^A-Za-z0-9_-]/g, "");
|
|
164
|
+
if (s.length > 64)
|
|
165
|
+
s = s.slice(0, 64);
|
|
166
|
+
while (s.length < 8)
|
|
167
|
+
s += "0";
|
|
168
|
+
return s;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* 保存の応答の分類。`final` は「結果が確定した(`req` を捨てて、次は新しい値にする)」。
|
|
172
|
+
* 200・409・412・400 ほかの 4xx は確定。5xx と通信失敗だけは確定しない(押し直しは同じ `req`・同じ意図で送る)。
|
|
173
|
+
*
|
|
174
|
+
* | type | 条件 |
|
|
175
|
+
* |---|---|
|
|
176
|
+
* | ok | 200 かつ `changed` が false でない |
|
|
177
|
+
* | noChange | 200 かつ `changed: false`(意図が既に満たされている。失敗にしない) |
|
|
178
|
+
* | revConflict | 412 かつ `code: "REV_CONFLICT"`(前提崩れ。`view` で置き換えて 1 行) |
|
|
179
|
+
* | clientOutdated | 400 かつ `code: "CLIENT_OUTDATED"`(再読み込みを促す) |
|
|
180
|
+
* | duplicate | 409 かつ `confirmable: "duplicate"`・`confirmableKind` がその欄の種別と完全一致 |
|
|
181
|
+
* | reqMismatch | 422 かつ `code: "REQ_MISMATCH"` |
|
|
182
|
+
* | failed | ほかの 4xx(形・権限・取り違え) |
|
|
183
|
+
* | retryable | 5xx・状態の読めない応答 |
|
|
184
|
+
*/
|
|
185
|
+
function configClassify(status, body, formKind) {
|
|
186
|
+
const j = body && typeof body === "object" ? body : {};
|
|
187
|
+
const message = typeof j.message === "string" ? j.message : "";
|
|
188
|
+
const view = configViewOk(j.view) ? j.view : null;
|
|
189
|
+
const st = Number(status) || 0;
|
|
190
|
+
if (st >= 200 && st < 300) {
|
|
191
|
+
return { type: j.changed === false ? "noChange" : "ok", final: true, view: view, message: message };
|
|
192
|
+
}
|
|
193
|
+
if (st === 412 && j.code === "REV_CONFLICT")
|
|
194
|
+
return { type: "revConflict", final: true, view: view, message: message };
|
|
195
|
+
if (st === 400 && j.code === "CLIENT_OUTDATED")
|
|
196
|
+
return { type: "clientOutdated", final: true, view: view, message: message };
|
|
197
|
+
if (st === 409 &&
|
|
198
|
+
j.confirmable === "duplicate" &&
|
|
199
|
+
typeof j.confirmableKind === "string" &&
|
|
200
|
+
!!formKind &&
|
|
201
|
+
j.confirmableKind === formKind) {
|
|
202
|
+
return { type: "duplicate", final: true, view: view, message: message };
|
|
203
|
+
}
|
|
204
|
+
if (st === 422 && j.code === "REQ_MISMATCH")
|
|
205
|
+
return { type: "reqMismatch", final: true, view: null, message: message };
|
|
206
|
+
if (st >= 400 && st < 500)
|
|
207
|
+
return { type: "failed", final: true, view: null, message: message };
|
|
208
|
+
return { type: "retryable", final: false, view: null, message: message };
|
|
209
|
+
}
|
|
210
|
+
/** 取り込み設定に結びついていない宛先(契約の `unlinked: true` の通知先の行)。種別を絞る */
|
|
211
|
+
function configUnlinkedNotify(view, kind) {
|
|
212
|
+
if (!view || !Array.isArray(view.notify))
|
|
213
|
+
return [];
|
|
214
|
+
return view.notify.filter(function (n) { return n && n.kind === kind && n.unlinked === true; });
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Drive のフォルダ ID。フォルダの URL(`/folders/<ID>`・`/u/0/folders/<ID>?…`)か ID そのもの。
|
|
218
|
+
* 読めなければ空文字(呼び手が「フォルダのURLを入力してください」を出す)
|
|
219
|
+
*/
|
|
220
|
+
function configDriveFolderId(value) {
|
|
221
|
+
const v = String(value == null ? "" : value).trim();
|
|
222
|
+
if (!v)
|
|
223
|
+
return "";
|
|
224
|
+
// URL は Drive のホストだけ(ほかのサイトの /folders/ を ID として送らない)
|
|
225
|
+
const m = /^https:\/\/drive\.google\.com\/(?:[^?#]*\/)?folders\/([A-Za-z0-9_-]+)/.exec(v);
|
|
226
|
+
if (m)
|
|
227
|
+
return m[1];
|
|
228
|
+
return /^[A-Za-z0-9_-]{10,}$/.test(v) ? v : "";
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* 追加の意図の `target`(種別ごとの欄は add だけが持つ・契約 §3)。読めなければ null。
|
|
232
|
+
* 名前(メモ)は空なら付けない。Slack は別の口(ワークスペースの実測)なのでここでは扱わない
|
|
233
|
+
*/
|
|
234
|
+
function configAddTarget(kind, value, name) {
|
|
235
|
+
const v = String(value == null ? "" : value).trim();
|
|
236
|
+
const nm = String(name == null ? "" : name).trim();
|
|
237
|
+
if (!v)
|
|
238
|
+
return null;
|
|
239
|
+
let t = null;
|
|
240
|
+
if (kind === "google-chat")
|
|
241
|
+
t = { url: v };
|
|
242
|
+
else if (kind === "github")
|
|
243
|
+
t = { repo: v };
|
|
244
|
+
else if (kind === "figma")
|
|
245
|
+
t = { url: v };
|
|
246
|
+
else if (kind === "backlog")
|
|
247
|
+
return { url: v };
|
|
248
|
+
else if (kind === "drive") {
|
|
249
|
+
const id = configDriveFolderId(v);
|
|
250
|
+
if (!id)
|
|
251
|
+
return null;
|
|
252
|
+
t = { folderId: id };
|
|
253
|
+
}
|
|
254
|
+
if (!t)
|
|
255
|
+
return null;
|
|
256
|
+
if (nm)
|
|
257
|
+
t.name = nm;
|
|
258
|
+
return t;
|
|
259
|
+
}
|
|
260
|
+
/** 行の名前のリンク先。サーバが `url` を返していればそれ、無ければ `ref` から組む(組めなければ空文字) */
|
|
261
|
+
function configSourceUrl(kind, row) {
|
|
262
|
+
if (!row || typeof row !== "object")
|
|
263
|
+
return "";
|
|
264
|
+
if (typeof row.url === "string" && /^https:\/\//.test(row.url))
|
|
265
|
+
return row.url;
|
|
266
|
+
const ref = String(row.ref || "");
|
|
267
|
+
if (!ref)
|
|
268
|
+
return "";
|
|
269
|
+
if (kind === "github")
|
|
270
|
+
return "https://github.com/" + ref;
|
|
271
|
+
if (kind === "figma")
|
|
272
|
+
return "https://www.figma.com/design/" + ref;
|
|
273
|
+
if (kind === "drive")
|
|
274
|
+
return "https://drive.google.com/drive/folders/" + ref;
|
|
275
|
+
if (kind === "backlog") {
|
|
276
|
+
const i = ref.indexOf("/");
|
|
277
|
+
return i > 0 ? "https://" + ref.slice(0, i) + "/projects/" + ref.slice(i + 1) : "";
|
|
278
|
+
}
|
|
279
|
+
return "";
|
|
280
|
+
}
|
|
281
|
+
/** Backlog の行の見出し(`ref` は `domain/projectKey`) */
|
|
282
|
+
function configBacklogLabel(ref) {
|
|
283
|
+
const s = String(ref || "");
|
|
284
|
+
const i = s.indexOf("/");
|
|
285
|
+
return i > 0 ? s.slice(0, i) + " / " + s.slice(i + 1) : s;
|
|
286
|
+
}
|
|
287
|
+
/** 「外す」の確認文(file の道の確認文と同じ約束・`last` は押した時点で最後の 1 件か) */
|
|
288
|
+
function configRemoveConfirmLines(kind, label, last) {
|
|
289
|
+
const l = String(label || "");
|
|
290
|
+
if (kind === "google-chat") {
|
|
291
|
+
return [
|
|
292
|
+
"「" + l + "」を、読み取り対象から外します。",
|
|
293
|
+
"",
|
|
294
|
+
"外すと、次の晩から読まなくなります。すでに記憶に入った内容は残ります。",
|
|
295
|
+
"※ 通知先にしている場合は、先に「通知を止める」を押してください(解除しても通知先は変わりません)。",
|
|
296
|
+
"",
|
|
297
|
+
"解除しますか?",
|
|
298
|
+
];
|
|
299
|
+
}
|
|
300
|
+
if (kind === "github") {
|
|
301
|
+
return [
|
|
302
|
+
"「" + l + "」を、読み取り対象の登録から削除します。",
|
|
303
|
+
"",
|
|
304
|
+
"次回の夜間の処理から、このリポジトリの Issues と PR の活動は記録されません。",
|
|
305
|
+
"",
|
|
306
|
+
"すでに記録した活動は消えません。",
|
|
307
|
+
"",
|
|
308
|
+
"削除しますか?",
|
|
309
|
+
];
|
|
310
|
+
}
|
|
311
|
+
if (kind === "figma") {
|
|
312
|
+
const lines = [
|
|
313
|
+
"「" + l + "」をデザイン同期の対象から外します。",
|
|
314
|
+
"",
|
|
315
|
+
"次回の同期から、このファイルのコメントは取り込まれなくなります。",
|
|
316
|
+
];
|
|
317
|
+
if (last) {
|
|
318
|
+
lines.push("", "これが最後の1件です。外すとデザイン同期は何もしなくなりますが、すでに取り込んだ分(デザイン/ の下)はリポジトリに残ります(不要なら手で削除してください)。");
|
|
319
|
+
}
|
|
320
|
+
lines.push("", "外しますか?");
|
|
321
|
+
return lines;
|
|
322
|
+
}
|
|
323
|
+
if (kind === "drive") {
|
|
324
|
+
const lines = [
|
|
325
|
+
"このフォルダを資料の取り込み対象から外します。",
|
|
326
|
+
"",
|
|
327
|
+
"次回の同期から、このフォルダの中身は取り込まれなくなります。",
|
|
328
|
+
"すでに取り込まれた資料はリポジトリに残ります(不要なら手で削除してください)。",
|
|
329
|
+
];
|
|
330
|
+
if (last)
|
|
331
|
+
lines.push("", "これが最後の1件です。外すと資料の自動取り込みは止まります。");
|
|
332
|
+
lines.push("", l, "", "外しますか?");
|
|
333
|
+
return lines;
|
|
334
|
+
}
|
|
335
|
+
if (kind === "backlog") {
|
|
336
|
+
const lines = ["「" + l + "」を同期の対象から外します。", ""];
|
|
337
|
+
if (last) {
|
|
338
|
+
lines.push("これが最後の接続先です。この接続先を外すと課題の同期が止まります。よろしいですか?");
|
|
339
|
+
}
|
|
340
|
+
else {
|
|
341
|
+
lines.push("次回の同期から、このプロジェクトの課題・ドキュメント・Wiki は取り込まれなくなります。", "取り込み済みの内容はリポジトリに残ります(不要なら手で削除してください)。", "", "外しますか?");
|
|
342
|
+
}
|
|
343
|
+
return lines;
|
|
344
|
+
}
|
|
345
|
+
return ["「" + l + "」を外します。", "", "外しますか?"];
|
|
346
|
+
}
|
|
347
|
+
/** 「他のプロジェクトでも使われている」確認文(409 duplicate のとき)。Slack は render.ts の既存の文 */
|
|
348
|
+
function configDuplicateLines(kind, value) {
|
|
349
|
+
const v = String(value == null ? "" : value);
|
|
350
|
+
if (kind === "github") {
|
|
351
|
+
return [
|
|
352
|
+
"このリポジトリは、別のプロジェクトでも追加されています。",
|
|
353
|
+
"",
|
|
354
|
+
"このまま追加すると、同じリポジトリの内容が、このプロジェクトの毎晩の記録にも入ります。",
|
|
355
|
+
"",
|
|
356
|
+
"別のリポジトリと取り違えていないか、入れた値を確かめてください:",
|
|
357
|
+
v,
|
|
358
|
+
"",
|
|
359
|
+
"追加しますか?",
|
|
360
|
+
];
|
|
361
|
+
}
|
|
362
|
+
if (kind === "figma") {
|
|
363
|
+
return [
|
|
364
|
+
"このFigmaファイルは、別のプロジェクトでも追加されています。",
|
|
365
|
+
"",
|
|
366
|
+
"このまま追加すると、同じファイルのデザインが、このプロジェクトのリポジトリにも取り込まれます。",
|
|
367
|
+
"",
|
|
368
|
+
"別のファイルと取り違えていないか、貼ったURLを確かめてください:",
|
|
369
|
+
v,
|
|
370
|
+
"",
|
|
371
|
+
"追加しますか?",
|
|
372
|
+
];
|
|
373
|
+
}
|
|
374
|
+
if (kind === "backlog") {
|
|
375
|
+
return [
|
|
376
|
+
"同じプロジェクトキーが、ほかのプロジェクトでも使われています。",
|
|
377
|
+
"",
|
|
378
|
+
"このまま追加すると、同じ課題・ドキュメント・Wiki が、このプロジェクトのリポジトリにも取り込まれます。",
|
|
379
|
+
"",
|
|
380
|
+
"別のBacklogプロジェクトと取り違えていないか、貼ったURLを確かめてください:",
|
|
381
|
+
v,
|
|
382
|
+
"",
|
|
383
|
+
"追加しますか?",
|
|
384
|
+
];
|
|
385
|
+
}
|
|
386
|
+
return [
|
|
387
|
+
"別のプロジェクトでも追加されています。",
|
|
388
|
+
"",
|
|
389
|
+
"別のものと取り違えていないか、貼った値を確かめてください:",
|
|
390
|
+
v,
|
|
391
|
+
"",
|
|
392
|
+
"追加しますか?",
|
|
393
|
+
];
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* 行に出す「その晩の更新で読めたか」の材料(便 DS-8c)。接続状態の行(`stateNow`)のうち、**夜の便りが書いた
|
|
397
|
+
* `ingested` の行**(`by: "bronze"`・記憶 v2 の夜の結果)だけを、その行の接続先に当てて 1 件返す。無ければ null。
|
|
398
|
+
* 返りは `{ status: "ok" | "failed", at }`。**`skipped`(読まなかった)と行が無いときは何も言わない**(測っていないことを断言しない)。
|
|
399
|
+
* 取り込み状態・その場の同期の結果(ミラー由来の書き手の行)は材料にしない。同じ接続先に複数あれば時刻の新しい行
|
|
400
|
+
*/
|
|
401
|
+
function configNightReadOf(rows, kind, ref, workspace) {
|
|
402
|
+
if (!Array.isArray(rows))
|
|
403
|
+
return null;
|
|
404
|
+
const caps = { slack: "chat", "google-chat": "google-chat", github: "dev", figma: "design", drive: "materials", backlog: "issues" };
|
|
405
|
+
const cap = caps[kind];
|
|
406
|
+
const want = String(ref == null ? "" : ref).trim();
|
|
407
|
+
if (!cap || !want)
|
|
408
|
+
return null;
|
|
409
|
+
// ref の畳み方: Backlog は書き手と同じ(ドメイン小文字 / キー大文字)。GitHub は書き手の畳み方が生値(大小まちまち)なので、画面の照合だけ小文字に寄せる
|
|
410
|
+
const fold = function (s) {
|
|
411
|
+
const t = String(s == null ? "" : s).trim();
|
|
412
|
+
if (kind === "github")
|
|
413
|
+
return t.toLowerCase();
|
|
414
|
+
if (kind === "backlog") {
|
|
415
|
+
const i = t.indexOf("/");
|
|
416
|
+
return i > 0 ? t.slice(0, i).toLowerCase() + "/" + t.slice(i + 1).toUpperCase() : t;
|
|
417
|
+
}
|
|
418
|
+
return t;
|
|
419
|
+
};
|
|
420
|
+
let best = null;
|
|
421
|
+
for (let i = 0; i < rows.length; i++) {
|
|
422
|
+
const r = rows[i];
|
|
423
|
+
if (!r || typeof r !== "object" || r.cap !== cap || r.question !== "ingested" || r.by !== "bronze")
|
|
424
|
+
continue;
|
|
425
|
+
if (fold(r.ref) !== fold(want))
|
|
426
|
+
continue;
|
|
427
|
+
if (kind === "slack" && !configSameWorkspace(r.workspace, workspace))
|
|
428
|
+
continue;
|
|
429
|
+
if (r.status !== "ok" && r.status !== "failed")
|
|
430
|
+
continue;
|
|
431
|
+
const at = typeof r.at === "string" ? r.at : "";
|
|
432
|
+
// 新しさは時刻として比べる(秒精度とミリ秒ありが混ざると文字列の辞書順は逆転する)
|
|
433
|
+
const ms = Date.parse(at);
|
|
434
|
+
if (!best || (Number.isFinite(ms) ? ms : -1) > best.ms)
|
|
435
|
+
best = { status: r.status, at: at, ms: Number.isFinite(ms) ? ms : -1 };
|
|
436
|
+
}
|
|
437
|
+
return best ? { status: best.status, at: best.at } : null;
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* 能力(課題管理・共有資料…)に、いまの `ConfigView` で登録された行が何本あるか(便 DS-8d・tools #1361)。
|
|
441
|
+
* 「未接続」「未使用」は**設定の事実(登録の有無)**から言う——夜に焼いた表の値で言うと、いま足した行が出ているのに
|
|
442
|
+
* 「接続先が設定されていません」と並ぶ。`adapter` はチャットの 2 タイル(slack / google-chat)の見分け。
|
|
443
|
+
* 能力が `ConfigView` の種別に当たらない(会議など)・`view` が読めないときは null(言えない)。
|
|
444
|
+
* **クロージャを持たない純関数に保つこと**(`.toString()` でクライアントへ渡す)
|
|
445
|
+
*/
|
|
446
|
+
function configCapRowCount(view, cap, adapter) {
|
|
447
|
+
if (!view || !Array.isArray(view.sources))
|
|
448
|
+
return null;
|
|
449
|
+
const kinds = { 課題管理: ["backlog"], 共有資料: ["drive"], デザイン: ["figma"], 開発: ["github"], チャット: ["slack", "google-chat"] };
|
|
450
|
+
let want = kinds[cap];
|
|
451
|
+
if (!want)
|
|
452
|
+
return null;
|
|
453
|
+
if (cap === "チャット" && (adapter === "slack" || adapter === "google-chat"))
|
|
454
|
+
want = [adapter];
|
|
455
|
+
let n = 0;
|
|
456
|
+
for (let i = 0; i < view.sources.length; i++) {
|
|
457
|
+
const r = view.sources[i];
|
|
458
|
+
if (r && want.indexOf(r.kind) >= 0)
|
|
459
|
+
n++;
|
|
460
|
+
}
|
|
461
|
+
return n;
|
|
462
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.REBUILT_MARKER = void 0;
|
|
37
|
+
exports.configModeHint = configModeHint;
|
|
38
|
+
/**
|
|
39
|
+
* 設定の置き場のヒント(cortex-engine #1911 便 DS-8b)。組み直した案件(木に `Memory/Home.md` がある)なら `table`。
|
|
40
|
+
*
|
|
41
|
+
* 見分けの正本はサーバ(Intake の `isRebuilt`・同じ `Memory/Home.md` の有無)。ここで焼くのは**最初の描画のためのヒントだけ**で、
|
|
42
|
+
* `op=state` の応答の `mode` が常に勝つ。組み直していない案件は何も返さない(`SiteData` に欄ごと出さない=出力が 1 バイトも変わらない)。
|
|
43
|
+
*/
|
|
44
|
+
const node_fs_1 = require("node:fs");
|
|
45
|
+
const path = __importStar(require("node:path"));
|
|
46
|
+
/** 組み直した案件の印(Intake の `isRebuilt` が GET する木のファイル) */
|
|
47
|
+
exports.REBUILT_MARKER = "Memory/Home.md";
|
|
48
|
+
/** 木にマーカーが(ファイルとして)あれば `"table"`。無い・読めない → `undefined`(`file` のまま) */
|
|
49
|
+
async function configModeHint(repoRoot) {
|
|
50
|
+
try {
|
|
51
|
+
const st = await node_fs_1.promises.stat(path.join(repoRoot, exports.REBUILT_MARKER));
|
|
52
|
+
return st.isFile() ? "table" : undefined;
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
}
|
package/dist/confirmed.js
CHANGED
|
@@ -37,14 +37,14 @@ exports.confirmedStatusesOf = confirmedStatusesOf;
|
|
|
37
37
|
exports.confirmedStatusChecked = confirmedStatusChecked;
|
|
38
38
|
exports.machineOwned = machineOwned;
|
|
39
39
|
exports.humanOwned = humanOwned;
|
|
40
|
-
/** path から区画を引く(`Cortex
|
|
40
|
+
/** path から区画を引く(`Cortex/<区画>/`・`Memory/<区画>/`(組み直した案件)を含むか。先頭か `/` の直後。分からなければ `null`) */
|
|
41
41
|
function sectionOfPath(p) {
|
|
42
42
|
const s = typeof p === "string" ? p.replace(/\\/g, "/") : "";
|
|
43
43
|
// 🔴 順番は engine の `Object.keys(CONFIRMED_STATUSES)` と同じ(決定・ルール・用語・関係者)
|
|
44
44
|
const sections = ["Decisions", "Rules", "Glossary", "Members"];
|
|
45
45
|
for (let i = 0; i < sections.length; i++) {
|
|
46
46
|
const section = sections[i];
|
|
47
|
-
if (s.indexOf("Cortex/" + section + "/") === 0 || s.indexOf("/Cortex/" + section + "/") !== -1)
|
|
47
|
+
if (s.indexOf("Cortex/" + section + "/") === 0 || s.indexOf("/Cortex/" + section + "/") !== -1 || s.indexOf("Memory/" + section + "/") === 0 || s.indexOf("/Memory/" + section + "/") !== -1)
|
|
48
48
|
return section;
|
|
49
49
|
}
|
|
50
50
|
return null;
|