@exaudeus/workrail 3.80.0 → 3.82.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.
@@ -52,6 +52,7 @@ const index_js_1 = require("./context-assembly/index.js");
52
52
  const infra_js_1 = require("./context-assembly/infra.js");
53
53
  const index_js_2 = require("./cli/commands/index.js");
54
54
  const stats_summary_js_1 = require("./daemon/stats-summary.js");
55
+ const worktrain_diagnose_js_1 = require("./cli/commands/worktrain-diagnose.js");
55
56
  const execFileAsync = (0, util_1.promisify)(child_process_1.execFile);
56
57
  const program = new commander_1.Command();
57
58
  program
@@ -744,6 +745,46 @@ program
744
745
  }
745
746
  }
746
747
  });
748
+ program
749
+ .command('diagnose [sessionId]')
750
+ .description('Session diagnostics. No args: fleet summary with step bottleneck analysis and token burn. ' +
751
+ 'Pass a sessionId for a per-session failure card with evidence and suggested fix. ' +
752
+ 'Searches the last 7 days of daemon event logs. Works without the daemon running.')
753
+ .option('--workflow <id>', 'Filter fleet view by workflow ID (e.g. wr.discovery)')
754
+ .option('--json', 'Output machine-readable JSON (per-session only)')
755
+ .option('--ascii', 'Use ASCII-only output (no Unicode glyphs)')
756
+ .action((sessionId, options) => {
757
+ const eventsDir = path_1.default.join(os_1.default.homedir(), '.workrail', 'events', 'daemon');
758
+ const readFile = (filePath) => {
759
+ try {
760
+ return fs_1.default.readFileSync(filePath, 'utf8');
761
+ }
762
+ catch {
763
+ return null;
764
+ }
765
+ };
766
+ if (sessionId !== undefined) {
767
+ const result = (0, worktrain_diagnose_js_1.parseDaemonEvents)(sessionId, eventsDir, 7, readFile);
768
+ if (options.json) {
769
+ process.stdout.write((0, worktrain_diagnose_js_1.formatDiagnosticJson)(result) + '\n');
770
+ }
771
+ else {
772
+ process.stdout.write((0, worktrain_diagnose_js_1.formatDiagnosticCard)(result, { ascii: options.ascii ?? false }) + '\n');
773
+ }
774
+ }
775
+ else {
776
+ const readDir = (dir) => {
777
+ try {
778
+ return fs_1.default.readdirSync(dir);
779
+ }
780
+ catch {
781
+ return null;
782
+ }
783
+ };
784
+ const analysis = (0, worktrain_diagnose_js_1.analyzeFleet)(readDir, readFile, eventsDir, options.workflow);
785
+ process.stdout.write((0, worktrain_diagnose_js_1.formatFleetSummary)(analysis, { ascii: options.ascii ?? false }) + '\n');
786
+ }
787
+ });
747
788
  program
748
789
  .command('health <sessionId>')
749
790
  .description('Print a health summary for a daemon session. Accepts sessionId (UUID prefix) or workrailSessionId (sess_xxx).')
@@ -760,7 +801,34 @@ program
760
801
  raw = fs_1.default.readFileSync(filePath, 'utf8');
761
802
  }
762
803
  catch {
763
- process.stdout.write(`No events today. Is the daemon running? (Expected: ${filePath})\n`);
804
+ raw = '';
805
+ }
806
+ const sessionInTodayLog = raw.split('\n').some((line) => {
807
+ if (!line.trim())
808
+ return false;
809
+ try {
810
+ const obj = JSON.parse(line);
811
+ const sid = typeof obj['sessionId'] === 'string' ? obj['sessionId'] : '';
812
+ const wrid = typeof obj['workrailSessionId'] === 'string' ? obj['workrailSessionId'] : '';
813
+ return sid.startsWith(sessionId) || sid === sessionId ||
814
+ wrid.startsWith(sessionId) || wrid === sessionId;
815
+ }
816
+ catch {
817
+ return false;
818
+ }
819
+ });
820
+ if (!sessionInTodayLog) {
821
+ const readFile = (fp) => {
822
+ try {
823
+ return fs_1.default.readFileSync(fp, 'utf8');
824
+ }
825
+ catch {
826
+ return null;
827
+ }
828
+ };
829
+ const result = (0, worktrain_diagnose_js_1.parseDaemonEvents)(sessionId, eventsDir, 7, readFile);
830
+ const card = (0, worktrain_diagnose_js_1.formatDiagnosticCard)(result);
831
+ process.stdout.write(card + '\n');
764
832
  return;
765
833
  }
766
834
  runHealthSummary(sessionId, raw);
@@ -782,15 +850,40 @@ program
782
850
  const eventsDir = path_1.default.join(os_1.default.homedir(), '.workrail', 'events', 'daemon');
783
851
  const date = new Date().toISOString().slice(0, 10);
784
852
  const filePath = path_1.default.join(eventsDir, `${date}.jsonl`);
785
- let raw;
853
+ let raw = '';
786
854
  try {
787
855
  raw = fs_1.default.readFileSync(filePath, 'utf8');
788
856
  }
789
857
  catch {
790
- process.stdout.write(`No events today. Is the daemon running? (Expected: ${filePath})\n`);
791
- return;
792
858
  }
793
859
  process.stderr.write(`\nNote: This is the old \`worktrain status <id>\` output. Use \`worktrain health <id>\` instead.\n\n`);
860
+ const sessionInTodayLog = raw.split('\n').some((line) => {
861
+ if (!line.trim())
862
+ return false;
863
+ try {
864
+ const obj = JSON.parse(line);
865
+ const sid = typeof obj['sessionId'] === 'string' ? obj['sessionId'] : '';
866
+ const wrid = typeof obj['workrailSessionId'] === 'string' ? obj['workrailSessionId'] : '';
867
+ return sid.startsWith(sessionId) || sid === sessionId ||
868
+ wrid.startsWith(sessionId) || wrid === sessionId;
869
+ }
870
+ catch {
871
+ return false;
872
+ }
873
+ });
874
+ if (!sessionInTodayLog) {
875
+ const readFile = (fp) => {
876
+ try {
877
+ return fs_1.default.readFileSync(fp, 'utf8');
878
+ }
879
+ catch {
880
+ return null;
881
+ }
882
+ };
883
+ const result = (0, worktrain_diagnose_js_1.parseDaemonEvents)(sessionId, eventsDir, 7, readFile);
884
+ process.stdout.write((0, worktrain_diagnose_js_1.formatDiagnosticCard)(result) + '\n');
885
+ return;
886
+ }
794
887
  runHealthSummary(sessionId, raw);
795
888
  return;
796
889
  }