claude-code-arcane 1.3.0 → 2.0.0
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/CHANGELOG.md +45 -0
- package/README.md +5 -4
- package/agents/ecommerce/ecommerce-architect.md +60 -0
- package/dist/cli.js +2 -0
- package/docs/SKILLS-CATALOG.md +25 -2
- package/docs/USER-GUIDE.md +2 -2
- package/docs/coding-standards.md +26 -0
- package/package.json +2 -1
- package/profiles/ecommerce.yaml +48 -0
- package/profiles/job-hunt.yaml +12 -2
- package/rules/drafter-reviewer.md +39 -0
- package/skills/cart-checkout/SKILL.md +85 -0
- package/skills/cart-checkout/references/checkout-flow-spec.md +50 -0
- package/skills/cold-outreach/SKILL.md +10 -10
- package/skills/cold-outreach/references/recruiter-playbook.md +5 -5
- package/skills/commerce-analytics/SKILL.md +85 -0
- package/skills/commerce-analytics/references/ga4-ecommerce-events.md +63 -0
- package/skills/commerce-analytics/scripts/ga4_ecommerce_validate.py +134 -0
- package/skills/commerce-data-model/SKILL.md +84 -0
- package/skills/commerce-data-model/references/data-model-spec.md +79 -0
- package/skills/cover-letter/SKILL.md +8 -8
- package/skills/cv-ats-export/SKILL.md +19 -6
- package/skills/cv-ats-export/scripts/cv_export.py +49 -4
- package/skills/cv-ats-export/scripts/verify_pdf.py +90 -0
- package/skills/cv-tailor/SKILL.md +7 -7
- package/skills/cv-tailor/references/ats-keywords.md +9 -9
- package/skills/interview-prep/SKILL.md +7 -7
- package/skills/interview-prep/references/star-framework.md +10 -10
- package/skills/inventory-stock/SKILL.md +86 -0
- package/skills/inventory-stock/scripts/restock_forecast.py +173 -0
- package/skills/job-aplicar/SKILL.md +88 -0
- package/skills/job-hunt/SKILL.md +20 -13
- package/skills/job-hunt/references/templates/Aplicacion.md +6 -2
- package/skills/job-hunt/references/templates/CV Custom.md +1 -1
- package/skills/job-hunt/references/templates/Contacto.md +1 -1
- package/skills/job-hunt/references/templates/Dashboard.md +19 -15
- package/skills/job-hunt/references/templates/Perfil.md +2 -2
- package/skills/job-outcome/SKILL.md +55 -0
- package/skills/job-scrape/SKILL.md +95 -0
- package/skills/job-scrape/references/dedup-playbook.md +25 -0
- package/skills/job-scrape/references/getonbrd-cli.md +62 -0
- package/skills/job-scrape/references/getonbrd-urls.md +37 -0
- package/skills/job-scrape/references/linkedin-cli.md +64 -0
- package/skills/job-scrape/references/linkedin-urls.md +42 -0
- package/skills/job-scrape/references/seen_jobs.schema.json +68 -0
- package/skills/job-scrape/scripts/getonbrd_search.py +501 -0
- package/skills/job-scrape/scripts/linkedin_search.py +428 -0
- package/skills/job-search/SKILL.md +22 -19
- package/skills/job-search/references/scoring-rubric.md +8 -8
- package/skills/job-upskill/SKILL.md +58 -0
- package/skills/linkedin-optimize/SKILL.md +5 -5
- package/skills/master-profile/SKILL.md +17 -17
- package/skills/merchant-feed/SKILL.md +85 -0
- package/skills/merchant-feed/references/feed-attribute-map.md +47 -0
- package/skills/merchant-feed/scripts/feed_validate.py +140 -0
- package/skills/network-map/SKILL.md +7 -7
- package/skills/order-lifecycle/SKILL.md +86 -0
- package/skills/order-lifecycle/references/order-state-machine.md +53 -0
- package/skills/payments-architecture/SKILL.md +80 -0
- package/skills/personal-brand/SKILL.md +10 -10
- package/skills/personal-brand/references/post-pillars.md +14 -14
- package/skills/portfolio-site/SKILL.md +11 -11
- package/skills/product-listing/SKILL.md +84 -0
- package/skills/promotions-discounts/SKILL.md +80 -0
- package/skills/shipping-tax/SKILL.md +85 -0
- package/skills/storefront-architecture/SKILL.md +80 -0
- package/skills/subscription-billing/SKILL.md +81 -0
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: commerce-analytics
|
|
3
|
+
description: "Define and audit e-commerce analytics — conversion rate, AOV, CLV, cohort/funnel metrics, and the standard GA4 ecommerce event spec (view_item, add_to_cart, begin_checkout, purchase). Includes a dataLayer event validator. Triggers: defining commerce KPIs, instrumenting GA4 ecommerce events, auditing tracking."
|
|
4
|
+
argument-hint: "[kpis | events | audit]"
|
|
5
|
+
category: "ecommerce"
|
|
6
|
+
user-invocable: true
|
|
7
|
+
allowed-tools: Read, Glob, Grep, Bash
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Commerce Analytics
|
|
11
|
+
|
|
12
|
+
You cannot optimize what you mis-measure. Define the KPIs that actually move a store and instrument the standard ecommerce event spec correctly — platform-agnostic, with a validator for GA4 dataLayer events.
|
|
13
|
+
|
|
14
|
+
## Before Starting
|
|
15
|
+
|
|
16
|
+
Gather: current tracking (GA4/GTM/other), business model (one-time vs subscription), key conversion actions, and goal (define KPIs, design/instrument events, or audit existing tracking).
|
|
17
|
+
|
|
18
|
+
## How This Skill Works
|
|
19
|
+
|
|
20
|
+
| Mode | Trigger | Output |
|
|
21
|
+
|------|---------|--------|
|
|
22
|
+
| **KPIs** | `kpis` | KPI framework + dashboard spec |
|
|
23
|
+
| **Events** | `events` | Ecommerce event taxonomy + dataLayer spec |
|
|
24
|
+
| **Audit** | `audit` | Tracking gap + data-quality review |
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Phase 1 — Core KPIs
|
|
29
|
+
|
|
30
|
+
| Metric | Formula | Watch for |
|
|
31
|
+
|--------|---------|-----------|
|
|
32
|
+
| **Conversion rate** | orders / sessions | Segment by device/channel |
|
|
33
|
+
| **AOV** | revenue / orders | Moves with promos/bundles |
|
|
34
|
+
| **CLV** | avg order value × frequency × lifespan | The number that justifies CAC |
|
|
35
|
+
| **Cart abandonment** | 1 − (checkouts / carts) | Diagnoses checkout friction |
|
|
36
|
+
| **Repeat rate** | repeat customers / total | Retention health |
|
|
37
|
+
| **Sell-through** | units sold / units received | Feeds `inventory-stock` forecasts |
|
|
38
|
+
|
|
39
|
+
**Rule:** Pair every acquisition metric with a margin metric — revenue growth on negative contribution margin is a trap.
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## Phase 2 — The Ecommerce Event Spec
|
|
44
|
+
|
|
45
|
+
The standard GA4 ecommerce funnel events, each carrying `currency` + an `items[]` array:
|
|
46
|
+
|
|
47
|
+
`view_item_list → view_item → add_to_cart → begin_checkout → add_shipping_info → add_payment_info → purchase` (plus `remove_from_cart`, `refund`).
|
|
48
|
+
|
|
49
|
+
Each `items[]` entry: `item_id`, `item_name`, `price`, `quantity` (+ optional `item_brand`, `item_category`, `item_variant`). `purchase` also needs `transaction_id` and `value`.
|
|
50
|
+
|
|
51
|
+
Validate a captured dataLayer against this spec:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
python skills/commerce-analytics/scripts/ga4_ecommerce_validate.py # demo events
|
|
55
|
+
python skills/commerce-analytics/scripts/ga4_ecommerce_validate.py events.json
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
It reports PASS/FAIL per event with the missing required fields. For the full taxonomy and parameter reference, see [references/ga4-ecommerce-events.md](references/ga4-ecommerce-events.md).
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## Phase 3 — Audit & Data Quality
|
|
63
|
+
|
|
64
|
+
Common faults: missing `transaction_id` (duplicate purchases), `value` excluding/including tax inconsistently, `items[]` absent on purchase, double-firing (GTM + hardcoded). Cross-check store revenue vs GA4 revenue — variance > 5% means a tracking bug.
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Output Artifacts
|
|
69
|
+
|
|
70
|
+
| Request | Output |
|
|
71
|
+
|---------|--------|
|
|
72
|
+
| KPIs | KPI framework + dashboard layout |
|
|
73
|
+
| Events | Event taxonomy + dataLayer spec |
|
|
74
|
+
| Audit | Gap analysis + data-quality scorecard |
|
|
75
|
+
|
|
76
|
+
Commerce tracking is **COMPLIANT** when the full funnel fires with valid `items[]` and `currency`, `purchase` carries a unique `transaction_id`, revenue reconciles within 5%, and KPIs pair growth with margin.
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## Related Skills
|
|
81
|
+
|
|
82
|
+
- **analytics-tracking**: general GA4/GTM setup this builds on for SaaS-style funnels.
|
|
83
|
+
- **inventory-stock**: sell-through metrics feed restock forecasting.
|
|
84
|
+
- **commerce-data-model**: item taxonomy maps to `items[]` parameters.
|
|
85
|
+
- **ab-test-setup** / **campaign-analytics**: experimentation and campaign measurement.
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# GA4 Ecommerce Event Taxonomy & Parameter Reference
|
|
2
|
+
|
|
3
|
+
The standard funnel. Fire these with a consistent `items[]` shape so reports, funnels, and audiences work. The `ga4_ecommerce_validate.py` script checks a captured dataLayer against this spec.
|
|
4
|
+
|
|
5
|
+
## The funnel
|
|
6
|
+
```
|
|
7
|
+
view_item_list → view_item → add_to_cart → view_cart → begin_checkout
|
|
8
|
+
→ add_shipping_info → add_payment_info → purchase
|
|
9
|
+
```
|
|
10
|
+
Plus: `select_item`, `remove_from_cart`, `refund`.
|
|
11
|
+
|
|
12
|
+
## Required parameters per event
|
|
13
|
+
| Event | Required (beyond items[]) |
|
|
14
|
+
|-------|---------------------------|
|
|
15
|
+
| view_item | currency |
|
|
16
|
+
| add_to_cart / remove_from_cart / view_cart | currency |
|
|
17
|
+
| begin_checkout | currency |
|
|
18
|
+
| add_shipping_info | currency |
|
|
19
|
+
| add_payment_info | currency |
|
|
20
|
+
| purchase | currency, value, transaction_id |
|
|
21
|
+
| refund | currency, transaction_id |
|
|
22
|
+
| view_item_list / select_item | (item_list_id / item_list_name recommended) |
|
|
23
|
+
|
|
24
|
+
## items[] object
|
|
25
|
+
| Field | Req? | Notes |
|
|
26
|
+
|-------|------|-------|
|
|
27
|
+
| item_id | yes | Map to SKU |
|
|
28
|
+
| item_name | yes | Product/variant name |
|
|
29
|
+
| price | recommended | Unit price (number) |
|
|
30
|
+
| quantity | recommended | Defaults to 1 |
|
|
31
|
+
| item_brand | optional | |
|
|
32
|
+
| item_category | optional | Up to item_category5 |
|
|
33
|
+
| item_variant | optional | e.g. "Red / M" |
|
|
34
|
+
| discount | optional | Per-unit discount |
|
|
35
|
+
| index | optional | Position in list |
|
|
36
|
+
|
|
37
|
+
## Worked example — purchase
|
|
38
|
+
```json
|
|
39
|
+
{
|
|
40
|
+
"event": "purchase",
|
|
41
|
+
"ecommerce": {
|
|
42
|
+
"currency": "USD",
|
|
43
|
+
"value": 42.00,
|
|
44
|
+
"transaction_id": "T-1001",
|
|
45
|
+
"tax": 3.50,
|
|
46
|
+
"shipping": 5.00,
|
|
47
|
+
"items": [
|
|
48
|
+
{ "item_id": "ACME-TEE-RED-M", "item_name": "Acme Cotton Tee",
|
|
49
|
+
"item_variant": "Red / M", "price": 21.00, "quantity": 2 }
|
|
50
|
+
]
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Common faults (audit checklist)
|
|
56
|
+
- [ ] `purchase` missing `transaction_id` → duplicate purchases on refresh
|
|
57
|
+
- [ ] `value` inconsistent about tax/shipping inclusion (pick one, document it)
|
|
58
|
+
- [ ] `items[]` absent or empty on funnel events
|
|
59
|
+
- [ ] Double-firing (GTM tag + hardcoded gtag)
|
|
60
|
+
- [ ] Currency missing → revenue not attributed
|
|
61
|
+
- [ ] Store revenue vs GA4 revenue variance > 5% → tracking bug
|
|
62
|
+
|
|
63
|
+
Verdict: **COMPLIANT** when the full funnel fires with valid `items[]` + `currency`, `purchase` has a unique `transaction_id`, and revenue reconciles within 5%.
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ga4_ecommerce_validate.py — Validate a captured dataLayer against the GA4
|
|
3
|
+
standard ecommerce event spec.
|
|
4
|
+
|
|
5
|
+
Checks each event for: a known ecommerce event name, required top-level
|
|
6
|
+
parameters (currency/value/transaction_id where applicable), a non-empty
|
|
7
|
+
items[] array, and required per-item fields. No external dependencies.
|
|
8
|
+
|
|
9
|
+
Usage:
|
|
10
|
+
python ga4_ecommerce_validate.py # built-in demo events
|
|
11
|
+
python ga4_ecommerce_validate.py events.json # your captured dataLayer
|
|
12
|
+
|
|
13
|
+
Input JSON: either a single event object or an array of event objects, each:
|
|
14
|
+
{"event": "purchase", "ecommerce": {"currency": "USD", "value": 42.0,
|
|
15
|
+
"transaction_id": "T123", "items": [{"item_id": "SKU1",
|
|
16
|
+
"item_name": "Tee", "price": 21.0, "quantity": 2}, ...]}}
|
|
17
|
+
|
|
18
|
+
Some setups put params at the top level instead of under "ecommerce"; both
|
|
19
|
+
shapes are accepted.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
import json
|
|
23
|
+
import sys
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# event name -> required top-level (ecommerce) params beyond items[]
|
|
27
|
+
EVENT_REQS = {
|
|
28
|
+
"view_item_list": [],
|
|
29
|
+
"view_item": ["currency"],
|
|
30
|
+
"select_item": [],
|
|
31
|
+
"add_to_cart": ["currency"],
|
|
32
|
+
"remove_from_cart": ["currency"],
|
|
33
|
+
"view_cart": ["currency"],
|
|
34
|
+
"begin_checkout": ["currency"],
|
|
35
|
+
"add_shipping_info": ["currency"],
|
|
36
|
+
"add_payment_info": ["currency"],
|
|
37
|
+
"purchase": ["currency", "value", "transaction_id"],
|
|
38
|
+
"refund": ["currency", "transaction_id"],
|
|
39
|
+
}
|
|
40
|
+
EVENTS_REQUIRING_ITEMS = set(EVENT_REQS) - {"refund"}
|
|
41
|
+
ITEM_REQUIRED = ["item_id", "item_name"] # GA4 needs id or name; we require both for quality
|
|
42
|
+
ITEM_RECOMMENDED = ["price", "quantity"]
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
DEMO_EVENTS = [
|
|
46
|
+
{"event": "view_item", "ecommerce": {"currency": "USD",
|
|
47
|
+
"items": [{"item_id": "ACME-TEE", "item_name": "Acme Tee", "price": 21.0}]}},
|
|
48
|
+
{"event": "add_to_cart", "ecommerce": {"currency": "USD",
|
|
49
|
+
"items": [{"item_id": "ACME-TEE", "item_name": "Acme Tee", "price": 21.0, "quantity": 2}]}},
|
|
50
|
+
{"event": "purchase", "ecommerce": {"currency": "USD", "value": 42.0, "transaction_id": "T-1001",
|
|
51
|
+
"items": [{"item_id": "ACME-TEE", "item_name": "Acme Tee", "price": 21.0, "quantity": 2}]}},
|
|
52
|
+
# intentionally broken: missing transaction_id + value, item missing name
|
|
53
|
+
{"event": "purchase", "ecommerce": {"currency": "USD",
|
|
54
|
+
"items": [{"item_id": "ACME-MUG", "price": 9.0}]}},
|
|
55
|
+
{"event": "add_to_cart", "ecommerce": { # missing currency + empty items
|
|
56
|
+
"items": []}},
|
|
57
|
+
]
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def get_ecom(event):
|
|
61
|
+
"""Return the param dict — under 'ecommerce' or at the top level."""
|
|
62
|
+
if isinstance(event.get("ecommerce"), dict):
|
|
63
|
+
return event["ecommerce"]
|
|
64
|
+
return {k: v for k, v in event.items() if k != "event"}
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def validate_event(event, idx):
|
|
68
|
+
errors = []
|
|
69
|
+
name = event.get("event")
|
|
70
|
+
if not name:
|
|
71
|
+
return [f"event[{idx}]: missing 'event' name"], None
|
|
72
|
+
if name not in EVENT_REQS:
|
|
73
|
+
return [f"event[{idx}] '{name}': not a recognized GA4 ecommerce event"], name
|
|
74
|
+
|
|
75
|
+
ecom = get_ecom(event)
|
|
76
|
+
|
|
77
|
+
for param in EVENT_REQS[name]:
|
|
78
|
+
if param not in ecom or ecom[param] in (None, ""):
|
|
79
|
+
errors.append(f"missing required param '{param}'")
|
|
80
|
+
|
|
81
|
+
if name in EVENTS_REQUIRING_ITEMS:
|
|
82
|
+
items = ecom.get("items")
|
|
83
|
+
if not isinstance(items, list) or not items:
|
|
84
|
+
errors.append("missing or empty 'items[]'")
|
|
85
|
+
else:
|
|
86
|
+
for i, it in enumerate(items):
|
|
87
|
+
missing = [f for f in ITEM_REQUIRED if not it.get(f)]
|
|
88
|
+
if missing:
|
|
89
|
+
errors.append(f"items[{i}]: missing {', '.join(missing)}")
|
|
90
|
+
rec_missing = [f for f in ITEM_RECOMMENDED if it.get(f) in (None, "")]
|
|
91
|
+
if rec_missing:
|
|
92
|
+
errors.append(f"items[{i}]: (recommended) missing {', '.join(rec_missing)}")
|
|
93
|
+
return errors, name
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def main():
|
|
97
|
+
if len(sys.argv) > 1:
|
|
98
|
+
path = sys.argv[1]
|
|
99
|
+
try:
|
|
100
|
+
with open(path, encoding="utf-8") as f:
|
|
101
|
+
data = json.load(f)
|
|
102
|
+
except (OSError, json.JSONDecodeError) as e:
|
|
103
|
+
print(f"[ERROR] cannot read {path}: {e}")
|
|
104
|
+
sys.exit(1)
|
|
105
|
+
else:
|
|
106
|
+
print("[INFO] No JSON given - using built-in demo events.\n")
|
|
107
|
+
data = DEMO_EVENTS
|
|
108
|
+
|
|
109
|
+
events = data if isinstance(data, list) else [data]
|
|
110
|
+
|
|
111
|
+
total_fail = 0
|
|
112
|
+
for idx, event in enumerate(events):
|
|
113
|
+
errors, name = validate_event(event, idx)
|
|
114
|
+
hard = [e for e in errors if "(recommended)" not in e]
|
|
115
|
+
if hard:
|
|
116
|
+
total_fail += 1
|
|
117
|
+
print(f"FAIL [{idx}] {name or '?'}")
|
|
118
|
+
for e in errors:
|
|
119
|
+
print(f" - {e}")
|
|
120
|
+
else:
|
|
121
|
+
warn = [e for e in errors if "(recommended)" in e]
|
|
122
|
+
print(f"PASS [{idx}] {name}")
|
|
123
|
+
for w in warn:
|
|
124
|
+
print(f" ~ {w}")
|
|
125
|
+
|
|
126
|
+
print()
|
|
127
|
+
passed = len(events) - total_fail
|
|
128
|
+
verdict = "COMPLIANT" if total_fail == 0 else "NON-COMPLIANT"
|
|
129
|
+
print(f"{verdict}: {passed}/{len(events)} events passed.")
|
|
130
|
+
sys.exit(0 if total_fail == 0 else 1)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
if __name__ == "__main__":
|
|
134
|
+
main()
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: commerce-data-model
|
|
3
|
+
description: "Design and audit the core e-commerce domain model — products, variants, SKUs, options, collections, attributes, taxonomy, faceted search and pagination. Language- and platform-agnostic. Triggers: modeling a catalog, reviewing an existing product schema, designing filtering/search."
|
|
4
|
+
argument-hint: "[model | audit | taxonomy]"
|
|
5
|
+
category: "ecommerce"
|
|
6
|
+
user-invocable: true
|
|
7
|
+
allowed-tools: Read, Glob, Grep, Write, Edit
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Commerce Data Model
|
|
11
|
+
|
|
12
|
+
Design the backbone of any storefront: a product catalog model that survives variants, bundles, localization, and faceted search — independent of language, framework, or database.
|
|
13
|
+
|
|
14
|
+
> **Note:** This skill may create or modify files in your project (a model spec or schema doc). It will ask before writing.
|
|
15
|
+
|
|
16
|
+
## Before Starting
|
|
17
|
+
|
|
18
|
+
Gather: business context (physical/digital/services, single vs multi-vendor, B2C/B2B), scale (SKU count, variant explosion, locales/currencies), and goal (model from scratch, audit an existing schema, or design taxonomy + filtering).
|
|
19
|
+
|
|
20
|
+
## How This Skill Works
|
|
21
|
+
|
|
22
|
+
| Mode | Trigger | Output |
|
|
23
|
+
|------|---------|--------|
|
|
24
|
+
| **Model** | `model` | Entity model spec: Product, Variant, SKU, Option, Collection, Attribute |
|
|
25
|
+
| **Audit** | `audit` | Gap analysis of an existing schema against commerce best practices |
|
|
26
|
+
| **Taxonomy** | `taxonomy` | Category tree, attribute schema, faceted-search and pagination design |
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Phase 1 — Core Entities
|
|
31
|
+
|
|
32
|
+
The canonical separation that prevents 90% of catalog pain:
|
|
33
|
+
|
|
34
|
+
| Entity | Is | Is NOT | Key fields |
|
|
35
|
+
|--------|----|--------|-----------|
|
|
36
|
+
| **Product** | The marketing/SEO unit ("Acme Tee") | A buyable thing | title, description, slug, status, brand |
|
|
37
|
+
| **Variant** | A buyable combination ("Tee / Red / M") | A category | option values, price, weight, barcode |
|
|
38
|
+
| **SKU** | The stock-keeping identifier | A variant's price | code, variant_id |
|
|
39
|
+
| **Option** | An axis of variation (Color, Size) | A value | name, position |
|
|
40
|
+
| **Collection** | A merchandising grouping | A taxonomy node | title, rules (manual/automatic) |
|
|
41
|
+
|
|
42
|
+
**Rule:** Price, inventory, and barcode live on the **Variant**, never the Product. A product with no options still has exactly one variant.
|
|
43
|
+
|
|
44
|
+
For the full field-level spec, identifiers (GTIN/MPN), bundles/kits, and digital vs physical modeling, see [references/data-model-spec.md](references/data-model-spec.md).
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## Phase 2 — Attributes & Taxonomy
|
|
49
|
+
|
|
50
|
+
- **Taxonomy** (category tree): hierarchical, navigational, one primary path per product. Stable URLs.
|
|
51
|
+
- **Collections**: many-to-many merchandising overlays (manual or rule-based). A product lives in one category branch but many collections.
|
|
52
|
+
- **Attributes**: typed key-values (`material: cotton`, `sleeve: short`). Drive facets. Define a controlled vocabulary — free-text attributes break filtering.
|
|
53
|
+
|
|
54
|
+
**Attribute typing:** `text | enum | number | boolean | measurement`. Enums must reference a value list. Measurements carry a unit.
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## Phase 3 — Faceted Search & Pagination
|
|
59
|
+
|
|
60
|
+
- Facets come from `enum`/`boolean`/`measurement` attributes, not free text.
|
|
61
|
+
- Multi-select within a facet = OR; across facets = AND.
|
|
62
|
+
- Always show counts; never show zero-result facet values.
|
|
63
|
+
- **Pagination:** prefer cursor/keyset over offset for deep catalogs (offset degrades and double-counts under writes). Cap page size.
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## Output Artifacts
|
|
68
|
+
|
|
69
|
+
| Request | Output |
|
|
70
|
+
|---------|--------|
|
|
71
|
+
| Model from scratch | Entity spec + ER sketch + identifier strategy |
|
|
72
|
+
| Audit schema | Verdict per entity: COMPLIANT / CONCERNS, with fixes |
|
|
73
|
+
| Design taxonomy | Category tree + attribute schema + facet plan |
|
|
74
|
+
|
|
75
|
+
A schema is **COMPLIANT** when price/inventory sit on variants, every product has ≥1 variant, attributes are typed against vocabularies, and URLs are stable.
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## Related Skills
|
|
80
|
+
|
|
81
|
+
- **storefront-architecture**: consumes this model to design PLP/PDP pages. Next step after modeling.
|
|
82
|
+
- **merchant-feed**: maps these entities to Google/Meta feed attributes.
|
|
83
|
+
- **inventory-stock**: owns stock levels that hang off the SKU/variant.
|
|
84
|
+
- **order-lifecycle**: references variants as order line items.
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# Commerce Data Model — Field-Level Spec & Worked Example
|
|
2
|
+
|
|
3
|
+
A language- and database-agnostic reference for the core catalog entities. Use it as the template for a model spec.
|
|
4
|
+
|
|
5
|
+
## Entities & Fields
|
|
6
|
+
|
|
7
|
+
### Product (marketing/SEO unit)
|
|
8
|
+
| Field | Type | Notes |
|
|
9
|
+
|-------|------|-------|
|
|
10
|
+
| id | id | Internal key |
|
|
11
|
+
| title | text | Display name |
|
|
12
|
+
| slug | text | Stable URL segment, unique |
|
|
13
|
+
| description | rich text | Long copy |
|
|
14
|
+
| brand | text | Brand/manufacturer |
|
|
15
|
+
| status | enum | draft / active / archived |
|
|
16
|
+
| options | Option[] | Axes of variation (0..n) |
|
|
17
|
+
| variants | Variant[] | ≥1 always |
|
|
18
|
+
| collections | Collection[] | Many-to-many |
|
|
19
|
+
| primary_category | Category | One navigational path |
|
|
20
|
+
| attributes | Attribute[] | Typed key-values |
|
|
21
|
+
|
|
22
|
+
### Variant (buyable unit)
|
|
23
|
+
| Field | Type | Notes |
|
|
24
|
+
|-------|------|-------|
|
|
25
|
+
| id | id | |
|
|
26
|
+
| sku | text | Stock-keeping code, unique |
|
|
27
|
+
| option_values | map | e.g. {Color: Red, Size: M} |
|
|
28
|
+
| price | integer (minor units) | Never a float |
|
|
29
|
+
| compare_at_price | integer | For showing discounts |
|
|
30
|
+
| barcode | text | GTIN/UPC/EAN |
|
|
31
|
+
| weight / dimensions | number + unit | For shipping/carrier rates |
|
|
32
|
+
| requires_shipping | bool | False for digital |
|
|
33
|
+
|
|
34
|
+
### Option / Option Value
|
|
35
|
+
`Option { name, position }` → `OptionValue { option_id, value, position }`. Example: Option "Size" → values S, M, L.
|
|
36
|
+
|
|
37
|
+
### Collection (merchandising)
|
|
38
|
+
`Collection { title, slug, type: manual|automatic, rules? }`. Automatic collections select by attribute rules (e.g. `tag = summer`).
|
|
39
|
+
|
|
40
|
+
### Attribute (faceting)
|
|
41
|
+
`Attribute { key, type: text|enum|number|boolean|measurement, value, unit? }`. Enum attributes reference a controlled value list.
|
|
42
|
+
|
|
43
|
+
## Identifiers
|
|
44
|
+
- **SKU** — your internal stock identifier (required, unique).
|
|
45
|
+
- **GTIN** (UPC/EAN/ISBN) — global trade identifier; required for marketplaces/feeds where one exists.
|
|
46
|
+
- **MPN** — manufacturer part number; fallback when no GTIN.
|
|
47
|
+
|
|
48
|
+
## Bundles / Kits
|
|
49
|
+
Model as a Product whose variant references component variants with quantities. Decide inventory rule: track by components (decrement each) vs by kit (own stock).
|
|
50
|
+
|
|
51
|
+
## Worked Example — "Acme Tee"
|
|
52
|
+
|
|
53
|
+
```yaml
|
|
54
|
+
product:
|
|
55
|
+
title: "Acme Cotton Tee"
|
|
56
|
+
slug: "acme-cotton-tee"
|
|
57
|
+
brand: "Acme"
|
|
58
|
+
status: active
|
|
59
|
+
options:
|
|
60
|
+
- { name: Color, values: [Red, Blue] }
|
|
61
|
+
- { name: Size, values: [S, M, L] }
|
|
62
|
+
attributes:
|
|
63
|
+
- { key: material, type: enum, value: cotton }
|
|
64
|
+
- { key: sleeve, type: enum, value: short }
|
|
65
|
+
variants:
|
|
66
|
+
- sku: ACME-TEE-RED-M
|
|
67
|
+
option_values: { Color: Red, Size: M }
|
|
68
|
+
price: 2100 # 21.00 in minor units
|
|
69
|
+
barcode: "0123456789012"
|
|
70
|
+
weight: { value: 180, unit: g }
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Checklist (verdict: COMPLIANT when all true)
|
|
74
|
+
- [ ] Price + inventory + barcode live on the variant, not the product
|
|
75
|
+
- [ ] Every product has ≥1 variant (even with no options)
|
|
76
|
+
- [ ] Attributes are typed and enums reference a vocabulary
|
|
77
|
+
- [ ] Primary category is single; collections are many-to-many
|
|
78
|
+
- [ ] Money stored as integer minor units
|
|
79
|
+
- [ ] Slugs/URLs stable and unique
|
|
@@ -7,9 +7,9 @@ user-invocable: true
|
|
|
7
7
|
allowed-tools: Read, Glob, Grep, Write, Edit, WebFetch
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
-
# Cover Letter
|
|
10
|
+
# Cover Letter - Mensaje de aplicación de élite
|
|
11
11
|
|
|
12
|
-
Escribís cover letters y mensajes de aplicación **concisos, seguros y custom**
|
|
12
|
+
Escribís cover letters y mensajes de aplicación **concisos, seguros y custom** - que suenan a una persona que entendió el rol y la empresa, no a una plantilla. El objetivo: que el lector quiera leer el CV.
|
|
13
13
|
|
|
14
14
|
## Inputs
|
|
15
15
|
|
|
@@ -20,19 +20,19 @@ Escribís cover letters y mensajes de aplicación **concisos, seguros y custom**
|
|
|
20
20
|
|
|
21
21
|
## Principios
|
|
22
22
|
|
|
23
|
-
- **Una página máximo; idealmente 150
|
|
23
|
+
- **Una página máximo; idealmente 150-250 palabras** para mensajes, 250-350 para carta formal.
|
|
24
24
|
- **El primer renglón gana o pierde.** Nada de "I am writing to apply for…". Abrí con un gancho específico: un logro relevante, un punto de conexión con la empresa, o por qué este rol te mueve.
|
|
25
|
-
- **Mostrá fit, no historia.** 1
|
|
25
|
+
- **Mostrá fit, no historia.** 1-2 logros que mapean directo a lo que piden, con número.
|
|
26
26
|
- **Específico de la empresa.** Una frase que demuestre que investigaste (producto, valor, problema que resuelven). Si la podés pegar en otra aplicación, no sirve.
|
|
27
27
|
- **Confiado, no arrogante; cálido, no servil.** Ni "would be honored", ni "I'm the best candidate".
|
|
28
28
|
- **CTA simple al final.** Disponibilidad / ganas de charlar, sin rogar.
|
|
29
29
|
|
|
30
30
|
## Estructura (carta formal)
|
|
31
31
|
|
|
32
|
-
1. **Hook**
|
|
33
|
-
2. **Fit**
|
|
34
|
-
3. **Empresa**
|
|
35
|
-
4. **Cierre**
|
|
32
|
+
1. **Hook** - gancho específico (logro o conexión real).
|
|
33
|
+
2. **Fit** - 1-2 logros mapeados a must-haves del JD, con métrica.
|
|
34
|
+
3. **Empresa** - por qué *esta* empresa/rol (research real).
|
|
35
|
+
4. **Cierre** - CTA breve + gracias.
|
|
36
36
|
|
|
37
37
|
## Estructura (mensaje corto / LinkedIn / email)
|
|
38
38
|
|
|
@@ -1,22 +1,23 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: cv-ats-export
|
|
3
|
-
description: "Export a Markdown CV to an ATS-compliant PDF (single column, selectable text, A4) using headless Chrome/Edge
|
|
3
|
+
description: "Export a Markdown CV to an ATS-compliant PDF (single column, selectable text, A4) using headless Chrome/Edge - no Obsidian or design tool needed. Triggers: exportar CV a PDF, generar PDF del CV, CV ATS PDF, convertir CV markdown a PDF, imprimir CV."
|
|
4
4
|
argument-hint: "[cv-name | all] [--workspace <path>]"
|
|
5
5
|
category: "career"
|
|
6
6
|
user-invocable: true
|
|
7
7
|
allowed-tools: Read, Glob, Grep, Bash, Write
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
-
# CV ATS Export
|
|
10
|
+
# CV ATS Export - Markdown → PDF ATS-compliant
|
|
11
11
|
|
|
12
|
-
Convertís un CV en Markdown a un **PDF que pasa filtros ATS**: una sola columna, texto seleccionable (no imagen), A4, tipografía limpia. Usa Chrome o Edge en modo headless
|
|
12
|
+
Convertís un CV en Markdown a un **PDF que pasa filtros ATS**: una sola columna, texto seleccionable (no imagen), A4, tipografía limpia. Usa Chrome o Edge en modo headless - no requiere Obsidian, LaTeX ni herramienta de diseño.
|
|
13
13
|
|
|
14
|
-
El motor es `scripts/cv_export.py
|
|
14
|
+
El motor es `scripts/cv_export.py` + `scripts/verify_pdf.py` (verificación ATS post-export). La primera vez, instalá ambos en el workspace (`tools/`) para que queden versionados con los CVs; o corrélos directo desde el skill (van juntos: `cv_export` importa `verify_pdf` desde su mismo directorio).
|
|
15
15
|
|
|
16
16
|
## Requisitos
|
|
17
17
|
|
|
18
18
|
- **Python 3** en el PATH.
|
|
19
19
|
- **Google Chrome o Microsoft Edge** instalado (el script autodetecta rutas estándar en Windows/macOS/Linux).
|
|
20
|
+
- **`pypdf` (opcional)** para la verificación ATS automática: `pip install pypdf`. Sin pypdf el export funciona igual y reporta `[ATS SKIP]`.
|
|
20
21
|
- CVs en Markdown dentro de `02-CVs/` del career workspace, con headers (`# Nombre`, `## Sección`), bullets y `---` como separadores.
|
|
21
22
|
|
|
22
23
|
## Uso
|
|
@@ -30,6 +31,9 @@ python scripts/cv_export.py "CV - Acme - Backend"
|
|
|
30
31
|
|
|
31
32
|
# Workspace explícito
|
|
32
33
|
python scripts/cv_export.py --workspace ./career-workspace "CV - Acme - Backend"
|
|
34
|
+
|
|
35
|
+
# Saltar la verificación ATS
|
|
36
|
+
python scripts/cv_export.py --no-verify "CV - Acme - Backend"
|
|
33
37
|
```
|
|
34
38
|
|
|
35
39
|
El script:
|
|
@@ -37,6 +41,15 @@ El script:
|
|
|
37
41
|
2. Lee el `.md`, separa frontmatter, convierte el cuerpo a HTML con el CSS ATS embebido.
|
|
38
42
|
3. Imprime a PDF con Chrome/Edge headless (`--headless=new --print-to-pdf`).
|
|
39
43
|
4. Guarda en `02-CVs/exports/`. El nombre sale del frontmatter `archivo_pdf` si existe; si no, del nombre del `.md` (sin el prefijo `CV - `).
|
|
44
|
+
5. **Verifica la capa de texto ATS** con `verify_pdf.py` (pypdf): texto extraíble (mínimo 200 chars), que el nombre del H1 y el email sobrevivan al export, y avisa si supera las 2 páginas. Reporta `[ATS OK]` / `[ATS WARN]` / `[ATS SKIP]`; nunca rompe el export. Se salta con `--no-verify`.
|
|
45
|
+
|
|
46
|
+
Los dos `[ATS SKIP]` son distintos: `verify_pdf.py not found next to cv_export.py` significa que copiaste solo un archivo al workspace (instalar pypdf no cambia nada); `pypdf not installed` es el que se resuelve con `pip install pypdf`.
|
|
47
|
+
|
|
48
|
+
`verify_pdf.py` también funciona standalone para chequear cualquier PDF:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
python scripts/verify_pdf.py "02-CVs/exports/Backend.pdf" --min-chars 200 --max-pages 2 --contains "Jane Doe"
|
|
52
|
+
```
|
|
40
53
|
|
|
41
54
|
## Markdown soportado
|
|
42
55
|
|
|
@@ -51,12 +64,12 @@ El script:
|
|
|
51
64
|
|
|
52
65
|
## Personalizar el estilo
|
|
53
66
|
|
|
54
|
-
El CSS ATS está en la constante `CSS` dentro de `scripts/cv_export.py` (tamaño de fuente, márgenes A4, estilos de h1/h2). Editá ahí si querés ajustar tipografía o espaciado
|
|
67
|
+
El CSS ATS está en la constante `CSS` dentro de `scripts/cv_export.py` (tamaño de fuente, márgenes A4, estilos de h1/h2). Editá ahí si querés ajustar tipografía o espaciado - mantené una sola columna y texto real.
|
|
55
68
|
|
|
56
69
|
## Reglas
|
|
57
70
|
|
|
58
71
|
- No metas tablas, columnas ni imágenes en el CV: rompen el parseo ATS.
|
|
59
|
-
-
|
|
72
|
+
- Atendé los `[ATS WARN]`: un PDF-imagen o sin el nombre/email extraíbles no pasa filtros ATS. Usá `--no-verify` solo para iterar rápido sobre el layout.
|
|
60
73
|
- No commitees PDFs con datos sensibles a un remoto sin confirmación.
|
|
61
74
|
|
|
62
75
|
## Handoff
|
|
@@ -8,6 +8,12 @@ Usage (from anywhere; the script locates the career workspace):
|
|
|
8
8
|
python cv_export.py # all base CVs (CV - *.md)
|
|
9
9
|
python cv_export.py "CV - Acme - Backend" # a single CV (with/without .md)
|
|
10
10
|
python cv_export.py --workspace ./career-workspace "CV - Acme"
|
|
11
|
+
python cv_export.py --no-verify ... # skip the ATS verification
|
|
12
|
+
|
|
13
|
+
After exporting, the PDF text layer is verified with verify_pdf.py (pypdf):
|
|
14
|
+
extractable text, the H1 name and the email must survive the export, and a
|
|
15
|
+
warning is printed above 2 pages. Reports [ATS OK] / [ATS WARN]; if pypdf is
|
|
16
|
+
not installed, [ATS SKIP] without breaking the export.
|
|
11
17
|
|
|
12
18
|
Workspace resolution order:
|
|
13
19
|
1. --workspace <path> flag
|
|
@@ -204,7 +210,41 @@ def find_browser():
|
|
|
204
210
|
return None
|
|
205
211
|
|
|
206
212
|
|
|
207
|
-
def
|
|
213
|
+
def verify_ats(pdf_path, body):
|
|
214
|
+
"""Verify the exported PDF's text layer. Never breaks the export: reports."""
|
|
215
|
+
# Two distinct failures, two distinct messages: verify_pdf.py missing next to
|
|
216
|
+
# this script (installing pypdf would not help) vs pypdf missing, which
|
|
217
|
+
# verify_pdf only imports when it actually reads the PDF.
|
|
218
|
+
try:
|
|
219
|
+
from verify_pdf import verify
|
|
220
|
+
except ImportError:
|
|
221
|
+
print(f" [ATS SKIP] verify_pdf.py not found next to {Path(__file__).name}")
|
|
222
|
+
return
|
|
223
|
+
|
|
224
|
+
contains = []
|
|
225
|
+
m = re.search(r"^#\s+(.+)$", body, re.MULTILINE)
|
|
226
|
+
if m:
|
|
227
|
+
contains.append(re.sub(r"\*|\[|\]\([^)]*\)", "", m.group(1)).strip())
|
|
228
|
+
m = re.search(r"[\w.+-]+@[\w-]+\.[\w.]+", body)
|
|
229
|
+
if m:
|
|
230
|
+
contains.append(m.group(0))
|
|
231
|
+
|
|
232
|
+
try:
|
|
233
|
+
problems = verify(pdf_path, min_chars=200, contains=contains, max_pages=2)
|
|
234
|
+
except ImportError:
|
|
235
|
+
print(" [ATS SKIP] pypdf not installed (pip install pypdf)")
|
|
236
|
+
return
|
|
237
|
+
except Exception as e:
|
|
238
|
+
problems = [f"could not verify: {e}"]
|
|
239
|
+
|
|
240
|
+
if problems:
|
|
241
|
+
for p in problems:
|
|
242
|
+
print(f" [ATS WARN] {p}")
|
|
243
|
+
else:
|
|
244
|
+
print(" [ATS OK] text layer verified (name + email extractable)")
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def export(md_path, browser, cv_dir, export_dir, check_ats=True):
|
|
208
248
|
text = md_path.read_text(encoding="utf-8")
|
|
209
249
|
fm, body = strip_frontmatter(text)
|
|
210
250
|
|
|
@@ -252,12 +292,15 @@ def export(md_path, browser, cv_dir, export_dir):
|
|
|
252
292
|
|
|
253
293
|
size_kb = pdf_path.stat().st_size // 1024
|
|
254
294
|
print(f"[OK] {md_path.name} -> {pdf_path} ({size_kb} KB)")
|
|
295
|
+
if check_ats:
|
|
296
|
+
verify_ats(pdf_path, body)
|
|
255
297
|
return True
|
|
256
298
|
|
|
257
299
|
|
|
258
300
|
def parse_args(argv):
|
|
259
301
|
workspace = None
|
|
260
302
|
names = []
|
|
303
|
+
check_ats = True
|
|
261
304
|
i = 0
|
|
262
305
|
while i < len(argv):
|
|
263
306
|
a = argv[i]
|
|
@@ -266,14 +309,16 @@ def parse_args(argv):
|
|
|
266
309
|
workspace = argv[i] if i < len(argv) else None
|
|
267
310
|
elif a.startswith("--workspace="):
|
|
268
311
|
workspace = a.split("=", 1)[1]
|
|
312
|
+
elif a == "--no-verify":
|
|
313
|
+
check_ats = False
|
|
269
314
|
else:
|
|
270
315
|
names.append(a)
|
|
271
316
|
i += 1
|
|
272
|
-
return workspace, names
|
|
317
|
+
return workspace, names, check_ats
|
|
273
318
|
|
|
274
319
|
|
|
275
320
|
def main():
|
|
276
|
-
workspace, names = parse_args(sys.argv[1:])
|
|
321
|
+
workspace, names, check_ats = parse_args(sys.argv[1:])
|
|
277
322
|
|
|
278
323
|
browser = find_browser()
|
|
279
324
|
if not browser:
|
|
@@ -298,7 +343,7 @@ def main():
|
|
|
298
343
|
print(f"[ERROR] No CVs (CV - *.md) found in {cv_dir}.")
|
|
299
344
|
sys.exit(1)
|
|
300
345
|
|
|
301
|
-
ok = all([export(t, browser, cv_dir, export_dir) for t in targets])
|
|
346
|
+
ok = all([export(t, browser, cv_dir, export_dir, check_ats) for t in targets])
|
|
302
347
|
sys.exit(0 if ok else 1)
|
|
303
348
|
|
|
304
349
|
|