reslop 0.2.7 → 0.3.0

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.
Files changed (158) hide show
  1. package/CHANGELOG.md +17 -1
  2. package/README.md +16 -14
  3. package/bin/reslop.js +8 -5
  4. package/lib/agents/agents.js +504 -0
  5. package/lib/agents/allow.js +71 -0
  6. package/lib/agents/allow.json +16 -0
  7. package/lib/agents/models.js +460 -0
  8. package/lib/api.js +15 -12
  9. package/lib/cli.js +43 -68
  10. package/lib/{files.js → common/files.js} +48 -55
  11. package/lib/{find.js → common/find.js} +1 -1
  12. package/lib/common/format.js +101 -0
  13. package/lib/{utilities.js → common/process.js} +7 -95
  14. package/lib/common/tasks.json +27 -0
  15. package/lib/common/utilities.js +112 -0
  16. package/lib/dashboard/git.js +9 -16
  17. package/lib/dashboard/model.js +90 -49
  18. package/lib/dashboard/npm.js +17 -13
  19. package/lib/dashboard/tree.js +27 -30
  20. package/lib/{session → dashboard}/watch.js +36 -31
  21. package/lib/{workspace.js → dashboard/workspace.js} +23 -38
  22. package/lib/{deps.js → deps/deps.js} +31 -45
  23. package/lib/{deps-diff.js → deps/diff.js} +35 -25
  24. package/lib/{manifest.js → deps/manifest.js} +47 -30
  25. package/lib/diff/align.js +1 -2
  26. package/lib/diff/diff.js +2 -2
  27. package/lib/diff/display.js +29 -5
  28. package/lib/diff/inline.js +10 -14
  29. package/lib/diff/items.js +6 -4
  30. package/lib/diff/patch.js +39 -3
  31. package/lib/diff/unit.js +45 -16
  32. package/lib/{git-branches.js → git/branches.js} +53 -48
  33. package/lib/{git-deps.js → git/deps.js} +123 -96
  34. package/lib/git/git.js +71 -0
  35. package/lib/git/worktree.js +475 -0
  36. package/lib/highlight/bash.js +1 -2
  37. package/lib/highlight/csv.js +1 -2
  38. package/lib/highlight/dot.js +1 -2
  39. package/lib/{highlight.js → highlight/highlight.js} +42 -11
  40. package/lib/highlight/html.js +20 -18
  41. package/lib/highlight/json.js +1 -2
  42. package/lib/highlight/log.js +1 -2
  43. package/lib/highlight/txt.js +1 -2
  44. package/lib/{session → input}/actions.js +111 -48
  45. package/lib/{editor.js → input/editor.js} +7 -8
  46. package/lib/{keys.js → input/keys.js} +15 -43
  47. package/lib/input/line-edit.js +77 -0
  48. package/lib/input/menu-pick.js +70 -0
  49. package/lib/render/agents.js +640 -0
  50. package/lib/render/branches.js +4 -5
  51. package/lib/render/commits.js +54 -64
  52. package/lib/render/{dash-activity.js → dashboard/activity.js} +25 -40
  53. package/lib/render/{dash-blocks.js → dashboard/blocks.js} +131 -137
  54. package/lib/render/{dashboard.js → dashboard/dashboard.js} +25 -22
  55. package/lib/render/diff.js +166 -45
  56. package/lib/render/field.js +131 -0
  57. package/lib/render/files.js +9 -14
  58. package/lib/render/footer.js +383 -0
  59. package/lib/render/frame.js +123 -25
  60. package/lib/render/header.js +176 -0
  61. package/lib/render/log.js +319 -0
  62. package/lib/render/menu.js +60 -0
  63. package/lib/render/notes.js +16 -11
  64. package/lib/render/npm.js +322 -196
  65. package/lib/render/packages.js +9 -13
  66. package/lib/render/primitives.js +101 -15
  67. package/lib/render/render.js +7 -11
  68. package/lib/render/repos.js +13 -25
  69. package/lib/render/status.js +370 -0
  70. package/lib/render/status.json +85 -0
  71. package/lib/render/{dash-table.js → table.js} +13 -60
  72. package/lib/render/tiles.js +109 -48
  73. package/lib/render/unit.js +32 -13
  74. package/lib/{report-model.js → report/model.js} +4 -6
  75. package/lib/{report-parse.js → report/parse.js} +9 -14
  76. package/lib/{report-render.js → report/render.js} +46 -14
  77. package/lib/{report-run.js → report/run.js} +42 -30
  78. package/lib/{report-test.js → report/test.js} +56 -55
  79. package/lib/{report-tools.js → report/tools.js} +24 -33
  80. package/lib/review/agent-instructions.md +9 -0
  81. package/lib/review/format.js +426 -0
  82. package/lib/{review-merge.js → review/merge.js} +9 -8
  83. package/lib/review/review.js +190 -0
  84. package/lib/review/store.js +281 -0
  85. package/lib/review/templates.js +83 -0
  86. package/lib/runs/commands.js +14 -0
  87. package/lib/runs/logs.js +299 -0
  88. package/lib/runs/output.js +174 -0
  89. package/lib/runs/process.js +113 -0
  90. package/lib/{runs.js → runs/runs.js} +84 -36
  91. package/lib/runs/scripts.js +150 -0
  92. package/lib/session/agents/agents.js +958 -0
  93. package/lib/session/agents/job.js +320 -0
  94. package/lib/session/agents/jobs.js +355 -0
  95. package/lib/session/agents/options.js +153 -0
  96. package/lib/session/agents/plans.js +83 -0
  97. package/lib/session/branches.js +33 -39
  98. package/lib/session/change-actions.js +34 -100
  99. package/lib/session/commit.js +91 -52
  100. package/lib/session/compose.js +181 -145
  101. package/lib/session/dashboard.js +23 -22
  102. package/lib/session/dispatch.js +85 -21
  103. package/lib/session/files-pane.js +47 -26
  104. package/lib/session/find.js +16 -29
  105. package/lib/session/import.js +82 -0
  106. package/lib/session/items.js +8 -17
  107. package/lib/session/lifecycle.js +75 -72
  108. package/lib/session/load.js +2 -4
  109. package/lib/session/navigation.js +6 -3
  110. package/lib/session/npm/history.js +249 -0
  111. package/lib/session/npm/npm.js +815 -0
  112. package/lib/session/{packages.js → npm/packages.js} +21 -14
  113. package/lib/session/ops.js +125 -34
  114. package/lib/session/pointer.js +202 -10
  115. package/lib/session/prefs.js +112 -0
  116. package/lib/session/reads.js +109 -0
  117. package/lib/session/review.js +28 -10
  118. package/lib/session/run-panel.js +40 -0
  119. package/lib/{session.js → session/session.js} +156 -63
  120. package/lib/session/tasks.js +320 -65
  121. package/lib/session/templates.js +1 -1
  122. package/lib/session/terminal.js +94 -12
  123. package/lib/session/unit.js +35 -30
  124. package/lib/session/update.js +194 -6
  125. package/lib/session/view.js +65 -65
  126. package/lib/session/workspace.js +39 -78
  127. package/lib/{capabilities.js → source/capabilities.js} +3 -12
  128. package/lib/{github.js → source/github.js} +102 -32
  129. package/lib/{gitlab.js → source/gitlab.js} +87 -10
  130. package/lib/{remote.js → source/remote.js} +144 -40
  131. package/lib/source/review-threads.graphql +20 -0
  132. package/lib/source/source.js +74 -0
  133. package/lib/term/ansi.js +396 -0
  134. package/lib/{clipboard.js → term/clipboard.js} +3 -6
  135. package/lib/{select.js → term/select.js} +2 -2
  136. package/lib/term/themes/dark.json +122 -0
  137. package/lib/term/themes/light.json +122 -0
  138. package/lib/term/wide.txt +21 -0
  139. package/lib/{wrap.js → term/wrap.js} +4 -4
  140. package/package.json +2 -2
  141. package/lib/ansi.js +0 -545
  142. package/lib/detect.js +0 -34
  143. package/lib/diff/edit.js +0 -33
  144. package/lib/git-worktree.js +0 -455
  145. package/lib/git.js +0 -79
  146. package/lib/npm-commands.js +0 -522
  147. package/lib/render/chrome.js +0 -806
  148. package/lib/render/present.js +0 -19
  149. package/lib/render/report.js +0 -43
  150. package/lib/render/search.js +0 -25
  151. package/lib/review.js +0 -745
  152. package/lib/session/accessors.js +0 -26
  153. package/lib/session/edit-click.js +0 -57
  154. package/lib/session/npm.js +0 -452
  155. package/lib/session/progress.js +0 -41
  156. package/lib/source.js +0 -36
  157. package/lib/update.js +0 -192
  158. /package/lib/{git-reword-todo.js → git/reword-todo.js} +0 -0
