runspec-node 0.11.1 → 0.12.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.
package/dist/serve.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"serve.js","sourceRoot":"","sources":["../src/serve.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"serve.js","sourceRoot":"","sources":["../src/serve.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "runspec-node",
3
- "version": "0.11.1",
3
+ "version": "0.12.0",
4
4
  "description": "Node/TypeScript language pack for runspec",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -24,9 +24,9 @@
24
24
  "node": ">=18.0.0"
25
25
  },
26
26
  "devDependencies": {
27
- "@types/jest": "^29.0.0",
28
- "@types/node": "^20.0.0",
29
- "jest": "^29.0.0",
27
+ "@types/jest": "^30.0.0",
28
+ "@types/node": "^25.9.1",
29
+ "jest": "^30.4.2",
30
30
  "ts-jest": "^29.0.0",
31
31
  "typescript": "^5.0.0"
32
32
  },
package/src/loader.ts CHANGED
@@ -40,10 +40,16 @@ function normaliseLogging(raw: Record<string, unknown> | undefined): LoggingConf
40
40
  // silencing INFO would break agent responses (stdout is the MCP tool
41
41
  // response body), and verbosity for debugging is handled by the `--debug`
42
42
  // flag injected at parse time.
43
+ //
44
+ // `summary` (default true) writes one record per run to the audit log and
45
+ // one human-readable line to stderr at process exit: duration, exit code,
46
+ // log-event counts by level. Suppress per-invocation with `--no-summary`
47
+ // or `RUNSPEC_NO_SUMMARY=1`.
43
48
  if (raw === undefined) return undefined;
44
49
  return {
45
50
  rotate: String(raw['rotate'] ?? 'midnight'),
46
51
  keep: Number(raw['keep'] ?? 7),
52
+ summary: raw['summary'] !== undefined ? Boolean(raw['summary']) : true,
47
53
  };
48
54
  }
49
55
 
@@ -15,6 +15,29 @@ let _configured = false;
15
15
  const _loggers = new Map<string, Logger>();
16
16
  const _handlers: Handler[] = [];
17
17
 
18
+ const RUN_SUMMARY_LOGGER = 'runspec.runsummary';
19
+
20
+ interface CapturedException {
21
+ type: string;
22
+ message: string;
23
+ traceback: string;
24
+ }
25
+
26
+ interface SummaryState {
27
+ counter: RunSummaryCounter;
28
+ start: bigint;
29
+ runnable: string;
30
+ autonomy: string | undefined;
31
+ agent: boolean;
32
+ commandPath: string[];
33
+ exception: CapturedException | null;
34
+ exitCode: number;
35
+ emitted: boolean;
36
+ }
37
+
38
+ let _summaryState: SummaryState | null = null;
39
+ let _exitHooksInstalled = false;
40
+
18
41
  // ── level map ─────────────────────────────────────────────────────────────────
19
42
 
