Skip to content

Export applySmartSnap functionality from CLI#2207

Merged
prklm10 merged 37 commits into
masterfrom
PPLT-5302
Jun 2, 2026
Merged

Export applySmartSnap functionality from CLI#2207
prklm10 merged 37 commits into
masterfrom
PPLT-5302

Conversation

@RaghavsBrowserStack

@RaghavsBrowserStack RaghavsBrowserStack commented Apr 30, 2026

Copy link
Copy Markdown
Contributor

Summary

Exports the applySmartSnap pipeline and SmartSnapBailError from @percy/cli-command, plus the SmartSnap client endpoints on @percy/client. Together these let @percy/cli-storybook filter snapshots down to those actually affected by a diff.

What's included

  • @percy/client: getSmartsnapSnapshotNameToCommit(), generateSmartsnapGraph(), and smartsnap_graph support on the existing getStatus() poll helper.
  • @percy/cli-command:
    • applySmartSnap(percy, snapshots, smartSnapConfig, buildDir) — orchestrates the pipeline: git diff vs. base build commit → .storybook/lockfile bail checks → stats parse → graph job → snapshot filter. Honors baseline, untraced, bailOnChanges, statsFile, and trace config.
    • SmartSnapBailError — recoverable failures throw this so callers fall back to a full snapshot set instead of crashing.
    • lockfileDiff.js — top-level dep diff via snyk-nodejs-lockfile-parser (optionalDep, Node ≥18).
    • graphTrace.js + graphTraceTemplate.html — renders an interactive Drawflow graph when trace: true.

Notes

  • snyk-nodejs-lockfile-parser is an optionalDependency because it requires Node ≥18 while the CLI supports ≥14. Missing parser → bail to full set, not a crash.
  • Trace HTML loads Drawflow from a CDN (with SRI); offline trace rendering is not supported.
  • Tests added for the three new client methods. Unit tests for the orchestration layer are tracked separately.

Test plan

  • yarn test in packages/client and packages/cli-command passes.
  • Smoke test via @percy/cli-storybook consuming applySmartSnap end-to-end.

@RaghavsBrowserStack
RaghavsBrowserStack marked this pull request as ready for review May 7, 2026 11:57
@RaghavsBrowserStack
RaghavsBrowserStack requested a review from a team as a code owner May 7, 2026 11:57
Comment thread packages/cli-command/src/smartsnap.js Fixed
Comment thread packages/cli-command/src/smartsnap.js Fixed
Comment thread packages/cli-command/src/smartsnap.js Fixed
Comment thread packages/cli-command/src/smartsnap.js Fixed
Comment thread packages/cli-command/src/smartsnap.js Fixed
Comment thread packages/cli-command/src/smartsnap.js Fixed
Comment thread packages/cli-command/src/smartsnap.js Fixed
Comment thread packages/cli-command/src/smartsnap.js Fixed
Comment thread packages/cli-command/src/graphTrace.js Fixed
RaghavsBrowserStack and others added 3 commits May 28, 2026 16:03
…egexp'

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
@RaghavsBrowserStack RaghavsBrowserStack changed the title Add smartsnap endpoints to percy client Export applySmartSnap functionality from CLI May 29, 2026
@RaghavsBrowserStack

Copy link
Copy Markdown
Contributor Author

Claude Code PR Review

PR: #2207Head: f7fdfd5Reviewers: stack:code-reviewer (ran but returned no structured findings — analysis completed by the orchestrator)

Summary

Adds a SmartSnap feature to the Percy CLI: it streams a Storybook build's module-graph stats (stream-json), diffs the project's lockfile via an optional snyk-nodejs-lockfile-parser, asks a new backend graph API which stories a change affects, and renders an interactive HTML trace. New library code lives in @percy/cli-command (smartsnap.js, lockfileDiff.js, graphTrace.js, graphTraceTemplate.html) with two new @percy/client endpoints. The PR also bundles three unrelated changes: a schema-driven httpReadOnly block on discovery.launchOptions.executable/args over /percy/config, propagation of labels onto POA comparisonData, a discovery test race fix, and deletion of test/regression/pages/comprehensive.html. Plus the beta.2→beta.4 version bump and yarn.lock.

