flecto 2.1.0 → 3.0.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/README.md CHANGED
@@ -5,52 +5,47 @@
5
5
  <h1 align="center">Flecto</h1>
6
6
 
7
7
  <p align="center">
8
- <strong>Config changes, in plain Englishwith risk flags built in.</strong><br/>
9
- Watch · Diff · Policy · CI · Webhooks
8
+ <strong>Know what your config actually changed and whether it's risky.</strong>
10
9
  </p>
11
10
 
12
11
  <p align="center">
13
12
  <a href="https://www.npmjs.com/package/flecto"><img alt="npm" src="https://img.shields.io/npm/v/flecto?style=flat-square&color=34d399&labelColor=0b1220"/></a>
14
13
  <a href="https://github.com/myselfsiddharth/Flecto/actions/workflows/ci.yml"><img alt="CI" src="https://img.shields.io/github/actions/workflow/status/myselfsiddharth/Flecto/ci.yml?branch=main&style=flat-square&label=CI&labelColor=0b1220"/></a>
15
14
  <a href="LICENSE"><img alt="MIT" src="https://img.shields.io/badge/license-MIT-8fa3bf?style=flat-square&labelColor=0b1220"/></a>
16
- <a href="https://github.com/myselfsiddharth/Flecto/stargazers"><img alt="Stars" src="https://img.shields.io/github/stars/myselfsiddharth/Flecto?style=flat-square&color=fbbf24&labelColor=0b1220"/></a>
15
+ <a href="#documentation"><img alt="Docs" src="https://img.shields.io/badge/docs-read-34d399?style=flat-square&labelColor=0b1220"/></a>
17
16
  </p>
18
17
 
19
18
  <p align="center">
20
- <a href="https://github.com/myselfsiddharth/Flecto/stargazers">⭐ Star this repo</a> if Flecto saves you from a noisy config diff it helps others find the project.
21
- </p>
22
-
23
- <p align="center">
24
- <img src="docs/assets/flecto-demo.png" alt="Flecto watch demo in the terminal" width="920"/>
25
- </p>
26
-
27
- <p align="center">
28
- <img src="docs/assets/demo-watch.svg" alt="Animated Flecto watch output" width="920"/>
19
+ <img src="docs/assets/demo-watch.svg" alt="Flecto reporting semantic config changes in the terminal" width="920"/>
29
20
  </p>
30
21
 
31
22
  ---
32
23
 
33
- ## Why teams use Flecto
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.
34
28
 
35
- Line diffs lie about config. Formatting churn, key reorders, and “small” YAML edits hide the changes that actually matter in production.
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.
36
31
 
37
- **Flecto** turns structured config into semantic events:
38
-
39
- - What changed (`pool_size: 5 → 20`)
40
- - What was added or removed
41
- - What looks risky (secrets, dangerous toggles, pool jumps)
42
- - What to do next (CI gate, webhook, shell command)
43
-
44
- See the [changelog and migration notes](CHANGELOG.md) for release history and
45
- upcoming v2.1 behavior changes.
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 |
46
37
 
47
- > Diff tools compare trees. **Flecto watches, scores risk, and alerts.**
38
+ The same engine reads whatever your change actually lives in:
48
39
 
49
- | Without Flecto | With Flecto |
40
+ | You are reviewing | Flecto reads |
50
41
  |---|---|
51
- | `+ 40 lines of YAML noise` | `~ database.pool_size: 5 20` |
52
- | Hope someone notices `debug: true` | Policy finding CI fails |
53
- | “Something in `.env` changed” | Exact keys + optional secret masking |
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.
54
49
 
55
50
  ---
56
51
 
@@ -60,358 +55,408 @@ upcoming v2.1 behavior changes.
60
55
  npm install -g flecto
61
56
  ```
62
57
 
63
- Requires Node.js 20.19.0 or later.
58
+ Requires **Node.js 20.19.0+**. Verify:
64
59
 
65
60
  ```bash
66
61
  flecto --version
67
62
  flecto doctor
