@aaac/observability 0.1.14 → 0.1.15
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/{chunk-J2F5GEMO.js → chunk-3DXZNA3E.js} +1011 -13
- package/dist/chunk-3DXZNA3E.js.map +1 -0
- package/dist/chunk-EKFRH7PX.js +266 -0
- package/dist/chunk-EKFRH7PX.js.map +1 -0
- package/dist/cli/global-record.d.ts +53 -0
- package/dist/cli/global-record.js +239 -0
- package/dist/cli/global-record.js.map +1 -0
- package/dist/cli/index.js +330 -210
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +385 -6
- package/dist/index.js +43 -3
- package/package.json +3 -2
- package/dist/chunk-J2F5GEMO.js.map +0 -1
|
@@ -154,6 +154,112 @@ function extractString(attrs, key) {
|
|
|
154
154
|
return typeof v === "string" && v.length > 0 ? v : void 0;
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
+
// src/normalize/vendor-otel-mapper.ts
|
|
158
|
+
var VENDOR_OTEL_SOURCE = "claude-code-otel";
|
|
159
|
+
var VENDOR_OTEL_DEDUP_PREFIX = "vendor-otel";
|
|
160
|
+
function convertAnyValue(value) {
|
|
161
|
+
if (value === void 0 || value === null) return void 0;
|
|
162
|
+
if (typeof value.stringValue === "string") return value.stringValue;
|
|
163
|
+
if (typeof value.boolValue === "boolean") return value.boolValue;
|
|
164
|
+
if (value.intValue !== void 0) {
|
|
165
|
+
const n = typeof value.intValue === "string" ? Number(value.intValue) : value.intValue;
|
|
166
|
+
return Number.isFinite(n) ? n : String(value.intValue);
|
|
167
|
+
}
|
|
168
|
+
if (typeof value.doubleValue === "number") return value.doubleValue;
|
|
169
|
+
if (value.arrayValue !== void 0) return JSON.stringify(value.arrayValue);
|
|
170
|
+
if (value.kvlistValue !== void 0) return JSON.stringify(value.kvlistValue);
|
|
171
|
+
return void 0;
|
|
172
|
+
}
|
|
173
|
+
function convertAttributes(attrs) {
|
|
174
|
+
const out = {};
|
|
175
|
+
if (!Array.isArray(attrs)) return out;
|
|
176
|
+
for (const kv of attrs) {
|
|
177
|
+
if (!kv || typeof kv.key !== "string" || kv.key.length === 0) continue;
|
|
178
|
+
const v = convertAnyValue(kv.value);
|
|
179
|
+
if (v !== void 0) out[kv.key] = v;
|
|
180
|
+
}
|
|
181
|
+
return out;
|
|
182
|
+
}
|
|
183
|
+
var SESSION_ID_KEYS = [
|
|
184
|
+
"session.id",
|
|
185
|
+
"session_id",
|
|
186
|
+
"agent.session_id",
|
|
187
|
+
"claude.session_id",
|
|
188
|
+
"conversation_id",
|
|
189
|
+
"conversation.id"
|
|
190
|
+
];
|
|
191
|
+
function firstString(attrs, keys2) {
|
|
192
|
+
for (const k of keys2) {
|
|
193
|
+
const v = attrs[k];
|
|
194
|
+
if (typeof v === "string" && v.length > 0) return v;
|
|
195
|
+
}
|
|
196
|
+
return void 0;
|
|
197
|
+
}
|
|
198
|
+
function toBigIntNano(value) {
|
|
199
|
+
if (value === void 0) return void 0;
|
|
200
|
+
try {
|
|
201
|
+
if (typeof value === "number") {
|
|
202
|
+
return Number.isFinite(value) ? BigInt(Math.trunc(value)) : void 0;
|
|
203
|
+
}
|
|
204
|
+
const trimmed = value.trim();
|
|
205
|
+
if (trimmed === "" || !/^\d+$/.test(trimmed)) return void 0;
|
|
206
|
+
return BigInt(trimmed);
|
|
207
|
+
} catch {
|
|
208
|
+
return void 0;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
function mapVendorOtelExport(doc, options = {}) {
|
|
212
|
+
const source = options.source ?? VENDOR_OTEL_SOURCE;
|
|
213
|
+
const events = [];
|
|
214
|
+
let skipped = 0;
|
|
215
|
+
const resourceSpans = doc?.resourceSpans;
|
|
216
|
+
if (!Array.isArray(resourceSpans)) return { events, skipped };
|
|
217
|
+
for (const rs of resourceSpans) {
|
|
218
|
+
const resourceAttrs = convertAttributes(rs?.resource?.attributes);
|
|
219
|
+
const projectId = firstString(resourceAttrs, ["project.id", "service.name", "service.namespace"]) ?? void 0;
|
|
220
|
+
for (const ss of rs?.scopeSpans ?? []) {
|
|
221
|
+
for (const span of ss?.spans ?? []) {
|
|
222
|
+
const traceId = typeof span?.traceId === "string" ? span.traceId : "";
|
|
223
|
+
const spanId = typeof span?.spanId === "string" ? span.spanId : "";
|
|
224
|
+
if (traceId === "" || spanId === "") {
|
|
225
|
+
skipped += 1;
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
const spanAttrs = convertAttributes(span.attributes);
|
|
229
|
+
const attributes = { ...resourceAttrs, ...spanAttrs };
|
|
230
|
+
const start = toBigIntNano(span.startTimeUnixNano);
|
|
231
|
+
const end = toBigIntNano(span.endTimeUnixNano);
|
|
232
|
+
if (start !== void 0 && end !== void 0 && end >= start) {
|
|
233
|
+
attributes["vendor.duration_ns"] = Number(end - start);
|
|
234
|
+
}
|
|
235
|
+
if (projectId !== void 0 && attributes["project.id"] === void 0) {
|
|
236
|
+
attributes["project.id"] = projectId;
|
|
237
|
+
}
|
|
238
|
+
const sessionId = firstString(attributes, SESSION_ID_KEYS);
|
|
239
|
+
if (sessionId !== void 0 && attributes["agent.session_id"] === void 0) {
|
|
240
|
+
attributes["agent.session_id"] = sessionId;
|
|
241
|
+
}
|
|
242
|
+
events.push({
|
|
243
|
+
id: generateId(),
|
|
244
|
+
timeUnixNano: start ?? 0n,
|
|
245
|
+
source,
|
|
246
|
+
eventType: typeof span.name === "string" && span.name.length > 0 ? span.name : "vendor.otel",
|
|
247
|
+
lifecycle: "instant",
|
|
248
|
+
traceId,
|
|
249
|
+
spanId,
|
|
250
|
+
...typeof span.parentSpanId === "string" && span.parentSpanId.length > 0 ? { parentSpanId: span.parentSpanId } : {},
|
|
251
|
+
...sessionId !== void 0 ? { sessionId } : {},
|
|
252
|
+
attributes,
|
|
253
|
+
links: [],
|
|
254
|
+
// Deterministic identity → idempotent re-ingestion (§9.2).
|
|
255
|
+
dedupKey: `${VENDOR_OTEL_DEDUP_PREFIX}:${traceId}:${spanId}`
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
return { events, skipped };
|
|
261
|
+
}
|
|
262
|
+
|
|
157
263
|
// src/correlate/cache-store.ts
|
|
158
264
|
var MemoryCacheStore = class {
|
|
159
265
|
map = /* @__PURE__ */ new Map();
|
|
@@ -588,6 +694,143 @@ function createPromotionRule() {
|
|
|
588
694
|
};
|
|
589
695
|
}
|
|
590
696
|
|
|
697
|
+
// src/enrich/rules/worktree.ts
|
|
698
|
+
var WORKTREE_WINDOW_MS = 60 * 60 * 1e3;
|
|
699
|
+
function worktreeRepoKey(repoRoot) {
|
|
700
|
+
return `worktree:repo:${repoRoot}`;
|
|
701
|
+
}
|
|
702
|
+
function nanoToMs(timeUnixNano) {
|
|
703
|
+
return Number(timeUnixNano / 1000000n);
|
|
704
|
+
}
|
|
705
|
+
function parseCommittedFiles(raw) {
|
|
706
|
+
if (typeof raw !== "string") return void 0;
|
|
707
|
+
try {
|
|
708
|
+
const parsed = JSON.parse(raw);
|
|
709
|
+
if (!Array.isArray(parsed)) return void 0;
|
|
710
|
+
return parsed.filter((x) => typeof x === "string");
|
|
711
|
+
} catch {
|
|
712
|
+
return void 0;
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
function createWorktreeRule(windowMs = WORKTREE_WINDOW_MS) {
|
|
716
|
+
return function worktreeRule(event, ctx) {
|
|
717
|
+
if (event.eventType === "promotion.worktree" && event.lifecycle === "instant") {
|
|
718
|
+
const repoRoot2 = event.attributes["repo.root"];
|
|
719
|
+
if (typeof repoRoot2 !== "string" || repoRoot2.length === 0) return;
|
|
720
|
+
const key = worktreeRepoKey(repoRoot2);
|
|
721
|
+
const ref = {
|
|
722
|
+
spanId: event.spanId,
|
|
723
|
+
traceId: event.traceId,
|
|
724
|
+
sessionId: event.sessionId,
|
|
725
|
+
timeMs: nanoToMs(event.timeUnixNano)
|
|
726
|
+
};
|
|
727
|
+
const existing = ctx.api.cacheGet(key);
|
|
728
|
+
if (!existing || ref.timeMs >= existing.timeMs) {
|
|
729
|
+
ctx.api.cacheSet(key, ref);
|
|
730
|
+
ctx.api.logDebug(
|
|
731
|
+
`R8: cached promotion.worktree span=${event.spanId} repo.root=${repoRoot2}`
|
|
732
|
+
);
|
|
733
|
+
}
|
|
734
|
+
return;
|
|
735
|
+
}
|
|
736
|
+
if (event.eventType !== "promotion.commit" || event.lifecycle !== "close") {
|
|
737
|
+
return;
|
|
738
|
+
}
|
|
739
|
+
const repoRoot = event.attributes["repo.root"];
|
|
740
|
+
if (typeof repoRoot !== "string" || repoRoot.length === 0) {
|
|
741
|
+
ctx.api.logDebug("R8: promotion.commit has no repo.root \u2014 skip");
|
|
742
|
+
return;
|
|
743
|
+
}
|
|
744
|
+
const wt = ctx.api.cacheGet(worktreeRepoKey(repoRoot));
|
|
745
|
+
if (!wt) {
|
|
746
|
+
ctx.api.logDebug(`R8: no cached worktree for repo.root=${repoRoot} \u2014 skip`);
|
|
747
|
+
return;
|
|
748
|
+
}
|
|
749
|
+
const commitMs = nanoToMs(event.timeUnixNano);
|
|
750
|
+
if (Math.abs(commitMs - wt.timeMs) > windowMs) {
|
|
751
|
+
ctx.api.logDebug(
|
|
752
|
+
`R8: worktree snapshot stale (\u0394=${Math.abs(commitMs - wt.timeMs)}ms) \u2014 skip`
|
|
753
|
+
);
|
|
754
|
+
return;
|
|
755
|
+
}
|
|
756
|
+
const alreadyLinked = event.links.some(
|
|
757
|
+
(l) => l.linkType === "materializes_as_commit" && l.targetSpanId === wt.spanId
|
|
758
|
+
);
|
|
759
|
+
if (alreadyLinked) return;
|
|
760
|
+
let confidence = "provisional";
|
|
761
|
+
const committedFiles = parseCommittedFiles(event.attributes["committed_files"]);
|
|
762
|
+
if (committedFiles && committedFiles.length > 0 && wt.sessionId) {
|
|
763
|
+
const committedSet = new Set(committedFiles);
|
|
764
|
+
const taskSpans = ctx.api.cacheGet(allTaskSpansKey(wt.sessionId)) ?? [];
|
|
765
|
+
const overlap = taskSpans.some((taskSpanId) => {
|
|
766
|
+
const files = ctx.api.cacheGet(taskFilesKey(wt.sessionId, taskSpanId)) ?? [];
|
|
767
|
+
return files.some((f) => committedSet.has(f));
|
|
768
|
+
});
|
|
769
|
+
if (overlap) confidence = "probable";
|
|
770
|
+
}
|
|
771
|
+
ctx.api.addLink({
|
|
772
|
+
linkType: "materializes_as_commit",
|
|
773
|
+
targetSpanId: wt.spanId,
|
|
774
|
+
targetTraceId: wt.traceId !== event.traceId ? wt.traceId : void 0,
|
|
775
|
+
attributes: { confidence }
|
|
776
|
+
});
|
|
777
|
+
ctx.api.logDebug(
|
|
778
|
+
`R8: linked promotion.commit \u2192 worktree=${wt.spanId} confidence=${confidence}`
|
|
779
|
+
);
|
|
780
|
+
};
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
// src/enrich/rules/issue-anchor.ts
|
|
784
|
+
function issueIdCacheKey(sessionId) {
|
|
785
|
+
return `session:${sessionId}:issue_id`;
|
|
786
|
+
}
|
|
787
|
+
function issueTypeCacheKey(sessionId) {
|
|
788
|
+
return `session:${sessionId}:issue_type`;
|
|
789
|
+
}
|
|
790
|
+
function createIssueAnchorRule() {
|
|
791
|
+
return function issueAnchorRule(event, ctx) {
|
|
792
|
+
const sid = event.sessionId;
|
|
793
|
+
if (!sid) return;
|
|
794
|
+
const attrIssueId = event.attributes["issue.id"];
|
|
795
|
+
const attrIssueType = event.attributes["issue.type"];
|
|
796
|
+
if (attrIssueId !== void 0 && attrIssueId !== "") {
|
|
797
|
+
const cached = ctx.api.cacheGet(issueIdCacheKey(sid));
|
|
798
|
+
if (cached === void 0) {
|
|
799
|
+
ctx.api.cacheSet(issueIdCacheKey(sid), String(attrIssueId));
|
|
800
|
+
ctx.api.logDebug(
|
|
801
|
+
`R-ISSUE: cached issue.id=${attrIssueId} for session=${sid}`
|
|
802
|
+
);
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
if (attrIssueType !== void 0 && attrIssueType !== "") {
|
|
806
|
+
const cached = ctx.api.cacheGet(issueTypeCacheKey(sid));
|
|
807
|
+
if (cached === void 0) {
|
|
808
|
+
ctx.api.cacheSet(issueTypeCacheKey(sid), String(attrIssueType));
|
|
809
|
+
ctx.api.logDebug(
|
|
810
|
+
`R-ISSUE: cached issue.type=${attrIssueType} for session=${sid}`
|
|
811
|
+
);
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
if (attrIssueId === void 0 || attrIssueId === "") {
|
|
815
|
+
const cachedId = ctx.api.cacheGet(issueIdCacheKey(sid));
|
|
816
|
+
if (cachedId !== void 0) {
|
|
817
|
+
event.attributes["issue.id"] = cachedId;
|
|
818
|
+
ctx.api.logDebug(
|
|
819
|
+
`R-ISSUE: propagated issue.id=${cachedId} to span=${event.spanId}`
|
|
820
|
+
);
|
|
821
|
+
} else {
|
|
822
|
+
event.attributes["issue.id_missing"] = true;
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
if ((attrIssueType === void 0 || attrIssueType === "") && event.attributes["issue.type"] === void 0) {
|
|
826
|
+
const cachedType = ctx.api.cacheGet(issueTypeCacheKey(sid));
|
|
827
|
+
if (cachedType !== void 0) {
|
|
828
|
+
event.attributes["issue.type"] = cachedType;
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
};
|
|
832
|
+
}
|
|
833
|
+
|
|
591
834
|
// src/enrich/enricher.ts
|
|
592
835
|
var Enricher = class {
|
|
593
836
|
correlator;
|
|
@@ -649,17 +892,63 @@ var Enricher = class {
|
|
|
649
892
|
};
|
|
650
893
|
function createDefaultRules(artifactPatterns = []) {
|
|
651
894
|
return [
|
|
895
|
+
createIssueAnchorRule(),
|
|
896
|
+
// R-ISSUE (#178 T-E1)
|
|
652
897
|
createArtifactRule(artifactPatterns),
|
|
653
898
|
// R7
|
|
654
899
|
createProcessRule(),
|
|
655
900
|
// R3/R4
|
|
656
901
|
createCrossAxisRule(),
|
|
657
902
|
// R5
|
|
658
|
-
createPromotionRule()
|
|
903
|
+
createPromotionRule(),
|
|
659
904
|
// R1/R2
|
|
905
|
+
createWorktreeRule()
|
|
906
|
+
// R8 (#175)
|
|
660
907
|
];
|
|
661
908
|
}
|
|
662
909
|
|
|
910
|
+
// src/cli/worktree.ts
|
|
911
|
+
import { execFileSync } from "child_process";
|
|
912
|
+
function defaultGitRunner(args) {
|
|
913
|
+
try {
|
|
914
|
+
const out = execFileSync("git", args, {
|
|
915
|
+
encoding: "utf8",
|
|
916
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
917
|
+
timeout: 2e3
|
|
918
|
+
});
|
|
919
|
+
return out;
|
|
920
|
+
} catch {
|
|
921
|
+
return void 0;
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
function captureWorktree(cwd, git = defaultGitRunner) {
|
|
925
|
+
const repoRootRaw = git(["-C", cwd, "rev-parse", "--show-toplevel"]);
|
|
926
|
+
const repoRoot = repoRootRaw?.trim();
|
|
927
|
+
if (!repoRoot) {
|
|
928
|
+
return void 0;
|
|
929
|
+
}
|
|
930
|
+
const shaRaw = git(["-C", cwd, "rev-parse", "HEAD"]);
|
|
931
|
+
const commitSha = shaRaw?.trim();
|
|
932
|
+
const statusRaw = git(["-C", cwd, "status", "--porcelain"]);
|
|
933
|
+
const dirty = typeof statusRaw === "string" ? statusRaw.trim().length > 0 : false;
|
|
934
|
+
const snapshot = { repoRoot, dirty };
|
|
935
|
+
if (commitSha && commitSha.length > 0) snapshot.commitSha = commitSha;
|
|
936
|
+
return snapshot;
|
|
937
|
+
}
|
|
938
|
+
function worktreeAttributes(snapshot, sessionId) {
|
|
939
|
+
const attributes = {
|
|
940
|
+
axis: "promotion",
|
|
941
|
+
dirty: snapshot.dirty
|
|
942
|
+
};
|
|
943
|
+
if (snapshot.commitSha) attributes["commit.sha"] = snapshot.commitSha;
|
|
944
|
+
if (snapshot.repoRoot) attributes["repo.root"] = snapshot.repoRoot;
|
|
945
|
+
if (sessionId) {
|
|
946
|
+
attributes["agent.session_id"] = sessionId;
|
|
947
|
+
attributes["session_id"] = sessionId;
|
|
948
|
+
}
|
|
949
|
+
return attributes;
|
|
950
|
+
}
|
|
951
|
+
|
|
663
952
|
// src/sink/sqlite-sink.ts
|
|
664
953
|
import { DatabaseSync } from "node:sqlite";
|
|
665
954
|
import { mkdirSync } from "fs";
|
|
@@ -686,7 +975,8 @@ CREATE TABLE IF NOT EXISTS canonical_events (
|
|
|
686
975
|
run_id TEXT,
|
|
687
976
|
session_id TEXT,
|
|
688
977
|
task_id TEXT,
|
|
689
|
-
attributes TEXT NOT NULL DEFAULT '{}'
|
|
978
|
+
attributes TEXT NOT NULL DEFAULT '{}',
|
|
979
|
+
dedup_key TEXT
|
|
690
980
|
);
|
|
691
981
|
|
|
692
982
|
CREATE INDEX IF NOT EXISTS idx_ce_trace_id ON canonical_events (trace_id);
|
|
@@ -752,6 +1042,24 @@ var SqliteSink = class {
|
|
|
752
1042
|
}
|
|
753
1043
|
configureSqliteDatabase(this.db);
|
|
754
1044
|
this.db.exec(SCHEMA);
|
|
1045
|
+
this.migrateDedupKey();
|
|
1046
|
+
}
|
|
1047
|
+
/**
|
|
1048
|
+
* Restart-safe migration for the dedup_key column + UNIQUE index (§9.2).
|
|
1049
|
+
*
|
|
1050
|
+
* `CREATE TABLE IF NOT EXISTS` is a no-op on databases created before dedup_key
|
|
1051
|
+
* existed, so the column must be added in-place. PRAGMA table_info lets us detect
|
|
1052
|
+
* its absence and ALTER it in (idempotent: skipped once present). The UNIQUE index
|
|
1053
|
+
* is created only after the column is guaranteed to exist.
|
|
1054
|
+
*/
|
|
1055
|
+
migrateDedupKey() {
|
|
1056
|
+
const columns = this.db.prepare("PRAGMA table_info(canonical_events)").all();
|
|
1057
|
+
if (!columns.some((c) => c.name === "dedup_key")) {
|
|
1058
|
+
this.db.exec("ALTER TABLE canonical_events ADD COLUMN dedup_key TEXT");
|
|
1059
|
+
}
|
|
1060
|
+
this.db.exec(
|
|
1061
|
+
"CREATE UNIQUE INDEX IF NOT EXISTS idx_ce_dedup_key ON canonical_events (dedup_key)"
|
|
1062
|
+
);
|
|
755
1063
|
}
|
|
756
1064
|
// ── Public write API ───────────────────────────────────────────────────────
|
|
757
1065
|
/**
|
|
@@ -759,13 +1067,19 @@ var SqliteSink = class {
|
|
|
759
1067
|
* 1. Append to canonical_events (immutable)
|
|
760
1068
|
* 2. Upsert spans materialised view
|
|
761
1069
|
* 3. Insert canonical_links rows
|
|
1070
|
+
*
|
|
1071
|
+
* Returns `false` when the event carried a dedup_key that already exists (the
|
|
1072
|
+
* INSERT was ignored and no span/link side effects were applied — making
|
|
1073
|
+
* re-runs of a Canonical Projection batch fully idempotent), otherwise `true`.
|
|
762
1074
|
*/
|
|
763
1075
|
write(event) {
|
|
764
|
-
this.insertCanonicalEvent(event);
|
|
1076
|
+
const inserted = this.insertCanonicalEvent(event);
|
|
1077
|
+
if (!inserted) return false;
|
|
765
1078
|
this.upsertSpan(event);
|
|
766
1079
|
if (event.links.length > 0) {
|
|
767
1080
|
this.insertLinks(event);
|
|
768
1081
|
}
|
|
1082
|
+
return true;
|
|
769
1083
|
}
|
|
770
1084
|
/** Close the database connection. */
|
|
771
1085
|
close() {
|
|
@@ -779,14 +1093,23 @@ var SqliteSink = class {
|
|
|
779
1093
|
return this.db;
|
|
780
1094
|
}
|
|
781
1095
|
// ── Private helpers ────────────────────────────────────────────────────────
|
|
1096
|
+
/**
|
|
1097
|
+
* Append the immutable canonical_events row.
|
|
1098
|
+
*
|
|
1099
|
+
* Uses INSERT OR IGNORE so a dedup_key collision (UNIQUE idx_ce_dedup_key) is a
|
|
1100
|
+
* silent no-op rather than an error — the basis of §9.2 idempotent re-ingestion.
|
|
1101
|
+
* Returns true when a new row was written, false when ignored on conflict.
|
|
1102
|
+
* Events without a dedup_key (NULL) are never ignored (multiple NULLs are
|
|
1103
|
+
* distinct under SQLite's UNIQUE semantics).
|
|
1104
|
+
*/
|
|
782
1105
|
insertCanonicalEvent(event) {
|
|
783
1106
|
const stmt = this.db.prepare(`
|
|
784
|
-
INSERT INTO canonical_events
|
|
1107
|
+
INSERT OR IGNORE INTO canonical_events
|
|
785
1108
|
(id, time_unix_nano, source, event_type, lifecycle,
|
|
786
|
-
trace_id, span_id, parent_span_id, run_id, session_id, task_id, attributes)
|
|
787
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1109
|
+
trace_id, span_id, parent_span_id, run_id, session_id, task_id, attributes, dedup_key)
|
|
1110
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
788
1111
|
`);
|
|
789
|
-
stmt.run(
|
|
1112
|
+
const result = stmt.run(
|
|
790
1113
|
event.id,
|
|
791
1114
|
event.timeUnixNano,
|
|
792
1115
|
event.source,
|
|
@@ -798,8 +1121,10 @@ var SqliteSink = class {
|
|
|
798
1121
|
event.runId ?? null,
|
|
799
1122
|
event.sessionId ?? null,
|
|
800
1123
|
event.taskId ?? null,
|
|
801
|
-
JSON.stringify(event.attributes)
|
|
1124
|
+
JSON.stringify(event.attributes),
|
|
1125
|
+
event.dedupKey ?? null
|
|
802
1126
|
);
|
|
1127
|
+
return Number(result.changes) > 0;
|
|
803
1128
|
}
|
|
804
1129
|
upsertSpan(event) {
|
|
805
1130
|
switch (event.lifecycle) {
|
|
@@ -973,7 +1298,7 @@ var OtelEmitter = class {
|
|
|
973
1298
|
{
|
|
974
1299
|
kind: SpanKind.INTERNAL,
|
|
975
1300
|
startTime: startMs,
|
|
976
|
-
attributes:
|
|
1301
|
+
attributes: convertAttributes2(mapped.attributes),
|
|
977
1302
|
links: mapped.links.map((l) => ({
|
|
978
1303
|
context: {
|
|
979
1304
|
traceId: l.targetTraceId ?? mapped.traceId,
|
|
@@ -988,7 +1313,7 @@ var OtelEmitter = class {
|
|
|
988
1313
|
for (const ev of mapped.spanEvents) {
|
|
989
1314
|
otelSpan.addEvent(
|
|
990
1315
|
ev.name,
|
|
991
|
-
|
|
1316
|
+
convertAttributes2(ev.attributes),
|
|
992
1317
|
Number(ev.timeUnixNano / 1000000n)
|
|
993
1318
|
);
|
|
994
1319
|
}
|
|
@@ -1006,7 +1331,7 @@ var OtelEmitter = class {
|
|
|
1006
1331
|
return trace.setSpanContext(ROOT_CONTEXT, spanContext);
|
|
1007
1332
|
}
|
|
1008
1333
|
};
|
|
1009
|
-
function
|
|
1334
|
+
function convertAttributes2(attrs) {
|
|
1010
1335
|
return attrs;
|
|
1011
1336
|
}
|
|
1012
1337
|
|
|
@@ -1238,8 +1563,9 @@ function parseEventRow(row) {
|
|
|
1238
1563
|
sessionId: row.session_id ?? void 0,
|
|
1239
1564
|
taskId: row.task_id ?? void 0,
|
|
1240
1565
|
attributes: safeParseJson(row.attributes),
|
|
1241
|
-
links: []
|
|
1566
|
+
links: [],
|
|
1242
1567
|
// links are stored in canonical_links, not inline on events
|
|
1568
|
+
...row.dedup_key ? { dedupKey: row.dedup_key } : {}
|
|
1243
1569
|
};
|
|
1244
1570
|
}
|
|
1245
1571
|
function parseLinkRow(row) {
|
|
@@ -1598,6 +1924,658 @@ function emitPromotionPr(collector, options) {
|
|
|
1598
1924
|
return spanId;
|
|
1599
1925
|
}
|
|
1600
1926
|
|
|
1927
|
+
// src/promotion/github-recording.ts
|
|
1928
|
+
function parseIssueIdFromText(text) {
|
|
1929
|
+
const hashMatch = text.match(/#(\d+)/);
|
|
1930
|
+
if (hashMatch) {
|
|
1931
|
+
return Number(hashMatch[1]);
|
|
1932
|
+
}
|
|
1933
|
+
const branchMatch = text.match(/issue-(\d+)/i);
|
|
1934
|
+
if (branchMatch) {
|
|
1935
|
+
return Number(branchMatch[1]);
|
|
1936
|
+
}
|
|
1937
|
+
return null;
|
|
1938
|
+
}
|
|
1939
|
+
function buildGithubPromotionAttributes(input) {
|
|
1940
|
+
const issueId = input.commitMessage !== void 0 ? parseIssueIdFromText(input.commitMessage) : null;
|
|
1941
|
+
const attrs = {
|
|
1942
|
+
axis: "promotion",
|
|
1943
|
+
"commit.sha": input.sha
|
|
1944
|
+
};
|
|
1945
|
+
if (issueId !== null) {
|
|
1946
|
+
attrs["issue.id"] = issueId;
|
|
1947
|
+
}
|
|
1948
|
+
if (input.kind === "ci") {
|
|
1949
|
+
attrs["ci.run_id"] = input.runId;
|
|
1950
|
+
attrs["ci.verdict"] = input.verdict;
|
|
1951
|
+
if (input.workflowName) {
|
|
1952
|
+
attrs["ci.workflow"] = input.workflowName;
|
|
1953
|
+
}
|
|
1954
|
+
return attrs;
|
|
1955
|
+
}
|
|
1956
|
+
attrs["deploy.id"] = input.runId;
|
|
1957
|
+
attrs["deploy.status"] = input.verdict;
|
|
1958
|
+
attrs["deploy.target"] = input.deployTarget ?? "unknown";
|
|
1959
|
+
return attrs;
|
|
1960
|
+
}
|
|
1961
|
+
function githubPromotionEventType(kind) {
|
|
1962
|
+
return kind === "ci" ? "promotion.ci" : "promotion.deploy";
|
|
1963
|
+
}
|
|
1964
|
+
|
|
1965
|
+
// src/analysis/outcome-chain.ts
|
|
1966
|
+
var OUTCOME_CHAIN_STAGES = [
|
|
1967
|
+
"agent.session",
|
|
1968
|
+
"process.task",
|
|
1969
|
+
"process.edit",
|
|
1970
|
+
"promotion.commit",
|
|
1971
|
+
"promotion.ci",
|
|
1972
|
+
"promotion.deploy"
|
|
1973
|
+
];
|
|
1974
|
+
function observationMatchesIssue(observation, issueId) {
|
|
1975
|
+
const attrs = observation.attributes ?? {};
|
|
1976
|
+
const attrIssue = attrs["issue.id"];
|
|
1977
|
+
if (attrIssue !== void 0 && String(attrIssue) === issueId) {
|
|
1978
|
+
return true;
|
|
1979
|
+
}
|
|
1980
|
+
const commitMessage = attrs["commit.message"];
|
|
1981
|
+
if (typeof commitMessage === "string") {
|
|
1982
|
+
const parsed = parseIssueIdFromText(commitMessage);
|
|
1983
|
+
if (parsed !== null && String(parsed) === issueId) {
|
|
1984
|
+
return true;
|
|
1985
|
+
}
|
|
1986
|
+
}
|
|
1987
|
+
const prCommand = attrs["pr.command"];
|
|
1988
|
+
if (typeof prCommand === "string" && prCommand.includes(`#${issueId}`)) {
|
|
1989
|
+
return true;
|
|
1990
|
+
}
|
|
1991
|
+
return false;
|
|
1992
|
+
}
|
|
1993
|
+
function isProvisionalObservation(observation) {
|
|
1994
|
+
const sessionId = observation.sessionId ?? observation.attributes?.["session_id"];
|
|
1995
|
+
if (sessionId === "unknown" || sessionId === void 0 || sessionId === "") {
|
|
1996
|
+
return true;
|
|
1997
|
+
}
|
|
1998
|
+
const confidence = observation.attributes?.["link.confidence"] ?? observation.attributes?.["confidence"];
|
|
1999
|
+
return confidence === "provisional";
|
|
2000
|
+
}
|
|
2001
|
+
function collectObservations(adapter) {
|
|
2002
|
+
const latest = /* @__PURE__ */ new Map();
|
|
2003
|
+
for (const event of adapter.queryEvents({})) {
|
|
2004
|
+
latest.set(`${event.spanId}:${event.eventType}`, event);
|
|
2005
|
+
}
|
|
2006
|
+
for (const span of adapter.querySpans({})) {
|
|
2007
|
+
latest.set(`${span.spanId}:${span.eventType}`, {
|
|
2008
|
+
id: span.spanId,
|
|
2009
|
+
timeUnixNano: span.startTime ?? 0n,
|
|
2010
|
+
source: "spans",
|
|
2011
|
+
eventType: span.eventType,
|
|
2012
|
+
lifecycle: span.status === "closed" ? "close" : span.status === "open" ? "open" : "instant",
|
|
2013
|
+
traceId: span.traceId ?? "",
|
|
2014
|
+
spanId: span.spanId,
|
|
2015
|
+
parentSpanId: span.parentSpanId ?? void 0,
|
|
2016
|
+
sessionId: span.sessionId ?? void 0,
|
|
2017
|
+
attributes: span.attributes,
|
|
2018
|
+
links: []
|
|
2019
|
+
});
|
|
2020
|
+
}
|
|
2021
|
+
return [...latest.values()];
|
|
2022
|
+
}
|
|
2023
|
+
function analyzeOutcomeChain(adapter, issueId) {
|
|
2024
|
+
const normalizedIssueId = String(issueId);
|
|
2025
|
+
const observations = collectObservations(adapter);
|
|
2026
|
+
const issueObservations = observations.filter(
|
|
2027
|
+
(obs) => observationMatchesIssue(obs, normalizedIssueId)
|
|
2028
|
+
);
|
|
2029
|
+
const stages = OUTCOME_CHAIN_STAGES.map((eventType) => {
|
|
2030
|
+
const matched = issueObservations.filter((obs) => obs.eventType === eventType);
|
|
2031
|
+
if (matched.length === 0) {
|
|
2032
|
+
return { eventType, status: "missing", spanIds: [] };
|
|
2033
|
+
}
|
|
2034
|
+
const provisionalOnly = matched.every(isProvisionalObservation);
|
|
2035
|
+
return {
|
|
2036
|
+
eventType,
|
|
2037
|
+
status: provisionalOnly ? "provisional" : "present",
|
|
2038
|
+
spanIds: matched.map((obs) => obs.spanId)
|
|
2039
|
+
};
|
|
2040
|
+
});
|
|
2041
|
+
const gaps = stages.filter((s) => s.status === "missing").map((s) => s.eventType);
|
|
2042
|
+
const provisional = stages.filter((s) => s.status === "provisional").map((s) => s.eventType);
|
|
2043
|
+
return {
|
|
2044
|
+
issueId: normalizedIssueId,
|
|
2045
|
+
stages,
|
|
2046
|
+
complete: gaps.length === 0,
|
|
2047
|
+
gaps,
|
|
2048
|
+
provisional
|
|
2049
|
+
};
|
|
2050
|
+
}
|
|
2051
|
+
|
|
2052
|
+
// src/analysis/diagnostic-report.ts
|
|
2053
|
+
var STAGE_SET = new Set(OUTCOME_CHAIN_STAGES);
|
|
2054
|
+
function attrStr(attrs, key) {
|
|
2055
|
+
const v = attrs[key];
|
|
2056
|
+
return v === void 0 || v === null ? "" : String(v);
|
|
2057
|
+
}
|
|
2058
|
+
function attrNum(attrs, key) {
|
|
2059
|
+
const v = attrs[key];
|
|
2060
|
+
if (v === void 0 || v === null || v === "") return 0;
|
|
2061
|
+
const n = typeof v === "number" ? v : Number(v);
|
|
2062
|
+
return Number.isFinite(n) ? n : 0;
|
|
2063
|
+
}
|
|
2064
|
+
function projectIdOf(span) {
|
|
2065
|
+
const p = attrStr(span.attributes, "project.id");
|
|
2066
|
+
if (p !== "") return p;
|
|
2067
|
+
const s = attrStr(span.attributes, "service.name");
|
|
2068
|
+
return s;
|
|
2069
|
+
}
|
|
2070
|
+
function issueIdOf(span) {
|
|
2071
|
+
return attrStr(span.attributes, "issue.id");
|
|
2072
|
+
}
|
|
2073
|
+
function sessionIdOf(span) {
|
|
2074
|
+
if (span.sessionId !== null && span.sessionId !== void 0 && span.sessionId !== "") {
|
|
2075
|
+
return span.sessionId;
|
|
2076
|
+
}
|
|
2077
|
+
const a = attrStr(span.attributes, "agent.session_id") || attrStr(span.attributes, "session_id");
|
|
2078
|
+
return a;
|
|
2079
|
+
}
|
|
2080
|
+
function round(value, decimals) {
|
|
2081
|
+
if (!Number.isFinite(value)) return 0;
|
|
2082
|
+
const f = 10 ** decimals;
|
|
2083
|
+
return Math.round(value * f) / f;
|
|
2084
|
+
}
|
|
2085
|
+
function average(values) {
|
|
2086
|
+
if (values.length === 0) return 0;
|
|
2087
|
+
return values.reduce((a, b) => a + b, 0) / values.length;
|
|
2088
|
+
}
|
|
2089
|
+
function inWindow(span, fromNano, toNano) {
|
|
2090
|
+
if (fromNano === void 0 && toNano === void 0) return true;
|
|
2091
|
+
const t = span.startTime;
|
|
2092
|
+
if (t === null) return false;
|
|
2093
|
+
if (fromNano !== void 0 && t < fromNano) return false;
|
|
2094
|
+
if (toNano !== void 0 && t > toNano) return false;
|
|
2095
|
+
return true;
|
|
2096
|
+
}
|
|
2097
|
+
function filterSpans(spans, opts) {
|
|
2098
|
+
return spans.filter((s) => {
|
|
2099
|
+
if (opts.projectId !== void 0 && projectIdOf(s) !== opts.projectId) {
|
|
2100
|
+
return false;
|
|
2101
|
+
}
|
|
2102
|
+
return inWindow(s, opts.fromNano, opts.toNano);
|
|
2103
|
+
});
|
|
2104
|
+
}
|
|
2105
|
+
function computeLayer1(spans) {
|
|
2106
|
+
const groups = /* @__PURE__ */ new Map();
|
|
2107
|
+
for (const s of spans) {
|
|
2108
|
+
const issueId = issueIdOf(s);
|
|
2109
|
+
if (issueId === "") continue;
|
|
2110
|
+
const key = `${issueId}\0${projectIdOf(s)}`;
|
|
2111
|
+
(groups.get(key) ?? groups.set(key, []).get(key)).push(s);
|
|
2112
|
+
}
|
|
2113
|
+
const rows = [];
|
|
2114
|
+
for (const group of groups.values()) {
|
|
2115
|
+
const issueId = issueIdOf(group[0]);
|
|
2116
|
+
const projectId = projectIdOf(group[0]);
|
|
2117
|
+
const issueType = group.map((s) => attrStr(s.attributes, "issue.type")).find((t) => t !== "") ?? "";
|
|
2118
|
+
const sessionIds = /* @__PURE__ */ new Set();
|
|
2119
|
+
let editCount = 0;
|
|
2120
|
+
let commitCount = 0;
|
|
2121
|
+
let ciCount = 0;
|
|
2122
|
+
let ciPassed = 0;
|
|
2123
|
+
for (const s of group) {
|
|
2124
|
+
if (s.eventType === "agent.session") {
|
|
2125
|
+
const sid = sessionIdOf(s);
|
|
2126
|
+
if (sid !== "") sessionIds.add(sid);
|
|
2127
|
+
}
|
|
2128
|
+
if (s.eventType === "process.edit") editCount += 1;
|
|
2129
|
+
if (s.eventType === "promotion.commit") commitCount += 1;
|
|
2130
|
+
if (s.eventType === "promotion.ci") {
|
|
2131
|
+
ciCount += 1;
|
|
2132
|
+
if (attrStr(s.attributes, "ci.verdict") === "passed") ciPassed += 1;
|
|
2133
|
+
}
|
|
2134
|
+
}
|
|
2135
|
+
const sessionCount = sessionIds.size;
|
|
2136
|
+
rows.push({
|
|
2137
|
+
issueId,
|
|
2138
|
+
issueType,
|
|
2139
|
+
projectId,
|
|
2140
|
+
sessionCount,
|
|
2141
|
+
editCount,
|
|
2142
|
+
commitCount,
|
|
2143
|
+
ciCount,
|
|
2144
|
+
ciPassed,
|
|
2145
|
+
leverage: sessionCount > 0 ? round(editCount / sessionCount, 2) : 0
|
|
2146
|
+
});
|
|
2147
|
+
}
|
|
2148
|
+
return rows.sort((a, b) => a.issueId.localeCompare(b.issueId));
|
|
2149
|
+
}
|
|
2150
|
+
function computeLayer2(spans) {
|
|
2151
|
+
const groups = /* @__PURE__ */ new Map();
|
|
2152
|
+
for (const s of spans) {
|
|
2153
|
+
const issueId = issueIdOf(s);
|
|
2154
|
+
if (issueId === "") continue;
|
|
2155
|
+
const key = `${issueId}\0${projectIdOf(s)}`;
|
|
2156
|
+
(groups.get(key) ?? groups.set(key, []).get(key)).push(s);
|
|
2157
|
+
}
|
|
2158
|
+
const rows = [];
|
|
2159
|
+
for (const group of groups.values()) {
|
|
2160
|
+
const issueId = issueIdOf(group[0]);
|
|
2161
|
+
const projectId = projectIdOf(group[0]);
|
|
2162
|
+
const sessionIds = /* @__PURE__ */ new Set();
|
|
2163
|
+
let commits = 0;
|
|
2164
|
+
let ciPassed = 0;
|
|
2165
|
+
let totalCostUsd = 0;
|
|
2166
|
+
for (const s of group) {
|
|
2167
|
+
if (s.eventType === "agent.session") {
|
|
2168
|
+
const sid = sessionIdOf(s);
|
|
2169
|
+
if (sid !== "") sessionIds.add(sid);
|
|
2170
|
+
totalCostUsd += attrNum(s.attributes, "cost_usd");
|
|
2171
|
+
}
|
|
2172
|
+
if (s.eventType === "promotion.commit") commits += 1;
|
|
2173
|
+
if (s.eventType === "promotion.ci" && attrStr(s.attributes, "ci.verdict") === "passed") {
|
|
2174
|
+
ciPassed += 1;
|
|
2175
|
+
}
|
|
2176
|
+
}
|
|
2177
|
+
const sessions = sessionIds.size;
|
|
2178
|
+
rows.push({
|
|
2179
|
+
issueId,
|
|
2180
|
+
projectId,
|
|
2181
|
+
sessions,
|
|
2182
|
+
commits,
|
|
2183
|
+
ciPassed,
|
|
2184
|
+
totalCostUsd: round(totalCostUsd, 4),
|
|
2185
|
+
commitRate: sessions > 0 ? round(commits / sessions, 4) : 0,
|
|
2186
|
+
ciPassRate: commits > 0 ? round(ciPassed / commits, 4) : 0,
|
|
2187
|
+
commitsPerDollar: totalCostUsd > 0 ? round(commits / totalCostUsd, 4) : 0
|
|
2188
|
+
});
|
|
2189
|
+
}
|
|
2190
|
+
return rows.sort((a, b) => a.issueId.localeCompare(b.issueId));
|
|
2191
|
+
}
|
|
2192
|
+
function computeLayer3(spans) {
|
|
2193
|
+
const groups = /* @__PURE__ */ new Map();
|
|
2194
|
+
for (const s of spans) {
|
|
2195
|
+
const issueId = issueIdOf(s);
|
|
2196
|
+
if (issueId === "") continue;
|
|
2197
|
+
const key = `${issueId}\0${projectIdOf(s)}`;
|
|
2198
|
+
(groups.get(key) ?? groups.set(key, []).get(key)).push(s);
|
|
2199
|
+
}
|
|
2200
|
+
const rows = [];
|
|
2201
|
+
for (const group of groups.values()) {
|
|
2202
|
+
const issueId = issueIdOf(group[0]);
|
|
2203
|
+
const projectId = projectIdOf(group[0]);
|
|
2204
|
+
const issueType = group.map((s) => attrStr(s.attributes, "issue.type")).find((t) => t !== "") ?? "";
|
|
2205
|
+
const stages = /* @__PURE__ */ new Set();
|
|
2206
|
+
let totalCostUsd = 0;
|
|
2207
|
+
let totalInputTokens = 0;
|
|
2208
|
+
let totalOutputTokens = 0;
|
|
2209
|
+
for (const s of group) {
|
|
2210
|
+
if (STAGE_SET.has(s.eventType)) stages.add(s.eventType);
|
|
2211
|
+
if (s.eventType === "agent.session") {
|
|
2212
|
+
totalCostUsd += attrNum(s.attributes, "cost_usd");
|
|
2213
|
+
totalInputTokens += attrNum(s.attributes, "gen_ai.usage.input_tokens");
|
|
2214
|
+
totalOutputTokens += attrNum(s.attributes, "gen_ai.usage.output_tokens");
|
|
2215
|
+
}
|
|
2216
|
+
}
|
|
2217
|
+
const stagesPresent = stages.size;
|
|
2218
|
+
rows.push({
|
|
2219
|
+
issueId,
|
|
2220
|
+
issueType,
|
|
2221
|
+
projectId,
|
|
2222
|
+
stagesPresent,
|
|
2223
|
+
stagesTotal: OUTCOME_CHAIN_STAGES.length,
|
|
2224
|
+
chainCompleteness: round(stagesPresent / OUTCOME_CHAIN_STAGES.length, 4),
|
|
2225
|
+
totalCostUsd: round(totalCostUsd, 4),
|
|
2226
|
+
totalInputTokens,
|
|
2227
|
+
totalOutputTokens
|
|
2228
|
+
});
|
|
2229
|
+
}
|
|
2230
|
+
return rows.sort((a, b) => a.issueId.localeCompare(b.issueId));
|
|
2231
|
+
}
|
|
2232
|
+
function computeSessionLinkRate(spans) {
|
|
2233
|
+
const byProject = /* @__PURE__ */ new Map();
|
|
2234
|
+
for (const s of spans) {
|
|
2235
|
+
if (s.eventType !== "agent.session") continue;
|
|
2236
|
+
const projectId = projectIdOf(s);
|
|
2237
|
+
const acc = byProject.get(projectId) ?? { total: 0, linked: 0 };
|
|
2238
|
+
acc.total += 1;
|
|
2239
|
+
if (issueIdOf(s) !== "") acc.linked += 1;
|
|
2240
|
+
byProject.set(projectId, acc);
|
|
2241
|
+
}
|
|
2242
|
+
return [...byProject.entries()].map(([projectId, { total, linked }]) => ({
|
|
2243
|
+
projectId,
|
|
2244
|
+
totalSessions: total,
|
|
2245
|
+
linkedSessions: linked,
|
|
2246
|
+
linkRate: total > 0 ? round(linked / total, 4) : 0
|
|
2247
|
+
})).sort((a, b) => a.projectId.localeCompare(b.projectId));
|
|
2248
|
+
}
|
|
2249
|
+
function computeStageArrival(spans) {
|
|
2250
|
+
const byProject = /* @__PURE__ */ new Map();
|
|
2251
|
+
for (const s of spans) {
|
|
2252
|
+
const issueId = issueIdOf(s);
|
|
2253
|
+
if (issueId === "") continue;
|
|
2254
|
+
const projectId = projectIdOf(s);
|
|
2255
|
+
const acc = byProject.get(projectId) ?? { commit: /* @__PURE__ */ new Set(), ci: /* @__PURE__ */ new Set(), deploy: /* @__PURE__ */ new Set() };
|
|
2256
|
+
if (s.eventType === "promotion.commit") acc.commit.add(issueId);
|
|
2257
|
+
if (s.eventType === "promotion.ci") acc.ci.add(issueId);
|
|
2258
|
+
if (s.eventType === "promotion.deploy") acc.deploy.add(issueId);
|
|
2259
|
+
byProject.set(projectId, acc);
|
|
2260
|
+
}
|
|
2261
|
+
return [...byProject.entries()].map(([projectId, { commit, ci, deploy }]) => {
|
|
2262
|
+
const issuesWithCommit = commit.size;
|
|
2263
|
+
const issuesWithCi = ci.size;
|
|
2264
|
+
const issuesWithDeploy = deploy.size;
|
|
2265
|
+
return {
|
|
2266
|
+
projectId,
|
|
2267
|
+
issuesWithCommit,
|
|
2268
|
+
issuesWithCi,
|
|
2269
|
+
issuesWithDeploy,
|
|
2270
|
+
ciArrivalRate: issuesWithCommit > 0 ? round(issuesWithCi / issuesWithCommit, 4) : 0,
|
|
2271
|
+
deployArrivalRate: issuesWithCommit > 0 ? round(issuesWithDeploy / issuesWithCommit, 4) : 0
|
|
2272
|
+
};
|
|
2273
|
+
}).sort((a, b) => a.projectId.localeCompare(b.projectId));
|
|
2274
|
+
}
|
|
2275
|
+
function computeProvisional(spans) {
|
|
2276
|
+
const byProject = /* @__PURE__ */ new Map();
|
|
2277
|
+
for (const s of spans) {
|
|
2278
|
+
const projectId = projectIdOf(s);
|
|
2279
|
+
const acc = byProject.get(projectId) ?? { total: 0, unknown: 0, provisional: 0 };
|
|
2280
|
+
acc.total += 1;
|
|
2281
|
+
const sid = sessionIdOf(s);
|
|
2282
|
+
if (sid === "" || sid === "unknown") acc.unknown += 1;
|
|
2283
|
+
const confidence = attrStr(s.attributes, "link.confidence") || attrStr(s.attributes, "confidence");
|
|
2284
|
+
if (confidence === "provisional") acc.provisional += 1;
|
|
2285
|
+
byProject.set(projectId, acc);
|
|
2286
|
+
}
|
|
2287
|
+
return [...byProject.entries()].map(([projectId, { total, unknown, provisional }]) => ({
|
|
2288
|
+
projectId,
|
|
2289
|
+
totalSpans: total,
|
|
2290
|
+
unknownSessionSpans: unknown,
|
|
2291
|
+
provisionalSpans: provisional,
|
|
2292
|
+
unknownRatio: total > 0 ? round(unknown / total, 4) : 0,
|
|
2293
|
+
provisionalRatio: total > 0 ? round(provisional / total, 4) : 0
|
|
2294
|
+
})).sort((a, b) => a.projectId.localeCompare(b.projectId));
|
|
2295
|
+
}
|
|
2296
|
+
function tierOf(issuesTouched) {
|
|
2297
|
+
if (issuesTouched >= 10) return "high";
|
|
2298
|
+
if (issuesTouched >= 3) return "mid";
|
|
2299
|
+
return "low";
|
|
2300
|
+
}
|
|
2301
|
+
function computeTierRatio(spans) {
|
|
2302
|
+
const byPerson = /* @__PURE__ */ new Map();
|
|
2303
|
+
for (const s of spans) {
|
|
2304
|
+
if (s.eventType !== "promotion.commit") continue;
|
|
2305
|
+
const email = attrStr(s.attributes, "git.user.email");
|
|
2306
|
+
if (email === "") continue;
|
|
2307
|
+
const projectId = projectIdOf(s);
|
|
2308
|
+
const key = `${email}\0${projectId}`;
|
|
2309
|
+
const acc = byPerson.get(key) ?? { projectId, commits: 0, issues: /* @__PURE__ */ new Set() };
|
|
2310
|
+
acc.commits += 1;
|
|
2311
|
+
const issueId = issueIdOf(s);
|
|
2312
|
+
if (issueId !== "") acc.issues.add(issueId);
|
|
2313
|
+
byPerson.set(key, acc);
|
|
2314
|
+
}
|
|
2315
|
+
const byTier = /* @__PURE__ */ new Map();
|
|
2316
|
+
for (const { projectId, commits, issues } of byPerson.values()) {
|
|
2317
|
+
const tier = tierOf(issues.size);
|
|
2318
|
+
const key = `${projectId}\0${tier}`;
|
|
2319
|
+
const acc = byTier.get(key) ?? { projectId, tier, personCount: 0, commits: 0, issues: 0 };
|
|
2320
|
+
acc.personCount += 1;
|
|
2321
|
+
acc.commits += commits;
|
|
2322
|
+
acc.issues += issues.size;
|
|
2323
|
+
byTier.set(key, acc);
|
|
2324
|
+
}
|
|
2325
|
+
const order = { high: 0, mid: 1, low: 2 };
|
|
2326
|
+
return [...byTier.values()].map((t) => ({
|
|
2327
|
+
projectId: t.projectId,
|
|
2328
|
+
tier: t.tier,
|
|
2329
|
+
personCount: t.personCount,
|
|
2330
|
+
tierCommits: t.commits,
|
|
2331
|
+
tierIssues: t.issues
|
|
2332
|
+
})).sort(
|
|
2333
|
+
(a, b) => a.projectId.localeCompare(b.projectId) || order[a.tier] - order[b.tier]
|
|
2334
|
+
);
|
|
2335
|
+
}
|
|
2336
|
+
function summarize(layer1, layer2, layer3, sessionLinkRate, tierRatio) {
|
|
2337
|
+
const totalSessions = sessionLinkRate.reduce((a, r) => a + r.totalSessions, 0);
|
|
2338
|
+
const linkedSessions = sessionLinkRate.reduce((a, r) => a + r.linkedSessions, 0);
|
|
2339
|
+
const tierCounts = { high: 0, mid: 0, low: 0 };
|
|
2340
|
+
for (const t of tierRatio) tierCounts[t.tier] += t.personCount;
|
|
2341
|
+
return {
|
|
2342
|
+
issueCount: new Set(layer1.map((r) => r.issueId)).size,
|
|
2343
|
+
sessionCount: totalSessions,
|
|
2344
|
+
totalCostUsd: round(
|
|
2345
|
+
layer2.reduce((a, r) => a + r.totalCostUsd, 0),
|
|
2346
|
+
4
|
|
2347
|
+
),
|
|
2348
|
+
avgLeverage: round(average(layer1.map((r) => r.leverage)), 4),
|
|
2349
|
+
avgCommitRate: round(average(layer2.map((r) => r.commitRate)), 4),
|
|
2350
|
+
avgCiPassRate: round(average(layer2.map((r) => r.ciPassRate)), 4),
|
|
2351
|
+
avgChainCompleteness: round(average(layer3.map((r) => r.chainCompleteness)), 4),
|
|
2352
|
+
sessionLinkRate: totalSessions > 0 ? round(linkedSessions / totalSessions, 4) : 0,
|
|
2353
|
+
tierCounts
|
|
2354
|
+
};
|
|
2355
|
+
}
|
|
2356
|
+
function computeMetrics(spans) {
|
|
2357
|
+
const layer1 = computeLayer1(spans);
|
|
2358
|
+
const layer2 = computeLayer2(spans);
|
|
2359
|
+
const layer3 = computeLayer3(spans);
|
|
2360
|
+
const sessionLinkRate = computeSessionLinkRate(spans);
|
|
2361
|
+
const stageArrival = computeStageArrival(spans);
|
|
2362
|
+
const provisional = computeProvisional(spans);
|
|
2363
|
+
const tierRatio = computeTierRatio(spans);
|
|
2364
|
+
return {
|
|
2365
|
+
layer1,
|
|
2366
|
+
layer2,
|
|
2367
|
+
layer3,
|
|
2368
|
+
linkCompletion: { sessionLinkRate, stageArrival, provisional },
|
|
2369
|
+
tierRatio,
|
|
2370
|
+
summary: summarize(layer1, layer2, layer3, sessionLinkRate, tierRatio)
|
|
2371
|
+
};
|
|
2372
|
+
}
|
|
2373
|
+
function diffSummary(after, before) {
|
|
2374
|
+
return {
|
|
2375
|
+
issueCount: after.issueCount - before.issueCount,
|
|
2376
|
+
sessionCount: after.sessionCount - before.sessionCount,
|
|
2377
|
+
totalCostUsd: round(after.totalCostUsd - before.totalCostUsd, 4),
|
|
2378
|
+
avgLeverage: round(after.avgLeverage - before.avgLeverage, 4),
|
|
2379
|
+
avgCommitRate: round(after.avgCommitRate - before.avgCommitRate, 4),
|
|
2380
|
+
avgCiPassRate: round(after.avgCiPassRate - before.avgCiPassRate, 4),
|
|
2381
|
+
avgChainCompleteness: round(after.avgChainCompleteness - before.avgChainCompleteness, 4),
|
|
2382
|
+
sessionLinkRate: round(after.sessionLinkRate - before.sessionLinkRate, 4)
|
|
2383
|
+
};
|
|
2384
|
+
}
|
|
2385
|
+
function generateDiagnosticReport(adapter, options = {}) {
|
|
2386
|
+
const allSpans = adapter.querySpans({});
|
|
2387
|
+
const fromNano = options.from !== void 0 ? isoToUnixNano(options.from) : void 0;
|
|
2388
|
+
const toNano = options.to !== void 0 ? isoToUnixNano(options.to) : void 0;
|
|
2389
|
+
const currentSpans = filterSpans(allSpans, {
|
|
2390
|
+
projectId: options.projectId,
|
|
2391
|
+
fromNano,
|
|
2392
|
+
toNano
|
|
2393
|
+
});
|
|
2394
|
+
const metrics = computeMetrics(currentSpans);
|
|
2395
|
+
const report = {
|
|
2396
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2397
|
+
projectId: options.projectId ?? null,
|
|
2398
|
+
period: { from: options.from ?? null, to: options.to ?? null },
|
|
2399
|
+
metrics
|
|
2400
|
+
};
|
|
2401
|
+
if (options.baselineFrom !== void 0 || options.baselineTo !== void 0) {
|
|
2402
|
+
const bFromNano = options.baselineFrom !== void 0 ? isoToUnixNano(options.baselineFrom) : void 0;
|
|
2403
|
+
const bToNano = options.baselineTo !== void 0 ? isoToUnixNano(options.baselineTo) : void 0;
|
|
2404
|
+
const baselineSpans = filterSpans(allSpans, {
|
|
2405
|
+
projectId: options.projectId,
|
|
2406
|
+
fromNano: bFromNano,
|
|
2407
|
+
toNano: bToNano
|
|
2408
|
+
});
|
|
2409
|
+
const baselineMetrics = computeMetrics(baselineSpans);
|
|
2410
|
+
report.baseline = {
|
|
2411
|
+
period: { from: options.baselineFrom ?? null, to: options.baselineTo ?? null },
|
|
2412
|
+
metrics: baselineMetrics,
|
|
2413
|
+
comparison: diffSummary(metrics.summary, baselineMetrics.summary)
|
|
2414
|
+
};
|
|
2415
|
+
}
|
|
2416
|
+
return report;
|
|
2417
|
+
}
|
|
2418
|
+
function bigIntReplacer(_key, value) {
|
|
2419
|
+
return typeof value === "bigint" ? value.toString() : value;
|
|
2420
|
+
}
|
|
2421
|
+
function renderReportJson(report) {
|
|
2422
|
+
return JSON.stringify(report, bigIntReplacer, 2);
|
|
2423
|
+
}
|
|
2424
|
+
function fmtPeriod(p) {
|
|
2425
|
+
if (p.from === null && p.to === null) return "all time";
|
|
2426
|
+
return `${p.from ?? "-\u221E"} \u2026 ${p.to ?? "now"}`;
|
|
2427
|
+
}
|
|
2428
|
+
function summaryTable(summary) {
|
|
2429
|
+
return [
|
|
2430
|
+
"| Metric | Value |",
|
|
2431
|
+
"|---|---|",
|
|
2432
|
+
`| Issues | ${summary.issueCount} |`,
|
|
2433
|
+
`| Sessions | ${summary.sessionCount} |`,
|
|
2434
|
+
`| Total cost (USD) | ${summary.totalCostUsd} |`,
|
|
2435
|
+
`| Avg leverage (edits/session) | ${summary.avgLeverage} |`,
|
|
2436
|
+
`| Avg commit rate | ${summary.avgCommitRate} |`,
|
|
2437
|
+
`| Avg CI pass rate | ${summary.avgCiPassRate} |`,
|
|
2438
|
+
`| Avg chain completeness | ${summary.avgChainCompleteness} |`,
|
|
2439
|
+
`| Session link rate | ${summary.sessionLinkRate} |`,
|
|
2440
|
+
`| Person tier (high/mid/low) | ${summary.tierCounts.high} / ${summary.tierCounts.mid} / ${summary.tierCounts.low} |`
|
|
2441
|
+
];
|
|
2442
|
+
}
|
|
2443
|
+
function signed(n) {
|
|
2444
|
+
return n > 0 ? `+${n}` : `${n}`;
|
|
2445
|
+
}
|
|
2446
|
+
function renderReportMarkdown(report) {
|
|
2447
|
+
const lines = [];
|
|
2448
|
+
lines.push("# AaaC Observability \u8A3A\u65AD\u30EC\u30DD\u30FC\u30C8");
|
|
2449
|
+
lines.push("");
|
|
2450
|
+
lines.push(`- Generated at: ${report.generatedAt}`);
|
|
2451
|
+
lines.push(`- Project: ${report.projectId ?? "(all)"}`);
|
|
2452
|
+
lines.push(`- Period: ${fmtPeriod(report.period)}`);
|
|
2453
|
+
lines.push("");
|
|
2454
|
+
lines.push("## Summary");
|
|
2455
|
+
lines.push("");
|
|
2456
|
+
lines.push(...summaryTable(report.metrics.summary));
|
|
2457
|
+
lines.push("");
|
|
2458
|
+
lines.push("## Layer 1 \u2014 Scope / Leverage");
|
|
2459
|
+
lines.push("");
|
|
2460
|
+
if (report.metrics.layer1.length === 0) {
|
|
2461
|
+
lines.push("_No issue-linked activity in range._");
|
|
2462
|
+
} else {
|
|
2463
|
+
lines.push("| Issue | Type | Project | Sessions | Edits | Commits | CI | CI passed | Leverage |");
|
|
2464
|
+
lines.push("|---|---|---|---|---|---|---|---|---|");
|
|
2465
|
+
for (const r of report.metrics.layer1) {
|
|
2466
|
+
lines.push(
|
|
2467
|
+
`| ${r.issueId} | ${r.issueType} | ${r.projectId} | ${r.sessionCount} | ${r.editCount} | ${r.commitCount} | ${r.ciCount} | ${r.ciPassed} | ${r.leverage} |`
|
|
2468
|
+
);
|
|
2469
|
+
}
|
|
2470
|
+
}
|
|
2471
|
+
lines.push("");
|
|
2472
|
+
lines.push("## Layer 2 \u2014 Outcome Efficiency");
|
|
2473
|
+
lines.push("");
|
|
2474
|
+
if (report.metrics.layer2.length === 0) {
|
|
2475
|
+
lines.push("_No issue-linked activity in range._");
|
|
2476
|
+
} else {
|
|
2477
|
+
lines.push("| Issue | Project | Sessions | Commits | CI passed | Cost (USD) | Commit rate | CI pass rate | Commits/$ |");
|
|
2478
|
+
lines.push("|---|---|---|---|---|---|---|---|---|");
|
|
2479
|
+
for (const r of report.metrics.layer2) {
|
|
2480
|
+
lines.push(
|
|
2481
|
+
`| ${r.issueId} | ${r.projectId} | ${r.sessions} | ${r.commits} | ${r.ciPassed} | ${r.totalCostUsd} | ${r.commitRate} | ${r.ciPassRate} | ${r.commitsPerDollar} |`
|
|
2482
|
+
);
|
|
2483
|
+
}
|
|
2484
|
+
}
|
|
2485
|
+
lines.push("");
|
|
2486
|
+
lines.push("## Layer 3 \u2014 Outcome Chain & Cost");
|
|
2487
|
+
lines.push("");
|
|
2488
|
+
if (report.metrics.layer3.length === 0) {
|
|
2489
|
+
lines.push("_No issue-linked activity in range._");
|
|
2490
|
+
} else {
|
|
2491
|
+
lines.push("| Issue | Type | Project | Stages | Completeness | Cost (USD) | Input tokens | Output tokens |");
|
|
2492
|
+
lines.push("|---|---|---|---|---|---|---|---|");
|
|
2493
|
+
for (const r of report.metrics.layer3) {
|
|
2494
|
+
lines.push(
|
|
2495
|
+
`| ${r.issueId} | ${r.issueType} | ${r.projectId} | ${r.stagesPresent}/${r.stagesTotal} | ${r.chainCompleteness} | ${r.totalCostUsd} | ${r.totalInputTokens} | ${r.totalOutputTokens} |`
|
|
2496
|
+
);
|
|
2497
|
+
}
|
|
2498
|
+
}
|
|
2499
|
+
lines.push("");
|
|
2500
|
+
lines.push("## Link Completion KPIs");
|
|
2501
|
+
lines.push("");
|
|
2502
|
+
lines.push("### Session link rate");
|
|
2503
|
+
lines.push("");
|
|
2504
|
+
if (report.metrics.linkCompletion.sessionLinkRate.length === 0) {
|
|
2505
|
+
lines.push("_No sessions in range._");
|
|
2506
|
+
} else {
|
|
2507
|
+
lines.push("| Project | Total sessions | Linked | Link rate |");
|
|
2508
|
+
lines.push("|---|---|---|---|");
|
|
2509
|
+
for (const r of report.metrics.linkCompletion.sessionLinkRate) {
|
|
2510
|
+
lines.push(`| ${r.projectId} | ${r.totalSessions} | ${r.linkedSessions} | ${r.linkRate} |`);
|
|
2511
|
+
}
|
|
2512
|
+
}
|
|
2513
|
+
lines.push("");
|
|
2514
|
+
lines.push("### Stage arrival rate (commit \u2192 CI \u2192 deploy)");
|
|
2515
|
+
lines.push("");
|
|
2516
|
+
if (report.metrics.linkCompletion.stageArrival.length === 0) {
|
|
2517
|
+
lines.push("_No issue-linked commits in range._");
|
|
2518
|
+
} else {
|
|
2519
|
+
lines.push("| Project | Commit | CI | Deploy | CI arrival | Deploy arrival |");
|
|
2520
|
+
lines.push("|---|---|---|---|---|---|");
|
|
2521
|
+
for (const r of report.metrics.linkCompletion.stageArrival) {
|
|
2522
|
+
lines.push(
|
|
2523
|
+
`| ${r.projectId} | ${r.issuesWithCommit} | ${r.issuesWithCi} | ${r.issuesWithDeploy} | ${r.ciArrivalRate} | ${r.deployArrivalRate} |`
|
|
2524
|
+
);
|
|
2525
|
+
}
|
|
2526
|
+
}
|
|
2527
|
+
lines.push("");
|
|
2528
|
+
lines.push("### Provisional / unknown ratio");
|
|
2529
|
+
lines.push("");
|
|
2530
|
+
if (report.metrics.linkCompletion.provisional.length === 0) {
|
|
2531
|
+
lines.push("_No spans in range._");
|
|
2532
|
+
} else {
|
|
2533
|
+
lines.push("| Project | Total spans | Unknown session | Provisional | Unknown ratio | Provisional ratio |");
|
|
2534
|
+
lines.push("|---|---|---|---|---|---|");
|
|
2535
|
+
for (const r of report.metrics.linkCompletion.provisional) {
|
|
2536
|
+
lines.push(
|
|
2537
|
+
`| ${r.projectId} | ${r.totalSpans} | ${r.unknownSessionSpans} | ${r.provisionalSpans} | ${r.unknownRatio} | ${r.provisionalRatio} |`
|
|
2538
|
+
);
|
|
2539
|
+
}
|
|
2540
|
+
}
|
|
2541
|
+
lines.push("");
|
|
2542
|
+
lines.push("## Person Tier Ratio");
|
|
2543
|
+
lines.push("");
|
|
2544
|
+
if (report.metrics.tierRatio.length === 0) {
|
|
2545
|
+
lines.push("_No committer activity in range._");
|
|
2546
|
+
} else {
|
|
2547
|
+
lines.push("| Project | Tier | People | Commits | Issues |");
|
|
2548
|
+
lines.push("|---|---|---|---|---|");
|
|
2549
|
+
for (const r of report.metrics.tierRatio) {
|
|
2550
|
+
lines.push(`| ${r.projectId} | ${r.tier} | ${r.personCount} | ${r.tierCommits} | ${r.tierIssues} |`);
|
|
2551
|
+
}
|
|
2552
|
+
}
|
|
2553
|
+
lines.push("");
|
|
2554
|
+
if (report.baseline !== void 0) {
|
|
2555
|
+
const b = report.baseline;
|
|
2556
|
+
lines.push("## Before / After Comparison");
|
|
2557
|
+
lines.push("");
|
|
2558
|
+
lines.push(`- Baseline (before): ${fmtPeriod(b.period)}`);
|
|
2559
|
+
lines.push(`- Current (after): ${fmtPeriod(report.period)}`);
|
|
2560
|
+
lines.push("");
|
|
2561
|
+
lines.push("| Metric | Before | After | \u0394 |");
|
|
2562
|
+
lines.push("|---|---|---|---|");
|
|
2563
|
+
const before = b.metrics.summary;
|
|
2564
|
+
const after = report.metrics.summary;
|
|
2565
|
+
const c = b.comparison;
|
|
2566
|
+
lines.push(`| Issues | ${before.issueCount} | ${after.issueCount} | ${signed(c.issueCount)} |`);
|
|
2567
|
+
lines.push(`| Sessions | ${before.sessionCount} | ${after.sessionCount} | ${signed(c.sessionCount)} |`);
|
|
2568
|
+
lines.push(`| Total cost (USD) | ${before.totalCostUsd} | ${after.totalCostUsd} | ${signed(c.totalCostUsd)} |`);
|
|
2569
|
+
lines.push(`| Avg leverage | ${before.avgLeverage} | ${after.avgLeverage} | ${signed(c.avgLeverage)} |`);
|
|
2570
|
+
lines.push(`| Avg commit rate | ${before.avgCommitRate} | ${after.avgCommitRate} | ${signed(c.avgCommitRate)} |`);
|
|
2571
|
+
lines.push(`| Avg CI pass rate | ${before.avgCiPassRate} | ${after.avgCiPassRate} | ${signed(c.avgCiPassRate)} |`);
|
|
2572
|
+
lines.push(`| Avg chain completeness | ${before.avgChainCompleteness} | ${after.avgChainCompleteness} | ${signed(c.avgChainCompleteness)} |`);
|
|
2573
|
+
lines.push(`| Session link rate | ${before.sessionLinkRate} | ${after.sessionLinkRate} | ${signed(c.sessionLinkRate)} |`);
|
|
2574
|
+
lines.push("");
|
|
2575
|
+
}
|
|
2576
|
+
return lines.join("\n");
|
|
2577
|
+
}
|
|
2578
|
+
|
|
1601
2579
|
// src/index.ts
|
|
1602
2580
|
function createPipeline(options = {}) {
|
|
1603
2581
|
const {
|
|
@@ -1682,6 +2660,9 @@ export {
|
|
|
1682
2660
|
NormalizationError,
|
|
1683
2661
|
validateRawEvent,
|
|
1684
2662
|
Normalizer,
|
|
2663
|
+
VENDOR_OTEL_SOURCE,
|
|
2664
|
+
VENDOR_OTEL_DEDUP_PREFIX,
|
|
2665
|
+
mapVendorOtelExport,
|
|
1685
2666
|
MemoryCacheStore,
|
|
1686
2667
|
SqliteCacheStore,
|
|
1687
2668
|
Correlator,
|
|
@@ -1696,8 +2677,17 @@ export {
|
|
|
1696
2677
|
recordTaskArtifact,
|
|
1697
2678
|
createCrossAxisRule,
|
|
1698
2679
|
createPromotionRule,
|
|
2680
|
+
WORKTREE_WINDOW_MS,
|
|
2681
|
+
worktreeRepoKey,
|
|
2682
|
+
createWorktreeRule,
|
|
2683
|
+
issueIdCacheKey,
|
|
2684
|
+
issueTypeCacheKey,
|
|
2685
|
+
createIssueAnchorRule,
|
|
1699
2686
|
Enricher,
|
|
1700
2687
|
createDefaultRules,
|
|
2688
|
+
defaultGitRunner,
|
|
2689
|
+
captureWorktree,
|
|
2690
|
+
worktreeAttributes,
|
|
1701
2691
|
DEFAULT_DB_PATH,
|
|
1702
2692
|
SqliteSink,
|
|
1703
2693
|
OtelEmitter,
|
|
@@ -1716,6 +2706,14 @@ export {
|
|
|
1716
2706
|
emitQualityGateResult,
|
|
1717
2707
|
extractPrUrl,
|
|
1718
2708
|
emitPromotionPr,
|
|
2709
|
+
parseIssueIdFromText,
|
|
2710
|
+
buildGithubPromotionAttributes,
|
|
2711
|
+
githubPromotionEventType,
|
|
2712
|
+
OUTCOME_CHAIN_STAGES,
|
|
2713
|
+
analyzeOutcomeChain,
|
|
2714
|
+
generateDiagnosticReport,
|
|
2715
|
+
renderReportJson,
|
|
2716
|
+
renderReportMarkdown,
|
|
1719
2717
|
createPipeline
|
|
1720
2718
|
};
|
|
1721
|
-
//# sourceMappingURL=chunk-
|
|
2719
|
+
//# sourceMappingURL=chunk-3DXZNA3E.js.map
|