@ngockhoale/ukit 1.4.2 → 1.4.4

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.
@@ -273,7 +273,7 @@ const { pathToFileURL } = require('url');
273
273
  }
274
274
 
275
275
  if (concreteTask) {
276
- if (/\b(bug|debug|error|crash|broken|failing|stack trace|triage|fix|login|timeout)\b/.test(lower)) {
276
+ if (/\b(bug|debug|error|crash|broken|failing|stack trace|triage|fix|timeout)\b/.test(lower)) {
277
277
  return 'debug-specific';
278
278
  }
279
279
  if (/\b(review|audit|diff|pr feedback|code review|kiem tra|soat)\b/.test(raw)) {
@@ -1307,9 +1307,11 @@ const { pathToFileURL } = require('url');
1307
1307
  && contextRecommendation.command.trim();
1308
1308
  const executionMode = routingContext.executionMode || null;
1309
1309
  const executionScores = routingContext.executionScores || null;
1310
+ const executionCandidates = routingContext.executionCandidates || null;
1310
1311
  const approachSelector = buildApproachSelectorResult({
1311
1312
  executionMode,
1312
1313
  executionScores,
1314
+ executionCandidates,
1313
1315
  });
1314
1316
  const executionContract = buildExecutionContract(executionMode);
1315
1317
  const completionState = buildCompletionState({
@@ -1345,6 +1347,7 @@ const { pathToFileURL } = require('url');
1345
1347
  policyMode,
1346
1348
  executionMode,
1347
1349
  executionScores,
1350
+ executionCandidates,
1348
1351
  approachSelector,
1349
1352
  executionContract,
1350
1353
  completionState,
@@ -1371,6 +1374,12 @@ const { pathToFileURL } = require('url');
1371
1374
  const buildSignal = /\b(build|create|add|implement|feature)\b/.test(raw);
1372
1375
  const debugSignal = intentMode === 'debug-specific' || /\b(root cause|debug|triage|flaky|investigate|why)\b/.test(raw);
1373
1376
  const reviewSignal = intentMode === 'review-specific' || /\b(review|audit|verify|release readiness)\b/.test(raw);
1377
+ const failureSignal = /\b(failing|failed|broken|error|crash|timeout|undefined|exception|eacces|trace)\b/.test(raw);
1378
+ const impactSignal = /\b(map impact|impact|blast radius|all affected|across (?:runtime|templates|helpers|adapters|mirrors)|affected adapters|affected mirrors|inspect impact)\b/.test(raw);
1379
+ const boundedEditSignal = /\b(one[- ]line|small|tiny|single|local|guard|log line|loading message|label)\b/.test(raw);
1380
+ const implementSignal = /\b(implement|apply|update|modify|add|create|ship|deliver|fix)\b/.test(raw)
1381
+ || /\bchange\s+.+\s+to\s+.+\b/.test(raw);
1382
+ const explicitTarget = Boolean(targetFile);
1374
1383
 
1375
1384
  let editCertainty = 0;
1376
1385
  if (directTransformSignal) {
@@ -1384,7 +1393,7 @@ const { pathToFileURL } = require('url');
1384
1393
  let investigationNeed = 0;
1385
1394
  if (debugSignal) {
1386
1395
  investigationNeed = 3;
1387
- } else if (/\b(failing|broken|error|crash|timeout)\b/.test(raw)) {
1396
+ } else if (failureSignal) {
1388
1397
  investigationNeed = 2;
1389
1398
  }
1390
1399
 
@@ -1417,6 +1426,17 @@ const { pathToFileURL } = require('url');
1417
1426
  blastRadius,
1418
1427
  verificationBurden,
1419
1428
  ambiguity,
1429
+ sharedRisk,
1430
+ directTransformSignal,
1431
+ smallFixSignal,
1432
+ buildSignal,
1433
+ debugSignal,
1434
+ reviewSignal,
1435
+ failureSignal,
1436
+ impactSignal,
1437
+ boundedEditSignal,
1438
+ implementSignal,
1439
+ explicitTarget,
1420
1440
  };
1421
1441
  }
1422
1442
 
@@ -1426,40 +1446,64 @@ const { pathToFileURL } = require('url');
1426
1446
  targetFile = null,
1427
1447
  intentMode = null,
1428
1448
  executionScores = {},
1449
+ executionCandidates = null,
1429
1450
  } = {}) {
1430
1451
  const raw = `${promptText || ''}\n${commandText || ''}`.toLowerCase();
1431
1452
  const scores = executionScores;
1453
+ const candidates = executionCandidates ?? buildExecutionModeCandidates({
1454
+ promptText,
1455
+ commandText,
1456
+ targetFile,
1457
+ intentMode,
1458
+ executionScores,
1459
+ });
1432
1460
  const explicitReviewLead = /\b(review this|audit this|review\b.*\brelease readiness|verify\b.*\brelease readiness)\b/.test(raw);
1461
+ const strongImpactLead = scores.sharedRisk
1462
+ && (scores.impactSignal || /\b(check all affected|map all affected|across all affected)\b/.test(raw));
1463
+ const boundedLocalBuildCandidate = scores.buildSignal && scores.boundedEditSignal && scores.explicitTarget && !scores.sharedRisk;
1433
1464
 
1434
- if (intentMode === 'review-specific' || explicitReviewLead) {
1465
+ if ((intentMode === 'review-specific' || explicitReviewLead) && !scores.implementSignal) {
1435
1466
  return 'review-release';
1436
1467
  }
1437
1468
 
1438
- if (scores.blastRadius >= 3 && (scores.ambiguity >= 2 || scores.investigationNeed >= 2)) {
1469
+ if (strongImpactLead || (scores.blastRadius >= 3 && (scores.ambiguity >= 2 || scores.investigationNeed >= 2))) {
1439
1470
  return 'map-impact';
1440
1471
  }
1441
1472
 
1442
- if (scores.blastRadius >= 3 || isSharedImpactFile(targetFile)) {
1473
+ if (scores.sharedRisk && scores.failureSignal && scores.investigationNeed >= 2 && !scores.impactSignal) {
1443
1474
  return 'shared-edit';
1444
1475
  }
1445
1476
 
1446
- if (scores.investigationNeed >= 3) {
1477
+ if (scores.blastRadius >= 3 || scores.sharedRisk) {
1478
+ return 'shared-edit';
1479
+ }
1480
+
1481
+ if (scores.investigationNeed >= 3 || (scores.failureSignal && scores.investigationNeed >= 2)) {
1447
1482
  return 'find-cause';
1448
1483
  }
1449
1484
 
1485
+ if (scores.directTransformSignal && !scores.explicitTarget && scores.investigationNeed === 0 && !scores.sharedRisk) {
1486
+ return 'local-build';
1487
+ }
1488
+
1450
1489
  if (
1451
1490
  scores.editCertainty >= 3
1452
- && scores.ambiguity === 0
1491
+ && scores.ambiguity <= 1
1453
1492
  && scores.blastRadius === 0
1454
1493
  && scores.verificationBurden <= 1
1455
1494
  ) {
1456
1495
  return 'tiny-fix';
1457
1496
  }
1458
1497
 
1498
+ if (boundedLocalBuildCandidate && scores.investigationNeed === 0) {
1499
+ return 'local-fix';
1500
+ }
1501
+
1459
1502
  if (
1460
1503
  /\b(build|create|add|implement|feature|summary card)\b/.test(raw)
1461
1504
  && scores.investigationNeed === 0
1462
1505
  && scores.blastRadius < 3
1506
+ && !scores.boundedEditSignal
1463
1507
  ) {
1464
1508
  return 'local-build';
1465
1509
  }
@@ -1468,7 +1512,107 @@ const { pathToFileURL } = require('url');
1468
1512
  return 'local-fix';
1469
1513
  }
1470
1514
 
1471
- return 'local-build';
1515
+ return applySafeUpwardBias(candidates, 'local-build');
1516
+ }
1517
+
1518
+ function buildExecutionModeCandidates({
1519
+ promptText = '',
1520
+ commandText = '',
1521
+ targetFile = null,
1522
+ intentMode = null,
1523
+ executionScores = {},
1524
+ } = {}) {
1525
+ const scores = executionScores;
1526
+ const raw = `${promptText || ''}\n${commandText || ''}`.toLowerCase();
1527
+ const explicitReviewLead = (intentMode === 'review-specific' || /\b(review this|audit this|review\b.*\brelease readiness|verify\b.*\brelease readiness)\b/.test(raw))
1528
+ && !scores.implementSignal;
1529
+
1530
+ const modeScores = {
1531
+ 'tiny-fix': (
1532
+ (scores.editCertainty * 4)
1533
+ + (scores.directTransformSignal ? 4 : 0)
1534
+ + (scores.explicitTarget ? 1 : 0)
1535
+ - (scores.failureSignal ? 4 : 0)
1536
+ - (scores.blastRadius * 4)
1537
+ - (scores.ambiguity * 2)
1538
+ ),
1539
+ 'local-fix': (
1540
+ (scores.editCertainty * 3)
1541
+ + (scores.boundedEditSignal ? 3 : 0)
1542
+ + (scores.explicitTarget ? 1 : 0)
1543
+ - (scores.failureSignal ? 2 : 0)
1544
+ - (scores.blastRadius * 4)
1545
+ ),
1546
+ 'local-build': (
1547
+ (scores.buildSignal ? 6 : 0)
1548
+ + (scores.editCertainty * 1)
1549
+ + (scores.explicitTarget ? 1 : 0)
1550
+ - (scores.boundedEditSignal ? 3 : 0)
1551
+ - (scores.failureSignal ? 3 : 0)
1552
+ - (scores.blastRadius * 4)
1553
+ ),
1554
+ 'find-cause': (
1555
+ (scores.investigationNeed * 4)
1556
+ + (scores.failureSignal ? 3 : 0)
1557
+ + (scores.debugSignal ? 2 : 0)
1558
+ - (scores.blastRadius >= 3 ? 1 : 0)
1559
+ ),
1560
+ 'shared-edit': (
1561
+ (scores.sharedRisk ? 8 : 0)
1562
+ + (scores.editCertainty * 1)
1563
+ + (scores.buildSignal ? 1 : 0)
1564
+ - (scores.impactSignal ? 2 : 0)
1565
+ ),
1566
+ 'map-impact': (
1567
+ (scores.sharedRisk ? 7 : 0)
1568
+ + (scores.impactSignal ? 5 : 0)
1569
+ + (scores.ambiguity * 2)
1570
+ + scores.investigationNeed
1571
+ ),
1572
+ 'review-release': (
1573
+ (explicitReviewLead ? 10 : 0)
1574
+ + (scores.reviewSignal ? 3 : 0)
1575
+ - (scores.implementSignal ? 5 : 0)
1576
+ ),
1577
+ };
1578
+
1579
+ return Object.entries(modeScores)
1580
+ .map(([mode, score]) => ({ mode, score }))
1581
+ .sort((a, b) => b.score - a.score || executionModeRank(a.mode) - executionModeRank(b.mode));
1582
+ }
1583
+
1584
+ function applySafeUpwardBias(candidates = [], fallbackMode = 'local-build') {
1585
+ const [topCandidate, competingCandidate] = candidates;
1586
+ if (!topCandidate) {
1587
+ return fallbackMode;
1588
+ }
1589
+
1590
+ if (!competingCandidate) {
1591
+ return topCandidate.mode;
1592
+ }
1593
+
1594
+ const scoreGap = Number(topCandidate.score ?? 0) - Number(competingCandidate.score ?? 0);
1595
+ const rankGap = executionModeRank(competingCandidate.mode) - executionModeRank(topCandidate.mode);
1596
+ if (scoreGap <= 1 && rankGap === 1) {
1597
+ return competingCandidate.mode;
1598
+ }
1599
+
1600
+ return topCandidate.mode;
1601
+ }
1602
+
1603
+ function executionModeRank(mode = '') {
1604
+ const orderedModes = [
1605
+ 'tiny-fix',
1606
+ 'local-fix',
1607
+ 'local-build',
1608
+ 'find-cause',
1609
+ 'shared-edit',
1610
+ 'map-impact',
1611
+ 'review-release',
1612
+ ];
1613
+
1614
+ const index = orderedModes.indexOf(mode);
1615
+ return index >= 0 ? index : orderedModes.length;
1472
1616
  }
1473
1617
 
1474
1618
  function buildExecutionContract(executionMode = null) {
@@ -1540,6 +1684,7 @@ const { pathToFileURL } = require('url');
1540
1684
  function buildApproachSelectorResult({
1541
1685
  executionMode = null,
1542
1686
  executionScores = null,
1687
+ executionCandidates = null,
1543
1688
  } = {}) {
1544
1689
  if (!executionMode) {
1545
1690
  return null;
@@ -1588,6 +1733,14 @@ const { pathToFileURL } = require('url');
1588
1733
  contextPolicy: selectedPolicy.contextPolicy,
1589
1734
  verificationPolicy: executionContract?.verificationPolicy ?? null,
1590
1735
  completionRule: executionContract?.completionRule ?? null,
1736
+ competingMode: executionCandidates?.[1]?.mode ?? null,
1737
+ competingScoreGap: executionCandidates?.length >= 2
1738
+ ? Number(executionCandidates[0]?.score ?? 0) - Number(executionCandidates[1]?.score ?? 0)
1739
+ : null,
1740
+ candidateModes: (executionCandidates ?? []).slice(0, 3).map((entry) => ({
1741
+ mode: entry.mode,
1742
+ score: entry.score,
1743
+ })),
1591
1744
  };
1592
1745
  }
1593
1746
 
@@ -1675,6 +1828,8 @@ const { pathToFileURL } = require('url');
1675
1828
  repeatCount: reasons.length > 0 ? 1 : 0,
1676
1829
  stuckRisk: null,
1677
1830
  rescueMode: null,
1831
+ wideningBlocked: false,
1832
+ milestonePriority: reasons.length > 0 ? 'normal' : null,
1678
1833
  };
1679
1834
  }
1680
1835
 
@@ -1689,6 +1844,8 @@ const { pathToFileURL } = require('url');
1689
1844
  repeatCount: 0,
1690
1845
  stuckRisk: null,
1691
1846
  rescueMode: null,
1847
+ wideningBlocked: false,
1848
+ milestonePriority: null,
1692
1849
  };
1693
1850
  }
1694
1851
 
@@ -1717,15 +1874,45 @@ const { pathToFileURL } = require('url');
1717
1874
  const stuckRisk = repeatCount >= 3
1718
1875
  ? 'high'
1719
1876
  : (repeatCount === 2 ? 'elevated' : null);
1877
+ const wideningBlocked = repeatCount >= 2 && Boolean(rescueMode);
1878
+ const nextMilestone = repeatCount >= 2 && currentReasons.includes('missing-write-evidence')
1879
+ ? 'finish-write-before-more-analysis'
1880
+ : (
1881
+ repeatCount >= 2 && currentReasons.includes('pending-bounded-context')
1882
+ ? 'finish-current-milestone-before-more-reading'
1883
+ : continuationState.nextMilestone
1884
+ );
1720
1885
 
1721
1886
  return {
1722
1887
  ...continuationState,
1888
+ nextMilestone,
1723
1889
  repeatCount,
1724
1890
  stuckRisk,
1725
1891
  rescueMode,
1892
+ wideningBlocked,
1893
+ milestonePriority: wideningBlocked ? 'execute-current-milestone' : 'normal',
1726
1894
  };
1727
1895
  }
1728
1896
 
1897
+ function applyRescueBiasToRouteSummary(routeSummary = null) {
1898
+ if (!routeSummary || typeof routeSummary !== 'object') {
1899
+ return routeSummary;
1900
+ }
1901
+
1902
+ const continuationState = routeSummary.continuationState;
1903
+ if (
1904
+ continuationState?.repeatCount >= 2
1905
+ && continuationState?.wideningBlocked
1906
+ && (continuationState.reasons ?? []).includes('missing-write-evidence')
1907
+ ) {
1908
+ routeSummary.nextActionType = 'execute-current-milestone';
1909
+ routeSummary.nextActionCommand = null;
1910
+ routeSummary.helperHint = null;
1911
+ }
1912
+
1913
+ return routeSummary;
1914
+ }
1915
+
1729
1916
  const SHARED_IMPACT_PATTERNS = [
1730
1917
  /^\.claude\/hooks\//,
1731
1918
  /^\.claude\/ukit\//,
@@ -1901,6 +2088,79 @@ const { pathToFileURL } = require('url');
1901
2088
  };
1902
2089
  }
