@ingeniomaps/cauce 0.16.1 → 0.17.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 +27 -0
- package/README.md +20 -0
- package/engine/agents/fork.js +152 -0
- package/engine/cli/ops.js +25 -2
- package/engine/core/manifest.js +13 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,33 @@ esa operación sea confiable en vez de sólo cómoda: acá se lee qué cambió a
|
|
|
8
8
|
un cambio en el protocolo, en las reglas del sistema o en un guard es visible para el usuario y sube
|
|
9
9
|
minor aunque no toque una sola línea de código.
|
|
10
10
|
|
|
11
|
+
## [0.17.0] - 2026-08-16
|
|
12
|
+
|
|
13
|
+
### Agregado
|
|
14
|
+
|
|
15
|
+
- **`ops agents fork <cargo>`: una empresa se lleva un cargo del catálogo y lo mantiene desde su
|
|
16
|
+
carpeta.** El tercer camino ya resolvía —un cargo propio con el mismo slug tapa al del sistema, en el
|
|
17
|
+
listado, en `learn` y en el puntero que instala el runner—, pero llegar hasta ahí era copiar a mano, y
|
|
18
|
+
eso sale mal de una forma que no se nota: se agarra el `SKILL.md`, que es lo que se ve, y quedan atrás
|
|
19
|
+
los casos adversariales, las fuentes y el modelo operativo. El cargo responde igual y ya no se puede
|
|
20
|
+
evaluar, sin ningún aviso.
|
|
21
|
+
|
|
22
|
+
No se heredan los informes de aprendizaje, las propuestas ni los veredictos de evaluación. Un veredicto
|
|
23
|
+
pertenece al contrato que lo ganó, y el fork nace para dejar de ser ese contrato; una propuesta
|
|
24
|
+
pendiente arrastraría a la empresa a firmar una decisión que era nuestra.
|
|
25
|
+
|
|
26
|
+
En el toolkit se niega: acá el catálogo se edita, no se lo copia. Dejarlo pasar creaba un duplicado que
|
|
27
|
+
tapaba al original, y el trabajo siguiente se hacía sobre la copia mientras la versión que se publica
|
|
28
|
+
quedaba quieta.
|
|
29
|
+
|
|
30
|
+
- **`check` y `upgrade` avisan cuando el cargo que forkeaste mejoró río arriba.** El mecanismo ya existía
|
|
31
|
+
para ADRs y reglas —«sobrescribir es legítimo; lo que no puede pasar es que ocurra en silencio»— pero
|
|
32
|
+
no cubría los cargos, que es donde más caro sale: un fork se hace una vez y se olvida.
|
|
33
|
+
|
|
34
|
+
Se compara contra los digests guardados al forkear, nunca contra la copia: la copia está editada a
|
|
35
|
+
propósito, así que medir contra ella devolvería «todo cambió» desde el primer ajuste. Editar lo propio
|
|
36
|
+
no dispara nada, y esa mitad es la que decide si el aviso se lee o se ignora.
|
|
37
|
+
|
|
11
38
|
## [0.16.1] - 2026-08-16
|
|
12
39
|
|
|
13
40
|
### Corregido
|
package/README.md
CHANGED
|
@@ -74,6 +74,7 @@ Lee [template/planning/PROTOCOL.md](template/planning/PROTOCOL.md) para el contr
|
|
|
74
74
|
| `ops tree <planning>` | Muestra roadmap, backlog, WIP, inbox y done sin mutar nada. |
|
|
75
75
|
| `ops context <planning>` | Emite el contexto mínimo de la tarea vigente para un runner. |
|
|
76
76
|
| `ops agents list <ops-root>` | Lista los cargos visibles resolviendo la precedencia. |
|
|
77
|
+
| `ops agents fork <cargo>` | Copia un cargo del catálogo a la empresa, que pasa a mantenerlo. |
|
|
77
78
|
| `ops upgrade <ops-root>` | Actualiza `system/` y el runtime sin tocar lo del proyecto. |
|
|
78
79
|
| `ops archive <planning> <NNN>` | Archiva el DONE de una épica cerrada de forma idempotente. |
|
|
79
80
|
| `ops learn <agent>` | Prepara el informe semanal que completa la automatización de Codex. |
|
|
@@ -183,6 +184,25 @@ todas las empresas: investigarla una vez y bien es mejor que repetirla en cada i
|
|
|
183
184
|
Por eso `learn` falla si lo corrés sobre un cargo del catálogo dentro de una instancia: escribiría en
|
|
184
185
|
el paquete y se perdería. El ciclo mensual de aprendizaje tampoco se distribuye — vive sólo acá.
|
|
185
186
|
|
|
187
|
+
#### Quedarse con una versión propia de un cargo del catálogo
|
|
188
|
+
|
|
189
|
+
```bash
|
|
190
|
+
npm run ops -- agents fork product-manager
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
Copia el cargo entero a `agents/roles/<slug>/` y desde ahí lo mantenés vos: `learn`, `evaluate` y el
|
|
194
|
+
puntero que instala el runner pasan a resolver contra tu copia. **Copiarlo a mano no es equivalente**
|
|
195
|
+
—se agarra el `SKILL.md`, que es lo que se ve, y quedan atrás los casos, las fuentes y el modelo
|
|
196
|
+
operativo: el cargo responde igual y ya no se puede evaluar—.
|
|
197
|
+
|
|
198
|
+
Lo que no viaja son los informes de aprendizaje, las propuestas y los veredictos de evaluación. Un
|
|
199
|
+
veredicto pertenece al contrato que lo ganó, y el fork nace para dejar de ser ese contrato.
|
|
200
|
+
|
|
201
|
+
A partir de ahí tu copia deja de recibir las mejoras del catálogo, que es lo que elegiste, pero no en
|
|
202
|
+
silencio: `check` y `upgrade` avisan cuando el original cambia río arriba. Editar tu propia copia no
|
|
203
|
+
dispara nada — se compara contra lo que el catálogo tenía el día del fork, no contra lo que vos
|
|
204
|
+
escribiste después.
|
|
205
|
+
|
|
186
206
|
### Versionado
|
|
187
207
|
|
|
188
208
|
Como `upgrade` reemplaza `system/` sin pedir confirmación, un cambio en el protocolo, en una regla del
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
// Llevarse un cargo del catálogo a la carpeta de la empresa para mantenerlo desde ahí.
|
|
4
|
+
//
|
|
5
|
+
// Existe porque copiar a mano sale mal de una forma que no se nota: quien copia agarra el `SKILL.md`
|
|
6
|
+
// —que es lo que se ve— y deja atrás los casos adversariales, las fuentes de aprendizaje y el modelo
|
|
7
|
+
// operativo. Queda un cargo que responde igual y ya no se puede evaluar, sin ningún aviso.
|
|
8
|
+
//
|
|
9
|
+
// **Qué no se hereda.** Los informes de aprendizaje, las propuestas y los veredictos de evaluación se
|
|
10
|
+
// quedan en el catálogo. No es prolijidad: un veredicto pertenece al contrato que lo ganó, y el fork
|
|
11
|
+
// nace para dejar de ser ese contrato. Heredarlos le daría a la copia una garantía que no rindió, y
|
|
12
|
+
// una propuesta pendiente arrastraría a la empresa a firmar una decisión que era nuestra.
|
|
13
|
+
//
|
|
14
|
+
// Lo que sí se hereda entero es el contrato y todo lo que lo hace verificable: casos, conductas
|
|
15
|
+
// esperadas, fuentes, automatización y referencias. La empresa recibe un cargo evaluable desde el
|
|
16
|
+
// primer minuto, y a partir de ahí lo mantiene.
|
|
17
|
+
|
|
18
|
+
const fs = require('node:fs')
|
|
19
|
+
const path = require('node:path')
|
|
20
|
+
|
|
21
|
+
const catalog = require('./catalog')
|
|
22
|
+
const manifest = require('../core/manifest')
|
|
23
|
+
const ownership = require('../core/ownership')
|
|
24
|
+
|
|
25
|
+
// Se comparan contra la ruta relativa dentro del cargo. `_template.md` sobrevive en las dos carpetas
|
|
26
|
+
// porque es andamiaje del mecanismo, no un artefacto de nadie.
|
|
27
|
+
function inherited(relative) {
|
|
28
|
+
if (/^learning\/(reports|proposals)\//.test(relative)) return path.basename(relative) === '_template.md'
|
|
29
|
+
if (relative.startsWith('evaluations/results/')) return false
|
|
30
|
+
return true
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function tree(dir, prefix = '') {
|
|
34
|
+
const found = []
|
|
35
|
+
let list = []
|
|
36
|
+
try { list = fs.readdirSync(dir, { withFileTypes: true }) } catch { return found }
|
|
37
|
+
for (const entry of list) {
|
|
38
|
+
const relative = prefix ? `${prefix}/${entry.name}` : entry.name
|
|
39
|
+
if (entry.isDirectory()) found.push(...tree(path.join(dir, entry.name), relative))
|
|
40
|
+
else found.push(relative)
|
|
41
|
+
}
|
|
42
|
+
return found.sort()
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// La misma resolución que usa todo lo demás —paquete primero, toolkit después— en vez de deducirla
|
|
46
|
+
// del directorio del motor: la versión que interesa es la del catálogo del que sale la copia.
|
|
47
|
+
function packageVersion(root) {
|
|
48
|
+
const file = ownership.packagePath(root, 'package.json')
|
|
49
|
+
if (!file) return ''
|
|
50
|
+
try { return JSON.parse(fs.readFileSync(file, 'utf8')).version || '' } catch { return '' }
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// La fila deja el límite por escrito: arriba, cómo llegó el contrato a ser lo que es —decisiones que
|
|
54
|
+
// tomamos nosotros—; abajo, lo que decida la empresa. Sin ese renglón, la primera fila propia parece
|
|
55
|
+
// continuación de una conversación ajena.
|
|
56
|
+
function markHistory(target, slug, version, date) {
|
|
57
|
+
const file = path.join(target, 'learning', 'HISTORY.md')
|
|
58
|
+
if (!fs.existsSync(file)) return
|
|
59
|
+
const origen = version ? `del catálogo de Cauce ${version}` : 'del catálogo de Cauce'
|
|
60
|
+
const row = `| ${date} | — | Copiado ${origen} | — | ${slug} pasa a mantenerlo esta empresa |\n`
|
|
61
|
+
const content = fs.readFileSync(file, 'utf8')
|
|
62
|
+
fs.writeFileSync(file, content.endsWith('\n') ? content + row : `${content}\n${row}`)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// `date` entra por parámetro en vez de leerse acá: la salida tiene que ser reproducible en una prueba.
|
|
66
|
+
function fork(root, slug, date) {
|
|
67
|
+
// En el toolkit no hay a quién copiarle: el catálogo se mantiene acá, directo. Dejarlo pasar creaba
|
|
68
|
+
// un duplicado en `agents/roles/` que tapaba al original, y el trabajo siguiente se hacía sobre la
|
|
69
|
+
// copia mientras la versión que se publica quedaba quieta.
|
|
70
|
+
try {
|
|
71
|
+
const config = JSON.parse(fs.readFileSync(path.join(root, 'ops.config.json'), 'utf8'))
|
|
72
|
+
if (config.mode === 'toolkit') {
|
|
73
|
+
throw new Error(`${slug} vive acá: en el toolkit se edita el catálogo, no se lo copia`)
|
|
74
|
+
}
|
|
75
|
+
} catch (error) { if (error instanceof Error && error.message.includes('vive acá')) throw error }
|
|
76
|
+
|
|
77
|
+
const found = catalog.find(root, slug)
|
|
78
|
+
if (!found.system) {
|
|
79
|
+
throw new Error(`${slug} ya lo mantiene esta empresa: ${path.relative(root, found.dir)}`)
|
|
80
|
+
}
|
|
81
|
+
const type = path.basename(path.dirname(path.dirname(found.dir)))
|
|
82
|
+
const target = path.join(root, 'agents', type, slug)
|
|
83
|
+
if (fs.existsSync(target)) throw new Error(`${path.relative(root, target)} ya existe`)
|
|
84
|
+
|
|
85
|
+
const files = tree(found.dir).filter(inherited)
|
|
86
|
+
if (!files.includes('SKILL.md')) throw new Error(`${slug} no tiene SKILL.md: no hay contrato que copiar`)
|
|
87
|
+
|
|
88
|
+
// El digest sale del origen, no de la copia. Es lo que el catálogo tenía al momento del fork, y es
|
|
89
|
+
// contra eso que después se responde «esto mejoró río arriba»; medirlo sobre la copia lo ataría a
|
|
90
|
+
// cualquier cosa que le hagamos acá —empezando por la fila de historial que se escribe abajo—.
|
|
91
|
+
const digests = {}
|
|
92
|
+
for (const relative of files) {
|
|
93
|
+
const from = path.join(found.dir, relative)
|
|
94
|
+
const to = path.join(target, relative)
|
|
95
|
+
fs.mkdirSync(path.dirname(to), { recursive: true })
|
|
96
|
+
fs.copyFileSync(from, to)
|
|
97
|
+
digests[relative] = manifest.digest(from)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const version = packageVersion(root)
|
|
101
|
+
markHistory(target, slug, version, date)
|
|
102
|
+
|
|
103
|
+
const forks = { ...manifest.readForks(root) }
|
|
104
|
+
forks[slug] = { type, version, files: digests }
|
|
105
|
+
manifest.write(root, null, null, forks)
|
|
106
|
+
|
|
107
|
+
return { slug, type, dir: target, files, version, skipped: tree(found.dir).filter((one) => !inherited(one)) }
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Qué cambió en el catálogo desde que la empresa se llevó su copia.
|
|
111
|
+
//
|
|
112
|
+
// El fork es legítimo y esperado —para eso está el comando—, pero deja de recibir las mejoras del
|
|
113
|
+
// toolkit, y eso no puede pasar en silencio: la empresa decidió mantener un cargo, no quedarse sin
|
|
114
|
+
// enterarse de que el original mejoró. Nadie va a comparar 14 archivos a mano en cada actualización.
|
|
115
|
+
//
|
|
116
|
+
// Se compara contra los digests guardados al forkear, nunca contra la copia: la copia está editada a
|
|
117
|
+
// propósito, así que medir contra ella devolvería «todo cambió» desde el primer ajuste. Y el aviso no
|
|
118
|
+
// dice qué hacer —integrar o no es decisión de la empresa—, sólo que hay algo que mirar.
|
|
119
|
+
function drift(root) {
|
|
120
|
+
const forks = manifest.readForks(root)
|
|
121
|
+
const catalogDir = ownership.packageDir(root, 'agents')
|
|
122
|
+
const found = []
|
|
123
|
+
for (const [slug, record] of Object.entries(forks)) {
|
|
124
|
+
if (!record || !record.files) continue
|
|
125
|
+
const source = catalogDir ? path.join(catalogDir, record.type || 'roles', 'system', slug) : ''
|
|
126
|
+
if (!source || !fs.existsSync(source)) continue
|
|
127
|
+
const current = tree(source).filter(inherited)
|
|
128
|
+
const changed = []
|
|
129
|
+
const removed = []
|
|
130
|
+
for (const [relative, recorded] of Object.entries(record.files)) {
|
|
131
|
+
if (!current.includes(relative)) { removed.push(relative); continue }
|
|
132
|
+
if (manifest.digest(path.join(source, relative)) !== recorded) changed.push(relative)
|
|
133
|
+
}
|
|
134
|
+
const added = current.filter((relative) => !(relative in record.files))
|
|
135
|
+
if (changed.length || added.length || removed.length) {
|
|
136
|
+
found.push({ slug, version: record.version || '', changed, added, removed })
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return found.sort((left, right) => left.slug.localeCompare(right.slug))
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Una línea por fork, para que `check` y `upgrade` digan lo mismo con las mismas palabras.
|
|
143
|
+
function driftLine(entry) {
|
|
144
|
+
const parts = []
|
|
145
|
+
if (entry.changed.length) parts.push(`${entry.changed.length} archivo(s) cambiaron`)
|
|
146
|
+
if (entry.added.length) parts.push(`${entry.added.length} nuevo(s)`)
|
|
147
|
+
if (entry.removed.length) parts.push(`${entry.removed.length} retirado(s)`)
|
|
148
|
+
const desde = entry.version ? ` desde ${entry.version}` : ''
|
|
149
|
+
return `${entry.slug}: tu copia no recibe mejoras del catálogo y ahí${desde} ${parts.join(', ')}`
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
module.exports = { drift, driftLine, fork, inherited }
|
package/engine/cli/ops.js
CHANGED
|
@@ -51,6 +51,7 @@ function usage() {
|
|
|
51
51
|
ops learn <agent> [--proposal]
|
|
52
52
|
ops evaluate <agent> [--cases [--json]]
|
|
53
53
|
ops agents list [ops-root] [--own|--system] [--json]
|
|
54
|
+
ops agents fork <cargo> [ops-root]
|
|
54
55
|
ops team list
|
|
55
56
|
ops team check <team>
|
|
56
57
|
ops team show <team>`)
|
|
@@ -300,6 +301,9 @@ function check(dir) {
|
|
|
300
301
|
for (const override of O.overrides(path.resolve(root, '..'))) {
|
|
301
302
|
warnings.push(`${override.collection}/${override.project} sobrescribe ${override.system} (override explícito)`)
|
|
302
303
|
}
|
|
304
|
+
// Misma regla para los cargos, que es donde más caro sale: un fork se hace una vez y se olvida.
|
|
305
|
+
const FK = require('../agents/fork')
|
|
306
|
+
for (const entry of FK.drift(path.resolve(root, '..'))) warnings.push(FK.driftLine(entry))
|
|
303
307
|
|
|
304
308
|
if (process.argv.includes('--json')) {
|
|
305
309
|
console.log(JSON.stringify({
|
|
@@ -604,6 +608,10 @@ function upgrade(dir) {
|
|
|
604
608
|
for (const name of runners) {
|
|
605
609
|
console.log(` reinstalá tu runner para que el wiring quede al día: make install-${name}`)
|
|
606
610
|
}
|
|
611
|
+
// Después de aplicar, no antes: recién acá el paquete tiene la versión nueva y la comparación dice
|
|
612
|
+
// algo. Es además el momento en que alguien está mirando qué le trajo la actualización.
|
|
613
|
+
const FK = require('../agents/fork')
|
|
614
|
+
for (const entry of FK.drift(root)) console.log(` ⚠ ${FK.driftLine(entry)}`)
|
|
607
615
|
}
|
|
608
616
|
|
|
609
617
|
// Lista los cargos visibles resolviendo la precedencia; evita que cada consumidor —CI incluido—
|
|
@@ -614,7 +622,22 @@ function opsRoot(dir) {
|
|
|
614
622
|
return path.resolve(dir || process.env.OPS_ROOT || '.')
|
|
615
623
|
}
|
|
616
624
|
|
|
617
|
-
function
|
|
625
|
+
function agentsFork(slug, dir) {
|
|
626
|
+
const root = opsRoot(dir)
|
|
627
|
+
if (!slug) fail('Falta el cargo: ops agents fork <cargo> [ops-root]', 2)
|
|
628
|
+
let result
|
|
629
|
+
const date = new Date().toISOString().slice(0, 10)
|
|
630
|
+
try { result = require('../agents/fork').fork(root, slug, date) } catch (error) { fail(error.message, 2) }
|
|
631
|
+
console.log(`+ ${path.relative(root, result.dir)} (${result.files.length} archivo(s))`)
|
|
632
|
+
if (result.skipped.length) {
|
|
633
|
+
console.log(` quedan en el catálogo: ${result.skipped.length} artefacto(s) que ganó su versión`)
|
|
634
|
+
}
|
|
635
|
+
console.log(` copiado de Cauce ${result.version || '(versión desconocida)'}; desde ahora lo mantenés vos`)
|
|
636
|
+
console.log(` reinstalá tu runner para que ${slug} apunte a tu copia`)
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
function agents(action, dir, extra) {
|
|
640
|
+
if (action === 'fork') return agentsFork(dir, extra)
|
|
618
641
|
if (action !== 'list') fail(`Acción de agents desconocida: ${action || '(vacía)'}`, 2)
|
|
619
642
|
const root = opsRoot(dir)
|
|
620
643
|
// Una empresa mantiene sus cargos, no los nuestros: `learn` sobre uno del catálogo se niega, así que
|
|
@@ -884,7 +907,7 @@ async function main() {
|
|
|
884
907
|
else if (command === 'tree') tree(arg[1])
|
|
885
908
|
else if (command === 'context') context(arg[1])
|
|
886
909
|
else if (command === 'upgrade') upgrade(arg[1])
|
|
887
|
-
else if (command === 'agents') agents(arg[1], arg[2])
|
|
910
|
+
else if (command === 'agents') agents(arg[1], arg[2], arg[3])
|
|
888
911
|
else if (command === 'archive') archive(arg[1], arg[2])
|
|
889
912
|
else if (command === 'integration') {
|
|
890
913
|
await integration(arg[1], arg[2], arg[3], arg[4])
|
package/engine/core/manifest.js
CHANGED
|
@@ -30,17 +30,24 @@ function readAll(root) {
|
|
|
30
30
|
return {
|
|
31
31
|
files: typeof data.files === 'object' && data.files ? data.files : {},
|
|
32
32
|
runners: typeof data.runners === 'object' && data.runners ? data.runners : {},
|
|
33
|
+
forks: typeof data.forks === 'object' && data.forks ? data.forks : {},
|
|
33
34
|
}
|
|
34
|
-
} catch { return { files: {}, runners: {} } }
|
|
35
|
+
} catch { return { files: {}, runners: {}, forks: {} } }
|
|
35
36
|
}
|
|
36
37
|
|
|
37
38
|
function read(root) { return readAll(root).files }
|
|
38
39
|
|
|
39
40
|
function readRunners(root) { return readAll(root).runners }
|
|
40
41
|
|
|
42
|
+
// Tercera sección, y de otra naturaleza que las dos anteriores: `files` y `runners` registran lo que
|
|
43
|
+
// Cauce entregó, mientras `forks` registra lo que la empresa se llevó. Guarda el contenido del cargo
|
|
44
|
+
// del sistema **en el momento de la copia**, que es lo único contra lo que se puede decir después
|
|
45
|
+
// «esto mejoró río arriba»: comparar contra el fork mismo mediría las ediciones de la empresa.
|
|
46
|
+
function readForks(root) { return readAll(root).forks }
|
|
47
|
+
|
|
41
48
|
// Cada sección que no se pasa se conserva: `upgrade` no sabe del wiring y `install` no sabe de la
|
|
42
49
|
// instancia, y ninguno de los dos debería borrar lo que el otro anotó.
|
|
43
|
-
function write(root, files, runners) {
|
|
50
|
+
function write(root, files, runners, forks) {
|
|
44
51
|
const current = readAll(root)
|
|
45
52
|
const target = path.join(root, FILE)
|
|
46
53
|
fs.mkdirSync(path.dirname(target), { recursive: true })
|
|
@@ -51,6 +58,7 @@ function write(root, files, runners) {
|
|
|
51
58
|
version: 1,
|
|
52
59
|
files: ordered(files || current.files),
|
|
53
60
|
runners: ordered(runners || current.runners),
|
|
61
|
+
forks: ordered(forks || current.forks),
|
|
54
62
|
}
|
|
55
63
|
fs.writeFileSync(target, `${JSON.stringify(data, null, 2)}\n`)
|
|
56
64
|
}
|
|
@@ -85,4 +93,6 @@ function edited(root, relative, files) {
|
|
|
85
93
|
})
|
|
86
94
|
}
|
|
87
95
|
|
|
88
|
-
module.exports = {
|
|
96
|
+
module.exports = {
|
|
97
|
+
FILE, digest, digestText, edited, prune, read, readForks, readRunners, record, write,
|
|
98
|
+
}
|