@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.
Files changed (168) hide show
  1. package/CHANGELOG.md +266 -0
  2. package/README.md +6 -6
  3. package/agents/README.md +38 -0
  4. package/agents/roles/system/accounting-specialist/learning/HISTORY.md +2 -0
  5. package/agents/roles/system/ai-governance-lead/learning/HISTORY.md +6 -1
  6. package/agents/roles/system/ai-governance-lead/learning/sources.yaml +4 -4
  7. package/agents/roles/system/ai-governance-lead/references/operating-model.md +2 -2
  8. package/agents/roles/system/ai-product-manager/learning/HISTORY.md +4 -1
  9. package/agents/roles/system/ai-product-manager/learning/sources.yaml +2 -2
  10. package/agents/roles/system/ai-product-manager/references/operating-model.md +2 -2
  11. package/agents/roles/system/analytics-engineer/learning/HISTORY.md +4 -1
  12. package/agents/roles/system/analytics-engineer/learning/sources.yaml +2 -2
  13. package/agents/roles/system/analytics-engineer/references/operating-model.md +2 -2
  14. package/agents/roles/system/backend-engineer/learning/HISTORY.md +2 -0
  15. package/agents/roles/system/business-strategist/learning/HISTORY.md +2 -0
  16. package/agents/roles/system/business-strategist/learning/sources.yaml +1 -1
  17. package/agents/roles/system/business-strategist/references/operating-model.md +2 -2
  18. package/agents/roles/system/cloud-architect/learning/HISTORY.md +1 -1
  19. package/agents/roles/system/cloud-architect/learning/sources.yaml +2 -2
  20. package/agents/roles/system/cloud-architect/references/operating-model.md +2 -2
  21. package/agents/roles/system/community-manager/learning/HISTORY.md +4 -1
  22. package/agents/roles/system/community-manager/learning/sources.yaml +2 -2
  23. package/agents/roles/system/community-manager/references/operating-model.md +1 -1
  24. package/agents/roles/system/content-specialist/learning/HISTORY.md +2 -0
  25. package/agents/roles/system/customer-success-manager/learning/HISTORY.md +2 -0
  26. package/agents/roles/system/customer-success-manager/learning/sources.yaml +4 -4
  27. package/agents/roles/system/customer-success-manager/references/operating-model.md +3 -3
  28. package/agents/roles/system/customer-support-specialist/learning/HISTORY.md +2 -0
  29. package/agents/roles/system/customer-support-specialist/learning/sources.yaml +2 -2
  30. package/agents/roles/system/customer-support-specialist/references/operating-model.md +2 -2
  31. package/agents/roles/system/data-analyst/learning/HISTORY.md +2 -0
  32. package/agents/roles/system/data-engineer/learning/HISTORY.md +4 -1
  33. package/agents/roles/system/data-engineer/learning/sources.yaml +3 -3
  34. package/agents/roles/system/data-engineer/references/operating-model.md +2 -2
  35. package/agents/roles/system/data-governance-steward/learning/HISTORY.md +2 -0
  36. package/agents/roles/system/data-scientist/learning/HISTORY.md +4 -1
  37. package/agents/roles/system/data-scientist/learning/sources.yaml +3 -3
  38. package/agents/roles/system/data-scientist/references/operating-model.md +2 -2
  39. package/agents/roles/system/database-administrator/learning/HISTORY.md +1 -1
  40. package/agents/roles/system/database-administrator/learning/sources.yaml +2 -2
  41. package/agents/roles/system/database-administrator/references/operating-model.md +2 -2
  42. package/agents/roles/system/developer-relations-engineer/learning/HISTORY.md +4 -1
  43. package/agents/roles/system/devops-engineer/learning/HISTORY.md +2 -0
  44. package/agents/roles/system/engineering-manager/learning/HISTORY.md +2 -0
  45. package/agents/roles/system/engineering-manager/learning/sources.yaml +1 -1
  46. package/agents/roles/system/engineering-manager/references/operating-model.md +2 -2
  47. package/agents/roles/system/financial-controller/learning/HISTORY.md +2 -0
  48. package/agents/roles/system/financial-controller/learning/sources.yaml +2 -2
  49. package/agents/roles/system/financial-controller/references/operating-model.md +1 -1
  50. package/agents/roles/system/finops-engineer/learning/HISTORY.md +2 -0
  51. package/agents/roles/system/fraud-risk-analyst/learning/HISTORY.md +2 -0
  52. package/agents/roles/system/frontend-engineer/learning/HISTORY.md +2 -0
  53. package/agents/roles/system/growth-marketer/learning/HISTORY.md +2 -0
  54. package/agents/roles/system/growth-marketer/learning/sources.yaml +1 -1
  55. package/agents/roles/system/implementation-manager/learning/HISTORY.md +1 -1
  56. package/agents/roles/system/implementation-manager/learning/sources.yaml +4 -4
  57. package/agents/roles/system/implementation-manager/references/operating-model.md +3 -3
  58. package/agents/roles/system/integrations-engineer/learning/HISTORY.md +2 -0
  59. package/agents/roles/system/kyc-aml-specialist/learning/HISTORY.md +3 -2
  60. package/agents/roles/system/kyc-aml-specialist/learning/sources.yaml +1 -1
  61. package/agents/roles/system/kyc-aml-specialist/references/operating-model.md +1 -1
  62. package/agents/roles/system/legal-counsel/learning/HISTORY.md +6 -1
  63. package/agents/roles/system/legal-counsel/learning/sources.yaml +1 -1
  64. package/agents/roles/system/legal-counsel/references/operating-model.md +1 -1
  65. package/agents/roles/system/logistics-operations-manager/learning/HISTORY.md +2 -0
  66. package/agents/roles/system/machine-learning-engineer/learning/HISTORY.md +4 -1
  67. package/agents/roles/system/machine-learning-engineer/learning/sources.yaml +6 -6
  68. package/agents/roles/system/machine-learning-engineer/references/operating-model.md +3 -3
  69. package/agents/roles/system/mlops-engineer/learning/HISTORY.md +4 -1
  70. package/agents/roles/system/mlops-engineer/learning/sources.yaml +2 -2
  71. package/agents/roles/system/mlops-engineer/references/operating-model.md +2 -2
  72. package/agents/roles/system/mobile-engineer/learning/HISTORY.md +2 -0
  73. package/agents/roles/system/mobile-engineer/learning/sources.yaml +1 -1
  74. package/agents/roles/system/partnerships-manager/learning/HISTORY.md +4 -1
  75. package/agents/roles/system/partnerships-manager/learning/sources.yaml +2 -2
  76. package/agents/roles/system/partnerships-manager/references/operating-model.md +2 -2
  77. package/agents/roles/system/people-operations-manager/learning/HISTORY.md +4 -1
  78. package/agents/roles/system/people-operations-manager/learning/sources.yaml +2 -2
  79. package/agents/roles/system/people-operations-manager/references/operating-model.md +2 -2
  80. package/agents/roles/system/privacy-compliance-specialist/learning/HISTORY.md +2 -0
  81. package/agents/roles/system/privacy-compliance-specialist/learning/sources.yaml +1 -1
  82. package/agents/roles/system/privacy-compliance-specialist/references/operating-model.md +1 -1
  83. package/agents/roles/system/procurement-manager/learning/HISTORY.md +1 -1
  84. package/agents/roles/system/procurement-manager/learning/sources.yaml +2 -2
  85. package/agents/roles/system/procurement-manager/references/operating-model.md +2 -2
  86. package/agents/roles/system/product-manager/learning/HISTORY.md +1 -1
  87. package/agents/roles/system/product-marketing-manager/learning/HISTORY.md +2 -0
  88. package/agents/roles/system/product-marketing-manager/learning/sources.yaml +1 -1
  89. package/agents/roles/system/project-manager/learning/HISTORY.md +4 -1
  90. package/agents/roles/system/project-manager/learning/sources.yaml +1 -1
  91. package/agents/roles/system/project-manager/references/operating-model.md +2 -2
  92. package/agents/roles/system/qa-engineer/learning/HISTORY.md +2 -0
  93. package/agents/roles/system/qa-engineer/learning/sources.yaml +9 -11
  94. package/agents/roles/system/qa-engineer/references/operating-model.md +2 -2
  95. package/agents/roles/system/release-manager/learning/HISTORY.md +1 -1
  96. package/agents/roles/system/sales-representative/learning/HISTORY.md +2 -0
  97. package/agents/roles/system/sales-representative/learning/sources.yaml +2 -2
  98. package/agents/roles/system/sales-representative/references/operating-model.md +1 -1
  99. package/agents/roles/system/security-engineer/learning/HISTORY.md +2 -0
  100. package/agents/roles/system/security-engineer/learning/sources.yaml +1 -1
  101. package/agents/roles/system/security-engineer/references/operating-model.md +1 -1
  102. package/agents/roles/system/site-reliability-engineer/learning/HISTORY.md +2 -0
  103. package/agents/roles/system/software-architect/learning/HISTORY.md +2 -0
  104. package/agents/roles/system/software-architect/learning/sources.yaml +2 -2
  105. package/agents/roles/system/software-architect/references/operating-model.md +1 -1
  106. package/agents/roles/system/solutions-engineer/learning/HISTORY.md +4 -1
  107. package/agents/roles/system/solutions-engineer/learning/sources.yaml +4 -4
  108. package/agents/roles/system/solutions-engineer/references/operating-model.md +2 -2
  109. package/agents/roles/system/tech-lead/learning/HISTORY.md +2 -0
  110. package/agents/roles/system/technical-program-manager/learning/HISTORY.md +4 -1
  111. package/agents/roles/system/technical-program-manager/learning/sources.yaml +2 -2
  112. package/agents/roles/system/technical-program-manager/references/operating-model.md +3 -3
  113. package/agents/roles/system/technical-writer/learning/HISTORY.md +4 -1
  114. package/agents/roles/system/treasury-analyst/learning/HISTORY.md +2 -0
  115. package/agents/roles/system/ui-designer/learning/HISTORY.md +2 -0
  116. package/agents/roles/system/ui-designer/learning/sources.yaml +2 -2
  117. package/agents/roles/system/user-researcher/learning/HISTORY.md +1 -1
  118. package/agents/roles/system/user-researcher/learning/sources.yaml +1 -1
  119. package/agents/roles/system/ux-designer/learning/HISTORY.md +2 -0
  120. package/agents/roles/system/ux-designer/learning/sources.yaml +1 -1
  121. package/agents/roles/system/ux-designer/references/operating-model.md +1 -1
  122. package/automatization/AGENTS.md +1 -1
  123. package/automatization/runners/antigravity/rules/cauce.md +1 -1
  124. package/automatization/runners/claude/CLAUDE.md +1 -1
  125. package/automatization/runners/codex/AGENTS.md +1 -1
  126. package/automatization/runners/gemini/GEMINI.md +1 -1
  127. package/automatization/shared/skills/autobuild/SKILL.md +1 -1
  128. package/automatization/workflows/autobuild.js +81 -27
  129. package/engine/agents/learning-seal.js +36 -5
  130. package/engine/agents/learning-sources.js +46 -2
  131. package/engine/agents/learning.js +13 -1
  132. package/engine/cli/archive.js +87 -0
  133. package/engine/cli/args.js +6 -2
  134. package/engine/cli/catalog.js +9 -1
  135. package/engine/cli/claims.js +125 -0
  136. package/engine/cli/ops.js +15 -4
  137. package/engine/cli/planning.js +139 -107
  138. package/engine/cli/worktree.js +89 -0
  139. package/engine/core/ownership.js +13 -5
  140. package/engine/core/repos.js +67 -0
  141. package/engine/hooks/files.js +3 -2
  142. package/engine/planning/adoption.js +1 -1
  143. package/engine/planning/claims.js +153 -0
  144. package/engine/planning/contracts.js +43 -202
  145. package/engine/planning/parser.js +51 -12
  146. package/engine/planning/state.js +39 -6
  147. package/engine/planning/structure.js +220 -0
  148. package/package.json +1 -1
  149. package/template/.gitattributes +19 -0
  150. package/template/AGENTS.md +47 -5
  151. package/template/automatization/AGENTS.md +1 -1
  152. package/template/gitignore +9 -0
  153. package/template/planning/BACKLOG.md +5 -0
  154. package/template/planning/FLOW.md +1 -1
  155. package/template/planning/PROTOCOL.md +34 -8
  156. package/template/planning/README.md +3 -3
  157. package/template/planning/RECURRING.md +1 -1
  158. package/template/planning/adr/system/OPS-001-planificacion-como-fuente-de-verdad.md +3 -2
  159. package/template/planning/business-rules/system/BR-OPS-001-una-sola-tarea-activa.md +7 -4
  160. package/template/planning/business-rules/system/BR-OPS-005-una-tarea-un-runner.md +44 -0
  161. package/template/planning/claims/README.md +70 -0
  162. package/template/planning/delivery/README.md +1 -0
  163. package/template/planning/delivery/multi-repo.md +11 -0
  164. package/template/planning/delivery/teamwork.md +162 -0
  165. package/template/planning/done/README.md +41 -0
  166. package/template/planning/wip/README.md +49 -0
  167. package/template/planning/DONE.md +0 -13
  168. package/template/planning/WIP.md +0 -22
