@laitszkin/apollo-toolkit 4.1.4 → 5.0.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.
Files changed (205) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/bin/apollo-toolkit.ts +4 -0
  3. package/dist/bin/apollo-toolkit.js +4 -0
  4. package/package.json +3 -2
  5. package/packages/cli/dist/help-text-builder.d.ts +23 -0
  6. package/packages/cli/dist/help-text-builder.js +166 -0
  7. package/packages/cli/dist/index.d.ts +6 -17
  8. package/packages/cli/dist/index.js +52 -246
  9. package/packages/cli/dist/installer.d.ts +1 -0
  10. package/packages/cli/dist/installer.js +20 -7
  11. package/packages/cli/dist/parsers/install-parser.d.ts +15 -0
  12. package/packages/cli/dist/parsers/install-parser.js +87 -0
  13. package/packages/cli/dist/parsers/parser-utils.d.ts +9 -0
  14. package/packages/cli/dist/parsers/parser-utils.js +16 -0
  15. package/packages/cli/dist/parsers/tool-parser.d.ts +16 -0
  16. package/packages/cli/dist/parsers/tool-parser.js +58 -0
  17. package/packages/cli/dist/parsers/types.d.ts +50 -0
  18. package/packages/cli/dist/parsers/types.js +1 -0
  19. package/packages/cli/dist/parsers/uninstall-parser.d.ts +15 -0
  20. package/packages/cli/dist/parsers/uninstall-parser.js +67 -0
  21. package/packages/cli/dist/tool-registration.d.ts +2 -0
  22. package/packages/cli/dist/tool-registration.js +2 -0
  23. package/packages/cli/dist/tsconfig.tsbuildinfo +1 -1
  24. package/packages/cli/dist/types.d.ts +3 -1
  25. package/packages/cli/dist/updater.js +11 -5
  26. package/packages/cli/help-text-builder.ts +180 -0
  27. package/packages/cli/index.ts +59 -251
  28. package/packages/cli/installer.ts +19 -7
  29. package/packages/cli/package.json +14 -4
  30. package/packages/cli/parsers/install-parser.ts +94 -0
  31. package/packages/cli/parsers/parser-utils.ts +17 -0
  32. package/packages/cli/parsers/tool-parser.ts +65 -0
  33. package/packages/cli/parsers/types.ts +56 -0
  34. package/packages/cli/parsers/uninstall-parser.ts +75 -0
  35. package/packages/cli/tool-registration.ts +3 -0
  36. package/packages/cli/types.ts +6 -1
  37. package/packages/cli/updater.ts +11 -5
  38. package/packages/tool-registry/dist/registry.js +3 -4
  39. package/packages/tool-registry/dist/tsconfig.tsbuildinfo +1 -1
  40. package/packages/tool-registry/dist/types.d.ts +2 -9
  41. package/packages/tool-registry/package.json +11 -4
  42. package/packages/tool-registry/registry.ts +3 -4
  43. package/packages/tool-registry/tsconfig.json +6 -2
  44. package/packages/tool-registry/types.ts +3 -9
  45. package/packages/tool-utils/app-error.ts +97 -0
  46. package/packages/tool-utils/dist/app-error.d.ts +49 -0
  47. package/packages/tool-utils/dist/app-error.js +80 -0
  48. package/packages/tool-utils/dist/index.d.ts +5 -0
  49. package/packages/tool-utils/dist/index.js +3 -0
  50. package/packages/tool-utils/dist/platform-adapter.d.ts +48 -0
  51. package/packages/tool-utils/dist/platform-adapter.js +73 -0
  52. package/packages/tool-utils/dist/schema.d.ts +68 -0
  53. package/packages/tool-utils/dist/schema.js +67 -0
  54. package/packages/tool-utils/dist/tsconfig.tsbuildinfo +1 -1
  55. package/packages/tool-utils/index.ts +12 -0
  56. package/packages/tool-utils/package.json +11 -4
  57. package/packages/tool-utils/platform-adapter.ts +112 -0
  58. package/packages/tool-utils/schema.ts +122 -0
  59. package/packages/tools/architecture/dist/index.d.ts +13 -0
  60. package/packages/tools/architecture/dist/index.js +55 -57
  61. package/packages/tools/architecture/dist/index.test.js +17 -4
  62. package/packages/tools/architecture/dist/tsconfig.tsbuildinfo +1 -1
  63. package/packages/tools/architecture/index.test.ts +27 -14
  64. package/packages/tools/architecture/index.ts +85 -88
  65. package/packages/tools/architecture/package.json +11 -4
  66. package/packages/tools/codegraph/dist/index.js +12 -22
  67. package/packages/tools/codegraph/dist/tsconfig.tsbuildinfo +1 -1
  68. package/packages/tools/codegraph/index.ts +13 -22
  69. package/packages/tools/codegraph/package.json +11 -4
  70. package/packages/tools/create-review-report/dist/index.d.ts +1 -2
  71. package/packages/tools/create-review-report/dist/index.js +46 -77
  72. package/packages/tools/create-review-report/dist/tsconfig.tsbuildinfo +1 -1
  73. package/packages/tools/create-review-report/index.ts +52 -81
  74. package/packages/tools/create-review-report/package.json +11 -4
  75. package/packages/tools/create-specs/dist/index.d.ts +1 -2
  76. package/packages/tools/create-specs/dist/index.js +70 -123
  77. package/packages/tools/create-specs/dist/tsconfig.tsbuildinfo +1 -1
  78. package/packages/tools/create-specs/index.ts +82 -128
  79. package/packages/tools/create-specs/package.json +11 -4
  80. package/packages/tools/docs-to-voice/dist/index.d.ts +1 -2
  81. package/packages/tools/docs-to-voice/dist/index.js +116 -219
  82. package/packages/tools/docs-to-voice/dist/tsconfig.tsbuildinfo +1 -1
  83. package/packages/tools/docs-to-voice/index.ts +265 -385
  84. package/packages/tools/docs-to-voice/package.json +11 -4
  85. package/packages/tools/enforce-video-aspect-ratio/dist/index.d.ts +1 -2
  86. package/packages/tools/enforce-video-aspect-ratio/dist/index.js +77 -154
  87. package/packages/tools/enforce-video-aspect-ratio/dist/tsconfig.tsbuildinfo +1 -1
  88. package/packages/tools/enforce-video-aspect-ratio/index.ts +87 -172
  89. package/packages/tools/enforce-video-aspect-ratio/package.json +11 -4
  90. package/packages/tools/eval/dist/index.js +7 -0
  91. package/packages/tools/eval/dist/tsconfig.tsbuildinfo +1 -1
  92. package/packages/tools/eval/index.ts +8 -0
  93. package/packages/tools/eval/package.json +11 -4
  94. package/packages/tools/extract-conversations/dist/index.d.ts +1 -2
  95. package/packages/tools/extract-conversations/dist/index.js +31 -29
  96. package/packages/tools/extract-conversations/dist/tsconfig.tsbuildinfo +1 -1
  97. package/packages/tools/extract-conversations/index.ts +37 -30
  98. package/packages/tools/extract-conversations/package.json +11 -4
  99. package/packages/tools/extract-pdf-text/dist/index.d.ts +1 -2
  100. package/packages/tools/extract-pdf-text/dist/index.js +44 -65
  101. package/packages/tools/extract-pdf-text/dist/tsconfig.tsbuildinfo +1 -1
  102. package/packages/tools/extract-pdf-text/index.ts +55 -74
  103. package/packages/tools/extract-pdf-text/package.json +11 -4
  104. package/packages/tools/filter-logs/dist/index.js +60 -84
  105. package/packages/tools/filter-logs/dist/tsconfig.tsbuildinfo +1 -1
  106. package/packages/tools/filter-logs/index.ts +67 -97
  107. package/packages/tools/filter-logs/package.json +11 -4
  108. package/packages/tools/find-github-issues/dist/index.d.ts +10 -0
  109. package/packages/tools/find-github-issues/dist/index.js +34 -5
  110. package/packages/tools/find-github-issues/dist/tsconfig.tsbuildinfo +1 -1
  111. package/packages/tools/find-github-issues/index.ts +37 -5
  112. package/packages/tools/find-github-issues/package.json +11 -4
  113. package/packages/tools/generate-storyboard-images/dist/index.d.ts +1 -2
  114. package/packages/tools/generate-storyboard-images/dist/index.js +98 -173
  115. package/packages/tools/generate-storyboard-images/dist/tsconfig.tsbuildinfo +1 -1
  116. package/packages/tools/generate-storyboard-images/index.ts +100 -188
  117. package/packages/tools/generate-storyboard-images/package.json +11 -4
  118. package/packages/tools/open-github-issue/dist/index.d.ts +13 -0
  119. package/packages/tools/open-github-issue/dist/index.js +67 -68
  120. package/packages/tools/open-github-issue/dist/tsconfig.tsbuildinfo +1 -1
  121. package/packages/tools/open-github-issue/index.ts +71 -72
  122. package/packages/tools/open-github-issue/package.json +11 -4
  123. package/packages/tools/read-github-issue/dist/index.d.ts +16 -1
  124. package/packages/tools/read-github-issue/dist/index.js +32 -40
  125. package/packages/tools/read-github-issue/dist/tsconfig.tsbuildinfo +1 -1
  126. package/packages/tools/read-github-issue/index.ts +32 -45
  127. package/packages/tools/read-github-issue/package.json +11 -4
  128. package/packages/tools/render-error-book/dist/index.d.ts +1 -2
  129. package/packages/tools/render-error-book/dist/index.js +74 -95
  130. package/packages/tools/render-error-book/dist/tsconfig.tsbuildinfo +1 -1
  131. package/packages/tools/render-error-book/index.ts +88 -103
  132. package/packages/tools/render-error-book/package.json +11 -4
  133. package/packages/tools/render-katex/dist/index.d.ts +1 -2
  134. package/packages/tools/render-katex/dist/index.js +70 -157
  135. package/packages/tools/render-katex/dist/tsconfig.tsbuildinfo +1 -1
  136. package/packages/tools/render-katex/index.ts +138 -222
  137. package/packages/tools/render-katex/package.json +11 -4
  138. package/packages/tools/review-threads/dist/index.d.ts +12 -0
  139. package/packages/tools/review-threads/dist/index.js +83 -86
  140. package/packages/tools/review-threads/dist/tsconfig.tsbuildinfo +1 -1
  141. package/packages/tools/review-threads/index.ts +90 -84
  142. package/packages/tools/review-threads/package.json +11 -4
  143. package/packages/tools/search-logs/dist/index.js +100 -136
  144. package/packages/tools/search-logs/dist/tsconfig.tsbuildinfo +1 -1
  145. package/packages/tools/search-logs/index.ts +113 -145
  146. package/packages/tools/search-logs/package.json +11 -4
  147. package/packages/tools/sync-memory-index/dist/index.js +34 -28
  148. package/packages/tools/sync-memory-index/dist/tsconfig.tsbuildinfo +1 -1
  149. package/packages/tools/sync-memory-index/index.ts +37 -28
  150. package/packages/tools/sync-memory-index/package.json +11 -4
  151. package/packages/tools/validate-openai-agent-config/dist/index.js +13 -7
  152. package/packages/tools/validate-openai-agent-config/dist/tsconfig.tsbuildinfo +1 -1
  153. package/packages/tools/validate-openai-agent-config/index.ts +13 -7
  154. package/packages/tools/validate-openai-agent-config/package.json +11 -4
  155. package/packages/tools/validate-skill-frontmatter/dist/index.js +12 -6
  156. package/packages/tools/validate-skill-frontmatter/dist/tsconfig.tsbuildinfo +1 -1
  157. package/packages/tools/validate-skill-frontmatter/index.ts +12 -6
  158. package/packages/tools/validate-skill-frontmatter/package.json +11 -4
  159. package/packages/tui/dist/index.d.ts +2 -1
  160. package/packages/tui/dist/index.js +1 -0
  161. package/packages/tui/dist/stdio-adapter.d.ts +36 -0
  162. package/packages/tui/dist/stdio-adapter.js +69 -0
  163. package/packages/tui/dist/terminal.js +3 -1
  164. package/packages/tui/dist/tsconfig.tsbuildinfo +1 -1
  165. package/packages/tui/dist/types.d.ts +17 -0
  166. package/packages/tui/index.ts +2 -1
  167. package/packages/tui/package.json +14 -6
  168. package/packages/tui/stdio-adapter.ts +85 -0
  169. package/packages/tui/terminal.ts +3 -1
  170. package/packages/tui/tsconfig.json +5 -2
  171. package/packages/tui/types.ts +19 -0
  172. package/resources/project-architecture/assets/architecture.css +2 -1
  173. package/resources/project-architecture/atlas/atlas.history.log +1 -0
  174. package/resources/project-architecture/atlas/atlas.history.undo.json +13 -2
  175. package/resources/project-architecture/atlas/atlas.history.undo.stack.json +610 -0
  176. package/resources/project-architecture/atlas/atlas.index.yaml +81 -5
  177. package/resources/project-architecture/atlas/features/cli-dispatch.yaml +43 -0
  178. package/resources/project-architecture/atlas/features/terminal-ui.yaml +29 -0
  179. package/resources/project-architecture/atlas/features/tool-registry.yaml +22 -0
  180. package/resources/project-architecture/atlas/features/tool-utils.yaml +22 -0
  181. package/resources/project-architecture/features/cli-dispatch/arg-parser.html +40 -0
  182. package/resources/project-architecture/features/cli-dispatch/help-builder.html +40 -0
  183. package/resources/project-architecture/features/cli-dispatch/index.html +64 -0
  184. package/resources/project-architecture/features/cli-dispatch/installer-core.html +40 -0
  185. package/resources/project-architecture/features/cli-dispatch/tool-discovery.html +40 -0
  186. package/resources/project-architecture/features/cli-dispatch/update-checker.html +40 -0
  187. package/resources/project-architecture/features/terminal-ui/banner-display.html +40 -0
  188. package/resources/project-architecture/features/terminal-ui/index.html +50 -0
  189. package/resources/project-architecture/features/terminal-ui/interactive-prompts.html +40 -0
  190. package/resources/project-architecture/features/terminal-ui/terminal-detection.html +40 -0
  191. package/resources/project-architecture/features/tool-registry/formatter.html +40 -0
  192. package/resources/project-architecture/features/tool-registry/index.html +43 -0
  193. package/resources/project-architecture/features/tool-registry/registry-core.html +40 -0
  194. package/resources/project-architecture/features/tool-utils/index.html +43 -0
  195. package/resources/project-architecture/features/tool-utils/log-utils.html +40 -0
  196. package/resources/project-architecture/features/tool-utils/skill-discovery.html +40 -0
  197. package/resources/project-architecture/index.html +365 -121
  198. package/scripts/rewrite-imports.mjs +2 -2
  199. package/scripts/test.sh +144 -8
  200. package/skills/design/SKILL.md +57 -64
  201. package/skills/design/assets/templates/DESIGN.md +12 -0
  202. package/skills/design/references/code-smells.md +94 -0
  203. package/skills/design/references/module-boundary-adjustment.md +126 -0
  204. package/skills/design/references/module-internal-restructuring.md +132 -0
  205. package/skills/design/references/module-internal-simplification.md +164 -0
