Skip to content

fix(intent): make workspace package resolution glob-aware and reusable#92

Closed
LadyBluenotes wants to merge 7 commits intomainfrom
updates
Closed

fix(intent): make workspace package resolution glob-aware and reusable#92
LadyBluenotes wants to merge 7 commits intomainfrom
updates

Conversation

@LadyBluenotes
Copy link
Member

@LadyBluenotes LadyBluenotes commented Mar 23, 2026

Summary

  • replace the workspace suffix-stripping logic with segment-by-segment resolution for literal segments, *, and **
  • normalize workspace patterns before resolution and return deduped, stably sorted package roots
  • support exclusion patterns from workspace config by subtracting !-prefixed matches from resolved package roots
  • extract the shared workspace discovery helpers into a dedicated module and update internal consumers to use it directly

Notes

This fixes nested workspace patterns like apps/*/packages/*, which were previously resolved incorrectly because the old resolver stripped glob suffixes and treated the remaining path as literal.

It also makes pnpm-style exclusion patterns work during workspace package resolution.

Fixes #88 and lays the groundwork for later shared project/context resolution work.

Summary by CodeRabbit

  • Bug Fixes
    • Fixed workspace package discovery for nested glob patterns; patterns are now normalized, deduplicated, sorted and support both * and **.
    • Improved monorepo detection error handling to emit warnings with the failing parent path when configuration parsing fails.
  • Tests
    • Added tests covering pattern parsing, nested/recursive resolution, exclusions, and filtering packages with skills.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 23, 2026

Caution

Review failed

Pull request was closed or merged during review

📝 Walkthrough

Walkthrough

Extracted workspace discovery logic from setup.ts into a new workspace-patterns.ts module; implemented normalized, deduplicated, sorted workspace pattern parsing and segment-by-segment glob resolution that supports * and **; updated imports/re-exports, added tests, and added a changeset documenting the patch.

Changes

Cohort / File(s) Summary
Changeset Documentation
\.changeset/busy-peas-fail.md
Adds a changeset documenting a patch release for @tanstack/intent and describing workspace discovery fixes (nested glob support, normalization, deduplication, sorting).
New workspace patterns module & tests
packages/intent/src/workspace-patterns.ts, packages/intent/tests/workspace-patterns.test.ts
Adds workspace discovery utilities: readWorkspacePatterns, resolveWorkspacePackages, findWorkspaceRoot, findPackagesWithSkills. Normalizes patterns, expands * and ** segment-by-segment, supports ! exclusions, skips node_modules/hidden dirs, dedupes/sorts results, and includes comprehensive unit tests.
Setup refactor / re-exports
packages/intent/src/setup.ts
Removed inline workspace-resolution code; imports workspace helpers from ./workspace-patterns.js and re-exports them; adjusted monorepo detection error logging to warn with parent path on failure.
Import updates in consumer modules
packages/intent/src/cli-support.ts, packages/intent/src/scanner.ts
Updated imports to load workspace helper functions from ./workspace-patterns.js instead of ./setup.js; existing control flow and usage remain unchanged.
Test tweak
packages/intent/tests/setup.test.ts
Added vi import and a test ensuring runEditPackageJson logs an error when parent package.json is invalid.

Sequence Diagram(s)

sequenceDiagram
  participant CLI as CLI (intent)
  participant Setup as setup.ts
  participant Patterns as workspace-patterns.ts
  participant FS as Filesystem

  CLI->>Setup: run workspace-related command
  Setup->>Patterns: findWorkspaceRoot(startPath)
  Patterns->>FS: stat/read ancestor dirs for pnpm-workspace.yaml / package.json
  FS-->>Patterns: workspace config or null
  Patterns-->>Setup: workspace root or null
  Setup->>Patterns: readWorkspacePatterns(root)
  Patterns->>FS: read pnpm-workspace.yaml or package.json (workspaces)
  FS-->>Patterns: raw patterns
  Patterns->>Patterns: normalize, dedupe, sort patterns
  Setup->>Patterns: resolveWorkspacePackages(root, patterns)
  Patterns->>FS: traverse directories (expand segments, skip node_modules/hidden)
  FS-->>Patterns: directories containing package.json
  Patterns-->>Setup: sorted package directories (exclusions applied)
  Setup-->>CLI: packages list / proceed with further actions
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • KyleAMathews

Poem

"🐇 I hopped through globs both wild and deep,
trimmed the dots and slashes in a sweep,
stars and double-stars now find their lane,
packages sorted, no duplicates remain,
a joyful nibble — tidy work, tiny leap! 🥕"

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 31.25% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'fix(intent): make workspace package resolution glob-aware and reusable' clearly describes the main change: extracting workspace discovery helpers and fixing glob pattern resolution.
Description check ✅ Passed The PR description covers key changes and rationale, though the template's checklist items are not explicitly marked off.
Linked Issues check ✅ Passed The PR successfully implements all objectives from issue #88: segment-by-segment glob resolution, pattern normalization, deduplication/sorting, and support for nested patterns like apps/*/packages/*.
Out of Scope Changes check ✅ Passed All changes are directly related to the linked issue objectives: new workspace-patterns module, refactored imports, enhanced error handling, and comprehensive tests. No unrelated modifications detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch updates
⚔️ Resolve merge conflicts
  • Resolve merge conflict in branch updates

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@pkg-pr-new
Copy link

pkg-pr-new bot commented Mar 23, 2026

Open in StackBlitz

npm i https://pkg.pr.new/@tanstack/intent@92

commit: 23232a0

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
packages/intent/tests/workspace-patterns.test.ts (1)

1-8: Add one pnpm-root end-to-end case.

This suite is solid for the pure resolver, but the extracted runtime helpers now used by packages/intent/src/cli-support.ts and packages/intent/src/setup.ts are findWorkspaceRoot and findPackagesWithSkills. One test that writes pnpm-workspace.yaml, starts from a nested child directory, and asserts only packages with SKILL.md are returned would cover the moved code path much better.

Also applies to: 37-139

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/intent/tests/workspace-patterns.test.ts` around lines 1 - 8, Add an
end-to-end test in packages/intent/tests/workspace-patterns.test.ts that writes
a pnpm-workspace.yaml, creates a workspace tree with multiple package folders
(only some containing SKILL.md), starts the test from a nested child directory,
then exercises the runtime helpers findWorkspaceRoot and findPackagesWithSkills
to resolve the workspace and packages; assert that the returned package list is
based on pnpm-workspace.yaml and only includes folders that contain SKILL.md.
Ensure the test uses temporary directories
(mkdtempSync/mkdirSync/writeFileSync), changes the CWD to the nested child for
resolution, and restores cleanup with rmSync/afterEach.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/intent/src/workspace-patterns.ts`:
- Around line 72-82: resolveWorkspacePackages currently treats patterns with a
leading '!' as literal segments (e.g., '!packages' after split) which causes
exclusion patterns from pnpm-workspace.yaml to be ignored; change
resolveWorkspacePackages to first partition normalizeWorkspacePatterns(patterns)
into inclusions and exclusions (patterns starting with '!' stripped of the '!'),
call resolveWorkspacePatternSegments(root, ...) separately for each set to
populate two Sets (includedDirs and excludedDirs), then compute the final result
as includedDirs minus excludedDirs and return that sorted array; keep references
to resolveWorkspacePackages, normalizeWorkspacePatterns, and
resolveWorkspacePatternSegments when locating where to implement this logic.
- Line 1: Import binding style violates the rule by mixing a type import inline;
change the import so that existsSync, readFileSync and readdirSync remain in the
runtime import and Dirent is imported in a separate type-only statement (e.g.,
keep the first line importing { existsSync, readFileSync, readdirSync } from
'node:fs' and add a second line `import type { Dirent } from 'node:fs'`) so
functions using Dirent in workspace-patterns.ts reference the type-only import.

---

Nitpick comments:
In `@packages/intent/tests/workspace-patterns.test.ts`:
- Around line 1-8: Add an end-to-end test in
packages/intent/tests/workspace-patterns.test.ts that writes a
pnpm-workspace.yaml, creates a workspace tree with multiple package folders
(only some containing SKILL.md), starts the test from a nested child directory,
then exercises the runtime helpers findWorkspaceRoot and findPackagesWithSkills
to resolve the workspace and packages; assert that the returned package list is
based on pnpm-workspace.yaml and only includes folders that contain SKILL.md.
Ensure the test uses temporary directories
(mkdtempSync/mkdirSync/writeFileSync), changes the CWD to the nested child for
resolution, and restores cleanup with rmSync/afterEach.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d4aabfb0-1459-4abb-bef5-2f8e744c8323

📥 Commits

Reviewing files that changed from the base of the PR and between a1ecb68 and 6317916.

📒 Files selected for processing (6)
  • .changeset/busy-peas-fail.md
  • packages/intent/src/cli-support.ts
  • packages/intent/src/scanner.ts
  • packages/intent/src/setup.ts
  • packages/intent/src/workspace-patterns.ts
  • packages/intent/tests/workspace-patterns.test.ts

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
packages/intent/src/workspace-patterns.ts (1)

73-83: ⚠️ Potential issue | 🟠 Major

resolveWorkspacePackages still ignores pnpm exclusion patterns (!…).

At Line 79, patterns starting with ! are resolved as literal path segments (e.g., !packages), so excluded workspaces remain included in results.

Suggested fix
 export function resolveWorkspacePackages(
   root: string,
   patterns: Array<string>,
 ): Array<string> {
-  const dirs = new Set<string>()
-
-  for (const pattern of normalizeWorkspacePatterns(patterns)) {
-    resolveWorkspacePatternSegments(root, pattern.split('/'), dirs)
-  }
-
-  return [...dirs].sort((a, b) => a.localeCompare(b))
+  const includedDirs = new Set<string>()
+  const excludedDirs = new Set<string>()
+
+  for (const pattern of normalizeWorkspacePatterns(patterns)) {
+    if (pattern.startsWith('!')) {
+      const negated = pattern.slice(1)
+      if (negated) {
+        resolveWorkspacePatternSegments(root, negated.split('/'), excludedDirs)
+      }
+      continue
+    }
+
+    resolveWorkspacePatternSegments(root, pattern.split('/'), includedDirs)
+  }
+
+  return [...includedDirs]
+    .filter((dir) => !excludedDirs.has(dir))
+    .sort((a, b) => a.localeCompare(b))
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/intent/src/workspace-patterns.ts` around lines 73 - 83, The function
resolveWorkspacePackages currently treats pnpm-style exclusion patterns
(starting with '!') as literal segments; update the loop that iterates
normalizeWorkspacePatterns(patterns) so that when a pattern startsWith('!') you
treat it as an exclusion: call resolveWorkspacePatternSegments with the pattern
(stripping the leading '!') into a temporary Set to collect matching dirs, then
remove those matches from the main dirs Set (instead of adding them); for
non-exclusion patterns keep the existing behavior of resolving into dirs.
Reference resolveWorkspacePackages, normalizeWorkspacePatterns, and
resolveWorkspacePatternSegments when applying this change.
🧹 Nitpick comments (1)
packages/intent/src/workspace-patterns.ts (1)

145-156: Normalize start to absolute path for defensive clarity.

All current callsites already pass absolute paths, but normalizing to absolute using resolve() and replacing join(dir, '..') with dirname() improves code clarity and guards against future misuse if this function is called with relative paths elsewhere.

Suggested improvement
-import { join } from 'node:path'
+import { dirname, join, resolve } from 'node:path'
...
 export function findWorkspaceRoot(start: string): string | null {
-  let dir = start
+  let dir = resolve(start)

   while (true) {
     if (readWorkspacePatterns(dir)) {
       return dir
     }

-    const next = join(dir, '..')
+    const next = dirname(dir)
     if (next === dir) return null
     dir = next
   }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/intent/src/workspace-patterns.ts` around lines 145 - 156, Normalize
the input and use dirname traversal: in findWorkspaceRoot, call resolve(start)
up-front to ensure start is absolute, then replace the upward-traversal logic
that uses join(dir, '..') with dirname(dir) to get the parent; keep the
readWorkspacePatterns(dir) check and return dir when it matches, and return null
when dirname(dir) === dir to stop. This ensures defensive handling of relative
inputs while preserving the existing readWorkspacePatterns behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/intent/src/workspace-patterns.ts`:
- Around line 1-5: The import order in workspace-patterns.ts violates the ESLint
import/order rule because the type import "import type { Dirent } from
'node:fs'" is placed among value imports; move that line to after the other
imports (group with any other type-only imports) so all runtime imports
(existsSync, readFileSync, readdirSync, join, parseYaml, findSkillFiles) come
first and the type import appears last, preserving the same specifier and symbol
name.

---

Duplicate comments:
In `@packages/intent/src/workspace-patterns.ts`:
- Around line 73-83: The function resolveWorkspacePackages currently treats
pnpm-style exclusion patterns (starting with '!') as literal segments; update
the loop that iterates normalizeWorkspacePatterns(patterns) so that when a
pattern startsWith('!') you treat it as an exclusion: call
resolveWorkspacePatternSegments with the pattern (stripping the leading '!')
into a temporary Set to collect matching dirs, then remove those matches from
the main dirs Set (instead of adding them); for non-exclusion patterns keep the
existing behavior of resolving into dirs. Reference resolveWorkspacePackages,
normalizeWorkspacePatterns, and resolveWorkspacePatternSegments when applying
this change.

---

Nitpick comments:
In `@packages/intent/src/workspace-patterns.ts`:
- Around line 145-156: Normalize the input and use dirname traversal: in
findWorkspaceRoot, call resolve(start) up-front to ensure start is absolute,
then replace the upward-traversal logic that uses join(dir, '..') with
dirname(dir) to get the parent; keep the readWorkspacePatterns(dir) check and
return dir when it matches, and return null when dirname(dir) === dir to stop.
This ensures defensive handling of relative inputs while preserving the existing
readWorkspacePatterns behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 47d78f7d-32fa-452b-8533-5d8e0d81e9eb

📥 Commits

Reviewing files that changed from the base of the PR and between 6317916 and 29fa591.

📒 Files selected for processing (1)
  • packages/intent/src/workspace-patterns.ts

- Return null from parseWorkspacePatterns when normalized result is empty (prevents
  empty workspace configs from being treated as valid monorepo roots)
- Replace empty catch {} in setup.ts monorepo detection with console.error warning
- Add absolute-path guard to findWorkspaceRoot
- Add JSDoc to all exported functions in workspace-patterns.ts
- Add tests: pnpm-workspace.yaml reading, YAML precedence, Yarn classic format,
  null-return for non-workspace dirs, exclusion normalization, corrupted parent
  package.json warning

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Collaborator

@KyleAMathews KyleAMathews left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ran a comprehensive review (code quality, error handling, test coverage, type design, comments). The workspace patterns extraction is well done — the segment-by-segment glob resolver is a big improvement over the old regex-strip approach.

Applied fixes in 5f38aa7:

  • workspace-patterns.ts: parseWorkspacePatterns now returns null for empty normalized arrays — previously returned [] (truthy), which caused findWorkspaceRoot to incorrectly identify dirs with empty workspace configs as workspace roots
  • workspace-patterns.ts: Added absolute-path guard to findWorkspaceRoot to prevent incorrect traversal on relative paths
  • workspace-patterns.ts: Added JSDoc to all 4 exported functions (lost during extraction from setup.ts)
  • setup.ts: Replaced empty catch {} in monorepo detection with console.error warning — was silently swallowing all errors including corrupted parent package.json
  • Tests: Added 7 new test cases covering pnpm-workspace.yaml reading, YAML-over-package.json precedence, Yarn classic workspaces.packages format, null return for non-workspace dirs, empty pattern normalization, exclusion !./ normalization, and corrupted parent package.json warning

All 227 tests pass.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug: resolveWorkspacePackages fails for nested workspace patterns

2 participants