pumuki 6.3.133 → 6.3.135
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 +14 -0
- package/docs/operations/RELEASE_NOTES.md +11 -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/git/resolveGitRefs.ts +35 -6
- 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/alignedPlatformGate.ts +20 -4
- package/integrations/mcp/autoExecuteAiStart.ts +2 -2
- package/integrations/mcp/preFlightCheck.ts +18 -5
- package/integrations/policy/skillsEnforcement.ts +0 -6
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,20 @@ This project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [6.3.135] - 2026-05-03
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- **Bootstrap de pre-push por delta real:** cuando una rama no tiene upstream, el bootstrap de `PRE_PUSH` elige la base con menor delta real entre `main` y `develop`, evitando falsos positivos de atomicidad en branches nacidas de `main`.
|
|
14
|
+
- **Repin desbloqueable:** esta versión corrige el bloqueo que impedía publicar el repin de `Flux_training` aunque el diff efectivo del cambio fuese mínimo.
|
|
15
|
+
|
|
16
|
+
## [6.3.134] - 2026-05-03
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- **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.
|
|
21
|
+
- **Release publicada y lista para repin:** esta versión ya está en npm y queda lista para repinear consumers activos como RuralGo con el fix real distribuido.
|
|
22
|
+
|
|
9
23
|
## [6.3.133] - 2026-05-03
|
|
10
24
|
|
|
11
25
|
### Fixed
|
|
@@ -4,6 +4,17 @@ 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.135)
|
|
8
|
+
|
|
9
|
+
- **Bootstrap de pre-push por delta real:** en ramas sin upstream, `PRE_PUSH` elige la base con menor delta real entre `main` y `develop`, evitando que un rollout nacido de `main` se bloquee como si heredara todo `develop`.
|
|
10
|
+
- **Repin desbloqueable:** esta release corrige el falso positivo que impedía publicar el repin de `Flux_training` aunque el diff efectivo fuese mínimo.
|
|
11
|
+
|
|
12
|
+
### 2026-05-03 (v6.3.134)
|
|
13
|
+
|
|
14
|
+
- **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.
|
|
15
|
+
- **Repin recomendado:** publicar `pumuki@6.3.134`, repinear RuralGo y revalidar `status`, `doctor`, `audit --stage=PRE_WRITE --json` y hooks gestionados.
|
|
16
|
+
- **Contrato de release estable:** no hace falta cerrar más gaps funcionales para este fix; la solución ya quedó validada localmente y la distribución de `6.3.134` ya está publicada.
|
|
17
|
+
|
|
7
18
|
### 2026-05-03 (v6.3.133)
|
|
8
19
|
|
|
9
20
|
- **Skills hard-blocking end-to-end:** `skillsEnforcement`, `evaluateAiGate`, `runPlatformGate` y el flujo CLI ya no dejan pasar advisory para violations de skills.
|
|
@@ -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/*).',
|
|
@@ -26,6 +26,37 @@ const resolveDefaultCiBaseRef = (): string => {
|
|
|
26
26
|
return 'HEAD';
|
|
27
27
|
};
|
|
28
28
|
|
|
29
|
+
const resolveDiffFileCount = (fromRef: string): number | null => {
|
|
30
|
+
try {
|
|
31
|
+
return runGit(['diff', '--name-only', '--diff-filter=ACMR', `${fromRef}..HEAD`])
|
|
32
|
+
.split('\n')
|
|
33
|
+
.map((line) => line.trim())
|
|
34
|
+
.filter((line) => line.length > 0).length;
|
|
35
|
+
} catch {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const resolveBestBootstrapBaseRef = (): string | null => {
|
|
41
|
+
const candidates = ['origin/main', 'main', 'origin/develop', 'develop'];
|
|
42
|
+
let best: { ref: string; changedFiles: number } | undefined;
|
|
43
|
+
|
|
44
|
+
for (const candidate of candidates) {
|
|
45
|
+
if (!isResolvableRef(candidate)) {
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
const changedFiles = resolveDiffFileCount(candidate);
|
|
49
|
+
if (changedFiles === null) {
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
if (!best || changedFiles < best.changedFiles) {
|
|
53
|
+
best = { ref: candidate, changedFiles };
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return best?.ref ?? null;
|
|
58
|
+
};
|
|
59
|
+
|
|
29
60
|
export const resolveUpstreamRef = (): string | null => {
|
|
30
61
|
try {
|
|
31
62
|
return runGit(['rev-parse', '@{u}']);
|
|
@@ -88,12 +119,10 @@ export const resolveCiBaseRef = (): string => {
|
|
|
88
119
|
};
|
|
89
120
|
|
|
90
121
|
export const resolvePrePushBootstrapBaseRef = (): string => {
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
return candidate;
|
|
95
|
-
}
|
|
122
|
+
const best = resolveBestBootstrapBaseRef();
|
|
123
|
+
if (best) {
|
|
124
|
+
return best;
|
|
96
125
|
}
|
|
97
126
|
|
|
98
|
-
return
|
|
127
|
+
return resolveDefaultCiBaseRef();
|
|
99
128
|
};
|
|
@@ -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
|
}
|
|
@@ -44,14 +44,30 @@ const resolveCiBaseRefInRepo = (repoRoot: string): string => {
|
|
|
44
44
|
};
|
|
45
45
|
|
|
46
46
|
const resolvePrePushBootstrapBaseRefInRepo = (repoRoot: string): string => {
|
|
47
|
-
const candidates = ['origin/
|
|
47
|
+
const candidates = ['origin/main', 'main', 'origin/develop', 'develop'];
|
|
48
|
+
let best: { ref: string; changedFiles: number } | undefined;
|
|
49
|
+
|
|
48
50
|
for (const candidate of candidates) {
|
|
49
|
-
if (runGit(repoRoot, ['rev-parse', '--verify', candidate])) {
|
|
50
|
-
|
|
51
|
+
if (!runGit(repoRoot, ['rev-parse', '--verify', candidate])) {
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
try {
|
|
55
|
+
const changedFiles = runGit(
|
|
56
|
+
repoRoot,
|
|
57
|
+
['diff', '--name-only', '--diff-filter=ACMR', `${candidate}..HEAD`]
|
|
58
|
+
)
|
|
59
|
+
.split('\n')
|
|
60
|
+
.map((line) => line.trim())
|
|
61
|
+
.filter((line) => line.length > 0).length;
|
|
62
|
+
if (!best || changedFiles < best.changedFiles) {
|
|
63
|
+
best = { ref: candidate, changedFiles };
|
|
64
|
+
}
|
|
65
|
+
} catch {
|
|
66
|
+
continue;
|
|
51
67
|
}
|
|
52
68
|
}
|
|
53
69
|
|
|
54
|
-
return
|
|
70
|
+
return best?.ref ?? resolveCiBaseRefInRepo(repoRoot);
|
|
55
71
|
};
|
|
56
72
|
|
|
57
73
|
const shouldAllowBootstrapPrePush = (rawInput: string): boolean => {
|
|
@@ -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,15 +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
5
|
export const resolveSkillsEnforcement = (): SkillsEnforcementResolution => {
|
|
10
6
|
return {
|
|
11
|
-
mode: 'strict',
|
|
12
|
-
source: 'default',
|
|
13
7
|
blocking: true,
|
|
14
8
|
};
|
|
15
9
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pumuki",
|
|
3
|
-
"version": "6.3.
|
|
3
|
+
"version": "6.3.135",
|
|
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": {
|