pumuki 6.3.39 → 6.3.41
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/README.md +21 -12
- package/VERSION +1 -1
- package/core/gate/evaluateRules.test.ts +40 -0
- package/core/gate/evaluateRules.ts +7 -1
- package/core/rules/Consequence.ts +1 -0
- package/docs/CONFIGURATION.md +50 -0
- package/docs/INSTALLATION.md +38 -11
- package/docs/MCP_SERVERS.md +1 -1
- package/docs/README.md +1 -0
- package/docs/RELEASE_NOTES.md +58 -0
- package/docs/USAGE.md +191 -9
- package/docs/registro-maestro-de-seguimiento.md +2 -2
- package/docs/seguimiento-activo-pumuki-saas-supermercados.md +1629 -1
- package/docs/validation/README.md +2 -1
- package/docs/validation/ast-intelligence-roadmap.md +96 -0
- package/integrations/config/skillsCustomRules.ts +14 -0
- package/integrations/config/skillsDetectorRegistry.ts +11 -1
- package/integrations/config/skillsLock.ts +30 -0
- package/integrations/config/skillsMarkdownRules.ts +14 -3
- package/integrations/config/skillsRuleSet.ts +25 -3
- package/integrations/evidence/readEvidence.test.ts +3 -2
- package/integrations/evidence/readEvidence.ts +14 -4
- package/integrations/evidence/repoState.ts +10 -2
- package/integrations/evidence/schema.test.ts +3 -2
- package/integrations/evidence/schema.ts +3 -0
- package/integrations/evidence/writeEvidence.test.ts +3 -2
- package/integrations/gate/evaluateAiGate.ts +511 -2
- package/integrations/git/GitService.ts +5 -1
- package/integrations/git/astIntelligenceDualValidation.ts +275 -0
- package/integrations/git/gitAtomicity.ts +42 -9
- package/integrations/git/resolveGitRefs.ts +37 -0
- package/integrations/git/runPlatformGate.ts +228 -1
- package/integrations/git/runPlatformGateEvaluation.ts +4 -0
- package/integrations/git/stageRunners.ts +116 -2
- package/integrations/lifecycle/cli.ts +759 -22
- package/integrations/lifecycle/doctor.ts +62 -0
- package/integrations/lifecycle/index.ts +1 -0
- package/integrations/lifecycle/packageInfo.ts +25 -3
- package/integrations/lifecycle/policyReconcile.ts +304 -0
- package/integrations/lifecycle/preWriteAutomation.ts +42 -2
- package/integrations/lifecycle/watch.ts +365 -0
- package/integrations/mcp/aiGateCheck.ts +59 -2
- package/integrations/mcp/autoExecuteAiStart.ts +25 -1
- package/integrations/mcp/preFlightCheck.ts +13 -0
- package/integrations/sdd/evidenceScaffold.ts +223 -0
- package/integrations/sdd/index.ts +2 -0
- package/integrations/sdd/stateSync.ts +400 -0
- package/integrations/sdd/syncDocs.ts +97 -2
- package/package.json +4 -1
- package/scripts/backlog-action-reasons-lib.ts +38 -0
- package/scripts/backlog-id-issue-map-lib.ts +69 -0
- package/scripts/backlog-json-contract-lib.ts +3 -0
- package/scripts/framework-menu-consumer-preflight-lib.ts +6 -0
- package/scripts/framework-menu-system-notifications-lib.ts +66 -6
- package/scripts/package-install-smoke-command-resolution-lib.ts +64 -0
- package/scripts/package-install-smoke-consumer-npm-lib.ts +43 -0
- package/scripts/package-install-smoke-consumer-repo-setup-lib.ts +2 -0
- package/scripts/package-install-smoke-execution-steps-lib.ts +27 -9
- package/scripts/package-install-smoke-lifecycle-lib.ts +15 -4
- package/scripts/package-install-smoke-workspace-factory-lib.ts +4 -1
- package/scripts/reconcile-consumer-backlog-issues-lib.ts +651 -0
- package/scripts/reconcile-consumer-backlog-issues.ts +348 -0
- package/scripts/watch-consumer-backlog-lib.ts +465 -0
- package/scripts/watch-consumer-backlog.ts +326 -0
|
@@ -13,7 +13,11 @@ import {
|
|
|
13
13
|
resolveActiveGateWaiver,
|
|
14
14
|
type GateWaiverResult,
|
|
15
15
|
} from './gateWaiver';
|
|
16
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
evaluateAstIntelligenceDualValidation,
|
|
18
|
+
evaluatePlatformGateFindings,
|
|
19
|
+
type AstIntelligenceDualValidationResult,
|
|
20
|
+
} from './runPlatformGateEvaluation';
|
|
17
21
|
import {
|
|
18
22
|
countScannedFilesFromFacts,
|
|
19
23
|
resolveFactsForGateScope,
|
|
@@ -46,6 +50,7 @@ export type GateDependencies = {
|
|
|
46
50
|
emitPlatformGateEvidence: typeof emitPlatformGateEvidence;
|
|
47
51
|
printGateFindings: typeof printGateFindings;
|
|
48
52
|
enforceTddBddPolicy: typeof enforceTddBddPolicy;
|
|
53
|
+
evaluateAstIntelligenceDualValidation: typeof evaluateAstIntelligenceDualValidation;
|
|
49
54
|
buildMemoryShadowRecommendation: (params: {
|
|
50
55
|
findings: ReadonlyArray<Finding>;
|
|
51
56
|
tddBddSnapshot?: TddBddSnapshot;
|
|
@@ -117,6 +122,7 @@ const defaultDependencies: GateDependencies = {
|
|
|
117
122
|
emitPlatformGateEvidence,
|
|
118
123
|
printGateFindings,
|
|
119
124
|
enforceTddBddPolicy,
|
|
125
|
+
evaluateAstIntelligenceDualValidation,
|
|
120
126
|
buildMemoryShadowRecommendation: buildDefaultMemoryShadowRecommendation,
|
|
121
127
|
evaluateSddForStage: (stage, repoRoot) =>
|
|
122
128
|
evaluateSddPolicy({
|
|
@@ -241,6 +247,41 @@ const isCriticalProfileSeverity = (severity: string): boolean => {
|
|
|
241
247
|
|
|
242
248
|
const toNormalizedPath = (value: string): string => value.replace(/\\/g, '/').trim();
|
|
243
249
|
|
|
250
|
+
const CODE_FILE_EXTENSIONS = new Set<string>([
|
|
251
|
+
'.js',
|
|
252
|
+
'.jsx',
|
|
253
|
+
'.ts',
|
|
254
|
+
'.tsx',
|
|
255
|
+
'.mjs',
|
|
256
|
+
'.cjs',
|
|
257
|
+
'.swift',
|
|
258
|
+
'.kt',
|
|
259
|
+
'.kts',
|
|
260
|
+
]);
|
|
261
|
+
|
|
262
|
+
const isObservedCodePath = (path: string): boolean => {
|
|
263
|
+
const normalized = toNormalizedPath(path).toLowerCase();
|
|
264
|
+
if (normalized.length === 0) {
|
|
265
|
+
return false;
|
|
266
|
+
}
|
|
267
|
+
if (
|
|
268
|
+
normalized.startsWith('apps/backend/')
|
|
269
|
+
|| normalized.startsWith('apps/frontend/')
|
|
270
|
+
|| normalized.startsWith('apps/web/')
|
|
271
|
+
|| normalized.startsWith('apps/admin-dashboard/')
|
|
272
|
+
|| normalized.startsWith('apps/ios/')
|
|
273
|
+
|| normalized.startsWith('apps/android/')
|
|
274
|
+
) {
|
|
275
|
+
return true;
|
|
276
|
+
}
|
|
277
|
+
for (const extension of CODE_FILE_EXTENSIONS) {
|
|
278
|
+
if (normalized.endsWith(extension)) {
|
|
279
|
+
return true;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
return false;
|
|
283
|
+
};
|
|
284
|
+
|
|
244
285
|
const collectObservedPathsFromFacts = (
|
|
245
286
|
facts: ReadonlyArray<Fact>
|
|
246
287
|
): ReadonlyArray<string> => {
|
|
@@ -263,6 +304,125 @@ const collectObservedPathsFromFacts = (
|
|
|
263
304
|
return [...observedPaths].sort();
|
|
264
305
|
};
|
|
265
306
|
|
|
307
|
+
const collectObservedCodePathsFromFacts = (
|
|
308
|
+
facts: ReadonlyArray<Fact>
|
|
309
|
+
): ReadonlyArray<string> => {
|
|
310
|
+
const codePaths = collectObservedPathsFromFacts(facts).filter((path) => isObservedCodePath(path));
|
|
311
|
+
return [...new Set(codePaths)].sort();
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
type IosTestFileContent = {
|
|
315
|
+
path: string;
|
|
316
|
+
content: string;
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
const isIosSwiftTestPath = (path: string): boolean => {
|
|
320
|
+
const normalized = toNormalizedPath(path).toLowerCase();
|
|
321
|
+
if (!normalized.endsWith('.swift')) {
|
|
322
|
+
return false;
|
|
323
|
+
}
|
|
324
|
+
return normalized.includes('/tests/') || normalized.includes('/uitests/');
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
const collectIosTestFileContents = (
|
|
328
|
+
facts: ReadonlyArray<Fact>
|
|
329
|
+
): ReadonlyArray<IosTestFileContent> => {
|
|
330
|
+
const filesByPath = new Map<string, string>();
|
|
331
|
+
for (const fact of facts) {
|
|
332
|
+
if (fact.kind !== 'FileContent') {
|
|
333
|
+
continue;
|
|
334
|
+
}
|
|
335
|
+
if (!isIosSwiftTestPath(fact.path)) {
|
|
336
|
+
continue;
|
|
337
|
+
}
|
|
338
|
+
const normalizedPath = toNormalizedPath(fact.path);
|
|
339
|
+
filesByPath.set(normalizedPath, fact.content);
|
|
340
|
+
}
|
|
341
|
+
return [...filesByPath.entries()]
|
|
342
|
+
.sort(([leftPath], [rightPath]) => leftPath.localeCompare(rightPath))
|
|
343
|
+
.map(([path, content]) => ({ path, content }));
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
const isXCTestSource = (content: string): boolean => {
|
|
347
|
+
return /\bimport\s+XCTest\b/.test(content) || /\bXCTestCase\b/.test(content);
|
|
348
|
+
};
|
|
349
|
+
|
|
350
|
+
const hasMakeSUTPattern = (content: string): boolean => /\bmakeSUT\s*\(/.test(content);
|
|
351
|
+
|
|
352
|
+
const hasTrackForMemoryLeaksPattern = (content: string): boolean =>
|
|
353
|
+
/\btrackForMemoryLeaks\s*\(/.test(content);
|
|
354
|
+
|
|
355
|
+
const toIosTestsQualityBlockingFinding = (params: {
|
|
356
|
+
stage: 'PRE_COMMIT' | 'PRE_PUSH' | 'CI';
|
|
357
|
+
facts: ReadonlyArray<Fact>;
|
|
358
|
+
}): Finding | undefined => {
|
|
359
|
+
const testFiles = collectIosTestFileContents(params.facts);
|
|
360
|
+
if (testFiles.length === 0) {
|
|
361
|
+
return undefined;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
const invalidFiles: string[] = [];
|
|
365
|
+
for (const testFile of testFiles) {
|
|
366
|
+
if (!isXCTestSource(testFile.content)) {
|
|
367
|
+
continue;
|
|
368
|
+
}
|
|
369
|
+
const missingMarkers: string[] = [];
|
|
370
|
+
if (!hasMakeSUTPattern(testFile.content)) {
|
|
371
|
+
missingMarkers.push('makeSUT()');
|
|
372
|
+
}
|
|
373
|
+
if (!hasTrackForMemoryLeaksPattern(testFile.content)) {
|
|
374
|
+
missingMarkers.push('trackForMemoryLeaks()');
|
|
375
|
+
}
|
|
376
|
+
if (missingMarkers.length === 0) {
|
|
377
|
+
continue;
|
|
378
|
+
}
|
|
379
|
+
invalidFiles.push(`${testFile.path}{missing=[${missingMarkers.join(', ')}]}`);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
if (invalidFiles.length === 0) {
|
|
383
|
+
return undefined;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
const sampleFiles = invalidFiles.slice(0, 3).join(' | ');
|
|
387
|
+
return {
|
|
388
|
+
ruleId: 'governance.skills.ios-test-quality.incomplete',
|
|
389
|
+
severity: 'ERROR',
|
|
390
|
+
code: 'IOS_TEST_QUALITY_PATTERN_MISSING_HIGH',
|
|
391
|
+
message:
|
|
392
|
+
`iOS test quality enforcement incomplete at ${params.stage}: ${sampleFiles}. ` +
|
|
393
|
+
'Use makeSUT() factory pattern and trackForMemoryLeaks() in XCTest sources.',
|
|
394
|
+
filePath: '.ai_evidence.json',
|
|
395
|
+
matchedBy: 'IosTestsQualityGuard',
|
|
396
|
+
source: 'skills-ios-test-quality',
|
|
397
|
+
};
|
|
398
|
+
};
|
|
399
|
+
|
|
400
|
+
const toActiveRulesEmptyForCodeChangesBlockingFinding = (params: {
|
|
401
|
+
stage: 'PRE_COMMIT' | 'PRE_PUSH' | 'CI';
|
|
402
|
+
facts: ReadonlyArray<Fact>;
|
|
403
|
+
activeRuleIds: ReadonlyArray<string>;
|
|
404
|
+
}): Finding | undefined => {
|
|
405
|
+
if (params.activeRuleIds.length > 0) {
|
|
406
|
+
return undefined;
|
|
407
|
+
}
|
|
408
|
+
const codePaths = collectObservedCodePathsFromFacts(params.facts);
|
|
409
|
+
if (codePaths.length === 0) {
|
|
410
|
+
return undefined;
|
|
411
|
+
}
|
|
412
|
+
const samplePaths = codePaths.slice(0, 5).join(', ');
|
|
413
|
+
return {
|
|
414
|
+
ruleId: 'governance.rules.active-rule-coverage.empty',
|
|
415
|
+
severity: 'ERROR',
|
|
416
|
+
code: 'ACTIVE_RULE_IDS_EMPTY_FOR_CODE_CHANGES_HIGH',
|
|
417
|
+
message:
|
|
418
|
+
`Active rules coverage is empty at ${params.stage} while code changes were detected. ` +
|
|
419
|
+
`sample_paths=[${samplePaths}]. Ensure skill/project rules are active before allowing this stage.`,
|
|
420
|
+
filePath: '.ai_evidence.json',
|
|
421
|
+
matchedBy: 'ActiveRulesCoverageGuard',
|
|
422
|
+
source: 'rules-coverage',
|
|
423
|
+
};
|
|
424
|
+
};
|
|
425
|
+
|
|
266
426
|
const detectRequiredSkillsScopesFromPaths = (
|
|
267
427
|
observedPaths: ReadonlyArray<string>
|
|
268
428
|
): Record<'ios' | 'android' | 'backend' | 'frontend', ReadonlyArray<string>> => {
|
|
@@ -575,6 +735,13 @@ const toCrossPlatformCriticalEnforcementBlockingFinding = (params: {
|
|
|
575
735
|
};
|
|
576
736
|
};
|
|
577
737
|
|
|
738
|
+
const shouldBlockFromFinding = (finding: Finding | undefined): boolean => {
|
|
739
|
+
if (!finding) {
|
|
740
|
+
return false;
|
|
741
|
+
}
|
|
742
|
+
return finding.severity === 'ERROR' || finding.severity === 'CRITICAL';
|
|
743
|
+
};
|
|
744
|
+
|
|
578
745
|
export async function runPlatformGate(params: {
|
|
579
746
|
policy: GatePolicy;
|
|
580
747
|
auditMode?: 'gate' | 'engine';
|
|
@@ -653,6 +820,7 @@ export async function runPlatformGate(params: {
|
|
|
653
820
|
projectRules,
|
|
654
821
|
heuristicRules,
|
|
655
822
|
coverage,
|
|
823
|
+
evaluationFacts = facts,
|
|
656
824
|
findings,
|
|
657
825
|
} = dependencies.evaluatePlatformGateFindings({
|
|
658
826
|
facts,
|
|
@@ -728,6 +896,25 @@ export async function runPlatformGate(params: {
|
|
|
728
896
|
evaluatedRuleIds: coverage?.evaluatedRuleIds ?? [],
|
|
729
897
|
})
|
|
730
898
|
: undefined;
|
|
899
|
+
const activeRulesEmptyForCodeChangesFinding =
|
|
900
|
+
params.policy.stage === 'PRE_COMMIT' ||
|
|
901
|
+
params.policy.stage === 'PRE_PUSH' ||
|
|
902
|
+
params.policy.stage === 'CI'
|
|
903
|
+
? toActiveRulesEmptyForCodeChangesBlockingFinding({
|
|
904
|
+
stage: params.policy.stage,
|
|
905
|
+
facts,
|
|
906
|
+
activeRuleIds: coverage?.activeRuleIds ?? [],
|
|
907
|
+
})
|
|
908
|
+
: undefined;
|
|
909
|
+
const iosTestsQualityFinding =
|
|
910
|
+
params.policy.stage === 'PRE_COMMIT' ||
|
|
911
|
+
params.policy.stage === 'PRE_PUSH' ||
|
|
912
|
+
params.policy.stage === 'CI'
|
|
913
|
+
? toIosTestsQualityBlockingFinding({
|
|
914
|
+
stage: params.policy.stage,
|
|
915
|
+
facts,
|
|
916
|
+
})
|
|
917
|
+
: undefined;
|
|
731
918
|
const policyAsCodeBlockingFinding =
|
|
732
919
|
params.policy.stage === 'PRE_COMMIT' ||
|
|
733
920
|
params.policy.stage === 'PRE_PUSH' ||
|
|
@@ -746,6 +933,33 @@ export async function runPlatformGate(params: {
|
|
|
746
933
|
policyTrace: params.policyTrace,
|
|
747
934
|
})
|
|
748
935
|
: undefined;
|
|
936
|
+
const astIntelligenceDualValidation:
|
|
937
|
+
| AstIntelligenceDualValidationResult
|
|
938
|
+
| undefined =
|
|
939
|
+
params.policy.stage === 'PRE_COMMIT'
|
|
940
|
+
|| params.policy.stage === 'PRE_PUSH'
|
|
941
|
+
|| params.policy.stage === 'CI'
|
|
942
|
+
? dependencies.evaluateAstIntelligenceDualValidation({
|
|
943
|
+
stage: params.policy.stage,
|
|
944
|
+
skillsRules: skillsRuleSet.rules,
|
|
945
|
+
facts: evaluationFacts,
|
|
946
|
+
legacyFindings: findings,
|
|
947
|
+
})
|
|
948
|
+
: undefined;
|
|
949
|
+
const astIntelligenceDualFinding = astIntelligenceDualValidation?.finding;
|
|
950
|
+
if (astIntelligenceDualValidation && astIntelligenceDualValidation.mode !== 'off') {
|
|
951
|
+
const summary = astIntelligenceDualValidation.summary;
|
|
952
|
+
process.stdout.write(
|
|
953
|
+
`[pumuki][ast-intelligence] mode=${astIntelligenceDualValidation.mode}` +
|
|
954
|
+
` mapped_rules=${summary.mapped_rules}` +
|
|
955
|
+
` compared_rules=${summary.compared_rules}` +
|
|
956
|
+
` divergences=${summary.divergences}` +
|
|
957
|
+
` false_positives=${summary.false_positives}` +
|
|
958
|
+
` false_negatives=${summary.false_negatives}` +
|
|
959
|
+
` latency_ms=${summary.latency_ms}` +
|
|
960
|
+
` languages=[${summary.languages.join(',') || 'none'}]\n`
|
|
961
|
+
);
|
|
962
|
+
}
|
|
749
963
|
const degradedModeBlocks = params.policyTrace?.degraded?.action === 'block';
|
|
750
964
|
const rulesCoverage = coverage
|
|
751
965
|
? {
|
|
@@ -797,6 +1011,9 @@ export async function runPlatformGate(params: {
|
|
|
797
1011
|
...(platformSkillsCoverageFinding ? [platformSkillsCoverageFinding] : []),
|
|
798
1012
|
...(crossPlatformCriticalFinding ? [crossPlatformCriticalFinding] : []),
|
|
799
1013
|
...(skillsScopeComplianceFinding ? [skillsScopeComplianceFinding] : []),
|
|
1014
|
+
...(activeRulesEmptyForCodeChangesFinding ? [activeRulesEmptyForCodeChangesFinding] : []),
|
|
1015
|
+
...(iosTestsQualityFinding ? [iosTestsQualityFinding] : []),
|
|
1016
|
+
...(astIntelligenceDualFinding ? [astIntelligenceDualFinding] : []),
|
|
800
1017
|
...(coverageBlockingFinding ? [coverageBlockingFinding] : []),
|
|
801
1018
|
...tddBddEvaluation.findings,
|
|
802
1019
|
...findings,
|
|
@@ -805,6 +1022,9 @@ export async function runPlatformGate(params: {
|
|
|
805
1022
|
|| platformSkillsCoverageFinding
|
|
806
1023
|
|| crossPlatformCriticalFinding
|
|
807
1024
|
|| skillsScopeComplianceFinding
|
|
1025
|
+
|| activeRulesEmptyForCodeChangesFinding
|
|
1026
|
+
|| iosTestsQualityFinding
|
|
1027
|
+
|| astIntelligenceDualFinding
|
|
808
1028
|
|| coverageBlockingFinding
|
|
809
1029
|
|| policyAsCodeBlockingFinding
|
|
810
1030
|
|| degradedModeFinding
|
|
@@ -816,11 +1036,15 @@ export async function runPlatformGate(params: {
|
|
|
816
1036
|
...(platformSkillsCoverageFinding ? [platformSkillsCoverageFinding] : []),
|
|
817
1037
|
...(crossPlatformCriticalFinding ? [crossPlatformCriticalFinding] : []),
|
|
818
1038
|
...(skillsScopeComplianceFinding ? [skillsScopeComplianceFinding] : []),
|
|
1039
|
+
...(activeRulesEmptyForCodeChangesFinding ? [activeRulesEmptyForCodeChangesFinding] : []),
|
|
1040
|
+
...(iosTestsQualityFinding ? [iosTestsQualityFinding] : []),
|
|
1041
|
+
...(astIntelligenceDualFinding ? [astIntelligenceDualFinding] : []),
|
|
819
1042
|
...(coverageBlockingFinding ? [coverageBlockingFinding] : []),
|
|
820
1043
|
...tddBddEvaluation.findings,
|
|
821
1044
|
...findings,
|
|
822
1045
|
]
|
|
823
1046
|
: findings;
|
|
1047
|
+
const hasAstIntelligenceBlockingFinding = shouldBlockFromFinding(astIntelligenceDualFinding);
|
|
824
1048
|
const decision = dependencies.evaluateGate([...effectiveFindings], params.policy);
|
|
825
1049
|
const baseGateOutcome =
|
|
826
1050
|
sddBlockingFinding ||
|
|
@@ -830,6 +1054,9 @@ export async function runPlatformGate(params: {
|
|
|
830
1054
|
platformSkillsCoverageFinding ||
|
|
831
1055
|
crossPlatformCriticalFinding ||
|
|
832
1056
|
skillsScopeComplianceFinding ||
|
|
1057
|
+
activeRulesEmptyForCodeChangesFinding ||
|
|
1058
|
+
iosTestsQualityFinding ||
|
|
1059
|
+
hasAstIntelligenceBlockingFinding ||
|
|
833
1060
|
coverageBlockingFinding ||
|
|
834
1061
|
hasTddBddBlockingFinding
|
|
835
1062
|
? 'BLOCK'
|
|
@@ -20,6 +20,10 @@ import {
|
|
|
20
20
|
import { extractHeuristicFacts } from '../../core/facts/extractHeuristicFacts';
|
|
21
21
|
import { buildCombinedBaselineRules } from './baselineRuleSets';
|
|
22
22
|
import { attachFindingTraceability } from './findingTraceability';
|
|
23
|
+
export {
|
|
24
|
+
evaluateAstIntelligenceDualValidation,
|
|
25
|
+
type AstIntelligenceDualValidationResult,
|
|
26
|
+
} from './astIntelligenceDualValidation';
|
|
23
27
|
|
|
24
28
|
type PlatformGateEvaluationResult = {
|
|
25
29
|
detectedPlatforms: DetectedPlatforms;
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { resolvePolicyForStage } from '../gate/stagePolicies';
|
|
2
2
|
import {
|
|
3
|
+
resolveAheadBehindFromRef,
|
|
4
|
+
resolveCurrentBranchRef,
|
|
3
5
|
resolveCiBaseRef,
|
|
4
6
|
resolvePrePushBootstrapBaseRef,
|
|
7
|
+
resolveUpstreamTrackingRef,
|
|
5
8
|
resolveUpstreamRef,
|
|
6
9
|
} from './resolveGitRefs';
|
|
7
10
|
import { runPlatformGate } from './runPlatformGate';
|
|
@@ -21,6 +24,11 @@ import type { EvidenceReadResult } from '../evidence/readEvidence';
|
|
|
21
24
|
|
|
22
25
|
const PRE_PUSH_UPSTREAM_REQUIRED_MESSAGE =
|
|
23
26
|
'pumuki pre-push blocked: branch has no upstream tracking reference. Configure upstream first (for example: git push --set-upstream origin <branch>) and retry.';
|
|
27
|
+
const PRE_PUSH_UPSTREAM_BOOTSTRAP_FALLBACK_MESSAGE =
|
|
28
|
+
'[pumuki][pre-push] branch has no upstream; using bootstrap range ';
|
|
29
|
+
const PRE_PUSH_MANUAL_FALLBACK_MESSAGE =
|
|
30
|
+
'[pumuki][pre-push] branch has no upstream and stdin is empty; using working-tree fallback scope.';
|
|
31
|
+
const PRE_PUSH_UPSTREAM_MISALIGNED_AHEAD_THRESHOLD = 5;
|
|
24
32
|
|
|
25
33
|
const PRE_COMMIT_EVIDENCE_MAX_AGE_SECONDS = 900;
|
|
26
34
|
const PRE_PUSH_EVIDENCE_MAX_AGE_SECONDS = 1800;
|
|
@@ -37,11 +45,16 @@ const BLOCKED_REMEDIATION_BY_CODE: Readonly<Record<string, string>> = {
|
|
|
37
45
|
EVIDENCE_RULES_COVERAGE_INCOMPLETE: 'Asegura coverage_ratio=1 y unevaluated=0.',
|
|
38
46
|
GITFLOW_PROTECTED_BRANCH: 'Trabaja en feature/* y evita ramas protegidas.',
|
|
39
47
|
PRE_PUSH_UPSTREAM_MISSING: 'Ejecuta: git push --set-upstream origin <branch>',
|
|
48
|
+
PRE_PUSH_UPSTREAM_MISALIGNED:
|
|
49
|
+
'Alinea upstream con la rama actual: git branch --unset-upstream && git push --set-upstream origin <branch>',
|
|
40
50
|
};
|
|
41
51
|
|
|
42
52
|
type StageRunnerDependencies = {
|
|
43
53
|
resolvePolicyForStage: typeof resolvePolicyForStage;
|
|
44
54
|
resolveUpstreamRef: typeof resolveUpstreamRef;
|
|
55
|
+
resolveUpstreamTrackingRef: typeof resolveUpstreamTrackingRef;
|
|
56
|
+
resolveCurrentBranchRef: typeof resolveCurrentBranchRef;
|
|
57
|
+
resolveAheadBehindFromRef: typeof resolveAheadBehindFromRef;
|
|
45
58
|
resolvePrePushBootstrapBaseRef: typeof resolvePrePushBootstrapBaseRef;
|
|
46
59
|
resolveCiBaseRef: typeof resolveCiBaseRef;
|
|
47
60
|
runPlatformGate: typeof runPlatformGate;
|
|
@@ -75,6 +88,9 @@ type StageRunnerDependencies = {
|
|
|
75
88
|
const defaultDependencies: StageRunnerDependencies = {
|
|
76
89
|
resolvePolicyForStage,
|
|
77
90
|
resolveUpstreamRef,
|
|
91
|
+
resolveUpstreamTrackingRef,
|
|
92
|
+
resolveCurrentBranchRef,
|
|
93
|
+
resolveAheadBehindFromRef,
|
|
78
94
|
resolvePrePushBootstrapBaseRef,
|
|
79
95
|
resolveCiBaseRef,
|
|
80
96
|
runPlatformGate,
|
|
@@ -233,6 +249,57 @@ const shouldAllowBootstrapPrePush = (rawInput: string): boolean => {
|
|
|
233
249
|
return false;
|
|
234
250
|
};
|
|
235
251
|
|
|
252
|
+
const PRE_PUSH_TOPIC_BRANCH_PREFIXES = [
|
|
253
|
+
'feature/',
|
|
254
|
+
'bugfix/',
|
|
255
|
+
'refactor/',
|
|
256
|
+
'chore/',
|
|
257
|
+
'docs/',
|
|
258
|
+
];
|
|
259
|
+
|
|
260
|
+
const toShortRef = (value: string): string =>
|
|
261
|
+
value
|
|
262
|
+
.replace(/^refs\/heads\//, '')
|
|
263
|
+
.replace(/^refs\/remotes\/[^/]+\//, '')
|
|
264
|
+
.replace(/^[^/]+\//, '');
|
|
265
|
+
|
|
266
|
+
const detectPrePushUpstreamMisalignment = (params: {
|
|
267
|
+
dependencies: StageRunnerDependencies;
|
|
268
|
+
upstreamRef: string;
|
|
269
|
+
}): { message: string; remediation: string } | null => {
|
|
270
|
+
const currentBranch = params.dependencies.resolveCurrentBranchRef();
|
|
271
|
+
const upstreamTrackingRef = params.dependencies.resolveUpstreamTrackingRef();
|
|
272
|
+
if (!currentBranch || !upstreamTrackingRef) {
|
|
273
|
+
return null;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const isTopicBranch = PRE_PUSH_TOPIC_BRANCH_PREFIXES.some((prefix) =>
|
|
277
|
+
currentBranch.startsWith(prefix)
|
|
278
|
+
);
|
|
279
|
+
if (!isTopicBranch) {
|
|
280
|
+
return null;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const upstreamShort = toShortRef(upstreamTrackingRef);
|
|
284
|
+
if (upstreamShort !== 'main' && upstreamShort !== 'develop') {
|
|
285
|
+
return null;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const aheadBehind = params.dependencies.resolveAheadBehindFromRef(params.upstreamRef);
|
|
289
|
+
if (!aheadBehind || aheadBehind.ahead < PRE_PUSH_UPSTREAM_MISALIGNED_AHEAD_THRESHOLD) {
|
|
290
|
+
return null;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return {
|
|
294
|
+
message:
|
|
295
|
+
`pumuki pre-push blocked: upstream appears misaligned for ${currentBranch}. ` +
|
|
296
|
+
`tracking=${upstreamTrackingRef} ahead=${aheadBehind.ahead} behind=${aheadBehind.behind}.`,
|
|
297
|
+
remediation:
|
|
298
|
+
'Alinea upstream con la rama actual para evaluar solo el delta real: ' +
|
|
299
|
+
'git branch --unset-upstream && git push --set-upstream origin <branch>',
|
|
300
|
+
};
|
|
301
|
+
};
|
|
302
|
+
|
|
236
303
|
const enforceGitAtomicityGate = (params: {
|
|
237
304
|
dependencies: StageRunnerDependencies;
|
|
238
305
|
repoRoot: string;
|
|
@@ -315,8 +382,19 @@ export async function runPrePushStage(
|
|
|
315
382
|
const upstreamRef = activeDependencies.resolveUpstreamRef();
|
|
316
383
|
if (!upstreamRef) {
|
|
317
384
|
const prePushInput = activeDependencies.readPrePushStdin();
|
|
318
|
-
|
|
319
|
-
|
|
385
|
+
const bootstrapBaseRef = activeDependencies.resolvePrePushBootstrapBaseRef();
|
|
386
|
+
const bootstrapByPrePushStdIn = shouldAllowBootstrapPrePush(prePushInput);
|
|
387
|
+
const bootstrapByFallbackBase = !bootstrapByPrePushStdIn && bootstrapBaseRef !== 'HEAD';
|
|
388
|
+
const manualInvocationFallback =
|
|
389
|
+
!bootstrapByPrePushStdIn &&
|
|
390
|
+
!bootstrapByFallbackBase &&
|
|
391
|
+
prePushInput.trim().length === 0;
|
|
392
|
+
if (bootstrapByPrePushStdIn || bootstrapByFallbackBase) {
|
|
393
|
+
if (bootstrapByFallbackBase) {
|
|
394
|
+
process.stderr.write(
|
|
395
|
+
`${PRE_PUSH_UPSTREAM_BOOTSTRAP_FALLBACK_MESSAGE}${bootstrapBaseRef}..HEAD\n`
|
|
396
|
+
);
|
|
397
|
+
}
|
|
320
398
|
if (
|
|
321
399
|
enforceGitAtomicityGate({
|
|
322
400
|
dependencies: activeDependencies,
|
|
@@ -347,6 +425,25 @@ export async function runPrePushStage(
|
|
|
347
425
|
notifyAuditSummaryForStage(activeDependencies, 'PRE_PUSH');
|
|
348
426
|
return exitCode;
|
|
349
427
|
}
|
|
428
|
+
if (manualInvocationFallback) {
|
|
429
|
+
process.stderr.write(`${PRE_PUSH_MANUAL_FALLBACK_MESSAGE}\n`);
|
|
430
|
+
const resolved = activeDependencies.resolvePolicyForStage('PRE_PUSH');
|
|
431
|
+
const exitCode = await activeDependencies.runPlatformGate({
|
|
432
|
+
policy: resolved.policy,
|
|
433
|
+
policyTrace: resolved.trace,
|
|
434
|
+
scope: {
|
|
435
|
+
kind: 'workingTree',
|
|
436
|
+
},
|
|
437
|
+
});
|
|
438
|
+
emitSuccessfulHookGateSummary({
|
|
439
|
+
dependencies: activeDependencies,
|
|
440
|
+
stage: 'PRE_PUSH',
|
|
441
|
+
policyTrace: resolved.trace,
|
|
442
|
+
exitCode,
|
|
443
|
+
});
|
|
444
|
+
notifyAuditSummaryForStage(activeDependencies, 'PRE_PUSH');
|
|
445
|
+
return exitCode;
|
|
446
|
+
}
|
|
350
447
|
process.stderr.write(`${PRE_PUSH_UPSTREAM_REQUIRED_MESSAGE}\n`);
|
|
351
448
|
notifyGateBlockedForStage({
|
|
352
449
|
dependencies: activeDependencies,
|
|
@@ -359,6 +456,23 @@ export async function runPrePushStage(
|
|
|
359
456
|
return 1;
|
|
360
457
|
}
|
|
361
458
|
|
|
459
|
+
const misalignedUpstream = detectPrePushUpstreamMisalignment({
|
|
460
|
+
dependencies: activeDependencies,
|
|
461
|
+
upstreamRef,
|
|
462
|
+
});
|
|
463
|
+
if (misalignedUpstream) {
|
|
464
|
+
process.stderr.write(`${misalignedUpstream.message}\n`);
|
|
465
|
+
notifyGateBlockedForStage({
|
|
466
|
+
dependencies: activeDependencies,
|
|
467
|
+
stage: 'PRE_PUSH',
|
|
468
|
+
fallbackCauseCode: 'PRE_PUSH_UPSTREAM_MISALIGNED',
|
|
469
|
+
fallbackCauseMessage: misalignedUpstream.message,
|
|
470
|
+
fallbackRemediation: misalignedUpstream.remediation,
|
|
471
|
+
});
|
|
472
|
+
notifyAuditSummaryForStage(activeDependencies, 'PRE_PUSH');
|
|
473
|
+
return 1;
|
|
474
|
+
}
|
|
475
|
+
|
|
362
476
|
if (
|
|
363
477
|
enforceGitAtomicityGate({
|
|
364
478
|
dependencies: activeDependencies,
|