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
|
@@ -9,6 +9,7 @@ const fs_1 = __importDefault(require("fs"));
|
|
|
9
9
|
const path_1 = __importDefault(require("path"));
|
|
10
10
|
const child_process_1 = require("child_process");
|
|
11
11
|
const providers_1 = require("../../providers");
|
|
12
|
+
const registry_1 = require("../renderers/registry");
|
|
12
13
|
const registries_1 = require("../registries");
|
|
13
14
|
const orchestrator_1 = require("../context/orchestrator");
|
|
14
15
|
const status_1 = require("../../commands/hooks/status");
|
|
@@ -31,15 +32,29 @@ function linkState(dir, skill) {
|
|
|
31
32
|
return fs_1.default.existsSync(p) ? 'present' : 'broken';
|
|
32
33
|
return 'present'; // un dir/archivo real también cuenta como presente
|
|
33
34
|
}
|
|
34
|
-
|
|
35
|
+
/** Clasifica artefactos por su presencia en disco.
|
|
36
|
+
*
|
|
37
|
+
* `renderer` NO es opcional a proposito. El escritor (`physicalTarget`) estampa
|
|
38
|
+
* la extension del renderer sobre el nombre — `using-awm.mdc`,
|
|
39
|
+
* `using-awm.instructions.md` — y este lector buscaba el nombre pelado
|
|
40
|
+
* (`using-awm`), asi que para Cursor y Copilot NADA figuraba nunca como
|
|
41
|
+
* instalado: `awm init` reinstalaba el baseline completo en cada corrida (2
|
|
42
|
+
* directorios de backup sin podar por corrida) y `awm doctor` mostraba un rojo
|
|
43
|
+
* permanente cuyo remedio no podia satisfacerlo. Exigir el renderer obliga a
|
|
44
|
+
* cada caller a declarar de que artefacto habla, y `renderedFilename` es la
|
|
45
|
+
* misma funcion que usa el escritor — no pueden volver a discrepar.
|
|
46
|
+
*
|
|
47
|
+
* Los nombres devueltos son los LOGICOS (los de entrada), no los fisicos: los
|
|
48
|
+
* consumidores comparan contra listas de nombres de bundle. */
|
|
49
|
+
function classifyLinks(names, dir, renderer) {
|
|
35
50
|
const linked = [];
|
|
36
51
|
const broken = [];
|
|
37
|
-
for (const
|
|
38
|
-
const st = linkState(dir,
|
|
52
|
+
for (const name of names) {
|
|
53
|
+
const st = linkState(dir, (0, registry_1.renderedFilename)(name, renderer));
|
|
39
54
|
if (st === 'present')
|
|
40
|
-
linked.push(
|
|
55
|
+
linked.push(name);
|
|
41
56
|
else if (st === 'broken')
|
|
42
|
-
broken.push(
|
|
57
|
+
broken.push(name);
|
|
43
58
|
}
|
|
44
59
|
return { linked, broken };
|
|
45
60
|
}
|
|
@@ -91,7 +106,7 @@ function gatherContextInjection() {
|
|
|
91
106
|
}
|
|
92
107
|
return out;
|
|
93
108
|
}
|
|
94
|
-
function gatherMachine(bundles, agent = 'claude-code') {
|
|
109
|
+
function gatherMachine(bundles, agent = 'claude-code', projectRoot) {
|
|
95
110
|
// registryCache: estado del primer registry configurado (baseline en una máquina sembrada)
|
|
96
111
|
const regs = (0, registries_1.listRegistries)();
|
|
97
112
|
const first = regs[0];
|
|
@@ -105,21 +120,33 @@ function gatherMachine(bundles, agent = 'claude-code') {
|
|
|
105
120
|
// caught while building this task's real end-to-end init test).
|
|
106
121
|
let hookPresent = false;
|
|
107
122
|
let hookDegraded = false;
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
123
|
+
// La aplicabilidad se computa DONDE el provider esta en alcance, no se
|
|
124
|
+
// infiere de que `computeHookStatus` haya tirado (un throw puede ser "no
|
|
125
|
+
// soporta hooks" o un error real, y confundirlos fue el bug).
|
|
126
|
+
const hookApplicable = (0, providers_1.providerFor)(agent).hooks !== undefined;
|
|
127
|
+
if (hookApplicable) {
|
|
128
|
+
try {
|
|
129
|
+
const hs = (0, status_1.computeHookStatus)(agent);
|
|
130
|
+
hookPresent = hs.checks.settingsEntry.ok;
|
|
131
|
+
hookDegraded = hs.overall === 'DEGRADED';
|
|
132
|
+
}
|
|
133
|
+
catch { /* soporta hooks pero no se pudo leer el estado → ausente */ }
|
|
112
134
|
}
|
|
113
|
-
catch { /* sin soporte de hooks → ausente */ }
|
|
114
135
|
// devCore (bundle baseline) — skillsDir is null only for providers with no global skill
|
|
115
136
|
// discovery mechanism (today: Copilot); for those, there is no global-scope devCore
|
|
116
137
|
// concept to satisfy at all, so it's treated as trivially satisfied (see below).
|
|
117
138
|
const skillsDir = (0, providers_1.providerFor)(agent).skill.global;
|
|
118
|
-
|
|
139
|
+
// TODOS los baseline, igual que `stepDevCore` los instala y que
|
|
140
|
+
// `reconciliation.ts` los reconcilia. Con `find`, el hecho que decide si el
|
|
141
|
+
// baseline esta satisfecho miraba solo el primero, asi que un segundo bundle
|
|
142
|
+
// baseline sin instalar reportaba `healthy` — y el step, que lee este mismo
|
|
143
|
+
// hecho, nunca lo instalaba.
|
|
144
|
+
const baselines = bundles.filter((b) => b.scope === 'baseline');
|
|
145
|
+
const baseline = baselines[0];
|
|
119
146
|
let devCorePresent = false;
|
|
120
147
|
let brokenLinks = [];
|
|
121
148
|
if (baseline) {
|
|
122
|
-
const skillNames = (0, bundles_1.resolveBundleSkills)(
|
|
149
|
+
const skillNames = [...new Set(baselines.flatMap((b) => (0, bundles_1.resolveBundleSkills)(b.name, bundles)))];
|
|
123
150
|
if (skillsDir === null) {
|
|
124
151
|
// No global skill directory for this agent (today: Copilot) — there is no
|
|
125
152
|
// global-scope devCore/baseline-bundle concept to satisfy for it at all, so
|
|
@@ -131,10 +158,28 @@ function gatherMachine(bundles, agent = 'claude-code') {
|
|
|
131
158
|
// (init/steps.ts) would fall through on every run and call installBundle at
|
|
132
159
|
// global scope, which throws (skill.global === null), rolling back the
|
|
133
160
|
// ENTIRE `awm init -a copilot` transaction, 100% of the time.
|
|
134
|
-
|
|
161
|
+
// Sin directorio global de skills (hoy: Copilot) el baseline se
|
|
162
|
+
// instala a nivel PROYECTO — `stepDevCore` lo hace con
|
|
163
|
+
// scopeOverride 'local'. Antes esto se declaraba "trivialmente
|
|
164
|
+
// satisfecho" incondicionalmente: eso evitaba el crash-loop del
|
|
165
|
+
// install global, pero significaba que Copilot terminaba con CERO
|
|
166
|
+
// skills mientras init imprimia `✔ dev-core` y doctor decia
|
|
167
|
+
// `healthy`. Ahora se mira donde el artefacto REALMENTE va.
|
|
168
|
+
if (projectRoot) {
|
|
169
|
+
const localDir = path_1.default.join(projectRoot, (0, providers_1.providerFor)(agent).skill.local);
|
|
170
|
+
const { linked, broken } = classifyLinks(skillNames, localDir, (0, providers_1.providerFor)(agent).skill.renderer);
|
|
171
|
+
const absent = skillNames.filter((s) => !linked.includes(s) && !broken.includes(s));
|
|
172
|
+
devCorePresent = skillNames.length > 0 && (linked.length + broken.length) > 0;
|
|
173
|
+
brokenLinks = [...broken, ...absent];
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
// Fuera de un proyecto no hay destino local: no hay nada que
|
|
177
|
+
// reclamar como faltante.
|
|
178
|
+
devCorePresent = true;
|
|
179
|
+
}
|
|
135
180
|
}
|
|
136
181
|
else {
|
|
137
|
-
const { linked, broken } = classifyLinks(skillNames, skillsDir);
|
|
182
|
+
const { linked, broken } = classifyLinks(skillNames, skillsDir, (0, providers_1.providerFor)(agent).skill.renderer);
|
|
138
183
|
const absent = skillNames.filter((s) => !linked.includes(s) && !broken.includes(s));
|
|
139
184
|
devCorePresent = skillNames.length > 0 && (linked.length + broken.length) > 0;
|
|
140
185
|
brokenLinks = [...broken, ...absent];
|
|
@@ -151,12 +196,24 @@ function gatherMachine(bundles, agent = 'claude-code') {
|
|
|
151
196
|
// (tests/integration/codex-provider-isolated.test.ts).
|
|
152
197
|
const agentConfig = (0, providers_1.providerFor)(agent).agent;
|
|
153
198
|
if (agentConfig && agentConfig.global !== null) {
|
|
154
|
-
const agentNames = (0, bundles_1.resolveBundleAgents)(
|
|
199
|
+
const agentNames = [...new Set(baselines.flatMap((b) => (0, bundles_1.resolveBundleAgents)(b.name, bundles)))];
|
|
155
200
|
if (agentNames.length > 0) {
|
|
156
|
-
|
|
157
|
-
|
|
201
|
+
// El installName real de un artefacto `agent` es `<n>.md`
|
|
202
|
+
// (bundle-install.ts), y el renderer decide la extension final:
|
|
203
|
+
// `.toml` para codex, `.md` tal cual para los renderers `link`.
|
|
204
|
+
// El ternario anterior devolvia `n` pelado para `link`, asi que
|
|
205
|
+
// `machine.devCore` quedaba permanentemente roto para
|
|
206
|
+
// claude-code — el provider por DEFECTO — contra el registry real.
|
|
207
|
+
const filenames = agentNames.map((n) => `${n}.md`);
|
|
208
|
+
const { linked: agentLinked, broken: agentBroken } = classifyLinks(filenames, agentConfig.global, agentConfig.renderer);
|
|
158
209
|
const agentAbsent = filenames.filter((f) => !agentLinked.includes(f) && !agentBroken.includes(f));
|
|
159
|
-
brokenLinks
|
|
210
|
+
// `brokenLinks` es de diagnostico: se le muestra al usuario para
|
|
211
|
+
// que vaya a buscar ese archivo. Por eso se reportan los nombres
|
|
212
|
+
// FISICOS (`development-process.toml`), aunque la clasificacion
|
|
213
|
+
// trabaje con los logicos — que es lo que necesitan las
|
|
214
|
+
// comparaciones por cantidad de arriba.
|
|
215
|
+
const physical = (n) => (0, registry_1.renderedFilename)(n, agentConfig.renderer);
|
|
216
|
+
brokenLinks = [...brokenLinks, ...agentBroken.map(physical), ...agentAbsent.map(physical)];
|
|
160
217
|
}
|
|
161
218
|
}
|
|
162
219
|
}
|
|
@@ -183,17 +240,17 @@ function gatherMachine(bundles, agent = 'claude-code') {
|
|
|
183
240
|
const skillNames = (0, bundles_1.resolveBundleSkills)(name, bundles);
|
|
184
241
|
if (skillNames.length === 0)
|
|
185
242
|
return false;
|
|
186
|
-
const { linked } = classifyLinks(skillNames, skillsDir);
|
|
243
|
+
const { linked } = classifyLinks(skillNames, skillsDir, (0, providers_1.providerFor)(agent).skill.renderer);
|
|
187
244
|
return linked.length === skillNames.length;
|
|
188
245
|
});
|
|
189
246
|
return {
|
|
190
247
|
registryCache: { present: cachePresent, gitState },
|
|
191
|
-
hook: { present: hookPresent, degraded: hookDegraded },
|
|
248
|
+
hook: { present: hookPresent, degraded: hookDegraded, applicable: hookApplicable },
|
|
192
249
|
devCore: { present: devCorePresent, brokenLinks },
|
|
193
250
|
ambient: { wanted, installed },
|
|
194
251
|
contextInjection: gatherContextInjection(),
|
|
195
252
|
globalSkills: skillsDir !== null
|
|
196
|
-
? (0, skill_integrity_1.
|
|
253
|
+
? (0, skill_integrity_1.classifySkillLinks)(skillsDir, (0, registries_1.contentRoots)())
|
|
197
254
|
: { valid: [], repairable: [], dead: [] },
|
|
198
255
|
};
|
|
199
256
|
}
|
|
@@ -207,7 +264,15 @@ function gatherProject(root, bundles, agent = 'claude-code') {
|
|
|
207
264
|
if (!expected.includes(s))
|
|
208
265
|
expected.push(s);
|
|
209
266
|
}
|
|
210
|
-
const { linked, broken } = classifyLinks(expected, localSkillsDir);
|
|
267
|
+
const { linked, broken } = classifyLinks(expected, localSkillsDir, (0, providers_1.providerFor)(agent).skill.renderer);
|
|
268
|
+
// Huerfanos: symlinks colgantes que el profile ya no reclama. Se clasifican sobre
|
|
269
|
+
// el dir REAL, no sobre la lista esperada — que es justamente lo que `linked`/
|
|
270
|
+
// `broken` de arriba no pueden ver. `awm sync` los cura o los poda.
|
|
271
|
+
const orphanScan = (0, skill_integrity_1.classifySkillLinks)(localSkillsDir, (0, registries_1.contentRoots)());
|
|
272
|
+
const orphanLinks = {
|
|
273
|
+
repairable: orphanScan.repairable.filter((n) => !expected.includes(n)),
|
|
274
|
+
dead: orphanScan.dead.filter((n) => !expected.includes(n)),
|
|
275
|
+
};
|
|
211
276
|
let context = { present: false };
|
|
212
277
|
if (fs_1.default.existsSync(path_1.default.join(root, 'CLAUDE.md')))
|
|
213
278
|
context = { present: true, file: 'CLAUDE.md' };
|
|
@@ -217,6 +282,7 @@ function gatherProject(root, bundles, agent = 'claude-code') {
|
|
|
217
282
|
root,
|
|
218
283
|
profile: { present: profilePresent, extensions: profile.extensions },
|
|
219
284
|
activeBundles: { expected, linked, broken },
|
|
285
|
+
orphanLinks,
|
|
220
286
|
sensors: { present: fs_1.default.existsSync(path_1.default.join(root, '.awm', 'sensors.json')) },
|
|
221
287
|
constitution: { present: fs_1.default.existsSync(path_1.default.join(root, 'CONSTITUTION.md')) },
|
|
222
288
|
context,
|
|
@@ -228,9 +294,9 @@ function gatherContext(opts = {}) {
|
|
|
228
294
|
const agent = opts.agent ?? 'claude-code';
|
|
229
295
|
const root = (0, profile_1.findProjectRoot)(cwd);
|
|
230
296
|
const agents = opts.agents ?? [agent];
|
|
231
|
-
const scanSkills = opts.scanSkills ?? ((dir) => (0, skill_integrity_1.
|
|
297
|
+
const scanSkills = opts.scanSkills ?? ((dir) => (0, skill_integrity_1.classifySkillLinks)(dir, (0, registries_1.contentRoots)()));
|
|
232
298
|
return {
|
|
233
|
-
machine: gatherMachine(bundles, agent),
|
|
299
|
+
machine: gatherMachine(bundles, agent, root ?? undefined),
|
|
234
300
|
project: root ? gatherProject(root, bundles, agent) : null,
|
|
235
301
|
providers: (0, provider_checks_1.gatherProviderChecks)(agents, scanSkills, root ?? undefined),
|
|
236
302
|
};
|
|
@@ -17,19 +17,12 @@ exports.gatherProviderChecks = gatherProviderChecks;
|
|
|
17
17
|
const fs_1 = __importDefault(require("fs"));
|
|
18
18
|
const path_1 = __importDefault(require("path"));
|
|
19
19
|
const providers_1 = require("../../providers");
|
|
20
|
+
const skill_integrity_1 = require("../skill-integrity");
|
|
20
21
|
const provider_version_1 = require("../provider-version");
|
|
21
22
|
const status_1 = require("../../commands/hooks/status");
|
|
22
23
|
const orchestrator_1 = require("../context/orchestrator");
|
|
23
24
|
const registries_1 = require("../registries");
|
|
24
|
-
|
|
25
|
-
* used by `skillsGlobalCheck` to require AWM-shaped evidence, not just an arbitrary
|
|
26
|
-
* non-empty directory (a user's own unrelated file in `~/.cursor/rules` would otherwise
|
|
27
|
-
* read as `'supported'`). Renderers absent from this map (i.e. `'link'`) never reach the
|
|
28
|
-
* branch that reads it. */
|
|
29
|
-
const RENDERED_SKILL_EXTENSIONS = {
|
|
30
|
-
'cursor-mdc': '.mdc',
|
|
31
|
-
'copilot-instructions': '.instructions.md',
|
|
32
|
-
};
|
|
25
|
+
const registry_1 = require("../renderers/registry");
|
|
33
26
|
/** Structural classification, computed purely from `provider`'s config shape — see
|
|
34
27
|
* `ProviderTier`'s doc comment in `types.ts` for what each tier means. */
|
|
35
28
|
function providerTier(provider) {
|
|
@@ -66,7 +59,7 @@ function binaryVersionCheck(agent) {
|
|
|
66
59
|
* `dir` is null — i.e. the provider has no global skill discovery mechanism at all
|
|
67
60
|
* (today: Copilot, see `globalUnsupportedReason` in providers/index.ts).
|
|
68
61
|
*
|
|
69
|
-
* `renderer` gates which verification is possible: `
|
|
62
|
+
* `renderer` gates which verification is possible: `classifySkillLinks` (via `integrity`)
|
|
70
63
|
* only ever sees symlinks — `if (!lst.isSymbolicLink()) continue;` — so for a rendered
|
|
71
64
|
* format (`cursor-mdc`, `copilot-instructions`) it scans a directory of real files and
|
|
72
65
|
* finds nothing, which would make `broken` silently read 0 regardless of whether the
|
|
@@ -87,7 +80,7 @@ function skillsGlobalCheck(dir, owners, integrity, renderer) {
|
|
|
87
80
|
// `remediationCode: 'awm-init'` is a worse remedy than none, but this
|
|
88
81
|
// check has no channel to report "can't tell" separately from "absent"
|
|
89
82
|
// (same tradeoff already made by this file's agentsNativeCheck and by
|
|
90
|
-
// skill-integrity.ts's
|
|
83
|
+
// skill-integrity.ts's classifySkillLinks — a systemic, pre-existing
|
|
91
84
|
// pattern in this codebase, not introduced here).
|
|
92
85
|
entries = [];
|
|
93
86
|
}
|
|
@@ -97,7 +90,12 @@ function skillsGlobalCheck(dir, owners, integrity, renderer) {
|
|
|
97
90
|
// Still not full integrity verification (a stray file with the right extension but
|
|
98
91
|
// wrong content still passes) — that residual gap is the same honest tradeoff this
|
|
99
92
|
// function's doc comment already accepts for the non-`'link'` branch generally.
|
|
100
|
-
|
|
93
|
+
// `rendererExtension` (core/renderers/registry.ts) is the ONE table mapping a
|
|
94
|
+
// renderer to the file it produces. This site used to keep its own partial copy —
|
|
95
|
+
// the fourth such copy in the codebase, and the exact drift that made a rendered
|
|
96
|
+
// artifact invisible to the "is it installed" check for a whole release. A new
|
|
97
|
+
// renderer must not be able to be added without this reading it.
|
|
98
|
+
const ext = (0, registry_1.rendererExtension)(renderer);
|
|
101
99
|
const present = ext ? entries.some((e) => e.endsWith(ext)) : entries.length > 0;
|
|
102
100
|
return {
|
|
103
101
|
id: 'skills.global',
|
|
@@ -165,20 +163,69 @@ function tomlAgentsHealthy(dir, entries) {
|
|
|
165
163
|
* run (e.g. claude-code-only, opencode-only, antigravity-only) would always render
|
|
166
164
|
* inapplicable rows as red ✖ and `overall` could never be 'healthy'.
|
|
167
165
|
*/
|
|
166
|
+
/**
|
|
167
|
+
* Workflows installed at machine scope. Today only Antigravity declares a `workflow`
|
|
168
|
+
* config at all (`~/.gemini/antigravity/global_workflows`), and nothing verified it:
|
|
169
|
+
* `awm init` installed the baseline's workflows there and every diagnostic looked only
|
|
170
|
+
* at skills and native agents, so a broken or emptied workflow directory reported
|
|
171
|
+
* `healthy` forever on the one provider that uses it.
|
|
172
|
+
*
|
|
173
|
+
* Same N/A discipline as `agentsNativeCheck`: a provider with no workflow config, or a
|
|
174
|
+
* registry that ships no `workflows/`, emits NO row rather than a red one nobody can
|
|
175
|
+
* act on — an absent row means "nothing to verify", not "verified fine".
|
|
176
|
+
*/
|
|
177
|
+
function workflowsGlobalCheck(agent) {
|
|
178
|
+
const provider = (0, providers_1.providerFor)(agent);
|
|
179
|
+
if (!provider.workflow || provider.workflow.global === null)
|
|
180
|
+
return null;
|
|
181
|
+
const dir = provider.workflow.global;
|
|
182
|
+
let entries;
|
|
183
|
+
try {
|
|
184
|
+
entries = fs_1.default.readdirSync(dir);
|
|
185
|
+
}
|
|
186
|
+
catch {
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
if (entries.length === 0)
|
|
190
|
+
return null;
|
|
191
|
+
// Los workflows se instalan con el renderer `link`, asi que un symlink colgante es
|
|
192
|
+
// exactamente la misma clase de rotura que en skills — y se clasifica con la misma
|
|
193
|
+
// funcion, no con una copia local que pueda divergir.
|
|
194
|
+
const integrity = (0, skill_integrity_1.classifySkillLinks)(dir, (0, registries_1.contentRoots)());
|
|
195
|
+
const broken = integrity.repairable.length + integrity.dead.length;
|
|
196
|
+
return {
|
|
197
|
+
id: 'workflows.global',
|
|
198
|
+
state: broken > 0 ? 'broken' : 'healthy',
|
|
199
|
+
target: dir,
|
|
200
|
+
detail: broken > 0 ? `${broken} broken link(s)` : undefined,
|
|
201
|
+
remediationCode: broken > 0 ? 'awm-init' : undefined,
|
|
202
|
+
};
|
|
203
|
+
}
|
|
168
204
|
function agentsNativeCheck(agent) {
|
|
169
205
|
const provider = (0, providers_1.providerFor)(agent);
|
|
170
206
|
if (!provider.agent || provider.agent.global === null)
|
|
171
207
|
return null;
|
|
172
208
|
const dir = provider.agent.global;
|
|
173
209
|
let entries;
|
|
210
|
+
// `absent` degrada el estado global, asi que sin `remediationCode` doctor
|
|
211
|
+
// salia 1 sin decir que hacer — y para un registry que simplemente no trae
|
|
212
|
+
// `agents/` (lo normal) ese rojo no tiene accion posible. Se reporta como
|
|
213
|
+
// `unsupported`, que describe la realidad: no hay artefactos nativos que
|
|
214
|
+
// verificar, y no es culpa de la instalacion.
|
|
215
|
+
// Un registry que simplemente no trae `agents/` es lo normal, no un defecto
|
|
216
|
+
// de la instalacion — y no hay accion que el usuario pueda tomar. Antes esto
|
|
217
|
+
// devolvia `absent`, un estado que DEGRADA, y sin `remediationCode`: doctor
|
|
218
|
+
// salia 1 mostrando `✖ native agents` sin decir que hacer, justo despues de
|
|
219
|
+
// un `awm init` exitoso. Cuando no hay nada nativo que verificar, no se
|
|
220
|
+
// emite fila — el mismo criterio que ya usan los demas casos N/A de aca.
|
|
174
221
|
try {
|
|
175
222
|
entries = fs_1.default.readdirSync(dir);
|
|
176
223
|
}
|
|
177
224
|
catch {
|
|
178
|
-
return
|
|
225
|
+
return null;
|
|
179
226
|
}
|
|
180
227
|
if (entries.length === 0)
|
|
181
|
-
return
|
|
228
|
+
return null;
|
|
182
229
|
if (provider.agent.renderer === 'codex-agent-toml') {
|
|
183
230
|
const { broken } = tomlAgentsHealthy(dir, entries);
|
|
184
231
|
return {
|
|
@@ -292,6 +339,7 @@ function gatherProviderChecks(agents, scanSkills, projectRoot) {
|
|
|
292
339
|
binaryVersionCheck(agent),
|
|
293
340
|
skillsGlobalCheck(dir, owners, integrity, provider.skill.renderer),
|
|
294
341
|
agentsNativeCheck(agent),
|
|
342
|
+
workflowsGlobalCheck(agent),
|
|
295
343
|
hookTrustCheck(agent),
|
|
296
344
|
contextGlobalCheck(agent, projectRoot),
|
|
297
345
|
].filter((check) => check !== null);
|
|
@@ -94,7 +94,7 @@ function discoverWorkflows(roots = (0, registries_1.contentRoots)()) {
|
|
|
94
94
|
for (const entry of fs_1.default.readdirSync(dir, { withFileTypes: true })) {
|
|
95
95
|
if (entry.isDirectory() || !entry.name.endsWith('.md'))
|
|
96
96
|
continue;
|
|
97
|
-
const name = entry.name.replace(
|
|
97
|
+
const name = entry.name.replace(/\.md$/, '');
|
|
98
98
|
const filePath = path_1.default.join(dir, entry.name);
|
|
99
99
|
mergeEntry('workflow', byName, {
|
|
100
100
|
name,
|
|
@@ -120,7 +120,7 @@ function discoverAgents(roots = (0, registries_1.contentRoots)()) {
|
|
|
120
120
|
for (const entry of fs_1.default.readdirSync(dir, { withFileTypes: true })) {
|
|
121
121
|
if (entry.isDirectory() || !entry.name.endsWith('.md'))
|
|
122
122
|
continue;
|
|
123
|
-
const name = entry.name.replace(
|
|
123
|
+
const name = entry.name.replace(/\.md$/, '');
|
|
124
124
|
const filePath = path_1.default.join(dir, entry.name);
|
|
125
125
|
mergeEntry('agent', byName, {
|
|
126
126
|
name,
|
|
@@ -17,6 +17,7 @@ exports.planInitMutationTargets = planInitMutationTargets;
|
|
|
17
17
|
// entry when nothing exists there yet). The one failure mode that matters is
|
|
18
18
|
// under-enumeration: a real write to a path NOT in this list would land
|
|
19
19
|
// outside the backup session and survive a rollback.
|
|
20
|
+
const fs_1 = __importDefault(require("fs"));
|
|
20
21
|
const path_1 = __importDefault(require("path"));
|
|
21
22
|
const providers_1 = require("../../providers");
|
|
22
23
|
const bundles_1 = require("../bundles");
|
|
@@ -40,6 +41,17 @@ function physicalTarget(type, agent, scope, installName, projectRoot) {
|
|
|
40
41
|
return null;
|
|
41
42
|
}
|
|
42
43
|
}
|
|
44
|
+
/** La forma canonica de un directorio: resuelve symlinks si existe, y si no existe
|
|
45
|
+
* todavia devuelve la entrada tal cual — enumerar un destino aun inexistente es
|
|
46
|
+
* deliberado en este modulo (un backup vacio no es un problema; no enumerarlo si). */
|
|
47
|
+
function canonicalDir(dir) {
|
|
48
|
+
try {
|
|
49
|
+
return fs_1.default.realpathSync(dir);
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return dir;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
43
55
|
function addBundleTargets(targets, bundleName, bundles, agent, scope, projectRoot, contentDir) {
|
|
44
56
|
let intents;
|
|
45
57
|
try {
|
|
@@ -72,7 +84,17 @@ function addBundleTargets(targets, bundleName, bundles, agent, scope, projectRoo
|
|
|
72
84
|
* read the project profile, but never writes).
|
|
73
85
|
*/
|
|
74
86
|
function planInitMutationTargets(params) {
|
|
75
|
-
const {
|
|
87
|
+
const { agent, bundles } = params;
|
|
88
|
+
// `cwd` canonico ANTES de derivar nada de el. Algunos destinos salen de `cwd` tal
|
|
89
|
+
// cual y otros de `findProjectRoot(cwd)`, que hace `realpathSync`: mientras las dos
|
|
90
|
+
// formas coincidan el `Set` deduplica y no se nota, pero en macOS `/var/folders/…`
|
|
91
|
+
// es un symlink a `/private/var/folders/…` y entonces NO coinciden — la CI de macOS
|
|
92
|
+
// mostro `AGENTS.md` y `.awm/context/awm-context.md` dos veces, una por forma.
|
|
93
|
+
//
|
|
94
|
+
// Esta lista es la que `beginBackupSession` respalda y la que el rollback restaura.
|
|
95
|
+
// Un archivo con dos entradas es un archivo respaldado dos veces y restaurado dos
|
|
96
|
+
// veces, en el mecanismo cuyo unico trabajo es dejar el disco como estaba.
|
|
97
|
+
const cwd = canonicalDir(params.cwd);
|
|
76
98
|
const targets = new Set();
|
|
77
99
|
// preferences.json + the artifact ownership ledger (state/artifacts.json)
|
|
78
100
|
targets.add(path_1.default.join((0, paths_1.awmHome)(), 'preferences.json'));
|
|
@@ -110,6 +132,17 @@ function planInitMutationTargets(params) {
|
|
|
110
132
|
for (const b of machineBundles) {
|
|
111
133
|
addBundleTargets(targets, b.name, bundles, agent, 'global', cwd, contentDir);
|
|
112
134
|
}
|
|
135
|
+
// Providers whose context injection is itself project-scope (managed-agents-md
|
|
136
|
+
// with a null globalPath — Cursor, Copilot) write AGENTS.md and the materialized
|
|
137
|
+
// .awm/context/ file. `stepContextInjection` now refuses to write those without a
|
|
138
|
+
// discovered project root, but enumerating them against `cwd` unconditionally
|
|
139
|
+
// costs nothing (a target that goes unwritten is a no-op backup entry, per this
|
|
140
|
+
// module's opening note) and keeps the guarantee from resting on a single site:
|
|
141
|
+
// under-enumeration here is the one failure mode that silently defeats rollback.
|
|
142
|
+
if (injection?.type === 'managed-agents-md' && injection.globalPath === null) {
|
|
143
|
+
targets.add(path_1.default.join(cwd, path_1.default.basename(injection.localFile)));
|
|
144
|
+
targets.add((0, materializer_1.projectContextPath)(cwd));
|
|
145
|
+
}
|
|
113
146
|
// project-level: profile, sensors manifest, project injection, and every
|
|
114
147
|
// extension currently recorded in .awm/profile.json
|
|
115
148
|
const projectRoot = (0, profile_1.findProjectRoot)(cwd);
|
|
@@ -55,6 +55,7 @@ exports.defaultActions = {
|
|
|
55
55
|
method: o.method,
|
|
56
56
|
projectRoot: o.projectRoot,
|
|
57
57
|
contentDir: o.contentDir,
|
|
58
|
+
scopeOverride: o.scopeOverride,
|
|
58
59
|
}),
|
|
59
60
|
// Same as installBundle above — defaults to the real applyInstallPlan.
|
|
60
61
|
syncProfile: (o) => (0, bundle_install_1.syncProfile)({
|
|
@@ -66,14 +67,21 @@ exports.defaultActions = {
|
|
|
66
67
|
}),
|
|
67
68
|
initSensors: (o) => {
|
|
68
69
|
const result = (0, init_1.initSensors)({ cwd: o.cwd, registryRoot: o.registryRoot, configure: o.configure });
|
|
69
|
-
|
|
70
|
+
// `unavailablePack`/`manifest` viajan a proposito: `stepSensors` los reporta.
|
|
71
|
+
// Este wrapper proyectaba solo `detection`, asi que cualquier hallazgo nuevo de
|
|
72
|
+
// initSensors moria aca en silencio antes de llegar al step que lo muestra.
|
|
73
|
+
return {
|
|
74
|
+
detection: result.detection,
|
|
75
|
+
manifest: { pack: result.manifest.pack },
|
|
76
|
+
...(result.unavailablePack ? { unavailablePack: result.unavailablePack } : {}),
|
|
77
|
+
};
|
|
70
78
|
},
|
|
71
79
|
addExtension: (root, name) => { (0, profile_1.addExtension)(root, name); },
|
|
72
80
|
ensureProfile: (root) => { (0, profile_1.ensureProfile)(root); },
|
|
73
81
|
gatherProject: (cwd, bundles, agent) => (0, context_1.gatherContext)({ cwd, bundles, agent }).project,
|
|
74
82
|
contextStatus: (op) => realInjectionOrchestrator.contextStatus(op),
|
|
75
83
|
installContext: (op) => { realInjectionOrchestrator.installContext(op); },
|
|
76
|
-
repairGlobalSkills: (skillsDir, registryContentDirs) => (0, skill_integrity_1.
|
|
84
|
+
repairGlobalSkills: (skillsDir, registryContentDirs) => (0, skill_integrity_1.repairSkillLinks)(skillsDir, registryContentDirs),
|
|
77
85
|
injectProjectConstitution: (o) => {
|
|
78
86
|
if ((0, providers_1.getInjection)(o.agent)?.type === 'managed-agents-md') {
|
|
79
87
|
return new codex_agents_1.CodexAgentsStrategy().injectProject(o.projectRoot, (0, providers_1.providerFor)(o.agent), o.agent) === 'injected' ? 'injected' : 'already';
|
|
@@ -113,8 +121,12 @@ function failed(id, level, error) {
|
|
|
113
121
|
* physical operation and simply re-confirms ownership — it does not change
|
|
114
122
|
* the co-owner's installed content.
|
|
115
123
|
*/
|
|
116
|
-
function sharedInstallAgents(d) {
|
|
117
|
-
|
|
124
|
+
function sharedInstallAgents(d, scope = 'global') {
|
|
125
|
+
// El scope tiene que ser el MISMO con el que se va a instalar: para un
|
|
126
|
+
// provider sin directorio global (Copilot) el baseline va a scope local, y
|
|
127
|
+
// preguntar por el grupo compartido en 'global' devolvia un grupo calculado
|
|
128
|
+
// sobre un destino que para el no existe.
|
|
129
|
+
const group = (0, install_planner_1.agentsSharingSkillTarget)(d.agent, d.enabledAgents, scope, d.cwd);
|
|
118
130
|
return group.includes(d.agent) ? group : [d.agent, ...group];
|
|
119
131
|
}
|
|
120
132
|
/**
|
|
@@ -188,18 +200,34 @@ function stepDevCore(d) {
|
|
|
188
200
|
if (devCore.present && devCore.brokenLinks.length === 0) {
|
|
189
201
|
return ok('machine.devCore', 'machine', 'skipped');
|
|
190
202
|
}
|
|
191
|
-
//
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
+
// TODOS los bundles baseline, no el primero. `scope: 'baseline'` es una propiedad
|
|
204
|
+
// que un bundle TIENE, no un lugar que solo uno puede ocupar — y
|
|
205
|
+
// `reconciliation.ts` (`awm update`) siempre los filtro todos. Con `find` aca, un
|
|
206
|
+
// registry con dos baselines instalaba uno en `awm init` y aparecia el segundo, de
|
|
207
|
+
// la nada, en el siguiente `awm update`; nada antes lo habia reportado como
|
|
208
|
+
// faltante. El fallback a 'dev' se conserva para un registry sin ningun baseline.
|
|
209
|
+
const baselineNames = d.bundles.filter((b) => b.scope === 'baseline').map((b) => b.name);
|
|
210
|
+
const toInstall = baselineNames.length > 0 ? baselineNames : ['dev'];
|
|
211
|
+
// Un provider sin directorio global de skills (hoy: Copilot) no tiene donde
|
|
212
|
+
// poner el baseline a nivel maquina — y el hecho `devCore` lo reporta como
|
|
213
|
+
// "trivialmente satisfecho" para no volver a intentar un install global
|
|
214
|
+
// condenado a tirar. Pero nada instalaba nunca nada en su lugar: Copilot
|
|
215
|
+
// terminaba con CERO skills mientras `awm init` imprimia `✔ dev-core` y
|
|
216
|
+
// `awm doctor` decia `healthy`. Su propio `globalUnsupportedReason` ya dice
|
|
217
|
+
// que hay que instalar por proyecto, asi que eso es lo que se hace.
|
|
218
|
+
const localOnly = (0, providers_1.providerFor)(d.agent).skill.global === null;
|
|
219
|
+
for (const bundleName of toInstall) {
|
|
220
|
+
d.actions.installBundle({
|
|
221
|
+
bundleName,
|
|
222
|
+
bundles: d.bundles,
|
|
223
|
+
agents: sharedInstallAgents(d, localOnly ? 'local' : 'global'),
|
|
224
|
+
method: d.installMethod,
|
|
225
|
+
projectRoot: d.cwd,
|
|
226
|
+
contentDir: d.contentDir,
|
|
227
|
+
...(localOnly ? { scopeOverride: 'local' } : {}),
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
return ok('machine.devCore', 'machine', 'applied', localOnly ? 'installed at project scope (no global skill dir)' : undefined);
|
|
203
231
|
}
|
|
204
232
|
/** Step 3.5 – Repair broken global skill symlinks (orphans outside the baseline). */
|
|
205
233
|
function stepGlobalSkillsRepair(d) {
|
|
@@ -294,7 +322,14 @@ function stepSensors(d) {
|
|
|
294
322
|
return ok('project.sensors', 'project', 'skipped', 'no project');
|
|
295
323
|
if (proj.sensors.present)
|
|
296
324
|
return ok('project.sensors', 'project', 'skipped');
|
|
297
|
-
d.actions.initSensors({ cwd: proj.root, registryRoot: d.sensorPacksRoot, configure: true });
|
|
325
|
+
const res = d.actions.initSensors({ cwd: proj.root, registryRoot: d.sensorPacksRoot, configure: true });
|
|
326
|
+
// A registry without the detected pack yields a fallback (or empty) manifest. The
|
|
327
|
+
// step still succeeded — a manifest was written — but reporting a bare 'applied'
|
|
328
|
+
// would let `awm init` hand back a quality gate that measures less than the
|
|
329
|
+
// operator has any reason to expect. Say which pack was missing, here, once.
|
|
330
|
+
if (res.unavailablePack) {
|
|
331
|
+
return ok('project.sensors', 'project', 'applied', `registry has no '${res.unavailablePack}' sensor-pack — wrote '${res.manifest?.pack ?? 'fallback'}' instead; run \`awm update\``);
|
|
332
|
+
}
|
|
298
333
|
return ok('project.sensors', 'project', 'applied');
|
|
299
334
|
}
|
|
300
335
|
/** Step 8 – Signal that the agent should run the project-constitution skill. */
|
|
@@ -348,8 +383,24 @@ function stepContextInjection(d) {
|
|
|
348
383
|
// AGENTS.md backup target via the same findProjectRoot(cwd) call, so using d.cwd here
|
|
349
384
|
// whenever it differs from the walked-up project root (e.g. `awm init` run from a
|
|
350
385
|
// subdirectory) would write to a path the backup session never snapshotted — a failed
|
|
351
|
-
// init couldn't roll it back.
|
|
352
|
-
//
|
|
386
|
+
// init couldn't roll it back.
|
|
387
|
+
//
|
|
388
|
+
// The `?? d.cwd` fallback below is the legitimate case: a provider whose ONLY
|
|
389
|
+
// delivery channel is project-scope (Copilot), run in a directory the user means
|
|
390
|
+
// as their project but which carries no marker yet (no .git, no package.json).
|
|
391
|
+
// Removing it would leave Copilot with no context at all there. What made it a bug
|
|
392
|
+
// was that mutation-targets.ts enumerated the AGENTS.md / .awm/context/ backup
|
|
393
|
+
// targets only inside `if (projectRoot)`, so this write landed outside the backup
|
|
394
|
+
// session and survived a rollback that reported a clean restore. That is fixed
|
|
395
|
+
// where it belongs — in the enumeration, which now covers `cwd` unconditionally
|
|
396
|
+
// for these providers.
|
|
397
|
+
//
|
|
398
|
+
// `--machine-only` is the case that genuinely must not write. It nulls
|
|
399
|
+
// `ctx.project` on purpose, and this MACHINE-level step read that as "no project
|
|
400
|
+
// found" and wrote into cwd anyway — under the one flag that promises not to.
|
|
401
|
+
if (scope === 'local' && d.machineOnly) {
|
|
402
|
+
return ok('machine.contextInjection', 'machine', 'skipped', `${(0, providers_1.providerFor)(d.agent).label} delivers context at project scope — skipped by --machine-only`);
|
|
403
|
+
}
|
|
353
404
|
const op = {
|
|
354
405
|
agent: d.agent,
|
|
355
406
|
scope,
|