tribunal-kit 3.0.0 → 4.0.0

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 (233) hide show
  1. package/.agent/ARCHITECTURE.md +99 -99
  2. package/.agent/GEMINI.md +52 -52
  3. package/.agent/agents/accessibility-reviewer.md +187 -220
  4. package/.agent/agents/ai-code-reviewer.md +199 -233
  5. package/.agent/agents/backend-specialist.md +215 -238
  6. package/.agent/agents/code-archaeologist.md +161 -181
  7. package/.agent/agents/database-architect.md +184 -207
  8. package/.agent/agents/debugger.md +191 -218
  9. package/.agent/agents/dependency-reviewer.md +103 -136
  10. package/.agent/agents/devops-engineer.md +218 -238
  11. package/.agent/agents/documentation-writer.md +201 -221
  12. package/.agent/agents/explorer-agent.md +160 -180
  13. package/.agent/agents/frontend-reviewer.md +160 -194
  14. package/.agent/agents/frontend-specialist.md +248 -237
  15. package/.agent/agents/game-developer.md +48 -52
  16. package/.agent/agents/logic-reviewer.md +116 -149
  17. package/.agent/agents/mobile-developer.md +200 -223
  18. package/.agent/agents/mobile-reviewer.md +162 -195
  19. package/.agent/agents/orchestrator.md +181 -211
  20. package/.agent/agents/penetration-tester.md +157 -174
  21. package/.agent/agents/performance-optimizer.md +183 -203
  22. package/.agent/agents/performance-reviewer.md +178 -211
  23. package/.agent/agents/precedence-reviewer.md +213 -0
  24. package/.agent/agents/product-manager.md +142 -162
  25. package/.agent/agents/product-owner.md +6 -25
  26. package/.agent/agents/project-planner.md +142 -162
  27. package/.agent/agents/qa-automation-engineer.md +225 -242
  28. package/.agent/agents/security-auditor.md +174 -194
  29. package/.agent/agents/seo-specialist.md +193 -213
  30. package/.agent/agents/sql-reviewer.md +161 -194
  31. package/.agent/agents/supervisor-agent.md +184 -203
  32. package/.agent/agents/swarm-worker-contracts.md +17 -17
  33. package/.agent/agents/swarm-worker-registry.md +46 -46
  34. package/.agent/agents/test-coverage-reviewer.md +160 -193
  35. package/.agent/agents/test-engineer.md +0 -21
  36. package/.agent/agents/type-safety-reviewer.md +175 -208
  37. package/.agent/patterns/generator.md +9 -9
  38. package/.agent/patterns/inversion.md +12 -12
  39. package/.agent/patterns/pipeline.md +9 -9
  40. package/.agent/patterns/reviewer.md +13 -13
  41. package/.agent/patterns/tool-wrapper.md +9 -9
  42. package/.agent/rules/GEMINI.md +63 -63
  43. package/.agent/scripts/append_flow.js +72 -0
  44. package/.agent/scripts/case_law_manager.py +525 -0
  45. package/.agent/scripts/compress_skills.py +167 -0
  46. package/.agent/scripts/consolidate_skills.py +173 -0
  47. package/.agent/scripts/deep_compress.py +202 -0
  48. package/.agent/scripts/minify_context.py +80 -0
  49. package/.agent/scripts/security_scan.py +1 -1
  50. package/.agent/scripts/skill_evolution.py +563 -0
  51. package/.agent/scripts/strip_tribunal.py +41 -0
  52. package/.agent/skills/agent-organizer/SKILL.md +100 -126
  53. package/.agent/skills/agentic-patterns/SKILL.md +0 -70
  54. package/.agent/skills/ai-prompt-injection-defense/SKILL.md +134 -160
  55. package/.agent/skills/api-patterns/SKILL.md +123 -215
  56. package/.agent/skills/api-security-auditor/SKILL.md +143 -177
  57. package/.agent/skills/app-builder/SKILL.md +334 -50
  58. package/.agent/skills/app-builder/templates/SKILL.md +13 -15
  59. package/.agent/skills/app-builder/templates/astro-static/TEMPLATE.md +16 -16
  60. package/.agent/skills/app-builder/templates/chrome-extension/TEMPLATE.md +22 -22
  61. package/.agent/skills/app-builder/templates/cli-tool/TEMPLATE.md +18 -18
  62. package/.agent/skills/app-builder/templates/electron-desktop/TEMPLATE.md +20 -20
  63. package/.agent/skills/app-builder/templates/express-api/TEMPLATE.md +17 -17
  64. package/.agent/skills/app-builder/templates/flutter-app/TEMPLATE.md +18 -18
  65. package/.agent/skills/app-builder/templates/monorepo-turborepo/TEMPLATE.md +21 -21
  66. package/.agent/skills/app-builder/templates/nextjs-fullstack/TEMPLATE.md +19 -19
  67. package/.agent/skills/app-builder/templates/nextjs-saas/TEMPLATE.md +26 -26
  68. package/.agent/skills/app-builder/templates/nextjs-static/TEMPLATE.md +26 -26
  69. package/.agent/skills/app-builder/templates/nuxt-app/TEMPLATE.md +19 -19
  70. package/.agent/skills/app-builder/templates/python-fastapi/TEMPLATE.md +18 -18
  71. package/.agent/skills/app-builder/templates/react-native-app/TEMPLATE.md +20 -20
  72. package/.agent/skills/appflow-wireframe/SKILL.md +95 -121
  73. package/.agent/skills/architecture/SKILL.md +169 -331
  74. package/.agent/skills/authentication-best-practices/SKILL.md +139 -173
  75. package/.agent/skills/bash-linux/SKILL.md +129 -154
  76. package/.agent/skills/behavioral-modes/SKILL.md +8 -69
  77. package/.agent/skills/brainstorming/SKILL.md +436 -104
  78. package/.agent/skills/building-native-ui/SKILL.md +152 -174
  79. package/.agent/skills/clean-code/SKILL.md +331 -360
  80. package/.agent/skills/code-review-checklist/SKILL.md +0 -62
  81. package/.agent/skills/config-validator/SKILL.md +115 -141
  82. package/.agent/skills/csharp-developer/SKILL.md +468 -528
  83. package/.agent/skills/database-design/SKILL.md +104 -369
  84. package/.agent/skills/deployment-procedures/SKILL.md +119 -145
  85. package/.agent/skills/devops-engineer/SKILL.md +295 -332
  86. package/.agent/skills/devops-incident-responder/SKILL.md +87 -113
  87. package/.agent/skills/doc.md +5 -5
  88. package/.agent/skills/documentation-templates/SKILL.md +27 -63
  89. package/.agent/skills/edge-computing/SKILL.md +131 -157
  90. package/.agent/skills/extract-design-system/SKILL.md +108 -134
  91. package/.agent/skills/framer-motion-expert/SKILL.md +111 -855
  92. package/.agent/skills/frontend-design/SKILL.md +151 -499
  93. package/.agent/skills/game-design-expert/SKILL.md +79 -105
  94. package/.agent/skills/game-engineering-expert/SKILL.md +96 -122
  95. package/.agent/skills/geo-fundamentals/SKILL.md +97 -124
  96. package/.agent/skills/github-operations/SKILL.md +279 -314
  97. package/.agent/skills/gsap-expert/SKILL.md +119 -826
  98. package/.agent/skills/i18n-localization/SKILL.md +113 -138
  99. package/.agent/skills/intelligent-routing/SKILL.md +167 -127
  100. package/.agent/skills/lint-and-validate/SKILL.md +16 -52
  101. package/.agent/skills/llm-engineering/SKILL.md +344 -357
  102. package/.agent/skills/local-first/SKILL.md +128 -154
  103. package/.agent/skills/mcp-builder/SKILL.md +92 -118
  104. package/.agent/skills/mobile-design/SKILL.md +213 -219
  105. package/.agent/skills/motion-engineering/SKILL.md +184 -0
  106. package/.agent/skills/nextjs-react-expert/SKILL.md +99 -698
  107. package/.agent/skills/nodejs-best-practices/SKILL.md +498 -559
  108. package/.agent/skills/observability/SKILL.md +293 -330
  109. package/.agent/skills/parallel-agents/SKILL.md +96 -122
  110. package/.agent/skills/performance-profiling/SKILL.md +217 -254
  111. package/.agent/skills/plan-writing/SKILL.md +92 -118
  112. package/.agent/skills/platform-engineer/SKILL.md +97 -123
  113. package/.agent/skills/playwright-best-practices/SKILL.md +137 -162
  114. package/.agent/skills/powershell-windows/SKILL.md +112 -146
  115. package/.agent/skills/project-idioms/SKILL.md +87 -0
  116. package/.agent/skills/python-patterns/SKILL.md +15 -35
  117. package/.agent/skills/python-pro/SKILL.md +148 -754
  118. package/.agent/skills/react-specialist/SKILL.md +123 -827
  119. package/.agent/skills/readme-builder/SKILL.md +23 -85
  120. package/.agent/skills/realtime-patterns/SKILL.md +269 -304
  121. package/.agent/skills/red-team-tactics/SKILL.md +18 -51
  122. package/.agent/skills/rust-pro/SKILL.md +623 -701
  123. package/.agent/skills/seo-fundamentals/SKILL.md +129 -154
  124. package/.agent/skills/server-management/SKILL.md +164 -190
  125. package/.agent/skills/shadcn-ui-expert/SKILL.md +181 -206
  126. package/.agent/skills/skill-creator/SKILL.md +24 -56
  127. package/.agent/skills/sql-pro/SKILL.md +579 -633
  128. package/.agent/skills/supabase-postgres-best-practices/SKILL.md +35 -66
  129. package/.agent/skills/swiftui-expert/SKILL.md +151 -176
  130. package/.agent/skills/systematic-debugging/SKILL.md +92 -118
  131. package/.agent/skills/tailwind-patterns/SKILL.md +516 -576
  132. package/.agent/skills/tdd-workflow/SKILL.md +111 -137
  133. package/.agent/skills/test-result-analyzer/SKILL.md +33 -73
  134. package/.agent/skills/testing-patterns/SKILL.md +512 -573
  135. package/.agent/skills/trend-researcher/SKILL.md +30 -71
  136. package/.agent/skills/ui-ux-pro-max/SKILL.md +8 -41
  137. package/.agent/skills/ui-ux-researcher/SKILL.md +51 -91
  138. package/.agent/skills/vue-expert/SKILL.md +127 -866
  139. package/.agent/skills/vulnerability-scanner/SKILL.md +354 -269
  140. package/.agent/skills/web-accessibility-auditor/SKILL.md +168 -193
  141. package/.agent/skills/web-design-guidelines/SKILL.md +25 -61
  142. package/.agent/skills/webapp-testing/SKILL.md +119 -145
  143. package/.agent/skills/whimsy-injector/SKILL.md +58 -132
  144. package/.agent/skills/workflow-optimizer/SKILL.md +28 -68
  145. package/.agent/workflows/api-tester.md +151 -151
  146. package/.agent/workflows/audit.md +127 -138
  147. package/.agent/workflows/brainstorm.md +110 -110
  148. package/.agent/workflows/changelog.md +112 -112
  149. package/.agent/workflows/create.md +124 -124
  150. package/.agent/workflows/debug.md +165 -189
  151. package/.agent/workflows/deploy.md +180 -189
  152. package/.agent/workflows/enhance.md +128 -151
  153. package/.agent/workflows/fix.md +114 -135
  154. package/.agent/workflows/generate.md +13 -4
  155. package/.agent/workflows/migrate.md +160 -160
  156. package/.agent/workflows/orchestrate.md +168 -168
  157. package/.agent/workflows/performance-benchmarker.md +114 -123
  158. package/.agent/workflows/plan.md +173 -173
  159. package/.agent/workflows/preview.md +80 -80
  160. package/.agent/workflows/refactor.md +161 -183
  161. package/.agent/workflows/review-ai.md +101 -129
  162. package/.agent/workflows/review.md +116 -116
  163. package/.agent/workflows/session.md +94 -94
  164. package/.agent/workflows/status.md +79 -79
  165. package/.agent/workflows/strengthen-skills.md +138 -139
  166. package/.agent/workflows/swarm.md +179 -179
  167. package/.agent/workflows/test.md +189 -211
  168. package/.agent/workflows/tribunal-backend.md +94 -113
  169. package/.agent/workflows/tribunal-database.md +95 -115
  170. package/.agent/workflows/tribunal-frontend.md +96 -118
  171. package/.agent/workflows/tribunal-full.md +93 -133
  172. package/.agent/workflows/tribunal-mobile.md +95 -119
  173. package/.agent/workflows/tribunal-performance.md +110 -133
  174. package/.agent/workflows/ui-ux-pro-max.md +122 -143
  175. package/README.md +30 -1
  176. package/bin/tribunal-kit.js +175 -12
  177. package/package.json +25 -4
  178. package/.agent/skills/api-patterns/api-style.md +0 -42
  179. package/.agent/skills/api-patterns/auth.md +0 -24
  180. package/.agent/skills/api-patterns/documentation.md +0 -26
  181. package/.agent/skills/api-patterns/graphql.md +0 -41
  182. package/.agent/skills/api-patterns/rate-limiting.md +0 -31
  183. package/.agent/skills/api-patterns/response.md +0 -37
  184. package/.agent/skills/api-patterns/rest.md +0 -40
  185. package/.agent/skills/api-patterns/security-testing.md +0 -122
  186. package/.agent/skills/api-patterns/trpc.md +0 -41
  187. package/.agent/skills/api-patterns/versioning.md +0 -22
  188. package/.agent/skills/app-builder/agent-coordination.md +0 -71
  189. package/.agent/skills/app-builder/feature-building.md +0 -53
  190. package/.agent/skills/app-builder/project-detection.md +0 -34
  191. package/.agent/skills/app-builder/scaffolding.md +0 -118
  192. package/.agent/skills/app-builder/tech-stack.md +0 -40
  193. package/.agent/skills/architecture/context-discovery.md +0 -43
  194. package/.agent/skills/architecture/examples.md +0 -94
  195. package/.agent/skills/architecture/pattern-selection.md +0 -68
  196. package/.agent/skills/architecture/patterns-reference.md +0 -50
  197. package/.agent/skills/architecture/trade-off-analysis.md +0 -77
  198. package/.agent/skills/brainstorming/dynamic-questioning.md +0 -360
  199. package/.agent/skills/database-design/database-selection.md +0 -43
  200. package/.agent/skills/database-design/indexing.md +0 -39
  201. package/.agent/skills/database-design/migrations.md +0 -48
  202. package/.agent/skills/database-design/optimization.md +0 -36
  203. package/.agent/skills/database-design/orm-selection.md +0 -30
  204. package/.agent/skills/database-design/schema-design.md +0 -56
  205. package/.agent/skills/frontend-design/animation-guide.md +0 -331
  206. package/.agent/skills/frontend-design/color-system.md +0 -329
  207. package/.agent/skills/frontend-design/decision-trees.md +0 -418
  208. package/.agent/skills/frontend-design/motion-graphics.md +0 -306
  209. package/.agent/skills/frontend-design/typography-system.md +0 -363
  210. package/.agent/skills/frontend-design/ux-psychology.md +0 -1116
  211. package/.agent/skills/frontend-design/visual-effects.md +0 -383
  212. package/.agent/skills/intelligent-routing/router-manifest.md +0 -65
  213. package/.agent/skills/mobile-design/decision-trees.md +0 -516
  214. package/.agent/skills/mobile-design/mobile-backend.md +0 -491
  215. package/.agent/skills/mobile-design/mobile-color-system.md +0 -420
  216. package/.agent/skills/mobile-design/mobile-debugging.md +0 -122
  217. package/.agent/skills/mobile-design/mobile-design-thinking.md +0 -357
  218. package/.agent/skills/mobile-design/mobile-navigation.md +0 -458
  219. package/.agent/skills/mobile-design/mobile-performance.md +0 -767
  220. package/.agent/skills/mobile-design/mobile-testing.md +0 -356
  221. package/.agent/skills/mobile-design/mobile-typography.md +0 -433
  222. package/.agent/skills/mobile-design/platform-android.md +0 -666
  223. package/.agent/skills/mobile-design/platform-ios.md +0 -561
  224. package/.agent/skills/mobile-design/touch-psychology.md +0 -537
  225. package/.agent/skills/nextjs-react-expert/1-async-eliminating-waterfalls.md +0 -312
  226. package/.agent/skills/nextjs-react-expert/2-bundle-bundle-size-optimization.md +0 -240
  227. package/.agent/skills/nextjs-react-expert/3-server-server-side-performance.md +0 -490
  228. package/.agent/skills/nextjs-react-expert/4-client-client-side-data-fetching.md +0 -264
  229. package/.agent/skills/nextjs-react-expert/5-rerender-re-render-optimization.md +0 -581
  230. package/.agent/skills/nextjs-react-expert/6-rendering-rendering-performance.md +0 -432
  231. package/.agent/skills/nextjs-react-expert/7-js-javascript-performance.md +0 -684
  232. package/.agent/skills/nextjs-react-expert/8-advanced-advanced-patterns.md +0 -150
  233. package/.agent/skills/vulnerability-scanner/checklists.md +0 -121
