@stackmemoryai/stackmemory 1.6.2 → 1.8.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 (86) hide show
  1. package/dist/src/agents/core/agent-task-manager.js +3 -3
  2. package/dist/src/agents/verifiers/formatter-verifier.js +1 -1
  3. package/dist/src/agents/verifiers/llm-judge.js +2 -2
  4. package/dist/src/cli/commands/conductor-traces.js +314 -0
  5. package/dist/src/cli/commands/context-rehydrate.js +2 -2
  6. package/dist/src/cli/commands/log.js +1 -1
  7. package/dist/src/cli/commands/onboard.js +4 -4
  8. package/dist/src/cli/commands/orchestrate.js +328 -27
  9. package/dist/src/cli/commands/orchestrator.js +50 -3
  10. package/dist/src/cli/commands/search.js +3 -3
  11. package/dist/src/cli/commands/setup.js +1 -1
  12. package/dist/src/cli/commands/signup.js +1 -1
  13. package/dist/src/cli/commands/skills.js +2 -2
  14. package/dist/src/cli/commands/worktree.js +1 -1
  15. package/dist/src/core/config/config-manager.js +3 -1
  16. package/dist/src/core/config/types.js +7 -0
  17. package/dist/src/core/context/dual-stack-manager.js +2 -2
  18. package/dist/src/core/context/frame-database.js +38 -0
  19. package/dist/src/core/context/frame-enrichment.js +93 -0
  20. package/dist/src/core/context/frame-handoff-manager.js +1 -1
  21. package/dist/src/core/context/frame-manager.js +4 -4
  22. package/dist/src/core/context/rehydration.js +6 -6
  23. package/dist/src/core/database/sqlite-adapter.js +136 -15
  24. package/dist/src/core/digest/frame-digest-integration.js +1 -1
  25. package/dist/src/core/digest/hybrid-digest-generator.js +1 -1
  26. package/dist/src/core/digest/hybrid-digest.js +3 -3
  27. package/dist/src/core/errors/recovery.js +1 -1
  28. package/dist/src/core/extensions/loader.js +10 -10
  29. package/dist/src/core/extensions/plugin-system.js +1 -1
  30. package/dist/src/core/monitoring/logger.js +2 -2
  31. package/dist/src/core/monitoring/metrics.js +2 -2
  32. package/dist/src/core/monitoring/progress-tracker.js +2 -2
  33. package/dist/src/core/performance/lazy-context-loader.js +1 -1
  34. package/dist/src/core/performance/performance-benchmark.js +8 -8
  35. package/dist/src/core/performance/performance-profiler.js +2 -2
  36. package/dist/src/core/performance/streaming-jsonl-parser.js +1 -1
  37. package/dist/src/core/projects/project-isolation.js +1 -1
  38. package/dist/src/core/projects/project-manager.js +5 -5
  39. package/dist/src/core/session/clear-survival.js +3 -3
  40. package/dist/src/core/session/handoff-generator.js +1 -1
  41. package/dist/src/core/session/session-manager.js +4 -4
  42. package/dist/src/core/skills/skill-storage.js +2 -2
  43. package/dist/src/core/storage/remote-storage.js +2 -2
  44. package/dist/src/core/trace/cli-trace-wrapper.js +3 -3
  45. package/dist/src/core/trace/debug-trace.js +2 -2
  46. package/dist/src/core/trace/trace-detector.js +1 -1
  47. package/dist/src/core/worktree/worktree-manager.js +2 -2
  48. package/dist/src/features/analytics/api/analytics-api.js +1 -1
  49. package/dist/src/features/analytics/core/analytics-service.js +2 -2
  50. package/dist/src/features/analytics/queries/metrics-queries.js +1 -1
  51. package/dist/src/features/web/server/index.js +8 -8
  52. package/dist/src/integrations/anthropic/client.js +4 -4
  53. package/dist/src/integrations/claude-code/post-task-hooks.js +3 -3
  54. package/dist/src/integrations/claude-code/subagent-client.js +2 -2
  55. package/dist/src/integrations/diffmem/client.js +1 -1
  56. package/dist/src/integrations/linear/client.js +1 -1
  57. package/dist/src/integrations/linear/migration.js +1 -1
  58. package/dist/src/integrations/linear/oauth-server.js +2 -2
  59. package/dist/src/integrations/linear/sync.js +1 -1
  60. package/dist/src/integrations/linear/webhook-handler.js +2 -2
  61. package/dist/src/integrations/linear/webhook-server.js +2 -2
  62. package/dist/src/integrations/linear/webhook.js +2 -2
  63. package/dist/src/integrations/mcp/handlers/discovery-handlers.js +3 -3
  64. package/dist/src/integrations/mcp/handlers/linear-handlers.js +1 -1
  65. package/dist/src/integrations/mcp/handlers/skill-handlers.js +2 -2
  66. package/dist/src/integrations/mcp/handlers/task-handlers.js +2 -2
  67. package/dist/src/integrations/mcp/handlers/trace-handlers.js +1 -1
  68. package/dist/src/integrations/mcp/refactored-server.js +2 -2
  69. package/dist/src/integrations/mcp/remote-server.js +1 -1
  70. package/dist/src/integrations/mcp/server.js +4 -4
  71. package/dist/src/integrations/ralph/bridge/ralph-stackmemory-bridge.js +13 -13
  72. package/dist/src/integrations/ralph/context/stackmemory-context-loader.js +1 -1
  73. package/dist/src/integrations/ralph/learning/pattern-learner.js +3 -3
  74. package/dist/src/integrations/ralph/orchestration/multi-loop-orchestrator.js +1 -1
  75. package/dist/src/integrations/ralph/patterns/compounding-engineering-pattern.js +2 -2
  76. package/dist/src/integrations/ralph/patterns/extended-coherence-sessions.js +19 -19
  77. package/dist/src/integrations/ralph/patterns/oracle-worker-pattern.js +5 -5
  78. package/dist/src/integrations/ralph/performance/performance-optimizer.js +1 -1
  79. package/dist/src/integrations/ralph/recovery/crash-recovery.js +1 -1
  80. package/dist/src/integrations/ralph/state/state-reconciler.js +1 -1
  81. package/dist/src/integrations/ralph/swarm/git-workflow-manager.js +4 -4
  82. package/dist/src/integrations/ralph/swarm/swarm-coordinator.js +8 -8
  83. package/dist/src/integrations/ralph/visualization/ralph-debugger.js +3 -3
  84. package/dist/src/orchestrators/multimodal/harness.js +3 -3
  85. package/dist/src/servers/production/auth-middleware.js +2 -2
  86. package/package.json +1 -1
