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
@@ -0,0 +1,21 @@
1
+ {
2
+ "file": "test/unit/production_readiness_evidence.test.js",
3
+ "riskScore": "Low",
4
+ "blastRadius": 0,
5
+ "imports": {
6
+ "fs": [],
7
+ "path": [],
8
+ "child_process": [],
9
+ "../../.agent/scripts/impact_classifier": [],
10
+ "../../.agent/scripts/socratic_gate_policy": [],
11
+ "../../.agent/scripts/token_budget_broker": [],
12
+ "../../.agent/scripts/integrity_manifest": [],
13
+ "../../bin/mcp-server": [
14
+ "handleRequest",
15
+ "stripBoilerplate",
16
+ "runTribunalAudit"
17
+ ]
18
+ },
19
+ "dependents": [],
20
+ "content": "/**\n * production_readiness_evidence.test.js\n * ══════════════════════════════════════════════════════════════════\n * Empirical evidence that the Tribunal-Kit repository state is:\n * 1. SAFE — input sanitization, path bounds, secret hygiene\n * 2. DETERMINISTIC — repeatable tier classification and budgets\n * 3. FAILURE TOLERANT — JS fallback, graceful degradation\n * 4. OBSERVABLE — structured error outputs, exit codes, audit logs\n * 5. REPRODUCIBLE — stable manifest hashes, version metadata\n * 6. PRODUCTION RELEASE SUITABLE — no phantom references, no count drift\n *\n * Zero external dependencies. Tests use only Node.js stdlib + Jest.\n */\n\n'use strict';\n\nconst fs = require('fs');\nconst path = require('path');\nconst { execFileSync } = require('child_process');\n\nconst fail = (msg = 'Test failed') => {\n throw new Error(msg);\n};\n\nconst ROOT = path.resolve(__dirname, '../..');\n\n// ─── Module Under Test: Impact Classifier ────────────────────────────────────\nconst { classifyImpact } = require('../../.agent/scripts/impact_classifier');\n\n// ─── Module Under Test: Socratic Gate Policy ─────────────────────────────────\nconst { evaluateSocraticGate } = require('../../.agent/scripts/socratic_gate_policy');\n\n// ─── Module Under Test: Token Budget Broker ──────────────────────────────────\nconst { getTokenBudget, TIER_TOKEN_LIMITS } = require('../../.agent/scripts/token_budget_broker');\n\n// ─── Module Under Test: Integrity Manifest ───────────────────────────────────\nconst {\n generateManifest,\n parseFrontmatter,\n crawlAgents: _crawlAgents,\n crawlSkills: _crawlSkills,\n crawlScripts: _crawlScripts,\n crawlWorkflows: _crawlWorkflows,\n} = require('../../.agent/scripts/integrity_manifest');\n\n// ─── Module Under Test: MCP Server ───────────────────────────────────────────\nprocess.env.NODE_ENV = 'test';\nconst { handleRequest, stripBoilerplate } = require('../../bin/mcp-server');\n\n// ─────────────────────────────────────────────────────────────────────────────\n// SECTION 1: SAFETY\n// ─────────────────────────────────────────────────────────────────────────────\n\ndescribe('SAFETY: Input Sanitization & Boundary Enforcement', () => {\n describe('Impact Classifier — malformed inputs', () => {\n test('handles null opts gracefully', async () => {\n const result = classifyImpact(null);\n expect(result).toHaveProperty('tier');\n expect(typeof result.tier).toBe('number');\n expect(result.tier).toBeGreaterThanOrEqual(0);\n expect(result.tier).toBeLessThanOrEqual(3);\n });\n\n test('handles undefined opts gracefully', async () => {\n const result = classifyImpact(undefined);\n expect(result).toHaveProperty('tier');\n expect(typeof result.score).toBe('number');\n });\n\n test('handles empty object gracefully', async () => {\n const result = classifyImpact({});\n expect(result).toHaveProperty('tier');\n expect(result).toHaveProperty('score');\n expect(result).toHaveProperty('maxReviewers');\n expect(result).toHaveProperty('requireGate');\n expect(result).toHaveProperty('reasoning');\n expect(result).toHaveProperty('fastPass');\n });\n\n test('filters non-string entries from files array', async () => {\n const result = classifyImpact({\n files: [null, undefined, 42, '', 'valid.css', { name: 'bad' }],\n task: 'fix style',\n lineCount: 2,\n });\n expect(result).toHaveProperty('tier');\n // Only \"valid.css\" should be considered\n expect(result.tier).toBe(0); // CSS file, small edit => Tier 0\n });\n\n test('handles numeric task gracefully (type coercion safety)', async () => {\n const result = classifyImpact({ task: 12345, files: [] });\n expect(result).toHaveProperty('tier');\n // Numeric task should be treated as empty string, not crash\n });\n\n test('handles negative lineCount gracefully', async () => {\n const result = classifyImpact({ lineCount: -10 });\n expect(result).toHaveProperty('tier');\n expect(result.tier).toBeGreaterThanOrEqual(0);\n });\n\n test('handles Infinity lineCount gracefully', async () => {\n const result = classifyImpact({ lineCount: Infinity });\n expect(result).toHaveProperty('tier');\n });\n\n test('handles NaN lineCount gracefully', async () => {\n const result = classifyImpact({ lineCount: NaN });\n expect(result).toHaveProperty('tier');\n });\n });\n\n describe('Token Budget Broker — boundary inputs', () => {\n test('handles negative tier by clamping to 0', async () => {\n const result = getTokenBudget(-5);\n expect(result.tier).toBe(0);\n expect(result.maxTokens).toBe(0);\n });\n\n test('handles tier > 3 by clamping to 3', async () => {\n const result = getTokenBudget(99);\n expect(result.tier).toBe(3);\n expect(result.maxTokens).toBe(32000);\n });\n\n test('handles NaN tier by defaulting to tier 1', async () => {\n const result = getTokenBudget(NaN);\n expect(result.tier).toBe(1);\n expect(result.maxTokens).toBe(2000);\n });\n\n test('handles string tier by defaulting to tier 1', async () => {\n const result = getTokenBudget('invalid');\n expect(result.tier).toBe(1);\n expect(result.maxTokens).toBe(2000);\n });\n\n test('handles undefined tier by defaulting to tier 1', async () => {\n const result = getTokenBudget(undefined);\n expect(result.tier).toBe(1);\n expect(result.maxTokens).toBe(2000);\n });\n\n test('rounds fractional tiers to nearest integer', async () => {\n const result = getTokenBudget(1.7);\n expect(result.tier).toBe(2);\n expect(result.maxTokens).toBe(8000);\n });\n });\n\n describe('MCP Server — argument validation', () => {\n test('rejects tools/call with missing params.name', async () => {\n const req = { jsonrpc: '2.0', id: 1, method: 'tools/call', params: {} };\n await expect(handleRequest(req)).rejects.toThrow();\n });\n\n test('rejects unknown tool name with -32601', async () => {\n const req = {\n jsonrpc: '2.0',\n id: 1,\n method: 'tools/call',\n params: { name: 'nonexistent_tool_xyz' },\n };\n try {\n await handleRequest(req);\n fail('Should have thrown');\n } catch (e) {\n expect(e.code).toBe(-32601);\n }\n });\n\n test('rejects search_case_law with non-string query', async () => {\n const req = {\n jsonrpc: '2.0',\n id: 1,\n method: 'tools/call',\n params: { name: 'search_case_law', arguments: { query: 123 } },\n };\n try {\n await handleRequest(req);\n fail('Should have thrown');\n } catch (e) {\n expect(e.code).toBe(-32602);\n }\n });\n\n test('rejects get_tribunal_agent with non-string name', async () => {\n const req = {\n jsonrpc: '2.0',\n id: 1,\n method: 'tools/call',\n params: { name: 'get_tribunal_agent', arguments: { name: 42 } },\n };\n try {\n await handleRequest(req);\n fail('Should have thrown');\n } catch (e) {\n expect(e.code).toBe(-32602);\n }\n });\n\n test('rejects get_tribunal_skill with non-string name', async () => {\n const req = {\n jsonrpc: '2.0',\n id: 1,\n method: 'tools/call',\n params: { name: 'get_tribunal_skill', arguments: { name: null } },\n };\n try {\n await handleRequest(req);\n fail('Should have thrown');\n } catch (e) {\n expect(e.code).toBe(-32602);\n }\n });\n\n test('rejects store_memory with invalid type', async () => {\n const req = {\n jsonrpc: '2.0',\n id: 1,\n method: 'tools/call',\n params: { name: 'store_memory', arguments: { type: 'invalid_type', content: 'test' } },\n };\n try {\n await handleRequest(req);\n fail('Should have thrown');\n } catch (e) {\n expect(e.code).toBe(-32602);\n }\n });\n });\n\n describe('MCP Server — path traversal defense', () => {\n test('get_tribunal_agent sanitizes path traversal attempts', async () => {\n const req = {\n jsonrpc: '2.0',\n id: 1,\n method: 'tools/call',\n params: { name: 'get_tribunal_agent', arguments: { name: '../../etc/passwd' } },\n };\n // Should not throw a path traversal error — path.basename strips traversal\n // Should return \"not found\" because \"passwd\" is not an agent\n const result = await handleRequest(req);\n expect(result.content[0].text).toContain('not found');\n });\n\n test('get_tribunal_skill sanitizes path traversal attempts', async () => {\n const req = {\n jsonrpc: '2.0',\n id: 1,\n method: 'tools/call',\n params: { name: 'get_tribunal_skill', arguments: { name: '../../../etc/shadow' } },\n };\n const result = await handleRequest(req);\n expect(result.content[0].text).toContain('not found');\n });\n });\n\n describe('Socratic Gate — flag override safety', () => {\n test('only recognized flags bypass the gate', async () => {\n // Unrecognized flag should NOT bypass\n const result = evaluateSocraticGate({ tier: 3, flags: { randomFlag: true } });\n expect(result.shouldBlock).toBe(true);\n });\n\n test('no-gate flag bypasses even Tier 3', async () => {\n const result = evaluateSocraticGate({ tier: 3, flags: { 'no-gate': true } });\n expect(result.shouldBlock).toBe(false);\n });\n });\n});\n\n// ─────────────────────────────────────────────────────────────────────────────\n// SECTION 2: DETERMINISM\n// ─────────────────────────────────────────────────────────────────────────────\n\ndescribe('DETERMINISM: Repeatable Classification & Budget Allocation', () => {\n describe('Impact Classifier — deterministic tier assignment', () => {\n const DETERMINISTIC_CASES = [\n {\n label: 'CSS typo fix → Tier 0',\n input: { files: ['src/button.css'], task: 'fix typo in comment', lineCount: 3 },\n expectedTier: 0,\n },\n {\n label: 'Single component logic → Tier 1',\n input: { files: ['src/Header.tsx'], task: 'update toggle', lineCount: 15 },\n expectedTier: 1,\n },\n {\n label: 'Auth file → Tier 3 (critical path)',\n input: { files: ['src/auth/jwt.ts'], task: 'update token check', lineCount: 8 },\n expectedTier: 3,\n },\n {\n label: 'Schema migration → Tier 3 (critical path)',\n input: { files: ['db/schema.prisma'], task: 'add user column', lineCount: 5 },\n expectedTier: 3,\n },\n {\n label: 'Multiple large files → Tier 2+',\n input: {\n files: ['src/api/routes.ts', 'src/api/handlers.ts'],\n task: 'refactor API',\n lineCount: 120,\n },\n expectedTier: 3, // refactor + large diff\n },\n ];\n\n test.each(DETERMINISTIC_CASES)('$label', ({ input, expectedTier }) => {\n // Run 10 times to prove determinism\n for (let i = 0; i < 10; i++) {\n const result = classifyImpact(input);\n expect(result.tier).toBe(expectedTier);\n }\n });\n\n test('score is always a finite number between 0 and 1', async () => {\n const cases = [\n {},\n { files: ['a.css'], task: 'typo', lineCount: 1 },\n { files: ['auth.ts'], task: 'security refactor migration', lineCount: 500 },\n ];\n for (const input of cases) {\n const result = classifyImpact(input);\n expect(Number.isFinite(result.score)).toBe(true);\n expect(result.score).toBeGreaterThanOrEqual(0);\n expect(result.score).toBeLessThanOrEqual(1);\n }\n });\n\n test('fastPass is true if and only if tier === 0', async () => {\n const tier0 = classifyImpact({ files: ['readme.md'], task: 'fix docs typo', lineCount: 1 });\n expect(tier0.fastPass).toBe(tier0.tier === 0);\n\n const tier3 = classifyImpact({ files: ['auth.ts'], task: 'update jwt', lineCount: 10 });\n expect(tier3.fastPass).toBe(false);\n });\n });\n\n describe('Token Budget Broker — deterministic budget allocation', () => {\n test.each([\n [0, 0],\n [1, 2000],\n [2, 8000],\n [3, 32000],\n ])('Tier %i always returns %i maxTokens', (tier, expectedTokens) => {\n for (let i = 0; i < 10; i++) {\n expect(getTokenBudget(tier).maxTokens).toBe(expectedTokens);\n }\n });\n\n test('TIER_TOKEN_LIMITS is immutable (frozen or consistent)', async () => {\n expect(TIER_TOKEN_LIMITS[0]).toBe(0);\n expect(TIER_TOKEN_LIMITS[1]).toBe(2000);\n expect(TIER_TOKEN_LIMITS[2]).toBe(8000);\n expect(TIER_TOKEN_LIMITS[3]).toBe(32000);\n });\n });\n\n describe('Socratic Gate — deterministic decisions', () => {\n test.each([\n [0, 0.0, false],\n [1, 0.0, false],\n [2, 0.3, false],\n [2, 0.8, true],\n [3, 0.0, true],\n ])('Tier %i + ambiguity %d → shouldBlock = %s', (tier, ambiguityScore, expected) => {\n for (let i = 0; i < 10; i++) {\n expect(evaluateSocraticGate({ tier, ambiguityScore }).shouldBlock).toBe(expected);\n }\n });\n });\n});\n\n// ─────────────────────────────────────────────────────────────────────────────\n// SECTION 3: FAILURE TOLERANCE\n// ─────────────────────────────────────────────────────────────────────────────\n\ndescribe('FAILURE TOLERANCE: Graceful Degradation Under Failure', () => {\n test('wrapper.js runRustBinary contains graceful fallback pattern (not process.exit on spawn error)', async () => {\n const wrapperSource = fs.readFileSync(path.join(ROOT, 'bin/wrapper.js'), 'utf8');\n\n // Extract the runRustBinary function body to verify it returns false on error\n // instead of calling process.exit(1)\n const fnMatch = wrapperSource.match(/function runRustBinary[\\s\\S]*?^}/m);\n expect(fnMatch).not.toBeNull();\n const fnBody = fnMatch[0];\n\n // The old dangerous pattern: process.exit(1) inside runRustBinary on result.error\n expect(fnBody).not.toContain('process.exit(1)');\n\n // The new safe pattern: return false to fall through\n expect(fnBody).toContain('return false');\n expect(wrapperSource).toContain('Falling back to JS engine');\n });\n\n test('TRIBUNAL_FORCE_JS=1 forces JS fallback without crashing', async () => {\n const wrapperPath = path.join(ROOT, 'bin/wrapper.js');\n // This should not throw — it should execute via JS engine\n try {\n const out = execFileSync(process.execPath, [wrapperPath, 'status'], {\n encoding: 'utf8',\n env: { ...process.env, TRIBUNAL_FORCE_JS: '1' },\n stdio: ['ignore', 'pipe', 'pipe'],\n timeout: 15000,\n });\n expect(typeof out).toBe('string');\n } catch (err) {\n // Non-zero exit is acceptable (e.g. \"not installed\"), but unhandled crash is not\n expect(err.status).toBeDefined();\n expect(err.status).not.toBe(null);\n }\n });\n\n test('MCP server handles batch JSON-RPC requests (protocol resilience)', async () => {\n const batch = [\n { jsonrpc: '2.0', id: 1, method: 'ping' },\n { jsonrpc: '2.0', id: 2, method: 'resources/list' },\n ];\n // Simulate batch by processing individually (batch is handled in rl.on line)\n for (const req of batch) {\n const result = await handleRequest(req);\n expect(result).toBeDefined();\n }\n });\n\n test('MCP server handles notification methods without response (no crash)', async () => {\n const notificationReq = { method: 'notifications/initialized' };\n const result = await handleRequest(notificationReq);\n expect(result).toBeNull();\n });\n\n test('MCP server returns proper error for unknown methods', async () => {\n const req = { jsonrpc: '2.0', id: 99, method: 'completely/unknown' };\n try {\n await handleRequest(req);\n fail('Should have thrown');\n } catch (e) {\n expect(e.code).toBe(-32601);\n expect(e.message).toContain('Unknown method');\n }\n });\n\n test('integrity manifest handles missing .agent directory gracefully', async () => {\n const result = generateManifest('/nonexistent/path/that/does/not/exist');\n expect(result).toHaveProperty('error');\n expect(typeof result.error).toBe('string');\n });\n\n test('parseFrontmatter handles empty string without crash', async () => {\n expect(parseFrontmatter('')).toEqual({});\n });\n\n test('parseFrontmatter handles malformed YAML without crash', async () => {\n const malformed = '---\\n\\n---\\n\\nContent';\n const result = parseFrontmatter(malformed);\n // Should return empty object (no valid key-value pairs)\n expect(typeof result).toBe('object');\n });\n});\n\n// ─────────────────────────────────────────────────────────────────────────────\n// SECTION 4: OBSERVABILITY\n// ─────────────────────────────────────────────────────────────────────────────\n\ndescribe('OBSERVABILITY: Structured Errors & Audit Outputs', () => {\n test('MCP initialize response contains valid server info', async () => {\n const req = { jsonrpc: '2.0', id: 1, method: 'initialize', params: {} };\n const result = await handleRequest(req);\n\n expect(result.protocolVersion).toBe('2025-03-26');\n expect(result.serverInfo.name).toBe('tribunal-kit-mcp');\n expect(typeof result.serverInfo.version).toBe('string');\n expect(result.serverInfo.version).toMatch(/^\\d+\\.\\d+\\.\\d+$/);\n });\n\n test('MCP tools/list returns well-formed tool descriptors', async () => {\n const req = { jsonrpc: '2.0', id: 1, method: 'tools/list', params: {} };\n const result = await handleRequest(req);\n\n expect(Array.isArray(result.tools)).toBe(true);\n expect(result.tools.length).toBeGreaterThan(0);\n\n for (const tool of result.tools) {\n expect(typeof tool.name).toBe('string');\n expect(tool.name.length).toBeGreaterThan(0);\n expect(typeof tool.description).toBe('string');\n expect(tool.inputSchema).toBeDefined();\n expect(tool.inputSchema.type).toBe('object');\n }\n });\n\n test('Impact classifier returns structured reasoning array', async () => {\n const result = classifyImpact({ files: ['auth.ts'], task: 'update jwt', lineCount: 10 });\n expect(Array.isArray(result.reasoning)).toBe(true);\n expect(result.reasoning.length).toBeGreaterThan(0);\n for (const reason of result.reasoning) {\n expect(typeof reason).toBe('string');\n expect(reason.length).toBeGreaterThan(0);\n }\n });\n\n test('Socratic gate returns structured reason string', async () => {\n const result = evaluateSocraticGate({ tier: 3 });\n expect(typeof result.reason).toBe('string');\n expect(result.reason.length).toBeGreaterThan(0);\n });\n\n test('Token budget returns all expected fields for observability', async () => {\n const result = getTokenBudget(2);\n expect(result).toHaveProperty('tier');\n expect(result).toHaveProperty('maxTokens');\n expect(result).toHaveProperty('includeFullRepo');\n expect(result).toHaveProperty('maxSkills');\n expect(result).toHaveProperty('maxReviewers');\n });\n\n test('stripBoilerplate function preserves core content while removing guardrail boilerplate', async () => {\n const input = 'Core content here.\\n\\n## Pre-Flight Checklist\\nBoilerplate content';\n const result = stripBoilerplate(input);\n expect(result).toContain('Core content here.');\n expect(result).not.toContain('Pre-Flight Checklist');\n });\n});\n\n// ─────────────────────────────────────────────────────────────────────────────\n// SECTION 5: REPRODUCIBILITY\n// ─────────────────────────────────────────────────────────────────────────────\n\ndescribe('REPRODUCIBILITY: Stable Manifests & Version Metadata', () => {\n test('integrity manifest is reproducible (two runs produce identical structure)', async () => {\n const manifest1 = generateManifest(ROOT);\n const manifest2 = generateManifest(ROOT);\n\n // Remove timestamps for comparison\n delete manifest1.generated_at;\n delete manifest2.generated_at;\n\n expect(manifest1.agents.total).toBe(manifest2.agents.total);\n expect(manifest1.skills.total).toBe(manifest2.skills.total);\n expect(manifest1.scripts.total).toBe(manifest2.scripts.total);\n expect(manifest1.workflows.total).toBe(manifest2.workflows.total);\n expect(manifest1.integrity.phantom_references).toBe(manifest2.integrity.phantom_references);\n expect(manifest1.integrity.invalid_claims).toBe(manifest2.integrity.invalid_claims);\n });\n\n test('package.json version matches Cargo.toml version', async () => {\n const pkg = JSON.parse(fs.readFileSync(path.join(ROOT, 'package.json'), 'utf8'));\n const cargoToml = fs.readFileSync(path.join(ROOT, 'crates/core/Cargo.toml'), 'utf8');\n const cargoVersion = cargoToml.match(/^version\\s*=\\s*\"([^\"]+)\"/m)?.[1];\n\n expect(cargoVersion).toBe(pkg.version);\n });\n\n test('package.json version matches Cargo.lock tribunal-core version', async () => {\n const pkg = JSON.parse(fs.readFileSync(path.join(ROOT, 'package.json'), 'utf8'));\n const cargoLock = fs.readFileSync(path.join(ROOT, 'Cargo.lock'), 'utf8');\n const lockVersion = cargoLock.match(\n /name\\s*=\\s*\"tribunal-core\"\\s+version\\s*=\\s*\"([^\"]+)\"/,\n )?.[1];\n\n expect(lockVersion).toBe(pkg.version);\n });\n\n test('optionalDependencies versions are in sync with package.json version', async () => {\n const pkg = JSON.parse(fs.readFileSync(path.join(ROOT, 'package.json'), 'utf8'));\n const expectedVersion = `^${pkg.version}`;\n\n for (const [_name, version] of Object.entries(pkg.optionalDependencies || {})) {\n expect(version).toBe(expectedVersion);\n }\n });\n\n test('package-lock.json root version matches package.json', async () => {\n const pkg = JSON.parse(fs.readFileSync(path.join(ROOT, 'package.json'), 'utf8'));\n const lock = JSON.parse(fs.readFileSync(path.join(ROOT, 'package-lock.json'), 'utf8'));\n\n expect(lock.version).toBe(pkg.version);\n });\n});\n\n// ─────────────────────────────────────────────────────────────────────────────\n// SECTION 6: PRODUCTION RELEASE SUITABILITY\n// ─────────────────────────────────────────────────────────────────────────────\n\ndescribe('PRODUCTION RELEASE SUITABILITY: Structural Integrity', () => {\n test('critical files exist for production release', async () => {\n const criticalFiles = [\n 'package.json',\n 'README.md',\n 'LICENSE',\n 'SECURITY.md',\n 'CONTRIBUTING.md',\n 'bin/wrapper.js',\n 'bin/mcp-server.js',\n 'bin/tribunal-kit.js',\n 'dist/cli.js',\n 'dist/index.d.ts',\n 'Cargo.toml',\n 'crates/core/Cargo.toml',\n 'crates/core/src/main.rs',\n '.agent/scripts/impact_classifier.js',\n '.agent/scripts/token_budget_broker.js',\n '.agent/scripts/integrity_manifest.js',\n '.agent/scripts/context_broker.js',\n '.agent/scripts/guardrail_engine.js',\n ];\n\n for (const file of criticalFiles) {\n const fullPath = path.join(ROOT, file);\n expect(fs.existsSync(fullPath)).toBe(true);\n }\n });\n\n test('bin/wrapper.js shebang is correct for cross-platform npm bin', async () => {\n const wrapper = fs.readFileSync(path.join(ROOT, 'bin/wrapper.js'), 'utf8');\n expect(wrapper.startsWith('#!/usr/bin/env node')).toBe(true);\n });\n\n test('bin/mcp-server.js shebang is correct', async () => {\n const mcp = fs.readFileSync(path.join(ROOT, 'bin/mcp-server.js'), 'utf8');\n expect(mcp.startsWith('#!/usr/bin/env node')).toBe(true);\n });\n\n test('package.json exports are correctly defined', async () => {\n const pkg = JSON.parse(fs.readFileSync(path.join(ROOT, 'package.json'), 'utf8'));\n\n expect(pkg.main).toBe('dist/cli.js');\n expect(pkg.types).toBe('dist/index.d.ts');\n expect(pkg.exports['.']).toBeDefined();\n expect(pkg.exports['.'].types).toBe('./dist/index.d.ts');\n expect(pkg.exports['.'].import).toBe('./dist/esm/index.mjs');\n expect(pkg.exports['.'].require).toBe('./dist/cli.js');\n });\n\n test('package.json bin entries point to existing files', async () => {\n const pkg = JSON.parse(fs.readFileSync(path.join(ROOT, 'package.json'), 'utf8'));\n\n for (const [_binName, binPath] of Object.entries(pkg.bin || {})) {\n const fullPath = path.resolve(ROOT, binPath);\n expect(fs.existsSync(fullPath)).toBe(true);\n }\n });\n\n test('package.json engines specifies minimum Node version', async () => {\n const pkg = JSON.parse(fs.readFileSync(path.join(ROOT, 'package.json'), 'utf8'));\n expect(pkg.engines).toBeDefined();\n expect(pkg.engines.node).toBeDefined();\n expect(pkg.engines.node).toMatch(/>=\\s*18/);\n });\n\n test('no hardcoded secrets in source files', async () => {\n const sensitivePatterns = [\n /sk-[a-zA-Z0-9]{20,}/, // OpenAI API keys\n /ghp_[a-zA-Z0-9]{36}/, // GitHub personal access tokens\n /AKIA[A-Z0-9]{16}/, // AWS access keys\n /password\\s*[:=]\\s*[\"'][^\"']{8,}/i, // Hardcoded passwords\n ];\n\n const filesToCheck = [\n 'bin/wrapper.js',\n 'bin/mcp-server.js',\n 'bin/tribunal-kit.js',\n 'dist/cli.js',\n '.agent/scripts/context_broker.js',\n '.agent/scripts/guardrail_engine.js',\n ];\n\n for (const file of filesToCheck) {\n const content = fs.readFileSync(path.join(ROOT, file), 'utf8');\n for (const pattern of sensitivePatterns) {\n expect(pattern.test(content)).toBe(false);\n }\n }\n });\n\n test('integrity manifest reports phantom references accurately', async () => {\n const manifest = generateManifest(ROOT);\n if (manifest.error) {\n // If .agent not found, skip (edge case in CI)\n return;\n }\n\n // The manifest should report phantom references deterministically\n expect(typeof manifest.integrity.phantom_references).toBe('number');\n expect(typeof manifest.integrity.invalid_claims).toBe('number');\n });\n\n test('routing_index.json exists and has valid structure', async () => {\n const routingIndexPath = path.join(ROOT, '.agent', 'routing_index.json');\n if (!fs.existsSync(routingIndexPath)) {\n // Skip if routing_index.json doesn't exist yet\n return;\n }\n\n const routingIndex = JSON.parse(fs.readFileSync(routingIndexPath, 'utf8'));\n expect(routingIndex).toHaveProperty('summary');\n expect(typeof routingIndex.summary.total_agents).toBe('number');\n expect(typeof routingIndex.summary.total_workflows).toBe('number');\n expect(typeof routingIndex.summary.total_skills).toBe('number');\n });\n\n test('RUST_COMMANDS set in wrapper.js matches Rust core command modules', async () => {\n const wrapperSource = fs.readFileSync(path.join(ROOT, 'bin/wrapper.js'), 'utf8');\n\n // Extract command names from the RUST_COMMANDS set\n const commandMatch = wrapperSource.match(/RUST_COMMANDS\\s*=\\s*new\\s+Set\\(\\[([\\s\\S]*?)\\]\\)/);\n expect(commandMatch).not.toBeNull();\n\n // Verify the set is not empty\n const commandsBlock = commandMatch[1];\n const commands = commandsBlock.match(/['\"]([^'\"]+)['\"]/g);\n expect(commands).not.toBeNull();\n expect(commands.length).toBeGreaterThan(0);\n\n // Verify essential commands are present\n const commandNames = commands.map(c => c.replace(/['\"]/g, ''));\n expect(commandNames).toContain('init');\n expect(commandNames).toContain('validate');\n expect(commandNames).toContain('status');\n expect(commandNames).toContain('memory');\n });\n});\n"
21
+ }
@@ -1,6 +1,5 @@
1
1
  {
2
2
  "file": "test/unit/selfInstall.test.js",
3
- "hash": "65b099af8b884c6c1d174f7f70c6d836c27e02ea",
4
3
  "riskScore": "Low",
5
4
  "blastRadius": 0,
6
5
  "imports": {
@@ -15,9 +14,10 @@
15
14
  "isSelfInstall",
16
15
  "CORE_AGENTS",
17
16
  "CORE_SKILLS",
18
- "generateIDEBridges"
17
+ "generateIDEBridges",
18
+ "cmdMarathon"
19
19
  ]
20
20
  },
21
21
  "dependents": [],
22
- "content": "'use strict';\r\n\r\nconst path = require('path');\r\nconst fs = require('fs');\r\nconst os = require('os');\r\nconst { isSelfInstall } = require('../../bin/tribunal-kit');\r\n\r\ndescribe('isSelfInstall', () => {\r\n test('returns true when target is the kit root (package root)', () => {\r\n // The kit root is one level above bin/\r\n const kitRoot = path.resolve(__dirname, '../../');\r\n expect(isSelfInstall(kitRoot)).toBe(true);\r\n });\r\n\r\n test('returns false for an unrelated directory', () => {\r\n expect(isSelfInstall(os.tmpdir())).toBe(false);\r\n });\r\n\r\n test('returns true when target package.json has name \"tribunal-kit\"', () => {\r\n // Create a temp dir with a fake package.json mimicking the kit\r\n const fakeKitDir = fs.mkdtempSync(path.join(os.tmpdir(), 'fake-tk-'));\r\n fs.writeFileSync(\r\n path.join(fakeKitDir, 'package.json'),\r\n JSON.stringify({ name: 'tribunal-kit', version: '1.0.0' })\r\n );\r\n expect(isSelfInstall(fakeKitDir)).toBe(true);\r\n fs.rmSync(fakeKitDir, { recursive: true, force: true });\r\n });\r\n\r\n test('returns false when target package.json has a different name', () => {\r\n const fakeDir = fs.mkdtempSync(path.join(os.tmpdir(), 'other-pkg-'));\r\n fs.writeFileSync(\r\n path.join(fakeDir, 'package.json'),\r\n JSON.stringify({ name: 'my-other-project', version: '1.0.0' })\r\n );\r\n expect(isSelfInstall(fakeDir)).toBe(false);\r\n fs.rmSync(fakeDir, { recursive: true, force: true });\r\n });\r\n\r\n test('returns false when target has no package.json', () => {\r\n const emptyDir = fs.mkdtempSync(path.join(os.tmpdir(), 'no-pkg-'));\r\n expect(isSelfInstall(emptyDir)).toBe(false);\r\n fs.rmSync(emptyDir, { recursive: true, force: true });\r\n });\r\n});\r\n"
22
+ "content": "'use strict';\n\nconst path = require('path');\nconst fs = require('fs');\nconst os = require('os');\nconst { isSelfInstall } = require('../../bin/tribunal-kit');\n\ndescribe('isSelfInstall', () => {\n test('returns true when target is the kit root (package root)', () => {\n // The kit root is one level above bin/\n const kitRoot = path.resolve(__dirname, '../../');\n expect(isSelfInstall(kitRoot)).toBe(true);\n });\n\n test('returns false for an unrelated directory', () => {\n expect(isSelfInstall(os.tmpdir())).toBe(false);\n });\n\n test('returns true when target package.json has name \"tribunal-kit\"', () => {\n // Create a temp dir with a fake package.json mimicking the kit\n const fakeKitDir = fs.mkdtempSync(path.join(os.tmpdir(), 'fake-tk-'));\n fs.writeFileSync(\n path.join(fakeKitDir, 'package.json'),\n JSON.stringify({ name: 'tribunal-kit', version: '1.0.0' }),\n );\n expect(isSelfInstall(fakeKitDir)).toBe(true);\n fs.rmSync(fakeKitDir, { recursive: true, force: true });\n });\n\n test('returns false when target package.json has a different name', () => {\n const fakeDir = fs.mkdtempSync(path.join(os.tmpdir(), 'other-pkg-'));\n fs.writeFileSync(\n path.join(fakeDir, 'package.json'),\n JSON.stringify({ name: 'my-other-project', version: '1.0.0' }),\n );\n expect(isSelfInstall(fakeDir)).toBe(false);\n fs.rmSync(fakeDir, { recursive: true, force: true });\n });\n\n test('returns false when target has no package.json', () => {\n const emptyDir = fs.mkdtempSync(path.join(os.tmpdir(), 'no-pkg-'));\n expect(isSelfInstall(emptyDir)).toBe(false);\n fs.rmSync(emptyDir, { recursive: true, force: true });\n });\n});\n"
23
23
  }
