pumuki 6.3.296 → 6.3.297
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 +4 -0
- package/VERSION +1 -1
- package/core/facts/detectors/text/ios.test.ts +83 -0
- package/core/facts/detectors/text/ios.ts +24 -0
- package/core/facts/extractHeuristicFacts.ts +1 -0
- package/core/rules/presets/heuristics/ios.test.ts +6 -1
- package/core/rules/presets/heuristics/ios.ts +19 -0
- package/integrations/config/skillsDetectorRegistry.ts +4 -0
- package/integrations/gate/evaluateAiGate.ts +37 -7
- package/package.json +1 -1
- package/skills.lock.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [6.3.297] - 2026-05-19
|
|
4
|
+
|
|
5
|
+
- `PUMUKI-INC-157`: staged no-code/documentation/repin scopes no longer fail the skills contract with `EVIDENCE_SKILLS_PLATFORMS_UNDETECTED`. When the effective staged scope has no iOS, Android, backend or frontend code paths, platform skills are reported as `NOT_APPLICABLE` instead of blocking, while code-bearing slices keep the hard skills gate.
|
|
6
|
+
|
|
3
7
|
## [6.3.296] - 2026-05-19
|
|
4
8
|
|
|
5
9
|
- `PUMUKI-INC-156`: follow-up after RuralGo replay. Broad SwiftUI findings are now considered non-actionable even when they include a `primary_node` if that node spans many lines. Only a bounded AST/node range can hard-block a staged diff; file-level brownfield SwiftUI debt remains advisory for the atomic slice.
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
6.3.
|
|
1
|
+
6.3.297
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
findSwiftPresentationSrpMatch,
|
|
9
9
|
collectSwiftModernizableXCTestSuiteLines,
|
|
10
10
|
collectSwiftLegacyExpectationDescriptionLines,
|
|
11
|
+
collectSwiftMakeSUTWithoutMemoryTrackingLines,
|
|
11
12
|
collectSwiftMixedTestingFrameworkLines,
|
|
12
13
|
collectSwiftQuickNimbleLines,
|
|
13
14
|
collectSwiftWaitForExpectationsLines,
|
|
@@ -78,6 +79,7 @@ import {
|
|
|
78
79
|
hasSwiftEnvironmentObjectUsage,
|
|
79
80
|
hasSwiftLowContrastStaticColorPairUsage,
|
|
80
81
|
hasSwiftMainThreadBlockingSleepUsage,
|
|
82
|
+
hasSwiftMakeSUTWithoutMemoryTrackingUsage,
|
|
81
83
|
hasSwiftMassiveViewControllerResponsibilityUsage,
|
|
82
84
|
hasSwiftMagicNumberLayoutUsage,
|
|
83
85
|
hasSwiftMixedTestingFrameworksUsage,
|
|
@@ -128,6 +130,7 @@ import {
|
|
|
128
130
|
hasSwiftExplicitColorStaticMemberUsage,
|
|
129
131
|
hasSwiftClosureBasedViewBuilderContentUsage,
|
|
130
132
|
collectSwiftForceUnwrapLines,
|
|
133
|
+
collectSwiftUIKitManualFrameLayoutLines,
|
|
131
134
|
collectSwiftWarningSuppressionLines,
|
|
132
135
|
hasSwiftLargeConfigContextViewPropertyUsage,
|
|
133
136
|
hasSwiftUiConditionalSameViewIdentityUsage,
|
|
@@ -147,6 +150,7 @@ import {
|
|
|
147
150
|
hasSwiftXCTestAssertionUsage,
|
|
148
151
|
hasSwiftXCTUnwrapUsage,
|
|
149
152
|
hasSwiftUncheckedSendableUsage,
|
|
153
|
+
hasSwiftUIKitManualFrameLayoutUsage,
|
|
150
154
|
} from './ios';
|
|
151
155
|
|
|
152
156
|
test('hasSwiftForceUnwrap detecta force unwrap postfix en expresiones', () => {
|
|
@@ -1129,6 +1133,44 @@ struct ProfileView: View {
|
|
|
1129
1133
|
assert.deepEqual(collectSwiftMagicNumberLayoutLines(constants), []);
|
|
1130
1134
|
});
|
|
1131
1135
|
|
|
1136
|
+
test('hasSwiftUIKitManualFrameLayoutUsage detecta layout manual UIKit y preserva Auto Layout y SwiftUI frame', () => {
|
|
1137
|
+
const source = `
|
|
1138
|
+
final class CheckoutView: UIView {
|
|
1139
|
+
private let titleLabel = UILabel(frame: CGRect(x: 0, y: 0, width: 320, height: 44))
|
|
1140
|
+
|
|
1141
|
+
func layoutBadge() {
|
|
1142
|
+
badgeView.frame = CGRect(x: 16, y: 16, width: 80, height: 32)
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
`;
|
|
1146
|
+
const safe = `
|
|
1147
|
+
final class CheckoutView: UIView {
|
|
1148
|
+
private let titleLabel = UILabel()
|
|
1149
|
+
|
|
1150
|
+
func installConstraints() {
|
|
1151
|
+
titleLabel.translatesAutoresizingMaskIntoConstraints = false
|
|
1152
|
+
NSLayoutConstraint.activate([
|
|
1153
|
+
titleLabel.leadingAnchor.constraint(equalTo: leadingAnchor)
|
|
1154
|
+
])
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
struct CheckoutSwiftUIView: View {
|
|
1159
|
+
var body: some View {
|
|
1160
|
+
Text("Checkout").frame(maxWidth: .infinity)
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
let sample = "UILabel(frame: CGRect(x: 0, y: 0, width: 10, height: 10))"
|
|
1165
|
+
// badgeView.frame = CGRect(x: 0, y: 0, width: 10, height: 10)
|
|
1166
|
+
`;
|
|
1167
|
+
|
|
1168
|
+
assert.equal(hasSwiftUIKitManualFrameLayoutUsage(source), true);
|
|
1169
|
+
assert.deepEqual(collectSwiftUIKitManualFrameLayoutLines(source), [3, 6]);
|
|
1170
|
+
assert.equal(hasSwiftUIKitManualFrameLayoutUsage(safe), false);
|
|
1171
|
+
assert.deepEqual(collectSwiftUIKitManualFrameLayoutLines(safe), []);
|
|
1172
|
+
});
|
|
1173
|
+
|
|
1132
1174
|
test('detectores de logging iOS detectan logs ad-hoc y PII en produccion', () => {
|
|
1133
1175
|
const adHoc = `
|
|
1134
1176
|
print(user.id)
|
|
@@ -2217,6 +2259,47 @@ final class BuyerOnboardingStringsTests: XCTestCase {
|
|
|
2217
2259
|
assert.deepEqual(collectSwiftXCTUnwrapLines(brownfieldSpec), []);
|
|
2218
2260
|
});
|
|
2219
2261
|
|
|
2262
|
+
test('hasSwiftMakeSUTWithoutMemoryTrackingUsage detecta specs con makeSUT sin trackForMemoryLeaks', () => {
|
|
2263
|
+
const source = `
|
|
2264
|
+
import XCTest
|
|
2265
|
+
|
|
2266
|
+
final class BuyerAuthScreenTests: XCTestCase {
|
|
2267
|
+
func testRendersTitle() {
|
|
2268
|
+
let sut = makeSUT()
|
|
2269
|
+
XCTAssertNotNil(sut)
|
|
2270
|
+
}
|
|
2271
|
+
|
|
2272
|
+
private func makeSUT() -> BuyerAuthScreen {
|
|
2273
|
+
BuyerAuthScreen()
|
|
2274
|
+
}
|
|
2275
|
+
}
|
|
2276
|
+
`;
|
|
2277
|
+
const safe = `
|
|
2278
|
+
import XCTest
|
|
2279
|
+
|
|
2280
|
+
final class BuyerAuthScreenTests: XCTestCase {
|
|
2281
|
+
func testRendersTitle() {
|
|
2282
|
+
let sut = makeSUT()
|
|
2283
|
+
XCTAssertNotNil(sut)
|
|
2284
|
+
}
|
|
2285
|
+
|
|
2286
|
+
private func makeSUT() -> BuyerAuthScreen {
|
|
2287
|
+
let sut = BuyerAuthScreen()
|
|
2288
|
+
trackForMemoryLeaks(sut)
|
|
2289
|
+
return sut
|
|
2290
|
+
}
|
|
2291
|
+
}
|
|
2292
|
+
|
|
2293
|
+
let sample = "makeSUT() without trackForMemoryLeaks"
|
|
2294
|
+
// private func makeSUT() -> Sample { Sample() }
|
|
2295
|
+
`;
|
|
2296
|
+
|
|
2297
|
+
assert.equal(hasSwiftMakeSUTWithoutMemoryTrackingUsage(source), true);
|
|
2298
|
+
assert.deepEqual(collectSwiftMakeSUTWithoutMemoryTrackingLines(source), [6, 10]);
|
|
2299
|
+
assert.equal(hasSwiftMakeSUTWithoutMemoryTrackingUsage(safe), false);
|
|
2300
|
+
assert.deepEqual(collectSwiftMakeSUTWithoutMemoryTrackingLines(safe), []);
|
|
2301
|
+
});
|
|
2302
|
+
|
|
2220
2303
|
test('hasSwiftMixedTestingFrameworksUsage detecta mezcla XCTestCase y Testing/@Test', () => {
|
|
2221
2304
|
const mixedSuite = `
|
|
2222
2305
|
import XCTest
|
|
@@ -1701,6 +1701,17 @@ export const hasSwiftNSLayoutConstraintUsage = (source: string): boolean => {
|
|
|
1701
1701
|
});
|
|
1702
1702
|
};
|
|
1703
1703
|
|
|
1704
|
+
export const collectSwiftUIKitManualFrameLayoutLines = (source: string): readonly number[] => {
|
|
1705
|
+
const manualFramePattern =
|
|
1706
|
+
/\b(?:UIView|UIStackView|UILabel|UIButton|UIImageView|UITableView|UICollectionView|UIScrollView|UITextField|UITextView|UIViewController)\s*\(\s*frame\s*:\s*CGRect\s*\(|\.\s*frame\s*=\s*CGRect\s*\(/g;
|
|
1707
|
+
|
|
1708
|
+
return collectSwiftRegexLines(source, manualFramePattern);
|
|
1709
|
+
};
|
|
1710
|
+
|
|
1711
|
+
export const hasSwiftUIKitManualFrameLayoutUsage = (source: string): boolean => {
|
|
1712
|
+
return collectSwiftUIKitManualFrameLayoutLines(source).length > 0;
|
|
1713
|
+
};
|
|
1714
|
+
|
|
1704
1715
|
export const hasSwiftUntypedNavigationLinkDestinationUsage = (source: string): boolean => {
|
|
1705
1716
|
const swiftSource = sanitizeSwiftSourceForMultilineRegex(source);
|
|
1706
1717
|
const destinationParameterPattern = /\bNavigationLink\s*\([^)]*\bdestination\s*:/;
|
|
@@ -1832,6 +1843,19 @@ const hasSwiftBrownfieldXCTestQualityPattern = (source: string): boolean => {
|
|
|
1832
1843
|
return /\bmakeSUT\s*\(/.test(sanitized) && /\btrackForMemoryLeaks\s*\(/.test(sanitized);
|
|
1833
1844
|
};
|
|
1834
1845
|
|
|
1846
|
+
export const collectSwiftMakeSUTWithoutMemoryTrackingLines = (source: string): readonly number[] => {
|
|
1847
|
+
const sanitized = sanitizeSwiftSourceForMultilineRegex(source);
|
|
1848
|
+
if (!/\bmakeSUT\s*\(/.test(sanitized) || /\btrackForMemoryLeaks\s*\(/.test(sanitized)) {
|
|
1849
|
+
return [];
|
|
1850
|
+
}
|
|
1851
|
+
|
|
1852
|
+
return collectSwiftRegexLines(source, /\bmakeSUT\s*\(/g);
|
|
1853
|
+
};
|
|
1854
|
+
|
|
1855
|
+
export const hasSwiftMakeSUTWithoutMemoryTrackingUsage = (source: string): boolean => {
|
|
1856
|
+
return collectSwiftMakeSUTWithoutMemoryTrackingLines(source).length > 0;
|
|
1857
|
+
};
|
|
1858
|
+
|
|
1835
1859
|
export const hasSwiftLegacyXCTestImportUsage = (source: string): boolean => {
|
|
1836
1860
|
if (!hasSwiftXCTestImportUsage(source)) {
|
|
1837
1861
|
return false;
|
|
@@ -778,6 +778,7 @@ const textDetectorRegistry: ReadonlyArray<TextDetectorRegistryEntry> = [
|
|
|
778
778
|
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftMassiveViewControllerResponsibilityUsage, ruleId: 'heuristics.ios.architecture.massive-view-controller.ast', code: 'HEURISTICS_IOS_ARCHITECTURE_MASSIVE_VIEW_CONTROLLER_AST', message: 'AST heuristic detected a UIViewController with direct infrastructure/data access; move data access behind application/domain boundaries.' },
|
|
779
779
|
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftNonIBOutletImplicitlyUnwrappedOptionalUsage, ruleId: 'heuristics.ios.safety.non-iboutlet-iuo.ast', code: 'HEURISTICS_IOS_SAFETY_NON_IBOUTLET_IUO_AST', message: 'AST heuristic detected an implicitly unwrapped optional outside IBOutlet wiring; explicit optionals or initialization guarantees remain the preferred baseline.' },
|
|
780
780
|
{ platform: 'ios', pathCheck: isIOSPresentationPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftMagicNumberLayoutUsage, locateLines: TextIOS.collectSwiftMagicNumberLayoutLines, primaryNode: (lines) => ({ kind: 'call', name: 'SwiftUI layout numeric literal', lines }), relatedNodes: (lines) => [{ kind: 'property', name: 'replacement: named metric constant or design token', lines }], why: 'Inline numeric layout literals hide design intent and make visual remediation dependent on scanning the whole view body.', impact: 'Pixel-perfect slices can be blocked without a concrete node unless the finding points to the exact spacing, frame or padding call to fix.', expected_fix: 'Move repeated or meaningful layout numbers into named constants or design tokens, or use relative layout APIs when the number encodes screen geometry.', ruleId: 'heuristics.ios.maintainability.magic-number-layout.ast', code: 'HEURISTICS_IOS_MAINTAINABILITY_MAGIC_NUMBER_LAYOUT_AST', message: 'AST heuristic detected SwiftUI layout magic numbers; named constants or design tokens remain the preferred baseline.' },
|
|
781
|
+
{ platform: 'ios', pathCheck: isIOSPresentationPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftUIKitManualFrameLayoutUsage, locateLines: TextIOS.collectSwiftUIKitManualFrameLayoutLines, primaryNode: (lines) => ({ kind: 'call', name: 'UIKit manual frame CGRect layout', lines }), relatedNodes: (lines) => [{ kind: 'call', name: 'replacement: Auto Layout constraints or SwiftUI relative layout', lines }], why: 'Manual UIKit frames hard-code geometry instead of using Auto Layout constraints or SwiftUI relative layout, so the layout cannot adapt reliably to devices, Dynamic Type or localization.', impact: 'Pixel-perfect work can regress across screens because fixed CGRect values bypass constraint solving and produce file-level ambiguity unless the exact frame node is reported.', expected_fix: 'Replace UIView(frame: CGRect(...)) and .frame = CGRect(...) layout with Auto Layout anchors/NSLayoutConstraint, UIStackView constraints, or SwiftUI relative layout where the screen is SwiftUI-first.', ruleId: 'heuristics.ios.uikit.manual-frame-layout.ast', code: 'HEURISTICS_IOS_UIKIT_MANUAL_FRAME_LAYOUT_AST', message: 'AST heuristic detected UIKit manual frame layout; use Auto Layout constraints or SwiftUI relative layout.' },
|
|
781
782
|
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftAdHocLoggingUsage, ruleId: 'heuristics.ios.logging.adhoc-print.ast', code: 'HEURISTICS_IOS_LOGGING_ADHOC_PRINT_AST', message: 'AST heuristic detected print/debugPrint/dump/NSLog/os_log usage in iOS production code.' },
|
|
782
783
|
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftSensitiveLoggingUsage, ruleId: 'heuristics.ios.logging.sensitive-data.ast', code: 'HEURISTICS_IOS_LOGGING_SENSITIVE_DATA_AST', message: 'AST heuristic detected sensitive data in an iOS logging call.' },
|
|
783
784
|
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftHardcodedSensitiveStringUsage, locateLines: TextIOS.collectSwiftHardcodedSensitiveStringLines, primaryNode: (lines) => ({ kind: 'property', name: 'hardcoded sensitive Swift string', lines }), relatedNodes: (lines) => [{ kind: 'property', name: 'replacement: Keychain, secure config or environment-specific secret source', lines }], why: 'Sensitive strings assigned directly to token/password/secret properties create static production secrets and cannot be rotated safely.', impact: 'Credentials or identifiers can leak through source, binaries, logs or screenshots and block release until the concrete assignment is removed.', expected_fix: 'Read sensitive values from Keychain, secure configuration, injected environment or a repository-approved secret provider. Keep user-facing copy in localization assets, not sensitive variables.', ruleId: 'heuristics.ios.security.hardcoded-sensitive-string.ast', code: 'HEURISTICS_IOS_SECURITY_HARDCODED_SENSITIVE_STRING_AST', message: 'AST heuristic detected hardcoded sensitive Swift string; Keychain, secure config or environment-specific secrets remain the preferred baseline.' },
|
|
@@ -3,7 +3,7 @@ import test from 'node:test';
|
|
|
3
3
|
import { iosRules } from './ios';
|
|
4
4
|
|
|
5
5
|
test('iosRules define reglas heurísticas locked para plataforma ios', () => {
|
|
6
|
-
assert.equal(iosRules.length,
|
|
6
|
+
assert.equal(iosRules.length, 106);
|
|
7
7
|
|
|
8
8
|
const ids = iosRules.map((rule) => rule.id);
|
|
9
9
|
assert.deepEqual(ids, [
|
|
@@ -35,6 +35,7 @@ test('iosRules define reglas heurísticas locked para plataforma ios', () => {
|
|
|
35
35
|
'heuristics.ios.architecture.swinject.ast',
|
|
36
36
|
'heuristics.ios.architecture.massive-view-controller.ast',
|
|
37
37
|
'heuristics.ios.maintainability.magic-number-layout.ast',
|
|
38
|
+
'heuristics.ios.uikit.manual-frame-layout.ast',
|
|
38
39
|
'heuristics.ios.safety.non-iboutlet-iuo.ast',
|
|
39
40
|
'heuristics.ios.logging.adhoc-print.ast',
|
|
40
41
|
'heuristics.ios.logging.sensitive-data.ast',
|
|
@@ -155,6 +156,10 @@ test('iosRules define reglas heurísticas locked para plataforma ios', () => {
|
|
|
155
156
|
byId.get('heuristics.ios.maintainability.warning-suppression.ast')?.then.code,
|
|
156
157
|
'HEURISTICS_IOS_MAINTAINABILITY_WARNING_SUPPRESSION_AST'
|
|
157
158
|
);
|
|
159
|
+
assert.equal(
|
|
160
|
+
byId.get('heuristics.ios.uikit.manual-frame-layout.ast')?.then.code,
|
|
161
|
+
'HEURISTICS_IOS_UIKIT_MANUAL_FRAME_LAYOUT_AST'
|
|
162
|
+
);
|
|
158
163
|
assert.equal(
|
|
159
164
|
byId.get('heuristics.ios.swiftui.onchange-task.ast')?.then.code,
|
|
160
165
|
'HEURISTICS_IOS_SWIFTUI_ONCHANGE_TASK_AST'
|
|
@@ -520,6 +520,25 @@ export const iosRules: RuleSet = [
|
|
|
520
520
|
code: 'HEURISTICS_IOS_MAINTAINABILITY_MAGIC_NUMBER_LAYOUT_AST',
|
|
521
521
|
},
|
|
522
522
|
},
|
|
523
|
+
{
|
|
524
|
+
id: 'heuristics.ios.uikit.manual-frame-layout.ast',
|
|
525
|
+
description: 'Detects UIKit manual frame CGRect layout in iOS presentation code.',
|
|
526
|
+
severity: 'WARN',
|
|
527
|
+
platform: 'ios',
|
|
528
|
+
locked: true,
|
|
529
|
+
when: {
|
|
530
|
+
kind: 'Heuristic',
|
|
531
|
+
where: {
|
|
532
|
+
ruleId: 'heuristics.ios.uikit.manual-frame-layout.ast',
|
|
533
|
+
},
|
|
534
|
+
},
|
|
535
|
+
then: {
|
|
536
|
+
kind: 'Finding',
|
|
537
|
+
message:
|
|
538
|
+
'AST heuristic detected UIKit manual frame layout; use Auto Layout constraints or SwiftUI relative layout.',
|
|
539
|
+
code: 'HEURISTICS_IOS_UIKIT_MANUAL_FRAME_LAYOUT_AST',
|
|
540
|
+
},
|
|
541
|
+
},
|
|
523
542
|
{
|
|
524
543
|
id: 'heuristics.ios.safety.non-iboutlet-iuo.ast',
|
|
525
544
|
description: 'Detects implicitly unwrapped optionals outside IBOutlet wiring.',
|
|
@@ -167,6 +167,10 @@ const registryByRuleId: Record<string, SkillsDetectorBinding> = {
|
|
|
167
167
|
heuristicDetector('ios.maintainability.magic-number-layout', [
|
|
168
168
|
'heuristics.ios.maintainability.magic-number-layout.ast',
|
|
169
169
|
]),
|
|
170
|
+
'skills.ios.guideline.ios.auto-layout-nslayoutconstraint': heuristicDetector(
|
|
171
|
+
'ios.uikit.manual-frame-layout',
|
|
172
|
+
['heuristics.ios.uikit.manual-frame-layout.ast']
|
|
173
|
+
),
|
|
170
174
|
'skills.ios.guideline.ios.prohibido-print-y-logs-ad-hoc': heuristicDetector(
|
|
171
175
|
'ios.logging.adhoc-print',
|
|
172
176
|
['heuristics.ios.logging.adhoc-print.ast']
|
|
@@ -542,6 +542,14 @@ const hasWorktreeCodePlatforms = (params: {
|
|
|
542
542
|
);
|
|
543
543
|
};
|
|
544
544
|
|
|
545
|
+
const hasEffectiveChangedCodePlatforms = (params: {
|
|
546
|
+
changedPaths: ReadonlyArray<string>;
|
|
547
|
+
requiredPlatforms: ReadonlyArray<PreWriteSkillsPlatform>;
|
|
548
|
+
}): boolean =>
|
|
549
|
+
params.changedPaths.some((filePath) =>
|
|
550
|
+
params.requiredPlatforms.some((platform) => isPlatformPath(platform, filePath))
|
|
551
|
+
);
|
|
552
|
+
|
|
545
553
|
const toLockRequiredPlatforms = (
|
|
546
554
|
requiredLock: SkillsLockV1 | undefined
|
|
547
555
|
): ReadonlyArray<PreWriteSkillsPlatform> => {
|
|
@@ -792,6 +800,25 @@ const toSkillsContractAssessment = (params: {
|
|
|
792
800
|
skillsEnforcement: SkillsEnforcementResolution;
|
|
793
801
|
}): AiGateSkillsContractAssessment => {
|
|
794
802
|
const requiredPlatforms = toLockRequiredPlatforms(params.requiredLock);
|
|
803
|
+
const effectiveChangedPaths = collectPreWriteEffectiveChangedPaths(params.repoRoot);
|
|
804
|
+
const hasEffectiveChangedPaths = effectiveChangedPaths.length > 0;
|
|
805
|
+
const hasEffectiveCodePlatforms = hasEffectiveChangedCodePlatforms({
|
|
806
|
+
changedPaths: effectiveChangedPaths,
|
|
807
|
+
requiredPlatforms,
|
|
808
|
+
});
|
|
809
|
+
const isNoCodeEffectiveScope =
|
|
810
|
+
requiredPlatforms.length > 0 && hasEffectiveChangedPaths && !hasEffectiveCodePlatforms;
|
|
811
|
+
|
|
812
|
+
if (isNoCodeEffectiveScope) {
|
|
813
|
+
return {
|
|
814
|
+
stage: params.stage,
|
|
815
|
+
enforced: false,
|
|
816
|
+
status: 'NOT_APPLICABLE',
|
|
817
|
+
detected_platforms: [],
|
|
818
|
+
requirements: [],
|
|
819
|
+
violations: [],
|
|
820
|
+
};
|
|
821
|
+
}
|
|
795
822
|
|
|
796
823
|
if (params.evidenceResult.kind !== 'valid') {
|
|
797
824
|
return {
|
|
@@ -841,7 +868,7 @@ const toSkillsContractAssessment = (params: {
|
|
|
841
868
|
const explicitlyDetectedPlatforms = toDetectedSkillsPlatforms(params.evidenceResult.evidence.platforms);
|
|
842
869
|
const inferredPlatforms = toCoverageInferredPlatforms(coverage);
|
|
843
870
|
const repoTreeDetectedPlatforms =
|
|
844
|
-
params.stage !== 'PRE_WRITE' && requiredPlatforms.length > 0
|
|
871
|
+
params.stage !== 'PRE_WRITE' && requiredPlatforms.length > 0 && !hasEffectiveChangedPaths
|
|
845
872
|
? toRepoTreeDetectedPlatforms({
|
|
846
873
|
repoRoot: params.repoRoot,
|
|
847
874
|
platforms: requiredPlatforms,
|
|
@@ -871,13 +898,16 @@ const toSkillsContractAssessment = (params: {
|
|
|
871
898
|
|
|
872
899
|
if (requiredPlatforms.length > 0 && detectedPlatforms.length === 0) {
|
|
873
900
|
if (
|
|
874
|
-
|
|
875
|
-
&& (
|
|
901
|
+
(
|
|
876
902
|
pendingChanges === 0
|
|
877
|
-
|| !
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
903
|
+
|| !hasEffectiveCodePlatforms
|
|
904
|
+
|| (
|
|
905
|
+
params.stage === 'PRE_WRITE'
|
|
906
|
+
&& !hasWorktreeCodePlatforms({
|
|
907
|
+
repoRoot: params.repoRoot,
|
|
908
|
+
requiredPlatforms,
|
|
909
|
+
})
|
|
910
|
+
)
|
|
881
911
|
)
|
|
882
912
|
) {
|
|
883
913
|
return {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pumuki",
|
|
3
|
-
"version": "6.3.
|
|
3
|
+
"version": "6.3.297",
|
|
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": {
|