Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,7 @@ name: Docs
- ".github/workflows/docs.yml"
workflow_dispatch:

permissions:
contents: read
pages: write
id-token: write
permissions: {}

concurrency:
group: pages
Expand All @@ -28,6 +25,8 @@ jobs:
name: docs-quality
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
contents: read
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
Expand Down Expand Up @@ -113,6 +112,8 @@ jobs:
needs: docs-quality
runs-on: ubuntu-latest
timeout-minutes: 60
permissions:
contents: read
Comment thread
coderabbitai[bot] marked this conversation as resolved.
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
Expand Down Expand Up @@ -158,6 +159,9 @@ jobs:
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
pages: write
id-token: write
environment:
name: ${{ 'github-pages' }}
url: ${{ steps.deployment.outputs.page_url }}
Expand Down
38 changes: 33 additions & 5 deletions .markdownlint.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,37 @@
{
"default": true,
"MD013": false,
"MD024": false,
"MD029": false,
"MD001": false,
"MD013": {
"code_block_line_length": 500,
"heading_line_length": 200,
"line_length": 500,
"tables": false
},
"MD022": false,
"MD023": false,
"MD024": {
"siblings_only": true
},
"MD025": false,
"MD031": false,
"MD033": {
"allowed_elements": [
"br",
"details",
"summary",
"kbd"
]
},
"MD036": false,
"MD040": false,
"MD051": false
"MD041": false,
"MD042": false,
"MD046": false,
"MD050": {
"style": "consistent"
},
"MD051": false,
"MD057": false,
"MD060": false,
"MD076": false,
Comment thread
marlon-costa-dc marked this conversation as resolved.
"default": true
}
24 changes: 24 additions & 0 deletions .markdownlintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
.omo/**
.beads/**
.tmp/**
.claude/**
.vscode/**
.codegraph/**
.code-review-graph/**
.serena/**
.snapshots/**
.benchmarks/**
.hypothesis/**
.mypy_cache/**
.pytest_cache/**
.ruff_cache/**
.venv/**
__pycache__/**
node_modules/**
.reports/**
dist/**
build/**
htmlcov/**
legado/**
.github/**
site/**
69 changes: 40 additions & 29 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,13 @@ BRANCH ?=
PYTEST_ARGS ?=
PYTEST_DIAG_ARGS ?= -rA --durations=0 --tb=long --showlocals
PYTEST_REPORT_ARGS ?= -ra --durations=25 --durations-min=0.001 --tb=short
PYTEST_PROCESS_TIMEOUT_SECONDS ?= 180
PYTEST_PROCESS_TIMEOUT_SECONDS ?= 360
# mro-99ae: the pytest process inherits a hard wall-clock boundary, mirroring
# MYPY_BOUNDED, so a hung run is terminated even if the typed runner stalls.
PYTEST_BOUNDED = timeout --signal=TERM --kill-after=5s "$(PYTEST_PROCESS_TIMEOUT_SECONDS)s"
PYTEST_REPORTS_DIR ?= .reports/tests
override PYTEST_CASE_TIMEOUT_SECONDS := 10
override PYTEST_RUN_TIMEOUT_SECONDS := 120
override PYTEST_RUN_TIMEOUT_SECONDS := 300
Comment thread
coderabbitai[bot] marked this conversation as resolved.
override PYTEST_TERMINATION_GRACE_SECONDS := 2
override PYTEST_TIMEOUT_EXIT_CODE := 124
override PYTEST_ENFORCEMENT_PLUGIN := flext_tests_enforcement
Expand All @@ -66,6 +66,16 @@ override PYTEST_PARALLEL_DISTRIBUTION := worksteal
override PYTEST_PROFILE_SORT := cumulative
override PYTEST_PROFILE_LIMIT := 50
override PROCESS_TIMEOUT_COMMAND := timeout
# CI ternary wall-clock budget per verb/what/project: CI=Y owns the fast
# gates (60s each); CI=N owns the slow whole-program analyses
# (300s each); an unset token runs unbounded.
ifeq ($(strip $(CI)),Y)
VERB_BOUNDED := timeout --signal=TERM --kill-after=5s 60s
else ifeq ($(strip $(CI)),N)
VERB_BOUNDED := timeout --signal=TERM --kill-after=5s 300s
else
VERB_BOUNDED :=
endif
override export FLEXT_PYTEST_ARGS_RAW := $(value PYTEST_ARGS)
override export FLEXT_PYTEST_FILE_RAW := $(value FILE)
override export FLEXT_PYTEST_FILES_RAW := $(value FILES)
Expand All @@ -91,18 +101,6 @@ MAKEFILE_ROOT := $(patsubst %/,%,$(dir $(SELF_MAKEFILE)))
PROJECT_ROOT := $(MAKEFILE_ROOT)
override export FLEXT_PYTEST_TARGET_RAW := tests
WORKSPACE ?= $(PROJECT_ROOT)
# A workspace lane is always registered at the workspace root. Other verbs may
# select a member through PROJECT, but `make work` keeps WORKSPACE at the root
# so one Git worktree owns the complete project matrix.
ifneq ($(filter work,$(MAKECMDGOALS)),work)
ifeq ($(filter command line override,$(origin WORKSPACE)),)
ifneq ($(strip $(PROJECT)),)
ifneq ($(filter $(PROJECT),$(WORKSPACE_MEMBERS)),)
override WORKSPACE := $(PROJECT_ROOT)/$(PROJECT)
endif
endif
endif
endif
# === SECTION: WORKSPACE_ROOT isolation (managed) ===
# Source: computed (rule: derive from current checkout unless caller overrides)
# Rule: WORKSPACE_ROOT is always derived from the current checkout unless the
Expand All @@ -115,6 +113,18 @@ ifeq ($(filter command line override,$(origin WORKSPACE_ROOT)),)
WORKSPACE_ROOT := $(shell cd "$(MAKEFILE_ROOT)" && root=$$(git rev-parse --show-superproject-working-tree 2>/dev/null); if [ -n "$$root" ]; then printf '%s\n' "$$root"; else git rev-parse --show-toplevel 2>/dev/null || printf '%s\n' "$(MAKEFILE_ROOT)"; fi)
endif
# End SECTION: WORKSPACE_ROOT isolation
# A workspace lane is always registered at the workspace root. Other verbs may
# select a member through PROJECT, but `make work` keeps WORKSPACE at the root
# so one Git worktree owns the complete project matrix.
ifneq ($(filter work,$(MAKECMDGOALS)),work)
ifeq ($(filter command line override,$(origin WORKSPACE)),)
ifneq ($(strip $(PROJECT)),)
ifneq ($(filter $(PROJECT),$(WORKSPACE_MEMBERS)),)
override WORKSPACE := $(WORKSPACE_ROOT)/$(PROJECT)
endif
endif
endif
endif

# === SECTION: verb dispatch (managed) ===
# Source: config:make.verbs[*].whats, config:make.check_gates_allowed,
Expand All @@ -139,7 +149,7 @@ _ALLOWED_WHATS_gen := check all apply $(shell sed -n 's/^_custom_gen_\([a-z0-9_-
_ALLOWED_WHATS_work := start status land finish $(shell sed -n 's/^_custom_work_\([a-z0-9_-]*\):.*/\1/p' "$(MAKEFILE_ROOT)/custom.mk" 2>/dev/null | sort -u | tr '\n' ' ')
_ALLOWED_WHATS_mod := check all apply $(shell sed -n 's/^_custom_mod_\([a-z0-9_-]*\):.*/\1/p' "$(MAKEFILE_ROOT)/custom.mk" 2>/dev/null | sort -u | tr '\n' ' ')

