pumuki 6.3.132 → 6.3.134
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 +15 -0
- package/docs/operations/RELEASE_NOTES.md +12 -0
- package/docs/product/CONFIGURATION.md +1 -2
- package/integrations/gate/evaluateAiGate.ts +24 -0
- package/integrations/gate/governanceActionCatalog.ts +22 -1
- package/integrations/gate/remediationCatalog.ts +4 -2
- package/integrations/gate/runPlatformGateConfig.ts +55 -0
- package/integrations/gate/runPlatformGateDefaults.ts +19 -0
- package/integrations/git/runPlatformGate.ts +61 -88
- package/integrations/git/runPlatformGateOutput.ts +13 -8
- package/integrations/lifecycle/audit.ts +1 -1
- package/integrations/lifecycle/cli.ts +6 -4
- package/integrations/lifecycle/doctor.ts +1 -1
- package/integrations/lifecycle/governanceNextAction.ts +10 -0
- package/integrations/lifecycle/governanceObservationSnapshot.ts +6 -0
- package/integrations/mcp/autoExecuteAiStart.ts +2 -2
- package/integrations/mcp/preFlightCheck.ts +18 -5
- package/integrations/policy/skillsEnforcement.ts +0 -46
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,21 @@ This project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [6.3.134] - 2026-05-03
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- **Policy hash drift accionable:** `governanceObservationSnapshot`, `governanceNextAction` y el catálogo de remediación ya convierten la divergencia entre stages en una acción estricta y aplicable.
|
|
14
|
+
- **Release preparada para repin:** esta versión queda lista para publicarse y repinear consumers activos como RuralGo con el fix real ya distribuido.
|
|
15
|
+
|
|
16
|
+
## [6.3.133] - 2026-05-03
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- **Skills enforcement endurecido a bloqueo duro:** `PRE_WRITE`, `PRE_COMMIT` y `PRE_PUSH` ya no admiten bypass advisory para violaciones de skills.
|
|
21
|
+
- **Contrato de gate alineado de punta a punta:** `skillsEnforcement`, `evaluateAiGate`, `runPlatformGate` y el flujo CLI bloquean de forma consistente cuando falta cobertura, bundles o contrato de skills.
|
|
22
|
+
- **Release listo para repin:** esta versión está preparada para publicarse y repinear consumers como RuralGo sin cerrar más gaps funcionales para este fix.
|
|
23
|
+
|
|
9
24
|
## [6.3.132] - 2026-05-03
|
|
10
25
|
|
|
11
26
|
### Fixed
|
|
@@ -4,6 +4,18 @@ 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-03 (v6.3.134)
|
|
8
|
+
|
|
9
|
+
- **Policy hash drift accionable:** `governanceObservationSnapshot`, `governanceNextAction` y el catálogo de remediación ya convierten la divergencia entre stages en una acción estricta y aplicable.
|
|
10
|
+
- **Repin recomendado:** publicar `pumuki@6.3.134`, repinear RuralGo y revalidar `status`, `doctor`, `audit --stage=PRE_WRITE --json` y hooks gestionados.
|
|
11
|
+
- **Contrato de release estable:** no hace falta cerrar más gaps funcionales para este fix; la solución ya quedó validada localmente y lo que falta es distribución.
|
|
12
|
+
|
|
13
|
+
### 2026-05-03 (v6.3.133)
|
|
14
|
+
|
|
15
|
+
- **Skills hard-blocking end-to-end:** `skillsEnforcement`, `evaluateAiGate`, `runPlatformGate` y el flujo CLI ya no dejan pasar advisory para violations de skills.
|
|
16
|
+
- **Repin recomendado:** publicar `pumuki@6.3.133`, repinear RuralGo y revalidar `status`, `doctor`, `audit --stage=PRE_WRITE --json` y hooks gestionados.
|
|
17
|
+
- **Contrato de release estable:** no hace falta cerrar más gaps funcionales para este fix; la solución ya quedó validada localmente y lo que falta es distribución.
|
|
18
|
+
|
|
7
19
|
### 2026-05-03 (v6.3.130)
|
|
8
20
|
|
|
9
21
|
- **Menú consumer legacy recuperado:** `pumuki-framework` vuelve a mostrar la portada plana de 9 opciones y la salida clásica de auditoría.
|
|
@@ -66,8 +66,7 @@ Current enforcement scope:
|
|
|
66
66
|
- curated template compilation (`skills.sources.json` -> `skills.lock.json`)
|
|
67
67
|
- stage-aware policy resolution via `resolvePolicyForStage`
|
|
68
68
|
- additive skills-derived rules merged through the shared gate runner
|
|
69
|
-
- strict skills enforcement by default;
|
|
70
|
-
is an explicit opt-in escape hatch, not the product baseline
|
|
69
|
+
- strict skills enforcement by default; advisory values are ignored by design
|
|
71
70
|
- strict heuristics, TDD/BDD evidence, SDD completeness, and Git atomicity by
|
|
72
71
|
default; their `advisory`/`0` env values are explicit legacy overrides, not
|
|
73
72
|
the product baseline
|
|
@@ -1338,6 +1338,26 @@ const collectEvidencePolicyThresholdViolations = (params: {
|
|
|
1338
1338
|
return [];
|
|
1339
1339
|
};
|
|
1340
1340
|
|
|
1341
|
+
const collectTddBddBaselineViolations = (params: {
|
|
1342
|
+
evidenceResult: EvidenceReadResult;
|
|
1343
|
+
}): AiGateViolation[] => {
|
|
1344
|
+
if (params.evidenceResult.kind !== 'valid') {
|
|
1345
|
+
return [];
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
const tddBdd = params.evidenceResult.evidence.snapshot.tdd_bdd;
|
|
1349
|
+
if (tddBdd?.status !== 'blocked') {
|
|
1350
|
+
return [];
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
return [
|
|
1354
|
+
toErrorViolation(
|
|
1355
|
+
'TDD_BDD_BASELINE_BLOCKED',
|
|
1356
|
+
'TDD/BDD baseline snapshot is blocked. Fix the failing baseline tests and refresh evidence before continuing.'
|
|
1357
|
+
),
|
|
1358
|
+
];
|
|
1359
|
+
};
|
|
1360
|
+
|
|
1341
1361
|
const toEvidenceSourceDescriptor = (
|
|
1342
1362
|
result: EvidenceReadResult,
|
|
1343
1363
|
repoRoot: string
|
|
@@ -1688,9 +1708,13 @@ export const evaluateAiGate = (
|
|
|
1688
1708
|
evidenceResult,
|
|
1689
1709
|
policy: resolvedPolicy.policy,
|
|
1690
1710
|
});
|
|
1711
|
+
const tddBddBaselineViolations = collectTddBddBaselineViolations({
|
|
1712
|
+
evidenceResult,
|
|
1713
|
+
});
|
|
1691
1714
|
const violations = [
|
|
1692
1715
|
...evidenceAssessment.violations,
|
|
1693
1716
|
...policyThresholdViolations,
|
|
1717
|
+
...tddBddBaselineViolations,
|
|
1694
1718
|
...stageSkillsContractViolations,
|
|
1695
1719
|
...gitflowViolations,
|
|
1696
1720
|
...trackingViolations,
|
|
@@ -19,7 +19,7 @@ export const buildGovernanceValidateCommand = (stage: AiGateStage): string =>
|
|
|
19
19
|
PRE_WRITE_VALIDATE_COMMAND.replace('PRE_WRITE', stage);
|
|
20
20
|
|
|
21
21
|
export const buildGovernancePolicyReconcileCommand = (stage: AiGateStage): string =>
|
|
22
|
-
`npx --yes --package pumuki@latest pumuki policy reconcile --strict --json && ${buildGovernanceValidateCommand(stage)}`;
|
|
22
|
+
`npx --yes --package pumuki@latest pumuki policy reconcile --strict --apply --json && ${buildGovernanceValidateCommand(stage)}`;
|
|
23
23
|
|
|
24
24
|
const buildFallbackAction = (code: string): GovernanceCatalogAction => ({
|
|
25
25
|
reason_code: code,
|
|
@@ -174,6 +174,17 @@ export const resolveGovernanceCatalogAction = (params: {
|
|
|
174
174
|
command: buildGovernancePolicyReconcileCommand(stage),
|
|
175
175
|
},
|
|
176
176
|
};
|
|
177
|
+
case 'POLICY_HASH_DIVERGENCE':
|
|
178
|
+
return {
|
|
179
|
+
reason_code: 'POLICY_HASH_DIVERGENCE',
|
|
180
|
+
instruction: 'Reconcilia policy/skills y aplica el contrato canónico para reducir el drift entre stages.',
|
|
181
|
+
next_action: {
|
|
182
|
+
kind: 'run_command',
|
|
183
|
+
message:
|
|
184
|
+
'Los hashes de policy difieren entre stages. Ejecuta reconcile estricto con apply y vuelve a validar.',
|
|
185
|
+
command: buildGovernancePolicyReconcileCommand(stage),
|
|
186
|
+
},
|
|
187
|
+
};
|
|
177
188
|
case 'SKILLS_CONTRACT_SURFACE_INCOMPLETE':
|
|
178
189
|
case 'EVIDENCE_SKILLS_CONTRACT_INCOMPLETE':
|
|
179
190
|
return {
|
|
@@ -207,6 +218,16 @@ export const resolveGovernanceCatalogAction = (params: {
|
|
|
207
218
|
command: validateCommand,
|
|
208
219
|
},
|
|
209
220
|
};
|
|
221
|
+
case 'TDD_BDD_BASELINE_BLOCKED':
|
|
222
|
+
return {
|
|
223
|
+
reason_code: 'TDD_BDD_BASELINE_BLOCKED',
|
|
224
|
+
instruction: 'Corrige el baseline TDD/BDD roto y regenera la evidencia antes de seguir.',
|
|
225
|
+
next_action: {
|
|
226
|
+
kind: 'run_command',
|
|
227
|
+
message: 'Revalida el stage actual tras reparar el baseline TDD/BDD y refrescar la evidencia.',
|
|
228
|
+
command: validateCommand,
|
|
229
|
+
},
|
|
230
|
+
};
|
|
210
231
|
case 'ACTIVE_RULE_IDS_EMPTY_FOR_CODE_CHANGES_HIGH':
|
|
211
232
|
case 'EVIDENCE_ACTIVE_RULE_IDS_EMPTY_FOR_CODE_CHANGES':
|
|
212
233
|
return {
|
|
@@ -10,12 +10,14 @@ export const REMEDIATION_HINT_BY_CODE: Readonly<Record<string, string>> = {
|
|
|
10
10
|
EVIDENCE_STALE: 'Refresca evidencia antes de continuar.',
|
|
11
11
|
EVIDENCE_REPO_ROOT_MISMATCH: 'Regenera evidencia desde este mismo repositorio.',
|
|
12
12
|
EVIDENCE_BRANCH_MISMATCH: 'Regenera evidencia en la rama actual y reintenta.',
|
|
13
|
+
TDD_BDD_BASELINE_BLOCKED:
|
|
14
|
+
'Corrige el baseline TDD/BDD roto y regenera la evidencia antes de continuar.',
|
|
13
15
|
EVIDENCE_RULES_COVERAGE_MISSING: 'Ejecuta auditoría completa para recalcular rules_coverage.',
|
|
14
16
|
EVIDENCE_RULES_COVERAGE_INCOMPLETE: 'Asegura coverage_ratio=1 y unevaluated=0.',
|
|
15
17
|
ACTIVE_RULE_IDS_EMPTY_FOR_CODE_CHANGES_HIGH:
|
|
16
|
-
'Reconcilia policy/skills y reintenta PRE_COMMIT: npx --yes --package pumuki@latest pumuki policy reconcile --strict --json && npx --yes --package pumuki@latest pumuki-pre-commit',
|
|
18
|
+
'Reconcilia policy/skills y reintenta PRE_COMMIT: npx --yes --package pumuki@latest pumuki policy reconcile --strict --apply --json && npx --yes --package pumuki@latest pumuki-pre-commit',
|
|
17
19
|
EVIDENCE_ACTIVE_RULE_IDS_EMPTY_FOR_CODE_CHANGES:
|
|
18
|
-
'Reconcilia policy/skills y revalida PRE_WRITE: npx --yes --package pumuki@latest pumuki policy reconcile --strict --json && npx --yes --package pumuki@latest pumuki sdd validate --stage=PRE_WRITE --json',
|
|
20
|
+
'Reconcilia policy/skills y revalida PRE_WRITE: npx --yes --package pumuki@latest pumuki policy reconcile --strict --apply --json && npx --yes --package pumuki@latest pumuki sdd validate --stage=PRE_WRITE --json',
|
|
19
21
|
GITFLOW_PROTECTED_BRANCH: 'Trabaja en feature/* y evita ramas protegidas.',
|
|
20
22
|
GITFLOW_BRANCH_NAMING_INVALID:
|
|
21
23
|
'Renombra o recrea la rama con un prefijo GitFlow válido (feature/*, bugfix/*, hotfix/*, release/*, chore/*, refactor/* o docs/*).',
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DEFAULT_DEGRADED_MODE_ACTION_ALLOW,
|
|
3
|
+
DEFAULT_DEGRADED_MODE_ACTION_BLOCK,
|
|
4
|
+
DEFAULT_GATE_AUDIT_MODE,
|
|
5
|
+
DEFAULT_LIST_SEPARATOR,
|
|
6
|
+
DEFAULT_MEMORY_SHADOW_DISPLAY_PRECISION,
|
|
7
|
+
DEFAULT_MEMORY_SHADOW_CONFIDENCE_ALLOW,
|
|
8
|
+
DEFAULT_MEMORY_SHADOW_CONFIDENCE_BLOCK,
|
|
9
|
+
DEFAULT_MEMORY_SHADOW_CONFIDENCE_WARN,
|
|
10
|
+
DEFAULT_MEMORY_SHADOW_CONFIDENCE_WARN_ADVISORY,
|
|
11
|
+
DEFAULT_RULES_COVERAGE_RATIO_DECIMALS,
|
|
12
|
+
LIFECYCLE_GATE_STAGES,
|
|
13
|
+
MAX_IOS_TEST_QUALITY_SAMPLE_FILES,
|
|
14
|
+
MAX_OBSERVED_CODE_PATHS_SAMPLE,
|
|
15
|
+
MAX_SCOPE_SAMPLE_PATHS,
|
|
16
|
+
} from './runPlatformGateDefaults';
|
|
17
|
+
|
|
18
|
+
const parseConfidence = (value: string | undefined, fallback: number): number => {
|
|
19
|
+
const parsed = Number.parseFloat(value ?? '');
|
|
20
|
+
return Number.isFinite(parsed) ? parsed : fallback;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const LIST_SEPARATOR = DEFAULT_LIST_SEPARATOR;
|
|
24
|
+
|
|
25
|
+
export const MEMORY_SHADOW_CONFIDENCE_BLOCK = parseConfidence(
|
|
26
|
+
process.env.PUMUKI_MEMORY_SHADOW_CONFIDENCE_BLOCK,
|
|
27
|
+
DEFAULT_MEMORY_SHADOW_CONFIDENCE_BLOCK
|
|
28
|
+
);
|
|
29
|
+
export const MEMORY_SHADOW_CONFIDENCE_WARN = parseConfidence(
|
|
30
|
+
process.env.PUMUKI_MEMORY_SHADOW_CONFIDENCE_WARN,
|
|
31
|
+
DEFAULT_MEMORY_SHADOW_CONFIDENCE_WARN
|
|
32
|
+
);
|
|
33
|
+
export const MEMORY_SHADOW_CONFIDENCE_WARN_ADVISORY = parseConfidence(
|
|
34
|
+
process.env.PUMUKI_MEMORY_SHADOW_CONFIDENCE_WARN_ADVISORY,
|
|
35
|
+
DEFAULT_MEMORY_SHADOW_CONFIDENCE_WARN_ADVISORY
|
|
36
|
+
);
|
|
37
|
+
export const MEMORY_SHADOW_CONFIDENCE_ALLOW = parseConfidence(
|
|
38
|
+
process.env.PUMUKI_MEMORY_SHADOW_CONFIDENCE_ALLOW,
|
|
39
|
+
DEFAULT_MEMORY_SHADOW_CONFIDENCE_ALLOW
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
export const DEGRADED_MODE_ACTION_BLOCK =
|
|
43
|
+
process.env.PUMUKI_DEGRADED_MODE_ACTION_BLOCK?.trim() || DEFAULT_DEGRADED_MODE_ACTION_BLOCK;
|
|
44
|
+
export const DEGRADED_MODE_ACTION_ALLOW =
|
|
45
|
+
process.env.PUMUKI_DEGRADED_MODE_ACTION_ALLOW?.trim() || DEFAULT_DEGRADED_MODE_ACTION_ALLOW;
|
|
46
|
+
|
|
47
|
+
export {
|
|
48
|
+
DEFAULT_GATE_AUDIT_MODE,
|
|
49
|
+
DEFAULT_MEMORY_SHADOW_DISPLAY_PRECISION,
|
|
50
|
+
DEFAULT_RULES_COVERAGE_RATIO_DECIMALS,
|
|
51
|
+
LIFECYCLE_GATE_STAGES,
|
|
52
|
+
MAX_IOS_TEST_QUALITY_SAMPLE_FILES,
|
|
53
|
+
MAX_OBSERVED_CODE_PATHS_SAMPLE,
|
|
54
|
+
MAX_SCOPE_SAMPLE_PATHS,
|
|
55
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export const DEFAULT_LIST_SEPARATOR = ', ';
|
|
2
|
+
|
|
3
|
+
export const DEFAULT_MEMORY_SHADOW_CONFIDENCE_BLOCK = 0.9;
|
|
4
|
+
export const DEFAULT_MEMORY_SHADOW_CONFIDENCE_WARN = 0.75;
|
|
5
|
+
export const DEFAULT_MEMORY_SHADOW_CONFIDENCE_WARN_ADVISORY = 0.7;
|
|
6
|
+
export const DEFAULT_MEMORY_SHADOW_CONFIDENCE_ALLOW = 0.65;
|
|
7
|
+
|
|
8
|
+
export const DEFAULT_DEGRADED_MODE_ACTION_BLOCK = 'block' as const;
|
|
9
|
+
export const DEFAULT_DEGRADED_MODE_ACTION_ALLOW = 'allow' as const;
|
|
10
|
+
|
|
11
|
+
export const DEFAULT_RULES_COVERAGE_RATIO_DECIMALS = 6;
|
|
12
|
+
export const DEFAULT_GATE_AUDIT_MODE = 'gate' as const;
|
|
13
|
+
export const DEFAULT_MEMORY_SHADOW_DISPLAY_PRECISION = 2;
|
|
14
|
+
|
|
15
|
+
export const MAX_IOS_TEST_QUALITY_SAMPLE_FILES = 3;
|
|
16
|
+
export const MAX_OBSERVED_CODE_PATHS_SAMPLE = 5;
|
|
17
|
+
export const MAX_SCOPE_SAMPLE_PATHS = 3;
|
|
18
|
+
|
|
19
|
+
export const LIFECYCLE_GATE_STAGES = ['PRE_COMMIT', 'PRE_PUSH', 'CI'] as const;
|
|
@@ -32,13 +32,29 @@ import { createEmptyEvaluationMetrics } from '../evidence/evaluationMetrics';
|
|
|
32
32
|
import { createEmptySnapshotRulesCoverage } from '../evidence/rulesCoverage';
|
|
33
33
|
import { enforceTddBddPolicy } from '../tdd/enforcement';
|
|
34
34
|
import type { TddBddSnapshot } from '../tdd/types';
|
|
35
|
-
import { resolveSkillsEnforcement } from '../policy/skillsEnforcement';
|
|
36
35
|
import { applyTddBddEnforcement } from '../policy/tddBddEnforcement';
|
|
37
36
|
import { collectAiGateRepoPolicyFindings } from './aiGateRepoPolicyFindings';
|
|
38
37
|
import {
|
|
39
38
|
filterFactsByPathPrefixes,
|
|
40
39
|
resolveGateScopePathPrefixesFromEnv,
|
|
41
40
|
} from './filterFactsByPathPrefixes';
|
|
41
|
+
import {
|
|
42
|
+
DEFAULT_MEMORY_SHADOW_DISPLAY_PRECISION,
|
|
43
|
+
DEGRADED_MODE_ACTION_ALLOW,
|
|
44
|
+
DEGRADED_MODE_ACTION_BLOCK,
|
|
45
|
+
DEFAULT_GATE_AUDIT_MODE,
|
|
46
|
+
DEFAULT_RULES_COVERAGE_RATIO_DECIMALS,
|
|
47
|
+
LIFECYCLE_GATE_STAGES,
|
|
48
|
+
MAX_IOS_TEST_QUALITY_SAMPLE_FILES,
|
|
49
|
+
MAX_OBSERVED_CODE_PATHS_SAMPLE,
|
|
50
|
+
MAX_SCOPE_SAMPLE_PATHS,
|
|
51
|
+
LIST_SEPARATOR,
|
|
52
|
+
MEMORY_SHADOW_CONFIDENCE_ALLOW,
|
|
53
|
+
MEMORY_SHADOW_CONFIDENCE_BLOCK,
|
|
54
|
+
MEMORY_SHADOW_CONFIDENCE_WARN,
|
|
55
|
+
MEMORY_SHADOW_CONFIDENCE_WARN_ADVISORY,
|
|
56
|
+
} from '../gate/runPlatformGateConfig';
|
|
57
|
+
import type { Severity } from '../../core/rules/Severity';
|
|
42
58
|
|
|
43
59
|
export type OperationalMemoryShadowRecommendation = {
|
|
44
60
|
recommendedOutcome: 'ALLOW' | 'WARN' | 'BLOCK';
|
|
@@ -80,13 +96,18 @@ const defaultServices: GateServices = {
|
|
|
80
96
|
evidence: new EvidenceService(),
|
|
81
97
|
};
|
|
82
98
|
|
|
99
|
+
const SEVERITY_CRITICAL: Severity = 'CRITICAL';
|
|
100
|
+
const SEVERITY_ERROR: Severity = 'ERROR';
|
|
101
|
+
const SEVERITY_WARN: Severity = 'WARN';
|
|
83
102
|
const buildDefaultMemoryShadowRecommendation = (params: {
|
|
84
103
|
findings: ReadonlyArray<Finding>;
|
|
85
104
|
tddBddSnapshot?: TddBddSnapshot;
|
|
86
105
|
}): OperationalMemoryShadowRecommendation | undefined => {
|
|
87
|
-
const hasCritical = params.findings.some(
|
|
88
|
-
|
|
89
|
-
|
|
106
|
+
const hasCritical = params.findings.some(
|
|
107
|
+
(finding) => finding.severity === SEVERITY_CRITICAL
|
|
108
|
+
);
|
|
109
|
+
const hasError = params.findings.some((finding) => finding.severity === SEVERITY_ERROR);
|
|
110
|
+
const hasWarn = params.findings.some((finding) => finding.severity === SEVERITY_WARN);
|
|
90
111
|
const reasonCodes: string[] = [];
|
|
91
112
|
|
|
92
113
|
if (hasCritical || hasError) {
|
|
@@ -108,27 +129,27 @@ const buildDefaultMemoryShadowRecommendation = (params: {
|
|
|
108
129
|
if (hasCritical || hasError || params.tddBddSnapshot?.status === 'blocked') {
|
|
109
130
|
return {
|
|
110
131
|
recommendedOutcome: 'BLOCK',
|
|
111
|
-
confidence:
|
|
132
|
+
confidence: MEMORY_SHADOW_CONFIDENCE_BLOCK,
|
|
112
133
|
reasonCodes,
|
|
113
134
|
};
|
|
114
135
|
}
|
|
115
136
|
if (hasWarn) {
|
|
116
137
|
return {
|
|
117
138
|
recommendedOutcome: 'WARN',
|
|
118
|
-
confidence:
|
|
139
|
+
confidence: MEMORY_SHADOW_CONFIDENCE_WARN,
|
|
119
140
|
reasonCodes,
|
|
120
141
|
};
|
|
121
142
|
}
|
|
122
143
|
if (params.tddBddSnapshot?.status === 'advisory') {
|
|
123
144
|
return {
|
|
124
145
|
recommendedOutcome: 'WARN',
|
|
125
|
-
confidence:
|
|
146
|
+
confidence: MEMORY_SHADOW_CONFIDENCE_WARN_ADVISORY,
|
|
126
147
|
reasonCodes,
|
|
127
148
|
};
|
|
128
149
|
}
|
|
129
150
|
return {
|
|
130
151
|
recommendedOutcome: 'ALLOW',
|
|
131
|
-
confidence:
|
|
152
|
+
confidence: MEMORY_SHADOW_CONFIDENCE_ALLOW,
|
|
132
153
|
reasonCodes,
|
|
133
154
|
};
|
|
134
155
|
};
|
|
@@ -202,7 +223,10 @@ const toRulesCoverageBlockingFinding = (params: {
|
|
|
202
223
|
}
|
|
203
224
|
const active = params.activeRuleIds.length;
|
|
204
225
|
const evaluated = params.evaluatedRuleIds.length;
|
|
205
|
-
const coverageRatio =
|
|
226
|
+
const coverageRatio =
|
|
227
|
+
active === 0
|
|
228
|
+
? 1
|
|
229
|
+
: Number((evaluated / active).toFixed(DEFAULT_RULES_COVERAGE_RATIO_DECIMALS));
|
|
206
230
|
const unevaluatedRuleIds = [...params.unevaluatedRuleIds].sort().join(', ');
|
|
207
231
|
|
|
208
232
|
return {
|
|
@@ -233,7 +257,7 @@ const toSkillsUnsupportedAutoRulesBlockingFinding = (params: {
|
|
|
233
257
|
return undefined;
|
|
234
258
|
}
|
|
235
259
|
|
|
236
|
-
const unsupportedRuleIdsToken = unsupportedRuleIds.join(
|
|
260
|
+
const unsupportedRuleIdsToken = unsupportedRuleIds.join(LIST_SEPARATOR);
|
|
237
261
|
|
|
238
262
|
return {
|
|
239
263
|
ruleId: 'governance.skills.detector-mapping.incomplete',
|
|
@@ -454,7 +478,9 @@ const toIosTestsQualityBlockingFinding = (params: {
|
|
|
454
478
|
return undefined;
|
|
455
479
|
}
|
|
456
480
|
|
|
457
|
-
const sampleFiles = invalidFiles
|
|
481
|
+
const sampleFiles = invalidFiles
|
|
482
|
+
.slice(0, MAX_IOS_TEST_QUALITY_SAMPLE_FILES)
|
|
483
|
+
.join(' | ');
|
|
458
484
|
return {
|
|
459
485
|
ruleId: 'governance.skills.ios-test-quality.incomplete',
|
|
460
486
|
severity: 'ERROR',
|
|
@@ -480,7 +506,7 @@ const toActiveRulesEmptyForCodeChangesBlockingFinding = (params: {
|
|
|
480
506
|
if (codePaths.length === 0) {
|
|
481
507
|
return undefined;
|
|
482
508
|
}
|
|
483
|
-
const samplePaths = codePaths.slice(0,
|
|
509
|
+
const samplePaths = codePaths.slice(0, MAX_OBSERVED_CODE_PATHS_SAMPLE).join(', ');
|
|
484
510
|
return {
|
|
485
511
|
ruleId: 'governance.rules.active-rule-coverage.empty',
|
|
486
512
|
severity: 'ERROR',
|
|
@@ -564,7 +590,7 @@ const toSkillsScopeComplianceBlockingFinding = (params: {
|
|
|
564
590
|
if (!hasEvaluatedRules) {
|
|
565
591
|
reasons.push(`evaluated_rules_prefix=${prefix} missing`);
|
|
566
592
|
}
|
|
567
|
-
const samplePaths = scopePaths.slice(0,
|
|
593
|
+
const samplePaths = scopePaths.slice(0, MAX_SCOPE_SAMPLE_PATHS).join(', ');
|
|
568
594
|
missingScopes.push(`${scope}{${reasons.join('; ')} sample_paths=[${samplePaths}]}`);
|
|
569
595
|
}
|
|
570
596
|
|
|
@@ -616,13 +642,13 @@ const toDegradedModeFinding = (params: {
|
|
|
616
642
|
if (!degraded?.enabled) {
|
|
617
643
|
return undefined;
|
|
618
644
|
}
|
|
619
|
-
if (degraded.action ===
|
|
645
|
+
if (degraded.action === DEGRADED_MODE_ACTION_BLOCK) {
|
|
620
646
|
return {
|
|
621
647
|
ruleId: 'governance.degraded-mode.blocked',
|
|
622
648
|
severity: 'ERROR',
|
|
623
649
|
code: degraded.code,
|
|
624
650
|
message:
|
|
625
|
-
`Degraded mode is active at ${params.stage} with fail-closed action
|
|
651
|
+
`Degraded mode is active at ${params.stage} with fail-closed action=${DEGRADED_MODE_ACTION_BLOCK}. ` +
|
|
626
652
|
`reason=${degraded.reason} source=${degraded.source}.`,
|
|
627
653
|
filePath: '.pumuki/degraded-mode.json',
|
|
628
654
|
matchedBy: 'DegradedModeGuard',
|
|
@@ -634,7 +660,7 @@ const toDegradedModeFinding = (params: {
|
|
|
634
660
|
severity: 'INFO',
|
|
635
661
|
code: degraded.code,
|
|
636
662
|
message:
|
|
637
|
-
`Degraded mode is active at ${params.stage} with fail-open action
|
|
663
|
+
`Degraded mode is active at ${params.stage} with fail-open action=${DEGRADED_MODE_ACTION_ALLOW}. ` +
|
|
638
664
|
`reason=${degraded.reason} source=${degraded.source}.`,
|
|
639
665
|
filePath: '.pumuki/degraded-mode.json',
|
|
640
666
|
matchedBy: 'DegradedModeGuard',
|
|
@@ -775,7 +801,6 @@ const toCrossPlatformCriticalEnforcementBlockingFinding = (params: {
|
|
|
775
801
|
.sort();
|
|
776
802
|
|
|
777
803
|
if (criticalSkillRules.length === 0) {
|
|
778
|
-
gaps.push(`${platform}{critical_profile_rules=missing}`);
|
|
779
804
|
continue;
|
|
780
805
|
}
|
|
781
806
|
|
|
@@ -819,35 +844,9 @@ const applySkillsFindingEnforcement = (
|
|
|
819
844
|
if (!finding) {
|
|
820
845
|
return undefined;
|
|
821
846
|
}
|
|
822
|
-
const skillsEnforcement = resolveSkillsEnforcement();
|
|
823
|
-
if (skillsEnforcement.blocking) {
|
|
824
|
-
return finding;
|
|
825
|
-
}
|
|
826
847
|
return {
|
|
827
848
|
...finding,
|
|
828
|
-
severity: '
|
|
829
|
-
};
|
|
830
|
-
};
|
|
831
|
-
|
|
832
|
-
const toSoftPreCommitSkillsFinding = (params: {
|
|
833
|
-
finding: Finding | undefined;
|
|
834
|
-
enabled: boolean;
|
|
835
|
-
observedCodePaths: ReadonlyArray<string>;
|
|
836
|
-
}): Finding | undefined => {
|
|
837
|
-
if (!params.finding) {
|
|
838
|
-
return undefined;
|
|
839
|
-
}
|
|
840
|
-
if (!params.enabled || !shouldBlockFromFinding(params.finding)) {
|
|
841
|
-
return params.finding;
|
|
842
|
-
}
|
|
843
|
-
return {
|
|
844
|
-
...params.finding,
|
|
845
|
-
severity: 'WARN',
|
|
846
|
-
code: `${params.finding.code}_SOFT_PRECOMMIT`,
|
|
847
|
-
message:
|
|
848
|
-
`${params.finding.message} ` +
|
|
849
|
-
`Soft-enforced at PRE_COMMIT for low-risk scope (observed_code_paths=${params.observedCodePaths.length}). ` +
|
|
850
|
-
'Strict enforcement remains active at PRE_PUSH/CI.',
|
|
849
|
+
severity: 'ERROR',
|
|
851
850
|
};
|
|
852
851
|
};
|
|
853
852
|
|
|
@@ -869,7 +868,7 @@ export async function runPlatformGate(params: {
|
|
|
869
868
|
...params.dependencies,
|
|
870
869
|
};
|
|
871
870
|
const repoRoot = git.resolveRepoRoot();
|
|
872
|
-
const auditMode = params.auditMode ??
|
|
871
|
+
const auditMode = params.auditMode ?? DEFAULT_GATE_AUDIT_MODE;
|
|
873
872
|
const shouldShortCircuitSdd = params.sddShortCircuit ?? false;
|
|
874
873
|
let sddDecision:
|
|
875
874
|
| Pick<SddDecision, 'allowed' | 'code' | 'message'>
|
|
@@ -1082,15 +1081,12 @@ export async function runPlatformGate(params: {
|
|
|
1082
1081
|
policyTrace: params.policyTrace,
|
|
1083
1082
|
})
|
|
1084
1083
|
: undefined;
|
|
1085
|
-
const degradedModeFinding =
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
policyTrace: params.policyTrace,
|
|
1092
|
-
})
|
|
1093
|
-
: undefined;
|
|
1084
|
+
const degradedModeFinding = LIFECYCLE_GATE_STAGES.includes(params.policy.stage)
|
|
1085
|
+
? toDegradedModeFinding({
|
|
1086
|
+
stage: params.policy.stage,
|
|
1087
|
+
policyTrace: params.policyTrace,
|
|
1088
|
+
})
|
|
1089
|
+
: undefined;
|
|
1094
1090
|
const astIntelligenceDualValidation:
|
|
1095
1091
|
| AstIntelligenceDualValidationResult
|
|
1096
1092
|
| undefined =
|
|
@@ -1120,7 +1116,8 @@ export async function runPlatformGate(params: {
|
|
|
1120
1116
|
);
|
|
1121
1117
|
}
|
|
1122
1118
|
}
|
|
1123
|
-
const degradedModeBlocks =
|
|
1119
|
+
const degradedModeBlocks =
|
|
1120
|
+
params.policyTrace?.degraded?.action === DEGRADED_MODE_ACTION_BLOCK;
|
|
1124
1121
|
const rulesCoverage = coverage
|
|
1125
1122
|
? {
|
|
1126
1123
|
stage: params.policy.stage,
|
|
@@ -1184,7 +1181,11 @@ export async function runPlatformGate(params: {
|
|
|
1184
1181
|
coverage_ratio:
|
|
1185
1182
|
coverage.activeRuleIds.length === 0
|
|
1186
1183
|
? 1
|
|
1187
|
-
: Number(
|
|
1184
|
+
: Number(
|
|
1185
|
+
(
|
|
1186
|
+
coverage.evaluatedRuleIds.length / coverage.activeRuleIds.length
|
|
1187
|
+
).toFixed(DEFAULT_RULES_COVERAGE_RATIO_DECIMALS)
|
|
1188
|
+
),
|
|
1188
1189
|
}
|
|
1189
1190
|
: createEmptySnapshotRulesCoverage(params.policy.stage);
|
|
1190
1191
|
const brownfieldHotspotFindings = dependencies.evaluateBrownfieldHotspotFindings({
|
|
@@ -1209,37 +1210,9 @@ export async function runPlatformGate(params: {
|
|
|
1209
1210
|
const hasNativeBlockingFinding = findings.some(
|
|
1210
1211
|
(finding) => finding.severity === 'ERROR' || finding.severity === 'CRITICAL'
|
|
1211
1212
|
);
|
|
1212
|
-
const
|
|
1213
|
-
const
|
|
1214
|
-
const
|
|
1215
|
-
params.policy.stage === 'PRE_COMMIT'
|
|
1216
|
-
&& preCommitSoftSkillsEnabled
|
|
1217
|
-
&& lowRiskPreCommitWindow
|
|
1218
|
-
&& !sddBlockingFinding
|
|
1219
|
-
&& !degradedModeBlocks
|
|
1220
|
-
&& !shouldBlockFromFinding(policyAsCodeBlockingFinding)
|
|
1221
|
-
&& !shouldBlockFromFinding(effectiveUnsupportedSkillsMappingFinding)
|
|
1222
|
-
&& !shouldBlockFromFinding(coverageBlockingFinding)
|
|
1223
|
-
&& !shouldBlockFromFinding(activeRulesEmptyForCodeChangesFinding)
|
|
1224
|
-
&& !shouldBlockFromFinding(effectiveIosTestsQualityFinding)
|
|
1225
|
-
&& !shouldBlockFromFinding(astIntelligenceDualFinding)
|
|
1226
|
-
&& !hasTddBddBlockingFinding
|
|
1227
|
-
&& !hasNativeBlockingFinding;
|
|
1228
|
-
const effectivePlatformSkillsCoverageFinding = toSoftPreCommitSkillsFinding({
|
|
1229
|
-
finding: effectivePlatformSkillsCoverageInput,
|
|
1230
|
-
enabled: shouldSoftEnforceSkillsFindings,
|
|
1231
|
-
observedCodePaths,
|
|
1232
|
-
});
|
|
1233
|
-
const effectiveCrossPlatformCriticalFinding = toSoftPreCommitSkillsFinding({
|
|
1234
|
-
finding: effectiveCrossPlatformCriticalInput,
|
|
1235
|
-
enabled: shouldSoftEnforceSkillsFindings,
|
|
1236
|
-
observedCodePaths,
|
|
1237
|
-
});
|
|
1238
|
-
const effectiveSkillsScopeComplianceFinding = toSoftPreCommitSkillsFinding({
|
|
1239
|
-
finding: effectiveSkillsScopeComplianceInput,
|
|
1240
|
-
enabled: shouldSoftEnforceSkillsFindings,
|
|
1241
|
-
observedCodePaths,
|
|
1242
|
-
});
|
|
1213
|
+
const effectivePlatformSkillsCoverageFinding = effectivePlatformSkillsCoverageInput;
|
|
1214
|
+
const effectiveCrossPlatformCriticalFinding = effectiveCrossPlatformCriticalInput;
|
|
1215
|
+
const effectiveSkillsScopeComplianceFinding = effectiveSkillsScopeComplianceInput;
|
|
1243
1216
|
const effectiveFindings = sddBlockingFinding
|
|
1244
1217
|
? [
|
|
1245
1218
|
sddBlockingFinding,
|
|
@@ -1390,7 +1363,7 @@ export async function runPlatformGate(params: {
|
|
|
1390
1363
|
if (params.silent !== true) {
|
|
1391
1364
|
process.stdout.write(
|
|
1392
1365
|
`[pumuki][memory-shadow] recommended=${memoryShadowRecommendation.recommendedOutcome}` +
|
|
1393
|
-
` confidence=${memoryShadowRecommendation.confidence.toFixed(
|
|
1366
|
+
` confidence=${memoryShadowRecommendation.confidence.toFixed(DEFAULT_MEMORY_SHADOW_DISPLAY_PRECISION)}` +
|
|
1394
1367
|
` reasons=${memoryShadowRecommendation.reasonCodes.join(',')}\n`
|
|
1395
1368
|
);
|
|
1396
1369
|
}
|
|
@@ -9,20 +9,19 @@ const BLOCK_NEXT_ACTION_BY_CODE: Readonly<Record<string, string>> = {
|
|
|
9
9
|
'git push --set-upstream origin <branch>',
|
|
10
10
|
PRE_PUSH_UPSTREAM_MISALIGNED:
|
|
11
11
|
'git branch --unset-upstream && git push --set-upstream origin <branch>',
|
|
12
|
-
EVIDENCE_STALE:
|
|
13
|
-
'npx --yes --package pumuki@latest pumuki-pre-commit',
|
|
14
|
-
EVIDENCE_BRANCH_MISMATCH:
|
|
15
|
-
'npx --yes --package pumuki@latest pumuki-pre-commit',
|
|
16
12
|
GIT_ATOMICITY_TOO_MANY_FILES:
|
|
17
13
|
'git restore --staged . && separa cambios en commits más pequeños',
|
|
18
14
|
GIT_ATOMICITY_TOO_MANY_SCOPES:
|
|
19
15
|
'Revisa scope_files del bloqueo y aplica: git restore --staged . && git add <scope>/ && git commit -m "<tipo>: <scope>"',
|
|
20
16
|
ACTIVE_RULE_IDS_EMPTY_FOR_CODE_CHANGES_HIGH:
|
|
21
|
-
'Reconcilia policy/skills y reintenta PRE_COMMIT: npx --yes --package pumuki@latest pumuki policy reconcile --strict --json && npx --yes --package pumuki@latest pumuki-pre-commit',
|
|
17
|
+
'Reconcilia policy/skills y reintenta PRE_COMMIT: npx --yes --package pumuki@latest pumuki policy reconcile --strict --apply --json && npx --yes --package pumuki@latest pumuki-pre-commit',
|
|
22
18
|
SKILLS_SKILLS_FRONTEND_NO_SOLID_VIOLATIONS:
|
|
23
19
|
'Aplica refactor incremental: extrae 1 componente/hook por commit y vuelve a ejecutar PRE_COMMIT.',
|
|
24
20
|
};
|
|
25
21
|
|
|
22
|
+
const buildStageValidateCommand = (stage: 'PRE_COMMIT' | 'PRE_PUSH' | 'CI' | 'PRE_WRITE'): string =>
|
|
23
|
+
`npx --yes --package pumuki@latest pumuki sdd validate --stage=${stage} --json`;
|
|
24
|
+
|
|
26
25
|
const severityWeight = (severity: string): number => {
|
|
27
26
|
switch (severity.toUpperCase()) {
|
|
28
27
|
case 'CRITICAL':
|
|
@@ -103,15 +102,21 @@ const formatFinding = (finding: Finding): string => {
|
|
|
103
102
|
return `[${severity}] ${finding.ruleId}: ${finding.message} -> ${location}`;
|
|
104
103
|
};
|
|
105
104
|
|
|
106
|
-
export const printGateFindings = (
|
|
105
|
+
export const printGateFindings = (
|
|
106
|
+
findings: ReadonlyArray<Finding>,
|
|
107
|
+
params?: { stage?: 'PRE_WRITE' | 'PRE_COMMIT' | 'PRE_PUSH' | 'CI' }
|
|
108
|
+
): void => {
|
|
107
109
|
if (findings.length === 0) {
|
|
108
110
|
return;
|
|
109
111
|
}
|
|
110
112
|
const orderedFindings = sortFindingsBySeverity(findings);
|
|
111
113
|
const primary = resolvePrimaryFinding(orderedFindings);
|
|
114
|
+
const stage = params?.stage ?? 'PRE_COMMIT';
|
|
112
115
|
const nextAction =
|
|
113
|
-
|
|
114
|
-
|
|
116
|
+
primary.code === 'EVIDENCE_STALE' || primary.code === 'EVIDENCE_BRANCH_MISMATCH'
|
|
117
|
+
? buildStageValidateCommand(stage)
|
|
118
|
+
: BLOCK_NEXT_ACTION_BY_CODE[primary.code]
|
|
119
|
+
?? 'Corrige el bloqueante primario y vuelve a ejecutar el mismo comando.';
|
|
115
120
|
process.stdout.write(
|
|
116
121
|
`[pumuki][block-summary] primary=${primary.code} severity=${primary.severity.toUpperCase()} rule=${primary.ruleId}\n`
|
|
117
122
|
);
|
|
@@ -55,7 +55,7 @@ type LifecycleAuditDependencies = {
|
|
|
55
55
|
};
|
|
56
56
|
|
|
57
57
|
const POLICY_RECONCILE_HINT =
|
|
58
|
-
'If .pumuki/policy-as-code.json signatures drift after a pumuki upgrade, run: pumuki policy reconcile --apply';
|
|
58
|
+
'If .pumuki/policy-as-code.json signatures drift after a pumuki upgrade, run: pumuki policy reconcile --strict --apply --json';
|
|
59
59
|
|
|
60
60
|
const countUntrackedMatchingExtensions = (
|
|
61
61
|
git: Pick<IGitService, 'resolveRepoRoot' | 'runGit'>,
|
|
@@ -1650,7 +1650,7 @@ const buildPreWriteValidateCommand = (params: {
|
|
|
1650
1650
|
const buildPreWritePolicyReconcileCommand = (repoRoot?: string): string =>
|
|
1651
1651
|
`${buildPinnedPumukiNpxCommand({
|
|
1652
1652
|
repoRoot,
|
|
1653
|
-
executableAndArgs: 'pumuki policy reconcile --strict --json',
|
|
1653
|
+
executableAndArgs: 'pumuki policy reconcile --strict --apply --json',
|
|
1654
1654
|
})} && ${buildPreWriteValidateCommand({ repoRoot, stage: 'PRE_WRITE' })}`;
|
|
1655
1655
|
|
|
1656
1656
|
type PreWriteValidationEnvelope = {
|
|
@@ -1838,11 +1838,13 @@ const PRE_WRITE_HINTS_BY_CODE: Readonly<Record<string, string>> = {
|
|
|
1838
1838
|
EVIDENCE_RULES_COVERAGE_MISSING: 'Ejecuta auditoría completa para recalcular rules_coverage.',
|
|
1839
1839
|
EVIDENCE_RULES_COVERAGE_INCOMPLETE: 'Asegura unevaluated=0 y coverage_ratio=1.',
|
|
1840
1840
|
EVIDENCE_ACTIVE_RULE_IDS_EMPTY_FOR_CODE_CHANGES:
|
|
1841
|
-
'No hay active_rule_ids para plataforma de código detectada. Reconcilia policy/skills en modo estricto y revalida PRE_WRITE.',
|
|
1841
|
+
'No hay active_rule_ids para plataforma de código detectada. Reconcilia policy/skills en modo estricto con apply y revalida PRE_WRITE.',
|
|
1842
1842
|
EVIDENCE_PLATFORM_CRITICAL_SKILLS_RULES_MISSING:
|
|
1843
|
-
'Reconcilia policy/skills en modo estricto y materializa reglas críticas (p.ej. skills.ios.critical-test-quality).',
|
|
1843
|
+
'Reconcilia policy/skills en modo estricto con apply y materializa reglas críticas (p.ej. skills.ios.critical-test-quality).',
|
|
1844
1844
|
EVIDENCE_CROSS_PLATFORM_CRITICAL_ENFORCEMENT_INCOMPLETE:
|
|
1845
|
-
'Reconcilia policy/skills en modo estricto para completar enforcement crítico transversal.',
|
|
1845
|
+
'Reconcilia policy/skills en modo estricto con apply para completar enforcement crítico transversal.',
|
|
1846
|
+
TDD_BDD_BASELINE_BLOCKED:
|
|
1847
|
+
'Corrige el baseline TDD/BDD roto y regenera la evidencia antes de continuar.',
|
|
1846
1848
|
EVIDENCE_SKILLS_CONTRACT_INCOMPLETE:
|
|
1847
1849
|
'Completa contrato skills/policy para el stage actual y vuelve a validar.',
|
|
1848
1850
|
EVIDENCE_PREWRITE_WORKTREE_OVER_LIMIT:
|
|
@@ -834,7 +834,7 @@ const buildPolicySignatureRemediation = (
|
|
|
834
834
|
const mismatch = Object.values(policyValidation.stages).some(
|
|
835
835
|
(stage) => stage.validationCode === 'POLICY_AS_CODE_SIGNATURE_MISMATCH'
|
|
836
836
|
);
|
|
837
|
-
return mismatch ? 'pumuki policy reconcile --apply' : undefined;
|
|
837
|
+
return mismatch ? 'pumuki policy reconcile --strict --apply --json' : undefined;
|
|
838
838
|
};
|
|
839
839
|
|
|
840
840
|
export const runLifecycleDoctor = (params?: {
|
|
@@ -91,6 +91,16 @@ const resolveBlockedAction = (
|
|
|
91
91
|
message: 'La política efectiva todavía no es estricta en todos los stages requeridos.',
|
|
92
92
|
};
|
|
93
93
|
}
|
|
94
|
+
if (snapshot.attention_codes.includes('POLICY_HASH_DIVERGENCE')) {
|
|
95
|
+
return {
|
|
96
|
+
stage,
|
|
97
|
+
phase: 'RED',
|
|
98
|
+
action: 'ask',
|
|
99
|
+
confidence_pct: 58,
|
|
100
|
+
...resolveGovernanceCatalogAction({ code: 'POLICY_HASH_DIVERGENCE', stage }),
|
|
101
|
+
message: 'Los hashes de policy difieren entre stages; reconcilia y aplica el contrato canónico.',
|
|
102
|
+
};
|
|
103
|
+
}
|
|
94
104
|
if (!snapshot.contract_surface.skills_lock_json || !snapshot.contract_surface.skills_sources_json) {
|
|
95
105
|
return {
|
|
96
106
|
stage,
|
|
@@ -263,6 +263,12 @@ export const readGovernanceObservationSnapshot = (params: {
|
|
|
263
263
|
if (!policyValidation.stages.CI.strict) {
|
|
264
264
|
attention.push('POLICY_CI_NOT_STRICT');
|
|
265
265
|
}
|
|
266
|
+
const policyHashes = new Set(
|
|
267
|
+
Object.values(policyValidation.stages).map((stage) => stage.hash)
|
|
268
|
+
);
|
|
269
|
+
if (policyHashes.size > 1) {
|
|
270
|
+
attention.push('POLICY_HASH_DIVERGENCE');
|
|
271
|
+
}
|
|
266
272
|
if (onProtected) {
|
|
267
273
|
attention.push('GITFLOW_PROTECTED_BRANCH_CONTEXT');
|
|
268
274
|
}
|
|
@@ -94,7 +94,7 @@ const nextActionFromViolation = (
|
|
|
94
94
|
command:
|
|
95
95
|
`${buildPinnedPumukiNpxCommand({
|
|
96
96
|
repoRoot,
|
|
97
|
-
executableAndArgs: 'pumuki policy reconcile --strict --json',
|
|
97
|
+
executableAndArgs: 'pumuki policy reconcile --strict --apply --json',
|
|
98
98
|
})} && ${buildPinnedPumukiNpxCommand({
|
|
99
99
|
repoRoot,
|
|
100
100
|
executableAndArgs: 'pumuki sdd validate --stage=PRE_WRITE --json',
|
|
@@ -121,7 +121,7 @@ const nextActionFromViolation = (
|
|
|
121
121
|
command:
|
|
122
122
|
`${buildPinnedPumukiNpxCommand({
|
|
123
123
|
repoRoot,
|
|
124
|
-
executableAndArgs: 'pumuki policy reconcile --strict --json',
|
|
124
|
+
executableAndArgs: 'pumuki policy reconcile --strict --apply --json',
|
|
125
125
|
})} && ${buildPinnedPumukiNpxCommand({
|
|
126
126
|
repoRoot,
|
|
127
127
|
executableAndArgs: 'pumuki sdd validate --stage=PRE_WRITE --json',
|
|
@@ -3,19 +3,30 @@ import {
|
|
|
3
3
|
type GovernanceCatalogNextAction,
|
|
4
4
|
} from '../gate/governanceActionCatalog';
|
|
5
5
|
import { evaluateAiGate, type AiGateStage, type AiGateViolation } from '../gate/evaluateAiGate';
|
|
6
|
+
import { readEvidenceResult } from '../evidence/readEvidence';
|
|
6
7
|
import { collectWorktreeAtomicSlices } from '../git/worktreeAtomicSlices';
|
|
7
8
|
import { readLifecyclePolicyValidationSnapshot } from '../lifecycle/policyValidationSnapshot';
|
|
8
9
|
import { resolveLearningContextExperimentalFeature } from '../policy/experimentalFeatures';
|
|
9
10
|
import { resolvePreWriteEnforcement } from '../policy/preWriteEnforcement';
|
|
10
11
|
import { readSddLearningContext, type SddLearningContext } from '../sdd/learningInsights';
|
|
11
12
|
|
|
13
|
+
const resolveTddStatus = (
|
|
14
|
+
repoRoot: string
|
|
15
|
+
): 'skipped' | 'passed' | 'advisory' | 'blocked' | 'waived' | null => {
|
|
16
|
+
const evidenceResult = readEvidenceResult(repoRoot);
|
|
17
|
+
if (evidenceResult.kind !== 'valid') {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
return evidenceResult.evidence.snapshot.tdd_bdd?.status ?? null;
|
|
21
|
+
};
|
|
22
|
+
|
|
12
23
|
const ACTIONABLE_HINTS_BY_CODE: Readonly<Record<string, string>> = {
|
|
13
24
|
EVIDENCE_MISSING:
|
|
14
25
|
'Ejecuta una auditoría (1/2/3/4 u opciones de motor 11–14) para regenerar .ai_evidence.json.',
|
|
15
26
|
EVIDENCE_INVALID: 'Regenera .ai_evidence.json desde una opción de auditoría.',
|
|
16
27
|
EVIDENCE_INTEGRITY_MISSING: 'Refresca evidencia para regenerar metadatos de integridad.',
|
|
17
28
|
EVIDENCE_ACTIVE_RULE_IDS_EMPTY_FOR_CODE_CHANGES:
|
|
18
|
-
'No hay active_rule_ids para plataforma de código detectada. Ejecuta reconcile --strict y revalida PRE_WRITE.',
|
|
29
|
+
'No hay active_rule_ids para plataforma de código detectada. Ejecuta reconcile --strict --apply y revalida PRE_WRITE.',
|
|
19
30
|
EVIDENCE_STALE: 'Refresca evidencia antes de continuar con commit/push.',
|
|
20
31
|
EVIDENCE_TIMESTAMP_INVALID: 'Regenera evidencia para obtener un timestamp válido.',
|
|
21
32
|
EVIDENCE_GATE_BLOCKED: 'Corrige primero las violaciones bloqueantes y vuelve a auditar.',
|
|
@@ -32,9 +43,11 @@ const ACTIONABLE_HINTS_BY_CODE: Readonly<Record<string, string>> = {
|
|
|
32
43
|
EVIDENCE_PLATFORM_SKILLS_BUNDLES_MISSING:
|
|
33
44
|
'Carga los bundles de skills requeridos por plataforma detectada y regenera evidencia.',
|
|
34
45
|
EVIDENCE_PLATFORM_CRITICAL_SKILLS_RULES_MISSING:
|
|
35
|
-
'Ejecuta `pumuki policy reconcile --strict --json`, materializa reglas críticas (p.ej. skills.ios.critical-test-quality) y revalida PRE_WRITE.',
|
|
46
|
+
'Ejecuta `pumuki policy reconcile --strict --apply --json`, materializa reglas críticas (p.ej. skills.ios.critical-test-quality) y revalida PRE_WRITE.',
|
|
36
47
|
EVIDENCE_CROSS_PLATFORM_CRITICAL_ENFORCEMENT_INCOMPLETE:
|
|
37
|
-
'Reconcilia policy/skills en modo estricto para enforcement crítico transversal y vuelve a validar PRE_WRITE.',
|
|
48
|
+
'Reconcilia policy/skills en modo estricto con apply para enforcement crítico transversal y vuelve a validar PRE_WRITE.',
|
|
49
|
+
TDD_BDD_BASELINE_BLOCKED:
|
|
50
|
+
'Corrige el baseline TDD/BDD roto y regenera la evidencia antes de continuar.',
|
|
38
51
|
EVIDENCE_SKILLS_CONTRACT_INCOMPLETE:
|
|
39
52
|
'Completa el contrato skills/policy para el stage solicitado y vuelve a validar.',
|
|
40
53
|
EVIDENCE_PREWRITE_WORKTREE_OVER_LIMIT:
|
|
@@ -161,7 +174,7 @@ export type EnterprisePreFlightCheckResult = {
|
|
|
161
174
|
hints: ReadonlyArray<string>;
|
|
162
175
|
learning_context: SddLearningContext | null;
|
|
163
176
|
ast_analysis: null;
|
|
164
|
-
tdd_status: null;
|
|
177
|
+
tdd_status: 'skipped' | 'passed' | 'advisory' | 'blocked' | 'waived' | null;
|
|
165
178
|
};
|
|
166
179
|
};
|
|
167
180
|
|
|
@@ -246,7 +259,7 @@ export const runEnterprisePreFlightCheck = (params: {
|
|
|
246
259
|
hints,
|
|
247
260
|
learning_context: learningContext,
|
|
248
261
|
ast_analysis: null,
|
|
249
|
-
tdd_status:
|
|
262
|
+
tdd_status: resolveTddStatus(params.repoRoot),
|
|
250
263
|
},
|
|
251
264
|
};
|
|
252
265
|
};
|
|
@@ -1,55 +1,9 @@
|
|
|
1
|
-
export type SkillsEnforcementMode = 'advisory' | 'strict';
|
|
2
|
-
|
|
3
1
|
export type SkillsEnforcementResolution = {
|
|
4
|
-
mode: SkillsEnforcementMode;
|
|
5
|
-
source: 'default' | 'env';
|
|
6
2
|
blocking: boolean;
|
|
7
3
|
};
|
|
8
4
|
|
|
9
|
-
const SKILLS_ENFORCEMENT_ENV = 'PUMUKI_SKILLS_ENFORCEMENT';
|
|
10
|
-
|
|
11
|
-
const toSkillsEnforcementMode = (
|
|
12
|
-
value: string | undefined
|
|
13
|
-
): SkillsEnforcementMode | null => {
|
|
14
|
-
if (typeof value !== 'string') {
|
|
15
|
-
return null;
|
|
16
|
-
}
|
|
17
|
-
const normalized = value.trim().toLowerCase();
|
|
18
|
-
if (
|
|
19
|
-
normalized === 'strict'
|
|
20
|
-
|| normalized === '1'
|
|
21
|
-
|| normalized === 'true'
|
|
22
|
-
|| normalized === 'yes'
|
|
23
|
-
|| normalized === 'on'
|
|
24
|
-
) {
|
|
25
|
-
return 'strict';
|
|
26
|
-
}
|
|
27
|
-
if (
|
|
28
|
-
normalized === 'advisory'
|
|
29
|
-
|| normalized === 'warn'
|
|
30
|
-
|| normalized === 'warning'
|
|
31
|
-
|| normalized === '0'
|
|
32
|
-
|| normalized === 'false'
|
|
33
|
-
|| normalized === 'no'
|
|
34
|
-
|| normalized === 'off'
|
|
35
|
-
) {
|
|
36
|
-
return 'advisory';
|
|
37
|
-
}
|
|
38
|
-
return null;
|
|
39
|
-
};
|
|
40
|
-
|
|
41
5
|
export const resolveSkillsEnforcement = (): SkillsEnforcementResolution => {
|
|
42
|
-
const modeFromEnv = toSkillsEnforcementMode(process.env[SKILLS_ENFORCEMENT_ENV]);
|
|
43
|
-
if (modeFromEnv) {
|
|
44
|
-
return {
|
|
45
|
-
mode: modeFromEnv,
|
|
46
|
-
source: 'env',
|
|
47
|
-
blocking: modeFromEnv === 'strict',
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
6
|
return {
|
|
51
|
-
mode: 'strict',
|
|
52
|
-
source: 'default',
|
|
53
7
|
blocking: true,
|
|
54
8
|
};
|
|
55
9
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pumuki",
|
|
3
|
-
"version": "6.3.
|
|
3
|
+
"version": "6.3.134",
|
|
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": {
|