@@ -1,6 +1,5 @@
1
1
  {
2
2
  "file": "test/unit/semver.test.js",
3
- "hash": "d3b4e92656e66f496f034174abd53012ed00b33c",
4
3
  "riskScore": "Low",
5
4
  "blastRadius": 0,
6
5
  "imports": {
@@ -12,9 +11,10 @@
12
11
  "isSelfInstall",
13
12
  "CORE_AGENTS",
14
13
  "CORE_SKILLS",
15
- "generateIDEBridges"
14
+ "generateIDEBridges",
15
+ "cmdMarathon"
16
16
  ]
17
17
  },
18
18
  "dependents": [],
19
- "content": "'use strict';\r\n\r\nconst { compareSemver } = require('../../bin/tribunal-kit');\r\n\r\ndescribe('compareSemver', () => {\r\n test('returns 1 when a > b (patch)', () => {\r\n expect(compareSemver('2.4.2', '2.4.1')).toBe(1);\r\n });\r\n\r\n test('returns 1 when a > b (minor)', () => {\r\n expect(compareSemver('2.5.0', '2.4.9')).toBe(1);\r\n });\r\n\r\n test('returns 1 when a > b (major)', () => {\r\n expect(compareSemver('3.0.0', '2.9.9')).toBe(1);\r\n });\r\n\r\n test('returns 0 when a === b', () => {\r\n expect(compareSemver('2.4.2', '2.4.2')).toBe(0);\r\n });\r\n\r\n test('returns -1 when a < b (patch)', () => {\r\n expect(compareSemver('2.4.1', '2.4.2')).toBe(-1);\r\n });\r\n\r\n test('returns -1 when a < b (minor)', () => {\r\n expect(compareSemver('2.4.9', '2.5.0')).toBe(-1);\r\n });\r\n\r\n test('strips leading \"v\" prefix from a', () => {\r\n expect(compareSemver('v2.4.2', '2.4.2')).toBe(0);\r\n });\r\n\r\n test('strips leading \"v\" prefix from b', () => {\r\n expect(compareSemver('2.4.2', 'v2.4.2')).toBe(0);\r\n });\r\n\r\n test('strips leading \"v\" from both', () => {\r\n expect(compareSemver('v2.4.3', 'v2.4.2')).toBe(1);\r\n });\r\n\r\n test('handles missing patch segment (treats as 0)', () => {\r\n expect(compareSemver('2.4', '2.4.0')).toBe(0);\r\n });\r\n});\r\n"
19
+ "content": "'use strict';\n\nconst { compareSemver } = require('../../bin/tribunal-kit');\n\ndescribe('compareSemver', () => {\n test('returns 1 when a > b (patch)', () => {\n expect(compareSemver('2.4.2', '2.4.1')).toBe(1);\n });\n\n test('returns 1 when a > b (minor)', () => {\n expect(compareSemver('2.5.0', '2.4.9')).toBe(1);\n });\n\n test('returns 1 when a > b (major)', () => {\n expect(compareSemver('3.0.0', '2.9.9')).toBe(1);\n });\n\n test('returns 0 when a === b', () => {\n expect(compareSemver('2.4.2', '2.4.2')).toBe(0);\n });\n\n test('returns -1 when a < b (patch)', () => {\n expect(compareSemver('2.4.1', '2.4.2')).toBe(-1);\n });\n\n test('returns -1 when a < b (minor)', () => {\n expect(compareSemver('2.4.9', '2.5.0')).toBe(-1);\n });\n\n test('strips leading \"v\" prefix from a', () => {\n expect(compareSemver('v2.4.2', '2.4.2')).toBe(0);\n });\n\n test('strips leading \"v\" prefix from b', () => {\n expect(compareSemver('2.4.2', 'v2.4.2')).toBe(0);\n });\n\n test('strips leading \"v\" from both', () => {\n expect(compareSemver('v2.4.3', 'v2.4.2')).toBe(1);\n });\n\n test('handles missing patch segment (treats as 0)', () => {\n expect(compareSemver('2.4', '2.4.0')).toBe(0);\n });\n});\n"
20
20
  }