@@ -22,6 +22,16 @@ import {
22
22
  getAgentStatusDir,
23
23
  getOutcomesLogPath
24
24
  } from "./orchestrator.js";
25
+ import {
26
+ openTracesDb,
27
+ listSessions,
28
+ getSessionTurns,
29
+ getPhaseBreakdown,
30
+ getToolFrequencies,
31
+ getFailureTurns,
32
+ getTraceStats,
33
+ classifyErrorText
34
+ } from "./conductor-traces.js";
25
35
  function getGlobalStorePath() {
26
36
  const dir = join(homedir(), ".stackmemory", "conductor");
27
37
  if (!existsSync(dir)) {
@@ -70,6 +80,17 @@ function fmtTokens(n) {
70
80
  if (n >= 1e3) return `${(n / 1e3).toFixed(1)}K`;
71
81
  return String(n);
72
82
  }
83
+ function formatTokens(n) {
84
+ return fmtTokens(n) + " tok";
85
+ }
86
+ function formatDuration(ms) {
87
+ const seconds = Math.floor(ms / 1e3);
88
+ if (seconds < 60) return `${seconds}s`;
89
+ const minutes = Math.floor(seconds / 60);
90
+ if (minutes < 60) return `${minutes}m ${seconds % 60}s`;
91
+ const hours = Math.floor(minutes / 60);
92
+ return `${hours}h ${minutes % 60}m`;
93
+ }
73
94
  function budgetBar(pct, width = 30) {
74
95
  const filled = Math.min(Math.round(pct / 100 * width), width);
75
96
  const empty = width - filled;
@@ -354,6 +375,44 @@ function predictDifficulty(labels, description, priority, outcomes) {
354
375
  }
355
376
  return { difficulty, confidence, reasons };
356
377
  }
378
+ function analyzeErrorsFromTraces(failedIssues) {
379
+ const patterns = {};
380
+ const evidence = [];
381
+ let db;
382
+ try {
383
+ db = openTracesDb();
384
+ } catch {
385
+ return { patterns, evidence };
386
+ }
387
+ try {
388
+ for (const issueId of failedIssues) {
389
+ const turns = getFailureTurns(issueId, 3, db);
390
+ if (turns.length === 0) continue;
391
+ for (const turn of turns) {
392
+ const preview = turn.message_preview || "";
393
+ const tools = turn.tool_names ? JSON.parse(turn.tool_names) : [];
394
+ const pattern = classifyErrorText(preview);
395
+ if (pattern) {
396
+ patterns[pattern] = (patterns[pattern] || 0) + 1;
397
+ }
398
+ if (evidence.length < 15) {
399
+ evidence.push({
400
+ issue: issueId,
401
+ phase: turn.phase || "unknown",
402
+ tools,
403
+ preview: preview.slice(0, 300)
404
+ });
405
+ }
406
+ }
407
+ }
408
+ } finally {
409
+ db.close();
410
+ }
411
+ if (Object.keys(patterns).length === 0 && failedIssues.length > 0) {
412
+ patterns["unknown"] = failedIssues.length;
413
+ }
414
+ return { patterns, evidence };
415
+ }
357
416
  function loadOutcomes() {
358
417
  const logPath = getOutcomesLogPath();
359
418
  if (!existsSync(logPath)) return [];
@@ -465,7 +524,8 @@ async function evolvePromptTemplate(input) {
465
524
  errorPatterns,
466
525
  recs,
467
526
  outcomes,
468
- dryRun
527
+ dryRun,
528
+ traceEvidence
469
529
  } = input;
470
530
  let currentTemplate;
471
531
  if (existsSync(templatePath)) {
@@ -478,11 +538,24 @@ async function evolvePromptTemplate(input) {
478
538
  }
479
539
  const failPhaseSummary = Object.entries(failPhases).sort((a, b) => b[1] - a[1]).map(([phase, count]) => ` - ${phase}: ${count} failures`).join("\n");
480
540
  const errorSummary = Object.entries(errorPatterns).sort((a, b) => b[1] - a[1]).map(([pattern, count]) => ` - ${pattern}: ${count} occurrences`).join("\n");
481
- const failedOutcomes = outcomes.filter((o) => o.outcome === "failure" && o.errorTail).slice(-5);
482
- const errorTails = failedOutcomes.map(
483
- (o) => ` [${o.issue} attempt ${o.attempt}, phase: ${o.phase}]
541
+ let failureEvidenceSection;
542
+ if (traceEvidence && traceEvidence.length > 0) {
543
+ const evidenceLines = traceEvidence.slice(0, 10).map((ev) => {
544
+ const tools = ev.tools.length > 0 ? ev.tools.join(", ") : "none";
545
+ return ` [${ev.issue}, phase: ${ev.phase}, tools: ${tools}]
546
+ ${ev.preview.slice(0, 200)}`;
547
+ });
548
+ failureEvidenceSection = `ACTUAL FAILURE EVIDENCE (from conversation traces):
549
+ ${evidenceLines.join("\n\n")}`;
550
+ } else {
551
+ const failedOutcomes = outcomes.filter((o) => o.outcome === "failure" && o.errorTail).slice(-5);
552
+ const errorTails = failedOutcomes.map(
553
+ (o) => ` [${o.issue} attempt ${o.attempt}, phase: ${o.phase}]
484
554
  ${o.errorTail}`
485
- ).join("\n\n");
555
+ ).join("\n\n");
556
+ failureEvidenceSection = `SAMPLE ERROR TAILS FROM RECENT FAILURES:
557
+ ${errorTails || " (none)"}`;
558
+ }
486
559
  const mutationPrompt = `You are optimizing a prompt template for autonomous AI coding agents managed by a conductor system.
487
560
 
488
561
  CURRENT PROMPT TEMPLATE:
@@ -500,8 +573,7 @@ ${failPhaseSummary || " (none)"}
500
573
  ERROR PATTERNS:
501
574
  ${errorSummary || " (none)"}
502
575
 
503
- SAMPLE ERROR TAILS FROM RECENT FAILURES:
504
- ${errorTails || " (none)"}
576
+ ${failureEvidenceSection}
505
577
 
506
578
  RECOMMENDATIONS FROM ANALYSIS:
507
579
  ${recs.map((r) => `- ${r}`).join("\n")}
@@ -991,6 +1063,9 @@ function createConductorCommands() {
991
1063
  "--dry-run",
992
1064
  "Show evolved template without writing (use with --evolve)",
993
1065
  false
1066
+ ).option(
1067
+ "--no-evidence",
1068
+ "Disable trace-based evidence display (on by default when traces.db exists)"
994
1069
  ).option(
995
1070
  "--predict",
996
1071
  "Show difficulty predictions alongside actual outcomes",
@@ -1039,22 +1114,23 @@ function createConductorCommands() {
1039
1114
  const retrySuccessRate = retries.length > 0 ? Math.round(
1040
1115
  retries.filter((o) => o.outcome === "success").length / retries.length * 100
1041
1116
  ) : 0;
1042
- const errorPatterns = {};
1043
- for (const o of outcomes.filter(
1044
- (o2) => o2.outcome === "failure" && o2.errorTail
1045
- )) {
1046
- const tail = o.errorTail;
1047
- if (tail.includes("lint"))
1048
- errorPatterns["lint_failure"] = (errorPatterns["lint_failure"] || 0) + 1;
1049
- else if (tail.includes("test"))
1050
- errorPatterns["test_failure"] = (errorPatterns["test_failure"] || 0) + 1;
1051
- else if (tail.includes("timeout") || tail.includes("timed out"))
1052
- errorPatterns["timeout"] = (errorPatterns["timeout"] || 0) + 1;
1053
- else if (tail.includes("conflict"))
1054
- errorPatterns["git_conflict"] = (errorPatterns["git_conflict"] || 0) + 1;
1055
- else if (tail.includes("429") || tail.includes("rate"))
1056
- errorPatterns["rate_limit"] = (errorPatterns["rate_limit"] || 0) + 1;
1057
- else errorPatterns["unknown"] = (errorPatterns["unknown"] || 0) + 1;
1117
+ const failedIssueIds = [
1118
+ ...new Set(
1119
+ outcomes.filter((o) => o.outcome === "failure").map((o) => o.issue)
1120
+ )
1121
+ ];
1122
+ const traceAnalysis = analyzeErrorsFromTraces(failedIssueIds);
1123
+ let errorPatterns = traceAnalysis.patterns;
1124
+ let traceEvidence = traceAnalysis.evidence;
1125
+ if (Object.keys(errorPatterns).length === 0 || Object.keys(errorPatterns).length === 1 && errorPatterns["unknown"]) {
1126
+ errorPatterns = {};
1127
+ traceEvidence = [];
1128
+ for (const o of outcomes.filter(
1129
+ (o2) => o2.outcome === "failure" && o2.errorTail
1130
+ )) {
1131
+ const pattern = classifyErrorText(o.errorTail) ?? "unknown";
1132
+ errorPatterns[pattern] = (errorPatterns[pattern] || 0) + 1;
1133
+ }
1058
1134
  }
1059
1135
  if (options.export) {
1060
1136
  const analysis = {
@@ -1101,16 +1177,42 @@ function createConductorCommands() {
1101
1177
  }
1102
1178
  }
1103
1179
  if (Object.keys(errorPatterns).length > 0) {
1104
- console.log(`
1105
- ${c.b}Error Patterns${c.r}`);
1180
+ const sourceLabel = traceEvidence.length > 0 ? "(from traces)" : "(from errorTail)";
1181
+ console.log(
1182
+ `
1183
+ ${c.b}Error Patterns${c.r} ${c.d}${sourceLabel}${c.r}`
1184
+ );
1106
1185
  const sorted = Object.entries(errorPatterns).sort(
1107
1186
  (a, b) => b[1] - a[1]
1108
1187
  );
1109
1188
  for (const [pattern, count] of sorted) {
1110
1189
  console.log(
1111
- ` ${c.red}\u25CF${c.r} ${pattern.padEnd(16)} ${c.white}${count}${c.r}`
1190
+ ` ${c.red}\u25CF${c.r} ${pattern.padEnd(20)} ${c.white}${count}${c.r}`
1191
+ );
1192
+ }
1193
+ }
1194
+ if (options.evidence && traceEvidence.length > 0) {
1195
+ console.log(
1196
+ `
1197
+ ${c.b}Failure Evidence${c.r} ${c.d}(from traces)${c.r}`
1198
+ );
1199
+ for (const ev of traceEvidence.slice(0, 15)) {
1200
+ const tools = ev.tools.length > 0 ? ev.tools.join(", ") : "-";
1201
+ console.log(
1202
+ ` ${c.cyan}${ev.issue}${c.r} [${ev.phase}] tools: ${tools}`
1112
1203
  );
1204
+ if (ev.preview) {
1205
+ const lines = ev.preview.split("\n").slice(0, 3);
1206
+ for (const line of lines) {
1207
+ console.log(` ${c.d}${line.slice(0, 100)}${c.r}`);
1208
+ }
1209
+ }
1113
1210
  }
1211
+ } else if (options.evidence && traceEvidence.length === 0) {
1212
+ console.log(
1213
+ `
1214
+ ${c.d}No trace data available. Run conductor with trace logging enabled to collect evidence.${c.r}`
1215
+ );
1114
1216
  }
1115
1217
  console.log(`
1116
1218
  ${c.b}Recommendations${c.r}`);
@@ -1186,7 +1288,8 @@ function createConductorCommands() {
1186
1288
  errorPatterns,
1187
1289
  recs,
1188
1290
  outcomes,
1189
- dryRun: options.dryRun
1291
+ dryRun: options.dryRun,
1292
+ traceEvidence
1190
1293
  });
1191
1294
  }
1192
1295
  if (options.predict) {
@@ -1704,6 +1807,204 @@ function createConductorCommands() {
1704
1807
  process.on("SIGTERM", cleanup);
1705
1808
  });
1706
1809
  });
1810
+ cmd.command("traces <issue-id>").description("Show conversation traces for an agent run").option("--session <id>", "Show specific session").option("--json", "Output as JSON", false).option("--tools", "Show tool frequency breakdown", false).option("--failures", "Show failure-turn details", false).option("-n, --tail <count>", "Failure tail turns", "5").action(
1811
+ (issueId, options) => {
1812
+ const db = openTracesDb();
1813
+ try {
1814
+ if (options.tools) {
1815
+ const freq = getToolFrequencies(issueId, db);
1816
+ if (freq.length === 0) {
1817
+ console.log(`No traces found for ${issueId}`);
1818
+ return;
1819
+ }
1820
+ console.log(
1821
+ `
1822
+ ${c.b}Tool Frequency${c.r} \u2014 ${c.cyan}${issueId}${c.r}
1823
+ `
1824
+ );
1825
+ for (const { tool_name, count } of freq.slice(0, 20)) {
1826
+ const bar = "\u2501".repeat(Math.min(count, 40));
1827
+ console.log(
1828
+ ` ${c.d}${tool_name.padEnd(20)}${c.r} ${bar} ${count}`
1829
+ );
1830
+ }
1831
+ console.log("");
1832
+ return;
1833
+ }
1834
+ if (options.failures) {
1835
+ const turns = getFailureTurns(
1836
+ issueId,
1837
+ parseInt(options.tail, 10),
1838
+ db
1839
+ );
1840
+ if (turns.length === 0) {
1841
+ console.log(`No traces found for ${issueId}`);
1842
+ return;
1843
+ }
1844
+ console.log(
1845
+ `
1846
+ ${c.b}Failure Turns${c.r} \u2014 ${c.cyan}${issueId}${c.r}
1847
+ `
1848
+ );
1849
+ for (const t of turns) {
1850
+ const tools = t.tool_names ? JSON.parse(t.tool_names).join(", ") : "-";
1851
+ const ts = new Date(t.timestamp).toISOString().slice(11, 19);
1852
+ console.log(
1853
+ ` ${c.d}[${ts}]${c.r} turn ${t.turn_number} | phase: ${t.phase || "-"} | tools: ${tools}`
1854
+ );
1855
+ if (t.message_preview) {
1856
+ const preview = t.message_preview.slice(0, 200);
1857
+ console.log(` ${c.d}${preview}${c.r}`);
1858
+ }
1859
+ }
1860
+ console.log("");
1861
+ return;
1862
+ }
1863
+ if (options.session) {
1864
+ const turns = getSessionTurns(options.session, db);
1865
+ if (turns.length === 0) {
1866
+ console.log(`No turns found for session ${options.session}`);
1867
+ return;
1868
+ }
1869
+ if (options.json) {
1870
+ console.log(JSON.stringify(turns, null, 2));
1871
+ return;
1872
+ }
1873
+ const phases = getPhaseBreakdown(options.session, db);
1874
+ console.log(
1875
+ `
1876
+ ${c.b}Session${c.r} ${c.cyan}${options.session}${c.r}`
1877
+ );
1878
+ console.log(` ${turns.length} turns
1879
+ `);
1880
+ if (phases.length > 0) {
1881
+ console.log(` ${c.b}Phase Breakdown${c.r}`);
1882
+ for (const p of phases) {
1883
+ console.log(
1884
+ ` ${(p.phase || "unknown").padEnd(14)} ${String(p.turns).padStart(3)} turns ${String(p.tool_calls).padStart(4)} tools ${formatTokens(p.input_tokens + p.output_tokens)}`
1885
+ );
1886
+ }
1887
+ console.log("");
1888
+ }
1889
+ console.log(` ${c.b}Turn Log${c.r}`);
1890
+ for (const t of turns) {
1891
+ const tools = t.tool_names ? JSON.parse(t.tool_names).join(", ") : "-";
1892
+ const ts = new Date(t.timestamp).toISOString().slice(11, 19);
1893
+ const tokens = t.input_tokens + t.output_tokens;
1894
+ console.log(
1895
+ ` ${c.d}${String(t.turn_number).padStart(3)}${c.r} [${ts}] ${(t.phase || "-").padEnd(12)} tools: ${tools.padEnd(30)} ${formatTokens(tokens)}`
1896
+ );
1897
+ }
1898
+ console.log("");
1899
+ return;
1900
+ }
1901
+ const sessions = listSessions(issueId, db);
1902
+ if (sessions.length === 0) {
1903
+ console.log(`No traces found for ${issueId}`);
1904
+ return;
1905
+ }
1906
+ if (options.json) {
1907
+ console.log(JSON.stringify(sessions, null, 2));
1908
+ return;
1909
+ }
1910
+ console.log(
1911
+ `
1912
+ ${c.b}Trace Sessions${c.r} \u2014 ${c.cyan}${issueId}${c.r}
1913
+ `
1914
+ );
1915
+ for (const s of sessions) {
1916
+ const dur = s.duration_ms > 0 ? formatDuration(s.duration_ms) : "-";
1917
+ const tokens = formatTokens(
1918
+ s.total_input_tokens + s.total_output_tokens
1919
+ );
1920
+ const time = new Date(s.started_at).toISOString().slice(0, 19);
1921
+ console.log(
1922
+ ` ${c.d}attempt ${s.attempt}${c.r} ${s.total_turns} turns ${s.total_tool_calls} tools ${tokens} ${dur} ${c.d}${time}${c.r}`
1923
+ );
1924
+ console.log(` ${c.d}session: ${s.session_id}${c.r}`);
1925
+ console.log(` phases: ${s.phases.join(" \u2192 ")}`);
1926
+ console.log("");
1927
+ }
1928
+ } finally {
1929
+ db.close();
1930
+ }
1931
+ }
1932
+ );
1933
+ cmd.command("replay <session-id>").description("Replay a full agent conversation from traces").option("-n, --turns <count>", "Show only last N turns").option("--json", "Output raw event JSON", false).action((sessionId, options) => {
1934
+ const db = openTracesDb();
1935
+ try {
1936
+ let turns = getSessionTurns(sessionId, db);
1937
+ if (turns.length === 0) {
1938
+ console.error(`No traces found for session ${sessionId}`);
1939
+ return;
1940
+ }
1941
+ if (options.turns) {
1942
+ const n = parseInt(options.turns, 10);
1943
+ turns = turns.slice(-n);
1944
+ }
1945
+ if (options.json) {
1946
+ for (const t of turns) {
1947
+ console.log(t.event_json);
1948
+ }
1949
+ return;
1950
+ }
1951
+ console.log(
1952
+ `
1953
+ ${c.b}Replay${c.r} ${c.cyan}${sessionId}${c.r} \u2014 ${turns.length} turns
1954
+ `
1955
+ );
1956
+ for (const t of turns) {
1957
+ const ts = new Date(t.timestamp).toISOString().slice(11, 19);
1958
+ const tokens = t.input_tokens + t.output_tokens;
1959
+ console.log(
1960
+ `${c.purple}\u2500\u2500 Turn ${t.turn_number} \u2500\u2500${c.r} [${ts}] ${t.phase || ""} ${tokens > 0 ? formatTokens(tokens) : ""}`
1961
+ );
1962
+ if (t.tool_names) {
1963
+ const tools = JSON.parse(t.tool_names);
1964
+ for (const tool of tools) {
1965
+ console.log(` ${c.cyan}\u25B8 ${tool}${c.r}`);
1966
+ }
1967
+ }
1968
+ if (t.message_preview) {
1969
+ const lines = t.message_preview.split("\n");
1970
+ for (const line of lines.slice(0, 10)) {
1971
+ console.log(` ${c.d}${line}${c.r}`);
1972
+ }
1973
+ if (lines.length > 10) {
1974
+ console.log(
1975
+ ` ${c.d}... (${lines.length - 10} more lines)${c.r}`
1976
+ );
1977
+ }
1978
+ }
1979
+ console.log("");
1980
+ }
1981
+ } finally {
1982
+ db.close();
1983
+ }
1984
+ });
1985
+ cmd.command("trace-stats").description("Show aggregate trace statistics").option("--json", "Output as JSON", false).action((options) => {
1986
+ const stats = getTraceStats();
1987
+ if (options.json) {
1988
+ console.log(JSON.stringify(stats, null, 2));
1989
+ return;
1990
+ }
1991
+ console.log(`
1992
+ ${c.b}Conductor Trace Stats${c.r}
1993
+ `);
1994
+ console.log(` Sessions: ${stats.total_sessions}`);
1995
+ console.log(` Turns: ${stats.total_turns}`);
1996
+ console.log(` Issues: ${stats.issues_traced}`);
1997
+ console.log(
1998
+ ` Tokens: ${formatTokens((stats.total_input_tokens || 0) + (stats.total_output_tokens || 0))}`
1999
+ );
2000
+ console.log(
2001
+ ` Input: ${formatTokens(stats.total_input_tokens || 0)}`
2002
+ );
2003
+ console.log(
2004
+ ` Output: ${formatTokens(stats.total_output_tokens || 0)}`
2005
+ );
2006
+ console.log("");
2007
+ });
1707
2008
  return cmd;
1708
2009
  }
1709
2010
  export {
@@ -30,6 +30,10 @@ import {
30
30
  } from "../../core/worktree/preflight.js";
31
31
  import { ContextCapture } from "../../core/worktree/capture.js";
32
32
  import { extractKeywords } from "../../core/utils/text.js";
33
+ import {
34
+ TraceCollector,
35
+ stringifyEventTruncated
36
+ } from "./conductor-traces.js";
33
37
  function getOutcomesLogPath() {
34
38
  return join(homedir(), ".stackmemory", "conductor", "outcomes.jsonl");
35
39
  }
@@ -1272,6 +1276,17 @@ class Conductor {
1272
1276
  run.logStream = logStream;
1273
1277
  const tee = new TeeTransform(logStream);
1274
1278
  proc.stdout.pipe(tee);
1279
+ let traceCollector;
1280
+ try {
1281
+ traceCollector = new TraceCollector({
1282
+ issueId: issue.identifier,
1283
+ attempt: run.attempt
1284
+ });
1285
+ } catch {
1286
+ logger.warn("Failed to initialize trace collector", {
1287
+ identifier: issue.identifier
1288
+ });
1289
+ }
1275
1290
  this.writeAgentStatus(issue.identifier, run);
1276
1291
  let stderr = "";
1277
1292
  let lastResultText = "";
@@ -1303,23 +1318,53 @@ class Conductor {
1303
1318
  this.trackUsage(issue.identifier, msgUsage);
1304
1319
  }
1305
1320
  const content = message?.content || [];
1321
+ const turnToolNames = [];
1322
+ let turnFilesModified = 0;
1323
+ const turnTextParts = [];
1306
1324
  for (const block of content) {
1307
1325
  if (block.type === "tool_use") {
1308
1326
  run.toolCalls++;
1309
- const toolLower = (block.name || "").toLowerCase();
1327
+ const name = block.name || "";
1328
+ turnToolNames.push(name);
1329
+ const toolLower = name.toLowerCase();
1310
1330
  if (toolLower.includes("edit") || toolLower.includes("write")) {
1311
1331
  run.filesModified++;
1332
+ turnFilesModified++;
1312
1333
  }
1313
1334
  }
1314
1335
  if (block.type === "text" && block.text) {
1315
- run.tokensUsed += Math.ceil(
1316
- block.text.length / 4
1336
+ const text = block.text;
1337
+ run.tokensUsed += Math.ceil(text.length / 4);
1338
+ turnTextParts.push(text);
1339
+ }
1340
+ }
1341
+ try {
1342
+ if (traceCollector) {
1343
+ const turnData = {
1344
+ toolNames: turnToolNames,
1345
+ toolCount: turnToolNames.length,
1346
+ filesModified: turnFilesModified,
1347
+ textPreview: turnTextParts.length > 0 ? turnTextParts.join("\n").slice(0, 500) : null,
1348
+ inputTokens: msgUsage?.input_tokens ?? 0,
1349
+ outputTokens: msgUsage?.output_tokens ?? 0,
1350
+ cacheCreationTokens: msgUsage?.cache_creation_input_tokens ?? 0,
1351
+ cacheReadTokens: msgUsage?.cache_read_input_tokens ?? 0
1352
+ };
1353
+ traceCollector.recordTurn(
1354
+ turnData,
1355
+ phase,
1356
+ stringifyEventTruncated(event)
1317
1357
  );
1318
1358
  }
1359
+ } catch {
1319
1360
  }
1320
1361
  }
1321
1362
  if (event.type === "result" && event.result) {
1322
1363
  lastResultText = typeof event.result === "string" ? event.result : JSON.stringify(event.result);
1364
+ try {
1365
+ traceCollector?.recordResult(event);
1366
+ } catch {
1367
+ }
1323
1368
  }
1324
1369
  if (run.toolCalls % 5 === 0 || phase) {
1325
1370
  this.writeAgentStatus(issue.identifier, run);
@@ -1337,10 +1382,12 @@ class Conductor {
1337
1382
  });
1338
1383
  proc.on("error", (err) => {
1339
1384
  clearTimeout(timer);
1385
+ traceCollector?.close();
1340
1386
  reject(new Error(`Failed to spawn claude: ${err.message}`));
1341
1387
  });
1342
1388
  proc.on("close", (code) => {
1343
1389
  clearTimeout(timer);
1390
+ traceCollector?.close();
1344
1391
  run.process = null;
1345
1392
  if (run.logStream && !run.logStream.destroyed) {
1346
1393
  run.logStream.end();
@@ -88,7 +88,7 @@ function createSearchCommand() {
88
88
  console.log("");
89
89
  totalResults += tasks.length;
90
90
  }
91
- } catch (error) {
91
+ } catch {
92
92
  }
93
93
  }
94
94
  if (searchContext) {
@@ -123,7 +123,7 @@ function createSearchCommand() {
123
123
  console.log("");
124
124
  totalResults += contexts.length;
125
125
  }
126
- } catch (error) {
126
+ } catch {
127
127
  }
128
128
  }
129
129
  if (searchContext) {
@@ -154,7 +154,7 @@ function createSearchCommand() {
154
154
  console.log("");
155
155
  totalResults += events.length;
156
156
  }
157
- } catch (error) {
157
+ } catch {
158
158
  }
159
159
  }
160
160
  db.close();
@@ -759,7 +759,7 @@ function createSetupRemoteCommand() {
759
759
  const serviceDir = platform === "darwin" ? join(home, "Library", "LaunchAgents") : join(home, ".config", "systemd", "user");
760
760
  const serviceFile = platform === "darwin" ? join(serviceDir, `${serviceName}.plist`) : join(serviceDir, `${serviceName}.service`);
761
761
  const logDir = join(home, ".stackmemory", "logs");
762
- const pidFile = join(home, ".stackmemory", "remote-mcp.pid");
762
+ const _pidFile = join(home, ".stackmemory", "remote-mcp.pid");
763
763
  if (options.status) {
764
764
  console.log(chalk.cyan("\nRemote MCP Server Status\n"));
765
765
  if (platform === "darwin") {
@@ -15,7 +15,7 @@ function registerSignupCommand(program) {
15
15
  try {
16
16
  await open(signupUrl);
17
17
  console.log(chalk.green("\u2713 Opened: ") + chalk.cyan(signupUrl));
18
- } catch (error) {
18
+ } catch {
19
19
  console.log(chalk.yellow("Could not open browser automatically."));
20
20
  console.log(chalk.gray("Please visit: ") + chalk.cyan(signupUrl));
21
21
  }
@@ -46,7 +46,7 @@ function getVersion() {
46
46
  _version = "0.0.0";
47
47
  return _version;
48
48
  }
49
- function getEnv(key, defaultValue) {
49
+ function _getEnv(key, defaultValue) {
50
50
  const value = process.env[key];
51
51
  if (value === void 0) {
52
52
  if (defaultValue !== void 0) return defaultValue;
@@ -58,7 +58,7 @@ function getEnv(key, defaultValue) {
58
58
  }
59
59
  return value;
60
60
  }
61
- function getOptionalEnv(key) {
61
+ function _getOptionalEnv(key) {
62
62
  return process.env[key];
63
63
  }
64
64
  async function initializeSkillContext() {
@@ -75,7 +75,7 @@ Detected ${worktrees.length} worktree(s):`));
75
75
  } else {
76
76
  contextStatus = "\u25CB Not initialized";
77
77
  }
78
- } catch (error) {
78
+ } catch {
79
79
  contextStatus = "\u2717 Error";
80
80
  }
81
81
  table.push([
@@ -7,7 +7,8 @@ import * as path from "path";
7
7
  import * as yaml from "js-yaml";
8
8
  import {
9
9
  DEFAULT_CONFIG,
10
- PRESET_PROFILES
10
+ PRESET_PROFILES,
11
+ DEFAULT_ENRICHMENT
11
12
  } from "./types.js";
12
13
  class ConfigManager {
13
14
  static instance = null;
@@ -75,6 +76,7 @@ class ConfigManager {
75
76
  }
76
77
  },
77
78
  performance: { ...DEFAULT_CONFIG.performance, ...loaded.performance },
79
+ enrichment: { ...DEFAULT_ENRICHMENT, ...loaded.enrichment },
78
80
  profiles: { ...PRESET_PROFILES, ...loaded.profiles }
79
81
  };
80
82
  if (config.profile && config.profiles?.[config.profile]) {
@@ -103,6 +103,11 @@ const PRESET_PROFILES = {
103
103
  }
104
104
  }
105
105
  };
106
+ const DEFAULT_ENRICHMENT = {
107
+ enabled: false,
108
+ lookbackDepth: 3,
109
+ extractEntities: true
110
+ };
106
111
  const DEFAULT_CONFIG = {
107
112
  version: "1.0",
108
113
  scoring: {
@@ -133,10 +138,12 @@ const DEFAULT_CONFIG = {
133
138
  retrieval_timeout_ms: 500,
134
139
  batch_upload_size: 100
135
140
  },
141
+ enrichment: DEFAULT_ENRICHMENT,
136
142
  profiles: PRESET_PROFILES
137
143
  };
138
144
  export {
139
145
  DEFAULT_CONFIG,
146
+ DEFAULT_ENRICHMENT,
140
147
  DEFAULT_TOOL_SCORES,
141
148
  DEFAULT_WEIGHTS,
142
149
  PRESET_PROFILES
@@ -19,7 +19,7 @@ class DualStackManager {
19
19
  activeContext;
20
20
  handoffRequests = /* @__PURE__ */ new Map();
21
21
  permissionManager;
22
- constructor(adapter, projectId, userId, defaultTeamId) {
22
+ constructor(adapter, projectId, userId, _defaultTeamId) {
23
23
  this.adapter = adapter;
24
24
  this.permissionManager = new PermissionManager();
25
25
  const rawDb = adapter instanceof SQLiteAdapter ? adapter.getRawDatabase() : null;
@@ -796,7 +796,7 @@ class DualStackManager {
796
796
  )
797
797
  );
798
798
  stacks.push(context);
799
- } catch (permissionError) {
799
+ } catch {
800
800
  logger.debug("User lacks access to stack", {
801
801
  stackId: context.stackId,
802
802
  userId: this.activeContext.ownerId