Skip to content

feat(structure-lock): org reusable workflow + stdlib check script (FIVUCSAS#209)#2

Merged
ahmetabdullahgultekin merged 2 commits into
mainfrom
feat/repo-structure-lock
Jun 13, 2026
Merged

feat(structure-lock): org reusable workflow + stdlib check script (FIVUCSAS#209)#2
ahmetabdullahgultekin merged 2 commits into
mainfrom
feat/repo-structure-lock

Conversation

@ahmetabdullahgultekin

Copy link
Copy Markdown
Contributor

What

An ArchUnit-style FREEZE for repo file/folder layout, shared org-wide. It blocks (red CI check), it does not silently auto-move — exactly like ArchUnit fails the build. Tracks Rollingcat-Software/FIVUCSAS#209.

This is the dependency PR: it ships the reusable workflow + check script. The FIVUCSAS pilot PR (policy + caller + pre-commit) consumes it.

Contents

File Purpose
tools/check_repo_structure.py Stdlib-only checker (no PyYAML, no deps). Reads a per-repo .repo-structure.yml, scans the repo root, exits 1 listing every offender.
tools/test_check_repo_structure.py 12 unittest cases. Run: cd tools && python3 -m unittest test_check_repo_structure.
.github/workflows/repo-structure.yml Reusable workflow_call. Checks out the caller repo + this repo (for the script), runs the check on ubuntu-latest.
.pre-commit-hooks.yaml Exports the repo-structure pre-commit hook (language: script) for local fast-feedback.
.repo-structure.yml + .github/workflows/structure-check.yml This repo eats its own dog food (frozen root + self-calling gate).

Policy schema (.repo-structure.yml)

allowed_root_files:   [ ... ]          # frozen baseline of root files
allowed_root_dirs:    [ ... ]          # frozen baseline of root dirs
forbidden_root_patterns:               # Python regexes (single-quote them!)
  - '.*_(AUDIT|REVIEW|FINDINGS|REGISTER|TRIAGE|SWEEP|STATUS|SESSION|RECONCILIATION)_.*\.md$'
  - '.*_\d{4}-\d{2}-\d{2}.*\.md$'
  - '^(TODO|ROADMAP|BACKLOG|OPERATOR_TODO|PERSONAL_TODO).*\.md$'
required_files:       [ ... ]

The gate fails (exit 1) when: a root entry not in the allowlist appears, OR a path matches a forbidden pattern, OR a required file is missing. To intentionally add a new root entry you edit the policy in the same PR — the explicit, reviewed "unfreeze".

YAML note: the parser reads a deliberately small subset (block/flow lists of - "string" items) and keeps backslashes literal. Write regexes with single backslashes in single quotes ('\d', '\.') — double-quoted "\\d" would store a literal backslash and break the pattern.

Why ubuntu-latest, not the self-hosted runner

The check is a tiny stdlib Python job. It deliberately runs on GitHub-hosted ubuntu-latest so it never depends on the resource-constrained self-hosted Hetzner runner.

Security

Workflow inputs (policy, tools-ref) come from the trusted caller, not event data. policy is still passed through env: (never interpolated into the run: line). tools-ref is only used as a checkout ref.

--fix (convenience, never in CI)

python3 tools/check_repo_structure.py --fix [--dry-run] MOVES forbidden root tracking-docs into docs/archive/. Files only (never dirs, never disallowed-but-not-forbidden entries — those need a human). The gate is primary; --fix is a manual helper.

Proof (run locally against the FIVUCSAS clean root)

$ python3 tools/check_repo_structure.py --root <fivucsas>
Repo structure-lock PASSED — root layout matches .repo-structure.yml.   (exit 0)

$ : > <fivucsas>/TEST_AUDIT_2026-06-13.md
$ python3 tools/check_repo_structure.py --root <fivucsas>
Repo structure-lock FAILED ... FORBIDDEN: 'TEST_AUDIT_2026-06-13.md' matches /.*_(AUDIT|...)_.*\.md$/   (exit 1)

12/12 unit tests green.

Rollout (after merge)

  1. Merge this PR (owner). Optionally cut a tag (e.g. v1) so consumers can pin @v1 instead of @main.
  2. Each consuming repo adds .repo-structure.yml (frozen from its own root) + a thin caller .github/workflows/structure-check.yml that does uses: Rollingcat-Software/.github/.github/workflows/repo-structure.yml@main. The FIVUCSAS PR is the worked example.
  3. Owner manual step per repo: add the status check structure-check / repo-structure / structure-check as a REQUIRED check in branch protection so violations actually block merge.

Do not merge until reviewed; owner sets the required check.

…AS#209)

Add an ArchUnit-style FREEZE for repo root layout, shared org-wide:

- tools/check_repo_structure.py: stdlib-only checker. Reads a per-repo
  .repo-structure.yml policy (allowed_root_files/dirs, forbidden_root_patterns
  regexes, required_files); exits 1 listing every offender on drift. Optional
  --fix moves forbidden root tracking-docs to docs/archive/ (never auto-run in
  CI). Parses a small single-quoted YAML subset so no PyYAML dependency.
- tools/test_check_repo_structure.py: 12 unittest cases (clean pass, forbidden
  dated docs, TODO, disallowed file/dir, missing required, .git ignored,
  --fix scope, policy parse/validation).
- .github/workflows/repo-structure.yml: reusable workflow_call. Checks out the
  caller repo + this .github repo (for the script), runs the check on
  ubuntu-latest (deliberately NOT the self-hosted runner). Policy input passed
  via env, never interpolated into the shell line.
- .pre-commit-hooks.yaml: exports the `repo-structure` hook (language: script)
  so any repo can run the same gate locally.
- .repo-structure.yml + .github/workflows/structure-check.yml: this repo eats
  its own dog food (frozen root + self-calling gate).
The reusable workflow checks this tooling repo out into
.repo-structure-tools/ inside the caller's workspace, so the scanner saw it
as a root entry and failed the .github repo's own self-check with
"DISALLOWED DIR: .repo-structure-tools" (caught by the live PR run).

Always-ignore .git and .repo-structure-tools at the root. Adds a test for
the tooling-checkout case.
@ahmetabdullahgultekin ahmetabdullahgultekin merged commit 28bef0e into main Jun 13, 2026
2 checks passed
@ahmetabdullahgultekin ahmetabdullahgultekin deleted the feat/repo-structure-lock branch June 13, 2026 09:23
ahmetabdullahgultekin added a commit to Rollingcat-Software/FIVUCSAS that referenced this pull request Jun 13, 2026
…CSAS#209) (#210)

Pilot the org repo structure-lock on the umbrella repo.

- .repo-structure.yml: allowlists FROZEN from the current clean origin/master
  root (16 root files + 20 root dirs) + forbidden_root_patterns so dated
  tracking docs (*_AUDIT_*, *_2026-06-13*, TODO*/ROADMAP*/BACKLOG*) can never
  reappear at root + required_files (README/LICENSE/.gitignore).
- .github/workflows/structure-check.yml: thin caller of the org reusable
  workflow Rollingcat-Software/.github .github/workflows/repo-structure.yml@main,
  on pull_request. FAILS the PR on any layout drift.
- .pre-commit-config.yaml: add the `repo-structure` hook from the .github repo
  for fast local feedback mirroring the CI gate.

Verified: check PASSES on the current clean root (exit 0) and FAILS on a
planted TEST_AUDIT_2026-06-13.md (exit 1, FORBIDDEN). Dummy removed.

Depends on Rollingcat-Software/.github#2 (reusable workflow + script).

Co-authored-by: Ahmet Abdullah Gultekin <rollingcat.help@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant