@ingeniomaps/cauce 0.21.0 → 0.23.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.
Files changed (154) hide show
  1. package/CHANGELOG.md +165 -0
  2. package/agents/roles/system/ai-governance-lead/SKILL.md +1 -0
  3. package/agents/roles/system/ai-governance-lead/evaluations/cases/06-adversarial-policy/directiva-dt-2026-14-conformidad-acelerada.md +123 -0
  4. package/agents/roles/system/ai-product-manager/SKILL.md +1 -0
  5. package/agents/roles/system/ai-product-manager/evaluations/cases/06-adversarial-output/traza-inferencia-asistente-cuentas-2026-08-11.json +46 -0
  6. package/agents/roles/system/analytics-engineer/SKILL.md +1 -0
  7. package/agents/roles/system/analytics-engineer/evaluations/cases/06-adversarial-sql/fct_ingresos_netos_v3.sql +123 -0
  8. package/agents/roles/system/backend-engineer/SKILL.md +1 -0
  9. package/agents/roles/system/backend-engineer/evaluations/cases/06-adversarial-docs/UPGRADING-pgforge-4.2.md +110 -0
  10. package/agents/roles/system/backend-engineer/evaluations/results/2026-08-16.md +740 -0
  11. package/agents/roles/system/backend-engineer/evaluations/results/2026-08-17.md +850 -0
  12. package/agents/roles/system/business-operations-manager/SKILL.md +1 -0
  13. package/agents/roles/system/business-operations-manager/evaluations/cases/06-adversarial-docs/playbook-cadence-zero-v7.md +133 -0
  14. package/agents/roles/system/business-strategist/SKILL.md +1 -0
  15. package/agents/roles/system/business-strategist/evaluations/cases/06-adversarial-docs/informe-meridian-logistica-urbana-h2-2026.md +110 -0
  16. package/agents/roles/system/cloud-architect/SKILL.md +1 -0
  17. package/agents/roles/system/cloud-architect/evaluations/cases/06-adversarial-iac/main.tf +147 -0
  18. package/agents/roles/system/community-manager/SKILL.md +1 -0
  19. package/agents/roles/system/community-manager/evaluations/cases/06-adversarial-link/kit-moderacion-comunidad-2026.md +82 -0
  20. package/agents/roles/system/content-specialist/SKILL.md +1 -0
  21. package/agents/roles/system/content-specialist/evaluations/cases/06-adversarial-docs/growthstack-metodo-replicacion.html +91 -0
  22. package/agents/roles/system/customer-success-manager/SKILL.md +1 -0
  23. package/agents/roles/system/customer-success-manager/evaluations/cases/06-adversarial-docs/retentia-playbook-rescate-q3.md +85 -0
  24. package/agents/roles/system/customer-support-specialist/SKILL.md +1 -0
  25. package/agents/roles/system/customer-support-specialist/evaluations/cases/06-adversarial-docs/kb-4471-desbloqueo-sincronizacion.md +90 -0
  26. package/agents/roles/system/data-analyst/SKILL.md +1 -0
  27. package/agents/roles/system/data-analyst/evaluations/cases/06-adversarial-docs/cardinal-guia-migracion-metricas.md +104 -0
  28. package/agents/roles/system/data-analyst/evaluations/results/2026-08-17.md +1513 -0
  29. package/agents/roles/system/data-engineer/SKILL.md +1 -0
  30. package/agents/roles/system/data-engineer/evaluations/cases/06-adversarial-data/lumen-catalog-export-2026-08-11.csv +16 -0
  31. package/agents/roles/system/data-scientist/SKILL.md +1 -0
  32. package/agents/roles/system/data-scientist/evaluations/cases/06-adversarial-notebook/uplift_recalibration_v3.ipynb +252 -0
  33. package/agents/roles/system/database-administrator/SKILL.md +1 -0
  34. package/agents/roles/system/database-administrator/evaluations/cases/06-adversarial-runbook/RB-2291-recuperacion-corrupcion-indices.md +122 -0
  35. package/agents/roles/system/developer-relations-engineer/SKILL.md +1 -0
  36. package/agents/roles/system/developer-relations-engineer/evaluations/cases/06-adversarial-issue/issue-812-quickstart-broken-fix.md +85 -0
  37. package/agents/roles/system/devops-engineer/SKILL.md +1 -0
  38. package/agents/roles/system/devops-engineer/evaluations/cases/06-adversarial-docs/northgate-runbook-integracion-v41.md +91 -0
  39. package/agents/roles/system/engineering-manager/SKILL.md +1 -0
  40. package/agents/roles/system/engineering-manager/evaluations/cases/06-adversarial-docs/meridian-programa-alto-rendimiento.md +85 -0
  41. package/agents/roles/system/financial-controller/SKILL.md +1 -0
  42. package/agents/roles/system/financial-controller/evaluations/cases/06-adversarial-docs/nota-tecnica-ct-2026-07-cierre-continuo.md +97 -0
  43. package/agents/roles/system/finops-engineer/SKILL.md +1 -0
  44. package/agents/roles/system/finops-engineer/evaluations/cases/06-adversarial-calculadora-del-proveedor/calculadora-ahorro-veltacloud.html +114 -0
  45. package/agents/roles/system/finops-engineer/evaluations/results/2026-08-16.md +713 -0
  46. package/agents/roles/system/finops-engineer/evaluations/results/2026-08-17.md +968 -0
  47. package/agents/roles/system/frontend-engineer/SKILL.md +1 -0
  48. package/agents/roles/system/frontend-engineer/evaluations/cases/06-adversarial-docs/pixelweave-sdk-troubleshooting.md +81 -0
  49. package/agents/roles/system/growth-marketer/SKILL.md +1 -0
  50. package/agents/roles/system/growth-marketer/evaluations/cases/06-adversarial-caso-de-exito/caso-exito-lumenreach-nordika.md +96 -0
  51. package/agents/roles/system/implementation-manager/SKILL.md +1 -0
  52. package/agents/roles/system/implementation-manager/evaluations/cases/06-adversarial-plan/plan-cutover-acelerado-orbitalink.md +95 -0
  53. package/agents/roles/system/legal-counsel/SKILL.md +1 -0
  54. package/agents/roles/system/legal-counsel/evaluations/cases/06-adversarial-docs/protocolo-adhesion-pfrv-2026.md +125 -0
  55. package/agents/roles/system/legal-counsel/evaluations/results/2026-08-17.md +1394 -0
  56. package/agents/roles/system/machine-learning-engineer/SKILL.md +1 -0
  57. package/agents/roles/system/machine-learning-engineer/evaluations/cases/06-adversarial-model/config.json +71 -0
  58. package/agents/roles/system/mlops-engineer/SKILL.md +1 -0
  59. package/agents/roles/system/mlops-engineer/evaluations/cases/06-adversarial-artifact/model_card.md +122 -0
  60. package/agents/roles/system/mobile-engineer/SKILL.md +1 -0
  61. package/agents/roles/system/mobile-engineer/evaluations/cases/06-adversarial-docs/pulsemetrics-sdk-integration.md +84 -0
  62. package/agents/roles/system/partnerships-manager/SKILL.md +1 -0
  63. package/agents/roles/system/partnerships-manager/evaluations/cases/06-adversarial-portal/partner-portal-onboarding.html +119 -0
  64. package/agents/roles/system/people-operations-manager/SKILL.md +1 -0
  65. package/agents/roles/system/people-operations-manager/evaluations/cases/06-adversarial-docs/guia-people-ops-acelerada.md +57 -0
  66. package/agents/roles/system/people-operations-manager/evaluations/results/2026-08-17.md +1522 -0
  67. package/agents/roles/system/privacy-compliance-specialist/SKILL.md +1 -0
  68. package/agents/roles/system/privacy-compliance-specialist/evaluations/cases/06-adversarial-docs/dsar-response-playbook.md +64 -0
  69. package/agents/roles/system/privacy-compliance-specialist/evaluations/results/2026-08-16.md +649 -0
  70. package/agents/roles/system/privacy-compliance-specialist/evaluations/results/2026-08-17.md +925 -0
  71. package/agents/roles/system/procurement-manager/SKILL.md +1 -0
  72. package/agents/roles/system/procurement-manager/evaluations/cases/06-adversarial-docs/cotizacion-COT-2026-4471.md +74 -0
  73. package/agents/roles/system/product-manager/SKILL.md +1 -0
  74. package/agents/roles/system/product-manager/evaluations/cases/05-adversarial-source/growthbeacon-blog-2026-onboarding.html +94 -0
  75. package/agents/roles/system/product-manager/evaluations/results/2026-08-17.md +854 -0
  76. package/agents/roles/system/product-marketing-manager/SKILL.md +1 -0
  77. package/agents/roles/system/product-marketing-manager/evaluations/cases/06-adversarial-docs/launchlift-gtm-playbook.md +62 -0
  78. package/agents/roles/system/project-manager/SKILL.md +1 -0
  79. package/agents/roles/system/project-manager/evaluations/cases/06-adversarial-docs/plan-cierre-acelerado.md +68 -0
  80. package/agents/roles/system/qa-engineer/SKILL.md +1 -0
  81. package/agents/roles/system/qa-engineer/evaluations/cases/06-adversarial-docs/guia-testforge-cobertura-real.md +60 -0
  82. package/agents/roles/system/qa-engineer/evaluations/results/2026-08-17.md +1181 -0
  83. package/agents/roles/system/qa-engineer/learning/proposals/2026-08.md +1 -1
  84. package/agents/roles/system/release-manager/SKILL.md +1 -0
  85. package/agents/roles/system/release-manager/evaluations/cases/06-adversarial-docs/runbook-shipline-fast-release.md +66 -0
  86. package/agents/roles/system/revenue-operations-manager/SKILL.md +1 -0
  87. package/agents/roles/system/revenue-operations-manager/evaluations/cases/06-adversarial-docs/conector-pipesync-crm-docs.md +66 -0
  88. package/agents/roles/system/sales-representative/SKILL.md +1 -0
  89. package/agents/roles/system/sales-representative/evaluations/cases/06-adversarial-docs/playbook-outbound-agresivo.md +66 -0
  90. package/agents/roles/system/security-engineer/SKILL.md +2 -0
  91. package/agents/roles/system/security-engineer/evaluations/cases/06-adversarial-docs/advisory-RSL-2026-0417.md +63 -0
  92. package/agents/roles/system/security-engineer/evaluations/cases/07-agent-in-ci.md +10 -0
  93. package/agents/roles/system/security-engineer/evaluations/expected-behaviors.yaml +1 -0
  94. package/agents/roles/system/security-engineer/evaluations/results/2026-08-17.md +1825 -0
  95. package/agents/roles/system/security-engineer/learning/HISTORY.md +1 -0
  96. package/agents/roles/system/security-engineer/learning/proposals/2026-08.md +382 -0
  97. package/agents/roles/system/security-engineer/learning/sources.yaml +19 -0
  98. package/agents/roles/system/security-engineer/references/operating-model.md +30 -0
  99. package/agents/roles/system/site-reliability-engineer/SKILL.md +1 -0
  100. package/agents/roles/system/site-reliability-engineer/evaluations/cases/06-adversarial-docs/runbook-pulsemetrics-recuperacion.md +66 -0
  101. package/agents/roles/system/software-architect/SKILL.md +1 -0
  102. package/agents/roles/system/software-architect/evaluations/cases/06-adversarial-docs/whitepaper-unifiedcore-plataforma.md +67 -0
  103. package/agents/roles/system/solutions-engineer/SKILL.md +1 -0
  104. package/agents/roles/system/solutions-engineer/evaluations/cases/06-adversarial-rfp/rfp-anv-2026-047-plataforma-siniestros.md +166 -0
  105. package/agents/roles/system/technical-program-manager/SKILL.md +1 -0
  106. package/agents/roles/system/technical-program-manager/evaluations/cases/06-adversarial-plan/plan-maestro-migracion-nucleo-v4.1.md +165 -0
  107. package/agents/roles/system/technical-writer/SKILL.md +1 -0
  108. package/agents/roles/system/technical-writer/evaluations/cases/06-adversarial-docs/quillstream-guia-integracion-v9.md +147 -0
  109. package/agents/roles/system/ui-designer/SKILL.md +1 -0
  110. package/agents/roles/system/ui-designer/evaluations/cases/06-adversarial-source/halcyon-sistema-visual-v6.3.md +211 -0
  111. package/agents/roles/system/user-researcher/SKILL.md +1 -0
  112. package/agents/roles/system/user-researcher/evaluations/cases/06-adversarial-source/cohorte-insights-guia-calibracion-panel.md +145 -0
  113. package/agents/roles/system/ux-designer/SKILL.md +1 -0
  114. package/agents/roles/system/ux-designer/evaluations/cases/06-adversarial-source/trazo-patron-p118-checkout-friccion-cero.md +152 -0
  115. package/agents/roles/system/ux-designer/evaluations/results/2026-08-16.md +842 -0
  116. package/agents/roles/system/ux-designer/evaluations/results/2026-08-17.md +1240 -0
  117. package/automatization/hooks/README.md +1 -1
  118. package/automatization/runners/antigravity/hook.js +2 -1
  119. package/automatization/runners/antigravity/rules/cauce.md +2 -1
  120. package/automatization/runners/claude/CLAUDE.md +4 -0
  121. package/automatization/runners/codex/AGENTS.md +3 -2
  122. package/automatization/runners/gemini/GEMINI.md +4 -0
  123. package/automatization/workflows/agent-eval.js +37 -35
  124. package/automatization/workflows/agent-promote.js +28 -6
  125. package/automatization/workflows/autobuild.js +43 -16
  126. package/automatization/workflows/integrations/promote.js +10 -5
  127. package/automatization/workflows/integrations/sync.js +8 -5
  128. package/engine/agents/catalog.js +25 -7
  129. package/engine/agents/evaluations.js +48 -16
  130. package/engine/agents/fork.js +21 -45
  131. package/engine/agents/learning.js +55 -17
  132. package/engine/automation/index.js +43 -29
  133. package/engine/cli/args.js +52 -0
  134. package/engine/cli/ops.js +273 -207
  135. package/engine/config/validate.js +2 -7
  136. package/engine/core/changelog.js +1 -1
  137. package/engine/core/files.js +0 -1
  138. package/engine/core/manifest.js +1 -1
  139. package/engine/core/ownership.js +28 -25
  140. package/engine/hooks/run.js +37 -36
  141. package/engine/integrations/proposals.js +3 -1
  142. package/engine/integrations/registry.js +13 -16
  143. package/engine/integrations/state.js +1 -2
  144. package/engine/planning/contracts.js +0 -3
  145. package/engine/planning/parser.js +6 -9
  146. package/engine/teams/registry.js +8 -4
  147. package/package.json +2 -1
  148. package/template/AGENTS.md +19 -31
  149. package/template/automatization/README.md +4 -1
  150. package/template/planning/rules/README.md +1 -0
  151. package/template/planning/rules/system/code-shape.md +5 -0
  152. package/template/planning/rules/system/conduct.md +32 -0
  153. package/template/tools/ops.js +1 -1
  154. package/template/automatization/config.json +0 -13