20
43
  const LEVEL_NUM: Record<string, number> = {
@@ -149,12 +172,16 @@ function formatConsole(record: LogRecord, showTracebacks: boolean): string {
149
172
  * Routes DEBUG/INFO records (i.e. below WARNING) to stdout.
150
173
  * Treated as the runnable's primary output — captured as the response body
151
174
  * when `runspec serve` invokes the runnable as a subprocess.
175
+ *
176
+ * Drops `runspec.runsummary` records — those are file-only; the human form
177
+ * of the summary is written directly to stderr by the exit hook.
152
178
  */
153
179
  class StdoutHandler implements Handler {
154
180
  constructor(public readonly level: number, private readonly showTracebacks: boolean) {}
155
181
 
156
182
  emit(record: LogRecord): void {
157
183
  if (record.levelNum >= 30) return; // WARNING+ belongs on stderr
184
+ if (record.loggerName === RUN_SUMMARY_LOGGER) return;
158
185
  try {
159
186
  process.stdout.write(formatConsole(record, this.showTracebacks) + '\n');
160
187
  } catch {
@@ -175,6 +202,7 @@ class StderrHandler implements Handler {
175
202
 
176
203
  emit(record: LogRecord): void {
177
204
  if (record.levelNum < 30) return;
205
+ if (record.loggerName === RUN_SUMMARY_LOGGER) return;
178
206
  try {
179
207
  process.stderr.write(formatConsole(record, this.showTracebacks) + '\n');
180
208
  } catch {
@@ -183,6 +211,28 @@ class StderrHandler implements Handler {
183
211
  }
184
212
  }
185
213
 
214
+ // ── run summary counter ───────────────────────────────────────────────────────
215
+
216
+ /**
217
+ * Counts records by level. Emits nothing — read at process exit by the
218
+ * summary hook. Always attached at level=DEBUG so every record is counted.
219
+ */
220
+ class RunSummaryCounter implements Handler {
221
+ readonly level = 10;
222
+ readonly counts: Record<string, number> = {
223
+ DEBUG: 0, INFO: 0, WARNING: 0, ERROR: 0, CRITICAL: 0,
224
+ };
225
+
226
+ emit(record: LogRecord): void {
227
+ // Don't count the summary record itself.
228
+ if (record.loggerName === RUN_SUMMARY_LOGGER) return;
229
+ const label = LEVEL_LABEL[record.levelNum];
230
+ if (label && label in this.counts) {
231
+ this.counts[label]++;
232
+ }
233
+ }
234
+ }
235
+
186
236
  // ── rotating file handlers ────────────────────────────────────────────────────
187
237
 
188
238
  function doRotate(logPath: string, keep: number): void {
@@ -289,21 +339,160 @@ function makeFileHandler(logPath: string, rotate: string, keep: number, level: n
289
339
 
290
340
  // ── log dir resolution ────────────────────────────────────────────────────────
291
341
 
342
+ /**
343
+ * Walk up from `start` looking for a `package.json` that is NOT inside a
344
+ * `node_modules` directory — that's the project root. Returns null if we
345
+ * reach the filesystem root without finding one.
346
+ *
347
+ * Skipping node_modules is intentional: dependency packages bundle their
348
+ * own package.json, but they're not the project root the user owns.
349
+ */
350
+ function findProjectRoot(start: string): string | null {
351
+ let dir = path.resolve(start);
352
+ while (true) {
353
+ if (!dir.split(path.sep).includes('node_modules')) {
354
+ if (fs.existsSync(path.join(dir, 'package.json'))) return dir;
355
+ }
356
+ const parent = path.dirname(dir);
357
+ if (parent === dir) return null; // hit filesystem root
358
+ dir = parent;
359
+ }
360
+ }
361
+
362
+ /**
363
+ * Resolve the log directory.
364
+ *
365
+ * Mirror of Python's `sys.prefix / "logs"`: pick the project's installation
366
+ * root — the nearest ancestor `package.json` of the runnable's runspec.toml,
367
+ * skipping anything under `node_modules`. Logs land at `{project_root}/logs/`,
368
+ * so one logs directory per project, surviving reinstalls.
369
+ *
370
+ * Falls back to `~/logs/` when no project root is found or the chosen
371
+ * directory is not writable (e.g. read-only volumes, system installs).
372
+ */
292
373
  function resolveLogDir(configPath: string): string {
293
- const candidate = path.join(path.dirname(configPath), 'logs');
374
+ const projectRoot = findProjectRoot(path.dirname(configPath));
375
+ if (projectRoot) {
376
+ const candidate = path.join(projectRoot, 'logs');
377
+ try {
378
+ fs.mkdirSync(candidate, { recursive: true });
379
+ const probe = path.join(candidate, '.wtest');
380
+ fs.writeFileSync(probe, '');
381
+ fs.unlinkSync(probe);
382
+ return candidate;
383
+ } catch {
384
+ // fall through to home
385
+ }
386
+ }
387
+ const fallback = path.join(os.homedir(), 'logs');
388
+ fs.mkdirSync(fallback, { recursive: true });
389
+ return fallback;
390
+ }
391
+
392
+ // ── run summary ──────────────────────────────────────────────────────────────
393
+
394
+ function formatSummaryLine(state: SummaryState, durationMs: number, exitCode: number): string {
395
+ const counts = state.counter.counts;
396
+ const total = (counts.DEBUG ?? 0) + (counts.INFO ?? 0) + (counts.WARNING ?? 0) + (counts.ERROR ?? 0) + (counts.CRITICAL ?? 0);
397
+ const warnings = counts.WARNING ?? 0;
398
+ const errors = (counts.ERROR ?? 0) + (counts.CRITICAL ?? 0);
399
+ const secs = (durationMs / 1000).toFixed(2);
400
+ const runnable = state.runnable;
401
+ const wSuffix = warnings === 1 ? '' : 's';
402
+ const eSuffix = errors === 1 ? '' : 's';
403
+ if (state.exception || exitCode !== 0) {
404
+ const excPart = state.exception ? `, ${state.exception.type}` : '';
405
+ return `runspec: ${runnable} failed in ${secs}s — exit ${exitCode}${excPart} — ${total} events (${warnings} warning${wSuffix}, ${errors} error${eSuffix})`;
406
+ }
407
+ return `runspec: ${runnable} completed in ${secs}s — ${total} events (${warnings} warning${wSuffix}, ${errors} error${eSuffix})`;
408
+ }
409
+
410
+ /**
411
+ * Emit one summary record to the file (via the standard logger pipeline —
412
+ * picked up by the file handler, dropped by the console handlers) and one
413
+ * formatted line directly to stderr. Idempotent — safe to call repeatedly.
414
+ */
415
+ export function emitRunSummary(): void {
416
+ const state = _summaryState;
417
+ if (state === null || state.emitted) return;
418
+ state.emitted = true;
419
+
420
+ const durationMs = Number((process.hrtime.bigint() - state.start) / 1_000_000n);
421
+ // Exit code: explicit capture (set by uncaughtException hook) or
422
+ // process.exitCode if the user set it. Defaults to 0.
423
+ const exitCode = state.exitCode !== 0 ? state.exitCode : (state.exception ? 1 : 0);
424
+
294
425
  try {
295
- fs.mkdirSync(candidate, { recursive: true });
296
- const probe = path.join(candidate, '.wtest');
297
- fs.writeFileSync(probe, '');
298
- fs.unlinkSync(probe);
299
- return candidate;
426
+ getLogger(RUN_SUMMARY_LOGGER).info('run completed', {
427
+ event: 'run_summary',
428
+ runnable: state.runnable,
429
+ command_path: state.commandPath,
430
+ duration_ms: durationMs,
431
+ exit_code: exitCode,
432
+ agent: state.agent,
433
+ autonomy: state.autonomy,
434
+ exception: state.exception,
435
+ events: { ...state.counter.counts },
436
+ });
300
437
  } catch {
301
- const fallback = path.join(os.homedir(), 'logs');
302
- fs.mkdirSync(fallback, { recursive: true });
303
- return fallback;
438
+ // never disrupt shutdown
439
+ }
440
+
441
+ try {
442
+ process.stderr.write(formatSummaryLine(state, durationMs, exitCode) + '\n');
443
+ } catch {
444
+ // never disrupt shutdown
304
445
  }
305
446
  }
306
447
 
448
+ function installExitHooks(): void {
449
+ if (_exitHooksInstalled) return;
450
+ _exitHooksInstalled = true;
451
+
452
+ process.on('exit', (code) => {
453
+ if (_summaryState && !_summaryState.emitted) {
454
+ // process.exitCode wins over the explicit exception capture only if
455
+ // it's non-zero — uncaughtException already set state.exitCode=1.
456
+ if (code !== 0 && _summaryState.exitCode === 0) _summaryState.exitCode = code;
457
+ emitRunSummary();
458
+ }
459
+ });
460
+
461
+ // Skip the crash-handlers under jest — they call process.exit(1), which
462
+ // would tear down the test runner if any test ever produced an unhandled
463
+ // rejection. The 'exit' hook above is harmless and still runs.
464
+ if (process.env['JEST_WORKER_ID'] !== undefined) return;
465
+
466
+ process.on('uncaughtException', (err: Error) => {
467
+ if (_summaryState) {
468
+ _summaryState.exception = {
469
+ type: err.name || 'Error',
470
+ message: err.message || String(err),
471
+ traceback: err.stack ?? '',
472
+ };
473
+ _summaryState.exitCode = 1;
474
+ }
475
+ // Preserve default Node behaviour: print and exit non-zero. The 'exit'
476
+ // hook above will fire and run emitRunSummary().
477
+ process.stderr.write((err.stack ?? String(err)) + '\n');
478
+ process.exit(1);
479
+ });
480
+
481
+ process.on('unhandledRejection', (reason: unknown) => {
482
+ if (_summaryState) {
483
+ const err = reason instanceof Error ? reason : new Error(String(reason));
484
+ _summaryState.exception = {
485
+ type: err.name || 'Error',
486
+ message: err.message || String(reason),
487
+ traceback: err.stack ?? '',
488
+ };
489
+ _summaryState.exitCode = 1;
490
+ }
491
+ process.stderr.write(`Unhandled rejection: ${reason instanceof Error ? (reason.stack ?? reason.message) : String(reason)}\n`);
492
+ process.exit(1);
493
+ });
494
+ }
495
+
307
496
  // ── public: configureLogging ──────────────────────────────────────────────────
308
497
 
309
498
  export interface ConfigureLoggingOptions {
@@ -311,6 +500,10 @@ export interface ConfigureLoggingOptions {
311
500
  runnableName: string;
312
501
  configPath: string;
313
502
  debug?: boolean;
503
+ noSummary?: boolean;
504
+ autonomy?: string;
505
+ agent?: boolean;
506
+ commandPath?: string[];
314
507
  }
315
508
 
316
509
  /**
@@ -331,7 +524,14 @@ export interface ConfigureLoggingOptions {
331
524
  * WARNING regardless.
332
525
  *
333
526
  * File handler is always JSON; level follows the same `--debug` toggle as
334
- * stdout (defaults to INFO — keeps third-party DEBUG noise out of the audit log).
527
+ * stdout (defaults to INFO — keeps third-party DEBUG noise out of the audit
528
+ * log). Log files land under `{project_root}/logs/` — the nearest ancestor
529
+ * `package.json` skipping `node_modules`, mirroring Python's venv-root
530
+ * convention. Falls back to `~/logs/` when no project root is found.
531
+ *
532
+ * Run summary (when `logCfg.summary` is true and `noSummary` is false)
533
+ * counts log events by level and emits a single record at process exit
534
+ * with duration, exit code, exception class, and per-level counts.
335
535
  */
336
536
  export function configureLogging(opts: ConfigureLoggingOptions): void {
337
537
  if (!opts.logCfg || _configured) return;
@@ -346,6 +546,31 @@ export function configureLogging(opts: ConfigureLoggingOptions): void {
346
546
  const logPath = path.join(logDir, `${opts.runnableName}.log`);
347
547
  _handlers.push(makeFileHandler(logPath, opts.logCfg.rotate, opts.logCfg.keep, floor));
348
548
 
549
+ // Always attach the counter — cost is one dict increment per log call.
550
+ // Only the exit hook + state population are conditional on summary mode.
551
+ const counter = new RunSummaryCounter();
552
+ _handlers.push(counter);
553
+
554
+ const summaryEnabled =
555
+ opts.logCfg.summary !== false &&
556
+ !opts.noSummary &&
557
+ !['1', 'true', 'yes'].includes((process.env['RUNSPEC_NO_SUMMARY'] ?? '').toLowerCase());
558
+
559
+ if (summaryEnabled) {
560
+ _summaryState = {
561
+ counter,
562
+ start: process.hrtime.bigint(),
563
+ runnable: opts.runnableName,
564
+ autonomy: opts.autonomy,
565
+ agent: opts.agent ?? false,
566
+ commandPath: opts.commandPath ?? [],
567
+ exception: null,
568
+ exitCode: 0,
569
+ emitted: false,
570
+ };
571
+ installExitHooks();
572
+ }
573
+
349
574
  _configured = true;
350
575
  }
351
576
 
@@ -355,6 +580,9 @@ export function _resetForTest(): void {
355
580
  _configured = false;
356
581
  _loggers.clear();
357
582
  _handlers.length = 0;
583
+ _summaryState = null;
584
+ // Note: process event listeners installed by installExitHooks() stay —
585
+ // they no-op when _summaryState is null, which is the test-time state.
358
586
  }
359
587
 
360
- export { _periodForDate };
588
+ export { _periodForDate, RUN_SUMMARY_LOGGER };
package/src/models.ts CHANGED
@@ -11,6 +11,7 @@ export interface JumpHostConfig {
11
11
  export interface LoggingConfig {
12
12
  rotate: string;
13
13
  keep: number;
14
+ summary: boolean;
14
15
  }
15
16
 
16
17
  export interface RawConfig {
package/src/parser.ts CHANGED
@@ -54,6 +54,26 @@ export function parse(opts: ParseOptions = {}): ParsedArgs {
54
54
  };
55
55
  }
56
56
 
57
+ // Auto-inject --no-summary when [config.logging] is present. Suppresses
58
+ // the per-run summary record and stderr line for that one invocation.
59
+ if (config.logging && !('no-summary' in rawScript.args)) {
60
+ rawScript = {
61
+ ...rawScript,
62
+ args: {
63
+ ...rawScript.args,
64
+ 'no-summary': {
65
+ name: 'no-summary',
66
+ type: 'flag',
67
+ default: false,
68
+ required: false,
69
+ description: 'Suppress the per-run summary record and stderr line.',
70
+ multiple: false,
71
+ env: 'RUNSPEC_NO_SUMMARY',
72
+ },
73
+ },
74
+ };
75
+ }
76
+
57
77
  let argv = argvOverride ?? process.argv.slice(2);
58
78
  let activeScript = rawScript;
59
79
  let commandPath: string[] = [];
@@ -90,6 +110,9 @@ export function parse(opts: ParseOptions = {}): ParsedArgs {
90
110
  const debug = config.logging
91
111
  ? Boolean(coercedValues['debug'] ?? false)
92
112
  : false;
113
+ const noSummary = config.logging
114
+ ? Boolean(coercedValues['no_summary'] ?? false)
115
+ : false;
93
116
 
94
117
  try {
95
118
  configureLogging({
@@ -97,6 +120,10 @@ export function parse(opts: ParseOptions = {}): ParsedArgs {
97
120
  runnableName: name,
98
121
  configPath,
99
122
  debug,
123
+ noSummary,
124
+ autonomy,
125
+ agent,
126
+ commandPath,
100
127
  });
101
128
  } catch (e) {
102
129
  throw new RunSpecError((e as Error).message);
package/src/serve.ts CHANGED
@@ -139,13 +139,26 @@ function handleToolsCall(
139
139
  const runspecEnv = argsToRunspecEnv(args, toolArgSpecs);
140
140
  const env = { ...process.env, RUNSPEC_AGENT: '1', ...runspecEnv };
141
141
 
142
+ const start = process.hrtime.bigint();
142
143
  const result = spawnSync(cmd, argv, { encoding: 'utf-8', env });
144
+ const durationMs = Number((process.hrtime.bigint() - start) / 1_000_000n);
145
+
146
+ // _meta is the MCP-standard extension point; clients that don't understand
147
+ // it ignore the block. Same envelope on success and failure so callers can
148
+ // rely on it being present.
149
+ const meta = {
150
+ runspec: { tool: name, duration_ms: durationMs, exit_code: result.status ?? null },
151
+ };
143
152
 
144
153
  if (result.status === 0) {
145
154
  return {
146
155
  jsonrpc: '2.0',
147
156
  id: reqId,
148
- result: { content: [{ type: 'text', text: result.stdout ?? '' }], isError: false },
157
+ result: {
158
+ content: [{ type: 'text', text: result.stdout ?? '' }],
159
+ isError: false,
160
+ _meta: meta,
161
+ },
149
162
  };
150
163
  }
151
164
 
@@ -156,7 +169,11 @@ function handleToolsCall(
156
169
  return {
157
170
  jsonrpc: '2.0',
158
171
  id: reqId,
159
- result: { content: [{ type: 'text', text: parts.join('\n') }], isError: true },
172
+ result: {
173
+ content: [{ type: 'text', text: parts.join('\n') }],
174
+ isError: true,
175
+ _meta: meta,
176
+ },
160
177
  };
161
178
  }
162
179
 
@@ -145,7 +145,7 @@ test('normalises [config.logging] with defaults', () => {
145
145
  description = "hi"
146
146
  `);
147
147
  const raw = loadRaw(file);
148
- expect(raw.config.logging).toEqual({ rotate: 'midnight', keep: 7 });
148
+ expect(raw.config.logging).toEqual({ rotate: 'midnight', keep: 7, summary: true });
149
149
  });
150
150
 
151
151
  test('normalises [config.logging] all fields', () => {
@@ -153,14 +153,28 @@ test('normalises [config.logging] all fields', () => {
153
153
  const file = path.join(dir, 'runspec.toml');
154
154
  fs.writeFileSync(file, `
155
155
  [config.logging]
156
- rotate = "10 MB"
157
- keep = 3
156
+ rotate = "10 MB"
157
+ keep = 3
158
+ summary = false
158
159
 
159
160
  [greet]
160
161
  description = "hi"
161
162
  `);
162
163
  const raw = loadRaw(file);
163
- expect(raw.config.logging).toEqual({ rotate: '10 MB', keep: 3 });
164
+ expect(raw.config.logging).toEqual({ rotate: '10 MB', keep: 3, summary: false });
165
+ });
166
+
167
+ test('[config.logging] summary defaults to true when omitted', () => {
168
+ const dir = tmpDir();
169
+ const file = path.join(dir, 'runspec.toml');
170
+ fs.writeFileSync(file, `
171
+ [config.logging]
172
+ rotate = "midnight"
173
+
174
+ [greet]
175
+ description = "hi"
176
+ `);
177
+ expect(loadRaw(file).config.logging?.summary).toBe(true);
164
178
  });
165
179
 
166
180
  test('logging is undefined when section absent', () => {
@@ -10,13 +10,20 @@ import {
10
10
  } from '../src/logging_setup';
11
11
 
12
12
  function tmpDir(): string {
13
- return fs.mkdtempSync(path.join(os.tmpdir(), 'runspec-log-test-'));
13
+ // Each test dir is its own project root so resolveLogDir lands inside it.
14
+ // Without a package.json the walk-up would escape to a real project root
15
+ // and write logs to a shared location — drop a marker file to anchor it.
16
+ const d = fs.mkdtempSync(path.join(os.tmpdir(), 'runspec-log-test-'));
17
+ fs.writeFileSync(path.join(d, 'package.json'), '{"name":"test","version":"0.0.0"}');
18
+ return d;
14
19
  }
15
20
 
16
21
  function makeCfg(dir: string, overrides: Record<string, unknown> = {}): Parameters<typeof configureLogging>[0] {
17
22
  const { debug, ...logOverrides } = overrides as { debug?: boolean } & Record<string, unknown>;
18
23
  return {
19
- logCfg: { rotate: 'midnight', keep: 7, ...logOverrides },
24
+ // summary defaults off in tests so we don't litter atexit emissions across
25
+ // unrelated assertions; the run-summary suite enables it explicitly.
26
+ logCfg: { rotate: 'midnight', keep: 7, summary: false, ...logOverrides },
20
27
  runnableName: 'myscript',
21
28
  configPath: path.join(dir, 'runspec.toml'),
22
29
  debug,
@@ -365,7 +372,7 @@ test('_periodForDate weekly: adjacent weeks differ', () => {
365
372
 
366
373
  // ── log dir fallback ──────────────────────────────────────────────────────────
367
374
 
368
- test('falls back to ~/logs when package dir is not writable', () => {
375
+ test('falls back to ~/logs when the project root is not writable', () => {
369
376
  const dir = tmpDir();
370
377
  const logsDir = path.join(dir, 'logs');
371
378
  // Create logs dir as unwritable