skilldrop-cli 0.16.24 → 0.16.26
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/solution-architect/skills/interactive-diagram/SKILL.md +7 -7
- package/packs/solution-architect/skills/interactive-diagram/evals/evals.json +30 -0
- package/packs/solution-architect/skills/interactive-diagram/evals/files/12/README.md +17 -0
- package/packs/solution-architect/skills/interactive-diagram/evals/files/12/app/api.py +25 -0
- package/packs/solution-architect/skills/interactive-diagram/evals/files/12/app/catalog.py +23 -0
- package/packs/solution-architect/skills/interactive-diagram/evals/files/12/app/db.py +40 -0
- package/packs/solution-architect/skills/interactive-diagram/evals/files/12/app/holds.py +62 -0
- package/packs/solution-architect/skills/interactive-diagram/evals/files/12/app/notify.py +32 -0
- package/packs/solution-architect/skills/interactive-diagram/evals/files/12/config.yaml +18 -0
- package/packs/solution-architect/skills/interactive-diagram/evals/files/12/jobs/fines.py +26 -0
- package/packs/solution-architect/skills/interactive-diagram/examples/checkout-map.html +2 -2
- package/packs/solution-architect/skills/interactive-diagram/examples/checkout-trace.html +2 -2
- package/packs/solution-architect/skills/interactive-diagram/examples/incident-timeline.html +2 -2
- package/packs/solution-architect/skills/interactive-diagram/examples/leave-approval.html +2 -2
- package/packs/solution-architect/skills/interactive-diagram/manifest.json +1 -1
- package/packs/solution-architect/skills/interactive-diagram/scripts/build_diagram.py +2 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "skilldrop-cli",
|
|
3
|
-
"version": "0.16.
|
|
3
|
+
"version": "0.16.26",
|
|
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",
|
|
@@ -99,10 +99,9 @@ Two things follow:
|
|
|
99
99
|
right lines or mark the item assumed. Add `--mermaid diagram.mmd` for a version to paste
|
|
100
100
|
into a README, and `--compare old.json` to show what a change adds, removes and alters.
|
|
101
101
|
|
|
102
|
-
5. **
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
were checked:
|
|
102
|
+
5. **Lead the reply with the map.** The script prints a titled link to the page. Put it at the
|
|
103
|
+
top, then the takeaway in one or two sentences, then one line each for what is drawn as an
|
|
104
|
+
assumption and what was left out, and last how many citations were checked:
|
|
106
105
|
|
|
107
106
|
> **What a checkout request touches** (the script's link line: the title, linked to the page)
|
|
108
107
|
>
|
|
@@ -110,8 +109,8 @@ Two things follow:
|
|
|
110
109
|
> Assumed: the shopper's browser (no client in this repo). Left out: the products table's
|
|
111
110
|
> other readers. 22 of 22 citations checked.
|
|
112
111
|
|
|
113
|
-
Keep the spec beside the page so the map can be rebuilt after the code changes.
|
|
114
|
-
|
|
112
|
+
Keep the spec beside the page so the map can be rebuilt after the code changes. At most one
|
|
113
|
+
short line about the build may come before the link; no recap of commands or layout.
|
|
115
114
|
|
|
116
115
|
6. **When the script can't run** (no `python3`), give the Mermaid version in the reply, with
|
|
117
116
|
assumptions marked, and say that the interactive page needs Python 3.
|
|
@@ -131,7 +130,8 @@ don't cite them in the first place, and read the page before it leaves the team.
|
|
|
131
130
|
- **Nothing on the map comes only from a README, a plan or what such systems usually have.**
|
|
132
131
|
- **The map answers one stated question,** and what was left out is said.
|
|
133
132
|
- **One HTML file that works offline:** no CDN, no external fonts, no network requests.
|
|
134
|
-
- **The
|
|
133
|
+
- **The link and the takeaway lead the reply,** with at most one short line about the build
|
|
134
|
+
before them and no account of how it was made.
|
|
135
135
|
|
|
136
136
|
## When to use this skill
|
|
137
137
|
|
|
@@ -159,6 +159,36 @@
|
|
|
159
159
|
"Each class and relation cites the file it came from, with a line where one applies (for example Greenhouse.java line 12 or 21 for the zones composition, AbstractSensor.java line 7 for implements Sensor)",
|
|
160
160
|
"The reply's first line is a link to or the path of the HTML file, followed by the takeaway: one interface, one abstract base and two sensors; Greenhouse owns its Zones but only references AlertService; and WateringSchedule is named in a comment but missing from the code"
|
|
161
161
|
]
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
"id": 12,
|
|
165
|
+
"held_out": "2026-10-19",
|
|
166
|
+
"prompt": "This is our library's holds service. A new developer starts Monday and I want to hand them one interactive diagram of how a hold works: a patron asks for a book, and later gets told it's ready to collect. Just that journey, nothing else. Can you make it?",
|
|
167
|
+
"assertions": [
|
|
168
|
+
"Writes one self-contained .html file that opens offline: no <script src>, stylesheet link, @import or font URL pointing at http(s) or a CDN",
|
|
169
|
+
"Draws the place-hold path as the code does it: POST /holds (app/api.py) -> holds.place_hold, which refuses when the card is missing or expired or the patron already has 10 active holds, inserts a 'waiting' hold, then either takes an on-shelf copy and makes the hold ready, or leaves it waiting and returns the queue position counted from the holds table",
|
|
170
|
+
"Draws the second way a hold becomes ready: POST /checkins -> holds.on_checkin, which picks the oldest waiting hold for that title (ORDER BY placed_at) and calls _make_ready, or puts the copy back on the shelf when nobody is waiting; both routes run through _make_ready -> notify.send_ready -> an HTTP POST to the NOTIFY_WEBHOOK_URL webhook",
|
|
171
|
+
"Does not draw Redis as part of the hold journey, even though README.md says Redis keeps each patron's place in the hold queue: the code works out the position from Postgres and only catalog.search uses Redis. The diagram or reply points out that the README and the code disagree",
|
|
172
|
+
"Leaves out the behaviours the user didn't ask about: catalogue search (GET /search) and the nightly fines job (jobs/fines.py) are not drawn as steps in the hold journey (at most shown greyed out or listed as out of scope)",
|
|
173
|
+
"Treats what sits behind the webhook as unknown: who receives hold.ready and how the patron is actually emailed or texted is drawn dashed or marked unknown, with no made-up provider name, and it notes that the code makes one attempt with a 5-second timeout, only logs a failure, and never retries",
|
|
174
|
+
"Boxes and arrows cite real places in the fixture files (for example app/holds.py place_hold near line 13, on_checkin near line 45, _make_ready near line 59, app/notify.py requests.post near line 26), and no component, table, queue or step appears that the repository doesn't contain",
|
|
175
|
+
"The reply's first line is the path or link to the HTML file, followed straight away by the takeaway (such as the two ways a hold becomes ready, or the README/Redis mismatch); no build log, tool output or status report comes before it"
|
|
176
|
+
]
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
"id": 13,
|
|
180
|
+
"held_out": "2026-10-19",
|
|
181
|
+
"prompt": "I coordinate adoptions at a small animal rescue (call it Hollyford Rescue) and I'd love an interactive diagram of our adoption process to share with new volunteers. Here's how it goes, as best I can explain it:\n\nSomeone spots an animal on our website and fills in the online enquiry. A volunteer rings them within 3 days for a chat. If it sounds like a fit we email them the full application form; if not, we point them to other rescues. When the application comes back we take up two references - one has to be their vet if they've had pets before. Then there's a home visit. We do a home visit for every adoption, except cats going to someone who's adopted from us before - unless they've moved house since, in which case we do it again. After that comes a meet-and-greet at the shelter, and dogs also meet any dog already living in the household. The adoption panel sits on Thursdays and approves or declines. If they decline we email the applicant, and they can reapply after 3 months. If approved, we hold the animal for them for 48 hours. They pay the fee (150 pounds for dogs, 90 for cats) and sign the adoption contract. Our vet does a final health check and transfers the microchip, then it's collection day. We ring them two weeks after collection, and someone visits at six months.",
|
|
182
|
+
"assertions": [
|
|
183
|
+
"Writes one self-contained .html file that opens offline: no <script src>, stylesheet link, @import or font URL pointing at http(s) or a CDN",
|
|
184
|
+
"All the steps appear in the order described, none merged away: online enquiry, phone chat within 3 days, application form emailed, application returned, two references, home visit, meet-and-greet, Thursday panel, 48-hour hold, fee and contract, vet check and microchip transfer, collection, two-week call, six-month visit",
|
|
185
|
+
"Gets the home-visit rule right: a home visit by default; skipped only for a cat going to a previous adopter who has not moved house; dogs always get one; a previous cat adopter who has moved gets one again. It does not draw 'previous adopters skip the home visit' without the cat and moved-house conditions",
|
|
186
|
+
"Shows the two branches the user gave: not a fit after the phone chat -> pointed to other rescues; panel declines -> email, can reapply after 3 months. The panel step says it sits on Thursdays",
|
|
187
|
+
"Gets the smaller conditions right: the vet must be one of the two references only if the applicant has had pets before; only dogs meet the household's resident dog; the fee is 150 pounds for a dog and 90 for a cat",
|
|
188
|
+
"Flags the gap: the description never says what happens if the 48 hours run out before the fee is paid and the contract signed. That outcome is drawn dashed or marked as an open question, and the reply raises it, rather than being drawn as a solid step",
|
|
189
|
+
"Nothing is invented: no step, fee, deposit, timing, role or outcome the user didn't describe (for example no path for a failed reference or a failed home visit) is drawn as fact; anything like that is dashed and labelled as an assumption. Steps cite the user's own words as their source",
|
|
190
|
+
"The reply's first line is the path or link to the HTML file, followed straight away by the takeaway (such as the 48-hour gap or the home-visit exception); no build log, tool output or status report comes before it"
|
|
191
|
+
]
|
|
162
192
|
}
|
|
163
193
|
]
|
|
164
194
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# shelfline
|
|
2
|
+
|
|
3
|
+
Holds and catalogue service for the Example County Library branches.
|
|
4
|
+
|
|
5
|
+
## Components
|
|
6
|
+
|
|
7
|
+
- **API** (`app/api.py`) - small Flask app: catalogue search, placing holds, checking books back in.
|
|
8
|
+
- **Postgres** - patrons, copies, loans, holds, fines.
|
|
9
|
+
- **Redis** - caches search results and keeps each patron's place in the hold queue.
|
|
10
|
+
- **Notifier** (`app/notify.py`) - tells patrons when a held book is ready to collect.
|
|
11
|
+
- **Fines job** (`jobs/fines.py`) - nightly, works out overdue fines.
|
|
12
|
+
|
|
13
|
+
## Running locally
|
|
14
|
+
|
|
15
|
+
export DATABASE_URL=postgres://localhost/shelfline
|
|
16
|
+
export NOTIFY_WEBHOOK_URL=http://localhost:9000/hooks/ready
|
|
17
|
+
flask --app app.api run
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from flask import Flask, jsonify, request
|
|
2
|
+
|
|
3
|
+
from app import catalog, holds
|
|
4
|
+
|
|
5
|
+
app = Flask(__name__)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@app.get("/search")
|
|
9
|
+
def search():
|
|
10
|
+
q = request.args.get("q", "")
|
|
11
|
+
return jsonify(catalog.search(q))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@app.post("/holds")
|
|
15
|
+
def place_hold():
|
|
16
|
+
body = request.get_json()
|
|
17
|
+
result = holds.place_hold(body["patron_id"], body["title_id"])
|
|
18
|
+
return jsonify(result), 201
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@app.post("/checkins")
|
|
22
|
+
def check_in():
|
|
23
|
+
body = request.get_json()
|
|
24
|
+
holds.on_checkin(body["copy_id"])
|
|
25
|
+
return "", 204
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
import redis
|
|
4
|
+
import yaml
|
|
5
|
+
|
|
6
|
+
from app import db
|
|
7
|
+
|
|
8
|
+
_cfg = yaml.safe_load(open("config.yaml"))
|
|
9
|
+
_cache = redis.Redis.from_url(_cfg["redis"]["url"])
|
|
10
|
+
_TTL = _cfg["redis"]["search_ttl_seconds"]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def search(q):
|
|
14
|
+
key = f"search:{q.lower()}"
|
|
15
|
+
hit = _cache.get(key)
|
|
16
|
+
if hit:
|
|
17
|
+
return json.loads(hit)
|
|
18
|
+
rows = db.query(
|
|
19
|
+
"SELECT id, title, author FROM titles WHERE title ILIKE %s LIMIT 50",
|
|
20
|
+
(f"%{q}%",),
|
|
21
|
+
)
|
|
22
|
+
_cache.setex(key, _TTL, json.dumps(rows))
|
|
23
|
+
return rows
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
import os
|
|
3
|
+
|
|
4
|
+
import psycopg2
|
|
5
|
+
import psycopg2.extras
|
|
6
|
+
|
|
7
|
+
_conn = psycopg2.connect(os.environ["DATABASE_URL"])
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _cur():
|
|
11
|
+
return _conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def query(sql, params=()):
|
|
15
|
+
with _cur() as c:
|
|
16
|
+
c.execute(sql, params)
|
|
17
|
+
return c.fetchall()
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def one(sql, params=()):
|
|
21
|
+
rows = query(sql, params)
|
|
22
|
+
return rows[0] if rows else None
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def execute(sql, params=()):
|
|
26
|
+
with _cur() as c:
|
|
27
|
+
c.execute(sql, params)
|
|
28
|
+
_conn.commit()
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def insert(sql, params=()):
|
|
32
|
+
with _cur() as c:
|
|
33
|
+
c.execute(sql, params)
|
|
34
|
+
new_id = c.fetchone()["id"]
|
|
35
|
+
_conn.commit()
|
|
36
|
+
return new_id
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def today():
|
|
40
|
+
return datetime.date.today()
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import yaml
|
|
2
|
+
|
|
3
|
+
from app import db, notify
|
|
4
|
+
|
|
5
|
+
_cfg = yaml.safe_load(open("config.yaml"))
|
|
6
|
+
MAX_ACTIVE = _cfg["holds"]["max_active_per_patron"]
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class HoldRefused(Exception):
|
|
10
|
+
pass
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def place_hold(patron_id, title_id):
|
|
14
|
+
patron = db.one("SELECT id, card_expires FROM patrons WHERE id = %s", (patron_id,))
|
|
15
|
+
if patron is None or patron["card_expires"] < db.today():
|
|
16
|
+
raise HoldRefused("card missing or expired")
|
|
17
|
+
|
|
18
|
+
active = db.one(
|
|
19
|
+
"SELECT count(*) AS n FROM holds WHERE patron_id = %s AND status IN ('waiting', 'ready')",
|
|
20
|
+
(patron_id,),
|
|
21
|
+
)
|
|
22
|
+
if active["n"] >= MAX_ACTIVE:
|
|
23
|
+
raise HoldRefused("too many active holds")
|
|
24
|
+
|
|
25
|
+
hold_id = db.insert(
|
|
26
|
+
"INSERT INTO holds (patron_id, title_id, status, placed_at) VALUES (%s, %s, 'waiting', now()) RETURNING id",
|
|
27
|
+
(patron_id, title_id),
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
copy = db.one(
|
|
31
|
+
"SELECT id FROM copies WHERE title_id = %s AND status = 'on_shelf' LIMIT 1",
|
|
32
|
+
(title_id,),
|
|
33
|
+
)
|
|
34
|
+
if copy:
|
|
35
|
+
_make_ready(hold_id, copy["id"])
|
|
36
|
+
return {"hold_id": hold_id, "status": "ready"}
|
|
37
|
+
|
|
38
|
+
position = db.one(
|
|
39
|
+
"SELECT count(*) AS n FROM holds WHERE title_id = %s AND status = 'waiting' AND id <= %s",
|
|
40
|
+
(title_id, hold_id),
|
|
41
|
+
)
|
|
42
|
+
return {"hold_id": hold_id, "status": "waiting", "position": position["n"]}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def on_checkin(copy_id):
|
|
46
|
+
copy = db.one("SELECT id, title_id FROM copies WHERE id = %s", (copy_id,))
|
|
47
|
+
db.execute("UPDATE loans SET returned_at = now() WHERE copy_id = %s AND returned_at IS NULL", (copy_id,))
|
|
48
|
+
|
|
49
|
+
nxt = db.one(
|
|
50
|
+
"SELECT id FROM holds WHERE title_id = %s AND status = 'waiting' ORDER BY placed_at LIMIT 1",
|
|
51
|
+
(copy["title_id"],),
|
|
52
|
+
)
|
|
53
|
+
if nxt is None:
|
|
54
|
+
db.execute("UPDATE copies SET status = 'on_shelf' WHERE id = %s", (copy_id,))
|
|
55
|
+
return
|
|
56
|
+
_make_ready(nxt["id"], copy_id)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _make_ready(hold_id, copy_id):
|
|
60
|
+
db.execute("UPDATE copies SET status = 'held' WHERE id = %s", (copy_id,))
|
|
61
|
+
db.execute("UPDATE holds SET status = 'ready', copy_id = %s WHERE id = %s", (copy_id, hold_id))
|
|
62
|
+
notify.send_ready(hold_id)
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
import requests
|
|
4
|
+
import yaml
|
|
5
|
+
|
|
6
|
+
from app import db
|
|
7
|
+
|
|
8
|
+
_cfg = yaml.safe_load(open("config.yaml"))
|
|
9
|
+
log = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def send_ready(hold_id):
|
|
13
|
+
row = db.one(
|
|
14
|
+
"SELECT h.id, p.id AS patron_id, p.contact_pref, t.title "
|
|
15
|
+
"FROM holds h JOIN patrons p ON p.id = h.patron_id JOIN titles t ON t.id = h.title_id "
|
|
16
|
+
"WHERE h.id = %s",
|
|
17
|
+
(hold_id,),
|
|
18
|
+
)
|
|
19
|
+
payload = {
|
|
20
|
+
"event": "hold.ready",
|
|
21
|
+
"patron_id": row["patron_id"],
|
|
22
|
+
"contact_pref": row["contact_pref"],
|
|
23
|
+
"title": row["title"],
|
|
24
|
+
}
|
|
25
|
+
try:
|
|
26
|
+
requests.post(
|
|
27
|
+
_cfg["notify"]["webhook_url"],
|
|
28
|
+
json=payload,
|
|
29
|
+
timeout=_cfg["notify"]["timeout_seconds"],
|
|
30
|
+
)
|
|
31
|
+
except requests.RequestException:
|
|
32
|
+
log.warning("hold.ready webhook failed for hold %s", hold_id)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
database:
|
|
2
|
+
url: ${DATABASE_URL}
|
|
3
|
+
pool_size: 5
|
|
4
|
+
|
|
5
|
+
redis:
|
|
6
|
+
url: redis://cache.internal:6379/0
|
|
7
|
+
search_ttl_seconds: 300
|
|
8
|
+
|
|
9
|
+
notify:
|
|
10
|
+
webhook_url: ${NOTIFY_WEBHOOK_URL}
|
|
11
|
+
timeout_seconds: 5
|
|
12
|
+
|
|
13
|
+
holds:
|
|
14
|
+
max_active_per_patron: 10
|
|
15
|
+
|
|
16
|
+
fines:
|
|
17
|
+
daily_rate_pence: 20
|
|
18
|
+
cap_pence: 500
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""Nightly: charge a daily fine on every overdue loan, capped per loan."""
|
|
2
|
+
import yaml
|
|
3
|
+
|
|
4
|
+
from app import db
|
|
5
|
+
|
|
6
|
+
_cfg = yaml.safe_load(open("config.yaml"))
|
|
7
|
+
RATE = _cfg["fines"]["daily_rate_pence"]
|
|
8
|
+
CAP = _cfg["fines"]["cap_pence"]
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def run():
|
|
12
|
+
overdue = db.query(
|
|
13
|
+
"SELECT id, patron_id, due_date FROM loans WHERE returned_at IS NULL AND due_date < current_date"
|
|
14
|
+
)
|
|
15
|
+
for loan in overdue:
|
|
16
|
+
days = (db.today() - loan["due_date"]).days
|
|
17
|
+
amount = min(days * RATE, CAP)
|
|
18
|
+
db.execute(
|
|
19
|
+
"INSERT INTO fines (loan_id, patron_id, amount_pence) VALUES (%s, %s, %s) "
|
|
20
|
+
"ON CONFLICT (loan_id) DO UPDATE SET amount_pence = EXCLUDED.amount_pence",
|
|
21
|
+
(loan["id"], loan["patron_id"], amount),
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
if __name__ == "__main__":
|
|
26
|
+
run()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<!doctype html>
|
|
2
2
|
<html lang="en" data-theme-default="auto"><head><meta charset="utf-8">
|
|
3
|
-
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'sha256-
|
|
3
|
+
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'sha256-dOZxqp+jotySWDDWlSu2y160GhZpNuhUrNadh3tdhNM='; style-src 'unsafe-inline'; img-src data: blob:; base-uri 'none'; form-action 'none'">
|
|
4
4
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
5
5
|
<!-- One self-contained file: no external scripts, styles, fonts or requests. The policy above makes the browser refuse any. -->
|
|
6
6
|
<title>What a checkout request touches</title><meta name="generator" content="interactive-diagram (skilldrop)"></head>
|
|
@@ -102,7 +102,7 @@ aside table.facts{border-collapse:collapse;font-size:13px;margin:6px 0}aside tab
|
|
|
102
102
|
var DATA=JSON.parse(document.getElementById('diagram-data').textContent);var WD=DATA.words;
|
|
103
103
|
var svg=document.getElementById('map'),stage=document.getElementById('stage'),panel=document.getElementById('panel');
|
|
104
104
|
var root=document.documentElement;
|
|
105
|
-
var pref=localStorage.getItem('idg-theme')||root.getAttribute('data-theme-default');
|
|
105
|
+
var pref=null;try{pref=localStorage.getItem('idg-theme')}catch(e){}pref=pref||root.getAttribute('data-theme-default');
|
|
106
106
|
if(pref==='auto'||!pref){pref=window.matchMedia&&matchMedia('(prefers-color-scheme: dark)').matches?'dark':'light'}
|
|
107
107
|
root.setAttribute('data-theme',pref);
|
|
108
108
|
var W=+svg.getAttribute('width'),H=+svg.getAttribute('height');svg.removeAttribute('width');svg.removeAttribute('height');
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<!doctype html>
|
|
2
2
|
<html lang="en" data-theme-default="auto"><head><meta charset="utf-8">
|
|
3
|
-
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'sha256-
|
|
3
|
+
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'sha256-dOZxqp+jotySWDDWlSu2y160GhZpNuhUrNadh3tdhNM='; style-src 'unsafe-inline'; img-src data: blob:; base-uri 'none'; form-action 'none'">
|
|
4
4
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
5
5
|
<!-- One self-contained file: no external scripts, styles, fonts or requests. The policy above makes the browser refuse any. -->
|
|
6
6
|
<title>Where a slow checkout spent its time</title><meta name="generator" content="interactive-diagram (skilldrop)"></head>
|
|
@@ -102,7 +102,7 @@ aside table.facts{border-collapse:collapse;font-size:13px;margin:6px 0}aside tab
|
|
|
102
102
|
var DATA=JSON.parse(document.getElementById('diagram-data').textContent);var WD=DATA.words;
|
|
103
103
|
var svg=document.getElementById('map'),stage=document.getElementById('stage'),panel=document.getElementById('panel');
|
|
104
104
|
var root=document.documentElement;
|
|
105
|
-
var pref=localStorage.getItem('idg-theme')||root.getAttribute('data-theme-default');
|
|
105
|
+
var pref=null;try{pref=localStorage.getItem('idg-theme')}catch(e){}pref=pref||root.getAttribute('data-theme-default');
|
|
106
106
|
if(pref==='auto'||!pref){pref=window.matchMedia&&matchMedia('(prefers-color-scheme: dark)').matches?'dark':'light'}
|
|
107
107
|
root.setAttribute('data-theme',pref);
|
|
108
108
|
var W=+svg.getAttribute('width'),H=+svg.getAttribute('height');svg.removeAttribute('width');svg.removeAttribute('height');
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<!doctype html>
|
|
2
2
|
<html lang="en" data-theme-default="auto"><head><meta charset="utf-8">
|
|
3
|
-
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'sha256-
|
|
3
|
+
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'sha256-dOZxqp+jotySWDDWlSu2y160GhZpNuhUrNadh3tdhNM='; style-src 'unsafe-inline'; img-src data: blob:; base-uri 'none'; form-action 'none'">
|
|
4
4
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
5
5
|
<!-- One self-contained file: no external scripts, styles, fonts or requests. The policy above makes the browser refuse any. -->
|
|
6
6
|
<title>Card payments failing, 3 October</title><meta name="generator" content="interactive-diagram (skilldrop)"></head>
|
|
@@ -102,7 +102,7 @@ aside table.facts{border-collapse:collapse;font-size:13px;margin:6px 0}aside tab
|
|
|
102
102
|
var DATA=JSON.parse(document.getElementById('diagram-data').textContent);var WD=DATA.words;
|
|
103
103
|
var svg=document.getElementById('map'),stage=document.getElementById('stage'),panel=document.getElementById('panel');
|
|
104
104
|
var root=document.documentElement;
|
|
105
|
-
var pref=localStorage.getItem('idg-theme')||root.getAttribute('data-theme-default');
|
|
105
|
+
var pref=null;try{pref=localStorage.getItem('idg-theme')}catch(e){}pref=pref||root.getAttribute('data-theme-default');
|
|
106
106
|
if(pref==='auto'||!pref){pref=window.matchMedia&&matchMedia('(prefers-color-scheme: dark)').matches?'dark':'light'}
|
|
107
107
|
root.setAttribute('data-theme',pref);
|
|
108
108
|
var W=+svg.getAttribute('width'),H=+svg.getAttribute('height');svg.removeAttribute('width');svg.removeAttribute('height');
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<!doctype html>
|
|
2
2
|
<html lang="en" data-theme-default="auto"><head><meta charset="utf-8">
|
|
3
|
-
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'sha256-
|
|
3
|
+
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'sha256-dOZxqp+jotySWDDWlSu2y160GhZpNuhUrNadh3tdhNM='; style-src 'unsafe-inline'; img-src data: blob:; base-uri 'none'; form-action 'none'">
|
|
4
4
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
5
5
|
<!-- One self-contained file: no external scripts, styles, fonts or requests. The policy above makes the browser refuse any. -->
|
|
6
6
|
<title>How annual leave gets approved</title><meta name="generator" content="interactive-diagram (skilldrop)"></head>
|
|
@@ -102,7 +102,7 @@ aside table.facts{border-collapse:collapse;font-size:13px;margin:6px 0}aside tab
|
|
|
102
102
|
var DATA=JSON.parse(document.getElementById('diagram-data').textContent);var WD=DATA.words;
|
|
103
103
|
var svg=document.getElementById('map'),stage=document.getElementById('stage'),panel=document.getElementById('panel');
|
|
104
104
|
var root=document.documentElement;
|
|
105
|
-
var pref=localStorage.getItem('idg-theme')||root.getAttribute('data-theme-default');
|
|
105
|
+
var pref=null;try{pref=localStorage.getItem('idg-theme')}catch(e){}pref=pref||root.getAttribute('data-theme-default');
|
|
106
106
|
if(pref==='auto'||!pref){pref=window.matchMedia&&matchMedia('(prefers-color-scheme: dark)').matches?'dark':'light'}
|
|
107
107
|
root.setAttribute('data-theme',pref);
|
|
108
108
|
var W=+svg.getAttribute('width'),H=+svg.getAttribute('height');svg.removeAttribute('width');svg.removeAttribute('height');
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "interactive-diagram",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "Turn a codebase, a system description, a process or a database schema into one self-contained interactive HTML diagram (architecture, workflow, data flow, lifecycle, ERD, sequence, class, tree, timeline or waterfall) where every box and arrow cites the file and line or the words it came from, anything inferred is drawn dashed as an assumption, and each cited line is checked against the repository before the page is written. Laid out automatically, opens offline with no CDN, and exports SVG, PNG and Mermaid. Use when the user asks to \"visualize\", \"map\" or \"make an interactive diagram of\" a repo, a request path, a pipeline, an approval process, a set of tables, an incident's events or a slow trace, or wants a diagram they can click through and share as one file.",
|
|
5
5
|
"entrypoint": "SKILL.md",
|
|
6
6
|
"deps": {
|
|
@@ -2262,7 +2262,7 @@ JS = r"""
|
|
|
2262
2262
|
var DATA=JSON.parse(document.getElementById('diagram-data').textContent);var WD=DATA.words;
|
|
2263
2263
|
var svg=document.getElementById('map'),stage=document.getElementById('stage'),panel=document.getElementById('panel');
|
|
2264
2264
|
var root=document.documentElement;
|
|
2265
|
-
var pref=localStorage.getItem('idg-theme')||root.getAttribute('data-theme-default');
|
|
2265
|
+
var pref=null;try{pref=localStorage.getItem('idg-theme')}catch(e){}pref=pref||root.getAttribute('data-theme-default');
|
|
2266
2266
|
if(pref==='auto'||!pref){pref=window.matchMedia&&matchMedia('(prefers-color-scheme: dark)').matches?'dark':'light'}
|
|
2267
2267
|
root.setAttribute('data-theme',pref);
|
|
2268
2268
|
var W=+svg.getAttribute('width'),H=+svg.getAttribute('height');svg.removeAttribute('width');svg.removeAttribute('height');
|
|
@@ -2591,7 +2591,7 @@ def main(argv=None):
|
|
|
2591
2591
|
else:
|
|
2592
2592
|
item, link = {"sequence": ("participants", "messages"), "tree": ("items", "links"), "timeline": ("events", "links"),
|
|
2593
2593
|
"waterfall": ("spans", "parent links"), "class": ("types", "relations")}.get(spec["type"], ("nodes", "edges"))
|
|
2594
|
-
print("
|
|
2594
|
+
print("Lead the reply with this line, then the takeaway:\n" + receipt["reply_first_line"] + "\n")
|
|
2595
2595
|
print("For the end of the reply: %d of %d file citations checked; %d drawn as assumptions. (%s, %d KB: %d %s, %d %s.)" % (
|
|
2596
2596
|
receipt["verified"], receipt["file_citations"], receipt["assumed"], a.out, receipt["bytes"] // 1024,
|
|
2597
2597
|
receipt["nodes"], item, receipt["edges"], link))
|