@flagshark/core 1.2.0 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/README.md +100 -18
  2. package/dist/config/defaults.d.ts +3 -0
  3. package/dist/config/defaults.d.ts.map +1 -0
  4. package/dist/config/defaults.js +6 -0
  5. package/dist/config/defaults.js.map +1 -0
  6. package/dist/config/excluder.d.ts +18 -0
  7. package/dist/config/excluder.d.ts.map +1 -0
  8. package/dist/config/excluder.js +51 -0
  9. package/dist/config/excluder.js.map +1 -0
  10. package/dist/config/ignore-file.d.ts +6 -0
  11. package/dist/config/ignore-file.d.ts.map +1 -0
  12. package/dist/config/ignore-file.js +23 -0
  13. package/dist/config/ignore-file.js.map +1 -0
  14. package/dist/config/index.d.ts +7 -0
  15. package/dist/config/index.d.ts.map +1 -0
  16. package/dist/config/index.js +7 -0
  17. package/dist/config/index.js.map +1 -0
  18. package/dist/config/loader.d.ts +7 -0
  19. package/dist/config/loader.d.ts.map +1 -0
  20. package/dist/config/loader.js +47 -0
  21. package/dist/config/loader.js.map +1 -0
  22. package/dist/config/presets.d.ts +11 -0
  23. package/dist/config/presets.d.ts.map +1 -0
  24. package/dist/config/presets.js +70 -0
  25. package/dist/config/presets.js.map +1 -0
  26. package/dist/config/schema.d.ts +322 -0
  27. package/dist/config/schema.d.ts.map +1 -0
  28. package/dist/config/schema.js +63 -0
  29. package/dist/config/schema.js.map +1 -0
  30. package/dist/detection/detectors/go.d.ts +9 -3
  31. package/dist/detection/detectors/go.d.ts.map +1 -1
  32. package/dist/detection/detectors/go.js +8 -2
  33. package/dist/detection/detectors/go.js.map +1 -1
  34. package/dist/detection/detectors/javascript.d.ts +9 -3
  35. package/dist/detection/detectors/javascript.d.ts.map +1 -1
  36. package/dist/detection/detectors/javascript.js +8 -2
  37. package/dist/detection/detectors/javascript.js.map +1 -1
  38. package/dist/detection/detectors/python.d.ts +9 -3
  39. package/dist/detection/detectors/python.d.ts.map +1 -1
  40. package/dist/detection/detectors/python.js +8 -2
  41. package/dist/detection/detectors/python.js.map +1 -1
  42. package/dist/detection/detectors/typescript.d.ts +9 -3
  43. package/dist/detection/detectors/typescript.d.ts.map +1 -1
  44. package/dist/detection/detectors/typescript.js +8 -2
  45. package/dist/detection/detectors/typescript.js.map +1 -1
  46. package/dist/detection/helpers.d.ts +1 -1
  47. package/dist/detection/helpers.d.ts.map +1 -1
  48. package/dist/detection/helpers.js +9 -1
  49. package/dist/detection/helpers.js.map +1 -1
  50. package/dist/detection/index.d.ts +10 -0
  51. package/dist/detection/index.d.ts.map +1 -1
  52. package/dist/detection/index.js +31 -7
  53. package/dist/detection/index.js.map +1 -1
  54. package/dist/detection/interface.d.ts +3 -1
  55. package/dist/detection/interface.d.ts.map +1 -1
  56. package/dist/detection/registry.d.ts +1 -1
  57. package/dist/detection/registry.d.ts.map +1 -1
  58. package/dist/detection/tree-sitter/const-resolver.d.ts +8 -0
  59. package/dist/detection/tree-sitter/const-resolver.d.ts.map +1 -0
  60. package/dist/detection/tree-sitter/const-resolver.js +36 -0
  61. package/dist/detection/tree-sitter/const-resolver.js.map +1 -0
  62. package/dist/detection/tree-sitter/engine.d.ts +11 -0
  63. package/dist/detection/tree-sitter/engine.d.ts.map +1 -0
  64. package/dist/detection/tree-sitter/engine.js +70 -0
  65. package/dist/detection/tree-sitter/engine.js.map +1 -0
  66. package/dist/detection/tree-sitter/parser-cache.d.ts +6 -0
  67. package/dist/detection/tree-sitter/parser-cache.d.ts.map +1 -0
  68. package/dist/detection/tree-sitter/parser-cache.js +68 -0
  69. package/dist/detection/tree-sitter/parser-cache.js.map +1 -0
  70. package/dist/detection/tree-sitter/queries/go.scm +11 -0
  71. package/dist/detection/tree-sitter/queries/javascript.scm +13 -0
  72. package/dist/detection/tree-sitter/queries/python.scm +11 -0
  73. package/dist/detection/tree-sitter/queries/typescript.scm +11 -0
  74. package/dist/detection/tree-sitter/query-runner.d.ts +20 -0
  75. package/dist/detection/tree-sitter/query-runner.d.ts.map +1 -0
  76. package/dist/detection/tree-sitter/query-runner.js +83 -0
  77. package/dist/detection/tree-sitter/query-runner.js.map +1 -0
  78. package/dist/index.d.ts +1 -0
  79. package/dist/index.d.ts.map +1 -1
  80. package/dist/index.js +2 -0
  81. package/dist/index.js.map +1 -1
  82. package/dist/scan-repo.d.ts +27 -0
  83. package/dist/scan-repo.d.ts.map +1 -1
  84. package/dist/scan-repo.js +26 -5
  85. package/dist/scan-repo.js.map +1 -1
  86. package/dist/scanner.d.ts +11 -2
  87. package/dist/scanner.d.ts.map +1 -1
  88. package/dist/scanner.js +22 -7
  89. package/dist/scanner.js.map +1 -1
  90. package/package.json +12 -4
package/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # @flagshark/core
2
2
 
3
- The detection engine behind [FlagShark](https://github.com/FlagShark/flagshark) — finds feature flag references across 13 languages and 12+ providers, and identifies stale flags via git blame.
3
+ The detection engine behind [FlagShark](https://github.com/FlagShark/flagshark) — finds feature flag references across 13 languages and 13 providers, and identifies stale flags via git blame.
4
4
 
5
- This is the **library**. If you want a CLI or GitHub Action, install [`flagshark`](https://www.npmjs.com/package/flagshark) instead.
5
+ This is the **library**. For a CLI or GitHub Action, install [`flagshark`](https://www.npmjs.com/package/flagshark) instead.
6
6
 
7
7
  ## Install
8
8
 
@@ -25,18 +25,26 @@ console.log(`Health score: ${result.healthScore}/100`)
25
25
  console.log(`Languages: ${Object.keys(result.languageBreakdown).join(', ')}`)
26
26
  ```
27
27
 
28
+ ## Detection
29
+
30
+ For tier-1 languages (TypeScript, JavaScript, Go, Python), detection uses [tree-sitter](https://tree-sitter.github.io/) (via WASM) to parse files into a real AST. That eliminates the false-positive class where regex matched flag-shaped strings inside comments, string literals, or unrelated call expressions. It also handles multi-line calls and resolves const-bound flag keys (`const FLAG = 'X'; client.variation(FLAG, …)`) in TS/JS.
31
+
32
+ The remaining 9 languages currently use regex-based detection; each migrates to tree-sitter in future releases (no API break).
33
+
34
+ The engine is a runtime concern, not a public API surface. Consumers calling `scanRepo()` or `createDefaultRegistry()` get the right engine per-language automatically. Override per-detector if needed (see below).
35
+
28
36
  ## API
29
37
 
30
38
  ### `scanRepo(options) → Promise<ScanRepoResult>`
31
39
 
32
- Top-level orchestrator. Walks a checked-out repository, detects flags, and runs staleness analysis.
40
+ Top-level orchestrator. Walks a checked-out repository, detects flags, runs staleness analysis, and returns a summary.
33
41
 
34
42
  ```ts
35
43
  interface ScanRepoOptions {
36
44
  /** Absolute path to the repository being scanned. */
37
45
  cwd: string
38
46
 
39
- /** Staleness threshold in months. Default: 6. */
47
+ /** Staleness threshold in months. Overridden by `config.threshold` if config is supplied. */
40
48
  threshold?: number
41
49
 
42
50
  /** If set, only scan files changed since this git ref (e.g. `origin/main`). */
@@ -48,20 +56,85 @@ interface ScanRepoOptions {
48
56
 
49
57
  /** Optional `{ debug, info, warn, error }` logger. Defaults to no-op. */
50
58
  logger?: ScanLogger
59
+
60
+ /** Explicit FlagsharkConfig. If undefined, scanRepo auto-discovers
61
+ * `.flagshark.yml` walking upward from `cwd`. */
62
+ config?: FlagsharkConfig
63
+
64
+ /** Skip `.flagshark.yml` discovery entirely. */
65
+ noConfig?: boolean
66
+
67
+ /** Skip `.flagsharkignore` discovery entirely. */
68
+ noIgnoreFile?: boolean
69
+
70
+ /** Populate `result.excludedPaths` with the full list of skipped file paths.
71
+ * Off by default — counts only. */
72
+ collectExcludedPaths?: boolean
51
73
  }
52
74
 
53
75
  interface ScanRepoResult {
54
76
  totalFlags: number
55
- filesScanned: number
56
77
  staleFlags: StaleFlag[]
57
78
  detectedProviders: string[]
58
79
  languageBreakdown: Record<string, number>
59
- healthScore: number // 0–100
60
- scanDuration: number // ms
80
+ healthScore: number // 0–100
81
+ scanDuration: number // ms
82
+ excludedCount?: number // files skipped by config + .flagsharkignore
83
+ excludedPaths?: string[] // set when collectExcludedPaths: true
84
+ effectiveExcludes?: EffectiveRules // for debug/verbose output
61
85
  }
62
86
  ```
63
87
 
64
- `scanRepo` walks the local filesystem. If you can't run a git checkout (e.g., serverless function reading from an API), use the lower-level primitives below.
88
+ `scanRepo` walks the local filesystem. If you can't run a git checkout (e.g., a serverless function reading from an API), use the lower-level primitives below.
89
+
90
+ ### Config module
91
+
92
+ The same config used by the CLI is exported as a building block:
93
+
94
+ ```ts
95
+ import {
96
+ buildDefaultConfig,
97
+ loadConfigFile,
98
+ loadIgnoreFile,
99
+ buildExcluder,
100
+ expandPresets,
101
+ PRESETS,
102
+ FlagsharkConfigSchema,
103
+ type FlagsharkConfig,
104
+ } from '@flagshark/core'
105
+
106
+ // Load a .flagshark.yml from disk (validated via zod)
107
+ const loaded = await loadConfigFile('/path/to/repo')
108
+ const config = loaded?.config ?? buildDefaultConfig()
109
+
110
+ // Load a .flagsharkignore (separate file at repo root)
111
+ const ignoreFile = await loadIgnoreFile('/path/to/repo')
112
+
113
+ // Build a unified excluder from all sources
114
+ const excluder = buildExcluder({
115
+ config,
116
+ ignoreFilePatterns: ignoreFile?.patterns ?? [],
117
+ })
118
+
119
+ // Use it directly:
120
+ excluder.shouldExclude('examples/demo.ts') // true
121
+ ```
122
+
123
+ The schema accepts:
124
+
125
+ ```yaml
126
+ threshold: 6
127
+ excludes:
128
+ paths: [] # gitignore-style globs
129
+ files: [] # same — split is purely organizational
130
+ presets: [] # ['test-files', 'snapshots', 'examples', 'stories', 'fixtures', 'generated']
131
+ suppress:
132
+ flags: [] # flag-name globs (post-detection filter)
133
+ paths:
134
+ - match: 'src/critical/**'
135
+ threshold: 3
136
+ providers: [] # custom provider definitions (see Custom Detectors)
137
+ ```
65
138
 
66
139
  ### Lower-level primitives
67
140
 
@@ -71,7 +144,7 @@ For consumers that fetch file contents elsewhere (GitHub API, S3, in-memory, etc
71
144
  import { createDefaultRegistry, PolyglotAnalyzer } from '@flagshark/core'
72
145
 
73
146
  const registry = createDefaultRegistry() // 13 detectors pre-registered
74
- const analyzer = new PolyglotAnalyzer(registry)
147
+ const analyzer = new PolyglotAnalyzer(registry, logger)
75
148
 
76
149
  // `files` is a Map<filePath, content>
77
150
  const result = await analyzer.analyzeFiles(files)
@@ -101,34 +174,43 @@ const registry = createDefaultRegistry()
101
174
  registry.register(new MyCustomDetector())
102
175
  ```
103
176
 
104
- Or import individual detectors and compose your own registry:
177
+ Or import individual detectors and compose your own registry. Each tier-1 detector accepts `{ engine: 'regex' | 'tree-sitter' }`:
105
178
 
106
179
  ```ts
107
- import { LanguageRegistry, GoDetector, TypeScriptDetector } from '@flagshark/core'
180
+ import {
181
+ LanguageRegistry,
182
+ TypeScriptDetector,
183
+ GoDetector,
184
+ type DetectorEngine,
185
+ } from '@flagshark/core'
108
186
 
109
187
  const registry = new LanguageRegistry()
110
- registry.register(new GoDetector())
111
- registry.register(new TypeScriptDetector())
188
+ registry.register(new TypeScriptDetector({ engine: 'tree-sitter' }))
189
+ registry.register(new GoDetector({ engine: 'regex' })) // opt back to regex
112
190
  ```
113
191
 
114
192
  ## Supported languages
115
193
 
116
- TypeScript/JavaScript, Go, Python, Java, Kotlin, Swift, Ruby, C#, PHP, Rust, C/C++, Objective-C.
194
+ TypeScript, JavaScript, Go, Python, Java, Kotlin, Swift, Ruby, C#, PHP, Rust, C/C++, Objective-C.
117
195
 
118
196
  ## Supported providers
119
197
 
120
- Auto-detected from imports — no configuration needed: LaunchDarkly, Unleash, Flipt, Split.io, PostHog, Flagsmith, ConfigCat, Statsig, GrowthBook, DevCycle, Eppo, Optimizely, plus generic custom-flag patterns.
198
+ Auto-detected from imports — no configuration needed:
199
+
200
+ LaunchDarkly · Unleash · Flipt · Split.io · PostHog · Flagsmith · ConfigCat · Statsig · GrowthBook · DevCycle · Eppo · Optimizely · plus generic custom-flag patterns.
121
201
 
122
202
  ## How detection works
123
203
 
124
- For each file, FlagShark looks for an import of a flag SDK. If none is found, the file is skipped — this prevents false positives from generic functions named `isEnabled()` etc. Once a file qualifies, language-specific regex patterns extract flag references along with provider attribution and source location.
204
+ FlagShark only scans files that actually import a flag SDK. A function called `isEnabled()` in a file that doesn't import LaunchDarkly/Unleash/etc. won't be flagged this prevents false positives from generic identifier names.
205
+
206
+ Once a file qualifies, the engine (tree-sitter for tier-1, regex for the rest) walks call expressions and extracts the flag-key argument at the configured position for each provider's method signatures. Provider attribution and source location come along automatically.
125
207
 
126
208
  ## How staleness works
127
209
 
128
210
  A flag is marked stale if **any** signal fires:
129
211
 
130
- 1. **Age:** `git blame` says the flag was added more than `threshold` months ago.
131
- 2. **Single file:** The flag name appears in only one file across the repo, suggesting a completed rollout.
212
+ 1. **`age`** `git blame` shows the flag's line was last modified more than `threshold` months ago.
213
+ 2. **`low-usage`** The flag name appears in only one file across the repo, suggesting a completed rollout.
132
214
 
133
215
  ## License
134
216
 
@@ -0,0 +1,3 @@
1
+ import { type FlagsharkConfig } from './schema.js';
2
+ export declare function buildDefaultConfig(): FlagsharkConfig;
3
+ //# sourceMappingURL=defaults.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"defaults.d.ts","sourceRoot":"","sources":["../../src/config/defaults.ts"],"names":[],"mappings":"AAAA,OAAO,EAAyB,KAAK,eAAe,EAAE,MAAM,aAAa,CAAA;AAEzE,wBAAgB,kBAAkB,IAAI,eAAe,CAGpD"}
@@ -0,0 +1,6 @@
1
+ import { FlagsharkConfigSchema } from './schema.js';
2
+ export function buildDefaultConfig() {
3
+ // Parse an empty object — schema fills in all defaults.
4
+ return FlagsharkConfigSchema.parse({});
5
+ }
6
+ //# sourceMappingURL=defaults.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"defaults.js","sourceRoot":"","sources":["../../src/config/defaults.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAwB,MAAM,aAAa,CAAA;AAEzE,MAAM,UAAU,kBAAkB;IAChC,wDAAwD;IACxD,OAAO,qBAAqB,CAAC,KAAK,CAAC,EAAE,CAAC,CAAA;AACxC,CAAC"}
@@ -0,0 +1,18 @@
1
+ import type { FlagsharkConfig } from './schema.js';
2
+ export interface EffectiveRules {
3
+ paths: string[];
4
+ files: string[];
5
+ presets: string[];
6
+ presetPatterns: string[];
7
+ ignoreFile: string[];
8
+ }
9
+ export interface Excluder {
10
+ shouldExclude(path: string): boolean;
11
+ effectiveRules: EffectiveRules;
12
+ }
13
+ export interface BuildExcluderInput {
14
+ config: FlagsharkConfig;
15
+ ignoreFilePatterns: string[];
16
+ }
17
+ export declare function buildExcluder(input: BuildExcluderInput): Excluder;
18
+ //# sourceMappingURL=excluder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"excluder.d.ts","sourceRoot":"","sources":["../../src/config/excluder.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAIlD,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,EAAE,CAAA;IACf,KAAK,EAAE,MAAM,EAAE,CAAA;IACf,OAAO,EAAE,MAAM,EAAE,CAAA;IACjB,cAAc,EAAE,MAAM,EAAE,CAAA;IACxB,UAAU,EAAE,MAAM,EAAE,CAAA;CACrB;AAED,MAAM,WAAW,QAAQ;IACvB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAA;IACpC,cAAc,EAAE,cAAc,CAAA;CAC/B;AAED,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,eAAe,CAAA;IACvB,kBAAkB,EAAE,MAAM,EAAE,CAAA;CAC7B;AAwBD,wBAAgB,aAAa,CAAC,KAAK,EAAE,kBAAkB,GAAG,QAAQ,CA4BjE"}
@@ -0,0 +1,51 @@
1
+ import ignoreModule from 'ignore';
2
+ import { expandPresets } from './presets.js';
3
+ const ignore = ignoreModule.default ?? ignoreModule;
4
+ /**
5
+ * Convert trailing-slash directory patterns (`examples/`) to recursive globs
6
+ * (`examples/**`) so that `!`-negation works downstream.
7
+ *
8
+ * The `ignore@5` package follows the strict gitignore spec: once a parent
9
+ * directory is excluded via a trailing-slash pattern, files inside it cannot
10
+ * be re-included via `!`. Converting to `examples/**` bypasses this restriction
11
+ * by treating the pattern as a glob rather than a directory match.
12
+ *
13
+ * Trade-off: `examples/**` is anchored to the repo root, while gitignore's
14
+ * `examples/` is unanchored (matches `examples/` anywhere in the tree).
15
+ * In practice the scanner's hardcoded SKIP_DIRS already filters node_modules,
16
+ * vendor, etc., so this anchoring change rarely affects real users — but it
17
+ * is a deliberate departure from strict gitignore semantics.
18
+ */
19
+ function normalisePattern(p) {
20
+ if (!p.startsWith('!') && p.endsWith('/')) {
21
+ return p + '**';
22
+ }
23
+ return p;
24
+ }
25
+ export function buildExcluder(input) {
26
+ const { config, ignoreFilePatterns } = input;
27
+ const presetPatterns = expandPresets(config.excludes.presets);
28
+ const allPatterns = [
29
+ ...config.excludes.paths,
30
+ ...config.excludes.files,
31
+ ...presetPatterns,
32
+ ...ignoreFilePatterns,
33
+ ];
34
+ const matcher = ignore().add(allPatterns.map(normalisePattern));
35
+ return {
36
+ shouldExclude(path) {
37
+ if (allPatterns.length === 0)
38
+ return false;
39
+ const normalised = path.startsWith('./') ? path.slice(2) : path;
40
+ return matcher.ignores(normalised);
41
+ },
42
+ effectiveRules: {
43
+ paths: [...config.excludes.paths],
44
+ files: [...config.excludes.files],
45
+ presets: [...config.excludes.presets],
46
+ presetPatterns,
47
+ ignoreFile: [...ignoreFilePatterns],
48
+ },
49
+ };
50
+ }
51
+ //# sourceMappingURL=excluder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"excluder.js","sourceRoot":"","sources":["../../src/config/excluder.ts"],"names":[],"mappings":"AAAA,OAAO,YAAY,MAAM,QAAQ,CAAA;AAEjC,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AAI5C,MAAM,MAAM,GAAI,YAA6D,CAAC,OAAO,IAAI,YAAY,CAAA;AAoBrG;;;;;;;;;;;;;;GAcG;AACH,SAAS,gBAAgB,CAAC,CAAS;IACjC,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QAC1C,OAAO,CAAC,GAAG,IAAI,CAAA;IACjB,CAAC;IACD,OAAO,CAAC,CAAA;AACV,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,KAAyB;IACrD,MAAM,EAAE,MAAM,EAAE,kBAAkB,EAAE,GAAG,KAAK,CAAA;IAE5C,MAAM,cAAc,GAAG,aAAa,CAAC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAA;IAE7D,MAAM,WAAW,GAAa;QAC5B,GAAG,MAAM,CAAC,QAAQ,CAAC,KAAK;QACxB,GAAG,MAAM,CAAC,QAAQ,CAAC,KAAK;QACxB,GAAG,cAAc;QACjB,GAAG,kBAAkB;KACtB,CAAA;IAED,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,GAAG,CAAC,WAAW,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC,CAAA;IAE/D,OAAO;QACL,aAAa,CAAC,IAAY;YACxB,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC;gBAAE,OAAO,KAAK,CAAA;YAC1C,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;YAC/D,OAAO,OAAO,CAAC,OAAO,CAAC,UAAU,CAAC,CAAA;QACpC,CAAC;QACD,cAAc,EAAE;YACd,KAAK,EAAE,CAAC,GAAG,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC;YACjC,KAAK,EAAE,CAAC,GAAG,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC;YACjC,OAAO,EAAE,CAAC,GAAG,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC;YACrC,cAAc;YACd,UAAU,EAAE,CAAC,GAAG,kBAAkB,CAAC;SACpC;KACF,CAAA;AACH,CAAC"}
@@ -0,0 +1,6 @@
1
+ export interface LoadedIgnore {
2
+ patterns: string[];
3
+ path: string;
4
+ }
5
+ export declare function loadIgnoreFile(startDir: string): Promise<LoadedIgnore | null>;
6
+ //# sourceMappingURL=ignore-file.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ignore-file.d.ts","sourceRoot":"","sources":["../../src/config/ignore-file.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,EAAE,CAAA;IAClB,IAAI,EAAE,MAAM,CAAA;CACb;AAED,wBAAsB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAkBnF"}
@@ -0,0 +1,23 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { homedir } from 'node:os';
3
+ import { dirname, join, resolve } from 'node:path';
4
+ export async function loadIgnoreFile(startDir) {
5
+ const home = homedir();
6
+ let dir = resolve(startDir);
7
+ for (;;) {
8
+ const candidate = join(dir, '.flagsharkignore');
9
+ if (existsSync(candidate)) {
10
+ const raw = readFileSync(candidate, 'utf-8');
11
+ const patterns = raw
12
+ .split('\n')
13
+ .map((line) => line.trim())
14
+ .filter((line) => line.length > 0 && !line.startsWith('#'));
15
+ return { patterns, path: candidate };
16
+ }
17
+ const parent = dirname(dir);
18
+ if (parent === dir || dir === home || dir === '/')
19
+ return null;
20
+ dir = parent;
21
+ }
22
+ }
23
+ //# sourceMappingURL=ignore-file.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ignore-file.js","sourceRoot":"","sources":["../../src/config/ignore-file.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAClD,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AACjC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAOlD,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,QAAgB;IACnD,MAAM,IAAI,GAAG,OAAO,EAAE,CAAA;IACtB,IAAI,GAAG,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAA;IAE3B,SAAS,CAAC;QACR,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,kBAAkB,CAAC,CAAA;QAC/C,IAAI,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;YAC1B,MAAM,GAAG,GAAG,YAAY,CAAC,SAAS,EAAE,OAAO,CAAC,CAAA;YAC5C,MAAM,QAAQ,GAAG,GAAG;iBACjB,KAAK,CAAC,IAAI,CAAC;iBACX,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;iBAC1B,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAA;YAC7D,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,SAAS,EAAE,CAAA;QACtC,CAAC;QACD,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,CAAA;QAC3B,IAAI,MAAM,KAAK,GAAG,IAAI,GAAG,KAAK,IAAI,IAAI,GAAG,KAAK,GAAG;YAAE,OAAO,IAAI,CAAA;QAC9D,GAAG,GAAG,MAAM,CAAA;IACd,CAAC;AACH,CAAC"}
@@ -0,0 +1,7 @@
1
+ export { buildDefaultConfig } from './defaults.js';
2
+ export { loadConfigFile, type LoadedConfig } from './loader.js';
3
+ export { loadIgnoreFile, type LoadedIgnore } from './ignore-file.js';
4
+ export { buildExcluder, type Excluder, type EffectiveRules, type BuildExcluderInput } from './excluder.js';
5
+ export { expandPresets, PRESETS } from './presets.js';
6
+ export { FlagsharkConfigSchema, type FlagsharkConfig, type ExcludesConfig, type SuppressConfig, type PathRuleConfig, type OutputConfig, type PresetName, } from './schema.js';
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/config/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAA;AAClD,OAAO,EAAE,cAAc,EAAE,KAAK,YAAY,EAAE,MAAM,aAAa,CAAA;AAC/D,OAAO,EAAE,cAAc,EAAE,KAAK,YAAY,EAAE,MAAM,kBAAkB,CAAA;AACpE,OAAO,EAAE,aAAa,EAAE,KAAK,QAAQ,EAAE,KAAK,cAAc,EAAE,KAAK,kBAAkB,EAAE,MAAM,eAAe,CAAA;AAC1G,OAAO,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AACrD,OAAO,EACL,qBAAqB,EACrB,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,cAAc,EACnB,KAAK,cAAc,EACnB,KAAK,YAAY,EACjB,KAAK,UAAU,GAChB,MAAM,aAAa,CAAA"}
@@ -0,0 +1,7 @@
1
+ export { buildDefaultConfig } from './defaults.js';
2
+ export { loadConfigFile } from './loader.js';
3
+ export { loadIgnoreFile } from './ignore-file.js';
4
+ export { buildExcluder } from './excluder.js';
5
+ export { expandPresets, PRESETS } from './presets.js';
6
+ export { FlagsharkConfigSchema, } from './schema.js';
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/config/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAA;AAClD,OAAO,EAAE,cAAc,EAAqB,MAAM,aAAa,CAAA;AAC/D,OAAO,EAAE,cAAc,EAAqB,MAAM,kBAAkB,CAAA;AACpE,OAAO,EAAE,aAAa,EAA+D,MAAM,eAAe,CAAA;AAC1G,OAAO,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AACrD,OAAO,EACL,qBAAqB,GAOtB,MAAM,aAAa,CAAA"}
@@ -0,0 +1,7 @@
1
+ import { type FlagsharkConfig } from './schema.js';
2
+ export interface LoadedConfig {
3
+ config: FlagsharkConfig;
4
+ path: string;
5
+ }
6
+ export declare function loadConfigFile(startDir: string): Promise<LoadedConfig | null>;
7
+ //# sourceMappingURL=loader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../src/config/loader.ts"],"names":[],"mappings":"AAMA,OAAO,EAAyB,KAAK,eAAe,EAAE,MAAM,aAAa,CAAA;AAIzE,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,eAAe,CAAA;IACvB,IAAI,EAAE,MAAM,CAAA;CACb;AAED,wBAAsB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAenF"}
@@ -0,0 +1,47 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { homedir } from 'node:os';
3
+ import { dirname, join, resolve } from 'node:path';
4
+ import { parse as parseYaml, YAMLParseError } from 'yaml';
5
+ import { FlagsharkConfigSchema } from './schema.js';
6
+ const FILENAMES = ['.flagshark.yml', '.flagshark.yaml'];
7
+ export async function loadConfigFile(startDir) {
8
+ const home = homedir();
9
+ let dir = resolve(startDir);
10
+ for (;;) {
11
+ for (const name of FILENAMES) {
12
+ const candidate = join(dir, name);
13
+ if (existsSync(candidate)) {
14
+ return readAndValidate(candidate);
15
+ }
16
+ }
17
+ const parent = dirname(dir);
18
+ if (parent === dir || dir === home || dir === '/')
19
+ return null;
20
+ dir = parent;
21
+ }
22
+ }
23
+ function readAndValidate(path) {
24
+ const raw = readFileSync(path, 'utf-8');
25
+ let parsed;
26
+ try {
27
+ parsed = parseYaml(raw);
28
+ }
29
+ catch (err) {
30
+ if (err instanceof YAMLParseError) {
31
+ throw new Error(`Invalid YAML in ${path}: ${err.message}`);
32
+ }
33
+ throw err;
34
+ }
35
+ if (parsed == null || typeof parsed !== 'object') {
36
+ return { config: FlagsharkConfigSchema.parse({}), path };
37
+ }
38
+ const result = FlagsharkConfigSchema.safeParse(parsed);
39
+ if (!result.success) {
40
+ const issues = result.error.issues
41
+ .map((i) => `${i.path.join('.') || '(root)'}: ${i.message}`)
42
+ .join('; ');
43
+ throw new Error(`Invalid .flagshark.yml at ${path}: ${issues}`);
44
+ }
45
+ return { config: result.data, path };
46
+ }
47
+ //# sourceMappingURL=loader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loader.js","sourceRoot":"","sources":["../../src/config/loader.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAClD,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AACjC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAElD,OAAO,EAAE,KAAK,IAAI,SAAS,EAAE,cAAc,EAAE,MAAM,MAAM,CAAA;AAEzD,OAAO,EAAE,qBAAqB,EAAwB,MAAM,aAAa,CAAA;AAEzE,MAAM,SAAS,GAAG,CAAC,gBAAgB,EAAE,iBAAiB,CAAC,CAAA;AAOvD,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,QAAgB;IACnD,MAAM,IAAI,GAAG,OAAO,EAAE,CAAA;IACtB,IAAI,GAAG,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAA;IAE3B,SAAS,CAAC;QACR,KAAK,MAAM,IAAI,IAAI,SAAS,EAAE,CAAC;YAC7B,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,CAAA;YACjC,IAAI,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;gBAC1B,OAAO,eAAe,CAAC,SAAS,CAAC,CAAA;YACnC,CAAC;QACH,CAAC;QACD,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,CAAA;QAC3B,IAAI,MAAM,KAAK,GAAG,IAAI,GAAG,KAAK,IAAI,IAAI,GAAG,KAAK,GAAG;YAAE,OAAO,IAAI,CAAA;QAC9D,GAAG,GAAG,MAAM,CAAA;IACd,CAAC;AACH,CAAC;AAED,SAAS,eAAe,CAAC,IAAY;IACnC,MAAM,GAAG,GAAG,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;IAEvC,IAAI,MAAe,CAAA;IACnB,IAAI,CAAC;QACH,MAAM,GAAG,SAAS,CAAC,GAAG,CAAC,CAAA;IACzB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,GAAG,YAAY,cAAc,EAAE,CAAC;YAClC,MAAM,IAAI,KAAK,CAAC,mBAAmB,IAAI,KAAK,GAAG,CAAC,OAAO,EAAE,CAAC,CAAA;QAC5D,CAAC;QACD,MAAM,GAAG,CAAA;IACX,CAAC;IAED,IAAI,MAAM,IAAI,IAAI,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;QACjD,OAAO,EAAE,MAAM,EAAE,qBAAqB,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,CAAA;IAC1D,CAAC;IAED,MAAM,MAAM,GAAG,qBAAqB,CAAC,SAAS,CAAC,MAAM,CAAC,CAAA;IACtD,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM;aAC/B,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,QAAQ,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC;aAC3D,IAAI,CAAC,IAAI,CAAC,CAAA;QACb,MAAM,IAAI,KAAK,CAAC,6BAA6B,IAAI,KAAK,MAAM,EAAE,CAAC,CAAA;IACjE,CAAC;IACD,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,IAAI,EAAE,IAAI,EAAE,CAAA;AACtC,CAAC"}
@@ -0,0 +1,11 @@
1
+ import type { PresetName } from './schema.js';
2
+ /**
3
+ * Built-in exclude presets. Each preset expands to a list of gitignore-style
4
+ * glob patterns that match common conventions for that category.
5
+ *
6
+ * Test-files patterns are derived from the private flag-shark monorepo's
7
+ * `packages/shared/lib/test-files.ts` `isTestFile()` helper — credit there.
8
+ */
9
+ export declare const PRESETS: Readonly<Record<PresetName, readonly string[]>>;
10
+ export declare function expandPresets(names: readonly PresetName[]): string[];
11
+ //# sourceMappingURL=presets.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"presets.d.ts","sourceRoot":"","sources":["../../src/config/presets.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAE7C;;;;;;GAMG;AACH,eAAO,MAAM,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC,CA6ClE,CAAA;AAEF,wBAAgB,aAAa,CAAC,KAAK,EAAE,SAAS,UAAU,EAAE,GAAG,MAAM,EAAE,CAcpE"}
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Built-in exclude presets. Each preset expands to a list of gitignore-style
3
+ * glob patterns that match common conventions for that category.
4
+ *
5
+ * Test-files patterns are derived from the private flag-shark monorepo's
6
+ * `packages/shared/lib/test-files.ts` `isTestFile()` helper — credit there.
7
+ */
8
+ export const PRESETS = Object.freeze({
9
+ 'test-files': Object.freeze([
10
+ // JavaScript / TypeScript
11
+ '**/*.test.ts', '**/*.test.tsx', '**/*.test.js', '**/*.test.jsx',
12
+ '**/*.test.mjs', '**/*.test.cjs',
13
+ '**/*.spec.ts', '**/*.spec.tsx', '**/*.spec.js', '**/*.spec.jsx',
14
+ // Go
15
+ '**/*_test.go',
16
+ // Python
17
+ '**/*_test.py', '**/test_*.py',
18
+ // Java / Kotlin
19
+ '**/*Test.java', '**/*Tests.java', '**/*Test.kt', '**/*Tests.kt',
20
+ // Swift
21
+ '**/*Tests.swift',
22
+ // Ruby
23
+ '**/*_spec.rb', '**/spec/**/*.rb',
24
+ // C# / .NET
25
+ '**/*Test.cs', '**/*Tests.cs',
26
+ // PHP
27
+ '**/*Test.php',
28
+ // Rust
29
+ '**/tests/**',
30
+ // Directories that always indicate tests
31
+ '**/__tests__/**', '**/test/**',
32
+ ]),
33
+ 'snapshots': Object.freeze([
34
+ '**/*.snap',
35
+ '**/__snapshots__/**',
36
+ ]),
37
+ 'examples': Object.freeze([
38
+ 'examples/**', 'example/**',
39
+ 'demo/**', 'demos/**',
40
+ ]),
41
+ 'stories': Object.freeze([
42
+ '**/*.stories.ts', '**/*.stories.tsx',
43
+ '**/*.stories.js', '**/*.stories.jsx',
44
+ '**/*.story.ts', '**/*.story.tsx',
45
+ ]),
46
+ 'fixtures': Object.freeze([
47
+ '**/__fixtures__/**', '**/fixtures/**',
48
+ ]),
49
+ 'generated': Object.freeze([
50
+ '**/*.generated.ts', '**/*.generated.js',
51
+ '**/*.gen.go', '**/generated/**',
52
+ ]),
53
+ });
54
+ export function expandPresets(names) {
55
+ const seen = new Set();
56
+ const out = [];
57
+ for (const name of names) {
58
+ const patterns = PRESETS[name];
59
+ if (!patterns)
60
+ continue;
61
+ for (const pattern of patterns) {
62
+ if (!seen.has(pattern)) {
63
+ seen.add(pattern);
64
+ out.push(pattern);
65
+ }
66
+ }
67
+ }
68
+ return out;
69
+ }
70
+ //# sourceMappingURL=presets.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"presets.js","sourceRoot":"","sources":["../../src/config/presets.ts"],"names":[],"mappings":"AAEA;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,OAAO,GAAoD,MAAM,CAAC,MAAM,CAAC;IACpF,YAAY,EAAE,MAAM,CAAC,MAAM,CAAC;QAC1B,0BAA0B;QAC1B,cAAc,EAAE,eAAe,EAAE,cAAc,EAAE,eAAe;QAChE,eAAe,EAAE,eAAe;QAChC,cAAc,EAAE,eAAe,EAAE,cAAc,EAAE,eAAe;QAChE,KAAK;QACL,cAAc;QACd,SAAS;QACT,cAAc,EAAE,cAAc;QAC9B,gBAAgB;QAChB,eAAe,EAAE,gBAAgB,EAAE,aAAa,EAAE,cAAc;QAChE,QAAQ;QACR,iBAAiB;QACjB,OAAO;QACP,cAAc,EAAE,iBAAiB;QACjC,YAAY;QACZ,aAAa,EAAE,cAAc;QAC7B,MAAM;QACN,cAAc;QACd,OAAO;QACP,aAAa;QACb,yCAAyC;QACzC,iBAAiB,EAAE,YAAY;KAChC,CAAC;IACF,WAAW,EAAE,MAAM,CAAC,MAAM,CAAC;QACzB,WAAW;QACX,qBAAqB;KACtB,CAAC;IACF,UAAU,EAAE,MAAM,CAAC,MAAM,CAAC;QACxB,aAAa,EAAE,YAAY;QAC3B,SAAS,EAAE,UAAU;KACtB,CAAC;IACF,SAAS,EAAE,MAAM,CAAC,MAAM,CAAC;QACvB,iBAAiB,EAAE,kBAAkB;QACrC,iBAAiB,EAAE,kBAAkB;QACrC,eAAe,EAAE,gBAAgB;KAClC,CAAC;IACF,UAAU,EAAE,MAAM,CAAC,MAAM,CAAC;QACxB,oBAAoB,EAAE,gBAAgB;KACvC,CAAC;IACF,WAAW,EAAE,MAAM,CAAC,MAAM,CAAC;QACzB,mBAAmB,EAAE,mBAAmB;QACxC,aAAa,EAAE,iBAAiB;KACjC,CAAC;CACH,CAAC,CAAA;AAEF,MAAM,UAAU,aAAa,CAAC,KAA4B;IACxD,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAA;IAC9B,MAAM,GAAG,GAAa,EAAE,CAAA;IACxB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;QAC9B,IAAI,CAAC,QAAQ;YAAE,SAAQ;QACvB,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;YAC/B,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;gBACvB,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,CAAA;gBACjB,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;YACnB,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC"}