@lincoln504/pi-research 0.0.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 (75) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +131 -0
  3. package/config/searxng/default-settings.yml +189 -0
  4. package/config/searxng/limiter.toml +34 -0
  5. package/config/tooling/eslint.config.js +73 -0
  6. package/config/tooling/vitest.config.integration.ts +17 -0
  7. package/config/tooling/vitest.config.ts +40 -0
  8. package/config/tooling/vitest.config.unit.ts +12 -0
  9. package/docs/ARCHITECTURE.md +109 -0
  10. package/docs/assets/README-banner.jpg +0 -0
  11. package/package.json +92 -0
  12. package/scripts/setup.js +174 -0
  13. package/src/config.ts +206 -0
  14. package/src/constants.ts +148 -0
  15. package/src/healthcheck/index.ts +309 -0
  16. package/src/index.ts +71 -0
  17. package/src/infrastructure/searxng-lifecycle.ts +577 -0
  18. package/src/infrastructure/searxng-manager.ts +1173 -0
  19. package/src/infrastructure/state-manager.ts +918 -0
  20. package/src/logger.ts +246 -0
  21. package/src/orchestration/deep-research-orchestrator.ts +848 -0
  22. package/src/orchestration/deep-research-reducer.ts +129 -0
  23. package/src/orchestration/deep-research-types.ts +54 -0
  24. package/src/orchestration/id-utils.ts +63 -0
  25. package/src/orchestration/researcher.ts +104 -0
  26. package/src/orchestration/session-context.ts +43 -0
  27. package/src/prompts/research-tool-usage.md +45 -0
  28. package/src/prompts/researcher.md +166 -0
  29. package/src/prompts/system-coordinator.md +24 -0
  30. package/src/prompts/system-lead-evaluator.md +225 -0
  31. package/src/security/cisa-kev.ts +242 -0
  32. package/src/security/github-advisories.ts +607 -0
  33. package/src/security/index.ts +406 -0
  34. package/src/security/nvd.ts +532 -0
  35. package/src/security/osv.ts +487 -0
  36. package/src/security/types.ts +138 -0
  37. package/src/stackexchange/cache.ts +111 -0
  38. package/src/stackexchange/index.ts +352 -0
  39. package/src/stackexchange/output/compact.ts +64 -0
  40. package/src/stackexchange/output/index.ts +7 -0
  41. package/src/stackexchange/output/json.ts +21 -0
  42. package/src/stackexchange/output/table.ts +123 -0
  43. package/src/stackexchange/queries.ts +168 -0
  44. package/src/stackexchange/rest-client.ts +127 -0
  45. package/src/stackexchange/types.ts +129 -0
  46. package/src/tool.ts +430 -0
  47. package/src/tools/grep.ts +335 -0
  48. package/src/tools/index.ts +77 -0
  49. package/src/tools/scrape.ts +451 -0
  50. package/src/tools/search.ts +188 -0
  51. package/src/tools/security.ts +292 -0
  52. package/src/tools/stackexchange.ts +101 -0
  53. package/src/tui/research-panel.ts +576 -0
  54. package/src/tui/searxng-status.ts +80 -0
  55. package/src/types/extension-context.ts +60 -0
  56. package/src/types/llm.ts +111 -0
  57. package/src/utils/inject-date.ts +29 -0
  58. package/src/utils/input-validation.ts +103 -0
  59. package/src/utils/json-utils.ts +262 -0
  60. package/src/utils/make-resource-loader.ts +43 -0
  61. package/src/utils/research-error.ts +205 -0
  62. package/src/utils/research-export.ts +199 -0
  63. package/src/utils/searxng-config.ts +94 -0
  64. package/src/utils/session-state.ts +295 -0
  65. package/src/utils/shared-links.ts +123 -0
  66. package/src/utils/shutdown-manager.ts +54 -0
  67. package/src/utils/text-utils.ts +83 -0
  68. package/src/utils/tool-usage-tracker.ts +135 -0
  69. package/src/web-research/dependencies.ts +20 -0
  70. package/src/web-research/retry-utils.ts +232 -0
  71. package/src/web-research/scrapers.ts +935 -0
  72. package/src/web-research/search.ts +235 -0
  73. package/src/web-research/types.ts +47 -0
  74. package/src/web-research/utils.ts +158 -0
  75. package/tsconfig.json +40 -0
