torusguard 1.3.4 → 1.3.5

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 (239) 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 +93 -7
  33. package/.torusguard/runs/run-20260912-214110-audit/findings.json +1256 -0
  34. package/.torusguard/runs/run-20260912-214110-audit/findings.md +534 -0
  35. package/.torusguard/runs/run-20260912-214110-audit/manifest.json +12 -0
  36. package/.torusguard/runs/run-20260912-214110-audit/summary.md +5 -0
  37. package/.torusguard/runs/run-20260912-214813-audit/findings.json +992 -0
  38. package/.torusguard/runs/run-20260912-214813-audit/findings.md +422 -0
  39. package/.torusguard/runs/run-20260912-214813-audit/manifest.json +12 -0
  40. package/.torusguard/runs/run-20260912-214813-audit/summary.md +5 -0
  41. package/.torusguard/runs/run-20260912-215555-audit/bundles/bnd-tg-auth-003-49-7c96f3/metadata.json +17 -0
  42. package/.torusguard/runs/run-20260912-215555-audit/bundles/bnd-tg-auth-003-49-7c96f3/minimal_patch_plan.md +21 -0
  43. package/.torusguard/runs/run-20260912-215555-audit/bundles/bnd-tg-auth-003-49-7c96f3/patch.diff +9 -0
  44. package/.torusguard/runs/run-20260912-215555-audit/bundles/bnd-tg-input-003-53-d167b4/metadata.json +17 -0
  45. package/.torusguard/runs/run-20260912-215555-audit/bundles/bnd-tg-input-003-53-d167b4/minimal_patch_plan.md +21 -0
  46. package/.torusguard/runs/run-20260912-215555-audit/bundles/bnd-tg-input-003-53-d167b4/patch.diff +9 -0
  47. package/.torusguard/runs/run-20260912-215555-audit/bundles/bnd-tg-platform-001-12-cf8156/metadata.json +17 -0
  48. package/.torusguard/runs/run-20260912-215555-audit/bundles/bnd-tg-platform-001-12-cf8156/minimal_patch_plan.md +21 -0
  49. package/.torusguard/runs/run-20260912-215555-audit/bundles/bnd-tg-platform-001-12-cf8156/patch.diff +9 -0
  50. package/.torusguard/runs/run-20260912-215555-audit/bundles/bnd-tg-rate-001-26-affcfa/metadata.json +17 -0
  51. package/.torusguard/runs/run-20260912-215555-audit/bundles/bnd-tg-rate-001-26-affcfa/minimal_patch_plan.md +21 -0
  52. package/.torusguard/runs/run-20260912-215555-audit/bundles/bnd-tg-rate-001-26-affcfa/patch.diff +9 -0
  53. package/.torusguard/runs/run-20260912-215555-audit/bundles/bnd-tg-sec-001-9-e22e50/metadata.json +17 -0
  54. package/.torusguard/runs/run-20260912-215555-audit/bundles/bnd-tg-sec-001-9-e22e50/minimal_patch_plan.md +21 -0
  55. package/.torusguard/runs/run-20260912-215555-audit/bundles/bnd-tg-sec-001-9-e22e50/patch.diff +9 -0
  56. package/.torusguard/runs/run-20260912-215555-audit/bundles/bnd-tg-ssrf-004-9-9803ad/metadata.json +17 -0
  57. package/.torusguard/runs/run-20260912-215555-audit/bundles/bnd-tg-ssrf-004-9-9803ad/minimal_patch_plan.md +21 -0
  58. package/.torusguard/runs/run-20260912-215555-audit/bundles/bnd-tg-ssrf-004-9-9803ad/patch.diff +9 -0
  59. package/.torusguard/runs/run-20260912-215555-audit/findings.json +530 -0
  60. package/.torusguard/runs/run-20260912-215555-audit/findings.md +232 -0
  61. package/.torusguard/runs/run-20260912-215555-audit/manifest.json +13 -0
  62. package/.torusguard/runs/run-20260912-215555-audit/remediation.md +102 -0
  63. package/.torusguard/runs/run-20260912-215555-audit/summary.md +5 -0
  64. package/.torusguard/runs/run-20260912-215731-audit/apply_plan.md +6 -0
  65. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-client-001-7-587129/metadata.json +17 -0
  66. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-client-001-7-587129/minimal_patch_plan.md +21 -0
  67. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-client-001-7-587129/patch.diff +9 -0
  68. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-db-002-9-5f7850/metadata.json +17 -0
  69. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-db-002-9-5f7850/minimal_patch_plan.md +21 -0
  70. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-db-002-9-5f7850/patch.diff +9 -0
  71. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-input-001-27-2486f1/metadata.json +17 -0
  72. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-input-001-27-2486f1/minimal_patch_plan.md +22 -0
  73. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-input-001-27-2486f1/patch.diff +10 -0
  74. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-input-003-53-d167b4/metadata.json +17 -0
  75. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-input-003-53-d167b4/minimal_patch_plan.md +21 -0
  76. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-input-003-53-d167b4/patch.diff +9 -0
  77. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-platform-001-12-cf8156/metadata.json +17 -0
  78. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-platform-001-12-cf8156/minimal_patch_plan.md +21 -0
  79. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-platform-001-12-cf8156/patch.diff +9 -0
  80. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-platform-002-17-876973/metadata.json +17 -0
  81. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-platform-002-17-876973/minimal_patch_plan.md +23 -0
  82. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-platform-002-17-876973/patch.diff +11 -0
  83. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-platform-002-5-40c794/metadata.json +17 -0
  84. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-platform-002-5-40c794/minimal_patch_plan.md +23 -0
  85. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-platform-002-5-40c794/patch.diff +11 -0
  86. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-rate-001-26-affcfa/metadata.json +17 -0
  87. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-rate-001-26-affcfa/minimal_patch_plan.md +21 -0
  88. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-rate-001-26-affcfa/patch.diff +9 -0
  89. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-rate-002-54-0b38d2/metadata.json +17 -0
  90. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-rate-002-54-0b38d2/minimal_patch_plan.md +21 -0
  91. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-rate-002-54-0b38d2/patch.diff +9 -0
  92. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-sec-001-18-a3c1e8/metadata.json +17 -0
  93. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-sec-001-18-a3c1e8/minimal_patch_plan.md +21 -0
  94. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-sec-001-18-a3c1e8/patch.diff +9 -0
  95. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-sec-001-9-e22e50/metadata.json +17 -0
  96. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-sec-001-9-e22e50/minimal_patch_plan.md +21 -0
  97. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-sec-001-9-e22e50/patch.diff +9 -0
  98. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-sec-004-28-a1a0a1/metadata.json +17 -0
  99. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-sec-004-28-a1a0a1/minimal_patch_plan.md +21 -0
  100. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-sec-004-28-a1a0a1/patch.diff +9 -0
  101. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-sec-004-30-00569e/metadata.json +17 -0
  102. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-sec-004-30-00569e/minimal_patch_plan.md +21 -0
  103. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-sec-004-30-00569e/patch.diff +9 -0
  104. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-ssrf-004-9-9803ad/metadata.json +17 -0
  105. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-ssrf-004-9-9803ad/minimal_patch_plan.md +21 -0
  106. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-ssrf-004-9-9803ad/patch.diff +9 -0
  107. package/.torusguard/runs/run-20260912-215731-audit/diff_summary.md +186 -0
  108. package/.torusguard/runs/run-20260912-215731-audit/findings.json +497 -0
  109. package/.torusguard/runs/run-20260912-215731-audit/findings.md +218 -0
  110. package/.torusguard/runs/run-20260912-215731-audit/manifest.json +14 -0
  111. package/.torusguard/runs/run-20260912-215731-audit/recheck.md +76 -0
  112. package/.torusguard/runs/run-20260912-215731-audit/remediation.md +234 -0
  113. package/.torusguard/runs/run-20260912-215731-audit/summary.md +5 -0
  114. package/.torusguard/runs/run-20260912-220134-audit/findings.json +68 -0
  115. package/.torusguard/runs/run-20260912-220134-audit/findings.md +36 -0
  116. package/.torusguard/runs/run-20260912-220134-audit/manifest.json +12 -0
  117. package/.torusguard/runs/run-20260912-220134-audit/summary.md +5 -0
  118. package/.torusguard/runs/run-20260912-220200-audit/findings.json +1 -0
  119. package/.torusguard/runs/run-20260912-220200-audit/findings.md +8 -0
  120. package/.torusguard/runs/run-20260912-220200-audit/manifest.json +12 -0
  121. package/.torusguard/runs/run-20260912-220200-audit/recheck.md +6 -0
  122. package/.torusguard/runs/run-20260912-220200-audit/summary.md +5 -0
  123. package/.torusguard/runs/run-20260912-220227-audit/findings.json +1 -0
  124. package/.torusguard/runs/run-20260912-220227-audit/findings.md +8 -0
  125. package/.torusguard/runs/run-20260912-220227-audit/manifest.json +12 -0
  126. package/.torusguard/runs/run-20260912-220227-audit/recheck.md +6 -0
  127. package/.torusguard/runs/run-20260912-220227-audit/summary.md +5 -0
  128. package/.torusguard/scripts/__pycache__/audit_runner.cpython-311.pyc +0 -0
  129. package/.torusguard/scripts/__pycache__/finding_scorer.cpython-311.pyc +0 -0
  130. package/.torusguard/scripts/__pycache__/report_sync.cpython-311.pyc +0 -0
  131. package/.torusguard/scripts/__pycache__/term_ui.cpython-311.pyc +0 -0
  132. package/.torusguard/scripts/apply_runner.py +383 -377
  133. package/.torusguard/scripts/audit_runner.py +1306 -653
  134. package/.torusguard/scripts/finding_scorer.py +14 -2
  135. package/.torusguard/scripts/harden_runner.py +628 -455
  136. package/.torusguard/scripts/recheck_runner.py +248 -238
  137. package/.torusguard/scripts/recipes_runner.py +137 -137
  138. package/.torusguard/scripts/report_sync.py +423 -0
  139. package/.torusguard/scripts/sarif_exporter.py +116 -116
  140. package/.torusguard/scripts/term_ui.py +162 -162
  141. package/.torusguard/snapshots/run-20260912-215731-audit/demo/playground/vulnerable_nextjs/actions.ts.bak +24 -0
  142. package/.torusguard/snapshots/run-20260912-215731-audit/examples/hardened-react-express/server/index.js.bak +121 -0
  143. package/.torusguard/snapshots/run-20260912-215731-audit/examples/python/fastapi-vuln/main.py.bak +20 -0
  144. package/.torusguard/snapshots/run-20260912-215731-audit/examples/vulnerable-react-express/client/src/App.jsx.bak +63 -0
  145. package/.torusguard/snapshots/run-20260912-215731-audit/examples/vulnerable-react-express/client/vite.config.js.bak +12 -0
  146. package/.torusguard/snapshots/run-20260912-215731-audit/examples/vulnerable-react-express/server/index.js.bak +82 -0
  147. package/.torusguard/workflows/apply.md +11 -4
  148. package/.torusguard/workflows/audit.md +7 -1
  149. package/.torusguard/workflows/authorize.md +2 -1
  150. package/.torusguard/workflows/exploit-check.md +2 -1
  151. package/.torusguard/workflows/harden.md +7 -1
  152. package/.torusguard/workflows/init.md +6 -1
  153. package/.torusguard/workflows/memory.md +2 -1
  154. package/.torusguard/workflows/recheck.md +7 -1
  155. package/.torusguard/workflows/report.md +7 -1
  156. package/.torusguard/workflows/status.md +6 -1
  157. package/.torusguard/workflows/verify.md +6 -1
  158. package/.torusguard/workflows/web-validate.md +2 -1
  159. package/README.md +470 -345
  160. package/bin/torusguard.js +550 -545
  161. package/package.json +1 -1
  162. package/skills/torusguard/SKILL.md +73 -72
  163. package/skills/torusguard/payload/.manifest.json +66 -63
  164. package/skills/torusguard/payload/rules/business-logic/TG-BIZ-001-client-controlled-sensitive-business-value.md +30 -14
  165. package/skills/torusguard/payload/rules/business-logic/TG-BIZ-002-replayable-one-time-operation.md +33 -16
  166. package/skills/torusguard/payload/rules/business-logic/TG-BIZ-003-missing-workflow-state-validation.md +31 -15
  167. package/skills/torusguard/payload/rules/business-logic/TG-BIZ-004-unrestricted-sensitive-business-flow.md +26 -16
  168. package/skills/torusguard/payload/rules/cache/TG-CACHE-001-sensitive-response-publicly-cacheable.md +24 -16
  169. package/skills/torusguard/payload/rules/cache/TG-CACHE-002-missing-user-cache-isolation.md +32 -18
  170. package/skills/torusguard/payload/rules/cache/TG-CACHE-003-sensitive-data-in-url.md +24 -17
  171. package/skills/torusguard/payload/rules/csrf/TG-CSRF-001-missing-csrf-protection.md +31 -18
  172. package/skills/torusguard/payload/rules/csrf/TG-CSRF-002-unsafe-credentialed-cross-origin-request.md +28 -17
  173. package/skills/torusguard/payload/rules/graphql/TG-GQL-002-resolver-missing-authorization.md +33 -17
  174. package/skills/torusguard/payload/rules/graphql/TG-GQL-003-unbounded-graphql-pagination-or-batching.md +31 -16
  175. package/skills/torusguard/payload/rules/graphql/TG-GQL-004-unnecessary-production-introspection.md +25 -16
  176. package/skills/torusguard/payload/rules/secrets/TG-SEC-005-secret-in-git-history.md +24 -19
  177. package/skills/torusguard/payload/rules/secrets/TG-SEC-006-secret-in-build-artifact.md +36 -19
  178. package/skills/torusguard/payload/rules/secrets/TG-SEC-007-secret-in-test-or-example-fixture.md +21 -16
  179. package/skills/torusguard/payload/rules/ssrf/TG-SSRF-002-missing-internal-network-protection.md +45 -18
  180. package/skills/torusguard/payload/rules/ssrf/TG-SSRF-003-unsafe-redirect-following.md +20 -16
  181. package/skills/torusguard/payload/rules/ssrf/TG-SSRF-004-unbounded-outbound-request.md +28 -19
  182. package/skills/torusguard/payload/rules/supply-chain/TG-SUPPLY-001-missing-or-ignored-dependency-lockfile.md +25 -18
  183. package/skills/torusguard/payload/rules/supply-chain/TG-SUPPLY-003-unsafe-ci-cd-secret-exposure.md +29 -19
  184. package/skills/torusguard/payload/rules/supply-chain/TG-SUPPLY-004-unpinned-ci-action.md +23 -18
  185. package/skills/torusguard/payload/rules/supply-chain/TG-SUPPLY-005-unsafe-dependency-install-script.md +23 -18
  186. package/skills/torusguard/payload/rules/webhook/TG-WEBHOOK-001-missing-webhook-signature-verification.md +36 -16
  187. package/skills/torusguard/payload/rules/webhook/TG-WEBHOOK-002-replayable-webhook.md +26 -17
  188. package/skills/torusguard/payload/rules/webhook/TG-WEBHOOK-003-missing-webhook-idempotency.md +30 -16
  189. package/skills/torusguard/payload/rules/webhook/TG-WEBHOOK-004-unvalidated-webhook-event-type.md +25 -16
  190. package/skills/torusguard/payload/rules/websocket/TG-WS-002-missing-channel-level-authorization.md +28 -17
  191. package/skills/torusguard/payload/rules/websocket/TG-WS-003-unvalidated-websocket-message.md +31 -17
  192. package/skills/torusguard/payload/rules/websocket/TG-WS-004-unbounded-websocket-connections.md +31 -17
  193. package/skills/torusguard/payload/rules_catalog.json +520 -0
  194. package/skills/torusguard/payload/scripts/apply_runner.py +383 -377
  195. package/skills/torusguard/payload/scripts/audit_runner.py +1306 -653
  196. package/skills/torusguard/payload/scripts/finding_scorer.py +14 -2
  197. package/skills/torusguard/payload/scripts/harden_runner.py +628 -455
  198. package/skills/torusguard/payload/scripts/recheck_runner.py +248 -238
  199. package/skills/torusguard/payload/scripts/recipes_runner.py +137 -137
  200. package/skills/torusguard/payload/scripts/report_sync.py +423 -0
  201. package/skills/torusguard/payload/scripts/sarif_exporter.py +116 -116
  202. package/skills/torusguard/payload/scripts/term_ui.py +162 -162
  203. package/skills/torusguard/payload/skills/torusguard/SKILL.md +75 -72
  204. package/skills/torusguard/payload/skills/torusguard-apply/SKILL.md +84 -80
  205. package/skills/torusguard/payload/skills/torusguard-audit/SKILL.md +87 -83
  206. package/skills/torusguard/payload/skills/torusguard-authorize/SKILL.md +7 -2
  207. package/skills/torusguard/payload/skills/torusguard-exploit-check/SKILL.md +7 -2
  208. package/skills/torusguard/payload/skills/torusguard-full/SKILL.md +7 -2
  209. package/skills/torusguard/payload/skills/torusguard-harden/SKILL.md +98 -94
  210. package/skills/torusguard/payload/skills/torusguard-init/SKILL.md +68 -64
  211. package/skills/torusguard/payload/skills/torusguard-recheck/SKILL.md +81 -77
  212. package/skills/torusguard/payload/skills/torusguard-report/SKILL.md +76 -72
  213. package/skills/torusguard/payload/skills/torusguard-status/SKILL.md +62 -58
  214. package/skills/torusguard/payload/skills/torusguard-verify/SKILL.md +7 -2
  215. package/skills/torusguard/payload/skills/torusguard-web-validate/SKILL.md +7 -2
  216. package/skills/torusguard/payload/workflows/apply.md +11 -4
  217. package/skills/torusguard/payload/workflows/audit.md +7 -1
  218. package/skills/torusguard/payload/workflows/authorize.md +2 -1
  219. package/skills/torusguard/payload/workflows/exploit-check.md +2 -1
  220. package/skills/torusguard/payload/workflows/harden.md +7 -1
  221. package/skills/torusguard/payload/workflows/init.md +6 -1
  222. package/skills/torusguard/payload/workflows/memory.md +2 -1
  223. package/skills/torusguard/payload/workflows/recheck.md +7 -1
  224. package/skills/torusguard/payload/workflows/report.md +7 -1
  225. package/skills/torusguard/payload/workflows/status.md +6 -1
  226. package/skills/torusguard/payload/workflows/verify.md +6 -1
  227. package/skills/torusguard/payload/workflows/web-validate.md +2 -1
  228. package/skills/torusguard-apply/SKILL.md +84 -80
  229. package/skills/torusguard-audit/SKILL.md +87 -83
  230. package/skills/torusguard-authorize/SKILL.md +7 -2
  231. package/skills/torusguard-exploit-check/SKILL.md +7 -2
  232. package/skills/torusguard-full/SKILL.md +7 -2
  233. package/skills/torusguard-harden/SKILL.md +98 -94
  234. package/skills/torusguard-init/SKILL.md +68 -64
  235. package/skills/torusguard-recheck/SKILL.md +81 -77
  236. package/skills/torusguard-report/SKILL.md +76 -72
  237. package/skills/torusguard-status/SKILL.md +62 -58
  238. package/skills/torusguard-verify/SKILL.md +7 -2
  239. package/skills/torusguard-web-validate/SKILL.md +7 -2