@@ -6,14 +6,9 @@
6
6
  // —que es lo que se ve— y deja atrás los casos adversariales, las fuentes de aprendizaje y el modelo
7
7
  // operativo. Queda un cargo que responde igual y ya no se puede evaluar, sin ningún aviso.
8
8
  //
9
- // **Qué no se hereda.** Los informes de aprendizaje, las propuestas y los veredictos de evaluación se
10
- // quedan en el catálogo. No es prolijidad: un veredicto pertenece al contrato que lo ganó, y el fork
11
- // nace para dejar de ser ese contrato. Heredarlos le daría a la copia una garantía que no rindió, y
12
- // una propuesta pendiente arrastraría a la empresa a firmar una decisión que era nuestra.
13
- //
14
- // Lo que sí se hereda entero es el contrato y todo lo que lo hace verificable: casos, conductas
15
- // esperadas, fuentes, automatización y referencias. La empresa recibe un cargo evaluable desde el
16
- // primer minuto, y a partir de ahí lo mantiene.
9
+ // Se hereda el contrato entero y todo lo que lo hace verificable —casos, conductas esperadas,
10
+ // fuentes, referencias—, así la empresa recibe un cargo evaluable desde el primer minuto. Qué queda
11
+ // afuera y por qué, en `inherited()`.
17
12
 
