@helpfeel/cosense-cli 1.17.0 → 1.17.1
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@helpfeel/cosense-cli",
|
|
3
|
-
"version": "1.17.
|
|
3
|
+
"version": "1.17.1",
|
|
4
4
|
"description": "Cosense (旧Scrapbox) のページを読み・調べ・編集するAgent Skillのハーネス",
|
|
5
5
|
"homepage": "https://github.com/helpfeel/cosense-cli",
|
|
6
6
|
"license": "MIT",
|
|
@@ -27,10 +27,10 @@
|
|
|
27
27
|
"tsx": "4.23.15"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
|
-
"@types/node": "24.19.
|
|
30
|
+
"@types/node": "24.19.2",
|
|
31
31
|
"npm-run-all": "4.1.5",
|
|
32
|
-
"oxfmt": "0.
|
|
33
|
-
"oxlint": "1.
|
|
32
|
+
"oxfmt": "0.72.0",
|
|
33
|
+
"oxlint": "1.87.0",
|
|
34
34
|
"typescript": "7.0.2"
|
|
35
35
|
},
|
|
36
36
|
"engines": {
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
serviceAccountSuffix,
|
|
11
11
|
type UserMap
|
|
12
12
|
} from '../lib/resolveUsers.ts';
|
|
13
|
-
import { resolveCredential } from '../lib/settings.ts';
|
|
13
|
+
import { resolveCredential, type Credential } from '../lib/settings.ts';
|
|
14
14
|
|
|
15
15
|
export const browsePageChangesSummary =
|
|
16
16
|
'ページの編集履歴(commit)をpageId起点で取得し、誰がいつ何をどう変えたかを自然言語で説明する。タイトル変更(リネーム)も検出する';
|
|
@@ -58,6 +58,7 @@ interface Change {
|
|
|
58
58
|
|
|
59
59
|
interface Commit {
|
|
60
60
|
id?: string;
|
|
61
|
+
parentId?: string | null;
|
|
61
62
|
changes?: Change[];
|
|
62
63
|
userId?: string;
|
|
63
64
|
created?: number;
|
|
@@ -65,6 +66,7 @@ interface Commit {
|
|
|
65
66
|
|
|
66
67
|
interface CommitsResponse {
|
|
67
68
|
commits?: Commit[];
|
|
69
|
+
hasMore?: boolean;
|
|
68
70
|
}
|
|
69
71
|
|
|
70
72
|
interface ParsedArgs {
|
|
@@ -116,6 +118,46 @@ const resolveUserName = (
|
|
|
116
118
|
|
|
117
119
|
const quote = (text: string | undefined): string => `「${text ?? ''}」`;
|
|
118
120
|
|
|
121
|
+
// 1回の応答は件数と大きさで打ち切られ、続きがある時はhasMoreが真になる
|
|
122
|
+
const fetchAllCommits = async (
|
|
123
|
+
apiUrl: string,
|
|
124
|
+
since: string | undefined,
|
|
125
|
+
credential: Credential | undefined
|
|
126
|
+
): Promise<Commit[]> => {
|
|
127
|
+
const commits: Commit[] = [];
|
|
128
|
+
let head = since;
|
|
129
|
+
let continuing = false;
|
|
130
|
+
const requestedHeads = new Set<string>(since ? [since] : []);
|
|
131
|
+
while (true) {
|
|
132
|
+
const url = head ? `${apiUrl}?head=${encodeURIComponent(head)}` : apiUrl;
|
|
133
|
+
const data = (await requestJson(url, { credential })) as CommitsResponse;
|
|
134
|
+
const batch = data.commits ?? [];
|
|
135
|
+
const first = batch[0];
|
|
136
|
+
// 続きが空だと、取れた分を完全な結果として出してしまうので止める。hasMoreは次の要求が空でない事まで保証しない
|
|
137
|
+
if (continuing && !first) {
|
|
138
|
+
throw new Error(`no commits follow head "${head}"`);
|
|
139
|
+
}
|
|
140
|
+
// headに続かない応答を辿ると、同じ応答を取り続けて終わらないので止める
|
|
141
|
+
if (head && first && first.parentId !== head) {
|
|
142
|
+
throw new Error(`commits do not follow head "${head}"`);
|
|
143
|
+
}
|
|
144
|
+
// spreadで足すと、1回の応答が大きい時に引数の上限を超える
|
|
145
|
+
for (const commit of batch) commits.push(commit);
|
|
146
|
+
if (!data.hasMore) return commits;
|
|
147
|
+
const next = batch.at(-1)?.id;
|
|
148
|
+
if (!next) {
|
|
149
|
+
throw new Error('hasMore is true but the last commit has no id');
|
|
150
|
+
}
|
|
151
|
+
// 親子が循環した応答はparentIdの確認を通るので、同じheadを2度要求しない事で止める
|
|
152
|
+
if (requestedHeads.has(next)) {
|
|
153
|
+
throw new Error(`commit "${next}" appears again as head`);
|
|
154
|
+
}
|
|
155
|
+
requestedHeads.add(next);
|
|
156
|
+
head = next;
|
|
157
|
+
continuing = true;
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
|
|
119
161
|
// 時系列の変更イベント。同一行への連続updateは1件に畳む
|
|
120
162
|
type ChangeEvent =
|
|
121
163
|
| { kind: 'title'; userIds: string[]; created?: number; title: string }
|
|
@@ -205,18 +247,14 @@ export const browsePageChanges = async (args: string[]): Promise<void> => {
|
|
|
205
247
|
const { projectUrl, pageId, since } = parseArgs(args);
|
|
206
248
|
const { origin, projectName } = parseProjectUrlStrict(projectUrl);
|
|
207
249
|
|
|
208
|
-
|
|
209
|
-
if (since) {
|
|
210
|
-
apiUrl += `?head=${encodeURIComponent(since)}`;
|
|
211
|
-
}
|
|
250
|
+
const apiUrl = `${origin}/api/commits/${projectName}/${pageId}`;
|
|
212
251
|
const credential = resolveCredential(origin, projectName);
|
|
213
252
|
|
|
214
|
-
const [
|
|
215
|
-
|
|
253
|
+
const [commits, userMap] = await Promise.all([
|
|
254
|
+
fetchAllCommits(apiUrl, since, credential),
|
|
216
255
|
fetchUserMap(origin, projectName)
|
|
217
256
|
]);
|
|
218
257
|
|
|
219
|
-
const commits = data.commits ?? [];
|
|
220
258
|
const events = buildEvents(commits);
|
|
221
259
|
|
|
222
260
|
const titleEvents = events.filter(e => e.kind === 'title');
|