@@ -0,0 +1,11 @@
1
+ {
2
+ "file": "test/unit/skill_evolution.test.js",
3
+ "riskScore": "Low",
4
+ "blastRadius": 0,
5
+ "imports": {
6
+ "../../.agent/scripts/signal_detector": [],
7
+ "../../.agent/scripts/skill_evolution": []
8
+ },
9
+ "dependents": [],
10
+ "content": "const { detectSignals } = require('../../.agent/scripts/signal_detector');\nconst { _semanticDelta, _architecturalWeight } = require('../../.agent/scripts/skill_evolution');\n\ndescribe('signal_detector.js', () => {\n it('should extract signals from JS stack trace', () => {\n const log = `TypeError: Cannot read property 'split' of undefined\\n at processFile (src/utils.js:45:20)\\n at Object.run (src/main.js:10:5)`;\n const signals = detectSignals(log);\n expect(signals.length).toBeGreaterThan(0);\n expect(signals[0].type).toBe('log_error');\n expect(signals[0].file).toBe('src/utils.js');\n expect(signals[0].line).toBe(45);\n });\n\n it('should extract signals from Python traceback', () => {\n const log = `Traceback (most recent call last):\\n File \"src/main.py\", line 12, in <module>\\n foo()\\nZeroDivisionError: division by zero`;\n const signals = detectSignals(log);\n expect(signals.length).toBeGreaterThan(0);\n expect(signals[0].type).toBe('log_error');\n expect(signals[0].file).toBe('src/main.py');\n expect(signals[0].line).toBe(12);\n });\n\n it('should extract signals from Rust compiler error', () => {\n const log = `error[E0308]: mismatched types\\n --> src/main.rs:12:34\\n |\\n12 | let x: u32 = \"hello\";\\n | ^^^^^^^ expected \\`u32\\`, found \\`&str\\``;\n const signals = detectSignals(log);\n expect(signals.length).toBeGreaterThan(0);\n expect(signals[0].type).toBe('log_error');\n expect(signals[0].file).toBe('src/main.rs');\n expect(signals[0].line).toBe(12);\n });\n\n it('should extract signals from ESLint inline line', () => {\n const log = `src/index.js: line 5, col 10, Error - 'x' is defined but never used (no-unused-vars)`;\n const signals = detectSignals(log);\n expect(signals.length).toBeGreaterThan(0);\n expect(signals[0].type).toBe('log_error');\n expect(signals[0].file).toBe('src/index.js');\n expect(signals[0].line).toBe(5);\n expect(signals[0].message).toContain('no-unused-vars');\n });\n\n it('should extract signals from performance bottleneck', () => {\n const log = `Slow query detected - execution time: 1200ms\\nSELECT * FROM users;`;\n const signals = detectSignals(log);\n expect(signals.length).toBeGreaterThan(0);\n expect(signals[0].type).toBe('perf_bottleneck');\n });\n});\n"
11
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "file": "test/unit/stress.test.js",
3
+ "riskScore": "Low",
4
+ "blastRadius": 0,
5
+ "imports": {
6
+ "path": [],
7
+ "../../scripts/stress_benchmark": [
8
+ "app",
9
+ "parseUser",
10
+ "runStressSuite"
11
+ ]
12
+ },
13
+ "dependents": [],
14
+ "content": "/**\n * test/unit/stress.test.js — Tribunal-Kit Stress & Performance Unit Test\n */\n\n'use strict';\n\nconst _path = require('path');\nconst { runStressSuite } = require('../../scripts/stress_benchmark');\n\ndescribe('Tribunal-Kit Governance Stress Suite', () => {\n let stressResults;\n\n beforeAll(async () => {\n // Run stress suite in memory\n stressResults = await runStressSuite();\n }, 30000); // 30s timeout for heavy stress run\n\n test('Concurrency stress test passes with high throughput', () => {\n expect(stressResults.concurrency).toBeDefined();\n expect(stressResults.concurrency.all_passed).toBe(true);\n expect(stressResults.concurrency.ops_per_sec).toBeGreaterThan(50);\n });\n\n test('Scale stress test handles 10,000 LOC within budget', () => {\n expect(stressResults.scale).toBeDefined();\n expect(stressResults.scale.lines_of_code).toBeGreaterThanOrEqual(10000);\n expect(stressResults.scale.parse_time_ms).toBeLessThan(5000);\n });\n\n test('Token compression achieves token savings', () => {\n expect(stressResults.token_compression).toBeDefined();\n expect(stressResults.token_compression.savings_percent).toBeGreaterThan(0);\n });\n\n test('Anti-hallucination barrier catches 100% of malicious security payloads', () => {\n expect(stressResults.anti_hallucination).toBeDefined();\n expect(stressResults.anti_hallucination.detection_rate_percent).toBe(100);\n expect(stressResults.anti_hallucination.caught_swarm_hallucination).toBe(true);\n });\n});\n"
15
+ }
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "file": "test/unit/swarm_dispatcher.test.js",
3
- "hash": "59ea8d2f8e73dd3a384df1966e55002db2df0947",
4
3
  "riskScore": "Low",
