type: standard
aspect: policy
title: "Совместимость кода — гарантия исполнения"
version: 1.0.0
date: 2026-04-05
status: active
languages: [Python, JavaScript, TypeScript, Go, Bash]
Цель: 100% исполнение кода платформы независимо от версий, диалектов, интерпретаторов и среды выполнения.
Область: весь код в library/, infra/, projects/, system/, tools/.
КОД ПИШЕТСЯ ПОД МИНИМУМ, А НЕ ПОД ТЕКУЩУЮ МАШИНУ.
Каждый файл декларирует свои требования.
Каждый автор знает границы совместимости.
Каждый CI проверяет соответствие.
| Язык | Минимальная версия | Целевая версия | Интерпретатор | Платформа |
|---|---|---|---|---|
| Python | 3.10 | 3.12 | CPython only | Linux x86_64 |
| JavaScript | Node.js 20 LTS | Node.js 22 LTS | Node.js only | Linux x86_64 |
| TypeScript | 5.0 | 5.4+ | tsc → Node.js | Linux x86_64 |
| Go | 1.21 | 1.22+ | gc (стандартный) | Linux x86_64 (cross: darwin, windows) |
| Bash | 5.0 | 5.1+ | bash only | Linux x86_64 |
Обоснование минимумов:
- Python 3.10 — сервер dev-pro-eu (Ubuntu 22.04), match/case, X | Y union types
- Node.js 20 — текущий LTS, поддержка до 2026-04
- Go 1.21 — slices/maps пакеты, min/max builtins
- Bash 5.0 — Ubuntu 20.04+, ассоциативные массивы, nameref
| Риск | Причина | Проявление | Решение |
|---|---|---|---|
list[int] vs List[int] |
Синтаксис generic типов изменился в 3.9 | TypeError на 3.8 |
Использовать list[int] (мин. 3.10) |
X \| Y union syntax |
Появился в 3.10 | SyntaxError на 3.9 |
Безопасно: минимум = 3.10 |
match/case |
Появился в 3.10 | SyntaxError на 3.9 |
Безопасно: минимум = 3.10 |
type X = ... |
Появился в 3.12 | SyntaxError на 3.10-3.11 |
ЗАПРЕЩЕНО — использовать TypeAlias |
f"{x:{fmt}}" вложенные |
Полноценно в 3.12 | SyntaxError на 3.10-3.11 |
ЗАПРЕЩЕНО — разделить на две строки |
ExceptionGroup |
Появился в 3.11 | NameError на 3.10 |
ЗАПРЕЩЕНО без проверки версии |
tomllib |
Появился в 3.11 | ModuleNotFoundError на 3.10 |
Использовать tomli (backport) для 3.10 |
encoding по умолчанию |
Windows=cp1252, Linux=utf-8 | Мусор в строках | Всегда указывать encoding="utf-8" |
dict порядок |
Гарантирован с 3.7 | — | Безопасно |
asyncio.TaskGroup |
Появился в 3.11 | AttributeError на 3.10 |
Использовать asyncio.gather() |
| PyPy / Jython | Другие VM | C-extensions падают | НЕ ПОДДЕРЖИВАЕМ |
M-PY-01. Shebang:
#!/usr/bin/env python3
Не #!/usr/bin/python3 — путь различается на разных дистрибутивах.
M-PY-02. Encoding в файловых операциях:
# ПРАВИЛЬНО
with open("file.txt", encoding="utf-8") as f:
data = f.read()
# ПРАВИЛЬНО — subprocess
result = subprocess.run(cmd, capture_output=True, text=True, encoding="utf-8")
M-PY-03. Типы — встроенные generics:
# ПРАВИЛЬНО (3.10+)
def process(items: list[str], cache: dict[str, int]) -> tuple[bool, str | None]:
...
# ДОПУСТИМО — для сложных типов
from typing import TypeVar, Generic, Protocol, TypeAlias
M-PY-04. Декларация совместимости в pyproject.toml:
[project]
requires-python = ">=3.10"
M-PY-05. Пути — только pathlib или os.path:
from pathlib import Path
config = Path(__file__).parent / "config.json"
# НЕ: config = __file__.replace("module.py", "config.json")
M-PY-06. Строки — только f-strings, без вложенных выражений:
# ПРАВИЛЬНО
name = f"Товар: {product.name}"
formatted = f"Цена: {price:,.2f}"
# ЗАПРЕЩЕНО (3.12+ only)
formatted = f"Цена: {price:{width}.{precision}f}"
# ПРАВИЛЬНО — альтернатива
fmt = f"{width}.{precision}f"
formatted = f"Цена: {format(price, fmt)}"
M-PY-07. Import — абсолютные пути для library/:
# ПРАВИЛЬНО
from library.primitives.types import Money
from library.functions.format.money import format_money
# ЗАПРЕЩЕНО для library/*
from ..primitives.types import Money
M-PY-08. Subprocess — список аргументов, не строка:
# ПРАВИЛЬНО
subprocess.run(["ls", "-la", path], check=True)
# ЗАПРЕЩЕНО (shell injection)
subprocess.run(f"ls -la {path}", shell=True)
| Паттерн | Почему запрещён | Альтернатива |
|---|---|---|
type X = int \| str |
Python 3.12+ only | X: TypeAlias = int \| str |
f"{val:{spec}}" вложенные f-strings |
3.12+ only | format(val, spec) |
ExceptionGroup / except* |
3.11+ only | try/except обычный |
asyncio.TaskGroup |
3.11+ only | asyncio.gather() |
tomllib без fallback |
3.11+ only | try: import tomllib; except: import tomli |
from typing import List, Dict, Tuple |
Устарело на 3.10+ | list, dict, tuple встроенные |
open("f.txt") без encoding |
Platform-dependent | open("f.txt", encoding="utf-8") |
os.system() |
Shell injection + нет контроля | subprocess.run() |
eval() / exec() с данными |
Произвольное выполнение кода | Парсеры (json, ast.literal_eval) |
import * |
Загрязнение namespace | Явные импорты |
sys.path.insert(0, ...) |
Хрупкие пути | pyproject.toml или PYTHONPATH |
datetime.now() без tz |
Зависит от системных настроек | datetime.now(tz=timezone.utc) |
В начале каждого скрипта (не библиотечного модуля):
#!/usr/bin/env python3
"""
Описание модуля.
Requires: Python >= 3.10
Dependencies: httpx, pydantic
"""
В pyproject.toml проекта:
[project]
requires-python = ">=3.10"
dependencies = [
"pydantic>=2.0",
"httpx>=0.25",
]
| Риск | Причина | Проявление | Решение |
|---|---|---|---|
| CJS vs ESM | Два несовместимых модульных стандарта | require is not defined / import not found |
Только ESM ("type": "module") |
import.meta.url |
Не работает в CJS | SyntaxError |
ESM only — безопасно |
Top-level await |
Только ESM | SyntaxError в CJS |
ESM only — безопасно |
structuredClone |
Node 17+ / Chrome 98+ | ReferenceError |
Безопасно: минимум Node 20 |
Array.at() |
Node 16.6+ | TypeError |
Безопасно: минимум Node 20 |
crypto.randomUUID() |
Node 14.17+ | TypeError |
Безопасно: минимум Node 20 |
fetch глобальный |
Node 18+ (experimental), Node 21+ (stable) | ReferenceError |
Безопасно: минимум Node 20, но помечен experimental |
| Legacy decorators vs TC39 | Разная семантика | Ошибки runtime | Только TC39 ("experimentalDecorators": false) |
moduleResolution |
node vs node16 vs bundler |
Разные пути резолва | Использовать "node16" или "bundler" |
.js в ESM импортах |
TypeScript требует .js extension при node16 |
ERR_MODULE_NOT_FOUND |
Всегда указывать расширение |
| Path separators | Windows \ vs Unix / |
Сломанные пути | path.join() / path.resolve() |
M-JS-01. Только ESM:
// package.json
{
"type": "module",
"engines": {
"node": ">=20"
}
}
M-JS-02. TypeScript — strict конфигурация:
// tsconfig.json
{
"compilerOptions": {
"strict": true,
"target": "ES2022",
"module": "node16",
"moduleResolution": "node16",
"esModuleInterop": true,
"skipLibCheck": true,
"outDir": "dist",
"declaration": true,
"isolatedModules": true,
"experimentalDecorators": false
}
}
M-JS-03. Импорты — с расширением:
// ПРАВИЛЬНО
import { handler } from "./handler.js";
import { Config } from "../types/config.js";
// ЗАПРЕЩЕНО (не резолвится в node16)
import { handler } from "./handler";
M-JS-04. Пути — только модуль path:
import { join, resolve } from "node:path";
const configPath = join(import.meta.dirname, "config.json");
// НЕ: const configPath = __dirname + "/config.json" // __dirname не существует в ESM
M-JS-05. Node.js API — с префиксом node:
// ПРАВИЛЬНО
import { readFile } from "node:fs/promises";
import { createServer } from "node:http";
// ДОПУСТИМО, но не рекомендуется
import { readFile } from "fs/promises";
M-JS-06. Обработка ошибок — типизированная:
// ПРАВИЛЬНО
try {
await operation();
} catch (error) {
if (error instanceof CustomError) {
// обработка
}
throw error;
}
// ЗАПРЕЩЕНО
try {
await operation();
} catch (e: any) {
console.log(e.message); // unsafe
}
| Паттерн | Почему запрещён | Альтернатива |
|---|---|---|
require() / module.exports |
CJS — устаревшая система | import / export |
__dirname / __filename |
Не существуют в ESM | import.meta.dirname, import.meta.filename (Node 21+) или fileURLToPath(import.meta.url) |
var |
Hoisting, scope утечки | const / let |
any тип (кроме boundary) |
Отключает проверки | unknown + type narrowing |
@ts-ignore |
Маскирует ошибки | @ts-expect-error с пояснением |
eval() |
Произвольное выполнение | JSON.parse(), Function constructor если unavoidable |
== (нестрогое) |
Неявные преобразования | === |
| Legacy decorators | Другая семантика | TC39 decorators или без них |
process.env.X! (non-null assert) |
Падение в runtime | Валидация при старте: z.object({ X: z.string() }) |
fs.readFileSync в async коде |
Блокирует event loop | fs.promises.readFile |
new Date().getTime() |
Менее читаемо | Date.now() |
setTimeout(fn, 0) для microtask |
Не microtask | queueMicrotask(fn) или Promise.resolve().then(fn) |
// package.json
{
"name": "@platform/service-name",
"version": "1.0.0",
"type": "module",
"engines": {
"node": ">=20"
},
"files": ["dist"],
"exports": {
".": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
}
}
}
| Риск | Причина | Проявление | Решение |
|---|---|---|---|
int размер |
32 бит на 32-bit платформах | Overflow | Использовать int64 для значений > 2^31 |
filepath vs path |
path — UNIX only, filepath — кроссплатформ |
Сломанные пути на Windows | Всегда path/filepath для FS-операций |
time.Now() точность |
Разная на ОС | Flaky тесты | Не сравнивать timestamp точно |
| CGO при кросс-компиляции | Нужен C toolchain | cgo: exec gcc: not found |
CGO_ENABLED=0 для кросс-билдов |
init() порядок |
Зависит от имён файлов | Непредсказуемая инициализация | Избегать init() с side effects |
| Loop variable capture (< 1.22) | Замыкание захватывает одну переменную | Все goroutine видят последнее значение | Go 1.22+ — исправлено; для 1.21 — копия |
slices.Sort vs sort.Slice |
slices — Go 1.21+ |
undefined: slices |
Безопасно: минимум 1.21 |
| generics | Go 1.18+ | syntax error |
Безопасно: минимум 1.21 |
range over integer |
Go 1.22+ | cannot range over 10 |
ЗАПРЕЩЕНО при минимуме 1.21, ОК при 1.22+ |
| Endianness | Big-endian vs little-endian | Неправильное чтение бинарных данных | binary.LittleEndian / BigEndian явно |
M-GO-01. go.mod — явная минимальная версия:
module github.com/platform/service
go 1.21
M-GO-02. Пути — только filepath для FS:
// ПРАВИЛЬНО
configPath := filepath.Join(baseDir, "config", "app.json")
// ЗАПРЕЩЕНО (UNIX only)
configPath := path.Join(baseDir, "config", "app.json")
// ЗАПРЕЩЕНО (hardcoded separator)
configPath := baseDir + "/" + "config" + "/" + "app.json"
M-GO-03. Ошибки — wrapping с контекстом:
// ПРАВИЛЬНО
if err != nil {
return fmt.Errorf("load config %s: %w", path, err)
}
// ЗАПРЕЩЕНО
if err != nil {
return err // потеря контекста
}
// ЗАПРЕЩЕНО
if err != nil {
return fmt.Errorf("error: %v", err) // %v теряет unwrap цепочку
}
M-GO-04. Goroutine — всегда с recovery:
go func() {
defer func() {
if r := recover(); r != nil {
log.Printf("panic in goroutine: %v\n%s", r, debug.Stack())
}
}()
doWork()
}()
M-GO-05. Context — всегда первый параметр:
func FetchData(ctx context.Context, url string) ([]byte, error) {
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
// ...
}
M-GO-06. Числовые типы — явные размеры для данных:
// ПРАВИЛЬНО — для бизнес-данных, протоколов, файлов
type Order struct {
ID int64 // явный размер
Amount int64 // копейки
Quantity int32 // не превысит 2B
}
// ДОПУСТИМО — для индексов, счётчиков, длин
for i := 0; i < len(items); i++ { // int ОК
M-GO-07. Кросс-компиляция:
# Сборка для Linux (с сервера — стандартный случай)
go build -o bin/service ./cmd/service
# Кросс-компиляция (для ПК оператора)
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o bin/service-linux ./cmd/service
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -o bin/service.exe ./cmd/service
| Паттерн | Почему запрещён | Альтернатива |
|---|---|---|
path.Join для файлов |
UNIX-only | filepath.Join |
"/" hardcoded separator |
UNIX-only | filepath.Separator или filepath.Join |
init() с side effects |
Непредсказуемый порядок | Явная инициализация в main() |
panic() в библиотечном коде |
Убивает вызывающий код | return error |
_ = err (игнорирование ошибки) |
Скрытые сбои | Обработать или явно задокументировать |
go func(){}() без recover |
Panic убивает процесс | Обёртка с defer recover() |
range over int |
Go 1.22+ only (если минимум 1.21) | for i := 0; i < n; i++ |
range over func |
Go 1.23+ only | Обычные циклы |
unsafe.Pointer |
Ломает memory safety | Безопасные альтернативы |
reflect без необходимости |
Медленно, хрупко | Generics (1.18+) или кодогенерация |
time.Sleep для синхронизации |
Race condition | sync.WaitGroup, channels, context |
map из горутин без мьютекса |
Data race | sync.Mutex или sync.Map |
// go.mod
module github.com/platform/service
go 1.21
require (
// зависимости с точными версиями
)
В //go:build для платформенного кода:
//go:build linux
// +build linux
package main
| Риск | Причина | Проявление | Решение |
|---|---|---|---|
[[ двойные скобки |
bash-only, нет в sh/dash | [[: not found |
Использовать [[ + shebang #!/bin/bash |
declare -A |
bash 4+ only | declare: -A: invalid option |
Безопасно: минимум bash 5.0 |
mapfile / readarray |
bash 4+ | mapfile: command not found |
Безопасно: минимум bash 5.0 |
<<< here-string |
bash-only | parse error | echo "$var" \| cmd как POSIX-альтернатива |
set -o pipefail |
bash/zsh only, не POSIX | illegal option |
Безопасно: shebang #!/bin/bash |
$RANDOM |
bash-only | пустая строка в sh | Безопасно: shebang #!/bin/bash |
local |
bash/dash/zsh, не строгий POSIX sh | local: not found |
Безопасно: shebang #!/bin/bash |
#!/bin/bash vs #!/usr/bin/env bash |
Разный путь на разных ОС | bad interpreter |
#!/usr/bin/env bash |
| Неэкранированные переменные | Globbing + word splitting | rm -rf / при пустой переменной |
Всегда "${var}" в кавычках |
cd без проверки |
Выполнение в неправильной директории | Удаление не тех файлов | cd /path || exit 1 |
| Command injection | Нефильтрованный ввод в команды | Произвольное выполнение | Валидация, кавычки |
M-SH-01. Заголовок каждого скрипта:
#!/usr/bin/env bash
set -euo pipefail
set -e — остановка при ошибкеset -u — ошибка при обращении к неопределённой переменнойset -o pipefail — ошибка в pipe не маскируетсяM-SH-02. Переменные — всегда в кавычках:
# ПРАВИЛЬНО
echo "${name}"
rm -rf "${target_dir:?}" # :? — ошибка если пустая (защита от rm -rf /)
cp "${source}" "${dest}"
# ЗАПРЕЩЕНО
echo $name # word splitting + globbing
rm -rf $dir/ # если $dir пустой → rm -rf /
M-SH-03. cd — только с проверкой:
# ПРАВИЛЬНО
cd "${workdir}" || { echo "FATAL: cannot cd to ${workdir}"; exit 1; }
# ЗАПРЕЩЕНО
cd $dir
rm -rf *
M-SH-04. Временные файлы — mktemp:
# ПРАВИЛЬНО
tmpfile=$(mktemp)
trap 'rm -f "${tmpfile}"' EXIT
# ЗАПРЕЩЕНО
tmpfile="/tmp/my_script_temp" # race condition, collision
M-SH-05. Функции — с local переменными:
do_backup() {
local source="${1:?source required}"
local dest="${2:?dest required}"
local timestamp
timestamp=$(date '+%Y%m%d_%H%M%S')
cp -r "${source}" "${dest}/${timestamp}"
}
M-SH-06. Условия — двойные скобки:
# ПРАВИЛЬНО (bash)
if [[ -f "${config}" ]]; then
source "${config}"
fi
if [[ "${status}" == "active" ]]; then
echo "running"
fi
# ПРАВИЛЬНО — числовое сравнение
if (( count > 10 )); then
echo "too many"
fi
M-SH-07. Логирование — функция:
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1"; }
err() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: $1" >&2; }
log "Starting backup"
err "Failed to connect" && exit 1
M-SH-08. Проверка зависимостей:
# В начале скрипта
for cmd in curl jq rsync; do
command -v "${cmd}" >/dev/null 2>&1 || {
err "Required command not found: ${cmd}"
exit 1
}
done
| Паттерн | Почему запрещён | Альтернатива |
|---|---|---|
#!/bin/sh для bash-скриптов |
dash не поддерживает bash-фичи | #!/usr/bin/env bash |
Переменные без кавычек $var |
Word splitting, globbing | "${var}" |
[ -f $file ] одинарные скобки |
Нет pattern matching, broken при пробелах | [[ -f "${file}" ]] |
cd dir без \|\| exit |
Молча продолжает в неверной директории | cd dir \|\| exit 1 |
rm -rf ${dir}/ |
Если $dir пустой → rm -rf / |
rm -rf "${dir:?}/" |
eval "$user_input" |
Command injection | Прямое выполнение без eval |
cat file \| grep |
Useless Use of Cat | grep pattern file |
echo ${password} |
Утечка в логи / ps |
Переменные окружения, файлы |
Backticks `cmd` |
Нечитаемо, не вложимо | $(cmd) |
function name { |
Нестандартный синтаксис | name() { |
for f in $(ls *.txt) |
Ломается на пробелах | for f in *.txt; do |
IFS без восстановления |
Ломает последующий парсинг | local IFS=... внутри функции |
Hardcoded пути /home/user/ |
Непортабельно | "${HOME}", переменные конфигурации |
В шапке каждого скрипта:
#!/usr/bin/env bash
# ═══════════════════════════════════════════
# НАЗВАНИЕ: описание
# REQUIRES: bash >= 5.0, curl, jq
# PLATFORM: Linux (Ubuntu 22.04+)
# ═══════════════════════════════════════════
set -euo pipefail
| Правило | Применимость |
|---|---|
| Все исходные файлы — UTF-8 без BOM | Все языки |
Все файловые операции — явный encoding="utf-8" |
Python |
| Все строки — UTF-8 | Go (нативно) |
.editorconfig в корне проекта |
Все |
# .editorconfig
root = true
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
indent_style = space
indent_size = 4
[*.{js,ts,json,yml,yaml}]
indent_size = 2
[*.go]
indent_style = tab
[Makefile]
indent_style = tab
| Правило | Где |
|---|---|
LF (\n) only |
Все файлы, все платформы |
.gitattributes запрещает CRLF |
Корень репозитория |
# .gitattributes
* text=auto eol=lf
*.sh text eol=lf
*.py text eol=lf
*.go text eol=lf
*.js text eol=lf
*.ts text eol=lf
| Язык | Правильно | Неправильно |
|---|---|---|
| Python | Path(__file__).parent / "config.json" |
__file__ + "/../config.json" |
| JS/TS | path.join(base, "config.json") |
base + "/config.json" |
| Go | filepath.Join(base, "config.json") |
base + "/" + "config.json" |
| Bash | "${SCRIPT_DIR}/config.json" |
./config.json (зависит от cwd) |
# Python
from pathlib import Path
SCRIPT_DIR = Path(__file__).resolve().parent
// TypeScript (ESM, Node.js 20+)
import { fileURLToPath } from "node:url";
import { dirname, join } from "node:path";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
// Go — compile-time, не нужно
// Конфиги через флаги или embed
//go:embed config.json
var configData []byte
# Bash
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Линтинг + совместимость
ruff check --target-version py310 .
# Проверка типов
mypy --python-version 3.10 --strict .
# Проверка зависимостей
pip-audit
# Проверка encoding
grep -rn "open(" --include="*.py" | grep -v "encoding="
# ^ все вхождения без encoding — WARNING
# TypeScript — строгая компиляция
tsc --noEmit
# Линтинг
eslint --max-warnings 0 .
# Проверка ESM
# package.json должен содержать "type": "module"
node -e "import('./src/index.js')"
# Проверка зависимостей
npm audit
# Vet — стандартные проверки
go vet ./...
# Staticcheck — расширенные проверки
staticcheck ./...
# Тесты с race detector
go test -race ./...
# Проверка кросс-компиляции
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build ./...
CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build ./...
# Уязвимости
govulncheck ./...
# ShellCheck — статический анализ
shellcheck -s bash scripts/*.sh
# Обязательные проверки ShellCheck:
# SC2086 — переменные без кавычек
# SC2046 — word splitting в подстановке
# SC2006 — backticks вместо $()
# SC2064 — trap с раскрытием переменных
# SC2155 — local + подстановка в одной строке
# Проверка shebang
head -1 scripts/*.sh | grep -v "#!/usr/bin/env bash"
# ^ все несоответствия — ERROR
# Encoding — все файлы UTF-8
file --mime-encoding $(find . -name "*.py" -o -name "*.sh" -o -name "*.go" -o -name "*.ts") \
| grep -v "utf-8\|us-ascii"
# ^ любые результаты — ERROR
# Line endings — только LF
grep -rlP "\r\n" --include="*.py" --include="*.sh" --include="*.go" --include="*.ts" .
# ^ любые результаты — ERROR
# Secrets в коде
grep -rn "password\s*=\s*['\"]" --include="*.py" --include="*.ts" --include="*.go" .
grep -rn "api_key\s*=\s*['\"]" --include="*.py" --include="*.ts" --include="*.go" .
# ^ любые результаты — CRITICAL
Как действовать когда нужна фича из более новой версии.
| Фича | Версия | Действие |
|---|---|---|
match/case |
3.10+ | Использовать (в рамках минимума) |
list[int] |
3.9+ (3.10 для union) | Использовать |
X \| Y union |
3.10+ | Использовать |
tomllib |
3.11+ | Backport: tomli |
ExceptionGroup |
3.11+ | Не использовать. asyncio.gather(return_exceptions=True) |
type X = ... |
3.12+ | TypeAlias из typing |
f-string вложенные |
3.12+ | format() |
| Free-threaded (no GIL) | 3.13+ | Не рассчитывать |
| Фича | Версия | Действие |
|---|---|---|
fetch global |
Node 18+ | Использовать (Node 20+, experimental) |
import.meta.dirname |
Node 21+ | fileURLToPath + dirname как fallback |
structuredClone |
Node 17+ | Использовать |
Array.findLast |
Node 18+ | Использовать |
import attributes |
Node 21+ | Не использовать. createRequire для JSON |
using keyword |
TS 5.2+ | Использовать с осторожностью |
| Фича | Версия | Действие |
|---|---|---|
| Generics | 1.18+ | Использовать |
slices, maps |
1.21+ | Использовать |
min(), max() |
1.21+ | Использовать |
range over int |
1.22+ | Только если go 1.22 в go.mod |
| Loop variable fix | 1.22+ | Только если go 1.22 в go.mod; иначе — копия |
range over func |
1.23+ | Не использовать |
log/slog |
1.21+ | Использовать |
| Фича | Версия | Действие |
|---|---|---|
declare -A |
bash 4.0+ | Использовать (минимум 5.0) |
mapfile |
bash 4.0+ | Использовать |
${var@U} (uppercase) |
bash 5.1+ | Использовать с осторожностью |
BASH_ARGV0 |
bash 5.0+ | Использовать |
Nameref declare -n |
bash 4.3+ | Использовать |
При code review проверять:
/home/user/, C:\Users\)#!/usr/bin/env python3encoding="utf-8" во всех open()from typing import List, Dict (использовать встроенные)type X = ... (3.12+)datetime с timezone"type": "module" в package.json"strict": true в tsconfig.json.jsrequire() / module.exportsany (кроме boundary types)"engines": { "node": ">=20" }go 1.21 (или выше) в go.modfilepath.Join (не path.Join) для FS%wdefer recover()context.Context первый параметр в публичных функцияхinit() с side effects#!/usr/bin/env bashset -euo pipefail"${var}"cd с || exit 1mktemp для временных файлов + trap на cleanupcommand -v для проверки зависимостейНа сервере dev-pro-eu (2026-04-05):
- Python 3.10.12
- Node.js 22.21.0
- Go 1.18.1 (ниже минимума 1.21 — требуется обновление)
- Bash 5.1.16
| Приоритет | Действие | Где |
|---|---|---|
| P0 | Обновить Go до 1.21+ | сервер dev-pro-eu |
| P1 | Добавить set -euo pipefail во все bash-скрипты |
infra/, .claude/scripts/ |
| P2 | Заменить from typing import List, Dict на встроенные |
library/ |
| P3 | Добавить encoding="utf-8" в open() |
library/, projects/ |
| P4 | Добавить .editorconfig и .gitattributes в корень |
workspace root |
Не переписывать всё сразу. При каждом изменении файла — привести к стандарту:
Открыл файл → Исправил задачу → Привёл к стандарту → Коммит
Версия: 1.0.0