Skip to content

feat: add structured errors module and home-path collapsing#6

Merged
amondnet merged 2 commits into
mainfrom
feat/structured-errors
Jun 25, 2026
Merged

feat: add structured errors module and home-path collapsing#6
amondnet merged 2 commits into
mainfrom
feat/structured-errors

Conversation

@amondnet

@amondnet amondnet commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Summary

Adopts two agent-DX features from axi-sdk-js into the toolkit (features 1 & 2 from the prior analysis):

  1. Structured errors module (./errors subpath)
    • CliErrorError subclass carrying a machine-readable code + suggestions[]
    • exitCodeForError(error) — maps VALIDATION_ERROR → exit 2, everything else → 1
    • errorOutput(message, code, suggestions?) / toErrorOutput(error) — produce a stable { error, code, help? } payload renderable as JSON/TOON
    • VALIDATION_ERROR / UNKNOWN_ERROR constants
  2. collapseHomeDirectory(path, homeDir?) (output module) — replaces a leading home prefix with ~, path-boundary aware (a sibling like /Users/alicebob is not collapsed against home /Users/alice), for portable/deterministic output.

All existing validators (validation/text.ts, validation/numeric.ts, output/json.ts format check) now throw CliError tagged VALIDATION_ERROR. Since CliError extends Error, existing try/catch and toThrow(message) assertions keep working unchanged.

Also pins packageManager to bun@1.3.14.

Why

Raises the toolkit's agent-DX: structured, machine-parseable errors with stable exit codes — directly aligned with the agent-dx-cli-scale rubric (structured JSON errors, safety rails) added in #5.

Files

  • New: src/errors/{error,output,index}.ts, src/output/path.ts + tests
  • Wired: validators throw CliError; package.json (./errors export, build steps, packageManager); src/index.ts / src/output/index.ts barrels; README.md

Test plan

  • bun test — 164 pass, 0 fail (32 new tests incl. sibling-prefix boundary regression + end-to-end catch→code→exit-code across all 7 validators)
  • bun run type-check — clean
  • bun run lint — clean
  • bun run build — emits dist/errors/ with .d.ts
  • /review:review-code — 6 aspects; only one Important (README example accuracy), fixed in this branch

Notes

  • Backward compatible: CliError is an Error; no message text changed.
  • Library uses @byjohann/toon; axi-sdk-js uses @toon-format/toon — kept the toolkit's existing dependency.

Summary by cubic

Adds structured, machine-readable errors and home-path collapsing to make CLI output agent-friendly and portable. Validators now throw CliError, enabling stable exit codes and consistent JSON/TOON error payloads; path collapsing is boundary-aware and cross-OS.

  • New Features
    • ./errors module: CliError (code + suggestions), exitCodeForError (validation → 2, else 1), errorOutput / toErrorOutput, and isCliError for robust detection across multiple package copies.
    • Validators (text, numeric, validateFormat) now throw CliError with VALIDATION_ERROR.
    • Added collapseHomeDirectory(path, homeDir?) to replace a leading home prefix with ~ (path-boundary aware, handles Windows \ and trailing slash in homeDir).

Written for commit 9c030e2. Summary will update on new commits.

Summary by CodeRabbit

  • New Features

    • Introduced structured CLI errors with machine-readable codes, suggestions, and an error payload format.
    • Added public “Errors” utilities (including error detection across module copies) and standardized exit-code mapping for validation failures.
    • Added collapseHomeDirectory for portable ~ formatting in output.
  • Bug Fixes

    • Updated validators and output format checks to throw standardized validation errors.
  • Documentation

    • Expanded README and API reference for the new errors/output utilities and payload shape.
  • Tests

    • Added unit and integration coverage for structured errors and home-directory collapsing.
  • Chores

    • Updated package export mappings and build outputs for the new errors entry point.

Adopt two agent-DX features from axi-sdk-js into the toolkit:

- errors module: CliError (machine-readable code + suggestions),
  exitCodeForError (VALIDATION_ERROR -> 2, else 1), and errorOutput/
  toErrorOutput for a stable { error, code, help } payload. Exposed via
  the new ./errors subpath export.
