@ingeniomaps/cauce 0.53.0 → 0.53.2
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 +35 -0
- package/automatization/hooks/guard-dependencies.sh +1 -1
- package/automatization/hooks/guard-destructive.sh +1 -1
- package/automatization/hooks/guard-engine.sh +1 -1
- package/automatization/hooks/guard-generated.sh +1 -1
- package/automatization/hooks/guard-git-add.sh +1 -1
- package/automatization/hooks/guard-governance.sh +1 -1
- package/automatization/hooks/guard-integration-snapshot.sh +1 -1
- package/automatization/hooks/guard-migrations.sh +1 -1
- package/automatization/hooks/guard-planning-drift.sh +1 -1
- package/automatization/hooks/guard-secrets.sh +1 -1
- package/automatization/hooks/guard-test-evidence.sh +1 -1
- package/automatization/hooks/guard-verify.sh +1 -1
- package/automatization/hooks/guard-workspace-boundary.sh +1 -1
- package/automatization/hooks/run-hook.sh +3 -2
- package/automatization/runners/antigravity/hook.js +13 -14
- package/automatization/shared/eval-only.js +3 -3
- package/automatization/workflows/agent-eval.js +3 -3
- package/automatization/workflows/agent-promote.js +4 -4
- package/automatization/workflows/autobuild.js +12 -12
- package/automatization/workflows/flow-eval.js +10 -7
- package/automatization/workflows/flow.js +9 -10
- package/automatization/workflows/onboard.js +4 -5
- package/engine/agents/evaluations.js +18 -16
- package/engine/agents/learning-files.js +111 -0
- package/engine/agents/learning-sources.js +166 -0
- package/engine/agents/learning.js +93 -300
- package/engine/automation/config.js +175 -0
- package/engine/automation/hooks.js +96 -0
- package/engine/automation/index.js +17 -431
- package/engine/automation/roles.js +72 -0
- package/engine/automation/runners.js +162 -0
- package/engine/cli/catalog.js +7 -12
- package/engine/cli/instance.js +11 -10
- package/engine/cli/io.js +1 -1
- package/engine/cli/ops.js +12 -10
- package/engine/cli/wiring.js +2 -4
- package/engine/config/validate.js +2 -1
- package/engine/core/frontmatter.js +2 -1
- package/engine/core/ownership.js +5 -4
- package/engine/core/scan.js +3 -0
- package/engine/flows/registry.js +2 -2
- package/engine/hooks/files.js +160 -0
- package/engine/hooks/input.js +129 -0
- package/engine/hooks/run.js +24 -449
- package/engine/hooks/shell.js +197 -0
- package/engine/integrations/registry.js +2 -0
- package/engine/planning/contracts.js +12 -10
- package/engine/planning/parser.js +4 -3
- package/engine/planning/state.js +2 -1
- package/package.json +5 -5
- package/template/tools/ops.js +2 -2
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
// Los guards que juzgan un comando antes de que se ejecute: qué destruye, qué publica, qué toca una
|
|
4
|
+
// dependencia y qué gate hay que haber corrido. Todos leen `commandOf` y miran el índice de git —de
|
|
5
|
+
// ahí que vayan juntos—, y son el grupo `pre-shell` que el registro ya declaraba.
|
|
6
|
+
|
|
7
|
+
const fs = require('node:fs')
|
|
8
|
+
const path = require('node:path')
|
|
9
|
+
const { spawnSync } = require('node:child_process')
|
|
10
|
+
const {
|
|
11
|
+
commandOf, cwdOf, block, gitDirectory, isCommit, stagedFiles, pushAllowed,
|
|
12
|
+
} = require('./input')
|
|
13
|
+
|
|
14
|
+
function destructive(input) {
|
|
15
|
+
const command = commandOf(input)
|
|
16
|
+
if (/\bgit\s+push\b/.test(command) && !pushAllowed(input)) {
|
|
17
|
+
block("'git push' publica cambios y requiere una acción humana. Se habilita con runner.allowPush.")
|
|
18
|
+
}
|
|
19
|
+
const rules = [
|
|
20
|
+
[/\bgit\s+reset\s+--hard\b/, "'git reset --hard' destruye cambios locales."],
|
|
21
|
+
[/\bgit\s+clean\s+-[^\s]*f/, "'git clean -f' borra archivos sin seguimiento."],
|
|
22
|
+
// `git checkout -- .` destruye lo mismo que `reset --hard` y sin recuperación, pero se escribe como
|
|
23
|
+
// una limpieza. Se bloquea sólo la forma ancha —`.`, `*`, `:/`, o sin ruta—: revertir un archivo
|
|
24
|
+
// nombrado es trabajo corriente y no se toca.
|
|
25
|
+
//
|
|
26
|
+
// Pasó dos veces en una sesión, las dos limpiando restos de una prueba: el comando revirtió también
|
|
27
|
+
// el trabajo de al lado, que no estaba commiteado. Lo que engaña es que el alcance no se ve en el
|
|
28
|
+
// comando — `.` es el cwd, y el cwd suele tener más de lo que uno está mirando.
|
|
29
|
+
[
|
|
30
|
+
// `git restore .` no lleva `--` y destruye igual: comprobado en `git restore --help` (git 2.43.0),
|
|
31
|
+
// que restaura el working tree por defecto y toma el pathspec sin separador.
|
|
32
|
+
/\bgit\s+(?:checkout|restore)\s+(?:[^;&|]*?\s)?(?:--\s*(?:$|[;&|])|(?:--\s+)?(?:\.|\*|:\/)\s*(?:$|[;&|]))/,
|
|
33
|
+
"'git checkout -- .' revierte todo lo no commiteado del directorio, no sólo lo que estás mirando. "
|
|
34
|
+
+ 'Nombrá el archivo, o commiteá lo que quieras conservar antes.',
|
|
35
|
+
],
|
|
36
|
+
[
|
|
37
|
+
/\bdocker(?:\s+\w+)*\s+(?:volume\s+(?:rm|prune)|system\s+prune|network\s+prune)\b/,
|
|
38
|
+
'La limpieza global de Docker puede borrar datos compartidos.',
|
|
39
|
+
],
|
|
40
|
+
[
|
|
41
|
+
/\bdocker(?:\s+compose|-compose)\s+(?:\S+\s+)*(?:down|stop|kill|rm)\b/,
|
|
42
|
+
'Detener un stack Compose puede interrumpir servicios compartidos.',
|
|
43
|
+
],
|
|
44
|
+
[
|
|
45
|
+
/(?:^|\s)(?:mkfs\S*|shred)\s|\bdd\s+[^;&|]*\bof=\/dev\/|>\s*\/dev\/(?:sd|nvme|disk)/,
|
|
46
|
+
'Operación destructiva sobre disco o dispositivo.',
|
|
47
|
+
],
|
|
48
|
+
[
|
|
49
|
+
/\brm\s+(?:-[^\s]*r[^\s]*\s+)+(?:\/\*?|~\/?|\$HOME|\.\.)(?:\s|$)/,
|
|
50
|
+
"'rm -r' sobre /, home o el directorio padre es catastrófico.",
|
|
51
|
+
],
|
|
52
|
+
]
|
|
53
|
+
for (const [pattern, message] of rules) if (pattern.test(command)) block(message)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function gitAdd(input) {
|
|
57
|
+
const command = commandOf(input)
|
|
58
|
+
if (/\bgit\s+add\s+(?:[^;&|]*\s)?(?:-A\b|--all\b|\.)(?:\s|$|[;&|])/.test(command)) {
|
|
59
|
+
block("'git add -A/--all/.' está prohibido. Stagea rutas explícitas.")
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function dependencies(input) {
|
|
64
|
+
if (process.env.OPS_DEPENDENCIES_OVERRIDE === '1') return
|
|
65
|
+
const command = commandOf(input)
|
|
66
|
+
const unsafePackageCommand = /\b(?:npm|pnpm|yarn|bun)\s+publish\b/.test(command)
|
|
67
|
+
|| /\b(?:npm|pnpm|yarn)\s+(?:install|add)\b[^;&|]*(?:\s-g\b|\s--global\b)/.test(command)
|
|
68
|
+
if (unsafePackageCommand) {
|
|
69
|
+
block('Publicar paquetes o instalar dependencias globales requiere una acción humana explícita.')
|
|
70
|
+
}
|
|
71
|
+
if (!isCommit(command)) return
|
|
72
|
+
const dir = gitDirectory(command, cwdOf(input))
|
|
73
|
+
const staged = stagedFiles(dir)
|
|
74
|
+
const manifests = new Set(['package.json', 'pyproject.toml', 'requirements.txt', 'go.mod', 'Cargo.toml'])
|
|
75
|
+
const locks = new Set([
|
|
76
|
+
'package-lock.json',
|
|
77
|
+
'pnpm-lock.yaml',
|
|
78
|
+
'yarn.lock',
|
|
79
|
+
'bun.lock',
|
|
80
|
+
'bun.lockb',
|
|
81
|
+
'poetry.lock',
|
|
82
|
+
'uv.lock',
|
|
83
|
+
'go.sum',
|
|
84
|
+
'Cargo.lock',
|
|
85
|
+
])
|
|
86
|
+
const byDir = new Map()
|
|
87
|
+
for (const file of staged) {
|
|
88
|
+
const base = path.basename(file)
|
|
89
|
+
if (!manifests.has(base) && !locks.has(base)) continue
|
|
90
|
+
const parent = path.dirname(file)
|
|
91
|
+
const state = byDir.get(parent) || { manifests: [], locks: [] }
|
|
92
|
+
state[manifests.has(base) ? 'manifests' : 'locks'].push(base)
|
|
93
|
+
byDir.set(parent, state)
|
|
94
|
+
}
|
|
95
|
+
for (const [parent, state] of byDir) {
|
|
96
|
+
const existingLocks = [...locks].filter((name) => fs.existsSync(path.join(dir, parent, name)))
|
|
97
|
+
if (existingLocks.length > 1) {
|
|
98
|
+
block(`${parent}: hay varios lockfiles (${existingLocks.join(', ')}). Conserva uno solo.`)
|
|
99
|
+
}
|
|
100
|
+
if (state.manifests.length && existingLocks.length && !state.locks.length) {
|
|
101
|
+
block(`${parent}: cambió ${state.manifests.join(', ')} sin actualizar su lockfile.`)
|
|
102
|
+
}
|
|
103
|
+
if (state.locks.length && !state.manifests.length) {
|
|
104
|
+
block(`${parent}: cambió ${state.locks.join(', ')} sin un cambio explícito en el manifest.`)
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function governance(input) {
|
|
110
|
+
if (process.env.OPS_GOVERNANCE_OVERRIDE === '1') return
|
|
111
|
+
const command = commandOf(input)
|
|
112
|
+
if (!isCommit(command)) return
|
|
113
|
+
const dir = gitDirectory(command, cwdOf(input))
|
|
114
|
+
// El contrato de un cargo y lo que lo mide son gobernanza, igual que un ADR o una regla. La firma de
|
|
115
|
+
// «Aprobación humana» sólo estaba protegida por una frase en un prompt; `SKILL.md` y `references/`
|
|
116
|
+
// son lo que la propuesta cambia, y editarlos directo saltea el ciclo entero; y `evaluations/` es el
|
|
117
|
+
// denominador con que se juzga, así que moverlo ablanda toda medición pasada sin tocar una regla.
|
|
118
|
+
//
|
|
119
|
+
// Quedan afuera las dos clases de evidencia, que registran lo que pasó un día en vez de decidir algo:
|
|
120
|
+
// `learning/reports/` y `evaluations/results/` —esta última se escribe en cada corrida, así que
|
|
121
|
+
// gobernarla pediría un override por evaluación—. Por eso `evaluations/` se nombra por partes.
|
|
122
|
+
const governedPattern = new RegExp(
|
|
123
|
+
String.raw`^(?:(?:template\/)?planning\/(?:rules\/|adr\/|PROTOCOL\.md|` +
|
|
124
|
+
String.raw`METHODOLOGY\.md|FLOW\.md)|automatization\/|engine\/` +
|
|
125
|
+
String.raw`|agents\/[a-z0-9-]+\/(?:system\/)?[a-z0-9-]+\/(?:SKILL\.md|references\/` +
|
|
126
|
+
String.raw`|evaluations\/(?:cases\/|expected-behaviors\.yaml)|learning\/proposals\/))`,
|
|
127
|
+
)
|
|
128
|
+
const governed = stagedFiles(dir).filter((file) => governedPattern.test(file))
|
|
129
|
+
if (governed.length) {
|
|
130
|
+
const files = governed.map((file) => ` - ${file}`).join('\n')
|
|
131
|
+
block(`El commit toca gobernanza protegida:\n${files}\n` +
|
|
132
|
+
'Usa OPS_GOVERNANCE_OVERRIDE=1 solo con aprobación.')
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function run(program, args, cwd) {
|
|
137
|
+
const env = { ...process.env }
|
|
138
|
+
delete env.NODE_TEST_CONTEXT
|
|
139
|
+
const result = spawnSync(program, args, { cwd, encoding: 'utf8', stdio: 'pipe', env })
|
|
140
|
+
return {
|
|
141
|
+
ok: result.status === 0,
|
|
142
|
+
status: result.status,
|
|
143
|
+
output: `${result.stdout || ''}${result.stderr || ''}`.trim(),
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function verify(input) {
|
|
148
|
+
if (process.env.OPS_SKIP_VERIFY === '1') return
|
|
149
|
+
const command = commandOf(input)
|
|
150
|
+
if (!isCommit(command)) return
|
|
151
|
+
const dir = gitDirectory(command, cwdOf(input))
|
|
152
|
+
const staged = stagedFiles(dir)
|
|
153
|
+
const changedOpenApi = staged.some((file) => /^(?:openapi|api|spec)(?:\/.*)?\/[^/]+\.ya?ml$/i.test(file))
|
|
154
|
+
|| staged.some((file) => /^(?:openapi|swagger)\.ya?ml$/i.test(file))
|
|
155
|
+
const changedSqlSource = staged.some((file) => /^(?:db\/queries|queries)\/.*\.sql$/i.test(file))
|
|
156
|
+
const hasApiGenerated = staged.some((file) => /(?:^|\/)[^/]*(?:generated|\.gen)\.(?:go|ts|js|py)$/i.test(file))
|
|
157
|
+
const hasSqlGenerated = staged.some((file) => /(?:^|\/)(?:sqlc|generated)(?:\/|.*\.(?:go|ts|js|py)$)/i.test(file))
|
|
158
|
+
if (changedOpenApi && !hasApiGenerated) {
|
|
159
|
+
block('Cambió una fuente OpenAPI/Swagger sin incluir código regenerado. Ejecuta el generador y stagea su salida.')
|
|
160
|
+
}
|
|
161
|
+
if (changedSqlSource && !hasSqlGenerated) {
|
|
162
|
+
block('Cambió una consulta SQL fuente sin artefactos regenerados. Ejecuta el generador.')
|
|
163
|
+
}
|
|
164
|
+
if (!staged.some((file) => /\.(?:ts|tsx|js|jsx|mjs|cjs|go|py|html|css|scss|prisma)$/.test(file))) return
|
|
165
|
+
const failures = []
|
|
166
|
+
if (fs.existsSync(path.join(dir, 'package.json'))) {
|
|
167
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(dir, 'package.json'), 'utf8'))
|
|
168
|
+
const usesPnpm = fs.existsSync(path.join(dir, 'pnpm-lock.yaml'))
|
|
169
|
+
&& !fs.existsSync(path.join(dir, 'package-lock.json'))
|
|
170
|
+
const pm = usesPnpm ? 'pnpm' : 'npm'
|
|
171
|
+
for (const script of ['test', 'lint', 'typecheck', 'build']) {
|
|
172
|
+
if (!pkg.scripts || !pkg.scripts[script]) continue
|
|
173
|
+
const result = run(pm, ['run', script], dir)
|
|
174
|
+
if (!result.ok) failures.push(`${script} (exit ${result.status})`)
|
|
175
|
+
}
|
|
176
|
+
} else if (fs.existsSync(path.join(dir, 'go.mod'))) {
|
|
177
|
+
const makefile = path.join(dir, 'Makefile')
|
|
178
|
+
if (fs.existsSync(makefile) && /^ci:/m.test(fs.readFileSync(makefile, 'utf8'))) {
|
|
179
|
+
const result = run('make', ['ci'], dir)
|
|
180
|
+
if (!result.ok) failures.push(`make ci (exit ${result.status})`)
|
|
181
|
+
} else {
|
|
182
|
+
for (const args of [['test', './...'], ['build', './...']]) {
|
|
183
|
+
const result = run('go', args, dir)
|
|
184
|
+
if (!result.ok) failures.push(`go ${args[0]} (exit ${result.status})`)
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
} else if (fs.existsSync(path.join(dir, 'pyproject.toml')) || fs.existsSync(path.join(dir, 'requirements.txt'))) {
|
|
188
|
+
const makefile = path.join(dir, 'Makefile')
|
|
189
|
+
if (fs.existsSync(makefile) && /^test:/m.test(fs.readFileSync(makefile, 'utf8'))) {
|
|
190
|
+
const result = run('make', ['test'], dir)
|
|
191
|
+
if (!result.ok) failures.push(`make test (exit ${result.status})`)
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
if (failures.length) block(`Verify falló en ${path.basename(dir)}: ${failures.join(', ')}. No se commitea en rojo.`)
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
module.exports = { destructive, gitAdd, dependencies, governance, verify, run }
|
|
@@ -178,6 +178,8 @@ function validate(root, onlyProvider = '') {
|
|
|
178
178
|
|
|
179
179
|
async function sync(root, name, options = {}) {
|
|
180
180
|
const { entry, config } = providerConfig(root, name)
|
|
181
|
+
// Son dos interruptores y se exigen los dos: el del registro dice que el proveedor está conectado
|
|
182
|
+
// al proyecto, y el suyo que hay a dónde apuntar.
|
|
181
183
|
if (!entry.enabled || !config.enabled) throw new Error(`${name} está deshabilitado`)
|
|
182
184
|
const provider = adapter(name)
|
|
183
185
|
const items = options.fixture
|
|
@@ -206,9 +206,9 @@ function validateRules(dir) {
|
|
|
206
206
|
}
|
|
207
207
|
|
|
208
208
|
// 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:
|
|
209
|
-
//
|
|
209
|
+
// casi una de cada cinco decisiones escritas con este modelo se publicó con el menú intacto. Presentar
|
|
210
210
|
// las opciones no obliga a elegir; esto sí. Las secciones son las cuatro que se escriben siempre —las
|
|
211
|
-
// alternativas quedan en el molde sin exigirse, porque pedirlas rechazaría
|
|
211
|
+
// alternativas quedan en el molde sin exigirse, porque pedirlas rechazaría a casi todas las que existen—.
|
|
212
212
|
const ADR_STATES = ['Propuesto', 'Aceptado', 'Obsoleto']
|
|
213
213
|
const ADR_SUPERSEDED = /^Reemplazada por \[[^\]]+\]\([^)]+\)(?: \(\d{4}-\d{2}-\d{2}\))?$/
|
|
214
214
|
const ADR_SECTIONS = ['Contexto', 'Decisión', 'Consecuencias', 'Estado de implementación']
|
|
@@ -381,23 +381,25 @@ function validateState({ epics, milestones, done, wip, roles = new Set(), humanA
|
|
|
381
381
|
const R17 = { taskCriteria: 5, epicCriteria: 7, milestoneTasks: 9 }
|
|
382
382
|
|
|
383
383
|
// Se cuenta lo que está estructurado: criterios de la épica, criterios que hereda una tarea, tareas del
|
|
384
|
-
// hito.
|
|
385
|
-
// y un número inventado ahí sería peor que ninguno
|
|
384
|
+
// hito. Quedan afuera las dos cosas que no son un conteo: la aceptación escrita en prosa —cuántas
|
|
385
|
+
// condiciones tiene una frase es una lectura, y un número inventado ahí sería peor que ninguno— y la
|
|
386
|
+
// segunda barra de R17, las cuatro horas de esfuerzo, que no está en el artefacto. Las dos las mira el
|
|
387
|
+
// review, que para eso está R3, y la de esfuerzo es la que R17 dice que encuentra lo que ésta deja pasar.
|
|
386
388
|
function oversizedUnits({ epics = [], milestones = [] }) {
|
|
387
389
|
const errors = []
|
|
388
|
-
const
|
|
390
|
+
const undecided = (what, count, limit) =>
|
|
389
391
|
`${what}: ${count} (umbral ${limit} de R17). Revisá si son dos resultados con vidas distintas y `
|
|
390
392
|
+ 'partilo; si es uno solo, partirlo lo empeora — dejalo entero agregando "(sin partir: <razón>)"'
|
|
391
|
-
const
|
|
392
|
-
if (count > limit && !unit.noSplit) errors.push(
|
|
393
|
+
const judge = (unit, what, count, limit) => {
|
|
394
|
+
if (count > limit && !unit.noSplit) errors.push(undecided(what, count, limit))
|
|
393
395
|
}
|
|
394
396
|
for (const epic of epics) {
|
|
395
|
-
|
|
397
|
+
judge(epic, `roadmap/${epic.file}: criterios`, epic.criteria.length, R17.epicCriteria)
|
|
396
398
|
}
|
|
397
399
|
for (const milestone of milestones) {
|
|
398
|
-
|
|
400
|
+
judge(milestone, `hito ${milestone.slug}: tareas`, milestone.tasks.length, R17.milestoneTasks)
|
|
399
401
|
for (const task of milestone.tasks) {
|
|
400
|
-
|
|
402
|
+
judge(task, `BACKLOG ${task.slug}: criterios`, task.criteria.length, R17.taskCriteria)
|
|
401
403
|
}
|
|
402
404
|
}
|
|
403
405
|
return errors
|
|
@@ -23,9 +23,10 @@ const STOP_REASONS = [
|
|
|
23
23
|
// que el lector no leía, que es la manera más cara de tener las dos cosas.
|
|
24
24
|
const MILESTONE_HEADING = /^##\s+Hito\s+([^\s]+)\s+[—-]\s+(.+)$/
|
|
25
25
|
|
|
26
|
-
// Los carriles, en orden de ceremonia creciente. El orden es parte del vocabulario
|
|
27
|
-
//
|
|
28
|
-
//
|
|
26
|
+
// Los carriles, en orden de ceremonia creciente. El orden es parte del vocabulario, porque un carril
|
|
27
|
+
// leído fuera de orden se elige por su nombre y no por su criterio. Que la prosa del PROTOCOL vaya en
|
|
28
|
+
// este mismo orden lo ata la suite del toolkit —`planning-template.test.js`—, no `check`: un workflow
|
|
29
|
+
// en sandbox no puede importar este módulo, así que la atadura es una prueba y no una validación.
|
|
29
30
|
const LANES = ['express', 'directo', 'lite', 'full']
|
|
30
31
|
const TASK_LINE = new RegExp(
|
|
31
32
|
String.raw`^-\s+\[\s\]\s+\*\*([^*]+)\*\*\s*(?:\[(${LANES.join('|')})\])?\s+[—-]\s+(.+)$`,
|
package/engine/planning/state.js
CHANGED
|
@@ -29,7 +29,8 @@ function pendingHumanActions(root) {
|
|
|
29
29
|
|
|
30
30
|
// Selecciona la tarea que un runner debe ejecutar ahora, con la misma precedencia que el protocolo:
|
|
31
31
|
// WIP activo primero —es el mutex y manda incluso si tiene una acción humana abierta—, si no la
|
|
32
|
-
// primera tarea no terminada y no bloqueada
|
|
32
|
+
// primera tarea no terminada y no bloqueada recorriendo los hitos en su orden: agotado el primero,
|
|
33
|
+
// sigue por el que viene.
|
|
33
34
|
function currentTask({ milestones, done, wip }, blockers = []) {
|
|
34
35
|
const queue = milestones.flatMap((milestone) => milestone.tasks.map((task) => ({ ...task, hito: milestone.slug })))
|
|
35
36
|
if (wip) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ingeniomaps/cauce",
|
|
3
|
-
"version": "0.53.
|
|
3
|
+
"version": "0.53.2",
|
|
4
4
|
"description": "Sistema portable de planificación y ejecución verificable para cualquier proyecto",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"planning",
|
|
@@ -30,10 +30,10 @@
|
|
|
30
30
|
"check": "node engine/cli/ops.js check template/planning",
|
|
31
31
|
"automation:check": "node engine/cli/ops.js automation check .",
|
|
32
32
|
"integration:check": "node engine/cli/ops.js integration check template jira",
|
|
33
|
-
"test": "bash test/hooks-smoke.sh && node --test test
|
|
34
|
-
"coverage": "bash test/coverage.sh",
|
|
35
|
-
"coverage:update": "bash test/coverage.sh --update",
|
|
36
|
-
"dead-imports": "node test/dead-imports.js",
|
|
33
|
+
"test": "bash test/tools/hooks-smoke.sh && node --test \"test/**/*.test.js\"",
|
|
34
|
+
"coverage": "bash test/tools/coverage.sh",
|
|
35
|
+
"coverage:update": "bash test/tools/coverage.sh --update",
|
|
36
|
+
"dead-imports": "node test/tools/dead-imports.js",
|
|
37
37
|
"ci": "npm run check && npm run automation:check && npm run integration:check && npm run coverage",
|
|
38
38
|
"prepublishOnly": "npm run ci"
|
|
39
39
|
},
|
package/template/tools/ops.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// Punto de entrada estable del proyecto: nadie —ni una persona ni un agente— necesita saber de
|
|
5
5
|
// dónde sale el motor. Viene de la dependencia npm, que el lockfile versiona.
|
|
6
6
|
//
|
|
7
|
-
// Nada de `require`, `__dirname` ni `import
|
|
7
|
+
// Nada de `require`, `__dirname` ni un `import` estático: el shim se instala en el proyecto y hereda el
|
|
8
8
|
// `type` de su `package.json`, así que el mismo archivo se carga como CommonJS en un repo y como
|
|
9
9
|
// ESM en el de al lado. `import()` dinámico y `process.argv[1]` son las dos únicas formas que
|
|
10
10
|
// existen bajo los dos cargadores; cualquier otra revienta en la mitad de los proyectos —`require
|
|
@@ -16,7 +16,7 @@ const root = self.slice(0, self.lastIndexOf('/', self.lastIndexOf('/') - 1)) ||
|
|
|
16
16
|
|
|
17
17
|
// El shim sabe dónde vive; quien lo invoca, no. En modo sidecar se lo llama desde la carpeta de la
|
|
18
18
|
// compañía —`node <empresa>-ops/tools/ops.js …`— y sin esto cada comando resolvería su raíz contra
|
|
19
|
-
// el cwd: `agents list` y `
|
|
19
|
+
// el cwd: `agents list` y `flow list` devolvían vacío en vez de fallar.
|
|
20
20
|
process.env.OPS_ROOT = process.env.OPS_ROOT || root
|
|
21
21
|
|
|
22
22
|
// El especificador se resuelve contra este archivo, así que sube a `<raíz>/node_modules` igual que
|