CHECK_GATES_ALLOWED := lint format pyrefly mypy pyright security markdown smells
CHECK_GATES_ALLOWED := lint pyrefly mypy pyright security markdown smells
CHECK_GATES_DEFAULT := lint pyrefly mypy pyright security markdown smells
# End SECTION: verb dispatch

Expand Down Expand Up @@ -400,9 +410,9 @@ define _dispatch
if [ "$$rc" -ne 2 ]; then $(SELF_MAKE) "$$hook" || exit $$?; fi; \
done; \
if [ "$$custom_rc" -ne 2 ]; then \
$(SELF_MAKE) "$$custom" || exit $$?; \
$(VERB_BOUNDED) $(SELF_MAKE) "$$custom" || exit $$?; \
else \
$(SELF_MAKE) "$$builtin" || exit $$?; \
$(VERB_BOUNDED) $(SELF_MAKE) "$$builtin" || exit $$?; \
fi; \
for hook in "post-$(1)-$$what" "post-$(1)"; do \
$(SELF_MAKE) -q "$$hook" >/dev/null 2>&1; rc=$$?; \
Expand Down Expand Up @@ -846,7 +856,7 @@ _builtin_deps_upgrade: _builtin_require_environment
set --; \
for project in $$selected; do set -- "$$@" --projects "$$project"; done; \
$(PROJECT_FLEXT_INFRA) deps modernize --workspace "$(PROJECT_ROOT)" \
--apply --rewrite-constraints --skip-check "$$@"
--apply $(if $(strip $(DEPENDENCY)),,--rewrite-constraints) --skip-check "$$@"
$(call _run_for_selected_projects,)


