agentic-workflow-manager 3.13.5 → 3.13.6
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/dist/src/commands/add.js +5 -0
- package/dist/src/commands/doctor.js +1 -0
- package/dist/src/commands/hooks/claude.js +15 -2
- package/dist/src/commands/hooks/shared.js +6 -1
- package/dist/src/commands/init.js +1 -0
- package/dist/src/commands/pin.js +24 -4
- package/dist/src/commands/preflight/checks.js +3 -2
- package/dist/src/commands/registry/add.js +1 -1
- package/dist/src/commands/sensors/index.js +7 -0
- package/dist/src/commands/sensors/init.js +48 -7
- package/dist/src/commands/sensors/install.js +20 -11
- package/dist/src/commands/sensors/run.js +31 -29
- package/dist/src/commands/sync.js +29 -0
- package/dist/src/commands/watch/index.js +11 -1
- package/dist/src/core/artifact-name.js +80 -0
- package/dist/src/core/bundle-install.js +55 -12
- package/dist/src/core/bundles.js +72 -13
- package/dist/src/core/context/regenerate.js +21 -4
- package/dist/src/core/context/strategies/codex-agents.js +18 -2
- package/dist/src/core/diagnostics/checks.js +26 -2
- package/dist/src/core/diagnostics/context.js +91 -25
- package/dist/src/core/diagnostics/provider-checks.js +62 -14
- package/dist/src/core/discovery.js +2 -2
- package/dist/src/core/init/mutation-targets.js +11 -0
- package/dist/src/core/init/steps.js +70 -19
- package/dist/src/core/install-planner.js +62 -17
- package/dist/src/core/install-transaction.js +13 -1
- package/dist/src/core/journal/adapter.js +10 -1
- package/dist/src/core/paths.js +61 -8
- package/dist/src/core/profile.js +12 -0
- package/dist/src/core/provider-artifacts.js +38 -13
- package/dist/src/core/provider-version.js +12 -6
- package/dist/src/core/registries.js +40 -19
- package/dist/src/core/renderers/registry.js +37 -0
- package/dist/src/core/skill-integrity.js +71 -10
- package/dist/src/core/update-check.js +14 -2
- package/dist/src/core/versioning.js +17 -2
- package/dist/src/index.js +31 -23
- package/dist/src/providers/index.js +7 -2
- package/dist/src/utils/config.js +7 -8
- package/dist/tests/commands/doctor.test.js +6 -0
- package/dist/tests/commands/pin.test.js +29 -2
- package/dist/tests/commands/preflight/preflight.test.js +31 -17
- package/dist/tests/commands/registry/install-bundles.test.js +6 -6
- package/dist/tests/commands/sensors/init-pack-unavailable.test.js +96 -0
- package/dist/tests/commands/sensors/install-settings-safety.test.js +87 -0
- package/dist/tests/commands/sensors/run-is-read-only.test.js +0 -0
- package/dist/tests/commands/sensors/run.test.js +24 -51
- package/dist/tests/commands/sensors/status-windows.test.js +39 -22
- package/dist/tests/commands/sensors/status.test.js +27 -12
- package/dist/tests/core/artifact-name-containment.test.js +86 -0
- package/dist/tests/core/context/agents-md-single-slot.test.js +69 -0
- package/dist/tests/core/context/regenerate.test.js +39 -8
- package/dist/tests/core/diagnostics/checks.test.js +14 -6
- package/dist/tests/core/diagnostics/provider-tier.test.js +3 -3
- package/dist/tests/core/diagnostics/rendered-artifact-visibility.test.js +69 -0
- package/dist/tests/core/init/all-baseline-bundles.test.js +64 -0
- package/dist/tests/core/init/context-injection-no-project.test.js +117 -0
- package/dist/tests/core/init/orchestrator.test.js +1 -1
- package/dist/tests/core/init/steps-registry-sync.test.js +1 -1
- package/dist/tests/core/init/steps.test.js +11 -5
- package/dist/tests/core/path-resolution-no-shell.test.js +136 -0
- package/dist/tests/core/paths.test.js +7 -34
- package/dist/tests/core/project-skill-links.test.js +159 -0
- package/dist/tests/core/provider-artifacts.test.js +66 -14
- package/dist/tests/core/provider-capability-guards.test.js +92 -0
- package/dist/tests/core/reconciliation.test.js +15 -15
- package/dist/tests/core/registry-manifest.test.js +1 -1
- package/dist/tests/core/semver-fails-closed.test.js +30 -0
- package/dist/tests/core/skill-integrity.test.js +10 -10
- package/dist/tests/core/skill-repair-safety.test.js +92 -0
- package/dist/tests/core/sync-profile-atomicity.test.js +122 -0
- package/dist/tests/integration/copilot-init-isolated.test.js +2 -2
- package/dist/tests/providers/index.test.js +1 -1
- package/dist/tests/structural/renderer-table-is-single-source.test.js +95 -0
- package/dist/tests/structural/symlink-type-is-explicit.test.js +73 -0
- package/dist/tests/utils/registry-view-overrides.test.js +2 -2
- package/package.json +4 -1
|
@@ -8,39 +8,64 @@ exports.scanLegacyArtifacts = scanLegacyArtifacts;
|
|
|
8
8
|
const fs_1 = __importDefault(require("fs"));
|
|
9
9
|
const path_1 = __importDefault(require("path"));
|
|
10
10
|
const providers_1 = require("../providers");
|
|
11
|
+
const artifact_state_1 = require("./artifact-state");
|
|
11
12
|
const ARTIFACT_TYPES = ['skill', 'workflow', 'agent'];
|
|
12
13
|
function preflightLinkArtifactPairs(pairs) {
|
|
13
14
|
for (const { agent, artifact } of pairs) {
|
|
14
15
|
(0, providers_1.assertLinkRenderer)(artifact.type, agent);
|
|
15
16
|
}
|
|
16
17
|
}
|
|
17
|
-
|
|
18
|
+
/**
|
|
19
|
+
* Artefactos instalados que AWM puede ofrecer para remover.
|
|
20
|
+
*
|
|
21
|
+
* Cuatro defectos corregidos aca, todos con perdida de datos o no-operacion:
|
|
22
|
+
*
|
|
23
|
+
* 1. **Clave por nombre solo**, a traves de TODOS los tipos y agentes. Un
|
|
24
|
+
* workflow `deploy.md` de antigravity y un agent `deploy.md` de claude-code
|
|
25
|
+
* colapsaban en UNA entrada, asi que elegir "deploy.md" borraba los dos. El
|
|
26
|
+
* escritor (`planInstall`) siempre clavea por ubicacion fisica.
|
|
27
|
+
* 2. **Scope local resuelto contra `process.cwd()`**: `config.local` es
|
|
28
|
+
* relativo (`.claude/skills`), asi que desde un subdirectorio no encontraba
|
|
29
|
+
* nada, y cuando encontraba algo le pasaba una ruta RELATIVA a `fs.rmSync`.
|
|
30
|
+
* Ahora recibe el projectRoot explicito, como todos los demas consumidores.
|
|
31
|
+
* 3. **Listaba el directorio entero**, ofreciendo borrar skills que el usuario
|
|
32
|
+
* escribio a mano. Ahora se cruza contra `readArtifactState()`: solo se
|
|
33
|
+
* ofrece lo que AWM realmente instalo. El registro de propiedad existe
|
|
34
|
+
* justamente para poder distinguirlo.
|
|
35
|
+
* 4. **Se tragaba `UnsupportedRendererError`**, asi que era un no-op permanente
|
|
36
|
+
* para skills de cursor/copilot y agents de codex: `awm remove -a cursor`
|
|
37
|
+
* decia "no hay artefactos instalados" con el directorio lleno.
|
|
38
|
+
*/
|
|
39
|
+
function scanLegacyArtifacts(agents, scope, projectRoot = process.cwd(), owned = (0, artifact_state_1.readArtifactState)()) {
|
|
18
40
|
const artifacts = new Map();
|
|
41
|
+
const ownedPaths = new Set(owned.map((r) => path_1.default.resolve(r.targetPath)));
|
|
19
42
|
for (const agent of agents) {
|
|
20
43
|
for (const type of ARTIFACT_TYPES) {
|
|
21
|
-
|
|
22
|
-
try {
|
|
23
|
-
config = (0, providers_1.assertLinkRenderer)(type, agent);
|
|
24
|
-
}
|
|
25
|
-
catch (error) {
|
|
26
|
-
if (error instanceof providers_1.UnsupportedRendererError)
|
|
27
|
-
continue;
|
|
28
|
-
throw error;
|
|
29
|
-
}
|
|
44
|
+
const config = (0, providers_1.providerFor)(agent)[type];
|
|
30
45
|
if (!config)
|
|
31
46
|
continue;
|
|
32
|
-
|
|
47
|
+
// Se usa la config del provider directamente: `assertLinkRenderer`
|
|
48
|
+
// TIRA para renderers que producen contenido, y atrapar ese throw
|
|
49
|
+
// convertia a cursor/copilot/codex en invisibles para `awm remove`.
|
|
50
|
+
const dir = scope === 'local' ? path_1.default.join(projectRoot, config.local) : config.global;
|
|
33
51
|
if (dir === null || !fs_1.default.existsSync(dir))
|
|
34
52
|
continue;
|
|
35
53
|
for (const entry of fs_1.default.readdirSync(dir, { withFileTypes: true })) {
|
|
36
54
|
const fullPath = path_1.default.join(dir, entry.name);
|
|
37
|
-
|
|
55
|
+
// Solo lo que AWM instalo. Un archivo del usuario en el mismo
|
|
56
|
+
// directorio no es nuestro para ofrecerlo en un menu de borrado.
|
|
57
|
+
if (!ownedPaths.has(path_1.default.resolve(fullPath)))
|
|
58
|
+
continue;
|
|
59
|
+
// Clave por TIPO + nombre: dos artefactos de tipos distintos con
|
|
60
|
+
// el mismo nombre son cosas distintas en lugares distintos.
|
|
61
|
+
const key = `${type}\u0000${entry.name}`;
|
|
62
|
+
const existing = artifacts.get(key);
|
|
38
63
|
if (existing) {
|
|
39
64
|
existing.installedIn.push(agent);
|
|
40
65
|
existing.fullPaths.push(fullPath);
|
|
41
66
|
}
|
|
42
67
|
else {
|
|
43
|
-
artifacts.set(
|
|
68
|
+
artifacts.set(key, {
|
|
44
69
|
name: entry.name,
|
|
45
70
|
type,
|
|
46
71
|
installedIn: [agent],
|
|
@@ -19,18 +19,24 @@ function assertProviderSupported(agent, exec = child_process_1.execFileSync) {
|
|
|
19
19
|
}
|
|
20
20
|
catch (error) {
|
|
21
21
|
const code = error.code;
|
|
22
|
+
// `provider.label` y `versionCommand`, no "Codex" literal. Esta funcion es
|
|
23
|
+
// generica sobre AgentTarget desde siempre, pero cada mensaje y el patron de
|
|
24
|
+
// parseo nombraban al unico provider que hoy declara `versionCommand` — el
|
|
25
|
+
// segundo en declararlo habria reportado "Codex no esta instalado" al no
|
|
26
|
+
// encontrar SU binario, y habria fallado a parsear una salida perfectamente
|
|
27
|
+
// valida contra el formato de otro programa.
|
|
22
28
|
if (code === 'ENOENT') {
|
|
23
|
-
throw new Error(
|
|
24
|
-
|
|
29
|
+
throw new Error(`${provider.label} is not installed or not available on PATH ` +
|
|
30
|
+
`(tried \`${provider.versionCommand.command}\`). Install it, then re-run.`);
|
|
25
31
|
}
|
|
26
|
-
throw new Error(
|
|
32
|
+
throw new Error(`${provider.label} version probe failed: ${error.message}`);
|
|
27
33
|
}
|
|
28
|
-
const match = output.trim().match(
|
|
34
|
+
const match = output.trim().match(provider.versionCommand.versionPattern);
|
|
29
35
|
if (!match) {
|
|
30
|
-
throw new Error(`could not parse
|
|
36
|
+
throw new Error(`could not parse ${provider.label} version from: ${output.trim()}`);
|
|
31
37
|
}
|
|
32
38
|
if ((0, versioning_1.compareSemver)(match[1], provider.minimumVersion) < 0) {
|
|
33
|
-
throw new Error(`requires
|
|
39
|
+
throw new Error(`requires ${provider.label} >= ${provider.minimumVersion}; found ${match[1]}`);
|
|
34
40
|
}
|
|
35
41
|
return { provider: agent, version: match[1] };
|
|
36
42
|
}
|
|
@@ -3,7 +3,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.REGISTRY_MANIFEST_NAME = exports.CONTENT_DIR_NAMES =
|
|
6
|
+
exports.REGISTRY_MANIFEST_NAME = exports.CONTENT_DIR_NAMES = void 0;
|
|
7
|
+
exports.registriesDir = registriesDir;
|
|
8
|
+
exports.registriesConfigPath = registriesConfigPath;
|
|
7
9
|
exports.registryContentRoot = registryContentRoot;
|
|
8
10
|
exports.readRegistriesConfig = readRegistriesConfig;
|
|
9
11
|
exports.writeRegistriesConfig = writeRegistriesConfig;
|
|
@@ -30,49 +32,57 @@ const versioning_1 = require("./versioning");
|
|
|
30
32
|
const cli_version_1 = require("./cli-version");
|
|
31
33
|
const paths_1 = require("./paths");
|
|
32
34
|
// Computed at require-time by calling the shared resolver (single source of truth: core/paths.ts).
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
35
|
+
// Funciones, no constantes de nivel de modulo. `awmHome()` lee AWM_HOME/HOME EN CADA
|
|
36
|
+
// LLAMADA a proposito (paths.ts) — resolverlo una sola vez al hacer `require` congelaba
|
|
37
|
+
// la respuesta en el instante del import, asi que cualquier cosa que fijara AWM_HOME
|
|
38
|
+
// despues (un test, un comando que cambia de home) seguia apuntando al viejo. Este
|
|
39
|
+
// modulo era el unico lugar que contradecia esa regla.
|
|
40
|
+
function registriesDir() {
|
|
41
|
+
return path_1.default.join((0, paths_1.awmHome)(), 'registries');
|
|
42
|
+
}
|
|
43
|
+
function registriesConfigPath() {
|
|
44
|
+
return path_1.default.join((0, paths_1.awmHome)(), 'registries.json');
|
|
45
|
+
}
|
|
36
46
|
exports.CONTENT_DIR_NAMES = ['skills', 'bundles', 'workflows', 'agents'];
|
|
37
47
|
function registryContentRoot(name) {
|
|
38
|
-
const root = path_1.default.join(
|
|
39
|
-
if (!path_1.default.resolve(root).startsWith(path_1.default.resolve(
|
|
48
|
+
const root = path_1.default.join(registriesDir(), name);
|
|
49
|
+
if (!path_1.default.resolve(root).startsWith(path_1.default.resolve(registriesDir()) + path_1.default.sep)) {
|
|
40
50
|
throw new Error(`Invalid registry name "${name}" — must not contain path separators`);
|
|
41
51
|
}
|
|
42
52
|
return root;
|
|
43
53
|
}
|
|
44
54
|
function readRegistriesConfig() {
|
|
45
|
-
if (!fs_1.default.existsSync(
|
|
55
|
+
if (!fs_1.default.existsSync(registriesConfigPath()))
|
|
46
56
|
return [];
|
|
47
57
|
let raw;
|
|
48
58
|
try {
|
|
49
|
-
raw = JSON.parse(fs_1.default.readFileSync(
|
|
59
|
+
raw = JSON.parse(fs_1.default.readFileSync(registriesConfigPath(), 'utf-8'));
|
|
50
60
|
}
|
|
51
61
|
catch (e) {
|
|
52
|
-
throw new Error(`Invalid registries config at ${
|
|
62
|
+
throw new Error(`Invalid registries config at ${registriesConfigPath()}: ${e instanceof Error ? e.message : String(e)}`);
|
|
53
63
|
}
|
|
54
64
|
if (!Array.isArray(raw)) {
|
|
55
|
-
throw new Error(`Invalid registries config at ${
|
|
65
|
+
throw new Error(`Invalid registries config at ${registriesConfigPath()}: expected a JSON array`);
|
|
56
66
|
}
|
|
57
67
|
for (const entry of raw) {
|
|
58
68
|
if (typeof entry?.name !== 'string' || typeof entry?.remote !== 'string') {
|
|
59
|
-
throw new Error(`Invalid registries config at ${
|
|
69
|
+
throw new Error(`Invalid registries config at ${registriesConfigPath()}: malformed entry ${JSON.stringify(entry)}`);
|
|
60
70
|
}
|
|
61
71
|
if (entry.name === '.' || entry.name.includes('/') || entry.name.includes('\\') || entry.name.includes('..')) {
|
|
62
|
-
throw new Error(`Invalid registries config at ${
|
|
72
|
+
throw new Error(`Invalid registries config at ${registriesConfigPath()}: malformed entry name "${entry.name}" (path traversal)`);
|
|
63
73
|
}
|
|
64
74
|
}
|
|
65
75
|
return raw;
|
|
66
76
|
}
|
|
67
77
|
function writeRegistriesConfig(entries) {
|
|
68
|
-
fs_1.default.mkdirSync(
|
|
69
|
-
fs_1.default.writeFileSync(
|
|
78
|
+
fs_1.default.mkdirSync((0, paths_1.awmHome)(), { recursive: true });
|
|
79
|
+
fs_1.default.writeFileSync(registriesConfigPath(), JSON.stringify(entries, null, 2) + '\n', 'utf-8');
|
|
70
80
|
}
|
|
71
81
|
/** Bootstrap de máquina (awm init): si no hay registries.json, lo crea con la
|
|
72
82
|
* entrada baseline (remote por cadena WS-2: env > prefs > default).
|
|
73
83
|
* Devuelve true si sembró. Nunca toca un registries.json existente. */
|
|
74
84
|
function seedBaselineRegistry() {
|
|
75
|
-
if (fs_1.default.existsSync(
|
|
85
|
+
if (fs_1.default.existsSync(registriesConfigPath()))
|
|
76
86
|
return false;
|
|
77
87
|
writeRegistriesConfig([{ name: 'baseline', remote: (0, registry_1.resolveBaseRemote)() }]);
|
|
78
88
|
return true;
|
|
@@ -111,7 +121,7 @@ async function syncRegistries() {
|
|
|
111
121
|
try {
|
|
112
122
|
const freshClone = !fs_1.default.existsSync(reg.contentRoot);
|
|
113
123
|
if (freshClone) {
|
|
114
|
-
fs_1.default.mkdirSync(
|
|
124
|
+
fs_1.default.mkdirSync(registriesDir(), { recursive: true });
|
|
115
125
|
try {
|
|
116
126
|
await (0, simple_git_1.default)().clone(reg.remote, reg.contentRoot);
|
|
117
127
|
}
|
|
@@ -219,11 +229,11 @@ function readRegistryManifest(root) {
|
|
|
219
229
|
}
|
|
220
230
|
return { overrides: new Set(overrides), minCliVersion };
|
|
221
231
|
}
|
|
222
|
-
/** Nombre del registry dueño de un path: el nombre del clone bajo
|
|
232
|
+
/** Nombre del registry dueño de un path: el nombre del clone bajo registriesDir(),
|
|
223
233
|
* o null si no pertenece a ninguno. */
|
|
224
234
|
function registryNameForPath(p) {
|
|
225
235
|
const resolved = path_1.default.resolve(p);
|
|
226
|
-
const regsRoot = path_1.default.resolve(
|
|
236
|
+
const regsRoot = path_1.default.resolve(registriesDir()) + path_1.default.sep;
|
|
227
237
|
if (resolved.startsWith(regsRoot)) {
|
|
228
238
|
const first = resolved.slice(regsRoot.length).split(path_1.default.sep)[0];
|
|
229
239
|
return first || null;
|
|
@@ -243,8 +253,19 @@ function verifyMinCliVersions(current = (0, cli_version_1.cliVersion)()) {
|
|
|
243
253
|
catch {
|
|
244
254
|
continue;
|
|
245
255
|
}
|
|
246
|
-
|
|
256
|
+
// Falla CERRADO: si alguna de las dos versiones no es legible, se
|
|
257
|
+
// reporta el fallo en vez de dejar pasar. Antes `compareSemver`
|
|
258
|
+
// devolvia NaN y `NaN < 0` es false, asi que un `minCliVersion`
|
|
259
|
+
// malformado hacia que el gate se saltara en silencio.
|
|
260
|
+
if (!min)
|
|
261
|
+
continue;
|
|
262
|
+
try {
|
|
263
|
+
if ((0, versioning_1.compareSemver)(current, min) < 0)
|
|
264
|
+
failures.push({ name: reg.name, min });
|
|
265
|
+
}
|
|
266
|
+
catch {
|
|
247
267
|
failures.push({ name: reg.name, min });
|
|
268
|
+
}
|
|
248
269
|
}
|
|
249
270
|
return failures;
|
|
250
271
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.renderedFilename = renderedFilename;
|
|
4
|
+
exports.rendererExtension = rendererExtension;
|
|
5
|
+
/** Extension que el renderer estampa sobre el nombre base, o `null` cuando el
|
|
6
|
+
* artefacto se instala con el nombre tal cual (renderer `link`). */
|
|
7
|
+
const RENDERER_EXTENSION = {
|
|
8
|
+
link: null,
|
|
9
|
+
'codex-agent-toml': '.toml',
|
|
10
|
+
'cursor-mdc': '.mdc',
|
|
11
|
+
'copilot-instructions': '.instructions.md',
|
|
12
|
+
};
|
|
13
|
+
/** `.md` es la unica extension que un `installName` de artefacto puede traer.
|
|
14
|
+
* Deliberadamente NO se usa `path.parse().name`: eso corta desde el ULTIMO
|
|
15
|
+
* punto, asi que un skill llamado `v1.2-migration` se truncaria a `v1`,
|
|
16
|
+
* perdiendo `2-migration` y arriesgando colision con otro skill `v1`. */
|
|
17
|
+
function stripMarkdownSuffix(installName) {
|
|
18
|
+
return installName.endsWith('.md') ? installName.slice(0, -'.md'.length) : installName;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Nombre de archivo FISICO que `renderer` produce para `installName`.
|
|
22
|
+
*
|
|
23
|
+
* Es la funcion que deben usar por igual el escritor (al planificar la
|
|
24
|
+
* instalacion) y todo lector (al preguntar "¿esta instalado?"). Que ambos lados
|
|
25
|
+
* salgan de aca es lo que hace estructuralmente imposible que vuelvan a
|
|
26
|
+
* discrepar.
|
|
27
|
+
*/
|
|
28
|
+
function renderedFilename(installName, renderer) {
|
|
29
|
+
const extension = RENDERER_EXTENSION[renderer];
|
|
30
|
+
return extension === null ? installName : `${stripMarkdownSuffix(installName)}${extension}`;
|
|
31
|
+
}
|
|
32
|
+
/** Extension del renderer, o `null` si instala con el nombre tal cual. Para los
|
|
33
|
+
* pocos casos que necesitan la extension suelta (p. ej. "¿hay ALGUN archivo
|
|
34
|
+
* renderizado en este directorio?") en vez de un nombre concreto. */
|
|
35
|
+
function rendererExtension(renderer) {
|
|
36
|
+
return RENDERER_EXTENSION[renderer];
|
|
37
|
+
}
|
|
@@ -3,12 +3,14 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.
|
|
7
|
-
exports.
|
|
6
|
+
exports.classifySkillLinks = classifySkillLinks;
|
|
7
|
+
exports.repairSkillLinks = repairSkillLinks;
|
|
8
8
|
exports.reconcileAllSkillLinks = reconcileAllSkillLinks;
|
|
9
|
+
exports.reconcileProjectSkillLinks = reconcileProjectSkillLinks;
|
|
9
10
|
const fs_1 = __importDefault(require("fs"));
|
|
10
11
|
const path_1 = __importDefault(require("path"));
|
|
11
12
|
const providers_1 = require("../providers");
|
|
13
|
+
const paths_1 = require("./paths");
|
|
12
14
|
function findRegistrySkillPath(registryContentDirs, name) {
|
|
13
15
|
for (const root of registryContentDirs) {
|
|
14
16
|
const p = path_1.default.join(root, 'skills', name);
|
|
@@ -17,8 +19,11 @@ function findRegistrySkillPath(registryContentDirs, name) {
|
|
|
17
19
|
}
|
|
18
20
|
return null;
|
|
19
21
|
}
|
|
20
|
-
/** Clasifica cada entrada de `skillsDir` (read-only, no muta nada).
|
|
21
|
-
|
|
22
|
+
/** Clasifica cada entrada de `skillsDir` (read-only, no muta nada).
|
|
23
|
+
* Agnostico al alcance: `skillsDir` es global (`provider.skill.global`) o de
|
|
24
|
+
* proyecto (`<projectRoot>/<provider.skill.local>`) — el nombre decia "Global" y
|
|
25
|
+
* eso basto para que nadie lo apuntara nunca a un dir de proyecto. */
|
|
26
|
+
function classifySkillLinks(skillsDir, registryContentDirs) {
|
|
22
27
|
const out = { valid: [], repairable: [], dead: [] };
|
|
23
28
|
let entries;
|
|
24
29
|
try {
|
|
@@ -51,10 +56,11 @@ function classifyGlobalSkills(skillsDir, registryContentDirs) {
|
|
|
51
56
|
return out;
|
|
52
57
|
}
|
|
53
58
|
/** Re-linkea los repairable al primer root que tenga la skill y poda los dead. Idempotente. Cada
|
|
54
|
-
* symlink aislado en try/catch — una falla no aborta el resto.
|
|
55
|
-
|
|
59
|
+
* symlink aislado en try/catch — una falla no aborta el resto. Agnostico al alcance:
|
|
60
|
+
* ver la nota de `classifySkillLinks`. */
|
|
61
|
+
function repairSkillLinks(skillsDir, registryContentDirs) {
|
|
56
62
|
const result = { relinked: [], pruned: [], failed: [] };
|
|
57
|
-
const { repairable, dead } =
|
|
63
|
+
const { repairable, dead } = classifySkillLinks(skillsDir, registryContentDirs);
|
|
58
64
|
for (const name of repairable) {
|
|
59
65
|
const p = path_1.default.join(skillsDir, name);
|
|
60
66
|
try {
|
|
@@ -63,8 +69,35 @@ function repairGlobalSkills(skillsDir, registryContentDirs) {
|
|
|
63
69
|
result.failed.push(name);
|
|
64
70
|
continue;
|
|
65
71
|
}
|
|
66
|
-
|
|
67
|
-
|
|
72
|
+
// Dos correcciones, ambas criticas en Windows:
|
|
73
|
+
//
|
|
74
|
+
// 1. `'junction'` en win32, no `'dir'`. Un symlink de directorio
|
|
75
|
+
// exige SeCreateSymbolicLinkPrivilege, denegado por defecto en
|
|
76
|
+
// cuentas sin privilegios (incluido el runner windows-latest);
|
|
77
|
+
// una junction la puede crear cualquiera y libuv la reporta
|
|
78
|
+
// igual. `executor.ts` ya documenta esto en extenso — este sitio
|
|
79
|
+
// nunca recibio el fix.
|
|
80
|
+
//
|
|
81
|
+
// 2. Se crea el link NUEVO antes de retirar el viejo. Antes el
|
|
82
|
+
// `rmSync` iba primero, asi que en Windows el link se borraba y
|
|
83
|
+
// la recreacion fallaba: el usuario quedaba PEOR que antes, y
|
|
84
|
+
// con la entrada ya ausente el proximo `awm init` ni siquiera
|
|
85
|
+
// podia verla como reparable. Un fallo ahora deja el estado
|
|
86
|
+
// original intacto.
|
|
87
|
+
const staged = path_1.default.join(skillsDir, `.${name}.${process.pid}.relink`);
|
|
88
|
+
try {
|
|
89
|
+
fs_1.default.rmSync(staged, { recursive: true, force: true });
|
|
90
|
+
fs_1.default.symlinkSync(target, staged, (0, paths_1.isWindowsNative)() ? 'junction' : 'dir');
|
|
91
|
+
fs_1.default.rmSync(p, { recursive: true, force: true });
|
|
92
|
+
fs_1.default.renameSync(staged, p);
|
|
93
|
+
}
|
|
94
|
+
finally {
|
|
95
|
+
// El staging jamas debe sobrevivir a esta iteracion: si quedara,
|
|
96
|
+
// `classifySkillLinks` lo veria como un symlink valido (apunta
|
|
97
|
+
// al registry, que existe) y nunca lo clasificaria como muerto,
|
|
98
|
+
// asi que se quedaria en el directorio para siempre.
|
|
99
|
+
fs_1.default.rmSync(staged, { recursive: true, force: true });
|
|
100
|
+
}
|
|
68
101
|
result.relinked.push(name);
|
|
69
102
|
}
|
|
70
103
|
catch {
|
|
@@ -92,7 +125,35 @@ function reconcileAllSkillLinks(registryContentDirs) {
|
|
|
92
125
|
const skillsDir = (0, providers_1.providerFor)(agent).skill.global;
|
|
93
126
|
if (skillsDir === null || !fs_1.default.existsSync(skillsDir))
|
|
94
127
|
continue;
|
|
95
|
-
out.push({ agent, result:
|
|
128
|
+
out.push({ agent, result: repairSkillLinks(skillsDir, registryContentDirs) });
|
|
129
|
+
}
|
|
130
|
+
return out;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* El equivalente de `reconcileAllSkillLinks` para el alcance de PROYECTO: reconcilia
|
|
134
|
+
* los symlinks de skills bajo `<projectRoot>/<provider.skill.local>` de cada agente
|
|
135
|
+
* pedido. Es la mitad que faltaba — todo lo de arriba servia igual para un dir local,
|
|
136
|
+
* pero ningun llamador lo apuntaba ahi, asi que un link colgante de proyecto no se
|
|
137
|
+
* curaba, no se podaba y no se reportaba en ninguna superficie. Para Copilot
|
|
138
|
+
* (`skill.global === null`) esto no era una mitad sino el total: TODAS sus skills son
|
|
139
|
+
* de proyecto.
|
|
140
|
+
*
|
|
141
|
+
* Un dir inexistente se omite (no hay nada que reconciliar y crearlo seria instalar,
|
|
142
|
+
* no reparar). Solo toca symlinks colgantes: los archivos y directorios reales del
|
|
143
|
+
* usuario, y los links vivos, quedan intactos — ver `classifySkillLinks`.
|
|
144
|
+
*/
|
|
145
|
+
function reconcileProjectSkillLinks(projectRoot, agents, registryContentDirs) {
|
|
146
|
+
const out = [];
|
|
147
|
+
const seen = new Set();
|
|
148
|
+
for (const agent of agents) {
|
|
149
|
+
// Varios providers comparten dir local (opencode y codex → `.agents/skills`).
|
|
150
|
+
// Reconciliar dos veces el mismo dir reportaria las mismas curaciones por
|
|
151
|
+
// duplicado, y la segunda pasada las veria ya sanas: se hace una sola vez.
|
|
152
|
+
const skillsDir = path_1.default.join(projectRoot, (0, providers_1.providerFor)(agent).skill.local);
|
|
153
|
+
if (seen.has(skillsDir) || !fs_1.default.existsSync(skillsDir))
|
|
154
|
+
continue;
|
|
155
|
+
seen.add(skillsDir);
|
|
156
|
+
out.push({ agent, result: repairSkillLinks(skillsDir, registryContentDirs) });
|
|
96
157
|
}
|
|
97
158
|
return out;
|
|
98
159
|
}
|
|
@@ -21,6 +21,18 @@ const picocolors_1 = __importDefault(require("picocolors"));
|
|
|
21
21
|
const prompts_1 = require("@clack/prompts");
|
|
22
22
|
const cli_version_1 = require("./cli-version");
|
|
23
23
|
const versioning_1 = require("./versioning");
|
|
24
|
+
/** El aviso de actualizacion es cosmetico: una version ilegible (de la cache o
|
|
25
|
+
* del registro de npm) no debe romper el comando que el usuario pidio — solo
|
|
26
|
+
* se omite el aviso. El gate de `minCliVersion`, en cambio, falla CERRADO
|
|
27
|
+
* (ver verifyMinCliVersions). */
|
|
28
|
+
function isNewer(a, b) {
|
|
29
|
+
try {
|
|
30
|
+
return (0, versioning_1.compareSemver)(a, b) > 0;
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
24
36
|
const paths_1 = require("./paths");
|
|
25
37
|
const TTL_MS = 24 * 60 * 60 * 1000;
|
|
26
38
|
const REGISTRY_URL = `https://registry.npmjs.org/${cli_version_1.CLI_PACKAGE_NAME}/latest`;
|
|
@@ -70,7 +82,7 @@ function maybeNotifyUpdate(opts) {
|
|
|
70
82
|
const now = opts?.now ?? Date.now();
|
|
71
83
|
const spawnWorker = opts?.spawnWorker ?? spawnRefreshWorker;
|
|
72
84
|
const cache = readUpdateCache();
|
|
73
|
-
if (cache?.latest && (
|
|
85
|
+
if (cache?.latest && isNewer(cache.latest, (0, cli_version_1.cliVersion)())) {
|
|
74
86
|
console.log(picocolors_1.default.dim(`\n⬆ awm v${cache.latest} available → npm i -g ${cli_version_1.CLI_PACKAGE_NAME}`));
|
|
75
87
|
}
|
|
76
88
|
if (!cache || now - cache.lastCheck > TTL_MS)
|
|
@@ -83,7 +95,7 @@ async function offerSelfUpdate(deps = {}) {
|
|
|
83
95
|
const current = deps.current ?? (0, cli_version_1.cliVersion)();
|
|
84
96
|
const latest = deps.latest !== undefined ? deps.latest : await fetchLatestVersion(deps.fetchImpl ?? fetch);
|
|
85
97
|
writeUpdateCache({ lastCheck: Date.now(), latest: latest ?? null });
|
|
86
|
-
if (!latest || (
|
|
98
|
+
if (!latest || !isNewer(latest, current))
|
|
87
99
|
return;
|
|
88
100
|
const confirmImpl = deps.confirmImpl ?? (async (message) => {
|
|
89
101
|
const r = await (0, prompts_1.confirm)({ message });
|
|
@@ -105,8 +105,23 @@ function machineVersionOpts(registryName) {
|
|
|
105
105
|
}
|
|
106
106
|
}
|
|
107
107
|
/** Compara "X.Y.Z" vs "X.Y.Z" (sin prefijo v) numéricamente. <0, 0, >0. */
|
|
108
|
+
/** Compara dos semver X.Y.Z.
|
|
109
|
+
*
|
|
110
|
+
* Antes devolvia `NaN` ante una entrada malformada (`'1.2'`, `'x.y.z'`), y como
|
|
111
|
+
* `NaN > 0` y `NaN < 0` son AMBOS false, el gate de version fallaba ABIERTO:
|
|
112
|
+
* `verifyMinCliVersions` dejaba pasar en silencio un CLI por debajo del minimo,
|
|
113
|
+
* y el aviso de actualizacion no disparaba nunca. Para un gate, la direccion
|
|
114
|
+
* correcta de fallo es la contraria. Ahora una entrada que no es semver lanza,
|
|
115
|
+
* nombrando el valor: es el llamador quien decide como tratarlo, con
|
|
116
|
+
* informacion suficiente para hacerlo. */
|
|
108
117
|
function compareSemver(a, b) {
|
|
109
|
-
const
|
|
110
|
-
|
|
118
|
+
const parse = (v) => {
|
|
119
|
+
const m = /^v?(\d+)\.(\d+)\.(\d+)/.exec(String(v).trim());
|
|
120
|
+
if (!m)
|
|
121
|
+
throw new Error(`not a semver version: ${JSON.stringify(v)}`);
|
|
122
|
+
return [Number(m[1]), Number(m[2]), Number(m[3])];
|
|
123
|
+
};
|
|
124
|
+
const pa = parse(a);
|
|
125
|
+
const pb = parse(b);
|
|
111
126
|
return (pa[0] - pb[0]) || (pa[1] - pb[1]) || (pa[2] - pb[2]);
|
|
112
127
|
}
|
package/dist/src/index.js
CHANGED
|
@@ -18,6 +18,9 @@ const provider_preflight_1 = require("./ui/provider-preflight");
|
|
|
18
18
|
const discovery_1 = require("./core/discovery");
|
|
19
19
|
const bundles_1 = require("./core/bundles");
|
|
20
20
|
const registries_1 = require("./core/registries");
|
|
21
|
+
const install_planner_1 = require("./core/install-planner");
|
|
22
|
+
const install_transaction_1 = require("./core/install-transaction");
|
|
23
|
+
const profile_1 = require("./core/profile");
|
|
21
24
|
const path_1 = __importDefault(require("path"));
|
|
22
25
|
const picocolors_1 = __importDefault(require("picocolors"));
|
|
23
26
|
const fs_1 = __importDefault(require("fs"));
|
|
@@ -166,31 +169,33 @@ program.command('add [name]')
|
|
|
166
169
|
(0, prompts_1.outro)(picocolors_1.default.yellow('No artifacts available.'));
|
|
167
170
|
return;
|
|
168
171
|
}
|
|
169
|
-
const preflightOk = (0, provider_preflight_1.preflightLinkArtifactsForCli)(targetAgents.flatMap((agent) => artifactsToInstall.map((artifact) => ({ agent, artifact }))));
|
|
170
|
-
if (!preflightOk)
|
|
171
|
-
return;
|
|
172
172
|
const installSpinner = (0, prompts_1.spinner)();
|
|
173
173
|
installSpinner.start('Installing artifacts...');
|
|
174
174
|
try {
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
175
|
+
// Se delega en el MISMO pipeline que `awm add <bundle>`
|
|
176
|
+
// (planInstall + applyInstallPlan): transaccional, con backups,
|
|
177
|
+
// rollback y soporte real de renderers.
|
|
178
|
+
//
|
|
179
|
+
// Antes esto instalaba a mano y pasaba por `assertLinkRenderer`,
|
|
180
|
+
// que TIRA para cualquier renderer que no sea `link` — asi que
|
|
181
|
+
// `awm add --all` moria con "Renderer 'cursor-mdc' … is not
|
|
182
|
+
// implemented yet" para cursor y copilot, y para CODEX era puro
|
|
183
|
+
// dano colateral: sus skills son `link` e instalan bien, pero un
|
|
184
|
+
// solo artefacto `agent` mataba la corrida entera. El pipeline
|
|
185
|
+
// real ya soporta los tres renderers — `awm add <bundle> -a cursor`
|
|
186
|
+
// lo demuestra — asi que el unico problema era este camino paralelo.
|
|
187
|
+
const summary = (0, install_transaction_1.applyInstallPlan)((0, install_planner_1.planInstall)({
|
|
188
|
+
artifacts: artifactsToInstall.map((a) => ({
|
|
189
|
+
name: a.name, type: a.type, installName: a.name, sourcePath: a.sourcePath,
|
|
190
|
+
})),
|
|
191
|
+
selectedAgents: targetAgents,
|
|
192
|
+
enabledAgents: prefs.enabledAgents,
|
|
193
|
+
scope: scopeVal,
|
|
194
|
+
projectRoot: (0, profile_1.findProjectRoot)(process.cwd()) ?? process.cwd(),
|
|
195
|
+
method: methodVal,
|
|
196
|
+
}));
|
|
197
|
+
const installed = summary.installed;
|
|
198
|
+
const skipped = summary.skipped;
|
|
194
199
|
const updatedPrefs = targetAgents.reduce((current, agent) => (0, config_1.enableAgent)(current, agent), prefs);
|
|
195
200
|
(0, config_1.savePreferences)({
|
|
196
201
|
...updatedPrefs,
|
|
@@ -497,7 +502,10 @@ program.command('remove')
|
|
|
497
502
|
});
|
|
498
503
|
handleCancel(scopeChoice);
|
|
499
504
|
const scopeVal = scopeChoice;
|
|
500
|
-
|
|
505
|
+
// projectRoot explicito: `config.local` es relativo, y resolverlo contra
|
|
506
|
+
// `process.cwd()` hacia que `awm remove` no encontrara nada desde un
|
|
507
|
+
// subdirectorio — y que le pasara rutas RELATIVAS a fs.rmSync cuando si.
|
|
508
|
+
const installed = (0, provider_artifacts_1.scanLegacyArtifacts)(targetAgents, scopeVal, (0, profile_1.findProjectRoot)(process.cwd()) ?? process.cwd());
|
|
501
509
|
if (installed.length === 0) {
|
|
502
510
|
(0, prompts_1.outro)(picocolors_1.default.yellow('No installed artifacts found for the selected agents/scope.'));
|
|
503
511
|
process.exit(0);
|
|
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.UnsupportedRendererError = exports.AGENT_TARGETS = void 0;
|
|
6
|
+
exports.UnsupportedRendererError = exports.RENDERER_IDS = exports.AGENT_TARGETS = void 0;
|
|
7
7
|
exports.isAgentTarget = isAgentTarget;
|
|
8
8
|
exports.requireAgentTarget = requireAgentTarget;
|
|
9
9
|
exports.unsupportedScopeError = unsupportedScopeError;
|
|
@@ -18,6 +18,11 @@ exports.getInjection = getInjection;
|
|
|
18
18
|
const path_1 = __importDefault(require("path"));
|
|
19
19
|
const paths_1 = require("../core/paths");
|
|
20
20
|
exports.AGENT_TARGETS = ['antigravity', 'opencode', 'claude-code', 'codex', 'cursor', 'copilot'];
|
|
21
|
+
/** Runtime-enumerable, so the renderer table in `core/renderers/registry.ts` can be
|
|
22
|
+
* checked for completeness against it (tests/structural/renderer-table-is-single-source).
|
|
23
|
+
* A type-only union cannot be iterated, which is part of why four partial copies of the
|
|
24
|
+
* renderer→extension mapping could drift apart unnoticed. */
|
|
25
|
+
exports.RENDERER_IDS = ['link', 'codex-agent-toml', 'cursor-mdc', 'copilot-instructions'];
|
|
21
26
|
function isAgentTarget(value) {
|
|
22
27
|
return typeof value === 'string' &&
|
|
23
28
|
exports.AGENT_TARGETS.includes(value);
|
|
@@ -102,7 +107,7 @@ function providers() {
|
|
|
102
107
|
codex: {
|
|
103
108
|
label: 'Codex',
|
|
104
109
|
minimumVersion: '0.145.0',
|
|
105
|
-
versionCommand: { command: 'codex', args: ['--version'] },
|
|
110
|
+
versionCommand: { command: 'codex', args: ['--version'], versionPattern: /^codex-cli (\d+\.\d+\.\d+)$/ },
|
|
106
111
|
skill: {
|
|
107
112
|
global: path_1.default.join(home, '.agents/skills'),
|
|
108
113
|
local: '.agents/skills',
|
package/dist/src/utils/config.js
CHANGED
|
@@ -13,6 +13,7 @@ const fs_1 = __importDefault(require("fs"));
|
|
|
13
13
|
const path_1 = __importDefault(require("path"));
|
|
14
14
|
const providers_1 = require("../providers");
|
|
15
15
|
const paths_1 = require("../core/paths");
|
|
16
|
+
const atomic_file_1 = require("../core/atomic-file");
|
|
16
17
|
const DEFAULT_PREFS = {
|
|
17
18
|
// claude-code matches `awm init`'s own documented default (see init.ts / `awm init --help`).
|
|
18
19
|
// Previously 'antigravity', which silently mis-installed bundles in claude-code
|
|
@@ -118,14 +119,12 @@ function loadPreferences(initialAgent = 'claude-code') {
|
|
|
118
119
|
}
|
|
119
120
|
function savePreferences(prefs) {
|
|
120
121
|
const normalized = normalizePreferences(prefs, false).prefs;
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
});
|
|
128
|
-
fs_1.default.renameSync(temp, file);
|
|
122
|
+
// `writeFileAtomic`, not a second hand-rolled write-temp-then-rename. The local copy
|
|
123
|
+
// predated the primitive and had drifted below it: no fsync, a pid-only temp name,
|
|
124
|
+
// and none of the guards the shared one grew. Same 0o600 intent and same
|
|
125
|
+
// mode-preservation semantics as `artifact-state.ts`, the other 0o600 file written
|
|
126
|
+
// through it — a file of this sensitivity has an established treatment here.
|
|
127
|
+
(0, atomic_file_1.writeFileAtomic)(prefsFile(), JSON.stringify(normalized, null, 2) + '\n', 0o600);
|
|
129
128
|
}
|
|
130
129
|
function enableAgent(prefs, agent) {
|
|
131
130
|
normalizePreferences(prefs);
|
|
@@ -148,6 +148,12 @@ describe('runDoctor', () => {
|
|
|
148
148
|
expect(code).toBe(1);
|
|
149
149
|
});
|
|
150
150
|
it('reports every enabled provider and stable remediation codes in JSON', () => {
|
|
151
|
+
// El check `agents.native` solo se emite si hay artefactos nativos que
|
|
152
|
+
// verificar: un registry sin `agents/` ya no produce una fila roja sin
|
|
153
|
+
// remedio. Se siembra uno para que el check sea genuino en vez de
|
|
154
|
+
// asertar sobre una fila que hoy, con razon, no existiria.
|
|
155
|
+
fs_1.default.mkdirSync(path_1.default.join(tmpHome, '.codex', 'agents'), { recursive: true });
|
|
156
|
+
fs_1.default.writeFileSync(path_1.default.join(tmpHome, '.codex', 'agents', 'demo.toml'), 'name = "demo"\n');
|
|
151
157
|
writePrefs(prefsWith(['claude-code', 'opencode', 'codex']));
|
|
152
158
|
const code = (0, doctor_1.runDoctor)({ cwd: tmpWork, json: true });
|
|
153
159
|
const report = JSON.parse(stdout());
|