68
63
  ```
69
64
 
70
- ---
71
-
72
- ## Quick start
73
-
74
- ```bash
75
- flecto watch config/prod.yaml
76
- flecto watch .env
77
- flecto watch settings.json
78
- flecto watch pyproject.toml
79
- flecto watch app.ini
80
- ```
81
-
82
- That’s it — Flecto prints a clear summary on every meaningful change.
65
+ Prefer not to install globally? Every example below works with
66
+ `npx --yes flecto@3` instead of `flecto`.
83
67
 
84
68
  ---
85
69
 
86
- ## Features at a glance
87
-
88
- - **Semantic diffs** for JSON, YAML, TOML, INI, and dotenv (`.env`, `.env.*`, `*.env`)
89
- - **Live watch** with optional command + webhook delivery
90
- - **Policy packs** (`default`, `strict-prod`, `compose`, `node-runtime`) + custom `policies/*.json` + local ESM plugins
91
- - **CI mode** with JSON / NDJSON / GitHub annotations and fail rules
92
- - **Snapshots & diffs** for deploy scripts and pre-commit hooks
93
- - **Profiles** via `--profile` or `FLECTO_PROFILE`
94
- - **Default array identity** (`id` / `name`, or `--array-id-key`) and secret masking
95
-
96
- ---
70
+ ## Quick start
97
71
 
98
- ## Common use cases
72
+ A complete walkthrough, start to finish. Copy-paste it anywhere.
99
73
 
100
- ### Watch multiple files
74
+ **1. Create a config file to track.**
101
75
 
102
76
  ```bash
103
- flecto watch "config/**/*.yaml" ".env"
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
104
87
  ```
105
88
 
106
- ### Verbose before/after
89
+ **2. Save it as your baseline.**
107
90
 
108
91
  ```bash
109
- flecto watch config/prod.yaml --mode verbose
92
+ flecto watch config/prod.yaml --snapshot
93
+ ```
94
+
95
+ ```
96
+ ✓ Snapshot saved: /path/to/flecto-demo/.flecto-snapshots/4b8cbbd70d1832a2.json
110
97
  ```
111
98
 
112
- ### Ignore noisy keys
99
+ **3. Make the kind of edit that causes incidents.**
113
100
 
114
101
  ```bash
115
- flecto watch config/prod.yaml --ignore "updated_at,meta.timestamp"
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
116
111
  ```
117
112
 
118
- | Pattern | What it ignores |
119
- |---|---|
120
- | `meta.timestamp` | That exact key |
121
- | `meta` | Everything under `meta.*` |
122
- | `servers[*].meta.timestamp` | That key inside any array item |
123
- | `**.updated_at` | Any key named `updated_at`, anywhere |
124
-
125
- ### Run a command on change
113
+ **4. Ask what changed.**
126
114
 
127
115
  ```bash
128
- flecto watch .env --command "docker-compose restart app"
116
+ flecto watch config/prod.yaml --diff
129
117
  ```
130
118
 
131
- Changes are passed as JSON via `FLECTO_CHANGES` (large payloads may use `FLECTO_CHANGES_FILE`).
119
+ ```
120
+ /path/to/flecto-demo/config/prod.yaml — 2 changes from snapshot:
121
+ ~ database.pool_size: 5 → 20
122
+ ~ logging.debug: false → true
123
+ ```
132
124
 
133
- ### Webhooks
125
+ Two sentences instead of a diff you have to interpret. Now let Flecto judge it:
134
126
 
135
127
  ```bash
136
- flecto watch config/prod.yaml \
137
- --webhook https://hooks.example.com/notify \
138
- --webhook-header "Authorization: Bearer TOKEN"
128
+ flecto ci config/prod.yaml --format github-annotations
139
129
  ```
140
130
 
141
- Envelope shape (`schema_version: "2.0"`):
142
-
143
- ```json
144
- {
145
- "schema_version": "2.0",
146
- "event_id": "uuid",
147
- "event_type": "changes",
148
- "emitted_at": "2026-04-14T10:42:31.000Z",
149
- "file": "/absolute/path/to/config/prod.yaml",
150
- "changes": [
151
- { "type": "changed", "path": "database.pool_size", "before": 5, "after": 20 }
152
- ],
153
- "policies": [
154
- {
155
- "id": "pool-size-jump",
156
- "severity": "warn",
157
- "path": "database.pool_size",
158
- "message": "Pool size increased from 5 to 20 (>=2x).",
159
- "pack": "default"
160
- }
161
- ]
162
- }
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.
163
136
  ```
164
137
 
165
- JSON Schema: [`schemas/flecto-envelope-2.0.json`](schemas/flecto-envelope-2.0.json).
138
+ Exit code `1`. In CI, that's a failed build — before the change ships.
166
139
 
167
- ### Policy packs and profiles
140
+ **5. Watch it live.** Leave this running and edit the file in another window:
168
141
 
169
142
  ```bash