- output: collapseHomeDirectory(path, homeDir?) replaces a leading home
  prefix with ~ (path-boundary aware) for portable, deterministic output.

Wire all validators (text, numeric, output format) to throw CliError
tagged VALIDATION_ERROR. CliError extends Error, so existing try/catch
and message assertions keep working.

Also pin packageManager to bun@1.3.14.
@codecov

codecov Bot commented Jun 24, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@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 a new structured errors module (CliError, exitCodeForError, toErrorOutput) to provide machine-readable error codes and suggestions for CLI tools, and integrates it across existing validation and output modules. It also adds a collapseHomeDirectory utility to normalize absolute home paths to ~. The review feedback suggests improving the robustness of collapseHomeDirectory by handling Windows path separators and trailing slashes, and recommends using duck typing instead of instanceof CliError to prevent failures in multi-instance package environments.

Comment thread src/output/path.ts
Comment thread src/errors/error.ts
Comment thread src/errors/output.ts
@coderabbitai

coderabbitai Bot commented Jun 24, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 808407ef-ffa3-41a0-8d23-c0ca6020ba1f

📥 Commits

Reviewing files that changed from the base of the PR and between 5e4b7e5 and 9c030e2.

📒 Files selected for processing (9)
  • package.json
  • src/errors/error.ts
  • src/errors/index.ts
  • src/errors/output.ts
  • src/index.ts
  • src/output/path.ts
  • test/errors/error.test.ts
  • test/errors/output.test.ts
  • test/output/path.test.ts
🚧 Files skipped from review as they are similar to previous changes (7)
  • src/output/path.ts
  • test/errors/output.test.ts
  • src/index.ts
  • test/errors/error.test.ts
  • src/errors/index.ts
  • src/errors/output.ts
  • package.json

📝 Walkthrough

Walkthrough

Adds structured CLI error utilities, migrates validators to CliError, adds collapseHomeDirectory, updates module exports, and refreshes tests and README documentation.

Changes

Structured Error System and Output Helpers

