Repository navigation
Step 2: consolidate the thirteen private pnpm-workspace.yaml parsers behind one shared enumerator — now unblocked, and the enumerator must NOT be a gate itself #11510
Copy link
Copy link
Closed
Description
Activity
Claim: devx lane PM seat, session
e2eac1a7-8000-5c95-9749-38aec2ace6fc, branchclaude/issue-11510-workspace-enumerator.Graded
pm:queue→pm:dispatchedin one step (the card was carryingtooling+domain:devxwith nopm:label). The card is self-contained and its step-1 blocker is discharged, so it needs no further triage from me beyond the dispatch.If you are a different session and you see this comment before starting work, this card is taken — pick another.
Generated by Claude Code
{ "issue": 11510, "status": "done", "branch": "claude/issue-11510-workspace-enumerator", "pr": "https://github.com/objectstack-ai/objectstack/pull/11797", "premise_still_valid": true, "summary": "Nine private pnpm-workspace.yaml `packages:` parsers now share scripts/workspace-enumerator.mjs, a plain module that is NOT a discovered gate file and declares ZERO path population. Re-measured on the day: 154 discovered families (card said 140), 15 families across 13 scripts read the file — the 15/13 figure holds. TWO CARD ASSUMPTIONS FALSIFIED. (1) The thirteen are not thirteen parsers: check-osv-exemptions.mjs names the file in prose only, check-cross-package-test-inputs.mjs uses it as a workspace-ROOT MARKER, and check-prerelease-pin-watch.mjs parses the `overrides:` block, not `packages:` — all three untouched, nothing to consolidate. A fourth real parser the family measurement could not see (ablation-dist-preflight.mjs, not a gate) was consolidated too: it was the strictest copy and silently truncated the workspace at any full-line comment inside the block. (2) The parsers were not equivalent: sliced from their real source bytes and run against 17 inputs, they agree on the repo's actual file and split into four clusters on 9 of 17 — a shared answer that was only ever true by coincidence. PARTIAL CONSOLIDATION, DEFENDED: the PARSE is shared, the DECLARATION is not. Priced on the live tree, spelling the workspace globs as literals in the shared module would have handed importers +41725 (gate, file) pairs — 13.6x the +3065 the follow already refuses — and turned check:release-body RED by contradicting its no-path-population marker. Three callers had already measured and refused that exact declaration in writing. So each gate keeps its own declaration; check-test-source-alias and check-type-source-resolution keep WORKSPACE_PARENT_GLOBS and gain a both-directions reconciliation against the live parse, which is what actually retires the drift between their byte-identical copies. Counterfactual measured: consolidating the declaration too drops check:test-source-alias to 0 matched-list occurrences for a packages/ card — 'naming nothing', exactly as the card predicted.", "tests": "All evidence measured at final commit 38a1d66c9 (gate union re-run after the last commit).\n\nSWEEP (before -> after, dispatch-gates' own discoverFamilies/trackedFiles/hintCovers): families 154 -> 154; tracked files 6577 -> 6578; (gate,file) pairs 49281 -> 49287, delta +6; FAMILIES THAT LOST COVERAGE: NONE. The +6 is exactly the one new file matched by the six `scripts/**` families (check:agent-test-spelling 353->354, check:cross-package-test-inputs 3248->3249 x2, check:entry-guard 242->243, check:parse-guard 242->243, check:pnpm-filter-targets 272->273). No other family's count moved; zero re-attribution.\n\nTHE THREE NAMED REGRESSION CHECKS, live derivations before (006c181a8, separate worktree) and after, over three card paths (packages/spec/src/data/filter.zod.ts, packages/qa/dogfood/src/x.ts, examples/app-showcase/src/a.ts): check:published-files 5396 -> 5396, check:test-source-alias 5395 -> 5395, check:type-source-resolution 5395 -> 5395 — all three still name real cards, and the `via` column shows each matched through its OWN declaration ('gate source packages/*'), not an inherited hint. COUNTERFACTUAL (trapped ablation in the BASE worktree, disk-confirmed): replacing WORKSPACE_PARENT_GLOBS with a runtime parse gives 0 matched-list occurrences of check:test-source-alias for a packages/ card, and it falls into the residue naming only package NAMES.\n\nPER-CALLER POPULATION EQUALITY (before side computed from origin/main's source bytes via git show, after side from the shared module): check-published-files 78->78, check-published-readme-exports 78->78, check-type-check-coverage 78->78, check-changeset-fixed(names) 69->69, check-override-consistency(names) 69->69, release-github-releases(names) 69->69, check-dev-prereqs(dirs) 86->86, pnpm-filter-targets(names) 78->78, ablation-dist-preflight(names) 78->78 — ALL IDENTICAL. NEGATIVE CONTROL: the same harness does separate 86 member dirs from 78 with a manifest, so 'identical' is a measurement, not a harness that compares nothing.\n\nZERO-POPULATION PROOF, with positive control: extractWatchHints(scripts/workspace-enumerator.mjs) -> [] and 0 live pairs contributed; same run, check-published-files.mjs -> 14 hints / 7254 pairs. 'pnpm-workspace.yaml' measured as a non-hint (hintCovers false — no path separator).\n\nREVERSE VERIFICATION — 3 legs, each with `trap '<restore>' EXIT INT TERM` and each mutation confirmed on disk by anchor-text grep -c before AND after (an editor's exit code is not evidence), plus a restore-leg count check. No build/dist is involved: these are plain .mjs run directly, so there is no dist staleness leg to report. Leg 1 — drop 'examples/*' from WORKSPACE_PARENT_GLOBS (x1 -> x0 on disk): exit=1, 'pnpm-workspace.yaml declares the workspace root examples, which WORKSPACE_PARENT_GLOBS does not'; restored x1, green. Leg 2 — add a root the YAML lacks (x0 -> x1): exit=1, 'WORKSPACE_PARENT_GLOBS declares nowhere, which pnpm-workspace.yaml does not'; restored x0. Leg 3 — plant ['packages/*','apps/*'] in the enumerator (x0 -> x1): exit=1 in ALL FIVE consolidated gates (published-files, published-readme-exports, type-check-coverage, dev-prereqs, pnpm-filter-targets); restored x0, green. LEG 3 FIRST PASSED, and that failure is in the diff as its own commit (45c62e9f4): the zero-literal guard stripped block comments with a hand-rolled /\\*...\\*/ regex, and a workspace glob CONTAINS a comment opener — the / and * of 'packages/*' ARE '/*' — so the stripper opened a comment at the literal it was hunting and ate forward to the next '*/'. The planted literal really did contribute 5154 pairs per importer while the guard read green. Fixed to use maskComments from js-comment-mask.mjs plus extractWatchHints' leading-./ strip, then re-ablated red. Direction observed matches the prediction in all three legs (red).\n\nGATE UNION at 38a1d66c9, all exit 0, verdict lines quoted from the gates themselves: check:agent-test-spelling, check:cross-package-test-inputs ('OK: 16 package(s) read outside themselves, all declared'), check:entry-guard ('144 scripts/ file(s)'), check:parse-guard, check:pnpm-filter-targets ('134/167 --filter occurrence(s) across 26 file(s) resolve against 78 workspace package(s)'), check:published-files ('69 publishable package(s) of 78 workspace member(s)'), check:release-body ('49 assertions'), check:test-source-alias ('72 packages with tests scanned'), check:type-check-coverage ('65/78 workspace packages type-checked'), check:type-source-resolution ('77 packages with a tsconfig.json scanned'), check:override-consistency ('8 published-manifest declaration(s)'), check:nul-bytes ('scanned 6573 text file(s) ... no raw ASCII control bytes'), check:pm-dispatch-gates ('dispatch-gates self-test: 579 cases pass' — this is the gate that owns the no-path-population contradiction assertion my change could have broken), check-changeset-fixed.mjs ('in sync with 69 public workspace packages'), check-cross-package-test-inputs.mjs, check-osv-exemptions.mjs, check-dev-prereqs --self-test, ablation-dist-preflight --self-test. Exit codes captured by redirect-then-capture, never after a pipe.\n\nFULL REPO LINT: `pnpm lint` (eslint . --no-inline-config, whole repo, NO narrowing) green in 55s under the verify lock. Gate list derived with `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack` and no paths (12 changed paths off the merge base), not from the dispatch prompt.\n\nTWO DECLARED NARROWINGS, both unbuilt-tree refusals in a worktree with no dist/, both of which CI builds past: (a) check:published-readme-exports exits 1 with '198 package(s) are not built' — output diffed BYTE-IDENTICAL to the capture taken at 006c181a8 BEFORE any edit, and lint.yml builds at line 3592 before this step; (b) check:type-check-debt refuses --re-measure without a built closure BY DESIGN, and lint.yml builds at line 3393 before it — its coverage half printed byte-identical output before and after (65/78 type-checked, 13 DEBT, 19 hiding tests, 436 frozen raw errors), and that output IS the enumeration readout, so the migration provably did not move it. Not run locally: a full workspace build, which is CI's run.", "open_questions": [], "out_of_scope_findings": [] }
Generated by Claude Code
Step 2 of #11190, filed rather than built: that card's step 1 (teach
scripts/pm/dispatch-gates.mjsto follow first-party imports) landed on its own branch, and #11190's own words are why the order matters — "doing 2 without 1 is a regression with no red gate".What is left to do
Re-measured on today's tree (140 discovered families), not recalled: 15 discovered families across 13 distinct scripts read
pnpm-workspace.yaml, each with its own block parser:(#10542 measured twelve when the farm was 119; #11190 measured fifteen at 137. The figure to use is the one you measure on the day.)
scripts/i18n-bundle-surface.mjsis the shape the shared module should take. Two of those scripts once carried byte-identical 11-entryWORKSPACE_PARENT_DIRSarrays with the same blind spot in both, which is the drift a single declaration site retires.The constraint step 1 imposes on this work — read before designing the module
The follow that unblocks this is deliberately narrow, and two of its rules decide where the enumerator may live:
check:invocation, because such a module's population already reaches the tree through its own family and attributing it to every importer was measured at +3065 fabricated (gate, file) pairs for a single caller. So: a plain module (thei18n-bundle-surface.mjs/regen-artifacts.mjsshape), never a module that some workflow also invokes directly.scripts/. Bare specifiers, dynamicimport(), and first-party modules outsidescripts/are all refused (each with a measurement infirstPartyImportTargets' docblock).How to know it worked
Sweep the same way step 1 did — the (gate, file) pair count and the per-family matched counts, before and after — and confirm no family loses coverage. The three declarations #11190 names as at risk are the ready-made regression check:
check:test-source-aliasandcheck:type-source-resolution(theirWORKSPACE_PARENT_GLOBSarrays, #9955 / PR #10540) andcheck:published-files(PR #11189). All three name real cards today; if any of them goes back to naming nothing, the consolidation has deleted the declaration it was meant to centralise.Related
#11190 (the parent finding and step 1), #10542 (the original observation), #10540 / #9955 (the declarations at risk), #10114 (the merged precedent for declaring a population).