@1aboveio/skills 0.20.2 → 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.
Files changed (85) hide show
  1. package/README.md +14 -6
  2. package/package.json +1 -1
  3. package/runtime/skills/distribution/generated/recipes.json +220 -25
  4. package/runtime/skills/distribution/scripts/bundles.mjs +167 -48
  5. package/runtime/skills/engineering/engineering-runtime/scripts/workflow-coherence.mjs +4 -0
  6. package/runtime/skills/engineering/engineering-runtime/scripts/workflow-policy.mjs +1 -1
  7. package/skills/compliance/compliance-audit/LICENSE +3 -0
  8. package/skills/compliance/compliance-audit/SKILL.md +466 -0
  9. package/skills/compliance/compliance-audit/references/findings-schema.md +290 -0
  10. package/skills/compliance/compliance-audit/references/vars-checklist.md +1068 -0
  11. package/skills/compliance/compliance-audit/references/vars-findings-schema.md +395 -0
  12. package/skills/compliance/compliance-audit/scripts/reconcile_tracker.py +454 -0
  13. package/skills/compliance/compliance-audit/scripts/test_validate_tracker.py +90 -0
  14. package/skills/compliance/compliance-audit/scripts/validate_tracker.py +752 -0
  15. package/skills/compliance/doc-authenticity/LICENSE +3 -0
  16. package/skills/compliance/doc-authenticity/SKILL.md +158 -0
  17. package/skills/compliance/doc-authenticity/references/agreement.md +66 -0
  18. package/skills/compliance/doc-authenticity/references/bank-statement.md +150 -0
  19. package/skills/compliance/doc-authenticity/references/forensic-signals.md +157 -0
  20. package/skills/compliance/doc-authenticity/references/image-signals.md +114 -0
  21. package/skills/compliance/doc-authenticity/references/invoice.md +66 -0
  22. package/skills/compliance/doc-authenticity/references/issuer-profiles.md +86 -0
  23. package/skills/compliance/doc-authenticity/references/verdict-and-report.md +95 -0
  24. package/skills/compliance/doc-authenticity/references/verdict.schema.json +81 -0
  25. package/skills/compliance/doc-authenticity/scripts/image_forensics.py +294 -0
  26. package/skills/compliance/doc-authenticity/scripts/pdf_forensics.py +401 -0
  27. package/skills/compliance/doc-authenticity/scripts/validate_codes.py +224 -0
  28. package/skills/compliance/doc-authenticity/scripts/validate_verdict.py +308 -0
  29. package/skills/compliance/report-writing/LICENSE +3 -0
  30. package/skills/compliance/report-writing/SKILL.md +206 -0
  31. package/skills/compliance/report-writing/references/audit-report-template.md +121 -0
  32. package/skills/compliance/report-writing/references/external-assessment-template.md +167 -0
  33. package/skills/compliance/report-writing/references/management-report-template.md +145 -0
  34. package/skills/compliance/report-writing/references/vars-report-template.md +224 -0
  35. package/skills/document-utils/doc-to-md/LICENSE +3 -0
  36. package/skills/document-utils/doc-to-md/SKILL.md +77 -0
  37. package/skills/document-utils/doc-to-md/scripts/convert.py +157 -0
  38. package/skills/document-utils/reg-parser/LICENSE +3 -0
  39. package/skills/document-utils/reg-parser/SKILL.md +201 -0
  40. package/skills/document-utils/reg-parser/references/checklist-schema.md +223 -0
  41. package/skills/document-utils/reg-parser/references/checklists/hk-amlo-checklist.json +1306 -0
  42. package/skills/document-utils/reg-parser/references/checklists/hk-amlo-checklist.md +469 -0
  43. package/skills/document-utils/reg-parser/references/checklists/hk-fi-corporate-onboarding-cdd-checklist.json +634 -0
  44. package/skills/document-utils/reg-parser/references/checklists/hk-fi-corporate-onboarding-cdd-checklist.md +233 -0
  45. package/skills/document-utils/reg-parser/references/checklists/hk-mso-guideline-checklist.json +2194 -0
  46. package/skills/document-utils/reg-parser/references/checklists/hk-mso-guideline-checklist.md +771 -0
  47. package/skills/document-utils/reg-parser/references/checklists/kyc-aml-checklist.md +258 -0
  48. package/skills/document-utils/reg-parser/references/checklists/visa-vars-checklist.md +1058 -0
  49. package/skills/document-utils/reg-parser/references/mandates/hk-amlo-cap615.md +389 -0
  50. package/skills/document-utils/reg-parser/references/mandates/visa-vars.md +205 -0
  51. package/skills/document-utils/reg-parser/scripts/compare_checklist.py +228 -0
  52. package/skills/document-utils/reg-parser/scripts/dump_checklist.py +348 -0
  53. package/skills/document-utils/reg-parser/scripts/preprocess-mandate.sh +171 -0
  54. package/skills/document-utils/reg-parser/scripts/test_quarantine.sh +102 -0
  55. package/skills/document-utils/reg-parser/scripts/validate_checklist.py +532 -0
  56. package/skills/document-utils/reg-parser/scripts/validate_mandate.py +242 -0
  57. package/skills/engineering/engineering-runtime/coherence/workflow.json +16 -16
  58. package/skills/engineering/engineering-runtime/scripts/workflow-coherence.mjs +4 -0
  59. package/skills/engineering/engineering-runtime/scripts/workflow-policy.mjs +1 -1
  60. package/skills/payment/similar-domain/LICENSE +3 -0
  61. package/skills/payment/similar-domain/SKILL.md +178 -0
  62. package/skills/payment/similar-domain/references/entities.md +58 -0
  63. package/skills/payment/similar-domain/references/fingerprints.md +82 -0
  64. package/skills/payment/similar-domain/references/pivots.md +66 -0
  65. package/skills/payment/similar-domain/references/report-template.md +76 -0
  66. package/skills/payment/similar-domain/references/scoring.md +61 -0
  67. package/skills/payment/similar-domain/references/terminology.md +37 -0
  68. package/skills/payment/similar-domain/references/workflow.md +79 -0
  69. package/skills/payment/tld-detection/SKILL.md +76 -21
  70. package/skills/payment/tld-detection/references/clean.md +1 -1
  71. package/skills/payment/tld-detection/references/clustering.md +21 -6
  72. package/skills/payment/tld-detection/references/decision.md +7 -2
  73. package/skills/payment/tld-detection/references/fx-conversion.md +229 -0
  74. package/skills/payment/tld-detection/references/report-template.md +63 -27
  75. package/skills/payment/tld-detection/references/streams.md +90 -0
  76. package/skills/payment/tld-detection/references/terminology.md +15 -7
  77. package/skills/payment/tld-detection/references/workflow.md +1 -1
  78. package/skills/payment/tld-detection/scripts/detect_streams.py +542 -0
  79. package/skills/engineering/ensure-coverage/evals/evals.json +0 -125
  80. package/skills/engineering/rush-issues/evals/evals.json +0 -65
  81. package/skills/engineering/rush-release/evals/evals.json +0 -58
  82. package/skills/fullstack/shadcn/evals/evals.json +0 -90
  83. package/skills/payment/fraud-analysis/evals/evals.json +0 -40
  84. package/skills/payment/tld-detection/evals/evals.json +0 -107
  85. package/skills/payment/tld-detection/tests/test_detect_ladders.py +0 -132
