torusguard 1.3.3 → 1.3.4

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 (127) hide show
  1. package/.torusguard/runs/report-latest.html +3 -3
  2. package/.torusguard/runs/run-20260910-120238-audit/bundles/bnd-tg-db-004-115-9a059e/metadata.json +15 -0
  3. package/.torusguard/runs/run-20260910-120238-audit/bundles/bnd-tg-db-004-115-9a059e/minimal_patch_plan.md +21 -0
  4. package/.torusguard/runs/run-20260910-120238-audit/bundles/bnd-tg-db-004-115-9a059e/patch.diff +9 -0
  5. package/.torusguard/runs/run-20260910-120238-audit/bundles/bnd-tg-db-004-119-61557c/metadata.json +15 -0
  6. package/.torusguard/runs/run-20260910-120238-audit/bundles/bnd-tg-db-004-119-61557c/minimal_patch_plan.md +21 -0
  7. package/.torusguard/runs/run-20260910-120238-audit/bundles/bnd-tg-db-004-119-61557c/patch.diff +9 -0
  8. package/.torusguard/runs/run-20260910-120238-audit/bundles/bnd-tg-db-004-265-996ca3/metadata.json +15 -0
  9. package/.torusguard/runs/run-20260910-120238-audit/bundles/bnd-tg-db-004-265-996ca3/minimal_patch_plan.md +21 -0
  10. package/.torusguard/runs/run-20260910-120238-audit/bundles/bnd-tg-db-004-265-996ca3/patch.diff +9 -0
  11. package/.torusguard/runs/run-20260910-120238-audit/bundles/bnd-tg-db-004-503-d11cf8/metadata.json +15 -0
  12. package/.torusguard/runs/run-20260910-120238-audit/bundles/bnd-tg-db-004-503-d11cf8/minimal_patch_plan.md +21 -0
  13. package/.torusguard/runs/run-20260910-120238-audit/bundles/bnd-tg-db-004-503-d11cf8/patch.diff +9 -0
  14. package/.torusguard/runs/run-20260910-120238-audit/bundles/bnd-tg-db-004-79-91f41f/metadata.json +15 -0
  15. package/.torusguard/runs/run-20260910-120238-audit/bundles/bnd-tg-db-004-79-91f41f/minimal_patch_plan.md +21 -0
  16. package/.torusguard/runs/run-20260910-120238-audit/bundles/bnd-tg-db-004-79-91f41f/patch.diff +9 -0
  17. package/.torusguard/runs/run-20260910-120238-audit/bundles/bnd-tg-input-002-107-516a40/metadata.json +15 -0
  18. package/.torusguard/runs/run-20260910-120238-audit/bundles/bnd-tg-input-002-107-516a40/minimal_patch_plan.md +21 -0
  19. package/.torusguard/runs/run-20260910-120238-audit/bundles/bnd-tg-input-002-107-516a40/patch.diff +9 -0
  20. package/.torusguard/runs/run-20260910-120238-audit/bundles/bnd-tg-input-002-128-7809a1/metadata.json +15 -0
  21. package/.torusguard/runs/run-20260910-120238-audit/bundles/bnd-tg-input-002-128-7809a1/minimal_patch_plan.md +21 -0
  22. package/.torusguard/runs/run-20260910-120238-audit/bundles/bnd-tg-input-002-128-7809a1/patch.diff +9 -0
  23. package/.torusguard/runs/run-20260910-120238-audit/bundles/bnd-tg-input-002-256-c8a22e/metadata.json +15 -0
  24. package/.torusguard/runs/run-20260910-120238-audit/bundles/bnd-tg-input-002-256-c8a22e/minimal_patch_plan.md +21 -0
  25. package/.torusguard/runs/run-20260910-120238-audit/bundles/bnd-tg-input-002-256-c8a22e/patch.diff +9 -0
  26. package/.torusguard/runs/run-20260910-120238-audit/bundles/bnd-tg-input-003-53-d167b4/metadata.json +15 -0
  27. package/.torusguard/runs/run-20260910-120238-audit/bundles/bnd-tg-input-003-53-d167b4/minimal_patch_plan.md +21 -0
  28. package/.torusguard/runs/run-20260910-120238-audit/bundles/bnd-tg-input-003-53-d167b4/patch.diff +9 -0
  29. package/.torusguard/runs/run-20260910-120238-audit/bundles/bnd-tg-platform-001-183-47fd5e/metadata.json +15 -0
  30. package/.torusguard/runs/run-20260910-120238-audit/bundles/bnd-tg-platform-001-183-47fd5e/minimal_patch_plan.md +21 -0
  31. package/.torusguard/runs/run-20260910-120238-audit/bundles/bnd-tg-platform-001-183-47fd5e/patch.diff +9 -0
  32. package/.torusguard/runs/run-20260910-120238-audit/bundles/bnd-tg-sec-001-209-4f6424/metadata.json +15 -0
  33. package/.torusguard/runs/run-20260910-120238-audit/bundles/bnd-tg-sec-001-209-4f6424/minimal_patch_plan.md +21 -0
  34. package/.torusguard/runs/run-20260910-120238-audit/bundles/bnd-tg-sec-001-209-4f6424/patch.diff +9 -0
  35. package/.torusguard/runs/run-20260910-120238-audit/bundles/bnd-tg-sec-001-257-89bf34/metadata.json +15 -0
  36. package/.torusguard/runs/run-20260910-120238-audit/bundles/bnd-tg-sec-001-257-89bf34/minimal_patch_plan.md +21 -0
  37. package/.torusguard/runs/run-20260910-120238-audit/bundles/bnd-tg-sec-001-257-89bf34/patch.diff +9 -0
  38. package/.torusguard/runs/run-20260910-120238-audit/bundles/bnd-tg-sec-001-341-ce228c/metadata.json +15 -0
  39. package/.torusguard/runs/run-20260910-120238-audit/bundles/bnd-tg-sec-001-341-ce228c/minimal_patch_plan.md +21 -0
  40. package/.torusguard/runs/run-20260910-120238-audit/bundles/bnd-tg-sec-001-341-ce228c/patch.diff +9 -0
  41. package/.torusguard/runs/run-20260910-120238-audit/bundles/bnd-tg-sec-001-355-7b190b/metadata.json +15 -0
  42. package/.torusguard/runs/run-20260910-120238-audit/bundles/bnd-tg-sec-001-355-7b190b/minimal_patch_plan.md +21 -0
  43. package/.torusguard/runs/run-20260910-120238-audit/bundles/bnd-tg-sec-001-355-7b190b/patch.diff +9 -0
  44. package/.torusguard/runs/run-20260910-120238-audit/bundles/bnd-tg-sec-001-64-5af31a/metadata.json +15 -0
  45. package/.torusguard/runs/run-20260910-120238-audit/bundles/bnd-tg-sec-001-64-5af31a/minimal_patch_plan.md +21 -0
  46. package/.torusguard/runs/run-20260910-120238-audit/bundles/bnd-tg-sec-001-64-5af31a/patch.diff +9 -0
  47. package/.torusguard/runs/run-20260910-120238-audit/bundles/bnd-tg-sec-001-9-f51467/metadata.json +15 -0
  48. package/.torusguard/runs/run-20260910-120238-audit/bundles/bnd-tg-sec-001-9-f51467/minimal_patch_plan.md +21 -0
  49. package/.torusguard/runs/run-20260910-120238-audit/bundles/bnd-tg-sec-001-9-f51467/patch.diff +9 -0
  50. package/.torusguard/runs/run-20260910-120238-audit/findings.json +1520 -0
  51. package/.torusguard/runs/run-20260910-120238-audit/findings.md +560 -0
  52. package/.torusguard/runs/run-20260910-120238-audit/manifest.json +13 -0
  53. package/.torusguard/runs/run-20260910-120238-audit/recheck.md +86 -0
  54. package/.torusguard/runs/run-20260910-120238-audit/remediation.md +262 -0
  55. package/.torusguard/runs/run-20260910-120238-audit/summary.md +5 -0
  56. package/.torusguard/runs/run-20260910-121238-audit/findings.json +794 -0
  57. package/.torusguard/runs/run-20260910-121238-audit/findings.md +344 -0
  58. package/.torusguard/runs/run-20260910-121238-audit/manifest.json +12 -0
  59. package/.torusguard/runs/run-20260910-121238-audit/summary.md +5 -0
  60. package/.torusguard/runs/run-20260910-130454-audit/findings.json +893 -0
  61. package/.torusguard/runs/run-20260910-130454-audit/findings.md +386 -0
  62. package/.torusguard/runs/run-20260910-130454-audit/manifest.json +12 -0
  63. package/.torusguard/runs/run-20260910-130454-audit/summary.md +5 -0
  64. package/.torusguard/runs/run-20260910-130519-audit/findings.json +563 -0
  65. package/.torusguard/runs/run-20260910-130519-audit/findings.md +246 -0
  66. package/.torusguard/runs/run-20260910-130519-audit/manifest.json +12 -0
  67. package/.torusguard/runs/run-20260910-130519-audit/summary.md +5 -0
  68. package/.torusguard/scripts/__pycache__/audit_runner.cpython-311.pyc +0 -0
  69. package/.torusguard/scripts/__pycache__/diff_guard.cpython-311.pyc +0 -0
  70. package/.torusguard/scripts/__pycache__/html_reporter.cpython-311.pyc +0 -0
  71. package/.torusguard/scripts/__pycache__/rules_sync.cpython-311.pyc +0 -0
  72. package/.torusguard/scripts/__pycache__/stack_detect.cpython-311.pyc +0 -0
  73. package/.torusguard/scripts/__pycache__/term_ui.cpython-311.pyc +0 -0
  74. package/.torusguard/scripts/apply_runner.py +138 -100
  75. package/.torusguard/scripts/audit_runner.py +219 -116
  76. package/.torusguard/scripts/harden_runner.py +144 -64
  77. package/.torusguard/scripts/recheck_runner.py +80 -63
  78. package/.torusguard/scripts/recipes_runner.py +38 -31
  79. package/.torusguard/scripts/term_ui.py +162 -0
  80. package/.torusguard/skills/torusguard/SKILL.md +61 -31
  81. package/.torusguard/skills/torusguard-apply/SKILL.md +80 -46
  82. package/.torusguard/skills/torusguard-audit/SKILL.md +83 -70
  83. package/.torusguard/skills/torusguard-authorize/SKILL.md +5 -4
  84. package/.torusguard/skills/torusguard-exploit-check/SKILL.md +6 -5
  85. package/.torusguard/skills/torusguard-full/SKILL.md +1 -1
  86. package/.torusguard/skills/torusguard-harden/SKILL.md +94 -63
  87. package/.torusguard/skills/torusguard-init/SKILL.md +64 -52
  88. package/.torusguard/skills/torusguard-recheck/SKILL.md +77 -53
  89. package/.torusguard/skills/torusguard-report/SKILL.md +72 -54
  90. package/.torusguard/skills/torusguard-status/SKILL.md +58 -42
  91. package/.torusguard/skills/torusguard-verify/SKILL.md +8 -7
  92. package/.torusguard/skills/torusguard-web-validate/SKILL.md +8 -7
  93. package/README.md +4 -3
  94. package/bin/torusguard.js +544 -437
  95. package/package.json +1 -1
  96. package/skills/torusguard/SKILL.md +61 -31
  97. package/skills/torusguard/payload/scripts/apply_runner.py +138 -100
  98. package/skills/torusguard/payload/scripts/audit_runner.py +219 -116
  99. package/skills/torusguard/payload/scripts/harden_runner.py +144 -64
  100. package/skills/torusguard/payload/scripts/recheck_runner.py +80 -63
  101. package/skills/torusguard/payload/scripts/recipes_runner.py +38 -31
  102. package/skills/torusguard/payload/scripts/term_ui.py +162 -0
  103. package/skills/torusguard/payload/skills/torusguard/SKILL.md +61 -31
  104. package/skills/torusguard/payload/skills/torusguard-apply/SKILL.md +80 -46
  105. package/skills/torusguard/payload/skills/torusguard-audit/SKILL.md +83 -70
  106. package/skills/torusguard/payload/skills/torusguard-authorize/SKILL.md +5 -4
  107. package/skills/torusguard/payload/skills/torusguard-exploit-check/SKILL.md +6 -5
  108. package/skills/torusguard/payload/skills/torusguard-full/SKILL.md +1 -1
  109. package/skills/torusguard/payload/skills/torusguard-harden/SKILL.md +94 -63
  110. package/skills/torusguard/payload/skills/torusguard-init/SKILL.md +64 -52
  111. package/skills/torusguard/payload/skills/torusguard-recheck/SKILL.md +77 -53
  112. package/skills/torusguard/payload/skills/torusguard-report/SKILL.md +72 -54
  113. package/skills/torusguard/payload/skills/torusguard-status/SKILL.md +58 -42
  114. package/skills/torusguard/payload/skills/torusguard-verify/SKILL.md +8 -7
  115. package/skills/torusguard/payload/skills/torusguard-web-validate/SKILL.md +8 -7
  116. package/skills/torusguard-apply/SKILL.md +80 -48
  117. package/skills/torusguard-audit/SKILL.md +83 -75
  118. package/skills/torusguard-authorize/SKILL.md +1 -1
  119. package/skills/torusguard-exploit-check/SKILL.md +1 -1
  120. package/skills/torusguard-full/SKILL.md +1 -1
  121. package/skills/torusguard-harden/SKILL.md +94 -67
  122. package/skills/torusguard-init/SKILL.md +64 -53
  123. package/skills/torusguard-recheck/SKILL.md +77 -54
  124. package/skills/torusguard-report/SKILL.md +72 -55
  125. package/skills/torusguard-status/SKILL.md +58 -43
  126. package/skills/torusguard-verify/SKILL.md +1 -1
  127. package/skills/torusguard-web-validate/SKILL.md +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "torusguard",
