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,90 @@
1
+ """
2
+ verify_pdf.py — Verify that an exported PDF is ATS-compliant.
3
+
4
+ Checks that the PDF has an extractable text layer (not an image), the
5
+ expected page count, and that given strings (name, email) survived the
6
+ export. Adapted from tools/verify_pdf.py of MadsLorentzen/ai-job-search,
7
+ replacing poppler (pdftotext/pdfinfo) with pypdf so it runs on Windows
8
+ without external binaries.
9
+
10
+ Usage:
11
+ python verify_pdf.py <pdf> [--pages N] [--max-pages N] [--min-chars N]
12
+ [--contains TEXT ...]
13
+
14
+ Example:
15
+ python verify_pdf.py "career-workspace/02-CVs/exports/Backend.pdf" \
16
+ --min-chars 200 --contains "Jane Doe"
17
+
18
+ Exit code 0 if all checks pass, 1 if any check fails, 2 if pypdf is missing.
19
+ """
20
+
21
+ import argparse
22
+ import sys
23
+ from pathlib import Path
24
+
25
+
26
+ def extract_text(pdf_path):
27
+ """Return (page_count, normalized_text) of the PDF."""
28
+ from pypdf import PdfReader
29
+
30
+ reader = PdfReader(str(pdf_path))
31
+ text = "\n".join(page.extract_text() or "" for page in reader.pages)
32
+ return len(reader.pages), " ".join(text.split())
33
+
34
+
35
+ def verify(pdf_path, pages=None, min_chars=1, contains=(), max_pages=None):
36
+ """Return a list of problems (empty = PDF OK). Raises ImportError without pypdf."""
37
+ pdf_path = Path(pdf_path)
38
+ if not pdf_path.exists():
39
+ return [f"does not exist: {pdf_path}"]
40
+
41
+ n_pages, text = extract_text(pdf_path)
42
+ problems = []
43
+
44
+ if pages is not None and n_pages != pages:
45
+ problems.append(f"has {n_pages} pages (expected {pages})")
46
+ if max_pages is not None and n_pages > max_pages:
47
+ problems.append(f"has {n_pages} pages (ideally 1-{max_pages} for a CV)")
48
+ if len(text) < min_chars:
49
+ problems.append(
50
+ f"text layer has {len(text)} extractable chars (minimum {min_chars}); "
51
+ "likely an image-only PDF that an ATS cannot read"
52
+ )
53
+ for needle in contains:
54
+ if " ".join(needle.split()).lower() not in text.lower():
55
+ problems.append(f'missing required text: "{needle}"')
56
+
57
+ return problems
58
+
59
+
60
+ def main():
61
+ ap = argparse.ArgumentParser(description="Verify the ATS text layer of a PDF.")
62
+ ap.add_argument("pdf", help="path to the PDF")
63
+ ap.add_argument("--pages", type=int, default=None, help="expected page count")
64
+ ap.add_argument("--min-chars", type=int, default=1, help="minimum extractable chars")
65
+ ap.add_argument("--max-pages", type=int, default=None, help="warn above N pages")
66
+ ap.add_argument(
67
+ "--contains", action="append", default=[], metavar="TEXT",
68
+ help="string that must appear in the text (repeatable)",
69
+ )
70
+ args = ap.parse_args()
71
+
72
+ try:
73
+ problems = verify(
74
+ args.pdf, args.pages, args.min_chars, args.contains, args.max_pages
75
+ )
76
+ except ImportError:
77
+ print("[ERROR] pypdf is missing. Install with: pip install pypdf")
78
+ sys.exit(2)
79
+
80
+ name = Path(args.pdf).name
81
+ if problems:
82
+ for p in problems:
83
+ print(f"[FAIL] {name}: {p}")
84
+ sys.exit(1)
85
+ print(f"[OK] {name}: ATS text layer verified")
86
+ sys.exit(0)
87
+
88
+
89
+ if __name__ == "__main__":
90
+ main()
@@ -7,9 +7,9 @@ user-invocable: true
7
7
  allowed-tools: Read, Glob, Grep, Write, Edit, WebFetch
8
8
  ---
9
9
 
10
- # CV Tailor — Adaptar el CV a la oferta
10
+ # CV Tailor - Adaptar el CV a la oferta
11
11
 
12
- Tomás un job description y el perfil base del usuario, y producís un **CV custom** que matchea con los filtros automáticos (ATS) y con la lectura humana del hiring manager. No reescribís la verdad — la reorganizás y la traducís al vocabulario de la oferta.
12
+ Tomás un job description y el perfil base del usuario, y producís un **CV custom** que matchea con los filtros automáticos (ATS) y con la lectura humana del hiring manager. No reescribís la verdad - la reorganizás y la traducís al vocabulario de la oferta.
13
13
 
14
14
  ## Inputs
15
15
 
@@ -20,7 +20,7 @@ Tomás un job description y el perfil base del usuario, y producís un **CV cust
20
20
  ## Proceso
21
21
 
22
22
  ### 1. Analizar la oferta