Review Table

Priority Category Check Status Notes
High Security No hardcoded secrets or credentials Pass None introduced.
High Security Authentication/authorization checks present Pass SmartSnap endpoints are token/project-scoped via the existing Authorization header; query context built with URLSearchParams (properly encoded).
High Security Input validation and sanitization Pass statsFile hardened (path.basename + ^[\w.-]+\.json$ + isFile); assertSafeRef rejects option-like/unsafe git refs; git run via spawnSync arg-array (no shell); trace template uses safeJson (escapes </, comments, U+2028/U+2029) + escapeHtml; CDN assets pinned with SRI. Strong.
High Security No IDOR — resource ownership validated N/A No direct object references exposed.
High Security No SQL injection (parameterized queries) N/A No SQL.
High Correctness Logic is correct, handles edge cases Fail replaceAll unsupported on declared Node 14 engine (F1); git failures bypass the documented bail contract (F2).
High Correctness Error handling is explicit, no swallowed exceptions Fail git() throws plain Error, not SmartSnapBailError, for gitDiffNames/gitProjectRoot/git show package.json — contradicts the "any recoverable failure → full snapshot" contract (F2).
High Correctness No race conditions or concurrency issues Pass Sequential enqueue→poll; buildId namespaces concurrent builds. Discovery test race fixed.
Medium Testing New code has corresponding tests Fail ~770 lines of the most error-prone new logic (applySmartSnap, diffLockfileDeps, computeLayout/safeJson) have no unit tests; only the thin client methods are covered (F3).
Medium Testing Error paths and edge cases tested Fail None of the SmartSnap bail paths, path-normalization, or lockfile-diff branches are exercised (F3).
Medium Testing Existing tests still pass (no regressions) N/A Not run as part of this review.
Medium Performance No N+1 queries or unbounded data fetching Pass Stats parsed via streaming JSON, not full read.
Medium Performance Long-running tasks use background jobs Pass Graph generation enqueued server-side and polled.
Medium Quality Follows existing codebase patterns Pass Matches client/logger/config conventions.
Medium Quality Changes are focused (single concern) Fail Bundles SmartSnap + httpReadOnly security fix + labels propagation + discovery test fix + fixture deletion (F5).
Low Quality Meaningful names, no dead code Fail applySmartSnap is exported but has no in-repo caller (consumed externally), so it ships unexercised here (F4).
Low Quality Comments explain why, not what Pass Comments are unusually thorough and explain rationale well.
Low Quality No unnecessary dependencies added Pass glob-to-regexp, stream-json (deps) and snyk-nodejs-lockfile-parser (optional) are all justified.

Findings

F1

  • File: packages/cli-command/src/smartsnap.js:22
  • Severity: High
  • Reviewer: orchestrator
  • Issue: const stripNull = s => (... ? s.replaceAll(NULL_CHAR, '') : s) uses String.prototype.replaceAll, added in Node 15. packages/cli-command/package.json declares "engines": { "node": ">=14" }, and lockfileDiff.js explicitly documents "the CLI supports Node >=14". On Node 14 this throws TypeError: s.replaceAll is not a function for every module path during readStats, surfacing as a plain TypeError (not a SmartSnapBailError) and aborting the run. Babel transpiles syntax, not prototype methods, so it is not polyfilled unless core-js/useBuiltIns is configured.
  • Suggestion: Use s.split(NULL_CHAR).join('') (keeps the no-control-regex avoidance), or raise the engine floor to >=16 if Node 14 is no longer truly supported. Verify against the real minimum runtime.

F2

  • File: packages/cli-command/src/smartsnap.js:38-46, 1021, 1042, 1088 (diff lines)
  • Severity: Medium
  • Reviewer: orchestrator
  • Issue: git() throws a plain Error on non-zero exit. gitDiffNames(baseRef), gitProjectRoot(), and the unwrapped oldPackageJson = git(['show', ...]) therefore escape the module's stated contract ("On any recoverable failure, returns the input list unchanged so the build runs as a full snapshot pass"). Most likely trigger: in CI shallow clones (fetch-depth: 1), the API-predicted base_build_commit_sha is often not present locally, so git diff <sha> HEAD fails — a hard error instead of a graceful full-snapshot fallback. (The git show for the lockfile is correctly wrapped; the diff/root/package.json calls are not.)
  • Suggestion: Wrap the diff/root/git show package.json calls and re-throw as SmartSnapBailError, and/or git fetch --depth=1 origin <baseRef> before diffing so shallow-clone CI still works.

