resultar 3.6.0 → 3.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,249 @@
1
+ # Resultar v3.7+ API Guide
2
+
3
+ Use this reference after checking the version installed by the consuming project. The local package
4
+ types and source are authoritative when they differ from this guide.
5
+
6
+ ## Contents
7
+
8
+ - [Package Baseline](#package-baseline)
9
+ - [Core Types And Constructors](#core-types-and-constructors)
10
+ - [Composition And Recovery](#composition-and-recovery)
11
+ - [Tagged Errors](#tagged-errors)
12
+ - [Boundary Matching](#boundary-matching)
13
+ - [Async Boundaries](#async-boundaries)
14
+ - [Async Policies](#async-policies)
15
+ - [Collections And Control Flow](#collections-and-control-flow)
16
+ - [`safeTry`](#safetry)
17
+ - [ResultTask (Lazy Workflows)](#resulttask-lazy-workflows)
18
+ - [Observation And Cleanup](#observation-and-cleanup)
19
+
20
+ ## Package Baseline
21
+
22
+ Resultar v3.7 is an ESM package targeting Node.js 24+. The repository currently uses TypeScript 7.
23
+ Do not infer consumer requirements from memory: inspect `package.json`, the lockfile, and exported
24
+ types first.
25
+
26
+ Prefer named imports and type-only imports:
27
+
28
+ ```ts
29
+ import { ResultAsync, createTaggedError, ok, safeTry, tryResult, tryResultAsync } from "resultar";
30
+ import type { StrictResult, StrictResultAsync } from "resultar";
31
+ ```
32
+
33
+ `tryCatch` and `tryCatchAsync` remain compatibility aliases. Prefer `tryResult` and
34
+ `tryResultAsync` in new v3.7+ code.
35
+
36
+ ## Core Types And Constructors
37
+
38
+ | Need | API |
39
+ | --------------------------------------------------- | ---------------------------------------- |
40
+ | Synchronous fallible value | `Result<T, E>` |
41
+ | Asynchronous fallible value | `ResultAsync<T, E>` |
42
+ | Error-only sync channel | `StrictResult<T, E extends Error>` |
43
+ | Error-only async channel | `StrictResultAsync<T, E extends Error>` |
44
+ | Successful value | `ok(value)` / `okAsync(value)` |
45
+ | Successful `undefined` | `unit()` / `unitAsync()` |
46
+ | Expected failure | `err(error)` / `errAsync(error)` |
47
+ | Catch synchronous throws now | `tryResult(fn, toError)` |
48
+ | Wrap a synchronous throwing function | `fromThrowable(fn, toError)` |
49
+ | Catch promise rejection or a throwing async factory | `tryResultAsync(input, toError)` |
50
+ | Wrap an async throwing function | `fromThrowableAsync(fn, toError)` |
51
+ | Deliberately leave Resultar at a terminal boundary | `runSync(result)` / `runPromise(result)` |
52
+
53
+ Use the named options form when it improves readability:
54
+
55
+ ```ts
56
+ const parsed = tryResult({
57
+ try: () => JSON.parse(input) as unknown,
58
+ catch: (cause) => new ParsePayloadError({ cause }),
59
+ });
60
+ ```
61
+
62
+ `runSync` returns the `Ok` value or throws the `Err`; `runPromise` resolves the `Ok` value or
63
+ rejects with the `Err`. Keep both at framework, bootstrap, CLI, or test boundaries.
64
+
65
+ ## Composition And Recovery
66
+
67
+ - `map` transforms an `Ok` value with an infallible function.
68
+ - `mapErr` transforms an `Err` value.
69
+ - `filterOrElse` validates or narrows an `Ok` value and introduces a typed error on failure.
70
+ - `andThen` chains fallible work. On `ResultAsync`, it accepts sync or async Resultar work.
71
+ - `asyncAndThen` bridges a synchronous `Result` into `ResultAsync`.
72
+ - `orElse` recovers the complete error channel.
73
+ - `catchTag` recovers one tagged error.
74
+ - `catchTags` recovers selected tagged errors; unhandled variants stay in the error union.
75
+ - `pipe` applies named combinators without changing Result semantics.
76
+
77
+ Let each step expose its narrow error type and let composition infer the union:
78
+
79
+ ```ts
80
+ type CreateUserError = InvalidEmailError | UserExistsError | SaveUserError;
81
+
82
+ const createUser = (input: CreateUserInput): StrictResultAsync<User, CreateUserError> =>
83
+ validateEmail(input.email)
84
+ .asyncAndThen(ensureEmailAvailable)
85
+ .andThen((email) => saveUser({ ...input, email }));
86
+ ```
87
+
88
+ Do not manually cast the final error union. A surprising inferred union is usually evidence that a
89
+ step has a broad or inaccurate signature.
90
+
91
+ ## Tagged Errors
92
+
93
+ `createTaggedError` creates nominal `Error` subclasses with stable `_tag`, message-template props,
94
+ `cause`, `fingerprint`, `toJSON()`, and `.err()` support.
95
+
96
+ ```ts
97
+ class UserNotFoundError extends createTaggedError({
98
+ name: "UserNotFoundError",
99
+ message: "User $userId was not found in $source",
100
+ }) {}
101
+
102
+ const missing = UserNotFoundError.err({ userId, source: "database" });
103
+ ```
104
+
105
+ - Let `$variables` in the message template define required constructor props.
106
+ - Preserve the original failure with `cause` when adapting external code.
107
+ - Keep the configured `name` equal to the class name.
108
+ - Do not override the generated constructor.
109
+ - Treat `ErrorClass.is(value)` as a nominal `instanceof`-based guard; a matching `_tag` is not
110
+ sufficient.
111
+ - Avoid reserved template fields such as `_tag`, `name`, `message`, `messageTemplate`,
112
+ `fingerprint`, `stack`, and `cause`.
113
+ - Use `TaggedEnum`/`taggedEnum` for lightweight tagged data that should not be an `Error`.
114
+ - Use `redact`, `isRedacted`, and `revealRedacted` when sensitive error metadata must not serialize
115
+ or log by default.
116
+ - Use `findCause` or an instance's `findCause` to inspect a typed cause chain.
117
+
118
+ ## Boundary Matching
119
+
120
+ Use branch methods inside an algorithm and matching when converting the completed result:
121
+
122
+ - `isOk()` / `isErr()` narrow for direct control flow.
123
+ - `match(onOk, onErr)` handles a simple boundary.
124
+ - `matchTags(onOk, handlers)` exhaustively maps tagged errors.
125
+ - `matchTagsPartial(onOk, handlers, fallback)` handles selected tags with a deliberate fallback.
126
+ - `matchError(error, handlers)` handles an error value directly.
127
+ - `matchErrorPartial(error, handlers, fallback)` partially handles an error value.
128
+
129
+ Include an `Error` handler whenever an error union can contain an untagged `Error`. Do not invent
130
+ `partialCatchTags`; `catchTags` already supports partial recovery.
131
+
132
+ ## Async Boundaries
133
+
134
+ Choose the wrapper based on ownership of the promise:
135
+
136
+ - `fromPromise(existingPromise, toError)` wraps a promise that has already been created.
137
+ - `tryResultAsync(() => createPromise(), toError)` also catches synchronous factory throws and is
138
+ preferred when the agent controls creation.
139
+ - `fromSafePromise(promise)` is only for promises guaranteed not to reject by contract.
140
+ - `fromCallback(options)` adapts callback or subscription APIs and returns
141
+ `ResultAsync<T, E | AbortError>`. Supply cleanup and forward cancellation where supported.
142
+ - `AbortError` and `isAbortError` identify Resultar cancellation.
143
+
144
+ Always map `unknown` causes into a specific error near the external boundary:
145
+
146
+ ```ts
147
+ const loadUser = (id: string): StrictResultAsync<User, LoadUserError> =>
148
+ tryResultAsync(
149
+ () => client.loadUser(id),
150
+ (cause) => new LoadUserError({ cause, id }),
151
+ );
152
+ ```
153
+
154
+ ## Async Policies
155
+
156
+ Use the built-in lazy policies instead of hand-written `Promise.race`, timers, or retry loops:
157
+
158
+ - `ResultAsync.retry(task, options)` retries typed failures and includes `AbortError` in the error
159
+ channel. Use `times`, `delayMs`, `jittered`, `while`, and the provided signal deliberately.
160
+ - `ResultAsync.retryOrElse(task, options)` applies a typed fallback after retry exhaustion. The
161
+ exhausted task error is replaced by the fallback error channel.
162
+ - `ResultAsync.timeout(task, options)` returns the task error or the typed `onTimeout` error.
163
+ - `ResultAsync.race(left, right)` and `raceAll(tasks)` return the first settled result.
164
+ - `ResultAsync.raceFirst(left, right)` returns the first `Ok`; if both fail, it returns the last
165
+ observed `Err`.
166
+ - `ResultAsync.raceWith(left, right, handlers)` exposes the winner and a handle to the still-running
167
+ task for custom cooperative policy.
168
+ - `ResultAsync.withResource({ acquire, use, release })` releases after every acquire-success path.
169
+ Release errors are best-effort and do not enter the result channel; model cleanup in `use` when it
170
+ must affect control flow.
171
+
172
+ Race, timeout, retry, callback, and resource helpers rely on cooperative cancellation. Pass their
173
+ `AbortSignal` into fetch, database, SDK, timer, or subscription work; aborting a wrapper cannot stop
174
+ an underlying operation that ignores the signal.
175
+
176
+ ## Collections And Control Flow
177
+
178
+ Both `Result` and `ResultAsync` provide collection helpers:
179
+
180
+ - `combine` preserves array/tuple/record shape and returns the first error.
181
+ - `combineWithAllErrors` collects every error.
182
+ - `validateAll` collects all validation errors; async mapped use supports bounded `concurrency`.
183
+ - `zip` combines exactly two values.
184
+ - `firstSuccessOf` tries candidates until one succeeds and otherwise returns the final error.
185
+ - `forEach` stops at the first error; use `{ discard: true }` when successful values are irrelevant.
186
+ - `when`, `unless`, `loop`, and `iterate` model Resultar-native control flow where they improve
187
+ clarity.
188
+
189
+ Prefer `ResultAsync.forEach(items, mapper, { concurrency })` for bounded first-error processing and
190
+ `ResultAsync.validateAll(items, mapper, { concurrency })` for independent validation where every
191
+ error matters.
192
+
193
+ ## `safeTry`
194
+
195
+ Use `safeTry` when a linear generator is clearer than a chain:
196
+
197
+ ```ts
198
+ const workflow = safeTry(async function* () {
199
+ const input = yield* validateInput(raw);
200
+ const remote = yield* loadRemote(input);
201
+ const saved = yield* saveRemote(remote);
202
+
203
+ return ok(saved);
204
+ });
205
+ ```
206
+
207
+ Use `yield*` for `Result` and `ResultAsync`. Wrap raw promises before yielding. Avoid raw `await`,
208
+ `try/catch`, and legacy `safeUnwrap()` inside the generator.
209
+
210
+ ## ResultTask (Lazy Workflows)
211
+
212
+ `ResultTask<A, E, R>` is a lazy, reusable description of work: building one starts nothing, and
213
+ each `run*` call executes it again. Never substitute `ResultAsync` (already-started) where
214
+ laziness matters.
215
+
216
+ - Build: `succeed`, `fail`, `fromResult`, `sync`, `try`, `tryPromise` (`try`/`tryPromise` map
217
+ external throws into `E`; `sync` treats them as defects).
218
+ - Compose lazily: `map`, `flatMap`, `catchAll`, `tap`, `gen` with `yield*` (also accepts
219
+ `yield* result`: `Ok` unwraps, `Err` short-circuits into `E`).
220
+ - Provide services: `yield* Tag`, then `provideService` / `provideServices` /
221
+ `provideServiceResolver` (data-first and curried); missing or incompatible providers are
222
+ compile-time errors, never runtime surprises.
223
+ - Resources: `acquireRelease` + `scoped`; LIFO awaited finalizers run on every exit; deferred
224
+ release errors live in `R` until `scoped` folds them into `E`; `catchAll` recovers a release
225
+ failure only after `scoped`.
226
+ - Boundaries: `runExit` preserves `Success`/`Fail`/`Die`/`Interrupt`/`Sequential`; `runResult`
227
+ returns one `Err` and rejects the rest (`AbortError` for interrupts, `ResultTaskCauseError`
228
+ for composites); `runPromise` unwraps or rejects. Cancellation is cooperative: no fibers,
229
+ `race`, `timeout`, or scheduler exist on `ResultTask` yet — use the `ResultAsync` helpers.
230
+
231
+ `Result.gen` is the canonical name for linear Result workflows; `safeTry` remains as its alias.
232
+ For lazy `ResultTask` workflows with typed services and scopes, see `packages/resultar/README.md`
233
+ (Lazy Workflows With ResultTask) and `docs/rfcs/rfc-0001-result-task-core.md`.
234
+
235
+ ## Observation And Cleanup
236
+
237
+ On `Result` and `ResultAsync`, `tap`, `tapError`, and `log` preserve the original result. Callback throws and rejected callback
238
+ promises are intentionally ignored, so use them only for best-effort logging, metrics, tracing, and
239
+ observation. Use `andThen`, `orElse`, or a Resultar boundary when side-effect failure must alter the
240
+ workflow.
241
+
242
+ `toDisposable` and `toAsyncDisposable` integrate with Node.js `using` / `await using`; their
243
+ cleanup callbacks are also best-effort. Prefer `withResource` for acquire/use/release workflows.
244
+
245
+ On `ResultTask`, `tap` and `tapError` are lazy effectful composition. Returned task errors and
246
+ requirements join the workflow type. A thrown callback or rejected Promise becomes `Die`;
247
+ a returned `ResultTask.fail(error)` becomes a typed failure. Use an explicitly recovered task
248
+ when observation must be best-effort. `runExit` preserves these exits; `runResult` can reject.
249
+ Do not substitute `ResultAsync.withResource` for awaited ResultTask resource ownership.
@@ -0,0 +1,264 @@
1
+ # Resultar Requests And Validation Tooling
2
+
3
+ Use this reference for HTTP JSON integrations and Resultar-specific diagnostics. Verify package
4
+ versions and local README files before copying an example.
5
+
6
+ ## Contents
7
+
8
+ - [Choose A Request Package](#choose-a-request-package)
9
+ - [Use The Schema-Agnostic Core](#use-the-schema-agnostic-core)
10
+ - [Use The TypeBox Adapter](#use-the-typebox-adapter)
11
+ - [Use The Zod Adapter](#use-the-zod-adapter)
12
+ - [Map Request Errors](#map-request-errors)
13
+ - [Apply Retry Correctly](#apply-retry-correctly)
14
+ - [Validate In The Preferred Order](#validate-in-the-preferred-order)
15
+ - [Configure The Native Checker](#configure-the-native-checker)
16
+ - [Act On Diagnostics](#act-on-diagnostics)
17
+ - [Discover Repository Commands](#discover-repository-commands)
18
+
19
+ ## Choose A Request Package
20
+
21
+ | Contract style | Package | Success type |
22
+ | -------------------------------- | -------------------------- | ---------------------------- |
23
+ | Custom type guard or decoder | `resultar-request` | Type guard or decoder output |
24
+ | TypeBox schema | `resultar-request-typebox` | `Static<typeof schema>` |
25
+ | Zod schema, including transforms | `resultar-request-zod` | `z.output<typeof schema>` |
26
+
27
+ All three share the same transport implementation. The adapters add schema validation and re-export
28
+ the public `resultar-request` types. Install only the core and schema adapter the service needs.
29
+
30
+ Keep request payloads concrete. Do not widen a known response contract to `JsonRecord`,
31
+ `Record<string, unknown>`, or `unknown` after validation.
32
+
33
+ ## Use The Schema-Agnostic Core
34
+
35
+ Use a type guard when validation does not transform the payload:
36
+
37
+ ```ts
38
+ import { requestJson } from "resultar-request";
39
+
40
+ type User = { readonly email: string; readonly id: string };
41
+
42
+ const isUser = (value: unknown): value is User =>
43
+ typeof value === "object" &&
44
+ value !== null &&
45
+ "email" in value &&
46
+ typeof value.email === "string" &&
47
+ "id" in value &&
48
+ typeof value.id === "string";
49
+
50
+ const user = requestJson({
51
+ request: () => fetch(`https://example.com/users/${userId}`),
52
+ validator: isUser,
53
+ });
54
+ ```
55
+
56
+ Use `decode` when validation also normalizes or transforms the response. The decoder must make
57
+ success and validation failure explicit; check the installed package types for the exact shape.
58
+
59
+ ## Use The TypeBox Adapter
60
+
61
+ ```ts
62
+ import { createTaggedError } from "resultar";
63
+ import {
64
+ requestJson as requestJsonTypeBox,
65
+ type RequestJsonMapError,
66
+ } from "resultar-request-typebox";
67
+ import { Type, type Static } from "typebox";
68
+
69
+ const userSchema = Type.Object(
70
+ {
71
+ email: Type.String(),
72
+ id: Type.String(),
73
+ plan: Type.Union([Type.Literal("free"), Type.Literal("team")]),
74
+ },
75
+ { additionalProperties: false },
76
+ );
77
+
78
+ type User = Static<typeof userSchema>;
79
+
80
+ class UserApiError extends createTaggedError({
81
+ name: "UserApiError",
82
+ message: "User API $reason: $detail",
83
+ }) {}
84
+
85
+ const mapUserError = {
86
+ 404: ({ statusCode }) => new UserApiError({ detail: `HTTP ${statusCode}`, reason: "not-found" }),
87
+ http: ({ statusCode }) => new UserApiError({ detail: `HTTP ${statusCode}`, reason: "http" }),
88
+ invalidJson: ({ cause }) =>
89
+ new UserApiError({ cause, detail: cause.message, reason: "invalid-json" }),
90
+ request: ({ cause }) => new UserApiError({ cause, detail: cause.message, reason: "request" }),
91
+ validation: ({ errors }) =>
92
+ new UserApiError({ detail: `${errors.length} validation issue(s)`, reason: "validation" }),
93
+ } satisfies RequestJsonMapError;
94
+
95
+ const user = requestJsonTypeBox({
96
+ request: () => fetch(`https://example.com/users/${userId}`),
97
+ schema: userSchema,
98
+ mapError: mapUserError,
99
+ retry: { times: 2, delayMs: ({ nextAttempt }) => nextAttempt * 100, jittered: 0.2 },
100
+ });
101
+ ```
102
+
103
+ The success channel is `User`, derived from `Static<typeof userSchema>`. Keep the named adapter
104
+ alias when a file uses more than one request helper or when the schema library should be obvious at
105
+ the call site.
106
+
107
+ ## Use The Zod Adapter
108
+
109
+ ```ts
110
+ import { createTaggedError } from "resultar";
111
+ import { requestJson as requestJsonZod, type RequestJsonMapError } from "resultar-request-zod";
112
+ import { z } from "zod";
113
+
114
+ const userSchema = z
115
+ .object({ email: z.string().email(), id: z.string(), seats: z.number().int().nonnegative() })
116
+ .transform((user) => ({
117
+ ...user,
118
+ email: user.email.toLowerCase(),
119
+ seatLabel: `${user.seats} seats`,
120
+ }));
121
+
122
+ type User = z.output<typeof userSchema>;
123
+
124
+ class UserApiError extends createTaggedError({
125
+ name: "UserApiError",
126
+ message: "User API $reason: $detail",
127
+ }) {}
128
+
129
+ const mapUserError = {
130
+ 404: ({ statusCode }) => new UserApiError({ detail: `HTTP ${statusCode}`, reason: "not-found" }),
131
+ http: ({ statusCode }) => new UserApiError({ detail: `HTTP ${statusCode}`, reason: "http" }),
132
+ invalidJson: ({ cause }) =>
133
+ new UserApiError({ cause, detail: cause.message, reason: "invalid-json" }),
134
+ request: ({ cause }) => new UserApiError({ cause, detail: cause.message, reason: "request" }),
135
+ validation: ({ errors }) =>
136
+ new UserApiError({ detail: `${errors.length} validation issue(s)`, reason: "validation" }),
137
+ } satisfies RequestJsonMapError;
138
+
139
+ const user = requestJsonZod({
140
+ request: () => fetch(`https://example.com/users/${userId}`),
141
+ schema: userSchema,
142
+ mapError: mapUserError,
143
+ retry: { times: 2, delayMs: ({ nextAttempt }) => nextAttempt * 100, jittered: 0.2 },
144
+ });
145
+ ```
146
+
147
+ Use `z.output`, not `z.input` or an independently maintained interface, because Zod transforms can
148
+ change the success type.
149
+
150
+ ## Map Request Errors
151
+
152
+ Without `mapError`, request helpers return the package's `RequestError` union. Use that union for
153
+ infrastructure-local code. At an application boundary, use `mapError` to convert these categories
154
+ to concrete tagged errors:
155
+
156
+ - request creation, network, or fetch failure;
157
+ - status-specific HTTP failure, with numeric handlers taking precedence over `http`;
158
+ - invalid JSON;
159
+ - response validation or decode failure.
160
+
161
+ Preserve `cause` for request and JSON failures and preserve status, URL, validation details, and
162
+ other safe context needed for diagnosis. Do not map every category to a message-only `Error`.
163
+
164
+ ## Apply Retry Correctly
165
+
166
+ ```ts
167
+ const user = requestJsonZod({
168
+ request: () => fetch(`https://example.com/users/${userId}`),
169
+ schema: userSchema,
170
+ retry: { times: 2, delayMs: ({ nextAttempt }) => nextAttempt * 100, jittered: 0.2 },
171
+ });
172
+ ```
173
+
174
+ The default policy retries request failures and HTTP `5xx`, but not `4xx`, invalid JSON, or
175
+ validation failures. Retry requires a request factory; an already-created promise cannot be
176
+ restarted. Ensure each attempt receives a fresh request. The current request retry configuration
177
+ does not expose ResultAsync's internal retry signal, so do not claim that `requestJson`
178
+ automatically forwards policy cancellation. Supply an external fetch signal through the request
179
+ closure when the caller owns separate cancellation.
180
+
181
+ ## Validate In The Preferred Order
182
+
183
+ 1. Run the native `resultar-check` CLI as the authoritative local and CI gate. It uses TypeScript-Go
184
+ to run compiler diagnostics and all Resultar rules over the same project.
185
+ 2. Run the repository's formatter, build, tests, static analysis, package smoke tests, and runnable
186
+ examples.
187
+
188
+ The Go analyzer is the single implementation of Resultar diagnostics.
189
+
190
+ ## Configure The Native Checker
191
+
192
+ Install Resultar Check. A separate TypeScript installation is not required:
193
+
194
+ ```sh
195
+ pnpm add -D resultar-check
196
+ pnpm exec resultar-check
197
+ ```
198
+
199
+ Add the checker configuration marker to `tsconfig.json` and tune rules only when project policy
200
+ requires it:
201
+
202
+ ```json
203
+ {
204
+ "$schema": "./node_modules/resultar-check/schema.json",
205
+ "compilerOptions": {
206
+ "plugins": [
207
+ {
208
+ "name": "resultar-check",
209
+ "noDiscard": "error",
210
+ "noUnsafeAwait": "error",
211
+ "preferAndThen": "error",
212
+ "preferMapErr": "error",
213
+ "unsafeResultTypeAssertion": "error"
214
+ }
215
+ ]
216
+ }
217
+ }
218
+ ```
219
+
220
+ The `plugins` entry is consumed by the native CLI and stdio LSP server; it does not install an editor
221
+ extension. Start `resultar-check lsp` from the editor's language-server configuration. Findings use
222
+ rule IDs such as `resultar/no-discard`; pass `--format json` (or `--json`) when machine-readable JSON
223
+ Lines output is needed.
224
+
225
+ Read `packages/check/README.md` for all rules, severities, modes, and command-line flags. Use narrow
226
+ `ignoreFilePatterns` only for deliberate tests, scripts, generated files, or terminal process boundaries.
227
+
228
+ ## Act On Diagnostics
229
+
230
+ When an agent encounters a diagnostic, fix the architecture first:
231
+
232
+ - handle, compose, return, or intentionally discard a returned Resultar value; a discard does not
233
+ run a lazy task and must not be applied just to silence diagnostics;
234
+ - replace nested `Result` from `map` with `andThen`;
235
+ - replace `Promise<Result>` / `Promise<StrictResult>` contracts with `ResultAsync` /
236
+ `StrictResultAsync` and return composed operations directly (`prefer-result-async`);
237
+ - enable `preferResultAsyncMode: "all"` with `preferResultAsync: "error"` to also catch raw
238
+ `Promise<T>` repository/service contracts and inferred returns; map driver errors in adapters
239
+ and document local suppressions for required native framework or conversion callbacks;
240
+ - replace error-only recovery with `mapErr`;
241
+ - replace project-wide `try/catch` with `tryResult` or `tryResultAsync` when `noTryCatch` is enabled;
242
+ - wrap raw promise awaits in Resultar contexts;
243
+ - use `yield*` and avoid `try/catch` inside `Result.gen` / `safeTry`;
244
+ - preserve the inferred error channel instead of asserting it away.
245
+
246
+ ## Discover Repository Commands
247
+
248
+ Inspect `package.json` scripts instead of assuming a package manager or command. In the Resultar
249
+ repository, the relevant root commands currently include:
250
+
251
+ ```sh
252
+ pnpm fmt:check
253
+ pnpm build
254
+ pnpm check
255
+ pnpm test
256
+ pnpm analyze
257
+ pnpm smoke:package
258
+ pnpm test:examples
259
+ pnpm test:agents
260
+ ```
261
+
262
+ Run the narrowest useful command while iterating, then the complete relevant validation set before
263
+ claiming completion. Report pre-existing dependency, lockfile, or environment blockers separately
264
+ from failures introduced by the skill work.