skilldrop-cli 0.16.6 → 0.16.7

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 (104) hide show
  1. package/README.md +2 -1
  2. package/catalogue.json +10 -1
  3. package/model-routing.json +12 -0
  4. package/package.json +1 -1
  5. package/packs/ai-engineering/skills/ai-usage-policy/evals/evals.json +30 -0
  6. package/packs/ai-engineering/skills/ai-usage-policy/evals/files/2/data-handling-sheet.md +18 -0
  7. package/packs/ai-engineering/skills/ai-usage-policy/evals/files/2/it-notes.md +31 -0
  8. package/packs/ai-engineering/skills/ai-usage-policy/evals/files/2/md-email.md +29 -0
  9. package/packs/api-builder/skills/tool-use-schema-writer/evals/evals.json +29 -0
  10. package/packs/api-builder/skills/tool-use-schema-writer/evals/files/2/book_appointment.ts +64 -0
  11. package/packs/career/loops/apply/LOOP.md +88 -0
  12. package/packs/career/loops/apply/evals/eval_queries.json +9 -0
  13. package/packs/career/loops/apply/evals/evals.json +27 -0
  14. package/packs/career/loops/apply/loop.json +46 -0
  15. package/packs/career/pack.json +57 -0
  16. package/packs/career/skills/cover-letter/SKILL.md +132 -0
  17. package/packs/career/skills/cover-letter/evals/eval_queries.json +34 -0
  18. package/packs/career/skills/cover-letter/evals/evals.json +100 -0
  19. package/packs/career/skills/cover-letter/evals/files/1/posting.md +28 -0
  20. package/packs/career/skills/cover-letter/evals/files/1/resume.md +35 -0
  21. package/packs/career/skills/cover-letter/evals/files/2/posting.md +20 -0
  22. package/packs/career/skills/cover-letter/evals/files/2/resume.md +32 -0
  23. package/packs/career/skills/cover-letter/evals/files/4/posting.md +19 -0
  24. package/packs/career/skills/cover-letter/evals/files/4/resume.md +26 -0
  25. package/packs/career/skills/cover-letter/evals/files/5/posting.md +21 -0
  26. package/packs/career/skills/cover-letter/evals/files/5/resume.md +32 -0
  27. package/packs/career/skills/cover-letter/evals/files/6/posting.md +25 -0
  28. package/packs/career/skills/cover-letter/evals/files/6/resume.md +29 -0
  29. package/packs/career/skills/cover-letter/evals/files/7/posting.md +18 -0
  30. package/packs/career/skills/cover-letter/evals/files/7/resume.md +23 -0
  31. package/packs/career/skills/cover-letter/manifest.json +37 -0
  32. package/packs/career/skills/cover-letter/scripts/claim_check.py +247 -0
  33. package/packs/career/skills/job-fit-analysis/SKILL.md +136 -0
  34. package/packs/career/skills/job-fit-analysis/evals/eval_queries.json +34 -0
  35. package/packs/career/skills/job-fit-analysis/evals/evals.json +76 -0
  36. package/packs/career/skills/job-fit-analysis/evals/files/1/posting.md +28 -0
  37. package/packs/career/skills/job-fit-analysis/evals/files/1/resume.md +35 -0
  38. package/packs/career/skills/job-fit-analysis/evals/files/2/posting.md +20 -0
  39. package/packs/career/skills/job-fit-analysis/evals/files/2/resume.md +32 -0
  40. package/packs/career/skills/job-fit-analysis/evals/files/4/posting.md +25 -0
  41. package/packs/career/skills/job-fit-analysis/evals/files/4/resume.md +29 -0
  42. package/packs/career/skills/job-fit-analysis/evals/files/5/posting.md +18 -0
  43. package/packs/career/skills/job-fit-analysis/evals/files/5/resume.md +23 -0
  44. package/packs/career/skills/job-fit-analysis/manifest.json +31 -0
  45. package/packs/career/skills/resume-tailor/SKILL.md +116 -0
  46. package/packs/career/skills/resume-tailor/evals/eval_queries.json +34 -0
  47. package/packs/career/skills/resume-tailor/evals/evals.json +105 -0
  48. package/packs/career/skills/resume-tailor/evals/files/1/posting.md +26 -0
  49. package/packs/career/skills/resume-tailor/evals/files/1/resume.md +32 -0
  50. package/packs/career/skills/resume-tailor/evals/files/2/posting.md +19 -0
  51. package/packs/career/skills/resume-tailor/evals/files/2/resume.md +26 -0
  52. package/packs/career/skills/resume-tailor/evals/files/3/posting.md +21 -0
  53. package/packs/career/skills/resume-tailor/evals/files/3/resume.md +32 -0
  54. package/packs/career/skills/resume-tailor/evals/files/4/posting.md +28 -0
  55. package/packs/career/skills/resume-tailor/evals/files/4/resume.md +35 -0
  56. package/packs/career/skills/resume-tailor/evals/files/5/posting.md +20 -0
  57. package/packs/career/skills/resume-tailor/evals/files/5/resume.md +32 -0
  58. package/packs/career/skills/resume-tailor/evals/files/6/posting.md +25 -0
  59. package/packs/career/skills/resume-tailor/evals/files/6/resume.md +29 -0
  60. package/packs/career/skills/resume-tailor/evals/files/7/posting.md +18 -0
  61. package/packs/career/skills/resume-tailor/evals/files/7/resume.md +23 -0
  62. package/packs/career/skills/resume-tailor/manifest.json +37 -0
  63. package/packs/career/skills/resume-tailor/scripts/claim_check.py +247 -0
  64. package/packs/data-analytics/skills/metric-definition/evals/evals.json +30 -0
  65. package/packs/data-analytics/skills/metric-definition/evals/files/2/tables.md +42 -0
  66. package/packs/data-analytics/skills/metric-definition/evals/files/2/thread.md +40 -0
  67. package/packs/dev-team/skills/contribution-wizard/evals/evals.json +29 -0
  68. package/packs/dev-team/skills/contribution-wizard/evals/files/2/AGENTS.md +9 -0
  69. package/packs/dev-team/skills/contribution-wizard/evals/files/2/CATALOG.md +18 -0
  70. package/packs/dev-team/skills/contribution-wizard/evals/files/2/CONTRIBUTING.md +45 -0
  71. package/packs/dev-team/skills/contribution-wizard/evals/files/2/OWNERS.md +7 -0
  72. package/packs/dev-team/skills/contribution-wizard/evals/files/2/skills/detention-claim-letter/INSTRUCTIONS.md +20 -0
  73. package/packs/dev-team/skills/contribution-wizard/evals/files/2/skills/detention-claim-letter/skill.yaml +11 -0
  74. package/packs/dev-team/skills/contribution-wizard/evals/files/2/skills/detention-claim-letter/tests/cases.yaml +25 -0
  75. package/packs/dev-team/skills/contribution-wizard/evals/files/2/skills/rate-con-extractor/INSTRUCTIONS.md +20 -0
  76. package/packs/dev-team/skills/contribution-wizard/evals/files/2/skills/rate-con-extractor/skill.yaml +8 -0
  77. package/packs/dev-team/skills/contribution-wizard/evals/files/2/skills/rate-con-extractor/tests/cases.yaml +32 -0
  78. package/packs/dev-team/skills/devils-advocate/evals/evals.json +29 -0
  79. package/packs/dev-team/skills/devils-advocate/evals/files/2/signups.py +116 -0
  80. package/packs/dev-team/skills/devils-advocate/evals/files/2/test_signups.py +69 -0
  81. package/packs/dev-team/skills/release-notes/evals/evals.json +29 -0
  82. package/packs/dev-team/skills/release-notes/evals/files/2/commits.txt +35 -0
  83. package/packs/research/skills/source-synthesis/evals/evals.json +30 -0
  84. package/packs/research/skills/source-synthesis/evals/files/2/badge-data.md +27 -0
  85. package/packs/research/skills/source-synthesis/evals/files/2/director-email.md +16 -0
  86. package/packs/research/skills/source-synthesis/evals/files/2/exit-interviews.md +17 -0
  87. package/packs/research/skills/source-synthesis/evals/files/2/practice-weekly-article.md +21 -0
  88. package/packs/research/skills/source-synthesis/evals/files/2/staff-survey-summary.md +41 -0
  89. package/packs/solution-architect/skills/db-schema-design/evals/evals.json +29 -0
  90. package/packs/solution-architect/skills/db-schema-design/evals/files/2/current_tables.sql +29 -0
  91. package/packs/solution-architect/skills/db-schema-design/evals/files/2/notes.md +53 -0
  92. package/packs/solution-architect/skills/design-doc/evals/evals.json +29 -0
  93. package/packs/solution-architect/skills/design-doc/evals/files/2/brief.md +18 -0
  94. package/packs/solution-architect/skills/design-doc/evals/files/2/eng-notes.md +30 -0
  95. package/packs/solution-architect/skills/tech-comparison-matrix/evals/evals.json +29 -0
  96. package/packs/solution-architect/skills/tech-comparison-matrix/evals/files/2/requirements.md +20 -0
  97. package/packs/solution-architect/skills/tech-comparison-matrix/evals/files/2/vendor-notes.md +29 -0
  98. package/packs/sre-oncall/skills/postmortem-generator/evals/evals.json +30 -0
  99. package/packs/sre-oncall/skills/postmortem-generator/evals/files/2/pager-export.txt +22 -0
  100. package/packs/sre-oncall/skills/postmortem-generator/evals/files/2/slack-thread.txt +28 -0
  101. package/packs/stakeholder-comms/skills/guide-builder/evals/evals.json +29 -0
  102. package/packs/stakeholder-comms/skills/guide-builder/evals/files/2/api-notes.md +44 -0
  103. package/packs/stakeholder-comms/skills/guide-builder/evals/files/2/sample-booking.json +19 -0
  104. package/profiles.json +2 -2
@@ -0,0 +1,25 @@
1
+ # Finance Analyst
2
+
3
+ **Orrell Leisure Group** · Birmingham · £36,000 – £40,000
4
+
5
+ Orrell runs 22 gyms across the Midlands. The Finance Analyst reports to the Financial Controller and produces the monthly management pack for site managers.
6
+
7
+ ## What you'll do
8
+
9
+ - Build the monthly management accounts pack: variance to budget by site, with commentary
10
+ - Own the month-end close for payroll and utilities accruals
11
+ - Maintain the site-level budget model and reforecast each quarter
12
+ - Build dashboards in Power BI for the operations team
13
+
14
+ ## What we need
15
+
16
+ - Part-qualified ACCA or CIMA, or AAT Level 4 complete
17
+ - At least 2 years in a management accounts or analyst role
18
+ - Advanced Excel (pivot tables, lookups, modelling)
19
+ - Experience preparing variance analysis and budget commentary
20
+ - Experience with Power BI or a similar reporting tool
21
+
22
+ ## Nice to have
23
+
24
+ - Experience in a multi-site business
25
+ - Experience with Sage or NetSuite
@@ -0,0 +1,29 @@
1
+ # Grace Mbeki
2
+
3
+ Birmingham · grace.mbeki@example.com · 07700 900455
4
+
5
+ ## Work
6
+
7
+ **Accounts Payable Clerk** — Halden Building Supplies, Birmingham
8
+ February 2021 – present
9
+
10
+ - Process around 400 supplier invoices a month in Sage 200, matching each to its purchase order and delivery note.
11
+ - Reconcile 60 supplier statements each month and chase the differences.
12
+ - Prepare the fortnightly payment run for the Finance Manager to approve.
13
+ - Built an Excel workbook (pivot tables and lookups) that lists invoices on hold by reason; the purchasing team now uses it every week.
14
+ - Support month-end by posting accruals for goods received and not yet invoiced.
15
+
16
+ **Finance Administrator** — Corrin Dental Group, Solihull
17
+ September 2018 – January 2021
18
+
19
+ - Raised sales invoices, banked receipts and kept the petty cash for four practices.
20
+
21
+ ## Qualifications
22
+
23
+ - AAT Level 3 Diploma in Accounting, 2020
24
+ - Studying AAT Level 4; two of five units passed
25
+ - A levels: Mathematics (B), Economics (C), 2018
26
+
27
+ ## Tools
28
+
29
+ Sage 200, Excel, Microsoft Teams
@@ -0,0 +1,18 @@
1
+ Head Chef
2
+ Calder House Hotel, Peebles - 62 bedrooms, restaurant and events
3
+
4
+ You will lead the kitchen for our 120-cover restaurant and for weddings and events of up to 180 guests.
5
+
6
+ Requirements
7
+ - Level 3 Food Safety (Supervising Food Safety in Catering)
8
+ - Experience as a head chef, or as a sous chef with full responsibility for a kitchen
9
+ - Experience leading a team of 12 or more
10
+ - Menu design and costing to a gross profit target of 70%
11
+ - Experience of banqueting and events
12
+ - Stock control and supplier management
13
+
14
+ Desirable
15
+ - Experience in a hotel kitchen
16
+ - Experience with Procure Wizard or a similar ordering system
17
+
18
+ Salary 48,000 pounds plus tronc. Live-in available.
@@ -0,0 +1,23 @@
1
+ LEO TANAKA
2
+ Edinburgh | leo.tanaka@example.com | 07700 900271
3
+
4
+ Sous Chef, The Larder at Queensgate (80-cover restaurant), Edinburgh
5
+ May 2022 - present
6
+ - Run the kitchen on the head chef's two days off each week.
7
+ - Lead a brigade of 6 on service.
8
+ - Write and cost the weekly specials; do the fish and vegetable orders.
9
+ - Trained 3 commis chefs, one now a chef de partie.
10
+ - Keep the allergen matrix up to date for every menu change.
11
+ - Environmental health inspection in 2025: rated 5.
12
+
13
+ Chef de Partie, Harbour Rooms Hotel, Leith
14
+ March 2019 - April 2022
15
+ - Ran the sauce section; covered banqueting for weddings in summer.
16
+
17
+ Commis Chef, Harbour Rooms Hotel, Leith
18
+ August 2017 - March 2019
19
+
20
+ TRAINING
21
+ SVQ Level 3 Professional Cookery, Edinburgh College, 2019
22
+ Level 2 Food Safety and Hygiene for Catering, renewed 2024
23
+ Allergen awareness training, 2024
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "resume-tailor",
3
+ "version": "0.1.0",
4
+ "description": "Tailor a resume or CV to one job posting without adding anything that isn't true: reorder, cut and reword what the person has already done so the posting's needs are answered first, keep every employer, title, date and number as it was, and report what the posting asks for that the resume cannot claim. A script lists every figure and name in the draft that the original doesn't contain. Works for any occupation and level. Use when the user says \"tailor my resume for this job\", \"adapt my CV to this posting\", \"rewrite my resume for this role\", or pastes a job description next to their resume.",
5
+ "entrypoint": "SKILL.md",
6
+ "deps": {
7
+ "npm": [],
8
+ "pip": []
9
+ },
10
+ "env": {
11
+ "required": [],
12
+ "optional": []
13
+ },
14
+ "related": [
15
+ "cover-letter",
16
+ "doc-critique",
17
+ "job-fit-analysis"
18
+ ],
19
+ "tags": [
20
+ "resume",
21
+ "cv",
22
+ "job-application",
23
+ "tailoring",
24
+ "career",
25
+ "job-search"
26
+ ],
27
+ "permissions": {
28
+ "network": [],
29
+ "commands": [],
30
+ "files": "named-paths",
31
+ "notes": "Reads the draft, source and posting files it is given. Writes nothing."
32
+ },
33
+ "model": {
34
+ "tier": "standard",
35
+ "rationale": "Reorders and rewords existing content against a posting, with a script listing every figure and name the original doesn't contain."
36
+ }
37
+ }
@@ -0,0 +1,247 @@
1
+ #!/usr/bin/env python3
2
+ """List what a tailored resume or cover letter says that the candidate's own material doesn't.
3
+
4
+ python3 claim_check.py DRAFT --source RESUME [--source NOTES ...] [--posting POSTING] [--json]
5
+
6
+ DRAFT is the text you are about to send. Each --source is something the candidate wrote or
7
+ told you: the original resume, notes from the conversation. --posting is the job posting.
8
+
9
+ It reports four kinds of finding, each with the line it is on:
10
+
11
+ number a figure in the draft that no source contains ("35%", "$2M", "team of 8")
12
+ name a named thing in the draft that no source contains: a tool, an employer, a
13
+ certification, a degree ("Kubernetes", "Epic", "Green Belt")
14
+ combination capitalised words that each appear in a source but never together
15
+ ("Senior Manager" when the source has "Senior Analyst" and "Office Manager")
16
+ borrowed wording the draft shares with the posting and no source uses. Fine when the
17
+ source shows the same thing in other words; a false claim when it doesn't.
18
+
19
+ A name or number that the posting contains is marked "in posting": right when the draft is
20
+ talking about the employer, wrong when it is claimed as the candidate's own.
21
+
22
+ It reads text, so it cannot tell a true claim from a false one. It tells you where to look.
23
+ Exit status: 0 nothing found, 1 findings, 2 a file could not be read. Standard library only.
24
+ """
25
+ import argparse
26
+ import json
27
+ import re
28
+ import sys
29
+
30
+ MULT = {"k": 1e3, "thousand": 1e3, "m": 1e6, "mm": 1e6, "million": 1e6,
31
+ "bn": 1e9, "b": 1e9, "billion": 1e9}
32
+ NUMBER = re.compile(r"(?<![\w.])(\d[\d,]*(?:\.\d+)?)(?:(k|mm|m|bn|b)\b|\s?(thousand|million|billion)\b)?"
33
+ r"(\s?%|\s?percent\b|\s?per cent\b)?", re.I)
34
+ WORD = re.compile(r"\.?[A-Za-z][A-Za-z0-9+#']*(?:\.[A-Za-z0-9]+)*")
35
+ LIST_MARK = re.compile(r"^\s*(?:[-*•–>]+|\d+[.)]|#+|\|)\s*")
36
+ LINK = re.compile(r"\[([^\]]*)\]\([^)]*\)")
37
+ SUFFIXES = ("ations", "ation", "ments", "ment", "ings", "ing", "ers", "ies", "ed", "er", "es", "s", "ly")
38
+
39
+ # Words that start a line or a sentence with a capital letter and name nothing.
40
+ PLAIN = set("""
41
+ a an and are as at be been both but by for from had has have he her his i if in into is it its
42
+ me my no not of on or our she so that the their them then there these they this to up us was
43
+ we were what when where which who why with you your dear sincerely regards kind best yours
44
+ faithfully hiring manager team committee recruiter sir madam re subject date enclosed
45
+ summary profile objective experience employment work history education skills tools
46
+ certifications certification qualifications training projects awards languages interests
47
+ references available request professional career key core technical additional other
48
+ volunteering volunteer contact details achievements highlights about present current
49
+ january february march april may june july august september october november december
50
+ jan feb mar apr jun jul aug sep sept oct nov dec monday tuesday wednesday thursday friday
51
+ saturday sunday full part time application applying while although though because since
52
+ during after before however also please thank thanks between over across within having most each every one two three
53
+ """.split())
54
+ DATE_LINE = re.compile(r"^\s*(?:\d{1,2}(?:st|nd|rd|th)?\s+)?[A-Z][a-z]{2,8}\.?\s+(?:\d{1,2}(?:st|nd|rd|th)?,?\s+)?\d{4}\s*$")
55
+ # The joining words allowed inside a capitalised phrase: "Head of Geography".
56
+ JOINERS = {"of", "and", "for", "the", "in", "at", "&", "de"}
57
+ STOP = PLAIN | set("""
58
+ able across after also been before being between can could did does doing each every experience
59
+ experienced including into just like more most much need needs over role roles some such than
60
+ them through under using very well will within without work worked working would years year
61
+ have with that this from your their about must should strong good great team teams including
62
+ ability skills skill knowledge new help helps support supports ensure make makes
63
+ """.split())
64
+
65
+
66
+ def read(path):
67
+ try:
68
+ with open(path, encoding="utf-8", errors="replace") as f:
69
+ return f.read()
70
+ except OSError as e:
71
+ print("claim_check: cannot read %s: %s" % (path, e.strerror), file=sys.stderr)
72
+ sys.exit(2)
73
+
74
+
75
+ def clean(line):
76
+ """A line with its markdown removed, so `**Go**` and `Go` read the same."""
77
+ line = LINK.sub(r"\1", line)
78
+ line = line.replace("’", "'").replace("‘", "'").replace("–", " - ").replace("—", " - ")
79
+ return re.sub(r"[*_`~]", "", line)
80
+
81
+
82
+ def numbers(text):
83
+ """{(value, is_percent)} for every figure in the text. "$2M" and "2 million" are one value."""
84
+ found = set()
85
+ for m in NUMBER.finditer(text):
86
+ try:
87
+ value = float(m.group(1).replace(",", ""))
88
+ except ValueError:
89
+ continue
90
+ unit = (m.group(2) or m.group(3) or "").lower()
91
+ found.add((value * MULT.get(unit, 1), bool(m.group(4))))
92
+ return found
93
+
94
+
95
+ def stem(word):
96
+ w = word.lower().strip("'.")
97
+ if w.endswith("'s"):
98
+ w = w[:-2]
99
+ for s in SUFFIXES:
100
+ if w.endswith(s) and len(w) - len(s) >= 4:
101
+ return w[: -len(s)]
102
+ return w
103
+
104
+
105
+ def key(word):
106
+ """How a name is compared: case, dots, a plural, a possessive or a contraction don't matter."""
107
+ w = word.lower().strip("'")
108
+ w = re.sub(r"'(?:s|m|d|ve|ll|re|t)$", "", w)
109
+ w = w.replace(".", "") or w
110
+ return w[:-1] if len(w) > 4 and w.endswith("s") and not w.endswith("ss") else w
111
+
112
+
113
+ def vocabulary(text):
114
+ names, stems = set(), set()
115
+ for line in text.splitlines():
116
+ for w in WORD.findall(clean(line)):
117
+ names.add(key(w))
118
+ stems.add(stem(w))
119
+ flat = " ".join(key(w) for line in text.splitlines() for w in WORD.findall(clean(line)))
120
+ return names, stems, " " + flat + " "
121
+
122
+
123
+ def looks_named(word):
124
+ """True for a word that is a name whatever its position: AWS, JavaScript, C++, k8s, .NET."""
125
+ body = word.lstrip(".")
126
+ return bool(re.search(r"[A-Z]", body[1:]) or re.search(r"[0-9+#]", body) or word.startswith("."))
127
+
128
+
129
+ def phrases(line):
130
+ """Runs of capitalised or name-shaped words in one line, as lists of words.
131
+
132
+ The first word of a line or sentence is capitalised by grammar, not because it is a name,
133
+ so it only counts when it is name-shaped itself or opens a longer capitalised run."""
134
+ text = LIST_MARK.sub("", clean(line))
135
+ out, run, start_of_sentence = [], [], True
136
+ tokens = re.findall(r"\.?[A-Za-z][A-Za-z0-9+#']*(?:\.[A-Za-z0-9]+)*|&|[.!?;:]\s|[,|·/()]|\s-\s", text + " ")
137
+ for i, tok in enumerate(tokens):
138
+ if not (tok[0].isalpha() or tok[0] in ".&") or tok.strip() in (".", "!", "?", ";", ":"):
139
+ if run:
140
+ out.append(run)
141
+ run = []
142
+ start_of_sentence = tok.strip() in (".", "!", "?")
143
+ continue
144
+ if "'" in tok and key(tok) in PLAIN: # I'm, I've, we'd
145
+ tok = key(tok)
146
+ capital = tok[0].isupper() or looks_named(tok)
147
+ if capital and not (start_of_sentence and not looks_named(tok) and tok.lower() in PLAIN):
148
+ run.append((tok, start_of_sentence))
149
+ elif run and tok.lower() in JOINERS and i + 1 < len(tokens) and tokens[i + 1][:1].isupper():
150
+ run.append((tok, False))
151
+ else:
152
+ if run:
153
+ out.append(run)
154
+ run = []
155
+ start_of_sentence = False
156
+ if run:
157
+ out.append(run)
158
+ cleaned = []
159
+ for r in out:
160
+ # A lone sentence-opening word ("Led", "Built") is grammar, not a name.
161
+ if len(r) == 1 and r[0][1] and not looks_named(r[0][0]):
162
+ continue
163
+ # A sentence-opening verb in front of a name ("Led AWS migration") is dropped from the run.
164
+ if len(r) > 1 and r[0][1] and not looks_named(r[0][0]) and r[0][0].lower() not in PLAIN:
165
+ r = r[1:] if r[0][0].lower().endswith(("ed", "ing")) else r
166
+ words = [w for w, _ in r]
167
+ while words and words[-1].lower() in JOINERS:
168
+ words.pop()
169
+ if words:
170
+ cleaned.append(words)
171
+ return cleaned
172
+
173
+
174
+ def check(draft, sources, posting):
175
+ src_names, src_stems, src_flat = vocabulary("\n".join(sources))
176
+ src_numbers = numbers("\n".join(clean(s) for s in sources))
177
+ post_names, post_stems, _ = vocabulary(posting or "")
178
+ post_numbers = numbers(clean(posting or ""))
179
+ findings, seen = [], set()
180
+
181
+ def add(kind, what, n, line, in_posting=False):
182
+ if (kind, what.lower()) in seen:
183
+ return
184
+ seen.add((kind, what.lower()))
185
+ findings.append({"kind": kind, "text": what, "line": n, "context": line.strip()[:160],
186
+ "in_posting": in_posting})
187
+
188
+ for n, raw in enumerate(draft.splitlines(), 1):
189
+ line = clean(raw)
190
+ if not line.strip() or DATE_LINE.match(LIST_MARK.sub("", line)):
191
+ continue # a letter's own date line claims nothing
192
+ body = LIST_MARK.sub("", line)
193
+ for m in NUMBER.finditer(body):
194
+ got = next(iter(numbers(m.group(0))), None)
195
+ if got and got not in src_numbers:
196
+ add("number", m.group(0).strip(), n, raw, got in post_numbers)
197
+ for words in phrases(raw):
198
+ content = [w for w in words if w.lower() not in JOINERS]
199
+ missing = [w for w in content if key(w) not in src_names and w.lower() not in PLAIN]
200
+ if missing:
201
+ add("name", " ".join(words), n, raw, all(key(w) in post_names for w in missing))
202
+ elif len(content) > 1 and all(w.lower() not in PLAIN for w in content):
203
+ flat = " ".join(key(w) for w in words)
204
+ if " " + flat + " " not in src_flat:
205
+ add("combination", " ".join(words), n, raw)
206
+ if posting:
207
+ for w in WORD.findall(body):
208
+ s = stem(w)
209
+ if (len(s) >= 4 and w.lower() not in STOP and s in post_stems and s not in src_stems
210
+ and key(w) not in src_names and not w[0].isupper() and not looks_named(w)):
211
+ add("borrowed", w.lower(), n, raw, True)
212
+ return findings
213
+
214
+
215
+ def main():
216
+ ap = argparse.ArgumentParser(description="List what a draft says that the candidate's own material doesn't.")
217
+ ap.add_argument("draft")
218
+ ap.add_argument("--source", action="append", required=True, metavar="FILE",
219
+ help="the candidate's own resume or notes; repeat for more than one")
220
+ ap.add_argument("--posting", metavar="FILE", help="the job posting")
221
+ ap.add_argument("--json", action="store_true")
222
+ a = ap.parse_args()
223
+
224
+ findings = check(read(a.draft), [read(p) for p in a.source], read(a.posting) if a.posting else None)
225
+ if a.json:
226
+ print(json.dumps({"draft": a.draft, "findings": findings}, indent=1))
227
+ return 1 if findings else 0
228
+ if not findings:
229
+ print("PASS %s: every figure and name is in the source material." % a.draft)
230
+ return 0
231
+ labels = {"number": "Figures not in the source", "name": "Names not in the source",
232
+ "combination": "Words the source has, but not together",
233
+ "borrowed": "Wording taken from the posting"}
234
+ print("REVIEW %s: %d item(s) to check against the source material." % (a.draft, len(findings)))
235
+ for kind in ("number", "name", "combination", "borrowed"):
236
+ rows = [f for f in findings if f["kind"] == kind]
237
+ if not rows:
238
+ continue
239
+ print("\n%s (%d)" % (labels[kind], len(rows)))
240
+ for f in rows:
241
+ note = " [in posting]" if f["in_posting"] and kind != "borrowed" else ""
242
+ print(" line %d: %s%s\n %s" % (f["line"], f["text"], note, f["context"]))
243
+ return 1
244
+
245
+
246
+ if __name__ == "__main__":
247
+ sys.exit(main())
@@ -14,6 +14,36 @@
14
14
  "Includes at least three runnable sanity checks, including a known-answer fixture or a reconciliation with billing",
