agentic-workflow-manager 9.6.1 → 9.7.1

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.
Files changed (55) hide show
  1. package/README.md +2 -0
  2. package/dist/src/commands/doctor.js +4 -2
  3. package/dist/src/commands/export.js +3 -0
  4. package/dist/src/commands/init.js +10 -2
  5. package/dist/src/commands/registry/add.js +2 -2
  6. package/dist/src/commands/registry/index.js +7 -5
  7. package/dist/src/commands/registry/install-bundles.js +4 -4
  8. package/dist/src/commands/registry/status.js +4 -4
  9. package/dist/src/commands/sync.js +2 -1
  10. package/dist/src/commands/track/index.js +3 -7
  11. package/dist/src/commands/update.js +3 -1
  12. package/dist/src/commands/watch/apply.js +15 -0
  13. package/dist/src/commands/watch/tracks.js +56 -7
  14. package/dist/src/core/bundle-install.js +2 -2
  15. package/dist/src/core/bundles.js +92 -19
  16. package/dist/src/core/diagnostics/context.js +1 -1
  17. package/dist/src/core/export/index.js +1 -1
  18. package/dist/src/core/export/resolve.js +2 -2
  19. package/dist/src/core/journal/types.js +1 -0
  20. package/dist/src/core/reconciliation.js +1 -1
  21. package/dist/src/core/tracks/git.js +15 -3
  22. package/dist/src/core/tracks/protocol.js +26 -6
  23. package/dist/src/index.js +46 -39
  24. package/dist/src/utils/grouping.js +1 -1
  25. package/dist/src/utils/registry-view.js +1 -1
  26. package/dist/tests/commands/bundle-diagnostics.test.js +145 -0
  27. package/dist/tests/commands/export.test.js +28 -2
  28. package/dist/tests/commands/multi-agent-targeting.test.js +1 -1
  29. package/dist/tests/commands/track/verbs.test.js +6 -0
  30. package/dist/tests/commands/watch/apply.test.js +46 -9
  31. package/dist/tests/commands/watch/track-remove-request.test.js +165 -0
  32. package/dist/tests/commands/watch/track-teardown-crash.test.js +27 -0
  33. package/dist/tests/core/bundle-install.test.js +36 -0
  34. package/dist/tests/core/bundles-multiroot.test.js +24 -2
  35. package/dist/tests/core/bundles-overrides.test.js +10 -7
  36. package/dist/tests/core/bundles.test.js +75 -6
  37. package/dist/tests/core/diagnostics/context.test.js +2 -2
  38. package/dist/tests/core/export/engine.test.js +1 -1
  39. package/dist/tests/core/export/resolve.test.js +1 -1
  40. package/dist/tests/core/init/mutation-targets.test.js +1 -1
  41. package/dist/tests/core/init/orchestrator.test.js +1 -1
  42. package/dist/tests/core/init/steps.test.js +1 -1
  43. package/dist/tests/core/journal/types.test.js +11 -0
  44. package/dist/tests/core/reconciliation.test.js +1 -1
  45. package/dist/tests/core/sync-profile-atomicity.test.js +1 -1
  46. package/dist/tests/core/tracks/git.test.js +7 -0
  47. package/dist/tests/core/tracks/protocol.test.js +103 -0
  48. package/dist/tests/structural/bundle-skill-reference-contract.test.js +56 -0
  49. package/dist/tests/utils/grouping.test.js +2 -2
  50. package/dist/tests/utils/registry-view.test.js +7 -7
  51. package/package.json +1 -1
  52. package/prebuilds/darwin-arm64/secure_fs.node +0 -0
  53. package/prebuilds/darwin-x64/secure_fs.node +0 -0
  54. package/prebuilds/win32-arm64/secure_fs.node +0 -0
  55. package/prebuilds/win32-x64/secure_fs.node +0 -0
