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.
Files changed (95) hide show
  1. package/.agent/config/claude.json +18 -0
  2. package/.agent/config/plugin.json +102 -0
  3. package/.agent/config/slash-commands.json +103 -0
  4. package/.agent/config/specialist-registry.json +415 -0
  5. package/.agent/config/system-prompt.md +191 -0
  6. package/.agent/history/architecture-graph.yaml +302 -3
  7. package/.agent/history/graph-cache.json +515 -45
  8. package/.agent/history/memory.db +0 -0
  9. package/.agent/history/snapshots/bin__adapter-install.js.json +13 -0
  10. package/.agent/history/snapshots/bin__global-store.js.json +15 -0
  11. package/.agent/history/snapshots/bin__mcp-server.js.json +19 -0
  12. package/.agent/history/snapshots/bin__proxy-server.js.json +20 -0
  13. package/.agent/history/snapshots/bin__spawn-agent.js.json +12 -0
  14. package/.agent/history/snapshots/bin__tk-proxy.js.json +21 -0
  15. package/.agent/history/snapshots/bin__tribunal-kit.js.json +9 -7
  16. package/.agent/history/snapshots/bin__wrapper.js.json +14 -0
  17. package/.agent/history/snapshots/eslint.config.js.json +1 -2
  18. package/.agent/history/snapshots/scripts__benchmark.js.json +14 -0
  19. package/.agent/history/snapshots/scripts__changelog.js.json +1 -2
  20. package/.agent/history/snapshots/scripts__fix-vbc.js.json +11 -0
  21. package/.agent/history/snapshots/scripts__stress_benchmark.js.json +15 -0
  22. package/.agent/history/snapshots/scripts__sync-version.js.json +1 -2
  23. package/.agent/history/snapshots/scripts__validate-payload.js.json +1 -2
  24. package/.agent/history/snapshots/scripts__visual_audit.js.json +11 -0
  25. package/.agent/history/snapshots/test__integration__bridges.test.js.json +1 -2
  26. package/.agent/history/snapshots/test__integration__graceful_degradation.test.js.json +12 -0
  27. package/.agent/history/snapshots/test__integration__init.test.js.json +1 -2
  28. package/.agent/history/snapshots/test__integration__minimal_change_pipeline.test.js.json +13 -0
  29. package/.agent/history/snapshots/test__integration__parallel_tribunal.test.js.json +12 -0
  30. package/.agent/history/snapshots/test__integration__routing.test.js.json +1 -2
  31. package/.agent/history/snapshots/test__integration__swarm_dispatcher.test.js.json +1 -2
  32. package/.agent/history/snapshots/test__integration__sync_status.test.js.json +13 -0
  33. package/.agent/history/snapshots/test__integration__wave2.test.js.json +1 -2
  34. package/.agent/history/snapshots/test__integration__wrapper.test.js.json +13 -0
  35. package/.agent/history/snapshots/test__unit__align.test.js.json +10 -0
  36. package/.agent/history/snapshots/test__unit__args.test.js.json +3 -3
  37. package/.agent/history/snapshots/test__unit__audit_release.test.js.json +16 -0
  38. package/.agent/history/snapshots/test__unit__case_law_manager.test.js.json +1 -2
  39. package/.agent/history/snapshots/test__unit__cicd_validator.test.js.json +12 -0
  40. package/.agent/history/snapshots/test__unit__compile.test.js.json +10 -0
  41. package/.agent/history/snapshots/test__unit__context_broker.test.js.json +1 -2
  42. package/.agent/history/snapshots/test__unit__contract_engine.test.js.json +13 -0
  43. package/.agent/history/snapshots/test__unit__copyDir.test.js.json +3 -3
  44. package/.agent/history/snapshots/test__unit__graph_tools.test.js.json +1 -2
  45. package/.agent/history/snapshots/test__unit__guardrail_engine.test.js.json +10 -0
  46. package/.agent/history/snapshots/test__unit__impact_classifier.test.js.json +12 -0
  47. package/.agent/history/snapshots/test__unit__init.test.js.json +14 -0
  48. package/.agent/history/snapshots/test__unit__inner_loop_validator.test.js.json +1 -2
  49. package/.agent/history/snapshots/test__unit__integrity_manifest.test.js.json +13 -0
  50. package/.agent/history/snapshots/test__unit__learn.test.js.json +11 -0
  51. package/.agent/history/snapshots/test__unit__marathon.test.js.json +22 -0
  52. package/.agent/history/snapshots/test__unit__mcp_server.test.js.json +16 -0
  53. package/.agent/history/snapshots/test__unit__memory.test.js.json +13 -0
  54. package/.agent/history/snapshots/test__unit__minimal_change.test.js.json +10 -0
  55. package/.agent/history/snapshots/test__unit__native.test.js.json +10 -0
  56. package/.agent/history/snapshots/test__unit__optimize.test.js.json +13 -0
  57. package/.agent/history/snapshots/test__unit__path_resolution.test.js.json +14 -0
  58. package/.agent/history/snapshots/test__unit__production_readiness_evidence.test.js.json +21 -0
  59. package/.agent/history/snapshots/test__unit__selfInstall.test.js.json +3 -3
  60. package/.agent/history/snapshots/test__unit__semver.test.js.json +3 -3
  61. package/.agent/history/snapshots/test__unit__skill_evolution.test.js.json +11 -0
  62. package/.agent/history/snapshots/test__unit__stress.test.js.json +15 -0
  63. package/.agent/history/snapshots/test__unit__swarm_dispatcher.test.js.json +3 -3
  64. package/.agent/history/snapshots/test__unit__utils.test.js.json +10 -0
  65. package/.agent/scripts/ast_context_loader.js +137 -0
  66. package/.agent/scripts/memory_engine.js +581 -0
  67. package/.agent/scripts/payload_schemas.js +146 -0
  68. package/.agent/scripts/prompt_compiler.js +59 -11
  69. package/.agent/scripts/swarm_dispatcher.js +295 -67
  70. package/.agent/scripts/token_budget_broker.js +117 -6
  71. package/.claude/CLAUDE.md +442 -0
  72. package/README.md +22 -12
  73. package/SECURITY.md +3 -3
  74. package/bin/adapter-install.js +240 -0
  75. package/bin/global-store.js +48 -0
  76. package/bin/mcp-server.js +568 -252
  77. package/bin/proxy-server.js +113 -0
  78. package/bin/spawn-agent.js +39 -0
  79. package/bin/tk-proxy.js +34 -0
  80. package/dist/commands/init.js +8 -51
  81. package/dist/commands/status.js +61 -36
  82. package/dist/commands/validate.js +22 -31
  83. package/dist/tui/banner.js +77 -0
  84. package/dist/tui/index.js +21 -0
  85. package/dist/tui/reviewer-grid.js +90 -0
  86. package/dist/tui/shimmer.js +91 -0
  87. package/dist/tui/theme.js +130 -0
  88. package/dist/tui/tree.js +93 -0
  89. package/dist/tui/wizard.js +148 -0
  90. package/dist/utils/helpers.js +5 -41
  91. package/package.json +27 -10
  92. package/scripts/build-graph.js +71 -0
  93. package/.agent/history/snapshots/migrate_refs.js.json +0 -11
  94. package/.agent/scripts/compile_router.py +0 -5
  95. package/.agent/scripts/migrate_skills_frontmatter.py +0 -5
