diff --git a/.erpaval/INDEX.md b/.erpaval/INDEX.md index b45dcb8..69333d2 100644 --- a/.erpaval/INDEX.md +++ b/.erpaval/INDEX.md @@ -28,6 +28,8 @@ development sessions. Solutions are reusable; specs are per-feature. - [Add typed kind-filtered enumeration to IGraphStore once 3+ packages need it](solutions/architecture-patterns/storage-list-nodes-over-scattered-sql.md) — `listNodes()` collapses N raw-SQL call sites into one typed rehydration; cross-adapter parity test catches schema drift. - [Lift pure helpers to the deepest shared workspace dependency to break future cycles](solutions/architecture-patterns/lift-pure-functions-to-shared-dep-to-break-cycles.md) — `mcp → pack → mcp` was averted by lifting `classifyDependencies` into `@opencodehub/analysis` (the LCA dep). 30-LOC mechanical chore commit. - [Worktree isolation — pin pwd at task start and exclude worktrees from biome v2](solutions/best-practices/worktree-isolation-pwd-pin-and-biome-exclusion.md) — gitignore is not enough for biome v2; scope to `packages/` or add `experimentalScannerIgnores`. Always `pwd && git rev-parse --show-toplevel` at task start. +- [pnpm install hangs on an Amazon EFS-mounted workdir](solutions/best-practices/pnpm-install-on-efs.md) — EFS home puts the pnpm CAS store on NFS (~22 ms/lookup, 100× the local cost) and the AL2023 `io_uring` cleanup bug makes Node look hung. Fix in user-global `~/.npmrc`: pin `store-dir` to local disk + set `UV_USE_IO_URING=0`. +- [Use finch as a drop-in docker via a PATH shim on AL2023 devboxes](solutions/best-practices/finch-as-docker-shim.md) — tools that shell out to `docker` (e.g. `tree-sitter build --wasm`, which runs `docker run emscripten/emsdk`) don't know about finch. A 3-line shim that `exec sudo … finch "$@"` symlinked as `docker` on a prepended PATH unblocks them without aliases the child process can't see. - [Resolve milestone-old spec drifts inline with the implementing commit](solutions/best-practices/spec-drift-amend-inline-with-implementing-commit.md) — amend spec wording in the same commit that implements the resolution; record drifts with `recommend` in explore-delta so Gate 0 is a confirmation, not a fresh debate. - [Segregate graph-only and tabular-only stores at the interface boundary](solutions/architecture-patterns/igraphstore-itemporalstore-segregation.md) — when one type extends multiple sub-interfaces and a concrete implementor can't honestly satisfy all, segregate at the interface, not the class. `IGraphStore` + `ITemporalStore` + `openStore()` composition factory. - [Replace raw-SQL escape hatches with typed finders on the storage interface](solutions/architecture-patterns/typed-finders-replace-raw-sql-in-consumers.md) — 108 raw-SQL sites collapse into 15 named finders. Adapters internalize dialect; consumers stay backend-agnostic. Liskov-clean parity harness via public-method rebuilder. @@ -57,9 +59,12 @@ development sessions. Solutions are reusable; specs are per-feature. - [Collapse parallel switches into a Record registry](solutions/architecture-patterns/collapse-parallel-switches-into-record-registry.md) — when 2+ functions each switch over the same closed union (one per derived attribute), fold them into `Record`. tsc preserves exhaustiveness, the functions become one-line lookups, honest `| null` replaces placeholder lies, and ONE table-driven test with a `Record` fixture pins every (key, attribute) pair — better coverage than the zero direct tests the switches had. - [A vendored-artifact dep bump must re-vendor in the same PR](solutions/conventions/vendored-artifact-bump-must-revendor-in-same-pr.md) — bumping a dep that has a committed vendored artifact (`web-tree-sitter` → `vendor/wasms/*.wasm` + manifest) without re-running the vendor script passes ALL CI (the `prepublishOnly` guard isn't a CI step) and detonates at `npm publish`, aborting the dependency-ordered multi-package release mid-stream. Scan Dependabot consolidations for vendored-artifact deps and re-vendor before merge. +- [tree-sitter-wasms catalog package is unusable with web-tree-sitter 0.26+](solutions/architecture-patterns/tree-sitter-wasms-catalog-incompat.md) — `tree-sitter-wasms@0.1.13` ships `.wasm` built with `tree-sitter-cli@0.20.8` (legacy 6-byte `dylink` section); `web-tree-sitter@0.26+` hard-requires the 8-byte `dylink.0` and throws `need the dylink section to be first`. Build the grammar `.wasm` yourself for grammars missing a vendored blob; don't reach for the shared catalog. - [npm trusted publisher matches the ENTRY workflow, not the reusable one](solutions/conventions/npm-trusted-publisher-matches-entry-workflow-not-reusable.md) — npm OIDC matches "Workflow filename" against the workflow that INITIATED the run, not the one running `npm publish`. With `release-please.yml` → `workflow_call` → `release.yml`, register `release-please.yml`. Wrong registration silently 404s the token exchange; only `workflow_dispatch` runs (entry = release.yml) ever publish, so npm lags the tags. Config is web-UI-only, passkey-gated, one entry per package (17 here). +- [release-please: a single root package with a scoped name cannot create a release](solutions/conventions/release-please-single-root-package-cannot-release.md) — a scoped single root package can't release: `this.component = options.component || normalizeComponent(packageName)` falsy-coerces `component:""`, derives `cli`, and `buildRelease`'s single-entry standalone check rejects the component-less PR → `release_created=false` → npm never publishes. Keep TWO components (root + cli) + the `linked-versions` plugin so the published CLI bumps in lockstep with root on every commit. + - [The MCP surface is read-only: tools write artifacts, never a user's source](solutions/architecture-patterns/mcp-surface-is-read-only-artifacts-not-source.md) — no source-mutating MCP tool, period (`rename` + `remove_dead_code` removed). `readOnlyHint:false` is fine for artifact writers (scan/pack_codebase/group_sync write to `.codehub/`); `destructiveHint:true` (source mutation) is the forbidden flag. Pin the 28-name set + "no destructiveHint tool" in a server contract test. Includes the full tool-removal surface checklist. - [Rip a superseded never-wired subsystem — but record the capability gap](solutions/architecture-patterns/rip-superseded-subsystem-record-the-gap.md) — stack-graphs (~1,900 LOC, test-only) was sound by design but superseded by SCIP-as-oracle and orphaned by upstream archival. Rip test: dead-at-runtime + superseded + upstream-dead. Investigate intent first (docstrings, vendor README, git log, prior research notes); record the precision gap the rip leaves (non-SCIP languages lose the precision overlay) in the commit/ADR so a future session doesn't revive the dead end. diff --git a/.erpaval/solutions/conventions/npm-trusted-publisher-matches-entry-workflow-not-reusable.md b/.erpaval/solutions/conventions/npm-trusted-publisher-matches-entry-workflow-not-reusable.md index ffd7d71..1fd67c8 100644 --- a/.erpaval/solutions/conventions/npm-trusted-publisher-matches-entry-workflow-not-reusable.md +++ b/.erpaval/solutions/conventions/npm-trusted-publisher-matches-entry-workflow-not-reusable.md @@ -10,6 +10,8 @@ session: session-88b46e related: - release-published-event-needs-pat-or-inline - vendored-artifact-bump-must-revendor-in-same-pr + - release-please-single-root-package-cannot-release + - workflow-call-permissions-ceiling --- # npm trusted publisher matches the ENTRY workflow, not the reusable one diff --git a/.erpaval/solutions/conventions/release-please-single-root-package-cannot-release.md b/.erpaval/solutions/conventions/release-please-single-root-package-cannot-release.md new file mode 100644 index 0000000..57821cd --- /dev/null +++ b/.erpaval/solutions/conventions/release-please-single-root-package-cannot-release.md @@ -0,0 +1,97 @@ +--- +name: release-please-single-root-package-cannot-release +description: release-please v17 CANNOT create a release for a single root package with a scoped package-name. `this.component = options.component || normalizeComponent(packageName)` falsy-coerces `component:""`, derives `cli` from `@opencodehub/cli`, and buildRelease's standalone-component check then rejects the component-less release PR (`PR component: undefined does not match configured component: cli`) → release_created=false → npm never publishes. For "one published CLI that bundles N private libs," keep TWO components (root + cli) and use the linked-versions plugin so cli bumps with root on every commit. +metadata: + type: convention + category: conventions +tags: [release-please, monorepo, linked-versions, component, npm, publish, single-package, buildRelease] +discovered: 2026-06-11 +session: session-f12592 +related: + - tsup-collapse-monorepo-to-single-cli + - npm-trusted-publisher-matches-entry-workflow-not-reusable + - release-published-event-needs-pat-or-inline + - workflow-call-permissions-ceiling +--- + +# release-please: a single root package with a scoped name cannot create a release + +## Symptom + +Published npm package stuck at an old version even though tags/commits advance. +release-please run logs, in the "Building releases" phase: + +``` +✔ Building release for path: . +⚠ PR component: undefined does not match configured component: cli +``` + +Result: zero candidate releases built, nothing tagged, `release_created=false`, +the `workflow_call` to release.yml never fires, npm never publishes. Downstream +you also see `⚠ There are untagged, merged release PRs outstanding - aborting` +— that is a SYMPTOM (createPullRequests refusing to open a new PR while a merged +`autorelease: pending` PR exists), not the cause. + +## Root cause (release-please v17.6.0 source) + +`src/strategies/base.ts`: `this.component = options.component || this.normalizeComponent(this.packageName)`. +The `||` (not `??`) **falsy-coerces `component: ""`** away, so a scoped +`package-name: "@opencodehub/cli"` derives component `cli` (Node strategy's +`normalizeComponent` strips the `@scope/`). Then `buildRelease`'s standalone +check (fires only when the PR has a SINGLE release entry) compares the release +PR's branch component (`undefined` — branch `release-please--branches--main` +carries no `--components--` segment) against the derived `cli`. `"" !== "cli"` → +warn + `return` → zero releases. + +`include-component-in-tag: false` does NOT help: it only governs TAG naming +(`getComponent`), not the matcher, which uses `getBranchComponent` (ignores that +flag). So you cannot get (clean `vX.Y.Z` tag) + (component-less PR) + (passing +matcher) simultaneously for a single scoped root package. `component: ""` is +provably impossible via the `||`. + +## Fix — keep TWO components + linked-versions + +With 2 components the aggregate release PR has `releaseData.length === 2`, so the +broken single-entry standalone-component check is SKIPPED entirely. This is why +the 2-component scheme released reliably for 20+ versions and the single-component +collapse broke it. + +```jsonc +"packages": { + ".": { "package-name": "opencodehub", "component": "root" }, + "packages/cli": { "package-name": "@opencodehub/cli", "component": "cli" } +}, +"plugins": [ + { "type": "linked-versions", "groupName": "opencodehub", "components": ["root", "cli"] } +] +``` + +`linked-versions` solves the ORIGINAL starvation bug (root `.` receives every +commit so it always bumps; the published cli only saw `packages/cli/**` and +starved): it syncs all listed components to the highest version, so any commit +that bumps root bumps cli in lockstep → cli publishes on every release. + +## Migration mechanics that bit us + +- Manifest must list BOTH components at versions whose **component-format tags + exist** (`root-v0.8.5`, `cli-v0.7.4`) — release-please uses the manifest for the + current version but needs a matching tag for the SHA boundary. Seeding a + version with no matching tag → "No latest release found" → giant-changelog risk. +- linked-versions syncs to the HIGHEST member, so root 0.8.5 + cli 0.7.4 → both + next bump to 0.8.6. The published cli jumps 0.7.4 → 0.8.6 (forward, npm-valid). + Accept the number jump; boundary-safety (both tags exist) beats a pretty number. +- Do NOT bootstrap a new tag scheme by hand-creating `vX.Y.Z` tags + GitHub + Releases: release-please's manual-vs-owned-release reconciliation is fragile, + AND a `gh release`/`workflow_dispatch` recovery makes `release.yml` the OIDC + entry workflow → fails the trusted-publisher match (registered for + `release-please.yml`). See [[npm-trusted-publisher-matches-entry-workflow-not-reusable]]. + Only the automated `release-please.yml → workflow_call → release.yml` chain + publishes. Recovery must go through that chain. + +## Verified + +After restoring 2 components + linked-versions: release PR bumped BOTH to 0.8.6, +merge → `release_created=true`, tags `root-v0.8.6`+`cli-v0.8.6`, automated chain +ran `npm publish (OIDC + provenance) => success`, `npm view @opencodehub/cli +version` → 0.8.6 (was stuck at 0.7.4). Confirmed via cache-busted +`npx @opencodehub/cli@latest --version`. diff --git a/.erpaval/solutions/conventions/release-published-event-needs-pat-or-inline.md b/.erpaval/solutions/conventions/release-published-event-needs-pat-or-inline.md index ae18bb4..d929b99 100644 --- a/.erpaval/solutions/conventions/release-published-event-needs-pat-or-inline.md +++ b/.erpaval/solutions/conventions/release-published-event-needs-pat-or-inline.md @@ -5,6 +5,10 @@ type: knowledge tags: [github-actions, release-please, release-published, github-token, sbom, code-pack, ci] session: session-85faf1 ac: AC-D-4 +related: + - npm-trusted-publisher-matches-entry-workflow-not-reusable + - workflow-call-permissions-ceiling + - release-please-single-root-package-cannot-release --- ## Context diff --git a/.erpaval/solutions/conventions/workflow-call-permissions-ceiling.md b/.erpaval/solutions/conventions/workflow-call-permissions-ceiling.md index c2de8d6..070e453 100644 --- a/.erpaval/solutions/conventions/workflow-call-permissions-ceiling.md +++ b/.erpaval/solutions/conventions/workflow-call-permissions-ceiling.md @@ -16,6 +16,10 @@ created: 2026-05-15 session: session-569b82 track: bug category: conventions +related: + - release-published-event-needs-pat-or-inline + - npm-trusted-publisher-matches-entry-workflow-not-reusable + - release-please-single-root-package-cannot-release --- # GitHub Actions: top-level `permissions:` is a hard ceiling