@claudetools/tools 0.8.3 → 0.8.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/dist/cli.js +41 -0
  2. package/dist/context/deduplication.d.ts +72 -0
  3. package/dist/context/deduplication.js +77 -0
  4. package/dist/context/deduplication.test.d.ts +6 -0
  5. package/dist/context/deduplication.test.js +84 -0
  6. package/dist/context/emergency-eviction.d.ts +73 -0
  7. package/dist/context/emergency-eviction.example.d.ts +13 -0
  8. package/dist/context/emergency-eviction.example.js +94 -0
  9. package/dist/context/emergency-eviction.js +226 -0
  10. package/dist/context/eviction-engine.d.ts +76 -0
  11. package/dist/context/eviction-engine.example.d.ts +7 -0
  12. package/dist/context/eviction-engine.example.js +144 -0
  13. package/dist/context/eviction-engine.js +176 -0
  14. package/dist/context/example-usage.d.ts +1 -0
  15. package/dist/context/example-usage.js +128 -0
  16. package/dist/context/exchange-summariser.d.ts +80 -0
  17. package/dist/context/exchange-summariser.js +261 -0
  18. package/dist/context/health-monitor.d.ts +97 -0
  19. package/dist/context/health-monitor.example.d.ts +1 -0
  20. package/dist/context/health-monitor.example.js +164 -0
  21. package/dist/context/health-monitor.js +210 -0
  22. package/dist/context/importance-scorer.d.ts +94 -0
  23. package/dist/context/importance-scorer.example.d.ts +1 -0
  24. package/dist/context/importance-scorer.example.js +140 -0
  25. package/dist/context/importance-scorer.js +187 -0
  26. package/dist/context/index.d.ts +9 -0
  27. package/dist/context/index.js +16 -0
  28. package/dist/context/session-helper.d.ts +10 -0
  29. package/dist/context/session-helper.js +51 -0
  30. package/dist/context/session-store.d.ts +94 -0
  31. package/dist/context/session-store.js +286 -0
  32. package/dist/context/usage-estimator.d.ts +131 -0
  33. package/dist/context/usage-estimator.js +260 -0
  34. package/dist/context/usage-estimator.test.d.ts +1 -0
  35. package/dist/context/usage-estimator.test.js +208 -0
  36. package/dist/context-cli.d.ts +16 -0
  37. package/dist/context-cli.js +309 -0
  38. package/dist/handlers/codedna-handlers.d.ts +1 -1
  39. package/dist/handlers/tool-handlers.js +215 -13
  40. package/dist/helpers/api-client.d.ts +5 -1
  41. package/dist/helpers/api-client.js +3 -1
  42. package/dist/helpers/circuit-breaker.d.ts +28 -0
  43. package/dist/helpers/circuit-breaker.js +97 -0
  44. package/dist/helpers/compact-formatter.d.ts +2 -0
  45. package/dist/helpers/compact-formatter.js +6 -0
  46. package/dist/helpers/error-tracking.js +1 -1
  47. package/dist/helpers/tasks-retry.d.ts +9 -0
  48. package/dist/helpers/tasks-retry.js +30 -0
  49. package/dist/helpers/tasks.d.ts +91 -5
  50. package/dist/helpers/tasks.js +261 -16
  51. package/dist/helpers/usage-analytics.js +1 -1
  52. package/dist/hooks/index.d.ts +4 -0
  53. package/dist/hooks/index.js +6 -0
  54. package/dist/hooks/post-tool-use-hook-cli.d.ts +2 -0
  55. package/dist/hooks/post-tool-use-hook-cli.js +34 -0
  56. package/dist/hooks/post-tool-use.d.ts +67 -0
  57. package/dist/hooks/post-tool-use.js +234 -0
  58. package/dist/hooks/stop-hook-cli.d.ts +2 -0
  59. package/dist/hooks/stop-hook-cli.js +34 -0
  60. package/dist/hooks/stop.d.ts +64 -0
  61. package/dist/hooks/stop.js +192 -0
  62. package/dist/index.d.ts +4 -0
  63. package/dist/index.js +2 -0
  64. package/dist/logger.d.ts +1 -1
  65. package/dist/logger.js +4 -0
  66. package/dist/setup.js +206 -2
  67. package/dist/tools.js +107 -2
  68. package/package.json +19 -18
  69. package/scripts/verify-prompt-compliance.sh +0 -0
