pumuki 6.3.38 → 6.3.39

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/VERSION CHANGED
@@ -1 +1 @@
1
- v6.3.38
1
+ v6.3.39
@@ -5,6 +5,32 @@ Detailed commit history remains available through Git history (`git log` / `git
5
5
 
6
6
  ## 2026-03 (enterprise hardening updates)
7
7
 
8
+ ### 2026-03-04 (v6.3.39)
9
+
10
+ - Adapter/runtime bootstrap hardening:
11
+ - adapter-generated hooks/CI templates now use `npx --yes --package pumuki@latest ...` for deterministic command resolution in consumer repos.
12
+ - Git-range robustness:
13
+ - commit-range facts now guard unresolved refs (`rev-parse --verify`) and avoid ambiguous failures on repos without `HEAD`.
14
+ - Cross-platform critical enforcement:
15
+ - gate now blocks when a detected platform does not have critical (`CRITICAL/ERROR`) skills rules active/evaluated.
16
+ - finding id: `governance.skills.cross-platform-critical.incomplete`.
17
+ - Git atomicity by default:
18
+ - atomicity guard is enabled by default in core gate flow (`PRE_COMMIT/PRE_PUSH/CI`).
19
+ - keeps env/config overrides for enterprise tuning without patching source.
20
+ - Versioned hooks diagnostics hardening (`core.hooksPath`):
21
+ - lifecycle hook resolution now includes fallback to local `.git/config` (`core.hooksPath`) when `git rev-parse --git-path hooks` is unavailable.
22
+ - `status/doctor` now expose effective hook path metadata (`hooksDirectory`, `hooksDirectoryResolution`) and print it in human-readable mode.
23
+ - Validation hardening:
24
+ - `test:stage-gates` stabilized and green with current contracts (`1020 pass / 0 fail / 4 skip`).
25
+ - fixtures aligned to evidence v2.1 (`evidence_chain`, `evidence.source`) and architecture guardrail overrides updated for `integrations/lifecycle/cli.ts`.
26
+ - Traceability:
27
+ - commits: `104fc92`, `2f175ec`, `da7b073`, `2c40a4c`, `b124599`, `2aeb435`
28
+ - Consumer quick verification:
29
+ - `npx --yes --package pumuki@latest pumuki status --json`
30
+ - `npx --yes --package pumuki@latest pumuki doctor --json`
31
+ - `npm run -s typecheck`
32
+ - `npm run -s test:stage-gates`
33
+
8
34
  ### 2026-03-04 (v6.3.38)
9
35
 
10
36
  - Blocked notification UX hardening for macOS:
@@ -7,9 +7,9 @@
7
7
  ## Estado actual
8
8
  - Plan activo: `docs/seguimiento-activo-pumuki-saas-supermercados.md`
9
9
  - Estado del plan: EJECUCION
10
- - Última task cerrada (`✅`): Fase 3.2 (actualización de `CHANGELOG.md` + `docs/RELEASE_NOTES.md` con fixes reales).
11
- - Task activa (`🚧`): Fase 3.3 publicación de versión.
12
- - Nuevos pendientes añadidos (`⏳`): sin cambios.
10
+ - Última task cerrada (`✅`): PUMUKI-019 (hardening de diagnóstico para hooks versionados `core.hooksPath` con fallback robusto + cobertura de no-regresión).
11
+ - Task activa (`🚧`): PUMUKI-020 (publicación del siguiente corte tras cierre de PUMUKI-019).
12
+ - Nuevos pendientes añadidos (`⏳`): ninguno en este bloque inmediato.
13
13
 
14
14
  ## Historial resumido
15
15
  - Bloque RuralGO cerrado: `docs/seguimiento-completo-validacion-ruralgo-03-03-2026.md`.
@@ -73,5 +73,57 @@
73
73
  - ✅ Ejecutar suite de tests de regresión afectada.
74
74
  - Evidencia (2026-03-04): `npx --yes tsx@4.21.0 --test scripts/__tests__/framework-menu-system-notifications.test.ts integrations/git/__tests__/stageRunners.test.ts integrations/lifecycle/__tests__/lifecycle.test.ts` -> `44 pass / 0 fail`.
75
75
  - ✅ Actualizar `CHANGELOG.md` y `docs/RELEASE_NOTES.md` con fixes reales.
76
- - Evidencia (2026-03-04): se documenta en `Unreleased` (CHANGELOG) y en `next patch candidate` (RELEASE_NOTES) el paquete de mejoras `PUMUKI-011` + baseline test alignment.
77
- - 🚧 Publicar versión cuando las tareas en construcción/pending críticas estén cerradas.
76
+ - Evidencia (2026-03-04): se documenta en `6.3.38` (CHANGELOG) y en `v6.3.38` (RELEASE_NOTES) el paquete de mejoras `PUMUKI-011` + baseline test alignment.
77
+ - Publicar versión cuando las tareas en construcción/pending críticas estén cerradas.
78
+ - Evidencia (2026-03-04): `npm publish --access public` => `+ pumuki@6.3.38` y verificación remota `npm view pumuki version` => `6.3.38`.
79
+
80
+ ## Fase 4. Post-release
81
+
82
+ - ✅ Monitorizar feedback de repos consumidores y registrar nuevos hallazgos canónicos.
83
+ - Evidencia (2026-03-04): se activa nuevo frente real en consumer repo con backlog dedicado en `/Users/juancarlosmerlosalbarracin/Developer/Projects/SAAS:APP_SUPERMERCADOS/docs/pumuki/PUMUKI_BUGS_MEJORAS.md`.
84
+ - ✅ Priorizar nuevos bugs/mejoras y abrir siguiente ciclo de implementación.
85
+ - Evidencia (2026-03-04): ciclo técnico arrancado en `ast-intelligence-hooks` con ejecución sobre bugs reales reportados desde SAAS.
86
+
87
+ ## Fase 4.1 Ciclo técnico actual (core Pumuki)
88
+
89
+ - ✅ PUMUKI-012: Endurecer comandos de adapter templates para hooks/CI sin dependencia frágil de `./node_modules/.bin`.
90
+ - Fix: `integrations/lifecycle/adapter.templates.json` ahora usa `npx --yes --package pumuki@latest ...` en `pre_write/pre_commit/pre_push/ci`.
91
+ - Test: `integrations/lifecycle/__tests__/adapter.test.ts`, `integrations/lifecycle/__tests__/doctor.test.ts`, `integrations/lifecycle/__tests__/cli.test.ts`.
92
+ - ✅ PUMUKI-013: Blindar resolución de rango Git cuando `HEAD`/refs no son resolubles (repos sin commits o refs ambiguas).
93
+ - Fix: `integrations/git/getCommitRangeFacts.ts` añade guardas `rev-parse --verify` + fallback seguro sin crash.
94
+ - Test: `integrations/git/__tests__/getCommitRangeFacts.test.ts` (nuevo caso repo sin commits) y `integrations/git/__tests__/runPlatformGateFacts.test.ts`.
95
+ - ✅ PUMUKI-014: Enforcement crítico transversal por plataforma (sin huecos entre skills activas y evaluación real).
96
+ - Fix: `integrations/git/runPlatformGate.ts` incorpora `governance.skills.cross-platform-critical.incomplete` y bloquea cuando una plataforma detectada no tiene reglas críticas (`CRITICAL/ERROR`) activas/evaluadas.
97
+ - Test: `integrations/git/__tests__/runPlatformGate.test.ts` añade casos de bloqueo/allow para cobertura crítica multi-plataforma.
98
+ - ✅ PUMUKI-015: Ejecutar validación extendida de no-regresión (suite stage-gates focal + smoke de hooks) y cerrar trazabilidad final de este bloque crítico.
99
+ - Evidencia (2026-03-04): `npm run -s test:stage-gates` -> `1018 pass / 0 fail / 4 skip`.
100
+ - Fixes incluidos para estabilizar la suite:
101
+ - `integrations/lifecycle/__tests__/saasIngestionBuilder.test.ts` (fixture de evidencia v2.1 con `evidence_chain` válido).
102
+ - `scripts/__tests__/framework-menu-consumer-preflight.test.ts` (contrato `evidence.source` completo en fixtures).
103
+ - `scripts/__tests__/architecture-file-size-guardrails.test.ts` (override explícito para `integrations/lifecycle/cli.ts` en límites de líneas/imports).
104
+ - ✅ PUMUKI-016: Preparar release notes del siguiente corte con trazabilidad de commits y validación ejecutada.
105
+ - Evidencia (2026-03-04):
106
+ - `CHANGELOG.md` actualizado en `[Unreleased]` con `adapter hooks`, `commit-range` y `cross-platform critical enforcement`.
107
+ - `docs/RELEASE_NOTES.md` actualizado con bloque `next cut candidate, post v6.3.38`.
108
+ - ✅ PUMUKI-017: Ejecutar siguiente bug/mejora del backlog SAAS (`PUMUKI-002`: enforcement de atomicidad Git por defecto) con RED->GREEN->REFACTOR.
109
+ - Fix:
110
+ - `integrations/git/gitAtomicity.ts` activa atomicidad por defecto (`enabled: true`) manteniendo override por env/config.
111
+ - `integrations/git/__tests__/gitAtomicity.test.ts` actualiza contrato base a enforcement activo por defecto.
112
+ - Evidencia (2026-03-04):
113
+ - `npx --yes tsx@4.21.0 --test integrations/git/__tests__/gitAtomicity.test.ts` -> `3 pass / 0 fail`.
114
+ - `npx --yes tsx@4.21.0 --test integrations/git/__tests__/stageRunners.test.ts` -> `21 pass / 0 fail`.
115
+ - `npx --yes tsx@4.21.0 --test integrations/lifecycle/__tests__/cli.test.ts` -> `29 pass / 0 fail`.
116
+ - ✅ PUMUKI-018: Preparar cierre de corte/publicación tras validación acumulada de PUMUKI-012..017.
117
+ - Evidencia (2026-03-04):
118
+ - `npm run -s test:stage-gates` -> `1018 pass / 0 fail / 4 skip` tras ajuste de regresión en `integrations/git/__tests__/hookGateSummary.test.ts`.
119
+ - Smoke complementario ya validado dentro del bloque: `gitAtomicity`, `stageRunners`, `lifecycle/cli`, `typecheck`.
120
+ - ✅ PUMUKI-019: Ejecutar siguiente bug/mejora del backlog SAAS de prioridad media (`PUMUKI-004`: hooks versionados `core.hooksPath`).
121
+ - Fix:
122
+ - `integrations/lifecycle/hookManager.ts` añade resolución robusta de hooks con fallback a `.git/config` (`core.hooksPath`) cuando no está disponible `git rev-parse --git-path hooks`.
123
+ - `integrations/lifecycle/status.ts` y `integrations/lifecycle/doctor.ts` exponen metadatos de ruta efectiva (`hooksDirectory`, `hooksDirectoryResolution`).
124
+ - `integrations/lifecycle/cli.ts` imprime ruta efectiva de hooks en `status` y `doctor` modo texto para diagnóstico humano.
125
+ - Evidencia (2026-03-04):
126
+ - `npx --yes tsx@4.21.0 --test integrations/lifecycle/__tests__/hookManager.test.ts integrations/lifecycle/__tests__/status.test.ts integrations/lifecycle/__tests__/doctor.test.ts integrations/lifecycle/__tests__/cli.test.ts` -> `50 pass / 0 fail`.
127
+ - `npm run -s test:stage-gates` -> `1020 pass / 0 fail / 4 skip`.
128
+ - `npm run -s typecheck` -> `PASS`.
129
+ - 🚧 PUMUKI-020: Preparar publicación del siguiente corte cuando PUMUKI-019 quede cerrada sin regresiones.
@@ -5,6 +5,24 @@ import { GitService } from './GitService';
5
5
 
6
6
  const defaultGit: IGitService = new GitService();
7
7
 
8
+ const isResolvableRef = (git: IGitService, ref: string): boolean => {
9
+ try {
10
+ git.runGit(['rev-parse', '--verify', ref]);
11
+ return true;
12
+ } catch {
13
+ return false;
14
+ }
15
+ };
16
+
17
+ const isRangeResolutionError = (error: unknown): boolean => {
18
+ if (!(error instanceof Error)) {
19
+ return false;
20
+ }
21
+ return /unknown revision|bad revision|ambiguous argument|fatal:\s+invalid object name/i.test(
22
+ error.message
23
+ );
24
+ };
25
+
8
26
  export async function getFactsForCommitRange(params: {
9
27
  fromRef: string;
10
28
  toRef: string;
@@ -12,11 +30,23 @@ export async function getFactsForCommitRange(params: {
12
30
  git?: IGitService;
13
31
  }): Promise<ReadonlyArray<Fact>> {
14
32
  const git = params.git ?? defaultGit;
15
- const diffOutput = git.runGit([
16
- 'diff',
17
- '--name-status',
18
- `${params.fromRef}..${params.toRef}`,
19
- ]);
33
+ if (!isResolvableRef(git, params.fromRef) || !isResolvableRef(git, params.toRef)) {
34
+ return [];
35
+ }
36
+
37
+ let diffOutput = '';
38
+ try {
39
+ diffOutput = git.runGit([
40
+ 'diff',
41
+ '--name-status',
42
+ `${params.fromRef}..${params.toRef}`,
43
+ ]);
44
+ } catch (error) {
45
+ if (isRangeResolutionError(error)) {
46
+ return [];
47
+ }
48
+ throw error;
49
+ }
20
50
  const changes = parseNameStatus(diffOutput).filter((change) =>
21
51
  hasAllowedExtension(change.path, params.extensions)
22
52
  );
@@ -32,7 +32,7 @@ const DEFAULT_COMMIT_PATTERN =
32
32
  '^(feat|fix|chore|refactor|docs|test|perf|build|ci|revert)(\\([^)]+\\))?:\\s.+$';
33
33
 
34
34
  const defaultConfig: GitAtomicityConfig = {
35
- enabled: false,
35
+ enabled: true,
36
36
  maxFiles: 25,
37
37
  maxScopes: 2,
38
38
  enforceCommitMessagePattern: true,
@@ -235,6 +235,10 @@ const PLATFORM_REQUIRED_SKILLS_BUNDLES: Record<
235
235
  frontend: ['frontend-guidelines'],
236
236
  };
237
237
 
238
+ const isCriticalProfileSeverity = (severity: string): boolean => {
239
+ return severity === 'CRITICAL' || severity === 'ERROR';
240
+ };
241
+
238
242
  const toNormalizedPath = (value: string): string => value.replace(/\\/g, '/').trim();
239
243
 
240
244
  const collectObservedPathsFromFacts = (
@@ -511,6 +515,66 @@ const toPlatformSkillsCoverageBlockingFinding = (params: {
511
515
  };
512
516
  };
513
517
 
518
+ const toCrossPlatformCriticalEnforcementBlockingFinding = (params: {
519
+ stage: 'PRE_COMMIT' | 'PRE_PUSH' | 'CI';
520
+ detectedPlatforms: DetectedPlatforms;
521
+ skillsRules: SkillsRuleSetLoadResult['rules'];
522
+ evaluatedRuleIds: ReadonlyArray<string>;
523
+ }): Finding | undefined => {
524
+ const detectedPlatformKeys = (
525
+ ['ios', 'android', 'backend', 'frontend'] as const
526
+ ).filter((platform) => params.detectedPlatforms[platform]?.detected === true);
527
+
528
+ if (detectedPlatformKeys.length === 0) {
529
+ return undefined;
530
+ }
531
+
532
+ const evaluatedRuleIds = new Set(params.evaluatedRuleIds);
533
+ const gaps: string[] = [];
534
+
535
+ for (const platform of detectedPlatformKeys) {
536
+ const rulePrefix = PLATFORM_SKILLS_RULE_PREFIXES[platform];
537
+ const criticalSkillRules = params.skillsRules
538
+ .filter(
539
+ (rule) =>
540
+ rule.id.startsWith(rulePrefix) &&
541
+ isCriticalProfileSeverity(rule.severity)
542
+ )
543
+ .map((rule) => rule.id)
544
+ .sort();
545
+
546
+ if (criticalSkillRules.length === 0) {
547
+ gaps.push(`${platform}{critical_profile_rules=missing}`);
548
+ continue;
549
+ }
550
+
551
+ const evaluatedCriticalSkillRules = criticalSkillRules.filter((ruleId) =>
552
+ evaluatedRuleIds.has(ruleId)
553
+ );
554
+ if (evaluatedCriticalSkillRules.length === 0) {
555
+ gaps.push(
556
+ `${platform}{critical_profile_rules=${criticalSkillRules.length}; evaluated=0}`
557
+ );
558
+ }
559
+ }
560
+
561
+ if (gaps.length === 0) {
562
+ return undefined;
563
+ }
564
+
565
+ return {
566
+ ruleId: 'governance.skills.cross-platform-critical.incomplete',
567
+ severity: 'ERROR',
568
+ code: 'SKILLS_CROSS_PLATFORM_CRITICAL_INCOMPLETE_S0',
569
+ message:
570
+ `Cross-platform critical enforcement incomplete at ${params.stage}: ${gaps.join(' | ')}. ` +
571
+ 'Ensure each detected platform has critical-profile skill rules active and evaluated.',
572
+ filePath: '.ai_evidence.json',
573
+ matchedBy: 'SkillsCrossPlatformCriticalGuard',
574
+ source: 'skills-cross-platform-critical',
575
+ };
576
+ };
577
+
514
578
  export async function runPlatformGate(params: {
515
579
  policy: GatePolicy;
516
580
  auditMode?: 'gate' | 'engine';
@@ -642,6 +706,17 @@ export async function runPlatformGate(params: {
642
706
  evaluatedRuleIds: coverage?.evaluatedRuleIds ?? [],
643
707
  })
644
708
  : undefined;
709
+ const crossPlatformCriticalFinding =
710
+ params.policy.stage === 'PRE_COMMIT' ||
711
+ params.policy.stage === 'PRE_PUSH' ||
712
+ params.policy.stage === 'CI'
713
+ ? toCrossPlatformCriticalEnforcementBlockingFinding({
714
+ stage: params.policy.stage,
715
+ detectedPlatforms,
716
+ skillsRules: skillsRuleSet.rules,
717
+ evaluatedRuleIds: coverage?.evaluatedRuleIds ?? [],
718
+ })
719
+ : undefined;
645
720
  const skillsScopeComplianceFinding =
646
721
  params.policy.stage === 'PRE_COMMIT' ||
647
722
  params.policy.stage === 'PRE_PUSH' ||
@@ -720,6 +795,7 @@ export async function runPlatformGate(params: {
720
795
  ...(policyAsCodeBlockingFinding ? [policyAsCodeBlockingFinding] : []),
721
796
  ...(unsupportedSkillsMappingFinding ? [unsupportedSkillsMappingFinding] : []),
722
797
  ...(platformSkillsCoverageFinding ? [platformSkillsCoverageFinding] : []),
798
+ ...(crossPlatformCriticalFinding ? [crossPlatformCriticalFinding] : []),
723
799
  ...(skillsScopeComplianceFinding ? [skillsScopeComplianceFinding] : []),
724
800
  ...(coverageBlockingFinding ? [coverageBlockingFinding] : []),
725
801
  ...tddBddEvaluation.findings,
@@ -727,6 +803,7 @@ export async function runPlatformGate(params: {
727
803
  ]
728
804
  : unsupportedSkillsMappingFinding
729
805
  || platformSkillsCoverageFinding
806
+ || crossPlatformCriticalFinding
730
807
  || skillsScopeComplianceFinding
731
808
  || coverageBlockingFinding
732
809
  || policyAsCodeBlockingFinding
@@ -737,6 +814,7 @@ export async function runPlatformGate(params: {
737
814
  ...(policyAsCodeBlockingFinding ? [policyAsCodeBlockingFinding] : []),
738
815
  ...(unsupportedSkillsMappingFinding ? [unsupportedSkillsMappingFinding] : []),
739
816
  ...(platformSkillsCoverageFinding ? [platformSkillsCoverageFinding] : []),
817
+ ...(crossPlatformCriticalFinding ? [crossPlatformCriticalFinding] : []),
740
818
  ...(skillsScopeComplianceFinding ? [skillsScopeComplianceFinding] : []),
741
819
  ...(coverageBlockingFinding ? [coverageBlockingFinding] : []),
742
820
  ...tddBddEvaluation.findings,
@@ -750,6 +828,7 @@ export async function runPlatformGate(params: {
750
828
  policyAsCodeBlockingFinding ||
751
829
  unsupportedSkillsMappingFinding ||
752
830
  platformSkillsCoverageFinding ||
831
+ crossPlatformCriticalFinding ||
753
832
  skillsScopeComplianceFinding ||
754
833
  coverageBlockingFinding ||
755
834
  hasTddBddBlockingFinding
@@ -5,16 +5,16 @@
5
5
  "payload": {
6
6
  "hooks": {
7
7
  "pre_write": {
8
- "command": "npx --yes pumuki-pre-write"
8
+ "command": "npx --yes --package pumuki@latest pumuki-pre-write"
9
9
  },
10
10
  "pre_commit": {
11
- "command": "./node_modules/.bin/pumuki-pre-commit"
11
+ "command": "npx --yes --package pumuki@latest pumuki-pre-commit"
12
12
  },
13
13
  "pre_push": {
14
- "command": "./node_modules/.bin/pumuki-pre-push"
14
+ "command": "npx --yes --package pumuki@latest pumuki-pre-push"
15
15
  },
16
16
  "ci": {
17
- "command": "npx --yes pumuki-ci"
17
+ "command": "npx --yes --package pumuki@latest pumuki-ci"
18
18
  }
19
19
  },
20
20
  "mcp": {
@@ -35,16 +35,16 @@
35
35
  "pumuki": {
36
36
  "hooks": {
37
37
  "pre_write": {
38
- "command": "npx --yes pumuki-pre-write"
38
+ "command": "npx --yes --package pumuki@latest pumuki-pre-write"
39
39
  },
40
40
  "pre_commit": {
41
- "command": "./node_modules/.bin/pumuki-pre-commit"
41
+ "command": "npx --yes --package pumuki@latest pumuki-pre-commit"
42
42
  },
43
43
  "pre_push": {
44
- "command": "./node_modules/.bin/pumuki-pre-push"
44
+ "command": "npx --yes --package pumuki@latest pumuki-pre-push"
45
45
  },
46
46
  "ci": {
47
- "command": "npx --yes pumuki-ci"
47
+ "command": "npx --yes --package pumuki@latest pumuki-ci"
48
48
  }
49
49
  }
50
50
  },
@@ -97,16 +97,16 @@
97
97
  },
98
98
  "pumukiHooks": {
99
99
  "pre_write": {
100
- "command": "npx --yes pumuki-pre-write"
100
+ "command": "npx --yes --package pumuki@latest pumuki-pre-write"
101
101
  },
102
102
  "pre_commit": {
103
- "command": "./node_modules/.bin/pumuki-pre-commit"
103
+ "command": "npx --yes --package pumuki@latest pumuki-pre-commit"
104
104
  },
105
105
  "pre_push": {
106
- "command": "./node_modules/.bin/pumuki-pre-push"
106
+ "command": "npx --yes --package pumuki@latest pumuki-pre-push"
107
107
  },
108
108
  "ci": {
109
- "command": "npx --yes pumuki-ci"
109
+ "command": "npx --yes --package pumuki@latest pumuki-ci"
110
110
  }
111
111
  }
112
112
  }
@@ -116,10 +116,10 @@
116
116
  {
117
117
  "path": ".codeium/adapter/hooks.json",
118
118
  "payload": {
119
- "preCommand": "npx --yes pumuki-pre-write",
120
- "postCommand": "./node_modules/.bin/pumuki-pre-commit",
121
- "pushCommand": "./node_modules/.bin/pumuki-pre-push",
122
- "ciCommand": "npx --yes pumuki-ci",
119
+ "preCommand": "npx --yes --package pumuki@latest pumuki-pre-write",
120
+ "postCommand": "npx --yes --package pumuki@latest pumuki-pre-commit",
121
+ "pushCommand": "npx --yes --package pumuki@latest pumuki-pre-push",
122
+ "ciCommand": "npx --yes --package pumuki@latest pumuki-ci",
123
123
  "mcpCommand": "npx --yes --package pumuki@latest pumuki-mcp-enterprise-stdio"
124
124
  }
125
125
  },
@@ -169,16 +169,16 @@
169
169
  "payload": {
170
170
  "hooks": {
171
171
  "pre_write": {
172
- "command": "npx --yes pumuki-pre-write"
172
+ "command": "npx --yes --package pumuki@latest pumuki-pre-write"
173
173
  },
174
174
  "pre_commit": {
175
- "command": "./node_modules/.bin/pumuki-pre-commit"
175
+ "command": "npx --yes --package pumuki@latest pumuki-pre-commit"
176
176
  },
177
177
  "pre_push": {
178
- "command": "./node_modules/.bin/pumuki-pre-push"
178
+ "command": "npx --yes --package pumuki@latest pumuki-pre-push"
179
179
  },
180
180
  "ci": {
181
- "command": "npx --yes pumuki-ci"
181
+ "command": "npx --yes --package pumuki@latest pumuki-ci"
182
182
  }
183
183
  },
184
184
  "mcp": {
@@ -975,6 +975,9 @@ const printDoctorReport = (
975
975
  ): void => {
976
976
  writeInfo(`[pumuki] repo: ${report.repoRoot}`);
977
977
  writeInfo(`[pumuki] package version: ${report.packageVersion}`);
978
+ writeInfo(
979
+ `[pumuki] hooks path: ${report.hooksDirectory} (${report.hooksDirectoryResolution})`
980
+ );
978
981
  writeInfo(
979
982
  `[pumuki] tracked node_modules paths: ${report.trackedNodeModulesPaths.length}`
980
983
  );
@@ -1422,6 +1425,9 @@ export const runLifecycleCli = async (
1422
1425
  writeInfo(`[pumuki] package version: ${status.packageVersion}`);
1423
1426
  writeInfo(`[pumuki] lifecycle installed: ${status.lifecycleState.installed ?? 'false'}`);
1424
1427
  writeInfo(`[pumuki] lifecycle version: ${status.lifecycleState.version ?? 'unknown'}`);
1428
+ writeInfo(
1429
+ `[pumuki] hooks path: ${status.hooksDirectory} (${status.hooksDirectoryResolution})`
1430
+ );
1425
1431
  writeInfo(
1426
1432
  `[pumuki] hooks: pre-commit=${status.hookStatus['pre-commit'].managedBlockPresent ? 'managed' : 'missing'}, pre-push=${status.hookStatus['pre-push'].managedBlockPresent ? 'managed' : 'missing'}`
1427
1433
  );
@@ -2,7 +2,7 @@ import { existsSync, readFileSync, realpathSync } from 'node:fs';
2
2
  import { join, resolve } from 'node:path';
3
3
  import { readEvidenceResult } from '../evidence/readEvidence';
4
4
  import { resolvePolicyForStage } from '../gate/stagePolicies';
5
- import { getPumukiHooksStatus } from './hookManager';
5
+ import { getPumukiHooksStatus, resolvePumukiHooksDirectory } from './hookManager';
6
6
  import { LifecycleGitService, type ILifecycleGitService } from './gitService';
7
7
  import { getCurrentPumukiVersion } from './packageInfo';
8
8
  import {
@@ -75,6 +75,8 @@ export type LifecycleDoctorReport = {
75
75
  lifecycleState: LifecycleState;
76
76
  trackedNodeModulesPaths: ReadonlyArray<string>;
77
77
  hookStatus: ReturnType<typeof getPumukiHooksStatus>;
78
+ hooksDirectory: string;
79
+ hooksDirectoryResolution: 'git-rev-parse' | 'git-config' | 'default';
78
80
  policyValidation: LifecyclePolicyValidationSnapshot;
79
81
  issues: ReadonlyArray<DoctorIssue>;
80
82
  deep?: DoctorDeepReport;
@@ -83,6 +85,7 @@ export type LifecycleDoctorReport = {
83
85
  const buildDoctorIssues = (params: {
84
86
  trackedNodeModulesPaths: ReadonlyArray<string>;
85
87
  hookStatus: ReturnType<typeof getPumukiHooksStatus>;
88
+ hooksDirectory: string;
86
89
  lifecycleState: LifecycleState;
87
90
  }): ReadonlyArray<DoctorIssue> => {
88
91
  const issues: DoctorIssue[] = [];
@@ -107,6 +110,7 @@ const buildDoctorIssues = (params: {
107
110
  severity: 'warning',
108
111
  message:
109
112
  `Lifecycle state says installed=true but managed hook blocks are incomplete (${managedHooks}/${totalHooks}). ` +
113
+ `Effective hooks path: ${params.hooksDirectory}. ` +
110
114
  'If you use versioned hooks via core.hooksPath, ensure those hooks include the PUMUKI MANAGED block or rerun "pumuki install".',
111
115
  });
112
116
  }
@@ -640,12 +644,14 @@ export const runLifecycleDoctor = (params?: {
640
644
  const cwd = params?.cwd ?? process.cwd();
641
645
  const repoRoot = git.resolveRepoRoot(cwd);
642
646
  const trackedNodeModulesPaths = git.trackedNodeModulesPaths(repoRoot);
647
+ const hooksDirectory = resolvePumukiHooksDirectory(repoRoot);
643
648
  const hookStatus = getPumukiHooksStatus(repoRoot);
644
649
  const lifecycleState = readLifecycleState(git, repoRoot);
645
650
 
646
651
  const issues = buildDoctorIssues({
647
652
  trackedNodeModulesPaths,
648
653
  hookStatus,
654
+ hooksDirectory: hooksDirectory.path,
649
655
  lifecycleState,
650
656
  });
651
657
  const deep = params?.deep
@@ -663,6 +669,8 @@ export const runLifecycleDoctor = (params?: {
663
669
  lifecycleState,
664
670
  trackedNodeModulesPaths,
665
671
  hookStatus,
672
+ hooksDirectory: hooksDirectory.path,
673
+ hooksDirectoryResolution: hooksDirectory.source,
666
674
  policyValidation: readLifecyclePolicyValidationSnapshot(repoRoot),
667
675
  issues,
668
676
  deep,
@@ -16,6 +16,16 @@ export type HookUninstallResult = {
16
16
  changedHooks: ReadonlyArray<PumukiManagedHook>;
17
17
  };
18
18
 
19
+ export type PumukiHooksDirectoryResolutionSource =
20
+ | 'git-rev-parse'
21
+ | 'git-config'
22
+ | 'default';
23
+
24
+ export type PumukiHooksDirectoryResolution = {
25
+ path: string;
26
+ source: PumukiHooksDirectoryResolutionSource;
27
+ };
28
+
19
29
  const HOOK_FILE_MODE = 0o755;
20
30
 
21
31
  const resolveGitPath = (repoRoot: string, gitPathTarget: string): string | null => {
@@ -34,8 +44,82 @@ const resolveGitPath = (repoRoot: string, gitPathTarget: string): string | null
34
44
  }
35
45
  };
36
46
 
47
+ const readCoreHooksPathFromGitConfig = (repoRoot: string): string | null => {
48
+ const configPath = join(repoRoot, '.git', 'config');
49
+ if (!existsSync(configPath)) {
50
+ return null;
51
+ }
52
+
53
+ let contents = '';
54
+ try {
55
+ contents = readFileSync(configPath, 'utf8');
56
+ } catch {
57
+ return null;
58
+ }
59
+
60
+ let inCoreSection = false;
61
+ for (const rawLine of contents.split(/\r?\n/)) {
62
+ const line = rawLine.trim();
63
+ if (line.length === 0 || line.startsWith(';') || line.startsWith('#')) {
64
+ continue;
65
+ }
66
+
67
+ if (line.startsWith('[') && line.endsWith(']')) {
68
+ inCoreSection = /^\[core\]$/i.test(line);
69
+ continue;
70
+ }
71
+
72
+ if (!inCoreSection) {
73
+ continue;
74
+ }
75
+
76
+ const match = /^hookspath\s*=\s*(.+)$/i.exec(line);
77
+ if (!match) {
78
+ continue;
79
+ }
80
+
81
+ let hooksPath = match[1]?.trim() ?? '';
82
+ if (hooksPath.startsWith('"') && hooksPath.endsWith('"')) {
83
+ hooksPath = hooksPath.slice(1, -1);
84
+ }
85
+ if (hooksPath.length === 0) {
86
+ continue;
87
+ }
88
+ return hooksPath;
89
+ }
90
+
91
+ return null;
92
+ };
93
+
94
+ export const resolvePumukiHooksDirectory = (
95
+ repoRoot: string
96
+ ): PumukiHooksDirectoryResolution => {
97
+ const gitPathHooks = resolveGitPath(repoRoot, 'hooks');
98
+ if (gitPathHooks) {
99
+ return {
100
+ path: gitPathHooks,
101
+ source: 'git-rev-parse',
102
+ };
103
+ }
104
+
105
+ const hooksPathFromConfig = readCoreHooksPathFromGitConfig(repoRoot);
106
+ if (hooksPathFromConfig) {
107
+ return {
108
+ path: isAbsolute(hooksPathFromConfig)
109
+ ? hooksPathFromConfig
110
+ : resolve(repoRoot, hooksPathFromConfig),
111
+ source: 'git-config',
112
+ };
113
+ }
114
+
115
+ return {
116
+ path: join(repoRoot, '.git', 'hooks'),
117
+ source: 'default',
118
+ };
119
+ };
120
+
37
121
  const resolveHooksDirectory = (repoRoot: string): string =>
38
- resolveGitPath(repoRoot, 'hooks') ?? join(repoRoot, '.git', 'hooks');
122
+ resolvePumukiHooksDirectory(repoRoot).path;
39
123
 
40
124
  const resolveHookPath = (repoRoot: string, hook: PumukiManagedHook): string =>
41
125
  join(resolveHooksDirectory(repoRoot), hook);
@@ -1,4 +1,4 @@
1
- import { getPumukiHooksStatus } from './hookManager';
1
+ import { getPumukiHooksStatus, resolvePumukiHooksDirectory } from './hookManager';
2
2
  import { LifecycleGitService, type ILifecycleGitService } from './gitService';
3
3
  import { getCurrentPumukiVersion } from './packageInfo';
4
4
  import {
@@ -12,6 +12,8 @@ export type LifecycleStatus = {
12
12
  packageVersion: string;
13
13
  lifecycleState: LifecycleState;
14
14
  hookStatus: ReturnType<typeof getPumukiHooksStatus>;
15
+ hooksDirectory: string;
16
+ hooksDirectoryResolution: 'git-rev-parse' | 'git-config' | 'default';
15
17
  trackedNodeModulesCount: number;
16
18
  policyValidation: LifecyclePolicyValidationSnapshot;
17
19
  };
@@ -23,6 +25,7 @@ export const readLifecycleStatus = (params?: {
23
25
  const git = params?.git ?? new LifecycleGitService();
24
26
  const cwd = params?.cwd ?? process.cwd();
25
27
  const repoRoot = git.resolveRepoRoot(cwd);
28
+ const hooksDirectory = resolvePumukiHooksDirectory(repoRoot);
26
29
  const trackedNodeModulesCount = git.trackedNodeModulesPaths(repoRoot).length;
27
30
 
28
31
  return {
@@ -30,6 +33,8 @@ export const readLifecycleStatus = (params?: {
30
33
  packageVersion: getCurrentPumukiVersion({ repoRoot }),
31
34
  lifecycleState: readLifecycleState(git, repoRoot),
32
35
  hookStatus: getPumukiHooksStatus(repoRoot),
36
+ hooksDirectory: hooksDirectory.path,
37
+ hooksDirectoryResolution: hooksDirectory.source,
33
38
  trackedNodeModulesCount,
34
39
  policyValidation: readLifecyclePolicyValidationSnapshot(repoRoot),
35
40
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pumuki",
3
- "version": "6.3.38",
3
+ "version": "6.3.39",
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": {