agentic-workflow-manager 3.13.5 → 3.13.7
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/scripts/support-matrix.js +138 -0
- 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 +34 -1
- 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/commands/watch/runner.test.js +10 -0
- 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/mutation-targets-canonical.test.js +81 -0
- package/dist/tests/core/init/mutation-targets.test.js +4 -4
- 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/support-matrix-is-current.test.js +100 -0
- package/dist/tests/structural/symlink-type-is-explicit.test.js +73 -0
- package/dist/tests/support/tmp.js +31 -0
- package/dist/tests/utils/registry-view-overrides.test.js +2 -2
- package/package.json +6 -2
|
@@ -5,6 +5,7 @@ const child_process_1 = require("child_process");
|
|
|
5
5
|
const init_1 = require("./init");
|
|
6
6
|
const supervisor_1 = require("./supervisor");
|
|
7
7
|
const process_1 = require("../../core/journal/process");
|
|
8
|
+
const adapter_1 = require("../../core/journal/adapter");
|
|
8
9
|
function currentBranch(cwd) {
|
|
9
10
|
// stdio explicito (ver EXEC_STDIO en journal/process.ts): evita el relay
|
|
10
11
|
// default de execFileSync del stderr de git hacia el stderr del llamante,
|
|
@@ -25,7 +26,7 @@ function registerWatchCommand(program) {
|
|
|
25
26
|
.command('watch')
|
|
26
27
|
.description('supervisor durable: ejecuta jobs, releva controladores caidos, nunca mata trabajo vivo')
|
|
27
28
|
.option('--init', 'bootstrap: crea el journal de la rama actual, detecta verificadores y sale')
|
|
28
|
-
.option('--provider <p>', '
|
|
29
|
+
.option('--provider <p>', adapter_1.WATCH_PROVIDERS.join(' | '), 'codex')
|
|
29
30
|
.option('--heartbeat-timeout <min>', 'minutos de silencio de heartbeat', '5')
|
|
30
31
|
.option('--activity-window <min>', 'minutos extra sin actividad de proceso', '10')
|
|
31
32
|
.action(async (opts) => {
|
|
@@ -36,6 +37,15 @@ function registerWatchCommand(program) {
|
|
|
36
37
|
process.stdout.write(`journal inicializado para ${branch}; verificadores requeridos: ${JSON.stringify(out.requiredVerifiers)}\n`);
|
|
37
38
|
return;
|
|
38
39
|
}
|
|
40
|
+
// Validado ACA, antes de tocar nada: `adapterFor` ya rechazaba lo
|
|
41
|
+
// desconocido, pero recien en el primer tick — con el journal escrito y el
|
|
42
|
+
// lock tomado, y el error saliendo del supervisor en vez de del flag que lo
|
|
43
|
+
// causo. Un typo en `--provider` tiene que costar un mensaje, no un ciclo.
|
|
44
|
+
if (!(0, adapter_1.isWatchProvider)(opts.provider)) {
|
|
45
|
+
process.stderr.write(`--provider invalido: ${String(opts.provider)} (validos: ${adapter_1.WATCH_PROVIDERS.join(', ')})\n`);
|
|
46
|
+
process.exitCode = 1;
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
39
49
|
const cfg = {
|
|
40
50
|
...supervisor_1.DEFAULT_SUPERVISOR_CONFIG,
|
|
41
51
|
provider: opts.provider,
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// src/core/artifact-name.ts
|
|
3
|
+
//
|
|
4
|
+
// Modulo HOJA (sin imports de otros modulos del proyecto): validacion de los
|
|
5
|
+
// nombres de artefacto que llegan desde CONTENIDO DE REGISTRY.
|
|
6
|
+
//
|
|
7
|
+
// Por que existe. Los nombres de `bundle.json` (`skills[]`, `workflows[]`,
|
|
8
|
+
// `agents[]`) se usaban verbatim para construir rutas de instalacion:
|
|
9
|
+
//
|
|
10
|
+
// path.join('~/.claude/skills', '../../.ssh/authorized_keys')
|
|
11
|
+
// => '~/.ssh/authorized_keys'
|
|
12
|
+
//
|
|
13
|
+
// y como `replaceArtifact` hace `fs.rmSync(targetPath, {recursive:true})` antes
|
|
14
|
+
// de enlazar, un nombre como `../../.ssh` BORRABA recursivamente el ~/.ssh real
|
|
15
|
+
// del usuario. Con `../../.config/autostart/x.desktop` se consigue ejecucion al
|
|
16
|
+
// siguiente login. Confirmado end-to-end contra el binario real.
|
|
17
|
+
//
|
|
18
|
+
// El registry es contenido de terceros: un registry de equipo, uno interno, o
|
|
19
|
+
// uno que alguien agrego con `awm registry add`. Todos los demas lectores de
|
|
20
|
+
// contenido de registry de este repo (readRegistriesConfig, readRegistryManifest,
|
|
21
|
+
// readProfile) ya rechazaban `..` y separadores — este camino era el unico sin
|
|
22
|
+
// la guarda.
|
|
23
|
+
//
|
|
24
|
+
// Esta validacion es la PRIMERA de dos capas. La segunda es la asercion de
|
|
25
|
+
// contencion en `physicalTarget` (install-planner.ts), que verifica que la ruta
|
|
26
|
+
// resuelta caiga realmente dentro del directorio destino. Se mantienen las dos
|
|
27
|
+
// a proposito: esta da un mensaje accionable que nombra el artefacto culpable;
|
|
28
|
+
// aquella es el ancla estructural que atrapa cualquier camino futuro que
|
|
29
|
+
// construya rutas sin pasar por aca.
|
|
30
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
31
|
+
exports.isSafeArtifactName = isSafeArtifactName;
|
|
32
|
+
exports.assertSafeArtifactName = assertSafeArtifactName;
|
|
33
|
+
/** Nombres reservados de Windows: no pueden ser un archivo ni un directorio.
|
|
34
|
+
* Se rechazan en TODA plataforma a proposito — el registry es contenido
|
|
35
|
+
* compartido, y un nombre que solo rompe en las maquinas Windows del equipo es
|
|
36
|
+
* peor que uno rechazado de forma consistente en todas. */
|
|
37
|
+
const WINDOWS_RESERVED = /^(con|prn|aux|nul|com[1-9]|lpt[1-9])(\..*)?$/i;
|
|
38
|
+
/** ¿Es seguro usar este nombre como UN componente de ruta dentro del directorio
|
|
39
|
+
* de instalacion? Sin `..`, sin separadores, sin rutas absolutas, sin bytes de
|
|
40
|
+
* control, sin nombres reservados. */
|
|
41
|
+
function isSafeArtifactName(name) {
|
|
42
|
+
if (typeof name !== 'string')
|
|
43
|
+
return false;
|
|
44
|
+
const value = name.trim();
|
|
45
|
+
if (value === '' || value !== name)
|
|
46
|
+
return false; // vacio, o con espacios al borde
|
|
47
|
+
// Windows recorta en silencio el punto/espacio final, asi que `evil.` y
|
|
48
|
+
// `evil` terminan en el MISMO archivo — un nombre que apunta a otro destino
|
|
49
|
+
// del que aparenta.
|
|
50
|
+
if (/[. ]$/.test(value))
|
|
51
|
+
return false;
|
|
52
|
+
// Bytes de control y NUL: truncan la ruta a nivel syscall en algunos SO.
|
|
53
|
+
// eslint-disable-next-line no-control-regex
|
|
54
|
+
if (/[\u0000-\u001f\u007f]/.test(value))
|
|
55
|
+
return false;
|
|
56
|
+
// Cualquier separador (de ambas plataformas) convierte esto en una ruta,
|
|
57
|
+
// no en un nombre. Idem `..`/`.` como componente completo.
|
|
58
|
+
if (value.includes('/') || value.includes('\\'))
|
|
59
|
+
return false;
|
|
60
|
+
if (value === '.' || value === '..')
|
|
61
|
+
return false;
|
|
62
|
+
// Absolutos de Windows (`C:\...`, `C:algo`) y de UNC ya quedan cubiertos por
|
|
63
|
+
// el chequeo de separador, pero un `C:` pelado no — y sigue siendo una
|
|
64
|
+
// referencia de unidad, no un nombre.
|
|
65
|
+
if (/^[a-zA-Z]:/.test(value))
|
|
66
|
+
return false;
|
|
67
|
+
if (WINDOWS_RESERVED.test(value))
|
|
68
|
+
return false;
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
/** Forma asertiva: lanza con un mensaje que nombra el tipo y el valor ofensivo,
|
|
72
|
+
* para que el operador pueda ubicarlo en el `bundle.json` del registry. */
|
|
73
|
+
function assertSafeArtifactName(name, type) {
|
|
74
|
+
if (!isSafeArtifactName(name)) {
|
|
75
|
+
throw new Error(`unsafe ${type} name from registry content: ${JSON.stringify(name)}. ` +
|
|
76
|
+
`Artifact names must be a single path component — no "..", no path separators, ` +
|
|
77
|
+
`no absolute paths, no control characters, and not a Windows reserved name.`);
|
|
78
|
+
}
|
|
79
|
+
return name;
|
|
80
|
+
}
|
|
@@ -10,6 +10,7 @@ exports.syncProfile = syncProfile;
|
|
|
10
10
|
// src/core/bundle-install.ts
|
|
11
11
|
const fs_1 = __importDefault(require("fs"));
|
|
12
12
|
const path_1 = __importDefault(require("path"));
|
|
13
|
+
const artifact_name_1 = require("./artifact-name");
|
|
13
14
|
const bundles_1 = require("./bundles");
|
|
14
15
|
const providers_1 = require("../providers");
|
|
15
16
|
const profile_1 = require("./profile");
|
|
@@ -18,14 +19,24 @@ const config_1 = require("../utils/config");
|
|
|
18
19
|
const install_planner_1 = require("./install-planner");
|
|
19
20
|
const install_transaction_1 = require("./install-transaction");
|
|
20
21
|
function bundleArtifacts(b, contentDir) {
|
|
22
|
+
// Estos nombres vienen VERBATIM del `bundle.json` de un registry — contenido
|
|
23
|
+
// de terceros. Sin validar, `path.join(installDir, name)` con `../../.ssh`
|
|
24
|
+
// escapaba del directorio destino, y como el instalador hace
|
|
25
|
+
// `rmSync(target, {recursive:true})` antes de enlazar, eso BORRABA el ~/.ssh
|
|
26
|
+
// real del usuario. Se valida en el origen, donde el mensaje puede nombrar
|
|
27
|
+
// el artefacto culpable; `physicalTarget` ademas asegura contencion
|
|
28
|
+
// estructural sobre la ruta ya resuelta.
|
|
21
29
|
const refs = [];
|
|
22
30
|
for (const s of b.skills) {
|
|
31
|
+
(0, artifact_name_1.assertSafeArtifactName)(s.name, 'skill');
|
|
23
32
|
refs.push({ name: s.name, type: 'skill', installName: s.name, sourcePath: path_1.default.join(contentDir, 'skills', s.name) });
|
|
24
33
|
}
|
|
25
34
|
for (const w of b.workflows) {
|
|
35
|
+
(0, artifact_name_1.assertSafeArtifactName)(w, 'workflow');
|
|
26
36
|
refs.push({ name: w, type: 'workflow', installName: `${w}.md`, sourcePath: path_1.default.join(contentDir, 'workflows', `${w}.md`) });
|
|
27
37
|
}
|
|
28
38
|
for (const a of b.agents) {
|
|
39
|
+
(0, artifact_name_1.assertSafeArtifactName)(a, 'agent');
|
|
29
40
|
refs.push({ name: a, type: 'agent', installName: `${a}.md`, sourcePath: path_1.default.join(contentDir, 'agents', `${a}.md`) });
|
|
30
41
|
}
|
|
31
42
|
return refs;
|
|
@@ -109,30 +120,62 @@ function addBundle(opts) {
|
|
|
109
120
|
*/
|
|
110
121
|
function syncProfile(opts) {
|
|
111
122
|
const profile = (0, profile_1.readProfile)(opts.projectRoot);
|
|
112
|
-
const installed = [];
|
|
113
123
|
const skipped = [];
|
|
114
|
-
|
|
115
|
-
|
|
124
|
+
// ONE plan for the whole sync, not one per extension.
|
|
125
|
+
//
|
|
126
|
+
// This used to loop `installBundle` over `profile.extensions`, so N extensions
|
|
127
|
+
// meant N independent `applyInstallPlan` transactions. Each is atomic on its own;
|
|
128
|
+
// none is atomic with the others. A failure on the third extension left the first
|
|
129
|
+
// two committed and the tree in a state matching neither before nor after — and
|
|
130
|
+
// `awm sync` is the command a team runs to make a checkout match its profile, so
|
|
131
|
+
// "partially matches" is the one outcome it must not produce.
|
|
132
|
+
//
|
|
133
|
+
// Merging is safe because the two halves were already built for it: expanding a
|
|
134
|
+
// bundle (`expandBundleArtifacts`) is pure, and `planInstall` dedups by physical
|
|
135
|
+
// target across ALL the artifacts it is handed — which is also why two extensions
|
|
136
|
+
// sharing a dependency now collapse into one operation instead of racing to write
|
|
137
|
+
// the same path twice. `applyInstallPlan` backs up and rolls back per plan, so one
|
|
138
|
+
// plan is precisely what makes the sync atomic.
|
|
139
|
+
const intents = [];
|
|
116
140
|
for (const ext of profile.extensions) {
|
|
117
|
-
|
|
141
|
+
const bundle = opts.bundles.find((b) => b.name === ext);
|
|
142
|
+
if (!bundle) {
|
|
118
143
|
skipped.push(`${ext} (bundle not found in registry — remove with \`awm remove ${ext}\`)`);
|
|
119
144
|
continue;
|
|
120
145
|
}
|
|
121
|
-
|
|
146
|
+
intents.push(...expandBundleArtifacts({
|
|
122
147
|
bundleName: ext,
|
|
123
148
|
bundles: opts.bundles,
|
|
124
149
|
agents: opts.agents,
|
|
125
150
|
method: opts.method,
|
|
126
151
|
projectRoot: opts.projectRoot,
|
|
127
152
|
contentDir: opts.contentDir,
|
|
128
|
-
|
|
129
|
-
});
|
|
130
|
-
installed.push(...summary.installed);
|
|
131
|
-
skipped.push(...summary.skipped);
|
|
132
|
-
modifiedFiles.push(...summary.modifiedFiles);
|
|
133
|
-
transactionIds.push(summary.transactionId);
|
|
153
|
+
}));
|
|
134
154
|
}
|
|
135
155
|
if (profile.extensions.length > 0)
|
|
136
156
|
(0, profile_1.ensureSkillsGitignored)(opts.projectRoot, opts.agents);
|
|
137
|
-
|
|
157
|
+
// Nothing resolvable: no plan, no transaction, nothing to name to `awm backup
|
|
158
|
+
// restore`. An empty `transactionIds` is the honest answer, not a phantom id.
|
|
159
|
+
if (intents.length === 0) {
|
|
160
|
+
return { installed: [], skipped, extensions: profile.extensions, transactionIds: [], modifiedFiles: [] };
|
|
161
|
+
}
|
|
162
|
+
const plan = (0, install_planner_1.planInstall)({
|
|
163
|
+
artifacts: intents,
|
|
164
|
+
selectedAgents: opts.agents,
|
|
165
|
+
enabledAgents: (0, config_1.getPreferences)().enabledAgents,
|
|
166
|
+
// Every extension in the profile is a project-scope bundle by construction
|
|
167
|
+
// (`shouldRecordExtension` only records those), so the whole sync resolves to
|
|
168
|
+
// one scope — which is what lets it be one plan.
|
|
169
|
+
scope: 'local',
|
|
170
|
+
projectRoot: opts.projectRoot,
|
|
171
|
+
method: opts.method,
|
|
172
|
+
});
|
|
173
|
+
const summary = (opts.applyPlan ?? install_transaction_1.applyInstallPlan)(plan);
|
|
174
|
+
return {
|
|
175
|
+
installed: summary.installed,
|
|
176
|
+
skipped: [...skipped, ...summary.skipped],
|
|
177
|
+
extensions: profile.extensions,
|
|
178
|
+
transactionIds: [summary.transactionId],
|
|
179
|
+
modifiedFiles: summary.modifiedFiles,
|
|
180
|
+
};
|
|
138
181
|
}
|
package/dist/src/core/bundles.js
CHANGED
|
@@ -16,15 +16,59 @@ const registries_1 = require("./registries");
|
|
|
16
16
|
function catalogPath(contentDir) {
|
|
17
17
|
return path_1.default.join(contentDir, 'catalog.json');
|
|
18
18
|
}
|
|
19
|
+
/** Lee `catalog.json` de un registry.
|
|
20
|
+
*
|
|
21
|
+
* El contenido de un registry es INPUT NO CONFIABLE (un registry de equipo,
|
|
22
|
+
* interno, o agregado con `awm registry add`), asi que se valida forma antes de
|
|
23
|
+
* desreferenciar. Sin esto, un solo archivo malformado en CUALQUIER registry
|
|
24
|
+
* — incluido uno que llega por un `awm update` rutinario — hacia que `awm list`
|
|
25
|
+
* y `awm add` murieran con un stack trace crudo de Node y ningun mensaje
|
|
26
|
+
* accionable. Los demas lectores de contenido de registry de este repo
|
|
27
|
+
* (readRegistriesConfig, readRegistryManifest, readProfile) ya validaban; estos
|
|
28
|
+
* dos eran los que faltaban. */
|
|
19
29
|
function readCatalog(contentDir) {
|
|
20
30
|
const file = catalogPath(contentDir);
|
|
21
31
|
if (!fs_1.default.existsSync(file))
|
|
22
32
|
return [];
|
|
23
|
-
|
|
24
|
-
|
|
33
|
+
let parsed;
|
|
34
|
+
try {
|
|
35
|
+
parsed = JSON.parse(fs_1.default.readFileSync(file, 'utf-8'));
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
throw new Error(`${file} is not valid JSON. Fix the file in its registry, then re-run \`awm update\`.`);
|
|
39
|
+
}
|
|
40
|
+
if (parsed === null || typeof parsed !== 'object') {
|
|
41
|
+
throw new Error(`${file} must contain a JSON object.`);
|
|
42
|
+
}
|
|
43
|
+
const bundles = parsed.bundles;
|
|
44
|
+
if (bundles === undefined)
|
|
45
|
+
return [];
|
|
46
|
+
if (!Array.isArray(bundles)) {
|
|
47
|
+
throw new Error(`${file}: "bundles" must be an array.`);
|
|
48
|
+
}
|
|
49
|
+
// Una entrada sin `source` usable haria que `path.join` tirara un
|
|
50
|
+
// TypeError opaco mas adelante; se descarta aca, nombrando el archivo.
|
|
51
|
+
return bundles.filter((entry) => entry !== null && typeof entry === 'object' && typeof entry.source === 'string');
|
|
25
52
|
}
|
|
53
|
+
/** Tolera `skills` ausente o mal formado: lo que no sea un array se trata como
|
|
54
|
+
* vacio en vez de tirar `(raw ?? []).map is not a function`, y cada entrada se
|
|
55
|
+
* valida individualmente. */
|
|
26
56
|
function normalizeSkillRefs(raw) {
|
|
27
|
-
|
|
57
|
+
if (!Array.isArray(raw))
|
|
58
|
+
return [];
|
|
59
|
+
return raw.flatMap((s) => {
|
|
60
|
+
if (typeof s === 'string')
|
|
61
|
+
return [{ name: s, onSignal: false }];
|
|
62
|
+
if (s !== null && typeof s === 'object' && typeof s.name === 'string') {
|
|
63
|
+
return [{ name: s.name, onSignal: s.onSignal === true }];
|
|
64
|
+
}
|
|
65
|
+
return [];
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
/** Array de strings, o vacio. Cualquier otra forma en el contenido del registry
|
|
69
|
+
* se ignora en vez de propagarse hasta un crash aguas abajo. */
|
|
70
|
+
function stringArray(raw) {
|
|
71
|
+
return Array.isArray(raw) ? raw.filter((v) => typeof v === 'string') : [];
|
|
28
72
|
}
|
|
29
73
|
function discoverBundles(contentDir) {
|
|
30
74
|
const entries = readCatalog(contentDir);
|
|
@@ -33,17 +77,32 @@ function discoverBundles(contentDir) {
|
|
|
33
77
|
const manifestPath = path_1.default.join(contentDir, entry.source, 'bundle.json');
|
|
34
78
|
if (!fs_1.default.existsSync(manifestPath))
|
|
35
79
|
continue;
|
|
36
|
-
|
|
80
|
+
let raw;
|
|
81
|
+
try {
|
|
82
|
+
raw = JSON.parse(fs_1.default.readFileSync(manifestPath, 'utf-8'));
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
throw new Error(`${manifestPath} is not valid JSON. Fix the file in its registry, then re-run \`awm update\`.`);
|
|
86
|
+
}
|
|
87
|
+
if (raw === null || typeof raw !== 'object') {
|
|
88
|
+
throw new Error(`${manifestPath} must contain a JSON object.`);
|
|
89
|
+
}
|
|
90
|
+
const m = raw;
|
|
91
|
+
// Un bundle sin nombre usable no es instalable y solo produciria errores
|
|
92
|
+
// opacos mas adelante — se nombra el archivo y se falla aca.
|
|
93
|
+
if (typeof m.name !== 'string' || m.name === '') {
|
|
94
|
+
throw new Error(`${manifestPath}: "name" must be a non-empty string.`);
|
|
95
|
+
}
|
|
37
96
|
bundles.push({
|
|
38
|
-
name:
|
|
39
|
-
description:
|
|
40
|
-
version:
|
|
41
|
-
scope:
|
|
42
|
-
visibility:
|
|
43
|
-
dependsOn:
|
|
44
|
-
skills: normalizeSkillRefs(
|
|
45
|
-
workflows:
|
|
46
|
-
agents:
|
|
97
|
+
name: m.name,
|
|
98
|
+
description: typeof m.description === 'string' ? m.description : '',
|
|
99
|
+
version: typeof m.version === 'string' ? m.version : '0.0.0',
|
|
100
|
+
scope: m.scope ?? 'project',
|
|
101
|
+
visibility: m.visibility ?? 'public',
|
|
102
|
+
dependsOn: stringArray(m.dependsOn),
|
|
103
|
+
skills: normalizeSkillRefs(m.skills),
|
|
104
|
+
workflows: stringArray(m.workflows),
|
|
105
|
+
agents: stringArray(m.agents),
|
|
47
106
|
contentRoot: contentDir,
|
|
48
107
|
});
|
|
49
108
|
}
|
|
@@ -5,7 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.regenerateGlobalContext = regenerateGlobalContext;
|
|
7
7
|
// W3 — Regeneración del contexto global tras `awm update`.
|
|
8
|
-
// Para cada agente con
|
|
8
|
+
// Para cada agente con un mecanismo de inyección aplicable:
|
|
9
9
|
// - 'stale' (sentinel presente, archivo materializado ausente/viejo) → re-materializa.
|
|
10
10
|
// - 'injected' (ya fresco) → no toca nada.
|
|
11
11
|
// - 'absent' (sentinel ausente) → no inyecta (eso es `awm init`).
|
|
@@ -21,10 +21,27 @@ function regenerateGlobalContext(targets = [...providers_1.AGENT_TARGETS], orch
|
|
|
21
21
|
const out = [];
|
|
22
22
|
for (const agent of targets) {
|
|
23
23
|
const inj = (0, providers_1.providerFor)(agent).injection;
|
|
24
|
-
if (!inj
|
|
25
|
-
continue;
|
|
26
|
-
if (!fs_1.default.existsSync(inj.configPath))
|
|
24
|
+
if (!inj)
|
|
27
25
|
continue;
|
|
26
|
+
// Antes esto exigia `type === 'config-instructions'`, o sea que SOLO se
|
|
27
|
+
// regeneraba OpenCode: para codex, cursor y copilot (`managed-agents-md`)
|
|
28
|
+
// `awm update` era un no-op silencioso — ni un aviso — y el unico modo
|
|
29
|
+
// de refrescar su contexto era volver a correr `awm init`. Peor: doctor
|
|
30
|
+
// marcaba ⚠ pero seguia reportando `status: healthy` / exit 0, asi que
|
|
31
|
+
// un AGENTS.md desactualizado era invisible para cualquier gate de CI
|
|
32
|
+
// que mirara el codigo de salida.
|
|
33
|
+
//
|
|
34
|
+
// El destino global de `managed-agents-md` puede ser null (cursor y
|
|
35
|
+
// copilot no tienen archivo de contexto global); ahi no hay nada global
|
|
36
|
+
// que regenerar y se saltea limpio.
|
|
37
|
+
if (inj.type === 'config-instructions') {
|
|
38
|
+
if (!fs_1.default.existsSync(inj.configPath))
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
else if (inj.type === 'managed-agents-md') {
|
|
42
|
+
if (inj.globalPath === null)
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
28
45
|
const op = {
|
|
29
46
|
agent,
|
|
30
47
|
scope: 'global',
|
|
@@ -101,9 +101,25 @@ class CodexAgentsStrategy {
|
|
|
101
101
|
// separate global file, so this is the only writer of the project AGENTS.md.
|
|
102
102
|
const injection = provider.injection;
|
|
103
103
|
const contextCoversThisFile = injection?.type === 'managed-agents-md' && injection.globalPath === null;
|
|
104
|
-
|
|
104
|
+
// La guarda de arriba solo mira a ESTE provider. Pero el bloque es uno
|
|
105
|
+
// solo por archivo, y OTRO provider ya habilitado (cursor/copilot, cuyo
|
|
106
|
+
// contexto ES este archivo) pudo haberlo escrito antes — con el contexto
|
|
107
|
+
// AWM completo, del que PROJECT_GUIDANCE es apenas la cola. Sin este
|
|
108
|
+
// chequeo, un `awm init -a codex` posterior lo reemplazaba por las 6
|
|
109
|
+
// lineas de PROJECT_GUIDANCE: 140 -> 6, y para Copilot ese archivo es su
|
|
110
|
+
// UNICO canal de entrega, asi que se quedaba sin nada. Y al reves otra
|
|
111
|
+
// vez en el siguiente init. Se compara contra el bloque real en disco en
|
|
112
|
+
// vez de asumir quien escribio: si ya cubre PROJECT_GUIDANCE, lo que hay
|
|
113
|
+
// es un superconjunto y pisarlo seria perder informacion.
|
|
114
|
+
const agentsMdPath = path_1.default.join(projectRoot, 'AGENTS.md');
|
|
115
|
+
const existingBody = fs_1.default.existsSync(agentsMdPath)
|
|
116
|
+
? (0, managed_block_1.managedBlockBody)(fs_1.default.readFileSync(agentsMdPath, 'utf-8'))
|
|
117
|
+
: null;
|
|
118
|
+
const alreadyCovered = existingBody !== null &&
|
|
119
|
+
(0, managed_block_1.normalizeManagedBody)(existingBody).includes((0, managed_block_1.normalizeManagedBody)(PROJECT_GUIDANCE));
|
|
120
|
+
const result = contextCoversThisFile || alreadyCovered
|
|
105
121
|
? 'unchanged'
|
|
106
|
-
: injectFile(
|
|
122
|
+
: injectFile(agentsMdPath, PROJECT_GUIDANCE);
|
|
107
123
|
let carrierResult = 'unchanged';
|
|
108
124
|
if (agent === 'cursor') {
|
|
109
125
|
// Cursor's Background/Cloud Agent does not reliably read AGENTS.md (open,
|
|
@@ -30,7 +30,15 @@ function machineChecks(m) {
|
|
|
30
30
|
detail: `git ${m.registryCache.gitState ?? 'unknown'}`, remedy: none });
|
|
31
31
|
}
|
|
32
32
|
// machine.hook
|
|
33
|
-
|
|
33
|
+
// Un provider sin mecanismo de hooks no puede "tener el hook instalado":
|
|
34
|
+
// no se emite fila alguna, igual que ya hacen el paso de init
|
|
35
|
+
// (init/steps.ts, "no hook mechanism for this agent") y el reporte por
|
|
36
|
+
// provider (provider-checks.ts, que descarta la fila). Emitir `missing`
|
|
37
|
+
// aca era lo que ponia `awm init` en exit 1 para 4 de los 6 providers.
|
|
38
|
+
if (!m.hook.applicable) {
|
|
39
|
+
// sin fila
|
|
40
|
+
}
|
|
41
|
+
else if (m.hook.present && !m.hook.degraded) {
|
|
34
42
|
out.push({ id: 'machine.hook', level: 'machine', label: 'hook SessionStart', status: 'ok', remedy: none });
|
|
35
43
|
}
|
|
36
44
|
else if (m.hook.present) {
|
|
@@ -106,6 +114,17 @@ function projectChecks(p) {
|
|
|
106
114
|
out.push({ id: 'project.activation', level: 'project', label: 'active bundles', status: 'missing',
|
|
107
115
|
detail: `${missingLinks.length} missing, ${p.activeBundles.broken.length} broken`, remedy: cmd('awm sync') });
|
|
108
116
|
}
|
|
117
|
+
// project.orphans — links colgantes que ya no pertenecen a ninguna extension.
|
|
118
|
+
// Fila propia y no parte de `project.activation`: ese check mide "falta lo que el
|
|
119
|
+
// profile pide", y esto es lo contrario — sobra lo que el profile ya no pide. Sin
|
|
120
|
+
// separarlos, un proyecto con todo lo esperado instalado reportaba `ok` mientras
|
|
121
|
+
// arrastraba skills muertas que el agente sigue intentando leer.
|
|
122
|
+
const orphans = p.orphanLinks.repairable.length + p.orphanLinks.dead.length;
|
|
123
|
+
if (orphans > 0) {
|
|
124
|
+
out.push({ id: 'project.orphans', level: 'project', label: 'orphaned skill links', status: 'warn',
|
|
125
|
+
detail: `${p.orphanLinks.repairable.length} repairable, ${p.orphanLinks.dead.length} dead`,
|
|
126
|
+
remedy: cmd('awm sync') });
|
|
127
|
+
}
|
|
109
128
|
// project.sensors
|
|
110
129
|
out.push(p.sensors.present
|
|
111
130
|
? { id: 'project.sensors', level: 'project', label: 'sensors', status: 'ok', remedy: none }
|
|
@@ -137,7 +156,12 @@ function projectChecks(p) {
|
|
|
137
156
|
// actionable-but-expected transient states (rendered with ◷/⚠, not ✖) and
|
|
138
157
|
// do not by themselves degrade `overall`, mirroring how a 'warn' CheckResult
|
|
139
158
|
// doesn't degrade the machine+project report either.
|
|
140
|
-
|
|
159
|
+
// `stale` cuenta como degradado: un contexto desactualizado es exactamente la
|
|
160
|
+
// clase de problema que un gate de CI tiene que ver. Antes doctor imprimia ⚠
|
|
161
|
+
// junto a `status: healthy` y exit 0, asi que un AGENTS.md viejo — o sobrescrito
|
|
162
|
+
// por otro provider — era invisible para cualquier script que mirara el codigo
|
|
163
|
+
// de salida. Un aviso que no cambia el veredicto no es un aviso, es ruido.
|
|
164
|
+
const DEGRADING_PROVIDER_STATES = ['missing', 'unsupported', 'broken', 'absent', 'conflict', 'stale'];
|
|
141
165
|
function computeProviderOverall(providers) {
|
|
142
166
|
const degraded = providers.some((p) => p.checks.some((c) => DEGRADING_PROVIDER_STATES.includes(c.state)));
|
|
143
167
|
return degraded ? 'degraded' : 'healthy';
|