pumuki 6.3.307 → 6.3.308
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 +6 -0
- package/core/facts/detectors/text/ios.test.ts +459 -0
- package/core/facts/detectors/text/ios.ts +484 -0
- package/core/facts/extractHeuristicFacts.ts +17 -4
- package/core/rules/presets/heuristics/ios.test.ts +58 -1
- package/core/rules/presets/heuristics/ios.ts +239 -0
- package/integrations/config/skillsDetectorRegistry.ts +94 -0
- package/integrations/evidence/buildEvidence.ts +46 -0
- package/integrations/lifecycle/adapter.templates.json +50 -1
- package/integrations/lifecycle/doctor.ts +41 -5
- package/integrations/lifecycle/preWriteLease.ts +5 -1
- package/package.json +1 -1
- package/scripts/framework-menu-matrix-canary-evidence.ts +42 -1
- package/scripts/framework-menu-matrix-canary-lib.ts +25 -2
- package/scripts/framework-menu-matrix-canary-scenario.ts +3 -1
- package/scripts/framework-menu-matrix-canary-types.ts +6 -1
- package/scripts/framework-menu-system-notifications-macos-dialog-payload.ts +2 -1
- package/scripts/framework-menu-system-notifications-payloads-blocked.ts +76 -2
- package/scripts/framework-menu-system-notifications-payloads.ts +1 -0
- package/skills.lock.json +1 -1
|
@@ -69,6 +69,11 @@ const registryByRuleId: Record<string, SkillsDetectorBinding> = {
|
|
|
69
69
|
'heuristics.ios.dispatchqueue.ast',
|
|
70
70
|
'heuristics.ios.dispatchsemaphore.ast',
|
|
71
71
|
]),
|
|
72
|
+
'skills.ios.guideline.ios.sincronizacio-n-nativa-actors-para-estado-compartido-osallocatedunfair':
|
|
73
|
+
heuristicDetector('ios.concurrency.shared-state-actor-boundary', [
|
|
74
|
+
'heuristics.ios.dispatchqueue.ast',
|
|
75
|
+
'heuristics.ios.dispatchsemaphore.ast',
|
|
76
|
+
]),
|
|
72
77
|
'skills.ios.no-dispatchgroup': heuristicDetector('ios.dispatchgroup', [
|
|
73
78
|
'heuristics.ios.dispatchgroup.ast',
|
|
74
79
|
]),
|
|
@@ -81,6 +86,10 @@ const registryByRuleId: Record<string, SkillsDetectorBinding> = {
|
|
|
81
86
|
'skills.ios.no-task-detached': heuristicDetector('ios.task-detached', [
|
|
82
87
|
'heuristics.ios.task-detached.ast',
|
|
83
88
|
]),
|
|
89
|
+
'skills.ios.guideline.ios.cancelar-correctamente-comprobar-task-iscancelled-en-operaciones-larga':
|
|
90
|
+
heuristicDetector('ios.concurrency.long-task-cancellation-check', [
|
|
91
|
+
'heuristics.ios.concurrency.long-task-without-cancellation-check.ast',
|
|
92
|
+
]),
|
|
84
93
|
'skills.ios.no-async-without-await': heuristicDetector('ios.concurrency.async-without-await', [
|
|
85
94
|
'heuristics.ios.concurrency.async-without-await.ast',
|
|
86
95
|
]),
|
|
@@ -88,6 +97,10 @@ const registryByRuleId: Record<string, SkillsDetectorBinding> = {
|
|
|
88
97
|
heuristicDetector('ios.concurrency.avoid-overuse-async-without-await', [
|
|
89
98
|
'heuristics.ios.concurrency.async-without-await.ast',
|
|
90
99
|
]),
|
|
100
|
+
'skills.ios.guideline.ios-concurrency.use-references-linting-md-for-rule-intent-and-preferred-fixes-avoid-du':
|
|
101
|
+
heuristicDetector('ios.concurrency.dummy-await', [
|
|
102
|
+
'heuristics.ios.concurrency.dummy-await.ast',
|
|
103
|
+
]),
|
|
91
104
|
'skills.ios.guideline.ios.delegation-pattern-weak-delegates-para-evitar-retain-cycles': heuristicDetector(
|
|
92
105
|
'ios.memory.strong-delegate',
|
|
93
106
|
['heuristics.ios.memory.strong-delegate.ast']
|
|
@@ -212,6 +225,10 @@ const registryByRuleId: Record<string, SkillsDetectorBinding> = {
|
|
|
212
225
|
'ios.error.typed-error-enum',
|
|
213
226
|
['heuristics.ios.error.nserror-throw.ast']
|
|
214
227
|
),
|
|
228
|
+
'skills.ios.guideline.ios.apiendpoint-como-struct-data-driven-ocp-endpoints-en-features-no-enum-':
|
|
229
|
+
heuristicDetector('ios.networking.endpoint-enum-ocp', [
|
|
230
|
+
'heuristics.ios.networking.endpoint-enum-ocp.ast',
|
|
231
|
+
]),
|
|
215
232
|
'skills.ios.guideline.ios.no-loggear-pii-tokens-emails-ids-sensibles': heuristicDetector(
|
|
216
233
|
'ios.logging.sensitive-data',
|
|
217
234
|
['heuristics.ios.logging.sensitive-data.ast']
|
|
@@ -255,6 +272,7 @@ const registryByRuleId: Record<string, SkillsDetectorBinding> = {
|
|
|
255
272
|
'heuristics.ios.dependencies.carthage.ast',
|
|
256
273
|
'heuristics.ios.architecture.swinject.ast',
|
|
257
274
|
'heuristics.ios.architecture.tca-composable-architecture.ast',
|
|
275
|
+
'heuristics.ios.testing.third-party-ui-test-framework.ast',
|
|
258
276
|
]),
|
|
259
277
|
'skills.ios.guideline.ios.libreri-as-de-terceros-usar-apis-nativas': heuristicDetector(
|
|
260
278
|
'ios.dependencies.third-party-native-baseline',
|
|
@@ -264,6 +282,7 @@ const registryByRuleId: Record<string, SkillsDetectorBinding> = {
|
|
|
264
282
|
'heuristics.ios.dependencies.carthage.ast',
|
|
265
283
|
'heuristics.ios.architecture.swinject.ast',
|
|
266
284
|
'heuristics.ios.architecture.tca-composable-architecture.ast',
|
|
285
|
+
'heuristics.ios.testing.third-party-ui-test-framework.ast',
|
|
267
286
|
]
|
|
268
287
|
),
|
|
269
288
|
'skills.ios.guideline.ios.keychain-passwords-tokens-no-userdefaults': heuristicDetector(
|
|
@@ -330,6 +349,10 @@ const registryByRuleId: Record<string, SkillsDetectorBinding> = {
|
|
|
330
349
|
'ios.performance.blocking-sleep',
|
|
331
350
|
['heuristics.ios.performance.blocking-sleep.ast']
|
|
332
351
|
),
|
|
352
|
+
'skills.ios.guideline.ios-concurrency.use-references-threading-md-to-avoid-thread-centric-debugging-and-rely':
|
|
353
|
+
heuristicDetector('ios.concurrency.thread-centric-debugging', [
|
|
354
|
+
'heuristics.ios.concurrency.thread-centric-debugging.ast',
|
|
355
|
+
]),
|
|
333
356
|
'skills.ios.guideline.ios.structured-concurrency-task-taskgroup-actor-asyncsequence-asyncstream':
|
|
334
357
|
heuristicDetector('ios.concurrency.structured-baseline', [
|
|
335
358
|
'heuristics.ios.callback-style.ast',
|
|
@@ -375,6 +398,21 @@ const registryByRuleId: Record<string, SkillsDetectorBinding> = {
|
|
|
375
398
|
'ios.accessibility.on-tap-without-button-trait',
|
|
376
399
|
['heuristics.ios.accessibility.on-tap-without-button-trait.ast']
|
|
377
400
|
),
|
|
401
|
+
'skills.ios.guideline.ios.voiceover-labels-hints-traits': heuristicDetector(
|
|
402
|
+
'ios.accessibility.voiceover-labels-traits',
|
|
403
|
+
[
|
|
404
|
+
'heuristics.ios.accessibility.icon-only-control-label.ast',
|
|
405
|
+
'heuristics.ios.accessibility.on-tap-without-button-trait.ast',
|
|
406
|
+
]
|
|
407
|
+
),
|
|
408
|
+
'skills.ios.guideline.ios-swiftui-expert.use-interactive-only-for-tappable-focusable-elements':
|
|
409
|
+
heuristicDetector('ios.swiftui.glass-interactive-static-element', [
|
|
410
|
+
'heuristics.ios.swiftui.glass-interactive-static-element.ast',
|
|
411
|
+
]),
|
|
412
|
+
'skills.ios.guideline.ios-swiftui-expert.use-glasseffectid-with-namespace-for-morphing-transitions':
|
|
413
|
+
heuristicDetector('ios.swiftui.glasseffectid-namespace', [
|
|
414
|
+
'heuristics.ios.swiftui.glasseffectid-without-namespace.ast',
|
|
415
|
+
]),
|
|
378
416
|
'skills.ios.guideline.ios.image-optimization-resize-compress-cache': heuristicDetector(
|
|
379
417
|
'ios.swiftui.image-data-decoding',
|
|
380
418
|
['heuristics.ios.swiftui.image-data-decoding.ast']
|
|
@@ -415,10 +453,30 @@ const registryByRuleId: Record<string, SkillsDetectorBinding> = {
|
|
|
415
453
|
heuristicDetector('ios.dependencies.swift-tools-version', [
|
|
416
454
|
'heuristics.ios.dependencies.swift-tools-version-below-6-2.ast',
|
|
417
455
|
]),
|
|
456
|
+
'skills.ios.guideline.ios-concurrency.use-read-on-package-swift-for-swiftpm-settings-tools-version-strict-co':
|
|
457
|
+
heuristicDetector('ios.dependencies.swift-tools-version', [
|
|
458
|
+
'heuristics.ios.dependencies.swift-tools-version-below-6-2.ast',
|
|
459
|
+
'heuristics.ios.concurrency.swiftpm-default-isolation-not-mainactor.ast',
|
|
460
|
+
'heuristics.ios.concurrency.swiftpm-strict-concurrency-below-complete.ast',
|
|
461
|
+
]),
|
|
418
462
|
'skills.ios.guideline.ios.strict-concurrency-checking-activar-en-complete':
|
|
419
463
|
heuristicDetector('ios.concurrency.strict-concurrency-below-complete', [
|
|
420
464
|
'heuristics.ios.concurrency.strict-concurrency-below-complete.ast',
|
|
421
465
|
]),
|
|
466
|
+
'skills.ios.guideline.ios-concurrency.use-grep-for-swiftstrictconcurrency-or-swiftdefaultactorisolation-in-p':
|
|
467
|
+
heuristicDetector('ios.concurrency.project-settings', [
|
|
468
|
+
'heuristics.ios.concurrency.strict-concurrency-below-complete.ast',
|
|
469
|
+
'heuristics.ios.concurrency.default-actor-isolation-not-mainactor.ast',
|
|
470
|
+
'heuristics.ios.concurrency.swiftpm-default-isolation-not-mainactor.ast',
|
|
471
|
+
'heuristics.ios.concurrency.swiftpm-strict-concurrency-below-complete.ast',
|
|
472
|
+
]),
|
|
473
|
+
'skills.ios.guideline.ios.validar-configuracio-n-de-concurrencia-swiftstrictconcurrency-swiftdef':
|
|
474
|
+
heuristicDetector('ios.concurrency.project-settings', [
|
|
475
|
+
'heuristics.ios.concurrency.strict-concurrency-below-complete.ast',
|
|
476
|
+
'heuristics.ios.concurrency.default-actor-isolation-not-mainactor.ast',
|
|
477
|
+
'heuristics.ios.concurrency.swiftpm-default-isolation-not-mainactor.ast',
|
|
478
|
+
'heuristics.ios.concurrency.swiftpm-strict-concurrency-below-complete.ast',
|
|
479
|
+
]),
|
|
422
480
|
'skills.ios.guideline.ios.file-naming-pascalcase-para-tipos': heuristicDetector(
|
|
423
481
|
'ios.naming.non-pascal-case-type',
|
|
424
482
|
['heuristics.ios.naming.non-pascal-case-type.ast']
|
|
@@ -646,6 +704,10 @@ const registryByRuleId: Record<string, SkillsDetectorBinding> = {
|
|
|
646
704
|
heuristicDetector('ios.swiftui.large-viewbuilder-function', [
|
|
647
705
|
'heuristics.ios.swiftui.large-viewbuilder-function.ast',
|
|
648
706
|
]),
|
|
707
|
+
'skills.ios.guideline.ios.composicio-n-de-views-views-pequen-os-reutilizables':
|
|
708
|
+
heuristicDetector('ios.swiftui.large-viewbuilder-function', [
|
|
709
|
+
'heuristics.ios.swiftui.large-viewbuilder-function.ast',
|
|
710
|
+
]),
|
|
649
711
|
'skills.ios.guideline.ios-swiftui-expert.pass-only-needed-values-to-views-avoid-large-config-or-context-objects':
|
|
650
712
|
heuristicDetector('ios.swiftui.large-config-context-prop', [
|
|
651
713
|
'heuristics.ios.swiftui.large-config-context-prop.ast',
|
|
@@ -775,6 +837,10 @@ const registryByRuleId: Record<string, SkillsDetectorBinding> = {
|
|
|
775
837
|
'ios.testing.quick-nimble',
|
|
776
838
|
['heuristics.ios.testing.quick-nimble.ast']
|
|
777
839
|
),
|
|
840
|
+
'skills.ios.guideline.ios.xcuitest-ui-testing-nativo': heuristicDetector(
|
|
841
|
+
'ios.testing.third-party-ui-test-framework',
|
|
842
|
+
['heuristics.ios.testing.third-party-ui-test-framework.ast']
|
|
843
|
+
),
|
|
778
844
|
'skills.ios.no-nsmanagedobject-boundary': heuristicDetector(
|
|
779
845
|
'ios.core-data.nsmanagedobject-boundary',
|
|
780
846
|
['heuristics.ios.core-data.nsmanagedobject-boundary.ast']
|
|
@@ -1150,6 +1216,34 @@ const registryByRuleId: Record<string, SkillsDetectorBinding> = {
|
|
|
1150
1216
|
'heuristics.ios.solid.lsp.narrowed-precondition.ast',
|
|
1151
1217
|
]
|
|
1152
1218
|
),
|
|
1219
|
+
'skills.ios.guideline.ios.feature-first-cada-feature-es-un-bounded-context-no-se-acoplan-entre-s':
|
|
1220
|
+
heuristicDetector('ios.architecture.cross-feature-import', [
|
|
1221
|
+
'heuristics.ios.architecture.cross-feature-import.ast',
|
|
1222
|
+
]),
|
|
1223
|
+
'skills.ios.guideline.ios.las-features-no-se-importan-entre-si':
|
|
1224
|
+
heuristicDetector('ios.architecture.cross-feature-import', [
|
|
1225
|
+
'heuristics.ios.architecture.cross-feature-import.ast',
|
|
1226
|
+
]),
|
|
1227
|
+
'skills.ios.guideline.ios.shared-kernel-solo-tipos-mi-nimos-compartidos-entre-features':
|
|
1228
|
+
heuristicDetector('ios.architecture.cross-feature-import', [
|
|
1229
|
+
'heuristics.ios.architecture.cross-feature-import.ast',
|
|
1230
|
+
]),
|
|
1231
|
+
'skills.ios.guideline.ios.clean-por-feature-presentation-application-domain-infrastructure-domai':
|
|
1232
|
+
heuristicDetector('ios.architecture.layer-direction', [
|
|
1233
|
+
'heuristics.ios.architecture.layer-direction-violation.ast',
|
|
1234
|
+
]),
|
|
1235
|
+
'skills.ios.guideline.ios.seguir-clean-architecture-domain-application-infrastructure-presentati':
|
|
1236
|
+
heuristicDetector('ios.architecture.layer-direction', [
|
|
1237
|
+
'heuristics.ios.architecture.layer-direction-violation.ast',
|
|
1238
|
+
]),
|
|
1239
|
+
'skills.ios.guideline.ios.ddd-entidades-value-objects-repositorios-protocolos-eventos-de-dominio':
|
|
1240
|
+
heuristicDetector('ios.architecture.layer-direction', [
|
|
1241
|
+
'heuristics.ios.architecture.layer-direction-violation.ast',
|
|
1242
|
+
]),
|
|
1243
|
+
'skills.ios.guideline.ios.public-api-solo-exponer-lo-necesario-public-internal-private':
|
|
1244
|
+
heuristicDetector('ios.architecture.excessive-public-api', [
|
|
1245
|
+
'heuristics.ios.architecture.excessive-public-api.ast',
|
|
1246
|
+
]),
|
|
1153
1247
|
'skills.ios.guideline.ios.testability-inyectar-protocols-no-tipos-concretos':
|
|
1154
1248
|
heuristicDetector('ios.testability.protocol-boundaries', [
|
|
1155
1249
|
'heuristics.ios.solid.dip.concrete-framework-dependency.ast',
|
|
@@ -271,7 +271,49 @@ const equivalentRuleFamilies: ReadonlyArray<ReadonlyArray<string>> = [
|
|
|
271
271
|
['skills.ios.no-task-detached', 'heuristics.ios.task-detached.ast'],
|
|
272
272
|
['skills.ios.no-unchecked-sendable', 'heuristics.ios.unchecked-sendable.ast'],
|
|
273
273
|
['skills.ios.no-preconcurrency', 'heuristics.ios.preconcurrency.ast'],
|
|
274
|
+
[
|
|
275
|
+
'skills.ios.guideline.ios-concurrency.use-grep-for-swiftstrictconcurrency-or-swiftdefaultactorisolation-in-p',
|
|
276
|
+
'heuristics.ios.concurrency.strict-concurrency-below-complete.ast',
|
|
277
|
+
'heuristics.ios.concurrency.default-actor-isolation-not-mainactor.ast',
|
|
278
|
+
'heuristics.ios.concurrency.swiftpm-default-isolation-not-mainactor.ast',
|
|
279
|
+
'heuristics.ios.concurrency.swiftpm-strict-concurrency-below-complete.ast',
|
|
280
|
+
],
|
|
281
|
+
[
|
|
282
|
+
'skills.ios.guideline.ios.validar-configuracio-n-de-concurrencia-swiftstrictconcurrency-swiftdef',
|
|
283
|
+
'heuristics.ios.concurrency.strict-concurrency-below-complete.ast',
|
|
284
|
+
'heuristics.ios.concurrency.default-actor-isolation-not-mainactor.ast',
|
|
285
|
+
'heuristics.ios.concurrency.swiftpm-default-isolation-not-mainactor.ast',
|
|
286
|
+
'heuristics.ios.concurrency.swiftpm-strict-concurrency-below-complete.ast',
|
|
287
|
+
],
|
|
274
288
|
['skills.ios.no-nonisolated-unsafe', 'heuristics.ios.nonisolated-unsafe.ast'],
|
|
289
|
+
[
|
|
290
|
+
'skills.ios.guideline.ios.feature-first-cada-feature-es-un-bounded-context-no-se-acoplan-entre-s',
|
|
291
|
+
'heuristics.ios.architecture.cross-feature-import.ast',
|
|
292
|
+
],
|
|
293
|
+
[
|
|
294
|
+
'skills.ios.guideline.ios.las-features-no-se-importan-entre-si',
|
|
295
|
+
'heuristics.ios.architecture.cross-feature-import.ast',
|
|
296
|
+
],
|
|
297
|
+
[
|
|
298
|
+
'skills.ios.guideline.ios.shared-kernel-solo-tipos-mi-nimos-compartidos-entre-features',
|
|
299
|
+
'heuristics.ios.architecture.cross-feature-import.ast',
|
|
300
|
+
],
|
|
301
|
+
[
|
|
302
|
+
'skills.ios.guideline.ios.clean-por-feature-presentation-application-domain-infrastructure-domai',
|
|
303
|
+
'heuristics.ios.architecture.layer-direction-violation.ast',
|
|
304
|
+
],
|
|
305
|
+
[
|
|
306
|
+
'skills.ios.guideline.ios.seguir-clean-architecture-domain-application-infrastructure-presentati',
|
|
307
|
+
'heuristics.ios.architecture.layer-direction-violation.ast',
|
|
308
|
+
],
|
|
309
|
+
[
|
|
310
|
+
'skills.ios.guideline.ios.ddd-entidades-value-objects-repositorios-protocolos-eventos-de-dominio',
|
|
311
|
+
'heuristics.ios.architecture.layer-direction-violation.ast',
|
|
312
|
+
],
|
|
313
|
+
[
|
|
314
|
+
'skills.ios.guideline.ios.public-api-solo-exponer-lo-necesario-public-internal-private',
|
|
315
|
+
'heuristics.ios.architecture.excessive-public-api.ast',
|
|
316
|
+
],
|
|
275
317
|
['skills.ios.no-assume-isolated', 'heuristics.ios.assume-isolated.ast'],
|
|
276
318
|
['skills.ios.no-observable-object', 'heuristics.ios.observable-object.ast'],
|
|
277
319
|
[
|
|
@@ -312,6 +354,10 @@ const equivalentRuleFamilies: ReadonlyArray<ReadonlyArray<string>> = [
|
|
|
312
354
|
'heuristics.ios.testing.legacy-expectation-description.ast',
|
|
313
355
|
],
|
|
314
356
|
['skills.ios.no-mixed-testing-frameworks', 'heuristics.ios.testing.mixed-frameworks.ast'],
|
|
357
|
+
[
|
|
358
|
+
'skills.ios.guideline.ios.xcuitest-ui-testing-nativo',
|
|
359
|
+
'heuristics.ios.testing.third-party-ui-test-framework.ast',
|
|
360
|
+
],
|
|
315
361
|
[
|
|
316
362
|
'skills.ios.no-nsmanagedobject-boundary',
|
|
317
363
|
'heuristics.ios.core-data.nsmanagedobject-boundary.ast',
|
|
@@ -24,6 +24,13 @@
|
|
|
24
24
|
"evidence": {
|
|
25
25
|
"command": "npx --yes --package pumuki@latest pumuki-mcp-evidence-stdio"
|
|
26
26
|
}
|
|
27
|
+
},
|
|
28
|
+
"enforcement": {
|
|
29
|
+
"pre_write": {
|
|
30
|
+
"required_before_edit": true,
|
|
31
|
+
"command": "npx --yes --package pumuki@latest pumuki-pre-write",
|
|
32
|
+
"agent_instruction": "STOP before editing files unless this command has produced a valid PRE_WRITE lease for the current repo and diff."
|
|
33
|
+
}
|
|
27
34
|
}
|
|
28
35
|
}
|
|
29
36
|
}
|
|
@@ -54,6 +61,13 @@
|
|
|
54
61
|
"evidence": {
|
|
55
62
|
"command": "npx --yes --package pumuki@latest pumuki-mcp-evidence-stdio"
|
|
56
63
|
}
|
|
64
|
+
},
|
|
65
|
+
"enforcement": {
|
|
66
|
+
"pre_write": {
|
|
67
|
+
"required_before_edit": true,
|
|
68
|
+
"command": "npx --yes --package pumuki@latest pumuki-pre-write",
|
|
69
|
+
"agent_instruction": "STOP before editing files unless this command has produced a valid PRE_WRITE lease for the current repo and diff."
|
|
70
|
+
}
|
|
57
71
|
}
|
|
58
72
|
}
|
|
59
73
|
}
|
|
@@ -76,6 +90,13 @@
|
|
|
76
90
|
"ci": {
|
|
77
91
|
"command": "npx --yes --package pumuki@latest pumuki-ci"
|
|
78
92
|
}
|
|
93
|
+
},
|
|
94
|
+
"enforcement": {
|
|
95
|
+
"pre_write": {
|
|
96
|
+
"required_before_edit": true,
|
|
97
|
+
"command": "npx --yes --package pumuki@latest pumuki-pre-write",
|
|
98
|
+
"agent_instruction": "STOP before editing files unless this command has produced a valid PRE_WRITE lease for the current repo and diff."
|
|
99
|
+
}
|
|
79
100
|
}
|
|
80
101
|
},
|
|
81
102
|
"mcpServers": {
|
|
@@ -139,6 +160,13 @@
|
|
|
139
160
|
"ci": {
|
|
140
161
|
"command": "npx --yes --package pumuki@latest pumuki-ci"
|
|
141
162
|
}
|
|
163
|
+
},
|
|
164
|
+
"enforcement": {
|
|
165
|
+
"pre_write": {
|
|
166
|
+
"required_before_edit": true,
|
|
167
|
+
"command": "npx --yes --package pumuki@latest pumuki-pre-write",
|
|
168
|
+
"agent_instruction": "STOP before editing files unless this command has produced a valid PRE_WRITE lease for the current repo and diff."
|
|
169
|
+
}
|
|
142
170
|
}
|
|
143
171
|
}
|
|
144
172
|
},
|
|
@@ -166,6 +194,13 @@
|
|
|
166
194
|
"evidence": {
|
|
167
195
|
"command": "npx --yes --package pumuki@latest pumuki-mcp-evidence-stdio"
|
|
168
196
|
}
|
|
197
|
+
},
|
|
198
|
+
"enforcement": {
|
|
199
|
+
"pre_write": {
|
|
200
|
+
"required_before_edit": true,
|
|
201
|
+
"command": "npx --yes --package pumuki@latest pumuki-pre-write",
|
|
202
|
+
"agent_instruction": "STOP before editing files unless this command has produced a valid PRE_WRITE lease for the current repo and diff."
|
|
203
|
+
}
|
|
169
204
|
}
|
|
170
205
|
}
|
|
171
206
|
}
|
|
@@ -178,7 +213,14 @@
|
|
|
178
213
|
"postCommand": "npx --yes --package pumuki@latest pumuki-pre-commit",
|
|
179
214
|
"pushCommand": "npx --yes --package pumuki@latest pumuki-pre-push",
|
|
180
215
|
"ciCommand": "npx --yes --package pumuki@latest pumuki-ci",
|
|
181
|
-
"mcpCommand": "npx --yes --package pumuki@latest pumuki-mcp-enterprise-stdio"
|
|
216
|
+
"mcpCommand": "npx --yes --package pumuki@latest pumuki-mcp-enterprise-stdio",
|
|
217
|
+
"enforcement": {
|
|
218
|
+
"pre_write": {
|
|
219
|
+
"required_before_edit": true,
|
|
220
|
+
"command": "npx --yes --package pumuki@latest pumuki-pre-write",
|
|
221
|
+
"agent_instruction": "STOP before editing files unless this command has produced a valid PRE_WRITE lease for the current repo and diff."
|
|
222
|
+
}
|
|
223
|
+
}
|
|
182
224
|
}
|
|
183
225
|
},
|
|
184
226
|
{
|
|
@@ -246,6 +288,13 @@
|
|
|
246
288
|
"evidence": {
|
|
247
289
|
"command": "npx --yes --package pumuki@latest pumuki-mcp-evidence-stdio"
|
|
248
290
|
}
|
|
291
|
+
},
|
|
292
|
+
"enforcement": {
|
|
293
|
+
"pre_write": {
|
|
294
|
+
"required_before_edit": true,
|
|
295
|
+
"command": "npx --yes --package pumuki@latest pumuki-pre-write",
|
|
296
|
+
"agent_instruction": "STOP before editing files unless this command has produced a valid PRE_WRITE lease for the current repo and diff."
|
|
297
|
+
}
|
|
249
298
|
}
|
|
250
299
|
}
|
|
251
300
|
}
|
|
@@ -269,6 +269,20 @@ const readNestedString = (
|
|
|
269
269
|
return typeof cursor === 'string' && cursor.trim().length > 0 ? cursor : undefined;
|
|
270
270
|
};
|
|
271
271
|
|
|
272
|
+
const readNestedBoolean = (
|
|
273
|
+
source: Record<string, unknown>,
|
|
274
|
+
path: ReadonlyArray<string>
|
|
275
|
+
): boolean | undefined => {
|
|
276
|
+
let cursor: unknown = source;
|
|
277
|
+
for (const segment of path) {
|
|
278
|
+
if (!isRecord(cursor)) {
|
|
279
|
+
return undefined;
|
|
280
|
+
}
|
|
281
|
+
cursor = cursor[segment];
|
|
282
|
+
}
|
|
283
|
+
return typeof cursor === 'boolean' ? cursor : undefined;
|
|
284
|
+
};
|
|
285
|
+
|
|
272
286
|
const hasRobustPumukiCommandResolution = (command: string): boolean => {
|
|
273
287
|
const normalized = command.trim();
|
|
274
288
|
if (normalized.length === 0) {
|
|
@@ -396,17 +410,26 @@ const evaluateAdapterWiringCheck = (repoRoot: string): DoctorDeepCheck => {
|
|
|
396
410
|
const missingPaths = requiredCommandPaths
|
|
397
411
|
.filter((path) => !readNestedString(parsed, path))
|
|
398
412
|
.map((path) => path.join('.'));
|
|
399
|
-
|
|
400
|
-
|
|
413
|
+
const missingContractPaths = [
|
|
414
|
+
!readNestedBoolean(parsed, ['enforcement', 'pre_write', 'required_before_edit'])
|
|
415
|
+
? 'enforcement.pre_write.required_before_edit'
|
|
416
|
+
: undefined,
|
|
417
|
+
!readNestedString(parsed, ['enforcement', 'pre_write', 'agent_instruction'])
|
|
418
|
+
? 'enforcement.pre_write.agent_instruction'
|
|
419
|
+
: undefined,
|
|
420
|
+
].filter((path): path is string => typeof path === 'string');
|
|
421
|
+
|
|
422
|
+
if (missingPaths.length > 0 || missingContractPaths.length > 0) {
|
|
423
|
+
const missing = [...missingPaths, ...missingContractPaths];
|
|
401
424
|
return buildDeepCheck({
|
|
402
425
|
id: 'adapter-wiring',
|
|
403
426
|
status: 'fail',
|
|
404
427
|
severity: 'error',
|
|
405
|
-
message: `Adapter wiring is incomplete (missing: ${
|
|
428
|
+
message: `Adapter wiring is incomplete (missing: ${missing.join(', ')}).`,
|
|
406
429
|
remediation: 'Re-run "pumuki adapter install --agent=codex" and keep generated commands unchanged.',
|
|
407
430
|
metadata: {
|
|
408
431
|
path: adapterPath,
|
|
409
|
-
missing_count:
|
|
432
|
+
missing_count: missing.length,
|
|
410
433
|
},
|
|
411
434
|
});
|
|
412
435
|
}
|
|
@@ -445,13 +468,26 @@ const evaluateAdapterWiringCheck = (repoRoot: string): DoctorDeepCheck => {
|
|
|
445
468
|
});
|
|
446
469
|
}
|
|
447
470
|
|
|
471
|
+
const preWriteEnforcementCommand =
|
|
472
|
+
readNestedString(parsed, ['enforcement', 'pre_write', 'command']) ??
|
|
473
|
+
readNestedString(parsed, ['hooks', 'pre_write', 'command']);
|
|
474
|
+
const preWriteAgentInstruction = readNestedString(parsed, [
|
|
475
|
+
'enforcement',
|
|
476
|
+
'pre_write',
|
|
477
|
+
'agent_instruction',
|
|
478
|
+
]);
|
|
479
|
+
|
|
448
480
|
return buildDeepCheck({
|
|
449
481
|
id: 'adapter-wiring',
|
|
450
482
|
status: 'pass',
|
|
451
483
|
severity: 'info',
|
|
452
|
-
message:
|
|
484
|
+
message:
|
|
485
|
+
'Adapter wiring contract is valid; PRE_WRITE hard-stop is required before editing.',
|
|
453
486
|
metadata: {
|
|
454
487
|
path: adapterPath,
|
|
488
|
+
pre_write_required_before_edit: true,
|
|
489
|
+
pre_write_command: preWriteEnforcementCommand ?? null,
|
|
490
|
+
pre_write_agent_instruction: preWriteAgentInstruction ?? null,
|
|
455
491
|
},
|
|
456
492
|
});
|
|
457
493
|
};
|
|
@@ -300,7 +300,11 @@ export const toPreWriteEnforcementGapFinding = (params: {
|
|
|
300
300
|
code: 'ENFORCEMENT_GAP_PRE_WRITE_LEASE_MISSING',
|
|
301
301
|
message:
|
|
302
302
|
`PRE_WRITE hard-stop lease is not valid at ${params.stage}: ${params.status.code}. ` +
|
|
303
|
-
'
|
|
303
|
+
'User action: run `npx pumuki-pre-write` before editing code, then rerun the gate. ' +
|
|
304
|
+
'AI agent instruction: STOP; do not edit files until a valid PRE_WRITE lease exists for this repo and diff.',
|
|
305
|
+
expected_fix:
|
|
306
|
+
'User: run `npx pumuki-pre-write` or `npx pumuki sdd validate --stage=PRE_WRITE --json` before editing. ' +
|
|
307
|
+
'AI agent: stop immediately, do not apply patches or write files, and continue only after the command produces a valid clean or validated-diff PRE_WRITE lease.',
|
|
304
308
|
filePath: '.pumuki/prewrite-lease.json',
|
|
305
309
|
matchedBy: 'PreWriteLeaseGuard',
|
|
306
310
|
source: 'prewrite-lease',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pumuki",
|
|
3
|
-
"version": "6.3.
|
|
3
|
+
"version": "6.3.308",
|
|
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": {
|
|
@@ -1,9 +1,30 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
|
|
4
|
-
export
|
|
4
|
+
export type CanaryEvidenceRuleMatch = {
|
|
5
|
+
ruleId: string;
|
|
6
|
+
filePath: string;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
const normalizeEvidencePath = (value: string): string => value.replaceAll('\\', '/');
|
|
10
|
+
|
|
11
|
+
const readEvidenceSnapshotFindings = (repoRoot: string): Array<Record<string, unknown>> => {
|
|
5
12
|
const evidencePath = join(repoRoot, '.ai_evidence.json');
|
|
6
13
|
try {
|
|
14
|
+
const parsed = JSON.parse(readFileSync(evidencePath, 'utf8')) as {
|
|
15
|
+
snapshot?: {
|
|
16
|
+
findings?: Array<Record<string, unknown>>;
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
return Array.isArray(parsed?.snapshot?.findings) ? parsed.snapshot.findings : [];
|
|
20
|
+
} catch {
|
|
21
|
+
return [];
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export const extractRuleIdsFromEvidence = (repoRoot: string): string[] => {
|
|
26
|
+
try {
|
|
27
|
+
const evidencePath = join(repoRoot, '.ai_evidence.json');
|
|
7
28
|
const parsed = JSON.parse(readFileSync(evidencePath, 'utf8')) as {
|
|
8
29
|
snapshot?: {
|
|
9
30
|
findings?: Array<{ ruleId?: unknown }>;
|
|
@@ -32,3 +53,23 @@ export const extractRuleIdsFromEvidence = (repoRoot: string): string[] => {
|
|
|
32
53
|
return [];
|
|
33
54
|
}
|
|
34
55
|
};
|
|
56
|
+
|
|
57
|
+
export const extractRuleMatchesFromEvidence = (repoRoot: string): CanaryEvidenceRuleMatch[] => {
|
|
58
|
+
return readEvidenceSnapshotFindings(repoRoot)
|
|
59
|
+
.map((finding) => {
|
|
60
|
+
const ruleId = typeof finding.ruleId === 'string' ? finding.ruleId : '';
|
|
61
|
+
const filePath =
|
|
62
|
+
typeof finding.filePath === 'string'
|
|
63
|
+
? finding.filePath
|
|
64
|
+
: typeof finding.file === 'string'
|
|
65
|
+
? finding.file
|
|
66
|
+
: typeof finding.path === 'string'
|
|
67
|
+
? finding.path
|
|
68
|
+
: '';
|
|
69
|
+
return {
|
|
70
|
+
ruleId,
|
|
71
|
+
filePath: normalizeEvidencePath(filePath),
|
|
72
|
+
};
|
|
73
|
+
})
|
|
74
|
+
.filter((match) => match.ruleId.length > 0 && match.filePath.length > 0);
|
|
75
|
+
};
|
|
@@ -11,7 +11,11 @@ import {
|
|
|
11
11
|
runWorkingTreePrePushGateSilent,
|
|
12
12
|
} from './framework-menu-gate-lib';
|
|
13
13
|
import { readMatrixOptionReport, type MatrixOptionId } from './framework-menu-matrix-evidence-lib';
|
|
14
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
extractRuleIdsFromEvidence,
|
|
16
|
+
extractRuleMatchesFromEvidence,
|
|
17
|
+
type CanaryEvidenceRuleMatch,
|
|
18
|
+
} from './framework-menu-matrix-canary-evidence';
|
|
15
19
|
import { resolveConsumerMenuCanaryScenario } from './framework-menu-matrix-canary-scenario';
|
|
16
20
|
import type {
|
|
17
21
|
ConsumerMenuCanaryPlatform,
|
|
@@ -102,6 +106,10 @@ export const runConsumerMenuCanary = async (params?: {
|
|
|
102
106
|
runGate?: (option: MatrixOptionId) => Promise<void>;
|
|
103
107
|
readOptionReport?: typeof readMatrixOptionReport;
|
|
104
108
|
extractRuleIds?: (repoRoot: string) => string[];
|
|
109
|
+
extractRuleMatches?: (
|
|
110
|
+
repoRoot: string,
|
|
111
|
+
scenario: ReturnType<typeof resolveConsumerMenuCanaryScenario>
|
|
112
|
+
) => CanaryEvidenceRuleMatch[];
|
|
105
113
|
};
|
|
106
114
|
}): Promise<ConsumerMenuCanaryResult> => {
|
|
107
115
|
const previousCwd = cwd();
|
|
@@ -116,6 +124,7 @@ export const runConsumerMenuCanary = async (params?: {
|
|
|
116
124
|
const runGate = dependencies?.runGate ?? runGateByOption;
|
|
117
125
|
const readOptionReport = dependencies?.readOptionReport ?? readMatrixOptionReport;
|
|
118
126
|
const extractRuleIds = dependencies?.extractRuleIds ?? extractRuleIdsFromEvidence;
|
|
127
|
+
const extractRuleMatches = dependencies?.extractRuleMatches ?? extractRuleMatchesFromEvidence;
|
|
119
128
|
|
|
120
129
|
chdir(repoRoot);
|
|
121
130
|
try {
|
|
@@ -132,11 +141,25 @@ export const runConsumerMenuCanary = async (params?: {
|
|
|
132
141
|
await runGate(scenario.option);
|
|
133
142
|
const optionReport = readOptionReport(repoRoot, scenario.option);
|
|
134
143
|
const ruleIds = extractRuleIds(repoRoot);
|
|
144
|
+
const expectedRuleIds = [scenario.expectedRuleId, ...(scenario.equivalentRuleIds ?? [])];
|
|
145
|
+
const ruleMatches = extractRuleMatches(repoRoot, scenario);
|
|
146
|
+
const canaryRuleMatched = ruleMatches.some((match) =>
|
|
147
|
+
expectedRuleIds.includes(match.ruleId)
|
|
148
|
+
&& match.filePath.replaceAll('\\', '/').endsWith(canaryRelativePath)
|
|
149
|
+
);
|
|
150
|
+
const blocked = optionReport.outcome === 'BLOCK' && optionReport.diagnosis === 'violations-detected';
|
|
135
151
|
|
|
136
152
|
return {
|
|
137
153
|
option: scenario.option,
|
|
138
154
|
detected:
|
|
139
|
-
|
|
155
|
+
blocked
|
|
156
|
+
&& optionReport.totalViolations > 0
|
|
157
|
+
&& expectedRuleIds.some((ruleId) => ruleIds.includes(ruleId))
|
|
158
|
+
&& canaryRuleMatched,
|
|
159
|
+
outcome: optionReport.outcome,
|
|
160
|
+
diagnosis: optionReport.diagnosis,
|
|
161
|
+
canaryRelativePath,
|
|
162
|
+
canaryRuleMatched,
|
|
140
163
|
totalViolations: optionReport.totalViolations,
|
|
141
164
|
filesScanned: optionReport.filesScanned,
|
|
142
165
|
ruleIds,
|
|
@@ -39,6 +39,7 @@ export const resolveConsumerMenuCanaryScenario = (params?: {
|
|
|
39
39
|
platform,
|
|
40
40
|
option: stage === 'PRE_COMMIT' ? '1' : '2',
|
|
41
41
|
expectedRuleId: 'skills.ios.no-force-unwrap',
|
|
42
|
+
equivalentRuleIds: ['ios.no-force-unwrap'],
|
|
42
43
|
canaryRelativePath: `apps/ios/App/__pumuki_matrix_canary_ios_${suffix}.swift`,
|
|
43
44
|
canarySource: [
|
|
44
45
|
'import Foundation',
|
|
@@ -78,11 +79,12 @@ export const resolveConsumerMenuCanaryScenario = (params?: {
|
|
|
78
79
|
platform: 'backend',
|
|
79
80
|
option: stage === 'PRE_COMMIT' ? '1' : '2',
|
|
80
81
|
expectedRuleId: 'skills.backend.no-empty-catch',
|
|
82
|
+
equivalentRuleIds: ['common.error.empty_catch', 'heuristics.ts.empty-catch.ast'],
|
|
81
83
|
canaryRelativePath: `apps/backend/src/__pumuki_matrix_canary_backend_${suffix}.ts`,
|
|
82
84
|
canarySource: [
|
|
83
85
|
'export const __pumukiMatrixCanaryBackend = (): void => {',
|
|
84
86
|
' try {',
|
|
85
|
-
|
|
87
|
+
' void 0;',
|
|
86
88
|
' } catch {}',
|
|
87
89
|
'};',
|
|
88
90
|
'',
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { MatrixOptionId } from './framework-menu-matrix-evidence-lib';
|
|
1
|
+
import type { MatrixOptionDiagnosis, MatrixOptionId } from './framework-menu-matrix-evidence-lib';
|
|
2
2
|
|
|
3
3
|
export type ConsumerMenuCanaryStage = 'PRE_COMMIT' | 'PRE_PUSH' | 'CI';
|
|
4
4
|
export type ConsumerMenuCanaryPlatform = 'ios' | 'android' | 'backend' | 'frontend';
|
|
@@ -6,6 +6,10 @@ export type ConsumerMenuCanaryPlatform = 'ios' | 'android' | 'backend' | 'fronte
|
|
|
6
6
|
export type ConsumerMenuCanaryResult = {
|
|
7
7
|
option: MatrixOptionId;
|
|
8
8
|
detected: boolean;
|
|
9
|
+
outcome: string;
|
|
10
|
+
diagnosis: MatrixOptionDiagnosis;
|
|
11
|
+
canaryRelativePath: string;
|
|
12
|
+
canaryRuleMatched: boolean;
|
|
9
13
|
totalViolations: number;
|
|
10
14
|
filesScanned: number;
|
|
11
15
|
ruleIds: string[];
|
|
@@ -16,6 +20,7 @@ export type ConsumerMenuCanaryScenario = {
|
|
|
16
20
|
platform: ConsumerMenuCanaryPlatform;
|
|
17
21
|
option: MatrixOptionId;
|
|
18
22
|
expectedRuleId: string;
|
|
23
|
+
equivalentRuleIds?: string[];
|
|
19
24
|
canaryRelativePath: string;
|
|
20
25
|
canarySource: string;
|
|
21
26
|
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { PumukiCriticalNotificationEvent } from './framework-menu-system-notifications-types';
|
|
2
2
|
import {
|
|
3
|
+
resolvePrioritizedBlockingCauses,
|
|
3
4
|
resolveBlockedCauseSummary,
|
|
4
5
|
resolveBlockedRemediation,
|
|
5
6
|
resolveProjectLabel,
|
|
@@ -25,7 +26,7 @@ const buildBlockingCausesDetails = (
|
|
|
25
26
|
const header = total === 1
|
|
26
27
|
? 'Causas bloqueantes: 1'
|
|
27
28
|
: `Causas bloqueantes: ${total}`;
|
|
28
|
-
return causes
|
|
29
|
+
return resolvePrioritizedBlockingCauses(causes)
|
|
29
30
|
.flatMap((cause, index) => formatBlockingCauseForDialog(cause, index))
|
|
30
31
|
.reduce((lines, line) => [...lines, line], [header])
|
|
31
32
|
.join('\n');
|