@ingeniomaps/cauce 0.87.0 → 0.89.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 +179 -0
- package/agents/roles/system/database-administrator/learning/HISTORY.md +1 -0
- package/agents/roles/system/finops-engineer/learning/HISTORY.md +1 -0
- package/agents/roles/system/frontend-engineer/learning/HISTORY.md +1 -0
- package/agents/roles/system/kyc-aml-specialist/learning/HISTORY.md +1 -0
- package/agents/roles/system/qa-engineer/learning/HISTORY.md +1 -0
- package/agents/roles/system/release-manager/learning/HISTORY.md +1 -0
- package/agents/roles/system/security-engineer/learning/HISTORY.md +1 -0
- package/automatization/shared/eval-measured.js +1 -1
- package/automatization/shared/workflow-root.js +15 -4
- package/automatization/workflows/autobuild.js +9 -6
- package/engine/agents/learning-files.js +47 -1
- package/engine/agents/learning-seal.js +25 -9
- package/engine/agents/learning-sources.js +1 -12
- package/engine/agents/learning.js +20 -8
- package/engine/automation/index.js +16 -0
- package/engine/automation/rules.js +99 -5
- package/engine/automation/runners.js +7 -3
- package/engine/cli/catalog.js +15 -1
- package/engine/cli/claims.js +9 -3
- package/engine/cli/io.js +7 -1
- package/engine/cli/ops.js +3 -2
- package/engine/cli/planning.js +21 -193
- package/engine/cli/validate.js +205 -0
- package/engine/planning/contracts.js +54 -0
- package/engine/planning/state.js +14 -4
- package/package.json +1 -1
- package/template/planning/PROTOCOL.md +5 -0
- package/template/planning/rules/README.md +31 -0
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
// La puerta de un planning: qué está mal y qué conviene mirar, en un solo comando que falla.
|
|
4
|
+
//
|
|
5
|
+
// Vive aparte de los que leen el mismo estado porque hace lo contrario que ellos. `tree`, `context` y
|
|
6
|
+
// `recurring` contestan una pregunta y salen en cero; éste junta errores y advertencias de dieciocho
|
|
7
|
+
// módulos —estructura, contratos, reclamos, integraciones, ownership, onboarding— y decide si la
|
|
8
|
+
// instancia puede seguir. De ahí que casi todo lo que el CLI de planning importa entre por acá y no por
|
|
9
|
+
// allá: lo que valida necesita conocer a todos, y lo que informa sólo necesita el estado ya compuesto.
|
|
10
|
+
|
|
11
|
+
const fs = require('node:fs')
|
|
12
|
+
const path = require('node:path')
|
|
13
|
+
const P = require('../planning/parser')
|
|
14
|
+
const B = require('../planning/business-rules')
|
|
15
|
+
const PC = require('../planning/contracts')
|
|
16
|
+
const SR = require('../planning/structure')
|
|
17
|
+
const SZ = require('../planning/sizing')
|
|
18
|
+
const RC = require('../planning/recurring')
|
|
19
|
+
const IB = require('../planning/inbox')
|
|
20
|
+
const CL = require('../planning/claims')
|
|
21
|
+
const R = require('../core/repos')
|
|
22
|
+
const AD = require('../planning/adoption')
|
|
23
|
+
const AP = require('../hooks/approval')
|
|
24
|
+
const I = require('../integrations/registry')
|
|
25
|
+
const O = require('../core/ownership')
|
|
26
|
+
const TR = require('../core/trails')
|
|
27
|
+
const OB = require('../core/onboarding')
|
|
28
|
+
const C = require('../config/validate')
|
|
29
|
+
const CP = require('../config/paths')
|
|
30
|
+
const AG = require('../agents/catalog')
|
|
31
|
+
const RL = require('../automation/rules')
|
|
32
|
+
const { fail, planningRoot, TODAY } = require('./io')
|
|
33
|
+
|
|
34
|
+
function check(dir, cli) {
|
|
35
|
+
const root = planningRoot(dir)
|
|
36
|
+
const errors = []
|
|
37
|
+
const warnings = []
|
|
38
|
+
// El plan no está: `wip/` es local y gitignoreado, así que un clon nuevo no lo trae y eso no es un
|
|
39
|
+
// error. Ausente se lee como IDLE, que es lo que significa.
|
|
40
|
+
const required = ['BACKLOG.md', 'INBOX.md', 'HUMAN_ACTIONS.md', 'PROTOCOL.md']
|
|
41
|
+
// `DONE.md` se retiró: la evidencia vive en un archivo por tarea. Un `DONE.md` que quede en disco
|
|
42
|
+
// ya no lo lee nadie, y eso no se nota — las épicas dejan de poder cerrar y sus historias figuran
|
|
43
|
+
// sin evidencia, que es lo mismo que se vería si nunca se hubieran hecho.
|
|
44
|
+
// `WIP.md` se retiró por lo mismo que `DONE.md`: era uno solo y lo escribían todos los que corren sobre
|
|
45
|
+
// una instancia sidecar. Uno que quede en disco ya no lo lee nadie, y su plan a medias se pierde sin
|
|
46
|
+
// que nada lo diga.
|
|
47
|
+
if (fs.existsSync(path.join(root, 'WIP.md'))) {
|
|
48
|
+
errors.push('WIP.md ya no se lee: el plan de cada runner vive en `wip/<runner>.md`; movelo y borralo')
|
|
49
|
+
}
|
|
50
|
+
if (fs.existsSync(path.join(root, 'DONE.md'))) {
|
|
51
|
+
errors.push('DONE.md ya no se lee: pasá cada entrada a su propio `done/<slug>.md` y borralo')
|
|
52
|
+
}
|
|
53
|
+
for (const file of required) if (!fs.existsSync(path.join(root, file))) errors.push(`falta ${file}`)
|
|
54
|
+
|
|
55
|
+
const configPath = path.join(root, '..', 'ops.config.json')
|
|
56
|
+
let config = null
|
|
57
|
+
if (fs.existsSync(configPath)) {
|
|
58
|
+
try {
|
|
59
|
+
const raw = fs.readFileSync(configPath, 'utf8')
|
|
60
|
+
config = JSON.parse(raw)
|
|
61
|
+
if (!raw.includes('{{')) {
|
|
62
|
+
errors.push(...C.validateOpsConfig(config))
|
|
63
|
+
warnings.push(...C.configWarnings(config))
|
|
64
|
+
if (Array.isArray(config.workspaceRoots)) {
|
|
65
|
+
for (const workspace of config.workspaceRoots) {
|
|
66
|
+
if (workspace && workspace.name && workspace.path
|
|
67
|
+
&& !fs.existsSync(path.resolve(path.dirname(configPath), workspace.path))) {
|
|
68
|
+
errors.push(`ops.config.json: no existe la raíz ${workspace.name} (${workspace.path})`)
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
// Es la única parte de la configuración que le levanta el límite a un guard, y quien la escribió
|
|
73
|
+
// no es quien la lee dentro de seis meses: va como advertencia permanente, igual que un override.
|
|
74
|
+
// Y se muestra resuelta porque resuelta es como la compara el guard — un `~` escrito solo exenta
|
|
75
|
+
// la casa entera, y escrito no se nota.
|
|
76
|
+
for (const exempt of CP.writableOutsideRoots(path.dirname(configPath), config)) {
|
|
77
|
+
warnings.push(`ops.config.json: ${exempt.declared} está exenta del límite `
|
|
78
|
+
+ `de raíces (${exempt.path})`)
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
} catch (error) {
|
|
82
|
+
errors.push(`ops.config.json: JSON inválido (${error.message})`)
|
|
83
|
+
}
|
|
84
|
+
} else {
|
|
85
|
+
warnings.push(`no existe ${path.relative(root, configPath)}`)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const epics = P.readEpics(root)
|
|
89
|
+
const milestones = P.readBacklog(root)
|
|
90
|
+
const done = P.readDone(root)
|
|
91
|
+
errors.push(...B.validate(path.join(root, 'business-rules')))
|
|
92
|
+
errors.push(...SR.validateRoadmapStructure(root))
|
|
93
|
+
errors.push(...SR.validateBacklogStructure(root))
|
|
94
|
+
errors.push(...SR.validateRules(root))
|
|
95
|
+
errors.push(...SR.validateAdr(root))
|
|
96
|
+
const backlog = milestones.flatMap((milestone) => milestone.tasks)
|
|
97
|
+
const roles = new Set(AG.list(path.resolve(root, '..')).map((role) => role.slug))
|
|
98
|
+
const wips = P.readWips(root)
|
|
99
|
+
const adopted = AD.read(root)
|
|
100
|
+
errors.push(...SZ.oversizedUnits({ epics, milestones }))
|
|
101
|
+
errors.push(...PC.validateState({
|
|
102
|
+
epics, milestones, done, wips, roles, humanActions: P.readHumanActions(root), adopted: new Set(adopted),
|
|
103
|
+
}))
|
|
104
|
+
warnings.push(...AD.report({ done, epics, adopted }))
|
|
105
|
+
warnings.push(...PC.doneCeremonyWarnings(done, new Set(adopted)))
|
|
106
|
+
// Antes de que el recorrido pague Build para descubrirlo en Verify. Cuesta un regex sobre la cola y
|
|
107
|
+
// corre en los cuatro carriles, incluidos los que saltean Ready (caso 140).
|
|
108
|
+
warnings.push(...PC.unverifiableAcceptance(milestones))
|
|
109
|
+
warnings.push(...R.coverageWarnings(path.resolve(root, '..'), done))
|
|
110
|
+
// Sin `RECURRING.md` no dice una palabra: una instancia que actualiza y no declara trabajo recurrente
|
|
111
|
+
// no tiene por qué enterarse de que el contrato existe. Vencida avisa y no frena — lo que frena vive
|
|
112
|
+
// en `HUMAN_ACTIONS.md`, y un aviso que salta siempre se termina apagando.
|
|
113
|
+
// Un reclamo que nombra una tarea que no existe bloquea la cola sin que nada lo explique, y uno viejo
|
|
114
|
+
// la bloquea para siempre. Lo primero es error; lo segundo avisa, porque abandonar no es un defecto.
|
|
115
|
+
const claims = CL.read(root)
|
|
116
|
+
errors.push(...CL.validate({ claims, milestones, done }))
|
|
117
|
+
// Si la rama de cada tarea tomada se movió, que es lo único barato que distingue una tarea larga de
|
|
118
|
+
// una abandonada. Sin repositorio resoluble el mapa queda vacío y el aviso vuelve a mirar sólo la
|
|
119
|
+
// fecha, que es lo que había antes: degrada, no rompe.
|
|
120
|
+
const activity = new Map()
|
|
121
|
+
for (const claim of claims.filter((one) => !done.set.has(one.slug))) {
|
|
122
|
+
const at = R.lastCommit(R.repoOf(path.join(root, '..'), claim.service), CL.branchOf(claim.slug))
|
|
123
|
+
if (at) activity.set(claim.slug, at)
|
|
124
|
+
}
|
|
125
|
+
warnings.push(...CL.warnings({ claims, done, today: TODAY(), activity }))
|
|
126
|
+
const recurring = RC.read(root)
|
|
127
|
+
errors.push(...RC.validate(recurring))
|
|
128
|
+
warnings.push(...RC.warnings(RC.status({ ...recurring, done, today: TODAY() })))
|
|
129
|
+
warnings.push(...IB.warnings(root, done, config))
|
|
130
|
+
warnings.push(...AD.sealWarnings(root))
|
|
131
|
+
warnings.push(...R.unrecordedHumanActions(path.resolve(root, '..'), P.readHumanActions(root)))
|
|
132
|
+
warnings.push(...AP.warnings(path.resolve(root, '..')))
|
|
133
|
+
warnings.push(...TR.warnings(path.resolve(root, '..')))
|
|
134
|
+
|
|
135
|
+
// Lo que `upgrade` conserva por estar editado deja de recibir mejoras, y eso es una deuda que no
|
|
136
|
+
// avisa sola: la instancia queda con medio molde viejo y todo se ve normal. Sale acá para que se vea
|
|
137
|
+
// en cada corrida y no sólo el día que alguien actualiza.
|
|
138
|
+
const congelados = O.localChanges(path.resolve(root, '..'))
|
|
139
|
+
if (congelados.length) {
|
|
140
|
+
warnings.push(`${congelados.length} archivo(s) del molde congelados por edición local; `
|
|
141
|
+
+ '`upgrade` los conserva y no les trae mejoras')
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Y lo que `upgrade` no retiró porque no pudo demostrar que fuera suyo: queda ahí, sin colgar de
|
|
145
|
+
// ningún mecanismo, hasta que alguien lo mueva o lo borre. Se cuenta por lo mismo que los congelados
|
|
146
|
+
// — un resto que no se ve se vuelve permanente.
|
|
147
|
+
const restos = O.RETIRED_COMPARTIDO.filter((relative) => fs.existsSync(path.join(root, '..', relative)))
|
|
148
|
+
if (restos.length) {
|
|
149
|
+
warnings.push(`${restos.length} ruta(s) retiradas siguen en disco con contenido tuyo `
|
|
150
|
+
+ `(${restos.join(', ')}); Cauce ya no las distribuye ni las toca`)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const integration = I.validate(path.resolve(root, '..'))
|
|
154
|
+
errors.push(...integration.errors)
|
|
155
|
+
warnings.push(...integration.warnings)
|
|
156
|
+
|
|
157
|
+
warnings.push(...SR.competingSections(root))
|
|
158
|
+
// Sobrescribir una entrada de system/ es legítimo y esperado; lo que no puede pasar es que
|
|
159
|
+
// ocurra en silencio, porque esa entrada deja de recibir las mejoras del toolkit.
|
|
160
|
+
for (const override of O.overrides(path.resolve(root, '..'))) {
|
|
161
|
+
// Y con qué se queda el proyecto: un override sano redefine lo que reemplaza, y el que deja IDs
|
|
162
|
+
// afuera los retira sin decirlo. Nombrarlos es lo único que separa una decisión de un descuido.
|
|
163
|
+
const retired = override.collection === 'planning/rules'
|
|
164
|
+
? SR.retiredByOverride(root, override.project)
|
|
165
|
+
: []
|
|
166
|
+
warnings.push(`${override.collection}/${override.project} sobrescribe ${override.system} `
|
|
167
|
+
+ `(override explícito)${retired.length ? `; deja de regir ${retired.join(', ')}` : ''}`)
|
|
168
|
+
}
|
|
169
|
+
// Y lo que instaló cada runner, contra esas mismas reglas (caso 099).
|
|
170
|
+
warnings.push(...RL.staleLines(path.resolve(root, '..')))
|
|
171
|
+
// Y cuánto pesa lo que ese bloque carga, cuando ya se pasó del umbral. Es la contracara de la línea que
|
|
172
|
+
// `install` imprime al elegir: una instancia suma reglas de a una, cada una razonable, y el total no lo
|
|
173
|
+
// mira nadie hasta que una corrida sale cara.
|
|
174
|
+
warnings.push(...RL.heavyRules(path.resolve(root, '..')))
|
|
175
|
+
// Misma regla para los cargos, que es donde más caro sale: un fork se hace una vez y se olvida.
|
|
176
|
+
const FK = require('../agents/fork')
|
|
177
|
+
for (const entry of FK.drift(path.resolve(root, '..'))) warnings.push(FK.driftLine(entry))
|
|
178
|
+
|
|
179
|
+
warnings.push(...OB.missingSections(path.resolve(root, '..')))
|
|
180
|
+
warnings.push(...OB.orphanCredentials(path.resolve(root, '..')))
|
|
181
|
+
|
|
182
|
+
if (cli.has('--json')) {
|
|
183
|
+
console.log(JSON.stringify({
|
|
184
|
+
ok: !errors.length,
|
|
185
|
+
epics: epics.length,
|
|
186
|
+
queued: backlog.length,
|
|
187
|
+
done: done.entries.length,
|
|
188
|
+
wips: wips.map((one) => one.task),
|
|
189
|
+
errors,
|
|
190
|
+
warnings,
|
|
191
|
+
}))
|
|
192
|
+
if (errors.length) process.exit(1)
|
|
193
|
+
return
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
for (const warning of warnings) console.warn(`⚠ ${warning}`)
|
|
197
|
+
for (const error of errors) console.error(`✗ ${error}`)
|
|
198
|
+
if (errors.length) fail(`\n${errors.length} error(es), ${warnings.length} advertencia(s)`)
|
|
199
|
+
console.log(
|
|
200
|
+
`✓ planning válido: ${epics.length} épica(s), ${backlog.length} tarea(s) en cola, ` +
|
|
201
|
+
`${done.entries.length} terminada(s)`,
|
|
202
|
+
)
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
module.exports = { check }
|
|
@@ -155,6 +155,59 @@ function doneCeremonyWarnings(done, adopted = new Set()) {
|
|
|
155
155
|
return warnings
|
|
156
156
|
}
|
|
157
157
|
|
|
158
|
+
// Lo que sólo existe después de Verify: el registro de la tarea, no su producto. `done/<slug>.md` lo
|
|
159
|
+
// escribe Done, el commit lo escribe Commit y el reclamo se libera al cerrar, así que una aceptación que
|
|
160
|
+
// pida verlos pide algo que en Verify todavía no puede estar.
|
|
161
|
+
const POST_VERIFY = [
|
|
162
|
+
[/\bplanning\/done\b|\bdone\/|\bentrada de DONE\b/i, 'planning/done/'],
|
|
163
|
+
[/\bla evidencia\b|\bevidencia registrada\b/i, 'la evidencia'],
|
|
164
|
+
[/\bel commit\b|\bcommitead[oa]\b/i, 'el commit'],
|
|
165
|
+
[/\bel reclamo\b|\bclaims\//i, 'el reclamo'],
|
|
166
|
+
]
|
|
167
|
+
|
|
168
|
+
// La salida explícita, con la forma que el repositorio ya usa dos veces: `(sin partir: …)` para el umbral
|
|
169
|
+
// de R17 y `n/a — razón` para `tests:` y `commit:`. Acá vale lo mismo que allá —«como lleva su razón
|
|
170
|
+
// escrita se lee en el propio artefacto sin que nadie la cruce»— y por eso no se intenta adivinar si la
|
|
171
|
+
// prosa excluye a Verify. Adivinarlo es lo que no se puede: la única aceptación real que nombra el commit
|
|
172
|
+
// lo hace justamente para decir que no es condición de Verify, y cualquier lista de frases que la
|
|
173
|
+
// reconociera enseñaría a escribir esa frase exacta para silenciar el aviso.
|
|
174
|
+
const OUT_OF_VERIFY = /\(fuera de verify:\s*[^)]+\)/i
|
|
175
|
+
|
|
176
|
+
// Una condición de aceptación que nombra el registro en vez del producto no se puede cumplir nunca: se
|
|
177
|
+
// comprueba en Verify, que corre antes que Commit y que Done. El recorrido lo detecta —y hace bien—, pero
|
|
178
|
+
// recién ahí: en la corrida que originó esto fueron 1,2 M de tokens y once agentes para terminar con el
|
|
179
|
+
// trabajo hecho, sin commit y sin poder cerrar (caso 140).
|
|
180
|
+
//
|
|
181
|
+
// Avisa y no falla, por lo mismo que el aviso de HUMAN_ACTIONS resueltas sin commit —que nació del 121,
|
|
182
|
+
// el mismo daño y el mismo tamaño—: el patrón es de texto y una aceptación legítima puede mencionar la
|
|
183
|
+
// palabra sin depender de ella. Un aviso que salta siempre se termina apagando.
|
|
184
|
+
//
|
|
185
|
+
// Se juzga condición por condición y no la aceptación entera, que es el grano con el que Verify contrasta
|
|
186
|
+
// —su `uncovered` enumera criterios—: las aceptaciones reales traen varias y marcar el párrafo completo
|
|
187
|
+
// señala a las que están bien por vecindad. Y la marca se busca en la condición, no en la tarea, para que
|
|
188
|
+
// excluir una no exima a las demás.
|
|
189
|
+
//
|
|
190
|
+
// Lo que corresponde casi siempre no es borrar la cláusula sino moverla: `tests:`, `qa:` y `commit:` de
|
|
191
|
+
// DONE ya exigen ese registro (PROTOCOL), así que repetirlo en la aceptación no agrega garantía — agrega
|
|
192
|
+
// un bloqueo. Cuando sí corresponde dejarla, se declara y pasa en silencio.
|
|
193
|
+
function unverifiableAcceptance(milestones = []) {
|
|
194
|
+
const warnings = []
|
|
195
|
+
for (const milestone of milestones) {
|
|
196
|
+
for (const task of milestone.tasks || []) {
|
|
197
|
+
for (const condition of String(task.acceptance || '').split(';').map((one) => one.trim())) {
|
|
198
|
+
if (!condition || OUT_OF_VERIFY.test(condition)) continue
|
|
199
|
+
const nombra = POST_VERIFY.filter(([pattern]) => pattern.test(condition))
|
|
200
|
+
if (!nombra.length) continue
|
|
201
|
+
warnings.push(`BACKLOG ${task.slug}: una condición nombra ${nombra.map(([, what]) => what).join(', ')}`
|
|
202
|
+
+ ', que existe después de Verify, así que no se puede comprobar cuando se la comprueba. Eso va '
|
|
203
|
+
+ 'en tests:, qa: o commit: de su entrada de DONE, que ya lo exigen; si de verdad va acá, '
|
|
204
|
+
+ 'declaralo con "(fuera de verify: <razón>)"')
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return warnings
|
|
209
|
+
}
|
|
210
|
+
|
|
158
211
|
function duplicates(values) {
|
|
159
212
|
return [...new Set(values.filter((value, index) => values.indexOf(value) !== index))]
|
|
160
213
|
}
|
|
@@ -362,6 +415,7 @@ module.exports = {
|
|
|
362
415
|
validateState,
|
|
363
416
|
doneEntryErrors,
|
|
364
417
|
doneCeremonyWarnings,
|
|
418
|
+
unverifiableAcceptance,
|
|
365
419
|
validCommitTrace,
|
|
366
420
|
validDecisionTrace,
|
|
367
421
|
validTestTrace,
|
package/engine/planning/state.js
CHANGED
|
@@ -46,7 +46,9 @@ function currentTask({ milestones, done, wips = [], claims = [] }, blockers = []
|
|
|
46
46
|
const wip = wips.find((one) => one.runner === P.wipName(runner)) || null
|
|
47
47
|
const queue = milestones.flatMap((milestone) => milestone.tasks.map((task) => ({ ...task, hito: milestone.slug })))
|
|
48
48
|
const mine = new Set(claims.filter((one) => runner && one.runner === runner).map((one) => one.slug))
|
|
49
|
-
|
|
49
|
+
// El reclamo entero y no sólo su dueño: quién lo tomó alcanza para decir que la tarea está ocupada, y
|
|
50
|
+
// no para decir cómo volver a ella. Eso lo dice el id desde el que se tomó, que vive en el mismo archivo.
|
|
51
|
+
const others = new Map(claims.filter((one) => !mine.has(one.slug)).map((one) => [one.slug, one]))
|
|
50
52
|
if (wip) {
|
|
51
53
|
const active = queue.find((task) => task.slug === wip.task)
|
|
52
54
|
|| {
|
|
@@ -69,15 +71,23 @@ function currentTask({ milestones, done, wips = [], claims = [] }, blockers = []
|
|
|
69
71
|
claimed: Boolean(claimed),
|
|
70
72
|
skipped: queue.filter((task) => !done.set.has(task.slug) && blocked.has(task.slug))
|
|
71
73
|
.map((task) => task.slug),
|
|
72
|
-
|
|
73
|
-
|
|
74
|
+
// Un reclamo hecho desde otro id no siempre es de otro agente: puede ser el mismo, con el id resuelto
|
|
75
|
+
// distinto porque `runner()` lo deduce del árbol donde corre el proceso y en sidecar hay dos árboles
|
|
76
|
+
// plausibles. Ahí el plan existe, está escrito y queda invisible para quien lo escribió. Por eso viajan
|
|
77
|
+
// los dos: el id crudo, que es lo que se exporta para volver, y el plan que ese id dejó, si lo dejó.
|
|
78
|
+
taken: pending.filter((task) => others.has(task.slug)).map((task) => {
|
|
79
|
+
const claim = others.get(task.slug)
|
|
80
|
+
const plan = wips.find((one) => one.task === task.slug && one.runner === P.wipName(claim.runner))
|
|
81
|
+
return { slug: task.slug, owner: claim.owner, runner: claim.runner, wip: plan ? `${plan.runner}.md` : '' }
|
|
82
|
+
}),
|
|
74
83
|
// Lo que espera a otra tarea, con cuál y quién la tiene: la tercera causa por la que una cola puede
|
|
75
84
|
// no ofrecer nada, y `context` las distingue por la misma razón que distingue las otras dos.
|
|
76
85
|
// El filtro garantiza que hay dependencias y que al menos una no está cerrada —sin eso la tarea
|
|
77
86
|
// estaría lista y no acá—, así que buscarla no puede fallar y no lleva defensa.
|
|
78
87
|
waiting: pending.filter((task) => !others.has(task.slug) && !ready(task)).map((task) => {
|
|
79
88
|
const dep = task.depends.find((one) => !done.set.has(one))
|
|
80
|
-
|
|
89
|
+
const otro = others.get(dep)
|
|
90
|
+
return { slug: task.slug, dep, owner: otro ? otro.owner : (mine.has(dep) ? 'vos' : '') }
|
|
81
91
|
}),
|
|
82
92
|
}
|
|
83
93
|
}
|
package/package.json
CHANGED
|
@@ -12,6 +12,11 @@ invariantes.
|
|
|
12
12
|
puede heredar aceptación usando `(→ CN) (epic: NNN)` y declarar `(depende: slug, otro)`. Lane y cast son
|
|
13
13
|
opcionales: sin ellos la tarea está sin clasificar, que es un estado y no un error. Una tarea con
|
|
14
14
|
dependencias no se ofrece ni se toma hasta que todas estén en DONE.
|
|
15
|
+
Una condición se comprueba en Verify, que corre antes que Commit y que Done: la que nombre el commit, el
|
|
16
|
+
reclamo, `done/` o la evidencia registrada pide algo que todavía no existe cuando se la mira, y su lugar
|
|
17
|
+
son los campos `tests:`, `qa:` y `commit:` de DONE, que ya lo exigen. `check` lo avisa sobre la cola. Si
|
|
18
|
+
aun así corresponde dejarla ahí, se declara en la propia condición con `(fuera de verify: <razón>)` —la
|
|
19
|
+
misma salida explícita que `(sin partir: …)` y que `n/a — razón`— y deja de avisarse.
|
|
15
20
|
- DONE: un archivo por tarea cerrada, `done/<slug>.md`, con su entrada `[x]` y los campos `acept:`,
|
|
16
21
|
`fecha:` en AAAA-MM-DD, `done:`, `qa:`, `tests:`, `commit:` y `lane:`. `lane:` repite el carril con el
|
|
17
22
|
que la tarea corrió —`express`, `directo`, `lite`, `full`— o `sin clasificar` si su línea no lo
|
|
@@ -26,3 +26,34 @@ lo hace—, y ahí queda exigida sin estar escrita en ningún lado. Por eso `che
|
|
|
26
26
|
una regla nueva: si lo era, va acá al lado como `P1..Pn` y no se lleva nada puesto.
|
|
27
27
|
|
|
28
28
|
Las convenciones específicas de lenguaje viven junto al servicio que usa ese lenguaje.
|
|
29
|
+
|
|
30
|
+
## Una regla que sólo rige sobre una superficie
|
|
31
|
+
|
|
32
|
+
Toda regla de este directorio se carga en el contexto de arranque de **cada** agente, y eso cuesta: sólo
|
|
33
|
+
lo que trae Cauce son ~16 K tokens por agente, medido. Una regla propia de dos páginas que importa en una
|
|
34
|
+
tarea de cada cien se lee cien veces.
|
|
35
|
+
|
|
36
|
+
Una regla puede declarar a qué superficie pertenece, y entonces se **nombra** sin cargarse:
|
|
37
|
+
|
|
38
|
+
```markdown
|
|
39
|
+
---
|
|
40
|
+
aplica: pagos
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
# Pagos
|
|
44
|
+
|
|
45
|
+
## P3 — Conciliar antes de cerrar
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Qué cambia: el bloque que `automation install` escribe la lista —`- ruta (aplica: pagos)`— en vez de
|
|
49
|
+
importarla, así que pesa una línea y no su archivo entero. Sigue rigiendo igual: `ops context` la devuelve
|
|
50
|
+
entre las reglas del proyecto, el recorrido se la nombra a cada agente que toca código, y quien trabaje
|
|
51
|
+
sobre esa superficie la lee antes de planificar o construir.
|
|
52
|
+
|
|
53
|
+
**Sin el campo, la regla se carga siempre.** Es el default a propósito: una regla que no se leyó no existe,
|
|
54
|
+
así que apartarla del arranque es una decisión del proyecto y nunca algo que se deduzca. Por lo mismo el
|
|
55
|
+
valor es libre y lo elige quien escribe la regla —`pagos`, `infraestructura`, `el front`—: lo que tiene que
|
|
56
|
+
hacer es que quien lo lea sepa cuándo le toca.
|
|
57
|
+
|
|
58
|
+
Conviene para lo que es de un dominio acotado —un proveedor, un stack, una integración— y no para lo que
|
|
59
|
+
gobierna cómo se trabaja: esas se pagan y se cargan.
|