Config changes, in plain English — with risk flags built in.
Watch · Diff · Policy · CI · Webhooks
⭐ Star this repo if Flecto saves you from a noisy config diff — it helps others find the project.
Line diffs lie about config. Formatting churn, key reorders, and “small” YAML edits hide the changes that actually matter in production.
Flecto turns structured config into semantic events:
- What changed (
pool_size: 5 → 20) - What was added or removed
- What looks risky (secrets, dangerous toggles, pool jumps)
- What to do next (CI gate, webhook, shell command)
See the changelog and migration notes for release history and upcoming v2.1 behavior changes.
Diff tools compare trees. Flecto watches, scores risk, and alerts.
| Without Flecto | With Flecto |
|---|---|
+ 40 lines of YAML noise |
~ database.pool_size: 5 → 20 |
Hope someone notices debug: true |
Policy finding → CI fails |
“Something in .env changed” |
Exact keys + optional secret masking |
npm install -g flectoRequires Node.js 20.19.0 or later.
flecto --version
flecto doctorflecto watch config/prod.yaml
flecto watch .env
flecto watch settings.json
flecto watch pyproject.toml
flecto watch app.iniThat’s it — Flecto prints a clear summary on every meaningful change.
- Semantic diffs for JSON, YAML, TOML, INI, and dotenv (
.env,.env.*,*.env) - Live watch with optional command + webhook delivery
- Policy packs (
default,strict-prod,compose,node-runtime) + custompolicies/*.json+ local ESM plugins - CI mode with JSON / NDJSON / GitHub annotations and fail rules
- Snapshots & diffs for deploy scripts and pre-commit hooks
- Profiles via
--profileorFLECTO_PROFILE - Default array identity (
id/name, or--array-id-key) and secret masking
flecto watch "config/**/*.yaml" ".env"flecto watch config/prod.yaml --mode verboseflecto watch config/prod.yaml --ignore "updated_at,meta.timestamp"| Pattern | What it ignores |
|---|---|
meta.timestamp |
That exact key |
meta |
Everything under meta.* |
servers[*].meta.timestamp |
That key inside any array item |
**.updated_at |
Any key named updated_at, anywhere |
flecto watch .env --command "docker-compose restart app"Changes are passed as JSON via FLECTO_CHANGES (large payloads may use FLECTO_CHANGES_FILE).
flecto watch config/prod.yaml \
--webhook https://hooks.example.com/notify \
--webhook-header "Authorization: Bearer TOKEN"Envelope shape (schema_version: "2.0"):
{
"schema_version": "2.0",
"event_id": "uuid",
"event_type": "changes",
"emitted_at": "2026-04-14T10:42:31.000Z",
"file": "/absolute/path/to/config/prod.yaml",
"changes": [
{ "type": "changed", "path": "database.pool_size", "before": 5, "after": 20 }
],
"policies": [
{
"id": "pool-size-jump",
"severity": "warn",
"path": "database.pool_size",
"message": "Pool size increased from 5 to 20 (>=2x).",
"pack": "default"
}
]
}JSON Schema: schemas/flecto-envelope-2.0.json.
flecto ci config/prod.yaml --profile prod --snapshot-ref HEAD~1{
"defaults": {
"policies": ["default"],
"maskSecrets": false
},
"profiles": {
"prod": {
"policies": ["default", "strict-prod"],
"severityRemap": { "pool-size-jump": "error" },
"maskSecrets": true
}
}
}Profile selection: --profile > FLECTO_PROFILE > defaults.
Custom packs: policies/<id>.json. Plugins: local ESM exporting evaluate(changes, ctx).
Rules combine their top-level predicates with AND. In addition to when, regex
match.path, afterEquals, and numericJump, packs can use:
beforeEquals,afterIn, andbeforeInfor exact values or allowed value lists.beforeTruthy: trueandafterTruthy: trueto require a truthy before/after value.afterMatchesto require a string after value that matches a regular expression.numericDelta: { "min": 10 }to match an absolute numeric change of at least 10.match.pathEqualsandmatch.pathPrefixfor exact or prefix path matching without regex.allOfandanyOfarrays of simple match clauses. EveryallOfclause and at least oneanyOfclause must match. Clauses support the same value, truthiness, numeric, andmatchpredicates, but cannot nest composition.
{
"id": "risky-feature-enable",
"severity": "error",
"allOf": [
{ "match": { "pathPrefix": "features." } },
{ "afterTruthy": true }
],
"anyOf": [
{ "afterEquals": true },
{ "afterIn": ["unsafe", "disabled"] }
]
}Pack loading fails closed for unknown rule or match fields, invalid regexes, and invalid
predicate shapes, so misspelled predicates cannot silently disable a rule.
Use severityRemap in defaults or a profile to change pack rule severities without forking a pack:
{
"profiles": {
"dev": {
"severityRemap": { "pool-size-jump": "off" }
},
"prod": {
"severityRemap": { "pool-size-jump": "error" }
}
}
}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.
Authoring guides: policy packs · plugins · plugin cookbook.
flecto policies list
flecto policies list --jsonThe command lists every bundled and local pack that resolves from the current
working directory, including its source path and rule count. For a given pack
id, Flecto resolves local files before bundled packs in this order:
policies/<id>.json, policies/<id>.yaml, policies/<id>.yml, then the
built-in pack. A local pack with the same id overrides its built-in counterpart.
flecto watch config/services.yamlArrays of objects automatically match by a shared, unique id key, falling back
to name when id is unavailable. This avoids false changes when named items
are reordered. Use a custom identity field when needed:
flecto watch config/services.yaml --array-id-key serviceKeyTo restore index-based diffs for every array, pass --no-array-id:
flecto watch config/services.yaml --no-array-idIn .flectorc, set "arrayId": false in defaults or a profile for the same
escape hatch.
flecto watch .env \
--command "make reload" \
--webhook https://hooks.example.com/notifyflecto watch config/prod.yaml \
--webhook https://hooks.example.com/notify \
--delivery-mode at-least-once \
--on-alert-failure retry| Flag | Options | What it does |
|---|---|---|
--delivery-mode |
best-effort (default), at-least-once |
Persist and retry failed webhook events |
--on-alert-failure |
warn, exit, retry |
Behavior when command/webhook fails |
flecto watch config/prod.yaml --snapshot
flecto watch config/prod.yaml --diff
flecto history config/prod.yaml --limit 10Exit codes: 0 clean · 1 changes detected.
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.
flecto ci "config/**/*.yaml" \
--snapshot-ref HEAD~1 \
--format github-annotations \
--fail-on "changed,policy,error"Formats: json, ndjson, github-annotations
Fail triggers: changed, added, removed, policy, error, warn
Unresolved --snapshot-ref fails closed (no silent empty baseline).
If every target is missing or unsupported, flecto ci and flecto watch --snapshot exit non-zero (pass --allow-empty to opt out).
Use the Flecto CI Action 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.
permissions:
contents: read
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 2
- uses: myselfsiddharth/Flecto/.github/actions/flecto-ci@main
with:
targets: config/**/*.{yaml,yml,json,toml,ini}
snapshot-ref: HEAD~1contents: 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.
| Input | Default | Description |
|---|---|---|
targets |
config/**/*.{yaml,yml,json,toml,ini} |
Whitespace-separated paths or glob patterns to check |
fail-on |
policy,error |
Comma-separated events that fail the job |
policies |
(empty) | Comma-separated policy pack IDs; omit to use .flectorc / Flecto defaults |
profile |
(empty) | Optional .flectorc profile |
format |
github-annotations |
Flecto output format |
snapshot-ref |
HEAD~1 |
Git ref or snapshot file used as the baseline |
node-version |
20 |
Node.js version used to run Flecto |
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.
Built-in pack ids:
default— secrets, dangerous toggles, and pool-size jumps.strict-prod— stricter severities and matching for production use.compose— privileged services, host networking, Docker socket mounts, and sensitive host-directory bind mounts.node-runtime— removed Node.js engine requirements, TLS verification bypasses, and enabled Node debugging or inspector options.
Fail CI with --fail-on policy.
schema_versionis now"2.0"- Type name is
FlectoEnvelope(docs/types) - New
policiesarray on change envelopes - Webhook headers unchanged (
X-Flecto-*)
flecto watch config/prod.yaml --polling --interval 500Polling is off by default (interval default 100ms when enabled).
flecto initLooks for .flectorc, .flectorc.json, .flectorc.yaml, or .flectorc.yml.
{
"defaults": {
"mode": "compact",
"interval": 100,
"ignore": ["**.updated_at"],
"deliveryMode": "best-effort",
"onAlertFailure": "warn",
"policies": ["default"],
"arrayId": true
},
"profiles": {
"dev": { "mode": "verbose" },
"ci": { "failOn": "policy,error" },
"prod": {
"policies": ["default", "strict-prod"],
"severityRemap": { "pool-size-jump": "error" },
"maskSecrets": true
}
},
"files": ["config/**/*.{yaml,yml,json,toml,ini}", ".env", ".env.*", "*.env"],
"exclude": ["**/node_modules/**"]
}flecto watch --profile dev
flecto ci --profile ci
flecto doctorCLI flags override profile/default values.
[HH:MM:SS] <filepath> — N changes
~ path: before → after (yellow — value changed)
+ path: value (green — key added)
- path: value (red — key removed)
[HH:MM:SS] <filepath> — N changes
~ path
before: old_value
after: new_value
| Situation | Behavior |
|---|---|
| File not found | Error + exit 1 |
| Unsupported format | Lists supported extensions + exit 1 |
| Parse error while watching | Warning, last valid state kept |
| Command / webhook fails | Warning (unless --on-alert-failure exit) |
| Ctrl+C | Clean shutdown |
- Parser — format by extension / dotenv naming → structured values
- Watcher — chokidar + debounce
- Differ — semantic tree diff (objects, arrays, ignore rules, automatic array ids)
- Policy engine — packs + plugins → severity findings
- Envelope — versioned automation payload (
2.0) - Alerter — command and/or webhook with retry modes
Flecto is open source. See CONTRIBUTING.md for setup, PR rules, and review expectations.
Please read the Code of Conduct and Security policy.
Roadmap lives in GitHub milestones.
If Flecto helps your team catch a risky config change — star the repo so more people can find it.
MIT — see LICENSE.

