pattern string required The glob pattern, taken exactly as sent: nothing is trimmed, '\' is an escape character and never a path separator. Capped at 512 characters, 64 '/'-separated segments and 256 characters per segment.
Test glob patterns and .gitignore rules against paths — which rule won, at which line, and whether a negation was cancelled by an excluded parent directory.
Authoritative reference data or standards computation
Returns the verdict plus a witness: alignment when it matched, failure when it did not, and the steps the bounded matcher spent.
| Parameter | Type | Default & allowed | Description |
|---|---|---|---|
pattern
required
| string | allowed: 1 – 512 example: src/**/*.ts | The glob pattern, taken exactly as sent: nothing is trimmed, '\' is an escape character and never a path separator. Capped at 512 characters, 64 '/'-separated segments and 256 characters per segment. |
path
required
| string | allowed: 1 – 1024 example: src/lib/util.ts | The path to test, '/'-separated. May be absolute — an absolute pattern only matches an absolute path. A leading './', repeated '/' and a trailing '/' are normalized away with a warning; '.' and '..' segments are matched literally, never resolved. Capped at 1,024 characters and 64 segments. |
syntax | enum | default: glob allowed: posix | glob | extglob example: extglob | Which dialect's rules to apply. `posix` is POSIX.1-2024 XCU 2.14 pattern matching with FNM_PATHNAME semantics: no globstar, no braces, no extglob. `glob` is this endpoint's documented superset — POSIX plus a `**` segment that spans zero or more path segments plus Bash brace expansion, which is what Node tooling implements and is not a published standard. `extglob` adds the five Bash extglob operators. |
dot | boolean | default: false example: true | Whether `*`, `?`, a bracket expression or a `**` segment may match a path segment that starts with a '.'. POSIX.1-2024 XCU 2.14.3 forbids it (that is FNM_PERIOD); `dot=true` lifts the restriction. git has no such rule, so /gitignore always behaves as `dot=true` and takes no `dot` parameter. |
caseSensitive | boolean | default: true example: false | `false` compares case-insensitively by folding both sides with Unicode simple lowercase, one code point at a time. It approximates a case-insensitive filesystem; it is not a reproduction of APFS, NTFS or `core.ignoreCase`, and a character whose lowercase is more than one code point is compared unchanged. |
pattern string required The glob pattern, taken exactly as sent: nothing is trimmed, '\' is an escape character and never a path separator. Capped at 512 characters, 64 '/'-separated segments and 256 characters per segment.
path string required The path to test, '/'-separated. May be absolute — an absolute pattern only matches an absolute path. A leading './', repeated '/' and a trailing '/' are normalized away with a warning; '.' and '..' segments are matched literally, never resolved. Capped at 1,024 characters and 64 segments.
syntax enum Which dialect's rules to apply. `posix` is POSIX.1-2024 XCU 2.14 pattern matching with FNM_PATHNAME semantics: no globstar, no braces, no extglob. `glob` is this endpoint's documented superset — POSIX plus a `**` segment that spans zero or more path segments plus Bash brace expansion, which is what Node tooling implements and is not a published standard. `extglob` adds the five Bash extglob operators.
dot boolean Whether `*`, `?`, a bracket expression or a `**` segment may match a path segment that starts with a '.'. POSIX.1-2024 XCU 2.14.3 forbids it (that is FNM_PERIOD); `dot=true` lifts the restriction. git has no such rule, so /gitignore always behaves as `dot=true` and takes no `dot` parameter.
caseSensitive boolean `false` compares case-insensitively by folding both sides with Unicode simple lowercase, one code point at a time. It approximates a case-insensitive filesystem; it is not a reproduction of APFS, NTFS or `core.ignoreCase`, and a character whose lowercase is more than one code point is compared unchanged.
| Parameter | Type | Default & allowed | Description |
|---|---|---|---|
fields | list | example: matched,syntax | Return only these fields (comma-separated). Mutually exclusive with 'exclude'. |
exclude | list | example: steps | Return all fields except these (comma-separated). |
pretty | boolean | default: false example: true | Pretty-print the JSON response. |
unwrap | boolean | default: false example: true | Drop the envelope: return the raw array/object without data/meta wrapper. |
fields list Return only these fields (comma-separated). Mutually exclusive with 'exclude'.
exclude list Return all fields except these (comma-separated).
pretty boolean Pretty-print the JSON response.
unwrap boolean Drop the envelope: return the raw array/object without data/meta wrapper.
| Field | Type | Description | Example |
|---|---|---|---|
matched | boolean | Whether the pattern matches the path under the resolved dialect. | true |
syntax | string | The dialect that was applied: posix, glob or extglob. | glob |
pattern | string | The pattern exactly as supplied — never trimmed, never rewritten. | src/**/*.ts |
path | string | The path that was actually matched, after the documented normalization: a leading './' dropped, repeated '/' collapsed and a trailing '/' removed. Each of those changes also emits a warning. Dot segments are never resolved. | src/lib/util.ts |
matchedPattern nullable | string | The brace expansion that matched, first in Bash expansion order. Equal to `pattern` when it contains no braces; null when nothing matched. | src/**/*.ts |
expandedPatterns | string[] | Every brace expansion in Bash left-to-right order — a single entry when the pattern has no braces, or when the dialect has no brace expansion at all. | ["src/**/*.ts"] |
pathSegments | string[] | The path split on '/'. An absolute path yields a leading empty segment, which only a pattern with a leading '/' can match. | ["src","lib","util.ts"] |
alignment nullable | object[] | The witness, non-null only when `matched`: one entry per pattern segment as `{ patternSegment, kind: literal|wildcard|globstar, consumed }`. Every non-globstar entry consumes exactly one path segment, and joining every `consumed` with '/' reproduces `path`. Where several alignments exist, the one that gives each globstar the fewest segments (leftmost globstar first) is returned. | [{"patternSegment":"src","kind":"literal","consumed":["src"]},{"patternSegment":"**","kind":"globstar","consumed":["lib"]},{"patternSegment":"*.ts","kind":"wildcard","consumed":["util.ts"]}] |
failure nullable | object | Non-null only when nothing matched: `{ patternSegmentIndex, patternSegment, pathSegmentsMatched, reason }` — the first pattern segment no prefix of the path can reach, and a one-sentence reason. With braces, it describes `expandedPatterns[0]`. | {"patternSegmentIndex":0,"patternSegment":"*.ts","pathSegmentsMatched":0,"reason":"The pattern segment '*.ts' does not match the path segment 'src', and it can never reach across a '/'."} |
steps | integer | Dynamic-program cell updates this match cost, against the documented budget of 400,000 per request. Published so an adversarial pattern is measurable rather than mysterious. | 12 |
matched boolean Whether the pattern matches the path under the resolved dialect.
example: true
syntax string The dialect that was applied: posix, glob or extglob.
example: glob
pattern string The pattern exactly as supplied — never trimmed, never rewritten.
example: src/**/*.ts
path string The path that was actually matched, after the documented normalization: a leading './' dropped, repeated '/' collapsed and a trailing '/' removed. Each of those changes also emits a warning. Dot segments are never resolved.
example: src/lib/util.ts
matchedPattern string nullable The brace expansion that matched, first in Bash expansion order. Equal to `pattern` when it contains no braces; null when nothing matched.
example: src/**/*.ts
expandedPatterns string[] Every brace expansion in Bash left-to-right order — a single entry when the pattern has no braces, or when the dialect has no brace expansion at all.
example: ["src/**/*.ts"]
pathSegments string[] The path split on '/'. An absolute path yields a leading empty segment, which only a pattern with a leading '/' can match.
example: ["src","lib","util.ts"]
alignment object[] nullable The witness, non-null only when `matched`: one entry per pattern segment as `{ patternSegment, kind: literal|wildcard|globstar, consumed }`. Every non-globstar entry consumes exactly one path segment, and joining every `consumed` with '/' reproduces `path`. Where several alignments exist, the one that gives each globstar the fewest segments (leftmost globstar first) is returned.
example: [{"patternSegment":"src","kind":"literal","consumed":["src"]},{"patternSegment":"**","kind":"globstar","consumed":["lib"]},{"patternSegment":"*.ts","kind":"wildcard","consumed":["util.ts"]}]
failure object nullable Non-null only when nothing matched: `{ patternSegmentIndex, patternSegment, pathSegmentsMatched, reason }` — the first pattern segment no prefix of the path can reach, and a one-sentence reason. With braces, it describes `expandedPatterns[0]`.
example: {"patternSegmentIndex":0,"patternSegment":"*.ts","pathSegmentsMatched":0,"reason":"The pattern segment '*.ts' does not match the path segment 'src', and it can never reach across a '/'."}
steps integer Dynamic-program cell updates this match cost, against the documented budget of 400,000 per request. Published so an adversarial pattern is measurable rather than mysterious.
example: 12
/api/glob-match/test?pattern=src/**/*.ts&path=src/lib/util.ts {
"data": {
"matched": true,
"syntax": "glob",
"pattern": "src/**/*.ts",
"path": "src/lib/util.ts",
"matchedPattern": "src/**/*.ts",
"expandedPatterns": [
"src/**/*.ts"
],
"pathSegments": [
"src",
"lib",
"util.ts"
],
"alignment": [
{
"patternSegment": "src",
"kind": "literal",
"consumed": [
"src"
]
},
{
"patternSegment": "**",
"kind": "globstar",
"consumed": [
"lib"
]
},
{
"patternSegment": "*.ts",
"kind": "wildcard",
"consumed": [
"util.ts"
]
}
],
"failure": null,
"steps": 16
…
"generatedAt": "2026-07-30T15:14:08.000Z"
}
} /api/glob-match/test?pattern=*.ts&path=src/lib/util.ts {
"data": {
"matched": false,
"syntax": "glob",
"pattern": "*.ts",
"path": "src/lib/util.ts",
"matchedPattern": null,
"expandedPatterns": [
"*.ts"
],
"pathSegments": [
"src",
"lib",
"util.ts"
],
"alignment": null,
"failure": {
"patternSegmentIndex": 0,
"patternSegment": "*.ts",
"pathSegmentsMatched": 0,
"reason": "The pattern segment '*.ts' does not match the path segment 'src', and it can never reach across a '/'."
},
"steps": 4
},
"meta": {
"endpoint": "glob-match",
"route": "/test",
"params": {
"pattern": "*.ts",
"path": "src/lib/util.ts",
"syntax": "glob",
"dot": false,
"caseSensitive": true
},
"generatedAt": "2026-07-30T15:14:08.000Z"
}
} /api/glob-match/test?pattern=src/**/*.ts&path=src/a/b/c.ts&syntax=posix {
"data": {
"matched": false,
"syntax": "posix",
"pattern": "src/**/*.ts",
"path": "src/a/b/c.ts",
"matchedPattern": null,
"expandedPatterns": [
"src/**/*.ts"
],
"pathSegments": [
"src",
"a",
"b",
"c.ts"
],
"alignment": null,
"failure": {
"patternSegmentIndex": 2,
"patternSegment": "*.ts",
"pathSegmentsMatched": 2,
"reason": "The pattern segment '*.ts' does not match the path segment 'b', and it can never reach across a '/'."
},
"steps": 8
},
"meta": {
"endpoint": "glob-match",
"route": "/test",
"params": {
"pattern": "src/**/*.ts",
"path": "src/a/b/c.ts",
"syntax": "posix",
"dot": false,
"caseSensitive": true
},
"generatedAt": "2026-07-30T15:14:08.000Z",
"warnings": [
"'**' is not alone in its path segment, so it behaves as a single '*' — gitignore(5) puts it plainly: \"Other consecutive asterisks are considered regular asterisks\".",
"POSIX.1-2024 defines no globstar, so '**' is two asterisks and behaves exactly as a single '*' — it never crosses a '/'. Pass syntax=glob for zero-or-more-segments matching."
]
}
} /api/glob-match/test?pattern=**/!(*.d).ts&path=src/types.d.ts&syntax=extglob {
"data": {
"matched": false,
"syntax": "extglob",
"pattern": "**/!(*.d).ts",
"path": "src/types.d.ts",
"matchedPattern": null,
"expandedPatterns": [
"**/!(*.d).ts"
],
"pathSegments": [
"src",
"types.d.ts"
],
"alignment": null,
"failure": {
"patternSegmentIndex": 1,
"patternSegment": "!(*.d).ts",
"pathSegmentsMatched": 2,
"reason": "The path only has 2 segments, so there is nothing left for the pattern segment '!(*.d).ts' to match."
},
"steps": 80
},
"meta": {
"endpoint": "glob-match",
"route": "/test",
"params": {
"pattern": "**/!(*.d).ts",
"path": "src/types.d.ts",
"syntax": "extglob",
"dot": false,
"caseSensitive": true
},
"generatedAt": "2026-07-30T15:14:08.000Z"
}
} /api/glob-match/test?pattern=**/*.json&path=.config/settings.json&dot=true {
"data": {
"matched": true,
"syntax": "glob",
"pattern": "**/*.json",
"path": ".config/settings.json",
"matchedPattern": "**/*.json",
"expandedPatterns": [
"**/*.json"
],
"pathSegments": [
".config",
"settings.json"
],
"alignment": [
{
"patternSegment": "**",
"kind": "globstar",
"consumed": [
".config"
]
},
{
"patternSegment": "*.json",
"kind": "wildcard",
"consumed": [
"settings.json"
]
}
],
"failure": null,
"steps": 12
},
"meta": {
"endpoint": "glob-match",
"route": "/test",
"params": {
"pattern": "**/*.json",
"path": ".config/settings.json",
"syntax": "glob",
…
"generatedAt": "2026-07-30T15:14:08.000Z"
}
} One row per supplied path. matching genuinely filters the rows, and count is only a limit on what is returned.
| Parameter | Type | Default & allowed | Description |
|---|---|---|---|
pattern
required
| string | allowed: 1 – 512 example: src/**/*.ts | The glob pattern, taken exactly as sent: nothing is trimmed, '\' is an escape character and never a path separator. Capped at 512 characters, 64 '/'-separated segments and 256 characters per segment. |
paths
required
| list | example: src/a.ts,src/a/b.ts,src/a.d.ts,README.md | Comma-separated paths to test — at most 100 entries, 256 characters each and 4,000 characters in total. Each entry is trimmed and empty entries are dropped, so a path containing a comma or a meaningful leading/trailing space cannot be tested here — use /test for that one path. |
syntax | enum | default: glob allowed: posix | glob | extglob example: extglob | Which dialect's rules to apply. `posix` is POSIX.1-2024 XCU 2.14 pattern matching with FNM_PATHNAME semantics: no globstar, no braces, no extglob. `glob` is this endpoint's documented superset — POSIX plus a `**` segment that spans zero or more path segments plus Bash brace expansion, which is what Node tooling implements and is not a published standard. `extglob` adds the five Bash extglob operators. |
dot | boolean | default: false example: true | Whether `*`, `?`, a bracket expression or a `**` segment may match a path segment that starts with a '.'. POSIX.1-2024 XCU 2.14.3 forbids it (that is FNM_PERIOD); `dot=true` lifts the restriction. git has no such rule, so /gitignore always behaves as `dot=true` and takes no `dot` parameter. |
caseSensitive | boolean | default: true example: false | `false` compares case-insensitively by folding both sides with Unicode simple lowercase, one code point at a time. It approximates a case-insensitive filesystem; it is not a reproduction of APFS, NTFS or `core.ignoreCase`, and a character whose lowercase is more than one code point is compared unchanged. |
matching | enum | default: all allowed: all | matched | unmatched example: unmatched | Which rows to return: every path, only the ones that matched, or only the ones that did not. Zero rows is an empty `data` array plus a warning naming the filter — never a silent empty response. |
pattern string required The glob pattern, taken exactly as sent: nothing is trimmed, '\' is an escape character and never a path separator. Capped at 512 characters, 64 '/'-separated segments and 256 characters per segment.
paths list required Comma-separated paths to test — at most 100 entries, 256 characters each and 4,000 characters in total. Each entry is trimmed and empty entries are dropped, so a path containing a comma or a meaningful leading/trailing space cannot be tested here — use /test for that one path.
syntax enum Which dialect's rules to apply. `posix` is POSIX.1-2024 XCU 2.14 pattern matching with FNM_PATHNAME semantics: no globstar, no braces, no extglob. `glob` is this endpoint's documented superset — POSIX plus a `**` segment that spans zero or more path segments plus Bash brace expansion, which is what Node tooling implements and is not a published standard. `extglob` adds the five Bash extglob operators.
dot boolean Whether `*`, `?`, a bracket expression or a `**` segment may match a path segment that starts with a '.'. POSIX.1-2024 XCU 2.14.3 forbids it (that is FNM_PERIOD); `dot=true` lifts the restriction. git has no such rule, so /gitignore always behaves as `dot=true` and takes no `dot` parameter.
caseSensitive boolean `false` compares case-insensitively by folding both sides with Unicode simple lowercase, one code point at a time. It approximates a case-insensitive filesystem; it is not a reproduction of APFS, NTFS or `core.ignoreCase`, and a character whose lowercase is more than one code point is compared unchanged.
matching enum Which rows to return: every path, only the ones that matched, or only the ones that did not. Zero rows is an empty `data` array plus a warning naming the filter — never a silent empty response.
| Parameter | Type | Default & allowed | Description |
|---|---|---|---|
count | int | default: all matches allowed: 1 – 100 example: 3 | Limit the number of returned records (1–100). Defaults to all matches. |
fields | list | example: path,matched | Return only these fields (comma-separated). Mutually exclusive with 'exclude'. |
exclude | list | example: failedAtSegment | Return all fields except these (comma-separated). |
format | enum | default: json allowed: json | ndjson | csv example: csv | Response format: json envelope, ndjson (one record per line) or csv. |
pretty | boolean | default: false example: true | Pretty-print the JSON response. |
unwrap | boolean | default: false example: true | Drop the envelope: return the raw array/object without data/meta wrapper. |
count int Limit the number of returned records (1–100). Defaults to all matches.
fields list Return only these fields (comma-separated). Mutually exclusive with 'exclude'.
exclude list Return all fields except these (comma-separated).
format enum Response format: json envelope, ndjson (one record per line) or csv.
pretty boolean Pretty-print the JSON response.
unwrap boolean Drop the envelope: return the raw array/object without data/meta wrapper.
| Field | Type | Description | Example |
|---|---|---|---|
path | string | The path as matched, after the same normalization /test applies. | src/a.ts |
matched | boolean | Whether the pattern matches this path. | true |
matchedPattern nullable | string | The brace expansion that matched this path, or null when none did. | src/**/*.ts |
failedAtSegment nullable | integer | Index of the first pattern segment no prefix of this path can reach; null when the path matched. Call /test on the path for the full reason and witness. | null |
path string The path as matched, after the same normalization /test applies.
example: src/a.ts
matched boolean Whether the pattern matches this path.
example: true
matchedPattern string nullable The brace expansion that matched this path, or null when none did.
example: src/**/*.ts
failedAtSegment integer nullable Index of the first pattern segment no prefix of this path can reach; null when the path matched. Call /test on the path for the full reason and witness.
example: null
/api/glob-match/filter?pattern=src/**/*.ts&paths=src/a.ts,src/a/b.ts,src/a.d.ts,README.md,test/a.ts {
"data": [
{
"path": "src/a.ts",
"matched": true,
"matchedPattern": "src/**/*.ts",
"failedAtSegment": null
},
{
"path": "src/a/b.ts",
"matched": true,
"matchedPattern": "src/**/*.ts",
"failedAtSegment": null
},
{
"path": "src/a.d.ts",
"matched": true,
"matchedPattern": "src/**/*.ts",
"failedAtSegment": null
},
{
"path": "README.md",
"matched": false,
"matchedPattern": null,
"failedAtSegment": 0
},
{
"path": "test/a.ts",
"matched": false,
"matchedPattern": null,
"failedAtSegment": 0
}
],
"meta": {
"endpoint": "glob-match",
"route": "/filter",
"count": 5,
"params": {
"pattern": "src/**/*.ts",
"paths": [
…
"generatedAt": "2026-07-30T15:14:08.000Z"
}
} /api/glob-match/filter?pattern=**/*.test.ts&paths=a.test.ts,b.ts,src/c.test.ts&matching=unmatched {
"data": [
{
"path": "b.ts",
"matched": false,
"matchedPattern": null,
"failedAtSegment": 1
}
],
"meta": {
"endpoint": "glob-match",
"route": "/filter",
"count": 1,
"params": {
"pattern": "**/*.test.ts",
"paths": [
"a.test.ts",
"b.ts",
"src/c.test.ts"
],
"syntax": "glob",
"dot": false,
"caseSensitive": true,
"matching": "unmatched"
},
"generatedAt": "2026-07-30T15:14:08.000Z"
}
} /api/glob-match/filter?pattern={src,test}/**/*.ts&paths=src/a.ts,test/b.ts,docs/c.ts {
"data": [
{
"path": "src/a.ts",
"matched": true,
"matchedPattern": "src/**/*.ts",
"failedAtSegment": null
},
{
"path": "test/b.ts",
"matched": true,
"matchedPattern": "test/**/*.ts",
"failedAtSegment": null
},
{
"path": "docs/c.ts",
"matched": false,
"matchedPattern": null,
"failedAtSegment": 0
}
],
"meta": {
"endpoint": "glob-match",
"route": "/filter",
"count": 3,
"params": {
"pattern": "{src,test}/**/*.ts",
"paths": [
"src/a.ts",
"test/b.ts",
"docs/c.ts"
],
"syntax": "glob",
"dot": false,
"caseSensitive": true,
"matching": "all"
},
"generatedAt": "2026-07-30T15:14:08.000Z"
}
} Evaluates a pasted .gitignore against one repository-relative path with git's last-match-wins precedence, and reports the excluded ancestor directory when a negation cannot take effect.
| Parameter | Type | Default & allowed | Description |
|---|---|---|---|
rules
required
| string | allowed: ≤ 8000 example: build/
!build/keep.txt | The .gitignore text itself — never fetched, never truncated. Newlines travel as %0A. A UTF-8 byte order mark is skipped and CRLF line endings are handled, exactly as git does. Capped at 8,000 characters, 500 lines and 500 usable rules, because a truncated rule file gives a wrong verdict. |
path
required
| string | allowed: 1 – 1024 example: build/keep.txt | Repository-root-relative path as git stores it: '/'-separated, no leading '/', no '.' or '..' segments. A trailing '/' is removed and, unless you passed `isDirectory` yourself, sets it to true. |
isDirectory | boolean | default: false example: true | Whether the final component is a directory. It cannot be inferred from the string and it changes the answer: a `build/` rule matches the directory `build` and never a file named `build` (verified against git). Ancestor components are always evaluated as directories. |
caseSensitive | boolean | default: true example: false | `false` approximates git's `core.ignoreCase` by folding ASCII bytes only, which is what git's own byte-wise folding does. It is an approximation of a case-insensitive checkout, not a reproduction of one. |
rules string required The .gitignore text itself — never fetched, never truncated. Newlines travel as %0A. A UTF-8 byte order mark is skipped and CRLF line endings are handled, exactly as git does. Capped at 8,000 characters, 500 lines and 500 usable rules, because a truncated rule file gives a wrong verdict.
path string required Repository-root-relative path as git stores it: '/'-separated, no leading '/', no '.' or '..' segments. A trailing '/' is removed and, unless you passed `isDirectory` yourself, sets it to true.
isDirectory boolean Whether the final component is a directory. It cannot be inferred from the string and it changes the answer: a `build/` rule matches the directory `build` and never a file named `build` (verified against git). Ancestor components are always evaluated as directories.
caseSensitive boolean `false` approximates git's `core.ignoreCase` by folding ASCII bytes only, which is what git's own byte-wise folding does. It is an approximation of a case-insensitive checkout, not a reproduction of one.
| Parameter | Type | Default & allowed | Description |
|---|---|---|---|
fields | list | example: ignored,path | Return only these fields (comma-separated). Mutually exclusive with 'exclude'. |
exclude | list | example: steps | Return all fields except these (comma-separated). |
pretty | boolean | default: false example: true | Pretty-print the JSON response. |
unwrap | boolean | default: false example: true | Drop the envelope: return the raw array/object without data/meta wrapper. |
fields list Return only these fields (comma-separated). Mutually exclusive with 'exclude'.
exclude list Return all fields except these (comma-separated).
pretty boolean Pretty-print the JSON response.
unwrap boolean Drop the envelope: return the raw array/object without data/meta wrapper.
| Field | Type | Description | Example |
|---|---|---|---|
ignored | boolean | Whether git would ignore the path: true when the path's own LAST matching rule excludes it, or when any ancestor directory is excluded (git never descends into an excluded directory). | true |
path | string | The normalized repository-relative path that was evaluated. | build/keep.txt |
isDirectory | boolean | The resolved flag. A trailing '/' on `path` sets it to true when you did not pass it explicitly, and `meta.params` echoes the resolved value. | false |
reason | string | Why: `rule` (the path's own last matching rule decided it — that rule may be a negation, in which case `ignored` is false), `parent-directory` (an ancestor is excluded, so git never looks inside), or `no-match` (no rule matches the path or any ancestor). | parent-directory |
matchedRule nullable | object | The rule that decided the verdict: `{ lineNumber, pattern, effectivePattern, negated, anchored, directoryOnly, matchedAgainst, appliedTo }`. `pattern` is the line as written (git's unescaped-trailing-space rule applied); `effectivePattern` is what was actually matched, with the '!', the leading '/' and the trailing '/' removed; `appliedTo` is the path or the ancestor directory the rule matched. Null when nothing matched. | {"lineNumber":1,"pattern":"build/","effectivePattern":"build","negated":false,"anchored":false,"directoryOnly":true,"matchedAgainst":"basename","appliedTo":"build"} |
competingRules | object[] | Every rule that matches THE PATH ITSELF, in file order, in the same shape minus `appliedTo`. gitignore is last-match-wins, so the LAST entry is the path's own decision — the opposite of the robots.txt endpoint, where the first entry wins. | [{"lineNumber":1,"pattern":"build/","effectivePattern":"build","negated":false,"anchored":false,"directoryOnly":true,"matchedAgainst":"basename"},{"lineNumber":2,"pattern":"!build/keep.txt","effectivePattern":"build/keep.txt","negated":true,"anchored":true,"directoryOnly":false,"matchedAgainst":"path"}] |
excludedByParent nullable | object | `{ directory, rule }` naming the shallowest excluded ancestor — the reason a '!' rule can sit in `competingRules` and still lose, which is the single most common .gitignore surprise. Null when no ancestor is excluded. | {"directory":"build","rule":{"lineNumber":1,"pattern":"build/","effectivePattern":"build","negated":false,"anchored":false,"directoryOnly":true,"matchedAgainst":"basename"}} |
ancestorDecisions | object[] | One entry per ancestor directory, root first: `{ directory, ignored, rule }`, where `ignored` is that directory's own last-match verdict evaluated on its own and `rule` is nullable. The shallowest `ignored: true` is the one that decides. | [{"directory":"build","ignored":true,"rule":{"lineNumber":1,"pattern":"build/","effectivePattern":"build","negated":false,"anchored":false,"directoryOnly":true,"matchedAgainst":"basename"}}] |
ruleCount | integer | Usable pattern lines parsed — blank lines, '#' comments and lines that carry no pattern are excluded. | 2 |
steps | integer | Dynamic-program cell updates the whole evaluation cost (every rule against the path and against each ancestor), against the documented budget of 400,000 per request. | 24 |
ignored boolean Whether git would ignore the path: true when the path's own LAST matching rule excludes it, or when any ancestor directory is excluded (git never descends into an excluded directory).
example: true
path string The normalized repository-relative path that was evaluated.
example: build/keep.txt
isDirectory boolean The resolved flag. A trailing '/' on `path` sets it to true when you did not pass it explicitly, and `meta.params` echoes the resolved value.
example: false
reason string Why: `rule` (the path's own last matching rule decided it — that rule may be a negation, in which case `ignored` is false), `parent-directory` (an ancestor is excluded, so git never looks inside), or `no-match` (no rule matches the path or any ancestor).
example: parent-directory
matchedRule object nullable The rule that decided the verdict: `{ lineNumber, pattern, effectivePattern, negated, anchored, directoryOnly, matchedAgainst, appliedTo }`. `pattern` is the line as written (git's unescaped-trailing-space rule applied); `effectivePattern` is what was actually matched, with the '!', the leading '/' and the trailing '/' removed; `appliedTo` is the path or the ancestor directory the rule matched. Null when nothing matched.
example: {"lineNumber":1,"pattern":"build/","effectivePattern":"build","negated":false,"anchored":false,"directoryOnly":true,"matchedAgainst":"basename","appliedTo":"build"}
competingRules object[] Every rule that matches THE PATH ITSELF, in file order, in the same shape minus `appliedTo`. gitignore is last-match-wins, so the LAST entry is the path's own decision — the opposite of the robots.txt endpoint, where the first entry wins.
example: [{"lineNumber":1,"pattern":"build/","effectivePattern":"build","negated":false,"anchored":false,"directoryOnly":true,"matchedAgainst":"basename"},{"lineNumber":2,"pattern":"!build/keep.txt","effectivePattern":"build/keep.txt","negated":true,"anchored":true,"directoryOnly":false,"matchedAgainst":"path"}]
excludedByParent object nullable `{ directory, rule }` naming the shallowest excluded ancestor — the reason a '!' rule can sit in `competingRules` and still lose, which is the single most common .gitignore surprise. Null when no ancestor is excluded.
example: {"directory":"build","rule":{"lineNumber":1,"pattern":"build/","effectivePattern":"build","negated":false,"anchored":false,"directoryOnly":true,"matchedAgainst":"basename"}}
ancestorDecisions object[] One entry per ancestor directory, root first: `{ directory, ignored, rule }`, where `ignored` is that directory's own last-match verdict evaluated on its own and `rule` is nullable. The shallowest `ignored: true` is the one that decides.
example: [{"directory":"build","ignored":true,"rule":{"lineNumber":1,"pattern":"build/","effectivePattern":"build","negated":false,"anchored":false,"directoryOnly":true,"matchedAgainst":"basename"}}]
ruleCount integer Usable pattern lines parsed — blank lines, '#' comments and lines that carry no pattern are excluded.
example: 2
steps integer Dynamic-program cell updates the whole evaluation cost (every rule against the path and against each ancestor), against the documented budget of 400,000 per request.
example: 24
/api/glob-match/gitignore?rules=build/%0A!build/keep.txt&path=build/keep.txt {
"data": {
"ignored": true,
"path": "build/keep.txt",
"isDirectory": false,
"reason": "parent-directory",
"matchedRule": {
"lineNumber": 1,
"pattern": "build/",
"effectivePattern": "build",
"negated": false,
"anchored": false,
"directoryOnly": true,
"matchedAgainst": "basename",
"appliedTo": "build"
},
"competingRules": [
{
"lineNumber": 2,
"pattern": "!build/keep.txt",
"effectivePattern": "build/keep.txt",
"negated": true,
"anchored": true,
"directoryOnly": false,
"matchedAgainst": "path"
}
],
"excludedByParent": {
"directory": "build",
"rule": {
"lineNumber": 1,
"pattern": "build/",
"effectivePattern": "build",
"negated": false,
"anchored": false,
"directoryOnly": true,
"matchedAgainst": "basename"
}
},
"ancestorDecisions": [
…
]
}
} /api/glob-match/gitignore?rules=/*%0A!/src&path=src/index.ts {
"data": {
"ignored": false,
"path": "src/index.ts",
"isDirectory": false,
"reason": "no-match",
"matchedRule": null,
"competingRules": [],
"excludedByParent": null,
"ancestorDecisions": [
{
"directory": "src",
"ignored": false,
"rule": {
"lineNumber": 2,
"pattern": "!/src",
"effectivePattern": "src",
"negated": true,
"anchored": true,
"directoryOnly": false,
"matchedAgainst": "path"
}
}
],
"ruleCount": 2,
"steps": 8
},
"meta": {
"endpoint": "glob-match",
"route": "/gitignore",
"params": {
"rules": "/*\n!/src",
"path": "src/index.ts",
"isDirectory": false,
"caseSensitive": true
},
"generatedAt": "2026-07-30T15:14:08.000Z"
}
} /api/glob-match/gitignore?rules=build/&path=build&isDirectory=false {
"data": {
"ignored": false,
"path": "build",
"isDirectory": false,
"reason": "no-match",
"matchedRule": null,
"competingRules": [],
"excludedByParent": null,
"ancestorDecisions": [],
"ruleCount": 1,
"steps": 0
},
"meta": {
"endpoint": "glob-match",
"route": "/gitignore",
"params": {
"rules": "build/",
"path": "build",
"isDirectory": false,
"caseSensitive": true
},
"generatedAt": "2026-07-30T15:14:08.000Z"
}
} /api/glob-match/gitignore?rules=abc/**&path=abc&isDirectory=true {
"data": {
"ignored": false,
"path": "abc",
"isDirectory": true,
"reason": "no-match",
"matchedRule": null,
"competingRules": [],
"excludedByParent": null,
"ancestorDecisions": [],
"ruleCount": 1,
"steps": 4
},
"meta": {
"endpoint": "glob-match",
"route": "/gitignore",
"params": {
"rules": "abc/**",
"path": "abc",
"isDirectory": true,
"caseSensitive": true
},
"generatedAt": "2026-07-30T15:14:08.000Z"
}
} /api/glob-match/gitignore?rules=*.log%0A!important.log&path=important.log {
"data": {
"ignored": false,
"path": "important.log",
"isDirectory": false,
"reason": "rule",
"matchedRule": {
"lineNumber": 2,
"pattern": "!important.log",
"effectivePattern": "important.log",
"negated": true,
"anchored": false,
"directoryOnly": false,
"matchedAgainst": "basename",
"appliedTo": "important.log"
},
"competingRules": [
{
"lineNumber": 1,
"pattern": "*.log",
"effectivePattern": "*.log",
"negated": false,
"anchored": false,
"directoryOnly": false,
"matchedAgainst": "basename"
},
{
"lineNumber": 2,
"pattern": "!important.log",
"effectivePattern": "important.log",
"negated": true,
"anchored": false,
"directoryOnly": false,
"matchedAgainst": "basename"
}
],
"excludedByParent": null,
"ancestorDecisions": [],
"ruleCount": 2,
"steps": 19
…
"generatedAt": "2026-07-30T15:14:08.000Z"
}
} Per-segment, per-token explanation for the dialect you name, plus the list of constructs that dialect does not interpret at all. Under a dialect with brace expansion the tokens describe explainedPattern — the first expansion, because that is what gets matched.
| Parameter | Type | Default & allowed | Description |
|---|---|---|---|
pattern
required
| string | allowed: 1 – 512 example: src/**/*.{ts,tsx} | The glob pattern, taken exactly as sent: nothing is trimmed, '\' is an escape character and never a path separator. Capped at 512 characters, 64 '/'-separated segments and 256 characters per segment. |
syntax | enum | default: glob allowed: posix | glob | extglob | gitignore example: gitignore | Which dialect's rules to apply. `posix` is POSIX.1-2024 XCU 2.14 pattern matching with FNM_PATHNAME semantics: no globstar, no braces, no extglob. `glob` is this endpoint's documented superset — POSIX plus a `**` segment that spans zero or more path segments plus Bash brace expansion, which is what Node tooling implements and is not a published standard. `extglob` adds the five Bash extglob operators. `gitignore` reads the text as one .gitignore line: a leading '!' negates, a trailing '/' means directory-only, and matching runs over UTF-8 bytes. |
pattern string required The glob pattern, taken exactly as sent: nothing is trimmed, '\' is an escape character and never a path separator. Capped at 512 characters, 64 '/'-separated segments and 256 characters per segment.
syntax enum Which dialect's rules to apply. `posix` is POSIX.1-2024 XCU 2.14 pattern matching with FNM_PATHNAME semantics: no globstar, no braces, no extglob. `glob` is this endpoint's documented superset — POSIX plus a `**` segment that spans zero or more path segments plus Bash brace expansion, which is what Node tooling implements and is not a published standard. `extglob` adds the five Bash extglob operators. `gitignore` reads the text as one .gitignore line: a leading '!' negates, a trailing '/' means directory-only, and matching runs over UTF-8 bytes.
| Parameter | Type | Default & allowed | Description |
|---|---|---|---|
fields | list | example: syntax,pattern | Return only these fields (comma-separated). Mutually exclusive with 'exclude'. |
exclude | list | example: expandedCount | Return all fields except these (comma-separated). |
pretty | boolean | default: false example: true | Pretty-print the JSON response. |
unwrap | boolean | default: false example: true | Drop the envelope: return the raw array/object without data/meta wrapper. |
fields list Return only these fields (comma-separated). Mutually exclusive with 'exclude'.
exclude list Return all fields except these (comma-separated).
pretty boolean Pretty-print the JSON response.
unwrap boolean Drop the envelope: return the raw array/object without data/meta wrapper.
| Field | Type | Description | Example |
|---|---|---|---|
syntax | string | The dialect the explanation is written for: posix, glob, extglob or gitignore. | gitignore |
pattern | string | The pattern exactly as supplied. | !/build/** |
explainedPattern | string | The pattern `segments` actually describes, which is `pattern` itself unless one of two documented things applies. Under `glob`/`extglob` with a correctly-formed brace group it is the FIRST of the `expandedCount` expansions — the one `/test` tries first — because braces are expanded BEFORE matching, so tokens of the raw text would describe a pattern that is never matched. Under `syntax=gitignore` it is the rule body, identical to `gitignore.effectivePattern`: the leading '!' and the anchoring or directory-only '/' are reported in the `gitignore` object instead of being tokenized. A warning names the reason whenever this differs from `pattern`; `/expand` lists every expansion. | build/** |
segments | object[] | One entry per '/'-separated segment of `explainedPattern`: `{ index, text, kind: literal|wildcard|globstar, tokens }`, where each token is `{ text, kind: literal|escape|any|star|bracket|class|extglob, meaning }`. Every `meaning` is a statement about the dialect named in `syntax`, so it is true of the pattern this endpoint would actually match. | [{"index":0,"text":"build","kind":"literal","tokens":[{"text":"build","kind":"literal","meaning":"Matches the literal text 'build'."}]}] |
features | string[] | Which constructs the dialect actually interprets in this pattern, from a closed vocabulary: globstar, braces, sequence, bracket-expression, character-class, escape, extglob, negation, anchor, directory-only. Collected across every brace expansion, so a bracket that only appears in the second expansion is still listed. | ["globstar","negation","anchor","directory-only"] |
gitignore nullable | object | Non-null only for `syntax=gitignore`: `{ negated, anchored, directoryOnly, matchedAgainst, effectivePattern }` — how git would file this single line. | {"negated":true,"anchored":true,"directoryOnly":false,"matchedAgainst":"path","effectivePattern":"build/**"} |
unsupported | object[] | Every construct present in the text that this dialect does NOT interpret, as `{ text, note }`. This is the honesty engine: it is how you find out that `**` is not a globstar under posix and that `{a,b}` is literal in a .gitignore. | [{"text":"{","note":"git has no brace expansion, so '{' and '}' are literal characters in a .gitignore rule."}] |
expandedCount | integer | How many patterns brace expansion produces — 1 when there are no braces or the dialect has none. Above 1, `explainedPattern` is the first of them. | 1 |
syntax string The dialect the explanation is written for: posix, glob, extglob or gitignore.
example: gitignore
pattern string The pattern exactly as supplied.
example: !/build/**
explainedPattern string The pattern `segments` actually describes, which is `pattern` itself unless one of two documented things applies. Under `glob`/`extglob` with a correctly-formed brace group it is the FIRST of the `expandedCount` expansions — the one `/test` tries first — because braces are expanded BEFORE matching, so tokens of the raw text would describe a pattern that is never matched. Under `syntax=gitignore` it is the rule body, identical to `gitignore.effectivePattern`: the leading '!' and the anchoring or directory-only '/' are reported in the `gitignore` object instead of being tokenized. A warning names the reason whenever this differs from `pattern`; `/expand` lists every expansion.
example: build/**
segments object[] One entry per '/'-separated segment of `explainedPattern`: `{ index, text, kind: literal|wildcard|globstar, tokens }`, where each token is `{ text, kind: literal|escape|any|star|bracket|class|extglob, meaning }`. Every `meaning` is a statement about the dialect named in `syntax`, so it is true of the pattern this endpoint would actually match.
example: [{"index":0,"text":"build","kind":"literal","tokens":[{"text":"build","kind":"literal","meaning":"Matches the literal text 'build'."}]}]
features string[] Which constructs the dialect actually interprets in this pattern, from a closed vocabulary: globstar, braces, sequence, bracket-expression, character-class, escape, extglob, negation, anchor, directory-only. Collected across every brace expansion, so a bracket that only appears in the second expansion is still listed.
example: ["globstar","negation","anchor","directory-only"]
gitignore object nullable Non-null only for `syntax=gitignore`: `{ negated, anchored, directoryOnly, matchedAgainst, effectivePattern }` — how git would file this single line.
example: {"negated":true,"anchored":true,"directoryOnly":false,"matchedAgainst":"path","effectivePattern":"build/**"}
unsupported object[] Every construct present in the text that this dialect does NOT interpret, as `{ text, note }`. This is the honesty engine: it is how you find out that `**` is not a globstar under posix and that `{a,b}` is literal in a .gitignore.
example: [{"text":"{","note":"git has no brace expansion, so '{' and '}' are literal characters in a .gitignore rule."}]
expandedCount integer How many patterns brace expansion produces — 1 when there are no braces or the dialect has none. Above 1, `explainedPattern` is the first of them.
example: 1
/api/glob-match/explain?pattern=src/**/*.{ts,tsx} {
"data": {
"syntax": "glob",
"pattern": "src/**/*.{ts,tsx}",
"explainedPattern": "src/**/*.ts",
"segments": [
{
"index": 0,
"text": "src",
"kind": "literal",
"tokens": [
{
"text": "src",
"kind": "literal",
"meaning": "Matches the literal text 'src'."
}
]
},
{
"index": 1,
"text": "**",
"kind": "globstar",
"tokens": [
{
"text": "**",
"kind": "star",
"meaning": "A '**' alone in a segment spans zero or more whole path segments."
}
]
},
{
"index": 2,
"text": "*.ts",
"kind": "wildcard",
"tokens": [
{
"text": "*",
"kind": "star",
"meaning": "Matches zero or more characters within one path segment, never a '/'."
},
…
]
}
} /api/glob-match/explain?pattern=!/build/**&syntax=gitignore {
"data": {
"syntax": "gitignore",
"pattern": "!/build/**",
"explainedPattern": "build/**",
"segments": [
{
"index": 0,
"text": "build",
"kind": "literal",
"tokens": [
{
"text": "build",
"kind": "literal",
"meaning": "Matches the literal text 'build'."
}
]
},
{
"index": 1,
"text": "**",
"kind": "globstar",
"tokens": [
{
"text": "**",
"kind": "star",
"meaning": "A '**' alone in a segment spans zero or more path segments; as the LAST segment of a .gitignore rule it needs at least one, so 'abc/**' does not match the directory 'abc'."
}
]
}
],
"features": [
"globstar",
"negation",
"anchor"
],
"gitignore": {
"negated": true,
"anchored": true,
"directoryOnly": false,
…
]
}
} /api/glob-match/explain?pattern=**/!(*.d).ts&syntax=extglob {
"data": {
"syntax": "extglob",
"pattern": "**/!(*.d).ts",
"explainedPattern": "**/!(*.d).ts",
"segments": [
{
"index": 0,
"text": "**",
"kind": "globstar",
"tokens": [
{
"text": "**",
"kind": "star",
"meaning": "A '**' alone in a segment spans zero or more whole path segments."
}
]
},
{
"index": 1,
"text": "!(*.d).ts",
"kind": "wildcard",
"tokens": [
{
"text": "!(*.d)",
"kind": "extglob",
"meaning": "Matches anything except one of the given patterns."
},
{
"text": ".ts",
"kind": "literal",
"meaning": "Matches the literal text '.ts'."
}
]
}
],
"features": [
"globstar",
"extglob"
],
…
"generatedAt": "2026-07-30T15:14:08.000Z"
}
} /api/glob-match/explain?pattern=src/**/!(*.d).{ts,tsx}&syntax=posix {
"data": {
"syntax": "posix",
"pattern": "src/**/!(*.d).{ts,tsx}",
"explainedPattern": "src/**/!(*.d).{ts,tsx}",
"segments": [
{
"index": 0,
"text": "src",
"kind": "literal",
"tokens": [
{
"text": "src",
"kind": "literal",
"meaning": "Matches the literal text 'src'."
}
]
},
{
"index": 1,
"text": "**",
"kind": "wildcard",
"tokens": [
{
"text": "**",
"kind": "star",
"meaning": "POSIX.1-2024 defines no globstar, so '**' behaves exactly as one '*': zero or more characters within one path segment, never a '/'."
}
]
},
{
"index": 2,
"text": "!(*.d).{ts,tsx}",
"kind": "wildcard",
"tokens": [
{
"text": "!(",
"kind": "literal",
"meaning": "Matches the literal text '!('."
},
…
]
}
} Bash 5.3 brace expansion, in left-to-right order, never sorted and never deduplicated. POSIX fnmatch() and git have no brace expansion at all.
| Parameter | Type | Default & allowed | Description |
|---|---|---|---|
pattern
required
| string | allowed: 1 – 512 example: src/{a,b}/*.{ts,tsx} | The pattern to expand, taken exactly as sent. Up to 256 expansions and 32,768 expanded characters — over that is a 400, never a truncated list. |
pattern string required The pattern to expand, taken exactly as sent. Up to 256 expansions and 32,768 expanded characters — over that is a 400, never a truncated list.
| Parameter | Type | Default & allowed | Description |
|---|---|---|---|
fields | list | example: pattern,patterns | Return only these fields (comma-separated). Mutually exclusive with 'exclude'. |
exclude | list | example: literalGroups | Return all fields except these (comma-separated). |
pretty | boolean | default: false example: true | Pretty-print the JSON response. |
unwrap | boolean | default: false example: true | Drop the envelope: return the raw array/object without data/meta wrapper. |
fields list Return only these fields (comma-separated). Mutually exclusive with 'exclude'.
exclude list Return all fields except these (comma-separated).
pretty boolean Pretty-print the JSON response.
unwrap boolean Drop the envelope: return the raw array/object without data/meta wrapper.
| Field | Type | Description | Example |
|---|---|---|---|
pattern | string | The pattern exactly as supplied. | src/{a,b}/*.{ts,tsx} |
patterns | string[] | The expansions in Bash left-to-right order — never sorted, never deduplicated, so `a{,}b` legitimately yields 'ab' twice. Backslashes are kept: brace expansion does not do quote removal. | ["src/a/*.ts","src/a/*.tsx","src/b/*.ts","src/b/*.tsx"] |
expandedCount | integer | Length of `patterns`, i.e. the product of every group that expanded. | 4 |
braceGroups | integer | How many brace groups actually expanded. | 2 |
literalGroups | string[] | Brace groups left exactly as written because they are not correctly formed — no unquoted comma and no valid sequence expression, e.g. `{a}`. Bash leaves these alone, and so do we. | [] |
pattern string The pattern exactly as supplied.
example: src/{a,b}/*.{ts,tsx}
patterns string[] The expansions in Bash left-to-right order — never sorted, never deduplicated, so `a{,}b` legitimately yields 'ab' twice. Backslashes are kept: brace expansion does not do quote removal.
example: ["src/a/*.ts","src/a/*.tsx","src/b/*.ts","src/b/*.tsx"]
expandedCount integer Length of `patterns`, i.e. the product of every group that expanded.
example: 4
braceGroups integer How many brace groups actually expanded.
example: 2
literalGroups string[] Brace groups left exactly as written because they are not correctly formed — no unquoted comma and no valid sequence expression, e.g. `{a}`. Bash leaves these alone, and so do we.
example: []
/api/glob-match/expand?pattern=src/{a,b}/*.{ts,tsx} {
"data": {
"pattern": "src/{a,b}/*.{ts,tsx}",
"patterns": [
"src/a/*.ts",
"src/a/*.tsx",
"src/b/*.ts",
"src/b/*.tsx"
],
"expandedCount": 4,
"braceGroups": 2,
"literalGroups": []
},
"meta": {
"endpoint": "glob-match",
"route": "/expand",
"params": {
"pattern": "src/{a,b}/*.{ts,tsx}"
},
"generatedAt": "2026-07-30T15:14:08.000Z"
}
} /api/glob-match/expand?pattern=v{1..3}/**/*.json {
"data": {
"pattern": "v{1..3}/**/*.json",
"patterns": [
"v1/**/*.json",
"v2/**/*.json",
"v3/**/*.json"
],
"expandedCount": 3,
"braceGroups": 1,
"literalGroups": []
},
"meta": {
"endpoint": "glob-match",
"route": "/expand",
"params": {
"pattern": "v{1..3}/**/*.json"
},
"generatedAt": "2026-07-30T15:14:08.000Z"
}
} /api/glob-match/expand?pattern=report-{01..12}.csv {
"data": {
"pattern": "report-{01..12}.csv",
"patterns": [
"report-01.csv",
"report-02.csv",
"report-03.csv",
"report-04.csv",
"report-05.csv",
"report-06.csv",
"report-07.csv",
"report-08.csv",
"report-09.csv",
"report-10.csv",
"report-11.csv",
"report-12.csv"
],
"expandedCount": 12,
"braceGroups": 1,
"literalGroups": []
},
"meta": {
"endpoint": "glob-match",
"route": "/expand",
"params": {
"pattern": "report-{01..12}.csv"
},
"generatedAt": "2026-07-30T15:14:08.000Z"
}
} /api/glob-match/expand?pattern=x{,y}{a} {
"data": {
"pattern": "x{,y}{a}",
"patterns": [
"x{a}",
"xy{a}"
],
"expandedCount": 2,
"braceGroups": 1,
"literalGroups": [
"{a}"
]
},
"meta": {
"endpoint": "glob-match",
"route": "/expand",
"params": {
"pattern": "x{,y}{a}"
},
"generatedAt": "2026-07-30T15:14:08.000Z",
"warnings": [
"Left as written because they are not correctly-formed brace expansions: {a}."
]
}
} Glob and .gitignore matching computed from strings you supply. Nothing is fetched, no filesystem is read and no repository is inspected, which is exactly why this works from CI against a .gitignore that is only in your working tree, and why a coding agent with no shell can still check the ignore rule it just wrote.
/gitignore is the flagship. It answers "would git ignore this path, and which line decided it?" — and, uniquely, it tells you when a ! negation lost to an excluded parent directory. That is the single most common .gitignore surprise: build/ excludes the directory, so git never descends into it and !build/keep.txt on the next line can never take effect. gitignore(5) states the rule plainly — "It is not possible to re-include a file if a parent directory of that file is excluded" — and this endpoint reports it as reason: "parent-directory" with the ancestor and its rule, instead of leaving you to guess. Write build/* instead and the negation works; /gitignore shows the difference in one request.
Remember which way precedence runs: gitignore is last-match-wins, so competingRules is in file order and the last entry is the path's own decision.
/test matches one path against one pattern and returns a witness: alignment shows which pattern segment consumed which path segments, so a match is auditable rather than asserted. When nothing matches, failure names the first pattern segment no prefix of the path can reach. /filter runs up to 100 paths against one pattern in a single request. /explain breaks a pattern into segments and tokens and — the part that matters — lists every construct the dialect you named does not interpret. Because braces are expanded before matching, it explains the first expansion and names it in explainedPattern rather than pretending {ts,tsx} is literal text. /expand shows exactly what Bash brace expansion produces.
Which dialect are you even asking about? Most "glob testers" silently pick one library's behaviour. Here syntax is explicit:
posix — POSIX.1-2024 XCU 2.14 with fnmatch()'s FNM_PATHNAME semantics. There is no globstar in POSIX: ** is two asterisks and behaves exactly as one *, so src/**/*.ts matches src/a/b.ts and not src/a/b/c.ts. No braces, no extglob. The dot parameter is FNM_PERIOD.glob (default) — POSIX plus a ** segment that spans zero or more path segments, plus Bash brace expansion. This is a documented superset matching what Node tooling implements; it is not a published standard, and we say so rather than implying one.extglob — glob plus the five Bash operators verbatim from the manual: ?(…) zero or one, *(…) zero or more, +(…) one or more, @(…) exactly one, !(…) anything except one of.gitignore (on /explain, and the whole of /gitignore) — gitignore(5) PATTERN FORMAT with git's three globstar forms. Matching runs over UTF-8 bytes, because git's ? matches one byte: ?.txt does not match é.txt but ??.txt does, and ????.txt matches 😀.txt. Every other dialect matches code points.What is real here. Every dialect was checked against its primary source and against the real tool on 2026-07-30: git 2.50.1 via git check-ignore in throwaway repositories, BSD fnmatch(3) with FNM_PATHNAME, picomatch 4.0.4, and bash for brace expansion. A committed script re-runs those comparisons against a golden file so a divergence cannot ship undocumented.
Documented divergences — no library parity is claimed.
abc/** matches the directory abc itself under glob (and in picomatch) but not in git, where a trailing /** needs at least one segment inside. Both answers are available: ask /test and /gitignore the same question and compare.? is one code point here, one byte under gitignore, and one UTF-16 code unit in picomatch — three different answers for 😀.txt.[!…] negation. [!a] matches any one character that is not a — POSIX.1-2024 XCU 2.14.1 replaces the regular-expression ^ with ! here, and git and fnmatch(3) both agree. picomatch compiles it to the JavaScript class [!a], where ! is an ordinary member, so it matches a and ! and rejects b — the exact opposite. [^a] works in all four. A ] immediately after the ! is a literal member too, so [!]a] matches b.txt.{**,x}/* is **/* OR x/*, and **/* matches the single segment a.ts. picomatch compiles the braces into one alternation and loses the globstar's zero-segment case there, so it answers true for **/* and false for {**,x}/* against the same path. expandedPatterns and matchedPattern show you exactly which expansion won.[ has three answers: POSIX.1-2024 XCU 2.14.1 makes a [ that introduces no valid bracket expression match the character itself, so posix/glob/extglob match the literal [; git's matcher makes the rule match nothing, and BSD fnmatch(3) also refuses. A warning tells you which reading applied./ inside a bracket expression can never match, so a[/]b never matches a/b (git and BSD fnmatch agree). Note that POSIX's own wording identifies slashes before brackets, which would instead make a[b/c]d match only the literal a[b/c]d; both real matchers on hand read the bracket first and so do we.{a,b} in a .gitignore is literal: git has no brace expansion at all. !(a).txt in a .gitignore is a negation of the literal (a).txt, not an extglob group.{1..5..2} and the zero-padding of {01..12}. macOS still ships bash 3.2, which has neither.Scope limits, stated up front.
git check-ignore. One rule file, treated as the repository-root .gitignore. No per-directory .gitignore stacking, no info/exclude, no core.excludesFile, no command-line patterns and no reading of your git config. And a .gitignore never affects a file git already tracks — that is the one cause of "why is my file not ignored" this endpoint cannot see.case-style matching where * crosses / (fnmatch() without FNM_PATHNAME) is out of scope.[[.a.]] and equivalence classes [[=a=]] are a 400, not a guess: they are locale-dependent, and git's own matcher makes such a rule match nothing. Write the members out. Character classes are ASCII ([[:alpha:]] is A-Za-z), matching both git and picomatch — a locale-aware fnmatch(3) in a UTF-8 locale would also accept é there, and POSIX leaves that to the locale rather than settling it.. and .. are never resolved. /gitignore rejects them, /test matches them literally with a warning.core.ignoreCase. Non-NFC input is warned about, because macOS hands out decomposed filenames and a precomposed pattern will not match them./filter takes one pattern, not a rule file. Testing many paths against a whole .gitignore means calling /gitignore per path: the per-path answer carries ancestorDecisions and competingRules, which do not compress into a list row. Note also that the paths list is comma-separated and each entry is trimmed, so a path containing a comma or a meaningful leading space has to go to /test.pattern ≤ 512, path ≤ 1024 and rules ≤ 8,000 characters over 500 lines. Newlines travel as %0A, and HTTP query decoding happens before we see the value, so a path that genuinely contains %2F must be sent double-encoded as %252F./test and /gitignore return the steps they actually used, so a*a*a*a*b against forty as is measurably cheap — a few hundred steps — instead of the classic blow-up. Over budget is a 400, never a wrong answer.Usually one of three things. /gitignore names the first two: reason: "parent-directory" means an ancestor directory is excluded so git never looks inside it, and reason: "no-match" means no rule matches the path at all. The third is outside this endpoint's reach — a .gitignore never affects a file git already tracks, so git rm --cached it first.
Because the line build/ excluded the directory first, and gitignore(5) says a file cannot be re-included when a parent directory of it is excluded. Real git reports the winning rule as line 1 build/, and so does /gitignore, with excludedByParent.directory set to build. Write build/* instead of build/ and the negation works.
No, and that is why syntax is explicit. posix has no globstar at all — ** is just *. glob and extglob treat a ** segment as zero or more path segments. gitignore has git's three documented forms, and there a trailing /** does not match the directory itself.
No, and no bug-for-bug compatibility is claimed. The divergences are listed on this page and re-checked by a committed script: abc/** matches abc here and in picomatch but not in git, ? is a code point here and a UTF-16 code unit in picomatch, and [!]a] matches b.txt here (as in POSIX and git) but not in picomatch.
No. You paste the file into rules and paste the path into path; nothing is fetched, no filesystem is read and no repository is opened. A draft in your editor tests exactly like a committed one.
No — git has no brace expansion, so {a,b}.txt matches a file literally named {a,b}.txt and /gitignore warns you about it. Use /expand to see what Bash brace expansion would produce, and /test with syntax=glob to match with braces applied.
Test a URL path against a robots.txt you paste, for any crawler, and see the winning rule under RFC 9309 most-octets precedence.
Parse octal Unix modes, apply POSIX symbolic chmod expressions and calculate umask results, including special bits, X and class copying.
Fake file metadata — kebab-case names, the correct MIME type for every extension, plausible per-kind sizes, unix paths and seeded SHA-256-style checksums.
Detect a file's real format from its magic bytes and flag when the filename or declared MIME type disagrees. Every signature cited to its own spec.
The complete IANA media type registry with file extensions merged in — look up any type or extension, or strictly parse a Content-Type value into its parts.
Parse absolute URLs or resolve relative references with the WHATWG URL algorithm — canonical hosts, ordered query pairs and no credential leakage.