tribunal-kit 7.0.0 → 8.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.
- package/.agent/config/claude.json +18 -0
- package/.agent/config/plugin.json +102 -0
- package/.agent/config/slash-commands.json +103 -0
- package/.agent/config/specialist-registry.json +415 -0
- package/.agent/config/system-prompt.md +191 -0
- package/.agent/history/architecture-graph.yaml +302 -3
- package/.agent/history/graph-cache.json +515 -45
- package/.agent/history/memory.db +0 -0
- package/.agent/history/snapshots/bin__adapter-install.js.json +13 -0
- package/.agent/history/snapshots/bin__global-store.js.json +15 -0
- package/.agent/history/snapshots/bin__mcp-server.js.json +19 -0
- package/.agent/history/snapshots/bin__proxy-server.js.json +20 -0
- package/.agent/history/snapshots/bin__spawn-agent.js.json +12 -0
- package/.agent/history/snapshots/bin__tk-proxy.js.json +21 -0
- package/.agent/history/snapshots/bin__tribunal-kit.js.json +9 -7
- package/.agent/history/snapshots/bin__wrapper.js.json +14 -0
- package/.agent/history/snapshots/eslint.config.js.json +1 -2
- package/.agent/history/snapshots/scripts__benchmark.js.json +14 -0
- package/.agent/history/snapshots/scripts__changelog.js.json +1 -2
- package/.agent/history/snapshots/scripts__fix-vbc.js.json +11 -0
- package/.agent/history/snapshots/scripts__stress_benchmark.js.json +15 -0
- package/.agent/history/snapshots/scripts__sync-version.js.json +1 -2
- package/.agent/history/snapshots/scripts__validate-payload.js.json +1 -2
- package/.agent/history/snapshots/scripts__visual_audit.js.json +11 -0
- package/.agent/history/snapshots/test__integration__bridges.test.js.json +1 -2
- package/.agent/history/snapshots/test__integration__graceful_degradation.test.js.json +12 -0
- package/.agent/history/snapshots/test__integration__init.test.js.json +1 -2
- package/.agent/history/snapshots/test__integration__minimal_change_pipeline.test.js.json +13 -0
- package/.agent/history/snapshots/test__integration__parallel_tribunal.test.js.json +12 -0
- package/.agent/history/snapshots/test__integration__routing.test.js.json +1 -2
- package/.agent/history/snapshots/test__integration__swarm_dispatcher.test.js.json +1 -2
- package/.agent/history/snapshots/test__integration__sync_status.test.js.json +13 -0
- package/.agent/history/snapshots/test__integration__wave2.test.js.json +1 -2
- package/.agent/history/snapshots/test__integration__wrapper.test.js.json +13 -0
- package/.agent/history/snapshots/test__unit__align.test.js.json +10 -0
- package/.agent/history/snapshots/test__unit__args.test.js.json +3 -3
- package/.agent/history/snapshots/test__unit__audit_release.test.js.json +16 -0
- package/.agent/history/snapshots/test__unit__case_law_manager.test.js.json +1 -2
- package/.agent/history/snapshots/test__unit__cicd_validator.test.js.json +12 -0
- package/.agent/history/snapshots/test__unit__compile.test.js.json +10 -0
- package/.agent/history/snapshots/test__unit__context_broker.test.js.json +1 -2
- package/.agent/history/snapshots/test__unit__contract_engine.test.js.json +13 -0
- package/.agent/history/snapshots/test__unit__copyDir.test.js.json +3 -3
- package/.agent/history/snapshots/test__unit__graph_tools.test.js.json +1 -2
- package/.agent/history/snapshots/test__unit__guardrail_engine.test.js.json +10 -0
- package/.agent/history/snapshots/test__unit__impact_classifier.test.js.json +12 -0
- package/.agent/history/snapshots/test__unit__init.test.js.json +14 -0
- package/.agent/history/snapshots/test__unit__inner_loop_validator.test.js.json +1 -2
- package/.agent/history/snapshots/test__unit__integrity_manifest.test.js.json +13 -0
- package/.agent/history/snapshots/test__unit__learn.test.js.json +11 -0
- package/.agent/history/snapshots/test__unit__marathon.test.js.json +22 -0
- package/.agent/history/snapshots/test__unit__mcp_server.test.js.json +16 -0
- package/.agent/history/snapshots/test__unit__memory.test.js.json +13 -0
- package/.agent/history/snapshots/test__unit__minimal_change.test.js.json +10 -0
- package/.agent/history/snapshots/test__unit__native.test.js.json +10 -0
- package/.agent/history/snapshots/test__unit__optimize.test.js.json +13 -0
- package/.agent/history/snapshots/test__unit__path_resolution.test.js.json +14 -0
- package/.agent/history/snapshots/test__unit__production_readiness_evidence.test.js.json +21 -0
- package/.agent/history/snapshots/test__unit__selfInstall.test.js.json +3 -3
- package/.agent/history/snapshots/test__unit__semver.test.js.json +3 -3
- package/.agent/history/snapshots/test__unit__skill_evolution.test.js.json +11 -0
- package/.agent/history/snapshots/test__unit__stress.test.js.json +15 -0
- package/.agent/history/snapshots/test__unit__swarm_dispatcher.test.js.json +3 -3
- package/.agent/history/snapshots/test__unit__utils.test.js.json +10 -0
- package/.agent/scripts/ast_context_loader.js +137 -0
- package/.agent/scripts/memory_engine.js +581 -0
- package/.agent/scripts/payload_schemas.js +146 -0
- package/.agent/scripts/prompt_compiler.js +59 -11
- package/.agent/scripts/swarm_dispatcher.js +295 -67
- package/.agent/scripts/token_budget_broker.js +117 -6
- package/.claude/CLAUDE.md +442 -0
- package/README.md +22 -12
- package/SECURITY.md +3 -3
- package/bin/adapter-install.js +240 -0
- package/bin/global-store.js +48 -0
- package/bin/mcp-server.js +568 -252
- package/bin/proxy-server.js +113 -0
- package/bin/spawn-agent.js +39 -0
- package/bin/tk-proxy.js +34 -0
- package/dist/commands/init.js +8 -51
- package/dist/commands/status.js +61 -36
- package/dist/commands/validate.js +22 -31
- package/dist/tui/banner.js +77 -0
- package/dist/tui/index.js +21 -0
- package/dist/tui/reviewer-grid.js +90 -0
- package/dist/tui/shimmer.js +91 -0
- package/dist/tui/theme.js +130 -0
- package/dist/tui/tree.js +93 -0
- package/dist/tui/wizard.js +148 -0
- package/dist/utils/helpers.js +5 -41
- package/package.json +27 -10
- package/scripts/build-graph.js +71 -0
- package/.agent/history/snapshots/migrate_refs.js.json +0 -11
- package/.agent/scripts/compile_router.py +0 -5
- package/.agent/scripts/migrate_skills_frontmatter.py +0 -5
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"file": "bin/adapter-install.js",
|
|
3
|
+
"riskScore": "Low",
|
|
4
|
+
"blastRadius": 0,
|
|
5
|
+
"imports": {
|
|
6
|
+
"fs": [],
|
|
7
|
+
"path": [],
|
|
8
|
+
"os": [],
|
|
9
|
+
"child_process": []
|
|
10
|
+
},
|
|
11
|
+
"dependents": [],
|
|
12
|
+
"content": "#!/usr/bin/env node\n\n/**\n * Tribunal Kit — Universal CLI Agent Adapter\n *\n * Auto-detects installed CLI agents and configures Tribunal Kit\n * for each one. Supports Claude Code, Aider, Codex, Gemini CLI,\n * OpenCode, Copilot CLI, Cursor, Windsurf, and Cline.\n *\n * Usage:\n * node bin/adapter-install.js # Auto-detect and install all\n * node bin/adapter-install.js claude # Install for Claude Code only\n * node bin/adapter-install.js --global # Install to ~/.tribunal-kit/\n */\n\nconst fs = require('fs');\nconst path = require('path');\nconst os = require('os');\nconst { execSync } = require('child_process');\n\nconst ADAPTERS = {\n 'claude-code': {\n detect: () => commandExists('claude'),\n rulesFile: '.claude/CLAUDE.md',\n description: 'Claude Code CLI',\n setup: (projectRoot, _globalMode) => {\n const rulesDir = path.join(projectRoot, '.claude');\n ensureDir(rulesDir);\n const source = path.join(__dirname, '..', '.claude', 'CLAUDE.md');\n const target = path.join(rulesDir, 'CLAUDE.md');\n if (fs.existsSync(source)) {\n fs.copyFileSync(source, target);\n }\n // Also install MCP server config\n installMcpConfig(projectRoot, 'claude');\n },\n },\n\n aider: {\n detect: () => commandExists('aider'),\n rulesFile: '.aider.conf.yml',\n description: 'Aider CLI',\n setup: projectRoot => {\n const conventionsPath = path.join(projectRoot, 'CONVENTIONS.md');\n const systemPrompt = fs.readFileSync(path.join(__dirname, '..', 'system-prompt.md'), 'utf8');\n fs.writeFileSync(conventionsPath, systemPrompt);\n console.log(` ✓ Wrote ${conventionsPath}`);\n },\n },\n\n codex: {\n detect: () => commandExists('codex'),\n rulesFile: 'AGENTS.md',\n description: 'OpenAI Codex CLI',\n setup: projectRoot => {\n const agentsPath = path.join(projectRoot, 'AGENTS.md');\n const systemPrompt = fs.readFileSync(path.join(__dirname, '..', 'system-prompt.md'), 'utf8');\n fs.writeFileSync(agentsPath, systemPrompt);\n console.log(` ✓ Wrote ${agentsPath}`);\n },\n },\n\n 'gemini-cli': {\n detect: () => commandExists('gemini'),\n rulesFile: '.gemini/rules/GEMINI.md',\n description: 'Google Gemini CLI',\n setup: projectRoot => {\n const geminiDir = path.join(projectRoot, '.gemini', 'rules');\n ensureDir(geminiDir);\n const systemPrompt = fs.readFileSync(path.join(__dirname, '..', 'system-prompt.md'), 'utf8');\n fs.writeFileSync(path.join(geminiDir, 'GEMINI.md'), systemPrompt);\n console.log(` ✓ Wrote ${path.join(geminiDir, 'GEMINI.md')}`);\n // Also install MCP config\n installMcpConfig(projectRoot, 'gemini');\n },\n },\n\n opencode: {\n detect: () => commandExists('opencode'),\n rulesFile: '.opencode/rules.md',\n description: 'OpenCode CLI',\n setup: projectRoot => {\n const opencodeDir = path.join(projectRoot, '.opencode');\n ensureDir(opencodeDir);\n const systemPrompt = fs.readFileSync(path.join(__dirname, '..', 'system-prompt.md'), 'utf8');\n fs.writeFileSync(path.join(opencodeDir, 'rules.md'), systemPrompt);\n console.log(` ✓ Wrote ${path.join(opencodeDir, 'rules.md')}`);\n },\n },\n\n 'copilot-cli': {\n detect: () => commandExists('gh') && hasGhExtension('copilot'),\n rulesFile: '.github/copilot-instructions.md',\n description: 'GitHub Copilot CLI',\n setup: projectRoot => {\n const ghDir = path.join(projectRoot, '.github');\n ensureDir(ghDir);\n const systemPrompt = fs.readFileSync(path.join(__dirname, '..', 'system-prompt.md'), 'utf8');\n fs.writeFileSync(path.join(ghDir, 'copilot-instructions.md'), systemPrompt);\n console.log(` ✓ Wrote ${path.join(ghDir, 'copilot-instructions.md')}`);\n },\n },\n};\n\n// --- Utility Functions ---\n\nfunction commandExists(cmd) {\n try {\n const isWin = os.platform() === 'win32';\n const check = isWin ? `where ${cmd}` : `which ${cmd}`;\n execSync(check, { stdio: 'ignore' });\n return true;\n } catch {\n return false;\n }\n}\n\nfunction hasGhExtension(ext) {\n try {\n const output = execSync('gh extension list', { encoding: 'utf8' });\n return output.includes(ext);\n } catch {\n return false;\n }\n}\n\nfunction ensureDir(dirPath) {\n if (!fs.existsSync(dirPath)) {\n fs.mkdirSync(dirPath, { recursive: true });\n }\n}\n\nfunction installMcpConfig(projectRoot, target) {\n const mcpConfig = {\n mcpServers: {\n 'tribunal-kit': {\n command: 'node',\n args: [path.join(__dirname, '..', 'bin', 'mcp-server.js')],\n env: { NODE_ENV: 'production' },\n },\n },\n };\n\n let configPath;\n if (target === 'claude') {\n configPath = path.join(projectRoot, '.claude', 'mcp.json');\n } else if (target === 'gemini') {\n configPath = path.join(projectRoot, '.gemini', 'settings.json');\n } else {\n configPath = path.join(projectRoot, 'mcp_config.json');\n }\n\n fs.writeFileSync(configPath, JSON.stringify(mcpConfig, null, 2));\n console.log(` ✓ MCP server registered at ${configPath}`);\n}\n\n// --- Main ---\n\nfunction main() {\n const args = process.argv.slice(2);\n const globalMode = args.includes('--global');\n const targetAgent = args.find(a => !a.startsWith('-'));\n const projectRoot = globalMode ? path.join(os.homedir(), '.tribunal-kit') : process.cwd();\n\n console.log('');\n console.log('┌─────────────────────────────────────────────┐');\n console.log('│ 🔱 Tribunal Kit — Universal Agent Adapter │');\n console.log('│ v7.0.0 · 52 specialists · 186 skills │');\n console.log('└─────────────────────────────────────────────┘');\n console.log('');\n\n if (globalMode) {\n ensureDir(projectRoot);\n console.log(`📁 Global mode: Installing to ${projectRoot}`);\n } else {\n console.log(`📁 Project mode: Installing to ${projectRoot}`);\n }\n console.log('');\n\n let installed = 0;\n let detected = 0;\n\n for (const [name, adapter] of Object.entries(ADAPTERS)) {\n if (targetAgent && name !== targetAgent) continue;\n\n const isDetected = adapter.detect();\n if (isDetected) detected++;\n\n if (targetAgent || isDetected) {\n console.log(`⚡ ${adapter.description} ${isDetected ? '(detected)' : '(manual)'}`);\n try {\n adapter.setup(projectRoot, globalMode);\n installed++;\n console.log(` ✅ Tribunal Kit configured for ${adapter.description}`);\n } catch (err) {\n console.error(` ❌ Failed: ${err.message}`);\n }\n console.log('');\n }\n }\n\n if (installed === 0 && !targetAgent) {\n console.log('⚠️ No CLI agents detected on this machine.');\n console.log('');\n console.log(' Supported agents:');\n for (const [name, adapter] of Object.entries(ADAPTERS)) {\n console.log(` • ${name.padEnd(15)} → ${adapter.description}`);\n }\n console.log('');\n console.log(' Install one, then run this command again.');\n console.log(' Or specify manually: node bin/adapter-install.js claude-code');\n } else {\n console.log('─────────────────────────────────────────────');\n console.log(`✅ Configured ${installed} agent(s) (${detected} auto-detected)`);\n console.log('');\n console.log(' Next steps:');\n console.log(' 1. Open your CLI agent normally');\n console.log(' 2. The Tribunal rules are now enforced automatically');\n console.log(' 3. Use /tribunal, /summon, /audit, /debug commands');\n }\n\n console.log('');\n}\n\nmain();\n"
|
|
13
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"file": "bin/global-store.js",
|
|
3
|
+
"riskScore": "Medium",
|
|
4
|
+
"blastRadius": 2,
|
|
5
|
+
"imports": {
|
|
6
|
+
"fs": [],
|
|
7
|
+
"path": [],
|
|
8
|
+
"os": []
|
|
9
|
+
},
|
|
10
|
+
"dependents": [
|
|
11
|
+
"bin/proxy-server.js",
|
|
12
|
+
"bin/tk-proxy.js"
|
|
13
|
+
],
|
|
14
|
+
"content": "const fs = require('fs');\nconst path = require('path');\nconst os = require('os');\n\nconst GLOBAL_STORE_PATH = path.join(os.homedir(), '.tribunal-kit');\nconst LOCAL_AGENT_PATH = path.join(__dirname, '..', '.agent');\n\nfunction initializeGlobalStore() {\n if (!fs.existsSync(GLOBAL_STORE_PATH)) {\n console.log(`[Tribunal Kit] Initializing global knowledge base at ${GLOBAL_STORE_PATH}...`);\n fs.mkdirSync(GLOBAL_STORE_PATH, { recursive: true });\n }\n\n // Copy .agent folder if it doesn't exist in the global store or if we want to force update\n const globalAgentPath = path.join(GLOBAL_STORE_PATH, '.agent');\n if (!fs.existsSync(globalAgentPath)) {\n console.log(`[Tribunal Kit] Copying specialists and skills to global store...`);\n copyDirectoryRecursiveSync(LOCAL_AGENT_PATH, globalAgentPath);\n }\n}\n\nfunction copyDirectoryRecursiveSync(source, target) {\n if (!fs.existsSync(target)) {\n fs.mkdirSync(target, { recursive: true });\n }\n\n const files = fs.readdirSync(source);\n for (const file of files) {\n const sourcePath = path.join(source, file);\n const targetPath = path.join(target, file);\n\n if (fs.lstatSync(sourcePath).isDirectory()) {\n copyDirectoryRecursiveSync(sourcePath, targetPath);\n } else {\n fs.copyFileSync(sourcePath, targetPath);\n }\n }\n}\n\nfunction getGlobalAgentPath() {\n return path.join(GLOBAL_STORE_PATH, '.agent');\n}\n\nmodule.exports = {\n initializeGlobalStore,\n getGlobalAgentPath,\n GLOBAL_STORE_PATH,\n};\n"
|
|
15
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"file": "bin/mcp-server.js",
|
|
3
|
+
"riskScore": "Medium",
|
|
4
|
+
"blastRadius": 3,
|
|
5
|
+
"imports": {
|
|
6
|
+
"path": [],
|
|
7
|
+
"child_process": [],
|
|
8
|
+
"readline": [],
|
|
9
|
+
"fs": [],
|
|
10
|
+
"../dist/commands/memory.js": [],
|
|
11
|
+
"../dist/commands/align.js": []
|
|
12
|
+
},
|
|
13
|
+
"dependents": [
|
|
14
|
+
"test/unit/audit_release.test.js",
|
|
15
|
+
"test/unit/mcp_server.test.js",
|
|
16
|
+
"test/unit/production_readiness_evidence.test.js"
|
|
17
|
+
],
|
|
18
|
+
"content": "#!/usr/bin/env node\n\n/**\n * Tribunal-Kit MCP Server (Performance-Optimized)\n *\n * This file exposes tribunal-kit tools via the Model Context Protocol (MCP)\n * over standard I/O, allowing AI clients (Cursor, Windsurf, Claude) to natively\n * invoke tribunal checks.\n *\n * In-process tools load reusable modules directly. Commands that depend on a\n * standalone CLI process remain isolated deliberately.\n *\n * Protocol: MCP 2024-11-05 over JSON-RPC 2.0 / stdio\n */\n\nconst path = require('path');\nconst { spawnSync } = require('child_process');\n\nconst PKG = require(path.resolve(__dirname, '../package.json'));\n\n// Timeout for intentionally isolated child processes (30 seconds).\nconst SPAWN_TIMEOUT_MS = 30000;\nconst GENERAL_TOOL_TIMEOUT_MS = 60000;\n\n// --- DSH Guard: Cooperative Timeout ---\nasync function withToolTimeout(fn, timeoutMs) {\n let timeoutId;\n const timeoutPromise = new Promise((_, reject) => {\n timeoutId = setTimeout(() => {\n reject(new Error('TOOL_TIMEOUT'));\n }, timeoutMs);\n });\n\n try {\n const result = await Promise.race([Promise.resolve().then(fn), timeoutPromise]);\n return result;\n } finally {\n clearTimeout(timeoutId);\n }\n}\n\n// --- DSH Guard: Loop Detection ---\nclass ToolRepeatGuard {\n constructor() {\n this.history = new Map();\n }\n\n observe(toolName, argsObj) {\n const canonicalize = obj => {\n if (Array.isArray(obj)) return obj.map(canonicalize);\n if (obj !== null && typeof obj === 'object') {\n const sorted = {};\n for (const key of Object.keys(obj).sort()) {\n sorted[key] = canonicalize(obj[key]);\n }\n return sorted;\n }\n return obj;\n };\n\n const canonicalArgs = JSON.stringify(canonicalize(argsObj) || {});\n const key = `${toolName}::${canonicalArgs}`;\n\n const currentCount = (this.history.get(key) || 0) + 1;\n this.history.clear();\n this.history.set(key, currentCount);\n\n if (currentCount === 3) {\n return 'SYSTEM NOTIFICATION: You are repeating the exact same tool call with identical arguments. Carefully analyze the previous result before calling again: if the task is not complete, try a different approach or different arguments instead of repeating the call.';\n }\n if (currentCount >= 5) {\n return `SYSTEM NOTIFICATION: Repeated tool call detected:\\n- tool: ${toolName}\\n- consecutive_calls: ${currentCount}\\n- arguments: ${canonicalArgs}\\nThe repeated calls are not making progress. Do not call this tool with these exact arguments again. Inspect the latest result and choose a different action, different arguments, or finish the task.`;\n }\n return null;\n }\n}\nconst repeatGuard = new ToolRepeatGuard();\n\nclass RpcError extends Error {\n constructor(code, message) {\n super(message);\n this.code = code;\n this.name = 'RpcError';\n }\n}\n\n// Minimal JSON-RPC 2.0 over stdio\nconst readline = require('readline');\n\nconst rl = readline.createInterface({\n input: process.stdin,\n output: process.stdout,\n terminal: false,\n});\n\n/**\n * Run the workspace integrity audit without invoking the CLI schema validator.\n * The MCP audit is about Tribunal assets, while `tk validate` validates one\n * explicitly supplied payload and schema.\n */\nfunction runTribunalAudit() {\n const fs = require('fs');\n const projectRoot = process.cwd();\n const manifestScript = path.join(projectRoot, '.agent', 'scripts', 'integrity_manifest.js');\n\n if (!fs.existsSync(manifestScript)) {\n return 'Error: .agent/scripts/integrity_manifest.js was not found. Run `tk init` first.';\n }\n\n try {\n const { generateManifest } = require(manifestScript);\n const manifest = generateManifest(projectRoot);\n if (manifest.error) return `Error: ${manifest.error}`;\n\n const { integrity } = manifest;\n const lines = [\n 'Tribunal audit complete.',\n `Agents: ${manifest.agents.total} (${manifest.agents.reviewer_count} reviewers)`,\n `Skills: ${manifest.skills.total}`,\n `Scripts: ${manifest.scripts.total}`,\n `Workflows: ${manifest.workflows.total}`,\n `References: ${integrity.total_references}; phantom references: ${integrity.phantom_references}`,\n `Count claims: ${integrity.total_claims}; invalid claims: ${integrity.invalid_claims}`,\n ];\n\n if (integrity.phantom_references > 0 || integrity.invalid_claims > 0) {\n lines.push(\n 'Audit found integrity issues; run `tk guardrail --scan` for remediation details.',\n );\n } else {\n lines.push('All discovered references and global asset-count claims are valid.');\n }\n return lines.join('\\n');\n } catch (error) {\n return `Audit failed: ${error.message}`;\n }\n}\n\n/**\n * Search case law in an isolated process because its CLI owns persistent state.\n */\nfunction searchCaseLaw(query) {\n const caseLawScript = path.resolve(__dirname, '../.agent/scripts/case_law_manager.js');\n // case_law_manager is a standalone stateful CLI, so retain its process boundary.\n const result = spawnSync(process.execPath, [caseLawScript, 'search-cases', '--query', query], {\n encoding: 'utf8',\n timeout: SPAWN_TIMEOUT_MS,\n });\n if (result.error) {\n if (result.error.code === 'ETIMEDOUT')\n return 'Error: Case law search timed out (exceeded 30s limit)';\n return `Error executing case law search: ${result.error.message}`;\n }\n return result.stdout || result.stderr || 'No results';\n}\n\nfunction stripBoilerplate(text) {\n if (!text) return text;\n let minified = text.replace(\n /AI coding assistants often fall into specific bad habits[\\s\\S]*$/g,\n '',\n );\n minified = minified.replace(/## 🤖 LLM-Specific Traps[\\s\\S]*$/g, '');\n minified = minified.replace(/## 🏛️ Tribunal Integration[\\s\\S]*$/g, '');\n minified = minified.replace(/## Pre-Flight Checklist[\\s\\S]*$/g, '');\n return minified.trim();\n}\n\nfunction getAgentDir() {\n const fs = require('fs');\n const local = path.join(process.cwd(), '.agent');\n if (fs.existsSync(local)) return local;\n return path.resolve(__dirname, '../.agent');\n}\n\nasync function handleRequest(req) {\n const fs = require('fs');\n if (req.method === 'notifications/initialized' || req.method === 'notifications/cancelled') {\n return null;\n }\n if (req.method === 'ping') {\n return {};\n }\n\n // MCP spec: method names follow path-style convention\n if (req.method === 'initialize') {\n return {\n protocolVersion: '2025-03-26',\n capabilities: {\n tools: {},\n resources: { subscribe: false, listChanged: false },\n prompts: { listChanged: false },\n },\n serverInfo: {\n name: 'tribunal-kit-mcp',\n version: PKG.version,\n },\n };\n }\n\n if (req.method === 'resources/list') {\n const agentDir = getAgentDir();\n const resources = [];\n\n // Agents\n const agentsDir = path.join(agentDir, 'agents');\n if (fs.existsSync(agentsDir)) {\n const files = fs.readdirSync(agentsDir).filter(f => f.endsWith('.md'));\n for (const f of files) {\n const name = path.basename(f, '.md');\n resources.push({\n uri: `tribunal://agent/${name}`,\n name: `Agent: ${name}`,\n description: `Tribunal Specialist Agent rule file for ${name}`,\n mimeType: 'text/markdown',\n });\n }\n }\n\n // Skills\n const skillsDir = path.join(agentDir, 'skills');\n if (fs.existsSync(skillsDir)) {\n const dirs = fs.readdirSync(skillsDir, { withFileTypes: true });\n for (const d of dirs) {\n if (d.isDirectory()) {\n const skillFile = path.join(skillsDir, d.name, 'SKILL.md');\n if (fs.existsSync(skillFile)) {\n resources.push({\n uri: `tribunal://skill/${d.name}`,\n name: `Skill: ${d.name}`,\n description: `Tribunal Skill instruction file for ${d.name}`,\n mimeType: 'text/markdown',\n });\n }\n }\n }\n }\n\n // Workflows\n const workflowsDir = path.join(agentDir, 'workflows');\n if (fs.existsSync(workflowsDir)) {\n const files = fs.readdirSync(workflowsDir).filter(f => f.endsWith('.md'));\n for (const f of files) {\n const name = path.basename(f, '.md');\n resources.push({\n uri: `tribunal://workflow/${name}`,\n name: `Workflow: ${name}`,\n description: `Tribunal Workflow guide for ${name}`,\n mimeType: 'text/markdown',\n });\n }\n }\n\n return { resources };\n }\n\n if (req.method === 'resources/read') {\n const uri = req.params && req.params.uri;\n if (!uri) throw new Error('Missing uri parameter');\n const agentDir = getAgentDir();\n let filePath = null;\n\n if (uri.startsWith('tribunal://agent/')) {\n const name = uri.replace('tribunal://agent/', '');\n filePath = path.join(agentDir, 'agents', `${name}.md`);\n } else if (uri.startsWith('tribunal://skill/')) {\n const name = uri.replace('tribunal://skill/', '');\n filePath = path.join(agentDir, 'skills', name, 'SKILL.md');\n } else if (uri.startsWith('tribunal://workflow/')) {\n const name = uri.replace('tribunal://workflow/', '');\n filePath = path.join(agentDir, 'workflows', `${name}.md`);\n }\n\n if (!filePath || !fs.existsSync(filePath)) {\n throw new Error(`Resource not found: ${uri}`);\n }\n\n const text = fs.readFileSync(filePath, 'utf8');\n return {\n contents: [\n {\n uri,\n mimeType: 'text/markdown',\n text,\n },\n ],\n };\n }\n\n if (req.method === 'prompts/list') {\n const agentDir = getAgentDir();\n const prompts = [];\n const workflowsDir = path.join(agentDir, 'workflows');\n\n if (fs.existsSync(workflowsDir)) {\n const files = fs.readdirSync(workflowsDir).filter(f => f.endsWith('.md'));\n for (const f of files) {\n const name = path.basename(f, '.md');\n prompts.push({\n name,\n description: `Execute tribunal workflow /${name}`,\n arguments: [\n {\n name: 'task',\n description: 'The task or target file to execute the workflow against',\n required: false,\n },\n ],\n });\n }\n }\n\n return { prompts };\n }\n\n if (req.method === 'prompts/get') {\n const name = req.params && req.params.name;\n const task = (req.params && req.params.arguments && req.params.arguments.task) || '';\n if (!name) throw new Error('Missing prompt name parameter');\n\n const agentDir = getAgentDir();\n const workflowPath = path.join(agentDir, 'workflows', `${name}.md`);\n if (!fs.existsSync(workflowPath)) {\n throw new Error(`Prompt workflow not found: ${name}`);\n }\n\n const content = fs.readFileSync(workflowPath, 'utf8');\n const promptText = task ? `${content}\\n\\nTarget Task/File: ${task}` : content;\n\n return {\n messages: [\n {\n role: 'user',\n content: {\n type: 'text',\n text: promptText,\n },\n },\n ],\n };\n }\n\n if (req.method === 'tools/list') {\n return {\n tools: [\n {\n name: 'run_tribunal_audit',\n description: 'Runs a full anti-hallucination audit across the workspace.',\n inputSchema: {\n type: 'object',\n properties: {},\n additionalProperties: false,\n },\n },\n {\n name: 'sync_ide_bridges',\n description: 'Synchronize IDE bridge files with the current GEMINI.md rules.',\n inputSchema: {\n type: 'object',\n properties: {},\n additionalProperties: false,\n },\n },\n {\n name: 'search_case_law',\n description:\n 'Search historical code rejections and legal precedent. Use this before writing code to avoid past mistakes.',\n inputSchema: {\n type: 'object',\n properties: {\n query: {\n type: 'string',\n description: \"Search query (e.g. 'useEffect state')\",\n },\n },\n required: ['query'],\n additionalProperties: false,\n },\n },\n {\n name: 'list_tribunal_agents',\n description: 'List all available Tribunal Kit agents.',\n inputSchema: {\n type: 'object',\n properties: {},\n additionalProperties: false,\n },\n },\n {\n name: 'get_tribunal_agent',\n description: 'Get the full markdown rules for a specific Tribunal agent.',\n inputSchema: {\n type: 'object',\n properties: {\n name: { type: 'string', description: \"The agent name (e.g. 'frontend-specialist')\" },\n },\n required: ['name'],\n additionalProperties: false,\n },\n },\n {\n name: 'list_tribunal_skills',\n description: 'List all available Tribunal Kit skills.',\n inputSchema: {\n type: 'object',\n properties: {},\n additionalProperties: false,\n },\n },\n {\n name: 'get_tribunal_skill',\n description: 'Get the full markdown instructions for a specific Tribunal skill.',\n inputSchema: {\n type: 'object',\n properties: {\n name: { type: 'string', description: \"The skill name (e.g. 'react-specialist')\" },\n },\n required: ['name'],\n additionalProperties: false,\n },\n },\n {\n name: 'recall_memory',\n description:\n 'Budget-constrained memory recall from the 4-Type Taxonomy Persistent Memory Engine. Returns the most relevant memories that fit within the token budget, ranked by relevance × recency × priority. Use this BEFORE writing code to recall project guidelines without bloating context.',\n inputSchema: {\n type: 'object',\n properties: {\n query: {\n type: 'string',\n description: \"Search query (e.g. 'database', 'auth', 'deploy')\",\n },\n budget: {\n type: 'number',\n description:\n 'Maximum token budget for recall (default: 2000). Only the top-ranked memories that fit within this budget are returned.',\n },\n },\n required: ['query'],\n additionalProperties: false,\n },\n },\n {\n name: 'store_memory',\n description:\n 'Store a new memory entry in the 4-Type Taxonomy Persistent Memory Engine. Memories are schema-validated and persisted across sessions. Types: semantic (permanent facts), procedural (how-to recipes), episodic (30-day TTL events), working (session scratch).',\n inputSchema: {\n type: 'object',\n properties: {\n type: {\n type: 'string',\n enum: ['semantic', 'procedural', 'episodic', 'working'],\n description:\n 'Memory type: semantic (facts), procedural (recipes), episodic (events), working (scratch)',\n },\n content: {\n type: 'string',\n description: 'The memory content to store',\n },\n tags: {\n type: 'array',\n items: { type: 'string' },\n description: 'Searchable tags for this memory',\n },\n },\n required: ['type', 'content'],\n additionalProperties: false,\n },\n },\n {\n name: 'get_sparse_context',\n description:\n 'Get a JIT, token-optimized context prompt tailored to the active task and files. Uses the Context Density Broker to score and select relevant skills, stripping duplicate boilerplate and saving up to 85% in prompt tokens.',\n inputSchema: {\n type: 'object',\n properties: {\n task: {\n type: 'string',\n description: \"The user task description (e.g. 'JWT auth API')\",\n },\n files: {\n type: 'array',\n items: { type: 'string' },\n description: \"List of files being touched (e.g. ['src/auth.js'])\",\n },\n model: {\n type: 'string',\n enum: ['large', 'small'],\n description:\n 'Model tier: large (default, includes key rules of supplementary skills) or small (essential skills only)',\n },\n },\n required: ['task'],\n additionalProperties: false,\n },\n },\n {\n name: 'align_output',\n description:\n 'Align model outputs to Fabel-5 constraints: strips conversational introductions and conclusions, collapses single/double bullet items to prose, and checks for code traps (unawaited dynamic functions in Next.js 15, deprecated hooks in React 19, or non-existent models).',\n inputSchema: {\n type: 'object',\n properties: {\n text: {\n type: 'string',\n description: 'The raw output text generated by the model to be aligned.',\n },\n },\n required: ['text'],\n additionalProperties: false,\n },\n },\n {\n name: 'verify_contracts',\n description:\n 'Verify proposed code changes against project behavioral contracts. Use BEFORE writing code to ensure compliance with team conventions.',\n inputSchema: {\n type: 'object',\n properties: {\n file: {\n type: 'string',\n description: \"Relative or absolute file path to verify (e.g. 'src/api/user.ts')\",\n },\n content: {\n type: 'string',\n description: 'Proposed file content to verify against loaded contracts',\n },\n },\n required: ['file', 'content'],\n additionalProperties: false,\n },\n },\n {\n name: 'query_semantic_graph',\n description:\n 'Query the Ahead-of-Time (AOT) Semantic Context Graph to quickly resolve function signatures, dependencies, and file structures without inflating context windows.',\n inputSchema: {\n type: 'object',\n properties: {\n targetPath: {\n type: 'string',\n description: 'The directory to query (defaults to current workspace).',\n },\n },\n additionalProperties: false,\n },\n },\n {\n name: 'exit_plan_mode',\n description:\n 'Present your finished plan for human reviewed designing your approach. Execution will suspend until the user approves or requests changes.',\n inputSchema: {\n type: 'object',\n properties: {\n plan_content: {\n type: 'string',\n description: 'The markdown content of your proposed plan.',\n },\n },\n required: ['plan_content'],\n additionalProperties: false,\n },\n },\n ],\n };\n }\n\n if (req.method === 'tools/call') {\n const toolName = req.params && req.params.name;\n const argsObj = req.params && req.params.arguments;\n if (!toolName) {\n throw new RpcError(-32602, 'Missing required parameter: params.name');\n }\n\n const reminder = repeatGuard.observe(toolName, argsObj);\n\n const executeTool = async () => {\n if (toolName === 'exit_plan_mode') {\n const planContent = req.params?.arguments?.plan_content;\n if (typeof planContent !== 'string') {\n throw new RpcError(-32602, 'Missing or invalid required argument: plan_content (string)');\n }\n // The wrapper CLI can intercept this, but for the LLM context, we explicitly tell it to wait.\n return {\n content: [\n {\n type: 'text',\n text: 'PLAN_SUBMITTED_FOR_REVIEW: The plan has been presented to the human. Please suspend execution and wait for the human to approve or provide feedback in the next turn. Do not call any further tools until you receive a response.',\n },\n ],\n };\n }\n\n if (toolName === 'run_tribunal_audit') {\n const text = runTribunalAudit();\n return { content: [{ type: 'text', text }] };\n }\n\n if (toolName === 'sync_ide_bridges') {\n const fs = require('fs');\n const cwd = process.cwd();\n const agentDest = path.join(cwd, '.agent');\n if (!fs.existsSync(agentDest)) {\n return {\n content: [\n {\n type: 'text',\n text: 'Error: .agent/ directory not found. Run `tk init` first.',\n },\n ],\n };\n }\n try {\n const { generateIDEBridges } = require(\n path.resolve(__dirname, '../dist/commands/init.js'),\n );\n // generateIDEBridges is async\n await generateIDEBridges(cwd, agentDest, true);\n return {\n content: [\n {\n type: 'text',\n text: 'Sync complete',\n },\n ],\n };\n } catch (e) {\n return {\n content: [\n {\n type: 'text',\n text: `Sync failed: ${e.message}`,\n },\n ],\n };\n }\n }\n\n if (toolName === 'search_case_law') {\n const query = req.params && req.params.arguments && req.params.arguments.query;\n if (!query || typeof query !== 'string') {\n throw new RpcError(-32602, 'Missing or invalid required argument: query (string)');\n }\n const text = searchCaseLaw(query);\n return { content: [{ type: 'text', text }] };\n }\n\n if (toolName === 'list_tribunal_agents') {\n const fs = require('fs');\n const agentDir = path.join(getAgentDir(), 'agents');\n if (!fs.existsSync(agentDir))\n return {\n content: [{ type: 'text', text: 'No agents found or .agent directory missing.' }],\n };\n const agents = fs\n .readdirSync(agentDir)\n .filter(f => f.endsWith('.md'))\n .map(f => f.replace('.md', ''));\n return { content: [{ type: 'text', text: 'Available Agents:\\n- ' + agents.join('\\n- ') }] };\n }\n\n if (toolName === 'get_tribunal_agent') {\n const fs = require('fs');\n const name = req.params?.arguments?.name;\n if (!name || typeof name !== 'string')\n throw new RpcError(-32602, 'Missing or invalid argument: name (string)');\n const sanitizedName = path.basename(name);\n const agentsDir = path.resolve(getAgentDir(), 'agents');\n const agentPath = path.resolve(agentsDir, `${sanitizedName}.md`);\n // Path containment: ensure resolved path stays within agents directory\n if (!agentPath.startsWith(agentsDir))\n throw new RpcError(-32602, 'Invalid agent name: path traversal detected');\n if (!fs.existsSync(agentPath))\n return { content: [{ type: 'text', text: `Agent '${sanitizedName}' not found.` }] };\n const text = fs.readFileSync(agentPath, 'utf8');\n return { content: [{ type: 'text', text: stripBoilerplate(text) }] };\n }\n\n if (toolName === 'list_tribunal_skills') {\n const fs = require('fs');\n const skillsDir = path.join(getAgentDir(), 'skills');\n if (!fs.existsSync(skillsDir))\n return {\n content: [{ type: 'text', text: 'No skills found or .agent directory missing.' }],\n };\n const skills = fs\n .readdirSync(skillsDir, { withFileTypes: true })\n .filter(d => d.isDirectory())\n .map(d => d.name);\n return { content: [{ type: 'text', text: 'Available Skills:\\n- ' + skills.join('\\n- ') }] };\n }\n\n if (toolName === 'get_tribunal_skill') {\n const fs = require('fs');\n const name = req.params?.arguments?.name;\n if (!name || typeof name !== 'string')\n throw new RpcError(-32602, 'Missing or invalid argument: name (string)');\n const sanitizedName = path.basename(name);\n const skillsDir = path.resolve(getAgentDir(), 'skills');\n const skillPath = path.resolve(skillsDir, sanitizedName, 'SKILL.md');\n // Path containment: ensure resolved path stays within skills directory\n if (!skillPath.startsWith(skillsDir))\n throw new RpcError(-32602, 'Invalid skill name: path traversal detected');\n if (!fs.existsSync(skillPath))\n return { content: [{ type: 'text', text: `Skill '${sanitizedName}' not found.` }] };\n const text = fs.readFileSync(skillPath, 'utf8');\n return { content: [{ type: 'text', text: stripBoilerplate(text) }] };\n }\n\n if (toolName === 'get_sparse_context') {\n const task = req.params?.arguments?.task;\n const files = req.params?.arguments?.files || [];\n const model = req.params?.arguments?.model || 'large';\n\n if (!task) throw new RpcError(-32602, 'Missing required argument: task');\n\n const agentDest = getAgentDir();\n const fs = require('fs');\n if (!fs.existsSync(agentDest)) {\n return {\n content: [\n { type: 'text', text: 'Error: .agent/ directory not found. Run `tk init` first.' },\n ],\n };\n }\n\n try {\n const brokerScript = path.join(agentDest, 'scripts', 'context_broker.js');\n const { broker } = require(brokerScript);\n const brokerResult = broker(task, files, model, agentDest);\n return { content: [{ type: 'text', text: stripBoilerplate(brokerResult.promptText) }] };\n } catch (e) {\n return {\n content: [{ type: 'text', text: `Failed to retrieve sparse context: ${e.message}` }],\n };\n }\n }\n\n if (toolName === 'recall_memory') {\n const query = req.params?.arguments?.query;\n if (!query || typeof query !== 'string') {\n throw new RpcError(-32602, 'Missing or invalid required argument: query (string)');\n }\n const budget = req.params?.arguments?.budget || 2000;\n const agentDest = getAgentDir();\n const fs = require('fs');\n if (!fs.existsSync(agentDest)) {\n return {\n content: [\n { type: 'text', text: 'Error: .agent/ directory not found. Run `tk init` first.' },\n ],\n };\n }\n try {\n const { _memoryRecall } = require('../dist/commands/memory.js');\n const { results, tokens_used } = _memoryRecall(agentDest, query, budget);\n if (results.length === 0) {\n return { content: [{ type: 'text', text: `No memories match query: \"${query}\"` }] };\n }\n let text = `## Memory Recall (${results.length} results, ~${tokens_used}/${budget} tokens)\\n\\n`;\n for (const entry of results) {\n text += `- **[${entry.memory_type.toUpperCase()}]** #${entry.id}: ${entry.content}`;\n if (entry.tags.length > 0) text += ` _(${entry.tags.join(', ')})_`;\n text += `\\n`;\n }\n return { content: [{ type: 'text', text }] };\n } catch (e) {\n return { content: [{ type: 'text', text: `Memory recall failed: ${e.message}` }] };\n }\n }\n\n if (toolName === 'store_memory') {\n const memType = req.params?.arguments?.type;\n const content = req.params?.arguments?.content;\n const tags = req.params?.arguments?.tags || [];\n if (!memType || !content) {\n throw new RpcError(-32602, 'Missing required arguments: type (string), content (string)');\n }\n const validTypes = ['semantic', 'procedural', 'episodic', 'working'];\n if (!validTypes.includes(memType)) {\n throw new RpcError(\n -32602,\n `Invalid memory type: \"${memType}\". Must be one of: ${validTypes.join(', ')}`,\n );\n }\n const agentDest = getAgentDir();\n const fs = require('fs');\n if (!fs.existsSync(agentDest)) {\n return {\n content: [\n { type: 'text', text: 'Error: .agent/ directory not found. Run `tk init` first.' },\n ],\n };\n }\n try {\n const { _memoryStore } = require('../dist/commands/memory.js');\n const result = _memoryStore(agentDest, memType, content, tags, null);\n return {\n content: [\n {\n type: 'text',\n text: `Memory stored: #${result.id} (${memType}, ~${result.token_estimate} tokens)`,\n },\n ],\n };\n } catch (e) {\n return { content: [{ type: 'text', text: `Memory store failed: ${e.message}` }] };\n }\n }\n\n if (toolName === 'query_semantic_graph') {\n const targetPath = req.params?.arguments?.targetPath || process.cwd();\n try {\n const result = spawnSync(\n 'node',\n [path.join(__dirname, '../scripts/build-graph.js'), targetPath],\n {\n encoding: 'utf8',\n timeout: 10000,\n },\n );\n if (result.error) throw result.error;\n return { content: [{ type: 'text', text: result.stdout || result.stderr }] };\n } catch (e) {\n return { content: [{ type: 'text', text: `Failed to query graph: ${e.message}` }] };\n }\n }\n\n if (toolName === 'align_output') {\n const text = req.params?.arguments?.text;\n if (typeof text !== 'string') {\n throw new RpcError(-32602, 'Missing or invalid required argument: text (string)');\n }\n try {\n const { alignText, validateCodeContent } = require('../dist/commands/align.js');\n const aligned = alignText(text);\n const warnings = validateCodeContent(aligned);\n\n let outputText = aligned;\n if (warnings.length > 0) {\n outputText += '\\n\\n⚠️ OCAE Alignment Validator Warnings:\\n';\n for (const warnMsg of warnings) {\n outputText += `● ${warnMsg}\\n`;\n }\n }\n return { content: [{ type: 'text', text: outputText }] };\n } catch (e) {\n return { content: [{ type: 'text', text: `Output alignment failed: ${e.message}` }] };\n }\n }\n\n if (toolName === 'verify_contracts') {\n const file = req.params?.arguments?.file;\n const content = req.params?.arguments?.content;\n\n if (!file || typeof file !== 'string' || typeof content !== 'string') {\n throw new RpcError(\n -32602,\n 'Missing or invalid required arguments: file (string), content (string)',\n );\n }\n\n try {\n const projectRoot = process.cwd();\n const contractEnginePath = path.join(getAgentDir(), 'scripts', 'contract_engine.js');\n if (!require('fs').existsSync(contractEnginePath)) {\n return {\n content: [\n { type: 'text', text: 'Error: contract_engine.js not found. Run `tk init` first.' },\n ],\n };\n }\n\n const contractEngine = require(contractEnginePath);\n const contracts = contractEngine.loadContracts(projectRoot);\n\n if (contracts.length === 0) {\n return {\n content: [\n {\n type: 'text',\n text: 'No active behavioral contracts found in .tribunal/contracts/.',\n },\n ],\n };\n }\n\n const relativePath = path.relative(projectRoot, file).replace(/\\\\/g, '/');\n const allViolations = [];\n\n for (const contract of contracts) {\n const vList = contractEngine.evaluateContract(contract, relativePath, content);\n if (vList.length > 0) {\n allViolations.push(...vList);\n }\n }\n\n if (allViolations.length === 0) {\n return {\n content: [\n {\n type: 'text',\n text: '✅ Contract check passed. Zero behavioral violations detected.',\n },\n ],\n };\n }\n\n let report = `📜 Contract Verification Results (${allViolations.length} violations):\\n`;\n for (const v of allViolations) {\n report += `● [${v.severity.toUpperCase()}] ${v.contract}: ${v.message}\\n`;\n if (v.line) report += ` Line ${v.line}: ${v.snippet || ''}\\n`;\n }\n\n return { content: [{ type: 'text', text: report }] };\n } catch (e) {\n return {\n content: [{ type: 'text', text: `Contract verification failed: ${e.message}` }],\n };\n }\n }\n\n throw new RpcError(-32601, `Unknown tool: ${toolName}`);\n };\n\n try {\n const result = await withToolTimeout(executeTool, GENERAL_TOOL_TIMEOUT_MS);\n if (reminder) {\n result.content.push({ type: 'text', text: '\\n\\n' + reminder });\n }\n return result;\n } catch (e) {\n if (e.message === 'TOOL_TIMEOUT') {\n const errorMsg = `Error: Tool execution timed out after ${GENERAL_TOOL_TIMEOUT_MS}ms`;\n const result = { content: [{ type: 'text', text: errorMsg }] };\n if (reminder) result.content.push({ type: 'text', text: '\\n\\n' + reminder });\n return result;\n }\n throw e;\n }\n }\n\n throw new RpcError(-32601, `Unknown method: ${req.method}`);\n}\n\nasync function processSingleRequest(req) {\n try {\n const result = await handleRequest(req);\n // If it's a notification, do not send a response\n if (req.id === undefined || req.id === null) {\n return null;\n }\n return { jsonrpc: '2.0', id: req.id, result };\n } catch (e) {\n const code = e && typeof e.code === 'number' ? e.code : -32603;\n const message = e && e.message ? e.message : 'Internal server error';\n if (req.id === undefined || req.id === null) {\n return {\n jsonrpc: '2.0',\n id: null,\n error: { code, message },\n };\n }\n return {\n jsonrpc: '2.0',\n id: req.id,\n error: { code, message },\n };\n }\n}\n\nrl.on('line', async line => {\n if (line.length > 1048576) {\n // 1MB limit\n const errorRes = {\n jsonrpc: '2.0',\n id: null,\n error: { code: -32700, message: 'Parse error: input line too long (exceeds 1MB limit)' },\n };\n console.log(JSON.stringify(errorRes));\n return;\n }\n if (!line.trim()) return;\n\n let req;\n try {\n req = JSON.parse(line);\n } catch (parseErr) {\n // Invalid JSON — send a parse error\n const errorRes = {\n jsonrpc: '2.0',\n id: null,\n error: { code: -32700, message: 'Parse error: ' + parseErr.message },\n };\n console.log(JSON.stringify(errorRes));\n return;\n }\n\n if (Array.isArray(req)) {\n const responses = [];\n for (const singleReq of req) {\n const response = await processSingleRequest(singleReq);\n if (response) {\n responses.push(response);\n }\n }\n if (responses.length > 0) {\n console.log(JSON.stringify(responses));\n }\n } else {\n const response = await processSingleRequest(req);\n if (response) {\n console.log(JSON.stringify(response));\n }\n }\n});\n\nif (process.env.NODE_ENV === 'test') {\n module.exports = {\n handleRequest,\n stripBoilerplate,\n runTribunalAudit,\n };\n}\n"
|
|
19
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"file": "bin/proxy-server.js",
|
|
3
|
+
"riskScore": "Low",
|
|
4
|
+
"blastRadius": 1,
|
|
5
|
+
"imports": {
|
|
6
|
+
"http": [],
|
|
7
|
+
"https": [],
|
|
8
|
+
"fs": [],
|
|
9
|
+
"path": [],
|
|
10
|
+
"./global-store": [
|
|
11
|
+
"initializeGlobalStore",
|
|
12
|
+
"getGlobalAgentPath",
|
|
13
|
+
"GLOBAL_STORE_PATH"
|
|
14
|
+
]
|
|
15
|
+
},
|
|
16
|
+
"dependents": [
|
|
17
|
+
"bin/tk-proxy.js"
|
|
18
|
+
],
|
|
19
|
+
"content": "const http = require('http');\nconst https = require('https');\nconst fs = require('fs');\nconst path = require('path');\nconst globalStore = require('./global-store');\n\nlet proxyServer = null;\n\nfunction getSystemPrompt() {\n const agentPath = globalStore.getGlobalAgentPath();\n const rulesPath = path.join(agentPath, 'rules', 'GEMINI.md');\n\n let rules = 'You are governed by Tribunal Kit rules.';\n if (fs.existsSync(rulesPath)) {\n rules = fs.readFileSync(rulesPath, 'utf8');\n }\n\n return `<tribunal_rules>\\n${rules}\\n</tribunal_rules>\\n`;\n}\n\nfunction injectRulesIntoAnthropicPayload(payloadString) {\n try {\n const payload = JSON.parse(payloadString);\n\n // Inject into Anthropic's system parameter\n const tribunalSystem = getSystemPrompt();\n\n if (payload.system) {\n if (Array.isArray(payload.system)) {\n payload.system.unshift({ type: 'text', text: tribunalSystem });\n } else if (typeof payload.system === 'string') {\n payload.system = tribunalSystem + '\\n' + payload.system;\n }\n } else {\n payload.system = tribunalSystem;\n }\n\n return JSON.stringify(payload);\n } catch (e) {\n console.error('[Tribunal Proxy] Failed to parse payload for injection:', e);\n return payloadString;\n }\n}\n\nfunction startProxyServer(port) {\n return new Promise((resolve, reject) => {\n proxyServer = http.createServer((clientReq, clientRes) => {\n let body = '';\n clientReq.on('data', chunk => {\n body += chunk.toString();\n // 5MB payload limit to prevent memory exhaustion (DoS)\n if (body.length > 5 * 1024 * 1024) {\n console.error('[Tribunal Proxy] Request too large, aborting.');\n clientReq.destroy();\n }\n });\n\n clientReq.on('end', () => {\n let modifiedBody = body;\n\n // Only intercept AI chat completion endpoints\n if (clientReq.url.includes('/v1/messages')) {\n console.log(`[Tribunal Proxy] Intercepting request to ${clientReq.url}`);\n modifiedBody = injectRulesIntoAnthropicPayload(body);\n }\n\n const options = {\n hostname: 'api.anthropic.com',\n port: 443,\n path: clientReq.url,\n method: clientReq.method,\n headers: {\n ...clientReq.headers,\n host: 'api.anthropic.com',\n 'content-length': Buffer.byteLength(modifiedBody),\n },\n };\n\n const proxyReq = https.request(options, proxyRes => {\n clientRes.writeHead(proxyRes.statusCode, proxyRes.headers);\n proxyRes.pipe(clientRes, { end: true });\n });\n\n proxyReq.on('error', err => {\n console.error('[Tribunal Proxy] Proxy request error:', err);\n clientRes.writeHead(500);\n clientRes.end();\n });\n\n proxyReq.write(modifiedBody);\n proxyReq.end();\n });\n });\n\n proxyServer.listen(port, () => {\n console.log(`[Tribunal Proxy] Listening on http://localhost:${port}`);\n resolve(port);\n });\n\n proxyServer.on('error', reject);\n });\n}\n\nfunction stopProxyServer() {\n if (proxyServer) {\n proxyServer.close();\n }\n}\n\nmodule.exports = {\n startProxyServer,\n stopProxyServer,\n};\n"
|
|
20
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"file": "bin/spawn-agent.js",
|
|
3
|
+
"riskScore": "Low",
|
|
4
|
+
"blastRadius": 1,
|
|
5
|
+
"imports": {
|
|
6
|
+
"child_process": []
|
|
7
|
+
},
|
|
8
|
+
"dependents": [
|
|
9
|
+
"bin/tk-proxy.js"
|
|
10
|
+
],
|
|
11
|
+
"content": "const { spawn } = require('child_process');\n\nfunction spawnAgent(agentCommand, proxyPort) {\n const proxyUrl = `http://localhost:${proxyPort}`;\n console.log(`[Tribunal Proxy] Launching ${agentCommand} with proxy ${proxyUrl}`);\n\n // Split command and arguments. e.g. \"claude-code\" or \"npx claude-code\"\n const args = agentCommand.split(' ');\n const command = args.shift();\n\n // Inject proxy environment variables\n // ANTHROPIC_BASE_URL is commonly respected by Anthropic-based CLI tools\n const env = {\n ...process.env,\n ANTHROPIC_BASE_URL: proxyUrl,\n // Note: If using HTTPS_PROXY, it would look like this:\n // HTTPS_PROXY: proxyUrl,\n // NODE_TLS_REJECT_UNAUTHORIZED: \"0\"\n };\n\n const child = spawn(command, args, {\n stdio: 'inherit', // Pass stdin, stdout, stderr directly to the TTY\n env,\n });\n\n child.on('close', code => {\n console.log(`[Tribunal Proxy] ${command} exited with code ${code}`);\n process.exit(code);\n });\n\n child.on('error', err => {\n console.error(`[Tribunal Proxy] Failed to start ${command}:`, err);\n process.exit(1);\n });\n}\n\nmodule.exports = {\n spawnAgent,\n};\n"
|
|
12
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"file": "bin/tk-proxy.js",
|
|
3
|
+
"riskScore": "Low",
|
|
4
|
+
"blastRadius": 0,
|
|
5
|
+
"imports": {
|
|
6
|
+
"./global-store": [
|
|
7
|
+
"initializeGlobalStore",
|
|
8
|
+
"getGlobalAgentPath",
|
|
9
|
+
"GLOBAL_STORE_PATH"
|
|
10
|
+
],
|
|
11
|
+
"./proxy-server": [
|
|
12
|
+
"startProxyServer",
|
|
13
|
+
"stopProxyServer"
|
|
14
|
+
],
|
|
15
|
+
"./spawn-agent": [
|
|
16
|
+
"spawnAgent"
|
|
17
|
+
]
|
|
18
|
+
},
|
|
19
|
+
"dependents": [],
|
|
20
|
+
"content": "#!/usr/bin/env node\n\nconst { initializeGlobalStore } = require('./global-store');\nconst { startProxyServer } = require('./proxy-server');\nconst { spawnAgent } = require('./spawn-agent');\n\nasync function main() {\n const args = process.argv.slice(2);\n\n if (args.length === 0) {\n console.error('Usage: tk-proxy <command>');\n console.error('Example: tk-proxy claude-code');\n process.exit(1);\n }\n\n const targetCommand = args.join(' ');\n\n // 1. Initialize the global rules database\n initializeGlobalStore();\n\n // 2. Start the local API proxy server\n // Port 0 will pick an available random port\n try {\n const port = await startProxyServer(0);\n\n // 3. Spawn the target CLI agent\n spawnAgent(targetCommand, port);\n } catch (e) {\n console.error('[Tribunal Proxy] Fatal error starting proxy:', e);\n process.exit(1);\n }\n}\n\nmain();\n"
|
|
21
|
+
}
|
|
@@ -1,19 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"file": "bin/tribunal-kit.js",
|
|
3
|
-
"
|
|
4
|
-
"
|
|
5
|
-
"blastRadius": 4,
|
|
3
|
+
"riskScore": "High",
|
|
4
|
+
"blastRadius": 5,
|
|
6
5
|
"imports": {
|
|
7
|
-
"fs": [],
|
|
8
6
|
"path": [],
|
|
9
|
-
"
|
|
10
|
-
"
|
|
7
|
+
"../dist/cli.js": [],
|
|
8
|
+
"../dist/utils/version": [],
|
|
9
|
+
"../dist/utils/fs": [],
|
|
10
|
+
"../dist/commands/init": [],
|
|
11
|
+
"../dist/commands/marathon": []
|
|
11
12
|
},
|
|
12
13
|
"dependents": [
|
|
13
14
|
"test/unit/args.test.js",
|
|
14
15
|
"test/unit/copyDir.test.js",
|
|
16
|
+
"test/unit/marathon.test.js",
|
|
15
17
|
"test/unit/selfInstall.test.js",
|
|
16
18
|
"test/unit/semver.test.js"
|
|
17
19
|
],
|
|
18
|
-
"content": "#!/usr/bin/env node\r\n/**\r\n * tribunal-kit CLI (alias: tk)\r\n * \r\n * Commands:\r\n * init — Install .agent/ into target project\r\n * update — Re-install to get latest changes\r\n * status — Check if .agent/ is installed\r\n * learn — Evolve project idioms based on git diffs\r\n * case — Manage Case Law precedents\r\n * hook — Install pre-push git hook\r\n * uninstall — Remove .agent/ from project\r\n * \r\n * Usage:\r\n * npx tribunal-kit init\r\n * npx tribunal-kit init --force\r\n * npx tribunal-kit init --path ./myapp\r\n * npx tribunal-kit init --quiet\r\n * npx tribunal-kit init --dry-run\r\n * tribunal-kit update\r\n * tribunal-kit status\r\n * tribunal-kit uninstall\r\n */\r\n\r\nconst fs = require('fs');\r\nconst path = require('path');\r\nconst https = require('https');\r\nconst { execSync } = require('child_process');\r\n\r\nconst PKG = require(path.resolve(__dirname, '..', 'package.json'));\r\nconst CURRENT_VERSION = PKG.version;\r\n\r\n// ── Colors ───────────────────────────────────────────────\r\nconst C = {\r\n reset: '\\x1b[0m',\r\n bold: '\\x1b[1m',\r\n dim: '\\x1b[2m',\r\n red: '\\x1b[91m',\r\n green: '\\x1b[92m',\r\n yellow: '\\x1b[93m',\r\n blue: '\\x1b[94m',\r\n magenta: '\\x1b[95m',\r\n cyan: '\\x1b[96m',\r\n white: '\\x1b[97m',\r\n gray: '\\x1b[90m',\r\n bgCyan: '\\x1b[46m',\r\n};\r\n\r\nfunction colorize(color, text) {\r\n return `${C[color]}${text}${C.reset}`;\r\n}\r\n\r\nfunction c(color, text) { return `${C[color]}${text}${C.reset}`; }\r\nfunction bold(text) { return `${C.bold}${text}${C.reset}`; }\r\n\r\n// ── Logging ──────────────────────────────────────────────\r\nlet quiet = false;\r\nlet verbose = false;\r\n\r\nfunction log(msg) { if (!quiet) console.log(msg); }\r\nfunction ok(msg) { if (!quiet) console.log(` ${c('green', '✔')} ${msg}`); }\r\nfunction warn(msg) { if (!quiet) console.log(` ${c('yellow', '⚠')} ${msg}`); }\r\nfunction err(msg) { console.error(` ${c('red', '✖')} ${msg}`); }\r\nfunction dim(msg) { if (!quiet) console.log(` ${c('gray', msg)}`); }\r\nfunction dbg(msg) { if (verbose) console.log(` ${c('gray', '⊡')} ${c('gray', msg)}`); }\r\n\r\n// ── Arg Parser ───────────────────────────────────────────\r\nfunction parseArgs(argv) {\r\n const args = { command: null, flags: {} };\r\n const raw = argv.slice(2);\r\n\r\n // First non-flag arg is the command\r\n for (const arg of raw) {\r\n if (!arg.startsWith('--') && !args.command) {\r\n args.command = arg;\r\n continue;\r\n }\r\n if (arg === '--force') { args.flags.force = true; continue; }\r\n if (arg === '--quiet') { args.flags.quiet = true; continue; }\r\n if (arg === '--verbose') { args.flags.verbose = true; continue; }\r\n if (arg === '--dry-run') { args.flags.dryRun = true; continue; }\r\n if (arg === '--minimal') { args.flags.minimal = true; continue; }\r\n if (arg === '--skip-update-check') { args.flags.skipUpdateCheck = true; continue; }\r\n if (arg === '--head') { args.flags.head = true; continue; }\r\n if (arg.startsWith('--path=')) {\r\n args.flags.path = arg.split('=').slice(1).join('=');\r\n }\r\n if (arg === '--path') {\r\n const idx = raw.indexOf('--path');\r\n const nextVal = raw[idx + 1];\r\n if (!nextVal || nextVal.startsWith('--')) {\r\n console.error(` \\x1b[91m✖ --path requires a directory argument\\x1b[0m`);\r\n process.exit(1);\r\n }\r\n args.flags.path = nextVal;\r\n }\r\n if (arg.startsWith('--branch=')) {\r\n args.flags.branch = arg.split('=').slice(1).join('=');\r\n }\r\n }\r\n\r\n return args;\r\n}\r\n\r\n// ── File Utilities ────────────────────────────────────────\r\n\r\n// Core agents to install in --minimal mode\r\nconst CORE_AGENTS = new Set([\r\n 'backend-specialist.md',\r\n 'frontend-specialist.md',\r\n 'database-architect.md',\r\n 'debugger.md',\r\n 'security-auditor.md',\r\n 'logic-reviewer.md',\r\n 'dependency-reviewer.md',\r\n 'type-safety-reviewer.md',\r\n 'performance-reviewer.md',\r\n 'orchestrator.md',\r\n 'explorer-agent.md',\r\n 'project-planner.md',\r\n 'test-engineer.md',\r\n]);\r\n\r\n// Core skills to install in --minimal mode\r\nconst CORE_SKILLS = new Set([\r\n 'clean-code', 'architecture', 'testing-patterns', 'systematic-debugging',\r\n 'frontend-design', 'database-design', 'api-patterns', 'nodejs-best-practices',\r\n 'vulnerability-scanner', 'typescript-advanced', 'python-pro', 'nextjs-react-expert',\r\n 'react-specialist', 'performance-profiling', 'lint-and-validate',\r\n]);\r\n\r\nfunction copyDir(src, dest, dryRun = false, filter = null) {\r\n if (!dryRun) {\r\n fs.mkdirSync(dest, { recursive: true });\r\n }\r\n\r\n const entries = fs.readdirSync(src, { withFileTypes: true });\r\n let count = 0;\r\n\r\n for (const entry of entries) {\r\n // Apply filter if provided (for --minimal mode)\r\n if (filter && !filter(entry.name, src)) {\r\n dbg(` skip: ${entry.name}`);\r\n continue;\r\n }\r\n\r\n const srcPath = path.join(src, entry.name);\r\n const destPath = path.join(dest, entry.name);\r\n\r\n if (entry.isDirectory()) {\r\n count += copyDir(srcPath, destPath, dryRun, filter);\r\n } else {\r\n if (!dryRun) {\r\n fs.cpSync(srcPath, destPath, { force: true });\r\n }\r\n dbg(` copy: ${entry.name}`);\r\n count++;\r\n }\r\n }\r\n\r\n return count;\r\n}\r\n\r\nfunction countDir(dir) {\r\n let count = 0;\r\n const entries = fs.readdirSync(dir, { withFileTypes: true });\r\n for (const e of entries) {\r\n if (e.isDirectory()) count += countDir(path.join(dir, e.name));\r\n else count++;\r\n }\r\n return count;\r\n}\r\n\r\n// ── Version Check & Auto-Update ──────────────────────────\r\n\r\n/**\r\n * Compare two semver strings. Returns:\r\n * 1 if a > b, -1 if a < b, 0 if equal.\r\n */\r\nfunction compareSemver(a, b) {\r\n const pa = a.replace(/^v/, '').split('.').map(Number);\r\n const pb = b.replace(/^v/, '').split('.').map(Number);\r\n for (let i = 0; i < 3; i++) {\r\n const na = pa[i] || 0;\r\n const nb = pb[i] || 0;\r\n if (na > nb) return 1;\r\n if (na < nb) return -1;\r\n }\r\n return 0;\r\n}\r\n\r\n/**\r\n * Fetch the latest version from npm registry.\r\n * Returns the version string (e.g. '4.0.0') or null on failure.\r\n */\r\nfunction fetchLatestVersion() {\r\n return new Promise((resolve) => {\r\n const req = https.get(\r\n 'https://registry.npmjs.org/tribunal-kit/latest',\r\n {\r\n headers: {\r\n 'Accept': 'application/json',\r\n 'User-Agent': `tribunal-kit/${CURRENT_VERSION}`\r\n },\r\n timeout: 5000\r\n },\r\n (res) => {\r\n let data = '';\r\n res.on('data', (chunk) => { data += chunk; });\r\n res.on('end', () => {\r\n try {\r\n const json = JSON.parse(data);\r\n const version = json.version || null;\r\n resolve(version);\r\n } catch {\r\n resolve(null);\r\n }\r\n });\r\n }\r\n );\r\n req.on('error', () => resolve(null));\r\n req.on('timeout', () => { req.destroy(); resolve(null); });\r\n });\r\n}\r\n\r\n/**\r\n * Check for a newer version and re-invoke with @latest if found.\r\n * Uses TK_SKIP_UPDATE_CHECK env var as recursion guard.\r\n * Returns true if a re-invoke happened (caller should exit), false otherwise.\r\n */\r\nasync function autoUpdateCheck(originalArgs) {\r\n // Recursion guard: if we're already a re-invoked process, skip\r\n if (process.env.TK_SKIP_UPDATE_CHECK === '1') {\r\n return false;\r\n }\r\n\r\n const latestVersion = await fetchLatestVersion();\r\n\r\n if (!latestVersion) {\r\n // Network fail — proceed silently with current version\r\n return false;\r\n }\r\n\r\n if (compareSemver(latestVersion, CURRENT_VERSION) <= 0) {\r\n // Already up to date\r\n dim(`Version ${CURRENT_VERSION} is up to date.`);\r\n return false;\r\n }\r\n\r\n // Newer version available — re-invoke\r\n log('');\r\n log(colorize('cyan', ` ⬆ New version available: ${colorize('bold', CURRENT_VERSION)} → ${colorize('bold', latestVersion)}`));\r\n log(colorize('gray', ' Re-invoking with latest version...'));\r\n log('');\r\n\r\n try {\r\n // Build the command pulling from npm registry\r\n const args = originalArgs.join(' ');\r\n const cmd = `npx -y tribunal-kit@${latestVersion} ${args}`;\r\n\r\n execSync(cmd, {\r\n stdio: 'inherit',\r\n env: { ...process.env, TK_SKIP_UPDATE_CHECK: '1' },\r\n });\r\n return true; // Re-invoke succeeded, caller should exit\r\n } catch (e) {\r\n warn(`Auto-update failed: ${e.message}`);\r\n warn('Continuing with current version...');\r\n return false; // Fall through to current version\r\n }\r\n}\r\n\r\n// ── Kit Source Location ───────────────────────────────────\r\nfunction getKitAgent() {\r\n // When installed via npm, the .agent/ folder is next to this script's package\r\n const kitRoot = path.resolve(__dirname, '..');\r\n const agentDir = path.join(kitRoot, '.agent');\r\n\r\n if (!fs.existsSync(agentDir)) {\r\n err(`Kit .agent/ folder not found at: ${agentDir}`);\r\n err('The package may be corrupted. Try: npm install -g tribunal-kit');\r\n process.exit(1);\r\n }\r\n\r\n return agentDir;\r\n}\r\n\r\n// ── Self-Install Guard ────────────────────────────────────\r\n/**\r\n * Returns true if the target directory IS the tribunal-kit package itself.\r\n * This prevents `init --force` / `update` from deleting the package's own files\r\n * when run from inside the project directory.\r\n */\r\nfunction isSelfInstall(targetDir) {\r\n const kitRoot = path.resolve(__dirname, '..');\r\n const resolvedTarget = path.resolve(targetDir);\r\n\r\n // Direct path match\r\n if (resolvedTarget === kitRoot) return true;\r\n\r\n // Check if the target's package.json is this package\r\n const targetPkg = path.join(resolvedTarget, 'package.json');\r\n if (fs.existsSync(targetPkg)) {\r\n try {\r\n const targetName = JSON.parse(fs.readFileSync(targetPkg, 'utf8')).name;\r\n if (targetName === PKG.name) return true;\r\n } catch {\r\n // Unreadable package.json — not a match\r\n }\r\n }\r\n\r\n return false;\r\n}\r\n\r\n// ── Banner ────────────────────────────────────────────────\r\nfunction banner() {\r\n if (quiet) return;\r\n // Big ASCII art (TRIBUNAL-KIT)\r\n const art = String.raw`\r\n████████╗██████╗ ██╗██████╗ ██╗ ██╗███╗ ██╗ █████╗ ██╗ ██╗ ██╗██╗████████╗\r\n╚══██╔══╝██╔══██╗██║██╔══██╗██║ ██║████╗ ██║██╔══██╗██║ ██║ ██╔╝██║╚══██╔══╝\r\n ██║ ██████╔╝██║██████╔╝██║ ██║██╔██╗ ██║███████║██║█████╗█████╔╝ ██║ ██║ \r\n ██║ ██╔══██╗██║██╔══██╗██║ ██║██║╚██╗██║██╔══██║██║╚════╝██╔═██╗ ██║ ██║ \r\n ██║ ██║ ██║██║██████╔╝╚██████╔╝██║ ╚████║██║ ██║███████╗ ██║ ██╗██║ ██║ \r\n ╚═╝ ╚═╝ ╚═╝╚═╝╚═════╝ ╚═════╝ ╚═╝ ╚═══╝╚═╝ ╚═╝╚══════╝ ╚═╝ ╚═╝╚═╝ ╚═╝ `.split('\\n').filter(Boolean);\r\n console.log();\r\n const _maxLen = Math.max(...art.map(line => line.length));\r\n for (const line of art) {\r\n let gradientLine = ' ' + C.bold;\r\n for (let i = 0; i < line.length; i++) {\r\n gradientLine += `\\x1b[38;2;255;22;55m${line[i]}`;\r\n }\r\n gradientLine += C.reset;\r\n log(gradientLine);\r\n }\r\n console.log();\r\n // Subtitle strip\r\n const W = 84;\r\n const sub = 'Anti-Hallucination Agent System';\r\n const sp = Math.max(0, W - sub.length);\r\n const centred = ' '.repeat(Math.floor(sp / 2)) + sub + ' '.repeat(Math.ceil(sp / 2));\r\n const RED_ANSI = '\\x1b[38;2;255;22;55m';\r\n console.log(` ${RED_ANSI}╔${'═'.repeat(W)}╗${C.reset}`);\r\n console.log(` ${RED_ANSI}║${C.reset}${c('gray', centred)}${RED_ANSI}║${C.reset}`);\r\n console.log(` ${RED_ANSI}╚${'═'.repeat(W)}╝${C.reset}`);\r\n console.log();\r\n}\r\n\r\n// ── Commands ──────────────────────────────────────────────\r\nfunction cmdInit(flags) {\r\n const agentSrc = getKitAgent();\r\n const targetDir = flags.path ? path.resolve(flags.path) : process.cwd();\r\n const agentDest = path.join(targetDir, '.agent');\r\n const dryRun = flags.dryRun || false;\r\n\r\n // ── Self-install guard ──────────────────────────────────\r\n if (isSelfInstall(targetDir)) {\r\n err('Cannot run init/update inside the tribunal-kit package itself.');\r\n err(`Target: ${targetDir}`);\r\n err(`Package: ${path.resolve(__dirname, '..')}`);\r\n console.log();\r\n dim('This command is designed to install .agent/ into OTHER projects.');\r\n dim('Run it from the root of the project you want to set up:');\r\n dim(' cd /path/to/your-project');\r\n dim(' npx tribunal-kit init');\r\n console.log();\r\n process.exit(1);\r\n }\r\n // ────────────────────────────────────────────────────────\r\n\r\n // ── Backup / Cleanup ────────────────────────────────────\r\n if (!dryRun && fs.existsSync(agentDest) && flags.force) {\r\n // Backup the existing subdirectories before overwriting\r\n const backupDir = path.join(agentDest, '.backups', `backup-${Date.now()}`);\r\n fs.mkdirSync(backupDir, { recursive: true });\r\n\r\n // PRESERVE_DIRS: user-generated content that must survive updates\r\n const _PRESERVE_DIRS = ['history', 'patterns', 'mcp_config.json'];\r\n const subdirs = ['agents', 'workflows', 'skills', 'scripts', '.shared', 'rules'];\r\n for (const sub of subdirs) {\r\n const subPath = path.join(agentDest, sub);\r\n if (fs.existsSync(subPath)) {\r\n // Copy to backup dir\r\n copyDir(subPath, path.join(backupDir, sub), false);\r\n fs.rmSync(subPath, { recursive: true, force: true });\r\n }\r\n }\r\n log(` ${c('gray', '✦ Backed up existing configurations to .agent/.backups/')}`);\r\n\r\n\r\n }\r\n // ────────────────────────────────────────────────────────\r\n\r\n banner();\r\n\r\n if (dryRun) {\r\n log(colorize('yellow', ' DRY RUN — no files will be written'));\r\n console.log();\r\n }\r\n\r\n // Check target exists\r\n if (!fs.existsSync(targetDir)) {\r\n err(`Target directory not found: ${targetDir}`);\r\n process.exit(1);\r\n }\r\n\r\n // Check if .agent already exists\r\n if (fs.existsSync(agentDest) && !flags.force) {\r\n warn('.agent/ already exists in this project.');\r\n log(` ${c('gray', '▸')} To refresh or update it, run: ${colorize('white', 'tribunal-kit init --force')}`);\r\n log(` ${c('gray', '▸')} Or check status with: ${colorize('cyan', 'tribunal-kit status')}`);\r\n console.log();\r\n process.exit(0);\r\n }\r\n\r\n // Ensure history dirs exist (Case Law + Skill Evolution)\r\n if (!dryRun) {\r\n const caseDir = path.join(agentDest, 'history', 'case-law', 'cases');\r\n const evoDir = path.join(agentDest, 'history', 'skill-evolution');\r\n fs.mkdirSync(caseDir, { recursive: true });\r\n fs.mkdirSync(evoDir, { recursive: true });\r\n const gkCase = path.join(caseDir, '.gitkeep');\r\n const gkEvo = path.join(evoDir, '.gitkeep');\r\n if (!fs.existsSync(gkCase)) fs.writeFileSync(gkCase, '');\r\n if (!fs.existsSync(gkEvo)) fs.writeFileSync(gkEvo, '');\r\n }\r\n\r\n // Count what we're installing\r\n const isMinimal = flags.minimal || false;\r\n if (isMinimal) {\r\n log(` ${c('yellow','⚡')} ${bold('Minimal mode')} — installing core agents and skills only`);\r\n console.log();\r\n }\r\n const totalFiles = countDir(agentSrc);\r\n dbg(`Source: ${agentSrc}`);\r\n dbg(`Target: ${agentDest}`);\r\n dbg(`Total source files: ${totalFiles}`);\r\n log(` ${c('gray','▸')} Scanning ${c('white', String(totalFiles))} files ${c('gray','→')} ${c('gray', agentDest)}`);\r\n\r\n try {\r\n // Build filter for --minimal mode\r\n const minimalFilter = isMinimal ? (name, parentDir) => {\r\n const parentName = path.basename(parentDir);\r\n if (parentName === 'agents') return CORE_AGENTS.has(name);\r\n if (parentName === 'skills') return CORE_SKILLS.has(name);\r\n return true; // everything else passes\r\n } : null;\r\n\r\n const copied = copyDir(agentSrc, agentDest, dryRun, minimalFilter);\r\n\r\n console.log();\r\n if (dryRun) {\r\n ok(`${bold('DRY RUN')} complete — would install ${c('cyan', String(copied))} files`);\r\n dim(`Target: ${agentDest}`);\r\n } else {\r\n // ── Success card — W=62, rows padded by plain-text length ──\r\n const W = 62;\r\n const agentsCount = fs.readdirSync(path.join(agentDest, 'agents')).length;\r\n const workflowsCount = fs.readdirSync(path.join(agentDest, 'workflows')).length;\r\n const skillsCount = fs.readdirSync(path.join(agentDest, 'skills')).length;\r\n const scriptsCount = fs.readdirSync(path.join(agentDest, 'scripts')).length;\r\n\r\n // Stat rows: compute trailing spaces from plain text so right ║ aligns\r\n const statRow = (icon, label, val, col) => {\r\n // emoji JS .length===2 == terminal display width 2 ✓\r\n const plain = ` ${icon} ${label.padEnd(10)}${String(val).padStart(3)} installed`;\r\n const trail = ' '.repeat(Math.max(0, W - plain.length));\r\n return ` ${c('cyan','║')} ${icon} ${c('white',label.padEnd(10))}${c(col,String(val).padStart(3))} ${c('gray','installed')}${trail}${c('cyan','║')}`;\r\n };\r\n // Plain-text rows (header / blank)\r\n const plainRow = (text, wrapFn) => {\r\n const trail = ' '.repeat(Math.max(0, W - text.length));\r\n return ` ${c('cyan','║')}${wrapFn(text)}${trail}${c('cyan','║')}`;\r\n };\r\n // Next-step rows: fixed cmd column + description\r\n const stepRow = (cmd, desc) => {\r\n const plain = ` ${cmd.padEnd(16)}${desc}`;\r\n const trail = ' '.repeat(Math.max(0, W - plain.length));\r\n return ` ${c('cyan','║')} ${c('white',cmd.padEnd(16))}${c('gray',desc)}${trail}${c('cyan','║')}`;\r\n };\r\n\r\n console.log(` ${c('green','✔')} ${bold(c('green','Installation complete'))} ${c('gray','—')} ${c('white',String(copied))} files`);\r\n console.log(` ${c('gray',' ╰─')} ${c('gray', agentDest)}`);\r\n console.log();\r\n console.log(` ${c('cyan', '╔' + '═'.repeat(W) + '╗')}`);\r\n console.log(plainRow(` What's inside:`, s => c('bold', c('white', s))));\r\n console.log(` ${c('cyan', '╠' + '═'.repeat(W) + '╣')}`);\r\n console.log(statRow('🤖', 'Agents', agentsCount, 'magenta'));\r\n console.log(statRow('⚡', 'Workflows', workflowsCount, 'yellow'));\r\n console.log(statRow('🧠', 'Skills', skillsCount, 'blue'));\r\n console.log(statRow('🔧', 'Scripts', scriptsCount, 'green'));\r\n console.log(` ${c('cyan', '╠' + '═'.repeat(W) + '╣')}`);\r\n console.log(plainRow('', () => ''));\r\n console.log(plainRow(` Next steps:`, s => c('gray', s)));\r\n console.log(stepRow('/generate', 'Generate code with anti-hallucination'));\r\n console.log(stepRow('/review', 'Audit existing code for issues'));\r\n console.log(stepRow('/tribunal-full', 'Run all 16 reviewers in parallel'));\r\n console.log(plainRow('', () => ''));\r\n console.log(` ${c('cyan', '╚' + '═'.repeat(W) + '╝')}`);\r\n console.log();\r\n log(` ${c('gray', '✦ Generating IDE bridge files...')}`);\r\n generateIDEBridges(targetDir, agentDest, dryRun);\r\n }\r\n\r\n console.log();\r\n } catch (e) {\r\n err(`Failed to install: ${e.message}`);\r\n process.exit(1);\r\n }\r\n}\r\n\r\n// ── IDE Bridge Files ──────────────────────────────────────\r\n// Each AI IDE reads rules from a different location.\r\n// We generate bridge files that point each IDE at .agent/\r\nfunction generateIDEBridges(targetDir, agentDest, dryRun = false) {\r\n const rulesFile = path.join(agentDest, 'rules', 'GEMINI.md');\r\n let rulesContent = '';\r\n if (fs.existsSync(rulesFile)) {\r\n rulesContent = fs.readFileSync(rulesFile, 'utf8');\r\n }\r\n\r\n // Helper: write a bridge file only if it doesn't already exist\r\n const writeBridge = (filePath, content, label) => {\r\n if (dryRun) {\r\n dbg(` would create: ${filePath}`);\r\n return;\r\n }\r\n const dir = path.dirname(filePath);\r\n if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });\r\n if (fs.existsSync(filePath)) {\r\n dbg(` skip (exists): ${path.basename(filePath)}`);\r\n return;\r\n }\r\n fs.writeFileSync(filePath, content, 'utf8');\r\n ok(`${label} → ${c('gray', path.relative(targetDir, filePath))}`);\r\n };\r\n\r\n // ── 1. Cursor (.cursorrules) ──────────────────────────\r\n const cursorRules = `# Tribunal Kit — Cursor Bridge\r\n# Auto-generated by tribunal-kit init. Do not edit manually.\r\n# Source: .agent/rules/GEMINI.md\r\n\r\n${rulesContent}\r\n`;\r\n writeBridge(\r\n path.join(targetDir, '.cursorrules'),\r\n cursorRules,\r\n 'Cursor'\r\n );\r\n\r\n // ── 2. Windsurf (.windsurfrules) ─────────────────────\r\n const windsurfRules = `# Tribunal Kit — Windsurf Bridge\r\n# Auto-generated by tribunal-kit init. Do not edit manually.\r\n# Source: .agent/rules/GEMINI.md\r\n\r\n${rulesContent}\r\n`;\r\n writeBridge(\r\n path.join(targetDir, '.windsurfrules'),\r\n windsurfRules,\r\n 'Windsurf'\r\n );\r\n\r\n // ── 3. Gemini / Antigravity (.gemini/settings.json) ──\r\n const geminiSettings = JSON.stringify({\r\n \"$schema\": \"https://raw.githubusercontent.com/anthropics/anthropic-cookbook/main/.gemini/settings.schema.json\",\r\n \"rules\": [\r\n { \"path\": \"../.agent/rules/GEMINI.md\", \"trigger\": \"always_on\" }\r\n ],\r\n \"agents\": { \"directory\": \"../.agent/agents\" },\r\n \"skills\": { \"directory\": \"../.agent/skills\" },\r\n \"workflows\": { \"directory\": \"../.agent/workflows\" }\r\n }, null, 2) + '\\n';\r\n writeBridge(\r\n path.join(targetDir, '.gemini', 'settings.json'),\r\n geminiSettings,\r\n 'Gemini/Antigravity'\r\n );\r\n\r\n // ── Also create .gemini/GEMINI.md as a direct rules file ──\r\n const geminiRulesBridge = `---\r\ntrigger: always_on\r\n---\r\n\r\n# Tribunal Kit — Gemini Bridge\r\n# Auto-generated by tribunal-kit init.\r\n# Full rules: .agent/rules/GEMINI.md\r\n\r\n${rulesContent}\r\n`;\r\n writeBridge(\r\n path.join(targetDir, '.gemini', 'GEMINI.md'),\r\n geminiRulesBridge,\r\n 'Gemini rules'\r\n );\r\n\r\n // ── 4. GitHub Copilot (.github/copilot-instructions.md) ──\r\n const copilotInstructions = `# Tribunal Kit — Copilot Bridge\r\n# Auto-generated by tribunal-kit init. Do not edit manually.\r\n# Source: .agent/rules/GEMINI.md\r\n\r\n${rulesContent}\r\n`;\r\n writeBridge(\r\n path.join(targetDir, '.github', 'copilot-instructions.md'),\r\n copilotInstructions,\r\n 'GitHub Copilot'\r\n );\r\n\r\n // ── 5. Claude (.claude/CLAUDE.md) ─────────────────────\r\n const claudeRules = `# Tribunal Kit — Claude Bridge\r\n# Auto-generated by tribunal-kit init. Do not edit manually.\r\n# Source: .agent/rules/GEMINI.md\r\n\r\n${rulesContent}\r\n`;\r\n writeBridge(\r\n path.join(targetDir, '.claude', 'CLAUDE.md'),\r\n claudeRules,\r\n 'Claude'\r\n );\r\n\r\n console.log();\r\n}\r\n\r\nfunction cmdUpdate(flags) {\r\n // ── Self-install guard (early, before banner) ───────────\r\n const targetDir = flags.path ? path.resolve(flags.path) : process.cwd();\r\n if (isSelfInstall(targetDir)) {\r\n err('Cannot run update inside the tribunal-kit package itself.');\r\n err(`Target: ${targetDir}`);\r\n console.log();\r\n dim('This command is designed to update .agent/ in OTHER projects.');\r\n dim('Run it from the root of the project you want to update:');\r\n dim(' cd /path/to/your-project');\r\n dim(' npx tribunal-kit update');\r\n console.log();\r\n process.exit(1);\r\n }\r\n // ────────────────────────────────────────────────────────\r\n\r\n // Update = init with --force\r\n flags.force = true;\r\n if (!quiet) {\r\n log(` ${c('cyan','↻')} ${bold('Updating')} ${c('white','.agent/')} to latest version...`);\r\n console.log();\r\n }\r\n cmdInit(flags);\r\n}\r\n\r\n\r\nfunction cmdLearn(flags) {\r\n const targetDir = flags.path ? path.resolve(flags.path) : process.cwd();\r\n const agentDest = path.join(targetDir, '.agent');\r\n\r\n if (!fs.existsSync(agentDest)) {\r\n err('.agent/ not found. Run: npx tribunal-kit init');\r\n process.exit(1);\r\n }\r\n\r\n banner();\r\n\r\n const W = 62;\r\n const title = ' Tribunal Learn — Supreme Court Mode';\r\n const trail = ' '.repeat(Math.max(0, W - title.length));\r\n console.log(` ${c('cyan', '\\u2554' + '\\u2550'.repeat(W) + '\\u2557')}`);\r\n console.log(` ${c('cyan', '\\u2551')}${c('bold', c('white', title))}${trail}${c('cyan', '\\u2551')}`);\r\n console.log(` ${c('cyan', '\\u255a' + '\\u2550'.repeat(W) + '\\u255d')}`);\r\n console.log();\r\n\r\n const dryRun = flags.dryRun ? '--dry-run' : '';\r\n const useHead = flags.head ? '--head' : '';\r\n\r\n\r\n // Phase 1: Skill Evolution\r\n log(` ${c('cyan', '\\u229b')} ${bold('Phase 1')} \\u2014 Skill Evolution Forge (auto-generating project idioms)`);\r\n const evoScript = path.join(agentDest, 'scripts', 'skill_evolution.js');\r\n if (!fs.existsSync(evoScript)) {\r\n warn('skill_evolution.js not found \\u2014 run: npx tribunal-kit update');\r\n } else {\r\n try {\r\n const cmd = `node \"${evoScript}\" digest ${dryRun} ${useHead}`.trim();\r\n execSync(cmd, { stdio: 'inherit', cwd: targetDir });\r\n } catch (e) {\r\n warn(`Skill Evolution error: ${e.message}`);\r\n }\r\n }\r\n\r\n console.log();\r\n\r\n // Phase 2: Case Law prompt\r\n log(` ${c('cyan', '\\u229b')} ${bold('Phase 2')} \\u2014 Case Law Engine (building precedence record)`);\r\n console.log();\r\n log(` ${c('gray','\\u25b8')} Record a new rejection precedent:`);\r\n log(` ${c('white', 'npx tribunal-kit case add')}`);\r\n console.log();\r\n log(` ${c('gray','\\u25b8')} Search existing case law:`);\r\n log(` ${c('white', 'npx tribunal-kit case search \"your query\"')}`);\r\n console.log();\r\n log(` ${c('green', '\\u2714')} ${bold('Learn cycle complete.')} Your Tribunal grows smarter with every commit.`);\r\n console.log();\r\n}\r\n\r\n// ── Async Main Wrapper ───────────────────────────────────\r\nasync function runWithUpdateCheck(command, flags) {\r\n const shouldSkip = flags.skipUpdateCheck || process.env.TK_SKIP_UPDATE_CHECK === '1';\r\n\r\n if (!shouldSkip && (command === 'init' || command === 'update')) {\r\n // Pass through the original args (minus the node/script path)\r\n const originalArgs = process.argv.slice(2);\r\n const didReInvoke = await autoUpdateCheck(originalArgs);\r\n if (didReInvoke) {\r\n process.exit(0); // Latest version handled it\r\n }\r\n }\r\n\r\n // Proceed with current version\r\n switch (command) {\r\n case 'init':\r\n cmdInit(flags);\r\n break;\r\n case 'update':\r\n cmdUpdate(flags);\r\n break;\r\n case 'status':\r\n cmdStatus(flags);\r\n break;\r\n case 'learn':\r\n cmdLearn(flags);\r\n break;\r\n case 'case':\r\n cmdCase(flags);\r\n break;\r\n case 'hook':\r\n cmdHook(flags);\r\n break;\r\n case 'graph':\r\n cmdGraph(flags);\r\n break;\r\n case 'mutate':\r\n cmdMutate(flags);\r\n break;\r\n case 'context':\r\n cmdContext(flags);\r\n break;\r\n case 'uninstall':\r\n cmdUninstall(flags);\r\n break;\r\n case 'help':\r\n case '--help':\r\n case '-h':\r\n case null:\r\n cmdHelp();\r\n break;\r\n default:\r\n err(`Unknown command: \"${command}\"`);\r\n console.log();\r\n dim('Run tribunal-kit --help for usage');\r\n process.exit(1);\r\n }\r\n}\r\n\r\nfunction cmdCase(flags) {\r\n const targetDir = flags.path ? path.resolve(flags.path) : process.cwd();\r\n const agentDest = path.join(targetDir, '.agent');\r\n\r\n if (!fs.existsSync(agentDest)) {\r\n err('.agent/ not found. Run: npx tribunal-kit init');\r\n process.exit(1);\r\n }\r\n\r\n const args = process.argv.slice(3).join(' ');\r\n if (!args || args === 'help' || args === '--help' || args === '-h') {\r\n banner();\r\n log(` ${c('cyan', '\\u2554' + '\\u2550'.repeat(60) + '\\u2557')}`);\r\n log(` ${c('cyan', '\\u2551')}${c('bold', c('white', ' Tribunal Case Law Engine \\u2014 Supreme Court '))}${c('cyan', '\\u2551')}`);\r\n log(` ${c('cyan', '\\u255a' + '\\u2550'.repeat(60) + '\\u255d')}`);\r\n console.log();\r\n log(` ${c('cyan', 'add'.padEnd(10))} ${c('gray', 'Record a new Case Law rejection pattern')}`);\r\n log(` ${c('cyan', 'search'.padEnd(10))} ${c('gray', 'Search existing cases (e.g., search \"query\")')}`);\r\n log(` ${c('cyan', 'list'.padEnd(10))} ${c('gray', 'List all recorded case law')}`);\r\n log(` ${c('cyan', 'show'.padEnd(10))} ${c('gray', 'Show full diff for a case (e.g., show --id 1)')}`);\r\n log(` ${c('cyan', 'stats'.padEnd(10))} ${c('gray', 'Show case law stats by domain/verdict')}`);\r\n log(` ${c('cyan', 'export'.padEnd(10))} ${c('gray', 'Export all cases to Markdown')}`);\r\n log(` ${c('cyan', 'overrule'.padEnd(10))} ${c('gray', 'Overrule a past precedent (e.g., overrule --id 1)')}`);\r\n console.log();\r\n process.exit(1);\r\n }\r\n\r\n const caseLawScript = path.join(agentDest, 'scripts', 'case_law_manager.js');\r\n\r\n // Make shorthand aliases\r\n let pyArgs = args;\r\n if (pyArgs.startsWith('add')) pyArgs = pyArgs.replace(/^add/, 'add-case');\r\n if (pyArgs.startsWith('search')) pyArgs = pyArgs.replace(/^search/, 'search-cases');\r\n\r\n try {\r\n const { execSync } = require('child_process');\r\n execSync(`node \"${caseLawScript}\" ${pyArgs}`, { stdio: 'inherit', cwd: targetDir });\r\n } catch {\r\n process.exit(1); // Script already prints errors\r\n }\r\n}\r\n\r\nfunction cmdGraph(flags) {\r\n const targetDir = flags.path ? path.resolve(flags.path) : process.cwd();\r\n const agentDest = path.join(targetDir, '.agent');\r\n\r\n if (!fs.existsSync(agentDest)) {\r\n err('.agent/ not found. Run: npx tribunal-kit init');\r\n process.exit(1);\r\n }\r\n\r\n banner();\r\n const { execSync } = require('child_process');\r\n const builderScript = path.join(agentDest, 'scripts', 'graph_builder.js');\r\n const visualizerScript = path.join(agentDest, 'scripts', 'graph_visualizer.js');\r\n const htmlFile = path.join(agentDest, 'history', 'architecture-explorer.html');\r\n\r\n try {\r\n execSync(`node \"${builderScript}\"`, { stdio: 'inherit', cwd: targetDir });\r\n execSync(`node \"${visualizerScript}\"`, { stdio: 'inherit', cwd: targetDir });\r\n \r\n log(` ${c('cyan', '▸')} Opening visualizer in browser...`);\r\n const opener = process.platform === 'win32' ? 'start' : process.platform === 'darwin' ? 'open' : 'xdg-open';\r\n execSync(`${opener} \"${htmlFile}\"`);\r\n } catch (e) {\r\n err(`Graph generation failed: ${e.message}`);\r\n process.exit(1);\r\n }\r\n}\r\n\r\nfunction cmdHook(flags) {\r\n const targetDir = flags.path ? path.resolve(flags.path) : process.cwd();\r\n const gitDir = path.join(targetDir, '.git');\r\n \r\n if (!fs.existsSync(gitDir)) {\r\n err('Not a git repository. Cannot install git hooks here.');\r\n process.exit(1);\r\n }\r\n \r\n const hooksDir = path.join(gitDir, 'hooks');\r\n if (!fs.existsSync(hooksDir)) {\r\n fs.mkdirSync(hooksDir, { recursive: true });\r\n }\r\n \r\n const prePushPath = path.join(hooksDir, 'pre-push');\r\n const hookScript = `#!/bin/sh\\n# Supreme Court - Auto Learn on Push\\necho \"⚖️ Tribunal Supreme Court: Evolving Skills...\"\\nnpx tribunal-kit learn --head\\n`;\r\n \r\n fs.writeFileSync(prePushPath, hookScript, { mode: 0o755 });\r\n \r\n console.log();\r\n log(` ${c('green', '✔')} Installed pre-push git hook.`);\r\n log(` ${c('gray', '▸')} Skill Evolution will now run automatically every time you git push.`);\r\n console.log();\r\n}\r\n\r\nfunction cmdMutate(flags) {\r\n const targetDir = flags.path ? path.resolve(flags.path) : process.cwd();\r\n const agentDest = path.join(targetDir, '.agent');\r\n\r\n if (!fs.existsSync(agentDest)) {\r\n err('.agent/ not found. Run: npx tribunal-kit init');\r\n process.exit(1);\r\n }\r\n\r\n const args = process.argv.slice(3);\r\n if (args.length < 2) {\r\n err('Usage: npx tribunal-kit mutate <target_file> <test_command>');\r\n process.exit(1);\r\n }\r\n\r\n const mutateScript = path.join(agentDest, 'scripts', 'mutation_runner.js');\r\n const { execSync } = require('child_process');\r\n try {\r\n execSync(`node \"${mutateScript}\" ${args.join(' ')}`, { stdio: 'inherit', cwd: targetDir });\r\n } catch {\r\n process.exit(1);\r\n }\r\n}\r\n\r\nfunction cmdUninstall(flags) {\r\n const targetDir = flags.path ? path.resolve(flags.path) : process.cwd();\r\n const agentDest = path.join(targetDir, '.agent');\r\n\r\n banner();\r\n\r\n if (!fs.existsSync(agentDest)) {\r\n log(` ${c('yellow','⚠')} ${bold('.agent/')} is not installed in this project.`);\r\n console.log();\r\n return;\r\n }\r\n\r\n if (flags.dryRun) {\r\n log(colorize('yellow', ' DRY RUN — would remove:'));\r\n log(` ${c('gray',' ╰─')} ${agentDest}`);\r\n console.log();\r\n return;\r\n }\r\n\r\n try {\r\n fs.rmSync(agentDest, { recursive: true, force: true });\r\n log(` ${c('green','✔')} ${bold('.agent/')} has been removed from this project.`);\r\n console.log();\r\n log(` ${c('gray','▸')} To reinstall: ${c('cyan','npx tribunal-kit init')}`);\r\n console.log();\r\n } catch (e) {\r\n err(`Failed to remove .agent/: ${e.message}`);\r\n process.exit(1);\r\n }\r\n}\r\n\r\nfunction cmdStatus(flags) {\r\n const targetDir = flags.path ? path.resolve(flags.path) : process.cwd();\r\n const agentDest = path.join(targetDir, '.agent');\r\n\r\n banner();\r\n\r\n if (!fs.existsSync(agentDest)) {\r\n log(` ${c('red','✖')} ${bold('Not installed')} in this project`);\r\n console.log();\r\n log(` ${c('gray','Run:')} ${c('cyan','npx tribunal-kit init')}`);\r\n console.log();\r\n return;\r\n }\r\n\r\n log(` ${c('green','✔')} ${bold(c('green','Installed'))} ${c('gray','→')} ${c('gray', agentDest)}`);\r\n console.log();\r\n\r\n const icons = { agents: '🤖', workflows: '⚡', skills: '🧠', scripts: '🔧' };\r\n const colors = { agents: 'magenta', workflows: 'yellow', skills: 'blue', scripts: 'green' };\r\n const subdirs = ['agents', 'workflows', 'skills', 'scripts'];\r\n for (const sub of subdirs) {\r\n const subPath = path.join(agentDest, sub);\r\n if (fs.existsSync(subPath)) {\r\n const count = fs.readdirSync(subPath).filter(f => !fs.statSync(path.join(subPath, f)).isDirectory()).length;\r\n log(` ${icons[sub]} ${c(colors[sub], sub.padEnd(12))}${c('white', String(count).padStart(3))} files`);\r\n }\r\n }\r\n console.log();\r\n}\r\n\r\nfunction cmdHelp() {\r\n banner();\r\n const cmd = (name, desc) => ` ${c('cyan', name.padEnd(10))} ${c('gray', desc)}`;\r\n const opt = (flag, desc) => ` ${c('yellow', flag.padEnd(22))} ${c('gray', desc)}`;\r\n const ex = (s) => ` ${c('gray', '▸')} ${c('white', s)}`;\r\n\r\n log(bold(' Commands'));\r\n log(` ${c('gray','─'.repeat(40))}`);\r\n log(cmd('init', 'Install .agent/ into current project'));\r\n log(cmd('update', 'Re-install to get latest version'));\r\n log(cmd('status', 'Check if .agent/ is installed'));\r\n log(cmd('learn', 'Evolve project idioms based on git diffs'));\r\n log(cmd('case', 'Manage Case Law precedents (add, search, list, show, stats, overrule)'));\r\n log(cmd('graph', 'Build and visualize the architecture graph'));\r\n log(cmd('mutate', 'Run the Mutation Engine to test test-suite reliability'));\r\n log(cmd('context', 'Retrieve a highly-optimized Context Snapshot for a file'));\r\n log(cmd('hook', 'Install pre-push git hook for auto-learning'));\r\n log(cmd('uninstall','Remove .agent/ folder from project'));\r\n console.log();\r\n log(bold(' Options'));\r\n log(` ${c('gray','─'.repeat(40))}`);\r\n log(opt('--force', 'Overwrite existing .agent/ folder'));\r\n log(opt('--path <dir>', 'Install in specific directory'));\r\n log(opt('--quiet', 'Suppress all output'));\r\n log(opt('--verbose', 'Show detailed debug logging'));\r\n log(opt('--dry-run', 'Preview actions without executing'));\r\n log(opt('--minimal', 'Install core agents/skills only (~13 agents)'));\r\n log(opt('--skip-update-check', 'Skip auto-update version check'));\r\n log(opt('--head', '(learn) Diff against last commit instead of staged'));\r\n console.log();\r\n log(bold(' Aliases'));\r\n log(` ${c('gray','─'.repeat(40))}`);\r\n log(` ${c('cyan', 'tk')} ${c('gray', 'Shorthand for tribunal-kit (e.g., tk init, tk status)')}`);\r\n console.log();\r\n log(bold(' Examples'));\r\n log(` ${c('gray','─'.repeat(40))}`);\r\n log(ex('npx tribunal-kit init'));\r\n log(ex('tk init --force'));\r\n log(ex('tk init --path ./my-app'));\r\n log(ex('npx tribunal-kit init --dry-run'));\r\n log(ex('tk update'));\r\n log(ex('tk status'));\r\n log(ex('tk learn'));\r\n log(ex('tk learn --dry-run'));\r\n log(ex('tk learn --head'));\r\n log(ex('tk case add'));\r\n log(ex('tk case search \"useEffect\"'));\r\n log(ex('tk case list'));\r\n log(ex('tk case show --id 1'));\r\n log(ex('tk case stats'));\r\n log(ex('tk case export'));\r\n log(ex('tk case overrule --id 1'));\r\n log(ex('tk graph'));\r\n log(ex('tk mutate src/utils.js \"npm test\"'));\r\n log(ex('tk hook'));\r\n log(ex('tk uninstall'));\r\n console.log();\r\n}\r\n\r\n\r\nfunction cmdContext(flags) {\r\n const targetDir = flags.path ? path.resolve(flags.path) : process.cwd();\r\n const agentDest = path.join(targetDir, '.agent');\r\n \r\n if (!fs.existsSync(agentDest)) {\r\n err('.agent/ not found. Run: npx tribunal-kit init');\r\n process.exit(1);\r\n }\r\n\r\n const args = process.argv.slice(3);\r\n if (args.length === 0 || args[0] === 'help' || args[0] === '--help') {\r\n console.error('Usage: npx tribunal-kit context <target_file>');\r\n process.exit(1);\r\n }\r\n\r\n const targetFile = args[0].replace(/\\\\/g, '/');\r\n const snapshotName = targetFile.replace(/[\\\\\\/]/g, '__') + '.json';\r\n const snapshotPath = require('path').join(agentDest, 'history', 'snapshots', snapshotName);\r\n\r\n if (!require('fs').existsSync(snapshotPath)) {\r\n console.error(' \\x1b[91m✖\\x1b[0m Context Snapshot not found for: ' + targetFile);\r\n console.log(' Run: npx tribunal-kit graph (to generate snapshots)');\r\n process.exit(1);\r\n }\r\n\r\n try {\r\n const snapshot = JSON.parse(require('fs').readFileSync(snapshotPath, 'utf8'));\r\n \r\n console.log('\\n# Context Snapshot: ' + snapshot.file);\r\n process.stdout.write('> Size Estimate: ' + (snapshot['estimatedTokens'] || 'Unknown') + '\\n');\r\n console.log('> Risk Score: ' + snapshot.riskScore + ' (Blast Radius: ' + snapshot.blastRadius + ')\\n');\r\n \r\n if (Object.keys(snapshot.imports).length > 0) {\r\n console.log('## Imports');\r\n for (const [imp, exports] of Object.entries(snapshot.imports)) {\r\n if (exports && exports.length > 0) {\r\n console.log('- `' + imp + '` (exports: ' + exports.join(', ') + ')');\r\n } else {\r\n console.log('- `' + imp + '`');\r\n }\r\n }\r\n console.log();\r\n }\r\n\r\n if (snapshot.dependents && snapshot.dependents.length > 0) {\r\n console.log('## Dependents');\r\n for (const dep of snapshot.dependents) {\r\n console.log('- `' + dep + '`');\r\n }\r\n console.log();\r\n }\r\n\r\n console.log('## Source Code');\r\n console.log('```javascript\\n' + snapshot.content + '\\n```\\n');\r\n \r\n } catch (e) {\r\n console.error('Failed to read snapshot: ' + e.message);\r\n process.exit(1);\r\n }\r\n}\r\n\r\n// ── Main ──────────────────────────────────────────────────\r\nconst { command, flags } = parseArgs(process.argv);\r\n\r\nif (flags.quiet) quiet = true;\r\nif (flags.verbose) verbose = true;\r\n\r\nrunWithUpdateCheck(command, flags);\r\n\r\n// -- Exports (for testing) -- do not remove\r\nif (require.main !== module) {\r\n module.exports = { parseArgs, compareSemver, copyDir, countDir, isSelfInstall, CORE_AGENTS, CORE_SKILLS, generateIDEBridges };\r\n}\r\n"
|
|
20
|
+
"content": "#!/usr/bin/env node\n/**\n * tribunal-kit CLI (alias: tk)\n *\n * Commands:\n * init — Install .agent/ into target project\n * update — Re-install to get latest changes\n * status — Check if .agent/ is installed\n * learn — Evolve project idioms based on git diffs\n * case — Manage Case Law precedents\n * hook — Install pre-push git hook\n * uninstall — Remove .agent/ from project\n * align — Clean AI outputs & enforce guardrails\n * compile — Compile rules for terminal agents\n * memory — 4-Type Taxonomy Persistent Memory Engine\n * guardrail — Validate .agent/ integrity\n *\n * Usage:\n * npx tribunal-kit init\n * npx tribunal-kit init --force\n * npx tribunal-kit init --path ./myapp\n * npx tribunal-kit init --quiet\n * npx tribunal-kit init --dry-run\n * tribunal-kit update\n * tribunal-kit status\n * tribunal-kit uninstall\n */\n\n'use strict';\n\nconst path = require('path');\n\n// Delegate core execution to the modular dist/ entry point\nconst { main } = require('../dist/cli.js');\n\n// Utilities re-exported for backwards compatibility with tests\nconst { compareSemver } = require('../dist/utils/version');\nconst { copyDir, countDir, isSelfInstall: _isSelfInstall } = require('../dist/utils/fs');\nconst { CORE_AGENTS, CORE_SKILLS, generateIDEBridges } = require('../dist/commands/init');\nconst { cmdMarathon: _cmdMarathon } = require('../dist/commands/marathon');\n\nfunction cmdMarathon(flags, processArgs = process.argv) {\n return _cmdMarathon(flags, processArgs, flags?.quiet || false);\n}\n\nconst PKG = require(path.resolve(__dirname, '..', 'package.json'));\n\n/**\n * Returns true if the target directory IS the tribunal-kit package itself.\n */\nfunction isSelfInstall(targetDir) {\n const kitRoot = path.resolve(__dirname, '..');\n return _isSelfInstall(targetDir, PKG.name, kitRoot);\n}\n\n/**\n * CLI Argument Parser for backward compatibility with unit tests.\n */\nfunction parseArgs(argv) {\n const args = { command: null, flags: {} };\n const raw = argv.slice(2);\n\n for (let i = 0; i < raw.length; i++) {\n const arg = raw[i];\n if (!arg.startsWith('--') && !arg.startsWith('-') && !args.command) {\n args.command = arg;\n continue;\n }\n if (arg === '--force') args.flags.force = true;\n else if (arg === '--quiet') args.flags.quiet = true;\n else if (arg === '--verbose') args.flags.verbose = true;\n else if (arg === '--dry-run') args.flags.dryRun = true;\n else if (arg === '--minimal') args.flags.minimal = true;\n else if (arg === '--token-optimized') args.flags.tokenOptimized = true;\n else if (arg === '--skip-update-check') args.flags.skipUpdateCheck = true;\n else if (arg === '--head') args.flags.head = true;\n else if (arg.startsWith('--path=')) {\n args.flags.path = arg.split('=').slice(1).join('=');\n } else if (arg === '--path' && raw[i + 1] && !raw[i + 1].startsWith('-')) {\n args.flags.path = raw[++i];\n } else if (arg.startsWith('--branch=')) {\n args.flags.branch = arg.split('=').slice(1).join('=');\n } else if (arg.startsWith('--log=')) {\n args.flags.log = arg.split('=').slice(1).join('=');\n } else if (arg === '--log' && raw[i + 1] && !raw[i + 1].startsWith('-')) {\n args.flags.log = raw[++i];\n } else if (arg.startsWith('--strategy=')) {\n args.flags.strategy = arg.split('=').slice(1).join('=');\n } else if (arg === '--strategy' && raw[i + 1] && !raw[i + 1].startsWith('-')) {\n args.flags.strategy = raw[++i];\n }\n }\n\n return args;\n}\n\n// Execute CLI when run directly\nif (require.main === module) {\n main();\n}\n\n// Module exports for unit test suite backward compatibility\nmodule.exports = {\n parseArgs,\n compareSemver,\n copyDir,\n countDir,\n isSelfInstall,\n CORE_AGENTS,\n CORE_SKILLS,\n generateIDEBridges,\n cmdMarathon,\n};\n"
|
|
19
21
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"file": "bin/wrapper.js",
|
|
3
|
+
"riskScore": "Low",
|
|
4
|
+
"blastRadius": 0,
|
|
5
|
+
"imports": {
|
|
6
|
+
"fs": [],
|
|
7
|
+
"path": [],
|
|
8
|
+
"child_process": [],
|
|
9
|
+
"os": [],
|
|
10
|
+
"../dist/cli.js": []
|
|
11
|
+
},
|
|
12
|
+
"dependents": [],
|
|
13
|
+
"content": "#!/usr/bin/env node\n/**\n * Tribunal-Kit Core Wrapper\n *\n * This script routes commands to the ultra-fast Rust binary if available and supported.\n * For legacy commands (or if the binary isn't available/compiled yet), it gracefully\n * falls back to the original JavaScript implementation.\n */\n\nconst fs = require('fs');\nconst path = require('path');\nconst { spawnSync } = require('child_process');\nconst os = require('os');\n\n// Commands that have been fully ported to Rust so far\nconst RUST_COMMANDS = new Set([\n 'init',\n 'validate',\n 'status',\n 'sync',\n 'hook',\n 'uninstall',\n 'memory',\n 'min-context',\n 'dag-schedule',\n 'context-compress',\n 'context-broker',\n 'optimize-step',\n 'impact-tier',\n]);\n\n// Determine the path to the compiled Rust binary\n// In a full production release, this checks optionalDependencies in node_modules\n// For development, it checks the local target/release folder\nfunction getBinaryPath() {\n if (process.env.TRIBUNAL_FORCE_JS === '1') {\n return null;\n }\n\n if (process.env.TRIBUNAL_CORE_PATH && fs.existsSync(process.env.TRIBUNAL_CORE_PATH)) {\n return process.env.TRIBUNAL_CORE_PATH;\n }\n\n const isWindows = os.platform() === 'win32';\n const ext = isWindows ? '.exe' : '';\n const platform = os.platform();\n const arch = os.arch();\n\n // First, try production resolution (from optionalDependencies)\n const pkgName = `@tribunal-kit/core-${platform}-${arch}`;\n try {\n const pkgPath = require.resolve(`${pkgName}/package.json`);\n const pkgDir = path.dirname(pkgPath);\n const binPath = path.resolve(pkgDir, `bin/tribunal-core${ext}`);\n if (fs.existsSync(binPath)) {\n return binPath;\n }\n const rootBinPath = path.resolve(pkgDir, `tribunal-core${ext}`);\n if (fs.existsSync(rootBinPath)) {\n return rootBinPath;\n }\n } catch {\n // Package not found, ignore and fall back to local dev targets\n }\n\n // Second, try to find the binary in local dev target directories\n const candidatePaths = [\n path.resolve(__dirname, '..', 'target', 'release', `tribunal-core${ext}`),\n path.resolve(__dirname, '..', 'target', 'debug', `tribunal-core${ext}`),\n path.resolve(__dirname, '..', '..', 'target', 'release', `tribunal-core${ext}`),\n path.resolve(__dirname, '..', '..', 'target', 'debug', `tribunal-core${ext}`),\n path.resolve(process.cwd(), 'target', 'release', `tribunal-core${ext}`),\n path.resolve(process.cwd(), 'target', 'debug', `tribunal-core${ext}`),\n path.resolve(process.cwd(), 'tribunal-kit', 'target', 'release', `tribunal-core${ext}`),\n path.resolve(process.cwd(), 'tribunal-kit', 'target', 'debug', `tribunal-core${ext}`),\n ];\n\n for (const candidate of candidatePaths) {\n if (fs.existsSync(candidate)) {\n return candidate;\n }\n }\n\n // Third, attempt on-demand compilation if Cargo.toml exists locally and cargo is installed\n const cargoTomlPath = path.resolve(__dirname, '..', 'Cargo.toml');\n if (fs.existsSync(cargoTomlPath)) {\n try {\n const buildResult = spawnSync('cargo', ['build', '--release'], {\n cwd: path.resolve(__dirname, '..'),\n stdio: 'ignore',\n timeout: 60000,\n });\n if (buildResult.status === 0) {\n const releasePath = candidatePaths[0];\n if (fs.existsSync(releasePath)) {\n return releasePath;\n }\n }\n } catch {\n // cargo not available or build failed, fallback gracefully\n }\n }\n\n return null;\n}\n\nfunction runRustBinary(binPath, args) {\n const stdio = ['inherit', 'inherit', 'inherit'];\n const result = spawnSync(binPath, args, {\n stdio: stdio,\n env: process.env,\n });\n\n if (result.error) {\n // Graceful degradation: fall back to JS engine instead of hard-crashing.\n // Spawn errors include missing binaries, permission denied, corrupted\n // executables, missing shared libraries, and OS-level exec failures.\n console.warn(\n `\\x1b[93m⚠ Rust engine failed (${result.error.code || result.error.message}). Falling back to JS engine.\\x1b[0m`,\n );\n return false; // Signal caller to fall back\n }\n\n process.exit(result.status || 0);\n}\n\nfunction runLegacyFallback() {\n // Use the modular dist/ CLI with lazy-loaded commands for faster cold-start.\n // Each command module is require()'d only when invoked (~70% fewer files loaded).\n const { main } = require('../dist/cli.js');\n main().catch(err => {\n console.error(`\\x1b[91m✖ Fatal Error:\\x1b[0m ${err.message || err}`);\n process.exit(1);\n });\n}\n\nfunction main() {\n // Skip 'node' and 'wrapper.js'\n const args = process.argv.slice(2);\n\n // Extract the command (the first non-flag argument)\n const command = args.find(a => !a.startsWith('-'));\n\n if (command && RUST_COMMANDS.has(command)) {\n const binPath = getBinaryPath();\n\n if (binPath) {\n // For the init command, Rust needs to know where the .agent template folder is.\n if (command === 'init') {\n const sourceDir = path.resolve(__dirname, '..', '.agent');\n args.push('--source-dir', sourceDir);\n }\n\n // Route to Rust engine\n // console.log('\\x1b[90m⚡ Executing via Rust Core Engine\\x1b[0m');\n const rustResult = runRustBinary(binPath, args);\n if (rustResult !== false) {\n return; // Rust engine handled it (process.exit was called)\n }\n // rustResult === false means spawn error; fall through to JS fallback\n } else {\n // Warn if Rust command was requested but binary is missing (in verbose mode)\n if (process.env.TK_VERBOSE || process.env.VERBOSE) {\n console.warn(\n '\\x1b[93m⚠ Rust binary not found in target/. Falling back to JS engine.\\x1b[0m',\n );\n }\n }\n }\n\n // Fall back to JS logic for un-ported commands (e.g. `learn`, `case`, `marathon`)\n runLegacyFallback();\n}\n\nmain();\n"
|
|
14
|
+
}
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"file": "eslint.config.js",
|
|
3
|
-
"hash": "d80785ac22194886311daa5c82c9765de8530876",
|
|
4
3
|
"riskScore": "Low",
|
|
5
4
|
"blastRadius": 0,
|
|
6
5
|
"imports": {},
|
|
7
6
|
"dependents": [],
|
|
8
|
-
"content": "module.exports = [\n {\n
|
|
7
|
+
"content": "module.exports = [\n {\n ignores: ['dist/**', 'node_modules/**', 'target/**', 'coverage/**', 'scratch/**'],\n },\n {\n files: ['**/*.js'],\n languageOptions: {\n ecmaVersion: 'latest',\n sourceType: 'commonjs',\n globals: {\n require: 'readonly',\n module: 'readonly',\n process: 'readonly',\n __dirname: 'readonly',\n console: 'readonly',\n exports: 'readonly',\n setTimeout: 'readonly',\n clearTimeout: 'readonly',\n setInterval: 'readonly',\n clearInterval: 'readonly',\n setImmediate: 'readonly',\n performance: 'readonly',\n Buffer: 'readonly',\n describe: 'readonly',\n test: 'readonly',\n it: 'readonly',\n expect: 'readonly',\n beforeEach: 'readonly',\n afterEach: 'readonly',\n beforeAll: 'readonly',\n afterAll: 'readonly',\n jest: 'readonly',\n fail: 'readonly',\n },\n },\n rules: {\n 'no-unused-vars': [\n 'warn',\n {\n argsIgnorePattern: '^_',\n varsIgnorePattern: '^_',\n caughtErrorsIgnorePattern: '^_',\n },\n ],\n 'no-undef': 'error',\n 'no-eval': 'error',\n 'no-implied-eval': 'error',\n 'no-new-func': 'error',\n eqeqeq: ['error', 'always', { null: 'ignore' }],\n 'prefer-const': 'warn',\n 'no-var': 'warn',\n 'no-throw-literal': 'error',\n 'no-return-await': 'warn',\n 'no-template-curly-in-string': 'warn',\n },\n },\n];\n"
|
|
9
8
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"file": "scripts/benchmark.js",
|
|
3
|
+
"riskScore": "Low",
|
|
4
|
+
"blastRadius": 0,
|
|
5
|
+
"imports": {
|
|
6
|
+
"child_process": [],
|
|
7
|
+
"path": [],
|
|
8
|
+
"fs": [],
|
|
9
|
+
"os": [],
|
|
10
|
+
"../.agent/scripts/minimal_change_engine": []
|
|
11
|
+
},
|
|
12
|
+
"dependents": [],
|
|
13
|
+
"content": "#!/usr/bin/env node\n/**\n * Tribunal-Kit Performance Benchmark\n *\n * Measures and reports performance metrics for key operations.\n * Run: node scripts/benchmark.js\n */\n\nconst { spawnSync } = require('child_process');\nconst path = require('path');\nconst fs = require('fs');\nconst os = require('os');\n\n// ANSI colors\nconst C = {\n reset: '\\x1b[0m',\n bold: '\\x1b[1m',\n dim: '\\x1b[2m',\n red: '\\x1b[91m',\n green: '\\x1b[92m',\n yellow: '\\x1b[93m',\n cyan: '\\x1b[96m',\n white: '\\x1b[97m',\n gray: '\\x1b[90m',\n};\n\nfunction c(color, text) {\n return `${C[color]}${text}${C.reset}`;\n}\nfunction bold(text) {\n return `${C.bold}${text}${C.reset}`;\n}\n\n/**\n * Time a command execution in milliseconds.\n * @param {string} label - Description of the benchmark\n * @param {function} fn - Function to benchmark\n * @param {number} [runs=3] - Number of runs for averaging\n * @returns {{ label: string, avg: number, min: number, max: number, runs: number }}\n */\nasync function benchmark(label, fn, runs = 3) {\n const times = [];\n for (let i = 0; i < runs; i++) {\n const start = performance.now();\n await fn();\n const end = performance.now();\n times.push(end - start);\n }\n const avg = times.reduce((a, b) => a + b, 0) / times.length;\n const min = Math.min(...times);\n const max = Math.max(...times);\n return { label, avg, min, max, runs };\n}\n\n/**\n * Time a shell command.\n */\nfunction benchmarkCommand(label, command, runs = 3) {\n return benchmark(\n label,\n () => {\n spawnSync('node', command.split(' '), {\n stdio: 'pipe',\n encoding: 'utf8',\n env: { ...process.env, TK_SKIP_UPDATE_CHECK: '1' },\n });\n },\n runs,\n );\n}\n\nasync function main() {\n console.log();\n console.log(bold(` ⚡ Tribunal-Kit Performance Benchmark`));\n console.log(c('gray', ` ─────────────────────────────────────────`));\n console.log(c('gray', ` Platform: ${os.platform()} ${os.arch()}`));\n console.log(c('gray', ` Node: ${process.version}`));\n console.log(c('gray', ` CPUs: ${os.cpus().length}x ${os.cpus()[0]?.model || 'unknown'}`));\n console.log(c('gray', ` ─────────────────────────────────────────`));\n console.log();\n\n const cliPath = path.resolve(__dirname, '../bin/wrapper.js');\n const tempDir = path.join(os.tmpdir(), `tribunal-bench-${Date.now()}`);\n fs.mkdirSync(tempDir, { recursive: true });\n\n const results = [];\n\n // 1. Cold start (help)\n console.log(c('cyan', ' ▸ Benchmarking: CLI cold-start (--help)'));\n const helpResult = await benchmarkCommand('CLI cold-start (--help)', `${cliPath} --help`, 5);\n results.push(helpResult);\n\n // 2. Status check\n console.log(c('cyan', ' ▸ Benchmarking: tk status'));\n const statusResult = await benchmarkCommand('Status check', `${cliPath} status --quiet`, 5);\n results.push(statusResult);\n\n // 3. Init (dry-run)\n console.log(c('cyan', ' ▸ Benchmarking: tk init --dry-run'));\n const initResult = await benchmarkCommand(\n 'Init (dry-run)',\n `${cliPath} init --dry-run --quiet --skip-update-check --path=${tempDir}`,\n 3,\n );\n results.push(initResult);\n\n // 4. Init (real, to temp dir)\n console.log(c('cyan', ' ▸ Benchmarking: tk init (real copy)'));\n const initRealResult = await benchmark(\n 'Init (full copy)',\n () => {\n const runDir = path.join(tempDir, `run-${Date.now()}`);\n fs.mkdirSync(runDir, { recursive: true });\n spawnSync('node', [cliPath, 'init', '--quiet', '--skip-update-check', `--path=${runDir}`], {\n stdio: 'pipe',\n encoding: 'utf8',\n env: { ...process.env, TK_SKIP_UPDATE_CHECK: '1' },\n });\n // Cleanup\n try {\n fs.rmSync(runDir, { recursive: true, force: true });\n } catch {}\n },\n 3,\n );\n results.push(initRealResult);\n\n // 5. DAG scheduling benchmark\n console.log(c('cyan', ' ▸ Benchmarking: DAG scheduling calculation'));\n const dagResult = await benchmark(\n 'DAG wave scheduling',\n () => {\n const workers = [\n { task_id: 'w1', dependencies: [] },\n { task_id: 'w2', dependencies: ['w1'] },\n { task_id: 'w3', dependencies: ['w1'] },\n { task_id: 'w4', dependencies: ['w2', 'w3'] },\n ];\n const inDegree = {};\n const adjList = {};\n workers.forEach(w => {\n inDegree[w.task_id] = 0;\n adjList[w.task_id] = [];\n });\n workers.forEach(w => {\n w.dependencies.forEach(dep => {\n adjList[dep].push(w.task_id);\n inDegree[w.task_id] += 1;\n });\n });\n let currentWave = Object.keys(inDegree).filter(id => inDegree[id] === 0);\n const waves = [];\n while (currentWave.length > 0) {\n waves.push(currentWave);\n const next = [];\n currentWave.forEach(id => {\n adjList[id].forEach(nbr => {\n inDegree[nbr] -= 1;\n if (inDegree[nbr] === 0) next.push(nbr);\n });\n });\n currentWave = next;\n }\n },\n 100,\n );\n results.push(dagResult);\n\n // 6. Minimal Change Governance Engine benchmark\n console.log(c('cyan', ' ▸ Benchmarking: Minimal Change Governance Engine'));\n const minResult = await benchmark(\n 'Minimal Change Audit',\n () => {\n const minEngine = require('../.agent/scripts/minimal_change_engine');\n minEngine.evaluateMinimalChange('add retry logic to API requests', {\n files_added: 0,\n files_modified: 1,\n estimated_lines_added: 15,\n });\n },\n 20,\n );\n results.push(minResult);\n\n // Print results table\n console.log();\n console.log(bold(` Results`));\n console.log(c('gray', ` ─────────────────────────────────────────────────────────`));\n console.log(\n ` ${c('white', 'Operation'.padEnd(30))} ${c('white', 'Avg (ms)'.padStart(10))} ${c('white', 'Min'.padStart(8))} ${c('white', 'Max'.padStart(8))}`,\n );\n console.log(c('gray', ` ─────────────────────────────────────────────────────────`));\n\n for (const r of results) {\n const avgColor = r.avg < 100 ? 'green' : r.avg < 500 ? 'yellow' : 'red';\n console.log(\n ` ${c('white', r.label.padEnd(30))} ${c(avgColor, String(Math.round(r.avg)).padStart(10))} ${c('gray', String(Math.round(r.min)).padStart(8))} ${c('gray', String(Math.round(r.max)).padStart(8))}`,\n );\n }\n\n console.log(c('gray', ` ─────────────────────────────────────────────────────────`));\n console.log();\n\n // Write results to JSON for CI/comparison\n const outputPath = path.resolve(__dirname, '../benchmark-results.json');\n const outputData = {\n timestamp: new Date().toISOString(),\n platform: `${os.platform()}-${os.arch()}`,\n node: process.version,\n results: results.map(r => ({\n label: r.label,\n avg_ms: Math.round(r.avg),\n min_ms: Math.round(r.min),\n max_ms: Math.round(r.max),\n runs: r.runs,\n })),\n };\n fs.writeFileSync(outputPath, JSON.stringify(outputData, null, 2));\n console.log(c('green', ` ✔ Results saved to benchmark-results.json`));\n\n // Cleanup temp\n try {\n fs.rmSync(tempDir, { recursive: true, force: true });\n } catch {}\n console.log();\n}\n\nmain().catch(err => {\n console.error(`Benchmark failed: ${err.message}`);\n process.exit(1);\n});\n"
|
|
14
|
+
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
{
|
|
2
2
|
"file": "scripts/changelog.js",
|
|
3
|
-
"hash": "a078be5cb402a420593b4759afdae249cde78fdf",
|
|
4
3
|
"riskScore": "Low",
|
|
5
4
|
"blastRadius": 0,
|
|
6
5
|
"imports": {
|
|
@@ -9,5 +8,5 @@
|
|
|
9
8
|
"path": []
|
|
10
9
|
},
|
|
11
10
|
"dependents": [],
|
|
12
|
-
"content": "#!/usr/bin/env node\
|
|
11
|
+
"content": "#!/usr/bin/env node\n/**\n * changelog.js — Auto-generate CHANGELOG from git history\n *\n * Categorizes commits by conventional commit type:\n * feat: → ✨ Features\n * fix: → 🐛 Bug Fixes\n * perf: → ⚡ Performance\n * docs: → 📝 Documentation\n * test: → ✅ Tests\n * refactor: → ♻️ Refactors\n * chore: → 🔧 Chores\n * BREAKING: → 💥 Breaking Changes\n *\n * Usage:\n * node scripts/changelog.js → Generate full changelog\n * node scripts/changelog.js --preview → Preview unreleased changes\n * node scripts/changelog.js --since v4.1.0 → Changes since a specific tag\n */\n\n'use strict';\n\nconst { execSync } = require('child_process');\nconst fs = require('fs');\nconst path = require('path');\n\nconst PKG = require(path.resolve(__dirname, '..', 'package.json'));\nconst CHANGELOG_PATH = path.resolve(__dirname, '..', 'CHANGELOG.md');\n\n// ── Commit Categories ────────────────────────────────────\nconst CATEGORIES = {\n feat: { emoji: '✨', title: 'Features' },\n fix: { emoji: '🐛', title: 'Bug Fixes' },\n perf: { emoji: '⚡', title: 'Performance' },\n docs: { emoji: '📝', title: 'Documentation' },\n test: { emoji: '✅', title: 'Tests' },\n refactor: { emoji: '♻️', title: 'Refactors' },\n chore: { emoji: '🔧', title: 'Chores' },\n ci: { emoji: '🏗️', title: 'CI/CD' },\n style: { emoji: '🎨', title: 'Style' },\n breaking: { emoji: '💥', title: 'Breaking Changes' },\n};\n\n// ── Git Helpers ──────────────────────────────────────────\nfunction git(cmd) {\n try {\n return execSync(`git ${cmd}`, { encoding: 'utf8', timeout: 10000 }).trim();\n } catch {\n return '';\n }\n}\n\nfunction getLatestTag() {\n return (\n git('describe --tags --abbrev=0 2>nul') || git('describe --tags --abbrev=0 2>/dev/null') || ''\n );\n}\n\nfunction getCommits(since) {\n const range = since ? `${since}..HEAD` : 'HEAD';\n const MAX_COMMITS = 500;\n const format = '--format=%H||%s||%an||%ai';\n const raw = git(`log ${range} ${format} --no-merges -n ${MAX_COMMITS}`);\n if (!raw) return [];\n\n return raw\n .replace(/\\r/g, '')\n .split('\\n')\n .filter(line => line.includes('||'))\n .map(line => {\n const [hash, subject = '', author = '', date = ''] = line.split('||');\n return {\n hash: hash?.replace(/^\"/, '').slice(0, 7),\n subject: subject.trim(),\n author: author.trim(),\n date: date?.replace(/\"$/, '').slice(0, 10),\n };\n });\n}\n\nfunction categorize(subject = '') {\n const lower = (subject || '').toLowerCase();\n\n // Check for BREAKING CHANGE\n if (lower.includes('breaking') || lower.includes('!:')) {\n return 'breaking';\n }\n\n // Match conventional commit prefix\n const match = (subject || '').match(/^(\\w+)(?:\\(.+?\\))?:\\s*/);\n if (match) {\n const type = match[1].toLowerCase();\n if (CATEGORIES[type]) return type;\n }\n\n // Heuristic fallback\n if (lower.includes('fix') || lower.includes('bug')) return 'fix';\n if (lower.includes('add') || lower.includes('new') || lower.includes('feat')) return 'feat';\n if (lower.includes('doc') || lower.includes('readme')) return 'docs';\n if (lower.includes('test')) return 'test';\n if (lower.includes('refactor') || lower.includes('clean')) return 'refactor';\n if (lower.includes('perf') || lower.includes('optim')) return 'perf';\n if (lower.includes('ci') || lower.includes('workflow')) return 'ci';\n\n return 'chore';\n}\n\n// ── Changelog Generation ─────────────────────────────────\nfunction generateChangelog(commits, version, date) {\n const grouped = {};\n for (const commit of commits) {\n const cat = categorize(commit.subject);\n if (!grouped[cat]) grouped[cat] = [];\n // Strip conventional prefix for cleaner display\n const clean = (commit.subject || '').replace(/^\\w+(\\(.+?\\))?:\\s*/, '');\n grouped[cat].push({ ...commit, clean });\n }\n\n let md = `## [${version}] — ${date}\\n\\n`;\n\n // Breaking changes first\n const order = [\n 'breaking',\n 'feat',\n 'fix',\n 'perf',\n 'refactor',\n 'docs',\n 'test',\n 'ci',\n 'style',\n 'chore',\n ];\n for (const cat of order) {\n if (!grouped[cat] || grouped[cat].length === 0) continue;\n const { emoji, title } = CATEGORIES[cat];\n md += `### ${emoji} ${title}\\n\\n`;\n for (const c of grouped[cat]) {\n md += `- ${c.clean} (\\`${c.hash}\\`)\\n`;\n }\n md += '\\n';\n }\n\n return md;\n}\n\n// ── Main ─────────────────────────────────────────────────\nfunction main() {\n const args = process.argv.slice(2);\n const isPreview = args.includes('--preview');\n const sinceIdx = args.indexOf('--since');\n const sinceTag = sinceIdx !== -1 ? args[sinceIdx + 1] : null;\n\n const since = sinceTag || getLatestTag();\n const commits = getCommits(since);\n\n if (commits.length === 0) {\n console.log(' ℹ️ No new commits found since', since || 'beginning');\n process.exit(0);\n }\n\n const today = new Date().toISOString().slice(0, 10);\n const version = isPreview ? 'Unreleased' : PKG.version;\n\n const changelog = generateChangelog(commits, version, today);\n\n if (isPreview) {\n console.log('\\n 📋 Changelog Preview\\n ' + '─'.repeat(40) + '\\n');\n console.log(changelog);\n console.log(` 📊 ${commits.length} commits since ${since || 'initial commit'}`);\n return;\n }\n\n // Write or prepend to CHANGELOG.md\n const header = `# Changelog\\n\\nAll notable changes to Tribunal Kit are documented here.\\nFormat follows [Keep a Changelog](https://keepachangelog.com/).\\n\\n`;\n\n let existing = '';\n if (fs.existsSync(CHANGELOG_PATH)) {\n existing = fs.readFileSync(CHANGELOG_PATH, 'utf8');\n // Remove existing header\n existing = existing.replace(/^# Changelog[\\s\\S]*?(?=## )/, '');\n }\n\n const full = header + changelog + existing;\n fs.writeFileSync(CHANGELOG_PATH, full, 'utf8');\n\n console.log(` ✔ CHANGELOG.md updated — v${version} (${commits.length} commits)`);\n}\n\nmain();\n"
|
|
13
12
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"file": "scripts/fix-vbc.js",
|
|
3
|
+
"riskScore": "Low",
|
|
4
|
+
"blastRadius": 0,
|
|
5
|
+
"imports": {
|
|
6
|
+
"fs": [],
|
|
7
|
+
"path": []
|
|
8
|
+
},
|
|
9
|
+
"dependents": [],
|
|
10
|
+
"content": "#!/usr/bin/env node\n/**\n * fix-vbc.js — Batch appends missing VBC Protocol and Pre-Flight sections\n * to all SKILL.md files in .agent/skills/ that are failing validation.\n *\n * Run: node scripts/fix-vbc.js\n * Dry-run: node scripts/fix-vbc.js --dry-run\n */\n\nconst fs = require('fs');\nconst path = require('path');\n\nconst ROOT = path.resolve(__dirname, '..');\nconst SKILLS_DIR = path.join(ROOT, '.agent', 'skills');\nconst DRY_RUN = process.argv.includes('--dry-run');\n\nconst PRE_FLIGHT_BLOCK = `\n### ✅ Pre-Flight Self-Audit\n\n\\`\\`\\`\n✅ Did I rely ONLY on real, verified tools and methods?\n✅ Is this solution appropriately scoped to the user's constraints?\n✅ Did I handle potential failure modes and edge cases?\n✅ Have I avoided generic boilerplate that doesn't add value?\n\\`\\`\\`\n`;\n\nconst VBC_BLOCK = `\n### 🛑 Verification-Before-Completion (VBC) Protocol\n\n**CRITICAL:** You must follow a strict \"evidence-based closeout\" state machine.\n\n- ❌ **Forbidden:** Declaring a task complete because the output \"looks correct.\"\n- ✅ **Required:** You are explicitly forbidden from finalizing any task without providing **concrete evidence** (terminal output, passing tests, compile success, or equivalent proof) that your output works as intended.\n`;\n\nlet fixedCount = 0;\nlet skippedCount = 0;\nlet errorCount = 0;\n\nconst skillDirs = fs.readdirSync(SKILLS_DIR).filter(d => {\n return fs.statSync(path.join(SKILLS_DIR, d)).isDirectory();\n});\n\nfor (const dir of skillDirs) {\n const skillPath = path.join(SKILLS_DIR, dir, 'SKILL.md');\n if (!fs.existsSync(skillPath)) continue;\n\n let content;\n try {\n content = fs.readFileSync(skillPath, 'utf8');\n } catch (e) {\n console.error(` ❌ Failed to read: ${dir}/SKILL.md — ${e.message}`);\n errorCount++;\n continue;\n }\n\n const hasPreFlight = content.includes('Pre-Flight Checklist') || content.includes('Pre-Flight');\n const hasVBC = content.includes('VBC Protocol') || content.includes('VBC');\n\n if (hasPreFlight && hasVBC) {\n skippedCount++;\n continue;\n }\n\n let appendText = '';\n\n if (!hasPreFlight) {\n appendText += PRE_FLIGHT_BLOCK;\n console.log(` 🔧 ${dir}/SKILL.md — appending Pre-Flight Self-Audit`);\n }\n\n if (!hasVBC) {\n appendText += VBC_BLOCK;\n console.log(` 🔧 ${dir}/SKILL.md — appending VBC Protocol`);\n }\n\n if (DRY_RUN) {\n console.log(` [DRY-RUN] Would append to ${dir}/SKILL.md`);\n } else {\n try {\n fs.appendFileSync(skillPath, appendText, 'utf8');\n fixedCount++;\n } catch (e) {\n console.error(` ❌ Failed to write: ${dir}/SKILL.md — ${e.message}`);\n errorCount++;\n }\n }\n}\n\nconsole.log(`\\n━━━ VBC Fix Summary ━━━━━━━━━━━━━━━━━━━━━`);\nconsole.log(` Fixed: ${fixedCount}`);\nconsole.log(` Skipped: ${skippedCount} (already compliant)`);\nconsole.log(` Errors: ${errorCount}`);\nconsole.log(` Mode: ${DRY_RUN ? 'DRY-RUN (no changes)' : 'LIVE'}`);\nconsole.log();\n"
|
|
11
|
+
}
|