@aaac/observability 0.1.13 → 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/README.md +33 -0
- package/dist/{chunk-RUVM5AAO.js → chunk-3DXZNA3E.js} +1024 -15
- 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-RUVM5AAO.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) {
|
|
@@ -826,9 +1151,15 @@ var SqliteSink = class {
|
|
|
826
1151
|
UPDATE spans
|
|
827
1152
|
SET end_time = ?,
|
|
828
1153
|
duration_ns = (? - start_time),
|
|
829
|
-
status = 'closed'
|
|
1154
|
+
status = 'closed',
|
|
1155
|
+
attributes = json_patch(attributes, ?)
|
|
830
1156
|
WHERE span_id = ?
|
|
831
|
-
`).run(
|
|
1157
|
+
`).run(
|
|
1158
|
+
event.timeUnixNano,
|
|
1159
|
+
event.timeUnixNano,
|
|
1160
|
+
JSON.stringify(event.attributes),
|
|
1161
|
+
event.spanId
|
|
1162
|
+
);
|
|
832
1163
|
break;
|
|
833
1164
|
case "instant":
|
|
834
1165
|
this.db.prepare(`
|
|
@@ -851,6 +1182,11 @@ var SqliteSink = class {
|
|
|
851
1182
|
);
|
|
852
1183
|
break;
|
|
853
1184
|
case "event":
|
|
1185
|
+
this.db.prepare(`
|
|
1186
|
+
UPDATE spans
|
|
1187
|
+
SET attributes = json_patch(attributes, ?)
|
|
1188
|
+
WHERE span_id = ?
|
|
1189
|
+
`).run(JSON.stringify(event.attributes), event.spanId);
|
|
854
1190
|
break;
|
|
855
1191
|
}
|
|
856
1192
|
}
|
|
@@ -962,7 +1298,7 @@ var OtelEmitter = class {
|
|
|
962
1298
|
{
|
|
963
1299
|
kind: SpanKind.INTERNAL,
|
|
964
1300
|
startTime: startMs,
|
|
965
|
-
attributes:
|
|
1301
|
+
attributes: convertAttributes2(mapped.attributes),
|
|
966
1302
|
links: mapped.links.map((l) => ({
|
|
967
1303
|
context: {
|
|
968
1304
|
traceId: l.targetTraceId ?? mapped.traceId,
|
|
@@ -977,7 +1313,7 @@ var OtelEmitter = class {
|
|
|
977
1313
|
for (const ev of mapped.spanEvents) {
|
|
978
1314
|
otelSpan.addEvent(
|
|
979
1315
|
ev.name,
|
|
980
|
-
|
|
1316
|
+
convertAttributes2(ev.attributes),
|
|
981
1317
|
Number(ev.timeUnixNano / 1000000n)
|
|
982
1318
|
);
|
|
983
1319
|
}
|
|
@@ -995,7 +1331,7 @@ var OtelEmitter = class {
|
|
|
995
1331
|
return trace.setSpanContext(ROOT_CONTEXT, spanContext);
|
|
996
1332
|
}
|
|
997
1333
|
};
|
|
998
|
-
function
|
|
1334
|
+
function convertAttributes2(attrs) {
|
|
999
1335
|
return attrs;
|
|
1000
1336
|
}
|
|
1001
1337
|
|
|
@@ -1227,8 +1563,9 @@ function parseEventRow(row) {
|
|
|
1227
1563
|
sessionId: row.session_id ?? void 0,
|
|
1228
1564
|
taskId: row.task_id ?? void 0,
|
|
1229
1565
|
attributes: safeParseJson(row.attributes),
|
|
1230
|
-
links: []
|
|
1566
|
+
links: [],
|
|
1231
1567
|
// links are stored in canonical_links, not inline on events
|
|
1568
|
+
...row.dedup_key ? { dedupKey: row.dedup_key } : {}
|
|
1232
1569
|
};
|
|
1233
1570
|
}
|
|
1234
1571
|
function parseLinkRow(row) {
|
|
@@ -1587,6 +1924,658 @@ function emitPromotionPr(collector, options) {
|
|
|
1587
1924
|
return spanId;
|
|
1588
1925
|
}
|
|
1589
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
|
+
|
|
1590
2579
|
// src/index.ts
|
|
1591
2580
|
function createPipeline(options = {}) {
|
|
1592
2581
|
const {
|
|
@@ -1671,6 +2660,9 @@ export {
|
|
|
1671
2660
|
NormalizationError,
|
|
1672
2661
|
validateRawEvent,
|
|
1673
2662
|
Normalizer,
|
|
2663
|
+
VENDOR_OTEL_SOURCE,
|
|
2664
|
+
VENDOR_OTEL_DEDUP_PREFIX,
|
|
2665
|
+
mapVendorOtelExport,
|
|
1674
2666
|
MemoryCacheStore,
|
|
1675
2667
|
SqliteCacheStore,
|
|
1676
2668
|
Correlator,
|
|
@@ -1685,8 +2677,17 @@ export {
|
|
|
1685
2677
|
recordTaskArtifact,
|
|
1686
2678
|
createCrossAxisRule,
|
|
1687
2679
|
createPromotionRule,
|
|
2680
|
+
WORKTREE_WINDOW_MS,
|
|
2681
|
+
worktreeRepoKey,
|
|
2682
|
+
createWorktreeRule,
|
|
2683
|
+
issueIdCacheKey,
|
|
2684
|
+
issueTypeCacheKey,
|
|
2685
|
+
createIssueAnchorRule,
|
|
1688
2686
|
Enricher,
|
|
1689
2687
|
createDefaultRules,
|
|
2688
|
+
defaultGitRunner,
|
|
2689
|
+
captureWorktree,
|
|
2690
|
+
worktreeAttributes,
|
|
1690
2691
|
DEFAULT_DB_PATH,
|
|
1691
2692
|
SqliteSink,
|
|
1692
2693
|
OtelEmitter,
|
|
@@ -1705,6 +2706,14 @@ export {
|
|
|
1705
2706
|
emitQualityGateResult,
|
|
1706
2707
|
extractPrUrl,
|
|
1707
2708
|
emitPromotionPr,
|
|
2709
|
+
parseIssueIdFromText,
|
|
2710
|
+
buildGithubPromotionAttributes,
|
|
2711
|
+
githubPromotionEventType,
|
|
2712
|
+
OUTCOME_CHAIN_STAGES,
|
|
2713
|
+
analyzeOutcomeChain,
|
|
2714
|
+
generateDiagnosticReport,
|
|
2715
|
+
renderReportJson,
|
|
2716
|
+
renderReportMarkdown,
|
|
1708
2717
|
createPipeline
|
|
1709
2718
|
};
|
|
1710
|
-
//# sourceMappingURL=chunk-
|
|
2719
|
+
//# sourceMappingURL=chunk-3DXZNA3E.js.map
|