pumuki 6.3.90 → 6.3.92

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.
@@ -1,3 +1,11 @@
1
+ ## 2026-04-20 (v6.3.92)
2
+ - **Purge real de uninstall**: `pumuki uninstall --purge-artifacts` elimina ya los artefactos bootstrap locales `.pumuki/adapter.json` y `.pumuki/bootstrap-manifest.json` cuando no están trackeados, en vez de dejar una instalación “medio desinstalada”.
3
+ - **Rollout recomendado**: publicar `pumuki@6.3.92`, repin inmediato en `Flux_training` y repetir `install -> uninstall --purge-artifacts` comprobando `adapter=no` y `manifest=no` al final.
4
+
5
+ ## 2026-04-20 (v6.3.91)
6
+ - **Observabilidad de skills alineada**: `status --json` y `doctor --json` dejan visible `governanceObservation.skills_contract` cuando la evidencia y el lock efectivo de skills muestran un contrato activo; el lifecycle ya no aparenta `NOT_APPLICABLE` mientras el gate real bloquea violaciones frontend/backend.
7
+ - **Rollout recomendado**: publicar `pumuki@6.3.91`, repin inmediato en `Flux_training` y repetir la repro con rojo frontend staged para confirmar `skills_contract.enforced=true`, `skills_contract.status=FAIL` y `attention_codes` con `SKILLS_CONTRACT_INCOMPLETE`.
8
+
1
9
  ## 2026-04-20 (v6.3.89)
2
10
  - **Aislamiento por worktree**: `pumuki install` detecta worktrees sin `core.hooksPath` explícito y fija un `core.hooksPath` local a `.pumuki/git-hooks`, evitando que los hooks gestionados se escriban en `.git/hooks` del checkout principal compartido.
3
11
  - **Rollback limpio**: `pumuki uninstall` retira ese `core.hooksPath` solo si fue creado por Pumuki para el worktree.
