@flagshark/core 2.0.0 → 2.1.0

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 (68) hide show
  1. package/README.md +2 -0
  2. package/dist/config/schema.d.ts +112 -0
  3. package/dist/config/schema.d.ts.map +1 -1
  4. package/dist/config/schema.js +63 -0
  5. package/dist/config/schema.js.map +1 -1
  6. package/dist/detection/detectors/typescript.d.ts.map +1 -1
  7. package/dist/detection/detectors/typescript.js +41 -0
  8. package/dist/detection/detectors/typescript.js.map +1 -1
  9. package/dist/detection/feature-flag.d.ts +28 -0
  10. package/dist/detection/feature-flag.d.ts.map +1 -1
  11. package/dist/detection/helpers.d.ts +16 -0
  12. package/dist/detection/helpers.d.ts.map +1 -1
  13. package/dist/detection/helpers.js +117 -6
  14. package/dist/detection/helpers.js.map +1 -1
  15. package/dist/detection/import-graph.d.ts +234 -0
  16. package/dist/detection/import-graph.d.ts.map +1 -0
  17. package/dist/detection/import-graph.js +641 -0
  18. package/dist/detection/import-graph.js.map +1 -0
  19. package/dist/detection/interface.d.ts +57 -0
  20. package/dist/detection/interface.d.ts.map +1 -1
  21. package/dist/detection/interface.js.map +1 -1
  22. package/dist/detection/polyglot-analyzer.d.ts +8 -0
  23. package/dist/detection/polyglot-analyzer.d.ts.map +1 -1
  24. package/dist/detection/polyglot-analyzer.js +2 -0
  25. package/dist/detection/polyglot-analyzer.js.map +1 -1
  26. package/dist/detection/tree-sitter/engine.d.ts.map +1 -1
  27. package/dist/detection/tree-sitter/engine.js +62 -15
  28. package/dist/detection/tree-sitter/engine.js.map +1 -1
  29. package/dist/detection/tree-sitter/parser-cache.d.ts +20 -0
  30. package/dist/detection/tree-sitter/parser-cache.d.ts.map +1 -1
  31. package/dist/detection/tree-sitter/parser-cache.js +32 -1
  32. package/dist/detection/tree-sitter/parser-cache.js.map +1 -1
  33. package/dist/output/json.d.ts.map +1 -1
  34. package/dist/output/json.js +28 -0
  35. package/dist/output/json.js.map +1 -1
  36. package/dist/output/markdown.d.ts.map +1 -1
  37. package/dist/output/markdown.js +47 -1
  38. package/dist/output/markdown.js.map +1 -1
  39. package/dist/output/text.d.ts.map +1 -1
  40. package/dist/output/text.js +85 -0
  41. package/dist/output/text.js.map +1 -1
  42. package/dist/providers/cross-reference.d.ts +24 -2
  43. package/dist/providers/cross-reference.d.ts.map +1 -1
  44. package/dist/providers/cross-reference.js +74 -7
  45. package/dist/providers/cross-reference.js.map +1 -1
  46. package/dist/providers/interface.d.ts +89 -2
  47. package/dist/providers/interface.d.ts.map +1 -1
  48. package/dist/providers/launchdarkly/client.d.ts +12 -0
  49. package/dist/providers/launchdarkly/client.d.ts.map +1 -1
  50. package/dist/providers/launchdarkly/client.js +163 -23
  51. package/dist/providers/launchdarkly/client.js.map +1 -1
  52. package/dist/providers/launchdarkly/types.d.ts +286 -0
  53. package/dist/providers/launchdarkly/types.d.ts.map +1 -1
  54. package/dist/providers/launchdarkly/types.js +56 -0
  55. package/dist/providers/launchdarkly/types.js.map +1 -1
  56. package/dist/providers/orchestrate.d.ts +34 -2
  57. package/dist/providers/orchestrate.d.ts.map +1 -1
  58. package/dist/providers/orchestrate.js +59 -7
  59. package/dist/providers/orchestrate.js.map +1 -1
  60. package/dist/scan-repo.d.ts +28 -0
  61. package/dist/scan-repo.d.ts.map +1 -1
  62. package/dist/scan-repo.js +290 -4
  63. package/dist/scan-repo.js.map +1 -1
  64. package/dist/staleness.d.ts +31 -1
  65. package/dist/staleness.d.ts.map +1 -1
  66. package/dist/staleness.js +55 -8
  67. package/dist/staleness.js.map +1 -1
  68. package/package.json +1 -1
@@ -10,15 +10,47 @@ export interface OrchestratePlatformsOptions {
10
10
  /** When true, skip cache for this run. */
11
11
  noCache?: boolean;
12
12
  signal?: AbortSignal;
13
+ /**
14
+ * Staleness threshold in days. Passed through to cross-reference so it
15
+ * can emit `platform-too-old` for flags whose platform-side creationDate
16
+ * exceeds the threshold. When omitted, the platform-too-old signal is
17
+ * never emitted.
18
+ */
19
+ thresholdDays?: number;
13
20
  /**
14
21
  * @internal — test seam. When set, used instead of platform.listFlags().
15
22
  * Allows tests to bypass network without monkey-patching globalThis.fetch.
16
23
  */
17
24
  listFlagsOverride?: (signal?: AbortSignal) => Promise<PlatformFlag[]>;
18
25
  }
26
+ /**
27
+ * Returned alongside platform signals: the names of flags excluded from
28
+ * staleness checks because a platform marked them permanent, indexed by
29
+ * the platform's displayName. Output formatters surface this directly
30
+ * so users see WHY a permanent flag isn't in the stale table.
31
+ */
32
+ export interface OrchestrateResult {
33
+ signals: Map<string, PlatformSignal[]>;
34
+ permanentByPlatform: Record<string, string[]>;
35
+ /**
36
+ * Per-flag platform-side metadata (tags, maintainer, status). Keyed
37
+ * by detected flag name. Populated only for flags that matched a
38
+ * platform integration AND the platform exposed the data. When
39
+ * multiple platforms report different metadata for the same flag,
40
+ * the LAST platform processed wins — the typical case is one
41
+ * platform per project anyway.
42
+ */
43
+ metadataByFlag: Map<string, {
44
+ tags?: string[];
45
+ maintainer?: string;
46
+ status?: 'new' | 'active' | 'inactive' | 'launched';
47
+ }>;
48
+ }
19
49
  /**
20
50
  * Runs each configured platform integration. Logs warnings on individual
21
- * platform failures and continues. Returns merged signals keyed by flag name.
51
+ * platform failures and continues. Returns merged signals keyed by flag name
52
+ * plus a per-platform record of flags marked permanent (suppressed from
53
+ * staleness; surfaced separately so users see what was excluded and why).
22
54
  */