@@ -0,0 +1,220 @@
1
+ 'use strict'
2
+
3
+ // Lo que se juzga leyendo el disco: cómo están armados los directorios de planning —roadmap, BACKLOG,
4
+ // reglas y ADR— antes de que alguien componga un estado con ellos.
5
+ //
6
+ // Vive aparte de `contracts.js` porque es la otra mitad de una costura: aquéllas reciben el estado ya
7
+ // leído y se prueban sin tocar disco, éstas abren archivos. Se separaron cuando el archivo cruzó las 500
8
+ // líneas, y lo que decidió el corte fue eso y no el número.
9
+
10
+ const fs = require('node:fs')
11
+ const path = require('node:path')
12
+ const P = require('./parser')
13
+
14
+ const EPIC_AUXILIARY_FILES = new Set(['notes.md', 'plan.md', 'research.md', 'spec.md'])
15
+
16
+ function validateRoadmapStructure(dir) {
17
+ const roadmap = path.join(dir, 'roadmap')
18
+ let entries = []
19
+ try { entries = fs.readdirSync(roadmap, { withFileTypes: true }) } catch { return ['falta roadmap/'] }
20
+ const errors = []
21
+ for (const entry of entries) {
22
+ // Un archivo que se llama como una épica y no cumple el patrón no lo lee nadie: ni `check`, ni
23
+ // `tree`, ni el runner que busca trabajo. Ignorarlo en silencio es peor que rechazarlo, porque el
24
+ // planning se reporta válido mientras la épica que alguien escribió no existe para el sistema.
25
+ if (/^epic-/.test(entry.name) && !/^epic-\d{3}-/.test(entry.name)) {
26
+ errors.push(
27
+ `roadmap/${entry.name}: nadie lo lee. Una épica se nombra epic-NNN-<slug>.md, `
28
+ + 'o un directorio epic-NNN-<slug>/ con spec.md adentro.',
29
+ )
30
+ continue
31
+ }
32
+ if (!entry.isDirectory() || !/^epic-\d{3}-/.test(entry.name)) continue
33
+ const epicDir = path.join(roadmap, entry.name)
34
+ if (!fs.existsSync(path.join(epicDir, 'spec.md'))) {
35
+ errors.push(`roadmap/${entry.name}: falta spec.md`)
36
+ }
37
+ for (const child of fs.readdirSync(epicDir, { withFileTypes: true })) {
38
+ if (!child.isFile() || !EPIC_AUXILIARY_FILES.has(child.name)) {
39
+ errors.push(`roadmap/${entry.name}/${child.name}: archivo auxiliar no permitido`)
40
+ }
41
+ }
42
+ }
43
+ return errors
44
+ }
45
+
46
+ // El BACKLOG es la única cola, y su lector descarta en silencio lo que no cumple el contrato: una
47
+ // viñeta mal escrita no está en cola, no aparece en `tree` y no la toma nadie, sin que nada falle.
48
+ // Se juzga sólo lo que vive bajo un hito —el encabezado del archivo es prosa— y sólo las viñetas,
49
+ // para no confundir con un error el texto que acompaña a una tarea.
50
+ function validateBacklogStructure(dir) {
51
+ const text = P.withoutComments(P.read(path.join(dir, 'BACKLOG.md')))
52
+ const errors = []
53
+ let milestone = ''
54
+ for (const line of text.split('\n')) {
55
+ const heading = line.match(P.MILESTONE_HEADING)
56
+ if (heading) { milestone = heading[1]; continue }
57
+ if (/^##\s+Hito\b/.test(line)) {
58
+ errors.push(`BACKLOG "${line.trim()}": encabezado inválido; se escribe ## Hito <slug> — <Título>, `
59
+ + 'y sin él las tareas que vienen abajo quedan huérfanas')
60
+ milestone = ''
61
+ continue
62
+ }
63
+ if (/^##\s+/.test(line)) { milestone = ''; continue }
64
+ if (!milestone || !/^\s*[-*]\s+\S/.test(line) || P.TASK_LINE.test(line)) continue
65
+ const lane = line.match(P.TASK_LINE_ANY_LANE)
66
+ if (lane) {
67
+ errors.push(`BACKLOG ${lane[1].trim()}: lane "${lane[2]}" no existe; usá ${P.LANES.join(' | ')}, `
68
+ + 'o dejá la tarea sin clasificar')
69
+ continue
70
+ }
71
+ const at = `BACKLOG hito ${milestone}: no la lee nadie`
72
+ if (/^-\s+\[[xX]\]/.test(line)) {
73
+ errors.push(`${at} — ${line.trim().slice(0, 60)}. Una tarea terminada se mueve a DONE.md, no se tilda acá.`)
74
+ continue
75
+ }
76
+ errors.push(`${at} — ${line.trim().slice(0, 60)}. Una tarea se escribe `
77
+ + '`- [ ] **slug** [lane] — descripción`, con `(→ CN) (epic: NNN)` o `_Aceptación:_` después del guión.')
78
+ }
79
+ return errors
80
+ }
81
+
82
+ // El número de una regla es su identificador, y lo cita todo el sistema: cargos, workflows, plantillas
83
+ // y entradas de DONE. El override se declara escribiendo un archivo con el mismo nombre que el del
84
+ // sistema —ahí redefinir sus números es el punto—; en cualquier otro archivo, reusar un `R` crea una
85
+ // segunda definición que nadie declaró y que ninguna herramienta veía. Las propias se numeran `P`.
86
+ function ruleIds(file) {
87
+ return [...P.read(file).matchAll(/^##\s+([A-Z]\d+)\s+[—-]/gm)].map((match) => match[1])
88
+ }
89
+
90
+ // Qué IDs deja de regir un override por nombre: los que definía el archivo del sistema y el propio no
91
+ // redefine. Reemplazar el archivo entero es la función del override y está documentada; lo que no se
92
+ // veía es la consecuencia, porque la advertencia nombraba el par de archivos y no la diferencia. El
93
+ // caso caro es una regla que el motor sigue exigiendo —R17 lo hace—: queda exigida y sin estar escrita
94
+ // en ningún lado, y quien la vea fallar la va a buscar en `rules/`, donde ya no está.
95
+ // Dos secciones de una épica que compiten por el mismo rol. El parser prefiere la exacta, así que
96
+ // resuelve —y en silencio: quien escribió las dos no se entera de que una se ignora entera. La
97
+ // promoción dejó de generarlas cuando lo importado empezó a bajar un nivel; a mano se siguen pudiendo
98
+ // escribir, y ahí el aviso es lo único que lo dice.
99
+ function competingSections(dir) {
100
+ const roadmap = path.join(dir, 'roadmap')
101
+ const avisos = []
102
+ let files = []
103
+ try { files = fs.readdirSync(roadmap).filter((file) => /^epic-\d{3}-/.test(file)) } catch { return [] }
104
+ for (const file of files.sort()) {
105
+ const text = P.read(path.join(roadmap, file))
106
+ const titles = [...text.matchAll(/^##\s+(.+)$/gm)].map((hit) => hit[1].trim())
107
+ for (const role of [/Criterios/i, /Historias/i]) {
108
+ const casan = titles.filter((title) => role.test(title))
109
+ if (casan.length < 2) continue
110
+ const exact = new RegExp(`^${role.source}$`, role.flags)
111
+ const gana = casan.find((title) => exact.test(title)) || casan[0]
112
+ const ignoradas = casan.filter((title) => title !== gana)
113
+ avisos.push(`roadmap/${file}: "## ${gana}" convive con "## ${ignoradas.join('", "## ')}"; `
114
+ + 'sólo se lee la primera y el resto se ignora entero')
115
+ }
116
+ }
117
+ return avisos
118
+ }
119
+
120
+ function retiredByOverride(dir, name) {
121
+ const rules = path.join(dir, 'rules')
122
+ const system = path.join(rules, 'system', name)
123
+ if (!fs.existsSync(system)) return []
124
+ const redefined = ruleIds(path.join(rules, name))
125
+ return ruleIds(system).filter((id) => !redefined.includes(id))
126
+ }
127
+
128
+ function validateRules(dir) {
129
+ const rules = path.join(dir, 'rules')
130
+ const owner = new Map()
131
+ const errors = []
132
+ const files = (sub) => {
133
+ try {
134
+ return fs.readdirSync(path.join(rules, sub), { withFileTypes: true })
135
+ .filter((entry) => entry.isFile() && entry.name.endsWith('.md') && entry.name !== 'README.md')
136
+ .map((entry) => entry.name).sort()
137
+ } catch { return [] }
138
+ }
139
+ const system = new Set(files('system'))
140
+ for (const name of system) {
141
+ for (const id of ruleIds(path.join(rules, 'system', name))) {
142
+ if (owner.has(id)) errors.push(`rules/system/${name}: ${id} ya lo define ${owner.get(id)}`)
143
+ else owner.set(id, `rules/system/${name}`)
144
+ }
145
+ }
146
+ for (const name of files('')) {
147
+ // El override se declara por nombre: redefinir los números del archivo que reemplaza es su función.
148
+ if (system.has(name)) continue
149
+ for (const id of ruleIds(path.join(rules, name))) {
150
+ const definedBy = owner.get(id)
151
+ if (!definedBy) { owner.set(id, `rules/${name}`); continue }
152
+ errors.push(definedBy.startsWith('rules/system/')
153
+ ? `rules/${name}: ${id} ya lo define ${definedBy}; una regla propia se numera P1..Pn, `
154
+ + 'o vive en un archivo con el mismo nombre para declarar el override'
155
+ : `rules/${name}: ${id} ya lo define ${definedBy}`)
156
+ }
157
+ }
158
+ return errors
159
+ }
160
+
161
+ // Una decisión que no dice si rige no decide nada, y el molde traía el menú entero en la línea de estado:
162
+ // casi una de cada cinco decisiones escritas con este modelo se publicó con el menú intacto. Presentar
163
+ // las opciones no obliga a elegir; esto sí. Las secciones son las cuatro que se escriben siempre —las
164
+ // alternativas quedan en el molde sin exigirse, porque pedirlas rechazaría a casi todas las que existen—.
165
+ const ADR_STATES = ['Propuesto', 'Aceptado', 'Obsoleto']
166
+ const ADR_SUPERSEDED = /^Reemplazada por \[[^\]]+\]\([^)]+\)(?: \(\d{4}-\d{2}-\d{2}\))?$/
167
+ const ADR_SECTIONS = ['Contexto', 'Decisión', 'Consecuencias', 'Estado de implementación']
168
+
169
+ function validateAdrFile(at, text) {
170
+ const errors = []
171
+ const declared = ((text.match(/^\*\*Estado:\*\*\s*(.+?)\s*$/m) || [])[1] || '').trim()
172
+ if (!declared) errors.push(`${at}: falta **Estado:**`)
173
+ else if (declared.includes('|')) errors.push(`${at}: el estado sigue siendo el menú de la plantilla; elegí uno`)
174
+ else if (!ADR_STATES.includes(declared) && !ADR_SUPERSEDED.test(declared)) {
175
+ errors.push(`${at}: estado "${declared}" fuera de ${ADR_STATES.join(' | ')} `
176
+ + '| Reemplazada por [NNN](NNN-slug.md)')
177
+ }
178
+ for (const section of ADR_SECTIONS) {
179
+ if (!new RegExp(`^##\\s+${section}\\s*$`, 'm').test(text)) errors.push(`${at}: falta ## ${section}`)
180
+ }
181
+ return errors
182
+ }
183
+
184
+ // El nombre lleva el id porque de ahí sale la identidad con que se detecta un override, y porque una
185
+ // decisión se cita por número. Sin él, el archivo existe y no lo alcanza ninguna referencia.
186
+ function validateAdr(dir) {
187
+ const adr = path.join(dir, 'adr')
188
+ const errors = []
189
+ const numbers = new Map()
190
+ const scan = (sub, pattern) => {
191
+ let entries = []
192
+ try { entries = fs.readdirSync(path.join(adr, sub), { withFileTypes: true }) } catch { return }
193
+ for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
194
+ if (!entry.isFile() || !entry.name.endsWith('.md')) continue
195
+ if (entry.name === 'README.md' || entry.name === '000-template.md') continue
196
+ const at = `adr/${sub ? `${sub}/` : ''}${entry.name}`
197
+ const id = entry.name.match(pattern)
198
+ if (!id) {
199
+ errors.push(`${at}: nadie lo lee como decisión. Una ADR se nombra NNN-<slug>.md, `
200
+ + 'y la del sistema <ID>-NNN-<slug>.md en system/.')
201
+ continue
202
+ }
203
+ if (numbers.has(id[1])) errors.push(`${at}: ${id[1]} ya lo usa ${numbers.get(id[1])}`)
204
+ else numbers.set(id[1], at)
205
+ errors.push(...validateAdrFile(at, P.read(path.join(adr, sub, entry.name))))
206
+ }
207
+ }
208
+ scan('', /^(\d{3})-[a-z0-9-]+\.md$/)
209
+ scan('system', /^([A-Z][A-Z0-9]*-\d{3})-[a-z0-9-]+\.md$/)
210
+ return errors
211
+ }
212
+
213
+ module.exports = {
214
+ validateRoadmapStructure,
215
+ validateBacklogStructure,
216
+ competingSections,
217
+ retiredByOverride,
218
+ validateRules,
219
+ validateAdr,
220
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ingeniomaps/cauce",
3
- "version": "0.69.0",
3
+ "version": "0.71.0",
4
4
  "description": "Sistema portable de planificación y ejecución verificable para cualquier proyecto",
5
5
  "keywords": [
6
6
  "planning",
@@ -0,0 +1,19 @@
1
+ # Cómo mergea git lo que el equipo escribe a la vez.
2
+ #
3
+ # `union` concatena los dos lados en vez de marcar conflicto. Vale para un archivo que sólo crece por el
4
+ # final, que es lo que hacen estos dos cuando dos personas registran un bloqueo el mismo día: las dos
5
+ # filas son buenas y van las dos, así que el conflicto no significa nada y hay que resolverlo igual.
6
+ #
7
+ # La evidencia de una tarea cerrada no está acá porque no la necesita: vive en su propio `done/<slug>.md`
8
+ # y nadie escribe el archivo de nadie.
9
+ #
10
+ # El borde: `ops archive human-actions` **quita** filas de la tabla activa al mover las resueltas al
11
+ # histórico, y eso no es crecer por el final. Archivar mientras otro registra un bloqueo puede devolver a
12
+ # la vida una fila resuelta. Es un acto raro y deliberado: se hace con el árbol limpio.
13
+ planning/HUMAN_ACTIONS.md merge=union
14
+ planning/done/human-actions.md merge=union
15
+
16
+ # INBOX, BACKLOG y RECURRING quedan afuera a propósito. En los tres, quitar una línea es una operación
17
+ # normal y frecuente —promover una idea, sacar una tarea de la cola, retirar una recurrencia— y union
18
+ # la desharía sin que nada lo dijera. Ahí el conflicto es la respuesta correcta: significa que dos
19
+ # personas decidieron sobre lo mismo, y eso se lee.
@@ -150,8 +150,9 @@ que hacía falta, apagala después, y que la razón quede escrita donde alguien
150
150
  Antes de abrir un archivo de `planning/`, preguntarle al CLI: es determinista, no gasta contexto y no
151
151
  muta nada.
152
152
 
153
- - `node tools/ops.js context planning` — gate, mutex de WIP y la tarea que corresponde ahora, con su
154
- aceptación y sus criterios. Es la entrada correcta para empezar a trabajar.
153
+ - `node tools/ops.js context planning [--hito <slug>]` — gate, mutex de WIP y la tarea que corresponde
154
+ ahora, con su aceptación y sus criterios. Es la entrada correcta para empezar a trabajar. Con `--hito`
155
+ la cola se acota a ese hito, que es como un equipo se reparte trabajo sin coordinarse.
155
156
  - `node tools/ops.js tree planning` — panorama de roadmap, backlog, WIP, inbox y done.
156
157
  - `node tools/ops.js recurring planning [--promote <qué>]` — qué trabajo recurrente venció y con
157
158
  qué línea se promueve. Emite esa línea; escribirla en `BACKLOG.md` es de una persona.
@@ -163,8 +164,49 @@ muta nada.
163
164
  nombrada haya corrido —eso depende del runner, y varios no la nombran al pasar— ni reemplaza a leer
164
165
  su fuente, que es lo que R9 pide.
165
166
 
166
- Los cinco aceptan `--json`. Leer `BACKLOG.md`, `WIP.md`, `HUMAN_ACTIONS.md` o `RECURRING.md` completos
167
- sólo cuando haga falta editarlos o cuando el CLI no responda la pregunta.
167
+ Los cinco aceptan `--json`. Leer `BACKLOG.md`, tu `wip/<runner>.md`, `HUMAN_ACTIONS.md` o `RECURRING.md`
168
+ completos sólo cuando haga falta editarlos o cuando el CLI no responda la pregunta.
169
+
170
+ ## Cómo tomar trabajo
171
+
172
+ Con equipo, la tarea que `context` devuelve puede estar libre o ya ser tuya, y la salida lo dice. Libre
173
+ se toma antes de empezar:
174
+
175
+ - `node tools/ops.js claim planning <tarea>` — la reserva a tu nombre y escribe `planning/claims/<tarea>.md`.
176
+ - `node tools/ops.js release planning <tarea>` — la devuelve a la cola.
177
+
178
+ Una tarea que declara `(depende: slug)` no se ofrece ni se puede tomar hasta que eso esté en DONE, y
179
+ `context` la muestra con una línea `WAIT`. No hay que adelantarse: lo que sigue es trabajo de quien tiene
180
+ la tarea de la que depende.
181
+
182
+ Tomar no es promover: la tarea ya estaba aprobada en `BACKLOG.md` y esto sólo dice quién la hace, así que
183
+ entra en la autonomía del runner. Lo que no entra es tocar el reclamo de otro — ni tomarlo, ni soltarlo—,
184
+ y `context` directamente no ofrece una tarea reclamada.
185
+
186
+ El reclamo hay que **commitearlo y empujarlo**: sin eso el otro runner lee lo que hay en su copia y la
187
+ reserva no existe para nadie más.
188
+
189
+ ## Con qué runner arrancás
190
+
191
+ Antes de pedir trabajo hay que saber quién lo tiene. Dos sesiones en la misma máquina resuelven la misma
192
+ identidad de git, así que lo que las distingue es el `CAUCE_RUNNER` de cada una.
193
+
194
+ `node tools/ops.js runners planning [--json]` dice qué runners tienen una tarea abierta, cuál, desde
195
+ cuándo y si su rama avanzó. Según lo que devuelva:
196
+
197
+ - **Ninguno** — arrancá con un id propio y no preguntes nada. No hay trabajo que retomar.
198
+ - **Uno o más** — **preguntale a la persona** cuál retoma o si arranca uno nuevo, nombrando la tarea de
199
+ cada uno, desde cuándo y si avanzó. Retomar el id de un agente que sigue corriendo le saca la tarea, y
200
+ arrancar uno nuevo cuando había trabajo a medias lo deja huérfano: las dos rompen algo, y por eso la
201
+ elección no es tuya.
202
+
203
+ Elegido el id, **exportalo vos** y usalo en cada `ops` de la sesión. **Nunca le pidas a una persona que
204
+ escriba una variable de entorno**: no es el idioma en el que trabaja, y el runner es cómo el toolkit
205
+ distingue dos sesiones, no una decisión de producto. Lo suyo es elegir; la mecánica es tuya.
206
+
207
+ - `node tools/ops.js worktree planning <tarea>` — prepara el árbol de trabajo de esa tarea y te devuelve
208
+ la ruta con el `export CAUCE_RUNNER` hecho. No clona nada: `git worktree` comparte el mismo `.git`, y
209
+ cada árbol queda fijado a su rama, así que ningún agente hace `checkout` sobre el trabajo de otro.
168
210
 
169
211
  ## Autonomía
170
212
 
@@ -200,5 +242,5 @@ publicación tampoco se decide ahí: la decide `allowPush`.
200
242
  3. QA valida el comportamiento por el camino real, no por un atajo interno.
201
243
  4. La deuda residual va a `planning/INBOX.md`.
202
244
  5. El cambio se commitea en el repo del servicio —uno por naturaleza del diff, y una tarea suele
203
- tener una sola— y el hash real queda en `DONE.md`.
245
+ tener una sola— y el hash real queda en la evidencia de la tarea, `planning/done/<slug>.md`.
204
246
  6. `node tools/ops.js check planning` queda verde.
@@ -62,6 +62,6 @@ cambia instalación o materialización, valida `ops init` en un directorio tempo
62
62
 
63
63
  ## Límites
64
64
 
65
- No edites `planning/BACKLOG.md`, `WIP.md` o `DONE.md` desde hooks o instaladores. No hagas que `ops init` active
65
+ No edites `planning/BACKLOG.md`, `planning/wip/` ni `planning/done/` desde hooks o instaladores. No hagas que `ops init` active
66
66
  un runner silenciosamente. No agregues lógica de negocio, nombres de servicios de un proyecto, tokens, rutas
67
67
  personales ni modelos concretos a esta capa reusable.
@@ -9,5 +9,14 @@ node_modules/
9
9
  # máquina. Committearlo sería historia que nadie lee y un conflicto por commit.
10
10
  planning/.verify-log
11
11
 
12
+ # El plan de cada runner. Es de la máquina que lo corre —existe para recuperar una ejecución
13
+ # interrumpida, y nadie más puede retomarla— y cambia en cada paso, así que compartirlo es un conflicto
14
+ # por commit a cambio de nada. Lo que el equipo sí necesita saber vive en `planning/claims/`.
15
+ #
16
+ # Se excluyen los planes y no el directorio: git no puede volver a incluir un archivo cuyo directorio
17
+ # está excluido, y el README de ahí adentro sí tiene que viajar.
18
+ planning/wip/*.md
19
+ !planning/wip/README.md
20
+
12
21
  *.tgz
13
22
  .DS_Store
@@ -5,6 +5,10 @@ Solo contiene trabajo aprobado y listo. Las ideas viven en `INBOX.md`.
5
5
  La aceptación se escribe en la línea o se hereda del criterio que la tarea cita. El lane y el cast son
6
6
  opcionales: sin ellos la tarea está sin clasificar, que es el estado que dispara al clasificador.
7
7
 
8
+ `(depende: slug)` declara qué tiene que estar en DONE antes de que esta tarea se pueda tomar. El orden de
9
+ la lista alcanzaba cuando trabajaba un runner; con dos, el segundo toma la que sigue mientras el primero
10
+ construye aquella de la que depende.
11
+
8
12
  Pasando de nueve tareas un hito, o de cinco criterios heredados una tarea, `check` pide decidir (R17):
9
13
  o se parte, o lleva `(sin partir: <razón>)` en su línea.
10
14
 
@@ -14,4 +18,5 @@ o se parte, o lleva `(sin partir: <razón>)` en su línea.
14
18
  - [ ] **slug-de-tarea** [full] — Resultado a construir. _Aceptación: conducta observable._ (service: ruta) (cast: quien-entrega → quien-revisa)
15
19
  - [ ] **otro-slug** [lite] — Resultado a construir. (→ C1) (epic: 001) (service: ruta) (cast: quien-entrega → quien-revisa, otro-revisor)
16
20
  - [ ] **sin-clasificar** — Resultado a construir. _Aceptación: conducta observable._ (service: ruta)
21
+ - [ ] **la-que-sigue** [lite] — Resultado a construir. _Aceptación: conducta observable._ (service: ruta) (depende: slug-de-tarea)
17
22
  -->
@@ -5,7 +5,7 @@ INBOX ──promoción humana──▶ roadmap ──historias listas──▶ B
5
5
  ▲ │
6
6
  │ Pick/Plan
7
7
  │ ▼
8
- │ done/ ◀── archive ◀── DONE ◀── Verify/QA ◀────────── WIP
8
+ │ done/<tarea>.md ◀── evidencia ◀── Verify/QA ◀───── WIP
9
9
  │ │
10
10
  └───────────── deuda adyacente ─────────────────────────────┤
11
11
  │
@@ -9,9 +9,12 @@ invariantes.
9
9
  `(service: ruta)`.
10
10
  - Hito: `## Hito slug — Título`.
11
11
  - Tarea: `- [ ] **slug** [express|directo|lite|full] — descripción. _Aceptación: observable._ (service: ruta) (cast: quien-entrega → quien-revisa, otro)`;
12
- puede heredar aceptación usando `(→ CN) (epic: NNN)`. Lane y cast son opcionales: sin ellos la tarea
13
- está sin clasificar, que es un estado y no un error.
14
- - DONE: entrada `[x]` con `acept:`, `done:`, `qa:`, `tests:` y `commit:`. `tests:` enlaza cada criterio
12
+ puede heredar aceptación usando `(→ CN) (epic: NNN)` y declarar `(depende: slug, otro)`. Lane y cast son
13
+ opcionales: sin ellos la tarea está sin clasificar, que es un estado y no un error. Una tarea con
14
+ dependencias no se ofrece ni se toma hasta que todas estén en DONE.
15
+ - DONE: un archivo por tarea cerrada, `done/<slug>.md`, con su entrada `[x]` y los campos `acept:`,
16
+ `fecha:` en AAAA-MM-DD, `done:`, `qa:`, `tests:` y `commit:`. La fecha es la del cierre, y es lo que
17
+ ordena una evidencia que ya no depende de su posición dentro de un archivo. `tests:` enlaza cada criterio
15
18
  mediante `CN → prueba`; usa `A → prueba` cuando no hay épica o `n/a — razón` si no existe una
16
19
  superficie ejecutable. `decisions:` es opcional y, si aparece, cita `[fuente: ...]` o
17
20
  `[supuesto: ...]`. `commit:` apunta a `<sha> <asunto>`, o a `n/a — razón` cuando la tarea no
@@ -25,12 +28,18 @@ invariantes.
25
28
  cola de su línea de BACKLOG. Vencer no bloquea: cada vuelta se promueve con el período en el slug
26
29
  —`<qué>-AAAA-MM`— y esa promoción la escribe una persona. Postergar se registra bajo
27
30
  `## Postergaciones` con `- **qué** AAAA-MM-DD — razón`.
28
- - WIP activo: frontmatter y checklist; inactivo: `status: IDLE`.
31
+ - Reclamo: `claims/<tarea>.md` con frontmatter `task/owner/runner/started/service`; el nombre del
32
+ archivo es el slug que reserva, y por eso un `task` que diga otra cosa es un error. `owner` dice a
33
+ quién preguntarle y `runner` decide de quién es: con varios agentes en una máquina la persona es
34
+ la misma y el árbol de trabajo no.
35
+ - WIP activo: frontmatter y checklist en `wip/<runner>.md`; inactivo cuando el archivo no está. Es
36
+ local y no viaja por git: existe para recuperar la sesión de quien lo escribió, y es uno por runner
37
+ porque una instancia sidecar la comparten todos los agentes de esa máquina.
29
38
 
30
39
  ## Gates de arranque
31
40
 
32
41
  1. Si existe `AWAITING_REVIEW.md`, parar y mostrar la acción que contiene.
33
- 2. Si WIP está activo y puede pertenecer a otro runner, parar: es el mutex.
42
+ 2. Si tu WIP está activo, la tarea es ésa: es el mutex del runner, y sólo se lee el propio.
34
43
  3. Si WIP está activo tras una interrupción confirmada, verificar los pasos `[x]` en disco y continuar
35
44
  desde el primer `[ ]`; no replanear.
36
45
  4. Si WIP apunta a una tarea ya en DONE y fuera de BACKLOG, reparar el cierre dejando WIP en IDLE.
@@ -38,7 +47,8 @@ invariantes.
38
47
  ## Máquina por tarea
39
48
 
40
49
  1. Triage: inspeccionar estado y cambios existentes.
41
- 2. Pick: primera tarea no bloqueada del primer hito.
50
+ 2. Pick: primera tarea no bloqueada ni reclamada por otro runner, recorriendo los hitos en orden;
51
+ reclamarla antes de empezar y empujar ese reclamo, que sin empujar no reserva nada.
42
52
  3. Classify: si la tarea no declara lane y cast, decidirlos y escribirlos en su línea.
43
53
  4. Ready: exigir aceptación concreta y decisiones resueltas.
44
54
  5. Decompose: dividir trabajo mayor a `maxTaskHours` o con más de cinco condiciones de aceptación.
@@ -49,7 +59,8 @@ invariantes.
49
59
  10. Verify: ejecutar los gates declarados por el servicio y registrar exit codes.
50
60
  11. QA: probar la aceptación por el camino que usa un consumidor real.
51
61
  12. Commit: stage explícito y commits verificables, uno por naturaleza del diff.
52
- 13. Done: mover la tarea, registrar evidencia, limpiar WIP y cerrar/archivar la épica si corresponde.
62
+ 13. Done: sacar la tarea de la cola, escribir su evidencia en `done/<slug>.md`, limpiar WIP, soltar
63
+ el reclamo y cerrar la épica si no le queda ninguna historia abierta.
53
64
  14. Cierre: check verde, deuda residual al INBOX y checkpoint entre hitos.
54
65
 
55
66
  ## Lanes
@@ -70,10 +81,25 @@ El lane reduce ceremonia, nunca seguridad, aceptación ni evidencia: Verify y el
70
81
  cuatro. Lo que decide el carril es la superficie del cambio y no su tamaño en líneas — un `if` en el
71
82
  chequeo de permisos es `full`, y un componente entero de presentación puede ser `directo`.
72
83
 
84
+ Escribir la línea es también contrastarla. Declara cuatro cosas —qué hace, en qué carril, quién entrega y
85
+ revisa, con qué se comprueba— y las cuatro salen de la misma mano en el mismo acto, así que nada las cruza
86
+ después. Releerlas no encuentra el hueco: una aceptación incompleta se lee perfecta, porque todo lo que
87
+ dice es cierto.
88
+
89
+ Antes de dar la tarea por escrita se recorre su descripción frase por frase y se contesta, por cada cosa
90
+ que promete, cuál condición de aceptación la comprueba; se lee el carril contra la superficie que toca y
91
+ no contra su tamaño; y se comprueba que el cast entregue a quien construye. Lo que quede sin condición se
92
+ agrega o se declara fuera de alcance en la línea.
93
+
94
+ Es el único momento en que las cuatro se pueden mirar juntas, y por eso la pasada vive acá y no en una
95
+ fase: después el carril ya decide cuáles corren, y la que revisaría es una de las que ese carril puede
96
+ saltar. Una tarea mal marcada `express` es justamente la que se salta la fase donde alguien lo notaría.
97
+
73
98
  ## Invariantes
74
99
 
75
100
  1. Una tarea tiene un dueño de estado: roadmap → BACKLOG → overlay WIP → DONE.
76
- 2. Un solo runner a la vez; WIP activo es mutex — `business-rules/system/BR-OPS-001`.
101
+ 2. Un runner lleva una tarea a la vez —WIP, que es local: `business-rules/system/BR-OPS-001`— y una
102
+ tarea la lleva un runner —el reclamo, que es compartido: `business-rules/system/BR-OPS-005`—.
77
103
  3. INBOX nunca se ejecuta automáticamente — `business-rules/system/BR-OPS-002`.
78
104
  4. No declarar éxito sin comandos, resultados y exit codes reales — `business-rules/system/BR-OPS-004`.
79
105
  5. No inventar credenciales ni decisiones; registrar HUMAN_ACTIONS.
@@ -7,8 +7,8 @@ Se lee y se escribe en cada tarea.
7
7
  | Pieza | Responsabilidad |
8
8
  |---|---|
9
9
  | `BACKLOG.md` | Única cola de tareas promovidas y listas. |
10
- | `WIP.md` | Única tarea en vuelo; recuperación y mutex. |
11
- | `DONE.md` | Evidencia activa de tareas terminadas. |
10
+ | `wip/` | El plan en vuelo de cada runner; recuperación y mutex por runner. No viaja por git. |
11
+ | `claims/` | Qué tarea tomó cada quien; un archivo por tarea. |
12
12
  | `HUMAN_ACTIONS.md` | Acciones externas que requieren una persona. |
13
13
  | `AWAITING_REVIEW.md` | Gate efímero; mientras existe no inicia trabajo. |
14
14
 
@@ -32,7 +32,7 @@ Evidencia que no se reescribe.
32
32
 
33
33
  | Pieza | Responsabilidad |
34
34
  |---|---|
35
- | `done/` | Historial inmutable: una épica cerrada por archivo, más las acciones humanas resueltas. |
35
+ | `done/` | Evidencia de lo terminado: una tarea cerrada por archivo, más las acciones humanas resueltas. |
36
36
  | `reports/` | Informes de recorridos de equipo. |
37
37
 
38
38
  El protocolo exacto está en `PROTOCOL.md`, la explicación visual en `FLOW.md` y los principios que
@@ -26,7 +26,7 @@ vencimiento se calcula cuando alguien corre el CLI. Promover sigue siendo un act
26
26
  Se cuenta desde la última vez que se cerró, no desde un día fijo del calendario: una recurrencia
27
27
  atrasada no debe tres vueltas, debe una, la que no se hizo.
28
28
 
29
- La fecha de ese último cierre **no se escribe acá**. Sale de `DONE.md` — la entrada más nueva cuyo slug
29
+ La fecha de ese último cierre **no se escribe acá**. Sale de `done/` — la entrada más nueva cuyo slug
30
30
  sea `<qué>-AAAA-MM`—, que es la evidencia de que efectivamente se hizo y no la afirmación de que se
31
31
  hizo. Una celda que alguien tiene que acordarse de actualizar miente a los tres meses, y el estado no se
32
32
  copia para representar progreso.
@@ -13,8 +13,9 @@ en conversaciones, tickets y memoria del runner hace imposible saber qué está
13
13
  ## Decisión
14
14
 
15
15
  **La instancia usa `planning/` como fuente de verdad operativa, legible y validable.** `INBOX.md` recibe ideas,
16
- el roadmap define resultados, `BACKLOG.md` contiene trabajo promovido, `WIP.md` conserva una única ejecución y
17
- `DONE.md` registra evidencia. El protocolo define las transiciones permitidas.
16
+ el roadmap define resultados, `BACKLOG.md` contiene trabajo promovido, `wip/<runner>.md` conserva la ejecución de cada runner y
17
+ y `done/<slug>.md` registra la evidencia de cada tarea cerrada, un archivo por tarea. El protocolo
18
+ define las transiciones permitidas.
18
19
 
19
20
  ## Alternativas consideradas
20
21
 
@@ -1,8 +1,9 @@
1
1
  # Una sola tarea activa
2
2
 
3
- > **Dominio:** planning | **Estado:** vigente | **Actualizado:** 2026-08-14
3
+ > **Dominio:** planning | **Estado:** vigente | **Actualizado:** 2026-09-07
4
4
 
5
- WIP protege la exclusión mutua y permite recuperar una ejecución interrumpida.
5
+ WIP protege la exclusión mutua dentro de un runner y permite recuperar una ejecución interrumpida.
6
+ Que dos runners no tomen la misma tarea es otra cosa, y la sostiene `BR-OPS-005-una-tarea-un-runner.md`.
6
7
  [fuente: ../../adr/system/OPS-001-planificacion-como-fuente-de-verdad.md]
7
8
 
8
9
  Elabora el invariante 2 de `../../PROTOCOL.md`, que lo enuncia en una línea: acá viven sus bordes
@@ -12,7 +13,7 @@ y su evidencia. Cambiar una sin la otra las separa.
12
13
 
13
14
  | ID | Regla | Condición y resultado |
14
15
  |---|---|---|
15
- | BR-OPS-001 | WIP es mutex | Si WIP está activo, ningún runner toma otra tarea hasta continuarlo o resolverlo. |
16
+ | BR-OPS-001 | WIP es mutex | Si WIP está activo, ese runner no toma otra tarea hasta continuarlo o resolverlo. |
16
17
 
17
18
  ## Por qué existe cada regla
18
19
 
@@ -24,7 +25,8 @@ y su evidencia. Cambiar una sin la otra las separa.
24
25
  |---|---|
25
26
  | Sesión interrumpida | Se verifican pasos persistidos y se continúa la misma tarea. |
26
27
  | Tarea ya cerrada | Se repara el cierre y WIP vuelve a `IDLE`; no se ejecuta otra vez. |
27
- | Dueño incierto | Se detiene y solicita revisión; no se asume abandono. |
28
+ | WIP ausente | Se lee como IDLE. El archivo es local y un clon nuevo no lo trae; eso no es un error. |
29
+ | Tarea de otro runner | No la ve: el WIP ajeno no viaja. Lo que la reserva es su reclamo (BR-OPS-005). |
28
30
 
29
31
  ## Evidencia
30
32
 
@@ -36,3 +38,4 @@ y su evidencia. Cambiar una sin la otra las separa.
36
38
  | Fecha | Cambio | Origen |
37
39
  |---|---|---|
38
40
  | 2026-08-14 | Creación | OPS-001 y `PROTOCOL.md`. |
41
+ | 2026-09-07 | El mutex se acota al runner; el WIP pasa a ser local | Trabajo en equipo: `../../delivery/teamwork.md`. |
@@ -0,0 +1,44 @@
1
+ # Una tarea, un runner
2
+
3
+ > **Dominio:** planning | **Estado:** vigente | **Actualizado:** 2026-09-07
4
+
5
+ El reclamo protege la exclusión mutua entre runners y hace visible quién sostiene cada tarea.
6
+ [fuente: ../../adr/system/OPS-001-planificacion-como-fuente-de-verdad.md]
7
+
8
+ Es la contracara de `BR-OPS-001-una-sola-tarea-activa.md`, y las dos juntas son el invariante 2 de
9
+ `../../PROTOCOL.md`. Se parecen y no son la misma: aquélla impide que un runner lleve dos tareas y vive
10
+ en su `wip/<runner>.md`, que es local; ésta impide que dos runners lleven la misma y vive en `claims/`, que es
11
+ compartido. Con una sola, un equipo trabaja una tarea por vez o duplica trabajo sin enterarse.
12
+
13
+ ## Reglas
14
+
15
+ | ID | Regla | Condición y resultado |
16
+ |---|---|---|
17
+ | BR-OPS-005 | El reclamo reserva | Una tarea reclamada no se ofrece a otro runner ni se toma hasta que su reclamo se suelte. |
18
+
19
+ ## Por qué existe cada regla
20
+
21
+ - **BR-OPS-005:** sin reserva, dos runners que preguntan a la vez reciben la misma tarea y ninguno lo
22
+ nota: el trabajo se duplica y el conflicto aparece recién al mergear, cuando ya se pagó dos veces.
23
+
24
+ ## Casos borde
25
+
26
+ | Caso | Comportamiento esperado |
27
+ |---|---|
28
+ | Reclamo propio | Se continúa esa tarea; es lo que el runner declaró que iba a hacer. |
29
+ | Reclamo de otro | La tarea no se ofrece; se toma la siguiente libre. |
30
+ | Reclamo sin empujar | No protege a nadie: el otro runner lee lo que hay en su copia. |
31
+ | Reclamo abandonado | Se avisa por antigüedad; soltarlo es un acto humano, no un comando. |
32
+ | Tarea ya en DONE | El reclamo sobra y se avisa; la evidencia no depende de él. |
33
+
34
+ ## Evidencia
35
+
36
+ - `ops context` no devuelve una tarea con reclamo ajeno y nombra quién la tiene.
37
+ - `ops check` rechaza un reclamo cuya tarea no existe en BACKLOG ni DONE.
38
+ - `ops claim` se niega a pisar el reclamo de otro.
39
+
40
+ ## Historial
41
+
42
+ | Fecha | Cambio | Origen |
43
+ |---|---|---|
44
+ | 2026-09-07 | Creación | Trabajo en equipo: `../../delivery/teamwork.md`. |