@kweaver-ai/kweaver-sdk 0.8.1 → 0.8.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.
Files changed (183) hide show
  1. package/README.md +19 -5
  2. package/README.zh.md +19 -5
  3. package/dist/agent-providers/index.d.ts +7 -0
  4. package/dist/agent-providers/index.js +5 -0
  5. package/dist/agent-providers/prompt-template.d.ts +62 -0
  6. package/dist/agent-providers/prompt-template.js +105 -0
  7. package/dist/agent-providers/prompts/rubric-judge-v1.prompt.md +51 -0
  8. package/dist/agent-providers/prompts/within-trace-synthesizer-v1.prompt.md +60 -0
  9. package/dist/agent-providers/providers/claude-code-subprocess.d.ts +74 -0
  10. package/dist/agent-providers/providers/claude-code-subprocess.js +259 -0
  11. package/dist/agent-providers/providers/stub.d.ts +47 -0
  12. package/dist/agent-providers/providers/stub.js +77 -0
  13. package/dist/agent-providers/registry.d.ts +45 -0
  14. package/dist/agent-providers/registry.js +77 -0
  15. package/dist/agent-providers/types.d.ts +91 -0
  16. package/dist/agent-providers/types.js +25 -0
  17. package/dist/api/agent-chat.js +8 -6
  18. package/dist/api/context-loader.d.ts +1 -0
  19. package/dist/api/semantic-search.d.ts +5 -0
  20. package/dist/api/semantic-search.js +5 -0
  21. package/dist/api/skills.d.ts +75 -2
  22. package/dist/api/skills.js +108 -12
  23. package/dist/api/trace.d.ts +5 -0
  24. package/dist/api/trace.js +4 -0
  25. package/dist/cli.js +7 -5
  26. package/dist/commands/agent/mode.d.ts +6 -0
  27. package/dist/commands/agent/mode.js +75 -0
  28. package/dist/commands/agent.js +101 -29
  29. package/dist/commands/context-loader.js +608 -38
  30. package/dist/commands/skill.d.ts +21 -1
  31. package/dist/commands/skill.js +389 -1
  32. package/dist/commands/trace.d.ts +26 -1
  33. package/dist/commands/trace.js +515 -15
  34. package/dist/index.d.ts +2 -2
  35. package/dist/index.js +1 -1
  36. package/dist/resources/bkn.d.ts +5 -0
  37. package/dist/resources/bkn.js +5 -0
  38. package/dist/resources/skills.d.ts +17 -1
  39. package/dist/resources/skills.js +32 -1
  40. package/dist/trace-ai/diagnose/agent-binding.d.ts +67 -0
  41. package/dist/trace-ai/diagnose/agent-binding.js +257 -0
  42. package/dist/trace-ai/diagnose/builtin-rules/tool-retry-intent-mismatch.yaml +68 -0
  43. package/dist/trace-ai/diagnose/index.d.ts +32 -0
  44. package/dist/trace-ai/diagnose/index.js +246 -0
  45. package/dist/trace-ai/diagnose/output-schema-converter.d.ts +24 -0
  46. package/dist/trace-ai/diagnose/output-schema-converter.js +81 -0
  47. package/dist/trace-ai/diagnose/query-extractor.d.ts +14 -0
  48. package/dist/trace-ai/diagnose/query-extractor.js +45 -0
  49. package/dist/trace-ai/diagnose/report-assembler.d.ts +31 -0
  50. package/dist/{trace-core → trace-ai}/diagnose/report-assembler.js +19 -9
  51. package/dist/trace-ai/diagnose/report-markdown.d.ts +18 -0
  52. package/dist/trace-ai/diagnose/report-markdown.js +192 -0
  53. package/dist/{trace-core → trace-ai}/diagnose/rule-loader.js +42 -8
  54. package/dist/{trace-core → trace-ai}/diagnose/schemas.d.ts +77 -2
  55. package/dist/trace-ai/diagnose/schemas.js +154 -0
  56. package/dist/trace-ai/diagnose/signal-probe.d.ts +17 -0
  57. package/dist/trace-ai/diagnose/signal-probe.js +39 -0
  58. package/dist/trace-ai/diagnose/synthesizer-agent.d.ts +40 -0
  59. package/dist/trace-ai/diagnose/synthesizer-agent.js +158 -0
  60. package/dist/{trace-core → trace-ai}/diagnose/trace-shaper.js +1 -0
  61. package/dist/{trace-core → trace-ai}/diagnose/types.d.ts +55 -6
  62. package/dist/trace-ai/eval-set/assertion-evaluator.d.ts +29 -0
  63. package/dist/trace-ai/eval-set/assertion-evaluator.js +100 -0
  64. package/dist/trace-ai/eval-set/builder.d.ts +36 -0
  65. package/dist/trace-ai/eval-set/builder.js +126 -0
  66. package/dist/trace-ai/eval-set/index.d.ts +15 -0
  67. package/dist/trace-ai/eval-set/index.js +10 -0
  68. package/dist/trace-ai/eval-set/output-writer.d.ts +27 -0
  69. package/dist/trace-ai/eval-set/output-writer.js +126 -0
  70. package/dist/trace-ai/eval-set/query-picker.d.ts +37 -0
  71. package/dist/trace-ai/eval-set/query-picker.js +147 -0
  72. package/dist/trace-ai/eval-set/redactor.d.ts +42 -0
  73. package/dist/trace-ai/eval-set/redactor.js +133 -0
  74. package/dist/trace-ai/eval-set/rubric-templates/answer-match-reference.prompt.md +19 -0
  75. package/dist/trace-ai/eval-set/schemas.d.ts +136 -0
  76. package/dist/trace-ai/eval-set/schemas.js +130 -0
  77. package/dist/trace-ai/eval-set/semantic-match-provider.d.ts +33 -0
  78. package/dist/trace-ai/eval-set/semantic-match-provider.js +51 -0
  79. package/dist/trace-ai/eval-set/test-runner.d.ts +34 -0
  80. package/dist/trace-ai/eval-set/test-runner.js +153 -0
  81. package/dist/trace-ai/eval-set/types.d.ts +46 -0
  82. package/dist/trace-ai/eval-set/types.js +8 -0
  83. package/dist/trace-ai/exp/bundle-writer.d.ts +10 -0
  84. package/dist/trace-ai/exp/bundle-writer.js +54 -0
  85. package/dist/trace-ai/exp/claude-binary.d.ts +5 -0
  86. package/dist/trace-ai/exp/claude-binary.js +30 -0
  87. package/dist/trace-ai/exp/coordinator.d.ts +45 -0
  88. package/dist/trace-ai/exp/coordinator.js +203 -0
  89. package/dist/trace-ai/exp/eval-runner.d.ts +14 -0
  90. package/dist/trace-ai/exp/eval-runner.js +47 -0
  91. package/dist/trace-ai/exp/exp-store/abort-signal.d.ts +3 -0
  92. package/dist/trace-ai/exp/exp-store/abort-signal.js +27 -0
  93. package/dist/trace-ai/exp/exp-store/candidate-lineage-yaml.d.ts +4 -0
  94. package/dist/trace-ai/exp/exp-store/candidate-lineage-yaml.js +37 -0
  95. package/dist/trace-ai/exp/exp-store/events-jsonl.d.ts +17 -0
  96. package/dist/trace-ai/exp/exp-store/events-jsonl.js +60 -0
  97. package/dist/trace-ai/exp/exp-store/exp-registry.d.ts +6 -0
  98. package/dist/trace-ai/exp/exp-store/exp-registry.js +41 -0
  99. package/dist/trace-ai/exp/exp-store/index.d.ts +46 -0
  100. package/dist/trace-ai/exp/exp-store/index.js +59 -0
  101. package/dist/trace-ai/exp/exp-store/lock.d.ts +3 -0
  102. package/dist/trace-ai/exp/exp-store/lock.js +73 -0
  103. package/dist/trace-ai/exp/exp-store/mission-md.d.ts +3 -0
  104. package/dist/trace-ai/exp/exp-store/mission-md.js +37 -0
  105. package/dist/trace-ai/exp/exp-store/readme-template.d.ts +5 -0
  106. package/dist/trace-ai/exp/exp-store/readme-template.js +25 -0
  107. package/dist/trace-ai/exp/exp-store/round-yaml.d.ts +3 -0
  108. package/dist/trace-ai/exp/exp-store/round-yaml.js +33 -0
  109. package/dist/trace-ai/exp/index.d.ts +8 -0
  110. package/dist/trace-ai/exp/index.js +238 -0
  111. package/dist/trace-ai/exp/info.d.ts +35 -0
  112. package/dist/trace-ai/exp/info.js +120 -0
  113. package/dist/trace-ai/exp/patch/agent-config.d.ts +1 -0
  114. package/dist/trace-ai/exp/patch/agent-config.js +26 -0
  115. package/dist/trace-ai/exp/patch/index.d.ts +2 -0
  116. package/dist/trace-ai/exp/patch/index.js +13 -0
  117. package/dist/trace-ai/exp/patch/skill.d.ts +1 -0
  118. package/dist/trace-ai/exp/patch/skill.js +24 -0
  119. package/dist/trace-ai/exp/providers/synthesizer-client.d.ts +14 -0
  120. package/dist/trace-ai/exp/providers/synthesizer-client.js +39 -0
  121. package/dist/trace-ai/exp/providers/triage-client.d.ts +19 -0
  122. package/dist/trace-ai/exp/providers/triage-client.js +51 -0
  123. package/dist/trace-ai/exp/schemas.d.ts +147 -0
  124. package/dist/trace-ai/exp/schemas.js +50 -0
  125. package/dist/trace-ai/exp/scoring.d.ts +2 -0
  126. package/dist/trace-ai/exp/scoring.js +46 -0
  127. package/dist/trace-ai/scan/aggregator.d.ts +20 -0
  128. package/dist/trace-ai/scan/aggregator.js +26 -0
  129. package/dist/trace-ai/scan/artifacts/paths.d.ts +12 -0
  130. package/dist/trace-ai/scan/artifacts/paths.js +18 -0
  131. package/dist/trace-ai/scan/artifacts/writer.d.ts +67 -0
  132. package/dist/trace-ai/scan/artifacts/writer.js +96 -0
  133. package/dist/trace-ai/scan/batched-rubric.d.ts +55 -0
  134. package/dist/trace-ai/scan/batched-rubric.js +159 -0
  135. package/dist/trace-ai/scan/cross-trace-synthesizer.d.ts +24 -0
  136. package/dist/trace-ai/scan/cross-trace-synthesizer.js +93 -0
  137. package/dist/trace-ai/scan/index.d.ts +31 -0
  138. package/dist/trace-ai/scan/index.js +390 -0
  139. package/dist/trace-ai/scan/prompts/builtin/cross-trace-synthesizer-v1.prompt.md +44 -0
  140. package/dist/trace-ai/scan/prompts/builtin/rubric-judge-batch-v1.prompt.md +44 -0
  141. package/dist/trace-ai/scan/runner.d.ts +25 -0
  142. package/dist/trace-ai/scan/runner.js +42 -0
  143. package/dist/trace-ai/scan/sampler.d.ts +18 -0
  144. package/dist/trace-ai/scan/sampler.js +81 -0
  145. package/dist/trace-ai/scan/scan-summary-markdown.d.ts +2 -0
  146. package/dist/trace-ai/scan/scan-summary-markdown.js +71 -0
  147. package/dist/trace-ai/scan/scan-summary-schema.d.ts +73 -0
  148. package/dist/trace-ai/scan/scan-summary-schema.js +61 -0
  149. package/dist/trace-ai/scan/single-agent-validator.d.ts +23 -0
  150. package/dist/trace-ai/scan/single-agent-validator.js +42 -0
  151. package/dist/trace-ai/scan/traces-list-parser.d.ts +15 -0
  152. package/dist/trace-ai/scan/traces-list-parser.js +46 -0
  153. package/package.json +2 -2
  154. package/dist/trace-core/diagnose/index.d.ts +0 -9
  155. package/dist/trace-core/diagnose/index.js +0 -104
  156. package/dist/trace-core/diagnose/report-assembler.d.ts +0 -12
  157. package/dist/trace-core/diagnose/schemas.js +0 -94
  158. package/dist/trace-core/diagnose/signal-probe.d.ts +0 -5
  159. package/dist/trace-core/diagnose/signal-probe.js +0 -21
  160. /package/dist/{trace-core → trace-ai}/diagnose/builtin-rules/excessive-tool-calls-per-turn.d.ts +0 -0
  161. /package/dist/{trace-core → trace-ai}/diagnose/builtin-rules/excessive-tool-calls-per-turn.js +0 -0
  162. /package/dist/{trace-core → trace-ai}/diagnose/builtin-rules/excessive-tool-calls-per-turn.yaml +0 -0
  163. /package/dist/{trace-core → trace-ai}/diagnose/builtin-rules/llm-response-truncated-no-continue.d.ts +0 -0
  164. /package/dist/{trace-core → trace-ai}/diagnose/builtin-rules/llm-response-truncated-no-continue.js +0 -0
  165. /package/dist/{trace-core → trace-ai}/diagnose/builtin-rules/llm-response-truncated-no-continue.yaml +0 -0
  166. /package/dist/{trace-core → trace-ai}/diagnose/builtin-rules/register.d.ts +0 -0
  167. /package/dist/{trace-core → trace-ai}/diagnose/builtin-rules/register.js +0 -0
  168. /package/dist/{trace-core → trace-ai}/diagnose/builtin-rules/retrieval-empty-no-fallback.d.ts +0 -0
  169. /package/dist/{trace-core → trace-ai}/diagnose/builtin-rules/retrieval-empty-no-fallback.js +0 -0
  170. /package/dist/{trace-core → trace-ai}/diagnose/builtin-rules/retrieval-empty-no-fallback.yaml +0 -0
  171. /package/dist/{trace-core → trace-ai}/diagnose/builtin-rules/tool-error-swallowed.d.ts +0 -0
  172. /package/dist/{trace-core → trace-ai}/diagnose/builtin-rules/tool-error-swallowed.js +0 -0
  173. /package/dist/{trace-core → trace-ai}/diagnose/builtin-rules/tool-error-swallowed.yaml +0 -0
  174. /package/dist/{trace-core → trace-ai}/diagnose/builtin-rules/tool-loop-no-state-change.d.ts +0 -0
  175. /package/dist/{trace-core → trace-ai}/diagnose/builtin-rules/tool-loop-no-state-change.js +0 -0
  176. /package/dist/{trace-core → trace-ai}/diagnose/builtin-rules/tool-loop-no-state-change.yaml +0 -0
  177. /package/dist/{trace-core → trace-ai}/diagnose/predicate-registry.d.ts +0 -0
  178. /package/dist/{trace-core → trace-ai}/diagnose/predicate-registry.js +0 -0
  179. /package/dist/{trace-core → trace-ai}/diagnose/rule-loader.d.ts +0 -0
  180. /package/dist/{trace-core → trace-ai}/diagnose/synthesizer-template.d.ts +0 -0
  181. /package/dist/{trace-core → trace-ai}/diagnose/synthesizer-template.js +0 -0
  182. /package/dist/{trace-core → trace-ai}/diagnose/trace-shaper.d.ts +0 -0
  183. /package/dist/{trace-core → trace-ai}/diagnose/types.js +0 -0
