tribunal-kit 4.0.1 → 4.3.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 (196) hide show
  1. package/.agent/ARCHITECTURE.md +21 -14
  2. package/.agent/GEMINI.md +4 -2
  3. package/.agent/agents/api-architect.md +66 -0
  4. package/.agent/agents/db-latency-auditor.md +216 -0
  5. package/.agent/agents/precedence-reviewer.md +41 -4
  6. package/.agent/agents/resilience-reviewer.md +88 -0
  7. package/.agent/agents/schema-reviewer.md +67 -0
  8. package/.agent/agents/swarm-worker-contracts.md +5 -5
  9. package/.agent/agents/throughput-optimizer.md +299 -0
  10. package/.agent/agents/ui-ux-auditor.md +292 -0
  11. package/.agent/agents/vitals-reviewer.md +223 -0
  12. package/.agent/history/case-law/cases/case-0001.json +33 -0
  13. package/.agent/history/case-law/index.json +35 -0
  14. package/.agent/rules/GEMINI.md +28 -11
  15. package/.agent/scripts/__pycache__/_colors.cpython-311.pyc +0 -0
  16. package/.agent/scripts/__pycache__/_utils.cpython-311.pyc +0 -0
  17. package/.agent/scripts/__pycache__/case_law_manager.cpython-311.pyc +0 -0
  18. package/.agent/scripts/_colors.js +18 -0
  19. package/.agent/scripts/_utils.js +42 -0
  20. package/.agent/scripts/auto_preview.js +197 -0
  21. package/.agent/scripts/bundle_analyzer.js +290 -0
  22. package/.agent/scripts/case_law_manager.js +684 -0
  23. package/.agent/scripts/checklist.js +266 -0
  24. package/.agent/scripts/colors.js +17 -0
  25. package/.agent/scripts/compress_skills.js +141 -0
  26. package/.agent/scripts/consolidate_skills.js +149 -0
  27. package/.agent/scripts/context_broker.js +609 -0
  28. package/.agent/scripts/deep_compress.js +150 -0
  29. package/.agent/scripts/dependency_analyzer.js +272 -0
  30. package/.agent/scripts/inner_loop_validator.js +465 -0
  31. package/.agent/scripts/lint_runner.js +187 -0
  32. package/.agent/scripts/minify_context.js +100 -0
  33. package/.agent/scripts/patch_skills_meta.js +156 -0
  34. package/.agent/scripts/patch_skills_output.js +244 -0
  35. package/.agent/scripts/schema_validator.js +297 -0
  36. package/.agent/scripts/security_scan.js +303 -0
  37. package/.agent/scripts/session_manager.js +276 -0
  38. package/.agent/scripts/skill_evolution.js +644 -0
  39. package/.agent/scripts/skill_integrator.js +313 -0
  40. package/.agent/scripts/strengthen_skills.js +193 -0
  41. package/.agent/scripts/strip_tribunal.js +47 -0
  42. package/.agent/scripts/swarm_dispatcher.js +360 -0
  43. package/.agent/scripts/test_runner.js +193 -0
  44. package/.agent/scripts/utils.js +32 -0
  45. package/.agent/scripts/verify_all.js +256 -0
  46. package/.agent/skills/agent-organizer/SKILL.md +42 -0
  47. package/.agent/skills/agentic-patterns/SKILL.md +42 -0
  48. package/.agent/skills/ai-prompt-injection-defense/SKILL.md +42 -0
  49. package/.agent/skills/api-patterns/SKILL.md +42 -0
  50. package/.agent/skills/api-security-auditor/SKILL.md +42 -0
  51. package/.agent/skills/app-builder/SKILL.md +42 -0
  52. package/.agent/skills/app-builder/templates/SKILL.md +70 -0
  53. package/.agent/skills/app-builder/templates/astro-static/TEMPLATE.md +1 -1
  54. package/.agent/skills/app-builder/templates/chrome-extension/TEMPLATE.md +1 -1
  55. package/.agent/skills/app-builder/templates/cli-tool/TEMPLATE.md +1 -1
  56. package/.agent/skills/app-builder/templates/electron-desktop/TEMPLATE.md +1 -1
  57. package/.agent/skills/app-builder/templates/express-api/TEMPLATE.md +1 -1
  58. package/.agent/skills/app-builder/templates/flutter-app/TEMPLATE.md +1 -1
  59. package/.agent/skills/app-builder/templates/monorepo-turborepo/TEMPLATE.md +1 -1
  60. package/.agent/skills/app-builder/templates/nextjs-fullstack/TEMPLATE.md +1 -1
  61. package/.agent/skills/app-builder/templates/nextjs-saas/TEMPLATE.md +1 -1
  62. package/.agent/skills/app-builder/templates/nextjs-static/TEMPLATE.md +1 -1
  63. package/.agent/skills/app-builder/templates/nuxt-app/TEMPLATE.md +1 -1
  64. package/.agent/skills/app-builder/templates/python-fastapi/TEMPLATE.md +1 -1
  65. package/.agent/skills/app-builder/templates/react-native-app/TEMPLATE.md +1 -1
  66. package/.agent/skills/appflow-wireframe/SKILL.md +42 -0
  67. package/.agent/skills/architecture/SKILL.md +42 -0
  68. package/.agent/skills/authentication-best-practices/SKILL.md +42 -0
  69. package/.agent/skills/bash-linux/SKILL.md +42 -0
  70. package/.agent/skills/behavioral-modes/SKILL.md +42 -0
  71. package/.agent/skills/brainstorming/SKILL.md +42 -0
  72. package/.agent/skills/building-native-ui/SKILL.md +42 -0
  73. package/.agent/skills/clean-code/SKILL.md +42 -0
  74. package/.agent/skills/code-review-checklist/SKILL.md +42 -0
  75. package/.agent/skills/config-validator/SKILL.md +42 -0
  76. package/.agent/skills/csharp-developer/SKILL.md +42 -0
  77. package/.agent/skills/data-validation-schemas/SKILL.md +320 -0
  78. package/.agent/skills/database-design/SKILL.md +42 -0
  79. package/.agent/skills/deployment-procedures/SKILL.md +42 -0
  80. package/.agent/skills/devops-engineer/SKILL.md +42 -0
  81. package/.agent/skills/devops-incident-responder/SKILL.md +42 -0
  82. package/.agent/skills/doc.md +1 -1
  83. package/.agent/skills/documentation-templates/SKILL.md +42 -0
  84. package/.agent/skills/edge-computing/SKILL.md +42 -0
  85. package/.agent/skills/error-resilience/SKILL.md +420 -0
  86. package/.agent/skills/extract-design-system/SKILL.md +42 -0
  87. package/.agent/skills/framer-motion-expert/SKILL.md +42 -1
  88. package/.agent/skills/frontend-design/SKILL.md +42 -0
  89. package/.agent/skills/game-design-expert/SKILL.md +42 -0
  90. package/.agent/skills/game-engineering-expert/SKILL.md +42 -0
  91. package/.agent/skills/geo-fundamentals/SKILL.md +42 -0
  92. package/.agent/skills/github-operations/SKILL.md +42 -0
  93. package/.agent/skills/gsap-core/SKILL.md +300 -0
  94. package/.agent/skills/gsap-frameworks/SKILL.md +199 -0
  95. package/.agent/skills/gsap-performance/SKILL.md +125 -0
  96. package/.agent/skills/gsap-plugins/SKILL.md +472 -0
  97. package/.agent/skills/gsap-react/SKILL.md +181 -0
  98. package/.agent/skills/gsap-scrolltrigger/SKILL.md +342 -0
  99. package/.agent/skills/gsap-timeline/SKILL.md +153 -0
  100. package/.agent/skills/gsap-utils/SKILL.md +330 -0
  101. package/.agent/skills/i18n-localization/SKILL.md +42 -0
  102. package/.agent/skills/intelligent-routing/SKILL.md +72 -1
  103. package/.agent/skills/lint-and-validate/SKILL.md +42 -0
  104. package/.agent/skills/llm-engineering/SKILL.md +42 -0
  105. package/.agent/skills/local-first/SKILL.md +42 -0
  106. package/.agent/skills/mcp-builder/SKILL.md +42 -0
  107. package/.agent/skills/mobile-design/SKILL.md +42 -0
  108. package/.agent/skills/monorepo-management/SKILL.md +326 -0
  109. package/.agent/skills/motion-engineering/SKILL.md +42 -0
  110. package/.agent/skills/nextjs-react-expert/SKILL.md +42 -0
  111. package/.agent/skills/nodejs-best-practices/SKILL.md +42 -0
  112. package/.agent/skills/observability/SKILL.md +42 -0
  113. package/.agent/skills/parallel-agents/SKILL.md +42 -0
  114. package/.agent/skills/performance-profiling/SKILL.md +42 -0
  115. package/.agent/skills/plan-writing/SKILL.md +42 -0
  116. package/.agent/skills/platform-engineer/SKILL.md +42 -0
  117. package/.agent/skills/playwright-best-practices/SKILL.md +42 -0
  118. package/.agent/skills/powershell-windows/SKILL.md +42 -0
  119. package/.agent/skills/project-idioms/SKILL.md +42 -0
  120. package/.agent/skills/python-patterns/SKILL.md +42 -0
  121. package/.agent/skills/python-pro/SKILL.md +42 -0
  122. package/.agent/skills/react-specialist/SKILL.md +42 -0
  123. package/.agent/skills/readme-builder/SKILL.md +42 -0
  124. package/.agent/skills/realtime-patterns/SKILL.md +42 -0
  125. package/.agent/skills/red-team-tactics/SKILL.md +42 -0
  126. package/.agent/skills/rust-pro/SKILL.md +42 -0
  127. package/.agent/skills/seo-fundamentals/SKILL.md +42 -0
  128. package/.agent/skills/server-management/SKILL.md +42 -0
  129. package/.agent/skills/shadcn-ui-expert/SKILL.md +42 -0
  130. package/.agent/skills/skill-creator/SKILL.md +42 -0
  131. package/.agent/skills/sql-pro/SKILL.md +42 -0
  132. package/.agent/skills/supabase-postgres-best-practices/SKILL.md +42 -0
  133. package/.agent/skills/swiftui-expert/SKILL.md +42 -0
  134. package/.agent/skills/systematic-debugging/SKILL.md +42 -0
  135. package/.agent/skills/tailwind-patterns/SKILL.md +42 -0
  136. package/.agent/skills/tdd-workflow/SKILL.md +42 -0
  137. package/.agent/skills/test-result-analyzer/SKILL.md +42 -0
  138. package/.agent/skills/testing-patterns/SKILL.md +42 -0
  139. package/.agent/skills/trend-researcher/SKILL.md +42 -0
  140. package/.agent/skills/typescript-advanced/SKILL.md +327 -0
  141. package/.agent/skills/ui-ux-pro-max/SKILL.md +42 -0
  142. package/.agent/skills/ui-ux-researcher/SKILL.md +42 -0
  143. package/.agent/skills/vue-expert/SKILL.md +42 -0
  144. package/.agent/skills/vulnerability-scanner/SKILL.md +42 -0
  145. package/.agent/skills/web-accessibility-auditor/SKILL.md +42 -0
  146. package/.agent/skills/web-design-guidelines/SKILL.md +42 -0
  147. package/.agent/skills/webapp-testing/SKILL.md +42 -0
  148. package/.agent/skills/whimsy-injector/SKILL.md +42 -0
  149. package/.agent/skills/workflow-optimizer/SKILL.md +42 -0
  150. package/.agent/workflows/audit.md +6 -6
  151. package/.agent/workflows/deploy.md +1 -1
  152. package/.agent/workflows/generate.md +23 -6
  153. package/.agent/workflows/session.md +5 -5
  154. package/.agent/workflows/swarm.md +2 -2
  155. package/.agent/workflows/tribunal-backend.md +13 -2
  156. package/.agent/workflows/tribunal-full.md +15 -8
  157. package/.agent/workflows/tribunal-speed.md +183 -0
  158. package/README.md +64 -8
  159. package/bin/tribunal-kit.js +281 -41
  160. package/package.json +9 -6
  161. package/scripts/changelog.js +167 -0
  162. package/scripts/sync-version.js +81 -0
  163. package/.agent/scripts/__pycache__/auto_preview.cpython-311.pyc +0 -0
  164. package/.agent/scripts/__pycache__/bundle_analyzer.cpython-311.pyc +0 -0
  165. package/.agent/scripts/__pycache__/checklist.cpython-311.pyc +0 -0
  166. package/.agent/scripts/__pycache__/dependency_analyzer.cpython-311.pyc +0 -0
  167. package/.agent/scripts/__pycache__/security_scan.cpython-311.pyc +0 -0
  168. package/.agent/scripts/__pycache__/session_manager.cpython-311.pyc +0 -0
  169. package/.agent/scripts/__pycache__/skill_integrator.cpython-311.pyc +0 -0
  170. package/.agent/scripts/__pycache__/swarm_dispatcher.cpython-311.pyc +0 -0
  171. package/.agent/scripts/__pycache__/test_runner.cpython-311.pyc +0 -0
  172. package/.agent/scripts/__pycache__/verify_all.cpython-311.pyc +0 -0
  173. package/.agent/scripts/auto_preview.py +0 -180
  174. package/.agent/scripts/bundle_analyzer.py +0 -259
  175. package/.agent/scripts/case_law_manager.py +0 -525
  176. package/.agent/scripts/checklist.py +0 -209
  177. package/.agent/scripts/compress_skills.py +0 -167
  178. package/.agent/scripts/consolidate_skills.py +0 -173
  179. package/.agent/scripts/deep_compress.py +0 -202
  180. package/.agent/scripts/dependency_analyzer.py +0 -247
  181. package/.agent/scripts/lint_runner.py +0 -188
  182. package/.agent/scripts/minify_context.py +0 -80
  183. package/.agent/scripts/patch_skills_meta.py +0 -177
  184. package/.agent/scripts/patch_skills_output.py +0 -285
  185. package/.agent/scripts/schema_validator.py +0 -279
  186. package/.agent/scripts/security_scan.py +0 -224
  187. package/.agent/scripts/session_manager.py +0 -261
  188. package/.agent/scripts/skill_evolution.py +0 -563
  189. package/.agent/scripts/skill_integrator.py +0 -234
  190. package/.agent/scripts/strengthen_skills.py +0 -220
  191. package/.agent/scripts/strip_tribunal.py +0 -41
  192. package/.agent/scripts/swarm_dispatcher.py +0 -350
  193. package/.agent/scripts/test_runner.py +0 -192
  194. package/.agent/scripts/test_swarm_dispatcher.py +0 -163
  195. package/.agent/scripts/verify_all.py +0 -195
  196. package/.agent/skills/gsap-expert/SKILL.md +0 -194