package/dist/setup.js CHANGED
@@ -747,11 +747,22 @@ fi
747
747
  # Escape the query for JSON (handle quotes and newlines)
748
748
  ESCAPED_QUERY=$(echo "$USER_QUERY" | head -c 500 | jq -Rs '.')
749
749
 
750
+ # Get session state for query-aware budget allocation (if SESSION_ID available)
751
+ SESSION_STATE_JSON='{}'
752
+ if [ -n "$SESSION_ID" ]; then
753
+ # Try to get session state from SessionStore
754
+ TOOLS_DIR="$HOME/.claudetools/node_modules/@claudetools/tools"
755
+ if [ -f "$TOOLS_DIR/dist/context/session-helper.js" ]; then
756
+ SESSION_STATE_JSON=$(node "$TOOLS_DIR/dist/context/session-helper.js" "$SESSION_ID" 2>/dev/null || echo '{}')
757
+ fi
758
+ fi
759
+
750
760
  # Inject context with semantic search based on the user's prompt
761
+ # Include session_state if available for dynamic budget calculation
751
762
  RESULT=$(curl -s --max-time 2 -X POST "$API_URL/api/v1/context/inject" \\
752
763
  -H "Authorization: Bearer $API_KEY" \\
753
764
  -H "Content-Type: application/json" \\
754
- -d "{\\"query\\": $ESCAPED_QUERY, \\"project_id\\": \\"$PROJECT_ID\\", \\"cwd\\": \\"$CWD\\"}" \\
765
+ -d "{\\"query\\": $ESCAPED_QUERY, \\"project_id\\": \\"$PROJECT_ID\\", \\"cwd\\": \\"$CWD\\", \\"session_state\\": $SESSION_STATE_JSON}" \\
755
766
  2>/dev/null)
756
767
 
757
768
  # Output context as additionalContext JSON for Claude Code
@@ -1330,6 +1341,170 @@ export async function runUninstall() {
1330
1341
  console.log('\n' + chalk.green('ClaudeTools removed from Claude Code.'));
1331
1342
  console.log(chalk.dim('Your ~/.claudetools/ config and data are preserved.\n'));
1332
1343
  }
