pumuki 6.3.252 → 6.3.254

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/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # Changelog
2
2
 
3
+ ## [6.3.254] - 2026-05-14
4
+
5
+ - iOS Swift Testing parity: `skills.ios.no-legacy-expectation-description` now emits AST-style line and node evidence for legacy `expectation(description:)` scaffolding, with actionable replacement guidance toward `await confirmation(...)` or awaited fulfillment flows.
6
+
7
+ ## [6.3.253] - 2026-05-14
8
+
9
+ ### Added
10
+ - **iOS AST node evidence for XCTest waits:** `skills.ios.no-wait-for-expectations` now emits actionable line and node metadata for `wait(for:)`, `self.wait(for:)`, `waitForExpectations(timeout:)` and `XCTWaiter.wait(for:)`, while ignoring strings and comments.
11
+
3
12
  All notable changes to `pumuki` are documented here.
4
13
 
5
14
  This project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
@@ -6,6 +6,8 @@ import {
6
6
  findSwiftOpenClosedSwitchMatch,
7
7
  findSwiftConcreteDependencyDipMatch,
8
8
  findSwiftPresentationSrpMatch,
9
+ collectSwiftLegacyExpectationDescriptionLines,
10
+ collectSwiftWaitForExpectationsLines,
9
11
  hasSwiftAnyViewUsage,
10
12
  hasSwiftAsyncWithoutAwaitUsage,
11
13
  hasSwiftCallbackStyleSignature,
@@ -1721,14 +1723,20 @@ test('hasSwiftWaitForExpectationsUsage detecta waits legacy y excluye await fulf
1721
1723
  let expectation = expectation(description: "Done")
1722
1724
  wait(for: [expectation], timeout: 1)
1723
1725
  waitForExpectations(timeout: 1)
1726
+ self.wait(for: [expectation], timeout: 1)
1727
+ XCTWaiter.wait(for: [expectation], timeout: 1)
1724
1728
  `;
1725
1729
  const modernWait = `
1726
1730
  let expectation = expectation(description: "Done")
1727
1731
  await fulfillment(of: [expectation], timeout: 1)
1732
+ let sample = "waitForExpectations(timeout: 1)"
1733
+ // wait(for: [expectation], timeout: 1)
1728
1734
  `;
1729
1735
 
1730
1736
  assert.equal(hasSwiftWaitForExpectationsUsage(legacyWait), true);
1731
1737
  assert.equal(hasSwiftWaitForExpectationsUsage(modernWait), false);
1738
+ assert.deepEqual(collectSwiftWaitForExpectationsLines(legacyWait), [3, 4, 5, 6]);
1739
+ assert.deepEqual(collectSwiftWaitForExpectationsLines(modernWait), []);
1732
1740
  });
1733
1741
 
1734
1742
  test('hasSwiftLegacyExpectationDescriptionUsage detecta expectation(description:) sin flujo moderno', () => {
@@ -1751,6 +1759,8 @@ await confirmation("Done") { confirm in
1751
1759
  assert.equal(hasSwiftLegacyExpectationDescriptionUsage(legacyExpectation), true);
1752
1760
  assert.equal(hasSwiftLegacyExpectationDescriptionUsage(modernExpectation), false);
1753
1761
  assert.equal(hasSwiftLegacyExpectationDescriptionUsage(confirmationOnly), false);
1762
+ assert.deepEqual(collectSwiftLegacyExpectationDescriptionLines(legacyExpectation), [2]);
1763
+ assert.deepEqual(collectSwiftLegacyExpectationDescriptionLines(confirmationOnly), []);
1754
1764
  });
1755
1765
 
1756
1766
  test('hasSwiftNSManagedObjectBoundaryUsage detecta boundaries con NSManagedObject y excluye IDs o subclases', () => {
@@ -1362,20 +1362,27 @@ const hasSwiftConfirmationUsage = (source: string): boolean => {
1362
1362
  return hasSwiftSanitizedRegexMatch(source, /\bawait\s+confirmation\b/);
1363
1363
  };
1364
1364
 
1365
+ export const collectSwiftWaitForExpectationsLines = (source: string): readonly number[] => {
1366
+ return sortedUniqueLines([
1367
+ ...collectSwiftRegexLines(source, /\b(?:self\s*\.\s*)?wait\s*\(\s*for\s*:/),
1368
+ ...collectSwiftRegexLines(source, /\bwaitForExpectations\s*\(/),
1369
+ ...collectSwiftRegexLines(source, /\bXCTWaiter\s*\.\s*wait\s*\(\s*for\s*:/),
1370
+ ]);
1371
+ };
1372
+
1365
1373
  export const hasSwiftWaitForExpectationsUsage = (source: string): boolean => {
1366
- return hasSwiftSanitizedRegexMatch(
1367
- source,
1368
- /\bwait\s*\(\s*for\s*:|\bwaitForExpectations\s*\(/
1369
- );
1374
+ return collectSwiftWaitForExpectationsLines(source).length > 0;
1370
1375
  };
1371
1376
 
1372
- export const hasSwiftLegacyExpectationDescriptionUsage = (source: string): boolean => {
1373
- const hasLegacyExpectation = collectSwiftRegexLines(
1377
+ export const collectSwiftLegacyExpectationDescriptionLines = (source: string): readonly number[] => {
1378
+ return sortedUniqueLines(collectSwiftRegexLines(
1374
1379
  source,
1375
1380
  /\bexpectation\s*\(\s*description\s*:/
1376
- ).length > 0;
1381
+ ));
1382
+ };
1377
1383
 
1378
- if (!hasLegacyExpectation) {
1384
+ export const hasSwiftLegacyExpectationDescriptionUsage = (source: string): boolean => {
1385
+ if (collectSwiftLegacyExpectationDescriptionLines(source).length === 0) {
1379
1386
  return false;
1380
1387
  }
1381
1388
 
@@ -627,6 +627,12 @@ type TextDetectorRegistryEntry = {
627
627
  readonly pathCheck: (path: string) => boolean;
628
628
  readonly excludePaths: ReadonlyArray<(path: string) => boolean>;
629
629
  readonly detect: (content: string) => boolean;
630
+ readonly locateLines?: (content: string) => readonly number[];
631
+ readonly primaryNode?: (lines: readonly number[]) => HeuristicFact['primary_node'];
632
+ readonly relatedNodes?: (lines: readonly number[]) => HeuristicFact['related_nodes'];
633
+ readonly why?: string;
634
+ readonly impact?: string;
635
+ readonly expected_fix?: string;
630
636
  readonly ruleId: string;
631
637
  readonly code: string;
632
638
  readonly message: string;
@@ -714,8 +720,8 @@ const textDetectorRegistry: ReadonlyArray<TextDetectorRegistryEntry> = [
714
720
  { platform: 'ios', pathCheck: isIOSSwiftTestPath, excludePaths: [], detect: TextIOS.hasSwiftModernizableXCTestSuiteUsage, ruleId: 'heuristics.ios.testing.xctest-suite-modernizable.ast', code: 'HEURISTICS_IOS_TESTING_XCTEST_SUITE_MODERNIZABLE_AST', message: 'AST heuristic detected XCTestCase/test... suite that may be modernizable to Swift Testing with import Testing and @Test.' },
715
721
  { platform: 'ios', pathCheck: isIOSSwiftTestPath, excludePaths: [], detect: TextIOS.hasSwiftXCTestAssertionUsage, ruleId: 'heuristics.ios.testing.xctassert.ast', code: 'HEURISTICS_IOS_TESTING_XCTASSERT_AST', message: 'AST heuristic detected XCTest assertion usage where #expect may be preferred.' },
716
722
  { platform: 'ios', pathCheck: isIOSSwiftTestPath, excludePaths: [], detect: TextIOS.hasSwiftXCTUnwrapUsage, ruleId: 'heuristics.ios.testing.xctunwrap.ast', code: 'HEURISTICS_IOS_TESTING_XCTUNWRAP_AST', message: 'AST heuristic detected XCTUnwrap usage where #require may be preferred.' },
717
- { platform: 'ios', pathCheck: isIOSSwiftTestPath, excludePaths: [], detect: TextIOS.hasSwiftWaitForExpectationsUsage, ruleId: 'heuristics.ios.testing.wait-for-expectations.ast', code: 'HEURISTICS_IOS_TESTING_WAIT_FOR_EXPECTATIONS_AST', message: 'AST heuristic detected wait(for:)/waitForExpectations usage where await fulfillment(of:) may be preferred.' },
718
- { platform: 'ios', pathCheck: isIOSSwiftTestPath, excludePaths: [], detect: TextIOS.hasSwiftLegacyExpectationDescriptionUsage, ruleId: 'heuristics.ios.testing.legacy-expectation-description.ast', code: 'HEURISTICS_IOS_TESTING_LEGACY_EXPECTATION_DESCRIPTION_AST', message: 'AST heuristic detected expectation(description:) usage without modern fulfillment/confirmation flow.' },
723
+ { platform: 'ios', pathCheck: isIOSSwiftTestPath, excludePaths: [], detect: TextIOS.hasSwiftWaitForExpectationsUsage, locateLines: TextIOS.collectSwiftWaitForExpectationsLines, primaryNode: (lines) => ({ kind: 'call', name: 'legacy XCTest wait call', lines }), relatedNodes: (lines) => [{ kind: 'call', name: 'replacement: await fulfillment(of:timeout:)', lines }], why: 'Legacy XCTest wait APIs block the current test thread and hide async intent that Swift concurrency can express directly.', impact: 'Async tests become less deterministic, harder to cancel and easier to keep tied to XCTest-only migration paths.', expected_fix: 'Replace wait(for:timeout:), self.wait(for:timeout:) or waitForExpectations(timeout:) with await fulfillment(of:timeout:) when the test target supports async XCTest migration.', ruleId: 'heuristics.ios.testing.wait-for-expectations.ast', code: 'HEURISTICS_IOS_TESTING_WAIT_FOR_EXPECTATIONS_AST', message: 'AST heuristic detected wait(for:)/waitForExpectations usage where await fulfillment(of:) may be preferred.' },
724
+ { platform: 'ios', pathCheck: isIOSSwiftTestPath, excludePaths: [], detect: TextIOS.hasSwiftLegacyExpectationDescriptionUsage, locateLines: TextIOS.collectSwiftLegacyExpectationDescriptionLines, primaryNode: (lines) => ({ kind: 'call', name: 'legacy XCTest expectation(description:) call', lines }), relatedNodes: (lines) => [{ kind: 'call', name: 'replacement: await confirmation or awaited fulfillment flow', lines }], why: 'Legacy expectation(description:) scaffolding keeps async tests coupled to XCTest-style callbacks instead of expressing confirmation intent directly.', impact: 'Tests can remain harder to read and migrate because the assertion flow is split between expectation creation, callback fulfillment and a later wait.', expected_fix: 'Prefer await confirmation(...) for callback confirmation, or pair legacy expectations with await fulfillment(of:timeout:) when the target still requires XCTest compatibility.', ruleId: 'heuristics.ios.testing.legacy-expectation-description.ast', code: 'HEURISTICS_IOS_TESTING_LEGACY_EXPECTATION_DESCRIPTION_AST', message: 'AST heuristic detected expectation(description:) usage without modern fulfillment/confirmation flow.' },
719
725
  { platform: 'ios', pathCheck: isIOSSwiftTestPath, excludePaths: [], detect: TextIOS.hasSwiftMixedTestingFrameworksUsage, ruleId: 'heuristics.ios.testing.mixed-frameworks.ast', code: 'HEURISTICS_IOS_TESTING_MIXED_FRAMEWORKS_AST', message: 'AST heuristic detected XCTestCase and Swift Testing markers mixed in the same test file without explicit compatibility reason.' },
720
726
  { platform: 'ios', pathCheck: isIOSSwiftTestPath, excludePaths: [], detect: TextIOS.hasSwiftQuickNimbleUsage, ruleId: 'heuristics.ios.testing.quick-nimble.ast', code: 'HEURISTICS_IOS_TESTING_QUICK_NIMBLE_AST', message: 'AST heuristic detected Quick/Nimble usage where native Swift Testing remains the preferred baseline.' },
721
727
  { platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftNSManagedObjectBoundaryUsage, ruleId: 'heuristics.ios.core-data.nsmanagedobject-boundary.ast', code: 'HEURISTICS_IOS_CORE_DATA_NSMANAGEDOBJECT_BOUNDARY_AST', message: 'AST heuristic detected NSManagedObject in a shared boundary.' },
@@ -812,12 +818,19 @@ export const extractHeuristicFacts = (
812
818
  (entry.excludePaths ?? []).every((exclude) => !exclude(fileFact.path)) &&
813
819
  entry.detect(fileFact.content)
814
820
  ) {
821
+ const lines = entry.locateLines?.(fileFact.content);
815
822
  heuristicFacts.push(
816
823
  createHeuristicFact({
817
824
  ruleId: entry.ruleId,
818
825
  code: entry.code,
819
826
  message: entry.message,
820
827
  filePath: fileFact.path,
828
+ lines,
829
+ primary_node: lines && lines.length > 0 ? entry.primaryNode?.(lines) : undefined,
830
+ related_nodes: lines && lines.length > 0 ? entry.relatedNodes?.(lines) : undefined,
831
+ why: entry.why,
832
+ impact: entry.impact,
833
+ expected_fix: entry.expected_fix,
821
834
  })
822
835
  );
823
836
  }
@@ -6,6 +6,15 @@ This file keeps only the operational highlights and rollout notes that matter wh
6
6
 
7
7
  ## 2026-04 (CLI stability and macOS notifications)
8
8
 
9
+ ### 2026-05-14 (v6.3.254)
10
+ - **Paridad AST iOS testing:** `skills.ios.no-legacy-expectation-description` ancla `expectation(description:)` a líneas y nodos accionables, con reemplazo recomendado hacia `await confirmation(...)` o `await fulfillment(of:timeout:)`.
11
+ - **Rollout recomendado:** publicar `pumuki@6.3.254` y repinear consumers que dependan de paridad iOS/Swift Testing.
12
+
13
+ ### 2026-05-14 (v6.3.253)
14
+
15
+ - **Paridad AST iOS testing:** `skills.ios.no-wait-for-expectations` queda respaldada por evidencia accionable de llamada Swift, con líneas, nodo primario y remediación hacia `await fulfillment(of:timeout:)`.
16
+ - **Rollout recomendado:** publicar `pumuki@6.3.253` y continuar con `skills.ios.no-legacy-expectation-description` si no entra bug externo nuevo.
17
+
9
18
  ### 2026-05-14 (v6.3.252)
10
19
 
11
20
  - **Parser de tracking estricto:** el refresh SDD ya no interpreta bullets operativos (`Siguiente`, `next`, `delegable`) como IDs de task activa.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pumuki",
3
- "version": "6.3.252",
3
+ "version": "6.3.254",
4
4
  "description": "Enterprise-grade AST Intelligence System with multi-platform support (iOS, Android, Backend, Frontend) and Feature-First + DDD + Clean Architecture enforcement. Includes dynamic violations API for intelligent querying.",
5
5
  "main": "index.js",
6
6
  "bin": {