@echo off setlocal enabledelayedexpansion rem Sync standards snapshot to project root. rem - Copies \rulesets\ -> \.agents\ rem - Updates \.gitattributes (append missing rules by default) rem Existing targets are backed up before overwrite. rem rem Multi rulesets: rem sync_standards.bat tsl cpp rem Notes: rem - When syncing multiple rulesets, .gitattributes is synced only once (first ruleset). set "SCRIPT_DIR=%~dp0" set "ROOT=%SYNC_ROOT%" if "%ROOT%"=="" for /f "delims=" %%R in ('git -C "%SCRIPT_DIR%" rev-parse --show-toplevel 2^>nul') do set "ROOT=%%R" if "%ROOT%"=="" set "ROOT=%cd%" for %%I in ("%ROOT%") do set "ROOT=%%~fI" for %%I in ("%SCRIPT_DIR%..") do set "SRC=%%~fI" set "AGENTS_SRC_ROOT=%SRC%\rulesets" if not exist "%AGENTS_SRC_ROOT%" ( if exist "%SRC%\.agents" ( set "AGENTS_SRC_ROOT=%SRC%\.agents" ) ) set "GITATTR_SRC=%SRC%\.gitattributes" if not exist "%AGENTS_SRC_ROOT%" ( echo ERROR: Standards snapshot not found at %AGENTS_SRC_ROOT% >&2 echo Run: git subtree add --prefix docs/standards/playbook ^ ^ --squash >&2 exit /b 1 ) set "AGENTS_NS=%AGENTS_NS%" set "GITATTR_DST=%ROOT%\.gitattributes" set "SYNC_GITATTR_MODE=%SYNC_GITATTR_MODE%" if "%SYNC_GITATTR_MODE%"=="" set "SYNC_GITATTR_MODE=append" rem Multi rulesets: only on outer invocation. if "%SYNC_STANDARDS_INNER%"=="" ( set "LANG_LIST=" if not "%~1"=="" set "LANG_LIST=%*" if "%LANG_LIST%"=="" ( if "%AGENTS_NS%"=="" ( if exist "%ROOT%\.agents" ( for /d %%D in ("%ROOT%\.agents\*") do ( set "CAND=%%~nxD" if exist "%AGENTS_SRC_ROOT%\!CAND!\" ( if defined LANG_LIST (set "LANG_LIST=!LANG_LIST! !CAND!") else set "LANG_LIST=!CAND!" ) ) ) ) ) if not "%LANG_LIST%"=="" ( set "FIRST=1" set "SYNC_FIRST=%SYNC_GITATTR_MODE%" for %%L in (!LANG_LIST!) do ( if "!FIRST!"=="1" ( set "FIRST=0" set "SYNC_STANDARDS_INNER=1" set "AGENTS_NS=%%~L" set "SYNC_GITATTR_MODE=!SYNC_FIRST!" call "%~f0" ) else ( set "SYNC_STANDARDS_INNER=1" set "AGENTS_NS=%%~L" set "SYNC_GITATTR_MODE=skip" call "%~f0" ) ) exit /b 0 ) ) if "%AGENTS_NS%"=="" set "AGENTS_NS=tsl" echo %AGENTS_NS%| findstr /r "[\\/]" >nul && ( echo ERROR: invalid AGENTS_NS=%AGENTS_NS% exit /b 1 ) echo %AGENTS_NS%| findstr /c:".." >nul && ( echo ERROR: invalid AGENTS_NS=%AGENTS_NS% exit /b 1 ) set "AGENTS_ROOT=%ROOT%\.agents" set "AGENTS_DST=%AGENTS_ROOT%\%AGENTS_NS%" set "AGENTS_SRC=%AGENTS_SRC_ROOT%\%AGENTS_NS%" if not exist "%AGENTS_SRC%" ( rem Backward-compatible fallback: older snapshots used ^\.agents\* directly. if exist "%AGENTS_SRC_ROOT%\index.md" if exist "%AGENTS_SRC_ROOT%\auth.md" ( set "AGENTS_SRC=%AGENTS_SRC_ROOT%" ) else ( echo ERROR: Standards snapshot not found at "%AGENTS_SRC%". echo Hint: set AGENTS_NS to one of the subdirs under "%AGENTS_SRC_ROOT%" ^(e.g. tsl/cpp^). exit /b 1 ) ) if not exist "%AGENTS_SRC%" ( echo ERROR: Standards snapshot not found at "%AGENTS_SRC%". echo Run: git subtree add --prefix docs/standards/playbook ^ ^ --squash exit /b 1 ) if /I "%SRC%"=="%ROOT%" ( echo Skip: snapshot root equals project root. goto AfterGitAttr ) if not exist "%AGENTS_ROOT%" mkdir "%AGENTS_ROOT%" if exist "%AGENTS_DST%" ( set "RAND=%RANDOM%" pushd "%AGENTS_ROOT%" ren "%AGENTS_NS%" "%AGENTS_NS%.bak.!RAND!" popd echo Backed up existing %AGENTS_NS% agents -> %AGENTS_NS%.bak.!RAND! ) xcopy "%AGENTS_SRC%\\*" "%AGENTS_DST%\\" /e /i /y >nul if errorlevel 1 ( echo ERROR: failed to copy .agents exit /b 1 ) echo Synced .agents\%AGENTS_NS% from standards. if not exist "%AGENTS_ROOT%\index.md" ( > "%AGENTS_ROOT%\index.md" echo # .agents(多语言) >> "%AGENTS_ROOT%\index.md" echo. >> "%AGENTS_ROOT%\index.md" echo 本目录用于存放仓库级/语言级的代理规则集。 >> "%AGENTS_ROOT%\index.md" echo. >> "%AGENTS_ROOT%\index.md" echo 建议约定: >> "%AGENTS_ROOT%\index.md" echo. >> "%AGENTS_ROOT%\index.md" echo - `.agents/tsl/`:TSL 相关规则集(由 `sync_standards.*` 同步;适用于 `.tsl`/`.tsf`) >> "%AGENTS_ROOT%\index.md" echo - `.agents/cpp/`:C++ 相关规则集(由 `sync_standards.*` 同步;适用于 C++23/Modules) >> "%AGENTS_ROOT%\index.md" echo - `.agents/python/`:Python 相关规则集(由 `sync_standards.*` 同步) >> "%AGENTS_ROOT%\index.md" echo - `.agents/markdown/`:Markdown 相关规则集(仅代码格式化) >> "%AGENTS_ROOT%\index.md" echo. >> "%AGENTS_ROOT%\index.md" echo 规则发生冲突时,建议以“更靠近代码的目录规则更具体”为准。 >> "%AGENTS_ROOT%\index.md" echo. >> "%AGENTS_ROOT%\index.md" echo 入口建议从: >> "%AGENTS_ROOT%\index.md" echo. >> "%AGENTS_ROOT%\index.md" echo - `.agents/tsl/index.md`(TSL 规则集入口) >> "%AGENTS_ROOT%\index.md" echo - `.agents/cpp/index.md`(C++ 规则集入口) >> "%AGENTS_ROOT%\index.md" echo - `.agents/markdown/index.md`(Markdown 规则集入口) >> "%AGENTS_ROOT%\index.md" echo - `docs/standards/playbook/docs/`(人类开发规范快照:`tsl/`、`cpp/`、`python/`、`common/`) echo Created .agents\index.md ) if not exist "%ROOT%\AGENTS.md" ( > "%ROOT%\AGENTS.md" echo # Agent Instructions >> "%ROOT%\AGENTS.md" echo. >> "%ROOT%\AGENTS.md" echo 请以 `.agents/` 下的规则为准: >> "%ROOT%\AGENTS.md" echo. >> "%ROOT%\AGENTS.md" echo - 入口:`.agents/index.md` >> "%ROOT%\AGENTS.md" echo - 语言规则:`.agents/tsl/index.md`、`.agents/cpp/index.md`、`.agents/python/index.md`、`.agents/markdown/index.md` echo Created AGENTS.md ) :SyncGitAttr if exist "%GITATTR_SRC%" ( if /I "%SYNC_GITATTR_MODE%"=="skip" ( echo Skip: .gitattributes sync ^(SYNC_GITATTR_MODE=skip^). goto AfterGitAttr ) if /I "%SYNC_GITATTR_MODE%"=="overwrite" ( for %%I in ("%GITATTR_SRC%") do set "GITATTR_SRC_F=%%~fI" for %%I in ("%GITATTR_DST%") do set "GITATTR_DST_F=%%~fI" if /I "!GITATTR_SRC_F!"=="!GITATTR_DST_F!" ( echo Skip: .gitattributes source equals destination. goto AfterGitAttr ) if exist "%GITATTR_DST%" ( set "RAND=%RANDOM%" set "BAK_NAME=.gitattributes.bak.!RAND!" ren "%GITATTR_DST%" "!BAK_NAME!" echo Backed up existing .gitattributes -> !BAK_NAME! ) copy /y "%GITATTR_SRC%" "%GITATTR_DST%" >nul echo Synced .gitattributes from standards ^(overwrite^). goto AfterGitAttr ) if /I "%SYNC_GITATTR_MODE%"=="append" ( for %%I in ("%GITATTR_SRC%") do set "GITATTR_SRC_F=%%~fI" for %%I in ("%GITATTR_DST%") do set "GITATTR_DST_F=%%~fI" if /I "!GITATTR_SRC_F!"=="!GITATTR_DST_F!" ( echo Skip: .gitattributes source equals destination. goto AfterGitAttr ) set "TMP_DST=%TEMP%\\gitattributes.dst.%RANDOM%.tmp" set "TMP_MISS=%TEMP%\\gitattributes.missing.%RANDOM%.tmp" if exist "!TMP_DST!" del /q "!TMP_DST!" >nul 2>nul if exist "!TMP_MISS!" del /q "!TMP_MISS!" >nul 2>nul type nul > "!TMP_DST!" type nul > "!TMP_MISS!" if exist "%GITATTR_DST%" ( for /f "usebackq delims=" %%L in ("%GITATTR_DST%") do ( set "LINE=%%L" for /f "tokens=* delims= " %%A in ("!LINE!") do set "LINE=%%A" if not "!LINE!"=="" ( if /I not "!LINE:~0,1!"=="#" ( echo(!LINE!>>"!TMP_DST!" ) ) ) ) for /f "usebackq delims=" %%L in ("%GITATTR_SRC%") do ( set "LINE=%%L" for /f "tokens=* delims= " %%A in ("!LINE!") do set "LINE=%%A" if not "!LINE!"=="" ( if /I not "!LINE:~0,1!"=="#" ( findstr /x /l /c:"!LINE!" "!TMP_DST!" >nul || ( findstr /x /l /c:"!LINE!" "!TMP_MISS!" >nul || echo(!LINE!>>"!TMP_MISS!" ) ) ) ) set "MISS_SIZE=0" if exist "!TMP_MISS!" for %%S in ("!TMP_MISS!") do set "MISS_SIZE=%%~zS" if "!MISS_SIZE!"=="0" ( del /q "!TMP_DST!" "!TMP_MISS!" >nul 2>nul echo No missing .gitattributes rules to append. goto AfterGitAttr ) if exist "%GITATTR_DST%" ( set "RAND=%RANDOM%" set "BAK_NAME=.gitattributes.bak.!RAND!" ren "%GITATTR_DST%" "!BAK_NAME!" echo Backed up existing .gitattributes -> !BAK_NAME! set "DST_IN=%ROOT%\\!BAK_NAME!" ) else ( set "DST_IN=" ) set "TMP_OUT=%TEMP%\\gitattributes.out.%RANDOM%.tmp" if exist "!TMP_OUT!" del /q "!TMP_OUT!" >nul 2>nul if not "!DST_IN!"=="" ( type "!DST_IN!" > "!TMP_OUT!" for %%S in ("!DST_IN!") do set "DST_SIZE=%%~zS" if not "!DST_SIZE!"=="0" echo.>>"!TMP_OUT!" ) set "SOURCE_NOTE=%GITATTR_SRC%" >>"!TMP_OUT!" echo # Added from playbook .gitattributes ^(source: !SOURCE_NOTE!^) type "!TMP_MISS!" >> "!TMP_OUT!" copy /y "!TMP_OUT!" "%GITATTR_DST%" >nul del /q "!TMP_DST!" "!TMP_MISS!" "!TMP_OUT!" >nul 2>nul echo Appended missing .gitattributes rules from standards. goto AfterGitAttr ) if /I not "%SYNC_GITATTR_MODE%"=="block" ( echo ERROR: invalid SYNC_GITATTR_MODE=%SYNC_GITATTR_MODE% ^(use block^|overwrite^|append^|skip^) exit /b 1 ) rem block mode: maintain a managed block inside the destination file set "BEGIN=# BEGIN playbook .gitattributes" set "END=# END playbook .gitattributes" set "BEGIN_OLD=# BEGIN tsl-playbook .gitattributes" set "END_OLD=# END tsl-playbook .gitattributes" set "TMP_FILE=%TEMP%\\gitattributes.%RANDOM%.tmp" if exist "%GITATTR_DST%" ( set "RAND=%RANDOM%" set "BAK_NAME=.gitattributes.bak.!RAND!" ren "%GITATTR_DST%" "!BAK_NAME!" echo Backed up existing .gitattributes -> !BAK_NAME! set "DST_IN=%ROOT%\\!BAK_NAME!" ) else ( set "DST_IN=" ) set "IN_BLOCK=0" set "DONE=0" if not "%DST_IN%"=="" ( > "!TMP_FILE!" ( for /f "usebackq delims=" %%L in ("!DST_IN!") do ( set "LINE=%%L" if "!LINE!"=="%BEGIN%" ( if "!DONE!"=="0" ( echo %BEGIN% type "%GITATTR_SRC%" echo %END% set "DONE=1" ) set "IN_BLOCK=1" ) else if "!LINE!"=="%BEGIN_OLD%" ( if "!DONE!"=="0" ( echo %BEGIN% type "%GITATTR_SRC%" echo %END% set "DONE=1" ) set "IN_BLOCK=1" ) else if "!LINE!"=="%END%" ( set "IN_BLOCK=0" ) else if "!LINE!"=="%END_OLD%" ( set "IN_BLOCK=0" ) else ( if "!IN_BLOCK!"=="0" echo(!LINE! ) ) if "!DONE!"=="0" ( echo. echo %BEGIN% type "%GITATTR_SRC%" echo %END% ) ) ) else ( > "!TMP_FILE!" ( echo %BEGIN% type "%GITATTR_SRC%" echo %END% ) ) copy /y "!TMP_FILE!" "%GITATTR_DST%" >nul del /q "!TMP_FILE!" >nul 2>nul echo Updated .gitattributes from standards ^(managed block^). ) :AfterGitAttr echo Done. endlocal