Skip to content

Reversible value parser mapping - #866

Merged
dahlia merged 13 commits into
mainfrom
transform
Jul 2, 2026
Merged

Reversible value parser mapping#866
dahlia merged 13 commits into
mainfrom
transform

Conversation

@dahlia

@dahlia dahlia commented Jul 2, 2026

Copy link
Copy Markdown
Owner

Value parsers already know how to parse, validate fallbacks, format values, and expose completion metadata, but parser-level map() cannot carry those contracts through a value-only conversion. This PR adds two helpers that make that conversion explicit instead of asking callers to rebuild the same parser behavior by hand.

The lower-level helper is transform(). It wraps an existing value parser with a reversible mapping, so the forward direction is used after parsing and the reverse direction is used for formatting, fallback validation, placeholders, choices, suggestions, and dependency replay. Keeping both directions in the API is the important part: without the reverse mapping, Optique would have to guess how a domain value should be serialized back into the original CLI-facing value.

The second helper is biject(), which covers the common one-to-one dictionary case. It builds on choice() plus transform(), but snapshots the mapping at construction time and validates the dictionary before any parsing happens. Empty dictionaries, empty input keys, array mappings, and duplicate mapped values fail early because they would make the public parser contract ambiguous. Mapped values are compared with the same equality rules as Map/Set, which keeps the runtime behavior familiar for primitives, NaN, and object identity.

The implementation also preserves the metadata that dependency-aware parsers need. A transformed derived parser keeps its dependency markers and default snapshots, and placeholder mapping stays lazy so a missing derived placeholder does not throw during parser construction. That lets transform() behave like a value-parser counterpart to parser-level map() without weakening the two-pass parse path.

Tests cover the straightforward parse and format cases, but also the edges that define the API shape: dynamic dependency defaults, lazy placeholders, fallback validation, mutated input mappings, numeric object keys, typed interfaces without index signatures, duplicate values, NaN, object identity, and invalid array input. The docs in docs/concepts/valueparsers.md describe both helpers, and the value parser catalog, landing page count, changelog, and Optique agent skill were updated so the new surface is discoverable.

dahlia added 2 commits July 2, 2026 22:17
Add transform() for value parsers that can map parsed values to a
public type while using the inverse mapping for formatting and fallback
validation.  The wrapper preserves parser mode, suggestions, dependency
metadata, and derived default snapshots.

Document the combinator in the value parser guide, catalog, agent skill,
and changelog for the PR #866 branch.

#866

Assisted-by: Codex:gpt-5.5
Value parsers need a first-class way to expose domain values while
still preserving CLI-facing spelling, formatting, fallback validation,
and completion metadata. Add transform() for reversible value-parser
mapping and biject() for one-to-one string-to-value dictionaries.

The biject() parser snapshots mappings at construction time, rejects
arrays, empty mappings, empty keys, and duplicate mapped values, and
keeps fallback validation tied to the mapped value set.

#866

Assisted-by: Codex:gpt-5.5
@dahlia dahlia added this to the Optique 1.2 milestone Jul 2, 2026
@dahlia
dahlia requested a review from Copilot July 2, 2026 14:36
@dahlia dahlia self-assigned this Jul 2, 2026
@dahlia dahlia added the enhancement New feature or request label Jul 2, 2026

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@dahlia

dahlia commented Jul 2, 2026

Copy link
Copy Markdown
Owner Author

@codex review

@codecov

codecov Bot commented Jul 2, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 87.92570% with 39 lines in your changes missing coverage. Please review.
✅ Project coverage is 93.41%. Comparing base (7cd2279) to head (fa56f91).
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
packages/core/src/valueparser.ts 87.75% 33 Missing and 3 partials ⚠️
packages/core/src/internal/mode-dispatch.ts 89.65% 1 Missing and 2 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #866      +/-   ##
==========================================
- Coverage   93.45%   93.41%   -0.05%     
==========================================
  Files          75       75              
  Lines       38181    38503     +322     
  Branches     9519     9577      +58     