1903
2090
 
2091
+ function buildRouteAuditEntry({
2092
+ routingContext = {},
2093
+ routeSummary = {},
2094
+ state = null,
2095
+ } = {}) {
2096
+ const candidateModes = Array.isArray(routeSummary?.approachSelector?.candidateModes)
2097
+ ? routeSummary.approachSelector.candidateModes
2098
+ : [];
2099
+
2100
+ return {
2101
+ ts: Date.now(),
2102
+ source: state?.source || 'skill-router',
2103
+ requestKey: state?.requestKey || null,
2104
+ adapter: routingContext.adapter || 'claude',
2105
+ promptFingerprint: stableMachineDigest({
2106
+ prompt: routingContext.lastExplicitUserPromptText || '',
2107
+ adapter: routingContext.adapter || 'claude',
2108
+ }),
2109
+ targetFile: routingContext.targetFile || null,
2110
+ taskType: routingContext.taskType || null,
2111
+ executionMode: routeSummary.executionMode || null,
2112
+ competingMode: routeSummary?.approachSelector?.competingMode || null,
2113
+ competingScoreGap: routeSummary?.approachSelector?.competingScoreGap || null,
2114
+ candidateModes: candidateModes.slice(0, 3),
2115
+ nextActionType: routeSummary.nextActionType || null,
2116
+ nextMilestone: routeSummary?.continuationState?.nextMilestone || null,
2117
+ repeatCount: routeSummary?.continuationState?.repeatCount || null,
2118
+ rescueMode: routeSummary?.continuationState?.rescueMode || null,
2119
+ wideningBlocked: routeSummary?.continuationState?.wideningBlocked || null,
2120
+ };
2121
+ }
2122
+
2123
+ function appendRouteAuditEntry(filePath, entry) {
2124
+ if (!entry || typeof entry !== 'object') {
2125
+ return;
2126
+ }
2127
+
2128
+ let parsed = { entries: [] };
2129
+ if (fs.existsSync(filePath)) {
2130
+ try {
2131
+ parsed = JSON.parse(fs.readFileSync(filePath, 'utf8'));
2132
+ } catch {
2133
+ parsed = { entries: [] };
2134
+ }
2135
+ }
2136
+
2137
+ const entries = Array.isArray(parsed?.entries) ? parsed.entries : [];
2138
+ const dedupeKey = stableMachineDigest({
2139
+ requestKey: entry.requestKey,
2140
+ executionMode: entry.executionMode,
2141
+ nextActionType: entry.nextActionType,
2142
+ nextMilestone: entry.nextMilestone,
2143
+ repeatCount: entry.repeatCount,
2144
+ rescueMode: entry.rescueMode,
2145
+ });
2146
+ const filtered = entries.filter((item) => {
2147
+ const itemKey = stableMachineDigest({
2148
+ requestKey: item?.requestKey || null,
2149
+ executionMode: item?.executionMode || null,
2150
+ nextActionType: item?.nextActionType || null,
2151
+ nextMilestone: item?.nextMilestone || null,
2152
+ repeatCount: item?.repeatCount || null,
2153
+ rescueMode: item?.rescueMode || null,
2154
+ });
2155
+ return itemKey !== dedupeKey;
2156
+ });
2157
+
2158
+ ensureDir(filePath);
2159
+ fs.writeFileSync(filePath, JSON.stringify({
2160
+ entries: [entry, ...filtered].slice(0, 40),
2161
+ }));
2162
+ }
2163
+
1904
2164
  function shouldIncludePreviousContext({ routingContext = {}, useIndexedContext = true } = {}) {
1905
2165
  const taskType = String(routingContext?.taskType || '').trim();
1906
2166
  const hasExplicitTarget = Boolean(String(routingContext?.targetFile || '').trim());
@@ -1999,6 +2259,7 @@ const { pathToFileURL } = require('url');
1999
2259
  const projectRoot = process.env.PROJECT_ROOT || process.cwd();
2000
2260
  const statePath = process.env.STATE_FILE || path.join(projectRoot, '.claude', 'ukit', 'skill-router-state.json');
2001
2261
  const routeCachePath = path.join(projectRoot, '.claude', 'ukit', 'route-cache.json');
2262
+ const routeAuditPath = path.join(projectRoot, '.ukit', 'storage', 'cache', 'route-audit.json');
2002
2263
  const cacheUtilsPath = path.join(projectRoot, '.claude', 'ukit', 'index', 'cache-utils.mjs');
2003
2264
  const previous = readJson(statePath, {});
2004
2265
  const runtimeConfig = loadRuntimeConfig(projectRoot);
@@ -2106,13 +2367,20 @@ const { pathToFileURL } = require('url');
2106
2367
  intentMode,
2107
2368
  taskType,
2108
2369
  });
