Skip to content

Harden agent UX, Sentry gating, and bundle rebuilds - #95

Merged
unbraind merged 11 commits into
mainfrom
agent-ux-sentry-bundle-hardening
Jun 1, 2026
Merged

unbraind merged 11 commits into
mainfrom
agent-ux-sentry-bundle-hardening

Conversation

@unbraind

@unbraind unbraind commented Jun 1, 2026

Copy link
Copy Markdown
Owner

Summary

This PR closes the concrete fixes from the June 1 package ecosystem / SDK / agent UX audit tracked in pm-z0ip.

  • Makes pm get --depth brief genuinely low-token by omitting body, linked containers, and claim-state history unless explicitly requested.
  • Compacts default pm validate --check-resolution --json diagnostics while preserving full output under --verbose-diagnostics.
  • Uses stable object equality for linked-test JSON assertions and runtime metadata comparisons, avoiding false mismatches from key insertion order.
  • Stops bundle-cli from deleting the live bundle before rebuilding, preventing transient missing chunk/module failures in shared agent checkouts.
  • Keeps expected handled CLI failures out of Sentry exception capture by default, and has the release Sentry gate report/ignore expected CLI error issues separately from crash issues.
  • Clarifies standalone extension docs so extension-only entrypoints do not import the SDK unless they are package-backed with dependencies installed.

Root Cause

The audit found several agent-facing friction points: brief single-item reads still emitted empty shells and loaded history; validation JSON could dump too many remediation commands; JSON.stringify equality treated semantically identical objects as different; local build gates could delete live dist/cli-bundle while another command was importing it; and expected package/user errors were being counted as Sentry high-priority release blockers.

Validation

  • node scripts/run-tests.mjs test -- tests/unit/cli-main-errors.spec.ts tests/integration/release-automation-contract.spec.ts tests/unit/get-append-command.spec.ts tests/unit/validate-command.spec.ts tests/unit/test-command.spec.ts tests/unit/update-many-command.spec.ts tests/integration/help-runtime.spec.ts passed: 7 files, 164 tests.
  • node scripts/run-tests.mjs coverage passed: 135 files, 2092 tests, 100% statements/branches/functions/lines.
  • pnpm build, pnpm typecheck, pnpm quality:static, pnpm quality:docs-skills, pnpm security:scan, and pnpm changelog:pm:check passed.
  • pm test pm-z0ip --run --progress passed both linked tests.
  • pm validate --check-resolution --check-history-drift --json passed with known pre-existing compact resolution warnings and no history drift.
  • pm health --check-only --brief --json passed.
  • node scripts/dogfood-package-first.mjs passed in an isolated temp project: 87 commands.
  • Telemetry/Sentry required gate passed with fresh telemetry and zero counted critical/high Sentry issues after resolving the fixed bundle issue; expected handled CLI errors are now reported separately.
  • Telemetry stack health passed with healthy containers, queue depth 0, and 10,809 events in the last 24h.

Tracker

  • pm-z0ip records duplicate-check evidence, sub-agent lane findings, linked files/docs/tests, dogfood evidence, and verification comments.

Track the June 1 package ecosystem/SDK audit in pm-z0ip and apply the concrete fixes found by the local and sub-agent review lanes.

Agent UX: make pm get --depth brief omit body, linked shells, and claim-state history; compact validate resolution diagnostics by default while preserving --verbose-diagnostics.

Correctness: use stable object equality for linked-test JSON assertions and runtime metadata comparisons so key insertion order does not create false mismatches.

Reliability: stop bundle-cli from deleting the live bundle before rebuilds, preventing transient missing chunk failures in shared checkouts; update dogfood coverage for the new brief contract.

Observability: keep expected handled CLI failures out of Sentry exception capture by default and have the Sentry gate ignore/report existing expected CLI error issues separately from crash issues.

Docs: clarify standalone extension entrypoints must be import-free unless packaged with SDK dependencies.

Verification: focused regression suite (7 files, 164 tests), full coverage (135 files, 2092 tests, 100% thresholds), typecheck, static quality, docs/skills, secret scan, changelog check, pm health, telemetry stack health, and linked pm tests passed locally.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Sorry @unbraind, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@unbraind

unbraind commented Jun 1, 2026

Copy link
Copy Markdown
Owner Author

@codex review

/gemini review

@gemini-code-assist review

@coderabbitai

coderabbitai Bot commented Jun 1, 2026 •

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@unbraind, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 2 minutes and 2 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: 53671b4d-948e-48b6-9987-ced3320f4e85

📥 Commits

Reviewing files that changed from the base of the PR and between 2be5514 and 4dd0998.

📒 Files selected for processing (8)
  • .agents/pm/history/pm-z0ip.jsonl
  • .agents/pm/tasks/pm-z0ip.toon
  • CHANGELOG.md
  • scripts/bundle-cli.mjs
  • scripts/release/sentry-telemetry-gate.mjs
  • src/core/shared/serialization.ts
  • tests/integration/release-automation-contract.spec.ts
  • tests/unit/serialization-utils.spec.ts
📝 Walkthrough

Walkthrough

This PR applies audit findings to compact brief-get output, add deterministic stable equality, reduce default diagnostic verbosity, refine Sentry gating/handled-error capture, harden concurrent bundle builds, update extension docs, and extend tests and audit metadata.

Changes

CLI UX and output hardening

Layer / File(s) Summary
Stable serialization for deterministic equality
src/core/shared/serialization.ts, src/cli/commands/test.ts, src/cli/commands/update-many.ts, src/cli/commands/update.ts, tests/unit/serialization-utils.spec.ts, tests/unit/test-command.spec.ts, tests/unit/update-many-command.spec.ts
Introduces stableValueEquals and replaces JSON.stringify-based comparisons with stable equality across runtime filters, update planning, and assertion comparisons; adds unit tests for primitives, object key-order invariance, and RegExp semantics.
Brief depth output optimization
src/cli/commands/get.ts, scripts/dogfood-package-first.mjs, tests/integration/help-runtime.spec.ts, tests/unit/get-append-command.spec.ts
get --depth brief now omits body, linked, and claim_state when no --fields projection is used; tests updated to expect absent/undefined fields.
Validate diagnostics truncation
src/cli/commands/validate.ts, tests/unit/validate-command.spec.ts
DIAGNOSTIC_LIST_SUMMARY_LIMIT reduced from 40 to 5; diagnostic builders accept verboseDiagnostics to disable truncation and tests cover compact vs verbose outputs.
Sentry error classification and conditional capture
scripts/release/sentry-telemetry-gate.mjs, src/cli/main.ts, tests/integration/release-automation-contract.spec.ts
Adds expected-handled-CLI issue patterns and isExpectedHandledCliIssue(), partitions issues into relevant/ignoredNoise/ignoredExpected, fetches metadata/logger/isUnhandled fields, and only captures handled CLI errors to Sentry when logged by the handler; gate output reports separate ignored buckets.

Build safety and extension documentation

Layer / File(s) Summary
Concurrent build safety and dogfood smoke test
scripts/bundle-cli.mjs, tests/integration/release-automation-contract.spec.ts
Wraps esbuild invocation in a filesystem lock with stale-lock eviction and post-build stale-file cleanup; avoids deleting live bundle before rebuild to prevent transient broken outputs.
Extension authoring patterns documentation
docs/EXTENSIONS.md
Clarifies standalone (file-URL, import-free entrypoint) vs package-backed extension shapes and updates the minimal standalone example to export activate(api) registering a deterministic hello command.

Audit task tracking and metadata

Layer / File(s) Summary
Task history and metadata logging
.agents/pm/history/pm-z0ip.jsonl, .agents/pm/tasks/pm-z0ip.toon
Adds the pm-z0ip audit task and history entries recording lanes, findings, files_added, test runs, and follow-up hardening notes covering build safety, stable equality, and Sentry gating.

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • unbraind/pm-cli#39: Related changes to runGet/get option handling and projection/response shaping.
  • unbraind/pm-cli#74: Introduced PmCliError, which aligns with the new Sentry "expected CLI error" classification used here.
  • unbraind/pm-cli#54: Related to history-drift and validate refactoring touching similar validation paths.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and specifically captures the three main hardening themes: agent UX improvements (get command), Sentry gating refinements, and bundle rebuild reliability.