==========================================
+ Hits        35683    35967     +284     
- Misses       1699     1733      +34     
- Partials      799      803       +4     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@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 the biject() parser and the transform() combinator to @optique/core/valueparser, allowing for reversible value parser transformations and one-to-one string-to-value dictionaries. Feedback focuses on improving runtime safety and robustness, including using wrapForMode in transform(), refining object/array validation in biject(), handling undefined placeholders properly, and guarding against non-object values in wrapIterableForMode.

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 packages/core/src/valueparser.ts
Comment thread packages/core/src/valueparser.ts
Comment thread packages/core/src/valueparser.test.ts
Comment thread packages/core/src/valueparser.ts
Comment thread packages/core/src/internal/mode-dispatch.ts
@chatgpt-codex-connector

Copy link
Copy Markdown

💡 Codex Review

enumerable: false,

P2 Badge Keep transformed placeholder enumerable

When a transform() parser (and therefore biject()) is passed to dependency(), the dependency wrapper clones the value parser with object spread in packages/core/src/internal/dependency.ts lines 573-574, which only copies enumerable own properties. Marking placeholder non-enumerable here means the cloned dependency source no longer has the required placeholder property, so using option("--mode", dependency(transform(...)))/dependency(biject(...)) is rejected by isValueParser() before parsing begins.

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

This PR adds biject() and transform() to @optique/core, along with internal mode-dispatch helpers they use. biject() builds a string-key-to-value parser with reverse mapping and construction-time validation. transform() wraps an existing parser and remaps parsed values while preserving mode and dependency metadata. Tests and documentation were updated to cover the new behavior.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

  • dahlia/optique#826: Related through the ValueParser.validate hook and fallback validation behavior used by transform().
  • dahlia/optique#852: Related through the Optique skill guidance updates that document biject() and transform().
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: reversible mapping support for value parsers.
Description check ✅ Passed The description directly describes the new transform() and biject() helpers and the related docs/tests updates.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch transform

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.

@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: 2

🤖 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 `@packages/core/src/valueparser.test.ts`:
- Around line 3133-3149: The constructor-time duplicate-value tests in biject
are only checking RangeError and should also pin the exact diagnostic message
for each case. Update the assertions in valueparser.test.ts around biject so the
duplicate Map-equality cases for positiveZero/negativeZero and first/second
verify both the thrown error type and the specific duplicate-value message text,
using the same constructor-time validation pattern as the other
option-validation tests.

In `@packages/core/src/valueparser.ts`:
- Around line 1027-1030: The fallback validation in ValueParser.value should
preserve the original transformed value when the round-trip fails, instead of
letting mapping.unmap()/format() exceptions escape. Update the value() path to
catch errors around the syncParser.format(mapping.unmap(value)) and
syncParser.parse(...) round-trip, and treat a thrown formatting/parsing step as
successful preservation of the original value. Keep the behavior aligned with
the existing validate() fallback flow in ValueParser and preserve the original
value rather than crashing default/fallback validation.
🪄 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

Review profile: ASSERTIVE

Plan: Pro

Run ID: 7eb97bba-c484-4535-8e2b-6700c5da2be7

📥 Commits

Reviewing files that changed from the base of the PR and between 7cd2279 and 77665cb.

📒 Files selected for processing (8)
  • CHANGES.md
  • docs/.vitepress/theme/components/ParserCatalog.vue
  • docs/concepts/valueparsers.md
  • docs/index.md
  • packages/core/skills/optique/SKILL.md
  • packages/core/src/internal/mode-dispatch.ts
  • packages/core/src/valueparser.test.ts
  • packages/core/src/valueparser.ts

