Skip to content

Commit 4e9caf3

Browse files
claude[bot]claude
andauthored
Gate the workspace manifest dependency graph against cycles (#14524)
* feat(scripts): gate the workspace manifest graph against cycles (#13513) Adds check:workspace-manifest-cycles, a new check family that walks every workspace: edge in dependencies, devDependencies, peerDependencies and optionalDependencies across all 78 workspace packages and refuses on any cycle, naming each edge with its declaration class. peerDependencies is walked deliberately: a scan omitting it reports 0 cycles on the pre-fix #13513 tree, honestly and wrongly, since that cycle's first edge was a peer edge. Wired into lint.yml next to check:turbo-task-graph, the repo's existing collector idiom for this kind of manifest sweep. Co-Authored-By: Claude Code <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV * fix(pm): classify check-workspace-manifest-cycles.mjs::runSelfTest in COMPOUND_ANCHOR_LEDGER The self-test declaration this PR adds in scripts/check-workspace-manifest-cycles.mjs (runSelfTest) is a compound name the anchor matches, and the pm-dispatch-gates census requires every such name be classified. It is a genuine self-test battery, so accidental: false, placed in file-path order among the other scripts/check-*.mjs accidental:false rows. Co-Authored-By: Claude Code <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent e6fd6b2 commit 4e9caf3

4 files changed

Lines changed: 749 additions & 0 deletions

File tree

.github/workflows/lint.yml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3631,6 +3631,39 @@ jobs:
36313631
- name: turbo.json package tasks name real packages and real scripts
36323632
run: pnpm check:turbo-task-graph
36333633

3634+
# The workspace manifest graph, over every `workspace:` edge in
3635+
# `dependencies`, `devDependencies`, `peerDependencies` and
3636+
# `optionalDependencies` across all workspace packages, has NO cycle
3637+
# (#13513, filed as #14195).
3638+
#
3639+
# #13513 cost seven independent dev seats a wasted build cycle each, on
3640+
# trees they did not touch, because one `devDependencies` edge made the
3641+
# graph cyclic. A cyclic edge does not fail where it is added -- the
3642+
# author's own package builds fine -- it fails later, in SOMEONE ELSE's
3643+
# closure build, in a package they did not touch, non-deterministically
3644+
# (which member loses the DTS race is a scheduling outcome). Before this
3645+
# gate the only signal was `pnpm install` printing `WARN There are
3646+
# cyclic workspace dependencies: …` on stderr with EXIT 0, and it had
3647+
# been printing that warning for the entire life of the #13513 defect
3648+
# before anyone read it.
3649+
#
3650+
# All FOUR declaration classes are walked, `peerDependencies` included:
3651+
# a scan over the other three alone reports 0 cycles on the pre-fix
3652+
# #13513 tree, honestly and wrongly -- the loop's first edge was a
3653+
# `peerDependencies` edge (#13513 comment `5473800149`, resolved in
3654+
# `5479206575`). Every cycle is named with the declaration class of each
3655+
# edge that closes it (`A --devDependencies--> B`), because "there is a
3656+
# cycle" does not tell an author which edge to cut.
3657+
#
3658+
# Reads the workspace manifests only (via the shared enumerator). No
3659+
# build, no pnpm invocation, no network, sub-second. The `check:` key
3660+
# runs `--self-test` first: this gate's defect class is its matching
3661+
# rule, which a clean tree cannot distinguish from a rule that stopped
3662+
# matching, and the peer-only / optional-only cases are the #13513
3663+
# false-zero control -- a walker that drops either class fails them.
3664+
- name: workspace manifest dependency graph has no cycle
3665+
run: pnpm check:workspace-manifest-cycles
3666+
36343667
# A relationship carrier is spelled as the target object's NAME (#13103).
36353668
# `FieldSchema.reference` is `z.string()`, and #13053 measured what a
36363669
# non-string one costs: a fixture spelling `reference: { object: '...' }`

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@
8080
"check:single-claim-paths": "node scripts/check-single-claim-paths.mjs --self-test",
8181
"check:pnpm-filter-targets": "node scripts/pnpm-filter-targets.mjs --self-test && node scripts/check-pnpm-filter-targets.mjs --self-test && node scripts/check-pnpm-filter-targets.mjs",
8282
"check:turbo-task-graph": "node scripts/check-turbo-task-graph.mjs --self-test && node scripts/check-turbo-task-graph.mjs",
83+
"check:workspace-manifest-cycles": "node scripts/check-workspace-manifest-cycles.mjs --self-test && node scripts/check-workspace-manifest-cycles.mjs",
8384
"check:adr-anchors": "node scripts/check-adr-anchors.mjs --self-test && node scripts/check-adr-anchors.mjs",
8485
"check:adr-links": "node scripts/check-adr-links.mjs --self-test && node scripts/check-adr-links.mjs",
8586
"check:platform-checklist": "node scripts/checklist-select.mjs --self-test && node scripts/check-platform-checklist.mjs",

0 commit comments

Comments
 (0)