pumuki 6.3.270 → 6.3.272

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 (33) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/VERSION +1 -1
  3. package/core/facts/detectors/text/android.test.ts +538 -0
  4. package/core/facts/detectors/text/android.ts +436 -0
  5. package/core/facts/detectors/text/ios.test.ts +328 -1
  6. package/core/facts/detectors/text/ios.ts +241 -0
  7. package/core/facts/detectors/typescript/index.test.ts +393 -0
  8. package/core/facts/detectors/typescript/index.ts +316 -0
  9. package/core/facts/extractHeuristicFacts.ts +70 -1
  10. package/core/rules/presets/heuristics/android.test.ts +91 -1
  11. package/core/rules/presets/heuristics/android.ts +360 -0
  12. package/core/rules/presets/heuristics/ios.test.ts +54 -1
  13. package/core/rules/presets/heuristics/ios.ts +243 -2
  14. package/core/rules/presets/heuristics/typescript.test.ts +50 -2
  15. package/core/rules/presets/heuristics/typescript.ts +162 -0
  16. package/docs/operations/RELEASE_NOTES.md +8 -0
  17. package/integrations/config/skillsDetectorRegistry.ts +501 -0
  18. package/integrations/config/skillsRuleClassification.ts +127 -3
  19. package/integrations/git/runPlatformGate.ts +4 -1
  20. package/integrations/lifecycle/preWriteAutomation.ts +5 -4
  21. package/integrations/lifecycle/preWriteLease.ts +41 -4
  22. package/package.json +1 -1
  23. package/scripts/classify-skills-rules.ts +2 -2
  24. package/scripts/framework-menu-consumer-actions-lib.ts +9 -9
  25. package/scripts/framework-menu-consumer-runtime-actions.ts +53 -117
  26. package/scripts/framework-menu-consumer-runtime-audit.ts +66 -0
  27. package/scripts/framework-menu-consumer-runtime-menu.ts +4 -4
  28. package/scripts/framework-menu-gate-lib.ts +86 -1
  29. package/scripts/framework-menu-layout-data.ts +3 -3
  30. package/scripts/framework-menu-legacy-audit-render-sections.ts +6 -0
  31. package/scripts/framework-menu.ts +10 -6
  32. package/scripts/package-install-smoke-consumer-npm-lib.ts +10 -4
  33. package/scripts/package-install-smoke-lifecycle-lib.ts +19 -0
@@ -37,6 +37,24 @@ export const iosRules: RuleSet = [
37
37
  code: 'HEURISTICS_IOS_ANYVIEW_AST',
38
38
  },
39
39
  },
40
+ {
41
+ id: 'heuristics.ios.type-erasure.any.ast',
42
+ description: 'Detects Swift Any/AnyObject/AnyHashable type erasure in production code.',
43
+ severity: 'WARN',
44
+ platform: 'ios',
45
+ locked: true,
46
+ when: {
47
+ kind: 'Heuristic',
48
+ where: {
49
+ ruleId: 'heuristics.ios.type-erasure.any.ast',
50
+ },
51
+ },
52
+ then: {
53
+ kind: 'Finding',
54
+ message: 'AST heuristic detected Swift Any/AnyObject/AnyHashable type erasure in production code.',
55
+ code: 'HEURISTICS_IOS_TYPE_ERASURE_ANY_AST',
56
+ },
57
+ },
40
58
  {
41
59
  id: 'heuristics.ios.force-try.ast',
42
60
  description: 'Detects Swift force try usage in production code.',
@@ -91,6 +109,25 @@ export const iosRules: RuleSet = [
91
109
  code: 'HEURISTICS_IOS_CALLBACK_STYLE_AST',
92
110
  },
93
111
  },
112
+ {
113
+ id: 'heuristics.ios.combine.sink-without-store.ast',
114
+ description: 'Detects Combine sink subscriptions that are not retained with store(in:).',
115
+ severity: 'WARN',
116
+ platform: 'ios',
117
+ locked: true,
118
+ when: {
119
+ kind: 'Heuristic',
120
+ where: {
121
+ ruleId: 'heuristics.ios.combine.sink-without-store.ast',
122
+ },
123
+ },
124
+ then: {
125
+ kind: 'Finding',
126
+ message:
127
+ 'AST heuristic detected Combine sink without store(in:); keep cancellables retained explicitly.',
128
+ code: 'HEURISTICS_IOS_COMBINE_SINK_WITHOUT_STORE_AST',
129
+ },
130
+ },
94
131
  {
95
132
  id: 'heuristics.ios.dispatchqueue.ast',
96
133
  description: 'Detects DispatchQueue usage in iOS production code.',
@@ -257,6 +294,24 @@ export const iosRules: RuleSet = [
257
294
  code: 'HEURISTICS_IOS_SWIFTUI_ONCHANGE_TASK_AST',
258
295
  },
259
296
  },