1344
+ async function runOnboarding(apiUrl, apiKey, projectId, projectName) {
1345
+ header('Project Onboarding');
1346
+ info('Answer a few questions to help Claude understand your project better.');
1347
+ console.log(chalk.dim('This creates memory facts that provide context in future sessions.\n'));
1348
+ const spinner = ora('Starting onboarding session...').start();
1349
+ try {
1350
+ // Start onboarding session
1351
+ const startResponse = await fetch(`${apiUrl}/api/v1/onboarding/start`, {
1352
+ method: 'POST',
1353
+ headers: {
1354
+ 'Content-Type': 'application/json',
1355
+ 'Authorization': `Bearer ${apiKey}`,
1356
+ },
1357
+ body: JSON.stringify({ project_id: projectId }),
1358
+ });
1359
+ if (!startResponse.ok) {
1360
+ spinner.fail('Could not start onboarding');
1361
+ const errorData = await startResponse.json().catch(() => ({}));
1362
+ error(errorData.error || `HTTP ${startResponse.status}`);
1363
+ return;
1364
+ }
1365
+ const startData = await startResponse.json();
1366
+ if (!startData.success || !startData.data) {
1367
+ spinner.fail('Failed to start onboarding');
1368
+ return;
1369
+ }
1370
+ spinner.succeed('Onboarding session started');
1371
+ const sessionId = startData.data.session_id;
1372
+ let questions = startData.data.questions;
1373
+ let state = startData.data.state;
1374
+ let questionCount = 0;
1375
+ const maxQuestions = 8; // Limit to avoid fatigue
1376
+ // Interactive question loop
1377
+ while (questions.length > 0 && questionCount < maxQuestions) {
1378
+ const question = questions[0];
1379
+ questionCount++;
1380
+ console.log('');
1381
+ console.log(chalk.cyan(`Question ${questionCount}`) + (question.category ? chalk.dim(` [${question.category}]`) : ''));
1382
+ console.log(chalk.bold(question.question));
1383
+ let answer;
1384
+ if (question.options && question.options.length > 0) {
1385
+ // Multiple choice question
1386
+ const { selected } = await prompts({
1387
+ type: 'select',
1388
+ name: 'selected',
1389
+ message: 'Select an option:',
1390
+ choices: [
1391
+ ...question.options.map(opt => ({ title: opt, value: opt })),
1392
+ { title: 'Skip this question', value: '__skip__' },
1393
+ ],
1394
+ });
1395
+ if (selected === '__skip__' || !selected) {
1396
+ questions.shift();
1397
+ continue;
1398
+ }
1399
+ answer = selected;
1400
+ }
1401
+ else {
1402
+ // Free text question
1403
+ const { response } = await prompts({
1404
+ type: 'text',
1405
+ name: 'response',
1406
+ message: '>',
1407
+ validate: (v) => v.length > 0 || 'Please enter a response (or type "skip" to skip)',
1408
+ });
1409
+ if (!response || response.toLowerCase() === 'skip') {
1410
+ questions.shift();
1411
+ continue;
1412
+ }
1413
+ answer = response;
1414
+ }
1415
+ // Submit answer
1416
+ const answerSpinner = ora('Processing...').start();
1417
+ try {
1418
+ const answerResponse = await fetch(`${apiUrl}/api/v1/onboarding/answer`, {
1419
+ method: 'POST',
1420
+ headers: {
1421
+ 'Content-Type': 'application/json',
1422
+ 'Authorization': `Bearer ${apiKey}`,
1423
+ },
1424
+ body: JSON.stringify({
1425
+ session_id: sessionId,
1426
+ question_id: question.id,
1427
+ answer,
1428
+ }),
1429
+ });
1430
+ if (answerResponse.ok) {
1431
+ const answerData = await answerResponse.json();
1432
+ if (answerData.success && answerData.data) {
1433
+ const factsCount = answerData.data.facts_extracted || 0;
1434
+ if (factsCount > 0) {
1435
+ answerSpinner.succeed(`Recorded (${factsCount} fact${factsCount > 1 ? 's' : ''} extracted)`);
1436
+ }
1437
+ else {
1438
+ answerSpinner.succeed('Recorded');
1439
+ }
1440
+ // Update questions and state
1441
+ if (answerData.data.next_questions) {
1442
+ questions = answerData.data.next_questions;
1443
+ }
1444
+ else {
1445
+ questions.shift();
1446
+ }
1447
+ if (answerData.data.state) {
1448
+ state = answerData.data.state;
1449
+ }
1450
+ }
1451
+ else {
1452
+ answerSpinner.warn('Answer recorded (no facts extracted)');
1453
+ questions.shift();
1454
+ }
1455
+ }
1456
+ else {
1457
+ answerSpinner.fail('Could not submit answer');
1458
+ questions.shift();
1459
+ }
1460
+ }
1461
+ catch {
1462
+ answerSpinner.fail('Network error');
1463
+ questions.shift();
1464
+ }
1465
+ // Check if we should stop
1466
+ if (state.phase === 'confirmation' || state.phase === 'done') {
1467
+ break;
1468
+ }
1469
+ }
1470
+ // Complete onboarding
1471
+ console.log('');
1472
+ const completeSpinner = ora('Completing onboarding...').start();
1473
+ try {
1474
+ const completeResponse = await fetch(`${apiUrl}/api/v1/onboarding/complete`, {
1475
+ method: 'POST',
1476
+ headers: {
1477
+ 'Content-Type': 'application/json',
1478
+ 'Authorization': `Bearer ${apiKey}`,
1479
+ },
1480
+ body: JSON.stringify({
1481
+ session_id: sessionId,
1482
+ project_id: projectId,
1483
+ }),
1484
+ });
1485
+ if (completeResponse.ok) {
1486
+ const completeData = await completeResponse.json();
1487
+ if (completeData.success && completeData.data) {
1488
+ const totalFacts = completeData.data.facts_extracted || 0;
1489
+ completeSpinner.succeed(`Onboarding complete! ${totalFacts} facts stored in memory.`);
1490
+ }
1491
+ else {
1492
+ completeSpinner.succeed('Onboarding complete!');
1493
+ }
1494
+ }
1495
+ else {
1496
+ completeSpinner.warn('Onboarding session saved (completion had issues)');
1497
+ }
1498
+ }
1499
+ catch {
1500
+ completeSpinner.warn('Could not finalize onboarding');
1501
+ }
1502
+ }
1503
+ catch (err) {
1504
+ spinner.fail('Onboarding failed');
1505
+ error(err instanceof Error ? err.message : String(err));
1506
+ }
1507
+ }
1333
1508
  // -----------------------------------------------------------------------------
