@tasksai/install 0.1.23 → 0.1.25

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tasksai/install",
3
- "version": "0.1.23",
3
+ "version": "0.1.25",
4
4
  "description": "Shared TasksAI MCP installer CLI",
5
5
  "type": "module",
6
6
  "bin": {
package/runtime/server.py CHANGED
@@ -28,6 +28,7 @@ import httpx
28
28
  import uuid
29
29
  from datetime import datetime
30
30
  from pathlib import Path
31
+ from urllib.parse import urlencode
31
32
 
32
33
  # The runtime is installed as a pair of adjacent Python files and is also
33
34
  # loaded directly by path in focused tests, so make the sibling matcher
@@ -124,7 +125,7 @@ if not LICENSE_KEY:
124
125
  print("Find your key in your purchase confirmation email.", file=sys.stderr, flush=True)
125
126
  sys.exit(1)
126
127
 
127
- SERVER_VERSION = "2.3.0"
128
+ SERVER_VERSION = "2.4.1"
128
129
 
129
130
  AUTH_HEADERS = {
130
131
  "Authorization": f"Bearer {LICENSE_KEY}",
@@ -519,6 +520,79 @@ async def api_post(path, payload):
519
520
  return resp.json()
520
521
 
521
522
 
523
+ def jurisdiction_sources_path(arguments):
524
+ """Build a metadata-only source lookup path without customer or property data."""
525
+ skill_id = (arguments.get("skill_id") or "").strip()
526
+ state = (arguments.get("state") or "").strip()
527
+ if not skill_id:
528
+ raise ValueError("skill_id is required.")
529
+
530
+ params = {"skill_id": skill_id}
531
+ if state:
532
+ params["state"] = state
533
+ for field in ("county", "locality", "district"):
534
+ value = (arguments.get(field) or "").strip()
535
+ if value:
536
+ params[field] = value
537
+ return f"/v1/skills/jurisdiction-sources?{urlencode(params)}"
538
+
539
+
540
+ def format_jurisdiction_source_pack(pack):
541
+ """Render the public source pack as compact instructions for the local AI."""
542
+ status = pack.get("status", "unsupported")
543
+ lines = [
544
+ "# Official Jurisdiction and Authority Source Pack",
545
+ "",
546
+ f"- Status: **{status}**",
547
+ f"- Coverage: **{pack.get('coverage_level', 'intake_only')}**",
548
+ f"- Skill: `{pack.get('skill_id', '')}`",
549
+ f"- Registry reviewed: {pack.get('registry_reviewed_on', 'not supplied')}",
550
+ f"- Registry refresh due: {pack.get('registry_refresh_due_on', 'not supplied')}",
551
+ ]
552
+ if pack.get("state_name"):
553
+ lines.append(f"- State: {pack['state_name']} ({pack.get('state_code', '')})")
554
+ if pack.get("county"):
555
+ lines.append(f"- County: {pack['county']}")
556
+ if pack.get("locality"):
557
+ lines.append(f"- Locality: {pack['locality']}")
558
+ if pack.get("district"):
559
+ lines.append(f"- District: {pack['district']}")
560
+ authority_layers = pack.get("authority_layers") or []
561
+ if authority_layers:
562
+ lines.append(f"- Authority layers: {', '.join(authority_layers)}")
563
+
564
+ sources = pack.get("sources") or []
565
+ if sources:
566
+ lines.extend(["", "## Sources"])
567
+ for source in sources:
568
+ effective = source.get("effective_date") or "not specified"
569
+ lines.extend(
570
+ [
571
+ "",
572
+ f"### {source.get('title', 'Official source')}",
573
+ f"- Authority: {source.get('authority', '')}",
574
+ f"- Jurisdiction: {source.get('jurisdiction', '')} ({source.get('scope_type', 'scope not supplied')})",
575
+ f"- Effective date: {effective}",
576
+ f"- Reviewed: {source.get('reviewed_on', '')}",
577
+ f"- Refresh due: {source.get('refresh_due_on', '')}",
578
+ f"- URL: {source.get('url', '')}",
579
+ f"- Use: {source.get('purpose', '')}",
580
+ f"- Limit: {source.get('limits', '')}",
581
+ ]
582
+ )
583
+
584
+ required_authorities = pack.get("required_user_authorities") or []
585
+ if required_authorities:
586
+ lines.extend(["", "## Professional-Supplied Authority Still Needed"])
587
+ lines.extend(f"- {item}" for item in required_authorities)
588
+
589
+ instructions = pack.get("instructions") or []
590
+ if instructions:
591
+ lines.extend(["", "## Required handling"])
592
+ lines.extend(f"- {instruction}" for instruction in instructions)
593
+ return "\n".join(lines)
594
+
595
+
522
596
  def get_install_id() -> str:
523
597
  """Stable anonymous install id for attribution; stored locally only."""
524
598
  global INSTALL_ID
@@ -828,10 +902,57 @@ def is_markdown_table_start(lines, index):
828
902
  return bool(header and markdown_table_alignments(separator))
829
903
 
830
904
 
905
+ def inferred_plain_heading_level(lines, index):
906
+ """Infer conservative headings when an AI omits Markdown # markers."""
907
+ text = lines[index].strip()
908
+ if not text or len(text) > 90 or "|" in text or ":" in text:
909
+ return None
910
+ if text.endswith((".", "?", "!", ";")):
911
+ return None
912
+
913
+ letters = [char for char in text if char.isalpha()]
914
+ if len(letters) >= 4 and all(char.isupper() for char in letters):
915
+ return 2
916
+
917
+ next_index = index + 1
918
+ while next_index < len(lines) and not lines[next_index].strip():
919
+ next_index += 1
920
+ next_line = lines[next_index] if next_index < len(lines) else ""
921
+ introduces_list = bool(
922
+ re.match(r"^\s*[-*+]\s+\S", next_line)
923
+ or re.match(r"^\s*\d+\.\s+\S", next_line)
924
+ )
925
+ introduces_facts = bool(re.match(r"^\s*[^:\n]{1,55}:\s+\S", next_line))
926
+ if introduces_list or introduces_facts or text.istitle():
927
+ return 3
928
+ return None
929
+
930
+
931
+ def labeled_fact(line):
932
+ """Return a label/value pair for a short professional fact line."""
933
+ match = re.match(r"^([^:\n]{1,55}):\s+(.+)$", line.strip())
934
+ if not match:
935
+ return None
936
+ return match.group(1).strip(), match.group(2).strip()
937
+
938
+
939
+ def add_labeled_text(paragraph, label, value):
940
+ """Render a label/value pair in an existing Word paragraph."""
941
+ label_run = paragraph.add_run(f"{label}: ")
942
+ label_run.bold = True
943
+ add_markdown_text(paragraph, value)
944
+
945
+
946
+ def add_labeled_fact(doc, label, value):
947
+ """Render a fact line with a bold label and distinct Word paragraph."""
948
+ paragraph = doc.add_paragraph()
949
+ add_labeled_text(paragraph, label, value)
950
+
951
+
831
952
  def iter_inline_markdown(text):
832
953
  """Yield (text, marks) spans for common inline Markdown."""
833
954
  token_re = re.compile(
834
- r"(`[^`]+`|\*\*[^*]+\*\*|__[^_]+__|\*[^*\s][^*]*\*|_[^_\s][^_]*_|\[[^\]]+\]\([^)]+\))"
955
+ r"(`[^`]+`|\*\*[^*]+\*\*|__[^_]+__|\*[^*\s][^*]*\*|(?<!\w)_[^_\s][^_]*_(?!\w)|\[[^\]]+\]\([^)]+\))"
835
956
  )
836
957
  pos = 0
837
958
  for match in token_re.finditer(text):
@@ -934,10 +1055,81 @@ def add_horizontal_rule(paragraph):
934
1055
  borders.append(bottom)
935
1056
 
936
1057
 
1058
+ def set_cell_shading(cell, fill):
1059
+ """Apply a solid background color to a Word table cell."""
1060
+ from docx.oxml import OxmlElement
1061
+ from docx.oxml.ns import qn
1062
+
1063
+ cell_properties = cell._tc.get_or_add_tcPr()
1064
+ shading = cell_properties.find(qn("w:shd"))
1065
+ if shading is None:
1066
+ shading = OxmlElement("w:shd")
1067
+ cell_properties.append(shading)
1068
+ shading.set(qn("w:fill"), fill)
1069
+
1070
+
1071
+ def set_cell_margins(cell, top=70, start=90, bottom=70, end=90):
1072
+ """Set compact, readable table cell margins in twentieths of a point."""
1073
+ from docx.oxml import OxmlElement
1074
+ from docx.oxml.ns import qn
1075
+
1076
+ cell_properties = cell._tc.get_or_add_tcPr()
1077
+ margins = cell_properties.first_child_found_in("w:tcMar")
1078
+ if margins is None:
1079
+ margins = OxmlElement("w:tcMar")
1080
+ cell_properties.append(margins)
1081
+ for name, value in (("top", top), ("start", start), ("bottom", bottom), ("end", end)):
1082
+ node = margins.find(qn(f"w:{name}"))
1083
+ if node is None:
1084
+ node = OxmlElement(f"w:{name}")
1085
+ margins.append(node)
1086
+ node.set(qn("w:w"), str(value))
1087
+ node.set(qn("w:type"), "dxa")
1088
+
1089
+
1090
+ def repeat_table_header(row):
1091
+ """Repeat the first row of a long table on subsequent pages."""
1092
+ from docx.oxml import OxmlElement
1093
+ from docx.oxml.ns import qn
1094
+
1095
+ row_properties = row._tr.get_or_add_trPr()
1096
+ repeat = row_properties.find(qn("w:tblHeader"))
1097
+ if repeat is None:
1098
+ repeat = OxmlElement("w:tblHeader")
1099
+ row_properties.append(repeat)
1100
+ repeat.set(qn("w:val"), "true")
1101
+
1102
+
1103
+ def prevent_table_row_split(row):
1104
+ """Keep a table row together when Word paginates the document."""
1105
+ from docx.oxml import OxmlElement
1106
+ from docx.oxml.ns import qn
1107
+
1108
+ row_properties = row._tr.get_or_add_trPr()
1109
+ if row_properties.find(qn("w:cantSplit")) is None:
1110
+ row_properties.append(OxmlElement("w:cantSplit"))
1111
+
1112
+
1113
+ def add_page_number(paragraph):
1114
+ """Add a dynamic PAGE field to a footer paragraph."""
1115
+ from docx.oxml import OxmlElement
1116
+ from docx.oxml.ns import qn
1117
+
1118
+ run = paragraph.add_run()
1119
+ begin = OxmlElement("w:fldChar")
1120
+ begin.set(qn("w:fldCharType"), "begin")
1121
+ instruction = OxmlElement("w:instrText")
1122
+ instruction.set(qn("xml:space"), "preserve")
1123
+ instruction.text = " PAGE "
1124
+ end = OxmlElement("w:fldChar")
1125
+ end.set(qn("w:fldCharType"), "end")
1126
+ run._r.extend((begin, instruction, end))
1127
+
1128
+
937
1129
  def add_markdown_table(doc, rows, alignments):
938
1130
  """Render a Markdown pipe table as a native Word table."""
939
1131
  from docx.enum.text import WD_ALIGN_PARAGRAPH
940
- from docx.shared import Pt
1132
+ from docx.shared import Pt, RGBColor
941
1133
 
942
1134
  if not rows:
943
1135
  return
@@ -947,15 +1139,22 @@ def add_markdown_table(doc, rows, alignments):
947
1139
  table = doc.add_table(rows=len(normalized), cols=width)
948
1140
  table.style = "Table Grid"
949
1141
  table.autofit = True
950
-
1142
+ repeat_table_header(table.rows[0])
951
1143
  for row_index, row in enumerate(normalized):
952
1144
  for column_index, value in enumerate(row):
953
1145
  cell = table.cell(row_index, column_index)
1146
+ set_cell_margins(cell)
1147
+ if row_index == 0:
1148
+ set_cell_shading(cell, "17365D")
1149
+ elif row_index % 2 == 0:
1150
+ set_cell_shading(cell, "EEF3F8")
954
1151
  paragraph = cell.paragraphs[0]
1152
+ paragraph.paragraph_format.space_after = Pt(0)
955
1153
  add_markdown_text(paragraph, value)
956
1154
  if row_index == 0:
957
1155
  for run in paragraph.runs:
958
1156
  run.bold = True
1157
+ run.font.color.rgb = RGBColor(255, 255, 255)
959
1158
  alignment = alignments[column_index] if column_index < len(alignments) else "left"
960
1159
  if alignment == "right":
961
1160
  paragraph.alignment = WD_ALIGN_PARAGRAPH.RIGHT
@@ -968,16 +1167,70 @@ def add_markdown_table(doc, rows, alignments):
968
1167
 
969
1168
 
970
1169
  def write_docx(path, title, content_markdown, product_name):
971
- """Write a simple, polished DOCX file locally."""
1170
+ """Write a professionally styled DOCX file locally."""
972
1171
  try:
973
1172
  from docx import Document
974
- from docx.shared import Inches, Pt
1173
+ from docx.enum.text import WD_ALIGN_PARAGRAPH
1174
+ from docx.shared import Inches, Pt, RGBColor
975
1175
  except Exception as exc:
976
1176
  raise RuntimeError(
977
1177
  "python-docx is not installed. Re-run the official installer or install python-docx in the MCP runtime environment."
978
1178
  ) from exc
979
1179
 
980
1180
  doc = Document()
1181
+ doc.core_properties.title = title or f"{product_name} Work Product"
1182
+ doc.core_properties.author = product_name
1183
+
1184
+ section = doc.sections[0]
1185
+ section.top_margin = Inches(0.55)
1186
+ section.bottom_margin = Inches(0.55)
1187
+ section.left_margin = Inches(0.7)
1188
+ section.right_margin = Inches(0.7)
1189
+ section.header_distance = Inches(0.3)
1190
+ section.footer_distance = Inches(0.3)
1191
+
1192
+ styles = doc.styles
1193
+ normal = styles["Normal"]
1194
+ normal.font.name = "Aptos"
1195
+ normal.font.size = Pt(9.3)
1196
+ normal.font.color.rgb = RGBColor(31, 41, 55)
1197
+ normal.paragraph_format.space_after = Pt(4)
1198
+ normal.paragraph_format.line_spacing = 1.05
1199
+
1200
+ title_style = styles["Title"]
1201
+ title_style.font.name = "Aptos Display"
1202
+ title_style.font.size = Pt(19)
1203
+ title_style.font.bold = True
1204
+ title_style.font.color.rgb = RGBColor(23, 54, 93)
1205
+ title_style.paragraph_format.space_after = Pt(5)
1206
+
1207
+ heading_sizes = {1: 14, 2: 11.5, 3: 10.5, 4: 9.5}
1208
+ for level, size in heading_sizes.items():
1209
+ style = styles[f"Heading {level}"]
1210
+ style.font.name = "Aptos Display"
1211
+ style.font.size = Pt(size)
1212
+ style.font.bold = True
1213
+ style.font.color.rgb = RGBColor(23, 54, 93)
1214
+ style.paragraph_format.space_before = Pt(8 if level <= 2 else 5)
1215
+ style.paragraph_format.space_after = Pt(3)
1216
+ style.paragraph_format.keep_with_next = True
1217
+
1218
+ header = section.header.paragraphs[0]
1219
+ header.text = f"{product_name} | Professional Work Product"
1220
+ header.alignment = WD_ALIGN_PARAGRAPH.RIGHT
1221
+ for run in header.runs:
1222
+ run.font.name = "Aptos"
1223
+ run.font.size = Pt(7.5)
1224
+ run.font.color.rgb = RGBColor(100, 116, 139)
1225
+
1226
+ footer = section.footer.paragraphs[0]
1227
+ footer.alignment = WD_ALIGN_PARAGRAPH.CENTER
1228
+ footer_run = footer.add_run(f"Generated locally by {product_name} | Private local output | Page ")
1229
+ footer_run.font.name = "Aptos"
1230
+ footer_run.font.size = Pt(7.5)
1231
+ footer_run.font.color.rgb = RGBColor(100, 116, 139)
1232
+ add_page_number(footer)
1233
+
981
1234
  if title:
982
1235
  doc.add_heading(title, level=0)
983
1236
 
@@ -987,16 +1240,24 @@ def write_docx(path, title, content_markdown, product_name):
987
1240
  def flush_paragraph():
988
1241
  if not paragraph_buffer:
989
1242
  return
990
- text = " ".join(part.strip() for part in paragraph_buffer if part.strip())
1243
+ text_parts = []
1244
+ for part in paragraph_buffer:
1245
+ clean = part.strip()
1246
+ if not clean:
1247
+ continue
1248
+ text_parts.append(clean)
1249
+ text_parts.append("\n" if part.endswith(" ") else " ")
1250
+ text = "".join(text_parts).rstrip()
991
1251
  paragraph_buffer.clear()
992
1252
  if text:
993
1253
  add_markdown_text(doc.add_paragraph(), text)
994
1254
 
995
1255
  in_code_block = False
1256
+ keep_checklist_together = False
996
1257
  index = 0
997
1258
  while index < len(lines):
998
1259
  raw_line = lines[index]
999
- line = raw_line.rstrip()
1260
+ line = raw_line.rstrip("\r\n")
1000
1261
  stripped = line.strip()
1001
1262
  if stripped.startswith("```"):
1002
1263
  flush_paragraph()
@@ -1016,10 +1277,34 @@ def write_docx(path, title, content_markdown, product_name):
1016
1277
  continue
1017
1278
 
1018
1279
  heading = re.match(r"^(#{1,4})\s+(.+)$", stripped)
1280
+ standalone_bold_heading = re.fullmatch(r"\*\*([^*\n]{1,120})\*\*", stripped)
1281
+ inferred_heading = inferred_plain_heading_level(lines, index)
1019
1282
  bullet = re.match(r"^(\s*)[-*+]\s+(.+)$", line)
1020
1283
  numbered = re.match(r"^(\s*)\d+\.\s+(.+)$", line)
1021
1284
  quote = re.match(r"^>\s?(.+)$", stripped)
1285
+ fact = (
1286
+ labeled_fact(stripped)
1287
+ if not paragraph_buffer
1288
+ and not line.endswith(" ")
1289
+ and not bullet
1290
+ and not numbered
1291
+ and not quote
1292
+ else None
1293
+ )
1022
1294
 
1295
+ if re.fullmatch(r"<!--\s*[^\n]*?\s*-->", stripped):
1296
+ # Public-copy delimiters and other machine-readable Markdown
1297
+ # comments are useful in the source but must not appear in the
1298
+ # downloadable customer document.
1299
+ flush_paragraph()
1300
+ index += 1
1301
+ continue
1302
+ if stripped == ">":
1303
+ # A blank line inside a Markdown blockquote is structural spacing,
1304
+ # not a literal greater-than character in the Word document.
1305
+ flush_paragraph()
1306
+ index += 1
1307
+ continue
1023
1308
  if is_markdown_table_start(lines, index):
1024
1309
  flush_paragraph()
1025
1310
  header = markdown_table_row(lines[index])
@@ -1036,7 +1321,28 @@ def write_docx(path, title, content_markdown, product_name):
1036
1321
  continue
1037
1322
  elif heading:
1038
1323
  flush_paragraph()
1039
- doc.add_heading(heading.group(2).strip(), level=min(len(heading.group(1)), 4))
1324
+ heading_text = heading.group(2).strip()
1325
+ if not (
1326
+ title
1327
+ and index == 0
1328
+ and re.sub(r"\W+", "", heading_text).casefold()
1329
+ == re.sub(r"\W+", "", title).casefold()
1330
+ ):
1331
+ doc.add_heading(heading_text, level=min(len(heading.group(1)), 4))
1332
+ keep_checklist_together = "checklist" in heading_text.casefold()
1333
+ index += 1
1334
+ elif standalone_bold_heading:
1335
+ flush_paragraph()
1336
+ doc.add_heading(standalone_bold_heading.group(1).strip(), level=3)
1337
+ index += 1
1338
+ elif inferred_heading:
1339
+ flush_paragraph()
1340
+ doc.add_heading(stripped, level=inferred_heading)
1341
+ keep_checklist_together = "checklist" in stripped.casefold()
1342
+ index += 1
1343
+ elif fact:
1344
+ flush_paragraph()
1345
+ add_labeled_fact(doc, fact[0], fact[1])
1040
1346
  index += 1
1041
1347
  elif bullet:
1042
1348
  flush_paragraph()
@@ -1047,7 +1353,19 @@ def write_docx(path, title, content_markdown, product_name):
1047
1353
  checkbox = re.match(r"^\[([ xX])\]\s+(.+)$", text)
1048
1354
  if checkbox:
1049
1355
  text = f"[{checkbox.group(1).lower()}] {checkbox.group(2).strip()}"
1050
- add_markdown_text(para, text)
1356
+ bullet_fact = labeled_fact(text)
1357
+ if bullet_fact:
1358
+ add_labeled_text(para, bullet_fact[0], bullet_fact[1])
1359
+ else:
1360
+ add_markdown_text(para, text)
1361
+ next_is_bullet = bool(
1362
+ index + 1 < len(lines)
1363
+ and re.match(r"^\s*[-*+]\s+\S", lines[index + 1])
1364
+ )
1365
+ if keep_checklist_together and next_is_bullet:
1366
+ para.paragraph_format.keep_with_next = True
1367
+ elif keep_checklist_together:
1368
+ keep_checklist_together = False
1051
1369
  index += 1
1052
1370
  elif numbered:
1053
1371
  flush_paragraph()
@@ -1070,17 +1388,11 @@ def write_docx(path, title, content_markdown, product_name):
1070
1388
  add_horizontal_rule(doc.add_paragraph())
1071
1389
  index += 1
1072
1390
  else:
1073
- paragraph_buffer.append(stripped)
1391
+ paragraph_buffer.append(line)
1074
1392
  index += 1
1075
1393
 
1076
1394
  flush_paragraph()
1077
1395
 
1078
- doc.add_paragraph()
1079
- footer = doc.add_paragraph()
1080
- run = footer.add_run(f"Generated locally by {product_name}. Content was not sent to TasksAI servers.")
1081
- run.italic = True
1082
- run.font.size = Pt(8)
1083
-
1084
1396
  doc.save(path)
1085
1397
 
1086
1398
 
@@ -1145,7 +1457,7 @@ def build_tools(prefix, product_name, occupation):
1145
1457
  domain_adjective = normalize_domain_adjective(occ_key)
1146
1458
  examples = _examples.get(domain_adjective, _examples.get(product_name.replace('TasksAI','').lower().strip(), f"e.g. a {domain_adjective} task"))
1147
1459
 
1148
- return [
1460
+ tools = [
1149
1461
  Tool(
1150
1462
  name=f"{prefix}_search",
1151
1463
  description=(
@@ -1250,6 +1562,46 @@ def build_tools(prefix, product_name, occupation):
1250
1562
  ),
1251
1563
  ]
1252
1564
 
1565
+ if prefix in {"realtortasksai", "farmertasksai", "teachertasksai"}:
1566
+ tools.insert(
1567
+ 2,
1568
+ Tool(
1569
+ name=f"{prefix}_jurisdiction_sources",
1570
+ description=(
1571
+ f"Retrieve the curated current jurisdiction-and-authority source pack for a jurisdiction-sensitive {product_name} workflow. "
1572
+ "Use after the applicable state is known and before applying state, local, district, regulator, form, or policy requirements. "
1573
+ "Send only the skill ID and general authority labels; never send an address, person name, student or client facts, documents, or generated content."
1574
+ ),
1575
+ inputSchema={
1576
+ "type": "object",
1577
+ "properties": {
1578
+ "skill_id": {
1579
+ "type": "string",
1580
+ "description": "Exact workflow ID requesting a task-specific source pack.",
1581
+ },
1582
+ "state": {
1583
+ "type": "string",
1584
+ "description": "Property state name or two-letter code. Omit only to obtain a blocking missing-jurisdiction response.",
1585
+ },
1586
+ "county": {
1587
+ "type": "string",
1588
+ "description": "Optional county name only; do not include an address.",
1589
+ },
1590
+ "locality": {
1591
+ "type": "string",
1592
+ "description": "Optional city or municipality name only; do not include an address.",
1593
+ },
1594
+ "district": {
1595
+ "type": "string",
1596
+ "description": "Optional public authority or school-district name only; do not include a person, student, or record identifier.",
1597
+ },
1598
+ },
1599
+ "required": ["skill_id"],
1600
+ },
1601
+ ),
1602
+ )
1603
+ return tools
1604
+
1253
1605
 
1254
1606
  def normalize_audience_label(label):
1255
1607
  """Return a natural audience phrase without naive pluralization."""
@@ -1441,6 +1793,16 @@ async def call_tool(name, arguments):
1441
1793
  f"---\n*Credits remaining: {credits_remaining}*"
1442
1794
  ))]
1443
1795
 
1796
+ # ── Jurisdiction Sources ─────────────────────────────────────────────
1797
+ elif name == f"{prefix}_jurisdiction_sources" and prefix in {
1798
+ "realtortasksai",
1799
+ "farmertasksai",
1800
+ "teachertasksai",
1801
+ }:
1802
+ path = jurisdiction_sources_path(arguments or {})
1803
+ result = await api_get(path)
1804
+ return [TextContent(type="text", text=format_jurisdiction_source_pack(result))]
1805
+
1444
1806
  # ── Save Document ───────────────────────────────────────────────────
1445
1807
  elif name == f"{prefix}_save_document":
1446
1808
  output_path, fmt = save_document(arguments or {}, product_name)
@@ -57,6 +57,12 @@ DEFAULT_CONTEXT_WORDS = {
57
57
  DEFAULT_THRESHOLDS = {"high": 90, "medium": 45}
58
58
  DEFAULT_RESULT_COUNT = 3
59
59
  MAXIMUM_RESULT_COUNT = 10
60
+ TRIGGER_EVIDENCE_RANK = {
61
+ "trigger_exact": 4,
62
+ "trigger_complete": 3,
63
+ "trigger_query_complete": 2,
64
+ "trigger_partial": 1,
65
+ }
60
66
 
61
67
 
62
68
  def normalize_text(value: object) -> str:
@@ -386,6 +392,7 @@ def _score_skill(
386
392
  "score": score,
387
393
  "confidence": confidence,
388
394
  "evidence": evidence,
395
+ "_trigger_rank": TRIGGER_EVIDENCE_RANK.get(trigger_evidence, 0),
389
396
  "_trigger_specificity": trigger_specificity,
390
397
  "_matched_tokens": matched_tokens,
391
398
  "_description_normalized": normalize_text(skill.get("description", "")),
@@ -395,6 +402,7 @@ def _score_skill(
395
402
 
396
403
  def _sort_key(result: Mapping[str, object]) -> tuple:
397
404
  return (
405
+ -result["_trigger_rank"],
398
406
  -result["score"],
399
407
  -result["_trigger_specificity"],
400
408
  -len(result["_matched_tokens"]),