pumuki 6.3.141 → 6.3.143
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/core/facts/detectors/typescript/index.test.ts +28 -0
- package/core/facts/detectors/typescript/index.ts +4 -0
- package/core/rules/presets/iosEnterpriseRuleSet.test.ts +5 -0
- package/core/rules/presets/iosEnterpriseRuleSet.ts +5 -5
- package/docs/operations/RELEASE_NOTES.md +12 -0
- package/integrations/config/skillsDetectorRegistry.ts +15 -0
- package/integrations/config/skillsMarkdownRules.ts +21 -0
- package/integrations/config/skillsRuleSet.ts +4 -1
- package/integrations/tdd/enforcement.ts +63 -0
- 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.143] - 2026-05-05
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- **PUMUKI-INC-060 baseline TDD/BDD fresco:** los cambios in-scope bloquean si la evidencia de baseline TDD/BDD está caducada, obligando a reejecutar los tests baseline del componente antes de editar código relacionado.
|
|
14
|
+
- **Ventana configurable de evidencia:** `PUMUKI_TDD_BDD_EVIDENCE_MAX_AGE_SECONDS` permite ajustar la frescura máxima; por defecto son 900 segundos y los valores inválidos mantienen el modo estricto.
|
|
15
|
+
|
|
16
|
+
## [6.3.142] - 2026-05-05
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- **PUMUKI-INC-059 iOS SOLID en PRE_WRITE:** la skill iOS `Verificar que NO viole SOLID (SRP, OCP, LSP, ISP, DIP)` se normaliza al id canónico `skills.ios.no-solid-violations` y al alias real del lock legacy, activa los nodos AST OCP/SRP/DIP/ISP/LSP y bloquea desde `PRE_WRITE` sin depender de `PUMUKI_ENABLE_AST_HEURISTICS`.
|
|
21
|
+
- **Skills hard-blocking multi-stage:** `no-solid-violations` se promueve a bloqueo desde `PRE_WRITE`, `PRE_COMMIT`, `PRE_PUSH` y `CI`, evitando que una violación iOS OCP/SRP llegue a disco o al commit.
|
|
22
|
+
|
|
9
23
|
## [6.3.141] - 2026-05-05
|
|
10
24
|
|
|
11
25
|
### Fixed
|
|
@@ -4248,6 +4248,34 @@ test('hasHardcodedValuePattern detecta literals de configuracion y omite valores
|
|
|
4248
4248
|
assert.equal(hasHardcodedValuePattern(neutralLiteralAst), false);
|
|
4249
4249
|
});
|
|
4250
4250
|
|
|
4251
|
+
test('hasHardcodedValuePattern no bloquea tokens internos de metadata AST', () => {
|
|
4252
|
+
const astNodeTokenAst = {
|
|
4253
|
+
type: 'VariableDeclarator',
|
|
4254
|
+
id: { type: 'Identifier', name: 'astNodeToken' },
|
|
4255
|
+
init: {
|
|
4256
|
+
type: 'StringLiteral',
|
|
4257
|
+
value: 'none',
|
|
4258
|
+
loc: { start: { line: 4 }, end: { line: 4 } },
|
|
4259
|
+
},
|
|
4260
|
+
loc: { start: { line: 4 }, end: { line: 4 } },
|
|
4261
|
+
};
|
|
4262
|
+
const astNodesTokenAst = {
|
|
4263
|
+
type: 'VariableDeclarator',
|
|
4264
|
+
id: { type: 'Identifier', name: 'astNodesToken' },
|
|
4265
|
+
init: {
|
|
4266
|
+
type: 'StringLiteral',
|
|
4267
|
+
value: 'none',
|
|
4268
|
+
loc: { start: { line: 8 }, end: { line: 8 } },
|
|
4269
|
+
},
|
|
4270
|
+
loc: { start: { line: 8 }, end: { line: 8 } },
|
|
4271
|
+
};
|
|
4272
|
+
|
|
4273
|
+
assert.equal(hasHardcodedValuePattern(astNodeTokenAst), false);
|
|
4274
|
+
assert.equal(findHardcodedValuePatternMatch(astNodeTokenAst), undefined);
|
|
4275
|
+
assert.equal(hasHardcodedValuePattern(astNodesTokenAst), false);
|
|
4276
|
+
assert.equal(findHardcodedValuePatternMatch(astNodesTokenAst), undefined);
|
|
4277
|
+
});
|
|
4278
|
+
|
|
4251
4279
|
test('hasHardcodedValuePattern usa tokens exactos y no subcadenas accidentales', () => {
|
|
4252
4280
|
const reportFunctionAst = {
|
|
4253
4281
|
type: 'FunctionDeclaration',
|
|
@@ -4908,6 +4908,10 @@ const isBenignConfigMetadataName = (value: string): boolean => {
|
|
|
4908
4908
|
if (normalized.length === 0) {
|
|
4909
4909
|
return true;
|
|
4910
4910
|
}
|
|
4911
|
+
const tokenSet = new Set(identifierNameTokens(normalized));
|
|
4912
|
+
if (tokenSet.has('ast') && (tokenSet.has('node') || tokenSet.has('nodes')) && tokenSet.has('token')) {
|
|
4913
|
+
return true;
|
|
4914
|
+
}
|
|
4911
4915
|
if (
|
|
4912
4916
|
normalized.startsWith('skills.') ||
|
|
4913
4917
|
normalized.startsWith('heuristics.') ||
|
|
@@ -33,6 +33,11 @@ test('iosEnterpriseRuleSet define reglas locked para plataforma ios', () => {
|
|
|
33
33
|
assert.equal(byId.get('ios.solid.isp.fat-protocol-dependency')?.when.kind, 'Heuristic');
|
|
34
34
|
assert.equal(byId.get('ios.solid.lsp.narrowed-precondition-substitution')?.when.kind, 'Heuristic');
|
|
35
35
|
assert.equal(byId.get('ios.solid.srp.presentation-mixed-responsibilities')?.when.kind, 'Heuristic');
|
|
36
|
+
assert.equal(byId.get('ios.solid.ocp.discriminator-switch-branching')?.stage, 'PRE_WRITE');
|
|
37
|
+
assert.equal(byId.get('ios.solid.dip.concrete-framework-dependency')?.stage, 'PRE_WRITE');
|
|
38
|
+
assert.equal(byId.get('ios.solid.isp.fat-protocol-dependency')?.stage, 'PRE_WRITE');
|
|
39
|
+
assert.equal(byId.get('ios.solid.lsp.narrowed-precondition-substitution')?.stage, 'PRE_WRITE');
|
|
40
|
+
assert.equal(byId.get('ios.solid.srp.presentation-mixed-responsibilities')?.stage, 'PRE_WRITE');
|
|
36
41
|
assert.equal(byId.get('ios.canary-001.presentation-mixed-responsibilities')?.when.kind, 'Heuristic');
|
|
37
42
|
assert.equal(byId.get('ios.tdd.domain-changes-require-tests')?.when.kind, 'All');
|
|
38
43
|
assert.equal(byId.get('ios.no-completion-handlers-outside-bridges')?.when.kind, 'Heuristic');
|
|
@@ -44,7 +44,7 @@ export const iosEnterpriseRuleSet: RuleSet = [
|
|
|
44
44
|
'Blocks iOS application or presentation types that must be modified to support new discriminator cases instead of extending behavior through abstractions.',
|
|
45
45
|
severity: 'CRITICAL',
|
|
46
46
|
platform: 'ios',
|
|
47
|
-
stage: '
|
|
47
|
+
stage: 'PRE_WRITE',
|
|
48
48
|
locked: true,
|
|
49
49
|
scope: {
|
|
50
50
|
include: ['**/*.swift'],
|
|
@@ -70,7 +70,7 @@ export const iosEnterpriseRuleSet: RuleSet = [
|
|
|
70
70
|
'Blocks iOS application or presentation types that depend directly on concrete framework services instead of abstractions.',
|
|
71
71
|
severity: 'CRITICAL',
|
|
72
72
|
platform: 'ios',
|
|
73
|
-
stage: '
|
|
73
|
+
stage: 'PRE_WRITE',
|
|
74
74
|
locked: true,
|
|
75
75
|
scope: {
|
|
76
76
|
include: ['**/*.swift'],
|
|
@@ -96,7 +96,7 @@ export const iosEnterpriseRuleSet: RuleSet = [
|
|
|
96
96
|
'Blocks iOS application or presentation types that depend on fat protocols instead of a minimal port tailored to the members they actually use.',
|
|
97
97
|
severity: 'CRITICAL',
|
|
98
98
|
platform: 'ios',
|
|
99
|
-
stage: '
|
|
99
|
+
stage: 'PRE_WRITE',
|
|
100
100
|
locked: true,
|
|
101
101
|
scope: {
|
|
102
102
|
include: ['**/*.swift'],
|
|
@@ -122,7 +122,7 @@ export const iosEnterpriseRuleSet: RuleSet = [
|
|
|
122
122
|
'Blocks iOS application or presentation types whose subtype narrows the contract preconditions and becomes unsafe to substitute for the base protocol or abstraction.',
|
|
123
123
|
severity: 'CRITICAL',
|
|
124
124
|
platform: 'ios',
|
|
125
|
-
stage: '
|
|
125
|
+
stage: 'PRE_WRITE',
|
|
126
126
|
locked: true,
|
|
127
127
|
scope: {
|
|
128
128
|
include: ['**/*.swift'],
|
|
@@ -148,7 +148,7 @@ export const iosEnterpriseRuleSet: RuleSet = [
|
|
|
148
148
|
'Blocks iOS presentation types that mix session, networking, persistence and navigation responsibilities in the same semantic node.',
|
|
149
149
|
severity: 'CRITICAL',
|
|
150
150
|
platform: 'ios',
|
|
151
|
-
stage: '
|
|
151
|
+
stage: 'PRE_WRITE',
|
|
152
152
|
locked: true,
|
|
153
153
|
scope: {
|
|
154
154
|
include: ['**/*.swift'],
|
|
@@ -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-05 (v6.3.143)
|
|
8
|
+
|
|
9
|
+
- **RuralGo PUMUKI-INC-060:** PRE_WRITE deja de aceptar evidencia TDD/BDD de baseline caducada para cambios in-scope.
|
|
10
|
+
- **Baseline antes de editar:** si el componente tocado requiere TDD/BDD, la evidencia debe ser reciente y pasada; si no, Pumuki bloquea y pide reejecutar baseline tests.
|
|
11
|
+
- **Rollout:** publicar `pumuki@6.3.143`, repinear primero RuralGo y revalidar `status`, `doctor` y el bloqueo por evidencia stale.
|
|
12
|
+
|
|
13
|
+
### 2026-05-05 (v6.3.142)
|
|
14
|
+
|
|
15
|
+
- **RuralGo PUMUKI-INC-059:** PRE_WRITE vuelve a pedir hechos AST para iOS SOLID aunque el flag experimental de heurísticas esté apagado.
|
|
16
|
+
- **OCP/SRP iOS bloqueante temprano:** `skills.ios.no-solid-violations` y el id real legacy del lock se vinculan a OCP/SRP/DIP/ISP/LSP; los findings se promueven a `ERROR` desde PRE_WRITE.
|
|
17
|
+
- **Rollout:** publicar `pumuki@6.3.142`, repinear primero RuralGo y revalidar `status`, `doctor` y un canary OCP iOS en PRE_WRITE/PRE_COMMIT.
|
|
18
|
+
|
|
7
19
|
### 2026-05-05 (v6.3.141)
|
|
8
20
|
|
|
9
21
|
- **PRE_PUSH sin falso bloqueo por historial base:** ramas de rollout que integran `main`/`develop` dejan de fallar por commits merge heredados como `Merge pull request ...`.
|
|
@@ -160,6 +160,21 @@ const registryByRuleId: Record<string, SkillsDetectorBinding> = {
|
|
|
160
160
|
'ios.core-data.nsmanagedobject-state-leak',
|
|
161
161
|
['heuristics.ios.core-data.nsmanagedobject-state-leak.ast']
|
|
162
162
|
),
|
|
163
|
+
'skills.ios.no-solid-violations': heuristicDetector('ios.solid', [
|
|
164
|
+
'heuristics.ios.solid.srp.presentation-mixed-responsibilities.ast',
|
|
165
|
+
'heuristics.ios.solid.dip.concrete-framework-dependency.ast',
|
|
166
|
+
'heuristics.ios.solid.ocp.discriminator-switch.ast',
|
|
167
|
+
'heuristics.ios.solid.isp.fat-protocol-dependency.ast',
|
|
168
|
+
'heuristics.ios.solid.lsp.narrowed-precondition.ast',
|
|
169
|
+
]),
|
|
170
|
+
'skills.ios.guideline.ios.verificar-que-no-viole-solid-srp-ocp-lsp-isp-dip':
|
|
171
|
+
heuristicDetector('ios.solid', [
|
|
172
|
+
'heuristics.ios.solid.srp.presentation-mixed-responsibilities.ast',
|
|
173
|
+
'heuristics.ios.solid.dip.concrete-framework-dependency.ast',
|
|
174
|
+
'heuristics.ios.solid.ocp.discriminator-switch.ast',
|
|
175
|
+
'heuristics.ios.solid.isp.fat-protocol-dependency.ast',
|
|
176
|
+
'heuristics.ios.solid.lsp.narrowed-precondition.ast',
|
|
177
|
+
]),
|
|
163
178
|
'skills.android.no-solid-violations': heuristicDetector('android.solid', [
|
|
164
179
|
'heuristics.android.solid.srp.presentation-mixed-responsibilities.ast',
|
|
165
180
|
'heuristics.android.solid.dip.concrete-framework-dependency.ast',
|
|
@@ -90,6 +90,8 @@ const inferRuleStage = (raw: string): SkillsStage | undefined => {
|
|
|
90
90
|
};
|
|
91
91
|
|
|
92
92
|
const KNOWN_RULE_DEFAULT_STAGE: Readonly<Record<string, SkillsStage>> = {
|
|
93
|
+
'skills.ios.no-solid-violations': 'PRE_WRITE',
|
|
94
|
+
'skills.ios.guideline.ios.verificar-que-no-viole-solid-srp-ocp-lsp-isp-dip': 'PRE_WRITE',
|
|
93
95
|
'skills.backend.no-solid-violations': 'PRE_PUSH',
|
|
94
96
|
'skills.frontend.no-solid-violations': 'PRE_PUSH',
|
|
95
97
|
'skills.backend.enforce-clean-architecture': 'PRE_PUSH',
|
|
@@ -275,8 +277,27 @@ const normalizeKnownRuleTarget = (
|
|
|
275
277
|
normalizedDescription: string
|
|
276
278
|
): string | null => {
|
|
277
279
|
const includes = (needle: string): boolean => normalizedDescription.includes(needle);
|
|
280
|
+
const hasWord = (needle: string): boolean =>
|
|
281
|
+
new RegExp(`(^|\\s)${needle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}(\\s|$)`).test(
|
|
282
|
+
normalizedDescription
|
|
283
|
+
);
|
|
278
284
|
|
|
279
285
|
if (platform === 'ios') {
|
|
286
|
+
if (
|
|
287
|
+
includes('solid') ||
|
|
288
|
+
includes('single responsibility') ||
|
|
289
|
+
hasWord('srp') ||
|
|
290
|
+
hasWord('ocp') ||
|
|
291
|
+
hasWord('lsp') ||
|
|
292
|
+
hasWord('isp') ||
|
|
293
|
+
hasWord('dip') ||
|
|
294
|
+
includes('open closed') ||
|
|
295
|
+
includes('open-closed') ||
|
|
296
|
+
includes('verificar que no viole solid') ||
|
|
297
|
+
includes('no viole solid')
|
|
298
|
+
) {
|
|
299
|
+
return 'skills.ios.no-solid-violations';
|
|
300
|
+
}
|
|
280
301
|
if (
|
|
281
302
|
includes('force unwrap') ||
|
|
282
303
|
includes('force unwrapping') ||
|
|
@@ -369,7 +369,10 @@ const resolveRuleSeverity = (params: {
|
|
|
369
369
|
const promotedRuleIds = params.bundlePolicy?.promoteToErrorRuleIds ?? [];
|
|
370
370
|
const shouldPromoteBySolidContract =
|
|
371
371
|
params.rule.id.endsWith('.no-solid-violations') &&
|
|
372
|
-
(params.stage === '
|
|
372
|
+
(params.stage === 'PRE_WRITE' ||
|
|
373
|
+
params.stage === 'PRE_COMMIT' ||
|
|
374
|
+
params.stage === 'PRE_PUSH' ||
|
|
375
|
+
params.stage === 'CI');
|
|
373
376
|
const shouldPromote =
|
|
374
377
|
shouldPromoteBySolidContract || promotedRuleIds.includes(params.rule.id);
|
|
375
378
|
|
|
@@ -76,10 +76,33 @@ const isTimelineOrdered = (timestamps: ReadonlyArray<string | undefined>): boole
|
|
|
76
76
|
return true;
|
|
77
77
|
};
|
|
78
78
|
|
|
79
|
+
const DEFAULT_EVIDENCE_MAX_AGE_SECONDS = 900;
|
|
80
|
+
|
|
81
|
+
const resolveEvidenceMaxAgeSeconds = (): number => {
|
|
82
|
+
const raw = process.env.PUMUKI_TDD_BDD_EVIDENCE_MAX_AGE_SECONDS?.trim();
|
|
83
|
+
if (!raw) {
|
|
84
|
+
return DEFAULT_EVIDENCE_MAX_AGE_SECONDS;
|
|
85
|
+
}
|
|
86
|
+
const parsed = Number.parseInt(raw, 10);
|
|
87
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
88
|
+
return DEFAULT_EVIDENCE_MAX_AGE_SECONDS;
|
|
89
|
+
}
|
|
90
|
+
return parsed;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const resolveEvidenceAgeSeconds = (generatedAt: string, nowMs: number): number | null => {
|
|
94
|
+
const generatedAtMs = new Date(generatedAt).getTime();
|
|
95
|
+
if (Number.isNaN(generatedAtMs)) {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
return Math.max(0, Math.floor((nowMs - generatedAtMs) / 1000));
|
|
99
|
+
};
|
|
100
|
+
|
|
79
101
|
export const enforceTddBddPolicy = (params: {
|
|
80
102
|
facts: ReadonlyArray<Fact>;
|
|
81
103
|
repoRoot: string;
|
|
82
104
|
branch: string | null;
|
|
105
|
+
now?: () => number;
|
|
83
106
|
}): TddBddEnforcementResult => {
|
|
84
107
|
const scope = classifyTddBddScope(params.facts);
|
|
85
108
|
const baseSnapshot: TddBddSnapshot = {
|
|
@@ -210,6 +233,46 @@ export const enforceTddBddPolicy = (params: {
|
|
|
210
233
|
};
|
|
211
234
|
}
|
|
212
235
|
|
|
236
|
+
const maxAgeSeconds = resolveEvidenceMaxAgeSeconds();
|
|
237
|
+
const ageSeconds = resolveEvidenceAgeSeconds(
|
|
238
|
+
evidenceRead.evidence.generated_at,
|
|
239
|
+
params.now?.() ?? Date.now()
|
|
240
|
+
);
|
|
241
|
+
if (ageSeconds === null || ageSeconds > maxAgeSeconds) {
|
|
242
|
+
const messageAge =
|
|
243
|
+
ageSeconds === null ? 'unknown' : `${ageSeconds}s`;
|
|
244
|
+
const finding = buildFinding({
|
|
245
|
+
ruleId: 'generic_tdd_baseline_required',
|
|
246
|
+
code: 'TDD_BDD_EVIDENCE_STALE',
|
|
247
|
+
message:
|
|
248
|
+
`TDD/BDD evidence is stale for this PRE_WRITE baseline: age=${messageAge}, max=${maxAgeSeconds}s. Re-run the baseline tests for the touched component and refresh evidence before editing related code.`,
|
|
249
|
+
filePath: evidenceRead.path,
|
|
250
|
+
});
|
|
251
|
+
return {
|
|
252
|
+
findings: [finding],
|
|
253
|
+
snapshot: {
|
|
254
|
+
...baseSnapshot,
|
|
255
|
+
status: 'blocked',
|
|
256
|
+
evidence: {
|
|
257
|
+
...baseSnapshot.evidence,
|
|
258
|
+
state: 'valid',
|
|
259
|
+
version: evidenceRead.evidence.version,
|
|
260
|
+
slices_total: evidenceRead.evidence.slices.length,
|
|
261
|
+
slices_valid: 0,
|
|
262
|
+
slices_invalid: evidenceRead.evidence.slices.length,
|
|
263
|
+
integrity_ok: evidenceRead.integrity.valid,
|
|
264
|
+
errors: ['TDD_BDD_EVIDENCE_STALE'],
|
|
265
|
+
baseline: {
|
|
266
|
+
required: true,
|
|
267
|
+
passed: 0,
|
|
268
|
+
missing: 0,
|
|
269
|
+
failed: 0,
|
|
270
|
+
},
|
|
271
|
+
},
|
|
272
|
+
},
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
|
|
213
276
|
const sliceFindings: Finding[] = [];
|
|
214
277
|
const seenSliceIds = new Set<string>();
|
|
215
278
|
let validSlices = 0;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pumuki",
|
|
3
|
-
"version": "6.3.
|
|
3
|
+
"version": "6.3.143",
|
|
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": {
|