torusguard 1.3.4 → 1.3.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (319) hide show
  1. package/.torusguard/.manifest.json +66 -63
  2. package/.torusguard/rules/business-logic/TG-BIZ-001-client-controlled-sensitive-business-value.md +30 -14
  3. package/.torusguard/rules/business-logic/TG-BIZ-002-replayable-one-time-operation.md +33 -16
  4. package/.torusguard/rules/business-logic/TG-BIZ-003-missing-workflow-state-validation.md +31 -15
  5. package/.torusguard/rules/business-logic/TG-BIZ-004-unrestricted-sensitive-business-flow.md +26 -16
  6. package/.torusguard/rules/cache/TG-CACHE-001-sensitive-response-publicly-cacheable.md +24 -16
  7. package/.torusguard/rules/cache/TG-CACHE-002-missing-user-cache-isolation.md +32 -18
  8. package/.torusguard/rules/cache/TG-CACHE-003-sensitive-data-in-url.md +24 -17
  9. package/.torusguard/rules/csrf/TG-CSRF-001-missing-csrf-protection.md +31 -18
  10. package/.torusguard/rules/csrf/TG-CSRF-002-unsafe-credentialed-cross-origin-request.md +28 -17
  11. package/.torusguard/rules/graphql/TG-GQL-002-resolver-missing-authorization.md +33 -17
  12. package/.torusguard/rules/graphql/TG-GQL-003-unbounded-graphql-pagination-or-batching.md +31 -16
  13. package/.torusguard/rules/graphql/TG-GQL-004-unnecessary-production-introspection.md +25 -16
  14. package/.torusguard/rules/secrets/TG-SEC-005-secret-in-git-history.md +24 -19
  15. package/.torusguard/rules/secrets/TG-SEC-006-secret-in-build-artifact.md +36 -19
  16. package/.torusguard/rules/secrets/TG-SEC-007-secret-in-test-or-example-fixture.md +21 -16
  17. package/.torusguard/rules/ssrf/TG-SSRF-002-missing-internal-network-protection.md +45 -18
  18. package/.torusguard/rules/ssrf/TG-SSRF-003-unsafe-redirect-following.md +20 -16
  19. package/.torusguard/rules/ssrf/TG-SSRF-004-unbounded-outbound-request.md +28 -19
  20. package/.torusguard/rules/supply-chain/TG-SUPPLY-001-missing-or-ignored-dependency-lockfile.md +25 -18
  21. package/.torusguard/rules/supply-chain/TG-SUPPLY-003-unsafe-ci-cd-secret-exposure.md +29 -19
  22. package/.torusguard/rules/supply-chain/TG-SUPPLY-004-unpinned-ci-action.md +23 -18
  23. package/.torusguard/rules/supply-chain/TG-SUPPLY-005-unsafe-dependency-install-script.md +23 -18
  24. package/.torusguard/rules/webhook/TG-WEBHOOK-001-missing-webhook-signature-verification.md +36 -16
  25. package/.torusguard/rules/webhook/TG-WEBHOOK-002-replayable-webhook.md +26 -17
  26. package/.torusguard/rules/webhook/TG-WEBHOOK-003-missing-webhook-idempotency.md +30 -16
  27. package/.torusguard/rules/webhook/TG-WEBHOOK-004-unvalidated-webhook-event-type.md +25 -16
  28. package/.torusguard/rules/websocket/TG-WS-002-missing-channel-level-authorization.md +28 -17
  29. package/.torusguard/rules/websocket/TG-WS-003-unvalidated-websocket-message.md +31 -17
  30. package/.torusguard/rules/websocket/TG-WS-004-unbounded-websocket-connections.md +31 -17
  31. package/.torusguard/rules_catalog.json +520 -0
  32. package/.torusguard/runs/report-latest.html +2700 -232
  33. package/.torusguard/runs/results-latest.sarif +20 -0
  34. package/.torusguard/runs/run-20260912-214110-audit/findings.json +1256 -0
  35. package/.torusguard/runs/run-20260912-214110-audit/findings.md +534 -0
  36. package/.torusguard/runs/run-20260912-214110-audit/manifest.json +12 -0
  37. package/.torusguard/runs/run-20260912-214110-audit/summary.md +5 -0
  38. package/.torusguard/runs/run-20260912-214813-audit/findings.json +992 -0
  39. package/.torusguard/runs/run-20260912-214813-audit/findings.md +422 -0
  40. package/.torusguard/runs/run-20260912-214813-audit/manifest.json +12 -0
  41. package/.torusguard/runs/run-20260912-214813-audit/summary.md +5 -0
  42. package/.torusguard/runs/run-20260912-215555-audit/bundles/bnd-tg-auth-003-49-7c96f3/metadata.json +17 -0
  43. package/.torusguard/runs/run-20260912-215555-audit/bundles/bnd-tg-auth-003-49-7c96f3/minimal_patch_plan.md +21 -0
  44. package/.torusguard/runs/run-20260912-215555-audit/bundles/bnd-tg-auth-003-49-7c96f3/patch.diff +9 -0
  45. package/.torusguard/runs/run-20260912-215555-audit/bundles/bnd-tg-input-003-53-d167b4/metadata.json +17 -0
  46. package/.torusguard/runs/run-20260912-215555-audit/bundles/bnd-tg-input-003-53-d167b4/minimal_patch_plan.md +21 -0
  47. package/.torusguard/runs/run-20260912-215555-audit/bundles/bnd-tg-input-003-53-d167b4/patch.diff +9 -0
  48. package/.torusguard/runs/run-20260912-215555-audit/bundles/bnd-tg-platform-001-12-cf8156/metadata.json +17 -0
  49. package/.torusguard/runs/run-20260912-215555-audit/bundles/bnd-tg-platform-001-12-cf8156/minimal_patch_plan.md +21 -0
  50. package/.torusguard/runs/run-20260912-215555-audit/bundles/bnd-tg-platform-001-12-cf8156/patch.diff +9 -0
  51. package/.torusguard/runs/run-20260912-215555-audit/bundles/bnd-tg-rate-001-26-affcfa/metadata.json +17 -0
  52. package/.torusguard/runs/run-20260912-215555-audit/bundles/bnd-tg-rate-001-26-affcfa/minimal_patch_plan.md +21 -0
  53. package/.torusguard/runs/run-20260912-215555-audit/bundles/bnd-tg-rate-001-26-affcfa/patch.diff +9 -0
  54. package/.torusguard/runs/run-20260912-215555-audit/bundles/bnd-tg-sec-001-9-e22e50/metadata.json +17 -0
  55. package/.torusguard/runs/run-20260912-215555-audit/bundles/bnd-tg-sec-001-9-e22e50/minimal_patch_plan.md +21 -0
  56. package/.torusguard/runs/run-20260912-215555-audit/bundles/bnd-tg-sec-001-9-e22e50/patch.diff +9 -0
  57. package/.torusguard/runs/run-20260912-215555-audit/bundles/bnd-tg-ssrf-004-9-9803ad/metadata.json +17 -0
  58. package/.torusguard/runs/run-20260912-215555-audit/bundles/bnd-tg-ssrf-004-9-9803ad/minimal_patch_plan.md +21 -0
  59. package/.torusguard/runs/run-20260912-215555-audit/bundles/bnd-tg-ssrf-004-9-9803ad/patch.diff +9 -0
  60. package/.torusguard/runs/run-20260912-215555-audit/findings.json +530 -0
  61. package/.torusguard/runs/run-20260912-215555-audit/findings.md +232 -0
  62. package/.torusguard/runs/run-20260912-215555-audit/manifest.json +13 -0
  63. package/.torusguard/runs/run-20260912-215555-audit/remediation.md +102 -0
  64. package/.torusguard/runs/run-20260912-215555-audit/summary.md +5 -0
  65. package/.torusguard/runs/run-20260912-215731-audit/apply_plan.md +6 -0
  66. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-client-001-7-587129/metadata.json +17 -0
  67. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-client-001-7-587129/minimal_patch_plan.md +21 -0
  68. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-client-001-7-587129/patch.diff +9 -0
  69. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-db-002-9-5f7850/metadata.json +17 -0
  70. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-db-002-9-5f7850/minimal_patch_plan.md +21 -0
  71. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-db-002-9-5f7850/patch.diff +9 -0
  72. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-input-001-27-2486f1/metadata.json +17 -0
  73. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-input-001-27-2486f1/minimal_patch_plan.md +22 -0
  74. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-input-001-27-2486f1/patch.diff +10 -0
  75. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-input-003-53-d167b4/metadata.json +17 -0
  76. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-input-003-53-d167b4/minimal_patch_plan.md +21 -0
  77. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-input-003-53-d167b4/patch.diff +9 -0
  78. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-platform-001-12-cf8156/metadata.json +17 -0
  79. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-platform-001-12-cf8156/minimal_patch_plan.md +21 -0
  80. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-platform-001-12-cf8156/patch.diff +9 -0
  81. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-platform-002-17-876973/metadata.json +17 -0
  82. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-platform-002-17-876973/minimal_patch_plan.md +23 -0
  83. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-platform-002-17-876973/patch.diff +11 -0
  84. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-platform-002-5-40c794/metadata.json +17 -0
  85. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-platform-002-5-40c794/minimal_patch_plan.md +23 -0
  86. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-platform-002-5-40c794/patch.diff +11 -0
  87. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-rate-001-26-affcfa/metadata.json +17 -0
  88. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-rate-001-26-affcfa/minimal_patch_plan.md +21 -0
  89. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-rate-001-26-affcfa/patch.diff +9 -0
  90. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-rate-002-54-0b38d2/metadata.json +17 -0
  91. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-rate-002-54-0b38d2/minimal_patch_plan.md +21 -0
  92. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-rate-002-54-0b38d2/patch.diff +9 -0
  93. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-sec-001-18-a3c1e8/metadata.json +17 -0
  94. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-sec-001-18-a3c1e8/minimal_patch_plan.md +21 -0
  95. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-sec-001-18-a3c1e8/patch.diff +9 -0
  96. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-sec-001-9-e22e50/metadata.json +17 -0
  97. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-sec-001-9-e22e50/minimal_patch_plan.md +21 -0
  98. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-sec-001-9-e22e50/patch.diff +9 -0
  99. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-sec-004-28-a1a0a1/metadata.json +17 -0
  100. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-sec-004-28-a1a0a1/minimal_patch_plan.md +21 -0
  101. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-sec-004-28-a1a0a1/patch.diff +9 -0
  102. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-sec-004-30-00569e/metadata.json +17 -0
  103. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-sec-004-30-00569e/minimal_patch_plan.md +21 -0
  104. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-sec-004-30-00569e/patch.diff +9 -0
  105. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-ssrf-004-9-9803ad/metadata.json +17 -0
  106. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-ssrf-004-9-9803ad/minimal_patch_plan.md +21 -0
  107. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-ssrf-004-9-9803ad/patch.diff +9 -0
  108. package/.torusguard/runs/run-20260912-215731-audit/diff_summary.md +186 -0
  109. package/.torusguard/runs/run-20260912-215731-audit/findings.json +497 -0
  110. package/.torusguard/runs/run-20260912-215731-audit/findings.md +218 -0
  111. package/.torusguard/runs/run-20260912-215731-audit/manifest.json +14 -0
  112. package/.torusguard/runs/run-20260912-215731-audit/recheck.md +76 -0
  113. package/.torusguard/runs/run-20260912-215731-audit/remediation.md +234 -0
  114. package/.torusguard/runs/run-20260912-215731-audit/summary.md +5 -0
  115. package/.torusguard/runs/run-20260912-220134-audit/findings.json +68 -0
  116. package/.torusguard/runs/run-20260912-220134-audit/findings.md +36 -0
  117. package/.torusguard/runs/run-20260912-220134-audit/manifest.json +12 -0
  118. package/.torusguard/runs/run-20260912-220134-audit/summary.md +5 -0
  119. package/.torusguard/runs/run-20260912-220200-audit/findings.json +1 -0
  120. package/.torusguard/runs/run-20260912-220200-audit/findings.md +8 -0
  121. package/.torusguard/runs/run-20260912-220200-audit/manifest.json +12 -0
  122. package/.torusguard/runs/run-20260912-220200-audit/recheck.md +6 -0
  123. package/.torusguard/runs/run-20260912-220200-audit/summary.md +5 -0
  124. package/.torusguard/runs/run-20260912-220227-audit/findings.json +1 -0
  125. package/.torusguard/runs/run-20260912-220227-audit/findings.md +8 -0
  126. package/.torusguard/runs/run-20260912-220227-audit/manifest.json +12 -0
  127. package/.torusguard/runs/run-20260912-220227-audit/recheck.md +6 -0
  128. package/.torusguard/runs/run-20260912-220227-audit/summary.md +5 -0
  129. package/.torusguard/runs/run-20260918-104221-audit/findings.json +1 -0
  130. package/.torusguard/runs/run-20260918-104221-audit/findings.md +8 -0
  131. package/.torusguard/runs/run-20260918-104221-audit/manifest.json +12 -0
  132. package/.torusguard/runs/run-20260918-104221-audit/summary.md +5 -0
  133. package/.torusguard/runs/run-20260918-104236-audit/findings.json +2314 -0
  134. package/.torusguard/runs/run-20260918-104236-audit/findings.md +960 -0
  135. package/.torusguard/runs/run-20260918-104236-audit/manifest.json +12 -0
  136. package/.torusguard/runs/run-20260918-104236-audit/summary.md +5 -0
  137. package/.torusguard/runs/run-20260918-105723-audit/findings.json +2382 -0
  138. package/.torusguard/runs/run-20260918-105723-audit/findings.md +960 -0
  139. package/.torusguard/runs/run-20260918-105723-audit/manifest.json +12 -0
  140. package/.torusguard/runs/run-20260918-105723-audit/summary.md +5 -0
  141. package/.torusguard/runs/run-20260918-105731-audit/findings.json +1 -0
  142. package/.torusguard/runs/run-20260918-105731-audit/findings.md +8 -0
  143. package/.torusguard/runs/run-20260918-105731-audit/manifest.json +12 -0
  144. package/.torusguard/runs/run-20260918-105731-audit/summary.md +5 -0
  145. package/.torusguard/runs/run-20260918-105940-audit/findings.json +1 -0
  146. package/.torusguard/runs/run-20260918-105940-audit/findings.md +8 -0
  147. package/.torusguard/runs/run-20260918-105940-audit/manifest.json +12 -0
  148. package/.torusguard/runs/run-20260918-105940-audit/summary.md +5 -0
  149. package/.torusguard/scripts/__pycache__/apply_runner.cpython-314.pyc +0 -0
  150. package/.torusguard/scripts/__pycache__/audit_runner.cpython-311.pyc +0 -0
  151. package/.torusguard/scripts/__pycache__/audit_runner.cpython-314.pyc +0 -0
  152. package/.torusguard/scripts/__pycache__/diff_guard.cpython-314.pyc +0 -0
  153. package/.torusguard/scripts/__pycache__/finding_scorer.cpython-311.pyc +0 -0
  154. package/.torusguard/scripts/__pycache__/finding_scorer.cpython-314.pyc +0 -0
  155. package/.torusguard/scripts/__pycache__/harden_runner.cpython-314.pyc +0 -0
  156. package/.torusguard/scripts/__pycache__/html_reporter.cpython-314.pyc +0 -0
  157. package/.torusguard/scripts/__pycache__/manifest_builder.cpython-314.pyc +0 -0
  158. package/.torusguard/scripts/__pycache__/memory_engine.cpython-314.pyc +0 -0
  159. package/.torusguard/scripts/__pycache__/monorepo_detector.cpython-314.pyc +0 -0
  160. package/.torusguard/scripts/__pycache__/recheck_runner.cpython-314.pyc +0 -0
  161. package/.torusguard/scripts/__pycache__/report_sync.cpython-311.pyc +0 -0
  162. package/.torusguard/scripts/__pycache__/report_sync.cpython-314.pyc +0 -0
  163. package/.torusguard/scripts/__pycache__/run_manager.cpython-314.pyc +0 -0
  164. package/.torusguard/scripts/__pycache__/sarif_exporter.cpython-314.pyc +0 -0
  165. package/.torusguard/scripts/__pycache__/stack_detect.cpython-314.pyc +0 -0
  166. package/.torusguard/scripts/__pycache__/term_ui.cpython-311.pyc +0 -0
  167. package/.torusguard/scripts/__pycache__/term_ui.cpython-314.pyc +0 -0
  168. package/.torusguard/scripts/apply_runner.py +389 -377
  169. package/.torusguard/scripts/audit_runner.py +1513 -653
  170. package/.torusguard/scripts/finding_scorer.py +14 -2
  171. package/.torusguard/scripts/harden_runner.py +628 -455
  172. package/.torusguard/scripts/html_reporter.py +2668 -648
  173. package/.torusguard/scripts/recheck_runner.py +248 -238
  174. package/.torusguard/scripts/recipes_runner.py +137 -137
  175. package/.torusguard/scripts/report_sync.py +719 -0
  176. package/.torusguard/scripts/sarif_exporter.py +156 -116
  177. package/.torusguard/scripts/stack_detect.py +44 -2
  178. package/.torusguard/scripts/term_ui.py +162 -162
  179. package/.torusguard/skills/torusguard/SKILL.md +75 -72
  180. package/.torusguard/skills/torusguard-apply/SKILL.md +84 -80
  181. package/.torusguard/skills/torusguard-audit/SKILL.md +87 -83
  182. package/.torusguard/skills/torusguard-authorize/SKILL.md +7 -2
  183. package/.torusguard/skills/torusguard-exploit-check/SKILL.md +7 -2
  184. package/.torusguard/skills/torusguard-full/SKILL.md +7 -2
  185. package/.torusguard/skills/torusguard-harden/SKILL.md +98 -94
  186. package/.torusguard/skills/torusguard-init/SKILL.md +68 -64
  187. package/.torusguard/skills/torusguard-recheck/SKILL.md +81 -77
  188. package/.torusguard/skills/torusguard-report/SKILL.md +93 -72
  189. package/.torusguard/skills/torusguard-status/SKILL.md +62 -58
  190. package/.torusguard/skills/torusguard-verify/SKILL.md +7 -2
  191. package/.torusguard/skills/torusguard-web-validate/SKILL.md +7 -2
  192. package/.torusguard/snapshots/run-20260912-215731-audit/demo/playground/vulnerable_nextjs/actions.ts.bak +24 -0
  193. package/.torusguard/snapshots/run-20260912-215731-audit/examples/hardened-react-express/server/index.js.bak +121 -0
  194. package/.torusguard/snapshots/run-20260912-215731-audit/examples/python/fastapi-vuln/main.py.bak +20 -0
  195. package/.torusguard/snapshots/run-20260912-215731-audit/examples/vulnerable-react-express/client/src/App.jsx.bak +63 -0
  196. package/.torusguard/snapshots/run-20260912-215731-audit/examples/vulnerable-react-express/client/vite.config.js.bak +12 -0
  197. package/.torusguard/snapshots/run-20260912-215731-audit/examples/vulnerable-react-express/server/index.js.bak +82 -0
  198. package/.torusguard/workflows/apply.md +11 -4
  199. package/.torusguard/workflows/audit.md +7 -1
  200. package/.torusguard/workflows/authorize.md +2 -1
  201. package/.torusguard/workflows/exploit-check.md +2 -1
  202. package/.torusguard/workflows/harden.md +7 -1
  203. package/.torusguard/workflows/init.md +6 -1
  204. package/.torusguard/workflows/memory.md +2 -1
  205. package/.torusguard/workflows/recheck.md +7 -1
  206. package/.torusguard/workflows/report.md +7 -1
  207. package/.torusguard/workflows/status.md +6 -1
  208. package/.torusguard/workflows/verify.md +6 -1
  209. package/.torusguard/workflows/web-validate.md +2 -1
  210. package/README.md +476 -345
  211. package/bin/torusguard.js +586 -545
  212. package/package.json +1 -1
  213. package/skills/torusguard/SKILL.md +73 -72
  214. package/skills/torusguard/bootstrap.py +7 -2
  215. package/skills/torusguard/payload/.manifest.json +66 -63
  216. package/skills/torusguard/payload/rules/business-logic/TG-BIZ-001-client-controlled-sensitive-business-value.md +30 -14
  217. package/skills/torusguard/payload/rules/business-logic/TG-BIZ-002-replayable-one-time-operation.md +33 -16
  218. package/skills/torusguard/payload/rules/business-logic/TG-BIZ-003-missing-workflow-state-validation.md +31 -15
  219. package/skills/torusguard/payload/rules/business-logic/TG-BIZ-004-unrestricted-sensitive-business-flow.md +26 -16
  220. package/skills/torusguard/payload/rules/cache/TG-CACHE-001-sensitive-response-publicly-cacheable.md +24 -16
  221. package/skills/torusguard/payload/rules/cache/TG-CACHE-002-missing-user-cache-isolation.md +32 -18
  222. package/skills/torusguard/payload/rules/cache/TG-CACHE-003-sensitive-data-in-url.md +24 -17
  223. package/skills/torusguard/payload/rules/csrf/TG-CSRF-001-missing-csrf-protection.md +31 -18
  224. package/skills/torusguard/payload/rules/csrf/TG-CSRF-002-unsafe-credentialed-cross-origin-request.md +28 -17
  225. package/skills/torusguard/payload/rules/graphql/TG-GQL-002-resolver-missing-authorization.md +33 -17
  226. package/skills/torusguard/payload/rules/graphql/TG-GQL-003-unbounded-graphql-pagination-or-batching.md +31 -16
  227. package/skills/torusguard/payload/rules/graphql/TG-GQL-004-unnecessary-production-introspection.md +25 -16
  228. package/skills/torusguard/payload/rules/secrets/TG-SEC-005-secret-in-git-history.md +24 -19
  229. package/skills/torusguard/payload/rules/secrets/TG-SEC-006-secret-in-build-artifact.md +36 -19
  230. package/skills/torusguard/payload/rules/secrets/TG-SEC-007-secret-in-test-or-example-fixture.md +21 -16
  231. package/skills/torusguard/payload/rules/ssrf/TG-SSRF-002-missing-internal-network-protection.md +45 -18
  232. package/skills/torusguard/payload/rules/ssrf/TG-SSRF-003-unsafe-redirect-following.md +20 -16
  233. package/skills/torusguard/payload/rules/ssrf/TG-SSRF-004-unbounded-outbound-request.md +28 -19
  234. package/skills/torusguard/payload/rules/supply-chain/TG-SUPPLY-001-missing-or-ignored-dependency-lockfile.md +25 -18
  235. package/skills/torusguard/payload/rules/supply-chain/TG-SUPPLY-003-unsafe-ci-cd-secret-exposure.md +29 -19
  236. package/skills/torusguard/payload/rules/supply-chain/TG-SUPPLY-004-unpinned-ci-action.md +23 -18
  237. package/skills/torusguard/payload/rules/supply-chain/TG-SUPPLY-005-unsafe-dependency-install-script.md +23 -18
  238. package/skills/torusguard/payload/rules/webhook/TG-WEBHOOK-001-missing-webhook-signature-verification.md +36 -16
  239. package/skills/torusguard/payload/rules/webhook/TG-WEBHOOK-002-replayable-webhook.md +26 -17
  240. package/skills/torusguard/payload/rules/webhook/TG-WEBHOOK-003-missing-webhook-idempotency.md +30 -16
  241. package/skills/torusguard/payload/rules/webhook/TG-WEBHOOK-004-unvalidated-webhook-event-type.md +25 -16
  242. package/skills/torusguard/payload/rules/websocket/TG-WS-002-missing-channel-level-authorization.md +28 -17
  243. package/skills/torusguard/payload/rules/websocket/TG-WS-003-unvalidated-websocket-message.md +31 -17
  244. package/skills/torusguard/payload/rules/websocket/TG-WS-004-unbounded-websocket-connections.md +31 -17
  245. package/skills/torusguard/payload/rules_catalog.json +520 -0
  246. package/skills/torusguard/payload/scripts/__pycache__/apply_runner.cpython-314.pyc +0 -0
  247. package/skills/torusguard/payload/scripts/__pycache__/audit_runner.cpython-311.pyc +0 -0
  248. package/skills/torusguard/payload/scripts/__pycache__/audit_runner.cpython-314.pyc +0 -0
  249. package/skills/torusguard/payload/scripts/__pycache__/diff_guard.cpython-311.pyc +0 -0
  250. package/skills/torusguard/payload/scripts/__pycache__/diff_guard.cpython-314.pyc +0 -0
  251. package/skills/torusguard/payload/scripts/__pycache__/finding_scorer.cpython-311.pyc +0 -0
  252. package/skills/torusguard/payload/scripts/__pycache__/finding_scorer.cpython-314.pyc +0 -0
  253. package/skills/torusguard/payload/scripts/__pycache__/harden_runner.cpython-314.pyc +0 -0
  254. package/skills/torusguard/payload/scripts/__pycache__/html_reporter.cpython-311.pyc +0 -0
  255. package/skills/torusguard/payload/scripts/__pycache__/html_reporter.cpython-314.pyc +0 -0
  256. package/skills/torusguard/payload/scripts/__pycache__/manifest_builder.cpython-314.pyc +0 -0
  257. package/skills/torusguard/payload/scripts/__pycache__/memory_engine.cpython-311.pyc +0 -0
  258. package/skills/torusguard/payload/scripts/__pycache__/memory_engine.cpython-314.pyc +0 -0
  259. package/skills/torusguard/payload/scripts/__pycache__/monorepo_detector.cpython-311.pyc +0 -0
  260. package/skills/torusguard/payload/scripts/__pycache__/monorepo_detector.cpython-314.pyc +0 -0
  261. package/skills/torusguard/payload/scripts/__pycache__/recheck_runner.cpython-314.pyc +0 -0
  262. package/skills/torusguard/payload/scripts/__pycache__/report_sync.cpython-311.pyc +0 -0
  263. package/skills/torusguard/payload/scripts/__pycache__/report_sync.cpython-314.pyc +0 -0
  264. package/skills/torusguard/payload/scripts/__pycache__/rules_sync.cpython-311.pyc +0 -0
  265. package/skills/torusguard/payload/scripts/__pycache__/run_manager.cpython-311.pyc +0 -0
  266. package/skills/torusguard/payload/scripts/__pycache__/run_manager.cpython-314.pyc +0 -0
  267. package/skills/torusguard/payload/scripts/__pycache__/sarif_exporter.cpython-314.pyc +0 -0
  268. package/skills/torusguard/payload/scripts/__pycache__/stack_detect.cpython-311.pyc +0 -0
  269. package/skills/torusguard/payload/scripts/__pycache__/stack_detect.cpython-314.pyc +0 -0
  270. package/skills/torusguard/payload/scripts/__pycache__/term_ui.cpython-311.pyc +0 -0
  271. package/skills/torusguard/payload/scripts/__pycache__/term_ui.cpython-314.pyc +0 -0
  272. package/skills/torusguard/payload/scripts/apply_runner.py +389 -377
  273. package/skills/torusguard/payload/scripts/audit_runner.py +1513 -653
  274. package/skills/torusguard/payload/scripts/finding_scorer.py +14 -2
  275. package/skills/torusguard/payload/scripts/harden_runner.py +628 -455
  276. package/skills/torusguard/payload/scripts/html_reporter.py +2668 -648
  277. package/skills/torusguard/payload/scripts/recheck_runner.py +248 -238
  278. package/skills/torusguard/payload/scripts/recipes_runner.py +137 -137
  279. package/skills/torusguard/payload/scripts/report_sync.py +719 -0
  280. package/skills/torusguard/payload/scripts/sarif_exporter.py +156 -116
  281. package/skills/torusguard/payload/scripts/stack_detect.py +44 -2
  282. package/skills/torusguard/payload/scripts/term_ui.py +162 -162
  283. package/skills/torusguard/payload/skills/torusguard/SKILL.md +75 -72
  284. package/skills/torusguard/payload/skills/torusguard-apply/SKILL.md +84 -80
  285. package/skills/torusguard/payload/skills/torusguard-audit/SKILL.md +87 -83
  286. package/skills/torusguard/payload/skills/torusguard-authorize/SKILL.md +7 -2
  287. package/skills/torusguard/payload/skills/torusguard-exploit-check/SKILL.md +7 -2
  288. package/skills/torusguard/payload/skills/torusguard-full/SKILL.md +7 -2
  289. package/skills/torusguard/payload/skills/torusguard-harden/SKILL.md +98 -94
  290. package/skills/torusguard/payload/skills/torusguard-init/SKILL.md +68 -64
  291. package/skills/torusguard/payload/skills/torusguard-recheck/SKILL.md +81 -77
  292. package/skills/torusguard/payload/skills/torusguard-report/SKILL.md +93 -72
  293. package/skills/torusguard/payload/skills/torusguard-status/SKILL.md +62 -58
  294. package/skills/torusguard/payload/skills/torusguard-verify/SKILL.md +7 -2
  295. package/skills/torusguard/payload/skills/torusguard-web-validate/SKILL.md +7 -2
  296. package/skills/torusguard/payload/workflows/apply.md +11 -4
  297. package/skills/torusguard/payload/workflows/audit.md +7 -1
  298. package/skills/torusguard/payload/workflows/authorize.md +2 -1
  299. package/skills/torusguard/payload/workflows/exploit-check.md +2 -1
  300. package/skills/torusguard/payload/workflows/harden.md +7 -1
  301. package/skills/torusguard/payload/workflows/init.md +6 -1
  302. package/skills/torusguard/payload/workflows/memory.md +2 -1
  303. package/skills/torusguard/payload/workflows/recheck.md +7 -1
  304. package/skills/torusguard/payload/workflows/report.md +7 -1
  305. package/skills/torusguard/payload/workflows/status.md +6 -1
  306. package/skills/torusguard/payload/workflows/verify.md +6 -1
  307. package/skills/torusguard/payload/workflows/web-validate.md +2 -1
  308. package/skills/torusguard-apply/SKILL.md +84 -80
  309. package/skills/torusguard-audit/SKILL.md +87 -83
  310. package/skills/torusguard-authorize/SKILL.md +7 -2
  311. package/skills/torusguard-exploit-check/SKILL.md +7 -2
  312. package/skills/torusguard-full/SKILL.md +7 -2
  313. package/skills/torusguard-harden/SKILL.md +98 -94
  314. package/skills/torusguard-init/SKILL.md +68 -64
  315. package/skills/torusguard-recheck/SKILL.md +81 -77
  316. package/skills/torusguard-report/SKILL.md +93 -72
  317. package/skills/torusguard-status/SKILL.md +62 -58
  318. package/skills/torusguard-verify/SKILL.md +7 -2
  319. package/skills/torusguard-web-validate/SKILL.md +7 -2
