@cyanheads/congressgov-mcp-server 0.7.0 → 0.7.2

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 (136) hide show
  1. package/AGENTS.md +31 -19
  2. package/CLAUDE.md +31 -19
  3. package/Dockerfile +2 -2
  4. package/README.md +216 -92
  5. package/changelog/0.1.x/0.1.0.md +36 -0
  6. package/changelog/0.2.x/0.2.0.md +26 -0
  7. package/changelog/0.2.x/0.2.1.md +16 -0
  8. package/changelog/0.3.x/0.3.0.md +26 -0
  9. package/changelog/0.3.x/0.3.1.md +14 -0
  10. package/changelog/0.3.x/0.3.10.md +15 -0
  11. package/changelog/0.3.x/0.3.11.md +18 -0
  12. package/changelog/0.3.x/0.3.12.md +15 -0
  13. package/changelog/0.3.x/0.3.13.md +20 -0
  14. package/changelog/0.3.x/0.3.14.md +30 -0
  15. package/changelog/0.3.x/0.3.15.md +28 -0
  16. package/changelog/0.3.x/0.3.16.md +34 -0
  17. package/changelog/0.3.x/0.3.17.md +28 -0
  18. package/changelog/0.3.x/0.3.18.md +18 -0
  19. package/changelog/0.3.x/0.3.19.md +36 -0
  20. package/changelog/0.3.x/0.3.2.md +12 -0
  21. package/changelog/0.3.x/0.3.20.md +22 -0
  22. package/changelog/0.3.x/0.3.21.md +15 -0
  23. package/changelog/0.3.x/0.3.22.md +29 -0
  24. package/changelog/0.3.x/0.3.23.md +25 -0
  25. package/changelog/0.3.x/0.3.24.md +20 -0
  26. package/changelog/0.3.x/0.3.25.md +12 -0
  27. package/changelog/0.3.x/0.3.26.md +25 -0
  28. package/changelog/0.3.x/0.3.27.md +16 -0
  29. package/changelog/0.3.x/0.3.28.md +18 -0
  30. package/changelog/0.3.x/0.3.29.md +13 -0
  31. package/changelog/0.3.x/0.3.3.md +11 -0
  32. package/changelog/0.3.x/0.3.30.md +28 -0
  33. package/changelog/0.3.x/0.3.31.md +28 -0
  34. package/changelog/0.3.x/0.3.32.md +21 -0
  35. package/changelog/0.3.x/0.3.4.md +18 -0
  36. package/changelog/0.3.x/0.3.5.md +23 -0
  37. package/changelog/0.3.x/0.3.6.md +10 -0
  38. package/changelog/0.3.x/0.3.7.md +15 -0
  39. package/changelog/0.3.x/0.3.8.md +29 -0
  40. package/changelog/0.3.x/0.3.9.md +21 -0
  41. package/changelog/0.4.x/0.4.0.md +28 -0
  42. package/changelog/0.4.x/0.4.1.md +17 -0
  43. package/changelog/0.4.x/0.4.2.md +39 -0
  44. package/changelog/0.4.x/0.4.3.md +16 -0
  45. package/changelog/0.5.x/0.5.0.md +22 -0
  46. package/changelog/0.5.x/0.5.1.md +16 -0
  47. package/changelog/0.5.x/0.5.2.md +29 -0
  48. package/changelog/0.6.x/0.6.0.md +24 -0
  49. package/changelog/0.7.x/0.7.0.md +13 -0
  50. package/changelog/0.7.x/0.7.1.md +35 -0
  51. package/changelog/0.7.x/0.7.2.md +20 -0
  52. package/changelog/template.md +151 -0
  53. package/dist/config/server-config.d.ts +3 -3
  54. package/dist/config/server-config.d.ts.map +1 -1
  55. package/dist/config/server-config.js +6 -3
  56. package/dist/config/server-config.js.map +1 -1
  57. package/dist/index.js +23 -3
  58. package/dist/index.js.map +1 -1
  59. package/dist/mcp-server/tools/definitions/bill-lookup.tool.d.ts +12 -4
  60. package/dist/mcp-server/tools/definitions/bill-lookup.tool.d.ts.map +1 -1
  61. package/dist/mcp-server/tools/definitions/bill-lookup.tool.js +94 -3
  62. package/dist/mcp-server/tools/definitions/bill-lookup.tool.js.map +1 -1
  63. package/dist/mcp-server/tools/definitions/bill-summaries.tool.d.ts +4 -0
  64. package/dist/mcp-server/tools/definitions/bill-summaries.tool.d.ts.map +1 -1
  65. package/dist/mcp-server/tools/definitions/bill-summaries.tool.js +34 -6
  66. package/dist/mcp-server/tools/definitions/bill-summaries.tool.js.map +1 -1
  67. package/dist/mcp-server/tools/definitions/committee-lookup.tool.d.ts +4 -0
  68. package/dist/mcp-server/tools/definitions/committee-lookup.tool.d.ts.map +1 -1
  69. package/dist/mcp-server/tools/definitions/committee-lookup.tool.js +2 -2
  70. package/dist/mcp-server/tools/definitions/committee-reports.tool.d.ts +9 -2
  71. package/dist/mcp-server/tools/definitions/committee-reports.tool.d.ts.map +1 -1
  72. package/dist/mcp-server/tools/definitions/committee-reports.tool.js +2 -2
  73. package/dist/mcp-server/tools/definitions/crs-reports.tool.d.ts +4 -0
  74. package/dist/mcp-server/tools/definitions/crs-reports.tool.d.ts.map +1 -1
  75. package/dist/mcp-server/tools/definitions/crs-reports.tool.js +2 -2
  76. package/dist/mcp-server/tools/definitions/daily-record.tool.d.ts +9 -2
  77. package/dist/mcp-server/tools/definitions/daily-record.tool.d.ts.map +1 -1
  78. package/dist/mcp-server/tools/definitions/daily-record.tool.js +2 -2
  79. package/dist/mcp-server/tools/definitions/enacted-laws.tool.d.ts +4 -0
  80. package/dist/mcp-server/tools/definitions/enacted-laws.tool.d.ts.map +1 -1
  81. package/dist/mcp-server/tools/definitions/enacted-laws.tool.js +2 -2
  82. package/dist/mcp-server/tools/definitions/member-lookup.tool.d.ts +4 -0
  83. package/dist/mcp-server/tools/definitions/member-lookup.tool.d.ts.map +1 -1
  84. package/dist/mcp-server/tools/definitions/member-lookup.tool.js +2 -2
  85. package/dist/mcp-server/tools/definitions/roll-votes.tool.d.ts +4 -0
  86. package/dist/mcp-server/tools/definitions/roll-votes.tool.d.ts.map +1 -1
  87. package/dist/mcp-server/tools/definitions/roll-votes.tool.js +2 -2
  88. package/dist/mcp-server/tools/definitions/search-bills.tool.d.ts +1 -1
  89. package/dist/mcp-server/tools/definitions/search-bills.tool.js +3 -3
  90. package/dist/mcp-server/tools/definitions/senate-nominations.tool.d.ts +4 -0
  91. package/dist/mcp-server/tools/definitions/senate-nominations.tool.d.ts.map +1 -1
  92. package/dist/mcp-server/tools/definitions/senate-nominations.tool.js +2 -2
  93. package/dist/mcp-server/tools/format-helpers.d.ts.map +1 -1
  94. package/dist/mcp-server/tools/format-helpers.js +85 -30
  95. package/dist/mcp-server/tools/format-helpers.js.map +1 -1
  96. package/dist/mcp-server/tools/summary-window.d.ts +112 -0
  97. package/dist/mcp-server/tools/summary-window.d.ts.map +1 -0
  98. package/dist/mcp-server/tools/summary-window.js +182 -0
  99. package/dist/mcp-server/tools/summary-window.js.map +1 -0
  100. package/dist/mcp-server/tools/tool-helpers.d.ts +26 -2
  101. package/dist/mcp-server/tools/tool-helpers.d.ts.map +1 -1
  102. package/dist/mcp-server/tools/tool-helpers.js +26 -2
  103. package/dist/mcp-server/tools/tool-helpers.js.map +1 -1
  104. package/dist/services/congress-api/congress-api-service.d.ts.map +1 -1
  105. package/dist/services/congress-api/congress-api-service.js +8 -11
  106. package/dist/services/congress-api/congress-api-service.js.map +1 -1
  107. package/dist/services/congress-documents/congress-documents-service.d.ts.map +1 -1
  108. package/dist/services/congress-documents/congress-documents-service.js +10 -9
  109. package/dist/services/congress-documents/congress-documents-service.js.map +1 -1
  110. package/dist/services/congress-documents/extract-text-stream.js +1 -1
  111. package/dist/services/congress-documents/extract-text-stream.js.map +1 -1
  112. package/dist/services/congress-documents/extract-text.d.ts +0 -6
  113. package/dist/services/congress-documents/extract-text.d.ts.map +1 -1
  114. package/dist/services/congress-documents/extract-text.js +4 -37
  115. package/dist/services/congress-documents/extract-text.js.map +1 -1
  116. package/dist/services/congress-mirror/congress-mirror-service.js +1 -1
  117. package/dist/services/congress-mirror/ingest.d.ts +1 -1
  118. package/dist/services/congress-mirror/normalize.d.ts +4 -1
  119. package/dist/services/congress-mirror/normalize.d.ts.map +1 -1
  120. package/dist/services/congress-mirror/normalize.js +8 -12
  121. package/dist/services/congress-mirror/normalize.js.map +1 -1
  122. package/dist/services/congress-mirror/types.d.ts +1 -1
  123. package/dist/services/senate-lis/parse.d.ts.map +1 -1
  124. package/dist/services/senate-lis/parse.js +74 -2
  125. package/dist/services/senate-lis/parse.js.map +1 -1
  126. package/dist/services/senate-lis/senate-vote-service.d.ts.map +1 -1
  127. package/dist/services/senate-lis/senate-vote-service.js +9 -7
  128. package/dist/services/senate-lis/senate-vote-service.js.map +1 -1
  129. package/dist/services/senate-lis/types.d.ts +8 -1
  130. package/dist/services/senate-lis/types.d.ts.map +1 -1
  131. package/dist/utils/character-references.d.ts +27 -0
  132. package/dist/utils/character-references.d.ts.map +1 -0
  133. package/dist/utils/character-references.js +60 -0
  134. package/dist/utils/character-references.js.map +1 -0
  135. package/package.json +11 -9
  136. package/server.json +3 -3