@@ -1,6 +1,16 @@
1
+ /*
2
+ * review-threads — Carryover tool migrated from the pre-monorepo CLI
3
+ * architecture (originally resolve-review-comments/scripts/review_threads.py).
4
+ *
5
+ * Carryover status (Round 16, 2026-06-06):
6
+ * - Added --help/-h support for subcommand-based usage.
7
+ * - Stdout/stderr invariant: resolved data -> stdout, failures -> stderr.
8
+ */
9
+
1
10
  import { execFile } from 'node:child_process';
2
11
  import { readFileSync } from 'node:fs';
3
12
  import type { ToolDefinition, ToolContext } from '@laitszkin/tool-registry';
13
+ import { UserInputError, SystemError } from '@laitszkin/tool-utils';
4
14
 
5
15
  const LIST_QUERY = `
6
16
  query($owner: String!, $name: String!, $number: Int!, $after: String) {
@@ -60,6 +70,7 @@ interface ReviewThreadsArgs {
60
70
  threadIdFile: string | null;
61
71
  allUnresolved: boolean;
62
72
  dryRun: boolean;
73
+ help: boolean;
63
74
  }
64
75
 
65
76
  function parseArgs(argv: string[]): ReviewThreadsArgs {
@@ -73,8 +84,15 @@ function parseArgs(argv: string[]): ReviewThreadsArgs {
73
84
  threadIdFile: null,
74
85
  allUnresolved: false,
75
86
  dryRun: false,
87
+ help: false,
76
88
  };
77
89
 
90
+ // Check for --help/-h before positional parsing
91
+ if (argv.includes('--help') || argv.includes('-h')) {
92
+ args.help = true;
93
+ return args;
94
+ }
95
+
78
96
  // First argument is the subcommand (list/resolve)
79
97
  let i = 0;
80
98
  if (i < argv.length && !argv[i].startsWith('-')) {
@@ -158,12 +176,12 @@ function runGh(cmdArgs: string[]): Promise<CommandResult> {
158
176
  function runGhJson(cmdArgs: string[]): Promise<Record<string, unknown>> {
159
177
  return runGh(cmdArgs).then((result) => {
160
178
  if (result.exitCode !== 0) {
161
- throw new Error(result.stderr.trim() || 'gh command failed');
179
+ throw new SystemError(result.stderr.trim() || 'gh command failed');
162
180
  }
163
181
  try {
164
182
  return JSON.parse(result.stdout);
165
183
  } catch (exc) {
166
- throw new Error('Failed to parse gh JSON output');
184
+ throw new SystemError('Failed to parse gh JSON output', undefined, { cause: exc });
167
185
  }
168
186
  });
169
187
  }
@@ -171,7 +189,7 @@ function runGhJson(cmdArgs: string[]): Promise<Record<string, unknown>> {
171
189
  function parseOwnerRepo(repo: string): [string, string] {
172
190
  const parts = repo.split('/');
173
191
  if (parts.length !== 2 || !parts[0] || !parts[1]) {
174
- throw new Error('repo must be in owner/name format');
192
+ throw new UserInputError('repo must be in owner/name format');
175
193
  }
176
194
  return [parts[0], parts[1]];
177
195
  }
@@ -191,7 +209,7 @@ async function resolveRepo(repo: string | null): Promise<string> {
191
209
  '.nameWithOwner',
192
210
  ]);
193
211
  if (result.exitCode !== 0) {
194
- throw new Error(result.stderr.trim() || 'Unable to resolve current repo');
212
+ throw new SystemError(result.stderr.trim() || 'Unable to resolve current repo');
195
213
  }
196
214
  return result.stdout.trim();
197
215
  }
@@ -210,7 +228,7 @@ async function resolvePrNumber(repo: string, pr: number | null): Promise<number>
210
228
  '.number',
211
229
  ]);
212
230
  if (result.exitCode !== 0) {
213
- throw new Error(
231
+ throw new UserInputError(
214
232
  'Unable to infer PR number from current branch context',
215
233
  );
216
234
  }
@@ -248,7 +266,7 @@ async function fetchReviewThreads(
248
266
 
249
267
  const pr = (payload.data as Record<string, unknown>)?.repository as Record<string, unknown> | undefined;
250
268
  if (!pr) {
251
- throw new Error(`PR #${prNumber} not found in ${repo}`);
269
+ throw new UserInputError(`PR #${prNumber} not found in ${repo}`);
252
270
  }