@@ -1,15 +1,10 @@
1
1
  'use strict';
2
2
 
3
- const metautil = require('metautil');
4
- const { delay, jsonParse, isHashObject } = metautil;
5
- const utilities = require('./utilities.js');
6
- const { oneLine } = utilities;
7
- const files = require('./files.js');
8
- const { itemPath, isPathInScope } = files;
9
- const diff = require('./diff/diff.js');
10
- const { parseDiff, itemsFromFiles, isCtxType } = diff;
11
- const deps = require('./deps.js');
12
- const { foldDepItems } = deps;
3
+ const { delay, jsonParse, isHashObject } = require('metautil');
4
+ const { oneLine, trimText } = require('../common/utilities.js');
5
+ const { itemPath, isPathInScope, ISSUE_FILE } = require('../common/files.js');
6
+ const { parseDiff, itemsFromFiles, isCtxType } = require('../diff/diff.js');
7
+ const { foldDepItems } = require('../deps/deps.js');
13
8
 
14
9
  const RETRY_ATTEMPTS = 3;
15
10
  const RETRY_DELAY_MS = 200;
@@ -62,19 +57,18 @@ class RemoteClient {
62
57
  throw this.error('PARSE', `${this.name} API: invalid ${label}`);
63
58
  }
64
59
 
