skilldrop-cli 0.16.44 → 0.16.45
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/packs/converters/skills/md-to-html/manifest.json +1 -1
- package/packs/converters/skills/md-to-html/scripts/md_to_html.py +3 -0
- package/packs/design/skills/brand-kit/manifest.json +1 -1
- package/packs/design/skills/brand-kit/scripts/check_brand.py +11 -1
- package/packs/dev-team/skills/behaviour-equivalence/manifest.json +1 -1
- package/packs/dev-team/skills/behaviour-equivalence/scripts/golden_diff.py +11 -1
- package/packs/frontend/skills/bundle-size-review/manifest.json +1 -1
- package/packs/frontend/skills/bundle-size-review/scripts/bundle_stats.py +13 -1
- package/packs/frontend/skills/web-performance-review/manifest.json +1 -1
- package/packs/frontend/skills/web-performance-review/scripts/lh_summary.py +12 -2
- package/packs/grc/skills/risk-register/manifest.json +1 -1
- package/packs/grc/skills/risk-register/scripts/check_register.py +9 -1
- package/packs/infra-as-code/skills/terraform-plan-review/manifest.json +1 -1
- package/packs/infra-as-code/skills/terraform-plan-review/scripts/plan_summary.py +12 -2
- package/packs/product-manager/skills/feedback-synthesis/manifest.json +1 -1
- package/packs/product-manager/skills/feedback-synthesis/scripts/theme_counts.py +6 -1
- package/packs/research/skills/hypothesis-comparison/manifest.json +1 -1
- package/packs/research/skills/hypothesis-comparison/scripts/ach_matrix.py +10 -2
- package/packs/solution-architect/skills/event-contract-draft/manifest.json +1 -1
- package/packs/solution-architect/skills/event-contract-draft/scripts/check_asyncapi.py +14 -2
- package/packs/sre-oncall/skills/cloud-cost-review/manifest.json +1 -1
- package/packs/sre-oncall/skills/cloud-cost-review/scripts/cost_summary.py +2 -0
- package/packs/trackers/skills/backlog-triage/manifest.json +1 -1
- package/packs/trackers/skills/backlog-triage/scripts/triage_backlog.py +4 -1
- package/packs/trackers/skills/team-status-report/manifest.json +1 -1
- package/packs/trackers/skills/team-status-report/scripts/status_counts.py +4 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "skilldrop-cli",
|
|
3
|
-
"version": "0.16.
|
|
3
|
+
"version": "0.16.45",
|
|
4
4
|
"description": "Portable AI-agent skills, measured against the agent without them. ADRs, PRDs, runbooks, decks and reviews, installable into Claude Code, Cursor, Kiro, and any IDE that takes custom instructions. One command per skill or per role-based pack.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Sanjay Ananthanarayan",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "md-to-html",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Turn a Markdown draft into one self-contained, print-friendly HTML file that survives being emailed or attached, with headings that have anchor links, tables, code, quotes, an optional table of contents, local images embedded, brand colours and fonts from a brand.json, Mermaid diagrams kept as text or drawn from a pinned CDN, and raw HTML escaped so nothing in the source runs. Use when the user wants Markdown \"as a web page\", \"as an HTML file I can send\", \"something people can open in a browser and print\", or says \"convert this to HTML\".",
|
|
5
5
|
"entrypoint": "SKILL.md",
|
|
6
6
|
"deps": {
|
|
@@ -552,6 +552,9 @@ def build_toc(headings):
|
|
|
552
552
|
|
|
553
553
|
|
|
554
554
|
def convert(src_text, base_dir, brand_path=None, toc=False, mermaid="none", allow_html=False):
|
|
555
|
+
# \x00 and \x01 mark the converter's own placeholders (code spans, line breaks); a source
|
|
556
|
+
# that contains them would collide with those markers, and neither belongs in a document.
|
|
557
|
+
src_text = src_text.replace("\x00", "").replace("\x01", "")
|
|
555
558
|
ctx = Ctx(base_dir, allow_html, mermaid)
|
|
556
559
|
brand = load_brand(brand_path, ctx)
|
|
557
560
|
lines = src_text.replace("\r\n", "\n").replace("\r", "\n").split("\n")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "brand-kit",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Capture a brand once as a reusable brand.json — logo files (and an on-dark version), colour roles as hex codes, heading and body fonts, voice, imagery rules, a PowerPoint template, contact details and the legal footer — plus a one-page BRAND.md, with text contrast checked and every value traced to where it came from. Use when the user wants decks, flyers or documents in their branding, says \"use our brand\", \"here's our logo and colours\", \"set up our brand kit\", or hands over brand guidelines.",
|
|
5
5
|
"entrypoint": "SKILL.md",
|
|
6
6
|
"deps": {
|
|
@@ -35,7 +35,7 @@ def contrast(a, b):
|
|
|
35
35
|
return (la + 0.05) / (lb + 0.05)
|
|
36
36
|
|
|
37
37
|
|
|
38
|
-
def
|
|
38
|
+
def _main(argv):
|
|
39
39
|
if len(argv) != 1:
|
|
40
40
|
print(__doc__)
|
|
41
41
|
return 2
|
|
@@ -116,5 +116,15 @@ def main(argv):
|
|
|
116
116
|
return 1 if fails else 0
|
|
117
117
|
|
|
118
118
|
|
|
119
|
+
|
|
120
|
+
def main(argv):
|
|
121
|
+
"""Run, and turn an input that parses but has the wrong shape into one clear error."""
|
|
122
|
+
try:
|
|
123
|
+
return _main(argv)
|
|
124
|
+
except (KeyError, TypeError, AttributeError, IndexError, ValueError) as e:
|
|
125
|
+
print("error: couldn't read the input as a brand file (%s: %s). If it is one, this is a bug: "
|
|
126
|
+
"please report it with the file." % (type(e).__name__, e), file=sys.stderr)
|
|
127
|
+
return 2
|
|
128
|
+
|
|
119
129
|
if __name__ == "__main__":
|
|
120
130
|
sys.exit(main(sys.argv[1:]))
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "behaviour-equivalence",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Prove that a rewritten, migrated or refactored component behaves the same as the old one with a golden-master comparison — record inputs and outputs at a boundary (HTTP, function calls, batch files, DB writes, events) from production-shaped traffic and edge cases, run the old system twice to find its nondeterminism, normalise narrowly field by field, replay against the new one, triage every difference as defect, intended change (with a named approver) or missing normalisation, and give a verdict of EQUIVALENT, EQUIVALENT EXCEPT LISTED CHANGES, NOT EQUIVALENT or NOT PROVEN. Use when the user asks \"does the new one behave the same as the old one\", \"can we sign off the rewrite\", \"are these diffs just noise\", wants a golden-master, approval or shadow-traffic comparison, a parity check between old and new outputs, or says \"prove behaviour equivalence\".",
|
|
5
5
|
"entrypoint": "SKILL.md",
|
|
6
6
|
"deps": { "npm": [], "pip": [] },
|
|
@@ -495,7 +495,7 @@ def render(rep, max_diffs):
|
|
|
495
495
|
return "\n".join(out) + "\n"
|
|
496
496
|
|
|
497
497
|
|
|
498
|
-
def
|
|
498
|
+
def _main(argv=None):
|
|
499
499
|
ap = argparse.ArgumentParser(description="Golden-master diff of old vs new recorded outputs.")
|
|
500
500
|
ap.add_argument("old", help="directory or JSONL file of the old system's outputs")
|
|
501
501
|
ap.add_argument("new", help="directory or JSONL file of the new system's outputs")
|
|
@@ -522,5 +522,15 @@ def main(argv=None):
|
|
|
522
522
|
return 0 if rep["verdict"] != "NOT EQUIVALENT" else 1
|
|
523
523
|
|
|
524
524
|
|
|
525
|
+
|
|
526
|
+
def main(argv=None):
|
|
527
|
+
"""Run, and turn an input that parses but has the wrong shape into one clear error."""
|
|
528
|
+
try:
|
|
529
|
+
return _main(argv)
|
|
530
|
+
except (KeyError, TypeError, AttributeError, IndexError, ValueError) as e:
|
|
531
|
+
print("error: couldn't read the input as recordings and rules (%s: %s). If it is one, this is a bug: "
|
|
532
|
+
"please report it with the file." % (type(e).__name__, e), file=sys.stderr)
|
|
533
|
+
return 2
|
|
534
|
+
|
|
525
535
|
if __name__ == "__main__":
|
|
526
536
|
sys.exit(main())
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bundle-size-review",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "Find what makes a JavaScript bundle heavy and what can safely come out, from the bundler's stats file (webpack stats.json, an esbuild metafile, or the rollup-plugin-visualizer raw-data JSON that Vite builds use) read together with the repo around it (package.json, .browserslistrc, the lockfile, the bundler config and the source files that import the heavy modules). Gives each entry's first-load JavaScript in the exact bytes the stats record, kept apart from lazy chunks; finds whole-library imports, CommonJS packages that can't be tree-shaken, packages bundled twice and polyfills the browser targets don't need; names the import chain that pulls each module in before suggesting a removal; and, given before and after stats, says what grew, why, whether it lands in the first load and which per-entry budget would have caught it. Use when the user says \"why is our bundle so big\", \"what can we cut from the bundle\", \"review this stats.json\", \"why is lodash or moment in our bundle\", \"the bundle grew in this PR\", or \"set a bundle size budget\".",
|
|
5
5
|
"entrypoint": "SKILL.md",
|
|
6
6
|
"deps": {
|
|
@@ -500,6 +500,8 @@ def load(path):
|
|
|
500
500
|
text = fh.read()
|
|
501
501
|
except OSError as e:
|
|
502
502
|
raise InputError("cannot read %s: %s" % (path, e))
|
|
503
|
+
except UnicodeDecodeError:
|
|
504
|
+
raise InputError("%s is not UTF-8 text; save it as UTF-8" % path)
|
|
503
505
|
if not text.strip():
|
|
504
506
|
raise InputError("%s is empty" % path)
|
|
505
507
|
try:
|
|
@@ -1574,7 +1576,7 @@ def to_jsonable(o):
|
|
|
1574
1576
|
raise TypeError(type(o))
|
|
1575
1577
|
|
|
1576
1578
|
|
|
1577
|
-
def
|
|
1579
|
+
def _main(argv=None):
|
|
1578
1580
|
ap = argparse.ArgumentParser(description="Size a JS bundle from webpack stats, an esbuild metafile or rollup-plugin-visualizer raw data, and read the project files around it.")
|
|
1579
1581
|
ap.add_argument("stats", nargs="?", help="stats file, or - for stdin")
|
|
1580
1582
|
ap.add_argument("--compare", nargs=2, metavar=("BEFORE", "AFTER"), help="diff two stats files")
|
|
@@ -1629,5 +1631,15 @@ def main(argv=None):
|
|
|
1629
1631
|
return 1 if over else 0
|
|
1630
1632
|
|
|
1631
1633
|
|
|
1634
|
+
|
|
1635
|
+
def main(argv=None):
|
|
1636
|
+
"""Run, and turn an input that parses but has the wrong shape into one clear error."""
|
|
1637
|
+
try:
|
|
1638
|
+
return _main(argv)
|
|
1639
|
+
except (KeyError, TypeError, AttributeError, IndexError, ValueError) as e:
|
|
1640
|
+
print("error: couldn't read the input as webpack, esbuild or Vite stats (%s: %s). If it is one, this is a bug: "
|
|
1641
|
+
"please report it with the file." % (type(e).__name__, e), file=sys.stderr)
|
|
1642
|
+
return 2
|
|
1643
|
+
|
|
1632
1644
|
if __name__ == "__main__":
|
|
1633
1645
|
sys.exit(main())
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "web-performance-review",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Read a Lighthouse report or PageSpeed Insights result, with field data when there is any (CrUX, PSI loadingExperience or RUM p75), and say what makes the page slow for real users and what to fix first, ranked by how much each fix moves LCP, INP and CLS at the 75th percentile. Names the LCP element and which LCP phase dominates, the elements that shift and why, the long tasks and third parties by main-thread time, and treats estimated savings as lab estimates that overlap. Use when the user shares a Lighthouse JSON or PSI result and asks \"why is this page slow\", \"what should we fix first\", \"we score 92 but Search Console says LCP is poor\", \"did our change fix INP\", \"review this Lighthouse report\", or \"how do we pass Core Web Vitals\".",
|
|
5
5
|
"entrypoint": "SKILL.md",
|
|
6
6
|
"deps": {
|
|
@@ -702,7 +702,7 @@ def cmp_md(c):
|
|
|
702
702
|
return "\n".join(L)
|
|
703
703
|
|
|
704
704
|
|
|
705
|
-
def
|
|
705
|
+
def _main():
|
|
706
706
|
ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
|
|
707
707
|
ap.add_argument("reports", nargs="+", help="Lighthouse JSON, PSI JSON or CrUX JSON path, or - for stdin (max 2)")
|
|
708
708
|
ap.add_argument("--field", help="extra field data: a PSI response or CrUX API record JSON")
|
|
@@ -744,5 +744,15 @@ def main():
|
|
|
744
744
|
print(text)
|
|
745
745
|
|
|
746
746
|
|
|
747
|
+
|
|
748
|
+
def main():
|
|
749
|
+
"""Run, and turn an input that parses but has the wrong shape into one clear error."""
|
|
750
|
+
try:
|
|
751
|
+
return _main()
|
|
752
|
+
except (KeyError, TypeError, AttributeError, IndexError, ValueError) as e:
|
|
753
|
+
print("error: couldn't read the input as a Lighthouse, PageSpeed Insights or CrUX report (%s: %s). If it is one, this is a bug: "
|
|
754
|
+
"please report it with the file." % (type(e).__name__, e), file=sys.stderr)
|
|
755
|
+
return 2
|
|
756
|
+
|
|
747
757
|
if __name__ == "__main__":
|
|
748
|
-
main()
|
|
758
|
+
sys.exit(main())
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "risk-register",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Build or update an enterprise or project risk register — each risk written as cause, event and consequence, scored for likelihood and impact on a defined scale, inherent and residual ratings, a named owner, a treatment (avoid, reduce, transfer, accept), controls, a due date and a review trigger — with a heat map, the top risks that need a decision, and a stdlib script that validates the register CSV and flags overdue reviews. Use when the user wants a risk register, a risk log, a RAID log's risk section, a heat map, or says \"what are our top risks\", \"update the risk register\", \"score these risks\".",
|
|
5
5
|
"entrypoint": "SKILL.md",
|
|
6
6
|
"deps": {
|
|
@@ -152,8 +152,16 @@ def main(argv=None):
|
|
|
152
152
|
except (IOError, OSError) as exc:
|
|
153
153
|
print("error: cannot read %s: %s" % (args.csv, exc), file=sys.stderr)
|
|
154
154
|
return 2
|
|
155
|
+
except UnicodeDecodeError:
|
|
156
|
+
print("error: %s is not UTF-8 text; save it as UTF-8 (in Excel: CSV UTF-8)" % args.csv, file=sys.stderr)
|
|
157
|
+
return 2
|
|
155
158
|
|
|
156
|
-
|
|
159
|
+
try: # parse once up front, so a malformed file is one clear error, not a traceback mid-loop
|
|
160
|
+
list(csv.reader(io.StringIO(text, newline="")))
|
|
161
|
+
except csv.Error as exc:
|
|
162
|
+
print("error: %s is not a readable CSV: %s" % (args.csv, exc), file=sys.stderr)
|
|
163
|
+
return 2
|
|
164
|
+
reader = csv.DictReader(io.StringIO(text, newline=""))
|
|
157
165
|
if not reader.fieldnames:
|
|
158
166
|
print("error: %s has no header row" % args.csv, file=sys.stderr)
|
|
159
167
|
return 2
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "terraform-plan-review",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Review a terraform plan before apply, from the plan text or terraform show -json output. Flags every destroy and replace with the attribute forcing it, changes to stateful resources such as databases, buckets and volumes, IAM and security-group widening, drift nobody made in code, and changes outside the intended scope, then gives a SAFE TO APPLY, APPLY WITH CARE or DO NOT APPLY verdict with reasons. Use when the user pastes a plan, says \"review this terraform plan\", \"is this plan safe to apply\", \"why is terraform replacing my database\", or \"check the plan before I apply\".",
|
|
5
5
|
"entrypoint": "SKILL.md",
|
|
6
6
|
"deps": {
|
|
@@ -338,7 +338,7 @@ def to_markdown(r):
|
|
|
338
338
|
return "\n".join(lines) + "\n"
|
|
339
339
|
|
|
340
340
|
|
|
341
|
-
def
|
|
341
|
+
def _main():
|
|
342
342
|
ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
|
|
343
343
|
ap.add_argument("plan", help="path to `terraform show -json` output, or - for stdin")
|
|
344
344
|
ap.add_argument("--scope", action="append", default=[],
|
|
@@ -359,5 +359,15 @@ def main():
|
|
|
359
359
|
sys.stdout.write(text)
|
|
360
360
|
|
|
361
361
|
|
|
362
|
+
|
|
363
|
+
def main():
|
|
364
|
+
"""Run, and turn an input that parses but has the wrong shape into one clear error."""
|
|
365
|
+
try:
|
|
366
|
+
return _main()
|
|
367
|
+
except (KeyError, TypeError, AttributeError, IndexError, ValueError) as e:
|
|
368
|
+
print("error: couldn't read the input as a Terraform JSON plan (%s: %s). If it is one, this is a bug: "
|
|
369
|
+
"please report it with the file." % (type(e).__name__, e), file=sys.stderr)
|
|
370
|
+
return 2
|
|
371
|
+
|
|
362
372
|
if __name__ == "__main__":
|
|
363
|
-
main()
|
|
373
|
+
sys.exit(main())
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "feedback-synthesis",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Turn raw customer feedback — interview notes, support tickets, NPS or CSAT verbatims, app-store reviews, sales-call notes — into ranked themes a product decision can rest on, each a problem statement rather than a feature request, counted by distinct people or accounts not mentions, with segments, severity kept separate from frequency, verbatim quotes with source IDs, what people value, where sources disagree, what the sample cannot tell, and the questions for the next interviews. A stdlib script computes the counts. Use when the user says \"what are customers telling us\", \"synthesise this feedback\", \"find the themes in these tickets / reviews / NPS comments\", \"affinity map these interview notes\", or \"what should we fix first based on feedback\".",
|
|
5
5
|
"entrypoint": "SKILL.md",
|
|
6
6
|
"deps": {
|
|
@@ -75,6 +75,8 @@ def load(path: Path, person_col: str, ignore: set, segments_wanted: list):
|
|
|
75
75
|
text = path.read_text(encoding="utf-8-sig")
|
|
76
76
|
except OSError as e:
|
|
77
77
|
die(f"cannot read {path}: {e}")
|
|
78
|
+
except UnicodeDecodeError:
|
|
79
|
+
die(f"{path} is not UTF-8 text; save it as UTF-8 (in Excel: CSV UTF-8)")
|
|
78
80
|
reader = csv.DictReader(text.splitlines())
|
|
79
81
|
if not reader.fieldnames:
|
|
80
82
|
die(f"{path} has no header row")
|
|
@@ -334,7 +336,10 @@ def main(argv=None) -> int:
|
|
|
334
336
|
a = ap.parse_args(argv)
|
|
335
337
|
|
|
336
338
|
ignore = DEFAULT_IGNORE | {c.lower() for c in a.ignore}
|
|
337
|
-
|
|
339
|
+
try:
|
|
340
|
+
rows, segs, warnings = load(a.csv, a.person_col, ignore, a.segment)
|
|
341
|
+
except csv.Error as e: # e.g. a NUL byte, which Python before 3.11 rejects
|
|
342
|
+
die(f"{a.csv} is not a readable CSV: {e}")
|
|
338
343
|
if not rows:
|
|
339
344
|
die(f"{a.csv} has no feedback rows")
|
|
340
345
|
label = a.person_label or ("person" if a.person_col == "person_id" else a.person_col.replace("_id", "").replace("_", " "))
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hypothesis-comparison",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Compare rival explanations for an observation with analysis of competing hypotheses (ACH) — list the hypotheses and the evidence, build a consistency matrix (consistent / inconsistent / neutral), rank hypotheses by how little evidence contradicts them rather than how much supports them, name the diagnostic evidence and the linchpins, and say which next observation would tell the leaders apart. Use when the user asks \"why did X happen\", \"which explanation fits the evidence\", \"what's really causing this\", wants to stop arguing over a favourite theory, or says \"compare these hypotheses\", \"competing explanations\", \"ACH\".",
|
|
5
5
|
"entrypoint": "SKILL.md",
|
|
6
6
|
"deps": {
|
|
@@ -33,6 +33,7 @@ Exit codes: 0 report written, 2 bad input.
|
|
|
33
33
|
|
|
34
34
|
import argparse
|
|
35
35
|
import csv
|
|
36
|
+
import io
|
|
36
37
|
import sys
|
|
37
38
|
from pathlib import Path
|
|
38
39
|
|
|
@@ -53,7 +54,11 @@ def load(path):
|
|
|
53
54
|
p = Path(path)
|
|
54
55
|
if not p.is_file():
|
|
55
56
|
fail("file not found: " + str(path))
|
|
56
|
-
|
|
57
|
+
try:
|
|
58
|
+
text = p.read_text(encoding="utf-8-sig")
|
|
59
|
+
except UnicodeDecodeError:
|
|
60
|
+
fail("{} is not UTF-8 text; save it as UTF-8 (in Excel: CSV UTF-8)".format(path))
|
|
61
|
+
with io.StringIO(text, newline="") as f:
|
|
57
62
|
reader = csv.DictReader(f)
|
|
58
63
|
if not reader.fieldnames:
|
|
59
64
|
fail("the CSV is empty")
|
|
@@ -123,7 +128,10 @@ def main():
|
|
|
123
128
|
ap.add_argument("-o", "--output", help="write the report here instead of stdout")
|
|
124
129
|
args = ap.parse_args()
|
|
125
130
|
|
|
126
|
-
|
|
131
|
+
try:
|
|
132
|
+
hyps, rows = load(args.matrix)
|
|
133
|
+
except csv.Error as e: # e.g. a NUL byte, which Python before 3.11 rejects
|
|
134
|
+
fail("{} is not a readable CSV: {}".format(args.matrix, e))
|
|
127
135
|
s = scores(hyps, rows)
|
|
128
136
|
order = ranking(hyps, s)
|
|
129
137
|
top = leaders(hyps, s)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "event-contract-draft",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Draft the contract for an event or message interface (Kafka topics, SNS/SQS, EventBridge, Pub/Sub, RabbitMQ, webhooks) as an AsyncAPI 3.0 document plus a short decisions page — past-tense event names, a required envelope with event id for deduplication, the partition key and the ordering it does and doesn't give, at-least-once delivery and how consumers stay idempotent, data minimisation with PII marked, additive-only versioning with dated deprecation, and retention, replay and dead-letter handling. Use when the user wants to define events, design a Kafka topic or message schema, write an AsyncAPI spec, agree what a producer publishes and what consumers must do, or version an event safely.",
|
|
5
5
|
"entrypoint": "SKILL.md",
|
|
6
6
|
"deps": { "npm": [], "pip": [] },
|
|
@@ -113,9 +113,11 @@ class Report:
|
|
|
113
113
|
|
|
114
114
|
def load(path):
|
|
115
115
|
try:
|
|
116
|
-
text = sys.stdin.read() if path == "-" else Path(path).read_text(encoding="utf-8")
|
|
116
|
+
text = sys.stdin.read() if path == "-" else Path(path).read_text(encoding="utf-8-sig")
|
|
117
117
|
except OSError as exc:
|
|
118
118
|
raise SystemExit(f"error: cannot read {path}: {exc.strerror}")
|
|
119
|
+
except UnicodeDecodeError:
|
|
120
|
+
raise SystemExit(f"error: {path} is not UTF-8 text; save it as UTF-8")
|
|
119
121
|
stripped = text.lstrip()
|
|
120
122
|
if not stripped:
|
|
121
123
|
raise SystemExit(f"error: {path} is empty")
|
|
@@ -484,7 +486,7 @@ def check(doc):
|
|
|
484
486
|
return rep, counts
|
|
485
487
|
|
|
486
488
|
|
|
487
|
-
def
|
|
489
|
+
def _main(argv=None):
|
|
488
490
|
parser = argparse.ArgumentParser(description=__doc__.split("\n\n")[0])
|
|
489
491
|
parser.add_argument("path", help="AsyncAPI 3.0 document as JSON, or - for stdin")
|
|
490
492
|
parser.add_argument("-o", "--output", help="write the report to this path as well as stdout")
|
|
@@ -505,5 +507,15 @@ def main(argv=None):
|
|
|
505
507
|
return 1 if rep.fails else 0
|
|
506
508
|
|
|
507
509
|
|
|
510
|
+
|
|
511
|
+
def main(argv=None):
|
|
512
|
+
"""Run, and turn an input that parses but has the wrong shape into one clear error."""
|
|
513
|
+
try:
|
|
514
|
+
return _main(argv)
|
|
515
|
+
except (KeyError, TypeError, AttributeError, IndexError, ValueError) as e:
|
|
516
|
+
print("error: couldn't read the input as an AsyncAPI document (%s: %s). If it is one, this is a bug: "
|
|
517
|
+
"please report it with the file." % (type(e).__name__, e), file=sys.stderr)
|
|
518
|
+
return 2
|
|
519
|
+
|
|
508
520
|
if __name__ == "__main__":
|
|
509
521
|
sys.exit(main())
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cloud-cost-review",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Review actual cloud spend from a cost export (AWS CUR or Data Exports, GCP billing export, Azure cost details) and produce a ranked savings report — idle and unattached resources, oversized instances backed by utilisation evidence, storage tiering, data transfer hot spots, commitment coverage and untagged spend — each finding with a monthly saving computed from the bill, a confidence level and an ease rating. Use when the user shares a cost CSV and says \"where are we wasting money\", \"review our AWS bill\", \"why did the cloud bill jump this month\", \"find savings in our GCP/Azure spend\", or \"should we buy savings plans\".",
|
|
5
5
|
"entrypoint": "SKILL.md",
|
|
6
6
|
"deps": {
|
|
@@ -607,6 +607,8 @@ def main(argv=None):
|
|
|
607
607
|
fmt, used, rows = load(Path(args.csv), args.format)
|
|
608
608
|
except UnicodeDecodeError:
|
|
609
609
|
raise InputError("%s is not UTF-8 text; save it as UTF-8 (CSV UTF-8) and retry" % args.csv)
|
|
610
|
+
except csv.Error as e: # e.g. a NUL byte, which Python before 3.11 rejects
|
|
611
|
+
raise InputError("%s is not a readable CSV: %s" % (args.csv, e))
|
|
610
612
|
a = analyse(rows, args.tag, args.month, args.top, fmt)
|
|
611
613
|
except InputError as e:
|
|
612
614
|
print("error: %s" % e, file=sys.stderr)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "backlog-triage",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Triage a whole backlog export from Jira, Linear or GitHub Projects (CSV, JSON or an MCP tool) — duplicate and near-duplicate pairs with the reason, items missing acceptance criteria or an owner, stale items, oversized items to split, priority conflicts, and a proposed ordering with a reason per item — ending in a change list the user can apply. A stdlib script computes the mechanical checks. Use when the user wants to clean up, groom or refine a backlog, find duplicate tickets, or says \"triage our backlog\", \"what should we pull next?\".",
|
|
5
5
|
"entrypoint": "SKILL.md",
|
|
6
6
|
"deps": {
|
|
@@ -254,7 +254,10 @@ def main(argv=None) -> int:
|
|
|
254
254
|
|
|
255
255
|
if not args.export.exists():
|
|
256
256
|
die("{} does not exist".format(args.export))
|
|
257
|
-
|
|
257
|
+
try:
|
|
258
|
+
rows = load_rows(args.export)
|
|
259
|
+
except csv.Error as e: # e.g. a NUL byte, which Python before 3.11 rejects
|
|
260
|
+
die("{} is not a readable export: {}".format(args.export, e))
|
|
258
261
|
if not rows:
|
|
259
262
|
die("{} has no issues in it".format(args.export))
|
|
260
263
|
headers = []
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "team-status-report",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Write a weekly team status report from tracker data (Jira, Linear or GitHub Projects, via CSV, JSON or an MCP tool) — shipped, in progress, blocked with the blocker and owner, slipped against plan, risks and asks — with every number computed from the data by a stdlib script, never estimated, and a RAG status that states the rule that set it. Use when the user wants a weekly status update, sprint report or stakeholder update from the tracker, or says \"write this week's status report\", \"are we on track?\".",
|
|
5
5
|
"entrypoint": "SKILL.md",
|
|
6
6
|
"deps": {
|
|
@@ -234,7 +234,10 @@ def main(argv=None) -> int:
|
|
|
234
234
|
|
|
235
235
|
if not args.export.exists():
|
|
236
236
|
die("{} does not exist".format(args.export))
|
|
237
|
-
|
|
237
|
+
try:
|
|
238
|
+
rows = load_rows(args.export)
|
|
239
|
+
except csv.Error as e: # e.g. a NUL byte, which Python before 3.11 rejects
|
|
240
|
+
die("{} is not a readable export: {}".format(args.export, e))
|
|
238
241
|
if not rows:
|
|
239
242
|
die("{} has no issues in it".format(args.export))
|
|
240
243
|
headers = []
|