diff --git a/.github/workflows/ci-matrix.yml b/.github/workflows/ci-matrix.yml index 8e975ba13..ec91f53f9 100644 --- a/.github/workflows/ci-matrix.yml +++ b/.github/workflows/ci-matrix.yml @@ -77,7 +77,7 @@ jobs: with: python-version-file: .python-version - name: Install mise toolchain - uses: jdx/mise-action@dba19683ed58901619b14f395a24841710cb4925 # v4.1.0 + uses: jdx/mise-action@9e7f7633ff6f6d6048a9418a68d48f288f50eb14 # v4.2.3 - name: Install uv uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0 with: @@ -102,7 +102,7 @@ jobs: with: python-version-file: .python-version - name: Install mise toolchain - uses: jdx/mise-action@dba19683ed58901619b14f395a24841710cb4925 # v4.1.0 + uses: jdx/mise-action@9e7f7633ff6f6d6048a9418a68d48f288f50eb14 # v4.2.3 - name: Install uv uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0 with: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 856de137d..0e32ef65f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -50,8 +50,6 @@ jobs: submodules: false fetch-depth: 0 - - - name: Install mise toolchain uses: jdx/mise-action@9e7f7633ff6f6d6048a9418a68d48f288f50eb14 # v4.2.3 diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 000000000..383975763 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,148 @@ +# Generated by `flext-infra codegen conform` for flext-cli — DO NOT EDIT. +name: Docs + +"on": + push: + branches: + - main + - 0.12.0-dev + paths: + - "docs/**" + - "mkdocs.yml" + - "pyproject.toml" + + - ".github/workflows/docs.yml" + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: pages + cancel-in-progress: false + +jobs: + docs-quality: + name: docs-quality + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - name: Checkout + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + submodules: false + fetch-depth: 0 + + - name: Setup Python + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + with: + python-version: "3.13" + + - name: Install uv + uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0 + with: + enable-cache: true + + - name: Sync workspace toolchain + run: make setup + + # mro-o6h5 (agent: kimi) — failure diagnostics: per-project logs live under + # .reports/workspace/docs/*.log and were invisible in CI ("0 errors" FAILs); + # dump them inline + per-project audit-report.md for non-zero issue counts + + # upload as artifact so the real error is always reachable. + - name: Docs audit (blocking) + run: | + make docs DOCS_PHASE=audit || { + for f in .reports/workspace/docs/*.log; do + echo "=== $f ===" + tail -60 "$f" + done + for r in */.reports/docs/audit-summary.json; do + [ -f "$r" ] || continue + n=$(grep -o '"issues": [0-9]*' "$r" | head -1 | grep -o '[0-9]*') + if [ "${n:-0}" -gt 0 ]; then + echo "=== $(dirname "$r")/audit-report.md ===" + cat "$(dirname "$r")/audit-report.md" + fi + done + exit 1 + } + + - name: Docs generate (materialize contract artifacts) + run: make docs WHAT=generate APPLY=Y + + - name: Docs validate (blocking) + run: | + make docs DOCS_PHASE=validate || { + for f in .reports/workspace/docs/*.log; do + echo "=== $f ===" + tail -60 "$f" + done + exit 1 + } + + # mro-o6h5 (agent: kimi) — include-hidden-files: .reports/ is a dot-dir and + # upload-artifact v4 skips hidden paths by default (artifact silently empty). + - name: Upload docs reports on failure + if: failure() + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: docs-reports + path: .reports/ + retention-days: 7 + include-hidden-files: true + + build: + name: build + needs: docs-quality + runs-on: ubuntu-latest + timeout-minutes: 60 + steps: + - name: Checkout + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + submodules: false + fetch-depth: 0 + + - name: Setup Python + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + with: + python-version: "3.13" + + - name: Install uv + uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0 + with: + enable-cache: true + + - name: Sync workspace toolchain + run: make setup + + - name: Generate docs + run: make docs WHAT=generate APPLY=Y + + - name: Build site + run: make docs WHAT=build + + - name: Configure Pages + uses: actions/configure-pages@45bfe0192ca1faeb007ade9deae92b16b8254a0d # v6.0.0 + + - name: Upload site artifact + uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0 + with: + path: .reports/docs/site + + deploy: + name: deploy + needs: build + if: github.ref == 'refs/heads/main' + runs-on: ubuntu-latest + timeout-minutes: 10 + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5.0.0 diff --git a/.gitignore b/.gitignore index fa926f1d5..8aceff5a3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,8 @@ # BLOCK: AI/Agent Local Data (NEVER track these) .claude/* -!.agents/skills/ -!.agents/skills/** + +# BLOCK: Agent surface outside the workspace root +.agents/ # Skill scan reports (transient, regenerated by skill_validate.py) .agents/skills/*/report.json diff --git a/Makefile b/Makefile index f2888bae4..d3adeb384 100644 --- a/Makefile +++ b/Makefile @@ -48,7 +48,7 @@ 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 ?= 60 +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" @@ -117,21 +117,21 @@ _ALLOWED_WHATS_deps := check lock upgrade $(shell sed -n 's/^_custom_deps_\([a-z _ALLOWED_WHATS_build := artifacts $(shell sed -n 's/^_custom_build_\([a-z0-9_-]*\):.*/\1/p' "$(MAKEFILE_ROOT)/custom.mk" 2>/dev/null | sort -u | tr '\n' ' ') _ALLOWED_WHATS_check := all $(shell sed -n 's/^_custom_check_\([a-z0-9_-]*\):.*/\1/p' "$(MAKEFILE_ROOT)/custom.mk" 2>/dev/null | sort -u | tr '\n' ' ') _ALLOWED_WHATS_test := all $(shell sed -n 's/^_custom_test_\([a-z0-9_-]*\):.*/\1/p' "$(MAKEFILE_ROOT)/custom.mk" 2>/dev/null | sort -u | tr '\n' ' ') -_ALLOWED_WHATS_fmt := check all $(shell sed -n 's/^_custom_fmt_\([a-z0-9_-]*\):.*/\1/p' "$(MAKEFILE_ROOT)/custom.mk" 2>/dev/null | sort -u | tr '\n' ' ') -_ALLOWED_WHATS_fix := check all $(shell sed -n 's/^_custom_fix_\([a-z0-9_-]*\):.*/\1/p' "$(MAKEFILE_ROOT)/custom.mk" 2>/dev/null | sort -u | tr '\n' ' ') +_ALLOWED_WHATS_fmt := check all apply $(shell sed -n 's/^_custom_fmt_\([a-z0-9_-]*\):.*/\1/p' "$(MAKEFILE_ROOT)/custom.mk" 2>/dev/null | sort -u | tr '\n' ' ') +_ALLOWED_WHATS_fix := check all apply $(shell sed -n 's/^_custom_fix_\([a-z0-9_-]*\):.*/\1/p' "$(MAKEFILE_ROOT)/custom.mk" 2>/dev/null | sort -u | tr '\n' ' ') _ALLOWED_WHATS_run := default $(shell sed -n 's/^_custom_run_\([a-z0-9_-]*\):.*/\1/p' "$(MAKEFILE_ROOT)/custom.mk" 2>/dev/null | sort -u | tr '\n' ' ') _ALLOWED_WHATS_status := diagnostics $(shell sed -n 's/^_custom_status_\([a-z0-9_-]*\):.*/\1/p' "$(MAKEFILE_ROOT)/custom.mk" 2>/dev/null | sort -u | tr '\n' ' ') _ALLOWED_WHATS_docs := all generate fix audit build validate $(shell sed -n 's/^_custom_docs_\([a-z0-9_-]*\):.*/\1/p' "$(MAKEFILE_ROOT)/custom.mk" 2>/dev/null | sort -u | tr '\n' ' ') _ALLOWED_WHATS_clean := generated $(shell sed -n 's/^_custom_clean_\([a-z0-9_-]*\):.*/\1/p' "$(MAKEFILE_ROOT)/custom.mk" 2>/dev/null | sort -u | tr '\n' ' ') _ALLOWED_WHATS_release := status $(shell sed -n 's/^_custom_release_\([a-z0-9_-]*\):.*/\1/p' "$(MAKEFILE_ROOT)/custom.mk" 2>/dev/null | sort -u | tr '\n' ' ') -_ALLOWED_WHATS_gen := check all $(shell sed -n 's/^_custom_gen_\([a-z0-9_-]*\):.*/\1/p' "$(MAKEFILE_ROOT)/custom.mk" 2>/dev/null | sort -u | tr '\n' ' ') +_ALLOWED_WHATS_gen := check all apply $(shell sed -n 's/^_custom_gen_\([a-z0-9_-]*\):.*/\1/p' "$(MAKEFILE_ROOT)/custom.mk" 2>/dev/null | sort -u | tr '\n' ' ') _ALLOWED_WHATS_worktree := list add update remove $(shell sed -n 's/^_custom_worktree_\([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_DEFAULT := lint pyrefly mypy pyright security markdown smells DOCS_ACTIONS := generate fix audit build validate -SERIALIZED_VERBS := check test gen fmt fix deps clean worktree -SERIALIZED_TARGETS := _serialized_check _serialized_test _serialized_gen _serialized_fmt _serialized_fix _serialized_deps _serialized_clean _serialized_worktree +SERIALIZED_VERBS := check test gen fmt fix deps clean worktree docs +SERIALIZED_TARGETS := _serialized_check _serialized_test _serialized_gen _serialized_fmt _serialized_fix _serialized_deps _serialized_clean _serialized_worktree _serialized_docs # End SECTION: verb dispatch # === SECTION: lint/type paths (managed) === @@ -177,11 +177,12 @@ _DEFAULT_gen := check _DEFAULT_worktree := list _APPLY_WHAT_deps := upgrade -_APPLY_WHAT_fmt := all -_APPLY_WHAT_fix := all +_APPLY_WHAT_fmt := apply +_APPLY_WHAT_fix := apply _APPLY_WHAT_run := default +_APPLY_WHAT_docs := generate _APPLY_WHAT_clean := generated -_APPLY_WHAT_gen := all +_APPLY_WHAT_gen := apply _APPLY_WHAT_worktree := update @@ -274,9 +275,12 @@ SETUP_ENVIRONMENT_RECIPE = set -eu; \ WORKSPACE_ORCHESTRATE = $(UV_RUN) python -m flext_infra workspace orchestrate REQUESTED_PROJECTS := $(strip $(if $(PROJECT),$(PROJECT),$(PROJECTS))) # A workspace root owns no local gate implementation: its verbs fan out to the -# declared members. Selecting the root here would make it orchestrate itself. +# declared members. Selecting the root (PROJECT=.) would make it orchestrate +# itself forever; map `.` to WORKSPACE_MEMBERS instead of failing closed mid-CI. DEFAULT_PROJECTS := $(WORKSPACE_MEMBERS) . + SELECTED_PROJECTS := $(if $(strip $(REQUESTED_PROJECTS)),$(REQUESTED_PROJECTS),$(DEFAULT_PROJECTS)) + WORKSPACE_PROJECT_ARGS := $(foreach project,$(SELECTED_PROJECTS),--projects $(project)) WORKSPACE_CHECK_ARGS := $(if $(strip $(CHECK_GATES)),--make-arg "CHECK_GATES=$(strip $(CHECK_GATES))") WORKSPACE_TEST_ARGS := $(if $(strip $(FLEXT_PYTEST_FILE_RAW)),--file "$${FLEXT_PYTEST_FILE_RAW}") $(if $(strip $(FLEXT_PYTEST_MATCH_RAW)),--match "$${FLEXT_PYTEST_MATCH_RAW}") $(if $(strip $(FLEXT_PYTEST_WHAT_RAW)),--what "$${FLEXT_PYTEST_WHAT_RAW}") @@ -370,7 +374,7 @@ define _run_for_selected_projects done endef -.PHONY: $(PUBLIC_VERBS) $(SERIALIZED_TARGETS) _builtin_help_usage _builtin_setup_environment _builtin_deps_check _builtin_deps_lock _builtin_deps_upgrade _builtin_build_artifacts _builtin_check_all _builtin_test_all _builtin_fmt_check _builtin_fmt_all _builtin_fix_check _builtin_fix_all _builtin_run_default _builtin_status_diagnostics _builtin_docs_all _builtin_docs_generate _builtin_docs_fix _builtin_docs_audit _builtin_docs_build _builtin_docs_validate _builtin_clean_generated _builtin_release_status _builtin_gen_check _builtin_gen_all _builtin_worktree_list _builtin_worktree_add _builtin_worktree_update _builtin_worktree_remove +.PHONY: $(PUBLIC_VERBS) $(SERIALIZED_TARGETS) _builtin_help_usage _builtin_setup_environment _builtin_deps_check _builtin_deps_lock _builtin_deps_upgrade _builtin_build_artifacts _builtin_check_all _builtin_test_all _builtin_fmt_check _builtin_fmt_all _builtin_fmt_apply _builtin_fix_check _builtin_fix_all _builtin_fix_apply _builtin_run_default _builtin_status_diagnostics _builtin_docs_all _builtin_docs_generate _builtin_docs_fix _builtin_docs_audit _builtin_docs_build _builtin_docs_validate _builtin_clean_generated _builtin_release_status _builtin_gen_check _builtin_gen_all _builtin_gen_apply _builtin_worktree_list _builtin_worktree_add _builtin_worktree_update _builtin_worktree_remove $(filter-out setup $(SERIALIZED_VERBS),$(PUBLIC_VERBS)): $(call _dispatch,$@) @@ -432,6 +436,13 @@ _serialized_worktree: $(call _dispatch,worktree) +docs: _builtin_require_environment + @$(PROJECT_FLEXT_INFRA) workspace serialize-make --workspace "$(PROJECT_ROOT)" --makefile "$(SELF_MAKEFILE)" --verb "docs" --selector-value "$(WHAT)" --apply-token "$(APPLY)" + +_serialized_docs: + $(call _dispatch,docs) + + # `setup` keeps its own recipe (it must not require the environment it is about # to build), but it still runs the pre-/post-setup lifecycle hooks so a project @@ -491,7 +502,7 @@ _builtin_help_usage: - @printf ' %-10s WHAT=%s\n' 'docs' "$$(printf '%s' '$(_ALLOWED_WHATS_docs)' | awk '{$$1=$$1; gsub(/ /, "|"); print}')"; + @printf ' %-10s WHAT=%s APPLY=Y\n' 'docs' "$$(printf '%s' '$(_ALLOWED_WHATS_docs)' | awk '{$$1=$$1; gsub(/ /, "|"); print}')"; @@ -636,10 +647,16 @@ _builtin_setup_submodules: exit 1; \ fi; \ need_fetch=1; \ - if git -C "$$child_root" rev-parse --verify "$$remote_ref" >/dev/null 2>&1 && \ - git -C "$$child_root" merge-base --is-ancestor "$$gitlink" HEAD && \ - git -C "$$child_root" merge-base --is-ancestor "$$remote_ref" HEAD; then \ - need_fetch=0; \ + if git -C "$$child_root" merge-base --is-ancestor "$$gitlink" HEAD; then \ + if git -C "$$child_root" rev-parse --verify "$$remote_ref" >/dev/null 2>&1; then \ + if git -C "$$child_root" merge-base --is-ancestor "$$remote_ref" HEAD; then \ + need_fetch=0; \ + fi; \ + else \ + # Pin is already present; origin tip may be absent on a shallow CI \ + # clone. Origin lag must not fail verify (setup never destroys). \ + need_fetch=0; \ + fi; \ fi; \ if [ "$$need_fetch" -eq 1 ]; then \ git -C "$$child_root" fetch --quiet origin "$$branch" || { \ @@ -786,6 +803,8 @@ _builtin_fmt_all: _builtin_require_environment $(call _require_apply) @$(UV_RUN) ruff format $(RUFF_PATHS) +_builtin_fmt_apply: _builtin_fmt_all + _builtin_fix_check: @printf 'ERROR: make fix requires APPLY=Y\n' >&2; exit 2 @@ -793,6 +812,8 @@ _builtin_fix_all: _builtin_require_environment $(call _require_apply) @$(UV_RUN) ruff check --fix $(RUFF_PATHS) +_builtin_fix_apply: _builtin_fix_all + _builtin_run_default: _builtin_require_environment @$(UV_RUN) $(PROJECT_NAME) $(ARGS) @@ -869,6 +890,8 @@ _builtin_gen_all: _builtin_require_environment @$(PROJECT_FLEXT_INFRA) deps modernize --workspace "$(PROJECT_ROOT)" --apply @$(PROJECT_FLEXT_INFRA) deps extra-paths --workspace "$(PROJECT_ROOT)" --apply +_builtin_gen_apply: _builtin_gen_all + _builtin_worktree_list: @$(PROJECT_FLEXT_INFRA) workspace worktree --workspace "$(WORKSPACE)" --operation list diff --git a/pyproject.toml b/pyproject.toml index 41698af1c..6b4bf0a46 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,7 +18,7 @@ classifiers = [ "Typing :: Typed", ] dependencies = [ - "cachetools>=6.2,<7.0", + "cachetools>=6.2,<8.0", "click>=8.3.3", "defusedxml>=0.7.1", "flext-core @ git+https://github.com/flext-sh/flext-core.git@0.12.0-dev", @@ -33,7 +33,7 @@ dependencies = [ "python-docx>=1.1.2", "python-pptx>=1.0.2", "pyyaml>=6.0.3", - "rich>=14,<15", + "rich>=14,<16", "ruamel.yaml>=0.18.16", "tabulate>=0.10.0", "tomlkit>=0.14.0",