package/bin/mcp-server.js CHANGED
@@ -20,6 +20,61 @@ const PKG = require(path.resolve(__dirname, '../package.json'));
20
20
 
21
21
  // Timeout for intentionally isolated child processes (30 seconds).
22
22
  const SPAWN_TIMEOUT_MS = 30000;
23
+ const GENERAL_TOOL_TIMEOUT_MS = 60000;
24
+
25
+ // --- DSH Guard: Cooperative Timeout ---
26
+ async function withToolTimeout(fn, timeoutMs) {
27
+ let timeoutId;
28
+ const timeoutPromise = new Promise((_, reject) => {
29
+ timeoutId = setTimeout(() => {
30
+ reject(new Error('TOOL_TIMEOUT'));
31
+ }, timeoutMs);
32
+ });
33
+
34
+ try {
35
+ const result = await Promise.race([Promise.resolve().then(fn), timeoutPromise]);
36
+ return result;
37
+ } finally {
38
+ clearTimeout(timeoutId);
39
+ }
40
+ }
41
+
42
+ // --- DSH Guard: Loop Detection ---
43
+ class ToolRepeatGuard {
44
+ constructor() {
45
+ this.history = new Map();
46
+ }
47
+
48
+ observe(toolName, argsObj) {
49
+ const canonicalize = obj => {
50
+ if (Array.isArray(obj)) return obj.map(canonicalize);
51
+ if (obj !== null && typeof obj === 'object') {
52
+ const sorted = {};
53
+ for (const key of Object.keys(obj).sort()) {
54
+ sorted[key] = canonicalize(obj[key]);
55
+ }
56
+ return sorted;
57
+ }
58
+ return obj;
59
+ };
60
+
61
+ const canonicalArgs = JSON.stringify(canonicalize(argsObj) || {});
62
+ const key = `${toolName}::${canonicalArgs}`;
63
+
64
+ const currentCount = (this.history.get(key) || 0) + 1;
65
+ this.history.clear();
66
+ this.history.set(key, currentCount);
67
+
68
+ if (currentCount === 3) {
69
+ 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.';
70
+ }
71
+ if (currentCount >= 5) {
72
+ 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.`;
73
+ }
74
+ return null;
75
+ }
76
+ }
77
+ const repeatGuard = new ToolRepeatGuard();
23
78
 
24
79
  class RpcError extends Error {
25
80
  constructor(code, message) {
@@ -118,7 +173,7 @@ function getAgentDir() {
118
173
  return path.resolve(__dirname, '../.agent');
119
174
  }
120
175
 
121
- function handleRequest(req) {
176
+ async function handleRequest(req) {
122
177
  const fs = require('fs');
123
178
  if (req.method === 'notifications/initialized' || req.method === 'notifications/cancelled') {
124
179
  return null;
@@ -475,334 +530,595 @@ function handleRequest(req) {
475
530
  additionalProperties: false,
476
531
  },
477
532
  },
533
+ {
534
+ name: 'query_semantic_graph',
535
+ description:
536
+ 'Query the Ahead-of-Time (AOT) Semantic Context Graph to quickly resolve function signatures, dependencies, and file structures without inflating context windows.',
537
+ inputSchema: {
538
+ type: 'object',
539
+ properties: {
540
+ targetPath: {
541
+ type: 'string',
542
+ description: 'The directory to query (defaults to current workspace).',
543
+ },
544
+ },
545
+ additionalProperties: false,
546
+ },
547
+ },
548
+ {
549
+ name: 'exit_plan_mode',
550
+ description:
551
+ 'Present your finished plan for human reviewed designing your approach. Execution will suspend until the user approves or requests changes.',
552
+ inputSchema: {
553
+ type: 'object',
554
+ properties: {
555
+ plan_content: {
556
+ type: 'string',
557
+ description: 'The markdown content of your proposed plan.',
558
+ },
559
+ },
560
+ required: ['plan_content'],
561
+ additionalProperties: false,
562
+ },
563
+ },
478
564
  ],
479
565
  };
480
566
  }
481
567
 
482
568
  if (req.method === 'tools/call') {
483
569
  const toolName = req.params && req.params.name;
570
+ const argsObj = req.params && req.params.arguments;
484
571
  if (!toolName) {
485
572
  throw new RpcError(-32602, 'Missing required parameter: params.name');
486
573
  }
487
574
 
488
- if (toolName === 'run_tribunal_audit') {
489
- const text = runTribunalAudit();
490
- return { content: [{ type: 'text', text }] };
491
- }
575
+ const reminder = repeatGuard.observe(toolName, argsObj);
492
576
 
493
- if (toolName === 'sync_ide_bridges') {
494
- const fs = require('fs');
495
- const cwd = process.cwd();
496
- const agentDest = path.join(cwd, '.agent');
497
- if (!fs.existsSync(agentDest)) {
498
- return {
499
- content: [
500
- {
501
- type: 'text',
502
- text: 'Error: .agent/ directory not found. Run `tk init` first.',
503
- },
504
- ],
505
- };
506
- }
507
- try {
508
- const { generateIDEBridges } = require(path.resolve(__dirname, '../dist/commands/init.js'));
509
- // generateIDEBridges is async — execute synchronously in MCP context
510
- generateIDEBridges(cwd, agentDest, true)
511
- .then(() => {})
512
- .catch(() => {});
513
- return {
514
- content: [
515
- {
516
- type: 'text',
517
- text: 'Sync complete',
518
- },
519
- ],
520
- };
521
- } catch (e) {
577
+ const executeTool = async () => {
578
+ if (toolName === 'exit_plan_mode') {
579
+ const planContent = req.params?.arguments?.plan_content;
580
+ if (typeof planContent !== 'string') {
581
+ throw new RpcError(-32602, 'Missing or invalid required argument: plan_content (string)');
582
+ }
583
+ // The wrapper CLI can intercept this, but for the LLM context, we explicitly tell it to wait.
522
584
  return {
523
585
  content: [
524
586
  {
525
587
  type: 'text',
526
- text: `Sync failed: ${e.message}`,
588
+ 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.',
527
589
  },
528
590
  ],
529
591
  };
530
592
  }
531
- }
532
593
 
533
- if (toolName === 'search_case_law') {
534
- const query = req.params && req.params.arguments && req.params.arguments.query;
535
- if (!query || typeof query !== 'string') {
536
- throw new RpcError(-32602, 'Missing or invalid required argument: query (string)');
537
- }
538
- const text = searchCaseLaw(query);
539
- return { content: [{ type: 'text', text }] };
540
- }
594
+ if (toolName === 'dispatch_swarm') {
595
+ const payload = req.params?.arguments?.payload;
596
+ const file = req.params?.arguments?.file;
597
+ const workspace = req.params?.arguments?.workspace || '.';
598
+ const mode = req.params?.arguments?.mode || 'legacy';
599
+ const useTui = req.params?.arguments?.use_tui || false;
600
+
601
+ if (!payload && !file) {
602
+ throw new RpcError(
603
+ -32602,
604
+ 'Missing required argument: either payload or file must be provided',
605
+ );
606
+ }
541
607
 
542
- if (toolName === 'list_tribunal_agents') {
543
- const fs = require('fs');
544
- const agentDir = path.join(getAgentDir(), 'agents');
545
- if (!fs.existsSync(agentDir))
546
- return {
547
- content: [{ type: 'text', text: 'No agents found or .agent directory missing.' }],
548
- };
549
- const agents = fs
550
- .readdirSync(agentDir)
551
- .filter(f => f.endsWith('.md'))
552
- .map(f => f.replace('.md', ''));
553
- return { content: [{ type: 'text', text: 'Available Agents:\n- ' + agents.join('\n- ') }] };
554
- }
608
+ if (payload && file) {
609
+ throw new RpcError(-32602, 'Invalid arguments: provide either payload or file, not both');
610
+ }
555
611
 
556
- if (toolName === 'get_tribunal_agent') {
557
- const fs = require('fs');
558
- const name = req.params?.arguments?.name;
559
- if (!name || typeof name !== 'string')
560
- throw new RpcError(-32602, 'Missing or invalid argument: name (string)');
561
- const sanitizedName = path.basename(name);
562
- const agentsDir = path.resolve(getAgentDir(), 'agents');
563
- const agentPath = path.resolve(agentsDir, `${sanitizedName}.md`);
564
- // Path containment: ensure resolved path stays within agents directory
565
- if (!agentPath.startsWith(agentsDir))
566
- throw new RpcError(-32602, 'Invalid agent name: path traversal detected');
567
- if (!fs.existsSync(agentPath))
568
- return { content: [{ type: 'text', text: `Agent '${sanitizedName}' not found.` }] };
569
- const text = fs.readFileSync(agentPath, 'utf8');
570
- return { content: [{ type: 'text', text: stripBoilerplate(text) }] };
571
- }
612
+ const agentDir = getAgentDir();
613
+ const fs = require('fs');
614
+ if (!fs.existsSync(agentDir)) {
615
+ return {
616
+ content: [
617
+ { type: 'text', text: 'Error: .agent/ directory not found. Run `tk init` first.' },
618
+ ],
619
+ };
620
+ }
572
621
 
573
- if (toolName === 'list_tribunal_skills') {
574
- const fs = require('fs');
575
- const skillsDir = path.join(getAgentDir(), 'skills');
576
- if (!fs.existsSync(skillsDir))
577
- return {
578
- content: [{ type: 'text', text: 'No skills found or .agent directory missing.' }],
579
- };
580
- const skills = fs
581
- .readdirSync(skillsDir, { withFileTypes: true })
582
- .filter(d => d.isDirectory())
583
- .map(d => d.name);
584
- return { content: [{ type: 'text', text: 'Available Skills:\n- ' + skills.join('\n- ') }] };
585
- }
622
+ try {
623
+ let _payloadData;
624
+ if (file) {
625
+ const filePath = path.resolve(workspace, file);
626
+ if (!fs.existsSync(filePath)) {
627
+ throw new Error(`File not found: ${filePath}`);
628
+ }
629
+ _payloadData = JSON.parse(fs.readFileSync(filePath, 'utf8'));
630
+ } else {
631
+ _payloadData = JSON.parse(payload);
632
+ }
586
633
 
587
- if (toolName === 'get_tribunal_skill') {
588
- const fs = require('fs');
589
- const name = req.params?.arguments?.name;
590
- if (!name || typeof name !== 'string')
591
- throw new RpcError(-32602, 'Missing or invalid argument: name (string)');
592
- const sanitizedName = path.basename(name);
593
- const skillsDir = path.resolve(getAgentDir(), 'skills');
594
- const skillPath = path.resolve(skillsDir, sanitizedName, 'SKILL.md');
595
- // Path containment: ensure resolved path stays within skills directory
596
- if (!skillPath.startsWith(skillsDir))
597
- throw new RpcError(-32602, 'Invalid skill name: path traversal detected');
598
- if (!fs.existsSync(skillPath))
599
- return { content: [{ type: 'text', text: `Skill '${sanitizedName}' not found.` }] };
600
- const text = fs.readFileSync(skillPath, 'utf8');
601
- return { content: [{ type: 'text', text: stripBoilerplate(text) }] };
602
- }
634
+ // Import swarm dispatcher functions
635
+ const _swarmDispatcher = require(path.join(agentDir, 'scripts', 'swarm_dispatcher.js'));
603
636
 
604
- if (toolName === 'get_sparse_context') {
605
- const task = req.params?.arguments?.task;
606
- const files = req.params?.arguments?.files || [];
607
- const model = req.params?.arguments?.model || 'large';
637
+ // Execute the swarm dispatcher
638
+ const originalArgv = process.argv;
639
+ process.argv = ['node', 'swarm_dispatcher.js', '--mode', mode, '--workspace', workspace];
608
640
 
609
- if (!task) throw new RpcError(-32602, 'Missing required argument: task');
641
+ if (useTui) {
642
+ process.argv.push('--tui');
643
+ }
610
644
 
611
- const agentDest = getAgentDir();
612
- const fs = require('fs');
613
- if (!fs.existsSync(agentDest)) {
614
- return {
615
- content: [
616
- { type: 'text', text: 'Error: .agent/ directory not found. Run `tk init` first.' },
617
- ],
618
- };
619
- }
645
+ if (file) {
646
+ process.argv.push('--file');
647
+ process.argv.push(file);
648
+ } else if (payload) {
649
+ process.argv.push('--payload');
650
+ process.argv.push(payload);
651
+ }
620
652
 
621
- try {
622
- const brokerScript = path.join(agentDest, 'scripts', 'context_broker.js');
623
- const { broker } = require(brokerScript);
624
- const brokerResult = broker(task, files, model, agentDest);
625
- return { content: [{ type: 'text', text: stripBoilerplate(brokerResult.promptText) }] };
626
- } catch (e) {
627
- return {
628
- content: [{ type: 'text', text: `Failed to retrieve sparse context: ${e.message}` }],
629
- };
630
- }
631
- }
653
+ // Capture output
654
+ const { spawnSync } = require('child_process');
655
+ const result = spawnSync(
656
+ process.execPath,
657
+ [path.join(agentDir, 'scripts', 'swarm_dispatcher.js'), ...process.argv.slice(2)],
658
+ {
659
+ encoding: 'utf8',
660
+ timeout: 30000,
661
+ },
662
+ );
663
+
664
+ // Restore argv
665
+ process.argv = originalArgv;
632
666
 
633
- if (toolName === 'recall_memory') {
634
- const query = req.params?.arguments?.query;
635
- if (!query || typeof query !== 'string') {
636
- throw new RpcError(-32602, 'Missing or invalid required argument: query (string)');
667
+ if (result.error) {
668
+ throw result.error;
669
+ }
670
+
671
+ return {
672
+ content: [
673
+ { type: 'text', text: result.stdout || result.stderr || 'Swarm dispatch completed' },
674
+ ],
675
+ };
676
+ } catch (e) {
677
+ return {
678
+ content: [{ type: 'text', text: `Swarm dispatch failed: ${e.message}` }],
679
+ };
680
+ }
637
681
  }
638
- const budget = req.params?.arguments?.budget || 2000;
639
- const agentDest = getAgentDir();
640
- const fs = require('fs');
641
- if (!fs.existsSync(agentDest)) {
642
- return {
643
- content: [
644
- { type: 'text', text: 'Error: .agent/ directory not found. Run `tk init` first.' },
645
- ],
646
- };
682
+
683
+ if (toolName === 'get_ast_context') {
684
+ const targetPath = req.params?.arguments?.targetPath || process.cwd();
685
+ try {
686
+ const result = spawnSync(
687
+ 'node',
688
+ [path.join(__dirname, '../scripts/build-graph.js'), targetPath],
689
+ {
690
+ encoding: 'utf8',
691
+ timeout: 10000,
692
+ },
693
+ );
694
+ if (result.error) throw result.error;
695
+ return { content: [{ type: 'text', text: result.stdout || result.stderr }] };
696
+ } catch (e) {
697
+ return { content: [{ type: 'text', text: `Failed to query graph: ${e.message}` }] };
698
+ }
647
699
  }
648
- try {
649
- const { _memoryRecall } = require('../dist/commands/memory.js');
650
- const { results, tokens_used } = _memoryRecall(agentDest, query, budget);
651
- if (results.length === 0) {
652
- return { content: [{ type: 'text', text: `No memories match query: "${query}"` }] };
700
+
701
+ if (toolName === 'validate_payload') {
702
+ const payloadStr = req.params?.arguments?.payload;
703
+ const schemaType = req.params?.arguments?.schemaType;
704
+
705
+ if (!payloadStr || typeof payloadStr !== 'string') {
706
+ throw new RpcError(-32602, 'Missing or invalid required argument: payload (string)');
707
+ }
708
+ if (!schemaType || typeof schemaType !== 'string') {
709
+ throw new RpcError(-32602, 'Missing or invalid required argument: schemaType (string)');
653
710
  }
654
- let text = `## Memory Recall (${results.length} results, ~${tokens_used}/${budget} tokens)\n\n`;
655
- for (const entry of results) {
656
- text += `- **[${entry.memory_type.toUpperCase()}]** #${entry.id}: ${entry.content}`;
657
- if (entry.tags.length > 0) text += ` _(${entry.tags.join(', ')})_`;
658
- text += `\n`;
711
+
712
+ const agentDir = getAgentDir();
713
+ const fs = require('fs');
714
+ if (!fs.existsSync(agentDir)) {
715
+ return {
716
+ content: [
717
+ { type: 'text', text: 'Error: .agent/ directory not found. Run `tk init` first.' },
718
+ ],
719
+ };
659
720
  }
721
+
722
+ try {
723
+ const payloadData = JSON.parse(payloadStr);
724
+ const {
725
+ WorkerRequestSchema,
726
+ WorkerResultSchema,
727
+ SwarmPayloadSchema,
728
+ validatePayloadOrThrow,
729
+ } = require(path.join(agentDir, 'scripts', 'payload_schemas.js'));
730
+
731
+ let schema;
732
+ switch (schemaType) {
733
+ case 'worker-request':
734
+ schema = WorkerRequestSchema;
735
+ break;
736
+ case 'worker-result':
737
+ schema = WorkerResultSchema;
738
+ break;
739
+ case 'swarm-payload':
740
+ schema = SwarmPayloadSchema;
741
+ break;
742
+ default:
743
+ throw new RpcError(
744
+ -32602,
745
+ `Invalid schemaType: ${schemaType}. Must be one of: worker-request, worker-result, swarm-payload`,
746
+ );
747
+ }
748
+
749
+ const validatedData = validatePayloadOrThrow(payloadData, schema);
750
+ return {
751
+ content: [
752
+ {
753
+ type: 'text',
754
+ text: `Payload validation successful.\n\nValidated payload:\n${JSON.stringify(validatedData, null, 2)}`,
755
+ },
756
+ ],
757
+ };
758
+ } catch (e) {
759
+ if (e instanceof SyntaxError) {
760
+ return { content: [{ type: 'text', text: `Invalid JSON payload: ${e.message}` }] };
761
+ } else {
762
+ return { content: [{ type: 'text', text: `Payload validation failed: ${e.message}` }] };
763
+ }
764
+ }
765
+ }
766
+
767
+ if (toolName === 'run_tribunal_audit') {
768
+ const text = runTribunalAudit();
660
769
  return { content: [{ type: 'text', text }] };
661
- } catch (e) {
662
- return { content: [{ type: 'text', text: `Memory recall failed: ${e.message}` }] };
663
770
  }
664
- }
665
771
 
666
- if (toolName === 'store_memory') {
667
- const memType = req.params?.arguments?.type;
668
- const content = req.params?.arguments?.content;
669
- const tags = req.params?.arguments?.tags || [];
670
- if (!memType || !content) {
671
- throw new RpcError(-32602, 'Missing required arguments: type (string), content (string)');
772
+ if (toolName === 'sync_ide_bridges') {
773
+ const fs = require('fs');
774
+ const cwd = process.cwd();
775
+ const agentDest = path.join(cwd, '.agent');
776
+ if (!fs.existsSync(agentDest)) {
777
+ return {
778
+ content: [
779
+ {
780
+ type: 'text',
781
+ text: 'Error: .agent/ directory not found. Run `tk init` first.',
782
+ },
783
+ ],
784
+ };
785
+ }
786
+ try {
787
+ const { generateIDEBridges } = require(
788
+ path.resolve(__dirname, '../dist/commands/init.js'),
789
+ );
790
+ // generateIDEBridges is async
791
+ await generateIDEBridges(cwd, agentDest, true);
792
+ return {
793
+ content: [
794
+ {
795
+ type: 'text',
796
+ text: 'Sync complete',
797
+ },
798
+ ],
799
+ };
800
+ } catch (e) {
801
+ return {
802
+ content: [
803
+ {
804
+ type: 'text',
805
+ text: `Sync failed: ${e.message}`,
806
+ },
807
+ ],
808
+ };
809
+ }
672
810
  }
673
- const validTypes = ['semantic', 'procedural', 'episodic', 'working'];
674
- if (!validTypes.includes(memType)) {
675
- throw new RpcError(
676
- -32602,
677
- `Invalid memory type: "${memType}". Must be one of: ${validTypes.join(', ')}`,
678
- );
811
+
812
+ if (toolName === 'search_case_law') {
813
+ const query = req.params && req.params.arguments && req.params.arguments.query;
814
+ if (!query || typeof query !== 'string') {
815
+ throw new RpcError(-32602, 'Missing or invalid required argument: query (string)');
816
+ }
817
+ const text = searchCaseLaw(query);
818
+ return { content: [{ type: 'text', text }] };
679
819
  }
680
- const agentDest = getAgentDir();
681
- const fs = require('fs');
682
- if (!fs.existsSync(agentDest)) {
683
- return {
684
- content: [
685
- { type: 'text', text: 'Error: .agent/ directory not found. Run `tk init` first.' },
686
- ],
687
- };
820
+
821
+ if (toolName === 'list_tribunal_agents') {
822
+ const fs = require('fs');
823
+ const agentDir = path.join(getAgentDir(), 'agents');
824
+ if (!fs.existsSync(agentDir))
825
+ return {
826
+ content: [{ type: 'text', text: 'No agents found or .agent directory missing.' }],
827
+ };
828
+ const agents = fs
829
+ .readdirSync(agentDir)
830
+ .filter(f => f.endsWith('.md'))
831
+ .map(f => f.replace('.md', ''));
832
+ return { content: [{ type: 'text', text: 'Available Agents:\n- ' + agents.join('\n- ') }] };
688
833
  }
689
- try {
690
- const { _memoryStore } = require('../dist/commands/memory.js');
691
- const result = _memoryStore(agentDest, memType, content, tags, null);
692
- return {
693
- content: [
694
- {
695
- type: 'text',
696
- text: `Memory stored: #${result.id} (${memType}, ~${result.token_estimate} tokens)`,
697
- },
698
- ],
699
- };
700
- } catch (e) {
701
- return { content: [{ type: 'text', text: `Memory store failed: ${e.message}` }] };
834
+
835
+ if (toolName === 'get_tribunal_agent') {
836
+ const fs = require('fs');
837
+ const name = req.params?.arguments?.name;
838
+ if (!name || typeof name !== 'string')
839
+ throw new RpcError(-32602, 'Missing or invalid argument: name (string)');
840
+ const sanitizedName = path.basename(name);
841
+ const agentsDir = path.resolve(getAgentDir(), 'agents');
842
+ const agentPath = path.resolve(agentsDir, `${sanitizedName}.md`);
843
+ // Path containment: ensure resolved path stays within agents directory
844
+ if (!agentPath.startsWith(agentsDir))
845
+ throw new RpcError(-32602, 'Invalid agent name: path traversal detected');
846
+ if (!fs.existsSync(agentPath))
847
+ return { content: [{ type: 'text', text: `Agent '${sanitizedName}' not found.` }] };
848
+ const text = fs.readFileSync(agentPath, 'utf8');
849
+ return { content: [{ type: 'text', text: stripBoilerplate(text) }] };
702
850
  }
703
- }
704
851
 
705
- if (toolName === 'align_output') {
706
- const text = req.params?.arguments?.text;
707
- if (typeof text !== 'string') {
708
- throw new RpcError(-32602, 'Missing or invalid required argument: text (string)');
852
+ if (toolName === 'list_tribunal_skills') {
853
+ const fs = require('fs');
854
+ const skillsDir = path.join(getAgentDir(), 'skills');
855
+ if (!fs.existsSync(skillsDir))
856
+ return {
857
+ content: [{ type: 'text', text: 'No skills found or .agent directory missing.' }],
858
+ };
859
+ const skills = fs
860
+ .readdirSync(skillsDir, { withFileTypes: true })
861
+ .filter(d => d.isDirectory())
862
+ .map(d => d.name);
863
+ return { content: [{ type: 'text', text: 'Available Skills:\n- ' + skills.join('\n- ') }] };
709
864
  }
710
- try {
711
- const { alignText, validateCodeContent } = require('../dist/commands/align.js');
712
- const aligned = alignText(text);
713
- const warnings = validateCodeContent(aligned);
714
-
715
- let outputText = aligned;
716
- if (warnings.length > 0) {
717
- outputText += '\n\n⚠️ OCAE Alignment Validator Warnings:\n';
718
- for (const warnMsg of warnings) {
719
- outputText += `● ${warnMsg}\n`;
720
- }
721
- }
722
- return { content: [{ type: 'text', text: outputText }] };
723
- } catch (e) {
724
- return { content: [{ type: 'text', text: `Output alignment failed: ${e.message}` }] };
865
+
866
+ if (toolName === 'get_tribunal_skill') {
867
+ const fs = require('fs');
868
+ const name = req.params?.arguments?.name;
869
+ if (!name || typeof name !== 'string')
870
+ throw new RpcError(-32602, 'Missing or invalid argument: name (string)');
871
+ const sanitizedName = path.basename(name);
872
+ const skillsDir = path.resolve(getAgentDir(), 'skills');
873
+ const skillPath = path.resolve(skillsDir, sanitizedName, 'SKILL.md');
874
+ // Path containment: ensure resolved path stays within skills directory
875
+ if (!skillPath.startsWith(skillsDir))
876
+ throw new RpcError(-32602, 'Invalid skill name: path traversal detected');
877
+ if (!fs.existsSync(skillPath))
878
+ return { content: [{ type: 'text', text: `Skill '${sanitizedName}' not found.` }] };
879
+ const text = fs.readFileSync(skillPath, 'utf8');
880
+ return { content: [{ type: 'text', text: stripBoilerplate(text) }] };
725
881
  }
726
- }
727
882
 
728
- if (toolName === 'verify_contracts') {
729
- const file = req.params?.arguments?.file;
730
- const content = req.params?.arguments?.content;
883
+ if (toolName === 'get_sparse_context') {
884
+ const task = req.params?.arguments?.task;
885
+ const files = req.params?.arguments?.files || [];
886
+ const model = req.params?.arguments?.model || 'large';
731
887
 
732
- if (!file || typeof file !== 'string' || typeof content !== 'string') {
733
- throw new RpcError(
734
- -32602,
735
- 'Missing or invalid required arguments: file (string), content (string)',
736
- );
737
- }
888
+ if (!task) throw new RpcError(-32602, 'Missing required argument: task');
738
889
 
739
- try {
740
- const projectRoot = process.cwd();
741
- const contractEnginePath = path.join(getAgentDir(), 'scripts', 'contract_engine.js');
742
- if (!require('fs').existsSync(contractEnginePath)) {
890
+ const agentDest = getAgentDir();
891
+ const fs = require('fs');
892
+ if (!fs.existsSync(agentDest)) {
743
893
  return {
744
894
  content: [
745
- { type: 'text', text: 'Error: contract_engine.js not found. Run `tk init` first.' },
895
+ { type: 'text', text: 'Error: .agent/ directory not found. Run `tk init` first.' },
746
896
  ],
747
897
  };
748
898
  }
749
899
 
750
- const contractEngine = require(contractEnginePath);
751
- const contracts = contractEngine.loadContracts(projectRoot);
900
+ try {
901
+ const brokerScript = path.join(agentDest, 'scripts', 'context_broker.js');
902
+ const { broker } = require(brokerScript);
903
+ const brokerResult = broker(task, files, model, agentDest);
904
+ return { content: [{ type: 'text', text: stripBoilerplate(brokerResult.promptText) }] };
905
+ } catch (e) {
906
+ return {
907
+ content: [{ type: 'text', text: `Failed to retrieve sparse context: ${e.message}` }],
908
+ };
909
+ }
910
+ }
752
911
 
753
- if (contracts.length === 0) {
912
+ if (toolName === 'recall_memory') {
913
+ const query = req.params?.arguments?.query;
914
+ if (!query || typeof query !== 'string') {
915
+ throw new RpcError(-32602, 'Missing or invalid required argument: query (string)');
916
+ }
917
+ const budget = req.params?.arguments?.budget || 2000;
918
+ const agentDest = getAgentDir();
919
+ const fs = require('fs');
920
+ if (!fs.existsSync(agentDest)) {
754
921
  return {
755
922
  content: [
756
- {
757
- type: 'text',
758
- text: 'No active behavioral contracts found in .tribunal/contracts/.',
759
- },
923
+ { type: 'text', text: 'Error: .agent/ directory not found. Run `tk init` first.' },
760
924
  ],
761
925
  };
762
926
  }
763
-
764
- const relativePath = path.relative(projectRoot, file).replace(/\\/g, '/');
765
- const allViolations = [];
766
-
767
- for (const contract of contracts) {
768
- const vList = contractEngine.evaluateContract(contract, relativePath, content);
769
- if (vList.length > 0) {
770
- allViolations.push(...vList);
927
+ try {
928
+ const { _memoryRecall } = require('../dist/commands/memory.js');
929
+ const { results, tokens_used } = _memoryRecall(agentDest, query, budget);
930
+ if (results.length === 0) {
931
+ return { content: [{ type: 'text', text: `No memories match query: "${query}"` }] };
932
+ }
933
+ let text = `## Memory Recall (${results.length} results, ~${tokens_used}/${budget} tokens)\n\n`;
934
+ for (const entry of results) {
935
+ text += `- **[${entry.memory_type.toUpperCase()}]** #${entry.id}: ${entry.content}`;
936
+ if (entry.tags.length > 0) text += ` _(${entry.tags.join(', ')})_`;
937
+ text += `\n`;
771
938
  }
939
+ return { content: [{ type: 'text', text }] };
940
+ } catch (e) {
941
+ return { content: [{ type: 'text', text: `Memory recall failed: ${e.message}` }] };
772
942
  }
943
+ }
773
944
 
774
- if (allViolations.length === 0) {
945
+ if (toolName === 'store_memory') {
946
+ const memType = req.params?.arguments?.type;
947
+ const content = req.params?.arguments?.content;
948
+ const tags = req.params?.arguments?.tags || [];
949
+ if (!memType || !content) {
950
+ throw new RpcError(-32602, 'Missing required arguments: type (string), content (string)');
951
+ }
952
+ const validTypes = ['semantic', 'procedural', 'episodic', 'working'];
953
+ if (!validTypes.includes(memType)) {
954
+ throw new RpcError(
955
+ -32602,
956
+ `Invalid memory type: "${memType}". Must be one of: ${validTypes.join(', ')}`,
957
+ );
958
+ }
959
+ const agentDest = getAgentDir();
960
+ const fs = require('fs');
961
+ if (!fs.existsSync(agentDest)) {
962
+ return {
963
+ content: [
964
+ { type: 'text', text: 'Error: .agent/ directory not found. Run `tk init` first.' },
965
+ ],
966
+ };
967
+ }
968
+ try {
969
+ const { _memoryStore } = require('../dist/commands/memory.js');
970
+ const result = _memoryStore(agentDest, memType, content, tags, null);
775
971
  return {
776
972
  content: [
777
973
  {
778
974
  type: 'text',
779
- text: '✅ Contract check passed. Zero behavioral violations detected.',
975
+ text: `Memory stored: #${result.id} (${memType}, ~${result.token_estimate} tokens)`,
780
976
  },
781
977
  ],
782
978
  };
979
+ } catch (e) {
980
+ return { content: [{ type: 'text', text: `Memory store failed: ${e.message}` }] };
783
981
  }
982
+ }
784
983
 
785
- let report = `📜 Contract Verification Results (${allViolations.length} violations):\n`;
786
- for (const v of allViolations) {
787
- report += `● [${v.severity.toUpperCase()}] ${v.contract}: ${v.message}\n`;
788
- if (v.line) report += ` Line ${v.line}: ${v.snippet || ''}\n`;
984
+ if (toolName === 'query_semantic_graph') {
985
+ const targetPath = req.params?.arguments?.targetPath || process.cwd();
986
+ try {
987
+ const result = spawnSync(
988
+ 'node',
989
+ [path.join(__dirname, '../scripts/build-graph.js'), targetPath],
990
+ {
991
+ encoding: 'utf8',
992
+ timeout: 10000,
993
+ },
994
+ );
995
+ if (result.error) throw result.error;
996
+ return { content: [{ type: 'text', text: result.stdout || result.stderr }] };
997
+ } catch (e) {
998
+ return { content: [{ type: 'text', text: `Failed to query graph: ${e.message}` }] };
789
999
  }
1000
+ }
790
1001
 
791
- return { content: [{ type: 'text', text: report }] };
792
- } catch (e) {
793
- return { content: [{ type: 'text', text: `Contract verification failed: ${e.message}` }] };
1002
+ if (toolName === 'align_output') {
1003
+ const text = req.params?.arguments?.text;
1004
+ if (typeof text !== 'string') {
1005
+ throw new RpcError(-32602, 'Missing or invalid required argument: text (string)');
1006
+ }
1007
+ try {
1008
+ const { alignText, validateCodeContent } = require('../dist/commands/align.js');
1009
+ const aligned = alignText(text);
1010
+ const warnings = validateCodeContent(aligned);
1011
+
1012
+ let outputText = aligned;
1013
+ if (warnings.length > 0) {
1014
+ outputText += '\n\n⚠️ OCAE Alignment Validator Warnings:\n';
1015
+ for (const warnMsg of warnings) {
1016
+ outputText += `● ${warnMsg}\n`;
1017
+ }
1018
+ }
1019
+ return { content: [{ type: 'text', text: outputText }] };
1020
+ } catch (e) {
1021
+ return { content: [{ type: 'text', text: `Output alignment failed: ${e.message}` }] };
1022
+ }
794
1023
  }
795
- }
796
1024
 
797
- throw new RpcError(-32601, `Unknown tool: ${toolName}`);
1025
+ if (toolName === 'verify_contracts') {
1026
+ const file = req.params?.arguments?.file;
1027
+ const content = req.params?.arguments?.content;
1028
+
1029
+ if (!file || typeof file !== 'string' || typeof content !== 'string') {
1030
+ throw new RpcError(
1031
+ -32602,
1032
+ 'Missing or invalid required arguments: file (string), content (string)',
1033
+ );
1034
+ }
1035
+
1036
+ try {
1037
+ const projectRoot = process.cwd();
1038
+ const contractEnginePath = path.join(getAgentDir(), 'scripts', 'contract_engine.js');
1039
+ if (!require('fs').existsSync(contractEnginePath)) {
1040
+ return {
1041
+ content: [
1042
+ { type: 'text', text: 'Error: contract_engine.js not found. Run `tk init` first.' },
1043
+ ],
1044
+ };
1045
+ }
1046
+
1047
+ const contractEngine = require(contractEnginePath);
1048
+ const contracts = contractEngine.loadContracts(projectRoot);
1049
+
1050
+ if (contracts.length === 0) {
1051
+ return {
1052
+ content: [
1053
+ {
1054
+ type: 'text',
1055
+ text: 'No active behavioral contracts found in .tribunal/contracts/.',
1056
+ },
1057
+ ],
1058
+ };
1059
+ }
1060
+
1061
+ const relativePath = path.relative(projectRoot, file).replace(/\\/g, '/');
1062
+ const allViolations = [];
1063
+
1064
+ for (const contract of contracts) {
1065
+ const vList = contractEngine.evaluateContract(contract, relativePath, content);
1066
+ if (vList.length > 0) {
1067
+ allViolations.push(...vList);
1068
+ }
1069
+ }
1070
+
1071
+ if (allViolations.length === 0) {
1072
+ return {
1073
+ content: [
1074
+ {
1075
+ type: 'text',
1076
+ text: '✅ Contract check passed. Zero behavioral violations detected.',
1077
+ },
1078
+ ],
1079
+ };
1080
+ }
1081
+
1082
+ let report = `📜 Contract Verification Results (${allViolations.length} violations):\n`;
1083
+ for (const v of allViolations) {
1084
+ report += `● [${v.severity.toUpperCase()}] ${v.contract}: ${v.message}\n`;
1085
+ if (v.line) report += ` Line ${v.line}: ${v.snippet || ''}\n`;
1086
+ }
1087
+
1088
+ return { content: [{ type: 'text', text: report }] };
1089
+ } catch (e) {
1090
+ return {
1091
+ content: [{ type: 'text', text: `Contract verification failed: ${e.message}` }],
1092
+ };
1093
+ }
1094
+ }
1095
+
1096
+ throw new RpcError(-32601, `Unknown tool: ${toolName}`);
1097
+ };
1098
+
1099
+ try {
1100
+ const result = await withToolTimeout(executeTool, GENERAL_TOOL_TIMEOUT_MS);
1101
+ if (reminder) {
1102
+ result.content.push({ type: 'text', text: '\n\n' + reminder });
1103
+ }
1104
+ return result;
1105
+ } catch (e) {
1106
+ if (e.message === 'TOOL_TIMEOUT') {
1107
+ const errorMsg = `Error: Tool execution timed out after ${GENERAL_TOOL_TIMEOUT_MS}ms`;
1108
+ const result = { content: [{ type: 'text', text: errorMsg }] };
1109
+ if (reminder) result.content.push({ type: 'text', text: '\n\n' + reminder });
1110
+ return result;
1111
+ }
1112
+ throw e;
1113
+ }
798
1114
  }
799
1115
 
800
1116
  throw new RpcError(-32601, `Unknown method: ${req.method}`);
801
1117
  }
802
1118
 
803
- function processSingleRequest(req) {
1119
+ async function processSingleRequest(req) {
804
1120
  try {
805
- const result = handleRequest(req);
1121
+ const result = await handleRequest(req);
806
1122
  // If it's a notification, do not send a response
807
1123
  if (req.id === undefined || req.id === null) {
808
1124
  return null;
@@ -826,7 +1142,7 @@ function processSingleRequest(req) {
826
1142
  }
827
1143
  }
828
1144
 
829
- rl.on('line', line => {
1145
+ rl.on('line', async line => {
830
1146
  if (line.length > 1048576) {
831
1147
  // 1MB limit
832
1148
  const errorRes = {
@@ -856,7 +1172,7 @@ rl.on('line', line => {
856
1172
  if (Array.isArray(req)) {
857
1173
  const responses = [];
858
1174
  for (const singleReq of req) {
859
- const response = processSingleRequest(singleReq);
1175
+ const response = await processSingleRequest(singleReq);
860
1176
  if (response) {
861
1177
  responses.push(response);
862
1178
  }
@@ -865,7 +1181,7 @@ rl.on('line', line => {
865
1181
  console.log(JSON.stringify(responses));
866
1182
  }
867
1183
  } else {
868
- const response = processSingleRequest(req);
1184
+ const response = await processSingleRequest(req);
869
1185
  if (response) {
870
1186
  console.log(JSON.stringify(response));
871
1187
  }