23
- - Extraer **keywords literales** del JD: tecnologías, metodologías, responsabilidades, soft skills, seniority. Literal importa — el ATS hace matching de strings (ej: si piden "React.js" no asumas que "React" matchea igual).
23
+ - Extraer **keywords literales** del JD: tecnologías, metodologías, responsabilidades, soft skills, seniority. Literal importa - el ATS hace matching de strings (ej: si piden "React.js" no asumas que "React" matchea igual).
24
24
  - Detectar **must-have vs nice-to-have**.
25
25
  - Detectar **señales de cultura / ángulo** (startup vs enterprise, code-forward, ownership, etc.).
26
26
  - Ver `references/ats-keywords.md` para cómo extraer y mapear keywords.
@@ -38,17 +38,17 @@ Tomás un job description y el perfil base del usuario, y producís un **CV cust
38
38
  ### 4. Score before/after
39
39
  - Calcular un **match rate** aproximado: % de keywords/requisitos del JD presentes en el CV, **antes** vs **después** del tailoring.
40
40
  - Reportar el delta ("match 48% → 81%") y qué keywords se cubrieron.
41
- - **Flag de keyword stuffing:** marcar cualquier inserción que a un revisor humano le suene forzada o poco natural — el objetivo es pasar el ATS *y* leer bien para una persona.
41
+ - **Flag de keyword stuffing:** marcar cualquier inserción que a un revisor humano le suene forzada o poco natural - el objetivo es pasar el ATS *y* leer bien para una persona.
42
42
 
43
43
  ### 5. Producir el CV custom
44
44
  - Crear/actualizar la nota en `02-CVs/CV - <Empresa> - <Rol>.md` usando el template `CV Custom` (`Templates/CV Custom.md`).
45
45
  - Completar frontmatter (`perfil_base`, `aplicacion`, `empresa`, `archivo_pdf`).
46
- - Documentar las decisiones (keywords, qué se resaltó/bajó) en las secciones del template — sirve para la entrevista y para iterar.
46
+ - Documentar las decisiones (keywords, qué se resaltó/bajó) en las secciones del template - sirve para la entrevista y para iterar.
47
47
  - El cuerpo del CV en sí debe quedar listo para `/cv-ats-export` (markdown limpio: `# nombre`, `## secciones`, bullets, `---`).
48
48
 
49
49
  ## Formato del CV (ATS-friendly)
50
50
 
51
- - **Una columna.** Sin tablas, sin cajas de texto, sin columnas — los ATS las leen mal.
51
+ - **Una columna.** Sin tablas, sin cajas de texto, sin columnas - los ATS las leen mal.
52
52
  - Secciones estándar con headers claros: Summary, Experience, Skills, Projects, Education.
53
53
  - Fechas consistentes (MM/YYYY).
54
54
  - Sin gráficos ni imágenes para el contenido crítico.
@@ -62,7 +62,7 @@ CV en **inglés** para roles internacionales/remotos globales; **español** si l
62
62
 
63
63
  - Nunca inventar experiencia, fechas o métricas. Tailoring = reorganizar y traducir, no mentir.
64
64
  - No keyword-stuffing: cada keyword debe vivir en un bullet verdadero.
65
- - Un CV custom por postulación — no pisar el perfil base ni el maestro.
65
+ - Un CV custom por postulación - no pisar el perfil base ni el maestro.
66
66
  - Después de generar el `.md`, recordar al usuario correr `/cv-ats-export` para el PDF.
67
67
 
68
68
  ## Handoff
@@ -1,4 +1,4 @@
1
- # ATS Keywords — Cómo extraer y mapear
1
+ # ATS Keywords - Cómo extraer y mapear
2
2
 
3
3
  Los Applicant Tracking Systems (Greenhouse, Lever, Workday, Workable, etc.) hacen un primer filtro automático antes de que un humano vea el CV. Entender cómo "leen" cambia el resultado.
4
4
 
@@ -11,12 +11,12 @@ Los Applicant Tracking Systems (Greenhouse, Lever, Workday, Workable, etc.) hace
11
11
 
12
12
  ## Extracción de keywords del JD
13
13
 
14
- 1. **Hard skills / tecnologías** — lenguajes, frameworks, DBs, cloud, herramientas. Copiá la forma **literal** (React.js ≠ React ≠ ReactJS para un match exacto; cuando puedas, incluí la variante que usa la oferta).
15
- 2. **Responsabilidades / verbos** — "design REST APIs", "lead a team", "CI/CD pipelines".
16
- 3. **Metodologías** — Agile, Scrum, TDD, clean architecture.
17
- 4. **Seniority y años** — "5+ years", "senior", "lead".
18
- 5. **Soft skills explícitos** — communication, ownership, cross-functional.
19
- 6. **Señales de cultura / frases gatillo** — ej. "code-forward", "startup mentality", "high autonomy". Suelen indicar qué valoran y dan ángulo para el summary y la cover letter.
14
+ 1. **Hard skills / tecnologías** - lenguajes, frameworks, DBs, cloud, herramientas. Copiá la forma **literal** (React.js ≠ React ≠ ReactJS para un match exacto; cuando puedas, incluí la variante que usa la oferta).
15
+ 2. **Responsabilidades / verbos** - "design REST APIs", "lead a team", "CI/CD pipelines".
16
+ 3. **Metodologías** - Agile, Scrum, TDD, clean architecture.
17
+ 4. **Seniority y años** - "5+ years", "senior", "lead".
18
+ 5. **Soft skills explícitos** - communication, ownership, cross-functional.
19
+ 6. **Señales de cultura / frases gatillo** - ej. "code-forward", "startup mentality", "high autonomy". Suelen indicar qué valoran y dan ángulo para el summary y la cover letter.
20
20
 
