@tangle-network/traces 0.8.0 → 0.8.11
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/README.md +81 -1
- package/dist/{chunk-BUD6I3ZJ.js → chunk-PRRWLKSI.js} +1943 -323
- package/dist/cli.js +309 -12
- package/dist/index.d.ts +471 -27
- package/dist/index.js +71 -5
- package/package.json +12 -13
package/dist/cli.js
CHANGED
|
@@ -1,23 +1,39 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
+
analyzeAdoption,
|
|
4
|
+
analyzeReactions,
|
|
3
5
|
analyzeSpans,
|
|
6
|
+
buildPolicyEvidenceRecord,
|
|
7
|
+
buildSessionIndexFromRows,
|
|
4
8
|
commandAnalyzer,
|
|
5
9
|
commandRedactor,
|
|
6
10
|
executeUpload,
|
|
11
|
+
exportTraceEvidenceFile,
|
|
7
12
|
haloAnalyzer,
|
|
13
|
+
inspectSessionIndex,
|
|
8
14
|
knownHarnesses,
|
|
15
|
+
parseSession,
|
|
9
16
|
parseSince,
|
|
10
17
|
planUpload,
|
|
18
|
+
readSessionIndexFile,
|
|
19
|
+
renderInspectionReport,
|
|
11
20
|
resolveAdapter,
|
|
12
21
|
runExternalAnalyzers,
|
|
13
22
|
runPipelines,
|
|
14
23
|
selectAdapters,
|
|
24
|
+
serializePolicyEvidence,
|
|
25
|
+
serializeSessionIndex,
|
|
26
|
+
serializeSpans,
|
|
15
27
|
watchSessions,
|
|
16
|
-
|
|
17
|
-
|
|
28
|
+
writeInspectionReportFile,
|
|
29
|
+
writeOtlpFile,
|
|
30
|
+
writePolicyEvidenceFile,
|
|
31
|
+
writeSessionIndexFile,
|
|
32
|
+
writeTraceEvidenceExportFile
|
|
33
|
+
} from "./chunk-PRRWLKSI.js";
|
|
18
34
|
|
|
19
35
|
// src/cli.ts
|
|
20
|
-
import { stat, writeFile } from "fs/promises";
|
|
36
|
+
import { readFile, stat, writeFile } from "fs/promises";
|
|
21
37
|
|
|
22
38
|
// src/report.ts
|
|
23
39
|
var SEVERITY_RANK = { critical: 0, high: 1, medium: 2, low: 3, info: 4 };
|
|
@@ -95,11 +111,93 @@ function renderPipelines(pr) {
|
|
|
95
111
|
lines.push("");
|
|
96
112
|
return lines.join("\n");
|
|
97
113
|
}
|
|
114
|
+
var REACTION_BADGE = {
|
|
115
|
+
correction: "correction",
|
|
116
|
+
frustration: "frustration",
|
|
117
|
+
jargon: "jargon-complaint",
|
|
118
|
+
structure: "structure-complaint",
|
|
119
|
+
praise: "praise"
|
|
120
|
+
};
|
|
121
|
+
function ratioStr(r) {
|
|
122
|
+
if (r === null) return "n/a (no reaction signals)";
|
|
123
|
+
if (!Number.isFinite(r)) return "\u221E (corrective with zero praise)";
|
|
124
|
+
return `${r.toFixed(2)}:1`;
|
|
125
|
+
}
|
|
126
|
+
function oneLine(s, n) {
|
|
127
|
+
const t = s.replace(/\s+/g, " ").trim();
|
|
128
|
+
return t.length > n ? `${t.slice(0, n)}\u2026` : t;
|
|
129
|
+
}
|
|
130
|
+
function renderReactions(rr) {
|
|
131
|
+
const lines = ["## user reactions (deterministic, human turns only)", ""];
|
|
132
|
+
const total = Object.values(rr.signals).reduce((a, b) => a + b, 0);
|
|
133
|
+
if (rr.humanReactionTurns === 0) {
|
|
134
|
+
lines.push("- No human turns followed an assistant turn (nothing to classify).");
|
|
135
|
+
lines.push("");
|
|
136
|
+
return lines.join("\n");
|
|
137
|
+
}
|
|
138
|
+
lines.push(
|
|
139
|
+
`- **Reaction turns:** ${rr.humanReactionTurns} human turn(s) followed an assistant turn; ${total} carried a reaction signal.`
|
|
140
|
+
);
|
|
141
|
+
lines.push(`- **Corrective-to-positive ratio:** ${ratioStr(rr.correctiveToPositiveRatio)}`);
|
|
142
|
+
lines.push("");
|
|
143
|
+
lines.push("| Signal | Count |");
|
|
144
|
+
lines.push("|---|---|");
|
|
145
|
+
for (const [k, v] of Object.entries(rr.signals)) {
|
|
146
|
+
if (v > 0) lines.push(`| ${REACTION_BADGE[k] ?? k} | ${v} |`);
|
|
147
|
+
}
|
|
148
|
+
lines.push("");
|
|
149
|
+
if (rr.triggerPairs.length > 0) {
|
|
150
|
+
lines.push("### top trigger pairs (assistant prose \u2192 human reaction)");
|
|
151
|
+
lines.push("");
|
|
152
|
+
for (const p of rr.triggerPairs) {
|
|
153
|
+
lines.push(`- **[${p.reactions.map((r) => REACTION_BADGE[r] ?? r).join(", ")}]**`);
|
|
154
|
+
lines.push(` - assistant: \`${oneLine(p.assistant, 180)}\``);
|
|
155
|
+
lines.push(` - human: \`${oneLine(p.human, 180)}\``);
|
|
156
|
+
}
|
|
157
|
+
lines.push("");
|
|
158
|
+
}
|
|
159
|
+
return lines.join("\n");
|
|
160
|
+
}
|
|
161
|
+
function renderAdoption(ar) {
|
|
162
|
+
const lines = ["## skill & subagent adoption (deterministic)", ""];
|
|
163
|
+
lines.push(
|
|
164
|
+
`- **Skill penetration:** ${(ar.skillPenetration * 100).toFixed(0)}% (${ar.sessionsWithSkill}/${ar.sessionCount} session(s) invoked a skill explicitly)`
|
|
165
|
+
);
|
|
166
|
+
lines.push(
|
|
167
|
+
`- **Explicit skill invocations:** ${ar.totalSkillInvocations} (Skill tool spans) \xB7 **Loop-dispatched runs:** ${ar.totalLoopDispatchedRuns} (from ${ar.skillRunFilesRead} \`.evolve/skill-runs.jsonl\` file(s))`
|
|
168
|
+
);
|
|
169
|
+
lines.push(
|
|
170
|
+
`- **Subagent spawns:** ${ar.totalSubagentSpawns} across ${ar.sessionsWithSubagent} session(s)`
|
|
171
|
+
);
|
|
172
|
+
lines.push("");
|
|
173
|
+
const skillRows = Object.entries(ar.skillInvocations).sort((a, b) => b[1] - a[1]);
|
|
174
|
+
const loopRows = Object.entries(ar.loopDispatchedRuns).sort((a, b) => b[1] - a[1]);
|
|
175
|
+
if (skillRows.length > 0 || loopRows.length > 0) {
|
|
176
|
+
lines.push("| Skill | Explicit invocations | Loop-dispatched runs |");
|
|
177
|
+
lines.push("|---|---|---|");
|
|
178
|
+
const names = [.../* @__PURE__ */ new Set([...skillRows.map((r) => r[0]), ...loopRows.map((r) => r[0])])].sort(
|
|
179
|
+
(a, b) => (ar.skillInvocations[b] ?? 0) + (ar.loopDispatchedRuns[b] ?? 0) - (ar.skillInvocations[a] ?? 0) - (ar.loopDispatchedRuns[a] ?? 0)
|
|
180
|
+
);
|
|
181
|
+
for (const n of names) {
|
|
182
|
+
lines.push(`| \`${n}\` | ${ar.skillInvocations[n] ?? 0} | ${ar.loopDispatchedRuns[n] ?? 0} |`);
|
|
183
|
+
}
|
|
184
|
+
lines.push("");
|
|
185
|
+
}
|
|
186
|
+
const agentRows = Object.entries(ar.subagentSpawns).sort((a, b) => b[1] - a[1]);
|
|
187
|
+
if (agentRows.length > 0) {
|
|
188
|
+
lines.push("| Subagent | Spawns |");
|
|
189
|
+
lines.push("|---|---|");
|
|
190
|
+
for (const [n, c] of agentRows) lines.push(`| \`${n}\` | ${c} |`);
|
|
191
|
+
lines.push("");
|
|
192
|
+
}
|
|
193
|
+
return lines.join("\n");
|
|
194
|
+
}
|
|
98
195
|
|
|
99
196
|
// src/cli.ts
|
|
100
197
|
function parseArgs(argv) {
|
|
101
198
|
const a = {
|
|
102
|
-
command: argv[0] ?? "help",
|
|
199
|
+
command: argv[0] === "--help" || argv[0] === "-h" ? "help" : argv[0] ?? "help",
|
|
200
|
+
help: argv[0] === "--help" || argv[0] === "-h",
|
|
103
201
|
harness: "claude-code",
|
|
104
202
|
harnessExplicit: false,
|
|
105
203
|
all: false,
|
|
@@ -111,7 +209,8 @@ function parseArgs(argv) {
|
|
|
111
209
|
dryRun: false,
|
|
112
210
|
yes: false,
|
|
113
211
|
noContent: false,
|
|
114
|
-
analyzers: []
|
|
212
|
+
analyzers: [],
|
|
213
|
+
attrs: []
|
|
115
214
|
};
|
|
116
215
|
for (let i = 1; i < argv.length; i++) {
|
|
117
216
|
const arg = argv[i];
|
|
@@ -151,6 +250,14 @@ function parseArgs(argv) {
|
|
|
151
250
|
case "--model":
|
|
152
251
|
a.model = next();
|
|
153
252
|
break;
|
|
253
|
+
case "--metadata":
|
|
254
|
+
a.metadata = next();
|
|
255
|
+
break;
|
|
256
|
+
case "--attr": {
|
|
257
|
+
const v = next();
|
|
258
|
+
if (v) a.attrs.push(v);
|
|
259
|
+
break;
|
|
260
|
+
}
|
|
154
261
|
case "--interval":
|
|
155
262
|
a.interval = Number(next());
|
|
156
263
|
break;
|
|
@@ -177,12 +284,21 @@ function parseArgs(argv) {
|
|
|
177
284
|
case "--redactor":
|
|
178
285
|
a.redactorCmd = next();
|
|
179
286
|
break;
|
|
287
|
+
case "--format":
|
|
288
|
+
a.format = next();
|
|
289
|
+
break;
|
|
290
|
+
case "--help":
|
|
291
|
+
case "-h":
|
|
292
|
+
a.help = true;
|
|
293
|
+
break;
|
|
180
294
|
case "--yes":
|
|
181
295
|
case "-y":
|
|
182
296
|
a.yes = true;
|
|
183
297
|
break;
|
|
184
298
|
default:
|
|
185
299
|
if (arg?.startsWith("--")) throw new Error(`unknown flag: ${arg}`);
|
|
300
|
+
if (arg && !a.input) a.input = arg;
|
|
301
|
+
else if (arg) throw new Error(`unexpected positional argument: ${arg}`);
|
|
186
302
|
}
|
|
187
303
|
}
|
|
188
304
|
return a;
|
|
@@ -243,23 +359,28 @@ async function collectSpans(args) {
|
|
|
243
359
|
harness: adapter.harness,
|
|
244
360
|
sessionId: args.session,
|
|
245
361
|
path: args.session,
|
|
246
|
-
cwd
|
|
362
|
+
// --session is an explicit file; honor --cwd so adoption can find the
|
|
363
|
+
// project's .evolve/skill-runs.jsonl (locate() infers cwd in the scan path).
|
|
364
|
+
cwd: args.cwd ?? null,
|
|
247
365
|
mtimeMs: st.mtimeMs
|
|
248
366
|
};
|
|
249
|
-
|
|
367
|
+
const spans2 = await parseSession(adapter, ref);
|
|
368
|
+
return { spans: spans2, harness: adapter.harness, sessionCount: 1, cwds: ref.cwd ? [ref.cwd] : [] };
|
|
250
369
|
}
|
|
251
370
|
const groups = await discover({ ...args, last: args.last || 1 });
|
|
252
371
|
const spans = [];
|
|
253
372
|
let sessionCount = 0;
|
|
254
373
|
const harnesses = [];
|
|
374
|
+
const cwds = [];
|
|
255
375
|
for (const { adapter, refs } of groups) {
|
|
256
376
|
if (refs.length > 0) harnesses.push(adapter.harness);
|
|
257
377
|
for (const ref of refs) {
|
|
258
|
-
spans.push(...await adapter
|
|
378
|
+
spans.push(...await parseSession(adapter, ref));
|
|
379
|
+
if (ref.cwd) cwds.push(ref.cwd);
|
|
259
380
|
sessionCount += 1;
|
|
260
381
|
}
|
|
261
382
|
}
|
|
262
|
-
return { spans, harness: harnesses.join("+") || args.harness, sessionCount };
|
|
383
|
+
return { spans, harness: harnesses.join("+") || args.harness, sessionCount, cwds };
|
|
263
384
|
}
|
|
264
385
|
async function cmdConvert(args) {
|
|
265
386
|
const { spans } = await collectSpans(args);
|
|
@@ -267,8 +388,125 @@ async function cmdConvert(args) {
|
|
|
267
388
|
const path = await writeOtlpFile(spans, args.otlp);
|
|
268
389
|
console.log(`wrote ${spans.length} spans \u2192 ${path}`);
|
|
269
390
|
}
|
|
391
|
+
async function collectSessionRows(args) {
|
|
392
|
+
if (args.session) {
|
|
393
|
+
const adapter = resolveAdapter(args.harness);
|
|
394
|
+
if (!adapter) throw new Error(`unknown harness "${args.harness}"`);
|
|
395
|
+
const st = await stat(args.session);
|
|
396
|
+
const ref = {
|
|
397
|
+
harness: adapter.harness,
|
|
398
|
+
sessionId: args.session,
|
|
399
|
+
path: args.session,
|
|
400
|
+
cwd: args.cwd ?? null,
|
|
401
|
+
mtimeMs: st.mtimeMs
|
|
402
|
+
};
|
|
403
|
+
return [{ ref, spans: await parseSession(adapter, ref) }];
|
|
404
|
+
}
|
|
405
|
+
const groups = await discover({ ...args, last: args.last || 20 });
|
|
406
|
+
const rows = [];
|
|
407
|
+
for (const { adapter, refs } of groups) {
|
|
408
|
+
for (const ref of refs) rows.push({ ref, spans: await parseSession(adapter, ref) });
|
|
409
|
+
}
|
|
410
|
+
return rows;
|
|
411
|
+
}
|
|
412
|
+
async function cmdEvidence(args) {
|
|
413
|
+
const rows = (await collectSessionRows(args)).filter((row) => row.spans.length > 0);
|
|
414
|
+
if (rows.length === 0) throw new Error("no spans found for the given selection");
|
|
415
|
+
const otlpPath = args.otlp ? await writeOtlpFile(rows.flatMap((row) => row.spans), args.otlp) : void 0;
|
|
416
|
+
const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
417
|
+
const records = await Promise.all(rows.map(
|
|
418
|
+
(row) => buildPolicyEvidenceRecord(row.ref, row.spans, {
|
|
419
|
+
generatedAt,
|
|
420
|
+
minLoopOccurrences: args.minLoop,
|
|
421
|
+
maxLoopExamples: 25,
|
|
422
|
+
otlpPath
|
|
423
|
+
})
|
|
424
|
+
));
|
|
425
|
+
if (args.out) {
|
|
426
|
+
const path = await writePolicyEvidenceFile(records, args.out);
|
|
427
|
+
console.log(`policy evidence \u2192 ${path} (${records.length} session rows${otlpPath ? `, OTLP: ${otlpPath}` : ""})`);
|
|
428
|
+
} else {
|
|
429
|
+
process.stdout.write(serializePolicyEvidence(records));
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
async function cmdIndex(args) {
|
|
433
|
+
const rows = (await collectSessionRows(args)).filter((row) => row.spans.length > 0);
|
|
434
|
+
if (rows.length === 0) throw new Error("no spans found for the given selection");
|
|
435
|
+
const index = await buildSessionIndexFromRows(rows, {
|
|
436
|
+
minLoopOccurrences: args.minLoop,
|
|
437
|
+
selection: {
|
|
438
|
+
command: "index",
|
|
439
|
+
harness: args.harnessExplicit ? args.harness : void 0,
|
|
440
|
+
all: args.all || void 0,
|
|
441
|
+
last: args.last || void 0,
|
|
442
|
+
session: args.session,
|
|
443
|
+
cwd: args.cwd,
|
|
444
|
+
since: args.since
|
|
445
|
+
}
|
|
446
|
+
});
|
|
447
|
+
if (args.out) {
|
|
448
|
+
const path = await writeSessionIndexFile(index, args.out);
|
|
449
|
+
console.log(`session index \u2192 ${path} (${index.totals.sessions} session rows)`);
|
|
450
|
+
} else {
|
|
451
|
+
process.stdout.write(serializeSessionIndex(index));
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
async function cmdInspect(args) {
|
|
455
|
+
if (!args.input) throw new Error("inspect needs an index file; run `traces index --out session-index.json` first");
|
|
456
|
+
const index = await readSessionIndexFile(args.input);
|
|
457
|
+
const report = inspectSessionIndex(index);
|
|
458
|
+
if (args.out) {
|
|
459
|
+
const path = await writeInspectionReportFile(report, args.out);
|
|
460
|
+
console.log(`inspection report \u2192 ${path} (${report.totals.findings} finding(s), high ${report.totals.high})`);
|
|
461
|
+
} else {
|
|
462
|
+
process.stdout.write(renderInspectionReport(report));
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
async function cmdExport(args) {
|
|
466
|
+
if (!args.input) throw new Error("export needs an input file; run `traces export --help` for examples");
|
|
467
|
+
const format = args.format ?? "auto";
|
|
468
|
+
if (format !== "auto" && format !== "policy-evidence" && format !== "sandbox-events" && format !== "openinference") {
|
|
469
|
+
throw new Error("--format must be auto, policy-evidence, sandbox-events, or openinference");
|
|
470
|
+
}
|
|
471
|
+
const attributes = await loadExportAttributes(args);
|
|
472
|
+
const outPath = args.out ?? args.otlp;
|
|
473
|
+
if (outPath) {
|
|
474
|
+
const result2 = await writeTraceEvidenceExportFile(args.input, outPath, { format, attributes });
|
|
475
|
+
console.log(
|
|
476
|
+
`exported ${result2.spans.length} OpenInference span(s) from ${result2.format} \u2192 ${result2.path} (${result2.redactionCount} redaction${result2.redactionCount === 1 ? "" : "s"})`
|
|
477
|
+
);
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
const result = await exportTraceEvidenceFile(args.input, { format, attributes });
|
|
481
|
+
process.stdout.write(serializeSpans(result.spans));
|
|
482
|
+
}
|
|
483
|
+
function parseAttributeValue(raw) {
|
|
484
|
+
if (raw === "true") return true;
|
|
485
|
+
if (raw === "false") return false;
|
|
486
|
+
if (/^-?\d+(?:\.\d+)?$/.test(raw)) {
|
|
487
|
+
const number = Number(raw);
|
|
488
|
+
if (Number.isFinite(number)) return number;
|
|
489
|
+
}
|
|
490
|
+
return raw;
|
|
491
|
+
}
|
|
492
|
+
async function loadExportAttributes(args) {
|
|
493
|
+
const attributes = {};
|
|
494
|
+
if (args.metadata) {
|
|
495
|
+
const parsed = JSON.parse(await readFile(args.metadata, "utf8"));
|
|
496
|
+
if (parsed == null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
497
|
+
throw new Error("--metadata must point to a JSON object");
|
|
498
|
+
}
|
|
499
|
+
Object.assign(attributes, parsed);
|
|
500
|
+
}
|
|
501
|
+
for (const attr of args.attrs) {
|
|
502
|
+
const eq = attr.indexOf("=");
|
|
503
|
+
if (eq <= 0) throw new Error(`--attr must be key=value, got ${attr}`);
|
|
504
|
+
attributes[attr.slice(0, eq)] = parseAttributeValue(attr.slice(eq + 1));
|
|
505
|
+
}
|
|
506
|
+
return attributes;
|
|
507
|
+
}
|
|
270
508
|
async function cmdAnalyze(args) {
|
|
271
|
-
const { spans, harness, sessionCount } = await collectSpans(args);
|
|
509
|
+
const { spans, harness, sessionCount, cwds } = await collectSpans(args);
|
|
272
510
|
if (spans.length === 0) throw new Error("no spans found for the given selection");
|
|
273
511
|
const ai = args.llm ? await buildAxService() : void 0;
|
|
274
512
|
const { otlpPath, result } = await analyzeSpans(spans, {
|
|
@@ -280,8 +518,12 @@ async function cmdAnalyze(args) {
|
|
|
280
518
|
`)
|
|
281
519
|
});
|
|
282
520
|
const pipelines = await runPipelines(spans, { minLoopOccurrences: args.minLoop });
|
|
521
|
+
const reactions = analyzeReactions(spans);
|
|
522
|
+
const adoption = await analyzeAdoption(spans, { cwds });
|
|
283
523
|
let report = `${renderReport(result, { harness, sessionCount, spanCount: spans.length, otlpPath })}
|
|
284
|
-
${renderPipelines(pipelines)}
|
|
524
|
+
${renderPipelines(pipelines)}
|
|
525
|
+
${renderReactions(reactions)}
|
|
526
|
+
${renderAdoption(adoption)}`;
|
|
285
527
|
if (args.analyzers.length > 0 && otlpPath) {
|
|
286
528
|
const engines = args.analyzers.map(
|
|
287
529
|
(spec) => spec === "halo" ? haloAnalyzer({ defaultPrompt: args.analyzerPrompt, model: args.model }) : commandAnalyzer({ name: spec, command: spec, args: (p, prompt) => prompt ? [p, prompt] : [p] })
|
|
@@ -382,6 +624,32 @@ Window: since ${new Date(sinceMs).toISOString()}
|
|
|
382
624
|
`Uploaded ${res.uploadedSessions} session(s), ${res.acceptedSpans} spans accepted, ${res.redactionCount} redaction(s); ${res.skippedSessions} already-uploaded skipped.`
|
|
383
625
|
);
|
|
384
626
|
}
|
|
627
|
+
function usageExport() {
|
|
628
|
+
console.log(`traces export \u2014 convert trace evidence files to OpenInference JSONL
|
|
629
|
+
|
|
630
|
+
Usage:
|
|
631
|
+
traces export <input.jsonl|input.json> --out <spans.jsonl> [--format auto]
|
|
632
|
+
traces export <input.json> --attr task.id=abc --attr arm=baseline --out spans.jsonl
|
|
633
|
+
traces export <input.json> --metadata run-metadata.json --out spans.jsonl
|
|
634
|
+
traces export <input.jsonl|input.json> > spans.jsonl
|
|
635
|
+
|
|
636
|
+
Input formats:
|
|
637
|
+
policy-evidence Compact JSONL rows from \`traces evidence --out policy-evidence.jsonl\`
|
|
638
|
+
sandbox-events Sandbox/OpenCode event arrays with start/raw/result/done/error events
|
|
639
|
+
openinference Existing OpenInference JSONL; rewrites through traces redaction
|
|
640
|
+
auto Detect the format from the file contents (default)
|
|
641
|
+
|
|
642
|
+
Examples:
|
|
643
|
+
traces evidence --all --since 24h --out policy-evidence.jsonl
|
|
644
|
+
traces export policy-evidence.jsonl --out spans.openinference.jsonl
|
|
645
|
+
traces export sandbox-events.json --format sandbox-events --out spans.openinference.jsonl
|
|
646
|
+
traces export sandbox-events.json --attr task.id=aec-001 --attr outcome.score=1 --out spans.openinference.jsonl
|
|
647
|
+
halo spans.openinference.jsonl --prompt "Analyze this trace slice" --max-turns 1
|
|
648
|
+
|
|
649
|
+
Safety:
|
|
650
|
+
export runs the same local regex redaction used by upload before writing spans.
|
|
651
|
+
--metadata must be a JSON object; --attr key=value is repeatable and overrides matching metadata keys.`);
|
|
652
|
+
}
|
|
385
653
|
function usage() {
|
|
386
654
|
console.log(`traces \u2014 analyze, observe & upload coding-agent sessions
|
|
387
655
|
|
|
@@ -389,6 +657,10 @@ Commands:
|
|
|
389
657
|
list List discovered sessions
|
|
390
658
|
analyze Run analyst suite + loop/waste pipelines, write a markdown report
|
|
391
659
|
convert Emit OTLP-JSONL only (HALO: use analyze --analyzer halo)
|
|
660
|
+
index Emit a reusable session index JSON for later investigation
|
|
661
|
+
inspect Read a session index and print ranked improvement findings
|
|
662
|
+
export Convert evidence/events files to OpenInference JSONL for HALO
|
|
663
|
+
evidence Emit compact session-evidence JSONL for downstream policy miners
|
|
392
664
|
watch Online observer: tail active sessions, notify on stuck loops (read-only)
|
|
393
665
|
upload Redact + upload sessions in a time window to the Tangle Intelligence Platform
|
|
394
666
|
|
|
@@ -400,7 +672,10 @@ Options:
|
|
|
400
672
|
--cwd <dir> Filter sessions by working directory
|
|
401
673
|
--since <t> upload: window \u2014 30m / 2h / 7d or an ISO date (default 24h); analyze: ISO cutoff
|
|
402
674
|
--out <path> Write report to a file
|
|
403
|
-
--otlp <path> OTLP artifact path (also
|
|
675
|
+
--otlp <path> OTLP artifact path (also evidence provenance / dry-run upload preview)
|
|
676
|
+
--format <kind> export: auto | policy-evidence | sandbox-events | openinference
|
|
677
|
+
--metadata <json> export: attach JSON object fields as span attributes
|
|
678
|
+
--attr <k=v> export: attach one span attribute (repeatable)
|
|
404
679
|
--llm Enable agentic RLM analysts (needs OPENAI_API_KEY / OPENAI_BASE_URL)
|
|
405
680
|
--model <id> --llm model id (e.g. a router model like glm-5.2); default is agent-eval's
|
|
406
681
|
--budget <usd> USD cap for agentic analysts
|
|
@@ -413,12 +688,22 @@ Options:
|
|
|
413
688
|
--no-content upload: strip prompt/response text \u2014 send metadata only
|
|
414
689
|
--redactor <cmd> upload: external PII scrubber (JSON array stdin\u2192stdout) after the regex pass
|
|
415
690
|
--yes, -y upload: skip the confirmation prompt
|
|
691
|
+
--help, -h Show help (use \`traces export --help\` for export examples)
|
|
416
692
|
|
|
417
693
|
Upload env: TANGLE_INGEST_URL (or TANGLE_ORCHESTRATOR_URL), TANGLE_INGEST_API_KEY (or TANGLE_API_KEY), TANGLE_TENANT_ID`);
|
|
418
694
|
}
|
|
419
695
|
async function main() {
|
|
420
696
|
const args = parseArgs(process.argv.slice(2));
|
|
697
|
+
if (args.help) {
|
|
698
|
+
if (args.command === "export" || args.command === "help" && args.input === "export") usageExport();
|
|
699
|
+
else usage();
|
|
700
|
+
return;
|
|
701
|
+
}
|
|
421
702
|
switch (args.command) {
|
|
703
|
+
case "help":
|
|
704
|
+
if (args.input === "export") usageExport();
|
|
705
|
+
else usage();
|
|
706
|
+
break;
|
|
422
707
|
case "list":
|
|
423
708
|
await cmdList(args);
|
|
424
709
|
break;
|
|
@@ -428,6 +713,18 @@ async function main() {
|
|
|
428
713
|
case "convert":
|
|
429
714
|
await cmdConvert(args);
|
|
430
715
|
break;
|
|
716
|
+
case "index":
|
|
717
|
+
await cmdIndex(args);
|
|
718
|
+
break;
|
|
719
|
+
case "inspect":
|
|
720
|
+
await cmdInspect(args);
|
|
721
|
+
break;
|
|
722
|
+
case "export":
|
|
723
|
+
await cmdExport(args);
|
|
724
|
+
break;
|
|
725
|
+
case "evidence":
|
|
726
|
+
await cmdEvidence(args);
|
|
727
|
+
break;
|
|
431
728
|
case "watch":
|
|
432
729
|
await cmdWatch(args);
|
|
433
730
|
break;
|