Complete flag reference for every Flecto command. For task-oriented guides, start with the README.
Global:
flecto --version
flecto --help
flecto help <command>Watch config files or globs and print semantic changes as they happen. Also hosts the snapshot and diff modes.
flecto watch config/prod.yaml
flecto watch "config/**/*.yaml" ".env"If you omit files, Flecto uses the files / include patterns from .flectorc.
| Flag | Default | Description |
|---|---|---|
-p, --profile <name> |
— | Use a profile from .flectorc (else FLECTO_PROFILE) |
-m, --mode <mode> |
compact |
Output mode: compact or verbose |
-i, --interval <ms> |
100 |
Polling interval, used when --polling is on |
--polling |
off | Force polling instead of native filesystem events |
-c, --command <cmd> |
— | Shell command to run on every change |
-w, --webhook <url> |
— | POST the change envelope to this URL |
--webhook-header <header> |
— | Extra webhook header, repeatable |
--webhook-timeout <ms> |
5000 |
Webhook request timeout |
--webhook-retries <n> |
2 |
Webhook retry attempts |
--webhook-format <service> |
flecto |
Payload shape: flecto, slack, discord, teams, or auto (detect from the URL) |
--delivery-mode <mode> |
best-effort |
best-effort or at-least-once |
--on-alert-failure <mode> |
warn |
warn, exit, or retry |
--ignore <keys> |
— | Comma-separated key paths to ignore |
--policies <ids> |
default |
Comma-separated policy pack ids |
--plugins <paths> |
— | Comma-separated local ESM plugin paths |
--array-id-key <key> |
auto | Diff arrays by this identity key |
--no-array-id |
— | Diff arrays by index instead of identity |
--array-ignore-order |
off | Treat array order as insignificant |
--mask-secrets |
off | Mask secret-like values in terminal output |
--mask-secrets-webhooks |
off | Also mask secrets in webhook payloads |
--snapshot |
— | Save current state as a baseline instead of watching |
--diff |
— | Diff against the saved baseline and exit |
--allow-empty |
off | Let --snapshot succeed when nothing was written |
--snapshot-store <id> |
local |
Where snapshots live: local or shared |
--snapshot-dir <path> |
per store | Override the store directory |
--snapshot-mask <mode> |
per store | How the store records secret-like values: hash or none |
--snapshot-retention <n> |
per store | Snapshots kept per file; 0 keeps every one |
See Snapshot stores for what the two stores are for.
Exit codes: watch mode runs until interrupted (0 on clean shutdown).
With --diff: 0 when the file matches the baseline, 1 when it differs, and
1 with an error when no target had a saved snapshot — exiting 0 there
would report "no drift" from a comparison that never happened. Targets that
individually lack a snapshot are warned about and counted, and the rest still
diff.
See configuration for --ignore pattern syntax and array
identity behavior, and webhooks for delivery details.
Run a one-shot semantic diff against a baseline and exit with a status code CI can gate on.
flecto ci "config/**/*.yaml" --snapshot-ref HEAD~1 --fail-on "policy,error"| Flag | Default | Description |
|---|---|---|
-p, --profile <name> |
— | Use a profile from .flectorc (else FLECTO_PROFILE) |
--snapshot-ref <ref> |
local snapshot | Baseline git revision. A value that is unambiguously a path (absolute, ./, ../) is read as a snapshot file instead |
--snapshot-file <path> |
— | Baseline snapshot file, never consulted as a git revision |
--format <type> |
json |
json, ndjson, sarif, github-annotations, or pr-comment |
--pr-comment-post |
off | With --format pr-comment, upsert the sticky comment on the PR |
--pr-provider <name> |
detect | Force the delivery target: github, gitlab, or bitbucket |
--fail-on <rules> |
changed,policy,error |
Comma-separated fail triggers |
--baseline <file> |
— | Gate only on findings not already recorded in this file |
--update-baseline |
off | Rewrite the --baseline file from the current findings |
--ignore <keys> |
— | Comma-separated key paths to ignore |
--policies <ids> |
default |
Comma-separated policy pack ids |
--plugins <paths> |
— | Comma-separated local ESM plugin paths |
--array-id-key <key> |
auto | Diff arrays by this identity key |
--no-array-id |
— | Diff arrays by index instead of identity |
--array-ignore-order |
off | Treat array order as insignificant |
--mask-secrets |
off | Mask secret-like values in CI output |
--show-suppressed |
off | List inline-suppressed findings instead of only counting them |
--allow-empty |
off | Succeed when no files were diffed |
--explain |
off | Add model-generated narration of the masked diff; advisory, never affects the exit code. See explain.md |
--explain-dry-run |
off | Print the narration request --explain would send (to stderr), and send nothing |
Inline suppressions (# flecto-ignore-next-line <rule> — <reason>) accept a
single deliberate finding in place; a reason is mandatory. See
ci.md.
Fail triggers: changed, added, removed, policy, error, warn.
Exit codes: 0 when nothing matched a fail trigger, 1 when something did
or when the run errored.
Two things fail closed by design: an unresolved --snapshot-ref is an error
rather than a silently empty baseline, and a run where every target is missing or
unsupported exits non-zero unless you pass --allow-empty.
With no --snapshot-ref, the baseline comes from the snapshot store — by default
the local one in .flecto-snapshots/, which is not committed and does not survive
an ephemeral runner. A file with no saved snapshot is an error naming the store it
looked in and the ways out — save one with flecto watch <file> --snapshot, commit
a --snapshot-store shared store, or diff against a committed revision with
--snapshot-ref <git-ref> — rather than a diff against nothing.
--snapshot-store <id> and --snapshot-dir <path> select which store ci reads.
See Snapshot stores.
--format pr-comment prints a markdown risk summary to stdout. It posts that
summary as a single sticky pull request comment only when --pr-comment-post is
passed and GITHUB_TOKEN, GITHUB_REPOSITORY, and a PR number (from
GITHUB_REF or GITHUB_EVENT_PATH) are all present — otherwise it warns and
prints. A delivery failure never changes the exit code.
Full CI guide, including the GitHub Action: ci.md.
Ask a model you configure to narrate the likely blast radius of the semantic diff. Opt-in, bring your own key, and advisory only.
FLECTO_EXPLAIN_PROVIDER=anthropic FLECTO_EXPLAIN_API_KEY=sk-ant-... \
flecto explain config/prod.yaml --snapshot-ref origin/main| Flag | Default | Description |
|---|---|---|
-p, --profile <name> |
— | Use a profile from .flectorc for diff and policy options |
--snapshot-ref <ref> |
snapshot store | Baseline git revision (a path-shaped value is read as a snapshot file) |
--snapshot-file <path> |
— | Baseline snapshot file, never consulted as a git revision |
--snapshot-store <id> / --snapshot-dir <path> |
local |
Which snapshot store to read when there is no --snapshot-ref |
--provider <id> |
FLECTO_EXPLAIN_PROVIDER |
anthropic or openai (any OpenAI-compatible server) |
--model <name> |
FLECTO_EXPLAIN_MODEL |
Model id; anthropic defaults to claude-opus-5 |
--max-tokens <n> |
16000 |
Output token cap |
--format <type> |
human |
human or json |
--dry-run |
off | Print the exact request (key replaced by its source) and send nothing |
--no-cache |
— | Neither read nor write the narration cache |
--ignore, --policies, --plugins, --array-id-key, --no-array-id, --array-ignore-order |
— | As for ci |
Only the masked semantic diff is sent (changed paths, before and after values,
and findings), masked whether or not --mask-secrets is passed. The output is
labeled as model-generated. explain* options declared in .flectorc are
refused, and the provider, endpoint, and key come only from the command line and
the environment.
Exit codes: 0 whether or not narration succeeded. A provider failure is a
warning, never a failed run. 1 for an invalid invocation: no provider
configured, an unknown provider, or an unresolvable baseline.
Full guide, including what is sent, caching, and cost: explain.md.
Diff two different files against each other — the same differ and policy engine
watch and ci use, pointed at environment skew instead of drift over time.
flecto compare config/prod.yaml config/staging.yaml
flecto compare config/prod.yaml config/prod.json --format jsonfileA is the baseline. Changes are reported as what fileB does to it:
+ exists only in fileB, - exists only in fileA, ~ is a value that
differs. Swap the arguments to flip the direction.
Both arguments are single file paths, not globs, and both are required — a missing one is an error rather than a skipped target. The two files do not have to share a format, since every supported format parses to a plain tree.
| Flag | Default | Description |
|---|---|---|
-p, --profile <name> |
— | Use a profile from .flectorc (else FLECTO_PROFILE) |
--format <type> |
human |
human, json, ndjson, or github-annotations |
--fail-on <rules> |
changed,added,removed,policy,error |
Comma-separated fail triggers |
--ignore <keys> |
— | Comma-separated key paths to ignore |
--policies <ids> |
default |
Comma-separated policy pack ids |
--plugins <paths> |
— | Comma-separated local ESM plugin paths |
--array-id-key <key> |
auto | Diff arrays by this identity key |
--no-array-id |
— | Diff arrays by index instead of identity |
--array-ignore-order |
off | Treat array order as insignificant |
--mask-secrets |
off | Mask secret-like values in output |
Fail triggers: the same set ci uses — changed, added, removed,
policy, error, warn. The default differs from ci: two environments that
are meant to match should also match on added and removed keys, so all three
change kinds gate by default.
Exit codes: 0 when the two files match under the active fail triggers, 1
when they differ, a policy trigger fires, or the run errored.
The non-human formats emit exactly what ci emits — an array of per-file
results, each with a 2.0 envelope — plus a baseline field naming fileA.
Envelopes carry source: "diff".
Diff Terraform plan JSON (the output of terraform show -json) and run
policies on it — the same differ, envelope, and exit-code model every other
Flecto command uses, pointed at a plan instead of a config file.
terraform plan -out plan.tfplan
terraform show -json plan.tfplan > plan.json
flecto plan plan.jsonFlecto never runs the terraform binary; it only reads the JSON you hand it.
| Flag | Default | Description |
|---|---|---|
-p, --profile <name> |
— | Use a profile from .flectorc (else FLECTO_PROFILE) |
--format <type> |
human |
human, json, ndjson, github-annotations, or pr-comment |
--pr-comment-post |
off | With --format pr-comment, upsert the comment on the PR (needs a token + merge request context) |
--pr-provider <name> |
detect | Force the delivery target: github, gitlab, or bitbucket |
--fail-on <rules> |
error |
Comma-separated fail rules: changed, added, removed, policy, error, warn |
--ignore <keys> |
— | Comma-separated key paths to ignore, e.g. **.tags_all,**.#action |
--policies <ids> |
terraform |
Comma-separated policy pack ids |
--plugins <paths> |
— | Comma-separated local ESM plugin paths |
--mask-secrets |
off | Also mask Flecto-detected secret-like values (Terraform-sensitive values are always redacted, regardless of this flag) |
Fail triggers: the same set ci uses — changed, added, removed,
policy, error, warn. The default is error alone, not changed: a plan
is supposed to contain changes, so gating on their existence would fail every
non-empty plan.
Exit codes: 0 when nothing matched a fail trigger, 1 when something did
or the run errored.
A replace (destroy-and-recreate) is reported as a removed event, so
--fail-on removed catches every replace with no policy pack loaded at all.
Values Terraform marks sensitive are redacted to (sensitive value)
unconditionally, before any formatter sees them.
Several plan files in one run each produce their own result entry, exactly
like ci with multiple files.
Full guide, including the terraform policy pack and CI wiring:
terraform.md.
Summarize drift across saved snapshots. Omit files to show all saved history.
Reads whichever snapshot store is selected — the local one in
.flecto-snapshots/ by default.
flecto history config/prod.yaml --limit 10| Flag | Default | Description |
|---|---|---|
-l, --limit <n> |
10 |
Number of recent snapshots to show |
-p, --profile <name> |
— | Use a profile from .flectorc (else FLECTO_PROFILE) |
--snapshot-store <id> |
local |
Store to read: local or shared |
--snapshot-dir <path> |
per store | Override the store directory |
--ignore <keys> |
— | Comma-separated key paths to ignore |
--array-id-key <key> |
auto | Diff arrays by this identity key |
--array-ignore-order |
off | Treat array order as insignificant |
Change counts use the same ignore paths, array identity, and order settings as
flecto watch --diff. This command is entirely local — it reads snapshot files
and sends nothing anywhere. The header names the store it read and how many
snapshots it found, so an empty answer is never mistaken for a clean one.
A snapshot with nothing before it reads as baseline, not 0 changes. The
first snapshot of a file was never compared against anything, so a change count
for it would be a result nobody computed. When every snapshot shown is a
baseline, the command says so on stderr: no history is not no drift.
Render the same snapshot history flecto history summarizes as a single
self-contained HTML file you can share. Omit files to report on all saved
history.
flecto report
flecto report config/prod.yaml --limit 20 --output drift.html
flecto report --profile prod --mask-secrets| Flag | Default | Description |
|---|---|---|
-o, --output <path> |
flecto-report.html |
Where to write the report |
-l, --limit <n> |
10 |
Number of recent snapshots to include |
-p, --profile <name> |
— | Use a profile from .flectorc (else FLECTO_PROFILE) |
--snapshot-store <id> |
local |
Store to read: local or shared |
--snapshot-dir <path> |
per store | Override the store directory |
--ignore <keys> |
— | Comma-separated key paths to ignore |
--policies <ids> |
default |
Comma-separated policy pack ids |
--plugins <paths> |
— | Comma-separated local ESM plugin paths |
--array-id-key <key> |
auto | Diff arrays by this identity key |
--no-array-id |
— | Diff arrays by index instead of identity |
--array-ignore-order |
off | Treat array order as insignificant |
--mask-secrets |
off | Mask secret-like values in the report |
The page holds a per-file timeline of snapshots — each with its UTC timestamp,
the snapshot it is measured against, every semantic change, and the policy
findings that change produced — plus a summary and all findings grouped by
severity. Missing directories in --output are created.
The file is self-contained. Inline CSS, one small inline script for
filtering and collapsing, and nothing else: no fonts, no images, no CDN scripts,
no analytics, no network access when it is opened. It reads the same snapshot
store flecto history reads, so nothing new is collected and nothing leaves your
machine. It follows the viewer's light or dark
theme and prints reasonably.
Every config value, path, and message is HTML-escaped, so a value containing
markup renders as text. Values that reach the page are the only place a secret
could leak from a report, so pass --mask-secrets (or set maskSecrets in a
profile) whenever you plan to share one: masking uses the same key-name and
value-pattern detection as everywhere else, and it also redacts policy messages
that interpolate values.
The default store is local to the working directory. .flecto-snapshots/ is
not committed and does not survive an ephemeral CI runner, so a pipeline that runs
flecto report without first saving snapshots has nothing to report on — use
--snapshot-store shared and commit it to report on a runner. A
report whose snapshots are all first-of-their-file says so in a banner and in a
Comparisons tile reading 0 — an empty report is no history, never an
all-clear. Individual first snapshots are labelled baseline rather than
0 changes, for the same reason.
Exit codes: 0 when a report was written, 1 when no snapshots matched —
the same message flecto history gives, and no file is written.
Install a policy pack from an npm package that follows the flecto-pack-*
convention. The package must already be installed; this command does not reach
the network.
npm install --save-dev flecto-pack-deployment-safety
flecto policies add deployment-safety
flecto policies add flecto-pack-deployment-safety # same thing
flecto policies add deployment-safety --force # after npm update| Flag | Default | Description |
|---|---|---|
--force |
off | Overwrite an existing local pack with the same id |
<name> is either the pack id or the full package name; the two are normalized
onto each other, and @scope/flecto-pack-<id> is supported. The pack is
validated with the same validator used at evaluation time, then written to
policies/<id>.json, where the normal resolution order finds it. Provenance is
recorded in policies/.flecto-packs.json and shown by flecto policies list;
resolution itself never consults that file.
Only the package's declarative pack file is read — no JavaScript from the package is imported or run, and JS in a pack package is ignored. Plugins, which do execute code, are never installed by this command.
Exit codes: 0 when the pack is written, 1 when the package is not
installed, carries no valid pack, or would overwrite a local pack without
--force.
See installing community packs and distributing a pack.
List every bundled and local policy pack that resolves from the current directory, with its source path and rule count.
flecto policies list
flecto policies list --json| Flag | Default | Description |
|---|---|---|
--json |
off | Machine-readable output |
Resolution order for a given pack id: policies/<id>.json, then
policies/<id>.yaml, then policies/<id>.yml, then the built-in pack. A local
pack with the same id overrides its built-in counterpart.
Packs installed with flecto policies add carry a package field naming the
npm package they came from (- in the text output when a pack was written by
hand).
Assert that a policy pack or plugin produces the findings you expect, from a fixture directory.
flecto policies test examples/fixtures/policies| Flag | Default | Description |
|---|---|---|
--config <name> |
flecto-policy-test.json |
Fixture config file name |
Exit codes: 0 when the fixture's expectations hold, 1 otherwise.
A worked example lives in
examples/fixtures/policies. See the
policy pack guide for authoring.
Write a starter .flectorc.json in the current directory, pre-selecting policy
packs and file patterns from the stack signals it finds there.
flecto init| Detected | Effect |
|---|---|
docker-compose.yml / .yaml, compose.yml / .yaml |
compose pack + the compose file in files |
package.json |
node-runtime pack + package.json in files |
config/ |
config/**/*.{yaml,yml,json,toml,ini} in files |
.env, .env.*, *.env |
.env, .env.*, *.env in files |
*.tf |
Reported only — no terraform pack exists yet, and .tf is not parseable |
The default pack is always enabled, and detected packs are added to the prod
profile alongside strict-prod. With no signals, the generic starter config is
written instead.
An existing .flectorc, .flectorc.json, .flectorc.yaml, or .flectorc.yml
is never overwritten: init reports the file it found and leaves it alone.
Exit codes: 0 whether a config was written or an existing one was kept.
See configuration for the generated keys.
Run Flecto as a read-only Model Context Protocol server over stdio, so an agent gets a small structured diff instead of reading a whole config file into its context.
flecto mcpIt exposes three read-only tools — flecto_diff, flecto_check, and
flecto_explain — each running the same ci path a pull request triggers and
returning the JSON envelope. Secrets are masked by default here (inverted
from the CLI, because the consumer is a model context), plugins stay off, and
tool arguments are path-contained. stdout carries JSON-RPC only; diagnostics go
to stderr.
Exit codes: runs until stdin closes; setup or fatal errors exit 1.
See Flecto as an MCP server for client setup (Claude Code, Cursor) and the full security posture.
Run a Language Server Protocol server over stdio. Open config files get Flecto's policy findings and semantic changes as diagnostics, anchored on the lines they're about.
flecto lsp --stdio| Flag | Default | Description |
|---|---|---|
--stdio |
— | Accepted and ignored; stdio is the only transport |
-p, --profile <name> |
— | Use a profile from .flectorc (else FLECTO_PROFILE) |
--snapshot-ref <ref> |
HEAD |
Git ref open files are compared with |
--snapshot-store <id> |
— | Compare with a snapshot store (local/shared) instead of git |
--snapshot-dir <path> |
store default | Directory holding the snapshot store |
--plugins <paths> |
— | Comma-separated absolute plugin paths; plugins in .flectorc are never loaded |
--changes <level> |
hint |
How semantic changes appear: hint, info, or none |
--debounce <ms> |
250 |
Quiet period after an edit before analyzing |
--timeout <ms> |
10000 |
Stop an analysis that runs longer than this |
Findings use the same .flectorc, packs, inline suppressions, and --baseline
file as ci, so the editor agrees with the gate. Analyses run in a worker
thread: a newer edit cancels a running one, and one past --timeout becomes a
single warning. stdout carries JSON-RPC only; diagnostics go to stderr.
Exit codes: 0 after an LSP shutdown then exit, 1 for an exit
without shutdown or an invalid flag.
Editor setup and how diagnostics are positioned: editor.md.
Check the local setup: which config file resolved, how many files its patterns match, and whether the Node.js runtime is supported.
flecto doctorExit codes: 0 when the environment is usable, 1 on an unsupported Node.js
version or a broken config.
Snapshots are the baseline every drift feature measures against: watch --diff,
ci without --snapshot-ref, history, and report all read one. Where they
live is selectable (#141), and the two stores answer different questions.
local (default) |
shared |
|
|---|---|---|
| Directory | .flecto-snapshots/ |
.flecto/snapshots/ |
| Keyed by | absolute path hash | repo-relative path |
| Layout | one file per snapshot | one file per config file, history inside |
| Masking | off | hash — secret-like values stored as digests |
| Retention | unlimited | 20 snapshots per file |
| Committed | no (gitignored) | yes, that is the point |
local is a scratch directory on your machine. It is keyed by the file's
absolute path, so it means nothing on another checkout, and nothing about it
changed in this release.
shared is meant to be committed. Because it is keyed by repo-relative path
and serialized deterministically, the snapshot you save on a laptop is the
baseline a CI runner reads on a checkout it has never seen before:
flecto watch "config/**/*.yaml" --snapshot --snapshot-store shared
git add .flecto/snapshots && git commit -m "chore: update config baseline"# on the runner — no --snapshot-ref, no cache, no setup step
flecto ci "config/**/*.yaml" --snapshot-store sharedSet it once in .flectorc ("defaults": { "snapshotStore": "shared" }) so the
editor of a config and the gate that blocks their pull request read the same
history.
Snapshots carry config values, and git history is permanent. That is why the
shared store masks by default: every value that trips Flecto's secret detection
is stored as flecto:sha256:<digest> rather than in the clear.
Detection is the same on both counts --mask-secrets uses — the shape of a
value (an opaque high-entropy string, a private key block, a URL with credentials
in it) and the name of its key (password, token, secret, api_key,
private_key, credential). The key-name half is the one that catches
password: hunter2, which looks like nothing at all, and it matters more here
than in the terminal: a log scrolls away, a commit does not. Keys are matched on
the configuration path, so a Kubernetes resource named token-service does not
turn every value inside it into a digest.
The digest is a change detector, not a vault. Rotating a credential changes the
digest, so drift is still reported — the point of a store that silently missed a
rotated key would be hard to defend — but a digest of a low-entropy value can be
brute-forced, so a masked snapshot is not a safe place to put something that was
never meant to be committed. --snapshot-mask none turns masking off and writes
values verbatim; the CLI says so when it does.
Masking is recorded in each store file. Switching modes mid-history warns, because entries written in the other form will read as changed on the next diff.
When the baseline is masked, the live file is masked the same way before the two
are compared, so an untouched secret produces no change and a rotated one shows
as flecto:sha256:aaa… → flecto:sha256:bbb…. Neither value appears anywhere.
--snapshot-retention <n> caps how many snapshots a file keeps, oldest pruned
first; 0 keeps every one. The shared store defaults to 20 because an
append-forever directory inside a repository becomes its own problem. The local
store defaults to unlimited, as it always has.
Shared-store files sort object keys at every level and end with a newline, so reordering keys in a config produces no diff at all and a real change produces exactly the lines that changed. A snapshot commit is meant to be reviewable.
| Variable | Read by | Description |
|---|---|---|
FLECTO_PROFILE |
all commands | Profile name, when --profile is not passed |
FLECTO_ALLOW_SYMLINK_TARGETS |
all commands | 1 allows a target — or a write destination — that leaves the project through a symlink |
FLECTO_ALLOW_RC_WRITES |
ci, report |
1 allows .flectorc to point --output / --baseline outside the project |
FLECTO_ALLOW_RC_PLUGINS |
all commands | 1 allows plugins declared in .flectorc |
FLECTO_ALLOW_RC_ALERTS |
watch |
1 allows .flectorc to declare command / webhook / webhookHeader |
FLECTO_ALLOW_RC_BASELINE |
ci |
1 allows .flectorc to declare snapshotRef, which chooses what every change is measured against |
FLECTO_EXPLAIN_PROVIDER, _MODEL, _API_KEY, _API_URL |
explain, ci --explain |
Narration provider settings; see explain.md |
FLECTO_EXPLAIN_MAX_TOKENS, _MAX_INPUT_TOKENS, _TIMEOUT_MS, _CACHE_DIR |
explain, ci --explain |
Narration cost, time, and cache bounds |
FLECTO_EXPLAIN |
explain, ci --explain |
0 disables narration on this runner |
Flecto also sets variables for --command subprocesses — see
webhooks and commands.