1334
1509
  // Project Init
1335
1510
  // -----------------------------------------------------------------------------
@@ -1491,12 +1666,41 @@ export async function runInit() {
1491
1666
  const newContent = existingContent.trimEnd() + '\n' + projectContent;
1492
1667
  writeFileSync(projectClaudeMd, newContent);
1493
1668
  success('Created .claude/CLAUDE.md');
1669
+ // Ensure hooks are installed and configured
1670
+ // (User might have run init without setup, or hooks might have been deleted)
1671
+ header('Hook Configuration');
1672
+ try {
1673
+ await installHooks();
1674
+ await configureSettings();
1675
+ info('Hooks configured successfully');
1676
+ }
1677
+ catch (err) {
1678
+ // Non-fatal - just warn
1679
+ console.log(chalk.yellow('⚠ Warning: Could not configure hooks. Run "claudetools --setup" to fix.'));
1680
+ }
1494
1681
  // Summary
1495
1682
  console.log('\n' + chalk.green(' Project initialized!\n'));
1496
1683
  console.log(' ' + chalk.bold('Project:') + ` ${projectName}`);
1497
1684
  console.log(' ' + chalk.bold('ID:') + ` ${projectId}`);
1498
1685
  console.log(' ' + chalk.bold('Config:') + ` ${projectClaudeMd}\n`);
1499
- console.log(chalk.dim(' Memory tools are now configured for this project.\n'));
1686
+ // Offer onboarding
1687
+ if (config.apiKey) {
1688
+ const { runOnboard } = await prompts({
1689
+ type: 'confirm',
1690
+ name: 'runOnboard',
1691
+ message: 'Run project onboarding? (Helps Claude understand your project)',
1692
+ initial: true,
1693
+ });
1694
+ if (runOnboard) {
1695
+ await runOnboarding(config.apiUrl || DEFAULT_CONFIG.apiUrl, config.apiKey, projectId, projectName);
1696
+ }
1697
+ else {
1698
+ console.log(chalk.dim('\n You can run onboarding later with: claudetools onboard\n'));
1699
+ }
1700
+ }
1701
+ else {
1702
+ console.log(chalk.dim(' Memory tools are now configured for this project.\n'));
1703
+ }
1500
1704
  }
1501
1705
  // -----------------------------------------------------------------------------
1502
1706
  // Cleanup Command (standalone)
package/dist/tools.js CHANGED
@@ -146,6 +146,10 @@ EXAMPLES:
146
146
  type: 'string',
147
147
  description: 'Project ID (optional, uses default if not provided)',
148
148
  },
149
+ is_critical: {
150
+ type: 'boolean',
151
+ description: 'Mark as critical fact - always injected regardless of query relevance. Use sparingly for truly essential facts.',
152
+ },
149
153
  },
150
154
  required: ['entity1', 'relationship', 'entity2', 'context'],
151
155
  },
@@ -461,6 +465,54 @@ High hit rate memories (frequently fetched after being indexed) are good candida
461
465
  required: ['goal', 'epic_title', 'tasks'],
462
466
  },
463
467
  },