Comment thread packages/core/src/valueparser.test.ts
Comment thread packages/core/src/valueparser.ts Outdated
Transformed value parsers sit on top of existing parser contracts, so they
need to keep the same mode and metadata guardrails as other wrappers. Route
wrapped parse results through the mode boundary, keep missing placeholders
missing, and leave transformed placeholders enumerable for dependency source
cloning.

Also guard async-iterator detection before using the in operator and keep the
array mapping error short and consistent with the surrounding TypeErrors.

#866 (comment)
#866 (comment)
#866 (comment)
#866 (comment)
#866 (comment)
#866 (comment)

Assisted-by: Codex:gpt-5.5
@dahlia

dahlia commented Jul 2, 2026

Copy link
Copy Markdown
Owner Author

Addressed in 43dec86: transformed placeholders remain enumerable, so dependency(transform(...)) and dependency(biject(...)) survive the dependency source object spread.

@dahlia

dahlia commented Jul 2, 2026

Copy link
Copy Markdown
Owner Author

/gemini review

@dahlia

dahlia commented Jul 2, 2026

Copy link
Copy Markdown
Owner Author

@codex review

Fallback validation should not crash when the validation round trip cannot
serialize a transformed value. Preserve the original transformed value when
unmapping, formatting, or parsing throws, matching the existing fallback
validation behavior elsewhere in the parser stack.

Also pin the duplicate-value diagnostics for the Map-equality biject() cases
so the constructor-time error messages stay covered.

#866 (comment)
#866 (comment)

Assisted-by: Codex:gpt-5.5

@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 the biject() parser and the transform() combinator to @optique/core/valueparser, along with updated documentation, skills guides, and comprehensive unit tests. Feedback focuses on strengthening the input validation in biject() to explicitly reject null and non-object primitives with a TypeError rather than letting them bypass checks or throw misleading errors, as well as adding corresponding unit tests for these edge cases.

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 packages/core/src/valueparser.ts
Comment thread packages/core/src/valueparser.test.ts
@dahlia

dahlia commented Jul 2, 2026

Copy link
Copy Markdown
Owner Author

@codex review

@dahlia

dahlia commented Jul 2, 2026

Copy link
Copy Markdown
Owner Author

/gemini review

The TypeScript overloads describe object mappings, but JavaScript callers can
still pass null or primitive values at runtime. Reject those values with a
TypeError before enumerating keys, so strings cannot become accidental indexed
mappings and null does not fall through to the empty-mapping RangeError.

#866 (comment)
#866 (comment)

Assisted-by: Codex:gpt-5.5
@dahlia

dahlia commented Jul 2, 2026

Copy link
Copy Markdown
Owner Author

@codex review

@dahlia

dahlia commented Jul 2, 2026

Copy link
Copy Markdown
Owner Author

/gemini review

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

@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 the biject() parser and the transform() combinator to @optique/core/valueparser, allowing for one-to-one string-to-value mappings and reversible value parser transformations. It also includes comprehensive unit tests, updated documentation, and internal mode-dispatch helpers. The review feedback suggests enhancing the robustness of biject() by explicitly validating that the input mapping is a non-null object before checking if it is an array, and using Object.keys() for a more idiomatic key extraction. Additionally, it recommends wrapping the inner parser's suggest result in transform() to guarantee consistent runtime safety and mode-dispatch behavior.

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 packages/core/src/valueparser.ts Outdated
Comment thread packages/core/src/valueparser.ts

@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: 2

Caution

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

⚠️ Outside diff range comments (3)
packages/core/src/valueparser.ts (2)

1012-1023: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Same fallback-crash risk in the validate() delegate branch.

mapping.unmap(value) and mapping.map(result.value) here are unguarded, so a throwing unmap/map implementation (e.g. for out-of-domain fallback/default values) propagates out of validate() instead of degrading gracefully. This mirrors the already-flagged issue in the adjacent sync-fallback branch below (lines 1024-1038), where the established convention in this codebase is to preserve/tolerate transformation failures during fallback validation rather than let them escape.

