Skip to content

[CSR-3436] feat: support artifacts upload#318

Open
miguelangaranocurrents wants to merge 29 commits intomainfrom
feat/support-artifacts
Open

[CSR-3436] feat: support artifacts upload#318
miguelangaranocurrents wants to merge 29 commits intomainfrom
feat/support-artifacts

Conversation

@miguelangaranocurrents
Copy link
Contributor

@miguelangaranocurrents miguelangaranocurrents commented Feb 19, 2026

Add Artifact Support for JUnit / Vitest and Improve Jest Artifacts

Summary

This branch extends Currents artifact support to JUnit-based flows and adds a concrete Vitest example. It also finalizes artifact handling in the Jest reporter.

  • Jest: @currents/jest now generates and uploads artifacts for stdout/stderr and media files (screenshots, videos).
  • JUnit/Vitest: currents convert --input-format=junit can now turn JUnit logs and attachment markers into Currents artifacts.
  • Docs + example: a Vitest project shows how to emit JUnit XML with attachment markers that map to artifacts in Currents.

JUnit Converter Changes

Location: packages/cmd/src/services/convert/index.ts

  • For each test attempt produced from JUnit XML:
    • Writes stdout to a text file in .currents/.../artifacts/ and adds a stdout artifact.

    • Writes stderr to a text file and also adds a stdout artifact (API only supports stdout for log artifacts).

    • Scans stdout and stderr for markers:

      [[ATTACHMENT|/absolute/or/relative/path/to/file.ext]]

    • For each marker:

      • Copies the referenced file into .currents/.../artifacts/<hash>.<ext>.
      • Adds an artifact with inferred type and content type:
        • Images (png, jpg, jpeg, bmp) → type: screenshot
        • Videos (mp4, webm) → type: video
        • Everything else → type: attachment

All of these appear under instances[].results.tests[].attempts[].artifacts[] and are compatible with the generic artifact API.

Jest Reporter Changes

Location: packages/jest/src/reporter.ts

  • Collects console output per test file, including special markers:

    [[ATTACHMENT|/absolute/or/relative/path/to/file.ext]]

  • For each test case:

    • Writes captured stdout/stderr to text files in the Jest .currents/.../artifacts/ directory.
    • Adds stdout artifacts pointing to those log files.
    • Parses [[ATTACHMENT|...]] markers, infers type/content type from extension (same rules as above), copies the files into artifacts/, and attaches them as screenshot / video / attachment artifacts to the correct test attempt.

Example usage lives under examples/jest/src/artifacts, where tests:

  • Generate real media files (MP4, BMP).
  • Log [[ATTACHMENT|...]] markers so those files appear as artifacts in Currents.

Vitest JUnit Example

Location: examples/vitest

  • vitest.config.ts enables the built-in JUnit reporter:

    • Reporters: ["default", ["junit", { outputFile: "./junit.xml" }]]
  • artifacts.test.ts:

    • Creates ./artifacts/vitest-screenshot.bmp programmatically.

    • Logs a marker from inside the test:

      console.log([[ATTACHMENT|<absolute path to vitest-screenshot.bmp>]])

    • Asserts that the file exists.

Flow to reproduce:

cd examples/vitest
# 1) Run tests and generate junit.xml
npm test

# 2) Convert JUnit XML to Currents format
npx currents convert --input-format=junit --input-file=./junit.xml --framework=vitest

The generated .currents/... directory will contain:

  • Log artifacts (type: stdout, text files).
  • A screenshot artifact (type: screenshot) for the BMP file.

How Users Should Use This (JUnit-based Flows)

  • Ensure the test framework emits JUnit XML and includes console output per <testcase> in <system-out>.

  • When a test generates an artifact file, log a marker from that test:

    [[ATTACHMENT|/path/to/file.ext]]

  • Run:

    npx currents convert \
      --input-format=junit \
      --input-file=<junit-xml-path> \
      --framework=<vitest|node|postman|wdio>

The converter will:

  • Persist stdout/stderr as stdout artifacts.
  • Copy the files referenced in markers into the report artifacts/ directory.
  • Attach them to the correct test attempts as screenshot, video, or attachment.

Summary by CodeRabbit

Release Notes

  • New Features

    • Added artifact attachment capabilities to attach videos, screenshots, and files directly to tests at spec, test, and attempt levels.
    • Enhanced artifact handling with conversion and upload support for test results.
  • Documentation

    • Added comprehensive artifact workflow documentation with examples and usage guides.
    • Included client-side implementation guidance and integration examples for Jest and Vitest.

@notion-workspace
Copy link

Copy link
Collaborator

@vCaisim vCaisim left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@miguelangaranocurrents nice job! I tested with server changes - works well.

Notes:

  • please carefully review the generated md files, leave only one and adjust it to the documentation style
  • add more artifacts types for vitest example - it will help to test better the convert command. The jest example is good.

Copy link
Collaborator

@vCaisim vCaisim left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@miguelangarano nice job.

I left some small refactor notes.

Still need to be addressed:

  • please carefully review the generated md files, leave only one and adjust it to the documentation style
  • add more artifacts types for vitest example - it will help to test better the convert command. The jest example is good.

Questions:

  • shouldn't the stdout also be uploaded? The stdout was written to a txt file, but I don't see the content in the dashboard.
Image

@miguelangaranocurrents
Copy link
Contributor Author

@vCaisim

* please carefully review the generated md files, leave only one and adjust it to the documentation style

Removed not required md files

* add more artifacts types for vitest example - it will help to test better the convert command. The jest example is good.

Added more, specifically for attachments

Questions:

* shouldn't the stdout also be uploaded? The stdout was written to a txt file, but I don't see the content in the dashboard.

fixed now

@coderabbitai
Copy link

coderabbitai bot commented Feb 27, 2026

📝 Walkthrough

Walkthrough

This PR introduces comprehensive artifact attachment functionality to the Currents test reporting system. It adds helper functions for attaching artifacts (videos, screenshots, files) at spec/test/attempt levels, implements artifact processing pipelines for Jest and Vitest reporters, enables artifact conversion from JUnit XML, and provides artifact upload capabilities. Includes examples, documentation, and type definitions across @currents/jest, @currents/cmd, and example projects.

Changes

