@1aboveio/skills 0.20.3 → 0.20.4

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/README.md CHANGED
@@ -57,8 +57,9 @@ Running `install` with no `--group` opens the interactive bundle picker; `update
57
57
  - `--disable-tdd` — keep TDD installed but allow only explicit user invocation.
58
58
 
59
59
  Selecting several groups (interactive picker, repeated `--group`, or `--all`) installs their
60
- members as one native `skills add` per skill from the unpacked package. A failure is reported by
61
- name, blocks only transitive dependents, and never aborts unrelated skills. Every native add uses `--yes` because
60
+ members in bulk: adjacent members from one source share one native `skills add` from the unpacked
61
+ package. Every member is digest-verified afterward, so a failure remains attributed by name,
62
+ blocks only transitive dependents, and never aborts unrelated source batches. Every native add uses `--yes` because
62
63
  `skills@1.5.22` has no independent `--symlink` flag: this selects the default canonical store plus
63
64
  per-harness symlinks without an installation-method prompt, and also confirms replacement of each
64
65
  planned same-named skill. The coordinator still owns group and new-member consent before planning.
@@ -104,11 +105,11 @@ copy. The default is No; `--yes` accepts archival non-interactively. Removal is
104
105
  same pinned native Skills CLI, only after the declared replacement is installed or already current.
105
106
  Declined or failed archival remains in selection memory so a later update offers it again.
106
107
 
107
- Install and update are best-effort per skill: they continue after a single skill failure except for
108
- skills whose required dependency is unavailable, summarize
108
+ Install and update are best-effort per member over bulk source commands: they continue after a
109
+ member failure except for skills whose required dependency is unavailable, summarize
109
110
  every skill as succeeded, unchanged, declined, or failed, and exit non-zero if anything failed. There
110
111
  is no rollback or transactional group update; the native CLI remains the sole filesystem write
111
- authority. Selection memory follows the same per-skill rule: it is rewritten when
112
+ authority. Selection memory follows the same per-member rule: it is rewritten when
112
113
  at least one user-facing member succeeded or was already unchanged, holding exactly those members —
113
114
  failed and declined members are omitted — and a run with no user-facing success leaves the prior
114
115
  file untouched.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@1aboveio/skills",
3
- "version": "0.20.3",
3
+ "version": "0.20.4",
4
4
  "description": "Install the 1AboveIO first-party skill groups through the native Skills CLI.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -24,7 +24,7 @@
24
24
  "id": "first-party",
25
25
  "type": "first-party",
26
26
  "package": "@1aboveio/skills",
27
- "version": "0.20.3",
27
+ "version": "0.20.4",
28
28
  "updatePolicy": "pinned-npm-version",
29
29
  "members": [
30
30
  {
@@ -263,7 +263,7 @@
263
263
  "sourceId": "first-party",
264
264
  "sourceType": "first-party",
265
265
  "package": "@1aboveio/skills",
266
- "version": "0.20.3",
266
+ "version": "0.20.4",
267
267
  "installPath": null,
268
268
  "members": [
269
269
  "harness-runtime",
@@ -297,7 +297,7 @@
297
297
  "sourceId": "first-party",
298
298
  "sourceType": "first-party",
299
299
  "package": "@1aboveio/skills",
300
- "version": "0.20.3",
300
+ "version": "0.20.4",
301
301
  "installPath": null,
302
302
  "members": [
303
303
  "harness-runtime",
@@ -341,7 +341,7 @@
341
341
  "sourceId": "first-party",
342
342
  "sourceType": "first-party",
343
343
  "package": "@1aboveio/skills",
344
- "version": "0.20.3",
344
+ "version": "0.20.4",
345
345
  "installPath": null,
346
346
  "members": [
347
347
  "harness-runtime",
@@ -374,7 +374,7 @@
374
374
  "sourceId": "first-party",
375
375
  "sourceType": "first-party",
376
376
  "package": "@1aboveio/skills",
377
- "version": "0.20.3",
377
+ "version": "0.20.4",
378
378
  "installPath": null,
379
379
  "members": [
380
380
  "harness-runtime",
@@ -417,7 +417,7 @@
417
417
  "sourceId": "first-party",
418
418
  "sourceType": "first-party",
419
419
  "package": "@1aboveio/skills",
420
- "version": "0.20.3",
420
+ "version": "0.20.4",
421
421
  "installPath": null,
422
422
  "members": [
423
423
  "harness-runtime",
@@ -445,7 +445,7 @@
445
445
  "sourceId": "first-party",
446
446
  "sourceType": "first-party",
447
447
  "package": "@1aboveio/skills",
448
- "version": "0.20.3",
448
+ "version": "0.20.4",
449
449
  "installPath": null,
450
450
  "members": [
451
451
  "harness-runtime",
@@ -483,7 +483,7 @@
483
483
  "sourceId": "first-party",
484
484
  "sourceType": "first-party",
485
485
  "package": "@1aboveio/skills",
486
- "version": "0.20.3",
486
+ "version": "0.20.4",
487
487
  "installPath": null,
488
488
  "members": [
489
489
  "harness-runtime",
@@ -511,7 +511,7 @@
511
511
  "sourceId": "first-party",
512
512
  "sourceType": "first-party",
513
513
  "package": "@1aboveio/skills",
514
- "version": "0.20.3",
514
+ "version": "0.20.4",
515
515
  "installPath": null,
516
516
  "members": [
517
517
  "harness-runtime",
@@ -549,7 +549,7 @@
549
549
  "sourceId": "first-party",
550
550
  "sourceType": "first-party",
551
551
  "package": "@1aboveio/skills",
552
- "version": "0.20.3",
552
+ "version": "0.20.4",
553
553
  "installPath": null,
554
554
  "members": [
555
555
  "harness-runtime",
@@ -579,7 +579,7 @@
579
579
  "sourceId": "first-party",
580
580
  "sourceType": "first-party",
581
581
  "package": "@1aboveio/skills",
582
- "version": "0.20.3",
582
+ "version": "0.20.4",
583
583
  "installPath": null,
584
584
  "members": [
585
585
  "harness-runtime",
@@ -619,7 +619,7 @@
619
619
  "sourceId": "first-party",
620
620
  "sourceType": "first-party",
621
621
  "package": "@1aboveio/skills",
622
- "version": "0.20.3",
622
+ "version": "0.20.4",
623
623
  "installPath": null,
624
624
  "members": [
625
625
  "harness-runtime",
@@ -647,7 +647,7 @@
647
647
  "sourceId": "first-party",
648
648
  "sourceType": "first-party",
649
649
  "package": "@1aboveio/skills",
650
- "version": "0.20.3",
650
+ "version": "0.20.4",
651
651
  "installPath": null,
652
652
  "members": [
653
653
  "harness-runtime",
@@ -685,7 +685,7 @@
685
685
  "sourceId": "first-party",
686
686
  "sourceType": "first-party",
687
687
  "package": "@1aboveio/skills",
688
- "version": "0.20.3",
688
+ "version": "0.20.4",
689
689
  "installPath": null,
690
690
  "members": [
691
691
  "harness-runtime",
@@ -715,7 +715,7 @@
715
715
  "sourceId": "first-party",
716
716
  "sourceType": "first-party",
717
717
  "package": "@1aboveio/skills",
718
- "version": "0.20.3",
718
+ "version": "0.20.4",
719
719
  "installPath": null,
720
720
  "members": [
721
721
  "harness-runtime",
@@ -755,7 +755,7 @@
755
755
  "sourceId": "first-party",
756
756
  "sourceType": "first-party",
757
757
  "package": "@1aboveio/skills",
758
- "version": "0.20.3",
758
+ "version": "0.20.4",
759
759
  "installPath": null,
760
760
  "members": [
761
761
  "harness-runtime",
@@ -787,7 +787,7 @@
787
787
  "sourceId": "first-party",
788
788
  "sourceType": "first-party",
789
789
  "package": "@1aboveio/skills",
790
- "version": "0.20.3",
790
+ "version": "0.20.4",
791
791
  "installPath": null,
792
792
  "members": [
793
793
  "harness-runtime",
@@ -827,7 +827,7 @@
827
827
  "sourceId": "first-party",
828
828
  "sourceType": "first-party",
829
829
  "package": "@1aboveio/skills",
830
- "version": "0.20.3",
830
+ "version": "0.20.4",
831
831
  "installPath": null,
832
832
  "members": [
833
833
  "harness-runtime"
@@ -853,7 +853,7 @@
853
853
  "sourceId": "first-party",
854
854
  "sourceType": "first-party",
855
855
  "package": "@1aboveio/skills",
856
- "version": "0.20.3",
856
+ "version": "0.20.4",
857
857
  "installPath": null,
858
858
  "members": [
859
859
  "harness-runtime"
@@ -885,7 +885,7 @@
885
885
  "sourceId": "first-party",
886
886
  "sourceType": "first-party",
887
887
  "package": "@1aboveio/skills",
888
- "version": "0.20.3",
888
+ "version": "0.20.4",
889
889
  "installPath": null,
890
890
  "members": [
891
891
  "harness-runtime",
@@ -945,7 +945,7 @@
945
945
  "sourceId": "first-party",
946
946
  "sourceType": "first-party",
947
947
  "package": "@1aboveio/skills",
948
- "version": "0.20.3",
948
+ "version": "0.20.4",
949
949
  "installPath": null,
950
950
  "members": [
951
951
  "harness-runtime",
@@ -305,19 +305,14 @@ function localFirstPartyCommand(command, localRoot, groupId) {
305
305
  return command.replace(NATIVE_ADD_SOURCE, (_match, prefix) => `${prefix}${shellArgument(localRoot)}`);
306
306
  }
307
307
 
308
- // The coordinator executes one native add PER SKILL (#1129) rather than coalescing a whole source
309
- // into one invocation. A coalesced add can only attribute a failure to the entire source; per-skill
310
- // adds let the run record exactly which skill failed, continue with the remaining skills, and keep
311
- // only the succeeded skills in selection memory. Recipe step/member order is preserved
312
- // (dependency-first), and a member shared by several selected groups (harness-runtime) is still
313
- // planned exactly once, at its first occurrence. The pinned native CLI has no `--symlink` selector,
314
- // so the coordinator adds `--yes` to choose its default symlink deployment without one installation-
315
- // method prompt per skill. Attribution, ordering and deduplication remain coordinator-owned.
316
- const SKILL_TAIL = /^(.*? --skill )(.+?)( && .*)?$/;
308
+ // The coordinator preserves generated dependency/member order, then coalesces each contiguous run
309
+ // from one source into a native add. A member shared by groups is included once and retains every
310
+ // owning selection for memory. Per-member attribution comes from post-install digest verification,
311
+ // not process count.
312
+ const SKILL_TAIL = /^(.*? --skill )(.+?)( --yes)?( && .*)?$/;
317
313
 
318
- // The generated step binds its runtime suffix (` && npm ci ...`) to the whole coalesced command.
319
- // Per skill it belongs to the runtime member's own add, so the dependency install runs only after
320
- // engineering-runtime itself was actually added — never after a sibling, never when it was skipped.
314
+ // The generated step binds its runtime suffix (` && npm ci ...`) to engineering-runtime. Keep it on
315
+ // the source batch only when that member remains selected after digest and consent filtering.
321
316
  const RUNTIME_MEMBER = 'engineering-runtime';
322
317
 
323
318
  function skillsFromCommand(command) {
@@ -329,14 +324,14 @@ function skillsFromCommand(command) {
329
324
  function withSkills(command, skills) {
330
325
  const match = SKILL_TAIL.exec(command);
331
326
  if (!match) throw new Error(`Native add has no --skill list to rewrite: ${command}`);
332
- return `${match[1]}${skills.join(' ')}${match[3] || ''}`;
327
+ return `${match[1]}${skills.join(' ')}${match[3] || ''}${match[4] || ''}`;
333
328
  }
334
329
 
335
330
  function withYesFlag(command) {
336
331
  // --yes must bind to `skills`, not to a trailing `&& npm ci ...`.
337
332
  const match = SKILL_TAIL.exec(command);
338
333
  if (!match) return `${command} --yes`;
339
- return `${match[1]}${match[2]} --yes${match[3] || ''}`;
334
+ return `${match[1]}${match[2]} --yes${match[4] || ''}`;
340
335
  }
341
336
 
342
337
  export function planBundleCommands(action, groupIds, {
@@ -363,12 +358,10 @@ export function planBundleCommands(action, groupIds, {
363
358
  if (!requested.includes(groupId)) requested.push(groupId);
364
359
  }
365
360
 
366
- // Plan from the selected groups (not recipes.all). A public package may expose only a subset of
367
- // groups while still shipping a full recipes.all from the private catalog; using that all-plan
368
- // would install skills that are not in the selection / not in the package.
361
+ // Plan individual member records first. The execution boundary coalesces adjacent records from
362
+ // the same source without weakening per-member dependency, policy, or selection metadata.
369
363
  const planned = [];
370
364
  const plannedByMember = new Map();
371
-
372
365
  for (const groupId of requested) {
373
366
  const group = groups.get(groupId);
374
367
  for (const step of group[action].steps) {
@@ -379,14 +372,10 @@ export function planBundleCommands(action, groupIds, {
379
372
  const command = step.sourceId === 'first-party'
380
373
  ? localFirstPartyCommand(generated.command, localRoot, groupId)
381
374
  : generated.command;
382
- // An explicit member subset (the digest skip) removes members from the native add but never
383
- // reorders the rest: the remaining skills keep recipe order, support dependency first.
384
- // Declined new members (#1128) are also filtered out, but consent never applies to support
385
- // dependencies (harness-runtime): they are silent install dependencies, not capabilities.
386
375
  const skills = skillsFromCommand(command)
387
376
  .filter((skill) => !skipMembers?.has(skill))
388
377
  .filter((skill) => SUPPORT_GROUPS.has(skill) || !omit.has(skill));
389
- const suffix = SKILL_TAIL.exec(command)?.[3] || '';
378
+ const suffix = SKILL_TAIL.exec(command)?.[4] || '';
390
379
  const bare = suffix ? command.slice(0, command.length - suffix.length) : command;
391
380
  for (const skill of skills) {
392
381
  const existing = plannedByMember.get(skill);
@@ -396,8 +385,6 @@ export function planBundleCommands(action, groupIds, {
396
385
  }
397
386
  let skillCommand = withSkills(bare, [skill]);
398
387
  if (suffix && skill === RUNTIME_MEMBER) skillCommand = `${skillCommand}${suffix}`;
399
- // The pinned CLI has no --symlink flag. --yes is its only supported way to select the
400
- // default symlink deployment without presenting the install-method chooser.
401
388
  skillCommand = withYesFlag(skillCommand);
402
389
  const invocationPolicy = skill === 'tdd' && enableTdd
403
390
  ? undefined
@@ -415,22 +402,51 @@ export function planBundleCommands(action, groupIds, {
415
402
  ...(memberMetadata.get(skill)?.requires?.length > 0
416
403
  ? { requires: memberMetadata.get(skill).requires }
417
404
  : {}),
405
+ metadata: memberMetadata.get(skill) ?? { installName: skill },
418
406
  };
419
407
  plannedByMember.set(skill, entry);
420
408
  planned.push(entry);
421
409
  }
422
410
  }
423
411
  }
424
-
425
412
  return planned;
426
413
  }
427
414
 
428
- // Continue-on-failure (#1129): planned per-skill adds run in dependency order. A failed skill blocks
429
- // only members that require it; unrelated later skills still run. The caller summarizes the buckets,
430
- // decides the exit status, and persists only members whose required closure is ready.
431
- export function runBundleCommands(commands, {
415
+ export function coalesceBundleCommands(commands) {
416
+ const batches = [];
417
+ for (const planned of commands) {
418
+ const previous = batches.at(-1);
419
+ if (!previous || previous.sourceId !== planned.sourceId || previous.cwd !== planned.cwd) {
420
+ batches.push({
421
+ sourceId: planned.sourceId,
422
+ members: [planned],
423
+ command: planned.command,
424
+ cwd: planned.cwd,
425
+ failureMessage: planned.failureMessage,
426
+ });
427
+ continue;
428
+ }
429
+ previous.members.push(planned);
430
+ const suffix = previous.members.map(({ command }) => SKILL_TAIL.exec(command)?.[4] || '').find(Boolean) || '';
431
+ const bare = suffix && previous.command.endsWith(suffix)
432
+ ? previous.command.slice(0, previous.command.length - suffix.length)
433
+ : previous.command;
434
+ previous.command = withSkills(bare, previous.members.map(({ member }) => member));
435
+ if (suffix) previous.command = `${previous.command}${suffix}`;
436
+ }
437
+ for (const batch of batches) {
438
+ batch.groupIds = [...new Set(batch.members.flatMap((member) => member.groupIds))];
439
+ batch.groupName = [...new Set(batch.members.map((member) => member.groupName))].join(', ');
440
+ }
441
+ return batches;
442
+ }
443
+
444
+ // Source batches continue independently. Within each batch, command status is only process evidence;
445
+ // installed-content verification is the per-member authority used for attribution and memory.
446
+ export function runBundleCommands(batches, {
432
447
  execute = (command, cwd) => spawnSync('/bin/bash', ['-lc', command], { cwd, stdio: 'inherit' }),
433
448
  applyPolicy = applyInvocationPolicy,
449
+ verifyMember,
434
450
  skillsRoot,
435
451
  stdout = process.stdout,
436
452
  stderr = process.stderr,
@@ -439,35 +455,79 @@ export function runBundleCommands(commands, {
439
455
  const succeeded = [];
440
456
  const failed = [];
441
457
  const available = new Set(satisfiedMembers);
442
- for (const planned of commands) {
443
- const blockedBy = (planned.requires ?? []).filter((dependency) => !available.has(dependency));
444
- if (blockedBy.length > 0) {
445
- stderr.write(`Dependency failed for ${planned.member}: ${blockedBy.join(', ')} is not available\n`);
446
- failed.push({ planned, status: 1, blockedBy });
447
- continue;
458
+ for (const candidate of batches) {
459
+ const legacySingle = !Array.isArray(candidate.members);
460
+ const batch = legacySingle ? { ...candidate, members: [candidate] } : candidate;
461
+ let runnable = [...batch.members];
462
+ let changed = true;
463
+ while (changed) {
464
+ changed = false;
465
+ const runnableNames = new Set(runnable.map(({ member }) => member));
466
+ for (const planned of [...runnable]) {
467
+ const blockedBy = (planned.requires ?? [])
468
+ .filter((dependency) => !available.has(dependency) && !runnableNames.has(dependency));
469
+ if (blockedBy.length === 0) continue;
470
+ stderr.write(`Dependency failed for ${planned.member}: ${blockedBy.join(', ')} is not available\n`);
471
+ failed.push({ planned, status: 1, blockedBy });
472
+ runnable = runnable.filter((candidate) => candidate !== planned);
473
+ changed = true;
474
+ }
448
475
  }
449
- stdout.write(`\nInstalling ${planned.member} (${planned.sourceId})...\n`);
450
- const result = execute(planned.command, planned.cwd);
451
- if (result.status === 0) {
452
- try {
453
- if (planned.invocationPolicy) {
476
+ if (runnable.length === 0) continue;
477
+ let command = batch.command;
478
+ let batchSuffix = '';
479
+ if (!legacySingle) {
480
+ batchSuffix = SKILL_TAIL.exec(batch.command)?.[4] || '';
481
+ command = withSkills(batch.command, runnable.map(({ member }) => member));
482
+ if (batchSuffix && !runnable.some(({ member }) => member === RUNTIME_MEMBER)) {
483
+ command = command.slice(0, command.length - batchSuffix.length);
484
+ }
485
+ }
486
+ stdout.write(`\nInstalling ${runnable.map(({ member }) => member).join(', ')} (${batch.sourceId})...\n`);
487
+ const result = execute(command, batch.cwd);
488
+ const status = Number.isInteger(result.status) ? result.status : 1;
489
+ const policyFailures = new Set();
490
+ if (status === 0) {
491
+ for (const planned of runnable) {
492
+ if (!planned.invocationPolicy) continue;
493
+ try {
454
494
  applyPolicy({
455
495
  installName: planned.member,
456
496
  policy: planned.invocationPolicy,
457
497
  ...(skillsRoot ? { skillsRoot } : {}),
458
498
  });
499
+ } catch (error) {
500
+ stderr.write(`Invocation policy failed for ${planned.member}: ${error.message}\n`);
501
+ policyFailures.add(planned.member);
459
502
  }
460
- succeeded.push(planned);
461
- available.add(planned.member);
503
+ }
504
+ } else {
505
+ stderr.write(`${batch.failureMessage}\n`);
506
+ }
507
+ for (const planned of runnable) {
508
+ if (policyFailures.has(planned.member)) {
509
+ failed.push({ planned, status: 1 });
462
510
  continue;
511
+ }
512
+ if (status !== 0 && batchSuffix && planned.member === RUNTIME_MEMBER) {
513
+ stderr.write(`Post-install command failed for ${planned.member}\n`);
514
+ failed.push({ planned, status });
515
+ continue;
516
+ }
517
+ try {
518
+ const verified = verifyMember ? verifyMember(planned) : status === 0;
519
+ if (verified) {
520
+ succeeded.push(planned);
521
+ available.add(planned.member);
522
+ } else {
523
+ stderr.write(`Post-install verification failed for ${planned.member}\n`);
524
+ failed.push({ planned, status: status || 1 });
525
+ }
463
526
  } catch (error) {
464
- stderr.write(`Invocation policy failed for ${planned.member}: ${error.message}\n`);
527
+ stderr.write(`Post-install verification failed for ${planned.member}: ${error.message}\n`);
465
528
  failed.push({ planned, status: 1 });
466
- continue;
467
529
  }
468
530
  }
469
- stderr.write(`${planned.failureMessage}\n`);
470
- failed.push({ planned, status: Number.isInteger(result.status) ? result.status : 1 });
471
531
  }
472
532
  return { succeeded, failed };
473
533
  }
@@ -873,6 +933,7 @@ function selectionIncludesTdd(groupIds, catalog) {
873
933
  export async function runCli(args, {
874
934
  execute,
875
935
  applyPolicy,
936
+ verifyMember,
876
937
  stdin = process.stdin,
877
938
  stdout = process.stdout,
878
939
  stderr = process.stderr,
@@ -968,10 +1029,11 @@ export async function runCli(args, {
968
1029
  skipMembers,
969
1030
  omitMembers: declined,
970
1031
  });
1032
+ const batches = coalesceBundleCommands(commands);
971
1033
  if (parsed.dryRun) {
972
1034
  if (buckets) printUpdateBuckets(stdout, buckets);
973
- for (const planned of commands) {
974
- stdout.write(`\n${planned.groupName} / ${planned.sourceId} / ${planned.member}\n${planned.command}\n`);
1035
+ for (const batch of batches) {
1036
+ stdout.write(`\n${batch.groupName} / ${batch.sourceId} / ${batch.members.map(({ member }) => member).join(', ')}\n${batch.command}\n`);
975
1037
  }
976
1038
  if (buckets && commands.length === 0 && archiveCandidates.length === 0) {
977
1039
  stdout.write('Nothing to do: every accepted skill is already up to date.\n');
@@ -984,9 +1046,21 @@ export async function runCli(args, {
984
1046
  const skipping = buckets.unchanged.map((entry) => entry.member).filter((member) => !SUPPORT_GROUPS.has(member));
985
1047
  if (skipping.length > 0) stdout.write(`Up to date, skipping: ${skipping.join(', ')}\n`);
986
1048
  }
987
- const { succeeded, failed } = runBundleCommands(commands, {
1049
+ const verificationTransport = resolveBundleTransport(transport);
1050
+ const verificationSources = new Map(readRecipes(verificationTransport.recipesPath).sources
1051
+ .map((source) => [source.id, source]));
1052
+ const verifyInstalledMember = verifyMember ?? (execute ? undefined : (planned) => {
1053
+ const source = verificationSources.get(planned.sourceId);
1054
+ return classifyInstalledMember(source, planned.metadata, {
1055
+ enableTdd,
1056
+ localRoot: verificationTransport.localRoot,
1057
+ skillsRoot,
1058
+ }).status === 'unchanged';
1059
+ });
1060
+ const { succeeded, failed } = runBundleCommands(batches, {
988
1061
  execute,
989
1062
  applyPolicy,
1063
+ verifyMember: verifyInstalledMember,
990
1064
  skillsRoot,
991
1065
  stdout,
992
1066
  stderr,
@@ -1001,13 +1075,14 @@ export async function runCli(args, {
1001
1075
  [...commands, ...(buckets?.unchanged ?? [])],
1002
1076
  stderr,
1003
1077
  ));
1078
+ const coherentSucceeded = succeeded.filter((planned) => readyMembers.has(planned.member));
1004
1079
  const archives = runArchiveCommands(
1005
1080
  archiveCandidates.filter((candidate) => !declinedArchives.has(candidate.installName)),
1006
1081
  readyMembers,
1007
1082
  { execute, skillsRoot, stdout, stderr },
1008
1083
  );
1009
1084
  printRunSummary(stdout, {
1010
- succeeded,
1085
+ succeeded: coherentSucceeded,
1011
1086
  failed,
1012
1087
  unchanged: buckets?.unchanged.map((entry) => entry.member),
1013
1088
  declined: [...declined],
@@ -86,7 +86,7 @@ export const WORKFLOW_TRUSTED_SOURCES = deepFreeze({
86
86
  id: 'first-party',
87
87
  type: 'first-party',
88
88
  package: '@1aboveio/skills',
89
- version: '0.20.3',
89
+ version: '0.20.4',
90
90
  },
91
91
  matt: {
92
92
  id: 'matt-pocock',
@@ -342,7 +342,7 @@
342
342
  "id": "first-party",
343
343
  "type": "first-party",
344
344
  "package": "@1aboveio/skills",
345
- "version": "0.20.3"
345
+ "version": "0.20.4"
346
346
  },
347
347
  "contentDigest": "eff6c7b5931bce5b2265a619bccddd371a89df6ce7bc2edc74f11d00060a71dd",
348
348
  "digestExcludes": []
@@ -355,7 +355,7 @@
355
355
  "id": "first-party",
356
356
  "type": "first-party",
357
357
  "package": "@1aboveio/skills",
358
- "version": "0.20.3"
358
+ "version": "0.20.4"
359
359
  },
360
360
  "contentDigest": "d97abeff7bdc5ae2e4be2f631d09dba46d4753ce1e67334793364329af59f066",
361
361
  "digestExcludes": []
@@ -368,7 +368,7 @@
368
368
  "id": "first-party",
369
369
  "type": "first-party",
370
370
  "package": "@1aboveio/skills",
371
- "version": "0.20.3"
371
+ "version": "0.20.4"
372
372
  },
373
373
  "contentDigest": "fe2ec3bae2bfec2dbef19db30ab0272391d7c1d9b6893341a76a9f339adefc57",
374
374
  "digestExcludes": []
@@ -381,7 +381,7 @@
381
381
  "id": "first-party",
382
382
  "type": "first-party",
383
383
  "package": "@1aboveio/skills",
384
- "version": "0.20.3"
384
+ "version": "0.20.4"
385
385
  },
386
386
  "contentDigest": "58b0556228a9271cf9e33727a05fc08a4fdfd29512ec0936b169f0a55d60e6ef",
387
387
  "digestExcludes": []
@@ -394,7 +394,7 @@
394
394
  "id": "first-party",
395
395
  "type": "first-party",
396
396
  "package": "@1aboveio/skills",
397
- "version": "0.20.3"
397
+ "version": "0.20.4"
398
398
  },
399
399
  "contentDigest": "d28dd4d3c4394df0cc7e6b48d0e5daa7d2277701ea459e39fba99acf1d8ff670",
400
400
  "digestExcludes": []
@@ -407,7 +407,7 @@
407
407
  "id": "first-party",
408
408
  "type": "first-party",
409
409
  "package": "@1aboveio/skills",
410
- "version": "0.20.3"
410
+ "version": "0.20.4"
411
411
  },
412
412
  "contentDigest": "56fb5843788c9073b0d5a65ffa72bd7d2fa80dc6a1ff06fb0e19fab513c51502",
413
413
  "digestExcludes": []
@@ -420,7 +420,7 @@
420
420
  "id": "first-party",
421
421
  "type": "first-party",
422
422
  "package": "@1aboveio/skills",
423
- "version": "0.20.3"
423
+ "version": "0.20.4"
424
424
  },
425
425
  "contentDigest": "f048fd00c69f2dc666fc7a3096cfeee6dfba73933bfb69602f3f0e26159798cc",
426
426
  "digestExcludes": []
@@ -433,7 +433,7 @@
433
433
  "id": "first-party",
434
434
  "type": "first-party",
435
435
  "package": "@1aboveio/skills",
436
- "version": "0.20.3"
436
+ "version": "0.20.4"
437
437
  },
438
438
  "contentDigest": "d533684db4483cfc90be2aa7161980b6548057ef18a4b54f0cdbcd53c79dd46d",
439
439
  "digestExcludes": []
@@ -446,15 +446,15 @@
446
446
  "id": "first-party",
447
447
  "type": "first-party",
448
448
  "package": "@1aboveio/skills",
449
- "version": "0.20.3"
449
+ "version": "0.20.4"
450
450
  },
451
- "contentDigest": "aa7b7dbeec2fd5d64883f4b6bf5ae99a3f77cad84f635cfbdf7cb1999de362e6",
451
+ "contentDigest": "86a91b567eabe6143a4ecac3cbec7271da252978202f292e287bb3a8da95a797",
452
452
  "digestExcludes": [
453
453
  "coherence/workflow.json"
454
454
  ]
455
455
  }
456
456
  ],
457
- "releaseIdentity": "4804a31efab8c7a93325ebdfeee74ce04c89130470d4eb5a878b37ac183266cd",
457
+ "releaseIdentity": "ffa735d74d402c79fbd8cbca332b415422884ee88f914412d4aa88c2d1353cb9",
458
458
  "lifecycleAuthority": "native-skills-cli",
459
459
  "repairRecipe": {
460
460
  "id": "engineering-workflow-dependency-first",
@@ -512,7 +512,7 @@
512
512
  "sourceId": "first-party",
513
513
  "sourceType": "first-party",
514
514
  "package": "@1aboveio/skills",
515
- "version": "0.20.3",
515
+ "version": "0.20.4",
516
516
  "installPath": null,
517
517
  "members": [
518
518
  "harness-runtime",
@@ -528,7 +528,7 @@
528
528
  "commands": [
529
529
  {
530
530
  "transport": "npm",
531
- "command": "npx @1aboveio/skills@0.20.3 install --group engineering-workflow --yes"
531
+ "command": "npx @1aboveio/skills@0.20.4 install --group engineering-workflow --yes"
532
532
  }
533
533
  ],
534
534
  "onFailure": {
@@ -86,7 +86,7 @@ export const WORKFLOW_TRUSTED_SOURCES = deepFreeze({
86
86
  id: 'first-party',
87
87
  type: 'first-party',
88
88
  package: '@1aboveio/skills',
89
- version: '0.20.3',
89
+ version: '0.20.4',
90
90
  },
91
91
  matt: {
92
92
  id: 'matt-pocock',