🐛 Proposed fix
       value(value: U): ValueParserResult<U> {
-        const result = validate(mapping.unmap(value));
-        return result.success
-          ? { success: true as const, value: mapping.map(result.value) }
-          : result;
+        let unmapped: T;
+        try {
+          unmapped = mapping.unmap(value);
+        } catch {
+          return { success: true as const, value };
+        }
+        const result = validate(unmapped);
+        if (!result.success) return result;
+        try {
+          return { success: true as const, value: mapping.map(result.value) };
+        } catch {
+          return { success: true as const, value };
+        }
       },
🤖 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 `@packages/core/src/valueparser.ts` around lines 1012 - 1023, The `validate()`
delegate in `valueparser.ts` still calls `mapping.unmap(value)` and
`mapping.map(result.value)` without protection, so a throwing mapping can escape
instead of being tolerated like the adjacent fallback path. Update the `typeof
parser.validate === "function"` branch in the transformed parser setup to catch
transformation failures around the `validate` wrapper, and make it return a safe
validation result rather than propagating exceptions; keep the behavior aligned
with the nearby sync-fallback handling and the `transformed.validate` override.

439-466: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Unguarded mapping.map() in the non-deferred success path can crash parse().

The deferred branch (450-464) wraps mapping.map(result.value) in try/catch and falls back to undefined on failure, but the primary non-deferred branch (465) — which handles real, successfully-parsed user input — calls mapping.map(result.value) with no error handling at all. If a user-supplied map() throws for any valid parsed value, this propagates as an uncaught exception through transform()'s parse() (line 979-985): synchronously for sync-mode parsers, or as an unhandled promise rejection for async-mode parsers (since it runs inside mapModeValue's .then()). This breaks the ValueParserResult contract, where callers expect {success: false, error} rather than a thrown/rejected value.

This asymmetry (deferred tolerates failures, non-deferred does not) is most concerning precisely because non-deferred is the case triggered by actual CLI input.

🐛 Proposed fix
-  return preserveSnapshot({ success: true, value: mapping.map(result.value) });
+  try {
+    return preserveSnapshot({
+      success: true,
+      value: mapping.map(result.value),
+    });
+  } catch (error) {
+    return {
+      success: false,
+      error: error instanceof Error ? error.message : String(error),
+    };
+  }
 }
🤖 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 `@packages/core/src/valueparser.ts` around lines 439 - 466, The non-deferred
success path in transformValueParserResult still calls mapping.map(result.value)
without protection, unlike the deferred branch, so a thrown mapping error can
escape parse(). Update transformValueParserResult to handle mapping.map failures
in the non-deferred branch the same way as the deferred branch, preserving the
snapshot via preserveSnapshot and returning a failed ValueParserResult instead
of throwing. Keep the fix localized to transformValueParserResult so callers of
transform() and parse() continue to receive the expected result shape.
packages/core/src/internal/mode-dispatch.ts (1)

125-148: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Guard fix looks correct.

canCheckAsyncIterator correctly prevents the in operator from throwing on null/primitive inputs before checking Symbol.asyncIterator, matching the accompanying test.

Minor: the canCheckAsyncIterator && Symbol.asyncIterator in value check is duplicated in both the sync and async closures. Hoisting it into a single const isAsyncIterable = ... above dispatchIterableByMode(...) would remove the duplication without changing behavior (only one closure executes per call, so this is a readability nit, not a bug).

