@tasksai/install 0.1.22 → 0.1.24
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 +1 -1
- package/runtime/requirements.txt +1 -1
- package/runtime/server.py +363 -16
- package/runtime/skill_matcher.py +8 -0
- package/src/index.js +15 -6
package/package.json
CHANGED
package/runtime/requirements.txt
CHANGED
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.
|
|
128
|
+
SERVER_VERSION = "2.4.0"
|
|
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,6 +902,53 @@ 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(
|
|
@@ -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,25 @@ 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
|
|
1142
|
+
repeat_table_header(table.rows[0])
|
|
1143
|
+
for table_row in table.rows:
|
|
1144
|
+
prevent_table_row_split(table_row)
|
|
950
1145
|
|
|
951
1146
|
for row_index, row in enumerate(normalized):
|
|
952
1147
|
for column_index, value in enumerate(row):
|
|
953
1148
|
cell = table.cell(row_index, column_index)
|
|
1149
|
+
set_cell_margins(cell)
|
|
1150
|
+
if row_index == 0:
|
|
1151
|
+
set_cell_shading(cell, "17365D")
|
|
1152
|
+
elif row_index % 2 == 0:
|
|
1153
|
+
set_cell_shading(cell, "EEF3F8")
|
|
954
1154
|
paragraph = cell.paragraphs[0]
|
|
1155
|
+
paragraph.paragraph_format.space_after = Pt(0)
|
|
955
1156
|
add_markdown_text(paragraph, value)
|
|
956
1157
|
if row_index == 0:
|
|
957
1158
|
for run in paragraph.runs:
|
|
958
1159
|
run.bold = True
|
|
1160
|
+
run.font.color.rgb = RGBColor(255, 255, 255)
|
|
959
1161
|
alignment = alignments[column_index] if column_index < len(alignments) else "left"
|
|
960
1162
|
if alignment == "right":
|
|
961
1163
|
paragraph.alignment = WD_ALIGN_PARAGRAPH.RIGHT
|
|
@@ -968,16 +1170,70 @@ def add_markdown_table(doc, rows, alignments):
|
|
|
968
1170
|
|
|
969
1171
|
|
|
970
1172
|
def write_docx(path, title, content_markdown, product_name):
|
|
971
|
-
"""Write a
|
|
1173
|
+
"""Write a professionally styled DOCX file locally."""
|
|
972
1174
|
try:
|
|
973
1175
|
from docx import Document
|
|
974
|
-
from docx.
|
|
1176
|
+
from docx.enum.text import WD_ALIGN_PARAGRAPH
|
|
1177
|
+
from docx.shared import Inches, Pt, RGBColor
|
|
975
1178
|
except Exception as exc:
|
|
976
1179
|
raise RuntimeError(
|
|
977
1180
|
"python-docx is not installed. Re-run the official installer or install python-docx in the MCP runtime environment."
|
|
978
1181
|
) from exc
|
|
979
1182
|
|
|
980
1183
|
doc = Document()
|
|
1184
|
+
doc.core_properties.title = title or f"{product_name} Work Product"
|
|
1185
|
+
doc.core_properties.author = product_name
|
|
1186
|
+
|
|
1187
|
+
section = doc.sections[0]
|
|
1188
|
+
section.top_margin = Inches(0.55)
|
|
1189
|
+
section.bottom_margin = Inches(0.55)
|
|
1190
|
+
section.left_margin = Inches(0.7)
|
|
1191
|
+
section.right_margin = Inches(0.7)
|
|
1192
|
+
section.header_distance = Inches(0.3)
|
|
1193
|
+
section.footer_distance = Inches(0.3)
|
|
1194
|
+
|
|
1195
|
+
styles = doc.styles
|
|
1196
|
+
normal = styles["Normal"]
|
|
1197
|
+
normal.font.name = "Aptos"
|
|
1198
|
+
normal.font.size = Pt(9.3)
|
|
1199
|
+
normal.font.color.rgb = RGBColor(31, 41, 55)
|
|
1200
|
+
normal.paragraph_format.space_after = Pt(4)
|
|
1201
|
+
normal.paragraph_format.line_spacing = 1.05
|
|
1202
|
+
|
|
1203
|
+
title_style = styles["Title"]
|
|
1204
|
+
title_style.font.name = "Aptos Display"
|
|
1205
|
+
title_style.font.size = Pt(19)
|
|
1206
|
+
title_style.font.bold = True
|
|
1207
|
+
title_style.font.color.rgb = RGBColor(23, 54, 93)
|
|
1208
|
+
title_style.paragraph_format.space_after = Pt(5)
|
|
1209
|
+
|
|
1210
|
+
heading_sizes = {1: 14, 2: 11.5, 3: 10.5, 4: 9.5}
|
|
1211
|
+
for level, size in heading_sizes.items():
|
|
1212
|
+
style = styles[f"Heading {level}"]
|
|
1213
|
+
style.font.name = "Aptos Display"
|
|
1214
|
+
style.font.size = Pt(size)
|
|
1215
|
+
style.font.bold = True
|
|
1216
|
+
style.font.color.rgb = RGBColor(23, 54, 93)
|
|
1217
|
+
style.paragraph_format.space_before = Pt(8 if level <= 2 else 5)
|
|
1218
|
+
style.paragraph_format.space_after = Pt(3)
|
|
1219
|
+
style.paragraph_format.keep_with_next = True
|
|
1220
|
+
|
|
1221
|
+
header = section.header.paragraphs[0]
|
|
1222
|
+
header.text = f"{product_name} | Professional Work Product"
|
|
1223
|
+
header.alignment = WD_ALIGN_PARAGRAPH.RIGHT
|
|
1224
|
+
for run in header.runs:
|
|
1225
|
+
run.font.name = "Aptos"
|
|
1226
|
+
run.font.size = Pt(7.5)
|
|
1227
|
+
run.font.color.rgb = RGBColor(100, 116, 139)
|
|
1228
|
+
|
|
1229
|
+
footer = section.footer.paragraphs[0]
|
|
1230
|
+
footer.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
|
1231
|
+
footer_run = footer.add_run(f"Generated locally by {product_name} | Private local output | Page ")
|
|
1232
|
+
footer_run.font.name = "Aptos"
|
|
1233
|
+
footer_run.font.size = Pt(7.5)
|
|
1234
|
+
footer_run.font.color.rgb = RGBColor(100, 116, 139)
|
|
1235
|
+
add_page_number(footer)
|
|
1236
|
+
|
|
981
1237
|
if title:
|
|
982
1238
|
doc.add_heading(title, level=0)
|
|
983
1239
|
|
|
@@ -987,16 +1243,24 @@ def write_docx(path, title, content_markdown, product_name):
|
|
|
987
1243
|
def flush_paragraph():
|
|
988
1244
|
if not paragraph_buffer:
|
|
989
1245
|
return
|
|
990
|
-
|
|
1246
|
+
text_parts = []
|
|
1247
|
+
for part in paragraph_buffer:
|
|
1248
|
+
clean = part.strip()
|
|
1249
|
+
if not clean:
|
|
1250
|
+
continue
|
|
1251
|
+
text_parts.append(clean)
|
|
1252
|
+
text_parts.append("\n" if part.endswith(" ") else " ")
|
|
1253
|
+
text = "".join(text_parts).rstrip()
|
|
991
1254
|
paragraph_buffer.clear()
|
|
992
1255
|
if text:
|
|
993
1256
|
add_markdown_text(doc.add_paragraph(), text)
|
|
994
1257
|
|
|
995
1258
|
in_code_block = False
|
|
1259
|
+
keep_checklist_together = False
|
|
996
1260
|
index = 0
|
|
997
1261
|
while index < len(lines):
|
|
998
1262
|
raw_line = lines[index]
|
|
999
|
-
line = raw_line.rstrip()
|
|
1263
|
+
line = raw_line.rstrip("\r\n")
|
|
1000
1264
|
stripped = line.strip()
|
|
1001
1265
|
if stripped.startswith("```"):
|
|
1002
1266
|
flush_paragraph()
|
|
@@ -1016,9 +1280,19 @@ def write_docx(path, title, content_markdown, product_name):
|
|
|
1016
1280
|
continue
|
|
1017
1281
|
|
|
1018
1282
|
heading = re.match(r"^(#{1,4})\s+(.+)$", stripped)
|
|
1283
|
+
inferred_heading = inferred_plain_heading_level(lines, index)
|
|
1019
1284
|
bullet = re.match(r"^(\s*)[-*+]\s+(.+)$", line)
|
|
1020
1285
|
numbered = re.match(r"^(\s*)\d+\.\s+(.+)$", line)
|
|
1021
1286
|
quote = re.match(r"^>\s?(.+)$", stripped)
|
|
1287
|
+
fact = (
|
|
1288
|
+
labeled_fact(stripped)
|
|
1289
|
+
if not paragraph_buffer
|
|
1290
|
+
and not line.endswith(" ")
|
|
1291
|
+
and not bullet
|
|
1292
|
+
and not numbered
|
|
1293
|
+
and not quote
|
|
1294
|
+
else None
|
|
1295
|
+
)
|
|
1022
1296
|
|
|
1023
1297
|
if is_markdown_table_start(lines, index):
|
|
1024
1298
|
flush_paragraph()
|
|
@@ -1036,7 +1310,24 @@ def write_docx(path, title, content_markdown, product_name):
|
|
|
1036
1310
|
continue
|
|
1037
1311
|
elif heading:
|
|
1038
1312
|
flush_paragraph()
|
|
1039
|
-
|
|
1313
|
+
heading_text = heading.group(2).strip()
|
|
1314
|
+
if not (
|
|
1315
|
+
title
|
|
1316
|
+
and index == 0
|
|
1317
|
+
and re.sub(r"\W+", "", heading_text).casefold()
|
|
1318
|
+
== re.sub(r"\W+", "", title).casefold()
|
|
1319
|
+
):
|
|
1320
|
+
doc.add_heading(heading_text, level=min(len(heading.group(1)), 4))
|
|
1321
|
+
keep_checklist_together = "checklist" in heading_text.casefold()
|
|
1322
|
+
index += 1
|
|
1323
|
+
elif inferred_heading:
|
|
1324
|
+
flush_paragraph()
|
|
1325
|
+
doc.add_heading(stripped, level=inferred_heading)
|
|
1326
|
+
keep_checklist_together = "checklist" in stripped.casefold()
|
|
1327
|
+
index += 1
|
|
1328
|
+
elif fact:
|
|
1329
|
+
flush_paragraph()
|
|
1330
|
+
add_labeled_fact(doc, fact[0], fact[1])
|
|
1040
1331
|
index += 1
|
|
1041
1332
|
elif bullet:
|
|
1042
1333
|
flush_paragraph()
|
|
@@ -1047,7 +1338,19 @@ def write_docx(path, title, content_markdown, product_name):
|
|
|
1047
1338
|
checkbox = re.match(r"^\[([ xX])\]\s+(.+)$", text)
|
|
1048
1339
|
if checkbox:
|
|
1049
1340
|
text = f"[{checkbox.group(1).lower()}] {checkbox.group(2).strip()}"
|
|
1050
|
-
|
|
1341
|
+
bullet_fact = labeled_fact(text)
|
|
1342
|
+
if bullet_fact:
|
|
1343
|
+
add_labeled_text(para, bullet_fact[0], bullet_fact[1])
|
|
1344
|
+
else:
|
|
1345
|
+
add_markdown_text(para, text)
|
|
1346
|
+
next_is_bullet = bool(
|
|
1347
|
+
index + 1 < len(lines)
|
|
1348
|
+
and re.match(r"^\s*[-*+]\s+\S", lines[index + 1])
|
|
1349
|
+
)
|
|
1350
|
+
if keep_checklist_together and next_is_bullet:
|
|
1351
|
+
para.paragraph_format.keep_with_next = True
|
|
1352
|
+
elif keep_checklist_together:
|
|
1353
|
+
keep_checklist_together = False
|
|
1051
1354
|
index += 1
|
|
1052
1355
|
elif numbered:
|
|
1053
1356
|
flush_paragraph()
|
|
@@ -1070,17 +1373,11 @@ def write_docx(path, title, content_markdown, product_name):
|
|
|
1070
1373
|
add_horizontal_rule(doc.add_paragraph())
|
|
1071
1374
|
index += 1
|
|
1072
1375
|
else:
|
|
1073
|
-
paragraph_buffer.append(
|
|
1376
|
+
paragraph_buffer.append(line)
|
|
1074
1377
|
index += 1
|
|
1075
1378
|
|
|
1076
1379
|
flush_paragraph()
|
|
1077
1380
|
|
|
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
1381
|
doc.save(path)
|
|
1085
1382
|
|
|
1086
1383
|
|
|
@@ -1145,7 +1442,7 @@ def build_tools(prefix, product_name, occupation):
|
|
|
1145
1442
|
domain_adjective = normalize_domain_adjective(occ_key)
|
|
1146
1443
|
examples = _examples.get(domain_adjective, _examples.get(product_name.replace('TasksAI','').lower().strip(), f"e.g. a {domain_adjective} task"))
|
|
1147
1444
|
|
|
1148
|
-
|
|
1445
|
+
tools = [
|
|
1149
1446
|
Tool(
|
|
1150
1447
|
name=f"{prefix}_search",
|
|
1151
1448
|
description=(
|
|
@@ -1250,6 +1547,46 @@ def build_tools(prefix, product_name, occupation):
|
|
|
1250
1547
|
),
|
|
1251
1548
|
]
|
|
1252
1549
|
|
|
1550
|
+
if prefix in {"realtortasksai", "farmertasksai", "teachertasksai"}:
|
|
1551
|
+
tools.insert(
|
|
1552
|
+
2,
|
|
1553
|
+
Tool(
|
|
1554
|
+
name=f"{prefix}_jurisdiction_sources",
|
|
1555
|
+
description=(
|
|
1556
|
+
f"Retrieve the curated current jurisdiction-and-authority source pack for a jurisdiction-sensitive {product_name} workflow. "
|
|
1557
|
+
"Use after the applicable state is known and before applying state, local, district, regulator, form, or policy requirements. "
|
|
1558
|
+
"Send only the skill ID and general authority labels; never send an address, person name, student or client facts, documents, or generated content."
|
|
1559
|
+
),
|
|
1560
|
+
inputSchema={
|
|
1561
|
+
"type": "object",
|
|
1562
|
+
"properties": {
|
|
1563
|
+
"skill_id": {
|
|
1564
|
+
"type": "string",
|
|
1565
|
+
"description": "Exact workflow ID requesting a task-specific source pack.",
|
|
1566
|
+
},
|
|
1567
|
+
"state": {
|
|
1568
|
+
"type": "string",
|
|
1569
|
+
"description": "Property state name or two-letter code. Omit only to obtain a blocking missing-jurisdiction response.",
|
|
1570
|
+
},
|
|
1571
|
+
"county": {
|
|
1572
|
+
"type": "string",
|
|
1573
|
+
"description": "Optional county name only; do not include an address.",
|
|
1574
|
+
},
|
|
1575
|
+
"locality": {
|
|
1576
|
+
"type": "string",
|
|
1577
|
+
"description": "Optional city or municipality name only; do not include an address.",
|
|
1578
|
+
},
|
|
1579
|
+
"district": {
|
|
1580
|
+
"type": "string",
|
|
1581
|
+
"description": "Optional public authority or school-district name only; do not include a person, student, or record identifier.",
|
|
1582
|
+
},
|
|
1583
|
+
},
|
|
1584
|
+
"required": ["skill_id"],
|
|
1585
|
+
},
|
|
1586
|
+
),
|
|
1587
|
+
)
|
|
1588
|
+
return tools
|
|
1589
|
+
|
|
1253
1590
|
|
|
1254
1591
|
def normalize_audience_label(label):
|
|
1255
1592
|
"""Return a natural audience phrase without naive pluralization."""
|
|
@@ -1441,6 +1778,16 @@ async def call_tool(name, arguments):
|
|
|
1441
1778
|
f"---\n*Credits remaining: {credits_remaining}*"
|
|
1442
1779
|
))]
|
|
1443
1780
|
|
|
1781
|
+
# ── Jurisdiction Sources ─────────────────────────────────────────────
|
|
1782
|
+
elif name == f"{prefix}_jurisdiction_sources" and prefix in {
|
|
1783
|
+
"realtortasksai",
|
|
1784
|
+
"farmertasksai",
|
|
1785
|
+
"teachertasksai",
|
|
1786
|
+
}:
|
|
1787
|
+
path = jurisdiction_sources_path(arguments or {})
|
|
1788
|
+
result = await api_get(path)
|
|
1789
|
+
return [TextContent(type="text", text=format_jurisdiction_source_pack(result))]
|
|
1790
|
+
|
|
1444
1791
|
# ── Save Document ───────────────────────────────────────────────────
|
|
1445
1792
|
elif name == f"{prefix}_save_document":
|
|
1446
1793
|
output_path, fmt = save_document(arguments or {}, product_name)
|
package/runtime/skill_matcher.py
CHANGED
|
@@ -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"]),
|
package/src/index.js
CHANGED
|
@@ -47,32 +47,40 @@ const LEGACY_MCP_SERVER_IDS = {
|
|
|
47
47
|
teacher: ["teachertasksai"]
|
|
48
48
|
};
|
|
49
49
|
|
|
50
|
+
// Test/development override used to keep synthetic installs away from a user's
|
|
51
|
+
// active client profile. It is intentionally environment-only and is not part
|
|
52
|
+
// of the normal customer installation flow.
|
|
53
|
+
function isolatedClientConfigPath(fileName, defaultPath) {
|
|
54
|
+
const root = process.env.TASKSAI_CLIENT_CONFIG_DIR;
|
|
55
|
+
return root ? path.join(path.resolve(root), fileName) : defaultPath;
|
|
56
|
+
}
|
|
57
|
+
|
|
50
58
|
const CLIENTS = {
|
|
51
59
|
"claude-desktop": {
|
|
52
60
|
id: "claude-desktop",
|
|
53
61
|
displayName: "Claude Desktop",
|
|
54
62
|
configPath() {
|
|
55
63
|
if (process.platform === "darwin") {
|
|
56
|
-
return path.join(os.homedir(), "Library", "Application Support", "Claude", "claude_desktop_config.json");
|
|
64
|
+
return isolatedClientConfigPath("claude_desktop_config.json", path.join(os.homedir(), "Library", "Application Support", "Claude", "claude_desktop_config.json"));
|
|
57
65
|
}
|
|
58
66
|
if (process.platform === "win32") {
|
|
59
|
-
return path.join(process.env.APPDATA || path.join(os.homedir(), "AppData", "Roaming"), "Claude", "claude_desktop_config.json");
|
|
67
|
+
return isolatedClientConfigPath("claude_desktop_config.json", path.join(process.env.APPDATA || path.join(os.homedir(), "AppData", "Roaming"), "Claude", "claude_desktop_config.json"));
|
|
60
68
|
}
|
|
61
|
-
return path.join(os.homedir(), ".config", "Claude", "claude_desktop_config.json");
|
|
69
|
+
return isolatedClientConfigPath("claude_desktop_config.json", path.join(os.homedir(), ".config", "Claude", "claude_desktop_config.json"));
|
|
62
70
|
}
|
|
63
71
|
},
|
|
64
72
|
cursor: {
|
|
65
73
|
id: "cursor",
|
|
66
74
|
displayName: "Cursor",
|
|
67
75
|
configPath() {
|
|
68
|
-
return path.join(os.homedir(), ".cursor", "mcp.json");
|
|
76
|
+
return isolatedClientConfigPath("mcp.json", path.join(os.homedir(), ".cursor", "mcp.json"));
|
|
69
77
|
}
|
|
70
78
|
},
|
|
71
79
|
windsurf: {
|
|
72
80
|
id: "windsurf",
|
|
73
81
|
displayName: "Windsurf",
|
|
74
82
|
configPath() {
|
|
75
|
-
return path.join(os.homedir(), ".codeium", "windsurf", "mcp_config.json");
|
|
83
|
+
return isolatedClientConfigPath("mcp_config.json", path.join(os.homedir(), ".codeium", "windsurf", "mcp_config.json"));
|
|
76
84
|
}
|
|
77
85
|
},
|
|
78
86
|
codex: {
|
|
@@ -80,7 +88,7 @@ const CLIENTS = {
|
|
|
80
88
|
displayName: "Codex",
|
|
81
89
|
configFormat: "toml",
|
|
82
90
|
configPath() {
|
|
83
|
-
return path.join(os.homedir(), ".codex", "config.toml");
|
|
91
|
+
return isolatedClientConfigPath("config.toml", path.join(os.homedir(), ".codex", "config.toml"));
|
|
84
92
|
}
|
|
85
93
|
}
|
|
86
94
|
};
|
|
@@ -196,6 +204,7 @@ Examples:
|
|
|
196
204
|
|
|
197
205
|
Environment:
|
|
198
206
|
TASKSAI_INSTALL_DIR may be used instead of --install-dir.
|
|
207
|
+
TASKSAI_CLIENT_CONFIG_DIR may be used for an isolated test/development client profile.
|
|
199
208
|
|
|
200
209
|
Development only:
|
|
201
210
|
--allow-untrusted-development-source permits a local, non-main, or non-official
|