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.
Files changed (67) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/README.md +5 -4
  3. package/agents/ecommerce/ecommerce-architect.md +60 -0
  4. package/dist/cli.js +2 -0
  5. package/docs/SKILLS-CATALOG.md +25 -2
  6. package/docs/USER-GUIDE.md +2 -2
  7. package/docs/coding-standards.md +26 -0
  8. package/package.json +2 -1
  9. package/profiles/ecommerce.yaml +48 -0
  10. package/profiles/job-hunt.yaml +12 -2
  11. package/rules/drafter-reviewer.md +39 -0
  12. package/skills/cart-checkout/SKILL.md +85 -0
  13. package/skills/cart-checkout/references/checkout-flow-spec.md +50 -0
  14. package/skills/cold-outreach/SKILL.md +10 -10
  15. package/skills/cold-outreach/references/recruiter-playbook.md +5 -5
  16. package/skills/commerce-analytics/SKILL.md +85 -0
  17. package/skills/commerce-analytics/references/ga4-ecommerce-events.md +63 -0
  18. package/skills/commerce-analytics/scripts/ga4_ecommerce_validate.py +134 -0
  19. package/skills/commerce-data-model/SKILL.md +84 -0
  20. package/skills/commerce-data-model/references/data-model-spec.md +79 -0
  21. package/skills/cover-letter/SKILL.md +8 -8
  22. package/skills/cv-ats-export/SKILL.md +19 -6
  23. package/skills/cv-ats-export/scripts/cv_export.py +49 -4
  24. package/skills/cv-ats-export/scripts/verify_pdf.py +90 -0
  25. package/skills/cv-tailor/SKILL.md +7 -7
  26. package/skills/cv-tailor/references/ats-keywords.md +9 -9
  27. package/skills/interview-prep/SKILL.md +7 -7
  28. package/skills/interview-prep/references/star-framework.md +10 -10
  29. package/skills/inventory-stock/SKILL.md +86 -0
  30. package/skills/inventory-stock/scripts/restock_forecast.py +173 -0
  31. package/skills/job-aplicar/SKILL.md +88 -0
  32. package/skills/job-hunt/SKILL.md +20 -13
  33. package/skills/job-hunt/references/templates/Aplicacion.md +6 -2
  34. package/skills/job-hunt/references/templates/CV Custom.md +1 -1
  35. package/skills/job-hunt/references/templates/Contacto.md +1 -1
  36. package/skills/job-hunt/references/templates/Dashboard.md +19 -15
  37. package/skills/job-hunt/references/templates/Perfil.md +2 -2
  38. package/skills/job-outcome/SKILL.md +55 -0
  39. package/skills/job-scrape/SKILL.md +95 -0
  40. package/skills/job-scrape/references/dedup-playbook.md +25 -0
  41. package/skills/job-scrape/references/getonbrd-cli.md +62 -0
  42. package/skills/job-scrape/references/getonbrd-urls.md +37 -0
  43. package/skills/job-scrape/references/linkedin-cli.md +64 -0
  44. package/skills/job-scrape/references/linkedin-urls.md +42 -0
  45. package/skills/job-scrape/references/seen_jobs.schema.json +68 -0
  46. package/skills/job-scrape/scripts/getonbrd_search.py +501 -0
  47. package/skills/job-scrape/scripts/linkedin_search.py +428 -0
  48. package/skills/job-search/SKILL.md +22 -19
  49. package/skills/job-search/references/scoring-rubric.md +8 -8
  50. package/skills/job-upskill/SKILL.md +58 -0
  51. package/skills/linkedin-optimize/SKILL.md +5 -5
  52. package/skills/master-profile/SKILL.md +17 -17
  53. package/skills/merchant-feed/SKILL.md +85 -0
  54. package/skills/merchant-feed/references/feed-attribute-map.md +47 -0
  55. package/skills/merchant-feed/scripts/feed_validate.py +140 -0
  56. package/skills/network-map/SKILL.md +7 -7
  57. package/skills/order-lifecycle/SKILL.md +86 -0
  58. package/skills/order-lifecycle/references/order-state-machine.md +53 -0
  59. package/skills/payments-architecture/SKILL.md +80 -0
  60. package/skills/personal-brand/SKILL.md +10 -10
  61. package/skills/personal-brand/references/post-pillars.md +14 -14
  62. package/skills/portfolio-site/SKILL.md +11 -11
  63. package/skills/product-listing/SKILL.md +84 -0
  64. package/skills/promotions-discounts/SKILL.md +80 -0
  65. package/skills/shipping-tax/SKILL.md +85 -0
  66. package/skills/storefront-architecture/SKILL.md +80 -0
  67. 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 — Mensaje de aplicación de élite
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** — 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.
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–250 palabras** para mensajes, 250–350 para carta formal.
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–2 logros que mapean directo a lo que piden, con número.
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** — 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.
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 — 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."
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 — Markdown → PDF ATS-compliant
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 — no requiere Obsidian, LaTeX ni herramienta de diseño.
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`. La primera vez, instalalo en el workspace (`tools/cv_export.py`) para que quede versionado con los CVs; o corrélo directo desde el skill.
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 — mantené una sola columna y texto real.
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
- - Verificá el PDF generado: que el texto sea seleccionable y entre en 1–2 páginas.
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 export(md_path, browser, cv_dir, export_dir):
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