F3

  • File: packages/cli-command/src/smartsnap.js (whole file), lockfileDiff.js, graphTrace.js
  • Severity: Medium
  • Reviewer: orchestrator
  • Issue: The PR's title commit is "write test for smartsnap api's", but tests only cover the ~50-line client wrappers (client.test.js). The ~770 lines of genuinely tricky logic — applySmartSnap's bail ladder, monorepo path normalization across invocation-dir/project-root frames, diffLockfileDeps resolved-vs-range diffing, computeLayout column propagation, and safeJson XSS escaping — have zero unit coverage. computeLayout/safeJson and diffLockfileDeps are pure and trivial to test in isolation.
  • Suggestion: Add unit tests for computeLayout, safeJson (assert </script>, <!--, U+2028/U+2029 are neutralized), diffLockfileDeps (add/remove/version-bump/range-only), and the key applySmartSnap bail branches (no buildId, .storybook hit, multi-dir manifest, empty diff).

F4

  • File: packages/cli-command/src/index.js:3 / smartsnap.js:205 (diff)
  • Severity: Low
  • Reviewer: orchestrator
  • Issue: applySmartSnap/SmartSnapBailError are exported but never called anywhere in this repo (the consumer is presumably an external @percy/storybook SDK). Combined with the absence of tests (F3), the entire feature ships in this PR without being exercised by any code path here.
  • Suggestion: Confirm the consuming PR lands in lockstep, or add at least one integration test that drives applySmartSnap end-to-end with a stubbed percy.client.

F5

  • File: PR scope / test/regression/pages/comprehensive.html (deleted)
  • Severity: Low
  • Reviewer: orchestrator
  • Issue: The PR bundles four unrelated changes alongside SmartSnap (the httpReadOnly launchOptions security block, POA labels propagation, a discovery test race fix, and deletion of the comprehensive.html regression fixture covering fonts/canvas/shadow-DOM/base64/SVG discovery). The deletion in particular removes asset-discovery regression coverage with no stated reason in the diff.
  • Suggestion: Confirm the comprehensive.html removal is intentional (not rebase collateral); ideally split the security/labels/test changes into their own PRs for cleaner history.

F6

  • File: packages/client/src/client.js (getStatus, diff ~line 442)
  • Severity: Low
  • Reviewer: orchestrator
  • Issue: id=${ids.join()} is interpolated into the query string unencoded. This was safe when ids were numeric (snapshot/comparison), but smartsnap_graph now passes a bundler-emitted buildId string; a value containing &, +, or whitespace would corrupt the request.
  • Suggestion: id=${ids.map(encodeURIComponent).join(',')}.

Strengths worth calling out: the security work is solid — schema-driven httpReadOnly stripping (clean, no list to maintain, well-tested), assertSafeRef + arg-array spawnSync for git, the statsFile traversal defense, and the safeJson/escapeHtml/SRI trio in the trace template. The inline comments are exemplary.


Verdict: FAIL

@RaghavsBrowserStack

Copy link
Copy Markdown
Contributor Author

F1: const stripNull = s => (... ? s.replaceAll(NULL_CHAR, '') : s) uses String.prototype.replaceAll, added in Node 15. Fixed
F2: git() throws a plain Error on non-zero exit: Fixed
F3: missing tests. Need the PR to get approve by security every single time the code changes. Need to get it completely reviewed before that's requested.
F4: applySmartSnap/SmartSnapBailError are exported but never called anywhere in this repo. Yeah completely intentionalal.
F5: The PR bundles four unrelated changes alongside SmartSnap. No it doesn't, check the files changed. The files its claiming I added are not there.
F6: id=${ids.join()} is interpolated into the query string unencoded. Its going to be 1 id, and that id is going to be a 32 char hex string. It'll be fine.