@@ -0,0 +1,192 @@
1
+ // Human-readable markdown view of a trace-diagnose report.
2
+ //
3
+ // The YAML report (see `report-assembler.reportToYamlObject`) is the source of
4
+ // truth; this file is a pure projection. Persisted alongside the yaml when
5
+ // `--format=both`. Markdown was chosen over a stdout pretty-print because
6
+ // reports are commonly pasted into tickets / PRs / wikis where ephemeral
7
+ // terminal output would be lost.
8
+ //
9
+ // Structure (inverted-pyramid: most actionable first):
10
+ // 1. Title + one-line meta
11
+ // 2. Summary — headline (+ primary root cause if any)
12
+ // 3. Fix priority table (omitted when empty)
13
+ // 4. Findings — one section per finding, excerpt as a blockquote
14
+ // 5. Cross-finding links (omitted when empty)
15
+ // 6. How to verify — kweaver CLI commands the reader can paste to
16
+ // independently re-confirm the report's claims against the live trace.
17
+ // Sourced from Report fields + the caller-supplied conversation_id /
18
+ // business_domain (which are not part of the yaml schema — yaml stays
19
+ // CLI-agnostic, markdown is the CLI-aware view).
20
+ // 7. Run — mode / synthesizer / rules applied & skipped (reference)
21
+ export function renderReportMarkdown(r, opts = {}) {
22
+ const lines = [];
23
+ const shortId = r.trace.traceId.length > 16 ? `${r.trace.traceId.slice(0, 16)}…` : r.trace.traceId;
24
+ lines.push(`# Trace Diagnose Report — \`${shortId}\``);
25
+ lines.push("");
26
+ lines.push(`> trace \`${r.trace.traceId}\` · agent \`${r.trace.agentId ?? "—"}\` · tenant \`${r.trace.tenant ?? "—"}\` · diagnosed ${r.run.diagnosedAt} · cli \`${r.run.cliVersion}\``);
27
+ lines.push("");
28
+ // ── Summary ──────────────────────────────────────────────────────────────
29
+ lines.push("## Summary");
30
+ lines.push("");
31
+ lines.push(`**${r.summary.headline}**`);
32
+ lines.push("");
33
+ if (r.summary.primaryRootCause !== null) {
34
+ const rc = r.summary.primaryRootCause;
35
+ const fids = rc.findingIds.map((i) => `#${i}`).join(", ");
36
+ lines.push(`Primary root cause spans findings ${fids} — target for fix: \`${rc.targetForFix}\`.`);
37
+ lines.push("");
38
+ lines.push(`> ${escapeBlockquote(rc.description)}`);
39
+ lines.push("");
40
+ }
41
+ // ── Fix priority ─────────────────────────────────────────────────────────
42
+ if (r.summary.fixPriority.length > 0) {
43
+ lines.push("## Fix priority");
44
+ lines.push("");
45
+ lines.push("| Order | Finding | Rule | Reason |");
46
+ lines.push("|---|---|---|---|");
47
+ r.summary.fixPriority.forEach((p, idx) => {
48
+ const f = r.findings[p.findingId];
49
+ const ruleCell = f ? `\`${f.ruleId}\` [${f.severity}/${f.judgmentKind}]` : `(unknown #${p.findingId})`;
50
+ lines.push(`| ${idx + 1} | #${p.findingId} | ${ruleCell} | ${escapeTableCell(p.reason)} |`);
51
+ });
52
+ lines.push("");
53
+ }
54
+ // ── Findings ─────────────────────────────────────────────────────────────
55
+ lines.push(`## Findings (${r.findings.length})`);
56
+ lines.push("");
57
+ if (r.findings.length === 0) {
58
+ lines.push(`_No findings were emitted by any of the ${r.run.rulesApplied.length} applied rules._`);
59
+ lines.push("");
60
+ }
61
+ else {
62
+ r.findings.forEach((f, idx) => renderFinding(lines, f, idx));
63
+ }
64
+ // ── Cross-finding links ──────────────────────────────────────────────────
65
+ if (r.summary.crossFindingLinks.length > 0) {
66
+ lines.push("## Cross-finding links");
67
+ lines.push("");
68
+ for (const link of r.summary.crossFindingLinks) {
69
+ const ids = link.findingIds.map((i) => `#${i}`).join(" ↔ ");
70
+ lines.push(`- ${ids} — ${link.relation}`);
71
+ }
72
+ lines.push("");
73
+ }
74
+ // ── How to verify ────────────────────────────────────────────────────────
75
+ renderVerificationSection(lines, r, opts);
76
+ // ── Run reference ────────────────────────────────────────────────────────
77
+ lines.push("## Run");
78
+ lines.push("");
79
+ lines.push(`- **mode**: \`${r.run.mode}\` · **synthesizer**: \`${r.run.synthesizerMode}\` · **rules**: ${r.run.rulesApplied.length} applied, ${r.run.rulesSkipped.length} skipped`);
80
+ lines.push(`- **applied**: ${r.run.rulesApplied.map((id) => `\`${id}\``).join(", ")}`);
81
+ if (r.run.rulesSkipped.length > 0) {
82
+ lines.push("- **skipped**:");
83
+ for (const s of r.run.rulesSkipped) {
84
+ lines.push(` - \`${s.ruleId}\` — ${s.reason}`);
85
+ }
86
+ }
87
+ lines.push("");
88
+ return lines.join("\n");
89
+ }
90
+ function renderFinding(lines, f, idx) {
91
+ lines.push(`### #${idx} \`${f.ruleId}\` — [${f.severity}/${f.judgmentKind}]`);
92
+ lines.push("");
93
+ if (f.evidence.excerpt.trim().length > 0) {
94
+ for (const ln of f.evidence.excerpt.trim().split(/\r?\n/)) {
95
+ lines.push(`> ${ln}`);
96
+ }
97
+ lines.push("");
98
+ }
99
+ const meta = [];
100
+ meta.push(`- **symptom**: ${f.symptom}`);
101
+ meta.push(`- **likely cause**: ${f.likelyCause}`);
102
+ meta.push(`- **confidence**: ${f.confidence}`);
103
+ if (f.evidence.spans.length > 0) {
104
+ meta.push(`- **evidence spans**: ${f.evidence.spans.map((s) => `\`${s}\``).join(", ")}`);
105
+ }
106
+ meta.push(`- **suggested fix** → \`${f.suggestedFix.target}\`: ${f.suggestedFix.change}`);
107
+ if (f.verifyWith.suggestedEvalCase.assertions.length > 0) {
108
+ meta.push(`- **verify with**:`);
109
+ for (const a of f.verifyWith.suggestedEvalCase.assertions) {
110
+ meta.push(` - ${a}`);
111
+ }
112
+ }
113
+ for (const m of meta)
114
+ lines.push(m);
115
+ lines.push("");
116
+ }
117
+ /**
118
+ * Render kweaver CLI verification commands so a reader can independently
119
+ * re-confirm the diagnosis against the live trace. Sections:
120
+ * 1. Re-fetch the raw spans (proves the trace data the report was built
121
+ * from still matches what observability returns)
122
+ * 2. Re-diagnose with --no-llm (reproducibility check — same symbolic
123
+ * findings should fire deterministically; rules out claude-side flake)
124
+ * 3. Inspect suspect spans per finding (only when findings.length > 0)
125
+ * 4. Check recurrence across the agent's other conversations
126
+ *
127
+ * The commands intentionally omit auth flags (--token / --base-url) — the
128
+ * reader is expected to have `kweaver auth` already configured or to be
129
+ * working in the same shell session that produced this report.
130
+ */
131
+ function renderVerificationSection(lines, r, opts) {
132
+ const bdFlag = opts.businessDomain ? ` -bd ${opts.businessDomain}` : "";
133
+ const convId = opts.conversationId ?? "<conversation_id>";
134
+ lines.push("## How to verify");
135
+ lines.push("");
136
+ lines.push("Paste these into a shell to independently re-confirm the report against the live trace.");
137
+ lines.push("");
138
+ // 1. Re-fetch raw spans for the trace.
139
+ lines.push("### 1. Re-fetch the raw trace");
140
+ lines.push("");
141
+ lines.push("```bash");
142
+ lines.push(`kweaver call -X POST '/api/agent-observability/v1/traces/_search' \\`);
143
+ lines.push(` -d '{"query":{"term":{"traceId":"${r.trace.traceId}"}}}'${bdFlag} \\`);
144
+ lines.push(` | jq '.hits.hits[]._source | {spanId, name, kind: .attributes."gen_ai.operation.name", status: .status.code}'`);
145
+ lines.push("```");
146
+ lines.push("");
147
+ // 2. Re-run diagnosis deterministically.
148
+ lines.push("### 2. Re-run diagnosis (reproducibility check)");
149
+ lines.push("");
150
+ lines.push("```bash");
151
+ lines.push(`kweaver trace diagnose ${convId} --no-llm --out /tmp/verify.yaml${bdFlag}`);
152
+ lines.push("# then diff against this report's yaml — symbolic findings should match exactly");
153
+ lines.push("```");
154
+ lines.push("");
155
+ // 3. Inspect suspect spans per finding.
156
+ if (r.findings.length > 0) {
157
+ lines.push("### 3. Inspect the suspect spans");
158
+ lines.push("");
159
+ r.findings.forEach((f, idx) => {
160
+ if (f.evidence.spans.length === 0)
161
+ return;
162
+ const spanList = f.evidence.spans.map((s) => `"${s}"`).join(", ");
163
+ lines.push(`Finding #${idx} (\`${f.ruleId}\`):`);
164
+ lines.push("");
165
+ lines.push("```bash");
166
+ lines.push(`kweaver call -X POST '/api/agent-observability/v1/traces/_search' \\`);
167
+ lines.push(` -d '{"query":{"terms":{"spanId":[${spanList}]}}}'${bdFlag} \\`);
168
+ lines.push(` | jq '.hits.hits[]._source.attributes'`);
169
+ lines.push("```");
170
+ lines.push("");
171
+ });
172
+ }
173
+ // 4. Recurrence check.
174
+ if (r.trace.agentId !== null) {
175
+ const sectionNum = r.findings.length > 0 ? 4 : 3;
176
+ lines.push(`### ${sectionNum}. Check whether this pattern recurs for the agent`);
177
+ lines.push("");
178
+ lines.push("```bash");
179
+ lines.push(`kweaver agent sessions ${r.trace.agentId} --limit 20${bdFlag}`);
180
+ lines.push("# sample a few conversation_ids from the list, re-diagnose each, count rule hits");
181
+ lines.push("```");
182
+ lines.push("");
183
+ }
184
+ }
185
+ function escapeTableCell(s) {
186
+ // Pipes and newlines break GFM tables; collapse newlines and escape `|`.
187
+ return s.replace(/\r?\n/g, " ").replace(/\|/g, "\\|");
188
+ }
189
+ function escapeBlockquote(s) {
190
+ // Blockquote-safe; just collapse newlines so the whole description sits in one line.
191
+ return s.replace(/\r?\n+/g, " ").trim();
192
+ }
@@ -3,6 +3,7 @@ import path from "node:path";
3
3
  import yaml from "js-yaml";