@@ -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 )(.+?)( && .*)?$/;
317
-
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.
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)?( && .*)?$/;
313
+
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, {
@@ -354,18 +349,19 @@ export function planBundleCommands(action, groupIds, {
354
349
  const recipes = readRecipes(recipesPath);
355
350
  assertLocalRoot(localRoot);
356
351
  const groups = new Map(recipes.groups.map((group) => [group.id, group]));
352
+ const memberMetadata = new Map((recipes.sources ?? [])
353
+ .flatMap((source) => source.members ?? [])
354
+ .map((member) => [member.installName, member]));
357
355
  const requested = [];
358
356
  for (const groupId of groupIds) {
359
357
  if (!groups.has(groupId) || SUPPORT_GROUPS.has(groupId)) throw new Error(`Unknown bundle: ${groupId}`);
360
358
  if (!requested.includes(groupId)) requested.push(groupId);
361
359
  }
362
360
 
363
- // Plan from the selected groups (not recipes.all). A public package may expose only a subset of
364
- // groups while still shipping a full recipes.all from the private catalog; using that all-plan
365
- // 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.
366
363
  const planned = [];
367
364
  const plannedByMember = new Map();
368
-
369
365
  for (const groupId of requested) {
370
366
  const group = groups.get(groupId);
371
367
  for (const step of group[action].steps) {
@@ -376,14 +372,10 @@ export function planBundleCommands(action, groupIds, {
376
372
  const command = step.sourceId === 'first-party'
377
373
  ? localFirstPartyCommand(generated.command, localRoot, groupId)
378
374
  : generated.command;
379
- // An explicit member subset (the digest skip) removes members from the native add but never
380
- // reorders the rest: the remaining skills keep recipe order, support dependency first.
381
- // Declined new members (#1128) are also filtered out, but consent never applies to support
382
- // dependencies (harness-runtime): they are silent install dependencies, not capabilities.
383
375
  const skills = skillsFromCommand(command)
384
376
  .filter((skill) => !skipMembers?.has(skill))
385
377
  .filter((skill) => SUPPORT_GROUPS.has(skill) || !omit.has(skill));
386
- const suffix = SKILL_TAIL.exec(command)?.[3] || '';
378
+ const suffix = SKILL_TAIL.exec(command)?.[4] || '';
387
379
  const bare = suffix ? command.slice(0, command.length - suffix.length) : command;
388
380
  for (const skill of skills) {
389
381
  const existing = plannedByMember.get(skill);
@@ -393,8 +385,6 @@ export function planBundleCommands(action, groupIds, {
393
385
  }
394
386
  let skillCommand = withSkills(bare, [skill]);
395
387
  if (suffix && skill === RUNTIME_MEMBER) skillCommand = `${skillCommand}${suffix}`;
396
- // The pinned CLI has no --symlink flag. --yes is its only supported way to select the
397
- // default symlink deployment without presenting the install-method chooser.
398
388
  skillCommand = withYesFlag(skillCommand);
399
389
  const invocationPolicy = skill === 'tdd' && enableTdd
400
390
  ? undefined
@@ -409,55 +399,158 @@ export function planBundleCommands(action, groupIds, {
409
399
  command: skillCommand,
410
400
  cwd: localRoot,
411
401
  failureMessage: step.onFailure.message,
402
+ ...(memberMetadata.get(skill)?.requires?.length > 0
403
+ ? { requires: memberMetadata.get(skill).requires }
404
+ : {}),
405
+ metadata: memberMetadata.get(skill) ?? { installName: skill },
412
406
  };
413
407
  plannedByMember.set(skill, entry);
414
408
  planned.push(entry);
415
409
  }
416
410
  }
417
411
  }
418
-
419
412
  return planned;
420
413
  }
421
414
 
422
- // Continue-on-failure (#1129): every planned per-skill add is attempted in dependency order; a
423
- // failed skill is recorded (its recipe failure message goes to stderr) and the run proceeds to the
424
- // remaining skills. Nothing here stops early — the caller summarizes the buckets and decides the
425
- // exit status, and selection persistence sees exactly which members succeeded.
426
- 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, {
427
447
  execute = (command, cwd) => spawnSync('/bin/bash', ['-lc', command], { cwd, stdio: 'inherit' }),
428
448
  applyPolicy = applyInvocationPolicy,
449
+ verifyMember,
429
450
  skillsRoot,
430
451
  stdout = process.stdout,
431
452
  stderr = process.stderr,
453
+ satisfiedMembers = [],
432
454
  } = {}) {
433
455
  const succeeded = [];
434
456
  const failed = [];
435
- for (const planned of commands) {
436
- stdout.write(`\nInstalling ${planned.member} (${planned.sourceId})...\n`);
437
- const result = execute(planned.command, planned.cwd);
438
- if (result.status === 0) {
439
- try {
440
- if (planned.invocationPolicy) {
457
+ const available = new Set(satisfiedMembers);
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
+ }
475
+ }
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 {
441
494
  applyPolicy({
442
495
  installName: planned.member,
443
496
  policy: planned.invocationPolicy,
444
497
  ...(skillsRoot ? { skillsRoot } : {}),
445
498
  });
499
+ } catch (error) {
500
+ stderr.write(`Invocation policy failed for ${planned.member}: ${error.message}\n`);
501
+ policyFailures.add(planned.member);
446
502
  }
447
- succeeded.push(planned);
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 });
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 });
448
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
+ }
449
526
  } catch (error) {
450
- stderr.write(`Invocation policy failed for ${planned.member}: ${error.message}\n`);
527
+ stderr.write(`Post-install verification failed for ${planned.member}: ${error.message}\n`);
451
528
  failed.push({ planned, status: 1 });
452
- continue;
453
529
  }
454
530
  }
455
- stderr.write(`${planned.failureMessage}\n`);
456
- failed.push({ planned, status: Number.isInteger(result.status) ? result.status : 1 });
457
531
  }
