pumuki 6.3.95 → 6.3.96

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,7 @@
1
+ ## 2026-04-20 (v6.3.96)
2
+ - **Diagnóstico explícito de skills faltantes**: `status`, `doctor` y el AI gate ya no degradan a señales genéricas cuando falta una skill requerida o su fuente es ilegible; ahora emiten `SKILLS_REQUIRED_SOURCE_MISSING` o `SKILLS_REQUIRED_SOURCE_UNREADABLE` con nombre de skill, ruta concreta y resolución accionable.
3
+ - **Rollout recomendado**: publicar `pumuki@6.3.96`, repin inmediato en `Flux_training` y repetir la repro del repo sin `docs/codex-skills`, confirmando que `status --json` y `doctor --json` incluyen la ruta `docs/codex-skills/windsurf-rules-backend.md` en `skills_contract.source_diagnostics`.
4
+
1
5
  ## 2026-04-20 (v6.3.95)
2
6
  - **Enforcement operativo de trazabilidad contractual**: `pumuki sdd evidence` exige ahora `--traceability-markdown=<path>` cuando el repositorio declara en `AGENTS.md` la matriz mínima `ARCHIVO | SKILL | REGLA | EVIDENCIA | ESTADO`; el comando falla si falta la cabecera exacta o si no existe al menos una fila real.
3
7
  - **Rollout recomendado**: publicar `pumuki@6.3.95`, repin inmediato en `Flux_training` y repetir la doble repro de `pumuki sdd evidence`: sin `--traceability-markdown` debe bloquear, y con un markdown repo-local que contenga la matriz contractual debe pasar.
@@ -49,6 +49,15 @@ export type CompiledImportedSkillsRulesResult = Omit<
49
49
  'outputPath'
50
50
  >;
51
51
 
