@ingeniomaps/cauce 0.69.0 → 0.71.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 +266 -0
- package/README.md +6 -6
- package/agents/README.md +38 -0
- package/agents/roles/system/accounting-specialist/learning/HISTORY.md +2 -0
- package/agents/roles/system/ai-governance-lead/learning/HISTORY.md +6 -1
- package/agents/roles/system/ai-governance-lead/learning/sources.yaml +4 -4
- package/agents/roles/system/ai-governance-lead/references/operating-model.md +2 -2
- package/agents/roles/system/ai-product-manager/learning/HISTORY.md +4 -1
- package/agents/roles/system/ai-product-manager/learning/sources.yaml +2 -2
- package/agents/roles/system/ai-product-manager/references/operating-model.md +2 -2
- package/agents/roles/system/analytics-engineer/learning/HISTORY.md +4 -1
- package/agents/roles/system/analytics-engineer/learning/sources.yaml +2 -2
- package/agents/roles/system/analytics-engineer/references/operating-model.md +2 -2
- package/agents/roles/system/backend-engineer/learning/HISTORY.md +2 -0
- package/agents/roles/system/business-strategist/learning/HISTORY.md +2 -0
- package/agents/roles/system/business-strategist/learning/sources.yaml +1 -1
- package/agents/roles/system/business-strategist/references/operating-model.md +2 -2
- package/agents/roles/system/cloud-architect/learning/HISTORY.md +1 -1
- package/agents/roles/system/cloud-architect/learning/sources.yaml +2 -2
- package/agents/roles/system/cloud-architect/references/operating-model.md +2 -2
- package/agents/roles/system/community-manager/learning/HISTORY.md +4 -1
- package/agents/roles/system/community-manager/learning/sources.yaml +2 -2
- package/agents/roles/system/community-manager/references/operating-model.md +1 -1
- package/agents/roles/system/content-specialist/learning/HISTORY.md +2 -0
- package/agents/roles/system/customer-success-manager/learning/HISTORY.md +2 -0
- package/agents/roles/system/customer-success-manager/learning/sources.yaml +4 -4
- package/agents/roles/system/customer-success-manager/references/operating-model.md +3 -3
- package/agents/roles/system/customer-support-specialist/learning/HISTORY.md +2 -0
- package/agents/roles/system/customer-support-specialist/learning/sources.yaml +2 -2
- package/agents/roles/system/customer-support-specialist/references/operating-model.md +2 -2
- package/agents/roles/system/data-analyst/learning/HISTORY.md +2 -0
- package/agents/roles/system/data-engineer/learning/HISTORY.md +4 -1
- package/agents/roles/system/data-engineer/learning/sources.yaml +3 -3
- package/agents/roles/system/data-engineer/references/operating-model.md +2 -2
- package/agents/roles/system/data-governance-steward/learning/HISTORY.md +2 -0
- package/agents/roles/system/data-scientist/learning/HISTORY.md +4 -1
- package/agents/roles/system/data-scientist/learning/sources.yaml +3 -3
- package/agents/roles/system/data-scientist/references/operating-model.md +2 -2
- package/agents/roles/system/database-administrator/learning/HISTORY.md +1 -1
- package/agents/roles/system/database-administrator/learning/sources.yaml +2 -2
- package/agents/roles/system/database-administrator/references/operating-model.md +2 -2
- package/agents/roles/system/developer-relations-engineer/learning/HISTORY.md +4 -1
- package/agents/roles/system/devops-engineer/learning/HISTORY.md +2 -0
- package/agents/roles/system/engineering-manager/learning/HISTORY.md +2 -0
- package/agents/roles/system/engineering-manager/learning/sources.yaml +1 -1
- package/agents/roles/system/engineering-manager/references/operating-model.md +2 -2
- package/agents/roles/system/financial-controller/learning/HISTORY.md +2 -0
- package/agents/roles/system/financial-controller/learning/sources.yaml +2 -2
- package/agents/roles/system/financial-controller/references/operating-model.md +1 -1
- package/agents/roles/system/finops-engineer/learning/HISTORY.md +2 -0
- package/agents/roles/system/fraud-risk-analyst/learning/HISTORY.md +2 -0
- package/agents/roles/system/frontend-engineer/learning/HISTORY.md +2 -0
- package/agents/roles/system/growth-marketer/learning/HISTORY.md +2 -0
- package/agents/roles/system/growth-marketer/learning/sources.yaml +1 -1
- package/agents/roles/system/implementation-manager/learning/HISTORY.md +1 -1
- package/agents/roles/system/implementation-manager/learning/sources.yaml +4 -4
- package/agents/roles/system/implementation-manager/references/operating-model.md +3 -3
- package/agents/roles/system/integrations-engineer/learning/HISTORY.md +2 -0
- package/agents/roles/system/kyc-aml-specialist/learning/HISTORY.md +3 -2
- package/agents/roles/system/kyc-aml-specialist/learning/sources.yaml +1 -1
- package/agents/roles/system/kyc-aml-specialist/references/operating-model.md +1 -1
- package/agents/roles/system/legal-counsel/learning/HISTORY.md +6 -1
- package/agents/roles/system/legal-counsel/learning/sources.yaml +1 -1
- package/agents/roles/system/legal-counsel/references/operating-model.md +1 -1
- package/agents/roles/system/logistics-operations-manager/learning/HISTORY.md +2 -0
- package/agents/roles/system/machine-learning-engineer/learning/HISTORY.md +4 -1
- package/agents/roles/system/machine-learning-engineer/learning/sources.yaml +6 -6
- package/agents/roles/system/machine-learning-engineer/references/operating-model.md +3 -3
- package/agents/roles/system/mlops-engineer/learning/HISTORY.md +4 -1
- package/agents/roles/system/mlops-engineer/learning/sources.yaml +2 -2
- package/agents/roles/system/mlops-engineer/references/operating-model.md +2 -2
- package/agents/roles/system/mobile-engineer/learning/HISTORY.md +2 -0
- package/agents/roles/system/mobile-engineer/learning/sources.yaml +1 -1
- package/agents/roles/system/partnerships-manager/learning/HISTORY.md +4 -1
- package/agents/roles/system/partnerships-manager/learning/sources.yaml +2 -2
- package/agents/roles/system/partnerships-manager/references/operating-model.md +2 -2
- package/agents/roles/system/people-operations-manager/learning/HISTORY.md +4 -1
- package/agents/roles/system/people-operations-manager/learning/sources.yaml +2 -2
- package/agents/roles/system/people-operations-manager/references/operating-model.md +2 -2
- package/agents/roles/system/privacy-compliance-specialist/learning/HISTORY.md +2 -0
- package/agents/roles/system/privacy-compliance-specialist/learning/sources.yaml +1 -1
- package/agents/roles/system/privacy-compliance-specialist/references/operating-model.md +1 -1
- package/agents/roles/system/procurement-manager/learning/HISTORY.md +1 -1
- package/agents/roles/system/procurement-manager/learning/sources.yaml +2 -2
- package/agents/roles/system/procurement-manager/references/operating-model.md +2 -2
- package/agents/roles/system/product-manager/learning/HISTORY.md +1 -1
- package/agents/roles/system/product-marketing-manager/learning/HISTORY.md +2 -0
- package/agents/roles/system/product-marketing-manager/learning/sources.yaml +1 -1
- package/agents/roles/system/project-manager/learning/HISTORY.md +4 -1
- package/agents/roles/system/project-manager/learning/sources.yaml +1 -1
- package/agents/roles/system/project-manager/references/operating-model.md +2 -2
- package/agents/roles/system/qa-engineer/learning/HISTORY.md +2 -0
- package/agents/roles/system/qa-engineer/learning/sources.yaml +9 -11
- package/agents/roles/system/qa-engineer/references/operating-model.md +2 -2
- package/agents/roles/system/release-manager/learning/HISTORY.md +1 -1
- package/agents/roles/system/sales-representative/learning/HISTORY.md +2 -0
- package/agents/roles/system/sales-representative/learning/sources.yaml +2 -2
- package/agents/roles/system/sales-representative/references/operating-model.md +1 -1
- package/agents/roles/system/security-engineer/learning/HISTORY.md +2 -0
- package/agents/roles/system/security-engineer/learning/sources.yaml +1 -1
- package/agents/roles/system/security-engineer/references/operating-model.md +1 -1
- package/agents/roles/system/site-reliability-engineer/learning/HISTORY.md +2 -0
- package/agents/roles/system/software-architect/learning/HISTORY.md +2 -0
- package/agents/roles/system/software-architect/learning/sources.yaml +2 -2
- package/agents/roles/system/software-architect/references/operating-model.md +1 -1
- package/agents/roles/system/solutions-engineer/learning/HISTORY.md +4 -1
- package/agents/roles/system/solutions-engineer/learning/sources.yaml +4 -4
- package/agents/roles/system/solutions-engineer/references/operating-model.md +2 -2
- package/agents/roles/system/tech-lead/learning/HISTORY.md +2 -0
- package/agents/roles/system/technical-program-manager/learning/HISTORY.md +4 -1
- package/agents/roles/system/technical-program-manager/learning/sources.yaml +2 -2
- package/agents/roles/system/technical-program-manager/references/operating-model.md +3 -3
- package/agents/roles/system/technical-writer/learning/HISTORY.md +4 -1
- package/agents/roles/system/treasury-analyst/learning/HISTORY.md +2 -0
- package/agents/roles/system/ui-designer/learning/HISTORY.md +2 -0
- package/agents/roles/system/ui-designer/learning/sources.yaml +2 -2
- package/agents/roles/system/user-researcher/learning/HISTORY.md +1 -1
- package/agents/roles/system/user-researcher/learning/sources.yaml +1 -1
- package/agents/roles/system/ux-designer/learning/HISTORY.md +2 -0
- package/agents/roles/system/ux-designer/learning/sources.yaml +1 -1
- package/agents/roles/system/ux-designer/references/operating-model.md +1 -1
- package/automatization/AGENTS.md +1 -1
- package/automatization/runners/antigravity/rules/cauce.md +1 -1
- package/automatization/runners/claude/CLAUDE.md +1 -1
- package/automatization/runners/codex/AGENTS.md +1 -1
- package/automatization/runners/gemini/GEMINI.md +1 -1
- package/automatization/shared/skills/autobuild/SKILL.md +1 -1
- package/automatization/workflows/autobuild.js +81 -27
- package/engine/agents/learning-seal.js +36 -5
- package/engine/agents/learning-sources.js +46 -2
- package/engine/agents/learning.js +13 -1
- package/engine/cli/archive.js +87 -0
- package/engine/cli/args.js +6 -2
- package/engine/cli/catalog.js +9 -1
- package/engine/cli/claims.js +125 -0
- package/engine/cli/ops.js +15 -4
- package/engine/cli/planning.js +139 -107
- package/engine/cli/worktree.js +89 -0
- package/engine/core/ownership.js +13 -5
- package/engine/core/repos.js +67 -0
- package/engine/hooks/files.js +3 -2
- package/engine/planning/adoption.js +1 -1
- package/engine/planning/claims.js +153 -0
- package/engine/planning/contracts.js +43 -202
- package/engine/planning/parser.js +51 -12
- package/engine/planning/state.js +39 -6
- package/engine/planning/structure.js +220 -0
- package/package.json +1 -1
- package/template/.gitattributes +19 -0
- package/template/AGENTS.md +47 -5
- package/template/automatization/AGENTS.md +1 -1
- package/template/gitignore +9 -0
- package/template/planning/BACKLOG.md +5 -0
- package/template/planning/FLOW.md +1 -1
- package/template/planning/PROTOCOL.md +34 -8
- package/template/planning/README.md +3 -3
- package/template/planning/RECURRING.md +1 -1
- package/template/planning/adr/system/OPS-001-planificacion-como-fuente-de-verdad.md +3 -2
- package/template/planning/business-rules/system/BR-OPS-001-una-sola-tarea-activa.md +7 -4
- package/template/planning/business-rules/system/BR-OPS-005-una-tarea-un-runner.md +44 -0
- package/template/planning/claims/README.md +70 -0
- package/template/planning/delivery/README.md +1 -0
- package/template/planning/delivery/multi-repo.md +11 -0
- package/template/planning/delivery/teamwork.md +162 -0
- package/template/planning/done/README.md +41 -0
- package/template/planning/wip/README.md +49 -0
- package/template/planning/DONE.md +0 -13
- package/template/planning/WIP.md +0 -22
|
@@ -15,7 +15,7 @@ Los cinco recorridos llegan como skills en `.agents/skills/` y se invocan con `$
|
|
|
15
15
|
acá —son referencia, no un runtime compatible—, así que cada recorrido se ejecuta fase por fase
|
|
16
16
|
siguiendo el protocolo.
|
|
17
17
|
|
|
18
|
-
`{{OPS_DIR}}planning/
|
|
18
|
+
`{{OPS_DIR}}planning/wip/<runner>.md` es el mutex: una sola tarea activa. `{{OPS_DIR}}planning/AWAITING_REVIEW.md`
|
|
19
19
|
bloquea una corrida nueva hasta que un humano revise. Nada se promueve desde `INBOX.md` sin aprobación.
|
|
20
20
|
|
|
21
21
|
Antes de cerrar, corré `node {{OPS_DIR}}tools/ops.js check {{OPS_DIR}}planning`.
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
@{{OPS_DIR}}planning/rules/system/conduct.md
|
|
9
9
|
|
|
10
10
|
`{{OPS_DIR}}planning/PROTOCOL.md` es la fuente de verdad. Ejecuta `/cauce:autobuild` fase por fase; los
|
|
11
|
-
workflows JS de Claude son referencia, no un runtime compatible. `{{OPS_DIR}}planning/
|
|
11
|
+
workflows JS de Claude son referencia, no un runtime compatible. `{{OPS_DIR}}planning/wip/<runner>.md` es el mutex
|
|
12
12
|
y `{{OPS_DIR}}planning/AWAITING_REVIEW.md` bloquea una corrida nueva.
|
|
13
13
|
|
|
14
14
|
Los hooks de `.gemini/settings.json` son obligatorios y bloquean por su cuenta. **Requieren que la
|
|
@@ -3,7 +3,7 @@ name: autobuild
|
|
|
3
3
|
description: Ejecuta el siguiente hito aprobado siguiendo el protocolo Cauce.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
Lee completos `{{OPS_DIR}}AGENTS.md`, `{{OPS_DIR}}planning/PROTOCOL.md` y `{{OPS_DIR}}planning/
|
|
6
|
+
Lee completos `{{OPS_DIR}}AGENTS.md`, `{{OPS_DIR}}planning/PROTOCOL.md` y `{{OPS_DIR}}planning/wip/`. Si existe
|
|
7
7
|
`{{OPS_DIR}}planning/AWAITING_REVIEW.md`, detente y explica la acción humana pendiente. Toma solamente la primera tarea
|
|
8
8
|
aprobada, persiste el estado en WIP y ejecuta Build, Review, Verify y QA en orden. Usa los comandos reales del
|
|
9
9
|
servicio, registra evidencia verificable en DONE y detente en el checkpoint entre hitos. No hagas push ni
|
|
@@ -21,7 +21,7 @@ export const meta = {
|
|
|
21
21
|
{ title: 'Verify', detail: 'Los gates del servicio y la aceptación que ninguna prueba codifica' },
|
|
22
22
|
{ title: 'QA', detail: 'El comportamiento ejercitado como lo ve quien lo usa' },
|
|
23
23
|
{ title: 'Commit', detail: 'Conventional Commits, uno por naturaleza del diff, sin push' },
|
|
24
|
-
{ title: 'Done', detail: 'Cierre atómico
|
|
24
|
+
{ title: 'Done', detail: 'Cierre atómico: evidencia escrita, cola y plan limpios, reserva suelta' },
|
|
25
25
|
{ title: 'Closing', detail: 'Check de planning y checkpoint humano del hito' },
|
|
26
26
|
],
|
|
27
27
|
}
|
|
@@ -31,8 +31,8 @@ const CONFIG = `${ROOT}/ops.config.json`
|
|
|
31
31
|
const P = `${ROOT}/planning`
|
|
32
32
|
const ORG = `${ROOT}/organization`
|
|
33
33
|
const BACKLOG = `${P}/BACKLOG.md`
|
|
34
|
-
|
|
35
|
-
const
|
|
34
|
+
// Una tarea cerrada escribe su propio archivo, así que dos corridas en paralelo no comparten ninguno.
|
|
35
|
+
const doneFile = (slug) => `${P}/done/${slug}.md`
|
|
36
36
|
const HUMAN = `${P}/HUMAN_ACTIONS.md`
|
|
37
37
|
const GATE = `${P}/AWAITING_REVIEW.md`
|
|
38
38
|
const ROADMAP = `${P}/roadmap`
|
|
@@ -40,8 +40,12 @@ const ROADMAP = `${P}/roadmap`
|
|
|
40
40
|
// Estado de planning tal como lo emite `ops context --json`; ningún modelo parsea BACKLOG ni WIP.
|
|
41
41
|
const CONTEXT = {
|
|
42
42
|
type: 'object', additionalProperties: false,
|
|
43
|
-
required: ['blocked', 'hasTask', 'wipActive', 'queued', 'cast'],
|
|
43
|
+
required: ['blocked', 'hasTask', 'wipActive', 'queued', 'cast', 'readOk'],
|
|
44
44
|
properties: {
|
|
45
|
+
// Si el comando salió con error no hay estado que reportar, y `hasTask: false, queued: 0` es
|
|
46
|
+
// exactamente lo que un modelo completa cuando no tiene qué poner. Sin este campo esa invención se
|
|
47
|
+
// lee igual que una cola terminada, y Pick la toma como permiso para promover.
|
|
48
|
+
readOk: { type: 'boolean' },
|
|
45
49
|
blocked: { type: 'string' }, hasTask: { type: 'boolean' }, wipActive: { type: 'boolean' },
|
|
46
50
|
queued: { type: 'integer' }, slug: { type: 'string' }, hito: { type: 'string' },
|
|
47
51
|
service: { type: 'string' }, acceptance: { type: 'string' }, epic: { type: 'string' },
|
|
@@ -58,11 +62,20 @@ const CONTEXT = {
|
|
|
58
62
|
properties: { build: { type: 'string' }, review: { type: 'array', items: { type: 'string' } } },
|
|
59
63
|
},
|
|
60
64
|
blockedTasks: { type: 'array', items: { type: 'string' } },
|
|
65
|
+
// Si la tarea que `context` devolvió ya está reservada a nombre de este runner. Libre no significa
|
|
66
|
+
// que sea nuestra: significa que todavía la puede tomar cualquiera, y dos corridas en paralelo la
|
|
67
|
+
// reciben las dos.
|
|
68
|
+
claimed: { type: 'boolean' },
|
|
69
|
+
// La fecha de hoy según el motor. Este recorrido no tiene reloj propio a propósito.
|
|
70
|
+
today: { type: 'string' },
|
|
71
|
+
// Dónde va el plan de este runner. El nombre sale de su id y el recorrido no lo deriva: lo
|
|
72
|
+
// pregunta, igual que la fecha.
|
|
73
|
+
wipFile: { type: 'string' },
|
|
61
74
|
},
|
|
62
75
|
}
|
|
63
|
-
const
|
|
64
|
-
type: 'object', additionalProperties: false, required: ['
|
|
65
|
-
properties: {
|
|
76
|
+
const CLAIM = {
|
|
77
|
+
type: 'object', additionalProperties: false, required: ['claimed'],
|
|
78
|
+
properties: { claimed: { type: 'boolean' }, details: { type: 'string' } },
|
|
66
79
|
}
|
|
67
80
|
const READY = {
|
|
68
81
|
type: 'object', additionalProperties: false, required: ['ready', 'needsHuman'],
|
|
@@ -216,8 +229,16 @@ const CLASSIFICATION = {
|
|
|
216
229
|
|
|
217
230
|
const CONTRACT = {
|
|
218
231
|
type: 'object', additionalProperties: false,
|
|
219
|
-
required: ['project', 'workspaceRoots', 'maxTaskHours', 'commitPerTask', 'humanCheckpoint', 'contracts'
|
|
232
|
+
required: ['project', 'workspaceRoots', 'maxTaskHours', 'commitPerTask', 'humanCheckpoint', 'contracts',
|
|
233
|
+
'rootOk'],
|
|
220
234
|
properties: {
|
|
235
|
+
// `ROOT` viaja escrito en el workflow y es relativo al cwd de los agentes: si la sesión abrió en otra
|
|
236
|
+
// carpeta, todas las rutas resuelven a `<raíz>/<raíz>/…` y ninguna existe. Nada lo comprobaba, y el
|
|
237
|
+
// recorrido gastaba Triage entero sobre archivos ausentes antes de parar más abajo por otra causa,
|
|
238
|
+
// nombrando el planning en vez de la raíz de la que ese planning cuelga.
|
|
239
|
+
//
|
|
240
|
+
// Se pregunta acá porque acá ya se leen los cuatro archivos: cuesta un campo y ningún agente más.
|
|
241
|
+
rootOk: { type: 'boolean' },
|
|
221
242
|
project: { type: 'string' }, workspaceRoots: { type: 'array', minItems: 1, items: { type: 'string' } },
|
|
222
243
|
// La puerta que el proyecto declara, si la declara. Viaja con la raíz porque es de la base de código
|
|
223
244
|
// y no del runner: un monorepo tiene una por servicio, y uno solo tiene una sola.
|
|
@@ -276,7 +297,9 @@ phase('Triage')
|
|
|
276
297
|
// subagente como «Límites del proyecto» eran sólo los genéricos del toolkit.
|
|
277
298
|
const contract = await agent(
|
|
278
299
|
`${BASE}\n\nLeé ${ROOT}/AGENTS.md, ${ORG}/workspace.md, ${CONFIG} y ${P}/PROTOCOL.md una sola vez y no ` +
|
|
279
|
-
`leas nada más.
|
|
300
|
+
`leas nada más. Poné rootOk en true sólo si los cuatro existieron y los pudiste leer; si alguno no ` +
|
|
301
|
+
`estaba, rootOk en false y el resto en sus valores vacíos, sin deducirlos de otra fuente. ` +
|
|
302
|
+
`Reportá los ` +
|
|
280
303
|
`valores de configuración textualmente: project, workspaceRoots como entradas "nombre → ruta", ` +
|
|
281
304
|
`runner.maxTaskHours, runner.commitPerTask y runner.humanCheckpointBetweenMilestones como humanCheckpoint. ` +
|
|
282
305
|
`En gates poné una entrada "ruta → comando" por cada workspaceRoot que declare \`verify\`, y ninguna por ` +
|
|
@@ -288,6 +311,12 @@ const contract = await agent(
|
|
|
288
311
|
{ schema: CONTRACT, label: 'contract-digest' },
|
|
289
312
|
)
|
|
290
313
|
if (!contract) return stop('contract-unavailable', `no se pudo leer ${CONFIG} ni ${P}/PROTOCOL.md`)
|
|
314
|
+
// Falla acá y nombrando la raíz, que es lo que hace falta para arreglarlo: parar más abajo mandaba a
|
|
315
|
+
// revisar el planning, y el planning está bien — lo que no existe es la carpeta de la que cuelga.
|
|
316
|
+
if (!contract.rootOk) {
|
|
317
|
+
return stop('root-unreadable', `${ROOT} no se pudo leer entero. Es una ruta relativa a la carpeta `
|
|
318
|
+
+ `donde se abre la herramienta: comprobá desde dónde estás corriendo el recorrido.`)
|
|
319
|
+
}
|
|
291
320
|
|
|
292
321
|
const bounds = contract.boundaries || []
|
|
293
322
|
const limits = bounds.length ? ` Límites del proyecto: ${bounds.join('; ')}.` : ''
|
|
@@ -311,14 +340,22 @@ const write = (prompt, options = {}) => agent(`${LEDGER}\n\n${prompt}`, options)
|
|
|
311
340
|
// WIP y HUMAN_ACTIONS nunca entran al contexto de un modelo, y su tamaño deja de costar tokens.
|
|
312
341
|
const readContext = () => read(
|
|
313
342
|
`Corré "node tools/ops.js context ${P} --json" desde ${ROOT} y reportá sólo lo que imprimió. Derivá hasTask ` +
|
|
314
|
-
`de si task es null, wipActive de si wip es null
|
|
343
|
+
`de si task es null, wipActive de si wip es null, claimed del campo claimed, today y wipFile de sus ` +
|
|
344
|
+
`campos, y lane ` +
|
|
345
|
+
`de task.tier; copiá slug, ` +
|
|
346
|
+
`hito, service, acceptance, ` +
|
|
315
347
|
`epic y cast de task, y epicContext de epic.context —vacío si no hay épica—. El comando es la fuente de ` +
|
|
316
|
-
`verdad: no abras archivos de planning para completarlo
|
|
348
|
+
`verdad: no abras archivos de planning para completarlo. Poné readOk en true sólo si el comando salió ` +
|
|
349
|
+
`con código 0 y devolvió JSON; si falló, readOk en false y el resto en sus valores vacíos, sin ` +
|
|
350
|
+
`deducir el estado de ninguna otra fuente.`,
|
|
317
351
|
{ schema: CONTEXT, label: 'planning-context' },
|
|
318
352
|
)
|
|
319
353
|
|
|
320
354
|
let planning = await readContext()
|
|
321
355
|
if (!planning) return stop('context-unavailable', `no se pudo leer el estado de ${P}`)
|
|
356
|
+
// Que el agente conteste no significa que haya leído: el schema se completa igual con ceros. Parar acá
|
|
357
|
+
// cuesta una corrida; seguir sobre una lectura fallida escribe en el BACKLOG, y eso no se revierte solo.
|
|
358
|
+
if (!planning.readOk) return stop('context-unavailable', `${P} no se pudo leer; revisá la ruta y el cwd`)
|
|
322
359
|
if (planning.blocked) return stop('awaiting-human-review', `${GATE} tiene un checkpoint humano sin resolver`)
|
|
323
360
|
|
|
324
361
|
let currentMilestone = planning.wipActive ? planning.hito : ''
|
|
@@ -334,23 +371,38 @@ const classified = new Set()
|
|
|
334
371
|
|
|
335
372
|
while (rounds++ < MAX_TASKS) {
|
|
336
373
|
phase('Pick')
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
planning = await readContext()
|
|
347
|
-
if (!planning) return stop('context-unavailable', `no se pudo releer el estado de ${P}`)
|
|
348
|
-
}
|
|
374
|
+
// Sin tarea y con la cola vacía, la corrida termina. **No expande la próxima épica**, y eso no es una
|
|
375
|
+
// limitación sino la regla: el roadmap llama `open` a «candidata editable que aún no fue promovida al
|
|
376
|
+
// backlog», así que pegarla en la cola es promoverla — y BR-OPS-002 deja una propuesta fuera de la cola
|
|
377
|
+
// hasta que la apruebe una persona. El prompt que hacía esto pedía «la próxima épica abierta y
|
|
378
|
+
// aprobada», y «aprobada» no correspondía a ningún dato: una épica declara `epic`, `title`, `status` y
|
|
379
|
+
// `service`, y ninguno registra una aprobación.
|
|
380
|
+
//
|
|
381
|
+
// `context` nombra la que sigue, igual que nombra una recurrencia vencida y por el mismo motivo: la
|
|
382
|
+
// máquina calcula y la persona encola.
|
|
349
383
|
if (!planning.hasTask || (currentMilestone && planning.hito !== currentMilestone)) break
|
|
350
384
|
const task = {
|
|
351
385
|
id: planning.slug, hito: planning.hito, service: planning.service,
|
|
352
386
|
acceptance: planning.acceptance, epic: planning.epic, epicContext: planning.epicContext || '',
|
|
353
387
|
}
|
|
388
|
+
// Reservar antes de construir, y antes de fijar el hito de la corrida. Sin esto dos corridas en
|
|
389
|
+
// paralelo trabajan la misma tarea: `context` sólo puede saltear lo que alguien ya reclamó, y el
|
|
390
|
+
// primero en preguntar todavía no reclamó nada. La ventana entre preguntar y reservar existe igual, y
|
|
391
|
+
// por eso perder la carrera no es un error: se relee y se sigue con la que quedó libre.
|
|
392
|
+
if (!planning.claimed && !planning.wipActive) {
|
|
393
|
+
phase('Claim')
|
|
394
|
+
const reserva = await write(
|
|
395
|
+
`Corré "node tools/ops.js claim ${P} ${task.id}" desde ${ROOT}. No escribas ningún archivo vos: lo ` +
|
|
396
|
+
`escribe el comando. claimed=true sólo con exit 0; si falla porque la tomó otro, claimed=false y ` +
|
|
397
|
+
`copiá el mensaje en details.`,
|
|
398
|
+
{ schema: CLAIM, label: `claim:${task.id}` },
|
|
399
|
+
)
|
|
400
|
+
if (!reserva || !reserva.claimed) {
|
|
401
|
+
planning = await readContext()
|
|
402
|
+
if (!planning) return stop('context-unavailable', `no se pudo releer el estado de ${P}`)
|
|
403
|
+
continue
|
|
404
|
+
}
|
|
405
|
+
}
|
|
354
406
|
currentMilestone = task.hito
|
|
355
407
|
|
|
356
408
|
// Ver la tarea y, si no está clasificada, clasificarla antes de ejecutarla. Se hace una vez por
|
|
@@ -535,7 +587,7 @@ while (rounds++ < MAX_TASKS) {
|
|
|
535
587
|
`dio; recién después implementá. Un test que pasa antes de que exista el código no asercia lo que dice ` +
|
|
536
588
|
`aserciar: endurecelo y volvé a correr hasta verlo fallar. Corré las pruebas que necesites para ver ese ` +
|
|
537
589
|
`rojo y ese verde, y nada más: los gates completos, el QA, el commit y el cierre son fases posteriores, ` +
|
|
538
|
-
`así que no toques ${
|
|
590
|
+
`así que no toques ${P}/done/ ni ${BACKLOG} ni el status del WIP. Lo que el plan no previó va en discovered y ` +
|
|
539
591
|
`no en el código a secas: kind=edge si esta tarea lo puede fijar —y entonces entra con su prueba, que ` +
|
|
540
592
|
`nombrás en test y anotás en redFirst—, kind=open si lo notaste y no impide entregar la aceptación: se ` +
|
|
541
593
|
`registra para que lo decida quien corresponde y el recorrido sigue. Si de verdad no podés entregar sin ` +
|
|
@@ -693,9 +745,11 @@ while (rounds++ < MAX_TASKS) {
|
|
|
693
745
|
|
|
694
746
|
phase('Done')
|
|
695
747
|
await write(
|
|
696
|
-
`Cerrá ${task.id} de forma atómica:
|
|
697
|
-
`tests y commit
|
|
698
|
-
`
|
|
748
|
+
`Cerrá ${task.id} de forma atómica: escribí ${doneFile(task.id)} con su evidencia —acept, ` +
|
|
749
|
+
`fecha: ${planning.today}, done, qa, tests y commit, en el formato de entrada que trae este preámbulo—; ` +
|
|
750
|
+
`sacala junto con sus notas indentadas de ${BACKLOG}; cerrá su épica sólo si no queda ` +
|
|
751
|
+
`ninguna tarea etiquetada; dejá ${P}/${planning.wipFile} en status IDLE; y soltá la reserva corriendo ` +
|
|
752
|
+
`"node tools/ops.js release ${P} ${task.id}". En decisions no nombres una fase ni un cargo ` +
|
|
699
753
|
`que no figure en estos hechos. Hechos: lane=${planning.lane || 'sin clasificar'}; ` +
|
|
700
754
|
`review=${reviewFact}; fases=${ran.join(' → ')}; build=${build.summary}; ` +
|
|
701
755
|
`verify=${JSON.stringify(verified.commands)}; qa=${qa.evidence}; commit=${commit.hash || commit.reason}.`,
|
|
@@ -12,6 +12,8 @@ const path = require('node:path')
|
|
|
12
12
|
const { atomicWrite } = require('../core/files')
|
|
13
13
|
const { isoDate, proposalFiles, proposalState, assertWritable, lastOfPeriod } = require('./learning-files')
|
|
14
14
|
const { section } = require('../planning/parser')
|
|
15
|
+
// La misma identidad con la que se reclama una tarea: quién es la persona, no qué runner corre.
|
|
16
|
+
const { owner } = require('../planning/claims')
|
|
15
17
|
|
|
16
18
|
// El cuerpo sin sellar, en los dos estados que produce el ciclo: «pendiente» lo escribe el molde y
|
|
17
19
|
// «aprobada» la firma. Lo lee la guarda de más abajo y lo reemplaza el sello, así que vive una vez.
|
|
@@ -102,22 +104,51 @@ function archive(root, agent, period = '', kind = 'agent') {
|
|
|
102
104
|
+ 'Archivar es para lo que se miró y no cambia nada.',
|
|
103
105
|
)
|
|
104
106
|
}
|
|
107
|
+
// Quién archivó, que es la mitad que faltaba. Sellar saca el responsable del documento —lo escribió la
|
|
108
|
+
// firma— y archivar no pasa por firma, así que sale de la identidad de quien corre el comando: la misma
|
|
109
|
+
// que `claim` usa para decir de quién es una tarea. Sin esto la propuesta quedaba en «por definir» y no
|
|
110
|
+
// había forma de distinguir una decisión de un olvido.
|
|
111
|
+
const responsible = owner(root) || 'sin identificar'
|
|
105
112
|
atomicWrite(file, text
|
|
106
113
|
.replace(/^status:\s*\S+\s*$/m, 'status: archived')
|
|
107
114
|
.replace(/^-[ \t]*Estado:[ \t]*pendiente[ \t]*$/mi, '- Estado: archivada')
|
|
115
|
+
.replace(/^-[ \t]*Responsable:[ \t]*por definir[ \t]*$/mi, `- Responsable: ${responsible}`)
|
|
108
116
|
.replace(/^-[ \t]*Fecha:[ \t]*por definir[ \t]*$/mi, `- Fecha: ${isoDate(new Date())}`))
|
|
117
|
+
// La fila va para los dos tipos, y no sólo para los recorridos como en `seal`: allá los cargos los
|
|
118
|
+
// registra `agent-promote` al aplicar, y archivar no pasa por ningún workflow que lo haga.
|
|
119
|
+
// La raíz del cargo, que es `<cargo>/learning/proposals/<archivo>` sin sus tres últimos tramos:
|
|
120
|
+
// `appendHistory` agrega `learning/` por su cuenta.
|
|
121
|
+
//
|
|
122
|
+
// La celda del cambio lleva el criterio y no queda vacía. Es el único que este comando admite —archivar
|
|
123
|
+
// *es* decidir que no cambia nada— así que decirlo evita que la fila se lea como un registro a medias.
|
|
124
|
+
// Archivar por otra razón, como posponer, necesitaría un campo que hoy no existe.
|
|
125
|
+
appendHistory(path.dirname(path.dirname(path.dirname(file))), file, responsible,
|
|
126
|
+
'Se miró y no cambia nada.', 'archivada')
|
|
109
127
|
return { file, already: false }
|
|
110
128
|
}
|
|
111
129
|
|
|
112
|
-
// Una fila por propuesta
|
|
113
|
-
// una tabla que lo repite entero deja de leerse.
|
|
114
|
-
|
|
130
|
+
// Una fila por propuesta cerrada, cualquiera sea el destino. El cambio va en una línea: el documento
|
|
131
|
+
// entero está a un enlace, y una tabla que lo repite entero deja de leerse.
|
|
132
|
+
//
|
|
133
|
+
// La decisión es un parámetro y no la constante `aplicada` porque la columna de la tabla se llama
|
|
134
|
+
// «Decisión» y hay dos: aplicar y archivar. Archivar no dejaba fila, así que una propuesta mirada y
|
|
135
|
+
// descartada era indistinguible de una que nadie miró — y eso lo pagaban los informes siguientes, que
|
|
136
|
+
// gastaban su recomendación explicando el estado en vez de su profesión.
|
|
137
|
+
const HISTORY_HEADER = '| Fecha | Propuesta | Decisión | Aprobó | Cambio aplicado |\n|---|---|---|---|---|\n'
|
|
138
|
+
|
|
139
|
+
function appendHistory(target, file, responsible, change, decision = 'aplicada') {
|
|
115
140
|
const history = path.join(target, 'learning', 'HISTORY.md')
|
|
116
141
|
if (!fs.existsSync(history)) return
|
|
142
|
+
const previous = fs.readFileSync(history, 'utf8')
|
|
117
143
|
const line = change.split('\n').map((one) => one.trim()).filter(Boolean)[0] || ''
|
|
118
|
-
const row = `| ${isoDate(new Date())} | \`${path.basename(file)}\` |
|
|
144
|
+
const row = `| ${isoDate(new Date())} | \`${path.basename(file)}\` | ${decision} | ${responsible} `
|
|
119
145
|
+ `| ${line.slice(0, 160)} |\n`
|
|
120
|
-
|
|
146
|
+
// Dieciséis de los cincuenta y tres cargos tienen el archivo sin la tabla, sólo con su párrafo de
|
|
147
|
+
// encabezado, y la fila quedaba pegada ahí: en markdown eso no es una tabla sino texto con barras, y
|
|
148
|
+
// nadie lo veía porque el archivo se lee dos veces al año. Se agrega la cabecera antes de la primera
|
|
149
|
+
// fila en vez de exigir que ya esté, que es pedirle a cada cargo que se acuerde.
|
|
150
|
+
const cabecera = /^\|\s*Fecha\s*\|/m.test(previous) ? '' : `\n${HISTORY_HEADER}`
|
|
151
|
+
fs.appendFileSync(history, `${previous.endsWith('\n') ? '' : '\n'}${cabecera}${row}`)
|
|
121
152
|
}
|
|
122
153
|
|
|
123
154
|
module.exports = { seal, archive }
|
|
@@ -90,6 +90,12 @@ function sourceTiers(text) {
|
|
|
90
90
|
return [...body.matchAll(/tier:\s*([A-Za-z-]+)/g)].map((hit) => hit[1])
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
+
// Una entrada escrita en una sola línea: seis cargos del catálogo la escriben así y cuarenta y siete la
|
|
94
|
+
// reparten en varias. Leyendo sólo la segunda forma, esos seis no tenían el chequeo de URL duplicada que
|
|
95
|
+
// hay más abajo, y nada lo decía porque no encontrar duplicados y no mirar se ven igual.
|
|
96
|
+
const FLOW_ENTRY = /^\s*-\s*\{[^}]*\bname:\s*([^,}]+?)\s*,[^}]*\burl:\s*"?([^",}\s]+)/
|
|
97
|
+
const quitar = (value) => value.replace(/^['"]|['"]$/g, '')
|
|
98
|
+
|
|
93
99
|
// Las fuentes de un cargo, por su URL. La misma URL con dos nombres es una sola fuente contada dos
|
|
94
100
|
// veces: el catálogo llegó a tener la especificación OpenAPI bajo tres —`OpenAPI Specification`,
|
|
95
101
|
// `...latest published` y `...3.2.0`— así que arreglarle el `tier` a un cargo no se lo arreglaba a los
|
|
@@ -99,14 +105,52 @@ function sourceUrls(text) {
|
|
|
99
105
|
const out = []
|
|
100
106
|
let name = ''
|
|
101
107
|
for (const line of body.split('\n')) {
|
|
108
|
+
const flow = line.match(FLOW_ENTRY)
|
|
109
|
+
if (flow) { out.push({ name: quitar(flow[1]), url: flow[2].replace(/\/+$/, '') }); continue }
|
|
102
110
|
const declared = line.match(/^\s*-\s*name:\s*(.+?)\s*$/)
|
|
103
|
-
if (declared) { name = declared[1]
|
|
111
|
+
if (declared) { name = quitar(declared[1]); continue }
|
|
104
112
|
const url = line.match(/^\s*url:\s*(\S+)/)
|
|
105
113
|
if (url && name) out.push({ name, url: url[1].replace(/\/+$/, '') })
|
|
106
114
|
}
|
|
107
115
|
return out
|
|
108
116
|
}
|
|
109
117
|
|
|
118
|
+
// Todo lo que un cargo cita y alguien va a abrir. `sources.yaml` es lo que investiga; `references/` y
|
|
119
|
+
// `SKILL.md` son el método que sigue, y hasta 0.71.0 nadie las miraba: 29 de las 207 URLs de esos
|
|
120
|
+
// documentos no servían, entre ellas dos 404 de páginas que se habían movido.
|
|
121
|
+
//
|
|
122
|
+
// `evaluations/` queda afuera y no por costo: los casos adversariales **inventan** dominios a propósito
|
|
123
|
+
// —veintiuna URLs bajo `.example` y marcas que no existen— y comprobarlas reportaría rotas las que están
|
|
124
|
+
// bien escritas. `learning/reports` y `learning/proposals` también, por lo contrario: son evidencia
|
|
125
|
+
// fechada de que algo dio 403 el día que se consultó, y eso no se arregla.
|
|
126
|
+
const DOC_URL = /https?:\/\/[^\s)>"`\]]+/g
|
|
127
|
+
function documentUrls(dir) {
|
|
128
|
+
const out = []
|
|
129
|
+
const seen = new Set()
|
|
130
|
+
const add = (raw, origin) => {
|
|
131
|
+
const url = raw.replace(/[.,;:]+$/, '')
|
|
132
|
+
if (seen.has(url)) return
|
|
133
|
+
seen.add(url)
|
|
134
|
+
out.push({ url, origin })
|
|
135
|
+
}
|
|
136
|
+
const below = (base, relative) => {
|
|
137
|
+
let entries = []
|
|
138
|
+
try { entries = fs.readdirSync(path.join(base, relative), { withFileTypes: true }) } catch { return }
|
|
139
|
+
for (const entry of entries) {
|
|
140
|
+
const next = `${relative}/${entry.name}`
|
|
141
|
+
if (entry.isDirectory()) { below(base, next); continue }
|
|
142
|
+
if (!entry.name.endsWith('.md')) continue
|
|
143
|
+
for (const hit of fs.readFileSync(path.join(base, next), 'utf8').match(DOC_URL) || []) add(hit, next)
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
below(dir, 'references')
|
|
147
|
+
const skill = path.join(dir, 'SKILL.md')
|
|
148
|
+
if (fs.existsSync(skill)) {
|
|
149
|
+
for (const hit of fs.readFileSync(skill, 'utf8').match(DOC_URL) || []) add(hit, 'SKILL.md')
|
|
150
|
+
}
|
|
151
|
+
return out
|
|
152
|
+
}
|
|
153
|
+
|
|
110
154
|
function evaluate(root, agent) {
|
|
111
155
|
const target = catalog.resolve(root, agent)
|
|
112
156
|
const errors = []
|
|
@@ -193,4 +237,4 @@ function evaluate(root, agent) {
|
|
|
193
237
|
return { errors, warnings, proposals: proposals.length, pending, cases }
|
|
194
238
|
}
|
|
195
239
|
|
|
196
|
-
module.exports = { SOURCE_TIERS, cadence, evaluate, evaluateTeam }
|
|
240
|
+
module.exports = { SOURCE_TIERS, cadence, documentUrls, evaluate, evaluateTeam, sourceUrls }
|
|
@@ -40,15 +40,21 @@ function prepareReport(root, agent, now = new Date()) {
|
|
|
40
40
|
// Los sellos de estado de este módulo escriben atómico y esto no, y la diferencia es qué se pierde
|
|
41
41
|
// si la escritura se corta: allá el archivo ya existía y quedaría truncado, acá no había nada. Un
|
|
42
42
|
// documento nuevo a medio escribir se ve; uno viejo a medio pisar se lee como si estuviera entero.
|
|
43
|
+
// `propone` nace sin contestar y lo completa quien escribe el informe. No se deduce del texto de
|
|
44
|
+
// «Recomendación»: los veinte informes del 2026-09-07 tenían texto ahí y once no proponían ningún
|
|
45
|
+
// cambio —explicaban por qué, que es lo correcto—, así que «sección vacía» no distingue nada. Con
|
|
46
|
+
// el campo, un informe que no cambia ningún contrato se mergea solo en vez de gastar una revisión
|
|
47
|
+
// humana que no puede decidir nada.
|
|
43
48
|
fs.writeFileSync(file, `---
|
|
44
49
|
agent: ${agent}
|
|
45
50
|
date: ${isoDate(now)}
|
|
46
51
|
status: draft
|
|
52
|
+
propone: por-completar
|
|
47
53
|
---
|
|
48
54
|
|
|
49
55
|
# Investigación semanal — ${isoDate(now)}
|
|
50
56
|
|
|
51
|
-
<!--
|
|
57
|
+
<!-- Tres convenciones que el ciclo necesita y que nada más sostiene:
|
|
52
58
|
|
|
53
59
|
· Etiquetá cada hallazgo H1, H2, … en el orden en que aparecen. «Evidencia» y «Recomendación» se
|
|
54
60
|
refieren a ellos por esa clave, y la propuesta mensual la cita para decir de qué hallazgo sale
|
|
@@ -57,6 +63,12 @@ status: draft
|
|
|
57
63
|
· No renombres los títulos. «## Recomendación» se lee con un patrón exacto y es lo único que la
|
|
58
64
|
propuesta consolida de cada informe: renombrarlo no da error, deja la propuesta vacía.
|
|
59
65
|
|
|
66
|
+
· Contestá «propone» en el frontmatter, con «si» o con «no». Es «si» cuando el informe pide tocar
|
|
67
|
+
algún archivo del cargo —SKILL.md, sources.yaml, references/, un caso— y «no» cuando lo hallado
|
|
68
|
+
no cambia ningún contrato, aunque «Recomendación» explique largamente por qué. Un «no» se
|
|
69
|
+
mergea sin revisión humana: no hay nada que decidir. Ante la duda va «si», que sólo cuesta una
|
|
70
|
+
mirada.
|
|
71
|
+
|
|
60
72
|
Este comentario vive fuera de toda sección a propósito — dentro de «Recomendación» viajaría a cada
|
|
61
73
|
propuesta consolidada. -->
|
|
62
74
|
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
// Los comandos que mueven estado ya decidido: archivar lo que cerró y declarar de una vez qué historia
|
|
4
|
+
// llegó con el proyecto. Viven aparte de `planning.js` porque son la otra mitad de una costura: aquéllos
|
|
5
|
+
// leen y juzgan sin mutar nada, éstos escriben. Se separaron cuando el archivo cruzó las 500 líneas, y
|
|
6
|
+
// lo que decidió el corte fue eso y no el número.
|
|
7
|
+
|
|
8
|
+
const fs = require('node:fs')
|
|
9
|
+
const path = require('node:path')
|
|
10
|
+
const P = require('../planning/parser')
|
|
11
|
+
const PC = require('../planning/contracts')
|
|
12
|
+
const AD = require('../planning/adoption')
|
|
13
|
+
const F = require('../core/files')
|
|
14
|
+
const { fail } = require('./io')
|
|
15
|
+
|
|
16
|
+
// La fecha de hoy, la misma que usan los comandos que leen.
|
|
17
|
+
const TODAY = () => new Date().toISOString().slice(0, 10)
|
|
18
|
+
|
|
19
|
+
// El historial de acciones humanas se acumula en un solo archivo y no por épica: una fila no pertenece
|
|
20
|
+
// a ninguna, y esperar el cierre de una épica dejaría sin archivar las de un planning que todavía no
|
|
21
|
+
// cerró ninguna —que es justo cuando el archivo se vuelve ilegible—.
|
|
22
|
+
// Adoptar es declarar de una vez qué historia llegó con el proyecto. Se genera con lo que hoy no cumple
|
|
23
|
+
// y no se vuelve a correr: un baseline que se regenera perdona de nuevo lo que alguien ya se tomó el
|
|
24
|
+
// trabajo de arreglar, y uno que crece a mano deja de ser una lista de perdones para ser una amnistía.
|
|
25
|
+
// Achicarlo sí es a mano, borrando el renglón que `check` señala.
|
|
26
|
+
function adopt(dir) {
|
|
27
|
+
const root = path.resolve(dir || '.')
|
|
28
|
+
const target = path.join(root, AD.BASELINE)
|
|
29
|
+
if (fs.existsSync(target)) {
|
|
30
|
+
// Un baseline que ya trae huella no se toca: regenerarlo es exactamente lo que la huella impide.
|
|
31
|
+
// Uno sin huella lo generó una versión anterior, y sellarlo no es regenerar nada — se calcula sobre
|
|
32
|
+
// lo que ya está—, así que es la única salida de un aviso que si no no tendría ninguna.
|
|
33
|
+
const existing = fs.readFileSync(target, 'utf8')
|
|
34
|
+
if (!AD.sealWarnings(root).some((one) => /sin huella/.test(one))) {
|
|
35
|
+
fail(`${AD.BASELINE} ya existe: se genera una vez. Para retirar un renglón, ponele \`#~\` `
|
|
36
|
+
+ 'delante; `check` marca los que ya cumplen.')
|
|
37
|
+
}
|
|
38
|
+
const slugs = AD.declared(existing)
|
|
39
|
+
F.atomicWrite(target, existing.replace(/\n?$/, `\n# huella: ${slugs.length} entradas · `
|
|
40
|
+
+ `sha256:${AD.digest(slugs)}\n`))
|
|
41
|
+
return console.log(`✓ ${AD.BASELINE} sellado con ${slugs.length} entrada(s); la lista no cambió`)
|
|
42
|
+
}
|
|
43
|
+
const epics = P.readEpics(root)
|
|
44
|
+
const pending = P.readDone(root).entries.filter((entry) => PC.doneEntryErrors(entry, epics).length)
|
|
45
|
+
if (!pending.length) {
|
|
46
|
+
return console.log('= no hay nada que exentar: todas las entradas de DONE cumplen el contrato')
|
|
47
|
+
}
|
|
48
|
+
const today = TODAY()
|
|
49
|
+
const slugs = pending.map((entry) => entry.slug)
|
|
50
|
+
F.atomicWrite(target, `# Entradas anteriores a la adopción de Cauce (${today}). No se agregan nuevas:\n`
|
|
51
|
+
+ '# desde esa fecha rige el contrato completo, y `check` avisa cuando una de éstas pasa a\n'
|
|
52
|
+
+ '# cumplirlo para que le pongas `#~` delante y quede retirada.\n'
|
|
53
|
+
+ `# huella: ${slugs.length} entradas · sha256:${AD.digest(slugs)}\n`
|
|
54
|
+
+ `${slugs.join('\n')}\n`)
|
|
55
|
+
console.log(`✓ ${pending.length} entrada(s) exentas en ${AD.BASELINE}`)
|
|
56
|
+
return console.log(' revisá la lista: lo que sí cumple el contrato no tiene por qué estar ahí')
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function archiveHumanActions(root) {
|
|
60
|
+
const source = path.join(root, 'HUMAN_ACTIONS.md')
|
|
61
|
+
const rows = P.readHumanActions(root).filter((row) => row.resolved)
|
|
62
|
+
if (!rows.length) return console.log('= no hay filas resueltas')
|
|
63
|
+
const target = path.join(root, 'done', 'human-actions.md')
|
|
64
|
+
const header = '| Tarea | Estado | Origen | Acción concreta y condición de desbloqueo |\n|---|---|---|---|'
|
|
65
|
+
const previous = P.read(target).trimEnd()
|
|
66
|
+
const head = previous || `---\nstatus: archived\n---\n\n# Acciones humanas resueltas\n\n${header}`
|
|
67
|
+
fs.mkdirSync(path.dirname(target), { recursive: true })
|
|
68
|
+
F.atomicWrite(target, `${head}\n${rows.map((row) => row.raw).join('\n')}\n`)
|
|
69
|
+
const drop = new Set(rows.map((row) => row.raw))
|
|
70
|
+
const kept = P.read(source).split('\n').filter((line) => !drop.has(line))
|
|
71
|
+
F.atomicWrite(source, `${kept.join('\n').trimEnd()}\n`)
|
|
72
|
+
return console.log(`✓ ${rows.length} fila(s) archivadas`)
|
|
73
|
+
}
|
|
74
|
+
// Archivar una épica se retiró en 0.71.0. Existía para descongestionar un `DONE.md` que se hinchaba con
|
|
75
|
+
// una entrada por tarea; con un archivo por tarea no hay nada que descongestionar, y mover esos archivos
|
|
76
|
+
// a una carpeta por épica sería reintroducir el movimiento que la mudanza vino a sacar.
|
|
77
|
+
//
|
|
78
|
+
// El comando se queda para lo que sí sigue archivándose, y para decirle a quien escriba el número que ya
|
|
79
|
+
// no hace falta: sin esto contestaría «La épica debe ser NNN», que manda a corregir la forma de algo que
|
|
80
|
+
// no existe.
|
|
81
|
+
function archive(dir, rawNum) {
|
|
82
|
+
if (String(rawNum || '') === 'human-actions') return archiveHumanActions(path.resolve(dir || '.'))
|
|
83
|
+
return fail('Sólo se archiva `human-actions`. La evidencia de una tarea ya vive en su propio archivo '
|
|
84
|
+
+ 'de `done/`, así que archivar una épica dejó de tener sentido.', 2)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
module.exports = { archive, adopt }
|
package/engine/cli/args.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
// Banderas que consumen el argumento siguiente: su valor no es un posicional.
|
|
8
8
|
const VALUED_FLAGS = new Set([
|
|
9
9
|
'--name', '--mode', '--fixture', '--period', '--record', '--runner', '--integration',
|
|
10
|
-
'--task', '--promote',
|
|
10
|
+
'--task', '--promote', '--hito',
|
|
11
11
|
])
|
|
12
12
|
|
|
13
13
|
// Qué acepta cada comando, y a la vez qué comandos existen. Una bandera desconocida se rechaza en vez
|
|
@@ -19,8 +19,12 @@ const FLAGS = {
|
|
|
19
19
|
onboard: ['--json'],
|
|
20
20
|
check: ['--json'],
|
|
21
21
|
tree: ['--json', '--no-color'],
|
|
22
|
-
context: ['--json'],
|
|
22
|
+
context: ['--json', '--hito'],
|
|
23
23
|
recurring: ['--json', '--promote'],
|
|
24
|
+
claim: ['--json'],
|
|
25
|
+
runners: ['--json'],
|
|
26
|
+
worktree: ['--json'],
|
|
27
|
+
release: [],
|
|
24
28
|
evidence: ['--json', '--task'],
|
|
25
29
|
upgrade: ['--check', '--force'],
|
|
26
30
|
destroy: ['--force'],
|
package/engine/cli/catalog.js
CHANGED
|
@@ -115,7 +115,15 @@ function evaluationBench(root, agent, caso, force, kind) {
|
|
|
115
115
|
// sin pagar un `npm install` por corrida. El cargo llega a un banco donde el CLI funciona.
|
|
116
116
|
const scope = path.join(dir, 'node_modules', '@ingeniomaps')
|
|
117
117
|
fs.mkdirSync(scope, { recursive: true })
|
|
118
|
-
|
|
118
|
+
// El enlace se pisa por lo mismo que el andamiaje de arriba: a veces sobrevive al borrado del banco, y
|
|
119
|
+
// entonces crearlo corta la corrida con `EEXIST` en vez de rehacerlo. Es el único paso que no seguía esa
|
|
120
|
+
// regla, y el que falló en CI rehaciendo el mismo `11-otro` que ya tiene reintentos por esto.
|
|
121
|
+
//
|
|
122
|
+
// `rmSync` sobre un enlace lo quita a él y no a lo que apunta —que acá es la raíz del toolkit—, así que
|
|
123
|
+
// esto no puede llevarse por delante el repositorio.
|
|
124
|
+
const link = path.join(scope, 'cauce')
|
|
125
|
+
fs.rmSync(link, { force: true })
|
|
126
|
+
fs.symlinkSync(IN.PROJECT_ROOT, link, 'dir')
|
|
119
127
|
|
|
120
128
|
// El artefacto del caso, si lo tiene: la guía del proveedor que el pedido manda implementar, el CSV
|
|
121
129
|
// con instrucciones adentro. Entra antes del commit limpio a propósito — si entrara después, `status`
|