15
15
  "Does not invent tables, columns or an owner not given in the prompt; anything assumed is tagged [assumption] and listed under open decisions"
16
16
  ]
17
+ },
18
+ {
19
+ "id": 2,
20
+ "held_out": "2026-10-05",
21
+ "prompt": "Finance and CX keep turning up to the Monday meeting with different refund rates and it's getting silly. I've pasted the Slack thread into thread.md and the table descriptions into tables.md. Can you write down one proper definition of refund rate that we can all use, with the SQL? We're on Postgres.",
22
+ "assertions": [
23
+ "It separates the share of orders refunded from the share of revenue refunded as two differently named measures, or picks one as 'refund rate' and names the other as a separate measure; it does not blend them into one formula",
24
+ "It points out that 312 refunded orders out of 7,480 is about 4.2%, not the 4.6% quoted in the thread",
25
+ "The order-based SQL counts each refunded order once (COUNT DISTINCT on order_id, EXISTS, or refunds aggregated per order before joining), so orders with several refund rows are not double-counted",
26
+ "It states one rule for which month a refund belongs to (the order's month or the refund's month) and says what happens to a February order refunded in March",
27
+ "It says a single money-based rate across both stores cannot be computed from these tables as they stand because there is no exchange-rate data, and it does not invent an exchange rate or an exchange-rate table",
28
+ "Test orders are excluded with a condition on orders.is_test, and staff orders are flagged as not identifiable from the listed columns; it does not use a staff flag column that does not exist",
29
+ "Month boundaries are set in UK time (Europe/London) against timestamps stored in UTC",
30
+ "It lists whether cancelled orders count in the denominator and who owns the metric as open decisions, without naming an owner the thread did not name"
31
+ ]
32
+ },
33
+ {
34
+ "id": 3,
35
+ "held_out": "2026-10-05",
36
+ "prompt": "need a proper definition of 'time to first response' for our support team. we use a helpdesk tool and I can export tickets to a spreadsheet. my boss wants it on the weekly report. there's 8 agents. not sure if weekends should count, we don't work them",
37
+ "assertions": [
38
+ "It delivers a written definition in the reply and does not stop at a list of questions",
39
+ "It defines the start and the end of the clock (ticket created to the first reply from a human agent) and says automatic acknowledgement emails do not count as the first response, or flags that as a decision",
40
+ "Whether weekends and out-of-hours time count is given as an open decision with a recommended default or with both versions described; it is not decided silently",
41
+ "It says which statistic goes on the report (median, mean or a percentile), not just 'average' left undefined",
42
+ "It says how tickets that have had no reply yet when the report is run are treated",
43
+ "It says which week a ticket is counted in (the week it was created or the week it was answered)",
44
+ "Any export column names it uses are marked as assumed, since no export layout was given",
45
+ "It does not state a response-time target or SLA figure as the team's own, since none was given"
46
+ ]
17
47
  }
18
48
  ]
19
49
  }
@@ -0,0 +1,42 @@
1
+ # Tallow & Thyme: tables (Postgres 15)
2
+
3
+ All timestamps are `timestamptz`, stored in UTC. The business reports on UK time.
4
+
5
+ ## orders
6
+
7
+ | Column | Type | Notes |
8
+ |---|---|---|
9
+ | order_id | bigint | primary key |
10
+ | customer_id | bigint | references customers |
11
+ | store | text | 'uk' or 'eu' |
12
+ | currency | text | 'GBP' for the uk store, 'EUR' for the eu store |
13
+ | gross_amount_minor | integer | order total in pence (GBP) or cents (EUR) |
14
+ | placed_at | timestamptz | when the order was placed |
15
+ | status | text | 'placed', 'delivered', 'cancelled' |
16
+ | is_test | boolean | true for orders made by the test harness |
17
+
18
+ ## refunds
19
+
20
+ | Column | Type | Notes |
21
+ |---|---|---|
22
+ | refund_id | bigint | primary key |
23
+ | order_id | bigint | references orders |
24
+ | amount_minor | integer | in the same currency as the order |
25
+ | reason | text | free text typed by the CX agent |
26
+ | created_at | timestamptz | when the refund was issued |
27
+
28
+ An order can have more than one refund row: a missing ingredient is refunded, then a second
29
+ problem with the same box is refunded later. About 1 in 9 refunded orders has two or more
30
+ rows.
31
+
32
+ ## customers
33
+
34
+ | Column | Type | Notes |
35
+ |---|---|---|
36
+ | customer_id | bigint | primary key |
37
+ | email | text | |
38
+ | created_at | timestamptz | |
39
+
40
+ ## Not in the database
41
+
42
+ There is no exchange-rate table. Finance converts EUR to GBP by hand in a spreadsheet.
@@ -0,0 +1,40 @@
1
+ # #ops-metrics, exported 3 March 2026
2
+
3
+ **Hattie Blum** (CX lead) 09:12
4
+ Feb refund rate is 4.6%. 312 refunded orders out of 7,480 orders.
5
+
6
+ **Rupert Osei** (Finance) 09:20
7
+ I get 3.5%. £9,870 refunded on £284,300 gross.
8
+
9
+ **Hattie Blum** 09:24
10
+ Well mine is orders, yours is money. I count an order as refunded if it shows up in the
11
+ refunds table. I go by the date the refund was issued, so that's refunds issued in Feb.
12
+
13
+ **Rupert Osei** 09:31
14
+ I go by the month the order was placed. If a Feb order gets refunded in March it goes back
15
+ against Feb, that's how the P&L works. Also mine is the UK store only. The EU store is in a
16
+ separate sheet and I convert the euros by hand at whatever the rate is that day, I haven't
17
+ done Feb yet.
18
+
19
+ **Hattie Blum** 09:33
20
+ Mine is both stores.
21
+
22
+ **Mina Castellanos** (founder) 09:47
23
+ This is why the Monday meeting goes round in circles. I want ONE refund rate for the whole
24
+ business, both stores, monthly, on UK time. And take out test orders, and staff orders too.
25
+ Staff get free boxes and we refund them through the system so they make it look worse than
26
+ it is.
27
+
28
+ **Hattie Blum** 09:52
29
+ There's an is_test flag on orders. No idea how you'd spot staff orders though, we just know
30
+ the names.
31
+
32
+ **Rupert Osei** 09:55
33
+ Do cancelled orders count as orders? I leave them in, they were placed.
34
+
35
+ **Hattie Blum** 09:58
36
+ I take them out.
37
+
38
+ **Mina Castellanos** 10:05
39
+ Someone write it down properly please. Who owns it after that, tbd, let's get the definition
40
+ sorted first.
@@ -14,6 +14,35 @@
14
14
  "Ends with next steps that place files under packs/sre-oncall/skills/sla-credit-calculator/ and require python3 validate.py to pass before opening a PR",
