pumuki 6.3.72 → 6.3.73

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 (53) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/VERSION +1 -1
  3. package/docs/README.md +1 -1
  4. package/docs/operations/RELEASE_NOTES.md +12 -1
  5. package/docs/product/USAGE.md +2 -5
  6. package/docs/tracking/plan-curso-pumuki-stack-my-architecture.md +56 -105
  7. package/integrations/gate/governanceActionCatalog.ts +230 -0
  8. package/integrations/git/runPlatformGate.ts +9 -1
  9. package/integrations/git/runPlatformGateFacts.ts +1 -7
  10. package/integrations/git/runPlatformGateOutput.ts +36 -27
  11. package/integrations/lifecycle/adapter.templates.json +3 -0
  12. package/integrations/lifecycle/audit.ts +101 -0
  13. package/integrations/lifecycle/cli.ts +80 -8
  14. package/integrations/lifecycle/doctor.ts +64 -1
  15. package/integrations/lifecycle/governanceNextAction.ts +164 -0
  16. package/integrations/lifecycle/governanceObservationSnapshot.ts +288 -0
  17. package/integrations/lifecycle/index.ts +2 -0
  18. package/integrations/lifecycle/status.ts +29 -2
  19. package/integrations/mcp/autoExecuteAiStart.ts +86 -84
  20. package/integrations/mcp/preFlightCheck.ts +41 -5
  21. package/integrations/platform/detectPlatforms.ts +37 -0
  22. package/package.json +8 -1
  23. package/scripts/build-ruralgo-s1-evidence-pack.ts +85 -0
  24. package/scripts/consumer-menu-matrix-baseline-report-lib.ts +38 -13
  25. package/scripts/consumer-postinstall-resolve-args.cjs +38 -0
  26. package/scripts/consumer-postinstall.cjs +10 -1
  27. package/scripts/framework-menu-consumer-actions-lib.ts +4 -28
  28. package/scripts/framework-menu-consumer-preflight-hints.ts +2 -5
  29. package/scripts/framework-menu-consumer-preflight-render.ts +6 -0
  30. package/scripts/framework-menu-consumer-preflight-run.ts +19 -0
  31. package/scripts/framework-menu-consumer-preflight-types.ts +8 -0
  32. package/scripts/framework-menu-consumer-runtime-actions.ts +6 -86
  33. package/scripts/framework-menu-consumer-runtime-audit.ts +2 -36
  34. package/scripts/framework-menu-consumer-runtime-lib.ts +0 -2
  35. package/scripts/framework-menu-consumer-runtime-types.ts +1 -3
  36. package/scripts/framework-menu-evidence-summary-lib.ts +0 -1
  37. package/scripts/framework-menu-evidence-summary-read.ts +5 -57
  38. package/scripts/framework-menu-evidence-summary-severity.ts +1 -3
  39. package/scripts/framework-menu-evidence-summary-types.ts +0 -7
  40. package/scripts/framework-menu-gate-lib.ts +0 -9
  41. package/scripts/framework-menu-layout-data.ts +0 -5
  42. package/scripts/framework-menu-matrix-baseline-lib.ts +14 -15
  43. package/scripts/framework-menu-matrix-canary-lib.ts +1 -22
  44. package/scripts/framework-menu-matrix-evidence-lib.ts +0 -1
  45. package/scripts/framework-menu-matrix-evidence-types.ts +1 -13
  46. package/scripts/framework-menu-matrix-runner-lib.ts +0 -35
  47. package/scripts/framework-menu-system-notifications-macos.ts +0 -4
  48. package/scripts/framework-menu.ts +0 -3
  49. package/scripts/pumuki-full-surface-smoke-lib.ts +37 -0
  50. package/scripts/pumuki-full-surface-smoke.ts +261 -0
  51. package/scripts/pumuki-smoke-installed-wrapper.cjs +31 -0
  52. package/scripts/ruralgo-s1-evidence-pack-lib.ts +200 -0
  53. package/scripts/framework-menu-consumer-runtime-evidence-classic.ts +0 -140
