[skip ci] chore(sync): maj hooks+docs agents centralisés
This commit is contained in:
parent
990de3e2d4
commit
7bfb2e0e7f
17
.cursor/rules/05-template-governance.mdc
Normal file
17
.cursor/rules/05-template-governance.mdc
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
---
|
||||||
|
alwaysApply: true
|
||||||
|
---
|
||||||
|
|
||||||
|
# Gouvernance du template 4NK
|
||||||
|
|
||||||
|
[portée]
|
||||||
|
Assurer que chaque projet adapte intelligemment le template et que les améliorations génériques reviennent dans `4NK_template`.
|
||||||
|
|
||||||
|
[directives]
|
||||||
|
- Conserver `security-audit` et `release-guard` dans tous projets.
|
||||||
|
- Adapter la CI, les docs et `AGENTS.md` au contexte local.
|
||||||
|
- En cas d'amélioration générique : ouvrir une issue "Template Feedback", prototyper, valider CI, mettre à jour `CHANGELOG.md`/`TEMPLATE_VERSION`.
|
||||||
|
|
||||||
|
[validation]
|
||||||
|
- Refuser un push/tag si l'adaptation a retiré les vérifications minimales (sécurité, tests, build, version/changelog/tag).
|
||||||
|
- Exiger une documentation claire dans `docs/TEMPLATE_ADAPTATION.md` et `docs/TEMPLATE_FEEDBACK.md`.
|
5
.cursor/rules/98-explain-complex-commands
Normal file
5
.cursor/rules/98-explain-complex-commands
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
alwaysApply: true
|
||||||
|
---
|
||||||
|
|
||||||
|
quand tu fais une commande ou un requète complexe, explique là avant de la lancer
|
9
.cursor/rules/99-lint-markdow.mdc
Normal file
9
.cursor/rules/99-lint-markdow.mdc
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
description:
|
||||||
|
globs:
|
||||||
|
alwaysApply: true
|
||||||
|
---
|
||||||
|
|
||||||
|
# Lint
|
||||||
|
|
||||||
|
respecter strictement les règles de lint du markdown
|
14
.markdownlint.json
Normal file
14
.markdownlint.json
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"MD013": {
|
||||||
|
"line_length": 200,
|
||||||
|
"code_blocks": false,
|
||||||
|
"tables": false,
|
||||||
|
"headings": false
|
||||||
|
},
|
||||||
|
"MD007": {
|
||||||
|
"indent": 2
|
||||||
|
},
|
||||||
|
"MD024": {
|
||||||
|
"siblings_only": true
|
||||||
|
}
|
||||||
|
}
|
1
TEMPLATE_VERSION
Normal file
1
TEMPLATE_VERSION
Normal file
@ -0,0 +1 @@
|
|||||||
|
v2025.08.5
|
8
docs/templates/API.md
vendored
Normal file
8
docs/templates/API.md
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
# Référence API — Template
|
||||||
|
|
||||||
|
- Vue d’ensemble
|
||||||
|
- Authentification/permissions
|
||||||
|
- Endpoints par domaine (schémas, invariants)
|
||||||
|
- Codes d’erreur
|
||||||
|
- Limites et quotas
|
||||||
|
- Sécurité et conformité
|
8
docs/templates/ARCHITECTURE.md
vendored
Normal file
8
docs/templates/ARCHITECTURE.md
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
# Architecture — Template
|
||||||
|
|
||||||
|
- Contexte et objectifs
|
||||||
|
- Découpage en couches (UI, services, données)
|
||||||
|
- Flux principaux
|
||||||
|
- Observabilité
|
||||||
|
- CI/CD
|
||||||
|
- Contraintes et SLA
|
6
docs/templates/CONFIGURATION.md
vendored
Normal file
6
docs/templates/CONFIGURATION.md
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
# Configuration — Template
|
||||||
|
|
||||||
|
- Variables d’environnement (nom, type, défaut, portée)
|
||||||
|
- Fichiers de configuration (format, validation)
|
||||||
|
- Réseau et sécurité (ports, TLS, auth)
|
||||||
|
- Observabilité (logs, métriques, traces)
|
12
docs/templates/INDEX.md
vendored
Normal file
12
docs/templates/INDEX.md
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
# Index — Templates de documentation (pour projets dérivés)
|
||||||
|
|
||||||
|
Utilisez ces squelettes pour démarrer la documentation de votre projet.
|
||||||
|
|
||||||
|
- API.md — squelette de référence API
|
||||||
|
- ARCHITECTURE.md — squelette d’architecture
|
||||||
|
- CONFIGURATION.md — squelette de configuration
|
||||||
|
- USAGE.md — squelette d’usage
|
||||||
|
- TESTING.md — squelette de stratégie de tests
|
||||||
|
- SECURITY_AUDIT.md — squelette d’audit sécurité
|
||||||
|
- RELEASE_PLAN.md — squelette de plan de release
|
||||||
|
- OPEN_SOURCE_CHECKLIST.md — squelette de checklist open source
|
7
docs/templates/OPEN_SOURCE_CHECKLIST.md
vendored
Normal file
7
docs/templates/OPEN_SOURCE_CHECKLIST.md
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# Checklist open source — Template
|
||||||
|
|
||||||
|
- Gouvernance: LICENSE, CONTRIBUTING, CODE_OF_CONDUCT
|
||||||
|
- CI/CD: workflows, tests, security-audit, release-guard
|
||||||
|
- Documentation: README, INDEX, guides essentiels
|
||||||
|
- Sécurité: secrets, permissions, audit
|
||||||
|
- Publication: tag, changelog, release notes
|
29
docs/templates/README.md
vendored
Normal file
29
docs/templates/README.md
vendored
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
# README — Template de projet
|
||||||
|
|
||||||
|
## Présentation
|
||||||
|
|
||||||
|
Décrivez brièvement l’objectif du projet, son périmètre et ses utilisateurs cibles.
|
||||||
|
|
||||||
|
## Démarrage rapide
|
||||||
|
|
||||||
|
- Prérequis (langages/outils)
|
||||||
|
- Étapes d’installation
|
||||||
|
- Commandes de démarrage
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
- Index: `docs/INDEX.md`
|
||||||
|
- Architecture: `docs/ARCHITECTURE.md`
|
||||||
|
- Configuration: `docs/CONFIGURATION.md`
|
||||||
|
- Tests: `docs/TESTING.md`
|
||||||
|
- Sécurité: `docs/SECURITY_AUDIT.md`
|
||||||
|
- Déploiement: `docs/DEPLOYMENT.md`
|
||||||
|
|
||||||
|
## Contribution
|
||||||
|
|
||||||
|
- GUIDE: `CONTRIBUTING.md`, `CODE_OF_CONDUCT.md`
|
||||||
|
- Processus de PR et revues
|
||||||
|
|
||||||
|
## Licence
|
||||||
|
|
||||||
|
- Indiquez la licence choisie (MIT/Apache-2.0/GPL)
|
7
docs/templates/RELEASE_PLAN.md
vendored
Normal file
7
docs/templates/RELEASE_PLAN.md
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# Plan de release — Template
|
||||||
|
|
||||||
|
- Vue d’ensemble, objectifs, date cible
|
||||||
|
- Préparation (docs/CI/tests/sécurité)
|
||||||
|
- Communication (annonces, canaux)
|
||||||
|
- Lancement (checklist, tagging)
|
||||||
|
- Post‑lancement (support, retours)
|
7
docs/templates/SECURITY_AUDIT.md
vendored
Normal file
7
docs/templates/SECURITY_AUDIT.md
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# Audit de sécurité — Template
|
||||||
|
|
||||||
|
- Menaces et surfaces d’attaque
|
||||||
|
- Contrôles préventifs et détectifs
|
||||||
|
- Gestion des secrets
|
||||||
|
- Politique de dépendances
|
||||||
|
- Vérifications CI (security-audit)
|
6
docs/templates/TESTING.md
vendored
Normal file
6
docs/templates/TESTING.md
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
# Tests — Template
|
||||||
|
|
||||||
|
- Pyramide: unit, integration, connectivity, external, performance
|
||||||
|
- Structure des répertoires
|
||||||
|
- Exécution et rapports
|
||||||
|
- Intégration CI
|
7
docs/templates/USAGE.md
vendored
Normal file
7
docs/templates/USAGE.md
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# Usage — Template
|
||||||
|
|
||||||
|
- Démarrage quotidien
|
||||||
|
- Opérations courantes
|
||||||
|
- Tests (référence vers TESTING.md)
|
||||||
|
- Sécurité (référence vers SECURITY_AUDIT.md)
|
||||||
|
- Déploiement (référence vers DEPLOYMENT.md)
|
0
scripts/checks/version_alignment.sh
Normal file → Executable file
0
scripts/checks/version_alignment.sh
Normal file → Executable file
145
scripts/deploy/setup.sh
Executable file
145
scripts/deploy/setup.sh
Executable file
@ -0,0 +1,145 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
ENV_DIR="${HOME}/.4nk_template"
|
||||||
|
ENV_FILE="${ENV_DIR}/.env"
|
||||||
|
TEMPLATE_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||||
|
TEMPLATE_IN_REPO="${TEMPLATE_ROOT}/scripts/env/.env.template"
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<USAGE
|
||||||
|
Usage: $0 <git_url> [--dest DIR] [--force]
|
||||||
|
|
||||||
|
Actions:
|
||||||
|
1) Provisionne ~/.4nk_template/.env (si absent)
|
||||||
|
2) Clone le dépôt cible si le dossier n'existe pas
|
||||||
|
3) Copie la structure normative 4NK_template dans le projet cible:
|
||||||
|
- .gitea/** (workflows, templates issues/PR)
|
||||||
|
- AGENTS.md
|
||||||
|
- .cursor/rules/** (si présent)
|
||||||
|
- scripts/agents/**, scripts/env/ensure_env.sh, scripts/deploy/setup.sh
|
||||||
|
- docs/templates/** et docs/INDEX.md (table des matières)
|
||||||
|
4) Ne remplace pas les fichiers existants sauf si --force
|
||||||
|
|
||||||
|
Exemples:
|
||||||
|
$0 https://git.example.com/org/projet.git
|
||||||
|
$0 git@host:org/projet.git --dest ~/work --force
|
||||||
|
USAGE
|
||||||
|
}
|
||||||
|
|
||||||
|
GIT_URL="${1:-}"
|
||||||
|
DEST_PARENT="$(pwd)"
|
||||||
|
FORCE_COPY=0
|
||||||
|
shift || true
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--dest)
|
||||||
|
DEST_PARENT="${2:-}"; shift 2 ;;
|
||||||
|
--force)
|
||||||
|
FORCE_COPY=1; shift ;;
|
||||||
|
-h|--help)
|
||||||
|
usage; exit 0 ;;
|
||||||
|
*)
|
||||||
|
echo "Option inconnue: $1" >&2; usage; exit 2 ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ -z "${GIT_URL}" ]]; then
|
||||||
|
usage; exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "${ENV_DIR}"
|
||||||
|
chmod 700 "${ENV_DIR}" || true
|
||||||
|
|
||||||
|
if [[ ! -f "${ENV_FILE}" ]]; then
|
||||||
|
if [[ -f "${TEMPLATE_IN_REPO}" ]]; then
|
||||||
|
cp "${TEMPLATE_IN_REPO}" "${ENV_FILE}"
|
||||||
|
else
|
||||||
|
cat >"${ENV_FILE}" <<'EOF'
|
||||||
|
# Fichier d'exemple d'environnement pour 4NK_template
|
||||||
|
# Copiez ce fichier vers ~/.4nk_template/.env puis complétez les valeurs.
|
||||||
|
# Ne committez jamais de fichier contenant des secrets.
|
||||||
|
|
||||||
|
# OpenAI (agents IA)
|
||||||
|
OPENAI_API_KEY=
|
||||||
|
OPENAI_MODEL=
|
||||||
|
OPENAI_API_BASE=https://api.openai.com/v1
|
||||||
|
OPENAI_TEMPERATURE=0.2
|
||||||
|
|
||||||
|
# Gitea (release via API)
|
||||||
|
BASE_URL=https://git.4nkweb.com
|
||||||
|
RELEASE_TOKEN=
|
||||||
|
EOF
|
||||||
|
fi
|
||||||
|
chmod 600 "${ENV_FILE}" || true
|
||||||
|
echo "Fichier créé: ${ENV_FILE}. Complétez les valeurs requises (ex: OPENAI_API_KEY, OPENAI_MODEL, RELEASE_TOKEN)." >&2
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 2) Clonage du dépôt si nécessaire
|
||||||
|
repo_name="$(basename -s .git "${GIT_URL}")"
|
||||||
|
target_dir="${DEST_PARENT%/}/${repo_name}"
|
||||||
|
if [[ ! -d "${target_dir}" ]]; then
|
||||||
|
echo "Clonage: ${GIT_URL} → ${target_dir}" >&2
|
||||||
|
git clone --depth 1 "${GIT_URL}" "${target_dir}"
|
||||||
|
else
|
||||||
|
echo "Dossier existant, pas de clone: ${target_dir}" >&2
|
||||||
|
fi
|
||||||
|
|
||||||
|
copy_item() {
|
||||||
|
local src="$1" dst="$2"
|
||||||
|
if [[ ! -e "$src" ]]; then return 0; fi
|
||||||
|
if [[ -d "$src" ]]; then
|
||||||
|
mkdir -p "$dst"
|
||||||
|
if (( FORCE_COPY )); then
|
||||||
|
cp -a "$src/." "$dst/"
|
||||||
|
else
|
||||||
|
(cd "$src" && find . -type f -print0) | while IFS= read -r -d '' f; do
|
||||||
|
if [[ ! -e "$dst/$f" ]]; then
|
||||||
|
mkdir -p "$(dirname "$dst/$f")"
|
||||||
|
cp -a "$src/$f" "$dst/$f"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if [[ -e "$dst" && $FORCE_COPY -eq 0 ]]; then return 0; fi
|
||||||
|
mkdir -p "$(dirname "$dst")" && cp -a "$src" "$dst"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# 3) Copie de la structure normative
|
||||||
|
copy_item "${TEMPLATE_ROOT}/.gitea" "${target_dir}/.gitea"
|
||||||
|
copy_item "${TEMPLATE_ROOT}/AGENTS.md" "${target_dir}/AGENTS.md"
|
||||||
|
copy_item "${TEMPLATE_ROOT}/.cursor" "${target_dir}/.cursor"
|
||||||
|
copy_item "${TEMPLATE_ROOT}/.cursorignore" "${target_dir}/.cursorignore"
|
||||||
|
copy_item "${TEMPLATE_ROOT}/.gitignore" "${target_dir}/.gitignore"
|
||||||
|
copy_item "${TEMPLATE_ROOT}/.markdownlint.json" "${target_dir}/.markdownlint.json"
|
||||||
|
copy_item "${TEMPLATE_ROOT}/LICENSE" "${target_dir}/LICENSE"
|
||||||
|
copy_item "${TEMPLATE_ROOT}/CONTRIBUTING.md" "${target_dir}/CONTRIBUTING.md"
|
||||||
|
copy_item "${TEMPLATE_ROOT}/CODE_OF_CONDUCT.md" "${target_dir}/CODE_OF_CONDUCT.md"
|
||||||
|
copy_item "${TEMPLATE_ROOT}/SECURITY.md" "${target_dir}/SECURITY.md"
|
||||||
|
copy_item "${TEMPLATE_ROOT}/TEMPLATE_VERSION" "${target_dir}/TEMPLATE_VERSION"
|
||||||
|
copy_item "${TEMPLATE_ROOT}/security" "${target_dir}/security"
|
||||||
|
copy_item "${TEMPLATE_ROOT}/scripts" "${target_dir}/scripts"
|
||||||
|
copy_item "${TEMPLATE_ROOT}/docs/templates" "${target_dir}/docs/templates"
|
||||||
|
|
||||||
|
# Génération docs/INDEX.md dans le projet cible (si absent ou --force)
|
||||||
|
INDEX_DST="${target_dir}/docs/INDEX.md"
|
||||||
|
if [[ ! -f "${INDEX_DST}" || $FORCE_COPY -eq 1 ]]; then
|
||||||
|
mkdir -p "$(dirname "${INDEX_DST}")"
|
||||||
|
cat >"${INDEX_DST}" <<'IDX'
|
||||||
|
# Documentation du projet
|
||||||
|
|
||||||
|
Cette table des matières oriente vers:
|
||||||
|
- Documentation spécifique au projet: `docs/project/`
|
||||||
|
- Modèles génériques à adapter: `docs/templates/`
|
||||||
|
|
||||||
|
## Sommaire
|
||||||
|
- À personnaliser: `docs/project/README.md`, `docs/project/INDEX.md`, `docs/project/ARCHITECTURE.md`, `docs/project/USAGE.md`, etc.
|
||||||
|
|
||||||
|
## Modèles génériques
|
||||||
|
- Voir: `docs/templates/`
|
||||||
|
IDX
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Template 4NK appliqué à: ${target_dir}" >&2
|
||||||
|
exit 0
|
0
scripts/deploy_first_install_no_certs.sh
Normal file → Executable file
0
scripts/deploy_first_install_no_certs.sh
Normal file → Executable file
0
scripts/deploy_first_install_with_certs.sh
Normal file → Executable file
0
scripts/deploy_first_install_with_certs.sh
Normal file → Executable file
15
scripts/dev/run_container.sh
Executable file
15
scripts/dev/run_container.sh
Executable file
@ -0,0 +1,15 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
IMAGE_NAME="4nk-template-dev:debian"
|
||||||
|
DOCKERFILE="docker/Dockerfile.debian"
|
||||||
|
|
||||||
|
echo "[build] ${IMAGE_NAME}"
|
||||||
|
docker build -t "${IMAGE_NAME}" -f "${DOCKERFILE}" .
|
||||||
|
|
||||||
|
echo "[run] launching container and executing agents"
|
||||||
|
docker run --rm -it \
|
||||||
|
-v "${PWD}:/work" -w /work \
|
||||||
|
"${IMAGE_NAME}" \
|
||||||
|
"scripts/agents/run.sh; ls -la tests/reports/agents || true"
|
||||||
|
|
14
scripts/dev/run_project_ci.sh
Executable file
14
scripts/dev/run_project_ci.sh
Executable file
@ -0,0 +1,14 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Build et lance le conteneur unifié (runner+agents) sur ce projet
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
ROOT_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||||
|
cd "$ROOT_DIR"
|
||||||
|
|
||||||
|
# Build image
|
||||||
|
docker compose -f docker-compose.ci.yml build
|
||||||
|
|
||||||
|
# Exécuter agents par défaut
|
||||||
|
RUNNER_MODE="${RUNNER_MODE:-agents}" BASE_URL="${BASE_URL:-}" REGISTRATION_TOKEN="${REGISTRATION_TOKEN:-}" \
|
||||||
|
docker compose -f docker-compose.ci.yml up --remove-orphans --abort-on-container-exit
|
42
scripts/env/ensure_env.sh
vendored
Executable file
42
scripts/env/ensure_env.sh
vendored
Executable file
@ -0,0 +1,42 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||||
|
TEMPLATE_FILE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/.env.template"
|
||||||
|
ENV_DIR="${HOME}/.4nk_template"
|
||||||
|
ENV_FILE="${ENV_DIR}/.env"
|
||||||
|
|
||||||
|
mkdir -p "${ENV_DIR}"
|
||||||
|
chmod 700 "${ENV_DIR}" || true
|
||||||
|
|
||||||
|
if [[ ! -f "${ENV_FILE}" ]]; then
|
||||||
|
if [[ -f "${TEMPLATE_FILE}" ]]; then
|
||||||
|
cp "${TEMPLATE_FILE}" "${ENV_FILE}"
|
||||||
|
chmod 600 "${ENV_FILE}" || true
|
||||||
|
echo "Fichier d'environnement créé: ${ENV_FILE}" >&2
|
||||||
|
echo "Veuillez renseigner les variables requises (OPENAI_API_KEY, OPENAI_MODEL, etc.)." >&2
|
||||||
|
exit 3
|
||||||
|
else
|
||||||
|
echo "Modèle d'environnement introuvable: ${TEMPLATE_FILE}" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Charger pour validation
|
||||||
|
set -a
|
||||||
|
. "${ENV_FILE}"
|
||||||
|
set +a
|
||||||
|
|
||||||
|
MISSING=()
|
||||||
|
for var in OPENAI_API_KEY OPENAI_MODEL; do
|
||||||
|
if [[ -z "${!var:-}" ]]; then
|
||||||
|
MISSING+=("$var")
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if (( ${#MISSING[@]} > 0 )); then
|
||||||
|
echo "Variables manquantes dans ${ENV_FILE}: ${MISSING[*]}" >&2
|
||||||
|
exit 4
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Environnement valide: ${ENV_FILE}" >&2
|
0
scripts/release/guard.sh
Normal file → Executable file
0
scripts/release/guard.sh
Normal file → Executable file
0
scripts/scripts/auto-ssh-push.sh
Normal file → Executable file
0
scripts/scripts/auto-ssh-push.sh
Normal file → Executable file
0
scripts/scripts/init-ssh-env.sh
Normal file → Executable file
0
scripts/scripts/init-ssh-env.sh
Normal file → Executable file
0
scripts/scripts/setup-ssh-ci.sh
Normal file → Executable file
0
scripts/scripts/setup-ssh-ci.sh
Normal file → Executable file
0
scripts/security/audit.sh
Normal file → Executable file
0
scripts/security/audit.sh
Normal file → Executable file
47
scripts/utils/check_md024.ps1
Normal file
47
scripts/utils/check_md024.ps1
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
Param(
|
||||||
|
[string]$Root = "."
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
|
||||||
|
$files = Get-ChildItem -Path $Root -Recurse -Filter *.md | Where-Object { $_.FullName -notmatch '\\archive\\' }
|
||||||
|
$had = $false
|
||||||
|
foreach ($f in $files) {
|
||||||
|
try {
|
||||||
|
$lines = Get-Content -LiteralPath $f.FullName -Encoding UTF8 -ErrorAction Stop
|
||||||
|
} catch {
|
||||||
|
Write-Warning ("Impossible de lire: {0} — {1}" -f $f.FullName, $_.Exception.Message)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
$map = @{}
|
||||||
|
$firstMap = @{}
|
||||||
|
$dups = @{}
|
||||||
|
for ($i = 0; $i -lt $lines.Count; $i++) {
|
||||||
|
$line = $lines[$i]
|
||||||
|
if ($line -match '^\s{0,3}#{1,6}\s+(.*)$') {
|
||||||
|
$t = $Matches[1].Trim()
|
||||||
|
$norm = ([regex]::Replace($t, '\s+', ' ')).ToLowerInvariant()
|
||||||
|
if ($map.ContainsKey($norm)) {
|
||||||
|
if (-not $dups.ContainsKey($norm)) {
|
||||||
|
$dups[$norm] = New-Object System.Collections.ArrayList
|
||||||
|
$firstMap[$norm] = $map[$norm]
|
||||||
|
}
|
||||||
|
[void]$dups[$norm].Add($i + 1)
|
||||||
|
} else {
|
||||||
|
$map[$norm] = $i + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($dups.Keys.Count -gt 0) {
|
||||||
|
$had = $true
|
||||||
|
Write-Output "=== $($f.FullName) ==="
|
||||||
|
foreach ($k in $dups.Keys) {
|
||||||
|
$first = $firstMap[$k]
|
||||||
|
$others = ($dups[$k] -join ', ')
|
||||||
|
Write-Output ("Heading: '{0}' first@{1} duplicates@[{2}]" -f $k, $first, $others)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (-not $had) {
|
||||||
|
Write-Output "No duplicate headings detected."
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user