5
4
  "blastRadius": 0,
6
5
  "imports": {
7
6
  "path": [],
8
- "fs": []
7
+ "fs": [],
8
+ "../../.agent/scripts/swarm_dispatcher.js": []
9
9
  },
10
10
  "dependents": [],
11
- "content": "const path = require('path');\r\nconst fs = require('fs');\r\n\r\ndescribe('swarm_dispatcher.js', () => {\r\n it('should be a valid javascript file', () => {\r\n const filePath = path.join(__dirname, '../../.agent/scripts/swarm_dispatcher.js');\r\n expect(fs.existsSync(filePath)).toBe(true);\r\n const content = fs.readFileSync(filePath, 'utf8');\r\n expect(content.includes('module.exports')).toBeTruthy();\r\n });\r\n \r\n // As swarm_dispatcher heavily relies on filesystem operations and child_process,\r\n // we would use jest.mock('child_process') and jest.mock('fs') for deeper testing.\r\n // This serves as the foundation for the swarm test suite.\r\n});\r\n"
11
+ "content": "const path = require('path');\nconst fs = require('fs');\nconst {\n validateWorkerRequest,\n validateWorkerResult,\n validateSwarmPayload,\n} = require('../../.agent/scripts/swarm_dispatcher.js');\n\ndescribe('swarm_dispatcher.js', () => {\n it('should be a valid javascript file', () => {\n const filePath = path.join(__dirname, '../../.agent/scripts/swarm_dispatcher.js');\n expect(fs.existsSync(filePath)).toBe(true);\n const content = fs.readFileSync(filePath, 'utf8');\n expect(content.includes('module.exports')).toBeTruthy();\n });\n\n it('should validate a valid worker request object', () => {\n const agentsDir = path.join(__dirname, '../../.agent/agents');\n const validRequest = {\n task_id: 'task-1',\n type: 'generate_code',\n agent: 'backend-specialist',\n goal: 'Implement authentication route',\n context: 'Minimal required context',\n };\n expect(validateWorkerRequest(validRequest, 0, agentsDir)).toEqual([]);\n });\n\n it('should reject invalid worker requests missing required fields', () => {\n const agentsDir = path.join(__dirname, '../../.agent/agents');\n const invalidRequest = { worker_id: 'worker-1' };\n expect(validateWorkerRequest(invalidRequest, 0, agentsDir).length).toBeGreaterThan(0);\n });\n\n it('should validate a valid worker result object', () => {\n const validResult = {\n task_id: 'task-1',\n agent: 'backend-specialist',\n status: 'success',\n output: 'Created auth handler',\n };\n expect(validateWorkerResult(validResult, 0)).toEqual([]);\n });\n\n it('should validate a complete swarm payload object', () => {\n const payload = {\n goal: 'Build feature',\n workers: [\n {\n task_id: 'task-1',\n type: 'generate_code',\n agent: 'backend-specialist',\n goal: 'Build auth route',\n context: 'Context info',\n },\n ],\n };\n const agentsDir = path.join(__dirname, '../../.agent/agents');\n expect(validateSwarmPayload(payload, agentsDir)).toBe(true);\n });\n});\n"
12
12
  }