package/src/logger.ts ADDED
@@ -0,0 +1,246 @@
1
+ /**
2
+ * Logger — scoped file-based diagnostics
3
+ *
4
+ * Silent by default. When --verbose or PI_RESEARCH_VERBOSE=1 is set,
5
+ * writes timestamped lines to {tmpdir}/pi-research-debug-{hash}.log where {hash}
6
+ * is a random 4-character alphanumeric suffix (a-z, 0-9) to keep logs separate per run.
7
+ *
8
+ * When NOT verbose: logFile is null, preventing all temp-dir writes.
9
+ * This module never patches process-global console.* methods.
10
+ */
11
+
12
+ import { appendFileSync } from 'node:fs';
13
+ import { AsyncLocalStorage } from 'node:async_hooks';
14
+ import { randomBytes } from 'node:crypto';
15
+ import * as os from 'node:os';
16
+ import * as path from 'node:path';
17
+
18
+ /**
19
+ * Log level enum
20
+ */
21
+ export enum LogLevel {
22
+ INFO = 'INFO',
23
+ ERROR = 'ERROR',
24
+ WARN = 'WARN',
25
+ DEBUG = 'DEBUG',
26
+ }
27
+
28
+ /**
29
+ * Logger interface for dependency injection
30
+ */
31
+ export interface ILogger {
32
+ log(...args: unknown[]): void;
33
+ info(...args: unknown[]): void;
34
+ error(...args: unknown[]): void;
35
+ warn(...args: unknown[]): void;
36
+ debug(...args: unknown[]): void;
37
+ }
38
+
39
+ /**
40
+ * Logger configuration options
41
+ */
42
+ export interface LoggerOptions {
43
+ verbose: boolean;
44
+ logFilePath?: string;
45
+ }
46
+
47
+ export interface LogContext {
48
+ sessionId?: string;
49
+ sessionFile?: string;
50
+ cwd?: string;
51
+ researchRunId?: string;
52
+ toolName?: string;
53
+ phase?: string;
54
+ eventName?: string;
55
+ }
56
+
57
+ const logContextStorage = new AsyncLocalStorage<LogContext>();
58
+
59
+ /**
60
+ * Generate a 4-character alphanumeric hash for uniqueness
61
+ */
62
+ function generateHash(): string {
63
+ const chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
64
+ let hash = '';
65
+ for (let i = 0; i < 4; i++) {
66
+ hash += chars.charAt(Math.floor(Math.random() * chars.length));
67
+ }
68
+ return hash;
69
+ }
70
+
71
+ function buildDefaultDebugLogPath(hash: string): string {
72
+ return path.join(os.tmpdir(), `pi-research-debug-${hash}.log`);
73
+ }
74
+
75
+ export function getDefaultDebugLogPathTemplate(): string {
76
+ return buildDefaultDebugLogPath('{hash}');
77
+ }
78
+
79
+ /**
80
+ * Check if verbose mode is enabled from environment
81
+ */
82
+ export function isVerboseFromEnv(): boolean {
83
+ return process.argv.includes('--verbose') || process.env['PI_RESEARCH_VERBOSE'] === '1';
84
+ }
85
+
86
+ export function createResearchRunId(): string {
87
+ return `run-${randomBytes(4).toString('hex')}`;
88
+ }
89
+
90
+ export function getLogContext(): LogContext {
91
+ return logContextStorage.getStore() ?? {};
92
+ }
93
+
94
+ export function runWithLogContext<T>(context: LogContext, callback: () => T): T {
95
+ const parent = logContextStorage.getStore() ?? {};
96
+ return logContextStorage.run({ ...parent, ...context }, callback);
97
+ }
98
+
99
+ function safeJsonStringify(value: unknown): string {
100
+ try {
101
+ return JSON.stringify(value, null, 0);
102
+ } catch {
103
+ return '[unserializable]';
104
+ }
105
+ }
106
+
107
+ function formatArg(arg: unknown): string {
108
+ if (arg instanceof Error) {
109
+ return arg.stack ?? arg.message;
110
+ }
111
+ if (typeof arg === 'object' && arg !== null) {
112
+ return safeJsonStringify(arg);
113
+ }
114
+ return String(arg);
115
+ }
116
+
117
+ /**
118
+ * Logger implementation — writes to file when verbose, silent otherwise
119
+ */
120
+ export class Logger implements ILogger {
121
+ private verbose: boolean;
122
+ private logFile: string | null;
123
+
124
+ constructor(options: Partial<LoggerOptions> = {}) {
125
+ this.verbose = options.verbose ?? isVerboseFromEnv();
126
+ // Only set logFile path if verbose mode is enabled
127
+ if (this.verbose) {
128
+ const hash = generateHash();
129
+ this.logFile = options.logFilePath ?? buildDefaultDebugLogPath(hash);
130
+ } else {
131
+ this.logFile = null;
132
+ }
133
+ }
134
+
135
+ /**
136
+ * Emit a log message to file (if verbose) or nowhere (if silent)
137
+ * @param level - Log level (INFO, ERROR, WARN, DEBUG)
138
+ * @param args - Arguments to log
139
+ */
140
+ private emit(level: string, ...args: unknown[]): void {
141
+ if (!this.verbose || !this.logFile) {
142
+ return;
143
+ }
144
+
145
+ const timestamp = new Date().toISOString();
146
+ const firstError = args.find((arg): arg is Error => arg instanceof Error);
147
+ const entry = {
148
+ timestamp,
149
+ level,
150
+ ...getLogContext(),
151
+ message: args.map(formatArg).join(' '),
152
+ ...(firstError
153
+ ? { errorMessage: firstError.message, errorStack: firstError.stack }
154
+ : {}),
155
+ };
156
+ const line = `${safeJsonStringify(entry)}\n`;
157
+
158
+ // Write to file
159
+ try {
160
+ appendFileSync(this.logFile, line);
161
+ } catch {
162
+ // Silently ignore file write errors
163
+ }
164
+ }
165
+
166
+ log(...args: unknown[]): void {
167
+ this.emit(LogLevel.INFO, ...args);
168
+ }
169
+
170
+ info(...args: unknown[]): void {
171
+ this.emit(LogLevel.INFO, ...args);
172
+ }
173
+
174
+ error(...args: unknown[]): void {
175
+ this.emit(LogLevel.ERROR, ...args);
176
+ }
177
+
178
+ warn(...args: unknown[]): void {
179
+ this.emit(LogLevel.WARN, ...args);
180
+ }
181
+
182
+ debug(...args: unknown[]): void {
183
+ this.emit(LogLevel.DEBUG, ...args);
184
+ }
185
+
186
+ /**
187
+ * Get the log file path
188
+ */
189
+ getLogFilePath(): string | null {
190
+ return this.logFile;
191
+ }
192
+
193
+ /**
194
+ * Check if verbose mode is enabled
195
+ */
196
+ isVerbose(): boolean {
197
+ return this.verbose;
198
+ }
199
+ }
200
+
201
+ /**
202
+ * Global logger instance
203
+ */
204
+ let globalLogger: Logger | null = null;
205
+
206
+ /**
207
+ * Create a new logger instance
208
+ */
209
+ export function createLogger(options: Partial<LoggerOptions> = {}): Logger {
210
+ return new Logger(options);
211
+ }
212
+
213
+ /**
214
+ * Get the global logger instance (singleton)
215
+ */
216
+ export function getLogger(): Logger {
217
+ if (!globalLogger) {
218
+ globalLogger = createLogger({ verbose: isVerboseFromEnv() });
219
+ }
220
+ return globalLogger;
221
+ }
222
+
223
+ /**
224
+ * Set the global logger instance (for testing)
225
+ */
226
+ export function setLogger(logger: Logger): void {
227
+ globalLogger = logger;
228
+ }
229
+
230
+ /**
231
+ * Reset the global logger instance (for testing)
232
+ */
233
+ export function resetLogger(): void {
234
+ globalLogger = null;
235
+ }
236
+
237
+ /**
238
+ * Logger singleton for backward compatibility
239
+ */
240
+ export const logger = {
241
+ log: (...args: unknown[]) => getLogger().log(...args),
242
+ info: (...args: unknown[]) => getLogger().info(...args),
243
+ error: (...args: unknown[]) => getLogger().error(...args),
244
+ warn: (...args: unknown[]) => getLogger().warn(...args),
245
+ debug: (...args: unknown[]) => getLogger().debug(...args),
246
+ };