253
271
 
254
272
  const reviewThreads = pr.reviewThreads as Record<string, unknown>;
@@ -381,12 +399,12 @@ function loadThreadIds(filePath: string): string[] {
381
399
  .map((item) => item.thread_id)
382
400
  .filter((id) => id !== undefined);
383
401
  } else {
384
- throw new Error(
402
+ throw new UserInputError(
385
403
  'JSON must include thread_ids, adopted_thread_ids, or threads',
386
404
  );
387
405
  }
388
406
  } else {
389
- throw new Error('Unsupported JSON payload for thread IDs');
407
+ throw new UserInputError('Unsupported JSON payload for thread IDs');
390
408
  }
391
409
 
392
410
  const output = ids
@@ -438,11 +456,11 @@ async function resolveThreads(
438
456
  payload.data as Record<string, unknown>
439
457
  )?.resolveReviewThread as Record<string, unknown> | undefined;
440
458
  if (!thread?.thread) {
441
- throw new Error('thread did not resolve');
459
+ throw new SystemError('thread did not resolve');
442
460
  }
443
461
  const resolvedThread = thread.thread as Record<string, unknown>;
444
462
  if (!resolvedThread.isResolved) {
445
- throw new Error('thread did not resolve');
463
+ throw new SystemError('thread did not resolve');
446
464
  }