Layer / File(s) Summary
CliError, codes, and exit mapping
src/errors/error.ts
Defines structured error codes, the CliError subclass with suggestions, the isCliError guard, and exitCodeForError.
ErrorOutput payload helpers
src/errors/output.ts
Defines ErrorOutput, builds structured payloads with optional help text, and converts thrown values into stable output.
Module exports and package subpaths
src/errors/index.ts, src/index.ts, src/output/index.ts, package.json
Adds the errors barrel, re-exports error and output helpers from the root entrypoint, exposes collapseHomeDirectory, reshapes package exports, and updates build/package metadata.
Validator throws and format checks
src/validation/text.ts, src/validation/numeric.ts, src/output/json.ts
Updates text, numeric, and JSON format validators to throw CliError with VALIDATION_ERROR and refreshes related @throws docs and imports.
collapseHomeDirectory helper
src/output/path.ts, src/output/index.ts
Adds collapseHomeDirectory with prefix normalization and boundary checks, and re-exports it from the output module.
Tests and README updates
test/errors/*, test/output/path.test.ts, README.md
Adds tests for the new error utilities, validator integration, and collapseHomeDirectory, and updates the README with structured error, output, and validation references.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐇 I hopped through errors, neat and bright,
With codes that land just right.
~ now blooms from home-bound trails,
And validator burrows tell their tales.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main changes: a structured errors module and home-path collapsing.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/structured-errors

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install failed. For unrecoverable errors, disable the tool in CodeRabbit configuration.


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

@cubic-dev-ai cubic-dev-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.

1 issue found across 15 files

Architecture diagram
sequenceDiagram
    participant Client as CLI Consumer
    participant App as App Code
    participant Validators as Validators Module
    participant Errors as Errors Module
    participant Output as Output Module
    
    Note over Client,Output: New: Structured Error Handling Flow
    
    Client->>App: Call command with invalid input
    App->>Validators: validateNonEmptyString() / validatePattern() / etc.
    
    alt Validation fails
        Validators->>Errors: NEW: throw new CliError(msg, VALIDATION_ERROR)
        Errors-->>Validators: CliError instance
        Validators-->>App: CliError thrown
        
        App->>Errors: catch(error)
        App->>Errors: toErrorOutput(error)
        Errors-->>App: { error, code, help? }
        
        alt Validation error (code === VALIDATION_ERROR)
            App->>Errors: exitCodeForError(error)
            Errors-->>App: 2
        else Other CliError or non-CliError
            App->>Errors: exitCodeForError(error)
            Errors-->>App: 1
        end
        
        App->>Output: outputData(errorOutput, format)
        Output-->>App: Rendered JSON/TOON error payload
        App-->>Client: Stable error output + exit code
    else Validation passes
        Validators-->>App: Valid result
        App-->>Client: Success
    end
    
    Note over Client,Output: New: Home Path Collapsing
    
    Client->>App: Request output with absolute path
    App->>Output: collapseHomeDirectory(path, homeDir?)
    
    alt Path starts with home directory
        Output->>Output: NEW: Check path boundary after prefix
        alt Path boundary after home (separator or exact match)
            Output-->>App: ~/rest/of/path
        else Sibling dir matching basename (e.g. /Users/alicebob vs /Users/alice)
            Output-->>App: Original path unchanged
        end
    else Path outside home directory
        Output-->>App: Original path unchanged
    end
    
    App->>Output: outputData(result, format)
    Output-->>App: Rendered output with collapsed paths
    App-->>Client: Portable/deterministic output
    
    Note over Client,Output: Runtime behavior with existing code still works
    Note over Validators: CliError extends Error – existing try/catch unbroken
Loading

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread src/output/path.ts Outdated

@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

🧹 Nitpick comments (1)
src/output/path.ts (1)

26-38: 🎯 Functional Correctness | 🔵 Trivial

Normalize the home separator before comparing

rest.startsWith('/') only handles POSIX separators, and a trailing slash in homeDir prevents subpaths from collapsing. If this helper should cover Windows too, normalize homeDir and compare against path.sep instead of hardcoding /.

🤖 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 `@src/output/path.ts` around lines 26 - 38, Update collapseHomeDirectory in
path.ts so it does not hardcode POSIX separators when checking the boundary
after homeDir. Normalize the homeDir prefix before comparing, and use the
platform separator (path.sep) when deciding whether the remaining path is a
valid subpath; make sure trailing slashes in homeDir still allow child paths to
collapse correctly while preserving the sibling-directory guard.
🤖 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 `@package.json`:
- Around line 35-36: The build setup is creating separate bundled copies of
CliError via each subpath entrypoint, so the same error class can have different
identities across `@pleaseai/cli-toolkit/errors`, validation, and output. Update
the package exports and build layout so all subpaths share the same CliError
implementation from the errors module, and add explicit types conditions to each
exports entry so TypeScript resolves the correct declarations for subpath
imports. Focus on the package.json exports map and the shared error entrypoints
to avoid multiple bundled definitions.

In `@test/output/path.test.ts`:
- Around line 25-29: The test for collapseHomeDirectory is using
process.env.HOME to derive the expected value, but the function’s default
behavior is based on os.homedir(). Update the assertion in the
collapseHomeDirectory test to use os.homedir() when building the expected path
so the test matches the actual default-home source and stays stable across
environments.

---

Nitpick comments:
In `@src/output/path.ts`:
- Around line 26-38: Update collapseHomeDirectory in path.ts so it does not
hardcode POSIX separators when checking the boundary after homeDir. Normalize
the homeDir prefix before comparing, and use the platform separator (path.sep)
when deciding whether the remaining path is a valid subpath; make sure trailing
slashes in homeDir still allow child paths to collapse correctly while
preserving the sibling-directory guard.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: b2fe6e1a-5fbc-4c93-88f7-b3e1b101e1e5

📥 Commits

Reviewing files that changed from the base of the PR and between b4d40f2 and 5e4b7e5.

📒 Files selected for processing (15)
  • README.md
  • package.json
  • src/errors/error.ts
  • src/errors/index.ts
  • src/errors/output.ts
  • src/index.ts
  • src/output/index.ts
  • src/output/json.ts
  • src/output/path.ts
  • src/validation/numeric.ts
  • src/validation/text.ts
  • test/errors/error.test.ts
  • test/errors/output.test.ts
  • test/errors/validation-integration.test.ts
  • test/output/path.test.ts

Comment thread package.json
Comment thread test/output/path.test.ts
- collapseHomeDirectory: handle Windows \ separator and trailing slash in homeDir
- add isCliError duck-typing guard so error detection survives multiple
  package copies/versions; use it in exitCodeForError and toErrorOutput
- package.json: add explicit types conditions to subpath exports
- tests: derive default-home expectation from os.homedir(); cover Windows
  paths and cross-copy CliError detection
@greptile-apps

greptile-apps Bot commented Jun 25, 2026

Copy link
Copy Markdown

Greptile Summary

Introduces a ./errors subpath module (CliError, isCliError, exitCodeForError, errorOutput, toErrorOutput) and a collapseHomeDirectory helper in the output module. All existing validators are updated to throw CliError(VALIDATION_ERROR) instead of plain Error, enabling stable exit codes and structured JSON/TOON error payloads for agent-facing CLIs.

  • src/errors/: New CliError class with code + suggestions, a duck-typed isCliError guard to survive cross-bundle duplicate class references, and exitCodeForError mapping validation → exit 2, everything else → 1.
  • src/output/path.ts: New collapseHomeDirectory that replaces a leading home prefix with ~, guarded against sibling-basename false matches and trailing-separator edge cases.
  • src/output/json.ts / validators: Updated to throw CliError(VALIDATION_ERROR); backward compatible since CliError extends Error.

Confidence Score: 4/5

Safe to merge; changes are backward compatible and well-tested, with two minor design rough edges that don't affect current test cases.

The implementation is clean and all 164 tests pass. Two issues are worth tracking: UNKNOWN_ERROR's string value is 'UNKNOWN' (not 'UNKNOWN_ERROR'), creating an asymmetry with VALIDATION_ERROR that could trip up consumers who pattern-match on string literals. Additionally, the ./output bundle now contains its own bundled copy of CliError (because json.ts imports from ../errors/error.ts), so direct instanceof CliError checks across the two subpath imports silently return false — users need to reach for isCliError instead, and that guidance is not yet surfaced where the error is thrown.

src/errors/error.ts (UNKNOWN_ERROR value asymmetry) and src/output/json.ts (cross-bundle CliError copy, stale JSDoc inline comment).

Important Files Changed

Filename Overview
src/errors/error.ts New file: defines CliError, isCliError duck-type guard, exitCodeForError, and VALIDATION_ERROR/UNKNOWN_ERROR constants. Well-structured, but UNKNOWN_ERROR value ('UNKNOWN') is asymmetric with its export name, unlike VALIDATION_ERROR ('VALIDATION_ERROR').
src/errors/output.ts New file: errorOutput and toErrorOutput helpers that produce a stable { error, code, help? } payload. Logic is clean; null-coalescing on suggestions handles duck-typed foreign CliError instances gracefully.
src/output/json.ts Updated: validateFormat now throws CliError(VALIDATION_ERROR). The cross-bundle import of CliError from ../errors/error.ts causes a separate class copy in the ./output bundle, breaking direct instanceof checks across subpath imports. Also has a stale JSDoc inline comment ('throws Error').
src/output/path.ts New file: collapseHomeDirectory correctly handles path-boundary awareness (sibling prefix protection), trailing separator normalization, and both POSIX/Windows separators.
src/validation/numeric.ts Updated: all three numeric validators now throw CliError(VALIDATION_ERROR) instead of plain Error. Backward compatible; message text unchanged.
src/validation/text.ts Updated: all text validators now throw CliError(VALIDATION_ERROR). Backward compatible; existing try/catch and toThrow() assertions unaffected.
package.json Added ./errors subpath export with types/default fields, pinned packageManager to bun@1.3.14, and wired errors bundle into build/build:prod scripts. Exports upgraded from bare string to objects with types+default.
test/errors/error.test.ts Comprehensive tests for CliError, isCliError, and exitCodeForError including a ForeignCliError class that reproduces the multi-bundle scenario. Good coverage.
test/errors/validation-integration.test.ts End-to-end integration test confirming all 7 validators throw CliError(VALIDATION_ERROR) mapped to exit code 2. Good regression coverage for the wiring.
test/output/path.test.ts Tests cover POSIX, Windows, trailing-separator, sibling-prefix, empty homeDir, and os.homedir() default cases. Boundary regression test for /Users/alicebob vs /Users/alice is present.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant CLI as CLI Command
    participant V as Validator (text/numeric/output)
    participant CE as CliError
    participant EO as errors/output
    participant P as process

    CLI->>V: validateNonEmptyString('') / validateFormat('xml') / etc.
    V-->>CE: throw new CliError(message, VALIDATION_ERROR)
    CE-->>CLI: caught in catch(error)
    CLI->>EO: toErrorOutput(error)
    EO->>EO: isCliError(error) → true (duck-type or instanceof)
    EO-->>CLI: "{ error, code, help? }"
    CLI->>P: "process.exitCode = exitCodeForError(error) → 2"
    CLI->>CLI: outputData(payload, 'toon')
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant CLI as CLI Command
    participant V as Validator (text/numeric/output)
    participant CE as CliError
    participant EO as errors/output
    participant P as process

    CLI->>V: validateNonEmptyString('') / validateFormat('xml') / etc.
    V-->>CE: throw new CliError(message, VALIDATION_ERROR)
    CE-->>CLI: caught in catch(error)
    CLI->>EO: toErrorOutput(error)
    EO->>EO: isCliError(error) → true (duck-type or instanceof)
    EO-->>CLI: "{ error, code, help? }"
    CLI->>P: "process.exitCode = exitCodeForError(error) → 2"
    CLI->>CLI: outputData(payload, 'toon')
Loading

Comments Outside Diff (1)

  1. src/output/json.ts, line 71 (link)

    P2 The inline example comment still says // throws Error after the validator was updated to throw CliError. Updating it avoids confusion when readers compare the @throws tag above with the example.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: src/output/json.ts
    Line: 71
    
    Comment:
    The inline example comment still says `// throws Error` after the validator was updated to throw `CliError`. Updating it avoids confusion when readers compare the `@throws` tag above with the example.
    
    
    
    How can I resolve this? If you propose a fix, please make it concise.

    Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

    Fix in Claude Code

Fix All in Claude Code

Prompt To Fix All With AI
Fix the following 3 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 3
src/errors/error.ts:14
**`UNKNOWN_ERROR` value doesn't match its export name**

`VALIDATION_ERROR = 'VALIDATION_ERROR'` (name == value), but `UNKNOWN_ERROR = 'UNKNOWN'` (name has an `_ERROR` suffix the value lacks). A consumer who writes `error.code === 'UNKNOWN_ERROR'` — a natural guess from the export name — will never match because the actual runtime value is `'UNKNOWN'`. Using the constant itself is always safe, but the asymmetry is a silent footgun when consumers switch to string literals (e.g. in switch cases or JSON comparisons). Aligning the value to `'UNKNOWN_ERROR'` would make the two constants consistent.

### Issue 2 of 3
src/output/json.ts:2-4
**Cross-bundle `instanceof CliError` silently returns `false`**

Because `src/output/json.ts` imports `CliError` from `../errors/error.ts`, Bun will bundle a separate copy of `CliError` into `dist/output/index.js` when building the `./output` subpath. A consumer who imports from both subpaths simultaneously will hold two distinct class references, so `error instanceof CliError` (from `./errors`) silently returns `false` for errors thrown by `validateFormat` (from `./output`). The `isCliError` type guard is the correct fix — but `instanceof` is the natural TypeScript pattern, and the README's error-handling example doesn't warn against it. Consider adding a note in the JSDoc of `validateFormat` (or the module-level README section) that direct `instanceof CliError` checks across subpath boundaries require `isCliError` instead.

### Issue 3 of 3
src/output/json.ts:71
The inline example comment still says `// throws Error` after the validator was updated to throw `CliError`. Updating it avoids confusion when readers compare the `@throws` tag above with the example.

```suggestion
 * validateFormat('xml')   // throws CliError
```

Reviews (1): Last reviewed commit: "chore: apply AI code review suggestions" | Re-trigger Greptile

Comment thread src/errors/error.ts
*
* Maps to process exit code `1` via {@link exitCodeForError}.
*/
export const UNKNOWN_ERROR = 'UNKNOWN'

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 UNKNOWN_ERROR value doesn't match its export name

