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,197 @@
1
+ import { appendFileSync, mkdirSync } from 'node:fs';
2
+ import { dirname, isAbsolute, resolve } from 'node:path';
3
+
4
+ export type StructuredTelemetryEvent = {
5
+ schema_version: '1';
6
+ timestamp: string;
7
+ source: 'pumuki';
8
+ channel: 'ai_gate' | 'hook_gate';
9
+ event: 'ai_gate.evaluated' | 'hook_gate.evaluated';
10
+ stage: 'PRE_WRITE' | 'PRE_COMMIT' | 'PRE_PUSH' | 'CI';
11
+ repo_root: string;
12
+ status: 'ALLOWED' | 'BLOCKED';
13
+ decision: 'ALLOW' | 'BLOCK';
14
+ policy: {
15
+ source: string;
16
+ bundle: string;
17
+ hash: string;
18
+ version: string;
19
+ signature: string;
20
+ };
21
+ evidence: {
22
+ kind: string;
23
+ age_seconds: number | null;
24
+ max_age_seconds: number | null;
25
+ source: string | null;
26
+ path: string | null;
27
+ digest: string | null;
28
+ generated_at: string | null;
29
+ integrity_status: string | null;
30
+ chain_hash: string | null;
31
+ };
32
+ metrics: {
33
+ violations_total: number;
34
+ violations_error: number;
35
+ violations_warn: number;
36
+ };
37
+ mcp_receipt?: {
38
+ required: boolean;
39
+ kind: string;
40
+ age_seconds: number | null;
41
+ max_age_seconds: number | null;
42
+ };
43
+ waivers?: {
44
+ status: string;
45
+ applied: number;
46
+ path: string | null;
47
+ };
48
+ };
49
+
50
+ export type StructuredTelemetryEmitResult = {
51
+ status: 'disabled' | 'written' | 'error';
52
+ jsonl_path: string;
53
+ otel_jsonl_path: string | null;
54
+ error: string | null;
55
+ };
56
+
57
+ const DEFAULT_STRUCTURED_TELEMETRY_JSONL_PATH = '.pumuki/telemetry/structured-events.jsonl';
58
+ const DEFAULT_STRUCTURED_TELEMETRY_OTEL_JSONL_PATH = '.pumuki/telemetry/otel-log-records.jsonl';
59
+
60
+ const isTruthyEnvValue = (value?: string): boolean => {
61
+ if (!value) {
62
+ return false;
63
+ }
64
+ const normalized = value.trim().toLowerCase();
65
+ return normalized === '1' || normalized === 'true' || normalized === 'yes';
66
+ };
67
+
68
+ const resolveOutputPath = (
69
+ repoRoot: string,
70
+ value: string | undefined,
71
+ fallbackRelativePath: string
72
+ ): string => {
73
+ const raw = value?.trim();
74
+ if (!raw) {
75
+ return resolve(repoRoot, fallbackRelativePath);
76
+ }
77
+ if (isAbsolute(raw)) {
78
+ return raw;
79
+ }
80
+ return resolve(repoRoot, raw);
81
+ };
82
+
83
+ const toOtelSeverityNumber = (
84
+ status: StructuredTelemetryEvent['status']
85
+ ): number => (status === 'BLOCKED' ? 17 : 9);
86
+
87
+ const toOtelSeverityText = (
88
+ status: StructuredTelemetryEvent['status']
89
+ ): 'ERROR' | 'INFO' => (status === 'BLOCKED' ? 'ERROR' : 'INFO');
90
+
91
+ const toOtelLogRecord = (
92
+ event: StructuredTelemetryEvent
93
+ ): Record<string, unknown> => ({
94
+ timeUnixNano: String(
95
+ Math.max(0, Math.floor(Date.parse(event.timestamp))) * 1_000_000
96
+ ),
97
+ observedTimeUnixNano: String(Date.now() * 1_000_000),
98
+ severityNumber: toOtelSeverityNumber(event.status),
99
+ severityText: toOtelSeverityText(event.status),
100
+ body: event.event,
101
+ attributes: {
102
+ 'pumuki.schema_version': event.schema_version,
103
+ 'pumuki.channel': event.channel,
104
+ 'pumuki.event': event.event,
105
+ 'pumuki.stage': event.stage,
106
+ 'pumuki.status': event.status,
107
+ 'pumuki.decision': event.decision,
108
+ 'pumuki.repo_root': event.repo_root,
109
+ 'pumuki.policy.source': event.policy.source,
110
+ 'pumuki.policy.bundle': event.policy.bundle,
111
+ 'pumuki.policy.hash': event.policy.hash,
112
+ 'pumuki.policy.version': event.policy.version,
113
+ 'pumuki.policy.signature': event.policy.signature,
114
+ 'pumuki.evidence.kind': event.evidence.kind,
115
+ 'pumuki.evidence.age_seconds': event.evidence.age_seconds,
116
+ 'pumuki.evidence.max_age_seconds': event.evidence.max_age_seconds,
117
+ 'pumuki.evidence.source': event.evidence.source,
118
+ 'pumuki.evidence.path': event.evidence.path,
119
+ 'pumuki.evidence.digest': event.evidence.digest,
120
+ 'pumuki.evidence.generated_at': event.evidence.generated_at,
121
+ 'pumuki.evidence.integrity_status': event.evidence.integrity_status,
122
+ 'pumuki.evidence.chain_hash': event.evidence.chain_hash,
123
+ 'pumuki.metrics.violations_total': event.metrics.violations_total,
124
+ 'pumuki.metrics.violations_error': event.metrics.violations_error,
125
+ 'pumuki.metrics.violations_warn': event.metrics.violations_warn,
126
+ 'pumuki.mcp.required': event.mcp_receipt?.required ?? null,
127
+ 'pumuki.mcp.kind': event.mcp_receipt?.kind ?? null,
128
+ 'pumuki.mcp.age_seconds': event.mcp_receipt?.age_seconds ?? null,
129
+ 'pumuki.mcp.max_age_seconds': event.mcp_receipt?.max_age_seconds ?? null,
130
+ 'pumuki.waivers.status': event.waivers?.status ?? null,
131
+ 'pumuki.waivers.applied': event.waivers?.applied ?? null,
132
+ 'pumuki.waivers.path': event.waivers?.path ?? null,
133
+ },
134
+ resource: {
135
+ attributes: {
136
+ 'service.name': 'pumuki',
137
+ 'service.namespace': 'ast-intelligence-hooks',
138
+ 'service.version': event.policy.version || 'unknown',
139
+ },
140
+ },
141
+ });
142
+
143
+ export const emitStructuredTelemetry = (params: {
144
+ repoRoot: string;
145
+ event: StructuredTelemetryEvent;
146
+ env?: NodeJS.ProcessEnv;
147
+ }): StructuredTelemetryEmitResult => {
148
+ const env = params.env ?? process.env;
149
+ const jsonlPath = resolveOutputPath(
150
+ params.repoRoot,
151
+ env.PUMUKI_TELEMETRY_JSONL_PATH,
152
+ DEFAULT_STRUCTURED_TELEMETRY_JSONL_PATH
153
+ );
154
+ const otelEnabled = isTruthyEnvValue(env.PUMUKI_TELEMETRY_EXPORT_OTEL);
155
+ const otelPath = otelEnabled
156
+ ? resolveOutputPath(
157
+ params.repoRoot,
158
+ env.PUMUKI_TELEMETRY_OTEL_JSONL_PATH,
159
+ DEFAULT_STRUCTURED_TELEMETRY_OTEL_JSONL_PATH
160
+ )
161
+ : null;
162
+
163
+ if (!isTruthyEnvValue(env.PUMUKI_TELEMETRY_EXPORT)) {
164
+ return {
165
+ status: 'disabled',
166
+ jsonl_path: jsonlPath,
167
+ otel_jsonl_path: otelPath,
168
+ error: null,
169
+ };
170
+ }
171
+
172
+ try {
173
+ mkdirSync(dirname(jsonlPath), { recursive: true });
174
+ appendFileSync(jsonlPath, `${JSON.stringify(params.event)}\n`, 'utf8');
175
+ if (otelPath) {
176
+ mkdirSync(dirname(otelPath), { recursive: true });
177
+ appendFileSync(
178
+ otelPath,
179
+ `${JSON.stringify(toOtelLogRecord(params.event))}\n`,
180
+ 'utf8'
181
+ );
182
+ }
183
+ return {
184
+ status: 'written',
185
+ jsonl_path: jsonlPath,
186
+ otel_jsonl_path: otelPath,
187
+ error: null,
188
+ };
189
+ } catch (error) {
190
+ return {
191
+ status: 'error',
192
+ jsonl_path: jsonlPath,
193
+ otel_jsonl_path: otelPath,
194
+ error: error instanceof Error ? error.message : String(error),
195
+ };
196
+ }
197
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pumuki",
3
- "version": "6.3.26",
3
+ "version": "6.3.28",
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": {
@@ -13,7 +13,9 @@
13
13
  "pumuki-pre-push": "bin/pumuki-pre-push.js",
14
14
  "pumuki-ci": "bin/pumuki-ci.js",
15
15
  "pumuki-mcp-evidence": "bin/pumuki-mcp-evidence.js",
16
- "pumuki-mcp-enterprise": "bin/pumuki-mcp-enterprise.js"
16
+ "pumuki-mcp-evidence-stdio": "bin/pumuki-mcp-evidence-stdio.js",
17
+ "pumuki-mcp-enterprise": "bin/pumuki-mcp-enterprise.js",
18
+ "pumuki-mcp-enterprise-stdio": "bin/pumuki-mcp-enterprise-stdio.js"
17
19
  },