@RaghavsBrowserStack

Copy link
Copy Markdown
Contributor Author

Claude Code PR Review (re-review)

PR: #2207Head: e8ee178Reviewers: stack:code-reviewer + orchestrator verification

Re-review after the author addressed the prior round (commits f7fdfd5 "resolve comments", e8ee178 "resolve claude comments"). Each prior finding (F1–F6) was re-checked against HEAD and against the author's inline responses.

Summary

Adds the SmartSnap feature to the Percy CLI: streams a Storybook build's module-graph stats (stream-json), diffs the project lockfile via the optional snyk-nodejs-lockfile-parser, asks a new backend graph API which stories a change affects, and renders an interactive HTML trace. New code lives in @percy/cli-command (smartsnap.js, lockfileDiff.js, graphTrace.js, graphTraceTemplate.html) plus two new @percy/client endpoints with tests.

Scope correction (resolves prior F5)

The earlier review computed the diff against a stale local master (4 commits behind origin/master). That pulled already-merged PRs #2242 (httpReadOnly launchOptions) and #2239 (POA labels, discovery test, comprehensive.html deletion) into the diff as if they were part of this PR. Against the true base origin/master...HEAD, this PR touches only: packages/cli-command/{smartsnap,lockfileDiff,graphTrace,graphTraceTemplate,index}, packages/client/src/client.js (+ test), and yarn.lock. The author was correct — the PR is focused on SmartSnap; prior F5 is retracted.

Review Table

Priority Category Check Status Notes
High Security No hardcoded secrets or credentials Pass None introduced.
High Security Authentication/authorization checks present Pass Endpoints token/project-scoped via existing Authorization header; query built with URLSearchParams.
High Security Input validation and sanitization Pass statsFile hardened (path.basename + ^[\w.-]+\.json$ + isFile); assertSafeRef rejects unsafe git refs; git via spawnSync arg-array (no shell); safeJson escapes </, <!--, -->/--!>, U+2028/U+2029; escapeHtml + SRI on CDN assets.
High Security No IDOR — resource ownership validated N/A No direct object references exposed.
High Security No SQL injection (parameterized queries) N/A No SQL.
High Correctness Logic is correct, handles edge cases Pass Prior F1 (replaceAll) fixed; prior F2 (git bail) fixed. See below.
High Correctness Error handling is explicit, no swallowed exceptions Pass git() now throws SmartSnapBailError on spawn-fail and non-zero exit, so all git calls honor the "bail to full snapshot" contract.
High Correctness No race conditions or concurrency issues Pass Sequential enqueue→poll; buildId namespaces concurrent builds.
Medium Testing New code has corresponding tests Fail Client endpoints covered; ~770 lines of smartsnap.js/lockfileDiff.js/graphTrace.js logic still uncovered. Author defers (see F3) pending full + security review before adding tests — known, accepted gap, not a code defect.
Medium Testing Error paths and edge cases tested Fail Bail ladder / path-normalization / lockfile-diff branches untested (same deferral).
Medium Testing Existing tests still pass (no regressions) N/A Not run as part of this review.
Medium Performance No N+1 queries or unbounded data fetching Pass Stats parsed via streaming JSON.
Medium Performance Long-running tasks use background jobs Pass Graph generation enqueued server-side and polled.
Medium Quality Follows existing codebase patterns Pass Matches client/logger/config conventions.
Medium Quality Changes are focused (single concern) Pass Corrected: PR is SmartSnap-only against the true base (see scope note).
Low Quality Meaningful names, no dead code Pass applySmartSnap/SmartSnapBailError exported for an external @percy/storybook consumer — intentional per author (F4).
Low Quality Comments explain why, not what Pass Comments are thorough and explain rationale (e.g. the split/join and safeJson notes).
Low Quality No unnecessary dependencies added Pass glob-to-regexp, stream-json (deps), snyk-nodejs-lockfile-parser (optional) justified.

Findings

