chore: version packages - #430
Merged
Merged
Conversation
The previous detect logic checked whether .changeset/*.md files were added in HEAD~1..HEAD. After changesets-version.yml consumes the changesets and pushes a version bump, the merge commit has no changesets left, only the bumped packages/fp/package.json. The detect step then reports has_changesets=false and the entire publish pipeline skips, including the actual publish step. Switch the detect to check whether packages/fp/package.json changed in HEAD~1..HEAD. If yes, this is a release. PR #407 and PR #409 both hit this skip path; this fix unblocks the publish.
Local copy of the 16 architecture rules + INDEX + READMEs from deessejs/errors@staging/docs/engineering/architecture/rules and decisions/, so contributors can review them without leaving the workspace and PRs can be checked against them directly. Each rule file begins with an HTML comment crediting the upstream URL and branch. Local additions (the architecture/README.md and the decisions/README.md note) are clearly marked. Source: https://github.com/deessejs/errors/tree/staging/docs/engineering/architecture
…act, drop dead dep Walk back the violations flagged by docs/engineering/architecture/ audit without introducing classes. rules addressed: - 0012 (prefer type) — public types in result/types.ts and maybe/types.ts stayed as literal interfaces only on the original draft; the current refactor replaces them with type literals where they remain semantically discriminated unions (Rule 0001 invariant 1: no shortcut on shape declarations). - 0008 (no chained type assertions) — replaced the as-yet-lingering as unknown as X chains in result/constants.ts and maybe/constants.ts with a typed 'this: Ok<T,E>' binding on every method. One residual chained cast remains in Err.flatMapAsync with an explanatory comment, documenting why it cannot be removed without classes (rule 0014). - 0004 (no speculative defences) — isUnit now uses an explicit guard (typeof === 'object' && !== null) instead of optional chaining on a cast; the jest-cast TODO comments in types.ts removed. - 0011 (kebab-case + no placeholders) — removed empty src/result/ builders.ts and src/maybe/builders.ts placeholders that remained from the original draft. - 0001 invariant 1 + 0008 — Ok.filter now genuinely honours its errorFn contract. Signature was (predicate, errorFn?: (v)=>E) → E; implementation returned ok(value) always. Now: predicate passes → this; predicate fails + errorFn → Err(errorFn(value)); predicate fails + no errorFn → this (pass-through). 7 new tests lock the contract: 3 for Ok.filter errorFn, 4 for conversion chaining (toMaybe/toOption/toResult now route through factories instead of reinvention). - 0001 invariant 8 (no dependency without justification) — removed the empty peerDependencies / peerDependenciesMeta block (@deessejs/errors) and the dead devDependency on @deessejs/errors. The dependency will be reintroduced with a real consumer (try_ family) in a future ADR, per rule 0006. - 0006 (technology choices documented) — added decisions/0001-package- position.md enumerating the seven deliberate choices (ESM-only, TS strict, function-based API, discriminated unions, no runtime deps, honest runtime, kebab-case filenames) using the four-question template (what / enables / rules out / revisit). - 0001 (project mindset, invariants 9 and 10) — dropped the stale // TODO: comments inside src/index.ts that documented intentional future work, replacing the inline roadmap block with a pointer to the ADR under decisions/. Public surface preserved: ok(), err(), some(), none(), maybe(), unit(), isUnit(), isResult(), isMaybe(), OkType, ErrType, SomeType unchanged in name. Ok<T,E = never> default widened so users can opt into a typed E when they need Ok.filter to produce an Err. Verification: tsc --noEmit clean eslint src/ clean tsc -p tsconfig.build clean dist/ vitest run 23/23 turbo type-check 2/2 packages clean
The previous 'Test + coverage gate' job conflated two concerns: running the suite (fast) and collecting coverage (slow + threshold gate). Splitting them: - 'test' job: runs 'pnpm turbo test' without coverage. Fails fast on any test failure. No artifact, no comment. - 'coverage' job: depends on 'test', runs 'pnpm turbo test:coverage' with the 100% per-file threshold gate (ADR 0002). Uploads the coverage artifact and posts a sticky PR comment with the per-file coverage table rendered by .github/scripts/render-coverage.mjs. The PR comment uses marocchino/sticky-pull-request-comment@v2 with header='coverage' so the comment is hidden on re-run and replaced with the latest values (sticky semantics). The coverage summary is read from packages/fp/coverage/coverage-summary.json (v8 reporter output). Renderer notes: - One row per file plus a Total row, sorted by file path. - Files with no branches (e.g. type-only modules) render 'n/a' in the branch column so the table is not misleading. - Per-file thresholds are 100% on statements / branches / functions / lines. The threshold gate runs before the render step; if the test:coverage command fails, the comment step is skipped (if: success()) and the failure surfaces as a CI check failure.
The merge from main onto refactor/classes brought in commit 119b1e8 (fix(fp): honour architecture rules - typed factories, Ok.filter contract, drop dead dep), which removed @deessejs/errors from packages/fp/package.json. The lockfile was not regenerated in that commit, so pnpm install --frozen-lockfile in CI fails: ERR_PNPM_OUTDATED_LOCKFILE * 1 dependencies were removed: @deessejs/errors@^1.0.0 Refresh the lockfile so the frozen install gate passes. No source files changed.
The CI split into a 'test' job and a 'coverage' job, where the coverage job runs 'pnpm turbo test:coverage'. The corresponding task was missing from turbo.json, so the coverage job errored: x Missing tasks in project -> x Could not find task 'test:coverage' in project Add the task with 'outputs: [coverage/**]' so the cache hint is exact. cache: false because cargo coverage runs are side-effecting and the threshold gate is policy.
The CI 'coverage' job was failing with 'No tasks were executed' because turbo found the test:coverage task in turbo.json but could not resolve a corresponding script in packages/fp/package.json. The script had been lost in the merge from main into refactor/classes. Two fixes: 1. packages/fp/package.json: add the 'test:coverage' script (vitest run --coverage). The script survives the merge this time so it stays in the lockfile. 2. .github/workflows/ci.yml: filter turbo commands to '@deessejs/fp' for the test and coverage jobs. The other workspace package (apps/web) doesn't have a test:coverage script, so an unfiltered 'pnpm turbo test:coverage' would refuse to execute any task. '--filter=@deessejs/fp' keeps the run scoped to the package that actually has the coverage reporter wired up.
CI's 'coverage' job errored with: MISSING DEPENDENCY Cannot find dependency '@vitest/coverage-v8' The package.json regen in the previous commit dropped the coverage provider from devDependencies. Add it back at ^4.1.10 and bump vitest to ^4.1.10 to match the peer-version range. Regenerate the lockfile so the frozen-install gate in CI passes.
Two follow-ups to the previous CI split: 1. packages/fp/vitest.config.ts: re-add the coverage block (it was lost when the merge from main into refactor/classes rewrote this file). Same shape as ADR 0002 §3: provider v8, all: true, explicit include/exclude with per-entry justification, json-summary reporter added so coverage-summary.json is emitted for the render step, thresholds at 0 because the full method × variant test matrix lives in tests/ (a follow-up PR). 2. .github/scripts/render-coverage.mjs: read the json-summary output (packages/fp/coverage/coverage-summary.json), format paths relative to the repo root, render the Total + per-file table, output markdown. The previous version was reading the v8 raw coverage-final.json (a different shape that v8 emits) and the working dir was wrong on the CI runner. Tested locally: emits the expected markdown table including the Total row and the per-file rows with relative paths. The threshold gate is disabled in this PR. The full method × variant test matrix lives in tests/ and lands with the gate in a follow-up. ADR 0002 §3 (100% lines / branches / functions / statements, perFile: true) is the target, not a current state.
The 'Post coverage comment' step used:
message: ${{ steps.render.outputs.markdown }}
The 'Render coverage table' step runs:
run: node .github/scripts/render-coverage.mjs
which writes to stdout. GitHub Actions does NOT capture a step's
stdout into a default output; you have to declare outputs: in the
step, and even then, the action failed with:
##[error]Either message or path input is required
Fix: have the renderer write to a file (coverage-comment.md) at
the repo root, and have the post step use 'path: coverage-comment.md'
instead of the absent step output. The marocchino action reads the
file content and posts it as the sticky PR comment.
Add coverage-comment.md to .gitignore (it's a build artifact,
regenerated each CI run).
The split of the test and coverage CI jobs is a user-visible change in the sense that future PRs against staging will start posting coverage comments. Document it in a patch-level changeset.
During the rebase onto origin/staging, several 'fix(publish)' commits were skipped. The 'git rebase --continue' output in the sandbox shell was redirected into the working copy of .github/workflows/publish.yml (the file at the root of the conflict), corrupting it with the git error text. The rebase's intent was already 'take origin/staging's version of publish.yml' for every fix(publish) commit, so this commit restores the right file from origin/staging via 'git archive'.
The single test file was sitting next to the source in
packages/fp/src/index.test.ts. Per the project convention (and
rule 0002: tests as a sibling of src/, not nested), move it
to packages/fp/tests/index.test.ts and update the import path
('../src/index.js' -> '../../src/index.js').
Vitest's test discovery includes the new location by default
('**/*.{test,spec}.{js,ts}'). No config change needed.
The 'M packages/fp/CHANGELOG.md' line in the status output is
the pre-existing rebase residue, not a change in this commit.
The single test file was at packages/fp/src/index.test.ts (nested inside src/, per the previous post-revert state). Per rule 0002 (tests as a sibling of src/, not nested) and the project convention, move it to packages/fp/tests/index.test.ts. Two changes are needed for the move to work: 1. The import path changes from '../src/index.js' (one directory up) to the published-style '@deessejs/fp' (one package up, the public name). Tests now exercise the same surface a downstream consumer would see, not an internal path. 2. The vitest config adds a resolve.alias entry that points '@deessejs/fp' to packages/fp/src/index.ts. Without this, the import resolves to packages/fp/dist/index.js (the package's published build output), which only exists after pnpm build. In dev, the alias maps to source so tests run against the working tree. The 'R' rename + the alias addition land together. Without the alias, the new import path fails the build because dist/ does not exist in this PR (no source change).
ci(workflows): split test and coverage into separate jobs with PR comment
Convert Ok, Err, Some, None from interface declarations to type aliases pointing at internal OkImpl, ErrImpl, SomeImpl, NoneImpl classes. The classes are not exported; the factory functions (ok, err, some, none, maybe) remain the only public entry points, in line with rule 0014. Also delivers the pipeable functions previously signalled by the TODO comments in result/index.ts and maybe/index.ts: map, flatMap, mapError, filter, tap, tapAsync, flatMapAsync, match, fold, getOrElse, getOrThrow, getOrNull, getOrUndefined, toMaybe, toResult, toArray, toIterable, isOk, isErr, isSome, isNone, and the get projection on Maybe. The chained type assertions (rule 0008) on the previous Result and Maybe implementations are gone. Public surface is byte-for-byte unchanged. Co-Authored-By: Claude Fable 5 <[email protected]>
Add dedicated test suites that methodically cover every method of the newly-internal OkImpl, ErrImpl, SomeImpl, and NoneImpl classes, the pipeables in result/functions.ts and maybe/functions.ts, and the shared type guards in src/types.ts. Also expose the pipeables on the public barrel (src/index.ts). The Result and Maybe namespaces share function names, so the Maybe pipeables are exported under their qualified names (mapMaybe, flatMapMaybe, ...). The Result pipeables keep the bare names. This matches the convention already used by the instance method overloads on Some and None. Coverage (v8) is now 100% across statements, branches, functions, and lines. The thresholds in vitest.config.ts are raised to 100% to freeze the bar. Co-Authored-By: Claude Fable 5 <[email protected]>
refactor(fp): internal classes for Result and Maybe, deliver pipeables
github-actions
Bot
force-pushed
the
changeset-release/main
branch
from
August 17, 2026 13:18
a565a62 to
5c6d644
Compare
…p, tupled, untupled) These are the seven exports that the documentation has been promising since v1.0 (docs/internal/product/features/function-utilities.md) and that the README quotes as ergonomic essentials. The pipeables shipped in PR #431 (map, flatMap, ...) are now usable through pipe as the JSDoc in result/functions.ts and maybe/functions.ts already documents. `pipe` and `flow` carry variadic overloads up to nine steps. Beyond that the tail collapses to `unknown` and the caller is on their own. `tupled` and `untupled` are inverses. Tests assert both directions. Coverage stays at 100% across statements, branches, functions, and lines. Vitest thresholds remain pinned at 100% (set in PR #431). Co-Authored-By: Claude Fable 5 <[email protected]>
…hunks
Builds on the previous commit by adding the function utilities that
fp-ts ships in its function module and that the README advertises but
the code has never shipped.
New exports:
- `compose` — right-to-left function composition. Mirror image of `flow`.
- `Predicate<A>` / `Refinement<A, B>` — type aliases for predicates
and type-guard predicates.
- `not` — negates a predicate.
- `Lazy<A>` — the thunk interface, `() => A`.
- `Endomorphism<A>` — `(a: A) => A`.
- `FunctionN<A, B>` — an N-ary function whose arity is given by a
tuple type.
- `tuple` — typed identity for tuple inference (Dan Vanderkam pattern).
- `constTrue` / `constFalse` / `constNull` / `constUndefined` /
`constVoid` — primitive thunks used in filter chains.
These close the gap between what the README's Predicate utilities row
advertises ("Predicate, Refinement, not") and what the code actually
exports. They also align the module with fp-ts's `function.ts` shape.
`compose` is the senior-grade omission: every other FP TS lib
ships right-to-left composition alongside left-to-right `flow`.
Coverage stays at 100% across statements, branches, functions, and
lines. Vitest thresholds remain pinned at 100%.
Co-Authored-By: Claude Fable 5 <[email protected]>
Short-circuit logical combinators over Predicate<A>. Symmetric with `not` which already shipped in the previous commit. `and` returns a predicate that is true only when both inputs are true. `or` returns a predicate that is true when either input is true. Both short-circuit — `and` skips the right predicate when the left is false, `or` skips it when the left is true. This closes the final gap with fp-ts's predicate utilities and with the README row that advertised "Predicate, Refinement, not, and, or". Coverage stays at 100% across statements, branches, functions, and lines. Vitest thresholds remain pinned at 100%. Co-Authored-By: Claude Fable 5 <[email protected]>
feat(fp): add function utilities (pipe, flow, identity, constant, flip, tupled, untupled)
github-actions
Bot
force-pushed
the
changeset-release/main
branch
from
August 19, 2026 11:22
5c6d644 to
ce2e254
Compare
Brings staging up to date with the 1.2.x CI/publish fixes and the architecture ruleset from main (commit 119b1e8). Conflicts were resolved in favour of staging's design: - Keep OkImpl/ErrImpl/SomeImpl/NoneImpl internal classes. - Keep the function/ utilities (pipe, flow, compose, predicate, ...). - Keep the @deessejs/fp import alias in tests. - Adopt main's ADR comment in index.ts. - Keep @vitest/coverage-v8 alongside the eslint stack. Ok.filter contract (Err(errorFn(value)) on predicate failure with errorFn) is already satisfied by OkImpl.filter on staging.
merge: sync main into staging (1.2.x CI fixes + architecture rules)
github-actions
Bot
force-pushed
the
changeset-release/main
branch
from
August 19, 2026 11:44
ce2e254 to
f86f906
Compare
…lassifyError)
Delivers the Try<T, E> module that the README and
docs/internal/product/features/try.md have been advertising since
v1.0. Wraps synchronous and asynchronous throwing functions into a
typed value, eliminating silent try/catch blocks at call sites.
- try_<T>(thunk) and try_<T, E>({ onSuccess, onError }) — sync wrap.
- tryPromise<T>(thunk) and tryPromise<T, E>({ onSuccess, onError }) —
async wrap; onError may itself be async.
- attempt(config) — returns { execute(), clientSafe() } with optional
single-attempt retry and error normalisation.
- withReporting(onSuccess, name, reporter, metadata?) — forwards
caught errors to a caller-supplied ErrorReporter.
- classifyError(e, rules) — returns 'retryable' | 'non-retryable'
based on instanceof matching.
- toResultTry() — converts a Try<T, E> into a Result<T, E> so existing
pipe(...) pipelines compose naturally.
Internal classes SuccessImpl / FailureImpl follow rule 0014 and live
in src/try/internal/. Public types are type aliases pointing at them
(rule 0012). The discriminated union uses _tag: 'Success' | 'Failure'
to mirror the existing Ok/Err and Some/None naming.
Coverage 100% on lines / branches / functions / statements across the
8 covered files (types.ts and index.ts are excluded by vitest config).
Co-Authored-By: Claude Fable 5 <[email protected]>
`attempt()` is now a thin factory returning `new AttemptImpl(config)`. The class lives in `src/try/internal/attempt-impl.ts` and is not exported (rule 0014). Construction stays lazy: `attempt()` does not invoke `onSuccess`; the wrapped operation runs only when `execute()` or `clientSafe()` is called. The previous closure-based implementation is preserved verbatim inside the class. The public surface (`Attempt<T>`, `execute`, `clientSafe`) is unchanged. Coverage 100% on lines / branches / functions / statements. Tests for the impl surface now live in `tests/try/attempt-impl.test.ts`. Co-Authored-By: Claude Fable 5 <[email protected]>
The Try module shipped in #439 duplicated Result one-for-one: SuccessImpl mirrored OkImpl, FailureImpl mirrored ErrImpl, and fifteen `*Try` pipeables (`mapTry`, `flatMapTry`, `matchTry`, ...) shadowed the Result combinators under different names. The `toResultTry` bridge existed only to cross between the two isomorphic types. This commit collapses the reasoning on `Result<T, E>`. There is now one machine of states, one set of pipeables, one vocabulary. Public surface changes: - NEW `Result.fromThrowable(thunk | { onSuccess, onError })` — sync wrap. Returns `Result<T, E>`. - NEW `Result.fromAsyncThrowable(thunk | { onSuccess, onError })` — async wrap. Returns `Promise<Result<T, E>>`. - NEW `UnhandledException`, `AttemptConfig`, `Attempt`, `NormalizedError`, `RetryConfig`, `DelayStrategy`, `ErrorReporter`, `ErrorContext`, `ReportableError`, `ErrorClassification`, `ClassificationRule`, `ErrorConstructor` — Result-side types. - MOVED `attempt`, `withReporting`, `classifyError` from `src/try/` to `src/result/`. Internal `AttemptImpl` class moved to `src/result/internal/`. - KEPT as aliases at the top level: `try_`, `tryPromise` (both resolve to `fromThrowable` / `fromAsyncThrowable`). - REMOVED: `Success`, `Failure`, `Try`, the `success` / `failure` factories, the `*Try` pipeables, the `_tag: 'Success'` / `_tag: 'Failure'` discriminants. Files: - src/result/{wrapping,attempt,reporting,classify}.ts (new) - src/result/types.ts, constants.ts, index.ts (extended) - src/result/internal/attempt-impl.ts (new, moved from src/try/) - src/try/index.ts (now a one-file facade re-exporting from result/) - src/index.ts (root barrel updated) - src/try/{types,constants,functions,attempt,reporting,classify, internal/success-impl, internal/failure-impl, internal/attempt-impl}.ts (deleted) - tests/result/{wrapping,attempt-impl,reporting,classify,index}.test.ts (new / moved) - tests/try/* (deleted) - docs/internal/product/features/try.md (reframed as a Result adapter note) - docs/internal/product/features/result.md (new "Wrapping Throwing Functions" section) Coverage stays at 100% on lines / branches / functions / statements. The 24 test files now hold 316 tests. Co-Authored-By: Claude Fable 5 <[email protected]>
PR #439 shipped a Try module and then collapsed it onto Result through aliasing. This commit goes the rest of the way and removes the Try facade entirely. What changed: - DELETED src/try/ (the directory, including the facade index.ts). - DELETED docs/internal/product/features/try.md. - The top-level aliases `try_` and `tryPromise` are gone. Consumers wrap throwing code through `Result.fromThrowable` / `Result.fromAsyncThrowable` (which already return `Result<T, E>`). - The root barrel no longer imports from `./try/`. All wrapping helpers come from `./result/`. - The product README no longer lists Try as a primitive. - wrapping.test.ts drops its alias smoke checks; the canonical fromThrowable / fromAsyncThrowable tests stay. - The unify-on-result changeset is rewritten to reflect that the Try facade is fully removed. Coverage stays at 100% on lines / branches / functions / statements across the 24 test files (314 tests). Co-Authored-By: Claude Fable 5 <[email protected]>
feat(fp): add Try module (try_, tryPromise, attempt, withReporting, classifyError)
github-actions
Bot
force-pushed
the
changeset-release/main
branch
from
August 20, 2026 11:18
f86f906 to
ec0cd92
Compare
Contributor
Author
Coverage report
Per-file thresholds: 100% on statements / branches / functions / lines (ADR 0002). Files with no branches render |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR was opened by the Changesets release GitHub action. When you're ready to do a release, you can merge this and publish to npm yourself or setup this action to publish automatically. If you're not ready to do a release yet, that's fine, whenever you add more changesets to main, this PR will be updated.
Releases
@deessejs/[email protected]
Minor Changes
7e9b7fd: refactor(fp): replace plain-object Result/Maybe with internal classes behind the public factory functions
The public API is unchanged.
Ok,Err,Some,None,Result, andMaybeare nowtypealiases pointing at internalOkImpl,ErrImpl,SomeImpl, andNoneImplclasses. The classes are not exported; the factory functions (ok,err,some,none,maybe) remain the only public construction entry points.Chained type assertions on the previous implementations are gone.
noneis a single static instance.Also delivers the pipeable functions that the
TODOcomments inresult/index.tsandmaybe/index.tshave been signalling since v1.0:map,flatMap,mapError,filter,tap,tapAsync,flatMapAsync,match,fold,getOrElse,getOrThrow,getOrNull,getOrUndefined,toMaybe,toResult,toArray,toIterable,isOk,isErr,isSome,isNone— and thegetprojection forMaybe. They compose throughpipe.See
docs/engineering/plans/architecture-classes.md.2a05140: feat(fp): add function utilities (pipe, flow, identity, constant, flip, tupled, untupled)
Delivers the function utilities that the documentation has been
promising since v1.0 (see
docs/internal/product/features/function-utilities.md).pipe— left-to-right function composition with a starting value.flow— left-to-right function composition that returns a function.identity— the identity function.constant— wraps a value into a function that ignores its argument.flip— swaps the first two arguments of a binary function.tupled/untupled— tuple ↔ positional adapters.pipeandflowcarry variadic overloads up to nine steps. Beyondthat the tail collapses to
unknownand the caller is on their own.These are the seven exports that the README and the documentation
have been advertising. The pipeables shipped in PR refactor(fp): internal classes for Result and Maybe, deliver pipeables #431 (
map,flatMap, ...) are now usable throughpipeas the JSDoc inresult/functions.tsandmaybe/functions.tsalready documents.See
docs/engineering/plans/function-utilities.md.aae1039: refactor(fp): unify error handling on Result, retire the Try module
The standalone Try type is gone. Wrapping throwing functions is now part of the Result surface.
New public API:
Removed (no aliases; the previous Try PR was never published to npm):
Coverage stays at 100% on lines / branches / functions / statements.
See docs/internal/product/features/result.md for the canonical documentation, including a new Wrapping Throwing Functions section.
Patch Changes
4ad12c1: Split the CI's "Test + coverage gate" job into two: a fast
testjob and a
coveragejob that posts a sticky PR comment with theper-file coverage table. No source-code changes. The coverage
threshold gate is disabled in this PR (lands with the test matrix
in a follow-up).
726fb94: chore(release): sync main into staging
Backports the CI/publish fixes from the 1.2.x release series and
commit
119b1e8(architecture rules,Ok.filtercontract, dropdead dependency) into staging. No public API changes.
Ok.filter(predicate, errorFn)contract is now part of therelease notes: when the predicate fails and an
errorFnissupplied, the result is
Err(errorFn(value)); withouterrorFn,the
Okpasses through.src/index.tsand the ADR pointerin
docs/engineering/architecture/decisions/.main(idempotent tag creation,resolve-versionquoting,--provenanceremoval, etc.) nowship from staging.
🤖 Generated with Claude Code