@theglitchking/gimme-the-lint 2.5.0 → 2.5.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.
@@ -6,13 +6,13 @@
6
6
  },
7
7
  "metadata": {
8
8
  "description": "Official marketplace for gimme-the-lint - polyglot progressive linting with per-app baselines and drift detection",
9
- "version": "2.5.0"
9
+ "version": "2.5.1"
10
10
  },
11
11
  "plugins": [
12
12
  {
13
13
  "name": "gimme-the-lint",
14
14
  "description": "Polyglot progressive linting for monorepos — adapter-driven baselines, per-app drift detection, and idempotent skips across JavaScript/TypeScript, Python, Go, Rust, Terraform, and Ansible.",
15
- "version": "2.5.0",
15
+ "version": "2.5.1",
16
16
  "author": {
17
17
  "name": "TheGlitchKing"
18
18
  },
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "gimme-the-lint",
3
3
  "description": "Polyglot progressive linting for monorepos — adapter-driven baselines, per-app drift detection, and idempotent skips across JavaScript/TypeScript, Python, Go, Rust, Terraform, and Ansible.",
4
- "version": "2.5.0",
4
+ "version": "2.5.1",
5
5
  "author": {
6
6
  "name": "TheGlitchKing",
7
7
  "email": "theglitchking@users.noreply.github.com"
package/CHANGELOG.md CHANGED
@@ -5,6 +5,26 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [2.5.1] - 2026-05-17
9
+
10
+ ### Fixed
11
+ - **tflint config resolution is now root-aware.** In the standard Terraform
12
+ monorepo — one repo-root `.tflint.hcl`, many nested `modules/*` / `envs/*`
13
+ units — the adapter resolved config only in the unit directory, so every
14
+ unit was linted with tflint defaults: the repo's preset, plugin declarations
15
+ and `rule { enabled = false }` overrides were silently ignored and
16
+ `tflint --init` never ran. Meanwhile `configHashFor()` *did* find the
17
+ repo-root config, so the baseline's `config_hash` reflected a config the
18
+ linter never actually used — a silent correctness bug. A new shared resolver,
19
+ `LinterAdapter.resolveConfigPath()`, walks up from the unit directory to the
20
+ project root; `buildCommand()` and `initCommand()` now pass the resolved
21
+ file as an absolute `--config`, and `configHashFor()` hashes that same file —
22
+ the hashed config and the linted config can no longer disagree. A unit with
23
+ its own `.tflint.hcl` still wins (nearest-first); a repo with no config
24
+ anywhere still runs the zero-config core ruleset. The resolver is generic
25
+ (no provider name in code) and root-aware config-hashing now benefits every
26
+ adapter.
27
+
8
28
  ## [2.5.0] - 2026-05-17
9
29
 
10
30
  ### Added
@@ -65,6 +65,38 @@ class LinterAdapter {
65
65
  return [];
66
66
  }
67
67
 
68
+ /**
69
+ * Resolve the EFFECTIVE config file for the unit being linted: walk up from
70
+ * `startDir` (default: appRoot) to projectRoot inclusive and return the
71
+ * absolute path of the nearest file named by configFiles(), or null if none
72
+ * exists anywhere on that path.
73
+ *
74
+ * Root-aware — this is what lets a single repo-root config (the standard
75
+ * monorepo layout: one config, many nested units) govern every unit. It is
76
+ * the SINGLE source of truth shared by the config-hash (baseline.js) and the
77
+ * linter invocation, so the hashed config and the linted config can never
78
+ * disagree.
79
+ * @param {string} [startDir]
80
+ * @returns {string|null}
81
+ */
82
+ resolveConfigPath(startDir) {
83
+ const names = this.configFiles();
84
+ if (!names || names.length === 0) return null;
85
+ const root = path.resolve(this.projectRoot);
86
+ let dir = path.resolve(startDir || this.appRoot);
87
+ while (true) {
88
+ for (const name of names) {
89
+ const candidate = path.join(dir, name);
90
+ if (fs.existsSync(candidate)) return candidate;
91
+ }
92
+ if (dir === root) break; // checked projectRoot — stop
93
+ const parent = path.dirname(dir);
94
+ if (parent === dir) break; // filesystem root reached
95
+ dir = parent;
96
+ }
97
+ return null;
98
+ }
99
+
68
100
  /** Return the first candidate path that exists, else the last candidate. */
69
101
  resolveBinary(candidates) {
70
102
  for (const candidate of candidates) {
@@ -66,16 +66,22 @@ class TflintAdapter extends LinterAdapter {
66
66
 
67
67
  // --- .tflint.hcl introspection (generic — no provider is special-cased) ---
68
68
 
69
- /** True when the unit directory carries a .tflint.hcl. */
69
+ /** True when an effective .tflint.hcl exists (the unit dir or up to root). */
70
70
  _hasConfig(dir) {
71
- return fs.existsSync(path.join(dir || this.appRoot, '.tflint.hcl'));
71
+ return this.resolveConfigPath(dir) !== null;
72
72
  }
73
73
 
74
- /** Enumerate every `plugin "<name>"` block declared in a unit's .tflint.hcl. */
74
+ /**
75
+ * Enumerate every `plugin "<name>"` block in the EFFECTIVE .tflint.hcl —
76
+ * the one resolveConfigPath() resolves, which may live up at the repo root,
77
+ * not in the unit dir.
78
+ */
75
79
  _declaredPlugins(dir) {
80
+ const configPath = this.resolveConfigPath(dir);
81
+ if (!configPath) return [];
76
82
  let hcl;
77
83
  try {
78
- hcl = fs.readFileSync(path.join(dir || this.appRoot, '.tflint.hcl'), 'utf8');
84
+ hcl = fs.readFileSync(configPath, 'utf8');
79
85
  } catch {
80
86
  return [];
81
87
  }
@@ -105,10 +111,12 @@ class TflintAdapter extends LinterAdapter {
105
111
  * base adapter). A repo with no .tflint.hcl gets core linting, no init.
106
112
  */
107
113
  initCommand(cwd) {
108
- if (this._hasConfig(cwd)) {
109
- return { cmd: this.binary, args: ['--init'] };
110
- }
111
- return null;
114
+ const configPath = this.resolveConfigPath(cwd);
115
+ if (!configPath) return null;
116
+ // Pass the resolved config explicitly so `--init` reads the right
117
+ // .tflint.hcl for its plugin declarations — even when it lives up at the
118
+ // repo root rather than in the unit dir.
119
+ return { cmd: this.binary, args: ['--init', `--config=${configPath}`] };
112
120
  }
113
121
 
114
122
  // --- availability: agree with what lint() will actually see --------------
@@ -178,6 +186,14 @@ class TflintAdapter extends LinterAdapter {
178
186
  // Unresolvable target — fall back to the default cwd.
179
187
  }
180
188
  }
189
+
190
+ // tflint runs IN the unit dir; pass the effective config — which may live
191
+ // up at the repo root — as an absolute --config so the unit is linted with
192
+ // the repo's preset / plugin declarations / rule overrides, not tflint
193
+ // defaults. No config anywhere → plain tflint (zero-config core ruleset).
194
+ const configPath = this.resolveConfigPath(cwd);
195
+ if (configPath) args.push(`--config=${configPath}`);
196
+
181
197
  return { cmd: this.binary, args, cwd };
182
198
  }
183
199
 
package/lib/baseline.js CHANGED
@@ -18,15 +18,17 @@ const gtlManifest = require('./gtl-manifest');
18
18
  // the baseline CLI, the hooks installer) can refuse to gate commits against a
19
19
  // baseline that never actually captured a linter.
20
20
 
21
- /** Hash the first config file found for an adapter (unit root, then project). */
21
+ /**
22
+ * Hash the EFFECTIVE config file for an adapter. Resolution walks up from the
23
+ * unit dir to the project root via the adapter's shared resolver, so the
24
+ * hashed config is exactly the one the linter is invoked with — a repo-root
25
+ * config governing nested units is hashed correctly, never missed or faked.
26
+ */
27
+ // eslint-disable-next-line no-unused-vars
22
28
  async function configHashFor(projectRoot, unitRoot, adapter) {
23
- for (const name of adapter.configFiles()) {
24
- for (const base of [unitRoot, projectRoot]) {
25
- const hash = await manifestManager.hashFile(path.join(base, name));
26
- if (hash !== 'unknown') return hash;
27
- }
28
- }
29
- return 'unknown';
29
+ const configPath = adapter.resolveConfigPath(unitRoot);
30
+ if (!configPath) return 'unknown';
31
+ return manifestManager.hashFile(configPath);
30
32
  }
31
33
 
32
34
  /** Baseline a single unit across all its bound linters. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@theglitchking/gimme-the-lint",
3
- "version": "2.5.0",
3
+ "version": "2.5.1",
4
4
  "description": "Polyglot progressive linting for monorepos — adapter-driven baselines, per-app drift detection, and idempotent skips across JavaScript/TypeScript, Python, Go, Rust, Terraform, and Ansible.",
5
5
  "keywords": [
6
6
  "linting",