468
+ {
469
+ name: 'task_plan_revise',
470
+ description: 'Modify an existing epic by adding new tasks, removing tasks, or updating task details. Enables iterative planning without recreating the entire plan.',
471
+ inputSchema: {
472
+ type: 'object',
473
+ properties: {
474
+ epic_id: {
475
+ type: 'string',
476
+ description: 'The ID of the epic to revise',
477
+ },
478
+ add_tasks: {
479
+ type: 'array',
480
+ items: {
481
+ type: 'object',
482
+ properties: {
483
+ title: { type: 'string' },
484
+ description: { type: 'string' },
485
+ effort: { type: 'string', enum: ['xs', 's', 'm', 'l', 'xl'] },
486
+ domain: { type: 'string' },
487
+ blocked_by: { type: 'array', items: { type: 'string' } },
488
+ },
489
+ required: ['title'],
490
+ },
491
+ description: 'New tasks to add to the epic',
492
+ },
493
+ remove_task_ids: {
494
+ type: 'array',
495
+ items: { type: 'string' },
496
+ description: 'Task IDs to cancel (sets status to cancelled)',
497
+ },
498
+ update_tasks: {
499
+ type: 'array',
500
+ items: {
501
+ type: 'object',
502
+ properties: {
503
+ task_id: { type: 'string' },
504
+ title: { type: 'string' },
505
+ description: { type: 'string' },
506
+ effort: { type: 'string', enum: ['xs', 's', 'm', 'l', 'xl'] },
507
+ },
508
+ required: ['task_id'],
509
+ },
510
+ description: 'Tasks to update (adds context about changes)',
511
+ },
512
+ },
513
+ required: ['epic_id'],
514
+ },
515
+ },
464
516
  {
465
517
  name: 'task_start',
466
518
  description: 'PROACTIVE: When starting work on a task, claim it and get all context. Use this before beginning any task work.',
@@ -604,7 +656,16 @@ High hit rate memories (frequently fetched after being indexed) are good candida
604
656
  },
605
657
  {
606
658
  name: 'task_claim',
607
- description: 'Claim a task for working on it. Creates a lock preventing other agents from working on it.',
659
+ description: `Claim a task for working on it. Creates a lock preventing other agents from working on it.
660
+
661
+ Lock duration is automatically scaled based on task effort:
662
+ - xs: 15 minutes
663
+ - s: 30 minutes (default if no effort specified)
664
+ - m: 60 minutes (1 hour)
665
+ - l: 120 minutes (2 hours)
666
+ - xl: 240 minutes (4 hours)
667
+
668
+ Explicit lock_duration_minutes parameter overrides the effort-based default.`,
608
669
  inputSchema: {
609
670
  type: 'object',
610
671
  properties: {
@@ -618,7 +679,7 @@ High hit rate memories (frequently fetched after being indexed) are good candida
618
679
  },
619
680
  lock_duration_minutes: {
620
681
  type: 'number',
621
- description: 'Lock duration in minutes (default: 30)',
682
+ description: 'Optional: Override effort-based lock duration (in minutes)',
622
683
  },
623
684
  },
624
685
  required: ['task_id', 'agent_id'],
@@ -735,6 +796,32 @@ High hit rate memories (frequently fetched after being indexed) are good candida
735
796
  required: ['task_id', 'agent_id'],
736
797
  },
737
798
  },
799
+ {
800
+ name: 'task_handoff',
801
+ description: 'Hand off a task to a different expert worker type during execution. Releases current lock, updates agent_type, and sets task back to ready status for re-dispatch.',
802
+ inputSchema: {
803
+ type: 'object',
804
+ properties: {
805
+ task_id: {
806
+ type: 'string',
807
+ description: 'The task ID to hand off',
808
+ },
809
+ new_worker_type: {
810
+ type: 'string',
811
+ description: 'Target worker type (e.g., "api-expert", "frontend-expert", "database-expert")',
812
+ },
813
+ reason: {
814
+ type: 'string',
815
+ description: 'Why the handoff is needed (e.g., "requires database expertise", "needs API integration")',
816
+ },
817
+ agent_id: {
818
+ type: 'string',
819
+ description: 'Your agent identifier (default: claude-code)',
820
+ },
821
+ },
822
+ required: ['task_id', 'new_worker_type', 'reason'],
823
+ },
824
+ },
738
825
  // =========================================================================
739
826
  // ORCHESTRATION TOOLS
740
827
  // =========================================================================
@@ -917,6 +1004,24 @@ High hit rate memories (frequently fetched after being indexed) are good candida
917
1004
  required: ['epic_id'],
918
1005
  },
919
1006
  },
1007
+ {
1008
+ name: 'task_aggregate',
1009
+ description: 'Aggregate work_log context from all sibling tasks under an epic for orchestrator synthesis. Returns structured summary of completed work across all tasks in the epic.',
1010
+ inputSchema: {
1011
+ type: 'object',
1012
+ properties: {
1013
+ epic_id: {
1014
+ type: 'string',
1015
+ description: 'Epic ID to aggregate from',
1016
+ },
1017
+ include_pending: {
1018
+ type: 'boolean',
1019
+ description: 'Include non-completed tasks (default: false)',
1020
+ },
1021
+ },
1022
+ required: ['epic_id'],
1023
+ },
1024
+ },
920
1025
  // =========================================================================
