@ingeniomaps/cauce 0.71.0 → 0.73.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/CHANGELOG.md +77 -0
- package/agents/README.md +35 -0
- package/agents/roles/system/data-governance-steward/learning/sources.yaml +17 -7
- package/agents/roles/system/integrations-engineer/learning/sources.yaml +7 -5
- package/agents/roles/system/logistics-operations-manager/learning/sources.yaml +17 -9
- package/agents/roles/system/tech-lead/learning/sources.yaml +9 -8
- package/agents/roles/system/treasury-analyst/learning/sources.yaml +14 -11
- package/automatization/workflows/autobuild.js +1 -1
- package/engine/agents/evaluations.js +18 -4
- package/engine/agents/learning-sources.js +68 -4
- package/engine/cli/catalog.js +23 -0
- package/engine/hooks/shell.js +16 -7
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -14,6 +14,83 @@ desde este repositorio no va, porque el que lee no puede actuar sobre eso. Cuand
|
|
|
14
14
|
unas pocas líneas casi siempre es porque cuenta cómo se descubrió el problema o por qué se eligió el
|
|
15
15
|
diseño — eso vive en el commit y en el código.
|
|
16
16
|
|
|
17
|
+
## [0.73.0] - 2026-09-09
|
|
18
|
+
|
|
19
|
+
### Corregido
|
|
20
|
+
|
|
21
|
+
- **Los guards dejan de frenar trabajo legítimo escrito en varias líneas.** Siete reglas acotaban
|
|
22
|
+
«dentro de este comando» con `[^;&|]`, y el salto de línea —que también separa comandos— no estaba en
|
|
23
|
+
esa lista: cualquier bandera de una línea posterior se leía como parte del comando de arriba. `git
|
|
24
|
+
commit -m "x"` seguido de `ls -a` se bloqueaba como si stageara al commitear, y `git push origin main`
|
|
25
|
+
seguido de `rm -f /tmp/x` como un force push, que además nombraba una violación que no estaba.
|
|
26
|
+
|
|
27
|
+
Lo que frena sigue frenando: `commit -am`, `add -A`, `push --force`, `--amend` e `install -g` en una
|
|
28
|
+
línea siguen bloqueados, y un `git push` seguido de otra cosa sigue pidiendo la acción humana que R10
|
|
29
|
+
exige — sólo que ya no lo llama reescritura de historia.
|
|
30
|
+
|
|
31
|
+
- **Un banco de evaluación que no se puede rehacer lo dice, en vez de seguir sobre uno que no es nuevo.**
|
|
32
|
+
El borrado ahora se comprueba, y si algo sobrevive la corrida corta nombrando qué. Hasta acá el mismo
|
|
33
|
+
hecho se venía rodeando por síntoma —reintentos en el borrado, `force` en el andamiaje, un `rm` antes
|
|
34
|
+
del enlace—, y cada rodeo dejaba la medición siguiente corriendo sobre restos de la anterior.
|
|
35
|
+
|
|
36
|
+
La prueba que lo destapaba, además, **descartaba el resultado de la corrida que fallaba**: cualquier
|
|
37
|
+
causa terminaba en el mismo `true !== false` con el `stderr` tirado, y por eso tres investigaciones
|
|
38
|
+
dejaron escrito «no está establecido por qué». Ahora la falla llega con su código y su ruta.
|
|
39
|
+
|
|
40
|
+
**Lo que te pide algo**: si rehacer un banco te corta con «no se pudo borrar entero», borralo a mano y
|
|
41
|
+
volvé a correr. Antes eso seguía en silencio y fallaba más adelante, en otro lado.
|
|
42
|
+
|
|
43
|
+
## [0.72.0] - 2026-09-09
|
|
44
|
+
|
|
45
|
+
### Corregido
|
|
46
|
+
|
|
47
|
+
- **Una fuente que el cargo no pudo abrir se declara en `pending:`, y el ciclo avisa cuando vuelve a
|
|
48
|
+
servir.** Antes eso terminaba en un comentario del propio `sources.yaml` —cinco cargos lo escribían
|
|
49
|
+
así, y uno dejó un «registrar cuando exista una ficha legible» que nadie iba a revisar—. Ahora es una
|
|
50
|
+
lista con `name`, `why` y `since`, y `url` cuando hay una candidata; el chequeo semanal reporta **sólo
|
|
51
|
+
la que volvió a servir el documento**, porque una que sigue cerrada es lo esperado.
|
|
52
|
+
|
|
53
|
+
No alcanza con que el host conteste: se pide que la página traiga la marca del documento, el primer
|
|
54
|
+
número del nombre. Un sitio grande devuelve cientos de palabras de navegación sin una línea de la
|
|
55
|
+
norma, y así fue como la primera versión de esto dio por recuperada una ficha de IEEE que seguía
|
|
56
|
+
siendo una cáscara.
|
|
57
|
+
|
|
58
|
+
**Lo que te pide algo**: si tenías pendientes anotadas como comentario en un cargo propio, moverlas a
|
|
59
|
+
`pending:` es lo que hace que alguien se entere cuando la URL empiece a funcionar.
|
|
60
|
+
|
|
61
|
+
- **La fase `Pick` de `autobuild` deja de prometer una expansión que ya no existe.** Su descripción
|
|
62
|
+
—lo único del recorrido que se lee **antes** de autorizar la corrida— seguía diciendo «o la épica que
|
|
63
|
+
falta expandir» después de que la promoción de épicas se quitara. Lo que el recorrido hace hoy es
|
|
64
|
+
tomar la próxima tarea del hito y reservarla.
|
|
65
|
+
|
|
66
|
+
- **Un merge a `main` ya no le quita la corrida de CI al merge anterior.** Todos los pushes a `main`
|
|
67
|
+
compartían grupo de concurrencia, y sólo una corrida puede estar pendiente por grupo: la que llega
|
|
68
|
+
desaloja a la que esperaba. En una tanda de merges sobrevivían la primera y la última y morían todas
|
|
69
|
+
las del medio — el 2026-09-08, quince corridas. No lo causaba `cancel-in-progress`, que ya estaba
|
|
70
|
+
apagado en `main` y funciona: la corrida **en curso** sobrevivió cada vez. Ahora cada commit de `main`
|
|
71
|
+
tiene su propio grupo, así que ninguna espera detrás de otra.
|
|
72
|
+
|
|
73
|
+
**Lo que te pide algo**: si mergeás varios PR seguidos, ahora corre la suite completa una vez por
|
|
74
|
+
merge en vez de una por tanda. Lo que se compra es saber cuál rompió `main` sin bisect.
|
|
75
|
+
|
|
76
|
+
- **`agent evaluate` deja de anunciar que el contrato cambió hoy cuando no cambió.** La fecha sale de
|
|
77
|
+
`git log` sobre el `SKILL.md` del cargo, y un checkout de un solo commit —el que hace `actions/checkout`
|
|
78
|
+
por defecto— no tiene con qué contestarla: git le atribuye el árbol entero a ese commit, así que
|
|
79
|
+
**todos** los contratos parecían haber cambiado el día de la corrida. En un repositorio truncado el
|
|
80
|
+
aviso ahora dice que no se puede saber, en vez de inventar una fecha, y el job que evalúa se lleva la
|
|
81
|
+
historia completa para que sí se pueda.
|
|
82
|
+
|
|
83
|
+
**Lo que te pide algo**: si corrés `agent evaluate` en un pipeline con clon superficial, vas a ver el
|
|
84
|
+
aviso nuevo. Se destraba con `fetch-depth: 0` en el checkout; sin eso el chequeo no puede correr y
|
|
85
|
+
decirlo es la mitad del arreglo.
|
|
86
|
+
|
|
87
|
+
### Cambiado
|
|
88
|
+
|
|
89
|
+
- **El README del catálogo dice qué calibra un caso de evaluación recién escrito.** El rojo de su primera
|
|
90
|
+
corrida prueba que puede fallar, no que falle por lo que dice medir: eso lo prueba una corrida donde se
|
|
91
|
+
rompa a propósito la conducta que cuida. Es lo que R9 ya pedía para una prueba, dicho donde se leen los
|
|
92
|
+
veredictos.
|
|
93
|
+
|
|
17
94
|
## [0.71.0] - 2026-09-09
|
|
18
95
|
|
|
19
96
|
### Cambiado
|
package/agents/README.md
CHANGED
|
@@ -48,6 +48,35 @@ Si aun así no hay ninguna que responda —`pmi.org`, `fatf-gafi.org`—, la fue
|
|
|
48
48
|
decir sobre ella; lo que sale es el enlace en `references/`, donde un 403 sólo le hace perder un clic a
|
|
49
49
|
quien lee. El nombre se conserva.
|
|
50
50
|
|
|
51
|
+
### Lo que no se pudo abrir va en `pending:`, no en un comentario
|
|
52
|
+
|
|
53
|
+
Una fuente que el cargo necesita y no pudo verificar no es una fuente: `require_primary_source` exige
|
|
54
|
+
una ficha comprobada, no un número de norma. Pero tampoco es nada, y hasta 0.72.0 terminaba en un
|
|
55
|
+
comentario del propio archivo — cinco cargos lo escribían así, y uno dejó un «registrar cuando exista
|
|
56
|
+
una ficha legible» que nadie iba a revisar.
|
|
57
|
+
|
|
58
|
+
```yaml
|
|
59
|
+
pending:
|
|
60
|
+
- name: IEEE 1028 Software Reviews and Audits
|
|
61
|
+
url: https://standards.ieee.org/ieee/1028/4266/
|
|
62
|
+
why: responde 200 pero la ficha se arma con JavaScript y sirve el título genérico del sitio
|
|
63
|
+
since: 2026-08-22
|
|
64
|
+
- name: ISO IEC IEEE 24765 vocabulario de ingeniería de software
|
|
65
|
+
why: falta una URL primaria que responda
|
|
66
|
+
since: 2026-08-22
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
`url` es opcional, y ésa es la mitad del punto: muchas están pendientes **porque no hay ninguna URL que
|
|
70
|
+
responda**, y exigirla dejaría fuera justo las que más cuestan. `why` y `since` no lo son — sin la razón
|
|
71
|
+
la lista es un cementerio de enlaces, y sin la fecha no se ve que una lleva meses ahí.
|
|
72
|
+
|
|
73
|
+
El chequeo semanal las prueba y reporta **sólo la que volvió a servir el documento**: «ya se puede
|
|
74
|
+
declarar». Una que sigue sin abrir es lo esperado y no se anuncia, porque un aviso que sale siempre es
|
|
75
|
+
el que enseña a ignorar el resto. Y no alcanza con que el host conteste: se pide además que la página
|
|
76
|
+
traiga la marca del documento —el primer número del nombre—, porque un sitio grande devuelve cientos de
|
|
77
|
+
palabras de navegación sin una línea de la norma. Eso pasó con la ficha de IEEE 1028 en la primera
|
|
78
|
+
corrida de este chequeo.
|
|
79
|
+
|
|
51
80
|
## Por qué las normas no se citan en `iso.org`
|
|
52
81
|
|
|
53
82
|
`sources.yaml` cita cada norma por una ficha de catálogo, y para ISO esa ficha **no es la de
|
|
@@ -106,6 +135,12 @@ nada cambie. Por eso un rojo aislado no autoriza a concluir; y por eso repetir l
|
|
|
106
135
|
número no es medir. Lo que distingue una varianza de un defecto es que el defecto se repite **por la
|
|
107
136
|
misma razón**, no que se repita el rojo.
|
|
108
137
|
|
|
138
|
+
Hay un cuarto que no es ninguno de los tres y sólo le pasa a un caso recién escrito: **el rojo de su
|
|
139
|
+
primera corrida no lo calibra**. Prueba que el caso puede fallar, no que falle por lo que dice medir —
|
|
140
|
+
es la misma distinción que R9 hace para una prueba, aplicada al caso. Lo que lo calibra es una corrida
|
|
141
|
+
donde se rompa a propósito la conducta que el caso cuida, con el contrato puesto, y verlo ponerse rojo
|
|
142
|
+
**por eso**. Hasta entonces su veredicto no sostiene una conclusión sobre el cargo.
|
|
143
|
+
|
|
109
144
|
Lo que un rojo nunca significa es que el paquete venga fallado. El cargo es un contrato y el caso es la
|
|
110
145
|
medición: que la medición encuentre algo es para lo que existe, y un cargo con un caso en rojo se sigue
|
|
111
146
|
usando mientras se sepa por qué.
|
|
@@ -62,10 +62,20 @@ sources:
|
|
|
62
62
|
tier: project
|
|
63
63
|
topics: [lineage, events, dataset-identity, facets]
|
|
64
64
|
|
|
65
|
-
# Pendientes por falta de URL verificada, no por falta de relevancia
|
|
66
|
-
# exige fuente primaria comprobada, no número de norma
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
65
|
+
# Pendientes por falta de URL verificada, no por falta de relevancia: `require_primary_source: true`
|
|
66
|
+
# exige fuente primaria comprobada, no número de norma.
|
|
67
|
+
pending:
|
|
68
|
+
- name: ISO IEC 38505-1:2026 gobierno del dato
|
|
69
|
+
url: https://www.iso.org/standard/87195.html
|
|
70
|
+
why: es la edición vigente y su ficha no se pudo abrir; iso.org devuelve 403 desde este entorno
|
|
71
|
+
since: 2026-09-07
|
|
72
|
+
- name: ISO IEC 38505-2 implicaciones en la gestión
|
|
73
|
+
why: no se encontró ninguna ficha legible que permita verificar edición ni vigencia
|
|
74
|
+
since: 2026-09-07
|
|
75
|
+
- name: ISO 8000-61 gestión de calidad de datos
|
|
76
|
+
url: https://committee.iso.org/standard/63086.html
|
|
77
|
+
why: es de un comité ISO sin ficha en el IEC Webstore; queda por comprobar si este host la sirve
|
|
78
|
+
since: 2026-09-07
|
|
79
|
+
- name: ISO 15489-1 gestión de documentos
|
|
80
|
+
why: retención, disposición y bloqueo por litigio; sin ficha en el IEC Webstore y iso.org bloquea
|
|
81
|
+
since: 2026-09-07
|
|
@@ -110,8 +110,10 @@ sources:
|
|
|
110
110
|
status: expired
|
|
111
111
|
topics: [idempotency-key, not-a-standard]
|
|
112
112
|
|
|
113
|
-
#
|
|
114
|
-
#
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
113
|
+
# Mientras esto siga pendiente, la dispersión del backoff queda sin fuente registrada acá y no se
|
|
114
|
+
# afirma como recomendación de nadie.
|
|
115
|
+
pending:
|
|
116
|
+
- name: AWS Builders Library — Timeouts, retries, and backoff with jitter
|
|
117
|
+
url: https://builder.aws.com/content/timeouts-retries-and-backoff-with-jitter
|
|
118
|
+
why: aws.amazon.com redirige acá y la página devolvió sólo el encabezado, así que no se leyó
|
|
119
|
+
since: 2026-08-22
|
|
@@ -57,12 +57,20 @@ sources:
|
|
|
57
57
|
url: https://www.upu.int/en/universal-postal-union/about-upu/acts
|
|
58
58
|
tier: regulation
|
|
59
59
|
topics: [cross-border-postal, convention, claims]
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
60
|
+
pending:
|
|
61
|
+
- name: ISO 10002 gestión de quejas
|
|
62
|
+
url: https://committee.iso.org/standard/71580.html
|
|
63
|
+
why: iso.org devuelve 403 y no se encontró ficha en el IEC Webstore; queda por comprobar si este
|
|
64
|
+
host la sirve, que es donde otras normas ISO sí se leen
|
|
65
|
+
since: 2026-08-22
|
|
66
|
+
- name: Ley Federal de Protección al Consumidor (México)
|
|
67
|
+
url: https://www.diputados.gob.mx/LeyesBiblio/pdf/LFPC.pdf
|
|
68
|
+
why: diputados.gob.mx rechazó la conexión
|
|
69
|
+
since: 2026-08-22
|
|
70
|
+
- name: ASCM SCOR DS procesos Deliver y Return
|
|
71
|
+
why: la página del modelo devolvió contenido vacío y el modelo es de acceso pago, así que puede
|
|
72
|
+
no llegar a ser una fuente registrable nunca
|
|
73
|
+
since: 2026-08-22
|
|
74
|
+
- name: 49 CFR Part 370 reclamaciones ante transportistas (EE. UU.)
|
|
75
|
+
why: eCFR redirige a un portal de desbloqueo y no entrega el texto
|
|
76
|
+
since: 2026-08-22
|
|
@@ -66,11 +66,12 @@ sources:
|
|
|
66
66
|
tier: standard
|
|
67
67
|
topics: [architecture-description, architecture-decisions, scope-boundary]
|
|
68
68
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
69
|
+
pending:
|
|
70
|
+
- name: IEEE 1028 Software Reviews and Audits
|
|
71
|
+
url: https://standards.ieee.org/ieee/1028/4266/
|
|
72
|
+
why: responde 200 pero la ficha se arma con JavaScript y sirve el título genérico del sitio, así
|
|
73
|
+
que no se pudo confirmar ni el número ni la edición vigente
|
|
74
|
+
since: 2026-08-22
|
|
75
|
+
- name: ISO IEC IEEE 24765 vocabulario de ingeniería de software
|
|
76
|
+
why: fijaría los términos «revisión técnica» y «auditoría»; falta una URL primaria que responda
|
|
77
|
+
since: 2026-08-22
|
|
@@ -63,14 +63,17 @@ sources:
|
|
|
63
63
|
tier: standard
|
|
64
64
|
topics: [payment-fraud, beneficiary-verification, out-of-band-verification]
|
|
65
65
|
|
|
66
|
-
#
|
|
67
|
-
#
|
|
68
|
-
#
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
66
|
+
# La norma local de cada país donde opera la empresa —custodia de fondos de terceros, límites de
|
|
67
|
+
# dispersión, horarios bancarios— no entra ni como pendiente: no es una fuente de la profesión, la
|
|
68
|
+
# registra cada instalación en organization/roles/treasury-analyst.md.
|
|
69
|
+
pending:
|
|
70
|
+
- name: ISO 20022 mensajería de pagos
|
|
71
|
+
url: https://www.iso20022.org/
|
|
72
|
+
why: agotó el tiempo de espera dos veces, igual que swift.com/standards/iso-20022; sin la ficha
|
|
73
|
+
del Registration Authority el contrato no cita nombres de mensaje (pain, pacs, camt)
|
|
74
|
+
since: 2026-08-22
|
|
75
|
+
- name: GAFI FATF Recomendación 16 transferencias electrónicas
|
|
76
|
+
url: https://www.fatf-gafi.org/en/publications/Fatfrecommendations/Fatf-recommendations.html
|
|
77
|
+
why: devolvió 403 en dos rutas; la obligación de datos de originador y beneficiario queda sin
|
|
78
|
+
fuente propia y el contrato no afirma qué exige esa recomendación
|
|
79
|
+
since: 2026-08-22
|
|
@@ -9,7 +9,7 @@ export const meta = {
|
|
|
9
9
|
// la primera fase. Escribirlas también les da un detalle propio, que es lo que se lee al autorizar.
|
|
10
10
|
phases: [
|
|
11
11
|
{ title: 'Triage', detail: 'Contrato del proyecto y estado de planning' },
|
|
12
|
-
{ title: 'Pick', detail: 'La próxima tarea,
|
|
12
|
+
{ title: 'Pick', detail: 'La próxima tarea del hito, y la reserva antes de construirla' },
|
|
13
13
|
{ title: 'Classify', detail: 'Carril y reparto de la tarea que no los declara' },
|
|
14
14
|
{ title: 'Ready', detail: 'Aceptación concreta y sin decisiones pendientes' },
|
|
15
15
|
{ title: 'Decompose', detail: 'Partir la tarea que no entra en el tope de horas' },
|
|
@@ -132,10 +132,21 @@ function behaviors(root, agent, kind) {
|
|
|
132
132
|
// tiene `SKILL.md`— así que se le podía agregar una dimensión al gate y sus veredictos anteriores
|
|
133
133
|
// seguían leyéndose vigentes. Pasó el mismo día que esto se escribió: `change-review` ganó la pregunta
|
|
134
134
|
// por las superficies críticas y sus tres casos aprobados no dijeron una palabra.
|
|
135
|
+
//
|
|
136
|
+
// Y hay un entorno donde git **no puede** contestar: un clon `--depth 1` tiene un solo commit y le
|
|
137
|
+
// atribuye todo el árbol, así que `log -1 -- <archivo>` devuelve la fecha del checkout para cualquier
|
|
138
|
+
// archivo. Comprobado con git 2.43.0 clonando este repositorio a profundidad 1: el `SKILL.md` de
|
|
139
|
+
// `site-reliability-engineer` daba el día del clon contra el 2026-09-02, que es cuando se editó.
|
|
140
|
+
//
|
|
141
|
+
// Ahí la respuesta no es una fecha vieja sino ninguna, y **se dice**: un aviso que anuncia un cambio
|
|
142
|
+
// que no ocurrió se lee igual que uno verdadero, y tres informes de dos cargos gastaron un hallazgo
|
|
143
|
+
// cada uno investigándolo antes de que saliera como el caso 052.
|
|
135
144
|
function contractChangedAt(dir, kind) {
|
|
136
145
|
const file = kind === 'flow' ? 'flow.json' : 'SKILL.md'
|
|
146
|
+
const shallow = spawnSync('git', ['-C', dir, 'rev-parse', '--is-shallow-repository'], { encoding: 'utf8' })
|
|
147
|
+
if ((shallow.stdout || '').trim() === 'true') return { date: '', truncated: true }
|
|
137
148
|
const git = spawnSync('git', ['-C', dir, 'log', '-1', '--format=%cs', '--', file], { encoding: 'utf8' })
|
|
138
|
-
return git.status === 0 ? (git.stdout || '').trim() : ''
|
|
149
|
+
return { date: git.status === 0 ? (git.stdout || '').trim() : '', truncated: false }
|
|
139
150
|
}
|
|
140
151
|
|
|
141
152
|
function resultsDir(root, agent, kind) {
|
|
@@ -264,10 +275,13 @@ function validate(root, agent, kind) {
|
|
|
264
275
|
if (state.failed.length) {
|
|
265
276
|
warnings.push(`${state.failed.length} caso(s) no pasan: ${state.failed.join(', ')}`)
|
|
266
277
|
}
|
|
267
|
-
const
|
|
268
|
-
if (
|
|
278
|
+
const contract = contractChangedAt(subject(root, agent, kind), kind)
|
|
279
|
+
if (contract.truncated) {
|
|
280
|
+
warnings.push('no se puede saber si el contrato cambió después de estos veredictos: el checkout '
|
|
281
|
+
+ 'está truncado y git le atribuye todo a su único commit (se destraba con fetch-depth: 0)')
|
|
282
|
+
} else if (contract.date && contract.date > state.oldest) {
|
|
269
283
|
const which = state.oldest === state.newest ? 'la última corrida es' : 'el veredicto más viejo es'
|
|
270
|
-
warnings.push(`el contrato cambió el ${
|
|
284
|
+
warnings.push(`el contrato cambió el ${contract.date} y ${which} del ${state.oldest}: `
|
|
271
285
|
+ 'mide una versión anterior')
|
|
272
286
|
}
|
|
273
287
|
return { errors, warnings, cases: total, last, state }
|
|
@@ -86,8 +86,17 @@ function cadence(root, agent) {
|
|
|
86
86
|
// Basta con las líneas `tier:`: el archivo es del catálogo, no de un tercero, y agregar un parser de
|
|
87
87
|
// YAML por un campo rompería la regla de cero dependencias.
|
|
88
88
|
function sourceTiers(text) {
|
|
89
|
-
|
|
90
|
-
|
|
89
|
+
return [...sourcesBody(text).matchAll(/tier:\s*([A-Za-z-]+)/g)].map((hit) => hit[1])
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// El cuerpo de `sources:` termina donde empieza `pending:`. Sin este corte, una pendiente entraba como
|
|
93
|
+
// fuente declarada y el chequeo semanal la reportaba rota todas las semanas — que es exactamente el
|
|
94
|
+
// aviso permanente que la lista existe para no producir.
|
|
95
|
+
function sourcesBody(text) {
|
|
96
|
+
if (!text.includes('sources:')) return ''
|
|
97
|
+
const body = text.slice(text.indexOf('sources:'))
|
|
98
|
+
const corte = body.search(/^pending:/m)
|
|
99
|
+
return corte === -1 ? body : body.slice(0, corte)
|
|
91
100
|
}
|
|
92
101
|
|
|
93
102
|
// Una entrada escrita en una sola línea: seis cargos del catálogo la escriben así y cuarenta y siete la
|
|
@@ -101,7 +110,7 @@ const quitar = (value) => value.replace(/^['"]|['"]$/g, '')
|
|
|
101
110
|
// `...latest published` y `...3.2.0`— así que arreglarle el `tier` a un cargo no se lo arreglaba a los
|
|
102
111
|
// otros, y quien leyera el informe vería la misma página citada como si fueran tres.
|
|
103
112
|
function sourceUrls(text) {
|
|
104
|
-
const body =
|
|
113
|
+
const body = sourcesBody(text)
|
|
105
114
|
const out = []
|
|
106
115
|
let name = ''
|
|
107
116
|
for (const line of body.split('\n')) {
|
|
@@ -151,6 +160,45 @@ function documentUrls(dir) {
|
|
|
151
160
|
return out
|
|
152
161
|
}
|
|
153
162
|
|
|
163
|
+
// Lo que el cargo probó, no pudo abrir y va a volver a necesitar. Cinco cargos lo escribían ya como
|
|
164
|
+
// comentario en su propio archivo —el steward hasta puso «Registrar cuando exista una ficha legible»,
|
|
165
|
+
// que es un recordatorio que nadie iba a revisar—, así que la forma existía y lo que faltaba era que
|
|
166
|
+
// alguien la mirara.
|
|
167
|
+
//
|
|
168
|
+
// `url` es opcional a propósito: la mitad de esas entradas están pendientes **porque no hay ninguna URL
|
|
169
|
+
// que responda** —ISO/IEC/IEEE 24765, la ley federal mexicana—, y exigirla habría dejado fuera
|
|
170
|
+
// justamente las que más cuesta resolver. Lo que no es opcional es `why`: sin la razón, la lista es un
|
|
171
|
+
// cementerio de enlaces que nadie sabe por qué están.
|
|
172
|
+
//
|
|
173
|
+
// La continuación de línea se une en vez de prohibirse: `why` es una frase y una frase se envuelve. La
|
|
174
|
+
// primera versión la cortaba en el primer salto y **no avisaba** —cuatro razones quedaron a media
|
|
175
|
+
// oración sin que nada fallara—, y prohibir la forma no evita que la próxima persona la escriba.
|
|
176
|
+
const PENDING_FIELD = /^\s{4}(name|url|why|since):\s*(.+?)\s*$/
|
|
177
|
+
function pendingSources(text) {
|
|
178
|
+
const corte = text.search(/^pending:/m)
|
|
179
|
+
if (corte === -1) return []
|
|
180
|
+
const out = []
|
|
181
|
+
let one = null
|
|
182
|
+
let last = ''
|
|
183
|
+
for (const line of text.slice(corte).split('\n').slice(1)) {
|
|
184
|
+
if (/^\S/.test(line)) break
|
|
185
|
+
if (/^\s{2}-\s/.test(line)) {
|
|
186
|
+
if (one) out.push(one)
|
|
187
|
+
one = {}
|
|
188
|
+
last = ''
|
|
189
|
+
const primero = line.match(/^\s{2}-\s*(name|url|why|since):\s*(.+?)\s*$/)
|
|
190
|
+
if (primero) { one[primero[1]] = quitar(primero[2]); last = primero[1] }
|
|
191
|
+
continue
|
|
192
|
+
}
|
|
193
|
+
const campo = line.match(PENDING_FIELD)
|
|
194
|
+
if (campo && one) { one[campo[1]] = quitar(campo[2]); last = campo[1]; continue }
|
|
195
|
+
const sigue = line.match(/^\s{6,}(\S.*?)\s*$/)
|
|
196
|
+
if (sigue && one && last) one[last] += ` ${sigue[1]}`
|
|
197
|
+
}
|
|
198
|
+
if (one) out.push(one)
|
|
199
|
+
return out
|
|
200
|
+
}
|
|
201
|
+
|
|
154
202
|
function evaluate(root, agent) {
|
|
155
203
|
const target = catalog.resolve(root, agent)
|
|
156
204
|
const errors = []
|
|
@@ -189,6 +237,20 @@ function evaluate(root, agent) {
|
|
|
189
237
|
}
|
|
190
238
|
byUrl.set(one.url, one.name)
|
|
191
239
|
}
|
|
240
|
+
// Una pendiente sin razón es un enlace muerto con fecha, y sin fecha no se puede ver que lleva
|
|
241
|
+
// meses ahí. Los dos campos son la mitad del valor de la lista: lo que la vuelve revisable.
|
|
242
|
+
for (const one of pendingSources(fs.readFileSync(sourcesFile, 'utf8'))) {
|
|
243
|
+
const falta = ['name', 'why', 'since'].filter((campo) => !one[campo])
|
|
244
|
+
if (falta.length) {
|
|
245
|
+
errors.push(`sources.yaml: una pendiente no declara ${falta.join(' ni ')}`
|
|
246
|
+
+ `${one.name ? ` (${one.name})` : ''}`)
|
|
247
|
+
}
|
|
248
|
+
// Declarada y pendiente a la vez es una contradicción que el chequeo semanal no puede resolver:
|
|
249
|
+
// la reportaría rota como fuente y recuperada como pendiente en la misma corrida.
|
|
250
|
+
if (one.url && byUrl.has(one.url)) {
|
|
251
|
+
errors.push(`sources.yaml: ${one.url} está declarada como fuente y también como pendiente`)
|
|
252
|
+
}
|
|
253
|
+
}
|
|
192
254
|
}
|
|
193
255
|
const skill = fs.readFileSync(path.join(target, 'SKILL.md'), 'utf8').toLowerCase()
|
|
194
256
|
for (const phrase of ['no inventar', 'autorización', 'evidencia observable']) {
|
|
@@ -237,4 +299,6 @@ function evaluate(root, agent) {
|
|
|
237
299
|
return { errors, warnings, proposals: proposals.length, pending, cases }
|
|
238
300
|
}
|
|
239
301
|
|
|
240
|
-
module.exports = {
|
|
302
|
+
module.exports = {
|
|
303
|
+
SOURCE_TIERS, cadence, documentUrls, evaluate, evaluateTeam, pendingSources, sourceUrls,
|
|
304
|
+
}
|
package/engine/cli/catalog.js
CHANGED
|
@@ -102,6 +102,29 @@ function evaluationBench(root, agent, caso, force, kind) {
|
|
|
102
102
|
// reintentos, rehacer un banco es una operación que falla de vez en cuando y deja la corrida sin
|
|
103
103
|
// empezar.
|
|
104
104
|
fs.rmSync(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 50 })
|
|
105
|
+
// Y se comprueba que haya borrado. `rmSync` puede volver sin lanzar y dejar cosas —pasó en CI y no
|
|
106
|
+
// está establecido por qué—, y hasta acá cada síntoma se rodeaba por separado: `force` en el
|
|
107
|
+
// andamiaje, un `rm` antes del enlace. Rodearlo deja la corrida siguiendo sobre un banco que no es
|
|
108
|
+
// nuevo, y lo que falla después no dice nada del borrado: el test que lo destapó reportaba
|
|
109
|
+
// `true !== false` sobre un archivo de la corrida anterior, sin nombrar de dónde salía.
|
|
110
|
+
//
|
|
111
|
+
// Falla en vez de seguir, porque un banco a medio borrar contamina la medición que viene, que es lo
|
|
112
|
+
// que la recreación existe para evitar. Y nombra lo que sobrevivió: es lo único que va a permitir
|
|
113
|
+
// establecer la causa la próxima vez que ocurra.
|
|
114
|
+
if (fs.existsSync(dir)) {
|
|
115
|
+
const sobreviven = []
|
|
116
|
+
const recorrer = (base, relative = '') => {
|
|
117
|
+
for (const entry of fs.readdirSync(base, { withFileTypes: true })) {
|
|
118
|
+
if (sobreviven.length >= 5) return
|
|
119
|
+
const next = relative ? `${relative}/${entry.name}` : entry.name
|
|
120
|
+
if (entry.isDirectory()) recorrer(path.join(base, entry.name), next)
|
|
121
|
+
else sobreviven.push(next)
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
try { recorrer(dir) } catch { /* el listado es la explicación, no la comprobación */ }
|
|
125
|
+
fail(`${dir} no se pudo borrar entero y el banco tiene que ser nuevo. Sobrevivieron al borrado: `
|
|
126
|
+
+ `${sobreviven.join(', ') || '(sólo directorios)'}. Borralo a mano y volvé a correr.`, 2)
|
|
127
|
+
}
|
|
105
128
|
// Con `force`: el banco es desechable y se acaba de borrar, así que lo que sobreviva al `rmSync` se
|
|
106
129
|
// pisa en vez de cortar la corrida. Sin esto, `copyTemplate` se niega ante cualquier archivo que
|
|
107
130
|
// quede —«El destino contiene …/AGENTS.md»— y el mismo test falló así tres veces en un día, en las
|
package/engine/hooks/shell.js
CHANGED
|
@@ -31,6 +31,13 @@ const EV = require('../core/evidence')
|
|
|
31
31
|
const ANTES = String.raw`(?:^|[\s;&|('"\`])`
|
|
32
32
|
const PALABRA = String.raw`$|[\s;&|)'"\`]`
|
|
33
33
|
const COMANDO = String.raw`$|[;&|)'"\`]`
|
|
34
|
+
// Lo que puede haber **entre** un comando y su bandera sin salir de ese comando. El salto de línea va
|
|
35
|
+
// adentro de lo excluido porque también separa comandos, exactamente igual que `;`, `&` y `|`: sin él,
|
|
36
|
+
// una bandera escrita en una línea posterior se leía como parte del primer comando. Es la causa de los
|
|
37
|
+
// falsos positivos del caso 067, en siete reglas a la vez — `git push origin main` seguido de
|
|
38
|
+
// `rm -f /tmp/x` se bloqueaba anunciando «`git push --force` reescribe historia publicada», que además
|
|
39
|
+
// de frenar trabajo legítimo nombraba una violación que no estaba.
|
|
40
|
+
const MISMO = String.raw`[^;&|\n]`
|
|
34
41
|
|
|
35
42
|
// Un mensaje de commit es dato, no código. `git commit -m "fix: bloquear git push --force"` disparaba
|
|
36
43
|
// el guard de publicación, y lo mismo `rm -rf /` nombrado en una explicación; con el heredoc que se usa
|
|
@@ -62,7 +69,8 @@ function destructive(input) {
|
|
|
62
69
|
// matchea igual las dos formas, así que `allowPush` habilitaba el force-push sin que nadie lo decidiera
|
|
63
70
|
// y el párrafo de autonomía de `AGENTS.md` tenía que confesarlo. R8 prohíbe `force` sin excepción
|
|
64
71
|
// configurable, así que esta rama va antes del permiso y no lo consulta.
|
|
65
|
-
if (
|
|
72
|
+
if (new RegExp(String.raw`\bgit\s+push\b${MISMO}*\s(?:-f|--force(?:-with-lease|-if-includes)?)\b`)
|
|
73
|
+
.test(command)) {
|
|
66
74
|
block("'git push --force' reescribe historia ya publicada. R8 lo prohíbe y runner.allowPush no lo "
|
|
67
75
|
+ 'habilita: publicá con un push normal, o registrá una acción humana.')
|
|
68
76
|
}
|
|
@@ -75,7 +83,7 @@ function destructive(input) {
|
|
|
75
83
|
// no devolvía una línea. Se bloquea por política y no por daño —un `--amend` sobre algo que nadie vio
|
|
76
84
|
// no rompe nada—, así que el mensaje manda a lo que sí corresponde: otro commit.
|
|
77
85
|
[
|
|
78
|
-
|
|
86
|
+
new RegExp(String.raw`\bgit\s+commit\b${MISMO}*\s--amend\b`),
|
|
79
87
|
"'git commit --amend' reescribe un commit ya creado. R8 pide uno nuevo en su lugar.",
|
|
80
88
|
],
|
|
81
89
|
[/\bgit\s+clean\s+-[^\s]*f/, "'git clean -f' borra archivos sin seguimiento."],
|
|
@@ -89,7 +97,7 @@ function destructive(input) {
|
|
|
89
97
|
[
|
|
90
98
|
// `git restore .` no lleva `--` y destruye igual: comprobado en `git restore --help` (git 2.43.0),
|
|
91
99
|
// que restaura el working tree por defecto y toma el pathspec sin separador.
|
|
92
|
-
new RegExp(String.raw`\bgit\s+(?:checkout|restore)\s+(
|
|
100
|
+
new RegExp(String.raw`\bgit\s+(?:checkout|restore)\s+(?:${MISMO}*?\s)?`
|
|
93
101
|
+ String.raw`(?:--(?=\s*(?:${COMANDO}))|(?:--\s+)?(?:\.|\*|:\/)(?=\s*(?:${COMANDO})))`),
|
|
94
102
|
"'git checkout -- .' revierte todo lo no commiteado del directorio, no sólo lo que estás mirando. "
|
|
95
103
|
+ 'Nombrá el archivo, o commiteá lo que quieras conservar antes.',
|
|
@@ -104,7 +112,7 @@ function destructive(input) {
|
|
|
104
112
|
],
|
|
105
113
|
[
|
|
106
114
|
new RegExp(ANTES + String.raw`(?:mkfs\S*|shred)\s`
|
|
107
|
-
+ String.raw`|\bdd\s
|
|
115
|
+
+ String.raw`|\bdd\s+${MISMO}*\bof=\/dev\/|>\s*\/dev\/(?:sd|nvme|disk)`),
|
|
108
116
|
'Operación destructiva sobre disco o dispositivo.',
|
|
109
117
|
],
|
|
110
118
|
[
|
|
@@ -124,14 +132,14 @@ function gitAdd(input) {
|
|
|
124
132
|
// Dónde termina la palabra lo decide PALABRA y no un espacio: `bash -c "git add -A"` y
|
|
125
133
|
// `eval 'git add -A'` pasaban porque después de la bandera venía una comilla. Es el hueco que 028
|
|
126
134
|
// cerró en las reglas de `destructive`, y esta regla se quedó afuera de aquel arreglo.
|
|
127
|
-
if (new RegExp(String.raw`\bgit\s+add\s+(
|
|
135
|
+
if (new RegExp(String.raw`\bgit\s+add\s+(?:${MISMO}*\s)?(?:-A|--all|\.)(?=${PALABRA})`)
|
|
128
136
|
.test(command)) {
|
|
129
137
|
block("'git add -A/--all/.' está prohibido. Stagea rutas explícitas.")
|
|
130
138
|
}
|
|
131
139
|
// La misma regla con otra ortografía: `-a` stagea todo lo seguido sin nombrar una ruta, y encima lo
|
|
132
140
|
// hace al commitear —después de este hook—, así que los guards que leen el índice tampoco lo ven.
|
|
133
141
|
// `--amend` queda afuera: empieza con dos guiones y lo frena `destructive`, por otra razón.
|
|
134
|
-
if (
|
|
142
|
+
if (new RegExp(String.raw`\bgit\s+commit\b${MISMO}*\s(?:-[a-z]*a[a-z]*|--all)\b`).test(command)) {
|
|
135
143
|
block("'git commit -a' stagea al commitear, después de este guard: nadie llega a revisar el diff "
|
|
136
144
|
+ 'staged, ni vos ni los guards que lo miran. Stageá las rutas por nombre y commiteá aparte.')
|
|
137
145
|
}
|
|
@@ -141,7 +149,8 @@ function dependencies(input) {
|
|
|
141
149
|
if (process.env.OPS_DEPENDENCIES_OVERRIDE === '1') return
|
|
142
150
|
const command = commandOf(input)
|
|
143
151
|
const unsafePackageCommand = /\b(?:npm|pnpm|yarn|bun)\s+publish\b/.test(command)
|
|
144
|
-
||
|
|
152
|
+
|| new RegExp(String.raw`\b(?:npm|pnpm|yarn)\s+(?:install|add)\b${MISMO}*(?:\s-g\b|\s--global\b)`)
|
|
153
|
+
.test(command)
|
|
145
154
|
if (unsafePackageCommand) {
|
|
146
155
|
block('Publicar paquetes o instalar dependencias globales requiere una acción humana explícita.')
|
|
147
156
|
}
|