github-proyect-tracking 0.4.1 → 0.5.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 +13 -10
- package/bin/cli.js +0 -0
- package/package.json +1 -1
- package/skill/SKILL.md +10 -5
- package/skill/references/api.md +7 -1
- package/skill/scripts/tracking.py +7 -1
- package/skill/scripts/tracking_core/commands.py +23 -8
- package/skill/scripts/tracking_core/common.py +47 -2
- package/skill/scripts/tracking_core/events.py +5 -1
- package/skill/scripts/tracking_core/github.py +12 -3
package/README.md
CHANGED
|
@@ -6,11 +6,14 @@ 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.
|
|
10
|
-
>
|
|
11
|
-
>
|
|
12
|
-
>
|
|
13
|
-
>
|
|
9
|
+
> **v0.5.0** — el Issue ahora identifica el proceso rastreado:
|
|
10
|
+
> `init --process-name "Mi Skill"` produce el título
|
|
11
|
+
> `[Process Tracking][Mi Skill] Execution ID: …` y el cuerpo incluye
|
|
12
|
+
> `- **Proceso:** Mi Skill` (compatibilidad total con Issues anteriores).
|
|
13
|
+
> Conserva las correcciones de `Review_V3.md` (v0.4.x): resultados
|
|
14
|
+
> terminales derivados del Issue, recuperación explícita tras `ERROR`,
|
|
15
|
+
> reconciliación por UUID, heartbeat, esquema v2 con migración e
|
|
16
|
+
> instaladores transaccionales.
|
|
14
17
|
|
|
15
18
|
---
|
|
16
19
|
|
|
@@ -145,7 +148,7 @@ npm publish # prepack empaqueta skill/ + README + LICENSE automáticament
|
|
|
145
148
|
```
|
|
146
149
|
|
|
147
150
|
`npm pack` genera el tarball verificable de forma idéntica
|
|
148
|
-
(`npx --yes ./npm/github-proyect-tracking-0.
|
|
151
|
+
(`npx --yes ./npm/github-proyect-tracking-0.5.0.tgz install`).
|
|
149
152
|
|
|
150
153
|
### Desinstalación
|
|
151
154
|
|
|
@@ -168,7 +171,7 @@ cp -R github-proyect-tracking/ ~/.hermes/profiles/<perfil>/skills/software-devel
|
|
|
168
171
|
### Ejecutar las pruebas
|
|
169
172
|
|
|
170
173
|
```bash
|
|
171
|
-
python3 tests/test_tracking.py #
|
|
174
|
+
python3 tests/test_tracking.py # 157 pruebas: 140 offline + 1 skip opt-in
|
|
172
175
|
```
|
|
173
176
|
|
|
174
177
|
---
|
|
@@ -358,7 +361,7 @@ Excepciones documentadas al contrato "un JSON por comando": `--help`/
|
|
|
358
361
|
|
|
359
362
|
| Comando | Efecto | Efectos secundarios |
|
|
360
363
|
|---|---|---|
|
|
361
|
-
| `init --url URL [--plan JSON \| --plan-file F] [--token T] [--store-token]` | Crea el Issue y la memoria (plan sanitizado) | Issue nuevo en GitHub + archivo local |
|
|
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, aparece en título/cuerpo del Issue) | Issue nuevo en GitHub + archivo local |
|
|
362
365
|
| `resume [--url URL]` | Veredicto de reanudación | Actualiza memoria local |
|
|
363
366
|
| `step-start --name N` | Comenta INICIANDO | Comentario + memoria |
|
|
364
367
|
| `step-done --name N` | Comenta COMPLETADO | Comentario + memoria |
|
|
@@ -411,7 +414,7 @@ rechazan.
|
|
|
411
414
|
|
|
412
415
|
### 4.6 Pruebas
|
|
413
416
|
|
|
414
|
-
La suite descubre **
|
|
417
|
+
La suite descubre **157 pruebas**: **140 offline** y una integración real
|
|
415
418
|
omitida por defecto. Las pruebas offline usan stdlib, `FakeGateway`, inyección
|
|
416
419
|
de fallos, subprocesos CLI e instaladores reales en directorios temporales.
|
|
417
420
|
Cubren los formatos visibles, máquina de estados, reconciliación con UUID y
|
|
@@ -420,7 +423,7 @@ contexto ambiguo, seguridad, rollback y contrato JSON.
|
|
|
420
423
|
|
|
421
424
|
```bash
|
|
422
425
|
python3 tests/test_tracking.py
|
|
423
|
-
# Ran
|
|
426
|
+
# Ran 157 tests ... OK (skipped=1)
|
|
424
427
|
python3 tools/check_docs.py
|
|
425
428
|
```
|
|
426
429
|
|
package/bin/cli.js
CHANGED
|
File without changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "github-proyect-tracking",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.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.5.0
|
|
5
5
|
author: Rommel (rommelmg), Hermes Agent
|
|
6
6
|
license: MIT
|
|
7
7
|
platforms: [linux, macos, windows]
|
|
@@ -69,7 +69,7 @@ python3 "$SKILL" doctor --url https://github.com/OWNER/REPO --token "$GITHUB_TOK
|
|
|
69
69
|
|
|
70
70
|
| Comando | Efecto |
|
|
71
71
|
|---|---|
|
|
72
|
-
| `init --url URL [--plan JSON] [--store-token]` | Valida URL+token, crea el Issue, guarda `active_process_id` |
|
|
72
|
+
| `init --url URL [--plan JSON] [--store-token] [--process-name N]` | Valida URL+token, crea el Issue (título `[Process Tracking][N] Execution ID: …` si se da N), guarda `active_process_id` y `process_name` |
|
|
73
73
|
| `resume [--url URL]` | Lee el ÚLTIMO comentario y dicta cómo continuar |
|
|
74
74
|
| `step-start --name N` | Comenta `[STATUS: INICIANDO] - Paso: N` (valida orden del plan) |
|
|
75
75
|
| `step-done --name N` | Comenta `[STATUS: COMPLETADO] - Paso: N` (exige paso en vuelo) |
|
|
@@ -95,9 +95,14 @@ exit 0) y `KeyboardInterrupt` (130).
|
|
|
95
95
|
lectura hace fallar `issues-write`; `UNVERIFIED` se reserva para permisos
|
|
96
96
|
granulares que la API no puede confirmar.
|
|
97
97
|
2. **Init.** `init --url URL --plan '["Paso 1: ...", ...]'` (plan
|
|
98
|
-
recomendado: nombres ÚNICOS; hace la reanudación determinista).
|
|
99
|
-
|
|
100
|
-
|
|
98
|
+
recomendado: nombres ÚNICOS; hace la reanudación determinista).
|
|
99
|
+
Si el proceso pertenece a un skill padre, añade
|
|
100
|
+
`--process-name "Mi Skill"`: el título del Issue pasa a
|
|
101
|
+
`[Process Tracking][Mi Skill] Execution ID: …` y el cuerpo incluye
|
|
102
|
+
`- **Proceso:** Mi Skill` — así el Issue identifica QUÉ se rastrea.
|
|
103
|
+
Si devuelve `RESUME_REQUIRED` (exit 7), NO crees otro Issue: ve al
|
|
104
|
+
paso 3. Criterio: `execution_id`, `process_name` e `issue_url` en la
|
|
105
|
+
salida.
|
|
101
106
|
3. **Reanudar (si aplica).** `resume` y sigue el `verdict.action`
|
|
102
107
|
(`RERUN_STEP`, `NEXT_STEP`, `START_FIRST`, `ALL_STEPS_DONE`/
|
|
103
108
|
`FINISH_CLEANUP` → finalize, `ABORTED_EXTERNALLY` → pregunta al
|
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]`
|
|
45
|
+
### `init --url URL [--plan JSON | --plan-file F] [--token T] [--prompt-token] [--store-token] [--process-name N]`
|
|
46
46
|
|
|
47
47
|
- Valida la URL (regex, redactada en errores), resuelve el token, valida
|
|
48
48
|
`issues:write` ANTES de crear nada.
|
|
@@ -54,6 +54,12 @@ texto y los usa para reconciliar únicamente el intento que originó el POST.
|
|
|
54
54
|
`resume` determinista.
|
|
55
55
|
- `--store-token`: persistencia del token OPT-IN (por defecto NO se
|
|
56
56
|
guarda; `--no-store-token` quedó obsoleto).
|
|
57
|
+
- `--process-name N`: nombre del proceso/skill padre. Título resultante:
|
|
58
|
+
`[Process Tracking][N] Execution ID: {exec_id}`; el cuerpo incluye
|
|
59
|
+
`- **Proceso:** N`; estado y JSON llevan `process_name`. Sin `N`, se
|
|
60
|
+
conserva el formato legado `[Process Tracking] Execution ID: …`.
|
|
61
|
+
Un secreto en `N` se redacta; `[`/`]`/saltos → `VALIDATION_ERROR`.
|
|
62
|
+
- Salida de `init`: añade `process_name` e `issue_title`.
|
|
57
63
|
|
|
58
64
|
Salida: `execution_id`, `issue_number`, `issue_url`, `state_file`, `plan`,
|
|
59
65
|
`first_step`, `token_source`.
|
|
@@ -95,9 +95,12 @@ def build_parser():
|
|
|
95
95
|
"init", help="nueva ejecución: valida, crea el Issue y guarda memoria")
|
|
96
96
|
common(subparser, url_required=True)
|
|
97
97
|
subparser.add_argument("--plan", default=None, metavar="JSON",
|
|
98
|
-
help='plan de pasos con nombres ÚNICOS, ej. \'["Paso 1","Paso 2"]\'')
|
|
98
|
+
help='plan de pasos con nombres ÚNICOS, ej. \'["Paso 1", "Paso 2"]\'')
|
|
99
99
|
subparser.add_argument("--plan-file", default=None, metavar="F",
|
|
100
100
|
help="archivo JSON con la lista de pasos")
|
|
101
|
+
subparser.add_argument("--process-name", default=None, metavar="N",
|
|
102
|
+
help="nombre del proceso/skill padre que se rastrea "
|
|
103
|
+
"(aparece en el título y cuerpo del Issue)")
|
|
101
104
|
subparser.add_argument("--prompt-token", action="store_true",
|
|
102
105
|
help="pedir el token interactivamente (TTY)")
|
|
103
106
|
subparser.add_argument("--store-token", action="store_true",
|
|
@@ -153,6 +156,9 @@ def build_parser():
|
|
|
153
156
|
common(subparser, url_required=True)
|
|
154
157
|
subparser.add_argument("--execution-id", required=True, metavar="ID",
|
|
155
158
|
help="Execution ID EXACTO del título del Issue")
|
|
159
|
+
subparser.add_argument("--process-name", default=None, metavar="N",
|
|
160
|
+
help="nombre del proceso a adoptar (filtro adicional "
|
|
161
|
+
"opcional si hay ambigüedad)")
|
|
156
162
|
subparser.add_argument("--force", action="store_true",
|
|
157
163
|
help="reemplazar un proceso activo existente (conserva token "
|
|
158
164
|
"e historial)")
|
|
@@ -14,8 +14,9 @@ from .common import (
|
|
|
14
14
|
EXIT_RESUME_REQUIRED, EXIT_RUNTIME, EXIT_TOKEN_INSUFFICIENT,
|
|
15
15
|
EXIT_VALIDATION, MAX_DETAILS_LEN, MAX_PLAN_STEPS, SCHEMA_VERSION,
|
|
16
16
|
STATUS_CANCELADO, STATUS_COMPLETADO, STATUS_ERROR, STATUS_FINALIZADO,
|
|
17
|
-
STATUS_INICIANDO, SkillError, emit, new_execution_id,
|
|
18
|
-
|
|
17
|
+
STATUS_INICIANDO, TITLE_RE, SkillError, emit, new_execution_id,
|
|
18
|
+
normalize_process_name, normalize_step_name, now_iso, ordered_unique,
|
|
19
|
+
issue_title, redact_url, sanitize_text, validate_repo_url,
|
|
19
20
|
)
|
|
20
21
|
from .events import (
|
|
21
22
|
build_issue_body, events_from_comments, fmt_cancelado, fmt_completado,
|
|
@@ -125,6 +126,7 @@ def cmd_init(args):
|
|
|
125
126
|
owner, repo = target
|
|
126
127
|
path = state_file_for(owner, repo)
|
|
127
128
|
plan = load_plan(args)
|
|
129
|
+
process_name = normalize_process_name(getattr(args, "process_name", None))
|
|
128
130
|
|
|
129
131
|
lock = StateLock(path)
|
|
130
132
|
with lock:
|
|
@@ -154,8 +156,9 @@ def cmd_init(args):
|
|
|
154
156
|
store = bool(args.store_token) and source in ("cli", "prompt")
|
|
155
157
|
|
|
156
158
|
execution_id = new_execution_id()
|
|
157
|
-
title =
|
|
158
|
-
body = build_issue_body(args.url.strip(), execution_id, plan
|
|
159
|
+
title = issue_title(execution_id, process_name)
|
|
160
|
+
body = build_issue_body(args.url.strip(), execution_id, plan,
|
|
161
|
+
process_name=process_name)
|
|
159
162
|
recovered = False
|
|
160
163
|
create_error = None
|
|
161
164
|
lock.ensure_owner()
|
|
@@ -202,6 +205,7 @@ def cmd_init(args):
|
|
|
202
205
|
"owner": owner, "repo": repo,
|
|
203
206
|
"active_process_id": number,
|
|
204
207
|
"execution_id": execution_id,
|
|
208
|
+
"process_name": process_name,
|
|
205
209
|
"issue_url": data.get("html_url"),
|
|
206
210
|
"process_state": "RUNNING",
|
|
207
211
|
"current_step": None,
|
|
@@ -224,6 +228,7 @@ def cmd_init(args):
|
|
|
224
228
|
"ok": True, "action": "INIT",
|
|
225
229
|
"execution_id": execution_id, "issue_number": number,
|
|
226
230
|
"issue_url": data.get("html_url"), "state_file": str(path),
|
|
231
|
+
"process_name": process_name, "issue_title": title,
|
|
227
232
|
"plan": plan, "first_step": plan[0] if plan else None,
|
|
228
233
|
"recovered": recovered,
|
|
229
234
|
"token_source": source, "token_stored": bool(state["github_token"]),
|
|
@@ -653,8 +658,9 @@ def cmd_adopt(args):
|
|
|
653
658
|
existing = load_state(path)
|
|
654
659
|
token, _ = need_token(args, existing)
|
|
655
660
|
|
|
656
|
-
|
|
657
|
-
|
|
661
|
+
# v0.5.0: el título puede incluir [Nombre]; se busca por el execution_id
|
|
662
|
+
# (único) y el filtrado canónico exacto lo hace canonical_title_match.
|
|
663
|
+
query = f'repo:{owner}/{repo} "{execution_id}" in:title is:issue'
|
|
658
664
|
status, data, _ = github.api(
|
|
659
665
|
"GET", "/search/issues?" + urllib.parse.urlencode({"q": query, "per_page": 30}),
|
|
660
666
|
token)
|
|
@@ -662,8 +668,10 @@ def cmd_adopt(args):
|
|
|
662
668
|
raise SkillError(EXIT_RUNTIME, "SEARCH_FAILED",
|
|
663
669
|
f"búsqueda de Issues devolvió {status}: "
|
|
664
670
|
f"{data.get('message', '')}")
|
|
671
|
+
process_name = normalize_process_name(getattr(args, "process_name", None))
|
|
665
672
|
matches = [issue for issue in (data.get("items") or [])
|
|
666
|
-
if github.canonical_title_match(issue, execution_id
|
|
673
|
+
if github.canonical_title_match(issue, execution_id,
|
|
674
|
+
process_name=process_name)]
|
|
667
675
|
if not matches:
|
|
668
676
|
raise SkillError(EXIT_NOT_FOUND, "ISSUE_NOT_FOUND",
|
|
669
677
|
f"no se encontró un Issue con título canónico para "
|
|
@@ -691,6 +699,10 @@ def cmd_adopt(args):
|
|
|
691
699
|
plan = parse_plan_from_body(issue.get("body") or "")
|
|
692
700
|
verdict = plan_resume(events, plan, issue.get("state", "open"))
|
|
693
701
|
action = verdict["action"]
|
|
702
|
+
title_match = TITLE_RE.match((issue.get("title") or "").strip())
|
|
703
|
+
adopted_name = (title_match.group("process_name")
|
|
704
|
+
if title_match and title_match.group("process_name")
|
|
705
|
+
else None)
|
|
694
706
|
if issue.get("state") == "open":
|
|
695
707
|
active = number
|
|
696
708
|
process_state = ("ERROR" if events and events[-1]["status"] == STATUS_ERROR
|
|
@@ -708,6 +720,7 @@ def cmd_adopt(args):
|
|
|
708
720
|
"owner": owner, "repo": repo,
|
|
709
721
|
"active_process_id": active,
|
|
710
722
|
"execution_id": execution_id,
|
|
723
|
+
"process_name": process_name or adopted_name,
|
|
711
724
|
"issue_url": issue.get("html_url"),
|
|
712
725
|
"process_state": process_state,
|
|
713
726
|
"current_step": verdict.get("resume_from_step"),
|
|
@@ -727,7 +740,9 @@ def cmd_adopt(args):
|
|
|
727
740
|
"ok": True, "action": "ADOPT", "execution_id": execution_id,
|
|
728
741
|
"issue_number": number, "issue_url": issue.get("html_url"),
|
|
729
742
|
"issue_state": issue.get("state"), "verdict": verdict,
|
|
730
|
-
"process_state": process_state,
|
|
743
|
+
"process_state": process_state,
|
|
744
|
+
"process_name": process_name or adopted_name,
|
|
745
|
+
"state_file": str(path),
|
|
731
746
|
})
|
|
732
747
|
return EXIT_OK
|
|
733
748
|
|
|
@@ -7,7 +7,7 @@ import re
|
|
|
7
7
|
import uuid
|
|
8
8
|
from datetime import datetime, timezone
|
|
9
9
|
|
|
10
|
-
VERSION = "0.
|
|
10
|
+
VERSION = "0.5.0"
|
|
11
11
|
API_BASE = "https://api.github.com"
|
|
12
12
|
USER_AGENT = f"github-proyect-tracking/{VERSION}"
|
|
13
13
|
|
|
@@ -47,10 +47,18 @@ REPO_URL_RE = re.compile(
|
|
|
47
47
|
r"(?P<repo>[A-Za-z0-9._-]+?)(?:\.git)?/?$",
|
|
48
48
|
re.IGNORECASE,
|
|
49
49
|
)
|
|
50
|
+
# Título dual (v0.5.0): con o sin nombre de proceso.
|
|
51
|
+
# [Process Tracking][Mi Skill] Execution ID: exec-...
|
|
52
|
+
# [Process Tracking] Execution ID: exec-... (formato <=0.4.x)
|
|
50
53
|
TITLE_RE = re.compile(
|
|
51
|
-
r"^\[Process Tracking\]
|
|
54
|
+
r"^\[Process Tracking\]"
|
|
55
|
+
r"(?:\[(?P<process_name>"
|
|
56
|
+
r"(?:[^\[\]\r\n]|\[RED[Aa]ctado:[^\[\]]*\]){1,200}"
|
|
57
|
+
r")\])?"
|
|
58
|
+
r"\s*(?:-\s*)?Execution ID:\s*(?P<exec_id>\S+?)\s*$",
|
|
52
59
|
re.IGNORECASE,
|
|
53
60
|
)
|
|
61
|
+
MAX_PROCESS_NAME_LEN = 100
|
|
54
62
|
|
|
55
63
|
SECRET_REDACTIONS = [
|
|
56
64
|
(re.compile(r"(?i)\b(authorization|proxy-authorization)\s*[:=]\s*"
|
|
@@ -68,6 +76,43 @@ SECRET_REDACTIONS = [
|
|
|
68
76
|
]
|
|
69
77
|
|
|
70
78
|
|
|
79
|
+
def normalize_process_name(name):
|
|
80
|
+
"""Normaliza el nombre del proceso (skill padre) para título y cuerpo
|
|
81
|
+
del Issue. Orden V2-aprendido: validar la entrada CRUDA (longitud y
|
|
82
|
+
corchetes) ANTES de sanitizar — los marcadores [REDactado:...] que
|
|
83
|
+
introduce la sanitización son legítimos y TITLE_RE los acepta."""
|
|
84
|
+
if name is None:
|
|
85
|
+
return None
|
|
86
|
+
text = str(name).strip()
|
|
87
|
+
if not text:
|
|
88
|
+
return None
|
|
89
|
+
if len(text) > MAX_PROCESS_NAME_LEN:
|
|
90
|
+
raise SkillError(
|
|
91
|
+
EXIT_VALIDATION, "VALIDATION_ERROR",
|
|
92
|
+
f"el nombre del proceso no puede exceder {MAX_PROCESS_NAME_LEN} "
|
|
93
|
+
f"caracteres (recibido: {len(text)})")
|
|
94
|
+
if any(ch in text for ch in "[]\r\n"):
|
|
95
|
+
raise SkillError(EXIT_VALIDATION, "VALIDATION_ERROR",
|
|
96
|
+
"el nombre del proceso no puede contener '[', ']' "
|
|
97
|
+
"ni saltos de línea (rompe el título canónico)")
|
|
98
|
+
clean, _ = sanitize_text(text)
|
|
99
|
+
clean = clean.strip()
|
|
100
|
+
if not clean:
|
|
101
|
+
raise SkillError(EXIT_VALIDATION, "VALIDATION_ERROR",
|
|
102
|
+
"el nombre del proceso quedó vacío tras sanitizar")
|
|
103
|
+
return clean
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def issue_title(execution_id, process_name=None):
|
|
107
|
+
"""Título canónico del Issue con el nombre del proceso (v0.5.0).
|
|
108
|
+
El nombre debe venir YA normalizado (normalize_process_name); aquí no
|
|
109
|
+
se re-valida porque los marcadores [REDactado:...] son legítimos."""
|
|
110
|
+
if process_name:
|
|
111
|
+
return f"[Process Tracking][{process_name}] Execution ID: {execution_id}"
|
|
112
|
+
return f"[Process Tracking] Execution ID: {execution_id}"
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
|
|
71
116
|
class SkillError(Exception):
|
|
72
117
|
"""Error operativo que se serializa como un único objeto JSON."""
|
|
73
118
|
|
|
@@ -98,11 +98,15 @@ def events_from_comments(comments):
|
|
|
98
98
|
return events
|
|
99
99
|
|
|
100
100
|
|
|
101
|
-
def build_issue_body(url, execution_id, plan):
|
|
101
|
+
def build_issue_body(url, execution_id, plan, process_name=None):
|
|
102
102
|
lines = [
|
|
103
103
|
"Proceso rastreado por el skill `github-proyect-tracking`.",
|
|
104
104
|
"",
|
|
105
105
|
f"- **Execution ID:** `{execution_id}`",
|
|
106
|
+
]
|
|
107
|
+
if process_name:
|
|
108
|
+
lines.append(f"- **Proceso:** {process_name}")
|
|
109
|
+
lines += [
|
|
106
110
|
f"- **Proyecto:** {url}",
|
|
107
111
|
f"- **Iniciado (UTC):** {now_iso()}",
|
|
108
112
|
"",
|
|
@@ -14,7 +14,7 @@ import uuid
|
|
|
14
14
|
from .common import (
|
|
15
15
|
AMBIGUOUS_HTTP_STATUSES, API_BASE, EXIT_NOT_FOUND, EXIT_NO_TOKEN,
|
|
16
16
|
EXIT_RUNTIME, EXIT_TOKEN_INSUFFICIENT, MAX_COMMENT_PAGES, TITLE_RE,
|
|
17
|
-
USER_AGENT, SkillError,
|
|
17
|
+
USER_AGENT, SkillError, issue_title,
|
|
18
18
|
)
|
|
19
19
|
from .events import events_from_comments, transition_metadata, with_transition_metadata
|
|
20
20
|
|
|
@@ -295,6 +295,7 @@ def close_issue(owner, repo, number, token):
|
|
|
295
295
|
|
|
296
296
|
|
|
297
297
|
def find_issue_by_title(owner, repo, title, token):
|
|
298
|
+
# v0.5.0: title ya viene construido con issue_title()
|
|
298
299
|
url = (f"{API_BASE}/repos/{owner}/{repo}/issues?state=all&sort=created"
|
|
299
300
|
"&direction=desc&per_page=100")
|
|
300
301
|
seen = set()
|
|
@@ -310,6 +311,14 @@ def find_issue_by_title(owner, repo, title, token):
|
|
|
310
311
|
return None
|
|
311
312
|
|
|
312
313
|
|
|
313
|
-
def canonical_title_match(issue, execution_id):
|
|
314
|
+
def canonical_title_match(issue, execution_id, process_name=None):
|
|
315
|
+
"""v0.5.0: casa el título canónico con o sin nombre de proceso; el
|
|
316
|
+
exec_id debe coincidir EXACTO y, si se indica process_name, también."""
|
|
314
317
|
match = TITLE_RE.match((issue.get("title") or "").strip())
|
|
315
|
-
|
|
318
|
+
if not match or match.group("exec_id").lower() != execution_id.lower():
|
|
319
|
+
return False
|
|
320
|
+
if process_name:
|
|
321
|
+
found = match.group("process_name")
|
|
322
|
+
if (found or "").strip().lower() != str(process_name).strip().lower():
|
|
323
|
+
return False
|
|
324
|
+
return True
|