921
1026
  // CODEBASE MAPPING TOOLS
922
1027
  // =========================================================================
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@claudetools/tools",
3
- "version": "0.8.3",
3
+ "version": "0.8.5",
4
4
  "description": "Persistent AI memory, task management, and codebase intelligence for Claude Code",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -14,22 +14,6 @@
14
14
  "README.md",
15
15
  "LICENSE"
16
16
  ],
17
- "scripts": {
18
- "build": "tsc",
19
- "start": "node dist/index.js",
20
- "dev": "tsx src/index.ts",
21
- "test": "vitest run",
22
- "test:watch": "vitest",
23
- "test:ui": "vitest --ui",
24
- "prepublishOnly": "npm run build",
25
- "codedna:monitor": "tsx -e \"import { runMonitoring } from './src/helpers/codedna-monitoring.js'; runMonitoring()\"",
26
- "codedna:analytics": "tsx -e \"import { weeklyAnalyticsSummary } from './src/helpers/usage-analytics.js'; weeklyAnalyticsSummary()\"",
27
- "codedna:analytics:24h": "tsx -e \"import { getLast24HoursAnalytics, printAnalytics } from './src/helpers/usage-analytics.js'; const r = await getLast24HoursAnalytics(); printAnalytics(r, 'Last 24 Hours')\"",
28
- "codedna:analytics:30d": "tsx -e \"import { getLast30DaysAnalytics, printAnalytics } from './src/helpers/usage-analytics.js'; const r = await getLast30DaysAnalytics(); printAnalytics(r, 'Last 30 Days')\"",
29
- "prompt:verify": "scripts/verify-prompt-compliance.sh",
30
- "eval:build-dataset": "tsx src/evaluation/build-dataset.ts",
31
- "eval:threshold": "tsx src/evaluation/threshold-eval.ts"
32
- },
33
17
  "repository": {
34
18
  "type": "git",
35
19
  "url": "git+https://github.com/claudetools/memory.git"
@@ -65,12 +49,29 @@
65
49
  "typescript": "^5.3.0"
66
50
  },
67
51
  "devDependencies": {
52
+ "@types/better-sqlite3": "^7.6.13",
68
53
  "@types/node": "^20.10.0",
69
54
  "@types/nunjucks": "^3.2.6",
70
55
  "@types/prompts": "^2.4.9",
71
56
  "@vitest/ui": "^4.0.15",
57
+ "better-sqlite3": "^12.5.0",
72
58
  "tsx": "^4.7.0",
73
59
  "typescript": "^5.3.0",
74
60
  "vitest": "^4.0.15"
61
+ },
62
+ "scripts": {
63
+ "build": "tsc",
64
+ "start": "node dist/index.js",
65
+ "dev": "tsx src/index.ts",
66
+ "test": "vitest run",
67
+ "test:watch": "vitest",
68
+ "test:ui": "vitest --ui",
69
+ "codedna:monitor": "tsx -e \"import { runMonitoring } from './src/helpers/codedna-monitoring.js'; runMonitoring()\"",
70
+ "codedna:analytics": "tsx -e \"import { weeklyAnalyticsSummary } from './src/helpers/usage-analytics.js'; weeklyAnalyticsSummary()\"",
71
+ "codedna:analytics:24h": "tsx -e \"import { getLast24HoursAnalytics, printAnalytics } from './src/helpers/usage-analytics.js'; const r = await getLast24HoursAnalytics(); printAnalytics(r, 'Last 24 Hours')\"",
72
+ "codedna:analytics:30d": "tsx -e \"import { getLast30DaysAnalytics, printAnalytics } from './src/helpers/usage-analytics.js'; const r = await getLast30DaysAnalytics(); printAnalytics(r, 'Last 30 Days')\"",
73
+ "prompt:verify": "scripts/verify-prompt-compliance.sh",
74
+ "eval:build-dataset": "tsx src/evaluation/build-dataset.ts",
75
+ "eval:threshold": "tsx src/evaluation/threshold-eval.ts"
75
76
  }
76
- }
77
+ }
File without changes