Description check ✅ Passed The description is comprehensive and directly related to the changeset, providing clear summaries of fixes, root causes, and validation results across all major areas of change.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request introduces several optimizations and bug fixes to the CLI and SDK ecosystem. Key changes include optimizing get --depth brief to omit body, linked, and claim_state to reduce token usage, replacing order-sensitive JSON.stringify comparisons with stableStringify across filters and assertions, suppressing expected handled CLI errors from Sentry telemetry, and preventing bundle-cli.mjs from deleting the live bundle directory during concurrent rebuilds. Additionally, default validation diagnostics are now more compact. Review feedback suggests importing readdir and unlink in scripts/bundle-cli.mjs to clean up stale chunks after the build completes.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread scripts/bundle-cli.mjs Outdated
#!/usr/bin/env node

import { readFile, rm, writeFile } from "node:fs/promises";
import { readFile, writeFile } from "node:fs/promises";

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

To support cleaning up stale chunks after the build completes, import readdir and unlink from node:fs/promises.

import { readdir, readFile, unlink, writeFile } from "node:fs/promises";

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request hardens the package ecosystem SDK and CLI agent UX by optimizing token usage in get --depth brief, ensuring order-insensitive object equality with stableStringify, making default validation diagnostics more compact, and preventing transient runtime errors during concurrent builds. The feedback highlights a few key areas for improvement: first, the reduced diagnostic limit permanently truncates lifecycle diagnostics because buildLifecycleCheck does not respect verboseDiagnostics; second, removing the directory cleanup in bundle-cli.mjs will accumulate stale chunk files over time; and third, performance can be optimized by adding fast-path strict equality checks before falling back to stableStringify in comparison helpers.

const GIT_LS_FILES_MAX_BUFFER = 32 * 1024 * 1024;
const FILE_LIST_SUMMARY_LIMIT = 40;
const DIAGNOSTIC_LIST_SUMMARY_LIMIT = 40;
const DIAGNOSTIC_LIST_SUMMARY_LIMIT = 5;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Reducing DIAGNOSTIC_LIST_SUMMARY_LIMIT to 5 is a great way to keep the default output compact. However, buildLifecycleCheck (unlike buildMetadataCheck and buildResolutionCheck) does not accept or respect the verboseDiagnostics option.

As a result, all lifecycle diagnostics (such as active_closure_like_metadata_rows, active_terminal_parent_rows, stale_blocker_rows, dependency_cycle_item_ids, and dependency_cycle_sample_paths) will now be permanently truncated to a maximum of 5 items, even when the user explicitly runs the command with --verbose-diagnostics.

To fix this, we should update buildLifecycleCheck to accept verboseDiagnostics: boolean and pass the appropriate limit to summarizeList calls inside it, similar to how it is handled in the other checks.

