agentic-workflow-manager 9.7.0 → 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 (41) hide show
  1. package/dist/src/commands/doctor.js +4 -2
  2. package/dist/src/commands/export.js +3 -0
  3. package/dist/src/commands/init.js +10 -2
  4. package/dist/src/commands/registry/add.js +2 -2
  5. package/dist/src/commands/registry/index.js +7 -5
  6. package/dist/src/commands/registry/install-bundles.js +4 -4
  7. package/dist/src/commands/registry/status.js +4 -4
  8. package/dist/src/commands/sync.js +2 -1
  9. package/dist/src/commands/update.js +3 -1
  10. package/dist/src/core/bundle-install.js +2 -2
  11. package/dist/src/core/bundles.js +92 -19
  12. package/dist/src/core/diagnostics/context.js +1 -1
  13. package/dist/src/core/export/index.js +1 -1
  14. package/dist/src/core/export/resolve.js +2 -2
  15. package/dist/src/core/reconciliation.js +1 -1
  16. package/dist/src/index.js +46 -39
  17. package/dist/src/utils/grouping.js +1 -1
  18. package/dist/src/utils/registry-view.js +1 -1
  19. package/dist/tests/commands/bundle-diagnostics.test.js +145 -0
  20. package/dist/tests/commands/export.test.js +28 -2
  21. package/dist/tests/commands/multi-agent-targeting.test.js +1 -1
  22. package/dist/tests/core/bundle-install.test.js +36 -0
  23. package/dist/tests/core/bundles-multiroot.test.js +24 -2
  24. package/dist/tests/core/bundles-overrides.test.js +10 -7
  25. package/dist/tests/core/bundles.test.js +75 -6
  26. package/dist/tests/core/diagnostics/context.test.js +2 -2
  27. package/dist/tests/core/export/engine.test.js +1 -1
  28. package/dist/tests/core/export/resolve.test.js +1 -1
  29. package/dist/tests/core/init/mutation-targets.test.js +1 -1
  30. package/dist/tests/core/init/orchestrator.test.js +1 -1
  31. package/dist/tests/core/init/steps.test.js +1 -1
  32. package/dist/tests/core/reconciliation.test.js +1 -1
  33. package/dist/tests/core/sync-profile-atomicity.test.js +1 -1
  34. package/dist/tests/structural/bundle-skill-reference-contract.test.js +56 -0
  35. package/dist/tests/utils/grouping.test.js +2 -2
  36. package/dist/tests/utils/registry-view.test.js +7 -7
  37. package/package.json +1 -1
  38. package/prebuilds/darwin-arm64/secure_fs.node +0 -0
  39. package/prebuilds/darwin-x64/secure_fs.node +0 -0
  40. package/prebuilds/win32-arm64/secure_fs.node +0 -0
  41. package/prebuilds/win32-x64/secure_fs.node +0 -0
@@ -19,6 +19,7 @@ const collect_1 = require("../core/dashboard/collect");
19
19
  const render_html_1 = require("../core/dashboard/render-html");
20
20
  const render_terminal_1 = require("../core/dashboard/render-terminal");
21
21
  const write_html_1 = require("../core/dashboard/write-html");