Expand All @@ -857,15 +867,19 @@ _builtin_build_artifacts:
# by `make fix APPLY=Y` and formatting by `make fmt APPLY=Y`, both run BEFORE
# check. APPLY here made the same tools run twice with conflicting intents,
# so it is rejected instead of silently honoured; FIX=1 became the `fix` verb.
# CI=Y runs make.ci.check_gates (RULING 2: rules not skip-list).
# CI=Y runs make.ci.check_gates and CI=N runs its strict
# complement, make.ci.local_check_gates (RULING 2: rules not skip-list).
_builtin_check_all: _builtin_require_environment
@set -eu; \
gates="$(strip $(CHECK_GATES))"; \
if [ -z "$$gates" ]; then gates="$$(printf '%s' '$(CHECK_GATES_DEFAULT)' | tr ' ' ',')"; fi; \
gates="$$(printf '%s' "$$gates" | tr -d '[:space:]')"; \
if [ "$(strip $(CI))" = "Y" ]; then \
gates="mypy,pyright,security,markdown,smells"; \
printf 'INFO: CI=Y runs check gates: mypy pyright security markdown smells\n'; \
gates="lint,pyright,security,markdown,smells"; \
printf 'INFO: CI=Y runs check gates: lint pyright security markdown smells\n'; \
elif [ "$(strip $(CI))" = "N" ]; then \
gates="pyrefly,mypy"; \
printf 'INFO: CI=N runs check gates: pyrefly mypy\n'; \
Comment thread
coderabbitai[bot] marked this conversation as resolved.
fi; \
for gate in $$(printf '%s' "$$gates" | tr ',' ' '); do \
case " $(CHECK_GATES_ALLOWED) " in *" $$gate "*) ;; \
Expand Down Expand Up @@ -931,7 +945,7 @@ _builtin_fix_check: _builtin_require_environment
_builtin_fix_all: _builtin_require_environment
$(call _require_apply)
@$(PROJECT_FLEXT_INFRA) check run --workspace "$(PROJECT_ROOT)" --projects . --fix \
--gates format,markdown,smells
--gates markdown,smells

_builtin_fix_apply: _builtin_fix_all

Expand Down Expand Up @@ -991,19 +1005,16 @@ _builtin_release_rel: _builtin_require_environment
$$push_flag \
$$projects_args

# Generation preserves the caller's scope. Conform owns analyzer roots in the
# rendered tooling context; dependency modernization owns only dependency
# settings that conform does not render.
# Generation has one owner. Conform preserves the caller's scope and applies
# the complete dependency/tooling projection before it verifies its fixed point.
# Dependency upgrades remain a separate explicit verb because they rewrite lock
# floors; gen must never run a second pyproject writer over conform's result.
_builtin_gen_check: _builtin_require_environment
@$(PROJECT_FLEXT_INFRA) codegen conform --root "$(PROJECT_ROOT)" --scope "$(CODEGEN_SCOPE)" --mode check
@$(PROJECT_FLEXT_INFRA) codegen init --workspace "$(PROJECT_ROOT)" --check
@$(PROJECT_FLEXT_INFRA) deps modernize --workspace "$(PROJECT_ROOT)" --check

