@ingeniomaps/cauce 0.71.0 → 0.72.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 CHANGED
@@ -14,6 +14,57 @@ 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.72.0] - 2026-09-09
18
+
19
+ ### Corregido
20
+
21
+ - **Una fuente que el cargo no pudo abrir se declara en `pending:`, y el ciclo avisa cuando vuelve a
22
+ servir.** Antes eso terminaba en un comentario del propio `sources.yaml` —cinco cargos lo escribían
23
+ así, y uno dejó un «registrar cuando exista una ficha legible» que nadie iba a revisar—. Ahora es una
24
+ lista con `name`, `why` y `since`, y `url` cuando hay una candidata; el chequeo semanal reporta **sólo
25
+ la que volvió a servir el documento**, porque una que sigue cerrada es lo esperado.
26
+
27
+ No alcanza con que el host conteste: se pide que la página traiga la marca del documento, el primer
28
+ número del nombre. Un sitio grande devuelve cientos de palabras de navegación sin una línea de la
29
+ norma, y así fue como la primera versión de esto dio por recuperada una ficha de IEEE que seguía
30
+ siendo una cáscara.
31
+
32
+ **Lo que te pide algo**: si tenías pendientes anotadas como comentario en un cargo propio, moverlas a
33
+ `pending:` es lo que hace que alguien se entere cuando la URL empiece a funcionar.
34
+
35
+ - **La fase `Pick` de `autobuild` deja de prometer una expansión que ya no existe.** Su descripción
36
+ —lo único del recorrido que se lee **antes** de autorizar la corrida— seguía diciendo «o la épica que
37
+ falta expandir» después de que la promoción de épicas se quitara. Lo que el recorrido hace hoy es
38
+ tomar la próxima tarea del hito y reservarla.
39
+
40
+ - **Un merge a `main` ya no le quita la corrida de CI al merge anterior.** Todos los pushes a `main`
41
+ compartían grupo de concurrencia, y sólo una corrida puede estar pendiente por grupo: la que llega
42
+ desaloja a la que esperaba. En una tanda de merges sobrevivían la primera y la última y morían todas
43
+ las del medio — el 2026-09-08, quince corridas. No lo causaba `cancel-in-progress`, que ya estaba
44
+ apagado en `main` y funciona: la corrida **en curso** sobrevivió cada vez. Ahora cada commit de `main`
45
+ tiene su propio grupo, así que ninguna espera detrás de otra.
46
+
47
+ **Lo que te pide algo**: si mergeás varios PR seguidos, ahora corre la suite completa una vez por
48
+ merge en vez de una por tanda. Lo que se compra es saber cuál rompió `main` sin bisect.
49
+
50
+ - **`agent evaluate` deja de anunciar que el contrato cambió hoy cuando no cambió.** La fecha sale de
51
+ `git log` sobre el `SKILL.md` del cargo, y un checkout de un solo commit —el que hace `actions/checkout`
52
+ por defecto— no tiene con qué contestarla: git le atribuye el árbol entero a ese commit, así que
53
+ **todos** los contratos parecían haber cambiado el día de la corrida. En un repositorio truncado el
54
+ aviso ahora dice que no se puede saber, en vez de inventar una fecha, y el job que evalúa se lleva la
55
+ historia completa para que sí se pueda.
56
+
57
+ **Lo que te pide algo**: si corrés `agent evaluate` en un pipeline con clon superficial, vas a ver el
58
+ aviso nuevo. Se destraba con `fetch-depth: 0` en el checkout; sin eso el chequeo no puede correr y
59
+ decirlo es la mitad del arreglo.
60
+
61
+ ### Cambiado
62
+
63
+ - **El README del catálogo dice qué calibra un caso de evaluación recién escrito.** El rojo de su primera
64
+ corrida prueba que puede fallar, no que falle por lo que dice medir: eso lo prueba una corrida donde se
65
+ rompa a propósito la conducta que cuida. Es lo que R9 ya pedía para una prueba, dicho donde se leen los
66
+ veredictos.
67
+
17
68
  ## [0.71.0] - 2026-09-09
18
69
 
19
70
  ### 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. `require_primary_source: true`