170
- flecto ci config/prod.yaml --profile prod --snapshot-ref HEAD~1
143
+ flecto watch config/prod.yaml
171
144
  ```
172
145
 
173
- ```json
174
- {
175
- "defaults": {
176
- "policies": ["default"],
177
- "maskSecrets": false
178
- },
179
- "profiles": {
180
- "prod": {
181
- "policies": ["default", "strict-prod"],
182
- "severityRemap": { "pool-size-jump": "error" },
183
- "maskSecrets": true
184
- }
185
- }
186
- }
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.
187
155
  ```
188
156
 
189
- Profile selection: `--profile` > `FLECTO_PROFILE` > defaults.
190
- Custom packs: `policies/<id>.json`. Plugins: local ESM exporting `evaluate(changes, ctx)`.
157
+ That's the whole product. Everything below is depth.
191
158
 
192
- #### Declarative rule predicates
159
+ ---
193
160
 
194
- Rules combine their top-level predicates with AND. In addition to `when`, regex
195
- `match.path`, `afterEquals`, and `numericJump`, packs can use:
161
+ ## What you can do with it
196
162
 
197
- - `beforeEquals`, `afterIn`, and `beforeIn` for exact values or allowed value lists.
198
- - `beforeTruthy: true` and `afterTruthy: true` to require a truthy before/after value.
199
- - `afterMatches` to require a string after value that matches a regular expression.
200
- - `numericDelta: { "min": 10 }` to match an absolute numeric change of at least 10.
201
- - `match.pathEquals` and `match.pathPrefix` for exact or prefix path matching without regex.
202
- - `allOf` and `anyOf` arrays of simple match clauses. Every `allOf` clause and at least
203
- one `anyOf` clause must match. Clauses support the same value, truthiness, numeric, and
204
- `match` predicates, but cannot nest composition.
163
+ ### Catch risky changes before they merge
205
164
 
206
- ```json
207
- {
208
- "id": "risky-feature-enable",
209
- "severity": "error",
210
- "allOf": [
211
- { "match": { "pathPrefix": "features." } },
212
- { "afterTruthy": true }
213
- ],
214
- "anyOf": [
215
- { "afterEquals": true },
216
- { "afterIn": ["unsafe", "disabled"] }
217
- ]
218
- }
165
+ Add one step to your workflow and risky config edits show up as annotations on
166
+ the pull request:
167
+
168
+ ```yaml
169
+ permissions:
170
+ contents: read
171
+
172
+ steps:
173
+ - uses: actions/checkout@v7
174
+ 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
219
180
  ```
220
181
 
221
- Pack loading fails closed for unknown rule or `match` fields, invalid regexes, and invalid
222
- predicate shapes, so misspelled predicates cannot silently disable a rule.
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.
223
187
 
224
- Use `severityRemap` in defaults or a profile to change pack rule severities without forking a pack:
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`:
225
190
 
226
- ```json
227
- {
228
- "profiles": {
229
- "dev": {
230
- "severityRemap": { "pool-size-jump": "off" }
231
- },
232
- "prod": {
233
- "severityRemap": { "pool-size-jump": "error" }
234
- }
235
- }
236
- }
191
+ ```yaml
192
+ permissions:
193
+ contents: read
194
+ pull-requests: write
195
+
196
+ steps:
197
+ - uses: actions/checkout@v7
198
+ with:
199
+ fetch-depth: 0
200
+ - uses: myselfsiddharth/Flecto/.github/actions/flecto-pr-risk@main
237
201
  ```
238
202
 
239
- Each key is a rule id and each value must be `info`, `warn`, `error`, or `off`. The remap applies after all configured built-in and local packs load, before findings and CI `--fail-on` checks. When multiple packs provide the same rule id, the remap applies to every matching pack rule. Plugin findings are unchanged. Unknown rule ids print a warning instead of being ignored silently.
203
+ A fork's pull request gets a read-only token, so the comment is skipped with a
204
+ warning there — the check itself still runs and still fails on risky changes.
205
+
206
+ Works on any CI runner — it's a plain CLI with meaningful exit codes.
207
+ → **[CI guide](docs/ci.md)**
240
208
 
241
- Authoring guides: [policy packs](docs/policy-packs.md) · [plugins](docs/plugins.md) · [plugin cookbook](docs/plugin-cookbook.md).
209
+ ### Trigger automation on change
242
210
 
243
- ### Discover policy packs
211
+ Restart a service, reload a process, or notify an endpoint whenever config moves:
244
212
 
245
213
  ```bash
246
- flecto policies list
247
- flecto policies list --json
214
+ flecto watch .env --command "docker-compose restart app"
215
+
216
+ flecto watch config/prod.yaml \
217
+ --webhook https://hooks.example.com/notify \
218
+ --delivery-mode at-least-once
248
219
  ```
249
220
 
250
- The command lists every bundled and local pack that resolves from the current
251
- working directory, including its source path and rule count. For a given pack
252
- id, Flecto resolves local files before bundled packs in this order:
253
- `policies/<id>.json`, `policies/<id>.yaml`, `policies/<id>.yml`, then the
254
- built-in pack. A local pack with the same id overrides its built-in counterpart.
221
+ Changes arrive as a versioned JSON envelope, and `at-least-once` persists and
222
+ retries failed deliveries.
255
223
 
256
- ### Array identity matching
224
+ Posting straight to chat needs no receiver of your own — `--webhook-format`
225
+ shapes the body for Slack, Discord, or Teams, colored by the highest policy
226
+ severity:
257
227
 