Resolved since prior review

  • F1 — String.prototype.replaceAll on Node 14 ✅ Fixed. smartsnap.js:24 now uses s.split(NULL_CHAR).join('') with an explanatory comment (smartsnap.js:22).
  • F2 — git failures bypassing the bail contract ✅ Fixed. git() (smartsnap.js:66–74) now throws SmartSnapBailError on both spawn failure and non-zero exit, so gitDiffNames, gitProjectRoot, and every git show propagate the documented "run full snapshot set" fallback.
  • F5 — bundled unrelated changes ✅ Retracted (stale-base artifact; see scope correction). Author was correct.
  • CodeQL "Bad HTML filtering regexp" (graphTrace.js:132) ✅ Addressed — safeJson escapes the <!-- opener and both -->/--!> closers; escaping the opener alone already prevents comment injection.

Accepted by author (non-blocking)

  • F4 — applySmartSnap exported but no in-repo caller
    • Severity: Low — Author: intentional; consumer is the external @percy/storybook SDK. Accepted.
  • F6 — getStatus interpolates id=${ids.join()} unencoded (client.js:422)
    • Severity: Low — Author: value is a single 32-char hex buildId, so encoding is moot. Accepted. (Cheap hardening if ever desired: id=${ids.map(encodeURIComponent).join(',')}.)

Open (Medium, non-blocking per author's process)

  • F3 — Test coverage for SmartSnap core logic
    • File: packages/cli-command/src/{smartsnap,lockfileDiff,graphTrace}.js
    • Severity: Medium
    • Issue: The error-prone logic (bail ladder, monorepo path normalization, diffLockfileDeps, computeLayout, safeJson) has no unit tests; only the thin client wrappers are covered.
    • Author response: Deferring — every code change requires a fresh security approval, so the author wants the feature fully reviewed before tests are requested. Reasonable as a sequencing decision; tracking as a known gap rather than a defect. Suggest landing the computeLayout/safeJson/diffLockfileDeps pure-function tests (cheap, no security surface) when the test pass happens.

Verdict: PASS

Comment thread packages/cli-command/src/smartsnap.js Fixed
Comment thread packages/cli-command/src/smartsnap.js Fixed
Comment thread packages/cli-command/src/smartsnap.js Fixed
Comment thread packages/cli-command/src/smartsnap.js Fixed
Comment thread packages/cli-command/src/smartsnap.js Fixed
Comment thread packages/cli-command/src/smartsnap.js Fixed
Comment thread packages/cli-command/test/graphTrace.test.js Fixed
Comment thread packages/cli-command/src/smartsnap.js Fixed
Comment thread packages/cli-command/src/smartsnap.js Fixed
Comment thread packages/cli-command/src/smartsnap.js Fixed
Comment thread packages/cli-command/src/smartsnap.js Fixed
Comment thread packages/cli-command/src/smartsnap.js Outdated
// pick a canonical source, so bail.
const LOCKFILE_NAMES = ['package-lock.json', 'yarn.lock', 'pnpm-lock.yaml'];
// n is from the hardcoded LOCKFILE_NAMES allowlist (reviewed, approved by security)
const presentLockfiles = LOCKFILE_NAMES.filter(n => fs.existsSync(path.join(absManifestDir, n))); // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
Comment thread packages/cli-command/src/smartsnap.js Outdated
// pick a canonical source, so bail.
const LOCKFILE_NAMES = ['package-lock.json', 'yarn.lock', 'pnpm-lock.yaml'];
// n is from the hardcoded LOCKFILE_NAMES allowlist (reviewed, approved by security)
const presentLockfiles = LOCKFILE_NAMES.filter(n => fs.existsSync(path.join(absManifestDir, n))); // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
Comment thread packages/cli-command/src/smartsnap.js Fixed
Comment thread packages/cli-command/src/smartsnap.js Fixed
Comment thread packages/cli-command/src/smartsnap.js Fixed
Comment thread packages/cli-command/test/graphTrace.test.js Fixed
throw new SmartSnapBailError(`SmartSnap: invalid statsFile "${statsName}" — must be a .json filename; running full snapshot set`);
}
// statsName is path.basename'd and regex-validated above; buildDir is operator-supplied config (reviewed, approved by security)
const resolvedStatsPath = path.join(path.resolve(buildDir), statsName); // nosemgrep
throw new SmartSnapBailError(`SmartSnap: invalid statsFile "${statsName}" — must be a .json filename; running full snapshot set`);
}
// statsName is path.basename'd and regex-validated above; buildDir is operator-supplied config (reviewed, approved by security)
const resolvedStatsPath = path.join(path.resolve(buildDir), statsName); // nosemgrep
}
const manifestDir = uniqueDirs[0]; // repo-relative; '.' for root
// manifestDir is derived from git-tracked paths under projectRoot (reviewed, approved by security)
const absManifestDir = path.resolve(projectRoot, manifestDir); // nosemgrep
}
const manifestDir = uniqueDirs[0]; // repo-relative; '.' for root
// manifestDir is derived from git-tracked paths under projectRoot (reviewed, approved by security)
const absManifestDir = path.resolve(projectRoot, manifestDir); // nosemgrep
}