4
4
  import { RuleSchema } from "./schemas.js";
5
5
  import { resolvePredicate } from "./predicate-registry.js";
6
+ import { rubricOutputToZod, OutputSchemaConversionError } from "./output-schema-converter.js";
6
7
  export class RuleLoadError extends Error {
7
8
  constructor(message) {
8
9
  super(message);
@@ -37,15 +38,47 @@ async function parseOne(filePath) {
37
38
  throw new RuleLoadError(`schema validation failed for ${filePath}: ${result.error.issues.map((i) => `${i.path.join('.')}: ${i.message}`).join('; ')}`);
38
39
  }
39
40
  const r = result.data;
40
- if (!r.predicate) {
41
- throw new RuleLoadError(`PR-A only supports symbolic rules; ${filePath} has no predicate`);
41
+ let predicateRef = null;
42
+ let rubric = null;
43
+ if (r.predicate) {
44
+ // resolvePredicate throws PredicateNotFoundError; rewrap for uniform caller experience.
45
+ try {
46
+ resolvePredicate(r.predicate);
47
+ }
48
+ catch (e) {
49
+ throw new RuleLoadError(`${filePath}: ${e.message}`);
50
+ }
51
+ predicateRef = r.predicate;
42
52
  }
43
- // resolvePredicate throws PredicateNotFoundError; rewrap for uniform caller experience.
44
- try {
45
- resolvePredicate(r.predicate);
53
+ else if (r.rubric) {
54
+ // Compile output_schema → zod at load time so authors see schema errors
55
+ // up-front via `trace diagnose rules validate <path>`, not at LLM call time.
56
+ let outputZodSchema;
57
+ try {
58
+ outputZodSchema = rubricOutputToZod(r.rubric);
59
+ }
60
+ catch (e) {
61
+ if (e instanceof OutputSchemaConversionError) {
62
+ throw new RuleLoadError(`${filePath}: rubric.output_schema: ${e.message}`);
63
+ }
64
+ throw e;
65
+ }
66
+ rubric = {
67
+ judgeQuestion: r.rubric.judge_question,
68
+ inputs: r.rubric.inputs.map((i) => ({ kind: i.kind, source: i.source })),
69
+ outputSchemaRaw: r.rubric.output_schema,
70
+ outputZodSchema,
71
+ agentBinding: {
72
+ provider: r.rubric.agent_binding.provider,
73
+ promptTemplateRef: r.rubric.agent_binding.prompt_template_ref,
74
+ },
75
+ gatesOn: r.rubric.gates_on,
76
+ };
46
77
  }
47
- catch (e) {
48
- throw new RuleLoadError(`${filePath}: ${e.message}`);
78
+ else {
79
+ // RuleSchema's XOR refinement should have already caught this; keep an
80
+ // explicit branch so the failure mode is obvious if schemas drift.
81
+ throw new RuleLoadError(`${filePath}: rule has neither predicate nor rubric`);
49
82
  }
50
83
  return {
51
84
  schemaVersion: r.schema_version,
@@ -55,7 +88,8 @@ async function parseOne(filePath) {
55
88
  taxonomy: { signalsAxis: r.taxonomy.signals_axis, msClass: r.taxonomy.ms_class },
56
89
  suggestedFix: { target: r.suggested_fix.target, changeTemplate: r.suggested_fix.change_template },
57
90
  verifyWith: { assertionTemplates: r.verify_with.assertion_templates },
58
- predicateRef: r.predicate,
91
+ predicateRef,
92
+ rubric,
59
93
  params: r.params,
60
94
  sourcePath: filePath,
61
95
  };
@@ -1,4 +1,38 @@
1
1
  import { z } from "zod";
2
+ /**
3
+ * Rubric input source descriptor. The supported source prefixes are
4
+ * resolved by `diagnose/agent-binding.ts` against the in-memory TraceTree:
5
+ *
6
+ * - `extract_from_root_attr:<dot.path>` → root span attribute by name
7
+ * - `filter_by_kind:[kind1,kind2,...]` → ordered span subset by kind
8
+ * - `literal:<json>` → constant blob (debug / fixtures)
9
+ *
10
+ * Authors describe **which slice of the trace** the agent needs as context;
11
+ * the binding does the actual extraction so rule YAML stays declarative.
12
+ */
13
+ declare const RubricInputSchema: z.ZodObject<{
14
+ kind: z.ZodString;
15
+ source: z.ZodString;
16
+ }, z.core.$strip>;
17
+ declare const RubricSchema: z.ZodObject<{
18
+ judge_question: z.ZodString;
19
+ inputs: z.ZodDefault<z.ZodArray<z.ZodObject<{
20
+ kind: z.ZodString;
21
+ source: z.ZodString;
22
+ }, z.core.$strip>>>;
23
+ output_schema: z.ZodObject<{
24
+ type: z.ZodLiteral<"object">;
25
+ required: z.ZodDefault<z.ZodArray<z.ZodString>>;
26
+ properties: z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodString, z.ZodUnknown>>;
27
+ }, z.core.$strip>;
28
+ agent_binding: z.ZodObject<{
29
+ provider: z.ZodString;
30
+ prompt_template_ref: z.ZodString;
31
+ }, z.core.$strip>;
32
+ gates_on: z.ZodOptional<z.ZodArray<z.ZodString>>;
33
+ }, z.core.$strip>;
34
+ export type RubricYaml = z.infer<typeof RubricSchema>;
35
+ export type RubricInputYaml = z.infer<typeof RubricInputSchema>;
2
36
  export declare const RuleSchema: z.ZodObject<{
3
37
  schema_version: z.ZodLiteral<"diagnosis-rule/v1">;
4
38
  id: z.ZodString;
@@ -31,7 +65,23 @@ export declare const RuleSchema: z.ZodObject<{
31
65
  assertion_templates: z.ZodDefault<z.ZodArray<z.ZodString>>;
32
66
  }, z.core.$strip>;
33
67
  predicate: z.ZodOptional<z.ZodString>;
34
- rubric: z.ZodOptional<z.ZodUnknown>;
68
+ rubric: z.ZodOptional<z.ZodObject<{
69
+ judge_question: z.ZodString;
70
+ inputs: z.ZodDefault<z.ZodArray<z.ZodObject<{
71
+ kind: z.ZodString;
72
+ source: z.ZodString;
73
+ }, z.core.$strip>>>;
74
+ output_schema: z.ZodObject<{
75
+ type: z.ZodLiteral<"object">;
76
+ required: z.ZodDefault<z.ZodArray<z.ZodString>>;
77
+ properties: z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodString, z.ZodUnknown>>;
78
+ }, z.core.$strip>;
79
+ agent_binding: z.ZodObject<{
80
+ provider: z.ZodString;
81
+ prompt_template_ref: z.ZodString;
82
+ }, z.core.$strip>;
83
+ gates_on: z.ZodOptional<z.ZodArray<z.ZodString>>;
84
+ }, z.core.$strip>>;
35
85
  params: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
36
86
  }, z.core.$strip>;
37
87
  export type RuleYaml = z.infer<typeof RuleSchema>;
@@ -80,6 +130,7 @@ export declare const ReportSchema: z.ZodObject<{
80
130
  rule_id: z.ZodString;
81
131
  judgment_kind: z.ZodEnum<{
82
132
  symbolic: "symbolic";
133
+ rubric: "rubric";
83
134
  }>;
84
135
  severity: z.ZodEnum<{
85
136
  low: "low";
@@ -96,7 +147,11 @@ export declare const ReportSchema: z.ZodObject<{
96
147
  target: z.ZodString;
97
148
  change: z.ZodString;
98
149
  }, z.core.$strip>;
99
- confidence: z.ZodLiteral<"low">;
150
+ confidence: z.ZodEnum<{
151
+ low: "low";
152
+ medium: "medium";
153
+ high: "high";
154
+ }>;
100
155
  verify_with: z.ZodObject<{
101
156
  suggested_eval_case: z.ZodObject<{
102
157
  query_id: z.ZodNullable<z.ZodString>;
@@ -107,3 +162,23 @@ export declare const ReportSchema: z.ZodObject<{
107
162
  }, z.core.$strip>>;
108
163
  }, z.core.$strip>;
109
164
  export type ReportYaml = z.infer<typeof ReportSchema>;
165
+ /** The Summary section in isolation — exported so the agent synthesizer
166
+ * can validate its LLM output against the same shape the report uses. */
167
+ export declare const SummaryOutputSchema: z.ZodObject<{
168
+ headline: z.ZodString;
169
+ primary_root_cause: z.ZodNullable<z.ZodObject<{
170
+ finding_ids: z.ZodArray<z.ZodNumber>;
171
+ description: z.ZodString;
172
+ target_for_fix: z.ZodString;
173
+ }, z.core.$strip>>;
174
+ fix_priority: z.ZodArray<z.ZodObject<{
175
+ finding_id: z.ZodNumber;
176
+ reason: z.ZodString;
177
+ }, z.core.$strip>>;
178
+ cross_finding_links: z.ZodArray<z.ZodObject<{
179
+ finding_ids: z.ZodArray<z.ZodNumber>;
180
+ relation: z.ZodString;
181
+ }, z.core.$strip>>;
182
+ }, z.core.$strip>;
183
+ export type SummaryOutput = z.infer<typeof SummaryOutputSchema>;
184
+ export {};
@@ -0,0 +1,154 @@
1
+ import { z } from "zod";
2
+ const TaxonomySchema = z.object({
3
+ signals_axis: z.enum(["interaction", "execution", "environment"]),
4
+ ms_class: z.enum([
5
+ "retry_loop",
6
+ "tool_misuse",
7
+ "context_loss",
8
+ "goal_drift",
9
+ "cascading_error",
10
+ "silent_quality_degradation",
11
+ ]),
12
+ });
13
+ const SuggestedFixSchema = z.object({
14
+ target: z.string().min(1),
15
+ change_template: z.string().min(1),
16
+ });
17
+ const VerifyWithSchema = z.object({
18
+ assertion_templates: z.array(z.string()).default([]),
19
+ });
20
+ /**
21
+ * Rubric input source descriptor. The supported source prefixes are
22
+ * resolved by `diagnose/agent-binding.ts` against the in-memory TraceTree:
23
+ *
24
+ * - `extract_from_root_attr:<dot.path>` → root span attribute by name
25
+ * - `filter_by_kind:[kind1,kind2,...]` → ordered span subset by kind
26
+ * - `literal:<json>` → constant blob (debug / fixtures)
27
+ *
28
+ * Authors describe **which slice of the trace** the agent needs as context;
29
+ * the binding does the actual extraction so rule YAML stays declarative.
30
+ */
31
+ const RubricInputSchema = z.object({
32
+ kind: z.string().min(1),
33
+ source: z.string().min(1),
34
+ });
35
+ /**
36
+ * Minimal JSON-Schema-ish shape we accept for rubric output_schema. We
37
+ * convert to a zod schema at load time (see `output-schema-converter.ts`);
38
+ * keeping this loose here lets authors paste literal JSON Schema without
39
+ * us re-implementing the whole spec — just the subset we need (object
40
+ * with required[] + properties{type,enum,items}).
41
+ */
42
+ const RubricOutputSchemaSchema = z.object({
43
+ type: z.literal("object"),
44
+ required: z.array(z.string()).default([]),
45
+ properties: z.record(z.string(), z.record(z.string(), z.unknown())),
46
+ });
47
+ const AgentBindingSchema = z.object({
48
+ provider: z.string().min(1),
49
+ prompt_template_ref: z.string().regex(/^builtin:[a-zA-Z0-9_-]+$/),
50
+ });
51
+ const RubricSchema = z.object({
52
+ judge_question: z.string().min(1),
53
+ inputs: z.array(RubricInputSchema).default([]),
54
+ output_schema: RubricOutputSchemaSchema,
55
+ agent_binding: AgentBindingSchema,
56
+ /**
57
+ * Optional symbolic rule_ids that act as gate for this rubric in batch mode.
58
+ * Empty/missing → rubric runs on all traces (PR-B fallback). In single-trace
59
+ * mode this field is ignored; rubric always runs.
60
+ */
61
+ gates_on: z.array(z.string()).optional(),
62
+ });
63
+ /**
64
+ * The convergence contract between Stage-1 (symbolic) and Stage-2 (rubric):
65
+ * every rubric verdict MUST emit `first_violating_step_id` so cross-finding
66
+ * links can correlate rubric findings with the spans symbolic rules cite.
67
+ *
68
+ * Enforced as a YAML-load-time check rather than at runtime so authors
69
+ * see the violation in `trace diagnose rules validate <path>`.
70
+ */
71
+ const FIRST_VIOLATING_STEP_ID = "first_violating_step_id";
72
+ export const RuleSchema = z
73
+ .object({
74
+ schema_version: z.literal("diagnosis-rule/v1"),
75
+ id: z.string().regex(/^[a-z][a-z0-9_]*$/),
76
+ severity: z.enum(["low", "medium", "high"]),
77
+ symptom: z.string().min(1),
78
+ taxonomy: TaxonomySchema,
79
+ suggested_fix: SuggestedFixSchema,
80
+ verify_with: VerifyWithSchema,
81
+ predicate: z.string().regex(/^builtin:[a-z][a-z0-9_]*$/).optional(),
82
+ rubric: RubricSchema.optional(),
83
+ params: z.record(z.string(), z.unknown()).default({}),
84
+ })
85
+ .refine((r) => Boolean(r.predicate) !== Boolean(r.rubric), { message: "exactly one of `predicate` or `rubric` must be present" })
86
+ .refine((r) => !r.rubric || r.rubric.output_schema.required.includes(FIRST_VIOLATING_STEP_ID), {
87
+ message: `rubric.output_schema.required must include '${FIRST_VIOLATING_STEP_ID}' (Stage-1↔Stage-2 convergence contract)`,
88
+ path: ["rubric", "output_schema", "required"],
89
+ });
90
+ const FindingSchema = z.object({
91
+ rule_id: z.string(),
92
+ judgment_kind: z.enum(["symbolic", "rubric"]),
93
+ severity: z.enum(["low", "medium", "high"]),
94
+ symptom: z.string(),
95
+ likely_cause: z.string(),
96
+ evidence: z.object({
97
+ spans: z.array(z.string()),
98
+ excerpt: z.string(),
99
+ }),
100
+ suggested_fix: z.object({
101
+ target: z.string(),
102
+ change: z.string(),
103
+ }),
104
+ // Symbolic findings always emit `low` (no semantic basis for higher).
105
+ // Rubric agent supplies its own confidence; rule-loader propagates the
106
+ // value the agent returned in the rubric output. Accept the union.
107
+ confidence: z.enum(["low", "medium", "high"]),
108
+ verify_with: z.object({
109
+ suggested_eval_case: z.object({
110
+ query_id: z.string().nullable(),
111
+ query: z.string().nullable(),
112
+ assertions: z.array(z.string()),
113
+ }),
114
+ }),
115
+ });
116
+ const SummarySchema = z.object({
117
+ headline: z.string().max(160),
118
+ primary_root_cause: z
119
+ .object({
120
+ finding_ids: z.array(z.number().int().nonnegative()).min(1),
121
+ description: z.string(),
122
+ target_for_fix: z.string(),
123
+ })
124
+ .nullable(),
125
+ fix_priority: z.array(z.object({
126
+ finding_id: z.number().int().nonnegative(),
127
+ reason: z.string(),
128
+ })),
129
+ cross_finding_links: z.array(z.object({
130
+ finding_ids: z.array(z.number().int().nonnegative()).min(2),
131
+ relation: z.string(),
132
+ })),
133
+ });
134
+ export const ReportSchema = z.object({
135
+ schema_version: z.literal("trace-diagnose-report/v1"),
136
+ trace: z.object({
137
+ trace_id: z.string(),
138
+ agent_id: z.string().nullable(),
139
+ tenant: z.string().nullable(),
140
+ }),
141
+ run: z.object({
142
+ diagnosed_at: z.string(),
143
+ cli_version: z.string(),
144
+ mode: z.enum(["symbolic-only", "rubric-only", "hybrid"]),
145
+ rules_applied: z.array(z.string()),
146
+ rules_skipped: z.array(z.object({ rule_id: z.string(), reason: z.string() })),
147
+ synthesizer_mode: z.enum(["template", "agent"]),
148
+ }),
149
+ summary: SummarySchema,
150
+ findings: z.array(FindingSchema),
151
+ });
152
+ /** The Summary section in isolation — exported so the agent synthesizer
153
+ * can validate its LLM output against the same shape the report uses. */
154
+ export const SummaryOutputSchema = SummarySchema;
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Stage-1 (symbolic) runner. Rubric rules are handled separately in
3
+ * `agent-binding.ts` and merged into the findings list by `index.ts`.
4
+ *
5
+ * Rationale for keeping the split here: symbolic predicates are cheap,
6
+ * deterministic, sync; rubric judgments are slow, non-deterministic,
7
+ * async. Running them in one loop would entangle backpressure,
8
+ * timeout, and retry concerns that only apply to one of the two paths.
9
+ */
10
+ import type { Hit, Rule, TraceTree } from "./types.js";
11
+ export declare class RuleProbeError extends Error {
12
+ constructor(ruleId: string, cause: Error);
13
+ }
14
+ export declare function runRules(rules: Rule[], tree: TraceTree): Promise<Map<string, Hit[]>>;
15
+ /** Helpers that split a rule list by which stage owns them. */
16
+ export declare function symbolicRules(rules: Rule[]): Rule[];
17
+ export declare function rubricRules(rules: Rule[]): Rule[];
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Stage-1 (symbolic) runner. Rubric rules are handled separately in
3
+ * `agent-binding.ts` and merged into the findings list by `index.ts`.
4
+ *
5
+ * Rationale for keeping the split here: symbolic predicates are cheap,
6
+ * deterministic, sync; rubric judgments are slow, non-deterministic,
7
+ * async. Running them in one loop would entangle backpressure,
8
+ * timeout, and retry concerns that only apply to one of the two paths.
9
+ */
10
+ import { resolvePredicate } from "./predicate-registry.js";
11
+ export class RuleProbeError extends Error {
12
+ constructor(ruleId, cause) {
13
+ super(`predicate failed for rule '${ruleId}': ${cause.message}`);
14
+ this.name = "RuleProbeError";
15
+ }
16
+ }
17
+ export async function runRules(rules, tree) {
18
+ const out = new Map();
19
+ for (const rule of rules) {
20
+ if (!rule.predicateRef)
21
+ continue; // rubric rule — handled by agent-binding
22
+ const fn = resolvePredicate(rule.predicateRef);
23
+ try {
24
+ const hits = fn(tree, rule.params);
25
+ out.set(rule.id, hits);
26
+ }
27
+ catch (e) {
28
+ throw new RuleProbeError(rule.id, e);
29
+ }
30
+ }
31
+ return out;
32
+ }
33
+ /** Helpers that split a rule list by which stage owns them. */
34
+ export function symbolicRules(rules) {
35
+ return rules.filter((r) => r.predicateRef !== null);
36
+ }
37
+ export function rubricRules(rules) {
38
+ return rules.filter((r) => r.rubric !== null);
39
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Stage-3 — agent-driven within-trace synthesizer.
3
+ *
4
+ * Takes the N findings produced by Stages 1+2 and asks the LLM to compose
5
+ * a `Summary` (headline + root cause + ordered fix priority + cross-finding
6
+ * links). Falls back to the deterministic `templateSynthesize` if:
7
+ * - findings.length === 0 (no narrative needed)
8
+ * - no provider registered / provider unavailable
9
+ * - the agent invocation fails for any reason (we still want a usable
10
+ * report even when the LLM judge times out)
11
+ *
12
+ * The agent path remains a *narrative* layer — symbolic and rubric findings
13
+ * are already in hand; the synthesizer doesn't fabricate new findings, only
14
+ * organizes the ones it was given. This keeps the contract small and the
15
+ * failure modes containable.
16
+ */
17
+ import type { Finding, Summary } from "./types.js";
18
+ import type { AgentProvider } from "../../agent-providers/types.js";
19
+ import { PromptTemplateRegistry, type AgentOutputLang } from "../../agent-providers/prompt-template.js";
20
+ import type { ArtifactWriter } from "../scan/artifacts/writer.js";
21
+ export interface AgentSynthesizeOpts {
22
+ findings: Finding[];
23
+ traceId: string;
24
+ agentId: string | null;
25
+ provider: AgentProvider | null;
26
+ promptRegistry: PromptTemplateRegistry;
27
+ promptRef?: string;
28
+ timeoutMs?: number;
29
+ /** Output locale for synthesizer prose. Default 'en'. */
30
+ lang?: AgentOutputLang;
31
+ /** When provided, writes Stage-3 prompt/response artifacts. */
32
+ artifacts?: ArtifactWriter;
33
+ }
34
+ export interface AgentSynthesizeResult {
35
+ summary: Summary;
36
+ mode: "agent" | "template";
37
+ /** Reason set when mode === 'template' under a non-default branch. */
38
+ fallbackReason?: string;
39
+ }
40
+ export declare function agentSynthesize(opts: AgentSynthesizeOpts): Promise<AgentSynthesizeResult>;