package/bin/torusguard.js CHANGED
@@ -1,545 +1,550 @@
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.5', 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.5'));
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.5'));
254
+ console.log(`\n ${BOLD}▸ Workspace:${RESET} ${GREEN}${cwd}${RESET}`);
255
+ console.log(` ${BOLD}▸ Governance:${RESET} ${GREEN}Full Local Governance (.torusguard/)${RESET}`);
256
+ console.log(` ${BOLD}▸ Living Report:${RESET} ${CYAN}security_report.md${RESET}\n`);
257
+
258
+ console.log(cardBorderTop('Environment & Stack'));
259
+ console.log(formatBoxLine(`Language: ${BOLD}${stackLang}${RESET}`));
260
+ console.log(formatBoxLine(`Framework: ${BOLD}${stackFw}${RESET}`));
261
+ console.log(formatBoxLine(`Data Layer: ${BOLD}${stackDb}${RESET}`));
262
+ console.log(formatBoxLine(`Stack Detection: ${stackStatus}`));
263
+ console.log(cardBorderBottom());
264
+ console.log();
265
+
266
+ console.log(cardBorderTop('Security Memory Engine'));
267
+ console.log(formatBoxLine(`Patterns Learned: ${GREEN}${memPatterns} active patterns${RESET}`));
268
+ console.log(formatBoxLine(`Events Recorded: ${WHITE}${memEvents} events (local-first log)${RESET}`));
269
+ console.log(formatBoxLine(`Context Window: ${CYAN}${memTokens} / 2,000 tokens${RESET} (${Math.round((memTokens / 2000) * 100)}% utilized)`));
270
+ console.log(formatBoxLine(`Fix Velocity Rate: ${YELLOW}${memFixRate}${RESET}`));
271
+ console.log(cardBorderBottom());
272
+ console.log();
273
+
274
+ console.log(cardBorderTop('Governance Telemetry'));
275
+ console.log(formatBoxLine(`Rules Catalog: ${GREEN}74 Canonical Security Rules${RESET} (18 families)`));
276
+ console.log(formatBoxLine(`Severity Floor: ${YELLOW}${cfg.severity_threshold || 'medium'}${RESET}`));
277
+ console.log(formatBoxLine(`Runs Directory: ${DIM}${cfg.runs_dir || '.torusguard/runs'}${RESET}`));
278
+ console.log(formatBoxLine(`Ponytail Bounds: ${GREEN}<= 35 additions, <= 25 deletions${RESET}`));
279
+ console.log(formatBoxLine(`Living Report: ${CYAN}security_report.md (ground truth)${RESET}`));
280
+ console.log(cardBorderBottom());
281
+ console.log();
282
+
283
+ console.log(cardBorderTop('Rule Families (18 Families / 74 Rules)'));
284
+ console.log(formatBoxLine(`${YELLOW}TG-SEC${RESET} Secrets (7) ${YELLOW}TG-DB${RESET} Database Safety (4)`));
285
+ console.log(formatBoxLine(`${YELLOW}TG-INPUT${RESET} Input Validation (6) ${YELLOW}TG-AUTH${RESET} Authentication (8)`));
286
+ console.log(formatBoxLine(`${YELLOW}TG-RATE${RESET} Rate Limiting (3) ${YELLOW}TG-AGENT${RESET} AI Agent Security (4)`));
287
+ console.log(formatBoxLine(`${YELLOW}TG-SSRF${RESET} Server Request (4) ${YELLOW}TG-WEBHOOK${RESET} Webhook Trust (4)`));
288
+ console.log(formatBoxLine(`${YELLOW}TG-WS${RESET} WebSocket (4) ${YELLOW}TG-CSRF${RESET} Cross-Site Request (2)`));
289
+ console.log(formatBoxLine(`${YELLOW}TG-GQL${RESET} GraphQL Security (4) ${YELLOW}TG-SUPPLY${RESET} Supply Chain (6)`));
290
+ console.log(formatBoxLine(`${YELLOW}TG-BIZ${RESET} Business Logic (4) ${YELLOW}TG-CACHE${RESET} Cache Isolation (3)`));
291
+ console.log(formatBoxLine(`${YELLOW}TG-CLIENT${RESET} Client Bundles (2) ${YELLOW}TG-PLATFORM${RESET}Platform Config (4)`));
292
+ console.log(formatBoxLine(`${YELLOW}TG-DIFF${RESET} Diff Inspection (3) ${YELLOW}TG-EDGE${RESET} Serverless / Edge (2)`));
293
+ console.log(cardBorderBottom());
294
+
295
+ console.log(`\n ${DIM}Quick Action:${RESET} In your AI chat, run ${CYAN}/torusguard-audit${RESET} to scan.\n`);
296
+ process.exit(0);
297
+ } catch (e) {
298
+ // Fallback to python runner
299
+ }
300
+ } else {
301
+ console.log(`
302
+ ${YELLOW}⚠${RESET} No .torusguard workspace found in current directory.
303
+ Run ${GREEN}npx torusguard init${RESET} to scaffold full workspace governance.
304
+ `);
305
+ process.exit(0);
306
+ }
307
+ }
308
+
309
+ // Subcommand: memory
310
+ if (command === 'memory') {
311
+ const sub = args[1] || 'status';
312
+ const memScript = path.join(cwd, '.torusguard', 'scripts', 'memory_engine.py');
313
+ const fallbackMemScript = path.join(rootDir, '.torusguard', 'scripts', 'memory_engine.py');
314
+ const actualMemScript = fs.existsSync(memScript) ? memScript : fallbackMemScript;
315
+
316
+ let pyArgs = [actualMemScript];
317
+ if (sub === 'status') {
318
+ pyArgs.push('--action', 'status');
319
+ } else if (sub === 'context') {
320
+ pyArgs.push('--action', 'context');
321
+ const roleIdx = args.indexOf('--role');
322
+ if (roleIdx !== -1 && args[roleIdx + 1]) {
323
+ pyArgs.push('--role', args[roleIdx + 1]);
324
+ }
325
+ const fileIdx = args.indexOf('--file') !== -1 ? args.indexOf('--file') : args.indexOf('-f');
326
+ if (fileIdx !== -1 && args[fileIdx + 1]) {
327
+ pyArgs.push('--file', args[fileIdx + 1]);
328
+ }
329
+ const ruleIdx = args.indexOf('--rule') !== -1 ? args.indexOf('--rule') : args.indexOf('-r');
330
+ if (ruleIdx !== -1 && args[ruleIdx + 1]) {
331
+ pyArgs.push('--rule-id', args[ruleIdx + 1]);
332
+ }
333
+ } else if (sub === 'distill') {
334
+ pyArgs.push('--action', 'distill');
335
+ } else if (sub === 'decay') {
336
+ pyArgs.push('--action', 'decay');
337
+ const ttlIdx = args.indexOf('--ttl');
338
+ if (ttlIdx !== -1 && args[ttlIdx + 1]) {
339
+ pyArgs.push('--ttl', args[ttlIdx + 1]);
340
+ }
341
+ } else if (sub === 'compact') {
342
+ pyArgs.push('--action', 'compact');
343
+ const ageIdx = args.indexOf('--older-than');
344
+ if (ageIdx !== -1 && args[ageIdx + 1]) {
345
+ pyArgs.push('--older-than', args[ageIdx + 1]);
346
+ }
347
+ } else if (sub === 'export') {
348
+ pyArgs.push('--action', 'export');
349
+ const pathIdx = args.indexOf('--path') !== -1 ? args.indexOf('--path') : args.indexOf('-p');
350
+ if (pathIdx !== -1 && args[pathIdx + 1]) {
351
+ pyArgs.push('--target', args[pathIdx + 1]);
352
+ } else {
353
+ pyArgs.push('--target', 'torusguard-memory-export.json');
354
+ }
355
+ if (args.includes('--sanitized')) {
356
+ pyArgs.push('--sanitized');
357
+ }
358
+ } else if (sub === 'import') {
359
+ pyArgs.push('--action', 'import');
360
+ const pathIdx = args.indexOf('--path') !== -1 ? args.indexOf('--path') : args.indexOf('-p');
361
+ if (pathIdx !== -1 && args[pathIdx + 1]) {
362
+ pyArgs.push('--source', args[pathIdx + 1]);
363
+ } else {
364
+ console.error(`${RED}Error: --path <file> is required for memory import${RESET}`);
365
+ process.exit(1);
366
+ }
367
+ } else if (sub === 'hook') {
368
+ const hookAction = args[2] === 'uninstall' ? 'hook-uninstall' : 'hook-install';
369
+ pyArgs.push('--action', hookAction);
370
+ } else if (sub === 'learn') {
371
+ pyArgs.push('--action', 'learn');
372
+ const commitsIdx = args.indexOf('--commits');
373
+ if (commitsIdx !== -1 && args[commitsIdx + 1]) {
374
+ pyArgs.push('--commits', args[commitsIdx + 1]);
375
+ }
376
+ const daysIdx = args.indexOf('--days');
377
+ if (daysIdx !== -1 && args[daysIdx + 1]) {
378
+ pyArgs.push('--days', args[daysIdx + 1]);
379
+ }
380
+ } else if (sub === 'recipe') {
381
+ pyArgs.push('--action', 'recipe', ...args.slice(2));
382
+ } else if (sub === 'fp') {
383
+ pyArgs.push('--action', 'fp');
384
+ const ruleIdx = args.indexOf('--rule') !== -1 ? args.indexOf('--rule') : args.indexOf('-r');
385
+ if (ruleIdx !== -1 && args[ruleIdx + 1]) {
386
+ pyArgs.push('--rule-id', args[ruleIdx + 1]);
387
+ } else {
388
+ console.error(`${RED}Error: --rule <rule_id> is required for fp suppression${RESET}`);
389
+ process.exit(1);
390
+ }
391
+ const reasonIdx = args.indexOf('--reason');
392
+ if (reasonIdx !== -1 && args[reasonIdx + 1]) {
393
+ pyArgs.push('--reason', args[reasonIdx + 1]);
394
+ }
395
+ } else {
396
+ pyArgs.push('--action', sub, ...args.slice(2));
397
+ }
398
+
399
+ const proc = spawnSync(pythonCmd, pyArgs, { stdio: 'inherit', cwd });
400
+ process.exit(proc.status !== null ? proc.status : 0);
401
+ }
402
+
403
+ // Subcommand: diff-guard
404
+ if (command === 'diff-guard') {
405
+ const diffScript = path.join(cwd, '.torusguard', 'scripts', 'diff_guard.py');
406
+ const fallbackDiffScript = path.join(rootDir, '.torusguard', 'scripts', 'diff_guard.py');
407
+ const actualDiffScript = fs.existsSync(fallbackDiffScript) ? fallbackDiffScript : diffScript;
408
+
409
+ const proc = spawnSync(pythonCmd, [actualDiffScript, ...args.slice(1)], { stdio: 'inherit', cwd });
410
+ process.exit(proc.status !== null ? proc.status : 0);
411
+ }
412
+
413
+ // Subcommand: rules
414
+ if (command === 'rules') {
415
+ const sub = args[1] || 'sync';
416
+ const rulesScript = path.join(cwd, '.torusguard', 'scripts', 'rules_sync.py');
417
+ const fallbackRulesScript = path.join(rootDir, '.torusguard', 'scripts', 'rules_sync.py');
418
+ const actualRulesScript = fs.existsSync(fallbackRulesScript) ? fallbackRulesScript : rulesScript;
419
+
420
+ let pyArgs = [actualRulesScript];
421
+ if (sub === 'sync') {
422
+ const fmtIdx = args.indexOf('--format');
423
+ if (fmtIdx !== -1 && args[fmtIdx + 1]) {
424
+ pyArgs.push('--format', args[fmtIdx + 1]);
425
+ }
426
+ const rootIdx = args.indexOf('--root') !== -1 ? args.indexOf('--root') : args.indexOf('--target');
427
+ if (rootIdx !== -1 && args[rootIdx + 1]) {
428
+ pyArgs.push('--root', args[rootIdx + 1]);
429
+ }
430
+ if (args.includes('--json')) {
431
+ pyArgs.push('--json');
432
+ }
433
+ } else {
434
+ pyArgs.push(...args.slice(1));
435
+ }
436
+
437
+ const proc = spawnSync(pythonCmd, pyArgs, { stdio: 'inherit', cwd });
438
+ process.exit(proc.status !== null ? proc.status : 0);
439
+ }
440
+
441
+ // Subcommand: audit
442
+ if (command === 'audit') {
443
+ const auditScript = path.join(cwd, '.torusguard', 'scripts', 'audit_runner.py');
444
+ const fallbackAuditScript = path.join(rootDir, '.torusguard', 'scripts', 'audit_runner.py');
445
+ const actualAuditScript = fs.existsSync(fallbackAuditScript) ? fallbackAuditScript : auditScript;
446
+
447
+ const proc = spawnSync(pythonCmd, [actualAuditScript, ...args.slice(1)], { stdio: 'inherit', cwd });
448
+ process.exit(proc.status !== null ? proc.status : 0);
449
+ }
450
+
451
+ // Subcommand: report
452
+ if (command === 'report') {
453
+ if (args.includes('--html') || args[1] === 'html') {
454
+ const htmlScript = path.join(cwd, '.torusguard', 'scripts', 'html_reporter.py');
455
+ const fallbackHtmlScript = path.join(rootDir, '.torusguard', 'scripts', 'html_reporter.py');
456
+ const actualHtmlScript = fs.existsSync(fallbackHtmlScript) ? fallbackHtmlScript : htmlScript;
457
+
458
+ let pyArgs = [actualHtmlScript];
459
+ const outIdx = args.indexOf('--out');
460
+ if (outIdx !== -1 && args[outIdx + 1]) {
461
+ pyArgs.push('--out', args[outIdx + 1]);
462
+ }
463
+ const rootIdx = args.indexOf('--root') !== -1 ? args.indexOf('--root') : args.indexOf('--target');
464
+ if (rootIdx !== -1 && args[rootIdx + 1]) {
465
+ pyArgs.push('--root', args[rootIdx + 1]);
466
+ }
467
+ if (args.includes('--json')) {
468
+ pyArgs.push('--json');
469
+ }
470
+
471
+ const proc = spawnSync(pythonCmd, pyArgs, { stdio: 'inherit', cwd });
472
+ process.exit(proc.status !== null ? proc.status : 0);
473
+ } else {
474
+ const sarifScript = path.join(cwd, '.torusguard', 'scripts', 'sarif_exporter.py');
475
+ const fallbackSarifScript = path.join(rootDir, '.torusguard', 'scripts', 'sarif_exporter.py');
476
+ const actualSarifScript = fs.existsSync(fallbackSarifScript) ? fallbackSarifScript : sarifScript;
477
+
478
+ const proc = spawnSync(pythonCmd, [actualSarifScript, ...args.slice(1)], { stdio: 'inherit', cwd });
479
+ process.exit(proc.status !== null ? proc.status : 0);
480
+ }
481
+ }
482
+
483
+ // Subcommand: harden
484
+ if (command === 'harden') {
485
+ const scriptPath = path.join(cwd, '.torusguard', 'scripts', 'harden_runner.py');
486
+ const fallbackScript = path.join(rootDir, '.torusguard', 'scripts', 'harden_runner.py');
487
+ const actualScript = fs.existsSync(fallbackScript) ? fallbackScript : scriptPath;
488
+
489
+ const proc = spawnSync(pythonCmd, [actualScript, ...args.slice(1)], { stdio: 'inherit', cwd });
490
+ process.exit(proc.status !== null ? proc.status : 0);
491
+ }
492
+
493
+ // Subcommand: apply
494
+ if (command === 'apply') {
495
+ const scriptPath = path.join(cwd, '.torusguard', 'scripts', 'apply_runner.py');
496
+ const fallbackScript = path.join(rootDir, '.torusguard', 'scripts', 'apply_runner.py');
497
+ const actualScript = fs.existsSync(fallbackScript) ? fallbackScript : scriptPath;
498
+
499
+ const proc = spawnSync(pythonCmd, [actualScript, ...args.slice(1)], { stdio: 'inherit', cwd });
500
+ process.exit(proc.status !== null ? proc.status : 0);
501
+ }
502
+
503
+ // Subcommand: rollback
504
+ if (command === 'rollback') {
505
+ const scriptPath = path.join(cwd, '.torusguard', 'scripts', 'apply_runner.py');
506
+ const fallbackScript = path.join(rootDir, '.torusguard', 'scripts', 'apply_runner.py');
507
+ const actualScript = fs.existsSync(fallbackScript) ? fallbackScript : scriptPath;
508
+
509
+ const proc = spawnSync(pythonCmd, [actualScript, '--rollback', ...args.slice(1)], { stdio: 'inherit', cwd });
510
+ process.exit(proc.status !== null ? proc.status : 0);
511
+ }
512
+
513
+ // Subcommand: recheck / verify
514
+ if (command === 'recheck' || command === 'verify') {
515
+ const scriptPath = path.join(cwd, '.torusguard', 'scripts', 'recheck_runner.py');
516
+ const fallbackScript = path.join(rootDir, '.torusguard', 'scripts', 'recheck_runner.py');
517
+ const actualScript = fs.existsSync(fallbackScript) ? fallbackScript : scriptPath;
518
+
519
+ const proc = spawnSync(pythonCmd, [actualScript, ...args.slice(1)], { stdio: 'inherit', cwd });
520
+ process.exit(proc.status !== null ? proc.status : 0);
521
+ }
522
+
523
+ // Subcommand: recipes
524
+ if (command === 'recipes') {
525
+ const scriptPath = path.join(cwd, '.torusguard', 'scripts', 'recipes_runner.py');
526
+ const fallbackScript = path.join(rootDir, '.torusguard', 'scripts', 'recipes_runner.py');
527
+ const actualScript = fs.existsSync(fallbackScript) ? fallbackScript : scriptPath;
528
+
529
+ const proc = spawnSync(pythonCmd, [actualScript, ...args.slice(1)], { stdio: 'inherit', cwd });
530
+ process.exit(proc.status !== null ? proc.status : 0);
531
+ }
532
+
533
+ // Subcommand: init (scaffold workspace)
534
+ if (command === 'init') {
535
+ const localBootstrap = path.join(rootDir, 'skills', 'torusguard', 'bootstrap.py');
536
+ const localInstall = path.join(rootDir, 'install.py');
537
+ const scriptToRun = fs.existsSync(localBootstrap) ? localBootstrap : localInstall;
538
+
539
+ const scriptArgs = [scriptToRun, '--full-commands', ...args.slice(1)];
540
+ const proc = spawnSync(pythonCmd, scriptArgs, {
541
+ stdio: 'inherit',
542
+ cwd: cwd,
543
+ });
544
+ process.exit(proc.status !== null ? proc.status : 0);
545
+ }
546
+
547
+ // Unknown command fallback
548
+ console.error(`\n ${RED}✖ Unknown command:${RESET} ${WHITE}${command}${RESET}`);
549
+ console.error(` Run ${GREEN}npx torusguard help${RESET} for available commands.\n`);
550
+ process.exit(1);