258
228
  ```bash
259
- flecto watch config/services.yaml
229
+ flecto watch config/prod.yaml \
230
+ --webhook "https://hooks.slack.com/services/T000/B000/XXXX" \
231
+ --webhook-format slack
260
232
  ```
261
233
 
262
- Arrays of objects automatically match by a shared, unique `id` key, falling back
263
- to `name` when `id` is unavailable. This avoids false changes when named items
264
- are reordered. Use a custom identity field when needed:
234
+ **[Webhooks and commands](docs/webhooks.md)**
265
235
 
266
- ```bash
267
- flecto watch config/services.yaml --array-id-key serviceKey
268
- ```
236
+ ### Compare two environments
269
237
 
270
- To restore index-based diffs for every array, pass `--no-array-id`:
238
+ "Works in staging, fails in prod" is usually one key apart:
271
239
 
272
240
  ```bash
273
- flecto watch config/services.yaml --no-array-id
241
+ flecto compare config/prod.yaml config/staging.yaml
242
+ ```
243
+
244
+ ```
245
+ "+" exists only in the compared file, "-" only in the baseline, "~" differs
246
+ /path/to/config/staging.yaml — 2 changes from /path/to/config/prod.yaml:
247
+ - only_in_prod: true
248
+ ~ database.pool_size: 5 → 20
249
+ ! policy(warn) [default] database.pool_size: Pool size increased from 5 to 20 (>=2x).
274
250
  ```
275
251
 