♻️ Optional dedup
 export function wrapIterableForMode<M extends Mode, T>(
   mode: M,
   value: Iterable<T> | AsyncIterable<T>,
 ): ModeIterable<M, T> {
   const canCheckAsyncIterator = value != null &&
     (typeof value === "object" || typeof value === "function");
+  const isAsyncIterable = canCheckAsyncIterator &&
+    Symbol.asyncIterator in value;
   return dispatchIterableByMode(
     mode,
     () => {
-      if (canCheckAsyncIterator && Symbol.asyncIterator in value) {
+      if (isAsyncIterable) {
         throw new TypeError(
           "Synchronous mode cannot wrap AsyncIterable value.",
         );
       }
       return value;
     },
     () => {
-      if (canCheckAsyncIterator && Symbol.asyncIterator in value) return value;
+      if (isAsyncIterable) return value;
       return (async function* () {
         yield* value;
       })();
     },
   );
 }
🤖 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 `@packages/core/src/internal/mode-dispatch.ts` around lines 125 - 148, The
guard in wrapIterableForMode is correct, but the async-iterable check is
duplicated in both closures passed to dispatchIterableByMode. Hoist the
Symbol.asyncIterator presence check into a single isAsyncIterable const in
wrapIterableForMode, then reuse that value in the sync and async branches so the
logic is centralized and easier to read without changing behavior.
🤖 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 `@packages/core/src/internal/mode-dispatch.test.ts`:
- Around line 1-18: Add a regression test for the async branch of
wrapIterableForMode to cover the same canCheckAsyncIterator guard used by
dispatchIterableByMode’s async path. Mirror the existing sync/null assertion in
mode-dispatch.test.ts by invoking wrapIterableForMode("async", null as never),
asserting it throws TypeError, and verifying the message does not contain the
native "in' operator" text so both branches are protected.

In `@packages/core/src/valueparser.test.ts`:
- Around line 2820-2831: The current test only indirectly proves the
enumerability guarantee for transformed placeholders. Strengthen the assertion
in the dependency/transform/biject test by explicitly checking that the
placeholder survives the `dependency(...)` object spread and remains enumerable
on the resulting parser object, rather than relying only on
`isValueParser(...)`. Use the existing `dependency`, `transform`, `biject`, and
`isValueParser` helpers to locate the test and add a direct assertion on the
placeholder’s presence/enumerability.

---

Outside diff comments:
In `@packages/core/src/internal/mode-dispatch.ts`:
- Around line 125-148: The guard in wrapIterableForMode is correct, but the
async-iterable check is duplicated in both closures passed to
dispatchIterableByMode. Hoist the Symbol.asyncIterator presence check into a
single isAsyncIterable const in wrapIterableForMode, then reuse that value in
the sync and async branches so the logic is centralized and easier to read
without changing behavior.

In `@packages/core/src/valueparser.ts`:
- Around line 1012-1023: The `validate()` delegate in `valueparser.ts` still
calls `mapping.unmap(value)` and `mapping.map(result.value)` without protection,
so a throwing mapping can escape instead of being tolerated like the adjacent
fallback path. Update the `typeof parser.validate === "function"` branch in the
transformed parser setup to catch transformation failures around the `validate`
wrapper, and make it return a safe validation result rather than propagating
exceptions; keep the behavior aligned with the nearby sync-fallback handling and
the `transformed.validate` override.
- Around line 439-466: The non-deferred success path in
transformValueParserResult still calls mapping.map(result.value) without
protection, unlike the deferred branch, so a thrown mapping error can escape
parse(). Update transformValueParserResult to handle mapping.map failures in the
non-deferred branch the same way as the deferred branch, preserving the snapshot
via preserveSnapshot and returning a failed ValueParserResult instead of
throwing. Keep the fix localized to transformValueParserResult so callers of
transform() and parse() continue to receive the expected result shape.
🪄 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

Review profile: ASSERTIVE

Plan: Pro

Run ID: 8168b56a-faad-4561-8c2a-eff49792fcbf

📥 Commits

Reviewing files that changed from the base of the PR and between 77665cb and 43dec86.

📒 Files selected for processing (4)
  • packages/core/src/internal/mode-dispatch.test.ts
  • packages/core/src/internal/mode-dispatch.ts
  • packages/core/src/valueparser.test.ts
  • packages/core/src/valueparser.ts

Comment thread packages/core/src/internal/mode-dispatch.test.ts
Comment thread packages/core/src/valueparser.test.ts

@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 two new value parsers to @optique/core/valueparser: biject(), which provides a one-to-one mapping between CLI string keys and domain values, and transform(), a combinator that maps the output of an existing parser to a different type using a reversible mapping. The changes also include corresponding documentation updates, comprehensive unit tests, and helper utilities in mode-dispatch.ts. Feedback on the implementation highlights a potential validation bypass in the fallback validate method for synchronous parsers, where exceptions thrown during unmap are caught and silently treated as successful.

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 packages/core/src/valueparser.ts

@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: a04840e1d6

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/core/src/valueparser.ts

@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.

Caution

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

⚠️ Outside diff range comments (1)
packages/core/src/valueparser.test.ts (1)

2662-2866: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Basic transform() suite covers the intended round-trip, failure, and throw scenarios.

This is a good baseline suite. Given the fallback validate() concern raised in valueparser.ts (sync fallback treating format()/unmap() throws the same as parse() failures), it would help to add/confirm an explicit assertion in the "Cannot format sentinel" case (around lines 2781-2798) that fallback validation preserves the original value rather than rejecting it, once/if that implementation is corrected.

🤖 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 `@packages/core/src/valueparser.test.ts` around lines 2662 - 2866, Add an
explicit test in the transform() suite around the fallback validation path using
the existing sentinel/“Cannot format sentinel” setup to assert the intended
behavior once valueparser.ts is fixed. In the parser.validate?.(sentinel) case,
verify that a format() throw during round-trip validation does not reject the
original fallback value and that the result stays successful/preserves the
sentinel. Use the existing transform(), validate(), and formatter objects in the
“should reject fallback values when round-trip validation throws” test to locate
the change.
♻️ Duplicate comments (1)
packages/core/src/valueparser.ts (1)

1040-1060: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Sync fallback validate() rejects instead of preserving the original value when format()/unmap() throws.

This single try/catch treats a thrown mapping.unmap(value)/syncParser.format(...) exactly the same as a thrown/failed syncParser.parse(...), returning transformMappingFailure() in both cases. Per the retrieved learning on this exact fallback pattern in this file, a thrown format() step should be treated as successful preservation of the original value, while only parse() failing/throwing on the formatted output should cause rejection. A prior review thread on this exact code path (same syncParser.parse(syncParser.format(mapping.unmap(value))) shape) was reportedly addressed by preserving the original value on throw, but the current code does not reflect that — it now stops the crash but still rejects instead of preserving.

This means fallback/default validation (e.g. bindEnv()/bindConfig()) for a transform()-wrapped parser will incorrectly reject values whose format() step throws, even though the value itself is perfectly valid.

🐛 Proposed fix to separate format()/unmap() throws from parse() failures
   } else if (parser.mode === "sync") {
     const syncParser = parser as ValueParser<"sync", T>;
     Object.defineProperty(transformed, "validate", {
       value(value: U): ValueParserResult<U> {
-        let result: ValueParserResult<T>;
-        try {
-          result = syncParser.parse(syncParser.format(mapping.unmap(value)));
-        } catch {
-          return transformMappingFailure();
-        }
+        let formatted: string;
+        try {
+          formatted = syncParser.format(mapping.unmap(value));
+        } catch {
+          return { success: true as const, value };
+        }
+        let result: ValueParserResult<T>;
+        try {
+          result = syncParser.parse(formatted);
+        } catch {
+          return transformMappingFailure();
+        }
         if (!result.success) return result;
         try {
           return { success: true as const, value: mapping.map(result.value) };
         } catch {
           return transformMappingFailure();
         }
       },

Based on learnings: "do not flag the 'fallback' validation pattern that round-trips via ValueParser.format() + ValueParser.parse() as invalid when it intentionally treats a thrown ValueParser.format() during format()parse() round-trips as successful preservation of the original value... Review should still flag... cases where parse() fails or throws while processing the formatted output."

Please verify against the actual test suite (e.g. the format()-throwing "sentinel" fixture around packages/core/src/valueparser.test.ts:2781-2798) whether this fallback currently preserves or rejects such values.

#!/bin/bash
# Inspect the sync fallback validate() implementation and its test coverage
sed -n '1030,1065p' packages/core/src/valueparser.ts
rg -n -B5 -A25 'Cannot format sentinel' packages/core/src/valueparser.test.ts
🤖 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 `@packages/core/src/valueparser.ts` around lines 1040 - 1060, The sync fallback
validate path in ValueParser’s transformed `validate()` currently collapses
`mapping.unmap(value)` / `syncParser.format(...)` throws into the same rejection
as `syncParser.parse(...)` failures. Update the `parser.mode === "sync"` branch
so `transformMappingFailure()` is only returned when
`syncParser.parse(syncParser.format(...))` fails or throws on the formatted
output, while a thrown `format()`/`unmap()` step preserves the original value
instead of rejecting. Keep the fix localized to the `validate` implementation
around `syncParser`, `mapping.unmap`, and `transformMappingFailure`, and align
it with the existing sentinel test coverage in `valueparser.test.ts`.

Source: Learnings

🤖 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.

Outside diff comments:
In `@packages/core/src/valueparser.test.ts`:
- Around line 2662-2866: Add an explicit test in the transform() suite around
the fallback validation path using the existing sentinel/“Cannot format
sentinel” setup to assert the intended behavior once valueparser.ts is fixed. In
the parser.validate?.(sentinel) case, verify that a format() throw during
round-trip validation does not reject the original fallback value and that the
result stays successful/preserves the sentinel. Use the existing transform(),
validate(), and formatter objects in the “should reject fallback values when
round-trip validation throws” test to locate the change.

---

Duplicate comments:
In `@packages/core/src/valueparser.ts`:
- Around line 1040-1060: The sync fallback validate path in ValueParser’s
transformed `validate()` currently collapses `mapping.unmap(value)` /
`syncParser.format(...)` throws into the same rejection as
`syncParser.parse(...)` failures. Update the `parser.mode === "sync"` branch so
`transformMappingFailure()` is only returned when
`syncParser.parse(syncParser.format(...))` fails or throws on the formatted
output, while a thrown `format()`/`unmap()` step preserves the original value
instead of rejecting. Keep the fix localized to the `validate` implementation
around `syncParser`, `mapping.unmap`, and `transformMappingFailure`, and align
it with the existing sentinel test coverage in `valueparser.test.ts`.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 2eebf891-45df-4333-a053-19ce37292397

📥 Commits

Reviewing files that changed from the base of the PR and between 1dd4b70 and a847eac.

📒 Files selected for processing (2)
  • packages/core/src/valueparser.test.ts
  • packages/core/src/valueparser.ts

Sync fallback validation should distinguish failures while preparing the
round-trip from failures while parsing the formatted value.  Preserve the
original transformed fallback when unmap() or format() cannot produce a CLI
spelling, while keeping malformed fallbacks that collapse to undefined rejected.

#866 (review)

Assisted-by: Codex:gpt-5.5
@dahlia

dahlia commented Jul 2, 2026

Copy link
Copy Markdown
Owner Author

@codex review

@dahlia

dahlia commented Jul 2, 2026

Copy link
Copy Markdown
Owner Author

/gemini 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 the transform() and biject() value parsers to @optique/core/valueparser, along with comprehensive tests and updated documentation. transform() allows wrapping an existing parser to map its results to another type using reversible mapping functions, while biject() provides a convenient way to map CLI string keys to unique values. Feedback on the changes highlights an inconsistency in packages/core/src/valueparser.ts regarding how unmap failures are handled during fallback validation between the explicit and synthesized validate branches, suggesting a consistent approach to preserve fallback values.

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 packages/core/src/valueparser.ts
@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Hooray!

Reviewed commit: 7a578ce1e9

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

The transformed parser should treat unmap() failures consistently whether the
wrapped parser provides validate() directly or falls back to a round-trip
validator.  Preserve sentinel fallback values when the inverse mapping cannot
serialize them, while still letting inner validate() failures report normally.

#866 (comment)

Assisted-by: Codex:gpt-5.5
@dahlia

dahlia commented Jul 2, 2026

Copy link
Copy Markdown
Owner Author

@codex review

@dahlia

dahlia commented Jul 2, 2026

Copy link
Copy Markdown
Owner Author

/gemini 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 two new value parsers to @optique/core/valueparser: biject(), which provides a one-to-one mapping between CLI string keys and domain values, and transform(), which allows wrapping an existing parser to map its output to a different type. The changes also include documentation updates, comprehensive unit tests, and internal helpers to manage execution modes. Feedback on the implementation suggests simplifying the validate method in the biject parser by removing a redundant parsing step when a value is not found in the reverse mapping.

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 packages/core/src/valueparser.ts Outdated

@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: be3862ceba

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/core/src/valueparser.ts Outdated
Comment thread packages/core/src/valueparser.ts
The transformed value parser should keep deferred placeholder results
alive when the placeholder cannot be mapped, since phase-two parsing still
needs to replay the real value later.  Direct dependency sources are now
rejected instead of silently registering transformed dependency values
against an untransformed source ID.  Invalid biject fallbacks also return
the choice-shaped error directly without an extra parse pass.

#866 (comment)
#866 (comment)
#866 (comment)

Assisted-by: Codex:gpt-5.5
@dahlia

dahlia commented Jul 2, 2026

Copy link
Copy Markdown
Owner Author

/gemini review

@dahlia

dahlia commented Jul 2, 2026

Copy link
Copy Markdown
Owner Author

@codex 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 two new value parsers to @optique/core/valueparser: transform(), which wraps an existing parser to map its results to another type, and biject(), which creates a parser from a one-to-one mapping of CLI keys to values. The changes also include comprehensive documentation updates, new tests, and internal helpers to manage execution modes. The review feedback suggests a critical improvement in transform()'s synchronous validation to wrap the inner parser's parse call with wrapForMode, preventing potential asynchronous leakage in synchronous contexts, along with importing the helper.

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 packages/core/src/valueparser.ts
Comment thread packages/core/src/valueparser.ts

@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: 6a72f08318

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/core/src/valueparser.ts Outdated
The transformed sync fallback validator now checks the formatted value
before replaying it through the inner parser, so malformed fallback shapes
cannot slip through as non-string input.  It also routes the replay through
wrapForMode so sync validation reports an accidental Promise return at the
same runtime boundary as parse().

#866 (comment)
#866 (comment)
#866 (comment)

Assisted-by: Codex:gpt-5.5
@dahlia

dahlia commented Jul 2, 2026

Copy link
Copy Markdown
Owner Author

@codex review

@dahlia

dahlia commented Jul 2, 2026

Copy link
Copy Markdown
Owner Author

/gemini 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 the biject() parser and transform() combinator to @optique/core/valueparser, enabling reversible value transformations and one-to-one string-to-value mappings. The changes include implementation details, extensive unit tests, documentation updates, and internal mode-dispatch helpers to preserve synchronous or asynchronous execution modes. There are no review comments on this pull request, and 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. What shall we delve into next?

Reviewed commit: fa56f91eba

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@dahlia
dahlia temporarily deployed to github-pages July 2, 2026 17:18 — with GitHub Actions Inactive
@dahlia
dahlia merged commit 747f36a into main Jul 2, 2026
12 of 14 checks passed
@dahlia
dahlia deleted the transform branch July 2, 2026 17:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants