flecto 4.0.0 → 4.1.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.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,106 @@ The format is based on [Keep a Changelog], and this project adheres to
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [4.1.1] - 2026-09-29
11
+
12
+ ### Fixed
13
+
14
+ - **The root `action.yml` description was too long to list on the Marketplace.**
15
+ GitHub rejects a listing whose description is 125 characters or more; 4.1.0
16
+ shipped 194. It is now 119, and a test pins the limit — the constraint is not
17
+ in GitHub's metadata-syntax documentation and the publish form only reports it
18
+ at publish time, by which point the release is already cut.
19
+
20
+ The rest of the pitch — that Flecto never runs `terraform`, `helm`, or `sops`,
21
+ and never decrypts — lives in the README, which has room for it.
22
+
23
+ ### Fixed
24
+
25
+ - **Documented Action pins moved from `@v4.0.0` to `@v4.1.0`.** Tags are
26
+ immutable, and the fix that stopped the bundled Actions installing the pre-4.0
27
+ CLI shipped *in* 4.1.0 — so while the examples said `@v4.0.0` they pointed at a
28
+ tag whose `flecto-ci` and `flecto-pr-risk` still run `flecto@3`, the line
29
+ exposed to the baseline-shadowing bypass 4.0 closed.
30
+
31
+ **If you pinned `@v4.0.0` following an earlier example, repin to `@v4.1.0`.**
32
+ The CLI at `flecto@4.0.0` is fine; it is only the bundled Action metadata at
33
+ that tag that is not. [docs/ci.md](docs/ci.md#pinning) now warns against it.
34
+
35
+ [RELEASE.md](RELEASE.md) gained a step that repins the documented Actions as
36
+ part of every release. That step is the real fix — the bug was not the pin, it
37
+ was that nothing tied the documented pin to the release that fixed what it
38
+ pointed at.
39
+
40
+ ## [4.1.0] - 2026-09-29
41
+
42
+ ### Added
43
+
44
+ - **A root [`action.yml`](action.yml), so the Action can be listed on the GitHub
45
+ Marketplace.** GitHub only lists an action whose metadata file sits at a public
46
+ repository's root; Flecto's Actions live in `.github/actions/`, which is why
47
+ they were never listable. The listed action is `flecto-pr-risk` — the pull
48
+ request risk comment — with branding and the wedge description.
49
+
50
+ `.github/actions/flecto-pr-risk/action.yml` **stays exactly where it is**, so
51
+ nothing referencing that path changes. The two files' `runs:` blocks are
52
+ byte-identical and a test enforces it, so a fix to one is a CI failure until it
53
+ lands in both.
54
+
55
+ Docs continue to reference
56
+ `myselfsiddharth/Flecto/.github/actions/flecto-pr-risk@v4.0.0` until a release
57
+ carrying the root file exists. The shorter `myselfsiddharth/Flecto@vX.Y.Z` form
58
+ becomes correct at that point; see [RELEASE.md](RELEASE.md) step 5.
59
+
60
+ - **`flecto-pr-risk` takes a Terraform plan directly.** A new `terraform-plan`
61
+ input points at `terraform show -json` output and switches the Action to
62
+ `flecto plan`, so reviewing a plan on every pull request is two steps:
63
+
64
+ ```yaml
65
+ - run: terraform plan -out=tf.plan && terraform show -json tf.plan > plan.json
66
+ - uses: myselfsiddharth/Flecto/.github/actions/flecto-pr-risk@v4.0.0
67
+ with:
68
+ terraform-plan: plan.json
69
+ fail-on: error
70
+ ```
71
+
72
+ Because a plan JSON carries its own before and after, **no baseline is resolved
73
+ and no git history is needed** in this mode — the `fetch-depth: 0` that config
74
+ mode wants does not apply, and the Action runs on events with no pull request
75
+ base commit. A missing plan file fails the step rather than letting Flecto
76
+ report nothing. `targets` is ignored; add a second step without
77
+ `terraform-plan` to also check config files.
78
+
79
+ Config mode is unchanged, including its fail-closed behaviour when no baseline
80
+ can be resolved. A complete workflow is in
81
+ [`examples/github-action/flecto-terraform-plan.yml`](examples/github-action/flecto-terraform-plan.yml).
82
+
83
+ - [docs/stability.md](docs/stability.md): what the public contract covers (the
84
+ `schema_version: "2.0"` envelope, exit codes, `.flectorc`, the CLI surface),
85
+ what it deliberately does not, and the deprecation sequence — one minor release
86
+ carrying a warning before any removal, with security fixes the stated
87
+ exception.
88
+
89
+ ### Fixed
90
+
91
+ - **The bundled GitHub Actions installed the pre-4.0 CLI.** `flecto-ci`
92
+ hardcoded `npx --yes flecto@3` and `flecto-pr-risk` defaulted
93
+ `flecto-version: "3"`, so both shipped Actions ran the 3.x line after 4.0.0
94
+ released. Two consequences: `flecto-ci`'s advertised `snapshot-file:` input
95
+ passed a flag that does not exist before 4.0, and its default
96
+ `snapshot-ref: HEAD~1` against a 3.x CLI is the baseline-shadowing bypass 4.0
97
+ closed — a pull request commits a file named `HEAD~1`, it is read instead of
98
+ the revision, the diff comes back empty and no `--fail-on` value catches it.
99
+
100
+ Both now default to `4`. `flecto-ci` gains a `flecto-version` input so the CLI
101
+ can be pinned without forking, matching `flecto-pr-risk`. A test asserts the
102
+ floor across both Actions, including hardcoded installs that would bypass the
103
+ input.
104
+
105
+ **If you copied an earlier README example you are affected**: the examples
106
+ referenced the Actions `@main`, which resolved to a 3.x install. Re-pin to
107
+ `@v4.0.0` — every example in the README and [docs/ci.md](docs/ci.md) now does,
108
+ with SHA pinning documented for security-sensitive users.
109
+
10
110
  ## [4.0.0] - 2026-09-23
11
111
 
12
112
  **A security release.** Every breaking change below exists because a pull
@@ -1222,7 +1322,9 @@ fixed — those runs were never actually gated — but the failure is new.
1222
1322
  - Misconfigured policy packs/plugins cause `watch` to exit non-zero instead of
1223
1323
  continuing with no policies.
1224
1324
 
1225
- [Unreleased]: https://github.com/myselfsiddharth/Flecto/compare/v3.1.0...HEAD
1325
+ [Unreleased]: https://github.com/myselfsiddharth/Flecto/compare/v4.1.1...HEAD
1326
+ [4.1.1]: https://github.com/myselfsiddharth/Flecto/compare/v4.1.0...v4.1.1
1327
+ [4.1.0]: https://github.com/myselfsiddharth/Flecto/compare/v4.0.0...v4.1.0
1226
1328
  [4.0.0]: https://github.com/myselfsiddharth/Flecto/compare/v3.1.0...v4.0.0
1227
1329
  [3.1.0]: https://github.com/myselfsiddharth/Flecto/compare/v3.0.2...v3.1.0
1228
1330
  [3.0.2]: https://github.com/myselfsiddharth/Flecto/compare/v3.0.1...v3.0.2
package/README.md CHANGED
@@ -1,11 +1,8 @@
1
- <p align="center">
2
- <img src="docs/assets/flecto-hero.png" alt="Flecto — semantic config watcher" width="920"/>
3
- </p>
4
-
5
1
  <h1 align="center">Flecto</h1>
6
2
 
7
3
  <p align="center">
8
- <strong>Know what your config actually changed — and whether it's risky.</strong>
4
+ <strong>Flecto reads your Terraform plan and Kubernetes changes and posts a
5
+ plain-English risk summary on every pull request, blocking the dangerous ones.</strong>
9
6
  </p>
10
7
 
11
8
  <p align="center">
@@ -15,178 +12,87 @@
15
12
  <a href="#documentation"><img alt="Docs" src="https://img.shields.io/badge/docs-read-34d399?style=flat-square&labelColor=0b1220"/></a>
16
13
  </p>
17
14
 
18
- <p align="center">
19
- <img src="docs/assets/demo-watch.svg" alt="Flecto reporting semantic config changes in the terminal" width="920"/>
20
- </p>
21
-
22
- ---
23
-
24
- Config drives the parts of a system that break loudest: connection pools, feature
25
- flags, TLS, retries, secrets. But we still review it as text — so a reordered key
26
- looks identical to a doubled pool size, and `debug: true` slips through in a
27
- 40-line formatting diff.
28
-
29
- Flecto reads config as structure, not lines. It tells you what changed in plain
30
- English, flags what looks risky, and gives you an exit code to gate on.
31
-
32
- | Reviewing config without Flecto | With Flecto |
33
- |---|---|
34
- | `+ 40 lines of YAML noise` | `~ database.pool_size: 5 → 20` |
35
- | Hope someone notices `debug: true` | Policy finding → build fails |
36
- | "Something in `.env` changed" | The exact keys, with secrets masked |
37
-
38
- The same engine reads whatever your change actually lives in:
39
-
40
- | You are reviewing | Flecto reads |
41
- |---|---|
42
- | App config — YAML, JSON, TOML, INI, dotenv | the files directly |
43
- | A Terraform change | `terraform show -json` output, via `flecto plan` |
44
- | A Kubernetes change | rendered manifests from `helm`, `kustomize`, or anything else |
45
- | A SOPS-encrypted file | its structure and recipients — **never its plaintext** |
46
-
47
- It never invokes `terraform`, `helm`, `kustomize`, `sops`, or `age`, so nothing
48
- extra has to exist on the CI runner.
49
-
50
- ---
51
-
52
- ## Install
53
-
54
- ```bash
55
- npm install -g flecto
56
- ```
57
-
58
- Requires **Node.js 20.19.0+**. Verify:
59
-
60
- ```bash
61
- flecto --version
62
- flecto doctor
63
- ```
64
-
65
- Prefer not to install globally? Every example below works with
66
- `npx --yes flecto@3` instead of `flecto`.
67
-
68
15
  ---
69
16
 
70
- ## Quick start
71
-
72
- A complete walkthrough, start to finish. Copy-paste it anywhere.
73
-
74
- **1. Create a config file to track.**
17
+ ## What lands on the pull request
75
18
 
76
- ```bash
77
- mkdir flecto-demo && cd flecto-demo && mkdir config
78
- cat > config/prod.yaml <<'EOF'
79
- database:
80
- host: db.internal
81
- pool_size: 5
82
- ssl: true
83
- logging:
84
- level: info
85
- debug: false
86
- EOF
87
- ```
19
+ <p align="center">
20
+ <img src="docs/assets/flecto-pr-comment.png" alt="Flecto's comment on a pull request: check failing, six policy errors naming an IAM wildcard, a disabled S3 public-access block, and security group ingress opened to 0.0.0.0/0" width="960"/>
21
+ </p>
88
22
 
89
- **2. Save it as your baseline.**
23
+ That is a real comment on a
24
+ **[real pull request](https://github.com/myselfsiddharth/flecto-example-terraform/pull/1)**
25
+ you can open right now. The PR says it is about partner access and changes eight
26
+ lines; it opens the web tier to the internet, turns off the bucket's
27
+ public-access protection, and widens an IAM policy to `s3:*` on `*`.
90
28
 
91
- ```bash
92
- flecto watch config/prod.yaml --snapshot
93
- ```
29
+ One sticky comment, updated in place on every push. Exit code `1`, so the build
30
+ fails before the change ships.
94
31
 
95
- ```
96
- ✓ Snapshot saved: /path/to/flecto-demo/.flecto-snapshots/4b8cbbd70d1832a2.json
97
- ```
98
-
99
- **3. Make the kind of edit that causes incidents.**
100
-
101
- ```bash
102
- cat > config/prod.yaml <<'EOF'
103
- database:
104
- host: db.internal
105
- pool_size: 20
106
- ssl: true
107
- logging:
108
- level: info
109
- debug: true
110
- EOF
111
- ```
32
+ **[The same gate on an ordinary change](https://github.com/myselfsiddharth/flecto-example-terraform/pull/2)**
33
+ reports *no findings* and passes. That matters as much: a check that fires on
34
+ everything gets uninstalled in a week.
112
35
 
113
- **4. Ask what changed.**
36
+ <details>
37
+ <summary>The same report as text, and what a plan with existing state adds</summary>
114
38
 
115
- ```bash
116
- flecto watch config/prod.yaml --diff
117
- ```
39
+ The comment above, as `flecto plan` prints it:
118
40
 
119
41
  ```
120
- /path/to/flecto-demo/config/prod.yaml — 2 changes from snapshot:
121
- ~ database.pool_size: 5 → 20
122
- ~ logging.debug: false → true
123
- ```
42
+ ❌ Check failing — 30 changes in 1 file — 0 changed, 30 added, 0 removed.
43
+ Policy: 6 errors.
124
44
 
125
- Two sentences instead of a diff you have to interpret. Now let Flecto judge it:
45
+ aws_security_group.web.ingress[0].cidr_blocks[0]
46
+ terraform-security-group-open-ingress
47
+ Security group ingress will accept traffic from the whole internet
48
+ (0.0.0.0/0). Restrict the source to a known CIDR, a prefix list, or
49
+ another security group.
126
50
 
127
- ```bash
128
- flecto ci config/prod.yaml --format github-annotations
129
- ```
51
+ aws_s3_bucket_public_access_block.uploads.block_public_acls (+3 more)
52
+ terraform-s3-public-access-block-disabled
53
+ S3 public access block is being turned off or removed.
130
54
 
131
- ```
132
- ::warning title=flecto changed::database.pool_size
133
- ::warning title=flecto changed::logging.debug
134
- ::warning title=flecto policy pool-size-jump [default]::database.pool_size: Pool size increased from 5 to 20 (>=2x).
135
- ::error title=flecto policy dangerous-toggle-enabled [default]::logging.debug: Potentially dangerous toggle enabled.
55
+ aws_iam_role_policy.app.policy
56
+ terraform-iam-wildcard
57
+ IAM policy grants a wildcard action or resource ("*").
136
58
  ```
137
59
 
138
- Exit code `1`. In CI, that's a failed build — before the change ships.
60
+ The example repository has no Terraform state, so every resource shows as
61
+ `create`. With existing state, a plan that replaces a database also reports:
139
62
 
140
- **5. Watch it live.** Leave this running and edit the file in another window:
141
-
142
- ```bash
143
- flecto watch config/prod.yaml
144
63
  ```
145
-
146
- ```
147
- flecto watching /path/to/flecto-demo/config/prod.yaml
148
- Press Ctrl+C to stop.
149
-
150
- [18:24:48] /path/to/flecto-demo/config/prod.yaml — 2 changes
151
- ~ database.pool_size: 5 → 20
152
- ~ logging.debug: false → true
153
- ! policy(warn) [default] database.pool_size: Pool size increased from 5 to 20 (>=2x).
154
- ! policy(error) [default] logging.debug: Potentially dangerous toggle enabled.
64
+ aws_db_instance.main.#action
65
+ terraform-stateful-resource-destroyed
66
+ Terraform will destroy a stateful resource. Its data does not survive.
67
+ Take a final snapshot, or add a prevent_destroy lifecycle block, before
68
+ applying.
155
69
  ```
156
70
 
157
- That's the whole product. Everything below is depth.
71
+ </details>
158
72
 
159
73
  ---
160
74
 
161
- ## What you can do with it
162
-
163
- ### Catch risky changes before they merge
75
+ ## Add it in 60 seconds
164
76
 
165
- Add one step to your workflow and risky config edits show up as annotations on
166
- the pull request:
77
+ **Terraform** — point it at the plan JSON:
167
78
 
168
79
  ```yaml
169
80
  permissions:
170
81
  contents: read
82
+ pull-requests: write
171
83
 
172
84
  steps:
173
85
  - uses: actions/checkout@v7
86
+ - run: |
87
+ terraform plan -out=tf.plan
88
+ terraform show -json tf.plan > plan.json
89
+ - uses: myselfsiddharth/Flecto/.github/actions/flecto-pr-risk@v4.1.0
174
90
  with:
175
- fetch-depth: 2
176
- - uses: myselfsiddharth/Flecto/.github/actions/flecto-ci@main
177
- with:
178
- targets: config/**/*.{yaml,yml,json,toml,ini}
179
- snapshot-ref: HEAD~1
91
+ terraform-plan: plan.json
92
+ fail-on: error
180
93
  ```
181
94
 
182
- Prefer a summary nobody can miss? `--format pr-comment` renders the changes and
183
- policy findings as markdown and, when you opt in with `--pr-comment-post` inside
184
- a GitHub PR run, keeps **one** sticky comment up to date instead of adding a new
185
- one per push. Without that flag it just prints the markdown, so it can't post
186
- from your laptop.
187
-
188
- The `flecto-pr-risk` Action is that, packaged — the whole adoption is one
189
- `uses:`, with the baseline resolved from the pull request rather than `HEAD~1`:
95
+ **Kubernetes** — render your manifests, then diff them against the PR's base:
190
96
 
191
97
  ```yaml
192
98
  permissions:
@@ -197,324 +103,64 @@ steps:
197
103
  - uses: actions/checkout@v7
198
104
  with:
199
105
  fetch-depth: 0
200
- - uses: myselfsiddharth/Flecto/.github/actions/flecto-pr-risk@main
201
- ```
202
-
203
- GitLab and Bitbucket work the same way — Flecto detects the host from CI
204
- variables, or `--pr-provider` forces one. See [CI](docs/ci.md#providers).
205
-
206
- A fork's pull request gets a read-only token, so the comment is skipped with a
207
- warning there — the check itself still runs and still fails on risky changes.
208
-
209
- Works on any CI runner — it's a plain CLI with meaningful exit codes.
210
- → **[CI guide](docs/ci.md)**
211
-
212
- ### Trigger automation on change
213
-
214
- Restart a service, reload a process, or notify an endpoint whenever config moves:
215
-
216
- ```bash
217
- flecto watch .env --command "docker-compose restart app"
218
-
219
- flecto watch config/prod.yaml \
220
- --webhook https://hooks.example.com/notify \
221
- --delivery-mode at-least-once
222
- ```
223
-
224
- Changes arrive as a versioned JSON envelope, and `at-least-once` persists and
225
- retries failed deliveries.
226
-
227
- Posting straight to chat needs no receiver of your own — `--webhook-format`
228
- shapes the body for Slack, Discord, or Teams, colored by the highest policy
229
- severity:
230
-
231
- ```bash
232
- flecto watch config/prod.yaml \
233
- --webhook "https://hooks.slack.com/services/T000/B000/XXXX" \
234
- --webhook-format slack
235
- ```
236
-
237
- → **[Webhooks and commands](docs/webhooks.md)**
238
-
239
- ### Compare two environments
240
-
241
- "Works in staging, fails in prod" is usually one key apart:
242
-
243
- ```bash
244
- flecto compare config/prod.yaml config/staging.yaml
245
- ```
246
-
247
- ```
248
- "+" exists only in the compared file, "-" only in the baseline, "~" differs
249
- /path/to/config/staging.yaml — 2 changes from /path/to/config/prod.yaml:
250
- - only_in_prod: true
251
- ~ database.pool_size: 5 → 20
252
- ! policy(warn) [default] database.pool_size: Pool size increased from 5 to 20 (>=2x).
253
- ```
254
-
255
- The first file is the baseline, the files don't have to share a format, and
256
- `--format json` gives you the same output `flecto ci` produces.
257
- → **[CLI reference](docs/cli-reference.md#flecto-compare-filea-fileb)**
258
-
259
- ### Track drift over time
260
-
261
- ```bash
262
- flecto history config/prod.yaml --limit 10
263
- ```
264
-
265
- Snapshots stay on your machine in `.flecto-snapshots/`. Nothing is uploaded and
266
- no account is required. To read the same history on a CI runner, save it to the
267
- git-tracked store instead — `--snapshot-store shared` writes a committable
268
- `.flecto/snapshots/`, masking secret-like values into digests as it goes.
269
- → **[CLI reference](docs/cli-reference.md#flecto-history-files)**
270
-
271
- ### Share what changed before the incident
272
-
273
- ```bash
274
- flecto report --limit 20 --mask-secrets --output drift.html
275
- ```
276
-
277
- One HTML file from that same local history: a timeline per file, every change
278
- with its UTC timestamp, and policy findings grouped by severity. Fully
279
- self-contained — inline styles, no fonts, no CDN scripts, no analytics — so you
280
- can attach it to an incident thread and it renders offline. No server and no
281
- account, same as everything else here.
282
- → **[CLI reference](docs/cli-reference.md#flecto-report-files)**
283
-
284
- ### Review a Kubernetes change before it reaches a cluster
285
-
286
- ArgoCD, Flux, and `helm diff` compare the cluster to the repo — which needs a
287
- cluster, and an apply that already happened. Flecto compares the manifests *this
288
- pull request would produce* against the ones `main` produces:
289
-
290
- ```bash
291
- helm template api ./charts/api -f values/prod.yaml > /tmp/head.yaml
292
- flecto compare /tmp/base.yaml /tmp/head.yaml --policies kubernetes --fail-on error
293
- ```
294
-
295
- ```
296
- ~ Service/prod/api.spec.type: "ClusterIP" → "LoadBalancer"
297
- ! policy(error) [kubernetes] Service type is LoadBalancer, which exposes the
298
- workload outside the cluster. Confirm the exposure is intended.
299
- ```
300
-
301
- Multi-document YAML is keyed by `kind/namespace/name`, so findings name the
302
- resource. Flecto never runs `helm` or `kustomize` — you render, it diffs, so any
303
- renderer works and no binary is needed in CI.
304
- → **[Kubernetes](docs/kubernetes.md)**
305
-
306
- ### Read a Terraform plan in plain English
307
-
308
- `terraform plan` output is precise and long. Flecto turns it into the handful of
309
- lines a reviewer actually needs to argue about:
310
-
311
- ```bash
312
- terraform show -json plan.tfplan > plan.json
313
- flecto plan plan.json --fail-on error
314
- ```
315
-
316
- ```
317
- plan.json — plan format 1.2
318
- Plan: 0 to add, 1 to change, 0 to destroy, 1 to replace.
319
- ~ aws_security_group.web.ingress[0].cidr_blocks[0]: "10.0.0.0/8" → "0.0.0.0/0"
320
- - aws_db_instance.main.#action: "replace" [terraform will destroy and recreate aws_db_instance.main]
321
- ~ aws_db_instance.main.password: "(sensitive value)" → "(sensitive value)" [sensitive]
322
- ! policy(error) [terraform] …cidr_blocks[0]: Security group ingress will accept
323
- traffic from the whole internet (0.0.0.0/0). Restrict the source to a known CIDR…
324
- ! policy(error) [terraform] …#action: Terraform will destroy a stateful resource.
325
- Its data does not survive. Take a final snapshot, or add a prevent_destroy…
326
- ```
327
-
328
- A **replace reads as a removal**, not a benign update — a recreated database
329
- should never look like a config tweak. Values Terraform marks sensitive are
330
- redacted during parsing, before the policy engine or any formatter sees them, and
331
- `after_unknown` renders as `(known after apply)` rather than `null`.
332
-
333
- **Flecto never runs `terraform`** — you produce the JSON, it reads it, so nothing
334
- extra has to exist on the CI runner.
335
- → **[Terraform plans](docs/terraform.md)**
336
-
337
- ### Encode your own rules
338
-
339
- Beyond the built-in packs, write rules as declarative JSON or YAML — no code:
340
-
341
- ```json
342
- {
343
- "id": "risky-feature-enable",
344
- "severity": "error",
345
- "allOf": [
346
- { "match": { "pathPrefix": "features." } },
347
- { "afterTruthy": true }
348
- ]
349
- }
350
- ```
351
-
352
- For anything a predicate can't express, a local ESM plugin exporting
353
- `evaluate(changes, ctx)` gets the full change set.
354
- → **[Writing policy packs](docs/policy-packs.md)** · **[Plugins](docs/plugins.md)**
355
-
356
- ### Cut the noise
357
-
358
- ```bash
359
- flecto watch config/prod.yaml --ignore "updated_at,**.meta.timestamp"
106
+ - run: helm template ./chart > rendered.yaml
107
+ - uses: myselfsiddharth/Flecto/.github/actions/flecto-pr-risk@v4.1.0
108
+ with:
109
+ targets: rendered.yaml
110
+ policies: kubernetes
360
111
  ```
361
112
 
362
- Arrays of objects are matched by `id` or `name`, so reordering a list of named
363
- services doesn't read as a wall of changes.
364
- → **[Configuration](docs/configuration.md)**
113
+ Flecto **never invokes** `terraform`, `helm`, `kustomize`, `sops`, or `age`, so
114
+ nothing extra has to exist on the runner. → **[CI guide](docs/ci.md)**
365
115
 
366
116
  ---
367
117
 
368
- ## Built-in policy packs
369
-
370
- | Pack | Catches |
371
- |---|---|
372
- | `default` | Secret-like keys added or changed, secret-shaped *values* under any key, dangerous toggles, pool-size jumps |
373
- | `strict-prod` | The same ground, with production-grade severities and matching |
374
- | `compose` | Privileged services, host networking, Docker socket mounts, sensitive bind mounts |
375
- | `kubernetes` | Privileged containers, host namespaces, weakened `runAsNonRoot`, added `SYS_ADMIN`, unpinned images, replica jumps, dropped limits, `LoadBalancer`/`NodePort` exposure |
376
- | `node-runtime` | Dropped engine requirements, TLS verification bypasses, debug/inspector flags |
377
- | `terraform` | Replaced and destroyed stateful resources, ingress opened to `0.0.0.0/0`, IAM wildcards, public S3, capacity jumps |
378
- | `sops` | Decryption recipients added or removed, a MAC that moved on its own, a file that stopped being encrypted |
379
- | `github-actions` | Changed workflow triggers, widened permissions, self-hosted runners, unpinned actions, pull-request head checkout, and secrets interpolated into `run` |
380
-
381
- ```bash
382
- flecto policies list # see what resolves here, built-in and local
383
- flecto ci "config/**/*.yaml" --policies "default,strict-prod" --fail-on policy
384
- ```
385
-
386
- Pass files or glob patterns, quoted so your shell doesn't expand them first — a
387
- bare directory is not a valid target.
388
-
389
- A local `policies/<id>.json` overrides the built-in pack of the same id, and
390
- `severityRemap` raises or silences individual rules per profile without forking
391
- anything. → **[Policy packs](docs/policy-packs.md)**
392
-
393
- Community packs ship on npm as `flecto-pack-<id>` packages — a package name and
394
- one declarative JSON file, nothing else:
395
-
396
- ```bash
397
- npm install --save-dev flecto-pack-deployment-safety
398
- flecto policies add deployment-safety
399
- ```
400
-
401
- `policies add` validates the pack, writes it to `policies/deployment-safety.json`,
402
- and runs no code from the package. →
403
- **[Installing community packs](docs/policy-packs.md#installing-a-community-pack)**
118
+ ## What it catches
119
+
120
+ - **A database about to be destroyed** — `terraform plan` says `replace`, the
121
+ diff says the data does not survive
122
+ - **Ingress widened to the world** — `10.0.0.0/8` → `0.0.0.0/0` on a security
123
+ group
124
+ - **An IAM policy gone wildcard** — `Action: "*"` where it used to be scoped
125
+ - **S3 public-access protection switched off** — the block removed, not just
126
+ loosened
127
+ - **A container turned privileged**, or `runAsNonRoot` quietly weakened
128
+ - **Resource limits removed** from a Deployment, so one pod can starve a node
129
+ - **An image tag unpinned** — `:1.4.0` → `:latest`
130
+ - **A new recipient on a SOPS file** — someone who could not decrypt it now can
131
+ - **`debug: true` in a 40-line formatting diff**, or a connection pool
132
+ quadrupled
133
+
134
+ Rules live in [policy packs](docs/policy-packs.md) you can extend, remap, or
135
+ replace.
404
136
 
405
137
  ---
406
138
 
407
- ## Supported formats
139
+ ## Also works as
408
140
 
409
- | Format | Extensions |
141
+ | | |
410
142
  |---|---|
411
- | JSON / JSONC | `.json`, `.jsonc` |
412
- | YAML | `.yaml`, `.yml` |
413
- | TOML | `.toml` |
414
- | INI | `.ini` |
415
- | dotenv | `.env`, `.env.*`, `*.env` |
416
- | age (armored) | `.age`, or any file whose contents are one armored blob |
417
-
418
- `.json` accepts comments and trailing commas, so `tsconfig.json`,
419
- `.vscode/settings.json`, `jsconfig.json`, and `devcontainer.json` are read as
420
- written. →
421
- **[JSON with comments](docs/configuration.md#json-with-comments)**
422
-
423
- Terraform plan JSON (`terraform show -json`) is read by **`flecto plan`**, which
424
- applies Terraform's own sensitivity marking. Point `plan` at it rather than `ci`
425
- or `watch` — those treat it as ordinary JSON and will print values Terraform
426
- marks sensitive ([#113](https://github.com/myselfsiddharth/Flecto/issues/113)).
427
-
428
- Multi-document YAML (`---`-separated, the usual shape of a Kubernetes manifest)
429
- is supported. Each document is diffed under its own key — `kind/name` for
430
- Kubernetes-shaped documents, so a document inserted at the top of the file does
431
- not renumber every other path. →
432
- **[Multi-document YAML](docs/configuration.md#multi-document-yaml)**
433
-
434
- ---
435
-
436
- ## Encrypted files
437
-
438
- A `sops`- or age-encrypted file is detected from its **contents**, and diffed
439
- structurally:
440
-
441
- ```
442
- + cache: {"ttl_seconds":300}
443
- ~ database.password: <encrypted value changed>
444
- + sops.age.age1exampleexample…: {"recipient":"age1exampleexample…","enc":"<encrypted value>"}
445
- ```
446
-
447
- You get keys added and removed, which encrypted values moved, and — the useful
448
- part — who can decrypt the file. A recipient added is a genuine security event
449
- and the `sops` pack raises it as one.
450
-
451
- **Flecto never decrypts.** It never shells out to `sops` or `age`, never reads a
452
- key file or agent socket, and never prints ciphertext — not even without
453
- `--mask-secrets`. Ciphertext is replaced with an opaque sentinel in the parser,
454
- so no diff, snapshot, webhook, or report can carry it. →
455
- **[Encrypted files](docs/encrypted-files.md)**
143
+ | **A CLI** | `flecto ci`, `flecto plan`, `flecto compare` — plain exit codes, any runner → **[CLI reference](docs/cli-reference.md)** |
144
+ | **A file watcher** | `flecto watch` reports changes as you edit → **[Getting started](docs/getting-started.md)** |
145
+ | **A webhook / command trigger** | Restart a service or notify an endpoint on change → **[Webhooks](docs/webhooks.md)** |
146
+ | **An MCP server** | Read-only `diff`, `check`, `explain` tools for agents → **[MCP](docs/mcp.md)** |
147
+ | **An editor language server** | `flecto lsp` — diagnostics while you type → **[Editor](docs/editor.md)** |
148
+ | **A drift detector** | `flecto-drift` compares declared config against what is running → **[Drift](docs/drift.md)** |
456
149
 
457
150
  ---
458
151
 
459
- ## Configuration
460
-
461
- Most teams commit a `.flectorc` so local runs and CI agree:
462
-
463
- ```bash
464
- flecto init
465
- ```
466
-
467
- ```json
468
- {
469
- "defaults": {
470
- "policies": ["default"],
471
- "ignore": ["**.updated_at"]
472
- },
473
- "profiles": {
474
- "dev": { "mode": "verbose" },
475
- "ci": { "failOn": "policy,error" },
476
- "prod": {
477
- "policies": ["default", "strict-prod"],
478
- "severityRemap": { "pool-size-jump": "error" },
479
- "maskSecrets": true
480
- }
481
- },
482
- "files": ["config/**/*.{yaml,yml,json,toml,ini}", ".env"]
483
- }
484
- ```
485
-
486
- ```bash
487
- flecto watch --profile dev
488
- flecto ci --profile ci
489
- ```
490
-
491
- Explicit CLI flags win over profiles, which win over `defaults`.
492
- → **[Full configuration reference](docs/configuration.md)**
152
+ ## Stability
493
153
 
494
- ---
154
+ Flecto runs inside your merge path. The **JSON envelope**
155
+ (`schema_version: "2.0"`), **exit codes** (`0` clean, `1` failed — it fails
156
+ closed), **`.flectorc`** keys, and **command and flag names** follow
157
+ [semver](https://semver.org/), and no breaking change to them ships without a
158
+ minor release that warns first. The one exception is a security fix.
495
159
 
496
- ## Commands
160
+ Terminal output, message wording, and anything under `src/` are deliberately
161
+ **not** stable — parse `--format json`.
497
162
 
498
- | Command | What it does |
499
- |---|---|
500
- | `flecto watch [files...]` | Watch for changes and print them as they happen |
501
- | `flecto watch --snapshot` | Save the current state as a baseline |
502
- | `flecto watch --diff` | Compare against the baseline and exit |
503
- | `flecto ci [files...]` | One-shot check with a gate-able exit code |
504
- | `flecto explain [files...]` | Advisory, model-generated narration of the masked diff (opt-in, your own key) |
505
- | `flecto compare <fileA> <fileB>` | Diff two files against each other (`fileA` is the baseline) |
506
- | `flecto plan <planFiles...>` | Review `terraform show -json` output and gate on it |
507
- | `flecto history [files...]` | Summarize drift across local snapshots |
508
- | `flecto report [files...]` | Render that history as a self-contained HTML file |
509
- | `flecto policies add <name>` | Install a pack from an `flecto-pack-*` npm package |
510
- | `flecto policies list` | List available policy packs |
511
- | `flecto policies test <dir>` | Assert pack and plugin findings from fixtures |
512
- | `flecto init` | Create a `.flectorc` from detected stack signals |
513
- | `flecto mcp` | Serve read-only diff/check/explain tools to an agent over MCP |
514
- | `flecto lsp` | Show findings and changes as diagnostics while a config file is edited |
515
- | `flecto doctor` | Check setup, config, and environment |
516
-
517
- → **[Every flag, every command](docs/cli-reference.md)**
163
+ → **[Full stability policy](docs/stability.md)**
518
164
 
519
165
  ---
520
166
 
@@ -522,40 +168,30 @@ Explicit CLI flags win over profiles, which win over `defaults`.
522
168
 
523
169
  | Guide | Covers |
524
170
  |---|---|
525
- | **[CLI reference](docs/cli-reference.md)** | Every command, flag, and exit code |
526
- | **[Configuration](docs/configuration.md)** | `.flectorc`, profiles, ignore patterns, array identity, masking |
527
- | **[Editor diagnostics](docs/editor.md)** | `flecto lsp` setup for Neovim, Helix, Emacs, and where diagnostics land |
528
- | **[Encrypted files](docs/encrypted-files.md)** | SOPS and age: what is detected, what is reported, why nothing is decrypted |
529
- | **[CI](docs/ci.md)** | Baselines, fail triggers, output formats, the bundled GitHub Actions |
530
- | **[Performance](docs/performance.md)** | Where time goes at scale, and how much smaller a diff is than the config |
531
- | **[Kubernetes](docs/kubernetes.md)** | Diffing rendered Helm/Kustomize manifests before they reach a cluster |
171
+ | **[Getting started](docs/getting-started.md)** | A full walkthrough, from install to a failing build |
172
+ | **[CI](docs/ci.md)** | Baselines, fail triggers, output formats, the bundled Actions, pinning |
532
173
  | **[Terraform plans](docs/terraform.md)** | Reviewing `terraform show -json` output and the `terraform` pack |
174
+ | **[Kubernetes](docs/kubernetes.md)** | Diffing rendered Helm/Kustomize manifests before they reach a cluster |
175
+ | **[Comparison](docs/comparison.md)** | Honest comparison with Checkov, Trivy/tfsec, conftest/OPA, tf-summarize, dyff |
176
+ | **[Supported formats](docs/formats.md)** | YAML, JSON, TOML, INI, dotenv, age — and what each one accepts |
177
+ | **[Encrypted files](docs/encrypted-files.md)** | SOPS and age: what is detected, why nothing is decrypted |
178
+ | **[Configuration](docs/configuration.md)** | `.flectorc`, profiles, ignore patterns, array identity, masking |
179
+ | **[CLI reference](docs/cli-reference.md)** | Every command, flag, and exit code |
180
+ | **[Policy packs](docs/policy-packs.md)** | Writing declarative rules |
181
+ | **[Plugins](docs/plugins.md)** · **[Cookbook](docs/plugin-cookbook.md)** | Rules that need real code |
533
182
  | **[Webhooks and commands](docs/webhooks.md)** | Envelope shape, delivery modes, command environment |
534
183
  | **[MCP server](docs/mcp.md)** | Read-only diff/check/explain tools for agents, and the security posture |
535
184
  | **[Explain](docs/explain.md)** | Opt-in model narration of a diff: what is sent, what it can never do, cost |
536
- | **[Policy packs](docs/policy-packs.md)** | Writing declarative rules |
537
- | **[Plugins](docs/plugins.md)** · **[Cookbook](docs/plugin-cookbook.md)** | Rules that need real code |
538
- | **[Live drift](docs/drift.md)** | `flecto-drift`: comparing a declared config against what is actually running |
185
+ | **[Editor diagnostics](docs/editor.md)** | `flecto lsp` setup for Neovim, Helix, Emacs |
186
+ | **[Live drift](docs/drift.md)** | `flecto-drift`: declared config versus what is actually running |
187
+ | **[Performance](docs/performance.md)** | Where time goes at scale |
188
+ | **[Stability](docs/stability.md)** | What you can build against, and the deprecation sequence |
539
189
  | **[Troubleshooting](docs/troubleshooting.md)** | When something doesn't behave |
540
- | **[Migrating to 4.0](docs/migrating-to-4.md)** | The five breaking changes, and how to tell whether they affect you |
190
+ | **[Migrating to 4.0](docs/migrating-to-4.md)** | The five breaking changes, and whether they affect you |
541
191
  | **[Changelog](CHANGELOG.md)** | Release history and migration notes |
542
192
 
543
193
  ---
544
194
 
545
- ## How it works
546
-
547
- 1. **Parse** — format detected by extension or dotenv naming → structured values
548
- 2. **Watch** — [chokidar](https://github.com/paulmillr/chokidar) with debounce
549
- 3. **Diff** — semantic tree comparison with ignore rules and array identity
550
- 4. **Evaluate** — policy packs and plugins → severity-tagged findings
551
- 5. **Emit** — a versioned envelope (`schema_version: "2.0"`)
552
- 6. **Deliver** — terminal output, shell command, webhook, or CI annotations
553
-
554
- Flecto runs entirely on your machine. Snapshots are local files, and nothing
555
- leaves the process unless you configure a webhook or command.
556
-
557
- ---
558
-
559
195
  ## Project
560
196
 
561
197
  - **Questions and ideas** — [Discussions](https://github.com/myselfsiddharth/Flecto/discussions)
package/package.json CHANGED
@@ -4,17 +4,24 @@
4
4
  "access": "public",
5
5
  "provenance": true
6
6
  },
7
- "version": "4.0.0",
8
- "description": "Flecto \u2014 semantic config watcher that reports meaningful changes in plain English",
7
+ "version": "4.1.1",
8
+ "description": "Reads your Terraform plan and Kubernetes changes and posts a plain-English risk summary on every pull request, blocking the dangerous ones",
9
9
  "license": "MIT",
10
10
  "keywords": [
11
- "flecto",
12
- "cli",
13
- "watcher",
14
- "config",
11
+ "terraform",
12
+ "terraform-plan",
13
+ "kubernetes",
14
+ "helm",
15
+ "pull-request",
16
+ "code-review",
17
+ "policy-as-code",
18
+ "github-actions",
15
19
  "semantic-diff",
20
+ "config",
16
21
  "devops",
17
- "ci"
22
+ "ci",
23
+ "cli",
24
+ "flecto"
18
25
  ],
19
26
  "homepage": "https://github.com/myselfsiddharth/Flecto#readme",
20
27
  "bugs": {
@@ -56,7 +63,7 @@
56
63
  "chalk": "^5.3.0",
57
64
  "chokidar": "^5.0.0",
58
65
  "commander": "^12.1.0",
59
- "dotenv": "^17.4.2",
66
+ "dotenv": "^18.0.1",
60
67
  "fast-glob": "^3.3.3",
61
68
  "js-yaml": "^4.3.0",
62
69
  "re2js": "^2.8.6"
package/src/positions.js CHANGED
@@ -655,9 +655,15 @@ function readJsonValue(state) {
655
655
  // dotenv
656
656
 
657
657
  /**
658
- * dotenv's own line pattern (dotenv/lib/main.js), with match indices. Reading
659
- * keys with the parser's exact pattern is what makes the positions agree with
660
- * it; verification catches a future dotenv that changes it.
658
+ * dotenv's own line pattern, with match indices. Reading keys with the parser's
659
+ * exact pattern is what makes the positions agree with it; verification catches
660
+ * a future dotenv that changes it.
661
+ *
662
+ * Upstream source moved in dotenv 18: it was `dotenv/lib/main.js`, and is now
663
+ * bundled and minified into `dotenv/dist/index.cjs`. The pattern itself is
664
+ * unchanged between 17.4.2 and 18.0.1 -- byte-identical apart from the `d` flag
665
+ * added here for match indices. To re-check it after a bump, grep the bundle for
666
+ * `export\s+`, which is distinctive enough to find the regex in minified code.
661
667
  */
662
668
  const DOTENV_LINE = /(?:^|^)\s*(?:export\s+)?([\w.-]+)(?:\s*=\s*?|:\s+?)(\s*'(?:\\'|[^'])*'|\s*"(?:\\"|[^"])*"|\s*`(?:\\`|[^`])*`|[^#\r\n]+)?\s*(?:#.*)?(?:$|$)/dgm;
663
669