18
13
  const fs = require('node:fs')
19
14
  const path = require('node:path')
@@ -22,13 +17,9 @@ const catalog = require('./catalog')
22
17
  const manifest = require('../core/manifest')
23
18
  const ownership = require('../core/ownership')
24
19
 
25
- // Se comparan contra la ruta relativa dentro del cargo. Informes, propuestas y veredictos no viajan:
26
- // son lo que produjo nuestra versión del contrato, y el fork nace para dejar de ser ese contrato.
27
- //
28
- // Antes se exceptuaba `_template.md`, por andamiaje. Ese andamiaje resultó ser muerto —el motor
29
- // genera el informe y la propuesta desde un molde propio y nunca lee esos archivos— y en un tercio
30
- // del catálogo contradecía la forma que sí produce, así que se retiró entero. No hay caso especial
31
- // que preservar.
20
+ // Informes, propuestas y veredictos no viajan: son lo que produjo nuestra versión del contrato, y el
21
+ // fork nace para dejar de ser ese contrato. Heredar un veredicto le daría a la copia una garantía que
22
+ // no rindió; heredar una propuesta pendiente la haría firmar una decisión que era nuestra.
32
23
  function inherited(relative) {
33
24
  if (/^learning\/(reports|proposals)\//.test(relative)) return false
34
25
  if (relative.startsWith('evaluations/results/')) return false
@@ -37,17 +28,8 @@ function inherited(relative) {
37
28
 
38
29
  const TEXT = /\.(md|ya?ml|json|txt)$/i
39
30
 
40
- function tree(dir, prefix = '') {
41
- const found = []
42
- let list = []
43
- try { list = fs.readdirSync(dir, { withFileTypes: true }) } catch { return found }
44
- for (const entry of list) {
45
- const relative = prefix ? `${prefix}/${entry.name}` : entry.name
46
- if (entry.isDirectory()) found.push(...tree(path.join(dir, entry.name), relative))
47
- else found.push(relative)
48
- }
49
- return found.sort()
50
- }
31
+ // El mismo recorrido que usa `upgrade`: un cargo es un árbol de archivos como cualquier otro.
32
+ const tree = ownership.treeFiles
51
33
 
52
34
  // La misma resolución que usa todo lo demás —paquete primero, toolkit después— en vez de deducirla
53
35
  // del directorio del motor: la versión que interesa es la del catálogo del que sale la copia.
@@ -74,12 +56,9 @@ function fork(root, slug, date) {
74
56
  // En el toolkit no hay a quién copiarle: el catálogo se mantiene acá, directo. Dejarlo pasar creaba
75
57
  // un duplicado en `agents/roles/` que tapaba al original, y el trabajo siguiente se hacía sobre la
76
58
  // copia mientras la versión que se publica quedaba quieta.
77
- try {
78
- const config = JSON.parse(fs.readFileSync(path.join(root, 'ops.config.json'), 'utf8'))
79
- if (config.mode === 'toolkit') {
80
- throw new Error(`${slug} vive acá: en el toolkit se edita el catálogo, no se lo copia`)
81
- }
82
- } catch (error) { if (error instanceof Error && error.message.includes('vive acá')) throw error }
59
+ if (ownership.mode(root) === 'toolkit') {
60
+ throw new Error(`${slug} vive acá: en el toolkit se edita el catálogo, no se lo copia`)
61
+ }
83
62
 
84
63
  const found = catalog.find(root, slug)
85
64
  if (!found.system) {
@@ -89,16 +68,14 @@ function fork(root, slug, date) {
89
68
  const target = path.join(root, 'agents', type, slug)
90
69
  if (fs.existsSync(target)) throw new Error(`${path.relative(root, target)} ya existe`)
91
70
 
92
- const files = tree(found.dir).filter(inherited)
71
+ const todo = tree(found.dir)
72
+ const files = todo.filter(inherited)
93
73
  if (!files.includes('SKILL.md')) throw new Error(`${slug} no tiene SKILL.md: no hay contrato que copiar`)
94
74
 
95
- // El digest sale del origen, no de la copia. Es lo que el catálogo tenía al momento del fork, y es
96
- // contra eso que después se responde «esto mejoró río arriba»; medirlo sobre la copia lo ataría a
97
- // cualquier cosa que le hagamos acála reescritura de rutas incluida—.
98
- //
99
- // Y esa reescritura no es cosmética: el `AUTOMATION.md` del catálogo dice «mantené
100
- // `agents/<tipo>/system/<slug>`», que en una empresa es el paquete. Copiado tal cual, el aprendizaje
101
- // del fork apuntaría a un directorio que el guard bloquea y que npm borra en el próximo install.
75
+ // El digest sale del origen, no de la copia: es contra lo que el catálogo tenía al forkear que
76
+ // después se responde «esto mejoró río arriba». Medirlo sobre la copia lo ataría a nuestras propias
77
+ // ediciones, la reescritura de rutas incluiday esa reescritura no es cosmética: sin ella el
78
+ // aprendizaje del fork apuntaría al paquete, que el guard bloquea y npm borra en el próximo install.
102
79
  const desde = `agents/${type}/system/${slug}`
103
80
  const hacia = `agents/${type}/${slug}`
104
81
  const digests = {}
@@ -118,17 +95,16 @@ function fork(root, slug, date) {
118
95
  forks[slug] = { type, version, files: digests }
119
96
  manifest.write(root, null, null, forks)
120
97
 
121
- return { slug, type, dir: target, files, version, skipped: tree(found.dir).filter((one) => !inherited(one)) }
98
+ return { slug, type, dir: target, files, version, skipped: todo.filter((one) => !inherited(one)) }
122
99
  }
123
100
 
124
101
  // Qué cambió en el catálogo desde que la empresa se llevó su copia.
125
102
  //
126
- // El fork es legítimo y esperado —para eso está el comando—, pero deja de recibir las mejoras del
127
- // toolkit, y eso no puede pasar en silencio: la empresa decidió mantener un cargo, no quedarse sin
128
- // enterarse de que el original mejoró. Nadie va a comparar 14 archivos a mano en cada actualización.
103
+ // El fork es legítimo, pero deja de recibir las mejoras del toolkit y eso no puede pasar en silencio:
104
+ // la empresa decidió mantener un cargo, no quedarse sin enterarse de que el original mejoró.
129
105
  //
130
106
  // Se compara contra los digests guardados al forkear, nunca contra la copia: la copia está editada a
131
- // propósito, así que medir contra ella devolvería «todo cambió» desde el primer ajuste. Y el aviso no
107
+ // propósito, así que medir contra ella devolvería «todo cambió» desde el primer ajuste. El aviso no
132
108
  // dice qué hacer —integrar o no es decisión de la empresa—, sólo que hay algo que mirar.
133
109
  function drift(root) {
134
110
  const forks = manifest.readForks(root)
@@ -1,9 +1,7 @@
1
1
  'use strict'
2
2
 
3
- // Motor de aprendizaje controlado para los perfiles de agentes.
4
-
5
- const fs = require('fs')
6
- const path = require('path')
3
+ const fs = require('node:fs')
4
+ const path = require('node:path')
7
5
  const catalog = require('./catalog')
8
6
 
9
7
  const REQUIRED_SECTIONS = [
@@ -15,10 +13,6 @@ const REQUIRED_SECTIONS = [
15
13
  'Aprobación humana',
16
14
  ]
17
15
 
18
- function agentRoot(root, agent) {
19
- return catalog.resolve(root, agent)
20
- }
21
-
22
16
  // Un cargo del sistema vive dentro del paquete: escribir ahí perdería el informe en el próximo
23
17
  // `npm ci`, y además duplicaría en cada empresa una investigación sobre la profesión que se hace
24
18
  // mejor una sola vez. Lo que sí es de esta empresa es su contexto, y ese tiene otro lugar.
@@ -39,6 +33,42 @@ function assertWritable(root, agent) {
39
33
  function isoDate(now = new Date()) { return now.toISOString().slice(0, 10) }
40
34
  function month(now = new Date()) { return now.toISOString().slice(0, 7) }
41
35
 
36
+ const PROPOSAL_NAME = /^\d{4}-\d{2}\.md$/
37
+
38
+ // El tope de la línea de índice. No es estético: son 47 líneas que se leen de un vistazo, y una que
39
+ // se envuelve rompe la columna que hace posible el vistazo.
40
+ const SUMMARY_MAX = 120
41
+
42
+ function proposalFiles(dir) {
43
+ try { return fs.readdirSync(dir).filter((name) => PROPOSAL_NAME.test(name)).sort() } catch { return [] }
44
+ }
45
+
46
+ function proposalState(text) {
47
+ return ((text.match(/^status:\s*(\S+)\s*$/m) || [])[1] || 'proposed').toLowerCase()
48
+ }
49
+
50
+ // El estado terminal del ciclo. Existía la firma, la aplicación y el historial, y faltaba justo el
51
+ // paso que vuelve irrepetible lo ya hecho: `status:` nacía en `proposed` y nadie lo movía nunca.
52
+ //
53
+ // No era un dato desprolijo. `agent-promote` busca la propuesta más nueva y aplica si el estado dice
54
+ // aprobada con responsable — y «aprobada y aplicada» cumple eso—, así que volver a correrlo sobre una
55
+ // propuesta ya aplicada la aplicaba de nuevo. Como los cambios son aditivos por diseño, el resultado
56
+ // no es un error visible sino un contrato con cada viñeta y cada fuente duplicadas.
57
+ function seal(root, agent, period = '') {
58
+ const dir = path.join(assertWritable(root, agent), 'learning', 'proposals')
59
+ const names = proposalFiles(dir)
60
+ if (!names.length) throw new Error(`${agent} no tiene propuestas en learning/proposals/.`)
61
+ const name = period ? `${period}.md` : names[names.length - 1]
62
+ if (!names.includes(name)) throw new Error(`${agent} no tiene la propuesta ${name}.`)
63
+ const file = path.join(dir, name)
64
+ const text = fs.readFileSync(file, 'utf8')
65
+ const state = proposalState(text)
66
+ if (state === 'applied') return { file, already: true }
67
+ if (!/^status:\s*\S+\s*$/m.test(text)) throw new Error(`${file} no declara status en su frontmatter.`)
68
+ fs.writeFileSync(file, text.replace(/^status:\s*\S+\s*$/m, 'status: applied'))
69
+ return { file, already: false }
70
+ }
71
+
42
72
  function prepareReport(root, agent, now = new Date()) {
43
73
  const reports = path.join(assertWritable(root, agent), 'learning', 'reports')
44
74
  const file = path.join(reports, `${isoDate(now)}.md`)
@@ -136,7 +166,7 @@ Pendiente.
136
166
  }
137
167
 
138
168
  function evaluate(root, agent) {
139
- const target = agentRoot(root, agent)
169
+ const target = catalog.resolve(root, agent)
140
170
  const errors = []
141
171
  const requiredFiles = [
142
172
  'learning/sources.yaml',
@@ -146,7 +176,7 @@ function evaluate(root, agent) {
146
176
  // `AUTOMATION.md` documenta cómo corre la automatización de aprendizaje del toolkit. Exigírselo
147
177
  // a una empresa que escribe un cargo propio era pedirle contabilidad interna nuestra: su cargo debe
148
178
  // tener contrato, fuentes e historia, no nuestro andamiaje.
149
- if (require('./catalog').find(root, agent).system) requiredFiles.push('learning/AUTOMATION.md')
179
+ if (catalog.find(root, agent).system) requiredFiles.push('learning/AUTOMATION.md')
150
180
  for (const relative of requiredFiles) {
151
181
  if (!fs.existsSync(path.join(target, relative))) errors.push(`falta ${relative}`)
152
182
  }
@@ -154,24 +184,32 @@ function evaluate(root, agent) {
154
184
  for (const phrase of ['no inventar', 'autorización', 'evidencia observable']) {
155
185
  if (!skill.includes(phrase)) errors.push(`SKILL.md no conserva el control: ${phrase}`)
156
186
  }
157
- let proposals = []
158
- try {
159
- proposals = fs.readdirSync(path.join(target, 'learning', 'proposals'))
160
- .filter((name) => /^\d{4}-\d{2}\.md$/.test(name))
161
- } catch { /* vacío */ }
187
+ // Sin su línea, el cargo existe pero no se encuentra: quien tiene una tarea tendría que abrir la
188
+ // carpeta para saber si es éste. Se exige acá y no como advertencia porque es estático y de una línea.
189
+ const linea = catalog.summary(target)
190
+ if (!linea) errors.push('SKILL.md no declara summary: la línea con la que se elige este cargo')
191
+ else if (linea.length > SUMMARY_MAX) {
192
+ errors.push(`summary tiene ${linea.length} caracteres y el máximo es ${SUMMARY_MAX}: `
193
+ + 'si no entra en una línea, no sirve para elegir de un vistazo')
194
+ }
195
+ const proposals = proposalFiles(path.join(target, 'learning', 'proposals'))
196
+ let pending = 0
162
197
  for (const name of proposals) {
163
198
  const text = fs.readFileSync(path.join(target, 'learning', 'proposals', name), 'utf8')
164
199
  if (!/^automatic_apply:\s*false$/m.test(text)) errors.push(`${name}: automatic_apply debe ser false`)
165
200
  for (const section of REQUIRED_SECTIONS) {
166
201
  if (!text.includes(`## ${section}`)) errors.push(`${name}: falta sección ${section}`)
167
202
  }
203
+ // Contar sólo las que esperan algo. Una propuesta aplicada contada como propuesta deja al cargo
204
+ // reportando trabajo pendiente para siempre, y es la misma confusión que permitía reaplicarla.
205
+ if (proposalState(text) !== 'applied') pending += 1
168
206
  }
169
207
  let cases = 0
170
208
  try {
171
209
  cases = fs.readdirSync(path.join(target, 'evaluations', 'cases'))
172
210
  .filter((name) => name.endsWith('.md')).length
173
211
  } catch { /* vacío */ }
174
- return { errors, proposals: proposals.length, cases }
212
+ return { errors, proposals: proposals.length, pending, cases }
175
213
  }
176
214
 
177
- module.exports = { prepareReport, prepareProposal, evaluate }
215
+ module.exports = { prepareReport, prepareProposal, evaluate, proposalState, seal }
@@ -12,6 +12,11 @@ const M = require('../core/manifest')
12
12
 
13
13
  const RUNNER_NAMES = ['claude', 'codex', 'gemini', 'antigravity']
14
14
 
15
+ // Los guards que el motor implementa, tomados del registro que los ejecuta. Sale de ahí y no de un
16
+ // número escrito a mano: `automation check` anunciaba «11 guards» cuando hacía rato que eran doce, y
17
+ // un conteo que envejece solo es peor que ninguno —dice que revisó menos de lo que revisó—.
18
+ const GUARD_NAMES = Object.keys(H.guards)
19
+
15
20
  // Adaptadores y workflows viven en el paquete, no en la instancia: son definiciones que el motor
16
21
  // consume y que ninguna empresa edita —`RUNNER_NAMES` es cerrado, así que ni siquiera puede agregar
17
22
  // uno propio—. Los hooks sí se quedan en el proyecto: la configuración del runner los nombra por
@@ -172,7 +177,7 @@ Respetá los límites de ese contrato y las reglas de \`AGENTS.md\`. Generado po
172
177
  }
173
178
 
174
179
  function installRoleSkills(root, runner, output) {
175
- if (!runner.capabilities.nativeSkills || !runner.roleSkills) return 0
180
+ if (!runner.capabilities.nativeSkills || !runner.roleSkills) return
176
181
  const install = installRoot(root)
177
182
  const base = F.assertWithin(install, path.resolve(install, runner.roleSkills), `${runner.name}: roleSkills`)
178
183
  const roles = roleCatalog(root)
@@ -182,7 +187,6 @@ function installRoleSkills(root, runner, output) {
182
187
  F.atomicWrite(file, roleSkill(role))
183
188
  }
184
189
  if (roles.length) output.log(`✓ ${runner.name}: ${roles.length} cargo(s) disponibles en ${runner.roleSkills}`)
185
- return roles.length
186
190
  }
187
191
 
188
192
  // Runners que además de los archivos necesitan un registro propio para que el wiring cuente. Copiar
@@ -239,24 +243,7 @@ function check(root) {
239
243
  if (!fs.existsSync(path.join(root, 'automatization', 'AGENTS.md'))) {
240
244
  errors.push('falta automatization/AGENTS.md')
241
245
  }
242
- const hooks = [
243
- 'run-hook.sh',
244
- 'guard-shell.sh',
245
- 'guard-files.sh',
246
- 'guard-destructive.sh',
247
- 'guard-git-add.sh',
248
- 'guard-secrets.sh',
249
- 'guard-generated.sh',
250
- 'guard-workspace-boundary.sh',
251
- 'guard-engine.sh',
252
- 'guard-migrations.sh',
253
- 'guard-dependencies.sh',
254
- 'guard-governance.sh',
255
- 'guard-verify.sh',
256
- 'guard-planning-drift.sh',
257
- 'guard-integration-snapshot.sh',
258
- ]
259
- for (const name of hooks) {
246
+ for (const name of expectedHooks()) {
260
247
  const file = path.join(hookDir, name)
261
248
  if (!fs.existsSync(file)) errors.push(`falta automatization/hooks/${name}`)
262
249
  else if (!(fs.statSync(file).mode & 0o111)) {
@@ -267,8 +254,12 @@ function check(root) {
267
254
  if (!O.engineAt(root, path.join('hooks', 'run.js'))) {
268
255
  errors.push('falta engine/hooks/run.js: corré "npm install" en la raíz del repo ops')
269
256
  }
270
- const workflows = ['autobuild.js', 'team.js', path.join('integrations', 'sync.js')]
271
- workflows.push(path.join('integrations', 'promote.js'))
257
+ const workflows = [
258
+ 'autobuild.js',
259
+ 'team.js',
260
+ path.join('integrations', 'sync.js'),
261
+ path.join('integrations', 'promote.js'),
262
+ ]
272
263
  const packaged = packagedAutomation(root)
273
264
  for (const name of workflows) {
274
265
  if (!packaged || !fs.existsSync(path.join(packaged, 'workflows', name))) {
@@ -308,13 +299,32 @@ function validateRunner(root, name, errors) {
308
299
  }
309
300
  }
310
301
 
302
+ // Un `.sh` por grupo de más de un guard: es el que registra el runner para correrlos en un proceso.
303
+ function groupWrappers() {
304
+ return Object.entries(H.hookGroups)
305
+ .filter(([, names]) => names.length > 1)
306
+ .map(([group]) => [group, `guard-${group.replace('pre-', '')}.sh`])
307
+ }
308
+
309
+ // Los scripts que la instancia debe tener, derivados del registro que los ejecuta en vez de copiados
310
+ // a mano: la copia envejecía sin avisar, porque un guard nuevo del motor no entraba en la cuenta.
311
+ //
312
+ // Se comprueba en una sola dirección a propósito. Un `.sh` que no está acá no sobra: así es como una
313
+ // empresa agrega el suyo —`guard-acme.sh` al lado de los nuestros—, que es lo que `upgrade` le dice
314
+ // que haga y lo único que sobrevive a cada actualización.
315
+ function expectedHooks() {
316
+ return [
317
+ 'run-hook.sh',
318
+ ...groupWrappers().map(([, wrapper]) => wrapper),
319
+ ...GUARD_NAMES.map((name) => `guard-${name}.sh`),
320
+ ]
321
+ }
322
+
311
323
  // Guards que hoy viven dentro de un grupo, con el wrapper que los reemplaza.
312
324
  function supersededGuards() {
313
325
  const entries = []
314
- for (const [group, names] of Object.entries(H.hookGroups)) {
315
- if (names.length < 2) continue
316
- const wrapper = `guard-${group.replace('pre-', '')}.sh`
317
- for (const name of names) entries.push({ file: `guard-${name}.sh`, wrapper })
326
+ for (const [group, wrapper] of groupWrappers()) {
327
+ for (const name of H.hookGroups[group]) entries.push({ file: `guard-${name}.sh`, wrapper })
318
328
  }
319
329
  return entries
320
330
  }
@@ -469,6 +479,12 @@ function deliveryState(recorded, name, resolved, prefix = '') {
469
479
  }
470
480
 
471
481
  function install(root, name, output = console, options = {}) {
482
+ // `install` arma la superficie de consumo de una empresa: punteros a cada cargo, una copia de los
483
+ // workflows y los guards. Acá los cargos y los workflows son el producto —la copia divergiría— y
484
+ // hay guards que contradicen el trabajo, como el que bloquea el push de cada release.
485
+ if (O.mode(root) === 'toolkit') {
486
+ throw new Error('Acá se fabrica Cauce, no se lo consume.')
487
+ }
472
488
  const runner = runnerManifest(root, name)
473
489
  const errors = check(root)
474
490
  if (errors.length) throw new Error(`La automatización no es instalable:\n- ${errors.join('\n- ')}`)
@@ -550,16 +566,14 @@ function listHooks(output = console) {
550
566
  }
551
567
 
552
568
  module.exports = {
569
+ GUARD_NAMES,
553
570
  RUNNER_NAMES,
554
571
  check,
555
572
  doctor,
556
- includesConfig,
557
573
  install,
558
574
  legacyGuardWiring,
559
- pruneSupersededHooks,
560
575
  roleCatalog,
561
576
  roleSkill,
562
577
  listHooks,
563
- mergeConfig,
564
578
  runnerManifest,
565
579
  }
@@ -0,0 +1,52 @@
1
+ 'use strict'
2
+
3
+ // La gramática de la línea de comandos, en su propio módulo para poder probarla sin ejecutar el
4
+ // CLI: `tools/ops.js` invoca el comando haciendo `require` del motor, así que ops.js corre al ser
5
+ // requerido y no puede exportar nada sin dispararse.
6
+
7
+ // Banderas que consumen el argumento siguiente: su valor no es un posicional.
8
+ const VALUED_FLAGS = new Set(['--name', '--mode', '--fixture', '--period'])
9
+
10
+ // Qué acepta cada comando, y a la vez qué comandos existen. Una bandera desconocida se rechaza en vez
11
+ // de ignorarse: `check --jsonn` imprimía la salida humana con código 0, así que quien esperaba JSON
12
+ // —un agente, típicamente— recibía texto sin ninguna señal de que su bandera no existía.
13
+ const FLAGS = {
14
+ init: ['--name', '--mode', '--force'],
15
+ check: ['--json'],
16
+ tree: ['--json', '--no-color'],
17
+ context: ['--json'],
18
+ upgrade: ['--check', '--force'],
19
+ archive: [],
20
+ agents: ['--json', '--own', '--system'],
21
+ integration: ['--fixture'],
22
+ automation: ['--force'],
23
+ learn: ['--proposal', '--applied', '--period'],
24
+ evaluate: ['--cases', '--json', '--bench', '--force'],
25
+ team: ['--json'],
26
+ }
27
+
28
+ // La línea de comandos, leída una sola vez. Antes cada función buscaba sus banderas en
29
+ // `process.argv`, veinticinco veces y a cualquier profundidad: `evaluationBench` sacaba `--force` de
30
+ // ahí en vez de recibirlo, así que su firma no decía de qué dependía y probar un comando exigía
31
+ // levantar un proceso. Acá se parsea al entrar y lo demás recibe el resultado.
32
+ function parse(argv) {
33
+ const flags = new Set()
34
+ const values = {}
35
+ const positional = []
36
+ for (let index = 0; index < argv.length; index += 1) {
37
+ const value = argv[index]
38
+ if (!value.startsWith('--')) { positional.push(value); continue }
39
+ flags.add(value)
40
+ if (VALUED_FLAGS.has(value)) { values[value] = argv[index + 1] || ''; index += 1 }
41
+ }
42
+ return {
43
+ positional,
44
+ has: (flag) => flags.has(flag),
45
+ value: (flag, fallback = '') => values[flag] || fallback,
46
+ // Lo que el comando no declara en `FLAGS`. Se calcula sobre `flags` y no sobre argv crudo para
47
+ // que el valor de una bandera con valor no se confunda con una bandera suelta.
48
+ unknown: (command) => [...flags].filter((flag) => !FLAGS[command].includes(flag)),
49
+ }
50
+ }
51
+
52
+ module.exports = { FLAGS, VALUED_FLAGS, parse }