297
+ {
298
+ id: 'heuristics.ios.swiftui.onchange-readonly-var.ast',
299
+ description: 'Detects local var declarations inside SwiftUI onChange closures where let should be used for read-only derived values.',
300
+ severity: 'WARN',
301
+ platform: 'ios',
302
+ locked: true,
303
+ when: {
304
+ kind: 'Heuristic',
305
+ where: {
306
+ ruleId: 'heuristics.ios.swiftui.onchange-readonly-var.ast',
307
+ },
308
+ },
309
+ then: {
310
+ kind: 'Finding',
311
+ message: 'AST heuristic detected local var inside SwiftUI onChange; prefer let for read-only derived values.',
312
+ code: 'HEURISTICS_IOS_SWIFTUI_ONCHANGE_READONLY_VAR_AST',
313
+ },
314
+ },
260
315
  {
261
316
  id: 'heuristics.ios.memory.strong-delegate.ast',
262
317
  description: 'Detects strong delegate/dataSource references in iOS production code.',
@@ -295,6 +350,63 @@ export const iosRules: RuleSet = [
295
350
  code: 'HEURISTICS_IOS_MEMORY_STRONG_SELF_ESCAPING_CLOSURE_AST',
296
351
  },
297
352
  },
353
+ {
354
+ id: 'heuristics.ios.memory.unowned-self-capture.ast',
355
+ description: 'Detects unowned captures in iOS closures.',
356
+ severity: 'WARN',
357
+ platform: 'ios',
358
+ locked: true,
359
+ when: {
360
+ kind: 'Heuristic',
361
+ where: {
362
+ ruleId: 'heuristics.ios.memory.unowned-self-capture.ast',
363
+ },
364
+ },
365
+ then: {
366
+ kind: 'Finding',
367
+ message:
368
+ 'AST heuristic detected unowned capture in an iOS closure; use weak capture unless lifetime is explicitly guaranteed.',
369
+ code: 'HEURISTICS_IOS_MEMORY_UNOWNED_SELF_CAPTURE_AST',
370
+ },
371
+ },
372
+ {
373
+ id: 'heuristics.ios.maintainability.nested-if-pyramid.ast',
374
+ description: 'Detects deeply nested if pyramids in iOS production code.',
375
+ severity: 'WARN',
376
+ platform: 'ios',
377
+ locked: true,
378
+ when: {
379
+ kind: 'Heuristic',
380
+ where: {
381
+ ruleId: 'heuristics.ios.maintainability.nested-if-pyramid.ast',
382
+ },
383
+ },
384
+ then: {
385
+ kind: 'Finding',
386
+ message:
387
+ 'AST heuristic detected nested if pyramid in iOS code; prefer guard clauses and early returns.',
388
+ code: 'HEURISTICS_IOS_MAINTAINABILITY_NESTED_IF_PYRAMID_AST',
389
+ },
390
+ },
391
+ {
392
+ id: 'heuristics.ios.maintainability.comment-trivia.ast',
393
+ description: 'Detects source comments in iOS production Swift code.',
394
+ severity: 'WARN',
395
+ platform: 'ios',
396
+ locked: true,
397
+ when: {
398
+ kind: 'Heuristic',
399
+ where: {
400
+ ruleId: 'heuristics.ios.maintainability.comment-trivia.ast',
401
+ },
402
+ },
403
+ then: {
404
+ kind: 'Finding',
405
+ message:
406
+ 'AST heuristic detected source comments in iOS production code; prefer self-documenting names and extracted concepts.',
407
+ code: 'HEURISTICS_IOS_MAINTAINABILITY_COMMENT_TRIVIA_AST',
408
+ },
409
+ },
298
410
  {
299
411
  id: 'heuristics.ios.architecture.custom-singleton.ast',
300
412
  description: 'Detects custom static shared singletons in iOS production code.',
@@ -590,6 +702,25 @@ export const iosRules: RuleSet = [
590
702
  code: 'HEURISTICS_IOS_LOCALIZATION_LOCALIZABLE_STRINGS_AST',
591
703
  },
592
704
  },