23
- export declare function orchestratePlatforms(opts: OrchestratePlatformsOptions): Promise<Map<string, PlatformSignal[]>>;
55
+ export declare function orchestratePlatforms(opts: OrchestratePlatformsOptions): Promise<OrchestrateResult>;
24
56
  //# sourceMappingURL=orchestrate.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"orchestrate.d.ts","sourceRoot":"","sources":["../../src/providers/orchestrate.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAClE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAA;AAC/D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAEjD,MAAM,WAAW,2BAA2B;IAC1C,iFAAiF;IACjF,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAA;IACpD,4CAA4C;IAC5C,aAAa,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,EAAE,CAAC,CAAA;IACzC,MAAM,EAAE,UAAU,CAAA;IAClB,0CAA0C;IAC1C,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,MAAM,CAAC,EAAE,WAAW,CAAA;IACpB;;;OAGG;IACH,iBAAiB,CAAC,EAAE,CAAC,MAAM,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,YAAY,EAAE,CAAC,CAAA;CACtE;AAED;;;GAGG;AACH,wBAAsB,oBAAoB,CACxC,IAAI,EAAE,2BAA2B,GAChC,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,cAAc,EAAE,CAAC,CAAC,CAsCxC"}
1
+ {"version":3,"file":"orchestrate.d.ts","sourceRoot":"","sources":["../../src/providers/orchestrate.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAClE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAA;AAC/D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAEjD,MAAM,WAAW,2BAA2B;IAC1C,iFAAiF;IACjF,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAA;IACpD,4CAA4C;IAC5C,aAAa,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,EAAE,CAAC,CAAA;IACzC,MAAM,EAAE,UAAU,CAAA;IAClB,0CAA0C;IAC1C,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,MAAM,CAAC,EAAE,WAAW,CAAA;IACpB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB;;;OAGG;IACH,iBAAiB,CAAC,EAAE,CAAC,MAAM,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,YAAY,EAAE,CAAC,CAAA;CACtE;AAED;;;;;GAKG;AACH,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,cAAc,EAAE,CAAC,CAAA;IACtC,mBAAmB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAA;IAC7C;;;;;;;OAOG;IACH,cAAc,EAAE,GAAG,CACjB,MAAM,EACN;QAAE,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,KAAK,GAAG,QAAQ,GAAG,UAAU,GAAG,UAAU,CAAA;KAAE,CAC9F,CAAA;CACF;AAED;;;;;GAKG;AACH,wBAAsB,oBAAoB,CACxC,IAAI,EAAE,2BAA2B,GAChC,OAAO,CAAC,iBAAiB,CAAC,CA8F5B"}
@@ -3,12 +3,17 @@ import { crossReference, mergePlatformSignals } from './cross-reference.js';
3
3
  import { computeCacheKey, loadPlatformFlagsCached } from './cache.js';
4
4
  /**
5
5
  * Runs each configured platform integration. Logs warnings on individual
6
- * platform failures and continues. Returns merged signals keyed by flag name.
6
+ * platform failures and continues. Returns merged signals keyed by flag name
7
+ * plus a per-platform record of flags marked permanent (suppressed from
8
+ * staleness; surfaced separately so users see what was excluded and why).
7
9
  */
8
10
  export async function orchestratePlatforms(opts) {
9
11
  const out = new Map();
10
- if (!opts.platformsConfig)
11
- return out;
12
+ const permanentByPlatform = {};
13
+ const metadataByFlag = new Map();
14
+ if (!opts.platformsConfig) {
15
+ return { signals: out, permanentByPlatform, metadataByFlag };
16
+ }
12
17
  for (const [name, rawConfig] of Object.entries(opts.platformsConfig)) {
13
18
  const def = findPlatform(name);
14
19
  if (!def) {
@@ -21,7 +26,13 @@ export async function orchestratePlatforms(opts) {
21
26
  continue;
22
27
  }
23
28
  const tokenEnv = rawConfig.token_env ?? def.defaultTokenEnv;
24
- const token = process.env[tokenEnv];
29
+ // Trim whitespace before passing to the auth header. A common foot-gun:
30
+ // tokens copied from a UI or sourced via `.env`/`export` end up with a
31
+ // trailing newline or leading space, which fetch propagates verbatim
32
+ // into `Authorization:` and the platform rejects as 401. Trim once
33
+ // here so every platform's createClient sees a clean string.
34
+ const rawToken = process.env[tokenEnv];
35
+ const token = rawToken?.trim() ?? '';
25
36
  if (!token) {
26
37
  opts.logger.warn(`${def.displayName}: missing ${tokenEnv}; skipping platform integration`);
27
38
  continue;
@@ -32,13 +43,54 @@ export async function orchestratePlatforms(opts) {
32
43
  const flags = opts.listFlagsOverride
33
44
  ? await opts.listFlagsOverride(opts.signal)
34
45
  : await loadPlatformFlagsCached(client, cacheKey, { noCache: opts.noCache, signal: opts.signal });
35
- const signals = crossReference(opts.detectedFlags, flags, def.displayName);
46
+ const signals = crossReference(opts.detectedFlags, flags, def.displayName, {
47
+ thresholdDays: opts.thresholdDays,
48
+ });
36
49
  mergePlatformSignals(out, signals);
50
+ // Track which flags this platform marked permanent so output
51
+ // formatters can show 'N flag(s) excluded as permanent in
52
+ // <Platform>: a, b, c'. Read the signals MAP we just merged in
53
+ // (not `signals` directly — same content, but consistent source).
54
+ const platformPermanent = [];
55
+ for (const [flagName, sigList] of signals) {
56
+ if (sigList.some((s) => s.type === 'platform-permanent')) {
57
+ platformPermanent.push(flagName);
58
+ }
59
+ }
60
+ if (platformPermanent.length > 0) {
61
+ permanentByPlatform[def.displayName] = platformPermanent.sort();
62
+ }
63
+ // Surface platform-side metadata (tags, maintainer, activity
64
+ // status) for every detected flag that matched this platform.
65
+ // Output formatters consume this to enrich the per-row display
66
+ // without re-querying the platform.
67
+ for (const flag of flags) {
68
+ if (!opts.detectedFlags.has(flag.key))
69
+ continue;
70
+ const hasMetadata = (flag.tags && flag.tags.length > 0) || flag.maintainer || flag.status;
71
+ if (!hasMetadata)
72
+ continue;
73
+ metadataByFlag.set(flag.key, {
74
+ tags: flag.tags && flag.tags.length > 0 ? flag.tags : undefined,
75
+ maintainer: flag.maintainer,
76
+ status: flag.status,
77
+ });
78
+ }
37
79
  }
38
80
  catch (err) {
39
- opts.logger.warn(`${def.displayName}: ${err.message}. Continuing with code-only signals.`);
81
+ // 401/403 are by far the most common failure mode here and the cause
82
+ // is almost always confusable token shapes (SDK key vs. API access
83
+ // token, project-scoped token pointed at the wrong project, project
84
+ // KEY vs. project NAME in the YAML). Surface a hint inline so users
85
+ // don't have to guess.
86
+ const message = err.message;
87
+ const isAuthError = /\b(401|403|Unauthorized|Forbidden)\b/i.test(message);
88
+ const hint = isAuthError
89
+ ? ` (check token type — API access tokens, not SDK keys, and the project key matches a project the token can read)`
90
+ : '';
91
+ opts.logger.warn(`${def.displayName}: ${message}${hint}. Continuing with code-only signals.`);
40
92
  }
41
93
  }
42
- return out;
94
+ return { signals: out, permanentByPlatform, metadataByFlag };
43
95
  }
44
96
  //# sourceMappingURL=orchestrate.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"orchestrate.js","sourceRoot":"","sources":["../../src/providers/orchestrate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAC5C,OAAO,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAA;AAC3E,OAAO,EAAE,eAAe,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAA;AAqBrE;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,IAAiC;IAEjC,MAAM,GAAG,GAAG,IAAI,GAAG,EAA4B,CAAA;IAC/C,IAAI,CAAC,IAAI,CAAC,eAAe;QAAE,OAAO,GAAG,CAAA;IAErC,KAAK,MAAM,CAAC,IAAI,EAAE,SAAS,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,eAAe,CAAC,EAAE,CAAC;QACrE,MAAM,GAAG,GAAG,YAAY,CAAC,IAAI,CAAC,CAAA;QAC9B,IAAI,CAAC,GAAG,EAAE,CAAC;YACT,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,qBAAqB,IAAI,cAAc,CAAC,CAAA;YACzD,SAAQ;QACV,CAAC;QAED,MAAM,MAAM,GAAG,GAAG,CAAC,YAAY,CAAC,SAAS,CAAC,SAAS,CAAC,CAAA;QACpD,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YACpB,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,gCAAgC,IAAI,MAAM,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC,CAAA;YAClF,SAAQ;QACV,CAAC;QAED,MAAM,QAAQ,GAAI,SAAoC,CAAC,SAAS,IAAI,GAAG,CAAC,eAAe,CAAA;QACvF,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAA;QACnC,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,GAAG,CAAC,WAAW,aAAa,QAAQ,iCAAiC,CAAC,CAAA;YAC1F,SAAQ;QACV,CAAC;QAED,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,GAAG,CAAC,YAAY,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,CAAA;YACnD,MAAM,QAAQ,GAAG,eAAe,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,CAAA;YAC1D,MAAM,KAAK,GAAG,IAAI,CAAC,iBAAiB;gBAClC,CAAC,CAAC,MAAM,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,MAAM,CAAC;gBAC3C,CAAC,CAAC,MAAM,uBAAuB,CAAC,MAAM,EAAE,QAAQ,EAAE,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAA;YACnG,MAAM,OAAO,GAAG,cAAc,CAAC,IAAI,CAAC,aAAa,EAAE,KAAK,EAAE,GAAG,CAAC,WAAW,CAAC,CAAA;YAC1E,oBAAoB,CAAC,GAAG,EAAE,OAAO,CAAC,CAAA;QACpC,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,GAAG,CAAC,WAAW,KAAM,GAAa,CAAC,OAAO,sCAAsC,CAAC,CAAA;QACvG,CAAC;IACH,CAAC;IAED,OAAO,GAAG,CAAA;AACZ,CAAC"}
1
+ {"version":3,"file":"orchestrate.js","sourceRoot":"","sources":["../../src/providers/orchestrate.ts"],"names":[],"mappings":"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"}
@@ -89,6 +89,34 @@ export interface ScanRepoResult {
89
89
  excludedCount?: number;
90
90
  /** Relative paths of excluded files. Only populated when collectExcludedPaths is true. */
91
91
  excludedPaths?: string[];
92
+ /**
93
+ * Count of files where the detector raised at least one parse error
94
+ * (i.e. the file was scanned but the tree-sitter/regex pass bailed). Mirrors
95
+ * `RepositoryAnalysisResult.parseErrorCount` and is exposed here so output
96
+ * formatters (text, json, markdown) can tell the user up front when a
97
+ * non-trivial slice of their code couldn't be analysed. Optional for
98
+ * backward compatibility with test fixtures and external callers that
99
+ * construct ScanRepoResult by hand; treat absent as `0`.
100
+ */
101
+ parseErrorCount?: number;
102
+ /**
103
+ * Flag names that were detected in code AND found in a platform but
104
+ * marked as permanent (LD's `temporary: false`, or equivalent on other
105
+ * platforms). These are filtered out of `staleFlags` because they're
106
+ * intentionally long-lived; surfacing them here lets output formatters
107
+ * show users WHY a flag they expected to see in the table isn't there.
108
+ * Empty array (not undefined) when no platforms are configured or no
109
+ * matches were permanent. Per-platform breakdown lives in the
110
+ * `permanentByPlatform` field below.
111
+ */
112
+ excludedPermanent?: string[];
113
+ /**
114
+ * Per-platform breakdown of `excludedPermanent`. Keyed by the
115
+ * platform's displayName (e.g. 'LaunchDarkly'). Lets the output
116
+ * formatter print 'X flags excluded as permanent in LaunchDarkly'
117
+ * (not just 'in some platform somewhere').
118
+ */
119
+ permanentByPlatform?: Record<string, string[]>;
92
120
  /** Diagnostic — populated only when logger.debug level is active or callers explicitly opt in. */
93
121
  effectiveExcludes?: EffectiveRules;
94
122
  }
@@ -1 +1 @@
1
- {"version":3,"file":"scan-repo.d.ts","sourceRoot":"","sources":["../src/scan-repo.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAaH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC/C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAA;AACzD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AAE1D,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAA;IACnC,IAAI,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAA;IAClC,IAAI,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAA;IAClC,KAAK,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAA;CACpC;AAED,MAAM,WAAW,eAAe;IAC9B;;;;OAIG;IACH,GAAG,EAAE,MAAM,CAAA;IAEX;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAElB;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,CAAA;IAEb;;;;OAIG;IACH,MAAM,CAAC,EAAE,WAAW,CAAA;IAEpB;;OAEG;IACH,MAAM,CAAC,EAAE,UAAU,CAAA;IAEnB,2EAA2E;IAC3E,MAAM,CAAC,EAAE,OAAO,GAAG,aAAa,CAAA;IAEhC;;;OAGG;IACH,MAAM,CAAC,EAAE,eAAe,CAAA;IAExB;;OAEG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAA;IAElB;;OAEG;IACH,YAAY,CAAC,EAAE,OAAO,CAAA;IAEtB;;OAEG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAA;IAE9B,qDAAqD;IACrD,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB;AAED,MAAM,WAAW,cAAc;IAC7B,uEAAuE;IACvE,UAAU,EAAE,MAAM,CAAA;IAElB,yDAAyD;IACzD,YAAY,EAAE,MAAM,CAAA;IAEpB;;;;OAIG;IACH,UAAU,EAAE,SAAS,EAAE,CAAA;IAEvB;;;OAGG;IACH,iBAAiB,EAAE,MAAM,EAAE,CAAA;IAE3B,0EAA0E;IAC1E,iBAAiB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAEzC;;;OAGG;IACH,WAAW,EAAE,MAAM,CAAA;IAEnB,uDAAuD;IACvD,YAAY,EAAE,MAAM,CAAA;IAEpB,uFAAuF;IACvF,aAAa,CAAC,EAAE,MAAM,CAAA;IAEtB,0FAA0F;IAC1F,aAAa,CAAC,EAAE,MAAM,EAAE,CAAA;IAExB,kGAAkG;IAClG,iBAAiB,CAAC,EAAE,cAAc,CAAA;CACnC;AAUD,wBAAsB,QAAQ,CAAC,IAAI,EAAE,eAAe,GAAG,OAAO,CAAC,cAAc,CAAC,CAmF7E"}
1
+ {"version":3,"file":"scan-repo.d.ts","sourceRoot":"","sources":["../src/scan-repo.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAgBH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC/C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAA;AACzD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AAE1D,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAA;IACnC,IAAI,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAA;IAClC,IAAI,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAA;IAClC,KAAK,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAA;CACpC;AAED,MAAM,WAAW,eAAe;IAC9B;;;;OAIG;IACH,GAAG,EAAE,MAAM,CAAA;IAEX;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAElB;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,CAAA;IAEb;;;;OAIG;IACH,MAAM,CAAC,EAAE,WAAW,CAAA;IAEpB;;OAEG;IACH,MAAM,CAAC,EAAE,UAAU,CAAA;IAEnB,2EAA2E;IAC3E,MAAM,CAAC,EAAE,OAAO,GAAG,aAAa,CAAA;IAEhC;;;OAGG;IACH,MAAM,CAAC,EAAE,eAAe,CAAA;IAExB;;OAEG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAA;IAElB;;OAEG;IACH,YAAY,CAAC,EAAE,OAAO,CAAA;IAEtB;;OAEG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAA;IAE9B,qDAAqD;IACrD,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB;AAED,MAAM,WAAW,cAAc;IAC7B,uEAAuE;IACvE,UAAU,EAAE,MAAM,CAAA;IAElB,yDAAyD;IACzD,YAAY,EAAE,MAAM,CAAA;IAEpB;;;;OAIG;IACH,UAAU,EAAE,SAAS,EAAE,CAAA;IAEvB;;;OAGG;IACH,iBAAiB,EAAE,MAAM,EAAE,CAAA;IAE3B,0EAA0E;IAC1E,iBAAiB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAEzC;;;OAGG;IACH,WAAW,EAAE,MAAM,CAAA;IAEnB,uDAAuD;IACvD,YAAY,EAAE,MAAM,CAAA;IAEpB,uFAAuF;IACvF,aAAa,CAAC,EAAE,MAAM,CAAA;IAEtB,0FAA0F;IAC1F,aAAa,CAAC,EAAE,MAAM,EAAE,CAAA;IAExB;;;;;;;;OAQG;IACH,eAAe,CAAC,EAAE,MAAM,CAAA;IAExB;;;;;;;;;OASG;IACH,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAA;IAE5B;;;;;OAKG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAA;IAE9C,kGAAkG;IAClG,iBAAiB,CAAC,EAAE,cAAc,CAAA;CACnC;AAUD,wBAAsB,QAAQ,CAAC,IAAI,EAAE,eAAe,GAAG,OAAO,CAAC,cAAc,CAAC,CAkK7E"}
package/dist/scan-repo.js CHANGED
@@ -4,6 +4,8 @@
4
4
  */
5
5
  import { collectFiles } from './scanner.js';
6
6
  import { createDefaultRegistry, createRegistryWithEngine } from './detection/index.js';
7
+ import { buildImportGraph, isScannedSourceFile, loadTsconfigAliases } from './detection/import-graph.js';
8
+ import { Languages, getImportPattern } from './detection/interface.js';
7
9
  import { PolyglotAnalyzer } from './detection/polyglot-analyzer.js';
8
10
  import { analyzeStaleness } from './staleness.js';
9
11
  import { buildDefaultConfig } from './config/defaults.js';
@@ -47,15 +49,55 @@ export async function scanRepo(opts) {
47
49
  });
48
50
  logger.debug(`Detected ${files.size} candidate files (excluded ${excludedCount})`);
49
51
  const filesScanned = files.size;
50
- const analysisResult = await analyzer.analyzeFiles(files, opts.signal);
51
- const platformSignals = await orchestratePlatforms({
52
+ // Wrapper-aware detection: build the TS/JS import graph and "lift" the SDK
53
+ // gate for any file that transitively reaches a known SDK through 1-N hops
54
+ // of relative imports. We do this by appending a single-line comment to
55
+ // the file content that mentions every reachable SDK pattern; the per-
56
+ // provider import gate (a substring check in helpers.ts / engine.ts) then
57
+ // passes for wrapper-consumer files. Appending to the END of content keeps
58
+ // every flag's line number correct.
59
+ //
60
+ // Direct importers already pass the gate, so the appended marker is a
61
+ // no-op for them. Files with no SDK reach are untouched.
62
+ //
63
+ // Why scan-repo.ts and not PolyglotAnalyzer: PolyglotAnalyzer is a generic
64
+ // multi-language coordinator. The graph is TS/JS-only and SDK-aware; that
65
+ // knowledge lives at the orchestrator layer where we also know the registry.
66
+ const tsJsSdkPatterns = collectSdkPatterns(registry);
67
+ const filesForAnalysis = augmentForWrapperDetection(files, tsJsSdkPatterns, logger, opts.cwd);
68
+ const analysisResult = await analyzer.analyzeFiles(filesForAnalysis, opts.signal);
69
+ // B3: user-configured custom detectors (struct-field-access only today).
70
+ // Layered on top of the standard detection so it never reduces recall,
71
+ // only adds matches — typically for codebases whose flag system bypasses
72
+ // the SDK model entirely (e.g. Mattermost's typed Config().FeatureFlags.X).
73
+ // Detected flags are tagged confidence: 'low' so cleanup pipelines route
74
+ // them to manual review rather than auto-merge.
75
+ if (config.custom_detectors && config.custom_detectors.length > 0) {
76
+ applyCustomDetectors(files, config.custom_detectors, analysisResult.totalFlags, logger);
77
+ }
78
+ const { signals: platformSignals, permanentByPlatform, metadataByFlag, } = await orchestratePlatforms({
52
79
  platformsConfig: config.platforms,
53
80
  detectedFlags: analysisResult.totalFlags,
54
81
  logger,
55
82
  noCache: opts.noCache,
56
83
  signal: opts.signal,
84
+ // Threshold drives the platform-too-old signal in cross-reference.
85
+ // Same threshold the staleness engine uses for code-age, so the two
86
+ // dimensions stay aligned ("if code older than N is stale, so is a
87
+ // platform record older than N").
88
+ thresholdDays: threshold,
89
+ });
90
+ const staleFlags = await analyzeStaleness(analysisResult.totalFlags, {
91
+ thresholdDays: threshold,
92
+ repoRoot: opts.cwd,
93
+ platformSignals,
94
+ platformMetadata: metadataByFlag,
57
95
  });
58
- const staleFlags = await analyzeStaleness(analysisResult.totalFlags, { thresholdDays: threshold, repoRoot: opts.cwd, platformSignals });
96
+ // Flatten the per-platform breakdown into a single sorted, deduplicated
97
+ // list so callers that only want "did anything get excluded?" don't
98
+ // have to walk the platform record themselves. The per-platform record
99
+ // is retained for output formatters that want to attribute correctly.
100
+ const excludedPermanent = Array.from(new Set(Object.values(permanentByPlatform).flat())).sort();
59
101
  const totalFlags = analysisResult.totalFlags.size;
60
102
  const uniqueStaleNames = new Set(staleFlags.map((f) => f.name)).size;
61
103
  const healthScore = totalFlags === 0 ? 100 : Math.round(((totalFlags - uniqueStaleNames) / totalFlags) * 100);
@@ -68,6 +110,30 @@ export async function scanRepo(opts) {
68
110
  .map((f) => f.provider)
69
111
  .filter((p) => p != null && p !== '')),
70
112
  ];
113
+ const scanDuration = Math.round(performance.now() - start);
114
+ // Structured metric line — one per scan, info-level. Same shape as the
115
+ // SaaS-side piranha_metric events so a single dashboard can join them.
116
+ // CloudWatch Logs Insights query: `filter event="flagshark_scan_complete"`.
117
+ // Off by default at debug-only loggers (e.g. the no-op logger used in
118
+ // tests); info-level loggers (CLI, Action) surface them.
119
+ logger.info('flagshark_scan_complete', {
120
+ event: 'flagshark_scan_complete',
121
+ durationMs: scanDuration,
122
+ filesScanned,
123
+ // Defensive ?? 0 fallbacks: collectFiles always returns excludedCount as
124
+ // a number and PolyglotAnalyzer always populates parseErrorCount, so the
125
+ // RHS of these expressions is unreachable today — kept for type safety
126
+ // against future refactors.
127
+ /* v8 ignore next 2 */
128
+ excludedCount: excludedCount ?? 0,
129
+ parseErrorCount: analysisResult.parseErrorCount ?? 0,
130
+ totalFlags,
131
+ staleFlags: uniqueStaleNames,
132
+ healthScore,
133
+ detectedProviders: detectedProviders.length,
134
+ languages: Object.keys(analysisResult.languages).length,
135
+ detectionEngine: opts.engine ?? 'regex',
136
+ });
71
137
  return {
72
138
  totalFlags,
73
139
  filesScanned,
@@ -76,10 +142,230 @@ export async function scanRepo(opts) {
76
142
  // analysisResult.languages is Map<Language, number> — convert to plain object
77
143
  languageBreakdown: Object.fromEntries(analysisResult.languages),
78
144
  healthScore,
79
- scanDuration: Math.round(performance.now() - start),
145
+ scanDuration,
80
146
  excludedCount,
81
147
  excludedPaths,
148
+ parseErrorCount: analysisResult.parseErrorCount,
149
+ excludedPermanent,
150
+ permanentByPlatform,
82
151
  effectiveExcludes: excluder.effectiveRules,
83
152
  };
84
153
  }
154
+ // -- Wrapper-aware detection helpers ------------------------------------------
155
+ /**
156
+ * Pulls every `importPattern` string registered on the language detectors
157
+ * the import graph supports today (TS/JS + Python). These are the seed
158
+ * packages for the graph — a file is "in SDK scope" iff its imports reach
159
+ * one of these via 1-N hops of relative imports.
160
+ *
161
+ * We pull TS/JS and Python together because the seed list is matched
162
+ * substring-style on each spec and both languages share the same matching
163
+ * surface inside the graph. Python-only seeds (`posthog`, `ldclient`) and
164
+ * TS-only seeds (`@launchdarkly/node-server-sdk`) never collide because
165
+ * their syntactic shapes are different.
166
+ */
167
+ function collectSdkPatterns(registry) {
168
+ const patterns = new Set();
169
+ for (const lang of [Languages.TypeScript, Languages.JavaScript, Languages.Python]) {
170
+ const detector = registry.getDetector(lang);
171
+ // Defensive skip: the default registry always populates TS/JS/Python
172
+ // detectors. Custom engines could theoretically omit one, hence the guard.
173
+ /* v8 ignore next */
174
+ if (!detector)
175
+ continue;
176
+ for (const provider of detector.getProviders()) {
177
+ const pat = getImportPattern(provider);
178
+ if (pat)
179
+ patterns.add(pat);
180
+ }
181
+ }
182
+ return [...patterns];
183
+ }
184
+ /**
185
+ * Marker comment appended to TS/JS files that transitively reach an SDK. The
186
+ * existing per-provider import gate is a `content.includes(importPat)` check;
187
+ * mentioning the SDK pattern in this comment satisfies the gate without
188
+ * touching the detector interface or shifting any line numbers (the marker
189
+ * goes at end-of-file, after the last line of real code).
190
+ *
191
+ * The marker text is recognisable so an operator inspecting an instrumented
192
+ * file or a debug log can tell the difference between a real import and our
193
+ * post-hoc marker.
194
+ */
195
+ const WRAPPER_MARKER_PREFIX = '// flagshark-internal: transitively reaches';
196
+ /**
197
+ * Same marker semantics but in Python comment syntax (`#`). When wrapper
198
+ * augmentation extended to .py files (B4), appending a `//` line would
199
+ * make the file unparseable — Python doesn't have `//` comments. The
200
+ * detector's import-gate is a substring `content.includes(importPat)`
201
+ * check that doesn't care WHICH comment syntax wraps the SDK pattern,
202
+ * so language-appropriate markers work transparently.
203
+ */
204
+ const WRAPPER_MARKER_PREFIX_PYTHON = '# flagshark-internal: transitively reaches';
205
+ /**
206
+ * Returns a new Map<filePath, content> where TS/JS files with transitive SDK
207
+ * reach have a single comment line appended that mentions each reachable SDK
208
+ * pattern. Returns the original Map unchanged if no SDK seeds exist (e.g.
209
+ * registry was constructed without TS/JS detectors).
210
+ *
211
+ * Non-TS/JS files are passed through untouched.
212
+ */
213
+ function augmentForWrapperDetection(files, tsJsSdkPatterns, logger, cwd) {
214
+ // Defensive early-return: every default registry contributes at least
215
+ // one SDK pattern, so this branch only fires for hand-built empty
216
+ // registries — not reachable from public scan paths.
217
+ /* v8 ignore next */
218
+ if (tsJsSdkPatterns.length === 0)
219
+ return files;
220
+ // Load tsconfig path aliases from the scan root, then pass them to the
221
+ // graph builder. Most TS monorepos use `@/foo`-style aliases; without
222
+ // this, the transitive wrapper detection stops at every aliased
223
+ // boundary and under-counts. Falls back gracefully — `loadTsconfigAliases`
224
+ // returns null when there's no tsconfig or no aliases declared, in which
225
+ // case the graph behaves exactly as before.
226
+ const aliases = loadTsconfigAliases(cwd);
227
+ if (aliases) {
228
+ logger.debug('tsconfig path aliases loaded', {
229
+ baseUrl: aliases.baseUrl,
230
+ aliasCount: aliases.paths.size,
231
+ });
232
+ }
233
+ const graph = buildImportGraph(files, {
234
+ seedSdkPatterns: tsJsSdkPatterns,
235
+ // Polyglot scope — graph now walks TS/JS *and* Python wrappers so a
236
+ // Python consumer file that does `from .feature_flags import is_enabled`
237
+ // (where feature_flags.py imports `posthog`) is in scope. The option
238
+ // name is legacy from when TS/JS was the only surface; the helper
239
+ // returns true for .py files too. See B4 in the bug inventory.
240
+ isTsJs: isScannedSourceFile,
241
+ aliases: aliases ?? undefined,
242
+ });
243
+ logger.debug('Import graph built', graph.stats);
244
+ // No transitive reach (seeds didn't propagate beyond themselves, or no seeds
245
+ // at all) -> return the original map. Avoids a wasted clone on repos that
246
+ // don't use any flag SDK.
247
+ if (graph.stats.inScopeFiles === 0) {
248
+ return files;
249
+ }
250
+ const augmented = new Map();
251
+ let augmentedCount = 0;
252
+ for (const [filePath, content] of files) {
253
+ const sdks = graph.transitiveSdks.get(filePath);
254
+ if (!sdks || sdks.size === 0) {
255
+ augmented.set(filePath, content);
256
+ continue;
257
+ }
258
+ // Append at end-of-file with a leading newline so we never glue onto a
259
+ // partial last line. Sorting the SDK list keeps the marker deterministic
260
+ // across runs (useful when diffing logs). Use Python comment syntax for
261
+ // .py files; the substring-based import gate doesn't care which prefix
262
+ // wraps the SDK pattern, so language-appropriate markers stay parseable.
263
+ const sdkList = [...sdks].sort().join(' ');
264
+ const prefix = filePath.toLowerCase().endsWith('.py')
265
+ ? WRAPPER_MARKER_PREFIX_PYTHON
266
+ : WRAPPER_MARKER_PREFIX;
267
+ augmented.set(filePath, `${content}\n${prefix} ${sdkList}\n`);
268
+ augmentedCount++;
269
+ }
270
+ logger.debug(`Wrapper-aware detection augmented ${augmentedCount} files`);
271
+ return augmented;
272
+ }
273
+ // -- Custom detector application (B3) -----------------------------------------
274
+ /**
275
+ * File extensions per language, for routing custom detectors to the right
276
+ * files. Kept in sync with the language detector registry; we duplicate
277
+ * the table here rather than pulling from the registry because the
278
+ * registry is awareness-of-detector, not awareness-of-extensions, and
279
+ * the registry's API is method-call-shaped (asks "do you support this
280
+ * file" per detector) rather than data-shaped.
281
+ */
282
+ const LANGUAGE_EXTENSIONS = {
283
+ go: ['.go'],
284
+ python: ['.py', '.pyx', '.pyi'],
285
+ typescript: ['.ts', '.tsx'],
286
+ javascript: ['.js', '.jsx', '.mjs', '.cjs'],
287
+ java: ['.java'],
288
+ kotlin: ['.kt', '.kts'],
289
+ swift: ['.swift'],
290
+ ruby: ['.rb'],
291
+ csharp: ['.cs'],
292
+ php: ['.php'],
293
+ rust: ['.rs'],
294
+ cpp: ['.c', '.cc', '.cpp', '.h', '.hpp'],
295
+ objc: ['.m', '.mm'],
296
+ };
297
+ /**
298
+ * Runs each configured custom detector over the appropriate file subset
299
+ * and adds matches to `totalFlags`. Mutates the map in place — same shape
300
+ * as the polyglot analyzer's output, so downstream staleness + JSON
301
+ * surfacing pick the new flags up without further wiring.
302
+ *
303
+ * Each match becomes a FeatureFlag with `confidence: 'low'` to signal
304
+ * that the detection came from a user-declared regex rather than a
305
+ * disciplined SDK gate. The legacy `'high'`/`'medium'` flags from the
306
+ * standard detector are left untouched.
307
+ */
308
+ function applyCustomDetectors(files, detectors, totalFlags, logger) {
309
+ for (const detector of detectors) {
310
+ let regex;
311
+ try {
312
+ regex = new RegExp(detector.access_pattern, 'g');
313
+ }
314
+ catch (err) {
315
+ logger.warn(`custom_detector regex failed to compile`, {
316
+ access_pattern: detector.access_pattern,
317
+ /* v8 ignore next */
318
+ error: err instanceof Error ? err.message : String(err),
319
+ });
320
+ continue;
321
+ }
322
+ const allowedExts = LANGUAGE_EXTENSIONS[detector.language] ?? [];
323
+ if (allowedExts.length === 0) {
324
+ logger.warn(`custom_detector skipped — unknown language`, {
325
+ language: detector.language,
326
+ });
327
+ continue;
328
+ }
329
+ const providerName = detector.name ?? 'Custom struct-field detector';
330
+ let matchCount = 0;
331
+ for (const [filePath, content] of files) {
332
+ const lower = filePath.toLowerCase();
333
+ if (!allowedExts.some((ext) => lower.endsWith(ext)))
334
+ continue;
335
+ // Walk every match in the file. Each must capture exactly one
336
+ // group (the flag name) — schemas validated at load time, but we
337
+ // defensively skip captureless matches at runtime too.
338
+ regex.lastIndex = 0;
339
+ let m;
340
+ while ((m = regex.exec(content)) !== null) {
341
+ const flagName = m[1];
342
+ /* v8 ignore next */
343
+ if (!flagName)
344
+ continue;
345
+ // Resolve line number from the match offset. Cheap: count
346
+ // newlines up to the match. Files are typically <10k lines so
347
+ // O(n) per match is fine.
348
+ const lineNumber = content.slice(0, m.index).split('\n').length;
349
+ const existing = totalFlags.get(flagName) ?? [];
350
+ existing.push({
351
+ name: flagName,
352
+ filePath,
353
+ lineNumber,
354
+ language: detector.language,
355
+ provider: providerName,
356
+ confidence: 'low',
357
+ });
358
+ totalFlags.set(flagName, existing);
359
+ matchCount++;
360
+ }
361
+ }
362
+ if (matchCount > 0) {
363
+ logger.debug(`custom_detector matched`, {
364
+ language: detector.language,
365
+ matchCount,
366
+ provider: providerName,
367
+ });
368
+ }
369
+ }
370
+ }
85
371
  //# sourceMappingURL=scan-repo.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"scan-repo.js","sourceRoot":"","sources":["../src/scan-repo.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"scan-repo.js","sourceRoot":"","sources":["../src/scan-repo.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  import { type FeatureFlag } from './detection/feature-flag.js';
2
2
  export interface StalenessSignal {
3
- type: 'age' | 'hardcoded' | 'low-usage' | 'missing-in-platform' | 'archived-in-platform';
3
+ type: 'age' | 'hardcoded' | 'low-usage' | 'missing-in-platform' | 'archived-in-platform' | 'platform-too-old' | 'platform-inactive' | 'platform-launched';
4
4
  severity: 'error' | 'warning';
5
5
  description: string;
6
6
  }
@@ -13,6 +13,25 @@ export interface StaleFlag {
13
13
  signals: StalenessSignal[];
14
14
  /** Human-readable age, e.g. "14 months ago" */
15
15
  age?: string;
16
+ /**
17
+ * Detection-quality tier inherited from the underlying FeatureFlag.
18
+ * See FlagConfidence in detection/feature-flag.ts for what the values
19
+ * mean. Absent = `'high'`. Surfaced here so consumers of the stale-flag
20
+ * list (the JSON output, the GitHub Action's PR comment) can route
21
+ * medium-confidence flags to manual review rather than auto-cleanup.
22
+ */
23
+ confidence?: 'high' | 'medium' | 'low';
24
+ /**
25
+ * Platform-side metadata propagated through from the cross-reference
26
+ * layer. Populated only when a platform integration is active AND the
27
+ * platform exposed the data. Output formatters surface these
28
+ * alongside each flag so reviewers see who owns it and how the
29
+ * platform classifies it without a context switch.
30
+ */
31
+ tags?: string[];
32
+ maintainer?: string;
33
+ /** LD's per-environment activity verdict; see PlatformFlag.status. */
34
+ platformStatus?: 'new' | 'active' | 'inactive' | 'launched';
16
35
  }
17
36
  export interface StalenessOptions {
18
37
  /** Flag lines older than this are considered stale. Default: 30. */
@@ -21,6 +40,17 @@ export interface StalenessOptions {
21
40
  repoRoot: string;
22
41
  /** Optional: pre-computed platform signals keyed by flag name. */
23
42
  platformSignals?: Map<string, import('./providers/interface.js').PlatformSignal[]>;
43
+ /**
44
+ * Optional: per-flag platform metadata surfaced by the orchestrator.
45
+ * Drives the tags / maintainer / platformStatus fields on each
46
+ * emitted StaleFlag. Keyed by detected flag name; values come
47
+ * straight from the matched PlatformFlag.
48
+ */
49
+ platformMetadata?: Map<string, {
50
+ tags?: string[];
51
+ maintainer?: string;
52
+ status?: 'new' | 'active' | 'inactive' | 'launched';
53
+ }>;
24
54
  }
25
55
  /**
26
56
  * Analyze a set of detected feature flags for staleness signals.
@@ -1 +1 @@
1
- {"version":3,"file":"staleness.d.ts","sourceRoot":"","sources":["../src/staleness.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,WAAW,EAAE,MAAM,6BAA6B,CAAA;AAI9D,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,KAAK,GAAG,WAAW,GAAG,WAAW,GAAG,qBAAqB,GAAG,sBAAsB,CAAA;IACxF,QAAQ,EAAE,OAAO,GAAG,SAAS,CAAA;IAC7B,WAAW,EAAE,MAAM,CAAA;CACpB;AAED,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,MAAM,CAAA;IAChB,UAAU,EAAE,MAAM,CAAA;IAClB,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,OAAO,EAAE,eAAe,EAAE,CAAA;IAC1B,+CAA+C;IAC/C,GAAG,CAAC,EAAE,MAAM,CAAA;CACb;AAED,MAAM,WAAW,gBAAgB;IAC/B,oEAAoE;IACpE,aAAa,EAAE,MAAM,CAAA;IACrB,gDAAgD;IAChD,QAAQ,EAAE,MAAM,CAAA;IAChB,kEAAkE;IAClE,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,0BAA0B,EAAE,cAAc,EAAE,CAAC,CAAA;CACnF;AAmKD;;;;;;GAMG;AACH,wBAAsB,gBAAgB,CACpC,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,EAAE,CAAC,EACjC,OAAO,EAAE,gBAAgB,GACxB,OAAO,CAAC,SAAS,EAAE,CAAC,CAmFtB"}
1
+ {"version":3,"file":"staleness.d.ts","sourceRoot":"","sources":["../src/staleness.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,WAAW,EAAE,MAAM,6BAA6B,CAAA;AAI9D,MAAM,WAAW,eAAe;IAK9B,IAAI,EACA,KAAK,GACL,WAAW,GACX,WAAW,GACX,qBAAqB,GACrB,sBAAsB,GACtB,kBAAkB,GAClB,mBAAmB,GACnB,mBAAmB,CAAA;IACvB,QAAQ,EAAE,OAAO,GAAG,SAAS,CAAA;IAC7B,WAAW,EAAE,MAAM,CAAA;CACpB;AAED,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,MAAM,CAAA;IAChB,UAAU,EAAE,MAAM,CAAA;IAClB,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,OAAO,EAAE,eAAe,EAAE,CAAA;IAC1B,+CAA+C;IAC/C,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,KAAK,CAAA;IAEtC;;;;;;OAMG;IACH,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;IACf,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,sEAAsE;IACtE,cAAc,CAAC,EAAE,KAAK,GAAG,QAAQ,GAAG,UAAU,GAAG,UAAU,CAAA;CAC5D;AAED,MAAM,WAAW,gBAAgB;IAC/B,oEAAoE;IACpE,aAAa,EAAE,MAAM,CAAA;IACrB,gDAAgD;IAChD,QAAQ,EAAE,MAAM,CAAA;IAChB,kEAAkE;IAClE,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,0BAA0B,EAAE,cAAc,EAAE,CAAC,CAAA;IAClF;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,GAAG,CACpB,MAAM,EACN;QAAE,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,KAAK,GAAG,QAAQ,GAAG,UAAU,GAAG,UAAU,CAAA;KAAE,CAC9F,CAAA;CACF;AAmKD;;;;;;GAMG;AACH,wBAAsB,gBAAgB,CACpC,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,EAAE,CAAC,EACjC,OAAO,EAAE,gBAAgB,GACxB,OAAO,CAAC,SAAS,EAAE,CAAC,CAgItB"}