// lockfileName is one of the hardcoded LOCKFILE_NAMES allowlist (reviewed, approved by security)
const newLockfile = fs.readFileSync(path.join(absManifestDir, lockfileName), 'utf8'); // nosemgrep
if (oldLockfile === newLockfile) return [];

// joined with the literal 'package.json' under the resolved absManifestDir (reviewed, approved by security)
const packageJson = fs.readFileSync(path.join(absManifestDir, 'package.json'), 'utf8'); // nosemgrep
// path.resolve treats it as the target directly; otherwise it's joined
// against `invocationDir`. Then re-base against the git project root.
// rel comes from build stats file paths, re-based against projectRoot on the next line (reviewed, approved by security)
const abs = path.resolve(invocationDir, rel); // nosemgrep
// applies, without exporting the module-private helpers it delegates to.
function embeddedJson(html, name) {
// test-only helper; name is a hardcoded literal ('vertices'/'edges') from the test, not external input (reviewed, approved by security)
let match = html.match(new RegExp(`const ${name} = (.*);`)); // nosemgrep
@RaghavsBrowserStack

Copy link
Copy Markdown
Contributor Author

Claude Code PR Review

PR: #2207Head: 9629722Reviewers: stack:code-reviewer

Summary

Exports SmartSnap (Storybook smart-snapshot selection) as a first-class @percy/cli-command API — a streaming stats-file parser, a git/lockfile-diff-driven affected-package resolver (optional snyk-nodejs-lockfile-parser, Node ≥18), a bounded graph-generation poll loop, and an HTML trace renderer — plus new client endpoints, Semgrep workflow hardening, and tests reaching 100% coverage on the Node-14 CI.

Review Table

Priority Category Check Status Notes
High Security No hardcoded secrets or credentials Pass None found
High Security Authentication/authorization checks present N/A No auth logic in scope
High Security Input validation and sanitization Pass path traversal guarded (basename + ^[\w.-]+\.json$), git refs via assertSafeRef, glob length cap, safeJson escapes <script> sinks
High Security No IDOR — resource ownership validated N/A No resource-ownership surface
High Security No SQL injection (parameterized queries) N/A No SQL
High Correctness Logic is correct, handles edge cases Pass Two low-likelihood edges noted: F1 (trace double-substitution, needs pathological file_path) and F3 (ENOENT on missing package.json)
High Correctness Error handling is explicit, no swallowed exceptions Pass git failures wrap to SmartSnapBailError; one unwrapped package.json read — F3
High Correctness No race conditions or concurrency issues N/A Sequential; poll loop is bounded (12×5s)
Medium Testing New code has corresponding tests Pass Extensive; 100% coverage on Node-14 CI
Medium Testing Error paths and edge cases tested Pass rejected baseline state not exercised — F2
Medium Testing Existing tests still pass (no regressions) Pass 138/138 on Node 22; 100% on Node 14
Medium Performance No N+1 queries or unbounded data fetching Pass stats read via stream-json; bounded polling
Medium Performance Long-running tasks use background jobs Pass graph generation is server-side + polled
Medium Quality Follows existing codebase patterns Pass Matches cli-command test/mock conventions
Medium Quality Changes are focused (single concern) Pass Large but cohesive single feature
Low Quality Meaningful names, no dead code Pass
Low Quality Comments explain why, not what Pass Strong rationale comments throughout
Low Quality No unnecessary dependencies added Pass snyk parser added as optionalDependency (Node ≥18), justified

Findings

  • File: packages/cli-command/src/graphTrace.js:150-153

  • Severity: Low

  • Reviewer: stack:code-reviewer

  • Issue: The three chained .replace() calls run on the already-substituted string. If a vertex file_path literally contained a sentinel (e.g. __EDGES_JSON__), the first substitution embeds it into the vertices payload and the next .replace() would hit that occurrence first, corrupting the output. Realistically unreachable (story paths never contain these sentinels), so hardening rather than a live bug.

  • Suggestion: Single-pass replace: template.replace(/__VERTICES_JSON__|__EDGES_JSON__|__TRANSITIVE_CLOSURE_JSON__/g, m => payloads[m]).

  • File: packages/cli-command/src/smartsnap.js (getAffectedPackages, the package.json read after the lockfile-diff guard)

  • Severity: Medium

  • Reviewer: stack:code-reviewer

  • Issue: When a lockfile changes but package.json is absent on disk in that workspace dir, fs.readFileSync(...package.json...) throws a raw ENOENT that escapes applySmartSnap, violating the documented "recoverable failures bail to a full snapshot set" contract. Unusual (they normally coexist) and fails loudly, not silently.

  • Suggestion: Wrap the read in try/catch and throw new SmartSnapBailError('... package.json not found ...; running full snapshot set').

  • File: packages/cli-command/test/smartsnap.test.js (selectAffectedSnapshots "forces re-snapshot for missing, failed and rejected baselines")

  • Severity: Low

  • Reviewer: stack:code-reviewer

  • Issue: Test title mentions rejected but no snapshot has state rejected, so FORCE_RESNAPSHOT_STATES.has('rejected') is never exercised.

  • Suggestion: Add an E: 'rejected' snapshot and assert it is force-kept.

  • File: packages/cli-command/test/smartsnap.test.js (enforceUntraced "keeps paths that do not match")

  • Severity: Low

  • Reviewer: stack:code-reviewer

  • Issue: *.snap does not match across directory separators (globstar), so src/a.snap is kept — correct but a user footgun (**/*.snap is needed for subdirs).

  • Suggestion: Add a clarifying comment / document the untraced glob semantics.

  • File: packages/client/src/client.js (getStatus URL build)

  • Severity: Low

  • Reviewer: stack:code-reviewer

  • Issue: id=${ids.join()} relies on the implicit , separator and on the API accepting comma-joined IDs in one param; fragile/undocumented (fine for the current single-id use).

  • Suggestion: Use ids.join(',') explicitly, or repeated encoded id= params if IDs may contain special chars.

  • File: .github/workflows/Semgrep.yml:16

  • Severity: Low

  • Reviewer: stack:code-reviewer

  • Issue: semgrep/semgrep:1.164.0 is tag-pinned; a tag can be overwritten on the registry.

  • Suggestion: Optionally pin by digest (semgrep/semgrep@sha256:...) for supply-chain robustness.


Verdict: PASS

@prklm10
prklm10 merged commit d3f7f71 into master Jun 2, 2026
45 checks passed
@prklm10
prklm10 deleted the PPLT-5302 branch June 2, 2026 06:43
RaghavsBrowserStack added a commit that referenced this pull request Jun 23, 2026
The PR #2207 revert also reverted the Semgrep image pin back to
returntocorp/semgrep, which reintroduces the bug where --sarif counts
nosemgrep-suppressed findings as blocking. Keep the 1.164.0 pin.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
RaghavsBrowserStack added a commit that referenced this pull request Jun 23, 2026
* Revert "PPLT-5495: Add file diff data to generate graph (#2273)"

This reverts commit 2392017.

* Revert "Export applySmartSnap functionality from CLI (#2207)"

This reverts commit d3f7f71.

* Keep Semgrep.yml pinned to semgrep/semgrep:1.164.0

The PR #2207 revert also reverted the Semgrep image pin back to
returntocorp/semgrep, which reintroduces the bug where --sarif counts
nosemgrep-suppressed findings as blocking. Keep the 1.164.0 pin.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.

4 participants