@ingeniomaps/cauce 0.52.0 → 0.53.1

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 (75) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/agents/roles/system/backend-engineer/learning/sources.yaml +2 -2
  3. package/agents/roles/system/community-manager/learning/sources.yaml +2 -2
  4. package/agents/roles/system/content-specialist/learning/sources.yaml +1 -1
  5. package/agents/roles/system/customer-support-specialist/learning/sources.yaml +1 -1
  6. package/agents/roles/system/data-analyst/learning/sources.yaml +2 -2
  7. package/agents/roles/system/data-governance-steward/learning/sources.yaml +1 -1
  8. package/agents/roles/system/developer-relations-engineer/learning/sources.yaml +3 -3
  9. package/agents/roles/system/devops-engineer/learning/sources.yaml +1 -1
  10. package/agents/roles/system/financial-controller/learning/sources.yaml +1 -1
  11. package/agents/roles/system/frontend-engineer/learning/sources.yaml +1 -1
  12. package/agents/roles/system/growth-marketer/learning/sources.yaml +2 -2
  13. package/agents/roles/system/integrations-engineer/learning/sources.yaml +6 -6
  14. package/agents/roles/system/mobile-engineer/learning/sources.yaml +1 -1
  15. package/agents/roles/system/people-operations-manager/learning/sources.yaml +1 -1
  16. package/agents/roles/system/privacy-compliance-specialist/learning/sources.yaml +1 -1
  17. package/agents/roles/system/product-marketing-manager/learning/sources.yaml +1 -1
  18. package/agents/roles/system/qa-engineer/learning/sources.yaml +2 -2
  19. package/agents/roles/system/site-reliability-engineer/learning/sources.yaml +1 -1
  20. package/agents/roles/system/software-architect/learning/sources.yaml +2 -2
  21. package/agents/roles/system/solutions-engineer/learning/sources.yaml +2 -2
  22. package/agents/roles/system/technical-writer/learning/sources.yaml +4 -4
  23. package/agents/roles/system/treasury-analyst/learning/sources.yaml +2 -2
  24. package/agents/roles/system/ui-designer/learning/sources.yaml +1 -1
  25. package/agents/roles/system/ux-designer/learning/sources.yaml +1 -1
  26. package/automatization/hooks/guard-dependencies.sh +1 -1
  27. package/automatization/hooks/guard-destructive.sh +1 -1
  28. package/automatization/hooks/guard-engine.sh +1 -1
  29. package/automatization/hooks/guard-generated.sh +1 -1
  30. package/automatization/hooks/guard-git-add.sh +1 -1
  31. package/automatization/hooks/guard-governance.sh +1 -1
  32. package/automatization/hooks/guard-integration-snapshot.sh +1 -1
  33. package/automatization/hooks/guard-migrations.sh +1 -1
  34. package/automatization/hooks/guard-planning-drift.sh +1 -1
  35. package/automatization/hooks/guard-secrets.sh +1 -1
  36. package/automatization/hooks/guard-test-evidence.sh +1 -1
  37. package/automatization/hooks/guard-verify.sh +1 -1
  38. package/automatization/hooks/guard-workspace-boundary.sh +1 -1
  39. package/automatization/hooks/run-hook.sh +3 -2
  40. package/automatization/runners/antigravity/hook.js +13 -14
  41. package/automatization/shared/eval-only.js +3 -3
  42. package/automatization/workflows/agent-eval.js +3 -3
  43. package/automatization/workflows/agent-promote.js +4 -4
  44. package/automatization/workflows/autobuild.js +12 -12
  45. package/automatization/workflows/flow-eval.js +10 -7
  46. package/automatization/workflows/flow.js +9 -10
  47. package/automatization/workflows/onboard.js +4 -5
  48. package/engine/agents/evaluations.js +18 -16
  49. package/engine/agents/learning-files.js +111 -0
  50. package/engine/agents/learning-sources.js +166 -0
  51. package/engine/agents/learning.js +93 -272
  52. package/engine/automation/config.js +175 -0
  53. package/engine/automation/hooks.js +96 -0
  54. package/engine/automation/index.js +17 -431
  55. package/engine/automation/roles.js +72 -0
  56. package/engine/automation/runners.js +162 -0
  57. package/engine/cli/catalog.js +7 -12
  58. package/engine/cli/instance.js +11 -10
  59. package/engine/cli/io.js +1 -1
  60. package/engine/cli/ops.js +12 -10
  61. package/engine/cli/wiring.js +2 -4
  62. package/engine/config/validate.js +2 -1
  63. package/engine/core/frontmatter.js +2 -1
  64. package/engine/core/ownership.js +5 -4
  65. package/engine/core/scan.js +3 -0
  66. package/engine/flows/registry.js +2 -2
  67. package/engine/hooks/files.js +160 -0
  68. package/engine/hooks/input.js +129 -0
  69. package/engine/hooks/run.js +24 -449
  70. package/engine/hooks/shell.js +197 -0
  71. package/engine/integrations/registry.js +2 -0
  72. package/engine/planning/contracts.js +12 -10
  73. package/engine/planning/parser.js +4 -3
  74. package/engine/planning/state.js +2 -1
  75. package/package.json +5 -5
@@ -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
- // tres de las dieciséis decisiones escritas con este modelo se publicaron con el menú intacto. Presentar
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 quince decisiones que existen—.
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. La aceptación escrita en prosa no se cuenta —cuántas condiciones tiene una frase es una lectura,
385
- // y un número inventado ahí sería peor que ninguno—; ésa la mira el review, que para eso está R3.
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 decidir = (what, count, limit) =>
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 juzgar = (unit, what, count, limit) => {
392
- if (count > limit && !unit.noSplit) errors.push(decidir(what, count, limit))
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
- juzgar(epic, `roadmap/${epic.file}: criterios`, epic.criteria.length, R17.epicCriteria)
397
+ judge(epic, `roadmap/${epic.file}: criterios`, epic.criteria.length, R17.epicCriteria)
396
398
  }
397
399
  for (const milestone of milestones) {
398
- juzgar(milestone, `hito ${milestone.slug}: tareas`, milestone.tasks.length, R17.milestoneTasks)
400
+ judge(milestone, `hito ${milestone.slug}: tareas`, milestone.tasks.length, R17.milestoneTasks)
399
401
  for (const task of milestone.tasks) {
400
- juzgar(task, `BACKLOG ${task.slug}: criterios`, task.criteria.length, R17.taskCriteria)
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: la prosa que los
27
- // describe va de menos a más y `check` lo contrasta, porque un carril leído fuera de orden se elige por
28
- // su nombre y no por su criterio.
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+(.+)$`,
@@ -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 del primer hito.
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.52.0",
3
+ "version": "0.53.1",
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/*.test.js",
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
  },