tribunal-kit 6.0.0 → 7.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 (353) hide show
  1. package/.agent/.manifest.json +78 -0
  2. package/.agent/ARCHITECTURE.md +19 -241
  3. package/.agent/agents/accessibility-reviewer.md +23 -16
  4. package/.agent/agents/ai-code-reviewer.md +25 -21
  5. package/.agent/agents/anti-pattern-reviewer.md +12 -7
  6. package/.agent/agents/api-architect.md +1 -0
  7. package/.agent/agents/backend-specialist.md +23 -22
  8. package/.agent/agents/cloud-engineer.md +1 -0
  9. package/.agent/agents/code-archaeologist.md +1 -0
  10. package/.agent/agents/complexity-reviewer.md +10 -7
  11. package/.agent/agents/database-architect.md +3 -2
  12. package/.agent/agents/db-latency-auditor.md +6 -2
  13. package/.agent/agents/debugger.md +3 -2
  14. package/.agent/agents/dependency-reviewer.md +1 -0
  15. package/.agent/agents/devops-engineer.md +9 -8
  16. package/.agent/agents/documentation-writer.md +6 -5
  17. package/.agent/agents/explorer-agent.md +1 -0
  18. package/.agent/agents/frontend-reviewer.md +21 -17
  19. package/.agent/agents/frontend-specialist.md +23 -20
  20. package/.agent/agents/game-developer.md +1 -0
  21. package/.agent/agents/interaction-reviewer.md +10 -5
  22. package/.agent/agents/logic-reviewer.md +3 -0
  23. package/.agent/agents/minimalist-reviewer.md +6 -0
  24. package/.agent/agents/mobile-developer.md +7 -6
  25. package/.agent/agents/mobile-reviewer.md +14 -13
  26. package/.agent/agents/orchestrator.md +3 -0
  27. package/.agent/agents/penetration-tester.md +1 -0
  28. package/.agent/agents/performance-optimizer.md +6 -5
  29. package/.agent/agents/performance-reviewer.md +6 -6
  30. package/.agent/agents/pipeline-reviewer.md +144 -0
  31. package/.agent/agents/precedence-reviewer.md +1 -0
  32. package/.agent/agents/product-manager.md +1 -0
  33. package/.agent/agents/product-owner.md +1 -0
  34. package/.agent/agents/product-reviewer.md +12 -7
  35. package/.agent/agents/project-planner.md +3 -0
  36. package/.agent/agents/qa-automation-engineer.md +50 -43
  37. package/.agent/agents/resilience-reviewer.md +1 -0
  38. package/.agent/agents/schema-reviewer.md +1 -0
  39. package/.agent/agents/security-auditor.md +24 -24
  40. package/.agent/agents/seo-specialist.md +33 -23
  41. package/.agent/agents/sql-reviewer.md +5 -4
  42. package/.agent/agents/supervisor-agent.md +12 -2
  43. package/.agent/agents/swarm-worker-contracts.md +13 -13
  44. package/.agent/agents/swarm-worker-registry.md +51 -51
  45. package/.agent/agents/system-architect.md +1 -0
  46. package/.agent/agents/test-coverage-reviewer.md +25 -22
  47. package/.agent/agents/test-engineer.md +12 -11
  48. package/.agent/agents/throughput-optimizer.md +45 -40
  49. package/.agent/agents/type-safety-reviewer.md +5 -4
  50. package/.agent/agents/ui-ux-auditor.md +6 -4
  51. package/.agent/agents/ui-visual-auditor.md +3 -0
  52. package/.agent/agents/ux-reviewer.md +11 -6
  53. package/.agent/agents/visual-reviewer.md +11 -6
  54. package/.agent/agents/vitals-reviewer.md +5 -4
  55. package/.agent/history/memory/.memory.idx +3716 -1
  56. package/.agent/history/memory/MEMORY.md +246 -1
  57. package/.agent/mcp_config.json +1 -6
  58. package/.agent/routing_index.json +2034 -263
  59. package/.agent/rules/GEMINI.md +12 -10
  60. package/.agent/rules/GEMINI_PLANNER.md +19 -21
  61. package/.agent/scripts/_colors.js +52 -54
  62. package/.agent/scripts/_utils.js +52 -53
  63. package/.agent/scripts/auto_preview.js +39 -43
  64. package/.agent/scripts/bundle_analyzer.js +47 -60
  65. package/.agent/scripts/case_law_manager.js +200 -294
  66. package/.agent/scripts/checklist.js +73 -94
  67. package/.agent/scripts/cicd_validator.js +305 -0
  68. package/.agent/scripts/compile_router.py +4 -111
  69. package/.agent/scripts/context_broker.js +317 -412
  70. package/.agent/scripts/contract_engine.js +402 -0
  71. package/.agent/scripts/dependency_analyzer.js +112 -124
  72. package/.agent/scripts/graph_builder.js +75 -103
  73. package/.agent/scripts/graph_visualizer.js +10 -12
  74. package/.agent/scripts/graph_zoom.js +38 -55
  75. package/.agent/scripts/guardrail_engine.js +195 -145
  76. package/.agent/scripts/impact_classifier.js +55 -30
  77. package/.agent/scripts/inner_loop_validator.js +108 -159
  78. package/.agent/scripts/integrity_manifest.js +59 -67
  79. package/.agent/scripts/lint_runner.js +65 -75
  80. package/.agent/scripts/marathon_harness.js +131 -208
  81. package/.agent/scripts/migrate_skills_frontmatter.py +4 -63
  82. package/.agent/scripts/minify_context.js +29 -33
  83. package/.agent/scripts/minimal_change_engine.js +95 -87
  84. package/.agent/scripts/mutation_runner.js +46 -62
  85. package/.agent/scripts/pipeline_engine.js +320 -209
  86. package/.agent/scripts/prompt_compiler.js +118 -93
  87. package/.agent/scripts/schema_validator.js +73 -121
  88. package/.agent/scripts/security_scan.js +88 -117
  89. package/.agent/scripts/session_manager.js +52 -69
  90. package/.agent/scripts/signal_detector.js +24 -22
  91. package/.agent/scripts/skill_evolution.js +184 -245
  92. package/.agent/scripts/skill_integrator.js +35 -48
  93. package/.agent/scripts/socratic_gate_policy.js +15 -12
  94. package/.agent/scripts/strengthen_skills.js +29 -33
  95. package/.agent/scripts/swarm_dispatcher.js +120 -151
  96. package/.agent/scripts/test_runner.js +54 -56
  97. package/.agent/scripts/token_budget_broker.js +9 -7
  98. package/.agent/scripts/trace_engine.js +126 -0
  99. package/.agent/scripts/verify_all.js +76 -93
  100. package/.agent/scripts/visual_audit.js +16 -21
  101. package/.agent/skill_topic_map.json +237 -0
  102. package/.agent/skills/12-principles-of-animation/SKILL.md +7 -0
  103. package/.agent/skills/60fps-animation/SKILL.md +7 -0
  104. package/.agent/skills/accessible-animation/SKILL.md +5 -1
  105. package/.agent/skills/adapt/SKILL.md +7 -1
  106. package/.agent/skills/advanced-rag-pipelines/SKILL.md +1 -0
  107. package/.agent/skills/agent-organizer/SKILL.md +1 -0
  108. package/.agent/skills/agentic-patterns/SKILL.md +33 -25
  109. package/.agent/skills/agentic-workflows-2026/SKILL.md +80 -0
  110. package/.agent/skills/ai-app-hardening/SKILL.md +59 -0
  111. package/.agent/skills/ai-prompt-injection-defense/SKILL.md +7 -6
  112. package/.agent/skills/animation-on-scroll/SKILL.md +24 -15
  113. package/.agent/skills/animation-systems/SKILL.md +11 -10
  114. package/.agent/skills/antfu-conventions/SKILL.md +7 -2
  115. package/.agent/skills/api-patterns/SKILL.md +14 -10
  116. package/.agent/skills/api-security-auditor/SKILL.md +15 -14
  117. package/.agent/skills/app-builder/SKILL.md +1 -0
  118. package/.agent/skills/app-builder/templates/nextjs-fullstack/TEMPLATE.md +2 -2
  119. package/.agent/skills/app-builder/templates/nextjs-saas/TEMPLATE.md +1 -1
  120. package/.agent/skills/app-builder/templates/nextjs-static/TEMPLATE.md +12 -12
  121. package/.agent/skills/app-builder/templates/nuxt-app/TEMPLATE.md +3 -3
  122. package/.agent/skills/apple-design/SKILL.md +8 -7
  123. package/.agent/skills/architecture/SKILL.md +3 -2
  124. package/.agent/skills/audit-and-fix/SKILL.md +8 -5
  125. package/.agent/skills/authentication-best-practices/SKILL.md +9 -8
  126. package/.agent/skills/backend-security-expert/SKILL.md +1 -0
  127. package/.agent/skills/baseline-ui/SKILL.md +8 -7
  128. package/.agent/skills/bash-linux/SKILL.md +1 -0
  129. package/.agent/skills/behavioral-modes/SKILL.md +1 -0
  130. package/.agent/skills/better-colors/SKILL.md +11 -7
  131. package/.agent/skills/better-typography/SKILL.md +30 -16
  132. package/.agent/skills/better-ui/SKILL.md +17 -10
  133. package/.agent/skills/bolder/SKILL.md +6 -0
  134. package/.agent/skills/brainstorming/SKILL.md +6 -4
  135. package/.agent/skills/browser-native-ai/SKILL.md +5 -4
  136. package/.agent/skills/build-primitive/SKILL.md +5 -0
  137. package/.agent/skills/building-native-ui/SKILL.md +1 -0
  138. package/.agent/skills/cicd-pro/SKILL.md +2 -1
  139. package/.agent/skills/clarify/SKILL.md +19 -14
  140. package/.agent/skills/clean-code/SKILL.md +16 -15
  141. package/.agent/skills/cloud-architect/SKILL.md +1 -0
  142. package/.agent/skills/cobejs/SKILL.md +7 -9
  143. package/.agent/skills/code-review-checklist/SKILL.md +1 -0
  144. package/.agent/skills/codebase-design/SKILL.md +7 -2
  145. package/.agent/skills/colorize/SKILL.md +8 -3
  146. package/.agent/skills/compact-landing/SKILL.md +8 -3
  147. package/.agent/skills/company-logos/SKILL.md +6 -0
  148. package/.agent/skills/config-validator/SKILL.md +10 -9
  149. package/.agent/skills/containerization-pro/SKILL.md +6 -5
  150. package/.agent/skills/context-engineering-pro/SKILL.md +70 -0
  151. package/.agent/skills/create-design-md/SKILL.md +13 -5
  152. package/.agent/skills/critique/SKILL.md +10 -8
  153. package/.agent/skills/csharp-developer/SKILL.md +1 -0
  154. package/.agent/skills/data-validation-schemas/SKILL.md +28 -23
  155. package/.agent/skills/database-design/SKILL.md +2 -1
  156. package/.agent/skills/delight/SKILL.md +6 -0
  157. package/.agent/skills/deployment-procedures/SKILL.md +2 -1
  158. package/.agent/skills/design-lab/SKILL.md +5 -0
  159. package/.agent/skills/devops-engineer/SKILL.md +3 -2
  160. package/.agent/skills/devops-incident-responder/SKILL.md +2 -1
  161. package/.agent/skills/diagnosing-bugs/SKILL.md +12 -2
  162. package/.agent/skills/distill/SKILL.md +7 -2
  163. package/.agent/skills/documentation-templates/SKILL.md +3 -2
  164. package/.agent/skills/domain-modeling/SKILL.md +12 -4
  165. package/.agent/skills/duckdb-analytical-sql/SKILL.md +59 -0
  166. package/.agent/skills/edge-ai-mobile/SKILL.md +50 -0
  167. package/.agent/skills/edge-computing/SKILL.md +9 -8
  168. package/.agent/skills/emil-design-eng/SKILL.md +43 -27
  169. package/.agent/skills/error-resilience/SKILL.md +34 -16
  170. package/.agent/skills/expo-router-v4/SKILL.md +81 -0
  171. package/.agent/skills/extract-design-system/SKILL.md +12 -11
  172. package/.agent/skills/fabel-protocol/SKILL.md +24 -17
  173. package/.agent/skills/fixing-accessibility/SKILL.md +23 -16
  174. package/.agent/skills/fixing-metadata/SKILL.md +24 -13
  175. package/.agent/skills/fixing-motion-performance/SKILL.md +7 -5
  176. package/.agent/skills/framer-motion-expert/SKILL.md +29 -15
  177. package/.agent/skills/frontend-design/SKILL.md +21 -13
  178. package/.agent/skills/frontend-security-expert/SKILL.md +1 -0
  179. package/.agent/skills/game-design-expert/SKILL.md +1 -0
  180. package/.agent/skills/game-engineering-expert/SKILL.md +1 -0
  181. package/.agent/skills/generative-ui-expert/SKILL.md +3 -2
  182. package/.agent/skills/geo-fundamentals/SKILL.md +2 -1
  183. package/.agent/skills/git-pro/SKILL.md +10 -9
  184. package/.agent/skills/github-operations/SKILL.md +2 -1
  185. package/.agent/skills/gpt-taste/SKILL.md +7 -0
  186. package/.agent/skills/gsap-core/SKILL.md +26 -22
  187. package/.agent/skills/gsap-frameworks/SKILL.md +6 -5
  188. package/.agent/skills/gsap-performance/SKILL.md +4 -3
  189. package/.agent/skills/gsap-plugins/SKILL.md +34 -33
  190. package/.agent/skills/gsap-react/SKILL.md +9 -8
  191. package/.agent/skills/gsap-scrolltrigger/SKILL.md +34 -33
  192. package/.agent/skills/gsap-timeline/SKILL.md +18 -15
  193. package/.agent/skills/gsap-utils/SKILL.md +25 -24
  194. package/.agent/skills/harden/SKILL.md +9 -3
  195. package/.agent/skills/harness-protocol/SKILL.md +1 -0
  196. package/.agent/skills/i18n-localization/SKILL.md +10 -9
  197. package/.agent/skills/impeccable/SKILL.md +5 -0
  198. package/.agent/skills/improve-codebase-architecture/SKILL.md +10 -5
  199. package/.agent/skills/improve-ui/SKILL.md +12 -5
  200. package/.agent/skills/intelligent-routing/SKILL.md +7 -6
  201. package/.agent/skills/knowledge-graph/SKILL.md +1 -0
  202. package/.agent/skills/landing-page/SKILL.md +1 -0
  203. package/.agent/skills/lint-and-validate/SKILL.md +9 -4
  204. package/.agent/skills/llm-engineering/SKILL.md +55 -54
  205. package/.agent/skills/local-first/SKILL.md +20 -19
  206. package/.agent/skills/local-first-architecture/SKILL.md +6 -1
  207. package/.agent/skills/lottie-animation/SKILL.md +7 -3
  208. package/.agent/skills/marquee-loop/SKILL.md +1 -0
  209. package/.agent/skills/masked-reveal/SKILL.md +17 -4
  210. package/.agent/skills/mcp-builder/SKILL.md +13 -10
  211. package/.agent/skills/micro-interaction/SKILL.md +20 -5
  212. package/.agent/skills/mobile-design/SKILL.md +26 -15
  213. package/.agent/skills/monorepo-management/SKILL.md +3 -2
  214. package/.agent/skills/morphing-icons/SKILL.md +16 -6
  215. package/.agent/skills/motion-engineering/SKILL.md +19 -15
  216. package/.agent/skills/nextjs-react-expert/SKILL.md +26 -21
  217. package/.agent/skills/nodejs-best-practices/SKILL.md +89 -76
  218. package/.agent/skills/observability/SKILL.md +45 -44
  219. package/.agent/skills/opentelemetry-observability/SKILL.md +59 -0
  220. package/.agent/skills/page-transition-animation/SKILL.md +6 -3
  221. package/.agent/skills/parallel-agents/SKILL.md +7 -2
  222. package/.agent/skills/performance-profiling/SKILL.md +24 -13
  223. package/.agent/skills/plan-writing/SKILL.md +1 -0
  224. package/.agent/skills/platform-engineer/SKILL.md +3 -2
  225. package/.agent/skills/platform-engineering-opentofu/SKILL.md +68 -0
  226. package/.agent/skills/playwright-ai-e2e/SKILL.md +60 -0
  227. package/.agent/skills/playwright-best-practices/SKILL.md +29 -28
  228. package/.agent/skills/polish/SKILL.md +13 -12
  229. package/.agent/skills/powershell-windows/SKILL.md +1 -0
  230. package/.agent/skills/pricing-page/SKILL.md +8 -3
  231. package/.agent/skills/product-aware-heuristics/SKILL.md +122 -72
  232. package/.agent/skills/progressive-blur/SKILL.md +9 -4
  233. package/.agent/skills/project-idioms/SKILL.md +1 -0
  234. package/.agent/skills/property-based-testing/SKILL.md +58 -0
  235. package/.agent/skills/python-patterns/SKILL.md +1 -0
  236. package/.agent/skills/python-pro/SKILL.md +1 -0
  237. package/.agent/skills/quieter/SKILL.md +6 -0
  238. package/.agent/skills/react-doctor/SKILL.md +6 -0
  239. package/.agent/skills/react-specialist/SKILL.md +32 -28
  240. package/.agent/skills/readme-builder/SKILL.md +1 -0
  241. package/.agent/skills/realtime-patterns/SKILL.md +34 -33
  242. package/.agent/skills/red-team-tactics/SKILL.md +1 -0
  243. package/.agent/skills/redesign-skill/SKILL.md +6 -0
  244. package/.agent/skills/review-animations/SKILL.md +5 -2
  245. package/.agent/skills/review-animations/STANDARDS.md +37 -23
  246. package/.agent/skills/rust-pro/SKILL.md +1 -0
  247. package/.agent/skills/seo-fundamentals/SKILL.md +4 -3
  248. package/.agent/skills/server-management/SKILL.md +1 -0
  249. package/.agent/skills/shadcn-ui-expert/SKILL.md +28 -22
  250. package/.agent/skills/shape/SKILL.md +9 -2
  251. package/.agent/skills/skill-creator/SKILL.md +1 -0
  252. package/.agent/skills/soft-skill/SKILL.md +6 -0
  253. package/.agent/skills/sounds-on-the-web/SKILL.md +1 -0
  254. package/.agent/skills/sql-pro/SKILL.md +1 -0
  255. package/.agent/skills/supabase-postgres-best-practices/SKILL.md +1 -0
  256. package/.agent/skills/svg-animation/SKILL.md +16 -7
  257. package/.agent/skills/swiftui-expert/SKILL.md +1 -0
  258. package/.agent/skills/swiss-design/SKILL.md +5 -0
  259. package/.agent/skills/system-design-pro/SKILL.md +1 -0
  260. package/.agent/skills/systematic-debugging/SKILL.md +1 -0
  261. package/.agent/skills/tailwind-patterns/SKILL.md +25 -11
  262. package/.agent/skills/taste-skill/SKILL.md +6 -0
  263. package/.agent/skills/tdd-workflow/SKILL.md +5 -0
  264. package/.agent/skills/test-result-analyzer/SKILL.md +1 -0
  265. package/.agent/skills/testing-patterns/SKILL.md +123 -115
  266. package/.agent/skills/thermo-nuclear-code-quality-review/SKILL.md +5 -0
  267. package/.agent/skills/thinking-protocol/SKILL.md +3 -0
  268. package/.agent/skills/to-spring-or-not-to-spring/SKILL.md +9 -8
  269. package/.agent/skills/transitions-dev/SKILL.md +19 -6
  270. package/.agent/skills/trend-researcher/SKILL.md +1 -0
  271. package/.agent/skills/typescript-advanced/SKILL.md +23 -18
  272. package/.agent/skills/typeset/SKILL.md +10 -1
  273. package/.agent/skills/ui-reasoning-engine/SKILL.md +67 -48
  274. package/.agent/skills/ui-skill-packs/SKILL.md +6 -1
  275. package/.agent/skills/ui-skills-root/SKILL.md +39 -38
  276. package/.agent/skills/ui-ux-pro-max/SKILL.md +34 -26
  277. package/.agent/skills/ui-ux-researcher/SKILL.md +1 -0
  278. package/.agent/skills/vector-search-pgvector/SKILL.md +78 -0
  279. package/.agent/skills/vue-expert/SKILL.md +22 -19
  280. package/.agent/skills/vulnerability-scanner/SKILL.md +22 -21
  281. package/.agent/skills/web-accessibility-auditor/SKILL.md +2 -1
  282. package/.agent/skills/web-design-guidelines/SKILL.md +16 -10
  283. package/.agent/skills/web-quality-audit/SKILL.md +5 -0
  284. package/.agent/skills/webapp-testing/SKILL.md +14 -13
  285. package/.agent/skills/webgpu-performance/SKILL.md +1 -0
  286. package/.agent/skills/whimsy-injector/SKILL.md +9 -5
  287. package/.agent/skills/workflow-optimizer/SKILL.md +1 -0
  288. package/.agent/skills/zero-trust-passkeys/SKILL.md +90 -0
  289. package/.agent/templates/DESIGN.md +119 -110
  290. package/.agent/templates/design-tokens.json +9 -6
  291. package/.agent/workflows/acf.md +1 -0
  292. package/.agent/workflows/api-tester.md +1 -0
  293. package/.agent/workflows/audit.md +1 -0
  294. package/.agent/workflows/brainstorm.md +1 -0
  295. package/.agent/workflows/changelog.md +1 -0
  296. package/.agent/workflows/contract.md +60 -0
  297. package/.agent/workflows/create.md +1 -0
  298. package/.agent/workflows/debug.md +1 -0
  299. package/.agent/workflows/deploy.md +1 -0
  300. package/.agent/workflows/enhance.md +1 -0
  301. package/.agent/workflows/fix-ci.md +89 -0
  302. package/.agent/workflows/fix.md +1 -0
  303. package/.agent/workflows/generate.md +1 -0
  304. package/.agent/workflows/marathon.md +1 -0
  305. package/.agent/workflows/migrate.md +1 -0
  306. package/.agent/workflows/minimal.md +9 -7
  307. package/.agent/workflows/orchestrate.md +1 -0
  308. package/.agent/workflows/performance-benchmarker.md +1 -0
  309. package/.agent/workflows/pipeline.md +26 -21
  310. package/.agent/workflows/plan.md +1 -0
  311. package/.agent/workflows/preview.md +1 -0
  312. package/.agent/workflows/refactor.md +3 -2
  313. package/.agent/workflows/review-ai.md +4 -3
  314. package/.agent/workflows/review.md +1 -0
  315. package/.agent/workflows/session.md +1 -0
  316. package/.agent/workflows/status.md +1 -0
  317. package/.agent/workflows/strengthen-skills.md +1 -0
  318. package/.agent/workflows/super-prompt.md +1 -0
  319. package/.agent/workflows/swarm.md +15 -3
  320. package/.agent/workflows/test.md +8 -7
  321. package/.agent/workflows/tribunal-backend.md +3 -2
  322. package/.agent/workflows/tribunal-cicd.md +104 -0
  323. package/.agent/workflows/tribunal-database.md +1 -0
  324. package/.agent/workflows/tribunal-frontend.md +8 -6
  325. package/.agent/workflows/tribunal-full.md +15 -14
  326. package/.agent/workflows/tribunal-mobile.md +6 -5
  327. package/.agent/workflows/tribunal-performance.md +1 -0
  328. package/.agent/workflows/tribunal-speed.md +1 -0
  329. package/.agent/workflows/tribunal-ui.md +29 -21
  330. package/.agent/workflows/ui-ux-pro-max.md +19 -15
  331. package/CONTRIBUTING.md +3 -3
  332. package/README.md +160 -88
  333. package/SECURITY.md +11 -11
  334. package/bin/mcp-server.js +512 -233
  335. package/bin/tribunal-kit.js +29 -30
  336. package/bin/wrapper.js +45 -48
  337. package/dist/cli.js +26 -0
  338. package/dist/commands/case.js +2 -0
  339. package/dist/commands/contract.js +440 -0
  340. package/dist/commands/memory.js +3 -2
  341. package/dist/commands/native.js +36 -0
  342. package/dist/commands/status.js +6 -0
  343. package/dist/esm/index.mjs +32 -0
  344. package/dist/index.d.ts +58 -3
  345. package/package.json +20 -16
  346. package/scripts/benchmark.js +62 -86
  347. package/scripts/changelog.js +67 -73
  348. package/scripts/fix-vbc.js +13 -15
  349. package/scripts/fix-vbc.ps1 +2 -118
  350. package/scripts/stress_benchmark.js +358 -0
  351. package/scripts/sync-version.js +71 -41
  352. package/scripts/validate-payload.js +23 -30
  353. package/scripts/visual_audit.js +23 -13