66
- # exige fuente primaria comprobada, no número de norma:
67
- # - ISO/IEC 38505-1:2026 — la edición vigente; su ficha no se pudo abrir en esta corrida.
68
- # - ISO/IEC 38505-2 (implicaciones del gobierno del dato en la gestión) — no verificada.
69
- # - ISO 8000-61 (gestión de calidad de datos) e ISO 15489-1 (gestión de documentos: retención,
70
- # disposición y bloqueo por litigio) — de comités ISO sin ficha en el IEC Webstore, y `iso.org`
71
- # devuelve 403 desde este entorno. Registrar cuando exista una ficha legible.
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
- # Pendiente de registrar: AWS Builders' Library, «Timeouts, retries, and backoff with jitter», que es
114
- # la fuente habitual para la dispersión del backoff. La URL de aws.amazon.com redirige a
115
- # builder.aws.com y esa página devolvió sólo el encabezado el 2026-08-22, así que **no se leyó** y no
116
- # se cita. Mientras tanto, la dispersión del backoff queda sin fuente registrada en este archivo y no
117
- # se afirma como recomendación de nadie.
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
- # Pendientes de registrar en 2026-09, con lo que impidió verificarlas el 2026-08-22:
61
- # - ISO 10002 (gestión de quejas): iso.org devuelve 403 y no se encontró ficha legible en el
62
- # IEC Webstore. Sin URL comprobada no entra: `require_primary_source` exige fuente abierta, no
63
- # número de norma.
64
- # - Ley Federal de Protección al Consumidor (México): diputados.gob.mx rechazó la conexión.
65
- # - ASCM SCOR DS (procesos Deliver y Return, métricas de orden perfecta): la página del modelo
66
- # devolvió contenido vacío y el modelo es de acceso pago.
67
- # - 49 CFR Part 370 (reclamaciones de pérdida y daño ante transportistas, EE. UU.): eCFR redirige a
68
- # un portal de desbloqueo y no entrega el texto.
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
- # Pendiente de verificar en la próxima corrida, y por eso todavía no es una fuente:
70
- #
71
- # - IEEE 1028 (Software Reviews and Audits). https://standards.ieee.org/ieee/1028/4266/ devuelve 200
72
- # pero la ficha se arma con JavaScript y el título servido es el genérico del sitio, así que la
73
- # corrida no pudo confirmar ni el número ni la edición vigente. Falta abrirla con un cliente que
74
- # ejecute la página, o encontrar una ficha estática equivalente.
75
- # - ISO/IEC/IEEE 24765 (vocabulario de ingeniería de software), para fijar los términos de «revisión
76
- # técnica» y «auditoría». Falta una URL primaria que responda; la de iso.org está bloqueada.
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
- # Pendientes: no entran porque no pude abrirlas en esta corrida, y `require_primary_source: true`
67
- # exige fuente primaria comprobada, no el número de la norma.
68
- # - ISO 20022, mensajería de pagos: iso20022.org y swift.com/standards/iso-20022 agotaron el
69
- # tiempo de espera dos veces el 2026-08-22. Sin la ficha del Registration Authority no se
70
- # registra, y por eso el contrato no cita nombres de mensaje (pain, pacs, camt).
71
- # - GAFI/FATF Recomendación 16, transferencias electrónicas: fatf-gafi.org devolvió 403 en dos
72
- # rutas el 2026-08-22. La obligación de datos de originador y beneficiario queda sin fuente
73
- # propia; hasta tenerla, el contrato no afirma qué exige esa recomendación.
74
- # - Norma local de cada país donde opera la empresa —custodia de fondos de terceros, límites de
75
- # dispersión, horarios bancarios—. No es una fuente de la profesión: la registra cada
76
- # instalación en organization/roles/treasury-analyst.md.
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, o la épica que falta expandir' },
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 changedAt = contractChangedAt(subject(root, agent, kind), kind)
268
- if (changedAt && changedAt > state.oldest) {
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 ${changedAt} y ${which} del ${state.oldest}: `
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
- const body = text.includes('sources:') ? text.slice(text.indexOf('sources:')) : ''
90
- return [...body.matchAll(/tier:\s*([A-Za-z-]+)/g)].map((hit) => hit[1])
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 = text.includes('sources:') ? text.slice(text.indexOf('sources:')) : ''
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 = { SOURCE_TIERS, cadence, documentUrls, evaluate, evaluateTeam, sourceUrls }
302
+ module.exports = {
303
+ SOURCE_TIERS, cadence, documentUrls, evaluate, evaluateTeam, pendingSources, sourceUrls,
304
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ingeniomaps/cauce",
3
- "version": "0.71.0",
3
+ "version": "0.72.0",
4
4
  "description": "Sistema portable de planificación y ejecución verificable para cualquier proyecto",
5
5
  "keywords": [
6
6
  "planning",