kcreate-scaffolding-gcp-script 0.0.0-stage → 0.2.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.
@@ -0,0 +1,72 @@
1
+ #!/usr/bin/env bash
2
+ # Envoltura delgada sobre github-proyect-tracking para este skill. El agente
3
+ # nunca llama a tracking.py directamente — siempre a través de este script,
4
+ # para no tener que recordar flags ni rutas.
5
+ #
6
+ # Uso:
7
+ # run_tracking.sh init-plan <config_file> <plan.json>
8
+ # run_tracking.sh resume <config_file>
9
+ # run_tracking.sh step-start <config_file> "<nombre del paso>"
10
+ # run_tracking.sh step-done <config_file> "<nombre del paso>"
11
+ # run_tracking.sh step-error <config_file> "<nombre del paso>" "<detalle>"
12
+ # run_tracking.sh status <config_file>
13
+ # run_tracking.sh finalize <config_file>
14
+ #
15
+ # Si github-proyect-tracking no está instalado junto a este skill, todos los
16
+ # subcomandos salen con error claro (código 3) en vez de fallar a medias.
17
+
18
+ set -euo pipefail
19
+
20
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
21
+ # shellcheck source=lib/common.sh
22
+ source "$SCRIPT_DIR/lib/common.sh"
23
+
24
+ SUBCOMMAND="${1:-}"
25
+ CONFIG_FILE="${2:-}"
26
+ [[ -n "$SUBCOMMAND" && -n "$CONFIG_FILE" ]] || ksc_die \
27
+ "uso: run_tracking.sh <subcomando> <config_file> [args...]"
28
+ [[ -f "$CONFIG_FILE" ]] || ksc_die "no existe el archivo de configuración: $CONFIG_FILE"
29
+
30
+ REPO_ROOT="$(ksc_repo_root)"
31
+ TRACKING_PY="$REPO_ROOT/.claude/skills/github-proyect-tracking/scripts/tracking.py"
32
+ [[ -f "$TRACKING_PY" ]] || { ksc_err "github-proyect-tracking no está instalado en este repo ($TRACKING_PY no existe). Reinstala con install.sh de KTerragruntScaffoldingScripts (sin --skip-tracking)."; exit 3; }
33
+
34
+ # shellcheck disable=SC1090
35
+ source "$CONFIG_FILE"
36
+ [[ -n "${TRACKING_GITHUB_PROJECT_URL:-}" ]] || ksc_die \
37
+ "TRACKING_GITHUB_PROJECT_URL no está definido en $CONFIG_FILE"
38
+
39
+ TP() { python3 "$TRACKING_PY" "$@" --url "$TRACKING_GITHUB_PROJECT_URL"; }
40
+
41
+ case "$SUBCOMMAND" in
42
+ init-plan)
43
+ PLAN_FILE="${3:-}"
44
+ [[ -n "$PLAN_FILE" && -f "$PLAN_FILE" ]] || ksc_die "init-plan requiere un archivo plan.json existente"
45
+ TP init --plan-file "$PLAN_FILE"
46
+ ;;
47
+ resume)
48
+ TP resume
49
+ ;;
50
+ step-start)
51
+ NAME="${3:-}"; [[ -n "$NAME" ]] || ksc_die "step-start requiere el nombre del paso"
52
+ TP step-start --name "$NAME"
53
+ ;;
54
+ step-done)
55
+ NAME="${3:-}"; [[ -n "$NAME" ]] || ksc_die "step-done requiere el nombre del paso"
56
+ TP step-done --name "$NAME"
57
+ ;;
58
+ step-error)
59
+ NAME="${3:-}"; DETAILS="${4:-sin detalle}"
60
+ [[ -n "$NAME" ]] || ksc_die "step-error requiere el nombre del paso"
61
+ TP step-error --name "$NAME" --details "$DETAILS"
62
+ ;;
63
+ status)
64
+ TP status --remote
65
+ ;;
66
+ finalize)
67
+ TP finalize
68
+ ;;
69
+ *)
70
+ ksc_die "subcomando desconocido: $SUBCOMMAND"
71
+ ;;
72
+ esac
@@ -0,0 +1,81 @@
1
+ # Archivo de configuración de kcreate-scaffolding-gcp-script
2
+ # Generado automáticamente por 01_init_config.sh — no es necesario crearlo a
3
+ # mano. Los campos marcados __PENDIENTE__ no se pudieron inferir del
4
+ # entorno fuente y deben llenarse antes de continuar. Los demás ya vienen
5
+ # autodetectados del entorno fuente (SOURCE_ENV) — revísalos, pero no es
6
+ # obligatorio tocarlos salvo que alguno esté mal.
7
+ #
8
+ # Formato: KEY="value", una por línea. Este archivo se `source`-ea
9
+ # directamente en bash — no agregues nada que no sea una asignación simple
10
+ # o un comentario.
11
+
12
+ # --- Identidad de la ejecución (fijos, no editar tras la primera corrida) ---
13
+ SOURCE_ENV="__SOURCE_ENV__"
14
+ NEW_ENV="__NEW_ENV__"
15
+
16
+ # --- Requeridos: no se pueden inferir del entorno fuente ---
17
+
18
+ # Nombre legible para GCP (project_name). Inferido de NEW_ENV con la
19
+ # convención <App-Con-Mayúscula-Inicial>-<QA|Prod> — confírmalo o corrígelo
20
+ # si el nombre de la app tiene una capitalización distinta a la obvia
21
+ # (ej. siglas, nombres compuestos).
22
+ PROJECT_NAME="__PENDIENTE__"
23
+
24
+ # Repo(s) de GitHub (formato owner/repo) que hacen `push` de imágenes
25
+ # Docker al Artifact Registry de este proyecto nuevo. Se asume que ya
26
+ # existen — este skill NO valida contra GitHub. Uno solo en el caso común;
27
+ # si la app tiene varios repos (como boletera-qa, que maneja 5), sepáralos
28
+ # con ";" — se genera un binding de github_workload_identity por cada uno.
29
+ APP_GITHUB_REPO="__PENDIENTE__"
30
+
31
+ # URL del repo de GitHub (https://github.com/{owner}/{repo}) usado por
32
+ # github-proyect-tracking para registrar el avance de esta ejecución como
33
+ # Issues/comentarios. Necesario para poder reanudar si el proceso se
34
+ # interrumpe.
35
+ TRACKING_GITHUB_PROJECT_URL="__PENDIENTE__"
36
+
37
+ # --- Autodetectados de SOURCE_ENV (verificar, normalmente no hace falta tocar) ---
38
+
39
+ ORG_ID="__PENDIENTE__"
40
+ ORG_BILLING_ACCOUNT="__PENDIENTE__"
41
+ REGION="__PENDIENTE__"
42
+ ZONE="__PENDIENTE__"
43
+ LABEL_OWNER="__PENDIENTE__"
44
+
45
+ # Organización de GitHub usada en github_workload_identity (local.github_organization).
46
+ GITHUB_ORGANIZATION="__PENDIENTE__"
47
+
48
+ # Entradas fijas (no "dev-<usuario>") de sql_postgres.authorized_networks en
49
+ # SOURCE_ENV, separadas por ";" como "nombre=ip/32;nombre2=ip2/32". Se
50
+ # copian tal cual al entorno nuevo — son IPs de oficina/admin compartidas
51
+ # entre entornos, no específicas de SOURCE_ENV.
52
+ FIXED_ADMIN_NETWORKS="__PENDIENTE__"
53
+
54
+ # --- Opcionales, con default vacío (no bloquean CONFIG_READY) -----------
55
+
56
+ # Componentes a incluir ADEMÁS de los base (la intersección real de todos
57
+ # los entornos de live/ — ver ksc_base_components). Por defecto NO se
58
+ # incluyen opcionales como cloud_armor/identity_platform/monitoring_alerts
59
+ # /secrets aunque SOURCE_ENV los tenga, siguiendo la guía (§1.2: "no
60
+ # incluir salvo pedido explícito"). Si NEW_ENV sí los necesita, lístalos
61
+ # aquí separados por ";", ej. "secrets;cloud_armor".
62
+ EXTRA_COMPONENTS=""
63
+
64
+ # Si sql_postgres de SOURCE_ENV usa dns_domain_name, da aquí el dominio
65
+ # real que NEW_ENV usa (ej. "xoloshop.mx."). Déjalo vacío para que el
66
+ # skill genere un valor por patrón (<app>.ninja., copiando la convención
67
+ # de SOURCE_ENV) marcado con [WARN] para confirmación humana — el dominio
68
+ # real registrado puede no coincidir con ese patrón visual.
69
+ DNS_DOMAIN=""
70
+
71
+ # --- Copiados de SOURCE_ENV pero marcados para verificación humana/del ---
72
+ # --- agente: no se puede confirmar que sigan siendo la versión más ---
73
+ # --- reciente sin consultar documentación externa en el momento. ---
74
+
75
+ # Versión de GKE (canal Stable). 10_build_components.sh usa este valor tal
76
+ # cual — si quieres la versión estable más reciente real (no la que ya
77
+ # usaba SOURCE_ENV), verifícala y corrígela aquí antes de construir.
78
+ GKE_VERSION_NEEDS_VERIFICATION="__PENDIENTE__"
79
+
80
+ # Versión mayor de Cloud SQL Postgres. Mismo criterio que GKE_VERSION.
81
+ DATABASE_VERSION_NEEDS_VERIFICATION="__PENDIENTE__"
@@ -0,0 +1,171 @@
1
+ ---
2
+ name: github-proyect-tracking
3
+ description: Track process execution state in GitHub Issues with resume.
4
+ version: 0.4.0
5
+ author: Rommel (rommelmg), Hermes Agent
6
+ license: MIT
7
+ platforms: [linux, macos, windows]
8
+ metadata:
9
+ hermes:
10
+ tags: [github, tracking, state-machine, resume, issues]
11
+ category: software-development
12
+ related_skills: [github]
13
+ ---
14
+
15
+ # github-proyect-tracking Skill
16
+
17
+ Persiste el estado de ejecución de un proceso estructurado en un Issue de
18
+ GitHub (uno por ejecución) y lo reanuda tras interrupciones leyendo el
19
+ último comentario. No ejecuta los pasos del proceso: los instrumenta —
20
+ el agente hace el trabajo real y reporta cada transición con este skill.
21
+ Depende solo de Python 3.8+ (stdlib); `gh` es opcional (fuente de token).
22
+
23
+ **v0.4.0**: cada transición nueva lleva metadatos HTML con `transition_id`
24
+ UUID para reconciliar respuestas perdidas y 500/502/503/504 sin confundir
25
+ otro emisor.
26
+ El resultado CANCELADO se conserva en reintentos, `resume` y `adopt`; tras
27
+ ERROR se exige una reanudación explícita. El lock tiene heartbeat automático,
28
+ el estado usa esquema v2 con migración desde v1 y los instaladores restauran
29
+ el respaldo exacto ante fallos.
30
+
31
+ ## When to Use
32
+
33
+ - El usuario pide rastrear/persistir la ejecución de un proceso con estados
34
+ verificables en GitHub (Issues como bitácora) y reanudar tras fallos.
35
+ - Un proceso se interrumpió (error, caída de sesión) y hay que continuar
36
+ exactamente donde quedó, usando el Issue como fuente de verdad.
37
+ - Contador: "trackea este proceso en GitHub", "reanuda donde quedó",
38
+ "registrando el avance del proceso".
39
+
40
+ Don't use for:
41
+ - Crear issues/PRs de mantenimiento sin seguimiento de proceso
42
+ (usa el skill `github`).
43
+ - Persistir secretos en el cuerpo de un Issue (los `Detalles` se
44
+ sanitizan por patrones, pero no guarantees absolutas: no metas
45
+ credenciales).
46
+
47
+ ## Prerequisites
48
+
49
+ - `github_project_url` exacta: `https://github.com/{owner}/{repo}`
50
+ (obligatoria en `init`; owner/repo se normalizan a minúsculas).
51
+ - Token de GitHub con permisos de escritura sobre Issues (classic PAT
52
+ scope `repo`; fine-grained: Issues → Read and write). Resolución
53
+ automática: `--token` → `GITHUB_TRACKING_TOKEN` → `GITHUB_TOKEN` →
54
+ `GH_TOKEN` → `gh auth token` → archivo de estado (solo con
55
+ `--store-token`). Sin token → pídelo al usuario; nunca lo inventes.
56
+ - Python 3.8+ en PATH (`python3`).
57
+
58
+ ## How to Run
59
+
60
+ Todo corre por `terminal` con un único JSON de respuesta por comando.
61
+ Ruta del script relativa a este skill: `scripts/tracking.py`:
62
+
63
+ ```bash
64
+ SKILL="$HOME/.hermes/profiles/<profile>/skills/software-development/github-proyect-tracking/scripts/tracking.py"
65
+ python3 "$SKILL" doctor --url https://github.com/OWNER/REPO --token "$GITHUB_TOKEN"
66
+ ```
67
+
68
+ ## Quick Reference
69
+
70
+ | Comando | Efecto |
71
+ |---|---|
72
+ | `init --url URL [--plan JSON] [--store-token]` | Valida URL+token, crea el Issue, guarda `active_process_id` |
73
+ | `resume [--url URL]` | Lee el ÚLTIMO comentario y dicta cómo continuar |
74
+ | `step-start --name N` | Comenta `[STATUS: INICIANDO] - Paso: N` (valida orden del plan) |
75
+ | `step-done --name N` | Comenta `[STATUS: COMPLETADO] - Paso: N` (exige paso en vuelo) |
76
+ | `step-error --name N --details D` | Comenta `[STATUS: ERROR]` sanitizado y detiene |
77
+ | `finalize [--force]` | FINALIZADO, o CANCELADO con `--force`; cierra y limpia memoria |
78
+ | `status [--remote]` | Estado local (+ veredicto remoto) |
79
+ | `adopt --url URL --execution-id ID [--force]` | Recupera memoria desde un Issue remoto |
80
+ | `clear [--hard]` | Limpia `active_process_id` (o borra el archivo) |
81
+ | `doctor [--url URL]` | Diagnóstico de entorno y permisos (exit 0) |
82
+
83
+ Exit codes: `0` ok · `2` validación/transición inválida · `3` token sin
84
+ permiso · `4` sin token · `5` sin proceso activo · `6` Issue no
85
+ encontrado/ambiguo · `7` resume requerido · `8` conflicto con el estado
86
+ remoto (Issue cerrado externamente o finalize bloqueado). Excepciones
87
+ documentadas al contrato JSON: `--help`/`--version` (salida humana,
88
+ exit 0) y `KeyboardInterrupt` (130).
89
+
90
+ ## Procedure
91
+
92
+ 1. **Preparar.** `doctor --url URL`. Si `token` o `issues-write` falla,
93
+ pide el PAT al usuario. Criterio: JSON con `"ok": true` en checks
94
+ requeridos (gh es opcional y no rompe `ok`). Un rol demostrado de solo
95
+ lectura hace fallar `issues-write`; `UNVERIFIED` se reserva para permisos
96
+ granulares que la API no puede confirmar.
97
+ 2. **Init.** `init --url URL --plan '["Paso 1: ...", ...]'` (plan
98
+ recomendado: nombres ÚNICOS; hace la reanudación determinista). Si
99
+ devuelve `RESUME_REQUIRED` (exit 7), NO crees otro Issue: ve al paso 3.
100
+ Criterio: `execution_id` e `issue_url` en la salida.
101
+ 3. **Reanudar (si aplica).** `resume` y sigue el `verdict.action`
102
+ (`RERUN_STEP`, `NEXT_STEP`, `START_FIRST`, `ALL_STEPS_DONE`/
103
+ `FINISH_CLEANUP` → finalize, `ABORTED_EXTERNALLY` → pregunta al
104
+ usuario, `NEXT_STEP_AGENT_DEFINED`). `NONE` y `FINISH_CLEANUP` llevan
105
+ `outcome=FINALIZADO|CANCELADO`; conserva ese resultado.
106
+ 4. **Ejecutar pasos.** Por cada paso, en orden estricto: `step-start`
107
+ ANTES de trabajar; si el trabajo falla, `step-error` (el proceso se
108
+ detiene; reanudar exige `resume` y solo permite reintentar ese paso);
109
+ si triunfa, `step-done`. Con plan, el CLI rechaza pasos fuera de orden,
110
+ desconocidos, duplicados o sin `step-start` previo (exit 2). Criterio:
111
+ `comment_url` por transición.
112
+ 5. **Finalizar.** Con todos los pasos COMPLETADO: `finalize` — rechaza
113
+ cerrar procesos incompletos (exit 8 `FINALIZE_BLOCKED`) salvo
114
+ `--force` (que registra CANCELADO, no éxito), y nunca un Issue cerrado
115
+ externamente. Sin plan exige al menos un COMPLETADO para éxito; sin
116
+ trabajo, `--force` cancela. Criterio: `issue_state: "closed"`,
117
+ `active_process_id: null` y `outcome` coherente.
118
+ 6. **Fallos transitorios de red.** GET/PATCH reintentan solos. Si un POST
119
+ pierde la respuesta o recibe 500/502/503/504, se reconcilia por el UUID
120
+ del intento antes de reintentar UNA vez; otro emisor con el mismo texto
121
+ no lo confirma. `init` también busca el Issue por título con paginación.
122
+
123
+ Memoria interna: `~/.github-proyect-tracking/{owner}__{repo}.json`
124
+ (owner/repo en minúsculas; esquema v2, migración automática desde v1 al
125
+ guardar, escritura atómica con tmp único + fsync y permisos 0600). El candado
126
+ por repositorio mantiene heartbeat durante toda la operación y comprueba su
127
+ propiedad antes de efectos remotos y escrituras. `active_process_id` (número
128
+ de Issue; null si no hay proceso) y `github_token` (SOLO con `--store-token`;
129
+ enmascarado en salidas). Overridable con `GITHUB_TRACKING_STATE`.
130
+
131
+ ## Pitfalls
132
+
133
+ - **Título exacto del Issue:** `[Process Tracking] Execution ID: {ID}` —
134
+ no lo edites; `adopt` exige igualdad canónica (IDs parciales se
135
+ rechazan).
136
+ - **Contrato de comentarios:** los formatos visibles `[STATUS: ...]` son
137
+ exactos. La línea HTML de metadatos no cambia ese contrato. Un comentario
138
+ manual que imite el formato se interpretará como evento (el modelo de
139
+ confianza es "cualquiera con acceso al repo puede comentar").
140
+ - **Sanitización de secretos:** `Detalles` y nombres de paso se redactan
141
+ por patrones (tokens GitHub/AWS, Authorization/Bearer, passwords,
142
+ URLs con credenciales) ANTES de publicar — no es una garantía
143
+ absoluta: revisa lo que envías.
144
+ - **`init` con proceso inconcluso** falla con exit 7 a propósito:
145
+ reanuda con `resume` o descarta con `clear`.
146
+ - **`step-error` detiene el proceso** (estado `ERROR`). Continuar exige
147
+ `resume` → `RERUN_STEP` (no se puede hacer `step-done` directo).
148
+ - **`finalize` exige plan completo** (o `--force`); sin plan requiere un
149
+ COMPLETADO para éxito. Un Issue cerrado por un humano nunca se convierte
150
+ en éxito (`ABORTED_EXTERNALLY`, exit 8).
151
+ - **Planes legados duplicados** producen `PLAN_AMBIGUOUS`; asigna nombres
152
+ únicos en el cuerpo del Issue, sin alterar los eventos.
153
+ - **Contexto ambiguo:** varios procesos activos exigen `--url`; `clear`
154
+ también lo exige con varios estados inactivos.
155
+ - **Token en el archivo de estado** solo con `--store-token` (opt-in);
156
+ prefiere variables de entorno o `gh auth token`. `adopt` sí puede usarlo.
157
+ - **Límite de comentarios:** un Issue con >10 000 comentarios produce
158
+ `COMMENTS_TOO_MANY` (error explícito, no veredicto truncado).
159
+
160
+ ## Verification
161
+
162
+ - `doctor` → `"ok": true` (gh opcional no rompe el global).
163
+ - Tras `init`: `status` muestra `active_process_id` = número del Issue.
164
+ - Tras `finalize`: `status` → `process_state: "FINALIZED"` o `"CANCELADO"`
165
+ según `outcome`, `active_process_id: null`; `resume` responde `NONE` con el
166
+ mismo `outcome`.
167
+ - Reanudación determinista: tras `step-error`, `resume` →
168
+ `verdict.action == "RERUN_STEP"` con el mismo paso; `step-done` sin
169
+ `step-start` previo falla con `NO_STEP_IN_FLIGHT`.
170
+ - 141 pruebas descubiertas: `python3 tests/test_tracking.py` (140 offline;
171
+ la integración real opt-in se omite por defecto).
@@ -0,0 +1,163 @@
1
+ # Referencia de API (CLI) — github-proyect-tracking
2
+
3
+ `scripts/tracking.py` — stdlib only, Python 3.8+. Cada comando imprime
4
+ EXACTAMENTE un objeto JSON a stdout y usa un exit code documentado. Los
5
+ ejemplos suponen `PY="python3 <ruta-del-skill>/scripts/tracking.py"`.
6
+
7
+ ## Contrato de respuesta
8
+
9
+ ```json
10
+ { "ok": true, "action": "INIT", "execution_id": "exec-...", "...": "..." }
11
+ ```
12
+
13
+ Error:
14
+
15
+ ```json
16
+ { "ok": false, "error": { "code": "RESUME_REQUIRED", "message": "...",
17
+ "state": {...}, "next": ["resume"] } }
18
+ ```
19
+
20
+ | Exit | code típico | Significado / acción del agente |
21
+ |---|---|---|
22
+ | 0 | — | Éxito |
23
+ | 1 | `NETWORK_ERROR`, `RATE_LIMITED`, `STATE_CORRUPT`, `COMMENTS_TOO_MANY`, `STATE_LOCKED`, `STATE_LOCK_LOST`… | Reintentar / diagnosticar / `resume` |
24
+ | 2 | `VALIDATION_ERROR`, `NO_STEP_IN_FLIGHT`, `STEP_OUT_OF_ORDER`, `STEP_NOT_IN_PLAN`, `STEP_ALREADY_DONE`, `STEP_IN_FLIGHT`, `CONTEXT_AMBIGUOUS` | Corregir argumentos o transición |
25
+ | 3 | `INVALID_TOKEN`, `INSUFFICIENT_SCOPE`, `NO_REPO_ACCESS`, `REPO_NOT_FOUND`, `ISSUES_WRITE_FAILED` | Token sin `issues:write` |
26
+ | 4 | `NO_TOKEN` | Pedir el token al usuario |
27
+ | 5 | `NO_STATE`, `NO_ACTIVE_PROCESS` | `init` primero |
28
+ | 6 | `ISSUE_NOT_FOUND`, `ISSUE_AMBIGUOUS` | Verificar número / `adopt` (ID EXACTO) |
29
+ | 7 | `RESUME_REQUIRED` (proceso inconcluso, estado `ERROR`, `adopt` sobre activo) | `resume` (o `adopt --force`) |
30
+ | 8 | `FINALIZE_BLOCKED`, `ABORTED_EXTERNALLY`, `ISSUE_CLOSED_EXTERNALLY`, `ALREADY_FINALIZED` | Conflicto con estado remoto |
31
+
32
+ `finalize --force` publica `[STATUS: CANCELADO] - El proceso fue cerrado
33
+ anticipadamente (--force).` con los pasos pendientes — distinguible del
34
+ éxito (`FINALIZADO`). `NONE` y `FINISH_CLEANUP` incluyen `outcome:
35
+ FINALIZADO|CANCELADO`; reintentar, reanudar o adoptar nunca reclasifica una
36
+ cancelación como éxito.
37
+
38
+ Los comentarios nuevos conservan exactamente esos formatos visibles y añaden
39
+ una línea HTML `<!-- github-proyect-tracking: {...} -->` con `execution_id`,
40
+ `sequence` y `transition_id` UUID. El parser ignora esos metadatos al leer el
41
+ texto y los usa para reconciliar únicamente el intento que originó el POST.
42
+
43
+ ## Comandos
44
+
45
+ ### `init --url URL [--plan JSON | --plan-file F] [--token T] [--prompt-token] [--store-token]`
46
+
47
+ - Valida la URL (regex, redactada en errores), resuelve el token, valida
48
+ `issues:write` ANTES de crear nada.
49
+ - Crea el Issue `[Process Tracking] Execution ID: {exec_id}` con el cuerpo
50
+ estándar (metadatos + plan numerado SANITIZADO) y guarda
51
+ `active_process_id`.
52
+ - `--plan` recomendado: lista JSON de cadenas (nombres únicos tras
53
+ normalizar: strip+sanitización; duplicados y colisiones se rechazan) →
54
+ `resume` determinista.
55
+ - `--store-token`: persistencia del token OPT-IN (por defecto NO se
56
+ guarda; `--no-store-token` quedó obsoleto).
57
+
58
+ Salida: `execution_id`, `issue_number`, `issue_url`, `state_file`, `plan`,
59
+ `first_step`, `token_source`.
60
+
61
+ ### `resume [--url URL] [--token T]`
62
+
63
+ - Trae Issue + TODOS los comentarios (paginación por `Link`), parsea
64
+ eventos, emite `verdict` (ver tabla en `references/architecture.md`).
65
+ - `--url` opcional si hay exactamente UN proceso activo; con varios
66
+ activos → `CONTEXT_AMBIGUOUS` (el error lista los repos activos).
67
+ - Registra `last_resume`: después de ERROR autoriza únicamente el reintento
68
+ del paso fallido. Un plan legado duplicado produce `PLAN_AMBIGUOUS` con
69
+ instrucciones para asignar nombres únicos en el cuerpo del Issue.
70
+
71
+ Salida: `issue_state`, `comment_count`, `verdict{action, outcome?,
72
+ resume_from_step, remaining_steps?, failed_step?, error_details?, reason}`.
73
+
74
+ ### `step-start --name N` / `step-done --name N`
75
+
76
+ Publican exactamente:
77
+
78
+ ```
79
+ [STATUS: INICIANDO] - Paso: {N}
80
+ [STATUS: COMPLETADO] - Paso: {N}
81
+ ```
82
+
83
+ ### `step-error --name N (--details D | --details-file F)`
84
+
85
+ Publica `[STATUS: ERROR] - Paso: {N} - Detalles: {D}` y fija
86
+ `process_state: "ERROR"` — **el proceso se detiene aquí** (por diseño;
87
+ reanudar exige `resume`). `--details-file` ideal para stacktraces largos
88
+ (trunca a 60000 chars).
89
+
90
+ ### `finalize [--url URL] [--token T] [--force]`
91
+
92
+ 1. Verifica el veredicto remoto: rechaza procesos con pasos pendientes
93
+ (`FINALIZE_BLOCKED`, exit 8) y abortes externos. Sin plan permite éxito
94
+ después de un COMPLETADO; sin trabajo exige `--force`.
95
+ 2. Sin `--force`: comenta `[STATUS: FINALIZADO] - El proceso concluyó
96
+ exitosamente.` (idempotente). Con `--force`: `[STATUS: CANCELADO] -
97
+ El proceso fue cerrado anticipadamente (--force).` con los pasos
98
+ pendientes (cierre anticipado distinguible, nunca éxito falso).
99
+ 3. Cierra el Issue (PATCH state=closed) y **verifica re-leyendo**.
100
+ 4. Limpia `active_process_id` (estado `FINALIZED`/`CANCELADO`) + historial
101
+ (con `outcome`). Un CANCELADO existente conserva ese resultado aunque el
102
+ reintento no lleve `--force`.
103
+
104
+ ### `status [--url URL] [--remote]`
105
+
106
+ Estado local enmascarado (nunca imprime el token) y, con `--remote`,
107
+ veredicto fresco del Issue.
108
+
109
+ ### `adopt --url URL --execution-id ID [--token T] [--force]`
110
+
111
+ Recuperación: busca el Issue por título canónico EXACTO (el ID completo;
112
+ parciales rechazados, ambigüedad → `ISSUE_AMBIGUOUS`), reconstruye el
113
+ archivo de estado local. Carga el estado candidato antes de resolver el
114
+ token, por lo que puede usar el guardado con `--store-token`. No sustituye
115
+ un proceso activo sin `--force` (que conserva token e historial). Si adopta
116
+ un ERROR, guarda `process_state=ERROR` y exige `resume`; conserva CANCELADO
117
+ y ABORTED según el Issue. Útil tras perder la máquina local o tras un
118
+ `clear --hard` accidental.
119
+
120
+ ### `clear [--url URL] [--hard]`
121
+
122
+ Soft: `active_process_id := null`, `process_state := "CLEARED"` (conserva
123
+ token/historial). Hard: elimina el archivo. Si el proceso era inconcluso,
124
+ avisa: el Issue queda abierto en GitHub (recuperable con `adopt`). Sin URL,
125
+ varios estados locales —activos o inactivos— producen `CONTEXT_AMBIGUOUS`;
126
+ nunca se elige un archivo destructivamente por fecha.
127
+
128
+ ### `doctor [--url URL] [--token T]`
129
+
130
+ Checks: python ≥ 3.8, directorio de estado escribible (crea el directorio
131
+ y un archivo de prueba — efecto de escritura declarado), token resuelto
132
+ (incluye el guardado con `--store-token` del repo de `--url`), GitHub API
133
+ + rate limit, `gh` CLI (opcional), archivos de estado, URL válida y
134
+ `issues:write` (con `--url`). Siempre exit 0 — es un reporte.
135
+
136
+ ## Variables de entorno
137
+
138
+ | Variable | Uso |
139
+ |---|---|
140
+ | `GITHUB_TRACKING_TOKEN` | Token (prioridad máxima de las fuentes automáticas) |
141
+ | `GITHUB_TOKEN` / `GH_TOKEN` | Fallbacks estándar |
142
+ | `GITHUB_TRACKING_STATE` | Directorio alternativo de archivos de estado |
143
+
144
+ ## Detalles de la API REST usada
145
+
146
+ | Operación | Endpoint |
147
+ |---|---|
148
+ | Validación de repo/permisos | `GET /repos/{owner}/{repo}` |
149
+ | Crear Issue | `POST /repos/{owner}/{repo}/issues` |
150
+ | Leer Issue / cerrar Issue | `GET` / `PATCH /repos/{owner}/{repo}/issues/{n}` |
151
+ | Comentarios (paginado 100/pág) | `GET /repos/{owner}/{repo}/issues/{n}/comments` |
152
+ | Publicar comentario | `POST /repos/{owner}/{repo}/issues/{n}/comments` |
153
+ | Buscar Issue (adopt) | `GET /search/issues?q=...` |
154
+ | Rate limit (doctor) | `GET /rate_limit` |
155
+
156
+ Cabeceras: `Authorization: Bearer`, `Accept: application/vnd.github+json`,
157
+ `X-GitHub-Api-Version: 2022-11-28`, `User-Agent: github-proyect-tracking/{v}`.
158
+ Reintentos: GET/PATCH/5xx con backoff. Ante respuesta perdida o
159
+ 500/502/503/504 en POST, se busca el `transition_id` UUID fuera de la línea
160
+ base y solo entonces se reconcilia; si no aparece, hay un único reintento con
161
+ el mismo UUID y una segunda reconciliación. La creación del Issue usa búsqueda
162
+ paginada por título exacto. Rate limit 403/429 con `X-RateLimit-Remaining: 0`
163
+ espera controlada (máx 90 s).
@@ -0,0 +1,171 @@
1
+ # Arquitectura — github-proyect-tracking
2
+
3
+ ## Visión general
4
+
5
+ El skill instrumenta procesos (no los ejecuta): convierte cada transición en
6
+ un evento persistido en un Issue de GitHub y mantiene una memoria local para
7
+ saber si hay un proceso inconcluso y en qué Issue vive.
8
+
9
+ ```
10
+ ┌──────────────┐ instrucción ┌─────────────────────────────────────┐
11
+ │ Agente (tú) │ ───────────────▶ │ scripts/tracking.py (CLI, stdlib) │
12
+ │ │ ◀─────────────── │ · 1 JSON por comando + exit code │
13
+ └──────────────┘ JSON + exit └───────────────┬─────────────────────┘
14
+ │ trabajo real del proceso │
15
+ ▼ ▼
16
+ ┌──────────────────┐ comentarios [STATUS:…] ┌──────────────────────────┐
17
+ │ Memoria interna │ ◀──────────────────────── │ GitHub REST API v3 │
18
+ │ ~/.github- │ Issue #N (fuente de │ /repos/{o}/{r}/issues/… │
19
+ │ proyect-tracking/│ verdad al reanudar) │ Bearer token │
20
+ │ {owner}__{repo}. │ └──────────────────────────┘
21
+ │ json │
22
+ └──────────────────┘
23
+ ```
24
+
25
+ Doble libro:
26
+
27
+ 1. **Remoto (autoritativo):** un Issue con título
28
+ `[Process Tracking] Execution ID: {id}`; cada transición es un comentario
29
+ con contrato visible exacto y el último evento decide la reanudación.
30
+ 2. **Local (rápido):** `~/.github-proyect-tracking/{owner}__{repo}.json` con
31
+ esquema v2, `active_process_id`, `github_token` opcional, plan y metadatos.
32
+ La carga migra v1 a v2 en memoria y el siguiente guardado la persiste.
33
+ Escritura atómica (tmp único + fsync + rename), permisos 0600.
34
+
35
+ ## Contrato de eventos y reconciliación
36
+
37
+ Formatos visibles:
38
+
39
+ ```text
40
+ [STATUS: INICIANDO] - Paso: {nombre}
41
+ [STATUS: ERROR] - Paso: {nombre} - Detalles: {detalles}
42
+ [STATUS: COMPLETADO] - Paso: {nombre}
43
+ [STATUS: FINALIZADO] - El proceso concluyó exitosamente.
44
+ [STATUS: CANCELADO] - El proceso fue cerrado anticipadamente (--force).
45
+ ```
46
+
47
+ Los eventos nuevos añaden una línea HTML invisible:
48
+
49
+ ```text
50
+ <!-- github-proyect-tracking: {"execution_id":"...","sequence":1,"transition_id":"..."} -->
51
+ ```
52
+
53
+ `transition_id` es un UUID por intento y se conserva en el reintento del mismo
54
+ comando. Ante respuesta de red perdida o HTTP 500/502/503/504, el cliente lee
55
+ los comentarios y solo reconcilia si encuentra ese UUID fuera de la línea base.
56
+ Un texto idéntico antiguo o nuevo de otro emisor no basta. Si no lo encuentra,
57
+ reintenta una vez y vuelve a reconciliar. `init` recupera una creación ambigua
58
+ buscando el título exacto con paginación; si no lo encuentra, el error incluye
59
+ Execution ID, título y comando `adopt` sugerido.
60
+
61
+ El parser elimina los metadatos antes de aplicar `COMMENT_RE`, por lo que los
62
+ formatos visibles y los comentarios antiguos siguen siendo compatibles.
63
+ `Detalles` admite stacktraces multilínea. `events_from_comments()` ordena por
64
+ `(created_at, comment_id)`, sin confiar en el orden de llegada.
65
+
66
+ ## Máquina de estados del proceso
67
+
68
+ ```
69
+ init (URL válida + token issues:write)
70
+ (sin proceso) ───────────────────────────────────────▶ RUNNING
71
+ │
72
+ ┌─────────── step-start / step-done ───────────────┤
73
+ ▼ │
74
+ RUNNING ── step-error ──▶ ERROR ──(resume: RERUN_STEP)─┘
75
+ │
76
+ ├──── finalize: FINALIZADO + Issue cerrado ────────▶ FINALIZED
77
+ └──── finalize --force: CANCELADO + Issue cerrado ─▶ CANCELADO
78
+ (active_process_id := null)
79
+ ```
80
+
81
+ | Estado en memoria | Significado | `active_process_id` |
82
+ |---|---|---|
83
+ | `RUNNING` | Issue abierto, último evento INICIANDO/COMPLETADO | número |
84
+ | `ERROR` | Último evento ERROR; proceso detenido | número |
85
+ | `FINALIZED` | Issue cerrado con FINALIZADO | `null` |
86
+ | `CANCELADO` | Issue cerrado anticipadamente con CANCELADO | `null` |
87
+ | `ABORTED` | Issue cerrado sin FINALIZADO/CANCELADO | `null` |
88
+ | `CLEARED` | Memoria descartada localmente | `null` |
89
+
90
+ `is_unfinished()` exige ID activo y estado RUNNING o ERROR. `init` sobre un
91
+ proceso inconcluso falla con exit 7 (`RESUME_REQUIRED`). El esquema valida
92
+ tipos y estas invariantes cruzadas; un estado terminal no puede conservar ID
93
+ activo.
94
+
95
+ Después de ERROR, `resume` registra `last_resume` con Issue y comentario de
96
+ error. `step-start` solo acepta esa autorización para reintentar el mismo paso;
97
+ `step-done` y `step-error` también se detienen mientras el último evento remoto
98
+ sea ERROR. `adopt` reconstruye ERROR, nunca lo sustituye por RUNNING.
99
+
100
+ ## Ciclo de vida de una ejecución
101
+
102
+ 1. **init** — valida URL y permisos, genera `exec-{timestamp}-{12 hex}`, crea
103
+ el Issue y guarda memoria. Un plan duplicado nuevo se rechaza.
104
+ 2. **resume** — lee Issue y todos los comentarios, deriva el veredicto y
105
+ sincroniza el estado. Los planes legados duplicados producen
106
+ `PLAN_AMBIGUOUS` con instrucciones de reparación; no se deduplican.
107
+ 3. **step-start / step-error / step-done** — una transición por comentario,
108
+ validada contra la misma instantánea remota.
109
+ 4. **finalize** — publica el evento terminal, cierra y verifica el Issue,
110
+ limpia memoria y conserva las últimas 20 entradas de historial.
111
+
112
+ Con plan, el éxito exige todos los pasos completados. Sin plan, el último
113
+ evento COMPLETADO permite FINALIZADO; sin trabajo, `finalize` se bloquea y
114
+ `--force` registra CANCELADO. El `outcome` siempre procede del evento remoto:
115
+ un reintento sin `--force` no convierte CANCELADO en FINALIZADO.
116
+
117
+ ## Veredictos de `resume`
118
+
119
+ | Último evento / situación | `verdict.action` | `outcome` | Acción |
120
+ |---|---|---|---|
121
+ | FINALIZADO + Issue abierto | `FINISH_CLEANUP` | `FINALIZADO` | Ejecutar `finalize` |
122
+ | CANCELADO + Issue abierto | `FINISH_CLEANUP` | `CANCELADO` | Ejecutar `finalize` |
123
+ | FINALIZADO + Issue cerrado | `NONE` | `FINALIZADO` | Nada; memoria auto-limpia |
124
+ | CANCELADO + Issue cerrado | `NONE` | `CANCELADO` | Nada; conservar cancelación |
125
+ | ERROR | `RERUN_STEP` | — | Re-ejecutar ese paso tras esta autorización |
126
+ | INICIANDO | `RERUN_STEP` | — | Re-ejecutar el paso en vuelo |
127
+ | COMPLETADO + plan pendiente | `NEXT_STEP` | — | Ejecutar `resume_from_step` |
128
+ | COMPLETADO + plan agotado | `ALL_STEPS_DONE` | — | Ejecutar `finalize` |
129
+ | Sin comentarios | `START_FIRST` | — | Empezar por el paso 1 |
130
+ | Issue cerrado sin evento terminal | `ABORTED_EXTERNALLY` | — | Preguntar al usuario |
131
+ | Sin plan explícito | `NEXT_STEP_AGENT_DEFINED` | — | Elegir paso o finalizar tras COMPLETADO |
132
+
133
+ ## Módulos
134
+
135
+ | Archivo | Responsabilidad |
136
+ |---|---|
137
+ | `scripts/tracking.py` | Parser CLI, punto de entrada y exportaciones compatibles |
138
+ | `tracking_core/common.py` | Versión, constantes, errores y validación/redacción |
139
+ | `tracking_core/events.py` | Contrato visible, metadatos, parser y planner |
140
+ | `tracking_core/state.py` | Esquema v2, migración, persistencia, selección y locks |
141
+ | `tracking_core/github.py` | Token, preflight, transporte, paginación y reconciliación |
142
+ | `tracking_core/commands.py` | Comandos y guardas sobre instantáneas remotas |
143
+
144
+ ## Locks y robustez
145
+
146
+ `StateLock` usa un directorio por repositorio, propietario con token y un hilo
147
+ daemon que refresca heartbeat durante toda la operación, incluidas las llamadas
148
+ HTTP. `ensure_owner()` se exige antes de efectos remotos y antes de guardar o
149
+ borrar. Perder el lock produce `STATE_LOCK_LOST` y exige `resume`.
150
+
151
+ - Escritura atómica, estado 0600, tokens enmascarados y persistencia opt-in.
152
+ - Reintentos de GET/PATCH y rate limit con espera acotada.
153
+ - Paginación completa por `Link`; más de 10 000 comentarios →
154
+ `COMMENTS_TOO_MANY`.
155
+ - `adopt` usa el token almacenado antes de buscar el Issue.
156
+ - `clear` sin URL exige contexto único también cuando solo hay estados
157
+ inactivos; varios candidatos → `CONTEXT_AMBIGUOUS`.
158
+ - Preflight: un rol demostrado de solo lectura falla; `triage`, `push`,
159
+ `maintain` o `admin` habilitan la gestión de Issues.
160
+ - Redacción de todo userinfo, query y fragment en URLs reflejadas.
161
+
162
+ ## Límites y garantías
163
+
164
+ - Un proceso activo por repositorio (la memoria es por `{owner}/{repo}`).
165
+ - Los eventos son append-only; una persona con acceso puede imitar el contrato
166
+ visible. Usa repositorios controlados y revisa la cronología ante anomalías.
167
+ - No hay transacciones distribuidas: el lock protege el estado local y el
168
+ heartbeat evita su expiración durante una operación viva, pero dos agentes
169
+ independientes pueden intervenir manualmente en el mismo Issue.
170
+ - La integración real es opt-in y requiere autorización y un repositorio
171
+ desechable; no se ejecuta en la suite offline por defecto.