VALIDATION_ERROR = 'VALIDATION_ERROR' (name == value), but UNKNOWN_ERROR = 'UNKNOWN' (name has an _ERROR suffix the value lacks). A consumer who writes error.code === 'UNKNOWN_ERROR' — a natural guess from the export name — will never match because the actual runtime value is 'UNKNOWN'. Using the constant itself is always safe, but the asymmetry is a silent footgun when consumers switch to string literals (e.g. in switch cases or JSON comparisons). Aligning the value to 'UNKNOWN_ERROR' would make the two constants consistent.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/errors/error.ts
Line: 14

Comment:
**`UNKNOWN_ERROR` value doesn't match its export name**

`VALIDATION_ERROR = 'VALIDATION_ERROR'` (name == value), but `UNKNOWN_ERROR = 'UNKNOWN'` (name has an `_ERROR` suffix the value lacks). A consumer who writes `error.code === 'UNKNOWN_ERROR'` — a natural guess from the export name — will never match because the actual runtime value is `'UNKNOWN'`. Using the constant itself is always safe, but the asymmetry is a silent footgun when consumers switch to string literals (e.g. in switch cases or JSON comparisons). Aligning the value to `'UNKNOWN_ERROR'` would make the two constants consistent.

How can I resolve this? If you propose a fix, please make it concise.

