@claude-flow/plugin-legal-contracts 3.0.0-alpha.1

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.
@@ -0,0 +1,966 @@
1
+ /**
2
+ * Legal Contracts Plugin - MCP Tools
3
+ *
4
+ * Implements 5 MCP tools for legal contract analysis:
5
+ * 1. legal/clause-extract - Extract and classify clauses
6
+ * 2. legal/risk-assess - Identify and score contractual risks
7
+ * 3. legal/contract-compare - Compare contracts with attention-based alignment
8
+ * 4. legal/obligation-track - Extract obligations with DAG analysis
9
+ * 5. legal/playbook-match - Match clauses against negotiation playbook
10
+ *
11
+ * Based on ADR-034: Legal Contract Analysis Plugin
12
+ *
13
+ * @module v3/plugins/legal-contracts/mcp-tools
14
+ */
15
+ import { ClauseExtractInputSchema, RiskAssessInputSchema, ContractCompareInputSchema, ObligationTrackInputSchema, PlaybookMatchInputSchema, RiskCategory, } from './types.js';
16
+ import { createAttentionBridge } from './bridges/attention-bridge.js';
17
+ import { createDAGBridge } from './bridges/dag-bridge.js';
18
+ // ============================================================================
19
+ // Clause Extract Tool
20
+ // ============================================================================
21
+ /**
22
+ * MCP Tool: legal/clause-extract
23
+ *
24
+ * Extract and classify clauses from legal documents
25
+ */
26
+ export const clauseExtractTool = {
27
+ name: 'legal/clause-extract',
28
+ description: 'Extract and classify clauses from legal documents',
29
+ category: 'legal',
30
+ version: '3.0.0-alpha.1',
31
+ inputSchema: ClauseExtractInputSchema,
32
+ handler: async (input, context) => {
33
+ const startTime = Date.now();
34
+ try {
35
+ // Validate input
36
+ const validated = ClauseExtractInputSchema.parse(input);
37
+ // Parse document and extract clauses
38
+ const metadata = parseDocumentMetadata(validated.document);
39
+ const clauses = await extractClauses(validated.document, validated.clauseTypes, validated.jurisdiction, context);
40
+ // Separate classified and unclassified
41
+ const classifiedClauses = clauses.filter(c => c.confidence >= 0.7);
42
+ const unclassified = clauses
43
+ .filter(c => c.confidence < 0.7)
44
+ .map(c => ({
45
+ text: c.text,
46
+ startOffset: c.startOffset,
47
+ endOffset: c.endOffset,
48
+ reason: `Low confidence: ${(c.confidence * 100).toFixed(1)}%`,
49
+ }));
50
+ const result = {
51
+ success: true,
52
+ clauses: classifiedClauses,
53
+ metadata,
54
+ unclassified,
55
+ durationMs: Date.now() - startTime,
56
+ };
57
+ return {
58
+ content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
59
+ data: result,
60
+ };
61
+ }
62
+ catch (error) {
63
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
64
+ return {
65
+ content: [{
66
+ type: 'text',
67
+ text: JSON.stringify({
68
+ success: false,
69
+ error: errorMessage,
70
+ durationMs: Date.now() - startTime,
71
+ }, null, 2),
72
+ }],
73
+ };
74
+ }
75
+ },
76
+ };
77
+ // ============================================================================
78
+ // Risk Assess Tool
79
+ // ============================================================================
80
+ /**
81
+ * MCP Tool: legal/risk-assess
82
+ *
83
+ * Assess contractual risks with severity scoring
84
+ */
85
+ export const riskAssessTool = {
86
+ name: 'legal/risk-assess',
87
+ description: 'Assess contractual risks with severity scoring',
88
+ category: 'legal',
89
+ version: '3.0.0-alpha.1',
90
+ inputSchema: RiskAssessInputSchema,
91
+ handler: async (input, context) => {
92
+ const startTime = Date.now();
93
+ try {
94
+ const validated = RiskAssessInputSchema.parse(input);
95
+ // Extract clauses first
96
+ const clauses = await extractClauses(validated.document, undefined, 'US', context);
97
+ // Assess risks
98
+ const risks = await assessRisks(clauses, validated.partyRole, validated.riskCategories, validated.industryContext);
99
+ // Filter by threshold if specified
100
+ const filteredRisks = validated.threshold
101
+ ? risks.filter(r => getSeverityLevel(r.severity) >= getSeverityLevel(validated.threshold))
102
+ : risks;
103
+ // Build category summary
104
+ const categorySummary = buildCategorySummary(filteredRisks);
105
+ // Calculate overall score
106
+ const overallScore = calculateOverallRiskScore(filteredRisks);
107
+ const grade = scoreToGrade(overallScore);
108
+ const result = {
109
+ success: true,
110
+ partyRole: validated.partyRole,
111
+ risks: filteredRisks,
112
+ categorySummary,
113
+ overallScore,
114
+ grade,
115
+ criticalRisks: filteredRisks
116
+ .filter(r => r.severity === 'critical' || r.severity === 'high')
117
+ .slice(0, 5),
118
+ durationMs: Date.now() - startTime,
119
+ };
120
+ return {
121
+ content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
122
+ data: result,
123
+ };
124
+ }
125
+ catch (error) {
126
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
127
+ return {
128
+ content: [{
129
+ type: 'text',
130
+ text: JSON.stringify({
131
+ success: false,
132
+ error: errorMessage,
133
+ durationMs: Date.now() - startTime,
134
+ }, null, 2),
135
+ }],
136
+ };
137
+ }
138
+ },
139
+ };
140
+ // ============================================================================
141
+ // Contract Compare Tool
142
+ // ============================================================================
143
+ /**
144
+ * MCP Tool: legal/contract-compare
145
+ *
146
+ * Compare two contracts with detailed diff and semantic alignment
147
+ */
148
+ export const contractCompareTool = {
149
+ name: 'legal/contract-compare',
150
+ description: 'Compare two contracts with detailed diff and semantic alignment',
151
+ category: 'legal',
152
+ version: '3.0.0-alpha.1',
153
+ inputSchema: ContractCompareInputSchema,
154
+ handler: async (input, context) => {
155
+ const startTime = Date.now();
156
+ try {
157
+ const validated = ContractCompareInputSchema.parse(input);
158
+ // Extract clauses from both documents
159
+ const baseClauses = await extractClauses(validated.baseDocument, undefined, 'US', context);
160
+ const compareClauses = await extractClauses(validated.compareDocument, undefined, 'US', context);
161
+ // Initialize attention bridge
162
+ const attention = context.bridges.attention;
163
+ if (!attention.isInitialized()) {
164
+ await attention.initialize();
165
+ }
166
+ // Align clauses using attention
167
+ const alignments = await attention.alignClauses(baseClauses, compareClauses);
168
+ // Detect changes
169
+ const changes = detectChanges(baseClauses, compareClauses, alignments);
170
+ // Calculate similarity score
171
+ const similarityScore = alignments.length > 0
172
+ ? alignments.reduce((sum, a) => sum + a.similarity, 0) / alignments.length
173
+ : 0;
174
+ // Build summary
175
+ const summary = {
176
+ totalChanges: changes.length,
177
+ added: changes.filter(c => c.type === 'added').length,
178
+ removed: changes.filter(c => c.type === 'removed').length,
179
+ modified: changes.filter(c => c.type === 'modified').length,
180
+ favorable: changes.filter(c => c.impact === 'favorable').length,
181
+ unfavorable: changes.filter(c => c.impact === 'unfavorable').length,
182
+ };
183
+ // Generate redline if requested
184
+ const redlineMarkup = validated.generateRedline
185
+ ? generateRedlineMarkup(validated.baseDocument, changes)
186
+ : undefined;
187
+ const result = {
188
+ success: true,
189
+ mode: validated.comparisonMode,
190
+ changes,
191
+ alignments,
192
+ similarityScore,
193
+ summary,
194
+ redlineMarkup,
195
+ durationMs: Date.now() - startTime,
196
+ };
197
+ return {
198
+ content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
199
+ data: result,
200
+ };
201
+ }
202
+ catch (error) {
203
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
204
+ return {
205
+ content: [{
206
+ type: 'text',
207
+ text: JSON.stringify({
208
+ success: false,
209
+ error: errorMessage,
210
+ durationMs: Date.now() - startTime,
211
+ }, null, 2),
212
+ }],
213
+ };
214
+ }
215
+ },
216
+ };
217
+ // ============================================================================
218
+ // Obligation Track Tool
219
+ // ============================================================================
220
+ /**
221
+ * MCP Tool: legal/obligation-track
222
+ *
223
+ * Extract obligations, deadlines, and dependencies using DAG analysis
224
+ */
225
+ export const obligationTrackTool = {
226
+ name: 'legal/obligation-track',
227
+ description: 'Extract obligations, deadlines, and dependencies using DAG analysis',
228
+ category: 'legal',
229
+ version: '3.0.0-alpha.1',
230
+ inputSchema: ObligationTrackInputSchema,
231
+ handler: async (input, context) => {
232
+ const startTime = Date.now();
233
+ try {
234
+ const validated = ObligationTrackInputSchema.parse(input);
235
+ // Extract obligations
236
+ let obligations = await extractObligations(validated.document, validated.obligationTypes);
237
+ // Filter by party if specified
238
+ if (validated.party) {
239
+ obligations = obligations.filter(o => o.party.toLowerCase().includes(validated.party.toLowerCase()));
240
+ }
241
+ // Filter by timeframe if specified
242
+ if (validated.timeframe) {
243
+ obligations = filterByTimeframe(obligations, validated.timeframe);
244
+ }
245
+ // Initialize DAG bridge
246
+ const dag = context.bridges.dag;
247
+ if (!dag.isInitialized()) {
248
+ await dag.initialize();
249
+ }
250
+ // Build dependency graph
251
+ const graph = await dag.buildDependencyGraph(obligations);
252
+ // Build timeline
253
+ const timeline = buildTimeline(obligations);
254
+ // Find upcoming deadlines (next 30 days)
255
+ const now = new Date();
256
+ const thirtyDaysLater = new Date(now.getTime() + 30 * 24 * 60 * 60 * 1000);
257
+ const upcomingDeadlines = obligations.filter(o => o.dueDate && o.dueDate >= now && o.dueDate <= thirtyDaysLater);
258
+ // Find overdue
259
+ const overdue = obligations.filter(o => o.dueDate && o.dueDate < now && o.status !== 'completed' && o.status !== 'waived');
260
+ const result = {
261
+ success: true,
262
+ obligations,
263
+ graph,
264
+ timeline,
265
+ upcomingDeadlines,
266
+ overdue,
267
+ durationMs: Date.now() - startTime,
268
+ };
269
+ return {
270
+ content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
271
+ data: result,
272
+ };
273
+ }
274
+ catch (error) {
275
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
276
+ return {
277
+ content: [{
278
+ type: 'text',
279
+ text: JSON.stringify({
280
+ success: false,
281
+ error: errorMessage,
282
+ durationMs: Date.now() - startTime,
283
+ }, null, 2),
284
+ }],
285
+ };
286
+ }
287
+ },
288
+ };
289
+ // ============================================================================
290
+ // Playbook Match Tool
291
+ // ============================================================================
292
+ /**
293
+ * MCP Tool: legal/playbook-match
294
+ *
295
+ * Compare contract clauses against negotiation playbook
296
+ */
297
+ export const playbookMatchTool = {
298
+ name: 'legal/playbook-match',
299
+ description: 'Compare contract clauses against negotiation playbook',
300
+ category: 'legal',
301
+ version: '3.0.0-alpha.1',
302
+ inputSchema: PlaybookMatchInputSchema,
303
+ handler: async (input, context) => {
304
+ const startTime = Date.now();
305
+ try {
306
+ const validated = PlaybookMatchInputSchema.parse(input);
307
+ // Parse playbook
308
+ const playbook = parsePlaybook(validated.playbook);
309
+ // Extract clauses from document
310
+ const clauses = await extractClauses(validated.document, undefined, 'US', context);
311
+ // Initialize attention bridge
312
+ const attention = context.bridges.attention;
313
+ if (!attention.isInitialized()) {
314
+ await attention.initialize();
315
+ }
316
+ // Match clauses against playbook
317
+ const matches = await matchAgainstPlaybook(clauses, playbook, validated.strictness, validated.suggestAlternatives, attention);
318
+ // Build summary
319
+ const summary = {
320
+ totalClauses: matches.length,
321
+ matchesPreferred: matches.filter(m => m.status === 'matches_preferred').length,
322
+ matchesAcceptable: matches.filter(m => m.status === 'matches_acceptable').length,
323
+ requiresFallback: matches.filter(m => m.status === 'requires_fallback').length,
324
+ violatesRedline: matches.filter(m => m.status === 'violates_redline').length,
325
+ noMatch: matches.filter(m => m.status === 'no_match').length,
326
+ };
327
+ // Find red line violations
328
+ const redLineViolations = matches.filter(m => m.status === 'violates_redline');
329
+ // Prioritize negotiations
330
+ const negotiationPriorities = buildNegotiationPriorities(matches, validated.prioritizeClauses);
331
+ const result = {
332
+ success: true,
333
+ playbook: {
334
+ id: playbook.id,
335
+ name: playbook.name,
336
+ version: playbook.version,
337
+ },
338
+ matches,
339
+ summary,
340
+ redLineViolations,
341
+ negotiationPriorities,
342
+ durationMs: Date.now() - startTime,
343
+ };
344
+ return {
345
+ content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
346
+ data: result,
347
+ };
348
+ }
349
+ catch (error) {
350
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
351
+ return {
352
+ content: [{
353
+ type: 'text',
354
+ text: JSON.stringify({
355
+ success: false,
356
+ error: errorMessage,
357
+ durationMs: Date.now() - startTime,
358
+ }, null, 2),
359
+ }],
360
+ };
361
+ }
362
+ },
363
+ };
364
+ // ============================================================================
365
+ // Helper Functions
366
+ // ============================================================================
367
+ /**
368
+ * Parse document metadata
369
+ */
370
+ function parseDocumentMetadata(document) {
371
+ const hash = simpleHash(document);
372
+ return {
373
+ id: `doc-${hash.substring(0, 8)}`,
374
+ format: 'txt',
375
+ wordCount: document.split(/\s+/).length,
376
+ charCount: document.length,
377
+ language: 'en',
378
+ parties: [],
379
+ contentHash: hash,
380
+ };
381
+ }
382
+ /**
383
+ * Simple hash function
384
+ */
385
+ function simpleHash(str) {
386
+ let hash = 0;
387
+ for (let i = 0; i < str.length; i++) {
388
+ const char = str.charCodeAt(i);
389
+ hash = ((hash << 5) - hash) + char;
390
+ hash = hash & hash;
391
+ }
392
+ return Math.abs(hash).toString(16).padStart(16, '0');
393
+ }
394
+ /**
395
+ * Extract clauses from document
396
+ */
397
+ async function extractClauses(document, clauseTypes, _jurisdiction, _context) {
398
+ const clauses = [];
399
+ // Define clause patterns
400
+ const clausePatterns = {
401
+ indemnification: [/indemnif/i, /hold\s+harmless/i, /defend\s+and\s+indemnify/i],
402
+ limitation_of_liability: [/limitation\s+of\s+liability/i, /liability\s+shall\s+not\s+exceed/i],
403
+ termination: [/termination/i, /right\s+to\s+terminate/i, /upon\s+termination/i],
404
+ confidentiality: [/confidential/i, /non-disclosure/i, /proprietary\s+information/i],
405
+ ip_assignment: [/intellectual\s+property/i, /assignment\s+of\s+(ip|rights)/i, /work\s+for\s+hire/i],
406
+ governing_law: [/governing\s+law/i, /governed\s+by\s+the\s+laws/i, /jurisdiction/i],
407
+ arbitration: [/arbitration/i, /arbitral\s+proceedings/i, /binding\s+arbitration/i],
408
+ force_majeure: [/force\s+majeure/i, /act\s+of\s+god/i, /beyond\s+reasonable\s+control/i],
409
+ warranty: [/warrant/i, /represents\s+and\s+warrants/i, /as-is/i],
410
+ payment_terms: [/payment/i, /invoic/i, /net\s+\d+/i],
411
+ non_compete: [/non-?compet/i, /not\s+compete/i],
412
+ non_solicitation: [/non-?solicit/i, /not\s+solicit/i],
413
+ assignment: [/assignment/i, /may\s+not\s+assign/i],
414
+ insurance: [/insurance/i, /maintain\s+coverage/i],
415
+ representations: [/represent/i, /represent\s+and\s+warrant/i],
416
+ covenants: [/covenant/i, /agrees\s+to/i],
417
+ data_protection: [/data\s+protection/i, /personal\s+data/i, /gdpr/i, /privacy/i],
418
+ audit_rights: [/audit/i, /right\s+to\s+inspect/i, /access\s+to\s+records/i],
419
+ };
420
+ // Split document into sections/paragraphs
421
+ const sections = document.split(/\n\n+/);
422
+ let offset = 0;
423
+ for (const section of sections) {
424
+ const sectionStart = document.indexOf(section, offset);
425
+ const sectionEnd = sectionStart + section.length;
426
+ offset = sectionEnd;
427
+ // Try to classify section
428
+ for (const [type, patterns] of Object.entries(clausePatterns)) {
429
+ const clauseType = type;
430
+ // Skip if not in requested types
431
+ if (clauseTypes && clauseTypes.length > 0 && !clauseTypes.includes(clauseType)) {
432
+ continue;
433
+ }
434
+ // Check patterns
435
+ let matchCount = 0;
436
+ for (const pattern of patterns) {
437
+ if (pattern.test(section)) {
438
+ matchCount++;
439
+ }
440
+ }
441
+ if (matchCount > 0) {
442
+ const confidence = Math.min(0.5 + matchCount * 0.2, 0.99);
443
+ clauses.push({
444
+ id: `clause-${clauses.length + 1}`,
445
+ type: clauseType,
446
+ text: section.trim(),
447
+ startOffset: sectionStart,
448
+ endOffset: sectionEnd,
449
+ confidence,
450
+ keyTerms: extractKeyTerms(section),
451
+ });
452
+ break; // Only classify as one type
453
+ }
454
+ }
455
+ }
456
+ return clauses;
457
+ }
458
+ /**
459
+ * Extract key terms from text
460
+ */
461
+ function extractKeyTerms(text) {
462
+ const terms = [];
463
+ const termPatterns = [
464
+ /\$[\d,]+/g, // Dollar amounts
465
+ /\d+\s*(days?|months?|years?)/gi, // Time periods
466
+ /\d+%/g, // Percentages
467
+ /"[^"]+"/g, // Quoted terms
468
+ ];
469
+ for (const pattern of termPatterns) {
470
+ const matches = text.match(pattern);
471
+ if (matches) {
472
+ terms.push(...matches);
473
+ }
474
+ }
475
+ return [...new Set(terms)].slice(0, 10);
476
+ }
477
+ /**
478
+ * Assess risks in clauses
479
+ */
480
+ async function assessRisks(clauses, _partyRole, categories, _industryContext) {
481
+ const risks = [];
482
+ // Risk patterns by clause type and party role
483
+ const riskPatterns = {
484
+ indemnification: [
485
+ {
486
+ pattern: /unlimited\s+indemnification/i,
487
+ severity: 'critical',
488
+ category: 'financial',
489
+ title: 'Unlimited Indemnification',
490
+ description: 'Contract requires unlimited indemnification which could expose party to significant financial risk',
491
+ mitigation: 'Negotiate cap on indemnification liability',
492
+ },
493
+ ],
494
+ limitation_of_liability: [
495
+ {
496
+ pattern: /no\s+limitation/i,
497
+ severity: 'high',
498
+ category: 'financial',
499
+ title: 'No Liability Cap',
500
+ description: 'Contract contains no limitation on liability',
501
+ mitigation: 'Add liability cap based on contract value or insurance coverage',
502
+ },
503
+ ],
504
+ termination: [
505
+ {
506
+ pattern: /immediate\s+termination/i,
507
+ severity: 'medium',
508
+ category: 'operational',
509
+ title: 'Immediate Termination Right',
510
+ description: 'Counterparty can terminate immediately without notice',
511
+ mitigation: 'Negotiate notice period for termination',
512
+ },
513
+ ],
514
+ warranty: [
515
+ {
516
+ pattern: /as-?is/i,
517
+ severity: 'medium',
518
+ category: 'legal',
519
+ title: 'As-Is Warranty Disclaimer',
520
+ description: 'Product/service provided without warranty',
521
+ mitigation: 'Negotiate minimum performance warranties',
522
+ },
523
+ ],
524
+ };
525
+ for (const clause of clauses) {
526
+ const patterns = riskPatterns[clause.type] ?? [];
527
+ for (const riskPattern of patterns) {
528
+ if (riskPattern.pattern.test(clause.text)) {
529
+ // Filter by category if specified
530
+ if (categories && !categories.includes(riskPattern.category)) {
531
+ continue;
532
+ }
533
+ risks.push({
534
+ id: `risk-${risks.length + 1}`,
535
+ category: riskPattern.category,
536
+ severity: riskPattern.severity,
537
+ title: riskPattern.title,
538
+ description: riskPattern.description,
539
+ clauseIds: [clause.id],
540
+ mitigations: [riskPattern.mitigation],
541
+ deviatesFromStandard: true,
542
+ confidence: clause.confidence,
543
+ });
544
+ }
545
+ }
546
+ }
547
+ return risks;
548
+ }
549
+ /**
550
+ * Get severity level as number
551
+ */
552
+ function getSeverityLevel(severity) {
553
+ const levels = {
554
+ low: 1,
555
+ medium: 2,
556
+ high: 3,
557
+ critical: 4,
558
+ };
559
+ return levels[severity];
560
+ }
561
+ /**
562
+ * Build category summary
563
+ */
564
+ function buildCategorySummary(risks) {
565
+ const summary = {};
566
+ for (const category of Object.values(RiskCategory.options)) {
567
+ summary[category] = { count: 0, highestSeverity: 'low', totalScore: 0 };
568
+ }
569
+ for (const risk of risks) {
570
+ const cat = summary[risk.category];
571
+ if (cat) {
572
+ cat.count++;
573
+ cat.totalScore += getSeverityLevel(risk.severity);
574
+ if (getSeverityLevel(risk.severity) > getSeverityLevel(cat.highestSeverity)) {
575
+ cat.highestSeverity = risk.severity;
576
+ }
577
+ }
578
+ }
579
+ const result = {};
580
+ for (const [category, data] of Object.entries(summary)) {
581
+ result[category] = {
582
+ count: data.count,
583
+ highestSeverity: data.highestSeverity,
584
+ averageScore: data.count > 0 ? data.totalScore / data.count : 0,
585
+ };
586
+ }
587
+ return result;
588
+ }
589
+ /**
590
+ * Calculate overall risk score
591
+ */
592
+ function calculateOverallRiskScore(risks) {
593
+ if (risks.length === 0)
594
+ return 100;
595
+ const maxScore = 100;
596
+ let penalty = 0;
597
+ for (const risk of risks) {
598
+ const severityPenalty = {
599
+ low: 2,
600
+ medium: 5,
601
+ high: 15,
602
+ critical: 30,
603
+ };
604
+ penalty += severityPenalty[risk.severity];
605
+ }
606
+ return Math.max(0, maxScore - penalty);
607
+ }
608
+ /**
609
+ * Convert score to grade
610
+ */
611
+ function scoreToGrade(score) {
612
+ if (score >= 90)
613
+ return 'A';
614
+ if (score >= 80)
615
+ return 'B';
616
+ if (score >= 70)
617
+ return 'C';
618
+ if (score >= 60)
619
+ return 'D';
620
+ return 'F';
621
+ }
622
+ /**
623
+ * Detect changes between documents
624
+ */
625
+ function detectChanges(baseClauses, compareClauses, alignments) {
626
+ const changes = [];
627
+ const alignedCompare = new Set(alignments.map(a => a.compareClauseId));
628
+ for (const alignment of alignments) {
629
+ const baseClause = baseClauses.find(c => c.id === alignment.baseClauseId);
630
+ const compareClause = compareClauses.find(c => c.id === alignment.compareClauseId);
631
+ if (alignment.alignmentType === 'no_match') {
632
+ changes.push({
633
+ type: 'removed',
634
+ clauseType: baseClause?.type,
635
+ baseSection: baseClause?.section,
636
+ baseText: baseClause?.text,
637
+ significance: 0.8,
638
+ impact: 'requires_review',
639
+ explanation: 'Clause exists in base but not in comparison document',
640
+ });
641
+ }
642
+ else if (alignment.alignmentType !== 'exact') {
643
+ changes.push({
644
+ type: 'modified',
645
+ clauseType: baseClause?.type,
646
+ baseSection: baseClause?.section,
647
+ compareSection: compareClause?.section,
648
+ baseText: baseClause?.text,
649
+ compareText: compareClause?.text,
650
+ significance: 1 - alignment.similarity,
651
+ impact: 'requires_review',
652
+ explanation: `Clause modified (${(alignment.similarity * 100).toFixed(1)}% similarity)`,
653
+ });
654
+ }
655
+ }
656
+ // Find added clauses
657
+ for (const clause of compareClauses) {
658
+ if (!alignedCompare.has(clause.id)) {
659
+ changes.push({
660
+ type: 'added',
661
+ clauseType: clause.type,
662
+ compareSection: clause.section,
663
+ compareText: clause.text,
664
+ significance: 0.7,
665
+ impact: 'requires_review',
666
+ explanation: 'New clause in comparison document',
667
+ });
668
+ }
669
+ }
670
+ return changes;
671
+ }
672
+ /**
673
+ * Generate redline markup
674
+ */
675
+ function generateRedlineMarkup(baseDocument, changes) {
676
+ // Simplified redline generation
677
+ let markup = baseDocument;
678
+ for (const change of changes) {
679
+ if (change.type === 'removed' && change.baseText) {
680
+ markup = markup.replace(change.baseText, `<del style="color:red">${change.baseText}</del>`);
681
+ }
682
+ else if (change.type === 'added' && change.compareText) {
683
+ markup += `\n<ins style="color:green">${change.compareText}</ins>`;
684
+ }
685
+ }
686
+ return markup;
687
+ }
688
+ /**
689
+ * Extract obligations from document
690
+ */
691
+ async function extractObligations(document, types) {
692
+ const obligations = [];
693
+ // Obligation patterns
694
+ const obligationPatterns = {
695
+ payment: [
696
+ { pattern: /shall\s+pay/i, type: 'payment' },
697
+ { pattern: /payment\s+due/i, type: 'payment' },
698
+ ],
699
+ delivery: [
700
+ { pattern: /shall\s+deliver/i, type: 'delivery' },
701
+ { pattern: /delivery\s+date/i, type: 'delivery' },
702
+ ],
703
+ notification: [
704
+ { pattern: /shall\s+notify/i, type: 'notification' },
705
+ { pattern: /provide\s+notice/i, type: 'notification' },
706
+ ],
707
+ approval: [
708
+ { pattern: /shall\s+approve/i, type: 'approval' },
709
+ { pattern: /written\s+approval/i, type: 'approval' },
710
+ ],
711
+ compliance: [
712
+ { pattern: /shall\s+comply/i, type: 'compliance' },
713
+ { pattern: /in\s+compliance\s+with/i, type: 'compliance' },
714
+ ],
715
+ };
716
+ const sentences = document.split(/[.!?]+/);
717
+ for (let i = 0; i < sentences.length; i++) {
718
+ const sentence = sentences[i]?.trim() ?? '';
719
+ if (!sentence)
720
+ continue;
721
+ for (const [, patterns] of Object.entries(obligationPatterns)) {
722
+ for (const { pattern, type } of patterns) {
723
+ if (types && !types.includes(type))
724
+ continue;
725
+ if (pattern.test(sentence)) {
726
+ obligations.push({
727
+ id: `obl-${obligations.length + 1}`,
728
+ type,
729
+ party: extractParty(sentence),
730
+ description: sentence,
731
+ dependsOn: [],
732
+ blocks: [],
733
+ clauseIds: [],
734
+ status: 'pending',
735
+ priority: 'medium',
736
+ });
737
+ break;
738
+ }
739
+ }
740
+ }
741
+ }
742
+ return obligations;
743
+ }
744
+ /**
745
+ * Extract party from sentence
746
+ */
747
+ function extractParty(sentence) {
748
+ const partyPatterns = [
749
+ /the\s+(buyer|seller|licensor|licensee|employer|employee)/i,
750
+ /(party\s+a|party\s+b)/i,
751
+ /the\s+company/i,
752
+ ];
753
+ for (const pattern of partyPatterns) {
754
+ const match = sentence.match(pattern);
755
+ if (match?.[1]) {
756
+ return match[1];
757
+ }
758
+ }
759
+ return 'Unknown Party';
760
+ }
761
+ /**
762
+ * Filter obligations by timeframe
763
+ */
764
+ function filterByTimeframe(obligations, _timeframe) {
765
+ // Parse ISO duration or date range
766
+ // Simplified implementation
767
+ return obligations;
768
+ }
769
+ /**
770
+ * Build timeline from obligations
771
+ */
772
+ function buildTimeline(obligations) {
773
+ const timeline = new Map();
774
+ for (const obligation of obligations) {
775
+ if (obligation.dueDate) {
776
+ const dateKey = obligation.dueDate.toISOString().split('T')[0] ?? '';
777
+ const existing = timeline.get(dateKey);
778
+ if (existing) {
779
+ existing.obligations.push(obligation.id);
780
+ }
781
+ else {
782
+ timeline.set(dateKey, {
783
+ date: obligation.dueDate,
784
+ obligations: [obligation.id],
785
+ isMilestone: obligation.priority === 'critical',
786
+ });
787
+ }
788
+ }
789
+ }
790
+ return Array.from(timeline.values()).sort((a, b) => a.date.getTime() - b.date.getTime());
791
+ }
792
+ /**
793
+ * Parse playbook from string (JSON or ID)
794
+ */
795
+ function parsePlaybook(playbookInput) {
796
+ try {
797
+ const parsed = JSON.parse(playbookInput);
798
+ return parsed;
799
+ }
800
+ catch {
801
+ // Return a default playbook
802
+ return {
803
+ id: playbookInput,
804
+ name: 'Default Playbook',
805
+ contractType: 'General',
806
+ jurisdiction: 'US',
807
+ partyRole: 'buyer',
808
+ updatedAt: new Date(),
809
+ version: '1.0.0',
810
+ positions: [],
811
+ };
812
+ }
813
+ }
814
+ /**
815
+ * Match clauses against playbook
816
+ */
817
+ async function matchAgainstPlaybook(clauses, playbook, strictness, suggestAlternatives, _attention) {
818
+ const matches = [];
819
+ for (const clause of clauses) {
820
+ const position = playbook.positions.find(p => p.clauseType === clause.type);
821
+ if (!position) {
822
+ matches.push({
823
+ clauseId: clause.id,
824
+ position: {
825
+ clauseType: clause.type,
826
+ preferredLanguage: '',
827
+ acceptableVariations: [],
828
+ redLines: [],
829
+ fallbackPositions: [],
830
+ negotiationNotes: '',
831
+ businessJustification: '',
832
+ },
833
+ status: 'no_match',
834
+ preferredSimilarity: 0,
835
+ recommendation: 'No playbook position defined for this clause type',
836
+ });
837
+ continue;
838
+ }
839
+ // Check against preferred language
840
+ const preferredSimilarity = calculateTextSimilarity(clause.text, position.preferredLanguage);
841
+ // Determine status based on similarity and strictness
842
+ let status;
843
+ const thresholds = {
844
+ strict: { preferred: 0.95, acceptable: 0.9, fallback: 0.8 },
845
+ moderate: { preferred: 0.85, acceptable: 0.75, fallback: 0.6 },
846
+ flexible: { preferred: 0.7, acceptable: 0.6, fallback: 0.4 },
847
+ };
848
+ const threshold = thresholds[strictness];
849
+ // Check red lines first
850
+ const violatesRedLine = position.redLines.some(rl => clause.text.toLowerCase().includes(rl.toLowerCase()));
851
+ if (violatesRedLine) {
852
+ status = 'violates_redline';
853
+ }
854
+ else if (preferredSimilarity >= threshold.preferred) {
855
+ status = 'matches_preferred';
856
+ }
857
+ else if (position.acceptableVariations.some(v => calculateTextSimilarity(clause.text, v) >= threshold.acceptable)) {
858
+ status = 'matches_acceptable';
859
+ }
860
+ else if (position.fallbackPositions.length > 0) {
861
+ status = 'requires_fallback';
862
+ }
863
+ else {
864
+ status = 'no_match';
865
+ }
866
+ matches.push({
867
+ clauseId: clause.id,
868
+ position,
869
+ status,
870
+ preferredSimilarity,
871
+ suggestedAlternative: suggestAlternatives ? position.preferredLanguage : undefined,
872
+ recommendation: generateRecommendation(status, clause.type),
873
+ });
874
+ }
875
+ return matches;
876
+ }
877
+ /**
878
+ * Calculate text similarity (simplified)
879
+ */
880
+ function calculateTextSimilarity(text1, text2) {
881
+ if (!text1 || !text2)
882
+ return 0;
883
+ const words1 = new Set(text1.toLowerCase().split(/\s+/));
884
+ const words2 = new Set(text2.toLowerCase().split(/\s+/));
885
+ const intersection = new Set([...words1].filter(w => words2.has(w)));
886
+ const union = new Set([...words1, ...words2]);
887
+ return intersection.size / union.size;
888
+ }
889
+ /**
890
+ * Generate recommendation based on match status
891
+ */
892
+ function generateRecommendation(status, _clauseType) {
893
+ const recommendations = {
894
+ matches_preferred: 'Clause matches preferred playbook position. No action required.',
895
+ matches_acceptable: 'Clause is within acceptable variation. Consider negotiating closer to preferred position.',
896
+ requires_fallback: 'Clause requires fallback position. Review fallback options and negotiate accordingly.',
897
+ violates_redline: 'CRITICAL: Clause violates red line. This must be negotiated before signing.',
898
+ no_match: 'No playbook position available. Conduct independent review of this clause.',
899
+ };
900
+ return recommendations[status];
901
+ }
902
+ /**
903
+ * Build negotiation priorities
904
+ */
905
+ function buildNegotiationPriorities(matches, prioritizedTypes) {
906
+ const priorities = [];
907
+ const statusPriority = {
908
+ violates_redline: 100,
909
+ requires_fallback: 70,
910
+ no_match: 50,
911
+ matches_acceptable: 30,
912
+ matches_preferred: 10,
913
+ };
914
+ for (const match of matches) {
915
+ let priority = statusPriority[match.status];
916
+ // Boost priority for prioritized clause types
917
+ if (prioritizedTypes?.includes(match.position.clauseType)) {
918
+ priority += 20;
919
+ }
920
+ priorities.push({
921
+ clauseId: match.clauseId,
922
+ priority,
923
+ reason: match.recommendation,
924
+ });
925
+ }
926
+ return priorities.sort((a, b) => b.priority - a.priority);
927
+ }
928
+ // ============================================================================
929
+ // Tool Registry
930
+ // ============================================================================
931
+ /**
932
+ * All Legal Contracts MCP Tools
933
+ */
934
+ export const legalContractsTools = [
935
+ clauseExtractTool,
936
+ riskAssessTool,
937
+ contractCompareTool,
938
+ obligationTrackTool,
939
+ playbookMatchTool,
940
+ ];
941
+ /**
942
+ * Tool name to handler map
943
+ */
944
+ export const toolHandlers = new Map([
945
+ ['legal/clause-extract', clauseExtractTool.handler],
946
+ ['legal/risk-assess', riskAssessTool.handler],
947
+ ['legal/contract-compare', contractCompareTool.handler],
948
+ ['legal/obligation-track', obligationTrackTool.handler],
949
+ ['legal/playbook-match', playbookMatchTool.handler],
950
+ ]);
951
+ /**
952
+ * Create tool context with bridges
953
+ */
954
+ export function createToolContext() {
955
+ const store = new Map();
956
+ return {
957
+ get: (key) => store.get(key),
958
+ set: (key, value) => { store.set(key, value); },
959
+ bridges: {
960
+ attention: createAttentionBridge(),
961
+ dag: createDAGBridge(),
962
+ },
963
+ };
964
+ }
965
+ export default legalContractsTools;
966
+ //# sourceMappingURL=mcp-tools.js.map