447
465
  resolved.push(threadId);
448
466
  } catch (exc) {
@@ -459,31 +477,11 @@ async function cmdList(
459
477
  args: ReviewThreadsArgs,
460
478
  context: ToolContext,
461
479
  ): Promise<number> {
462
- const { stdout, stderr } = context;
463
-
464
- let repo: string;
465
- try {
466
- repo = await resolveRepo(args.repo);
467
- } catch (err) {
468
- stderr!.write(`Error: ${(err as Error).message}\n`);
469
- return 1;
470
- }
471
-
472
- let prNumber: number;
473
- try {
474
- prNumber = await resolvePrNumber(repo, args.pr);
475
- } catch (err) {
476
- stderr!.write(`Error: ${(err as Error).message}\n`);
477
- return 1;
478
- }
480
+ const { stdout } = context;
479
481
 
480
- let threads: Array<Record<string, unknown>>;
481
- try {
482
- threads = await fetchReviewThreads(repo, prNumber);
483
- } catch (err) {
484
- stderr!.write(`Error: ${(err as Error).message}\n`);
485
- return 1;
486
- }
482
+ const repo = await resolveRepo(args.repo);
483
+ const prNumber = await resolvePrNumber(repo, args.pr);
484
+ const threads: Array<Record<string, unknown>> = await fetchReviewThreads(repo, prNumber);
487
485
 
488
486
  const filtered = filterThreads(threads, args.state);
489
487
  const normalized = filtered.map(normalizeThread);
@@ -512,79 +510,87 @@ async function cmdResolve(
512
510
  args: ReviewThreadsArgs,
513
511
  context: ToolContext,
514
512
  ): Promise<number> {
515
- const { stdout, stderr } = context;
516
-
517
- let repo: string;
518
- try {
519
- repo = await resolveRepo(args.repo);
520
- } catch (err) {
521
- stderr!.write(`Error: ${(err as Error).message}\n`);
522
- return 1;
523
- }
513
+ const { stdout } = context;
524
514
 
525
- let prNumber: number;
526
- try {
527
- prNumber = await resolvePrNumber(repo, args.pr);
528
- } catch (err) {
529
- stderr!.write(`Error: ${(err as Error).message}\n`);
530
- return 1;
531
- }
532
-
533
- let threads: Array<Record<string, unknown>>;
534
- try {
535
- threads = await fetchReviewThreads(repo, prNumber);
536
- } catch (err) {
537
- stderr!.write(`Error: ${(err as Error).message}\n`);
538
- return 1;
539
- }
515
+ const repo = await resolveRepo(args.repo);
516
+ const prNumber = await resolvePrNumber(repo, args.pr);
517
+ const threads: Array<Record<string, unknown>> = await fetchReviewThreads(repo, prNumber);
540
518
 
541
519
  const unresolved = filterThreads(threads, 'unresolved').map(normalizeThread);
542
520
  const threadIds = collectThreadIds(args, unresolved);
543
521
 
544
522
  if (threadIds.length === 0) {
545
- stderr!.write(
546
- 'Error: no thread IDs selected. Use --thread-id, --thread-id-file, or --all-unresolved.\n',
523
+ throw new UserInputError(
524
+ 'no thread IDs selected. Use --thread-id, --thread-id-file, or --all-unresolved.',
547
525
  );
548
- return 1;
549
526
  }
550
527
 
551
528
  const { resolved, failed } = await resolveThreads(threadIds, args.dryRun);
552
529
 
553
- const summary = {
554
- repo,
555
- pr_number: prNumber,
556
- requested: threadIds,
557
- resolved,
558
- failed,
559
- dry_run: args.dryRun,
560
- };
561
- stdout!.write(JSON.stringify(summary, null, 2) + '\n');
530
+ stdout!.write(JSON.stringify({ resolved }, null, 2) + '\n');
531
+ context.stderr!.write(JSON.stringify({ failed }, null, 2) + '\n');
562
532
 
563
533
  return failed.length > 0 ? 1 : 0;
564
534
  }
565
535
 
536
+ // ---- Help ----
537
+
538
+ function printHelp(stdout: NodeJS.WriteStream): void {
539
+ stdout.write(`Usage: apltk review-threads <subcommand> [options]
540
+
541
+ List or resolve GitHub PR review threads.
542
+
543
+ Subcommands:
544
+ list List review threads for a pull request
545
+ resolve Resolve specific review threads
546
+
547
+ Options:
548
+ --repo <owner/name> GitHub repository (default: current repo)
549
+ --pr <number> PR number (default: current branch's PR)
550
+ --state <unresolved|resolved|all>
551
+ Filter threads by state (default: unresolved)
552
+ --output <table|json> Output format for list (default: table)
553
+ --thread-id <id> Thread ID to resolve (may be repeated)
554
+ --thread-id-file <path> JSON file with thread IDs to resolve
555
+ --all-unresolved Resolve all unresolved threads
556
+ --dry-run Simulate resolution without mutating
557
+ --help, -h Show this help message
558
+ `);
559
+ }
560
+
566
561
  // ---- Main handler ----
567
562
 
563
+ /**
564
+ * reviewThreadsHandler — Known carryover from createToolRunner migration.
565
+ *
566
+ * Reason for not using createToolRunner:
567
+ * - Positional subcommand architecture (list/resolve) doesn't map cleanly
568
+ * to createToolRunner's options schema.
569
+ * - Error handling follows the AppError convention (UserInputError/SystemError
570
+ * throws) — errors propagate to the CLI boundary's formatAppError.
571
+ * - Argument parsing is handled manually via a 63-line parseArgs().
572
+ *
573
+ * See DESIGN.md §2.3 for the full architecture discussion.
574
+ */
568
575
  export async function reviewThreadsHandler(
569
576
  argv: string[],
570
577
  context: ToolContext,
571
578
  ): Promise<number> {
572
- const { stderr } = context;
579
+ const { stdout } = context;
573
580
  const args = parseArgs(argv);
574
581
 
575
- try {
576
- switch (args.command) {
577
- case 'list':
578
- return await cmdList(args, context);
579
- case 'resolve':
580
- return await cmdResolve(args, context);
581
- default:
582
- stderr!.write(`Unsupported command: ${args.command}\n`);
583
- return 1;
584
- }
585
- } catch (err) {
586
- stderr!.write(`Error: ${(err as Error).message}\n`);
587
- return 1;
582
+ if (args.help) {
583
+ printHelp(stdout!);
584
+ return 0;
585
+ }
586
+
587
+ switch (args.command) {
588
+ case 'list':
589
+ return await cmdList(args, context);
590
+ case 'resolve':
591
+ return await cmdResolve(args, context);
592
+ default:
593
+ throw new UserInputError(`Unsupported command: ${args.command}`);
588
594
  }
589
595
  }
590
596
 
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@laitszkin/tool-review-threads",
3
- "version": "4.0.8",
4
- "description": "Apollo Toolkit CLI tool",
3
+ "version": "5.0.0",
4
+ "description": "Apollo Toolkit \u2014 CLI tool",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "main": "./dist/index.js",
@@ -18,5 +18,12 @@
18
18
  },
19
19
  "dependencies": {
20
20
  "@laitszkin/tool-registry": "*"
21
- }
22
- }
21
+ },
22
+ "publishConfig": {
23
+ "access": "public",
24
+ "registry": "https://registry.npmjs.org"
25
+ },
26
+ "files": [
27
+ "dist"
28
+ ]
29
+ }
@@ -1,75 +1,15 @@
1
- import { extractTimestamp, inWindow, iterInputLines, parseCliTimestamp, buildTimezone, validateTimeWindow, } from '../../../tool-utils/dist/index.js';
2
- function parseArgs(argv) {
3
- const args = {
4
- paths: [],
5
- keyword: [],
6
- regex: [],
7
- mode: 'any',
8
- ignoreCase: false,
9
- start: null,
10
- end: null,
11
- assumeTimezone: 'UTC',
12
- beforeContext: 0,
13
- afterContext: 0,
14
- countOnly: false,
15
- };
16
- let i = 0;
17
- while (i < argv.length) {
18
- const arg = argv[i];
19
- if (arg === '--keyword' && i + 1 < argv.length) {
20
- args.keyword.push(argv[++i]);
21
- }
22
- else if (arg === '--regex' && i + 1 < argv.length) {
23
- args.regex.push(argv[++i]);
24
- }
25
- else if (arg === '--mode' && i + 1 < argv.length) {
26
- const val = argv[++i];
27
- if (val === 'any' || val === 'all') {
28
- args.mode = val;
29
- }
30
- }
31
- else if (arg === '--ignore-case') {
32
- args.ignoreCase = true;
33
- }
34
- else if (arg === '--start' && i + 1 < argv.length) {
35
- args.start = argv[++i];
36
- }
37
- else if (arg === '--end' && i + 1 < argv.length) {
38
- args.end = argv[++i];
39
- }
40
- else if (arg === '--assume-timezone' && i + 1 < argv.length) {
41
- args.assumeTimezone = argv[++i];
42
- }
43
- else if (arg === '--before-context' && i + 1 < argv.length) {
44
- args.beforeContext = parseInt(argv[++i], 10) || 0;
45
- }
46
- else if (arg === '--after-context' && i + 1 < argv.length) {
47
- args.afterContext = parseInt(argv[++i], 10) || 0;
48
- }
49
- else if (arg === '--count-only') {
50
- args.countOnly = true;
51
- }
52
- else if (arg.startsWith('-')) {
53
- // skip unknown flags
54
- }
55
- else {
56
- args.paths.push(arg);
57
- }
58
- i++;
59
- }
60
- return args;
61
- }
62
- function buildMatchers(args) {
1
+ import { UserInputError, SystemError, createToolRunner, extractTimestamp, inWindow, iterInputLines, parseCliTimestamp, buildTimezone, } from '../../../tool-utils/dist/index.js';
2
+ function buildMatchers(keywords, regexPatterns, ignoreCase, mode) {
63
3
  const matchers = [];
64
- for (const keyword of args.keyword) {
65
- const needle = args.ignoreCase ? keyword.toLowerCase() : keyword;
4
+ for (const keyword of keywords) {
5
+ const needle = ignoreCase ? keyword.toLowerCase() : keyword;
66
6
  matchers.push((line) => {
67
- const haystack = args.ignoreCase ? line.toLowerCase() : line;
7
+ const haystack = ignoreCase ? line.toLowerCase() : line;
68
8
  return haystack.includes(needle);
69
9
  });
70
10
  }
71
- for (const pattern of args.regex) {
72
- const flags = args.ignoreCase ? 'i' : '';
11
+ for (const pattern of regexPatterns) {
12
+ const flags = ignoreCase ? 'i' : '';
73
13
  const compiled = new RegExp(pattern, flags);
74
14
  matchers.push((line) => compiled.test(line));
75
15
  }
@@ -83,86 +23,110 @@ function lineMatches(line, matchers, mode) {
83
23
  }
84
24
  return matchers.every((m) => m(line));
85
25
  }
86
- async function searchLogsHandler(argv, context) {
87
- const stdout = context.stdout ?? process.stdout;
88
- const stderr = context.stderr ?? process.stderr;
89
- const args = parseArgs(argv);
90
- try {
91
- buildTimezone(args.assumeTimezone);
92
- }
93
- catch (err) {
94
- stderr.write(`Error: invalid timezone: ${args.assumeTimezone}\n`);
95
- return 1;
96
- }
97
- let start = null;
98
- let end = null;
99
- try {
100
- if (args.start) {
101
- start = parseCliTimestamp(args.start, args.assumeTimezone);
26
+ const schema = {
27
+ options: {
28
+ keyword: { type: 'string', multiple: true },
29
+ regex: { type: 'string', multiple: true },
30
+ mode: { type: 'string', default: 'any' },
31
+ 'ignore-case': { type: 'boolean', default: false },
32
+ start: { type: 'string' },
33
+ end: { type: 'string' },
34
+ 'assume-timezone': { type: 'string', default: 'UTC' },
35
+ 'before-context': { type: 'string', default: '0' },
36
+ 'after-context': { type: 'string', default: '0' },
37
+ 'count-only': { type: 'boolean', default: false },
38
+ },
39
+ allowPositionals: true,
40
+ strict: true,
41
+ usage: 'apltk search-logs [options] [<file>...]',
42
+ description: 'Search log lines by keywords or regex patterns with time filters',
43
+ handler: async (values, positionals, context) => {
44
+ const stdout = context.stdout ?? process.stdout;
45
+ const mode = values.mode;
46
+ if (mode !== 'any' && mode !== 'all') {
47
+ throw new UserInputError('--mode must be "any" or "all"');
102
48
  }
103
- if (args.end) {
104
- end = parseCliTimestamp(args.end, args.assumeTimezone);
49
+ const keywords = values.keyword || [];
50
+ const regexPatterns = values.regex || [];
51
+ const ignoreCase = values['ignore-case'];
52
+ const assumeTimezone = values['assume-timezone'];
53
+ const beforeContext = parseInt(values['before-context'], 10) || 0;
54
+ const afterContext = parseInt(values['after-context'], 10) || 0;
55
+ const countOnly = values['count-only'];
56
+ try {
57
+ buildTimezone(assumeTimezone);
105
58
  }
106
- }
107
- catch (err) {
108
- stderr.write(`Error: ${err.message}\n`);
109
- return 1;
110
- }
111
- if (!validateTimeWindow(start, end, stderr)) {
112
- return 1;
113
- }
114
- const matchers = buildMatchers(args);
115
- let matches = 0;
116
- const beforeBuffer = [];
117
- let afterRemaining = 0;
118
- try {
119
- for await (const line of iterInputLines(args.paths)) {
120
- const timestamp = extractTimestamp(line, args.assumeTimezone);
121
- // When time filter is active, skip lines outside the window
122
- if (args.start || args.end) {
123
- if (!inWindow(timestamp, start, end)) {
124
- beforeBuffer.push(line);
125
- if (beforeBuffer.length > args.beforeContext) {
126
- beforeBuffer.shift();
59
+ catch {
60
+ throw new UserInputError(`invalid timezone: ${assumeTimezone}`);
61
+ }
62
+ let start = null;
63
+ let end = null;
64
+ try {
65
+ if (values.start) {
66
+ start = parseCliTimestamp(values.start, assumeTimezone);
67
+ }
68
+ if (values.end) {
69
+ end = parseCliTimestamp(values.end, assumeTimezone);
70
+ }
71
+ }
72
+ catch (err) {
73
+ throw new UserInputError(err.message);
74
+ }
75
+ if (start && end && start > end) {
76
+ throw new UserInputError('--start must be earlier than or equal to --end.');
77
+ }
78
+ const matchers = buildMatchers(keywords, regexPatterns, ignoreCase, mode);
79
+ let matches = 0;
80
+ const beforeBuffer = [];
81
+ let afterRemaining = 0;
82
+ try {
83
+ for await (const line of iterInputLines(positionals)) {
84
+ const timestamp = extractTimestamp(line, assumeTimezone);
85
+ // When time filter is active, skip lines outside the window
86
+ if (values.start || values.end) {
87
+ if (!inWindow(timestamp, start, end)) {
88
+ beforeBuffer.push(line);
89
+ if (beforeBuffer.length > beforeContext) {
90
+ beforeBuffer.shift();
91
+ }
92
+ continue;
127
93
  }
128
- continue;
129
94
  }
130
- }
131
- const isMatch = lineMatches(line, matchers, args.mode);
132
- if (isMatch) {
133
- matches++;
134
- if (!args.countOnly) {
135
- // Flush before context
136
- for (const ctxLine of beforeBuffer) {
137
- stdout.write(ctxLine + '\n');
95
+ const isMatch = lineMatches(line, matchers, mode);
96
+ if (isMatch) {
97
+ matches++;
98
+ if (!countOnly) {
99
+ // Flush before context
100
+ for (const ctxLine of beforeBuffer) {
101
+ stdout.write(ctxLine + '\n');
102
+ }
103
+ stdout.write(line + '\n');
138
104
  }
105
+ afterRemaining = afterContext;
106
+ }
107
+ else if (afterRemaining > 0 && !countOnly) {
139
108
  stdout.write(line + '\n');
109
+ afterRemaining--;
110
+ }
111
+ // Maintain before context buffer
112
+ beforeBuffer.push(line);
113
+ if (beforeBuffer.length > beforeContext) {
114
+ beforeBuffer.shift();
140
115
  }
141
- afterRemaining = args.afterContext;
142
- }
143
- else if (afterRemaining > 0 && !args.countOnly) {
144
- stdout.write(line + '\n');
145
- afterRemaining--;
146
- }
147
- // Maintain before context buffer
148
- beforeBuffer.push(line);
149
- if (beforeBuffer.length > args.beforeContext) {
150
- beforeBuffer.shift();
151
116
  }
152
117
  }
153
- }
154
- catch (err) {
155
- stderr.write(`Error: ${err.message}\n`);
156
- return 1;
157
- }
158
- if (args.countOnly) {
159
- stdout.write(String(matches) + '\n');
160
- }
161
- return 0;
162
- }
118
+ catch (err) {
119
+ throw new SystemError(err.message);
120
+ }
121
+ if (countOnly) {
122
+ stdout.write(String(matches) + '\n');
123
+ }
124
+ return 0;
125
+ },
126
+ };
163
127
  export const tool = {
164
128
  name: 'search-logs',
165
129
  category: 'Log Analysis',
166
130
  description: 'Search log lines by keywords or regex patterns with time filters',
167
- handler: searchLogsHandler,
131
+ handler: createToolRunner(schema),
168
132
  };