Fix in Claude Code

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good catch — aligned the value to 'UNKNOWN_ERROR' to match the export name and stay consistent with VALIDATION_ERROR. Since this changes the code field for fallback/unknown errors (a breaking change to the output contract), it's handled in a follow-up PR rather than amending the merged one: #8. Acceptable pre-1.0; consumers using the UNKNOWN_ERROR constant are unaffected.

Comment thread src/output/json.ts
Comment on lines +2 to 4
import { CliError, VALIDATION_ERROR } from '../errors/error.ts'
import { filterFields } from './filter.ts'
import { outputToon } from './toon.ts'

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 Cross-bundle instanceof CliError silently returns false

Because src/output/json.ts imports CliError from ../errors/error.ts, Bun will bundle a separate copy of CliError into dist/output/index.js when building the ./output subpath. A consumer who imports from both subpaths simultaneously will hold two distinct class references, so error instanceof CliError (from ./errors) silently returns false for errors thrown by validateFormat (from ./output). The isCliError type guard is the correct fix — but instanceof is the natural TypeScript pattern, and the README's error-handling example doesn't warn against it. Consider adding a note in the JSDoc of validateFormat (or the module-level README section) that direct instanceof CliError checks across subpath boundaries require isCliError instead.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/output/json.ts
Line: 2-4

Comment:
**Cross-bundle `instanceof CliError` silently returns `false`**

Because `src/output/json.ts` imports `CliError` from `../errors/error.ts`, Bun will bundle a separate copy of `CliError` into `dist/output/index.js` when building the `./output` subpath. A consumer who imports from both subpaths simultaneously will hold two distinct class references, so `error instanceof CliError` (from `./errors`) silently returns `false` for errors thrown by `validateFormat` (from `./output`). The `isCliError` type guard is the correct fix — but `instanceof` is the natural TypeScript pattern, and the README's error-handling example doesn't warn against it. Consider adding a note in the JSDoc of `validateFormat` (or the module-level README section) that direct `instanceof CliError` checks across subpath boundaries require `isCliError` instead.

How can I resolve this? If you propose a fix, please make it concise.

Fix in Claude Code

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Agreed — the isCliError guard already exists, the gap was documentation. Added a note in the README error-handling section warning against bare instanceof CliError across subpaths (each bundles its own copy) and pointing to isCliError, plus added it to the API reference. Follow-up PR: #8.

@amondnet
amondnet merged commit fd0a64f into main Jun 25, 2026
7 checks passed
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