package/bin/torusguard.js CHANGED
@@ -1,545 +1,586 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * TorusGuard CLI Runner (NPM Distribution)
5
- * Provides zero-dependency command orchestration for TorusGuard governance.
6
- * Adheres to Ponytail principles: concise, surgical, zero fluff.
7
- */
8
-
9
- const { spawnSync } = require('child_process');
10
- const path = require('path');
11
- const fs = require('fs');
12
-
13
- const args = process.argv.slice(2);
14
- const command = args[0] || 'init';
15
- const rootDir = path.resolve(__dirname, '..');
16
- const cwd = process.cwd();
17
-
18
- // ─── ANSI Color Helpers ───────────────────────────────────────────────────────
19
- const BOLD = '\x1b[1m';
20
- const DIM = '\x1b[2m';
21
- const RESET = '\x1b[0m';
22
- const CYAN = '\x1b[36m';
23
- const GREEN = '\x1b[32m';
24
- const YELLOW = '\x1b[33m';
25
- const WHITE = '\x1b[97m';
26
- const GRAY = '\x1b[90m';
27
- const RED = '\x1b[31m';
28
-
29
- // Detect available Python binary (python or python3)
30
- let pythonCmd = 'python';
31
- try {
32
- const check = spawnSync('python', ['--version'], { encoding: 'utf-8' });
33
- if (check.status !== 0) {
34
- const check3 = spawnSync('python3', ['--version'], { encoding: 'utf-8' });
35
- if (check3.status === 0) {
36
- pythonCmd = 'python3';
37
- }
38
- }
39
- } catch (e) {
40
- try {
41
- const check3 = spawnSync('python3', ['--version'], { encoding: 'utf-8' });
42
- if (check3.status === 0) {
43
- pythonCmd = 'python3';
44
- }
45
- } catch (err) {}
46
- }
47
-
48
- const ANSI_REGEX = /\x1b\[[0-9;]*m/g;
49
-
50
- function getVisualWidth(text) {
51
- const clean = text.replace(ANSI_REGEX, '');
52
- let width = 0;
53
- for (let i = 0; i < clean.length; i++) {
54
- const cp = clean.codePointAt(i);
55
- if (cp > 0xffff) i++;
56
- if ((cp >= 0xfe00 && cp <= 0xfe0f) || (cp >= 0xe0100 && cp <= 0xe01ef)) continue;
57
- if (cp === 0x200b || cp === 0x200c || cp === 0x200d || cp === 0x00ad) continue;
58
- if (cp >= 0x1f300 || (cp >= 0x1100 && (
59
- cp <= 0x115f || cp === 0x2329 || cp === 0x232a ||
60
- (cp >= 0x2e80 && cp <= 0xa4cf && cp !== 0x303f) ||
61
- (cp >= 0xac00 && cp <= 0xd7a3) ||
62
- (cp >= 0xf900 && cp <= 0xfaff) ||
63
- (cp >= 0xfe10 && cp <= 0xfe19) ||
64
- (cp >= 0xfe30 && cp <= 0xfe6f) ||
65
- (cp >= 0xff00 && cp <= 0xff60) ||
66
- (cp >= 0xffe0 && cp <= 0xffe6)
67
- ))) {
68
- width += 2;
69
- } else {
70
- width += 1;
71
- }
72
- }
73
- return width;
74
- }
75
-
76
- function truncateVisual(text, maxW = 67) {
77
- if (getVisualWidth(text) <= maxW) return text;
78
- let currW = 0;
79
- let out = '';
80
- let inAnsi = false;
81
- let ansiBuf = '';
82
- for (let i = 0; i < text.length; i++) {
83
- const ch = text[i];
84
- if (ch === '\x1b') {
85
- inAnsi = true;
86
- ansiBuf = ch;
87
- continue;
88
- }
89
- if (inAnsi) {
90
- ansiBuf += ch;
91
- if (ch === 'm') {
92
- inAnsi = false;
93
- out += ansiBuf;
94
- }
95
- continue;
96
- }
97
- const cp = text.codePointAt(i);
98
- if (cp > 0xffff) i++;
99
- if ((cp >= 0xfe00 && cp <= 0xfe0f) || (cp >= 0xe0100 && cp <= 0xe01ef) || cp === 0x200b || cp === 0x200c || cp === 0x200d || cp === 0x00ad) {
100
- out += String.fromCodePoint(cp);
101
- continue;
102
- }
103
- const cw = cp >= 0x1f300 ? 2 : 1;
104
- if (currW + cw > maxW - 3) {
105
- out += '\x1b[0m...';
106
- currW += 3;
107
- break;
108
- }
109
- out += String.fromCodePoint(cp);
110
- currW += cw;
111
- }
112
- out += '\x1b[0m';
113
- return out;
114
- }
115
-
116
- function formatBoxLine(content, width = 67, border = '│', borderColor = CYAN) {
117
- const trunc = truncateVisual(content, width);
118
- const vis = getVisualWidth(trunc);
119
- const pad = ' '.repeat(Math.max(0, width - vis));
120
- return ` ${borderColor}${border}${RESET} ${trunc}${pad} ${borderColor}${border}${RESET}`;
121
- }
122
-
123
- function cardBorderTop(title = '', borderColor = CYAN, double = false) {
124
- const left = double ? '╔' : '┌';
125
- const right = double ? '╗' : '┐';
126
- const h = double ? '═' : '─';
127
- if (title) {
128
- const vis = getVisualWidth(title);
129
- const rem = Math.max(0, 68 - vis);
130
- return ` ${borderColor}${left}${h} ${BOLD}${WHITE}${title}${RESET}${borderColor} ${h.repeat(rem)}${right}${RESET}`;
131
- }
132
- return ` ${borderColor}${left}${h.repeat(71)}${right}${RESET}`;
133
- }
134
-
135
- function cardBorderBottom(borderColor = CYAN, double = false) {
136
- const left = double ? '╚' : '└';
137
- const right = double ? '╝' : '┘';
138
- const h = double ? '═' : '─';
139
- return ` ${borderColor}${left}${h.repeat(71)}${right}${RESET}`;
140
- }
141
-
142
- function cardDivider(title = '', borderColor = CYAN, double = false) {
143
- const left = double ? '╠' : '├';
144
- const right = double ? '╣' : '┤';
145
- const h = double ? '═' : '─';
146
- if (title) {
147
- const vis = getVisualWidth(title);
148
- const rem = Math.max(0, 68 - vis);
149
- return ` ${borderColor}${left}${h} ${BOLD}${WHITE}${title}${RESET}${borderColor} ${h.repeat(rem)}${right}${RESET}`;
150
- }
151
- return ` ${borderColor}${left}${h.repeat(71)}${right}${RESET}`;
152
- }
153
-
154
- function cardHeader(title, subtitle = '', version = 'v1.3.4', borderColor = CYAN) {
155
- const top = ` ${borderColor}╭${'─'.repeat(71)}╮${RESET}`;
156
- const bottom = ` ${borderColor}╰${'─'.repeat(71)}╯${RESET}`;
157
- const empty = ` ${borderColor}│${' '.repeat(71)}│${RESET}`;
158
- const titleVis = getVisualWidth(title);
159
- const verVis = getVisualWidth(version);
160
- const spaceCount = Math.max(1, 67 - titleVis - verVis);
161
- const titleStr = `${BOLD}${WHITE}${title}${RESET}${' '.repeat(spaceCount)}${GRAY}${version}${RESET}`;
162
- const lines = [top, empty, formatBoxLine(titleStr, 67, '│', borderColor)];
163
- if (subtitle) {
164
- lines.push(formatBoxLine(`${DIM}${subtitle}${RESET}`, 67, '│', borderColor));
165
- }
166
- lines.push(empty, bottom);
167
- return lines.join('\n');
168
- }
169
-
170
- function printHelp() {
171
- console.log();
172
- console.log(cardHeader('🛡️ T O R U S G U A R D C L I', 'Autonomous Security Engine for AI-Built Applications', 'v1.3.3'));
173
- console.log(`\n ${BOLD}Usage:${RESET} ${GREEN}npx torusguard${RESET} ${WHITE}[command]${RESET} ${GRAY}[options]${RESET}\n`);
174
-
175
- console.log(cardBorderTop('Commands'));
176
- console.log(formatBoxLine(`${GREEN}init${RESET} Scaffold ${BOLD}.torusguard/${RESET} workspace + unlock slash commands`));
177
- console.log(formatBoxLine(`${GREEN}status${RESET} Display active security posture, memory, rules, & stack`));
178
- console.log(formatBoxLine(`${GREEN}rules${RESET} Auto-sync memory to AI IDE rules (.cursorrules, CLAUDE.md)`));
179
- console.log(formatBoxLine(`${GREEN}memory${RESET} Manage persistent security memory (export, hook, learn)`));
180
- console.log(formatBoxLine(`${GREEN}audit${RESET} Run static AST security scan on target project`));
181
- console.log(formatBoxLine(`${GREEN}harden${RESET} Formulate minimal candidate patches (Ponytail bounded)`));
182
- console.log(formatBoxLine(`${GREEN}apply${RESET} Apply candidate patches with automatic .bak snapshots`));
183
- console.log(formatBoxLine(`${GREEN}recheck${RESET} Targeted differential verification of applied fixes`));
184
- console.log(formatBoxLine(`${GREEN}recipes${RESET} List & inspect distilled Golden Fix Recipes in memory`));
185
- console.log(formatBoxLine(`${GREEN}rollback${RESET} Instantly revert files from latest pre-apply snapshot`));
186
- console.log(formatBoxLine(`${GREEN}report${RESET} Export OASIS SARIF v2.1.0 or single-file visual HTML report`));
187
- console.log(formatBoxLine(`${GREEN}diff-guard${RESET} Scan diffs or wire pre-commit hook (--install-hook)`));
188
- console.log(formatBoxLine(`${GREEN}help${RESET} Show this interactive command guide`));
189
- console.log(cardDivider('AI & Memory Subcommands'));
190
- console.log(formatBoxLine(`${WHITE}rules sync${RESET} ${DIM}[--format all|cursor|claude|agent|windsurf]${RESET}`));
191
- console.log(formatBoxLine(`${WHITE}report --html${RESET} ${DIM}[--out <path>] Self-contained visual HTML dashboard${RESET}`));
192
- console.log(formatBoxLine(`${WHITE}memory context${RESET} ${DIM}[--role auditor|remediator|reviewer] [--file <f>]${RESET}`));
193
- console.log(formatBoxLine(`${WHITE}memory hook${RESET} ${DIM}[install|uninstall] Git pre-commit regression hook${RESET}`));
194
- console.log(formatBoxLine(`${WHITE}memory learn${RESET} ${DIM}[--commits <range>] Ingest security commit fixes${RESET}`));
195
- console.log(formatBoxLine(`${WHITE}memory export${RESET} ${DIM}[--path <file>] [--sanitized] Safe team sharing${RESET}`));
196
- console.log(cardDivider('Options'));
197
- console.log(formatBoxLine(`${GRAY}--target <dir>${RESET} Target directory to analyze or scaffold ${DIM}(default: .)${RESET}`));
198
- console.log(formatBoxLine(`${GRAY}--force${RESET} Overwrite existing workspace and re-scaffold`));
199
- console.log(formatBoxLine(`${GRAY}--yes, -y${RESET} Non-interactive auto-approval for patch application`));
200
- console.log(cardBorderBottom());
201
-
202
- console.log(`\n ${BOLD}AI Chat Commands:${RESET}`);
203
- console.log(` ${DIM}In your AI IDE chat, use any of these slash commands:${RESET}`);
204
- console.log(` ${CYAN}/torusguard${RESET} Main orchestrator (status, audit, harden, memory)`);
205
- console.log(` ${CYAN}/torusguard-audit${RESET} Static AST security scan`);
206
- console.log(` ${CYAN}/torusguard-harden${RESET} Generate governed fix patches`);
207
- console.log(` ${CYAN}/torusguard-apply${RESET} Apply patches with rollback snapshots`);
208
- console.log(` ${CYAN}/torusguard-recheck${RESET} Differential AST fix closure verification`);
209
- console.log(` ${CYAN}/torusguard-report${RESET} Executive security posture report`);
210
- console.log(` ${CYAN}/torusguard-status${RESET} Read-only workspace diagnostic`);
211
- console.log(`\n ${DIM}Documentation:${RESET} ${CYAN}https://github.com/githubmofo/TorusGuard${RESET}`);
212
- console.log(` ${DIM}NPM Package:${RESET} ${CYAN}https://npmjs.com/package/torusguard${RESET}\n`);
213
- }
214
-
215
- if (command === 'help' || command === '--help' || command === '-h') {
216
- printHelp();
217
- process.exit(0);
218
- }
219
-
220
- if (command === 'status') {
221
- const cfgPath = path.join(cwd, '.torusguard', 'config', 'torusguard.json');
222
- if (fs.existsSync(cfgPath)) {
223
- try {
224
- const cfg = JSON.parse(fs.readFileSync(cfgPath, 'utf-8'));
225
- const stackLang = cfg.detected_stack?.language || 'Not yet detected';
226
- const stackFw = cfg.detected_stack?.framework || 'Not yet detected';
227
- const stackDb = cfg.detected_stack?.data_layer || 'Not yet detected';
228
- const stackStatus = cfg.detected_stack ? `${GREEN}Detected${RESET}` : `${YELLOW}Pending first audit${RESET}`;
229
-
230
- // Memory telemetry
231
- const memProfilePath = path.join(cwd, '.torusguard', 'memory', 'profile.json');
232
- const memContextPath = path.join(cwd, '.torusguard', 'memory', 'context.json');
233
- let memPatterns = 0;
234
- let memEvents = 0;
235
- let memTokens = 0;
236
- let memFixRate = 'N/A';
237
- if (fs.existsSync(memProfilePath)) {
238
- try {
239
- const prof = JSON.parse(fs.readFileSync(memProfilePath, 'utf-8'));
240
- memPatterns = prof.active_patterns_count || 0;
241
- memEvents = prof.total_events || 0;
242
- memFixRate = prof.fix_rate_percentage !== null && prof.fix_rate_percentage !== undefined ? `${prof.fix_rate_percentage}%` : 'N/A';
243
- } catch (e) {}
244
- }
245
- if (fs.existsSync(memContextPath)) {
246
- try {
247
- const ctx = JSON.parse(fs.readFileSync(memContextPath, 'utf-8'));
248
- memTokens = ctx.token_estimate || 0;
249
- } catch (e) {}
250
- }
251
-
252
- console.log();
253
- console.log(cardHeader('🛡️ TORUSGUARD SECURITY POSTURE', '', 'v1.3.3'));
254
- console.log(`\n ${BOLD}▸ Workspace:${RESET} ${GREEN}${cwd}${RESET}`);
255
- console.log(` ${BOLD}▸ Governance:${RESET} ${GREEN}Full Local Governance (.torusguard/)${RESET}\n`);
256
-
257
- console.log(cardBorderTop('Environment & Stack'));
258
- console.log(formatBoxLine(`Language: ${BOLD}${stackLang}${RESET}`));
259
- console.log(formatBoxLine(`Framework: ${BOLD}${stackFw}${RESET}`));
260
- console.log(formatBoxLine(`Data Layer: ${BOLD}${stackDb}${RESET}`));
261
- console.log(formatBoxLine(`Stack Detection: ${stackStatus}`));
262
- console.log(cardBorderBottom());
263
- console.log();
264
-
265
- console.log(cardBorderTop('Security Memory Engine'));
266
- console.log(formatBoxLine(`Patterns Learned: ${GREEN}${memPatterns} active patterns${RESET}`));
267
- console.log(formatBoxLine(`Events Recorded: ${WHITE}${memEvents} events (local-first log)${RESET}`));
268
- console.log(formatBoxLine(`Context Window: ${CYAN}${memTokens} / 2,000 tokens${RESET} (${Math.round((memTokens / 2000) * 100)}% utilized)`));
269
- console.log(formatBoxLine(`Fix Velocity Rate: ${YELLOW}${memFixRate}${RESET}`));
270
- console.log(cardBorderBottom());
271
- console.log();
272
-
273
- console.log(cardBorderTop('Governance Telemetry'));
274
- console.log(formatBoxLine(`Rules Catalog: ${GREEN}71 Canonical Security Rules${RESET} (11 families)`));
275
- console.log(formatBoxLine(`Severity Floor: ${YELLOW}${cfg.severity_threshold || 'medium'}${RESET}`));
276
- console.log(formatBoxLine(`Runs Directory: ${DIM}${cfg.runs_dir || '.torusguard/runs'}${RESET}`));
277
- console.log(formatBoxLine(`Ponytail Bounds: ${GREEN}<= 35 additions, <= 25 deletions${RESET}`));
278
- console.log(cardBorderBottom());
279
- console.log();
280
-
281
- console.log(cardBorderTop('Rule Families'));
282
- console.log(formatBoxLine(`${YELLOW}TG-SEC${RESET} Secrets & Credentials ${YELLOW}TG-DB${RESET} Database Safety`));
283
- console.log(formatBoxLine(`${YELLOW}TG-INPUT${RESET} Input Validation ${YELLOW}TG-AUTH${RESET} Authentication`));
284
- console.log(formatBoxLine(`${YELLOW}TG-CLIENT${RESET} Client Bundle Leaks ${YELLOW}TG-DIFF${RESET} Diff Inspection`));
285
- console.log(formatBoxLine(`${YELLOW}TG-AGENT${RESET} AI Agent Security ${YELLOW}TG-EDGE${RESET} Serverless`));
286
- console.log(formatBoxLine(`${YELLOW}TG-SUPPLY${RESET} Supply Chain & CI/CD ${YELLOW}TG-SSRF${RESET} Outbound Net`));
287
- console.log(formatBoxLine(`${YELLOW}TG-BIZ${RESET} Business Logic`));
288
- console.log(cardBorderBottom());
289
-
290
- console.log(`\n ${DIM}Quick Action:${RESET} In your AI chat, run ${CYAN}/torusguard-audit${RESET} to scan.\n`);
291
- process.exit(0);
292
- } catch (e) {
293
- // Fallback to python runner
294
- }
295
- } else {
296
- console.log(`
297
- ${YELLOW}⚠${RESET} No .torusguard workspace found in current directory.
298
- Run ${GREEN}npx torusguard init${RESET} to scaffold full workspace governance.
299
- `);
300
- process.exit(0);
301
- }
302
- }
303
-
304
- // Subcommand: memory
305
- if (command === 'memory') {
306
- const sub = args[1] || 'status';
307
- const memScript = path.join(cwd, '.torusguard', 'scripts', 'memory_engine.py');
308
- const fallbackMemScript = path.join(rootDir, '.torusguard', 'scripts', 'memory_engine.py');
309
- const actualMemScript = fs.existsSync(memScript) ? memScript : fallbackMemScript;
310
-
311
- let pyArgs = [actualMemScript];
312
- if (sub === 'status') {
313
- pyArgs.push('--action', 'status');
314
- } else if (sub === 'context') {
315
- pyArgs.push('--action', 'context');
316
- const roleIdx = args.indexOf('--role');
317
- if (roleIdx !== -1 && args[roleIdx + 1]) {
318
- pyArgs.push('--role', args[roleIdx + 1]);
319
- }
320
- const fileIdx = args.indexOf('--file') !== -1 ? args.indexOf('--file') : args.indexOf('-f');
321
- if (fileIdx !== -1 && args[fileIdx + 1]) {
322
- pyArgs.push('--file', args[fileIdx + 1]);
323
- }
324
- const ruleIdx = args.indexOf('--rule') !== -1 ? args.indexOf('--rule') : args.indexOf('-r');
325
- if (ruleIdx !== -1 && args[ruleIdx + 1]) {
326
- pyArgs.push('--rule-id', args[ruleIdx + 1]);
327
- }
328
- } else if (sub === 'distill') {
329
- pyArgs.push('--action', 'distill');
330
- } else if (sub === 'decay') {
331
- pyArgs.push('--action', 'decay');
332
- const ttlIdx = args.indexOf('--ttl');
333
- if (ttlIdx !== -1 && args[ttlIdx + 1]) {
334
- pyArgs.push('--ttl', args[ttlIdx + 1]);
335
- }
336
- } else if (sub === 'compact') {
337
- pyArgs.push('--action', 'compact');
338
- const ageIdx = args.indexOf('--older-than');
339
- if (ageIdx !== -1 && args[ageIdx + 1]) {
340
- pyArgs.push('--older-than', args[ageIdx + 1]);
341
- }
342
- } else if (sub === 'export') {
343
- pyArgs.push('--action', 'export');
344
- const pathIdx = args.indexOf('--path') !== -1 ? args.indexOf('--path') : args.indexOf('-p');
345
- if (pathIdx !== -1 && args[pathIdx + 1]) {
346
- pyArgs.push('--target', args[pathIdx + 1]);
347
- } else {
348
- pyArgs.push('--target', 'torusguard-memory-export.json');
349
- }
350
- if (args.includes('--sanitized')) {
351
- pyArgs.push('--sanitized');
352
- }
353
- } else if (sub === 'import') {
354
- pyArgs.push('--action', 'import');
355
- const pathIdx = args.indexOf('--path') !== -1 ? args.indexOf('--path') : args.indexOf('-p');
356
- if (pathIdx !== -1 && args[pathIdx + 1]) {
357
- pyArgs.push('--source', args[pathIdx + 1]);
358
- } else {
359
- console.error(`${RED}Error: --path <file> is required for memory import${RESET}`);
360
- process.exit(1);
361
- }
362
- } else if (sub === 'hook') {
363
- const hookAction = args[2] === 'uninstall' ? 'hook-uninstall' : 'hook-install';
364
- pyArgs.push('--action', hookAction);
365
- } else if (sub === 'learn') {
366
- pyArgs.push('--action', 'learn');
367
- const commitsIdx = args.indexOf('--commits');
368
- if (commitsIdx !== -1 && args[commitsIdx + 1]) {
369
- pyArgs.push('--commits', args[commitsIdx + 1]);
370
- }
371
- const daysIdx = args.indexOf('--days');
372
- if (daysIdx !== -1 && args[daysIdx + 1]) {
373
- pyArgs.push('--days', args[daysIdx + 1]);
374
- }
375
- } else if (sub === 'recipe') {
376
- pyArgs.push('--action', 'recipe', ...args.slice(2));
377
- } else if (sub === 'fp') {
378
- pyArgs.push('--action', 'fp');
379
- const ruleIdx = args.indexOf('--rule') !== -1 ? args.indexOf('--rule') : args.indexOf('-r');
380
- if (ruleIdx !== -1 && args[ruleIdx + 1]) {
381
- pyArgs.push('--rule-id', args[ruleIdx + 1]);
382
- } else {
383
- console.error(`${RED}Error: --rule <rule_id> is required for fp suppression${RESET}`);
384
- process.exit(1);
385
- }
386
- const reasonIdx = args.indexOf('--reason');
387
- if (reasonIdx !== -1 && args[reasonIdx + 1]) {
388
- pyArgs.push('--reason', args[reasonIdx + 1]);
389
- }
390
- } else {
391
- pyArgs.push('--action', sub, ...args.slice(2));
392
- }
393
-
394
- const proc = spawnSync(pythonCmd, pyArgs, { stdio: 'inherit', cwd });
395
- process.exit(proc.status !== null ? proc.status : 0);
396
- }
397
-
398
- // Subcommand: diff-guard
399
- if (command === 'diff-guard') {
400
- const diffScript = path.join(cwd, '.torusguard', 'scripts', 'diff_guard.py');
401
- const fallbackDiffScript = path.join(rootDir, '.torusguard', 'scripts', 'diff_guard.py');
402
- const actualDiffScript = fs.existsSync(fallbackDiffScript) ? fallbackDiffScript : diffScript;
403
-
404
- const proc = spawnSync(pythonCmd, [actualDiffScript, ...args.slice(1)], { stdio: 'inherit', cwd });
405
- process.exit(proc.status !== null ? proc.status : 0);
406
- }
407
-
408
- // Subcommand: rules
409
- if (command === 'rules') {
410
- const sub = args[1] || 'sync';
411
- const rulesScript = path.join(cwd, '.torusguard', 'scripts', 'rules_sync.py');
412
- const fallbackRulesScript = path.join(rootDir, '.torusguard', 'scripts', 'rules_sync.py');
413
- const actualRulesScript = fs.existsSync(fallbackRulesScript) ? fallbackRulesScript : rulesScript;
414
-
415
- let pyArgs = [actualRulesScript];
416
- if (sub === 'sync') {
417
- const fmtIdx = args.indexOf('--format');
418
- if (fmtIdx !== -1 && args[fmtIdx + 1]) {
419
- pyArgs.push('--format', args[fmtIdx + 1]);
420
- }
421
- const rootIdx = args.indexOf('--root') !== -1 ? args.indexOf('--root') : args.indexOf('--target');
422
- if (rootIdx !== -1 && args[rootIdx + 1]) {
423
- pyArgs.push('--root', args[rootIdx + 1]);
424
- }
425
- if (args.includes('--json')) {
426
- pyArgs.push('--json');
427
- }
428
- } else {
429
- pyArgs.push(...args.slice(1));
430
- }
431
-
432
- const proc = spawnSync(pythonCmd, pyArgs, { stdio: 'inherit', cwd });
433
- process.exit(proc.status !== null ? proc.status : 0);
434
- }
435
-
436
- // Subcommand: audit
437
- if (command === 'audit') {
438
- const auditScript = path.join(cwd, '.torusguard', 'scripts', 'audit_runner.py');
439
- const fallbackAuditScript = path.join(rootDir, '.torusguard', 'scripts', 'audit_runner.py');
440
- const actualAuditScript = fs.existsSync(fallbackAuditScript) ? fallbackAuditScript : auditScript;
441
-
442
- const proc = spawnSync(pythonCmd, [actualAuditScript, ...args.slice(1)], { stdio: 'inherit', cwd });
443
- process.exit(proc.status !== null ? proc.status : 0);
444
- }
445
-
446
- // Subcommand: report
447
- if (command === 'report') {
448
- if (args.includes('--html') || args[1] === 'html') {
449
- const htmlScript = path.join(cwd, '.torusguard', 'scripts', 'html_reporter.py');
450
- const fallbackHtmlScript = path.join(rootDir, '.torusguard', 'scripts', 'html_reporter.py');
451
- const actualHtmlScript = fs.existsSync(fallbackHtmlScript) ? fallbackHtmlScript : htmlScript;
452
-
453
- let pyArgs = [actualHtmlScript];
454
- const outIdx = args.indexOf('--out');
455
- if (outIdx !== -1 && args[outIdx + 1]) {
456
- pyArgs.push('--out', args[outIdx + 1]);
457
- }
458
- const rootIdx = args.indexOf('--root') !== -1 ? args.indexOf('--root') : args.indexOf('--target');
459
- if (rootIdx !== -1 && args[rootIdx + 1]) {
460
- pyArgs.push('--root', args[rootIdx + 1]);
461
- }
462
- if (args.includes('--json')) {
463
- pyArgs.push('--json');
464
- }
465
-
466
- const proc = spawnSync(pythonCmd, pyArgs, { stdio: 'inherit', cwd });
467
- process.exit(proc.status !== null ? proc.status : 0);
468
- } else {
469
- const sarifScript = path.join(cwd, '.torusguard', 'scripts', 'sarif_exporter.py');
470
- const fallbackSarifScript = path.join(rootDir, '.torusguard', 'scripts', 'sarif_exporter.py');
471
- const actualSarifScript = fs.existsSync(fallbackSarifScript) ? fallbackSarifScript : sarifScript;
472
-
473
- const proc = spawnSync(pythonCmd, [actualSarifScript, ...args.slice(1)], { stdio: 'inherit', cwd });
474
- process.exit(proc.status !== null ? proc.status : 0);
475
- }
476
- }
477
-
478
- // Subcommand: harden
479
- if (command === 'harden') {
480
- const scriptPath = path.join(cwd, '.torusguard', 'scripts', 'harden_runner.py');
481
- const fallbackScript = path.join(rootDir, '.torusguard', 'scripts', 'harden_runner.py');
482
- const actualScript = fs.existsSync(fallbackScript) ? fallbackScript : scriptPath;
483
-
484
- const proc = spawnSync(pythonCmd, [actualScript, ...args.slice(1)], { stdio: 'inherit', cwd });
485
- process.exit(proc.status !== null ? proc.status : 0);
486
- }
487
-
488
- // Subcommand: apply
489
- if (command === 'apply') {
490
- const scriptPath = path.join(cwd, '.torusguard', 'scripts', 'apply_runner.py');
491
- const fallbackScript = path.join(rootDir, '.torusguard', 'scripts', 'apply_runner.py');
492
- const actualScript = fs.existsSync(fallbackScript) ? fallbackScript : scriptPath;
493
-
494
- const proc = spawnSync(pythonCmd, [actualScript, ...args.slice(1)], { stdio: 'inherit', cwd });
495
- process.exit(proc.status !== null ? proc.status : 0);
496
- }
497
-
498
- // Subcommand: rollback
499
- if (command === 'rollback') {
500
- const scriptPath = path.join(cwd, '.torusguard', 'scripts', 'apply_runner.py');
501
- const fallbackScript = path.join(rootDir, '.torusguard', 'scripts', 'apply_runner.py');
502
- const actualScript = fs.existsSync(fallbackScript) ? fallbackScript : scriptPath;
503
-
504
- const proc = spawnSync(pythonCmd, [actualScript, '--rollback', ...args.slice(1)], { stdio: 'inherit', cwd });
505
- process.exit(proc.status !== null ? proc.status : 0);
506
- }
507
-
508
- // Subcommand: recheck / verify
509
- if (command === 'recheck' || command === 'verify') {
510
- const scriptPath = path.join(cwd, '.torusguard', 'scripts', 'recheck_runner.py');
511
- const fallbackScript = path.join(rootDir, '.torusguard', 'scripts', 'recheck_runner.py');
512
- const actualScript = fs.existsSync(fallbackScript) ? fallbackScript : scriptPath;
513
-
514
- const proc = spawnSync(pythonCmd, [actualScript, ...args.slice(1)], { stdio: 'inherit', cwd });
515
- process.exit(proc.status !== null ? proc.status : 0);
516
- }
517
-
518
- // Subcommand: recipes
519
- if (command === 'recipes') {
520
- const scriptPath = path.join(cwd, '.torusguard', 'scripts', 'recipes_runner.py');
521
- const fallbackScript = path.join(rootDir, '.torusguard', 'scripts', 'recipes_runner.py');
522
- const actualScript = fs.existsSync(fallbackScript) ? fallbackScript : scriptPath;
523
-
524
- const proc = spawnSync(pythonCmd, [actualScript, ...args.slice(1)], { stdio: 'inherit', cwd });
525
- process.exit(proc.status !== null ? proc.status : 0);
526
- }
527
-
528
- // Subcommand: init (scaffold workspace)
529
- if (command === 'init') {
530
- const localBootstrap = path.join(rootDir, 'skills', 'torusguard', 'bootstrap.py');
531
- const localInstall = path.join(rootDir, 'install.py');
532
- const scriptToRun = fs.existsSync(localBootstrap) ? localBootstrap : localInstall;
533
-
534
- const scriptArgs = [scriptToRun, '--full-commands', ...args.slice(1)];
535
- const proc = spawnSync(pythonCmd, scriptArgs, {
536
- stdio: 'inherit',
537
- cwd: cwd,
538
- });
539
- process.exit(proc.status !== null ? proc.status : 0);
540
- }
541
-
542
- // Unknown command fallback
543
- console.error(`\n ${RED}✖ Unknown command:${RESET} ${WHITE}${command}${RESET}`);
544
- console.error(` Run ${GREEN}npx torusguard help${RESET} for available commands.\n`);
545
- process.exit(1);
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * TorusGuard CLI Runner (NPM Distribution)
5
+ * Provides zero-dependency command orchestration for TorusGuard governance.
6
+ * Adheres to Ponytail principles: concise, surgical, zero fluff.
7
+ */
8
+
9
+ const { spawnSync } = require('child_process');
10
+ const path = require('path');
11
+ const fs = require('fs');
12
+
13
+ const args = process.argv.slice(2);
14
+ const command = args[0] || 'init';
15
+ const rootDir = path.resolve(__dirname, '..');
16
+ const cwd = process.cwd();
17
+
18
+ // ─── ANSI Color Helpers ───────────────────────────────────────────────────────
19
+ const BOLD = '\x1b[1m';
20
+ const DIM = '\x1b[2m';
21
+ const RESET = '\x1b[0m';
22
+ const CYAN = '\x1b[36m';
23
+ const GREEN = '\x1b[32m';
24
+ const YELLOW = '\x1b[33m';
25
+ const WHITE = '\x1b[97m';
26
+ const GRAY = '\x1b[90m';
27
+ const RED = '\x1b[31m';
28
+
29
+ // Detect available Python binary (python or python3)
30
+ let pythonCmd = 'python';
31
+ try {
32
+ const check = spawnSync('python', ['--version'], { encoding: 'utf-8' });
33
+ if (check.status !== 0) {
34
+ const check3 = spawnSync('python3', ['--version'], { encoding: 'utf-8' });
35
+ if (check3.status === 0) {
36
+ pythonCmd = 'python3';
37
+ }
38
+ }
39
+ } catch (e) {
40
+ try {
41
+ const check3 = spawnSync('python3', ['--version'], { encoding: 'utf-8' });
42
+ if (check3.status === 0) {
43
+ pythonCmd = 'python3';
44
+ }
45
+ } catch (err) {}
46
+ }
47
+
48
+ const ANSI_REGEX = /\x1b\[[0-9;]*m/g;
49
+
50
+ function getVisualWidth(text) {
51
+ const clean = text.replace(ANSI_REGEX, '');
52
+ let width = 0;
53
+ for (let i = 0; i < clean.length; i++) {
54
+ const cp = clean.codePointAt(i);
55
+ if (cp > 0xffff) i++;
56
+ if ((cp >= 0xfe00 && cp <= 0xfe0f) || (cp >= 0xe0100 && cp <= 0xe01ef)) continue;
57
+ if (cp === 0x200b || cp === 0x200c || cp === 0x200d || cp === 0x00ad) continue;
58
+ if (cp >= 0x1f300 || (cp >= 0x1100 && (
59
+ cp <= 0x115f || cp === 0x2329 || cp === 0x232a ||
60
+ (cp >= 0x2e80 && cp <= 0xa4cf && cp !== 0x303f) ||
61
+ (cp >= 0xac00 && cp <= 0xd7a3) ||
62
+ (cp >= 0xf900 && cp <= 0xfaff) ||
63
+ (cp >= 0xfe10 && cp <= 0xfe19) ||
64
+ (cp >= 0xfe30 && cp <= 0xfe6f) ||
65
+ (cp >= 0xff00 && cp <= 0xff60) ||
66
+ (cp >= 0xffe0 && cp <= 0xffe6)
67
+ ))) {
68
+ width += 2;
69
+ } else {
70
+ width += 1;
71
+ }
72
+ }
73
+ return width;
74
+ }
75
+
76
+ function truncateVisual(text, maxW = 67) {
77
+ if (getVisualWidth(text) <= maxW) return text;
78
+ let currW = 0;
79
+ let out = '';
80
+ let inAnsi = false;
81
+ let ansiBuf = '';
82
+ for (let i = 0; i < text.length; i++) {
83
+ const ch = text[i];
84
+ if (ch === '\x1b') {
85
+ inAnsi = true;
86
+ ansiBuf = ch;
87
+ continue;
88
+ }
89
+ if (inAnsi) {
90
+ ansiBuf += ch;
91
+ if (ch === 'm') {
92
+ inAnsi = false;
93
+ out += ansiBuf;
94
+ }
95
+ continue;
96
+ }
97
+ const cp = text.codePointAt(i);
98
+ if (cp > 0xffff) i++;
99
+ if ((cp >= 0xfe00 && cp <= 0xfe0f) || (cp >= 0xe0100 && cp <= 0xe01ef) || cp === 0x200b || cp === 0x200c || cp === 0x200d || cp === 0x00ad) {
100
+ out += String.fromCodePoint(cp);
101
+ continue;
102
+ }
103
+ const cw = cp >= 0x1f300 ? 2 : 1;
104
+ if (currW + cw > maxW - 3) {
105
+ out += '\x1b[0m...';
106
+ currW += 3;
107
+ break;
108
+ }
109
+ out += String.fromCodePoint(cp);
110
+ currW += cw;
111
+ }
112
+ out += '\x1b[0m';
113
+ return out;
114
+ }
115
+
116
+ function formatBoxLine(content, width = 67, border = '│', borderColor = CYAN) {
117
+ const trunc = truncateVisual(content, width);
118
+ const vis = getVisualWidth(trunc);
119
+ const pad = ' '.repeat(Math.max(0, width - vis));
120
+ return ` ${borderColor}${border}${RESET} ${trunc}${pad} ${borderColor}${border}${RESET}`;
121
+ }
122
+
123
+ function cardBorderTop(title = '', borderColor = CYAN, double = false) {
124
+ const left = double ? '╔' : '┌';
125
+ const right = double ? '╗' : '┐';
126
+ const h = double ? '═' : '─';
127
+ if (title) {
128
+ const vis = getVisualWidth(title);
129
+ const rem = Math.max(0, 68 - vis);
130
+ return ` ${borderColor}${left}${h} ${BOLD}${WHITE}${title}${RESET}${borderColor} ${h.repeat(rem)}${right}${RESET}`;
131
+ }
132
+ return ` ${borderColor}${left}${h.repeat(71)}${right}${RESET}`;
133
+ }
134
+
135
+ function cardBorderBottom(borderColor = CYAN, double = false) {
136
+ const left = double ? '╚' : '└';
137
+ const right = double ? '╝' : '┘';
138
+ const h = double ? '═' : '─';
139
+ return ` ${borderColor}${left}${h.repeat(71)}${right}${RESET}`;
140
+ }
141
+
142
+ function cardDivider(title = '', borderColor = CYAN, double = false) {
143
+ const left = double ? '╠' : '├';
144
+ const right = double ? '╣' : '┤';
145
+ const h = double ? '═' : '─';
146
+ if (title) {
147
+ const vis = getVisualWidth(title);
148
+ const rem = Math.max(0, 68 - vis);
149
+ return ` ${borderColor}${left}${h} ${BOLD}${WHITE}${title}${RESET}${borderColor} ${h.repeat(rem)}${right}${RESET}`;
150
+ }
151
+ return ` ${borderColor}${left}${h.repeat(71)}${right}${RESET}`;
152
+ }
153
+
154
+ function cardHeader(title, subtitle = '', version = 'v1.3.6', borderColor = CYAN) {
155
+ const top = ` ${borderColor}╭${'─'.repeat(71)}╮${RESET}`;
156
+ const bottom = ` ${borderColor}╰${'─'.repeat(71)}╯${RESET}`;
157
+ const empty = ` ${borderColor}│${' '.repeat(71)}│${RESET}`;
158
+ const titleVis = getVisualWidth(title);
159
+ const verVis = getVisualWidth(version);
160
+ const spaceCount = Math.max(1, 67 - titleVis - verVis);
161
+ const titleStr = `${BOLD}${WHITE}${title}${RESET}${' '.repeat(spaceCount)}${GRAY}${version}${RESET}`;
162
+ const lines = [top, empty, formatBoxLine(titleStr, 67, '│', borderColor)];
163
+ if (subtitle) {
164
+ lines.push(formatBoxLine(`${DIM}${subtitle}${RESET}`, 67, '│', borderColor));
165
+ }
166
+ lines.push(empty, bottom);
167
+ return lines.join('\n');
168
+ }
169
+
170
+ function printHelp() {
171
+ console.log();
172
+ console.log(cardHeader('🛡️ T O R U S G U A R D C L I', 'Autonomous Security Engine for AI-Built Applications', 'v1.3.6'));
173
+ console.log(`\n ${BOLD}Usage:${RESET} ${GREEN}npx torusguard${RESET} ${WHITE}[command]${RESET} ${GRAY}[options]${RESET}\n`);
174
+
175
+ console.log(cardBorderTop('Commands'));
176
+ console.log(formatBoxLine(`${GREEN}init${RESET} Scaffold ${BOLD}.torusguard/${RESET} workspace + unlock slash commands`));
177
+ console.log(formatBoxLine(`${GREEN}status${RESET} Display active security posture, memory, rules, & stack`));
178
+ console.log(formatBoxLine(`${GREEN}rules${RESET} Auto-sync memory to AI IDE rules (.cursorrules, CLAUDE.md)`));
179
+ console.log(formatBoxLine(`${GREEN}memory${RESET} Manage persistent security memory (export, hook, learn)`));
180
+ console.log(formatBoxLine(`${GREEN}audit${RESET} Run static AST security scan on target project`));
181
+ console.log(formatBoxLine(`${GREEN}harden${RESET} Formulate minimal candidate patches (Ponytail bounded)`));
182
+ console.log(formatBoxLine(`${GREEN}apply${RESET} Apply candidate patches with automatic .bak snapshots`));
183
+ console.log(formatBoxLine(`${GREEN}recheck${RESET} Targeted differential verification of applied fixes`));
184
+ console.log(formatBoxLine(`${GREEN}recipes${RESET} List & inspect distilled Golden Fix Recipes in memory`));
185
+ console.log(formatBoxLine(`${GREEN}rollback${RESET} Instantly revert files from latest pre-apply snapshot`));
186
+ console.log(formatBoxLine(`${GREEN}report${RESET} Export OASIS SARIF v2.1.0 or single-file visual HTML report`));
187
+ console.log(formatBoxLine(`${GREEN}diff-guard${RESET} Scan diffs or wire pre-commit hook (--install-hook)`));
188
+ console.log(formatBoxLine(`${GREEN}help${RESET} Show this interactive command guide`));
189
+ console.log(cardDivider('AI & Memory Subcommands'));
190
+ console.log(formatBoxLine(`${WHITE}rules sync${RESET} ${DIM}[--format all|cursor|claude|agent|windsurf]${RESET}`));
191
+ console.log(formatBoxLine(`${WHITE}report --html${RESET} ${DIM}[--out <path>] Self-contained visual HTML dashboard${RESET}`));
192
+ console.log(formatBoxLine(`${WHITE}report --sarif${RESET} ${DIM}[--out <path>] OASIS SARIF v2.1.0 GitHub Scanning log${RESET}`));
193
+ console.log(formatBoxLine(`${WHITE}memory context${RESET} ${DIM}[--role auditor|remediator|reviewer] [--file <f>]${RESET}`));
194
+ console.log(formatBoxLine(`${WHITE}memory hook${RESET} ${DIM}[install|uninstall] Git pre-commit regression hook${RESET}`));
195
+ console.log(formatBoxLine(`${WHITE}memory learn${RESET} ${DIM}[--commits <range>] Ingest security commit fixes${RESET}`));
196
+ console.log(formatBoxLine(`${WHITE}memory export${RESET} ${DIM}[--path <file>] [--sanitized] Safe team sharing${RESET}`));
197
+ console.log(cardDivider('Options'));
198
+ console.log(formatBoxLine(`${GRAY}--target <dir>${RESET} Target directory to analyze or scaffold ${DIM}(default: .)${RESET}`));
199
+ console.log(formatBoxLine(`${GRAY}--force${RESET} Overwrite existing workspace and re-scaffold`));
200
+ console.log(formatBoxLine(`${GRAY}--yes, -y${RESET} Non-interactive auto-approval for patch application`));
201
+ console.log(cardBorderBottom());
202
+
203
+ console.log(`\n ${BOLD}AI Chat Commands:${RESET}`);
204
+ console.log(` ${DIM}In your AI IDE chat, use any of these slash commands:${RESET}`);
205
+ console.log(` ${CYAN}/torusguard${RESET} Main orchestrator (status, audit, harden, memory)`);
206
+ console.log(` ${CYAN}/torusguard-audit${RESET} Static AST security scan`);
207
+ console.log(` ${CYAN}/torusguard-harden${RESET} Generate governed fix patches`);
208
+ console.log(` ${CYAN}/torusguard-apply${RESET} Apply patches with rollback snapshots`);
209
+ console.log(` ${CYAN}/torusguard-recheck${RESET} Differential AST fix closure verification`);
210
+ console.log(` ${CYAN}/torusguard-report${RESET} Executive security posture report`);
211
+ console.log(` ${CYAN}/torusguard-status${RESET} Read-only workspace diagnostic`);
212
+ console.log(`\n ${DIM}Documentation:${RESET} ${CYAN}https://github.com/githubmofo/TorusGuard${RESET}`);
213
+ console.log(` ${DIM}NPM Package:${RESET} ${CYAN}https://npmjs.com/package/torusguard${RESET}\n`);
214
+ }
215
+
216
+ if (command === 'help' || command === '--help' || command === '-h') {
217
+ printHelp();
218
+ process.exit(0);
219
+ }
220
+
221
+ if (command === 'status') {
222
+ const cfgPath = path.join(cwd, '.torusguard', 'config', 'torusguard.json');
223
+ if (fs.existsSync(cfgPath)) {
224
+ try {
225
+ const cfg = JSON.parse(fs.readFileSync(cfgPath, 'utf-8'));
226
+ const stackLang = cfg.detected_stack?.language || 'Not yet detected';
227
+ const stackFw = cfg.detected_stack?.framework || 'Not yet detected';
228
+ const stackDb = cfg.detected_stack?.data_layer || 'Not yet detected';
229
+ const stackStatus = cfg.detected_stack ? `${GREEN}Detected${RESET}` : `${YELLOW}Pending first audit${RESET}`;
230
+
231
+ // Memory telemetry
232
+ const memProfilePath = path.join(cwd, '.torusguard', 'memory', 'profile.json');
233
+ const memContextPath = path.join(cwd, '.torusguard', 'memory', 'context.json');
234
+ let memPatterns = 0;
235
+ let memEvents = 0;
236
+ let memTokens = 0;
237
+ let memFixRate = 'N/A';
238
+ if (fs.existsSync(memProfilePath)) {
239
+ try {
240
+ const prof = JSON.parse(fs.readFileSync(memProfilePath, 'utf-8'));
241
+ memPatterns = prof.active_patterns_count || 0;
242
+ memEvents = prof.total_events || 0;
243
+ memFixRate = prof.fix_rate_percentage !== null && prof.fix_rate_percentage !== undefined ? `${prof.fix_rate_percentage}%` : 'N/A';
244
+ } catch (e) {}
245
+ }
246
+ if (fs.existsSync(memContextPath)) {
247
+ try {
248
+ const ctx = JSON.parse(fs.readFileSync(memContextPath, 'utf-8'));
249
+ memTokens = ctx.token_estimate || 0;
250
+ } catch (e) {}
251
+ }
252
+
253
+ console.log();
254
+ console.log(cardHeader('🛡️ TORUSGUARD SECURITY POSTURE', '', 'v1.3.6'));
255
+ console.log(`\n ${BOLD}▸ Workspace:${RESET} ${GREEN}${cwd}${RESET}`);
256
+ console.log(` ${BOLD}▸ Governance:${RESET} ${GREEN}Full Local Governance (.torusguard/)${RESET}`);
257
+ console.log(` ${BOLD}▸ Living Report:${RESET} ${CYAN}security_report.md${RESET}\n`);
258
+
259
+ console.log(cardBorderTop('Environment & Stack'));
260
+ console.log(formatBoxLine(`Language: ${BOLD}${stackLang}${RESET}`));
261
+ console.log(formatBoxLine(`Framework: ${BOLD}${stackFw}${RESET}`));
262
+ console.log(formatBoxLine(`Data Layer: ${BOLD}${stackDb}${RESET}`));
263
+ console.log(formatBoxLine(`Stack Detection: ${stackStatus}`));
264
+ console.log(cardBorderBottom());
265
+ console.log();
266
+
267
+ console.log(cardBorderTop('Security Memory Engine'));
268
+ console.log(formatBoxLine(`Patterns Learned: ${GREEN}${memPatterns} active patterns${RESET}`));
269
+ console.log(formatBoxLine(`Events Recorded: ${WHITE}${memEvents} events (local-first log)${RESET}`));
270
+ console.log(formatBoxLine(`Context Window: ${CYAN}${memTokens} / 2,000 tokens${RESET} (${Math.round((memTokens / 2000) * 100)}% utilized)`));
271
+ console.log(formatBoxLine(`Fix Velocity Rate: ${YELLOW}${memFixRate}${RESET}`));
272
+ console.log(cardBorderBottom());
273
+ console.log();
274
+
275
+ console.log(cardBorderTop('Governance Telemetry'));
276
+ console.log(formatBoxLine(`Rules Catalog: ${GREEN}74 Canonical Security Rules${RESET} (18 families)`));
277
+ console.log(formatBoxLine(`Severity Floor: ${YELLOW}${cfg.severity_threshold || 'medium'}${RESET}`));
278
+ console.log(formatBoxLine(`Runs Directory: ${DIM}${cfg.runs_dir || '.torusguard/runs'}${RESET}`));
279
+ console.log(formatBoxLine(`Ponytail Bounds: ${GREEN}<= 35 additions, <= 25 deletions${RESET}`));
280
+ console.log(formatBoxLine(`Living Report: ${CYAN}security_report.md (ground truth)${RESET}`));
281
+ console.log(cardBorderBottom());
282
+ console.log();
283
+
284
+ console.log(cardBorderTop('Rule Families (18 Families / 74 Rules)'));
285
+ console.log(formatBoxLine(`${YELLOW}TG-SEC${RESET} Secrets (7) ${YELLOW}TG-DB${RESET} Database Safety (4)`));
286
+ console.log(formatBoxLine(`${YELLOW}TG-INPUT${RESET} Input Validation (6) ${YELLOW}TG-AUTH${RESET} Authentication (8)`));
287
+ console.log(formatBoxLine(`${YELLOW}TG-RATE${RESET} Rate Limiting (3) ${YELLOW}TG-AGENT${RESET} AI Agent Security (4)`));
288
+ console.log(formatBoxLine(`${YELLOW}TG-SSRF${RESET} Server Request (4) ${YELLOW}TG-WEBHOOK${RESET} Webhook Trust (4)`));
289
+ console.log(formatBoxLine(`${YELLOW}TG-WS${RESET} WebSocket (4) ${YELLOW}TG-CSRF${RESET} Cross-Site Request (2)`));
290
+ console.log(formatBoxLine(`${YELLOW}TG-GQL${RESET} GraphQL Security (4) ${YELLOW}TG-SUPPLY${RESET} Supply Chain (6)`));
291
+ console.log(formatBoxLine(`${YELLOW}TG-BIZ${RESET} Business Logic (4) ${YELLOW}TG-CACHE${RESET} Cache Isolation (3)`));
292
+ console.log(formatBoxLine(`${YELLOW}TG-CLIENT${RESET} Client Bundles (2) ${YELLOW}TG-PLATFORM${RESET}Platform Config (4)`));
293
+ console.log(formatBoxLine(`${YELLOW}TG-DIFF${RESET} Diff Inspection (3) ${YELLOW}TG-EDGE${RESET} Serverless / Edge (2)`));
294
+ console.log(cardBorderBottom());
295
+
296
+ console.log(`\n ${DIM}Quick Action:${RESET} In your AI chat, run ${CYAN}/torusguard-audit${RESET} to scan.\n`);
297
+ process.exit(0);
298
+ } catch (e) {
299
+ // Fallback to python runner
300
+ }
301
+ } else {
302
+ console.log(`
303
+ ${YELLOW}⚠${RESET} No .torusguard workspace found in current directory.
304
+ Run ${GREEN}npx torusguard init${RESET} to scaffold full workspace governance.
305
+ `);
306
+ process.exit(0);
307
+ }
308
+ }
309
+
310
+ // Subcommand: memory
311
+ if (command === 'memory') {
312
+ const sub = args[1] || 'status';
313
+ const memScript = path.join(cwd, '.torusguard', 'scripts', 'memory_engine.py');
314
+ const fallbackMemScript = path.join(rootDir, '.torusguard', 'scripts', 'memory_engine.py');
315
+ const actualMemScript = fs.existsSync(memScript) ? memScript : fallbackMemScript;
316
+
317
+ let pyArgs = [actualMemScript];
318
+ if (sub === 'status') {
319
+ pyArgs.push('--action', 'status');
320
+ } else if (sub === 'context') {
321
+ pyArgs.push('--action', 'context');
322
+ const roleIdx = args.indexOf('--role');
323
+ if (roleIdx !== -1 && args[roleIdx + 1]) {
324
+ pyArgs.push('--role', args[roleIdx + 1]);
325
+ }
326
+ const fileIdx = args.indexOf('--file') !== -1 ? args.indexOf('--file') : args.indexOf('-f');
327
+ if (fileIdx !== -1 && args[fileIdx + 1]) {
328
+ pyArgs.push('--file', args[fileIdx + 1]);
329
+ }
330
+ const ruleIdx = args.indexOf('--rule') !== -1 ? args.indexOf('--rule') : args.indexOf('-r');
331
+ if (ruleIdx !== -1 && args[ruleIdx + 1]) {
332
+ pyArgs.push('--rule-id', args[ruleIdx + 1]);
333
+ }
334
+ } else if (sub === 'distill') {
335
+ pyArgs.push('--action', 'distill');
336
+ } else if (sub === 'decay') {
337
+ pyArgs.push('--action', 'decay');
338
+ const ttlIdx = args.indexOf('--ttl');
339
+ if (ttlIdx !== -1 && args[ttlIdx + 1]) {
340
+ pyArgs.push('--ttl', args[ttlIdx + 1]);
341
+ }
342
+ } else if (sub === 'compact') {
343
+ pyArgs.push('--action', 'compact');
344
+ const ageIdx = args.indexOf('--older-than');
345
+ if (ageIdx !== -1 && args[ageIdx + 1]) {
346
+ pyArgs.push('--older-than', args[ageIdx + 1]);
347
+ }
348
+ } else if (sub === 'export') {
349
+ pyArgs.push('--action', 'export');
350
+ const pathIdx = args.indexOf('--path') !== -1 ? args.indexOf('--path') : args.indexOf('-p');
351
+ if (pathIdx !== -1 && args[pathIdx + 1]) {
352
+ pyArgs.push('--target', args[pathIdx + 1]);
353
+ } else {
354
+ pyArgs.push('--target', 'torusguard-memory-export.json');
355
+ }
356
+ if (args.includes('--sanitized')) {
357
+ pyArgs.push('--sanitized');
358
+ }
359
+ } else if (sub === 'import') {
360
+ pyArgs.push('--action', 'import');
361
+ const pathIdx = args.indexOf('--path') !== -1 ? args.indexOf('--path') : args.indexOf('-p');
362
+ if (pathIdx !== -1 && args[pathIdx + 1]) {
363
+ pyArgs.push('--source', args[pathIdx + 1]);
364
+ } else {
365
+ console.error(`${RED}Error: --path <file> is required for memory import${RESET}`);
366
+ process.exit(1);
367
+ }
368
+ } else if (sub === 'hook') {
369
+ const hookAction = args[2] === 'uninstall' ? 'hook-uninstall' : 'hook-install';
370
+ pyArgs.push('--action', hookAction);
371
+ } else if (sub === 'learn') {
372
+ pyArgs.push('--action', 'learn');
373
+ const commitsIdx = args.indexOf('--commits');
374
+ if (commitsIdx !== -1 && args[commitsIdx + 1]) {
375
+ pyArgs.push('--commits', args[commitsIdx + 1]);
376
+ }
377
+ const daysIdx = args.indexOf('--days');
378
+ if (daysIdx !== -1 && args[daysIdx + 1]) {
379
+ pyArgs.push('--days', args[daysIdx + 1]);
380
+ }
381
+ } else if (sub === 'recipe') {
382
+ pyArgs.push('--action', 'recipe', ...args.slice(2));
383
+ } else if (sub === 'fp') {
384
+ pyArgs.push('--action', 'fp');
385
+ const ruleIdx = args.indexOf('--rule') !== -1 ? args.indexOf('--rule') : args.indexOf('-r');
386
+ if (ruleIdx !== -1 && args[ruleIdx + 1]) {
387
+ pyArgs.push('--rule-id', args[ruleIdx + 1]);
388
+ } else {
389
+ console.error(`${RED}Error: --rule <rule_id> is required for fp suppression${RESET}`);
390
+ process.exit(1);
391
+ }
392
+ const reasonIdx = args.indexOf('--reason');
393
+ if (reasonIdx !== -1 && args[reasonIdx + 1]) {
394
+ pyArgs.push('--reason', args[reasonIdx + 1]);
395
+ }
396
+ } else {
397
+ pyArgs.push('--action', sub, ...args.slice(2));
398
+ }
399
+
400
+ const proc = spawnSync(pythonCmd, pyArgs, { stdio: 'inherit', cwd });
401
+ process.exit(proc.status !== null ? proc.status : 0);
402
+ }
403
+
404
+ // Subcommand: diff-guard
405
+ if (command === 'diff-guard') {
406
+ const diffScript = path.join(cwd, '.torusguard', 'scripts', 'diff_guard.py');
407
+ const fallbackDiffScript = path.join(rootDir, '.torusguard', 'scripts', 'diff_guard.py');
408
+ const actualDiffScript = fs.existsSync(fallbackDiffScript) ? fallbackDiffScript : diffScript;
409
+
410
+ const proc = spawnSync(pythonCmd, [actualDiffScript, ...args.slice(1)], { stdio: 'inherit', cwd });
411
+ process.exit(proc.status !== null ? proc.status : 0);
412
+ }
413
+
414
+ // Subcommand: rules
415
+ if (command === 'rules') {
416
+ const sub = args[1] || 'sync';
417
+ const rulesScript = path.join(cwd, '.torusguard', 'scripts', 'rules_sync.py');
418
+ const fallbackRulesScript = path.join(rootDir, '.torusguard', 'scripts', 'rules_sync.py');
419
+ const actualRulesScript = fs.existsSync(fallbackRulesScript) ? fallbackRulesScript : rulesScript;
420
+
421
+ let pyArgs = [actualRulesScript];
422
+ if (sub === 'sync') {
423
+ const fmtIdx = args.indexOf('--format');
424
+ if (fmtIdx !== -1 && args[fmtIdx + 1]) {
425
+ pyArgs.push('--format', args[fmtIdx + 1]);
426
+ }
427
+ const rootIdx = args.indexOf('--root') !== -1 ? args.indexOf('--root') : args.indexOf('--target');
428
+ if (rootIdx !== -1 && args[rootIdx + 1]) {
429
+ pyArgs.push('--root', args[rootIdx + 1]);
430
+ }
431
+ if (args.includes('--json')) {
432
+ pyArgs.push('--json');
433
+ }
434
+ } else {
435
+ pyArgs.push(...args.slice(1));
436
+ }
437
+
438
+ const proc = spawnSync(pythonCmd, pyArgs, { stdio: 'inherit', cwd });
439
+ process.exit(proc.status !== null ? proc.status : 0);
440
+ }
441
+
442
+ // Subcommand: audit
443
+ if (command === 'audit') {
444
+ const auditScript = path.join(cwd, '.torusguard', 'scripts', 'audit_runner.py');
445
+ const fallbackAuditScript = path.join(rootDir, '.torusguard', 'scripts', 'audit_runner.py');
446
+ const actualAuditScript = fs.existsSync(fallbackAuditScript) ? fallbackAuditScript : auditScript;
447
+
448
+ const proc = spawnSync(pythonCmd, [actualAuditScript, ...args.slice(1)], { stdio: 'inherit', cwd });
449
+ process.exit(proc.status !== null ? proc.status : 0);
450
+ }
451
+
452
+ // Subcommand: report
453
+ if (command === 'report') {
454
+ const wantHtml = args.includes('--html') || args[1] === 'html' || args.includes('--out');
455
+ const wantSarif = args.includes('--sarif') || (!wantHtml && args[1] !== 'html');
456
+
457
+ // Resolve target directory (supports --target and --root)
458
+ const rootIdx = args.indexOf('--root') !== -1 ? args.indexOf('--root') : args.indexOf('--target');
459
+ let targetRoot = cwd;
460
+ if (rootIdx !== -1 && args[rootIdx + 1]) {
461
+ targetRoot = path.resolve(cwd, args[rootIdx + 1]);
462
+ }
463
+
464
+ let exitCode = 0;
465
+
466
+ // 1. If SARIF export is requested
467
+ if (wantSarif) {
468
+ const sarifScript = path.join(targetRoot, '.torusguard', 'scripts', 'sarif_exporter.py');
469
+ const fallbackSarifScript = path.join(rootDir, '.torusguard', 'scripts', 'sarif_exporter.py');
470
+ const actualSarifScript = fs.existsSync(fallbackSarifScript) ? fallbackSarifScript : sarifScript;
471
+
472
+ const sarifArgs = [actualSarifScript, '--root', targetRoot];
473
+ const sarifOutIdx = args.indexOf('--sarif-out');
474
+ if (sarifOutIdx !== -1 && args[sarifOutIdx + 1]) {
475
+ sarifArgs.push('--output', args[sarifOutIdx + 1]);
476
+ } else if (!wantHtml) {
477
+ const outIdx = args.indexOf('--out');
478
+ if (outIdx !== -1 && args[outIdx + 1]) {
479
+ sarifArgs.push('--output', args[outIdx + 1]);
480
+ }
481
+ }
482
+ const runIdIdx = args.indexOf('--run');
483
+ if (runIdIdx !== -1 && args[runIdIdx + 1]) {
484
+ sarifArgs.push('--run-id', args[runIdIdx + 1]);
485
+ }
486
+
487
+ const proc = spawnSync(pythonCmd, sarifArgs, { stdio: 'inherit', cwd: targetRoot });
488
+ if (proc.status !== 0 && proc.status !== null) {
489
+ exitCode = proc.status;
490
+ }
491
+ }
492
+
493
+ // 2. If Visual HTML dashboard is requested
494
+ if (wantHtml) {
495
+ const htmlScript = path.join(targetRoot, '.torusguard', 'scripts', 'html_reporter.py');
496
+ const fallbackHtmlScript = path.join(rootDir, '.torusguard', 'scripts', 'html_reporter.py');
497
+ const actualHtmlScript = fs.existsSync(fallbackHtmlScript) ? fallbackHtmlScript : htmlScript;
498
+
499
+ let pyArgs = [actualHtmlScript, '--root', targetRoot];
500
+ const outIdx = args.indexOf('--out');
501
+ if (outIdx !== -1 && args[outIdx + 1]) {
502
+ pyArgs.push('--out', path.resolve(cwd, args[outIdx + 1]));
503
+ } else {
504
+ pyArgs.push('--out', path.join(targetRoot, 'report.html'));
505
+ }
506
+ if (args.includes('--json')) {
507
+ pyArgs.push('--json');
508
+ }
509
+
510
+ const proc = spawnSync(pythonCmd, pyArgs, { stdio: 'inherit', cwd: targetRoot });
511
+ if (proc.status !== 0 && proc.status !== null) {
512
+ exitCode = proc.status;
513
+ }
514
+ }
515
+
516
+ process.exit(exitCode);
517
+ }
518
+
519
+ // Subcommand: harden
520
+ if (command === 'harden') {
521
+ const scriptPath = path.join(cwd, '.torusguard', 'scripts', 'harden_runner.py');
522
+ const fallbackScript = path.join(rootDir, '.torusguard', 'scripts', 'harden_runner.py');
523
+ const actualScript = fs.existsSync(fallbackScript) ? fallbackScript : scriptPath;
524
+
525
+ const proc = spawnSync(pythonCmd, [actualScript, ...args.slice(1)], { stdio: 'inherit', cwd });
526
+ process.exit(proc.status !== null ? proc.status : 0);
527
+ }
528
+
529
+ // Subcommand: apply
530
+ if (command === 'apply') {
531
+ const scriptPath = path.join(cwd, '.torusguard', 'scripts', 'apply_runner.py');
532
+ const fallbackScript = path.join(rootDir, '.torusguard', 'scripts', 'apply_runner.py');
533
+ const actualScript = fs.existsSync(fallbackScript) ? fallbackScript : scriptPath;
534
+
535
+ const proc = spawnSync(pythonCmd, [actualScript, ...args.slice(1)], { stdio: 'inherit', cwd });
536
+ process.exit(proc.status !== null ? proc.status : 0);
537
+ }
538
+
539
+ // Subcommand: rollback
540
+ if (command === 'rollback') {
541
+ const scriptPath = path.join(cwd, '.torusguard', 'scripts', 'apply_runner.py');
542
+ const fallbackScript = path.join(rootDir, '.torusguard', 'scripts', 'apply_runner.py');
543
+ const actualScript = fs.existsSync(fallbackScript) ? fallbackScript : scriptPath;
544
+
545
+ const proc = spawnSync(pythonCmd, [actualScript, '--rollback', ...args.slice(1)], { stdio: 'inherit', cwd });
546
+ process.exit(proc.status !== null ? proc.status : 0);
547
+ }
548
+
549
+ // Subcommand: recheck / verify
550
+ if (command === 'recheck' || command === 'verify') {
551
+ const scriptPath = path.join(cwd, '.torusguard', 'scripts', 'recheck_runner.py');
552
+ const fallbackScript = path.join(rootDir, '.torusguard', 'scripts', 'recheck_runner.py');
553
+ const actualScript = fs.existsSync(fallbackScript) ? fallbackScript : scriptPath;
554
+
555
+ const proc = spawnSync(pythonCmd, [actualScript, ...args.slice(1)], { stdio: 'inherit', cwd });
556
+ process.exit(proc.status !== null ? proc.status : 0);
557
+ }
558
+
559
+ // Subcommand: recipes
560
+ if (command === 'recipes') {
561
+ const scriptPath = path.join(cwd, '.torusguard', 'scripts', 'recipes_runner.py');
562
+ const fallbackScript = path.join(rootDir, '.torusguard', 'scripts', 'recipes_runner.py');
563
+ const actualScript = fs.existsSync(fallbackScript) ? fallbackScript : scriptPath;
564
+
565
+ const proc = spawnSync(pythonCmd, [actualScript, ...args.slice(1)], { stdio: 'inherit', cwd });
566
+ process.exit(proc.status !== null ? proc.status : 0);
567
+ }
568
+
569
+ // Subcommand: init (scaffold workspace)
570
+ if (command === 'init') {
571
+ const localBootstrap = path.join(rootDir, 'skills', 'torusguard', 'bootstrap.py');
572
+ const localInstall = path.join(rootDir, 'install.py');
573
+ const scriptToRun = fs.existsSync(localBootstrap) ? localBootstrap : localInstall;
574
+
575
+ const scriptArgs = [scriptToRun, '--full-commands', ...args.slice(1)];
576
+ const proc = spawnSync(pythonCmd, scriptArgs, {
577
+ stdio: 'inherit',
578
+ cwd: cwd,
579
+ });
580
+ process.exit(proc.status !== null ? proc.status : 0);
581
+ }
582
+
583
+ // Unknown command fallback
584
+ console.error(`\n ${RED}✖ Unknown command:${RESET} ${WHITE}${command}${RESET}`);
585
+ console.error(` Run ${GREEN}npx torusguard help${RESET} for available commands.\n`);
586
+ process.exit(1);