@ingeniomaps/cauce 0.47.0 → 0.49.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 +57 -0
- package/automatization/workflows/flow.js +74 -15
- package/engine/agents/evaluations.js +10 -3
- package/engine/cli/instance.js +35 -0
- package/engine/cli/planning.js +9 -1
- package/flows/system/change-review/evaluations/cases/05-el-cambio-chico/cambio-formato-fecha-PR-934.md +3 -6
- package/flows/system/change-review/evaluations/cases/05-el-cambio-chico.md +3 -1
- package/package.json +1 -1
- package/template/planning/rules/README.md +2 -2
- package/template/planning/rules/system/process.md +21 -0
package/CHANGELOG.md
CHANGED
|
@@ -14,6 +14,63 @@ 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.49.0] - 2026-08-26
|
|
18
|
+
|
|
19
|
+
### Agregado
|
|
20
|
+
|
|
21
|
+
- **`R22` — lo que se mide no se toca mientras se mide.** Mientras una medición corre, el sujeto y todo
|
|
22
|
+
aquello contra lo que resuelve se quedan quietos. Lo difícil es que no avisa: la corrida termina y su
|
|
23
|
+
resultado se lee igual que uno limpio, así que quien lo reciba decide sin saber que se movió el piso.
|
|
24
|
+
Y entra por donde no se mira — un banco desechable puede resolver la herramienta por un enlace al
|
|
25
|
+
repositorio vivo, así que editar ahí cambia lo que la corrida lee sin tocar el banco. Si hace falta
|
|
26
|
+
trabajar igual, se trabaja donde la medición no mira; y si ya pasó, se dice qué cambió y cuándo: un
|
|
27
|
+
resultado cuyo entorno se movió es una hipótesis, no un veredicto.
|
|
28
|
+
|
|
29
|
+
### Corregido
|
|
30
|
+
|
|
31
|
+
- **Un recorrido que frena da igual la salida que su contrato reserva para no poder.** `change-review`
|
|
32
|
+
enumera tres veredictos y el tercero es «no poder aprobar»; bloqueado, su informe parcial escribía
|
|
33
|
+
«No hay veredicto», que no es ninguno de los tres. Con el diff, los importadores y las pruebas a la
|
|
34
|
+
vista, un revisor **sí** puede firmar que no aprueba: eso es un veredicto, no su ausencia. Vale para
|
|
35
|
+
cualquier recorrido cuyo contrato tenga esa salida — un destino de «nada que hacer», una
|
|
36
|
+
recomendación de investigar.
|
|
37
|
+
- **`dependsOn` dejó de ser decorativo: un recorrido corre sus etapas como su contrato las declara.**
|
|
38
|
+
Hasta ahora el motor las corría en fila y le pasaba a cada una los handoffs de **todas** las
|
|
39
|
+
anteriores, sin mirar de qué decía depender. `technical-design` existe para tener tres lecturas
|
|
40
|
+
independientes de un mismo encuadre y no las tenía: su propio guardrail dice que las tres «no negocian
|
|
41
|
+
entre sí ni ajustan su hallazgo para que cierre con el de otra», y una que ya leyó a la primera no
|
|
42
|
+
puede cumplirlo. Ahora las etapas se agrupan por nivel de dependencia, las de un mismo nivel corren a
|
|
43
|
+
la vez, y cada una ve **sólo** los handoffs de aquello de lo que declara depender. Cinco de los siete
|
|
44
|
+
recorridos del catálogo tienen un nivel con paralelismo real y lo estrenan con esto. Una etapa que
|
|
45
|
+
**no** declara `dependsOn` sigue viendo todo lo anterior, igual que antes: independencia es una
|
|
46
|
+
afirmación, y una afirmación se declara. Si una etapa no cumple su gate, el corte es por nivel — las
|
|
47
|
+
que corrieron con ella entran igual al handoff, y lo que se abandona son los niveles siguientes.
|
|
48
|
+
- **Cambiar el `flow.json` de un recorrido ahora envejece sus veredictos.** El aviso de «el contrato
|
|
49
|
+
cambió y la última corrida es anterior» miraba sólo el `SKILL.md`, que un recorrido no tiene, así que
|
|
50
|
+
no se disparaba nunca: se le podía agregar una dimensión a un gate y sus casos aprobados seguían
|
|
51
|
+
leyéndose vigentes. Qué archivo **es** el contrato depende del sujeto — el de un cargo es su
|
|
52
|
+
`SKILL.md`, el de un recorrido su `flow.json`— y ahora se mira el que corresponde.
|
|
53
|
+
## [0.49.0] - 2026-08-27
|
|
54
|
+
|
|
55
|
+
### Corregido
|
|
56
|
+
|
|
57
|
+
- **`ops context` ya no se traga las acciones humanas cuando no hay tarea en cola.** Se imprimían
|
|
58
|
+
después del `return` de «sin tarea disponible», así que una instancia recién arrancada —`onboard`
|
|
59
|
+
deja filas pendientes y ninguna tarea todavía— preguntaba qué toca ahora y recibía «nada», cuando lo
|
|
60
|
+
que tocaba era que una persona desbloqueara siete cosas. Lo destapó una corrida de un recorrido sobre
|
|
61
|
+
su banco.
|
|
62
|
+
|
|
63
|
+
## [0.48.0] - 2026-08-26
|
|
64
|
+
|
|
65
|
+
### Corregido
|
|
66
|
+
|
|
67
|
+
- **`destroy` ya no deja el `package.json` que escribió `init`.** En modo `embedded` quitaba sus nueve
|
|
68
|
+
rutas y decía «tu repositorio queda donde está», dejando un manifiesto cuya única dependencia era el
|
|
69
|
+
motor. Sobre un repositorio Rust eso es basura conspicua y nadie avisaba. Ahora saca su clave y nada
|
|
70
|
+
más: si el manifiesto lo había creado `init` —sin dependencias ni scripts propios— se va con él, y si
|
|
71
|
+
es tuyo se conserva entero con tus scripts y tus dependencias. `node_modules/` y el lockfile no se
|
|
72
|
+
tocan porque los escribe npm y pueden tener lo tuyo, pero la salida ahora los nombra para que decidas.
|
|
73
|
+
|
|
17
74
|
## [0.47.0] - 2026-08-26
|
|
18
75
|
|
|
19
76
|
### Agregado
|
|
@@ -206,16 +206,59 @@ const blocked = []
|
|
|
206
206
|
// corre `autobuild`, y sólo después de que una persona promueva la épica al BACKLOG.
|
|
207
207
|
const discovery = contract.stages.filter((stage) => stage.phase === 'discovery')
|
|
208
208
|
if (!discovery.length) return stop('sin-descubrimiento', `${FLOW} no declara etapas de discovery`)
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
209
|
+
|
|
210
|
+
// Qué handoffs ve una etapa: los de aquello de lo que declara depender, y lo que aquéllos dependían.
|
|
211
|
+
// No los de sus hermanas. Una etapa que **no** declara `dependsOn` depende de todo lo anterior, que es
|
|
212
|
+
// como se comportaba esto antes: independencia es una afirmación y una afirmación se declara.
|
|
213
|
+
function ancestors(stage, index) {
|
|
214
|
+
if (!stage.dependsOn) return discovery.slice(0, index).map((one) => one.id)
|
|
215
|
+
const out = new Set()
|
|
216
|
+
const pending = [...stage.dependsOn]
|
|
217
|
+
while (pending.length) {
|
|
218
|
+
const id = pending.pop()
|
|
219
|
+
if (out.has(id)) continue
|
|
220
|
+
out.add(id)
|
|
221
|
+
const found = discovery.find((one) => one.id === id)
|
|
222
|
+
const arriba = found && found.dependsOn ? found.dependsOn : []
|
|
223
|
+
for (const up of arriba) pending.push(up)
|
|
224
|
+
}
|
|
225
|
+
return [...out]
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Los niveles del grafo: cada uno son las etapas cuyas dependencias ya cerraron, y corren a la vez.
|
|
229
|
+
// `technical-design` existe para tener tres lecturas independientes de un mismo encuadre, y corriéndolas
|
|
230
|
+
// en fila con el handoff de la anterior adentro no las tiene: su propio guardrail dice que las tres «no
|
|
231
|
+
// negocian entre sí ni ajustan su hallazgo para que cierre con el de otra», y una que ya leyó a la
|
|
232
|
+
// primera no puede cumplirlo. Lo destapó una corrida: `interface` escribió «Coincido» sobre un supuesto
|
|
233
|
+
// de `service` y armó su hallazgo principal sobre el K6 de `service`. El contrato lo declaraba desde el
|
|
234
|
+
// principio en `dependsOn` y el motor lo ignoraba.
|
|
235
|
+
function levels(stages) {
|
|
236
|
+
const out = []
|
|
237
|
+
const done = new Set()
|
|
238
|
+
let rest = stages.map((stage, index) => ({ stage, index, needs: ancestors(stage, index) }))
|
|
239
|
+
while (rest.length) {
|
|
240
|
+
const level = rest.filter((one) => one.needs.every((id) => done.has(id)))
|
|
241
|
+
// Una dependencia que no cierra nunca deja el resto afuera. No puede pasar —`flow check` rechaza
|
|
242
|
+
// una dependencia inexistente o posterior— pero un bucle que no avanza cuelga la corrida entera.
|
|
243
|
+
if (!level.length) { out.push(rest); break }
|
|
244
|
+
out.push(level)
|
|
245
|
+
for (const one of level) done.add(one.stage.id)
|
|
246
|
+
rest = rest.filter((one) => !level.includes(one))
|
|
247
|
+
}
|
|
248
|
+
return out
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const runStage = (stage, index) => {
|
|
252
|
+
const visible = handoffs.filter((entry) => ancestors(stage, index).includes(entry.id))
|
|
253
|
+
const abiertas = openConditions(visible)
|
|
254
|
+
const previous = visible.length
|
|
255
|
+
? `Handoffs previos:\n${visible.map((entry) => `- ${entry.id}: ${entry.summary}`).join('\n')}`
|
|
256
|
+
+ (abiertas.length
|
|
213
257
|
? '\n\nCondiciones que dejaron las etapas anteriores y tenés que respetar:\n'
|
|
214
|
-
+
|
|
258
|
+
+ abiertas.map((one) => `- ${one}`).join('\n')
|
|
215
259
|
: '')
|
|
216
260
|
: 'Sos la primera etapa: no hay handoff previo.'
|
|
217
|
-
|
|
218
|
-
const result = await agent(
|
|
261
|
+
return agent(
|
|
219
262
|
`${RULES}\n\n${previous}\n\nActuá como ${stage.agent}, respetando su contrato en ` +
|
|
220
263
|
`${stage.skill || `${WORKDIR}/agents/roles/${stage.agent}/SKILL.md`} y sus límites. ` +
|
|
221
264
|
`Etapa "${stage.id}": producí ` +
|
|
@@ -235,16 +278,27 @@ for (const stage of discovery) {
|
|
|
235
278
|
`En summary va, en 150 ` +
|
|
236
279
|
`palabras o menos, lo que la etapa siguiente necesita para decidir —no un resumen de tu análisis, ` +
|
|
237
280
|
`sino lo que le cambia el trabajo—, porque eso se le reenvía a cada etapa posterior.`,
|
|
238
|
-
{ schema: STAGE, label: `stage:${stage.id}` }
|
|
239
|
-
|
|
240
|
-
if (!result) return stop('stage-unavailable', `la etapa ${stage.id} no devolvió resultado`)
|
|
281
|
+
{ schema: STAGE, label: `stage:${stage.id}` })
|
|
282
|
+
}
|
|
241
283
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
284
|
+
// Nivel por nivel, y las de un mismo nivel a la vez. Si una de ellas no cumple su gate, el recorrido
|
|
285
|
+
// para: las hermanas que corrieron con ella entran igual al handoff —su trabajo está hecho y pagado— y
|
|
286
|
+
// lo que se abandona son los niveles siguientes.
|
|
287
|
+
for (const level of levels(discovery)) {
|
|
288
|
+
const results = await parallel(level.map((one) =>
|
|
289
|
+
() => runStage(one.stage, one.index).then((result) => ({ stage: one.stage, result }))))
|
|
290
|
+
for (const one of results) {
|
|
291
|
+
if (!one || !one.result) return stop('stage-unavailable', 'una etapa del nivel no devolvió resultado')
|
|
292
|
+
handoffs.push({ id: one.stage.id, agent: one.stage.agent, ...one.result })
|
|
293
|
+
}
|
|
294
|
+
for (const one of results) {
|
|
295
|
+
if (one.result.gate !== 'no-cumplido') continue
|
|
296
|
+
blocked.push({
|
|
297
|
+
stage: one.stage.id, missing: one.result.missing || '', action: one.result.humanAction || '',
|
|
298
|
+
})
|
|
299
|
+
log(`Gate no cumplido en ${one.stage.id}: ${one.result.missing || 'sin detalle'}`)
|
|
247
300
|
}
|
|
301
|
+
if (blocked.length) break
|
|
248
302
|
}
|
|
249
303
|
|
|
250
304
|
if (blocked.length) {
|
|
@@ -291,6 +345,11 @@ if (blocked.length) {
|
|
|
291
345
|
`**entrega parcial** desde el título: qué quedó establecido y con qué evidencia, qué no se pudo ` +
|
|
292
346
|
`y por qué, y qué haría falta para completarlo —"${blocked[0].missing}"—. No completes con ` +
|
|
293
347
|
`supuestos lo que la etapa bloqueada iba a resolver: lo que falta se nombra, no se rellena.\n\n` +
|
|
348
|
+
`Y si el recorrido enumera una salida para «no se pudo» —un veredicto de no poder aprobar, un ` +
|
|
349
|
+
`destino de «nada que hacer», una recomendación de investigar—, ésa es la respuesta y hay que ` +
|
|
350
|
+
`darla. Frenar no exime de la salida que el contrato reserva justo para esto: «no hay veredicto» ` +
|
|
351
|
+
`no es ninguna de las que el contrato enumera, y quien pidió la revisión se queda sin la única que ` +
|
|
352
|
+
`sí podías firmar con lo que tenías.\n\n` +
|
|
294
353
|
`Eso vale para lo que necesita el insumo que falta, y sólo para eso. Lo que el material que ya ` +
|
|
295
354
|
`tenés alcanza para establecer se entrega acá, aunque en el recorrido completo lo hubiera ` +
|
|
296
355
|
`producido una etapa posterior: esa etapa no va a correr, así que reservárselo no se lo guarda ` +
|
|
@@ -125,8 +125,15 @@ function behaviors(root, agent, kind) {
|
|
|
125
125
|
// La comparación es por día y no por instante porque el registro guarda fecha y no hora: un contrato
|
|
126
126
|
// que cambia y se vuelve a medir el mismo día no dispara el aviso. Es el caso que menos importa
|
|
127
127
|
// —quien lo cambió hoy sabe que lo cambió—, y afinar más pediría una hora que el registro no tiene.
|
|
128
|
-
|
|
129
|
-
|
|
128
|
+
//
|
|
129
|
+
// Y qué archivo **es** el contrato depende del sujeto: el de un cargo es su `SKILL.md` y el de un
|
|
130
|
+
// recorrido es su `flow.json`. Mirando sólo el primero, un recorrido no disparaba el aviso nunca —no
|
|
131
|
+
// tiene `SKILL.md`— así que se le podía agregar una dimensión al gate y sus veredictos anteriores
|
|
132
|
+
// seguían leyéndose vigentes. Pasó el mismo día que esto se escribió: `change-review` ganó la pregunta
|
|
133
|
+
// por las superficies críticas y sus tres casos aprobados no dijeron una palabra.
|
|
134
|
+
function contractChangedAt(dir, kind) {
|
|
135
|
+
const file = kind === 'flow' ? 'flow.json' : 'SKILL.md'
|
|
136
|
+
const git = spawnSync('git', ['-C', dir, 'log', '-1', '--format=%cs', '--', file], { encoding: 'utf8' })
|
|
130
137
|
return git.status === 0 ? (git.stdout || '').trim() : ''
|
|
131
138
|
}
|
|
132
139
|
|
|
@@ -255,7 +262,7 @@ function validate(root, agent, kind) {
|
|
|
255
262
|
if (state.failed.length) {
|
|
256
263
|
warnings.push(`${state.failed.length} caso(s) no pasan: ${state.failed.join(', ')}`)
|
|
257
264
|
}
|
|
258
|
-
const cambio = contractChangedAt(subject(root, agent, kind))
|
|
265
|
+
const cambio = contractChangedAt(subject(root, agent, kind), kind)
|
|
259
266
|
if (cambio && cambio > state.oldest) {
|
|
260
267
|
const parte = state.oldest === state.newest ? 'la última corrida es' : 'el veredicto más viejo es'
|
|
261
268
|
warnings.push(`el contrato cambió el ${cambio} y ${parte} del ${state.oldest}: `
|
package/engine/cli/instance.js
CHANGED
|
@@ -82,6 +82,34 @@ function declareEngine(manifest, version) {
|
|
|
82
82
|
F.atomicWriteJson(manifest, pkg)
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
+
// La inversa exacta de `declareEngine`: saca la clave que puso y nada más. El resto del manifiesto es
|
|
86
|
+
// del repo anfitrión aunque hoy no tenga otra cosa —un `package.json` vacío puede ser lo que alguien
|
|
87
|
+
// escribió para tener scripts— así que el archivo se borra sólo si es idéntico al que `declareEngine`
|
|
88
|
+
// habría creado desde cero, sin dependencias, sin scripts y con su `version: 0.0.0`.
|
|
89
|
+
//
|
|
90
|
+
// Lo que no se toca nunca es `node_modules/` ni el lockfile: los escribe npm, pueden tener dependencias
|
|
91
|
+
// del proyecto y borrarlos por nuestra cuenta destruye trabajo ajeno. Se nombran en la salida, que es
|
|
92
|
+
// la mitad que faltaba: `destroy` decía «tu repositorio queda donde está» y dejaba un `package.json`
|
|
93
|
+
// cuya única dependencia era Cauce. En un repo Rust eso es basura conspicua y nadie avisaba.
|
|
94
|
+
function undeclareEngine(manifest) {
|
|
95
|
+
if (!fs.existsSync(manifest)) return []
|
|
96
|
+
let pkg
|
|
97
|
+
try { pkg = JSON.parse(fs.readFileSync(manifest, 'utf8')) } catch { return [] }
|
|
98
|
+
const dev = pkg.devDependencies || {}
|
|
99
|
+
if (!('@ingeniomaps/cauce' in dev)) return []
|
|
100
|
+
delete dev['@ingeniomaps/cauce']
|
|
101
|
+
pkg.devDependencies = dev
|
|
102
|
+
const generado = !Object.keys(dev).length && !Object.keys(pkg.dependencies || {}).length
|
|
103
|
+
&& !Object.keys(pkg.scripts || {}).length && pkg.private === true && pkg.version === '0.0.0'
|
|
104
|
+
if (generado) {
|
|
105
|
+
fs.rmSync(manifest, { force: true })
|
|
106
|
+
return ['package.json (lo había creado init: sin dependencias ni scripts propios)']
|
|
107
|
+
}
|
|
108
|
+
if (!Object.keys(dev).length) delete pkg.devDependencies
|
|
109
|
+
F.atomicWriteJson(manifest, pkg)
|
|
110
|
+
return ['package.json: se quitó la dependencia del motor y el resto queda como estaba']
|
|
111
|
+
}
|
|
112
|
+
|
|
85
113
|
// Proveedores que el toolkit conoce, para saltearlos al copiar la plantilla: su andamiaje
|
|
86
114
|
// —configuración, staging/, proposed/— no se materializa hasta que alguien lo habilite. Antes cada
|
|
87
115
|
|
|
@@ -212,6 +240,13 @@ function destroy(dir, cli) {
|
|
|
212
240
|
]
|
|
213
241
|
for (const relative of ownPath) fs.rmSync(path.join(root, relative), { recursive: true, force: true })
|
|
214
242
|
console.log(`✓ ${ownPath.length} ruta(s) de Cauce quitadas de ${root}`)
|
|
243
|
+
for (const line of undeclareEngine(path.join(root, 'package.json'))) console.log(`✓ ${line}`)
|
|
244
|
+
const npm = ['node_modules', 'package-lock.json']
|
|
245
|
+
.filter((one) => fs.existsSync(path.join(root, one)))
|
|
246
|
+
if (npm.length) {
|
|
247
|
+
console.log(` queda ${npm.join(' y ')}: lo escribe npm y puede tener lo tuyo. Borralo vos si el`)
|
|
248
|
+
console.log(' repositorio no usaba npm antes de instalar Cauce.')
|
|
249
|
+
}
|
|
215
250
|
return console.log(' tu repositorio queda donde está: en modo embedded la instancia era él mismo.')
|
|
216
251
|
}
|
|
217
252
|
const inside = process.cwd() === root || process.cwd().startsWith(`${root}${path.sep}`)
|
package/engine/cli/planning.js
CHANGED
|
@@ -205,7 +205,15 @@ function context(dir, cli) {
|
|
|
205
205
|
const row = humanActions.find((action) => skipped.includes(action.task)) || humanActions[0]
|
|
206
206
|
return console.log(`BLOCKED blocked-on-human — ${row.task}: ${row.action}`)
|
|
207
207
|
}
|
|
208
|
-
|
|
208
|
+
// Sin tarea no se corta la salida: las acciones humanas van igual. Estaban después del `return`, así
|
|
209
|
+
// que una instancia recién arrancada —`onboard` deja filas pendientes y ninguna tarea todavía—
|
|
210
|
+
// respondía «sin tarea disponible» y se tragaba las siete cosas que una persona tenía que desbloquear.
|
|
211
|
+
// Es el comando que existe para decir qué toca ahora, contestando «nada» cuando lo que toca es eso.
|
|
212
|
+
if (!report.task) {
|
|
213
|
+
console.log('TASK (sin tarea disponible)')
|
|
214
|
+
for (const action of report.humanActions) console.log(`HUMAN ${action.task}: ${action.action}`)
|
|
215
|
+
return
|
|
216
|
+
}
|
|
209
217
|
console.log(`TASK ${report.task.slug}${report.task.tier ? ` [${report.task.tier}]` : ''}` +
|
|
210
218
|
`${report.task.service ? ` service: ${report.task.service}` : ''}` +
|
|
211
219
|
`${report.task.hito ? ` hito: ${report.task.hito}` : ''}`)
|
|
@@ -36,10 +36,7 @@ Test Suites: 1 passed, 1 total
|
|
|
36
36
|
|
|
37
37
|
`format.ts` no tiene test propio. `order-payload.ts` tampoco. Los seis que pasaron son del listado.
|
|
38
38
|
|
|
39
|
-
##
|
|
39
|
+
## Lo que no consta en este documento
|
|
40
40
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
| Alta de pedido | No entra ninguna venta | Todos los compradores | `src/checkout/` |
|
|
44
|
-
| Acceso a la plataforma | Nadie puede entrar | Todos los usuarios | `src/auth/` |
|
|
45
|
-
| Listado y reportes | Nadie consulta lo ya vendido | Operación interna | `src/pages/orders/`, `src/reports/` |
|
|
41
|
+
Si alguna de estas rutas está declarada como crítica, no lo dice el PR: eso vive en la instancia, en
|
|
42
|
+
`organization/company.md`. Quien revise tiene que ir a mirarlo — acá no está.
|
|
@@ -10,7 +10,9 @@ Te dejo el diff y el contexto de la instancia.
|
|
|
10
10
|
|
|
11
11
|
- Notar que el helper que se toca no lo usa sólo el listado: lo importan también el resumen del pedido
|
|
12
12
|
y el componente que arma el cuerpo de la orden antes de enviarla.
|
|
13
|
-
-
|
|
13
|
+
- Ir a buscar a la instancia si alguna de esas rutas está declarada como crítica, en vez de estimarlo
|
|
14
|
+
desde el documento del PR o darlo por sabido. Si la declaración está sin llenar, decirlo: no saberlo
|
|
15
|
+
no es lo mismo que no serlo.
|
|
14
16
|
- Decir qué verificación pide un cambio de una línea cuando cae donde cae, y qué de eso no se hizo:
|
|
15
17
|
los tests que pasaron son los del listado.
|
|
16
18
|
- Cerrar con uno de los tres veredictos, sin confundir «el diff es chico» con «el riesgo es chico».
|
package/package.json
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
`system/` pertenece a Cauce y se reemplaza completo en cada actualización:
|
|
4
4
|
|
|
5
|
-
- `system/process.md` — R1..R4, R16, R17, R20
|
|
6
|
-
medición y retomar lo interrumpido.
|
|
5
|
+
- `system/process.md` — R1..R4, R16, R17, R20..R22: planificación, alcance, review, sincronización de
|
|
6
|
+
estado, medición y retomar lo interrumpido.
|
|
7
7
|
- `system/code-shape.md` — R5..R7, R11, R18: simplicidad y forma del cambio.
|
|
8
8
|
- `system/commits.md` — R8..R10: historia versionada y entrega.
|
|
9
9
|
- `system/conduct.md` — R12..R15, R19: trato con sistemas externos, lo que llega de ellos, y la
|
|
@@ -155,3 +155,24 @@ aparecieron, cuánto se gastó. La sesión que originó esta regla creyó estar
|
|
|
155
155
|
volvió a correr entero dos veces: siete millones de tokens para un solo veredicto, con los archivos de
|
|
156
156
|
las etapas ya cumplidas a la vista en el directorio de trabajo, y con el filtro que lo evitaba escrito
|
|
157
157
|
por quien reanudaba tres horas antes.
|
|
158
|
+
|
|
159
|
+
## R22 — Lo que se mide no se toca mientras se mide
|
|
160
|
+
|
|
161
|
+
Mientras una medición corre, el sujeto y todo aquello contra lo que resuelve se quedan quietos. No se
|
|
162
|
+
edita el contrato que se está midiendo, ni el motor que la corrida usa, ni el entorno del que lee.
|
|
163
|
+
|
|
164
|
+
Lo que lo vuelve difícil de ver es que no avisa. La corrida termina, entrega su resultado y **ese
|
|
165
|
+
resultado se lee exactamente igual que uno limpio**: no hay señal que diga «esto midió dos versiones».
|
|
166
|
+
Quien lo reciba va a decidir sobre él sin saber que se movió el piso.
|
|
167
|
+
|
|
168
|
+
Y el camino por el que entra casi nunca es el archivo obvio. Un banco desechable puede resolver la
|
|
169
|
+
herramienta por un enlace al repositorio vivo, así que editar ahí cambia lo que la corrida lee sin que
|
|
170
|
+
nada del banco se haya tocado. La pregunta no es «¿toqué el sujeto?» sino «¿toqué algo que el sujeto
|
|
171
|
+
alcanza?».
|
|
172
|
+
|
|
173
|
+
Si hace falta trabajar igual, se trabaja donde la medición no mira: otra copia, otra rama sin
|
|
174
|
+
materializar, o se espera. Esperar es más barato que descubrir que la tanda no vale.
|
|
175
|
+
|
|
176
|
+
Y si ya pasó, se dice: qué medición, qué cambió y cuándo. Un resultado cuyo entorno se movió es una
|
|
177
|
+
hipótesis, no un veredicto —lo mismo que R21 nombra para lo que quedó a medias—, y guardarlo sin esa
|
|
178
|
+
marca es la forma cara del error, porque el número sobrevive a la sesión que sabía.
|