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.
package:file vs. Global p.*)\), whereas macOS and Linux use forward slashes (/)..contains('foo/'), .startsWith('foo/'), or .split('/') silently fail on Windows native paths.'$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(...).
p.join)p.join(dir, 'sub', 'file.json') so package:path inserts OS-native separators (\ on Windows, / on POSIX) between every component.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).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.p.normalize vs. p.canonicalize)p.normalize(path) resolves . and .. segments purely lexically without consulting the filesystem or standardizing case.p.canonicalize(path).<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).:line-col suffix via regular expression (RegExp(r'^(.*?):(\d+(?:-\d+)?)$')) before passing the file path to package:path.Uri objects, always use p.toUri(path) and p.fromUri(uri) rather than Uri.parse(path) or manual string concatenation.p.join(dir, file)'$dir/$file' or 'a/$b'/ on Windows and creates duplicate
slashes (//) when $dir ends with a trailing separator.p.split(path).contains('foo')path.contains('foo/')foo\bar) and produces
false positives on partial substring names (e.g. barfoo/).if (p.split(path) case ['foo', ...]) (or case ['foo', ...final rest] when extracting tail segments), or p.isWithin('foo', path)path.startsWith('foo/') or p.split(path).first == 'foo'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))).p.extension(path) == '.wasm'path.endsWith('.wasm')foo.wasm/)
or non-extension suffixes.p.withoutExtension(path) and p.extension(path, 2)path.lastIndexOf('.') and manual substring slicing.gitignore) and
compound extensions (.js.map, .tar.gz).p.posix.joinAll(p.split(path)) or p.url.joinAll(p.split(path))path.replaceAll(r'\', '/')p.toUri(path) and p.fromUri(uri)Uri.parse(path) and uri.pathC:) and leaks
percent-encoding (e.g. %20 for spaces).String canonicalDirName(Directory d) => p.basename(p.normalize(d.absolute.path));p.basename(p.normalize(dir.absolute.path)) inline
across files.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:
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]),
};
}.gitignore pattern rules, .gitattributes,
and git-tracked symlinks strictly use POSIX forward slashes (/), even on
Windows.\) into .gitignore or git commands
causes Git to treat \ as an escape character rather than a directory
separator, silently breaking pattern matching..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(...).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.
p.* functions bind to the host operating system running the test.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):
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();
}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.
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):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';
}'$dir/$file') with p.join(dir, file)..contains('dir/') and .startsWith('dir/') with p.split(path) list pattern checks (case ['dir', ...final rest]) or p.isWithin(parent, child)..replaceAll(r'\', '/') with p.posix.joinAll(p.split(path)) (or p.url.joinAll)..endsWith('.ext') on file paths with p.extension(path) == '.ext'.p.withoutExtension(path) and p.extension(path, [level]).package:file accesses fileSystem.path instead of global p.*..gitignore entries, and symlink targets use p.posix forward slashes.8da8c54
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.