705
+ {
706
+ id: 'heuristics.ios.interface-builder.storyboard-xib.ast',
707
+ description: 'Detects Storyboard/XIB usage where programmatic UI should be used.',
708
+ severity: 'WARN',
709
+ platform: 'ios',
710
+ locked: true,
711
+ when: {
712
+ kind: 'Heuristic',
713
+ where: {
714
+ ruleId: 'heuristics.ios.interface-builder.storyboard-xib.ast',
715
+ },
716
+ },
717
+ then: {
718
+ kind: 'Finding',
719
+ message:
720
+ 'AST heuristic detected Storyboard/XIB usage; programmatic SwiftUI/UIKit UI remains the preferred baseline.',
721
+ code: 'HEURISTICS_IOS_INTERFACE_BUILDER_STORYBOARD_XIB_AST',
722
+ },
723
+ },
593
724
  {
594
725
  id: 'heuristics.ios.localization.hardcoded-ui-string.ast',
595
726
  description: 'Detects hardcoded user-facing SwiftUI text where String(localized:) and String Catalogs are preferred.',
@@ -698,6 +829,43 @@ export const iosRules: RuleSet = [
698
829
  code: 'HEURISTICS_IOS_ACCESSIBILITY_ICON_ONLY_CONTROL_LABEL_AST',
699
830
  },
700
831
  },
832
+ {
833
+ id: 'heuristics.ios.accessibility.missing-accessibility-identifier.ast',
834
+ description: 'Detects interactive SwiftUI controls without accessibilityIdentifier.',
835
+ severity: 'WARN',
836
+ platform: 'ios',
837
+ locked: true,
838
+ when: {
839
+ kind: 'Heuristic',
840
+ where: {
841
+ ruleId: 'heuristics.ios.accessibility.missing-accessibility-identifier.ast',
842
+ },
843
+ },
844
+ then: {
845
+ kind: 'Finding',
846
+ message:
847
+ 'AST heuristic detected an interactive SwiftUI control without accessibilityIdentifier; stable identifiers are required for UI automation and traceability.',
848
+ code: 'HEURISTICS_IOS_ACCESSIBILITY_MISSING_ACCESSIBILITY_IDENTIFIER_AST',
849
+ },
850
+ },
851
+ {
852
+ id: 'heuristics.ios.swiftui.missing-bindable-observable-binding.ast',
853
+ description: 'Detects injected @Observable values used as bindings without @Bindable.',
854
+ severity: 'WARN',
855
+ platform: 'ios',
856
+ locked: true,
857
+ when: {
858
+ kind: 'Heuristic',
859
+ where: {
860
+ ruleId: 'heuristics.ios.swiftui.missing-bindable-observable-binding.ast',
861
+ },
862
+ },
863
+ then: {
864
+ kind: 'Finding',
865
+ message: 'AST heuristic detected an injected @Observable used as a binding without @Bindable.',
866
+ code: 'HEURISTICS_IOS_SWIFTUI_MISSING_BINDABLE_OBSERVABLE_BINDING_AST',
867
+ },
868
+ },
701
869
  {
702
870
  id: 'heuristics.ios.unchecked-sendable.ast',
703
871
  description: 'Detects @unchecked Sendable usage in iOS production code.',
@@ -788,6 +956,24 @@ export const iosRules: RuleSet = [
788
956
  code: 'HEURISTICS_IOS_OBSERVABLE_OBJECT_AST',
789
957
  },
790
958
  },
959
+ {
960
+ id: 'heuristics.ios.swiftui.legacy-preview-provider.ast',
961
+ description: 'Detects PreviewProvider usage where modern SwiftUI #Preview macros should be used.',
962
+ severity: 'WARN',
963
+ platform: 'ios',
964
+ locked: true,
965
+ when: {
966
+ kind: 'Heuristic',
967
+ where: {
968
+ ruleId: 'heuristics.ios.swiftui.legacy-preview-provider.ast',
969
+ },
970
+ },
971
+ then: {
972
+ kind: 'Finding',
973
+ message: 'AST heuristic detected PreviewProvider usage; use #Preview macros for modern SwiftUI previews.',
974
+ code: 'HEURISTICS_IOS_SWIFTUI_LEGACY_PREVIEW_PROVIDER_AST',
975
+ },
976
+ },
791
977
  {
792
978
  id: 'heuristics.ios.legacy-swiftui-observable-wrapper.ast',
793
979
  description: 'Detects @StateObject and @ObservedObject usage in modern SwiftUI production code.',
@@ -806,6 +992,61 @@ export const iosRules: RuleSet = [
806
992
  code: 'HEURISTICS_IOS_LEGACY_SWIFTUI_OBSERVABLE_WRAPPER_AST',
807
993
  },
808
994
  },