@@ -0,0 +1,288 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { readEvidenceResult } from '../evidence/readEvidence';
4
+ import { readSddStatus } from '../sdd';
5
+ import type { SddStatusPayload } from '../sdd/types';
6
+ import type { LifecycleExperimentalFeaturesSnapshot } from './experimentalFeaturesSnapshot';
7
+ import type { ILifecycleGitService } from './gitService';
8
+ import { LifecycleGitService } from './gitService';
9
+ import type { LifecyclePolicyValidationSnapshot } from './policyValidationSnapshot';
10
+ import { writeInfo } from './cliOutputs';
11
+
12
+ const DEFAULT_PROTECTED_BRANCHES = new Set(['main', 'master', 'develop', 'dev']);
13
+
14
+ export type GovernanceEvidenceSummary = {
15
+ path: string;
16
+ readable: 'missing' | 'invalid' | 'valid';
17
+ snapshot_stage?: string;
18
+ snapshot_outcome?: 'PASS' | 'WARN' | 'BLOCK';
19
+ matched_warn_count?: number;
20
+ matched_blocking_count?: number;
21
+ findings_count?: number;
22
+ ai_gate_status?: 'ALLOWED' | 'BLOCKED';
23
+ human_summary_preview: string[];
24
+ };
25
+
26
+ export type GovernanceContractSurface = {
27
+ agents_md: boolean;
28
+ skills_lock_json: boolean;
29
+ skills_sources_json: boolean;
30
+ vendor_skills_dir: boolean;
31
+ pumuki_adapter_json: boolean;
32
+ };
33
+
34
+ export type GovernanceObservationSnapshot = {
35
+ schema_version: '1';
36
+ sdd: {
37
+ experimental_raw: string | null;
38
+ effective_mode: 'off' | 'advisory' | 'strict';
39
+ experimental_source: string;
40
+ };
41
+ sdd_session: {
42
+ active: boolean;
43
+ valid: boolean;
44
+ change_id: string | null;
45
+ remaining_seconds: number | null;
46
+ };
47
+ policy_strict: {
48
+ pre_write: boolean;
49
+ pre_commit: boolean;
50
+ pre_push: boolean;
51
+ ci: boolean;
52
+ };
53
+ enterprise_warn_as_block_env: boolean;
54
+ evidence: GovernanceEvidenceSummary;
55
+ git: {
56
+ current_branch: string | null;
57
+ on_protected_branch_hint: boolean;
58
+ };
59
+ contract_surface: GovernanceContractSurface;
60
+ attention_codes: ReadonlyArray<string>;
61
+ governance_effective: 'green' | 'attention' | 'blocked';
62
+ agent_bootstrap_hints: ReadonlyArray<string>;
63
+ };
64
+
65
+ const truthyEnv = (value: string | undefined): boolean => {
66
+ if (typeof value !== 'string') {
67
+ return false;
68
+ }
69
+ const normalized = value.trim().toLowerCase();
70
+ return normalized === '1' || normalized === 'true' || normalized === 'yes' || normalized === 'strict';
71
+ };
72
+
73
+ const readCurrentBranch = (git: ILifecycleGitService, repoRoot: string): string | null => {
74
+ try {
75
+ const branch = git.runGit(['rev-parse', '--abbrev-ref', 'HEAD'], repoRoot).trim();
76
+ return branch.length > 0 ? branch : null;
77
+ } catch {
78
+ return null;
79
+ }
80
+ };
81
+
82
+ const readSddStatusSafe = (repoRoot: string): SddStatusPayload => {
83
+ try {
84
+ return readSddStatus(repoRoot);
85
+ } catch {
86
+ return {
87
+ repoRoot,
88
+ openspec: {
89
+ installed: false,
90
+ projectInitialized: false,
91
+ minimumVersion: '0.0.0',
92
+ recommendedVersion: '0.0.0',
93
+ compatible: false,
94
+ },
95
+ session: {
96
+ repoRoot,
97
+ active: false,
98
+ valid: false,
99
+ },
100
+ };
101
+ }
102
+ };
103
+
104
+ const buildContractSurface = (repoRoot: string): GovernanceContractSurface => ({
105
+ agents_md: existsSync(join(repoRoot, 'AGENTS.md')),
106
+ skills_lock_json: existsSync(join(repoRoot, 'skills.lock.json')),
107
+ skills_sources_json: existsSync(join(repoRoot, 'skills.sources.json')),
108
+ vendor_skills_dir: existsSync(join(repoRoot, 'vendor', 'skills')),
109
+ pumuki_adapter_json: existsSync(join(repoRoot, '.pumuki', 'adapter.json')),
110
+ });
111
+
112
+ const summarizeEvidence = (repoRoot: string): GovernanceEvidenceSummary => {
113
+ const evidenceResult = readEvidenceResult(repoRoot);
114
+ const path = evidenceResult.source_descriptor.path;
115
+ if (evidenceResult.kind === 'missing') {
116
+ return { path, readable: 'missing', human_summary_preview: [] };
117
+ }
118
+ if (evidenceResult.kind === 'invalid') {
119
+ return {
120
+ path,
121
+ readable: 'invalid',
122
+ human_summary_preview: [evidenceResult.detail ?? evidenceResult.reason],
123
+ };
124
+ }
125
+
126
+ const snapshot = evidenceResult.evidence.snapshot;
127
+ const hints = evidenceResult.evidence.operational_hints?.human_summary_lines ?? [];
128
+ const breakdown = evidenceResult.evidence.operational_hints?.rule_execution_breakdown;
129
+ return {
130
+ path,
131
+ readable: 'valid',
132
+ snapshot_stage: snapshot.stage,
133
+ snapshot_outcome: snapshot.outcome,
134
+ matched_warn_count: breakdown?.matched_warn_count,
135
+ matched_blocking_count: breakdown?.matched_blocking_count,
136
+ findings_count: Array.isArray(snapshot.findings) ? snapshot.findings.length : 0,
137
+ ai_gate_status: evidenceResult.evidence.ai_gate.status,
138
+ human_summary_preview: hints.slice(0, 5),
139
+ };
140
+ };
141
+
142
+ const buildHints = (
143
+ surface: GovernanceContractSurface,
144
+ branch: string | null,
145
+ protectedBranchHint: boolean
146
+ ): string[] => {
147
+ const hints: string[] = [];
148
+ if (surface.agents_md) {
149
+ hints.push('AGENTS.md presente: aplica el contrato del repo antes de dar governance en verde.');
150
+ }
151
+ if (!surface.skills_lock_json) {
152
+ hints.push('Falta skills.lock.json: genera lock canónico de skills antes de cerrar la gobernanza.');
153
+ }
154
+ if (!surface.pumuki_adapter_json) {
155
+ hints.push('Falta .pumuki/adapter.json: instala el adaptador si quieres wiring IDE/MCP explícito.');
156
+ }
157
+ if (protectedBranchHint && branch) {
158
+ hints.push(`La rama "${branch}" cae en el set protegido por defecto: usa feature/* o refactor/*.`);
159
+ }
160
+ hints.push('SDD/OpenSpec: usa PUMUKI_EXPERIMENTAL_SDD=advisory|strict cuando el loop SDD esté activo.');
161
+ hints.push('WARN-as-BLOCK: activa PUMUKI_ENTERPRISE_STRICT_WARN_AS_BLOCK=1 si el repo exige promoción dura.');
162
+ return hints;
163
+ };
164
+
165
+ export const readGovernanceObservationSnapshot = (params: {
166
+ repoRoot: string;
167
+ experimentalFeatures: LifecycleExperimentalFeaturesSnapshot;
168
+ policyValidation: LifecyclePolicyValidationSnapshot;
169
+ git?: ILifecycleGitService;
170
+ }): GovernanceObservationSnapshot => {
171
+ const git = params.git ?? new LifecycleGitService();
172
+ const { repoRoot, experimentalFeatures, policyValidation } = params;
173
+ const rawSdd = process.env.PUMUKI_EXPERIMENTAL_SDD?.trim();
174
+ const sddStatus = readSddStatusSafe(repoRoot);
175
+ const evidence = summarizeEvidence(repoRoot);
176
+ const branch = readCurrentBranch(git, repoRoot);
177
+ const onProtected = typeof branch === 'string' && DEFAULT_PROTECTED_BRANCHES.has(branch.trim().toLowerCase());
178
+ const surface = buildContractSurface(repoRoot);
179
+ const warnAsBlock = truthyEnv(process.env.PUMUKI_ENTERPRISE_STRICT_WARN_AS_BLOCK);
180
+
181
+ const attention: string[] = [];
182
+ if (evidence.readable === 'invalid') {
183
+ attention.push('EVIDENCE_INVALID_OR_CHAIN');
184
+ }
185
+ if (evidence.readable === 'valid' && evidence.ai_gate_status === 'BLOCKED') {
186
+ attention.push('AI_GATE_BLOCKED');
187
+ }
188
+ if (evidence.readable === 'valid' && evidence.snapshot_outcome === 'WARN') {
189
+ attention.push('EVIDENCE_SNAPSHOT_WARN');
190
+ }
191
+ if (evidence.readable === 'valid' && evidence.snapshot_outcome === 'BLOCK') {
192
+ attention.push('EVIDENCE_SNAPSHOT_BLOCK');
193
+ }
194
+ if (sddStatus.session.active === true && sddStatus.session.valid !== true) {
195
+ attention.push('SDD_SESSION_INVALID_OR_EXPIRED');
196
+ }
197
+ if (!policyValidation.stages.PRE_WRITE.strict) {
198
+ attention.push('POLICY_PRE_WRITE_NOT_STRICT');
199
+ }
200
+ if (!policyValidation.stages.PRE_COMMIT.strict) {
201
+ attention.push('POLICY_PRE_COMMIT_NOT_STRICT');
202
+ }
203
+ if (!policyValidation.stages.PRE_PUSH.strict) {
204
+ attention.push('POLICY_PRE_PUSH_NOT_STRICT');
205
+ }
206
+ if (!policyValidation.stages.CI.strict) {
207
+ attention.push('POLICY_CI_NOT_STRICT');
208
+ }
209
+ if (onProtected) {
210
+ attention.push('GITFLOW_PROTECTED_BRANCH_CONTEXT');
211
+ }
212
+
213
+ let governanceEffective: GovernanceObservationSnapshot['governance_effective'] = 'green';
214
+ if (
215
+ evidence.readable === 'invalid'
216
+ || (evidence.readable === 'valid' && evidence.ai_gate_status === 'BLOCKED')
217
+ || (evidence.readable === 'valid' && evidence.snapshot_outcome === 'BLOCK')
218
+ ) {
219
+ governanceEffective = 'blocked';
220
+ } else if (attention.length > 0) {
221
+ governanceEffective = 'attention';
222
+ }
223
+
224
+ return {
225
+ schema_version: '1',
226
+ sdd: {
227
+ experimental_raw: rawSdd && rawSdd.length > 0 ? rawSdd : null,
228
+ effective_mode: experimentalFeatures.features.sdd.mode,
229
+ experimental_source: experimentalFeatures.features.sdd.source,
230
+ },
231
+ sdd_session: {
232
+ active: sddStatus.session.active,
233
+ valid: sddStatus.session.valid,
234
+ change_id: sddStatus.session.changeId ?? null,
235
+ remaining_seconds:
236
+ typeof sddStatus.session.remainingSeconds === 'number' ? sddStatus.session.remainingSeconds : null,
237
+ },
238
+ policy_strict: {
239
+ pre_write: policyValidation.stages.PRE_WRITE.strict,
240
+ pre_commit: policyValidation.stages.PRE_COMMIT.strict,
241
+ pre_push: policyValidation.stages.PRE_PUSH.strict,
242
+ ci: policyValidation.stages.CI.strict,
243
+ },
244
+ enterprise_warn_as_block_env: warnAsBlock,
245
+ evidence,
246
+ git: {
247
+ current_branch: branch,
248
+ on_protected_branch_hint: onProtected,
249
+ },
250
+ contract_surface: surface,
251
+ attention_codes: attention,
252
+ governance_effective: governanceEffective,
253
+ agent_bootstrap_hints: buildHints(surface, branch, onProtected),
254
+ };
255
+ };
256
+
257
+ export const buildGovernanceObservationSummaryLines = (
258
+ snapshot: GovernanceObservationSnapshot
259
+ ): string[] => {
260
+ const lines = [
261
+ `Governance: ${snapshot.governance_effective.toUpperCase()}`,
262
+ `Contract: AGENTS=${snapshot.contract_surface.agents_md ? 'yes' : 'no'} skills.lock=${snapshot.contract_surface.skills_lock_json ? 'yes' : 'no'} skills.sources=${snapshot.contract_surface.skills_sources_json ? 'yes' : 'no'} vendor/skills=${snapshot.contract_surface.vendor_skills_dir ? 'yes' : 'no'} adapter=${snapshot.contract_surface.pumuki_adapter_json ? 'yes' : 'no'}`,
263
+ `SDD: env=${snapshot.sdd.experimental_raw ?? '(unset)'} effective=${snapshot.sdd.effective_mode} session_active=${snapshot.sdd_session.active} session_valid=${snapshot.sdd_session.valid} change=${snapshot.sdd_session.change_id ?? 'none'}`,
264
+ `Evidence: readable=${snapshot.evidence.readable} stage=${snapshot.evidence.snapshot_stage ?? 'n/a'} outcome=${snapshot.evidence.snapshot_outcome ?? 'n/a'} ai_gate=${snapshot.evidence.ai_gate_status ?? 'n/a'} findings=${snapshot.evidence.findings_count ?? 'n/a'}`,
265
+ `GitFlow: branch=${snapshot.git.current_branch ?? 'unknown'} protected_hint=${snapshot.git.on_protected_branch_hint ? 'yes' : 'no'}`,
266
+ `Policy strict: PRE_WRITE=${snapshot.policy_strict.pre_write} PRE_COMMIT=${snapshot.policy_strict.pre_commit} PRE_PUSH=${snapshot.policy_strict.pre_push} CI=${snapshot.policy_strict.ci}`,
267
+ ];
268
+ if (snapshot.attention_codes.length > 0) {
269
+ lines.push(`Attention: ${snapshot.attention_codes.join(', ')}`);
270
+ }
271
+ return lines;
272
+ };
273
+
274
+ export const printGovernanceObservationHuman = (snapshot: GovernanceObservationSnapshot): void => {
275
+ writeInfo('[pumuki] governance truth (S1 / governance console baseline):');
276
+ for (const line of buildGovernanceObservationSummaryLines(snapshot)) {
277
+ writeInfo(`[pumuki] ${line}`);
278
+ }
279
+ for (const hint of snapshot.evidence.human_summary_preview) {
280
+ writeInfo(`[pumuki] evidence hint: ${hint}`);
281
+ }
282
+ };
283
+
284
+ export const doctorGovernanceIsBlocking = (snapshot: GovernanceObservationSnapshot): boolean =>
285
+ snapshot.governance_effective === 'blocked';
286
+
287
+ export const doctorGovernanceNeedsAttention = (snapshot: GovernanceObservationSnapshot): boolean =>
288
+ snapshot.governance_effective !== 'green';
@@ -1,4 +1,6 @@
1
1
  export { runLifecycleDoctor, doctorHasBlockingIssues } from './doctor';
2
+ export { runLifecycleAudit } from './audit';
3
+ export type { LifecycleAuditResult, LifecycleAuditStage } from './audit';
2
4
  export { runLifecycleInstall } from './install';
3
5
  export { runLifecycleUninstall } from './uninstall';
4
6
  export { runLifecycleRemove } from './remove';
@@ -9,6 +9,15 @@ import {
9
9
  readLifecyclePolicyValidationSnapshot,
10
10
  type LifecyclePolicyValidationSnapshot,
11
11
  } from './policyValidationSnapshot';
12
+ import {
13
+ readGovernanceObservationSnapshot,
14
+ type GovernanceObservationSnapshot,
15
+ } from './governanceObservationSnapshot';
16
+ import {
17
+ readGovernanceNextAction,
18
+ type GovernanceNextActionReader,
19
+ type GovernanceNextActionSummary,
20
+ } from './governanceNextAction';
12
21
  import { readLifecycleState, type LifecycleState } from './state';
13
22
 
14
23
  export type LifecycleStatus = {
@@ -22,11 +31,14 @@ export type LifecycleStatus = {
22
31
  trackedNodeModulesCount: number;
23
32
  policyValidation: LifecyclePolicyValidationSnapshot;
24
33
  experimentalFeatures: LifecycleExperimentalFeaturesSnapshot;
34
+ governanceObservation: GovernanceObservationSnapshot;
35
+ governanceNextAction: GovernanceNextActionSummary;
25
36
  };
26
37
 
27
38
  export const readLifecycleStatus = (params?: {
28
39
  cwd?: string;
29
40
  git?: ILifecycleGitService;
41
+ governanceNextActionReader?: GovernanceNextActionReader;
30
42
  }): LifecycleStatus => {
31
43
  const git = params?.git ?? new LifecycleGitService();
32
44
  const cwd = params?.cwd ?? process.cwd();
@@ -38,6 +50,19 @@ export const readLifecycleStatus = (params?: {
38
50
  repoRoot,
39
51
  lifecycleVersion: lifecycleState.version,
40
52
  });
53
+ const policyValidation = readLifecyclePolicyValidationSnapshot(repoRoot);
54
+ const experimentalFeatures = readLifecycleExperimentalFeaturesSnapshot();
55
+ const governanceObservation = readGovernanceObservationSnapshot({
56
+ repoRoot,
57
+ experimentalFeatures,
58
+ policyValidation,
59
+ git,
60
+ });
61
+ const governanceNextAction = (params?.governanceNextActionReader ?? readGovernanceNextAction)({
62
+ repoRoot,
63
+ stage: 'PRE_WRITE',
64
+ governanceObservation,
65
+ });
41
66
 
42
67
  return {
43
68
  repoRoot,
@@ -48,7 +73,9 @@ export const readLifecycleStatus = (params?: {
48
73
  hooksDirectory: hooksDirectory.path,
49
74
  hooksDirectoryResolution: hooksDirectory.source,
50
75
  trackedNodeModulesCount,
51
- policyValidation: readLifecyclePolicyValidationSnapshot(repoRoot),
52
- experimentalFeatures: readLifecycleExperimentalFeaturesSnapshot(),
76
+ policyValidation,
77
+ experimentalFeatures,
78
+ governanceNextAction,
79
+ governanceObservation,
53
80
  };
54
81
  };
@@ -1,3 +1,7 @@
1
+ import {
2
+ buildGovernanceValidateCommand,
3
+ resolveGovernanceCatalogAction,
4
+ } from '../gate/governanceActionCatalog';
1
5
  import { evaluateAiGate, type AiGateStage, type AiGateViolation } from '../gate/evaluateAiGate';
2
6
  import { collectWorktreeAtomicSlices } from '../git/worktreeAtomicSlices';
3
7
  import { resolveLearningContextExperimentalFeature } from '../policy/experimentalFeatures';
@@ -56,90 +60,87 @@ const confidenceFromViolation = (violationCode: string | null): number => {
56
60
  return 50;
57
61
  };
58
62
 
59
- const nextActionFromViolation = (
60
- violation: AiGateViolation | undefined,
61
- repoRoot: string
62
- ): AutoExecuteNextAction => {
63
- if (!violation) {
64
- return {
65
- kind: 'info',
66
- message: 'Gate listo. Puedes continuar con implementación.',
67
- };
68
- }
69
- switch (violation.code) {
70
- case 'EVIDENCE_MISSING':
63
+ const normalizeGovernanceCatalogCode = (code: string): string => {
64
+ switch (code) {
71
65
  case 'EVIDENCE_INVALID':
72
66
  case 'EVIDENCE_CHAIN_INVALID':
73
- case 'EVIDENCE_STALE':
74
- return {
75
- kind: 'run_command',
76
- message: 'Regenera o refresca evidencia y vuelve a evaluar PRE_WRITE.',
77
- command: 'npx --yes --package pumuki@latest pumuki sdd validate --stage=PRE_WRITE --json',
78
- };
79
- case 'EVIDENCE_ACTIVE_RULE_IDS_EMPTY_FOR_CODE_CHANGES':
80
- return {
81
- kind: 'run_command',
82
- message:
83
- 'No hay active_rule_ids para plataforma de código detectada. Reconciliación strict de policy/skills y revalidación PRE_WRITE.',
84
- command:
85
- 'npx --yes --package pumuki@latest pumuki policy reconcile --strict --json && npx --yes --package pumuki@latest pumuki sdd validate --stage=PRE_WRITE --json',
86
- };
87
- case 'EVIDENCE_PLATFORM_SKILLS_SCOPE_INCOMPLETE':
88
- case 'EVIDENCE_PLATFORM_SKILLS_BUNDLES_MISSING':
89
- case 'EVIDENCE_SKILLS_CONTRACT_INCOMPLETE':
90
- return {
91
- kind: 'run_command',
92
- message:
93
- 'Completa cobertura de skills por plataforma (prefijos + bundles) y revalida PRE_WRITE.',
94
- command: 'npx --yes --package pumuki@latest pumuki sdd validate --stage=PRE_WRITE --json',
95
- };
96
- case 'EVIDENCE_PLATFORM_CRITICAL_SKILLS_RULES_MISSING':
97
- case 'EVIDENCE_CROSS_PLATFORM_CRITICAL_ENFORCEMENT_INCOMPLETE':
98
- return {
99
- kind: 'run_command',
100
- message:
101
- 'Reconcilia policy/skills en modo estricto (incluida skills.ios.critical-test-quality cuando aplique) y revalida PRE_WRITE.',
102
- command:
103
- 'npx --yes --package pumuki@latest pumuki policy reconcile --strict --json && npx --yes --package pumuki@latest pumuki sdd validate --stage=PRE_WRITE --json',
104
- };
105
- case 'EVIDENCE_PREWRITE_WORKTREE_OVER_LIMIT':
106
- case 'EVIDENCE_PREWRITE_WORKTREE_WARN':
107
- {
108
- const plan = collectWorktreeAtomicSlices({
109
- repoRoot,
110
- maxSlices: 3,
111
- maxFilesPerSlice: 4,
112
- });
113
- if (plan.slices.length > 0) {
114
- const firstSlice = plan.slices[0];
115
- return {
116
- kind: 'run_command',
117
- message:
118
- `Particiona el worktree en slices atómicos por scope. Primer lote sugerido: ${firstSlice?.scope ?? 'scope-desconocido'}.`,
119
- command:
120
- `${firstSlice?.staged_command ?? 'git add -p'} && npx --yes --package pumuki@latest pumuki sdd validate --stage=PRE_WRITE --json`,
121
- };
122
- }
123
- }
124
- return {
125
- kind: 'run_command',
126
- message:
127
- 'Particiona el worktree en slices atómicos y revalida PRE_WRITE para continuar sin fricción.',
128
- command:
129
- 'git status --short && git add -p && npx --yes --package pumuki@latest pumuki sdd validate --stage=PRE_WRITE --json',
130
- };
67
+ return 'EVIDENCE_INVALID_OR_CHAIN';
131
68
  case 'GITFLOW_PROTECTED_BRANCH':
132
- return {
133
- kind: 'run_command',
134
- message: 'Cambia a una rama feature/* antes de continuar.',
135
- command: 'git checkout -b feature/<descripcion-kebab-case>',
136
- };
69
+ return 'GITFLOW_PROTECTED_BRANCH_CONTEXT';
137
70
  default:
71
+ return code;
72
+ }
73
+ };
74
+
75
+ const resolveAutoExecuteRemediation = (params: {
76
+ violation: AiGateViolation | undefined;
77
+ repoRoot: string;
78
+ stage: AiGateStage;
79
+ allowed: boolean;
80
+ }): {
81
+ instruction: string;
82
+ nextAction: AutoExecuteNextAction;
83
+ } => {
84
+ if (!params.violation) {
85
+ const readyAction = resolveGovernanceCatalogAction({
86
+ code: 'READY',
87
+ stage: params.stage,
88
+ });
89
+ return {
90
+ instruction: readyAction.instruction,
91
+ nextAction: readyAction.next_action,
92
+ };
93
+ }
94
+
95
+ if (
96
+ params.violation.code === 'EVIDENCE_PREWRITE_WORKTREE_OVER_LIMIT'
97
+ || params.violation.code === 'EVIDENCE_PREWRITE_WORKTREE_WARN'
98
+ ) {
99
+ const validateCommand = buildGovernanceValidateCommand(params.stage);
100
+ const plan = collectWorktreeAtomicSlices({
101
+ repoRoot: params.repoRoot,
102
+ maxSlices: 3,
103
+ maxFilesPerSlice: 4,
104
+ });
105
+ if (plan.slices.length > 0) {
106
+ const firstSlice = plan.slices[0];
138
107
  return {
139
- kind: 'info',
140
- message: 'Corrige la violación bloqueante y vuelve a ejecutar auto_execute_ai_start.',
108
+ instruction: 'Particiona el worktree en slices atómicos antes de continuar.',
109
+ nextAction: {
110
+ kind: params.allowed ? 'info' : 'run_command',
111
+ message:
112
+ `Particiona el worktree en slices atómicos por scope. Primer lote sugerido: ${firstSlice?.scope ?? 'scope-desconocido'}.`,
113
+ command: params.allowed
114
+ ? undefined
115
+ : `${firstSlice?.staged_command ?? 'git add -p'} && ${validateCommand}`,
116
+ },
141
117
  };
118
+ }
119
+ return {
120
+ instruction: 'Particiona el worktree en slices atómicos antes de continuar.',
121
+ nextAction: {
122
+ kind: params.allowed ? 'info' : 'run_command',
123
+ message: 'Particiona el worktree en slices atómicos y revalida PRE_WRITE para continuar sin fricción.',
124
+ command: params.allowed
125
+ ? undefined
126
+ : `git status --short && git add -p && ${validateCommand}`,
127
+ },
128
+ };
142
129
  }
130
+
131
+ const governanceAction = resolveGovernanceCatalogAction({
132
+ code: normalizeGovernanceCatalogCode(params.violation.code),
133
+ stage: params.stage,
134
+ });
135
+ return {
136
+ instruction: governanceAction.instruction,
137
+ nextAction: params.allowed
138
+ ? {
139
+ kind: 'info',
140
+ message: governanceAction.next_action.message,
141
+ }
142
+ : governanceAction.next_action,
143
+ };
143
144
  };
144
145
 
145
146
  export type EnterpriseAutoExecuteAiStartResult = {
@@ -190,19 +191,20 @@ export const runEnterpriseAutoExecuteAiStart = (params: {
190
191
  const action: AutoExecuteAction = evaluation.allowed ? 'proceed' : 'ask';
191
192
  const phase = toAutoExecutePhase(action);
192
193
  const confidencePct = confidenceFromViolation(firstViolation?.code ?? null);
193
- const nextAction = evaluation.allowed
194
- ? {
195
- kind: 'info' as const,
196
- message: 'Gate en verde. Continúa con la implementación.',
197
- }
198
- : nextActionFromViolation(firstViolation, params.repoRoot);
194
+ const remediation = resolveAutoExecuteRemediation({
195
+ violation: firstViolation,
196
+ repoRoot: params.repoRoot,
197
+ stage,
198
+ allowed: evaluation.allowed,
199
+ });
200
+ const nextAction = remediation.nextAction;
199
201
 
200
202
  let message = toHumanMessage({
201
203
  action,
202
204
  confidencePct,
203
205
  reasonCode,
204
206
  });
205
- let instruction = nextAction.message;
207
+ let instruction = remediation.instruction;
206
208
  if (learningContext?.recommended_actions[0]) {
207
209
  message = `${message} Learning: ${learningContext.recommended_actions[0]}`;
208
210
  instruction = `${instruction} Learning: ${learningContext.recommended_actions[0]}`;
@@ -1,11 +1,14 @@
1
+ import {
2
+ resolveGovernanceCatalogAction,
3
+ type GovernanceCatalogNextAction,
4
+ } from '../gate/governanceActionCatalog';
1
5
  import { evaluateAiGate, type AiGateStage, type AiGateViolation } from '../gate/evaluateAiGate';
2
6
  import { collectWorktreeAtomicSlices } from '../git/worktreeAtomicSlices';
3
7
  import { resolveLearningContextExperimentalFeature } from '../policy/experimentalFeatures';
4
8
  import { readSddLearningContext, type SddLearningContext } from '../sdd/learningInsights';
5
9
 
6
10
  const ACTIONABLE_HINTS_BY_CODE: Readonly<Record<string, string>> = {
7
- EVIDENCE_MISSING:
8
- 'Ejecuta una auditoría (1/2/3/4 u opciones de motor 11–14) para regenerar .ai_evidence.json.',
11
+ EVIDENCE_MISSING: 'Ejecuta una auditoría (1/2/3/4) para regenerar .ai_evidence.json.',
9
12
  EVIDENCE_INVALID: 'Regenera .ai_evidence.json desde una opción de auditoría.',
10
13
  EVIDENCE_INTEGRITY_MISSING: 'Refresca evidencia para regenerar metadatos de integridad.',
11
14
  EVIDENCE_ACTIVE_RULE_IDS_EMPTY_FOR_CODE_CHANGES:
@@ -41,6 +44,18 @@ const ACTIONABLE_HINTS_BY_CODE: Readonly<Record<string, string>> = {
41
44
  GITFLOW_PROTECTED_BRANCH: 'Evita trabajo directo en ramas protegidas (usa feature/*).',
42
45
  };
43
46
 
47
+ const normalizeGovernanceCatalogCode = (code: string): string => {
48
+ switch (code) {
49
+ case 'EVIDENCE_INVALID':
50
+ case 'EVIDENCE_CHAIN_INVALID':
51
+ return 'EVIDENCE_INVALID_OR_CHAIN';
52
+ case 'GITFLOW_PROTECTED_BRANCH':
53
+ return 'GITFLOW_PROTECTED_BRANCH_CONTEXT';
54
+ default:
55
+ return code;
56
+ }
57
+ };
58
+
44
59
  const buildPreFlightHints = (params: {
45
60
  repoRoot: string;
46
61
  stage: AiGateStage;
@@ -115,6 +130,8 @@ export type EnterprisePreFlightCheckResult = {
115
130
  phase: 'GREEN' | 'RED';
116
131
  message: string;
117
132
  instruction: string;
133
+ reason_code: string;
134
+ next_action: GovernanceCatalogNextAction;
118
135
  stage: ReturnType<typeof evaluateAiGate>['stage'];
119
136
  policy: ReturnType<typeof evaluateAiGate>['policy'];
120
137
  violations: ReturnType<typeof evaluateAiGate>['violations'];
@@ -154,13 +171,30 @@ export const runEnterprisePreFlightCheck = (params: {
154
171
  upstream: evaluation.repo_state.git.upstream,
155
172
  learningContext,
156
173
  });
174
+ const firstViolation = evaluation.violations[0];
175
+ const reasonCode = firstViolation?.code ?? 'READY';
176
+ const governanceAction = firstViolation
177
+ ? resolveGovernanceCatalogAction({
178
+ code: normalizeGovernanceCatalogCode(firstViolation.code),
179
+ stage: evaluation.stage,
180
+ })
181
+ : resolveGovernanceCatalogAction({
182
+ code: 'READY',
183
+ stage: evaluation.stage,
184
+ });
157
185
  const phase: 'GREEN' | 'RED' = evaluation.allowed ? 'GREEN' : 'RED';
158
186
  const message = evaluation.allowed
159
187
  ? '✅ Pre-flight aprobado: puedes continuar con la implementación.'
160
- : `🔴 Pre-flight bloqueado: corrige ${evaluation.violations[0]?.code ?? 'la causa'} y vuelve a ejecutar.`;
161
- const instruction = evaluation.allowed
188
+ : `🔴 Pre-flight bloqueado: corrige ${reasonCode} y vuelve a ejecutar.`;
189
+ const instruction = !firstViolation && evaluation.allowed
162
190
  ? 'Implementa el cambio mínimo para pasar en verde y vuelve a validar.'
163
- : hints[0] ?? 'Corrige la causa bloqueante y vuelve a ejecutar el pre-flight.';
191
+ : governanceAction.instruction;
192
+ const nextAction: GovernanceCatalogNextAction = evaluation.allowed
193
+ ? {
194
+ kind: 'info',
195
+ message: governanceAction.next_action.message,
196
+ }
197
+ : governanceAction.next_action;
164
198
 
165
199
  return {
166
200
  tool: 'pre_flight_check',
@@ -173,6 +207,8 @@ export const runEnterprisePreFlightCheck = (params: {
173
207
  phase,
174
208
  message,
175
209
  instruction,
210
+ reason_code: reasonCode,
211
+ next_action: nextAction,
176
212
  stage: evaluation.stage,
177
213
  policy: evaluation.policy,
178
214
  violations: evaluation.violations,