@@ -18,28 +18,27 @@
18
18
  * .agent/history/case-law/cases/ ← one JSON file per case
19
19
  */
20
20
 
21
- "use strict";
21
+ 'use strict';
22
22
 
23
- const fs = require("fs");
24
- const path = require("path");
25
- const crypto = require("crypto");
26
- const readline = require("readline");
23
+ const fs = require('fs');
24
+ const path = require('path');
25
+ const crypto = require('crypto');
26
+ const readline = require('readline');
27
27
 
28
28
  // ── Colours ──────────────────────────────────────────────────────────────────
29
- const { GREEN, YELLOW, CYAN, RED, BOLD, DIM, RESET } = require("./_colors");
29
+ const { GREEN, YELLOW, CYAN, RED, BOLD, DIM, RESET } = require('./_colors');
30
30
 
31
31
  // ── Shared Utilities ──────────────────────────────────────────────────────────
32
- const { findAgentDir } = require("./_utils");
33
-
32
+ const { findAgentDir } = require('./_utils');
34
33
 
35
34
  // ── Lazy path resolution (avoids side effects at require-time) ───────────────
36
35
  let _paths = null;
37
36
  function getPaths() {
38
37
  if (_paths) return _paths;
39
38
  const agentDir = findAgentDir();
40
- const historyDir = path.join(agentDir, "history", "case-law");
41
- const casesDir = path.join(historyDir, "cases");
42
- const indexFile = path.join(historyDir, "index.json");
39
+ const historyDir = path.join(agentDir, 'history', 'case-law');
40
+ const casesDir = path.join(historyDir, 'cases');
41
+ const indexFile = path.join(historyDir, 'index.json');
43
42
  _paths = {
44
43
  AGENT_DIR: agentDir,
45
44
  HISTORY_DIR: historyDir,
@@ -50,23 +49,23 @@ function getPaths() {
50
49
  }
51
50
 
52
51
  const VALID_DOMAINS = new Set([
53
- "backend",
54
- "frontend",
55
- "database",
56
- "security",
57
- "performance",
58
- "mobile",
59
- "testing",
60
- "devops",
61
- "minimal_change",
62
- "general",
52
+ 'backend',
53
+ 'frontend',
54
+ 'database',
55
+ 'security',
56
+ 'performance',
57
+ 'mobile',
58
+ 'testing',
59
+ 'devops',
60
+ 'minimal_change',
61
+ 'general',
63
62
  ]);
64
63
  const VALID_VERDICTS = new Set([
65
- "REJECTED",
66
- "APPROVED_WITH_CONDITIONS",
67
- "PRECEDENT_SET",
68
- "OVERRULED",
69
- "MINIMALITY_REJECTED",
64
+ 'REJECTED',
65
+ 'APPROVED_WITH_CONDITIONS',
66
+ 'PRECEDENT_SET',
67
+ 'OVERRULED',
68
+ 'MINIMALITY_REJECTED',
70
69
  ]);
71
70
 
72
71
  // ── Noise filter ────────────────────────────────────────────────────────────
@@ -84,7 +83,7 @@ const NOISE_PATTERNS = [
84
83
 
85
84
  function isNoiseRejection(reason) {
86
85
  const lower = reason.toLowerCase();
87
- return NOISE_PATTERNS.some((p) => p.test(lower));
86
+ return NOISE_PATTERNS.some(p => p.test(lower));
88
87
  }
89
88
 
90
89
  // ── Trivial-change filter (Semantic Delta) ────────────────────────────────────
@@ -97,36 +96,32 @@ const TRIVIAL_PATTERNS = [
97
96
  ];
98
97
 
99
98
  function isTrivialLine(line) {
100
- return TRIVIAL_PATTERNS.some((p) => p.test(line));
99
+ return TRIVIAL_PATTERNS.some(p => p.test(line));
101
100
  }
102
101
 
103
102
  function semanticDelta(diffText) {
104
- const lines = diffText.split("\n");
103
+ const lines = diffText.split('\n');
105
104
  const meaningful = [];
106
105
  for (const line of lines) {
107
- if (
108
- line.startsWith("+++") ||
109
- line.startsWith("---") ||
110
- line.startsWith("@@")
111
- ) {
106
+ if (line.startsWith('+++') || line.startsWith('---') || line.startsWith('@@')) {
112
107
  meaningful.push(line);
113
108
  continue;
114
109
  }
115
- if (line.startsWith("+") || line.startsWith("-")) {
110
+ if (line.startsWith('+') || line.startsWith('-')) {
116
111
  const codePart = line.slice(1);
117
112
  if (!isTrivialLine(codePart)) meaningful.push(line);
118
113
  } else {
119
114
  meaningful.push(line);
120
115
  }
121
116
  }
122
- let filtered = meaningful.join("\n");
123
- filtered = filtered.replace(/(\n[ ]?\n){3,}/g, "\n\n");
117
+ let filtered = meaningful.join('\n');
118
+ filtered = filtered.replace(/(\n[ ]?\n){3,}/g, '\n\n');
124
119
  return filtered.trim();
125
120
  }
126
121
 
127
122
  function contentHash(text) {
128
123
  const cleaned = semanticDelta(text);
129
- return crypto.createHash("sha256").update(cleaned).digest("hex").slice(0, 8);
124
+ return crypto.createHash('sha256').update(cleaned).digest('hex').slice(0, 8);
130
125
  }
131
126
 
132
127
  // ── Index helpers ─────────────────────────────────────────────────────────────
@@ -141,31 +136,28 @@ function loadIndex() {
141
136
  const { INDEX_FILE } = getPaths();
142
137
  if (fs.existsSync(INDEX_FILE)) {
143
138
  try {
144
- return JSON.parse(fs.readFileSync(INDEX_FILE, "utf8"));
139
+ return JSON.parse(fs.readFileSync(INDEX_FILE, 'utf8'));
145
140
  } catch {
146
141
  /* fallthrough */
147
142
  }
148
143
  }
149
- return { version: "1.0", cases: [], next_id: 1 };
144
+ return { version: '1.0', cases: [], next_id: 1 };
150
145
  }
151
146
 
152
147
  function saveIndex(index) {
153
148
  ensureDirs();
154
149
  const { INDEX_FILE } = getPaths();
155
- const tmp = INDEX_FILE + ".tmp";
156
- fs.writeFileSync(tmp, JSON.stringify(index, null, 2), "utf8");
150
+ const tmp = INDEX_FILE + '.tmp';
151
+ fs.writeFileSync(tmp, JSON.stringify(index, null, 2), 'utf8');
157
152
  fs.renameSync(tmp, INDEX_FILE);
158
153
  }
159
154
 
160
155
  function loadCase(caseId) {
161
156
  const { CASES_DIR } = getPaths();
162
- const p = path.join(
163
- CASES_DIR,
164
- `case-${String(caseId).padStart(4, "0")}.json`,
165
- );
157
+ const p = path.join(CASES_DIR, `case-${String(caseId).padStart(4, '0')}.json`);
166
158
  if (!fs.existsSync(p)) return null;
167
159
  try {
168
- return JSON.parse(fs.readFileSync(p, "utf8"));
160
+ return JSON.parse(fs.readFileSync(p, 'utf8'));
169
161
  } catch {
170
162
  return null;
171
163
  }
@@ -173,61 +165,58 @@ function loadCase(caseId) {
173
165
 
174
166
  function saveCase(caseRecord) {
175
167
  const { CASES_DIR } = getPaths();
176
- const p = path.join(
177
- CASES_DIR,
178
- `case-${String(caseRecord.id).padStart(4, "0")}.json`,
179
- );
180
- fs.writeFileSync(p, JSON.stringify(caseRecord, null, 2), "utf8");
168
+ const p = path.join(CASES_DIR, `case-${String(caseRecord.id).padStart(4, '0')}.json`);
169
+ fs.writeFileSync(p, JSON.stringify(caseRecord, null, 2), 'utf8');
181
170
  }
182
171
 
183
172
  // ── Keyword/tag extraction ─────────────────────────────────────────────────────
184
173
  function extractTags(text) {
185
174
  const tokens = text.match(/\b[a-zA-Z_][a-zA-Z0-9_]{2,}\b/g) || [];
186
175
  const stopWords = new Set([
187
- "the",
188
- "and",
189
- "for",
190
- "was",
191
- "this",
192
- "with",
193
- "that",
194
- "has",
195
- "a",
196
- "an",
197
- "from",
198
- "are",
199
- "not",
200
- "use",
201
- "but",
202
- "also",
203
- "code",
204
- "have",
205
- "will",
206
- "should",
207
- "must",
208
- "can",
209
- "may",
210
- "any",
211
- "all",
212
- "new",
213
- "old",
214
- "add",
215
- "get",
216
- "set",
217
- "var",
218
- "let",
219
- "const",
220
- "function",
221
- "return",
222
- "import",
223
- "export",
224
- "class",
225
- "async",
226
- "await",
227
- "true",
228
- "false",
229
- "null",
230
- "undefined",
176
+ 'the',
177
+ 'and',
178
+ 'for',
179
+ 'was',
180
+ 'this',
181
+ 'with',
182
+ 'that',
183
+ 'has',
184
+ 'a',
185
+ 'an',
186
+ 'from',
187
+ 'are',
188
+ 'not',
189
+ 'use',
190
+ 'but',
191
+ 'also',
192
+ 'code',
193
+ 'have',
194
+ 'will',
195
+ 'should',
196
+ 'must',
197
+ 'can',
198
+ 'may',
199
+ 'any',
200
+ 'all',
201
+ 'new',
202
+ 'old',
203
+ 'add',
204
+ 'get',
205
+ 'set',
206
+ 'var',
207
+ 'let',
208
+ 'const',
209
+ 'function',
210
+ 'return',
211
+ 'import',
212
+ 'export',
213
+ 'class',
214
+ 'async',
215
+ 'await',
216
+ 'true',
217
+ 'false',
218
+ 'null',
219
+ 'undefined',
231
220
  ]);
232
221
  const seen = new Set();
233
222
  const tags = [];
@@ -259,10 +248,8 @@ function extractTags(text) {
259
248
  function parseVersionFilter(filterStr) {
260
249
  const result = {};
261
250
  if (!filterStr) return result;
262
- for (const segment of filterStr.split(",")) {
263
- const m = segment
264
- .trim()
265
- .match(/^([a-zA-Z0-9_.-]+)\s*(?:>=|>|<=|<|=)?\s*(\d+(?:\.\d+)?)/);
251
+ for (const segment of filterStr.split(',')) {
252
+ const m = segment.trim().match(/^([a-zA-Z0-9_.-]+)\s*(?:>=|>|<=|<|=)?\s*(\d+(?:\.\d+)?)/);
266
253
  if (m) result[m[1].toLowerCase()] = parseFloat(m[2]);
267
254
  }
268
255
  return result;
@@ -288,10 +275,8 @@ function caseMatchesVersionFilter(caseEntry, versionFilter) {
288
275
 
289
276
  // Parse the case's own version constraint
290
277
  const caseConstraints = [];
291
- for (const segment of caseEntry.stack_version.split(",")) {
292
- const m = segment
293
- .trim()
294
- .match(/^([a-zA-Z0-9_.-]+)\s*(>=|>|<=|<|=)\s*(\d+(?:\.\d+)?)/);
278
+ for (const segment of caseEntry.stack_version.split(',')) {
279
+ const m = segment.trim().match(/^([a-zA-Z0-9_.-]+)\s*(>=|>|<=|<|=)\s*(\d+(?:\.\d+)?)/);
295
280
  if (m)
296
281
  caseConstraints.push({
297
282
  lib: m[1].toLowerCase(),
@@ -306,13 +291,13 @@ function caseMatchesVersionFilter(caseEntry, versionFilter) {
306
291
  if (projectVer === undefined) continue; // Unknown lib → don't filter on it
307
292
 
308
293
  const satisfied =
309
- op === ">="
294
+ op === '>='
310
295
  ? projectVer >= ver
311
- : op === ">"
296
+ : op === '>'
312
297
  ? projectVer > ver
313
- : op === "<="
298
+ : op === '<='
314
299
  ? projectVer <= ver
315
- : op === "<"
300
+ : op === '<'
316
301
  ? projectVer < ver
317
302
  : /* = */ projectVer === ver;
318
303
  if (!satisfied) return false; // This case's version context doesn't match
@@ -369,104 +354,74 @@ function createRl() {
369
354
  }
370
355
 
371
356
  function ask(rl, prompt) {
372
- return new Promise((resolve) =>
373
- rl.question(` ${BOLD}${prompt}${RESET} `, resolve),
374
- );
357
+ return new Promise(resolve => rl.question(` ${BOLD}${prompt}${RESET} `, resolve));
375
358
  }
376
359
 
377
360
  function askMultiline(rl, prompt, sentinel) {
378
- return new Promise((resolve) => {
361
+ return new Promise(resolve => {
379
362
  console.log(` ${BOLD}${prompt}${RESET}`);
380
363
  console.log(
381
364
  ` ${DIM}(Type or paste content. Type '${sentinel}' on its own line when done.)${RESET}`,
382
365
  );
383
366
  const lines = [];
384
- const listener = (line) => {
367
+ const listener = line => {
385
368
  if (line.trim() === sentinel) {
386
- rl.removeListener("line", listener);
387
- resolve(lines.join("\n"));
369
+ rl.removeListener('line', listener);
370
+ resolve(lines.join('\n'));
388
371
  } else {
389
372
  lines.push(line);
390
373
  }
391
374
  };
392
- rl.on("line", listener);
375
+ rl.on('line', listener);
393
376
  });
394
377
  }
395
378
 
396
379
  function askChoice(rl, label, choices, defaultVal) {
397
- return new Promise((resolve) => {
398
- const opts = choices
399
- .map((c) => (c === defaultVal ? `${BOLD}${c}${RESET}` : c))
400
- .join(" / ");
401
- rl.question(
402
- ` ${BOLD}${label}${RESET} [${opts}] (default: ${defaultVal}): `,
403
- (answer) => {
404
- const val = (answer || "").trim().toLowerCase();
405
- resolve(val && choices.includes(val) ? val : defaultVal);
406
- },
407
- );
380
+ return new Promise(resolve => {
381
+ const opts = choices.map(c => (c === defaultVal ? `${BOLD}${c}${RESET}` : c)).join(' / ');
382
+ rl.question(` ${BOLD}${label}${RESET} [${opts}] (default: ${defaultVal}): `, answer => {
383
+ const val = (answer || '').trim().toLowerCase();
384
+ resolve(val && choices.includes(val) ? val : defaultVal);
385
+ });
408
386
  });
409
387
  }
410
388
 
411
389
  // ── Commands ──────────────────────────────────────────────────────────────────
412
390
  async function cmdAddCase() {
413
- console.log(
414
- `\n${BOLD}${CYAN}━━━ Recording New Case ━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}`,
415
- );
391
+ console.log(`\n${BOLD}${CYAN}━━━ Recording New Case ━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}`);
416
392
  const rl = createRl();
417
393
 
418
- const diffText = await askMultiline(
419
- rl,
420
- "Paste the REJECTED diff (code snippet):",
421
- "END_DIFF",
422
- );
394
+ const diffText = await askMultiline(rl, 'Paste the REJECTED diff (code snippet):', 'END_DIFF');
423
395
  if (!diffText.trim()) {
424
396
  console.log(`${RED}✖ Diff cannot be empty. Aborting.${RESET}`);
425
397
  rl.close();
426
398
  process.exit(1);
427
399
  }
428
400
 
429
- const reason = await ask(rl, "Rejection reason (1-2 sentences):");
401
+ const reason = await ask(rl, 'Rejection reason (1-2 sentences):');
430
402
  if (!reason.trim()) {
431
403
  console.log(`${RED}✖ Reason cannot be empty. Aborting.${RESET}`);
432
404
  rl.close();
433
405
  process.exit(1);
434
406
  }
435
407
 
436
- const domain = await askChoice(
437
- rl,
438
- "Domain",
439
- [...VALID_DOMAINS].sort(),
440
- "general",
441
- );
442
- const verdict = await askChoice(
443
- rl,
444
- "Verdict",
445
- [...VALID_VERDICTS].sort(),
446
- "REJECTED",
447
- );
448
- const prRef =
449
- (await ask(rl, "PR / commit reference (optional, e.g. PR-404):")).trim() ||
450
- null;
408
+ const domain = await askChoice(rl, 'Domain', [...VALID_DOMAINS].sort(), 'general');
409
+ const verdict = await askChoice(rl, 'Verdict', [...VALID_VERDICTS].sort(), 'REJECTED');
410
+ const prRef = (await ask(rl, 'PR / commit reference (optional, e.g. PR-404):')).trim() || null;
451
411
  const reviewer =
452
- (
453
- await ask(rl, "Reviewer agent (optional, e.g. security-auditor):")
454
- ).trim() || null;
412
+ (await ask(rl, 'Reviewer agent (optional, e.g. security-auditor):')).trim() || null;
455
413
 
456
414
  // FIX: stack_version — prevents stale cases from blocking valid code after
457
415
  // framework upgrades. Format: "react>=19, node>=20" or leave blank for all.
458
416
  const stackVersionRaw = (
459
- await ask(
460
- rl,
461
- "Stack version constraint (optional, e.g. react>=18, node>=20):",
462
- )
417
+ await ask(rl, 'Stack version constraint (optional, e.g. react>=18, node>=20):')
463
418
  ).trim();
464
419
  const stackVersion = stackVersionRaw || null;
465
420
  rl.close();
466
421
 
467
422
  const delta = semanticDelta(diffText);
468
423
  const fingerprint = contentHash(diffText);
469
- const tags = extractTags(diffText + " " + reason);
424
+ const tags = extractTags(diffText + ' ' + reason);
470
425
 
471
426
  const index = loadIndex();
472
427
  const caseId = index.next_id;
@@ -501,26 +456,23 @@ async function cmdAddCase() {
501
456
  index.next_id = caseId + 1;
502
457
  saveIndex(index);
503
458
 
504
- console.log(
505
- `\n${GREEN}✔ Case #${String(caseId).padStart(4, "0")} recorded${RESET}`,
506
- );
459
+ console.log(`\n${GREEN}✔ Case #${String(caseId).padStart(4, '0')} recorded${RESET}`);
507
460
  console.log(` ${DIM}Fingerprint : ${fingerprint}${RESET}`);
508
461
  console.log(` ${DIM}Domain : ${domain}${RESET}`);
509
- console.log(` ${DIM}Tags : ${tags.slice(0, 8).join(", ")}${RESET}`);
510
- if (stackVersion)
511
- console.log(` ${DIM}Stack version : ${stackVersion}${RESET}`);
462
+ console.log(` ${DIM}Tags : ${tags.slice(0, 8).join(', ')}${RESET}`);
463
+ if (stackVersion) console.log(` ${DIM}Stack version : ${stackVersion}${RESET}`);
512
464
  console.log();
513
465
  }
514
466
 
515
467
  function cmdSearchCases(args) {
516
- let query = args.filter((a) => !a.startsWith("--")).join(" ");
468
+ let query = args.filter(a => !a.startsWith('--')).join(' ');
517
469
  if (!query) {
518
- const qi = process.argv.indexOf("--query");
470
+ const qi = process.argv.indexOf('--query');
519
471
  if (qi !== -1)
520
472
  query = process.argv
521
473
  .slice(qi + 1)
522
- .filter((a) => !a.startsWith("--"))
523
- .join(" ");
474
+ .filter(a => !a.startsWith('--'))
475
+ .join(' ');
524
476
  }
525
477
  if (!query) {
526
478
  console.log(
@@ -532,11 +484,9 @@ function cmdSearchCases(args) {
532
484
  // FIX: --version-filter skips cases whose stack_version constraint doesn't
533
485
  // match the specified versions. Prevents stale React 17/18 cases blocking React 19 code.
534
486
  // Usage: search-cases "useEffect" --version-filter "react=19,node=22"
535
- const vfIdx = args.indexOf("--version-filter");
487
+ const vfIdx = args.indexOf('--version-filter');
536
488
  const versionFilter =
537
- vfIdx !== -1 && args[vfIdx + 1]
538
- ? parseVersionFilter(args[vfIdx + 1])
539
- : null;
489
+ vfIdx !== -1 && args[vfIdx + 1] ? parseVersionFilter(args[vfIdx + 1]) : null;
540
490
 
541
491
  const queryTags = extractTags(query);
542
492
  const index = loadIndex();
@@ -549,12 +499,12 @@ function cmdSearchCases(args) {
549
499
 
550
500
  // Apply version filter before scoring
551
501
  const eligibleCases = versionFilter
552
- ? index.cases.filter((e) => caseMatchesVersionFilter(e, versionFilter))
502
+ ? index.cases.filter(e => caseMatchesVersionFilter(e, versionFilter))
553
503
  : index.cases;
554
504
 
555
505
  const skipped = index.cases.length - eligibleCases.length;
556
506
 
557
- const corpus = eligibleCases.map((e) => e.tags || []);
507
+ const corpus = eligibleCases.map(e => e.tags || []);
558
508
  const idf = buildIdf(corpus);
559
509
 
560
510
  const scored = [];
@@ -571,41 +521,29 @@ function cmdSearchCases(args) {
571
521
  console.log(
572
522
  ` ${DIM}${skipped} case(s) skipped by version filter: ${args[vfIdx + 1]}${RESET}`,
573
523
  );
574
- console.log(
575
- ` ${DIM}Try broader terms or check 'list' for available cases.${RESET}`,
576
- );
524
+ console.log(` ${DIM}Try broader terms or check 'list' for available cases.${RESET}`);
577
525
  return;
578
526
  }
579
527
 
580
- console.log(
581
- `\n${BOLD}${CYAN}━━━ Case Law Search Results ━━━━━━━━━━━━━━━━━━━━━━━${RESET}`,
582
- );
528
+ console.log(`\n${BOLD}${CYAN}━━━ Case Law Search Results ━━━━━━━━━━━━━━━━━━━━━━━${RESET}`);
583
529
  console.log(` Query : ${BOLD}${query}${RESET}`);
584
530
  console.log(
585
531
  ` Matches: ${top.length} of ${index.cases.length} cases` +
586
- (skipped > 0
587
- ? ` ${DIM}(${skipped} skipped by version filter)${RESET}`
588
- : "") +
589
- "\n",
532
+ (skipped > 0 ? ` ${DIM}(${skipped} skipped by version filter)${RESET}` : '') +
533
+ '\n',
590
534
  );
591
535
 
592
536
  for (const { score, entry } of top) {
593
- const vc = entry.verdict === "REJECTED" ? RED : YELLOW;
537
+ const vc = entry.verdict === 'REJECTED' ? RED : YELLOW;
594
538
  console.log(
595
- ` ${BOLD}Case #${String(entry.id).padStart(4, "0")}${RESET} ${vc}[${entry.verdict}]${RESET} ${DIM}${(entry.timestamp || "").slice(0, 10)}${RESET} score=${score.toFixed(2)}`,
539
+ ` ${BOLD}Case #${String(entry.id).padStart(4, '0')}${RESET} ${vc}[${entry.verdict}]${RESET} ${DIM}${(entry.timestamp || '').slice(0, 10)}${RESET} score=${score.toFixed(2)}`,
596
540
  );
597
541
  console.log(` ${DIM}Domain: ${entry.domain}${RESET}`);
598
542
  console.log(` ${entry.reason_summary}`);
599
- console.log(
600
- ` ${DIM}Tags: ${(entry.tags || []).slice(0, 8).join(", ")}${RESET}\n`,
601
- );
543
+ console.log(` ${DIM}Tags: ${(entry.tags || []).slice(0, 8).join(', ')}${RESET}\n`);
602
544
  }
603
- console.log(
604
- ` ${DIM}Run 'show --id <N>' to see the full diff for any case.${RESET}`,
605
- );
606
- console.log(
607
- `${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}\n`,
608
- );
545
+ console.log(` ${DIM}Run 'show --id <N>' to see the full diff for any case.${RESET}`);
546
+ console.log(`${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}\n`);
609
547
  }
610
548
 
611
549
  function cmdList(args) {
@@ -617,40 +555,33 @@ function cmdList(args) {
617
555
  }
618
556
 
619
557
  let domainFilter = null;
620
- const di = args.indexOf("--domain");
558
+ const di = args.indexOf('--domain');
621
559
  if (di !== -1 && args[di + 1]) domainFilter = args[di + 1].toLowerCase();
622
560
 
623
- const filtered = domainFilter
624
- ? cases.filter((c) => c.domain === domainFilter)
625
- : cases;
561
+ const filtered = domainFilter ? cases.filter(c => c.domain === domainFilter) : cases;
626
562
  const total = filtered.length;
627
563
 
628
- console.log(
629
- `\n${BOLD}${CYAN}━━━ Case Law Index (${total} cases) ━━━━━━━━━━━━━━━━━━━━${RESET}`,
630
- );
631
- if (domainFilter)
632
- console.log(` ${DIM}Filtered by domain: ${domainFilter}${RESET}\n`);
564
+ console.log(`\n${BOLD}${CYAN}━━━ Case Law Index (${total} cases) ━━━━━━━━━━━━━━━━━━━━${RESET}`);
565
+ if (domainFilter) console.log(` ${DIM}Filtered by domain: ${domainFilter}${RESET}\n`);
633
566
 
634
567
  const last20 = filtered.slice(-20).reverse();
635
568
  for (const entry of last20) {
636
- const vc = entry.verdict === "REJECTED" ? RED : YELLOW;
569
+ const vc = entry.verdict === 'REJECTED' ? RED : YELLOW;
637
570
  console.log(
638
- ` ${BOLD}#${String(entry.id).padStart(4, "0")}${RESET} ${vc}[${entry.verdict}]${RESET} ${DIM}${(entry.domain || "").toUpperCase()}${RESET} ${(entry.timestamp || "").slice(0, 10)}`,
571
+ ` ${BOLD}#${String(entry.id).padStart(4, '0')}${RESET} ${vc}[${entry.verdict}]${RESET} ${DIM}${(entry.domain || '').toUpperCase()}${RESET} ${(entry.timestamp || '').slice(0, 10)}`,
639
572
  );
640
- console.log(` ${(entry.reason_summary || "").slice(0, 80)}`);
573
+ console.log(` ${(entry.reason_summary || '').slice(0, 80)}`);
641
574
  }
642
575
  if (total > 20)
643
576
  console.log(
644
577
  `\n ${YELLOW}... showing last 20 of ${total}. Use 'export' for full history.${RESET}`,
645
578
  );
646
- console.log(
647
- `${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}\n`,
648
- );
579
+ console.log(`${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}\n`);
649
580
  }
650
581
 
651
582
  function cmdShow(args) {
652
583
  let caseId = null;
653
- const ii = args.indexOf("--id");
584
+ const ii = args.indexOf('--id');
654
585
  if (ii !== -1 && args[ii + 1]) caseId = parseInt(args[ii + 1], 10);
655
586
  if (caseId == null || isNaN(caseId)) {
656
587
  console.log(`${RED}✖ Provide a case ID: show --id 7${RESET}`);
@@ -659,15 +590,13 @@ function cmdShow(args) {
659
590
 
660
591
  const caseRecord = loadCase(caseId);
661
592
  if (!caseRecord) {
662
- console.log(
663
- `${RED}✖ Case #${String(caseId).padStart(4, "0")} not found.${RESET}`,
664
- );
593
+ console.log(`${RED}✖ Case #${String(caseId).padStart(4, '0')} not found.${RESET}`);
665
594
  process.exit(1);
666
595
  }
667
596
 
668
- const vc = caseRecord.verdict === "REJECTED" ? RED : YELLOW;
597
+ const vc = caseRecord.verdict === 'REJECTED' ? RED : YELLOW;
669
598
  console.log(
670
- `\n${BOLD}${CYAN}━━━ Case #${String(caseRecord.id).padStart(4, "0")} ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}`,
599
+ `\n${BOLD}${CYAN}━━━ Case #${String(caseRecord.id).padStart(4, '0')} ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}`,
671
600
  );
672
601
  console.log(` Verdict : ${vc}${BOLD}${caseRecord.verdict}${RESET}`);
673
602
  console.log(` Domain : ${caseRecord.domain}`);
@@ -678,22 +607,18 @@ function cmdShow(args) {
678
607
  console.log(` ${caseRecord.reason}`);
679
608
  console.log(`\n ${BOLD}Semantic Delta (meaningful changes only):${RESET}`);
680
609
  console.log(` ${DIM}─────────────────────────────────────────${RESET}`);
681
- const deltaLines = (caseRecord.diff_delta || caseRecord.diff_raw)
682
- .split("\n")
683
- .slice(0, 40);
610
+ const deltaLines = (caseRecord.diff_delta || caseRecord.diff_raw).split('\n').slice(0, 40);
684
611
  for (const line of deltaLines) {
685
- if (line.startsWith("+")) console.log(` ${GREEN}${line}${RESET}`);
686
- else if (line.startsWith("-")) console.log(` ${RED}${line}${RESET}`);
612
+ if (line.startsWith('+')) console.log(` ${GREEN}${line}${RESET}`);
613
+ else if (line.startsWith('-')) console.log(` ${RED}${line}${RESET}`);
687
614
  else console.log(` ${DIM}${line}${RESET}`);
688
615
  }
689
- console.log(`\n ${BOLD}Tags:${RESET} ${(caseRecord.tags || []).join(", ")}`);
690
- console.log(
691
- `${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}\n`,
692
- );
616
+ console.log(`\n ${BOLD}Tags:${RESET} ${(caseRecord.tags || []).join(', ')}`);
617
+ console.log(`${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}\n`);
693
618
  }
694
619
 
695
620
  function cmdExport(args) {
696
- const toStdout = args.includes("--stdout");
621
+ const toStdout = args.includes('--stdout');
697
622
  const index = loadIndex();
698
623
  const cases = index.cases || [];
699
624
  if (!cases.length) {
@@ -703,30 +628,30 @@ function cmdExport(args) {
703
628
 
704
629
  const now = new Date().toISOString().slice(0, 19);
705
630
  const lines = [
706
- "# Tribunal Case Law — Full Export\n",
631
+ '# Tribunal Case Law — Full Export\n',
707
632
  `Generated: ${now}`,
708
633
  `Total Cases: ${cases.length}\n`,
709
- "---\n",
634
+ '---\n',
710
635
  ];
711
636
  for (const entry of cases) {
712
637
  const caseRecord = loadCase(entry.id) || entry;
713
- const badge = `[${caseRecord.verdict || "REJECTED"}]`;
714
- lines.push(`## Case #${String(entry.id).padStart(4, "0")} ${badge}`);
638
+ const badge = `[${caseRecord.verdict || 'REJECTED'}]`;
639
+ lines.push(`## Case #${String(entry.id).padStart(4, '0')} ${badge}`);
715
640
  lines.push(`**Domain:** ${entry.domain} `);
716
- lines.push(`**Recorded:** ${(entry.timestamp || "").slice(0, 10)} `);
641
+ lines.push(`**Recorded:** ${(entry.timestamp || '').slice(0, 10)} `);
717
642
  if (caseRecord.pr_ref) lines.push(`**PR/Ref:** ${caseRecord.pr_ref} `);
718
643
  lines.push(`\n**Reason:** ${entry.reason_summary}\n`);
719
- lines.push(`**Tags:** \`${(entry.tags || []).slice(0, 8).join(", ")}\`\n`);
720
- lines.push("---\n");
644
+ lines.push(`**Tags:** \`${(entry.tags || []).slice(0, 8).join(', ')}\`\n`);
645
+ lines.push('---\n');
721
646
  }
722
- const content = lines.join("\n");
647
+ const content = lines.join('\n');
723
648
  if (toStdout) {
724
649
  console.log(content);
725
650
  return;
726
651
  }
727
652
 
728
- const outPath = path.join(getPaths().HISTORY_DIR, "case-law-export.md");
729
- fs.writeFileSync(outPath, content, "utf8");
653
+ const outPath = path.join(getPaths().HISTORY_DIR, 'case-law-export.md');
654
+ fs.writeFileSync(outPath, content, 'utf8');
730
655
  console.log(`${GREEN}✔ Exported ${cases.length} cases to ${outPath}${RESET}`);
731
656
  }
732
657
 
@@ -740,48 +665,40 @@ function cmdStats() {
740
665
  verdictCounts[c.verdict] = (verdictCounts[c.verdict] || 0) + 1;
741
666
  }
742
667
 
743
- console.log(
744
- `\n${BOLD}${CYAN}━━━ Case Law Statistics ━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}`,
745
- );
668
+ console.log(`\n${BOLD}${CYAN}━━━ Case Law Statistics ━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}`);
746
669
  console.log(` Total cases: ${BOLD}${cases.length}${RESET}`);
747
670
  console.log(`\n ${BOLD}By Verdict:${RESET}`);
748
671
  for (const v of Object.keys(verdictCounts).sort()) {
749
- const color = v === "REJECTED" ? RED : YELLOW;
672
+ const color = v === 'REJECTED' ? RED : YELLOW;
750
673
  console.log(` ${color}${v.padEnd(30)}${RESET} ${verdictCounts[v]}`);
751
674
  }
752
675
  console.log(`\n ${BOLD}By Domain:${RESET}`);
753
- for (const [d, c] of Object.entries(domainCounts).sort(
754
- (a, b) => b[1] - a[1],
755
- )) {
676
+ for (const [d, c] of Object.entries(domainCounts).sort((a, b) => b[1] - a[1])) {
756
677
  console.log(` ${CYAN}${d.padEnd(20)}${RESET} ${c}`);
757
678
  }
758
- console.log(
759
- `${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}\n`,
760
- );
679
+ console.log(`${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}\n`);
761
680
  }
762
681
 
763
682
  function cmdAutoRecord() {
764
683
  function getFlag(name) {
765
684
  const flag = `--${name}`;
766
685
  const idx = process.argv.indexOf(flag);
767
- return idx !== -1 && process.argv[idx + 1] ? process.argv[idx + 1] : "";
686
+ return idx !== -1 && process.argv[idx + 1] ? process.argv[idx + 1] : '';
768
687
  }
769
688
 
770
- const diffText = getFlag("diff");
771
- const reason = getFlag("reason");
772
- let domain = getFlag("domain") || "general";
773
- let verdict = getFlag("verdict") || "REJECTED";
774
- const reviewer = getFlag("reviewer") || null;
775
- const prRef = getFlag("pr-ref") || null;
689
+ const diffText = getFlag('diff');
690
+ const reason = getFlag('reason');
691
+ let domain = getFlag('domain') || 'general';
692
+ let verdict = getFlag('verdict') || 'REJECTED';
693
+ const reviewer = getFlag('reviewer') || null;
694
+ const prRef = getFlag('pr-ref') || null;
776
695
  // FIX: --stack-version persists version context with the case record so
777
696
  // future searches can skip it when the project has moved past that version.
778
697
  // Example: --stack-version "react=18,next=14"
779
- const stackVersion = getFlag("stack-version") || null;
698
+ const stackVersion = getFlag('stack-version') || null;
780
699
 
781
700
  if (!diffText || !reason) {
782
- console.log(
783
- `${RED}✖ auto-record requires --diff and --reason flags.${RESET}`,
784
- );
701
+ console.log(`${RED}✖ auto-record requires --diff and --reason flags.${RESET}`);
785
702
  console.log(
786
703
  ` Usage: auto-record --diff "code" --reason "why" --domain security --reviewer agent-name --stack-version "react=18"`,
787
704
  );
@@ -789,27 +706,25 @@ function cmdAutoRecord() {
789
706
  }
790
707
 
791
708
  if (isNoiseRejection(reason)) {
792
- console.log(
793
- `${DIM}⊘ Skipped: trivial rejection (noise filter matched).${RESET}`,
794
- );
709
+ console.log(`${DIM}⊘ Skipped: trivial rejection (noise filter matched).${RESET}`);
795
710
  return;
796
711
  }
797
- if (!VALID_DOMAINS.has(domain)) domain = "general";
798
- if (!VALID_VERDICTS.has(verdict)) verdict = "REJECTED";
712
+ if (!VALID_DOMAINS.has(domain)) domain = 'general';
713
+ if (!VALID_VERDICTS.has(verdict)) verdict = 'REJECTED';
799
714
 
800
715
  const fingerprint = contentHash(diffText);
801
716
  const index = loadIndex();
802
717
  for (const existing of index.cases) {
803
718
  if (existing.fingerprint === fingerprint) {
804
719
  console.log(
805
- `${YELLOW}⊘ Duplicate: Case #${String(existing.id).padStart(4, "0")} already records this pattern.${RESET}`,
720
+ `${YELLOW}⊘ Duplicate: Case #${String(existing.id).padStart(4, '0')} already records this pattern.${RESET}`,
806
721
  );
807
722
  return;
808
723
  }
809
724
  }
810
725
 
811
726
  const delta = semanticDelta(diffText);
812
- const tags = extractTags(diffText + " " + reason);
727
+ const tags = extractTags(diffText + ' ' + reason);
813
728
  const caseId = index.next_id;
814
729
  const now = new Date().toISOString().slice(0, 19);
815
730
 
@@ -843,16 +758,15 @@ function cmdAutoRecord() {
843
758
  index.next_id = caseId + 1;
844
759
  saveIndex(index);
845
760
  console.log(
846
- `${GREEN}✔ Auto-recorded Case #${String(caseId).padStart(4, "0")}${RESET} [${verdict}] domain=${domain}`,
761
+ `${GREEN}✔ Auto-recorded Case #${String(caseId).padStart(4, '0')}${RESET} [${verdict}] domain=${domain}`,
847
762
  );
848
763
  console.log(` ${DIM}Reason: ${reason.slice(0, 80)}${RESET}`);
849
- if (stackVersion)
850
- console.log(` ${DIM}Stack version: ${stackVersion}${RESET}`);
764
+ if (stackVersion) console.log(` ${DIM}Stack version: ${stackVersion}${RESET}`);
851
765
  }
852
766
 
853
767
  async function cmdOverrule(args) {
854
768
  let caseId = null;
855
- const ii = args.indexOf("--id");
769
+ const ii = args.indexOf('--id');
856
770
  if (ii !== -1 && args[ii + 1]) caseId = parseInt(args[ii + 1], 10);
857
771
  if (caseId == null || isNaN(caseId)) {
858
772
  console.log(`${RED}✖ Provide a case ID: overrule --id 7${RESET}`);
@@ -861,25 +775,21 @@ async function cmdOverrule(args) {
861
775
 
862
776
  const caseRecord = loadCase(caseId);
863
777
  if (!caseRecord) {
864
- console.log(
865
- `${RED}✖ Case #${String(caseId).padStart(4, "0")} not found.${RESET}`,
866
- );
778
+ console.log(`${RED}✖ Case #${String(caseId).padStart(4, '0')} not found.${RESET}`);
867
779
  process.exit(1);
868
780
  }
869
- if (caseRecord.verdict === "OVERRULED") {
870
- console.log(
871
- `${YELLOW}Case #${String(caseId).padStart(4, "0")} is already OVERRULED.${RESET}`,
872
- );
781
+ if (caseRecord.verdict === 'OVERRULED') {
782
+ console.log(`${YELLOW}Case #${String(caseId).padStart(4, '0')} is already OVERRULED.${RESET}`);
873
783
  return;
874
784
  }
875
785
 
876
786
  let reason = null;
877
- const ri = args.indexOf("--reason");
787
+ const ri = args.indexOf('--reason');
878
788
  if (ri !== -1 && args[ri + 1]) reason = args[ri + 1];
879
789
 
880
790
  if (!reason) {
881
791
  const rl = createRl();
882
- reason = await ask(rl, "Reason for overruling this precedent:");
792
+ reason = await ask(rl, 'Reason for overruling this precedent:');
883
793
  rl.close();
884
794
  }
885
795
  if (!reason || !reason.trim()) {
@@ -888,7 +798,7 @@ async function cmdOverrule(args) {
888
798
  }
889
799
 
890
800
  const oldVerdict = caseRecord.verdict;
891
- caseRecord.verdict = "OVERRULED";
801
+ caseRecord.verdict = 'OVERRULED';
892
802
  caseRecord.overruled_at = new Date().toISOString().slice(0, 19);
893
803
  caseRecord.overrule_reason = reason.trim();
894
804
  caseRecord.previous_verdict = oldVerdict;
@@ -897,27 +807,23 @@ async function cmdOverrule(args) {
897
807
  const index = loadIndex();
898
808
  for (const entry of index.cases) {
899
809
  if (entry.id === caseId) {
900
- entry.verdict = "OVERRULED";
810
+ entry.verdict = 'OVERRULED';
901
811
  break;
902
812
  }
903
813
  }
904
814
  saveIndex(index);
905
815
 
906
- console.log(
907
- `\n${GREEN}✔ Case #${String(caseId).padStart(4, "0")} OVERRULED${RESET}`,
908
- );
816
+ console.log(`\n${GREEN}✔ Case #${String(caseId).padStart(4, '0')} OVERRULED${RESET}`);
909
817
  console.log(` ${DIM}Previous verdict : ${oldVerdict}${RESET}`);
910
818
  console.log(` ${DIM}Overrule reason : ${reason.trim()}${RESET}`);
911
- console.log(
912
- ` ${DIM}The case is preserved in history but no longer blocks reviews.${RESET}\n`,
913
- );
819
+ console.log(` ${DIM}The case is preserved in history but no longer blocks reviews.${RESET}\n`);
914
820
  }
915
821
 
916
822
  // ── Main ──────────────────────────────────────────────────────────────────────
917
823
  const COMMANDS = {
918
- "add-case": cmdAddCase,
919
- "auto-record": cmdAutoRecord,
920
- "search-cases": cmdSearchCases,
824
+ 'add-case': cmdAddCase,
825
+ 'auto-record': cmdAutoRecord,
826
+ 'search-cases': cmdSearchCases,
921
827
  list: cmdList,
922
828
  show: cmdShow,
923
829
  overrule: cmdOverrule,
@@ -927,7 +833,7 @@ const COMMANDS = {
927
833
 
928
834
  async function main() {
929
835
  const argv = process.argv.slice(2);
930
- if (!argv.length || ["-h", "--help", "help"].includes(argv[0])) {
836
+ if (!argv.length || ['-h', '--help', 'help'].includes(argv[0])) {
931
837
  console.log(`
932
838
  ${BOLD}case_law_manager.js${RESET} — Tribunal Case Law Engine
933
839
 
@@ -941,8 +847,8 @@ ${BOLD}Commands:${RESET}
941
847
  export [--stdout] Export all cases to Markdown
942
848
  stats Show breakdown by domain/verdict
943
849
 
944
- ${BOLD}Domains:${RESET} ${[...VALID_DOMAINS].sort().join(", ")}
945
- ${BOLD}Verdicts:${RESET} ${[...VALID_VERDICTS].sort().join(", ")}
850
+ ${BOLD}Domains:${RESET} ${[...VALID_DOMAINS].sort().join(', ')}
851
+ ${BOLD}Verdicts:${RESET} ${[...VALID_VERDICTS].sort().join(', ')}
946
852
  `);
947
853
  return;
948
854
  }
@@ -951,7 +857,7 @@ ${BOLD}Verdicts:${RESET} ${[...VALID_VERDICTS].sort().join(", ")}
951
857
  const rest = argv.slice(1);
952
858
  if (!COMMANDS[cmd]) {
953
859
  console.log(`${RED}✖ Unknown command: '${cmd}'${RESET}`);
954
- console.log(` Valid: ${Object.keys(COMMANDS).join(", ")}`);
860
+ console.log(` Valid: ${Object.keys(COMMANDS).join(', ')}`);
955
861
  process.exit(1);
956
862
  }
957
863
  await COMMANDS[cmd](rest);
@@ -974,7 +880,7 @@ module.exports = {
974
880
  };
975
881
 
976
882
  if (require.main === module) {
977
- main().catch((err) => {
883
+ main().catch(err => {
978
884
  console.error(err);
979
885
  process.exit(1);
980
886
  });