agentic-workflow-manager 6.1.1 → 6.2.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.
|
@@ -60,14 +60,14 @@ function binaryVersionCheck(agent) {
|
|
|
60
60
|
* `dir` is null — i.e. the provider has no global skill discovery mechanism at all
|
|
61
61
|
* (today: Copilot, see `globalUnsupportedReason` in providers/index.ts).
|
|
62
62
|
*
|
|
63
|
-
* `renderer`
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
function skillsGlobalCheck(dir, owners, integrity, renderer) {
|
|
63
|
+
* `renderer` parte la verificacion en dos caminos distintos porque los artefactos son
|
|
64
|
+
* distintos: `classifySkillLinks` solo ve symlinks — `if (!lst.isSymbolicLink()) continue;`
|
|
65
|
+
* — asi que sobre un directorio de archivos renderizados no encuentra nada. Durante un
|
|
66
|
+
* tiempo esa rama reporto SOLO presencia, y lo decia en el `detail` para no fingir una
|
|
67
|
+
* verificacion que no ocurria. Ahora tambien comprueba contenido, con el marcador que
|
|
68
|
+
* declara el renderer (`rendererIntegrityMarker`): un archivo con la extension correcta
|
|
69
|
+
* y el cuerpo vacio o truncado ya no pasa como sano. */
|
|
70
|
+
function skillsGlobalCheck(dir, owners, integrity, renderer, artifacts) {
|
|
71
71
|
if (dir === null)
|
|
72
72
|
return null;
|
|
73
73
|
if (renderer !== 'link') {
|
|
@@ -88,23 +88,56 @@ function skillsGlobalCheck(dir, owners, integrity, renderer) {
|
|
|
88
88
|
// Require at least one entry with the extension this renderer actually produces,
|
|
89
89
|
// not just ANY file — a directory non-empty only because of the user's own
|
|
90
90
|
// pre-existing, unrelated rule/instructions file must not read as "AWM installed".
|
|
91
|
-
// Still not full integrity verification (a stray file with the right extension but
|
|
92
|
-
// wrong content still passes) — that residual gap is the same honest tradeoff this
|
|
93
|
-
// function's doc comment already accepts for the non-`'link'` branch generally.
|
|
94
91
|
// `rendererExtension` (core/renderers/registry.ts) is the ONE table mapping a
|
|
95
92
|
// renderer to the file it produces. This site used to keep its own partial copy —
|
|
96
93
|
// the fourth such copy in the codebase, and the exact drift that made a rendered
|
|
97
94
|
// artifact invisible to the "is it installed" check for a whole release. A new
|
|
98
95
|
// renderer must not be able to be added without this reading it.
|
|
99
96
|
const ext = (0, registry_1.rendererExtension)(renderer);
|
|
100
|
-
const
|
|
97
|
+
const rendered = ext ? entries.filter((e) => e.endsWith(ext)) : entries;
|
|
98
|
+
const present = rendered.length > 0;
|
|
99
|
+
if (!present) {
|
|
100
|
+
return {
|
|
101
|
+
id: 'skills.global', state: 'absent', target: dir,
|
|
102
|
+
owners: owners.length > 1 ? owners : undefined,
|
|
103
|
+
remediationCode: 'awm-init',
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
// Ya no se reporta "content integrity not verified": AHORA se verifica.
|
|
107
|
+
// Comprobar presencia y extension dejaba pasar un archivo correcto por fuera y
|
|
108
|
+
// vacio o truncado por dentro — el agente cargaba nada y doctor decia que si.
|
|
109
|
+
const corrupt = corruptRendered(dir, rendered, renderer);
|
|
110
|
+
if (corrupt.length > 0) {
|
|
111
|
+
return {
|
|
112
|
+
id: 'skills.global', state: 'broken', target: dir,
|
|
113
|
+
owners: owners.length > 1 ? owners : undefined,
|
|
114
|
+
detail: `${corrupt.length} rendered file(s) missing their expected content (${corrupt.slice(0, 3).join(', ')})`,
|
|
115
|
+
// Codigo propio: no es una usurpacion (nadie lo reemplazo por otra cosa) ni
|
|
116
|
+
// un symlink colgante. Es nuestro archivo, con el contenido incompleto — lo
|
|
117
|
+
// arregla re-instalar el bundle, que lo reescribe.
|
|
118
|
+
remediationCode: 'reinstall-rendered-artifacts',
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
// Frescura, DESPUES de integridad: un archivo truncado tambien difiere de su
|
|
122
|
+
// fuente, y "incompleto" es mas util que "viejo". Solo aplica a renderizados — un
|
|
123
|
+
// symlink apunta al registry, asi que `awm update` lo actualiza solo. Los
|
|
124
|
+
// generados NO: quedan con el contenido de la version anterior hasta que alguien
|
|
125
|
+
// corra `awm sync`, y hasta ahora nada lo decia.
|
|
126
|
+
const stale = staleRendered(dir, artifacts);
|
|
101
127
|
return {
|
|
102
128
|
id: 'skills.global',
|
|
103
|
-
state:
|
|
129
|
+
state: stale.length > 0 ? 'stale' : 'supported',
|
|
104
130
|
target: dir,
|
|
105
131
|
owners: owners.length > 1 ? owners : undefined,
|
|
106
|
-
detail:
|
|
107
|
-
|
|
132
|
+
detail: stale.length > 0
|
|
133
|
+
? `${stale.length} rendered file(s) no longer match the installed registry (${stale.slice(0, 3).join(', ')})`
|
|
134
|
+
: undefined,
|
|
135
|
+
// `reinstall-bundle`, y NO `awm-sync` ni `awm-init`: se midio cual de los
|
|
136
|
+
// cuatro comandos refresca de verdad un renderizado viejo, y solo `awm add`
|
|
137
|
+
// lo hace. `init` y `sync` son idempotentes por presencia —ven el archivo y
|
|
138
|
+
// no lo tocan— y `update` refresca el registry, no lo derivado de el. Ofrecer
|
|
139
|
+
// un remedio que corre limpio sin cambiar nada es peor que no ofrecer ninguno.
|
|
140
|
+
remediationCode: stale.length > 0 ? 'reinstall-bundle' : undefined,
|
|
108
141
|
};
|
|
109
142
|
}
|
|
110
143
|
const shared = owners.length > 1;
|
|
@@ -153,21 +186,48 @@ function skillsGlobalCheck(dir, owners, integrity, renderer) {
|
|
|
153
186
|
: broken > 0 ? 'repair-global-skills' : undefined,
|
|
154
187
|
};
|
|
155
188
|
}
|
|
156
|
-
/**
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
189
|
+
/**
|
|
190
|
+
* Archivos renderizados cuyo contenido ya no es lo que el renderer escribe.
|
|
191
|
+
*
|
|
192
|
+
* El marcador sale de `rendererIntegrityMarker` — la MISMA tabla que da la extension —
|
|
193
|
+
* y no de una copia local. Antes solo `codex-agent-toml` tenia esta verificacion, con su
|
|
194
|
+
* marcador horneado en esta funcion: el tercer renderer en agregarse habria pasado sin
|
|
195
|
+
* verificar y nadie se habria enterado.
|
|
196
|
+
*
|
|
197
|
+
* Un archivo ilegible cuenta como corrupto: no poder leerlo no es evidencia de que este
|
|
198
|
+
* bien. Es la misma disciplina que el resto de los checks — nunca verde por no mirar.
|
|
199
|
+
*/
|
|
200
|
+
function staleRendered(dir, records) {
|
|
201
|
+
return records
|
|
202
|
+
.filter((r) => r.renderer !== 'link' && path_1.default.dirname(r.targetPath) === dir)
|
|
203
|
+
.filter((r) => {
|
|
160
204
|
try {
|
|
161
|
-
const
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
return
|
|
205
|
+
const expected = (0, registry_1.renderArtifact)(r.renderer, r.sourcePath);
|
|
206
|
+
if (expected === null)
|
|
207
|
+
return false;
|
|
208
|
+
return fs_1.default.readFileSync(r.targetPath, 'utf8') !== expected;
|
|
209
|
+
}
|
|
210
|
+
catch {
|
|
211
|
+
// Fuente ausente (el registry ya no la trae) o target ilegible: no es
|
|
212
|
+
// desactualizacion, es otra cosa, y la reportan los checks que
|
|
213
|
+
// corresponden. Aca "no puedo comparar" no se convierte en "esta viejo".
|
|
214
|
+
return false;
|
|
215
|
+
}
|
|
216
|
+
})
|
|
217
|
+
.map((r) => path_1.default.basename(r.targetPath));
|
|
218
|
+
}
|
|
219
|
+
function corruptRendered(dir, files, renderer) {
|
|
220
|
+
const marker = (0, registry_1.rendererIntegrityMarker)(renderer);
|
|
221
|
+
if (marker === null)
|
|
222
|
+
return [];
|
|
223
|
+
return files.filter((file) => {
|
|
224
|
+
try {
|
|
225
|
+
return !fs_1.default.readFileSync(path_1.default.join(dir, file), 'utf8').includes(marker);
|
|
165
226
|
}
|
|
166
227
|
catch {
|
|
167
228
|
return true;
|
|
168
229
|
}
|
|
169
|
-
})
|
|
170
|
-
return { broken };
|
|
230
|
+
});
|
|
171
231
|
}
|
|
172
232
|
/**
|
|
173
233
|
* Returns `null` when a check doesn't structurally apply to `agent` (e.g. Antigravity
|
|
@@ -255,7 +315,10 @@ function agentsNativeCheck(agent) {
|
|
|
255
315
|
if (entries.length === 0)
|
|
256
316
|
return null;
|
|
257
317
|
if (provider.agent.renderer === 'codex-agent-toml') {
|
|
258
|
-
|
|
318
|
+
// La extension sale de la tabla, no de un literal: escribir '.toml' aca fue
|
|
319
|
+
// exactamente lo que el guard estructural existe para detener, y lo detuvo.
|
|
320
|
+
const tomlExt = (0, registry_1.rendererExtension)('codex-agent-toml');
|
|
321
|
+
const broken = corruptRendered(dir, entries.filter((e) => e.endsWith(tomlExt)), 'codex-agent-toml').length;
|
|
259
322
|
return {
|
|
260
323
|
id: 'agents.native',
|
|
261
324
|
state: broken > 0 ? 'broken' : 'healthy',
|
|
@@ -386,6 +449,10 @@ function gatherProviderChecks(agents, scanSkills, projectRoot) {
|
|
|
386
449
|
for (const dir of ownersByDir.keys()) {
|
|
387
450
|
scansByDir.set(dir, scanSkills(dir));
|
|
388
451
|
}
|
|
452
|
+
// El ledger se lee UNA vez por corrida y se comparte: es la unica forma de saber
|
|
453
|
+
// que fuente produjo cada artefacto renderizado, y por lo tanto de preguntar si
|
|
454
|
+
// sigue coincidiendo con ella.
|
|
455
|
+
const artifacts = safeArtifactState();
|
|
389
456
|
return agents.map((agent) => {
|
|
390
457
|
const provider = (0, providers_1.providerFor)(agent);
|
|
391
458
|
const dir = provider.skill.global;
|
|
@@ -394,7 +461,7 @@ function gatherProviderChecks(agents, scanSkills, projectRoot) {
|
|
|
394
461
|
?? { valid: [], repairable: [], dead: [], usurped: [] };
|
|
395
462
|
const checks = [
|
|
396
463
|
binaryVersionCheck(agent),
|
|
397
|
-
skillsGlobalCheck(dir, owners, integrity, provider.skill.renderer),
|
|
464
|
+
skillsGlobalCheck(dir, owners, integrity, provider.skill.renderer, artifacts),
|
|
398
465
|
agentsNativeCheck(agent),
|
|
399
466
|
workflowsGlobalCheck(agent),
|
|
400
467
|
hookTrustCheck(agent),
|
|
@@ -30,9 +30,7 @@ const crypto_1 = __importDefault(require("crypto"));
|
|
|
30
30
|
const artifact_state_1 = require("./artifact-state");
|
|
31
31
|
const paths_1 = require("./paths");
|
|
32
32
|
const atomic_file_1 = require("./atomic-file");
|
|
33
|
-
const
|
|
34
|
-
const cursor_mdc_1 = require("./renderers/cursor-mdc");
|
|
35
|
-
const copilot_instructions_1 = require("./renderers/copilot-instructions");
|
|
33
|
+
const registry_1 = require("./renderers/registry");
|
|
36
34
|
const executor_1 = require("./executor");
|
|
37
35
|
/**
|
|
38
36
|
* The single timestamp-sanitization rule for transaction IDs, shared by
|
|
@@ -249,9 +247,6 @@ function stageRenderedFile(content, targetPath) {
|
|
|
249
247
|
* are sourced from that directory's SKILL.md, so every call site needs this
|
|
250
248
|
* same one-line join instead of reading `op.sourcePath` directly.
|
|
251
249
|
*/
|
|
252
|
-
function readSkillMdSource(op) {
|
|
253
|
-
return fs_1.default.readFileSync(path_1.default.join(op.sourcePath, 'SKILL.md'), 'utf8');
|
|
254
|
-
}
|
|
255
250
|
/**
|
|
256
251
|
* The real, filesystem-touching TransactionDeps used by applyInstallPlan by
|
|
257
252
|
* default. Renders `codex-agent-toml`/`cursor-mdc`/`copilot-instructions`
|
|
@@ -270,15 +265,10 @@ function defaultTransactionDeps() {
|
|
|
270
265
|
}
|
|
271
266
|
// Renders without writing anything, purely to surface parse errors
|
|
272
267
|
// before any backup/replace happens.
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
(0, cursor_mdc_1.renderCursorMdc)(readSkillMdSource(op));
|
|
278
|
-
}
|
|
279
|
-
else if (op.renderer === 'copilot-instructions') {
|
|
280
|
-
(0, copilot_instructions_1.renderCopilotInstructions)(readSkillMdSource(op));
|
|
281
|
-
}
|
|
268
|
+
// Renderiza y descarta: el objetivo es que un error de parseo salte ANTES
|
|
269
|
+
// de tocar nada. El dispatch sale de la tabla (`renderArtifact`), no de una
|
|
270
|
+
// copia local — esta era una de las dos que habia en este archivo.
|
|
271
|
+
(0, registry_1.renderArtifact)(op.renderer, op.sourcePath);
|
|
282
272
|
},
|
|
283
273
|
backup(op, backupDir) {
|
|
284
274
|
if (createdAt === null)
|
|
@@ -295,19 +285,11 @@ function defaultTransactionDeps() {
|
|
|
295
285
|
return entry.existed ? path_1.default.join(backupDir, entry.backupRelPath) : null;
|
|
296
286
|
},
|
|
297
287
|
stage(op) {
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
const rendered = (0, cursor_mdc_1.renderCursorMdc)(readSkillMdSource(op));
|
|
304
|
-
return stageRenderedFile(rendered, op.targetPath);
|
|
305
|
-
}
|
|
306
|
-
if (op.renderer === 'copilot-instructions') {
|
|
307
|
-
const rendered = (0, copilot_instructions_1.renderCopilotInstructions)(readSkillMdSource(op));
|
|
308
|
-
return stageRenderedFile(rendered, op.targetPath);
|
|
309
|
-
}
|
|
310
|
-
return (0, executor_1.stageArtifact)(op.sourcePath, op.targetPath, op.method);
|
|
288
|
+
const rendered = (0, registry_1.renderArtifact)(op.renderer, op.sourcePath);
|
|
289
|
+
// `null` = renderer `link`: no genera contenido, se instala el artefacto tal cual.
|
|
290
|
+
return rendered === null
|
|
291
|
+
? (0, executor_1.stageArtifact)(op.sourcePath, op.targetPath, op.method)
|
|
292
|
+
: stageRenderedFile(rendered, op.targetPath);
|
|
311
293
|
},
|
|
312
294
|
replace(op, staged) {
|
|
313
295
|
(0, executor_1.replaceArtifact)(staged, op.targetPath);
|
|
@@ -1,7 +1,40 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
6
|
exports.renderedFilename = renderedFilename;
|
|
4
7
|
exports.rendererExtension = rendererExtension;
|
|
8
|
+
exports.rendererIntegrityMarker = rendererIntegrityMarker;
|
|
9
|
+
exports.renderArtifact = renderArtifact;
|
|
10
|
+
// src/core/renderers/registry.ts
|
|
11
|
+
//
|
|
12
|
+
// Modulo HOJA: la UNICA fuente de verdad sobre "que archivo produce cada
|
|
13
|
+
// renderer en disco".
|
|
14
|
+
//
|
|
15
|
+
// Por que existe. Ese hecho estaba escrito a mano en TRES lugares divergentes
|
|
16
|
+
// — `physicalTarget` (install-planner.ts, el escritor), `RENDERED_SKILL_EXTENSIONS`
|
|
17
|
+
// (diagnostics/provider-checks.ts, que ademas omitia `codex-agent-toml`), y un
|
|
18
|
+
// ternario suelto para agentes en diagnostics/context.ts — y estaba AUSENTE en
|
|
19
|
+
// el cuarto, `classifyLinks`, que es justamente el que decide si un artefacto
|
|
20
|
+
// "esta instalado".
|
|
21
|
+
//
|
|
22
|
+
// La consecuencia no fue teorica: el escritor dejaba `using-awm.mdc` en disco y
|
|
23
|
+
// el lector buscaba `using-awm`, asi que para Cursor y Copilot
|
|
24
|
+
// - `awm init` NUNCA era idempotente (reinstalaba el baseline entero en cada
|
|
25
|
+
// corrida, dejando 2 directorios de backup sin podar por corrida), y
|
|
26
|
+
// - `awm doctor` mostraba un rojo permanente cuyo remedio no podia satisfacerlo.
|
|
27
|
+
// Lo mismo para los artefactos `agent` de claude-code, el provider por defecto.
|
|
28
|
+
//
|
|
29
|
+
// Regla: NINGUN modulo vuelve a derivar una extension a mano. Si aparece un
|
|
30
|
+
// renderer nuevo, se agrega aca y todos los consumidores quedan correctos por
|
|
31
|
+
// construccion — que es lo que convierte "agregar un provider" en un cambio
|
|
32
|
+
// localizado en vez de una caceria por seis archivos.
|
|
33
|
+
const fs_1 = __importDefault(require("fs"));
|
|
34
|
+
const path_1 = __importDefault(require("path"));
|
|
35
|
+
const codex_agent_1 = require("./codex-agent");
|
|
36
|
+
const cursor_mdc_1 = require("./cursor-mdc");
|
|
37
|
+
const copilot_instructions_1 = require("./copilot-instructions");
|
|
5
38
|
/** Extension que el renderer estampa sobre el nombre base, o `null` cuando el
|
|
6
39
|
* artefacto se instala con el nombre tal cual (renderer `link`). */
|
|
7
40
|
const RENDERER_EXTENSION = {
|
|
@@ -10,6 +43,26 @@ const RENDERER_EXTENSION = {
|
|
|
10
43
|
'cursor-mdc': '.mdc',
|
|
11
44
|
'copilot-instructions': '.instructions.md',
|
|
12
45
|
};
|
|
46
|
+
/**
|
|
47
|
+
* Cadena que el renderer SIEMPRE estampa en lo que produce, y que por lo tanto
|
|
48
|
+
* distingue "el archivo existe" de "el archivo sigue siendo lo que escribimos".
|
|
49
|
+
*
|
|
50
|
+
* Vive en esta tabla, al lado de la extension, por la misma razon que la extension:
|
|
51
|
+
* son los dos hechos que un renderer nuevo tiene que declarar, y tenerlos juntos hace
|
|
52
|
+
* imposible agregar uno y olvidarse del segundo. `codex-agent-toml` ya tenia su marcador
|
|
53
|
+
* — horneado dentro de `tomlAgentsHealthy` en provider-checks.ts — y era el UNICO de los
|
|
54
|
+
* tres renderers con verificacion de contenido. Los otros dos comprobaban presencia y
|
|
55
|
+
* extension: un archivo correcto por fuera y vacio o truncado por dentro pasaba como sano.
|
|
56
|
+
*
|
|
57
|
+
* `null` en `link`: ahi la integridad la responde el clasificador de symlinks, que
|
|
58
|
+
* ademas distingue colgante de usurpado (D-007). Un marcador de texto no aplica.
|
|
59
|
+
*/
|
|
60
|
+
const RENDERER_INTEGRITY_MARKER = {
|
|
61
|
+
link: null,
|
|
62
|
+
'codex-agent-toml': 'developer_instructions = ',
|
|
63
|
+
'cursor-mdc': 'alwaysApply:',
|
|
64
|
+
'copilot-instructions': 'applyTo:',
|
|
65
|
+
};
|
|
13
66
|
/** `.md` es la unica extension que un `installName` de artefacto puede traer.
|
|
14
67
|
* Deliberadamente NO se usa `path.parse().name`: eso corta desde el ULTIMO
|
|
15
68
|
* punto, asi que un skill llamado `v1.2-migration` se truncaria a `v1`,
|
|
@@ -35,3 +88,40 @@ function renderedFilename(installName, renderer) {
|
|
|
35
88
|
function rendererExtension(renderer) {
|
|
36
89
|
return RENDERER_EXTENSION[renderer];
|
|
37
90
|
}
|
|
91
|
+
/** Marcador de integridad del renderer, o `null` cuando no aplica (`link`).
|
|
92
|
+
* Ver `RENDERER_INTEGRITY_MARKER`. */
|
|
93
|
+
function rendererIntegrityMarker(renderer) {
|
|
94
|
+
return RENDERER_INTEGRITY_MARKER[renderer];
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Contenido que `renderer` produce AHORA a partir de `sourcePath`, o `null` para `link`
|
|
98
|
+
* (no genera nada: instala el artefacto tal cual).
|
|
99
|
+
*
|
|
100
|
+
* Es la tercera cosa que un renderer tiene que declarar, junto con su extension y su
|
|
101
|
+
* marcador — y por eso vive en esta tabla. El dispatch `renderer id → funcion` estaba
|
|
102
|
+
* escrito DOS veces dentro de `install-transaction.ts` (una en `validate`, otra en
|
|
103
|
+
* `stage`), y agregar un consumidor mas —el chequeo de frescura del diagnostico— habria
|
|
104
|
+
* hecho tres. Es exactamente la duplicacion que el comentario de arriba de este modulo
|
|
105
|
+
* describe como el bug que costo una release.
|
|
106
|
+
*
|
|
107
|
+
* Puro: lee la fuente y devuelve texto. No escribe nada, asi que sirve igual para
|
|
108
|
+
* instalar, para validar antes de instalar, y para preguntar "¿lo instalado sigue
|
|
109
|
+
* coincidiendo con la fuente?".
|
|
110
|
+
*/
|
|
111
|
+
function renderArtifact(renderer, sourcePath) {
|
|
112
|
+
switch (renderer) {
|
|
113
|
+
case 'link':
|
|
114
|
+
return null;
|
|
115
|
+
case 'codex-agent-toml':
|
|
116
|
+
return (0, codex_agent_1.renderCodexAgent)(fs_1.default.readFileSync(sourcePath, 'utf8'));
|
|
117
|
+
case 'cursor-mdc':
|
|
118
|
+
return (0, cursor_mdc_1.renderCursorMdc)(readSkillMd(sourcePath));
|
|
119
|
+
case 'copilot-instructions':
|
|
120
|
+
return (0, copilot_instructions_1.renderCopilotInstructions)(readSkillMd(sourcePath));
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
/** Las skills se rinden desde el `SKILL.md` de su directorio; los agentes de Codex,
|
|
124
|
+
* desde el archivo directo. La diferencia la sabe cada rama de `renderArtifact`. */
|
|
125
|
+
function readSkillMd(sourceDir) {
|
|
126
|
+
return fs_1.default.readFileSync(path_1.default.join(sourceDir, 'SKILL.md'), 'utf8');
|
|
127
|
+
}
|
|
@@ -183,7 +183,24 @@ describe('skillsGlobalCheck — renderer-aware (Task 4.4 / deferred Task 4.3 fin
|
|
|
183
183
|
const skillsCheck = facts[0].checks.find((c) => c.id === 'skills.global');
|
|
184
184
|
expect(skillsCheck?.state).not.toBe('healthy');
|
|
185
185
|
expect(skillsCheck?.state).toBe('supported');
|
|
186
|
-
|
|
186
|
+
// Antes decia 'content integrity not verified' — honesto entonces, porque no se
|
|
187
|
+
// verificaba. Ahora SI se verifica, y este archivo tiene su marcador, asi que no
|
|
188
|
+
// hay nada que reportar.
|
|
189
|
+
expect(skillsCheck?.detail).toBeUndefined();
|
|
190
|
+
});
|
|
191
|
+
it('a rendered file with the right extension but a truncated body is reported broken', () => {
|
|
192
|
+
// El hueco que cerraba C3: presencia + extension dejaba pasar un archivo correcto
|
|
193
|
+
// por fuera y vacio por dentro. El agente cargaba nada y doctor decia que si.
|
|
194
|
+
const rulesDir = path_1.default.join(tmpHome, '.cursor/rules');
|
|
195
|
+
fs_1.default.mkdirSync(rulesDir, { recursive: true });
|
|
196
|
+
fs_1.default.writeFileSync(path_1.default.join(rulesDir, 'development-process.mdc'), '---\ndescription: x\n');
|
|
197
|
+
const scanSkills = jest.fn(() => ({ valid: [], repairable: [], dead: [], usurped: [] }));
|
|
198
|
+
const { gatherProviderChecks } = require('../../../src/core/diagnostics/provider-checks');
|
|
199
|
+
const facts = gatherProviderChecks(['cursor'], scanSkills);
|
|
200
|
+
const skillsCheck = facts[0].checks.find((c) => c.id === 'skills.global');
|
|
201
|
+
expect(skillsCheck?.state).toBe('broken');
|
|
202
|
+
expect(skillsCheck?.detail).toContain('development-process.mdc');
|
|
203
|
+
expect(skillsCheck?.remediationCode).toBe('reinstall-rendered-artifacts');
|
|
187
204
|
});
|
|
188
205
|
it('Gap B — non-link renderer (cursor-mdc) against REAL renderer/pipeline output, not a hand-written approximation', () => {
|
|
189
206
|
// The test above hand-writes a `.mdc` file whose frontmatter shape is only an
|
|
@@ -221,9 +238,50 @@ describe('skillsGlobalCheck — renderer-aware (Task 4.4 / deferred Task 4.3 fin
|
|
|
221
238
|
const facts = gatherProviderChecks(['cursor'], scanSkills);
|
|
222
239
|
const skillsCheck = facts[0].checks.find((c) => c.id === 'skills.global');
|
|
223
240
|
expect(skillsCheck).toMatchObject({ id: 'skills.global', state: 'supported', target: rulesDir });
|
|
224
|
-
|
|
241
|
+
// La verificacion de contenido corre sobre la salida REAL del renderer, no sobre
|
|
242
|
+
// una aproximacion escrita a mano: si el marcador declarado en la tabla no
|
|
243
|
+
// coincidiera con lo que el renderer emite de verdad, este test lo detectaria.
|
|
244
|
+
expect(skillsCheck?.detail).toBeUndefined();
|
|
225
245
|
fs_1.default.rmSync(content, { recursive: true, force: true });
|
|
226
246
|
});
|
|
247
|
+
it('a rendered file that no longer matches its registry source is reported stale', () => {
|
|
248
|
+
// El caso que C4 realmente pedia. Un symlink apunta al registry, asi que
|
|
249
|
+
// `awm update` lo actualiza solo — por eso claude-code/codex/opencode nunca
|
|
250
|
+
// quedan viejos. Un `.mdc` es un archivo GENERADO: se queda con el contenido de
|
|
251
|
+
// la version anterior hasta que alguien corra `awm sync`, y nada lo decia.
|
|
252
|
+
const source = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-stale-src-'));
|
|
253
|
+
fs_1.default.mkdirSync(path_1.default.join(source, 'using-awm'), { recursive: true });
|
|
254
|
+
const skillMd = path_1.default.join(source, 'using-awm', 'SKILL.md');
|
|
255
|
+
fs_1.default.writeFileSync(skillMd, '---\nname: using-awm\ndescription: original\n---\n\nCuerpo v1.\n');
|
|
256
|
+
const rulesDir = path_1.default.join(tmpHome, '.cursor/rules');
|
|
257
|
+
fs_1.default.mkdirSync(rulesDir, { recursive: true });
|
|
258
|
+
const target = path_1.default.join(rulesDir, 'using-awm.mdc');
|
|
259
|
+
const { renderArtifact } = require('../../../src/core/renderers/registry');
|
|
260
|
+
fs_1.default.writeFileSync(target, renderArtifact('cursor-mdc', path_1.default.join(source, 'using-awm')));
|
|
261
|
+
const stateDir = path_1.default.join(tmpHome, '.awm', 'state');
|
|
262
|
+
fs_1.default.mkdirSync(stateDir, { recursive: true });
|
|
263
|
+
fs_1.default.writeFileSync(path_1.default.join(stateDir, 'artifacts.json'), JSON.stringify([{
|
|
264
|
+
name: 'using-awm', type: 'skill', scope: 'global',
|
|
265
|
+
targetPath: target, sourcePath: path_1.default.join(source, 'using-awm'),
|
|
266
|
+
renderer: 'cursor-mdc', owners: ['cursor'],
|
|
267
|
+
}]));
|
|
268
|
+
const scanSkills = jest.fn(() => ({ valid: [], repairable: [], dead: [], usurped: [] }));
|
|
269
|
+
const { gatherProviderChecks } = require('../../../src/core/diagnostics/provider-checks');
|
|
270
|
+
// Recien instalado: coincide con su fuente.
|
|
271
|
+
const fresh = gatherProviderChecks(['cursor'], scanSkills)[0]
|
|
272
|
+
.checks.find((c) => c.id === 'skills.global');
|
|
273
|
+
expect(fresh?.state).toBe('supported');
|
|
274
|
+
// `awm update` trae una version nueva de la skill upstream.
|
|
275
|
+
fs_1.default.writeFileSync(skillMd, '---\nname: using-awm\ndescription: original\n---\n\nCuerpo v2, cambiado upstream.\n');
|
|
276
|
+
const stale = gatherProviderChecks(['cursor'], scanSkills)[0]
|
|
277
|
+
.checks.find((c) => c.id === 'skills.global');
|
|
278
|
+
expect(stale?.state).toBe('stale');
|
|
279
|
+
expect(stale?.detail).toContain('using-awm.mdc');
|
|
280
|
+
// Medido, no supuesto: de `init`/`update`/`sync`/`add`, solo `add` refresca un
|
|
281
|
+
// renderizado viejo. Los otros tres corren limpios y no cambian nada.
|
|
282
|
+
expect(stale?.remediationCode).toBe('reinstall-bundle');
|
|
283
|
+
fs_1.default.rmSync(source, { recursive: true, force: true });
|
|
284
|
+
});
|
|
227
285
|
it('non-link renderer with an empty/missing dir reports absent, not a false healthy', () => {
|
|
228
286
|
const scanSkills = jest.fn(() => ({ valid: [], repairable: [], dead: [], usurped: [] }));
|
|
229
287
|
const { gatherProviderChecks } = require('../../../src/core/diagnostics/provider-checks');
|