@@ -0,0 +1,10 @@
1
+ {
2
+ "file": "test/unit/utils.test.js",
3
+ "riskScore": "Low",
4
+ "blastRadius": 0,
5
+ "imports": {
6
+ "../../.agent/scripts/_utils": []
7
+ },
8
+ "dependents": [],
9
+ "content": "'use strict';\n\nconst { normalizeCommand } = require('../../.agent/scripts/_utils');\n\ndescribe('normalizeCommand', () => {\n test('only appends .cmd for Windows package-manager shims', () => {\n expect(normalizeCommand('npm', 'win32')).toBe('npm.cmd');\n expect(normalizeCommand('node', 'win32')).toBe('node');\n expect(normalizeCommand('cargo', 'win32')).toBe('cargo');\n expect(normalizeCommand('C:\\\\tools\\\\node.exe', 'win32')).toBe('C:\\\\tools\\\\node.exe');\n });\n});\n"
10
+ }
@@ -0,0 +1,137 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ /**
5
+ * Normalizes a file path to consistently use forward slashes for cross-platform compatibility.
6
+ */
7
+ function normalizePath(p) {
8
+ return path.normalize(p).replace(/\\/g, '/');
9
+ }
10
+
11
+ /**
12
+ * Loads the dependency graph from .tribunal/graph.json.
13
+ * @param {string} projectRoot - The root directory of the project
14
+ * @returns {Object|null} The semantic graph object or null if not found
15
+ */
16
+ function loadGraph(projectRoot) {
17
+ const graphPath = path.join(projectRoot, '.tribunal', 'graph.json');
18
+ if (!fs.existsSync(graphPath)) {
19
+ return null;
20
+ }
21
+ try {
22
+ const data = fs.readFileSync(graphPath, 'utf8');
23
+ return JSON.parse(data);
24
+ } catch (err) {
25
+ console.error('[AST Context] Failed to parse graph.json:', err.message);
26
+ return null;
27
+ }
28
+ }
29
+
30
+ /**
31
+ * Gets the direct dependencies (files imported by) and dependents (files that import) a target file.
32
+ * Includes transitive dependencies up to 2 levels deep.
33
+ * @param {string} projectRoot - The root directory of the project
34
+ * @param {string} targetFile - The relative or absolute path of the target file
35
+ * @returns {Object} An object containing { dependencies, dependents } as arrays of file paths
36
+ */
37
+ function getContext(projectRoot, targetFile) {
38
+ const graph = loadGraph(projectRoot);
39
+ if (!graph || !graph.nodes) {
40
+ return { error: 'Graph not found or invalid. Run `tribunal-core graph-ast .` first.' };
41
+ }
42
+
43
+ // Convert target to a consistent format (usually absolute, normalized)
44
+ const normalizedTarget = normalizePath(path.resolve(projectRoot, targetFile));
45
+
46
+ const dependencies = new Set();
47
+ const dependents = new Set();
48
+
49
+ // Helper to resolve imports and find matching files
50
+ function resolveImports(filePath, imports) {
51
+ const resolved = new Set();
52
+ const fileDir = path.dirname(filePath);
53
+
54
+ for (const imp of imports || []) {
55
+ if (imp.startsWith('.')) {
56
+ const resolvedImport = normalizePath(path.resolve(fileDir, imp));
57
+ // Find actual file matching this import
58
+ const matchingNodes = graph.nodes.filter(n => {
59
+ const nAbs = normalizePath(path.resolve(projectRoot, n.file_path));
60
+ return nAbs === resolvedImport;
61
+ });
62
+ matchingNodes.forEach(m =>
63
+ resolved.add(normalizePath(path.resolve(projectRoot, m.file_path))),
64
+ );
65
+ }
66
+ }
67
+ return resolved;
68
+ }
69
+
70
+ // First pass: find direct dependencies and dependents
71
+ graph.nodes.forEach(node => {
72
+ const nodeAbsPath = normalizePath(path.resolve(projectRoot, node.file_path));
73
+ const nodeDir = path.dirname(nodeAbsPath);
74
+
75
+ // Does this node import our target?
76
+ for (const imp of node.imports || []) {
77
+ if (imp.startsWith('.')) {
78
+ const resolvedImport = normalizePath(path.resolve(nodeDir, imp));
79
+ // Check if resolved import matches our target exactly
80
+ if (normalizedTarget === resolvedImport) {
81
+ dependents.add(nodeAbsPath);
82
+ }
83
+ }
84
+ }
85
+
86
+ // Is this node our target? If so, what does it import?
87
+ if (nodeAbsPath === normalizedTarget) {
88
+ const directDeps = resolveImports(nodeAbsPath, node.imports || []);
89
+ directDeps.forEach(dep => dependencies.add(dep));
90
+ }
91
+ });
92
+
93
+ // Second pass: find transitive dependencies (dependencies of dependencies)
94
+ const directDependencies = Array.from(dependencies);
95
+ directDependencies.forEach(dep => {
96
+ // Find the node for this dependency
97
+ const depNode = graph.nodes.find(
98
+ n => normalizePath(path.resolve(projectRoot, n.file_path)) === dep,
99
+ );
100
+
101
+ if (depNode) {
102
+ const transitiveDeps = resolveImports(dep, depNode.imports || []);
103
+ transitiveDeps.forEach(tdep => {
104
+ // Don't add the target itself or create cycles
105
+ if (tdep !== normalizedTarget && !dependencies.has(tdep)) {
106
+ dependencies.add(tdep);
107
+ }
108
+ });
109
+ }
110
+ });
111
+
112
+ return {
113
+ target: normalizedTarget,
114
+ dependencies: Array.from(dependencies),
115
+ dependents: Array.from(dependents),
116
+ };
117
+ }
118
+
119
+ module.exports = {
120
+ loadGraph,
121
+ getContext,
122
+ };
123
+
124
+ // If run directly from CLI
125
+ if (require.main === module) {
126
+ const args = process.argv.slice(2);
127
+ if (args.length < 1) {
128
+ console.error('Usage: node ast_context_loader.js <target_file>');
129
+ process.exit(1);
130
+ }
131
+
132
+ const target = args[0];
133
+ const projectRoot = process.cwd();
134
+ const result = getContext(projectRoot, target);
135
+
136
+ console.log(JSON.stringify(result, null, 2));
137
+ }