{"_id":"@astratra/tooling","_rev":"9-f29b34cd44e5e12a02ea518a8d984de7","name":"@astratra/tooling","dist-tags":{"latest":"1.5.0"},"versions":{"0.1.0":{"name":"@astratra/tooling","version":"0.1.0","keywords":["astratra","tooling","cli"],"license":"MIT","_id":"@astratra/tooling@0.1.0","maintainers":[{"name":"emch99","email":"emchkongo@gmail.com"}],"bin":{"astratra":"bin/astratra.js"},"dist":{"shasum":"0558d75d2ceb8a20f163d72565654b5a9f890e29","tarball":"https://registry.npmjs.org/@astratra/tooling/-/tooling-0.1.0.tgz","fileCount":17,"integrity":"sha512-gziYrC3QUgns7c2+s30DO+nyn6S29QN1gZAEdkMRSjdpsDShysAzzc1ZyH4dhTUOcWKU/QvqnGOZ9+zntuv5PQ==","signatures":[{"sig":"MEYCIQCsx5ZrGQmCIuCNCFXX+un4jJP6H6VakYtZZwwXo5XPBQIhAN9qu4bPBv1/Hb0FVmneeh77cYVjJa8OWmmKrU9S8U8a","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":31329},"jest":{"testMatch":["**/__tests__/**/*.test.js"]},"main":"src/index.js","type":"commonjs","types":"src/index.d.ts","gitHead":"1e576a2e512d525705894c42f7269882b6a09a7f","scripts":{"test":"jest"},"_npmUser":{"name":"emch99","email":"emchkongo@gmail.com"},"_npmVersion":"11.5.1","description":"Generic audit, test, and deploy tooling for Astratra workspaces.","directories":{},"_nodeVersion":"22.18.0","dependencies":{"@astratra/core":"^0.1.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"jest":"30.4.2"},"_npmOperationalInternal":{"tmp":"tmp/tooling_0.1.0_1786200642185_0.8726867954687274","host":"s3://npm-registry-packages-npm-production"}},"1.0.0":{"name":"@astratra/tooling","version":"1.0.0","keywords":["astratra","tooling","cli"],"license":"MIT","_id":"@astratra/tooling@1.0.0","maintainers":[{"name":"emch99","email":"emchkongo@gmail.com"}],"bin":{"astratra":"bin/astratra.js"},"dist":{"shasum":"7cae71bd0957d1b25ecb59f0c1d94e9747f38f15","tarball":"https://registry.npmjs.org/@astratra/tooling/-/tooling-1.0.0.tgz","fileCount":17,"integrity":"sha512-ggaVGsN82+mKI/YzzDzCBHQXLRWjfLTueaJh50EYB+aAZaqusjQ4+MPa/yWStYexU4ajB53ypXQoNFcKeQQLgA==","signatures":[{"sig":"MEUCIQDiaMTikc1uMTyGw7idn+o7DAsTNOUvXQ3thlA5z1EHqQIgdNw4pRJUmQPYMh7yPnm6vNgncHvTg8UxP86KTyd5Tz8=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":31246},"jest":{"testMatch":["**/__tests__/**/*.test.js"]},"main":"src/index.js","type":"commonjs","types":"src/index.d.ts","gitHead":"e4ba5847cd3342fd9398c867592b762f852a8ef8","scripts":{"test":"jest"},"_npmUser":{"name":"emch99","email":"emchkongo@gmail.com"},"_npmVersion":"11.13.0","description":"Generic audit, test, and deploy tooling for Astratra workspaces.","directories":{},"_nodeVersion":"24.16.0","dependencies":{"@astratra/core":"^1.0.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"jest":"30.4.2"},"_npmOperationalInternal":{"tmp":"tmp/tooling_1.0.0_1786210195070_0.8053242234625748","host":"s3://npm-registry-packages-npm-production"}},"1.0.1":{"name":"@astratra/tooling","version":"1.0.1","keywords":["astratra","tooling","cli"],"license":"MIT","_id":"@astratra/tooling@1.0.1","maintainers":[{"name":"emch99","email":"emchkongo@gmail.com"}],"bin":{"astratra":"bin/astratra.js"},"dist":{"shasum":"975ea6daeccd0aeb51e89b179122c5f7ddb98f99","tarball":"https://registry.npmjs.org/@astratra/tooling/-/tooling-1.0.1.tgz","fileCount":17,"integrity":"sha512-0pRy+vPVvOiNkHpulSo6RDCwOG5MrZoDZgjUHbgi5Hhs0+BH3p3YSLbGhqO/yUXBl3ybKzrzYID4uns160W96Q==","signatures":[{"sig":"MEQCID4jdnu8K3d+RT0wldi/CKgmjCe2CrsL9qOZszaprnxrAiA+6Dyaq2cRt7EjkjMkcnmjqJfmm4Pk6NsTa4P67nR+ZA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":31285},"jest":{"testMatch":["**/__tests__/**/*.test.js"]},"main":"src/index.js","type":"commonjs","types":"src/index.d.ts","engines":{"node":">=20"},"gitHead":"c0f1cf78836764168bbbeabdebb1574f2ec68543","scripts":{"test":"jest"},"_npmUser":{"name":"emch99","email":"emchkongo@gmail.com"},"_npmVersion":"11.13.0","description":"Generic audit, test, and deploy tooling for Astratra workspaces.","directories":{},"_nodeVersion":"24.16.0","dependencies":{"@astratra/core":"^1.0.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"jest":"30.4.2"},"_npmOperationalInternal":{"tmp":"tmp/tooling_1.0.1_1786297919654_0.3242915068289338","host":"s3://npm-registry-packages-npm-production"}},"1.1.0":{"name":"@astratra/tooling","version":"1.1.0","keywords":["astratra","tooling","cli"],"license":"MIT","_id":"@astratra/tooling@1.1.0","maintainers":[{"name":"emch99","email":"emchkongo@gmail.com"}],"bin":{"astratra":"bin/astratra.js"},"dist":{"shasum":"48be221fd4c9a4b7c7ae8197c9d270e6a126f68b","tarball":"https://registry.npmjs.org/@astratra/tooling/-/tooling-1.1.0.tgz","fileCount":18,"integrity":"sha512-yFjHgdNYa4SPBIgyI68Xqd2hFHaAaYptGD3Nb1PJPjTxeMUh9ithuZsta0YMGRzssAf/tUEv5WhiWlZ6PqAsYw==","signatures":[{"sig":"MEUCIHkmYIBjbJaVb+7Uha+yfW32SuOybRFSq6AOVMRLvNikAiEA7EGWOYjfqvaRiGxac4sxWOhNaVVsDzffPbjzUk0hZHo=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":36805},"jest":{"testMatch":["**/__tests__/**/*.test.js"]},"main":"src/index.js","type":"commonjs","types":"src/index.d.ts","engines":{"node":">=20"},"gitHead":"80411079da8516d644f2a47e5438e7a6d3678893","scripts":{"test":"jest"},"_npmUser":{"name":"emch99","email":"emchkongo@gmail.com"},"_npmVersion":"11.13.0","description":"Generic audit, test, and deploy tooling for Astratra workspaces.","directories":{},"_nodeVersion":"24.16.0","dependencies":{"@astratra/core":"^1.0.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"jest":"30.4.2"},"_npmOperationalInternal":{"tmp":"tmp/tooling_1.1.0_1786753013521_0.9840079629450336","host":"s3://npm-registry-packages-npm-production"}},"1.2.0":{"name":"@astratra/tooling","version":"1.2.0","keywords":["astratra","tooling","cli"],"license":"MIT","_id":"@astratra/tooling@1.2.0","maintainers":[{"name":"emch99","email":"emchkongo@gmail.com"}],"bin":{"astratra":"bin/astratra.js"},"dist":{"shasum":"29f783264dec513a082a8e8f94d64cb4a37e3409","tarball":"https://registry.npmjs.org/@astratra/tooling/-/tooling-1.2.0.tgz","fileCount":22,"integrity":"sha512-vT9/KbU6RaMhIOx+a3jTEk6t0onRhSjDbuzUJ+6jdV9BObCZ1I/co+kXhcRpYd2z/oTLu9xFINkBSkkARg4D7g==","signatures":[{"sig":"MEQCIG6jitQMJ7unWmXiBJWofREdRMfbEzPYaeKKWzFxnRMZAiB8tuARee9prpLC2JTssRsLs2/E9wYBMLrlxpvwwLVBuA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEUCIGguHIpwzc4gb5cjNQ1Lyf3KK3d2Z7ETZnDgQA3LMp/3AiEA7LXXqDz3ZsNsipUAtnGPAzNSC5w7vdW8oW/Q8h19VVo=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":73623},"jest":{"testMatch":["**/__tests__/**/*.test.js"]},"main":"src/index.js","type":"commonjs","types":"src/index.d.ts","engines":{"node":">=20"},"gitHead":"a6eda5bf009d1d47caff00955940c6d2303bee2c","scripts":{"test":"jest"},"_npmUser":{"name":"emch99","email":"emchkongo@gmail.com"},"_npmVersion":"11.13.0","description":"Generic audit, test, and deploy tooling for Astratra workspaces.","directories":{},"_nodeVersion":"24.16.0","dependencies":{"@astratra/core":"^1.0.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"jest":"30.4.2"},"_npmOperationalInternal":{"tmp":"tmp/tooling_1.2.0_1789991511791_0.5909785370266647","host":"s3://npm-registry-packages-npm-production"}},"1.3.0":{"name":"@astratra/tooling","version":"1.3.0","keywords":["astratra","tooling","cli","deploy","expo","google-play","app-store-connect"],"license":"MIT","_id":"@astratra/tooling@1.3.0","maintainers":[{"name":"emch99","email":"emchkongo@gmail.com"}],"bin":{"astratra":"bin/astratra.js"},"dist":{"shasum":"825f1b4b324cb38ae3fc05196aaa6d21a1473f98","tarball":"https://registry.npmjs.org/@astratra/tooling/-/tooling-1.3.0.tgz","fileCount":39,"integrity":"sha512-GDEHv2s2/7u2Tzr400PmNxWNIl/qFIuFMYEFSJgX4hk5Orc9NSKFjE0UbcACl44LEk4B9kg81VC/3dLZboAhdw==","signatures":[{"sig":"MEUCIQCs18saxeK6ZwWpFfYBLgIzCHENe2vhroHEGq1sQYIjjAIgefW+byutrjUVhLAi51cRpQ/w/WFr1Rn/B8EYIR1rMGY=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEUCIBBSrZAZorbIKkm9FDII8+x6673TYVNqbdZVw/QdBqnUAiEA8vcq9L6/ON61+WshB2q2Tj0jWIBwXCQ+fOMaWuYV8D0=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":197747},"jest":{"testMatch":["**/__tests__/**/*.test.js"]},"main":"src/index.js","type":"commonjs","types":"src/index.d.ts","engines":{"node":">=20"},"gitHead":"fa5ec86eac3e65bcf1d4599f8af962dc57751cbd","scripts":{"test":"jest"},"_npmUser":{"name":"emch99","email":"emchkongo@gmail.com"},"_npmVersion":"11.13.0","description":"Generic audit, test, deploy and app-publishing tooling for Astratra workspaces.","directories":{},"_nodeVersion":"24.16.0","dependencies":{"@astratra/core":"^1.0.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"jest":"30.4.2"},"peerDependencies":{"@expo/fingerprint":">=0.10.0"},"peerDependenciesMeta":{"@expo/fingerprint":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/tooling_1.3.0_1790628175263_0.43853647450539013","host":"s3://npm-registry-packages-npm-production"}},"1.3.1":{"name":"@astratra/tooling","version":"1.3.1","keywords":["astratra","tooling","cli","deploy","expo","google-play","app-store-connect"],"license":"MIT","_id":"@astratra/tooling@1.3.1","maintainers":[{"name":"emch99","email":"emchkongo@gmail.com"}],"bin":{"astratra":"bin/astratra.js"},"dist":{"shasum":"c96cdb12c5ab29f21a9f4f6ca6ca7f067ebf60f6","tarball":"https://registry.npmjs.org/@astratra/tooling/-/tooling-1.3.1.tgz","fileCount":39,"integrity":"sha512-vPk2wrMamZLF4+2SD9fF+0/Yh1VfmbDnzJIsvne+YWzRy6kAcRPYyaeJrYTagw28iBrZrOxZafNXzuek+xz2+Q==","signatures":[{"sig":"MEUCIDO/owxCEh3kLDjqy5CJe9xaimVupMQSiazXGvZ366g4AiEArIa1/o+LEMLs3GIC4iAksV8zr50GVU4KJRfAokTxgU8=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEUCIQDmNA5dmUQKaTsCELP2FM4kssFYHBrmla/UyxJG6AgpeQIgRoH3zRYgJccRhZ4agLE4V3L3IN9l7YUMjJXs5YsEgoY=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":201140},"jest":{"testMatch":["**/__tests__/**/*.test.js"]},"main":"src/index.js","type":"commonjs","types":"src/index.d.ts","engines":{"node":">=20"},"gitHead":"fa5ec86eac3e65bcf1d4599f8af962dc57751cbd","scripts":{"test":"jest"},"_npmUser":{"name":"emch99","email":"emchkongo@gmail.com"},"_npmVersion":"11.13.0","description":"Generic audit, test, deploy and app-publishing tooling for Astratra workspaces.","directories":{},"_nodeVersion":"24.16.0","dependencies":{"@astratra/core":"^1.0.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"jest":"30.4.2"},"peerDependencies":{"@expo/fingerprint":">=0.10.0"},"peerDependenciesMeta":{"@expo/fingerprint":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/tooling_1.3.1_1790638450325_0.8502002756025053","host":"s3://npm-registry-packages-npm-production"}},"1.4.0":{"name":"@astratra/tooling","version":"1.4.0","keywords":["astratra","tooling","cli","deploy","expo","google-play","app-store-connect"],"license":"MIT","_id":"@astratra/tooling@1.4.0","maintainers":[{"name":"emch99","email":"emchkongo@gmail.com"}],"bin":{"astratra":"bin/astratra.js"},"dist":{"shasum":"43488a596f6ae2638b33bcc9dfd4e92796a301c9","tarball":"https://registry.npmjs.org/@astratra/tooling/-/tooling-1.4.0.tgz","fileCount":41,"integrity":"sha512-pdqgelBhyojf7CRKCcTBdXzwPw2A5wpZp9mvS3jX967CPJ6gLjOIvFHoJmLdjDBvolZEUzS+jEYIBeXz5x0lIA==","signatures":[{"sig":"MEUCIQDLE9V8aEA0D7IxZ0FEQOVXt4ScCEfK6Zmcw1EKGxFw4AIgFZMAhcQxwl1EkXskOHxs3S2UUBXeiG2AkBWNijaVbM4=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEUCIDGY3e6LbfyjK3K3IODxG1U7gDZ3qTuErYumRA3x943lAiEAyuMBQnHLzdGssWtjuo38BG6tqk0b9qGRcK+bqEmKZ/E=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":219695},"jest":{"testMatch":["**/__tests__/**/*.test.js"]},"main":"src/index.js","type":"commonjs","types":"src/index.d.ts","engines":{"node":">=20"},"gitHead":"28e680aaed8dfdf7c5aa00c7dfbde74ab8e377de","scripts":{"test":"jest"},"_npmUser":{"name":"emch99","email":"emchkongo@gmail.com"},"_npmVersion":"11.13.0","description":"Generic audit, test, deploy and app-publishing tooling for Astratra workspaces.","directories":{},"_nodeVersion":"24.16.0","dependencies":{"@astratra/core":"^1.0.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"jest":"30.4.2","promptfoo":"^0.123.1"},"peerDependencies":{"promptfoo":">=0.120.0","@expo/fingerprint":">=0.10.0"},"peerDependenciesMeta":{"promptfoo":{"optional":true},"@expo/fingerprint":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/tooling_1.4.0_1790882403445_0.2963068393792252","host":"s3://npm-registry-packages-npm-production"}},"1.5.0":{"_id":"@astratra/tooling@1.5.0","bin":{"astratra":"bin/astratra.js"},"dist":{"shasum":"b2bd47f7e94affd0297b003331b4e3058a804d39","tarball":"https://registry.npmjs.org/@astratra/tooling/-/tooling-1.5.0.tgz","fileCount":42,"integrity":"sha512-RnlPWmgVGEcSge5e1iPuL4KM5TR25JSitkG3Ios3eMGg29X0YB3pKQPnfZCwso2Xzl4DEzGKgRCNVU9lsAlRDA==","signatures":[{"sig":"MEYCIQDtU0HGWMC/wlDa1HiaNUIfMVHmi4AjwDQHPlsJP/XPgQIhAOW8MS/WVqoJ+oV92YM1Xry+1JbRcGAkhD0d2Tad4mbL","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIHCJEVuIOjMhfP+zPCHH2nhVayKabMdFqqIKHKTr0sMkAiBeHwF9zSxHBfOeAIc72Hpx2RzZO+qaekQJsnl4AGEi8Q=="}],"unpackedSize":226660},"jest":{"testMatch":["**/__tests__/**/*.test.js"]},"main":"src/index.js","name":"@astratra/tooling","type":"commonjs","types":"src/index.d.ts","engines":{"node":">=20"},"gitHead":"d59304afb95019abf41f8e3a1e7f9ff74034db53","license":"MIT","scripts":{"test":"jest"},"version":"1.5.0","_npmUser":{"name":"emch99","email":"emchkongo@gmail.com"},"keywords":["astratra","tooling","cli","deploy","expo","google-play","app-store-connect"],"_npmVersion":"11.13.0","description":"Generic audit, test, deploy and app-publishing tooling for Astratra workspaces.","directories":{},"maintainers":[{"name":"emch99","email":"emchkongo@gmail.com"}],"_nodeVersion":"24.16.0","dependencies":{"@astratra/core":"^1.0.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"jest":"30.4.2","promptfoo":"^0.123.1"},"peerDependencies":{"promptfoo":">=0.120.0","@expo/fingerprint":">=0.10.0"},"peerDependenciesMeta":{"promptfoo":{"optional":true},"@expo/fingerprint":{"optional":true}},"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/tooling_1.5.0_1790954166714_0.713706077987909"}}},"time":{"created":"2026-08-08T14:50:41.990Z","modified":"2026-10-02T15:16:07.049Z","0.1.0":"2026-08-08T14:50:42.331Z","1.0.0":"2026-08-08T17:29:55.214Z","1.0.1":"2026-08-09T17:51:59.816Z","1.1.0":"2026-08-15T00:16:53.671Z","1.2.0":"2026-09-21T11:51:51.887Z","1.3.0":"2026-09-28T20:42:55.373Z","1.3.1":"2026-09-28T23:34:10.412Z","1.4.0":"2026-10-01T19:20:03.548Z","1.5.0":"2026-10-02T15:16:06.851Z"},"license":"MIT","keywords":["astratra","tooling","cli","deploy","expo","google-play","app-store-connect"],"description":"Generic audit, test, deploy and app-publishing tooling for Astratra workspaces.","maintainers":[{"name":"emch99","email":"emchkongo@gmail.com"}],"readme":"# @astratra/tooling\n\nCLI pour l'audit et les tâches génériques de maintenance de projet. Fournit\nun binaire `astratra`. Dépend de `@astratra/core`.\n\nRien dans ce package ne connaît la structure de dossiers, les noms de rôles\nou la cible de déploiement d'un projet précis — chaque chemin et pattern a\nune valeur par défaut neutre, surchargeable via `astratra.config.json` (ou\n`.js`) à la racine du projet consommateur.\n\n## Commandes\n\n```bash\nastratra audit:secrets [--dir=<path>]        # détecte les secrets littéraux loggés ou renvoyés dans des réponses API\nastratra audit:routes  [--dir=<path>]        # détecte les routes Express *.routes.js sans middleware d'auth apparent\nastratra audit:i18n    [--dir=<path>]        # détecte les incohérences de clés de traduction entre langues\nastratra audit:deps    [--severity=<level>]  # relaie \"npm audit\" et échoue si une dépendance a une CVE >= seuil\nastratra test                                # lance le script 'test' de chaque workspace et agrège le résultat\nastratra deploy [--mode=<name>]              # exécute les étapes de déploiement définies dans votre propre config\nastratra deploy --remote  (ou deploy:remote)  # déploiement git complet vers un serveur (voir « Déploiement distant »)\nastratra deploy:health                       # santé du serveur : URLs publiques et internes, pm2, âge de la dernière sauvegarde\nastratra publish <ios|android|all|update>    # build EAS (chez Expo ou sur la machine), montée de version auto, envoi Google Play / App Store Connect\nastratra publish:fingerprint [--record]      # compare l'empreinte native à la dernière publiée\nastratra publish:upload --platform=<p> --file=<archive>   # envoie un .aab/.ipa déjà construit\nastratra publish:check-ios                   # vérifie la clé App Store Connect (un appel signé, rien de construit)\nastratra dispatch:generate [--out=<file>] [--key=<file.pub>]   # script de commande forcée SSH + ligne authorized_keys\nastratra eval --provider=<id> [--cases=<file>] [--base-url=<url>] [--api-key-env=<VAR>] [--min-pass=<0..1>]   # évalue un modèle sur un jeu de cas (promptfoo)\n```\n\nChaque commande retourne un exit code non-zéro en cas de findings/échecs —\nutilisable directement en CI.\n\n`audit:deps` ne réimplémente rien : il lance `npm audit --json` dans le\nprojet consommateur et filtre le rapport par sévérité (`info` < `low` <\n`moderate` < `high` < `critical`, seuil par défaut `moderate`, l'échelle de\nnpm elle-même). Ce que ça ajoute par rapport à un simple `npm audit` : un\nexit code homogène avec les autres commandes `audit:*` pour la CI, et un\nseuil configurable au lieu du tout-ou-rien de npm.\n\n## Configuration (`astratra.config.json`)\n\n```json\n{\n  \"audit\": {\n    \"secrets\": { \"dirs\": [\"src\"] },\n    \"routes\": {\n      \"dirs\": [\"src\"],\n      \"authMiddlewarePatterns\": [\"authMiddleware\", \"authorizeRoles\"],\n      \"publicMarkers\": [\"public\", \"webhook\", \"health\"]\n    },\n    \"i18n\": { \"localesDir\": \"locales\", \"sourceDirs\": [\"src\"] },\n    \"deps\": { \"severityThreshold\": \"moderate\" }\n  },\n  \"test\": { \"workspaces\": null },\n  \"deploy\": {\n    \"steps\": [\"npm run build\", \"npm test\"],\n    \"modes\": { \"fast\": { \"skip\": [\"step-2\"] } }\n  }\n}\n```\n\n`deploy` sans option exécute les commandes shell que vous listez, dans\nl'ordre, et s'arrête au premier échec. Une étape peut porter son propre `cwd`,\nun `env` et un `logFile` (sortie longue envoyée dans un fichier).\n\n## Déploiement distant (`deploy --remote`)\n\n`--dry-run` affiche la configuration effective sans prendre de verrou, lancer\nles tests, pousser, ouvrir SSH ni sonder la production. Le marqueur distant\nvaut `@@astratra` par défaut ; `deploy.remote.marker` permet par exemple\n`@@tertius`, et `showMarkers: true` les affiche aussi dans le terminal. Le\ncommit poussé est vérifié avec `git ls-remote`, et un code SSH\nnon nul reste un échec même si le serveur a imprimé `result=deployed`.\n\nAucun hôte, utilisateur, port ni nom pm2 n'est écrit dans le package : tout\nvient de `deploy.remote`.\n\n```json\n{\n  \"deploy\": {\n    \"remote\": {\n      \"host\": \"mon-vps\",\n      \"appUser\": \"app\", \"appDir\": \"/home/app/app\", \"nodeDir\": \"/home/app/node\",\n      \"branch\": \"main\",\n      \"preSteps\": [{ \"name\": \"tests serveur\", \"command\": \"npm test\", \"cwd\": \"apps/server\", \"logFile\": \".deploy/tests.log\" }],\n      \"depsPattern\": \"^(package-lock\\\\.json|apps/server/package\\\\.json)$\",\n      \"installCommand\": \"npm ci --omit=dev --workspace server --include-workspace-root=false\",\n      \"pm2\": { \"ecosystem\": \"deploy/ecosystem.config.cjs\" },\n      \"health\": {\n        \"internal\": [\"http://127.0.0.1:3100/health\", \"http://127.0.0.1:3101/health\"],\n        \"public\": [\"https://api.example.com/health\"]\n      },\n      \"allowTrackedFiles\": []\n    }\n  }\n}\n```\n\nDans l'ordre : verrou (un seul déploiement à la fois ; le verrou d'un\nprocessus mort est repris), refus d'un arbre non commité, refus d'une autre\nbranche que `branch`, refus d'un fichier secret suivi par git (`.env`,\n`.env.*` sauf `.example/.sample/.template`, clés `.p8/.pem/.p12/.jks/.keystore`,\n`id_rsa`…, JSON de compte de service — `secretPatterns` remplace la liste,\n`allowTrackedFiles` excepte un chemin précis), `preSteps` (les tests), `git\npush`, vérification que `HEAD` est bien `remote/branch`, puis sur le serveur\n(`ssh host bash -s`, script sur l'entrée standard, valeurs en arguments\nquotés) : `git fetch` + `reset --hard <commit>`, installation seulement si un\nfichier de dépendances a bougé, rechargement, santé interne avec relances, et\n**retour au commit précédent** sur toute panne (installation, rechargement ou\nsanté), dépendances réinstallées si besoin. Enfin la santé publique, depuis la\nmachine locale. Codes : `DEPLOY_DIRTY_TREE`, `DEPLOY_WRONG_BRANCH`,\n`DEPLOY_SECRET_TRACKED`, `DEPLOY_PRESTEP_FAILED`, `DEPLOY_PUSH_FAILED`,\n`DEPLOY_TARGET_NOT_PUSHED`, `DEPLOY_ROLLED_BACK`, `DEPLOY_ROLLBACK_FAILED`,\n`DEPLOY_REMOTE_FAILED`, `DEPLOY_SSH_FAILED`, `DEPLOY_PUBLIC_UNHEALTHY`,\n`DEPLOY_LOCKED`.\n\n`deploy:health` lit la même section, plus `status` :\n`{ \"pm2Apps\": [\"api\", \"ai\"], \"backupLog\": \"/home/app/backup.log\", \"backupPattern\": \"backup sent\", \"backupMaxAgeDays\": 1 }`.\n\n## Publication mobile (`publish`)\n\n`publish ios|android|all|update --dry-run` affiche le projet, la cible et les\nchoix d'envoi sans lire les clés ni modifier les versions, ni contacter Expo ou\nles stores. `eas.maxViewFailures` règle le nombre d'échecs consécutifs admis\npour la lecture du statut (5 par défaut). Un fichier Apple présent mais\ninvalide fait échouer la publication ; seul l'absence de clé déclenche le\nrepli Transporter. Un transfert manuel ne marque pas l'empreinte comme publiée :\naprès livraison effective sur le store, utiliser `publish:fingerprint --record`.\nLorsque `versionBump` vaut `false`, relever soi-même la version de l'app avant\nun build avec changement natif. `publish.version` peut fournir la version Expo\neffective quand elle est déclarée ailleurs que dans `package.json`.\n\n```json\n{\n  \"publish\": {\n    \"appName\": \"MonApp\",\n    \"projectDir\": \"apps/mobile\",\n    \"fingerprintFile\": \"scripts/.empreinte-native-publiee\",\n    \"versionBump\": \"patch\",\n    \"eas\": { \"profile\": \"production\", \"channel\": \"production\" },\n    \"downloadsDir\": \"~/Downloads\",\n    \"android\": { \"packageName\": \"com.exemple.app\", \"track\": \"internal\", \"serviceAccountPath\": \"play-service-account.json\" },\n    \"ios\": { \"ascEnvFile\": \"~/.appstoreconnect/monapp.env\", \"bundleId\": \"com.exemple.app\", \"fallback\": \"transporter\" }\n  }\n}\n```\n\n- **Version automatique** : l'empreinte native (`@expo/fingerprint`, pair\n  optionnel, ou `fingerprint.command`) est comparée à celle du dernier build\n  publié ; si elle diffère, `package.json` (et `package-lock.json`) montent\n  d'un cran. À utiliser avec `runtimeVersion: { policy: \"appVersion\" }` —\n  un avertissement s'affiche sinon.\n- **Android** : JWT RS256 signé avec `node:crypto`, puis édition Google Play :\n  création → envoi du `.aab` → piste → validation ; une édition ouverte avant\n  une panne est supprimée. Codes `PLAY_KEY_MISSING`, `PLAY_KEY_INVALID`,\n  `PLAY_TOKEN_REFUSED`, `PLAY_EDIT_FAILED`, `PLAY_UPLOAD_FAILED`,\n  `PLAY_TRACK_FAILED`, `PLAY_COMMIT_FAILED`, `PLAY_NETWORK`.\n  `\"upload\": \"manual\"` ouvre la Play Console à la place.\n- **iOS** : la clé (`ASC_KEY_ID`, `ASC_ISSUER_ID` lus dans le fichier — lu,\n  jamais exécuté — ou l'environnement ; `AuthKey_<id>.p8` cherché dans les\n  dossiers d'altool) est vérifiée par un appel ES256 à `/v1/apps` **avant**\n  le build, puis `xcrun altool --upload-app` envoie le `.ipa` (une erreur\n  `ITMS-` fait échouer même si altool sort en 0). Sans clé : Transporter\n  s'ouvre avec le fichier (`fallback: \"none\"` pour échouer).\n- **Build local** : `\"eas\": { \"mode\": \"local\" }` fabrique sur la machine\n  (`eas build --local`, Xcode pour iOS, JDK 17 et SDK Android pour Android)\n  au lieu d'attendre la file d'Expo ; l'archive est écrite dans\n  `downloadsDir` sous un nom provisoire puis renommée, puis envoyée comme un\n  build EAS. `eas.localWorkDir` (par exemple un disque externe) reçoit le\n  dossier de travail, vidé avant et après chaque build. Le nom du fichier\n  porte l'heure du build (`local-AAAAMMJJ-HHMM`) à la place du numéro.\n  Code `EAS_LOCAL_BUILD_FAILED` ; un mode inconnu (`PUBLISH_BUILD_MODE_INVALID`)\n  arrête tout avant la montée de version.\n- **update** : le paquet est préparé sur la machine (`expo export`, iPhone et Android seulement) avec les variables du profil de `eas.json` (`extends` compris), qui recouvrent le `.env` local ; une variable `EXPO_PUBLIC_*` qui pointe vers la machine (localhost, 127.0.0.1) arrête tout avant, et une adresse locale du `.env` retrouvée dans le paquet arrête tout avant l'envoi. Puis `eas update --environment <eas.environment> --skip-bundler --input-dir`. Message = dernier commit si absent.\n- `all` s'arrête à la première plateforme en échec ; l'empreinte est\n  enregistrée dès qu'une plateforme est passée.\n\nAucun secret n'est affiché ni journalisé : clés par chemin de fichier ou nom de\nvariable d'environnement, jeton Google et identifiants Apple masqués dans les\nerreurs et la sortie d'altool.\n\n## Commandes à distance (`dispatch:generate`)\n\nProduit le script d'une clé SSH à commande forcée : la clé ne peut lancer\nque les actions listées.\n\n```json\n{\n  \"dispatch\": {\n    \"installPath\": \"/Users/moi/bin/actions-distantes.sh\",\n    \"publicKeyFile\": \"keys/iphone.pub\",\n    \"logDir\": \"~/Library/Logs/actions-distantes\",\n    \"path\": [\"/opt/homebrew/bin\", \"/usr/bin\", \"/bin\"],\n    \"notify\": \"macos\",\n    \"statusAction\": \"etat\",\n    \"actions\": [\n      { \"name\": \"deployer\", \"cwd\": \"~/projets/app\", \"command\": [\"npx\", \"astratra\", \"deploy\", \"--remote\"] },\n      { \"name\": \"sante\", \"cwd\": \"~/projets/app\", \"command\": [\"npx\", \"astratra\", \"deploy:health\"], \"background\": false }\n    ]\n  }\n}\n```\n\nLe nom reçu (`$SSH_ORIGINAL_COMMAND`) doit valoir `^[a-z][a-z0-9-]{0,63}$`\nen entier, sinon il est **refusé** (jamais « nettoyé » en un nom valide), puis\négaler une entrée d'un `case` figé. Les actions longues partent en arrière-plan\navec verrou par action (repris si son processus est mort, libéré même si le\ndossier manque), journal horodaté et notification ; `etat` montre la fin de\nchaque journal. La ligne `authorized_keys` produite :\n`command=\"…\",no-port-forwarding,no-pty,no-agent-forwarding,no-X11-forwarding <clé>`.\nRien n'est installé dans `~/.ssh` : la ligne est affichée.\n\n## Gardes de test\n\nTrois fonctions à appeler depuis les tests de ton application. Elles lisent\ntes sources et tes textes ; aucun rôle, aucun mot, aucun chemin n'est imposé.\n\n### Ce rôle voit, il n'écrit pas\n\n```js\nconst { assertRoleReadOnly } = require('@astratra/tooling');\n\ntest('le support lit, il n’écrit pas', () => {\n  assertRoleReadOnly({\n    rootDir: path.join(__dirname, '..'),\n    dirs: ['src/modules'],\n    role: ['ROLES.SUPPORT', \"'support'\"],          // chaque graphie du rôle\n    exceptions: [\n      { match: '\"/:id/activate\"', reason: 'activer un compte relève de la plateforme' },\n    ],\n  });\n});\n```\n\nRelève chaque `router.post|put|patch|delete(...)` (et `app.*`, `xxxRouter.*`,\n`.route('/x').put(...)`) dont les arguments contiennent le rôle, directement ou\nvia une liste déclarée **dans le même fichier** (`authorizeRoles(...WRITERS)`).\nRelève aussi les listes nommées comme des listes d'auteurs (`writers`,\n`EDITORS`, `APPROVERS`...) qui le contiennent — l'autorisation vit parfois dans\nle contrôleur.\n\n- `x !== ROLE`, `liste.filter((r) => r !== ROLE)` et `except(liste, ROLE)`\n  retirent le rôle : la route qui le **refuse** n'est pas signalée.\n- Une exception **sans raison écrite est refusée**, et une exception qui ne\n  correspond plus à rien fait échouer le test : elle attendait d'excuser en\n  silence la prochaine route qui prendrait ce nom.\n- Un commentaire ou une chaîne qui nomme le rôle ne compte pas.\n\n`auditRoleWrites` rend `{ findings, exempted, unusedExceptions }` sans lever,\n`auditRoleWriteSource(source, options)` travaille sur une chaîne. Options :\n`writeCalls` (groupe 1 = verbe), `authorListNames` (RegExp ou `false`),\n`exclusions`, `include`, `skippedDirs`.\n\nC'est une heuristique sur le texte : une liste importée d'un autre fichier\nn'est pas suivie, un garde posé par `router.use(...)` n'est pas vu.\n\n### Ce que dit le texte = ce que fait le code\n\n```js\nconst { assertFactsAligned, extractMatches, pickPaths } = require('@astratra/tooling');\n\nconst facts = {\n  ...extractMatches(read('config/plans.js'), { pro: /key: \"pro\"[^}]*?price: \"\\$(\\d+)\"/ }),\n  rate: require('../config/billing').COMMISSION_RATE,\n};\nconst claims = pickPaths(require('../knowledge/rules.json'), { pro: 'plans.pro.price', rate: 'commission.rate' });\n\nassertFactsAligned({ facts, claims });\n```\n\n- `mismatches` : le texte contredit le code (listes comparées sans ordre par\n  défaut, nombres à 1e-9 près).\n- `unextracted` : une valeur est `undefined`/`NaN`. **Deux extractions ratées ne\n  sont jamais un accord** — une regex qui ne matche plus rend `undefined` des\n  deux côtés, et une comparaison naïve appelle ça égal.\n- `unbacked` : une affirmation qu'aucun fait ne vérifie.\n- `unstated` : un fait que le texte tait (échec seulement avec `requireEveryFact`).\n\n### Mots interdits dans les textes produits\n\n```js\nconst { assertNoForbiddenTerms, findForbiddenTermsInFiles } = require('@astratra/tooling');\n\nassertNoForbiddenTerms({ invite: buildPrompt('admin') }, [\n  { pattern: /north(ern)?/i, reason: 'le produit est vendu partout' },\n  'ACME',\n], { required: ['worldwide'], allow: ['Acme Pay'] });\n```\n\nUn terme chaîne est un **mot entier**, sans casse, compatible Unicode : en\nsous-chaîne, un sigle de trois lettres se trouve dans des mots ordinaires. Pour\nune racine, passe une RegExp. **Une mention niée reste une mention** : « pas\nseulement pour X » nomme X, et un modèle de langage lit les mots, pas\nl'intention de la phrase. Seul `allow` (appliqué à la ligne) fait une exception.\n`findForbiddenTermsInFiles({ dirs, terms })` fait la même chose sur disque, en\nsautant les tests.\n\n## Évaluer une IA (`eval`)\n\nMesure un fournisseur ou un modèle sur **un jeu de cas** avec\n[promptfoo](https://github.com/promptfoo/promptfoo) (MIT). `promptfoo` est une\n*devDependency* (pair optionnel) : `npm install --save-dev promptfoo`, Node 22.22\nou plus. Tout reste local : télémétrie, vérification de mise à jour et partage en\nligne sont coupés, et rien n'est écrit dans l'historique de promptfoo.\n\n1. Écris `evals/cases.json` (exemple complet : `examples/eval-cases.example.json`) :\n\n```json\n{\n  \"prompt\": \"Tu es l'assistant d'une école. Réponds en français.\\n\\nQuestion : {{question}}\",\n  \"cases\": [\n    {\n      \"description\": \"Capitale de la RDC\",\n      \"vars\": { \"question\": \"Quelle est la capitale de la République démocratique du Congo ?\" },\n      \"assert\": [{ \"type\": \"icontains\", \"value\": \"Kinshasa\" }]\n    }\n  ]\n}\n```\n\nLes assertions sont celles de promptfoo (`icontains`, `not-icontains`, `javascript`,\n`llm-rubric`…). Un cas **sans assertion est refusé** : il réussirait toujours et\nfausserait le taux.\n\n2. Lance :\n\n```bash\n# Un modèle servi par llama.cpp (voir @astratra/ai (fournisseur llama.cpp)), la clé lue dans LLAMA_API_KEY :\nastratra eval --provider=openai:chat:local --base-url=http://127.0.0.1:8080/v1 --api-key-env=LLAMA_API_KEY --min-pass=0.9\n# Un fournisseur promptfoo quelconque :\nastratra eval --provider=openai:chat:gpt-… --cases=evals/orthographe.json\n```\n\nou fixe-les dans `astratra.config.json` :\n\n```json\n{ \"eval\": { \"cases\": \"evals/cases.json\", \"minPassRate\": 0.9,\n            \"providers\": [{ \"id\": \"openai:chat:local\", \"baseUrl\": \"http://127.0.0.1:8080/v1\", \"apiKeyEnv\": \"LLAMA_API_KEY\" }] } }\n```\n\nLa commande écrit la configuration promptfoo dans `.astratra-evals/promptfooconfig.json`\n(sans aucune clé : seul le **nom** de la variable d'environnement y figure) et le\nrésultat dans `.astratra-evals/last-result.json`, affiche un score par fournisseur\navec les cas ratés et leur raison, et sort en **1** si un fournisseur passe sous\n`minPassRate` (défaut 1, soit tous les cas) — utilisable en CI. Une erreur\nd'exécution (réseau, serveur arrêté) compte comme un échec, pas comme un succès.\n\n## Tests\n\n```bash\nnpm test --workspace @astratra/tooling\n```\n","readmeFilename":"README.md"}