21
21
  ## Mapeo contra el perfil
22
22
 
@@ -24,7 +24,7 @@ Los Applicant Tracking Systems (Greenhouse, Lever, Workday, Workable, etc.) hace
24
24
  |---|---|---|---|
25
25
  | React.js | Sí | "Built React + TS frontends @ X" | resaltar arriba |
26
26
  | Kubernetes | Parcial | exposición en CI/CD | mencionar honesto |
27
- | Go | No | — | gap; no inventar |
27
+ | Go | No | - | gap; no inventar |
28
28
 
29
29
  - **Must-have que tenés** → arriba, con espacio, con la palabra exacta.
30
30
  - **Must-have parcial** → mencionar con honestidad ("exposure to…").
@@ -41,6 +41,6 @@ Los Applicant Tracking Systems (Greenhouse, Lever, Workday, Workable, etc.) hace
41
41
  - [ ] Una sola columna, sin tablas/text boxes.
42
42
  - [ ] Headers de sección estándar (Experience, Skills, Education…).
43
43
  - [ ] Fechas consistentes MM/YYYY.
44
- - [ ] Texto seleccionable (no imagen) — el export de `/cv-ats-export` ya lo garantiza.
44
+ - [ ] Texto seleccionable (no imagen) - el export de `/cv-ats-export` ya lo garantiza.
45
45
  - [ ] Nombre del archivo profesional (`Nombre_Apellido_Empresa.pdf`).
46
46
  - [ ] Contacto en el cuerpo, no en el header/footer del documento.
@@ -7,14 +7,14 @@ user-invocable: true
7
7
  allowed-tools: Read, Glob, Grep, Write, Edit, WebFetch
8
8
  ---
9
9
 
10
- # Interview Prep — Preparación de entrevista
10
+ # Interview Prep - Preparación de entrevista
11
11
 
12
12
  Preparás al usuario para una entrevista concreta: research de la empresa, banco de preguntas probable según rol y ronda, respuestas **STAR** construidas desde su experiencia real, preguntas inteligentes para hacer, y red flags a detectar. El output queda en una nota de `06-Entrevistas/`.
13
13
 
14
14
  ## Inputs
15
15
 
16
16
  - **Aplicación / empresa / rol** (`03-Aplicaciones/`, `04-Empresas/`) y la **ronda** (recruiter screen, técnica, system design, hiring manager, cultural, final).
17
- - **Perfil maestro** (`01-Perfiles/`) — de ahí salen las historias para las respuestas STAR.
17
+ - **Perfil maestro** (`01-Perfiles/`) - de ahí salen las historias para las respuestas STAR.
18
18
  - **Entrevistadores** si se conocen (research de background ayuda).
19
19
 
20
20
  ## Qué producís
@@ -28,19 +28,19 @@ Preparás al usuario para una entrevista concreta: research de la empresa, banco
28
28
 
29
29
  ### 2. Banco de preguntas por ronda
30
30
  - **Recruiter screen:** motivación, expectativas salariales, disponibilidad, "contame de vos".
31
- - **Técnica:** según stack del JD — fundamentos, problemas, debugging, code review.
31
+ - **Técnica:** según stack del JD - fundamentos, problemas, debugging, code review.
32
32
  - **System design** (si aplica): patrones, trade-offs, escala.
33
33
  - **Hiring manager / comportamiento:** liderazgo, conflicto, fracaso, impacto.
34
34
  - **Cultural:** valores, formas de trabajo, por qué esta empresa.
35
- - Marcar las **3–5 más probables** para este rol específico.
35
+ - Marcar las **3-5 más probables** para este rol específico.
36
36
 
37
37
  ### 3. Respuestas STAR
38
38
  - Construir respuestas con el método **STAR** (Situación, Tarea, Acción, Resultado) usando logros reales del perfil maestro.
39
- - Cada respuesta: concreta, con métrica, 60–90 segundos hablada.
39
+ - Cada respuesta: concreta, con métrica, 60-90 segundos hablada.
40
40
  - Ver `references/star-framework.md`.
41
41
 
42
42
  ### 4. Preguntas para hacer
43
- - 5–7 preguntas inteligentes que demuestran research y filtran si la empresa te conviene (equipo, expectativas de los primeros 90 días, cómo miden éxito, deuda técnica, proceso).
43
+ - 5-7 preguntas inteligentes que demuestran research y filtran si la empresa te conviene (equipo, expectativas de los primeros 90 días, cómo miden éxito, deuda técnica, proceso).
44
44
 
45
45
  ### 5. Red flags a detectar
46
46
  - Señales de mala empresa/equipo durante la entrevista (proceso caótico, evasión sobre cultura/turnover, expectativas irreales, falta de claridad de rol, hostilidad).
@@ -56,7 +56,7 @@ Preparás al usuario para una entrevista concreta: research de la empresa, banco
56
56
 