995
+ {
996
+ id: 'heuristics.ios.testing.test-double-without-protocol.ast',
997
+ description: 'Detects Swift Mock/Fake/Spy/Stub classes without protocol conformance.',
998
+ severity: 'WARN',
999
+ platform: 'ios',
1000
+ locked: true,
1001
+ when: {
1002
+ kind: 'Heuristic',
1003
+ where: {
1004
+ ruleId: 'heuristics.ios.testing.test-double-without-protocol.ast',
1005
+ },
1006
+ },
1007
+ then: {
1008
+ kind: 'Finding',
1009
+ message: 'AST heuristic detected a Swift test double class without protocol conformance.',
1010
+ code: 'HEURISTICS_IOS_TESTING_TEST_DOUBLE_WITHOUT_PROTOCOL_AST',
1011
+ },
1012
+ },
1013
+ {
1014
+ id: 'heuristics.ios.testing.production-test-double.ast',
1015
+ description: 'Detects Mock/Fake/Spy/Stub usage in iOS production code.',
1016
+ severity: 'WARN',
1017
+ platform: 'ios',
1018
+ locked: true,
1019
+ when: {
1020
+ kind: 'Heuristic',
1021
+ where: {
1022
+ ruleId: 'heuristics.ios.testing.production-test-double.ast',
1023
+ },
1024
+ },
1025
+ then: {
1026
+ kind: 'Finding',
1027
+ message: 'AST heuristic detected Mock/Fake/Spy/Stub usage in iOS production code.',
1028
+ code: 'HEURISTICS_IOS_TESTING_PRODUCTION_TEST_DOUBLE_AST',
1029
+ },
1030
+ },
1031
+ {
1032
+ id: 'heuristics.ios.accessibility.low-contrast-static-color-pair.ast',
1033
+ description: 'Detects static SwiftUI foreground/background color pairs with insufficient contrast.',
1034
+ severity: 'WARN',
1035
+ platform: 'ios',
1036
+ locked: true,
1037
+ when: {
1038
+ kind: 'Heuristic',
1039
+ where: {
1040
+ ruleId: 'heuristics.ios.accessibility.low-contrast-static-color-pair.ast',
1041
+ },
1042
+ },
1043
+ then: {
1044
+ kind: 'Finding',
1045
+ message:
1046
+ 'AST heuristic detected a static SwiftUI foreground/background color pair with insufficient contrast.',
1047
+ code: 'HEURISTICS_IOS_ACCESSIBILITY_LOW_CONTRAST_STATIC_COLOR_PAIR_AST',
1048
+ },
1049
+ },
809
1050
  {
810
1051
  id: 'heuristics.ios.swiftui.non-private-state-ownership.ast',
811
1052
  description: 'Detects @State/@StateObject declarations without private visibility in SwiftUI presentation code.',
@@ -1454,7 +1695,7 @@ export const iosRules: RuleSet = [
1454
1695
  },
1455
1696
  {
1456
1697
  id: 'heuristics.ios.testing.wait-for-expectations.ast',
1457
- description: 'Detects wait(for:) and waitForExpectations(timeout:) usage in async iOS tests.',
1698
+ description: 'Detects wait(for:), waitForExpectations(timeout:) and waitForExistence(timeout:) usage in async iOS tests.',
1458
1699
  severity: 'WARN',
1459
1700
  platform: 'ios',
1460
1701
  locked: true,
@@ -1466,7 +1707,7 @@ export const iosRules: RuleSet = [
1466
1707
  },
1467
1708
  then: {
1468
1709
  kind: 'Finding',
1469
- message: 'AST heuristic detected wait(for:)/waitForExpectations usage where await fulfillment(of:) may be preferred.',
1710
+ message: 'AST heuristic detected wait(for:)/waitForExpectations/waitForExistence usage where an explicit async wait contract may be preferred.',
1470
1711
  code: 'HEURISTICS_IOS_TESTING_WAIT_FOR_EXPECTATIONS_AST',
1471
1712
  },
1472
1713
  },
@@ -3,7 +3,7 @@ import test from 'node:test';
3
3
  import { typescriptRules } from './typescript';
4
4
 
5
5
  test('typescriptRules define reglas heurísticas locked para plataforma generic', () => {
6
- assert.equal(typescriptRules.length, 19);
6
+ assert.equal(typescriptRules.length, 28);
7
7
 
8
8
  const ids = typescriptRules.map((rule) => rule.id);
9
9
  assert.deepEqual(ids, [
@@ -16,6 +16,15 @@ test('typescriptRules define reglas heurísticas locked para plataforma generic'
16
16
  'heuristics.ts.set-timeout-string.ast',
17
17
  'heuristics.ts.set-interval-string.ast',
18
18
  'heuristics.ts.new-promise-async.ast',
19
+ 'heuristics.ts.bcrypt-weak-salt-rounds.ast',
20
+ 'heuristics.ts.sql-interpolated-unsafe-call.ast',
21
+ 'heuristics.ts.sensitive-token-in-url.ast',
22
+ 'heuristics.tsx.non-semantic-clickable.ast',
23
+ 'heuristics.tsx.redundant-aria-role.ast',
24
+ 'heuristics.ts.frontend-test-direct-network-call.ast',
25
+ 'heuristics.ts.callback-hell.ast',
26
+ 'heuristics.ts.nested-if-else.ast',
27
+ 'heuristics.ts.next-pages-api-route.ast',
19
28
  'heuristics.ts.with-statement.ast',
20
29
  'heuristics.ts.delete-operator.ast',
21
30
  'heuristics.ts.debugger.ast',
@@ -37,6 +46,42 @@ test('typescriptRules define reglas heurísticas locked para plataforma generic'
37
46
  byId.get('heuristics.ts.debugger.ast')?.then.code,
38
47
  'HEURISTICS_DEBUGGER_AST'
39
48
  );
49
+ assert.equal(
50
+ byId.get('heuristics.ts.callback-hell.ast')?.then.code,
51
+ 'HEURISTICS_CALLBACK_HELL_AST'
52
+ );
53
+ assert.equal(
54
+ byId.get('heuristics.ts.nested-if-else.ast')?.then.code,
55
+ 'HEURISTICS_NESTED_IF_ELSE_AST'
56
+ );
57
+ assert.equal(
58
+ byId.get('heuristics.ts.next-pages-api-route.ast')?.then.code,
59
+ 'HEURISTICS_NEXT_PAGES_API_ROUTE_AST'
60
+ );
61
+ assert.equal(
62
+ byId.get('heuristics.ts.bcrypt-weak-salt-rounds.ast')?.then.code,
63
+ 'HEURISTICS_BCRYPT_WEAK_SALT_ROUNDS_AST'
64
+ );
65
+ assert.equal(
66
+ byId.get('heuristics.ts.sql-interpolated-unsafe-call.ast')?.then.code,
67
+ 'HEURISTICS_SQL_INTERPOLATED_UNSAFE_CALL_AST'
68
+ );
69
+ assert.equal(
70
+ byId.get('heuristics.ts.sensitive-token-in-url.ast')?.then.code,
71
+ 'HEURISTICS_SENSITIVE_TOKEN_IN_URL_AST'
72
+ );
73
+ assert.equal(
74
+ byId.get('heuristics.ts.frontend-test-direct-network-call.ast')?.then.code,
75
+ 'HEURISTICS_FRONTEND_TEST_DIRECT_NETWORK_CALL_AST'
76
+ );
77
+ assert.equal(
78
+ byId.get('heuristics.tsx.non-semantic-clickable.ast')?.then.code,
79
+ 'HEURISTICS_TSX_NON_SEMANTIC_CLICKABLE_AST'
80
+ );
81
+ assert.equal(
82
+ byId.get('heuristics.tsx.redundant-aria-role.ast')?.then.code,
83
+ 'HEURISTICS_TSX_REDUNDANT_ARIA_ROLE_AST'
84
+ );
40
85
  assert.equal(
41
86
  byId.get('heuristics.ts.solid.dip.framework-import.ast')?.then.code,
42
87
  'HEURISTICS_SOLID_DIP_FRAMEWORK_IMPORT_AST'
@@ -52,7 +97,10 @@ test('typescriptRules define reglas heurísticas locked para plataforma generic'
52
97
 
53
98
  for (const rule of typescriptRules) {
54
99
  assert.equal(rule.platform, 'generic');
55
- if (rule.id === 'heuristics.ts.god-class-large-class.ast') {
100
+ if (
101
+ rule.id === 'heuristics.ts.god-class-large-class.ast' ||
102
+ rule.id === 'heuristics.ts.sensitive-token-in-url.ast'
103
+ ) {
56
104
  assert.equal(rule.severity, 'ERROR');
57
105
  } else {
58
106
  assert.equal(rule.severity, 'WARN');
@@ -163,6 +163,168 @@ export const typescriptRules: RuleSet = [
163
163
  code: 'HEURISTICS_NEW_PROMISE_ASYNC_AST',
164
164
  },
165
165
  },
166
+ {
167
+ id: 'heuristics.ts.bcrypt-weak-salt-rounds.ast',
168
+ description: 'Detects bcrypt hashing calls with numeric salt rounds below 10.',
169
+ severity: 'WARN',
170
+ platform: 'generic',
171
+ locked: true,
172
+ when: {
173
+ kind: 'Heuristic',
174
+ where: {
175
+ ruleId: 'heuristics.ts.bcrypt-weak-salt-rounds.ast',
176
+ },
177
+ },
178
+ then: {
179
+ kind: 'Finding',
180
+ message: 'AST heuristic detected bcrypt salt rounds below 10.',
181
+ code: 'HEURISTICS_BCRYPT_WEAK_SALT_ROUNDS_AST',
182
+ },
183
+ },
184
+ {
185
+ id: 'heuristics.ts.sql-interpolated-unsafe-call.ast',
186
+ description: 'Detects interpolated template SQL passed to unsafe query/raw calls.',
187
+ severity: 'WARN',
188
+ platform: 'generic',
189
+ locked: true,
190
+ when: {
191
+ kind: 'Heuristic',
192
+ where: {
193
+ ruleId: 'heuristics.ts.sql-interpolated-unsafe-call.ast',
194
+ },
195
+ },
196
+ then: {
197
+ kind: 'Finding',
198
+ message: 'AST heuristic detected interpolated SQL passed to an unsafe query/raw call.',
199
+ code: 'HEURISTICS_SQL_INTERPOLATED_UNSAFE_CALL_AST',
200
+ },
201
+ },
202
+ {
203
+ id: 'heuristics.ts.sensitive-token-in-url.ast',
204
+ description: 'Detects sensitive credential query parameters in network call URLs.',
205
+ severity: 'ERROR',
206
+ platform: 'generic',
207
+ locked: true,
208
+ when: {
209
+ kind: 'Heuristic',
210
+ where: {
211
+ ruleId: 'heuristics.ts.sensitive-token-in-url.ast',
212
+ },
213
+ },
214
+ then: {
215
+ kind: 'Finding',
216
+ message: 'AST heuristic detected sensitive token or credential in a network URL.',
217
+ code: 'HEURISTICS_SENSITIVE_TOKEN_IN_URL_AST',
218
+ },
219
+ },
220
+ {
221
+ id: 'heuristics.tsx.non-semantic-clickable.ast',
222
+ description: 'Detects non-semantic JSX elements with click handlers and no keyboard accessibility evidence.',
223
+ severity: 'WARN',
224
+ platform: 'generic',
225
+ locked: true,
226
+ when: {
227
+ kind: 'Heuristic',
228
+ where: {
229
+ ruleId: 'heuristics.tsx.non-semantic-clickable.ast',
230
+ },
231
+ },
232
+ then: {
233
+ kind: 'Finding',
234
+ message: 'AST heuristic detected non-semantic clickable JSX without keyboard accessibility.',
235
+ code: 'HEURISTICS_TSX_NON_SEMANTIC_CLICKABLE_AST',
236
+ },
237
+ },
238
+ {
239
+ id: 'heuristics.tsx.redundant-aria-role.ast',
240
+ description: 'Detects redundant ARIA roles on semantic JSX elements where native semantics should be used.',
241
+ severity: 'WARN',
242
+ platform: 'generic',
243
+ locked: true,
244
+ when: {
245
+ kind: 'Heuristic',
246
+ where: {
247
+ ruleId: 'heuristics.tsx.redundant-aria-role.ast',
248
+ },
249
+ },
250
+ then: {
251
+ kind: 'Finding',
252
+ message: 'AST heuristic detected redundant ARIA role on semantic JSX.',
253
+ code: 'HEURISTICS_TSX_REDUNDANT_ARIA_ROLE_AST',
254
+ },
255
+ },
256
+ {
257
+ id: 'heuristics.ts.frontend-test-direct-network-call.ast',
258
+ description: 'Detects direct fetch/axios/request calls in frontend test files where MSW should own API mocking.',
259
+ severity: 'WARN',
260
+ platform: 'generic',
261
+ locked: true,
262
+ when: {
263
+ kind: 'Heuristic',
264
+ where: {
265
+ ruleId: 'heuristics.ts.frontend-test-direct-network-call.ast',
266
+ },
267
+ },
268
+ then: {
269
+ kind: 'Finding',
270
+ message: 'AST heuristic detected direct network call usage in a frontend test; use MSW handlers instead.',
271
+ code: 'HEURISTICS_FRONTEND_TEST_DIRECT_NETWORK_CALL_AST',
272
+ },
273
+ },
274
+ {
275
+ id: 'heuristics.ts.callback-hell.ast',
276
+ description: 'Detects nested callback functions passed to calls in TypeScript production files.',
277
+ severity: 'WARN',
278
+ platform: 'generic',
279
+ locked: true,
280
+ when: {
281
+ kind: 'Heuristic',
282
+ where: {
283
+ ruleId: 'heuristics.ts.callback-hell.ast',
284
+ },
285
+ },
286
+ then: {
287
+ kind: 'Finding',
288
+ message: 'AST heuristic detected nested callback usage.',
289
+ code: 'HEURISTICS_CALLBACK_HELL_AST',
290
+ },
291
+ },
292
+ {
293
+ id: 'heuristics.ts.nested-if-else.ast',
294
+ description: 'Detects nested if/else control flow where early returns should be preferred.',
295
+ severity: 'WARN',
296
+ platform: 'generic',
297
+ locked: true,
298
+ when: {
299
+ kind: 'Heuristic',
300
+ where: {
301
+ ruleId: 'heuristics.ts.nested-if-else.ast',
302
+ },
303
+ },
304
+ then: {
305
+ kind: 'Finding',
306
+ message: 'AST heuristic detected nested if/else control flow; prefer early returns.',
307
+ code: 'HEURISTICS_NESTED_IF_ELSE_AST',
308
+ },
309
+ },
310
+ {
311
+ id: 'heuristics.ts.next-pages-api-route.ast',
312
+ description: 'Detects legacy Next.js pages/api route handlers where app/api route handlers should be used.',
313
+ severity: 'WARN',
314
+ platform: 'generic',
315
+ locked: true,
316
+ when: {
317
+ kind: 'Heuristic',
318
+ where: {
319
+ ruleId: 'heuristics.ts.next-pages-api-route.ast',
320
+ },
321
+ },
322
+ then: {
323
+ kind: 'Finding',
324
+ message: 'AST heuristic detected a legacy Next.js pages/api default route handler; use app/api route handlers.',
325
+ code: 'HEURISTICS_NEXT_PAGES_API_ROUTE_AST',
326
+ },
327
+ },
166
328
  {
167
329
  id: 'heuristics.ts.with-statement.ast',
168
330
  description: 'Detects with-statement usage in TypeScript/TSX production files.',
@@ -4,6 +4,14 @@ This file tracks the active deterministic framework line used in this repository
4
4
  Canonical release chronology lives in `CHANGELOG.md`.
5
5
  This file keeps only the operational highlights and rollout notes that matter while running the framework.
6
6
 
7
+ ### 2026-05-18 (v6.3.272)
8
+
9
+ - `PUMUKI-INC-142`: `sdd validate --stage=PRE_WRITE` refreshes evidence with PRE_WRITE semantics before writing the validated-diff lease, avoiding the PRE_COMMIT/PRE_PUSH bootstrap loop that still produced `ENFORCEMENT_GAP_PRE_WRITE_LEASE_MISSING` in real RuralGo commits.
10
+
11
+ ### 2026-05-18 (v6.3.271)
12
+
13
+ - Published `pumuki@6.3.271` with `PUMUKI-INC-142` lease parity: PRE_WRITE validated staged slices now create a `validated-diff` lease consumed by PRE_COMMIT/PRE_PUSH when the diff is unchanged, while changed slices still fail closed.
14
+
7
15
  ## 2026-04 (CLI stability and macOS notifications)
8
16
 
9
17
  ### 2026-05-14 (v6.3.267)