agentic-workflow-manager 4.0.0 → 4.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -11,6 +11,8 @@ const picocolors_1 = __importDefault(require("picocolors"));
11
11
  const context_1 = require("../core/diagnostics/context");
12
12
  const checks_1 = require("../core/diagnostics/checks");
13
13
  const paths_1 = require("../core/paths");
14
+ // `readPreferences`, no `getPreferences`: este comando esta documentado como
15
+ // read-only y el segundo auto-vivifica el archivo. Ver la nota en config.ts.
14
16
  const config_1 = require("../utils/config");
15
17
  const agent_targets_1 = require("../core/agent-targets");
16
18
  function glyph(status) {
@@ -128,7 +130,7 @@ function runDoctor(opts = {}) {
128
130
  // internal-error catch further down.
129
131
  let targets;
130
132
  try {
131
- const prefs = (0, config_1.getPreferences)();
133
+ const prefs = (0, config_1.readPreferences)();
132
134
  targets = resolveTargets({ prefs, explicit: opts.agent });
133
135
  }
134
136
  catch (err) {
package/dist/src/index.js CHANGED
@@ -477,18 +477,43 @@ program.command('list [package]')
477
477
  console.log(picocolors_1.default.dim(` awm list <pkg> · awm list --all`));
478
478
  (0, prompts_1.outro)(`Run ${picocolors_1.default.green('awm add')} to install artifacts.`);
479
479
  });
480
- program.command('remove')
481
- .description('Remove an installed skill or workflow')
480
+ // Simetrico con `add`: si se puede instalar scripteado, se tiene que poder desinstalar
481
+ // scripteado. `remove` era interactivo puro — sin nombre posicional, sin `--scope`, sin
482
+ // `--yes` — asi que cualquier limpieza automatizada quedaba bloqueada, y el playbook de
483
+ // aceptacion (CORE-17) scripteaba `awm remove dev --yes`, una invocacion que nunca
484
+ // existio. Tercera vez en esta sesion que un playbook se escribio contra lo que la doc
485
+ // prometia y no contra el comportamiento. Ver docs/decisions.md, D-006.
486
+ //
487
+ // El nombre es de BUNDLE, igual que en `add` (D-001).
488
+ program.command('remove [name]')
489
+ .description('Remove an installed bundle interactively, or non-interactively with flags')
482
490
  .option('-a, --agent <agent>', `Target agent(s), comma-separated: ${providers_1.AGENT_TARGETS.join(', ')} (defaults to every enabled agent)`)
483
- .action(async (options) => {
491
+ .option('-s, --scope <scope>', 'Scope: local or global (skips the prompt)')
492
+ .option('-y, --yes', 'Skip the confirmation prompt (requires a name)')
493
+ .action(async (name, options) => {
484
494
  (0, prompts_1.intro)(picocolors_1.default.bgCyan(picocolors_1.default.black(' AWM - Remove Artifact ')));
485
495
  const prefs = (0, config_1.getPreferences)();
496
+ // `--yes` sin nombre borraria lo que el usuario nunca eligio: un `rm -rf` silencioso
497
+ // sobre todo lo instalado. Se rechaza antes de tocar nada.
498
+ if (options.yes && !name) {
499
+ console.error(picocolors_1.default.red('--yes requires a bundle name: `awm remove <bundle> --yes`.'));
500
+ console.error(picocolors_1.default.dim('Without a name, removal stays interactive so you see what you are deleting.'));
501
+ process.exit(1);
502
+ }
486
503
  // Multi-agent selection (matching the add command flow). --agent skips
487
504
  // the interactive prompt and resolves the same way as add/sync/update/doctor (R12/R13).
488
505
  let targetAgents;
489
506
  if (options.agent) {
490
507
  targetAgents = resolveTargetsOrExit(prefs, options.agent);
491
508
  }
509
+ else if (options.yes) {
510
+ // `--yes` significa CERO prompts, no "cero confirmaciones". Sin esto, un
511
+ // `awm remove dev --yes` sin `--agent` seguia abriendo el multiselect de
512
+ // agentes y colgaba cualquier script — el flag prometia no-interactivo y no lo
513
+ // era. Sin `--agent`, el default son los agentes habilitados, igual que en
514
+ // `add`, `sync`, `update` y `doctor`.
515
+ targetAgents = [...prefs.enabledAgents];
516
+ }
492
517
  else {
493
518
  const agentChoice = await (0, prompts_1.multiselect)({
494
519
  message: 'From which agent(s)?',
@@ -502,16 +527,26 @@ program.command('remove')
502
527
  handleCancel(agentChoice);
503
528
  targetAgents = agentChoice;
504
529
  }
505
- const scopeChoice = await (0, prompts_1.select)({
506
- message: 'Scope?',
507
- options: [
508
- { value: 'local', label: 'Project (Local)' },
509
- { value: 'global', label: 'Global' }
510
- ],
511
- initialValue: prefs.defaultScope
512
- });
513
- handleCancel(scopeChoice);
514
- const scopeVal = scopeChoice;
530
+ let scopeVal;
531
+ if (options.scope) {
532
+ if (options.scope !== 'local' && options.scope !== 'global') {
533
+ console.error(picocolors_1.default.red(`Invalid scope "${options.scope}". Use: local or global.`));
534
+ process.exit(1);
535
+ }
536
+ scopeVal = options.scope;
537
+ }
538
+ else {
539
+ const scopeChoice = await (0, prompts_1.select)({
540
+ message: 'Scope?',
541
+ options: [
542
+ { value: 'local', label: 'Project (Local)' },
543
+ { value: 'global', label: 'Global' }
544
+ ],
545
+ initialValue: prefs.defaultScope
546
+ });
547
+ handleCancel(scopeChoice);
548
+ scopeVal = scopeChoice;
549
+ }
515
550
  // projectRoot explicito: `config.local` es relativo, y resolverlo contra
516
551
  // `process.cwd()` hacia que `awm remove` no encontrara nada desde un
517
552
  // subdirectorio — y que le pasara rutas RELATIVAS a fs.rmSync cuando si.
@@ -532,16 +567,43 @@ program.command('remove')
532
567
  }
533
568
  return `${icons} ${c.baseName} ${picocolors_1.default.dim(`(in: ${Array.from(locations).join(', ')})`)}`;
534
569
  });
535
- const toRemove = await (0, prompts_1.multiselect)({
536
- message: 'Select artifact(s) to remove',
537
- options: groupedOpts,
538
- required: true
539
- });
540
- handleCancel(toRemove);
541
- const resolved = resolveSelectedArtifacts(toRemove);
570
+ let resolved;
571
+ if (name) {
572
+ // Los artefactos del bundle pedido que estan REALMENTE instalados en este
573
+ // scope. Se cruza contra `installed`: lo que no esta instalado no se puede
574
+ // remover, y decirlo es mejor que borrar un subconjunto en silencio.
575
+ const bundle = (0, bundles_1.discoverAllBundles)().find((b) => b.name === name);
576
+ if (!bundle) {
577
+ console.error(picocolors_1.default.red(`Bundle "${name}" not found in registry.`));
578
+ console.error(picocolors_1.default.dim('Run `awm list` to see available packages.'));
579
+ process.exit(1);
580
+ }
581
+ const owned = new Set([
582
+ ...bundle.skills.map((sk) => sk.name),
583
+ ...bundle.workflows,
584
+ ...bundle.agents,
585
+ ]);
586
+ resolved = installed.filter((a) => owned.has(a.name));
587
+ if (resolved.length === 0) {
588
+ (0, prompts_1.outro)(picocolors_1.default.yellow(`Nothing from "${name}" is installed at ${scopeVal} scope for the selected agent(s).`));
589
+ process.exit(0);
590
+ }
591
+ }
592
+ else {
593
+ const toRemove = await (0, prompts_1.multiselect)({
594
+ message: 'Select artifact(s) to remove',
595
+ options: groupedOpts,
596
+ required: true
597
+ });
598
+ handleCancel(toRemove);
599
+ resolved = resolveSelectedArtifacts(toRemove);
600
+ }
542
601
  const names = resolved.map(a => picocolors_1.default.red(a.name)).join(', ');
543
- const confirmRemove = await (0, prompts_1.confirm)({ message: `Remove ${names}?` });
544
- handleCancel(confirmRemove);
602
+ // `--yes` salta la confirmacion, nunca la seleccion: lo que se borra siempre sale
603
+ // de un nombre que el usuario escribio.
604
+ const confirmRemove = options.yes ? true : await (0, prompts_1.confirm)({ message: `Remove ${names}?` });
605
+ if (!options.yes)
606
+ handleCancel(confirmRemove);
545
607
  if (confirmRemove) {
546
608
  try {
547
609
  for (const artifact of resolved) {
@@ -4,6 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.preferencesExist = preferencesExist;
7
+ exports.readPreferences = readPreferences;
7
8
  exports.getPreferences = getPreferences;
8
9
  exports.loadPreferences = loadPreferences;
9
10
  exports.savePreferences = savePreferences;
@@ -84,6 +85,28 @@ function normalizePreferences(value, allowEnabledAgentsMigration = true) {
84
85
  JSON.stringify(source) !== JSON.stringify(enabledAgents),
85
86
  };
86
87
  }
88
+ /**
89
+ * Las preferencias TAL COMO ESTAN, sin escribir nada. Devuelve los defaults en memoria
90
+ * cuando el archivo no existe todavia.
91
+ *
92
+ * `getPreferences` auto-vivifica: si no hay archivo, lo crea con los defaults. Eso esta
93
+ * bien para un comando que va a configurar la maquina, y esta mal para uno que solo
94
+ * mira. `awm doctor` —documentado como «Read-only dashboard. Changes nothing.»— lo
95
+ * llamaba, asi que en una maquina limpia el comando de inspeccion dejaba un
96
+ * `preferences.json` con `claude-code` como agente por defecto, que despues se lee como
97
+ * «el usuario configuro esto» cuando no lo hizo. Lo encontro el playbook de aceptacion,
98
+ * no la suite: ningun test preguntaba si un comando de lectura escribia.
99
+ *
100
+ * Se midieron los otros siete comandos de inspeccion (`list`, `preflight`, `sensors
101
+ * status`, `context-budget`, `backup list`, `ledger list`, `registry status`): todos
102
+ * limpios. `doctor` era el unico.
103
+ */
104
+ function readPreferences() {
105
+ const loaded = loadPreferences();
106
+ return loaded.exists
107
+ ? loaded.prefs
108
+ : { ...DEFAULT_PREFS, enabledAgents: [...DEFAULT_PREFS.enabledAgents] };
109
+ }
87
110
  function getPreferences() {
88
111
  const loaded = loadPreferences();
89
112
  if (!loaded.exists) {
@@ -0,0 +1,97 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ // `awm doctor` está documentado como «Read-only dashboard of machine + project harness
7
+ // state. **Changes nothing.**» — y escribía.
8
+ //
9
+ // `getPreferences()` auto-vivifica `preferences.json` con los defaults cuando el archivo
10
+ // no existe (`config.ts`: `if (!loaded.exists) savePreferences(DEFAULT_PREFS)`). Un
11
+ // getter con efecto de escritura escondido adentro. `doctor` lo llamaba para resolver a
12
+ // qué agentes mirar, así que en una máquina limpia el comando de inspección dejaba un
13
+ // `preferences.json` con `claude-code` como agente por defecto — que después se lee como
14
+ // «el usuario configuró esto» cuando no lo hizo.
15
+ //
16
+ // Lo encontró el playbook de aceptación en su segundo check, no la suite: ningún test
17
+ // preguntaba si un comando de lectura escribía. Se midieron los otros siete comandos de
18
+ // inspección (`list`, `preflight`, `sensors status`, `context-budget`, `backup list`,
19
+ // `ledger list`, `registry status`) y todos estaban limpios — así que acá el sitio ES la
20
+ // clase, con medición en vez de suposición.
21
+ const fs_1 = __importDefault(require("fs"));
22
+ const os_1 = __importDefault(require("os"));
23
+ const path_1 = __importDefault(require("path"));
24
+ describe('awm doctor no escribe nada', () => {
25
+ let home;
26
+ let projectRoot;
27
+ let saved;
28
+ let writeSpy;
29
+ beforeEach(() => {
30
+ home = fs_1.default.realpathSync(fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-doctor-ro-')));
31
+ saved = { HOME: process.env.HOME, AWM_HOME: process.env.AWM_HOME };
32
+ process.env.HOME = home;
33
+ process.env.AWM_HOME = path_1.default.join(home, '.awm');
34
+ projectRoot = path_1.default.join(home, 'proj');
35
+ fs_1.default.mkdirSync(path_1.default.join(projectRoot, '.git'), { recursive: true });
36
+ jest.resetModules();
37
+ writeSpy = jest.spyOn(process.stdout, 'write').mockImplementation(() => true);
38
+ });
39
+ afterEach(() => {
40
+ writeSpy.mockRestore();
41
+ if (saved.HOME === undefined)
42
+ delete process.env.HOME;
43
+ else
44
+ process.env.HOME = saved.HOME;
45
+ if (saved.AWM_HOME === undefined)
46
+ delete process.env.AWM_HOME;
47
+ else
48
+ process.env.AWM_HOME = saved.AWM_HOME;
49
+ fs_1.default.rmSync(home, { recursive: true, force: true });
50
+ });
51
+ /** Todo lo que exista bajo `home`, para comparar el antes y el después. */
52
+ const treeOf = (dir) => {
53
+ const out = [];
54
+ const walk = (sub) => {
55
+ for (const e of fs_1.default.readdirSync(path_1.default.join(dir, sub), { withFileTypes: true })) {
56
+ const rel = path_1.default.join(sub, e.name);
57
+ out.push(rel);
58
+ if (e.isDirectory())
59
+ walk(rel);
60
+ }
61
+ };
62
+ walk('.');
63
+ return out.sort();
64
+ };
65
+ it('no crea AWM_HOME ni preferences.json en una máquina limpia', () => {
66
+ const { runDoctor } = require('../../src/commands/doctor');
67
+ const before = treeOf(home);
68
+ runDoctor({ cwd: projectRoot, json: true });
69
+ expect(fs_1.default.existsSync(path_1.default.join(home, '.awm', 'preferences.json'))).toBe(false);
70
+ expect(treeOf(home)).toEqual(before);
71
+ });
72
+ it('tampoco escribe cuando ya hay preferencias', () => {
73
+ // El otro camino de `getPreferences`: el archivo existe. `migrationRequired`
74
+ // podía reescribirlo — sigue siendo una escritura desde un comando de lectura.
75
+ fs_1.default.mkdirSync(path_1.default.join(home, '.awm'), { recursive: true });
76
+ const prefsPath = path_1.default.join(home, '.awm', 'preferences.json');
77
+ fs_1.default.writeFileSync(prefsPath, JSON.stringify({
78
+ enabledAgents: ['claude-code'], defaultAgent: 'claude-code',
79
+ installMethod: 'symlink', defaultScope: 'global',
80
+ }));
81
+ const before = fs_1.default.readFileSync(prefsPath, 'utf-8');
82
+ const beforeTree = treeOf(home);
83
+ const { runDoctor } = require('../../src/commands/doctor');
84
+ runDoctor({ cwd: projectRoot, json: true });
85
+ expect(fs_1.default.readFileSync(prefsPath, 'utf-8')).toBe(before);
86
+ expect(treeOf(home)).toEqual(beforeTree);
87
+ });
88
+ it('sigue reportando sobre el agente por defecto sin archivo de preferencias', () => {
89
+ // No alcanza con no escribir: tiene que seguir funcionando. Sin preferencias,
90
+ // los defaults en memoria son la respuesta correcta.
91
+ const { runDoctor } = require('../../src/commands/doctor');
92
+ const code = runDoctor({ cwd: projectRoot, json: true });
93
+ expect([0, 1]).toContain(code); // 0 sano / 1 degradado, nunca 2 (error)
94
+ const emitted = writeSpy.mock.calls.map((c) => String(c[0])).join('');
95
+ expect(JSON.parse(emitted).providers.map((p) => p.id)).toEqual(['claude-code']);
96
+ });
97
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentic-workflow-manager",
3
- "version": "4.0.0",
3
+ "version": "4.1.0",
4
4
  "main": "dist/src/index.js",
5
5
  "bin": {
6
6
  "awm": "./dist/src/index.js"