18
20
  "scripts": {
19
21
  "install-hooks": "node bin/pumuki.js install",
@@ -95,6 +97,14 @@
95
97
  "validation:phase8:ready-handoff": "bash scripts/build-phase8-ready-handoff-summary.sh",
96
98
  "validation:phase8:close-ready": "bash scripts/run-phase8-close-ready.sh",
97
99
  "validation:progress-single-active": "bash scripts/check-refactor-progress-single-active.sh",
100
+ "validation:tracking-single-active": "bash scripts/check-tracking-single-active.sh",
101
+ "validation:p9:ruralgo-branch-ready": "node --import tsx scripts/check-p9-ruralgo-branch-ready.ts",
102
+ "validation:p9:ruralgo-branch-ready:api": "npm run -s validation:p9:ruralgo-branch-ready -- --expected-branch=feature/p9-api-contact-contract --base-ref=origin/develop",
103
+ "validation:p9:ruralgo-branch-ready:ux": "npm run -s validation:p9:ruralgo-branch-ready -- --expected-branch=feature/p9-ux-contact-states --base-ref=origin/develop",
104
+ "validation:p9:ruralgo-runtime-ready": "node --import tsx scripts/check-p9-ruralgo-runtime-ready.ts",
105
+ "validation:p9:ruralgo-baseline-clean": "node --import tsx scripts/check-p9-ruralgo-baseline-clean.ts",
106
+ "validation:p9:ruralgo-install-health": "node --import tsx scripts/check-p9-ruralgo-install-health.ts",
107
+ "validation:p9:ruralgo-baseline-versioned": "node --import tsx scripts/check-p9-ruralgo-baseline-versioned.ts",
98
108
  "validation:phase5-escalation:ready-to-submit": "bash scripts/check-phase5-escalation-ready-to-submit.sh",
99
109
  "validation:phase5-escalation:prepare": "bash scripts/prepare-phase5-escalation-submission.sh",
100
110
  "validation:phase5-escalation:close-submission": "bash scripts/close-phase5-escalation-submission.sh",
@@ -106,12 +116,15 @@
106
116
  "validation:package-smoke:minimal": "node --import tsx scripts/package-install-smoke.ts --mode=minimal",
107
117
  "validation:lifecycle-smoke": "node --import tsx scripts/package-install-smoke.ts --mode=minimal",
108
118
  "validation:c020-benchmark": "node --import tsx scripts/run-c020-benchmark.ts",
119
+ "validation:p9:manifests": "node --import tsx scripts/build-p9-validation-manifests.ts",
109
120
  "validation:clean-artifacts": "npx --yes tsx@4.21.0 scripts/clean-validation-artifacts.ts",
110
121
  "skills:compile": "npx --yes tsx@4.21.0 scripts/compile-skills-lock.ts",
111
122
  "skills:lock:check": "npx --yes tsx@4.21.0 scripts/compile-skills-lock.ts --check",
112
123
  "skills:import:custom": "npx --yes tsx@4.21.0 scripts/import-custom-skills.ts",
113
124
  "mcp:evidence": "node bin/pumuki-mcp-evidence.js",
125
+ "mcp:evidence:stdio": "node bin/pumuki-mcp-evidence-stdio.js",
114
126
  "mcp:enterprise": "node bin/pumuki-mcp-enterprise.js",
127
+ "mcp:enterprise:stdio": "node bin/pumuki-mcp-enterprise-stdio.js",
115
128
  "validate:adapter-hooks-local": "echo 'Migrated to modern TS scripts — use validation:adapter-readiness instead'",
116
129
  "verify:adapter-hooks-runtime": "echo 'Migrated to modern TS scripts — use validation:adapter-readiness instead'"
117
130
  },