_builtin_gen_all: _builtin_require_environment
$(call _require_apply)
@$(PROJECT_FLEXT_INFRA) codegen conform --root "$(PROJECT_ROOT)" --scope "$(CODEGEN_SCOPE)" --mode apply
@$(PROJECT_FLEXT_INFRA) codegen init --workspace "$(PROJECT_ROOT)" --apply
@$(PROJECT_FLEXT_INFRA) deps modernize --workspace "$(PROJECT_ROOT)" --apply

_builtin_gen_apply: _builtin_gen_all

Expand Down
4 changes: 2 additions & 2 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ src/flext_cli/
├── cli.py # Única fronteira com Typer/Click
├── cli_params.py # Parâmetros reutilizáveis para comandos Typer/Click
├── commands.py # Registro e resolução de comandos estruturais
├── settings.py # Singleton de configuração validada
├── _settings.py # Singleton de configuração validada
├── constants.py # Constantes e mensagens compartilhadas
├── debug.py # Utilidades de depuração
├── file_tools.py # I/O de arquivos (texto, JSON, YAML, CSV, zip)
Expand All @@ -52,7 +52,7 @@ src/flext_cli/
1. **Registro de comandos**: modelos em `commands.py` são validados em `FlextCliCore.register_command` antes de serem armazenados.
1. **Execução**: `FlextCliCore.execute_command` resolve o comando registrado; `FlextCliCmd` fornece operações utilitárias ligadas à configuração persistida.
1. **Entrada/Saída**: `prompts.py` coleta entrada; `output.py`, `formatters.py` e `tables.py` geram saídas em Rich/ASCII/JSON/YAML/CSV sem expor o Rich diretamente.
1. **Configuração**: `settings.py` gerencia configuração imutável; sessões são armazenadas em `core`.
1. **Configuração**: `_settings.py` gerencia configuração imutável; sessões são armazenadas em `core`.

## Integração com flext-core

Expand Down
2 changes: 1 addition & 1 deletion docs/development.md
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ src/flext_cli/
│
└── Data Models (value objects)
├── models.py # All Pydantic models
└── settings.py # FlextCliSettings
└── _settings.py # FlextCliSettings
```

### Direct Access Pattern
Expand Down
24 changes: 20 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ namespace_packages = true
no_implicit_reexport = true
plugins = ["pydantic.mypy"]
python_version = "3.13"
show_traceback = true
strict_equality = true
warn_redundant_casts = true
warn_return_any = true
Expand Down Expand Up @@ -399,6 +400,7 @@ filterwarnings = [
"error",
"module::flext_core._constants.enforcement.FlextMroViolation",
]
flext_slow_timeout_seconds = "60"
markers = [
"docker: tests requiring Docker",
"e2e: end-to-end integration tests",
Expand Down Expand Up @@ -513,9 +515,20 @@ known-first-party = ["flext_cli", "flext_core", "flext_infra", "flext_tests"]
split-on-trailing-comma = false

[tool.ruff.lint.per-file-ignores]
"**/.vulture_whitelist.py" = ["ALL"]
"**/*_pb2*.py" = ["ALL"]
"**/__init__.py" = ["ALL"]
"**/__init__.py" = [
"any-type",
"builtin-import-shadowing",
"implicit-namespace-package",
"import-private-name",
"invalid-module-name",
"non-empty-init-module",
"runtime-import-in-type-checking-block",
"undefined-export",
"undefined-local-with-import-star",
"undefined-local-with-import-star-usage",
"undocumented-public-init",
"unused-import",
]
"**/tests/**" = [
"assert",
"compare-to-empty-string",
Expand Down Expand Up @@ -569,6 +582,9 @@ dependencies = ["flext-cli"]
[tool.uv.exclude-dependencies.package]
name = "flext-tests"

[tool.uv.exclude-newer-package]
mypy = "2026-08-15T03:04:00Z"

[tool.vulture]
exclude = ["*/_protocols/*"]
min_confidence = 100
Expand Down Expand Up @@ -620,7 +636,7 @@ dev = [
"mkdocs-versioning>=0.2",
"mkdocstrings>=0.24",
"mkdocstrings-python>=1.7",
"mypy>=2.3.0",
"mypy>=2.3.1",
"pip-audit>=2.7.3",
"pre-commit>=4.6.1",
"psutil>=7.2.2",
Expand Down
Loading