57
57
  1. Leer aplicación + empresa + perfil. WebFetch del sitio/notas de la empresa.
58
58
  2. Crear nota en `06-Entrevistas/` con el template `Entrevista`.
59
- 3. Llenar research, banco de preguntas (marcando las top), STAR de las 3–5 historias clave, preguntas a hacer, red flags.
59
+ 3. Llenar research, banco de preguntas (marcando las top), STAR de las 3-5 historias clave, preguntas a hacer, red flags.
60
60
  4. Ofrecer un **mock**: vos hacés de entrevistador y das feedback sobre las respuestas.
61
61
  5. Post-entrevista: ayudar a registrar feedback y definir próximo paso / follow-up (con `/cold-outreach`).
62
62
 
@@ -1,15 +1,15 @@
1
- # STAR Framework — Respuestas de comportamiento
1
+ # STAR Framework - Respuestas de comportamiento
2
2
 
3
3
  STAR estructura respuestas a preguntas de comportamiento ("contame de una vez que…") para que sean concretas, completas y memorables. La mayoría de los candidatos divaga; STAR te da una historia con resultado.
4
4
 
5
5
  ## Las 4 partes
6
6
 
7
- - **S — Situación:** el contexto, breve. Dónde, cuándo, qué estaba pasando. (1–2 oraciones)
8
- - **T — Tarea:** tu responsabilidad / el problema a resolver. (1 oración)
9
- - **A — Acción:** qué hiciste *vos* (no "el equipo"). El grueso de la respuesta. Decisiones concretas. (2–4 oraciones)
10
- - **R — Resultado:** el impacto, **con número** siempre que se pueda. Qué aprendiste. (1–2 oraciones)
7
+ - **S - Situación:** el contexto, breve. Dónde, cuándo, qué estaba pasando. (1-2 oraciones)
8
+ - **T - Tarea:** tu responsabilidad / el problema a resolver. (1 oración)
9
+ - **A - Acción:** qué hiciste *vos* (no "el equipo"). El grueso de la respuesta. Decisiones concretas. (2-4 oraciones)
10
+ - **R - Resultado:** el impacto, **con número** siempre que se pueda. Qué aprendiste. (1-2 oraciones)
11
11
 
12
- Objetivo: 60–90 segundos hablado. Ni telegrama, ni monólogo de 5 minutos.
12
+ Objetivo: 60-90 segundos hablado. Ni telegrama, ni monólogo de 5 minutos.
13
13
 
14
14
  ## Ejemplo
15
15
 
@@ -20,9 +20,9 @@ Objetivo: 60–90 segundos hablado. Ni telegrama, ni monólogo de 5 minutos.
20
20
  > **A:** Perfilé el hot path, encontré queries N+1 y serialización redundante; introduje caching por sesión y batch de writes, y agregué métricas para validar.
21
21
  > **R:** Latencia -25% y estabilidad del server +40%; duplicamos jugadores concurrentes sin sumar costos. Aprendí a medir antes de optimizar.
22
22
 
23
- ## Banco de historias (preparar 5–6)
23
+ ## Banco de historias (preparar 5-6)
24
24
 
25
- Construí 5–6 historias STAR reutilizables desde el perfil maestro, cubriendo:
25
+ Construí 5-6 historias STAR reutilizables desde el perfil maestro, cubriendo:
26
26
 
27
27
  1. **Logro/impacto** del que estés orgulloso (con métrica).
28
28
  2. **Problema técnico difícil** que resolviste.
@@ -31,11 +31,11 @@ Construí 5–6 historias STAR reutilizables desde el perfil maestro, cubriendo:
31
31
  5. **Liderazgo / iniciativa** (lideraste algo, mentoreaste, empujaste un cambio).
32
32
  6. **Ambigüedad / cambio de prioridades** que manejaste.
33
33
 
34
- La misma historia puede servir para varias preguntas — lo que cambia es qué parte enfatizás.
34
+ La misma historia puede servir para varias preguntas - lo que cambia es qué parte enfatizás.
35
35
 
36
36
  ## Errores comunes
37
37
 
38
- - Quedarse en "nosotros" — la entrevista evalúa *tu* aporte. Decí "yo".
38
+ - Quedarse en "nosotros" - la entrevista evalúa *tu* aporte. Decí "yo".
39
39
  - Saltearse el Resultado, o un resultado sin número.
40
40
  - Demasiada Situación, poca Acción.
41
41
  - Historias vagas/inventadas que no aguantan repreguntas.
