github-proyect-tracking 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +58 -5
- package/package.json +1 -1
- package/skill/SKILL.md +5 -1
- package/skill/references/api.md +10 -2
- package/skill/scripts/tracking.py +5 -0
- package/skill/scripts/tracking_core/commands.py +46 -1
- package/skill/scripts/tracking_core/common.py +1 -1
- package/skill/scripts/tracking_core/events.py +26 -1
package/README.md
CHANGED
|
@@ -6,6 +6,12 @@ de GitHub mediante Issues y comentarios**. Si el proceso se interrumpe (error,
|
|
|
6
6
|
caída de sesión, reinicio), el skill reanuda exactamente donde quedó leyendo el
|
|
7
7
|
último comentario del Issue.
|
|
8
8
|
|
|
9
|
+
> **v0.6.0** — el Issue documenta QUÉ y CON QUÉ: `--process-name` nombra
|
|
10
|
+
> el proceso (título `[Process Tracking][N] …`) y `--process-inputs
|
|
11
|
+
> '{"k":"v",…}` publica los parámetros de entrada del proceso en una
|
|
12
|
+
> sección del cuerpo (secretos redactados) — la única forma de saber
|
|
13
|
+
> exactamente qué tarea estaba realizando la ejecución.
|
|
14
|
+
>
|
|
9
15
|
> **v0.5.0** — el Issue ahora identifica el proceso rastreado:
|
|
10
16
|
> `init --process-name "Mi Skill"` produce el título
|
|
11
17
|
> `[Process Tracking][Mi Skill] Execution ID: …` y el cuerpo incluye
|
|
@@ -22,7 +28,14 @@ caída de sesión, reinicio), el skill reanuda exactamente donde quedó leyendo
|
|
|
22
28
|
`github-proyect-tracking` convierte cada ejecución de un proceso en un Issue de
|
|
23
29
|
GitHub que funciona como bitácora persistente y auditable:
|
|
24
30
|
|
|
25
|
-
- **Un Issue por ejecución**, titulado
|
|
31
|
+
- **Un Issue por ejecución**, titulado
|
|
32
|
+
`[Process Tracking][{proceso}] Execution ID: {id}` — el nombre del proceso
|
|
33
|
+
(skill padre) se declara con `--process-name` y deja claro **qué** se
|
|
34
|
+
rastrea; sin él se conserva el formato legado
|
|
35
|
+
`[Process Tracking] Execution ID: {id}`.
|
|
36
|
+
- **Parámetros de entrada del proceso** (`--process-inputs '{"k":"v",…}`): se
|
|
37
|
+
publican en la sección "Parámetros de entrada" del cuerpo del Issue — la
|
|
38
|
+
constancia de **con qué** se ejecutó la tarea (secretos redactados).
|
|
26
39
|
- **Cada transición del proceso es un comentario** con un contrato visible
|
|
27
40
|
cerrado y sin ambigüedades:
|
|
28
41
|
|
|
@@ -54,6 +67,7 @@ transición. No hay dependencias de terceros: solo Python 3.8+ (stdlib);
|
|
|
54
67
|
| | |
|
|
55
68
|
|---|---|
|
|
56
69
|
| Entrada obligatoria | `github_project_url` — `https://github.com/{owner}/{repo}` |
|
|
70
|
+
| Identificación del proceso | `--process-name N` (skill padre; título del Issue) y `--process-inputs JSON` (parámetros de entrada, sección del cuerpo) — ambos opcionales pero recomendados |
|
|
57
71
|
| Salida de cada comando | Un único objeto JSON + exit code documentado |
|
|
58
72
|
| Permisos requeridos | PAT classic con scope `repo`, o fine-grained con Issues → Read and write |
|
|
59
73
|
| Plataformas | Linux, macOS, Windows |
|
|
@@ -171,7 +185,7 @@ cp -R github-proyect-tracking/ ~/.hermes/profiles/<perfil>/skills/software-devel
|
|
|
171
185
|
### Ejecutar las pruebas
|
|
172
186
|
|
|
173
187
|
```bash
|
|
174
|
-
python3 tests/test_tracking.py #
|
|
188
|
+
python3 tests/test_tracking.py # 172 pruebas: 140 offline + 1 skip opt-in
|
|
175
189
|
```
|
|
176
190
|
|
|
177
191
|
---
|
|
@@ -334,9 +348,48 @@ code documentado:
|
|
|
334
348
|
```json
|
|
335
349
|
{ "ok": true, "action": "INIT", "execution_id": "exec-20261028T101500Z-ab12cd34ef56",
|
|
336
350
|
"issue_number": 42, "issue_url": "https://github.com/o/r/issues/42",
|
|
351
|
+
"process_name": "Mi Skill", "issue_title": "[Process Tracking][Mi Skill] Execution ID: exec-20261028T101500Z-ab12cd34ef56",
|
|
352
|
+
"process_inputs": { "archivo": "datos.csv", "modo": "lint" },
|
|
337
353
|
"state_file": "~/.github-proyect-tracking/o__r.json", "first_step": "Paso 1" }
|
|
338
354
|
```
|
|
339
355
|
|
|
356
|
+
`process_name` y `process_inputs` son `null` cuando no se pasaron las
|
|
357
|
+
opciones correspondientes.
|
|
358
|
+
|
|
359
|
+
### Ejemplo del Issue generado
|
|
360
|
+
|
|
361
|
+
Con `init --url … --process-name "Mi Skill" --process-inputs
|
|
362
|
+
'{"archivo":"datos.csv","modo":"lint"}' --plan '["Paso 1","Paso 2"]'`:
|
|
363
|
+
|
|
364
|
+
```markdown
|
|
365
|
+
[Process Tracking][Mi Skill] Execution ID: exec-20261030T153448Z-9071da2dae76
|
|
366
|
+
```
|
|
367
|
+
*(título)* — y el cuerpo:
|
|
368
|
+
|
|
369
|
+
```markdown
|
|
370
|
+
Proceso rastreado por el skill `github-proyect-tracking`.
|
|
371
|
+
|
|
372
|
+
- **Execution ID:** `exec-20261030T153448Z-9071da2dae76`
|
|
373
|
+
- **Proceso:** Mi Skill
|
|
374
|
+
- **Proyecto:** https://github.com/o/r
|
|
375
|
+
- **Iniciado (UTC):** 2026-10-30T15:34:48+00:00
|
|
376
|
+
|
|
377
|
+
## Parámetros de entrada
|
|
378
|
+
|
|
379
|
+
- **archivo:** `datos.csv`
|
|
380
|
+
- **modo:** `lint`
|
|
381
|
+
|
|
382
|
+
## Plan de pasos
|
|
383
|
+
|
|
384
|
+
1. Paso 1
|
|
385
|
+
2. Paso 2
|
|
386
|
+
|
|
387
|
+
> El avance se registra como comentarios con el contrato `[STATUS: ...]`.
|
|
388
|
+
> El ÚLTIMO comentario es la fuente de verdad para reanudar.
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
Ejemplo de error (JSON con `ok: false` + exit code documentado):
|
|
392
|
+
|
|
340
393
|
```json
|
|
341
394
|
{ "ok": false, "error": { "code": "RESUME_REQUIRED", "message": "...",
|
|
342
395
|
"state": { "...": "..." }, "next": ["resume"] } }
|
|
@@ -361,7 +414,7 @@ Excepciones documentadas al contrato "un JSON por comando": `--help`/
|
|
|
361
414
|
|
|
362
415
|
| Comando | Efecto | Efectos secundarios |
|
|
363
416
|
|---|---|---|
|
|
364
|
-
| `init --url URL [--plan JSON \| --plan-file F] [--token T] [--store-token] [--process-name N]` | Crea el Issue y la memoria (plan sanitizado; `N` = skill padre
|
|
417
|
+
| `init --url URL [--plan JSON \| --plan-file F] [--token T] [--store-token] [--process-name N] [--process-inputs JSON]` | Crea el Issue y la memoria (plan sanitizado; `N` = skill padre en título/cuerpo; JSON = parámetros de entrada del proceso, publicados en el Issue con secretos redactados) | Issue nuevo en GitHub + archivo local |
|
|
365
418
|
| `resume [--url URL]` | Veredicto de reanudación | Actualiza memoria local |
|
|
366
419
|
| `step-start --name N` | Comenta INICIANDO | Comentario + memoria |
|
|
367
420
|
| `step-done --name N` | Comenta COMPLETADO | Comentario + memoria |
|
|
@@ -414,7 +467,7 @@ rechazan.
|
|
|
414
467
|
|
|
415
468
|
### 4.6 Pruebas
|
|
416
469
|
|
|
417
|
-
La suite descubre **
|
|
470
|
+
La suite descubre **172 pruebas**: **140 offline** y una integración real
|
|
418
471
|
omitida por defecto. Las pruebas offline usan stdlib, `FakeGateway`, inyección
|
|
419
472
|
de fallos, subprocesos CLI e instaladores reales en directorios temporales.
|
|
420
473
|
Cubren los formatos visibles, máquina de estados, reconciliación con UUID y
|
|
@@ -423,7 +476,7 @@ contexto ambiguo, seguridad, rollback y contrato JSON.
|
|
|
423
476
|
|
|
424
477
|
```bash
|
|
425
478
|
python3 tests/test_tracking.py
|
|
426
|
-
# Ran
|
|
479
|
+
# Ran 172 tests ... OK (skipped=1)
|
|
427
480
|
python3 tools/check_docs.py
|
|
428
481
|
```
|
|
429
482
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "github-proyect-tracking",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Instalador npx del skill github-proyect-tracking para Hermes Agent: rastrea el estado de procesos en GitHub Issues con reanudacion.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"github-proyect-tracking": "bin/cli.js"
|
package/skill/SKILL.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: github-proyect-tracking
|
|
3
3
|
description: Track process execution state in GitHub Issues with resume.
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.6.0
|
|
5
5
|
author: Rommel (rommelmg), Hermes Agent
|
|
6
6
|
license: MIT
|
|
7
7
|
platforms: [linux, macos, windows]
|
|
@@ -100,6 +100,10 @@ exit 0) y `KeyboardInterrupt` (130).
|
|
|
100
100
|
`--process-name "Mi Skill"`: el título del Issue pasa a
|
|
101
101
|
`[Process Tracking][Mi Skill] Execution ID: …` y el cuerpo incluye
|
|
102
102
|
`- **Proceso:** Mi Skill` — así el Issue identifica QUÉ se rastrea.
|
|
103
|
+
Para dejar constancia de CON QUÉ se ejecutó, añade `--process-inputs
|
|
104
|
+
'{"archivo":"x.csv","modo":"lint"}'` (objeto clave→valor; se publica
|
|
105
|
+
en la sección "Parámetros de entrada" del Issue, con secretos
|
|
106
|
+
redactados).
|
|
103
107
|
Si devuelve `RESUME_REQUIRED` (exit 7), NO crees otro Issue: ve al
|
|
104
108
|
paso 3. Criterio: `execution_id`, `process_name` e `issue_url` en la
|
|
105
109
|
salida.
|
package/skill/references/api.md
CHANGED
|
@@ -42,7 +42,7 @@ texto y los usa para reconciliar únicamente el intento que originó el POST.
|
|
|
42
42
|
|
|
43
43
|
## Comandos
|
|
44
44
|
|
|
45
|
-
### `init --url URL [--plan JSON | --plan-file F] [--token T] [--prompt-token] [--store-token] [--process-name N]`
|
|
45
|
+
### `init --url URL [--plan JSON | --plan-file F] [--token T] [--prompt-token] [--store-token] [--process-name N] [--process-inputs JSON]`
|
|
46
46
|
|
|
47
47
|
- Valida la URL (regex, redactada en errores), resuelve el token, valida
|
|
48
48
|
`issues:write` ANTES de crear nada.
|
|
@@ -59,7 +59,15 @@ texto y los usa para reconciliar únicamente el intento que originó el POST.
|
|
|
59
59
|
`- **Proceso:** N`; estado y JSON llevan `process_name`. Sin `N`, se
|
|
60
60
|
conserva el formato legado `[Process Tracking] Execution ID: …`.
|
|
61
61
|
Un secreto en `N` se redacta; `[`/`]`/saltos → `VALIDATION_ERROR`.
|
|
62
|
-
-
|
|
62
|
+
- `--process-inputs JSON`: objeto clave→valor con los parámetros de
|
|
63
|
+
entrada del proceso. Se publican en la sección "Parámetros de entrada"
|
|
64
|
+
del cuerpo del Issue (ordenados por clave, valores entre backticks) y
|
|
65
|
+
se guardan en el estado (`process_inputs`) y en la salida de `init`
|
|
66
|
+
(`process_inputs`). Secretos en claves/valores se REDACTAN; límites:
|
|
67
|
+
50 pares, valores ≤ 2000 caracteres; claves vacías, JSON inválido o no
|
|
68
|
+
objeto → `VALIDATION_ERROR`. Sin la opción, el estado queda limpio
|
|
69
|
+
(`process_inputs: null`) y el cuerpo no tiene la sección.
|
|
70
|
+
- Salida de `init`: añade `process_name`, `process_inputs` e `issue_title`.
|
|
63
71
|
|
|
64
72
|
Salida: `execution_id`, `issue_number`, `issue_url`, `state_file`, `plan`,
|
|
65
73
|
`first_step`, `token_source`.
|
|
@@ -101,6 +101,11 @@ def build_parser():
|
|
|
101
101
|
subparser.add_argument("--process-name", default=None, metavar="N",
|
|
102
102
|
help="nombre del proceso/skill padre que se rastrea "
|
|
103
103
|
"(aparece en el título y cuerpo del Issue)")
|
|
104
|
+
subparser.add_argument("--process-inputs", default=None, metavar="JSON",
|
|
105
|
+
help='parámetros de entrada del proceso (objeto '
|
|
106
|
+
"clave→valor, se publican en el Issue; los "
|
|
107
|
+
"secretos se redactan), ej. \'{\"archivo\": "
|
|
108
|
+
'\"datos.csv\"}\'')
|
|
104
109
|
subparser.add_argument("--prompt-token", action="store_true",
|
|
105
110
|
help="pedir el token interactivamente (TTY)")
|
|
106
111
|
subparser.add_argument("--store-token", action="store_true",
|
|
@@ -97,6 +97,47 @@ def load_plan(args):
|
|
|
97
97
|
return plan
|
|
98
98
|
|
|
99
99
|
|
|
100
|
+
MAX_INPUTS_COUNT = 50
|
|
101
|
+
MAX_INPUT_VALUE_LEN = 2000
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def load_process_inputs(args):
|
|
105
|
+
"""Valida y normaliza --process-inputs (objeto JSON clave→valor).
|
|
106
|
+
|
|
107
|
+
- claves y valores se convierten a cadena; claves vacías se rechazan
|
|
108
|
+
- secretos en valores (y claves) se REDACTAN antes de guardar/publicar
|
|
109
|
+
- valores se truncan a MAX_INPUT_VALUE_LEN; máx MAX_INPUTS_COUNT pares
|
|
110
|
+
- devuelve None cuando no se pasó la opción (mantiene el estado limpio)
|
|
111
|
+
"""
|
|
112
|
+
raw = getattr(args, "process_inputs", None)
|
|
113
|
+
if raw is None:
|
|
114
|
+
return None
|
|
115
|
+
try:
|
|
116
|
+
data = json.loads(raw)
|
|
117
|
+
except json.JSONDecodeError as error:
|
|
118
|
+
raise SkillError(EXIT_VALIDATION, "VALIDATION_ERROR",
|
|
119
|
+
f"--process-inputs no es JSON válido: {error}")
|
|
120
|
+
if not isinstance(data, dict):
|
|
121
|
+
raise SkillError(EXIT_VALIDATION, "VALIDATION_ERROR",
|
|
122
|
+
"--process-inputs debe ser un objeto JSON "
|
|
123
|
+
"clave→valor, ej. '{\"archivo\": \"x.csv\", "
|
|
124
|
+
"\"modo\": \"lint\"}'")
|
|
125
|
+
if len(data) > MAX_INPUTS_COUNT:
|
|
126
|
+
raise SkillError(EXIT_VALIDATION, "VALIDATION_ERROR",
|
|
127
|
+
f"--process-inputs no puede exceder "
|
|
128
|
+
f"{MAX_INPUTS_COUNT} pares")
|
|
129
|
+
inputs = {}
|
|
130
|
+
for key, value in data.items():
|
|
131
|
+
clean_key, _ = sanitize_text(str(key).strip())
|
|
132
|
+
clean_key = clean_key.strip()
|
|
133
|
+
if not clean_key:
|
|
134
|
+
raise SkillError(EXIT_VALIDATION, "VALIDATION_ERROR",
|
|
135
|
+
"--process-inputs: las claves no pueden ser vacías")
|
|
136
|
+
clean_value, _ = sanitize_text(str(value))
|
|
137
|
+
inputs[clean_key] = clean_value[:MAX_INPUT_VALUE_LEN]
|
|
138
|
+
return inputs or None
|
|
139
|
+
|
|
140
|
+
|
|
100
141
|
def _remote_snapshot(owner, repo, number, token):
|
|
101
142
|
issue = github.fetch_issue(owner, repo, number, token)
|
|
102
143
|
comments = github.fetch_all_comments(owner, repo, number, token)
|
|
@@ -127,6 +168,7 @@ def cmd_init(args):
|
|
|
127
168
|
path = state_file_for(owner, repo)
|
|
128
169
|
plan = load_plan(args)
|
|
129
170
|
process_name = normalize_process_name(getattr(args, "process_name", None))
|
|
171
|
+
process_inputs = load_process_inputs(args)
|
|
130
172
|
|
|
131
173
|
lock = StateLock(path)
|
|
132
174
|
with lock:
|
|
@@ -158,7 +200,8 @@ def cmd_init(args):
|
|
|
158
200
|
execution_id = new_execution_id()
|
|
159
201
|
title = issue_title(execution_id, process_name)
|
|
160
202
|
body = build_issue_body(args.url.strip(), execution_id, plan,
|
|
161
|
-
process_name=process_name
|
|
203
|
+
process_name=process_name,
|
|
204
|
+
process_inputs=process_inputs)
|
|
162
205
|
recovered = False
|
|
163
206
|
create_error = None
|
|
164
207
|
lock.ensure_owner()
|
|
@@ -206,6 +249,7 @@ def cmd_init(args):
|
|
|
206
249
|
"active_process_id": number,
|
|
207
250
|
"execution_id": execution_id,
|
|
208
251
|
"process_name": process_name,
|
|
252
|
+
"process_inputs": process_inputs or None,
|
|
209
253
|
"issue_url": data.get("html_url"),
|
|
210
254
|
"process_state": "RUNNING",
|
|
211
255
|
"current_step": None,
|
|
@@ -229,6 +273,7 @@ def cmd_init(args):
|
|
|
229
273
|
"execution_id": execution_id, "issue_number": number,
|
|
230
274
|
"issue_url": data.get("html_url"), "state_file": str(path),
|
|
231
275
|
"process_name": process_name, "issue_title": title,
|
|
276
|
+
"process_inputs": process_inputs or None,
|
|
232
277
|
"plan": plan, "first_step": plan[0] if plan else None,
|
|
233
278
|
"recovered": recovered,
|
|
234
279
|
"token_source": source, "token_stored": bool(state["github_token"]),
|
|
@@ -98,7 +98,22 @@ def events_from_comments(comments):
|
|
|
98
98
|
return events
|
|
99
99
|
|
|
100
100
|
|
|
101
|
-
def
|
|
101
|
+
def _normalized_inputs(process_inputs):
|
|
102
|
+
"""Normaliza los parámetros de entrada a pares (clave, valor) ordenados,
|
|
103
|
+
con ambos convertidos a cadena. Devuelve [] si no hay inputs."""
|
|
104
|
+
if not process_inputs:
|
|
105
|
+
return []
|
|
106
|
+
if not isinstance(process_inputs, dict):
|
|
107
|
+
return []
|
|
108
|
+
entries = []
|
|
109
|
+
for key in sorted(process_inputs, key=str):
|
|
110
|
+
value = process_inputs[key]
|
|
111
|
+
entries.append((str(key).strip(), str(value)))
|
|
112
|
+
return [(k, v) for k, v in entries if k]
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def build_issue_body(url, execution_id, plan, process_name=None,
|
|
116
|
+
process_inputs=None):
|
|
102
117
|
lines = [
|
|
103
118
|
"Proceso rastreado por el skill `github-proyect-tracking`.",
|
|
104
119
|
"",
|
|
@@ -109,6 +124,16 @@ def build_issue_body(url, execution_id, plan, process_name=None):
|
|
|
109
124
|
lines += [
|
|
110
125
|
f"- **Proyecto:** {url}",
|
|
111
126
|
f"- **Iniciado (UTC):** {now_iso()}",
|
|
127
|
+
]
|
|
128
|
+
|
|
129
|
+
# v0.6.0: parámetros de entrada del proceso — la única forma de saber
|
|
130
|
+
# exactamente qué tarea estaba realizando esta ejecución.
|
|
131
|
+
entries = _normalized_inputs(process_inputs)
|
|
132
|
+
if entries:
|
|
133
|
+
lines += ["", "## Parámetros de entrada", ""]
|
|
134
|
+
lines += [f"- **{key}:** `{value}`" for key, value in entries]
|
|
135
|
+
|
|
136
|
+
lines += [
|
|
112
137
|
"",
|
|
113
138
|
"## Plan de pasos",
|
|
114
139
|
]
|