52
+ export type SkillImportSourceDiagnostic = {
53
+ skillName: string;
54
+ canonicalSkillName: string;
55
+ sourcePath: string;
56
+ sourceType: 'declared_path' | 'required_skill';
57
+ issue: 'missing' | 'unreadable';
58
+ resolution: string;
59
+ };
60
+
52
61
  type VendorSkillManifestEntry = {
53
62
  name: string;
54
63
  file: string;
@@ -423,22 +432,57 @@ const loadVendorSkillsManifest = (
423
432
  return bySkillName;
424
433
  };
425
434
 
426
- export const resolveSkillImportSources = (params: {
435
+ const buildSkillImportResolution = (sourcePath: string): string => {
436
+ const normalized = sourcePath.replace(/\\/g, '/');
437
+ if (normalized.includes('/docs/codex-skills/') || normalized.startsWith('docs/codex-skills/')) {
438
+ return `Restaura ${sourcePath} o ejecuta ./scripts/sync-codex-skills.sh para resincronizar las skills vendorizadas.`;
439
+ }
440
+ if (normalized.includes('/vendor/skills/') || normalized.startsWith('vendor/skills/')) {
441
+ return `Restaura ${sourcePath} desde vendor/skills o resincroniza las dependencias del repositorio.`;
442
+ }
443
+ return `Corrige la ruta declarada o restaura ${sourcePath} para que la skill requerida vuelva a estar disponible.`;
444
+ };
445
+
446
+ const canReadSkillSourceFile = (sourcePath: string): boolean => {
447
+ try {
448
+ readFileSync(sourcePath, 'utf8');
449
+ return true;
450
+ } catch {
451
+ return false;
452
+ }
453
+ };
454
+
455
+ export const resolveSkillImportSourcesWithDiagnostics = (params: {
427
456
  repoRoot?: string;
428
457
  explicitSources?: ReadonlyArray<string>;
429
- }): string[] => {
458
+ }): { sourceFiles: string[]; diagnostics: SkillImportSourceDiagnostic[] } => {
430
459
  const repoRoot = params.repoRoot ?? process.cwd();
431
460
  const resolved = new Map<string, { path: string; priority: number }>();
432
461
  const vendoredManifest = loadVendorSkillsManifest(repoRoot);
433
-
434
- const pushIfExists = (rawPath: string): void => {
462
+ const diagnostics: SkillImportSourceDiagnostic[] = [];
463
+
464
+ const registerDiagnostic = (
465
+ rawPath: string,
466
+ skillName: string,
467
+ canonicalSkillName: string,
468
+ sourceType: SkillImportSourceDiagnostic['sourceType'],
469
+ issue: SkillImportSourceDiagnostic['issue'],
470
+ ): void => {
435
471
  const normalized = normalizePath({
436
472
  repoRoot,
437
473
  path: rawPath,
438
474
  });
439
- if (!existsSync(normalized)) {
440
- return;
441
- }
475
+ diagnostics.push({
476
+ skillName,
477
+ canonicalSkillName,
478
+ sourcePath: normalized,
479
+ sourceType,
480
+ issue,
481
+ resolution: buildSkillImportResolution(normalized),
482
+ });
483
+ };
484
+
485
+ const pushResolved = (normalized: string): void => {
442
486
  const skillKey = toCanonicalImportedSkillName(sourceSkillNameFromPath(normalized));
443
487
  const priority = isVendoredSkillMarkdownPath(normalized) ? 2 : 1;
444
488
  const current = resolved.get(skillKey);
@@ -451,13 +495,36 @@ export const resolveSkillImportSources = (params: {
451
495
  }
452
496
  };
453
497
 
498
+ const registerSource = (
499
+ rawPath: string,
500
+ skillName: string,
501
+ canonicalSkillName: string,
502
+ sourceType: SkillImportSourceDiagnostic['sourceType'],
503
+ ): void => {
504
+ const normalized = normalizePath({
505
+ repoRoot,
506
+ path: rawPath,
507
+ });
508
+ if (!existsSync(normalized)) {
509
+ registerDiagnostic(rawPath, skillName, canonicalSkillName, sourceType, 'missing');
510
+ return;
511
+ }
512
+ if (!canReadSkillSourceFile(normalized)) {
513
+ registerDiagnostic(rawPath, skillName, canonicalSkillName, sourceType, 'unreadable');
514
+ return;
515
+ }
516
+ pushResolved(normalized);
517
+ };
518
+
454
519
  if (params.explicitSources && params.explicitSources.length > 0) {
455
520
  for (const source of params.explicitSources) {
456
- pushIfExists(source);
521
+ const normalized = normalizePath({ repoRoot, path: source });
522
+ registerSource(source, sourceSkillNameFromPath(normalized), toCanonicalImportedSkillName(sourceSkillNameFromPath(normalized)), 'declared_path');
457
523
  }
458
- return [...resolved.values()]
459
- .map((item) => item.path)
460
- .sort();
524
+ return {
525
+ sourceFiles: [...resolved.values()].map((item) => item.path).sort(),
526
+ diagnostics,
527
+ };
461
528
  }
462
529
 
463
530
  for (const profileFile of ['AGENTS.md', 'SKILLS.md']) {
@@ -467,23 +534,37 @@ export const resolveSkillImportSources = (params: {
467
534
  }
468
535
  const content = readFileSync(profilePath, 'utf8');
469
536
  for (const candidate of extractSkillPathsFromText(content)) {
470
- pushIfExists(candidate);
537
+ const normalized = normalizePath({ repoRoot, path: candidate });
538
+ const skillName = sourceSkillNameFromPath(normalized);
539
+ registerSource(candidate, skillName, toCanonicalImportedSkillName(skillName), 'declared_path');
471
540
  }
472
541
  for (const requiredSkillName of extractRequiredSkillNamesFromText(content)) {
473
542
  const canonicalName = toCanonicalImportedSkillName(requiredSkillName);
474
543
  const manifestPath = vendoredManifest.get(canonicalName);
475
544
  if (manifestPath) {
476
- pushIfExists(manifestPath);
545
+ registerSource(manifestPath, requiredSkillName, canonicalName, 'required_skill');
477
546
  continue;
478
547
  }
479
- pushIfExists(`vendor/skills/${requiredSkillName}/SKILL.md`);
480
- pushIfExists(`vendor/skills/${canonicalName}/SKILL.md`);
548
+ const canonicalVendoredPath = `vendor/skills/${canonicalName}/SKILL.md`;
549
+ const requiredVendoredPath = `vendor/skills/${requiredSkillName}/SKILL.md`;
550
+ const selectedPath = existsSync(normalizePath({ repoRoot, path: requiredVendoredPath }))
551
+ ? requiredVendoredPath
552
+ : canonicalVendoredPath;
553
+ registerSource(selectedPath, requiredSkillName, canonicalName, 'required_skill');
481
554
  }
482
555
  }
483
556
 
484
- return [...resolved.values()]
485
- .map((item) => item.path)
486
- .sort();
557
+ return {
558
+ sourceFiles: [...resolved.values()].map((item) => item.path).sort(),
559
+ diagnostics,
560
+ };
561
+ };
562
+
563
+ export const resolveSkillImportSources = (params: {
564
+ repoRoot?: string;
565
+ explicitSources?: ReadonlyArray<string>;
566
+ }): string[] => {
567
+ return resolveSkillImportSourcesWithDiagnostics(params).sourceFiles;
487
568
  };
488
569
 
489
570
  export const compileImportedSkillsRules = (params: {
@@ -12,6 +12,10 @@ import {
12
12
  loadEffectiveSkillsLock,
13
13
  loadRequiredSkillsLock,
14
14
  } from '../config/skillsEffectiveLock';
15
+ import {
16
+ resolveSkillImportSourcesWithDiagnostics,
17
+ type SkillImportSourceDiagnostic,
18
+ } from '../config/skillsCustomRules';
15
19
  import {
16
20
  resolveSkillsEnforcement,
17
21
  type SkillsEnforcementResolution,
@@ -56,6 +60,7 @@ export type AiGateSkillsContractAssessment = {
56
60
  status: 'PASS' | 'FAIL' | 'NOT_APPLICABLE';
57
61
  detected_platforms: ReadonlyArray<PreWriteSkillsPlatform>;
58
62
  requirements: ReadonlyArray<AiGateSkillsContractPlatformRequirement>;
63
+ source_diagnostics: ReadonlyArray<SkillImportSourceDiagnostic>;
59
64
  violations: ReadonlyArray<AiGateViolation>;
60
65
  };
61
66
 
@@ -725,6 +730,20 @@ const collectPreWriteCrossPlatformCriticalViolations = (params: {
725
730
  ];
726
731
  };
727
732
 
733
+ const toRequiredSkillSourceViolations = (
734
+ diagnostics: ReadonlyArray<SkillImportSourceDiagnostic>,
735
+ skillsEnforcement: SkillsEnforcementResolution
736
+ ): AiGateViolation[] =>
737
+ diagnostics.map((diagnostic) =>
738
+ toSkillsViolation(
739
+ skillsEnforcement,
740
+ diagnostic.issue === 'missing'
741
+ ? 'SKILLS_REQUIRED_SOURCE_MISSING'
742
+ : 'SKILLS_REQUIRED_SOURCE_UNREADABLE',
743
+ `La skill requerida "${diagnostic.skillName}" no está disponible en ${diagnostic.sourcePath}. ${diagnostic.resolution}`
744
+ )
745
+ );
746
+
728
747
  const toSkillsContractAssessment = (params: {
729
748
  stage: AiGateStage;
730
749
  repoRoot: string;
@@ -734,12 +753,22 @@ const toSkillsContractAssessment = (params: {
734
753
  skillsEnforcement: SkillsEnforcementResolution;
735
754
  }): AiGateSkillsContractAssessment => {
736
755
  const requiredPlatforms = toLockRequiredPlatforms(params.requiredLock);
756
+ const sourceDiagnostics = resolveSkillImportSourcesWithDiagnostics({
757
+ repoRoot: params.repoRoot,
758
+ }).diagnostics;
737
759
 
738
760
  if (params.evidenceResult.kind !== 'valid') {
761
+ const sourceViolations = toRequiredSkillSourceViolations(
762
+ sourceDiagnostics,
763
+ params.skillsEnforcement
764
+ );
739
765
  return {
740
766
  stage: params.stage,
741
- enforced: requiredPlatforms.length > 0,
742
- status: requiredPlatforms.length > 0 ? 'FAIL' : 'NOT_APPLICABLE',
767
+ enforced: requiredPlatforms.length > 0 || sourceDiagnostics.length > 0,
768
+ status:
769
+ requiredPlatforms.length > 0 || sourceDiagnostics.length > 0
770
+ ? 'FAIL'
771
+ : 'NOT_APPLICABLE',
743
772
  detected_platforms: [],
744
773
  requirements: requiredPlatforms.map((platform) => ({
745
774
  platform,
@@ -758,8 +787,10 @@ const toSkillsContractAssessment = (params: {
758
787
  ...PREWRITE_TRANSVERSAL_CRITICAL_SKILLS_RULES[platform],
759
788
  ],
760
789
  })),
761
- violations:
762
- requiredPlatforms.length > 0
790
+ source_diagnostics: sourceDiagnostics,
791
+ violations: [
792
+ ...sourceViolations,
793
+ ...(requiredPlatforms.length > 0
763
794
  ? [
764
795
  toSkillsViolation(
765
796
  params.skillsEnforcement,
@@ -767,7 +798,8 @@ const toSkillsContractAssessment = (params: {
767
798
  `Required repo skills exist, but active platforms could not be detected for ${params.stage}.`
768
799
  ),
769
800
  ]
770
- : [],
801
+ : []),
802
+ ],
771
803
  };
772
804
  }
773
805
 
@@ -825,11 +857,15 @@ const toSkillsContractAssessment = (params: {
825
857
  ) {
826
858
  return {
827
859
  stage: params.stage,
828
- enforced: false,
829
- status: 'NOT_APPLICABLE',
860
+ enforced: sourceDiagnostics.length > 0,
861
+ status: sourceDiagnostics.length > 0 ? 'FAIL' : 'NOT_APPLICABLE',
830
862
  detected_platforms: [],
831
863
  requirements: [],
832
- violations: [],
864
+ source_diagnostics: sourceDiagnostics,
865
+ violations: toRequiredSkillSourceViolations(
866
+ sourceDiagnostics,
867
+ params.skillsEnforcement
868
+ ),
833
869
  };
834
870
  }
835
871
  const requirements: AiGateSkillsContractPlatformRequirement[] = requiredPlatforms.map((platform) => ({
@@ -856,7 +892,12 @@ const toSkillsContractAssessment = (params: {
856
892
  status: 'FAIL',
857
893
  detected_platforms: [],
858
894
  requirements,
895
+ source_diagnostics: sourceDiagnostics,
859
896
  violations: [
897
+ ...toRequiredSkillSourceViolations(
898
+ sourceDiagnostics,
899
+ params.skillsEnforcement
900
+ ),
860
901
  toSkillsViolation(
861
902
  params.skillsEnforcement,
862
903
  'EVIDENCE_SKILLS_PLATFORMS_UNDETECTED',
@@ -868,11 +909,15 @@ const toSkillsContractAssessment = (params: {
868
909
  if (assessmentPlatforms.length === 0) {
869
910
  return {
870
911
  stage: params.stage,
871
- enforced: false,
872
- status: 'NOT_APPLICABLE',
912
+ enforced: sourceDiagnostics.length > 0,
913
+ status: sourceDiagnostics.length > 0 ? 'FAIL' : 'NOT_APPLICABLE',
873
914
  detected_platforms: [],
874
915
  requirements: [],
875
- violations: [],
916
+ source_diagnostics: sourceDiagnostics,
917
+ violations: toRequiredSkillSourceViolations(
918
+ sourceDiagnostics,
919
+ params.skillsEnforcement
920
+ ),
876
921
  };
877
922
  }
878
923
 
@@ -884,7 +929,10 @@ const toSkillsContractAssessment = (params: {
884
929
  );
885
930
 
886
931
  const requirements: AiGateSkillsContractPlatformRequirement[] = [];
887
- const violations: AiGateViolation[] = [];
932
+ const violations: AiGateViolation[] = toRequiredSkillSourceViolations(
933
+ sourceDiagnostics,
934
+ params.skillsEnforcement
935
+ );
888
936
  if (requiredPlatforms.length > 0 && detectedPlatforms.length === 0) {
889
937
  violations.push(
890
938
  toSkillsViolation(
@@ -995,6 +1043,7 @@ const toSkillsContractAssessment = (params: {
995
1043
  status: violations.length === 0 ? 'PASS' : 'FAIL',
996
1044
  detected_platforms: detectedPlatforms,
997
1045
  requirements,
1046
+ source_diagnostics: sourceDiagnostics,
998
1047
  violations,
999
1048
  };
1000
1049
  };
@@ -1596,6 +1645,11 @@ export const evaluateAiGate = (
1596
1645
  || (params.stage === 'PRE_WRITE' && requiredSkillsPlatforms.length === 0)
1597
1646
  ? []
1598
1647
  : [
1648
+ ...skillsContract.violations.filter(
1649
+ (violation) =>
1650
+ violation.code === 'SKILLS_REQUIRED_SOURCE_MISSING'
1651
+ || violation.code === 'SKILLS_REQUIRED_SOURCE_UNREADABLE'
1652
+ ),
1599
1653
  toSkillsViolation(
1600
1654
  skillsEnforcement,
1601
1655
  'EVIDENCE_SKILLS_CONTRACT_INCOMPLETE',
@@ -4,6 +4,10 @@ import { readEvidenceResult } from '../evidence/readEvidence';
4
4
  import { readRepoTrackingState } from '../evidence/trackingContract';
5
5
  import type { RepoTrackingState } from '../evidence/schema';
6
6
  import { loadRequiredSkillsLock } from '../config/skillsEffectiveLock';
7
+ import {
8
+ resolveSkillImportSourcesWithDiagnostics,
9
+ type SkillImportSourceDiagnostic,
10
+ } from '../config/skillsCustomRules';
7
11
  import { readSddStatus } from '../sdd';
8
12
  import type { SddStatusPayload } from '../sdd/types';
9
13
  import type { LifecycleExperimentalFeaturesSnapshot } from './experimentalFeaturesSnapshot';
@@ -54,6 +58,7 @@ export type GovernanceSkillsContractSummary = {
54
58
  status: 'PASS' | 'FAIL' | 'NOT_APPLICABLE';
55
59
  detected_platforms: ReadonlyArray<GovernanceSkillsContractPlatform>;
56
60
  requirements: ReadonlyArray<GovernanceSkillsContractPlatformRequirement>;
61
+ source_diagnostics: ReadonlyArray<SkillImportSourceDiagnostic>;
57
62
  violations: ReadonlyArray<GovernanceSkillsContractViolation>;
58
63
  };
59
64
 
@@ -228,15 +233,29 @@ const toCoverageDetectedPlatforms = (
228
233
 
229
234
  const summarizeSkillsContract = (repoRoot: string): GovernanceSkillsContractSummary => {
230
235
  const requiredPlatforms = toRequiredSkillsPlatforms(repoRoot);
236
+ const sourceDiagnostics = resolveSkillImportSourcesWithDiagnostics({ repoRoot }).diagnostics;
231
237
  const evidenceResult = readEvidenceResult(repoRoot);
232
238
  if (evidenceResult.kind !== 'valid') {
233
239
  return {
234
240
  stage: 'PRE_WRITE',
235
- enforced: false,
236
- status: 'NOT_APPLICABLE',
241
+ enforced: requiredPlatforms.length > 0 || sourceDiagnostics.length > 0,
242
+ status:
243
+ requiredPlatforms.length > 0 || sourceDiagnostics.length > 0
244
+ ? 'FAIL'
245
+ : 'NOT_APPLICABLE',
237
246
  detected_platforms: [],
238
247
  requirements: [],
239
- violations: [],
248
+ source_diagnostics: sourceDiagnostics,
249
+ violations: sourceDiagnostics.map((diagnostic) => ({
250
+ severity: 'ERROR',
251
+ code:
252
+ diagnostic.issue === 'missing'
253
+ ? 'SKILLS_REQUIRED_SOURCE_MISSING'
254
+ : 'SKILLS_REQUIRED_SOURCE_UNREADABLE',
255
+ message:
256
+ `La skill requerida "${diagnostic.skillName}" no está disponible en ` +
257
+ `${diagnostic.sourcePath}. ${diagnostic.resolution}`,
258
+ })),
240
259
  };
241
260
  }
242
261
 
@@ -246,11 +265,21 @@ const summarizeSkillsContract = (repoRoot: string): GovernanceSkillsContractSumm
246
265
  if (assessmentPlatforms.length === 0) {
247
266
  return {
248
267
  stage: 'PRE_WRITE',
249
- enforced: false,
250
- status: 'NOT_APPLICABLE',
268
+ enforced: sourceDiagnostics.length > 0,
269
+ status: sourceDiagnostics.length > 0 ? 'FAIL' : 'NOT_APPLICABLE',
251
270
  detected_platforms: [],
252
271
  requirements: [],
253
- violations: [],
272
+ source_diagnostics: sourceDiagnostics,
273
+ violations: sourceDiagnostics.map((diagnostic) => ({
274
+ severity: 'ERROR',
275
+ code:
276
+ diagnostic.issue === 'missing'
277
+ ? 'SKILLS_REQUIRED_SOURCE_MISSING'
278
+ : 'SKILLS_REQUIRED_SOURCE_UNREADABLE',
279
+ message:
280
+ `La skill requerida "${diagnostic.skillName}" no está disponible en ` +
281
+ `${diagnostic.sourcePath}. ${diagnostic.resolution}`,
282
+ })),
254
283
  };
255
284
  }
256
285
 
@@ -266,6 +295,18 @@ const summarizeSkillsContract = (repoRoot: string): GovernanceSkillsContractSumm
266
295
 
267
296
  const requirements: GovernanceSkillsContractPlatformRequirement[] = [];
268
297
  const violations: GovernanceSkillsContractViolation[] = [];
298
+ for (const diagnostic of sourceDiagnostics) {
299
+ violations.push({
300
+ severity: 'ERROR',
301
+ code:
302
+ diagnostic.issue === 'missing'
303
+ ? 'SKILLS_REQUIRED_SOURCE_MISSING'
304
+ : 'SKILLS_REQUIRED_SOURCE_UNREADABLE',
305
+ message:
306
+ `La skill requerida "${diagnostic.skillName}" no está disponible en ` +
307
+ `${diagnostic.sourcePath}. ${diagnostic.resolution}`,
308
+ });
309
+ }
269
310
  for (const platform of assessmentPlatforms) {
270
311
  const requiredRulePrefix = GOVERNANCE_SKILLS_RULE_PREFIXES[platform];
271
312
  const requiredBundles = [...GOVERNANCE_REQUIRED_SKILLS_BUNDLES[platform]];
@@ -355,6 +396,7 @@ const summarizeSkillsContract = (repoRoot: string): GovernanceSkillsContractSumm
355
396
  status: violations.length === 0 ? 'PASS' : 'FAIL',
356
397
  detected_platforms: detectedPlatforms,
357
398
  requirements,
399
+ source_diagnostics: sourceDiagnostics,
358
400
  violations,
359
401
  };
360
402
  };
@@ -485,12 +527,16 @@ export const readGovernanceObservationSnapshot = (params: {
485
527
  if (skillsContract.status === 'FAIL') {
486
528
  attention.push('SKILLS_CONTRACT_INCOMPLETE');
487
529
  }
530
+ if (skillsContract.source_diagnostics.length > 0) {
531
+ attention.push('SKILLS_REQUIRED_SOURCE_INVALID');
532
+ }
488
533
 
489
534
  let governanceEffective: GovernanceObservationSnapshot['governance_effective'] = 'green';
490
535
  if (
491
536
  evidence.readable === 'invalid'
492
537
  || (evidence.readable === 'valid' && evidence.ai_gate_status === 'BLOCKED')
493
538
  || (evidence.readable === 'valid' && evidence.snapshot_outcome === 'BLOCK')
539
+ || skillsContract.source_diagnostics.length > 0
494
540
  ) {
495
541
  governanceEffective = 'blocked';
496
542
  } else if (attention.length > 0) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pumuki",
3
- "version": "6.3.95",
3
+ "version": "6.3.96",
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": {