@@ -11,7 +11,12 @@ import {
11
11
  import { dirname, isAbsolute, join, resolve } from 'node:path';
12
12
  import type { ILifecycleGitService } from './gitService';
13
13
 
14
- const PUMUKI_ARTIFACTS = ['.ai_evidence.json', '.AI_EVIDENCE.json'] as const;
14
+ const PUMUKI_ARTIFACTS = [
15
+ '.ai_evidence.json',
16
+ '.AI_EVIDENCE.json',
17
+ '.pumuki/adapter.json',
18
+ '.pumuki/bootstrap-manifest.json',
19
+ ] as const;
15
20
  const RUNTIME_ARTIFACT_IGNORE_ENTRIES = [...PUMUKI_ARTIFACTS, '.pumuki/'] as const;
16
21
  const RUNTIME_ARTIFACT_IGNORE_BEGIN = '# >>> pumuki-runtime-artifacts >>>';
17
22
  const RUNTIME_ARTIFACT_IGNORE_END = '# <<< pumuki-runtime-artifacts <<<';
@@ -3,6 +3,7 @@ import { join } from 'node:path';
3
3
  import { readEvidenceResult } from '../evidence/readEvidence';
4
4
  import { readRepoTrackingState } from '../evidence/trackingContract';
5
5
  import type { RepoTrackingState } from '../evidence/schema';
6
+ import { loadRequiredSkillsLock } from '../config/skillsEffectiveLock';
6
7
  import { readSddStatus } from '../sdd';
7
8
  import type { SddStatusPayload } from '../sdd/types';
8
9
  import type { LifecycleExperimentalFeaturesSnapshot } from './experimentalFeaturesSnapshot';
@@ -25,6 +26,37 @@ export type GovernanceEvidenceSummary = {
25
26
  human_summary_preview: string[];
26
27
  };
27
28
 
29
+ type GovernanceSkillsContractPlatform = 'ios' | 'android' | 'backend' | 'frontend';
30
+
31
+ type GovernanceSkillsContractViolation = {
32
+ code: string;
33
+ message: string;
34
+ severity: 'ERROR' | 'WARN';
35
+ };
36
+
37
+ type GovernanceSkillsContractPlatformRequirement = {
38
+ platform: GovernanceSkillsContractPlatform;
39
+ required_rule_prefix: string;
40
+ required_bundles: ReadonlyArray<string>;
41
+ required_critical_rule_ids: ReadonlyArray<string>;
42
+ required_any_transversal_critical_rule_ids: ReadonlyArray<string>;
43
+ active_prefix_covered: boolean;
44
+ evaluated_prefix_covered: boolean;
45
+ missing_bundles: ReadonlyArray<string>;
46
+ missing_critical_rule_ids: ReadonlyArray<string>;
47
+ transversal_critical_covered: boolean;
48
+ missing_any_transversal_critical_rule_ids: ReadonlyArray<string>;
49
+ };
50
+
51
+ export type GovernanceSkillsContractSummary = {
52
+ stage: 'PRE_WRITE';
53
+ enforced: boolean;
54
+ status: 'PASS' | 'FAIL' | 'NOT_APPLICABLE';
55
+ detected_platforms: ReadonlyArray<GovernanceSkillsContractPlatform>;
56
+ requirements: ReadonlyArray<GovernanceSkillsContractPlatformRequirement>;
57
+ violations: ReadonlyArray<GovernanceSkillsContractViolation>;
58
+ };
59
+
28
60
  export type GovernanceContractSurface = {
29
61
  agents_md: boolean;
30
62
  skills_lock_json: boolean;
@@ -54,6 +86,7 @@ export type GovernanceObservationSnapshot = {
54
86
  };
55
87
  enterprise_warn_as_block_env: boolean;
56
88
  evidence: GovernanceEvidenceSummary;
89
+ skills_contract: GovernanceSkillsContractSummary;
57
90
  git: {
58
91
  current_branch: string | null;
59
92
  on_protected_branch_hint: boolean;
@@ -65,6 +98,40 @@ export type GovernanceObservationSnapshot = {
65
98
  agent_bootstrap_hints: ReadonlyArray<string>;
66
99
  };
67
100
 
101
+ const GOVERNANCE_SKILLS_PLATFORMS = ['ios', 'android', 'backend', 'frontend'] as const;
102
+ const GOVERNANCE_SKILLS_RULE_PREFIXES: Readonly<
103
+ Record<GovernanceSkillsContractPlatform, string>
104
+ > = {
105
+ ios: 'skills.ios.',
106
+ android: 'skills.android.',
107
+ backend: 'skills.backend.',
108
+ frontend: 'skills.frontend.',
109
+ };
110
+ const GOVERNANCE_REQUIRED_SKILLS_BUNDLES: Readonly<
111
+ Record<GovernanceSkillsContractPlatform, ReadonlyArray<string>>
112
+ > = {
113
+ ios: ['ios-guidelines', 'ios-concurrency-guidelines', 'ios-swiftui-expert-guidelines'],
114
+ android: ['android-guidelines'],
115
+ backend: ['backend-guidelines'],
116
+ frontend: ['frontend-guidelines'],
117
+ };
118
+ const GOVERNANCE_CRITICAL_SKILLS_RULES: Readonly<
119
+ Record<GovernanceSkillsContractPlatform, ReadonlyArray<string>>
120
+ > = {
121
+ ios: ['skills.ios.critical-test-quality'],
122
+ android: [],
123
+ backend: [],
124
+ frontend: [],
125
+ };
126
+ const GOVERNANCE_TRANSVERSAL_CRITICAL_SKILLS_RULES: Readonly<
127
+ Record<GovernanceSkillsContractPlatform, ReadonlyArray<string>>
128
+ > = {
129
+ ios: [],
130
+ android: ['skills.android.no-runblocking', 'skills.android.no-thread-sleep'],
131
+ backend: ['skills.backend.no-empty-catch', 'skills.backend.avoid-explicit-any'],
132
+ frontend: ['skills.frontend.no-empty-catch', 'skills.frontend.avoid-explicit-any'],
133
+ };
134
+
68
135
  const truthyEnv = (value: string | undefined): boolean => {
69
136
  if (typeof value !== 'string') {
70
137
  return false;
@@ -112,6 +179,186 @@ const buildContractSurface = (repoRoot: string): GovernanceContractSurface => ({
112
179
  pumuki_adapter_json: existsSync(join(repoRoot, '.pumuki', 'adapter.json')),
113
180
  });
114
181
 
182
+ const toRequiredSkillsPlatforms = (repoRoot: string): GovernanceSkillsContractPlatform[] => {
183
+ const requiredLock = loadRequiredSkillsLock(repoRoot);
184
+ if (!requiredLock) {
185
+ return [];
186
+ }
187
+ const detected = new Set<GovernanceSkillsContractPlatform>();
188
+ for (const bundle of requiredLock.bundles) {
189
+ for (const rule of bundle.rules) {
190
+ if (
191
+ rule.platform === 'ios'
192
+ || rule.platform === 'android'
193
+ || rule.platform === 'backend'
194
+ || rule.platform === 'frontend'
195
+ ) {
196
+ detected.add(rule.platform);
197
+ }
198
+ }
199
+ }
200
+ return GOVERNANCE_SKILLS_PLATFORMS.filter((platform) => detected.has(platform));
201
+ };
202
+
203
+ const toCoverageDetectedPlatforms = (
204
+ evidenceResult: ReturnType<typeof readEvidenceResult>
205
+ ): GovernanceSkillsContractPlatform[] => {
206
+ if (evidenceResult.kind !== 'valid') {
207
+ return [];
208
+ }
209
+ const explicit = GOVERNANCE_SKILLS_PLATFORMS.filter((platform) => {
210
+ const candidate = evidenceResult.evidence.platforms?.[platform];
211
+ return candidate?.detected === true;
212
+ });
213
+ if (explicit.length > 0) {
214
+ return explicit;
215
+ }
216
+ const coverage = evidenceResult.evidence.snapshot.rules_coverage;
217
+ if (!coverage) {
218
+ return [];
219
+ }
220
+ return GOVERNANCE_SKILLS_PLATFORMS.filter((platform) => {
221
+ const prefix = GOVERNANCE_SKILLS_RULE_PREFIXES[platform];
222
+ return (
223
+ coverage.active_rule_ids.some((ruleId) => ruleId.startsWith(prefix))
224
+ || coverage.evaluated_rule_ids.some((ruleId) => ruleId.startsWith(prefix))
225
+ );
226
+ });
227
+ };
228
+
229
+ const summarizeSkillsContract = (repoRoot: string): GovernanceSkillsContractSummary => {
230
+ const requiredPlatforms = toRequiredSkillsPlatforms(repoRoot);
231
+ const evidenceResult = readEvidenceResult(repoRoot);
232
+ if (evidenceResult.kind !== 'valid') {
233
+ return {
234
+ stage: 'PRE_WRITE',
235
+ enforced: false,
236
+ status: 'NOT_APPLICABLE',
237
+ detected_platforms: [],
238
+ requirements: [],
239
+ violations: [],
240
+ };
241
+ }
242
+
243
+ const coverage = evidenceResult.evidence.snapshot.rules_coverage;
244
+ const detectedPlatforms = toCoverageDetectedPlatforms(evidenceResult);
245
+ const assessmentPlatforms = requiredPlatforms.length > 0 ? requiredPlatforms : detectedPlatforms;
246
+ if (assessmentPlatforms.length === 0) {
247
+ return {
248
+ stage: 'PRE_WRITE',
249
+ enforced: false,
250
+ status: 'NOT_APPLICABLE',
251
+ detected_platforms: [],
252
+ requirements: [],
253
+ violations: [],
254
+ };
255
+ }
256
+
257
+ const activeSkillsBundles = new Set(
258
+ (evidenceResult.evidence.rulesets ?? [])
259
+ .filter((ruleset) => ruleset.platform === 'skills')
260
+ .map((ruleset) => {
261
+ const [bundleName] = ruleset.bundle.split('@');
262
+ return bundleName?.trim().toLowerCase() ?? '';
263
+ })
264
+ .filter((bundle) => bundle.length > 0)
265
+ );
266
+
267
+ const requirements: GovernanceSkillsContractPlatformRequirement[] = [];
268
+ const violations: GovernanceSkillsContractViolation[] = [];
269
+ for (const platform of assessmentPlatforms) {
270
+ const requiredRulePrefix = GOVERNANCE_SKILLS_RULE_PREFIXES[platform];
271
+ const requiredBundles = [...GOVERNANCE_REQUIRED_SKILLS_BUNDLES[platform]];
272
+ const requiredCriticalRuleIds = [...GOVERNANCE_CRITICAL_SKILLS_RULES[platform]];
273
+ const requiredAnyTransversalCriticalRuleIds = [
274
+ ...GOVERNANCE_TRANSVERSAL_CRITICAL_SKILLS_RULES[platform],
275
+ ];
276
+ const activePrefixCovered = coverage
277
+ ? coverage.active_rule_ids.some((ruleId) => ruleId.startsWith(requiredRulePrefix))
278
+ : false;
279
+ const evaluatedPrefixCovered = coverage
280
+ ? coverage.evaluated_rule_ids.some((ruleId) => ruleId.startsWith(requiredRulePrefix))
281
+ : false;
282
+ const missingBundles = requiredBundles.filter(
283
+ (bundleName) => !activeSkillsBundles.has(bundleName.toLowerCase())
284
+ );
285
+ const missingCriticalRuleIds = coverage
286
+ ? requiredCriticalRuleIds.filter((ruleId) => {
287
+ const hasActive = coverage.active_rule_ids.includes(ruleId);
288
+ const hasEvaluated = coverage.evaluated_rule_ids.includes(ruleId);
289
+ return !hasActive || !hasEvaluated;
290
+ })
291
+ : [...requiredCriticalRuleIds];
292
+ const transversalCriticalCovered =
293
+ requiredAnyTransversalCriticalRuleIds.length === 0
294
+ ? true
295
+ : Boolean(
296
+ coverage
297
+ && requiredAnyTransversalCriticalRuleIds.some((ruleId) =>
298
+ coverage.active_rule_ids.includes(ruleId)
299
+ && coverage.evaluated_rule_ids.includes(ruleId)
300
+ )
301
+ );
302
+ const missingAnyTransversalCriticalRuleIds = transversalCriticalCovered
303
+ ? []
304
+ : [...requiredAnyTransversalCriticalRuleIds];
305
+
306
+ requirements.push({
307
+ platform,
308
+ required_rule_prefix: requiredRulePrefix,
309
+ required_bundles: requiredBundles,
310
+ required_critical_rule_ids: requiredCriticalRuleIds,
311
+ required_any_transversal_critical_rule_ids: requiredAnyTransversalCriticalRuleIds,
312
+ active_prefix_covered: activePrefixCovered,
313
+ evaluated_prefix_covered: evaluatedPrefixCovered,
314
+ missing_bundles: missingBundles,
315
+ missing_critical_rule_ids: missingCriticalRuleIds,
316
+ transversal_critical_covered: transversalCriticalCovered,
317
+ missing_any_transversal_critical_rule_ids: missingAnyTransversalCriticalRuleIds,
318
+ });
319
+
320
+ if (!activePrefixCovered || !evaluatedPrefixCovered) {
321
+ violations.push({
322
+ severity: 'ERROR',
323
+ code: 'EVIDENCE_PLATFORM_SKILLS_SCOPE_INCOMPLETE',
324
+ message: `Skills contract scope coverage missing for ${platform}.`,
325
+ });
326
+ }
327
+ if (missingBundles.length > 0) {
328
+ violations.push({
329
+ severity: 'ERROR',
330
+ code: 'EVIDENCE_PLATFORM_SKILLS_BUNDLES_MISSING',
331
+ message: `Skills contract missing bundles for ${platform}: [${missingBundles.join(', ')}].`,
332
+ });
333
+ }
334
+ if (missingCriticalRuleIds.length > 0) {
335
+ violations.push({
336
+ severity: 'ERROR',
337
+ code: 'EVIDENCE_PLATFORM_CRITICAL_SKILLS_RULES_MISSING',
338
+ message: `Skills contract missing critical rule coverage for ${platform}: [${missingCriticalRuleIds.join(', ')}].`,
339
+ });
340
+ }
341
+ if (!transversalCriticalCovered && requiredAnyTransversalCriticalRuleIds.length > 0) {
342
+ violations.push({
343
+ severity: 'ERROR',
344
+ code: 'EVIDENCE_CROSS_PLATFORM_CRITICAL_ENFORCEMENT_INCOMPLETE',
345
+ message:
346
+ `Skills contract missing transversal critical coverage for ${platform}: ` +
347
+ `[${requiredAnyTransversalCriticalRuleIds.join(', ')}].`,
348
+ });
349
+ }
350
+ }
351
+
352
+ return {
353
+ stage: 'PRE_WRITE',
354
+ enforced: true,
355
+ status: violations.length === 0 ? 'PASS' : 'FAIL',
356
+ detected_platforms: detectedPlatforms,
357
+ requirements,
358
+ violations,
359
+ };
360
+ };
361
+
115
362
  const summarizeEvidence = (repoRoot: string): GovernanceEvidenceSummary => {
116
363
  const evidenceResult = readEvidenceResult(repoRoot);
117
364
  const path = evidenceResult.source_descriptor.path;
@@ -193,6 +440,7 @@ export const readGovernanceObservationSnapshot = (params: {
193
440
  const surface = buildContractSurface(repoRoot);
194
441
  const tracking = readRepoTrackingState(repoRoot);
195
442
  const warnAsBlock = truthyEnv(process.env.PUMUKI_ENTERPRISE_STRICT_WARN_AS_BLOCK);
443
+ const skillsContract = summarizeSkillsContract(repoRoot);
196
444
 
197
445
  const attention: string[] = [];
198
446
  if (evidence.readable === 'invalid') {
@@ -234,6 +482,9 @@ export const readGovernanceObservationSnapshot = (params: {
234
482
  if (tracking.enforced && tracking.single_in_progress_valid === false) {
235
483
  attention.push('TRACKING_CANONICAL_IN_PROGRESS_INVALID');
236
484
  }
485
+ if (skillsContract.status === 'FAIL') {
486
+ attention.push('SKILLS_CONTRACT_INCOMPLETE');
487
+ }
237
488
 
238
489
  let governanceEffective: GovernanceObservationSnapshot['governance_effective'] = 'green';
239
490
  if (
@@ -268,6 +519,7 @@ export const readGovernanceObservationSnapshot = (params: {
268
519
  },
269
520
  enterprise_warn_as_block_env: warnAsBlock,
270
521
  evidence,
522
+ skills_contract: skillsContract,
271
523
  git: {
272
524
  current_branch: branch,
273
525
  on_protected_branch_hint: onProtected,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pumuki",
3
- "version": "6.3.90",
3
+ "version": "6.3.92",
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": {