@laitszkin/apollo-toolkit 3.13.2 → 3.14.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/AGENTS.md +7 -7
- package/CHANGELOG.md +36 -0
- package/CLAUDE.md +8 -8
- package/analyse-app-logs/SKILL.md +3 -3
- package/bin/apollo-toolkit.ts +7 -0
- package/codex/codex-memory-manager/SKILL.md +2 -2
- package/codex/learn-skill-from-conversations/SKILL.md +3 -3
- package/dist/bin/apollo-toolkit.d.ts +2 -0
- package/dist/bin/apollo-toolkit.js +7 -0
- package/dist/lib/cli.d.ts +41 -0
- package/dist/lib/cli.js +655 -0
- package/dist/lib/installer.d.ts +59 -0
- package/dist/lib/installer.js +404 -0
- package/dist/lib/tool-runner.d.ts +19 -0
- package/dist/lib/tool-runner.js +536 -0
- package/dist/lib/tools/architecture.d.ts +2 -0
- package/dist/lib/tools/architecture.js +23 -0
- package/dist/lib/tools/create-specs.d.ts +2 -0
- package/dist/lib/tools/create-specs.js +175 -0
- package/dist/lib/tools/docs-to-voice.d.ts +2 -0
- package/dist/lib/tools/docs-to-voice.js +705 -0
- package/dist/lib/tools/enforce-video-aspect-ratio.d.ts +2 -0
- package/dist/lib/tools/enforce-video-aspect-ratio.js +312 -0
- package/dist/lib/tools/extract-conversations.d.ts +2 -0
- package/dist/lib/tools/extract-conversations.js +105 -0
- package/dist/lib/tools/extract-pdf-text.d.ts +2 -0
- package/dist/lib/tools/extract-pdf-text.js +92 -0
- package/dist/lib/tools/filter-logs.d.ts +2 -0
- package/dist/lib/tools/filter-logs.js +94 -0
- package/dist/lib/tools/find-github-issues.d.ts +2 -0
- package/dist/lib/tools/find-github-issues.js +176 -0
- package/dist/lib/tools/generate-storyboard-images.d.ts +2 -0
- package/dist/lib/tools/generate-storyboard-images.js +419 -0
- package/dist/lib/tools/log-cli-utils.d.ts +35 -0
- package/dist/lib/tools/log-cli-utils.js +233 -0
- package/dist/lib/tools/open-github-issue.d.ts +2 -0
- package/dist/lib/tools/open-github-issue.js +750 -0
- package/dist/lib/tools/read-github-issue.d.ts +2 -0
- package/dist/lib/tools/read-github-issue.js +134 -0
- package/dist/lib/tools/render-error-book.d.ts +2 -0
- package/dist/lib/tools/render-error-book.js +265 -0
- package/dist/lib/tools/render-katex.d.ts +2 -0
- package/dist/lib/tools/render-katex.js +294 -0
- package/dist/lib/tools/review-threads.d.ts +2 -0
- package/dist/lib/tools/review-threads.js +491 -0
- package/dist/lib/tools/search-logs.d.ts +2 -0
- package/dist/lib/tools/search-logs.js +164 -0
- package/dist/lib/tools/sync-memory-index.d.ts +2 -0
- package/dist/lib/tools/sync-memory-index.js +113 -0
- package/dist/lib/tools/validate-openai-agent-config.d.ts +2 -0
- package/dist/lib/tools/validate-openai-agent-config.js +190 -0
- package/dist/lib/tools/validate-skill-frontmatter.d.ts +2 -0
- package/dist/lib/tools/validate-skill-frontmatter.js +118 -0
- package/dist/lib/types.d.ts +82 -0
- package/dist/lib/types.js +2 -0
- package/dist/lib/updater.d.ts +34 -0
- package/dist/lib/updater.js +112 -0
- package/dist/lib/utils/format.d.ts +2 -0
- package/dist/lib/utils/format.js +6 -0
- package/dist/lib/utils/terminal.d.ts +12 -0
- package/dist/lib/utils/terminal.js +26 -0
- package/docs-to-voice/SKILL.md +0 -1
- package/generate-spec/SKILL.md +1 -1
- package/katex/SKILL.md +1 -2
- package/lib/cli.ts +780 -0
- package/lib/installer.ts +466 -0
- package/lib/tool-runner.ts +561 -0
- package/lib/tools/architecture.ts +20 -0
- package/lib/tools/create-specs.ts +204 -0
- package/lib/tools/docs-to-voice.ts +799 -0
- package/lib/tools/enforce-video-aspect-ratio.ts +368 -0
- package/lib/tools/extract-conversations.ts +114 -0
- package/lib/tools/extract-pdf-text.ts +99 -0
- package/lib/tools/filter-logs.ts +118 -0
- package/lib/tools/find-github-issues.ts +211 -0
- package/lib/tools/generate-storyboard-images.ts +455 -0
- package/lib/tools/log-cli-utils.ts +262 -0
- package/lib/tools/open-github-issue.ts +930 -0
- package/lib/tools/read-github-issue.ts +179 -0
- package/lib/tools/render-error-book.ts +300 -0
- package/lib/tools/render-katex.ts +325 -0
- package/lib/tools/review-threads.ts +590 -0
- package/lib/tools/search-logs.ts +200 -0
- package/lib/tools/sync-memory-index.ts +114 -0
- package/lib/tools/validate-openai-agent-config.ts +213 -0
- package/lib/tools/validate-skill-frontmatter.ts +124 -0
- package/lib/types.ts +90 -0
- package/lib/updater.ts +165 -0
- package/lib/utils/format.ts +7 -0
- package/lib/utils/terminal.ts +22 -0
- package/open-github-issue/SKILL.md +2 -2
- package/optimise-skill/SKILL.md +1 -1
- package/package.json +13 -4
- package/resources/project-architecture/assets/architecture.css +764 -0
- package/resources/project-architecture/assets/viewer.client.js +144 -0
- package/resources/project-architecture/index.html +42 -0
- package/review-spec-related-changes/SKILL.md +1 -1
- package/solve-issues-found-during-review/SKILL.md +2 -1
- package/tsconfig.json +28 -0
- package/analyse-app-logs/scripts/__pycache__/filter_logs_by_time.cpython-312.pyc +0 -0
- package/analyse-app-logs/scripts/__pycache__/log_cli_utils.cpython-312.pyc +0 -0
- package/analyse-app-logs/scripts/__pycache__/search_logs.cpython-312.pyc +0 -0
- package/analyse-app-logs/scripts/filter_logs_by_time.py +0 -64
- package/analyse-app-logs/scripts/log_cli_utils.py +0 -112
- package/analyse-app-logs/scripts/search_logs.py +0 -137
- package/analyse-app-logs/tests/test_filter_logs_by_time.py +0 -95
- package/analyse-app-logs/tests/test_search_logs.py +0 -100
- package/codex/codex-memory-manager/scripts/extract_recent_conversations.py +0 -369
- package/codex/codex-memory-manager/scripts/sync_memory_index.py +0 -130
- package/codex/codex-memory-manager/tests/test_extract_recent_conversations.py +0 -177
- package/codex/codex-memory-manager/tests/test_memory_template.py +0 -37
- package/codex/codex-memory-manager/tests/test_sync_memory_index.py +0 -84
- package/codex/learn-skill-from-conversations/scripts/extract_recent_conversations.py +0 -369
- package/codex/learn-skill-from-conversations/tests/test_extract_recent_conversations.py +0 -177
- package/docs-to-voice/scripts/__pycache__/docs_to_voice.cpython-312.pyc +0 -0
- package/docs-to-voice/scripts/docs_to_voice.py +0 -1385
- package/docs-to-voice/scripts/docs_to_voice.sh +0 -11
- package/docs-to-voice/tests/test_docs_to_voice_api_max_chars.py +0 -210
- package/docs-to-voice/tests/test_docs_to_voice_sentence_timeline.py +0 -115
- package/docs-to-voice/tests/test_docs_to_voice_settings.py +0 -43
- package/docs-to-voice/tests/test_docs_to_voice_shell_wrapper.py +0 -51
- package/docs-to-voice/tests/test_docs_to_voice_speech_rate.py +0 -57
- package/generate-spec/scripts/__pycache__/create-specscpython-312.pyc +0 -0
- package/generate-spec/scripts/create-specs +0 -215
- package/generate-spec/tests/test_create_specs.py +0 -200
- package/init-project-html/scripts/architecture-bootstrap-render.js +0 -16
- package/init-project-html/scripts/architecture.js +0 -296
- package/katex/scripts/__pycache__/render_katex.cpython-312.pyc +0 -0
- package/katex/scripts/render_katex.py +0 -247
- package/katex/scripts/render_katex.sh +0 -11
- package/katex/tests/test_render_katex.py +0 -174
- package/learning-error-book/scripts/render_error_book_json_to_pdf.py +0 -590
- package/learning-error-book/tests/test_render_error_book_json_to_pdf.py +0 -134
- package/open-github-issue/scripts/__pycache__/open_github_issue.cpython-312.pyc +0 -0
- package/open-github-issue/scripts/open_github_issue.py +0 -705
- package/open-github-issue/tests/test_open_github_issue.py +0 -381
- package/openai-text-to-image-storyboard/scripts/generate_storyboard_images.py +0 -763
- package/openai-text-to-image-storyboard/tests/test_generate_storyboard_images.py +0 -177
- package/read-github-issue/scripts/__pycache__/find_issues.cpython-312.pyc +0 -0
- package/read-github-issue/scripts/__pycache__/read_issue.cpython-312.pyc +0 -0
- package/read-github-issue/scripts/find_issues.py +0 -148
- package/read-github-issue/scripts/read_issue.py +0 -108
- package/read-github-issue/tests/test_find_issues.py +0 -127
- package/read-github-issue/tests/test_read_issue.py +0 -109
- package/resolve-review-comments/scripts/__pycache__/review_threads.cpython-312.pyc +0 -0
- package/resolve-review-comments/scripts/review_threads.py +0 -425
- package/resolve-review-comments/tests/test_review_threads.py +0 -74
- package/scripts/validate_openai_agent_config.py +0 -209
- package/scripts/validate_skill_frontmatter.py +0 -131
- package/text-to-short-video/scripts/__pycache__/enforce_video_aspect_ratio.cpython-312.pyc +0 -0
- package/text-to-short-video/scripts/enforce_video_aspect_ratio.py +0 -350
- package/text-to-short-video/tests/test_enforce_video_aspect_ratio.py +0 -194
- package/weekly-financial-event-report/scripts/extract_pdf_text_pdfkit.swift +0 -99
- package/weekly-financial-event-report/tests/test_extract_pdf_text_pdfkit.py +0 -64
|
@@ -0,0 +1,491 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.reviewThreadsHandler = reviewThreadsHandler;
|
|
4
|
+
// @ts-nocheck
|
|
5
|
+
const node_child_process_1 = require("node:child_process");
|
|
6
|
+
const node_fs_1 = require("node:fs");
|
|
7
|
+
const LIST_QUERY = `
|
|
8
|
+
query($owner: String!, $name: String!, $number: Int!, $after: String) {
|
|
9
|
+
repository(owner: $owner, name: $name) {
|
|
10
|
+
pullRequest(number: $number) {
|
|
11
|
+
reviewThreads(first: 100, after: $after) {
|
|
12
|
+
nodes {
|
|
13
|
+
id
|
|
14
|
+
isResolved
|
|
15
|
+
isOutdated
|
|
16
|
+
path
|
|
17
|
+
line
|
|
18
|
+
startLine
|
|
19
|
+
comments(first: 20) {
|
|
20
|
+
nodes {
|
|
21
|
+
id
|
|
22
|
+
url
|
|
23
|
+
body
|
|
24
|
+
author {
|
|
25
|
+
login
|
|
26
|
+
}
|
|
27
|
+
createdAt
|
|
28
|
+
path
|
|
29
|
+
line
|
|
30
|
+
outdated
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
pageInfo {
|
|
35
|
+
hasNextPage
|
|
36
|
+
endCursor
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
`;
|
|
43
|
+
const RESOLVE_MUTATION = `
|
|
44
|
+
mutation($threadId: ID!) {
|
|
45
|
+
resolveReviewThread(input: {threadId: $threadId}) {
|
|
46
|
+
thread {
|
|
47
|
+
id
|
|
48
|
+
isResolved
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
`;
|
|
53
|
+
function parseArgs(argv) {
|
|
54
|
+
const args = {
|
|
55
|
+
command: '',
|
|
56
|
+
repo: null,
|
|
57
|
+
pr: null,
|
|
58
|
+
state: 'unresolved',
|
|
59
|
+
output: 'table',
|
|
60
|
+
threadId: [],
|
|
61
|
+
threadIdFile: null,
|
|
62
|
+
allUnresolved: false,
|
|
63
|
+
dryRun: false,
|
|
64
|
+
};
|
|
65
|
+
// First argument is the subcommand (list/resolve)
|
|
66
|
+
let i = 0;
|
|
67
|
+
if (i < argv.length && !argv[i].startsWith('-')) {
|
|
68
|
+
args.command = argv[i++];
|
|
69
|
+
}
|
|
70
|
+
while (i < argv.length) {
|
|
71
|
+
const arg = argv[i];
|
|
72
|
+
switch (arg) {
|
|
73
|
+
case '--repo':
|
|
74
|
+
if (i + 1 < argv.length)
|
|
75
|
+
args.repo = argv[++i];
|
|
76
|
+
break;
|
|
77
|
+
case '--pr':
|
|
78
|
+
if (i + 1 < argv.length) {
|
|
79
|
+
const n = parseInt(argv[++i], 10);
|
|
80
|
+
if (n > 0)
|
|
81
|
+
args.pr = n;
|
|
82
|
+
}
|
|
83
|
+
break;
|
|
84
|
+
case '--state':
|
|
85
|
+
if (i + 1 < argv.length) {
|
|
86
|
+
const val = argv[++i];
|
|
87
|
+
if (['unresolved', 'resolved', 'all'].includes(val))
|
|
88
|
+
args.state = val;
|
|
89
|
+
}
|
|
90
|
+
break;
|
|
91
|
+
case '--output':
|
|
92
|
+
if (i + 1 < argv.length) {
|
|
93
|
+
const val = argv[++i];
|
|
94
|
+
if (val === 'table' || val === 'json')
|
|
95
|
+
args.output = val;
|
|
96
|
+
}
|
|
97
|
+
break;
|
|
98
|
+
case '--thread-id':
|
|
99
|
+
if (i + 1 < argv.length)
|
|
100
|
+
args.threadId.push(argv[++i]);
|
|
101
|
+
break;
|
|
102
|
+
case '--thread-id-file':
|
|
103
|
+
if (i + 1 < argv.length)
|
|
104
|
+
args.threadIdFile = argv[++i];
|
|
105
|
+
break;
|
|
106
|
+
case '--all-unresolved':
|
|
107
|
+
args.allUnresolved = true;
|
|
108
|
+
break;
|
|
109
|
+
case '--dry-run':
|
|
110
|
+
args.dryRun = true;
|
|
111
|
+
break;
|
|
112
|
+
default:
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
i++;
|
|
116
|
+
}
|
|
117
|
+
return args;
|
|
118
|
+
}
|
|
119
|
+
function runGh(cmdArgs) {
|
|
120
|
+
return new Promise((resolve) => {
|
|
121
|
+
(0, node_child_process_1.execFile)('gh', cmdArgs, { maxBuffer: 10 * 1024 * 1024 }, (error, stdout, stderr) => {
|
|
122
|
+
if (error) {
|
|
123
|
+
resolve({
|
|
124
|
+
stdout: stdout || '',
|
|
125
|
+
stderr: stderr || '',
|
|
126
|
+
exitCode: error.status ?? 1,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
resolve({ stdout: stdout || '', stderr: stderr || '', exitCode: 0 });
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
function runGhJson(cmdArgs) {
|
|
136
|
+
return runGh(cmdArgs).then((result) => {
|
|
137
|
+
if (result.exitCode !== 0) {
|
|
138
|
+
throw new Error(result.stderr.trim() || 'gh command failed');
|
|
139
|
+
}
|
|
140
|
+
try {
|
|
141
|
+
return JSON.parse(result.stdout);
|
|
142
|
+
}
|
|
143
|
+
catch (exc) {
|
|
144
|
+
throw new Error('Failed to parse gh JSON output');
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
function parseOwnerRepo(repo) {
|
|
149
|
+
const parts = repo.split('/');
|
|
150
|
+
if (parts.length !== 2 || !parts[0] || !parts[1]) {
|
|
151
|
+
throw new Error('repo must be in owner/name format');
|
|
152
|
+
}
|
|
153
|
+
return [parts[0], parts[1]];
|
|
154
|
+
}
|
|
155
|
+
async function resolveRepo(repo) {
|
|
156
|
+
if (repo) {
|
|
157
|
+
parseOwnerRepo(repo);
|
|
158
|
+
return repo;
|
|
159
|
+
}
|
|
160
|
+
const result = await runGh([
|
|
161
|
+
'repo',
|
|
162
|
+
'view',
|
|
163
|
+
'--json',
|
|
164
|
+
'nameWithOwner',
|
|
165
|
+
'--jq',
|
|
166
|
+
'.nameWithOwner',
|
|
167
|
+
]);
|
|
168
|
+
if (result.exitCode !== 0) {
|
|
169
|
+
throw new Error(result.stderr.trim() || 'Unable to resolve current repo');
|
|
170
|
+
}
|
|
171
|
+
return result.stdout.trim();
|
|
172
|
+
}
|
|
173
|
+
async function resolvePrNumber(repo, pr) {
|
|
174
|
+
if (pr !== null)
|
|
175
|
+
return pr;
|
|
176
|
+
const result = await runGh([
|
|
177
|
+
'pr',
|
|
178
|
+
'view',
|
|
179
|
+
'--repo',
|
|
180
|
+
repo,
|
|
181
|
+
'--json',
|
|
182
|
+
'number',
|
|
183
|
+
'--jq',
|
|
184
|
+
'.number',
|
|
185
|
+
]);
|
|
186
|
+
if (result.exitCode !== 0) {
|
|
187
|
+
throw new Error('Unable to infer PR number from current branch context');
|
|
188
|
+
}
|
|
189
|
+
return parseInt(result.stdout.trim(), 10);
|
|
190
|
+
}
|
|
191
|
+
function ghGraphql(query, variables) {
|
|
192
|
+
const cmdArgs = ['api', 'graphql', '-f', `query=${query}`];
|
|
193
|
+
for (const [key, value] of Object.entries(variables)) {
|
|
194
|
+
cmdArgs.push('-F', `${key}=${JSON.stringify(value)}`);
|
|
195
|
+
}
|
|
196
|
+
return runGhJson(cmdArgs);
|
|
197
|
+
}
|
|
198
|
+
// ---- Thread fetching ----
|
|
199
|
+
async function fetchReviewThreads(repo, prNumber) {
|
|
200
|
+
const [owner, name] = parseOwnerRepo(repo);
|
|
201
|
+
const threads = [];
|
|
202
|
+
let after = null;
|
|
203
|
+
while (true) {
|
|
204
|
+
const payload = await ghGraphql(LIST_QUERY, {
|
|
205
|
+
owner,
|
|
206
|
+
name,
|
|
207
|
+
number: prNumber,
|
|
208
|
+
after,
|
|
209
|
+
});
|
|
210
|
+
const pr = payload.data?.repository;
|
|
211
|
+
if (!pr) {
|
|
212
|
+
throw new Error(`PR #${prNumber} not found in ${repo}`);
|
|
213
|
+
}
|
|
214
|
+
const reviewThreads = pr.reviewThreads;
|
|
215
|
+
const nodes = reviewThreads.nodes || [];
|
|
216
|
+
threads.push(...nodes);
|
|
217
|
+
const pageInfo = reviewThreads.pageInfo;
|
|
218
|
+
if (!pageInfo.hasNextPage)
|
|
219
|
+
break;
|
|
220
|
+
after = pageInfo.endCursor || null;
|
|
221
|
+
if (!after)
|
|
222
|
+
break;
|
|
223
|
+
}
|
|
224
|
+
return threads;
|
|
225
|
+
}
|
|
226
|
+
function filterThreads(threads, state) {
|
|
227
|
+
if (state === 'all')
|
|
228
|
+
return threads;
|
|
229
|
+
if (state === 'resolved') {
|
|
230
|
+
return threads.filter((item) => item.isResolved);
|
|
231
|
+
}
|
|
232
|
+
return threads.filter((item) => !item.isResolved);
|
|
233
|
+
}
|
|
234
|
+
function normalizeThread(thread) {
|
|
235
|
+
const commentNodes = thread.comments?.nodes;
|
|
236
|
+
const normalizedComments = (commentNodes || []).map((comment) => ({
|
|
237
|
+
id: comment.id,
|
|
238
|
+
url: comment.url,
|
|
239
|
+
author: comment.author?.login || null,
|
|
240
|
+
body: comment.body || '',
|
|
241
|
+
created_at: comment.createdAt,
|
|
242
|
+
path: comment.path,
|
|
243
|
+
line: comment.line,
|
|
244
|
+
outdated: comment.outdated,
|
|
245
|
+
}));
|
|
246
|
+
return {
|
|
247
|
+
thread_id: thread.id,
|
|
248
|
+
is_resolved: thread.isResolved,
|
|
249
|
+
is_outdated: thread.isOutdated,
|
|
250
|
+
path: thread.path,
|
|
251
|
+
line: thread.line,
|
|
252
|
+
start_line: thread.startLine,
|
|
253
|
+
comments: normalizedComments,
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
function truncate(text, width) {
|
|
257
|
+
if (text.length <= width)
|
|
258
|
+
return text;
|
|
259
|
+
if (width <= 3)
|
|
260
|
+
return text.slice(0, width);
|
|
261
|
+
return text.slice(0, width - 3) + '...';
|
|
262
|
+
}
|
|
263
|
+
function previewBody(thread) {
|
|
264
|
+
const comments = thread.comments;
|
|
265
|
+
if (!comments || comments.length === 0)
|
|
266
|
+
return '-';
|
|
267
|
+
const body = (comments[0].body || '').replace(/\n/g, ' ').trim();
|
|
268
|
+
return truncate(body || '-', 72);
|
|
269
|
+
}
|
|
270
|
+
function renderLocation(thread) {
|
|
271
|
+
const path = thread.path || '-';
|
|
272
|
+
const line = thread.line;
|
|
273
|
+
if (line == null)
|
|
274
|
+
return path;
|
|
275
|
+
return `${path}:${line}`;
|
|
276
|
+
}
|
|
277
|
+
function printTable(threads, context) {
|
|
278
|
+
const { stdout } = context;
|
|
279
|
+
const widths = {
|
|
280
|
+
idx: 4,
|
|
281
|
+
thread: 12,
|
|
282
|
+
location: 36,
|
|
283
|
+
author: 18,
|
|
284
|
+
preview: 72,
|
|
285
|
+
};
|
|
286
|
+
const header = `${'#'.padEnd(widths.idx)} ` +
|
|
287
|
+
`${'THREAD_ID'.padEnd(widths.thread)} ` +
|
|
288
|
+
`${'LOCATION'.padEnd(widths.location)} ` +
|
|
289
|
+
`${'AUTHOR'.padEnd(widths.author)} ` +
|
|
290
|
+
`${'COMMENT_PREVIEW'.padEnd(widths.preview)}`;
|
|
291
|
+
stdout.write(header + '\n');
|
|
292
|
+
stdout.write('-'.repeat(header.length) + '\n');
|
|
293
|
+
for (let idx = 0; idx < threads.length; idx++) {
|
|
294
|
+
const thread = threads[idx];
|
|
295
|
+
const comments = thread.comments;
|
|
296
|
+
const author = comments?.[0]?.author ?? '-';
|
|
297
|
+
const row = `${String(idx + 1).padEnd(widths.idx)} ` +
|
|
298
|
+
`${truncate(String(thread.thread_id ?? '-'), widths.thread).padEnd(widths.thread)} ` +
|
|
299
|
+
`${truncate(renderLocation(thread), widths.location).padEnd(widths.location)} ` +
|
|
300
|
+
`${truncate(String(author ?? '-'), widths.author).padEnd(widths.author)} ` +
|
|
301
|
+
`${previewBody(thread).padEnd(widths.preview)}`;
|
|
302
|
+
stdout.write(row + '\n');
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
// ---- Thread ID loading ----
|
|
306
|
+
function loadThreadIds(filePath) {
|
|
307
|
+
const raw = (0, node_fs_1.readFileSync)(filePath, 'utf-8');
|
|
308
|
+
const payload = JSON.parse(raw);
|
|
309
|
+
let ids;
|
|
310
|
+
if (Array.isArray(payload)) {
|
|
311
|
+
ids = payload;
|
|
312
|
+
}
|
|
313
|
+
else if (typeof payload === 'object' && payload !== null) {
|
|
314
|
+
const p = payload;
|
|
315
|
+
if (Array.isArray(p.thread_ids)) {
|
|
316
|
+
ids = p.thread_ids;
|
|
317
|
+
}
|
|
318
|
+
else if (Array.isArray(p.adopted_thread_ids)) {
|
|
319
|
+
ids = p.adopted_thread_ids;
|
|
320
|
+
}
|
|
321
|
+
else if (Array.isArray(p.threads)) {
|
|
322
|
+
ids = p.threads
|
|
323
|
+
.filter((item) => typeof item === 'object' && item !== null)
|
|
324
|
+
.map((item) => item.thread_id)
|
|
325
|
+
.filter((id) => id !== undefined);
|
|
326
|
+
}
|
|
327
|
+
else {
|
|
328
|
+
throw new Error('JSON must include thread_ids, adopted_thread_ids, or threads');
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
else {
|
|
332
|
+
throw new Error('Unsupported JSON payload for thread IDs');
|
|
333
|
+
}
|
|
334
|
+
const output = ids
|
|
335
|
+
.filter((item) => typeof item === 'string' && item.trim().length > 0)
|
|
336
|
+
.map((item) => item.trim());
|
|
337
|
+
return [...new Set(output)];
|
|
338
|
+
}
|
|
339
|
+
function collectThreadIds(args, unresolvedThreads) {
|
|
340
|
+
const ids = [];
|
|
341
|
+
if (args.allUnresolved) {
|
|
342
|
+
for (const item of unresolvedThreads) {
|
|
343
|
+
if (item.thread_id) {
|
|
344
|
+
ids.push(item.thread_id);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
ids.push(...args.threadId);
|
|
349
|
+
if (args.threadIdFile) {
|
|
350
|
+
ids.push(...loadThreadIds(args.threadIdFile));
|
|
351
|
+
}
|
|
352
|
+
const normalized = ids.filter(Boolean);
|
|
353
|
+
return [...new Set(normalized)];
|
|
354
|
+
}
|
|
355
|
+
async function resolveThreads(threadIds, dryRun) {
|
|
356
|
+
const resolved = [];
|
|
357
|
+
const failed = [];
|
|
358
|
+
for (const threadId of threadIds) {
|
|
359
|
+
if (dryRun) {
|
|
360
|
+
resolved.push(threadId);
|
|
361
|
+
continue;
|
|
362
|
+
}
|
|
363
|
+
try {
|
|
364
|
+
const payload = await ghGraphql(RESOLVE_MUTATION, { threadId });
|
|
365
|
+
const thread = payload.data?.resolveReviewThread;
|
|
366
|
+
if (!thread?.thread) {
|
|
367
|
+
throw new Error('thread did not resolve');
|
|
368
|
+
}
|
|
369
|
+
const resolvedThread = thread.thread;
|
|
370
|
+
if (!resolvedThread.isResolved) {
|
|
371
|
+
throw new Error('thread did not resolve');
|
|
372
|
+
}
|
|
373
|
+
resolved.push(threadId);
|
|
374
|
+
}
|
|
375
|
+
catch (exc) {
|
|
376
|
+
failed.push({ thread_id: threadId, error: exc.message });
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
return { resolved, failed };
|
|
380
|
+
}
|
|
381
|
+
// ---- Subcommands ----
|
|
382
|
+
async function cmdList(args, context) {
|
|
383
|
+
const { stdout, stderr } = context;
|
|
384
|
+
let repo;
|
|
385
|
+
try {
|
|
386
|
+
repo = await resolveRepo(args.repo);
|
|
387
|
+
}
|
|
388
|
+
catch (err) {
|
|
389
|
+
stderr.write(`Error: ${err.message}\n`);
|
|
390
|
+
return 1;
|
|
391
|
+
}
|
|
392
|
+
let prNumber;
|
|
393
|
+
try {
|
|
394
|
+
prNumber = await resolvePrNumber(repo, args.pr);
|
|
395
|
+
}
|
|
396
|
+
catch (err) {
|
|
397
|
+
stderr.write(`Error: ${err.message}\n`);
|
|
398
|
+
return 1;
|
|
399
|
+
}
|
|
400
|
+
let threads;
|
|
401
|
+
try {
|
|
402
|
+
threads = await fetchReviewThreads(repo, prNumber);
|
|
403
|
+
}
|
|
404
|
+
catch (err) {
|
|
405
|
+
stderr.write(`Error: ${err.message}\n`);
|
|
406
|
+
return 1;
|
|
407
|
+
}
|
|
408
|
+
const filtered = filterThreads(threads, args.state);
|
|
409
|
+
const normalized = filtered.map(normalizeThread);
|
|
410
|
+
const result = {
|
|
411
|
+
repo,
|
|
412
|
+
pr_number: prNumber,
|
|
413
|
+
state: args.state,
|
|
414
|
+
thread_count: normalized.length,
|
|
415
|
+
threads: normalized,
|
|
416
|
+
};
|
|
417
|
+
if (args.output === 'json') {
|
|
418
|
+
stdout.write(JSON.stringify(result, null, 2) + '\n');
|
|
419
|
+
}
|
|
420
|
+
else {
|
|
421
|
+
stdout.write(`Repository: ${repo}\n`);
|
|
422
|
+
stdout.write(`PR: #${prNumber}\n`);
|
|
423
|
+
stdout.write(`Threads (${args.state}): ${normalized.length}\n`);
|
|
424
|
+
printTable(normalized, context);
|
|
425
|
+
}
|
|
426
|
+
return 0;
|
|
427
|
+
}
|
|
428
|
+
async function cmdResolve(args, context) {
|
|
429
|
+
const { stdout, stderr } = context;
|
|
430
|
+
let repo;
|
|
431
|
+
try {
|
|
432
|
+
repo = await resolveRepo(args.repo);
|
|
433
|
+
}
|
|
434
|
+
catch (err) {
|
|
435
|
+
stderr.write(`Error: ${err.message}\n`);
|
|
436
|
+
return 1;
|
|
437
|
+
}
|
|
438
|
+
let prNumber;
|
|
439
|
+
try {
|
|
440
|
+
prNumber = await resolvePrNumber(repo, args.pr);
|
|
441
|
+
}
|
|
442
|
+
catch (err) {
|
|
443
|
+
stderr.write(`Error: ${err.message}\n`);
|
|
444
|
+
return 1;
|
|
445
|
+
}
|
|
446
|
+
let threads;
|
|
447
|
+
try {
|
|
448
|
+
threads = await fetchReviewThreads(repo, prNumber);
|
|
449
|
+
}
|
|
450
|
+
catch (err) {
|
|
451
|
+
stderr.write(`Error: ${err.message}\n`);
|
|
452
|
+
return 1;
|
|
453
|
+
}
|
|
454
|
+
const unresolved = filterThreads(threads, 'unresolved').map(normalizeThread);
|
|
455
|
+
const threadIds = collectThreadIds(args, unresolved);
|
|
456
|
+
if (threadIds.length === 0) {
|
|
457
|
+
stderr.write('Error: no thread IDs selected. Use --thread-id, --thread-id-file, or --all-unresolved.\n');
|
|
458
|
+
return 1;
|
|
459
|
+
}
|
|
460
|
+
const { resolved, failed } = await resolveThreads(threadIds, args.dryRun);
|
|
461
|
+
const summary = {
|
|
462
|
+
repo,
|
|
463
|
+
pr_number: prNumber,
|
|
464
|
+
requested: threadIds,
|
|
465
|
+
resolved,
|
|
466
|
+
failed,
|
|
467
|
+
dry_run: args.dryRun,
|
|
468
|
+
};
|
|
469
|
+
stdout.write(JSON.stringify(summary, null, 2) + '\n');
|
|
470
|
+
return failed.length > 0 ? 1 : 0;
|
|
471
|
+
}
|
|
472
|
+
// ---- Main handler ----
|
|
473
|
+
async function reviewThreadsHandler(argv, context) {
|
|
474
|
+
const { stderr } = context;
|
|
475
|
+
const args = parseArgs(argv);
|
|
476
|
+
try {
|
|
477
|
+
switch (args.command) {
|
|
478
|
+
case 'list':
|
|
479
|
+
return await cmdList(args, context);
|
|
480
|
+
case 'resolve':
|
|
481
|
+
return await cmdResolve(args, context);
|
|
482
|
+
default:
|
|
483
|
+
stderr.write(`Unsupported command: ${args.command}\n`);
|
|
484
|
+
return 1;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
catch (err) {
|
|
488
|
+
stderr.write(`Error: ${err.message}\n`);
|
|
489
|
+
return 1;
|
|
490
|
+
}
|
|
491
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.searchLogsHandler = searchLogsHandler;
|
|
4
|
+
const log_cli_utils_1 = require("./log-cli-utils");
|
|
5
|
+
function parseArgs(argv) {
|
|
6
|
+
const args = {
|
|
7
|
+
paths: [],
|
|
8
|
+
keyword: [],
|
|
9
|
+
regex: [],
|
|
10
|
+
mode: 'any',
|
|
11
|
+
ignoreCase: false,
|
|
12
|
+
start: null,
|
|
13
|
+
end: null,
|
|
14
|
+
assumeTimezone: 'UTC',
|
|
15
|
+
beforeContext: 0,
|
|
16
|
+
afterContext: 0,
|
|
17
|
+
countOnly: false,
|
|
18
|
+
};
|
|
19
|
+
let i = 0;
|
|
20
|
+
while (i < argv.length) {
|
|
21
|
+
const arg = argv[i];
|
|
22
|
+
if (arg === '--keyword' && i + 1 < argv.length) {
|
|
23
|
+
args.keyword.push(argv[++i]);
|
|
24
|
+
}
|
|
25
|
+
else if (arg === '--regex' && i + 1 < argv.length) {
|
|
26
|
+
args.regex.push(argv[++i]);
|
|
27
|
+
}
|
|
28
|
+
else if (arg === '--mode' && i + 1 < argv.length) {
|
|
29
|
+
const val = argv[++i];
|
|
30
|
+
if (val === 'any' || val === 'all') {
|
|
31
|
+
args.mode = val;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
else if (arg === '--ignore-case') {
|
|
35
|
+
args.ignoreCase = true;
|
|
36
|
+
}
|
|
37
|
+
else if (arg === '--start' && i + 1 < argv.length) {
|
|
38
|
+
args.start = argv[++i];
|
|
39
|
+
}
|
|
40
|
+
else if (arg === '--end' && i + 1 < argv.length) {
|
|
41
|
+
args.end = argv[++i];
|
|
42
|
+
}
|
|
43
|
+
else if (arg === '--assume-timezone' && i + 1 < argv.length) {
|
|
44
|
+
args.assumeTimezone = argv[++i];
|
|
45
|
+
}
|
|
46
|
+
else if (arg === '--before-context' && i + 1 < argv.length) {
|
|
47
|
+
args.beforeContext = parseInt(argv[++i], 10) || 0;
|
|
48
|
+
}
|
|
49
|
+
else if (arg === '--after-context' && i + 1 < argv.length) {
|
|
50
|
+
args.afterContext = parseInt(argv[++i], 10) || 0;
|
|
51
|
+
}
|
|
52
|
+
else if (arg === '--count-only') {
|
|
53
|
+
args.countOnly = true;
|
|
54
|
+
}
|
|
55
|
+
else if (arg.startsWith('-')) {
|
|
56
|
+
// skip unknown flags
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
args.paths.push(arg);
|
|
60
|
+
}
|
|
61
|
+
i++;
|
|
62
|
+
}
|
|
63
|
+
return args;
|
|
64
|
+
}
|
|
65
|
+
function buildMatchers(args) {
|
|
66
|
+
const matchers = [];
|
|
67
|
+
for (const keyword of args.keyword) {
|
|
68
|
+
const needle = args.ignoreCase ? keyword.toLowerCase() : keyword;
|
|
69
|
+
matchers.push((line) => {
|
|
70
|
+
const haystack = args.ignoreCase ? line.toLowerCase() : line;
|
|
71
|
+
return haystack.includes(needle);
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
for (const pattern of args.regex) {
|
|
75
|
+
const flags = args.ignoreCase ? 'i' : '';
|
|
76
|
+
const compiled = new RegExp(pattern, flags);
|
|
77
|
+
matchers.push((line) => compiled.test(line));
|
|
78
|
+
}
|
|
79
|
+
return matchers;
|
|
80
|
+
}
|
|
81
|
+
function lineMatches(line, matchers, mode) {
|
|
82
|
+
if (matchers.length === 0)
|
|
83
|
+
return true;
|
|
84
|
+
if (mode === 'any') {
|
|
85
|
+
return matchers.some((m) => m(line));
|
|
86
|
+
}
|
|
87
|
+
return matchers.every((m) => m(line));
|
|
88
|
+
}
|
|
89
|
+
async function searchLogsHandler(argv, context) {
|
|
90
|
+
const { stdout, stderr } = context;
|
|
91
|
+
const args = parseArgs(argv);
|
|
92
|
+
try {
|
|
93
|
+
(0, log_cli_utils_1.buildTimezone)(args.assumeTimezone);
|
|
94
|
+
}
|
|
95
|
+
catch (err) {
|
|
96
|
+
stderr.write(`Error: invalid timezone: ${args.assumeTimezone}\n`);
|
|
97
|
+
return 1;
|
|
98
|
+
}
|
|
99
|
+
let start = null;
|
|
100
|
+
let end = null;
|
|
101
|
+
try {
|
|
102
|
+
if (args.start) {
|
|
103
|
+
start = (0, log_cli_utils_1.parseCliTimestamp)(args.start, args.assumeTimezone);
|
|
104
|
+
}
|
|
105
|
+
if (args.end) {
|
|
106
|
+
end = (0, log_cli_utils_1.parseCliTimestamp)(args.end, args.assumeTimezone);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
catch (err) {
|
|
110
|
+
stderr.write(`Error: ${err.message}\n`);
|
|
111
|
+
return 1;
|
|
112
|
+
}
|
|
113
|
+
if (!(0, log_cli_utils_1.validateTimeWindow)(start, end, stderr)) {
|
|
114
|
+
return 1;
|
|
115
|
+
}
|
|
116
|
+
const matchers = buildMatchers(args);
|
|
117
|
+
let matches = 0;
|
|
118
|
+
const beforeBuffer = [];
|
|
119
|
+
let afterRemaining = 0;
|
|
120
|
+
try {
|
|
121
|
+
for await (const line of (0, log_cli_utils_1.iterInputLines)(args.paths)) {
|
|
122
|
+
const timestamp = (0, log_cli_utils_1.extractTimestamp)(line, args.assumeTimezone);
|
|
123
|
+
// When time filter is active, skip lines outside the window
|
|
124
|
+
if (args.start || args.end) {
|
|
125
|
+
if (!(0, log_cli_utils_1.inWindow)(timestamp, start, end)) {
|
|
126
|
+
beforeBuffer.push(line);
|
|
127
|
+
if (beforeBuffer.length > args.beforeContext) {
|
|
128
|
+
beforeBuffer.shift();
|
|
129
|
+
}
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
const isMatch = lineMatches(line, matchers, args.mode);
|
|
134
|
+
if (isMatch) {
|
|
135
|
+
matches++;
|
|
136
|
+
if (!args.countOnly) {
|
|
137
|
+
// Flush before context
|
|
138
|
+
for (const ctxLine of beforeBuffer) {
|
|
139
|
+
stdout.write(ctxLine + '\n');
|
|
140
|
+
}
|
|
141
|
+
stdout.write(line + '\n');
|
|
142
|
+
}
|
|
143
|
+
afterRemaining = args.afterContext;
|
|
144
|
+
}
|
|
145
|
+
else if (afterRemaining > 0 && !args.countOnly) {
|
|
146
|
+
stdout.write(line + '\n');
|
|
147
|
+
afterRemaining--;
|
|
148
|
+
}
|
|
149
|
+
// Maintain before context buffer
|
|
150
|
+
beforeBuffer.push(line);
|
|
151
|
+
if (beforeBuffer.length > args.beforeContext) {
|
|
152
|
+
beforeBuffer.shift();
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
catch (err) {
|
|
157
|
+
stderr.write(`Error: ${err.message}\n`);
|
|
158
|
+
return 1;
|
|
159
|
+
}
|
|
160
|
+
if (args.countOnly) {
|
|
161
|
+
stdout.write(String(matches) + '\n');
|
|
162
|
+
}
|
|
163
|
+
return 0;
|
|
164
|
+
}
|