2370
+ const executionCandidates = buildExecutionModeCandidates({
2371
+ promptText,
2372
+ commandText,
2373
+ targetFile: filePath,
2374
+ intentMode,
2375
+ executionScores,
2376
+ });
2109
2377
  const executionMode = deriveExecutionMode({
2110
2378
  promptText,
2111
2379
  commandText,
2112
2380
  targetFile: filePath,
2113
2381
  intentMode,
2114
- taskType,
2115
2382
  executionScores,
2383
+ executionCandidates,
2116
2384
  });
2117
2385
  const lastExplicitUserPromptText = promptText.trim()
2118
2386
  || previous?.routingContext?.lastExplicitUserPromptText
@@ -2126,6 +2394,7 @@ const { pathToFileURL } = require('url');
2126
2394
  taskType,
2127
2395
  intentMode,
2128
2396
  executionScores,
2397
+ executionCandidates,
2129
2398
  executionMode,
2130
2399
  };
2131
2400
  const useIndexedContext = shouldUseIndexedContext({
@@ -2193,6 +2462,9 @@ const { pathToFileURL } = require('url');
2193
2462
  ),
2194
2463
  }
2195
2464
  : null;
2465
+ if (rescuedRouteSummary) {
2466
+ applyRescueBiasToRouteSummary(rescuedRouteSummary);
2467
+ }
2196
2468
  const reusedState = {
2197
2469
  ...cachedRouteState,
2198
2470
  source: 'skill-router',
@@ -2208,6 +2480,9 @@ const { pathToFileURL } = require('url');
2208
2480
 
2209
2481
  ensureDir(statePath);
2210
2482
  fs.writeFileSync(statePath, JSON.stringify(reusedState));
2483
+ appendRouteAuditEntry(routeAuditPath, buildRouteAuditEntry({
2484
+ state: reusedState,
2485
+ }));
2211
2486
 
2212
2487
  process.stdout.write(`[ukit-skill-router] Read skills: ${getActiveSkillPaths(reusedState.activeSkills || []).join(', ')}\n`);
2213
2488
  process.stdout.write(`[ukit-skill-router] Route: ${formatDisplayRouteSummary(reusedState.routeSummary, reusedState.routingContext)}\n`);
@@ -2263,6 +2538,7 @@ const { pathToFileURL } = require('url');
2263
2538
  routeSummary.continuationState,
2264
2539
  previous?.routeSummary?.continuationState || null,
2265
2540
  );