65
- httpError(status, body, token) {
60
+ httpError(status, body, token, subject = this.subject) {
66
61
  const { name, prefix } = this;
67
- const text = `${body ?? ''}`.trim();
62
+ const text = trimText(body);
68
63
  const parsed = jsonParse(text);
69
64
  const fields = this.errorFields;
70
65
  const field = fields.find((key) => typeof parsed?.[key] === 'string');
71
- const message =
72
- isHashObject(parsed) && field ? parsed[field] : oneLine(text);
66
+ const fromField = isHashObject(parsed) && field;
67
+ const message = fromField ? parsed[field] : oneLine(text);
73
68
  if (status === 404) {
74
- const hint = token
75
- ? ''
76
- : ` (set ${prefix}_TOKEN for private repositories)`;
77
- const notFound = `${name} ${this.subject} not found${hint}`;
69
+ let hint = '';
70
+ if (!token) hint = ` (set ${prefix}_TOKEN for private repositories)`;
71
+ const notFound = `${name} ${subject} not found${hint}`;
78
72
  return this.error('NOT_FOUND', notFound, { status });
79
73
  }
80
74
  if (status === 401) {
@@ -101,18 +95,22 @@ class RemoteClient {
101
95
  const init = { method: options.method || 'GET', headers };
102
96
  if (options.body !== undefined) init.body = options.body;
103
97
  if (signal) init.signal = signal;
104
- let response;
105
- try {
106
- response = await options.fetch(url, init);
107
- } catch (cause) {
108
- if (isAbortError(cause, signal)) throw cause;
109
- const detail = oneLine(cause && cause.message);
110
- const suffix = detail ? `: ${detail}` : '';
111
- const message = `${this.name} request failed${suffix}`;
112
- throw this.error('NETWORK', message, { cause });
113
- }
98
+ const load = async () => {
99
+ try {
100
+ return await options.fetch(url, init);
101
+ } catch (cause) {
102
+ if (isAbortError(cause, signal)) throw cause;
103
+ const detail = oneLine(cause && cause.message);
104
+ const suffix = detail ? `: ${detail}` : '';
105
+ const message = `${this.name} request failed${suffix}`;
106
+ throw this.error('NETWORK', message, { cause });
107
+ }
108
+ };
109
+ const response = await load();
114
110
  const text = await response.text();
115
- if (!response.ok) throw this.httpError(response.status, text, token);
111
+ if (!response.ok) {
112
+ throw this.httpError(response.status, text, token, options.subject);
113
+ }
116
114
  return { body: text, headers: response.headers };
117
115
  }
118
116
 
@@ -183,7 +181,14 @@ const listUrl = (base) => {
183
181
  return url.toString();
184
182
  };
185
183
 
186
- const commentBody = (value) => `${value ?? ''}`.trim();
184
+ const commentBody = (value) => trimText(value);
185
+
186
+ const issueText = (title, body) => {
187
+ const head = trimText(title);
188
+ const rest = commentBody(body);
189
+ if (head && rest) return `${head}\n\n${rest}`;
190
+ return head || rest;
191
+ };
187
192
 
188
193
  const sideLineNumber = (type, oldNo, newNo, side) => {
189
194
  if (side === 'LEFT') return type === 'add' ? 0 : oldNo;
@@ -228,6 +233,21 @@ const importedTodo = (file, entry, host) => ({
228
233
  done: entry.resolved === true,
229
234
  });
230
235
 
236
+ const issueTodoText = (entry, host) => {
237
+ const reviewer = entry.reviewer || 'unknown';
238
+ const body = `@${reviewer} at ${host}: ${commentBody(entry.body)}`;
239
+ const url = trimText(entry.url);
240
+ if (!url) return body;
241
+ return `${body}\nIssue: ${url}`;
242
+ };
243
+
244
+ const issueTodo = (entry, host) => ({
245
+ kind: 'todo',
246
+ file: ISSUE_FILE,
247
+ text: issueTodoText(entry, host),
248
+ done: entry.resolved === true,
249
+ });
250
+
231
251
  const importedFeedback = (item, path, entry, host) => {
232
252
  const hunk = item.hunk;
233
253
  return {
@@ -281,17 +301,100 @@ const changeFiles = (items) => {
281
301
  return [...rels];
282
302
  };
283
303
 
304
+ const requestOptions = (options) => ({
305
+ fetch: options.fetch ?? globalThis.fetch,
306
+ token: options.token ?? '',
307
+ retry: options.retry ?? {},
308
+ signal: options.signal,
309
+ });
310
+
311
+ const stateIs = (state, name) => `${state ?? ''}`.toLowerCase() === name;
312
+
313
+ const issueChange = (site, issue) => {
314
+ const number = issue.number || 0;
315
+ const tail = number ? `/${number}` : '';
316
+ return {
317
+ id: `${site.id}${tail}`,
318
+ source: 'issue',
319
+ title: issue.title || '',
320
+ author: issue.author || '',
321
+ base: '',
322
+ head: '',
323
+ url: issue.url || `${site.page}${tail}`,
324
+ repository: site.repository,
325
+ number,
326
+ };
327
+ };
328
+
329
+ const issueTasks = (source, issue, url, comments) => {
330
+ const { host, readComment } = source;
331
+ const tasks = [];
332
+ const body = issueText(issue.title, issue.body);
333
+ if (body) {
334
+ const resolved = stateIs(issue.state, 'closed');
335
+ const entry = { reviewer: issue.author, body, resolved, url };
336
+ tasks.push(issueTodo(entry, host));
337
+ }
338
+ for (const comment of comments) {
339
+ const entry = readComment(comment);
340
+ if (!entry || !commentBody(entry.body)) continue;
341
+ tasks.push(issueTodo(entry, host));
342
+ }
343
+ return tasks;
344
+ };
345
+
346
+ const loadIssue = async (ref, options, source) => {
347
+ const { client, site, issuesUrl, readIssue } = source;
348
+ const request = { ...requestOptions(options), subject: 'issue' };
349
+ const readComments = (number) => {
350
+ const url = `${issuesUrl}/${number}/${source.comments}`;
351
+ return client.list(listUrl(url), request);
352
+ };
353
+ const tasks = [];
354
+ let change = issueChange(site, {});
355
+ if (ref.list) {
356
+ const open = listUrl(`${issuesUrl}?state=${source.openState}`);
357
+ for (const data of await client.list(open, request)) {
358
+ if (!source.isOpen(data)) continue;
359
+ const issue = readIssue(data);
360
+ const quiet = issue.comments === 0;
361
+ const comments = quiet ? [] : await readComments(issue.number);
362
+ const { url } = issueChange(site, issue);
363
+ tasks.push(...issueTasks(source, issue, url, comments));
364
+ }
365
+ } else {
366
+ const [page, comments] = await Promise.all([
367
+ client.request(`${issuesUrl}/${ref.number}`, request),
368
+ readComments(ref.number),
369
+ ]);
370
+ const data = client.parseJson(page.body, 'issue payload');
371
+ const issue = readIssue(data);
372
+ issue.number ||= ref.number;
373
+ change = issueChange(site, issue);
374
+ tasks.push(...issueTasks(source, issue, change.url, comments));
375
+ }
376
+ return {
377
+ top: options.cwd,
378
+ items: [],
379
+ sourceLabel: ref.list ? site.repository : `#${change.number}`,
380
+ change,
381
+ imported: { feedback: [], tasks },
382
+ };
383
+ };
384
+
284
385
  const loadChange = async (ref, options, source) => {
285
386
  const { client, url, diffUrl = url, diffAccept } = source;
286
- const { cwd, paths = [], fetch = globalThis.fetch } = options;
287
- const { token = '', retry = {}, signal } = options;
288
- const request = { fetch, token, retry, signal };
289
- const jsonPage = await client.request(url, request);
387
+ const { cwd, paths = [], signal } = options;
388
+ const request = requestOptions(options);
389
+ const discussion = Promise.resolve().then(() =>
390
+ source.loadDiscussion(ref, request),
391
+ );
392
+ discussion.catch(() => {});
393
+ const [jsonPage, diffPage] = await Promise.all([
394
+ client.request(url, request),
395
+ client.request(diffUrl, { ...request, accept: diffAccept }),
396
+ ]);
290
397
  const data = client.parseJson(jsonPage.body, `${client.subject} payload`);
291
- const diffPage = await client.request(diffUrl, {
292
- ...request,
293
- accept: diffAccept,
294
- });
295
398
  const files = filterChangeFiles(parseDiff(diffPage.body), paths);
296
399
  const items = foldDepItems(itemsFromFiles(files, 'pr'));
297
400
  const change = {
@@ -302,8 +405,7 @@ const loadChange = async (ref, options, source) => {
302
405
  const sourceLabel = `${source.prefix}${change.number}`;
303
406
  let imported = { feedback: [], tasks: [] };
304
407
  try {
305
- const discussion = await source.loadDiscussion(ref, request);
306
- imported = source.discussionToNotes(discussion, items, paths);
408
+ imported = source.discussionToNotes(await discussion, items, paths);
307
409
  } catch (error) {
308
410
  if (isAbortError(error, signal)) throw error;
309
411
  }
@@ -320,6 +422,8 @@ module.exports = {
320
422
  importedTodo,
321
423
  noteFromLocation,
322
424
  splitNotes,
425
+ stateIs,
323
426
  filterChangeFiles,
427
+ loadIssue,
324
428
  loadChange,
325
429
  };
@@ -0,0 +1,20 @@
1
+ query ($owner: String!, $name: String!, $number: Int!, $after: String) {
2
+ repository(owner: $owner, name: $name) {
3
+ pullRequest(number: $number) {
4
+ reviewThreads(first: 100, after: $after) {
5
+ pageInfo {
6
+ hasNextPage
7
+ endCursor
8
+ }
9
+ nodes {
10
+ isResolved
11
+ comments(first: 100) {
12
+ nodes {
13
+ databaseId
14
+ }
15
+ }
16
+ }
17
+ }
18
+ }
19
+ }
20
+ }
@@ -0,0 +1,74 @@
1
+ 'use strict';
2
+
3
+ const github = require('./github.js');
4
+ const gitlab = require('./gitlab.js');
5
+ const { capabilitiesFor, attachCapabilities } = require('./capabilities.js');
6
+ const { parseGithubPrUrl, parseGithubIssueUrl } = github;
7
+ const { parseGithubIssueListUrl } = github;
8
+ const { parseGitlabMrUrl, parseGitlabIssueUrl } = gitlab;
9
+ const { parseGitlabIssueListUrl } = gitlab;
10
+
11
+ const REMOTE_KINDS = {
12
+ pr: {
13
+ option: 'loadPullRequest',
14
+ load: github.loadPullRequest,
15
+ token: github.githubToken,
16
+ target: (selected) => selected.pr,
17
+ },
18
+ mr: {
19
+ option: 'loadMergeRequest',
20
+ load: gitlab.loadMergeRequest,
21
+ token: gitlab.gitlabToken,
22
+ target: (selected) => selected.mr,
23
+ },
24
+ issue: {
25
+ option: 'loadGithubIssue',
26
+ load: github.loadGithubIssue,
27
+ token: github.githubToken,
28
+ target: (selected) => selected.issue,
29
+ },
30
+ 'gl-issue': {
31
+ option: 'loadGitlabIssue',
32
+ load: gitlab.loadGitlabIssue,
33
+ token: gitlab.gitlabToken,
34
+ target: (selected) => selected.issue,
35
+ },
36
+ };
37
+
38
+ const remoteKind = (selected) => REMOTE_KINDS[selected.kind] ?? null;
39
+
40
+ const selectChangeSource = (paths) => {
41
+ if (!paths.length) return { kind: 'local', paths };
42
+ const first = paths[0];
43
+ const rest = paths.slice(1);
44
+ const pr = parseGithubPrUrl(first);
45
+ if (pr) return { kind: 'pr', pr, paths: rest };
46
+ const githubIssue = parseGithubIssueUrl(first);
47
+ if (githubIssue) return { kind: 'issue', issue: githubIssue, paths: rest };
48
+ const githubIssues = parseGithubIssueListUrl(first);
49
+ if (githubIssues) return { kind: 'issue', issue: githubIssues, paths: rest };
50
+ const mr = parseGitlabMrUrl(first);
51
+ if (mr) return { kind: 'mr', mr, paths: rest };
52
+ const gitlabIssue = parseGitlabIssueUrl(first);
53
+ if (gitlabIssue) return { kind: 'gl-issue', issue: gitlabIssue, paths: rest };
54
+ const gitlabIssues = parseGitlabIssueListUrl(first);
55
+ if (gitlabIssues) {
56
+ return { kind: 'gl-issue', issue: gitlabIssues, paths: rest };
57
+ }
58
+ return { kind: 'local', paths };
59
+ };
60
+
61
+ const createLoadedSource = (loaded, kind = 'pr', extra = {}) => {
62
+ const load = async () => {
63
+ const items = loaded.items ?? [];
64
+ return { ...loaded, items: [...items] };
65
+ };
66
+ const impl = { load: extra.load ?? load, resolveRev: () => null };
67
+ return attachCapabilities(impl, capabilitiesFor(kind));
68
+ };
69
+
70
+ module.exports = {
71
+ remoteKind,
72
+ selectChangeSource,
73
+ createLoadedSource,
74
+ };