@tangle-network/traces 0.9.0 → 0.9.2

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/cli.js CHANGED
@@ -3,9 +3,7 @@ import {
3
3
  ACTOR_ATTR,
4
4
  ATTR,
5
5
  CORRUPTION_RECEIPT_DISPLAY_LIMIT,
6
- analyzeAdoption,
7
- analyzeReactions,
8
- analyzeSpans,
6
+ appendAll,
9
7
  buildPolicyEvidenceRecord,
10
8
  buildSessionIndexFromRows,
11
9
  commandAnalyzer,
@@ -23,14 +21,8 @@ import {
23
21
  parseSince,
24
22
  planUpload,
25
23
  readSessionIndexFile,
26
- renderAdoption,
27
24
  renderInspectionReport,
28
- renderPipelines,
29
- renderReactions,
30
- renderReport,
31
25
  resolveAdapter,
32
- runExternalAnalyzers,
33
- runPipelines,
34
26
  runTraceImprovement,
35
27
  runTraceInvestigation,
36
28
  saveReport,
@@ -41,7 +33,6 @@ import {
41
33
  serializeTraceStreamEvent,
42
34
  sessionIdFromAttributes,
43
35
  streamSessions,
44
- summarizeDeterministicSignals,
45
36
  traceStreamEventsFromSpans,
46
37
  watchSessions,
47
38
  writeInspectionReportFile,
@@ -49,11 +40,11 @@ import {
49
40
  writePolicyEvidenceFile,
50
41
  writeSessionIndexFile,
51
42
  writeTraceEvidenceExportFile
52
- } from "./chunk-5SBXL62V.js";
43
+ } from "./chunk-RUG26PIS.js";
53
44
 
54
45
  // src/cli.ts
55
46
  import { readFileSync } from "fs";
56
- import { readFile, stat, writeFile } from "fs/promises";
47
+ import { readFile, stat } from "fs/promises";
57
48
  import { basename, resolve } from "path";
58
49
  function packageVersion() {
59
50
  const pkg = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
@@ -217,6 +208,50 @@ async function discover(args) {
217
208
  }
218
209
  return out;
219
210
  }
211
+ function missingPath(error) {
212
+ return typeof error === "object" && error !== null && error.code === "ENOENT";
213
+ }
214
+ async function resolveSelectedSession(args) {
215
+ if (!args.session) throw new Error("resolveSelectedSession requires --session");
216
+ const adapter = resolveAdapter(args.harness);
217
+ if (!adapter && !args.all) throw new Error(`unknown harness "${args.harness}"`);
218
+ try {
219
+ const st = await stat(args.session);
220
+ if (!adapter) {
221
+ throw new Error("--session <path> requires --harness when --all is set");
222
+ }
223
+ return {
224
+ adapter,
225
+ ref: {
226
+ harness: adapter.harness,
227
+ sessionId: args.session,
228
+ path: args.session,
229
+ // --session is an explicit file; honor --cwd so adoption can find the
230
+ // project's .evolve/skill-runs.jsonl (locate() infers cwd in the scan path).
231
+ cwd: args.cwd ?? null,
232
+ mtimeMs: st.mtimeMs
233
+ }
234
+ };
235
+ } catch (error) {
236
+ if (!missingPath(error)) throw error;
237
+ }
238
+ const matches = [];
239
+ for (const candidate of args.all ? adaptersFor(args) : [adapter]) {
240
+ const refs = await locateSessions(candidate, { cwd: args.cwd });
241
+ for (const ref of refs) {
242
+ if (ref.sessionId === args.session) matches.push({ adapter: candidate, ref });
243
+ }
244
+ }
245
+ if (matches.length === 1) return matches[0];
246
+ if (matches.length === 0) {
247
+ throw new Error(
248
+ `no ${args.all ? "selected" : adapter.harness} session with ID "${args.session}"; run \`traces list\` to select an ID, or pass an explicit session path`
249
+ );
250
+ }
251
+ throw new Error(
252
+ `session ID "${args.session}" is ambiguous across ${matches.length} files; select one harness or pass its explicit session path`
253
+ );
254
+ }
220
255
  async function buildAxService() {
221
256
  const apiKey = process.env.OPENAI_API_KEY;
222
257
  if (!apiKey) {
@@ -226,7 +261,7 @@ async function buildAxService() {
226
261
  }
227
262
  const apiURL = process.env.OPENAI_BASE_URL || void 0;
228
263
  const { AxAI } = await import("@ax-llm/ax");
229
- return new AxAI({ name: "openai", apiKey, ...apiURL ? { apiURL } : {} });
264
+ return AxAI.create({ name: "openai", apiKey, ...apiURL ? { apiURL } : {} });
230
265
  }
231
266
  async function cmdList(args) {
232
267
  const groups = await discover({ ...args, last: args.last || 20 });
@@ -274,18 +309,7 @@ function selectedSessionSource(ref, spans, sessionIdOverride) {
274
309
  async function collectSpans(args) {
275
310
  if (args.input) return collectImportedSpans(args);
276
311
  if (args.session) {
277
- const adapter = resolveAdapter(args.harness);
278
- if (!adapter) throw new Error(`unknown harness "${args.harness}"`);
279
- const st = await stat(args.session);
280
- const ref = {
281
- harness: adapter.harness,
282
- sessionId: args.session,
283
- path: args.session,
284
- // --session is an explicit file; honor --cwd so adoption can find the
285
- // project's .evolve/skill-runs.jsonl (locate() infers cwd in the scan path).
286
- cwd: args.cwd ?? null,
287
- mtimeMs: st.mtimeMs
288
- };
312
+ const { adapter, ref } = await resolveSelectedSession(args);
289
313
  const spans2 = await parseSession(adapter, ref);
290
314
  return {
291
315
  spans: spans2,
@@ -303,7 +327,7 @@ async function collectSpans(args) {
303
327
  if (refs.length > 0) harnesses.push(adapter.harness);
304
328
  for (const ref of refs) {
305
329
  const parsed = await parseSession(adapter, ref);
306
- spans.push(...parsed);
330
+ appendAll(spans, parsed);
307
331
  sources.push(selectedSessionSource(ref, parsed));
308
332
  if (ref.cwd) cwds.push(ref.cwd);
309
333
  }
@@ -318,16 +342,7 @@ async function cmdConvert(args) {
318
342
  }
319
343
  async function collectSessionRows(args) {
320
344
  if (args.session) {
321
- const adapter = resolveAdapter(args.harness);
322
- if (!adapter) throw new Error(`unknown harness "${args.harness}"`);
323
- const st = await stat(args.session);
324
- const ref = {
325
- harness: adapter.harness,
326
- sessionId: args.session,
327
- path: args.session,
328
- cwd: args.cwd ?? null,
329
- mtimeMs: st.mtimeMs
330
- };
345
+ const { adapter, ref } = await resolveSelectedSession(args);
331
346
  return [{ ref, spans: await parseSession(adapter, ref) }];
332
347
  }
333
348
  const groups = await discover({ ...args, last: args.last || 20 });
@@ -438,52 +453,14 @@ async function loadExportAttributes(args) {
438
453
  return attributes;
439
454
  }
440
455
  async function cmdAnalyze(args) {
441
- const { spans, harness, cwds, sources } = await collectSpans(args);
442
- if (spans.length === 0) throw new Error("no spans found for the given selection");
443
- const ai = args.llm ? await buildAxService() : void 0;
444
- const { otlpPath, execution, result } = await analyzeSpans(spans, {
445
- ai,
446
- model: args.model,
447
- budgetUsd: args.budget,
448
- otlpOutPath: args.otlp,
449
- log: (msg) => process.stderr.write(`${msg}
450
- `)
451
- });
452
- const pipelines = await runPipelines(spans, { minLoopOccurrences: args.minLoop });
453
- const reactions = analyzeReactions(spans);
454
- const adoption = await analyzeAdoption(spans, { cwds });
455
- const deterministic = summarizeDeterministicSignals(pipelines, reactions);
456
- let report = `${renderReport(result, {
457
- harness,
458
- sessionCount: adoption.identifiedSessionCount,
459
- unassignedTraceCount: adoption.unassignedTraceCount,
460
- spanCount: spans.length,
461
- otlpPath,
462
- execution,
463
- deterministic,
464
- sources
465
- })}
466
- ${renderPipelines(pipelines)}
467
- ${renderReactions(reactions)}
468
- ${renderAdoption(adoption)}`;
469
- if (args.analyzers.length > 0 && otlpPath) {
470
- const engines = externalAnalyzersFromArgs(args);
471
- const results = await runExternalAnalyzers(otlpPath, engines, { prompt: args.analyzerPrompt });
472
- for (const r of results) {
473
- report += `
474
-
475
- ## ${r.analyzer} (external analyzer)
476
-
477
- ${r.ok ? r.output || "(no output)" : `failed: ${r.error}`}`;
478
- }
479
- }
456
+ const result = await investigate(args, { loadDefaultConfig: false });
480
457
  if (args.out) {
481
- await writeFile(args.out, report, "utf8");
458
+ await saveReport(args.out, result.report);
482
459
  console.log(
483
- `report \u2192 ${args.out} (${result.findings.length} findings, ${pipelines.stuckLoops.findings.length} loops, OTLP: ${otlpPath})`
460
+ `report \u2192 ${args.out} (${result.findings.length} findings, ${result.pipelines.stuckLoops.findings.length} loops, OTLP: ${result.otlpPath})`
484
461
  );
485
462
  } else {
486
- console.log(report);
463
+ console.log(result.report);
487
464
  }
488
465
  }
489
466
  async function collectImportedSpans(args) {
@@ -521,11 +498,20 @@ function externalAnalyzersFromArgs(args) {
521
498
  );
522
499
  }
523
500
  async function cmdInvestigate(args) {
501
+ const result = await investigate(args);
502
+ if (args.out) {
503
+ await saveReport(args.out, result.report);
504
+ console.log(`investigation report \u2192 ${args.out} (${result.findings.length} findings with actions, OTLP: ${result.otlpPath})`);
505
+ } else {
506
+ console.log(result.report);
507
+ }
508
+ }
509
+ async function investigate(args, options = {}) {
524
510
  const { spans, harness, cwds, sources } = await collectSpans(args);
525
511
  if (spans.length === 0) throw new Error("no spans found for the given selection");
526
- const config = await loadTracesConfig(args.config);
512
+ const config = args.config !== void 0 || options.loadDefaultConfig !== false ? await loadTracesConfig(args.config) : void 0;
527
513
  const ai = args.llm ? await buildAxService() : void 0;
528
- const result = await runTraceInvestigation(mergeTracesConfig({
514
+ return runTraceInvestigation(mergeTracesConfig({
529
515
  spans,
530
516
  harness,
531
517
  sources,
@@ -540,12 +526,6 @@ async function cmdInvestigate(args) {
540
526
  log: (msg) => process.stderr.write(`${msg}
541
527
  `)
542
528
  }, config));
543
- if (args.out) {
544
- await saveReport(args.out, result.report);
545
- console.log(`investigation report \u2192 ${args.out} (${result.findings.length} findings with actions, OTLP: ${result.otlpPath})`);
546
- } else {
547
- console.log(result.report);
548
- }
549
529
  }
550
530
  async function cmdImprove(args) {
551
531
  const { spans, harness, cwds, sources } = await collectSpans(args);
@@ -822,7 +802,7 @@ Options:
822
802
  --harness <id> Harness or alias (default: claude-code). Known: ${knownHarnesses().join(", ")}
823
803
  --all Sweep every known harness
824
804
  --last <n> Most-recent N sessions
825
- --session <path> Analyze/stream one explicit harness session file
805
+ --session <id|path> Analyze one listed session ID or one explicit harness session file
826
806
  --cwd <dir> Filter sessions by working directory
827
807
  --since <t> upload: window, 30m / 2h / 7d or an ISO date (default 24h); analyze: ISO cutoff
828
808
  --out <path> Write report to a file
package/dist/index.d.ts CHANGED
@@ -815,13 +815,9 @@ declare function analyzeReactions(spans: readonly OtlpSpan[]): ReactionReport;
815
815
  * 2. per-skill invocation frequency
816
816
  * 3. subagent (Task/Agent) spawn frequency
817
817
  *
818
- * CRITICAL undercount fix: the trace-level `Skill` tool-use count only sees
819
- * skills a session invoked *interactively*. Skills dispatched inside an
820
- * automation loop (e.g. `/evolve`, `/converge`) are logged to
821
- * `.evolve/skill-runs.jsonl` under the session's cwd and never surface as a
822
- * `Skill` tool span — historically ~370× more runs than the trace count. So we
823
- * read those files too and report "explicit invocations" and "loop-dispatched
824
- * runs" SEPARATELY, so neither silently reads as zero.
818
+ * A repository's `.evolve/skill-runs.jsonl` is historical context, not session
819
+ * evidence, unless a row carries the selected session ID. This keeps old runs
820
+ * from being attributed to the current trace.
825
821
  */
826
822
 
827
823
  interface AdoptionReport {
@@ -843,6 +839,11 @@ interface AdoptionReport {
843
839
  sessionsWithMaterializedSkills: number;
844
840
  /** Sessions with tool inputs that reference a SKILL.md path. */
845
841
  sessionsWithSkillFileReference: number;
842
+ /** Skill documents opened by a successful captured shell-read command.
843
+ * Inspection is not evidence that the skill changed the outcome. */
844
+ skillDocumentReads: Record<string, number>;
845
+ /** Total observed skill-document reads. */
846
+ totalSkillDocumentReads: number;
846
847
  /** Explicit `Skill` tool invocations per skill name, corpus-wide. */
847
848
  skillInvocations: Record<string, number>;
848
849
  /** Total explicit `Skill` tool invocations. */
@@ -853,19 +854,18 @@ interface AdoptionReport {
853
854
  totalSubagentSpawns: number;
854
855
  /** Sessions that spawned ≥1 subagent. */
855
856
  sessionsWithSubagent: number;
856
- /** Loop-dispatched skill runs per skill, read from `.evolve/skill-runs.jsonl`
857
- * (NOT visible at trace level — counted separately to avoid silent zeros). */
857
+ /** Loop-dispatched skill runs whose row names a selected session ID. */
858
858
  loopDispatchedRuns: Record<string, number>;
859
- /** Total loop-dispatched skill runs. */
859
+ /** Total session-linked loop-dispatched skill runs. */
860
860
  totalLoopDispatchedRuns: number;
861
861
  /** `.evolve/skill-runs.jsonl` files that were read. */
862
862
  skillRunFilesRead: number;
863
+ /** Same-repository loop history without a selected session ID. Never treat
864
+ * this as evidence that a selected session used a skill. */
865
+ unlinkedLoopDispatchedRuns: Record<string, number>;
866
+ /** Total unlinked same-repository loop history rows. */
867
+ totalUnlinkedLoopDispatchedRuns: number;
863
868
  }
864
- /**
865
- * Count loop-dispatched skill runs from a `.evolve/skill-runs.jsonl` file's raw
866
- * contents. Each line records one or more skills under `skills` (array) or
867
- * `skill` (string) — the schema varies across producers, so accept both.
868
- */
869
869
  declare function countSkillRunsJsonl(raw: string): Record<string, number>;
870
870
  interface AdoptionOptions {
871
871
  /** Session cwds to probe for `.evolve/skill-runs.jsonl`. The CLI passes the
package/dist/index.js CHANGED
@@ -115,7 +115,7 @@ import {
115
115
  writeSessionIndexFile,
116
116
  writeTraceEvidenceExportFile,
117
117
  writeTraceImprovementArtifacts
118
- } from "./chunk-5SBXL62V.js";
118
+ } from "./chunk-RUG26PIS.js";
119
119
 
120
120
  // src/collect.ts
121
121
  async function collectSessions(opts = {}) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/traces",
3
- "version": "0.9.0",
3
+ "version": "0.9.2",
4
4
  "description": "Point it at your coding-agent session traces (Claude Code, Codex, OpenCode, Gemini, Pi, …) and get failure-mode + efficiency findings. CLI + SDK over the @tangle-network/agent-eval analyst suite — observe live sessions, run your own analysts, redact, and upload to the Tangle Intelligence Platform.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -44,8 +44,8 @@
44
44
  "node": ">=22"
45
45
  },
46
46
  "dependencies": {
47
- "@ax-llm/ax": "^19.0.45",
48
- "@tangle-network/agent-eval": "^0.118.3"
47
+ "@ax-llm/ax": "^23.0.1",
48
+ "@tangle-network/agent-eval": "^0.122.8"
49
49
  },
50
50
  "devDependencies": {
51
51
  "@types/node": "^22.0.0",