@@ -0,0 +1,525 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ case_law_manager.py — Tribunal Kit Case Law Engine
4
+ =====================================================
5
+ Records rejected code patterns as "Cases" and surfaces them as
6
+ binding Legal Precedence during future Tribunal reviews.
7
+
8
+ Usage:
9
+ python .agent/scripts/case_law_manager.py add-case
10
+ python .agent/scripts/case_law_manager.py search-cases --query "forEach side effects"
11
+ python .agent/scripts/case_law_manager.py list
12
+ python .agent/scripts/case_law_manager.py show --id 7
13
+ python .agent/scripts/case_law_manager.py export
14
+ python .agent/scripts/case_law_manager.py stats
15
+
16
+ Storage:
17
+ .agent/history/case-law/index.json ← master index of all cases
18
+ .agent/history/case-law/cases/ ← one JSON file per case
19
+ """
20
+
21
+ import os
22
+ import sys
23
+ import json
24
+ import hashlib
25
+ import re
26
+ from pathlib import Path
27
+ from datetime import datetime
28
+
29
+ # ── Colours ──────────────────────────────────────────────────────────────────
30
+ GREEN = "\033[92m"
31
+ YELLOW = "\033[93m"
32
+ CYAN = "\033[96m"
33
+ RED = "\033[91m"
34
+ BLUE = "\033[94m"
35
+ BOLD = "\033[1m"
36
+ DIM = "\033[2m"
37
+ RESET = "\033[0m"
38
+
39
+ # ── Paths ─────────────────────────────────────────────────────────────────────
40
+ def find_agent_dir() -> Path:
41
+ """Walk up until we find .agent/"""
42
+ current = Path.cwd()
43
+ while current != current.parent:
44
+ candidate = current / ".agent"
45
+ if candidate.is_dir():
46
+ return candidate
47
+ current = current.parent
48
+
49
+ print("\033[91m✖ Error: '.agent' directory not found. Please run 'npx tribunal-kit init' first.\033[0m")
50
+ sys.exit(1)
51
+
52
+ AGENT_DIR = find_agent_dir()
53
+ HISTORY_DIR = AGENT_DIR / "history" / "case-law"
54
+ CASES_DIR = HISTORY_DIR / "cases"
55
+ INDEX_FILE = HISTORY_DIR / "index.json"
56
+
57
+ VALID_DOMAINS = {
58
+ "backend", "frontend", "database", "security",
59
+ "performance", "mobile", "testing", "devops", "general"
60
+ }
61
+
62
+ VALID_VERDICTS = {"REJECTED", "APPROVED_WITH_CONDITIONS", "PRECEDENT_SET"}
63
+
64
+ # ── Trivial-change filter (Semantic Delta) ────────────────────────────────────
65
+ TRIVIAL_PATTERNS = [
66
+ r"^\s*$", # blank lines
67
+ r"^\s*//.*$", # comment-only lines
68
+ r"^\s*#.*$", # python comments
69
+ r"^\s*\*.*$", # JSDoc lines
70
+ r"^[\+\-]\s*(import\s+\{[^}]+\}|from\s+['\"])", # import reorders
71
+ ]
72
+
73
+ def is_trivial_line(line: str) -> bool:
74
+ for pattern in TRIVIAL_PATTERNS:
75
+ if re.match(pattern, line):
76
+ return True
77
+ return False
78
+
79
+ def semantic_delta(diff_text: str) -> str:
80
+ """
81
+ Strip trivial changes from a diff and return only the meaningful
82
+ architectural delta. This is the core token-saving mechanism:
83
+ 80% of whitespace/comment/import-order noise is removed before
84
+ any LLM sees the content.
85
+ """
86
+ lines = diff_text.splitlines()
87
+ meaningful = []
88
+ for line in lines:
89
+ if line.startswith(("+++", "---", "@@")):
90
+ meaningful.append(line)
91
+ continue
92
+ if line.startswith(("+", "-")):
93
+ code_part = line[1:]
94
+ if not is_trivial_line(code_part):
95
+ meaningful.append(line)
96
+ else:
97
+ meaningful.append(line)
98
+
99
+ filtered = "\n".join(meaningful)
100
+ # Collapse runs of 3+ blank context lines into separator
101
+ filtered = re.sub(r"(\n[ ]{0,1}\n){3,}", "\n\n", filtered)
102
+ return filtered.strip()
103
+
104
+ def content_hash(text: str) -> str:
105
+ """Stable 8-char fingerprint of meaningful content."""
106
+ cleaned = semantic_delta(text)
107
+ return hashlib.sha256(cleaned.encode()).hexdigest()[:8]
108
+
109
+ # ── Index helpers ─────────────────────────────────────────────────────────────
110
+ def load_index() -> dict:
111
+ HISTORY_DIR.mkdir(parents=True, exist_ok=True)
112
+ CASES_DIR.mkdir(parents=True, exist_ok=True)
113
+ if INDEX_FILE.exists():
114
+ try:
115
+ return json.loads(INDEX_FILE.read_text(encoding="utf-8"))
116
+ except (json.JSONDecodeError, IOError):
117
+ pass
118
+ return {"version": "1.0", "cases": [], "next_id": 1}
119
+
120
+ def save_index(index: dict) -> None:
121
+ HISTORY_DIR.mkdir(parents=True, exist_ok=True)
122
+ INDEX_FILE.write_text(json.dumps(index, indent=2), encoding="utf-8")
123
+
124
+ def load_case(case_id: int) -> dict | None:
125
+ path = CASES_DIR / f"case-{case_id:04d}.json"
126
+ if not path.exists():
127
+ return None
128
+ try:
129
+ return json.loads(path.read_text(encoding="utf-8"))
130
+ except Exception:
131
+ return None
132
+
133
+ def save_case(case: dict) -> None:
134
+ path = CASES_DIR / f"case-{case['id']:04d}.json"
135
+ path.write_text(json.dumps(case, indent=2), encoding="utf-8")
136
+
137
+ # ── Keyword/tag extraction ─────────────────────────────────────────────────────
138
+ def extract_tags(text: str) -> list[str]:
139
+ """Extract meaningful code-level keywords from diff/reason text."""
140
+ # Pull identifiers: camelCase, snake_case, method calls
141
+ tokens = re.findall(r"\b[a-zA-Z_][a-zA-Z0-9_]{2,}\b", text)
142
+ stop_words = {
143
+ "the", "and", "for", "was", "this", "with", "that",
144
+ "from", "are", "not", "use", "but", "also", "code",
145
+ "have", "will", "should", "must", "can", "may", "any",
146
+ "all", "new", "old", "add", "get", "set", "var", "let",
147
+ "const", "function", "return", "import", "export", "class",
148
+ "async", "await", "true", "false", "null", "undefined"
149
+ }
150
+ seen = set()
151
+ tags = []
152
+ for token in tokens:
153
+ lower = token.lower()
154
+ if lower not in stop_words and lower not in seen:
155
+ seen.add(lower)
156
+ tags.append(lower)
157
+ if len(tags) >= 20:
158
+ break
159
+ return tags
160
+
161
+ # ── Similarity scoring ────────────────────────────────────────────────────────
162
+ def jaccard_similarity(tags_a: list[str], tags_b: list[str]) -> float:
163
+ """Simple token overlap — no LLM required."""
164
+ if not tags_a or not tags_b:
165
+ return 0.0
166
+ set_a, set_b = set(tags_a), set(tags_b)
167
+ return len(set_a & set_b) / len(set_a | set_b)
168
+
169
+ # ── Commands ──────────────────────────────────────────────────────────────────
170
+ def cmd_add_case(args: list[str]) -> None:
171
+ """
172
+ Interactive case recording. All fields collected via prompts so the
173
+ agent can call this without any arguments — the script does the rest.
174
+ """
175
+ print(f"\n{BOLD}{CYAN}━━━ Recording New Case ━━━━━━━━━━━━━━━━━━━━━━━━━━━━{RESET}")
176
+
177
+ # ── Gather fields ─────
178
+ diff_text = prompt_multiline("Paste the REJECTED diff (code snippet):", "END_DIFF")
179
+ if not diff_text.strip():
180
+ print(f"{RED}✖ Diff cannot be empty. Aborting.{RESET}")
181
+ sys.exit(1)
182
+
183
+ reason = prompt_line("Rejection reason (1-2 sentences):")
184
+ if not reason.strip():
185
+ print(f"{RED}✖ Reason cannot be empty. Aborting.{RESET}")
186
+ sys.exit(1)
187
+
188
+ domain = prompt_choice(
189
+ "Domain",
190
+ sorted(VALID_DOMAINS),
191
+ default="general"
192
+ )
193
+
194
+ verdict = prompt_choice(
195
+ "Verdict",
196
+ sorted(VALID_VERDICTS),
197
+ default="REJECTED"
198
+ )
199
+
200
+ pr_ref = prompt_line("PR / commit reference (optional, e.g. PR-404):").strip() or None
201
+ reviewer = prompt_line("Reviewer agent (optional, e.g. security-auditor):").strip() or None
202
+
203
+ # ── Build delta ──────
204
+ delta = semantic_delta(diff_text)
205
+ fingerprint = content_hash(diff_text)
206
+ tags = extract_tags(diff_text + " " + reason)
207
+
208
+ # ── Persist ──────────
209
+ index = load_index()
210
+ case_id = index["next_id"]
211
+
212
+ case = {
213
+ "id": case_id,
214
+ "fingerprint": fingerprint,
215
+ "timestamp": datetime.now().isoformat(timespec="seconds"),
216
+ "domain": domain,
217
+ "verdict": verdict,
218
+ "reason": reason.strip(),
219
+ "pr_ref": pr_ref,
220
+ "reviewer": reviewer,
221
+ "tags": tags,
222
+ "diff_raw": diff_text.strip(),
223
+ "diff_delta": delta
224
+ }
225
+
226
+ save_case(case)
227
+
228
+ # Update index
229
+ index["cases"].append({
230
+ "id": case_id,
231
+ "fingerprint": fingerprint,
232
+ "domain": domain,
233
+ "verdict": verdict,
234
+ "tags": tags,
235
+ "timestamp": case["timestamp"],
236
+ "reason_summary": reason.strip()[:120]
237
+ })
238
+ index["next_id"] = case_id + 1
239
+ save_index(index)
240
+
241
+ print(f"\n{GREEN}✔ Case #{case_id:04d} recorded{RESET}")
242
+ print(f" {DIM}Fingerprint : {fingerprint}{RESET}")
243
+ print(f" {DIM}Domain : {domain}{RESET}")
244
+ print(f" {DIM}Tags : {', '.join(tags[:8])}{RESET}")
245
+ print(f" {DIM}Stored at : {CASES_DIR / f'case-{case_id:04d}.json'}{RESET}")
246
+ print()
247
+
248
+
249
+ def cmd_search_cases(args: list[str]) -> None:
250
+ """
251
+ Search past cases by query text using Jaccard tag similarity.
252
+ Token-free — no LLM call required.
253
+ """
254
+ query = " ".join(args)
255
+ if not query:
256
+ # Try reading from --query flag
257
+ try:
258
+ qi = sys.argv.index("--query")
259
+ query = " ".join(sys.argv[qi + 1:])
260
+ except (ValueError, IndexError):
261
+ pass
262
+
263
+ if not query:
264
+ print(f"{RED}✖ Provide a search query: search-cases --query \"forEach side effects\"{RESET}")
265
+ sys.exit(1)
266
+
267
+ query_tags = extract_tags(query)
268
+ index = load_index()
269
+
270
+ if not index["cases"]:
271
+ print(f"{YELLOW}No cases recorded yet. Use 'add-case' to record your first rejection.{RESET}")
272
+ return
273
+
274
+ # Score every case
275
+ scored = []
276
+ for entry in index["cases"]:
277
+ score = jaccard_similarity(query_tags, entry.get("tags", []))
278
+ if score > 0.0:
279
+ scored.append((score, entry))
280
+
281
+ scored.sort(key=lambda x: x[0], reverse=True)
282
+ top = scored[:5]
283
+
284
+ if not top:
285
+ print(f"{YELLOW}No matching cases found for: \"{query}\"{RESET}")
286
+ print(f" {DIM}Try broader terms or check 'list' for available cases.{RESET}")
287
+ return
288
+
289
+ print(f"\n{BOLD}{CYAN}━━━ Case Law Search Results ━━━━━━━━━━━━━━━━━━━━━━━{RESET}")
290
+ print(f" Query : {BOLD}{query}{RESET}")
291
+ print(f" Matches: {len(top)} of {len(index['cases'])} cases\n")
292
+
293
+ for score, entry in top:
294
+ verdict_color = RED if entry["verdict"] == "REJECTED" else YELLOW
295
+ print(f" {BOLD}Case #{entry['id']:04d}{RESET} {verdict_color}[{entry['verdict']}]{RESET} "
296
+ f"{DIM}{entry['timestamp'][:10]}{RESET} score={score:.2f}")
297
+ print(f" {DIM}Domain: {entry['domain']}{RESET}")
298
+ print(f" {entry['reason_summary']}")
299
+ print(f" {DIM}Tags: {', '.join(entry['tags'][:8])}{RESET}")
300
+ print()
301
+
302
+ print(f" {DIM}Run 'show --id <N>' to see the full diff for any case.{RESET}")
303
+ print(f"{CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━{RESET}\n")
304
+
305
+
306
+ def cmd_list(args: list[str]) -> None:
307
+ index = load_index()
308
+ cases = index.get("cases", [])
309
+ if not cases:
310
+ print(f"{YELLOW}No cases recorded yet.{RESET}")
311
+ return
312
+
313
+ domain_filter = None
314
+ if "--domain" in args:
315
+ try:
316
+ domain_filter = args[args.index("--domain") + 1].lower()
317
+ except IndexError:
318
+ pass
319
+
320
+ filtered = [c for c in cases if not domain_filter or c["domain"] == domain_filter]
321
+ total = len(filtered)
322
+
323
+ print(f"\n{BOLD}{CYAN}━━━ Case Law Index ({total} cases) ━━━━━━━━━━━━━━━━━━━━{RESET}")
324
+ if domain_filter:
325
+ print(f" {DIM}Filtered by domain: {domain_filter}{RESET}\n")
326
+
327
+ for entry in reversed(filtered[-20:]):
328
+ verdict_color = RED if entry["verdict"] == "REJECTED" else YELLOW
329
+ print(f" {BOLD}#{entry['id']:04d}{RESET} "
330
+ f"{verdict_color}[{entry['verdict']}]{RESET} "
331
+ f"{DIM}{entry['domain'].upper()}{RESET} "
332
+ f"{entry['timestamp'][:10]}")
333
+ print(f" {entry['reason_summary'][:80]}")
334
+
335
+ if total > 20:
336
+ print(f"\n {YELLOW}... showing last 20 of {total}. Use 'export' for full history.{RESET}")
337
+ print(f"{CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━{RESET}\n")
338
+
339
+
340
+ def cmd_show(args: list[str]) -> None:
341
+ case_id = None
342
+ if "--id" in args:
343
+ try:
344
+ case_id = int(args[args.index("--id") + 1])
345
+ except (IndexError, ValueError):
346
+ pass
347
+
348
+ if case_id is None:
349
+ print(f"{RED}✖ Provide a case ID: show --id 7{RESET}")
350
+ sys.exit(1)
351
+
352
+ case = load_case(case_id)
353
+ if not case:
354
+ print(f"{RED}✖ Case #{case_id:04d} not found.{RESET}")
355
+ sys.exit(1)
356
+
357
+ verdict_color = RED if case["verdict"] == "REJECTED" else YELLOW
358
+ print(f"\n{BOLD}{CYAN}━━━ Case #{case['id']:04d} ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━{RESET}")
359
+ print(f" Verdict : {verdict_color}{BOLD}{case['verdict']}{RESET}")
360
+ print(f" Domain : {case['domain']}")
361
+ print(f" Recorded : {case['timestamp']}")
362
+ if case.get("pr_ref"):
363
+ print(f" PR / Ref : {case['pr_ref']}")
364
+ if case.get("reviewer"):
365
+ print(f" Reviewer : {case['reviewer']}")
366
+ print(f"\n {BOLD}Reason:{RESET}")
367
+ print(f" {case['reason']}")
368
+ print(f"\n {BOLD}Semantic Delta (meaningful changes only):{RESET}")
369
+ print(f" {DIM}─────────────────────────────────────────{RESET}")
370
+ for line in case.get("diff_delta", case["diff_raw"]).splitlines()[:40]:
371
+ if line.startswith("+"):
372
+ print(f" {GREEN}{line}{RESET}")
373
+ elif line.startswith("-"):
374
+ print(f" {RED}{line}{RESET}")
375
+ else:
376
+ print(f" {DIM}{line}{RESET}")
377
+ print(f"\n {BOLD}Tags:{RESET} {', '.join(case.get('tags', []))}")
378
+ print(f"{CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━{RESET}\n")
379
+
380
+
381
+ def cmd_export(args: list[str]) -> None:
382
+ to_stdout = "--stdout" in args
383
+ index = load_index()
384
+ cases = index.get("cases", [])
385
+
386
+ if not cases:
387
+ print(f"{YELLOW}No cases to export.{RESET}")
388
+ return
389
+
390
+ lines = [
391
+ "# Tribunal Case Law — Full Export\n",
392
+ f"Generated: {datetime.now().isoformat(timespec='seconds')}",
393
+ f"Total Cases: {len(cases)}\n",
394
+ "---\n"
395
+ ]
396
+
397
+ for entry in cases:
398
+ case = load_case(entry["id"]) or entry
399
+ verdict_badge = f"[{case.get('verdict', 'REJECTED')}]"
400
+ lines.append(f"## Case #{entry['id']:04d} {verdict_badge}")
401
+ lines.append(f"**Domain:** {entry['domain']} ")
402
+ lines.append(f"**Recorded:** {entry['timestamp'][:10]} ")
403
+ if case.get("pr_ref"):
404
+ lines.append(f"**PR/Ref:** {case['pr_ref']} ")
405
+ lines.append(f"\n**Reason:** {entry['reason_summary']}\n")
406
+ lines.append(f"**Tags:** `{', '.join(entry['tags'][:8])}`\n")
407
+ lines.append("---\n")
408
+
409
+ content = "\n".join(lines)
410
+
411
+ if to_stdout:
412
+ print(content)
413
+ else:
414
+ out_path = HISTORY_DIR / "case-law-export.md"
415
+ out_path.write_text(content, encoding="utf-8")
416
+ print(f"{GREEN}✔ Exported {len(cases)} cases to {out_path}{RESET}")
417
+
418
+
419
+ def cmd_stats(args: list[str]) -> None:
420
+ index = load_index()
421
+ cases = index.get("cases", [])
422
+
423
+ domain_counts: dict[str, int] = {}
424
+ verdict_counts: dict[str, int] = {}
425
+ for c in cases:
426
+ domain_counts[c["domain"]] = domain_counts.get(c["domain"], 0) + 1
427
+ verdict_counts[c["verdict"]] = verdict_counts.get(c["verdict"], 0) + 1
428
+
429
+ print(f"\n{BOLD}{CYAN}━━━ Case Law Statistics ━━━━━━━━━━━━━━━━━━━━━━━━━━━{RESET}")
430
+ print(f" Total cases: {BOLD}{len(cases)}{RESET}")
431
+ print(f"\n {BOLD}By Verdict:{RESET}")
432
+ for verdict, count in sorted(verdict_counts.items()):
433
+ color = RED if verdict == "REJECTED" else YELLOW
434
+ print(f" {color}{verdict:<30}{RESET} {count}")
435
+ print(f"\n {BOLD}By Domain:{RESET}")
436
+ for domain, count in sorted(domain_counts.items(), key=lambda x: -x[1]):
437
+ print(f" {CYAN}{domain:<20}{RESET} {count}")
438
+ print(f"{CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━{RESET}\n")
439
+
440
+
441
+ # ── Input helpers ─────────────────────────────────────────────────────────────
442
+ def prompt_multiline(prompt: str, sentinel: str) -> str:
443
+ print(f" {BOLD}{prompt}{RESET}")
444
+ print(f" {DIM}(Type or paste content. Type '{sentinel}' on its own line when done.){RESET}")
445
+ lines = []
446
+ while True:
447
+ try:
448
+ line = input()
449
+ except EOFError:
450
+ break
451
+ if line.strip() == sentinel:
452
+ break
453
+ lines.append(line)
454
+ return "\n".join(lines)
455
+
456
+
457
+ def prompt_line(prompt: str) -> str:
458
+ print(f" {BOLD}{prompt}{RESET}", end=" ")
459
+ try:
460
+ return input()
461
+ except EOFError:
462
+ return ""
463
+
464
+
465
+ def prompt_choice(label: str, choices: list[str], default: str) -> str:
466
+ opts = " / ".join(
467
+ f"{BOLD}{c}{RESET}" if c == default else c
468
+ for c in choices
469
+ )
470
+ print(f" {BOLD}{label}{RESET} [{opts}] (default: {default}): ", end="")
471
+ try:
472
+ value = input().strip().lower()
473
+ except EOFError:
474
+ return default
475
+ if not value or value not in choices:
476
+ return default
477
+ return value
478
+
479
+
480
+ # ── Main ──────────────────────────────────────────────────────────────────────
481
+ COMMANDS = {
482
+ "add-case": cmd_add_case,
483
+ "search-cases": cmd_search_cases,
484
+ "list": cmd_list,
485
+ "show": cmd_show,
486
+ "export": cmd_export,
487
+ "stats": cmd_stats,
488
+ }
489
+
490
+ def main() -> None:
491
+ # Ensure Unicode output works on Windows terminals
492
+ if hasattr(sys.stdout, "reconfigure"):
493
+ sys.stdout.reconfigure(encoding="utf-8", errors="replace")
494
+
495
+ argv = sys.argv[1:]
496
+ if not argv or argv[0] in ("-h", "--help", "help"):
497
+ print(f"""
498
+ {BOLD}case_law_manager.py{RESET} — Tribunal Case Law Engine
499
+
500
+ {BOLD}Commands:{RESET}
501
+ add-case Record a new rejected pattern
502
+ search-cases --query <text> Find relevant precedents (token-free)
503
+ list [--domain <domain>] List all recorded cases
504
+ show --id <N> Show full diff for a case
505
+ export [--stdout] Export all cases to Markdown
506
+ stats Show breakdown by domain/verdict
507
+
508
+ {BOLD}Domains:{RESET} {', '.join(sorted(VALID_DOMAINS))}
509
+ {BOLD}Verdicts:{RESET} {', '.join(sorted(VALID_VERDICTS))}
510
+ """)
511
+ return
512
+
513
+ cmd = argv[0]
514
+ rest = argv[1:]
515
+
516
+ if cmd not in COMMANDS:
517
+ print(f"{RED}✖ Unknown command: '{cmd}'{RESET}")
518
+ print(f" Valid: {', '.join(COMMANDS)}")
519
+ sys.exit(1)
520
+
521
+ COMMANDS[cmd](rest)
522
+
523
+
524
+ if __name__ == "__main__":
525
+ main()
@@ -0,0 +1,167 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ compress_skills.py - Aggressive token reduction for .agent/skills/**/*.md files
4
+ WHAT IT DOES:
5
+ 1. Strips ## 🏛️ Tribunal Integration sections (in GEMINI.md globally)
6
+ 2. Strips ## ✅ Pre-Flight Self-Audit sections (duplication)
7
+ 3. Strips ## Cross-Workflow Navigation sections
8
+ 4. Strips ## Output Format sections
9
+ 5. Strips inline comment blocks inside code (// Description of obvious stuff)
10
+ 6. Collapses 3+ empty lines → 1
11
+ 7. Removes chatty intro paragraphs (patterns like "X is Y. If you Z, you're Z.")
12
+ """
13
+
14
+ import os, re, sys
15
+
16
+ # ─── Section strippers ──────────────────────────────────────────────────────
17
+
18
+ SECTION_PATTERNS = [
19
+ # Tribunal / pre-flight boilerplate (global in GEMINI.md)
20
+ r'(?m)^## 🏛️ Tribunal Integration[\s\S]*?(?=\n## |\n# |\Z)',
21
+ r'(?m)^## Tribunal Integration[\s\S]*?(?=\n## |\n# |\Z)',
22
+ r'(?m)^### ✅ Pre-Flight Self-Audit[\s\S]*?(?=\n## |\n### |\n# |\Z)',
23
+ r'(?m)^## Pre-Flight Self-Audit[\s\S]*?(?=\n## |\n# |\Z)',
24
+ r'(?m)^## Cross-Workflow Navigation[\s\S]*?(?=\n## |\n# |\Z)',
25
+ r'(?m)^## Output Format\s*\n```[\s\S]*?```\s*\n',
26
+ r'(?m)^## VBC Protocol[\s\S]*?(?=\n## |\n# |\Z)',
27
+ r'(?m)^## LLM Traps[\s\S]*?(?=\n## |\n# |\Z)',
28
+ ]
29
+
30
+ # ─── Chatty intro line patterns ───────────────────────────────────────────
31
+ # e.g. "React 19 is a paradigm shift. Server Components are the default."
32
+ # These are after the frontmatter H1 title — motivational filler.
33
+ CHATTY_INTRO = re.compile(
34
+ r'(?m)^(# .+\n)\n[A-Z][^#\n]{60,}\n[A-Z][^#\n]{40,}\n\n---',
35
+ )
36
+
37
+ def strip_chatty_intro(content):
38
+ def replace(m):
39
+ return m.group(1) + '\n---'
40
+ return CHATTY_INTRO.sub(replace, content)
41
+
42
+ # ─── Trim verbose inline comments in code blocks ─────────────────────────
43
+ # Removes "// comment that just restates the surrounding code name"
44
+ OBVIOUS_COMMENT = re.compile(
45
+ r'(?m)^(\s*)(// (default for most properties|shorthand|number of repeats|default: \d+|spring tension|resistance|weight|approximate duration|deceleration rate)[^\n]*)\n'
46
+ )
47
+
48
+ def strip_obvious_comments(content):
49
+ return OBVIOUS_COMMENT.sub('', content)
50
+
51
+ # ─── Long repetitive ✅/❌ rule blocks that repeat main section ────────────
52
+ # These are usually "Performance Rules" text blocks restating code above
53
+ PERF_TEXT_BLOCK = re.compile(
54
+ r'(?m)^```\n(✅ Use \w[^\n]*\n → [^\n]*\n\n?){3,}```\n'
55
+ )
56
+ def compress_perf_blocks(content):
57
+ def to_bullets(m):
58
+ text = m.group(0)
59
+ lines = text.strip('`\n').splitlines()
60
+ bullets = []
61
+ for line in lines:
62
+ stripped = line.strip()
63
+ if stripped.startswith('✅') or stripped.startswith('❌'):
64
+ bullets.append(f'- {stripped}')
65
+ elif stripped.startswith('→'):
66
+ bullets[-1] += f' ({stripped[1:].strip()})'
67
+ return '\n'.join(bullets) + '\n'
68
+ return PERF_TEXT_BLOCK.sub(to_bullets, content)
69
+
70
+ # ─── Collapse empty lines ────────────────────────────────────────────────
71
+ def collapse_blanks(content):
72
+ return re.sub(r'\n{3,}', '\n\n', content)
73
+
74
+ # ─── Remove "This is X — not Y" filler sentences before the first ## ─────
75
+ FILLER_BEFORE_SECTION = re.compile(
76
+ r'(?m)(^# .+\n\n)([A-Z][^\n]+\n){1,4}(\n---\n)',
77
+ re.MULTILINE
78
+ )
79
+ def remove_filler_between_title_and_hr(content):
80
+ def replacement(m):
81
+ return m.group(1) + m.group(3)
82
+ return FILLER_BEFORE_SECTION.sub(replacement, content)
83
+
84
+ # ─── Strip redundant version comment banners ────────────────────────────
85
+ # e.g. "// motion.div, motion.span, motion.button, motion.svg, motion.path, etc."
86
+ # "// Any HTML or SVG element can be prefixed with `motion.`" - obvious
87
+ REDUNDANT_NOTE = re.compile(r'(?m)^// (motion\.\w+|Any HTML|Note:|Variant names propagate|// )[^\n]*\n')
88
+
89
+ def strip_redundant_notes(content):
90
+ return REDUNDANT_NOTE.sub('', content)
91
+
92
+ # ─── Main pipeline ────────────────────────────────────────────────────────
93
+
94
+ def compress_file(path):
95
+ with open(path, 'r', encoding='utf-8', errors='ignore') as f:
96
+ original = f.read()
97
+
98
+ content = original
99
+
100
+ # 1. Strip global boilerplate sections
101
+ for pattern in SECTION_PATTERNS:
102
+ content = re.sub(pattern, '', content)
103
+
104
+ # 2. Remove chatty intro paragraphs
105
+ content = strip_chatty_intro(content)
106
+
107
+ # 3. Remove filler between title and first ---
108
+ content = remove_filler_between_title_and_hr(content)
109
+
110
+ # 4. Strip obvious inline comments from code
111
+ content = strip_obvious_comments(content)
112
+
113
+ # 5. Strip redundant notes
114
+ content = strip_redundant_notes(content)
115
+
116
+ # 6. Compress verbose perf rule text blocks to bullet lists
117
+ content = compress_perf_blocks(content)
118
+
119
+ # 7. Collapse 3+ blank lines
120
+ content = collapse_blanks(content)
121
+
122
+ # Write back
123
+ with open(path, 'w', encoding='utf-8') as f:
124
+ f.write(content.strip() + '\n')
125
+
126
+ saved = len(original) - len(content)
127
+ return len(original), len(content), saved
128
+
129
+ def main():
130
+ base = '.agent/skills'
131
+ if not os.path.exists(base):
132
+ print(f"ERROR: '{base}' not found. Run from tribunal-kit root.", file=sys.stderr)
133
+ sys.exit(1)
134
+
135
+ total_orig = 0
136
+ total_new = 0
137
+ results = []
138
+
139
+ for root, _, files in os.walk(base):
140
+ for fname in files:
141
+ if fname.endswith('.md'):
142
+ path = os.path.join(root, fname)
143
+ orig, new, saved = compress_file(path)
144
+ total_orig += orig
145
+ total_new += new
146
+ if saved > 0:
147
+ results.append((saved, path))
148
+
149
+ results.sort(reverse=True)
150
+
151
+ print(f"\n{'='*55}")
152
+ print(f" Skill Compression Complete")
153
+ print(f"{'='*55}")
154
+ print(f" Original : {total_orig:,} bytes ({total_orig//1024}KB)")
155
+ print(f" After : {total_new:,} bytes ({total_new//1024}KB)")
156
+ saved_total = total_orig - total_new
157
+ pct = saved_total / total_orig * 100 if total_orig else 0
158
+ print(f" Saved : {saved_total:,} bytes ({saved_total//1024}KB) — {pct:.1f}%")
159
+ print(f"\n Top savings:")
160
+ for saved, path in results[:15]:
161
+ skill = os.path.basename(os.path.dirname(path))
162
+ fname = os.path.basename(path)
163
+ print(f" -{saved//1024:2}KB {skill}/{fname}")
164
+ print()
165
+
166
+ if __name__ == '__main__':
167
+ main()