458
532
  return { succeeded, failed };
459
533
  }
460
534
 
535
+ export function enforceReadyDependencies(readyMembers, records, stderr = process.stderr) {
536
+ const metadata = new Map(records.map((record) => [record.member, record]));
537
+ const blocked = [];
538
+ let changed = true;
539
+ while (changed) {
540
+ changed = false;
541
+ for (const member of [...readyMembers]) {
542
+ const record = metadata.get(member);
543
+ const blockedBy = (record?.requires ?? []).filter((dependency) => !readyMembers.has(dependency));
544
+ if (blockedBy.length === 0) continue;
545
+ readyMembers.delete(member);
546
+ blocked.push({ planned: record, status: 1, blockedBy });
547
+ stderr.write(`Dependency unavailable for ${member}: ${blockedBy.join(', ')} is not ready\n`);
548
+ changed = true;
549
+ }
550
+ }
551
+ return blocked;
552
+ }
553
+
461
554
  // The four run buckets, printed after every non-dry run. `unchanged` (digest-identical skip, #1127)
462
555
  // and `declined` (new-member consent, #1128) render from empty lists until those units land — the
463
556
  // summary contract is stable from here on, so both buckets are always shown.
@@ -508,7 +601,12 @@ export function expectedMemberDigest(source, member, { localRoot }) {
508
601
  // installed store (`~/.agents/skills`); the member lives under it by installName.
509
602
  export function classifyInstalledMember(source, member, { enableTdd = true, localRoot, skillsRoot }) {
510
603
  const directory = join(skillsRoot, member.installName);
511
- const entry = { member: member.installName, sourceId: source?.id ?? null, directory };
604
+ const entry = {
605
+ member: member.installName,
606
+ sourceId: source?.id ?? null,
607
+ directory,
608
+ ...(member.requires?.length > 0 ? { requires: member.requires } : {}),
609
+ };
512
610
  if (!statSync(directory, { throwIfNoEntry: false })?.isDirectory()) {
513
611
  return { ...entry, status: 'missing' };
514
612
  }
@@ -835,6 +933,7 @@ function selectionIncludesTdd(groupIds, catalog) {
835
933
  export async function runCli(args, {
836
934
  execute,
837
935
  applyPolicy,
936
+ verifyMember,
838
937
  stdin = process.stdin,
839
938
  stdout = process.stdout,
840
939
  stderr = process.stderr,
@@ -930,10 +1029,11 @@ export async function runCli(args, {
930
1029
  skipMembers,
931
1030
  omitMembers: declined,
932
1031
  });
1032
+ const batches = coalesceBundleCommands(commands);
933
1033
  if (parsed.dryRun) {
934
1034
  if (buckets) printUpdateBuckets(stdout, buckets);
935
- for (const planned of commands) {
936
- 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`);
937
1037
  }
938
1038
  if (buckets && commands.length === 0 && archiveCandidates.length === 0) {
939
1039
  stdout.write('Nothing to do: every accepted skill is already up to date.\n');
@@ -946,24 +1046,43 @@ export async function runCli(args, {
946
1046
  const skipping = buckets.unchanged.map((entry) => entry.member).filter((member) => !SUPPORT_GROUPS.has(member));
947
1047
  if (skipping.length > 0) stdout.write(`Up to date, skipping: ${skipping.join(', ')}\n`);
948
1048
  }
949
- 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, {
950
1061
  execute,
951
1062
  applyPolicy,
1063
+ verifyMember: verifyInstalledMember,
952
1064
  skillsRoot,
953
1065
  stdout,
954
1066
  stderr,
1067
+ satisfiedMembers: buckets?.unchanged.map((entry) => entry.member),
955
1068
  });
956
1069
  const readyMembers = new Set([
957
1070
  ...succeeded.map((planned) => planned.member),
958
1071
  ...(buckets?.unchanged.map((entry) => entry.member) ?? []),
959
1072
  ]);
1073
+ failed.push(...enforceReadyDependencies(
1074
+ readyMembers,
1075
+ [...commands, ...(buckets?.unchanged ?? [])],
1076
+ stderr,
1077
+ ));
1078
+ const coherentSucceeded = succeeded.filter((planned) => readyMembers.has(planned.member));
960
1079
  const archives = runArchiveCommands(
961
1080
  archiveCandidates.filter((candidate) => !declinedArchives.has(candidate.installName)),
962
1081
  readyMembers,
963
1082
  { execute, skillsRoot, stdout, stderr },
964
1083
  );
965
1084
  printRunSummary(stdout, {
966
- succeeded,
1085
+ succeeded: coherentSucceeded,
967
1086
  failed,
968
1087
  unchanged: buckets?.unchanged.map((entry) => entry.member),
969
1088
  declined: [...declined],
@@ -154,6 +154,10 @@ const TEST_FIXTURE = /\.fixtures?\.[^.]+$/;
154
154
  export function publishedContentExcluded(name) {
155
155
  return name.startsWith('.')
156
156
  || name === 'node_modules'
157
+ || name === '__pycache__'
158
+ || name === 'evals'
159
+ || name === 'samples'
160
+ || name === 'tests'
157
161
  || name.endsWith('-workspace')
158
162
  || name.endsWith('.test.mjs')
159
163
  || TEST_FIXTURE.test(name);
@@ -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.2',
89
+ version: '0.20.4',
90
90
  },
91
91
  matt: {
92
92
  id: 'matt-pocock',
@@ -0,0 +1,3 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 1AboveIO