dart-use-path-package — Claude Code skill: cross-platform Dart paths
Teaches the agent to handle Dart file paths via package:path, avoiding string operations that break on Windows.
npx skills add flutter/agent-plugins --skill dart-use-path-package -a claude-codeWhat dart-use-path-package does
The skill replaces unsafe string operations like '$dir/$file', .split('/'), .endsWith('.ext'), or .replaceAll('\\', '/') with package:path methods: p.join for joining, p.split for segment parsing, p.extension for extensions, p.normalize and p.canonicalize for path normalization. The agent learns cross-platform path handling, POSIX and URL conversion for Git and manifests, compound extension processing like .tar.gz and .js.map, and safe path-to-Uri bridging.
Uses Dart list pattern matching for segment inspection, prevents Windows backslash bugs, duplicate separators, and false substring matches. Not for HTTP routing, database query strings, or non-path string processing. Critical for projects targeting Windows: string path operations silently fail where macOS and Linux let them through.
What it helps with
- join paths without duplicate slashes or Windows backslash bugs
- extract file extensions correctly without matching directories
- convert native paths to POSIX format for Git or archives
- match path segments cross-platform without breaking on backslashes
- parse compound extensions like
.tar.gzor.js.map
Example prompts for the agent
- Replace all `'$dir/$file'` with `p.join` in lib/
- Audit path operations in src/ for Windows compatibility
- Convert paths in manifest.yaml to POSIX format using `p.posix.joinAll`
How to install dart-use-path-package
1Skills CLI (Vercel)
npx skills add flutter/agent-plugins --skill dart-use-path-package -a claude-code2Manually: put the files in place
~/.claude/skills/dart-use-path-package/SKILL.mdCopy the whole skill folder; for one project use .claude/skills/dart-use-path-package/
1Skills CLI (Vercel)
npx skills add flutter/agent-plugins --skill dart-use-path-package -a codex2Manually: put the files in place
.agents/skills/dart-use-path-package/SKILL.mdFor all projects use ~/.agents/skills/
1Skills CLI (Vercel)
npx skills add flutter/agent-plugins --skill dart-use-path-package -a cursor2Manually: put the files in place
.cursor/skills/dart-use-path-package/SKILL.mdCursor also reads .agents/skills/ and .claude/skills/
1GitHub CLI
gh skill install flutter/agent-plugins dart-use-path-package2Manually: put the files in place
.github/skills/dart-use-path-package/SKILL.md1Skills CLI (Vercel)
npx skills add flutter/agent-plugins --skill dart-use-path-package -a gemini-cli2Platform CLI
gemini skills install https://github.com/flutter/agent-plugins --path skills/dart-use-path-packageManually: put the files in place
.devin/skills/dart-use-path-package/SKILL.mdThe legacy .windsurf/skills/ path still works
Skills CLI (Vercel)
npx skills add flutter/agent-plugins --skill dart-use-path-package -a opencodeClaude.ai
Zip the folder and upload it in Settings → Capabilities → Skills
From the same set14
The whole set →Similar
Questions about dart-use-path-package
How to install dart-use-path-package in Claude Code?
Run npx skills add flutter/agent-plugins --skill dart-use-path-package -a claude-code. The skill will appear in ~/.claude/skills/dart-use-path-package/.
What does dart-use-path-package do?
Teaches the agent to replace string path operations with package:path methods that work identically on Windows, macOS, and Linux. Prevents backslash bugs, duplicate separators, and false extension matches.
When to use p.normalize vs p.canonicalize?
p.normalize resolves . and .. lexically without touching the filesystem. p.canonicalize is needed to compare physical file identity across symlinks, relative roots, or case-insensitive filesystems.
Source text
SKILL.md · BSD-3-Clause
---
name: dart-use-path-package
description: >-
Cross-platform file and directory path manipulation, segment splitting, extension extraction, and context conversion using `package:path` and `package:file`. Use when writing, inspecting, joining, splitting, or refactoring file paths, directory names, or extensions, or replacing raw string path operations (`.split('/')`, `'$dir/$file'`, `.endsWith('.ext')`, `.replaceAll('\\', '/')`). Don't use for HTTP network URI routing, database query strings, or non-path string processing.
metadata:
model: models/gemini-3.1-pro-preview
last_modified: Wed, 23 Sep 2026 22:10:00 GMT
---
# Safe Cross-Platform Path Manipulation in Dart
## Contents
* [1. Core Principles & Cross-Platform Rules](#1-core-principles--cross-platform-rules)
* [2. Recommended package:path Idioms vs. String Anti-Patterns](#2-recommended-packagepath-idioms-vs-string-anti-patterns)
* [3. Bridging Native Paths to POSIX, Git, & URL Contexts](#3-bridging-native-paths-to-posix-git--url-contexts)
* [4. Mockable File Systems (`package:file` vs. Global `p.*`)](#4-mockable-file-systems-packagefile-vs-global-p)
* [5. Extensions, Compound Extensions & Stem Extraction](#5-extensions-compound-extensions--stem-extraction)
* [6. Workflows & Audit Checklist](#6-workflows--audit-checklist)
* [References & Examples](#references--examples)
---
## 1. Core Principles & Cross-Platform Rules
### Avoid Treating File Paths as Raw Strings
* Native file paths on Windows use backslashes (`\`), whereas macOS and Linux use forward slashes (`/`).
* String operations like `.contains('foo/')`, `.startsWith('foo/')`, or `.split('/')` silently fail on Windows native paths.
* String interpolation like `'$dir/$file'` injects forward slashes on Windows and produces duplicate slashes (`//`) when `$dir` ends with a trailing slash.
**Rule**: Always decompose paths into segments using `p.split(path)` before inspecting directory hierarchy or segment names, and always join path components using `p.join(...)`.
### Pragmatic Boundary Joining vs. Multi-Segment Decomposition (`p.join`)
* **Cross-Platform Libraries (Windows + POSIX)**: Pass individual path segments to `p.join(dir, 'sub', 'file.json')` so `package:path` inserts OS-native separators (`\` on Windows, `/` on POSIX) between every component.
* **POSIX-Only Tools & Static Subpath Greppability**: In codebases exclusively targeting Linux/macOS (or when joining a dynamic base path to a known static subpath), decomposing 5–6 static segments into separate arguments (`p.join(home, '.local', 'share', 'app', 'bin', 'config.json')`) causes `dart format` to wrap across 6–8 vertical lines and **destroys substring greppability** (`grep` / `code_search` for `.local/share/app/bin`).
* **Rule for POSIX Targets**: Prefer **2-argument boundary joining** (`p.join(home, '.local/share/app/bin/config.json')`). This prevents duplicate-slash bugs (`//`) at variable boundaries while preserving single-line readability and exact string searchability.
### Normalization vs. Canonicalization (`p.normalize` vs. `p.canonicalize`)
* `p.normalize(path)` resolves `.` and `..` segments purely lexically without consulting the filesystem or standardizing case.
* When deduplicating directory paths or comparing physical file identity across symlinks, relative roots, or case-insensitive filesystems, use `p.canonicalize(path)`.
### Strip Location Specifiers & Convert URIs Safely
* Strings formatted as `<path>:<line>-<col>` or `<path>:<line>` are not pure file paths. Passing them directly to `p.normalize` or `Uri.parse` causes bugs (on Windows, `Uri.parse` mistakes `C:` for a URI scheme and `:line` for a port).
* Extract the trailing `:line-col` suffix via regular expression (`RegExp(r'^(.*?):(\d+(?:-\d+)?)$')`) *before* passing the file path to `package:path`.
* **URI Boundary Conversions**: When converting between file paths and `Uri` objects, always use `p.toUri(path)` and `p.fromUri(uri)` rather than `Uri.parse(path)` or manual string concatenation.
---
## 2. Recommended package:path Idioms vs. String Anti-Patterns
### Path Joining
* **Prefer**: `p.join(dir, file)`
* **Avoid**: `'$dir/$file'` or `'a/$b'`
* **Why**: String interpolation injects `/` on Windows and creates duplicate
slashes (`//`) when `$dir` ends with a trailing separator.
### Segment Matching
* **Prefer**: `p.split(path).contains('foo')`
* **Avoid**: `path.contains('foo/')`
* **Why**: String matching fails on Windows backslashes (`foo\bar`) and produces
false positives on partial substring names (e.g. `barfoo/`).
### Root and Directory Prefixes
* **Prefer**: `if (p.split(path) case ['foo', ...])` (or `case ['foo', ...final rest]` when extracting tail segments), or `p.isWithin('foo', path)`
* **Avoid**: `path.startsWith('foo/')` or `p.split(path).first == 'foo'`
* **Why**: String prefix matching fails on Windows separators (`foo\bar`). Calling
`p.split(path).first` throws a `StateError` on empty lists and requires
separate `.skip(1)` slicing, whereas list patterns safely check non-emptiness,
match multi-segment prefixes, and optionally bind `...final rest` in a single
step. When unnormalized relative prefixes like `./foo/bar` may appear, use
`p.isWithin('foo', path)` (or `p.split(p.normalize(path))`).
### File Extensions
* **Prefer**: `p.extension(path) == '.wasm'`
* **Avoid**: `path.endsWith('.wasm')`
* **Why**: Substring suffix matching falsely matches directories (`foo.wasm/`)
or non-extension suffixes.
### Extension Slicing and Compound Extensions
* **Prefer**: `p.withoutExtension(path)` and `p.extension(path, 2)`
* **Avoid**: `path.lastIndexOf('.')` and manual `substring` slicing
* **Why**: Manual arithmetic breaks on hidden dotfiles (`.gitignore`) and
compound extensions (`.js.map`, `.tar.gz`).
### POSIX and URL Path Conversion
* **Prefer**: `p.posix.joinAll(p.split(path))` or `p.url.joinAll(p.split(path))`
* **Avoid**: `path.replaceAll(r'\', '/')`
* **Why**: Ad-hoc separator replacement fails on root drives and mixes OS
context with POSIX or URL targets.
### URI Conversion
* **Prefer**: `p.toUri(path)` and `p.fromUri(uri)`
* **Avoid**: `Uri.parse(path)` and `uri.path`
* **Why**: Direct URI parsing fails on Windows drive letters (`C:`) and leaks
percent-encoding (e.g. `%20` for spaces).
### Directory Basename Helper
* **Prefer**:
`String canonicalDirName(Directory d) => p.basename(p.normalize(d.absolute.path));`
* **Avoid**: Repeating `p.basename(p.normalize(dir.absolute.path))` inline
across files.
* **Why**: Centralizes canonical directory naming logic and reduces boilerplate.
---
## 3. Bridging Native Paths to POSIX, Git, & URL Contexts
Avoid calling `.replaceAll('\\', '/')` or `.replaceAll(r'\', '/')` to convert
OS-native paths into POSIX paths (for Git, YAML, archive manifests) or URL
segments.
**Rule**: Split the relative native path using `p.split(...)`, inspect segments
with **Dart 3 list pattern matching**, and join using `p.posix.joinAll(...)` or
`p.url.joinAll(...)`. Always call `p.relative(filePath, from: root)` first so
leading root segments (`'/'` on POSIX or `r'C:\'` on Windows) do not interfere
with relative prefix patterns:
```dart
import 'package:path/path.dart' as p;
String computeWebAssetKey(String filePath, String projectRoot) {
final relative = p.relative(filePath, from: projectRoot);
final segments = p.split(relative);
return switch (segments) {
['assets', ...] => p.posix.joinAll(segments),
_ => p.posix.joinAll(['assets', ...segments]),
};
}
```
### Git Paths and Repository Metadata
* Git repository tree objects, `.gitignore` pattern rules, `.gitattributes`,
and git-tracked symlinks strictly use POSIX forward slashes (`/`), even on
Windows.
* Inserting native Windows backslashes (`\`) into `.gitignore` or git commands
causes Git to treat `\` as an escape character rather than a directory
separator, silently breaking pattern matching.
* When generating `.gitignore` entries, repository manifests, or symlink
targets programmatically from native file paths, convert the relative native
path using `p.posix.joinAll(p.split(relativePath))` or `p.posix.join(...)`.
---
## 4. Mockable File Systems (`package:file` vs. Global `p.*`)
In codebases that use `package:file` (e.g., CLI applications or services tested
with `MemoryFileSystem`), avoid calling top-level `p.*` functions on `File` or
`Directory` paths.
* Top-level `p.*` functions bind to the *host operating system* running the test.
* If a unit test creates a `MemoryFileSystem(style: FileSystemStyle.windows)` on a Linux or macOS runner, global `p.split(file.path)` will split on `/` instead of `\`, breaking the test.
**Rule**: Always use the `Context` attached to the `FileSystem` (`file.fileSystem.path`):
```dart
import 'package:file/file.dart';
List<String> listSubdirectoryNames(Directory dir) {
final pathContext = dir.fileSystem.path;
return dir
.listSync()
.whereType<Directory>()
.map((d) => pathContext.basename(d.path))
.toList();
}
```
---
## 5. Extensions, Compound Extensions & Stem Extraction
Avoid manual `.lastIndexOf('.')` and `.substring()` arithmetic when extracting file extensions or inserting content hashes. `p.extension` natively supports multi-level extensions via its optional `level` parameter.
* **Multi-Dot Stem Nuance**: Calling `p.extension('main.dart.wasm', 2)` returns `'.dart.wasm'` because it blindly captures the last two dot-separated segments. When hashing or stripping extensions on files that may have multi-dot stems (e.g., `main.dart.wasm` vs. `main.dart.js.map`), check whether `p.extension(filename, 2)` matches a known compound extension (or `.endsWith('.map')`) before falling back to single-level `p.extension(filename)`:
```dart
import 'package:path/path.dart' as p;
String insertContentHash(String filename, String hash) {
final compoundExt = p.extension(filename, 2);
// Only use the 2-level extension for true compound suffixes (e.g., '.js.map')
final ext = compoundExt.endsWith('.map')
? compoundExt
: p.extension(filename);
final stem = filename.substring(0, filename.length - ext.length);
return '$stem.$hash$ext';
}
```
---
## 6. Workflows & Audit Checklist
### Path Refactoring Checklist
- [ ] Replace string interpolation (`'$dir/$file'`) with `p.join(dir, file)`.
- [ ] Replace `.contains('dir/')` and `.startsWith('dir/')` with `p.split(path)` list pattern checks (`case ['dir', ...final rest]`) or `p.isWithin(parent, child)`.
- [ ] Replace `.replaceAll(r'\', '/')` with `p.posix.joinAll(p.split(path))` (or `p.url.joinAll`).
- [ ] Replace `.endsWith('.ext')` on file paths with `p.extension(path) == '.ext'`.
- [ ] Replace manual dot-index slicing with `p.withoutExtension(path)` and `p.extension(path, [level])`.
- [ ] Verify that code using `package:file` accesses `fileSystem.path` instead of global `p.*`.
- [ ] Ensure Git paths, `.gitignore` entries, and symlink targets use `p.posix` forward slashes.
---
## References & Examples
* **Cross-Platform Path & POSIX Conversion Examples**: [examples/cross_platform_paths.dart](examples/cross_platform_paths.dart)
* **Mockable FileSystem Path Context Example**: [examples/file_system_context.dart](examples/file_system_context.dart)
© flutter · BSD-3-Clause · flutter/agent-plugins