@ingeniomaps/cauce 0.55.0 → 0.57.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 +46 -0
- package/README.md +7 -3
- package/engine/automation/config.js +15 -19
- package/engine/automation/index.js +9 -5
- package/engine/cli/dependency.js +73 -0
- package/engine/cli/instance.js +72 -88
- package/engine/cli/ops.js +12 -15
- package/engine/cli/planning.js +7 -1
- package/engine/cli/wiring.js +2 -1
- package/engine/core/onboarding.js +6 -2
- package/engine/planning/contracts.js +14 -0
- package/package.json +1 -1
- package/template/AGENTS.md +12 -15
- package/template/Makefile +1 -1
- package/template/organization/README.md +2 -0
- package/template/organization/workspace.md +34 -0
- package/template/planning/rules/README.md +6 -0
package/CHANGELOG.md
CHANGED
|
@@ -14,6 +14,52 @@ desde este repositorio no va, porque el que lee no puede actuar sobre eso. Cuand
|
|
|
14
14
|
unas pocas líneas casi siempre es porque cuenta cómo se descubrió el problema o por qué se eligió el
|
|
15
15
|
diseño — eso vive en el commit y en el código.
|
|
16
16
|
|
|
17
|
+
## [0.57.0] - 2026-09-03
|
|
18
|
+
|
|
19
|
+
### Agregado
|
|
20
|
+
|
|
21
|
+
- **`organization/workspace.md`: dónde va lo que sólo sabe tu proyecto.** El mapa real, las
|
|
22
|
+
integraciones con su entorno concreto y las excepciones de autonomía. Es tuyo y `upgrade` no lo toca.
|
|
23
|
+
Hasta ahora el paso 2 del README te mandaba completar `AGENTS.md`, que es del toolkit y se reemplaza
|
|
24
|
+
entero: no perdías nada —desde 0.55.0 el `upgrade` se detiene y lo nombra— pero te tocaba fusionar a
|
|
25
|
+
mano en cada versión, sobre el único archivo garantizado a entrar en conflicto. Es la separación que
|
|
26
|
+
el toolkit ya usa en `planning/rules/system/` y en `planning/delivery/project.md`.
|
|
27
|
+
|
|
28
|
+
**Si ya llenaste tu `AGENTS.md`**: movés esas secciones a `organization/workspace.md` y listo. El
|
|
29
|
+
`upgrade` te lo dice cuando se detenga, antes de que puedas descartarlas con `--force`.
|
|
30
|
+
|
|
31
|
+
### Corregido
|
|
32
|
+
|
|
33
|
+
- **`check` te dice qué reglas deja de regir un override.** Un archivo propio con el nombre de uno de
|
|
34
|
+
`system/` lo reemplaza entero, no las reglas que mencionás: las que ese archivo definía y el tuyo no
|
|
35
|
+
redefine dejan de existir para tu proyecto. La advertencia nombraba el par de archivos y se leía como
|
|
36
|
+
benigna. El caso caro es una regla que el motor **sigue exigiendo** —R17 lo hace—: quedaba exigida sin
|
|
37
|
+
estar escrita en ningún lado. Ahora dice cuáles: `deja de regir R2, R17…`. Sigue siendo advertencia.
|
|
38
|
+
|
|
39
|
+
- **Instalar un runner ya no hace que `upgrade` te acuse de editar `AGENTS.md`.** En modo `embedded`,
|
|
40
|
+
`automation install` con Codex escribe sus instrucciones dentro de ese archivo, entre marcas. El
|
|
41
|
+
registro anotaba el bloque en un solo lado, así que el `upgrade` siguiente se detenía sobre un archivo
|
|
42
|
+
que nadie había tocado —lo había escrito el comando que el README manda correr justo después—. Lo que
|
|
43
|
+
vos escribas alrededor se sigue detectando igual.
|
|
44
|
+
|
|
45
|
+
## [0.56.0] - 2026-09-03
|
|
46
|
+
|
|
47
|
+
### Corregido
|
|
48
|
+
|
|
49
|
+
- **El upgrade que documentaba el README rompía el pin exacto que puso `init`.** La razón para no
|
|
50
|
+
saltear su primer paso es que «`init` fija la versión exacta, así que `npm update` no la mueve», y
|
|
51
|
+
el comando de al lado la desarmaba: npm guarda con caret por defecto, así que
|
|
52
|
+
`npm install --save-dev @ingeniomaps/cauce@latest` dejaba `^0.55.0`, y dentro de `0.x` ese caret
|
|
53
|
+
alcanza a los patches —que Cauce publica—. Un `npm install` en otra máquina podía dejar el motor en
|
|
54
|
+
una versión que `ops.config.json` no decía.
|
|
55
|
+
|
|
56
|
+
Se arregla por los dos lados. El comando lleva `--save-exact` en los tres lugares que lo dictan —el
|
|
57
|
+
README, el `make upgrade` de tu instancia y la salida de `upgrade --check`—, y además **`upgrade`
|
|
58
|
+
repone la versión exacta al terminar**, así que una instancia que ya quedó con un rango se repara
|
|
59
|
+
sola en la próxima actualización. **Qué cambia para vos**: `upgrade` puede tocar ahora una línea de
|
|
60
|
+
tu `package.json`, y lo dice cuando lo hace. El resto del manifiesto queda como estaba, y una
|
|
61
|
+
instancia sin `package.json` no recibe uno.
|
|
62
|
+
|
|
17
63
|
## [0.55.0] - 2026-09-02
|
|
18
64
|
|
|
19
65
|
### Corregido
|
package/README.md
CHANGED
|
@@ -224,7 +224,9 @@ equivalentes.
|
|
|
224
224
|
Después de inicializar:
|
|
225
225
|
|
|
226
226
|
1. Edita `ops.config.json`: nombre, modo y raíces de código.
|
|
227
|
-
2. Completa `
|
|
227
|
+
2. Completa `organization/workspace.md`: el mapa real, las integraciones con su entorno y las
|
|
228
|
+
excepciones de autonomía. Es del proyecto y `upgrade` no lo toca; `AGENTS.md`, en cambio, lo
|
|
229
|
+
mantiene Cauce entero.
|
|
228
230
|
3. Completa `organization/company.md` y `organization/product.md` con el contexto estable de la empresa.
|
|
229
231
|
4. Copia `planning/roadmap/epic-000-template.md` a `epic-001-<slug>.md`.
|
|
230
232
|
5. Ejecuta `node tools/ops.js check planning` antes de activar cualquier runner.
|
|
@@ -266,13 +268,15 @@ existente detiene el `upgrade` antes de pisarlo.
|
|
|
266
268
|
Son tres pasos y `make upgrade` hace los dos primeros:
|
|
267
269
|
|
|
268
270
|
```bash
|
|
269
|
-
npm install --save-dev @ingeniomaps/cauce@latest # trae el motor nuevo
|
|
271
|
+
npm install --save-exact --save-dev @ingeniomaps/cauce@latest # trae el motor nuevo
|
|
270
272
|
node tools/ops.js upgrade . # aplica system/ y el runtime
|
|
271
273
|
node tools/ops.js automation install . claude # el wiring del runner
|
|
272
274
|
```
|
|
273
275
|
|
|
274
276
|
El primero no se puede saltear: `init` fija la versión exacta, así que `npm update` no la mueve y
|
|
275
|
-
`upgrade` compara contra el motor instalado —lo dice en su salida—.
|
|
277
|
+
`upgrade` compara contra el motor instalado —lo dice en su salida—. Va con `--save-exact` porque npm
|
|
278
|
+
guarda con caret por defecto, y ese caret es justamente lo que volvería falsa la frase anterior; si
|
|
279
|
+
alguna vez se instaló sin él, `upgrade` repone la versión exacta al terminar. El tercero tampoco: los workflows y
|
|
276
280
|
las skills viven en el runner, no en la instancia, y `upgrade` no los toca. `upgrade` lo recuerda al
|
|
277
281
|
terminar.
|
|
278
282
|
|
|
@@ -32,21 +32,17 @@ function mergeConfig(current, incoming) {
|
|
|
32
32
|
return incoming
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
// Una entrada de hook que puso Cauce se reconoce por el guard al que apunta, y lo nuestro es lo que
|
|
36
|
-
// efectivamente entregamos, no todo lo que vive en nuestra carpeta. Reconocer por directorio
|
|
37
|
-
// desregistraba el guard propio del proyecto en cada reinstalación: `template/AGENTS.md` invita a
|
|
38
|
-
// ponerlo justo ahí y promete que sobrevive a cada actualización, el archivo quedaba en disco, y lo
|
|
39
|
-
// único que se veía era una línea diciendo que se había quitado una entrada obsoleta.
|
|
40
|
-
// Se saca del archivo del usuario antes de fusionar para que el merge deje exactamente las de esta
|
|
41
|
-
// versión, ni una más.
|
|
42
35
|
const HOOK_PATH = /automatization\/hooks\/([a-z0-9-]+\.sh)(?:\s|$)/
|
|
43
36
|
|
|
44
|
-
//
|
|
45
|
-
//
|
|
46
|
-
//
|
|
47
|
-
//
|
|
48
|
-
//
|
|
49
|
-
//
|
|
37
|
+
// Si una entrada de hook la puso Cauce. Lo nuestro es lo que efectivamente entregamos, no todo lo que
|
|
38
|
+
// vive en nuestra carpeta: `template/AGENTS.md` invita al proyecto a poner su guard justo ahí y le
|
|
39
|
+
// promete que sobrevive a cada actualización, así que reconocer por directorio lo desregistraba en
|
|
40
|
+
// cada reinstalación —el archivo quedaba en disco y el guard dejaba de correr—.
|
|
41
|
+
//
|
|
42
|
+
// Por eso son dos fuentes y no una. `expectedHooks()` es lo que entrega el motor de hoy, y alcanza
|
|
43
|
+
// para una instancia que nunca instaló; `delivered` es lo que ésta registró haber recibido, y es lo
|
|
44
|
+
// único que reconoce un guard que entregamos en una versión y retiramos en la siguiente, cuyo nombre
|
|
45
|
+
// ya no está en la primera. Un guard del proyecto no está en ninguna de las dos, y sobrevive.
|
|
50
46
|
function isDelivered(command, delivered) {
|
|
51
47
|
const hit = String(command).match(HOOK_PATH)
|
|
52
48
|
if (!hit) return false
|
|
@@ -58,6 +54,8 @@ function deliveredHookCommands(commands) {
|
|
|
58
54
|
return [...commands].filter((command) => HOOK_PATH.test(String(command)))
|
|
59
55
|
}
|
|
60
56
|
|
|
57
|
+
// Saca del archivo del usuario lo que pusimos nosotros, para que el merge deje exactamente las
|
|
58
|
+
// entradas de esta versión y ni una más.
|
|
61
59
|
function withoutDeliveredHooks(config, live, delivered = new Set()) {
|
|
62
60
|
const dropped = []
|
|
63
61
|
const walk = (node) => {
|
|
@@ -105,12 +103,10 @@ function reportRemoved(name, dropped, live, output) {
|
|
|
105
103
|
function includesConfig(actual, expected) {
|
|
106
104
|
if (Array.isArray(expected)) {
|
|
107
105
|
return Array.isArray(actual) && expected.every((item) => actual.some((value) => {
|
|
108
|
-
// Un grupo de hooks se compara por su contenido
|
|
109
|
-
//
|
|
110
|
-
// con un hook de más
|
|
111
|
-
//
|
|
112
|
-
// diferencia real— no se miraba nunca. La instalación promete conservar lo que el proyecto ya
|
|
113
|
-
// tenía, así que sumar un hook propio a un grupo nuestro es exactamente lo que permite.
|
|
106
|
+
// Un grupo de hooks se compara por su contenido y no por su forma serializada, porque la
|
|
107
|
+
// instalación promete conservar lo que el proyecto ya tenía: con el ítem entero como unidad, un
|
|
108
|
+
// grupo con un hook propio de más contaba como ausente y `doctor` llamaba divergente a una
|
|
109
|
+
// configuración completa.
|
|
114
110
|
if (item && typeof item === 'object' && Array.isArray(item.hooks)) {
|
|
115
111
|
return value && typeof value === 'object'
|
|
116
112
|
&& value.matcher === item.matcher
|
|
@@ -384,8 +384,6 @@ function install(root, name, output = console, options = {}) {
|
|
|
384
384
|
// guard que el runner intenta ejecutar y falla. Se quitan las nuestras y las vuelve a poner el merge.
|
|
385
385
|
const live = new Set((JSON.stringify(incoming).match(/"command":"[^"]*"/g) || [])
|
|
386
386
|
.map((entry) => JSON.parse(`{${entry}}`).command))
|
|
387
|
-
// Lo que este adaptador anotó haber entregado la última vez. Es lo único que reconoce una entrada
|
|
388
|
-
// nuestra cuyo guard el motor ya no trae: por nombre no se distingue de la que agregó el proyecto.
|
|
389
387
|
const previous = new Set(recorded[deliveryKey(name, HOOKS_KEY)] || [])
|
|
390
388
|
const clean = runner.config.owned
|
|
391
389
|
? { config: {}, dropped: [] }
|
|
@@ -399,9 +397,8 @@ function install(root, name, output = console, options = {}) {
|
|
|
399
397
|
}
|
|
400
398
|
output.log(`✓ ${name}: configuración instalada en ${runner.config.target}`)
|
|
401
399
|
const deliveredPaths = { ...recorded }
|
|
402
|
-
//
|
|
403
|
-
//
|
|
404
|
-
// registrarlas nos autorizaría a borrar lo ajeno en la instalación siguiente.
|
|
400
|
+
// Se anota lo que instalamos, no lo que quedó en el archivo: ahí conviven las entradas del proyecto,
|
|
401
|
+
// y registrarlas nos autorizaría a borrar lo ajeno en la instalación siguiente.
|
|
405
402
|
const ourHooks = deliveredHookCommands(live)
|
|
406
403
|
if (ourHooks.length) deliveredPaths[deliveryKey(name, HOOKS_KEY)] = ourHooks
|
|
407
404
|
else delete deliveredPaths[deliveryKey(name, HOOKS_KEY)]
|
|
@@ -417,6 +414,13 @@ function install(root, name, output = console, options = {}) {
|
|
|
417
414
|
output.log(`✓ ${name}: sus instrucciones quedaron dentro de ${resolved.item.target}`)
|
|
418
415
|
}
|
|
419
416
|
deliveredPaths[deliveryKey(name, resolved.item.target)] = M.digest(resolved.target)
|
|
417
|
+
// Y en la otra sección, porque este archivo está en las dos: `AGENTS.md` es del sistema y es
|
|
418
|
+
// donde este runner deja su bloque. Anotarlo sólo acá dejaba a `localChanges` comparando contra
|
|
419
|
+
// el digest previo al bloque, así que el `upgrade` siguiente se detenía echándole a la empresa
|
|
420
|
+
// una edición que había hecho el comando de al lado. Lo que la empresa escriba alrededor cambia
|
|
421
|
+
// el digest igual, así que se sigue viendo.
|
|
422
|
+
const own = path.relative(root, resolved.target).split(path.sep).join('/')
|
|
423
|
+
if (O.SYSTEM_FILES.includes(own)) M.write(root, M.recordPaths(root, [own], M.read(root)))
|
|
420
424
|
continue
|
|
421
425
|
}
|
|
422
426
|
if (status === 'ajeno' && ownFile) {
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
// Cómo una instancia declara el motor en su `package.json`: ponerlo, reponerlo y sacarlo. Vive aparte
|
|
4
|
+
// del ciclo de vida porque su reloj es otro —npm y el versionado, no crear/actualizar/borrar— y porque
|
|
5
|
+
// sus tres consumidores no se solapan: `scaffold` declara, `upgrade` repone y `destroy` saca.
|
|
6
|
+
|
|
7
|
+
const fs = require('node:fs')
|
|
8
|
+
const path = require('node:path')
|
|
9
|
+
const F = require('../core/files')
|
|
10
|
+
const { fail } = require('./io')
|
|
11
|
+
|
|
12
|
+
// Declara el motor como dependencia exacta: el lockfile decide qué versión corre, no una copia.
|
|
13
|
+
// Conserva el manifiesto existente porque el repo anfitrión puede tener el suyo.
|
|
14
|
+
function declareEngine(manifest, version) {
|
|
15
|
+
let pkg = { name: path.basename(path.dirname(manifest)), private: true, version: '0.0.0' }
|
|
16
|
+
if (fs.existsSync(manifest)) {
|
|
17
|
+
try { pkg = JSON.parse(fs.readFileSync(manifest, 'utf8')) } catch (error) {
|
|
18
|
+
fail(`package.json inválido en ${manifest}: ${error.message}`)
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
pkg.devDependencies = { ...pkg.devDependencies, '@ingeniomaps/cauce': version }
|
|
22
|
+
F.atomicWriteJson(manifest, pkg)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Repone la versión exacta que `declareEngine` había dejado, y devuelve la que había si cambió algo.
|
|
26
|
+
//
|
|
27
|
+
// El pin no sobrevive por sí solo: npm guarda con caret, así que el `npm install @latest` del primer
|
|
28
|
+
// paso del upgrade lo convierte en un rango, y con eso deja de valer la razón que el README da para no
|
|
29
|
+
// saltear ese paso —«init fija la versión exacta, así que npm update no la mueve»—. Repararlo acá y no
|
|
30
|
+
// sólo documentar el comando es lo que vuelve la propiedad independiente de cómo se haya instalado.
|
|
31
|
+
//
|
|
32
|
+
// Sin manifiesto no hay nada que reponer: una instancia que no lo tiene resuelve el motor de otra
|
|
33
|
+
// forma, y crearle uno sería otra operación que nadie pidió en un upgrade.
|
|
34
|
+
function pinEngine(root, version) {
|
|
35
|
+
const manifest = path.join(root, 'package.json')
|
|
36
|
+
if (!fs.existsSync(manifest)) return null
|
|
37
|
+
let pkg
|
|
38
|
+
try { pkg = JSON.parse(fs.readFileSync(manifest, 'utf8')) } catch { return 'ilegible' }
|
|
39
|
+
const current = (pkg.devDependencies || {})['@ingeniomaps/cauce']
|
|
40
|
+
if (!current || current === version) return null
|
|
41
|
+
declareEngine(manifest, version)
|
|
42
|
+
return current
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// La inversa exacta de `declareEngine`: saca la clave que puso y nada más. El resto del manifiesto es
|
|
46
|
+
// del repo anfitrión aunque hoy no tenga otra cosa —un `package.json` vacío puede ser lo que alguien
|
|
47
|
+
// escribió para tener scripts— así que el archivo se borra sólo si es idéntico al que `declareEngine`
|
|
48
|
+
// habría creado desde cero, sin dependencias, sin scripts y con su `version: 0.0.0`.
|
|
49
|
+
//
|
|
50
|
+
// Lo que no se toca nunca es `node_modules/` ni el lockfile: los escribe npm, pueden tener dependencias
|
|
51
|
+
// del proyecto y borrarlos por nuestra cuenta destruye trabajo ajeno. Se nombran en la salida, que es
|
|
52
|
+
// la mitad que faltaba: `destroy` decía «tu repositorio queda donde está» y dejaba un `package.json`
|
|
53
|
+
// cuya única dependencia era Cauce. En un repo Rust eso es basura conspicua y nadie avisaba.
|
|
54
|
+
function undeclareEngine(manifest) {
|
|
55
|
+
if (!fs.existsSync(manifest)) return []
|
|
56
|
+
let pkg
|
|
57
|
+
try { pkg = JSON.parse(fs.readFileSync(manifest, 'utf8')) } catch { return [] }
|
|
58
|
+
const dev = pkg.devDependencies || {}
|
|
59
|
+
if (!('@ingeniomaps/cauce' in dev)) return []
|
|
60
|
+
delete dev['@ingeniomaps/cauce']
|
|
61
|
+
pkg.devDependencies = dev
|
|
62
|
+
const ours = !Object.keys(dev).length && !Object.keys(pkg.dependencies || {}).length
|
|
63
|
+
&& !Object.keys(pkg.scripts || {}).length && pkg.private === true && pkg.version === '0.0.0'
|
|
64
|
+
if (ours) {
|
|
65
|
+
fs.rmSync(manifest, { force: true })
|
|
66
|
+
return ['package.json (lo había creado init: sin dependencias ni scripts propios)']
|
|
67
|
+
}
|
|
68
|
+
if (!Object.keys(dev).length) delete pkg.devDependencies
|
|
69
|
+
F.atomicWriteJson(manifest, pkg)
|
|
70
|
+
return ['package.json: se quitó la dependencia del motor y el resto queda como estaba']
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
module.exports = { declareEngine, pinEngine, undeclareEngine }
|
package/engine/cli/instance.js
CHANGED
|
@@ -16,6 +16,7 @@ const P = require('../planning/parser')
|
|
|
16
16
|
const ST = require('../planning/state')
|
|
17
17
|
const A = require('../automation')
|
|
18
18
|
const { fail } = require('./io')
|
|
19
|
+
const { declareEngine, pinEngine, undeclareEngine } = require('./dependency')
|
|
19
20
|
|
|
20
21
|
const PROJECT_ROOT = path.resolve(__dirname, '..', '..')
|
|
21
22
|
|
|
@@ -29,10 +30,8 @@ function providerNames() {
|
|
|
29
30
|
} catch { return [] }
|
|
30
31
|
}
|
|
31
32
|
|
|
32
|
-
// Devuelve, por archivo conservado, el digest de lo que
|
|
33
|
-
//
|
|
34
|
-
// el manifiesto lo registra hasheando el disco queda declarado como entregado por Cauce con contenido
|
|
35
|
-
// que Cauce nunca entregó, y el `upgrade` siguiente no ve ninguna edición local y lo reemplaza.
|
|
33
|
+
// Devuelve, por archivo conservado, el digest de lo que **habría** escrito. Quien registra la entrega
|
|
34
|
+
// necesita esos dos contenidos distintos, y el que quedó en disco no es uno de ellos.
|
|
36
35
|
function copyTemplate(source, target, replacements, force, skip = [], quiet = false) {
|
|
37
36
|
F.assertNoSymlinkPath(path.dirname(target), target)
|
|
38
37
|
fs.mkdirSync(target, { recursive: true })
|
|
@@ -86,47 +85,6 @@ function copyRuntime(source, target, preserve = false, boundary = target, skip =
|
|
|
86
85
|
return preserved
|
|
87
86
|
}
|
|
88
87
|
|
|
89
|
-
// Declara el motor como dependencia exacta: el lockfile decide qué versión corre, no una copia.
|
|
90
|
-
// Conserva el manifiesto existente porque el repo anfitrión puede tener el suyo.
|
|
91
|
-
function declareEngine(manifest, version) {
|
|
92
|
-
let pkg = { name: path.basename(path.dirname(manifest)), private: true, version: '0.0.0' }
|
|
93
|
-
if (fs.existsSync(manifest)) {
|
|
94
|
-
try { pkg = JSON.parse(fs.readFileSync(manifest, 'utf8')) } catch (error) {
|
|
95
|
-
fail(`package.json inválido en ${manifest}: ${error.message}`)
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
pkg.devDependencies = { ...pkg.devDependencies, '@ingeniomaps/cauce': version }
|
|
99
|
-
F.atomicWriteJson(manifest, pkg)
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// La inversa exacta de `declareEngine`: saca la clave que puso y nada más. El resto del manifiesto es
|
|
103
|
-
// del repo anfitrión aunque hoy no tenga otra cosa —un `package.json` vacío puede ser lo que alguien
|
|
104
|
-
// escribió para tener scripts— así que el archivo se borra sólo si es idéntico al que `declareEngine`
|
|
105
|
-
// habría creado desde cero, sin dependencias, sin scripts y con su `version: 0.0.0`.
|
|
106
|
-
//
|
|
107
|
-
// Lo que no se toca nunca es `node_modules/` ni el lockfile: los escribe npm, pueden tener dependencias
|
|
108
|
-
// del proyecto y borrarlos por nuestra cuenta destruye trabajo ajeno. Se nombran en la salida, que es
|
|
109
|
-
// la mitad que faltaba: `destroy` decía «tu repositorio queda donde está» y dejaba un `package.json`
|
|
110
|
-
// cuya única dependencia era Cauce. En un repo Rust eso es basura conspicua y nadie avisaba.
|
|
111
|
-
function undeclareEngine(manifest) {
|
|
112
|
-
if (!fs.existsSync(manifest)) return []
|
|
113
|
-
let pkg
|
|
114
|
-
try { pkg = JSON.parse(fs.readFileSync(manifest, 'utf8')) } catch { return [] }
|
|
115
|
-
const dev = pkg.devDependencies || {}
|
|
116
|
-
if (!('@ingeniomaps/cauce' in dev)) return []
|
|
117
|
-
delete dev['@ingeniomaps/cauce']
|
|
118
|
-
pkg.devDependencies = dev
|
|
119
|
-
const ours = !Object.keys(dev).length && !Object.keys(pkg.dependencies || {}).length
|
|
120
|
-
&& !Object.keys(pkg.scripts || {}).length && pkg.private === true && pkg.version === '0.0.0'
|
|
121
|
-
if (ours) {
|
|
122
|
-
fs.rmSync(manifest, { force: true })
|
|
123
|
-
return ['package.json (lo había creado init: sin dependencias ni scripts propios)']
|
|
124
|
-
}
|
|
125
|
-
if (!Object.keys(dev).length) delete pkg.devDependencies
|
|
126
|
-
F.atomicWriteJson(manifest, pkg)
|
|
127
|
-
return ['package.json: se quitó la dependencia del motor y el resto queda como estaba']
|
|
128
|
-
}
|
|
129
|
-
|
|
130
88
|
// El andamiaje de una instancia, sin leer argv. `init` es la cáscara que traduce banderas a esto, y
|
|
131
89
|
// el banco de evaluación lo llama directo: crear una instancia programáticamente no puede depender de
|
|
132
90
|
// cómo venga escrita la línea de comandos.
|
|
@@ -155,10 +113,13 @@ function scaffold(root, { name, mode, force = false, quiet = false }) {
|
|
|
155
113
|
if (fs.existsSync(dir)) deliveredPaths = M.record(root, relative, O.treeFiles(dir), deliveredPaths)
|
|
156
114
|
}
|
|
157
115
|
deliveredPaths = M.recordPaths(root, O.SYSTEM_FILES, deliveredPaths)
|
|
158
|
-
//
|
|
159
|
-
//
|
|
160
|
-
//
|
|
161
|
-
//
|
|
116
|
+
// Adoptar Cauce en un repositorio con contenido es `init --force`, y lo que se conserva ahí lo
|
|
117
|
+
// escribió la empresa. Registrarlo hasheando el disco lo declaraba entregado por Cauce con contenido
|
|
118
|
+
// que Cauce nunca entregó, así que `localChanges` no veía ninguna edición y el `upgrade` siguiente lo
|
|
119
|
+
// reemplazaba: entra con el digest del molde, y esa diferencia es lo que detiene al upgrade.
|
|
120
|
+
//
|
|
121
|
+
// Sólo se pisan claves que ya están: el manifiesto declara lo que Cauce rastrea, y un archivo propio
|
|
122
|
+
// fuera de esa frontera no le incumbe.
|
|
162
123
|
for (const [file, hash] of Object.entries(preserved)) {
|
|
163
124
|
const relative = path.relative(root, file).split(path.sep).join('/')
|
|
164
125
|
if (relative in deliveredPaths) deliveredPaths[relative] = hash
|
|
@@ -301,6 +262,51 @@ function renameTeamsToFlows(root) {
|
|
|
301
262
|
return moved
|
|
302
263
|
}
|
|
303
264
|
|
|
265
|
+
// Qué hacer en vez de haber editado, según a quién pertenece cada archivo que se perdería. Vive
|
|
266
|
+
// aparte de `upgrade` porque su reloj es el de la frontera de propiedad y el de la redacción del
|
|
267
|
+
// consejo, no el del procedimiento que lo imprime, y porque así se puede probar sin tocar el disco.
|
|
268
|
+
//
|
|
269
|
+
// Tres clases distintas, y antes eran dos: todo lo que no vivía bajo `system/` recibía el consejo del
|
|
270
|
+
// runtime, así que editar el protocolo respondía con cómo desactivar un guard. Decirle a alguien la
|
|
271
|
+
// salida ajena lo manda a buscar una configuración que no existe.
|
|
272
|
+
function adviceFor(changed) {
|
|
273
|
+
const ruleFiles = changed.filter((file) => file.includes('/system/'))
|
|
274
|
+
const runtime = changed.filter((file) => !file.includes('/system/')
|
|
275
|
+
&& O.RUNTIME_PATHS.some((base) => file.startsWith(`${base}/`)))
|
|
276
|
+
const docs = changed.filter((file) => !ruleFiles.includes(file) && !runtime.includes(file))
|
|
277
|
+
const advice = []
|
|
278
|
+
if (ruleFiles.length) {
|
|
279
|
+
advice.push(
|
|
280
|
+
'Las ruleFiles y decisiones bajo system/ son del toolkit. Para cambiar una, escribí la tuya al\n'
|
|
281
|
+
+ 'lado con el mismo ID: el proyecto manda y `check` lo reporta como override explícito.',
|
|
282
|
+
)
|
|
283
|
+
}
|
|
284
|
+
if (runtime.length) {
|
|
285
|
+
advice.push(
|
|
286
|
+
'El runtime es del toolkit: en vez de editarlo, agregá lo tuyo al lado con otro nombre —un\n'
|
|
287
|
+
+ 'guard propio sobrevive a cada actualización— y registralo en la configuración de tu runner,\n'
|
|
288
|
+
+ 'que sí es del proyecto. Para desactivar un guard alcanza con quitarlo de esa configuración.',
|
|
289
|
+
)
|
|
290
|
+
}
|
|
291
|
+
if (docs.length) {
|
|
292
|
+
advice.push(
|
|
293
|
+
'Esos docs son del toolkit y no llevan una línea de la empresa: se reemplazan enteros en\n'
|
|
294
|
+
+ 'cada actualización para que las mejoras lleguen. Lo que tu proyecto decide distinto va donde sí\n'
|
|
295
|
+
+ 'es suyo —una ADR propia, una regla propia, o `planning/delivery/project.md` para la entrega—.',
|
|
296
|
+
)
|
|
297
|
+
}
|
|
298
|
+
// `AGENTS.md` se lo gana aparte porque hasta ahora el README mandaba completarlo, así que el consejo
|
|
299
|
+
// genérico de arriba —«no llevan una línea de la empresa»— le miente justo a quien le hizo caso.
|
|
300
|
+
if (docs.includes('AGENTS.md')) {
|
|
301
|
+
advice.push(
|
|
302
|
+
'En `AGENTS.md` en particular: el mapa real, las integraciones y las excepciones de autonomía\n'
|
|
303
|
+
+ 'ahora van en `organization/workspace.md`, que es del proyecto y no se reemplaza. Movelos ahí\n'
|
|
304
|
+
+ 'antes de repetir con --force, o los vas a tener que fusionar de nuevo en cada versión.',
|
|
305
|
+
)
|
|
306
|
+
}
|
|
307
|
+
return advice.join('\n\n')
|
|
308
|
+
}
|
|
309
|
+
|
|
304
310
|
// Actualiza sólo lo que el toolkit declara suyo. Todo lo demás —planning, organization, reglas
|
|
305
311
|
// propias, agentes editados— queda intacto por construcción, no por comparación.
|
|
306
312
|
function upgrade(dir, cli) {
|
|
@@ -328,18 +334,19 @@ function upgrade(dir, cli) {
|
|
|
328
334
|
const overrides = O.overrides(root)
|
|
329
335
|
|
|
330
336
|
if (dry) {
|
|
331
|
-
//
|
|
332
|
-
//
|
|
333
|
-
//
|
|
334
|
-
// que no podía avisar del conflicto, justo en el estado normal entre actualizaciones: `init` fija
|
|
335
|
-
// la versión exacta, así que instancia y motor coinciden casi todo el tiempo.
|
|
337
|
+
// Antes de mirar versiones, porque son dos preguntas distintas —«¿hay algo más nuevo?» y «¿qué
|
|
338
|
+
// tengo editado que se perdería?»— y la segunda tiene respuesta útil aunque la primera sea que no.
|
|
339
|
+
// Adentro del `if` de abajo, el único modo que no toca nada era el único que no podía avisar.
|
|
336
340
|
for (const file of changed) console.log(` editado localmente: ${file}`)
|
|
337
341
|
if (from === to) {
|
|
338
342
|
// Contra el motor instalado, no contra lo publicado: la comparación es local y sin red. Decirlo
|
|
339
343
|
// importa porque `init` fija la versión exacta, así que el motor no se mueve solo y esta línea,
|
|
340
344
|
// a secas, se leía como «no hay nada nuevo» durante todas las versiones siguientes.
|
|
341
345
|
console.log(`= ${to}: la instancia está al día con el motor instalado`)
|
|
342
|
-
|
|
346
|
+
// Con `--save-exact`, sin el cual el caret de npm vuelve falsa la línea de arriba. Por qué, y por
|
|
347
|
+
// qué no alcanza con documentarlo, en `pinEngine`.
|
|
348
|
+
console.log(' para traer una versión más nueva:'
|
|
349
|
+
+ ' npm install --save-exact --save-dev @ingeniomaps/cauce@latest')
|
|
343
350
|
if (changed.length) process.exit(1)
|
|
344
351
|
return
|
|
345
352
|
}
|
|
@@ -368,37 +375,9 @@ function upgrade(dir, cli) {
|
|
|
368
375
|
|
|
369
376
|
if (changed.length && !force) {
|
|
370
377
|
for (const file of changed) console.error(`✗ ${file}`)
|
|
371
|
-
// Tres clases distintas, y antes eran dos: todo lo que no vivía bajo `system/` recibía el consejo
|
|
372
|
-
// del runtime, así que editar el protocolo respondía con cómo desactivar un guard. Cada una tiene
|
|
373
|
-
// su salida y decirle la ajena manda a buscar una configuración que no existe.
|
|
374
|
-
const ruleFiles = changed.filter((file) => file.includes('/system/'))
|
|
375
|
-
const runtime = changed.filter((file) => !file.includes('/system/')
|
|
376
|
-
&& O.RUNTIME_PATHS.some((base) => file.startsWith(`${base}/`)))
|
|
377
|
-
const docs = changed.filter((file) => !ruleFiles.includes(file) && !runtime.includes(file))
|
|
378
|
-
const advice = []
|
|
379
|
-
if (ruleFiles.length) {
|
|
380
|
-
advice.push(
|
|
381
|
-
'Las ruleFiles y decisiones bajo system/ son del toolkit. Para cambiar una, escribí la tuya al\n'
|
|
382
|
-
+ 'lado con el mismo ID: el proyecto manda y `check` lo reporta como override explícito.',
|
|
383
|
-
)
|
|
384
|
-
}
|
|
385
|
-
if (runtime.length) {
|
|
386
|
-
advice.push(
|
|
387
|
-
'El runtime es del toolkit: en vez de editarlo, agregá lo tuyo al lado con otro nombre —un\n'
|
|
388
|
-
+ 'guard propio sobrevive a cada actualización— y registralo en la configuración de tu runner,\n'
|
|
389
|
-
+ 'que sí es del proyecto. Para desactivar un guard alcanza con quitarlo de esa configuración.',
|
|
390
|
-
)
|
|
391
|
-
}
|
|
392
|
-
if (docs.length) {
|
|
393
|
-
advice.push(
|
|
394
|
-
'Esos docs son del toolkit y no llevan una línea de la empresa: se reemplazan enteros en\n'
|
|
395
|
-
+ 'cada actualización para que las mejoras lleguen. Lo que tu proyecto decide distinto va donde sí\n'
|
|
396
|
-
+ 'es suyo —una ADR propia, una regla propia, o `planning/delivery/project.md` para la entrega—.',
|
|
397
|
-
)
|
|
398
|
-
}
|
|
399
378
|
fail(
|
|
400
379
|
`\n${changed.length} archivo(s) que mantiene Cauce fueron editados y se perderían.\n\n` +
|
|
401
|
-
`${
|
|
380
|
+
`${adviceFor(changed)}\n\nSi el cambio ya no te sirve, repetí con --force para descartarlo.`,
|
|
402
381
|
)
|
|
403
382
|
}
|
|
404
383
|
|
|
@@ -446,20 +425,25 @@ function upgrade(dir, cli) {
|
|
|
446
425
|
const config = JSON.parse(fs.readFileSync(path.join(root, 'ops.config.json'), 'utf8'))
|
|
447
426
|
config.cauceVersion = to
|
|
448
427
|
F.atomicWriteJson(path.join(root, 'ops.config.json'), config)
|
|
428
|
+
// Acá es donde las tres versiones se saben a la vez, así que es donde pueden quedar diciendo lo mismo.
|
|
429
|
+
const pinned = pinEngine(root, to)
|
|
449
430
|
|
|
450
431
|
console.log(`✓ Cauce ${from || '(previa)'} → ${to}`)
|
|
451
432
|
// Descartar con --force es legítimo; hacerlo sin dejar rastro no. Queda en la salida del comando,
|
|
452
433
|
// que es la evidencia que el protocolo pide para cualquier cambio.
|
|
453
434
|
for (const file of changed) console.log(`− descartado tu cambio en ${file}`)
|
|
454
435
|
for (const relative of retired) console.log(`− retirado ${relative}: Cauce ya no lo distribuye`)
|
|
436
|
+
// Se dice porque explica un diff en un archivo que la empresa versiona, y que si no aparecería sin
|
|
437
|
+
// autor.
|
|
438
|
+
if (pinned === 'ilegible') console.log(' ⚠ package.json no se pudo leer: su versión quedó como estaba')
|
|
439
|
+
else if (pinned) console.log(` package.json: ${pinned} → ${to}, la versión exacta que acabás de aplicar`)
|
|
455
440
|
printChangelog(from, to)
|
|
456
441
|
console.log(` ${system.length} ruta(s) del sistema y ${O.RUNTIME_PATHS.length} del runtime actualizadas`)
|
|
457
442
|
for (const override of overrides) {
|
|
458
443
|
console.log(`= conservado ${override.collection}/${override.project}: sobrescribe ${override.system}`)
|
|
459
444
|
}
|
|
460
|
-
// Sólo cuando es cierto
|
|
461
|
-
//
|
|
462
|
-
// todo lo propio quedó intacto contradice a la única señal que recibe quien corrió el comando.
|
|
445
|
+
// Sólo cuando es cierto: llegar acá con algo en `changed` es haber descartado contenido de la
|
|
446
|
+
// empresa con --force, que las líneas de arriba enumeran.
|
|
463
447
|
if (!changed.length) console.log(' planning, organization y todo lo propio quedaron intactos')
|
|
464
448
|
// No se borra: sin la dependencia declarada, quitarle `.ops/` la dejaría sin motor. Se avisa y
|
|
465
449
|
// decide una persona.
|
|
@@ -481,4 +465,4 @@ function upgrade(dir, cli) {
|
|
|
481
465
|
for (const entry of FK.drift(root)) console.log(` ⚠ ${FK.driftLine(entry)}`)
|
|
482
466
|
}
|
|
483
467
|
|
|
484
|
-
module.exports = { copyTemplate, scaffold, providerNames, upgrade, destroy, PROJECT_ROOT }
|
|
468
|
+
module.exports = { copyTemplate, scaffold, providerNames, adviceFor, upgrade, destroy, PROJECT_ROOT }
|
package/engine/cli/ops.js
CHANGED
|
@@ -16,21 +16,20 @@ const BOOT = require('./bootstrap')
|
|
|
16
16
|
// Dónde aterriza una instancia cuando nadie eligió: una carpeta propia junto al código.
|
|
17
17
|
const DEFAULT_TARGET = 'ops'
|
|
18
18
|
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
// instancia; no hay una segunda adentro.
|
|
23
|
-
function implicitTarget(cwd) {
|
|
24
|
-
return isInstanceDir(cwd) ? '.' : DEFAULT_TARGET
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// La misma heurística, aplicada al destino ya resuelto: una carpeta que ya nombra al toolkit **es** la
|
|
28
|
-
// instancia, y el código de la empresa vive al lado.
|
|
19
|
+
// Una carpeta que ya nombra al toolkit **es** la instancia; no hay una segunda adentro, y el código de
|
|
20
|
+
// la empresa vive al lado. Es la única regla, y de ella salen las dos decisiones que `init` toma sin
|
|
21
|
+
// que nadie las escriba: dónde aterriza y en qué modo.
|
|
29
22
|
function isInstanceDir(dir) {
|
|
30
23
|
const base = path.basename(dir)
|
|
31
24
|
return base === DEFAULT_TARGET || base.endsWith('-ops')
|
|
32
25
|
}
|
|
33
26
|
|
|
27
|
+
// Parada frecuente: el dev ya creó `acme-ops/` y corre `init` adentro. Sin esto la instancia caía en
|
|
28
|
+
// `acme-ops/ops/` y el proyecto quedaba llamándose «acme-ops».
|
|
29
|
+
function implicitTarget(cwd) {
|
|
30
|
+
return isInstanceDir(cwd) ? '.' : DEFAULT_TARGET
|
|
31
|
+
}
|
|
32
|
+
|
|
34
33
|
// El nombre sale de la carpeta del proyecto, no de la que aloja la instancia: `ops/` y `acme-ops/`
|
|
35
34
|
// nombran al toolkit, y quien lee `project` en la configuración espera leer «acme».
|
|
36
35
|
function defaultName(root) {
|
|
@@ -79,11 +78,9 @@ async function init(target, cli) {
|
|
|
79
78
|
// install` ya asume —el wiring del runner va al padre, donde se abre la herramienta—, así que lo
|
|
80
79
|
// único que faltaba era que fuera lo que pasa cuando no se elige nada.
|
|
81
80
|
const root = path.resolve(target || implicitTarget(process.cwd()))
|
|
82
|
-
//
|
|
83
|
-
//
|
|
84
|
-
//
|
|
85
|
-
// `guard-workspace-boundary` deja de reconocer los repos hermanos y el wiring del runner se escribe
|
|
86
|
-
// adentro en vez de al lado, donde el dev abre su herramienta.
|
|
81
|
+
// Del destino resuelto y no de si alguien escribió el argumento: `init` e `init .` apuntan al mismo
|
|
82
|
+
// directorio y elegían modos opuestos, así que escribir el punto daba el layout contrario al de
|
|
83
|
+
// arriba sin que nadie lo pidiera.
|
|
87
84
|
const mode = cli.value('--mode', isInstanceDir(root) ? 'sidecar' : 'embedded')
|
|
88
85
|
if (!['embedded', 'sidecar'].includes(mode)) fail('--mode debe ser embedded o sidecar.', 2)
|
|
89
86
|
const name = cli.value('--name', defaultName(root))
|
package/engine/cli/planning.js
CHANGED
|
@@ -80,7 +80,13 @@ function check(dir, cli) {
|
|
|
80
80
|
// Sobrescribir una entrada de system/ es legítimo y esperado; lo que no puede pasar es que
|
|
81
81
|
// ocurra en silencio, porque esa entrada deja de recibir las mejoras del toolkit.
|
|
82
82
|
for (const override of O.overrides(path.resolve(root, '..'))) {
|
|
83
|
-
|
|
83
|
+
// Y con qué se queda el proyecto: un override sano redefine lo que reemplaza, y el que deja IDs
|
|
84
|
+
// afuera los retira sin decirlo. Nombrarlos es lo único que separa una decisión de un descuido.
|
|
85
|
+
const retired = override.collection === 'planning/rules'
|
|
86
|
+
? PC.retiredByOverride(root, override.project)
|
|
87
|
+
: []
|
|
88
|
+
warnings.push(`${override.collection}/${override.project} sobrescribe ${override.system} `
|
|
89
|
+
+ `(override explícito)${retired.length ? `; deja de regir ${retired.join(', ')}` : ''}`)
|
|
84
90
|
}
|
|
85
91
|
// Misma regla para los cargos, que es donde más caro sale: un fork se hace una vez y se olvida.
|
|
86
92
|
const FK = require('../agents/fork')
|
package/engine/cli/wiring.js
CHANGED
|
@@ -179,7 +179,8 @@ function onboard(rootArg, cli, runner = '') {
|
|
|
179
179
|
console.log(runner
|
|
180
180
|
? `\n→ Abrí ${runner} acá y contestale esa pregunta.`
|
|
181
181
|
: '\n→ Contestá esa pregunta cuando corras el arranque.')
|
|
182
|
-
console.log(' Con tus respuestas escribe organization/, el mapa real
|
|
182
|
+
console.log(' Con tus respuestas escribe organization/, el mapa real en'
|
|
183
|
+
+ ' organization/workspace.md y la primera épica.')
|
|
183
184
|
}
|
|
184
185
|
|
|
185
186
|
// El registro de proveedores, leído igual por todos los que lo tocan. `list` lo parseaba suelto y un
|
|
@@ -74,7 +74,7 @@ function guide(root, services = []) {
|
|
|
74
74
|
function missingSections(root) {
|
|
75
75
|
const warnings = []
|
|
76
76
|
const template = path.join(PACKAGE_ROOT, 'template', 'organization')
|
|
77
|
-
for (const name of ['company.md', 'product.md', 'domains.md']) {
|
|
77
|
+
for (const name of ['company.md', 'product.md', 'domains.md', 'workspace.md']) {
|
|
78
78
|
const written = path.join(root, 'organization', name)
|
|
79
79
|
if (!fs.existsSync(written) || !fs.existsSync(path.join(template, name))) continue
|
|
80
80
|
const headings = (text) => new Set((text.match(/^##\s+(.+)$/gm) || []).map((line) => line.trim()))
|
|
@@ -99,7 +99,11 @@ function missingSections(root) {
|
|
|
99
99
|
// Sólo cuando la instancia ya tiene contexto escrito: antes del arranque no hay dónde estuvieran.
|
|
100
100
|
function orphanCredentials(root) {
|
|
101
101
|
if (guide(root).fresh) return []
|
|
102
|
-
|
|
102
|
+
// El mapa del proyecto vive en `organization/workspace.md` desde que `AGENTS.md` pasó a ser del
|
|
103
|
+
// toolkit entero; el viejo se sigue mirando porque una instancia anterior lo tiene ahí y una
|
|
104
|
+
// credencial ya declarada no debería volver a reportarse como huérfana por haber mudado el archivo.
|
|
105
|
+
const contracts = [path.join('organization', 'workspace.md'), 'AGENTS.md',
|
|
106
|
+
path.join('planning', 'HUMAN_ACTIONS.md')]
|
|
103
107
|
.map((file) => { try { return fs.readFileSync(path.join(root, file), 'utf8') } catch { return '' } })
|
|
104
108
|
.join('\n')
|
|
105
109
|
if (!contracts) return []
|
|
@@ -172,6 +172,19 @@ function ruleIds(file) {
|
|
|
172
172
|
return [...P.read(file).matchAll(/^##\s+([A-Z]\d+)\s+[—-]/gm)].map((match) => match[1])
|
|
173
173
|
}
|
|
174
174
|
|
|
175
|
+
// Qué IDs deja de regir un override por nombre: los que definía el archivo del sistema y el propio no
|
|
176
|
+
// redefine. Reemplazar el archivo entero es la función del override y está documentada; lo que no se
|
|
177
|
+
// veía es la consecuencia, porque la advertencia nombraba el par de archivos y no la diferencia. El
|
|
178
|
+
// caso caro es una regla que el motor sigue exigiendo —R17 lo hace—: queda exigida y sin estar escrita
|
|
179
|
+
// en ningún lado, y quien la vea fallar la va a buscar en `rules/`, donde ya no está.
|
|
180
|
+
function retiredByOverride(dir, name) {
|
|
181
|
+
const rules = path.join(dir, 'rules')
|
|
182
|
+
const system = path.join(rules, 'system', name)
|
|
183
|
+
if (!fs.existsSync(system)) return []
|
|
184
|
+
const redefined = ruleIds(path.join(rules, name))
|
|
185
|
+
return ruleIds(system).filter((id) => !redefined.includes(id))
|
|
186
|
+
}
|
|
187
|
+
|
|
175
188
|
function validateRules(dir) {
|
|
176
189
|
const rules = path.join(dir, 'rules')
|
|
177
190
|
const owner = new Map()
|
|
@@ -418,6 +431,7 @@ module.exports = {
|
|
|
418
431
|
oversizedUnits,
|
|
419
432
|
validateAdr,
|
|
420
433
|
validateRules,
|
|
434
|
+
retiredByOverride,
|
|
421
435
|
validateBacklogStructure,
|
|
422
436
|
validCommitTrace,
|
|
423
437
|
validDecisionTrace,
|
package/package.json
CHANGED
package/template/AGENTS.md
CHANGED
|
@@ -2,24 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
Este archivo gobierna el qué y el cuándo. `planning/PROTOCOL.md` gobierna el flujo y
|
|
4
4
|
`planning/rules/` el cómo: sus reglas rigen cada tarea y se leen antes de empezar, no cuando algo sale
|
|
5
|
-
mal.
|
|
6
|
-
|
|
5
|
+
mal. Los tres los mantiene Cauce y valen para cualquier proyecto. Lo que este proyecto tiene de propio
|
|
6
|
+
—su mapa, sus integraciones, hasta dónde llega la autonomía acá— vive en `organization/workspace.md`.
|
|
7
7
|
|
|
8
|
-
##
|
|
8
|
+
## Qué sabe este proyecto y no este archivo
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
El mapa real —qué se construye, qué repos existen, qué está fuera de alcance—, las integraciones con su
|
|
11
|
+
entorno concreto y las excepciones de autonomía viven en **`organization/workspace.md`**, que es del
|
|
12
|
+
proyecto y `upgrade` no toca. Acá no: este archivo lo mantiene Cauce y se reemplaza entero en cada
|
|
13
|
+
actualización, así que lo que escribieras se perdería o te haría fusionar a mano todas las veces.
|
|
11
14
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
- Dónde documenta cada servicio sus comandos de test, lint y build.
|
|
15
|
-
- Qué directorios son legacy o están fuera de alcance.
|
|
16
|
-
|
|
17
|
-
El mapa no debe duplicar documentación técnica: enlaza a la fuente de verdad de cada servicio.
|
|
18
|
-
|
|
19
|
-
## Integraciones y ambientes
|
|
20
|
-
|
|
21
|
-
R12 fija el trato con todo sistema externo: real y de sólo lectura mientras no se lo nombre como
|
|
22
|
-
sandbox. Las excepciones de este proyecto —y sólo ellas— van acá, con el entorno concreto.
|
|
15
|
+
Es la misma separación que `planning/rules/system/` con las reglas propias y que `planning/delivery/`
|
|
16
|
+
con `project.md`: lo que el toolkit mejora, aparte de lo que sólo vos podés escribir.
|
|
23
17
|
|
|
24
18
|
## Cargos disponibles
|
|
25
19
|
|
|
@@ -130,6 +124,9 @@ Registra la acción exacta en `planning/HUMAN_ACTIONS.md` y, si bloquea todo, cr
|
|
|
130
124
|
Nunca amplía el alcance, promueve sus propias ideas, reescribe el proceso durante una tarea, usa
|
|
131
125
|
`git add .`/`git add -A`, hace push/force/amend, ni afirma éxito sin evidencia real.
|
|
132
126
|
|
|
127
|
+
Eso rige sin que nadie escriba nada. Lo que este proyecto amplíe o restrinja va en
|
|
128
|
+
`organization/workspace.md`, con su razón; los cuatro límites del párrafo anterior no se amplían ahí.
|
|
129
|
+
|
|
133
130
|
## Definición de terminado
|
|
134
131
|
|
|
135
132
|
1. La aceptación se observa y queda cubierta por pruebas cuando existe superficie testeable.
|
package/template/Makefile
CHANGED
|
@@ -29,7 +29,7 @@ destroy: ## Muestra qué se pierde al borrar esta instancia (borrar exige --forc
|
|
|
29
29
|
@node tools/ops.js destroy .
|
|
30
30
|
|
|
31
31
|
upgrade: ## Trae la última versión de Cauce y la aplica, sin tocar lo del proyecto
|
|
32
|
-
@npm install --save-dev @ingeniomaps/cauce@latest
|
|
32
|
+
@npm install --save-exact --save-dev @ingeniomaps/cauce@latest
|
|
33
33
|
@node tools/ops.js upgrade .
|
|
34
34
|
|
|
35
35
|
automation-check: ## Comprueba hooks, workflows y adaptadores
|
|
@@ -7,6 +7,8 @@ Con molde, y `check` avisa si a alguno le faltan secciones del original:
|
|
|
7
7
|
- `company.md`: misión, modelo de negocio, objetivos, estructura y derechos de decisión.
|
|
8
8
|
- `product.md`: problema, usuarios, propuesta de valor y límites.
|
|
9
9
|
- `domains.md`: lenguaje ubicuo, dominios y dueños.
|
|
10
|
+
- `workspace.md`: el mapa real, las integraciones con su entorno y las excepciones de autonomía.
|
|
11
|
+
Es lo que `AGENTS.md` no puede llevar: ese archivo lo mantiene Cauce y se reemplaza al actualizar.
|
|
10
12
|
|
|
11
13
|
Recomendados, sin molde: escribilos con la forma que le sirva a este proyecto.
|
|
12
14
|
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Este workspace
|
|
2
|
+
|
|
3
|
+
Lo que sólo sabe este proyecto: qué hay, contra qué se conecta y hasta dónde puede decidir solo un
|
|
4
|
+
runner. `AGENTS.md` gobierna el qué y el cuándo para cualquier proyecto; esto es lo que cambia entre
|
|
5
|
+
uno y otro, y por eso vive acá, donde Cauce no escribe: `upgrade` no lo toca nunca.
|
|
6
|
+
|
|
7
|
+
## Mapa real
|
|
8
|
+
|
|
9
|
+
Completar antes de la primera tarea:
|
|
10
|
+
|
|
11
|
+
- Qué producto se construye.
|
|
12
|
+
- Qué repos o servicios existen y dónde viven.
|
|
13
|
+
- Dónde documenta cada servicio sus comandos de test, lint y build.
|
|
14
|
+
- Qué directorios son legacy o están fuera de alcance.
|
|
15
|
+
|
|
16
|
+
El mapa no debe duplicar documentación técnica: enlaza a la fuente de verdad de cada servicio.
|
|
17
|
+
|
|
18
|
+
## Integraciones y ambientes
|
|
19
|
+
|
|
20
|
+
R12 fija el trato con todo sistema externo: real y de producción mientras no se lo nombre como sandbox.
|
|
21
|
+
Las excepciones de este proyecto —y sólo ellas— van acá, nombrando el entorno concreto.
|
|
22
|
+
|
|
23
|
+
Las credenciales que cada integración necesita se nombran acá o en `planning/HUMAN_ACTIONS.md`, con
|
|
24
|
+
quién las carga y dónde: `check` avisa cuando el proyecto declara una variable que no aparece en
|
|
25
|
+
ninguno de los dos, porque una credencial sin dueño no rompe nada hasta el día del despliegue.
|
|
26
|
+
|
|
27
|
+
## Excepciones de autonomía
|
|
28
|
+
|
|
29
|
+
Los límites que rigen sin escribir nada están en `AGENTS.md`, y son los mismos para todo proyecto. Acá
|
|
30
|
+
va lo que este proyecto amplía o restringe, con su razón: un servicio donde el runner no puede tocar
|
|
31
|
+
migraciones, un repo donde sí puede commitear directo, un entorno de pruebas que es seguro escribir.
|
|
32
|
+
|
|
33
|
+
Lo que no se puede ampliar acá: promover trabajo propio, prometer fechas, inventar evidencia o exceder
|
|
34
|
+
la autoridad de un cargo. Eso no depende del proyecto.
|
|
@@ -19,4 +19,10 @@ fuera de él para que no existan dos reglas con el mismo nombre.
|
|
|
19
19
|
Para cambiar una regla del sistema, escribí la tuya **con el mismo nombre de archivo**: ahí redefinir
|
|
20
20
|
sus números es la función, la del proyecto manda, y `check` reporta el override en vez de fallar.
|
|
21
21
|
|
|
22
|
+
Lo que reemplaza es **el archivo entero, no las reglas que mencionás**: las que ese archivo del sistema
|
|
23
|
+
definía y el tuyo no redefine dejan de regir para tu proyecto. Alguna la sigue exigiendo el motor —R17
|
|
24
|
+
lo hace—, y ahí queda exigida sin estar escrita en ningún lado. Por eso `check` te dice cuáles son
|
|
25
|
+
—«deja de regir R2, R17…»— y por eso conviene, antes de sobrescribir un archivo, mirar si lo tuyo era
|
|
26
|
+
una regla nueva: si lo era, va acá al lado como `P1..Pn` y no se lleva nada puesto.
|
|
27
|
+
|
|
22
28
|
Las convenciones específicas de lenguaje viven junto al servicio que usa ese lenguaje.
|