@@ -179,6 +192,7 @@
179
192
  },
180
193
  "files": [
181
194
  "bin/*.js",
195
+ "core/gate/*.js",
182
196
  "core/utils/*.ts",
183
197
  "core/facts/*.ts",
184
198
  "core/facts/detectors/**/*.ts",
@@ -189,14 +203,19 @@
189
203
  "integrations/config/*.ts",
190
204
  "integrations/evidence/*.ts",
191
205
  "integrations/gate/*.ts",
206
+ "integrations/git/*.js",
192
207
  "integrations/git/*.ts",
193
208
  "integrations/tdd/*.ts",
209
+ "integrations/lifecycle/*.js",
194
210
  "integrations/lifecycle/*.ts",
195
211
  "integrations/lifecycle/*.json",
212
+ "integrations/sdd/*.js",
196
213
  "integrations/sdd/*.ts",
214
+ "integrations/mcp/*.js",
197
215
  "integrations/mcp/*.ts",
198
216
  "integrations/notifications/*.ts",
199
217
  "integrations/platform/*.ts",
218
+ "integrations/telemetry/*.ts",
200
219
  "scripts/*.ts",
201
220
  "scripts/adapters/*.ts",
202
221
  "scripts/adapters/*.md",
@@ -220,12 +239,12 @@
220
239
  "exports": {
221
240
  ".": "./index.js",
222
241
  "./package.json": "./package.json",
223
- "./integrations/git": "./integrations/git/index.ts",
224
- "./integrations/lifecycle": "./integrations/lifecycle/index.ts",
225
- "./integrations/sdd": "./integrations/sdd/index.ts",
226
- "./integrations/mcp": "./integrations/mcp/index.ts",
227
- "./core/gate/evaluateGate": "./core/gate/evaluateGate.ts",
228
- "./core/gate/evaluateRules": "./core/gate/evaluateRules.ts"
242
+ "./integrations/git": "./integrations/git/index.js",
243
+ "./integrations/lifecycle": "./integrations/lifecycle/index.js",
244
+ "./integrations/sdd": "./integrations/sdd/index.js",
245
+ "./integrations/mcp": "./integrations/mcp/index.js",
246
+ "./core/gate/evaluateGate": "./core/gate/evaluateGate.js",
247
+ "./core/gate/evaluateRules": "./core/gate/evaluateRules.js"
229
248
  },
230
249
  "peerDependencies": {
231
250
  "ts-morph": ">=27.0.2"
@@ -0,0 +1,53 @@
1
+ import { resolve } from 'node:path';
2
+ import { writeP9ValidationManifests } from './p9-validation-manifests-lib';
3
+
4
+ type CliOptions = {
5
+ repoRoot: string;
6
+ outDir: string;
7
+ };
8
+
9
+ const parseArgs = (argv: ReadonlyArray<string>): CliOptions => {
10
+ const options: CliOptions = {
11
+ repoRoot: process.cwd(),
12
+ outDir: 'docs/validation/evidence',
13
+ };
14
+ for (const arg of argv) {
15
+ if (arg.startsWith('--repo-root=')) {
16
+ options.repoRoot = arg.slice('--repo-root='.length).trim() || options.repoRoot;
17
+ continue;
18
+ }
19
+ if (arg.startsWith('--out-dir=')) {
20
+ options.outDir = arg.slice('--out-dir='.length).trim() || options.outDir;
21
+ continue;
22
+ }
23
+ throw new Error(`Unsupported argument "${arg}". Use --repo-root=<path> or --out-dir=<path>.`);
24
+ }
25
+ return options;
26
+ };
27
+
28
+ const run = (): number => {
29
+ const options = parseArgs(process.argv.slice(2));
30
+ const repoRoot = resolve(options.repoRoot);
31
+ const outDir = resolve(repoRoot, options.outDir);
32
+ const result = writeP9ValidationManifests(repoRoot, outDir);
33
+ process.stdout.write(
34
+ JSON.stringify(
35
+ {
36
+ repoRoot,
37
+ outDir,
38
+ functionalityPath: result.functionalityPath,
39
+ rulesPath: result.rulesPath,
40
+ totals: {
41
+ functionality: result.functionality.totals,
42
+ rules: result.rules.totals,
43
+ },
44
+ },
45
+ null,
46
+ 2
47
+ ) + '\n'
48
+ );
49
+ return 0;
50
+ };
51
+
52
+ process.exit(run());
53
+
@@ -0,0 +1,200 @@
1
+ import { execFileSync } from 'node:child_process';
2
+ import { existsSync, readFileSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import process from 'node:process';
5
+ import { hasPumukiManagedBlock } from '../integrations/lifecycle/hookBlock';
6
+ import {
7
+ evaluateP9RuralgoBaselineClean,
8
+ type P9RuralgoBaselinePolicy,
9
+ } from './p9-ruralgo-baseline-clean-lib';
10
+
11
+ type CliOptions = {
12
+ repoPath: string;
13
+ json: boolean;
14
+ policy: P9RuralgoBaselinePolicy;
15
+ };
16
+
17
+ const DEFAULT_REPO_PATH =
18
+ '/Users/juancarlosmerlosalbarracin/Developer/Projects/ruralgo-fork';
19
+
20
+ const printHelp = (): void => {
21
+ process.stdout.write(
22
+ [
23
+ 'Uso:',
24
+ ' node --import tsx scripts/check-p9-ruralgo-baseline-clean.ts [opciones]',
25
+ '',
26
+ 'Opciones:',
27
+ ' --repo=<path> Ruta del repo ruralgo-fork (default: ~/Developer/Projects/ruralgo-fork)',
28
+ ' --allow-managed-hooks Permite hooks gestionados por Pumuki',
29
+ ' --allow-ai-evidence Permite .ai_evidence.json',
30
+ ' --allow-artifacts Permite artifacts/',
31
+ ' --allow-pumuki-dir Permite .pumuki/',
32
+ ' --allow-node-modules Permite node_modules/',
33
+ ' --allow-package-lock Permite package-lock.json',
34
+ ' --json Salida JSON',
35
+ ' --help Muestra ayuda',
36
+ '',
37
+ 'Exit code:',
38
+ ' 0 => baseline limpio para F1.T2',
39
+ ' 1 => baseline no limpio',
40
+ '',
41
+ ].join('\n'),
42
+ );
43
+ };
44
+
45
+ const parseArgs = (argv: ReadonlyArray<string>): CliOptions => {
46
+ const options: CliOptions = {
47
+ repoPath: DEFAULT_REPO_PATH,
48
+ json: false,
49
+ policy: {
50
+ requireNoManagedHooks: true,
51
+ requireNoAiEvidence: true,
52
+ requireNoArtifacts: true,
53
+ requireNoPumukiDir: true,
54
+ requireNoNodeModules: true,
55
+ requireNoPackageLock: true,
56
+ },
57
+ };
58
+
59
+ for (const arg of argv) {
60
+ if (arg === '--json') {
61
+ options.json = true;
62
+ continue;
63
+ }
64
+ if (arg === '--allow-managed-hooks') {
65
+ options.policy.requireNoManagedHooks = false;
66
+ continue;
67
+ }
68
+ if (arg === '--allow-ai-evidence') {
69
+ options.policy.requireNoAiEvidence = false;
70
+ continue;
71
+ }
72
+ if (arg === '--allow-artifacts') {
73
+ options.policy.requireNoArtifacts = false;
74
+ continue;
75
+ }
76
+ if (arg === '--allow-pumuki-dir') {
77
+ options.policy.requireNoPumukiDir = false;
78
+ continue;
79
+ }
80
+ if (arg === '--allow-node-modules') {
81
+ options.policy.requireNoNodeModules = false;
82
+ continue;
83
+ }
84
+ if (arg === '--allow-package-lock') {
85
+ options.policy.requireNoPackageLock = false;
86
+ continue;
87
+ }
88
+ if (arg.startsWith('--repo=')) {
89
+ options.repoPath = arg.slice('--repo='.length);
90
+ continue;
91
+ }
92
+ throw new Error(`Argumento no soportado: ${arg}`);
93
+ }
94
+
95
+ return options;
96
+ };
97
+
98
+ const runGit = (repoPath: string, args: ReadonlyArray<string>): string | null => {
99
+ try {
100
+ return execFileSync('git', ['-C', repoPath, ...args], {
101
+ encoding: 'utf8',
102
+ stdio: ['ignore', 'pipe', 'pipe'],
103
+ }).trim();
104
+ } catch {
105
+ return null;
106
+ }
107
+ };
108
+
109
+ const readHookManagedStatus = (hooksPath: string, hookName: string): boolean => {
110
+ const filePath = join(hooksPath, hookName);
111
+ if (!existsSync(filePath)) {
112
+ return false;
113
+ }
114
+ const contents = readFileSync(filePath, 'utf8');
115
+ return hasPumukiManagedBlock(contents);
116
+ };
117
+
118
+ const main = (): number => {
119
+ const argv = process.argv.slice(2);
120
+ if (argv.includes('--help')) {
121
+ printHelp();
122
+ return 0;
123
+ }
124
+
125
+ let options: CliOptions;
126
+ try {
127
+ options = parseArgs(argv);
128
+ } catch (error) {
129
+ process.stderr.write(`[p9][baseline-clean] ${(error as Error).message}\n`);
130
+ process.stderr.write('Usa --help para ver opciones.\n');
131
+ return 1;
132
+ }
133
+
134
+ const isGitRepo = runGit(options.repoPath, ['rev-parse', '--is-inside-work-tree']) === 'true';
135
+ const hooksPath =
136
+ runGit(options.repoPath, ['rev-parse', '--git-path', 'hooks']) ?? join(options.repoPath, '.git/hooks');
137
+ const preCommitManaged = isGitRepo ? readHookManagedStatus(hooksPath, 'pre-commit') : false;
138
+ const prePushManaged = isGitRepo ? readHookManagedStatus(hooksPath, 'pre-push') : false;
139
+
140
+ const result = evaluateP9RuralgoBaselineClean(
141
+ {
142
+ isGitRepo,
143
+ hasManagedPreCommitHook: preCommitManaged,
144
+ hasManagedPrePushHook: prePushManaged,
145
+ hasAiEvidenceFile: existsSync(join(options.repoPath, '.ai_evidence.json')),
146
+ hasArtifactsDir: existsSync(join(options.repoPath, 'artifacts')),
147
+ hasPumukiDir: existsSync(join(options.repoPath, '.pumuki')),
148
+ hasNodeModulesDir: existsSync(join(options.repoPath, 'node_modules')),
149
+ hasPackageLockFile: existsSync(join(options.repoPath, 'package-lock.json')),
150
+ },
151
+ options.policy,
152
+ );
153
+
154
+ const payload = {
155
+ ready: result.ready,
156
+ repoPath: options.repoPath,
157
+ policy: options.policy,
158
+ snapshot: {
159
+ isGitRepo,
160
+ hooksPath,
161
+ preCommitManaged,
162
+ prePushManaged,
163
+ hasAiEvidenceFile: existsSync(join(options.repoPath, '.ai_evidence.json')),
164
+ hasArtifactsDir: existsSync(join(options.repoPath, 'artifacts')),
165
+ hasPumukiDir: existsSync(join(options.repoPath, '.pumuki')),
166
+ hasNodeModulesDir: existsSync(join(options.repoPath, 'node_modules')),
167
+ hasPackageLockFile: existsSync(join(options.repoPath, 'package-lock.json')),
168
+ },
169
+ issues: result.issues,
170
+ };
171
+
172
+ if (options.json) {
173
+ process.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
174
+ } else {
175
+ process.stdout.write('[p9][baseline-clean] PRECHECK\n');
176
+ process.stdout.write(`[p9][baseline-clean] repo=${payload.repoPath}\n`);
177
+ process.stdout.write(`[p9][baseline-clean] hooks_path=${payload.snapshot.hooksPath}\n`);
178
+ process.stdout.write(
179
+ `[p9][baseline-clean] managed_hooks(pre-commit/pre-push)=${payload.snapshot.preCommitManaged}/${payload.snapshot.prePushManaged}\n`,
180
+ );
181
+ process.stdout.write(
182
+ `[p9][baseline-clean] ai_evidence=${payload.snapshot.hasAiEvidenceFile} artifacts=${payload.snapshot.hasArtifactsDir} .pumuki=${payload.snapshot.hasPumukiDir}\n`,
183
+ );
184
+ process.stdout.write(
185
+ `[p9][baseline-clean] node_modules=${payload.snapshot.hasNodeModulesDir} package_lock=${payload.snapshot.hasPackageLockFile}\n`,
186
+ );
187
+ process.stdout.write(
188
+ `[p9][baseline-clean] status=${payload.ready ? 'READY' : 'NOT_READY'} issues=${payload.issues.length}\n`,
189
+ );
190
+ for (const issue of payload.issues) {
191
+ process.stdout.write(
192
+ `[p9][baseline-clean][${issue.severity}] ${issue.code}: ${issue.message}\n`,
193
+ );
194
+ }
195
+ }
196
+
197
+ return payload.ready ? 0 : 1;
198
+ };
199
+
200
+ process.exitCode = main();