@@ -21,24 +21,28 @@ export declare const crsReportsTool: import("@cyanheads/mcp-ts-core").ToolDefini
21
21
  }, z.core.$loose>, readonly [{
22
22
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.NotFound;
23
23
  readonly reason: 'not_found';
24
+ readonly thrownBy: 'service';
24
25
  readonly retryable: false;
25
26
  readonly when: 'A requested bill, member, committee, report, vote, or nomination does not exist in Congress.gov.';
26
27
  readonly recovery: "Use the tool's list or browse operation to discover valid identifiers, then retry with one that exists.";
27
28
  }, {
28
29
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.RateLimited;
29
30
  readonly reason: 'rate_limited';
31
+ readonly thrownBy: 'service';
30
32
  readonly retryable: true;
31
33
  readonly when: 'The Congress.gov API rate limit (5,000 requests/hour per key) was exceeded.';
32
34
  readonly recovery: 'Wait for the hourly rate-limit window to reset before retrying, or reduce the request frequency.';
33
35
  }, {
34
36
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.InvalidParams;
35
37
  readonly reason: 'invalid_request';
38
+ readonly thrownBy: 'service';
36
39
  readonly retryable: false;
37
40
  readonly when: 'Congress.gov rejected the request as malformed — a bad date range or an identifier with an unexpected shape.';
38
41
  readonly recovery: 'Check parameter formats: dates must be ISO 8601 like 2026-05-01T00:00:00Z and identifiers must match their documented shape.';
39
42
  }, {
40
43
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.ServiceUnavailable;
41
44
  readonly reason: 'upstream_error';
45
+ readonly thrownBy: 'service';
42
46
  readonly retryable: true;
43
47
  readonly when: 'Congress.gov returned an unexpected error (5xx or another non-2xx status).';
44
48
  readonly recovery: 'Retry after a short delay; if the failure persists the Congress.gov service may be temporarily degraded.';
@@ -1 +1 @@
1
- {"version":3,"file":"crs-reports.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/crs-reports.tool.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAQ,CAAC,EAAE,MAAM,wBAAwB,CAAC;AAYjD,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAkDzB,CAAC"}
1
+ {"version":3,"file":"crs-reports.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/crs-reports.tool.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAQ,CAAC,EAAE,MAAM,wBAAwB,CAAC;AAYjD,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAkDzB,CAAC"}
@@ -4,8 +4,8 @@
4
4
  */
5
5
  import { tool, z } from '@cyanheads/mcp-ts-core';
6
6
  import { validationError } from '@cyanheads/mcp-ts-core/errors';
7
- import { formatCrsReports } from '../../../mcp-server/tools/format-helpers.js';
8
- import { congressErrorContracts, listEnrichment, listOrDetail, notifyIfNoMatches, } from '../../../mcp-server/tools/tool-helpers.js';
7
+ import { formatCrsReports } from '../format-helpers.js';
8
+ import { congressErrorContracts, listEnrichment, listOrDetail, notifyIfNoMatches, } from '../tool-helpers.js';
9
9
  import { getCongressApi } from '../../../services/congress-api/congress-api-service.js';
10
10
  export const crsReportsTool = tool('congressgov_crs_reports', {
11
11
  title: 'Congress.gov CRS Reports',
@@ -31,24 +31,28 @@ export declare const dailyRecordTool: import("@cyanheads/mcp-ts-core").ToolDefin
31
31
  }, z.core.$loose>, readonly [{
32
32
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.NotFound;
33
33
  readonly reason: 'not_found';
34
+ readonly thrownBy: 'service';
34
35
  readonly retryable: false;
35
36
  readonly when: 'A requested bill, member, committee, report, vote, or nomination does not exist in Congress.gov.';
36
37
  readonly recovery: "Use the tool's list or browse operation to discover valid identifiers, then retry with one that exists.";
37
38
  }, {
38
39
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.RateLimited;
39
40
  readonly reason: 'rate_limited';
41
+ readonly thrownBy: 'service';
40
42
  readonly retryable: true;
41
43
  readonly when: 'The Congress.gov API rate limit (5,000 requests/hour per key) was exceeded.';
42
44
  readonly recovery: 'Wait for the hourly rate-limit window to reset before retrying, or reduce the request frequency.';
43
45
  }, {
44
46
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.InvalidParams;
45
47
  readonly reason: 'invalid_request';
48
+ readonly thrownBy: 'service';
46
49
  readonly retryable: false;
47
50
  readonly when: 'Congress.gov rejected the request as malformed — a bad date range or an identifier with an unexpected shape.';
48
51
  readonly recovery: 'Check parameter formats: dates must be ISO 8601 like 2026-05-01T00:00:00Z and identifiers must match their documented shape.';
49
52
  }, {
50
53
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.ServiceUnavailable;
51
54
  readonly reason: 'upstream_error';
55
+ readonly thrownBy: 'service';
52
56
  readonly retryable: true;
53
57
  readonly when: 'Congress.gov returned an unexpected error (5xx or another non-2xx status).';
54
58
  readonly recovery: 'Retry after a short delay; if the failure persists the Congress.gov service may be temporarily degraded.';
@@ -67,21 +71,24 @@ export declare const dailyRecordTool: import("@cyanheads/mcp-ts-core").ToolDefin
67
71
  }, {
68
72
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.ServiceUnavailable;
69
73
  readonly reason: 'document_fetch_failed';
74
+ readonly thrownBy: 'service';
70
75
  readonly retryable: true;
71
76
  readonly when: 'www.congress.gov did not return a readable document — a non-2xx status, a network failure, an empty body, or a content type that is not text or XML.';
72
77
  readonly recovery: 'Retry after a short delay; if the failure persists, request a different format or read the document at the sourceUrl the text operation returns.';
73
78
  }, {
74
79
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.InvalidParams;
75
80
  readonly reason: 'document_too_large';
81
+ readonly thrownBy: 'service';
76
82
  readonly retryable: false;
77
83
  readonly when: 'The document is larger than the byte ceiling this server retrieves, so no character window can be served from it.';
78
84
  readonly recovery: 'Read the document at the sourceUrl the text operation returns. A document this size clears the ceiling in every format it publishes, so switching format reaches the same limit.';
79
85
  }, {
80
86
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.InvalidParams;
81
87
  readonly reason: 'offset_past_end';
88
+ readonly thrownBy: 'service';
82
89
  readonly retryable: false;
83
- readonly when: 'characterOffset is at or beyond the last character of the document, so the window would be empty.';
84
- readonly recovery: 'Restart the walk at characterOffset 0 and follow nextOffset, which goes null once the document has been read to the end.';
90
+ readonly when: "characterOffset is at or beyond the last character of every text the call would window — the document on 'content', or every returned row's summary text on 'summaries' — so the window would be empty.";
91
+ readonly recovery: "Restart the walk at characterOffset 0 and follow the response's nextOffset (or a summary row's textNextOffset), which goes null once the text has been read to the end.";
85
92
  }], {
86
93
  effectiveQuery: z.ZodString;
87
94
  totalCount: z.ZodNumber;
@@ -1 +1 @@
1
- {"version":3,"file":"daily-record.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/daily-record.tool.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAQ,CAAC,EAAE,MAAM,wBAAwB,CAAC;AAqBjD,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAkK1B,CAAC"}
1
+ {"version":3,"file":"daily-record.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/daily-record.tool.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAQ,CAAC,EAAE,MAAM,wBAAwB,CAAC;AAqBjD,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAkK1B,CAAC"}
@@ -4,8 +4,8 @@
4
4
  */
5
5
  import { tool, z } from '@cyanheads/mcp-ts-core';
6
6
  import { validationError } from '@cyanheads/mcp-ts-core/errors';
7
- import { formatDailyRecord } from '../../../mcp-server/tools/format-helpers.js';
8
- import { congressErrorContracts, documentErrorContracts, documentWindowInput, listEnrichment, listOrDetail, notifyIfNoMatches, numericIdentifier, toIdentifierNumber, } from '../../../mcp-server/tools/tool-helpers.js';
7
+ import { formatDailyRecord } from '../format-helpers.js';
8
+ import { congressErrorContracts, documentErrorContracts, documentWindowInput, listEnrichment, listOrDetail, notifyIfNoMatches, numericIdentifier, toIdentifierNumber, } from '../tool-helpers.js';
9
9
  import { getCongressApi } from '../../../services/congress-api/congress-api-service.js';
10
10
  import { getCongressDocuments } from '../../../services/congress-documents/congress-documents-service.js';
11
11
  import { describeFormat, selectDocumentUrl, } from '../../../services/congress-documents/document-formats.js';
@@ -26,24 +26,28 @@ export declare const enactedLawsTool: import("@cyanheads/mcp-ts-core").ToolDefin
26
26
  }, z.core.$loose>, readonly [{
27
27
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.NotFound;
28
28
  readonly reason: 'not_found';
29
+ readonly thrownBy: 'service';
29
30
  readonly retryable: false;
30
31
  readonly when: 'A requested bill, member, committee, report, vote, or nomination does not exist in Congress.gov.';
31
32
  readonly recovery: "Use the tool's list or browse operation to discover valid identifiers, then retry with one that exists.";
32
33
  }, {
33
34
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.RateLimited;
34
35
  readonly reason: 'rate_limited';
36
+ readonly thrownBy: 'service';
35
37
  readonly retryable: true;
36
38
  readonly when: 'The Congress.gov API rate limit (5,000 requests/hour per key) was exceeded.';
37
39
  readonly recovery: 'Wait for the hourly rate-limit window to reset before retrying, or reduce the request frequency.';
38
40
  }, {
39
41
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.InvalidParams;
40
42
  readonly reason: 'invalid_request';
43
+ readonly thrownBy: 'service';
41
44
  readonly retryable: false;
42
45
  readonly when: 'Congress.gov rejected the request as malformed — a bad date range or an identifier with an unexpected shape.';
43
46
  readonly recovery: 'Check parameter formats: dates must be ISO 8601 like 2026-05-01T00:00:00Z and identifiers must match their documented shape.';
44
47
  }, {
45
48
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.ServiceUnavailable;
46
49
  readonly reason: 'upstream_error';
50
+ readonly thrownBy: 'service';
47
51
  readonly retryable: true;
48
52
  readonly when: 'Congress.gov returned an unexpected error (5xx or another non-2xx status).';
49
53
  readonly recovery: 'Retry after a short delay; if the failure persists the Congress.gov service may be temporarily degraded.';
@@ -1 +1 @@
1
- {"version":3,"file":"enacted-laws.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/enacted-laws.tool.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAQ,CAAC,EAAE,MAAM,wBAAwB,CAAC;AAejD,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAkF1B,CAAC"}
1
+ {"version":3,"file":"enacted-laws.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/enacted-laws.tool.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAQ,CAAC,EAAE,MAAM,wBAAwB,CAAC;AAejD,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAkF1B,CAAC"}
@@ -4,8 +4,8 @@
4
4
  */
5
5
  import { tool, z } from '@cyanheads/mcp-ts-core';
6
6
  import { validationError } from '@cyanheads/mcp-ts-core/errors';
7
- import { formatLaws } from '../../../mcp-server/tools/format-helpers.js';
8
- import { buildEffectiveQuery, congressErrorContracts, lawNumberIdentifier, listEnrichment, listOrDetail, notifyIfNoMatches, toLawNumber, } from '../../../mcp-server/tools/tool-helpers.js';
7
+ import { formatLaws } from '../format-helpers.js';
8
+ import { buildEffectiveQuery, congressErrorContracts, lawNumberIdentifier, listEnrichment, listOrDetail, notifyIfNoMatches, toLawNumber, } from '../tool-helpers.js';
9
9
  import { getCongressApi } from '../../../services/congress-api/congress-api-service.js';
10
10
  export const enactedLawsTool = tool('congressgov_enacted_laws', {
11
11
  title: 'Congress.gov Enacted Laws',
@@ -27,24 +27,28 @@ export declare const memberLookupTool: import("@cyanheads/mcp-ts-core").ToolDefi
27
27
  }, z.core.$loose>, readonly [{
28
28
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.NotFound;
29
29
  readonly reason: 'not_found';
30
+ readonly thrownBy: 'service';
30
31
  readonly retryable: false;
31
32
  readonly when: 'A requested bill, member, committee, report, vote, or nomination does not exist in Congress.gov.';
32
33
  readonly recovery: "Use the tool's list or browse operation to discover valid identifiers, then retry with one that exists.";
33
34
  }, {
34
35
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.RateLimited;
35
36
  readonly reason: 'rate_limited';
37
+ readonly thrownBy: 'service';
36
38
  readonly retryable: true;
37
39
  readonly when: 'The Congress.gov API rate limit (5,000 requests/hour per key) was exceeded.';
38
40
  readonly recovery: 'Wait for the hourly rate-limit window to reset before retrying, or reduce the request frequency.';
39
41
  }, {
40
42
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.InvalidParams;
41
43
  readonly reason: 'invalid_request';
44
+ readonly thrownBy: 'service';
42
45
  readonly retryable: false;
43
46
  readonly when: 'Congress.gov rejected the request as malformed — a bad date range or an identifier with an unexpected shape.';
44
47
  readonly recovery: 'Check parameter formats: dates must be ISO 8601 like 2026-05-01T00:00:00Z and identifiers must match their documented shape.';
45
48
  }, {
46
49
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.ServiceUnavailable;
47
50
  readonly reason: 'upstream_error';
51
+ readonly thrownBy: 'service';
48
52
  readonly retryable: true;
49
53
  readonly when: 'Congress.gov returned an unexpected error (5xx or another non-2xx status).';
50
54
  readonly recovery: 'Retry after a short delay; if the failure persists the Congress.gov service may be temporarily degraded.';
@@ -1 +1 @@
1
- {"version":3,"file":"member-lookup.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/member-lookup.tool.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAQ,CAAC,EAAE,MAAM,wBAAwB,CAAC;AAajD,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA0H3B,CAAC"}
1
+ {"version":3,"file":"member-lookup.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/member-lookup.tool.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAQ,CAAC,EAAE,MAAM,wBAAwB,CAAC;AAajD,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA0H3B,CAAC"}
@@ -4,8 +4,8 @@
4
4
  */
5
5
  import { tool, z } from '@cyanheads/mcp-ts-core';
6
6
  import { validationError } from '@cyanheads/mcp-ts-core/errors';
7
- import { formatMembers } from '../../../mcp-server/tools/format-helpers.js';
8
- import { buildEffectiveQuery, congressErrorContracts, listEnrichment, listOrDetail, notifyIfNoMatches, } from '../../../mcp-server/tools/tool-helpers.js';
7
+ import { formatMembers } from '../format-helpers.js';
8
+ import { buildEffectiveQuery, congressErrorContracts, listEnrichment, listOrDetail, notifyIfNoMatches, } from '../tool-helpers.js';
9
9
  import { getCongressApi } from '../../../services/congress-api/congress-api-service.js';
10
10
  export const memberLookupTool = tool('congressgov_member_lookup', {
11
11
  title: 'Congress.gov Member Lookup',
@@ -34,24 +34,28 @@ export declare const rollVotesTool: import("@cyanheads/mcp-ts-core").ToolDefinit
34
34
  }, z.core.$loose>, readonly [{
35
35
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.NotFound;
36
36
  readonly reason: 'not_found';
37
+ readonly thrownBy: 'service';
37
38
  readonly retryable: false;
38
39
  readonly when: 'A requested bill, member, committee, report, vote, or nomination does not exist in Congress.gov.';
39
40
  readonly recovery: "Use the tool's list or browse operation to discover valid identifiers, then retry with one that exists.";
40
41
  }, {
41
42
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.RateLimited;
42
43
  readonly reason: 'rate_limited';
44
+ readonly thrownBy: 'service';
43
45
  readonly retryable: true;
44
46
  readonly when: 'The Congress.gov API rate limit (5,000 requests/hour per key) was exceeded.';
45
47
  readonly recovery: 'Wait for the hourly rate-limit window to reset before retrying, or reduce the request frequency.';
46
48
  }, {
47
49
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.InvalidParams;
48
50
  readonly reason: 'invalid_request';
51
+ readonly thrownBy: 'service';
49
52
  readonly retryable: false;
50
53
  readonly when: 'Congress.gov rejected the request as malformed — a bad date range or an identifier with an unexpected shape.';
51
54
  readonly recovery: 'Check parameter formats: dates must be ISO 8601 like 2026-05-01T00:00:00Z and identifiers must match their documented shape.';
52
55
  }, {
53
56
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.ServiceUnavailable;
54
57
  readonly reason: 'upstream_error';
58
+ readonly thrownBy: 'service';
55
59
  readonly retryable: true;
56
60
  readonly when: 'Congress.gov returned an unexpected error (5xx or another non-2xx status).';
57
61
  readonly recovery: 'Retry after a short delay; if the failure persists the Congress.gov service may be temporarily degraded.';
@@ -1 +1 @@
1
- {"version":3,"file":"roll-votes.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/roll-votes.tool.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EAAQ,CAAC,EAAE,MAAM,wBAAwB,CAAC;AAcjD,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA4HxB,CAAC"}
1
+ {"version":3,"file":"roll-votes.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/roll-votes.tool.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EAAQ,CAAC,EAAE,MAAM,wBAAwB,CAAC;AAcjD,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA4HxB,CAAC"}
@@ -6,8 +6,8 @@
6
6
  */
7
7
  import { tool, z } from '@cyanheads/mcp-ts-core';
8
8
  import { validationError } from '@cyanheads/mcp-ts-core/errors';
9
- import { formatVotes } from '../../../mcp-server/tools/format-helpers.js';
10
- import { buildEffectiveQuery, congressErrorContracts, listEnrichment, listOrDetail, notifyIfNoMatches, } from '../../../mcp-server/tools/tool-helpers.js';
9
+ import { formatVotes } from '../format-helpers.js';
10
+ import { buildEffectiveQuery, congressErrorContracts, listEnrichment, listOrDetail, notifyIfNoMatches, } from '../tool-helpers.js';
11
11
  import { getCongressApi } from '../../../services/congress-api/congress-api-service.js';
12
12
  import { getSenateVoteService } from '../../../services/senate-lis/senate-vote-service.js';
13
13
  export const rollVotesTool = tool('congressgov_roll_votes', {
@@ -5,7 +5,7 @@
5
5
  * live fallback: a mirror that has not finished its initial build returns an empty
6
6
  * result with an enrichment notice rather than a thrown error. Gated behind
7
7
  * CONGRESS_MIRROR_ENABLED via disabledTool() — when the mirror is off (the
8
- * default) the tool stays on the manifest and the landing page for operators but
8
+ * default) the tool stays on the landing page for operators but
9
9
  * is skipped during MCP registration, so clients never see it in tools/list.
10
10
  * @module mcp-server/tools/definitions/search-bills
11
11
  */
@@ -5,14 +5,14 @@
5
5
  * live fallback: a mirror that has not finished its initial build returns an empty
6
6
  * result with an enrichment notice rather than a thrown error. Gated behind
7
7
  * CONGRESS_MIRROR_ENABLED via disabledTool() — when the mirror is off (the
8
- * default) the tool stays on the manifest and the landing page for operators but
8
+ * default) the tool stays on the landing page for operators but
9
9
  * is skipped during MCP registration, so clients never see it in tools/list.
10
10
  * @module mcp-server/tools/definitions/search-bills
11
11
  */
12
12
  import { disabledTool, tool, z } from '@cyanheads/mcp-ts-core';
13
13
  import { getServerConfig } from '../../../config/server-config.js';
14
- import { formatSearchBills } from '../../../mcp-server/tools/format-helpers.js';
15
- import { buildEffectiveQuery, listEnrichment, listOutput, notifyIfNoMatches, } from '../../../mcp-server/tools/tool-helpers.js';
14
+ import { formatSearchBills } from '../format-helpers.js';
15
+ import { buildEffectiveQuery, listEnrichment, listOutput, notifyIfNoMatches, } from '../tool-helpers.js';
16
16
  import { BILL_TYPE_CODES } from '../../../services/congress-api/types.js';
17
17
  import { getCongressMirror } from '../../../services/congress-mirror/congress-mirror-service.js';
18
18
  const searchBillsDef = tool('congressgov_search_bills', {
@@ -27,24 +27,28 @@ export declare const senateNominationsTool: import("@cyanheads/mcp-ts-core").Too
27
27
  }, z.core.$loose>, readonly [{
28
28
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.NotFound;
29
29
  readonly reason: 'not_found';
30
+ readonly thrownBy: 'service';
30
31
  readonly retryable: false;
31
32
  readonly when: 'A requested bill, member, committee, report, vote, or nomination does not exist in Congress.gov.';
32
33
  readonly recovery: "Use the tool's list or browse operation to discover valid identifiers, then retry with one that exists.";
33
34
  }, {
34
35
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.RateLimited;
35
36
  readonly reason: 'rate_limited';
37
+ readonly thrownBy: 'service';
36
38
  readonly retryable: true;
37
39
  readonly when: 'The Congress.gov API rate limit (5,000 requests/hour per key) was exceeded.';
38
40
  readonly recovery: 'Wait for the hourly rate-limit window to reset before retrying, or reduce the request frequency.';
39
41
  }, {
40
42
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.InvalidParams;
41
43
  readonly reason: 'invalid_request';
44
+ readonly thrownBy: 'service';
42
45
  readonly retryable: false;
43
46
  readonly when: 'Congress.gov rejected the request as malformed — a bad date range or an identifier with an unexpected shape.';
44
47
  readonly recovery: 'Check parameter formats: dates must be ISO 8601 like 2026-05-01T00:00:00Z and identifiers must match their documented shape.';
45
48
  }, {
46
49
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.ServiceUnavailable;
47
50
  readonly reason: 'upstream_error';
51
+ readonly thrownBy: 'service';
48
52
  readonly retryable: true;
49
53
  readonly when: 'Congress.gov returned an unexpected error (5xx or another non-2xx status).';
50
54
  readonly recovery: 'Retry after a short delay; if the failure persists the Congress.gov service may be temporarily degraded.';
@@ -1 +1 @@
1
- {"version":3,"file":"senate-nominations.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/senate-nominations.tool.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAQ,CAAC,EAAE,MAAM,wBAAwB,CAAC;AAYjD,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAgIhC,CAAC"}
1
+ {"version":3,"file":"senate-nominations.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/senate-nominations.tool.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAQ,CAAC,EAAE,MAAM,wBAAwB,CAAC;AAYjD,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAgIhC,CAAC"}
@@ -4,8 +4,8 @@
4
4
  */
5
5
  import { tool, z } from '@cyanheads/mcp-ts-core';
6
6
  import { validationError } from '@cyanheads/mcp-ts-core/errors';
7
- import { formatNominations } from '../../../mcp-server/tools/format-helpers.js';
8
- import { buildEffectiveQuery, congressErrorContracts, listEnrichment, listOrDetail, notifyIfNoMatches, } from '../../../mcp-server/tools/tool-helpers.js';
7
+ import { formatNominations } from '../format-helpers.js';
8
+ import { buildEffectiveQuery, congressErrorContracts, listEnrichment, listOrDetail, notifyIfNoMatches, } from '../tool-helpers.js';
9
9
  import { getCongressApi } from '../../../services/congress-api/congress-api-service.js';
10
10
  export const senateNominationsTool = tool('congressgov_senate_nominations', {
11
11
  title: 'Congress.gov Senate Nominations',
@@ -1 +1 @@
1
- {"version":3,"file":"format-helpers.d.ts","sourceRoot":"","sources":["../../../src/mcp-server/tools/format-helpers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,KAAK,SAAS,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAw2DhD,sFAAsF;AACtF,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,EAAE,CAYxE;AAwCD,2DAA2D;AAC3D,eAAO,MAAM,eAAe,WAvEhB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,SAAS,EAuEkB,CAAC;AA8CpE,qDAAqD;AACrD,eAAO,MAAM,iBAAiB,WAtHlB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,SAAS,EAsHuB,CAAC;AAEzE,oEAAoE;AACpE,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,EAAE,CAoB1E;AAWD,iFAAiF;AACjF,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,EAAE,CAkB7E;AAED,kDAAkD;AAClD,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,EAAE,CAWnF;AAED,mCAAmC;AACnC,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,EAAE,CAK7E;AAED,4FAA4F;AAC5F,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,EAAE,CAc9E;AAED,8FAA8F;AAC9F,eAAO,MAAM,UAAU,WArNX,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,SAAS,EAqNiC,CAAC;AAEnF;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,EAAE,CAcxE;AAED,iEAAiE;AACjE,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,EAAE,CAa9E"}
1
+ {"version":3,"file":"format-helpers.d.ts","sourceRoot":"","sources":["../../../src/mcp-server/tools/format-helpers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAIH,KAAK,SAAS,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AA45DhD,sFAAsF;AACtF,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,EAAE,CAYxE;AAgDD,2DAA2D;AAC3D,eAAO,MAAM,eAAe,WA/EhB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,SAAS,EA+EkB,CAAC;AA8CpE,qDAAqD;AACrD,eAAO,MAAM,iBAAiB,WA9HlB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,SAAS,EA8HuB,CAAC;AAEzE,oEAAoE;AACpE,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,EAAE,CAoB1E;AAWD,iFAAiF;AACjF,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,EAAE,CAkB7E;AAED,kDAAkD;AAClD,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,EAAE,CAWnF;AAED,mCAAmC;AACnC,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,EAAE,CAK7E;AAED,4FAA4F;AAC5F,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,EAAE,CAc9E;AAED,8FAA8F;AAC9F,eAAO,MAAM,UAAU,WA7NX,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,SAAS,EA6NiC,CAAC;AAEnF;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,EAAE,CAcxE;AAED,iEAAiE;AACjE,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,EAAE,CAa9E"}
@@ -26,6 +26,12 @@
26
26
  *
27
27
  * @module mcp-server/tools/format-helpers
28
28
  */
29
+ import { decodeCharacterReferences } from '../../utils/character-references.js';
30
+ /**
31
+ * Horizontal whitespace at this surface: spaces, tabs, and the literal U+00A0 in
32
+ * the class — what a decoded ` ` leaves behind, and ordinary spacing here.
33
+ */
34
+ const HORIZONTAL_WHITESPACE_RE = /[ \t ]+/g;
29
35
  // ── Primitives ──────────────────────────────────────────────────────
30
36
  function tb(content) {
31
37
  return [{ type: 'text', text: content }];
@@ -39,30 +45,35 @@ function preserveHtmlBlockBoundaries(html) {
39
45
  .replace(/<\s*\/\s*li\s*>/gi, '\n')
40
46
  .replace(/<\s*\/\s*(?:p|ul|ol)\s*>/gi, '\n\n');
41
47
  }
48
+ /**
49
+ * Collapse horizontal whitespace and blank-line runs, keeping the paragraph and
50
+ * line structure a block rendering carries.
51
+ */
52
+ function normalizeBlockWhitespace(text) {
53
+ return text
54
+ .replace(HORIZONTAL_WHITESPACE_RE, ' ')
55
+ .replace(/\n[ \t]+/g, '\n')
56
+ .replace(/[ \t]+\n/g, '\n')
57
+ .replace(/\n{3,}/g, '\n\n')
58
+ .trim();
59
+ }
42
60
  /**
43
61
  * Strip HTML to plain text while preserving paragraph and line breaks. Upstream
44
62
  * summary fields and other narrative bodies ship as HTML; we want the visible
45
63
  * structure (paragraph boundaries) to survive into the rendered Markdown.
46
64
  *
65
+ * Character references decode in one pass once the tags are gone, so text that
66
+ * literally says `&amp;lt;` reads as `&lt;` and not as a tag it never contained.
67
+ * A decoded `&nbsp;` is ordinary spacing at this surface, so it collapses with
68
+ * the rest of the horizontal whitespace.
69
+ *
47
70
  * Inline contexts that need single-line output should pass `{ inline: true }`.
48
71
  */
49
72
  function stripHtml(html, { inline = false } = {}) {
50
- const text = preserveHtmlBlockBoundaries(html)
51
- .replace(/<[^>]*>/g, '')
52
- .replace(/&nbsp;/g, ' ')
53
- .replace(/&#039;/g, "'")
54
- .replace(/&amp;/g, '&')
55
- .replace(/&lt;/g, '<')
56
- .replace(/&gt;/g, '>')
57
- .replace(/&quot;/g, '"');
73
+ const text = decodeCharacterReferences(preserveHtmlBlockBoundaries(html).replace(/<[^>]*>/g, ''));
58
74
  if (inline)
59
75
  return text.replace(/\s+/g, ' ').trim();
60
- return text
61
- .replace(/[ \t]+/g, ' ')
62
- .replace(/\n[ \t]+/g, '\n')
63
- .replace(/[ \t]+\n/g, '\n')
64
- .replace(/\n{3,}/g, '\n\n')
65
- .trim();
76
+ return normalizeBlockWhitespace(text);
66
77
  }
67
78
  /**
68
79
  * Wrap an emphasis span's content in Markdown markers with the span's own
@@ -92,23 +103,13 @@ function wrapEmphasis(inner, marker) {
92
103
  * through to the generic strip instead of leaving a stray marker.
93
104
  */
94
105
  function htmlToMarkdown(html) {
95
- return preserveHtmlBlockBoundaries(html)
106
+ const markup = preserveHtmlBlockBoundaries(html)
96
107
  .replace(/<\s*p[^>]*>/gi, '')
97
108
  .replace(/<\s*(?:strong|b)\s*>([\s\S]*?)<\s*\/\s*(?:strong|b)\s*>/gi, (_m, inner) => wrapEmphasis(inner, '**'))
98
109
  .replace(/<\s*(?:em|i)\s*>([\s\S]*?)<\s*\/\s*(?:em|i)\s*>/gi, (_m, inner) => wrapEmphasis(inner, '*'))
99
110
  .replace(/<\s*a[^>]*href=["']([^"']*)["'][^>]*>([\s\S]*?)<\s*\/\s*a\s*>/gi, '[$2]($1)')
100
- .replace(/<[^>]*>/g, '')
101
- .replace(/&nbsp;/g, ' ')
102
- .replace(/&#039;/g, "'")
103
- .replace(/&amp;/g, '&')
104
- .replace(/&lt;/g, '<')
105
- .replace(/&gt;/g, '>')
106
- .replace(/&quot;/g, '"')
107
- .replace(/[ \t]+/g, ' ')
108
- .replace(/\n[ \t]+/g, '\n')
109
- .replace(/[ \t]+\n/g, '\n')
110
- .replace(/\n{3,}/g, '\n\n')
111
- .trim();
111
+ .replace(/<[^>]*>/g, '');
112
+ return normalizeBlockWhitespace(decodeCharacterReferences(markup));
112
113
  }
113
114
  /** Safe deep access for compact field display — collapses whitespace to a single line. */
114
115
  function s(obj, ...path) {
@@ -530,6 +531,36 @@ const MEMBER_ROW_KEYS = new Set([
530
531
  'district',
531
532
  'url',
532
533
  ]);
534
+ /**
535
+ * Window metadata a bounded summary row carries. Both summary renderers show it
536
+ * as one curated line, so it stays out of their fall-through key sets.
537
+ */
538
+ const SUMMARY_WINDOW_KEYS = ['textTotalCharacters', 'textTruncated', 'textNextOffset'];
539
+ /**
540
+ * The window line a row gains once its `text` was bounded — absent on a row
541
+ * returned whole, which is what keeps an unbounded page's rendering unchanged.
542
+ *
543
+ * The row publishes the total and the next offset; this window's own start is
544
+ * derived from the text it returned, the same way the vote roster derives its
545
+ * range — `textNextOffset` is one past the last character returned.
546
+ */
547
+ function summaryWindowLine(item) {
548
+ const total = item.textTotalCharacters;
549
+ if (typeof total !== 'number')
550
+ return;
551
+ const text = typeof item.text === 'string' ? item.text : '';
552
+ const nextOffset = typeof item.textNextOffset === 'number' ? item.textNextOffset : null;
553
+ if (text.length === 0) {
554
+ return `**Summary text:** 0 of ${total.toLocaleString('en-US')} characters — characterOffset is past this summary's end`;
555
+ }
556
+ const end = nextOffset ?? total;
557
+ const start = end - text.length;
558
+ return join([
559
+ `**Summary text:** characters ${(start + 1).toLocaleString('en-US')}–${end.toLocaleString('en-US')} of ${total.toLocaleString('en-US')}`,
560
+ `**Truncated:** ${item.textTruncated === true}`,
561
+ nextOffset != null ? `next characterOffset: ${nextOffset}` : '_end of summary_',
562
+ ]);
563
+ }
533
564
  function renderSummaryItem(item, i) {
534
565
  const named = new Set(SUMMARY_ROW_KEYS);
535
566
  const billType = s(item, 'bill', 'type')?.toUpperCase() ?? '';
@@ -557,6 +588,9 @@ function renderSummaryItem(item, i) {
557
588
  lines.push(bill && typeof bill === 'object' && !Array.isArray(bill)
558
589
  ? withUnnamedFields(bill, billLine, BILL_REF_KEYS)
559
590
  : billLine);
591
+ const window = summaryWindowLine(item);
592
+ if (window)
593
+ lines.push(window);
560
594
  // The summary text is the critical data — the whole point of this tool
561
595
  lines.push('');
562
596
  lines.push(text || '_Summary text not available._');
@@ -565,7 +599,7 @@ function renderSummaryItem(item, i) {
565
599
  return withRowRest(item, lines, named);
566
600
  }
567
601
  /** `actionDesc`/`versionCode` and the two update dates are resolved by `pick`. */
568
- const SUMMARY_ROW_KEYS = ['actionDate', 'text', 'url', 'bill'];
602
+ const SUMMARY_ROW_KEYS = ['actionDate', 'text', 'url', 'bill', ...SUMMARY_WINDOW_KEYS];
569
603
  /** Fields of a summary row's nested bill reference the heading already renders. */
570
604
  const BILL_REF_KEYS = new Set(['congress', 'type', 'number', 'title', 'url']);
571
605
  function renderCrsReportItem(item, i) {
@@ -1204,7 +1238,7 @@ function renderSenateVoteItem(item, i) {
1204
1238
  if (question)
1205
1239
  lines.push(`**Question:** ${measure ? `${question} (${measure})` : question}`);
1206
1240
  const meta = join([
1207
- f('Date', s(item, 'voteDate')),
1241
+ f('Date', senateVoteDate(item)),
1208
1242
  f('Yeas', s(item, 'yeas')),
1209
1243
  f('Nays', s(item, 'nays')),
1210
1244
  ]);
@@ -1215,6 +1249,18 @@ function renderSenateVoteItem(item, i) {
1215
1249
  lines.push(title);
1216
1250
  return withRowRest(item, lines, SENATE_VOTE_ROW_KEYS);
1217
1251
  }
1252
+ /**
1253
+ * The resolved calendar date leads, with the menu's own year-less short form
1254
+ * beside it — a row carries both, and the short form is what the Senate's own
1255
+ * vote pages show.
1256
+ */
1257
+ function senateVoteDate(item) {
1258
+ const iso = s(item, 'voteDateIso');
1259
+ const published = s(item, 'voteDate');
1260
+ if (iso && published)
1261
+ return `${iso} (${published})`;
1262
+ return iso ?? published;
1263
+ }
1218
1264
  /** `chamber` is the dispatch marker, implicit in the Senate heading. */
1219
1265
  const SENATE_VOTE_ROW_KEYS = new Set([
1220
1266
  'chamber',
@@ -1224,6 +1270,7 @@ const SENATE_VOTE_ROW_KEYS = new Set([
1224
1270
  'question',
1225
1271
  'measure',
1226
1272
  'voteDate',
1273
+ 'voteDateIso',
1227
1274
  'yeas',
1228
1275
  'nays',
1229
1276
  'title',
@@ -1776,12 +1823,20 @@ function renderBillSubresourceSummaryItem(item, i) {
1776
1823
  const meta = join([f('Action Date', actionDate), f('Summary Updated', updated)]);
1777
1824
  if (meta)
1778
1825
  lines.push(meta);
1826
+ const window = summaryWindowLine(item);
1827
+ if (window)
1828
+ lines.push(window);
1779
1829
  const text = typeof item.text === 'string' ? htmlToMarkdown(item.text) : '';
1780
1830
  if (text)
1781
1831
  lines.push('', text);
1782
1832
  return withRowRest(item, lines, named);
1783
1833
  }
1784
- const BILL_SUMMARY_SUBRESOURCE_ROW_KEYS = ['actionDate', 'updateDate', 'text'];
1834
+ const BILL_SUMMARY_SUBRESOURCE_ROW_KEYS = [
1835
+ 'actionDate',
1836
+ 'updateDate',
1837
+ 'text',
1838
+ ...SUMMARY_WINDOW_KEYS,
1839
+ ];
1785
1840
  function pickBillListRenderer(first) {
1786
1841
  if ('title' in first && 'number' in first)
1787
1842
  return renderBillItem;