@@ -136,13 +136,25 @@ function ownedWorktreeExists(repo, worktreePath, branch) {
136
136
  * worktree (contenido de filesystem que SÍ podría ser ajeno), una branch es
137
137
  * solo un ref; nada más crea una con ese nombre exacto. */
138
138
  function branchExists(repo, branch) {
139
+ // `show-ref --quiet` puede devolver 1 tanto para una ref ausente como
140
+ // para un repositorio que ni siquiera pudo abrirse. Primero probamos que
141
+ // git puede identificar el repositorio; solo después status 1 significa
142
+ // honestamente "la branch no existe".
143
+ try {
144
+ git(repo, ['rev-parse', '--git-dir']);
145
+ }
146
+ catch (error) {
147
+ throw new Error(`branchExists: repo de ${branch} indemostrable: ${error.message}`);
148
+ }
139
149
  try {
140
150
  git(repo, ['show-ref', '--verify', '--quiet', `refs/heads/${branch}`]);
141
- return true;
142
151
  }
143
- catch {
144
- return false;
152
+ catch (error) {
153
+ if (error.status === 1)
154
+ return false;
155
+ throw new Error(`branchExists: existencia de ${branch} indemostrable: ${error.message}`);
145
156
  }
157
+ return true;
146
158
  }
147
159
  /** Task 9 (paso BRANCH_REMOVED, Task 13 endurece el guard — R4.10/C9): borra
148
160
  * la branch local determinista de un track SOLO después de que su worktree
@@ -269,7 +269,25 @@ function nextProtocolEffect(s) {
269
269
  }
270
270
  function reconcileProtocol(s, observation) {
271
271
  const out = structuredClone(s);
272
- if (observation.kind === 'prepare-failed') {
272
+ if (observation.kind === 'teardown-requested') {
273
+ const requested = out.tracks[observation.trackId];
274
+ if (requested === undefined)
275
+ throw new Error(`track desconocido: ${observation.trackId}`);
276
+ if (out.cohortPhase !== 'BLOCKED' && out.cohortPhase !== 'SERIAL' && out.cohortPhase !== 'COMPLETE'
277
+ && out.cohortPhase !== 'FALLBACK_PENDING') {
278
+ out.cohortPhase = 'FALLBACK_PENDING';
279
+ out.fallbackReason = `controller-requested:${observation.trackId}`;
280
+ // La evidencia de QA/integración describe la ruta de finalización
281
+ // que acabamos de abandonar. Debe desaparecer ANTES de mover los
282
+ // tracks a teardown: de otro modo el invariante de integración
283
+ // final ve evidence viva junto a tracks no mergeados y rechaza la
284
+ // transición segura hacia fallback.
285
+ out.globalQaHeadSha = undefined;
286
+ out.finalIntegrationJobId = undefined;
287
+ markTeardownRequested(out.tracks);
288
+ }
289
+ }
290
+ else if (observation.kind === 'prepare-failed') {
273
291
  out.cohortPhase = 'FALLBACK_PENDING';
274
292
  out.fallbackReason = `prepare-failed:${observation.trackId}`;
275
293
  markTeardownRequested(out.tracks);
@@ -295,10 +313,11 @@ function reconcileProtocol(s, observation) {
295
313
  // `decideTeardown` documenta para BLOCKED: solo el operador lo
296
314
  // saca de acá.
297
315
  const t = out.tracks[observation.trackId];
298
- if (t !== undefined) {
299
- t.phase = 'BLOCKED';
300
- t.blockedReason = observation.detail;
301
- }
316
+ if (t === undefined)
317
+ throw new Error(`track desconocido: ${observation.trackId}`);
318
+ t.phase = 'BLOCKED';
319
+ t.blockedReason = observation.detail;
320
+ out.cohortPhase = 'BLOCKED';
302
321
  }
303
322
  // effect-failed fuera de PREPARING/begin-teardown: sin transición definida todavía (no ejercitado por T1).
304
323
  }
@@ -351,6 +370,7 @@ function reconcileProtocol(s, observation) {
351
370
  if (decision === 'block-foreign') {
352
371
  t.phase = 'BLOCKED';
353
372
  t.blockedReason = observation.foreignSupervisor ? 'identidad de supervisor ajena' : 'worktree preexistente ajeno';
373
+ out.cohortPhase = 'BLOCKED';
354
374
  }
355
375
  else if (decision === 'accept-supervisor-stopped') {
356
376
  t.phase = 'SUPERVISOR_STOPPED';
@@ -432,7 +452,7 @@ function reconcileProtocol(s, observation) {
432
452
  }
433
453
  function observeProtocolEffect(s, effect, observation) {
434
454
  if (observation.kind === 'effect-failed' || observation.kind === 'prepare-failed' || observation.kind === 'join-observation'
435
- || observation.kind === 'freeze-observation' || observation.kind === 'join-requested'
455
+ || observation.kind === 'freeze-observation' || observation.kind === 'join-requested' || observation.kind === 'teardown-requested'
436
456
  || observation.kind === 'supervisor-observed' || observation.kind === 'worktree-observed'
437
457
  || observation.kind === 'teardown-observation'
438
458
  || observation.kind === 'global-qa-pass'
package/dist/src/index.js CHANGED
@@ -4,6 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  return (mod && mod.__esModule) ? mod : { "default": mod };
5
5
  };
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.program = void 0;
7
8
  const prompts_1 = require("@clack/prompts");
8
9
  const commander_1 = require("commander");
9
10
  const config_1 = require("./utils/config");
@@ -51,9 +52,9 @@ const update_1 = require("./commands/update");
51
52
  const agent_targets_1 = require("./core/agent-targets");
52
53
  const update_check_1 = require("./core/update-check");
53
54
  const cli_version_1 = require("./core/cli-version");
54
- const program = new commander_1.Command();
55
- program.name('awm').description('Agentic Workflow Manager').version((0, cli_version_1.cliVersion)());
56
- program.hook('postAction', (_command, actionCommand) => {
55
+ exports.program = new commander_1.Command();
56
+ exports.program.name('awm').description('Agentic Workflow Manager').version((0, cli_version_1.cliVersion)());
57
+ exports.program.hook('postAction', (_command, actionCommand) => {
57
58
  if (actionCommand.name() === 'preflight')
58
59
  return;
59
60
  if (actionCommand.parent?.name() === 'plan' && actionCommand.name() === 'validate' && actionCommand.opts().json === true)
@@ -108,7 +109,7 @@ function resolveSelectedArtifacts(selections) {
108
109
  // --type skill` como el camino scripteado — una invocacion que siempre fallo con
109
110
  // «Bundle "<skill>" not found». Los playbooks se escribieron contra la documentacion, no
110
111
  // contra el comportamiento. Un flag que no se lee es peor que uno ausente: promete.
111
- program.command('add [name]')
112
+ exports.program.command('add [name]')
112
113
  .description('Add a bundle (package of skills) interactively, or non-interactively with flags')
113
114
  .option('-a, --agent <agent>', `Target agent: ${providers_1.AGENT_TARGETS.join(', ')}`)
114
115
  .option('-s, --scope <scope>', 'Scope: local or global')
@@ -117,6 +118,7 @@ program.command('add [name]')
117
118
  .option('--all', 'install all artifacts from all packages without prompting')
118
119
  .action(async (name, options) => {
119
120
  (0, prompts_1.intro)(picocolors_1.default.bgCyan(picocolors_1.default.black(' AWM - Agentic Workflow Manager ')));
121
+ const reporter = (0, bundles_1.createBundleDiagnosticReporter)((message) => process.stderr.write(`${message}\n`));
120
122
  // 1. Sync the registry
121
123
  const s = (0, prompts_1.spinner)();
122
124
  s.start('Syncing registries...');
@@ -137,7 +139,7 @@ program.command('add [name]')
137
139
  }
138
140
  // 1b. If `name` matches a bundle, run the bundle-activation flow and exit.
139
141
  if (name) {
140
- const allBundles = (0, bundles_1.discoverAllBundles)();
142
+ const allBundles = (0, bundles_1.discoverAllBundles)(undefined, reporter);
141
143
  const prefs = (0, config_1.getPreferences)();
142
144
  const outcome = (0, add_1.runAddBundleCore)({ name, agent: options.agent, scope: options.scope, method: options.method }, prefs, allBundles);
143
145
  if (outcome.code !== 0)
@@ -181,7 +183,7 @@ program.command('add [name]')
181
183
  }
182
184
  const includeWorkflows = targetAgents.some((a) => (0, providers_1.providerFor)(a).workflow !== null);
183
185
  const includeAgents = targetAgents.some((a) => (0, providers_1.providerFor)(a).agent !== null);
184
- const allView = (0, registry_view_1.buildPackageView)(skills, includeWorkflows ? workflows : [], includeAgents ? agents : [], (0, bundles_1.discoverAllBundles)());
186
+ const allView = (0, registry_view_1.buildPackageView)(skills, includeWorkflows ? workflows : [], includeAgents ? agents : [], (0, bundles_1.discoverAllBundles)(undefined, reporter));
185
187
  const allDedup = new Map();
186
188
  for (const pkg of allView) {
187
189
  for (const a of pkg.artifacts) {
@@ -294,7 +296,7 @@ program.command('add [name]')
294
296
  // 4. Build the package view, filtered to artifact types the target agent(s) support
295
297
  const includeWorkflows = targetAgents.some(a => (0, providers_1.providerFor)(a).workflow !== null);
296
298
  const includeAgents = targetAgents.some(a => (0, providers_1.providerFor)(a).agent !== null);
297
- const view = (0, registry_view_1.buildPackageView)(skills, includeWorkflows ? workflows : [], includeAgents ? agents : [], (0, bundles_1.discoverAllBundles)());
299
+ const view = (0, registry_view_1.buildPackageView)(skills, includeWorkflows ? workflows : [], includeAgents ? agents : [], (0, bundles_1.discoverAllBundles)(undefined, reporter));
298
300
  if (view.length === 0) {
299
301
  (0, prompts_1.outro)(picocolors_1.default.yellow('No artifacts available for the selected agent(s).'));
300
302
  process.exit(0);
@@ -417,7 +419,7 @@ program.command('add [name]')
417
419
  process.exit(1);
418
420
  }
419
421
  });
420
- program.command('update')
422
+ exports.program.command('update')
421
423
  .description('Sync all configured registries, reconcile artifacts and re-sync hooks')
422
424
  .option('-a, --agent <agent>', `Target agent(s), comma-separated: ${providers_1.AGENT_TARGETS.join(', ')} (defaults to every enabled agent)`)
423
425
  .option('-y, --yes', 'Non-interactive: never prompt, and self-update the CLI without asking')
@@ -431,7 +433,7 @@ program.command('update')
431
433
  (0, prompts_1.outro)(result.code === 0 ? message : picocolors_1.default.red(message));
432
434
  process.exitCode = result.code;
433
435
  });
434
- program.command('sync')
436
+ exports.program.command('sync')
435
437
  .description('Rebuild local skill symlinks from .awm/profile.json (e.g. after cloning on a new machine)')
436
438
  .option('-a, --agent <agent>', `Target agent(s), comma-separated: ${providers_1.AGENT_TARGETS.join(', ')} (defaults to every enabled agent)`)
437
439
  .option('-m, --method <method>', 'Install method: symlink or copy', 'symlink')
@@ -441,11 +443,12 @@ program.command('sync')
441
443
  (0, prompts_1.outro)(code === 0 ? 'Done.' : picocolors_1.default.red('Sync failed — see errors above.'));
442
444
  process.exitCode = code;
443
445
  });
444
- program.command('list [package]')
446
+ exports.program.command('list [package]')
445
447
  .description('List available artifacts. With no argument shows a package summary; pass a package name or --all for detail.')
446
448
  .option('-a, --all', 'Expand every package')
447
449
  .action(async (packageName, options) => {
448
450
  (0, prompts_1.intro)(picocolors_1.default.bgCyan(picocolors_1.default.black(' AWM - Registry Listing ')));
451
+ const reporter = (0, bundles_1.createBundleDiagnosticReporter)((message) => process.stderr.write(`${message}\n`));
449
452
  const s = (0, prompts_1.spinner)();
450
453
  s.start('Syncing registries...');
451
454
  const listSyncResults = await (0, registries_1.syncRegistries)();
@@ -462,7 +465,7 @@ program.command('list [package]')
462
465
  process.exitCode = 1;
463
466
  return;
464
467
  }
465
- const fullView = (0, registry_view_1.buildPackageView)((0, discovery_1.discoverSkills)(), (0, discovery_1.discoverWorkflows)(), (0, discovery_1.discoverAgents)(), (0, bundles_1.discoverAllBundles)());
468
+ const fullView = (0, registry_view_1.buildPackageView)((0, discovery_1.discoverSkills)(), (0, discovery_1.discoverWorkflows)(), (0, discovery_1.discoverAgents)(), (0, bundles_1.discoverAllBundles)(undefined, reporter));
466
469
  const view = options.all ? fullView : fullView.filter((p) => p.visibility !== 'private');
467
470
  if (view.length === 0) {
468
471
  (0, prompts_1.outro)(picocolors_1.default.yellow('No artifacts found in the registry. Run `awm update` or check your registry content.'));
@@ -516,13 +519,14 @@ program.command('list [package]')
516
519
  // prometia y no contra el comportamiento. Ver docs/decisions.md, D-006.
517
520
  //
518
521
  // El nombre es de BUNDLE, igual que en `add` (D-001).
519
- program.command('remove [name]')
522
+ exports.program.command('remove [name]')
520
523
  .description('Remove an installed bundle interactively, or non-interactively with flags')
521
524
  .option('-a, --agent <agent>', `Target agent(s), comma-separated: ${providers_1.AGENT_TARGETS.join(', ')} (defaults to every enabled agent)`)
522
525
  .option('-s, --scope <scope>', 'Scope: local or global (skips the prompt)')
523
526
  .option('-y, --yes', 'Skip the confirmation prompt (requires a name)')
524
527
  .action(async (name, options) => {
525
528
  (0, prompts_1.intro)(picocolors_1.default.bgCyan(picocolors_1.default.black(' AWM - Remove Artifact ')));
529
+ const reporter = (0, bundles_1.createBundleDiagnosticReporter)((message) => process.stderr.write(`${message}\n`));
526
530
  const prefs = (0, config_1.getPreferences)();
527
531
  // `--yes` sin nombre borraria lo que el usuario nunca eligio: un `rm -rf` silencioso
528
532
  // sobre todo lo instalado. Se rechaza antes de tocar nada.
@@ -591,7 +595,8 @@ program.command('remove [name]')
591
595
  (0, prompts_1.outro)(picocolors_1.default.yellow('No installed artifacts found for the selected agents/scope.'));
592
596
  process.exit(0);
593
597
  }
594
- const groupedOpts = (0, grouping_1.buildGroupedOptions)(installed, (0, bundles_1.discoverAllBundles)(), (c) => {
598
+ const bundles = (0, bundles_1.discoverAllBundles)(undefined, reporter);
599
+ const groupedOpts = (0, grouping_1.buildGroupedOptions)(installed, bundles, (c) => {
595
600
  const hasSkill = c.artifacts.some(a => a.type === 'skill');
596
601
  const hasWf = c.artifacts.some(a => a.type === 'workflow');
597
602
  const hasAgent = c.artifacts.some(a => a.type === 'agent');
@@ -608,14 +613,14 @@ program.command('remove [name]')
608
613
  // Los artefactos del bundle pedido que estan REALMENTE instalados en este
609
614
  // scope. Se cruza contra `installed`: lo que no esta instalado no se puede
610
615
  // remover, y decirlo es mejor que borrar un subconjunto en silencio.
611
- const bundle = (0, bundles_1.discoverAllBundles)().find((b) => b.name === name);
616
+ const bundle = bundles.find((b) => b.name === name);
612
617
  if (!bundle) {
613
618
  console.error(picocolors_1.default.red(`Bundle "${name}" not found in registry.`));
614
619
  console.error(picocolors_1.default.dim('Run `awm list` to see available packages.'));
615
620
  process.exit(1);
616
621
  }
617
622
  const owned = new Set([
618
- ...bundle.skills.map((sk) => sk.name),
623
+ ...bundle.skills,
619
624
  ...bundle.workflows,
620
625
  ...bundle.agents,
621
626
  ]);
@@ -677,7 +682,7 @@ function loadEnvFile(cwd) {
677
682
  }
678
683
  return env;
679
684
  }
680
- const miroCmd = program.command('miro').description('Miro board integration');
685
+ const miroCmd = exports.program.command('miro').description('Miro board integration');
681
686
  miroCmd.command('sync <storyMapPath>')
682
687
  .description('Sync a story-map.md file to a Miro board frame')
683
688
  .action(async (storyMapPath) => {
@@ -726,29 +731,31 @@ miroCmd.command('sync <storyMapPath>')
726
731
  process.exit(1);
727
732
  }
728
733
  });
729
- (0, hooks_1.registerHooksCommand)(program);
730
- (0, sensors_1.registerSensorsCommand)(program);
731
- (0, ledger_1.registerLedgerCommand)(program);
732
- (0, context_budget_1.registerContextBudgetCommand)(program);
733
- (0, preflight_1.registerPreflightCommand)(program);
734
- (0, plan_1.registerPlanCommand)(program);
735
- (0, doctor_1.registerDoctorCommand)(program);
736
- (0, backup_1.registerBackupCommand)(program);
737
- (0, init_1.registerInitCommand)(program);
738
- (0, registry_1.registerRegistryCommand)(program);
739
- (0, pin_1.registerPinCommands)(program);
740
- (0, export_1.registerExportCommand)(program);
741
- (0, agent_1.registerAgentCommand)(program);
742
- (0, job_1.registerJobCommand)(program);
743
- (0, watch_1.registerWatchCommand)(program);
744
- (0, track_1.registerTrackCommand)(program);
745
- (0, evidence_1.registerEvidenceCommand)(program);
746
- (0, process_1.registerProcessCommand)(program);
747
- (0, context_1.registerContextCommand)(program);
734
+ (0, hooks_1.registerHooksCommand)(exports.program);
735
+ (0, sensors_1.registerSensorsCommand)(exports.program);
736
+ (0, ledger_1.registerLedgerCommand)(exports.program);
737
+ (0, context_budget_1.registerContextBudgetCommand)(exports.program);
738
+ (0, preflight_1.registerPreflightCommand)(exports.program);
739
+ (0, plan_1.registerPlanCommand)(exports.program);
740
+ (0, doctor_1.registerDoctorCommand)(exports.program);
741
+ (0, backup_1.registerBackupCommand)(exports.program);
742
+ (0, init_1.registerInitCommand)(exports.program);
743
+ (0, registry_1.registerRegistryCommand)(exports.program);
744
+ (0, pin_1.registerPinCommands)(exports.program);
745
+ (0, export_1.registerExportCommand)(exports.program);
746
+ (0, agent_1.registerAgentCommand)(exports.program);
747
+ (0, job_1.registerJobCommand)(exports.program);
748
+ (0, watch_1.registerWatchCommand)(exports.program);
749
+ (0, track_1.registerTrackCommand)(exports.program);
750
+ (0, evidence_1.registerEvidenceCommand)(exports.program);
751
+ (0, process_1.registerProcessCommand)(exports.program);
752
+ (0, context_1.registerContextCommand)(exports.program);
748
753
  // Commander only waits for async action handlers through parseAsync(). The CLI has
749
754
  // async commands (including `sensors coverage`), so returning its promise keeps the
750
755
  // process alive until their JSON/output contract has been completed.
751
- program.parseAsync().catch((error) => {
752
- console.error(error instanceof Error ? error.message : String(error));
753
- process.exitCode = 1;
754
- });
756
+ if (require.main === module) {
757
+ exports.program.parseAsync().catch((error) => {
758
+ console.error(error instanceof Error ? error.message : String(error));
759
+ process.exitCode = 1;
760
+ });
761
+ }
@@ -8,7 +8,7 @@ function buildGroupedOptions(artifacts, bundles, formatLabel) {
8
8
  let foundParent = false;
9
9
  const baseName = (a.type === 'workflow' || a.type === 'agent') ? a.name.replace(/\.md$/, '') : a.name;
10
10
  for (const p of bundles) {
11
- const skillNames = p.skills.map((s) => s.name);
11
+ const skillNames = p.skills;
12
12
  if ((a.type === 'skill' && skillNames.includes(baseName)) ||
13
13
  (a.type === 'workflow' && p.workflows.includes(baseName)) ||
14
14
  (a.type === 'agent' && p.agents.includes(baseName))) {
@@ -40,7 +40,7 @@ function buildPackageView(skills, workflows, agents, bundles) {
40
40
  const packages = [];
41
41
  const claimed = new Set();
42
42
  for (const b of bundles) {
43
- const skillNames = b.skills.map((s) => s.name);
43
+ const skillNames = b.skills;
44
44
  const arts = all.filter((a) => (a.type === 'skill' && skillNames.includes(a.name)) ||
45
45
  (a.type === 'workflow' && b.workflows.includes(a.name)) ||
46
46
  (a.type === 'agent' && b.agents.includes(a.name)));
@@ -0,0 +1,145 @@
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
+ const child_process_1 = require("child_process");
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const os_1 = __importDefault(require("os"));
9
+ const path_1 = __importDefault(require("path"));
10
+ jest.mock('@clack/prompts', () => ({
11
+ intro: jest.fn(), outro: jest.fn(), spinner: () => ({ start: jest.fn(), stop: jest.fn() }),
12
+ select: jest.fn(), multiselect: jest.fn(), confirm: jest.fn(), isCancel: jest.fn(() => false),
13
+ }));
14
+ function writeManifest(root, legacy) {
15
+ fs_1.default.mkdirSync(path_1.default.join(root, 'bundles', 'demo'), { recursive: true });
16
+ fs_1.default.mkdirSync(path_1.default.join(root, 'skills', 'demo-skill'), { recursive: true });
17
+ fs_1.default.writeFileSync(path_1.default.join(root, 'catalog.json'), JSON.stringify({ version: 1, bundles: [{ name: 'demo', source: './bundles/demo', version: '1.0.0', scope: 'baseline' }] }));
18
+ fs_1.default.writeFileSync(path_1.default.join(root, 'bundles', 'demo', 'bundle.json'), JSON.stringify({
19
+ name: 'demo', description: 'Diagnostic fixture', version: '1.0.0', scope: 'baseline', dependsOn: [],
20
+ skills: legacy ? [{ name: 'demo-skill', onSignal: true }] : ['demo-skill'], workflows: [], agents: [],
21
+ }));
22
+ fs_1.default.writeFileSync(path_1.default.join(root, 'skills', 'demo-skill', 'SKILL.md'), '---\nname: demo-skill\nportable: true\ndescription: fixture\n---\n');
23
+ }
24
+ function git(cwd, args) {
25
+ (0, child_process_1.execFileSync)('git', ['-c', 'user.email=test@example.test', '-c', 'user.name=Test', ...args], { cwd, stdio: 'pipe' });
26
+ }
27
+ describe('S2 bundle compatibility diagnostic command routes', () => {
28
+ let home;
29
+ let work;
30
+ let priorHome;
31
+ let priorAwmHome;
32
+ let stdout;
33
+ beforeEach(() => {
34
+ home = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-bundle-command-home-'));
35
+ work = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-bundle-command-work-'));
36
+ priorHome = process.env.HOME;
37
+ priorAwmHome = process.env.AWM_HOME;
38
+ process.env.HOME = home;
39
+ process.env.AWM_HOME = path_1.default.join(home, '.awm');
40
+ stdout = jest.spyOn(process.stdout, 'write').mockImplementation(() => true);
41
+ jest.spyOn(process.stderr, 'write').mockImplementation(() => true);
42
+ jest.spyOn(process, 'exit').mockImplementation(((code) => {
43
+ throw new Error(`unexpected process.exit(${code ?? 0})`);
44
+ }));
45
+ jest.spyOn(console, 'log').mockImplementation(() => undefined);
46
+ jest.spyOn(console, 'warn').mockImplementation(() => undefined);
47
+ jest.spyOn(console, 'error').mockImplementation(() => undefined);
48
+ });
49
+ afterEach(() => {
50
+ jest.restoreAllMocks();
51
+ jest.resetModules();
52
+ fs_1.default.rmSync(home, { recursive: true, force: true });
53
+ fs_1.default.rmSync(work, { recursive: true, force: true });
54
+ if (priorHome === undefined)
55
+ delete process.env.HOME;
56
+ else
57
+ process.env.HOME = priorHome;
58
+ if (priorAwmHome === undefined)
59
+ delete process.env.AWM_HOME;
60
+ else
61
+ process.env.AWM_HOME = priorAwmHome;
62
+ });
63
+ function configureRegistry(legacy) {
64
+ fs_1.default.rmSync(path_1.default.join(process.env.AWM_HOME, 'registries'), { recursive: true, force: true });
65
+ const root = path_1.default.join(process.env.AWM_HOME, 'registries', 'baseline');
66
+ writeManifest(root, legacy);
67
+ fs_1.default.mkdirSync(process.env.AWM_HOME, { recursive: true });
68
+ fs_1.default.writeFileSync(path_1.default.join(process.env.AWM_HOME, 'registries.json'), JSON.stringify([{ name: 'baseline', remote: root }]));
69
+ fs_1.default.writeFileSync(path_1.default.join(process.env.AWM_HOME, 'preferences.json'), JSON.stringify({
70
+ defaultAgent: 'claude-code', enabledAgents: ['claude-code'], installMethod: 'symlink', defaultScope: 'local',
71
+ }));
72
+ return root;
73
+ }
74
+ function stdoutText() {
75
+ return stdout.mock.calls.map((call) => String(call[0])).join('');
76
+ }
77
+ async function run(route, legacy) {
78
+ fs_1.default.rmSync(work, { recursive: true, force: true });
79
+ fs_1.default.mkdirSync(work, { recursive: true });
80
+ if (route === 'registry-add')
81
+ fs_1.default.rmSync(process.env.AWM_HOME, { recursive: true, force: true });
82
+ const root = route === 'registry-add' ? undefined : configureRegistry(legacy);
83
+ const warnings = [];
84
+ const reporter = (warning) => warnings.push(warning);
85
+ if (route === 'init') {
86
+ const { runInit } = require('../../src/commands/init');
87
+ await runInit({ cwd: work, yes: true, json: true, reporter, assertProviderSupported: () => { }, actions: { syncCache: async () => [], installHook: () => ({ status: 'installed' }) } });
88
+ expect(JSON.parse(stdoutText())).toHaveProperty('result');
89
+ }
90
+ else if (route === 'sync') {
91
+ fs_1.default.mkdirSync(path_1.default.join(work, '.awm'), { recursive: true });
92
+ fs_1.default.writeFileSync(path_1.default.join(work, 'package.json'), '{}');
93
+ fs_1.default.writeFileSync(path_1.default.join(work, '.awm', 'profile.json'), JSON.stringify({ extensions: ['demo'] }));
94
+ const { runSyncCore } = require('../../src/commands/sync');
95
+ await runSyncCore({ cwd: work, reporter }, {
96
+ syncRegistries: async () => [], verifyMinCliVersions: () => [], verifyProjectPins: async () => [],
97
+ syncProfile: () => ({ extensions: ['demo'], installed: [], skipped: [], transactionIds: [] }), reconcileProjectSkillLinks: () => [],
98
+ });
99
+ }
100
+ else if (route === 'update') {
101
+ const { runUpdateCore } = require('../../src/commands/update');
102
+ const { planReconciliation } = require('../../src/core/reconciliation');
103
+ await runUpdateCore({ yes: true, reporter }, {
104
+ syncRegistries: async () => [{ name: 'baseline', action: 'pulled', version: 'v1.0.0' }], verifyMinCliVersions: () => [],
105
+ regenerateGlobalContext: () => [], planReconciliation, applyInstallPlan: () => ({ installed: [], skipped: [], transactionId: 'test', modifiedFiles: [] }),
106
+ resyncInstalledHooks: () => [], offerSelfUpdate: async () => { },
107
+ });
108
+ }
109
+ else if (route === 'doctor') {
110
+ const { runDoctor } = require('../../src/commands/doctor');
111
+ runDoctor({ cwd: work, json: true, reporter });
112
+ expect(JSON.parse(stdoutText())).toHaveProperty('providers');
113
+ }
114
+ else if (route === 'export') {
115
+ const { runExportCommand } = require('../../src/commands/export');
116
+ runExportCommand('demo', { target: 'claude-ai', out: path_1.default.join(work, 'export') }, { roots: [root], reporter, log: () => { }, zip: () => ({ ok: false, missing: true }) });
117
+ }
118
+ else {
119
+ jest.resetModules();
120
+ const bundles = require('../../src/core/bundles');
121
+ const createReporter = bundles.createBundleDiagnosticReporter;
122
+ jest.spyOn(bundles, 'createBundleDiagnosticReporter').mockImplementation(() => createReporter((warning) => warnings.push(warning)));
123
+ const { registerRegistryCommand } = require('../../src/commands/registry');
124
+ const program = new (require('commander').Command)();
125
+ registerRegistryCommand(program);
126
+ if (route === 'registry-add') {
127
+ const source = path_1.default.join(work, 'source');
128
+ writeManifest(source, legacy);
129
+ git(source, ['init', '-q']);
130
+ git(source, ['add', '-A']);
131
+ git(source, ['commit', '-qm', 'fixture']);
132
+ await program.parseAsync(['node', 'awm', 'registry', 'add', source, '--name', 'fixture', '--no-install']);
133
+ }
134
+ else {
135
+ await program.parseAsync(['node', 'awm', 'registry', 'list']);
136
+ }
137
+ }
138
+ return { warnings };
139
+ }
140
+ it.each(['init', 'sync', 'update', 'doctor', 'export', 'registry-add', 'registry-status'])('%s emits one legacy warning and none for canonical manifests', async (route) => {
141
+ expect((await run(route, true)).warnings).toHaveLength(1);
142
+ stdout.mockClear();
143
+ expect((await run(route, false)).warnings).toEqual([]);
144
+ });
145
+ });
@@ -11,7 +11,7 @@ const okZip = (cwd, zipName) => {
11
11
  fs_1.default.writeFileSync(path_1.default.join(cwd, zipName), 'fake-zip');
12
12
  return { ok: true, missing: false };
13
13
  };
14
- function makeRoot() {
14
+ function makeRoot(legacy = false) {
15
15
  const root = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-cmd-root-'));
16
16
  fs_1.default.mkdirSync(path_1.default.join(root, 'bundles/dev'), { recursive: true });
17
17
  fs_1.default.writeFileSync(path_1.default.join(root, 'catalog.json'), JSON.stringify({
@@ -20,7 +20,7 @@ function makeRoot() {
20
20
  }));
21
21
  fs_1.default.writeFileSync(path_1.default.join(root, 'bundles/dev/bundle.json'), JSON.stringify({
22
22
  name: 'dev', version: '1.0.0', scope: 'baseline', dependsOn: [],
23
- skills: ['proc-skill', { name: 'mermaid', onSignal: true }], workflows: [], agents: [],
23
+ skills: legacy ? ['proc-skill', { name: 'mermaid', onSignal: true }] : ['proc-skill', 'mermaid'], workflows: [], agents: [],
24
24
  }));
25
25
  const mk = (name, fm) => {
26
26
  const dir = path_1.default.join(root, 'skills', name);
@@ -61,4 +61,30 @@ describe('runExportCommand (salida al usuario)', () => {
61
61
  expect(() => (0, export_1.runExportCommand)('dev', { target: 'nope', out }, { roots: [root], zip: okZip, log }))
62
62
  .toThrow(/Valid targets/);
63
63
  });
64
+ it('reports one legacy warning and no canonical warning through the command reporter seam', () => {
65
+ const legacyWarnings = [];
66
+ const legacy = makeRoot(true);
67
+ try {
68
+ (0, export_1.runExportCommand)('dev', { target: 'claude-ai', out }, {
69
+ roots: [legacy], zip: okZip, log,
70
+ reporter: (message) => legacyWarnings.push(message),
71
+ });
72
+ expect(legacyWarnings).toHaveLength(1);
73
+ }
74
+ finally {
75
+ fs_1.default.rmSync(legacy, { recursive: true, force: true });
76
+ }
77
+ const canonicalWarnings = [];
78
+ const canonical = makeRoot(false);
79
+ try {
80
+ (0, export_1.runExportCommand)('dev', { target: 'claude-ai', out }, {
81
+ roots: [canonical], zip: okZip, log,
82
+ reporter: (message) => canonicalWarnings.push(message),
83
+ });
84
+ expect(canonicalWarnings).toEqual([]);
85
+ }
86
+ finally {
87
+ fs_1.default.rmSync(canonical, { recursive: true, force: true });
88
+ }
89
+ });
64
90
  });
@@ -60,7 +60,7 @@ describe('multi-agent targeting (add/remove/sync/update/doctor)', () => {
60
60
  function bundle(over = {}) {
61
61
  return {
62
62
  name: 'demo', description: '', version: '1.0.0', scope: 'baseline', visibility: 'public',
63
- dependsOn: [], skills: [{ name: 'demo-skill', onSignal: false }], workflows: [], agents: [],
63
+ dependsOn: [], skills: ['demo-skill'], workflows: [], agents: [],
64
64
  ...over,
65
65
  };
66
66
  }
@@ -103,6 +103,12 @@ describe('awm track — verbos mutantes son request-only (R6.1)', () => {
103
103
  expect(git(repo, ['rev-parse', 'HEAD'])).toBe(beforeHead);
104
104
  expect(fs_1.default.readFileSync((0, paths_1.statePath)(repo, branch), 'utf8')).toBe(beforeState);
105
105
  });
106
+ test('remove se anuncia como cancelación request-only soportada (TR-REQ-10)', async () => {
107
+ const out = await runCli(repo, ['track', 'remove', '--help']);
108
+ expect(out.exitCode).toBe(0);
109
+ expect(out.out).toContain('cancela la cohorte');
110
+ expect(out.out).not.toContain('NO IMPLEMENTADO');
111
+ });
106
112
  // `finalize` es PLAN-scoped y sin <trackId>, así que no entra en las matrices de
107
113
  // add/join/remove — pero comparte con ellas la propiedad que define a la superficie:
108
114
  // emite una request y no toca Git ni state.json.
@@ -126,7 +126,7 @@ describe('aplicacion transaccional de requests', () => {
126
126
  expect(Object.keys((0, store_1.readJournal)(repo, 'rama').state.jobs)).toHaveLength(1); // sin duplicados
127
127
  });
128
128
  test('register-entity CREA task con VerificationPlan y ReviewObligations; cycle-plan y dispatch (R1.4/R1.4b)', () => {
129
- (0, requests_1.emitRequest)(repo, 'rama', {
129
+ const request = (0, requests_1.emitRequest)(repo, 'rama', {
130
130
  kind: 'register-entity', generationToken: 'g1', idempotencyKey: 'e1',
131
131
  payload: {
132
132
  entity: 'task', taskId: 'T1', title: 'implementar',
@@ -513,20 +513,57 @@ describe('aplicacion transaccional de requests', () => {
513
513
  expect(out.rejectedInvalid).toBe(1);
514
514
  expect(fs_1.default.existsSync(`${r.file}.rejected`)).toBe(true);
515
515
  });
516
- test('un kind SIN handler falla cerrado: rechazo visible, no un `applied` que borra el archivo', () => {
517
- // `track-teardown-request` es un kind real, emitido por `awm track remove`, que hoy
518
- // no tiene handler en el supervisor. Antes de este cambio se contaba como aplicado y
519
- // desaparecía sin dejar rastro; ahora queda registrado como problema y el archivo se
520
- // conserva con sufijo `.rejected`, que es lo que permite darse cuenta.
521
- const r = (0, requests_1.emitRequest)(repo, 'rama', {
522
- kind: 'track-teardown-request', generationToken: 'g1', idempotencyKey: 'teardown-1',
516
+ test('track-teardown-request persiste intención sin mover fase ni cohorte', () => {
517
+ const s0 = (0, store_1.readJournal)(repo, 'rama').state;
518
+ s0.tracks = [{ ...trackRef('alpha'), phase: 'ACTIVE' }, { ...trackRef('beta'), phase: 'ACTIVE' }];
519
+ s0.cohortPhase = 'JOINING';
520
+ (0, store_1.writeJournal)(repo, 'rama', s0);
521
+ const request = (0, requests_1.emitRequest)(repo, 'rama', {
522
+ kind: 'track-teardown-request', generationToken: 'g1', idempotencyKey: 'teardown-alpha',
523
523
  payload: { trackId: 'alpha' },
524
524
  });
525
+ expect((0, apply_1.consumePendingRequests)(repo, 'rama', 'g1').applied).toBe(1);
526
+ const state = (0, store_1.readJournal)(repo, 'rama').state;
527
+ expect(state.tracks[0].teardownRequested).toBe(true);
528
+ expect(state.tracks[0].phase).toBe('ACTIVE');
529
+ expect(state.cohortPhase).toBe('JOINING');
530
+ expect(state.appliedRequests[request.requestId]).toMatchObject({ outcome: 'applied', resultRef: 'alpha' });
531
+ });
532
+ test.each([
533
+ ['ausente', {}],
534
+ ['vacio', { trackId: '' }],
535
+ ['malformado', { trackId: 42 }],
536
+ ['desconocido', { trackId: 'no-existe' }],
537
+ ])('track-teardown-request %s se rechaza visiblemente sin mutar el track', (_case, payload) => {
538
+ const s0 = (0, store_1.readJournal)(repo, 'rama').state;
539
+ s0.tracks = [{ ...trackRef('alpha'), phase: 'ACTIVE' }, { ...trackRef('beta'), phase: 'ACTIVE' }];
540
+ s0.cohortPhase = 'JOINING';
541
+ (0, store_1.writeJournal)(repo, 'rama', s0);
542
+ const r = (0, requests_1.emitRequest)(repo, 'rama', {
543
+ kind: 'track-teardown-request', generationToken: 'g1', idempotencyKey: `teardown-${_case}`,
544
+ payload,
545
+ });
525
546
  const out = (0, apply_1.consumePendingRequests)(repo, 'rama', 'g1');
526
547
  expect(out.applied).toBe(0);
527
548
  expect(out.rejectedInvalid).toBe(1);
528
549
  expect(fs_1.default.existsSync(`${r.file}.rejected`)).toBe(true);
529
- expect((0, store_1.readJournal)(repo, 'rama').state.requestProblems.some((p) => p.kind === 'rejected')).toBe(true);
550
+ const state = (0, store_1.readJournal)(repo, 'rama').state;
551
+ expect(state.tracks).toEqual([{ ...trackRef('alpha'), phase: 'ACTIVE' }, { ...trackRef('beta'), phase: 'ACTIVE' }]);
552
+ expect(state.cohortPhase).toBe('JOINING');
553
+ });
554
+ test('track-teardown-request se rechaza si la cohorte no puede reconciliarse', () => {
555
+ const s0 = (0, store_1.readJournal)(repo, 'rama').state;
556
+ s0.tracks = [{ ...trackRef('alpha'), phase: 'ACTIVE' }];
557
+ s0.cohortPhase = 'JOINING';
558
+ (0, store_1.writeJournal)(repo, 'rama', s0);
559
+ const request = (0, requests_1.emitRequest)(repo, 'rama', {
560
+ kind: 'track-teardown-request', generationToken: 'g1', idempotencyKey: 'teardown-invalid-cohort',
561
+ payload: { trackId: 'alpha' },
562
+ });
563
+ const out = (0, apply_1.consumePendingRequests)(repo, 'rama', 'g1');
564
+ expect(out.rejectedInvalid).toBe(1);
565
+ expect(fs_1.default.existsSync(`${request.file}.rejected`)).toBe(true);
566
+ expect((0, store_1.readJournal)(repo, 'rama').state.tracks[0].teardownRequested).toBeUndefined();
530
567
  });
531
568
  test('track-finalize-request persiste el autoreporte del controller (qaFinalizeRequested) — puramente declarativo (R7.2)', () => {
532
569
  (0, requests_1.emitRequest)(repo, 'rama', {