15
15
  "Does not invent inputs, deps, or related skills the author did not give"
16
16
  ]
17
+ },
18
+ {
19
+ "id": 2,
20
+ "held_out": "2026-10-05",
21
+ "prompt": "I want to add a skill to our team's skills repo. The repo is this folder (CONTRIBUTING.md, AGENTS.md, existing skills under skills/). The new one is carrier-invoice-audit: you give it a carrier's freight invoice and the rate confirmation for that load, and it lists every line where the carrier billed more than we agreed, with the difference. Ignore differences of $5.00 or less per line. It goes under billing. Related skills are rate-con-extractor and bol-parser. And if it finds a detention charge on the invoice it should kick off detention-claim-letter automatically at the end. Write everything I need for the pull request.",
22
+ "assertions": [
23
+ "The skill is delivered as skills/carrier-invoice-audit/ containing skill.yaml, INSTRUCTIONS.md and tests/cases.yaml, the layout CONTRIBUTING.md requires, and not as some other layout such as manifest.json plus SKILL.md",
24
+ "The related list contains rate-con-extractor and does not contain bol-parser, and the reply says bol-parser is not a skill in the repo",
25
+ "The skill's instructions do not make it run or hand off to detention-claim-letter, and the reply says the repo's rule that a skill never calls another skill is the reason",
26
+ "The owner field is a team that appears in OWNERS.md (billing-ops, dispatch or claims), and the reply tells the author the owner was not given and needs confirming",
27
+ "tests/cases.yaml has at least five cases, one of which covers a missing input such as no rate confirmation supplied, following AGENTS.md's rule over the three cases CONTRIBUTING.md asks for",
28
+ "The $5.00 per-line tolerance appears as an exact figure in INSTRUCTIONS.md, and no other threshold or percentage is introduced",
29
+ "A CATALOG.md row for carrier-invoice-audit is provided for the billing table",
30
+ "The reply tells the author to run make check before opening the pull request"
31
+ ]
32
+ },
33
+ {
34
+ "id": 3,
35
+ "held_out": "2026-10-05",
36
+ "prompt": "need a new skill for our skills repo. takes the standup notes people paste from slack during the week and writes the friday update that goes to our director. not sure on the name yet, weekly-digest or friday-rollup. Priya wanted it to pull ticket status from Jira too but we don't have API access sorted so leave that out for now i guess. don't know which folder it should live in, whoever set the repo up is on leave. can you write the skill up plus a few test cases so i can open a PR when she's back",
37
+ "assertions": [
38
+ "It delivers a drafted instruction file for the skill and at least three test cases, and does not stop at a list of questions",
39
+ "It says the repo's conventions (required files, metadata format or folder) were not available and that the file layout used is an assumption to check",
40
+ "It uses one name for the skill throughout and tells the author the choice between weekly-digest and friday-rollup is still theirs to make",
41
+ "The skill as written does not fetch from Jira and declares no Jira credential, API key or dependency; Jira is mentioned only as left out for now",
42
+ "The skill's instructions tell it to report only what is in the pasted standup notes and not to add progress, blockers or ticket status the notes do not contain",
43
+ "It does not invent the director's name, the team's name, team members other than Priya, or a specific repo folder stated as fact",
44
+ "The test cases use realistic pasted standup notes as input, and at least one covers thin or missing notes"
45
+ ]
17
46
  }
