@tracecode/harness 0.6.1 → 0.6.5

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/CHANGELOG.md CHANGED
@@ -4,6 +4,29 @@ All notable changes to this project are documented here.
4
4
 
5
5
  This repo uses Git tags as release boundaries. Version notes below summarize what shipped in each tagged release.
6
6
 
7
+ ## [0.6.5] - 2026-04-26
8
+
9
+ ### Added
10
+
11
+ - Added Java visualizer harness support for public runtime trace metadata used by the app visualization path.
12
+
13
+ ### Fixed
14
+
15
+ - Improved Java trace bookkeeping parity so emitted trace steps line up with the shared runtime contract.
16
+ - Fixed Python runtime access attribution regressions caught while validating cross-language visualization parity.
17
+ - Preserved Java script-mode tracing behavior through the updated harness assets.
18
+
19
+ ### Notes
20
+
21
+ - `0.6.5` skips `0.6.4` intentionally because this release bundles the larger Java visualization compatibility update.
22
+
23
+ ## [0.6.2] - 2026-04-23
24
+
25
+ ### Added
26
+
27
+ - Enabled Java script-style browser execution using an empty function name, `executionStyle: "function"`, and the top-level `result` variable convention.
28
+ - Added direct Java worker regression coverage for script-mode normalization, result serialization, trace function mapping, and invalid style rejection.
29
+
7
30
  ## [0.6.1] - 2026-04-23
8
31
 
9
32
  ### Fixed
package/README.md CHANGED
@@ -13,7 +13,7 @@ This package provides an execution and tracing runtime for browser applications.
13
13
  It includes:
14
14
 
15
15
  - browser-hosted execution for Python, JavaScript, and TypeScript
16
- - an experimental browser-local Java 17 lane for `function`, `solution-method`, `ops-class`, and `interviewMode` execution
16
+ - an experimental browser-local Java 17 lane for `function`, `solution-method`, `ops-class`, `script`, and `interviewMode` execution
17
17
  - trace capture and normalized runtime contracts
18
18
  - browser worker assets and asset sync tooling
19
19
  - runtime-side structural annotations such as object kinds and hash/map payloads
@@ -233,8 +233,8 @@ Current language status:
233
233
 
234
234
  Current Java scope:
235
235
 
236
- - supported: `function`, `solution-method`, `ops-class`, `interviewMode`, tracing, compile diagnostics, structural visualization payloads
237
- - not yet supported: `script`
236
+ - supported: `function`, `solution-method`, `ops-class`, `script`, `interviewMode`, tracing, compile diagnostics, structural visualization payloads
237
+ - script mode uses an empty function name with `executionStyle: "function"` and reads the top-level `result` variable
238
238
 
239
239
  ## Example Consumer
240
240
 
@@ -279,6 +279,7 @@ pnpm generate:python-harness
279
279
 
280
280
  This repo uses explicit versioned release boundaries.
281
281
 
282
+ - `0.6.2` enables experimental Java script-style execution using the empty function name and top-level `result` convention
282
283
  - `0.6.1` resolves Dependabot-reported vulnerabilities in Vite, DOMPurify, and Picomatch dependency paths
283
284
  - `0.6.0` adds an experimental browser-local Java 17 runtime lane for `function`, `solution-method`, `ops-class`, and `interviewMode` execution, plus public asset packaging and browser smoke coverage for Java
284
285
  - `0.5.0` improves JavaScript tree/list input hydration, fixes sparse tree deserialization, and trims GitHub CI to the non-browser verification set
package/dist/browser.cjs CHANGED
@@ -860,7 +860,7 @@ var JAVA_RUNTIME_PROFILE = {
860
860
  function: true,
861
861
  solutionMethod: true,
862
862
  opsClass: true,
863
- script: false,
863
+ script: true,
864
864
  interviewMode: true
865
865
  },
866
866
  timeouts: {
@@ -913,9 +913,9 @@ var JAVA_RUNTIME_PROFILE = {
913
913
  }
914
914
  },