276
- In `.flectorc`, set `"arrayId": false` in `defaults` or a profile for the same
277
- escape hatch.
252
+ The first file is the baseline, the files don't have to share a format, and
253
+ `--format json` gives you the same output `flecto ci` produces.
254
+ → **[CLI reference](docs/cli-reference.md#flecto-compare-filea-fileb)**
278
255
 
279
- ### Command + webhook together
256
+ ### Track drift over time
280
257
 
281
258
  ```bash
282
- flecto watch .env \
283
- --command "make reload" \
284
- --webhook https://hooks.example.com/notify
259
+ flecto history config/prod.yaml --limit 10
285
260
  ```
286
261
 
287
- ### Retry on failure
262
+ Snapshots stay on your machine in `.flecto-snapshots/`. Nothing is uploaded and
263
+ no account is required. → **[CLI reference](docs/cli-reference.md#flecto-history-files)**
264
+
265
+ ### Share what changed before the incident
288
266
 
289
267
  ```bash
290
- flecto watch config/prod.yaml \
291
- --webhook https://hooks.example.com/notify \
292
- --delivery-mode at-least-once \
293
- --on-alert-failure retry
268
+ flecto report --limit 20 --mask-secrets --output drift.html
294
269
  ```
295
270
 
296
- | Flag | Options | What it does |
297
- |---|---|---|
298
- | `--delivery-mode` | `best-effort` (default), `at-least-once` | Persist and retry failed webhook events |
299
- | `--on-alert-failure` | `warn`, `exit`, `retry` | Behavior when command/webhook fails |
271
+ One HTML file from that same local history: a timeline per file, every change
272
+ with its UTC timestamp, and policy findings grouped by severity. Fully
273
+ self-contained inline styles, no fonts, no CDN scripts, no analytics so you
274
+ can attach it to an incident thread and it renders offline. No server and no
275
+ account, same as everything else here.
276
+ → **[CLI reference](docs/cli-reference.md#flecto-report-files)**
300
277
 
301
- ---
278
+ ### Review a Kubernetes change before it reaches a cluster
302
279
 
303
- ## Snapshots & diffs
280
+ ArgoCD, Flux, and `helm diff` compare the cluster to the repo — which needs a
281
+ cluster, and an apply that already happened. Flecto compares the manifests *this
282
+ pull request would produce* against the ones `main` produces:
304
283
 
305
284
  ```bash
306
- flecto watch config/prod.yaml --snapshot
307
- flecto watch config/prod.yaml --diff
308
- flecto history config/prod.yaml --limit 10
285
+ helm template api ./charts/api -f values/prod.yaml > /tmp/head.yaml
286
+ flecto compare /tmp/base.yaml /tmp/head.yaml --policies kubernetes --fail-on error
309
287
  ```
310
288
 
311
- Exit codes: `0` clean · `1` changes detected.
289
+ ```
290
+ ~ Service/prod/api.spec.type: "ClusterIP" → "LoadBalancer"
291
+ ! policy(error) [kubernetes] Service type is LoadBalancer, which exposes the
292
+ workload outside the cluster. Confirm the exposure is intended.
293
+ ```
312
294
 
313
- `flecto history` stays local: it lists recent snapshots from `.flecto-snapshots/` with their timestamps and semantic change counts from the previous snapshot. Counts use the same ignore paths, array identity, and order settings as `flecto watch --diff` (CLI flags or `.flectorc`). Omit files to view all saved snapshot history.
295
+ Multi-document YAML is keyed by `kind/namespace/name`, so findings name the
296
+ resource. Flecto never runs `helm` or `kustomize` — you render, it diffs, so any
297
+ renderer works and no binary is needed in CI.
298
+ → **[Kubernetes](docs/kubernetes.md)**
314
299
 
315
- ---
300
+ ### Read a Terraform plan in plain English
316
301
 
317
- ## CI mode
302
+ `terraform plan` output is precise and long. Flecto turns it into the handful of
303
+ lines a reviewer actually needs to argue about:
318
304
 
319
305
  ```bash
320
- flecto ci "config/**/*.yaml" \
321
- --snapshot-ref HEAD~1 \
322
- --format github-annotations \
323
- --fail-on "changed,policy,error"
306
+ terraform show -json plan.tfplan > plan.json
307
+ flecto plan plan.json --fail-on error
324
308
  ```
325
309
 
326
- **Formats:** `json`, `ndjson`, `github-annotations`
327
- **Fail triggers:** `changed`, `added`, `removed`, `policy`, `error`, `warn`
328
- Unresolved `--snapshot-ref` fails closed (no silent empty baseline).
329
- If every target is missing or unsupported, `flecto ci` and `flecto watch --snapshot` exit non-zero (pass `--allow-empty` to opt out).
310
+ ```
311
+ plan.json plan format 1.2
312
+ Plan: 0 to add, 1 to change, 0 to destroy, 1 to replace.
313
+ ~ aws_security_group.web.ingress[0].cidr_blocks[0]: "10.0.0.0/8" "0.0.0.0/0"
314
+ - aws_db_instance.main.#action: "replace" [terraform will destroy and recreate aws_db_instance.main]
315
+ ~ aws_db_instance.main.password: "(sensitive value)" → "(sensitive value)" [sensitive]
316
+ ! policy(error) [terraform] …cidr_blocks[0]: Security group ingress will accept
317
+ traffic from the whole internet (0.0.0.0/0). Restrict the source to a known CIDR…
318
+ ! policy(error) [terraform] …#action: Terraform will destroy a stateful resource.
319
+ Its data does not survive. Take a final snapshot, or add a prevent_destroy…
320
+ ```
330
321
 
331
- ### GitHub Action
322
+ A **replace reads as a removal**, not a benign update — a recreated database
323
+ should never look like a config tweak. Values Terraform marks sensitive are
324
+ redacted during parsing, before the policy engine or any formatter sees them, and
325
+ `after_unknown` renders as `(known after apply)` rather than `null`.
332
326
 
333
- Use the [Flecto CI Action](.github/actions/flecto-ci/action.yml) to run `flecto ci` in a workflow with GitHub annotations enabled by default. A complete local-action workflow is available at [`examples/github-action/flecto-ci.yml`](examples/github-action/flecto-ci.yml).
327
+ **Flecto never runs `terraform`** you produce the JSON, it reads it, so nothing
328
+ extra has to exist on the CI runner.
329
+ → **[Terraform plans](docs/terraform.md)**
334
330
 
335
- ```yaml
336
- permissions:
337
- contents: read
331
+ ### Encode your own rules
338
332
 
339
- steps:
340
- - uses: actions/checkout@v7
341
- with:
342
- fetch-depth: 2
343
- - uses: myselfsiddharth/Flecto/.github/actions/flecto-ci@main
344
- with:
345
- targets: config/**/*.{yaml,yml,json,toml,ini}
346
- snapshot-ref: HEAD~1
333
+ Beyond the built-in packs, write rules as declarative JSON or YAML — no code:
334
+
335
+ ```json
336
+ {
337
+ "id": "risky-feature-enable",
338
+ "severity": "error",
339
+ "allOf": [
340
+ { "match": { "pathPrefix": "features." } },
341
+ { "afterTruthy": true }
342
+ ]
343
+ }
347
344
  ```
348
345
 
349
- `contents: read` is required by `actions/checkout`. The Action emits workflow-command annotations and needs no write permissions. Keep `fetch-depth: 2` (or use `fetch-depth: 0`) when using the default `HEAD~1` baseline.
346
+ For anything a predicate can't express, a local ESM plugin exporting
347
+ `evaluate(changes, ctx)` gets the full change set.
348
+ → **[Writing policy packs](docs/policy-packs.md)** · **[Plugins](docs/plugins.md)**
350
349
 
351
- | Input | Default | Description |
352
- |---|---|---|
353
- | `targets` | `config/**/*.{yaml,yml,json,toml,ini}` | Whitespace-separated paths or glob patterns to check |
354
- | `fail-on` | `policy,error` | Comma-separated events that fail the job |
355
- | `policies` | _(empty)_ | Comma-separated policy pack IDs; omit to use `.flectorc` / Flecto defaults |
356
- | `profile` | _(empty)_ | Optional `.flectorc` profile |
357
- | `format` | `github-annotations` | Flecto output format |
358
- | `snapshot-ref` | `HEAD~1` | Git ref or snapshot file used as the baseline |
359
- | `node-version` | `20` | Node.js version used to run Flecto |
350
+ ### Cut the noise
360
351
 
361
- The Action runs `npx --yes flecto@2 ci`: the major version is pinned so compatible Flecto updates are received. For fully reproducible builds, pin the Action reference to a commit SHA and replace `@2` in a forked Action with an exact published Flecto version.
352
+ ```bash
353
+ flecto watch config/prod.yaml --ignore "updated_at,**.meta.timestamp"
354
+ ```
355
+
356
+ Arrays of objects are matched by `id` or `name`, so reordering a list of named
357
+ services doesn't read as a wall of changes.
358
+ → **[Configuration](docs/configuration.md)**
362
359
 
363
360
  ---
364
361
 
365
- ## Built-in policy checks
362
+ ## Built-in policy packs
366
363
 
367
- Built-in pack ids:
364
+ | Pack | Catches |
365
+ |---|---|
366
+ | `default` | Secret-like keys added or changed, secret-shaped *values* under any key, dangerous toggles, pool-size jumps |
367
+ | `strict-prod` | The same ground, with production-grade severities and matching |
368
+ | `compose` | Privileged services, host networking, Docker socket mounts, sensitive bind mounts |
369
+ | `kubernetes` | Privileged containers, host namespaces, weakened `runAsNonRoot`, added `SYS_ADMIN`, unpinned images, replica jumps, dropped limits, `LoadBalancer`/`NodePort` exposure |
370
+ | `node-runtime` | Dropped engine requirements, TLS verification bypasses, debug/inspector flags |
371
+ | `terraform` | Replaced and destroyed stateful resources, ingress opened to `0.0.0.0/0`, IAM wildcards, public S3, capacity jumps |
372
+ | `sops` | Decryption recipients added or removed, a MAC that moved on its own, a file that stopped being encrypted |
368
373
 
369
- - `default` — secrets, dangerous toggles, and pool-size jumps.
370
- - `strict-prod` stricter severities and matching for production use.
371
- - `compose` privileged services, host networking, Docker socket mounts, and sensitive host-directory bind mounts.
372
- - `node-runtime` — removed Node.js engine requirements, TLS verification bypasses, and enabled Node debugging or inspector options.
374
+ ```bash
375
+ flecto policies list # see what resolves here, built-in and local
376
+ flecto ci "config/**/*.yaml" --policies "default,strict-prod" --fail-on policy
377
+ ```
378
+
379
+ Pass files or glob patterns, quoted so your shell doesn't expand them first — a
380
+ bare directory is not a valid target.
381
+
382
+ A local `policies/<id>.json` overrides the built-in pack of the same id, and
383
+ `severityRemap` raises or silences individual rules per profile without forking
384
+ anything. → **[Policy packs](docs/policy-packs.md)**
385
+
386
+ Community packs ship on npm as `flecto-pack-<id>` packages — a package name and
387
+ one declarative JSON file, nothing else:
373
388
 
374
- Fail CI with `--fail-on policy`.
389
+ ```bash
390
+ npm install --save-dev flecto-pack-deployment-safety
391
+ flecto policies add deployment-safety
392
+ ```
393
+
394
+ `policies add` validates the pack, writes it to `policies/deployment-safety.json`,
395
+ and runs no code from the package. →
396
+ **[Installing community packs](docs/policy-packs.md#installing-a-community-pack)**
375
397
 
376
398
  ---
377
399
 
378
- ## Migrating from envelope 1.1
400
+ ## Supported formats
379
401
 
380
- - `schema_version` is now `"2.0"`
381
- - Type name is `FlectoEnvelope` (docs/types)
382
- - New `policies` array on change envelopes
383
- - Webhook headers unchanged (`X-Flecto-*`)
402
+ | Format | Extensions |
403
+ |---|---|
404
+ | JSON | `.json` |
405
+ | YAML | `.yaml`, `.yml` |
406
+ | TOML | `.toml` |
407
+ | INI | `.ini` |
408
+ | dotenv | `.env`, `.env.*`, `*.env` |
409
+ | age (armored) | `.age`, or any file whose contents are one armored blob |
410
+
411
+ Terraform plan JSON (`terraform show -json`) is read by **`flecto plan`**, which
412
+ applies Terraform's own sensitivity marking. Point `plan` at it rather than `ci`
413
+ or `watch` — those treat it as ordinary JSON and will print values Terraform
414
+ marks sensitive ([#113](https://github.com/myselfsiddharth/Flecto/issues/113)).
415
+
416
+ Multi-document YAML (`---`-separated, the usual shape of a Kubernetes manifest)
417
+ is supported. Each document is diffed under its own key — `kind/name` for
418
+ Kubernetes-shaped documents, so a document inserted at the top of the file does
419
+ not renumber every other path. →
420
+ **[Multi-document YAML](docs/configuration.md#multi-document-yaml)**
384
421
 
385
422
  ---
386
423
 
387
- ## Tuning for network drives / odd editors
424
+ ## Encrypted files
388
425
 
389
- ```bash
390
- flecto watch config/prod.yaml --polling --interval 500
426
+ A `sops`- or age-encrypted file is detected from its **contents**, and diffed
427
+ structurally:
428
+
429
+ ```
430
+ + cache: {"ttl_seconds":300}
431
+ ~ database.password: <encrypted value changed>
432
+ + sops.age.age1exampleexample…: {"recipient":"age1exampleexample…","enc":"<encrypted value>"}
391
433
  ```
392
434
 
393
- Polling is off by default (interval default `100ms` when enabled).
435
+ You get keys added and removed, which encrypted values moved, and — the useful
436
+ part — who can decrypt the file. A recipient added is a genuine security event
437
+ and the `sops` pack raises it as one.
438
+
439
+ **Flecto never decrypts.** It never shells out to `sops` or `age`, never reads a
440
+ key file or agent socket, and never prints ciphertext — not even without
441
+ `--mask-secrets`. Ciphertext is replaced with an opaque sentinel in the parser,
442
+ so no diff, snapshot, webhook, or report can carry it. →
443
+ **[Encrypted files](docs/encrypted-files.md)**
394
444
 
395
445
  ---
396
446
 
397
- ## Config file (`.flectorc`)
447
+ ## Configuration
448
+
449
+ Most teams commit a `.flectorc` so local runs and CI agree:
398
450
 
399
451
  ```bash
400
452
  flecto init
401
453
  ```
402
454
 
403
- Looks for `.flectorc`, `.flectorc.json`, `.flectorc.yaml`, or `.flectorc.yml`.
404
-
405
455
  ```json
406
456
  {
407
457
  "defaults": {
408
- "mode": "compact",
409
- "interval": 100,
410
- "ignore": ["**.updated_at"],
411
- "deliveryMode": "best-effort",
412
- "onAlertFailure": "warn",
413
458
  "policies": ["default"],
414
- "arrayId": true
459
+ "ignore": ["**.updated_at"]
415
460
  },
416
461
  "profiles": {
417
462
  "dev": { "mode": "verbose" },
@@ -422,81 +467,80 @@ Looks for `.flectorc`, `.flectorc.json`, `.flectorc.yaml`, or `.flectorc.yml`.
422
467
  "maskSecrets": true
423
468
  }
424
469
  },
425
- "files": ["config/**/*.{yaml,yml,json,toml,ini}", ".env", ".env.*", "*.env"],
426
- "exclude": ["**/node_modules/**"]
470
+ "files": ["config/**/*.{yaml,yml,json,toml,ini}", ".env"]
427
471
  }
428
472
  ```
429
473
 
430
474
  ```bash
431
475
  flecto watch --profile dev
432
476
  flecto ci --profile ci
433
- flecto doctor
434
477
  ```
435
478
 
436
- CLI flags override profile/default values.
479
+ Explicit CLI flags win over profiles, which win over `defaults`.
480
+ → **[Full configuration reference](docs/configuration.md)**
437
481
 
438
482
  ---
439
483
 
440
- ## Output format reference
441
-
442
- ### Compact (default)
443
-
444
- ```
445
- [HH:MM:SS] <filepath> — N changes
446
- ~ path: before → after (yellow — value changed)
447
- + path: value (green — key added)
448
- - path: value (red — key removed)
449
- ```
450
-
451
- ### Verbose (`--mode verbose`)
484
+ ## Commands
452
485
 
453
- ```
454
- [HH:MM:SS] <filepath> — N changes
455
- ~ path
456
- before: old_value
457
- after: new_value
458
- ```
486
+ | Command | What it does |
487
+ |---|---|
488
+ | `flecto watch [files...]` | Watch for changes and print them as they happen |
489
+ | `flecto watch --snapshot` | Save the current state as a baseline |
490
+ | `flecto watch --diff` | Compare against the baseline and exit |
491
+ | `flecto ci [files...]` | One-shot check with a gate-able exit code |
492
+ | `flecto compare <fileA> <fileB>` | Diff two files against each other (`fileA` is the baseline) |
493
+ | `flecto plan <planFiles...>` | Review `terraform show -json` output and gate on it |
494
+ | `flecto history [files...]` | Summarize drift across local snapshots |
495
+ | `flecto report [files...]` | Render that history as a self-contained HTML file |
496
+ | `flecto policies add <name>` | Install a pack from an `flecto-pack-*` npm package |
497
+ | `flecto policies list` | List available policy packs |
498
+ | `flecto policies test <dir>` | Assert pack and plugin findings from fixtures |
499
+ | `flecto init` | Create a `.flectorc` from detected stack signals |
500
+ | `flecto doctor` | Check setup, config, and environment |
501
+
502
+ → **[Every flag, every command](docs/cli-reference.md)**
459
503
 
460
504
  ---
461
505
 
462
- ## Error handling
506
+ ## Documentation
463
507
 
464
- | Situation | Behavior |
508
+ | Guide | Covers |
465
509
  |---|---|
466
- | File not found | Error + exit 1 |
467
- | Unsupported format | Lists supported extensions + exit 1 |
468
- | Parse error while watching | Warning, last valid state kept |
469
- | Command / webhook fails | Warning (unless `--on-alert-failure exit`) |
470
- | Ctrl+C | Clean shutdown |
510
+ | **[CLI reference](docs/cli-reference.md)** | Every command, flag, and exit code |
511
+ | **[Configuration](docs/configuration.md)** | `.flectorc`, profiles, ignore patterns, array identity, masking |
512
+ | **[Encrypted files](docs/encrypted-files.md)** | SOPS and age: what is detected, what is reported, why nothing is decrypted |
513
+ | **[CI](docs/ci.md)** | Baselines, fail triggers, output formats, the bundled GitHub Actions |
514
+ | **[Kubernetes](docs/kubernetes.md)** | Diffing rendered Helm/Kustomize manifests before they reach a cluster |
515
+ | **[Terraform plans](docs/terraform.md)** | Reviewing `terraform show -json` output and the `terraform` pack |
516
+ | **[Webhooks and commands](docs/webhooks.md)** | Envelope shape, delivery modes, command environment |
517
+ | **[Policy packs](docs/policy-packs.md)** | Writing declarative rules |
518
+ | **[Plugins](docs/plugins.md)** · **[Cookbook](docs/plugin-cookbook.md)** | Rules that need real code |
519
+ | **[Troubleshooting](docs/troubleshooting.md)** | When something doesn't behave |
520
+ | **[Changelog](CHANGELOG.md)** | Release history and migration notes |
471
521
 
472
522
  ---
473
523
 
474
524
  ## How it works
475
525
 
476
- 1. **Parser** — format by extension / dotenv naming → structured values
477
- 2. **Watcher** — [chokidar](https://github.com/paulmillr/chokidar) + debounce
478
- 3. **Differ** — semantic tree diff (objects, arrays, ignore rules, automatic array ids)
479
- 4. **Policy engine** — packs + plugins → severity findings
480
- 5. **Envelope** — versioned automation payload (`2.0`)
481
- 6. **Alerter** — command and/or webhook with retry modes
482
-
483
- ---
484
-
485
- ## Contributing
486
-
487
- Flecto is open source. See [CONTRIBUTING.md](CONTRIBUTING.md) for setup, PR rules, and review expectations.
488
- Please read the [Code of Conduct](CODE_OF_CONDUCT.md) and [Security policy](SECURITY.md).
526
+ 1. **Parse** — format detected by extension or dotenv naming → structured values
527
+ 2. **Watch** — [chokidar](https://github.com/paulmillr/chokidar) with debounce
528
+ 3. **Diff** — semantic tree comparison with ignore rules and array identity
529
+ 4. **Evaluate** — policy packs and plugins → severity-tagged findings
530
+ 5. **Emit** — a versioned envelope (`schema_version: "2.0"`)
531
+ 6. **Deliver** — terminal output, shell command, webhook, or CI annotations
489
532
 
490
- Roadmap lives in [GitHub milestones](https://github.com/myselfsiddharth/Flecto/milestones).
533
+ Flecto runs entirely on your machine. Snapshots are local files, and nothing
534
+ leaves the process unless you configure a webhook or command.
491
535
 
492
536
  ---
493
537
 
494
- ## Star the project
495
-
496
- If Flecto helps your team catch a risky config change — **[star the repo](https://github.com/myselfsiddharth/Flecto)** so more people can find it.
497
-
498
- ---
538
+ ## Project
499
539
 
500
- ## License
540
+ - **Questions and ideas** — [Discussions](https://github.com/myselfsiddharth/Flecto/discussions)
541
+ - **Bugs and requests** — [Issues](https://github.com/myselfsiddharth/Flecto/issues)
542
+ - **Contributing** — [CONTRIBUTING.md](CONTRIBUTING.md) · [Code of Conduct](CODE_OF_CONDUCT.md)
543
+ - **Security** — [SECURITY.md](SECURITY.md), private disclosure only
544
+ - **Roadmap** — [Milestones](https://github.com/myselfsiddharth/Flecto/milestones)
501
545
 
502
- MIT see [LICENSE](./LICENSE).
546
+ Released under the [MIT License](LICENSE).