Cohort / File(s) Summary
Artifact Type System & Exports
packages/cmd/src/types.ts, packages/jest/src/types.ts
Introduces new Artifact interface and type definitions (ArtifactType, ArtifactLevel) across packages; extends InstanceReport, InstanceReportTest, and InstanceReportTestAttempt with optional artifacts arrays.
CMD Core Helpers
packages/cmd/src/helpers/index.ts, packages/cmd/src/api/create-run.ts
Adds attachArtifact and convenience wrappers (attachVideo, attachScreenshot, attachFile); introduces ArtifactUploadInstruction type and uploadStdout helper for server communication; exports new helpers entry point in package.json.
Jest Artifact API
packages/jest/src/api.ts, packages/jest/src/artifacts.ts
Implements client-side artifact attachment (attachArtifact with MD5-keyed per-test artifact files, attempt tracking via Jest state) and comprehensive artifact preparation pipeline (parsing logs, collecting file artifacts, grouping by test, hashing filenames).
Jest Reporter Integration
packages/jest/src/reporter.ts, packages/jest/src/lib/fs.ts
Integrates artifact preparation into reporter lifecycle; adds artifact cleanup, per-attempt artifact collection, and spec/test/attempt artifact composition; adds fs utilities (removeFolder, copyFileAsync, readFileAsync).
CMD Conversion & Processing
packages/cmd/src/services/convert/index.ts, packages/cmd/src/services/convert/utils.ts, packages/cmd/src/services/convert/postman/instances.ts
Adds artifact parsing from JUnit XML properties and console logs; implements artifact hashing and directory copying; threads artifact data through test case construction; supports spec, test, and attempt-level artifacts from multiple sources.
CMD Artifact Upload
packages/cmd/src/services/upload/index.ts
Implements server-side artifact upload coordination; aggregates per-instance stdout/stderr across attempts; batches artifact uploads using provided upload URLs; adds handleInstanceStdout and uploadArtifacts helpers.
Build Configuration & Package Exports
packages/cmd/tsup.config.ts, packages/cmd/package.json, packages/jest/jest.config.cjs, packages/jest/package.json, packages/jest/src/index.ts, packages/jest/src/lib/index.ts
Adds helpers entry to cmd exports; adds xxhashjs dependency and types; introduces Jest tsconfig; exports api functions from jest index; adds ts-jest dev dependency.
Jest Example Project
examples/jest/jest.config.js, examples/jest/package.json, examples/jest/src/artifacts/*
Configures Jest reporter order and verbosity; updates API credentials; adds test suites for file, media, and retry-based artifact attachment scenarios.
Vitest Example Project
examples/vitest/*
Creates complete Vitest setup with package.json, vitest.config.ts, junit.xml reporter; includes comprehensive artifact test suite with multiple levels, media types, and retry scenarios; adds JUnit XML samples with artifact properties.
Documentation
docs/artifact-workflow.md, docs/client-side-gap-analysis.md, packages/jest/README.md
Adds artifact workflow guide (user API, conversion methods, internal flows, diagrams); documents implementation gaps (spec/test levels, discovery strategy divergence, log merging); updates Jest README with artifact helpers.
Configuration
.gitignore
Adds artifacts/ directory to ignore list.
Test Fixtures & Tests
packages/cmd/src/services/convert/__tests__/*
Adds comprehensive convert artifact tests with mock instances, temporary directories, and artifact validation; updates InstanceReportTest fixtures to include artifacts array.

Sequence Diagram(s)

sequenceDiagram
    participant Test as Test Suite
    participant API as attachArtifact API
    participant FS as File System
    participant Store as .currents-artifacts
    participant Reporter as Jest Reporter
    participant Output as Report JSON

    Test->>API: attachArtifact(path, type, contentType, level)
    API->>FS: Read artifact file
    Note over API: Infer type/contentType from extension if needed<br/>Compute MD5 hash of test path<br/>Get current attempt via Jest state
    API->>Store: Append JSONL with artifact metadata
    API->>Test: Return (silent on error)
    
    Note over Reporter: Test execution completes
    Reporter->>Store: Read .currents-artifacts for test file
    Reporter->>FS: Parse artifact metadata from JSONL
    Note over Reporter: Group artifacts by test + attempt<br/>Map test titles to line numbers<br/>Deduplicate and cleanup
    Reporter->>Reporter: Process spec/test/attempt artifacts
    Reporter->>FS: Copy artifacts to artifacts/ dir with hashes
    Reporter->>Output: Build InstanceReport with artifact references
Loading
sequenceDiagram
    participant JUnit as JUnit XML Report
    participant Convert as Conversion Service
    participant Props as Property Parser
    participant Logs as Log Parser
    participant FS as File System
    participant API as Server API
    participant Upload as Upload Service

    JUnit->>Convert: Parse XML with test properties
    Convert->>Props: Extract currents.artifact.* properties
    Props->>Convert: Return Artifact objects (spec/test/attempt)
    
    Convert->>Logs: Extract [[CURRENTS.ATTACHMENT]] markers<br/>and currents.artifact JSON blocks
    Logs->>Convert: Return log-based Artifact objects
    
    Note over Convert: Create artifacts/ directory
    Convert->>FS: Copy each artifact file<br/>Hash filename<br/>Update artifact.path
    FS->>Convert: Return new relative path
    
    Convert->>Convert: Build InstanceReport with artifacts
    Convert->>FS: Write instances/*.json<br/>with embedded artifact refs
    
    Convert->>API: Create run with instances
    API->>Convert: Return artifactUploadUrls
    
    Convert->>Upload: Process chunks with upload URLs
    Upload->>FS: Read artifact file content
    Upload->>API: PUT to uploadUrl for each artifact
    API->>API: Store artifact binary
    Upload->>Upload: Aggregate stdout per instance
    Upload->>API: Upload aggregated stdout
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

The PR introduces a substantial, multi-faceted artifact system spanning type definitions, client-side collection (Jest), server-side conversion (CMD), and upload orchestration. While changes follow consistent patterns, they involve dense logic in artifact preparation, parsing from multiple sources (logs, properties), file hashing, attempt tracking via Jest internals, and integration across multiple lifecycle points. The heterogeneity—from simple type additions to complex state management in the reporter—demands careful review of interactions between components.

Poem

🐰 Artifacts bounce through tests with glee,
Screenshots, videos, and logs so free!
From Jest to CMD, they travel far,
Hashed and bundled like a shooting star! ⭐
Test attempts retry, artifacts stay true,
Currents collects them all—old and new!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 8.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title '[CSR-3436] feat: support artifacts upload' clearly and concisely describes the main feature addition: artifact upload support across Jest and JUnit workflows.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/support-artifacts

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 13

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/jest/src/reporter.ts (1)

250-273: ⚠️ Potential issue | 🟡 Minor

Remove unnecessary async keyword from forEach callback.

The callback contains no await statements or async operations. The async keyword makes the callback return an unhandled Promise that forEach never awaits. This is a code smell that can mislead developers into thinking async work should occur here or will be safely handled. While currently harmless, it's an anti-pattern that should be removed.

Suggested fix
-    testResult.testResults.forEach(async (testCaseResult) => {
+    testResult.testResults.forEach((testCaseResult) => {
       const testId = getTestCaseId(test, testCaseResult);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/jest/src/reporter.ts` around lines 250 - 273, The forEach callback
passed to testResult.testResults.forEach is marked async but contains no awaits,
causing it to return unused Promises; remove the unnecessary async modifier from
the callback so it is a plain synchronous function that still calls
getTestCaseId, getTestCaseKey and mutates this.specInfo[specKey].testCaseList
and this.resultsDeferred[testCaseKey] as before (preserve all existing logic
inside the callback).
🧹 Nitpick comments (21)
examples/jest/src/artifacts/artifacts.test.ts (3)

59-59: Move jest.retryTimes to the top of the file.

Placing jest.retryTimes(2) at the end of the file after the test definitions is unconventional and may confuse readers. Jest configuration calls are typically placed at the top of the file for visibility.

♻️ Proposed fix
 import * as fs from 'fs';
 import * as path from 'path';
 import { attachFile } from '@currents/jest';
+
+jest.retryTimes(2);

 const artifactsDir = path.join(__dirname, '..', '..', 'artifacts');

And remove from the end:

-jest.retryTimes(2);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/jest/src/artifacts/artifacts.test.ts` at line 59, Move the
jest.retryTimes(2) call from the end of artifacts.test.ts into the top-level
setup area of the file (above the test suite definitions) so Jest configuration
is visible immediately; remove the trailing jest.retryTimes(2) call at the
bottom to avoid duplicate/confusing placement and ensure the retry setting
applies globally to the tests in this file.

34-36: Remove shadowed variable declaration.

artifactsDir on line 35 shadows the module-level variable declared on line 5. Since they have the same value, use the existing variable.

♻️ Proposed fix
   it('generates artifacts for multiple attempts', () => {
-    const artifactsDir = path.join(__dirname, '..', '..', 'artifacts');
     const attemptFile = path.join(artifactsDir, 'jest-attempt-count.txt');
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/jest/src/artifacts/artifacts.test.ts` around lines 34 - 36, The
local declaration of artifactsDir inside the test "generates artifacts for
multiple attempts" shadows the module-level artifactsDir; remove the const
artifactsDir = ... so the test reuses the top-level artifactsDir variable and
update any dependent expressions (e.g., attemptFile) to reference the existing
artifactsDir; locate this in the test function in artifacts.test.ts and delete
the redundant variable declaration to avoid shadowing.

36-43: Consider cleanup of state file between test suite runs.

The jest-attempt-count.txt file persists across test runs, which could cause unexpected behavior if the test suite is run multiple times without cleanup. Consider adding cleanup in an afterAll hook or documenting this behavior.

♻️ Proposed fix - add cleanup
+afterAll(() => {
+  const attemptFile = path.join(artifactsDir, 'jest-attempt-count.txt');
+  if (fs.existsSync(attemptFile)) {
+    fs.unlinkSync(attemptFile);
+  }
+});
+
 describe('Artifacts Test', () => {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/jest/src/artifacts/artifacts.test.ts` around lines 36 - 43, The test
currently persists a state file via attemptFile and increments attempt across
runs; add teardown to remove or reset that file after the suite so repeated runs
start clean. Update the test file (artifacts.test.ts) to add an afterAll (or
afterEach if appropriate) hook that checks for attemptFile (the same
path/variable used when creating jest-attempt-count.txt) and deletes it (or
writes "0") using fs.unlinkSync or fs.writeFileSync, ensuring the attempt
counter logic (attempt variable and its read/write) runs against a fresh state
on subsequent runs.
docs/artifact-workflow.md (1)

353-364: Add a language specifier to the code block.

The fenced code block for the directory structure is missing a language identifier. While this is a minor formatting issue, adding a specifier improves rendering consistency.

📝 Proposed fix
-```
+```text
 .currents/
   ├── {TIMESTAMP-UUID}/          # Unique Report ID
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/artifact-workflow.md` around lines 353 - 364, The fenced code block
showing the directory tree starting with ".currents/" is missing a language
specifier; update that fenced block to include a language identifier (e.g.,
change ``` to ```text) so the directory structure (the block beginning with
".currents/") renders consistently.
examples/jest/src/artifacts/media.test.ts (1)

1-4: Consider using ES module imports for TypeScript consistency.

This TypeScript file uses CommonJS require() syntax. For consistency with TypeScript conventions and better type inference, consider using ES module imports.

♻️ Proposed fix
-
-const fs = require('fs');
-const path = require('path');
-const { attachVideo, attachScreenshot } = require('@currents/jest');
+import * as fs from 'fs';
+import * as path from 'path';
+import { attachVideo, attachScreenshot } from '@currents/jest';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/jest/src/artifacts/media.test.ts` around lines 1 - 4, Replace the
CommonJS requires with ES module imports to keep TypeScript consistency: change
the top-of-file requires for fs and path and the import of attachVideo and
attachScreenshot from '@currents/jest' to ES module import statements, updating
any uses to match (refer to the variables fs, path and the functions attachVideo
and attachScreenshot to locate where to change).
packages/jest/src/artifacts.ts (2)

283-301: Add debug logging for JSON parse failures in artifact parsing.

Similar to the spec artifact parsing, this empty catch block silently swallows errors, making debugging difficult.

♻️ Proposed fix
     if (log.key === 'JSON_ARTIFACT') {
         try {
             const artifact = JSON.parse(log.value);
             // ... artifact processing ...
-        } catch (e) {}
+        } catch (e) {
+            debug('Failed to parse JSON artifact: %s - %o', log.value, e);
+        }
         continue;
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/jest/src/artifacts.ts` around lines 283 - 301, The empty catch
swallowing JSON.parse errors for 'JSON_ARTIFACT' makes debugging impossible;
update the catch in the loop that handles log.key === 'JSON_ARTIFACT' to log the
parse failure (include the thrown error and the offending log.value and/or log)
instead of ignoring it — use the same logger used elsewhere in this module
(e.g., processLogger.error or the module's debug logger) so you get a
descriptive message whenever JSON.parse(log.value) fails while populating
jsonArtifacts.

186-194: Add debug logging for JSON parse failures.

The empty catch block silently swallows JSON parse errors. Adding debug logging would help troubleshoot malformed artifact entries.

♻️ Proposed fix
         try {
             const artifact = JSON.parse(log.value);
             if (artifact.path && artifact.type && artifact.contentType && artifact.level === 'spec') {
                 jsonArtifacts.push(artifact);
             }
-        } catch (e) {}
+        } catch (e) {
+            debug('Failed to parse JSON artifact from log: %s - %o', log.value, e);
+        }
         continue;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/jest/src/artifacts.ts` around lines 186 - 194, The catch block
swallowing JSON.parse errors for entries where log.key === 'JSON_ARTIFACT'
should be replaced with debug logging: inside the catch for
JSON.parse(log.value) log the parse error and relevant context (e.g., the
offending log.value or a truncated version and the log key) instead of doing
nothing so malformed artifact entries are visible during debugging; update the
catch in the loop that iterates over logs (references: logs, log.key,
'JSON_ARTIFACT', JSON.parse, jsonArtifacts) to emit a debug message via the
project's logger or console.debug including the error and identifying
information.
packages/jest/src/lib/fs.ts (1)

60-68: Align copyFileAsync with the equivalent function in packages/cmd/src/lib/fs.ts by ensuring the destination directory exists.

This function lacks the directory existence check that the similar function in packages/cmd/src/lib/fs.ts (lines 90) includes. While current callers pre-create the artifacts directory, adding this safety check makes the function more robust and consistent across the codebase.

♻️ Proposed fix
+import { dirname } from 'path';
+
 export async function copyFileAsync(source: string, destination: string) {
   try {
+    await fs.ensureDir(dirname(destination));
     await fs.copy(source, destination);
     debug('File copied from %s to %s', source, destination);
   } catch (err) {
     error(`Error copying file from ${source} to ${destination}:`, err);
     throw err;
   }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/jest/src/lib/fs.ts` around lines 60 - 68, copyFileAsync currently
copies a file without ensuring the destination directory exists, unlike the
counterpart in packages/cmd; update copyFileAsync to compute the destination
directory (using path.dirname(destination)) and call the fs.ensureDir (or
equivalent) on that directory before calling fs.copy, preserving the existing
try/catch and debug/error logging in the same function so the operation is
robust if callers don't pre-create the artifacts directory.
packages/jest/src/types.ts (1)

44-52: Align the Artifact type definitions across jest and cmd packages to prevent data loss during serialization.

The Artifact interfaces differ: jest uses type: ArtifactType with an attempt?: number field and named ArtifactLevel, while cmd uses type: string with an inline level union and no attempt field. Although packages don't directly import each other's types, if artifacts are serialized and consumed across package boundaries, cmd's interface won't account for the attempt field or the stricter typing.

Recommend aligning the interfaces (preferably by adopting jest's more type-safe structure) or adding explicit type conversions at any integration points.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/jest/src/types.ts` around lines 44 - 52, The Artifact interface in
packages/jest (Artifact, ArtifactType, ArtifactLevel, attempt?) is stricter than
the cmd package's artifact shape (which uses type: string, inline level union,
and lacks attempt), causing potential serialization/deserialization data loss;
align the definitions by updating the cmd package to use the same exported
types/names (Artifact, ArtifactType, ArtifactLevel) or export a shared artifact
type and replace the cmd inline union and string-typed type with the shared
types, and ensure the optional attempt?: number field is present in both sides
or add explicit conversion/mapping at integration points to preserve attempt and
stricter typing during serialization.
examples/vitest/package.json (1)

12-14: Consider pinning the @currents/cmd version.

Using "*" as a version specifier can lead to unexpected breaking changes when the package is updated. For an example project, consider pinning to a specific version or using "workspace:*" if this is a monorepo workspace.

♻️ Suggested version specifier
   "devDependencies": {
-    "@currents/cmd": "*",
+    "@currents/cmd": "workspace:*",
     "vitest": "^2.1.9"
   },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/vitest/package.json` around lines 12 - 14, Replace the wildcard "*"
for the "@currents/cmd" devDependency in package.json with a pinned version or
workspace specifier to avoid unexpected upgrades; update the "devDependencies"
entry for "@currents/cmd" (currently "*") to a specific semver like "1.2.3" or
"workspace:*" depending on whether this repo is a workspace, then run install to
lock the chosen version.
examples/vitest/junit.xml (1)

14-67: Hardcoded absolute paths reduce portability.

The JUnit XML fixture contains absolute paths to a specific developer's machine (e.g., /Users/miguelangarano/Documents/GitHub/currents-reporter/...). For example fixtures, consider using relative paths or documenting that these paths are illustrative and will be replaced during actual test runs.

If this file is auto-generated by running the tests locally, consider adding it to .gitignore and providing instructions to regenerate it, or use placeholder paths that clearly indicate they should be replaced.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/vitest/junit.xml` around lines 14 - 67, The JUnit XML fixture
contains hardcoded absolute paths in system-out entries (e.g., lines with
currents.artifact.{... "path": "..."} and markers like
[[CURRENTS.ATTACHMENT|...]]), which reduces portability; update those entries to
use relative paths or clear placeholders (e.g., ./examples/vitest/artifacts/...
or /PATH/TO/REPO/artifacts/...) in the testcase blocks with names like "Vitest
JUnit artifacts › generates spec, test, and attempt level artifacts via JSON
logs" and others, and either add this fixture to .gitignore if it is
auto-generated or add a short README/script note that documents how to
regenerate the file during local test runs so contributors know it will be
replaced.
packages/cmd/src/services/upload/index.ts (2)

6-6: Unused import: generateShortHash.

The generateShortHash function is imported but not used in this file.

♻️ Remove unused import
-import { maskRecordKey, nanoid, readJsonFile, writeFileAsync, generateShortHash } from '@lib';
+import { maskRecordKey, nanoid, readJsonFile, writeFileAsync } from '@lib';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/cmd/src/services/upload/index.ts` at line 6, The import list in
packages/cmd/src/services/upload/index.ts includes an unused symbol
generateShortHash; remove generateShortHash from the named imports (alongside
maskRecordKey, nanoid, readJsonFile, writeFileAsync) in the import statement to
eliminate the unused import and restore linting/build hygiene.

323-327: Static analysis: forEach callback should not return a value.

The forEach callback implicitly returns the result of contentTypeMap.set(). While this doesn't cause runtime issues, it's cleaner to make the return explicit or use a for...of loop.

♻️ Proposed fix
   const processArtifacts = (artifacts?: any[]) => {
     if (artifacts) {
-      artifacts.forEach((a) => contentTypeMap.set(a.path, a.contentType));
+      for (const a of artifacts) {
+        contentTypeMap.set(a.path, a.contentType);
+      }
     }
   };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/cmd/src/services/upload/index.ts` around lines 323 - 327, The
forEach callback in processArtifacts implicitly returns the Map.set value;
change the implementation to avoid returning from the callback—e.g., iterate
with a for...of loop over artifacts (in function processArtifacts) and call
contentTypeMap.set(a.path, a.contentType) inside the loop, or otherwise ensure
the callback returns void; update the code around the processArtifacts
declaration to use the new loop so the forEach callback no longer returns a
value.
packages/cmd/src/helpers/index.ts (2)

13-49: Duplicate type inference logic across packages.

The extension-based type/contentType inference logic here is duplicated in packages/jest/src/api.ts (lines 20-44). Consider extracting this to a shared utility to maintain consistency and reduce duplication.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/cmd/src/helpers/index.ts` around lines 13 - 49, The extension-based
type/contentType inference in attachArtifact is duplicated elsewhere; extract
the logic into a shared utility (e.g., inferArtifactType or
getArtifactTypeFromExt) and replace the inline switch in attachArtifact with a
call to that utility, returning { type, contentType } based on the file
extension; update other callers (the duplicate in api.ts) to use the same
utility so inference is centralized and consistent across packages.

55-55: attachVideo hardcodes video/mp4 but doesn't verify the extension.

If a user calls attachVideo('/path/to/video.webm'), it will still be marked as video/mp4. Consider either:

  1. Inferring the content type from the extension (webm → video/webm)
  2. Documenting that this helper is specifically for MP4 files
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/cmd/src/helpers/index.ts` at line 55, attachVideo currently
hardcodes "video/mp4" which mislabels other formats; update the attachVideo
implementation (in packages/cmd/src/helpers/index.ts) to derive the MIME type
from the file extension before calling attachArtifact: parse the extension from
the path, map common video extensions (.mp4 -> video/mp4, .webm -> video/webm,
.ogg/.ogv -> video/ogg, .mov -> video/quicktime, etc.), use the mapped MIME when
calling attachArtifact(path, 'video', inferredMime), and choose a clear fallback
(either default to video/mp4 or throw/log and surface an error) if the extension
is unrecognized; keep attachArtifact usage and behavior otherwise.
examples/vitest/artifacts.test.ts (2)

194-209: File-based attempt counter may cause issues with parallel test execution.

The attempt tracking mechanism writes to a shared file (attempt-count.txt). If Vitest runs tests in parallel (which is the default), multiple tests or test files accessing this file could cause race conditions.

For a demonstration/example test this is acceptable, but consider adding a comment noting this limitation or using --no-threads / --single-thread in the example's run configuration.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/vitest/artifacts.test.ts` around lines 194 - 209, The attempt
counter uses a shared file (attempt-count.txt) via attemptFile/attempt variable
which can race under parallel Vitest workers; either add a concise comment above
the block (near artifactsDir/attemptFile/attachFile) stating this example is not
safe for parallel execution and recommend running the example with
single-threading (e.g., --no-threads/--single-thread) or change the example to
use a per-process counter (e.g., process.pid-based filename) to avoid
shared-file races.

93-101: Inconsistent attachment marker format.

This test uses [[ATTACHMENT|${videoPath}]] (line 99), while the test at lines 67-74 uses [[CURRENTS.ATTACHMENT|${path}|level]]. According to the convert logic in packages/cmd/src/services/convert/index.ts, the legacy format is [[ATTACHMENT|path]] and the new JSON format uses currents.artifact.{...}.

If both formats are intentionally supported, consider adding a comment to clarify. Otherwise, align on one approach for documentation clarity.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/vitest/artifacts.test.ts` around lines 93 - 101, The test "generates
a video artifact and logs attachment marker" currently emits the legacy marker
`[[ATTACHMENT|${videoPath}]]` which is inconsistent with the other test that
uses the `[[CURRENTS.ATTACHMENT|${path}|level]]` style and the newer
currents.artifact JSON format expected by the convert logic in convert/index.ts;
either update the console.log in this test to emit the new currents.artifact
JSON structure (matching the other test's output and the convert service) or add
a short inline comment above the test clarifying that both legacy
`[[ATTACHMENT|path]]` markers and the new currents.artifact format are
intentionally supported so future readers know this divergence, referencing the
test name and the convert/index.ts behavior when making the change.
packages/jest/src/api.ts (1)

82-86: Silent error handling may hide configuration issues.

All errors in attachArtifact are silently caught and ignored. While this prevents test crashes, it could hide issues like:

  • Missing .currents-artifacts directory permissions
  • Invalid artifact paths
  • Disk space issues

Consider at minimum logging a warning in development/debug mode.

♻️ Add optional debug logging
   } catch (e) {
-    // Ignore errors or let them propagate?
-    // User requested "no fallback", so we just silence the error or throw it?
-    // Silencing is safer for test execution not to crash because of reporting.
+    // Silencing is safer for test execution not to crash because of reporting.
+    // Log to stderr only if DEBUG is set to help with troubleshooting.
+    if (process.env.DEBUG) {
+      console.error('[currents] Failed to attach artifact:', e);
+    }
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/jest/src/api.ts` around lines 82 - 86, The catch block in
attachArtifact currently swallows all errors; change it to log a warning with
the caught error and context (e.g., path being attached and operation name) when
running in dev/debug mode (check process.env.NODE_ENV !== 'production' or an
existing debug flag), rather than silently ignoring; keep silent behavior in
production but ensure the log includes error.message/stack so issues like
permission, path, or disk errors are visible during development/troubleshooting.
packages/jest/src/reporter.artifacts.test.ts (1)

9-173: Missing cleanup of temporary directory.

The test creates a temporary directory but never cleans it up after the test completes. This can lead to disk space accumulation over repeated test runs, especially in CI environments.

♻️ Proposed fix to add cleanup
 describe('CustomReporter artifacts', () => {
+  let baseDir: string;
+
+  afterEach(async () => {
+    if (baseDir) {
+      await fs.remove(baseDir);
+    }
+  });
+
   it('creates artifacts from console logs via properties', async () => {
-    const baseDir = await fs.mkdtemp(
+    baseDir = await fs.mkdtemp(
       join(os.tmpdir(), 'currents-jest-artifacts-')
     );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/jest/src/reporter.artifacts.test.ts` around lines 9 - 173, The test
creates a temporary directory (baseDir from fs.mkdtemp) but never deletes it;
add cleanup to remove baseDir after the test finishes—either wrap the test body
in try/finally and call fs.remove(fsPath) / fs.rm(baseDir, { recursive: true,
force: true }) in the finally, or register an afterEach that deletes baseDir if
defined; update the test named "creates artifacts from console logs via
properties" to ensure baseDir is removed to avoid leaking temp dirs.
packages/cmd/src/types.ts (1)

40-47: Type inconsistency between packages.

The Artifact interface here uses type: string, but in packages/jest/src/types.ts (lines 43-51), the equivalent Artifact interface uses type: ArtifactType which is a union of specific string literals ('screenshot' | 'video' | 'trace' | 'attachment' | 'stdout' | 'coverage').

This inconsistency may cause type-safety gaps when artifacts flow between Jest and cmd packages. Consider aligning these definitions or creating a shared type.

♻️ Proposed fix to use a stricter type
+export type ArtifactType = 'screenshot' | 'video' | 'trace' | 'attachment' | 'stdout' | 'coverage';
+
 export interface Artifact {
   path: string;
-  type: string;
+  type: ArtifactType | string; // Allow known types with fallback
   contentType: string;
   name?: string;
   metadata?: Record<string, string>;
   level?: 'spec' | 'test' | 'attempt';
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/cmd/src/types.ts` around lines 40 - 47, The Artifact interface's
type property is currently a plain string (Artifact) which conflicts with the
stricter ArtifactType union used elsewhere; change Artifact.type to use the
shared union type (ArtifactType) instead of string by creating or using a single
shared type definition and importing it where needed (or copy the union into
this module if sharing isn't possible), update the Artifact interface to
reference ArtifactType, and adjust any imports/exports and downstream usages
(e.g., where Artifact is constructed or validated) so TypeScript compiles
without widening the artifact type.
packages/cmd/src/services/convert/utils.ts (1)

181-212: Remove the speculative narrative comment block from parser logic.

This block reads like investigation notes and makes the parsing behavior harder to maintain. Keep a short intent-focused comment only.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/cmd/src/services/convert/utils.ts` around lines 181 - 212, Remove
the long investigative/narrative comment block inside the branch that checks
prop.name === 'currents.artifact.JSON_ARTIFACT' in
packages/cmd/src/services/convert/utils.ts; replace it with a concise
intent-focused comment (one or two lines) that states what the branch does
(e.g., "Handle JSON artifact entries extracted from JUnit properties or logs")
so the parser logic remains clear and maintainable—do not change parsing
behavior in the prop.name branch or touch the related extractAttachmentsFromLog
logic, only simplify the comments.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/client-side-gap-analysis.md`:
- Line 10: The table cell containing the marker `[[ATTACHMENT|path]]` is
breaking the Markdown table; update the cell so the pipe is escaped inside the
inline code span (change `[[ATTACHMENT|path]]` to `[[ATTACHMENT\|path]]` within
the backticks) so the Markdown parser treats it as literal text and the table
columns stay aligned.

In `@examples/jest/package.json`:
- Line 8: The package.json "report" script currently hardcodes the API key
IRmZjZWBkkd3jqve; replace that literal with an environment variable (e.g.
CURRENTS_API_KEY) so the "report" script uses the env var instead of the
committed key, and update any README or comments to document setting
CURRENTS_API_KEY for local/dev use (or explicitly mark the key as a
non-production test key if it must remain visible).

In `@examples/vitest/package.json`:
- Line 9: The "report" npm script currently hardcodes an API key and should be
changed to use an environment variable instead: update the "report" script (the
"report" entry in package.json) to read the key from an env var (e.g.,
CURRENTS_API_KEY) rather than the literal "IRmZjZWBkkd3jqve", and document that
callers must set CURRENTS_API_KEY (and keep the existing CURRENTS_API_URL
handling) so credentials are not stored in the example.

In `@packages/cmd/src/api/create-run.ts`:
- Around line 91-110: In uploadStdout, change the initial warn() call to debug()
and await the makeRequest promise so rejections are caught by the try/catch;
specifically, in the uploadStdout function replace warn(...) with debug(...) and
call await makeRequest<void, { stdout: string }>(ClientType.API, { ... }) inside
the try block (keeping the existing debug('Error uploading stdout: %o', e) in
the catch).

In `@packages/cmd/src/services/convert/index.ts`:
- Line 36: The current extraction using
log.matchAll(/currents\.artifact\.(\{.*?\})/g) can break on nested JSON; update
the extraction in packages/cmd/src/services/convert/index.ts to iterate lines
(split log by '\n') and for each line use a regex that captures the JSON to the
end of the line (e.g., match /currents\.artifact\.(\{.+\})$/) and then
JSON.parse inside a try/catch; replace the existing use of
jsonMatches/log.matchAll with this line-based parsing and ensure parsing
failures are ignored or logged.
- Around line 19-55: The function extractAttachmentsFromLog is dead code; remove
its entire definition (the extractAttachmentsFromLog function block) from the
file, and also remove any now-unused imports it relies on (e.g., extname) to
avoid lint errors; alternatively, if you intend to keep it, replace the body
with a short TODO comment and export or document expected usage, but by default
delete the function and cleanup related unused symbols like extname.

In `@packages/cmd/src/services/convert/utils.ts`:
- Around line 66-88: extractArtifactsFromLog(stdout) returns artifacts that may
include a `level` indicating whether they belong to the test-level or
attempt-level; the current code blindly pushes the same artifacts into both
testArtifacts and attemptArtifacts.get(0), causing duplicate uploads and
ignoring `level`. Update the block that handles testCase['system-out'] so you
first map/filter stdoutArtifacts by their `level` (e.g., artifacts with level
=== 'attempt' -> push clones into attemptArtifacts.get(0), artifacts with level
=== 'test' or unspecified -> push clones into testArtifacts), clone each
artifact when inserting (to avoid in-place path mutation), and do not add the
same artifact to both collections; keep usage of functions/vars
extractArtifactsFromLog, testArtifacts, attemptArtifacts, and ensureArray to
locate and modify the logic.
- Around line 121-129: The current regex in utils.ts (jsonMatches =
log.matchAll(/currents\.artifact\.(\{.*?\})/g)) fails for nested JSON and
silently drops artifacts; replace the fragile regex approach with a robust
extractor: search for the "currents.artifact.{" literal, then walk the string
from the opening brace to the matching closing brace using a brace counter to
capture the full balanced JSON substring, then JSON.parse it and push to
artifacts (same check for path/type/contentType). Update the loop that currently
iterates jsonMatches to use this brace-matching extraction before parsing so
nested objects (e.g. metadata) are preserved and parse errors are not silent.

In `@packages/cmd/src/services/upload/index.ts`:
- Around line 157-159: The log is inverted: the info(...) message is emitted
when response.artifactUploadUrls exists and has items. Update the condition to
log only when there are no upload URLs (e.g., if (!response.artifactUploadUrls
|| response.artifactUploadUrls.length === 0)) and reword the message for clarity
(suggestion: "No artifacts to handle: initial run created without instances").
Ensure you update the code that references response.artifactUploadUrls and the
info(...) call accordingly.

In `@packages/jest/src/api.ts`:
- Around line 41-44: The fallback contentType in this file currently sets
contentType = 'text/plain' while the helpers package uses
'application/octet-stream'; update the default in the else branch where
variables type and contentType are assigned (the contentType and type assignment
block) to use 'application/octet-stream' so the artifact processing defaults are
consistent with the helpers package; ensure no other logic depends on plain text
as the implicit default.
- Around line 119-130: Update getAttempt to treat the jest-internal Symbol
lookup as an optional optimization and make the fallback heuristic (using
expect.getState()/expect.setState()) the primary code path: add a
top-of-function comment stating this behavior and that the implementation is
tested against Jest 29.5.0+ and may break on future Jest versions; move the
existing Symbol(JEST_STATE_SYMBOL) branch to run only if the fallback fails or
as a short-circuit optimization, and add a brief inline comment above the Symbol
branch explaining it is used for performance/reliability when available but
relies on internal APIs; keep the existing try/catch and defensive checks around
global[stateSymbol] and the expect-based fallback intact and ensure the function
still returns the same fallback value when symbol access is unavailable.

In `@packages/jest/src/artifacts.ts`:
- Around line 391-434: The regex in parseAttachmentLogs currently only matches
the marker with the CURRENTS. prefix; update the pattern used in
parseAttachmentLogs to accept both forms ([[ATTACHMENT|...]] and
[[CURRENTS.ATTACHMENT|...]]). Modify the matchAll regex (in parseAttachmentLogs)
to make the "CURRENTS\." prefix optional (e.g. allow an optional group before
ATTACHMENT) while preserving the existing capture groups for path and optional
level, then run/adjust tests in packages/cmd and the convert-artifacts tests to
ensure backward compatibility.

In `@packages/jest/src/reporter.ts`:
- Around line 363-383: The code currently only sets `_testArtifacts` when `index
=== 0` and later promotes artifacts from `t.attempts[0]`, which drops artifacts
emitted on retries; remove the `index === 0` gating so each attempt can set
`_testArtifacts`, and change the promotion logic in the `tests.forEach` block to
locate the first (or last) attempt that has a `_testArtifacts` property (e.g.,
find(attempt => attempt._testArtifacts) or scan from last to first) and assign
that attempt's `_testArtifacts` to `t.artifacts`, then delete the
`_testArtifacts` from that attempt; update references to `_testArtifacts`,
`tests.forEach`, and `t.attempts` accordingly.

---

Outside diff comments:
In `@packages/jest/src/reporter.ts`:
- Around line 250-273: The forEach callback passed to
testResult.testResults.forEach is marked async but contains no awaits, causing
it to return unused Promises; remove the unnecessary async modifier from the
callback so it is a plain synchronous function that still calls getTestCaseId,
getTestCaseKey and mutates this.specInfo[specKey].testCaseList and
this.resultsDeferred[testCaseKey] as before (preserve all existing logic inside
the callback).

---

Nitpick comments:
In `@docs/artifact-workflow.md`:
- Around line 353-364: The fenced code block showing the directory tree starting
with ".currents/" is missing a language specifier; update that fenced block to
include a language identifier (e.g., change ``` to ```text) so the directory
structure (the block beginning with ".currents/") renders consistently.

In `@examples/jest/src/artifacts/artifacts.test.ts`:
- Line 59: Move the jest.retryTimes(2) call from the end of artifacts.test.ts
into the top-level setup area of the file (above the test suite definitions) so
Jest configuration is visible immediately; remove the trailing
jest.retryTimes(2) call at the bottom to avoid duplicate/confusing placement and
ensure the retry setting applies globally to the tests in this file.
- Around line 34-36: The local declaration of artifactsDir inside the test
"generates artifacts for multiple attempts" shadows the module-level
artifactsDir; remove the const artifactsDir = ... so the test reuses the
top-level artifactsDir variable and update any dependent expressions (e.g.,
attemptFile) to reference the existing artifactsDir; locate this in the test
function in artifacts.test.ts and delete the redundant variable declaration to
avoid shadowing.
- Around line 36-43: The test currently persists a state file via attemptFile
and increments attempt across runs; add teardown to remove or reset that file
after the suite so repeated runs start clean. Update the test file
(artifacts.test.ts) to add an afterAll (or afterEach if appropriate) hook that
checks for attemptFile (the same path/variable used when creating
jest-attempt-count.txt) and deletes it (or writes "0") using fs.unlinkSync or
fs.writeFileSync, ensuring the attempt counter logic (attempt variable and its
read/write) runs against a fresh state on subsequent runs.

In `@examples/jest/src/artifacts/media.test.ts`:
- Around line 1-4: Replace the CommonJS requires with ES module imports to keep
TypeScript consistency: change the top-of-file requires for fs and path and the
import of attachVideo and attachScreenshot from '@currents/jest' to ES module
import statements, updating any uses to match (refer to the variables fs, path
and the functions attachVideo and attachScreenshot to locate where to change).

In `@examples/vitest/artifacts.test.ts`:
- Around line 194-209: The attempt counter uses a shared file
(attempt-count.txt) via attemptFile/attempt variable which can race under
parallel Vitest workers; either add a concise comment above the block (near
artifactsDir/attemptFile/attachFile) stating this example is not safe for
parallel execution and recommend running the example with single-threading
(e.g., --no-threads/--single-thread) or change the example to use a per-process
counter (e.g., process.pid-based filename) to avoid shared-file races.
- Around line 93-101: The test "generates a video artifact and logs attachment
marker" currently emits the legacy marker `[[ATTACHMENT|${videoPath}]]` which is
inconsistent with the other test that uses the
`[[CURRENTS.ATTACHMENT|${path}|level]]` style and the newer currents.artifact
JSON format expected by the convert logic in convert/index.ts; either update the
console.log in this test to emit the new currents.artifact JSON structure
(matching the other test's output and the convert service) or add a short inline
comment above the test clarifying that both legacy `[[ATTACHMENT|path]]` markers
and the new currents.artifact format are intentionally supported so future
readers know this divergence, referencing the test name and the convert/index.ts
behavior when making the change.

In `@examples/vitest/junit.xml`:
- Around line 14-67: The JUnit XML fixture contains hardcoded absolute paths in
system-out entries (e.g., lines with currents.artifact.{... "path": "..."} and
markers like [[CURRENTS.ATTACHMENT|...]]), which reduces portability; update
those entries to use relative paths or clear placeholders (e.g.,
./examples/vitest/artifacts/... or /PATH/TO/REPO/artifacts/...) in the testcase
blocks with names like "Vitest JUnit artifacts › generates spec, test, and
attempt level artifacts via JSON logs" and others, and either add this fixture
to .gitignore if it is auto-generated or add a short README/script note that
documents how to regenerate the file during local test runs so contributors know
it will be replaced.

In `@examples/vitest/package.json`:
- Around line 12-14: Replace the wildcard "*" for the "@currents/cmd"
devDependency in package.json with a pinned version or workspace specifier to
avoid unexpected upgrades; update the "devDependencies" entry for
"@currents/cmd" (currently "*") to a specific semver like "1.2.3" or
"workspace:*" depending on whether this repo is a workspace, then run install to
lock the chosen version.

In `@packages/cmd/src/helpers/index.ts`:
- Around line 13-49: The extension-based type/contentType inference in
attachArtifact is duplicated elsewhere; extract the logic into a shared utility
(e.g., inferArtifactType or getArtifactTypeFromExt) and replace the inline
switch in attachArtifact with a call to that utility, returning { type,
contentType } based on the file extension; update other callers (the duplicate
in api.ts) to use the same utility so inference is centralized and consistent
across packages.
- Line 55: attachVideo currently hardcodes "video/mp4" which mislabels other
formats; update the attachVideo implementation (in
packages/cmd/src/helpers/index.ts) to derive the MIME type from the file
extension before calling attachArtifact: parse the extension from the path, map
common video extensions (.mp4 -> video/mp4, .webm -> video/webm, .ogg/.ogv ->
video/ogg, .mov -> video/quicktime, etc.), use the mapped MIME when calling
attachArtifact(path, 'video', inferredMime), and choose a clear fallback (either
default to video/mp4 or throw/log and surface an error) if the extension is
unrecognized; keep attachArtifact usage and behavior otherwise.

In `@packages/cmd/src/services/convert/utils.ts`:
- Around line 181-212: Remove the long investigative/narrative comment block
inside the branch that checks prop.name === 'currents.artifact.JSON_ARTIFACT' in
packages/cmd/src/services/convert/utils.ts; replace it with a concise
intent-focused comment (one or two lines) that states what the branch does
(e.g., "Handle JSON artifact entries extracted from JUnit properties or logs")
so the parser logic remains clear and maintainable—do not change parsing
behavior in the prop.name branch or touch the related extractAttachmentsFromLog
logic, only simplify the comments.

In `@packages/cmd/src/services/upload/index.ts`:
- Line 6: The import list in packages/cmd/src/services/upload/index.ts includes
an unused symbol generateShortHash; remove generateShortHash from the named
imports (alongside maskRecordKey, nanoid, readJsonFile, writeFileAsync) in the
import statement to eliminate the unused import and restore linting/build
hygiene.
- Around line 323-327: The forEach callback in processArtifacts implicitly
returns the Map.set value; change the implementation to avoid returning from the
callback—e.g., iterate with a for...of loop over artifacts (in function
processArtifacts) and call contentTypeMap.set(a.path, a.contentType) inside the
loop, or otherwise ensure the callback returns void; update the code around the
processArtifacts declaration to use the new loop so the forEach callback no
longer returns a value.

In `@packages/cmd/src/types.ts`:
- Around line 40-47: The Artifact interface's type property is currently a plain
string (Artifact) which conflicts with the stricter ArtifactType union used
elsewhere; change Artifact.type to use the shared union type (ArtifactType)
instead of string by creating or using a single shared type definition and
importing it where needed (or copy the union into this module if sharing isn't
possible), update the Artifact interface to reference ArtifactType, and adjust
any imports/exports and downstream usages (e.g., where Artifact is constructed
or validated) so TypeScript compiles without widening the artifact type.

In `@packages/jest/src/api.ts`:
- Around line 82-86: The catch block in attachArtifact currently swallows all
errors; change it to log a warning with the caught error and context (e.g., path
being attached and operation name) when running in dev/debug mode (check
process.env.NODE_ENV !== 'production' or an existing debug flag), rather than
silently ignoring; keep silent behavior in production but ensure the log
includes error.message/stack so issues like permission, path, or disk errors are
visible during development/troubleshooting.

In `@packages/jest/src/artifacts.ts`:
- Around line 283-301: The empty catch swallowing JSON.parse errors for
'JSON_ARTIFACT' makes debugging impossible; update the catch in the loop that
handles log.key === 'JSON_ARTIFACT' to log the parse failure (include the thrown
error and the offending log.value and/or log) instead of ignoring it — use the
same logger used elsewhere in this module (e.g., processLogger.error or the
module's debug logger) so you get a descriptive message whenever
JSON.parse(log.value) fails while populating jsonArtifacts.
- Around line 186-194: The catch block swallowing JSON.parse errors for entries
where log.key === 'JSON_ARTIFACT' should be replaced with debug logging: inside
the catch for JSON.parse(log.value) log the parse error and relevant context
(e.g., the offending log.value or a truncated version and the log key) instead
of doing nothing so malformed artifact entries are visible during debugging;
update the catch in the loop that iterates over logs (references: logs, log.key,
'JSON_ARTIFACT', JSON.parse, jsonArtifacts) to emit a debug message via the
project's logger or console.debug including the error and identifying
information.

In `@packages/jest/src/lib/fs.ts`:
- Around line 60-68: copyFileAsync currently copies a file without ensuring the
destination directory exists, unlike the counterpart in packages/cmd; update
copyFileAsync to compute the destination directory (using
path.dirname(destination)) and call the fs.ensureDir (or equivalent) on that
directory before calling fs.copy, preserving the existing try/catch and
debug/error logging in the same function so the operation is robust if callers
don't pre-create the artifacts directory.

In `@packages/jest/src/reporter.artifacts.test.ts`:
- Around line 9-173: The test creates a temporary directory (baseDir from
fs.mkdtemp) but never deletes it; add cleanup to remove baseDir after the test
finishes—either wrap the test body in try/finally and call fs.remove(fsPath) /
fs.rm(baseDir, { recursive: true, force: true }) in the finally, or register an
afterEach that deletes baseDir if defined; update the test named "creates
artifacts from console logs via properties" to ensure baseDir is removed to
avoid leaking temp dirs.

In `@packages/jest/src/types.ts`:
- Around line 44-52: The Artifact interface in packages/jest (Artifact,
ArtifactType, ArtifactLevel, attempt?) is stricter than the cmd package's
artifact shape (which uses type: string, inline level union, and lacks attempt),
causing potential serialization/deserialization data loss; align the definitions
by updating the cmd package to use the same exported types/names (Artifact,
ArtifactType, ArtifactLevel) or export a shared artifact type and replace the
cmd inline union and string-typed type with the shared types, and ensure the
optional attempt?: number field is present in both sides or add explicit
conversion/mapping at integration points to preserve attempt and stricter typing
during serialization.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ad54ba6 and 65de857.

⛔ Files ignored due to path filters (2)
  • package-lock.json is excluded by !**/package-lock.json
  • packages/cmd/src/services/convert/__tests__/__snapshots__/instances.test.ts.snap is excluded by !**/*.snap
📒 Files selected for processing (35)
  • .gitignore
  • docs/artifact-workflow.md
  • docs/client-side-gap-analysis.md
  • examples/jest/jest.config.js
  • examples/jest/package.json
  • examples/jest/src/artifacts/artifacts.test.ts
  • examples/jest/src/artifacts/media.test.ts
  • examples/vitest/artifacts.test.ts
  • examples/vitest/junit.xml
  • examples/vitest/package.json
  • examples/vitest/properties-junit.xml
  • examples/vitest/vitest.config.ts
  • packages/cmd/package.json
  • packages/cmd/src/api/create-run.ts
  • packages/cmd/src/helpers/index.ts
  • packages/cmd/src/lib/fs.ts
  • packages/cmd/src/services/convert/__tests__/convert-artifacts.test.ts
  • packages/cmd/src/services/convert/__tests__/fixtures/index.ts
  • packages/cmd/src/services/convert/index.ts
  • packages/cmd/src/services/convert/postman/instances.ts
  • packages/cmd/src/services/convert/utils.ts
  • packages/cmd/src/services/upload/index.ts
  • packages/cmd/src/types.ts
  • packages/cmd/tsup.config.ts
  • packages/jest/README.md
  • packages/jest/jest.config.cjs
  • packages/jest/package.json
  • packages/jest/src/api.ts
  • packages/jest/src/artifacts.ts
  • packages/jest/src/index.ts
  • packages/jest/src/lib/fs.ts
  • packages/jest/src/lib/index.ts
  • packages/jest/src/reporter.artifacts.test.ts
  • packages/jest/src/reporter.ts
  • packages/jest/src/types.ts

| Feature | Plan (Docs) | Implementation (Current State) | Status |
| :--- | :--- | :--- | :--- |
| **Artifact Levels** | Support **Spec**, **Test**, and **Attempt** levels. | Only **Attempt-level** is fully implemented. | ⚠️ Partial |
| **Discovery Mechanism** | Parse custom **JUnit XML properties** (e.g., `currents.artifact.path`). | Parses **Console Logs** for markers (`[[ATTACHMENT|path]]`). | ⚠️ Changed |
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Escape the pipe character in the table cell.

The | character inside `[[ATTACHMENT|path]]` is being interpreted as a column delimiter, causing the table to have mismatched columns. Escape it with a backslash.

📝 Proposed fix
-| **Discovery Mechanism** | Parse custom **JUnit XML properties** (e.g., `currents.artifact.path`). | Parses **Console Logs** for markers (`[[ATTACHMENT|path]]`). | ⚠️ Changed |
+| **Discovery Mechanism** | Parse custom **JUnit XML properties** (e.g., `currents.artifact.path`). | Parses **Console Logs** for markers (`[[ATTACHMENT\|path]]`). | ⚠️ Changed |
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
| **Discovery Mechanism** | Parse custom **JUnit XML properties** (e.g., `currents.artifact.path`). | Parses **Console Logs** for markers (`[[ATTACHMENT|path]]`). | ⚠️ Changed |
| **Discovery Mechanism** | Parse custom **JUnit XML properties** (e.g., `currents.artifact.path`). | Parses **Console Logs** for markers (`[[ATTACHMENT\|path]]`). | ⚠️ Changed |
🧰 Tools
🪛 markdownlint-cli2 (0.21.0)

[warning] 10-10: Table column count
Expected: 4; Actual: 5; Too many cells, extra data will be missing

(MD056, table-column-count)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/client-side-gap-analysis.md` at line 10, The table cell containing the
marker `[[ATTACHMENT|path]]` is breaking the Markdown table; update the cell so
the pipe is escaped inside the inline code span (change `[[ATTACHMENT|path]]` to
`[[ATTACHMENT\|path]]` within the backticks) so the Markdown parser treats it as
literal text and the table columns stay aligned.

"scripts": {
"test": "jest",
"report": "CURRENTS_API_URL=http://localhost:1234 currents --project-id=xW2Ijf --key=9bqJY1huXL2l3ONF",
"report": "CURRENTS_API_URL=http://localhost:1234 currents --project-id=PrZ3j8 --key=IRmZjZWBkkd3jqve",
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Hardcoded API key should be replaced with environment variable.

The API key IRmZjZWBkkd3jqve is committed to the repository. Even for local development or test environments, hardcoding credentials is a security risk and bad practice. Use an environment variable instead.

🔒 Proposed fix
-    "report": "CURRENTS_API_URL=http://localhost:1234 currents --project-id=PrZ3j8 --key=IRmZjZWBkkd3jqve",
+    "report": "CURRENTS_API_URL=http://localhost:1234 currents --project-id=${CURRENTS_PROJECT_ID:-PrZ3j8} --key=${CURRENTS_KEY}",

Alternatively, if this is intentionally a public/test key for demonstration purposes, add a comment in the repository documenting this, and consider using a clearly named test key that cannot be mistaken for a production credential.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"report": "CURRENTS_API_URL=http://localhost:1234 currents --project-id=PrZ3j8 --key=IRmZjZWBkkd3jqve",
"report": "CURRENTS_API_URL=http://localhost:1234 currents --project-id=${CURRENTS_PROJECT_ID:-PrZ3j8} --key=${CURRENTS_KEY}",
🧰 Tools
🪛 Gitleaks (8.30.0)

[high] 8-8: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/jest/package.json` at line 8, The package.json "report" script
currently hardcodes the API key IRmZjZWBkkd3jqve; replace that literal with an
environment variable (e.g. CURRENTS_API_KEY) so the "report" script uses the env
var instead of the committed key, and update any README or comments to document
setting CURRENTS_API_KEY for local/dev use (or explicitly mark the key as a
non-production test key if it must remain visible).

"scripts": {
"test": "vitest --reporter=default --reporter=junit --outputFile=./junit.xml || true",
"convert": "npx currents convert --input-format=junit --input-file=./junit.xml --framework=vitest",
"report": "CURRENTS_API_URL=http://localhost:1234 currents --project-id=PrZ3j8 --key=IRmZjZWBkkd3jqve",
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Hardcoded API key detected in example script.

Even in example code, hardcoding API keys is a security risk as it can lead to accidental credential exposure. Consider using an environment variable placeholder instead.

🔒 Proposed fix to use environment variable
-    "report": "CURRENTS_API_URL=http://localhost:1234 currents --project-id=PrZ3j8 --key=IRmZjZWBkkd3jqve",
+    "report": "CURRENTS_API_URL=http://localhost:1234 currents --project-id=${CURRENTS_PROJECT_ID:-PrZ3j8} --key=${CURRENTS_API_KEY}",
🧰 Tools
🪛 Gitleaks (8.30.0)

[high] 9-9: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/vitest/package.json` at line 9, The "report" npm script currently
hardcodes an API key and should be changed to use an environment variable
instead: update the "report" script (the "report" entry in package.json) to read
the key from an env var (e.g., CURRENTS_API_KEY) rather than the literal
"IRmZjZWBkkd3jqve", and document that callers must set CURRENTS_API_KEY (and
keep the existing CURRENTS_API_URL handling) so credentials are not stored in
the example.

Comment on lines +91 to +110
export async function uploadStdout(
instanceId: string,
stdout: string,
config: RunCreationConfig
) {
warn(`Uploading stdout for instance ${instanceId} with ${stdout.length} bytes`);
try {
return makeRequest<void, { stdout: string }>(ClientType.API, {
url: `instances/${instanceId}/stdout`,
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
data: { stdout },
});
} catch (e) {
debug('Error uploading stdout: %o', e);
throw e;
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

The try-catch is ineffective and log level is inappropriate.

Two issues:

  1. Missing await: The makeRequest call isn't awaited, so the Promise is returned immediately and any rejection won't be caught by the try-catch block.

  2. Inappropriate log level: Using warn() for routine upload operations pollutes warning logs. This should use debug() for consistency with other API functions in this file.

🐛 Proposed fix
 export async function uploadStdout(
   instanceId: string,
   stdout: string,
   config: RunCreationConfig
 ) {
-  warn(`Uploading stdout for instance ${instanceId} with ${stdout.length} bytes`);
+  debug('Uploading stdout for instance %s with %d bytes', instanceId, stdout.length);
   try {
-    return makeRequest<void, { stdout: string }>(ClientType.API, {
+    return await makeRequest<void, { stdout: string }>(ClientType.API, {
       url: `instances/${instanceId}/stdout`,
       method: 'PUT',
       headers: {
         'Content-Type': 'application/json',
       },
       data: { stdout },
     });
   } catch (e) {
     debug('Error uploading stdout: %o', e);
     throw e;
   }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/cmd/src/api/create-run.ts` around lines 91 - 110, In uploadStdout,
change the initial warn() call to debug() and await the makeRequest promise so
rejections are caught by the try/catch; specifically, in the uploadStdout
function replace warn(...) with debug(...) and call await makeRequest<void, {
stdout: string }>(ClientType.API, { ... }) inside the try block (keeping the
existing debug('Error uploading stdout: %o', e) in the catch).

Comment on lines +19 to +55
function extractAttachmentsFromLog(
log: string
): { sourcePath: string; ext: string; type?: string; contentType?: string }[] {
const out: { sourcePath: string; ext: string; type?: string; contentType?: string }[] = [];

// Legacy format: [[ATTACHMENT|path]]
const matches = log.matchAll(/\[\[ATTACHMENT\|([^\]]+)\]\]/g);
for (const match of matches) {
const sourcePath = match[1];
const ext = extname(sourcePath).slice(1).toLowerCase();
out.push({ sourcePath, ext });
}

// New JSON format: currents.artifact.{"path":...}
// The log might contain multiple artifacts or lines.
// We need to scan for "currents.artifact.{...}"
// Since logs can be messy, let's use a regex that captures the JSON part.
const jsonMatches = log.matchAll(/currents\.artifact\.(\{.*?\})/g);
for (const match of jsonMatches) {
try {
const artifact = JSON.parse(match[1]);
if (artifact.path) {
const ext = extname(artifact.path).slice(1).toLowerCase();
out.push({
sourcePath: artifact.path,
ext,
type: artifact.type,
contentType: artifact.contentType
});
}
} catch (e) {
// Ignore invalid JSON
}
}

return out;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

rg -n 'extractAttachmentsFromLog' --type ts

Repository: currents-dev/currents-reporter

Length of output: 520


🏁 Script executed:

#!/bin/bash
# Search for function calls (with parentheses)
echo "=== Function calls (invocations) ==="
rg 'extractAttachmentsFromLog\s*\(' --type ts

echo ""
echo "=== Export statements ==="
rg 'export.*extractAttachmentsFromLog' --type ts

echo ""
echo "=== Import statements ==="
rg 'import.*extractAttachmentsFromLog' --type ts

Repository: currents-dev/currents-reporter

Length of output: 243


Function extractAttachmentsFromLog is unused and should be removed.

This function is defined but never called anywhere in the codebase. It is not exported or imported elsewhere. Remove it to keep the codebase clean, or if it's intended for future use, add a TODO comment and document the expected usage.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/cmd/src/services/convert/index.ts` around lines 19 - 55, The
function extractAttachmentsFromLog is dead code; remove its entire definition
(the extractAttachmentsFromLog function block) from the file, and also remove
any now-unused imports it relies on (e.g., extname) to avoid lint errors;
alternatively, if you intend to keep it, replace the body with a short TODO
comment and export or document expected usage, but by default delete the
function and cleanup related unused symbols like extname.

Comment on lines +157 to +159
if (response.artifactUploadUrls && response.artifactUploadUrls.length > 0) {
info('No artifacts handling: initial run created without instances');
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Log message logic appears inverted.

The message "No artifacts handling: initial run created without instances" is logged when artifactUploadUrls exists and has items. This seems backwards - the presence of upload URLs suggests artifacts are being handled.

If the intent is to note that the initial run (with empty instances) won't process artifacts yet, consider rewording for clarity.

📝 Suggested rewording
       if (response.artifactUploadUrls && response.artifactUploadUrls.length > 0) {
-        info('No artifacts handling: initial run created without instances');
+        debug('Initial run created; artifact upload URLs received, will process with instance chunks');
       }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (response.artifactUploadUrls && response.artifactUploadUrls.length > 0) {
info('No artifacts handling: initial run created without instances');
}
if (response.artifactUploadUrls && response.artifactUploadUrls.length > 0) {
debug('Initial run created; artifact upload URLs received, will process with instance chunks');
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/cmd/src/services/upload/index.ts` around lines 157 - 159, The log is
inverted: the info(...) message is emitted when response.artifactUploadUrls
exists and has items. Update the condition to log only when there are no upload
URLs (e.g., if (!response.artifactUploadUrls ||
response.artifactUploadUrls.length === 0)) and reword the message for clarity
(suggestion: "No artifacts to handle: initial run created without instances").
Ensure you update the code that references response.artifactUploadUrls and the
info(...) call accordingly.

Comment on lines +41 to +44
}else {
type = type || 'attachment';
contentType = contentType || 'text/plain';
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Default contentType differs from helpers package.

The default fallback contentType here is 'text/plain', but in packages/cmd/src/helpers/index.ts (line 37), the fallback is 'application/octet-stream'. This inconsistency could cause unexpected behavior when artifacts are processed.

♻️ Align with helpers package
     }else {
       type = type || 'attachment';
-      contentType = contentType || 'text/plain';
+      contentType = contentType || 'application/octet-stream';
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
}else {
type = type || 'attachment';
contentType = contentType || 'text/plain';
}
}else {
type = type || 'attachment';
contentType = contentType || 'application/octet-stream';
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/jest/src/api.ts` around lines 41 - 44, The fallback contentType in
this file currently sets contentType = 'text/plain' while the helpers package
uses 'application/octet-stream'; update the default in the else branch where
variables type and contentType are assigned (the contentType and type assignment
block) to use 'application/octet-stream' so the artifact processing defaults are
consistent with the helpers package; ensure no other logic depends on plain text
as the implicit default.

Comment on lines +119 to +130
export function getAttempt(): number {
try {
const symbols = Object.getOwnPropertySymbols(global);
const stateSymbol = symbols.find(s => s.toString() === 'Symbol(JEST_STATE_SYMBOL)');

if (stateSymbol) {
// @ts-ignore
const jestState = global[stateSymbol];
if (jestState && jestState.currentlyRunningTest && typeof jestState.currentlyRunningTest.invocations === 'number') {
return jestState.currentlyRunningTest.invocations - 1;
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🌐 Web query:

Jest 29 JEST_STATE_SYMBOL public API documentation

💡 Result:

JEST_STATE_SYMBOL is not a documented public Jest API. In Jest 29.7.0, it’s just the description string used when jest-circus creates an internal symbol:

  • packages/jest-circus/src/types.ts: export const STATE_SYM = Symbol('JEST_STATE_SYMBOL'); (this symbol is used as a key on global[STATE_SYM]). (raw.githubusercontent.com)
  • packages/jest-circus/src/state.ts: the circus runner state is stored/read via global[STATE_SYM] and exposed via resetState(), getState(), setState(). (raw.githubusercontent.com)

If you’re looking for the “public” way to access that state, use the exported functions (instead of trying to access the symbol):

Note: jest-circus explicitly warns that mutating event/state is unsupported and may break without warning. (raw.githubusercontent.com)

Citations:


🏁 Script executed:

# Find and read the complete function to see the fallback heuristic
fd -t f "api.ts" packages/jest/src/

Repository: currents-dev/currents-reporter

Length of output: 98


🏁 Script executed:

# Check the Jest version requirement in package.json
fd -t f "package.json" packages/jest/

Repository: currents-dev/currents-reporter

Length of output: 100


🏁 Script executed:

# Read the complete getAttempt function
cat -n packages/jest/src/api.ts | sed -n '119,162p'

Repository: currents-dev/currents-reporter

Length of output: 1742


🏁 Script executed:

# Check Jest version constraint
cat packages/jest/package.json

Repository: currents-dev/currents-reporter

Length of output: 2338


Jest's JEST_STATE_SYMBOL is not a public API; document version dependency and consider the fallback approach as primary.

The symbol lookup (lines 119-130) accesses Jest's internal state via an undocumented implementation detail. Web verification confirms JEST_STATE_SYMBOL is intentionally internal to jest-circus (Jest 29.7.0); jest-circus explicitly warns that state mutation is unsupported and may break without notice.

The current code has good defensive layers: try-catch, symbol presence check, and a fallback heuristic (lines 132-162). However, the fallback also relies on internal APIs (expect.getState()/expect.setState()).

Consider:

  1. Add a comment noting this code is tested against Jest 29.5.0+ and may require updates for future Jest versions
  2. Reorder the logic to rely on the fallback heuristic as the primary approach, with the symbol lookup as an optimization only
  3. Document why the Symbol approach exists (performance, reliability) if that's the intent
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/jest/src/api.ts` around lines 119 - 130, Update getAttempt to treat
the jest-internal Symbol lookup as an optional optimization and make the
fallback heuristic (using expect.getState()/expect.setState()) the primary code
path: add a top-of-function comment stating this behavior and that the
implementation is tested against Jest 29.5.0+ and may break on future Jest
versions; move the existing Symbol(JEST_STATE_SYMBOL) branch to run only if the
fallback fails or as a short-circuit optimization, and add a brief inline
comment above the Symbol branch explaining it is used for
performance/reliability when available but relies on internal APIs; keep the
existing try/catch and defensive checks around global[stateSymbol] and the
expect-based fallback intact and ensure the function still returns the same
fallback value when symbol access is unavailable.

Comment on lines +391 to +434
function parseAttachmentLogs(
consoleEntries: TestResult['console'],
testFilePath: string
): PropertyLog[] {
return (consoleEntries ?? [])
.map((log) => {
// Look for [[ATTACHMENT|path]] or [[ATTACHMENT|path|level]] pattern

// We use a regex that optionally captures a second parameter (level)
// Format: [[CURRENTS.ATTACHMENT|path]] or [[CURRENTS.ATTACHMENT|path|level]]
// The path cannot contain ']', so we use [^\]|]+ for path segment if followed by another |?
// Simpler: [[CURRENTS.ATTACHMENT| part1 (| part2)? ]]

const matches = [...log.message.matchAll(/\[\[CURRENTS\.ATTACHMENT\|([^|\]]+)(?:\|([^\]]+))?\]\]/g)];
if (matches.length === 0) return [];

const line = getLineFromOrigin(log.origin, testFilePath);

return matches.map(match => {
const filePath = match[1].trim();
const levelRaw = match[2]?.trim();

// Validate level if provided, default to 'attempt'
let level = 'attempt';
if (levelRaw && ['spec', 'test', 'attempt'].includes(levelRaw)) {
level = levelRaw;
}

const artifact = {
path: filePath,
type: inferArtifactType(filePath),
contentType: 'application/octet-stream',
level: level,
};

return {
key: 'JSON_ARTIFACT',
value: JSON.stringify(artifact),
line
};
});
})
.flat();
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, find and read the documentation file
fd artifact-workflow.md

Repository: currents-dev/currents-reporter

Length of output: 99


🏁 Script executed:

# Search for ATTACHMENT patterns in the codebase to understand usage
rg "ATTACHMENT" --type=ts --type=md -B2 -A2

Repository: currents-dev/currents-reporter

Length of output: 11239


🏁 Script executed:

# Look specifically for examples and test files mentioning ATTACHMENT
fd -e test.ts -e spec.ts -e example.ts | xargs rg -l "ATTACHMENT" 2>/dev/null || echo "No test files found with ATTACHMENT"

Repository: currents-dev/currents-reporter

Length of output: 177


Regex does not match documented marker format.

The regex on line 404 of packages/jest/src/artifacts.ts expects [[CURRENTS.ATTACHMENT|...]], but documentation (docs/artifact-workflow.md lines 75, 87, 150) and existing code in packages/cmd/src/services/convert/__tests__/convert-artifacts.test.ts use [[ATTACHMENT|...]] without the CURRENTS. prefix. The vitest examples also use both formats, indicating this inconsistency affects multiple code paths.

Update the regex to accept both formats for backward compatibility:

Proposed fix
-      const matches = [...log.message.matchAll(/\[\[CURRENTS\.ATTACHMENT\|([^|\]]+)(?:\|([^\]]+))?\]\]/g)];
+      const matches = [...log.message.matchAll(/\[\[(?:CURRENTS\.)?ATTACHMENT\|([^|\]]+)(?:\|([^\]]+))?\]\]/g)];
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function parseAttachmentLogs(
consoleEntries: TestResult['console'],
testFilePath: string
): PropertyLog[] {
return (consoleEntries ?? [])
.map((log) => {
// Look for [[ATTACHMENT|path]] or [[ATTACHMENT|path|level]] pattern
// We use a regex that optionally captures a second parameter (level)
// Format: [[CURRENTS.ATTACHMENT|path]] or [[CURRENTS.ATTACHMENT|path|level]]
// The path cannot contain ']', so we use [^\]|]+ for path segment if followed by another |?
// Simpler: [[CURRENTS.ATTACHMENT| part1 (| part2)? ]]
const matches = [...log.message.matchAll(/\[\[CURRENTS\.ATTACHMENT\|([^|\]]+)(?:\|([^\]]+))?\]\]/g)];
if (matches.length === 0) return [];
const line = getLineFromOrigin(log.origin, testFilePath);
return matches.map(match => {
const filePath = match[1].trim();
const levelRaw = match[2]?.trim();
// Validate level if provided, default to 'attempt'
let level = 'attempt';
if (levelRaw && ['spec', 'test', 'attempt'].includes(levelRaw)) {
level = levelRaw;
}
const artifact = {
path: filePath,
type: inferArtifactType(filePath),
contentType: 'application/octet-stream',
level: level,
};
return {
key: 'JSON_ARTIFACT',
value: JSON.stringify(artifact),
line
};
});
})
.flat();
}
function parseAttachmentLogs(
consoleEntries: TestResult['console'],
testFilePath: string
): PropertyLog[] {
return (consoleEntries ?? [])
.map((log) => {
// Look for [[ATTACHMENT|path]] or [[ATTACHMENT|path|level]] pattern
// We use a regex that optionally captures a second parameter (level)
// Format: [[CURRENTS.ATTACHMENT|path]] or [[CURRENTS.ATTACHMENT|path|level]]
// The path cannot contain ']', so we use [^\]|]+ for path segment if followed by another |?
// Simpler: [[CURRENTS.ATTACHMENT| part1 (| part2)? ]]
const matches = [...log.message.matchAll(/\[\[(?:CURRENTS\.)?ATTACHMENT\|([^|\]]+)(?:\|([^\]]+))?\]\]/g)];
if (matches.length === 0) return [];
const line = getLineFromOrigin(log.origin, testFilePath);
return matches.map(match => {
const filePath = match[1].trim();
const levelRaw = match[2]?.trim();
// Validate level if provided, default to 'attempt'
let level = 'attempt';
if (levelRaw && ['spec', 'test', 'attempt'].includes(levelRaw)) {
level = levelRaw;
}
const artifact = {
path: filePath,
type: inferArtifactType(filePath),
contentType: 'application/octet-stream',
level: level,
};
return {
key: 'JSON_ARTIFACT',
value: JSON.stringify(artifact),
line
};
});
})
.flat();
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/jest/src/artifacts.ts` around lines 391 - 434, The regex in
parseAttachmentLogs currently only matches the marker with the CURRENTS. prefix;
update the pattern used in parseAttachmentLogs to accept both forms
([[ATTACHMENT|...]] and [[CURRENTS.ATTACHMENT|...]]). Modify the matchAll regex
(in parseAttachmentLogs) to make the "CURRENTS\." prefix optional (e.g. allow an
optional group before ATTACHMENT) while preserving the existing capture groups
for path and optional level, then run/adjust tests in packages/cmd and the
convert-artifacts tests to ensure backward compatibility.

Comment on lines +363 to +383
_testArtifacts: index === 0 ? testArtifacts : [],

errors,
error: errors[0],
};
})
),
};
}
)
);

const flakyCount = tests.filter((t) => t.isFlaky).length;

// Move test-level artifacts from attempts to test object
tests.forEach((t) => {
const firstAttempt = t.attempts[0];
if (firstAttempt && (firstAttempt as any)._testArtifacts) {
t.artifacts = (firstAttempt as any)._testArtifacts;
delete (firstAttempt as any)._testArtifacts;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Test-level artifacts are currently sourced only from attempt 0.

Line 363 gates _testArtifacts to index === 0, and Lines 379-383 only promote from the first attempt. If test-level artifacts are emitted during retries, they can be lost.

💡 Suggested fix
-                  _testArtifacts: index === 0 ? testArtifacts : [],
+                  _testArtifacts: testArtifacts,
...
-    tests.forEach((t) => {
-      const firstAttempt = t.attempts[0];
-      if (firstAttempt && (firstAttempt as any)._testArtifacts) {
-        t.artifacts = (firstAttempt as any)._testArtifacts;
-        delete (firstAttempt as any)._testArtifacts;
-      }
-    });
+    tests.forEach((t) => {
+      const collected = t.attempts.flatMap(
+        (a) => ((a as any)._testArtifacts as any[]) ?? []
+      );
+      if (collected.length > 0) {
+        t.artifacts = collected;
+      }
+      t.attempts.forEach((a) => delete (a as any)._testArtifacts);
+    });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
_testArtifacts: index === 0 ? testArtifacts : [],
errors,
error: errors[0],
};
})
),
};
}
)
);
const flakyCount = tests.filter((t) => t.isFlaky).length;
// Move test-level artifacts from attempts to test object
tests.forEach((t) => {
const firstAttempt = t.attempts[0];
if (firstAttempt && (firstAttempt as any)._testArtifacts) {
t.artifacts = (firstAttempt as any)._testArtifacts;
delete (firstAttempt as any)._testArtifacts;
}
_testArtifacts: testArtifacts,
errors,
error: errors[0],
};
})
),
};
}
)
);
const flakyCount = tests.filter((t) => t.isFlaky).length;
// Move test-level artifacts from attempts to test object
tests.forEach((t) => {
const collected = t.attempts.flatMap(
(a) => ((a as any)._testArtifacts as any[]) ?? []
);
if (collected.length > 0) {
t.artifacts = collected;
}
t.attempts.forEach((a) => delete (a as any)._testArtifacts);
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/jest/src/reporter.ts` around lines 363 - 383, The code currently
only sets `_testArtifacts` when `index === 0` and later promotes artifacts from
`t.attempts[0]`, which drops artifacts emitted on retries; remove the `index ===
0` gating so each attempt can set `_testArtifacts`, and change the promotion
logic in the `tests.forEach` block to locate the first (or last) attempt that
has a `_testArtifacts` property (e.g., find(attempt => attempt._testArtifacts)
or scan from last to first) and assign that attempt's `_testArtifacts` to
`t.artifacts`, then delete the `_testArtifacts` from that attempt; update
references to `_testArtifacts`, `tests.forEach`, and `t.attempts` accordingly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants