pumuki 6.3.26 → 6.3.28

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.
Files changed (76) hide show
  1. package/README.md +3 -1
  2. package/bin/pumuki-mcp-enterprise-stdio.js +5 -0
  3. package/bin/pumuki-mcp-evidence-stdio.js +5 -0
  4. package/core/gate/conditionMatches.ts +1 -21
  5. package/core/gate/evaluateGate.js +5 -0
  6. package/core/gate/evaluateRules.js +5 -0
  7. package/core/gate/evaluateRules.ts +1 -24
  8. package/core/gate/scopeMatcher.ts +84 -0
  9. package/docs/EXECUTION_BOARD.md +749 -376
  10. package/docs/MCP_SERVERS.md +41 -2
  11. package/docs/README.md +6 -2
  12. package/docs/REFRACTOR_PROGRESS.md +374 -6
  13. package/docs/validation/README.md +11 -1
  14. package/docs/validation/p9-ruralgo-bug-registry.md +607 -0
  15. package/docs/validation/p9-ruralgo-fork-validation-tracking.md +904 -0
  16. package/docs/validation/real-repo-manual-e2e-ruralgo-fork.md +372 -0
  17. package/integrations/config/skillsCompliance.ts +212 -0
  18. package/integrations/evidence/integrity.ts +352 -0
  19. package/integrations/evidence/rulesCoverage.ts +94 -0
  20. package/integrations/evidence/schema.test.ts +16 -0
  21. package/integrations/evidence/schema.ts +41 -0
  22. package/integrations/evidence/writeEvidence.test.ts +68 -0
  23. package/integrations/evidence/writeEvidence.ts +23 -2
  24. package/integrations/gate/evaluateAiGate.ts +382 -15
  25. package/integrations/gate/stagePolicies.ts +70 -15
  26. package/integrations/gate/waivers.ts +209 -0
  27. package/integrations/git/findingTraceability.ts +3 -23
  28. package/integrations/git/index.js +5 -0
  29. package/integrations/git/runCliCommand.ts +16 -0
  30. package/integrations/git/runPlatformGate.ts +53 -1
  31. package/integrations/git/runPlatformGateEvaluation.ts +13 -0
  32. package/integrations/git/stageRunners.ts +168 -5
  33. package/integrations/lifecycle/adapter.templates.json +72 -5
  34. package/integrations/lifecycle/adapter.ts +78 -4
  35. package/integrations/lifecycle/cli.ts +384 -14
  36. package/integrations/lifecycle/doctor.ts +534 -0
  37. package/integrations/lifecycle/hookBlock.ts +2 -1
  38. package/integrations/lifecycle/index.js +5 -0
  39. package/integrations/lifecycle/install.ts +115 -3
  40. package/integrations/lifecycle/openSpecBootstrap.ts +68 -8
  41. package/integrations/lifecycle/preWriteAutomation.ts +142 -0
  42. package/integrations/mcp/aiGateCheck.ts +6 -0
  43. package/integrations/mcp/aiGateReceipt.ts +188 -0
  44. package/integrations/mcp/enterpriseServer.ts +14 -1
  45. package/integrations/mcp/enterpriseStdioServer.cli.ts +315 -0
  46. package/integrations/mcp/evidenceStdioServer.cli.ts +342 -0
  47. package/integrations/mcp/index.js +5 -0
  48. package/integrations/sdd/index.js +5 -0
  49. package/integrations/sdd/index.ts +2 -0
  50. package/integrations/sdd/policy.ts +191 -2
  51. package/integrations/sdd/sessionStore.ts +139 -19
  52. package/integrations/sdd/syncDocs.ts +180 -0
  53. package/integrations/sdd/types.ts +4 -1
  54. package/integrations/telemetry/structuredTelemetry.ts +197 -0
  55. package/package.json +27 -8
  56. package/scripts/build-p9-validation-manifests.ts +53 -0
  57. package/scripts/check-p9-ruralgo-baseline-clean.ts +200 -0
  58. package/scripts/check-p9-ruralgo-baseline-versioned.ts +198 -0
  59. package/scripts/check-p9-ruralgo-branch-ready.ts +215 -0
  60. package/scripts/check-p9-ruralgo-install-health.ts +288 -0
  61. package/scripts/check-p9-ruralgo-runtime-ready.ts +188 -0
  62. package/scripts/check-package-manifest.ts +49 -0
  63. package/scripts/check-tracking-single-active.sh +40 -0
  64. package/scripts/framework-menu-consumer-preflight-lib.ts +31 -0
  65. package/scripts/framework-menu-consumer-runtime-lib.ts +3 -3
  66. package/scripts/framework-menu-legacy-audit-lib.ts +35 -7
  67. package/scripts/framework-menu-matrix-evidence-lib.ts +6 -2
  68. package/scripts/manage-library.sh +1 -1
  69. package/scripts/p9-ruralgo-baseline-clean-lib.ts +117 -0
  70. package/scripts/p9-ruralgo-baseline-versioned-lib.ts +119 -0
  71. package/scripts/p9-ruralgo-branch-ready-lib.ts +128 -0
  72. package/scripts/p9-ruralgo-install-health-lib.ts +121 -0
  73. package/scripts/p9-ruralgo-runtime-ready-lib.ts +149 -0
  74. package/scripts/p9-validation-manifests-lib.ts +366 -0
  75. package/scripts/package-manifest-lib.ts +9 -0
  76. package/skills.lock.json +1 -1
@@ -0,0 +1,119 @@
1
+ export type P9RuralgoBaselineVersionedIssueCode =
2
+ | 'NOT_GIT_REPO'
3
+ | 'OPENSPEC_PROJECT_MISSING'
4
+ | 'OPENSPEC_ARCHIVE_GITKEEP_MISSING'
5
+ | 'OPENSPEC_SPECS_GITKEEP_MISSING'
6
+ | 'AI_EVIDENCE_MISSING'
7
+ | 'OPENSPEC_PROJECT_UNTRACKED'
8
+ | 'OPENSPEC_ARCHIVE_GITKEEP_UNTRACKED'
9
+ | 'OPENSPEC_SPECS_GITKEEP_UNTRACKED'
10
+ | 'AI_EVIDENCE_UNTRACKED';
11
+
12
+ export type P9RuralgoBaselineVersionedIssue = {
13
+ code: P9RuralgoBaselineVersionedIssueCode;
14
+ severity: 'error' | 'warning';
15
+ message: string;
16
+ };
17
+
18
+ export type P9RuralgoBaselineVersionedSnapshot = {
19
+ isGitRepo: boolean;
20
+ openspecProjectExists: boolean;
21
+ openspecArchiveGitkeepExists: boolean;
22
+ openspecSpecsGitkeepExists: boolean;
23
+ aiEvidenceExists: boolean;
24
+ openspecProjectTracked: boolean;
25
+ openspecArchiveGitkeepTracked: boolean;
26
+ openspecSpecsGitkeepTracked: boolean;
27
+ aiEvidenceTracked: boolean;
28
+ };
29
+
30
+ export type P9RuralgoBaselineVersionedPolicy = {
31
+ requireExists: boolean;
32
+ requireTracked: boolean;
33
+ };
34
+
35
+ export type P9RuralgoBaselineVersionedResult = {
36
+ ready: boolean;
37
+ issues: ReadonlyArray<P9RuralgoBaselineVersionedIssue>;
38
+ };
39
+
40
+ export const DEFAULT_P9_RURALGO_BASELINE_VERSIONED_POLICY: P9RuralgoBaselineVersionedPolicy = {
41
+ requireExists: true,
42
+ requireTracked: true,
43
+ };
44
+
45
+ const pushIssue = (
46
+ issues: P9RuralgoBaselineVersionedIssue[],
47
+ code: P9RuralgoBaselineVersionedIssueCode,
48
+ message: string,
49
+ severity: 'error' | 'warning' = 'error',
50
+ ): void => {
51
+ issues.push({ code, severity, message });
52
+ };
53
+
54
+ export const evaluateP9RuralgoBaselineVersioned = (
55
+ snapshot: P9RuralgoBaselineVersionedSnapshot,
56
+ policyOverrides?: Partial<P9RuralgoBaselineVersionedPolicy>,
57
+ ): P9RuralgoBaselineVersionedResult => {
58
+ const policy: P9RuralgoBaselineVersionedPolicy = {
59
+ ...DEFAULT_P9_RURALGO_BASELINE_VERSIONED_POLICY,
60
+ ...(policyOverrides ?? {}),
61
+ };
62
+ const issues: P9RuralgoBaselineVersionedIssue[] = [];
63
+
64
+ if (!snapshot.isGitRepo) {
65
+ pushIssue(issues, 'NOT_GIT_REPO', 'La ruta objetivo no es un repositorio git válido.');
66
+ return { ready: false, issues };
67
+ }
68
+
69
+ if (policy.requireExists && !snapshot.openspecProjectExists) {
70
+ pushIssue(issues, 'OPENSPEC_PROJECT_MISSING', 'Falta openspec/project.md.');
71
+ }
72
+ if (policy.requireExists && !snapshot.openspecArchiveGitkeepExists) {
73
+ pushIssue(
74
+ issues,
75
+ 'OPENSPEC_ARCHIVE_GITKEEP_MISSING',
76
+ 'Falta openspec/changes/archive/.gitkeep.',
77
+ );
78
+ }
79
+ if (policy.requireExists && !snapshot.openspecSpecsGitkeepExists) {
80
+ pushIssue(issues, 'OPENSPEC_SPECS_GITKEEP_MISSING', 'Falta openspec/specs/.gitkeep.');
81
+ }
82
+ if (policy.requireExists && !snapshot.aiEvidenceExists) {
83
+ pushIssue(issues, 'AI_EVIDENCE_MISSING', 'Falta .ai_evidence.json.');
84
+ }
85
+
86
+ if (policy.requireTracked && !snapshot.openspecProjectTracked) {
87
+ pushIssue(
88
+ issues,
89
+ 'OPENSPEC_PROJECT_UNTRACKED',
90
+ 'openspec/project.md no está trackeado por git.',
91
+ );
92
+ }
93
+ if (policy.requireTracked && !snapshot.openspecArchiveGitkeepTracked) {
94
+ pushIssue(
95
+ issues,
96
+ 'OPENSPEC_ARCHIVE_GITKEEP_UNTRACKED',
97
+ 'openspec/changes/archive/.gitkeep no está trackeado por git.',
98
+ );
99
+ }
100
+ if (policy.requireTracked && !snapshot.openspecSpecsGitkeepTracked) {
101
+ pushIssue(
102
+ issues,
103
+ 'OPENSPEC_SPECS_GITKEEP_UNTRACKED',
104
+ 'openspec/specs/.gitkeep no está trackeado por git.',
105
+ );
106
+ }
107
+ if (policy.requireTracked && !snapshot.aiEvidenceTracked) {
108
+ pushIssue(
109
+ issues,
110
+ 'AI_EVIDENCE_UNTRACKED',
111
+ '.ai_evidence.json no está trackeado por git.',
112
+ );
113
+ }
114
+
115
+ return {
116
+ ready: !issues.some((issue) => issue.severity === 'error'),
117
+ issues,
118
+ };
119
+ };
@@ -0,0 +1,128 @@
1
+ export type P9RuralgoBranchReadinessIssueCode =
2
+ | 'NOT_GIT_REPO'
3
+ | 'CURRENT_BRANCH_UNKNOWN'
4
+ | 'BRANCH_MISMATCH'
5
+ | 'INVALID_BRANCH_NAMING'
6
+ | 'UPSTREAM_ALREADY_SET'
7
+ | 'WORKTREE_DIRTY'
8
+ | 'BASE_NOT_ANCESTOR'
9
+ | 'BASE_REF_UNAVAILABLE';
10
+
11
+ export type P9RuralgoBranchReadinessIssue = {
12
+ code: P9RuralgoBranchReadinessIssueCode;
13
+ severity: 'error' | 'warning';
14
+ message: string;
15
+ };
16
+
17
+ export type P9RuralgoBranchReadinessInput = {
18
+ repoPath: string;
19
+ expectedBranch: string;
20
+ currentBranch: string | null;
21
+ upstreamRef: string | null;
22
+ baseRef: string;
23
+ baseReachable: boolean | null;
24
+ worktreeDirty: boolean;
25
+ isGitRepo: boolean;
26
+ };
27
+
28
+ export type P9RuralgoBranchReadinessPolicy = {
29
+ requireNoUpstream: boolean;
30
+ requireCleanWorktree: boolean;
31
+ requireFeatureNaming: boolean;
32
+ };
33
+
34
+ export type P9RuralgoBranchReadinessResult = {
35
+ ready: boolean;
36
+ issues: ReadonlyArray<P9RuralgoBranchReadinessIssue>;
37
+ };
38
+
39
+ export const DEFAULT_P9_RURALGO_BRANCH_POLICY: P9RuralgoBranchReadinessPolicy = {
40
+ requireNoUpstream: true,
41
+ requireCleanWorktree: true,
42
+ requireFeatureNaming: true,
43
+ };
44
+
45
+ const FEATURE_BRANCH_PATTERN = /^feature\/[a-z0-9][a-z0-9-]*$/;
46
+
47
+ const pushIssue = (
48
+ issues: P9RuralgoBranchReadinessIssue[],
49
+ code: P9RuralgoBranchReadinessIssueCode,
50
+ message: string,
51
+ severity: 'error' | 'warning' = 'error',
52
+ ): void => {
53
+ issues.push({ code, severity, message });
54
+ };
55
+
56
+ export const evaluateP9RuralgoBranchReadiness = (
57
+ input: P9RuralgoBranchReadinessInput,
58
+ policyOverrides?: Partial<P9RuralgoBranchReadinessPolicy>,
59
+ ): P9RuralgoBranchReadinessResult => {
60
+ const policy: P9RuralgoBranchReadinessPolicy = {
61
+ ...DEFAULT_P9_RURALGO_BRANCH_POLICY,
62
+ ...(policyOverrides ?? {}),
63
+ };
64
+ const issues: P9RuralgoBranchReadinessIssue[] = [];
65
+
66
+ if (!input.isGitRepo) {
67
+ pushIssue(
68
+ issues,
69
+ 'NOT_GIT_REPO',
70
+ `La ruta no es un repositorio git válido: ${input.repoPath}.`,
71
+ );
72
+ return { ready: false, issues };
73
+ }
74
+
75
+ if (!input.currentBranch) {
76
+ pushIssue(issues, 'CURRENT_BRANCH_UNKNOWN', 'No se pudo resolver la rama actual.');
77
+ } else {
78
+ if (input.currentBranch !== input.expectedBranch) {
79
+ pushIssue(
80
+ issues,
81
+ 'BRANCH_MISMATCH',
82
+ `Rama activa "${input.currentBranch}" distinta de la esperada "${input.expectedBranch}".`,
83
+ );
84
+ }
85
+ if (policy.requireFeatureNaming && !FEATURE_BRANCH_PATTERN.test(input.currentBranch)) {
86
+ pushIssue(
87
+ issues,
88
+ 'INVALID_BRANCH_NAMING',
89
+ `La rama "${input.currentBranch}" no cumple el patrón GitFlow "feature/<kebab-case>".`,
90
+ );
91
+ }
92
+ }
93
+
94
+ if (policy.requireNoUpstream && input.upstreamRef) {
95
+ pushIssue(
96
+ issues,
97
+ 'UPSTREAM_ALREADY_SET',
98
+ `La rama ya tiene upstream configurado (${input.upstreamRef}); para ST2 se espera sin upstream.`,
99
+ );
100
+ }
101
+
102
+ if (policy.requireCleanWorktree && input.worktreeDirty) {
103
+ pushIssue(
104
+ issues,
105
+ 'WORKTREE_DIRTY',
106
+ 'El worktree tiene cambios locales; para preparar la rama se espera estado limpio.',
107
+ );
108
+ }
109
+
110
+ if (input.baseReachable === false) {
111
+ pushIssue(
112
+ issues,
113
+ 'BASE_NOT_ANCESTOR',
114
+ `HEAD no desciende de ${input.baseRef}; la rama no está preparada sobre la base esperada.`,
115
+ );
116
+ } else if (input.baseReachable === null) {
117
+ pushIssue(
118
+ issues,
119
+ 'BASE_REF_UNAVAILABLE',
120
+ `No se pudo verificar la relación con ${input.baseRef}.`,
121
+ );
122
+ }
123
+
124
+ return {
125
+ ready: !issues.some((issue) => issue.severity === 'error'),
126
+ issues,
127
+ };
128
+ };
@@ -0,0 +1,121 @@
1
+ export type P9RuralgoInstallHealthIssueCode =
2
+ | 'NOT_GIT_REPO'
3
+ | 'STATUS_COMMAND_FAILED'
4
+ | 'STATUS_JSON_INVALID'
5
+ | 'LIFECYCLE_NOT_INSTALLED'
6
+ | 'HOOK_PRE_COMMIT_UNMANAGED'
7
+ | 'HOOK_PRE_PUSH_UNMANAGED'
8
+ | 'DOCTOR_COMMAND_FAILED'
9
+ | 'DOCTOR_VERDICT_NOT_PASS'
10
+ | 'DOCTOR_VERDICT_UNPARSABLE';
11
+
12
+ export type P9RuralgoInstallHealthIssue = {
13
+ code: P9RuralgoInstallHealthIssueCode;
14
+ severity: 'error' | 'warning';
15
+ message: string;
16
+ };
17
+
18
+ export type P9RuralgoInstallHealthSnapshot = {
19
+ isGitRepo: boolean;
20
+ statusCommandOk: boolean;
21
+ statusJsonValid: boolean;
22
+ lifecycleInstalled: boolean | null;
23
+ preCommitManaged: boolean | null;
24
+ prePushManaged: boolean | null;
25
+ doctorCommandOk: boolean;
26
+ doctorVerdictPass: boolean | null;
27
+ };
28
+
29
+ export type P9RuralgoInstallHealthPolicy = {
30
+ requireInstalled: boolean;
31
+ requireManagedHooks: boolean;
32
+ requireDoctorPass: boolean;
33
+ };
34
+
35
+ export type P9RuralgoInstallHealthResult = {
36
+ ready: boolean;
37
+ issues: ReadonlyArray<P9RuralgoInstallHealthIssue>;
38
+ };
39
+
40
+ export const DEFAULT_P9_RURALGO_INSTALL_HEALTH_POLICY: P9RuralgoInstallHealthPolicy = {
41
+ requireInstalled: true,
42
+ requireManagedHooks: true,
43
+ requireDoctorPass: true,
44
+ };
45
+
46
+ const pushIssue = (
47
+ issues: P9RuralgoInstallHealthIssue[],
48
+ code: P9RuralgoInstallHealthIssueCode,
49
+ message: string,
50
+ severity: 'error' | 'warning' = 'error',
51
+ ): void => {
52
+ issues.push({ code, severity, message });
53
+ };
54
+
55
+ export const evaluateP9RuralgoInstallHealth = (
56
+ snapshot: P9RuralgoInstallHealthSnapshot,
57
+ policyOverrides?: Partial<P9RuralgoInstallHealthPolicy>,
58
+ ): P9RuralgoInstallHealthResult => {
59
+ const policy: P9RuralgoInstallHealthPolicy = {
60
+ ...DEFAULT_P9_RURALGO_INSTALL_HEALTH_POLICY,
61
+ ...(policyOverrides ?? {}),
62
+ };
63
+ const issues: P9RuralgoInstallHealthIssue[] = [];
64
+
65
+ if (!snapshot.isGitRepo) {
66
+ pushIssue(issues, 'NOT_GIT_REPO', 'La ruta objetivo no es un repositorio git válido.');
67
+ return { ready: false, issues };
68
+ }
69
+
70
+ if (!snapshot.statusCommandOk) {
71
+ pushIssue(issues, 'STATUS_COMMAND_FAILED', 'Falló `pumuki status --json`.');
72
+ } else if (!snapshot.statusJsonValid) {
73
+ pushIssue(issues, 'STATUS_JSON_INVALID', '`pumuki status --json` no devolvió JSON válido.');
74
+ }
75
+
76
+ if (policy.requireInstalled && snapshot.lifecycleInstalled !== true) {
77
+ pushIssue(
78
+ issues,
79
+ 'LIFECYCLE_NOT_INSTALLED',
80
+ 'Lifecycle no está instalado según `pumuki status --json`.',
81
+ );
82
+ }
83
+
84
+ if (policy.requireManagedHooks && snapshot.preCommitManaged !== true) {
85
+ pushIssue(
86
+ issues,
87
+ 'HOOK_PRE_COMMIT_UNMANAGED',
88
+ 'Hook `pre-commit` no está gestionado por Pumuki.',
89
+ );
90
+ }
91
+ if (policy.requireManagedHooks && snapshot.prePushManaged !== true) {
92
+ pushIssue(
93
+ issues,
94
+ 'HOOK_PRE_PUSH_UNMANAGED',
95
+ 'Hook `pre-push` no está gestionado por Pumuki.',
96
+ );
97
+ }
98
+
99
+ if (policy.requireDoctorPass) {
100
+ if (!snapshot.doctorCommandOk) {
101
+ pushIssue(issues, 'DOCTOR_COMMAND_FAILED', 'Falló `pumuki doctor --json`.');
102
+ } else if (snapshot.doctorVerdictPass === false) {
103
+ pushIssue(
104
+ issues,
105
+ 'DOCTOR_VERDICT_NOT_PASS',
106
+ '`pumuki doctor --json` no devolvió verdict PASS.',
107
+ );
108
+ } else if (snapshot.doctorVerdictPass === null) {
109
+ pushIssue(
110
+ issues,
111
+ 'DOCTOR_VERDICT_UNPARSABLE',
112
+ 'No se pudo interpretar el verdict de `pumuki doctor --json`.',
113
+ );
114
+ }
115
+ }
116
+
117
+ return {
118
+ ready: !issues.some((issue) => issue.severity === 'error'),
119
+ issues,
120
+ };
121
+ };
@@ -0,0 +1,149 @@
1
+ export type P9RuralgoRuntimeIssueCode =
2
+ | 'PACKAGE_JSON_MISSING'
3
+ | 'PACKAGE_JSON_INVALID'
4
+ | 'ENGINES_NODE_MISSING'
5
+ | 'ENGINES_NPM_MISSING'
6
+ | 'NODE_VERSION_UNKNOWN'
7
+ | 'NODE_VERSION_MISMATCH'
8
+ | 'NPM_VERSION_MISMATCH'
9
+ | 'NPM_VERSION_UNKNOWN'
10
+ | 'ENGINE_RANGE_UNSUPPORTED';
11
+
12
+ export type P9RuralgoRuntimeIssue = {
13
+ code: P9RuralgoRuntimeIssueCode;
14
+ severity: 'error' | 'warning';
15
+ message: string;
16
+ };
17
+
18
+ export type P9RuralgoRuntimeInput = {
19
+ requiredNode: string | null;
20
+ requiredNpm: string | null;
21
+ currentNode: string | null;
22
+ currentNpm: string | null;
23
+ packageJsonAvailable: boolean;
24
+ packageJsonValid: boolean;
25
+ };
26
+
27
+ export type P9RuralgoRuntimePolicy = {
28
+ requireNodeEngine: boolean;
29
+ requireNpmEngine: boolean;
30
+ };
31
+
32
+ export type P9RuralgoRuntimeResult = {
33
+ ready: boolean;
34
+ issues: ReadonlyArray<P9RuralgoRuntimeIssue>;
35
+ };
36
+
37
+ export const DEFAULT_P9_RURALGO_RUNTIME_POLICY: P9RuralgoRuntimePolicy = {
38
+ requireNodeEngine: true,
39
+ requireNpmEngine: true,
40
+ };
41
+
42
+ export const normalizeSemverToken = (value: string): string => value.trim().replace(/^v/i, '');
43
+
44
+ export const isExactSemver = (value: string): boolean =>
45
+ /^\d+\.\d+\.\d+$/.test(normalizeSemverToken(value));
46
+
47
+ const pushIssue = (
48
+ issues: P9RuralgoRuntimeIssue[],
49
+ code: P9RuralgoRuntimeIssueCode,
50
+ message: string,
51
+ severity: 'error' | 'warning' = 'error',
52
+ ): void => {
53
+ issues.push({ code, severity, message });
54
+ };
55
+
56
+ const evaluateExactEngine = (
57
+ issues: P9RuralgoRuntimeIssue[],
58
+ requiredVersion: string,
59
+ currentVersion: string | null,
60
+ mismatchCode: Extract<P9RuralgoRuntimeIssueCode, 'NODE_VERSION_MISMATCH' | 'NPM_VERSION_MISMATCH'>,
61
+ unknownCode: Extract<P9RuralgoRuntimeIssueCode, 'NODE_VERSION_UNKNOWN' | 'NPM_VERSION_UNKNOWN'>,
62
+ runtimeLabel: 'node' | 'npm',
63
+ ): void => {
64
+ if (!isExactSemver(requiredVersion)) {
65
+ pushIssue(
66
+ issues,
67
+ 'ENGINE_RANGE_UNSUPPORTED',
68
+ `Engine de ${runtimeLabel} no exacto ("${requiredVersion}"). Usa versión exacta para validación determinista.`,
69
+ 'warning',
70
+ );
71
+ return;
72
+ }
73
+ if (!currentVersion) {
74
+ pushIssue(
75
+ issues,
76
+ unknownCode,
77
+ `No se pudo resolver versión actual de ${runtimeLabel}.`,
78
+ );
79
+ return;
80
+ }
81
+ const normalizedRequired = normalizeSemverToken(requiredVersion);
82
+ const normalizedCurrent = normalizeSemverToken(currentVersion);
83
+ if (normalizedRequired !== normalizedCurrent) {
84
+ pushIssue(
85
+ issues,
86
+ mismatchCode,
87
+ `${runtimeLabel} requerido=${normalizedRequired} actual=${normalizedCurrent}`,
88
+ );
89
+ }
90
+ };
91
+
92
+ export const evaluateP9RuralgoRuntimeReadiness = (
93
+ input: P9RuralgoRuntimeInput,
94
+ policyOverrides?: Partial<P9RuralgoRuntimePolicy>,
95
+ ): P9RuralgoRuntimeResult => {
96
+ const policy: P9RuralgoRuntimePolicy = {
97
+ ...DEFAULT_P9_RURALGO_RUNTIME_POLICY,
98
+ ...(policyOverrides ?? {}),
99
+ };
100
+ const issues: P9RuralgoRuntimeIssue[] = [];
101
+
102
+ if (!input.packageJsonAvailable) {
103
+ pushIssue(issues, 'PACKAGE_JSON_MISSING', 'No existe package.json en el repo objetivo.');
104
+ return { ready: false, issues };
105
+ }
106
+ if (!input.packageJsonValid) {
107
+ pushIssue(issues, 'PACKAGE_JSON_INVALID', 'package.json no es JSON válido.');
108
+ return { ready: false, issues };
109
+ }
110
+
111
+ if (policy.requireNodeEngine && !input.requiredNode) {
112
+ pushIssue(
113
+ issues,
114
+ 'ENGINES_NODE_MISSING',
115
+ 'Falta engines.node en package.json del repo objetivo.',
116
+ );
117
+ } else if (input.requiredNode) {
118
+ evaluateExactEngine(
119
+ issues,
120
+ input.requiredNode,
121
+ input.currentNode,
122
+ 'NODE_VERSION_MISMATCH',
123
+ 'NODE_VERSION_UNKNOWN',
124
+ 'node',
125
+ );
126
+ }
127
+
128
+ if (policy.requireNpmEngine && !input.requiredNpm) {
129
+ pushIssue(
130
+ issues,
131
+ 'ENGINES_NPM_MISSING',
132
+ 'Falta engines.npm en package.json del repo objetivo.',
133
+ );
134
+ } else if (input.requiredNpm) {
135
+ evaluateExactEngine(
136
+ issues,
137
+ input.requiredNpm,
138
+ input.currentNpm,
139
+ 'NPM_VERSION_MISMATCH',
140
+ 'NPM_VERSION_UNKNOWN',
141
+ 'npm',
142
+ );
143
+ }
144
+
145
+ return {
146
+ ready: !issues.some((issue) => issue.severity === 'error'),
147
+ issues,
148
+ };
149
+ };