3
- "version": "1.3.3",
3
+ "version": "1.3.4",
4
4
  "description": "Security guardrails, governed remediation, and authorized runtime validation for AI-built web applications.",
5
5
  "main": "bin/torusguard.js",
6
6
  "bin": {
@@ -1,42 +1,72 @@
1
1
  ---
2
2
  name: torusguard
3
- description: "Universal autonomous security engine: 71 rules across 11 families, polyglot stack detection across 16+ languages, governed remediation (Ponytail Protocol <=35 add, <=25 del), SARIF v2.1.0 exports, single-file HTML dashboards, and persistent security memory context for modern web applications."
4
- version: 1.3.2
3
+ description: Universal autonomous security engine: 71 canonical rules across 11 families, polyglot stack detection across 16+ languages, Ponytail remediation bounds (<=35 add, <=25 del), standardized 75-column terminal UI, SARIF v2.1.0 exports, and persistent security memory context.
4
+ version: 1.3.4
5
5
  ---
6
6
 
7
- # TorusGuard Master Security Router
7
+ # TorusGuard Master Security Engine & Command Router
8
8
 
9
- **Principle:** If the browser or client receives it, users can inspect it. Keep secrets, database queries, and authorization strictly on trusted server-side code.
9
+ **Core Invariant:** If the browser or client receives it, users can inspect it. Never expose database credentials, master keys, or private API secrets in client bundles. Always isolate multi-tenant database queries.
10
10
 
11
11
  ---
12
12
 
13
- ## ⚡ Workspace Bootstrap Check
14
- 1. **If `.torusguard/` exists on disk:** Full autonomous governance active. Load `.torusguard/memory/context.json` into prompt context for local-first intelligence. Route commands to specialist workflows below.
15
- 2. **If `.torusguard/` is absent (Autonomous AI Mode):** Operates standalone in any AI agent (Kimi, Antigravity, VS Code, Cursor, Claude Code, Windsurf). Apply universal security invariants:
16
- - Server-side auth & tenant scoping (`.filter(tenant=...)`, `.Where("tenant_id = ?")`, `where: { tenantId: ... }`).
17
- - Zero client-side secrets (`SUPABASE_SERVICE_ROLE_KEY`, live API keys, Stripe secret keys).
18
- - Ponytail Protocol patch limits ($\le 35$ additions, $\le 25$ deletions, zero full-file rewrites).
19
- - *Upgrade:* Run `npx torusguard init` to unlock local `.torusguard/` workspace tracking, persistent memory, and individual slash commands.
13
+ ## Command Catalog: Terminal CLI & AI Chat Parity
14
+
15
+ TorusGuard operates with 100% feature parity across both the terminal CLI and AI chat slash commands:
16
+
17
+ | Capability | Terminal CLI Command | AI Chat Slash Command | Specialist Skill | Purpose |
18
+ | :--- | :--- | :--- | :--- | :--- |
19
+ | **Init** | `npx torusguard init` | `/torusguard init` | `skills/torusguard-init` | Stack discovery, rule activation, workspace scaffolding |
20
+ | **Status** | `npx torusguard status` | `/torusguard status` | `skills/torusguard-status` | Posture score, active rules, run history, scope check |
21
+ | **Audit** | `npx torusguard audit` | `/torusguard audit` | `skills/torusguard-audit` | Static AST scan, invariant fingerprinting, confidence scoring |
22
+ | **Verify** | `npx torusguard verify` | `/torusguard verify` | `skills/torusguard-verify` | Evidence sufficiency audit & line match calibration |
23
+ | **Harden** | `npx torusguard harden` | `/torusguard harden` | `skills/torusguard-harden` | Ponytail patch formulation ($\le 35$ add, $\le 25$ del) |
24
+ | **Apply** | `npx torusguard apply [--yes]` | `/torusguard apply` | `skills/torusguard-apply` | Human Gate review, `.bak` snapshots, patch application |
25
+ | **Rollback** | `npx torusguard rollback` | `/torusguard rollback` | `skills/torusguard-apply` | Instant 1-step rollback from pre-apply snapshots |
26
+ | **Recheck** | `npx torusguard recheck` | `/torusguard recheck` | `skills/torusguard-recheck` | Targeted differential AST scan, fix closure verification |
27
+ | **Recipes** | `npx torusguard recipes` | `/torusguard recipes` | `skills/torusguard-harden` | Explore verified Golden Fix Recipes from persistent memory |
28
+ | **Report** | `npx torusguard report --html` | `/torusguard report` | `skills/torusguard-report` | Executive posture reporting, SARIF v2.1.0 & visual HTML |
29
+ | **Authorize** | `npx torusguard authorize` | `/torusguard authorize` | `skills/torusguard-authorize`| Legal scope definition & safety boundaries |
30
+ | **Validate** | `npx torusguard web-validate` | `/torusguard web-validate` | `skills/torusguard-web-validate`| Authorized non-destructive HTTP probing |
31
+ | **Exploit** | `npx torusguard exploit-check` | `/torusguard exploit-check`| `skills/torusguard-exploit-check`| Bounded single-step exploitability confirmation |
32
+ | **Full** | `npx torusguard full` | `/torusguard full` | `skills/torusguard-full` | End-to-end 7-stage closed-loop execution |
33
+ | **Sync** | `npx torusguard rules sync` | `/torusguard rules sync` | `skills/torusguard-init` | Synchronize rules across AI editors (Cursor, Claude, Antigravity) |
34
+
35
+ ---
36
+
37
+ ## Non-Negotiable Invariants
38
+
39
+ 1. **Browser-Code Truth:** If the client receives it, it is public. Zero hardcoded secrets (`SUPABASE_SERVICE_ROLE_KEY`, private tokens, live API keys) in frontend code.
40
+ 2. **Multi-Tenant Isolation:** All database lookups must be scoped by organization or user ownership (`tenant_id`, `organization_id`, `where: { tenantId }`).
41
+ 3. **Ponytail Churn Bounds:** Patches must be minimal and surgical ($\le 35$ additions, $\le 25$ deletions). Never perform full-file rewrites.
42
+ 4. **Standardized 75-Column Terminal:** All CLI terminal output is strictly normalized to 75 visual columns with Unicode emoji width calculation, ANSI escape handling, and visual truncation with ellipsis (`...`).
43
+ 5. **Zero Security Bypasses:** Never insert `# nosec`, `verify=False`, `InsecureSkipVerify: true`, `[AllowAnonymous]`, or `csrf().disable()`.
44
+ 6. **Snapshots Before Edits:** Every code modification must capture a byte-for-byte pre-apply backup in `.torusguard/snapshots/<run_id>/` before touching disk code.
20
45
 
21
46
  ---
22
47
 
23
- ## Specialist Skill Routing (Lazy Loading)
24
- Load **only** the designated sub-skill on demand to preserve the 1,000–1,500 token budget:
25
-
26
- | Command | Specialist Skill | Agent | Purpose |
27
- | :--- | :--- | :--- | :--- |
28
- | `/torusguard init` | `skills/torusguard-init/SKILL.md` | `profiler` | Workspace discovery, stack detection & scaffolding |
29
- | `/torusguard authorize` | `skills/torusguard-authorize/SKILL.md` | `reviewer` | Legal scope definition & safety boundaries |
30
- | `/torusguard audit` | `skills/torusguard-audit/SKILL.md` | `auditor` | Static AST security audit & root-cause clustering |
31
- | `/torusguard verify` | `skills/torusguard-verify/SKILL.md` | `validator` | Evidence sufficiency & line match audit |
32
- | `/torusguard web-validate` | `skills/torusguard-web-validate/SKILL.md` | `validator` | Authorized non-destructive HTTP probing |
33
- | `/torusguard exploit-check`| `skills/torusguard-exploit-check/SKILL.md`| `validator` | Bounded single-step exploitability confirmation |
34
- | `/torusguard harden` | `skills/torusguard-harden/SKILL.md` | `remediator`| Governed remediation under Ponytail Protocol |
35
- | `/torusguard apply` | `skills/torusguard-apply/SKILL.md` | `remediator`| Governed patch application with rollback snapshots |
36
- | `/torusguard recheck` | `skills/torusguard-recheck/SKILL.md` | `reviewer` | Differential AST re-scan & closure verification |
37
- | `/torusguard report` | `skills/torusguard-report/SKILL.md` | `reviewer` | Executive posture reporting, SARIF & visual HTML export |
38
- | `/torusguard status` | `skills/torusguard-status/SKILL.md` | `reviewer` | Diagnostic overview of posture, stack & rules |
39
- | `/torusguard memory` | `skills/torusguard/SKILL.md` | `reviewer` | Persistent memory inspection, decay & context cards |
40
- | `/torusguard full` | `skills/torusguard-full/SKILL.md` | *All* | End-to-end 7-stage closed-loop execution |
41
-
42
- *Note: Never load all skills simultaneously. Lazy-load strictly on demand.*
48
+ ## Workspace Layout Structure
49
+ ```
50
+ .torusguard/
51
+ ├── config/
52
+ │ ├── torusguard.json # Detected stack, settings, and rule counts
53
+ │ └── scope.json # Authorized runtime validation targets & TTL
54
+ ├── memory/
55
+ │ ├── context.json # Aggregated security memory context
56
+ │ ├── patterns.json # Distilled Golden Fix Recipes
57
+ │ └── events.json # Historical audit, apply, and recheck events
58
+ ├── rules/
59
+ │ └── active/ # Active TG-* rule definitions
60
+ ├── runs/
61
+ │ └── run-YYYYMMDD-HHMMSS-audit/
62
+ │ ├── findings.json # Machine-readable AST findings
63
+ │ ├── findings.md # Actionable markdown finding cards
64
+ │ ├── remediation.md # Formulated candidate patch catalog
65
+ │ ├── diff_summary.md # Summary of applied diffs
66
+ │ ├── recheck.md # Differential recheck status transitions
67
+ │ ├── report.html # Single-file visual dark-mode HTML report
68
+ │ ├── results.sarif # OASIS SARIF v2.1.0 log
69
+ │ └── bundles/ # Formulated Ponytail remediation bundles
70
+ └── snapshots/
71
+ └── run-YYYYMMDD-HHMMSS-audit/ # Pre-apply .bak files for instant rollback
72
+ ```
@@ -1,8 +1,9 @@
1
1
  #!/usr/bin/env python3
2
2
  """
3
- TorusGuard Governed Patch Application & Snapshot Engine (v1.3.0)
3
+ TorusGuard Governed Patch Application & Snapshot Engine (v1.3.3)
4
4
  Interactive Human Gate authorization, pre-apply .bak rollback snapshots,
5
5
  atomic patch application, and Golden Fix Recipe distillation into persistent memory.
6
+ Strict 75-column terminal UI formatting.
6
7
 
7
8
  Pure Python 3.10+ standard library (zero external dependencies).
8
9
  """
@@ -15,9 +16,18 @@ import shutil
15
16
  import hashlib
16
17
  import argparse
17
18
  import datetime
18
- import unicodedata
19
19
  from pathlib import Path
20
- from typing import Dict, List, Any, Optional, Tuple
20
+ from typing import Dict, List, Any, Optional
21
+
22
+ # Ensure scripts dir is in sys.path for term_ui import
23
+ scripts_dir = Path(__file__).resolve().parent
24
+ if str(scripts_dir) not in sys.path:
25
+ sys.path.insert(0, str(scripts_dir))
26
+
27
+ try:
28
+ import term_ui
29
+ except ImportError:
30
+ term_ui = None
21
31
 
22
32
  # Windows console UTF-8 support
23
33
  if sys.stdout and hasattr(sys.stdout, "reconfigure"):
@@ -32,7 +42,7 @@ IST = datetime.timezone(datetime.timedelta(hours=5, minutes=30), name="IST")
32
42
  def get_ist_now() -> datetime.datetime:
33
43
  return datetime.datetime.now(IST)
34
44
 
35
- # ─── ANSI Colors & Formatter ───────────────────────────────────────────────────
45
+ # ─── Formatting Helpers ────────────────────────────────────────────────────────
36
46
  BOLD = "\033[1m"
37
47
  DIM = "\033[2m"
38
48
  RESET = "\033[0m"
@@ -43,25 +53,25 @@ WHITE = "\033[97m"
43
53
  GRAY = "\033[90m"
44
54
  RED = "\033[31m"
45
55
 
46
- ANSI_REGEX = re.compile(r'\033\[[0-9;]*m')
47
-
48
- def get_visual_width(text: str) -> int:
49
- clean = ANSI_REGEX.sub('', text)
50
- width = 0
51
- for ch in clean:
52
- ea = unicodedata.east_asian_width(ch)
53
- if ea in ('W', 'F'):
54
- width += 2
55
- elif ord(ch) >= 0x1F300:
56
- width += 2
57
- else:
58
- width += 1
59
- return width
56
+ def box_line(content: str, width: int = 67, border: str = "│", border_color: str = CYAN) -> str:
57
+ if term_ui:
58
+ return term_ui.format_box_line(content, width=width, border=border, border_color=border_color)
59
+ return f" {border_color}{border}{RESET} {content}"
60
+
61
+ def box_header(title: str, subtitle: str = "", version: str = "v1.3.3", border_color: str = CYAN) -> str:
62
+ if term_ui:
63
+ return term_ui.card_header(title, subtitle, version, border_color)
64
+ return f"=== {title} ({version}) ==="
65
+
66
+ def border_top(title: str = "", border_color: str = CYAN, double: bool = False) -> str:
67
+ if term_ui:
68
+ return term_ui.card_border_top(title, border_color=border_color, double=double)
69
+ return "┌" + "─" * 71 + "┐"
60
70
 
61
- def format_box_line(content: str, width: int = 71, border: str = "│", border_color: str = CYAN) -> str:
62
- vis = get_visual_width(content)
63
- pad = " " * max(0, width - vis)
64
- return f" {border_color}{border}{RESET} {content}{pad}{border_color}{border}{RESET}"
71
+ def border_bottom(border_color: str = CYAN, double: bool = False) -> str:
72
+ if term_ui:
73
+ return term_ui.card_border_bottom(border_color=border_color, double=double)
74
+ return "└" + "─" * 71 + "┘"
65
75
 
66
76
 
67
77
  def find_latest_audit_run(runs_dir: Path) -> Optional[Path]:
@@ -98,17 +108,17 @@ def execute_rollback(target_root: Path, run_id_arg: Optional[str] = None) -> Non
98
108
  shutil.copy2(bak_file, dest_file)
99
109
  restored_files.append(orig_rel)
100
110
 
101
- print(f"\n {CYAN}╭─────────────────────────────────────────────────────────────────────────╮{RESET}")
102
- print(format_box_line(f"{BOLD}🛡️ TORUSGUARD ROLLBACK RESTORATION v1.3.1{RESET}", border_color=CYAN))
103
- print(format_box_line(f"{DIM}Restored original files from pre-apply snapshot{RESET}", border_color=CYAN))
104
- print(f" {CYAN}╰─────────────────────────────────────────────────────────────────────────╯{RESET}\n")
111
+ print()
112
+ print(box_header("🛡️ TORUSGUARD ROLLBACK RESTORATION", "Restored original files from pre-apply snapshot", "v1.3.3"))
113
+ print()
105
114
 
106
- print(f" {CYAN}┌─ Rollback Snapshot Restored ────────────────────────────────────────────┐{RESET}")
107
- print(format_box_line(f"Snapshot Run: {WHITE}{snapshot_dir.name}{RESET}"))
108
- print(format_box_line(f"Files Restored: {GREEN}{len(restored_files)} file(s) cleanly reverted{RESET}"))
115
+ print(border_top("Rollback Snapshot Restored"))
116
+ print(box_line(f"Snapshot Run: {WHITE}{snapshot_dir.name}{RESET}"))
117
+ print(box_line(f"Files Restored: {GREEN}{len(restored_files)} file(s) cleanly reverted{RESET}"))
109
118
  for rf in restored_files:
110
- print(format_box_line(f" ✔ {rf}"))
111
- print(f" {CYAN}└─────────────────────────────────────────────────────────────────────────┘{RESET}\n")
119
+ print(box_line(f" ✔ {rf}"))
120
+ print(border_bottom())
121
+ print()
112
122
  sys.exit(0)
113
123
 
114
124
 
@@ -127,21 +137,17 @@ def execute_apply(target_root: Path, run_id_arg: Optional[str] = None, auto_appr
127
137
  bundles_dir = run_folder / "bundles"
128
138
  bundle_meta_files = list(bundles_dir.glob("*/metadata.json")) if bundles_dir.is_dir() else []
129
139
  if not bundle_meta_files:
130
- print(f"\n {CYAN}╭─────────────────────────────────────────────────────────────────────────╮{RESET}")
131
- print(f" {CYAN}│ │{RESET}")
132
- print(format_box_line(f"{BOLD}🛡️ TORUSGUARD GOVERNED PATCH APPLIER v1.3.1{RESET}", border_color=CYAN))
133
- print(format_box_line(f"{DIM}Human-Gate Authorization, Snapshots & Golden Recipe Distillation{RESET}", border_color=CYAN))
134
- print(f" {CYAN}│ │{RESET}")
135
- print(f" {CYAN}╰─────────────────────────────────────────────────────────────────────────╯{RESET}\n")
136
-
140
+ print()
141
+ print(box_header("🛡️ TORUSGUARD GOVERNED PATCH APPLIER", "Human-Gate Authorization, Snapshots & Golden Recipe Distillation", "v1.3.3"))
142
+ print()
137
143
  print(f" {YELLOW}ℹ Zero candidate remediation bundles found in run: {run_folder.name}{RESET}")
138
144
  print(f" {GRAY}The findings in this run require manual review or AI-assisted remediation.{RESET}\n")
139
- print(f" {CYAN}┌─ Recommended Next Steps ────────────────────────────────────────────────┐{RESET}")
140
- print(format_box_line("1. In AI Chat (Antigravity/Cursor/Claude): Run /torusguard-harden"))
141
- print(format_box_line(" to formulate custom architectural patches with your AI agent."))
142
- print(format_box_line(f"2. Inspect findings: .torusguard/runs/{run_folder.name}/findings.md"))
143
- print(format_box_line("3. View visual posture report: npx torusguard report --html"))
144
- print(f" {CYAN}└─────────────────────────────────────────────────────────────────────────┘{RESET}\n")
145
+ print(border_top("Recommended Next Steps"))
146
+ print(box_line("1. In AI Chat: Run /torusguard-harden to synthesize custom fixes"))
147
+ print(box_line(f"2. Inspect findings: .torusguard/runs/{run_folder.name}/findings.md"))
148
+ print(box_line("3. View visual posture report: npx torusguard report --html"))
149
+ print(border_bottom())
150
+ print()
145
151
  sys.exit(0)
146
152
 
147
153
  bundles = []
@@ -156,18 +162,16 @@ def execute_apply(target_root: Path, run_id_arg: Optional[str] = None, auto_appr
156
162
  snapshot_dir.mkdir(parents=True, exist_ok=True)
157
163
 
158
164
  # Header Card
159
- print(f"\n {CYAN}╭─────────────────────────────────────────────────────────────────────────╮{RESET}")
160
- print(f" {CYAN}│ │{RESET}")
161
- print(format_box_line(f"{BOLD}🛡️ TORUSGUARD GOVERNED PATCH APPLIER v1.3.0{RESET}", border_color=CYAN))
162
- print(format_box_line(f"{DIM}Human-Gate Authorization, Snapshots & Golden Recipe Distillation{RESET}", border_color=CYAN))
163
- print(f" {CYAN}│ │{RESET}")
164
- print(f" {CYAN}╰─────────────────────────────────────────────────────────────────────────╯{RESET}\n")
165
-
166
- print(f" {CYAN}┌─ Candidate Bundles Ready for Application ───────────────────────────────┐{RESET}")
167
- print(format_box_line(f"Active Run: {WHITE}{run_folder.name}{RESET}"))
168
- print(format_box_line(f"Total Bundles: {GREEN}{len(bundles)} candidate surgical patches formulated{RESET}"))
169
- print(format_box_line(f"Rollback Safety:Automatic pre-apply snapshot to .torusguard/snapshots/{run_folder.name}"))
170
- print(f" {CYAN}└─────────────────────────────────────────────────────────────────────────┘{RESET}\n")
165
+ print()
166
+ print(box_header("🛡️ TORUSGUARD GOVERNED PATCH APPLIER", "Human-Gate Authorization, Snapshots & Golden Recipe Distillation", "v1.3.3"))
167
+ print()
168
+
169
+ print(border_top("Candidate Bundles Ready for Application"))
170
+ print(box_line(f"Active Run: {WHITE}{run_folder.name}{RESET}"))
171
+ print(box_line(f"Total Bundles: {GREEN}{len(bundles)} candidate surgical patches formulated{RESET}"))
172
+ print(box_line(f"Rollback Safety:Automatic pre-apply snapshot to .torusguard/snapshots/"))
173
+ print(border_bottom())
174
+ print()
171
175
 
172
176
  applied_count = 0
173
177
  skipped_count = 0
@@ -176,10 +180,10 @@ def execute_apply(target_root: Path, run_id_arg: Optional[str] = None, auto_appr
176
180
  applied_bundles = []
177
181
 
178
182
  # Import memory_engine for Golden Recipe distillation
179
- scripts_dir = Path(__file__).resolve().parent
180
- if str(scripts_dir) not in sys.path:
181
- sys.path.insert(0, str(scripts_dir))
182
- import memory_engine
183
+ try:
184
+ import memory_engine
185
+ except ImportError:
186
+ memory_engine = None
183
187
 
184
188
  for idx, b in enumerate(bundles, 1):
185
189
  rule_id = b.get("rule_id", "TG-SEC")
@@ -228,39 +232,67 @@ def execute_apply(target_root: Path, run_id_arg: Optional[str] = None, auto_appr
228
232
  print(f" {RED}✖ Target file not found: {target_file}{RESET}\n")
229
233
  continue
230
234
 
231
- # 1. Pre-apply Snapshot (.bak)
235
+ # 1. Pre-apply Snapshot (.bak) if not already created
232
236
  bak_dest = snapshot_dir / (rel_path + ".bak")
233
- bak_dest.parent.mkdir(parents=True, exist_ok=True)
234
- shutil.copy2(target_file, bak_dest)
235
-
236
- # 2. Write Patched Content
237
- if patched_content:
237
+ if not bak_dest.is_file():
238
+ bak_dest.parent.mkdir(parents=True, exist_ok=True)
239
+ shutil.copy2(target_file, bak_dest)
240
+
241
+ # 2. Write Patched Content (Sequential Line-Aware or Full Patch)
242
+ target_line = b.get("target_line")
243
+ replacement_line = b.get("replacement_line")
244
+ curr_text = target_file.read_text(encoding="utf-8", errors="replace")
245
+ curr_lines = curr_text.splitlines()
246
+
247
+ if target_line and replacement_line is not None:
248
+ applied_line = False
249
+ # Try expected line index
250
+ if 0 <= line_no - 1 < len(curr_lines) and curr_lines[line_no - 1] == target_line:
251
+ curr_lines[line_no - 1] = replacement_line
252
+ applied_line = True
253
+ else:
254
+ # Search across whole file for the target line
255
+ for i, cl in enumerate(curr_lines):
256
+ if cl == target_line:
257
+ curr_lines[i] = replacement_line
258
+ applied_line = True
259
+ break
260
+ if applied_line:
261
+ target_file.write_text("\n".join(curr_lines) + "\n", encoding="utf-8")
262
+ elif patched_content:
263
+ target_file.write_text(patched_content, encoding="utf-8")
264
+ elif patched_content:
238
265
  target_file.write_text(patched_content, encoding="utf-8")
239
266
  else:
240
267
  continue
241
268
 
242
269
  # 3. Distill into Golden Fix Recipe in Memory Engine
243
- try:
244
- fn = getattr(memory_engine, "record_golden_recipe", getattr(memory_engine, "store_golden_recipe", None))
245
- if fn:
246
- fn(
247
- rule_id=rule_id,
248
- before_snippet=target_file.read_text(encoding="utf-8").splitlines()[max(0, line_no-1)],
249
- after_snippet=desc,
250
- diff_snippet=diff,
251
- file_type=target_file.suffix,
252
- description=desc,
253
- root_dir=target_root
254
- )
255
- # Record event
256
- memory_engine.record_event("fix_applied", {
257
- "rule_id": rule_id,
258
- "file_path": rel_path,
259
- "line_number": line_no,
260
- "bundle_id": b.get("bundle_id")
261
- }, root_dir=target_root)
262
- except Exception:
263
- pass
270
+ if memory_engine:
271
+ try:
272
+ fn = getattr(memory_engine, "record_golden_recipe", getattr(memory_engine, "store_golden_recipe", None))
273
+ if fn:
274
+ before_lines = target_file.read_text(encoding="utf-8").splitlines()
275
+ before_snip = before_lines[max(0, line_no - 1)] if before_lines else ""
276
+ fn(
277
+ rule_id=rule_id,
278
+ before_snippet=before_snip,
279
+ after_snippet=desc,
280
+ diff_snippet=diff,
281
+ file_type=target_file.suffix,
282
+ description=desc,
283
+ root_dir=target_root
284
+ )
285
+ # Record event
286
+ record_ev = getattr(memory_engine, "record_event", None)
287
+ if record_ev:
288
+ record_ev("fix_applied", {
289
+ "rule_id": rule_id,
290
+ "file_path": rel_path,
291
+ "line_number": line_no,
292
+ "bundle_id": b.get("bundle_id")
293
+ }, root_dir=target_root)
294
+ except Exception:
295
+ pass
264
296
 
265
297
  applied_count += 1
266
298
  applied_bundles.append(b)
@@ -268,7 +300,11 @@ def execute_apply(target_root: Path, run_id_arg: Optional[str] = None, auto_appr
268
300
  diff_summary_lines.append("```diff")
269
301
  diff_summary_lines.append(diff.strip())
270
302
  diff_summary_lines.append("```\n")
271
- print(f" {GREEN}✔ Applied successfully.{RESET} (Snapshot saved: {bak_dest.relative_to(target_root)})\n")
303
+ try:
304
+ rel_snapshot = bak_dest.relative_to(target_root)
305
+ except Exception:
306
+ rel_snapshot = bak_dest.name
307
+ print(f" {GREEN}✔ Applied successfully.{RESET} (Snapshot saved: {rel_snapshot})\n")
272
308
 
273
309
  # Write apply artifacts in run folder
274
310
  (run_folder / "diff_summary.md").write_text("\n".join(diff_summary_lines), encoding="utf-8")
@@ -294,18 +330,20 @@ def execute_apply(target_root: Path, run_id_arg: Optional[str] = None, auto_appr
294
330
  pass
295
331
 
296
332
  # Summary Box
297
- print(f" {CYAN}┌─ Application Summary ───────────────────────────────────────────────────┐{RESET}")
298
- print(format_box_line(f"Applied Patches:{GREEN}{applied_count} applied cleanly{RESET}"))
299
- print(format_box_line(f"Skipped / Pass: {GRAY}{skipped_count} skipped{RESET}"))
300
- print(format_box_line(f"Golden Recipes: {GREEN}{applied_count} distilled into persistent memory{RESET}"))
301
- print(format_box_line(f"Snapshot Safety:.torusguard/snapshots/{run_folder.name}"))
302
- print(f" {CYAN}└─────────────────────────────────────────────────────────────────────────┘{RESET}\n")
303
-
304
- print(f" {GREEN}╔═ Next Governed Action ═════════════════════════════════════════════════════╗{RESET}")
305
- print(format_box_line(f"Differential Recheck: {BOLD}{WHITE}npx torusguard recheck{RESET} (Verify fix closure)", border="║", border_color=GREEN))
306
- print(format_box_line(f"View Golden Recipes: {CYAN}npx torusguard recipes{RESET} (Inspect memory)", border="║", border_color=GREEN))
307
- print(format_box_line(f"Emergency Rollback: {YELLOW}npx torusguard rollback{RESET} (Instant revert)", border="║", border_color=GREEN))
308
- print(f" {GREEN}╚═════════════════════════════════════════════════════════════════════════════╝{RESET}\n")
333
+ print(border_top("Application Summary"))
334
+ print(box_line(f"Applied Patches:{GREEN}{applied_count} applied cleanly{RESET}"))
335
+ print(box_line(f"Skipped / Pass: {GRAY}{skipped_count} skipped{RESET}"))
336
+ print(box_line(f"Golden Recipes: {GREEN}{applied_count} distilled into persistent memory{RESET}"))
337
+ print(box_line(f"Snapshot Safety:.torusguard/snapshots/{run_folder.name}"))
338
+ print(border_bottom())
339
+ print()
340
+
341
+ print(border_top("Next Governed Action", border_color=GREEN, double=True))
342
+ print(box_line(f"Differential Recheck: {BOLD}{WHITE}npx torusguard recheck{RESET} (Verify fix closure)", border="║", border_color=GREEN))
343
+ print(box_line(f"View Golden Recipes: {CYAN}npx torusguard recipes{RESET} (Inspect memory)", border="║", border_color=GREEN))
344
+ print(box_line(f"Emergency Rollback: {YELLOW}npx torusguard rollback{RESET} (Instant revert)", border="║", border_color=GREEN))
345
+ print(border_bottom(border_color=GREEN, double=True))
346
+ print()
309
347
 
310
348
  return {
311
349
  "applied_count": applied_count,