@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
|
@@ -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
|
@@ -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.
|
package/template/AGENTS.md
CHANGED
|
@@ -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
|
|
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`, `
|
|
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 `
|
|
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`, `
|
|
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.
|
package/template/gitignore
CHANGED
|
@@ -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
|
-
│
|
|
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
|
|
13
|
-
está sin clasificar, que es un estado y no un error.
|
|
14
|
-
|
|
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
|
-
-
|
|
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
|
|
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
|
|
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:
|
|
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
|
|
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
|
-
| `
|
|
11
|
-
| `
|
|
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/` |
|
|
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 `
|
|
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, `
|
|
17
|
-
`
|
|
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-
|
|
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,
|
|
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
|
-
|
|
|
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`. |
|