@@ -0,0 +1,86 @@
1
+ ---
2
+ name: inventory-stock
3
+ description: "Design inventory and stock control — availability tracking, optimistic locking to prevent overselling, reservations, multi-location, backorders, and restock forecasting. Includes a safety-stock/reorder-point calculator. Triggers: designing inventory, fixing overselling, forecasting restock."
4
+ argument-hint: "[design | forecast | audit]"
5
+ category: "ecommerce"
6
+ user-invocable: true
7
+ allowed-tools: Read, Glob, Grep, Bash
8
+ ---
9
+
10
+ # Inventory & Stock
11
+
12
+ Stock is where data integrity meets revenue: oversell and you lose trust, over-reserve and you lose sales. Design availability that is correct under concurrency and forecast restocks from real demand — platform-agnostic.
13
+
14
+ ## Before Starting
15
+
16
+ Gather: inventory model (single vs multi-location), sales velocity and lead times, fulfillment style (own stock, dropship, backorder), and goal (design the model, forecast restock, or audit for oversell risk).
17
+
18
+ ## How This Skill Works
19
+
20
+ | Mode | Trigger | Output |
21
+ |------|---------|--------|
22
+ | **Design** | `design` | Availability model + concurrency strategy |
23
+ | **Forecast** | `forecast` | Safety stock + reorder points (via script) |
24
+ | **Audit** | `audit` | Oversell-risk and accuracy review |
25
+
26
+ ---
27
+
28
+ ## Phase 1 — Availability Model
29
+
30
+ Track three numbers per SKU per location, never one:
31
+
32
+ | Quantity | Meaning |
33
+ |----------|---------|
34
+ | **on_hand** | Physically present |
35
+ | **reserved** | Allocated to unfulfilled orders/checkouts |
36
+ | **available** | `on_hand − reserved` — the only number a shopper sees |
37
+
38
+ **Rule:** Reserve at checkout start with a TTL; release on timeout/abandonment. Decrement `on_hand` at fulfillment, not at order placement.
39
+
40
+ ---
41
+
42
+ ## Phase 2 — Preventing Overselling
43
+
44
+ The classic race: two shoppers buy the last unit. Defenses, in order of preference:
45
+
46
+ 1. **Optimistic locking** — version/row check on decrement; retry on conflict. Default choice.
47
+ 2. **Atomic conditional update** — `UPDATE ... WHERE available >= qty` and check rows-affected.
48
+ 3. **Pessimistic lock** — only for high-contention single rows; costs throughput.
49
+
50
+ Always treat "reserve stock" as a transaction that can FAIL and surface that to checkout cleanly. Multi-location adds an allocation step (which location fulfills) before the decrement.
51
+
52
+ For backorder rules, multi-location allocation strategies, and reservation TTL tuning, see the patterns above and hand off to `order-lifecycle` for fulfillment state.
53
+
54
+ ---
55
+
56
+ ## Phase 3 — Restock Forecasting
57
+
58
+ Use the script to turn a sales CSV into reorder points:
59
+
60
+ ```bash
61
+ python skills/inventory-stock/scripts/restock_forecast.py # demo data
62
+ python skills/inventory-stock/scripts/restock_forecast.py sales.csv --lead-time 14 --service-level 0.95
63
+ ```
64
+
65
+ It computes average daily demand, demand variability, **safety stock** (`z × σ_demand × √lead_time`), and **reorder point** (`demand × lead_time + safety_stock`). CSV columns: `sku,date,units_sold`.
66
+
67
+ ---
68
+
69
+ ## Output Artifacts
70
+
71
+ | Request | Output |
72
+ |---------|--------|
73
+ | Design | Availability model + chosen concurrency strategy |
74
+ | Forecast | Per-SKU reorder point + safety stock table |
75
+ | Audit | Oversell-risk findings: PASS / FAIL per check |
76
+
77
+ An inventory design **PASSES** when shoppers only see `available`, reservations expire, decrements are atomic/versioned, and forecasts drive reorder points instead of guesswork.
78
+
79
+ ---
80
+
81
+ ## Related Skills
82
+
83
+ - **commerce-data-model**: defines the SKU/variant that stock hangs off.
84
+ - **order-lifecycle**: consumes reservations and triggers decrements at fulfillment.
85
+ - **cart-checkout**: starts the reservation when checkout begins.
86
+ - **commerce-analytics**: sell-through and turnover metrics that feed forecasts.
@@ -0,0 +1,173 @@
1
+ """
2
+ restock_forecast.py — Safety stock & reorder points from a sales history CSV.
3
+
4
+ Turns daily sales into per-SKU reorder signals using the classic
5
+ reorder-point model. No external dependencies (stdlib only), cross-platform.
6
+
7
+ Usage:
8
+ python restock_forecast.py # built-in demo data
9
+ python restock_forecast.py sales.csv # your data, defaults
10
+ python restock_forecast.py sales.csv --lead-time 14 --service-level 0.95
11
+ python restock_forecast.py sales.csv --on-hand on_hand.csv # flag what to reorder
12
+
13
+ CSV format (header required):
14
+ sku,date,units_sold
15
+ ACME-TEE-RED-M,2026-05-01,4
16
+ ACME-TEE-RED-M,2026-05-02,2
17
+ ...
18
+
19
+ Optional on-hand CSV (header required): sku,on_hand
20
+
21
+ Model:
22
+ avg_daily_demand = mean(units_sold per day)
23
+ sigma_demand = stdev(units_sold per day)
24
+ safety_stock = z(service_level) * sigma_demand * sqrt(lead_time)
25
+ reorder_point = avg_daily_demand * lead_time + safety_stock
26
+
27
+ When on-hand is provided, a SKU is flagged REORDER when on_hand <= reorder_point.
28
+ """
29
+
30
+ import argparse
31
+ import csv
32
+ import math
33
+ import sys
34
+ from collections import defaultdict
35
+
36
+
37
+ # Embedded demo data: two SKUs, ~3 weeks of daily sales.
38
+ DEMO_ROWS = [
39
+ ("ACME-TEE-RED-M", d, u)
40
+ for d, u in zip(
41
+ [f"2026-05-{n:02d}" for n in range(1, 22)],
42
+ [4, 2, 5, 3, 6, 8, 7, 3, 4, 5, 2, 6, 9, 7, 4, 3, 5, 6, 8, 4, 5],
43
+ )
44
+ ] + [
45
+ ("ACME-MUG-BLUE", d, u)
46
+ for d, u in zip(
47
+ [f"2026-05-{n:02d}" for n in range(1, 22)],
48
+ [1, 0, 2, 1, 1, 3, 2, 0, 1, 1, 0, 2, 1, 2, 1, 0, 1, 2, 3, 1, 1],
49
+ )
50
+ ]
51
+ DEMO_ON_HAND = {"ACME-TEE-RED-M": 40, "ACME-MUG-BLUE": 60}
52
+
53
+
54
+ def z_for_service_level(p):
55
+ """Inverse standard-normal CDF (Acklam's rational approximation)."""
56
+ if not 0.0 < p < 1.0:
57
+ raise ValueError("service-level must be between 0 and 1 (exclusive)")
58
+ a = [-3.969683028665376e+01, 2.209460984245205e+02, -2.759285104469687e+02,
59
+ 1.383577518672690e+02, -3.066479806614716e+01, 2.506628277459239e+00]
60
+ b = [-5.447609879822406e+01, 1.615858368580409e+02, -1.556989798598866e+02,
61
+ 6.680131188771972e+01, -1.328068155288572e+01]
62
+ c = [-7.784894002430293e-03, -3.223964580411365e-01, -2.400758277161838e+00,
63
+ -2.549732539343734e+00, 4.374664141464968e+00, 2.938163982698783e+00]
64
+ d = [7.784695709041462e-03, 3.224671290700398e-01, 2.445134137142996e+00,
65
+ 3.754408661907416e+00]
66
+ plow, phigh = 0.02425, 1 - 0.02425
67
+ if p < plow:
68
+ q = math.sqrt(-2 * math.log(p))
69
+ return (((((c[0]*q+c[1])*q+c[2])*q+c[3])*q+c[4])*q+c[5]) / \
70
+ ((((d[0]*q+d[1])*q+d[2])*q+d[3])*q+1)
71
+ if p > phigh:
72
+ q = math.sqrt(-2 * math.log(1 - p))
73
+ return -(((((c[0]*q+c[1])*q+c[2])*q+c[3])*q+c[4])*q+c[5]) / \
74
+ ((((d[0]*q+d[1])*q+d[2])*q+d[3])*q+1)
75
+ q = p - 0.5
76
+ r = q * q
77
+ return (((((a[0]*r+a[1])*r+a[2])*r+a[3])*r+a[4])*r+a[5])*q / \
78
+ (((((b[0]*r+b[1])*r+b[2])*r+b[3])*r+b[4])*r+1)
79
+
80
+
81
+ def load_sales(path):
82
+ rows = []
83
+ with open(path, newline="", encoding="utf-8") as f:
84
+ reader = csv.DictReader(f)
85
+ required = {"sku", "date", "units_sold"}
86
+ if not required.issubset({c.strip() for c in (reader.fieldnames or [])}):
87
+ print(f"[ERROR] {path}: header must include sku,date,units_sold")
88
+ sys.exit(1)
89
+ for r in reader:
90
+ try:
91
+ rows.append((r["sku"].strip(), r["date"].strip(), float(r["units_sold"])))
92
+ except (KeyError, ValueError):
93
+ print(f"[WARN] skipping malformed row: {r}")
94
+ return rows
95
+
96
+
97
+ def load_on_hand(path):
98
+ out = {}
99
+ with open(path, newline="", encoding="utf-8") as f:
100
+ for r in csv.DictReader(f):
101
+ try:
102
+ out[r["sku"].strip()] = float(r["on_hand"])
103
+ except (KeyError, ValueError):
104
+ pass
105
+ return out
106
+
107
+
108
+ def stdev(xs):
109
+ if len(xs) < 2:
110
+ return 0.0
111
+ mean = sum(xs) / len(xs)
112
+ return math.sqrt(sum((x - mean) ** 2 for x in xs) / (len(xs) - 1))
113
+
114
+
115
+ def forecast(rows, lead_time, service_level, on_hand):
116
+ z = z_for_service_level(service_level)
117
+ by_sku = defaultdict(list)
118
+ for sku, _date, units in rows:
119
+ by_sku[sku].append(units)
120
+
121
+ results = []
122
+ for sku in sorted(by_sku):
123
+ daily = by_sku[sku]
124
+ avg = sum(daily) / len(daily)
125
+ sigma = stdev(daily)
126
+ safety = z * sigma * math.sqrt(lead_time)
127
+ rop = avg * lead_time + safety
128
+ oh = on_hand.get(sku)
129
+ flag = ""
130
+ if oh is not None:
131
+ flag = "REORDER" if oh <= rop else "OK"
132
+ results.append((sku, avg, sigma, safety, rop, oh, flag))
133
+ return results
134
+
135
+
136
+ def main():
137
+ ap = argparse.ArgumentParser(description="Safety stock & reorder points from sales CSV.")
138
+ ap.add_argument("csv", nargs="?", help="sales CSV (sku,date,units_sold). Omit for demo.")
139
+ ap.add_argument("--lead-time", type=float, default=14, help="supplier lead time in days (default 14)")
140
+ ap.add_argument("--service-level", type=float, default=0.95, help="0-1 target fill rate (default 0.95)")
141
+ ap.add_argument("--on-hand", help="optional CSV (sku,on_hand) to flag what to reorder")
142
+ args = ap.parse_args()
143
+
144
+ if args.csv:
145
+ rows = load_sales(args.csv)
146
+ on_hand = load_on_hand(args.on_hand) if args.on_hand else {}
147
+ else:
148
+ print("[INFO] No CSV given - using built-in demo data.\n")
149
+ rows = DEMO_ROWS
150
+ on_hand = DEMO_ON_HAND
151
+
152
+ if not rows:
153
+ print("[ERROR] No sales rows to analyze.")
154
+ sys.exit(1)
155
+
156
+ results = forecast(rows, args.lead_time, args.service_level, on_hand)
157
+
158
+ z = z_for_service_level(args.service_level)
159
+ print(f"Lead time: {args.lead_time:g}d | Service level: {args.service_level:g} (z={z:.2f})\n")
160
+ header = f"{'SKU':<20} {'avg/day':>8} {'sigma':>7} {'safety':>8} {'reorder_pt':>11} {'on_hand':>8} {'flag':>8}"
161
+ print(header)
162
+ print("-" * len(header))
163
+ for sku, avg, sigma, safety, rop, oh, flag in results:
164
+ oh_s = f"{oh:g}" if oh is not None else "-"
165
+ print(f"{sku:<20} {avg:>8.2f} {sigma:>7.2f} {safety:>8.1f} {rop:>11.1f} {oh_s:>8} {flag:>8}")
166
+
167
+ reorder = [r for r in results if r[6] == "REORDER"]
168
+ if on_hand:
169
+ print(f"\n{'PASS' if not reorder else 'ACTION'}: {len(reorder)} SKU(s) at/below reorder point.")
170
+
171
+
172
+ if __name__ == "__main__":
173
+ main()
@@ -0,0 +1,88 @@
1
+ ---
2
+ name: job-aplicar
3
+ description: "Orchestrate a full job application from a URL or note: fit score, application note, tailored CV, cover letter, fresh-context review and ATS-verified PDF. Sending is always manual. Triggers: aplicar a esta oferta, postularme a, armar la postulacion, pipeline de aplicacion, preparar CV y cover para una oferta, aplicar a este trabajo."
4
+ argument-hint: "<url | nota de aplicación>"
5
+ category: "career"
6
+ user-invocable: true
7
+ allowed-tools: Read, Glob, Grep, Bash, Write, Edit, WebFetch, WebSearch, Task
8
+ ---
9
+
10
+ # /job-aplicar - Pipeline completo de una postulación
11
+
12
+ Orquesta de punta a punta: evaluar fit → nota de aplicación → CV custom → cover → reviewer → PDF ATS → dashboard. Adaptado del `/apply` de MadsLorentzen/ai-job-search a los skills y reglas de este skillset. **El envío final es siempre manual del usuario; este skill nunca marca `estado: aplicado`** (eso lo registra `/job-outcome` después de enviar).
13
+
14
+ Argumento: `$ARGUMENTS` (URL de la oferta, o nombre/wikilink de una nota de `03-Aplicaciones/`).
15
+
16
+ Rutas relativas al career workspace (`--workspace`, env `CAREER_WORKSPACE`, o `./career-workspace/`). Usa los skills `cv-tailor`, `cover-letter` y `cv-ats-export`, la regla `drafter-reviewer` y, si está instalado, los CLIs y el estado de dedup de `job-scrape`.
17
+
18
+ ## Paso 0 - Resolver argumento y detectar re-entrada
19
+
20
+ - Si es nombre/wikilink: cargar la nota de `03-Aplicaciones/`.
21
+ - Si es URL: canonicalizarla (reglas en `.claude/skills/job-scrape/references/dedup-playbook.md`) y buscar nota existente por `link_oferta` (Grep). Si existe, modo re-entrada.
22
+ - **Detectar desde los artefactos qué pasos ya están hechos** (no hay archivo de estado; los artefactos SON el estado):
23
+ 1. ¿Existe la nota? 2. ¿Tiene `match_score`? 3. ¿`cv_usado` apunta a un CV .md existente en `02-CVs/`? 4. ¿La nota tiene sección "Cover letter" con contenido? 5. ¿Hay resumen de decisiones de review en `## Notas`? 6. ¿Existe el PDF en `02-CVs/exports/` (o `archivo_pdf`)? 7. ¿Hay fila en el dashboard?
24
+ - Informar "retomo desde el paso X" y saltar lo ya hecho.
25
+
26
+ ## Paso 1 - Obtener el JD
27
+
28
+ - URL de GetOnBoard → `python .claude/skills/job-scrape/scripts/getonbrd_search.py detail "<url>" --format plain` (si reporta CERRADA, avisar y preguntar si igual seguir).
29
+ - URL de LinkedIn → `python .claude/skills/job-scrape/scripts/linkedin_search.py detail "<url>" --format plain`.
30
+ - Otra fuente, o si `job-scrape` no está instalado → WebFetch.
31
+ - Registrar `fuente` (getonbrd / linkedin / otro).
32
+
33
+ ## Paso 2 - Score
34
+
35
+ Aplicar la rúbrica de `.claude/skills/job-search/references/scoring-rubric.md` (si el skill job-search no está instalado, pedir instalarlo o scorear a criterio declarando la limitación) contra el perfil correspondiente de `01-Perfiles/` (elegir por stack del JD; preguntar si es ambiguo). Presentar: score 0-100, breakdown por dimensión, gaps reales, red flags.
36
+
37
+ ## Paso 3 - CONFIRMACIÓN 1: ¿proceder?
38
+
39
+ Pedir approval explícito antes de crear cualquier archivo:
40
+ - **Proceder** → paso 4.
41
+ - **Descartar** → si existe `<workspace>/tools/job_scraper/seen_jobs.json`, registrar `status: descartado` + motivo; terminar. No crear nada.
42
+
43
+ ## Paso 4 - Nota de aplicación
44
+
45
+ Crear (o completar) la nota desde el template `Templates/Aplicacion.md` del workspace: frontmatter completo (`estado: interesado`, `match_score`, `link_oferta` canónica, `perfil`, `fuente`, `salario_rango`, `modalidad`, `ubicacion`, `prioridad`, `contacto_principal` si se conoce), secciones "Por qué me interesa" y "Match con mi perfil" (alineaciones + gaps reales), línea en Timeline. En el idioma de las notas del workspace, sin guiones largos.
46
+
47
+ ## Paso 5 - CV custom
48
+
49
+ Usar el skill **cv-tailor** con el JD y el perfil base: genera `02-CVs/CV - <Empresa> - <Rol>.md`, con keywords ATS de la oferta y delta de match reportado. Setear `cv_usado` en la nota de aplicación.
50
+
51
+ ## Paso 6 - Cover letter
52
+
53
+ Usar el skill **cover-letter** según el canal de aplicación (formulario ATS → carta 250-350 palabras; LinkedIn/email → mensaje 150-250). Guardar en la sección "Cover letter (v1)" de la nota.
54
+
55
+ ## Paso 7 - Reviewer (regla drafter-reviewer, obligatorio)
56
+
57
+ Seguir `.claude/rules/drafter-reviewer.md`: lanzar UN agente general-purpose de contexto fresco con el JD + borradores inline; lectura acotada al perfil; research web breve de la empresa. Aplicar la Parte A (reemplazos exactos) e incorporar lo relevante de la Parte B. Una sola ronda. Never stuff keywords: los gaps se reconocen en el cover, no se rellenan en el CV. Normalizar guiones largos del texto sugerido.
58
+
59
+ ## Paso 8 - CONFIRMACIÓN 2: aprobar CV final
60
+
61
+ Mostrar el CV final (y cover) con las decisiones del review aplicadas. Solo con approval explícito, exportar:
62
+
63
+ ```
64
+ python .claude/skills/cv-ats-export/scripts/cv_export.py "CV - <Empresa> - <Rol>"
65
+ ```
66
+
67
+ (o la copia instalada en `<workspace>/tools/`). La verificación ATS corre automática ([ATS OK] esperado; si [ATS WARN], corregir y re-exportar). Registrar la ruta del PDF en la nota (sección "Archivo final exportado" del CV custom).
68
+
69
+ ## Paso 9 - Dashboard
70
+
71
+ Agregar/actualizar la fila de la oferta en la sección de aplicaciones activas de `00-Dashboard.md`, respetando las columnas exactas del header, escapando `\|` en wikilinks con alias. Estado ⬜ (interesado).
72
+
73
+ ## Paso 10 - Cierre
74
+
75
+ - Resumen de 3-5 decisiones de tailoring en `## Notas` de la nota (qué se enfatizó, ángulo de empresa, gaps reconocidos).
76
+ - Si existe seen_jobs: entrada → `nota_creada` (crearla si no existía).
77
+ - Recordatorio al usuario: **enviar es manual**; el PDF y el cover están listos, el link de aplicación está en la nota y el dashboard. Al enviar, correr `/job-outcome <empresa>` para marcar `aplicado`.
78
+
79
+ ## Qué NO hace
80
+
81
+ - No envía la postulación ni marca `aplicado` (ToS de los portales + control de calidad humano).
82
+ - No exporta PDF sin la confirmación 2.
83
+ - No toca otras notas ni otras filas del dashboard.
84
+ - No inventa experiencia: los gaps se reconocen, no se rellenan.
85
+
86
+ ## Handoff
87
+
88
+ Con el PDF verificado y el cover aprobados, el paquete queda READY para enviar a mano. Después de enviar: `/job-outcome <empresa>`. Si hay un contacto directo en la empresa, `/cold-outreach` para el mensaje al recruiter/hiring manager.