These tools use Python 3.10+ and Git. They run on Windows, Linux and macOS.
All source and document inventories come from git ls-files; gitlinks,
untracked files and nested worktrees are excluded. No tool searches the checkout
recursively. CMake's explicitly selected generated File API reply directory is
the only generated-file inventory. Outputs use UTF-8 without a BOM.
All lint commands default to report mode (exit 0 with findings). Add
--strict for blocking validation. Operational failures, such as unreadable
input or missing File API replies, fail in either mode. Commands which perform
an explicit equivalence check (normalize_includes --check, line coverage and
snapshot comparison) return a nonzero exit code when the check fails.
src/layers.tsv is a UTF-8 TSV with columns:
kind, path, module, group, rank, rule, target, owner, expires, note
modulerows define canonical prefixes, globally unique module names, groups and ranks. CMake consumers should select only these rows.forbid,restrict,thirdparty,symbolandallowdefine semantic restrictions and exact single-outlet/contact-table registrations.exceptionrequires a source glob, target glob, rule, owner, reason and an ISO date. It expires on that date. Strict mode requires no exception rows.exemptrecords the agreed web and stb scope exclusions for R12/R15.ownership_allowrecords an exact canonical file, named C++ scope, metric, owner, reason and maximum count (rankcolumn). It cannot exempt another file, another scope, or excess occurrences. This is for approved RAII primitives and private-constructor/third-party factories, not whole folders.- R4 is reserved because the approved design does not define it.
src_layout_map.tsv uses old_path, new_path, phase, kind, note.
Longest matching path wins; directory keys and replacements end in /.
move maps entire files, delete records separately authorized P0 removals,
and extract records planned splits and never participates in a blind
replacement. Some exact rows describe planned variants which are absent on a
particular base; validate_map lists these explicitly.
Layer checks use the canonical mapping during P0-P2. --layout final rejects
legacy source roots. The default auto switches to final as soon as grouped
source files exist; use --layout transition explicitly during a rehearsal.
The mapping does not authorize deleting files. Mutation is limited to explicitly
selected normalization, baseline capture and migration modes described below.
python scripts/layers/check_layers.py --output layers.json
python scripts/layers/check_layers.py --enforce-parent-includes --output layers.json
python scripts/layers/check_layers.py --enforce R8 --output layers.json
python scripts/layers/check_layers.py --strict --layout final
python scripts/layers/check_file_size.py --strict
python scripts/layers/check_ownership.py --strict
python scripts/layers/check_ownership.py --final
python scripts/refactor/check_doc_paths.py --strict
python scripts/refactor/validate_map.py --strict
python scripts/refactor/normalize_includes.py --check --scope src
python scripts/refactor/normalize_includes.py --scope src
python scripts/refactor/normalize_includes.py --check --scope tests
bash scripts/refactor/branch_inventory.sh --base master --output inventory.mdAll repository-oriented commands accept --repo. The branch tool can be called
directly with Python on Windows. It includes local and remote-tracking refs by
default, never fetches, and records unknown dirty status as unknown rather than
clean. --local-only omits remote-tracking refs. --format json preserves the
full git cherry results and worktree status records. Source/test counts are
distinct paths touched by + commits, so patch-equivalent historical branches
are not mistaken for unique work.
Include normalization preserves every non-path byte, mixed CRLF/LF, final
newline state, and inactive platform preprocessor branches. It refuses an
ambiguous rewrite. Same-directory bare includes are left alone as required by
P1; R8 separately reports ambiguity with generated headers. Test helpers get
their full test_support/ prefix after the P1 file moves.
R15 is a lexical inventory, not a C++ lifetime proof. It masks comments, quoted
strings and raw strings, distinguishes deleted functions from delete
expressions, and excludes borrowed thread parameters. Synchronous CV predicates,
standard-library algorithms, immediately invoked lambdas, and local closures
used only as direct calls are reported separately. Unsafe capture entries
are tagged escaping-context or requires-review; uncertain lifetimes remain
counted rather than silently waived. --final additionally enforces the
phase-one ownership targets. Review actual occurrences when approving a
baseline. The optional raw-handle category implements the sixth row in the
ownership design (the mother task lists five categories).
P1 should use --enforce-parent-includes. Full R8 also checks root-level bare
headers and generated-name ambiguity; those are resolved in P2 before the final
strict gate.
--write-baseline explicitly captures the R12 or R15 baseline. Do not run it as
part of ordinary CI validation. Reduce reviewed limits when code shrinks.
python scripts/refactor/cmake_target_snapshot.py --build-dir build-verify --query
cmake -S . -B build-verify <normal project configuration flags>
python scripts/refactor/cmake_target_snapshot.py --build-dir build-verify --configuration Release --output targets.json
python scripts/refactor/cmake_target_snapshot.py --build-dir build-verify --configuration Release --map scripts/refactor/src_layout_map.tsv --reverse-map --compare old-targets.json
python scripts/refactor/gtest_inventory.py --binary build-verify/tests/acecode_unit_tests --run --ctest-dir build-verify --output gtest.json
python scripts/refactor/check_line_coverage.py --source-ref pre-split --map lines.tsv --original original-file.cppSnapshots enumerate all File API targets, including EXCLUDE_FROM_ALL, and
compare every (configuration, target, source, language, definitions, options)
tuple. Source-level definitions are included. Absolute source/build roots are
normalized only at path boundaries; a similarly named adjacent directory is
not rewritten. The build must be configured after creating the File API query.
gtest inventories retain parameterized and disabled case names, actual XML
SKIPs with reasons, failures, executed cases and the ctest registration list.
--xml and --list-file can consume previously captured output. Without a run
or XML input, actual_results is null, not an empty SKIP list. Baseline test
failures are recorded; a missing XML result or timeout is a capture error.
Line mappings have columns old_path, old_start, old_end, new_path, new_start, new_end, mode, reason with one-based inclusive ranges. Every original line must
appear exactly once; every destination must be tracked and in bounds. Default
copy mappings verify actual line bytes (apart from newline style). edited
ranges require an explicit reason. Deleted/unmapped ranges cannot pass.
Repeat --original to detect complete files accidentally omitted from a map.
python scripts/refactor/compare_snapshots.py --before baseline/g0/original/linux-x64/targets.json --after out/targets.json --allowed-addition src/base/utils/abandonable_call.cpp --gtest-before baseline/g0/original/linux-x64/gtest.json --gtest-after out/gtest.json --output comparison.jsonTwo target snapshots are compared tuple by tuple with the same normalization
as cmake_target_snapshot.py. Removed tuples are authorized only when their
source (or the generated object of that source) is a delete row of
src_layout_map.tsv; added tuples only when they are an explicitly listed
--allowed-addition production file or a new tests/ source of
acecode_unit_tests. Target additions/removals and any other tuple change
make the report unexpected and return exit code 1. The optional gtest
section lists added/removed case names, SKIP and failure changes; it is
informational only. The four-platform baselines live under
openspec/changes/refactor20260927-restructure-src-layers/baseline/g0/.
python -m unittest discover -s scripts/refactor/tests -p 'test_*.py' -vThe tests create temporary Git repositories, preserve mixed line endings, exercise nested-worktree isolation and deliberate rule violations, and configure a real CMake C++ project with an excluded executable and source-specific defines. A working CMake C++ compiler is required for that integration test.
apply_layout.py executes src_layout_map.tsv one phase at a time and splits
the work into the commits P3 expects. It reuses the migration rewrite functions
and never invents a second path rule set.
python scripts/refactor/apply_layout.py plan --phase P3 # report moves and unfinished earlier rows
python scripts/refactor/apply_layout.py move --phase P3 --commit # M1: git mv only, staged diff must be all R100
python scripts/refactor/apply_layout.py rewrite --phase P3 --commit # M2: CMake lists, cpp_source_paths, docs, help site, includes
python scripts/refactor/apply_layout.py seed --seed-version 2026-09-29.1 --commit # M2b: seed SKILL paths, version, hashes, test literal
python scripts/refactor/apply_layout.py blame --revs M1_SHA M2_SHA --title "P3 M1/M2" --commit # M3
python scripts/refactor/apply_layout.py move --phase P2-08 --commit # P2 phases land in the transition directories src/<module>/moveselects onlymoverows of the given phase(s). Earlier phases with anold_pathstill present are refused;--allow-unfinishedis for rehearsals only. A P2 phase landssrc/<group>/<module>/rows insrc/<module>/(transition_path); P3 uses the finalnew_path. The tree must have no uncommitted tracked changes (--allow-dirtyoverrides), destinations must not exist, tracked symlinks are refused, emptied directories are removed and the staged diff is verified to contain onlyR100renames.rewriteruns aftermove. Module-relative CMake paths are resolved against the pre-move inventory (the phase map reversed) and translated forward; P3 uses the whole map for build files and documents, a P2 phase only its own rows. Documents go throughdocuments_plan(help build whendocs/help-sourcechanges;--no-design-noteskips the openspec fingerprint note).normalize_includesruns last; P3 must report 0 changed include lines because module-root includes do not change under the group move.--commituses the series subject prefix and tagsmovewith[no-build](P3) or[mechanical](P2) andrewritewith[mechanical].seedandblamewrap the seed-only--docs --seed-versiontransaction and the.git-blame-ignore-revsregistration (full SHAs, duplicates skipped, untagged subjects reported as warnings).apply_include_roots.pyis the structural half of P3 M2 that no path mapping can express: it definesACECODE_INCLUDE_ROOTS(the six group roots plusexternalandgenerated), replaces every bare${CMAKE_SOURCE_DIR}/srcinclude root in the three build files and dropsALLOW_LEGACYfromacecode_assert_known_roots. It is idempotent; run it right afterrewrite --phase P3and fold it into the M2 commit.
migrate_branch.py has five modes. rebase and patch always create a new
isolated repository, outside all existing worktrees. They do not fetch, push,
change source refs, modify a user's checkout/index, or initialize submodules.
The destination must not already exist. It remains available on failure, with
conflicts and the full JSON report under .git/refactor-migration/.
python scripts/refactor/migrate_branch.py rebase origin/legacy --onto POST_FREEZE_SHA --destination ../migration-rebase --output rebase.json
python scripts/refactor/migrate_branch.py patch origin/legacy --onto POST_FREEZE_SHA --destination ../migration-patch --output patch.json
python scripts/refactor/migrate_branch.py --apply-map --repo ../migration-patch --dry-run
python scripts/refactor/migrate_branch.py --apply-map --repo ../migration-patch
python scripts/refactor/migrate_branch.py --docs --repo ../migration-patch
python scripts/refactor/migrate_branch.py --docs --repo ../migration-patch --seed-version YYYY-MM-DD.N
python scripts/refactor/migrate_branch.py --check --repo ../migration-patch --output check.jsonrebaseuses--rebase-merges --no-update-refsin the isolated repository, preserving the legacy commit sequence and letting Git follow directory moves. The source and target SHA and merge base are pinned before cloning.--baseselects another verified ancestor when the branch requires it.patchmigrates the aggregate merge-base-to-branch delta, including files outside C++. It projects both old and new blobs before producing a binary, full-index patch, then actually runsgit apply --3way --index. It never edits hunk text or leaves stale blob hashes. A companionpatch-objects.bundlesupplies the transformed base objects required to apply the exported patch in another repository (git fetch BUNDLE migration-patch-base migration-patch-headbeforegit apply --3way --index PATCH). Git's raw diagnostics and unmerged paths are retained. Patch-equivalent commits and merge history are visible in thecherryrecord; this aggregate route does not claim to preserve commits.--apply-mapmoves indexed files and normalizes includes/build references using the longest path mapping. Includes are resolved against the old file inventory, so a bare include follows a file moved to a different module. Root CMake variables and module-relative paths inside each CMakeLists.txt are resolved in their actual source directory; included CMake files do not invent a CMAKE_CURRENT_SOURCE_DIR context. Explicit relative document links are likewise resolved from the document's directory, never from a substring. Mixed EOLs, missing final newlines, binary data and all unrelated bytes stay unchanged. The plan refuses collisions, untracked destinations, symlinks, ambiguous includes, deletes and semantic extraction rows before writing. There is no automatic delete or semantic split. A changed legacy file which was deleted upstream blocks patch generation; extraction issues remain explicit even if Git happens to apply cleanly.--dry-runwrites nothing.--docsupdates authored root/docs paths, then runs the help builder in a temporary snapshot containing only indexed inputs. Only tracked generated outputs are copied back;sources.jsonandsearch-index.jsare never regex edited. A failed builder prevents all document writes. Active OpenSpec designs receive a map fingerprint note; their historical text, archive and specs are preserved.--docs --seed-version VERSIONis a separate seed-only transaction/commit: mapped SKILLs, seed.version, MANIFEST bundle_version, canonical-LF hashes and exact prior bundle-version test literals change together. Older upgrade fixtures are retained. The current packaged test reads the bundle version dynamically, so there are no current literals to replace.--checkis read-only and blocking, unlike the report-mode lint tools. It requires zero final-layout R1-R14 findings, no R15 ratchet increases, normalized includes, valid maps/document paths/seed hashes, no pending build path rewrites, no conflict markers and no unmerged index entries. It does not silently update a baseline. Four-platform build/target/test acceptance and semantic review are still separate requirements.
Before P2/P3 finishes, use --layout current to rehearse the real unchanged
layout. Final migration refuses an unfinished target by default. --projection
explicitly creates a synthetic directory/include fixture, retains unfinished
deletions and records missing semantic extractions. It is not a buildable final
baseline and never means P2/P3 has completed. This option also permits inspecting
an --apply-map projection with reported issues; it still exits nonzero when
issues exist. There is no projection bypass for --check.
The isolated clones use a read-only shared object store and disable pushes to
the source. Keep the source repository available while reviewing them. To retain
a migrated branch independently, run git repack -a inside that isolated clone
and remove its alternates file after verifying the repack, or transfer the branch
with a normal Git bundle. The tool does not clean up any user directory.
The fixed P0-02 nine-ref inventory has a reproducible runner:
python scripts/refactor/tests/rehearse_legacy_refs.py --base FIXED_SHA --output-dir REPORT_DIRECTORY --jobs 3It executes current-layout rebase/patch and explicitly synthetic projection
patches in separate temporary repositories and verifies original refs, index and
working diff stay unchanged. --scenario projection-patch selects only that
scenario. Git conflicts are recorded outcomes, not a reason to discard a report
or silently merge a feature branch. All reports retain full diagnostics.
Run check_layers.py --layout final --strict, check_file_size.py --strict, check_ownership.py --strict --final and check_doc_paths.py --strict. Include normalization remains blocking for src and tests. CTest exposes layer_lint; CI retains reports on failure. Never refresh a baseline just to hide a violation. Ownership allowances identify exact scopes and counts; layering exceptions stay empty. Tool self-tests use isolated repositories to prove upward dependencies, oversized files and new detach calls fail.
文档检查只把真实仓库引用作为存在性断言。示例工作区路径、文件补全前缀及历史方案中未实施的文件,逐项登记在 doc_path_references.json,按文档、完整路径和出现次数匹配,并在报告中单独列出;没有目录级豁免。新出现的缺失路径仍使 strict 检查失败。所有权的同步回调人工核对项同样限定文件、命名作用域、调用 API 和数量,不能被新增的异步回调占用。