915
915
  notes: [
916
- "Java currently supports the browser-local Java 17 lane for function, solution-method, and ops-class execution.",
917
- "Interview-mode Java reuses the same browser-local execution path and remains experimental in the first harness slice.",
918
- "Script-style Java is not enabled yet."
916
+ "Java currently supports the browser-local Java 17 lane for function, solution-method, ops-class, and script-style execution.",
917
+ "Interview-mode Java reuses the same browser-local execution path and remains experimental.",
918
+ 'Script-style Java uses an empty function name with executionStyle="function" and reads the top-level result variable.'
919
919
  ]
920
920
  };
921
921
  var LANGUAGE_RUNTIME_PROFILES = {
@@ -1223,6 +1223,16 @@ function parseScalar(raw) {
1223
1223
  }
1224
1224
  return raw;
1225
1225
  }
1226
+ function normalizeJavaSerializedResult(output) {
1227
+ if (typeof output !== "string") {
1228
+ return output;
1229
+ }
1230
+ try {
1231
+ return JSON.parse(output);
1232
+ } catch {
1233
+ return output;
1234
+ }
1235
+ }
1226
1236
  function parseKeyValuePairs(fragment) {
1227
1237
  const variables = {};
1228
1238
  const matches = Array.from(fragment.matchAll(/\b([A-Za-z_][A-Za-z0-9_.]*)=/g));
@@ -1403,10 +1413,29 @@ function parseAccessEvent(payload) {
1403
1413
  pathDepth: 1
1404
1414
  }];
1405
1415
  }
1416
+ const indexedMutatingCall = payload.match(/^mutate-indexed ([A-Za-z_][A-Za-z0-9_]*)\[(\d+)\] method=([A-Za-z_][A-Za-z0-9_]*)$/);
1417
+ if (indexedMutatingCall) {
1418
+ return [{
1419
+ variable: indexedMutatingCall[1],
1420
+ kind: "mutating-call",
1421
+ indices: [Number.parseInt(indexedMutatingCall[2], 10)],
1422
+ method: indexedMutatingCall[3],
1423
+ pathDepth: 1
1424
+ }];
1425
+ }
1426
+ const keyedCall = payload.match(/^keyed-call ([A-Za-z_][A-Za-z0-9_]*) method=([A-Za-z_][A-Za-z0-9_]*)(?:\s+.*)?$/);
1427
+ if (keyedCall) {
1428
+ return [{
1429
+ variable: keyedCall[1],
1430
+ kind: "mutating-call",
1431
+ method: keyedCall[2],
1432
+ pathDepth: 1
1433
+ }];
1434
+ }
1406
1435
  return void 0;
1407
1436
  }
1408
1437
  function parseStructureState(payload) {
1409
- const match = payload.match(/^state (linked-list|tree) ([A-Za-z_][A-Za-z0-9_]*)=(.+)$/);
1438
+ const match = payload.match(/^state (linked-list|tree|graph-adjacency) ([A-Za-z_][A-Za-z0-9_]*)=(.+)$/);
1410
1439
  if (!match) return null;
1411
1440
  return {
1412
1441
  structure: match[1],
@@ -1422,6 +1451,22 @@ function parseObjectState(payload) {
1422
1451
  visualization: JSON.parse(match[2])
1423
1452
  };
1424
1453
  }
1454
+ function parseMapState(payload) {
1455
+ const match = payload.match(/^map-state ([A-Za-z_][A-Za-z0-9_]*)=(.+)$/);
1456
+ if (!match) return null;
1457
+ return {
1458
+ variable: match[1],
1459
+ visualization: JSON.parse(match[2])
1460
+ };
1461
+ }
1462
+ function parseSetState(payload) {
1463
+ const match = payload.match(/^set-state ([A-Za-z_][A-Za-z0-9_]*)=(.+)$/);
1464
+ if (!match) return null;
1465
+ return {
1466
+ variable: match[1],
1467
+ visualization: JSON.parse(match[2])
1468
+ };
1469
+ }
1425
1470
  function parseObjectFieldEvent(payload) {
1426
1471
  const match = payload.match(/^(access|write) ([A-Za-z_][A-Za-z0-9_]*)\.([A-Za-z_][A-Za-z0-9_]*)=(.+)$/);
1427
1472
  if (!match) return null;
@@ -1431,6 +1476,9 @@ function parseObjectFieldEvent(payload) {
1431
1476
  value: parseScalar(match[4])
1432
1477
  };
1433
1478
  }
1479
+ function isArrayLengthAccessEvent(payload) {
1480
+ return /^access [A-Za-z_][A-Za-z0-9_]*\.length=\d+$/.test(payload);
1481
+ }
1434
1482
  function buildFieldVisualization(event) {
1435
1483
  return {
1436
1484
  objectKinds: {
@@ -1510,12 +1558,13 @@ function filterStructuredVariables(variables) {
1510
1558
  }
1511
1559
  return Object.fromEntries(entries);
1512
1560
  }
1513
- function appendJavaTraceStep(trace, step, pendingAccesses) {
1561
+ function appendJavaTraceStep(trace, step, pendingAccesses, options = {}) {
1514
1562
  const nextStep = pendingAccesses.length > 0 ? { ...step, accesses: [...pendingAccesses] } : step;
1515
1563
  pendingAccesses.length = 0;
1516
- if (!maybeMergeConsecutiveLineStep(trace, nextStep)) {
1517
- trace.push(nextStep);
1564
+ if (options.allowMerge !== false && maybeMergeConsecutiveLineStep(trace, nextStep)) {
1565
+ return;
1518
1566
  }
1567
+ trace.push(nextStep);
1519
1568
  }
1520
1569
  function mergeIntoPreviousMatchingLineStep(trace, line, currentFunction, currentCallStack, patch) {
1521
1570
  const candidate = trace.at(-1);
@@ -1549,11 +1598,14 @@ function eventsToRawTrace(events, sourceText) {
1549
1598
  const stack = [];
1550
1599
  const pendingAccesses = [];
1551
1600
  let currentFunction = "<module>";
1601
+ let previousRawLine = null;
1552
1602
  const lineRemap = buildLineRemap(sourceText);
1553
1603
  const declarationLines = buildMethodDeclarationLineSet(sourceText);
1554
1604
  for (const rawEvent of events) {
1555
1605
  if (rawEvent === "clear" || rawEvent === "reset") continue;
1556
1606
  const metadata = extractLineMetadata(rawEvent);
1607
+ const previousEventRawLine = previousRawLine;
1608
+ previousRawLine = metadata.line;
1557
1609
  const line = lineRemap?.get(metadata.line) ?? metadata.line;
1558
1610
  const payload = metadata.payload;
1559
1611
  const isDeclarationLine = declarationLines?.has(line) === true;
@@ -1561,28 +1613,31 @@ function eventsToRawTrace(events, sourceText) {
1561
1613
  const match = payload.match(/^call\s+(\S+)(?:\s+(.*))?$/);
1562
1614
  const functionName = match?.[1] ?? currentFunction;
1563
1615
  const argsFragment = match?.[2] ?? "";
1564
- Object.assign(variables, parseKeyValuePairs(argsFragment));
1616
+ const args = parseKeyValuePairs(argsFragment);
1617
+ Object.assign(variables, args);
1565
1618
  currentFunction = functionName || currentFunction;
1566
- stack.push({ function: currentFunction, line });
1619
+ stack.push({ function: currentFunction, line, args });
1567
1620
  appendJavaTraceStep(trace, {
1568
1621
  line,
1569
1622
  event: "call",
1570
1623
  function: currentFunction,
1571
1624
  variables: { ...variables },
1572
- callStack: stack.map((frame) => ({ function: frame.function, line: frame.line, args: {} }))
1625
+ callStack: stack.map((frame) => ({ function: frame.function, line: frame.line, args: { ...frame.args } }))
1573
1626
  }, pendingAccesses);
1574
1627
  continue;
1575
1628
  }
1576
1629
  if (payload.startsWith("return ")) {
1577
- const match = payload.match(/^return\s+(\S+)$/);
1630
+ const match = payload.match(/^return\s+(\S+)(?:\s+value=(.*))?$/);
1578
1631
  const functionName = match?.[1] ?? currentFunction;
1632
+ const returnValue = match?.[2] !== void 0 ? parseScalar(match[2].trim()) : void 0;
1579
1633
  const returnVariables = functionName === "<module>" ? { ...variables } : filterStructuredVariables(variables) ?? {};
1580
1634
  appendJavaTraceStep(trace, {
1581
1635
  line,
1582
1636
  event: "return",
1583
1637
  function: functionName || currentFunction,
1584
1638
  variables: returnVariables,
1585
- callStack: stack.map((frame) => ({ function: frame.function, line: frame.line, args: {} }))
1639
+ callStack: stack.map((frame) => ({ function: frame.function, line: frame.line, args: { ...frame.args } })),
1640
+ ...returnValue !== void 0 ? { returnValue } : {}
1586
1641
  }, pendingAccesses);
1587
1642
  stack.pop();
1588
1643
  currentFunction = stack[stack.length - 1]?.function ?? "<module>";
@@ -1597,8 +1652,8 @@ function eventsToRawTrace(events, sourceText) {
1597
1652
  event: "line",
1598
1653
  function: currentFunction,
1599
1654
  variables: { ...variables },
1600
- ...stack.length > 0 ? { callStack: stack.map((frame) => ({ function: frame.function, line: frame.line, args: {} })) } : {}
1601
- }, pendingAccesses);
1655
+ ...stack.length > 0 ? { callStack: stack.map((frame) => ({ function: frame.function, line: frame.line, args: { ...frame.args } })) } : {}
1656
+ }, pendingAccesses, { allowMerge: previousEventRawLine !== metadata.line });
1602
1657
  continue;
1603
1658
  }
1604
1659
  const structureState = parseStructureState(payload);
@@ -1610,7 +1665,7 @@ function eventsToRawTrace(events, sourceText) {
1610
1665
  event: "line",
1611
1666
  function: currentFunction,
1612
1667
  variables: { ...variables },
1613
- callStack: stack.map((frame) => ({ function: frame.function, line: frame.line, args: {} })),
1668
+ callStack: stack.map((frame) => ({ function: frame.function, line: frame.line, args: { ...frame.args } })),
1614
1669
  visualization: {
1615
1670
  objectKinds: { ...objectKinds }
1616
1671
  }
@@ -1625,7 +1680,7 @@ function eventsToRawTrace(events, sourceText) {
1625
1680
  event: "line",
1626
1681
  function: currentFunction,
1627
1682
  variables: { ...variables },
1628
- callStack: stack.map((frame) => ({ function: frame.function, line: frame.line, args: {} })),
1683
+ callStack: stack.map((frame) => ({ function: frame.function, line: frame.line, args: { ...frame.args } })),
1629
1684
  visualization: {
1630
1685
  objectKinds: { [objectState.variable]: "object" },
1631
1686
  hashMaps: [objectState.visualization]
@@ -1633,10 +1688,56 @@ function eventsToRawTrace(events, sourceText) {
1633
1688
  }, pendingAccesses);
1634
1689
  continue;
1635
1690
  }
1691
+ const mapState = parseMapState(payload);
1692
+ if (mapState) {
1693
+ const entries = mapState.visualization.entries.map((entry) => [entry.key, entry.value]);
1694
+ variables[mapState.variable] = { __type__: "map", entries };
1695
+ appendJavaTraceStep(trace, {
1696
+ line,
1697
+ event: "line",
1698
+ function: currentFunction,
1699
+ variables: { ...variables },
1700
+ callStack: stack.map((frame) => ({ function: frame.function, line: frame.line, args: { ...frame.args } })),
1701
+ visualization: {
1702
+ objectKinds: { [mapState.variable]: "map" },
1703
+ hashMaps: [mapState.visualization]
1704
+ }
1705
+ }, pendingAccesses);
1706
+ continue;
1707
+ }
1708
+ const setState = parseSetState(payload);
1709
+ if (setState) {
1710
+ const values = setState.visualization.entries.map((entry) => entry.key);
1711
+ variables[setState.variable] = { __type__: "set", values };
1712
+ appendJavaTraceStep(trace, {
1713
+ line,
1714
+ event: "line",
1715
+ function: currentFunction,
1716
+ variables: { ...variables },
1717
+ callStack: stack.map((frame) => ({ function: frame.function, line: frame.line, args: { ...frame.args } })),
1718
+ visualization: {
1719
+ objectKinds: { [setState.variable]: "set" },
1720
+ hashMaps: [setState.visualization]
1721
+ }
1722
+ }, pendingAccesses);
1723
+ continue;
1724
+ }
1725
+ if (isArrayLengthAccessEvent(payload)) {
1726
+ continue;
1727
+ }
1728
+ const accesses = parseAccessEvent(payload);
1729
+ if (accesses) {
1730
+ const currentCallStack = stack.length > 0 ? stack.map((frame) => ({ function: frame.function, line: frame.line, args: { ...frame.args } })) : void 0;
1731
+ if (mergeAccessesIntoPreviousMatchingLineStep(trace, line, currentFunction, currentCallStack, accesses)) {
1732
+ continue;
1733
+ }
1734
+ pendingAccesses.push(...accesses);
1735
+ continue;
1736
+ }
1636
1737
  const objectField = parseObjectFieldEvent(payload);
1637
1738
  if (objectField) {
1638
1739
  variables[objectField.variable] = { __ref__: `${objectField.variable}-object` };
1639
- const currentCallStack = stack.length > 0 ? stack.map((frame) => ({ function: frame.function, line: frame.line, args: {} })) : void 0;
1740
+ const currentCallStack = stack.length > 0 ? stack.map((frame) => ({ function: frame.function, line: frame.line, args: { ...frame.args } })) : void 0;
1640
1741
  if (mergeIntoPreviousMatchingLineStep(trace, line, currentFunction, currentCallStack, {
1641
1742
  variables: { ...variables },
1642
1743
  accesses: void 0,
@@ -1649,27 +1750,18 @@ function eventsToRawTrace(events, sourceText) {
1649
1750
  event: "line",
1650
1751
  function: currentFunction,
1651
1752
  variables: { ...variables },
1652
- callStack: stack.map((frame) => ({ function: frame.function, line: frame.line, args: {} })),
1753
+ callStack: stack.map((frame) => ({ function: frame.function, line: frame.line, args: { ...frame.args } })),
1653
1754
  visualization: buildFieldVisualization(objectField)
1654
1755
  }, pendingAccesses);
1655
1756
  continue;
1656
1757
  }
1657
1758
  Object.assign(variables, parseKeyValuePairs(payload));
1658
- const accesses = parseAccessEvent(payload);
1659
- if (accesses) {
1660
- const currentCallStack = stack.length > 0 ? stack.map((frame) => ({ function: frame.function, line: frame.line, args: {} })) : void 0;
1661
- if (mergeAccessesIntoPreviousMatchingLineStep(trace, line, currentFunction, currentCallStack, accesses)) {
1662
- continue;
1663
- }
1664
- pendingAccesses.push(...accesses);
1665
- continue;
1666
- }
1667
1759
  appendJavaTraceStep(trace, {
1668
1760
  line,
1669
1761
  event: "line",
1670
1762
  function: currentFunction,
1671
1763
  variables: { ...variables },
1672
- ...stack.length > 0 ? { callStack: stack.map((frame) => ({ function: frame.function, line: frame.line, args: {} })) } : {}
1764
+ ...stack.length > 0 ? { callStack: stack.map((frame) => ({ function: frame.function, line: frame.line, args: { ...frame.args } })) } : {}
1673
1765
  }, pendingAccesses);
1674
1766
  }
1675
1767
  if (pendingAccesses.length > 0 && trace.length > 0) {
@@ -1678,11 +1770,11 @@ function eventsToRawTrace(events, sourceText) {
1678
1770
  }
1679
1771
  return trace;
1680
1772
  }
1681
- function buildJavaExecutionResult(output, events, executionTimeMs = 0, traceLimitExceeded, timeoutReason, maxTraceSteps, sourceText) {
1773
+ function buildJavaExecutionResult(output, events, executionTimeMs = 0, traceLimitExceeded, timeoutReason, maxTraceSteps, sourceText, options = {}) {
1682
1774
  const trace = eventsToRawTrace(events, sourceText);
1683
1775
  return {
1684
1776
  success: true,
1685
- output,
1777
+ output: options.outputIsSerialized === false ? output : normalizeJavaSerializedResult(output),
1686
1778
  trace,
1687
1779
  executionTimeMs,
1688
1780
  consoleOutput: [],
@@ -1740,7 +1832,8 @@ var JavaRuntimeClient = class {
1740
1832
  rawResult.traceLimitExceeded,
1741
1833
  rawResult.timeoutReason,
1742
1834
  void 0,
1743
- rawResult.sourceText
1835
+ rawResult.sourceText,
1836
+ { outputIsSerialized: false }
1744
1837
  )
1745
1838
  );
1746
1839
  return {