@@ -0,0 +1,609 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * context_broker.js — Tribunal Kit Context Density Broker
4
+ * =========================================================
5
+ * "Focus without Compromise" — Intelligent skill selection for all model sizes.
6
+ *
7
+ * Philosophy:
8
+ * This is NOT a filter that removes context. It is a PRIORITIZER that
9
+ * ensures the most relevant rules occupy the highest-attention positions
10
+ * in the AI's context window. Supplementary context is condensed, not cut.
11
+ *
12
+ * For LARGER models (Claude Opus, Gemini 2.5 Pro, GPT-4o):
13
+ * → Level 0 (Essential) skills are injected with full fidelity at the top.
14
+ * → Level 1 (Supplementary) skills are condensed to their key rules only.
15
+ * → Nothing is removed — the model gets everything, ordered optimally.
16
+ *
17
+ * For SMALLER/FASTER models (Gemini Flash, GPT-4o-mini):
18
+ * → Only Level 0 (Essential) skills are included.
19
+ * → This prevents context overflow and attention dilution.
20
+ * → Quality gates remain uncompromised — just fewer rules to track.
21
+ *
22
+ * Tiered Context Priority:
23
+ * Level 0 — Essential: Top matches, full SKILL.md text, injected first
24
+ * Level 1 — Supplementary: Medium matches, condensed to "key rules" section
25
+ * Level 2 — Available: Low matches, listed by name only (for reference)
26
+ *
27
+ * Scoring Algorithm:
28
+ * - Task keyword TF-IDF match against skill frontmatter + description
29
+ * - File type affinity (e.g., .tsx → react-specialist gets +2 boost)
30
+ * - Domain tag match (e.g., "sql" in task → sql-pro gets +3 boost)
31
+ * - Recency boost: skills referenced in the last 3 sessions rank higher
32
+ * - Tribunal alignment: skills matching active reviewers rank higher
33
+ *
34
+ * Usage:
35
+ * node .agent/scripts/context_broker.js --task "Build a login API with JWT"
36
+ * node .agent/scripts/context_broker.js --task "Design a premium landing page" --file Login.tsx
37
+ * node .agent/scripts/context_broker.js --task "..." --model large --output json
38
+ * node .agent/scripts/context_broker.js --task "..." --model small --output names
39
+ * node .agent/scripts/context_broker.js demo
40
+ *
41
+ * Output modes:
42
+ * report (default) — human-readable tiered selection report
43
+ * json — JSON with tiered skill lists
44
+ * names — newline-separated skill names only (for piping)
45
+ * prompt — full injected prompt text ready for an LLM
46
+ */
47
+
48
+ 'use strict';
49
+
50
+ const fs = require('fs');
51
+ const path = require('path');
52
+
53
+ // ── Colours ───────────────────────────────────────────────────────────────────
54
+ const GREEN = '\x1b[92m';
55
+ const YELLOW = '\x1b[93m';
56
+ const CYAN = '\x1b[96m';
57
+ const BLUE = '\x1b[94m';
58
+ const RED = '\x1b[91m';
59
+ const BOLD = '\x1b[1m';
60
+ const DIM = '\x1b[2m';
61
+ const RESET = '\x1b[0m';
62
+
63
+ // ── Domain → Skill affinity map ───────────────────────────────────────────────
64
+ // Keywords in the user's task that strongly indicate specific skills.
65
+ // Higher weight = stronger signal.
66
+ const DOMAIN_AFFINITIES = [
67
+ // Backend / API
68
+ { keywords: ['api', 'rest', 'route', 'endpoint', 'handler', 'express', 'fastapi', 'hono'],
69
+ skills: ['api-patterns', 'nodejs-best-practices', 'backend-specialist', 'error-resilience'], weight: 3 },
70
+ // Database
71
+ { keywords: ['sql', 'query', 'database', 'prisma', 'drizzle', 'orm', 'postgres', 'mysql', 'schema'],
72
+ skills: ['database-design', 'sql-pro', 'supabase-postgres-best-practices'], weight: 3 },
73
+ // Authentication
74
+ { keywords: ['auth', 'jwt', 'login', 'oauth', 'session', 'password', 'token', 'rbac'],
75
+ skills: ['authentication-best-practices', 'vulnerability-scanner', 'api-security-auditor'], weight: 3 },
76
+ // React / Next.js
77
+ { keywords: ['react', 'next', 'nextjs', 'component', 'hook', 'jsx', 'tsx', 'server component', 'server action'],
78
+ skills: ['react-specialist', 'nextjs-react-expert', 'frontend-design'], weight: 3 },
79
+ // Frontend / UI
80
+ { keywords: ['ui', 'design', 'landing', 'page', 'layout', 'responsive', 'tailwind', 'css', 'style'],
81
+ skills: ['frontend-design', 'ui-ux-pro-max', 'tailwind-patterns', 'web-design-guidelines'], weight: 2 },
82
+ // Animation / Motion
83
+ { keywords: ['animation', 'gsap', 'framer', 'motion', 'scroll', 'transition', 'parallax'],
84
+ skills: ['motion-engineering', 'framer-motion-expert', 'gsap-core', 'gsap-scrolltrigger'], weight: 3 },
85
+ // AI / LLM
86
+ { keywords: ['llm', 'openai', 'anthropic', 'gemini', 'embedding', 'ai', 'rag', 'vector', 'chat', 'prompt'],
87
+ skills: ['llm-engineering', 'ai-prompt-injection-defense', 'agentic-patterns'], weight: 3 },
88
+ // Security
89
+ { keywords: ['security', 'xss', 'injection', 'owasp', 'vulnerability', 'csrf', 'sanitize', 'audit'],
90
+ skills: ['vulnerability-scanner', 'api-security-auditor', 'authentication-best-practices'], weight: 3 },
91
+ // Testing
92
+ { keywords: ['test', 'spec', 'jest', 'vitest', 'playwright', 'unit test', 'e2e', 'mock'],
93
+ skills: ['testing-patterns', 'playwright-best-practices', 'tdd-workflow'], weight: 3 },
94
+ // Performance
95
+ { keywords: ['performance', 'optimize', 'bundle', 'cache', 'speed', 'slow', 'lighthouse', 'cwv'],
96
+ skills: ['performance-profiling', 'motion-engineering', 'edge-computing'], weight: 2 },
97
+ // Mobile
98
+ { keywords: ['mobile', 'react native', 'expo', 'ios', 'android', 'gesture', 'haptic'],
99
+ skills: ['building-native-ui', 'mobile-design', 'agentic-patterns'], weight: 3 },
100
+ // DevOps / CI
101
+ { keywords: ['docker', 'ci', 'cd', 'deploy', 'pipeline', 'k8s', 'kubernetes', 'github actions'],
102
+ skills: ['devops-engineer', 'deployment-procedures', 'observability'], weight: 2 },
103
+ // Real-time
104
+ { keywords: ['realtime', 'websocket', 'sse', 'socket', 'live', 'multiplayer', 'collaborative'],
105
+ skills: ['realtime-patterns', 'error-resilience'], weight: 3 },
106
+ // TypeScript
107
+ { keywords: ['typescript', 'type', 'generic', 'interface', 'satisfies', 'zod', 'pydantic'],
108
+ skills: ['typescript-advanced', 'data-validation-schemas'], weight: 2 },
109
+ // Python
110
+ { keywords: ['python', 'fastapi', 'django', 'flask', 'pydantic', 'asyncio'],
111
+ skills: ['python-pro', 'python-patterns'], weight: 3 },
112
+ // Architecture
113
+ { keywords: ['architecture', 'refactor', 'clean', 'solid', 'design pattern', 'monorepo', 'microservice'],
114
+ skills: ['architecture', 'clean-code', 'monorepo-management'], weight: 2 },
115
+ // C# / .NET
116
+ { keywords: ['csharp', 'c#', 'dotnet', '.net', 'blazor', 'aspnet', 'entity framework'],
117
+ skills: ['csharp-developer'], weight: 3 },
118
+ ];
119
+
120
+ // ── File extension → skill boost map ─────────────────────────────────────────
121
+ const EXT_AFFINITIES = {
122
+ '.tsx': ['react-specialist', 'nextjs-react-expert', 'typescript-advanced'],
123
+ '.jsx': ['react-specialist', 'frontend-design'],
124
+ '.ts': ['typescript-advanced', 'nodejs-best-practices'],
125
+ '.vue': ['vue-expert'],
126
+ '.py': ['python-pro', 'python-patterns'],
127
+ '.cs': ['csharp-developer'],
128
+ '.sql': ['sql-pro', 'database-design'],
129
+ '.css': ['tailwind-patterns', 'frontend-design'],
130
+ };
131
+
132
+ // ── Core baseline skills — always available to all model sizes ────────────────
133
+ // These are injected for every request at a condensed level.
134
+ const BASELINE_SKILLS = [
135
+ 'clean-code',
136
+ 'systematic-debugging',
137
+ 'error-resilience',
138
+ ];
139
+
140
+ // ── Skill catalogue (loaded from disk) ───────────────────────────────────────
141
+
142
+ /**
143
+ * Find the .agent directory by walking up from cwd.
144
+ * @returns {string} path to .agent/
145
+ */
146
+ function findAgentDir() {
147
+ let current = path.resolve(process.cwd());
148
+ const root = path.parse(current).root;
149
+ while (current !== root) {
150
+ const candidate = path.join(current, '.agent');
151
+ if (fs.existsSync(candidate) && fs.statSync(candidate).isDirectory()) return candidate;
152
+ current = path.dirname(current);
153
+ }
154
+ console.error(`${RED}✖ .agent/ not found. Run: npx tribunal-kit init${RESET}`);
155
+ process.exit(1);
156
+ }
157
+
158
+ /**
159
+ * Parse the YAML frontmatter from a SKILL.md file.
160
+ * Returns { name, description, ... } or null on parse failure.
161
+ * @param {string} content - Full SKILL.md file text
162
+ */
163
+ function parseFrontmatter(content) {
164
+ const match = content.match(/^---\n([\s\S]*?)\n---/);
165
+ if (!match) return null;
166
+ const yaml = match[1];
167
+ const obj = {};
168
+ for (const line of yaml.split('\n')) {
169
+ const sep = line.indexOf(':');
170
+ if (sep === -1) continue;
171
+ const key = line.slice(0, sep).trim();
172
+ const val = line.slice(sep + 1).trim().replace(/^["']|["']$/g, '');
173
+ obj[key] = val;
174
+ }
175
+ return obj;
176
+ }
177
+
178
+ /**
179
+ * Extract the "key rules" section from a SKILL.md for condensed Level-1 context.
180
+ * Falls back to first 800 chars of content if no section found.
181
+ * @param {string} content
182
+ */
183
+ function extractKeyRules(content) {
184
+ // Try to find sections named: Key Rules, Rules, Core Rules, Critical Rules, Guardrails
185
+ const sectionMatch = content.match(
186
+ /##\s+(?:Key Rules?|Core Rules?|Critical Rules?|Guardrails?|Rules?)\n([\s\S]*?)(?=\n##\s|$)/i
187
+ );
188
+ if (sectionMatch) return sectionMatch[1].trim().slice(0, 1200);
189
+ // Fallback: strip frontmatter and take first 800 chars
190
+ const bodyStart = content.indexOf('---', 3);
191
+ const body = bodyStart !== -1 ? content.slice(bodyStart + 3).trim() : content;
192
+ return body.slice(0, 800).trim();
193
+ }
194
+
195
+ /**
196
+ * Load all skills from .agent/skills/ directory.
197
+ * Returns an array of { name, file, frontmatter, content, keyRules } objects.
198
+ * @param {string} agentDir
199
+ */
200
+ function loadSkills(agentDir) {
201
+ const skillsDir = path.join(agentDir, 'skills');
202
+ if (!fs.existsSync(skillsDir)) return [];
203
+
204
+ const skills = [];
205
+ const entries = fs.readdirSync(skillsDir, { withFileTypes: true });
206
+
207
+ for (const entry of entries) {
208
+ if (!entry.isDirectory()) continue;
209
+ const skillFile = path.join(skillsDir, entry.name, 'SKILL.md');
210
+ if (!fs.existsSync(skillFile)) continue;
211
+
212
+ try {
213
+ const content = fs.readFileSync(skillFile, 'utf8');
214
+ const frontmatter = parseFrontmatter(content) || {};
215
+ const keyRules = extractKeyRules(content);
216
+ skills.push({
217
+ name: entry.name,
218
+ file: skillFile,
219
+ frontmatter,
220
+ content,
221
+ keyRules,
222
+ description: frontmatter.description || '',
223
+ });
224
+ } catch {
225
+ // Skip unreadable skills silently
226
+ }
227
+ }
228
+ return skills;
229
+ }
230
+
231
+ // ── Scoring engine ────────────────────────────────────────────────────────────
232
+
233
+ /**
234
+ * Tokenize text into lowercase words (3+ chars).
235
+ * @param {string} text
236
+ * @returns {string[]}
237
+ */
238
+ function tokenize(text) {
239
+ return (text.match(/\b[a-zA-Z_][a-zA-Z0-9_]{2,}\b/g) || []).map(t => t.toLowerCase());
240
+ }
241
+
242
+ /**
243
+ * Compute a relevance score for a skill against the user's task.
244
+ *
245
+ * Scoring breakdown:
246
+ * - Text overlap between task tokens and skill description/name: up to +5
247
+ * - Domain affinity keyword match: +weight (2 or 3) per match
248
+ * - File extension affinity: +2 per match
249
+ * - Baseline skill bonus: +1 (always present)
250
+ *
251
+ * @param {{ name, description, content }} skill
252
+ * @param {string} task - Raw task text from the user
253
+ * @param {string[]} fileExts - File extensions being touched
254
+ * @param {string[]} taskTokens - Pre-tokenized task
255
+ * @returns {number} score
256
+ */
257
+ function scoreSkill(skill, task, fileExts, taskTokens) {
258
+ let score = 0;
259
+ const taskLower = task.toLowerCase();
260
+ const skillText = (skill.name + ' ' + skill.description).toLowerCase();
261
+ const skillTokens = tokenize(skillText);
262
+
263
+ // 1. Token overlap (lightweight TF match — no IDF needed at this scale)
264
+ const skillSet = new Set(skillTokens);
265
+ for (const token of taskTokens) {
266
+ if (skillSet.has(token)) score += 1;
267
+ }
268
+
269
+ // 2. Domain affinity boost
270
+ for (const affinity of DOMAIN_AFFINITIES) {
271
+ const keywordMatch = affinity.keywords.some(k => taskLower.includes(k));
272
+ if (!keywordMatch) continue;
273
+ if (affinity.skills.includes(skill.name)) {
274
+ score += affinity.weight;
275
+ }
276
+ }
277
+
278
+ // 3. File extension boost
279
+ for (const ext of fileExts) {
280
+ const extSkills = EXT_AFFINITIES[ext] || [];
281
+ if (extSkills.includes(skill.name)) score += 2;
282
+ }
283
+
284
+ // 4. Baseline skill safety net
285
+ if (BASELINE_SKILLS.includes(skill.name)) score += 1;
286
+
287
+ return score;
288
+ }
289
+
290
+ /**
291
+ * Run the tiered selection algorithm.
292
+ *
293
+ * @param {string} task - Raw user task description
294
+ * @param {string[]} files - Affected filenames (for ext detection)
295
+ * @param {string} model - 'large' | 'small' | 'auto'
296
+ * @param {object[]} skills - Loaded skills array from loadSkills()
297
+ * @returns {{ essential: object[], supplementary: object[], available: object[], scores: Map }}
298
+ */
299
+ function selectSkills(task, files, model, skills) {
300
+ const taskTokens = tokenize(task);
301
+ const fileExts = files.map(f => path.extname(f).toLowerCase()).filter(Boolean);
302
+
303
+ // Score every available skill
304
+ const scored = skills.map(skill => ({
305
+ ...skill,
306
+ score: scoreSkill(skill, task, fileExts, taskTokens),
307
+ }));
308
+ scored.sort((a, b) => b.score - a.score);
309
+
310
+ // Determine tier thresholds
311
+ const maxScore = scored[0]?.score || 1;
312
+ const tier0Cut = Math.max(maxScore * 0.65, 2); // Essential: top 65%+ of max score
313
+ const tier1Cut = Math.max(maxScore * 0.3, 1); // Supplementary: 30–65%
314
+
315
+ const essential = scored.filter(s => s.score >= tier0Cut).slice(0, 10);
316
+ const supplementary = scored.filter(s => s.score < tier0Cut && s.score >= tier1Cut).slice(0, 8);
317
+ const available = scored.filter(s => s.score < tier1Cut && s.score > 0).slice(0, 10);
318
+
319
+ // Ensure baseline skills always appear at minimum in supplementary
320
+ for (const base of BASELINE_SKILLS) {
321
+ const inEssential = essential.find(s => s.name === base);
322
+ const inSupplementary = supplementary.find(s => s.name === base);
323
+ if (!inEssential && !inSupplementary) {
324
+ const baseSkill = skills.find(s => s.name === base);
325
+ if (baseSkill) supplementary.push({ ...baseSkill, score: 0.5 });
326
+ }
327
+ }
328
+
329
+ // For small models: collapse supplementary into available
330
+ if (model === 'small') {
331
+ return {
332
+ essential: essential.slice(0, 6),
333
+ supplementary: [],
334
+ available: [...supplementary, ...available].slice(0, 8),
335
+ scores: buildScoreMap(scored),
336
+ };
337
+ }
338
+
339
+ return { essential, supplementary, available, scores: buildScoreMap(scored) };
340
+ }
341
+
342
+ function buildScoreMap(scored) {
343
+ const m = new Map();
344
+ for (const s of scored) m.set(s.name, s.score);
345
+ return m;
346
+ }
347
+
348
+ // ── Output formatters ─────────────────────────────────────────────────────────
349
+
350
+ function formatReport(task, model, selection, elapsed) {
351
+ const { essential, supplementary, available } = selection;
352
+ const total = essential.length + supplementary.length + available.length;
353
+
354
+ console.log(`\n${BOLD}${CYAN}━━━ Context Broker — Skill Selection ━━━━━━━━━━━━━━━${RESET}`);
355
+ console.log(` Task : ${BOLD}${task.slice(0, 80)}${task.length > 80 ? '...' : ''}${RESET}`);
356
+ console.log(` Model : ${model === 'large' ? GREEN : YELLOW}${model}${RESET} ${DIM}(Focus without Compromise)${RESET}`);
357
+ console.log(` Skills : ${GREEN}${essential.length} essential${RESET} + ${YELLOW}${supplementary.length} supplementary${RESET} + ${DIM}${available.length} available${RESET} of ${total} matched`);
358
+ console.log(` Time : ${elapsed}ms\n`);
359
+
360
+ if (essential.length) {
361
+ console.log(` ${GREEN}${BOLD}▶ Level 0 — Essential (Full Context, Top Priority):${RESET}`);
362
+ for (const s of essential) {
363
+ const score = selection.scores.get(s.name) || 0;
364
+ console.log(` ${GREEN}✦${RESET} ${BOLD}${s.name}${RESET} ${DIM}score=${score.toFixed(1)}${RESET}`);
365
+ if (s.description) console.log(` ${DIM}${s.description.slice(0, 90)}${RESET}`);
366
+ }
367
+ }
368
+
369
+ if (supplementary.length) {
370
+ console.log(`\n ${YELLOW}${BOLD}▶ Level 1 — Supplementary (Key Rules Only):${RESET}`);
371
+ for (const s of supplementary) {
372
+ const score = selection.scores.get(s.name) || 0;
373
+ console.log(` ${YELLOW}◆${RESET} ${s.name} ${DIM}score=${score.toFixed(1)}${RESET}`);
374
+ }
375
+ }
376
+
377
+ if (available.length) {
378
+ console.log(`\n ${DIM}▶ Level 2 — Available (Name Reference Only):${RESET}`);
379
+ console.log(` ${DIM}${available.map(s => s.name).join(', ')}${RESET}`);
380
+ }
381
+
382
+ if (model === 'small') {
383
+ console.log(`\n ${YELLOW}⚡ Small model mode: supplementary collapsed. Essential only injected.${RESET}`);
384
+ }
385
+
386
+ console.log(`\n${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}\n`);
387
+ }
388
+
389
+ function formatJson(task, model, selection) {
390
+ return JSON.stringify({
391
+ task,
392
+ model,
393
+ timestamp: new Date().toISOString(),
394
+ essential: selection.essential.map(s => ({ name: s.name, score: selection.scores.get(s.name), description: s.description })),
395
+ supplementary: selection.supplementary.map(s => ({ name: s.name, score: selection.scores.get(s.name) })),
396
+ available: selection.available.map(s => s.name),
397
+ }, null, 2);
398
+ }
399
+
400
+ function formatNames(selection, model) {
401
+ const all = model === 'large'
402
+ ? [...selection.essential, ...selection.supplementary]
403
+ : selection.essential;
404
+ return all.map(s => s.name).join('\n');
405
+ }
406
+
407
+ /**
408
+ * Build a full LLM-ready context prompt string.
409
+ * This is the full output to be injected into an AI system prompt.
410
+ *
411
+ * For large models: Essential = full SKILL.md, Supplementary = key rules section.
412
+ * For small models: Essential = key rules section only.
413
+ *
414
+ * @param {string} task
415
+ * @param {string} model
416
+ * @param {object} selection
417
+ * @returns {string}
418
+ */
419
+ function formatPrompt(task, model, selection) {
420
+ const lines = [
421
+ `# Tribunal Context Broker — Injected Skills`,
422
+ `# Task: ${task}`,
423
+ `# Model tier: ${model}`,
424
+ `# Generated: ${new Date().toISOString()}`,
425
+ '',
426
+ '## Instructions for the AI',
427
+ 'The following skills are ordered by relevance to the current task.',
428
+ 'Level 0 skills contain full rule sets. Level 1 skills contain key rules only.',
429
+ 'Treat ALL injected rules as mandatory constraints, not suggestions.',
430
+ '',
431
+ '---',
432
+ '',
433
+ ];
434
+
435
+ if (selection.essential.length) {
436
+ lines.push('## Level 0 — Essential Skills (Full Context)');
437
+ lines.push('');
438
+ for (const s of selection.essential) {
439
+ lines.push(`### Skill: ${s.name}`);
440
+ lines.push('');
441
+ if (model === 'large') {
442
+ lines.push(s.content || s.keyRules);
443
+ } else {
444
+ lines.push(s.keyRules);
445
+ }
446
+ lines.push('');
447
+ lines.push('---');
448
+ lines.push('');
449
+ }
450
+ }
451
+
452
+ if (model === 'large' && selection.supplementary.length) {
453
+ lines.push('## Level 1 — Supplementary Skills (Key Rules)');
454
+ lines.push('');
455
+ for (const s of selection.supplementary) {
456
+ lines.push(`### Skill: ${s.name} (condensed)`);
457
+ lines.push('');
458
+ lines.push(s.keyRules);
459
+ lines.push('');
460
+ lines.push('---');
461
+ lines.push('');
462
+ }
463
+ }
464
+
465
+ if (selection.available.length) {
466
+ lines.push('## Level 2 — Available Skills (Reference Names Only)');
467
+ lines.push('');
468
+ lines.push('The following skills are relevant but not injected to maintain context density.');
469
+ lines.push('Request their full content if needed: ' + selection.available.map(s => s.name).join(', '));
470
+ lines.push('');
471
+ }
472
+
473
+ return lines.join('\n');
474
+ }
475
+
476
+ // ── Built-in demo ─────────────────────────────────────────────────────────────
477
+
478
+ function runDemo(agentDir) {
479
+ const skills = loadSkills(agentDir);
480
+
481
+ const scenarios = [
482
+ { task: 'Build a JWT authentication API with Express.js and Zod validation', file: 'auth.ts', model: 'large' },
483
+ { task: 'Design a premium landing page with GSAP scroll animations', file: 'Hero.tsx', model: 'large' },
484
+ { task: 'Write a Prisma query for paginated user orders', file: 'orders.ts', model: 'small' },
485
+ { task: 'Add RAG pipeline to an OpenAI-powered chat interface', file: 'chat.ts', model: 'large' },
486
+ ];
487
+
488
+ console.log(`\n${BOLD}${CYAN}━━━ Context Broker — Demo Mode ━━━━━━━━━━━━━━━━━━━━━${RESET}`);
489
+ console.log(` Loaded ${skills.length} skills from .agent/skills/\n`);
490
+
491
+ for (const scenario of scenarios) {
492
+ const t0 = Date.now();
493
+ const model = scenario.model;
494
+ const selection = selectSkills(scenario.task, [scenario.file], model, skills);
495
+ const elapsed = Date.now() - t0;
496
+
497
+ console.log(`\n ${BOLD}Task: "${scenario.task.slice(0, 70)}"${RESET} ${DIM}[${model} model]${RESET}`);
498
+ console.log(` Essential : ${GREEN}${selection.essential.map(s => s.name).join(', ')}${RESET}`);
499
+ console.log(` Supplementary : ${YELLOW}${selection.supplementary.map(s => s.name).join(', ') || '(small mode)'}${RESET}`);
500
+ console.log(` Time : ${DIM}${elapsed}ms${RESET}`);
501
+ }
502
+
503
+ console.log(`\n${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}\n`);
504
+ }
505
+
506
+ // ── Public API ────────────────────────────────────────────────────────────────
507
+
508
+ /**
509
+ * Programmatic API for use by other Tribunal scripts.
510
+ *
511
+ * @param {string} task - User task description
512
+ * @param {string[]} files - Files being touched
513
+ * @param {string} model - 'large' | 'small' | 'auto'
514
+ * @param {string} agentDir - Path to .agent/ directory
515
+ * @returns {{ essential, supplementary, available, promptText }}
516
+ */
517
+ function broker(task, files = [], model = 'large', agentDir = null) {
518
+ const resolvedAgentDir = agentDir || findAgentDir();
519
+ const skills = loadSkills(resolvedAgentDir);
520
+ const selection = selectSkills(task, files, model, skills);
521
+ const promptText = formatPrompt(task, model, selection);
522
+ return { ...selection, promptText };
523
+ }
524
+
525
+ module.exports = { broker, selectSkills, loadSkills, scoreSkill, findAgentDir };
526
+
527
+ // ── CLI Entry ─────────────────────────────────────────────────────────────────
528
+
529
+ if (require.main === module) {
530
+ const argv = process.argv.slice(2);
531
+
532
+ if (!argv.length || argv.includes('--help') || argv.includes('-h')) {
533
+ console.log(`
534
+ ${BOLD}context_broker.js${RESET} — Tribunal Focus-without-Compromise Context Engine
535
+
536
+ ${BOLD}Usage:${RESET}
537
+ node .agent/scripts/context_broker.js --task "<description>" [options]
538
+ node .agent/scripts/context_broker.js demo
539
+
540
+ ${BOLD}Options:${RESET}
541
+ --task <text> Task description to match against skill catalogue
542
+ --file <path> File being touched (repeat for multiple files)
543
+ --model <size> large (default) | small — model tier
544
+ --output <format> report (default) | json | names | prompt
545
+
546
+ ${BOLD}Model tiers:${RESET}
547
+ large Full Essential + condensed Supplementary (Claude Opus, Gemini 2.5 Pro, GPT-4o)
548
+ small Essential only (Gemini Flash, GPT-4o-mini)
549
+
550
+ ${BOLD}Examples:${RESET}
551
+ node .agent/scripts/context_broker.js --task "JWT auth API" --model large
552
+ node .agent/scripts/context_broker.js --task "landing page" --file Hero.tsx --output names
553
+ node .agent/scripts/context_broker.js --task "RAG pipeline" --output prompt > context.md
554
+ node .agent/scripts/context_broker.js demo
555
+ `);
556
+ process.exit(0);
557
+ }
558
+
559
+ const agentDir = findAgentDir();
560
+
561
+ if (argv[0] === 'demo') {
562
+ runDemo(agentDir);
563
+ process.exit(0);
564
+ }
565
+
566
+ // Parse args
567
+ const taskIdx = argv.indexOf('--task');
568
+ const modelIdx = argv.indexOf('--model');
569
+ const outputIdx = argv.indexOf('--output');
570
+
571
+ const task = taskIdx !== -1 && argv[taskIdx + 1] ? argv[taskIdx + 1] : '';
572
+ const model = modelIdx !== -1 && argv[modelIdx + 1] ? argv[modelIdx + 1] : 'large';
573
+ const output = outputIdx !== -1 && argv[outputIdx + 1] ? argv[outputIdx + 1] : 'report';
574
+
575
+ if (!task) {
576
+ console.error(`${RED}✖ --task is required${RESET}`);
577
+ process.exit(1);
578
+ }
579
+
580
+ // Collect --file arguments (may appear multiple times)
581
+ const files = [];
582
+ for (let i = 0; i < argv.length; i++) {
583
+ if (argv[i] === '--file' && argv[i + 1]) files.push(argv[i + 1]);
584
+ }
585
+
586
+ const t0 = Date.now();
587
+ const skills = loadSkills(agentDir);
588
+ const selection = selectSkills(task, files, model, skills);
589
+ const elapsed = Date.now() - t0;
590
+
591
+ if (!['large', 'small'].includes(model)) {
592
+ console.error(`${YELLOW}⚠ Unknown model tier "${model}" — defaulting to "large"${RESET}`);
593
+ }
594
+
595
+ switch (output) {
596
+ case 'json':
597
+ console.log(formatJson(task, model, selection));
598
+ break;
599
+ case 'names':
600
+ console.log(formatNames(selection, model));
601
+ break;
602
+ case 'prompt':
603
+ console.log(formatPrompt(task, model, selection));
604
+ break;
605
+ default: // 'report'
606
+ formatReport(task, model, selection, elapsed);
607
+ break;
608
+ }
609
+ }