22
+ const bundles_1 = require("../core/bundles");
22
23
  function glyph(status) {
23
24
  if (status === 'ok')
24
25
  return picocolors_1.default.green('✔');
@@ -127,6 +128,7 @@ function renderProviderReport(report) {
127
128
  }
128
129
  function runDoctor(opts = {}) {
129
130
  const resolveTargets = opts.resolveTargets ?? agent_targets_1.resolveAgentTargets;
131
+ const reporter = opts.reporter ?? (0, bundles_1.createBundleDiagnosticReporter)((message) => process.stderr.write(`${message}\n`));
130
132
  const invalid = (message) => { process.stderr.write(`awm doctor: ${message}\n`); return 2; };
131
133
  const htmlRequested = opts.html !== undefined;
132
134
  if (opts.json && opts.full)
@@ -143,7 +145,7 @@ function runDoctor(opts = {}) {
143
145
  const target = htmlRequested ? (0, write_html_1.resolveHtmlTarget)({ cwd, target: opts.html, force: opts.force }) : undefined;
144
146
  const targets = resolveTargets({ prefs: (0, config_1.readPreferences)(), explicit: opts.agent });
145
147
  const collectSnapshot = opts.collectSnapshot ?? collect_1.collectDashboardSnapshot;
146
- const context = (0, context_1.gatherContext)({ cwd, agents: targets });
148
+ const context = (0, context_1.gatherContext)({ cwd, agents: targets, reporter });
147
149
  const snapshot = collectSnapshot({ cwd, now: new Date().toISOString(), adapters: {
148
150
  ...(0, collect_1.productionDashboardAdapters)(context),
149
151
  } });
@@ -175,7 +177,7 @@ function runDoctor(opts = {}) {
175
177
  }
176
178
  let report;
177
179
  try {
178
- const ctx = (0, context_1.gatherContext)({ cwd: opts.cwd, agents: targets });
180
+ const ctx = (0, context_1.gatherContext)({ cwd: opts.cwd, agents: targets, reporter });
179
181
  const providers = ctx.providers ?? [];
180
182
  report = { providers, overall: (0, checks_1.computeProviderOverall)(providers) };
181
183
  }
@@ -8,14 +8,17 @@ exports.registerExportCommand = registerExportCommand;
8
8
  const picocolors_1 = __importDefault(require("picocolors"));
9
9
  const path_1 = __importDefault(require("path"));
10
10
  const export_1 = require("../core/export");
11
+ const bundles_1 = require("../core/bundles");
11
12
  function runExportCommand(name, flags, deps = {}) {
12
13
  const log = deps.log ?? console.log;
14
+ const reporter = deps.reporter ?? (0, bundles_1.createBundleDiagnosticReporter)((message) => process.stderr.write(`${message}\n`));
13
15
  const opts = {
14
16
  name,
15
17
  target: flags.target,
16
18
  out: flags.out,
17
19
  roots: deps.roots,
18
20
  zip: deps.zip,
21
+ reporter,
19
22
  };
20
23
  const summary = (0, export_1.runExport)(opts);
21
24
  log(picocolors_1.default.dim(`Exported ${summary.kind} "${name}"`));
@@ -138,6 +138,14 @@ async function runInit(opts = {}) {
138
138
  (0, paths_1.noteWindowsCaveat)((m) => console.log(picocolors_1.default.dim(`ℹ ${m}`)));
139
139
  }
140
140
  const cwd = opts.cwd ?? process.cwd();
141
+ const report = opts.reporter ?? (0, bundles_1.createBundleDiagnosticReporter)((message) => process.stderr.write(`${message}\n`));
142
+ const reportedDiagnostics = new Set();
143
+ const reporter = (diagnostic) => {
144
+ if (reportedDiagnostics.has(diagnostic))
145
+ return;
146
+ reportedDiagnostics.add(diagnostic);
147
+ report(diagnostic);
148
+ };
141
149
  const agent = opts.agent === undefined ? 'claude-code' : (0, providers_1.requireAgentTarget)(opts.agent);
142
150
  // R2: gate BEFORE anything is read or written — an unsupported provider
143
151
  // version must never touch preferences.json or any provider file.
@@ -190,7 +198,7 @@ async function runInit(opts = {}) {
190
198
  // that exact run won't roll those specific artifacts back — every
191
199
  // other target (preferences, hook, injection, previously-installed
192
200
  // bundle content) is covered.
193
- const preSyncBundles = (0, bundles_1.discoverAllBundles)();
201
+ const preSyncBundles = (0, bundles_1.discoverAllBundles)(undefined, reporter);
194
202
  const mutationTargets = (0, mutation_targets_1.planInitMutationTargets)({
195
203
  cwd,
196
204
  agent,
@@ -207,7 +215,7 @@ async function runInit(opts = {}) {
207
215
  // step's failure instead of being reported as its own cause.
208
216
  (0, registries_1.assertSyncedRegistriesUsable)((await mergedActions.syncCache()) ?? []);
209
217
  }
210
- const bundles = (0, bundles_1.discoverAllBundles)();
218
+ const bundles = (0, bundles_1.discoverAllBundles)(undefined, reporter);
211
219
  const ctx = (0, context_1.gatherContext)({ cwd, bundles, agent });
212
220
  // In machineOnly mode, null out the project context so project steps are skipped
213
221
  const effectiveCtx = opts.machineOnly
@@ -26,7 +26,7 @@ function deriveRegistryName(remote) {
26
26
  const base = remote.replace(/[/\\]+$/, '').split(/[/\\:]/).pop() ?? '';
27
27
  return base.replace(/\.git$/, '');
28
28
  }
29
- async function addRegistry(remote, nameOverride) {
29
+ async function addRegistry(remote, nameOverride, reporter) {
30
30
  const name = nameOverride ?? deriveRegistryName(remote);
31
31
  if (!name || name === '.' || name.includes('..') || /[/\\]/.test(name)) {
32
32
  return { ok: false, error: `Invalid registry name "${name}" — use --name <simple-dir-name>` };
@@ -61,7 +61,7 @@ async function addRegistry(remote, nameOverride) {
61
61
  (0, discovery_1.discoverSkills)(roots);
62
62
  (0, discovery_1.discoverWorkflows)(roots);
63
63
  (0, discovery_1.discoverAgents)(roots);
64
- (0, bundles_1.discoverAllBundles)(roots);
64
+ (0, bundles_1.discoverAllBundles)(roots, reporter);
65
65
  }
66
66
  catch (e) {
67
67
  fs_1.default.rmSync(dest, { recursive: true, force: true });
@@ -28,10 +28,11 @@ function registerRegistryCommand(program) {
28
28
  .option('--install-all', 'install every bundle from the new registry for the default agent')
29
29
  .option('--no-install', 'skip the bundle install offer')
30
30
  .action(async (remote, options) => {
31
+ const reporter = (0, bundles_1.createBundleDiagnosticReporter)((message) => process.stderr.write(`${message}\n`));
31
32
  (0, prompts_1.intro)(picocolors_1.default.bgCyan(picocolors_1.default.black(' AWM - Add Registry ')));
32
33
  const s = (0, prompts_1.spinner)();
33
34
  s.start('Cloning and validating registry...');
34
- const result = await (0, add_1.addRegistry)(remote, options.name);
35
+ const result = await (0, add_1.addRegistry)(remote, options.name, reporter);
35
36
  if (!result.ok) {
36
37
  s.stop('Failed.');
37
38
  console.error(picocolors_1.default.red(result.error));
@@ -49,7 +50,7 @@ function registerRegistryCommand(program) {
49
50
  }
50
51
  // Bundle install offer — failure NEVER reverts the add.
51
52
  try {
52
- const available = (0, install_bundles_1.bundlesInRegistry)(result.contentRoot);
53
+ const available = (0, install_bundles_1.bundlesInRegistry)(result.contentRoot, reporter);
53
54
  if (available.length > 0) {
54
55
  const prefs = (0, config_1.getPreferences)();
55
56
  const projectRoot = (0, profile_1.findProjectRoot)(process.cwd()) ?? process.cwd();
@@ -82,7 +83,7 @@ function registerRegistryCommand(program) {
82
83
  }
83
84
  }
84
85
  if (selection) {
85
- for (const r of (0, install_bundles_1.installBundlesFromRegistry)(result.contentRoot, selection, agents, projectRoot)) {
86
+ for (const r of (0, install_bundles_1.installBundlesFromRegistry)(result.contentRoot, selection, agents, projectRoot, reporter)) {
86
87
  for (const line of r.installed)
87
88
  console.log(picocolors_1.default.green(` ✓ ${line}`));
88
89
  for (const sk of r.skipped)
@@ -103,6 +104,7 @@ function registerRegistryCommand(program) {
103
104
  reg.command('list')
104
105
  .description('list configured additional registries')
105
106
  .action(() => {
107
+ const reporter = (0, bundles_1.createBundleDiagnosticReporter)((message) => process.stderr.write(`${message}\n`));
106
108
  const regs = (0, registries_1.listRegistries)();
107
109
  if (regs.length === 0) {
108
110
  console.log(picocolors_1.default.dim('No additional registries. Add one with `awm registry add <git-url>`.'));
@@ -125,12 +127,12 @@ function registerRegistryCommand(program) {
125
127
  try {
126
128
  const counts = [
127
129
  `${(0, discovery_1.discoverSkills)([r.contentRoot]).length} skills`,
128
- `${(0, bundles_1.discoverAllBundles)([r.contentRoot]).length} bundles`,
130
+ `${(0, bundles_1.discoverAllBundles)([r.contentRoot], reporter).length} bundles`,
129
131
  `${(0, discovery_1.discoverWorkflows)([r.contentRoot]).length} workflows`,
130
132
  `${(0, discovery_1.discoverAgents)([r.contentRoot]).length} agents`,
131
133
  ].join(', ');
132
134
  console.log(`${picocolors_1.default.cyan(r.name)} ${r.remote} ${picocolors_1.default.dim(counts)}`);
133
- for (const o of (0, status_1.overrideStatus)(r.contentRoot, earlier)) {
135
+ for (const o of (0, status_1.overrideStatus)(r.contentRoot, earlier, reporter)) {
134
136
  console.log(o.active
135
137
  ? picocolors_1.default.yellow(` ↑ override active: ${o.name}`)
136
138
  : picocolors_1.default.dim(` ∅ override with no effect: ${o.name}`));
@@ -9,16 +9,16 @@ const bundles_1 = require("../../core/bundles");
9
9
  const bundle_install_1 = require("../../core/bundle-install");
10
10
  /** Bundles disponibles en un content root concreto (candidatos a instalar tras el add).
11
11
  * Uses single-root discovery to avoid surfacing cross-registry collision errors here. */
12
- function bundlesInRegistry(contentRoot) {
13
- return (0, bundles_1.discoverBundles)(contentRoot).map((b) => b.name);
12
+ function bundlesInRegistry(contentRoot, reporter) {
13
+ return (0, bundles_1.discoverBundles)(contentRoot, reporter).map((b) => b.name);
14
14
  }
15
15
  /**
16
16
  * Instala bundles del registry `contentRoot` para los agentes dados.
17
17
  * `selection` = 'all' instala todos los del registry; una lista instala solo esos.
18
18
  * Las dependencias se resuelven contra TODOS los roots (pueden vivir en el base).
19
19
  */
20
- function installBundlesFromRegistry(contentRoot, selection, agents, projectRoot) {
21
- const allBundles = (0, bundles_1.discoverAllBundles)();
20
+ function installBundlesFromRegistry(contentRoot, selection, agents, projectRoot, reporter) {
21
+ const allBundles = (0, bundles_1.discoverAllBundles)(undefined, reporter);
22
22
  const candidates = allBundles.filter((b) => b.contentRoot === contentRoot);
23
23
  const wanted = selection === 'all' ? candidates : candidates.filter((b) => selection.includes(b.name));
24
24
  const results = [];
@@ -13,7 +13,7 @@ const bundles_1 = require("../../core/bundles");
13
13
  function canDiscoverRegistryContent(contentRoot, safeRoots) {
14
14
  return safeRoots.has(contentRoot);
15
15
  }
16
- function artifactNamesInRoot(root) {
16
+ function artifactNamesInRoot(root, reporter) {
17
17
  const names = new Set();
18
18
  for (const s of (0, discovery_1.discoverSkills)([root]))
19
19
  names.add(s.name);
@@ -21,17 +21,17 @@ function artifactNamesInRoot(root) {
21
21
  names.add(w.name);
22
22
  for (const a of (0, discovery_1.discoverAgents)([root]))
23
23
  names.add(a.name);
24
- for (const b of (0, bundles_1.discoverAllBundles)([root]))
24
+ for (const b of (0, bundles_1.discoverAllBundles)([root], reporter))
25
25
  names.add(b.name);
26
26
  return names;
27
27
  }
28
- function overrideStatus(contentRoot, earlierRoots) {
28
+ function overrideStatus(contentRoot, earlierRoots, reporter) {
29
29
  const declared = Array.from((0, registries_1.readRegistryManifest)(contentRoot).overrides);
30
30
  if (declared.length === 0)
31
31
  return [];
32
32
  const earlier = new Set();
33
33
  for (const root of earlierRoots) {
34
- for (const n of artifactNamesInRoot(root))
34
+ for (const n of artifactNamesInRoot(root, reporter))
35
35
  earlier.add(n);
36
36
  }
37
37
  return declared.map((name) => ({ name, active: earlier.has(name) }));
@@ -51,6 +51,7 @@ async function runSyncCore(options, deps = {}) {
51
51
  // emission point for this command (mirrors `runInit`/`runUpdateCore`).
52
52
  (0, paths_1.noteWindowsCaveat)((m) => console.log(picocolors_1.default.dim(`ℹ ${m}`)));
53
53
  const d = { ...defaultDeps, ...deps };
54
+ const reporter = options.reporter ?? (0, bundles_1.createBundleDiagnosticReporter)((message) => process.stderr.write(`${message}\n`));
54
55
  const cwd = options.cwd ?? process.cwd();
55
56
  const projectRoot = (0, profile_1.findProjectRoot)(cwd);
56
57
  if (!projectRoot) {
@@ -130,7 +131,7 @@ async function runSyncCore(options, deps = {}) {
130
131
  const method = options.method === 'copy' ? 'copy' : 'symlink';
131
132
  let result;
132
133
  try {
133
- result = d.syncProfile({ projectRoot, bundles: (0, bundles_1.discoverAllBundles)(), agents: selectedAgents, method });
134
+ result = d.syncProfile({ projectRoot, bundles: (0, bundles_1.discoverAllBundles)(undefined, reporter), agents: selectedAgents, method });
134
135
  }
135
136
  catch (e) {
136
137
  console.error(picocolors_1.default.red(e.message));
@@ -29,6 +29,7 @@ const registries_1 = require("../core/registries");
29
29
  const paths_2 = require("../core/paths");
30
30
  const regenerate_1 = require("../core/context/regenerate");
31
31
  const reconciliation_1 = require("../core/reconciliation");
32
+ const bundles_1 = require("../core/bundles");
32
33
  const install_transaction_1 = require("../core/install-transaction");
33
34
  const resync_1 = require("../commands/hooks/resync");
34
35
  const defaultDeps = {
@@ -79,6 +80,7 @@ async function runUpdateCore(options = {}, deps = {}) {
79
80
  // single emission point for this command (mirrors `runInit`/`runSyncCore`).
80
81
  (0, paths_1.noteWindowsCaveat)((m) => console.log(picocolors_1.default.dim(`ℹ ${m}`)));
81
82
  const d = { ...defaultDeps, ...deps };
83
+ const reporter = options.reporter ?? (0, bundles_1.createBundleDiagnosticReporter)((message) => process.stderr.write(`${message}\n`));
82
84
  const prefs = (0, config_1.getPreferences)();
83
85
  const resolved = (0, agent_targets_1.resolveAgentTargetsOrError)({ prefs, explicit: options.agent });
84
86
  if (!resolved.ok) {
@@ -154,7 +156,7 @@ async function runUpdateCore(options = {}, deps = {}) {
154
156
  console.log(picocolors_1.default.green(` ✓ Regenerated AWM context for: ${refreshed.join(', ')}`));
155
157
  let artifactResult;
156
158
  try {
157
- const artifactPlan = d.planReconciliation({ targets: selectedAgents, roots: (0, registries_1.contentRoots)() });
159
+ const artifactPlan = d.planReconciliation({ targets: selectedAgents, roots: (0, registries_1.contentRoots)(), reporter });
158
160
  artifactResult = d.applyInstallPlan(artifactPlan);
159
161
  }
160
162
  catch (e) {
@@ -28,8 +28,8 @@ function bundleArtifacts(b, contentDir) {
28
28
  // estructural sobre la ruta ya resuelta.
29
29
  const refs = [];
30
30
  for (const s of b.skills) {
31
- (0, artifact_name_1.assertSafeArtifactName)(s.name, 'skill');
32
- refs.push({ name: s.name, type: 'skill', installName: s.name, sourcePath: path_1.default.join(contentDir, 'skills', s.name) });
31
+ (0, artifact_name_1.assertSafeArtifactName)(s, 'skill');
32
+ refs.push({ name: s, type: 'skill', installName: s, sourcePath: path_1.default.join(contentDir, 'skills', s) });
33
33
  }
34
34
  for (const w of b.workflows) {
35
35
  (0, artifact_name_1.assertSafeArtifactName)(w, 'workflow');
@@ -4,15 +4,20 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.readCatalog = readCatalog;
7
+ exports.inspectBundles = inspectBundles;
8
+ exports.createBundleDiagnosticReporter = createBundleDiagnosticReporter;
7
9
  exports.discoverBundles = discoverBundles;
8
10
  exports.resolveBundleSkills = resolveBundleSkills;
9
11
  exports.resolveBundleAgents = resolveBundleAgents;
10
12
  exports.defaultScopeForBundle = defaultScopeForBundle;
13
+ exports.inspectAllBundles = inspectAllBundles;
11
14
  exports.discoverAllBundles = discoverAllBundles;
12
15
  exports.resolveBundleClosure = resolveBundleClosure;
13
16
  const fs_1 = __importDefault(require("fs"));
17
+ const crypto_1 = __importDefault(require("crypto"));
14
18
  const path_1 = __importDefault(require("path"));
15
19
  const registries_1 = require("./registries");
20
+ const text_1 = require("./text");
16
21
  function catalogPath(contentDir) {
17
22
  return path_1.default.join(contentDir, 'catalog.json');
18
23
  }
@@ -85,29 +90,63 @@ function readCatalog(contentDir) {
85
90
  // TypeError opaco mas adelante; se descarta aca, nombrando el archivo.
86
91
  return bundles.filter((entry) => entry !== null && typeof entry === 'object' && typeof entry.source === 'string');
87
92
  }
88
- /** Tolera `skills` ausente o mal formado: lo que no sea un array se trata como
89
- * vacio en vez de tirar `(raw ?? []).map is not a function`, y cada entrada se
90
- * valida individualmente. */
91
- function normalizeSkillRefs(raw) {
92
- if (!Array.isArray(raw))
93
- return [];
94
- return raw.flatMap((s) => {
95
- if (typeof s === 'string')
96
- return [{ name: s, onSignal: false }];
97
- if (s !== null && typeof s === 'object' && typeof s.name === 'string') {
98
- return [{ name: s.name, onSignal: s.onSignal === true }];
93
+ function safeManifestIdentity(manifestPath) {
94
+ const sanitized = (0, text_1.sanitizeDiagnosticText)(manifestPath);
95
+ if (sanitized.length <= 240)
96
+ return sanitized;
97
+ const suffix = crypto_1.default.createHash('sha256').update(sanitized).digest('hex').slice(0, 16);
98
+ return `${sanitized.slice(0, 220)}…#${suffix}`;
99
+ }
100
+ function boundedDiagnostic(diagnostic) {
101
+ return (0, text_1.sanitizeDiagnosticText)(diagnostic).slice(0, 512);
102
+ }
103
+ /** Normaliza la única forma de compatibilidad CLI 9 antes de que el manifest
104
+ * alcance consumidores operativos. Los objetos nunca salen de este límite. */
105
+ function normalizeSkillRefs(raw, manifestPath) {
106
+ const identity = safeManifestIdentity(manifestPath);
107
+ if (!Array.isArray(raw)) {
108
+ throw new Error(`${identity}: "skills" must be an array.`);
109
+ }
110
+ const skills = [];
111
+ let legacyCount = 0;
112
+ raw.forEach((ref, index) => {
113
+ const location = `${identity}: skills[${index}]`;
114
+ if (typeof ref === 'string') {
115
+ if (ref === '')
116
+ throw new Error(`${location} must be a non-empty string.`);
117
+ skills.push(ref);
118
+ return;
99
119
  }
100
- return [];
120
+ if (ref === null || typeof ref !== 'object' || Array.isArray(ref)) {
121
+ throw new Error(`${location} must be a non-empty string or a supported legacy object.`);
122
+ }
123
+ const legacy = ref;
124
+ const keys = Object.keys(legacy);
125
+ if (!keys.every((key) => key === 'name' || key === 'onSignal')) {
126
+ throw new Error(`${location} legacy object may contain only "name" and optional "onSignal".`);
127
+ }
128
+ if (typeof legacy.name !== 'string' || legacy.name === '') {
129
+ throw new Error(`${location} legacy object "name" must be a non-empty string.`);
130
+ }
131
+ if (legacy.onSignal !== undefined && typeof legacy.onSignal !== 'boolean') {
132
+ throw new Error(`${location} legacy object "onSignal" must be a boolean.`);
133
+ }
134
+ skills.push(legacy.name);
135
+ legacyCount++;
101
136
  });
137
+ const diagnostics = legacyCount === 0 ? [] : [boundedDiagnostic(`${identity}: ${legacyCount} legacy object skill reference(s) accepted for CLI 9; ` +
138
+ 'replace with canonical skills: ["skill-name"]. Legacy object support is removed in v10.')];
139
+ return { skills, diagnostics };
102
140
  }
103
141
  /** Array de strings, o vacio. Cualquier otra forma en el contenido del registry
104
142
  * se ignora en vez de propagarse hasta un crash aguas abajo. */
105
143
  function stringArray(raw) {
106
144
  return Array.isArray(raw) ? raw.filter((v) => typeof v === 'string') : [];
107
145
  }
108
- function discoverBundles(contentDir) {
146
+ function inspectBundles(contentDir) {
109
147
  const entries = readCatalog(contentDir);
110
148
  const bundles = [];
149
+ const diagnostics = [];
111
150
  for (const entry of entries) {
112
151
  const manifestPath = bundleManifestPath(contentDir, entry.source);
113
152
  if (!(0, registries_1.assertRegularRegistryFile)(manifestPath))
@@ -128,6 +167,8 @@ function discoverBundles(contentDir) {
128
167
  if (typeof m.name !== 'string' || m.name === '') {
129
168
  throw new Error(`${manifestPath}: "name" must be a non-empty string.`);
130
169
  }
170
+ const normalizedSkills = normalizeSkillRefs(m.skills, manifestPath);
171
+ diagnostics.push(...normalizedSkills.diagnostics);
131
172
  bundles.push({
132
173
  name: m.name,
133
174
  description: typeof m.description === 'string' ? m.description : '',
@@ -135,13 +176,36 @@ function discoverBundles(contentDir) {
135
176
  scope: m.scope ?? 'project',
136
177
  visibility: m.visibility ?? 'public',
137
178
  dependsOn: stringArray(m.dependsOn),
138
- skills: normalizeSkillRefs(m.skills),
179
+ skills: normalizedSkills.skills,
139
180
  workflows: stringArray(m.workflows),
140
181
  agents: stringArray(m.agents),
141
182
  contentRoot: contentDir,
142
183
  });
143
184
  }
144
- return bundles;
185
+ return { bundles, diagnostics };
186
+ }
187
+ /** Creates the command-scoped CLI reporter used by discovery facades. */
188
+ function createBundleDiagnosticReporter(write = (message) => console.error(message)) {
189
+ const emitted = new Set();
190
+ return (diagnostic) => {
191
+ const safeDiagnostic = boundedDiagnostic(diagnostic);
192
+ if (emitted.has(safeDiagnostic))
193
+ return;
194
+ emitted.add(safeDiagnostic);
195
+ write(`warning: ${safeDiagnostic}`);
196
+ };
197
+ }
198
+ const defaultBundleDiagnosticReporter = createBundleDiagnosticReporter();
199
+ function reportDiagnostics(diagnostics, reporter) {
200
+ const emit = reporter ?? defaultBundleDiagnosticReporter;
201
+ for (const diagnostic of new Set(diagnostics))
202
+ emit(diagnostic);
203
+ }
204
+ /** Compatibility facade for callers that only need bundle definitions. */
205
+ function discoverBundles(contentDir, reporter) {
206
+ const result = inspectBundles(contentDir);
207
+ reportDiagnostics(result.diagnostics, reporter);
208
+ return result.bundles;
145
209
  }
146
210
  function resolveBundleSkills(bundleName, bundles) {
147
211
  const byName = new Map(bundles.map((b) => [b.name, b]));
@@ -157,7 +221,7 @@ function resolveBundleSkills(bundleName, bundles) {
157
221
  for (const dep of b.dependsOn)
158
222
  visit(dep);
159
223
  for (const s of b.skills)
160
- skills.add(s.name);
224
+ skills.add(s);
161
225
  };
162
226
  visit(bundleName);
163
227
  return Array.from(skills);
@@ -192,11 +256,14 @@ function defaultScopeForBundle(scope) {
192
256
  /** Descubre bundles de TODOS los roots (base + registries adicionales).
193
257
  * Colisión de nombre entre roots: override declarado en awm-registry.json
194
258
  * del root posterior → reemplaza; no declarado → error nombrando ambas fuentes. */
195
- function discoverAllBundles(roots = (0, registries_1.contentRoots)()) {
259
+ function inspectAllBundles(roots = (0, registries_1.contentRoots)()) {
196
260
  const byName = new Map();
261
+ const diagnostics = [];
197
262
  for (const root of roots) {
198
263
  const overrides = (0, registries_1.readRegistryManifest)(root).overrides;
199
- for (const b of discoverBundles(root)) {
264
+ const result = inspectBundles(root);
265
+ diagnostics.push(...result.diagnostics);
266
+ for (const b of result.bundles) {
200
267
  const prev = byName.get(b.name);
201
268
  if (!prev) {
202
269
  byName.set(b.name, b);
@@ -210,7 +277,13 @@ function discoverAllBundles(roots = (0, registries_1.contentRoots)()) {
210
277
  `Remove or rename one of them, or declare "${b.name}" in "overrides" of the later registry's awm-registry.json.`);
211
278
  }
212
279
  }
213
- return Array.from(byName.values());
280
+ return { bundles: Array.from(byName.values()), diagnostics: Array.from(new Set(diagnostics)) };
281
+ }
282
+ /** Compatibility facade for callers that only need bundle definitions. */
283
+ function discoverAllBundles(roots = (0, registries_1.contentRoots)(), reporter) {
284
+ const result = inspectAllBundles(roots);
285
+ reportDiagnostics(result.diagnostics, reporter);
286
+ return result.bundles;
214
287
  }
215
288
  /**
216
289
  * Resolves the dependency closure of a bundle in deps-first order, deduped.
@@ -307,7 +307,7 @@ function gatherProject(root, bundles, agent = 'claude-code') {
307
307
  }
308
308
  function gatherContext(opts = {}) {
309
309
  const cwd = opts.cwd ?? process.cwd();
310
- const bundles = opts.bundles ?? (0, bundles_1.discoverAllBundles)();
310
+ const bundles = opts.bundles ?? (0, bundles_1.discoverAllBundles)(undefined, opts.reporter);
311
311
  const agent = opts.agent ?? 'claude-code';
312
312
  const root = (0, profile_1.findProjectRoot)(cwd);
313
313
  const agents = opts.agents ?? [agent];
@@ -23,7 +23,7 @@ function runExport(opts) {
23
23
  }
24
24
  const roots = opts.roots ?? (0, registries_1.contentRoots)();
25
25
  const outDir = path_1.default.join(opts.out ?? path_1.default.join(process.cwd(), 'awm-export'), target);
26
- const resolution = (0, resolve_1.resolveExport)(opts.name, roots);
26
+ const resolution = (0, resolve_1.resolveExport)(opts.name, roots, opts.reporter);
27
27
  fs_1.default.mkdirSync(outDir, { recursive: true });
28
28
  const exported = [];
29
29
  let zipAvailable = true;
@@ -44,8 +44,8 @@ function assertOverrideConsistency(s) {
44
44
  throw new Error(`Inconsistent metadata for skill "${s.name}": ${OVERRIDE_FILE} exists but SKILL.md does not declare portable: true.`);
45
45
  }
46
46
  }
47
- function resolveExport(requested, roots) {
48
- const bundles = (0, bundles_1.discoverAllBundles)(roots);
47
+ function resolveExport(requested, roots, reporter) {
48
+ const bundles = (0, bundles_1.discoverAllBundles)(roots, reporter);
49
49
  if (bundles.some((b) => b.name === requested)) {
50
50
  const skills = [];
51
51
  const skipped = [];
@@ -16,7 +16,7 @@ function intentKey(intent) {
16
16
  */
17
17
  function planReconciliation(params) {
18
18
  const { targets, roots } = params;
19
- const bundles = params.bundles ?? (0, bundles_1.discoverAllBundles)(roots);
19
+ const bundles = params.bundles ?? (0, bundles_1.discoverAllBundles)(roots, params.reporter);
20
20
  const contentDir = roots[0] ?? '';
21
21
  const machineBundles = bundles.filter((b) => b.scope === 'baseline' || b.scope === 'ambient');
22
22
  const seen = new Set();