tracemeld 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/build/analysis/bottleneck.d.ts +23 -0
  2. package/build/analysis/bottleneck.js +55 -0
  3. package/build/analysis/bottleneck.js.map +1 -0
  4. package/build/analysis/explain.d.ts +38 -0
  5. package/build/analysis/explain.js +139 -0
  6. package/build/analysis/explain.js.map +1 -0
  7. package/build/analysis/hotpaths.d.ts +19 -0
  8. package/build/analysis/hotpaths.js +55 -0
  9. package/build/analysis/hotpaths.js.map +1 -0
  10. package/build/analysis/hotspots.d.ts +24 -0
  11. package/build/analysis/hotspots.js +75 -0
  12. package/build/analysis/hotspots.js.map +1 -0
  13. package/build/analysis/query.d.ts +25 -0
  14. package/build/analysis/query.js +102 -0
  15. package/build/analysis/query.js.map +1 -0
  16. package/build/analysis/spinpaths.d.ts +17 -0
  17. package/build/analysis/spinpaths.js +48 -0
  18. package/build/analysis/spinpaths.js.map +1 -0
  19. package/build/analysis/starvations.d.ts +21 -0
  20. package/build/analysis/starvations.js +76 -0
  21. package/build/analysis/starvations.js.map +1 -0
  22. package/build/analysis/summary.d.ts +27 -0
  23. package/build/analysis/summary.js +130 -0
  24. package/build/analysis/summary.js.map +1 -0
  25. package/build/analysis/waste.d.ts +19 -0
  26. package/build/analysis/waste.js +33 -0
  27. package/build/analysis/waste.js.map +1 -0
  28. package/build/cli.d.ts +2 -0
  29. package/build/cli.js +4 -0
  30. package/build/cli.js.map +1 -0
  31. package/build/exporters/collapsed.d.ts +2 -0
  32. package/build/exporters/collapsed.js +34 -0
  33. package/build/exporters/collapsed.js.map +1 -0
  34. package/build/importers/chrome-trace.d.ts +2 -0
  35. package/build/importers/chrome-trace.js +146 -0
  36. package/build/importers/chrome-trace.js.map +1 -0
  37. package/build/importers/collapsed.d.ts +2 -0
  38. package/build/importers/collapsed.js +52 -0
  39. package/build/importers/collapsed.js.map +1 -0
  40. package/build/importers/detect.d.ts +2 -0
  41. package/build/importers/detect.js +60 -0
  42. package/build/importers/detect.js.map +1 -0
  43. package/build/importers/gecko.d.ts +2 -0
  44. package/build/importers/gecko.js +77 -0
  45. package/build/importers/gecko.js.map +1 -0
  46. package/build/importers/import.d.ts +11 -0
  47. package/build/importers/import.js +98 -0
  48. package/build/importers/import.js.map +1 -0
  49. package/build/importers/pprof.d.ts +2 -0
  50. package/build/importers/pprof.js +238 -0
  51. package/build/importers/pprof.js.map +1 -0
  52. package/build/importers/types.d.ts +6 -0
  53. package/build/importers/types.js +2 -0
  54. package/build/importers/types.js.map +1 -0
  55. package/build/index.d.ts +6 -0
  56. package/build/index.js +6 -0
  57. package/build/index.js.map +1 -0
  58. package/build/instrument/mark.d.ts +11 -0
  59. package/build/instrument/mark.js +13 -0
  60. package/build/instrument/mark.js.map +1 -0
  61. package/build/instrument/trace.d.ts +16 -0
  62. package/build/instrument/trace.js +107 -0
  63. package/build/instrument/trace.js.map +1 -0
  64. package/build/model/frame-table.d.ts +10 -0
  65. package/build/model/frame-table.js +27 -0
  66. package/build/model/frame-table.js.map +1 -0
  67. package/build/model/profile.d.ts +20 -0
  68. package/build/model/profile.js +89 -0
  69. package/build/model/profile.js.map +1 -0
  70. package/build/model/state.d.ts +21 -0
  71. package/build/model/state.js +59 -0
  72. package/build/model/state.js.map +1 -0
  73. package/build/model/types.d.ts +73 -0
  74. package/build/model/types.js +10 -0
  75. package/build/model/types.js.map +1 -0
  76. package/build/patterns/blind-edit.d.ts +3 -0
  77. package/build/patterns/blind-edit.js +119 -0
  78. package/build/patterns/blind-edit.js.map +1 -0
  79. package/build/patterns/redundant-read.d.ts +3 -0
  80. package/build/patterns/redundant-read.js +72 -0
  81. package/build/patterns/redundant-read.js.map +1 -0
  82. package/build/patterns/registry.d.ts +10 -0
  83. package/build/patterns/registry.js +27 -0
  84. package/build/patterns/registry.js.map +1 -0
  85. package/build/patterns/retry-loop.d.ts +3 -0
  86. package/build/patterns/retry-loop.js +57 -0
  87. package/build/patterns/retry-loop.js.map +1 -0
  88. package/build/patterns/types.d.ts +14 -0
  89. package/build/patterns/types.js +2 -0
  90. package/build/patterns/types.js.map +1 -0
  91. package/build/server.d.ts +3 -0
  92. package/build/server.js +247 -0
  93. package/build/server.js.map +1 -0
  94. package/package.json +47 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mark.js","sourceRoot":"","sources":["../../src/instrument/mark.ts"],"names":[],"mappings":"AAcA,MAAM,UAAU,UAAU,CAAC,KAAoB,EAAE,KAAgB;IAC/D,MAAM,MAAM,GAAG,KAAK,CAAC,YAAY,CAAC;IAClC,MAAM,QAAQ,GAAG,KAAK,CAAC,YAAY,EAAE,CAAC;IACtC,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAE7B,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,MAAM,EAAE;QAC9B,SAAS;QACT,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,QAAQ,EAAE,KAAK,CAAC,QAAQ,IAAI,MAAM;QAClC,IAAI,EAAE,KAAK,CAAC,IAAI;KACjB,CAAC,CAAC;IAEH,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC;AAC5C,CAAC"}
