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,261 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import { existsSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import {
5
+ installedBinMarkerPath,
6
+ resolveSmokeLayout,
7
+ } from './pumuki-full-surface-smoke-lib';
8
+
9
+ type SmokeRow = {
10
+ id: string;
11
+ bin: string;
12
+ args: ReadonlyArray<string>;
13
+ timeoutMs: number;
14
+ allowNonZero?: boolean;
15
+ };
16
+
17
+ const layout = resolveSmokeLayout({
18
+ scriptFileUrl: import.meta.url,
19
+ env: process.env,
20
+ });
21
+
22
+ const { pumukiPackageRoot, smokeCwd, binStrategy, binRoot } = layout;
23
+ const node = process.execPath;
24
+ const binPath = (name: string): string => join(binRoot, 'bin', name);
25
+
26
+ const run = (row: SmokeRow): { code: number | null; signal: NodeJS.Signals | null; ms: number } => {
27
+ const started = Date.now();
28
+ const r = spawnSync(node, [binPath(row.bin), ...row.args], {
29
+ cwd: smokeCwd,
30
+ encoding: 'utf8',
31
+ timeout: row.timeoutMs,
32
+ maxBuffer: 50 * 1024 * 1024,
33
+ env: { ...process.env, FORCE_COLOR: '0' },
34
+ });
35
+ return { code: r.status, signal: r.signal, ms: Date.now() - started };
36
+ };
37
+
38
+ const rows: ReadonlyArray<SmokeRow> = [
39
+ { id: 'cli.help_implicit', bin: 'pumuki.js', args: [], timeoutMs: 15_000, allowNonZero: true },
40
+ { id: 'cli.help_explicit', bin: 'pumuki.js', args: ['--help'], timeoutMs: 15_000 },
41
+ {
42
+ id: 'cli.unknown_subcommand',
43
+ bin: 'pumuki.js',
44
+ args: ['__no_such_lifecycle_command__'],
45
+ timeoutMs: 15_000,
46
+ allowNonZero: true,
47
+ },
48
+ { id: 'doctor.json', bin: 'pumuki.js', args: ['doctor', '--json'], timeoutMs: 60_000 },
49
+ {
50
+ id: 'doctor.deep_json',
51
+ bin: 'pumuki.js',
52
+ args: ['doctor', '--deep', '--json'],
53
+ timeoutMs: 180_000,
54
+ allowNonZero: true,
55
+ },
56
+ { id: 'doctor.parity_json', bin: 'pumuki.js', args: ['doctor', '--parity', '--json'], timeoutMs: 120_000 },
57
+ { id: 'status.json', bin: 'pumuki.js', args: ['status', '--json'], timeoutMs: 90_000 },
58
+ {
59
+ id: 'audit.default_json',
60
+ bin: 'pumuki.js',
61
+ args: ['audit', '--json'],
62
+ timeoutMs: 300_000,
63
+ allowNonZero: true,
64
+ },
65
+ {
66
+ id: 'audit.pre_push_json',
67
+ bin: 'pumuki.js',
68
+ args: ['audit', '--stage=PRE_PUSH', '--json'],
69
+ timeoutMs: 300_000,
70
+ allowNonZero: true,
71
+ },
72
+ {
73
+ id: 'audit.ci_engine_json',
74
+ bin: 'pumuki.js',
75
+ args: ['audit', '--stage=CI', '--engine', '--json'],
76
+ timeoutMs: 300_000,
77
+ allowNonZero: true,
78
+ },
79
+ {
80
+ id: 'watch.once_json',
81
+ bin: 'pumuki.js',
82
+ args: [
83
+ 'watch',
84
+ '--once',
85
+ '--json',
86
+ '--stage=PRE_COMMIT',
87
+ '--scope=repo',
88
+ '--interval-ms=200',
89
+ '--no-notify',
90
+ ],
91
+ timeoutMs: 180_000,
92
+ },
93
+ {
94
+ id: 'watch.once_staged_json',
95
+ bin: 'pumuki.js',
96
+ args: [
97
+ 'watch',
98
+ '--once',
99
+ '--json',
100
+ '--stage=PRE_COMMIT',
101
+ '--scope=staged',
102
+ '--interval-ms=200',
103
+ '--no-notify',
104
+ ],
105
+ timeoutMs: 180_000,
106
+ allowNonZero: true,
107
+ },
108
+ { id: 'loop.list_json', bin: 'pumuki.js', args: ['loop', 'list', '--json'], timeoutMs: 30_000 },
109
+ {
110
+ id: 'loop.run_smoke',
111
+ bin: 'pumuki.js',
112
+ args: ['loop', 'run', '--objective=smoke-surface', '--max-attempts=1', '--json'],
113
+ timeoutMs: 300_000,
114
+ allowNonZero: true,
115
+ },
116
+ {
117
+ id: 'adapter.install_dry_repo_json',
118
+ bin: 'pumuki.js',
119
+ args: ['adapter', 'install', '--agent=repo', '--dry-run', '--json'],
120
+ timeoutMs: 30_000,
121
+ },
122
+ {
123
+ id: 'adapter.install_dry_codex_json',
124
+ bin: 'pumuki.js',
125
+ args: ['adapter', 'install', '--agent=codex', '--dry-run', '--json'],
126
+ timeoutMs: 30_000,
127
+ },
128
+ {
129
+ id: 'analytics.hotspots_report',
130
+ bin: 'pumuki.js',
131
+ args: ['analytics', 'hotspots', 'report', '--top=3', '--since-days=30', '--json'],
132
+ timeoutMs: 120_000,
133
+ allowNonZero: true,
134
+ },
135
+ {
136
+ id: 'analytics.hotspots_diagnose',
137
+ bin: 'pumuki.js',
138
+ args: ['analytics', 'hotspots', 'diagnose', '--json'],
139
+ timeoutMs: 120_000,
140
+ allowNonZero: true,
141
+ },
142
+ {
143
+ id: 'policy.reconcile_json',
144
+ bin: 'pumuki.js',
145
+ args: ['policy', 'reconcile', '--json'],
146
+ timeoutMs: 120_000,
147
+ allowNonZero: true,
148
+ },
149
+ {
150
+ id: 'policy.reconcile_strict_json',
151
+ bin: 'pumuki.js',
152
+ args: ['policy', 'reconcile', '--strict', '--json'],
153
+ timeoutMs: 120_000,
154
+ allowNonZero: true,
155
+ },
156
+ { id: 'sdd.status_json', bin: 'pumuki.js', args: ['sdd', 'status', '--json'], timeoutMs: 60_000 },
157
+ {
158
+ id: 'sdd.validate_pre_write_json',
159
+ bin: 'pumuki.js',
160
+ args: ['sdd', 'validate', '--stage=PRE_WRITE', '--json'],
161
+ timeoutMs: 120_000,
162
+ allowNonZero: true,
163
+ },
164
+ {
165
+ id: 'sdd.validate_precommit_json',
166
+ bin: 'pumuki.js',
167
+ args: ['sdd', 'validate', '--stage=PRE_COMMIT', '--json'],
168
+ timeoutMs: 120_000,
169
+ allowNonZero: true,
170
+ },
171
+ {
172
+ id: 'sdd.validate_prepush_json',
173
+ bin: 'pumuki.js',
174
+ args: ['sdd', 'validate', '--stage=PRE_PUSH', '--json'],
175
+ timeoutMs: 120_000,
176
+ allowNonZero: true,
177
+ },
178
+ {
179
+ id: 'sdd.validate_ci_json',
180
+ bin: 'pumuki.js',
181
+ args: ['sdd', 'validate', '--stage=CI', '--json'],
182
+ timeoutMs: 120_000,
183
+ allowNonZero: true,
184
+ },
185
+ { id: 'hook.pre_write', bin: 'pumuki-pre-write.js', args: [], timeoutMs: 300_000, allowNonZero: true },
186
+ { id: 'hook.pre_commit', bin: 'pumuki-pre-commit.js', args: [], timeoutMs: 300_000, allowNonZero: true },
187
+ { id: 'hook.pre_push', bin: 'pumuki-pre-push.js', args: [], timeoutMs: 300_000, allowNonZero: true },
188
+ { id: 'hook.ci', bin: 'pumuki-ci.js', args: [], timeoutMs: 300_000, allowNonZero: true },
189
+ ];
190
+
191
+ const main = (): number => {
192
+ if (binStrategy === 'installed') {
193
+ const marker = installedBinMarkerPath(layout);
194
+ if (!existsSync(marker)) {
195
+ process.stderr.write(
196
+ `[pumuki] smoke: PUMUKI_SMOKE_BIN_STRATEGY=installed pero no existe ${marker}. ` +
197
+ 'Ejecuta npm install en el consumidor o revisa PUMUKI_SMOKE_REPO_ROOT.\n'
198
+ );
199
+ return 2;
200
+ }
201
+ }
202
+
203
+ const lines: string[] = [];
204
+ lines.push('# Pumuki superficie — smoke');
205
+ lines.push('');
206
+ lines.push(`- binStrategy: \`${binStrategy}\` (PUMUKI_SMOKE_BIN_STRATEGY=source|installed)`);
207
+ lines.push(`- binRoot: \`${binRoot}\``);
208
+ lines.push(`- pumukiPackageRoot (script host): \`${pumukiPackageRoot}\``);
209
+ lines.push(`- smokeCwd (gate/doctor cwd): \`${smokeCwd}\``);
210
+ lines.push(`- node: \`${node}\``);
211
+ lines.push(`- filas: **${rows.length}** (incluye CLI, doctor/status, audit×3, watch×2, loop, adapter×2, analytics×2, policy×2, sdd×5, hooks×4)`);
212
+ lines.push('');
213
+ lines.push('| id | exit | ms | ok |');
214
+ lines.push('|----|------|----|----|');
215
+ let failed = 0;
216
+ for (const row of rows) {
217
+ const { code, signal, ms } = run(row);
218
+ const exit = signal ? `signal:${signal}` : String(code ?? 'null');
219
+ const ok =
220
+ signal !== null
221
+ ? false
222
+ : row.allowNonZero
223
+ ? true
224
+ : code === 0;
225
+ if (!ok) {
226
+ failed += 1;
227
+ }
228
+ lines.push(`| ${row.id} | ${exit} | ${ms} | ${ok ? 'yes' : 'no'} |`);
229
+ }
230
+ lines.push('');
231
+ lines.push('## Interpretación de exit codes');
232
+ lines.push('');
233
+ lines.push('- `audit` devuelve el **mismo código que el gate** del alcance auditado (p. ej. 1 si hay `BLOCK` en el repo). Eso es correcto, no indica CLI roto.');
234
+ lines.push('- `doctor --deep` puede devolver **1** si `doctorHasBlockingIssues` (issues `error` o `deep.blocking`) o mismatch de parity esperado.');
235
+ lines.push('- Los **hooks** (`pre-commit`, etc.) reflejan el gate sobre el estado actual del worktree; 1 con cambios locales o rama protegida es esperable.');
236
+ lines.push('- `sdd validate` y `policy reconcile --strict` pueden devolver **1** según política del repo; el smoke los marca con `allowNonZero`.');
237
+ lines.push('');
238
+ lines.push('## No cubierto aquí (manual o destructivo)');
239
+ lines.push('');
240
+ lines.push('- `pumuki install|uninstall|remove|update|bootstrap` (mutan hooks/artefactos).');
241
+ lines.push('- `pumuki framework` / menú interactivo (`pumuki-framework.js`).');
242
+ lines.push('- Servidores MCP stdio (`pumuki-mcp-*-stdio.js`): esperan JSON-RPC por stdin; probar con cliente MCP.');
243
+ lines.push('- `pumuki sdd session|sync|learn|evidence|state-sync|auto-sync`: requieren ids / evidencia / cambios reales.');
244
+ lines.push('- `pumuki doctor|status --remote-checks`: dependen de red / GitHub.');
245
+ lines.push('');
246
+ lines.push('## Otro repo (consumidor)');
247
+ lines.push('');
248
+ lines.push(
249
+ '- **Bins del tree pumuki (desarrollo):** `PUMUKI_SMOKE_REPO_ROOT=/ruta/al/repo npm run -s smoke:pumuki-surface` desde la raíz de **pumuki** (`PUMUKI_SMOKE_BIN_STRATEGY=source` por defecto).'
250
+ );
251
+ lines.push(
252
+ '- **Bins instalados en el consumidor (`node_modules/pumuki`):** `PUMUKI_SMOKE_REPO_ROOT=/ruta/al/repo PUMUKI_SMOKE_BIN_STRATEGY=installed npm run -s smoke:pumuki-surface` o `npm run -s smoke:pumuki-surface-installed` (exige `PUMUKI_SMOKE_REPO_ROOT`).'
253
+ );
254
+ lines.push('');
255
+ lines.push(`**Resumen:** ${failed} filas con fallo estricto (sin allowNonZero).`);
256
+ process.stdout.write(lines.join('\n'));
257
+ process.stdout.write('\n');
258
+ return failed > 0 ? 1 : 0;
259
+ };
260
+
261
+ process.exitCode = main();
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const { spawnSync } = require('node:child_process');
5
+ const { join } = require('node:path');
6
+
7
+ if (!process.env.PUMUKI_SMOKE_REPO_ROOT || !String(process.env.PUMUKI_SMOKE_REPO_ROOT).trim()) {
8
+ console.error(
9
+ '[pumuki] smoke:pumuki-surface-installed requires PUMUKI_SMOKE_REPO_ROOT (absolute path to consumer repo).'
10
+ );
11
+ process.exit(1);
12
+ }
13
+
14
+ const root = join(__dirname, '..');
15
+ const env = {
16
+ ...process.env,
17
+ PUMUKI_SMOKE_BIN_STRATEGY: 'installed',
18
+ };
19
+
20
+ const result = spawnSync(
21
+ 'npx',
22
+ ['--yes', 'tsx@4.21.0', 'scripts/pumuki-full-surface-smoke.ts'],
23
+ {
24
+ cwd: root,
25
+ env,
26
+ stdio: 'inherit',
27
+ }
28
+ );
29
+
30
+ const code = typeof result.status === 'number' ? result.status : 1;
31
+ process.exitCode = code;
@@ -0,0 +1,200 @@
1
+ import { mkdirSync, writeFileSync } from 'node:fs';
2
+ import { dirname, resolve } from 'node:path';
3
+
4
+ type RuralGoS1EvidenceEntry = {
5
+ title: string;
6
+ mode: 'shell' | 'mcp';
7
+ command: string;
8
+ capture: ReadonlyArray<string>;
9
+ expectedFragments: ReadonlyArray<string>;
10
+ incs: ReadonlyArray<string>;
11
+ };
12
+
13
+ export type RuralGoS1EvidencePackOptions = {
14
+ cwd: string;
15
+ consumerRoot: string;
16
+ packageVersion: string;
17
+ generatedAt: string;
18
+ };
19
+
20
+ const resolvePumukiPackageSelector = (packageVersion: string): string => {
21
+ const trimmed = packageVersion.trim();
22
+ const isStableSemver = /^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/.test(trimmed);
23
+ if (isStableSemver) {
24
+ return `pumuki@${trimmed}`;
25
+ }
26
+ return 'pumuki@latest';
27
+ };
28
+
29
+ const EVIDENCE_ENTRIES: ReadonlyArray<RuralGoS1EvidenceEntry> = [
30
+ {
31
+ title: 'Lifecycle status',
32
+ mode: 'shell',
33
+ command: 'npm run pumuki:status',
34
+ capture: [
35
+ 'Bloque `governance truth` completo.',
36
+ 'Indicadores de contrato efectivo, rama y skills surface.',
37
+ ],
38
+ expectedFragments: [
39
+ 'governance truth',
40
+ 'governance_effective',
41
+ 'contract_surface',
42
+ 'current_branch',
43
+ ],
44
+ incs: ['PUMUKI-INC-070', 'PUMUKI-INC-071', 'PUMUKI-INC-073', 'PUMUKI-INC-076'],
45
+ },
46
+ {
47
+ title: 'Lifecycle doctor',
48
+ mode: 'shell',
49
+ command: 'npm run pumuki:doctor',
50
+ capture: [
51
+ 'Veredicto humano final.',
52
+ 'Bloque `governance truth` con `next_action` visible.',
53
+ ],
54
+ expectedFragments: [
55
+ 'governance truth',
56
+ 'next_action',
57
+ 'reason_code',
58
+ 'WARN',
59
+ ],
60
+ incs: ['PUMUKI-INC-070', 'PUMUKI-INC-071', 'PUMUKI-INC-073'],
61
+ },
62
+ {
63
+ title: 'PRE_WRITE canónico',
64
+ mode: 'shell',
65
+ command: 'npx --yes --package pumuki@latest pumuki sdd validate --stage=PRE_WRITE --json',
66
+ capture: [
67
+ 'Salida JSON completa.',
68
+ 'Campos de session/mode y remediación inmediata.',
69
+ ],
70
+ expectedFragments: [
71
+ '"stage":"PRE_WRITE"',
72
+ '"decision"',
73
+ '"next_action"',
74
+ ],
75
+ incs: ['PUMUKI-INC-070', 'PUMUKI-INC-072'],
76
+ },
77
+ {
78
+ title: 'Hook pre-commit / gate',
79
+ mode: 'shell',
80
+ command: 'git commit --allow-empty -m "test: pumuki s1 validation"',
81
+ capture: [
82
+ 'Bloque de gate con `reason_code`, `instruction` y `next_action`.',
83
+ 'Si bloquea, conservar `NEXT:` y `REMEDIATION:`.',
84
+ ],
85
+ expectedFragments: [
86
+ 'reason_code=',
87
+ 'instruction=',
88
+ 'next_action=',
89
+ ],
90
+ incs: ['PUMUKI-INC-071', 'PUMUKI-INC-073', 'PUMUKI-INC-076'],
91
+ },
92
+ {
93
+ title: 'MCP pre_flight_check',
94
+ mode: 'mcp',
95
+ command: 'mcp::pre_flight_check(stage=PRE_WRITE)',
96
+ capture: [
97
+ 'Payload completo de `result`.',
98
+ 'Campos `reason_code`, `instruction`, `next_action`, `hints`.',
99
+ ],
100
+ expectedFragments: [
101
+ 'reason_code',
102
+ 'instruction',
103
+ 'next_action',
104
+ 'hints',
105
+ ],
106
+ incs: ['PUMUKI-INC-071', 'PUMUKI-INC-072', 'PUMUKI-INC-073'],
107
+ },
108
+ {
109
+ title: 'MCP auto_execute_ai_start',
110
+ mode: 'mcp',
111
+ command: 'mcp::auto_execute_ai_start(stage=PRE_WRITE)',
112
+ capture: [
113
+ 'Payload completo de `result`.',
114
+ 'Campos `action`, `reason_code`, `next_action`, `confidence_pct`.',
115
+ ],
116
+ expectedFragments: [
117
+ 'action',
118
+ 'reason_code',
119
+ 'next_action',
120
+ 'confidence_pct',
121
+ ],
122
+ incs: ['PUMUKI-INC-071', 'PUMUKI-INC-073', 'PUMUKI-INC-076'],
123
+ },
124
+ ];
125
+
126
+ const renderEntry = (params: {
127
+ entry: RuralGoS1EvidenceEntry;
128
+ consumerRoot: string;
129
+ }): string => {
130
+ const command = params.entry.mode === 'shell'
131
+ ? `cd ${params.consumerRoot} && ${params.entry.command}`
132
+ : params.entry.command;
133
+
134
+ return [
135
+ `### ${params.entry.title}`,
136
+ '',
137
+ `- mode: ${params.entry.mode}`,
138
+ `- command: \`${command}\``,
139
+ `- incs: ${params.entry.incs.join(', ')}`,
140
+ '- capture:',
141
+ ...params.entry.capture.map((item) => ` - ${item}`),
142
+ '- expected_fragments:',
143
+ ...params.entry.expectedFragments.map((item) => ` - ${item}`),
144
+ '',
145
+ ].join('\n');
146
+ };
147
+
148
+ export const buildRuralGoS1EvidencePackMarkdown = (
149
+ options: RuralGoS1EvidencePackOptions
150
+ ): string => {
151
+ const pumukiPackageSelector = resolvePumukiPackageSelector(options.packageVersion);
152
+ const evidenceEntries: ReadonlyArray<RuralGoS1EvidenceEntry> = EVIDENCE_ENTRIES.map((entry) =>
153
+ entry.title === 'PRE_WRITE canónico'
154
+ ? {
155
+ ...entry,
156
+ command: `npx --yes --package ${pumukiPackageSelector} pumuki sdd validate --stage=PRE_WRITE --json`,
157
+ }
158
+ : entry
159
+ );
160
+
161
+ return [
162
+ '# RuralGo S1 Evidence Pack',
163
+ '',
164
+ `- generated_at: ${options.generatedAt}`,
165
+ `- consumer_root: \`${options.consumerRoot}\``,
166
+ `- package_version: ${options.packageVersion}`,
167
+ '- objective: validar S1 contra PUMUKI-INC-071/073/076 y reunir soporte adicional para 070/072.',
168
+ '',
169
+ '## Uso',
170
+ '',
171
+ '- Ejecuta los comandos shell desde el consumer real tras repinear la semver publicada.',
172
+ '- Captura las respuestas MCP desde una sesión conectada al servidor enterprise.',
173
+ '- No muevas un INC a `FIXED` si falta convergencia entre lifecycle, hooks y MCP.',
174
+ '',
175
+ ...evidenceEntries.map((entry) =>
176
+ renderEntry({
177
+ entry,
178
+ consumerRoot: options.consumerRoot,
179
+ })
180
+ ),
181
+ '## Criterio rápido de cierre',
182
+ '',
183
+ '- `PUMUKI-INC-071`: candidato a FIXED si lifecycle, hooks y MCP exponen contrato efectivo del repo.',
184
+ '- `PUMUKI-INC-073`: candidato a FIXED si el verde parcial desaparece y se ve governance real.',
185
+ '- `PUMUKI-INC-076`: candidato a FIXED si hooks y surfaces muestran GitFlow/naming como parte del gate.',
186
+ '- `PUMUKI-INC-072`: no cerrar salvo que el pre-edit gate aparezca de forma homogénea y automática.',
187
+ '',
188
+ ].join('\n');
189
+ };
190
+
191
+ export const writeRuralGoS1EvidencePack = (params: {
192
+ cwd: string;
193
+ outFile: string;
194
+ markdown: string;
195
+ }): string => {
196
+ const outputPath = resolve(params.cwd, params.outFile);
197
+ mkdirSync(dirname(outputPath), { recursive: true });
198
+ writeFileSync(outputPath, params.markdown, 'utf8');
199
+ return outputPath;
200
+ };
@@ -1,140 +0,0 @@
1
- import type { EnterpriseEvidenceSeverity } from './framework-menu-evidence-summary-types';
2
- import type { FrameworkMenuEvidenceSummary } from './framework-menu-evidence-summary-lib';
3
- import { renderLegacyPanel, resolveLegacyPanelOuterWidth } from './framework-menu-legacy-audit-lib';
4
- import { applyCliPalette, buildCliDesignTokens } from './framework-menu-ui-components-lib';
5
- import type { CliPaletteRole } from './framework-menu-ui-components-types';
6
-
7
- const enterpriseSeverityToRole = (severity: EnterpriseEvidenceSeverity): CliPaletteRole => {
8
- if (severity === 'CRITICAL') {
9
- return 'critical';
10
- }
11
- if (severity === 'HIGH') {
12
- return 'high';
13
- }
14
- if (severity === 'MEDIUM') {
15
- return 'medium';
16
- }
17
- return 'low';
18
- };
19
-
20
- const outcomeToRole = (outcome: string | null): CliPaletteRole => {
21
- const normalized = (outcome ?? '').trim().toUpperCase();
22
- if (normalized === 'BLOCK' || normalized === 'BLOCKED') {
23
- return 'critical';
24
- }
25
- if (normalized === 'WARN') {
26
- return 'statusWarning';
27
- }
28
- if (normalized === 'PASS' || normalized === 'ALLOW' || normalized === 'ALLOWED') {
29
- return 'goal';
30
- }
31
- return 'sectionTitle';
32
- };
33
-
34
- export const formatVintageEvidenceReportLines = (
35
- summary: FrameworkMenuEvidenceSummary,
36
- tokens: ReturnType<typeof buildCliDesignTokens>
37
- ): string[] => {
38
- if (summary.status !== 'ok') {
39
- return [
40
- applyCliPalette('PUMUKI — Classic evidence view', 'title', tokens),
41
- applyCliPalette(
42
- summary.status === 'missing'
43
- ? 'No .ai_evidence.json — run an engine audit (1, 11–14) first.'
44
- : 'Invalid .ai_evidence.json — regenerate from a gate run.',
45
- 'muted',
46
- tokens
47
- ),
48
- ];
49
- }
50
-
51
- const ent = summary.byEnterpriseSeverity ?? {
52
- CRITICAL: summary.bySeverity.CRITICAL,
53
- HIGH: summary.bySeverity.ERROR,
54
- MEDIUM: summary.bySeverity.WARN,
55
- LOW: summary.bySeverity.INFO,
56
- };
57
-
58
- const lines: string[] = [
59
- applyCliPalette('PUMUKI — Classic evidence view (severities + platforms)', 'title', tokens),
60
- '',
61
- `${applyCliPalette('Stage', 'sectionTitle', tokens)}: ${summary.stage ?? 'unknown'} ${applyCliPalette('Outcome', 'sectionTitle', tokens)}: ${applyCliPalette(String(summary.outcome ?? 'unknown'), outcomeToRole(summary.outcome), tokens)}`,
62
- `${applyCliPalette('Findings', 'sectionTitle', tokens)}: ${summary.totalFindings} ${applyCliPalette('Scanned', 'muted', tokens)}: ${summary.filesScanned} ${applyCliPalette('Affected', 'muted', tokens)}: ${summary.filesAffected}`,
63
- '',
64
- applyCliPalette('Enterprise severities', 'sectionTitle', tokens),
65
- ];
66
-
67
- const order: EnterpriseEvidenceSeverity[] = ['CRITICAL', 'HIGH', 'MEDIUM', 'LOW'];
68
- for (const band of order) {
69
- const count = ent[band] ?? 0;
70
- if (count === 0) {
71
- continue;
72
- }
73
- lines.push(
74
- ` ${applyCliPalette(`${band}:`, enterpriseSeverityToRole(band), tokens)} ${count}`
75
- );
76
- }
77
-
78
- lines.push('', applyCliPalette('Legacy severities', 'sectionTitle', tokens));
79
- lines.push(
80
- ` ${applyCliPalette('CRITICAL', 'critical', tokens)} ${summary.bySeverity.CRITICAL} ` +
81
- `${applyCliPalette('ERROR', 'high', tokens)} ${summary.bySeverity.ERROR} ` +
82
- `${applyCliPalette('WARN', 'medium', tokens)} ${summary.bySeverity.WARN} ` +
83
- `${applyCliPalette('INFO', 'low', tokens)} ${summary.bySeverity.INFO}`
84
- );
85
-
86
- if (summary.platformAuditRows && summary.platformAuditRows.length > 0) {
87
- lines.push('', applyCliPalette('Platforms (from evidence snapshot)', 'sectionTitle', tokens));
88
- for (const row of summary.platformAuditRows) {
89
- lines.push(
90
- ` ${applyCliPalette(row.platform, 'rule', tokens)}: ${row.violations} violations`
91
- );
92
- }
93
- lines.push(
94
- applyCliPalette(
95
- 'Note: platform buckets are heuristic (path + ruleId); "Other" is not absence of iOS/Android work.',
96
- 'muted',
97
- tokens
98
- )
99
- );
100
- }
101
-
102
- lines.push('', applyCliPalette('Top violations (ranked)', 'sectionTitle', tokens));
103
- if (summary.topFindings.length === 0) {
104
- lines.push(applyCliPalette(' (none)', 'goal', tokens));
105
- } else {
106
- for (const finding of summary.topFindings) {
107
- const role = enterpriseSeverityToRole(finding.severity);
108
- lines.push(
109
- ` ${applyCliPalette(`[${finding.severity}]`, role, tokens)} ${finding.ruleId}`
110
- );
111
- lines.push(
112
- ` ${applyCliPalette(`${finding.file}:${finding.line}`, 'muted', tokens)}`
113
- );
114
- }
115
- }
116
-
117
- return lines;
118
- };
119
-
120
- export const renderVintageEvidenceReport = (params: {
121
- write: (text: string) => void;
122
- summary: FrameworkMenuEvidenceSummary;
123
- useColor: () => boolean;
124
- }): void => {
125
- if (process.env.PUMUKI_MENU_VINTAGE_REPORT === '0') {
126
- return;
127
- }
128
- const tokens = buildCliDesignTokens({
129
- width: resolveLegacyPanelOuterWidth(),
130
- color: params.useColor(),
131
- });
132
-
133
- const lines = formatVintageEvidenceReportLines(params.summary, tokens);
134
- params.write(
135
- `\n${renderLegacyPanel(lines, {
136
- width: resolveLegacyPanelOuterWidth(),
137
- color: params.useColor(),
138
- })}\n`
139
- );
140
- };