@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.
- package/README.md +391 -0
- package/dist/bridges/attention-bridge.d.ts +83 -0
- package/dist/bridges/attention-bridge.d.ts.map +1 -0
- package/dist/bridges/attention-bridge.js +348 -0
- package/dist/bridges/attention-bridge.js.map +1 -0
- package/dist/bridges/dag-bridge.d.ts +75 -0
- package/dist/bridges/dag-bridge.d.ts.map +1 -0
- package/dist/bridges/dag-bridge.js +423 -0
- package/dist/bridges/dag-bridge.js.map +1 -0
- package/dist/bridges/index.d.ts +8 -0
- package/dist/bridges/index.d.ts.map +1 -0
- package/dist/bridges/index.js +8 -0
- package/dist/bridges/index.js.map +1 -0
- package/dist/index.d.ts +107 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +149 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp-tools.d.ts +93 -0
- package/dist/mcp-tools.d.ts.map +1 -0
- package/dist/mcp-tools.js +966 -0
- package/dist/mcp-tools.js.map +1 -0
- package/dist/types.d.ts +840 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +272 -0
- package/dist/types.js.map +1 -0
- package/package.json +79 -0
|
@@ -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
|