@@ -0,0 +1,16 @@
1
+ import type { ProfilerState } from '../model/state.js';
2
+ export interface TraceInput {
3
+ action: 'begin' | 'end';
4
+ kind: string;
5
+ name?: string;
6
+ cost?: Record<string, number>;
7
+ error?: string;
8
+ metadata?: Record<string, unknown>;
9
+ }
10
+ export interface TraceResult {
11
+ span_id: string;
12
+ depth: number;
13
+ elapsed_ms?: number;
14
+ parent_id?: string;
15
+ }
16
+ export declare function handleTrace(state: ProfilerState, input: TraceInput): TraceResult;
@@ -0,0 +1,107 @@
1
+ export function handleTrace(state, input) {
2
+ const laneId = state.activeLaneId;
3
+ if (input.action === 'begin') {
4
+ return handleBegin(state, laneId, input);
5
+ }
6
+ else {
7
+ return handleEnd(state, laneId, input);
8
+ }
9
+ }
10
+ function handleBegin(state, laneId, input) {
11
+ const frameName = input.name ? `${input.kind}:${input.name}` : input.kind;
12
+ const frameIdx = state.builder.frameTable.getOrInsert({ name: frameName });
13
+ const spanId = state.nextSpanId();
14
+ const parentId = state.currentSpanId(laneId);
15
+ const now = Date.now();
16
+ state.builder.addSpan(laneId, {
17
+ id: spanId,
18
+ frame_index: frameIdx,
19
+ parent_id: parentId,
20
+ start_time: now,
21
+ end_time: now, // will be updated on end
22
+ values: state.builder.emptyValues(),
23
+ args: input.metadata ? { ...input.metadata } : {},
24
+ children: [],
25
+ });
26
+ // Update parent's children list
27
+ if (parentId) {
28
+ const lane = state.builder.getLane(laneId);
29
+ const parent = lane?.spans.find((s) => s.id === parentId);
30
+ if (parent)
31
+ parent.children.push(spanId);
32
+ }
33
+ state.pushSpan(laneId, spanId);
34
+ return {
35
+ span_id: spanId,
36
+ depth: state.spanDepth(laneId),
37
+ parent_id: parentId ?? undefined,
38
+ };
39
+ }
40
+ function handleEnd(state, laneId, input) {
41
+ const lane = state.builder.getLane(laneId);
42
+ if (!lane)
43
+ return { span_id: '', depth: 0 };
44
+ const currentId = state.currentSpanId(laneId);
45
+ if (!currentId)
46
+ return { span_id: '', depth: 0 };
47
+ // Find the span matching this kind
48
+ const targetKind = input.name ? `${input.kind}:${input.name}` : input.kind;
49
+ // Check if current stack top matches
50
+ const topSpan = lane.spans.find((s) => s.id === currentId);
51
+ if (!topSpan)
52
+ return { span_id: '', depth: 0 };
53
+ const topFrameName = state.builder.profile.frames[topSpan.frame_index]?.name;
54
+ if (topFrameName !== targetKind && !topFrameName.startsWith(`${input.kind}:`)) {
55
+ // Mismatch: auto-close spans until we find a match or exhaust the stack
56
+ autoCloseUntilMatch(state, laneId, input.kind);
57
+ }
58
+ // Now close the current top
59
+ const spanId = state.currentSpanId(laneId);
60
+ if (!spanId)
61
+ return { span_id: '', depth: 0 };
62
+ const span = lane.spans.find((s) => s.id === spanId);
63
+ if (!span)
64
+ return { span_id: '', depth: 0 };
65
+ const now = Date.now();
66
+ span.end_time = now;
67
+ const elapsed = now - span.start_time;
68
+ if (input.cost) {
69
+ state.builder.mergeCost(span.values, input.cost);
70
+ }
71
+ if (input.error) {
72
+ span.error = input.error;
73
+ }
74
+ if (input.metadata) {
75
+ Object.assign(span.args, input.metadata);
76
+ }
77
+ state.popSpan(laneId);
78
+ return {
79
+ span_id: span.id,
80
+ depth: state.spanDepth(laneId),
81
+ elapsed_ms: elapsed,
82
+ };
83
+ }
84
+ function autoCloseUntilMatch(state, laneId, kind) {
85
+ const lane = state.builder.getLane(laneId);
86
+ if (!lane)
87
+ return;
88
+ // Close spans from the top until we find one matching the kind
89
+ let safety = 100;
90
+ while (safety-- > 0) {
91
+ const topId = state.currentSpanId(laneId);
92
+ if (!topId)
93
+ break;
94
+ const topSpan = lane.spans.find((s) => s.id === topId);
95
+ if (!topSpan)
96
+ break;
97
+ const frameName = state.builder.profile.frames[topSpan.frame_index]?.name ?? '';
98
+ if (frameName === kind || frameName.startsWith(`${kind}:`)) {
99
+ break; // Found the match, stop auto-closing
100
+ }
101
+ // Auto-close this span
102
+ topSpan.end_time = Date.now();
103
+ topSpan.args['auto_closed'] = true;
104
+ state.popSpan(laneId);
105
+ }
106
+ }
107
+ //# sourceMappingURL=trace.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"trace.js","sourceRoot":"","sources":["../../src/instrument/trace.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import type { Frame } from './types.js';
2
+ export declare class FrameTable {
3
+ private _frames;
4
+ private _index;
5
+ private key;
6
+ getOrInsert(frame: Frame): number;
7
+ get(index: number): Frame | undefined;
8
+ get frames(): readonly Frame[];
9
+ get length(): number;
10
+ }
@@ -0,0 +1,27 @@
1
+ export class FrameTable {
2
+ _frames = [];
3
+ _index = new Map();
4
+ key(frame) {
5
+ return `${frame.name}\0${frame.file ?? ''}\0${frame.line ?? ''}\0${frame.col ?? ''}\0${frame.category_index ?? ''}`;
6
+ }
7
+ getOrInsert(frame) {
8
+ const k = this.key(frame);
9
+ const existing = this._index.get(k);
10
+ if (existing !== undefined)
11
+ return existing;
12
+ const idx = this._frames.length;
13
+ this._frames.push({ ...frame });
14
+ this._index.set(k, idx);
15
+ return idx;
16
+ }
17
+ get(index) {
18
+ return this._frames[index];
19
+ }
20
+ get frames() {
21
+ return this._frames;
22
+ }
23
+ get length() {
24
+ return this._frames.length;
25
+ }
26
+ }
27
+ //# sourceMappingURL=frame-table.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"frame-table.js","sourceRoot":"","sources":["../../src/model/frame-table.ts"],"names":[],"mappings":"AAGA,MAAM,OAAO,UAAU;IACb,OAAO,GAAY,EAAE,CAAC;IACtB,MAAM,GAAG,IAAI,GAAG,EAAkB,CAAC;IAEnC,GAAG,CAAC,KAAY;QACtB,OAAO,GAAG,KAAK,CAAC,IAAI,KAAK,KAAK,CAAC,IAAI,IAAI,EAAE,KAAK,KAAK,CAAC,IAAI,IAAI,EAAE,KAAK,KAAK,CAAC,GAAG,IAAI,EAAE,KAAK,KAAK,CAAC,cAAc,IAAI,EAAE,EAAE,CAAC;IACtH,CAAC;IAED,WAAW,CAAC,KAAY;QACtB,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QAC1B,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QACpC,IAAI,QAAQ,KAAK,SAAS;YAAE,OAAO,QAAQ,CAAC;QAE5C,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC;QAChC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,GAAG,KAAK,EAAE,CAAC,CAAC;QAChC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;QACxB,OAAO,GAAG,CAAC;IACb,CAAC;IAED,GAAG,CAAC,KAAa;QACf,OAAO,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IAC7B,CAAC;IAED,IAAI,MAAM;QACR,OAAO,IAAI,CAAC,OAAO,CAAC;IACtB,CAAC;IAED,IAAI,MAAM;QACR,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC;IAC7B,CAAC;CACF"}
@@ -0,0 +1,20 @@
1
+ import type { Profile, ValueType, Lane, LaneKind, Span, Sample, Marker } from './types.js';
2
+ import { FrameTable } from './frame-table.js';
3
+ export declare class ProfileBuilder {
4
+ readonly profile: Profile;
5
+ readonly frameTable: FrameTable;
6
+ private _valueTypeIndex;
7
+ constructor(name: string, valueTypes?: ValueType[]);
8
+ addLane(id: string, kind?: LaneKind): Lane;
9
+ getLane(id: string): Lane | undefined;
10
+ addSpan(laneId: string, span: Span): Span;
11
+ addSample(laneId: string, sample: Sample): Sample;
12
+ addMarker(laneId: string, marker: Marker): Marker;
13
+ valueTypeIndex(key: string): number;
14
+ /** Add a value type if it doesn't already exist. Returns the index. */
15
+ addValueType(vt: ValueType): number;
16
+ /** Build a zero-filled values array for the current value_types. */
17
+ emptyValues(): number[];
18
+ /** Merge a cost record into a values array. */
19
+ mergeCost(values: number[], cost: Record<string, number>): void;
20
+ }
@@ -0,0 +1,89 @@
1
+ import { LLM_VALUE_TYPES } from './types.js';
2
+ import { FrameTable } from './frame-table.js';
3
+ export class ProfileBuilder {
4
+ profile;
5
+ frameTable;
6
+ _valueTypeIndex;
7
+ constructor(name, valueTypes) {
8
+ const vt = valueTypes ?? [...LLM_VALUE_TYPES];
9
+ this.frameTable = new FrameTable();
10
+ this._valueTypeIndex = new Map();
11
+ for (let i = 0; i < vt.length; i++) {
12
+ this._valueTypeIndex.set(vt[i].key, i);
13
+ }
14
+ this.profile = {
15
+ id: crypto.randomUUID(),
16
+ name,
17
+ created_at: Date.now(),
18
+ value_types: vt,
19
+ categories: [],
20
+ frames: this.frameTable.frames,
21
+ lanes: [],
22
+ metadata: {},
23
+ };
24
+ // Create default main lane
25
+ this.addLane('main', 'main');
26
+ }
27
+ addLane(id, kind = 'custom') {
28
+ const lane = {
29
+ id,
30
+ name: id,
31
+ kind,
32
+ samples: [],
33
+ spans: [],
34
+ markers: [],
35
+ };
36
+ this.profile.lanes.push(lane);
37
+ return lane;
38
+ }
39
+ getLane(id) {
40
+ return this.profile.lanes.find((l) => l.id === id);
41
+ }
42
+ addSpan(laneId, span) {
43
+ const lane = this.getLane(laneId);
44
+ if (!lane)
45
+ throw new Error(`Lane not found: ${laneId}`);
46
+ lane.spans.push(span);
47
+ return span;
48
+ }
49
+ addSample(laneId, sample) {
50
+ const lane = this.getLane(laneId);
51
+ if (!lane)
52
+ throw new Error(`Lane not found: ${laneId}`);
53
+ lane.samples.push(sample);
54
+ return sample;
55
+ }
56
+ addMarker(laneId, marker) {
57
+ const lane = this.getLane(laneId);
58
+ if (!lane)
59
+ throw new Error(`Lane not found: ${laneId}`);
60
+ lane.markers.push(marker);
61
+ return marker;
62
+ }
63
+ valueTypeIndex(key) {
64
+ return this._valueTypeIndex.get(key) ?? -1;
65
+ }
66
+ /** Add a value type if it doesn't already exist. Returns the index. */
67
+ addValueType(vt) {
68
+ const existing = this.valueTypeIndex(vt.key);
69
+ if (existing >= 0)
70
+ return existing;
71
+ const idx = this.profile.value_types.length;
72
+ this.profile.value_types.push(vt);
73
+ this._valueTypeIndex.set(vt.key, idx);
74
+ return idx;
75
+ }
76
+ /** Build a zero-filled values array for the current value_types. */
77
+ emptyValues() {
78
+ return new Array(this.profile.value_types.length).fill(0);
79
+ }
80
+ /** Merge a cost record into a values array. */
81
+ mergeCost(values, cost) {
82
+ for (const [key, val] of Object.entries(cost)) {
83
+ const idx = this.valueTypeIndex(key);
84
+ if (idx >= 0)
85
+ values[idx] += val;
86
+ }
87
+ }
88
+ }
89
+ //# sourceMappingURL=profile.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"profile.js","sourceRoot":"","sources":["../../src/model/profile.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ import { ProfileBuilder } from './profile.js';
2
+ import type { DetectedPattern } from './types.js';
3
+ import { PatternRegistry } from '../patterns/registry.js';
4
+ export declare class ProfilerState {
5
+ readonly builder: ProfileBuilder;
6
+ readonly registry: PatternRegistry;
7
+ readonly imported: Map<string, ProfileBuilder>;
8
+ private spanStacks;
9
+ activeLaneId: string;
10
+ patternCache: DetectedPattern[] | null;
11
+ private _nextSpanId;
12
+ private _nextMarkerId;
13
+ constructor();
14
+ nextSpanId(): string;
15
+ nextMarkerId(): string;
16
+ pushSpan(laneId: string, spanId: string): void;
17
+ popSpan(laneId: string): string | null;
18
+ currentSpanId(laneId: string): string | null;
19
+ spanDepth(laneId: string): number;
20
+ invalidatePatternCache(): void;
21
+ }
@@ -0,0 +1,59 @@
1
+ // src/model/state.ts
2
+ import { ProfileBuilder } from './profile.js';
3
+ import { PatternRegistry } from '../patterns/registry.js';
4
+ import { detectRetryLoop } from '../patterns/retry-loop.js';
5
+ import { detectRedundantRead } from '../patterns/redundant-read.js';
6
+ import { detectBlindEdit } from '../patterns/blind-edit.js';
7
+ export class ProfilerState {
8
+ builder;
9
+ registry;
10
+ imported = new Map();
11
+ spanStacks = new Map();
12
+ activeLaneId = 'main';
13
+ patternCache = null;
14
+ _nextSpanId = 0;
15
+ _nextMarkerId = 0;
16
+ constructor() {
17
+ this.builder = new ProfileBuilder('session');
18
+ this.registry = new PatternRegistry();
19
+ this.registry.register(detectRetryLoop);
20
+ this.registry.register(detectRedundantRead);
21
+ this.registry.register(detectBlindEdit);
22
+ }
23
+ nextSpanId() {
24
+ return `s${this._nextSpanId++}`;
25
+ }
26
+ nextMarkerId() {
27
+ return `m${this._nextMarkerId++}`;
28
+ }
29
+ pushSpan(laneId, spanId) {
30
+ let stack = this.spanStacks.get(laneId);
31
+ if (!stack) {
32
+ stack = [];
33
+ this.spanStacks.set(laneId, stack);
34
+ }
35
+ stack.push(spanId);
36
+ this.invalidatePatternCache();
37
+ }
38
+ popSpan(laneId) {
39
+ const stack = this.spanStacks.get(laneId);
40
+ if (!stack || stack.length === 0)
41
+ return null;
42
+ this.invalidatePatternCache();
43
+ return stack.pop() ?? null;
44
+ }
45
+ currentSpanId(laneId) {
46
+ const stack = this.spanStacks.get(laneId);
47
+ if (!stack || stack.length === 0)
48
+ return null;
49
+ return stack[stack.length - 1] ?? null;
50
+ }
51
+ spanDepth(laneId) {
52
+ return this.spanStacks.get(laneId)?.length ?? 0;
53
+ }
54
+ invalidatePatternCache() {
55
+ this.patternCache = null;
56
+ this.registry.invalidate();
57
+ }
58
+ }
59
+ //# sourceMappingURL=state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state.js","sourceRoot":"","sources":["../../src/model/state.ts"],"names":[],"mappings":"AAAA,qBAAqB;AACrB,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAE9C,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAC5D,OAAO,EAAE,mBAAmB,EAAE,MAAM,+BAA+B,CAAC;AACpE,OAAO,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAE5D,MAAM,OAAO,aAAa;IACf,OAAO,CAAiB;IACxB,QAAQ,CAAkB;IAC1B,QAAQ,GAAG,IAAI,GAAG,EAA0B,CAAC;IAC9C,UAAU,GAAG,IAAI,GAAG,EAAoB,CAAC;IACjD,YAAY,GAAG,MAAM,CAAC;IACtB,YAAY,GAA6B,IAAI,CAAC;IACtC,WAAW,GAAG,CAAC,CAAC;IAChB,aAAa,GAAG,CAAC,CAAC;IAE1B;QACE,IAAI,CAAC,OAAO,GAAG,IAAI,cAAc,CAAC,SAAS,CAAC,CAAC;QAC7C,IAAI,CAAC,QAAQ,GAAG,IAAI,eAAe,EAAE,CAAC;QACtC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,eAAe,CAAC,CAAC;QACxC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,mBAAmB,CAAC,CAAC;QAC5C,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,eAAe,CAAC,CAAC;IAC1C,CAAC;IAED,UAAU;QACR,OAAO,IAAI,IAAI,CAAC,WAAW,EAAE,EAAE,CAAC;IAClC,CAAC;IAED,YAAY;QACV,OAAO,IAAI,IAAI,CAAC,aAAa,EAAE,EAAE,CAAC;IACpC,CAAC;IAED,QAAQ,CAAC,MAAc,EAAE,MAAc;QACrC,IAAI,KAAK,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACxC,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,KAAK,GAAG,EAAE,CAAC;YACX,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;QACrC,CAAC;QACD,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACnB,IAAI,CAAC,sBAAsB,EAAE,CAAC;IAChC,CAAC;IAED,OAAO,CAAC,MAAc;QACpB,MAAM,KAAK,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QAC1C,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC;QAC9C,IAAI,CAAC,sBAAsB,EAAE,CAAC;QAC9B,OAAO,KAAK,CAAC,GAAG,EAAE,IAAI,IAAI,CAAC;IAC7B,CAAC;IAED,aAAa,CAAC,MAAc;QAC1B,MAAM,KAAK,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QAC1C,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC;QAC9C,OAAO,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,IAAI,IAAI,CAAC;IACzC,CAAC;IAED,SAAS,CAAC,MAAc;QACtB,OAAO,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC,CAAC;IAClD,CAAC;IAED,sBAAsB;QACpB,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC;QACzB,IAAI,CAAC,QAAQ,CAAC,UAAU,EAAE,CAAC;IAC7B,CAAC;CACF"}
@@ -0,0 +1,73 @@
1
+ export type Unit = 'nanoseconds' | 'microseconds' | 'milliseconds' | 'seconds' | 'bytes' | 'none';
2
+ export interface ValueType {
3
+ key: string;
4
+ unit: Unit;
5
+ description?: string;
6
+ }
7
+ export interface Category {
8
+ name: string;
9
+ color?: string;
10
+ subcategories?: string[];
11
+ }
12
+ export interface Frame {
13
+ name: string;
14
+ file?: string;
15
+ line?: number;
16
+ col?: number;
17
+ category_index?: number;
18
+ metadata?: Record<string, unknown>;
19
+ }
20
+ export interface Sample {
21
+ timestamp: number | null;
22
+ stack: number[];
23
+ values: number[];
24
+ labels?: Record<string, string | number>[];
25
+ }
26
+ export interface Span {
27
+ id: string;
28
+ frame_index: number;
29
+ parent_id: string | null;
30
+ start_time: number;
31
+ end_time: number;
32
+ values: number[];
33
+ args: Record<string, unknown>;
34
+ error?: string;
35
+ children: string[];
36
+ }
37
+ export interface Marker {
38
+ timestamp: number;
39
+ name: string;
40
+ category_index?: number;
41
+ severity?: 'info' | 'warning' | 'error';
42
+ data?: Record<string, unknown>;
43
+ end_time?: number;
44
+ }
45
+ export type LaneKind = 'main' | 'worker' | 'agent' | 'subprocess' | 'custom';
46
+ export interface Lane {
47
+ id: string;
48
+ name: string;
49
+ pid?: number;
50
+ tid?: number;
51
+ kind: LaneKind;
52
+ samples: Sample[];
53
+ spans: Span[];
54
+ markers: Marker[];
55
+ }
56
+ export interface Profile {
57
+ id: string;
58
+ name: string;
59
+ created_at: number;
60
+ value_types: ValueType[];
61
+ categories: Category[];
62
+ frames: Frame[];
63
+ lanes: Lane[];
64
+ metadata: Record<string, unknown>;
65
+ }
66
+ export interface DetectedPattern {
67
+ name: string;
68
+ description: string;
69
+ severity: 'info' | 'warning' | 'critical';
70
+ evidence: Record<string, unknown>;
71
+ span_ids?: string[];
72
+ }
73
+ export declare const LLM_VALUE_TYPES: ValueType[];
@@ -0,0 +1,10 @@
1
+ // src/model/types.ts
2
+ export const LLM_VALUE_TYPES = [
3
+ { key: 'wall_ms', unit: 'milliseconds', description: 'Wall-clock duration' },
4
+ { key: 'input_tokens', unit: 'none', description: 'Input/prompt tokens consumed' },
5
+ { key: 'output_tokens', unit: 'none', description: 'Output/completion tokens generated' },
6
+ { key: 'cost_usd', unit: 'none', description: 'Estimated dollar cost' },
7
+ { key: 'bytes_read', unit: 'bytes', description: 'Bytes read from disk/network' },
8
+ { key: 'bytes_written', unit: 'bytes', description: 'Bytes written to disk/network' },
9
+ ];
10
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/model/types.ts"],"names":[],"mappings":"AAAA,qBAAqB;AA2FrB,MAAM,CAAC,MAAM,eAAe,GAAgB;IAC1C,EAAE,GAAG,EAAE,SAAS,EAAE,IAAI,EAAE,cAAc,EAAE,WAAW,EAAE,qBAAqB,EAAE;IAC5E,EAAE,GAAG,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,8BAA8B,EAAE;IAClF,EAAE,GAAG,EAAE,eAAe,EAAE,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,oCAAoC,EAAE;IACzF,EAAE,GAAG,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,uBAAuB,EAAE;IACvE,EAAE,GAAG,EAAE,YAAY,EAAE,IAAI,EAAE,OAAO,EAAE,WAAW,EAAE,8BAA8B,EAAE;IACjF,EAAE,GAAG,EAAE,eAAe,EAAE,IAAI,EAAE,OAAO,EAAE,WAAW,EAAE,+BAA+B,EAAE;CACtF,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { Profile } from '../model/types.js';
2
+ import type { PatternMatch } from './types.js';
3
+ export declare function detectBlindEdit(profile: Profile): PatternMatch[];
@@ -0,0 +1,119 @@
1
+ import { getAllSpans, extractKind } from '../analysis/query.js';
2
+ export function detectBlindEdit(profile) {
3
+ const matches = [];
4
+ const allSpans = getAllSpans(profile);
5
+ // Find turn spans in order using extractKind
6
+ const turnSpans = allSpans
7
+ .filter((s) => {
8
+ const name = profile.frames[s.frame_index]?.name ?? '';
9
+ return extractKind(name) === 'turn';
10
+ })
11
+ .sort((a, b) => a.start_time - b.start_time);
12
+ // Build a set of files read per turn by walking full subtree
13
+ const filesReadByTurn = new Map();
14
+ for (const turn of turnSpans) {
15
+ const filesRead = new Set();
16
+ collectFileOpsDeep(profile, turn, allSpans, 'file_read', filesRead);
17
+ filesReadByTurn.set(turn.id, filesRead);
18
+ }
19
+ // Check each turn's writes against current + previous turn's reads
20
+ for (let i = 0; i < turnSpans.length; i++) {
21
+ const turn = turnSpans[i];
22
+ const currentReads = filesReadByTurn.get(turn.id) ?? new Set();
23
+ const prevReads = i > 0
24
+ ? (filesReadByTurn.get(turnSpans[i - 1].id) ?? new Set())
25
+ : new Set();
26
+ const allReads = new Set([...currentReads, ...prevReads]);
27
+ // Find file_write spans in this turn's subtree
28
+ const writeSpans = getFileWriteSpansDeep(profile, turn, allSpans);
29
+ for (const writeSpan of writeSpans) {
30
+ const frameName = profile.frames[writeSpan.frame_index]?.name ?? '';
31
+ const file = frameName.includes(':')
32
+ ? frameName.substring(frameName.indexOf(':') + 1)
33
+ : '';
34
+ if (file && !allReads.has(file)) {
35
+ matches.push({
36
+ pattern: {
37
+ name: 'blind_edit',
38
+ description: `Edited '${file}' without reading it first`,
39
+ severity: 'warning',
40
+ evidence: { file },
41
+ },
42
+ span_ids: [writeSpan.id],
43
+ counterfactual_savings: {},
44
+ recommendation: 'Always read the current state of a file before editing it.',
45
+ });
46
+ }
47
+ }
48
+ }
49
+ // Handle case with no turn structure
50
+ if (turnSpans.length === 0) {
51
+ const filesRead = new Set();
52
+ const orderedSpans = [...allSpans].sort((a, b) => a.start_time - b.start_time);
53
+ for (const span of orderedSpans) {
54
+ const frameName = profile.frames[span.frame_index]?.name ?? '';
55
+ const kind = extractKind(frameName);
56
+ const detail = frameName.includes(':')
57
+ ? frameName.substring(frameName.indexOf(':') + 1)
58
+ : '';
59
+ if (kind === 'file_read' && detail) {
60
+ filesRead.add(detail);
61
+ }
62
+ else if (kind === 'file_write' && detail && !filesRead.has(detail)) {
63
+ matches.push({
64
+ pattern: {
65
+ name: 'blind_edit',
66
+ description: `Edited '${detail}' without reading it first`,
67
+ severity: 'warning',
68
+ evidence: { file: detail },
69
+ },
70
+ span_ids: [span.id],
71
+ counterfactual_savings: {},
72
+ recommendation: 'Always read the current state of a file before editing it.',
73
+ });
74
+ }
75
+ }
76
+ }
77
+ return matches;
78
+ }
79
+ /** Walk the full subtree collecting file paths for a given kind prefix. */
80
+ function collectFileOpsDeep(profile, parent, allSpans, kindPrefix, result) {
81
+ const stack = [...parent.children];
82
+ while (stack.length > 0) {
83
+ const id = stack.pop();
84
+ if (!id)
85
+ continue;
86
+ const span = allSpans.find((s) => s.id === id);
87
+ if (!span)
88
+ continue;
89
+ const frameName = profile.frames[span.frame_index]?.name ?? '';
90
+ const kind = extractKind(frameName);
91
+ const detail = frameName.includes(':')
92
+ ? frameName.substring(frameName.indexOf(':') + 1)
93
+ : '';
94
+ if (kind === kindPrefix && detail) {
95
+ result.add(detail);
96
+ }
97
+ stack.push(...span.children);
98
+ }
99
+ }
100
+ /** Walk the full subtree finding file_write spans. */
101
+ function getFileWriteSpansDeep(profile, parent, allSpans) {
102
+ const writes = [];
103
+ const stack = [...parent.children];
104
+ while (stack.length > 0) {
105
+ const id = stack.pop();
106
+ if (!id)
107
+ continue;
108
+ const span = allSpans.find((s) => s.id === id);
109
+ if (!span)
110
+ continue;
111
+ const frameName = profile.frames[span.frame_index]?.name ?? '';
112
+ if (extractKind(frameName) === 'file_write') {
113
+ writes.push(span);
114
+ }
115
+ stack.push(...span.children);
116
+ }
117
+ return writes;
118
+ }
119
+ //# sourceMappingURL=blind-edit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"blind-edit.js","sourceRoot":"","sources":["../../src/patterns/blind-edit.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { Profile } from '../model/types.js';
2
+ import type { PatternMatch } from './types.js';
3
+ export declare function detectRedundantRead(profile: Profile): PatternMatch[];