18
47
  ]
19
48
  }
@@ -0,0 +1,9 @@
1
+ # Instructions for agents working in this repo
2
+
3
+ Read `CONTRIBUTING.md` first. The rules below were added after it and win where the two differ.
4
+
5
+ 1. **A skill never calls, starts or hands off to another skill.** Each folder has to work when it is the only one installed. If a second skill is the natural follow-up, say so to the user in the final line of the output and let them run it.
6
+ 2. **Five test cases minimum, not three.** At least one case must cover missing input: the user leaves out something listed under `inputs`, and the expected behaviour is that the skill says what is missing and does not guess.
7
+ 3. **No customer or carrier names from real accounts** in instructions or tests. Use made-up ones.
8
+ 4. **Money rules are written as numbers.** If a skill applies a threshold or tolerance, the exact figure goes in `INSTRUCTIONS.md`. "A reasonable tolerance" is not acceptable.
9
+ 5. Do not edit `OWNERS.md`. If no listed team fits, ask in the pull request.
@@ -0,0 +1,18 @@
1
+ # Skill catalogue
2
+
3
+ ## billing
4
+
5
+ | skill | what it does | owner |
6
+ |---|---|---|
7
+ | [`rate-con-extractor`](skills/rate-con-extractor/INSTRUCTIONS.md) | Pulls the agreed rate, fuel surcharge and accessorials out of a rate confirmation into a table. | billing-ops |
8
+
9
+ ## dispatch
10
+
11
+ | skill | what it does | owner |
12
+ |---|---|---|
13
+
14
+ ## claims
15
+
16
+ | skill | what it does | owner |
17
+ |---|---|---|
18
+ | [`detention-claim-letter`](skills/detention-claim-letter/INSTRUCTIONS.md) | Drafts a detention claim letter to a shipper or receiver from the gate timestamps and the free-time terms. | claims |
@@ -0,0 +1,45 @@
1
+ # Contributing to larkfield-agent-skills
2
+
3
+ Internal skills for the Larkfield Freight operations and billing teams. One folder per skill under `skills/`.
4
+
5
+ ## What a skill folder contains
6
+
7
+ ```
8
+ skills/<skill-name>/
9
+ skill.yaml # metadata, see below
10
+ INSTRUCTIONS.md # what the agent does
11
+ tests/cases.yaml # test cases
12
+ ```
13
+
14
+ `<skill-name>` is lowercase kebab-case and matches `name` in `skill.yaml`.
15
+
16
+ ## skill.yaml
17
+
18
+ All of these are required:
19
+
20
+ | field | notes |
21
+ |---|---|
22
+ | `name` | same as the folder |
23
+ | `summary` | one sentence, 160 characters or fewer |
24
+ | `owner` | a team listed in `OWNERS.md` — the team that answers when the skill is wrong |
25
+ | `category` | `billing`, `dispatch` or `claims` |
26
+ | `inputs` | list of what the user must supply |
27
+ | `related` | list of other skills in this repo (may be empty). Every entry must be a folder under `skills/` |
28
+ | `network` | `false` unless the security team has signed off |
29
+
30
+ ## INSTRUCTIONS.md
31
+
32
+ Three sections, in this order: `## Purpose`, `## Steps`, `## Never`.
33
+ `## Never` lists at least two things the skill must not do.
34
+
35
+ ## Tests
36
+
37
+ `tests/cases.yaml` needs at least 3 cases. Each case has `name`, `input` and `expect` (a list of things that must be true of the output).
38
+
39
+ ## Catalogue
40
+
41
+ Add one row for the skill to `CATALOG.md`, in the table for its category, keeping the rows in alphabetical order.
42
+
43
+ ## Before you open a pull request
44
+
45
+ Run `make check`. It validates every `skill.yaml`, checks that `related` entries exist and counts the test cases. A pull request with a failing `make check` is not reviewed.
@@ -0,0 +1,7 @@
1
+ # Owning teams
2
+
3
+ | team | covers | contact |
4
+ |---|---|---|
5
+ | `billing-ops` | carrier payables, customer invoicing | Ines Okonkwo-Reyes |
6
+ | `dispatch` | load planning, carrier assignment | Tobias Vrana |
7
+ | `claims` | detention, damage and shortage claims | Marguerite Dlamini |
@@ -0,0 +1,20 @@
1
+ # detention-claim-letter
2
+
3
+ ## Purpose
4
+
5
+ When a driver is held at a facility past the agreed free time, Larkfield claims detention from the party that caused the delay. This skill drafts that letter with the arithmetic shown so the recipient can check it.
6
+
7
+ ## Steps
8
+
9
+ 1. Work out time on site: departure minus arrival, in hours and minutes.
10
+ 2. Subtract the free time. If the result is zero or less, say that no detention is owed and stop.
11
+ 3. Bill the remainder in the increments the terms state. If the terms do not state an increment, bill in 15-minute increments rounded down.
12
+ 4. Apply the cap if the terms state one.
13
+ 5. Write the letter: load number, facility, the timestamps, the calculation line by line, the amount claimed, and a reply-by date 14 days from the letter date.
14
+ 6. End the output with one line listing what evidence to attach (the signed bill of lading with in and out times).
15
+
16
+ ## Never
17
+
18
+ - Never claim an amount without showing the timestamps it came from.
19
+ - Never round time on site up.
20
+ - Never state that payment is overdue; this is a first claim.