Comment thread scripts/bundle-cli.mjs Outdated
// Do not delete the live bundle before rebuilding. Agents often run docs,
// dogfood, and build gates concurrently in one checkout; removing this folder
// creates a transient broken `dist/cli.js` runtime.
await build({

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

By removing await rm(outputDir, { recursive: true, force: true }); to prevent transient broken runtimes during concurrent builds, esbuild will now overwrite existing files but will not clean up old chunks.

Since splitting: true is enabled and chunk names contain hashes (chunks/[name]-[hash]), every code change will generate new chunk files with different hashes. Over time, the dist/cli-bundle/chunks/ directory will accumulate stale chunk files indefinitely. This can significantly bloat the packaged size when publishing to npm or running local checkouts.

Consider enabling metafile: true in the esbuild options, and then performing a post-build cleanup step to delete any files in outputDir that are not listed in the generated metafile. This safely prunes stale chunks without causing transient runtime failures during the build.

Comment on lines 5 to 7
function valuesEqual(left: unknown, right: unknown): boolean {
return JSON.stringify(left) === JSON.stringify(right);
return stableStringify(left) === stableStringify(right);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Calling stableStringify on every comparison has non-trivial serialization overhead, especially when processing large datasets of items with primitive field values.

Adding a fast-path reference/primitive equality check (left === right) before falling back to stableStringify will significantly improve performance for identical values.

Suggested change
function valuesEqual(left: unknown, right: unknown): boolean {
return JSON.stringify(left) === JSON.stringify(right);
return stableStringify(left) === stableStringify(right);
}
function valuesEqual(left: unknown, right: unknown): boolean {
if (left === right) return true;
return stableStringify(left) === stableStringify(right);
}

Comment on lines 356 to 358
function areValuesEqual(left: unknown, right: unknown): boolean {
return JSON.stringify(left) === JSON.stringify(right);
return stableStringify(left) === stableStringify(right);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Calling stableStringify on every comparison introduces unnecessary serialization overhead for primitive values.

Adding a fast-path equality check (left === right) before falling back to stableStringify will optimize performance when comparing identical primitives or references.

Suggested change
function areValuesEqual(left: unknown, right: unknown): boolean {
return JSON.stringify(left) === JSON.stringify(right);
return stableStringify(left) === stableStringify(right);
}
function areValuesEqual(left: unknown, right: unknown): boolean {
if (left === right) return true;
return stableStringify(left) === stableStringify(right);
}

Comment thread src/cli/commands/update.ts Outdated
Comment on lines 1875 to 1877
if (stableStringify(metadataRecord[fieldKey]) === stableStringify(fieldValue)) {
continue;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

We can avoid the serialization overhead of stableStringify for identical primitive values by adding a fast-path strict equality check first.

Suggested change
if (stableStringify(metadataRecord[fieldKey]) === stableStringify(fieldValue)) {
continue;
}
if (metadataRecord[fieldKey] === fieldValue || stableStringify(metadataRecord[fieldKey]) === stableStringify(fieldValue)) {
continue;
}

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b84f1cfca9

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".


function valuesEqual(left: unknown, right: unknown): boolean {
return JSON.stringify(left) === JSON.stringify(right);
return stableStringify(left) === stableStringify(right);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3 Badge Avoid treating missing runtime fields as literal strings

When a runtime string filter is set to the literal value undefined, items that do not have that metadata key now pass the filter because stableStringify(undefined) and stableStringify("undefined") both produce the same serialized string. This affects list/search/calendar runtime filters for sparse metadata: a query for that real string value will include items where the field is absent, whereas the previous JSON.stringify comparison kept those cases distinct.

Useful? React with 👍 / 👎.

const GIT_LS_FILES_MAX_BUFFER = 32 * 1024 * 1024;
const FILE_LIST_SUMMARY_LIMIT = 40;
const DIAGNOSTIC_LIST_SUMMARY_LIMIT = 40;
const DIAGNOSTIC_LIST_SUMMARY_LIMIT = 5;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Honor verbose diagnostics for all validate ID lists

When --check-command-references or --check-history-drift finds more than five affected IDs, this shared limit now truncates those diagnostics to five entries, but --verbose-diagnostics is only passed to metadata/resolution checks and these two checks still call summarizeList with the default limit. The validate help promises full diagnostic ID lists with --verbose-diagnostics, so users in those contexts cannot recover the omitted stale references or drifted item IDs from validate output.

Useful? React with 👍 / 👎.

Comment thread scripts/bundle-cli.mjs
Comment on lines +12 to +14
// Do not delete the live bundle before rebuilding. Agents often run docs,
// dogfood, and build gates concurrently in one checkout; removing this folder
// creates a transient broken `dist/cli.js` runtime.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3 Badge Clean stale bundle chunks after successful rebuilds

When pnpm build runs repeatedly in the same checkout after code-splitting output changes, esbuild writes new hashed chunks but does not remove old ones from outdir. Because package.json publishes dist/** and the Sentry upload scans dist/, leaving dist/cli-bundle in place can ship or upload orphaned chunks/maps from prior builds; use a temp directory/swap or post-success cleanup of unreferenced chunks instead of leaving stale files behind.

Useful? React with 👍 / 👎.

function isExpectedHandledCliIssue(issue) {
const metadata = issue && typeof issue === "object" ? issue.metadata : null;
const type = metadata && typeof metadata.type === "string" ? metadata.type : "";
return type === "PmCliError" || type === "CommandError";

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Do not suppress all typed CLI errors in the Sentry gate

For any unresolved fatal/error Sentry issue whose metadata.type is PmCliError or CommandError, the release gate now drops it before tallying severity, without checking whether it was an expected usage/not-found/conflict outcome. The repo also throws PmCliError for internal/generic failures, so a real production regression reported under that type can make the gate pass even though the queried issue is still unresolved at error/fatal level.

Useful? React with 👍 / 👎.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (1)
.agents/pm/history/pm-z0ip.jsonl (1)

1-39: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Protected system-of-record file is being edited directly.

This PR directly modifies .agents/pm/history/pm-z0ip.jsonl, which is disallowed for this repository path policy. Please remove direct .agents/pm edits from this PR and apply updates through the pm workflow/tooling that owns these records.

As per coding guidelines .agents/pm/**: pm is the system of record. Do not edit .agents/pm files directly.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.agents/pm/history/pm-z0ip.jsonl around lines 1 - 39, The PR illegally
modifies the system-of-record audit entry "pm-z0ip" (metadata/id pm-z0ip) by
adding/updating metadata, comments, files, tests and test_runs; revert all
direct edits to that record in this branch and remove any changes touching the
pm-z0ip JSONL entry from the patch, then re-apply the intended updates using the
authorized pm workflow/tooling (use the pm CLI or service to claim/update the
task, add comments, files, tests or test_runs through its commands rather than
editing metadata/*, comments, files or test_runs fields directly), and confirm
the repo no longer contains any direct modifications to the pm-z0ip record
before rebasing and re-submitting the PR.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/cli/commands/validate.ts`:
- Around line 127-128: The DIAGNOSTIC_LIST_SUMMARY_LIMIT was lowered to 5 which
unintentionally truncates arrays in checks that never receive the
verboseDiagnostics flag (notably the lifecycle, history_drift, and
command_references builders); either pass the verboseDiagnostics boolean into
those builders (e.g., thread the verboseDiagnostics param from the validate
command into lifecycle, history_drift, and command_references functions) so they
can bypass the shared DIAGNOSTIC_LIST_SUMMARY_LIMIT when verbose is true, or
change those builders to use a separate cap (or conditional that checks
verboseDiagnostics) instead of the global DIAGNOSTIC_LIST_SUMMARY_LIMIT so pm
validate --verbose-diagnostics restores full diagnostics for fields like
active_closure_like_metadata_rows, drifted_items, and
stale_pm_id_reference_rows.

---

Outside diff comments:
In @.agents/pm/history/pm-z0ip.jsonl:
- Around line 1-39: The PR illegally modifies the system-of-record audit entry
"pm-z0ip" (metadata/id pm-z0ip) by adding/updating metadata, comments, files,
tests and test_runs; revert all direct edits to that record in this branch and
remove any changes touching the pm-z0ip JSONL entry from the patch, then
re-apply the intended updates using the authorized pm workflow/tooling (use the
pm CLI or service to claim/update the task, add comments, files, tests or
test_runs through its commands rather than editing metadata/*, comments, files
or test_runs fields directly), and confirm the repo no longer contains any
direct modifications to the pm-z0ip record before rebasing and re-submitting the
PR.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: 5707b5da-3183-4602-aa55-c0c28f8e12ab

📥 Commits

Reviewing files that changed from the base of the PR and between ba2f39a and b84f1cf.

📒 Files selected for processing (19)
  • .agents/pm/history/pm-z0ip.jsonl
  • .agents/pm/tasks/pm-z0ip.toon
  • docs/EXTENSIONS.md
  • scripts/bundle-cli.mjs
  • scripts/dogfood-package-first.mjs
  • scripts/release/sentry-telemetry-gate.mjs
  • src/cli/commands/get.ts
  • src/cli/commands/test.ts
  • src/cli/commands/update-many.ts
  • src/cli/commands/update.ts
  • src/cli/commands/validate.ts
  • src/cli/main.ts
  • src/core/schema/runtime-field-filters.ts
  • tests/integration/help-runtime.spec.ts
  • tests/integration/release-automation-contract.spec.ts
  • tests/unit/get-append-command.spec.ts
  • tests/unit/test-command.spec.ts
  • tests/unit/update-many-command.spec.ts
  • tests/unit/validate-command.spec.ts

Comment thread src/cli/commands/validate.ts Resolved
@unbraind

unbraind commented Jun 1, 2026

Copy link
Copy Markdown
Owner Author

@codex review

/gemini review

@gemini-code-assist review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request hardens the package ecosystem and CLI agent UX. Key changes include optimizing get --depth brief to omit body, linked, and claim state data; introducing a primitive-safe stableValueEquals helper to perform order-insensitive object comparisons; limiting default validation diagnostics to reduce token usage; and updating the CLI build script to avoid deleting the live bundle during rebuilds. Feedback on the changes suggests updating the file collection helper in the build script to also clean up stale symbolic links by checking entry.isSymbolicLink().

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread scripts/bundle-cli.mjs Outdated
Comment on lines +26 to +28
if (entry.isFile()) {
files.push(absolutePath);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Stale symbolic links in the output directory won't be collected or cleaned up because entry.isFile() is false for symlinks. Including entry.isSymbolicLink() ensures that any stale symlinks are also cleaned up correctly.

    if (entry.isFile() || entry.isSymbolicLink()) {
      files.push(absolutePath);
    }

@unbraind

unbraind commented Jun 1, 2026

Copy link
Copy Markdown
Owner Author

@codex review

/gemini review

@gemini-code-assist review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6dbd4e038f

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

function isExpectedHandledCliIssue(issue) {
const metadata = issue && typeof issue === "object" ? issue.metadata : null;
const type = metadata && typeof metadata.type === "string" ? metadata.type : "";
return (type === "PmCliError" || type === "CommandError") && issue?.isUnhandled === false;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Include isUnhandled when fetching Sentry issues via API

When the release gate has a Sentry token set, fetchSentryIssues uses the REST API path rather than the CLI fallback; Sentry's List a Project's Issues response schema includes metadata but not isUnhandled, and buildSentryIssuesUrl does not request any event field that would add it. In that normal required-gate context, issue?.isUnhandled is undefined, so handled PmCliError/CommandError groups are still counted as high instead of reported under ignored_expected_cli_error_total, leaving the gate blocked on the expected CLI errors this change is meant to ignore.

Useful? React with 👍 / 👎.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request hardens the package ecosystem SDK and optimizes the agent UX. Key changes include: updating get --depth brief to omit body, linked, and claim_state fields to reduce token usage; introducing a primitive-safe stableValueEquals helper to replace order-sensitive JSON.stringify comparisons across several commands; making default validation diagnostics more compact unless verbose diagnostics are requested; preventing the deletion of the live bundle during rebuilds to avoid breaking concurrent runs; and suppressing expected handled CLI errors from Sentry reporting. Documentation and extensive test coverage have been updated accordingly. There are no review comments, and I have no additional feedback to provide.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3ce3f5f9c3

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread .agents/pm/tasks/pm-z0ip.toon Outdated
title: 2026-06-01 package ecosystem SDK agent UX audit and hardening
description: "Detailed manual audit, temp-project dogfood, SDK/ecosystem review, agent-optimized UX review, telemetry/Sentry validation, CI/PR review loop, and targeted hardening on latest main. Duplicate check: pm search found prior closed broad-audit lineage pm-59gj/pm-kd9n/pm-etxf/pm-qmx3 and no in-progress audit items; this task records the new June 1 requested pass."
type: Task
status: in_progress

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Close the completed pm item before committing

This task records final verification and links the changed files/tests, but the committed tracker state still leaves it as status: in_progress with an assignee, so it will continue to appear in pm list-in-progress and look actively owned by this agent. AGENTS.md requires the close/release step for completed work; please close and release this item through pm so the repository’s system-of-record state matches the shipped change.

Useful? React with 👍 / 👎.

@unbraind

unbraind commented Jun 1, 2026

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3ce3f5f9c3

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread scripts/bundle-cli.mjs Outdated
chunkNames: "chunks/[name]-[hash]",
logLevel: "warning",
});
await removeStaleBundleFiles(buildResult.metafile.outputs);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Avoid deleting another concurrent bundle's chunks

When two pnpm build/bundle-cli runs overlap in the same checkout and the second build produces different chunk hashes, this post-build stale cleanup can delete files that the other build has just written because its metafile outputs are not in this process's expectedFiles set. That can leave dist/cli-bundle/main.js pointing at a now-missing chunk, recreating the transient broken bundle state this change is meant to prevent; stale cleanup needs serialization/locking or a safer generation swap strategy.

Useful? React with 👍 / 👎.

@unbraind

unbraind commented Jun 1, 2026

Copy link
Copy Markdown
Owner Author

@codex review

/gemini review

@gemini-code-assist review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request hardens the CLI and optimizes agent UX by introducing a build lock mechanism to serialize concurrent bundle builds, making get --depth brief more compact to reduce token usage, and limiting default validation diagnostics unless verbose mode is requested. It also replaces order-sensitive JSON.stringify comparisons with a primitive-safe stableValueEquals helper and suppresses handled CLI errors from Sentry. The review feedback correctly identifies a race condition in the stale lock cleanup logic in scripts/bundle-cli.mjs and suggests using an atomic rename operation to safely clean up stale locks.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread scripts/bundle-cli.mjs Outdated
#!/usr/bin/env node

import { readFile, rm, writeFile } from "node:fs/promises";
import { mkdir, readdir, readFile, rm, stat, unlink, writeFile } from "node:fs/promises";

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

Add rename to the imports from node:fs/promises to support atomic stale lock cleanup.

import { mkdir, readdir, readFile, rename, rm, stat, unlink, writeFile } from "node:fs/promises";

Comment thread scripts/bundle-cli.mjs
Comment on lines +20 to +44
async function acquireBundleBuildLock() {
await mkdir(path.dirname(lockDir), { recursive: true });
const startedAt = Date.now();
while (true) {
try {
await mkdir(lockDir);
return async () => {
await rm(lockDir, { recursive: true, force: true });
};
} catch (error) {
if (!error || typeof error !== "object" || !("code" in error) || error.code !== "EEXIST") {
throw error;
}
const lockStats = await stat(lockDir).catch(() => null);
if (lockStats && Date.now() - lockStats.mtimeMs > staleLockMs) {
await rm(lockDir, { recursive: true, force: true });
continue;
}
if (Date.now() - startedAt > lockTimeoutMs) {
throw new Error(`Timed out waiting for bundle build lock at ${lockDir}`);
}
await sleep(lockRetryMs);
}
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

There is a race condition in the stale lock cleanup logic. If multiple concurrent processes find that the lock is stale, they will all attempt to delete it using rm(lockDir). Once one process successfully deletes and recreates the lock directory via mkdir(lockDir), another concurrent process's pending rm(lockDir) can delete the newly acquired lock, allowing a third process to also acquire it. This violates mutual exclusion.

To prevent this race condition, we can atomically rename the stale lock directory to a unique temporary name using rename before deleting it. If the rename operation succeeds, that process has safely claimed the right to clean up the stale lock without risk of deleting a newly created lock.

async function acquireBundleBuildLock() {
  await mkdir(path.dirname(lockDir), { recursive: true });
  const startedAt = Date.now();
  while (true) {
    try {
      await mkdir(lockDir);
      return async () => {
        await rm(lockDir, { recursive: true, force: true });
      };
    } catch (error) {
      if (!error || typeof error !== "object" || !("code" in error) || error.code !== "EEXIST") {
        throw error;
      }
      const lockStats = await stat(lockDir).catch(() => null);
      if (lockStats && Date.now() - lockStats.mtimeMs > staleLockMs) {
        const tempLockDir = lockDir + ".stale." + Date.now() + "." + Math.random().toString(36).slice(2);
        try {
          await rename(lockDir, tempLockDir);
          await rm(tempLockDir, { recursive: true, force: true });
        } catch (renameError) {
          // If rename fails, another process already claimed or deleted it
        }
        continue;
      }
      if (Date.now() - startedAt > lockTimeoutMs) {
        throw new Error(`Timed out waiting for bundle build lock at ${lockDir}`);
      }
      await sleep(lockRetryMs);
    }
  }
}

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Can't wait for the next one!

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

@unbraind

unbraind commented Jun 1, 2026

Copy link
Copy Markdown
Owner Author

@codex review

/gemini review

@gemini-code-assist review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request introduces several optimizations and hardening improvements to the CLI package ecosystem. Key changes include implementing a file-system-based build lock in bundle-cli.mjs to serialize concurrent builds safely, reducing token usage for agent reads by omitting body, linked, and claim_state in get --depth brief, and compacting default validation diagnostics. Additionally, it introduces a primitive-safe stableValueEquals helper for order-insensitive object comparisons and suppresses expected handled CLI errors from Sentry reporting. Feedback on these changes suggests optimizing the build lock retry loop when a lock is immediately released, and handling non-plain objects like RegExp in stableValueEquals to prevent false-positive equality matches.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread scripts/bundle-cli.mjs Outdated
Comment on lines +33 to +34
const lockStats = await stat(lockDir).catch(() => null);
if (lockStats && Date.now() - lockStats.mtimeMs > staleLockMs) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

When stat(lockDir) fails (e.g., because the lock was just released and deleted by another process), lockStats resolves to null. Currently, the loop continues to check the timeout and then sleeps for lockRetryMs (250ms) before retrying.

Since a null stats object indicates the lock is now free, we can immediately continue the loop to attempt to acquire the lock without any delay. This optimizes responsiveness during concurrent builds.

      const lockStats = await stat(lockDir).catch(() => null);
      if (!lockStats) {
        continue;
      }
      if (Date.now() - lockStats.mtimeMs > staleLockMs) {

Comment on lines +52 to +60
export function stableValueEquals(left: unknown, right: unknown): boolean {
if (Object.is(left, right)) {
return true;
}
if (left === null || right === null || typeof left !== "object" || typeof right !== "object") {
return false;
}
return stableStringify(left) === stableStringify(right);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The new stableValueEquals helper relies on stableStringify for object comparison. However, stableStringify (via sortObjectKeys) does not support non-plain objects such as RegExp, serializing them all to empty objects ("{}"). This can lead to false positives where semantically different regular expressions (e.g., /abc/ and /def/) are evaluated as equal.

Consider adding an explicit check for RegExp instances to ensure correctness.

export function stableValueEquals(left: unknown, right: unknown): boolean {
  if (Object.is(left, right)) {
    return true;
  }
  if (left === null || right === null || typeof left !== "object" || typeof right !== "object") {
    return false;
  }
  if (left instanceof RegExp && right instanceof RegExp) {
    return left.toString() === right.toString();
  }
  return stableStringify(left) === stableStringify(right);
}

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: eee78ebcbd

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread scripts/bundle-cli.mjs Outdated
await Promise.all(
existingFiles
.filter((filePath) => !expectedFiles.has(filePath))
.map((filePath) => unlink(filePath).catch((error) => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Keep old bundle chunks until readers drain

When a bundle rebuild overlaps with an already-running dist/cli.js, this cleanup can still delete the old hashed chunks that the running process may import later; src/cli/main.ts lazy-loads command registrars with dynamic import(...), so a process that loaded the old main.js before this rebuild can fail with the same missing-module error when it reaches one of those lazy imports. The lock only serializes rebuilds, not CLI readers, so stale chunks need a grace/retention strategy instead of immediate unlinking.

Useful? React with 👍 / 👎.

function isExpectedHandledCliIssue(issue) {
const metadata = issue && typeof issue === "object" ? issue.metadata : null;
const type = metadata && typeof metadata.type === "string" ? metadata.type : "";
return (type === "PmCliError" || type === "CommandError") && issue?.isUnhandled === false;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Keep generic handled failures in the Sentry gate

This treats every handled PmCliError/CommandError issue as expected, but the CLI still intentionally reports handled errors whose exit code is not in the expected set (shouldLogHandledErrorToSentry only suppresses usage/not-found/conflict), and there are real PmCliError(..., EXIT_CODE.GENERIC_FAILURE) paths such as invalid update-many checkpoints. Those high/critical handled generic failures will now be removed from relevant and can no longer block the release gate, so the filter should distinguish expected exit codes/error categories rather than only type plus isUnhandled === false.

Useful? React with 👍 / 👎.

@unbraind

unbraind commented Jun 1, 2026

Copy link
Copy Markdown
Owner Author

@codex review

/gemini review

@gemini-code-assist review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request introduces several robustness and performance improvements, including a file-locking mechanism for concurrent CLI bundle builds, stable object equality comparisons (stableValueEquals) to prevent order-sensitive failures, and a verboseDiagnostics option for compact validation outputs. It also refines the get --depth brief command to omit unnecessary payload fields and adds Sentry telemetry filtering for expected handled CLI errors. The review feedback highlights a potential infinite loop in the bundle build lock acquisition if unexpected errors occur, suggests using lstat instead of stat to correctly handle symbolic links during stale file cleanup, and recommends relaxing the isUnhandled check in the Sentry telemetry gate to safely handle missing or undefined fields.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread scripts/bundle-cli.mjs Outdated
Comment on lines +34 to +37
const lockStats = await stat(lockDir).catch(() => null);
if (!lockStats) {
continue;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

If stat(lockDir) fails with an error other than ENOENT (for example, permission denied EACCES or EPERM), the catch block currently silences the error and returns null. This causes the loop to immediately continue without sleeping, leading to an infinite, CPU-burning tight loop.

We should only ignore ENOENT errors and propagate any other unexpected errors to fail the build gracefully.

      const lockStats = await stat(lockDir).catch((err) => {
        if (err && err.code === "ENOENT") {
          return null;
        }
        throw err;
      });
      if (!lockStats) {
        continue;
      }

Comment thread scripts/bundle-cli.mjs Outdated
#!/usr/bin/env node

import { readFile, rm, writeFile } from "node:fs/promises";
import { mkdir, readdir, readFile, rename, rm, stat, unlink, writeFile } from "node:fs/promises";

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Import lstat from node:fs/promises to safely inspect symbolic links without following them during stale file cleanup.

import { lstat, mkdir, readdir, readFile, rename, rm, stat, unlink, writeFile } from "node:fs/promises";

Comment thread scripts/bundle-cli.mjs Outdated
existingFiles
.filter((filePath) => !expectedFiles.has(filePath))
.map(async (filePath) => {
const fileStats = await stat(filePath).catch(() => null);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Using stat on symbolic links follows them to their target. If a symlink is broken, stat will fail with ENOENT and return null, meaning broken symlinks will never be cleaned up. Additionally, for valid symlinks, stat checks the target's modification time rather than the symlink's own creation/modification time.

Using lstat instead of stat ensures we inspect the symlink itself, allowing broken symlinks to be cleaned up and correctly checking the symlink's own age.

        const fileStats = await lstat(filePath).catch(() => null);

Comment on lines +140 to +142
if ((type !== "PmCliError" && type !== "CommandError") || issue?.isUnhandled !== false) {
return false;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

If isUnhandled is missing or undefined in the Sentry API response (which is common for issue group endpoints), issue?.isUnhandled !== false will evaluate to true, causing the function to return false and fail to ignore expected handled CLI errors.

Changing the check to issue?.isUnhandled === true ensures we only reject explicitly unhandled errors (crashes), while safely treating missing/undefined values as handled CLI errors.

Suggested change
if ((type !== "PmCliError" && type !== "CommandError") || issue?.isUnhandled !== false) {
return false;
}
if ((type !== "PmCliError" && type !== "CommandError") || issue?.isUnhandled === true) {
return false;
}

const gateSource = await readFile(path.join(repoRoot, "scripts/release/sentry-telemetry-gate.mjs"), "utf8");
expect(gateSource).toContain('commandFor("sentry")');
expect(gateSource).toContain("function isExpectedHandledCliIssue");
expect(gateSource).toContain('issue?.isUnhandled !== false');

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Update the test assertion to match the relaxed isUnhandled check in sentry-telemetry-gate.mjs.

Suggested change
expect(gateSource).toContain('issue?.isUnhandled !== false');
expect(gateSource).toContain('issue?.isUnhandled === true');

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.agents/pm/history/pm-z0ip.jsonl:
- Around line 39-49: Summary: Direct edits were made to the pm system-of-record
JSONL (.agents/pm/history/pm-z0ip.jsonl); remove them and regenerate via the
supported PM workflow. Fix: remove the file .agents/pm/history/pm-z0ip.jsonl
from the PR (undo the changes/patches that add/replace metadata entries), revert
any commits that modified .agents/pm/** so the repository returns to the
canonical pm state, and re-create the intended history/tasks using the official
PM workflow/tool (do not hand-edit .agents/pm files); ensure future changes use
the PM system (refer to the .agents/pm/** pattern and the specific file
pm-z0ip.jsonl when locating the edits).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: 55aed889-ce2c-428c-a8c9-42d229ac0515

📥 Commits

Reviewing files that changed from the base of the PR and between b84f1cf and 2be5514.

📒 Files selected for processing (14)
  • .agents/pm/history/pm-z0ip.jsonl
  • .agents/pm/tasks/pm-z0ip.toon
  • scripts/bundle-cli.mjs
  • scripts/release/sentry-telemetry-gate.mjs
  • src/cli/commands/test.ts
  • src/cli/commands/update-many.ts
  • src/cli/commands/update.ts
  • src/cli/commands/validate.ts
  • src/core/schema/runtime-field-filters.ts
  • src/core/shared/serialization.ts
  • tests/integration/release-automation-contract.spec.ts
  • tests/unit/serialization-utils.spec.ts
  • tests/unit/update-many-command.spec.ts
  • tests/unit/validate-command.spec.ts
✅ Files skipped from review due to trivial changes (1)
  • .agents/pm/tasks/pm-z0ip.toon
🚧 Files skipped from review as they are similar to previous changes (5)
  • src/cli/commands/update-many.ts
  • tests/unit/update-many-command.spec.ts
  • src/cli/commands/update.ts
  • tests/integration/release-automation-contract.spec.ts
  • scripts/release/sentry-telemetry-gate.mjs

Comment on lines +39 to +49
{"ts":"2026-06-01T21:52:05.920Z","author":"codex-audit-20260601","op":"files_add","patch":[{"op":"add","path":"/metadata/files/15","value":{"path":"tests/unit/validate-command.spec.ts","scope":"project","note":"Cover compact versus verbose command-reference diagnostics"}},{"op":"replace","path":"/metadata/updated_at","value":"2026-06-01T21:52:05.920Z"}],"before_hash":"a70966ef26322c8a190da02253c45007dd9cbbc19428a9dc73a5829f579a7029","after_hash":"b81a9119ece675eeb85ecdc2901ac2334b7b77c210f850441a0937da7bea65d9"}
{"ts":"2026-06-01T21:52:06.002Z","author":"codex-audit-20260601","op":"files_add","patch":[{"op":"replace","path":"/metadata/files/15/note","value":"Runtime filter stable-equality regression lives in existing unit spec to preserve directory-load limits"},{"op":"replace","path":"/metadata/files/15/path","value":"tests/unit/update-many-command.spec.ts"},{"op":"replace","path":"/metadata/files/14/note","value":"Covers order-insensitive linked-test JSON assertions"},{"op":"replace","path":"/metadata/files/14/path","value":"tests/unit/test-command.spec.ts"},{"op":"replace","path":"/metadata/files/13/note","value":"Cover stableValueEquals primitive and object comparison branches"},{"op":"replace","path":"/metadata/files/13/path","value":"tests/unit/serialization-utils.spec.ts"},{"op":"add","path":"/metadata/files/16","value":{"path":"tests/unit/validate-command.spec.ts","scope":"project","note":"Cover compact versus verbose command-reference diagnostics"}},{"op":"replace","path":"/metadata/updated_at","value":"2026-06-01T21:52:06.002Z"}],"before_hash":"b81a9119ece675eeb85ecdc2901ac2334b7b77c210f850441a0937da7bea65d9","after_hash":"81cdb1bd73d6e122c58d30ef42143be62adbb8bbd5e2ac4acf13868d879633f2"}
{"ts":"2026-06-01T21:52:13.248Z","author":"codex-audit-20260601","op":"files_add","patch":[{"op":"replace","path":"/metadata/files/16/note","value":"Runtime filter stable-equality regression lives in existing unit spec to preserve directory-load limits"},{"op":"replace","path":"/metadata/files/16/path","value":"tests/unit/update-many-command.spec.ts"},{"op":"replace","path":"/metadata/files/15/note","value":"Covers order-insensitive linked-test JSON assertions"},{"op":"replace","path":"/metadata/files/15/path","value":"tests/unit/test-command.spec.ts"},{"op":"replace","path":"/metadata/files/14/note","value":"Cover stableValueEquals primitive and object comparison branches"},{"op":"replace","path":"/metadata/files/14/path","value":"tests/unit/serialization-utils.spec.ts"},{"op":"replace","path":"/metadata/files/13/note","value":"Covers brief get output and linked projection branches"},{"op":"replace","path":"/metadata/files/13/path","value":"tests/unit/get-append-command.spec.ts"},{"op":"replace","path":"/metadata/files/12/note","value":"Covers handled-error Sentry suppression helper behavior"},{"op":"replace","path":"/metadata/files/12/path","value":"tests/unit/cli-main-errors.spec.ts"},{"op":"replace","path":"/metadata/files/11/note","value":"Covers bundle rebuild and Sentry gate contract"},{"op":"replace","path":"/metadata/files/11/path","value":"tests/integration/release-automation-contract.spec.ts"},{"op":"replace","path":"/metadata/files/10/note","value":"Add primitive-safe stable equality helper for runtime comparisons"},{"op":"replace","path":"/metadata/files/10/path","value":"src/core/shared/serialization.ts"},{"op":"add","path":"/metadata/files/17","value":{"path":"tests/unit/validate-command.spec.ts","scope":"project","note":"Cover compact versus verbose command-reference diagnostics"}},{"op":"replace","path":"/metadata/updated_at","value":"2026-06-01T21:52:13.248Z"}],"before_hash":"81cdb1bd73d6e122c58d30ef42143be62adbb8bbd5e2ac4acf13868d879633f2","after_hash":"91c0c053968830f68c1f8095ad8b4fb8c323defe70c0aaba8026aa293b7ba9a4"}
{"ts":"2026-06-01T21:52:19.884Z","author":"codex-audit-20260601","op":"tests_add","patch":[{"op":"replace","path":"/metadata/tests/1/command","value":"node scripts/run-tests.mjs test -- tests/unit/serialization-utils.spec.ts tests/unit/cli-main-errors.spec.ts tests/integration/release-automation-contract.spec.ts tests/unit/get-append-command.spec.ts tests/unit/validate-command.spec.ts tests/unit/test-command.spec.ts tests/unit/update-many-command.spec.ts tests/integration/help-runtime.spec.ts"},{"op":"add","path":"/metadata/tests/2","value":{"command":"pnpm build && node scripts/dogfood-package-first.mjs","scope":"project","timeout_seconds":300}},{"op":"replace","path":"/metadata/updated_at","value":"2026-06-01T21:52:19.884Z"}],"before_hash":"91c0c053968830f68c1f8095ad8b4fb8c323defe70c0aaba8026aa293b7ba9a4","after_hash":"dd47ed03296d4ea0be99a17c1f55e442618df654004181762206c1e5f0348aed"}
{"ts":"2026-06-01T21:52:24.946Z","author":"codex-audit-20260601","op":"tests_add","patch":[{"op":"replace","path":"/metadata/tests/2/command","value":"node scripts/run-tests.mjs test -- tests/unit/serialization-utils.spec.ts tests/unit/cli-main-errors.spec.ts tests/integration/release-automation-contract.spec.ts tests/unit/get-append-command.spec.ts tests/unit/validate-command.spec.ts tests/unit/test-command.spec.ts tests/unit/update-many-command.spec.ts tests/integration/help-runtime.spec.ts"},{"op":"replace","path":"/metadata/tests/1/timeout_seconds","value":240},{"op":"replace","path":"/metadata/tests/1/command","value":"node scripts/run-tests.mjs test -- tests/unit/cli-main-errors.spec.ts tests/integration/release-automation-contract.spec.ts tests/unit/get-append-command.spec.ts tests/unit/validate-command.spec.ts tests/unit/test-command.spec.ts tests/unit/update-many-command.spec.ts tests/integration/help-runtime.spec.ts"},{"op":"replace","path":"/metadata/tests/0/timeout_seconds","value":360},{"op":"replace","path":"/metadata/tests/0/command","value":"node scripts/run-tests.mjs coverage"},{"op":"add","path":"/metadata/tests/3","value":{"command":"pnpm build && node scripts/dogfood-package-first.mjs","scope":"project","timeout_seconds":300}},{"op":"replace","path":"/metadata/updated_at","value":"2026-06-01T21:52:24.946Z"}],"before_hash":"dd47ed03296d4ea0be99a17c1f55e442618df654004181762206c1e5f0348aed","after_hash":"c6097adc1c62cbe4638127bb2bc20ad8c09bd35c1e6462c7f12b1beb21324f02"}
{"ts":"2026-06-01T21:52:31.425Z","author":"codex-audit-20260601","op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/11","value":{"created_at":"2026-06-01T21:52:31.425Z","author":"codex-audit-20260601","text":"Review follow-up evidence: addressed Gemini/Codex findings by preserving stale bundle cleanup after successful esbuild metafile output, applying compact validate diagnostics to lifecycle/history/command-reference surfaces with verbose expansion, replacing stableStringify equality with primitive-safe stableValueEquals, and narrowing expected Sentry issue ignores to handled CLI errors only. Verified focused suite 8 files/171 tests, full coverage 135 files/2094 tests at 100%, package-first dogfood 87 commands, changelog check unchanged, typecheck/static/docs/security gates, Sentry strict and required telemetry gates."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-06-01T21:52:31.425Z"}],"before_hash":"c6097adc1c62cbe4638127bb2bc20ad8c09bd35c1e6462c7f12b1beb21324f02","after_hash":"96388b8c8911f0fabc19f7a4915ccc7134dc50c3a0132d0e54072bebdd28a30d"}
{"ts":"2026-06-01T21:58:03.850Z","author":"codex-audit-20260601","op":"test_run_track","patch":[{"op":"add","path":"/metadata/test_runs/2","value":{"run_id":"test-local-mpvr2g5d-hq4uhk","kind":"test","status":"passed","started_at":"2026-06-01T21:52:36.990Z","finished_at":"2026-06-01T21:58:03.842Z","recorded_at":"2026-06-01T21:58:03.842Z","passed":4,"failed":0,"skipped":0}},{"op":"replace","path":"/metadata/updated_at","value":"2026-06-01T21:58:03.850Z"}],"before_hash":"96388b8c8911f0fabc19f7a4915ccc7134dc50c3a0132d0e54072bebdd28a30d","after_hash":"245e2a104f38f2a4c633cc4be7937fff6b14c0fba75fdbac3d28c70f6273cd4a","message":"Track test run summary (test-local-mpvr2g5d-hq4uhk)"}
{"ts":"2026-06-01T22:03:27.300Z","author":"codex-audit-20260601","op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/12","value":{"created_at":"2026-06-01T22:03:27.300Z","author":"codex-audit-20260601","text":"Review follow-up: Gemini noted stale symlinks in dist/cli-bundle would not be collected by the post-build stale-file cleanup. Updated collectFiles to include Dirent symlinks and added release automation contract coverage. Verification: pnpm build and node scripts/run-tests.mjs test -- tests/integration/release-automation-contract.spec.ts."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-06-01T22:03:27.300Z"}],"before_hash":"245e2a104f38f2a4c633cc4be7937fff6b14c0fba75fdbac3d28c70f6273cd4a","after_hash":"4a8edcdc76b98d606ff393feb09dd6a41d382a778461b12e36b8b827e72111e3"}
{"ts":"2026-06-01T22:16:51.754Z","author":"codex-audit-20260601","op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/13","value":{"created_at":"2026-06-01T22:16:51.754Z","author":"codex-audit-20260601","text":"Review follow-up: Codex found concurrent bundle cleanups could delete another build's fresh hashed chunks. Added a stale-aware .cli-bundle-build.lock around the esbuild output plus stale cleanup phase so builds serialize while the live bundle stays available for CLI users. Verification: pnpm build, pnpm typecheck, and node scripts/run-tests.mjs test -- tests/integration/release-automation-contract.spec.ts."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-06-01T22:16:51.754Z"}],"before_hash":"4a8edcdc76b98d606ff393feb09dd6a41d382a778461b12e36b8b827e72111e3","after_hash":"1e68bbffb2e1ace19fe573f5d1bd9bd4dcd6b8e714582aabef433573f9e87062"}
{"ts":"2026-06-01T22:22:28.609Z","author":"codex-audit-20260601","op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/14","value":{"created_at":"2026-06-01T22:22:28.609Z","author":"codex-audit-20260601","text":"Review follow-up: Gemini found stale bundle-lock cleanup could delete a newly acquired lock if multiple builders detected the same stale lock. Changed stale cleanup to atomically rename the stale lock directory to a unique candidate before removing it, preserving mutual exclusion. Verification: pnpm build, pnpm typecheck, and node scripts/run-tests.mjs test -- tests/integration/release-automation-contract.spec.ts."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-06-01T22:22:28.609Z"}],"before_hash":"1e68bbffb2e1ace19fe573f5d1bd9bd4dcd6b8e714582aabef433573f9e87062","after_hash":"7529c675a8d28556d526c1ad9ba6036992735034c01ff55e49e90842b2c123de"}
{"ts":"2026-06-01T22:32:02.982Z","author":"codex-audit-20260601","op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/15","value":{"created_at":"2026-06-01T22:32:02.982Z","author":"codex-audit-20260601","text":"Review follow-up: Gemini/Codex found immediate stale bundle cleanup could affect already-running lazy imports, stale-lock retry could sleep after a released lock, RegExp values could compare equal through stableStringify, and the Sentry expected-error filter needed message-level narrowing. Added bundle stale-file retention, immediate retry on missing lock stats, RegExp-aware stableValueEquals, and a known expected handled CLI issue allowlist. Verification: pnpm build, pnpm typecheck, focused release/serialization tests, full coverage 135 files/2094 tests at 100%, Sentry strict gate, and required telemetry gate."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-06-01T22:32:02.982Z"}],"before_hash":"7529c675a8d28556d526c1ad9ba6036992735034c01ff55e49e90842b2c123de","after_hash":"4d258cb54e2b6b497b880e5bdd28dae39b10bd54054836d3bdc60f296b78f594"}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Avoid direct edits under .agents/pm.

These JSONL mutations are being committed directly into the pm system-of-record area. Please remove this file from the PR and regenerate any needed history/task state through the supported pm workflow instead.

As per coding guidelines, .agents/pm/**: pm is the system of record. Do not edit .agents/pm files directly.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.agents/pm/history/pm-z0ip.jsonl around lines 39 - 49, Summary: Direct edits
were made to the pm system-of-record JSONL (.agents/pm/history/pm-z0ip.jsonl);
remove them and regenerate via the supported PM workflow. Fix: remove the file
.agents/pm/history/pm-z0ip.jsonl from the PR (undo the changes/patches that
add/replace metadata entries), revert any commits that modified .agents/pm/** so
the repository returns to the canonical pm state, and re-create the intended
history/tasks using the official PM workflow/tool (do not hand-edit .agents/pm
files); ensure future changes use the PM system (refer to the .agents/pm/**
pattern and the specific file pm-z0ip.jsonl when locating the edits).

@unbraind

unbraind commented Jun 1, 2026

Copy link
Copy Markdown
Owner Author

@codex review

/gemini review

@gemini-code-assist review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request hardens the package ecosystem SDK and improves agent UX. Key changes include introducing a build lock and stale-file retention in the CLI bundler to prevent concurrent build corruption, omitting body, linked containers, and claim state in get --depth brief to reduce token usage, and making default validation diagnostics more compact unless verbose diagnostics are requested. Additionally, it replaces order-sensitive JSON.stringify comparisons with a primitive-safe stableValueEquals helper, suppresses expected handled CLI errors from Sentry reporting, and updates documentation and tests accordingly. There are no review comments to address, so I have no feedback to provide.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. 🚀

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

@unbraind

unbraind commented Jun 1, 2026

Copy link
Copy Markdown
Owner Author

@codex review

/gemini review

@gemini-code-assist review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request hardens the package ecosystem SDK and agent UX. Key changes include implementing a build lock and stale-file retention in the CLI bundler to prevent concurrent build issues, optimizing get --depth brief to omit token-heavy fields, and introducing a primitive-safe stableValueEquals helper to replace order-sensitive JSON.stringify comparisons. Additionally, validation diagnostics are now compact by default, and expected handled CLI errors are filtered out from Sentry reporting. Feedback suggests expanding stableValueEquals to correctly handle Date, Set, and Map objects, and making the stale-file cleanup in the bundler more robust by ignoring all filesystem errors during lstat and unlink operations.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +52 to +63
export function stableValueEquals(left: unknown, right: unknown): boolean {
if (Object.is(left, right)) {
return true;
}
if (left === null || right === null || typeof left !== "object" || typeof right !== "object") {
return false;
}
if (left instanceof RegExp || right instanceof RegExp) {
return left instanceof RegExp && right instanceof RegExp && left.toString() === right.toString();
}
return stableStringify(left) === stableStringify(right);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The stableValueEquals helper currently does not handle Date, Set, or Map objects correctly. Since stableStringify relies on JSON.stringify (which serializes Set and Map to empty objects "{}"), different sets or maps will incorrectly compare as equal. Similarly, Date objects might not be compared correctly depending on how sortObjectKeys processes them.

We should explicitly handle Date, Set, and Map instances to ensure robust and correct equality checks.

export function stableValueEquals(left: unknown, right: unknown): boolean {
  if (Object.is(left, right)) {
    return true;
  }
  if (left === null || right === null || typeof left !== "object" || typeof right !== "object") {
    return false;
  }
  if (left instanceof RegExp || right instanceof RegExp) {
    return left instanceof RegExp && right instanceof RegExp && left.toString() === right.toString();
  }
  if (left instanceof Date || right instanceof Date) {
    return left instanceof Date && right instanceof Date && left.getTime() === right.getTime();
  }
  if (left instanceof Set || right instanceof Set) {
    if (!(left instanceof Set && right instanceof Set) || left.size !== right.size) {
      return false;
    }
    const leftArr = [...left].map(stableStringify).sort();
    const rightArr = [...right].map(stableStringify).sort();
    return leftArr.every((val, i) => val === rightArr[i]);
  }
  if (left instanceof Map || right instanceof Map) {
    if (!(left instanceof Map && right instanceof Map) || left.size !== right.size) {
      return false;
    }
    const leftArr = [...left.entries()].map(([k, v]) => stableStringify([k, v])).sort();
    const rightArr = [...right.entries()].map(([k, v]) => stableStringify([k, v])).sort();
    return leftArr.every((val, i) => val === rightArr[i]);
  }
  return stableStringify(left) === stableStringify(right);
}

Comment thread scripts/bundle-cli.mjs Outdated
Comment on lines +99 to +113
const fileStats = await lstat(filePath).catch((lstatError) => {
if (lstatError && typeof lstatError === "object" && "code" in lstatError && lstatError.code === "ENOENT") {
return null;
}
throw lstatError;
});
if (!fileStats || now - fileStats.mtimeMs < bundleStaleRetentionMs) {
return;
}
await unlink(filePath).catch((error) => {
if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") {
return;
}
throw error;
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

During the stale file cleanup process, any transient permission issues, file locks, or other filesystem errors (besides ENOENT) when calling lstat or unlink will throw an error and crash the entire build process.

Since this is a non-critical cleanup step, we should make it robust by catching and ignoring all errors, ensuring that the build itself does not fail due to cleanup issues.

        const fileStats = await lstat(filePath).catch(() => null);
        if (!fileStats || now - fileStats.mtimeMs < bundleStaleRetentionMs) {
          return;
        }
        await unlink(filePath).catch(() => {});

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Bravo.

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

@unbraind

unbraind commented Jun 1, 2026

Copy link
Copy Markdown
Owner Author

@codex review

/gemini review

@gemini-code-assist review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request hardens the package ecosystem SDK and agent UX. Key changes include optimizing the get --depth brief command to omit body, linked, and claim state fields; introducing a compact mode for validation diagnostics with a verboseDiagnostics toggle; implementing a primitive-safe stableValueEquals helper to replace order-sensitive JSON.stringify comparisons; adding concurrency locking and stale file retention to the CLI bundler; and suppressing expected handled CLI errors from Sentry reporting. Feedback highlights a critical issue in stableValueEquals where nested RegExp, Set, or Map instances inside objects or arrays will incorrectly compare as equal because the function falls back to stableStringify (which serializes them to {}). A recursive deep-equality implementation is suggested to resolve this.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +52 to +82
export function stableValueEquals(left: unknown, right: unknown): boolean {
if (Object.is(left, right)) {
return true;
}
if (left === null || right === null || typeof left !== "object" || typeof right !== "object") {
return false;
}
if (left instanceof RegExp || right instanceof RegExp) {
return left instanceof RegExp && right instanceof RegExp && left.toString() === right.toString();
}
if (left instanceof Date || right instanceof Date) {
return left instanceof Date && right instanceof Date && left.getTime() === right.getTime();
}
if (left instanceof Set || right instanceof Set) {
if (!(left instanceof Set && right instanceof Set) || left.size !== right.size) {
return false;
}
const leftValues = [...left].map(stableStringify).sort();
const rightValues = [...right].map(stableStringify).sort();
return leftValues.every((value, index) => value === rightValues[index]);
}
if (left instanceof Map || right instanceof Map) {
if (!(left instanceof Map && right instanceof Map) || left.size !== right.size) {
return false;
}
const leftEntries = [...left.entries()].map((entry) => stableStringify(entry)).sort();
const rightEntries = [...right.entries()].map((entry) => stableStringify(entry)).sort();
return leftEntries.every((value, index) => value === rightEntries[index]);
}
return stableStringify(left) === stableStringify(right);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

The current implementation of stableValueEquals relies on stableStringify (which uses JSON.stringify) for nested object/array comparisons and for elements inside Set and Map. Since JSON.stringify does not natively support RegExp, Set, or Map (serialising them to {}), nested instances of these types will incorrectly compare as equal (e.g., { r: /abc/ } and { r: /def/ } would be considered equal).

Additionally, serialising entire objects to strings is less efficient than a recursive deep-equality check, which can short-circuit as soon as a difference is found.

We should replace this with a fully recursive deep-equality implementation that correctly handles nested arrays, plain objects, RegExp, Date, Set, and Map instances.

export function stableValueEquals(left: unknown, right: unknown): boolean {
  if (Object.is(left, right)) {
    return true;
  }
  if (left === null || right === null || typeof left !== "object" || typeof right !== "object") {
    return false;
  }
  if (left instanceof RegExp || right instanceof RegExp) {
    return left instanceof RegExp && right instanceof RegExp && left.toString() === right.toString();
  }
  if (left instanceof Date || right instanceof Date) {
    return left instanceof Date && right instanceof Date && left.getTime() === right.getTime();
  }
  if (left instanceof Set || right instanceof Set) {
    if (!(left instanceof Set && right instanceof Set) || left.size !== right.size) {
      return false;
    }
    const rightArray = [...right];
    const matched = new Set<number>();
    for (const leftVal of left) {
      let found = false;
      for (let i = 0; i < rightArray.length; i++) {
        if (matched.has(i)) {
          continue;
        }
        if (stableValueEquals(leftVal, rightArray[i])) {
          matched.add(i);
          found = true;
          break;
        }
      }
      if (!found) {
        return false;
      }
    }
    return true;
  }
  if (left instanceof Map || right instanceof Map) {
    if (!(left instanceof Map && right instanceof Map) || left.size !== right.size) {
      return false;
    }
    const rightEntries = [...right.entries()];
    const matched = new Set<number>();
    for (const [leftKey, leftVal] of left.entries()) {
      let found = false;
      for (let i = 0; i < rightEntries.length; i++) {
        if (matched.has(i)) {
          continue;
        }
        const [rightKey, rightVal] = rightEntries[i];
        if (stableValueEquals(leftKey, rightKey) && stableValueEquals(leftVal, rightVal)) {
          matched.add(i);
          found = true;
          break;
        }
      }
      if (!found) {
        return false;
      }
    }
    return true;
  }
  if (Array.isArray(left) || Array.isArray(right)) {
    if (!Array.isArray(left) || !Array.isArray(right) || left.length !== right.length) {
      return false;
    }
    return left.every((val, index) => stableValueEquals(val, right[index]));
  }
  const leftKeys = Object.keys(left).sort();
  const rightKeys = Object.keys(right).sort();
  if (leftKeys.length !== rightKeys.length) {
    return false;
  }
  for (let i = 0; i < leftKeys.length; i++) {
    if (leftKeys[i] !== rightKeys[i]) {
      return false;
    }
    const key = leftKeys[i];
    if (!stableValueEquals((left as Record<string, unknown>)[key], (right as Record<string, unknown>)[key])) {
      return false;
    }
  }
  return true;
}

Replace the remaining stableValueEquals stringification fallback with recursive comparison for nested arrays, objects, RegExp, Date, Set, and Map values.

Add regression coverage for nested non-JSON values and duplicate deep-equal collection entries so the shared equality helper keeps 100% coverage.

Verification: node scripts/run-tests.mjs test -- tests/unit/serialization-utils.spec.ts; pnpm typecheck; pnpm build; pnpm changelog:pm:check; node scripts/run-tests.mjs coverage.
@unbraind

unbraind commented Jun 1, 2026

Copy link
Copy Markdown
Owner Author

@codex review

/gemini review

@gemini-code-assist review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request introduces several hardening and optimization improvements to the CLI and SDK. Key changes include implementing a build-locking mechanism to serialize concurrent bundle rebuilds, optimizing the get --depth brief command to omit heavy fields for lower-token agent reads, replacing order-sensitive JSON.stringify comparisons with a recursive stableValueEquals helper, and keeping default validation diagnostics compact unless verbose mode is enabled. Additionally, expected handled CLI errors are now suppressed from Sentry. Feedback on the changes suggests converting Sentry issue text to lowercase in sentry-telemetry-gate.mjs to prevent casing mismatches against lowercase patterns.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +143 to +144
const combinedText = issueTextValue(issue);
return KNOWN_EXPECTED_HANDLED_CLI_ISSUE_PATTERNS.some((pattern) => combinedText.includes(pattern));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The error patterns in KNOWN_EXPECTED_HANDLED_CLI_ISSUE_PATTERNS are defined in lowercase, but actual CLI error messages captured by Sentry might contain uppercase characters (e.g., 'Slack webhook request failed' or 'Authentication required...'). To ensure robust matching and prevent the release gate from failing due to casing mismatches, convert combinedText to lowercase before performing the .includes() check.

Suggested change
const combinedText = issueTextValue(issue);
return KNOWN_EXPECTED_HANDLED_CLI_ISSUE_PATTERNS.some((pattern) => combinedText.includes(pattern));
const combinedText = issueTextValue(issue).toLowerCase();
return KNOWN_EXPECTED_HANDLED_CLI_ISSUE_PATTERNS.some((pattern) => combinedText.includes(pattern));

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d347e76051

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

function isExpectedHandledCliIssue(issue) {
const metadata = issue && typeof issue === "object" ? issue.metadata : null;
const type = metadata && typeof metadata.type === "string" ? metadata.type : "";
if ((type !== "PmCliError" && type !== "CommandError") || issue?.isUnhandled === true) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Require handled status before ignoring Sentry issues

When the gate uses the REST API token path (the normal path whenever a Sentry token is present), the project issues response schema lists fields such as level, logger, metadata, title, and type but not isUnhandled (Sentry docs: https://docs.sentry.io/api/events/list-a-projects-issues/). That means issue?.isUnhandled === true is false for API results, so an unresolved unhandled PmCliError/CommandError whose title/value matches one of these expected patterns is moved to ignoredExpected and excluded from the critical/high tally. Please only ignore these issues when the payload positively proves they are handled, or fetch/query the unhandled state before partitioning.

Useful? React with 👍 / 👎.

Make release-gate issue text lowercase at the pattern matching sites for ignored console noise and expected handled CLI errors.

Add a release automation contract assertion so expected handled CLI issue matching stays case-insensitive.

Verification: node scripts/run-tests.mjs test -- tests/integration/release-automation-contract.spec.ts tests/unit/serialization-utils.spec.ts; pnpm typecheck; pnpm build; pnpm changelog:pm:check; SENTRY_FORCE_ENV_TOKEN=1 SENTRY_AUTH_TOKEN=*** node scripts/release/sentry-telemetry-gate.mjs --json --telemetry-mode off --max-critical 0 --max-high 0.
@unbraind

unbraind commented Jun 1, 2026

Copy link
Copy Markdown
Owner Author

@codex review

/gemini review

@gemini-code-assist review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request hardens the package ecosystem, SDK, and agent UX. Key changes include introducing a recursive, order-insensitive stableValueEquals utility to replace order-sensitive JSON.stringify comparisons, optimizing get --depth brief to omit empty fields for lower token usage, implementing a file-locking mechanism in bundle-cli.mjs for safe concurrent builds, and compacting default validation diagnostics. Additionally, expected handled CLI errors are now filtered out from Sentry telemetry. I have no feedback to provide as there are no review comments.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. You're on a roll.

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

@unbraind
unbraind merged commit e4b1e5f into main Jun 1, 2026
13 checks passed
@unbraind
unbraind deleted the agent-ux-sentry-bundle-hardening branch June 1, 2026 23:51
unbraind added a commit that referenced this pull request Jul 17, 2026
…cked counters vs edge-aware pm next), PR#577 lane reconciled zero-leak, 96h7 frontier note

Co-Authored-By: Claude Fable 5 <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.

1 participant