2541
+ applyRescueBiasToRouteSummary(routeSummary);
2266
2542
 
2267
2543
  const fingerprint = buildRouteStateFingerprint({
2268
2544
  activeSkills: selected,
@@ -2292,6 +2568,11 @@ const { pathToFileURL } = require('url');
2292
2568
  routeSummary: compactRouteSummary(routeSummary),
2293
2569
  };
2294
2570
  fs.writeFileSync(statePath, JSON.stringify(sharedState));
2571
+ appendRouteAuditEntry(routeAuditPath, buildRouteAuditEntry({
2572
+ routingContext,
2573
+ routeSummary,
2574
+ state: sharedState,
2575
+ }));
2295
2576
  if (typeof cacheUtils?.writeRecentCacheEntry === 'function') {
2296
2577
  await cacheUtils.writeRecentCacheEntry(routeCachePath, compactRouteCacheState(sharedState), {
2297
2578
  maxEntries: cacheUtils.DEFAULT_RECENT_CACHE_MAX_ENTRIES,
@@ -178,7 +178,7 @@ export const ROUTE_CATALOG = [
178
178
  order: 20,
179
179
  contextMode: 'standalone',
180
180
  signals: [
181
- { type: 'prompt', regex: /\b(pdf|pdf form|fill (?:this )?pdf|merge pdf|split pdf|extract tables? from .*pdf|extract text from .*pdf)\b/i, score: 8 },
181
+ { type: 'prompt', regex: /\b(pdf form|fillable pdf|fill (?:this )?pdf|merge pdfs?|split pdfs?|extract tables? from .*pdf|extract text from .*pdf|read (?:this |the |a )?pdf|open (?:this |the |a )?pdf|analy(?:s|z)e (?:this |the |a )?pdf|annotate (?:this |the |a )?pdf|create pdf|convert .*pdf|pdf extraction|pdf parser|pdf parsing)\b/i, score: 8 },
182
182
  { type: 'command', regex: /\b(pdftotext|qpdf|pypdf|pdfplumber|reportlab)\b/i, score: 5 },
183
183
  { type: 'file', regex: /\.pdf$/i, score: 5 },
184
184
  ],